Skip to content

Quantum models

QuantumModel(circuit, observable=None, backend=BackendName.PYQTORCH, diff_mode=DiffMode.AD, protocol=None, configuration=None)

Bases: Module

The central class of qadence that executes QuantumCircuits and make them differentiable.

This class should be used as base class for any new quantum model supported in the qadence framework for information on the implementation of custom models see here.

Initialize a generic QuantumModel instance.

PARAMETER DESCRIPTION
circuit

The circuit that is executed.

TYPE: QuantumCircuit

observable

Optional observable(s) that are used only in the expectation method. You can also provide observables on the fly to the expectation call directly.

TYPE: list[AbstractBlock] | AbstractBlock | None DEFAULT: None

backend

A backend for circuit execution.

TYPE: BackendName | str DEFAULT: PYQTORCH

diff_mode

A differentiability mode. Parameter shift based modes work on all backends. AD based modes only on PyTorch based backends.

TYPE: DiffMode DEFAULT: AD

protocol

Optional measurement protocol. If None, use exact expectation value with a statevector simulator.

TYPE: Measurements | None DEFAULT: None

configuration

Configuration for the backend.

TYPE: BackendConfiguration | dict | None DEFAULT: None

RAISES DESCRIPTION
ValueError

if the diff_mode argument is set to None

Source code in qadence/models/quantum_model.py
def __init__(
    self,
    circuit: QuantumCircuit,
    observable: list[AbstractBlock] | AbstractBlock | None = None,
    backend: BackendName | str = BackendName.PYQTORCH,
    diff_mode: DiffMode = DiffMode.AD,
    protocol: Measurements | None = None,
    configuration: BackendConfiguration | dict | None = None,
):
    """Initialize a generic QuantumModel instance.

    Arguments:
        circuit: The circuit that is executed.
        observable: Optional observable(s) that are used only in the `expectation` method. You
            can also provide observables on the fly to the expectation call directly.
        backend: A backend for circuit execution.
        diff_mode: A differentiability mode. Parameter shift based modes work on all backends.
            AD based modes only on PyTorch based backends.
        protocol: Optional measurement protocol. If None, use
            exact expectation value with a statevector simulator.
        configuration: Configuration for the backend.

    Raises:
        ValueError: if the `diff_mode` argument is set to None
    """
    super().__init__()

    if not isinstance(circuit, QuantumCircuit):
        TypeError(
            f"The circuit should be of type '<class QuantumCircuit>'. Got {type(circuit)}."
        )

    self.inputs = [p for p in circuit.unique_parameters if not p.trainable and not p.is_number]
    if diff_mode is None:
        raise ValueError("`diff_mode` cannot be `None` in a `QuantumModel`.")

    self.backend = backend_factory(
        backend=backend, diff_mode=diff_mode, configuration=configuration
    )

    if isinstance(observable, list) or observable is None:
        observable = observable
    else:
        observable = [observable]

    conv = self.backend.convert(circuit, observable)
    self.embedding_fn = conv.embedding_fn
    self._circuit = conv.circuit
    self._observable = conv.observable
    self._backend_name = backend
    self._diff_mode = diff_mode
    self._protocol = protocol

    self._params = nn.ParameterDict(
        {
            str(key): nn.Parameter(val, requires_grad=val.requires_grad)
            for key, val in conv.params.items()
        }
    )

in_features: int property

Number of inputs.

num_vparams: int property

The number of variational parameters

out_features: int | None property

Number of outputs

vals_vparams: Tensor property

Dictionary with parameters which are actually updated during optimization

assign_parameters(values)

Return the final, assigned circuit that is used in e.g. backend.run

Source code in qadence/models/quantum_model.py
def assign_parameters(self, values: dict[str, Tensor]) -> Any:
    """Return the final, assigned circuit that is used in e.g. `backend.run`"""
    params = self.embedding_fn(self._params, values)
    return self.backend.assign_parameters(self._circuit, params)

expectation(values={}, observable=None, state=None, protocol=None, endianness=Endianness.BIG)

Compute expectation using the given backend.

RETURNS DESCRIPTION
Tensor

A torch.Tensor of shape n_batches x n_obs

Source code in qadence/models/quantum_model.py
def expectation(
    self,
    values: dict[str, Tensor] = {},
    observable: list[ConvertedObservable] | ConvertedObservable | None = None,
    state: Optional[Tensor] = None,
    protocol: Measurements | None = None,
    endianness: Endianness = Endianness.BIG,
) -> Tensor:
    """Compute expectation using the given backend.

    Returns:
        A torch.Tensor of shape n_batches x n_obs
    """
    if observable is None:
        if self._observable is None:
            raise ValueError(
                "Provide an AbstractBlock as the observable to compute expectation."
                "Either pass a 'native_observable' directly to 'QuantumModel.expectation'"
                "or pass a (non-native) '<class AbstractBlock>' to the 'QuantumModel.__init__'."
            )
        observable = self._observable

    params = self.embedding_fn(self._params, values)
    if protocol is None:
        protocol = self._protocol

    return self.backend.expectation(
        circuit=self._circuit,
        observable=observable,
        param_values=params,
        state=state,
        protocol=protocol,
        endianness=endianness,
    )

reset_vparams(values)

Reset all the variational parameters with a given list of values

Source code in qadence/models/quantum_model.py
def reset_vparams(self, values: Sequence) -> None:
    """Reset all the variational parameters with a given list of values"""
    current_vparams = OrderedDict({k: v for k, v in self._params.items() if v.requires_grad})

    assert (
        len(values) == self.num_vparams
    ), "Pass an iterable with the values of all variational parameters"
    for i, k in enumerate(current_vparams.keys()):
        current_vparams[k].data = torch.tensor([values[i]])

QNN(circuit, observable, transform=None, backend=BackendName.PYQTORCH, diff_mode=DiffMode.AD, protocol=None, configuration=None)

Bases: QuantumModel

Quantum neural network model for n-dimensional inputs

Examples:

import torch
from qadence import QuantumCircuit, QNN
from qadence import hea, feature_map, hamiltonian_factory, Z

# create the circuit
n_qubits, depth = 2, 4
fm = feature_map(n_qubits)
ansatz = hea(n_qubits=n_qubits, depth=depth)
circuit = QuantumCircuit(n_qubits, fm, ansatz)
obs_base = hamiltonian_factory(n_qubits, detuning = Z)

# the QNN will yield two outputs
obs = [2.0 * obs_base, 4.0 * obs_base]

# initialize and use the model
qnn = QNN(circuit, obs, diff_mode="ad", backend="pyqtorch")
y = qnn.expectation({"phi": torch.rand(3)})
tensor([[0.7110, 1.4221],
        [1.6172, 3.2344],
        [1.1616, 2.3231]], grad_fn=<CatBackward0>)

Initialize the QNN

The number of inputs is determined by the feature parameters in the input quantum circuit while the number of outputs is determined by how many observables are provided as input

PARAMETER DESCRIPTION
circuit

The quantum circuit to use for the QNN.

TYPE: QuantumCircuit

transform

A transformation applied to the output of the QNN.

TYPE: Callable[[Tensor], Tensor] DEFAULT: None

backend

The chosen quantum backend.

TYPE: BackendName DEFAULT: PYQTORCH

diff_mode

The differentiation engine to use. Choices 'gpsr' or 'ad'.

TYPE: DiffMode DEFAULT: AD

protocol

optional measurement protocol. If None, use exact expectation value with a statevector simulator

TYPE: Measurements | None DEFAULT: None

configuration

optional configuration for the backend

TYPE: BackendConfiguration | dict | None DEFAULT: None

Source code in qadence/models/qnn.py
def __init__(
    self,
    circuit: QuantumCircuit,
    observable: list[AbstractBlock] | AbstractBlock,
    transform: Callable[[Tensor], Tensor] = None,  # transform output of the QNN
    backend: BackendName = BackendName.PYQTORCH,
    diff_mode: DiffMode = DiffMode.AD,
    protocol: Measurements | None = None,
    configuration: BackendConfiguration | dict | None = None,
):
    """Initialize the QNN

    The number of inputs is determined by the feature parameters in the input
    quantum circuit while the number of outputs is determined by how many
    observables are provided as input

    Args:
        circuit: The quantum circuit to use for the QNN.
        transform: A transformation applied to the output of the QNN.
        backend: The chosen quantum backend.
        diff_mode: The differentiation engine to use. Choices 'gpsr' or 'ad'.
        protocol: optional measurement protocol. If None,
            use exact expectation value with a statevector simulator
        configuration: optional configuration for the backend

    """
    super().__init__(
        circuit=circuit,
        observable=observable,
        backend=backend,
        diff_mode=diff_mode,
        protocol=protocol,
        configuration=configuration,
    )

    if self.out_features is None:
        raise ValueError("You need to provide at least one observable in the QNN constructor")

    self.transform = transform if transform else lambda x: x

forward(values=None, state=None, protocol=None, endianness=Endianness.BIG)

Forward pass of the model

This returns the (differentiable) expectation value of the given observable operator defined in the constructor. Differently from the base QuantumModel class, the QNN accepts also a tensor as input for the forward pass. The tensor is expected to have shape: n_batches x in_features where n_batches is the number of data points and in_features is the dimensionality of the problem

The output of the forward pass is the expectation value of the input observable(s). If a single observable is given, the output shape is n_batches while if multiple observables are given the output shape is instead n_batches x n_observables

PARAMETER DESCRIPTION
values

the values of the feature parameters

TYPE: dict[str, Tensor] | Tensor DEFAULT: None

RETURNS DESCRIPTION
Tensor

a tensor with the expectation value of the observables passed in the constructor of the model

TYPE: Tensor

Source code in qadence/models/qnn.py
def forward(
    self,
    values: dict[str, Tensor] | Tensor = None,
    state: Tensor | None = None,
    protocol: Measurements | None = None,
    endianness: Endianness = Endianness.BIG,
) -> Tensor:
    """Forward pass of the model

    This returns the (differentiable) expectation value of the given observable
    operator defined in the constructor. Differently from the base QuantumModel
    class, the QNN accepts also a tensor as input for the forward pass. The
    tensor is expected to have shape: `n_batches x in_features` where `n_batches`
    is the number of data points and `in_features` is the dimensionality of the problem

    The output of the forward pass is the expectation value of the input
    observable(s). If a single observable is given, the output shape is
    `n_batches` while if multiple observables are given the output shape
    is instead `n_batches x n_observables`

    Args:
        values (dict[str, Tensor] | Tensor): the values of the feature parameters

    Returns:
        Tensor: a tensor with the expectation value of the observables passed
            in the constructor of the model
    """
    if values is None:
        values = {}
    if not isinstance(values, dict):
        values = self._format_to_dict(values)
    if protocol is None:
        protocol = self._protocol

    return self.transform(
        self.expectation(values=values, state=state, protocol=protocol, endianness=endianness)
    )