Skip to content

Layers

Four nn.Module layers cover the usual patterns, and they can all be used inside nn.Sequential. Most of these layers expose in_features and out_features. MinMaxNorm uses feature_dim.

The three layers that build a circuit, Basic, Reupload and Ensemble, take the same provider= argument as Circuit, and they pass it to the circuit unchanged. MinMaxNorm is classical arithmetic and has no circuit to run, so it does not take the argument, and passing it raises a TypeError.

The same three layers return the provider on .provider, as the resolved backend, whichever form was passed in. .provider.name is its registered name:

from eigenkit import Basic, PennyLaneConfig

layer = Basic(n_qubits=4, depth=2, provider=PennyLaneConfig())
layer.provider.name  # "pennylane"
Layer Pattern
Basic One encoding, one variational block, and a measurement of each qubit.
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 batch normalisation.

Basic

Basic is the thinnest wrapper around Circuit: one encoding, one variational block, and a measurement of each qubit. Use it for textbook variational classifiers and regressors, where n_features == n_qubits.

import torch
from eigenkit import Basic

# Defaults: RX encoding, RY variational, Z basis, which are three different axes.
layer = Basic(n_qubits=4, depth=2)
out = layer(torch.randn(8, 4))  # shape (8, 4)

The circuit a Basic layer builds (light theme) The circuit a Basic layer builds (dark theme)

The circuit that this call builds. RX(x_i) encodes one feature per qubit. Then the basic variational block, which is one RY(θ) per qubit followed by the ladder of controlled-NOT (CNOT) gates, repeats depth times, and every qubit is measured in Z. These three axes are the defaults. With variational_type="strong", each RY(θ) box becomes the three rotations RZ, RY and RZ.

Some combinations of axes make the circuit inert

Some choices of enc_rotation, var_rotation and basis build a circuit whose weights, or whose input, provably cannot reach the output. All three layers check for this and issue a UserWarning that names the reason. The layer is still built, because you may want to build a degenerate circuit on purpose, as a baseline.

There are three ways to get one:

  • The weights commute with the observable. With variational_type="basic" and var_rotation on the measurement axis (RZ with basis="Z", or RX with basis="X"), the rotations and the CNOT ladder both preserve that group of Pauli operators. The weights then cancel out of the expectation value, and every gradient is exactly 0. Rotate away from the measurement axis, or use variational_type="strong", whose three rotations RZ, RY and RZ always include one away from the axis.
  • RZ encoding. The circuit starts in |0...0⟩, which every RZ leaves unchanged up to a phase, so the input never enters. Reupload escapes this once it encodes again, because the second encoding acts on a state that has already moved. Basic and Ensemble encode only once, so they do not escape it.
  • A real circuit measured in Y. RY and CNOT are real matrices, so an RY encoding with RY rotations keeps the statevector real, and ⟨Y⟩ is always 0 on a real state.

Sharing an axis is not a problem in itself. RY rotations with basis="Y" train well on two qubits or more, because the CNOT ladder does not preserve strings of Y operators.

Reupload

Reupload is standard data reuploading in a single circuit. The input is encoded again depth times, with new variational weights between the encodings. Set trainable_scale=True to add a trainable scale for each pair of layer and feature (Schuld et al. 2021).

Here, in_features == n_qubits.

from eigenkit import Reupload

# Encoding on RX, variational on RY: same rule as Basic.
layer = Reupload(n_qubits=4, depth=3, enc_rotation="RX", trainable_scale=True)
out = layer(torch.randn(8, 4))  # shape (8, 4)

The circuit a Reupload layer builds (light theme) The circuit a Reupload layer builds (dark theme)

What distinguishes this layer from Basic is where the outer bracket starts. It covers the encoding as well as the variational block, so the input is encoded again at each repetition, with new weights. The circuit is drawn with enc_rotation="RX", as in the call above, and without the trainable scale. With trainable_scale=True, each RX(x_i) becomes RX(λ·x_i), with one trainable λ for each pair of layer and feature.

Keep enc_rotation and var_rotation on different axes here too. When they share one, each R(x) and the R(θ) that follows it combine into a single R(x + θ), and the variational weights of that layer become a plain offset of the data angle. The defaults of Reupload put both rotations on RY, so pass enc_rotation explicitly, as in the call above.

Ensemble

Ensemble runs N independent circuits, one for each chunk of the input, each with its own weights. The outputs are concatenated along the feature dimension.

from eigenkit import Ensemble

layer = Ensemble(in_features=20, n_qubits=4, depth=5)
out = layer(torch.randn(128, 20))  # shape (128, 20): 5 circuits * 4 qubits

How an Ensemble layer routes its input (light theme) How an Ensemble layer routes its input (dark theme)

The input is split into chunks of n_qubits features, and each chunk gets its own circuit, with its own weights and its own measurement. The outputs of the circuits are concatenated back into one feature vector. Nothing is shared between the branches: no statevector and no parameters. Four branches are drawn, while the call above has five. Note also that on this layer, the variational block defaults to variational_type="strong", not "basic".

MinMaxNorm

MinMaxNorm normalises each feature with its minimum and maximum, and keeps running statistics. It is useful before encoding rotations, which expect bounded angles.

import torch
from eigenkit import MinMaxNorm

norm = MinMaxNorm(feature_dim=16, min_val=0.0, max_val=torch.pi)

In training mode, it uses the minimum and maximum of each batch, and it updates an exponential moving average of them (momentum=0.1 by default). In evaluation mode, it uses the running statistics. The output is clamped to [min_val, max_val], to guard against outliers that were not seen in training.

API reference

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
import torch
from eigenkit import Basic

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

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
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 property

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
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
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 property

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.