## `eigenkit.photonics`

Photonic circuits, which compute with linear optics.

This package is separate from the qubit core, and works with Quandela's Perceval software. It does *not* use the qubit `Provider` protocol, because Fock space, components that act on optical modes and photon statistics do not fit a `CircuitSpec` with Pauli `Observable`s.

It has the same layers as the qubit core. `ir` holds the data, `executor` defines the protocol, `gradients` computes the gradients, `backends/` holds the executors that call a software development kit (SDK), and `torch` is the PyTorch interface. Importing this package does not import Perceval or PyTorch. The Perceval SDK is imported only when an executor runs, and the PyTorch interface (`PhotonicCircuit`) is imported the first time the name is accessed. `PhotonicCircuit` is listed in `__all__`, so `from eigenkit.photonics import *` *does* import PyTorch. If that matters, import only the names that you use.

### `BeamSplitter`

A beam splitter on two modes.

`modes` are two adjacent modes `(m, m + 1)`, because Perceval can place a beam splitter only on adjacent modes. `theta` sets the reflectivity, with the Perceval convention `R = cos²(theta/2)`, so `theta = π/2` gives a balanced 50:50 beam splitter. `phi` is a relative phase. Each angle is a `Parameter` expression or a plain number, and a plain number is stored as a `Constant`. The exact unitary is set by the executor, which maps the component to Perceval's `pcvl.BS`, so this class only describes the component.

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

### `FockProbability`

The probability of detecting exactly the output Fock state `target`.

In formula form, `<O> = P(target)`.

### `PercevalBackend`

The photonic executor that runs on a local Perceval simulator.

`compile(spec)` returns a `PercevalCompiled`.

Parameters:

| Name           | Type  | Description                                                                                                                                                                                                                                                                                                               | Default  |
| -------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `backend_name` | `str` | A Perceval backend that computes the full output distribution. "SLOS" (the default) and "Naive" are exact, while "MPS" and "NaiveApprox" approximate the distribution, and so the gradients too. Backends that only sample, such as "CliffordClifford2017", are rejected, because run returns probabilities, not samples. | `'SLOS'` |

Raises:

| Type         | Description                                 |
| ------------ | ------------------------------------------- |
| `ValueError` | If backend_name is unknown or only samples. |

#### `compile(spec)`

Wrap `spec` in a reusable handle. Perceval runs at each call of the handle.

Parameters:

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

Returns:

| Type               | Description                           |
| ------------------ | ------------------------------------- |
| `PercevalCompiled` | The PercevalCompiled handle for spec. |

### `PercevalCompiled`

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

It is the linear-optics counterpart of `CompiledCircuit`. `PercevalBackend.compile` builds it.

Parameters:

| Name           | Type                  | Description                                                    | Default    |
| -------------- | --------------------- | -------------------------------------------------------------- | ---------- |
| `spec`         | `PhotonicCircuitSpec` | The circuit that this handle evaluates.                        | *required* |
| `backend_name` | `str`                 | The Perceval backend, as for PercevalBackend. Default: "SLOS". | `'SLOS'`   |

Raises:

| Type         | Description                                 |
| ------------ | ------------------------------------------- |
| `ValueError` | If backend_name is unknown or only samples. |

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

Return the expectation value of a `PhotonicObservable`, as a single number.

The observable is diagonal in the Fock basis.

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

Raises:

| Type         | Description                                              |
| ------------ | -------------------------------------------------------- |
| `ValueError` | If observable can never match the output of the circuit. |

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

Return `(d<O>/d weights, d<O>/d inputs)` for an observable.

The observable is diagonal in the Fock basis. Only one sample is accepted (1-D 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`                | "parameter_shift" for the exact photonic parameter-shift rule (in gradients.py), or "finite_difference" for central finite differences, the reference method. Default: "parameter_shift". | `'parameter_shift'` |
| `need_weight_grad` | `bool`               | If True, compute the gradient with respect to the weights. If False, it is skipped, and None is returned in its place.                                                                    | `True`              |
| `need_input_grad`  | `bool`               | If True, compute the gradient with respect to the inputs. If False, it is skipped, and None is returned in its place.                                                                     | `True`              |

Raises:

| Type         | Description                               |
| ------------ | ----------------------------------------- |
| `ValueError` | If method is neither of these two values. |

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

Return the output distribution of photon numbers.

The distribution is a dictionary `{photon_counts_tuple: probability}`. Fock states with probability zero may be left out, because Perceval's `prob_distribution` is sparse.

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

Raises:

| Type         | Description                               |
| ------------ | ----------------------------------------- |
| `ValueError` | If weights or inputs has the wrong shape. |

### `Permutation`

A reordering of the modes, which is a network of mode swaps given as one component.

`perm[i]` is the output mode to which input mode `i` is sent. The component acts on the modes `0` to `len(perm) - 1`, so `perm` must reorder exactly these modes.

### `PhaseShifter`

A phase shift `exp(i·phi·n̂)` on a single mode.

`n̂` is the photon-number operator of the mode. `phi` is a `Parameter` expression or a plain number, and a plain number is stored as a `Constant`.

### `PhotonNumber`

The expected number of photons in a mode: `<O> = <n̂_mode>`.

### `PhotonicCircuitSpec`

A description of a linear-optical circuit that does not depend on the backend.

It is the photonic counterpart of `core.ir.CircuitSpec`. It cannot be changed after it is built, and it can be hashed and pickled.

`input_state` is the Fock state sent into the circuit, as the number of photons in each mode. For example, `(1, 0, 1, 0)` means one photon in mode `0` and one in mode `2`. What is measured (the full distribution of photon numbers, the expected photon number of each mode, or a function of the detection pattern) is left to the executor for now. A `PhotonicMeasurement` type is planned.

Every component must act on the modes `0` to `n_modes - 1`, and `input_state` must hold non-negative integers. Both are checked when the object is built, so a wrong circuit fails at that point, and not inside Perceval.

#### `n_photons`

The total number of photons sent into the circuit.

This number sets the cost of the simulation in Fock space.

### `QuandelaCloudBackend`

An executor that runs on a Quandela Cloud platform.

The platform is a real quantum processing unit (QPU) or a cloud simulator.

Parameters:

| Name                   | Type    | Description                                                                                                                             | Default                                                                                                                                            |
| ---------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `platform`             | `str`   | The name of a Quandela Cloud platform, for example "qpu:ascella" for hardware or "sim:slos" for a cloud simulator. Default: "sim:slos". | `'sim:slos'`                                                                                                                                       |
| `token`                | \`str   | None\`                                                                                                                                  | The access token. Without it, the token comes from $QUANDELA_TOKEN or $PERCEVAL_TOKEN, then from the token that Perceval has saved. Default: None. |
| `shots`                | `int`   | The number of shots sampled in each run. Default: 10000.                                                                                | `10000`                                                                                                                                            |
| `url`                  | \`str   | None\`                                                                                                                                  | The web address of the Quandela Cloud service, if it is not Perceval's default. Default: None.                                                     |
| `min_detected_photons` | \`int   | None\`                                                                                                                                  | Keep only the shots that detect at least this many photons (post-selection). The default is the number of photons in the input state.              |
| `poll_timeout_seconds` | `float` | How long, in seconds, run and result() wait for a job before they raise TimeoutError. Default: 3600.                                    | `3600`                                                                                                                                             |

#### `compile(spec)`

Tie `spec` to this platform, offline, with no network connection or token.

Parameters:

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

Returns:

| Type             | Description                         |
| ---------------- | ----------------------------------- |
| `RemoteCompiled` | The RemoteCompiled handle for spec. |
