## `eigenkit.core`

The internal circuit layer of eigenkit.

This package re-exports the classes and functions that users need, and it registers the default backends when it is imported.

The entry points are:

- `Circuit` creates a circuit that can be run.
- `CircuitBuilder` builds a circuit one gate at a time.
- `AngleEncoding`, `BasicVariational`, `StrongVariational`, `Reuploading`, `CustomBlock` and `assemble` combine blocks into a circuit.
- `X`, `Y`, `Z`, `all_z`, `from_string` and `Observable` build observables.
- `PennyLaneConfig`, `QiskitConfig` and the other configurations hold the settings of each backend.
- `register` gives a configuration a name, so that it can be used again.
- `BaseQuantumSolver` is the base class of every solver that computes its result in a single call.

The top-level `provider=` argument accepts a registered name, a configuration dataclass, or a backend instance.

### `AmplitudeEncoding`

Encode a classical vector as the amplitudes of a quantum state.

The vector is padded with zeros to the next power of two. With `normalize=True`, the default, it is also rescaled to norm `1`. The number of qubits follows from the length of the vector: it is `max(1, ceil(log2(len(amplitudes))))`.

The block adds a single `AmplitudeEncoding` gate. The `data` field of the gate holds the prepared amplitudes as a flat tuple, and each backend loads them with its own state preparation method.

Parameters:

| Name         | Type     | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | Default    |
| ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- |
| `amplitudes` | `object` | The vector to load, as an array-like. Its length sets the number of qubits, and it is padded with zeros to the next power of two. A ValueError is raised if the vector is empty, if all its values are zero, or if it holds a not-a-number (NaN) or infinite value.                                                                                                                                                                                                                                                      | *required* |
| `normalize`  | `bool`   | If True, the vector is rescaled to norm 1 before it is loaded. Pass False when the vector is already a state. The block then stores it unchanged and checks it: a ValueError is raised if its norm differs from 1 by more than 1e-10. This check exists because, without it, the same mistake would be caught or not depending on the backend: the NumPy simulator rescales the vector without a warning, and PennyLane refuses it. The same circuit would then behave differently on different backends. Default: True. | `True`     |

Example

```python
from eigenkit.core.blocks import AmplitudeEncoding, assemble

spec = assemble([AmplitudeEncoding([0.6, 0.8], normalize=False)])
```

#### `build(n_qubits)`

Build the gates of this block for a circuit with `n_qubits` qubits.

Parameters:

| Name       | Type  | Description                                                                                                         | Default    |
| ---------- | ----- | ------------------------------------------------------------------------------------------------------------------- | ---------- |
| `n_qubits` | `int` | The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list. | *required* |

Returns:

| Type           | Description                                                                                                                                          |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `'IRFragment'` | The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit. |

#### `from_dict(d)`

Rebuild a block from the output of `to_dict()`.

Parameters:

| Name | Type   | Description                         | Default    |
| ---- | ------ | ----------------------------------- | ---------- |
| `d`  | `dict` | A dictionary returned by to_dict(). | *required* |

Returns:

| Type                  | Description                    |
| --------------------- | ------------------------------ |
| `'AmplitudeEncoding'` | The rebuilt AmplitudeEncoding. |

#### `required_qubits()`

Return the number of qubits that the padded vector needs.

It is `ceil(log2(len(amplitudes)))`, and at least `1`.

#### `to_dict()`

Return a description of this block that can be saved as JSON.

When a saved model is loaded, the `type` key selects the block class whose `from_dict()` rebuilds the block.

Returns:

| Type   | Description                            |
| ------ | -------------------------------------- |
| `dict` | The fields of the block, and its type. |

### `AngleEncoding`

Encode `in_features` data values as single-qubit rotations.

Feature `i` sets the angle of one rotation on qubit `i`, so the block uses the qubits `0` to `in_features - 1`. This matters only when the block is combined with others. A circuit has as many qubits as its widest block, and when another block in the list needs more qubits, this block leaves the extra qubits unchanged.

The block takes `in_features` inputs and no weights.

Parameters:

| Name          | Type       | Description                                                                                                                                                                  | Default    |
| ------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `in_features` | `int`      | The number of features to encode, one per qubit. It must be positive, and a ValueError is raised otherwise.                                                                  | *required* |
| `rotation`    | `GateName` | The rotation gate: "RX", "RY" or "RZ". Default: "RY".                                                                                                                        | `'RY'`     |
| `scale`       | `float`    | A constant factor applied to every input angle. It is useful when the data is normalised to [0, 1] and the circuit needs angles in [0, 2π]. It must be finite. Default: 1.0. | `1.0`      |

#### `build(n_qubits)`

Build the gates of this block for a circuit with `n_qubits` qubits.

The qubits with an index of `in_features` or more are left unchanged.

Parameters:

| Name       | Type  | Description                                                                                                         | Default    |
| ---------- | ----- | ------------------------------------------------------------------------------------------------------------------- | ---------- |
| `n_qubits` | `int` | The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list. | *required* |

Returns:

| Type         | Description                                                                                                                                          |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `IRFragment` | The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit. |

#### `from_dict(d)`

Rebuild a block from the output of `to_dict()`.

Parameters:

| Name | Type   | Description                         | Default    |
| ---- | ------ | ----------------------------------- | ---------- |
| `d`  | `dict` | A dictionary returned by to_dict(). | *required* |

Returns:

| Type              | Description                |
| ----------------- | -------------------------- |
| `'AngleEncoding'` | The rebuilt AngleEncoding. |

#### `required_qubits()`

Return `in_features`, because the block uses one qubit per feature.

#### `to_dict()`

Return a description of this block that can be saved as JSON.

When a saved model is loaded, the `type` key selects the block class whose `from_dict()` rebuilds the block.

Returns:

| Type   | Description                            |
| ------ | -------------------------------------- |
| `dict` | The fields of the block, and its type. |

### `BaseQuantumSolver`

Bases: `ABC`

Base class for quantum solvers that compute a result in a single call.

Unlike BaseQuantumEstimator, a solver has no training phase and no fitted state, and it does not follow the scikit-learn estimator interface. It receives a mathematical problem directly in `solve()` and returns a structured result object.

Four classes inherit from it: HHL, IQAE, QAOA and VQE. PortfolioRiskEstimator does not, by design. It is a scikit-learn-style estimator with `fit()`, `estimate()` and `var_at()`, and it has no `solve()` method. As a result, `isinstance(x, BaseQuantumSolver)` tells you whether an object can be given a problem directly, and it returns `False` for the estimator.

Each subclass sets its own `provider` default in its signature and passes it to this class. All of them use `"numpy"`. Two of the modes of `IQAE`, a callable oracle and `shot_mode="provider"`, need `provider="pennylane"`.

Parameters:

| Name       | Type           | Description                                                                                                                                                                                                   | Default   |
| ---------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
| `provider` | `ProviderSpec` | The backend to use: a registered name ("numpy", "pennylane", "qiskit" and so on), a config instance, or a backend object. The default is "numpy", the built-in simulator that the core installation includes. | `'numpy'` |

Example

```python
from eigenkit.core import BaseQuantumSolver
from eigenkit.optimization import QAOA

isinstance(QAOA(), BaseQuantumSolver)   # True
```

#### `solve(*args, **kwargs)`

Compute and return the solution.

Each subclass declares the signature that its problem needs. `HHL` takes a matrix and a right-hand side, `IQAE` takes an oracle, `QAOA` takes a problem, and `VQE` takes a Hamiltonian and the number of qubits.

### `BasicVariational`

`depth` trainable layers of single-qubit rotations and entangling gates.

Each layer applies one trainable rotation to every qubit, then a controlled-NOT (CNOT) gate from each qubit to the next one. The block takes `depth * n_qubits` weights and no inputs.

Parameters:

| Name       | Type       | Description                                                                      | Default    |
| ---------- | ---------- | -------------------------------------------------------------------------------- | ---------- |
| `depth`    | `int`      | The number of layers. It must be positive, and a ValueError is raised otherwise. | *required* |
| `rotation` | `GateName` | The rotation gate of the trainable layer: "RX", "RY" or "RZ". Default: "RY".     | `'RY'`     |

#### `build(n_qubits)`

Build the gates of this block for a circuit with `n_qubits` qubits.

The layers act on every qubit of the circuit.

Parameters:

| Name       | Type  | Description                                                                                                         | Default    |
| ---------- | ----- | ------------------------------------------------------------------------------------------------------------------- | ---------- |
| `n_qubits` | `int` | The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list. | *required* |

Returns:

| Type         | Description                                                                                                                                          |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `IRFragment` | The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit. |

#### `from_dict(d)`

Rebuild a block from the output of `to_dict()`.

Parameters:

| Name | Type   | Description                         | Default    |
| ---- | ------ | ----------------------------------- | ---------- |
| `d`  | `dict` | A dictionary returned by to_dict(). | *required* |

Returns:

| Type                 | Description                   |
| -------------------- | ----------------------------- |
| `'BasicVariational'` | The rebuilt BasicVariational. |

#### `required_qubits()`

Return `1`, because the block extends to all the qubits of the circuit.

#### `to_dict()`

Return a description of this block that can be saved as JSON.

When a saved model is loaded, the `type` key selects the block class whose `from_dict()` rebuilds the block.

Returns:

| Type   | Description                            |
| ------ | -------------------------------------- |
| `dict` | The fields of the block, and its type. |

### `Circuit`

A compiled circuit, ready to evaluate.

It holds a `CircuitSpec`, the observables to measure and a compiled backend handle. The circuit is compiled once, when the instance is created, and every call reuses the compiled handle. The instance does not store weights: you pass the `weights` at each call.

This is the NumPy interface. The top-level `Circuit(...)` function of the package returns this class for `interface="numpy"` and a `TorchCircuit` for `interface="torch"`.

Parameters:

| Name          | Type             | Description                                                                                                                                                                                                                                                                                      | Default    |
| ------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- |
| `spec`        | `CircuitSpec`    | The circuit to run, usually built with assemble(blocks).                                                                                                                                                                                                                                         | *required* |
| `observables` | `ObservableLike` | What to measure. None measures Z on every qubit and returns one value per qubit. A single Observable or PauliExpr returns a single value, and a list of them returns one value for each. Default: None.                                                                                          | `None`     |
| `provider`    | `ProviderSpec`   | The backend that runs the circuit. It can be a registered name ("numpy", "pennylane" and others), a configuration instance, or a provider object. Default: "numpy".                                                                                                                              | `'numpy'`  |
| `interface`   | `Interface`      | How a call is evaluated. With "numpy", a call runs the NumPy path and returns NumPy values. With "torch", each call is passed to the registered PyTorch interface, and TorchCircuit builds its core circuit in this way. The top-level Circuit function always passes "numpy". Default: "numpy". | `'numpy'`  |

Example

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

# The circuit stores no weights, so you pass them at every call.
weights = np.zeros(circuit.spec.n_weights)
values = circuit(weights, np.array([0.1, 0.2]))

# Gradients, without PyTorch.
grad = circuit.jacobian(weights, np.array([0.1, 0.2]))
```

#### `draw(*, decimals=3)`

Return a text drawing of the circuit, over several lines.

The drawing does not depend on the provider: it is built from the `CircuitSpec`, not from the compiled backend object. See `eigenkit.core.draw` for the details of the layout.

Parameters:

| Name       | Type  | Description                                                                  | Default |
| ---------- | ----- | ---------------------------------------------------------------------------- | ------- |
| `decimals` | `int` | The number of significant digits shown for a numeric gate angle. Default: 3. | `3`     |

Returns:

| Type  | Description                                             |
| ----- | ------------------------------------------------------- |
| `str` | The drawing, as a single string that contains newlines. |

#### `draw_mpl(**kwargs)`

Draw the circuit as a matplotlib figure, and return `(fig, ax)`.

Gates are drawn as labelled boxes and controls as filled dots, and each measured wire (from `self.observables`) ends with a measurement symbol. As with `draw()`, the picture is built from the `CircuitSpec` and the observables, not from the compiled backend, so it does not depend on the provider.

Pass `style="eigen"` for the brand colours (each gate is coloured by its kind: rotations, `H` and block gates, and measurements), or `style="roles"` to colour the outline of each gate by its role (encoding, trainable or fixed). The default is black on white. Each style has a `"-dark"` variant (for example `"eigen-dark"`) for dark backgrounds, and `"eigen-universal"` is a single image that works on both.

matplotlib is an optional dependency. Install it with `eigenkit[viz]`.

Parameters:

| Name       | Type  | Description                                                                                               | Default |
| ---------- | ----- | --------------------------------------------------------------------------------------------------------- | ------- |
| `**kwargs` | `Any` | Arguments passed to draw_mpl(): decimals, show_params, show_measurements, fontsize, style, legend and ax. | `{}`    |

Returns:

| Name     | Type    | Description                    |
| -------- | ------- | ------------------------------ |
| `fig_ax` | `tuple` | The matplotlib (fig, ax) pair. |

#### `dry_run_info()`

Describe the prepared circuit in a dictionary, without submitting it.

It is available only on backends whose `CompiledCircuit` provides a `dry_run_info` method. Currently, these are Braket (on its local simulator as well as on cloud devices), Qiskit Runtime and IQM. It is useful to check the size of the circuit, the gate counts after transpilation, the number of shots and the number of circuits that each call runs, before you submit to a cloud simulator or a quantum processing unit (QPU). It does not estimate a price.

#### `jacobian(weights, inputs=None, *, input_grad=False)`

Return the gradient of the expectation values with respect to the weights.

It returns `d<O>/d(weights)`, of shape `(n_weights,)` for a single observable or `(n_observables, n_weights)` for a list of observables, like the output of `__call__()`. Pass a single 1-D `inputs` sample. For a batch, call it once for each row.

The NumPy interface can compute every gradient without PyTorch. This method uses the same sequence of gradient methods as the variational optimisation tools and the PyTorch interface. It uses the backend's own differentiation when the backend has one (the analytic adjoint method of the NumPy simulator, the differentiation of PennyLane, and so on), and otherwise the parameter-shift rule, which works on any backend.

With `input_grad=True`, it returns `(weight_jacobian, input_jacobian)`, where `input_jacobian` is `d<O>/d(inputs)`, with the same leading shape.

Parameters:

| Name         | Type      | Description                                                                   | Default                                                                                                                                                                                                                                  |
| ------------ | --------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `weights`    | `ndarray` | The weight vector, of shape (n_weights,).                                     | *required*                                                                                                                                                                                                                               |
| `inputs`     | \`ndarray | None\`                                                                        | One 1-D sample, of shape (n_inputs,). This method takes a single sample, so for a batch, call it once for each row. None works only for a circuit that takes no inputs, and it raises a ValueError for any other circuit. Default: None. |
| `input_grad` | `bool`    | If True, also return the jacobian with respect to the inputs. Default: False. | `False`                                                                                                                                                                                                                                  |

Returns:

| Name       | Type      | Description               |
| ---------- | --------- | ------------------------- |
| `jacobian` | \`ndarray | tuple[ndarray, ndarray]\` |

Raises:

| Type           | Description                                                                                     |
| -------------- | ----------------------------------------------------------------------------------------------- |
| `RuntimeError` | On a hardware backend whose configuration leaves enable_gradients off (gradient_method="none"). |

#### `probabilities(weights, inputs=None)`

Return the measurement distribution over all `2**n_qubits` basis states.

The basis states are ordered with qubit `0` as the most significant bit. The distribution is used to decode samples. For example, the quantum approximate optimisation algorithm (QAOA) reads the most probable bitstrings from the optimised state.

Parameters:

| Name      | Type      | Description                               | Default                                                                                                                              |
| --------- | --------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `weights` | `ndarray` | The weight vector, of shape (n_weights,). | *required*                                                                                                                           |
| `inputs`  | \`ndarray | None\`                                    | One 1-D sample. None works only for a circuit that takes no inputs, and it raises a ValueError for any other circuit. Default: None. |

Returns:

| Type      | Description                                                       |
| --------- | ----------------------------------------------------------------- |
| `ndarray` | The probabilities of all basis states, of shape (2\*\*n_qubits,). |

Raises:

| Type                  | Description                                                                                                  |
| --------------------- | ------------------------------------------------------------------------------------------------------------ |
| `NotImplementedError` | On a backend that has neither a probabilities() method nor a state vector to compute the probabilities from. |

#### `resources(**kwargs)`

Report the resources that the circuit uses once transpiled for its target.

It reports the native gate counts, the number of two-qubit gates and the two-qubit depth, the physical qubits chosen by the layout, the number of two-qubit gates added by routing (compared with the same circuit on the native gates with all-to-all connectivity), and the success probability predicted by the error rates of the target. Nothing is submitted.

Parameters:

| Name       | Type  | Description                                                                                                   | Default |
| ---------- | ----- | ------------------------------------------------------------------------------------------------------------- | ------- |
| `**kwargs` | `Any` | Arguments passed to the backend, for example measured=False on Qiskit Runtime for the bare estimator circuit. | `{}`    |

Returns:

| Type   | Description                                                       |
| ------ | ----------------------------------------------------------------- |
| `dict` | A dictionary. See QiskitRuntimeCompiled.resources() for its keys. |

Raises:

| Type                  | Description                                                                                                                                                          |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NotImplementedError` | On backends that do not transpile for a device, which includes every simulator. To inspect a device locally, use Qiskit Runtime or IQM with a fake backend_instance. |

#### `sample(weights, inputs=None)`

Return the raw measurement histogram, which maps each bitstring to a count.

It is what a sampling backend measured, before any averaging. These are the counts behind `probabilities()`, and it is the only form that stays usable when the circuit has too many qubits for a dense distribution. In each bitstring, qubit `0` is the most significant bit, so `"100"` means that qubit `0` was measured in state `1`.

Only sampling backends can return a histogram, because it needs a finite number of shots. Exact statevector simulators raise an error instead of making up a histogram. Use `probabilities()` with those.

Parameters:

| Name      | Type      | Description                               | Default                                                                                                                              |
| --------- | --------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `weights` | `ndarray` | The weight vector, of shape (n_weights,). | *required*                                                                                                                           |
| `inputs`  | \`ndarray | None\`                                    | One 1-D sample. None works only for a circuit that takes no inputs, and it raises a ValueError for any other circuit. Default: None. |

Returns:

| Name     | Type             | Description                                                                                                    |
| -------- | ---------------- | -------------------------------------------------------------------------------------------------------------- |
| `counts` | `dict[str, int]` | The number of shots for each measured bitstring. The counts add up to the number of shots set for the backend. |

Raises:

| Type                  | Description                                                                                        |
| --------------------- | -------------------------------------------------------------------------------------------------- |
| `NotImplementedError` | On backends that do not sample. This includes every exact simulator, which has no shots to report. |

#### `statevector(weights, inputs=None, initial_state=None)`

Return the full complex statevector after the circuit runs.

It is a flat array of length `2**n_qubits`, ordered with qubit `0` as the most significant bit. A 2-D `inputs` batch returns an array of shape `(B, 2**n_qubits)`. With `initial_state`, a normalised flat statevector in the same order, the evolution starts from that state instead of `|0…0⟩`. Reading a state out and passing it back in this way is the basic operation behind the state carried over in quantum reservoirs and behind amplitude encoding.

Parameters:

| Name            | Type      | Description                               | Default                                                                                                                                            |
| --------------- | --------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `weights`       | `ndarray` | The weight vector, of shape (n_weights,). | *required*                                                                                                                                         |
| `inputs`        | \`ndarray | None\`                                    | A 1-D sample, or a 2-D batch. None works only for a circuit that takes no inputs, and it raises a ValueError for any other circuit. Default: None. |
| `initial_state` | \`ndarray | None\`                                    | The state to start the evolution from, instead of                                                                                                  |

Returns:

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

Raises:

| Type                  | Description                                                                                                                 |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `NotImplementedError` | On backends without access to the statevector, which is the case for most quantum processing unit (QPU) and cloud backends. |

#### `submit(weights, inputs=None)`

Submit a job without waiting for it, and return a `JobHandle`.

`handle.result()` returns the expectation value of each observable.

Parameters:

| Name      | Type      | Description                               | Default                                                                                                                              |
| --------- | --------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `weights` | `ndarray` | The weight vector, of shape (n_weights,). | *required*                                                                                                                           |
| `inputs`  | \`ndarray | None\`                                    | One 1-D sample. None works only for a circuit that takes no inputs, and it raises a ValueError for any other circuit. Default: None. |

Returns:

| Name     | Type        | Description                     |
| -------- | ----------- | ------------------------------- |
| `handle` | `JobHandle` | A handle for the submitted job. |

Raises:

| Type                  | Description                                                                                                          |
| --------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `NotImplementedError` | If the CompiledCircuit of the backend does not provide submit. Currently, Braket, Qiskit Runtime and IQM provide it. |

#### `transpiled_circuit(**kwargs)`

Return the `qiskit.QuantumCircuit` that a hardware backend submits.

The circuit is transpiled for the target of the backend. Use it to draw the circuit (`.draw("mpl")`) or to inspect it.

Raises:

| Type                  | Description                                     |
| --------------------- | ----------------------------------------------- |
| `NotImplementedError` | On backends that do not transpile for a device. |

#### `zero_state_probabilities(weights, inputs)`

Compute `zero_state_probability()` for every row of a batch of inputs.

`inputs` has shape `(B, n_inputs)`, and the result is an array of shape `(B,)` of all-zeros probabilities. Backends that provide a batched `zero_state_probabilities` (such as the NumPy simulator) compute the whole batch at once. The other backends call the single-sample method once for each row. This is what lets QuantumKernel build a fidelity matrix without a Python loop over pairs of samples.

Parameters:

| Name      | Type      | Description                               | Default    |
| --------- | --------- | ----------------------------------------- | ---------- |
| `weights` | `ndarray` | The weight vector, of shape (n_weights,). | *required* |
| `inputs`  | `ndarray` | A batch, of shape (B, n_inputs).          | *required* |

Returns:

| Type      | Description                                                        |
| --------- | ------------------------------------------------------------------ |
| `ndarray` | One all-zeros probability for each row, as an array of shape (B,). |

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

Return the probability of measuring the all-zeros state after the circuit.

It is useful for quantities based on fidelity: quantum kernels built from inversion circuits, swap tests and the Bures distance. Each backend computes it in its own way. PennyLane returns `qml.probs(...)[0]`, the Qiskit and NumPy simulators return the squared amplitude of the all-zeros basis state, Braket uses its own probability result, and IQM and Qiskit Runtime estimate it from shots. This method does not go through the `Observable` path, because the value is not the expectation value of a Pauli operator.

Parameters:

| Name      | Type      | Description                               | Default                                                                                                                              |
| --------- | --------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `weights` | `ndarray` | The weight vector, of shape (n_weights,). | *required*                                                                                                                           |
| `inputs`  | \`ndarray | None\`                                    | One 1-D sample. None works only for a circuit that takes no inputs, and it raises a ValueError for any other circuit. Default: None. |

Returns:

| Type    | Description                         |
| ------- | ----------------------------------- |
| `float` | The probability, a float in [0, 1]. |

Raises:

| Type                  | Description                                                    |
| --------------------- | -------------------------------------------------------------- |
| `NotImplementedError` | If the active backend does not provide zero_state_probability. |

### `CircuitBuilder`

Build a `CircuitSpec` one gate at a time.

Example

```python
qc = CircuitBuilder(n_qubits=3)
weights = qc.add_weights(6)
inputs = qc.add_inputs(3)
for qubit in range(3):
    qc.ry(inputs[qubit], qubit=qubit)
for depth in range(2):
    for qubit in range(3):
        qc.ry(weights[depth * 3 + qubit], qubit=qubit)
    for qubit in range(2):
        qc.cnot(qubit, qubit + 1)
spec = qc.build()
```

Parameters:

| Name       | Type  | Description                                                                                                             | Default    |
| ---------- | ----- | ----------------------------------------------------------------------------------------------------------------------- | ---------- |
| `n_qubits` | `int` | The number of qubits of the circuit being built. Every qubit index that a gate uses must be between 0 and n_qubits - 1. | *required* |

Raises:

| Type         | Description                  |
| ------------ | ---------------------------- |
| `ValueError` | If n_qubits is not positive. |

#### `n_inputs`

The number of input features that `add_inputs()` has reserved so far.

The built CircuitSpec has the same number.

#### `n_qubits`

The number of qubits of the circuit, as given to the constructor.

Every qubit index that a gate uses must be smaller than this number, and the number never changes.

#### `n_weights`

The number of trainable weights that `add_weights()` has reserved so far.

The built CircuitSpec has the same number.

#### `add_inputs(count)`

Reserve `count` input features and return references to them.

This is the counterpart of `add_weights()` for data instead of trainable parameters.

Parameters:

| Name    | Type  | Description                              | Default    |
| ------- | ----- | ---------------------------------------- | ---------- |
| `count` | `int` | The number of input features to reserve. | *required* |

Returns:

| Type             | Description                                       |
| ---------------- | ------------------------------------------------- |
| `list[InputRef]` | One InputRef for each reserved feature, in order. |

#### `add_weights(count)`

Reserve `count` trainable weights and return references to them.

Pass a returned `WeightRef` wherever a gate takes an angle. The indices are local to this builder and start at `0`. When the block is combined with others, `assemble` shifts them to their positions in the whole circuit.

Parameters:

| Name    | Type  | Description                       | Default    |
| ------- | ----- | --------------------------------- | ---------- |
| `count` | `int` | The number of weights to reserve. | *required* |

Returns:

| Type              | Description                                       |
| ----------------- | ------------------------------------------------- |
| `list[WeightRef]` | One WeightRef for each reserved weight, in order. |

#### `build()`

Return the current state of the builder as an immutable `CircuitSpec`.

#### `cnot(control, target)`

Add a controlled-NOT (CNOT) gate.

Parameters:

| Name      | Type  | Description                       | Default    |
| --------- | ----- | --------------------------------- | ---------- |
| `control` | `int` | The qubit that controls the flip. | *required* |
| `target`  | `int` | The qubit that is flipped.        | *required* |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `'CircuitBuilder'` | This builder, so that calls can be chained. |

Raises:

| Type         | Description                               |
| ------------ | ----------------------------------------- |
| `ValueError` | If control and target are the same qubit. |

#### `cphase(angle, control, target)`

Add a controlled phase shift gate.

The gate is symmetric: the control and the target play the same role.

Parameters:

| Name      | Type            | Description                                                                     | Default    |
| --------- | --------------- | ------------------------------------------------------------------------------- | ---------- |
| `angle`   | `ParameterLike` | The phase. It can be a number or a symbolic reference.                          | *required* |
| `control` | `int`           | One of the two qubits.                                                          | *required* |
| `target`  | `int`           | The other qubit. The gate acts in the same way if the two qubits are exchanged. | *required* |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `'CircuitBuilder'` | This builder, so that calls can be chained. |

#### `cry(angle, control, target)`

Add a controlled `RY` gate.

Parameters:

| Name      | Type            | Description                                                     | Default    |
| --------- | --------------- | --------------------------------------------------------------- | ---------- |
| `angle`   | `ParameterLike` | The rotation angle. It can be a number or a symbolic reference. | *required* |
| `control` | `int`           | The qubit that controls the rotation.                           | *required* |
| `target`  | `int`           | The qubit that is rotated.                                      | *required* |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `'CircuitBuilder'` | This builder, so that calls can be chained. |

#### `h(qubit)`

Add a Hadamard gate.

Parameters:

| Name    | Type  | Description       | Default    |
| ------- | ----- | ----------------- | ---------- |
| `qubit` | `int` | The target qubit. | *required* |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `'CircuitBuilder'` | This builder, so that calls can be chained. |

#### `mcx(*qubits)`

Add a multi-controlled `X` gate.

All the qubits except the last one are controls.

Parameters:

| Name      | Type  | Description                                                                           | Default |
| --------- | ----- | ------------------------------------------------------------------------------------- | ------- |
| `*qubits` | `int` | Two or more distinct qubits. The controls come first, and the target is the last one. | `()`    |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `'CircuitBuilder'` | This builder, so that calls can be chained. |

Raises:

| Type         | Description                                                    |
| ------------ | -------------------------------------------------------------- |
| `ValueError` | If fewer than two qubits are given, or if a qubit is repeated. |

#### `mcz(*qubits)`

Add a multi-controlled `Z` gate.

All the qubits except the last one are controls.

Parameters:

| Name      | Type  | Description                                                                           | Default |
| --------- | ----- | ------------------------------------------------------------------------------------- | ------- |
| `*qubits` | `int` | Two or more distinct qubits. The controls come first, and the target is the last one. | `()`    |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `'CircuitBuilder'` | This builder, so that calls can be chained. |

Raises:

| Type         | Description                                                    |
| ------------ | -------------------------------------------------------------- |
| `ValueError` | If fewer than two qubits are given, or if a qubit is repeated. |

#### `pauli_exponential(paulis, angle, coefficient=1.0)`

Add the exponential of a product of Pauli operators.

The gate is `exp(-i * coefficient * angle * P)`, where `P` is the product of `paulis`. Each element of `paulis` is a pair `(op, qubit)`, where `op` is `"X"`, `"Y"` or `"Z"`, and the qubits are distinct.

This is the Trotter building block that the quantum approximate optimisation algorithm (QAOA) needs for its cost and mixer layers, and that the unitary coupled-cluster singles and doubles (UCCSD) ansatz needs for its excitation operators. It is built from the core gates only. First, `H` gates (for `X`) and `RX(π/2)` gates (for `Y`) change each factor to the `Z` basis. Then a ladder of controlled-NOT (CNOT) gates accumulates the parity on the last qubit, and a central `RZ(2 * coefficient * angle)` is applied (because `RZ(φ) = exp(-i φ Z / 2)`). Finally, the inverse of the first steps is applied.

Parameters:

| Name          | Type                        | Description                                                                                                                                                                | Default    |
| ------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `paulis`      | `Sequence[tuple[str, int]]` | The Pauli product, as (operator, qubit) pairs, where the operator is "X", "Y" or "Z" and the qubits are distinct. An empty sequence is the identity, and nothing is added. | *required* |
| `angle`       | `ParameterLike`             | The rotation angle. It can be a number or a symbolic reference.                                                                                                            | *required* |
| `coefficient` | `float`                     | A number that multiplies the angle. This is where the weight of a Hamiltonian term goes. Default: 1.0.                                                                     | `1.0`      |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `'CircuitBuilder'` | This builder, so that calls can be chained. |

#### `phase_shift(angle, qubit)`

Add a phase shift gate `R(φ)`.

The gate multiplies `|1⟩` by `e^(iφ)` and leaves `|0⟩` unchanged.

Parameters:

| Name    | Type            | Description                                              | Default    |
| ------- | --------------- | -------------------------------------------------------- | ---------- |
| `angle` | `ParameterLike` | The phase φ. It can be a number or a symbolic reference. | *required* |
| `qubit` | `int`           | The target qubit.                                        | *required* |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `'CircuitBuilder'` | This builder, so that calls can be chained. |

#### `rx(angle, qubit)`

Add a rotation about the `X` axis.

Parameters:

| Name    | Type            | Description                                                                                                                          | Default    |
| ------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------- |
| `angle` | `ParameterLike` | The rotation angle. It can be a number, or a WeightRef, an InputRef or an expression, which is evaluated when the circuit is called. | *required* |
| `qubit` | `int`           | The target qubit.                                                                                                                    | *required* |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `'CircuitBuilder'` | This builder, so that calls can be chained. |

#### `ry(angle, qubit)`

Add a rotation about the `Y` axis.

Parameters:

| Name    | Type            | Description                                                                           | Default    |
| ------- | --------------- | ------------------------------------------------------------------------------------- | ---------- |
| `angle` | `ParameterLike` | The rotation angle. It can be a number, or a WeightRef, an InputRef or an expression. | *required* |
| `qubit` | `int`           | The target qubit.                                                                     | *required* |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `'CircuitBuilder'` | This builder, so that calls can be chained. |

#### `rz(angle, qubit)`

Add a rotation about the `Z` axis.

Parameters:

| Name    | Type            | Description                                                                           | Default    |
| ------- | --------------- | ------------------------------------------------------------------------------------- | ---------- |
| `angle` | `ParameterLike` | The rotation angle. It can be a number, or a WeightRef, an InputRef or an expression. | *required* |
| `qubit` | `int`           | The target qubit.                                                                     | *required* |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `'CircuitBuilder'` | This builder, so that calls can be chained. |

#### `s(qubit)`

Add an `S` gate, which is a quarter turn about the `Z` axis.

Its inverse is `sdg()`.

Parameters:

| Name    | Type  | Description       | Default    |
| ------- | ----- | ----------------- | ---------- |
| `qubit` | `int` | The target qubit. | *required* |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `'CircuitBuilder'` | This builder, so that calls can be chained. |

#### `sdg(qubit)`

Add an S-dagger gate (`Sdg`), which is the inverse of `s()`.

Parameters:

| Name    | Type  | Description       | Default    |
| ------- | ----- | ----------------- | ---------- |
| `qubit` | `int` | The target qubit. | *required* |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `'CircuitBuilder'` | This builder, so that calls can be chained. |

#### `swap(qubit0, qubit1)`

Add a `SWAP` gate, which exchanges the states of two qubits.

Parameters:

| Name     | Type  | Description       | Default    |
| -------- | ----- | ----------------- | ---------- |
| `qubit0` | `int` | The first qubit.  | *required* |
| `qubit1` | `int` | The second qubit. | *required* |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `'CircuitBuilder'` | This builder, so that calls can be chained. |

#### `to_block()`

Wrap the current state of the builder in a `CustomBlock`.

This is useful to build a part of a circuit one gate at a time, and then to combine it with other blocks. When the blocks are assembled, the indices of the `WeightRef` and `InputRef` objects of this block are shifted to their positions in the whole circuit.

#### `x(qubit)`

Add a Pauli `X` gate, which is a bit flip.

Parameters:

| Name    | Type  | Description       | Default    |
| ------- | ----- | ----------------- | ---------- |
| `qubit` | `int` | The target qubit. | *required* |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `'CircuitBuilder'` | This builder, so that calls can be chained. |

#### `z(qubit)`

Add a Pauli `Z` gate, which is a phase flip.

Parameters:

| Name    | Type  | Description       | Default    |
| ------- | ----- | ----------------- | ---------- |
| `qubit` | `int` | The target qubit. | *required* |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `'CircuitBuilder'` | This builder, so that calls can be chained. |

### `CircuitSpec`

A whole circuit as data.

It holds the list of gates, the number of qubits, and the lengths of the weight and input vectors that the gates refer to. Every provider compiles this object, and `assemble` builds it from a list of blocks. It holds no weights and no observables: the weights are passed at each call, and the observables are set by the `Circuit` that wraps the spec.

Attributes:

| Name        | Type               | Description                                                                |
| ----------- | ------------------ | -------------------------------------------------------------------------- |
| `n_qubits`  | `int`              | The number of qubits of the circuit.                                       |
| `n_weights` | `int`              | The length of the weight vector that the gates refer to through WeightRef. |
| `n_inputs`  | `int`              | The length of the input vector that the gates refer to through InputRef.   |
| `gates`     | `tuple[Gate, ...]` | The gates, in the order in which they are applied.                         |

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

### `CustomBlock`

A block made from the gates of a `CircuitBuilder`.

`CircuitBuilder.to_block()` creates it. It is useful when you want to write a part of a circuit gate by gate and combine it with the standard blocks.

Parameters:

| Name        | Type               | Description                                                                                                                     | Default    |
| ----------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `n_qubits`  | `int`              | The number of qubits that the builder was given, which is also the number of qubits that this block needs. It must be positive. | *required* |
| `gates`     | `tuple[Gate, ...]` | The gates that the builder added, in the order in which they are applied.                                                       | *required* |
| `n_weights` | `int`              | The number of trainable weights that these gates use. It must not be negative.                                                  | *required* |
| `n_inputs`  | `int`              | The number of input features that these gates use. It must not be negative.                                                     | *required* |

#### `build(n_qubits)`

Build the gates of this block for a circuit with `n_qubits` qubits.

Parameters:

| Name       | Type  | Description                                                                                                         | Default    |
| ---------- | ----- | ------------------------------------------------------------------------------------------------------------------- | ---------- |
| `n_qubits` | `int` | The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list. | *required* |

Returns:

| Type         | Description                                                                                                                                          |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `IRFragment` | The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit. |

#### `required_qubits()`

Return `n_qubits`, the number of qubits that the builder was given.

### `Grover`

The Grover operator `Q = A · S₀ · A† · S_ψ₁` used for amplitude estimation.

This is the operator that iterative quantum amplitude estimation (IQAE) uses. The block builds it from a state-preparation oracle `A`, given as a `CircuitSpec`, and from these standard parts:

- **`S_ψ₁`** flips the phase of the ancilla qubit, which is the last qubit. It is the gate `Z(n-1)`.
- **`A†`** is the adjoint of the oracle. It uses the same weights and inputs as `A`.
- **`S₀`** is the reflection about the all-zero state: `X^⊗n → MCZ → X^⊗n`, where `MCZ` is a multi-controlled `Z` gate.
- **`A`** is the oracle.

The gates are applied to the state in this order, from left to right:

```text
Z(ancilla) → A† → S₀ → A
```

`A` and `A†` share their weights and inputs, so `fragment.n_weights == a_circuit.n_weights`: the count is not doubled.

Parameters:

| Name        | Type          | Description                                                                                                      | Default    |
| ----------- | ------------- | ---------------------------------------------------------------------------------------------------------------- | ---------- |
| `a_circuit` | `CircuitSpec` | The state-preparation oracle A, as a CircuitSpec with at least one qubit. Its last qubit is used as the ancilla. | *required* |

Example

```python
from eigenkit.core.blocks import Grover, assemble
from eigenkit.core.builder import CircuitBuilder

qb = CircuitBuilder(n_qubits=2)
qb.h(0).cnot(0, 1)
spec = assemble([Grover(a_circuit=qb.build())])
```

#### `build(n_qubits)`

Build the gates of this block for a circuit with `n_qubits` qubits.

The oracle `A` and its adjoint `A†` share their weights and inputs, so these are not counted twice.

Parameters:

| Name       | Type  | Description                                                                                                         | Default    |
| ---------- | ----- | ------------------------------------------------------------------------------------------------------------------- | ---------- |
| `n_qubits` | `int` | The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list. | *required* |

Returns:

| Type           | Description                                                                                                                                          |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `'IRFragment'` | The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit. |

#### `from_dict(d)`

Rebuild a block from the output of `to_dict()`.

Parameters:

| Name | Type   | Description                         | Default    |
| ---- | ------ | ----------------------------------- | ---------- |
| `d`  | `dict` | A dictionary returned by to_dict(). | *required* |

Returns:

| Type       | Description         |
| ---------- | ------------------- |
| `'Grover'` | The rebuilt Grover. |

#### `required_qubits()`

Return the number of qubits of the oracle.

The operator adds no qubits of its own.

#### `to_dict()`

Return a description of this block that can be saved as JSON.

When a saved model is loaded, the `type` key selects the block class whose `from_dict()` rebuilds the block.

Returns:

| Type   | Description                            |
| ------ | -------------------------------------- |
| `dict` | The fields of the block, and its type. |

### `JobHandle`

Bases: `Protocol`

A handle to a job running on a quantum processing unit (QPU) or in the cloud.

`compiled.submit(...)` returns it without waiting for the job. Call `.result()` to wait for the job and combine its results.

#### `result()`

Wait until the job finishes, then return its expectation values.

The shape follows the same rule as CompiledCircuit.run(): `(n_obs,)` for a single sample, and `(B, n_obs)` for a batch.

#### `status()`

Return the state of the job as the provider reports it, without waiting.

The strings are the provider's own. For example, Braket returns `"CREATED"`, `"QUEUED"`, `"RUNNING"`, `"COMPLETED"`, `"FAILED"` or `"CANCELLED"`. Compare them within one provider, not across providers.

### `NumpyConfig`

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

### `NumpySimulator`

The provider for the reference NumPy statevector simulator.

It is registered automatically as `"numpy"`. It uses only NumPy and no optional dependency, so it is always available.

It declares `gradient_method = "adjoint"`, so the gradient code of the core package uses NumpyCompiled.jacobian() (analytic, in reverse mode, with one forward pass and one backward pass whatever the number of parameters) instead of the parameter-shift rule, whose cost is `O(n_params)`.

#### `capabilities`

What this backend supports.

It runs every gate of the library, evaluates a batch of inputs in one call, returns exact expectation values only (it does not sample shots), and has no limit on the number of qubits. See Capabilities.

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

Build the backend from a device configuration.

Parameters:

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

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

### `Observable`

A Hermitian operator to measure, written as a sum of Pauli terms.

You can build one with the `z`, `x` and `y` methods for a single Pauli operator on one qubit, with `from_string` for a whole sum, or with the `X`, `Y` and `Z` functions of `eigenkit.core.observables`, which support arithmetic.

Attributes:

| Name    | Type                    | Description                                                                                                                                                                                                                                                             |
| ------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `terms` | `tuple[PauliTerm, ...]` | The Pauli terms of the sum. It can be empty: jordan_wigner returns an empty observable, together with an offset, when the operator reduces to a multiple of the identity. However, Circuit refuses to measure an empty observable, because there is nothing to measure. |

#### `from_string(text)`

Parse a sum of Pauli terms written as a string.

Examples of such strings are `"0.5 Z0 X1 + 0.3 Y2"` and `"-Z0 + Z1"`.

The grammar is as follows. The terms are joined by `+` or `-`. Each term starts with an optional numeric coefficient, in plain or scientific notation (`1e-3` is accepted). The coefficient is followed by an optional `*` and by one or more `[XYZ]<qubit>` operators on distinct qubits. Any amount of whitespace is accepted, including none.

Every part of the string must be a valid term, and every term must contain Pauli operators. A constant term (`"0.5 + Z0"`, `"0.5 I"`) raises an error instead of being dropped from the sum: an `Observable` holds only Pauli terms, and the constant must be kept next to it as an offset.

Parameters:

| Name   | Type  | Description          | Default    |
| ------ | ----- | -------------------- | ---------- |
| `text` | `str` | The string to parse. | *required* |

Returns:

| Type           | Description            |
| -------------- | ---------------------- |
| `'Observable'` | The parsed observable. |

Raises:

| Type         | Description                                                                                                                    |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| `ValueError` | If text is empty, if it contains a constant term, if a part of it is not a valid term, or if a term uses the same qubit twice. |

#### `x(qubit)`

Return the observable `X` on the single qubit `qubit`.

Parameters:

| Name    | Type  | Description             | Default    |
| ------- | ----- | ----------------------- | ---------- |
| `qubit` | `int` | The index of the qubit. | *required* |

#### `y(qubit)`

Return the observable `Y` on the single qubit `qubit`.

Parameters:

| Name    | Type  | Description             | Default    |
| ------- | ----- | ----------------------- | ---------- |
| `qubit` | `int` | The index of the qubit. | *required* |

#### `z(qubit)`

Return the observable `Z` on the single qubit `qubit`.

Parameters:

| Name    | Type  | Description             | Default    |
| ------- | ----- | ----------------------- | ---------- |
| `qubit` | `int` | The index of the qubit. | *required* |

### `QubitUnitary`

Apply any unitary matrix to the given qubits.

The matrix is stored in the `data` field of the gate as a nested tuple, so that the `Gate` stays hashable.

Parameters:

| Name      | Type              | Description                                                                                                         | Default    |
| --------- | ----------------- | ------------------------------------------------------------------------------------------------------------------- | ---------- |
| `unitary` | `object`          | A 2^n × 2^n matrix, as an array-like, where n is len(wires). It is checked to be unitary when the block is created. | *required* |
| `wires`   | `tuple[int, ...]` | The target qubits, in the order that the matrix expects.                                                            | *required* |

Example

```python
import numpy as np
from eigenkit.core.blocks import QubitUnitary, assemble

U = np.array([[0, 1], [1, 0]])  # the Pauli X matrix
spec = assemble([QubitUnitary(U, wires=(0,))])
```

#### `build(n_qubits)`

Build the gates of this block for a circuit with `n_qubits` qubits.

Parameters:

| Name       | Type  | Description                                                                                                         | Default    |
| ---------- | ----- | ------------------------------------------------------------------------------------------------------------------- | ---------- |
| `n_qubits` | `int` | The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list. | *required* |

Returns:

| Type           | Description                                                                                                                                          |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `'IRFragment'` | The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit. |

#### `from_dict(d)`

Rebuild a block from the output of `to_dict()`.

Parameters:

| Name | Type   | Description                         | Default    |
| ---- | ------ | ----------------------------------- | ---------- |
| `d`  | `dict` | A dictionary returned by to_dict(). | *required* |

Returns:

| Type             | Description               |
| ---------------- | ------------------------- |
| `'QubitUnitary'` | The rebuilt QubitUnitary. |

#### `required_qubits()`

Return `max(wires) + 1`, one more than the highest target qubit.

#### `to_dict()`

Return a description of this block that can be saved as JSON.

When a saved model is loaded, the `type` key selects the block class whose `from_dict()` rebuilds the block.

Returns:

| Type   | Description                            |
| ------ | -------------------------------------- |
| `dict` | The fields of the block, and its type. |

### `Reuploading`

Data reuploading: the input is encoded several times, between trainable layers.

The block repeats an encoding of the input followed by a variational layer, `depth` times. Each encoding can have a trainable scale for each feature and repetition.

The arguments give two patterns:

1. Standard reuploading (the default; Pérez-Salinas et al. 2020). The same input is encoded `depth` times, with new variational weights between the encodings.
1. Trainable feature scaling (`trainable_scale=True`; Schuld et al. 2021). Each encoding rotation takes the angle `λ_{d,i} * x_i`, where each pair of repetition `d` and feature `i` has its own trainable `λ_{d,i}`.

Parameters:

| Name                   | Type       | Description                                                                                                                                                                                                         | Default    |
| ---------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `in_features`          | `int`      | The total number of input features. It must be positive.                                                                                                                                                            | *required* |
| `depth`                | `int`      | The number of times the whole sequence is repeated. It must be positive.                                                                                                                                            | *required* |
| `encoding_rotation`    | `GateName` | The rotation gate of the encoding: "RX", "RY" or "RZ". Default: "RY".                                                                                                                                               | `'RY'`     |
| `variational_rotation` | `GateName` | The rotation gate of the variational layer: "RX", "RY" or "RZ". It is used only when variational_type="basic". Default: "RY".                                                                                       | `'RY'`     |
| `variational_type`     | `str`      | "basic", for one rotation per qubit followed by a ladder of controlled-NOT (CNOT) gates, or "strong", for RZ, RY and RZ rotations on every qubit followed by a ladder of CNOT gates. Default: "basic".              | `'basic'`  |
| `trainable_scale`      | `bool`     | If True, each encoding rotation gets a trainable scale for each repetition and each feature. The CUDA Quantum backend does not support it, because it allows only limited arithmetic on parameters. Default: False. | `False`    |

Weight count

- With `variational_type="basic"` and no trainable scale, the block has `depth * in_features` weights.
- With `variational_type="strong"` and no trainable scale, it has `3 * depth * in_features` weights.
- `trainable_scale=True` adds `depth * in_features` weights.

#### `build(n_qubits)`

Build the gates of this block for a circuit with `n_qubits` qubits.

Parameters:

| Name       | Type  | Description                                                                                                         | Default    |
| ---------- | ----- | ------------------------------------------------------------------------------------------------------------------- | ---------- |
| `n_qubits` | `int` | The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list. | *required* |

Returns:

| Type         | Description                                                                                                                                          |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `IRFragment` | The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit. |

#### `from_dict(d)`

Rebuild a block from the output of `to_dict()`.

Parameters:

| Name | Type   | Description                         | Default    |
| ---- | ------ | ----------------------------------- | ---------- |
| `d`  | `dict` | A dictionary returned by to_dict(). | *required* |

Returns:

| Type            | Description              |
| --------------- | ------------------------ |
| `'Reuploading'` | The rebuilt Reuploading. |

#### `required_qubits()`

Return `in_features`: one qubit for each feature.

#### `to_dict()`

Return a description of this block that can be saved as JSON.

When a saved model is loaded, the `type` key selects the block class whose `from_dict()` rebuilds the block.

Returns:

| Type   | Description                            |
| ------ | -------------------------------------- |
| `dict` | The fields of the block, and its type. |

### `S0`

Reflection about the all-zero state `|0…0⟩`, built as `X^⊗n → MCZ → X^⊗n`.

The block applies `X` to every qubit, then a multi-controlled `Z` gate (`MCZ`) on all of them, then `X` to every qubit again. It acts on the qubits `0` to `n_qubits - 1`, which are all the qubits of the circuit unless another block in the list needs more. This reflection is the `S₀` part of the Grover operator used by iterative quantum amplitude estimation (IQAE). The Grover block adds the same gates itself.

Parameters:

| Name       | Type  | Description                                                                                | Default    |
| ---------- | ----- | ------------------------------------------------------------------------------------------ | ---------- |
| `n_qubits` | `int` | The number of qubits of the reflection. Every one of them takes part. It must be positive. | *required* |

Example

```python
from eigenkit.core.blocks import S0, assemble

spec = assemble([S0(n_qubits=3)])
```

#### `build(n_qubits)`

Build the gates of this block for a circuit with `n_qubits` qubits.

Parameters:

| Name       | Type  | Description                                                                                                         | Default    |
| ---------- | ----- | ------------------------------------------------------------------------------------------------------------------- | ---------- |
| `n_qubits` | `int` | The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list. | *required* |

Returns:

| Type           | Description                                                                                                                                          |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `'IRFragment'` | The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit. |

#### `from_dict(d)`

Rebuild a block from the output of `to_dict()`.

Parameters:

| Name | Type   | Description                         | Default    |
| ---- | ------ | ----------------------------------- | ---------- |
| `d`  | `dict` | A dictionary returned by to_dict(). | *required* |

Returns:

| Type   | Description     |
| ------ | --------------- |
| `'S0'` | The rebuilt S0. |

#### `required_qubits()`

Return `n_qubits`, the number of qubits that the reflection acts on.

#### `to_dict()`

Return a description of this block that can be saved as JSON.

When a saved model is loaded, the `type` key selects the block class whose `from_dict()` rebuilds the block.

Returns:

| Type   | Description                            |
| ------ | -------------------------------------- |
| `dict` | The fields of the block, and its type. |

### `StrongVariational`

`depth` trainable layers of `RZ`, `RY` and `RZ` rotations and entangling gates.

Each layer applies the rotations `RZ`, `RY` and `RZ` to every qubit, then a controlled-NOT (CNOT) gate from each qubit to the next one. The block is modelled on PennyLane's `StronglyEntanglingLayers`.

The gates are added one layer at a time: all the `RZ` rotations, then all the `RY` rotations, then all the `RZ` rotations, then the CNOT gates. They are not added as three rotations per qubit. The result is the same, because rotations on different qubits commute. The layered order lets the backends that support it apply each layer of rotations in one fast call, in place of `3 * n_qubits` calls on single qubits.

The block takes `3 * depth * n_qubits` weights and no inputs.

Parameters:

| Name    | Type  | Description                                                                      | Default    |
| ------- | ----- | -------------------------------------------------------------------------------- | ---------- |
| `depth` | `int` | The number of layers. It must be positive, and a ValueError is raised otherwise. | *required* |

#### `build(n_qubits)`

Build the gates of this block for a circuit with `n_qubits` qubits.

The layers act on every qubit of the circuit.

Parameters:

| Name       | Type  | Description                                                                                                         | Default    |
| ---------- | ----- | ------------------------------------------------------------------------------------------------------------------- | ---------- |
| `n_qubits` | `int` | The number of qubits of the circuit being assembled, which is the number of qubits of the widest block in the list. | *required* |

Returns:

| Type         | Description                                                                                                                                          |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `IRFragment` | The gates of the block, as an IRFragment. Its weight and input indices start at 0, and they are shifted when the blocks are combined into a circuit. |

#### `from_dict(d)`

Rebuild a block from the output of `to_dict()`.

Parameters:

| Name | Type   | Description                         | Default    |
| ---- | ------ | ----------------------------------- | ---------- |
| `d`  | `dict` | A dictionary returned by to_dict(). | *required* |

Returns:

| Type                  | Description                    |
| --------------------- | ------------------------------ |
| `'StrongVariational'` | The rebuilt StrongVariational. |

#### `required_qubits()`

Return `1`, because the block extends to all the qubits of the circuit.

#### `to_dict()`

Return a description of this block that can be saved as JSON.

When a saved model is loaded, the `type` key selects the block class whose `from_dict()` rebuilds the block.

Returns:

| Type   | Description                            |
| ------ | -------------------------------------- |
| `dict` | The fields of the block, and its type. |

### `VariationalResult`

Bases: `ValueEquality`

Outcome of minimize_expectation().

The result is read-only. Assigning to a field raises `FrozenInstanceError`, and writing into an array field raises `ValueError`. This prevents a mistyped `=` (instead of `==`) or an in-place edit from leaving a result whose scalar fields describe a different run from the array it holds. To get a writable copy of the array, use `np.array(result.field)`.

Results compare by value, including their arrays. They are not hashable, because a result is a collection of numbers and not a key, and a correct hash of a float array is expensive to compute.

Attributes:

| Name                 | Type                | Description                                                                                                                                                                                                                                                                                                                  |
| -------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `optimal_value`      | `float`             | The lowest expectation value reached. When the observable is a Hamiltonian, it is the estimated ground-state energy.                                                                                                                                                                                                         |
| `optimal_parameters` | `ndarray`           | The weights that produced this value, of shape (n_weights,).                                                                                                                                                                                                                                                                 |
| `n_iterations`       | `int`               | The number of iterations reported by the SciPy optimiser. When there is no such count (a custom callable, "spsa", or a SciPy method that does not report one), it is the number of cost evaluations, as in n_evaluations.                                                                                                    |
| `n_evaluations`      | `int`               | The number of circuit evaluations that the optimiser made. With a SciPy method it is usually higher than n_iterations, because a line search costs several evaluations per step. Evaluations made only for bookkeeping are not counted, so a custom optimiser that never called the cost function reports 0.                 |
| `success`            | `bool`              | Whether the run converged. A SciPy optimiser reports it. A custom callable reports it only if it returns (parameters, converged), and otherwise it is inferred from whether the final cost is lower than the starting cost. A False with a plausible energy usually means that the maximum number of iterations was reached. |
| `message`            | `str`               | The termination message of the optimiser. For a custom callable that reported nothing, it gives the cost before and after the run.                                                                                                                                                                                           |
| `history`            | `tuple[float, ...]` | The expectation value at every evaluation, in order. Plot it to see whether the run converged or stalled. It is a tuple, so nothing can be appended to it after the run.                                                                                                                                                     |

#### `__post_init__()`

Make the array field read-only.

Freezing the dataclass prevents a field from being reassigned, but it does not prevent writing into the array that the field holds, and a result that has been written into still reports the scalar values of the original run. The result therefore keeps its own copy, so the array it was built from can still be changed by the caller. To get a writable copy, use `np.array(result.optimal_parameters)`.

### `X(qubit)`

Return Pauli `X` on `qubit`, as an expression that can be added and scaled.

Parameters:

| Name    | Type  | Description             | Default    |
| ------- | ----- | ----------------------- | ---------- |
| `qubit` | `int` | The index of the qubit. | *required* |

Returns:

| Type        | Description     |
| ----------- | --------------- |
| `PauliExpr` | The expression. |

### `Y(qubit)`

Return Pauli `Y` on `qubit`, as an expression that can be added and scaled.

Parameters:

| Name    | Type  | Description             | Default    |
| ------- | ----- | ----------------------- | ---------- |
| `qubit` | `int` | The index of the qubit. | *required* |

Returns:

| Type        | Description     |
| ----------- | --------------- |
| `PauliExpr` | The expression. |

### `Z(qubit)`

Return Pauli `Z` on `qubit`, as an expression that can be added and scaled.

Parameters:

| Name    | Type  | Description             | Default    |
| ------- | ----- | ----------------------- | ---------- |
| `qubit` | `int` | The index of the qubit. | *required* |

Returns:

| Type        | Description     |
| ----------- | --------------- |
| `PauliExpr` | The expression. |

### `__getattr__(name)`

Return the objects that are re-exported only when they are first accessed.

These are the backend classes and configurations. Because of this, importing `core` does not import any framework until one is needed.

### `adjoint(circuit)`

Reverse the order of the gates and invert each gate.

The transform does not depend on the provider. It returns another `CircuitSpec` that any backend can compile, so the transform does not have to be written again for each backend.

Each type of gate is inverted as follows:

- **Rotations with parameters** (`RX`, `RY`, `RZ`, `PhaseShift`, `CRY`, `CPHASE`): the angle parameters are negated.
- **Gates without parameters** (`H`, `X`, `Z`, `CNOT`, `SWAP`, `MCZ`, `MCX`): they are copied unchanged, because applying them twice gives the identity. `S` and `Sdg` are not their own inverse, so each one is replaced by the other.
- **`QubitUnitary(U)`**: the stored matrix is replaced by its conjugate transpose `U†`. It is computed without NumPy, so that the intermediate representation stays pure Python.
- **`ControlledQubitUnitary(U)`**: the target unitary (`data[0]`) is replaced by its conjugate transpose, and the control values (`data[1]`) are kept unchanged.
- **`AmplitudeEncoding`**: the transform flips `gate.adjoint` and leaves the inversion to the backend. PennyLane, for example, applies the conjugate transpose of the state-preparation unitary. The Qiskit backend does not support this adjoint: `Circuit` rejects such a circuit with a `ValueError`, and the backend itself raises `NotImplementedError`.

Parameters:

| Name      | Type          | Description            | Default    |
| --------- | ------------- | ---------------------- | ---------- |
| `circuit` | `CircuitSpec` | The circuit to invert. | *required* |

Returns:

| Type          | Description                                            |
| ------------- | ------------------------------------------------------ |
| `CircuitSpec` | The inverted circuit, with the gates in reverse order. |

### `all_z(n_qubits)`

Return one `Z` observable for each qubit.

This is useful as a default measurement.

Parameters:

| Name       | Type  | Description                          | Default    |
| ---------- | ----- | ------------------------------------ | ---------- |
| `n_qubits` | `int` | The number of qubits of the circuit. | *required* |

Returns:

| Type               | Description                    |
| ------------------ | ------------------------------ |
| `list[Observable]` | One Observable for each qubit. |

### `assemble(blocks)`

Combine blocks into a single `CircuitSpec`.

A `CircuitBuilder` can take the place of a block. It is replaced by the result of its `to_block()`, so a part of a circuit written gate by gate can go directly in the list.

The blocks are combined as follows:

- `n_qubits` is the largest `required_qubits()` of the blocks.
- The weights and the inputs of the blocks are placed one after the other, in the order of the blocks.
- The local `WeightRef` and `InputRef` indices of each block are shifted by the number of weights and inputs of the blocks before it.

Parameters:

| Name     | Type              | Description        | Default                                                                                                                                              |
| -------- | ----------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `blocks` | \`Iterable\[Block | CircuitBuilder\]\` | The blocks to combine, in the order in which they are applied. Any of them can be a CircuitBuilder, which is taken as it is when assemble is called. |

Returns:

| Type          | Description                                   |
| ------------- | --------------------------------------------- |
| `CircuitSpec` | One CircuitSpec that contains all the blocks. |

Raises:

| Type         | Description         |
| ------------ | ------------------- |
| `ValueError` | If blocks is empty. |

Example

```python
from eigenkit import Encoding, Variational
from eigenkit.core import assemble

spec = assemble([Encoding(in_features=4), Variational(depth=2)])
print(spec.n_qubits, spec.n_weights, spec.n_inputs)  # 4 8 4
```

### `from_string(text)`

Parse an observable from a string.

This function is a shortcut for `Observable.from_string`.

Parameters:

| Name   | Type  | Description          | Default    |
| ------ | ----- | -------------------- | ---------- |
| `text` | `str` | The string to parse. | *required* |

Returns:

| Type         | Description            |
| ------------ | ---------------------- |
| `Observable` | The parsed observable. |

### `minimize_expectation(circuit, *, initial_parameters=None, optimizer='L-BFGS-B', max_iterations=None, tol=None, random_state=42, callback=None)`

Minimise `<H>(θ)` for a circuit whose parameters are all variational.

Parameters:

| Name                 | Type                          | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | Default                                                                                                                                                  |
| -------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `circuit`            | `Circuit`                     | A core NumPy Circuit whose single observable is the Hamiltonian H and whose spec.n_inputs == 0 (every parameter is a variational weight). It works with any provider.                                                                                                                                                                                                                                                                                                                                                                                                    | *required*                                                                                                                                               |
| `initial_parameters` | \`ndarray                     | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | The starting θ, of shape (n_weights,). When it is not given, it is drawn at random with the seed random_state.                                           |
| `optimizer`          | `Optimizer`                   | The name of a scipy.optimize.minimize method (the default is "L-BFGS-B"), "spsa" for spsa(), or a callable optimizer(cost, theta0, gradient) -> parameters for a custom or framework optimiser. SPSA needs no gradient: each iteration evaluates the circuit twice for the gradient estimate and once more to record the history. A callable can instead return (parameters, converged) to report convergence itself. Otherwise, success is inferred from whether the final cost is lower than the starting cost, because a vector alone says nothing about convergence. | `'L-BFGS-B'`                                                                                                                                             |
| `max_iterations`     | \`int                         | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | The maximum number of iterations. It is passed to SciPy as options={"maxiter": ...}, and to spsa() as maxiter, which is 100 when max_iterations is None. |
| `tol`                | \`float                       | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | The tolerance, passed to scipy.optimize.minimize.                                                                                                        |
| `random_state`       | \`int                         | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | The seed for the random starting point, which also seeds the perturbations of "spsa". None draws an unseeded starting point.                             |
| `callback`           | \`Callable\[[ndarray], None\] | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | An optional function called after every iteration with the current parameters. It is passed to SciPy, and "spsa" also calls it.                          |

Returns:

| Type                | Description                                                                                                  |
| ------------------- | ------------------------------------------------------------------------------------------------------------ |
| `VariationalResult` | A VariationalResult with the optimal value, the parameters, and the history of the cost at every evaluation. |

Example

```python
from eigenkit import Circuit, Variational
from eigenkit.core.ir import Observable
from eigenkit.core.variational import minimize_expectation

# One qubit, one trainable rotation, H = Z. The minimum is -1.
circuit = Circuit(
    blocks=[Variational(depth=1)],
    observables=Observable.from_string("Z0"),
    provider="numpy",
)

result = minimize_expectation(circuit, random_state=42)

print(round(result.optimal_value, 6))        # -1.0
print(result.success, result.n_iterations)   # True 4
```

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

### `register_lazy(name, loader)`

Register a backend under `name` without importing its package yet.

`loader` is called the first time the name is looked up, and its result is kept for later lookups. The optional backends are registered in this way, so that naming them imports nothing.

It replaces whatever `name` currently refers to, as `register` does. If the registration were skipped when the name was already taken, a caller could not replace a built-in backend with a lazy one, and would get no sign that the loader had been ignored.

Parameters:

| Name     | Type                     | Description                                                                                               | Default    |
| -------- | ------------------------ | --------------------------------------------------------------------------------------------------------- | ---------- |
| `name`   | `str`                    | The name to register.                                                                                     | *required* |
| `loader` | `Callable[[], Provider]` | A function with no arguments that returns the backend. It is called the first time the name is looked up. | *required* |

### `registered_names()`

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