## `eigenkit.reservoir`

Quantum reservoir computing (QRC).

A circuit with fixed weights, usually random, is driven over input sequences. The quantum state is carried from one timestep to the next, and classical features are read out from it.

The reservoir circuit is never trained; only a classical readout is. The driver, the policies, the feature extractors and the ridge-regression readout use only NumPy. Like `optimization` and `chemistry`, this package belongs to the core of the library, so it needs neither PyTorch nor a backend package.

Three policies cover the common variants of QRC:

- Stateless starts every step from `|0…0⟩` and encodes a sliding window of inputs. All timesteps run in parallel, in one batched evaluation.
- Carry carries the state from one step to the next with the circuit's unitary: `ψ_t = U(x_t) ψ_{t-1}`.
- LeakyCarry mixes the carried state with a reset state, and renormalises the result.

Example

```python
from eigenkit import Circuit
from eigenkit.reservoir import (
    run_reservoir, Carry, expectation_features, LinearReadout,
)

states = run_reservoir(circuit, Carry(), sequences, weights)  # (B, T, 2**n)
feats = expectation_features(states, "Z0 + Z1 + 0.5 X0 X1")   # (B, T, 1)
readout = LinearReadout(ridge=1e-4).fit(feats.reshape(B * T, -1), targets)
```

### `Carry`

Policy that applies the circuit to the carried state: `ψ_t = U(x_t) ψ_{t-1}`.

The quantum state is the memory of the reservoir, and the first step starts from `|0…0⟩`. This policy is equivalent to `LeakyCarry(alpha=1.0)`.

#### `advance(x_t, evolved, state)`

Build the state for the next timestep from the result of this step.

Parameters:

| Name      | Type             | Description                                  | Default    |
| --------- | ---------------- | -------------------------------------------- | ---------- |
| `x_t`     | `ndarray`        | The input of this timestep, of shape (B, F). | *required* |
| `evolved` | `ndarray`        | The statevector that the circuit produced.   | *required* |
| `state`   | `ReservoirState` | The state that went into this step.          | *required* |

Returns:

| Type             | Description                      |
| ---------------- | -------------------------------- |
| `ReservoirState` | The state for the next timestep. |

#### `initial(batch)`

Return the state carried into the first timestep.

Parameters:

| Name    | Type  | Description                                   | Default    |
| ------- | ----- | --------------------------------------------- | ---------- |
| `batch` | `int` | The number of sequences that run in parallel. | *required* |

Returns:

| Type             | Description                 |
| ---------------- | --------------------------- |
| `ReservoirState` | The initial ReservoirState. |

#### `inject(x_t, state)`

Turn the input of this timestep into the arguments of the circuit call.

Parameters:

| Name    | Type             | Description                                    | Default    |
| ------- | ---------------- | ---------------------------------------------- | ---------- |
| `x_t`   | `ndarray`        | The input of this timestep, of shape (B, F).   | *required* |
| `state` | `ReservoirState` | The state carried over from the previous step. | *required* |

Returns:

| Name            | Type      | Description                                     |
| --------------- | --------- | ----------------------------------------------- |
| `initial_state` | \`ndarray | None\`                                          |
| `gate_inputs`   | `ndarray` | The values to pass to the gates of the circuit. |

### `LeakyCarry`

Policy that mixes the carried state with a reset state and renormalises it.

It approximates a leaky, dissipative reservoir with pure states: `ψ_t = normalise(α U(x_t) ψ_{t-1} + (1 − α) reset)`.

`reset` defaults to `|0…0⟩` and should be a normalised statevector; this is not checked. With `alpha=1`, the policy is exactly Carry. This is not a true dissipative channel, which would need density matrices that the pure-state backends do not provide. It is an approximation that uses pure states only.

Attributes:

| Name    | Type      | Description                                                                                                                    |
| ------- | --------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `alpha` | `float`   | The mixing weight of the carried state, in [0, 1]. Lower values forget faster, and 1 keeps everything, which is exactly Carry. |
| `reset` | \`ndarray | None\`                                                                                                                         |

#### `advance(x_t, evolved, state)`

Build the state for the next timestep from the result of this step.

Parameters:

| Name      | Type             | Description                                  | Default    |
| --------- | ---------------- | -------------------------------------------- | ---------- |
| `x_t`     | `ndarray`        | The input of this timestep, of shape (B, F). | *required* |
| `evolved` | `ndarray`        | The statevector that the circuit produced.   | *required* |
| `state`   | `ReservoirState` | The state that went into this step.          | *required* |

Returns:

| Type             | Description                      |
| ---------------- | -------------------------------- |
| `ReservoirState` | The state for the next timestep. |

#### `initial(batch)`

Return the state carried into the first timestep.

Parameters:

| Name    | Type  | Description                                   | Default    |
| ------- | ----- | --------------------------------------------- | ---------- |
| `batch` | `int` | The number of sequences that run in parallel. | *required* |

Returns:

| Type             | Description                 |
| ---------------- | --------------------------- |
| `ReservoirState` | The initial ReservoirState. |

#### `inject(x_t, state)`

Turn the input of this timestep into the arguments of the circuit call.

Parameters:

| Name    | Type             | Description                                    | Default    |
| ------- | ---------------- | ---------------------------------------------- | ---------- |
| `x_t`   | `ndarray`        | The input of this timestep, of shape (B, F).   | *required* |
| `state` | `ReservoirState` | The state carried over from the previous step. | *required* |

Returns:

| Name            | Type      | Description                                     |
| --------------- | --------- | ----------------------------------------------- |
| `initial_state` | \`ndarray | None\`                                          |
| `gate_inputs`   | `ndarray` | The values to pass to the gates of the circuit. |

### `LinearReadout`

Ridge regression in closed form, with a bias term.

It solves `W = (XᵀX + λI)⁻¹ XᵀY`, and the bias column is not regularised. `Y` can have one output or several. `ridge` is the strength `λ` of the L2 penalty, which adds `λ‖W‖²` to the squared error of the fit. A small positive value also keeps the solution numerically stable when the reservoir features are collinear.

Parameters:

| Name    | Type    | Description                                                                                                                                                                   | Default |
| ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `ridge` | `float` | The strength λ of the L2 penalty. Raise it when the readout overfits or the feature matrix is nearly singular, and lower it when the readout underfits. The default is 1e-06. | `1e-06` |

#### `fit(X, y)`

Fit the readout to `X` and `y`.

Parameters:

| Name | Type      | Description                                                                               | Default    |
| ---- | --------- | ----------------------------------------------------------------------------------------- | ---------- |
| `X`  | `ndarray` | The samples, of shape (n_samples, n_features).                                            | *required* |
| `y`  | `ndarray` | The targets, of shape (n_samples,) for one output, or (n_samples, n_outputs) for several. | *required* |

Returns:

| Type              | Description                                 |
| ----------------- | ------------------------------------------- |
| `'LinearReadout'` | This readout, so that calls can be chained. |

#### `predict(X)`

Predict the target for every sample in `X`.

Parameters:

| Name | Type      | Description                                    | Default    |
| ---- | --------- | ---------------------------------------------- | ---------- |
| `X`  | `ndarray` | The samples, of shape (n_samples, n_features). | *required* |

Returns:

| Type      | Description                                                                                           |
| --------- | ----------------------------------------------------------------------------------------------------- |
| `ndarray` | One prediction per sample, with one column per output when the readout was fitted on several outputs. |

### `ReservoirPolicy`

Bases: `Protocol`

A reservoir policy that carries state from one timestep to the next.

Parallel policies, which carry no state, do not implement this protocol. They provide `expand_inputs(sequences)`, which returns the inputs of every timestep with shape `(B, T, n_inputs)`, and the driver evaluates all timesteps at once. See Stateless.

#### `advance(x_t, evolved, state)`

Build the state for the next timestep from the result of this step.

Parameters:

| Name      | Type             | Description                                  | Default    |
| --------- | ---------------- | -------------------------------------------- | ---------- |
| `x_t`     | `ndarray`        | The input of this timestep, of shape (B, F). | *required* |
| `evolved` | `ndarray`        | The statevector that the circuit produced.   | *required* |
| `state`   | `ReservoirState` | The state that went into this step.          | *required* |

Returns:

| Type             | Description                      |
| ---------------- | -------------------------------- |
| `ReservoirState` | The state for the next timestep. |

#### `initial(batch)`

Return the state carried into the first timestep.

Parameters:

| Name    | Type  | Description                                   | Default    |
| ------- | ----- | --------------------------------------------- | ---------- |
| `batch` | `int` | The number of sequences that run in parallel. | *required* |

Returns:

| Type             | Description                 |
| ---------------- | --------------------------- |
| `ReservoirState` | The initial ReservoirState. |

#### `inject(x_t, state)`

Turn the input of this timestep into the arguments of the circuit call.

Parameters:

| Name    | Type             | Description                                    | Default    |
| ------- | ---------------- | ---------------------------------------------- | ---------- |
| `x_t`   | `ndarray`        | The input of this timestep, of shape (B, F).   | *required* |
| `state` | `ReservoirState` | The state carried over from the previous step. | *required* |

Returns:

| Name            | Type      | Description                                     |
| --------------- | --------- | ----------------------------------------------- |
| `initial_state` | \`ndarray | None\`                                          |
| `gate_inputs`   | `ndarray` | The values to pass to the gates of the circuit. |

### `ReservoirState`

The state that sequential policies carry from one timestep to the next.

`vector` is the `(B, 2**n)` statevector to inject at the next step, and `None` means that the step starts from `|0…0⟩`. `history` holds the recent raw inputs, of shape `(B, k, F)`, for a sequential policy that encodes a window of inputs.

Attributes:

| Name      | Type      | Description |
| --------- | --------- | ----------- |
| `vector`  | \`ndarray | None\`      |
| `history` | \`ndarray | None\`      |

### `Stateless`

Parallel policy that carries no state.

Every step starts from `|0…0⟩`, and the last `window` inputs are encoded as rotation angles of the circuit's gates. The memory is purely classical (the window), and the quantum state carries nothing from one step to the next. Every timestep is therefore independent, and the driver evaluates all of them in one batched run. With `window=1`, the policy has no memory: it encodes only the current input.

The circuit's `n_inputs` must equal `window * F`, where `F` is the number of features per step, because each step passes the whole flattened window to the encoding gates.

Attributes:

| Name     | Type  | Description                                                                                                                                                                                        |
| -------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `window` | `int` | The number of consecutive inputs to encode at each step. 1 means no memory: only the current input is encoded. Whatever the value, the circuit's n_inputs must equal window * F. The default is 1. |

#### `expand_inputs(sequences)`

Return the gate inputs of every timestep, with shape `(B, T, window * F)`.

Step `t` encodes the inputs `t - window + 1` to `t`. The first `window - 1` steps are padded with zeros at the front. No quantum state is involved, so this is only a reshaping of the input sequence.

Parameters:

| Name        | Type      | Description                                | Default    |
| ----------- | --------- | ------------------------------------------ | ---------- |
| `sequences` | `ndarray` | The whole input batch, of shape (B, T, F). | *required* |

Returns:

| Type      | Description                                                      |
| --------- | ---------------------------------------------------------------- |
| `ndarray` | The gate inputs for every timestep, of shape (B, T, window * F). |

### `amplitude_features(states)`

The raw statevector as real features: its real and imaginary parts, concatenated.

`states` is a complex array of shape `(B, T, 2**n)`, and the result is a real array of shape `(B, T, 2 * 2**n)`. It exposes the full state, which only a simulator can provide; it cannot be measured on hardware.

Parameters:

| Name     | Type      | Description                                              | Default    |
| -------- | --------- | -------------------------------------------------------- | ---------- |
| `states` | `ndarray` | The trajectory of statevectors, of shape (B, T, 2\*\*n). | *required* |

Returns:

| Type      | Description                                                    |
| --------- | -------------------------------------------------------------- |
| `ndarray` | The real and imaginary parts, concatenated, for each timestep. |

### `expectation_features(states, observables)`

Expectation values `⟨ψ|H_k|ψ⟩` of each observable, over the whole trajectory.

`states` is a complex array of shape `(B, T, 2**n)`, and the result is a real array of shape `(B, T, n_obs)`. `observables` can be an `Observable`, a `PauliExpr` or a Hamiltonian string, or a sequence of these. These are the same types that the rest of the library uses, so measuring an arbitrary Hamiltonian, as QRC experiments often require, needs no manual handling of Pauli terms. A single Hamiltonian string is one observable, and gives one feature.

Parameters:

| Name          | Type                   | Description                                              | Default    |
| ------------- | ---------------------- | -------------------------------------------------------- | ---------- |
| `states`      | `ndarray`              | The trajectory of statevectors, of shape (B, T, 2\*\*n). | *required* |
| `observables` | `Sequence[Observable]` | The observables to measure.                              | *required* |

Returns:

| Type      | Description                                  |
| --------- | -------------------------------------------- |
| `ndarray` | One feature per observable and per timestep. |

### `pauli_z_features(states)`

The expectation value `⟨Z_q⟩` on each qubit, the usual default readout in QRC.

`states` has shape `(B, T, 2**n)`, and the result has shape `(B, T, n)`. The values are computed with expectation_features(), so both functions share the same tested measurement code.

Parameters:

| Name     | Type      | Description                                              | Default    |
| -------- | --------- | -------------------------------------------------------- | ---------- |
| `states` | `ndarray` | The trajectory of statevectors, of shape (B, T, 2\*\*n). | *required* |

Returns:

| Type      | Description                             |
| --------- | --------------------------------------- |
| `ndarray` | One feature per qubit and per timestep. |

### `probability_features(states)`

The measurement distribution over all `2**n` basis states, `|ψ|²`.

`states` is a complex array of shape `(B, T, 2**n)`, and the result is a real array of the same shape.

Parameters:

| Name     | Type      | Description                                              | Default    |
| -------- | --------- | -------------------------------------------------------- | ---------- |
| `states` | `ndarray` | The trajectory of statevectors, of shape (B, T, 2\*\*n). | *required* |

Returns:

| Type      | Description                                                |
| --------- | ---------------------------------------------------------- |
| `ndarray` | The distribution over the basis states, for each timestep. |

### `run_reservoir(circuit, policy, sequences, weights)`

Evolve `circuit` over `sequences` under `policy`, with frozen `weights`.

The result is the trajectory of statevectors, a complex array of shape `(B, T, 2**n)`.

`sequences` has shape `(B, T, F)`: `B` independent series, `T` timesteps and `F` features per step. `weights` is the fixed 1-D parameter vector of the reservoir, shared by the whole batch. Features are extracted separately from the returned trajectory (see the feature extractors), so the same run can produce expectation, probability or amplitude features without evolving the circuit again.

Parameters:

| Name        | Type      | Description                                         | Default    |
| ----------- | --------- | --------------------------------------------------- | ---------- |
| `circuit`   | `Circuit` | The reservoir circuit, run with frozen weights.     | *required* |
| `policy`    | `Policy`  | How the state passes from one timestep to the next. | *required* |
| `sequences` | `ndarray` | The inputs, of shape (B, T, F).                     | *required* |
| `weights`   | `ndarray` | The frozen weights of the reservoir.                | *required* |

Returns:

| Type      | Description                                              |
| --------- | -------------------------------------------------------- |
| `ndarray` | The trajectory of statevectors, of shape (B, T, 2\*\*n). |
