## `eigenkit.core.blocks`

Blocks: reusable parts of a circuit that do not depend on the provider.

Given the total number of qubits of the circuit, a block builds its gates and states how many weights and inputs it uses. Blocks are combined with `assemble`:

```python
from eigenkit.core.blocks import AngleEncoding, BasicVariational, assemble

spec = assemble([AngleEncoding(in_features=3), BasicVariational(depth=2)])
```

`assemble` goes through the blocks in order and chooses the total number of qubits. It shifts the local `WeightRef` and `InputRef` indices of each block so that they point to the right place in the weight and input vectors of the whole circuit, and it returns a single `CircuitSpec`. Blocks do not depend on a provider or on PyTorch, and most of them do not use NumPy. Only the blocks that check or compute a matrix (`QubitUnitary`, `ControlledUnitaryBlock` and `QPEBlock`) use NumPy. The blocks use `CircuitBuilder` internally, because it makes their code easier to read.

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

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

### `Block`

Bases: `Protocol`

The interface that every circuit block follows.

A block is a reusable part of a circuit, such as an encoding, a variational layer or a fixed subroutine. `assemble` asks each block in turn how many qubits it needs, gives the circuit as many qubits as the widest block needs, and then asks every block to build its gates for that number of qubits. Any object with these two methods works as a block. It does not need to inherit from this class.

#### `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. | *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 the minimum number of qubits that this block needs.

### `ControlledUnitaryBlock`

Apply a unitary matrix `U` to `target_wires`, controlled by `control_wires`.

Parameters:

| Name             | Type              | Description                                                                                        | Default                                                                                                       |
| ---------------- | ----------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `unitary`        | `object`          | A 2^n_target × 2^n_target matrix, as an array-like, where n_target is the number of target qubits. | *required*                                                                                                    |
| `control_wires`  | `tuple[int, ...]` | The qubits that control the gate.                                                                  | *required*                                                                                                    |
| `target_wires`   | `tuple[int, ...]` | The qubits that the matrix acts on.                                                                | *required*                                                                                                    |
| `control_values` | \`tuple[int, ...] | None\`                                                                                             | The bit value that activates each control, one for each entry of control_wires. Default: 1 for every control. |

Example

```python
import numpy as np
import scipy.linalg
from eigenkit.core.blocks import ControlledUnitaryBlock, assemble

A = np.diag([1.0, 2.0, 3.0, 4.0])  # a Hermitian matrix on two qubits
t = 0.5
U = scipy.linalg.expm(1j * A * t)
block = ControlledUnitaryBlock(U, control_wires=(0,), target_wires=(1, 2))
spec = assemble([block])
```

#### `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                         |
| -------------------------- | ----------------------------------- |
| `'ControlledUnitaryBlock'` | The rebuilt ControlledUnitaryBlock. |

#### `required_qubits()`

Return one more than the highest control or 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. |

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

### `HamiltonianQPEBlock`

Quantum phase estimation (QPE) of `e^{iAt}`, built with Trotter products.

This block replaces QPEBlock in the clock register of the Harrow-Hassidim-Lloyd (HHL) solver with `mode="honest"`, where the circuit is built as hardware would run it. It turns the Hermitian matrix `A = Σ_k c_k P_k` into elementary gates with Trotter products. It does not exponentiate a dense oracle and raise it to powers with `matrix_power`: both of these cost `O(N³)` for an `N × N` matrix, and neither gives a circuit of elementary gates. The controlled evolution `controlled-e^{iA·2^m·t}` that a clock qubit needs is a Trotter product with a total time of `2^m·t`, and never a matrix power. The step size `t/steps` is fixed, so the power `2^m` uses `steps·2^m` steps.

Only the single `RZ` of each Trotter term is controlled. The controlled `RZ` is built from `RZ` and controlled-NOT (CNOT) gates, as `CRZ = RZ·CNOT·RZ·CNOT`. The change of basis and the ladder gates cancel out when the control is `|0⟩`. The identity term of `A` becomes a `PhaseShift` on the clock qubit, which is a controlled global phase. As a result, the estimated phase reflects the full eigenvalue and not only its traceless part, which matches what QPEBlock gives from `expm(1j·A·t)`.

The qubits are laid out as follows, with indices local to the block:

```text
[clock(0 … n_clock-1) | state(n_clock … n_clock+n_state-1)]
```

Clock qubit `k` controls `e^{iA·2^(n_clock-1-k)·t}`. This is the big-endian convention of QPE, the same as in QPEBlock. The caller maps these local indices to the qubits of the full circuit, for example to skip the HHL ancilla qubit.

Parameters:

| Name          | Type                            | Description                                                                                                                   | Default    |
| ------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `pauli_terms` | `tuple[tuple[float, str], ...]` | The (coeff, pauli_string) terms of the Hermitian matrix A, for example from pauli_decompose. Every string has length n_state. | *required* |
| `n_clock`     | `int`                           | The number of clock qubits, which sets the resolution of QPE.                                                                 | *required* |
| `time`        | `float`                         | The base evolution time t.                                                                                                    | *required* |
| `steps`       | `int`                           | The number of Trotter steps for the base time t. Default: 1.                                                                  | `1`        |
| `order`       | `int`                           | The Trotter order, 1 or 2. Default: 1.                                                                                        | `1`        |
| `inverse`     | `bool`                          | If True, the block builds the inverse QPE, which undoes the phase estimation. Default: False.                                 | `False`    |

Example

```python
import numpy as np
from eigenkit.algorithms._hamiltonian_sim import pauli_decompose
from eigenkit.core.blocks import HamiltonianQPEBlock, assemble

A = np.array([[1.0, 0.5], [0.5, -1.0]])  # a Hermitian matrix on one qubit
terms = pauli_decompose(A)
spec = assemble([HamiltonianQPEBlock(terms, n_clock=4, time=1.0, steps=8)])
```

#### `build(n_qubits)`

Build the phase estimation, with Trotter products, for `n_qubits` qubits.

The qubit indices are local to the block, and the caller maps them to the qubits of the full 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. It holds elementary gates only, and no weights. |

#### `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                      |
| ----------------------- | -------------------------------- |
| `'HamiltonianQPEBlock'` | The rebuilt HamiltonianQPEBlock. |

#### `required_qubits()`

Return `n_clock` plus the number of state qubits.

The clock qubits come first, then the state qubits.

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

### `IRFragment`

The gates that one block adds to a circuit.

The `WeightRef` and `InputRef` indices of a fragment are local to it, and they start at `0`. When the blocks are combined into a circuit, `assemble` shifts them so that they point to the right place in the weight and input vectors of the whole circuit.

Attributes:

| Name        | Type               | Description                                                     |
| ----------- | ------------------ | --------------------------------------------------------------- |
| `gates`     | `tuple[Gate, ...]` | The gates of the block, in the order in which they are applied. |
| `n_weights` | `int`              | The number of trainable weights that the block uses.            |
| `n_inputs`  | `int`              | The number of input features that the block uses.               |

### `QFTBlock`

Quantum Fourier transform (QFT) on `n_qubits` qubits.

The transform is built from `H`, `CPHASE` and `SWAP` gates only, which are all gates that the intermediate representation (IR) of the circuit defines.

Parameters:

| Name       | Type   | Description                                                                                                               | Default    |
| ---------- | ------ | ------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `n_qubits` | `int`  | The number of qubits of the transform. It must be positive.                                                               | *required* |
| `inverse`  | `bool` | If True, the block builds the inverse QFT, which quantum phase estimation (QPE) applies as its last step. Default: False. | `False`    |

Example

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

b = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]  # eight values for three qubits
spec = assemble([AmplitudeEncoding(b), QFTBlock(n_qubits=3, inverse=True)])
```

#### `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           |
| ------------ | --------------------- |
| `'QFTBlock'` | The rebuilt QFTBlock. |

#### `required_qubits()`

Return `n_qubits`, the number of qubits that the transform 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. |

### `QPEBlock`

Quantum phase estimation (QPE) of a unitary oracle.

The block applies `H` to every clock qubit, then the controlled powers `U^{2^k}` of the oracle, then the inverse quantum Fourier transform (`QFT†`): `H^⊗n_clock → controlled-U^{2^k} → QFT†`. Set `inverse=True` for the adjoint: `QFT → controlled-U†^{2^k} → H^⊗n_clock`.

The oracle must be a `CircuitSpec` whose unitary can be read as a dense matrix. In practice, this means a circuit made of a single QubitUnitary gate. Parametrised oracles made of several gates are not supported. To use an AmplitudeEncoding or a ControlledUnitaryBlock as an oracle, first compute its unitary matrix and wrap it in a QubitUnitary.

The qubits are laid out as follows, with indices local to the block:

```text
[clock(0 … n_clock-1) | state(n_clock … n_clock+n_state-1)]
```

The caller is responsible for mapping these local indices to the qubits of the full circuit, for example to skip the ancilla qubit of the Harrow-Hassidim-Lloyd (HHL) algorithm.

Parameters:

| Name             | Type          | Description                                                                                                                                                                                               | Default    |
| ---------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `oracle_circuit` | `CircuitSpec` | A CircuitSpec whose single QubitUnitary gate defines the unitary U that acts on the state qubits. The single-gate requirement is checked when the block is built, and the type when the block is created. | *required* |
| `n_clock`        | `int`         | The number of clock qubits, which sets the resolution of QPE. It must be at least 1.                                                                                                                      | *required* |
| `inverse`        | `bool`        | If True, the block builds the inverse QPE, which undoes the phase estimation. Default: False.                                                                                                             | `False`    |

Example

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

A = np.array([[2, 0], [0, 1]], dtype=float)
U = scipy.linalg.expm(1j * A * np.pi)
oracle = assemble([QubitUnitary(U, wires=(0,))])
spec = assemble([QPEBlock(oracle, n_clock=3)])
```

#### `build(n_qubits)`

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

The qubit indices are local to the block, and the caller maps them to the qubits of the full 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           |
| ------------ | --------------------- |
| `'QPEBlock'` | The rebuilt QPEBlock. |

#### `required_qubits()`

Return `n_clock` plus the number of qubits of the oracle.

The clock qubits come first, then the state qubits.

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

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

### `TrotterBlock`

First-order or second-order Trotter approximation of `e^{iAt}`.

The block builds the time evolution under a Hermitian matrix `A = Σ_k c_k P_k`, given as `(coeff, pauli_string)` terms (for example from `pauli_decompose()`). It uses elementary gates only (`H`, `RX`, controlled-NOT (CNOT) and `RZ`), and no dense `QubitUnitary`. It replaces applying `scipy.linalg.expm(1j*A*t)` as a single opaque unitary, and it builds the evolution as hardware would run it.

Each term `e^{i·c·P·τ}` is built as follows. Every factor of `P` that is not `Z` is rotated into the eigenbasis of `Z` (with `H` for `X`, and `RX(+π/2)` for `Y`). A ladder of CNOT gates collects the parity of the active qubits on the last one, a single `RZ(-2cτ)` is applied there, and then the ladder and the change of basis are undone. With the convention `RZ(θ) = e^{-iθZ/2}` of the library, this gives `e^{+iAt}` up to a global phase, which matches the sign of the reference Harrow-Hassidim-Lloyd (HHL) solver. The identity term is only a global phase, so it is skipped: it has no observable effect here, and it becomes a phase on the clock qubit in the controlled version used for quantum phase estimation (QPE).

Parameters:

| Name          | Type                            | Description                                                                                                                                                                                  | Default    |
| ------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `pauli_terms` | `tuple[tuple[float, str], ...]` | A sequence of (coeff, pauli_string) pairs. All the strings have the same length n, which is the number of qubits. Each coeff must be real up to numerical tolerance, because A is Hermitian. | *required* |
| `time`        | `float`                         | The evolution time t.                                                                                                                                                                        | *required* |
| `steps`       | `int`                           | The number of Trotter steps. The approximation error decreases as steps increases. Default: 1.                                                                                               | `1`        |
| `order`       | `int`                           | 1 (Lie-Trotter) or 2 (symmetric Strang splitting, which has a smaller error for the same steps). Default: 1.                                                                                 | `1`        |

Example

```python
import numpy as np
from eigenkit.algorithms._hamiltonian_sim import pauli_decompose
from eigenkit.core.blocks import TrotterBlock, assemble

A = np.array([[1.0, 0.5], [0.5, -1.0]])  # a Hermitian matrix on one qubit
terms = pauli_decompose(A)
spec = assemble([TrotterBlock(terms, time=1.0, steps=20, order=2)])
```

#### `build(n_qubits)`

Build the Trotter product for a circuit with `n_qubits` qubits.

The qubit indices are local to the block, and the caller maps them to the qubits of the full 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. It holds elementary gates only, and no weights. |

#### `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               |
| ---------------- | ------------------------- |
| `'TrotterBlock'` | The rebuilt TrotterBlock. |

#### `required_qubits()`

Return the length of the Pauli strings, one qubit for each factor.

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

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