## `eigenkit.torch.layers`

Quantum layers for the standard patterns.

- `Basic`: one `Encoding`, one `Variational` block and a measurement.
- `Reupload`: standard data reuploading, optionally with a trainable scale.
- `Ensemble`: `N` independent circuits, one for each chunk of the input.
- `MinMaxNorm`: min-max normalisation of each feature, in the style of `BatchNorm`.

### `Basic`

Bases: `Module`

One `Encoding`, one `Variational` block and a measurement on each qubit.

Parameters:

| Name               | Type                         | Description                                                                                                                                                                                                                      | Default    |
| ------------------ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `n_qubits`         | `int`                        | The number of qubits, which is also the number of input features.                                                                                                                                                                | *required* |
| `depth`            | `int`                        | The number of variational layers.                                                                                                                                                                                                | *required* |
| `enc_rotation`     | `Literal['RX', 'RY', 'RZ']`  | The encoding rotation: "RX", "RY" or "RZ". Default: "RX".                                                                                                                                                                        | `'RX'`     |
| `var_rotation`     | `Literal['RX', 'RY', 'RZ']`  | The variational rotation. It is used only when variational_type="basic". The default, "RY", is off both the "RX" encoding axis and the "Z" measurement axis, and this is what lets the weights change the output. Default: "RY". | `'RY'`     |
| `variational_type` | `Literal['basic', 'strong']` | "basic", for one rotation and a controlled-NOT (CNOT) ladder in each layer, or "strong", for RZ, RY and RZ rotations and a CNOT ladder in each layer. Default: "basic".                                                          | `'basic'`  |
| `basis`            | `Literal['X', 'Y', 'Z']`     | The Pauli measurement basis: "X", "Y" or "Z". Default: "Z".                                                                                                                                                                      | `'Z'`      |
| `provider`         | `ProviderSpec`               | The backend that runs the circuit. Default: "numpy".                                                                                                                                                                             | `'numpy'`  |

Example

```python
import torch
from eigenkit import Basic

layer = Basic(n_qubits=4, depth=2)
out = layer(torch.randn(8, 4))  # shape (8, 4)
```

#### `provider`

The backend that the circuit of this layer was compiled for.

The constructor takes `provider=` in the same forms as `Circuit` (a registered name, a configuration or a backend object) and turns it into one `Provider`. This property returns it. The registered name is `provider.name`.

Returns:

| Name       | Type       | Description                           |
| ---------- | ---------- | ------------------------------------- |
| `provider` | `Provider` | The backend that the circuit runs on. |

#### `forward(x)`

Run the layer.

Parameters:

| Name | Type     | Description                                     | Default    |
| ---- | -------- | ----------------------------------------------- | ---------- |
| `x`  | `Tensor` | The input batch, of shape (batch, in_features). | *required* |

Returns:

| Name  | Type     | Description                             |
| ----- | -------- | --------------------------------------- |
| `out` | `Tensor` | The output of the layer for that batch. |

### `Ensemble`

Bases: `Module`

An ensemble of independent quantum circuits, one for each chunk of the input.

Parameters:

| Name               | Type                         | Description                                                                             | Default    |
| ------------------ | ---------------------------- | --------------------------------------------------------------------------------------- | ---------- |
| `in_features`      | `int`                        | The total number of input features. It must be a multiple of n_qubits.                  | *required* |
| `n_qubits`         | `int`                        | The number of qubits of each circuit.                                                   | *required* |
| `depth`            | `int`                        | The number of variational layers of each circuit.                                       | *required* |
| `enc_rotation`     | `Literal['RX', 'RY', 'RZ']`  | The encoding rotation: "RX", "RY" or "RZ". Default: "RX".                               | `'RX'`     |
| `var_rotation`     | `Literal['RX', 'RY', 'RZ']`  | The variational rotation. It is used only when variational_type="basic". Default: "RY". | `'RY'`     |
| `variational_type` | `Literal['basic', 'strong']` | "basic" or "strong". Default: "strong".                                                 | `'strong'` |
| `basis`            | `Literal['X', 'Y', 'Z']`     | The Pauli measurement basis: "X", "Y" or "Z". Default: "Z".                             | `'Z'`      |
| `provider`         | `ProviderSpec`               | The backend that runs the circuits. Default: "numpy".                                   | `'numpy'`  |

Example

```python
import torch
from eigenkit import Ensemble

layer = Ensemble(in_features=20, n_qubits=4, depth=5)
out = layer(torch.randn(128, 20))  # shape (128, 20)
```

#### `provider`

The backend that the circuits of this layer were compiled for.

The constructor takes `provider=` in the same forms as `Circuit` (a registered name, a configuration or a backend object) and turns it into one `Provider`. This property returns it. The registered name is `provider.name`.

Returns:

| Name       | Type       | Description                           |
| ---------- | ---------- | ------------------------------------- |
| `provider` | `Provider` | The backend that the circuits run on. |

#### `forward(x)`

Run the layer.

Parameters:

| Name | Type     | Description                                     | Default    |
| ---- | -------- | ----------------------------------------------- | ---------- |
| `x`  | `Tensor` | The input batch, of shape (batch, in_features). | *required* |

Returns:

| Name  | Type     | Description                             |
| ----- | -------- | --------------------------------------- |
| `out` | `Tensor` | The output of the layer for that batch. |

### `MinMaxNorm`

Bases: `Module`

Min-max normalisation of each feature, with running statistics.

It rescales each feature to `[min_val, max_val]`. In training mode, it uses the minimum and maximum of each batch, and it updates an exponential moving average of these statistics. In evaluation mode, it uses the running statistics. A training batch of a single row has no range, so it uses the running statistics too, and if no batch with a range has been seen yet, it returns the row unchanged with a warning. Training and evaluation modes work as in the `BatchNorm` and `LayerNorm` layers of PyTorch, but the output is rescaled to a fixed interval instead of standardised.

It is useful to prepare the inputs of quantum encoding rotations, which expect angles in a bounded range such as `[0, π]`.

Parameters:

| Name          | Type    | Description                                                                                                 | Default    |
| ------------- | ------- | ----------------------------------------------------------------------------------------------------------- | ---------- |
| `feature_dim` | `int`   | The number of features in the last dimension of the input. The inputs have shape (batch_size, feature_dim). | *required* |
| `min_val`     | `float` | The lower bound of the target interval. Default: 0.0.                                                       | `0.0`      |
| `max_val`     | `float` | The upper bound of the target interval. Default: π.                                                         | `pi`       |
| `momentum`    | `float` | The momentum factor of the exponential moving average of the running statistics. Default: 0.1.              | `0.1`      |

Example

```python
import torch
from eigenkit import Ensemble, MinMaxNorm

norm = MinMaxNorm(feature_dim=16, min_val=0.0, max_val=torch.pi)
layer = Ensemble(in_features=16, n_qubits=4, depth=5)
out = layer(norm(torch.randn(32, 16)))
```

#### `forward(x)`

Run the layer.

Parameters:

| Name | Type     | Description                                     | Default    |
| ---- | -------- | ----------------------------------------------- | ---------- |
| `x`  | `Tensor` | The input batch, of shape (batch, feature_dim). | *required* |

Returns:

| Name  | Type     | Description                             |
| ----- | -------- | --------------------------------------- |
| `out` | `Tensor` | The output of the layer for that batch. |

### `Reupload`

Bases: `Module`

The data-reuploading layer (Perez-Salinas 2020; Schuld 2021).

It supports two patterns:

- Standard reuploading (the default). The same input is encoded `depth` times, with new variational weights between the encodings.
- Trainable feature scaling (`trainable_scale=True`). Each encoding rotation becomes `R(lambda * x)`, with a trainable `lambda` for each pair of layer and feature.

Parameters:

| Name               | Type                         | Description                                                                                                                                                                                            | Default    |
| ------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- |
| `n_qubits`         | `int`                        | The number of qubits, which is also the number of input features.                                                                                                                                      | *required* |
| `depth`            | `int`                        | The number of times the input is encoded.                                                                                                                                                              | *required* |
| `enc_rotation`     | `Literal['RX', 'RY', 'RZ']`  | The encoding rotation: "RX", "RY" or "RZ". Default: "RY".                                                                                                                                              | `'RY'`     |
| `var_rotation`     | `Literal['RX', 'RY', 'RZ']`  | The variational rotation. It is used only when variational_type="basic". Default: "RY".                                                                                                                | `'RY'`     |
| `variational_type` | `Literal['basic', 'strong']` | "basic" or "strong". Default: "basic".                                                                                                                                                                 | `'basic'`  |
| `basis`            | `Literal['X', 'Y', 'Z']`     | The Pauli measurement basis: "X", "Y" or "Z". Default: "Z".                                                                                                                                            | `'Z'`      |
| `trainable_scale`  | `bool`                       | If True, each encoding rotation gets a trainable scale for each pair of layer and feature. The CUDA Quantum backend does not support it, because its QuakeValue arithmetic is limited. Default: False. | `False`    |
| `provider`         | `ProviderSpec`               | The backend that runs the circuit. Default: "numpy".                                                                                                                                                   | `'numpy'`  |

Example

```python
import torch
from eigenkit import Reupload

layer = Reupload(n_qubits=4, depth=3, trainable_scale=True)
out = layer(torch.randn(8, 4))  # shape (8, 4)
```

#### `provider`

The backend that the circuit of this layer was compiled for.

The constructor takes `provider=` in the same forms as `Circuit` (a registered name, a configuration or a backend object) and turns it into one `Provider`. This property returns it. The registered name is `provider.name`.

Returns:

| Name       | Type       | Description                           |
| ---------- | ---------- | ------------------------------------- |
| `provider` | `Provider` | The backend that the circuit runs on. |

#### `forward(x)`

Run the layer.

Parameters:

| Name | Type     | Description                                     | Default    |
| ---- | -------- | ----------------------------------------------- | ---------- |
| `x`  | `Tensor` | The input batch, of shape (batch, in_features). | *required* |

Returns:

| Name  | Type     | Description                             |
| ----- | -------- | --------------------------------------- |
| `out` | `Tensor` | The output of the layer for that batch. |
