## `eigenkit.photonics.reservoir`

Photonic reservoir computing, as a quantum extreme learning machine.

The reservoir is a *fixed* linear-optical interferometer, which is random and never trained. At each timestep, the input is encoded into its phases, the photons pass through it, and photon-number statistics are read out. This gives a nonlinear map to many features. Only a classical linear readout is trained, so the executor only has to evaluate the circuit, and no gradients go through the optics.

The parts that do not depend on the kind of hardware come from the qubit reservoir package, `eigenkit.reservoir`. This module uses its `Stateless` policy, which gives temporal context with a classical window of past inputs, and the readout can be that package's `LinearReadout`, a ridge regression solved in closed form. The part specific to photonics turns the input of each timestep into a vector of features from a Fock distribution, with a photonic `Executor`.

Scope: no memory, or a classical window of inputs (`Stateless`). Carrying the photonic *state* from one timestep to the next (a quantum memory, like the qubit `Carry` policy) needs the executor to accept a Fock state as input, and is not supported yet. Policies that carry state raise an error here.

### `fock_basis(n_modes, n_photons)`

Return the sorted list of output Fock states.

The list holds every way to place `n_photons` photons into `n_modes` modes. Passive linear optics conserves the number of photons, so these are exactly the outputs that the circuit can reach. They are the fixed columns of the feature vector.

Parameters:

| Name        | Type  | Description                  | Default    |
| ----------- | ----- | ---------------------------- | ---------- |
| `n_modes`   | `int` | The number of optical modes. | *required* |
| `n_photons` | `int` | The total number of photons. | *required* |

### `fock_probability_features(dist, basis)`

Return the probability of every state in `basis`, as a dense vector.

These are the detailed features of the reservoir.

Parameters:

| Name    | Type                    | Description                                                                   | Default    |
| ------- | ----------------------- | ----------------------------------------------------------------------------- | ---------- |
| `dist`  | `dict`                  | A Fock distribution, as {photon_counts_tuple: probability}.                   | *required* |
| `basis` | `list[tuple[int, ...]]` | The output states, in the order of the features, for example from fock_basis. | *required* |

### `photon_number_features(dist, n_modes)`

Return the expected number of photons `<n̂_m>` in each mode `m`.

This readout is compact, and easy to measure with detectors.

Parameters:

| Name      | Type   | Description                                                 | Default    |
| --------- | ------ | ----------------------------------------------------------- | ---------- |
| `dist`    | `dict` | A Fock distribution, as {photon_counts_tuple: probability}. | *required* |
| `n_modes` | `int`  | The number of optical modes.                                | *required* |

### `random_reservoir_spec(n_modes, n_inputs, *, depth=2, input_state=None, input_scale=1.0, rng=None)`

Build a fixed, random photonic reservoir and its frozen weights.

A mixing layer comes first, so that the photons are spread over the modes. Then the `n_inputs` features are encoded as phases on the modes `0` to `n_inputs - 1` (each multiplied by `input_scale`), and `depth` more mixing layers follow. A mixing layer is a beam splitter on every pair of neighbouring modes, followed by a phase shifter on every mode. All their angles are random weights, drawn uniformly from `[0, 2π)`.

Parameters:

| Name          | Type              | Description                                                                                                      | Default                                                                                                                            |
| ------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `n_modes`     | `int`             | The number of optical modes. It must be at least 2.                                                              | *required*                                                                                                                         |
| `n_inputs`    | `int`             | The number of features encoded at each step, with one phase each. It must be at least 1 and at most n_modes - 1. | *required*                                                                                                                         |
| `depth`       | `int`             | The number of mixing layers after the encoding. It must be at least 1. Default: 2.                               | `2`                                                                                                                                |
| `input_state` | \`tuple[int, ...] | None\`                                                                                                           | The Fock state sent into the circuit. The default is a single photon in mode 0. More photons give a larger space of Fock features. |
| `input_scale` | `float`           | The factor applied to each input before it is used as a phase. Default: 1.0.                                     | `1.0`                                                                                                                              |
| `rng`         | \`Generator       | None\`                                                                                                           | The source of the random weights of the reservoir. The default is a new, unseeded generator.                                       |

Returns:

| Name      | Type                  | Description                                                   |
| --------- | --------------------- | ------------------------------------------------------------- |
| `spec`    | `PhotonicCircuitSpec` | The circuit of the reservoir.                                 |
| `weights` | `ndarray`             | The frozen weights of the reservoir, which are never trained. |

Raises:

| Type         | Description                                                                                                                                                                                                                                                         |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ValueError` | If n_modes < 2, depth < 1, n_inputs is outside [1, n_modes - 1], or input_state holds no photon. In each of these cases, the features of the reservoir would ignore the input, or, when every mode encodes an input, would see only the differences between inputs. |

### `run_photonic_reservoir(spec, weights, sequences, *, executor=None, policy=None, features='fock')`

Run the fixed reservoir over input sequences, and return the features.

The reservoir `spec`, with its frozen `weights`, is evaluated at every timestep of `sequences`, of shape `(B, T, F)`. The result is the sequence of features, of shape `(B, T, n_features)`.

Parameters:

| Name        | Type                  | Description                                                                                                                                                                                                                      | Default                                                                                                                                                                                                                                                                                           |
| ----------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `spec`      | `PhotonicCircuitSpec` | The reservoir, for example from random_reservoir_spec.                                                                                                                                                                           | *required*                                                                                                                                                                                                                                                                                        |
| `weights`   | `ndarray`             | The frozen weights of the reservoir.                                                                                                                                                                                             | *required*                                                                                                                                                                                                                                                                                        |
| `sequences` | `ndarray`             | The input sequences, of shape (B, T, F).                                                                                                                                                                                         | *required*                                                                                                                                                                                                                                                                                        |
| `executor`  | \`Executor            | None\`                                                                                                                                                                                                                           | The executor that runs each step. The default is PercevalBackend().                                                                                                                                                                                                                               |
| `policy`    | \`Stateless           | None\`                                                                                                                                                                                                                           | The policy whose expand_inputs turns each timestep into the circuit's n_inputs values (window * F). The default is Stateless(window=1). Policies that carry the state from one step to the next are not supported for photonics yet, because they need a Fock state as the input of the executor. |
| `features`  | `str`                 | "fock" gives the probabilities of all the states of the Fock basis with the input number of photons. This is the default, and gives the most features. "photon_number" gives the expected number of photons \<n̂_m> in each mode. | `'fock'`                                                                                                                                                                                                                                                                                          |

Returns:

| Type      | Description                                                                                                                                                                   |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ndarray` | The features, of shape (B, T, n_features). n_features is the number of Fock states with n_modes modes and spec.n_photons photons for "fock", and n_modes for "photon_number". |

Raises:

| Type                  | Description                                                                                                                        |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `ValueError`          | If features is unknown, sequences is not 3-D, or the number of inputs per step that the policy gives does not match spec.n_inputs. |
| `NotImplementedError` | If policy carries state from one step to the next.                                                                                 |
