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']
CircuitLayer ¶
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).
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
- The last dimension must equal
n_features. - If
n_features < 2**n_qubitsthe vector is zero-padded on the right to2**n_qubitsamplitudes. - 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, |
None
|
device_name
|
DeviceName
|
PennyLane device name. Default |
'auto'
|
diff_method
|
DiffMethod
|
Gradient method, |
'auto'
|
entangler
|
Entangler
|
The variational block: |
'ring'
|
readout
|
Readout
|
|
'all'
|
noise_level
|
float
|
Training-time noise, exactly as for
|
0.0
|
noise_position
|
float
|
Training-time noise, exactly as for
|
0.0
|
noise_method
|
float
|
Training-time noise, exactly as for
|
0.0
|
noise_trajectories
|
float
|
Training-time noise, exactly as for
|
0.0
|
noise_channel
|
float
|
Training-time noise, exactly as for
|
0.0
|
readout_error
|
tuple[float, float] | None
|
A train-mode readout error |
None
|
shots
|
int | None
|
Finite-shot sampling and the device seed, exactly as for
|
None
|
seed
|
int | None
|
Finite-shot sampling and the device seed, exactly as for
|
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
n_qubits |
int
|
|
n_layers |
int
|
|
n_features |
int
|
Width of the input, before padding to |
n_amplitudes |
int
|
|
qlayer |
TorchLayer
|
|
Methods:
| Name | Description |
|---|---|
prepare_inputs |
The padding and normalisation |
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])
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 |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
Shape |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the last dimension of |
RuntimeError
|
If |
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 |
2
|
rotation
|
RotationAxis
|
Pauli axis for AngleEmbedding: |
'X'
|
device_name
|
DeviceName
|
PennyLane device name. Default |
'auto'
|
diff_method
|
DiffMethod
|
|
'auto'
|
entangler
|
Entangler
|
|
'ring'
|
readout
|
Readout
|
|
'all'
|
noise_level
|
float
|
Strength of the |
0.0
|
noise_position
|
Position
|
|
'all'
|
noise_method
|
NoiseMethod
|
|
'density'
|
noise_trajectories
|
int
|
Draws averaged per sample with |
1
|
noise_channel
|
Channel
|
The channel |
'depolarizing'
|
shots
|
int | None
|
|
None
|
seed
|
int | None
|
Seed of the device's generator, which draws the shot samples; a
non-negative |
None
|
readout_error
|
tuple[float, float] | None
|
|
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
n_qubits |
int
|
|
n_features |
int
|
Width of the input, one feature per qubit: |
n_layers |
int
|
|
n_outputs |
int
|
Width of the output: |
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])
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 |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
Quantum expectation values of shape |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the last dimension of |
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
|
2
|
rotation
|
RotationAxis
|
Pauli axis of the embedding rotations. Default: |
'X'
|
device_name
|
DeviceName
|
PennyLane device name. Default |
'auto'
|
diff_method
|
DiffMethod
|
|
'auto'
|
trainable_input_scaling
|
bool
|
Add a trainable |
False
|
entangler
|
Entangler
|
|
'ring'
|
readout
|
Readout
|
|
'all'
|
noise_level
|
float
|
Training-time noise, exactly as for
|
0.0
|
noise_position
|
float
|
Training-time noise, exactly as for
|
0.0
|
noise_method
|
float
|
Training-time noise, exactly as for
|
0.0
|
noise_trajectories
|
float
|
Training-time noise, exactly as for
|
0.0
|
noise_channel
|
float
|
Training-time noise, exactly as for
|
0.0
|
readout_error
|
tuple[float, float] | None
|
A train-mode readout error |
None
|
shots
|
int | None
|
Finite-shot sampling and the device seed, exactly as for
|
None
|
seed
|
int | None
|
Finite-shot sampling and the device seed, exactly as for
|
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
n_qubits, n_layers |
int
|
|
n_features |
int
|
Width of the input, one feature per qubit: |
n_outputs |
int
|
Width of the output: |
rotation |
str
|
|
trainable_input_scaling |
bool
|
|
entangler, readout |
str
|
|
qlayer |
TorchLayer
|
Owns |
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])
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 |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
Shape |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the last dimension of |
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.
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.
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 explicitdiff_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 toAUTO_BACKPROP_MAX_QUBITSqubits,"lightning.qubit"above. - Method. With
shots,"parameter-shift": the state-vector methods cannot run on samples (seevalidate_shots). Otherwise by the device:"backprop"ondefault.qubitanddefault.mixed, which vectorise a batch;"adjoint"on the three lightning simulators inKNOWN_DEVICES;"parameter-shift"on any other device (lightning.tensorincluded, 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 |
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 |
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 ringCNOT(i → i+1 mod n), thenRot(φ, θ, ω)on every qubit."strongly_entangling":qml.StronglyEntanglingLayers, i.e.Roton 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, thenRoton 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) whilen_layersis small againstn_qubits; seehqnn_forge.initializers.restricted_variancefor the measured gradient variance."hardware_efficient": a nearest-neighbourCZ(i, i+1)ladder, thenRY(θ)on every qubit (Kandala et al. 2017):hqnn_forge.circuits.hardware_efficient_layer. One angle per qubit per layer andn − 1two-qubit gates per layer: a third of the parameters of theRotblocks, 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'
|
device_name
|
DeviceName
|
PennyLane device name. Default |
'auto'
|
diff_method
|
DiffMethod
|
Differentiation strategy:
|
'auto'
|
entangler
|
Entangler
|
|
'ring'
|
readout
|
Readout
|
|
'all'
|
shots
|
int | None
|
Finite-shot sampling and the device seed, as for
|
None
|
seed
|
int | None
|
Finite-shot sampling and the device seed, as for
|
None
|
Returns:
| Type | Description |
|---|---|
QNode
|
A callable QNode with signature
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
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'
|
device_name
|
DeviceName
|
'auto'
|
|
diff_method
|
DeviceName
|
'auto'
|
|
trainable_input_scaling
|
bool
|
If |
False
|
entangler
|
Entangler
|
'ring'
|
|
readout
|
Entangler
|
'ring'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
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. |
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
|
Returns:
| Type | Description |
|---|---|
Device
|
An initialised PennyLane device ready for QNode attachment. |
Raises:
| Type | Description |
|---|---|
DeviceError
|
If |
RuntimeError
|
If the state vector does not fit in memory: the lightning plugins
raise one naming memory (see |
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;
|
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 |