## `eigenkit.photonics.executor`

The executor protocol for photonic circuits, the linear-optics counterpart of `core.provider`.

A photonic **executor** compiles a PhotonicCircuitSpec into a `Compiled` handle that can be reused. This is a *different contract* from the qubit `core.provider.Provider`: there is no `CircuitSpec`, no Pauli `Observable` and no array of expectation values. This is why photonics is a separate package, and not a qubit backend.

The executors themselves are in `photonics/backends/` (for example, the Perceval backend, which uses Perceval's simulation of linear optical systems (SLOS)). This module does not depend on any framework (neither Perceval nor PyTorch). It defines only the contract, and a small registry of names whose executors are built on first use, like the one in `core.provider`.

### `Compiled`

Bases: `Protocol`

A handle to a compiled photonic circuit, cheap to call many times.

Its contents are internal to the executor that built it.

#### `expectation(weights, inputs, observable)`

Evaluate one observable that is diagonal in the Fock basis, for one sample.

Parameters:

| Name         | Type                 | Description                          | Default    |
| ------------ | -------------------- | ------------------------------------ | ---------- |
| `weights`    | `ndarray`            | The 1-D weight vector.               | *required* |
| `inputs`     | `ndarray`            | The 1-D input vector.                | *required* |
| `observable` | `PhotonicObservable` | A PhotonNumber or a FockProbability. | *required* |

Returns:

| Type    | Description                          |
| ------- | ------------------------------------ |
| `float` | The expectation value of observable. |

#### `jacobian(weights, inputs, observable, *, method='parameter_shift', need_weight_grad=True, need_input_grad=True)`

Compute the gradient of one observable with respect to weights and inputs.

Parameters:

| Name               | Type                 | Description                                                                                                                                                                                      | Default             |
| ------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------- |
| `weights`          | `ndarray`            | The 1-D weight vector.                                                                                                                                                                           | *required*          |
| `inputs`           | `ndarray`            | The 1-D input vector.                                                                                                                                                                            | *required*          |
| `observable`       | `PhotonicObservable` | A PhotonNumber or a FockProbability.                                                                                                                                                             | *required*          |
| `method`           | `str`                | How to differentiate, for example "parameter_shift".                                                                                                                                             | `'parameter_shift'` |
| `need_weight_grad` | `bool`               | If True, compute the gradient with respect to the weights. If False, the implementation may skip it and return None in its place. The PyTorch bridge takes this value from ctx.needs_input_grad. | `True`              |
| `need_input_grad`  | `bool`               | The same as need_weight_grad, for the gradient with respect to the inputs.                                                                                                                       | `True`              |

Returns:

| Name         | Type      | Description |
| ------------ | --------- | ----------- |
| `weight_jac` | \`ndarray | None\`      |
| `input_jac`  | \`ndarray | None\`      |

#### `run(weights, inputs)`

Evaluate the circuit for one sample.

Parameters:

| Name      | Type      | Description                                      | Default    |
| --------- | --------- | ------------------------------------------------ | ---------- |
| `weights` | `ndarray` | The 1-D weight vector, of length spec.n_weights. | *required* |
| `inputs`  | `ndarray` | The 1-D input vector, of length spec.n_inputs.   | *required* |

Returns:

| Type   | Description                                                                       |
| ------ | --------------------------------------------------------------------------------- |
| `dict` | The output Fock distribution, as a dictionary {photon_counts_tuple: probability}. |

### `Executor`

Bases: `Protocol`

An object that compiles a `PhotonicCircuitSpec` into a `Compiled` handle.

The handle can be reused for many evaluations.

#### `compile(spec)`

Prepare `spec` so that it can be evaluated many times.

Parameters:

| Name   | Type                  | Description             | Default    |
| ------ | --------------------- | ----------------------- | ---------- |
| `spec` | `PhotonicCircuitSpec` | The circuit to compile. | *required* |

Returns:

| Type       | Description                 |
| ---------- | --------------------------- |
| `Compiled` | A Compiled handle for spec. |

### `get(name)`

Return the executor registered under `name`, building it on first use.

Parameters:

| Name   | Type  | Description                     | Default    |
| ------ | ----- | ------------------------------- | ---------- |
| `name` | `str` | A name passed to register_lazy. | *required* |

Returns:

| Name       | Type       | Description                                                                                    |
| ---------- | ---------- | ---------------------------------------------------------------------------------------------- |
| `executor` | `Executor` | The executor. It is kept after the first call, and the same object is returned by later calls. |

Raises:

| Type       | Description                          |
| ---------- | ------------------------------------ |
| `KeyError` | If nothing is registered under name. |

### `register_lazy(name, loader)`

Register a function that builds an executor, without building it yet.

The first registration of a name is kept, and later ones are ignored.

Parameters:

| Name     | Type                     | Description                                                                                               | Default    |
| -------- | ------------------------ | --------------------------------------------------------------------------------------------------------- | ---------- |
| `name`   | `str`                    | The name that get looks up.                                                                               | *required* |
| `loader` | `Callable[[], Executor]` | A function with no arguments that builds the executor. It is called the first time the name is looked up. | *required* |

### `registered_names()`

Return the names of all registered executors, in sorted order.
