## `eigenkit.core.interfaces`

Differentiation interfaces, which connect the core to automatic differentiation.

The core Circuit computes forward values and analytic NumPy jacobians (with the parameter-shift rule, the chain rule or the adjoint method). An *interface* connects this NumPy code to the automatic differentiation of a framework. It wraps the NumPy `run` and jacobians of the circuit into a differentiable operation of that framework (a `torch.autograd.Function` now, a `jax.custom_vjp` later), and it converts between the framework and NumPy.

NumPy needs no interface, because the core returns arrays directly. Frameworks register themselves when their package is imported. Adding JAX therefore takes a new adapter module and one register_interface() call. The core never changes and never imports a framework.

`get_interface("torch")` imports the PyTorch package (`eigenkit.torch`) the first time it is needed, and raises a clear error if PyTorch is not installed.

### `Interface`

Bases: `Protocol`

An adapter from the NumPy core to the automatic differentiation of a framework.

`run_circuit` receives the core `Circuit` (for `compiled`, `provider.gradient_method`, `observables` and the jacobian helpers), and the `weights` and `inputs` as tensors of the framework. It returns a differentiable output, also as a tensor of the framework.

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

Run `circuit` on tensors of the framework and return a differentiable result.

An implementation that builds its own default `inputs` (for a circuit that takes none) must call `circuit._check_parameter_lengths(weights, inputs)` once that default exists. The core cannot run this check before it passes the call here, and without it an input of the wrong size can be cut short without any error.

Parameters:

| Name      | Type      | Description                                | Default    |
| --------- | --------- | ------------------------------------------ | ---------- |
| `circuit` | `Circuit` | The core circuit being run.                | *required* |
| `weights` | `Any`     | The weights, as a tensor of the framework. | *required* |
| `inputs`  | `Any`     | The inputs, as a tensor of the framework.  | *required* |

Returns:

| Name     | Type  | Description                                            |
| -------- | ----- | ------------------------------------------------------ |
| `result` | `Any` | A differentiable result, as a tensor of the framework. |

### `get_interface(name)`

Return the interface registered under `name`, importing its package if needed.

Parameters:

| Name   | Type  | Description          | Default    |
| ------ | ----- | -------------------- | ---------- |
| `name` | `str` | The registered name. | *required* |

Returns:

| Type        | Description               |
| ----------- | ------------------------- |
| `Interface` | The registered interface. |

Raises:

| Type          | Description                                                                                                      |
| ------------- | ---------------------------------------------------------------------------------------------------------------- |
| `ImportError` | If the package of the interface is known but its framework is not installed. The message says how to install it. |
| `KeyError`    | If no interface is registered under name.                                                                        |

### `register_interface(name, interface)`

Register the interface of a framework under `name` (for example `"torch"`).

Parameters:

| Name        | Type        | Description              | Default    |
| ----------- | ----------- | ------------------------ | ---------- |
| `name`      | `str`       | The name to register.    | *required* |
| `interface` | `Interface` | The adapter to register. | *required* |

### `registered_interfaces()`

Return the names of the interfaces registered so far.

An interface appears only once its package has been imported, so this lists what is loaded now, not everything that is installed.

Example

```python
from eigenkit.core.interfaces import registered_interfaces

print(registered_interfaces())   # []

import eigenkit.torch  # registers itself on import
print(registered_interfaces())   # ['torch']
```

Returns:

| Type        | Description                     |
| ----------- | ------------------------------- |
| `list[str]` | The registered interface names. |
