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")
_modules/pennylane/resource/error/error
Download Python script
Download Notebook
View on GitHub