Source code for pyqit.models.layers.vqc

import inspect

import numpy as np
import pennylane as qml

from pyqit.ansatzes.sel import SELAnsatz
from pyqit.core.embeddings import AngleEmbedding
from pyqit.models.base.quantum_model import BaseQuantumModel


def z_from_probs(n_qubits: int):
    """Matrix ``M`` with ``probs @ M`` the ``<Z>`` of every wire, wire 0 first.

    Reading ``qml.probs`` and projecting keeps the QNode's output one tensor
    on both backends; a tuple of ``qml.expval`` comes back from ``TorchLayer``
    in a version-dependent shape.
    """
    bits = (np.arange(2**n_qubits)[:, None] >> np.arange(n_qubits)[::-1]) & 1
    return 1.0 - 2.0 * bits


[docs] class BaseVQC(BaseQuantumModel): """An embedding, an ansatz, a measurement: the circuit the VQC models share. Builds the ansatz and the embedding from their classes, draws the weights, and registers one QNode under ``main_circuit``. It has no readout of its own, so you subclass it and never instantiate it directly. A subclass implements ``_resolve_readout(n_qubits, measure_fn, measure_wires)``, which sets ``_measure_fn`` and ``_measure_wires``, and ``forward``, which runs ``execute_qnode("main_circuit", X, **custom_weights)`` and maps the raw output. `VQCClassifier`, `VQCRegressor` and `QuantumLayer` differ only in those two methods. Parameters ---------- n_qubits : int, default 4 n_layers : int, default 3 Depth passed to `ansatz`. ansatz : type, default SELAnsatz Ansatz class, not an instance. encoder : type, default AngleEmbedding Embedding class, not an instance. Stored as ``embedding_obj``, which drives prescaling. measure_fn : callable, optional Handed to ``_resolve_readout``, which picks the default. measure_wires : list of int, optional Handed to ``_resolve_readout``, which picks the default. device : str, default "default.qubit" shots : int, optional diff_method : str, default "best" Passed to the QNode. ``"best"`` picks backprop on a simulator; ``"parameter-shift"`` rehearses a hardware run's gradient cost. """ def __init__( self, n_qubits=4, n_layers=3, ansatz=SELAnsatz, encoder=AngleEmbedding, measure_fn=None, measure_wires=None, device="default.qubit", shots=None, diff_method="best", ): if not inspect.isclass(ansatz): raise TypeError( f"'ansatz' must be a class (e.g., SELAnsatz), " f"got {type(ansatz).__name__}" ) if not inspect.isclass(encoder): raise TypeError( f"'encoder' must be a class (e.g., AngleEmbedding), " f"got {type(encoder).__name__}" ) super().__init__(device=device, shots=shots, diff_method=diff_method) self.n_qubits = n_qubits self.n_layers = n_layers self.ansatz = ansatz self.encoder = encoder self.measure_fn = measure_fn self.measure_wires = measure_wires self._ansatz_name = self.ansatz.__name__ self._encoder_name = self.encoder.__name__ self.ansatz_obj = self.ansatz(n_qubits=n_qubits, n_layers=n_layers) self.embedding_obj = self.encoder(n_qubits=n_qubits) self._resolve_readout(n_qubits, measure_fn, measure_wires) weight_shapes = self.ansatz_obj.get_weight_shapes() self.weight_keys = list(weight_shapes.keys()) init_weights = self.init_weights(weight_shapes) dev = qml.device(self.device, wires=self.n_qubits) primary_qnode = qml.set_shots( qml.QNode( self._circuit, dev, interface=self.get_interface(), diff_method=self.diff_method, ), shots=self.shots, ) self.register_qnode( "main_circuit", primary_qnode, weight_shapes, weights=init_weights ) def _circuit(self, inputs, **weights): self.embedding_obj.forward(inputs) self.ansatz_obj.build_circuit(weights) return self._measure_fn(self._measure_wires)