Source code for pennylane.resource.error.error

# Copyright 2018-2024 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Stores classes and logic to define and track algorithmic error in a quantum workflow.
"""
from abc import ABC, abstractmethod
from collections.abc import Callable
from functools import partial

import pennylane as qml
from pennylane.operation import Operation, Operator


[docs] class AlgorithmicError(ABC): """Abstract base class representing an abstract type of error. This class can be used to create objects that track and propagate errors introduced by approximations and other algorithmic inaccuracies. Args: error (float): The numerical value of the error .. note:: Child classes must implement the :func:`~.AlgorithmicError.combine` method which combines two instances of this error type (as if the associated gates were applied in series). """ def __init__(self, error: float): self.error = error
[docs] @abstractmethod def combine(self, other): """A method to combine two errors of the same type. (e.g., additive, square additive, multiplicative, etc.) Args: other (AlgorithmicError): The other instance of error being combined. Returns: AlgorithmicError: The total error after combination. """
[docs] @staticmethod def get_error(approximate_op, exact_op): """A method to allow users to compute this type of error between two operators. Args: approximate_op (.Operator): The approximate operator. exact_op (.Operator): The exact operator. Returns: float: The error between the exact operator and its approximation. """ raise NotImplementedError
[docs] class ErrorOperation(Operation): r"""Base class that represents quantum operations which carry some form of algorithmic error. .. note:: Child classes must implement the :func:`~.ErrorOperation.error` method which computes the error of the operation. """
[docs] @abstractmethod def error(self, *args, **kwargs) -> AlgorithmicError: """Computes the error of the operation. Returns: AlgorithmicError: The error. """
[docs] class SpectralNormError(AlgorithmicError): """Class representing the spectral norm error. The spectral norm error is defined as the distance, in spectral norm, between the true unitary we intend to apply and the approximate unitary that is actually applied. Args: error (float): The numerical value of the error **Example** >>> s1 = SpectralNormError(0.01) >>> s2 = SpectralNormError(0.02) >>> s1.combine(s2) SpectralNormError(0.03) """ def __repr__(self): """Return formal string representation.""" return f"SpectralNormError({self.error})"
[docs] def combine(self, other: "SpectralNormError"): """Combine two spectral norm errors. Args: other (SpectralNormError): The other instance of error being combined. Returns: SpectralNormError: The total error after combination. **Example** >>> s1 = SpectralNormError(0.01) >>> s2 = SpectralNormError(0.02) >>> s1.combine(s2) SpectralNormError(0.03) """ return self.__class__(self.error + other.error)
[docs] @staticmethod def get_error(approximate_op: Operator, exact_op: Operator): """Compute spectral norm error between two operators. Args: approximate_op (Operator): The approximate operator. exact_op (Operator): The exact operator. Returns: float: The error between the exact operator and its approximation. **Example** >>> Op1 = qml.RY(0.40, 0) >>> Op2 = qml.RY(0.41, 0) >>> SpectralNormError.get_error(Op1, Op2) np.float64(0.004999994791668287) """ wire_order = exact_op.wires m1 = qml.matrix(exact_op, wire_order=wire_order) m2 = qml.matrix(approximate_op, wire_order=wire_order) return qml.math.max(qml.math.svd(m1 - m2, compute_uv=False))
def _compute_algo_error(tape) -> dict[str, AlgorithmicError]: """Given a quantum circuit (tape), this function computes the algorithmic error generated by standard PennyLane operations. Args: tape (.QuantumTape): The quantum circuit for which we compute errors Returns: dict[str->.AlgorithmicError]: dict with error name and combined error as key-value pair """ algo_errors = {} for op in tape.operations: if isinstance(op, ErrorOperation): op_error = op.error() error_name = op_error.__class__.__name__ error = algo_errors.get(error_name, None) error_value = op_error if error is None else error.combine(op_error) algo_errors[error_name] = error_value return algo_errors def _algo_error_qnode( qnode, level, *args, **kwargs ) -> dict[str, "AlgorithmicError"] | list[dict[str, "AlgorithmicError"]]: """Returns the algorithmic error dictionary for the provided QNode. Returns: dict[str, AlgorithmicError] | list[dict[str, AlgorithmicError]]: A single dictionary with error type names as keys and error objects as values when there is only one tape, or a list of such dictionaries when there are multiple tapes in the batch. """ batch, _ = qml.workflow.construct_batch(qnode, level=level)(*args, **kwargs) # Compute errors for each tape separately errors_list = [_compute_algo_error(tape) for tape in batch] # Return a single dict if only one tape, otherwise return the list return errors_list[0] if len(errors_list) == 1 else errors_list
[docs] def algo_error( qnode, level: str | int | slice = "gradient", ) -> Callable[..., dict[str, "AlgorithmicError"] | list[dict[str, "AlgorithmicError"]]]: r"""Computes the algorithmic errors in a quantum circuit. This transform converts a QNode into a callable that returns algorithmic error information after applying the specified amount of transforms/expansions. Args: qnode (.QNode): the QNode to calculate the algorithmic errors for. level (str | int | slice | iter[int]): An indication of which transforms to apply before computing the errors. See :func:`~pennylane.workflow.get_transform_program` for more information about allowable levels. Returns: A function that has the same argument signature as ``qnode``. When called, this function returns either: - A single dictionary with error type names as keys (e.g., ``"SpectralNormError"``) and :class:`~.resource.AlgorithmicError` objects as values, when there is only one tape in the batch. - A list of such dictionaries, one for each tape in the batch, when there are multiple tapes. **Example** Consider a circuit with operations that introduce algorithmic errors, such as :class:`~.TrotterProduct`: .. code-block:: python import pennylane as qml dev = qml.device("null.qubit", wires=2) Hamiltonian = qml.dot([1.0, 0.5], [qml.X(0), qml.Y(0)]) @qml.qnode(dev) def circuit(time): qml.TrotterProduct(Hamiltonian, time=time, n=4, order=2) qml.TrotterProduct(Hamiltonian, time=time, n=4, order=4) return qml.state() We can compute the errors using ``algo_error``: >>> errors = qml.resource.algo_error(circuit)(time=1.0) >>> print(errors) {'SpectralNormError': SpectralNormError(...)} The error values can be accessed from the returned dictionary: >>> errors["SpectralNormError"].error np.float64(0.4299...) .. note:: This function is the standard way to retrieve algorithm-specific error metrics from quantum circuits that use :class:`~.resource.ErrorOperation` subclasses. Operations like :class:`~.TrotterProduct` and :class:`~.QuantumPhaseEstimation` implement the ``error()`` method and will contribute to the returned error dictionary. .. seealso:: :class:`~.resource.AlgorithmicError`, :class:`~.resource.SpectralNormError`, :class:`~.resource.ErrorOperation`, :class:`~.TrotterProduct` """ if isinstance(qnode, qml.QNode): return partial(_algo_error_qnode, qnode, level) try: from pennylane.qnn.torch import TorchLayer # pylint: disable=import-outside-toplevel if isinstance(qnode, TorchLayer) and isinstance(qnode.qnode, qml.QNode): return partial(_algo_error_qnode, qnode, level) except ImportError: # pragma: no cover pass raise ValueError("qml.resource.algo_error can only be applied to a QNode")