A Python library for practical quantum

Build quantum algorithms once.
Run them anywhere.

Write a quantum circuit in Python. Run it on a simulator on your laptop or on a real quantum processor in the cloud. Switching between the two is one argument, and the rest of your code stays the same.

$ pip install eigenkit
8Simulators1NumPy>>> circuit = Circuit(spec, observables=obs,... provider="numpy")Built-in simulator, adjoint gradients, no extra install2PennyLane>>> circuit = Circuit(spec, observables=obs,... provider="pennylane")PennyLane's own differentiation3Qiskit>>> circuit = Circuit(spec, observables=obs,... provider="qiskit")Same circuit, same gradients, different backend4Qiskit Aer>>> circuit = Circuit(spec, observables=obs,... provider="qiskit_aer")Matrix-product-state method leads at 24 qubits5Qulacs>>> circuit = Circuit(spec, observables=obs,... provider="qulacs")Adjoint gradients. 9.2× NumPy on VQE and QAOA6qsim>>> circuit = Circuit(spec, observables=obs,... provider="qsim")Fastest single forward pass at 20 qubits7TensorCircuit>>> circuit = Circuit(spec, observables=obs,... provider="tensorcircuit")Adjoint gradients. 2.6× NumPy on hybrid model training8CUDA-Q>>> circuit = Circuit(spec, observables=obs,... provider="cudaq")Same circuit, same gradients, different backend4Quantum hardware9IBM>>> circuit = Circuit(spec, observables=obs,... provider=QiskitRuntimeConfig(backend_name="ibm_kingston"))Billable: CostWarning at compile time, dry_run_info() before submit10IQM>>> circuit = Circuit(spec, observables=obs,... provider=IQMConfig(quantum_computer="emerald"))IQM Resonance. Billable: CostWarning at compile time, dry_run_info() before submit11Amazon Braket>>> circuit = Circuit(spec, observables=obs,... provider="braket")Billable: CostWarning at compile time, dry_run_info() before submit12Quandela Perceval>>> circuit = Circuit(spec, observables=obs,... provider="perceval")Linear-optics module, locally or through Quandela Cloudeigenkit
  • Simulators8
  • Quantum hardware4

From one circuit to every backend

Only the provider argument changes.

1

Write the circuit once

Gate by gate, from ready blocks or inside an estimator. Every route produces the same internal circuit, and the rest of the workflow runs on it.

  • Gate by gate
  • Blocks
  • Estimators
from eigenkit import CircuitBuilder, Z

edges = [(0, 1), (1, 2), (2, 3), (3, 0)]
qc = CircuitBuilder(n_qubits=4)
for q in range(4):
    qc.h(q)
for (i, j), angle in zip(edges, [0.7, 1.1, 0.4, 1.9]):
    qc.pauli_exponential((("Z", i), ("Z", j)), angle)
for q in range(4):
    qc.h(q)

spec = qc.build()
obs = [Z(0) * Z(1), Z(1) * Z(2)]
2

Run it on any simulator

A provider name is all it takes. The training loop and the gradients stay the same on every simulator.

  • NumPy
  • Qulacs
  • TensorCircuit
  • qsim
from eigenkit import Circuit

for provider in ["numpy", "qulacs", "tensorcircuit", "qsim"]:
    circuit = Circuit(spec, observables=obs, provider=provider)
    print(provider, circuit([]))

# same circuit, same gradients, four simulators
3

Send it to quantum hardware

Replace the name with a hardware config. Paid devices raise a CostWarning, and the job can be previewed before anything is submitted.

  • IBM Qiskit Runtime
  • IQM Resonance
  • Amazon Braket
  • Quandela Perceval
from eigenkit import Circuit, IQMConfig, QiskitRuntimeConfig

ibm = QiskitRuntimeConfig(backend_name="ibm_kingston")
iqm = IQMConfig(quantum_computer="emerald")

circuit = Circuit(spec, observables=obs, provider=ibm)
info = circuit.dry_run_info()

CostWarning: Qiskit Runtime compile against 'ibm_kingston' via estimator
4

Check it against classical

Quantum and classical models run on the same data over several seeds. A significance test shows whether the difference is real.

  • Classical baselines
  • Several seeds
  • Wilcoxon test
from sklearn.svm import SVC
from eigenkit import QSVC
from eigenkit.benchmark import compare

result = compare(models=[QSVC(), SVC()], dataset="moons", n_seeds=10)
print(result.table(show_significance=True))

# mean ± std across seeds, * and ** mark p < 0.05 and p < 0.01
from eigenkit import CircuitBuilder, Z

edges = [(0, 1), (1, 2), (2, 3), (3, 0)]
qc = CircuitBuilder(n_qubits=4)
for q in range(4):
    qc.h(q)
for (i, j), angle in zip(edges, [0.7, 1.1, 0.4, 1.9]):
    qc.pauli_exponential((("Z", i), ("Z", j)), angle)
for q in range(4):
    qc.h(q)

spec = qc.build()
obs = [Z(0) * Z(1), Z(1) * Z(2)]
from eigenkit import Circuit

for provider in ["numpy", "qulacs", "tensorcircuit", "qsim"]:
    circuit = Circuit(spec, observables=obs, provider=provider)
    print(provider, circuit([]))

# same circuit, same gradients, four simulators
from eigenkit import Circuit, IQMConfig, QiskitRuntimeConfig

ibm = QiskitRuntimeConfig(backend_name="ibm_kingston")
iqm = IQMConfig(quantum_computer="emerald")

circuit = Circuit(spec, observables=obs, provider=ibm)
info = circuit.dry_run_info()

CostWarning: Qiskit Runtime compile against 'ibm_kingston' via estimator
from sklearn.svm import SVC
from eigenkit import QSVC
from eigenkit.benchmark import compare

result = compare(models=[QSVC(), SVC()], dataset="moons", n_seeds=10)
print(result.table(show_significance=True))

# mean ± std across seeds, * and ** mark p < 0.05 and p < 0.01

One circuit, 12 providers

To switch to another simulator or a quantum processor, you change one argument. The circuit, the training loop and the gradients stay as they are.

3domains
Your code3 domains
Machine learningOptimizationChemistry
1interface
eigenkitOne interface for every backendproviderNumPy
12providers
Simulators8
NumPyPennyLaneQiskitQiskit AerQulacsqsimTensorCircuitCUDA-Q
Hardware4
IBM Qiskit
Runtime
IQM
Resonance
Amazon
Braket
Quandela
Perceval
6QPU vendors
QPU vendors6
IBMIQMIonQRigettiAQTQuandela

What the library does

with every circuit

1

Gradients on every backend

Native where a provider has them, parameter-shift everywhere else, through the same interface.

AdjointNumPyQulacsTensorCircuit
Own autodiffPennyLane
Parameter-shiftQiskitQiskit AerqsimCUDA-Q
Hardwareenable_gradients=Trueopt-in
2

Works with your ML stack

Circuits as PyTorch layers, models as scikit-learn estimators.

  • PyTorchnn.Module
  • scikit-learnfit, predict
  • NumPydefault
Dilution refrigerator of a quantum processor
3

Ready-made algorithms

Common quantum algorithms ship ready to use, so you can start from a working model.

Machine learningHybrid neural networksQSVCQSVRReservoir computing
OptimizationQAOAQUBOMax-Cut
ChemistryVQEUCCSD
GeneralHHLIQAEVaR and CVaR
4

Benchmarks against classical

Quantum and classical models on the same data across several seeds. A Wilcoxon test marks significant differences.

>>> compare(models, dataset="iris", n_seeds=10)
  • Classical baselines
  • 12 registered datasets
  • Synthetic generators
  • Dataset reports
5

Low overhead

Only a few percent of added overhead per call, where distinguishable from noise. Compilation is paid once per circuit.

There is no universally best backend

The fastest simulator for one task can be the slowest for another, and the same holds for quantum processors. With eigenkit, switching takes one argument.

No simulator is fastest everywhere

Speed relative to the built-in NumPy simulator (1×), log scale

1×NumPy10×0.03×
0.49×
2.6×
1.8×
QulacsTensorCircuitPennyLane default

Circuit size changes the ranking

Fastest single forward pass

Qulacs, TensorCircuit
qsim
Aer MPS
4 qubits
12
20
24

On hardware, circuit topology picks the processor

Same code on both devices, 8000 shots each

Two-qubit gates after routing, 54-qubit grid
IQM Emerald
0
IBM Heron
0
Match with ideal values, 1 is ideal
Square grid, 54 qubitsIQM 0.00, IBM 0.00
Heavy-hex, 54 qubitsIBM 0.00, IQM 0.00

Write the circuit once,
choose the backend later

The base install includes the built-in simulator, optimization and chemistry. PyTorch, scikit-learn and quantum SDKs are optional extras.

$ pip install eigenkit
Add extras:

Python 3.12, 3.13 and 3.14. Bug reports, ideas and pull requests are welcome.