## `eigenkit.core.backends.qulacs_backend`

The Qulacs backend.

Qulacs is an optional dependency (`pip install eigenkit[qulacs]`). The package is imported only when it is needed, so the rest of the library works without it, and `compile()` raises a clear `ImportError` if it is missing.

It declares `gradient_method = "adjoint"`. Qulacs provides an analytic reverse-mode gradient (`ParametricQuantumCircuit.backprop`), so, as with the NumPy reference simulator, the compiled circuit has a `jacobian` method, and the gradient code of the core package prefers it to the parameter-shift rule, whose cost is `O(n_params)`.

#### Compile and run

`compile()` builds one `ParametricQuantumCircuit` and the Qulacs `Observable` objects once. Every rotation whose angle depends on the weights or inputs becomes a *parametric slot*. `run()` sets the slots with `set_parameter`, without rebuilding the circuit at each call, and `jacobian()` reads the gradients of all slots from one `backprop` pass for each observable.

#### Conventions matched with the NumPy and PennyLane reference

- **Rotation sign.** Qulacs rotations are `e^{+iθP/2}`, while the library and PennyLane use `e^{-iθP/2}`. Every rotation angle is negated when it is passed to Qulacs: the Qulacs parameter of a slot is `φ = -(coeff · angle)`. As a result, `backprop` returns `d⟨O⟩/dφ`, and the chain rule through `φ` gives `d⟨O⟩/d(weights)` with the sign handled automatically (see `jacobian`).
- **Bit order.** Qulacs stores the amplitudes with qubit `0` as the least significant bit (as Qiskit does), while the library uses qubit `0` as the most significant bit. The statevector input and output, and the matrices of multi-qubit gates, are bit-reversed to match the two. The Pauli *observables* need no reindexing, because the qubit labels match: only the storage order of the amplitudes differs.

#### Gate coverage

`add_parametric_R{X,Y,Z}` covers the trainable rotations directly. The gates that have no parametric primitive in Qulacs are decomposed into these rotations, so that they stay differentiable:

- `PhaseShift(θ)` becomes a parametric `RZ`. The two are equal up to a global phase that cannot be observed.
- `CRY(θ)` becomes `RY(θ/2) CX RY(-θ/2) CX`, where `CX` is a controlled-NOT gate. This decomposition is exact.
- `CPHASE(θ)` becomes `P(θ/2)_c CX P(-θ/2)_t CX P(θ/2)_t`, with each `P` as an `RZ` slot. This decomposition is exact up to a global phase that cannot be observed.

`PhaseShift`, `CPHASE` and `CRY` with a constant angle, and the non-parametric gates (`H`, `X`, `Z`, `S`, `Sdg`, `CNOT`, `SWAP`, `MCX`, `MCZ`, `QubitUnitary`, `ControlledQubitUnitary`, `AmplitudeEncoding`), are added once as exact Qulacs gates. `backprop` accepts these fixed gates, so native adjoint gradients are available for every circuit.

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="qulacs")

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

circuit = Circuit(
    blocks=blocks,
    observables=all_z(4),
    provider=QulacsConfig(),
)
```

Gradients are computed natively in reverse mode, with `backprop`. This backend needs the `qulacs` extra.

### `QulacsBackend`

The provider for the Qulacs statevector simulator.

It is optional: it is registered as `"qulacs"` when the package is installed, and the package is imported only on first use. It declares `gradient_method = "adjoint"`, so the gradient code of the core package uses QulacsCompiled.jacobian() (analytic, in reverse mode, through Qulacs `backprop`) instead of the parameter-shift rule, whose cost is `O(n_params)`.

#### `capabilities`

What this backend supports.

It runs every gate, returns exact expectation values only, does not evaluate a batch in one call, and has no limit on the number of qubits. The adjoint of `AmplitudeEncoding` is excluded, because a state preparation cannot be inverted as a Qulacs gate. See Capabilities.

#### `__init__(config=None, name='qulacs')`

Build the backend from a device configuration.

Parameters:

| Name     | Type           | Description                                                    | Default    |
| -------- | -------------- | -------------------------------------------------------------- | ---------- |
| `config` | `QulacsConfig` | The device settings. Default: None, which uses QulacsConfig(). | `None`     |
| `name`   | `str`          | The registry name, used in error messages. Default: "qulacs".  | `'qulacs'` |

#### `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                                       |
| ---------------- | ------------------------------------------------- |
| `QulacsCompiled` | The prepared circuit, as a QulacsCompiled object. |

### `QulacsCompiled`

Bases: `CompiledCircuit`

A compiled circuit: one `ParametricQuantumCircuit`, reused across calls.

`_slots[k] = (expr, coeff)` records that the parametric Qulacs gate `k` implements the library angle `coeff · expr`. Its Qulacs parameter is set to `-(coeff · resolve(expr))`, because Qulacs uses the opposite rotation sign.

#### `__init__(circuit, observables, config)`

Prepare the circuit for repeated evaluation.

`QulacsBackend.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`      | `QulacsConfig`         | The device settings.        | *required* |

#### `jacobian(weights, inputs, *, need_input_grad=False)`

Compute the gradient with the native Qulacs method, the analytic adjoint.

The gradient code of the core package looks for a `jacobian` method. When a backend has one, it is used in preference to the parameter-shift rule, which works on any backend and is the fallback. Here the gradient comes from `backprop`: one pass for each observable gives `d⟨O⟩/dφ` for every parametric slot. These gradients are mapped onto the weights (and the inputs) with the same symbolic chain-rule factors as the parameter-shift code, so angles built with `Scale`, `Multiply`, `Sum` or `Negate` (for example in `Reuploading(trainable_scale=True)`) are also handled.

For a single sample, the two jacobians have shapes `(n_obs, n_weights)` and `(n_obs, n_inputs)`. For a 2-D `inputs` batch, they have a leading batch axis, and the batch is evaluated in a loop.

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* |
| `need_input_grad` | `bool`    | If True, also compute the jacobian with respect to the inputs. Default: False. | `False`    |

Returns:

| Name              | Type      | Description                               |
| ----------------- | --------- | ----------------------------------------- |
| `weight_jacobian` | `ndarray` | The jacobian with respect to the weights. |
| `input_jacobian`  | \`ndarray | None\`                                    |

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

Qubit `0` is the most significant bit. `initial_state`, in the same order, is the starting state instead of `|0…0⟩`. A 2-D `inputs` batch is evaluated in a loop and returns an array of shape `(B, 2**n)`. `initial_state` can then be one state of shape `(2**n,)`, shared by all samples, or a stack of shape `(B, 2**n)` with one state for each sample.

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\`                                                                    | The state to start from, instead of |

Returns:

| Type      | Description                                                                    |
| --------- | ------------------------------------------------------------------------------ |
| `ndarray` | The statevector, of shape (2\*\*n_qubits,), or (B, 2\*\*n_qubits) for a batch. |

#### `zero_state_probability(weights, inputs)`

Return `|⟨0…0|ψ⟩|²`, the squared amplitude of `|0…0⟩`.

The basis state `0` is `|0…0⟩` whether qubit `0` is the least or the most significant bit, so no reindexing is needed. The fidelity quantum kernel is built on this method.

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

### `QulacsConfig`

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