Models#

A model owns the QNode. It builds the circuit from an ansatz and an embedding, holds the weights, and exposes forward. BaseModel defines the contract and BaseQuantumModel adds the quantum-specific plumbing that every circuit model shares.

VQCClassifier is the model to start with. It takes a qubit count, a depth, an ansatz class and an embedding class, and wires them together:

from pyqit.ansatzes import SELAnsatz
from pyqit.core import AngleEmbedding
from pyqit.models import VQCClassifier

model = VQCClassifier(
    n_qubits=4,
    n_layers=3,
    ansatz=SELAnsatz,
    encoder=AngleEmbedding,
)

The ansatz and encoder arrive as classes, not instances. The model builds them with its own n_qubits, so the two cannot disagree about width.

Available models#

Each page gives the circuit, the paper it follows, every constructor argument and a runnable example.

VQCClassifier

Variational quantum classifier: an embedding, an ansatz, a measurement.

VQCRegressor

Variational quantum regressor: the VQCClassifier circuit read as a value.

DataReuploadingClassifier

Data re-uploading classifier of Perez-Salinas et al. (2020).

DressedQuantumClassifier

Dressed quantum circuit of Mari et al. (2020): dense, circuit, dense.

Two tags say what a model is. model_type is "quantum", "hybrid" or "classical"; estimator_type is "classifier" or "regressor". Trainer prints both in its summary, and all_objects filters on them:

from pyqit.base import all_objects

all_objects("model", filter_tags={"model_type": "hybrid"})

Classifiers and regressors#

A mixin decides how the Trainer reads a model’s output. ClassifierMixin turns it into class probabilities and hard labels. RegressorMixin passes the raw output through, and the training loops record accuracy as NaN for it.

Models that encode their own input#

A model that takes encoder exposes the built embedding as embedding_obj, and the DataModule prescales the input for it. A model that takes n_features instead encodes the input inside its own circuit. It has no embedding_obj, so the DataModule normalizes the features and leaves them unscaled.

Hybrid networks are pipelines#

A hybrid model mixes classical and quantum layers in one network and trains them together. Every hybrid model here is built the same way. It is a BaseQuantumModel that builds its layers from pyqit.models.layers and runs them through a pipeline inside forward. Its class page names the paper it follows and the layers it uses.

From the outside it is an ordinary model. The same Trainer fits, checkpoints and evaluates it, check_bp runs on it, and it can be a stage in a larger pipeline. The layers’ weights are the model’s own and sit in the same flat dict as any other model’s, under one prefix per layer.

from pyqit.models import DressedQuantumClassifier

model = DressedQuantumClassifier(n_features=8, n_qubits=4, n_layers=6)
history = pyqit.Trainer(max_epochs=20).fit(model, dm)
model.weights            # one prefix per layer

A hybrid model takes n_features instead of an encoder. The DataModule normalizes its input and leaves it unscaled, and the pipeline inside prescales each quantum layer’s input for that layer’s embedding.

The layers in pyqit.models.layers are reusable blocks, used by quantum and hybrid models alike. The pipeline page shows how to compose and train them yourself.

Weights exist before training#

A model draws its weights in __init__, not on the first fit. Two things follow. Seeding afterwards will not reproduce them, and calling set_backend() afterwards will not move the model, because each object reads the backend once in its own __init__ and caches it.

Weights come back as a flat dict keyed "<qnode_name>.<weight_name>", the same on both backends:

model.weights            # {"main_circuit.weights": array(...)}

update_weights writes that dict back. It is a no-op under torch, where autograd owns the parameters directly.

Devices#

device= goes straight to qml.device, so any PennyLane device name works, plugins included. shots=None asks for analytic simulation, and the defaults assume a local analytic simulator. PennyLane picks the differentiation method per device. default.qubit gets backprop, lightning.qubit adjoint, and any device with shots, or real hardware, parameter-shift. Parameter-shift runs 1 + 2 * n_params circuits for every gradient, so a model that trains in seconds locally can take hours on a queue. diff_methods tells you which method you are getting, and Trainer(verbose=2) prints it in the model summary next to the device. diff_method= names one instead; forcing "parameter-shift" on a simulator rehearses a hardware run’s gradient cost, and check_bp then counts the executions it takes.

model = VQCClassifier(n_qubits=4, device="qiskit.aer", shots=1024)
model.diff_methods(dm.X_train[:1])   # {"main_circuit": "parameter-shift"}

rehearsal = VQCClassifier(n_qubits=4, diff_method="parameter-shift")

pip install pyqit[qiskit] adds the Qiskit plugin. Its local simulators sample even at shots=None, where they run 1024 shots, so expect parameter-shift and shot noise there. No real QPU has been run against pyqit yet.

Writing a new model#

Expose the encoder as embedding_obj. The framework reads that attribute to decide prescaling, and a mismatched name disables prescaling silently instead of raising, which is the kind of bug that produces plausible numbers for weeks.

Beyond that, give the class an object_type tag of "model" and a get_test_params() method returning a list of kwarg dicts. There is no registration step. The suite discovers the class by walking the package and parametrizes every model test over it. Then add the class name to the list above.

You subclass these and never instantiate them directly.

BaseModel

Base class for all trainable models in PyQit.

BaseQuantumModel

Base class wiring a PennyLane QNode into either backend.

ClassifierMixin

Turns raw circuit output into class probabilities and hard labels.

RegressorMixin

Marks a model as a regressor: predictions are its raw output.

See the contributing guide for the checklist and the PR conventions.