## `eigenkit.circuit`

The top-level `Circuit` function, which builds a circuit for the chosen interface.

`Circuit(...)` returns a different type of circuit depending on `interface`, as in PennyLane:

- With `interface="numpy"`, the default, it returns the core NumPy `Circuit`. This circuit does not store weights: you pass the `weights` at each call. The variational optimisation tools and the `chemistry` and `optimization` packages use this form. With the default `provider="numpy"`, the circuit runs on the built-in reference simulator, so `pip install eigenkit` with no extra is enough to build and run circuits.
- With `interface="torch"`, it returns a TorchCircuit. This is a PyTorch `nn.Module` that stores trainable weights and can be used inside PyTorch models. It comes from `eigenkit.torch`, which needs the `torch` extra. PyTorch is imported only when this interface is requested, so an environment without PyTorch never imports it.

Both `interface` and `provider` default to NumPy. To use PyTorch, pass `interface="torch"`. To use a faster simulator or hardware, pass a provider such as `provider="pennylane"` or `provider="qiskit"`.

Example

```python
import numpy as np
from eigenkit import Circuit, Encoding, Variational, all_z

blocks = [Encoding(in_features=4), Variational(depth=2, rotation="RY")]
circuit = Circuit(blocks=blocks, observables=all_z(4))
weights = np.zeros(circuit.spec.n_weights)
values = circuit(weights, np.array([0.1, 0.2, 0.3, 0.4]))  # one value per qubit

# A differentiable PyTorch module on PennyLane (torch and pennylane extras):
layer = Circuit(
    blocks=blocks, observables=all_z(4), interface="torch", provider="pennylane"
)
```

### `Circuit(blocks, observables=None, provider='numpy', interface='numpy', init=None)`

Build a circuit and compile it for a backend.

This is the entry point for the rest of the library: you give it blocks, and it returns a circuit that you can call. The type of the circuit depends on `interface`. The NumPy interface returns a core `Circuit`, and the PyTorch interface returns a `TorchCircuit`.

Parameters:

| Name          | Type             | Description                                                                                                                                                                                                                                                                           | Default                                                                                                                                                                                                       |
| ------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `blocks`      | `_BlocksOrSpec`  | The circuit. It can be a list of blocks to assemble, a CircuitSpec that is already assembled, or a CircuitBuilder. A builder is built when Circuit is called, so gates added to it afterwards are not part of this circuit. A builder can also take the place of a block in the list. | *required*                                                                                                                                                                                                    |
| `observables` | `ObservableLike` | What to measure. None measures Z on every qubit and returns one value per qubit. A single Observable or PauliExpr returns a single value, and a list of them returns one value for each. Default: None.                                                                               | `None`                                                                                                                                                                                                        |
| `provider`    | `ProviderSpec`   | The backend that runs the circuit. It can be a registered name ("numpy", "pennylane", "qiskit" and others), a configuration instance such as PennyLaneConfig(...), or a backend object. Default: "numpy".                                                                             | `'numpy'`                                                                                                                                                                                                     |
| `interface`   | `_Interface`     | "numpy" returns a circuit that takes the weights at each call and does not depend on any framework. "torch" returns a TorchCircuit, which is a PyTorch nn.Module that stores the weights. Default: "numpy".                                                                           | `'numpy'`                                                                                                                                                                                                     |
| `init`        | \`Tensor         | None\`                                                                                                                                                                                                                                                                                | The initial weights of the PyTorch circuit, of shape (n_weights,). They are random when init is not given. The NumPy interface stores no weights, so passing init with it raises a ValueError. Default: None. |

Returns:

| Name      | Type      | Description    |
| --------- | --------- | -------------- |
| `circuit` | \`Circuit | TorchCircuit\` |

Raises:

| Type         | Description                                                                                 |
| ------------ | ------------------------------------------------------------------------------------------- |
| `ValueError` | If interface is neither "numpy" nor "torch", or if init is passed with the NumPy interface. |

Example

```python
import numpy as np
from eigenkit import Circuit, Encoding, Variational, all_z

circuit = Circuit(
    blocks=[Encoding(in_features=4), Variational(depth=2)],
    observables=all_z(4),
)
weights = np.zeros(circuit.spec.n_weights)
values = circuit(weights, np.random.uniform(0, np.pi, size=4))
```
