Skip to content

Encoding layers

hqnn_forge.encoding

hqnn_forge.encoding

Quantum feature-map modules for projecting classical tabular vectors into an n-qubit Hilbert space.

Every encoder layer and QNode factory, the circuit helpers they share and the option types in their signatures are exported here, and from hqnn_forge.encoding import … is the documented import path; a new encoder adds its layer and QNode factory to this list.

Exported symbols

Encoders:

QuantumEncodingLayer nn.Module: angle embedding + entangling VQC (TorchLayer). build_encoding_qnode Factory that wires the angle-embedding QNode to a device and diff method. AngleEmbeddingQNode Alias of build_encoding_qnode. IQPEncodingLayer nn.Module: IQP embedding (pairwise ZZ phases) + the same VQC. build_iqp_qnode Factory for the IQP-embedding QNode. AmplitudeEncodingLayer nn.Module: up to 2**n_qubits features as state amplitudes. build_amplitude_qnode Factory for the amplitude-embedding QNode. DataReuploadingLayer nn.Module: angle embedding repeated before every layer. build_data_reuploading_qnode Factory for the data re-uploading QNode.

Backend choice:

resolve_backend What device_name="auto" and diff_method="auto" become. AUTO_BACKPROP_MAX_QUBITS Largest circuit "auto" runs on default.qubit with backprop.

Circuit building blocks shared by the encoders:

apply_variational_layers The entangler + Rot blocks, inside a QNode. readout_wires Wires measured under a readout option. measure_z The ⟨Z_i⟩ measurements a circuit returns. input_scaling_shape Shape of the re-uploading QNode's input_scaling weights.

The encoder interface:

EncodingLayer Protocol: qlayer, n_qubits, n_features, prepare_inputs; forward is qlayer(prepare_inputs(x)). The contract is spelled out in hqnn_forge._encoding_contract. CircuitLayer Protocol: qlayer and n_qubits, the minimum the diagnostics accept. is_encoding_layer Runtime check for EncodingLayer (isinstance cannot do it). is_circuit_layer Runtime check for CircuitLayer.

Option types, for annotating calls:

DeviceName Any PennyLane device name (str); see KNOWN_DEVICES. DiffMethod Literal of the supported differentiation methods. Entangler Literal of the entangler options. Position Literal of where training noise is inserted (from hqnn_forge.noise). Readout Literal of the readout options. RotationAxis Literal of the embedding rotation axes.

AUTO_BACKPROP_MAX_QUBITS module-attribute

AUTO_BACKPROP_MAX_QUBITS = 12

Largest circuit device_name="auto" simulates on default.qubit with backprop; larger ones go to lightning.qubit with adjoint. Measured for one training step at batch 64 (examples/benchmark_batching.py --crossover, README): backprop is 12x faster at 8 qubits and still 2.6x at 12, while at 14 the two are about as fast and backprop's memory, which grows fourfold per two qubits, has passed 1 GB against lightning's 21 MB. At 13 backprop was still faster (0.5 s against 0.9 s) but took +526 MB against +18 MB, so the switch comes one qubit early, to bound memory rather than to win the last bit of speed. Only batch 64 was measured: backprop's memory also grows with the batch (scaled linearly, the +283 MB at 12 qubits would be about 4.5 GB at batch 1024; not measured), so for large batches near the threshold pass device_name="lightning.qubit" explicitly. The same holds for inference: predict_proba and the trainer's validation pass their whole input as one batch, so at 12 qubits a 57k-row split holds a (57k, 4096) complex state, about 3.7 GB per copy, until they evaluate in chunks (#469).

AngleEmbeddingQNode module-attribute

AngleEmbeddingQNode = build_encoding_qnode

Alias: build_encoding_qnode — returns the raw QNode without an nn.Module wrapper.

DeviceName module-attribute

DeviceName = str

A PennyLane device name: one of KNOWN_DEVICES, or any other.

DiffMethod module-attribute

DiffMethod = Literal['auto', 'adjoint', 'parameter-shift', 'backprop', 'finite-diff']

Entangler module-attribute

Entangler = Literal['ring', 'strongly_entangling', 'brickwork', 'hardware_efficient']

Readout module-attribute

Readout = Literal['all', 'first']

RotationAxis module-attribute

RotationAxis = Literal['X', 'Y', 'Z']

Position module-attribute

Position = Literal['all', 'end']

CircuitLayer

Bases: Protocol

An nn.Module with a qlayer TorchLayer on n_qubits wires.

qlayer instance-attribute

qlayer: TorchLayer

n_qubits instance-attribute

n_qubits: int

EncodingLayer

Bases: CircuitLayer, Protocol

A CircuitLayer taking n_features inputs through prepare_inputs.

forward(x) is qlayer(prepare_inputs(x)), except for training-time noise in train() mode and a readout error applied to the output (see the module docstring).

n_features instance-attribute

n_features: int

prepare_inputs

prepare_inputs(x: Tensor) -> torch.Tensor

Validate and transform a batch exactly as forward does before the QNode.

AmplitudeEncodingLayer

Bases: TrainingNoiseMixin, Module

A PyTorch nn.Module wrapping the amplitude-embedding QNode.

API-consistent with QuantumEncodingLayer and IQPEncodingLayer: same constructor shape, same qlayer.weights parameter of shape (n_layers, n_qubits, 3), same (batch, n_qubits) output of ⟨Z_i⟩. The difference is the input width: up to 2**n_qubits features per sample instead of exactly n_qubits.

Input handling in forward
  1. The last dimension must equal n_features.
  2. If n_features < 2**n_qubits the vector is zero-padded on the right to 2**n_qubits amplitudes.
  3. The padded vector is L2-normalised, after dividing by its largest absolute feature so the norm cannot overflow or underflow. A sample containing NaN or ±inf, or one that is exactly all zeros, raises ValueError: the zero vector has no direction, so there is no state to prepare. Any non-zero scale, however small, is accepted.

Both steps are differentiable, so the gradient can reach whatever produced the features (see Differentiation methods for which methods support that).

Cost versus the other encoders

Angle embedding uses n single-qubit rotations for n features; IQP uses O(n²) gates for n features; amplitude embedding takes up to 2**n features but needs an O(2^n)-gate state-preparation circuit on hardware (Möttönen et al. 2005). On a simulator the difference is one vector write against n rotations, so the wall-clock gap is small; on hardware it is exponential. Choose amplitude embedding when the feature count is the constraint, not when the gate budget is.

Differentiation methods

Weight gradients are correct under every method. The input gradient is only supported under "backprop" on default.qubit; every other method raises RuntimeError when x requires a gradient, because PennyLane returns NaN or a silently wrong value for zero and small amplitudes (see the module docstring).

The check only applies when a gradient will actually be computed: detached inputs, and any input under torch.no_grad(), are fine with every method. With a classical encoder upstream, use backprop. The default diff_method="auto" is backprop on default.qubit up to 12 qubits, where input gradients work; above that it is adjoint on lightning, and input gradients are refused. The hybrid classifiers pick backprop whatever the size when their encoder feeds this layer.

Parameters:

Name Type Description Default
n_qubits int

Number of qubits. Default: 8 (256 amplitudes).

8
n_layers int

Entangling + rotation blocks in the VQC ansatz. Default: 2.

2
n_features int | None

Width of the input vectors, 1 ≤ n_features ≤ 2**n_qubits. Default: 2**n_qubits (no padding).

None
device_name DeviceName

PennyLane device name. Default "auto": default.qubit up to 12 qubits, lightning.qubit above (see resolve_backend). The simulators in KNOWN_DEVICES fall back along lightning.qubit → default.qubit with a warning per step when unavailable; any other name (a plugin or hardware) is constructed as given, and PennyLane's error surfaces if it cannot be. Hardware needs shots and diff_method="parameter-shift".

'auto'
diff_method DiffMethod

Gradient method, "auto" by default. See Differentiation methods above.

'auto'
entangler Entangler

The variational block: "ring" (default), "strongly_entangling", "brickwork" or "hardware_efficient"; see apply_variational_layers.

'ring'
readout Readout

"all" (default): the layer returns (batch, n_qubits). "first": ⟨Z_0⟩ only, (batch, 1).

'all'
noise_level float

Training-time noise, exactly as for QuantumEncodingLayer: noise_level is the strength of noise_channel (default "depolarizing"), in [0, 0.75] for depolarizing and [0, 1] for the damping and flip channels (default 0, noiseless), applied in train mode only, at "all" gates or at the "end", simulated exactly ("density") or by trajectories (amplitude damping with restrictions). See hqnn_forge.noise.

0.0
noise_position float

Training-time noise, exactly as for QuantumEncodingLayer: noise_level is the strength of noise_channel (default "depolarizing"), in [0, 0.75] for depolarizing and [0, 1] for the damping and flip channels (default 0, noiseless), applied in train mode only, at "all" gates or at the "end", simulated exactly ("density") or by trajectories (amplitude damping with restrictions). See hqnn_forge.noise.

0.0
noise_method float

Training-time noise, exactly as for QuantumEncodingLayer: noise_level is the strength of noise_channel (default "depolarizing"), in [0, 0.75] for depolarizing and [0, 1] for the damping and flip channels (default 0, noiseless), applied in train mode only, at "all" gates or at the "end", simulated exactly ("density") or by trajectories (amplitude damping with restrictions). See hqnn_forge.noise.

0.0
noise_trajectories float

Training-time noise, exactly as for QuantumEncodingLayer: noise_level is the strength of noise_channel (default "depolarizing"), in [0, 0.75] for depolarizing and [0, 1] for the damping and flip channels (default 0, noiseless), applied in train mode only, at "all" gates or at the "end", simulated exactly ("density") or by trajectories (amplitude damping with restrictions). See hqnn_forge.noise.

0.0
noise_channel float

Training-time noise, exactly as for QuantumEncodingLayer: noise_level is the strength of noise_channel (default "depolarizing"), in [0, 0.75] for depolarizing and [0, 1] for the damping and flip channels (default 0, noiseless), applied in train mode only, at "all" gates or at the "end", simulated exactly ("density") or by trajectories (amplitude damping with restrictions). See hqnn_forge.noise.

0.0
readout_error tuple[float, float] | None

A train-mode readout error (p01, p10) on the outputs, exactly as for QuantumEncodingLayer: train mode only, so evaluation has none unless it runs inside apply_readout_error, and with shots it flips each shot (#486). Default None.

None
shots int | None

Finite-shot sampling and the device seed, exactly as for QuantumEncodingLayer.

None
seed int | None

Finite-shot sampling and the device seed, exactly as for QuantumEncodingLayer.

None

Attributes:

Name Type Description
n_qubits int
n_layers int
n_features int

Width of the input, before padding to n_amplitudes.

n_amplitudes int

2**n_qubits.

qlayer TorchLayer

Methods:

Name Description
prepare_inputs

The padding and normalisation forward applies before the QNode.

Examples:

>>> import torch
>>> from hqnn_forge.encoding import AmplitudeEncodingLayer
>>> layer = AmplitudeEncodingLayer(
...     n_qubits=3, n_layers=1, n_features=6,
...     device_name="default.qubit", diff_method="backprop",
... )
>>> x = torch.randn(4, 6)   # 6 features → padded to 8 amplitudes
>>> layer(x).shape
torch.Size([4, 3])

n_qubits instance-attribute

n_qubits = n_qubits

n_layers instance-attribute

n_layers = n_layers

n_features instance-attribute

n_features = n_features

n_amplitudes instance-attribute

n_amplitudes = n_amplitudes

entangler instance-attribute

entangler = entangler

readout instance-attribute

readout = readout

n_outputs instance-attribute

n_outputs = len(readout_wires(n_qubits, readout))

qlayer instance-attribute

qlayer = qml.qnn.TorchLayer(qnode, weight_shapes)

seed instance-attribute

seed = validate_seed(seed)

prepare_inputs

prepare_inputs(x: Tensor) -> torch.Tensor

Zero-pad x to 2**n_qubits and L2-normalise each sample.

This is the classical step forward applies before the QNode. Every encoding layer has one, so tools which replay the circuit (hqnn_forge.kernels) validate and transform inputs exactly as forward does.

forward

forward(x: Tensor) -> torch.Tensor

Embed a batch of feature vectors into Pauli-Z expectation values.

Parameters:

Name Type Description Default
x Tensor

Classical input tensor of shape (batch_size, n_features). Any real scale; only the direction is encoded.

required

Returns:

Type Description
Tensor

Shape (batch_size, n_outputs) (n_qubits, or 1 with readout="first"), each element ∈ [-1, 1].

Raises:

Type Description
ValueError

If the last dimension of x is not n_features, or a sample is all zeros or contains NaN or ±inf.

RuntimeError

If x requires a gradient and diff_method is not "backprop" (see Differentiation methods).

extra_repr

extra_repr() -> str

QuantumEncodingLayer

Bases: TrainingNoiseMixin, Module

A PyTorch nn.Module that wraps the angle-embedding QNode as a fully differentiable layer via pennylane.qnn.TorchLayer.

The layer owns the variational weights as nn.Parameter objects. During the forward pass the classical inputs tensor is embedded into the quantum circuit and the Pauli-Z expectation values are returned as a real-valued tensor, enabling direct composition with classical nn.Linear layers.

Weight Shapes

The internal TorchLayer registers one trainable parameter:

+-----------+------------------------------------+ | Name | Shape | +===========+====================================+ | weights| (n_layers, n_qubits, 3) | +-----------+------------------------------------+

(n_layers, n_qubits) for entangler="hardware_efficient"; see variational_weight_shape.

Important: Call hqnn_forge.initializers.restricted_normal_init_ on layer.qlayer.weights immediately after construction to obtain small-angle initial values (see hqnn_forge.initializers for what that heuristic does and does not guarantee).

Parameters:

Name Type Description Default
n_qubits int

Number of qubits / input feature dimensions. Default: 8.

8
n_layers int

Number of entangling + rotation blocks in the VQC ansatz. Default: 2. At 1, ⟨Z_0⟩ does not see feature 0 under the default ring and RX, so readout="first" wants 2 or more; see step 2 of _make_angle_embedding_circuit.

2
rotation RotationAxis

Pauli axis for AngleEmbedding: "X" | "Y"; "Z" raises, see build_encoding_qnode.

'X'
device_name DeviceName

PennyLane device name. Default "auto": default.qubit up to 12 qubits, lightning.qubit above (see resolve_backend). The simulators in KNOWN_DEVICES fall back along lightning.qubit → default.qubit with a warning per step when unavailable; any other name (a plugin or hardware) is constructed as given, and PennyLane's error surfaces if it cannot be. Hardware needs shots and diff_method="parameter-shift".

'auto'
diff_method DiffMethod

"auto" (default) picks by device: "backprop" on default.qubit, "adjoint" on lightning, "parameter-shift" with shots or on any other device. Or one of "adjoint", "parameter-shift", "backprop", "finite-diff".

'auto'
entangler Entangler

"ring" (default), "strongly_entangling", "brickwork" (the same parameter count) or "hardware_efficient" (a third of it: one RY angle per qubit per layer); see apply_variational_layers.

'ring'
readout Readout

"all" (default): the layer returns (batch, n_qubits). "first": ⟨Z_0⟩ only, (batch, 1), the published SHNN readout.

'all'
noise_level float

Strength of the noise_channel applied to the circuit in train mode: the depolarizing probability in [0, 0.75], or the damping or flip probability in [0, 1] for the other channels (the table in hqnn_forge.noise); 0 (default) is the plain noiseless layer. With noise_level > 0 the train-mode forward pass runs the circuit on default.mixed with that channel inserted, so gradients are computed through the noisy circuit (noise-aware training); eval mode is noiseless, like dropout, outside the blocks of hqnn_forge.noise. With the default noise_method, backprop keeps a batch × 4^n density matrix per operation, so this is practical up to about 6 qubits. See hqnn_forge.noise.

0.0
noise_position Position

"all" (after every gate, default) or "end" (before measurement), as in hqnn_forge.noise.apply_depolarizing_noise.

'all'
noise_method NoiseMethod

"density" (default): the exact channel on default.mixed. "trajectories": Pauli-trajectory sampling on this layer's own device and diff_method, at pure-state memory; the train-mode output is then random, and equal to the "density" output on average. See hqnn_forge.noise.

'density'
noise_trajectories int

Draws averaged per sample with noise_method="trajectories". Default 1; must be 1 for "density". Use 8 or more at noise of a few percent per gate: with fewer draws some runs on the breast-cancer proxy had not started to train within 30 epochs (#347, #480; see hqnn_forge.noise).

1
noise_channel Channel

The channel noise_level is the strength of: "depolarizing" (default), "amplitude_damping" (T1), "phase_damping" (T2), "bit_flip" (also a symmetric readout error at "end") or "phase_flip"; see hqnn_forge.noise. With noise_method="trajectories", amplitude damping needs diff_method="backprop", "parameter-shift" or "finite-diff", default.qubit or lightning.qubit, and no shots.

'depolarizing'
shots int | None

None (default): exact expectation values. An int: every readout is estimated from that many samples, as on hardware. Needs diff_method="parameter-shift"; with training noise, only noise_method="trajectories". The samples come from the device's own generator, which neither torch.manual_seed nor numpy.random.seed reaches: pass seed for samples that repeat run to run. The shots attribute reads the QNode the layer runs, so it follows hqnn_forge.noise.apply_shots.

None
seed int | None

Seed of the device's generator, which draws the shot samples; a non-negative int (NumPy integers are converted) or None (default: unseeded). Inert for exact simulation. Shown in the repr; see resolve_device.

None
readout_error tuple[float, float] | None

None (default) or a pair (p01, p10), each in [0, 1]: in train mode each measured bit reads 1 instead of 0 with probability p01 and 0 instead of 1 with p10, which maps every output to (1 − p01 − p10) ⟨Z⟩ + (p10 − p01) (hqnn_forge.noise.readout_error_map). Independent of noise_level, and applied after its channel. Eval mode has none; evaluate under one with hqnn_forge.noise.apply_readout_error. Not applied inside an apply_depolarizing_noise block, like the rest of the training noise. The head learns the offset p10 − p01, so a model trained with it is evaluated on outputs without that shift unless the evaluation runs inside apply_readout_error with the same pair (#488). With shots each shot is flipped instead, so the output has the spread of a device with this error, (1 − ⟨Z'⟩²)/shots, and the map is its mean and gives its gradient (#486; see apply_readout_error). Stored as plain floats; shown in the repr.

None

Attributes:

Name Type Description
n_qubits int
n_features int

Width of the input, one feature per qubit: n_qubits.

n_layers int
n_outputs int

Width of the output: n_qubits or 1.

entangler str
readout str
noise_level float
noise_channel str
noise_position str
noise_method str
noise_trajectories int
readout_error tuple[float, float] or None
qlayer TorchLayer

The underlying differentiable quantum layer.

Examples:

>>> import torch
>>> from hqnn_forge.encoding import QuantumEncodingLayer
>>> from hqnn_forge.initializers import restricted_normal_init_
>>>
>>> layer = QuantumEncodingLayer(n_qubits=8, n_layers=2)
>>> _ = restricted_normal_init_(layer.qlayer.weights, n_qubits=8, n_layers=2)
>>>
>>> x = torch.randn(4, 8)   # batch of 4 samples
>>> out = layer(x)           # shape: (4, 8)
>>> out.shape
torch.Size([4, 8])

n_qubits instance-attribute

n_qubits = n_qubits

n_features instance-attribute

n_features = n_qubits

n_layers instance-attribute

n_layers = n_layers

entangler instance-attribute

entangler = entangler

readout instance-attribute

readout = readout

n_outputs instance-attribute

n_outputs = len(readout_wires(n_qubits, readout))

qlayer instance-attribute

qlayer = qml.qnn.TorchLayer(qnode, weight_shapes)

seed instance-attribute

seed = validate_seed(seed)

prepare_inputs

prepare_inputs(x: Tensor) -> torch.Tensor

Check that x has n_qubits finite features; the angles are used as given.

This is the classical step forward applies before the QNode. Every encoding layer has one, so tools which replay the circuit (hqnn_forge.kernels) validate and transform inputs exactly as forward does.

forward

forward(x: Tensor) -> torch.Tensor

Embed a batch of feature vectors into Pauli-Z expectation values.

Parameters:

Name Type Description Default
x Tensor

Classical input tensor of shape (batch_size, n_qubits). Values should be in [-π, π] for meaningful angle embedding (apply torch.tanh(x) * π or similar normalisation upstream).

required

Returns:

Type Description
Tensor

Quantum expectation values of shape (batch_size, n_outputs) (n_qubits, or 1 with readout="first"), each ∈ [-1, 1].

Raises:

Type Description
ValueError

If the last dimension of x does not equal self.n_qubits.

extra_repr

extra_repr() -> str

DataReuploadingLayer

Bases: TrainingNoiseMixin, Module

A PyTorch nn.Module wrapping the data re-uploading QNode.

API-consistent with QuantumEncodingLayer: same constructor arguments (plus trainable_input_scaling), same qlayer.weights shape for each entangler, same (batch, n_qubits) input and (batch, n_outputs) output. The difference is inside the circuit: the features are embedded before every variational layer, not only before the first.

Expressivity versus depth

With one upload the model's dependence on each feature is a degree-one trigonometric polynomial, whatever n_layers is; with L uploads it is of degree L (Schuld, Sweke & Meyer 2021), or L - 1 for rotation="Z", whose first upload is a phase on |0⟩. The price over the single-upload encoder at the same n_layers is the L - 1 extra uploads: (L - 1)·n_qubits single-qubit gates, L - 1 units of depth and, on hardware, L times the data-loading cost. For n_layers=1 without input scaling the two layers are identical.

Weight Shapes

qlayer.weights has shape (n_layers, n_qubits, 3) ((n_layers, n_qubits) for entangler="hardware_efficient") and, like the other encoders, starts from TorchLayer's default Uniform(0, 2π). Call hqnn_forge.initializers.restricted_normal_init_ on it immediately after construction for the library's small-angle initial values (see hqnn_forge.initializers for what they do and do not guarantee). Leave qlayer.input_scaling at its ones-initialisation, which starts the layer as the plain re-uploading circuit.

Parameters:

Name Type Description Default
n_qubits int

Number of qubits / input features. Default: 8.

8
n_layers int

Number of uploads, each followed by one variational block of entangler. Default: 2.

2
rotation RotationAxis

Pauli axis of the embedding rotations. Default: "X". "Z" requires n_layers ≥ 2.

'X'
device_name DeviceName

PennyLane device name. Default "auto": default.qubit up to 12 qubits, lightning.qubit above (see resolve_backend). The simulators in KNOWN_DEVICES fall back along lightning.qubit → default.qubit with a warning per step when unavailable; any other name (a plugin or hardware) is constructed as given, and PennyLane's error surfaces if it cannot be. Hardware needs shots and diff_method="parameter-shift".

'auto'
diff_method DiffMethod

"auto" (default) picks by device: "backprop" on default.qubit, "adjoint" on lightning, "parameter-shift" with shots or on any other device. Or one of "adjoint", "parameter-shift", "backprop", "finite-diff".

'auto'
trainable_input_scaling bool

Add a trainable qlayer.input_scaling of shape (n_layers, n_qubits), initialised to ones, that multiplies the features before each upload (Pérez-Salinas et al. 2020); for rotation="Z" it is (n_layers - 1, n_qubits) and leaves the first upload, a global phase, unscaled. Default: False, so the parameter count matches the other encoders.

False
entangler Entangler

"ring" (default), "strongly_entangling", "brickwork" or "hardware_efficient"; see apply_variational_layers.

'ring'
readout Readout

"all" (default): the layer returns (batch, n_qubits). "first": ⟨Z_0⟩ only, (batch, 1).

'all'
noise_level float

Training-time noise, exactly as for QuantumEncodingLayer: noise_level is the strength of noise_channel (default "depolarizing"), in [0, 0.75] for depolarizing and [0, 1] for the damping and flip channels (default 0, noiseless), applied in train mode only, at "all" gates or at the "end", simulated exactly ("density") or by trajectories (amplitude damping with restrictions). See hqnn_forge.noise.

0.0
noise_position float

Training-time noise, exactly as for QuantumEncodingLayer: noise_level is the strength of noise_channel (default "depolarizing"), in [0, 0.75] for depolarizing and [0, 1] for the damping and flip channels (default 0, noiseless), applied in train mode only, at "all" gates or at the "end", simulated exactly ("density") or by trajectories (amplitude damping with restrictions). See hqnn_forge.noise.

0.0
noise_method float

Training-time noise, exactly as for QuantumEncodingLayer: noise_level is the strength of noise_channel (default "depolarizing"), in [0, 0.75] for depolarizing and [0, 1] for the damping and flip channels (default 0, noiseless), applied in train mode only, at "all" gates or at the "end", simulated exactly ("density") or by trajectories (amplitude damping with restrictions). See hqnn_forge.noise.

0.0
noise_trajectories float

Training-time noise, exactly as for QuantumEncodingLayer: noise_level is the strength of noise_channel (default "depolarizing"), in [0, 0.75] for depolarizing and [0, 1] for the damping and flip channels (default 0, noiseless), applied in train mode only, at "all" gates or at the "end", simulated exactly ("density") or by trajectories (amplitude damping with restrictions). See hqnn_forge.noise.

0.0
noise_channel float

Training-time noise, exactly as for QuantumEncodingLayer: noise_level is the strength of noise_channel (default "depolarizing"), in [0, 0.75] for depolarizing and [0, 1] for the damping and flip channels (default 0, noiseless), applied in train mode only, at "all" gates or at the "end", simulated exactly ("density") or by trajectories (amplitude damping with restrictions). See hqnn_forge.noise.

0.0
readout_error tuple[float, float] | None

A train-mode readout error (p01, p10) on the outputs, exactly as for QuantumEncodingLayer: train mode only, so evaluation has none unless it runs inside apply_readout_error, and with shots it flips each shot (#486). Default None.

None
shots int | None

Finite-shot sampling and the device seed, exactly as for QuantumEncodingLayer.

None
seed int | None

Finite-shot sampling and the device seed, exactly as for QuantumEncodingLayer.

None

Attributes:

Name Type Description
n_qubits, n_layers int
n_features int

Width of the input, one feature per qubit: n_qubits.

n_outputs int

Width of the output: n_qubits or 1.

rotation str
trainable_input_scaling bool
entangler, readout str
qlayer TorchLayer

Owns weights and, if enabled, input_scaling.

Examples:

>>> import torch
>>> from hqnn_forge.encoding import DataReuploadingLayer
>>> from hqnn_forge.initializers import restricted_normal_init_
>>> layer = DataReuploadingLayer(n_qubits=4, n_layers=3)
>>> _ = restricted_normal_init_(layer.qlayer.weights, n_qubits=4, n_layers=3)
>>> layer(torch.rand(2, 4)).shape
torch.Size([2, 4])

n_qubits instance-attribute

n_qubits = n_qubits

n_features instance-attribute

n_features = n_qubits

n_layers instance-attribute

n_layers = n_layers

rotation instance-attribute

rotation = rotation

trainable_input_scaling instance-attribute

trainable_input_scaling = trainable_input_scaling

entangler instance-attribute

entangler = entangler

readout instance-attribute

readout = readout

n_outputs instance-attribute

n_outputs = len(readout_wires(n_qubits, readout))

qlayer instance-attribute

qlayer = qml.qnn.TorchLayer(qnode, weight_shapes)

seed instance-attribute

seed = validate_seed(seed)

prepare_inputs

prepare_inputs(x: Tensor) -> torch.Tensor

Check that x has n_qubits finite features; the angles are used as given.

The optional input_scaling is a circuit parameter, applied inside the QNode, not here.

This is the classical step forward applies before the QNode. Every encoding layer has one, so tools which replay the circuit (hqnn_forge.kernels) validate and transform inputs exactly as forward does.

forward

forward(x: Tensor) -> torch.Tensor

Embed a batch of feature vectors, re-uploading before every layer.

Parameters:

Name Type Description Default
x Tensor

Shape (batch_size, n_qubits), values in [-π, π] as for the angle encoder.

required

Returns:

Type Description
Tensor

Shape (batch_size, n_outputs), each element ∈ [-1, 1].

Raises:

Type Description
ValueError

If the last dimension of x is not n_qubits.

extra_repr

extra_repr() -> str

IQPEncodingLayer

Bases: TrainingNoiseMixin, Module

A PyTorch nn.Module wrapping the IQP-embedding QNode.

entangler and readout are the options of QuantumEncodingLayer; the output width is n_outputs (n_qubits, or 1 with readout="first"), and the input width n_features is n_qubits, one feature per qubit. noise_level / noise_position / noise_method / noise_trajectories / noise_channel add training-time noise (depolarizing by default; noise_level's range depends on the channel), readout_error a train-mode readout error (p01, p10) on the outputs, and shots and seed finite-shot sampling and its device seed, exactly as in QuantumEncodingLayer.

n_qubits instance-attribute

n_qubits = n_qubits

n_features instance-attribute

n_features = n_qubits

n_layers instance-attribute

n_layers = n_layers

n_repeats instance-attribute

n_repeats = n_repeats

entangler instance-attribute

entangler = entangler

readout instance-attribute

readout = readout

n_outputs instance-attribute

n_outputs = len(readout_wires(n_qubits, readout))

qlayer instance-attribute

qlayer = qml.qnn.TorchLayer(qnode, weight_shapes)

seed instance-attribute

seed = validate_seed(seed)

prepare_inputs

prepare_inputs(x: Tensor) -> torch.Tensor

Check that x has n_qubits finite features; the values are used as given.

This is the classical step forward applies before the QNode. Every encoding layer has one, so tools which replay the circuit (hqnn_forge.kernels) validate and transform inputs exactly as forward does.

forward

forward(x: Tensor) -> torch.Tensor

Embed a batch of feature vectors.

extra_repr

extra_repr() -> str

is_circuit_layer

is_circuit_layer(obj: object) -> TypeGuard[CircuitLayer]

Whether obj is an nn.Module with a TorchLayer qlayer and an int n_qubits.

bool is refused as n_qubits although it is an int subclass.

is_encoding_layer

is_encoding_layer(obj: object) -> TypeGuard[EncodingLayer]

Whether obj is a CircuitLayer with n_features and prepare_inputs.

n_features must be an int (bool refused, as for n_qubits) and prepare_inputs callable.

resolve_backend

resolve_backend(device_name: DeviceName, diff_method: DiffMethod, n_qubits: int, *, shots: int | None = None, require_backprop: bool = False) -> tuple[DeviceName, DiffMethod]

Resolve "auto" in device_name and diff_method to concrete names.

Anything other than "auto" is returned unchanged. The rules:

  • Device. With require_backprop, or an explicit diff_method="backprop", "default.qubit": backprop needs it. With an explicit "adjoint", "lightning.qubit", whose adjoint is the fast one. Otherwise by size: "default.qubit" up to AUTO_BACKPROP_MAX_QUBITS qubits, "lightning.qubit" above.
  • Method. With shots, "parameter-shift": the state-vector methods cannot run on samples (see validate_shots). Otherwise by the device: "backprop" on default.qubit and default.mixed, which vectorise a batch; "adjoint" on the three lightning simulators in KNOWN_DEVICES; "parameter-shift" on any other device (lightning.tensor included, which has no adjoint), the method every device and hardware supports.

The rules look at names only. If "lightning.qubit" is chosen but not installed, resolve_device still falls back to default.qubit with its warning, and the method stays "adjoint", which default.qubit also supports.

require_backprop is for amplitude encoding behind a classical encoder, which trains through the circuit's input gradient: only backprop gives it correctly (see hqnn_forge.encoding.amplitude_embedding). It only steers "auto"; an explicit conflicting choice is the caller's to refuse.

build_amplitude_qnode

build_amplitude_qnode(n_qubits: int = 8, n_layers: int = 2, device_name: DeviceName = 'auto', diff_method: DiffMethod = 'auto', entangler: Entangler = 'ring', readout: Readout = 'all', shots: int | None = None, seed: int | None = None) -> qml.QNode

Build and return a PennyLane QNode for the amplitude feature map.

The QNode expects unit-norm inputs of shape (2**n_qubits,) or (batch, 2**n_qubits); use AmplitudeEncodingLayer for the padding and normalisation of raw feature vectors.

When inputs requires a gradient under a method other than "backprop", calling the QNode raises RuntimeError: PennyLane would return NaN, zero or a silently wrong value for that gradient on some inputs. See the module docstring.

Parameters:

Name Type Description Default
n_qubits int

Number of qubits. The state has 2**n_qubits amplitudes.

8
n_layers int

Number of entangling + rotation layers in the VQC ansatz.

2
device_name DeviceName 'auto'
diff_method DeviceName 'auto'
entangler DeviceName 'auto'
readout DeviceName 'auto'

Raises:

Type Description
ValueError

If n_qubits < 2, or entangler or readout is unknown.

apply_variational_layers

apply_variational_layers(weights: Tensor, n_qubits: int, n_layers: int, entangler: Entangler = 'ring', layer_offset: int = 0) -> None

Apply the n_layers variational blocks to the current circuit.

entangler selects the block:

  • "ring" (the library's default): CNOT ring CNOT(i → i+1 mod n), then Rot(φ, θ, ω) on every qubit.
  • "strongly_entangling": qml.StronglyEntanglingLayers, i.e. Rot on every qubit then a CNOT ring whose range grows with the layer index, r = ℓ mod (n-1) + 1. This is the block the published SHNN uses (Schuld et al. 2020, PennyLane template).
  • "brickwork": nearest-neighbour CNOTs on the even pairs (0,1), (2,3), …, then on the odd pairs (1,2), (3,4), …, with no wrap-around, then Rot on every qubit. Unlike the two cascades above, which carry a readout across the whole register at shallow depth (⟨Z_0⟩ ↦ Z_1⋯Z_{n-1} through one ring), each layer widens the backward light cone of a single-qubit readout by at most two qubits on each side, so the ⟨Z_i⟩ readouts stay local costs in the sense of Cerezo et al. (2021) while n_layers is small against n_qubits; see hqnn_forge.initializers.restricted_variance for the measured gradient variance.
  • "hardware_efficient": a nearest-neighbour CZ(i, i+1) ladder, then RY(θ) on every qubit (Kandala et al. 2017): hqnn_forge.circuits.hardware_efficient_layer. One angle per qubit per layer and n − 1 two-qubit gates per layer: a third of the parameters of the Rot blocks, with CZ native on many devices.

"ring", "strongly_entangling" and "brickwork" take weights of shape (n_layers, n_qubits, 3) and use n_layers · n_qubits Rot gates. The ring and "strongly_entangling" use n_qubits CNOTs per layer and differ in gate order and, from the second layer on, in which qubits the CNOTs connect; "brickwork" uses n_qubits - 1. "hardware_efficient" takes (n_layers, n_qubits). variational_weight_shape gives the shape for each. The "ring" block is hqnn_forge.circuits.strongly_entangling_layer applied per layer.

layer_offset is the index of the first block within the whole ansatz, for circuits that interleave other gates between blocks and so apply them a few at a time: the "strongly_entangling" range of block ℓ is (layer_offset + ℓ) mod (n-1) + 1, so applying the blocks one by one with offsets 0 … L-1 gives the same ranges as applying all L at once. The "ring", "brickwork" and "hardware_efficient" blocks do not depend on the layer index.

build_encoding_qnode

build_encoding_qnode(n_qubits: int = 8, n_layers: int = 2, rotation: RotationAxis = 'X', device_name: DeviceName = 'auto', diff_method: DiffMethod = 'auto', entangler: Entangler = 'ring', readout: Readout = 'all', shots: int | None = None, seed: int | None = None) -> qml.QNode

Build and return a PennyLane QNode for the angle-embedding feature map.

The QNode is bound to the device resolve_device returns for device_name and configured for the specified differentiation method.

Parameters:

Name Type Description Default
n_qubits int

Number of qubits. Must equal the dimensionality of the input feature vector after PCA reduction. Default: 8.

8
n_layers int

Number of entangling + rotation layers in the VQC ansatz. More layers increase expressibility but deepen the circuit. Default: 2.

2
rotation RotationAxis

Pauli rotation axis for AngleEmbedding: "X" (default) or "Y". "Z" raises: a single RZ on |0⟩ is only a phase, so the layer would not depend on its inputs.

'X'
device_name DeviceName

PennyLane device name. Default "auto": default.qubit up to 12 qubits, lightning.qubit above (see resolve_backend). The simulators in KNOWN_DEVICES fall back along lightning.qubit → default.qubit with a warning per step when unavailable; any other name (a plugin or hardware) is constructed as given, and PennyLane's error surfaces if it cannot be. Hardware needs shots and diff_method="parameter-shift".

'auto'
diff_method DiffMethod

Differentiation strategy:

  • "auto" — the default; chosen by device, see resolve_backend.
  • "adjoint" — exact, O(p) memory; fastest on lightning.
  • "parameter-shift" — exact, hardware-compatible, O(p) circuit evals.
  • "backprop" — auto-diff through simulator; requires default.qubit.
  • "finite-diff" — approximate; avoid for training.
'auto'
entangler Entangler

"ring" (default), "strongly_entangling", "brickwork" or "hardware_efficient"; see apply_variational_layers.

'ring'
readout Readout

"all" (default): ⟨Z_i⟩ on every qubit. "first": ⟨Z_0⟩ only.

'all'
shots int | None

Finite-shot sampling and the device seed, as for QuantumEncodingLayer.

None
seed int | None

Finite-shot sampling and the device seed, as for QuantumEncodingLayer.

None

Returns:

Type Description
QNode

A callable QNode with signature (inputs: Tensor, weights: Tensor) -> Tensor where outputs are ⟨Z_i⟩ expectation values, shape (n_qubits,) (or (1,) with readout="first").

Raises:

Type Description
ValueError

If n_qubits < 2 (minimum for a meaningful entangling ring), or if rotation, entangler or readout is not one of the values above, or if rotation="Z" -- all checked here, before the circuit first runs.

Examples:

>>> qnode = build_encoding_qnode(n_qubits=8, n_layers=2)
>>> x = torch.rand(8)
>>> w = torch.zeros(2, 8, 3)
>>> result = qnode(x, w)  # list of 8 expectation values

measure_z

measure_z(n_qubits: int, readout: Readout = 'all') -> list[qml.measurements.ExpectationMP]

[⟨Z_i⟩ for i in readout_wires(...)]: the circuit's return value.

readout_wires

readout_wires(n_qubits: int, readout: Readout = 'all') -> list[int]

Wires measured in ⟨Z⟩: every qubit ("all") or qubit 0 only ("first").

build_data_reuploading_qnode

build_data_reuploading_qnode(n_qubits: int = 8, n_layers: int = 2, rotation: RotationAxis = 'X', device_name: DeviceName = 'auto', diff_method: DiffMethod = 'auto', trainable_input_scaling: bool = False, entangler: Entangler = 'ring', readout: Readout = 'all', shots: int | None = None, seed: int | None = None) -> qml.QNode

Build and return a PennyLane QNode for the data re-uploading circuit.

Parameters:

Name Type Description Default
n_qubits int

Number of qubits (= number of input features).

8
n_layers int

Number of uploads, each followed by one entangling + rotation block.

2
rotation RotationAxis

Pauli axis of the embedding rotations: "X" (default), "Y", "Z".

'X'
device_name DeviceName 'auto'
diff_method DeviceName 'auto'
trainable_input_scaling bool

If True the QNode takes a third argument input_scaling of shape input_scaling_shape that multiplies the features before each scaled upload.

False
entangler Entangler 'ring'
readout Entangler 'ring'

Raises:

Type Description
ValueError

If n_qubits < 2, n_layers < 1, rotation, entangler or readout is not one of the values above, or rotation="Z" with n_layers=1 (a lone RZ upload on |0⟩ is a global phase, so the outputs would not depend on the inputs). All are checked here, before the circuit first runs.

input_scaling_shape

input_scaling_shape(n_qubits: int, n_layers: int, rotation: RotationAxis) -> tuple[int, int]

Shape of input_scaling: one row per scaled upload, one column per qubit.

build_iqp_qnode

build_iqp_qnode(n_qubits: int = 8, n_layers: int = 2, n_repeats: int = 1, device_name: DeviceName = 'auto', diff_method: DiffMethod = 'auto', entangler: Entangler = 'ring', readout: Readout = 'all', shots: int | None = None, seed: int | None = None) -> qml.QNode

Build and return a PennyLane QNode for the IQP feature map.

Input checks

Not exported from hqnn_forge.encoding, but used by every encoder to validate its options and inputs; import them from their module when writing a new one (see the contributor docs on extending the library).

hqnn_forge.encoding.angle_embedding.validate_circuit_options

validate_circuit_options(n_qubits: int, entangler: Entangler, readout: Readout, rotation: RotationAxis | None = None) -> None

Raise ValueError for an entangler, readout or (if given) rotation outside the allowed values.

The encoding builders call this eagerly: qml.AngleEmbedding only rejects the axis when the circuit first runs, which is a forward pass away from the constructor that was given it -- and past get_config and a checkpoint.

hqnn_forge.encoding.angle_embedding.check_inputs

check_inputs(x: Tensor, expected: int, name: str = 'n_qubits', hint: str = '') -> None

Raise ValueError unless x has expected features and is finite.

The shared check of every encoding layer's prepare_inputs. A NaN or ±inf angle is simulated without error and gives NaN outputs, so it is refused here, where forward and hqnn_forge.kernels both see it. hint is appended to the width message.

Devices and weight shapes

Also not exported from hqnn_forge.encoding: the device a layer is built on, with its fallback chain, and the shape of the variational weights for each entangler.

hqnn_forge.encoding.angle_embedding.resolve_device

resolve_device(device_name: DeviceName, n_qubits: int, *, seed: int | None = None) -> qml.devices.Device

Create device_name, falling back along FALLBACK_CHAIN when a backend is not installed or has no usable hardware.

A backend that fails is remembered for the rest of the process: later layers skip it without trying again, and its RuntimeWarning is issued once, not once per layer (reset_device_fallback forgets them). The warning is attributed to the first frame outside hqnn_forge -- the user's call -- whichever layer or classifier constructor led here.

The chain is requested → lightning.qubit → default.qubit; entries at or before the requested device are skipped, so lightning.qubit falls straight to default.qubit and default.qubit has no fallback.

Only the four simulators in KNOWN_DEVICES enter the chain. Any other name -- a PennyLane plugin device or hardware -- is constructed exactly as given, and PennyLane's error surfaces if it cannot be: a typo such as "default.qbit" raises rather than quietly running on another simulator.

Parameters:

Name Type Description Default
device_name DeviceName

Preferred PennyLane device string. "lightning.gpu" (cuQuantum, NVIDIA) and "lightning.kokkos" (Kokkos: OpenMP on the PyPI wheel, CUDA/HIP when built from source) are the accelerated backends; see the README for their prerequisites.

required
n_qubits int

Number of qubits to allocate.

required
seed int | None

Seed of the device's own random generator, which draws the shot samples. None creates an unseeded private generator, without drawing from NumPy's global RNG. Neither torch.manual_seed nor numpy.random.seed controls it: pass seed for repeatable shot-based results. default.qubit, default.mixed and the lightning devices honour it. Other plugins receive an explicit seed as given, while None leaves their default unchanged (they need not accept a seed keyword). See validate_seed.

None

Returns:

Type Description
Device

An initialised PennyLane device ready for QNode attachment.

Raises:

Type Description
DeviceError

If device_name is outside KNOWN_DEVICES and no installed plugin registers it, e.g. a typo. A plugin that registers the name but cannot construct the device raises its own exception.

RuntimeError

If the state vector does not fit in memory: the lightning plugins raise one naming memory (see is_out_of_memory), which is raised rather than fallen back from.

RuntimeWarning

If a warnings-as-errors filter is active: the warning a fallback issues is raised instead of falling back.

Exception

Any other error constructing a device in the chain, which is raised at that step, and the last backend's own error if every step fails; default.qubit has no dependencies, so the latter means PennyLane itself is broken.

hqnn_forge.encoding.angle_embedding.is_out_of_memory

is_out_of_memory(exc: BaseException) -> bool

Whether a device-creation failure is the state vector not fitting.

Every backend in the chain allocates the same 2**n_qubits amplitudes, so falling back cannot help and would only move the allocation from GPU memory to host memory, where it can get the process killed instead of raising. The lightning plugins raise a bare RuntimeError naming memory. AllocationError is accepted too, as a precaution: PennyLane raises it for dynamically allocated wires, not for device creation.

hqnn_forge.encoding.angle_embedding.KNOWN_DEVICES module-attribute

KNOWN_DEVICES: tuple[str, ...] = get_args(KnownDevice)

The simulators resolve_device puts on the fallback chain when their backend is unavailable (default.qubit, the chain's end, has nowhere to fall back to). Every other name is constructed as given.

hqnn_forge.encoding.angle_embedding.FALLBACK_CHAIN module-attribute

FALLBACK_CHAIN: tuple[str, ...] = ('lightning.qubit', 'default.qubit')

Devices tried, in order, after the requested one fails. Each is a strict subset of the previous one's requirements: lightning.qubit needs only the pennylane-lightning wheel, default.qubit ships with PennyLane.

hqnn_forge.encoding.angle_embedding.reset_device_fallback

reset_device_fallback() -> None

Forget the backends that failed to initialise, so the next layer tries them again -- e.g. after installing a plugin in a running session.

hqnn_forge.encoding.angle_embedding.variational_weight_shape

variational_weight_shape(entangler: Entangler, n_qubits: int, n_layers: int) -> tuple[int, ...]

Shape of the weights tensor apply_variational_layers reads for entangler.

The one place an encoder's variational weight shape is defined: every encoding layer registers its weights with this shape. Dim 0 is always the layer index, which block_local_init_ and the diagnostics' n_layers fallback rely on: (n_layers, n_qubits, 3) for the Rot blocks ("ring", "strongly_entangling", "brickwork"), (n_layers, n_qubits) for the RY of "hardware_efficient".

Raises:

Type Description
ValueError

For an unknown entangler.