Skip to content

qsim_backend

eigenkit.core.backends.qsim_backend

The qsim backend, through Cirq.

qsim is a fast Schrödinger statevector simulator from Google, and qsimcirq connects it to Cirq. Both are in the optional [qsim] extra (pip install eigenkit[qsim]) and are imported only when needed, so the rest of the library works without them.

The circuit is translated once into a parametrised cirq.Circuit, whose angles are SymPy symbols w{i} and x{i}. Each call sets the values of the symbols and runs the circuit on a qsimcirq.QSimSimulator. Expectation values use the native qsim method simulate_expectation_values (each Observable of the library becomes a cirq.PauliSum), and statevectors come from simulate(...).final_state_vector.

It declares gradient_method = "parameter_shift": qsim has no cheap analytic reverse-mode jacobian here, so the gradient code of the library differentiates with the parameter-shift rule.

Precision. qsim is a single-precision simulator (32-bit floating point), and the pip wheel has no double-precision (64-bit) build. The outputs are returned as complex128, but they carry an accumulated error of about 1e-6, so they agree with a double-precision backend to about 1e-4, not to machine precision. This difference comes from the precision and is not a bug, as with the single-precision path of CUDA Quantum. It does mean that qsim is the wrong choice when you need exact agreement rather than speed.

Bit order: Cirq orders the statevector with the first qubit of qubit_order as the most significant bit. This backend passes [LineQubit(0), …, LineQubit(n-1)], so index 0 is qubit 0, the most significant bit. This is the same convention as in NumPy and PennyLane. No reordering is needed, and the backend comparison tests check this against the NumPy reference. The number of threads follows OMP_NUM_THREADS, unless a configuration sets it.

On macOS, importing this module sets KMP_DUPLICATE_LIB_OK=TRUE (unless it is already set). qsimcirq and PyTorch each include their own OpenMP runtime, and without this setting the second one to start aborts the process.

Example
from eigenkit import Circuit, Encoding, Variational, all_z

blocks = [Encoding(in_features=4), Variational(depth=2)]

# by registered name
circuit = Circuit(blocks=blocks, observables=all_z(4), provider="qsim")

# or with the config, when a device setting matters
from eigenkit.configs import QsimConfig

circuit = Circuit(
    blocks=blocks,
    observables=all_z(4),
    provider=QsimConfig(threads=8),
)

qsim is fast and single-precision, with an error of about 1e-6. This backend needs the qsim extra.

QsimBackend

The provider for qsim, the statevector simulator from Google, through Cirq.

It is registered as "qsim". It is internal: you select this backend with provider="qsim" or by passing a QsimConfig, not by building this class.

capabilities property

What this backend supports.

It runs the subset of gates that can be translated to Cirq, returns exact expectation values only, does not evaluate a batch in one call, and has no limit on the number of qubits. A circuit that uses a gate outside this subset is rejected before compilation, with a message that names the gate. See Capabilities.

__init__(config=None, name='qsim')

Build the backend from a device configuration.

Parameters:

Name Type Description Default
config QsimConfig

The device settings. Default: None, which uses QsimConfig().

None
name str

The registry name, used in error messages. Default: "qsim".

'qsim'

compile(circuit, observables)

Prepare circuit for repeated evaluation of observables.

A Circuit calls it once and reuses the result for every call, so the expensive work belongs here.

Parameters:

Name Type Description Default
circuit CircuitSpec

The circuit to prepare.

required
observables Sequence[Observable]

The observables to measure.

required

Returns:

Type Description
QsimCompiled

The prepared circuit, as a QsimCompiled object.

QsimCompiled

Bases: CompiledCircuit

A circuit prepared for qsim, as a Cirq circuit.

It is internal: QsimBackend.compile() builds it and returns it, and it is never built directly.

__init__(circuit, observables, config)

Prepare the circuit for repeated evaluation.

QsimBackend.compile() builds this object, so there is no reason to build one directly.

Parameters:

Name Type Description Default
circuit CircuitSpec

The circuit to prepare.

required
observables Sequence[Observable]

The observables to measure.

required
config QsimConfig

The device settings.

required

run(weights, inputs)

Evaluate the circuit and return one expectation value per observable.

It follows the CompiledCircuit contract: the result has shape (n_obs,) when weights and inputs are both 1-D, and (B, n_obs) otherwise.

Parameters:

Name Type Description Default
weights ndarray

The parameter vector: 1-D for one sample, or 2-D with a leading batch axis.

required
inputs ndarray

The input features, with the same 1-D or 2-D shape convention as weights.

required

Returns:

Type Description
ndarray

The expectation values, one for each observable.

statevector(weights, inputs, initial_state=None)

Return the full complex statevector after the circuit has run.

The result is a flat array of 2**n_qubits amplitudes, with qubit 0 as the most significant bit.

Parameters:

Name Type Description Default
weights ndarray

The parameter vector (1-D).

required
inputs ndarray

The input features: 1-D for one sample, or 2-D with a leading batch axis.

required
initial_state ndarray | None

Must be None. This backend can only start from |0…0⟩. Default: None.

None

Returns:

Type Description
ndarray

The statevector, of shape (2**n_qubits,), or (B, 2**n_qubits) for a batch.

Raises:

Type Description
NotImplementedError

If initial_state is not None.

zero_state_probability(weights, inputs)

Return the probability of measuring the state |0…0⟩.

Fidelity kernels and swap tests are built on it. It does not go through Observable, because it is not the expectation value of a Pauli operator.

Parameters:

Name Type Description Default
weights ndarray

The parameter vector (1-D).

required
inputs ndarray

The input features of one sample (1-D).

required

Returns:

Type Description
float

The probability, a float in [0, 1].

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.