## `eigenkit.photonics.gradients`

Gradients of photonic circuits: the photonic parameter-shift rule, and finite differences as a simple reference method.

The qubit rule shifts the angle by `±π/2`. It holds for a Pauli rotation because a Pauli generator has the eigenvalues `±½`, which give a single frequency. The generator of a linear-optical component instead has *equally spaced* eigenvalues. For a phase shifter (and for the `phi` of a beam splitter), the generator is the number operator `n̂`, whose eigenvalues are integers. The expectation value of any observable that is diagonal in the Fock basis is then a trigonometric polynomial in the angle, of degree at most `N`, where `N` is the total number of photons. Its derivative is given *exactly* by the general shift rule for equally spaced frequencies (Wierichs et al., 2022), which uses `2N` terms. At `N = 1`, this rule is the usual `±π/2` rule.

The `theta` of a beam splitter needs more care. Its generator is a generator of the special unitary group SU(2), on the two modes that the beam splitter acts on. The eigenvalues of this generator are integers or half-integers, depending on whether the number of photons `n_ij` in those two modes is even or odd: `U(theta + 2π) = (-1)^n_ij`. This is a sign that depends on the photon-number sector, not a global phase. In a circuit with two modes, `n_ij = N` always, so the frequencies are integers at most `N`, and the rule above applies. With more modes, the components around the beam splitter create coherent combinations of sectors with even and odd photon numbers, and half-integer frequencies appear. The rule is then applied in the angle `theta / 2`, with order `2N` (`4N` evaluations).

This module is the linear-optics counterpart of `core.provider.parameter_shift_jacobian`. It reuses the `chain_factor` and `chain_factor_input` functions of the qubit intermediate representation to turn the derivative with respect to a component angle into derivatives with respect to the weights and inputs that the angle depends on. The executor reuses the same `Parameter` expression tree.

Reference: the photonic parameter-shift rule, arXiv:2410.02726 (Phys. Rev. A 111, 032429).

Scope: one sample at a time (1-D weights and inputs), and observables that are diagonal in the Fock basis.

### `finite_difference_jacobian(executor, spec, weights, inputs, observable, *, eps=1e-06, need_weight_grad=True, need_input_grad=True)`

Compute the gradients with central finite differences, as a simple reference.

It shifts the weights and inputs themselves, so the chain rule is included automatically. It is the reference that checks the exact rule, in the same way as the NumPy simulator is the reference for qubit circuits.

Parameters:

| Name               | Type                  | Description                                                                                                            | Default    |
| ------------------ | --------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------- |
| `executor`         | `Executor`            | The executor that evaluates the circuit, for example PercevalBackend().                                                | *required* |
| `spec`             | `PhotonicCircuitSpec` | The circuit to differentiate.                                                                                          | *required* |
| `weights`          | `ndarray`             | The 1-D weight vector.                                                                                                 | *required* |
| `inputs`           | `ndarray`             | The 1-D input vector.                                                                                                  | *required* |
| `observable`       | `PhotonicObservable`  | An observable that is diagonal in the Fock basis.                                                                      | *required* |
| `eps`              | `float`               | The step of the central difference. Default: 1e-6.                                                                     | `1e-06`    |
| `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`     |

Returns:

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

Raises:

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

### `photonic_parameter_shift_jacobian(executor, spec, weights, inputs, observable, *, order=None, need_weight_grad=True, need_input_grad=True)`

Compute the exact `d<O>/d(weights)` and `d<O>/d(inputs)` with the photonic parameter-shift rule.

Parameters:

| Name               | Type                  | Description                                                                                                                                  | Default                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| ------------------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `executor`         | `Executor`            | The executor that evaluates each shifted circuit, for example PercevalBackend().                                                             | *required*                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `spec`             | `PhotonicCircuitSpec` | The circuit to differentiate.                                                                                                                | *required*                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `weights`          | `ndarray`             | The 1-D weight vector.                                                                                                                       | *required*                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `inputs`           | `ndarray`             | The 1-D input vector.                                                                                                                        | *required*                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `observable`       | `PhotonicObservable`  | An observable that is diagonal in the Fock basis.                                                                                            | *required*                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `order`            | \`int                 | None\`                                                                                                                                       | The degree of the trigonometric polynomial to reconstruct. The default is the total number of photons, which is a safe upper bound: the rule is exact whenever the true degree is at most order. In a circuit with more than two modes, the theta of a beam splitter is reconstructed in theta / 2 with order 2 * order (see the module docstring). order=1 gives the simple ±π/2 estimate, which is in general wrong with more than one photon. The tests use it to show that the rule with more terms is necessary. |
| `need_weight_grad` | `bool`                | If True, compute the gradient with respect to the weights. If False, its shifted evaluations are 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, its shifted evaluations are skipped, and None is returned in its place.  | `True`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |

Returns:

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

Raises:

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