Source code for pennylane.noise.conditionals
# 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.
"""Contains utility functions for building boolean conditionals for noise models
Developer note: Conditionals inherit from BooleanFn and store the condition they
utilize in the ``condition`` attribute.
"""
from functools import partial
from inspect import isclass, signature
import pennylane as qml
from pennylane.boolean_fn import BooleanFn
from pennylane.measurements import MeasurementValue, MidMeasureMP
from pennylane.ops import Adjoint, Controlled
from pennylane.templates import ControlledSequence
from pennylane.wires import WireError, Wires
# pylint: disable = unnecessary-lambda, too-few-public-methods
[docs]class WiresIn(BooleanFn):
"""A conditional for evaluating if the wires of an operation exist in a specified set of wires.
Args:
wires (Union[Iterable[int, str], Wires]): Sequence of wires for building the wire set.
.. seealso:: Users are advised to use :func:`~.wires_in` for a functional construction.
"""
def __init__(self, wires):
self._cond = set(wires)
self.condition = self._cond
super().__init__(
lambda wire: _get_wires(wire).issubset(self._cond), f"WiresIn({list(wires)})"
)
[docs]class WiresEq(BooleanFn):
"""A conditional for evaluating if a given wire is equal to a specified set of wires.
Args:
wires (Union[Iterable[int, str], Wires]): Sequence of wires for building the wire set.
.. seealso:: Users are advised to use :func:`~.wires_eq` for a functional construction.
"""
def __init__(self, wires):
self._cond = set(wires)
self.condition = self._cond
super().__init__(
lambda wire: _get_wires(wire) == self._cond,
f"WiresEq({list(wires) if len(wires) > 1 else list(wires)[0]})",
)
def _get_wires(val):
"""Extract wires as a set from an integer, string, Iterable, Wires or Operation instance.
Args:
val (Union[int, str, Iterable, ~.wires.Wires, ~.operation.Operation]): object to be used
for building the wire set.
Returns:
set[Union[int, str]]: computed wire set
Raises:
ValueError: if the wire set cannot be computed for ``val``.
"""
iters = val if isinstance(val, (list, tuple, set, Wires)) else getattr(val, "wires", [val])
try:
wires = set().union(*((getattr(w, "wires", None) or Wires(w)).tolist() for w in iters))
except (TypeError, WireError):
raise ValueError(f"Wires cannot be computed for {val}") from None
return wires
[docs]def wires_in(wires):
"""Builds a conditional as a :class:`~.BooleanFn` for evaluating
if the wires of an input operation are within the specified set of wires.
Args:
wires (Union(Iterable[int, str], Wires, Operation, int, str)): Object to be used
for building the wire set.
Returns:
:class:`WiresIn <pennylane.noise.conditionals.WiresIn>`: A callable object with
signature ``Union(Iterable[int, str], Wires, Operation, int, str)``. It evaluates
to ``True`` if the wire set constructed from the input to the callable is a
subset of the one built from the specified ``wires`` set.
Raises:
ValueError: If the wire set cannot be computed from ``wires``.
**Example**
One may use ``wires_in`` with a given sequence of wires which are used as a wire set:
>>> cond_func = qml.noise.wires_in([0, 1])
>>> cond_func(qml.X(0))
True
>>> cond_func(qml.X(3))
False
Additionally, if an :class:`Operation <pennylane.operation.Operation>` is provided,
its ``wires`` are extracted and used to build the wire set:
>>> cond_func = qml.noise.wires_in(qml.CNOT(["alice", "bob"]))
>>> cond_func("alice")
True
>>> cond_func("eve")
False
"""
return WiresIn(_get_wires(wires))
[docs]def wires_eq(wires):
"""Builds a conditional as a :class:`~.BooleanFn` for evaluating
if a given wire is equal to specified set of wires.
Args:
wires (Union(Iterable[int, str], Wires, Operation, int, str)): Object to be used
for building the wire set.
Returns:
:class:`WiresEq <pennylane.noise.conditionals.WiresEq>`: A callable object with
signature ``Union(Iterable[int, str], Wires, Operation, int, str)``. It evaluates
to ``True`` if the wire set constructed from the input to the callable is equal
to the one built from the specified ``wires`` set.
Raises:
ValueError: If the wire set cannot be computed from ``wires``.
**Example**
One may use ``wires_eq`` with a given sequence of wires which are used as a wire set:
>>> cond_func = qml.noise.wires_eq(0)
>>> cond_func(qml.X(0))
True
>>> cond_func(qml.RY(1.23, wires=[3]))
False
Additionally, if an :class:`Operation <pennylane.operation.Operation>` is provided,
its ``wires`` are extracted and used to build the wire set:
>>> cond_func = qml.noise.wires_eq(qml.RX(1.0, "dino"))
>>> cond_func(qml.RZ(1.23, wires="dino"))
True
>>> cond_func("eve")
False
"""
return WiresEq(_get_wires(wires))
[docs]class OpIn(BooleanFn):
"""A conditional for evaluating if a given operation exist in a specified set of operations.
Args:
ops (Union[str, class, Operation, list[str, class, Operation]]): Sequence of operation
instances, string representations or classes to build the operation set.
.. seealso:: Users are advised to use :func:`~.op_in` for a functional construction.
"""
def __init__(self, ops):
self._cond = ops
self._cops = _get_ops(ops)
self.condition = self._cops
super().__init__(
self._check_in_ops, f"OpIn({[getattr(op, '__name__', op) for op in self._cops]})"
)
def _check_in_ops(self, operation):
xs = operation if isinstance(operation, (list, tuple, set)) else [operation]
cs = _get_ops(xs)
try:
return all(
(
c in self._cops
if isclass(x) or not getattr(x, "arithmetic_depth", 0)
else any(
(
_check_arithmetic_ops(op, x)
if isinstance(op, cp) and getattr(op, "arithmetic_depth", 0)
else cp == _get_ops(x)[0]
)
for op, cp in zip(self._cond, self._cops)
)
)
for x, c in zip(xs, cs)
)
except: # pylint: disable = bare-except # pragma: no cover
raise ValueError(
"OpIn does not support arithmetic operations "
"that cannot be converted to a linear combination"
) from None
[docs]class OpEq(BooleanFn):
"""A conditional for evaluating if a given operation is equal to the specified operation.
Args:
ops (Union[str, class, Operation]): An operation instance, string representation or
class to build the operation set.
.. seealso:: Users are advised to use :func:`~.op_eq` for a functional construction.
"""
def __init__(self, ops):
self._cond = [ops] if not isinstance(ops, (list, tuple, set)) else ops
self._cops = _get_ops(ops)
self.condition = self._cops
cops_names = list(getattr(op, "__name__", op) for op in self._cops)
super().__init__(
self._check_eq_ops,
f"OpEq({cops_names if len(cops_names) > 1 else cops_names[0]})",
)
def _check_eq_ops(self, operation):
if all(isclass(op) or not getattr(op, "arithmetic_depth", 0) for op in self._cond):
return _get_ops(operation) == self._cops
try:
xs = operation if isinstance(operation, (list, tuple, set)) else [operation]
return (
len(xs) == len(self._cond)
and _get_ops(xs) == self._cops
and all(
_check_arithmetic_ops(op, x)
for (op, x) in zip(self._cond, xs)
if not isclass(x) and getattr(x, "arithmetic_depth", 0)
)
)
except: # pylint: disable = bare-except # pragma: no cover
raise ValueError(
"OpEq does not support arithmetic operations "
"that cannot be converted to a linear combination"
) from None
def _get_ops(val):
"""Computes the class for a given argument from its string name, instance,
or a sequence of them.
Args:
val (Union[str, Operation, Iterable]): object to be used
for building the operation set.
Returns:
tuple[class]: tuple of :class:`Operation <pennylane.operation.Operation>`
classes corresponding to val.
"""
vals = val if isinstance(val, (list, tuple, set)) else [val]
op_names = []
for _val in vals:
if isinstance(_val, str):
op_names.append(getattr(qml.ops, _val, None) or getattr(qml, _val))
elif isclass(_val):
op_names.append(_val)
elif isinstance(_val, (MeasurementValue, MidMeasureMP)):
mid_measure = _val if isinstance(_val, MidMeasureMP) else _val.measurements[0]
op_names.append(["MidMeasure", "Reset"][getattr(mid_measure, "reset", 0)])
else:
op_names.append(getattr(_val, "__class__"))
return tuple(op_names)
def _check_arithmetic_ops(op1, op2):
"""Helper method for comparing two arithmetic operators based on type check of the bases"""
# pylint: disable = unnecessary-lambda-assignment
if isinstance(op1, (Adjoint, Controlled, ControlledSequence)) or isinstance(
op2, (Adjoint, Controlled, ControlledSequence)
):
return (
isinstance(op1, type(op2))
and op1.arithmetic_depth == op2.arithmetic_depth
and _get_ops(op1.base) == _get_ops(op2.base)
)
lc_cop = lambda op: qml.ops.LinearCombination(*op.terms())
if isinstance(op1, qml.ops.Exp) or isinstance(op2, qml.ops.Exp):
if (
not isinstance(op1, type(op2))
or (op1.base.arithmetic_depth != op2.base.arithmetic_depth)
or not qml.math.allclose(op1.coeff, op2.coeff)
or (op1.num_steps != op2.num_steps)
):
return False
if op1.base.arithmetic_depth:
return _check_arithmetic_ops(op1.base, op2.base)
return _get_ops(op1.base) == _get_ops(op2.base)
op1, op2 = qml.simplify(op1), qml.simplify(op2)
if op1.arithmetic_depth != op2.arithmetic_depth:
return False
coeffs, op_terms = lc_cop(op1).terms()
sprods = [_get_ops(getattr(op_term, "operands", op_term)) for op_term in op_terms]
def _lc_op(x):
coeffs2, op_terms2 = lc_cop(x).terms()
sprods2 = [_get_ops(getattr(op_term, "operands", op_term)) for op_term in op_terms2]
for coeff, sprod in zip(coeffs2, sprods2):
present, p_index = False, -1
while sprod in sprods[p_index + 1 :]:
p_index = sprods[p_index + 1 :].index(sprod) + (p_index + 1)
if qml.math.allclose(coeff, coeffs[p_index]):
coeffs.pop(p_index)
sprods.pop(p_index)
present = True
break
if not present:
break
return present
return _lc_op(op2)
[docs]def op_in(ops):
"""Builds a conditional as a :class:`~.BooleanFn` for evaluating
if a given operation exist in a specified set of operations.
Args:
ops (str, class, Operation, list(Union[str, class, Operation])): Sequence of string
representations, instances, or classes of the operation(s).
Returns:
:class:`OpIn <pennylane.noise.conditionals.OpIn>`: A callable object that accepts
an :class:`~.Operation` and returns a boolean output. It accepts any input from:
``Union[str, class, Operation, list(Union[str, class, Operation])]`` and evaluates
to ``True`` if the input operation(s) exists in the set of operation(s) specified by
``ops``. Comparison is based on the operation's type, irrespective of wires.
**Example**
One may use ``op_in`` with a string representation of the name of the operation:
>>> cond_func = qml.noise.op_in(["RX", "RY"])
>>> cond_func(qml.RX(1.23, wires=[0]))
True
>>> cond_func(qml.RZ(1.23, wires=[3]))
False
>>> cond_func([qml.RX(1.23, wires=[1]), qml.RY(4.56, wires=[2])])
True
Additionally, an instance of :class:`Operation <pennylane.operation.Operation>`
can also be provided:
>>> cond_func = qml.noise.op_in([qml.RX(1.0, "dino"), qml.RY(2.0, "rhino")])
>>> cond_func(qml.RX(1.23, wires=["eve"]))
True
>>> cond_func(qml.RY(1.23, wires=["dino"]))
True
>>> cond_func([qml.RX(1.23, wires=[1]), qml.RZ(4.56, wires=[2])])
False
"""
ops = [ops] if not isinstance(ops, (list, tuple, set)) else ops
return OpIn(ops)
[docs]def op_eq(ops):
"""Builds a conditional as a :class:`~.BooleanFn` for evaluating
if a given operation is equal to the specified operation.
Args:
ops (str, class, Operation): String representation, an instance or class of the operation.
Returns:
:class:`OpEq <pennylane.noise.conditionals.OpEq>`: A callable object that accepts
an :class:`~.Operation` and returns a boolean output. It accepts any input from:
``Union[str, class, Operation]`` and evaluates to ``True`` if the input operation(s)
is equal to the set of operation(s) specified by ``ops``. Comparison is based on
the operation's type, irrespective of wires.
**Example**
One may use ``op_eq`` with a string representation of the name of the operation:
>>> cond_func = qml.noise.op_eq("RX")
>>> cond_func(qml.RX(1.23, wires=[0]))
True
>>> cond_func(qml.RZ(1.23, wires=[3]))
False
>>> cond_func("CNOT")
False
Additionally, an instance of :class:`Operation <pennylane.operation.Operation>`
can also be provided:
>>> cond_func = qml.noise.op_eq(qml.RX(1.0, "dino"))
>>> cond_func(qml.RX(1.23, wires=["eve"]))
True
>>> cond_func(qml.RY(1.23, wires=["dino"]))
False
"""
return OpEq(ops)
def _rename(newname):
"""Decorator function for renaming ``_partial_op`` function used in partial_wires"""
def decorator(f):
f.__name__ = newname
return f
return decorator
[docs]def partial_wires(operation, *args, **kwargs):
"""Builds a partial function based on the given operation with
all argument frozen except ``wires``.
Args:
operation (Operation, class): Instance of an operation or the class
corresponding to the operation.
*args: Positional arguments provided in the case where the keyword argument
``operation`` is a class for building the partially evaluated instance.
**kwargs: Keyword arguments for the building the partially evaluated instance.
These will override any arguments present in the operation instance or ``args``.
Returns:
callable: A wrapper function that accepts a sequence of wires as an argument or
any object with a ``wires`` property.
Raises:
ValueError: If ``args`` are provided when the given ``operation`` is an instance.
**Example**
One may give an instance of :class:`Operation <pennylane.operation.Operation>`
for the ``operation`` argument:
>>> func = qml.noise.partial_wires(qml.RX(1.2, [12]))
>>> func(2)
qml.RX(1.2, wires=[2])
>>> func(qml.RY(1.0, ["wires"]))
qml.RX(1.2, wires=["wires"])
Additionally, an :class:`Operation <pennylane.operation.Operation>` class can
also be provided, while providing required positional arguments via ``args``:
>>> func = qml.noise.partial_wires(qml.RX, 3.2, [20])
>>> func(qml.RY(1.0, [0]))
qml.RX(3.2, wires=[0])
Finally, one can also use ``kwargs`` instead of positional arguments:
>>> func = qml.noise.partial_wires(qml.RX, phi=1.2)
>>> func(qml.RY(1.0, [2]))
qml.RX(1.2, wires=[2])
>>> rfunc = qml.noise.partial_wires(qml.RX(1.2, [12]), phi=2.3)
>>> rfunc(qml.RY(1.0, ["light"]))
qml.RX(2.3, wires=["light"])
"""
if not callable(operation):
if args:
raise ValueError(
"Args cannot be provided when operation is an instance, "
f"got operation = {operation} and args = {args}."
)
args, metadata = getattr(operation, "_flatten")()
if len(metadata) > 1:
kwargs = {**dict(metadata[1]), **kwargs}
operation = type(operation)
fsignature = signature(getattr(operation, "__init__", operation)).parameters
parameters = list(fsignature)[int("self" in fsignature) :]
arg_params = {**dict(zip(parameters, args)), **kwargs}
if "wires" in arg_params: # Ensure we don't include wires arg
arg_params.pop("wires")
op = partial(operation, **{**arg_params, **kwargs})
op_name = f"{operation.__name__}("
for key, val in op.keywords.items():
op_name += f"{key}={val}, "
op_name = op_name[: -2 if len(op.keywords) else -1] + ")"
@_rename(op_name)
def _partial_op(wires, **model_kwargs): # pylint: disable = unused-argument
"""Wrapper function for partial_wires"""
wires = getattr(wires, "wires", None) or (
[wires] if isinstance(wires, (int, str)) else list(wires)
)
return op(wires=wires)
return _partial_op
_modules/pennylane/noise/conditionals
Download Python script
Download Notebook
View on GitHub