## `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

```python
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`

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 |

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`

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. |

#### `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. |
