Skip to content

Models

hqnn_forge.models

hqnn_forge.models

Full hybrid quantum-classical architectures.

Exported symbols

ClassifierBase nn.Module base of every classifier: get_config, count_parameters. BinaryClassifierBase ClassifierBase + sigmoid predict_proba / thresholded predict. HybridBinaryClassifier Linear encoder → QuantumEncodingLayer → Linear head. ParallelHybridClassifier classical MLP branch ‖ QuantumEncodingLayer branch → Linear head. MulticlassHybridClassifier Linear encoder → QuantumEncodingLayer → n_classes heads (softmax / OvR). ClassicalBaseline Plain MLP with the same interface: the classical control of an ablation. LinearClassifier One affine map to a logit: a linear head, logistic regression under BCE.

BinaryClassifierBase

Bases: ClassifierBase

Base class for hybrid binary classifiers.

Contract for subclasses

forward(x) takes (batch, n_input_features) and returns raw logits of shape (batch, 1). predict_proba and predict are derived from it and must not be overridden to keep the two models interchangeable.

Methods:

Name Description
predict_proba

Sigmoid of the logits, shape (batch,), computed in eval mode.

predict

predict_proba(x) >= threshold as torch.long.

count_parameters

Attributes:

Name Type Description
head Linear

The output layer producing the single logit. Every subclass assigns it in __init__, so code that only reads it can be typed against this class. classical_encoder and quantum_layer are deliberately not declared here: the hybrid models have them, but ClassicalBaseline does not, so a declaration on the base would let mypy accept an access that raises AttributeError at runtime.

head instance-attribute

head: Linear

forward

forward(x: Tensor) -> torch.Tensor

predict_proba

predict_proba(x: Tensor) -> torch.Tensor

Compute positive-class probabilities (inference mode, no gradients).

Runs in eval mode whatever mode the model is in, so dropout is off and repeated calls on the same input agree (except under finite shots, whose readouts are sampled afresh on every call). Every submodule's training flag is restored afterwards, so calling this mid-training leaves the model exactly as it was.

Parameters:

Name Type Description Default
x Tensor

Input tensor, shape (batch_size, n_input_features).

required

Returns:

Type Description
Tensor

Probability of class 1, shape (batch_size,), values ∈ [0, 1].

predict

predict(x: Tensor, threshold: float = 0.5) -> torch.Tensor

Predict binary labels. Runs in eval mode, like predict_proba.

Parameters:

Name Type Description Default
x Tensor

Input tensor, shape (batch_size, n_input_features).

required
threshold float

Decision threshold. Default: 0.5. For imbalanced datasets consider tuning via ROC/PR curves.

0.5

Returns:

Type Description
Tensor

Binary label tensor of shape (batch_size,), dtype torch.long.

ClassifierBase

Bases: Module

Head-agnostic base class for the classifiers.

Methods:

Name Description
count_parameters

Total number of (trainable) parameters, quantum and classical.

get_config

The constructor arguments, so type(model)(**model.get_config()) rebuilds an equivalent architecture. Subclasses record them in self._config at the top of __init__.

forward

forward(x: Tensor) -> torch.Tensor

count_parameters

count_parameters(trainable_only: bool = True) -> int

Return total parameter count (quantum + classical).

get_config

get_config() -> dict[str, Any]

Constructor arguments of this model, as a fresh dict.

type(model)(**model.get_config()) builds a model with the same architecture (load a state_dict for the trained weights). Its initial weights are fresh draws only if init_seed is None: a model built with init_seed rebuilds the same initial weights, so for restarts or ensemble members pass init_seed=None or a new seed. The same holds for a sampling model's device seed, which would replay the same shot noise: pass seed=None or a new one as well. Used by hqnn_forge.utils.checkpoint.

A module argument (a custom classical_encoder) is deep-copied, so a model built from the config does not share it with this one; the copy carries the module's current weights, since the model never re-initialises a custom encoder.

ClassicalBaseline

Bases: BinaryClassifierBase

MLP binary classifier with the hybrid classifiers' interface.

Parameters:

Name Type Description Default
n_input_features int

Input width.

required
hidden_dims Sequence[int]

Width of each hidden layer, at least one.

required
activation str

"relu" (default) or "tanh", after every hidden layer.

'relu'
dropout_p float

Dropout before the output layer, in [0, 1). Default: 0.0.

0.0
init_seed int | None

Seed for weight initialisation, as on the hybrid classifiers. None (default) draws the initial weights from the global torch RNG; an int draws them from a private RNG seeded with it, so the same seed gives the same weights and the global RNG is left exactly as it was.

None

Examples:

>>> model = ClassicalBaseline(n_input_features=30, hidden_dims=[10])
>>> model.count_parameters()
321

n_input_features instance-attribute

n_input_features = n_input_features

body instance-attribute

body = nn.Sequential(*layers)

dropout instance-attribute

dropout = nn.Dropout(dropout_p) if dropout_p > 0 else nn.Identity()

head instance-attribute

head = nn.Linear(hidden[-1], 1)

forward

forward(x: Tensor) -> torch.Tensor

Raw logits, shape (batch_size, 1).

HybridBinaryClassifier

Bases: QuantumTrunk, BinaryClassifierBase

Hybrid quantum-classical binary classifier.

See module docstring for architecture overview.

The published SHNN (thesis / hqnn-fraud-detection-benchmark) is embedding_rotation="Y", entangler="strongly_entangling", readout="first", encoder_activation="sigmoid", init_strategy="normal"; see published_shnn.

Parameters:

Name Type Description Default
n_input_features int

Number of raw (or PCA-reduced) input features. Default: 8.

8
n_qubits int

Number of qubits in the quantum encoding layer. Default: 8.

8
n_layers int

VQC ansatz layers. Default: 2. At 1, with angle encoding and readout="first", ⟨Z_0⟩ misses the first encoded angle under the default ring and RX embedding, and most of them under entangler="brickwork"; under "hardware_efficient" it sees at most n_layers + 1 of them at any depth. Use 2 or more with a ring. See step 2 of hqnn_forge.encoding.angle_embedding._make_angle_embedding_circuit.

2
use_classical_encoder bool

Prepend Linear(n_input_features → n_qubits) + Tanh. Default: True. If False, input must already lie in (-π, π); it is not rescaled.

True
dropout_p float

Dropout probability applied after the quantum layer. Default: 0.0.

0.0
device_name DeviceName

PennyLane device name. Default "auto": default.qubit up to 12 qubits, lightning.qubit above (see resolve_backend). The four simulators fall back along lightning.qubit → default.qubit with a warning when a backend cannot be initialised; any other name (a plugin or hardware) is used as given.

'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'
init_strategy str

"restricted" (default), "block_local", or "normal" (N(0, init_std²), the published SHNN's init).

'restricted'
encoding_type str

The quantum embedding. Default: "angle".

  • "angle": one rotation per feature (QuantumEncodingLayer).
  • "iqp": Hadamards, RZ(x_i) and pairwise x_i x_j phases (IQPEncodingLayer).
  • "reuploading": the angle embedding repeated before every variational layer (DataReuploadingLayer), optionally with trainable_input_scaling.
  • "amplitude": the features as the 2**n_qubits amplitudes of the state (AmplitudeEncodingLayer). The classical encoder then maps to 2**n_qubits features, and the ·π scaling is irrelevant because the layer normalises. Its input gradient is only correct under backprop, so with a classical encoder "auto" picks default.qubit/backprop at any size, and an explicit choice that resolves to another method raises. Without one, 1 to 2**n_qubits raw features are zero-padded.
'angle'
embedding_rotation RotationAxis

Pauli axis of the angle embedding, "X" (default), "Y" or "Z". Angle and re-uploading encodings only. "Z" raises under angle encoding, since a single RZ embedding on |0⟩ ignores the input; under re-uploading it needs n_layers ≥ 2.

'X'
entangler Entangler

"ring" (default: CNOT ring then Rot), "strongly_entangling" (qml.StronglyEntanglingLayers: Rot then a CNOT ring of growing range), "brickwork" (nearest-neighbour CNOT pairs, so each ⟨Z_i⟩ keeps a local light cone at shallow depth) or "hardware_efficient" (a CZ ladder then RY: a third of the circuit parameters). See hqnn_forge.encoding.angle_embedding.apply_variational_layers.

'ring'
readout Readout

"all" (default): the head reads every ⟨Z_i⟩. "first": ⟨Z_0⟩ only, so the head is Linear(1 → 1).

'all'
encoder_activation str

"tanh" (default): encoder output tanh(·)·π in (-π, π). "sigmoid": π·sigmoid(·) in (0, π). Requires use_classical_encoder=True; there is no activation without an encoder, so a non-default value raises rather than being ignored.

DEFAULT_ENCODER_ACTIVATION
init_std float

Standard deviation for init_strategy="normal". Default: 0.1. Raises under the other strategies, which derive their own sigma.

DEFAULT_INIT_STD
noise_level float

Training-time strength of noise_channel for the quantum layer: the depolarizing probability in [0, 0.75], or the damping or flip probability in [0, 1] for the other channels. Default: 0.0 (noiseless). Applied in train mode only. With the default noise_method it runs on default.mixed with backprop, whose memory grows as batch × 4^n_qubits per operation: practical up to about 6 qubits. See hqnn_forge.noise.

0.0
noise_position Position

"all" (default) or "end"; where the channel is inserted.

'all'
noise_method NoiseMethod

"density" (default, exact) or "trajectories" (Pauli-trajectory sampling on the layer's own device, at pure-state memory; equal to "density" on average). See QuantumEncodingLayer.

'density'
noise_trajectories int

Draws averaged per sample with "trajectories". Default: 1. 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
init_seed int | None

Seed for weight initialisation. None (default) draws the initial weights from the global torch RNG; an int draws them from a private RNG seeded with it, so the same seed gives the same weights and the global RNG is left exactly as it was.

None
classical_encoder Module | None

Your own module in place of the built-in Linear(n_input_features → n_qubits), trained together with the quantum layer: a small MLP, or a CNN or sequence model that reshapes the flat (batch, n_input_features) input itself. It must return (batch, n_qubits) ((batch, 2**n_qubits) with encoding_type="amplitude"), which is checked here with one forward pass. The model owns the angle range: it applies encoder_activation and the factor π on top of the module, exactly as for the built-in encoder, so the module should output unbounded features and not end in Tanh or Sigmoid (that warns). The module is used as given and never re-initialised, so pretrained weights are kept. Requires use_classical_encoder=True. save_checkpoint refuses a model with a custom encoder; save its state_dict instead. Default: None, the built-in encoder.

None
trainable_input_scaling bool

With encoding_type="reuploading" only: a trainable per-upload scale on the features, initialised to 1. Default: False.

False
shots int | None

None (default): exact expectation values. An int: each circuit is sampled that many times, as on hardware, so predictions carry shot noise. diff_method="auto" then picks "parameter-shift", the only method that works: adjoint and backprop need the exact state, and finite-diff's tiny step turns the shot noise into gradients of order 1e6. The samples come from the device's own generator, which neither torch.manual_seed nor numpy.random.seed reaches: pass seed for a model with shots that repeats run to run. hqnn_forge.noise.apply_shots evaluates a model with a finite shot count without rebuilding it.

None
noise_channel Channel

The channel noise_level is the strength of: "depolarizing" (default), "amplitude_damping", "phase_damping", "bit_flip" 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'
seed int | None

Seed of the device's random generator, which draws the shot samples, so a shot-based model gives the same samples on every run. Default None: unseeded, and neither torch.manual_seed nor numpy.random.seed reaches it. See hqnn_forge.encoding._common.resolve_device.

None
readout_error tuple[float, float] | None

(p01, p10): in train mode, each measured bit reads 1 instead of 0 with probability p01 and 0 instead of 1 with p10, applied exactly to the ⟨Z⟩ readouts (see hqnn_forge.noise.readout_error_map). It shifts every readout by p10 − p01, which the head learns, and predict_proba runs without it: evaluate inside hqnn_forge.noise.apply_readout_error with the same pair to keep the shift (#488). Default None.

None

Attributes:

Name Type Description
classical_encoder Sequential or Identity

Sequential(Linear, activation), Sequential(custom module, activation) with a custom classical_encoder, or Identity.

quantum_layer QuantumEncodingLayer
dropout Dropout
head Linear

Examples:

>>> import torch
>>> from hqnn_forge.models import HybridBinaryClassifier
>>> model = HybridBinaryClassifier(n_input_features=30, n_qubits=8, n_layers=2)
>>> x = torch.randn(4, 30)
>>> logits = model(x)           # shape (4, 1)
>>> probs  = model.predict_proba(x)  # shape (4,), values in [0, 1]

head instance-attribute

head = nn.Linear(n_readouts, 1)

published_shnn classmethod

published_shnn(**overrides: object) -> HybridBinaryClassifier

The SHNN configuration published in the thesis and in hqnn-fraud-detection-benchmark (configs/default.yaml, shnn): 8 qubits, 2 layers, Linear(8→8) + π·sigmoid, RY angle embedding, StronglyEntanglingLayers, ⟨Z_0⟩ readout, Linear(1→1) head, N(0, 0.1²) quantum init. 122 trainable parameters, 48 quantum.

Live parameters: 102 of the 122. With the ⟨Z_0⟩ readout, 20 of the 48 quantum weights can never move the output, for any input or weight values: the whole last-layer Rot on wires 0, 1, 3, 5 and 7, the last-layer ω on wires 2, 4 and 6, and the first-layer ω on wires 0 and 1 (measured with autograd and pinned in tests/test_published_shnn_parity.py). circuit_summary reports 16 inert, a lower bound: backwards through the last layer's range-2 CNOTs, Z_0 becomes Z_2 Z_4 Z_6, a cancellation a per-wire analysis cannot see.

The dead weights are kept (#234): the (2, 8, 3) weight shape is the published one, so the model and its checkpoints stay reproducible, and both counts are reported instead. Parameter efficiency figures use the total, as the published results do (MCC 0.5758 over 0.122 kParam = 4.72); over the 102 live parameters the same MCC is 5.65 per kParam. hqnn_forge.evaluation.parameter_efficiency takes an integer count for that.

Parameters:

Name Type Description Default
**overrides object

Constructor arguments to change, e.g. device_name or diff_method; the structural options above can be overridden too, at which point the model is no longer the published one.

{}

forward

forward(x: Tensor) -> torch.Tensor

Forward pass: classical encode → quantum encode → classification head.

Parameters:

Name Type Description Default
x Tensor

Input tensor, shape (batch_size, n_input_features).

required

Returns:

Type Description
Tensor

Raw logits, shape (batch_size, 1). Apply torch.sigmoid for probabilities, or pass directly to FocalLoss.

extra_repr

extra_repr() -> str

LinearClassifier

Bases: BinaryClassifierBase

Linear binary classifier, z = w · x + b, with the classifiers' interface.

One raw logit per sample, like every classifier in this package, so it trains under hqnn_forge.training.train_model and scores through predict_proba. The module docstring gives the model and says when it is a logistic regression.

Parameters:

Name Type Description Default
n_input_features int

Input width n, an integer >= 1.

required
init_seed int | None

Seed for weight initialisation, as on the other classifiers. None (default) draws the initial weights from the global torch RNG; an int draws them from a private RNG seeded with it, so the same seed gives the same weights and the global RNG is left exactly as it was.

None

Attributes:

Name Type Description
head Linear

Linear(n_input_features → 1): the whole model.

n_input_features int

Raises:

Type Description
TypeError

If init_seed is neither an int nor None.

ValueError

If n_input_features is not an integer >= 1.

Examples:

>>> import torch
>>> from hqnn_forge.models import LinearClassifier
>>> model = LinearClassifier(n_input_features=4)
>>> model.count_parameters()  # 4 weights and a bias
5
>>> with torch.no_grad():
...     _ = model.head.weight.copy_(torch.tensor([[1.0, 0.0, -1.0, 2.0]]))
...     _ = model.head.bias.fill_(0.5)
>>> model(torch.tensor([[2.0, 7.0, 1.0, -1.0]])).tolist()  # 2 − 1 − 2 + 0.5
[[-0.5]]

n_input_features instance-attribute

n_input_features: int = int(n_input_features)

head instance-attribute

head = nn.Linear(self.n_input_features, 1)

forward

forward(x: Tensor) -> torch.Tensor

Raw logits w · x + b, shape (batch_size, 1).

MulticlassHybridClassifier

Bases: QuantumTrunk, ClassifierBase

Hybrid quantum-classical multiclass classifier.

See the module docstring for the architecture and the two strategies.

Parameters:

Name Type Description Default
n_input_features int

Number of raw (or PCA-reduced) input features. Default: 8.

8
n_qubits int

Number of qubits in the quantum encoding layer. Default: 8.

8
n_layers int

VQC ansatz layers. Default: 2.

2
n_classes int

Number of classes. Default: 3. Must be ≥ 2.

3
strategy MulticlassStrategy

"softmax" (default) or "one_vs_rest".

'softmax'
use_classical_encoder bool
True
dropout_p bool
True
device_name bool
True
diff_method bool
True
init_strategy str
'restricted'
encoding_type str
'restricted'
embedding_rotation str
'restricted'
entangler str
'restricted'
readout str
'restricted'
encoder_activation str
DEFAULT_ENCODER_ACTIVATION
init_std str
DEFAULT_ENCODER_ACTIVATION
noise_level str
DEFAULT_ENCODER_ACTIVATION
noise_position str
DEFAULT_ENCODER_ACTIVATION
noise_method str
DEFAULT_ENCODER_ACTIVATION
noise_trajectories int
1
classical_encoder int
1
trainable_input_scaling int
1
shots int
1
noise_channel int
1
seed int
1
readout_error tuple[float, float] | None

The trunk's options, exactly as for HybridBinaryClassifier. With readout="first" every class head reads ⟨Z_0⟩ alone.

None
init_seed int | None

Seed for weight initialisation. None (default) draws the initial weights from the global torch RNG; an int draws them from a private RNG seeded with it, so the same seed gives the same weights and the global RNG is left exactly as it was.

None

Attributes:

Name Type Description
classical_encoder Sequential or Identity
quantum_layer QuantumEncodingLayer or IQPEncodingLayer
dropout Dropout or Identity
head Linear

weight[c] and bias[c] are class c's head.

Examples:

>>> import torch
>>> from hqnn_forge.models import MulticlassHybridClassifier
>>> model = MulticlassHybridClassifier(n_input_features=6, n_qubits=4, n_layers=1, n_classes=3)
>>> x = torch.randn(5, 6)
>>> model(x).shape                 # logits
torch.Size([5, 3])
>>> model.predict_proba(x).shape   # rows sum to 1
torch.Size([5, 3])
>>> model.predict(x).shape         # argmax labels in {0, 1, 2}
torch.Size([5])

n_classes instance-attribute

n_classes = n_classes

strategy instance-attribute

strategy = strategy

head instance-attribute

head = nn.Linear(n_readouts, n_classes)

forward

forward(x: Tensor) -> torch.Tensor

Forward pass: classical encode → quantum encode → class heads.

Parameters:

Name Type Description Default
x Tensor

Input tensor, shape (batch_size, n_input_features).

required

Returns:

Type Description
Tensor

Raw logits, shape (batch_size, n_classes). Feed to nn.CrossEntropyLoss (strategy="softmax") or to nn.BCEWithLogitsLoss with one_hot targets (strategy="one_vs_rest").

predict_proba

predict_proba(x: Tensor) -> torch.Tensor

Per-class probabilities, shape (batch_size, n_classes), rows summing to one. Softmax of the logits under "softmax"; sigmoid of each logit, normalised across classes, under "one_vs_rest". The latter is computed as softmax(logsigmoid(logits)), which is the same quantity but stays finite when every sigmoid in a row underflows (all logits below about -88 in float32), where the direct ratio would be 0 / 0.

Runs in eval mode whatever mode the model is in (dropout off) and restores every submodule's training flag afterwards.

predict

predict(x: Tensor) -> torch.Tensor

Predicted class labels, shape (batch_size,), dtype torch.long: the argmax of the logits, under both strategies.

This is the argmax of predict_proba in exact arithmetic, since both normalisations are monotone per row, but not always in floating point: under "one_vs_rest" large positive logits saturate the sigmoid, so e.g. logits [17, 20, 30] give float32 probabilities in which classes 1 and 2 tie, and predict_proba(x).argmax(-1) returns class 1 while predict returns class 2. Use this method, not predict_proba(x).argmax(-1), for labels.

one_hot

one_hot(y: Tensor) -> torch.Tensor

Integer labels (batch_size,) → one-hot (batch_size, n_classes) in the dtype of the class heads, the target format of nn.BCEWithLogitsLoss for the one-vs-rest strategy. Matching the heads' dtype keeps the loss of a model.double() in float64.

Float labels are accepted only if integer-valued; anything else (e.g. smoothed targets) raises instead of being truncated.

extra_repr

extra_repr() -> str

ParallelHybridClassifier

Bases: QuantumTrunk, BinaryClassifierBase

Parallel-topology hybrid quantum-classical binary classifier.

See module docstring for architecture overview. API-consistent with HybridBinaryClassifier — same encoding_type / init_strategy options and predict_proba / predict / count_parameters interface.

The published SHNN (thesis / hqnn-fraud-detection-benchmark) is embedding_rotation="Y", entangler="strongly_entangling", readout="first", encoder_activation="sigmoid", init_strategy="normal"; see published_shnn.

Parameters:

Name Type Description Default
n_input_features int

Number of raw (or PCA-reduced) input features. Default: 8.

8
n_qubits int

Number of qubits in the quantum branch. Default: 8.

8
n_layers int

VQC ansatz layers. Default: 2. At 1, with angle encoding and readout="first", ⟨Z_0⟩ misses the first encoded angle under the default ring and RX embedding, and most of them under entangler="brickwork"; under "hardware_efficient" it sees at most n_layers + 1 of them at any depth. Use 2 or more with a ring. See step 2 of hqnn_forge.encoding.angle_embedding._make_angle_embedding_circuit.

2
classical_hidden_dim int

Width of the classical MLP branch. Default: 16.

16
use_classical_encoder bool

Prepend Linear(n_input_features → n_qubits) + Tanh to the quantum branch. Default: True. If False, input must already lie in (-π, π); it is not rescaled.

True
dropout_p float

Dropout probability applied to the fused branch outputs. Default: 0.0.

0.0
device_name DeviceName

PennyLane device name. Default "auto": default.qubit up to 12 qubits, lightning.qubit above (see resolve_backend). The four simulators fall back along lightning.qubit → default.qubit with a warning when a backend cannot be initialised; any other name (a plugin or hardware) is used as given.

'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'
init_strategy str

"restricted" (default), "block_local", or "normal" (N(0, init_std²), the published SHNN's init).

'restricted'
encoding_type str

The quantum embedding. Default: "angle".

  • "angle": one rotation per feature (QuantumEncodingLayer).
  • "iqp": Hadamards, RZ(x_i) and pairwise x_i x_j phases (IQPEncodingLayer).
  • "reuploading": the angle embedding repeated before every variational layer (DataReuploadingLayer), optionally with trainable_input_scaling.
  • "amplitude": the features as the 2**n_qubits amplitudes of the state (AmplitudeEncodingLayer). The classical encoder then maps to 2**n_qubits features, and the ·π scaling is irrelevant because the layer normalises. Its input gradient is only correct under backprop, so with a classical encoder "auto" picks default.qubit/backprop at any size, and an explicit choice that resolves to another method raises. Without one, 1 to 2**n_qubits raw features are zero-padded.
'angle'
embedding_rotation RotationAxis

Pauli axis of the angle embedding, "X" (default), "Y" or "Z". Angle and re-uploading encodings only. "Z" raises under angle encoding, since a single RZ embedding on |0⟩ ignores the input; under re-uploading it needs n_layers ≥ 2.

'X'
entangler Entangler

"ring" (default: CNOT ring then Rot), "strongly_entangling" (qml.StronglyEntanglingLayers: Rot then a CNOT ring of growing range), "brickwork" (nearest-neighbour CNOT pairs, so each ⟨Z_i⟩ keeps a local light cone at shallow depth) or "hardware_efficient" (a CZ ladder then RY: a third of the circuit parameters). See hqnn_forge.encoding.angle_embedding.apply_variational_layers.

'ring'
readout Readout

"all" (default): the head reads every ⟨Z_i⟩. "first": ⟨Z_0⟩ only, so the head is Linear(1 → 1).

'all'
encoder_activation str

"tanh" (default): encoder output tanh(·)·π in (-π, π). "sigmoid": π·sigmoid(·) in (0, π). Requires use_classical_encoder=True; there is no activation without an encoder, so a non-default value raises rather than being ignored.

DEFAULT_ENCODER_ACTIVATION
init_std float

Standard deviation for init_strategy="normal". Default: 0.1. Raises under the other strategies, which derive their own sigma.

DEFAULT_INIT_STD
noise_level float

Training-time strength of noise_channel for the quantum layer: the depolarizing probability in [0, 0.75], or the damping or flip probability in [0, 1] for the other channels. Default: 0.0 (noiseless). Applied in train mode only. With the default noise_method it runs on default.mixed with backprop, whose memory grows as batch × 4^n_qubits per operation: practical up to about 6 qubits. See hqnn_forge.noise.

0.0
noise_position Position

"all" (default) or "end"; where the channel is inserted.

'all'
noise_method NoiseMethod

"density" (default, exact) or "trajectories" (Pauli-trajectory sampling on the layer's own device, at pure-state memory; equal to "density" on average). See QuantumEncodingLayer.

'density'
noise_trajectories int

Draws averaged per sample with "trajectories". Default: 1. 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
init_seed int | None

Seed for weight initialisation. None (default) draws the initial weights from the global torch RNG; an int draws them from a private RNG seeded with it, so the same seed gives the same weights and the global RNG is left exactly as it was.

None
classical_encoder Module | None

Your own module in place of the built-in Linear(n_input_features → n_qubits), trained together with the quantum layer: a small MLP, or a CNN or sequence model that reshapes the flat (batch, n_input_features) input itself. It must return (batch, n_qubits) ((batch, 2**n_qubits) with encoding_type="amplitude"), which is checked here with one forward pass. The model owns the angle range: it applies encoder_activation and the factor π on top of the module, exactly as for the built-in encoder, so the module should output unbounded features and not end in Tanh or Sigmoid (that warns). The module is used as given and never re-initialised, so pretrained weights are kept. Requires use_classical_encoder=True. save_checkpoint refuses a model with a custom encoder; save its state_dict instead. Default: None, the built-in encoder.

None
trainable_input_scaling bool

With encoding_type="reuploading" only: a trainable per-upload scale on the features, initialised to 1. Default: False.

False
shots int | None

None (default): exact expectation values. An int: each circuit is sampled that many times, as on hardware, so predictions carry shot noise. diff_method="auto" then picks "parameter-shift", the only method that works: adjoint and backprop need the exact state, and finite-diff's tiny step turns the shot noise into gradients of order 1e6. The samples come from the device's own generator, which neither torch.manual_seed nor numpy.random.seed reaches: pass seed for a model with shots that repeats run to run. hqnn_forge.noise.apply_shots evaluates a model with a finite shot count without rebuilding it.

None
noise_channel Channel

The channel noise_level is the strength of: "depolarizing" (default), "amplitude_damping", "phase_damping", "bit_flip" 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'
seed int | None

Seed of the device's random generator, which draws the shot samples, so a shot-based model gives the same samples on every run. Default None: unseeded, and neither torch.manual_seed nor numpy.random.seed reaches it. See hqnn_forge.encoding._common.resolve_device.

None
readout_error tuple[float, float] | None

(p01, p10): in train mode, each measured bit reads 1 instead of 0 with probability p01 and 0 instead of 1 with p10, applied exactly to the ⟨Z⟩ readouts (see hqnn_forge.noise.readout_error_map). It shifts every readout by p10 − p01, which the head learns, and predict_proba runs without it: evaluate inside hqnn_forge.noise.apply_readout_error with the same pair to keep the shift (#488). Default None.

None

Attributes:

Name Type Description
classical_branch Sequential
classical_encoder Sequential or Identity

Sequential(Linear, activation), Sequential(custom module, activation) with a custom classical_encoder, or Identity.

quantum_layer QuantumEncodingLayer or IQPEncodingLayer
dropout Dropout
head Linear

Examples:

>>> import torch
>>> from hqnn_forge.models import ParallelHybridClassifier
>>> model = ParallelHybridClassifier(n_input_features=30, n_qubits=8, n_layers=2)
>>> x = torch.randn(4, 30)
>>> logits = model(x)           # shape (4, 1)
>>> probs  = model.predict_proba(x)  # shape (4,), values in [0, 1]

classical_hidden_dim instance-attribute

classical_hidden_dim = classical_hidden_dim

classical_branch instance-attribute

classical_branch = nn.Sequential(nn.Linear(n_input_features, classical_hidden_dim), nn.ReLU(), nn.Linear(classical_hidden_dim, classical_hidden_dim), nn.ReLU())

head instance-attribute

head = nn.Linear(classical_hidden_dim + n_readouts, 1)

published_shnn classmethod

published_shnn(**overrides: object) -> ParallelHybridClassifier

The published SHNN's quantum branch and encoder (see HybridBinaryClassifier.published_shnn) alongside the classical MLP branch. overrides are passed to the constructor.

forward

forward(x: Tensor) -> torch.Tensor

Forward pass: classical branch and quantum branch process x independently, are concatenated, and fed through the classification head.

Parameters:

Name Type Description Default
x Tensor

Input tensor, shape (batch_size, n_input_features).

required

Returns:

Type Description
Tensor

Raw logits, shape (batch_size, 1). Apply torch.sigmoid for probabilities, or pass directly to FocalLoss.

extra_repr

extra_repr() -> str