Skip to content

Quantum models

QuantumModel(circuit, observable=None, backend=BackendName.PYQTORCH, diff_mode=DiffMode.AD, measurement=None, noise=None, mitigation=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

measurement

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

noise

A noise model to use.

TYPE: Noise | 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,
    measurement: Measurements | None = None,
    noise: Noise | None = None,
    mitigation: Mitigations | 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.
        measurement: Optional measurement protocol. If None, use
            exact expectation value with a statevector simulator.
        configuration: Configuration for the backend.
        noise: A noise model to use.

    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)}."
        )

    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]

    def _is_feature_param(p: Parameter) -> bool:
        return not p.trainable and not p.is_number

    if observable is None:
        self.inputs = list(filter(_is_feature_param, circuit.unique_parameters))
    else:
        uparams = unique_parameters(chain(circuit.block, *observable))
        self.inputs = list(filter(_is_feature_param, uparams))

    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._measurement = measurement
    self._noise = noise
    self._mitigation = mitigation
    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, measurement=None, noise=None, mitigation=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,
    measurement: Measurements | None = None,
    noise: Noise | None = None,
    mitigation: Mitigations | 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 measurement is None:
        measurement = self._measurement
    if noise is None:
        noise = self._noise
    else:
        self._noise = noise
    if mitigation is None:
        mitigation = self._mitigation
    return self.backend.expectation(
        circuit=self._circuit,
        observable=observable,
        param_values=params,
        state=state,
        measurement=measurement,
        noise=noise,
        mitigation=mitigation,
        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, measurement=None, noise=None, configuration=None, inputs=None)

Bases: QuantumModel

Quantum neural network model for n-dimensional inputs.

Examples:

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

# create the circuit
n_qubits, depth = 2, 4
fm = kron(
    feature_map(1, support=(0,), param="x"),
    feature_map(1, support=(1,), param="y")
)
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, inputs=["x", "y"])
y = qnn(torch.rand(3, 2))
tensor([[1.7388, 3.4776],
        [2.3549, 4.7099],
        [1.6751, 3.3501]], 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

inputs

Tuple that indicates the order of variables of the tensors that are passed to the model. Given input tensors xs = torch.rand(batch_size, input_size:=2) a QNN with inputs=("t", "x") will assign t, x = xs[:,0], xs[:,1].

TYPE: list[Basic | str] | None 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

measurement

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

TYPE: Measurements | None DEFAULT: None

noise

A noise model to use.

TYPE: Noise | 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,
    measurement: Measurements | None = None,
    noise: Noise | None = None,
    configuration: BackendConfiguration | dict | None = None,
    inputs: list[sympy.Basic | str] | 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.
        inputs: Tuple that indicates the order of variables of the tensors that are passed
            to the model. Given input tensors `xs = torch.rand(batch_size, input_size:=2)` a QNN
            with `inputs=("t", "x")` will assign `t, x = xs[:,0], xs[:,1]`.
        backend: The chosen quantum backend.
        diff_mode: The differentiation engine to use. Choices 'gpsr' or 'ad'.
        measurement: optional measurement protocol. If None,
            use exact expectation value with a statevector simulator
        noise: A noise model to use.
        configuration: optional configuration for the backend
    """
    super().__init__(
        circuit,
        observable=observable,
        backend=backend,
        diff_mode=diff_mode,
        measurement=measurement,
        configuration=configuration,
        noise=noise,
    )
    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

    if (inputs is not None) and (len(self.inputs) == len(inputs)):
        self.inputs = [sympy.symbols(x) if isinstance(x, str) else x for x in inputs]  # type: ignore[union-attr]
    elif (inputs is None) and len(self.inputs) <= 1:
        self.inputs = [sympy.symbols(x) if isinstance(x, str) else x for x in self.inputs]  # type: ignore[union-attr]
    else:
        raise ValueError(
            """
            Your QNN has more than one input. Please provide a list of inputs in the order of
            your tensor domain. For example, if you want to pass
            `xs = torch.rand(batch_size, input_size:=3)` to you QNN, where
            ```
            t = x[:,0]
            x = x[:,1]
            y = x[:,2]
            ```
            you have to specify
            ```
            QNN(circuit, observable, inputs=["t", "x", "y"])
            ```
            You can also pass a list of sympy symbols.
        """
        )

forward(values=None, state=None, measurement=None, noise=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

state

Initial state.

TYPE: Tensor | None DEFAULT: None

measurement

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

TYPE: Measurements | None DEFAULT: None

noise

A noise model to use.

TYPE: Noise | None DEFAULT: None

endianness

Endianness of the resulting bit strings.

TYPE: Endianness DEFAULT: BIG

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,
    measurement: Measurements | None = None,
    noise: Noise | 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: the values of the feature parameters
        state: Initial state.
        measurement: optional measurement protocol. If None,
            use exact expectation value with a statevector simulator
        noise: A noise model to use.
        endianness: Endianness of the resulting bit strings.

    Returns:
        Tensor: a tensor with the expectation value of the observables passed
            in the constructor of the model
    """
    return self.expectation(
        values, state=state, measurement=measurement, noise=noise, endianness=endianness
    )