Skip to content

Providers

Every entry point that builds a circuit accepts a provider= argument, in the same three forms: a string (a registered name), a configuration dataclass, or a backend instance. The same circuit can run on any backend without changes.

from eigenkit import Circuit, PennyLaneConfig

# String: registered name lookup.
Circuit(blocks=..., provider="pennylane")

# Config: routes to the matching backend via `make_backend()`.
Circuit(blocks=..., provider=PennyLaneConfig(device_name="lightning.qubit"))

# Backend instance: used directly.
from eigenkit import CudaQuantumBackend, register

Circuit(blocks=..., provider=CudaQuantumBackend())

# Or register it once under a name, then use the name anywhere.
register("cudaq", CudaQuantumBackend())
Circuit(blocks=..., provider="cudaq")

The built-in NumPy simulator is the default (provider="numpy"), and it is always available, with no extra. PennyLane, Qiskit, Braket, IQM, TensorCircuit, Qulacs and qsim register their names automatically once their extras are installed. CUDA Quantum and Qiskit Runtime do not register a name by themselves: pass an instance as provider=, or import the backend and call register(). A QiskitRuntimeConfig passed as provider= does not need registration either.

Naming a backend whose package is not installed raises an ImportError, which tells you which extra to install. The name stays registered, so once the extra is installed, the same process finds it, without a restart:

ImportError: provider 'pennylane' is registered but its package is not
installed (install with `pip install eigenkit[pennylane]`).

A name that the library does not know at all raises a KeyError instead, and lists the names that it knows. These are two different problems, and the two messages keep them apart.

What "faster" is measured against

When these docs call a backend fast or faster without naming a baseline, the comparison is with the built-in NumPy reference simulator. This is the default provider="numpy", run on the central processing unit (CPU), analytically (shots=None) and in double precision. That simulator is written to be readable, and it is not tuned, which is what makes it the zero point. "Faster than NumPy" says that a backend is worth installing for a given workload. It does not say that the backend beats every other backend.

The batching table further down uses a different baseline, and its heading says so. It compares the native batched call of a backend with the library's own Python loop over the samples, on that same backend. The speedup of about 30× given for NumPy there is that same comparison between the loop and the batch, not a comparison between backends.

All these figures are rough expectations from development runs, not published benchmarks with fixed hardware and versions. Time your own circuit before you choose a backend for its speed.

Backend matrix

Backend Registration Gradients Native batching Notes
NumPy Built in ("numpy"). Adjoint. Yes, vectorised. The default. The reference statevector simulator, with no extra dependencies.
PennyLane Lazy ("pennylane"). Native. Yes. Any PennyLane device, through PennyLaneConfig. It needs the pennylane extra.
Qiskit Lazy ("qiskit"). Parameter shift. No, it loops. A statevector simulator. Finite shots are not supported yet, and any value other than shots=None raises an error.
Braket (local) Lazy ("braket"). Parameter shift. No, it loops. LocalSimulator.
Braket (cloud or QPU) The same backend, chosen by the configuration. Parameter shift, or none. No, it loops. BraketConfig(device_arn=...). On a quantum processing unit (QPU), gradients are turned off unless enable_gradients=True, and shots > 0 is required.
Qiskit Runtime On request (register("qiskit-runtime", ...)). None, or parameter shift. Yes. IBM Eagle and Heron devices, and the fake backends. Gradients only with enable_gradients=True.
IQM Lazy ("iqm"). None, or parameter shift. No, one job per sample. IQM Resonance through iqm-client, and the fake backends of IQM, which model its noise. The hardware returns only counts, so every value is computed from histograms of bitstrings. Gradients only with enable_gradients=True. It needs the iqm extra.
CUDA Quantum On request (register("cudaq", ...)). Parameter shift. Partly. The optional cudaq dependency. Sum and Multiply expressions are not supported.
TensorCircuit Lazy ("tensorcircuit"). Automatic differentiation with JAX. Yes, with jax.vmap. A tensor-network simulator, through tensorcircuit-ng on JAX. It needs the tensorcircuit extra. It is best for workloads that are run many times (see the note below).
Qulacs Lazy ("qulacs"). Adjoint. No, it loops. A fast statevector simulator written in C++. It has a native reverse-mode gradient through backprop, and it supports the full set of gates. It needs the qulacs extra.
Qiskit Aer Lazy ("qiskit-aer"). Parameter shift. No, it loops. The dense statevector core of Aer. It comes with the qiskit extra, so it adds no dependency.
Qiskit Aer MPS Lazy ("qiskit-aer-mps"). Parameter shift. No, it loops. The same backend, with method="matrix_product_state". It is exact when the bond dimension is not limited. It is the natural point of comparison for a simulation based on the time-evolving block decimation (TEBD) algorithm.
qsim Lazy ("qsim"). Parameter shift. No, it loops. The qsim simulator of Google, through Cirq. It works in single precision (float32), so it is fast but has a residual error of about 1e-6. It needs the qsim extra.

NumPy (built in)

This is the default: a reference statevector simulator in pure NumPy, with no extra dependencies, so that an install with no extra can run circuits immediately:

import numpy as np
from eigenkit import Circuit, Encoding, Variational, all_z

circuit = Circuit(
    blocks=[Encoding(in_features=2), Variational(depth=1)],
    observables=all_z(2),
)  # provider="numpy", interface="numpy" by default
circuit(np.zeros(circuit.spec.n_weights), np.array([0.3, -0.2]))

Gradients use the analytic adjoint method, which is exact and costs O(1) in the number of parameters on statevector simulators. The simulator takes the simple speed gains that NumPy allows. A batch of inputs evolves in one vectorised sweep, with a leading batch axis on the state tensor, which is about 30× faster than a loop over the samples with a batch of 64. A CNOT is applied as a swap of amplitudes (np.flip), not as a matrix product. It is still the readable reference and not a tuned simulator, and it is the baseline of every "faster" claim in these docs. Install pennylane for the fast path. The forward values and the gradients agree with PennyLane to machine precision.

PennyLane

from eigenkit import Circuit, PennyLaneConfig

config = PennyLaneConfig(device_name="lightning.qubit", shots=None)
Circuit(blocks=..., provider=config)

device_name is the identifier of any PennyLane device. shots=None evaluates the circuit analytically. The PyTorch interface of PennyLane computes gradients natively.

Qiskit

from eigenkit import Circuit, QiskitConfig

config = QiskitConfig(shots=None)
Circuit(blocks=..., provider=config)

The backend builds a parametric QuantumCircuit once. Each forward pass binds the parameters with assign_parameters and computes the expectation values with Statevector. Gradients use the parameter-shift rule.

Braket

BraketConfig chooses between the local simulator, a cloud simulator and a QPU, based on device_arn:

from eigenkit import BraketConfig

# Local (default).
local = BraketConfig()

# Cloud simulator (SV1).
sv1 = BraketConfig(
    device_arn="arn:aws:braket:::device/quantum-simulator/amazon/sv1",
    shots=1000,
)

# QPU (gradients disabled unless enable_gradients=True, shots > 0 enforced).
qpu = BraketConfig(
    device_arn="arn:aws:braket:us-east-1::device/qpu/ionq/Aria-1",
    shots=1000,
)

Runs on the cloud and on a QPU issue a single CostWarning when the circuit is compiled. See QPU and cloud workflows for submitting without waiting and for dry runs.

Braket runs run, sample, probabilities and zero_state_probability, either waiting for the result or through submit(). sample() needs shots > 0. With shots=0, probabilities() and zero_state_probability() are exact on the local simulator, SV1 and DM1. The result type that each device uses, and the largest number of qubits for the exact results, are in the table of Braket result types.

Qiskit Runtime

This backend targets real IBM hardware, and the fake backends of qiskit_ibm_runtime, which model the noise of the devices. The token is looked up in this order: an explicit token, then the QISKIT_IBM_TOKEN environment variable, then the saved account file. channel defaults to "ibm_quantum_platform", because the older "ibm_quantum" channel was retired.

from eigenkit import Circuit, QiskitRuntimeConfig

config = QiskitRuntimeConfig(backend_name="ibm_fez", shots=1024)
Circuit(blocks=..., provider=config)

# Or let the service choose its least busy operational device.
config = QiskitRuntimeConfig(backend_name="least_busy", shots=1024)

backend_name is required unless backend_instance is given. There is no default device, because IBM retires devices. Passing the configuration does not need a register() call.

For tests, pass a FakeBackendV2 directly through backend_instance. No authentication is needed.

primitive="sampler" uses SamplerV2 in place of EstimatorV2, and computes the expectation values from the raw counts of bitstrings. This also makes sample() and probabilities() available. See QPU and cloud workflows.

IQM

This backend targets IQM hardware through the software of IQM itself, and not through Braket. The difference is between a bill from Amazon Web Services (AWS) and the plans of IQM, and this is the only way to use the fake backends of IQM and its Star architectures, which use MOVE gates. The token is looked up in this order: an explicit token, then the IQM_TOKEN environment variable, then whatever iqm-client finds by itself.

from eigenkit import Circuit, IQMConfig

config = IQMConfig(
    url="https://resonance.iqm.tech/",
    quantum_computer="garnet",
    shots=2048,
)
Circuit(blocks=..., provider=config)

The service of IQM returns only histograms of bitstrings, so every expectation value is computed from counts, and shots > 0 is required. For tests, pass an IQMFakeAdonis() or an IQMFakeDeneb() directly through backend_instance. They run locally on Aer, with a model of the noise of IQM, and they need no token and no network.

CUDA Quantum

from eigenkit import (
    Circuit,
    CudaQuantumConfig,
    CudaQuantumBackend,
    register,
)

register("cudaq", CudaQuantumBackend())
config = CudaQuantumConfig(target="nvidia", shots=None)
Circuit(blocks=..., provider=config)

Common targets are "qpp-cpu" (a statevector on the CPU, the default), "nvidia" (one graphics processing unit, or GPU), "nvidia-mgpu", "nvidia-mqpu" and "tensornet".

The arithmetic of CUDA Quantum on parameters (QuakeValue) is limited: Sum and Multiply expressions are not supported. This rules out Reuploading(trainable_scale=True) on CUDA Quantum.

TensorCircuit

from eigenkit import Circuit, TensorCircuitConfig

# Auto-registered once the `tensorcircuit` extra is installed.
Circuit(blocks=..., provider="tensorcircuit")

# Equivalently, via the config.
Circuit(blocks=..., provider=TensorCircuitConfig())

This backend simulates circuits as tensor networks, with tensorcircuit-ng on the JAX backend (the tensorcircuit extra). Expectation values are exact contractions of the tensor network. The batch of parameters is vectorised with jax.vmap, and the whole forward pass and gradient are compiled with jax.jit. Gradients use the native reverse-mode automatic differentiation of JAX (jax.jacrev), exposed as the jacobian of the provider. So both the NumPy variational optimisation tools (VQE and QAOA through minimize_expectation) and the PyTorch interface use them, without the parameter-shift rule. The software is imported only when it is needed, so import eigenkit does not import JAX until this provider is resolved. The supported gates are RX, RY, RZ, H, X, Z, CNOT and SWAP.

When it pays off. The simulation is still an exact contraction, so what this backend gives is a constant factor, not access to larger circuits. It has a one-time compilation cost with jax.jit of a few seconds, which grows with the number of qubits and the size of the Hamiltonian. So it suits workloads that are run many times (training loops, searches over hyperparameters, VQE and QAOA with an ansatz of fixed shape), where this cost is spread over many evaluations. For single runs or exploration, the NumPy simulator is faster. The forward values and the gradients match the NumPy reference to machine precision.

Qulacs

from eigenkit import Circuit, QulacsConfig, QulacsBackend, register

register("qulacs", QulacsBackend())  # auto-registers when the extra is installed
Circuit(blocks=..., provider="qulacs")  # or provider=QulacsConfig()

qulacs is a statevector simulator with a compiled C++ core, where the NumPy reference is written in Python and NumPy. It has a native adjoint gradient (ParametricQuantumCircuit.backprop), so it declares gradient_method="adjoint", like the NumPy reference simulator. One backprop sweep per observable gives the gradient of every gate angle in reverse mode, at a cost of O(1) in the number of parameters. These gradients are mapped onto the weights and the inputs with the chain factors of the intermediate representation, so Reuploading(trainable_scale=True) and other angles with Scale or Multiply are differentiated correctly. The forward values, the gradients, zero_state_probability (the primitive of quantum kernels) and the reading and writing of statevector all agree with the NumPy reference to machine precision.

The full set of gates is supported. Gates for which qulacs has no parametric primitive are decomposed onto parametric rotations, so that they stay differentiable: PhaseShift becomes RZ, CRY becomes RY·CX·RY·CX, and CPHASE becomes three RZ and CX gates. adjoint(AmplitudeEncoding) is not supported, as with Qiskit, because a state preparation cannot be inverted as a gate. Install it with pip install eigenkit[qulacs].

Qiskit Aer

from eigenkit import Circuit, QiskitAerConfig

Circuit(blocks=..., provider="qiskit-aer")  # dense statevector core
Circuit(blocks=..., provider="qiskit-aer-mps")  # matrix-product-state core
Circuit(blocks=..., provider=QiskitAerConfig(method="matrix_product_state"))

Aer is the high-performance simulator of Qiskit. It already comes with the qiskit extra, so this backend adds no new dependency. It reuses the translation of the plain Qiskit backend without changes (the lowering of gates, the binding of symbolic Parameter values, and the SparsePauliOp observables). Only the way the circuit is executed changes: the expectation values are read through save_expectation_value of Aer, in place of Statevector.from_instruction.

Two methods are available, as two provider names. qiskit-aer is the dense statevector core, which is exact and matches the NumPy reference to about 1e-12. qiskit-aer-mps is the matrix product state (MPS) core. It is exact when the bond dimension is not limited (the default of Aer), it is a fast approximation for circuits with little entanglement, and it is the natural point of comparison for a simulation with tensor networks. It agrees with NumPy to about 1e-9, and the difference comes from the truncation of the singular value decomposition.

The full set of gates is supported by both. Both methods translate the circuit to the native gates of Aer first (optimization_level=0, with no coupling map, so no routing and no permutation of the qubits). This is needed because the MPS core rejects cry, and because the statevector core cannot run the controlled unitary that a ControlledQubitUnitary becomes. Sampling with finite shots is not supported yet, so shots must be None. Install it with pip install eigenkit[qiskit].

qsim

from eigenkit import Circuit, QsimConfig

Circuit(blocks=..., provider="qsim")  # or provider=QsimConfig()
Circuit(blocks=..., provider=QsimConfig(threads=8))

qsim is the high-performance Schrödinger statevector simulator of Google, used through its qsimcirq bridge to Cirq. The intermediate representation is translated once into a parametrised cirq.Circuit, whose angles are SymPy symbols, and each call resolves the symbols and runs the circuit.

qsim works in single precision (float32), because the pip package has no build in double precision. The values are returned as complex128, but they carry an accumulated error of about 1e-6, so qsim does not pass a parity test at 1e-9. This residual comes from the precision and is not a bug, as with the single-precision path of CUDA Quantum. The parity tests check qsim at 1e-4. What qsim gives is speed over the NumPy reference simulator, and the residual should be reported together with any timing, not hidden.

The full set of gates of the circuit algebra is supported. The primitives that carry data (AmplitudeEncoding, QubitUnitary and ControlledQubitUnitary) are not, and they are rejected before the run. An initial_state is not supported, and passing one raises a NotImplementedError. The number of threads follows OMP_NUM_THREADS unless QsimConfig(threads=...) sets it, so qsim respects a benchmark setup that fixes the threads. Install it with pip install eigenkit[qsim].

Batched evaluation

Batching works on both interfaces and on every backend. Pass a 2-D inputs array of shape (B, n_inputs), and you get an output of shape (B, n_obs), or (B,) when there is a single observable. weights stays 1-D, because the weights are shared across the batch.

import numpy as np
from eigenkit import Circuit, Encoding, Variational, all_z

# numpy interface (the default): weights are passed per call.
circuit = Circuit(
    blocks=[Encoding(in_features=4), Variational(rotation="RY", depth=2)],
    observables=all_z(4),
)
weights = np.zeros(circuit.spec.n_weights)  # or a trained vector
x_batch = np.random.uniform(0, np.pi, size=(128, 4))
y_batch = circuit(weights, x_batch)  # shape (128, 4)

On the PyTorch interface, the module stores the weights, so you pass only the batch:

import torch
from eigenkit import Circuit, Encoding, Variational, all_z

circuit = Circuit(
    blocks=[Encoding(in_features=4), Variational(rotation="RY", depth=2)],
    observables=all_z(4),
    interface="torch",
    provider="pennylane",
)
y_batch = circuit(torch.randn(128, 4, dtype=torch.float64))  # shape (128, 4)

What "native batching" means here

When a call has the shape of the fast path (weights.ndim == 1 and inputs.ndim == 2), the backends with native batching send the whole batch through one batched call of the provider. The Qiskit statevector backend and the local Braket backend use the shared Python loop batch_call. Any other combination of shapes (batched weights, a single sample of inputs in a 2-D wrapper and so on) falls back to a loop in Python, which gives the index of a sample that fails.

The speedups below are within one backend: the native batched call of the backend against the library's own loop over the samples, on the same simulator. They are not comparisons between backends, and they are not measured against the NumPy reference.

Backend How the fast path works Speedup over the loop on the same backend
NumPy A leading batch axis on the state tensor, so the whole batch evolves in one sweep. The inputs are transposed to (n_inputs, B), so an angle that depends on the inputs becomes a (B,) vector (a stack of (B, 2, 2) rotations), while angles that depend only on weights stay a single shared (2, 2) matrix. A CNOT is a swap of amplitudes with np.flip, not a matrix product. About 30× over the loop with a batch of 64.
PennyLane Parameter broadcasting in the QNode. The inputs are transposed to (n_inputs, B), so each InputRef(j) becomes a vector of shape (B,), and every gate angle is broadcast through a single evaluation of the tape. About B× over the loop. On default.qubit with inputs of shape (128, 4), the overhead of the library is at most 20% of the plain PennyLane QNode call.
Qiskit Runtime The EstimatorV2 primitive accepts a (B, n_params) array of parameters in each Primitive Unified Bloc (PUB), so the whole batch is sent as one job, with one PUB per observable, in place of one job per sample. Large. IBM Runtime is dominated by the overhead of each job, so collapsing one job per sample into one job is a big change. Check it on your own backend.
CUDA Quantum cudaq.observe(kernel, ham, *2D_args) uses the broadcast helper of CUDA Quantum. Limitation: this helper is itself a Python loop for kernels built with cudaq.make_kernel(...), so the cost of just-in-time compilation is not shared. The behaviour is correct, and it is the usual way to batch in CUDA Quantum, but in practice the run time is currently within about 10% of the explicit loop. Full vectorisation would require moving the backend to @cudaq.kernel declarations. About the same as the loop.
TensorCircuit jax.vmap over the contraction of one sample, compiled with jax.jit, so the batch of parameters is a vectorised extra index of the tensor-network contraction. About B× once it is compiled. The one-time compilation takes seconds, grows with the number of qubits, and is spread over the loop.
Qiskit (statevector) None. Statevector.expectation_value works on one state at a time, so the backend loops through the shared helper. None.
Braket (local) None. It calls LocalSimulator.run once per task. Braket has run_batch to submit independent tasks in parallel, but it is not used yet. None.
Qulacs None. Observable.get_expectation_value works on one state at a time, so the backend loops through the shared helper. The gradients also loop over backprop, one sample at a time, and each call is fast because it runs in C++. None.

Gradients through batched inputs

For the backends with native automatic differentiation (PennyLane), gradients through a 2-D inputs batch go through the same broadcasting as the forward pass, so the library does no extra work.

For the backends that use the parameter-shift rule (Qiskit, local Braket, CUDA Quantum), the backward pass of the autograd function loops over the samples. It computes the jacobian of each sample, adds them into the shared gradient of the weights, and stacks the gradients of the inputs of each sample. This loop is part of the parameter-shift rule, because each sample has its own input vector, and therefore its own shifted expectation values. The forward pass still uses the batched path of the backend. Qiskit Runtime and IQM refuse gradients (gradient_method="none") unless their configuration sets enable_gradients=True. Then they use the same loop, and every shifted evaluation is a paid forward call.

Registering custom configs

Use register() to give a configuration a name, so that you can reuse it:

from eigenkit import PennyLaneConfig, register

register("lightning", PennyLaneConfig(device_name="lightning.qubit"))

Circuit(blocks=..., provider="lightning")

Reusing the same circuit on another provider

with_provider is a method of the PyTorch circuit (interface="torch"). If you already have one, and you want the same CircuitSpec and observables on a different backend, call it instead of building the list of blocks again. The provider argument accepts the same three forms as Circuit(..., provider=...): a registered name, a configuration with make_backend(), or a backend instance.

import torch

from eigenkit import Circuit, Encoding, Variational, all_z, PennyLaneConfig

layer = Circuit(
    blocks=[Encoding(in_features=2), Variational(depth=1)],
    observables=all_z(2),
    provider="pennylane",
    interface="torch",
)
x = torch.randn(2, dtype=torch.float64)

layer_qiskit = layer.with_provider("qiskit")
layer_lightning = layer.with_provider(
    PennyLaneConfig(device_name="lightning.qubit"),
)

By default, the weights are cloned from the original circuit. Pass copy_weights=False for new random weights, or init=... to set the weights explicitly (it takes precedence over copy_weights).

API reference

eigenkit.core.provider.register(name, provider_or_config)

Register a backend, or a configuration that can build one, under name.

Later calls to Circuit(..., provider=name) then use that backend. If a configuration is passed, its make_backend() method is called to build the backend.

Parameters:

Name Type Description Default
name str

The name to register.

required
provider_or_config Provider | object

A backend instance, or a configuration with a make_backend() method.

required

eigenkit.core.provider.registered_names()

Return every provider name that get accepts, including the lazy ones.

eigenkit.torch.circuit.TorchCircuit.with_provider(provider, *, copy_weights=True, init=None)

Return a new TorchCircuit for another provider.

The new circuit has the same spec and the same observables argument as this instance, and it is compiled for the given provider.

provider accepts the same forms as in __init__(): a registered name (a str), a configuration object with a make_backend() method (for example PennyLaneConfig), or a backend instance.

The gradients follow the gradient_method of the new backend. The forward values should match across simulators up to numerical tolerance, but not exactly.

Parameters:

Name Type Description Default
provider ProviderSpec

The backend for the new circuit.

required
copy_weights bool

If True, the new circuit starts from a copy of self.weights. It is ignored when init is given. Default: True.

True
init Tensor | None

An optional weight tensor of shape (spec.n_weights,). When it is given, it takes precedence over copy_weights. Default: None.

None

Returns:

Type Description
'TorchCircuit'

The new TorchCircuit.

eigenkit.core.provider.CostWarning

Bases: UserWarning

Warning issued before a submission to a paid quantum backend.

A paid backend is a quantum processing unit (QPU) or a cloud simulator that charges for use. The first compilation for such a device issues this warning, so that costs cannot build up without the user noticing.

Config dataclasses

eigenkit.core.backends.numpy_backend.NumpyConfig dataclass

Configuration for the reference NumPy simulator.

It has no settings yet. It exists so that Circuit(provider=NumpyConfig()) works in the same way as with the configurations of the other backends.

make_backend()

Build the backend that this configuration describes.

The provider layer calls it when a configuration is passed as provider=. There is no reason to call it directly.

Returns:

Type Description
'NumpySimulator'

The NumpySimulator for this configuration.

eigenkit.core.backends.pennylane_backend.PennyLaneConfig dataclass

Device settings, used for every qml.device() call.

Pass it directly to Circuit(provider=PennyLaneConfig(...)), or pass it to register() under a name to reuse it.

Parameters:

Name Type Description Default
device_name str

The identifier of the PennyLane simulator plugin: "default.qubit", "lightning.qubit", "lightning.gpu", or any other installed device. Default: "default.qubit".

'default.qubit'
shots int | None

None computes exact expectation values, and a positive integer samples that many shots for each expectation value. Default: None.

None
diff_method str

The PennyLane differentiation method. With "best", PennyLane chooses for each device, usually backpropagation on a simulator and the parameter-shift rule on hardware. Default: "best".

'best'

make_backend()

Build the backend that this configuration describes.

The provider layer calls it when a configuration is passed as provider=. There is no reason to call it directly.

Returns:

Type Description
'PennyLaneBackend'

The PennyLaneBackend for this configuration.

eigenkit.core.backends.qiskit_backend.QiskitConfig dataclass

Device configuration for the Qiskit backend.

Pass it to Circuit(provider=QiskitConfig(...)), or register it under a name with register() to use it again.

Parameters:

Name Type Description Default
shots int | None

None computes exact expectation values with Statevector. Finite-shot sampling is not supported on this backend yet, and any other value makes the compilation raise NotImplementedError. For sampling, use the Qiskit Runtime backend, which can also run locally on a fake device. Default: None.

None

make_backend()

Build the backend that this configuration describes.

The provider layer calls it when a configuration is passed as provider=. There is no reason to call it directly.

Returns:

Type Description
'QiskitBackend'

The QiskitBackend for this configuration.

eigenkit.core.backends.qiskit_aer_backend.QiskitAerConfig dataclass

Device configuration for the Qiskit Aer backend.

Parameters:

Name Type Description Default
method str

The Aer simulation method. "statevector" selects the exact dense simulator. "matrix_product_state" selects the matrix product state (MPS) simulator, which is exact when the bond dimension is not bounded, and an approximation for circuits with little entanglement otherwise. The provider name "qiskit-aer-mps" is this configuration with the MPS method already set. Any other value raises ValueError. Default: "statevector".

'statevector'
shots int | None

None returns exact expectation values. Finite-shot sampling is not supported yet, and any other value makes the compilation raise NotImplementedError. Default: None.

None

make_backend()

Build the backend that this configuration describes.

The provider layer calls it when a configuration is passed as provider=. There is no reason to call it directly.

Returns:

Type Description
'QiskitAerBackend'

The QiskitAerBackend for this configuration.

eigenkit.core.backends.braket_backend.BraketConfig dataclass

Device configuration for the Braket backend.

Parameters:

Name Type Description Default
device_arn str | None

None (the default) for the local simulator, or the Amazon Resource Name (ARN) of an Amazon Web Services (AWS) device. An example for a cloud simulator is arn:aws:braket:::device/quantum-simulator/amazon/sv1. Quantum processing units (QPUs) have ARNs of the form arn:aws:braket:<region>::device/qpu/<vendor>/<name>.

None
local_backend str

The LocalSimulator backend to use when device_arn is None. Common values are "braket_sv" (state vector) and "braket_dm" (density matrix).

'braket_sv'
shots int

0 means exact values (simulators only), and a positive integer turns on sampling with that number of shots. AWS QPUs require shots > 0.

0
s3_bucket str | None

The Amazon Simple Storage Service (S3) bucket for the results of cloud tasks. With None, the Braket package picks the default of the account.

None
s3_prefix str

The key prefix inside s3_bucket.

'braket-results'
poll_timeout_seconds int

How many seconds result() waits for a submitted task.

3600
enable_gradients bool

Whether to allow parameter-shift gradients on a QPU, for PyTorch .backward(), jacobian() and gradient-based optimisers. It is off by default, because one backward pass costs at least 2 * n_weights paid tasks. When it is set, compiling for a QPU issues a CostWarning that gives this count. Simulators compute gradients in either case.

False

is_cloud property

Whether device_arn is set.

If it is, the circuit runs on Amazon Web Services (AWS), on a cloud simulator or a quantum processing unit (QPU), instead of on the local simulator.

is_qpu property

Whether device_arn names real hardware (it contains /qpu/).

make_backend()

Build the backend that this configuration describes.

The provider layer calls it when a configuration is passed as provider=. There is no reason to call it directly.

Returns:

Type Description
'BraketBackend'

The BraketBackend for this configuration.

eigenkit.core.backends.qiskit_runtime_backend.QiskitRuntimeConfig dataclass

Bases: SecretFieldsMixin

Device configuration for the Qiskit Runtime backend.

There are two ways to choose the target backend:

  • backend_name is looked up with QiskitRuntimeService. It needs credentials: a token, an environment variable or a saved account.
  • backend_instance is a BackendV2 that is used directly. It is useful for tests with a fake backend, or to pass any backend that is already configured.

Parameters:

Name Type Description Default
backend_name str | None

The IBM backend name, for example "ibm_fez", or "least_busy" for the least busy operational device that the account can use, chosen once per compiled circuit. It is required unless backend_instance is set, and it is ignored when it is. Default: None.

None
backend_instance Any

A backend that is already configured. It takes precedence over backend_name. Default: None.

None
shots int

The number of shots per circuit run. Default: 1024.

1024
optimization_level int

The optimisation level passed to qiskit.transpile, from 0 to 3. Default: 1.

1
seed_transpiler int | None

The seed of the random passes of the transpiler, mainly the SabreLayout and SabreSwap passes, which choose the layout and the routing. None, the default, draws a new seed for every compile, so a circuit that needs routing compiles to a different number of gates on every run. Within one compile, all the transpilations share the seed, including the shifted copies that a gradient on hardware compiles. Set it whenever two compilations must be comparable. Since compiling submits nothing, trying a few seeds and keeping the cheapest circuit is a cheap and fair way to choose a layout.

None
primitive Primitive

The Runtime primitive that computes the expectation values. "estimator", the default, uses EstimatorV2, with one Primitive Unified Bloc (PUB) per observable in each job. "sampler" uses SamplerV2, with one PUB per qubit-wise commuting measurement group, and reduces the raw counts locally. sample() and probabilities() use SamplerV2 with either value.

'estimator'
group_commuting bool

Whether the sampler puts qubit-wise commuting Pauli terms into shared measurement circuits. False runs one circuit per term, which runs more circuits but keeps the shot budget of each term independent. The estimator ignores it. Default: True.

True
token str | None

The token passed to QiskitRuntimeService when backend_instance is None. With None, the backend uses QISKIT_IBM_TOKEN, then the saved account file. It is secret: it is never shown by repr(), and it is never saved in .eigenkit bundles, pickles or joblib checkpoints. After a load it is None, so set QISKIT_IBM_TOKEN.

None
channel str

The service channel: "ibm_quantum_platform" (the default), "ibm_cloud" or "local". The legacy "ibm_quantum" channel was retired, and QiskitRuntimeService no longer accepts it.

'ibm_quantum_platform'
instance str | None

The service instance, as a Cloud Resource Name (CRN) or a name, when one applies. Default: None.

None
service_kwargs dict

Extra keyword arguments passed to QiskitRuntimeService, such as region, url or proxies.

dict()
poll_timeout_seconds int

How long result() waits before it gives up, in seconds. Default: 3600.

3600
enable_gradients bool

Whether to allow parameter-shift gradients on the device: PyTorch .backward(), jacobian() and gradient-based optimisers. It is off by default, because one backward pass costs at least 2 * n_weights times the jobs of a forward call. When it is on, compiling for a paid device gives a CostWarning that states this cost.

False

__post_init__()

Reject an unknown primitive when the configuration is built.

If the check waited for the first submission, the error would come as a confusing failure on a paid backend, after the transpilation and the cost warning.

make_backend()

Build the backend that this configuration describes.

The provider layer calls it when a configuration is passed as provider=. There is no reason to call it directly.

Returns:

Type Description
'QiskitRuntimeBackend'

The QiskitRuntimeBackend for this configuration.

eigenkit.core.backends.iqm_backend.IQMConfig dataclass

Bases: SecretFieldsMixin

Device configuration for the IQM backend.

There are two ways to choose a target:

  • url, and optionally quantum_computer, opens an IQMProvider for an IQM Server. This needs a token, from token or from the IQM_TOKEN environment variable.
  • backend_instance uses any IQM BackendV2 directly: a fake backend for offline work, or a backend that you configured yourself. It takes precedence over url, and it needs no token and no network access.

Parameters:

Name Type Description Default
url str | None

The address of the IQM Server, for example "https://resonance.iqm.tech/". It is ignored when backend_instance is set. With None, the backend reads the address from the IQM_SERVER_URL environment variable when the circuit is compiled.

None
quantum_computer str | None

The machine to use when the server hosts more than one, for example "garnet". With None, the server's default is used.

None
backend_instance Any

An IQM backend that is already configured. It takes precedence over url.

None
shots int

The number of shots for each circuit run. It must be positive, because hardware cannot compute exact values.

1024
optimization_level int

The optimisation level passed to qiskit.transpile, from 0 to 3.

1
seed_transpiler int | None

The seed of the random steps of the transpiler, mainly the SabreLayout and SabreSwap passes, which choose the layout and the routing. With None, the default, a new seed is drawn for every compilation, so a circuit that needs routing compiles to a different number of gates on every run. Within one compilation, all the transpilations share the seed, including those of the shifted copies that a hardware gradient compiles. Set it whenever two compilations must be comparable. Trying a few seeds and keeping the cheapest result is a cheap and fair way to choose a layout, because compiling submits nothing.

None
transpile_mode TranspileMode

"parametric" transpiles once and sets the parameter values at each call. "per_call" sets the values first and transpiles at every call, which gives shallower circuits but costs one transpilation per evaluation.

'parametric'
group_commuting bool

Whether to put Pauli terms that commute qubit by qubit into shared measurement circuits. With False, one circuit runs for each term: there are more jobs, but each term gets all the shots.

True
token str | None

The access token for the IQM Server. It is passed on only when it is set. With None, iqm-client handles authentication on its own, from IQM_TOKEN and from its own tokens file. Do not set both token and IQM_TOKEN, because iqm-client rejects a token given in two ways at once. This field is secret: repr() never shows it, and it is never saved in .eigenkit bundles, pickles or joblib checkpoints. After a load it is None, so set IQM_TOKEN.

None
calibration_set_id Any

The calibration set to use, as a UUID. With None, the server's default is used.

None
use_metrics bool

Whether to ask the server for the error rates of each qubit and each coupler, and load them into the InstructionProperties of the target. With False (the default of iqm-client itself), the target has no error rates, so the Qiskit layout pass does not see the noise. It then cannot prefer the better qubits, and a comparison with a backend that does have calibration data is not like for like. It costs one extra request at compile time, and it is ignored when backend_instance is set.

False
provider_kwargs dict

Extra keyword arguments passed to IQMProvider, for example tokens_file.

dict()
poll_timeout_seconds int

How many seconds result() waits before giving up.

3600
enable_gradients bool

Whether to allow parameter-shift gradients on the device, for PyTorch .backward(), jacobian() and gradient-based optimisers. It is off by default, because one backward pass runs the circuits of a forward call at least 2 * n_weights times. When it is set, compiling for a real device issues a CostWarning that gives this count.

False

__post_init__()

Reject the settings that can never work, when the configuration is created.

If they were left to the first submission, the error would appear after the transpilation and the cost warning, which on a paid device is the wrong time to find out. Only the settings that are wrong on their own are checked here. Whether a target can be found depends on the environment at compile time, so _resolve_backend checks that.

make_backend()

Build the backend that this configuration describes.

The provider layer calls it when a configuration is passed as provider=. There is no reason to call it directly.

Returns:

Type Description
'IQMBackend'

The IQMBackend for this configuration.

eigenkit.core.backends.cudaquantum_backend.CudaQuantumConfig dataclass

Device configuration for the CUDA Quantum backend.

Parameters:

Name Type Description Default
target str

The cudaq target. Common values are "qpp-cpu", a statevector simulator on the central processing unit (CPU), which is the default; "nvidia", on one graphics processing unit (GPU); "nvidia-mgpu", on several GPUs; "nvidia-mqpu", with several quantum processing units (QPUs); and "tensornet". Default: "qpp-cpu".

'qpp-cpu'
shots int | None

None for exact expectation values, or a positive integer for a finite number of shots. Default: None.

None

make_backend()

Build the backend that this configuration describes.

The provider layer calls it when a configuration is passed as provider=. There is no reason to call it directly.

Returns:

Type Description
'CudaQuantumBackend'

The CudaQuantumBackend for this configuration.

eigenkit.core.backends.tensorcircuit_backend.TensorCircuitConfig dataclass

Configuration for the tensorcircuit backend.

It has no settings yet: the contraction path is the default of tensorcircuit, and the batch axis is vectorised with jax.vmap. It exists for consistency with the configurations of the other backends, and so that provider=TensorCircuitConfig() works without a register call.

make_backend()

Build the backend that this configuration describes.

The provider layer calls it when a configuration is passed as provider=. There is no reason to call it directly.

Returns:

Type Description
'TensorCircuitBackend'

The TensorCircuitBackend for this configuration.

eigenkit.core.backends.qulacs_backend.QulacsConfig dataclass

Device configuration for the Qulacs backend.

The Qulacs statevector simulator for the central processing unit (CPU) needs no settings, so this configuration is currently only a marker. It makes Circuit(provider=QulacsConfig()) select this backend, and it leaves room for future fields, for example a state on a graphics processing unit (GPU).

Pass it to Circuit(provider=QulacsConfig()), or pass it to register() under a name to reuse it.

make_backend()

Build the backend that this configuration describes.

The provider layer calls it when a configuration is passed as provider=. There is no reason to call it directly.

Returns:

Type Description
'QulacsBackend'

The QulacsBackend for this configuration.

eigenkit.core.backends.qsim_backend.QsimConfig dataclass

Device configuration for the qsim backend.

Parameters:

Name Type Description Default
threads int | None

The size of the internal thread pool of qsim. None reads OMP_NUM_THREADS instead, which defaults to 1, so qsim uses one core unless you set it here or in the environment. Set it explicitly before you report any timing. Default: None.

None

make_backend()

Build the backend that this configuration describes.

The provider layer calls it when a configuration is passed as provider=. There is no reason to call it directly.

Returns:

Type Description
'QsimBackend'

The QsimBackend for this configuration.