## `eigenkit.torch.interface`

The PyTorch interface, which makes the NumPy core differentiable in PyTorch.

This module is the PyTorch *adapter* of the interface model (see `eigenkit.core.interfaces`). The core `Circuit` computes the forward values and the jacobians with NumPy. This module wraps them into `torch.autograd.Function` objects, so that the automatic differentiation of PyTorch works through a quantum circuit. When it is imported, the module registers itself as the `"torch"` interface.

Nothing here is part of the core, and importing this module needs PyTorch. This is intended: PyTorch is an optional layer on top of the NumPy core.

### `TorchInterface`

The `"torch"` differentiation interface.

It chooses how to run a call of a core `Circuit`:

- When the backend's `gradient_method` is `"native"` and it provides `run_torch`, PyTorch differentiates through the simulator itself.
- When the backend computes no gradients (`gradient_method="none"`), the forward values are wrapped in an operation that has no backward pass.
- Otherwise, the call goes through a `torch.autograd.Function` whose backward pass asks the core circuit for the jacobians. The core uses the backend's own jacobian when there is one (for example the adjoint method of the NumPy simulator), and the parameter-shift rule otherwise.

#### `run_circuit(circuit, weights, inputs)`

Run the circuit with PyTorch, so that PyTorch can differentiate the result.

This is the PyTorch side of the `Interface` protocol. It uses the backend's own differentiation when there is one, and otherwise it wraps the NumPy path in a `torch.autograd.Function`.

Parameters:

| Name      | Type      | Description                            | Default    |
| --------- | --------- | -------------------------------------- | ---------- |
| `circuit` | `Circuit` | The core circuit to run.               | *required* |
| `weights` | `Tensor`  | The trainable parameters of the layer. | *required* |
| `inputs`  | `Tensor`  | The input features, 1-D or batched.    | *required* |

Returns:

| Name     | Type     | Description                                       |
| -------- | -------- | ------------------------------------------------- |
| `values` | `Tensor` | The expectation values, which are differentiable. |

### `batch_call_torch(run_one, weights, inputs)`

Loop over the first axis of a batch and keep the PyTorch autograd graph.

It is the PyTorch version of `core.provider.batch_call`. It lives in the PyTorch package and not in the core, so that the core never imports PyTorch. The backends that differentiate with PyTorch themselves (PennyLane) use it.

Parameters:

| Name      | Type       | Description                                | Default    |
| --------- | ---------- | ------------------------------------------ | ---------- |
| `run_one` | `Callable` | A function that evaluates a single sample. | *required* |
| `weights` | `Tensor`   | The parameter vector, 1-D or batched.      | *required* |
| `inputs`  | `Tensor`   | The input features, 1-D or batched.        | *required* |

Returns:

| Name      | Type     | Description                                                          |
| --------- | -------- | -------------------------------------------------------------------- |
| `stacked` | `Tensor` | The results for each sample, stacked. They are still differentiable. |
