From c470e1061c975885894a564f959467d5cb3deb62 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 22 Oct 2021 08:15:57 -0400 Subject: [PATCH 01/62] Add to init --- pennylane/transforms/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane/transforms/__init__.py b/pennylane/transforms/__init__.py index 01f9a1da276..525e58e5b7a 100644 --- a/pennylane/transforms/__init__.py +++ b/pennylane/transforms/__init__.py @@ -117,6 +117,7 @@ from .hamiltonian_expand import hamiltonian_expand from .measurement_grouping import measurement_grouping from .metric_tensor import metric_tensor +from .noise import add_noise_to_tape from .optimization import ( cancel_inverses, commute_controlled, From 76eb9c8b43addebc0a112185374f620c86c8339a Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 22 Oct 2021 08:17:38 -0400 Subject: [PATCH 02/62] Add to docs --- pennylane/transforms/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane/transforms/__init__.py b/pennylane/transforms/__init__.py index 525e58e5b7a..d5e74ef1e1e 100644 --- a/pennylane/transforms/__init__.py +++ b/pennylane/transforms/__init__.py @@ -83,6 +83,7 @@ .. autosummary:: :toctree: api + ~transforms.add_noise_to_tape ~transforms.measurement_grouping ~transforms.hamiltonian_expand From 68494fdbce224d0d5c16ec21cd22e614e7bfe9da Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 22 Oct 2021 08:42:18 -0400 Subject: [PATCH 03/62] Add base transform --- pennylane/transforms/noise.py | 94 +++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 pennylane/transforms/noise.py diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/noise.py new file mode 100644 index 00000000000..15191186ea6 --- /dev/null +++ b/pennylane/transforms/noise.py @@ -0,0 +1,94 @@ +# Copyright 2021 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. +""" +Provides transforms for adding simple noise models to quantum circuits. +""" +from collections import Sequence +from typing import Union, Type + +from pennylane import apply +from pennylane.operation import Channel +from pennylane.tape import QuantumTape +from pennylane.transforms.qfunc_transforms import single_tape_transform +from pennylane.ops.channel import __qubit_channels__ + + +@single_tape_transform +def add_noise_to_tape(tape: QuantumTape, noisy_op: Type[Channel], noisy_op_args: Union[tuple, float], position: str = "all") -> QuantumTape: + """Add noisy operations to an input tape. + + The tape will be updated to have noisy gates, specified by the ``noisy_op`` argument, added + according to the positioning specified in the ``position`` argument. + + Args: + tape (QuantumTape): the input tape + noisy_op (Type[Channel]): the noisy operation to be added at positions within the tape + noisy_op_args (tuple or float): the arguments fed to the noisy operation, or a single float + specifying the noise strength + position (str): Specification of where to add noise. Should be one of: ``"all"`` to add + the noisy operation after all gates; ``"start"`` to add the noisy operation to all wires + at the start of the circuit; ``"end"`` to add the noisy operation to all wires at the + end of the circuit. + + Returns: + QuantumTape: a noisy version of the input tape + + **Example:** + + Consider the following tape: + + .. code-block:: python3 + with qml.tape.QuantumTape() as tape: + qml.RX(0.9, wires=0) + qml.RY(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(0.5, wires=0) + qml.RX(0.6, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + We can add the :class:`~.AmplitudeDamping` channel to the start of the circuit using: + + >>> from pennylane.transforms import add_noise_to_tape + >>> noisy_tape = add_noise_to_tape(tape, qml.AmplitudeDamping, 0.05, position="start") + >>> print(noisy_tape.draw()) + 0: ──AmplitudeDamping(0.05)──RX(0.9)──╭C──RY(0.5)──╭┤ ⟨Z ⊗ Z⟩ + 1: ──AmplitudeDamping(0.05)──RY(0.4)──╰X──RX(0.6)──╰┤ ⟨Z ⊗ Z⟩ + """ + if noisy_op.num_wires != 1: + raise ValueError("Adding noise to the tape is only supported for single-qubit noisy operations") + if position not in ("start", "end", "all"): + raise ValueError("Position must be either 'start', 'end', or 'all' (default)") + if noisy_op.__name__ not in __qubit_channels__: + raise ValueError("The noisy_op argument must be a noisy operation such as qml.AmplitudeDamping") + + if not isinstance(noisy_op_args, Sequence): + noisy_op_args = [noisy_op_args] + + if position == "start": + for w in tape.wires: + noisy_op(*noisy_op_args, wires=w) + + for op in tape.operations: + apply(op) + + if position == "all": + for w in op.wires: + noisy_op(*noisy_op_args, wires=w) + + if position == "end": + for w in tape.wires: + noisy_op(*noisy_op_args, wires=w) + + for m in tape.measurements: + apply(m) From 801496cdd7462a981cbb7e762a2f5e0f486182af Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 22 Oct 2021 08:55:51 -0400 Subject: [PATCH 04/62] Add tests --- tests/transforms/test_noise.py | 124 +++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 tests/transforms/test_noise.py diff --git a/tests/transforms/test_noise.py b/tests/transforms/test_noise.py new file mode 100644 index 00000000000..05089a52bda --- /dev/null +++ b/tests/transforms/test_noise.py @@ -0,0 +1,124 @@ +# Copyright 2021 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. +""" +Tests for the noise-adding transforms. +""" +import numpy as np +import pytest +import pennylane as qml +from pennylane.tape import QuantumTape +from pennylane.operation import Expectation + +from pennylane.transforms.noise import add_noise_to_tape + + +class TestAddNoiseToTape: + """Tests for the add_noise_to_tape function""" + + with QuantumTape() as tape: + qml.RX(0.9, wires=0) + qml.RY(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(0.5, wires=0) + qml.RX(0.6, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + def test_multiwire_noisy_op(self): + """Tests if a ValueError is raised when multiqubit channels are requested""" + with pytest.raises(ValueError, match="Adding noise to the tape is only"): + add_noise_to_tape(self.tape, qml.QubitChannel, []) + + def test_invalid_position(self): + """Test if a ValueError is raised when an invalid position is requested""" + with pytest.raises(ValueError, match="Position must be either 'start', 'end', or 'all'"): + add_noise_to_tape(self.tape, qml.AmplitudeDamping, 0.4, position="ABC") + + def test_not_noisy(self): + """Test if a ValueError is raised when something that is not a noisy channel is fed to the + noisy_op argument""" + with pytest.raises(ValueError, match="The noisy_op argument must be a noisy operation"): + add_noise_to_tape(self.tape, qml.PauliX, 0.4) + + def test_start(self): + """Test if the expected tape is returned when the start position is requested""" + tape = add_noise_to_tape(self.tape, qml.AmplitudeDamping, 0.4, position="start") + + with QuantumTape() as tape_exp: + qml.AmplitudeDamping(0.4, wires=0) + qml.AmplitudeDamping(0.4, wires=1) + qml.RX(0.9, wires=0) + qml.RY(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(0.5, wires=0) + qml.RX(0.6, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(np.allclose(o1.parameters, o2.parameters) for o1, o2 in + zip(tape.operations, tape_exp.operations)) + assert len(tape.measurements) == 1 + assert tape.observables[0].name == ["PauliZ", "PauliZ"] + assert tape.observables[0].wires.tolist() == [0, 1] + assert tape.measurements[0].return_type is Expectation + + def test_all(self): + """Test if the expected tape is returned when the all position is requested""" + tape = add_noise_to_tape(self.tape, qml.PhaseDamping, 0.4, position="all") + + with QuantumTape() as tape_exp: + qml.RX(0.9, wires=0) + qml.PhaseDamping(0.4, wires=0) + qml.RY(0.4, wires=1) + qml.PhaseDamping(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.PhaseDamping(0.4, wires=0) + qml.PhaseDamping(0.4, wires=1) + qml.RY(0.5, wires=0) + qml.PhaseDamping(0.4, wires=0) + qml.RX(0.6, wires=1) + qml.PhaseDamping(0.4, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(np.allclose(o1.parameters, o2.parameters) for o1, o2 in + zip(tape.operations, tape_exp.operations)) + assert len(tape.measurements) == 1 + assert tape.observables[0].name == ["PauliZ", "PauliZ"] + assert tape.observables[0].wires.tolist() == [0, 1] + assert tape.measurements[0].return_type is Expectation + + def test_end(self): + """Test if the expected tape is returned when the end position is requested""" + tape = add_noise_to_tape(self.tape, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end") + + with QuantumTape() as tape_exp: + qml.RX(0.9, wires=0) + qml.RY(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(0.5, wires=0) + qml.RX(0.6, wires=1) + qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=0) + qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(np.allclose(o1.parameters, o2.parameters) for o1, o2 in + zip(tape.operations, tape_exp.operations)) + assert len(tape.measurements) == 1 + assert tape.observables[0].name == ["PauliZ", "PauliZ"] + assert tape.observables[0].wires.tolist() == [0, 1] + assert tape.measurements[0].return_type is Expectation From 36212a4433aeba084832946d965c42e9ce164527 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 22 Oct 2021 09:51:50 -0400 Subject: [PATCH 05/62] Add qfunc transform --- pennylane/transforms/noise.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/noise.py index 15191186ea6..5ab260e066a 100644 --- a/pennylane/transforms/noise.py +++ b/pennylane/transforms/noise.py @@ -20,7 +20,7 @@ from pennylane import apply from pennylane.operation import Channel from pennylane.tape import QuantumTape -from pennylane.transforms.qfunc_transforms import single_tape_transform +from pennylane.transforms.qfunc_transforms import single_tape_transform, qfunc_transform from pennylane.ops.channel import __qubit_channels__ @@ -92,3 +92,6 @@ def add_noise_to_tape(tape: QuantumTape, noisy_op: Type[Channel], noisy_op_args: for m in tape.measurements: apply(m) + + +add_noise_to_qfunc = qfunc_transform(add_noise_to_tape) From 3e5057aec32633e0789a9316d8e4446303e99d20 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 22 Oct 2021 10:18:56 -0400 Subject: [PATCH 06/62] Add qfunc transform --- pennylane/transforms/__init__.py | 3 +- pennylane/transforms/noise.py | 59 +++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/pennylane/transforms/__init__.py b/pennylane/transforms/__init__.py index d5e74ef1e1e..62ef663280f 100644 --- a/pennylane/transforms/__init__.py +++ b/pennylane/transforms/__init__.py @@ -49,6 +49,7 @@ ~ctrl ~apply_controlled_Q ~quantum_monte_carlo + ~transforms.add_noise_to_qfunc Transforms for circuit compilation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -118,7 +119,7 @@ from .hamiltonian_expand import hamiltonian_expand from .measurement_grouping import measurement_grouping from .metric_tensor import metric_tensor -from .noise import add_noise_to_tape +from .noise import add_noise_to_tape, add_noise_to_qfunc from .optimization import ( cancel_inverses, commute_controlled, diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/noise.py index 5ab260e066a..ff57ce5746e 100644 --- a/pennylane/transforms/noise.py +++ b/pennylane/transforms/noise.py @@ -26,7 +26,7 @@ @single_tape_transform def add_noise_to_tape(tape: QuantumTape, noisy_op: Type[Channel], noisy_op_args: Union[tuple, float], position: str = "all") -> QuantumTape: - """Add noisy operations to an input tape. + r"""Add noisy operations to an input tape. The tape will be updated to have noisy gates, specified by the ``noisy_op`` argument, added according to the positioning specified in the ``position`` argument. @@ -44,11 +44,18 @@ def add_noise_to_tape(tape: QuantumTape, noisy_op: Type[Channel], noisy_op_args: Returns: QuantumTape: a noisy version of the input tape + Raises: + ValueError: if the noisy operation passed in ``noisy_op`` applies to more than one wire + ValueError: if the requested ``position`` argument is now ``'start'``, ``'end'`` or + ``'all'`` + ValueError: if the noisy operation passed in ``noisy_op`` is not a noisy channel + **Example:** Consider the following tape: .. code-block:: python3 + with qml.tape.QuantumTape() as tape: qml.RX(0.9, wires=0) qml.RY(0.4, wires=1) @@ -95,3 +102,53 @@ def add_noise_to_tape(tape: QuantumTape, noisy_op: Type[Channel], noisy_op_args: add_noise_to_qfunc = qfunc_transform(add_noise_to_tape) +add_noise_to_qfunc.__doc__ = """Add noisy operations to an input quantum function. + + The function will be updated to have noisy gates, specified by the ``noisy_op`` argument, added + according to the positioning specified in the ``position`` argument. + + Args: + fn (Callable): the quantum function + noisy_op (Type[Channel]): the noisy operation to be added at positions within the tape + noisy_op_args (tuple or float): the arguments fed to the noisy operation, or a single float + specifying the noise strength + position (str): Specification of where to add noise. Should be one of: ``"all"`` to add + the noisy operation after all gates; ``"start"`` to add the noisy operation to all wires + at the start of the circuit; ``"end"`` to add the noisy operation to all wires at the + end of the circuit. + + Returns: + Callable: a noisy version of the input function + + Raises: + ValueError: if the noisy operation passed in ``noisy_op`` applies to more than one wire + ValueError: if the requested ``position`` argument is now ``'start'``, ``'end'`` or + ``'all'`` + ValueError: if the noisy operation passed in ``noisy_op`` is not a noisy channel + + **Example:** + + The following QNode can be transformed to add noise to the circuit: + + .. code-block:: python3 + + from pennylane.transforms import add_noise_to_qfunc + + dev = qml.device("default.mixed", wires=2) + + @qml.qnode(dev) + @add_noise_to_qfunc(qml.AmplitudeDamping, 0.2, position="end") + def f(w, x, y, z): + qml.RX(w, wires=0) + qml.RY(x, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(y, wires=0) + qml.RX(z, wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + Executions of this circuit will differ from the noise-free value: + + >>> f(0.9, 0.4, 0.5, 0.6) + tensor(0.754847, requires_grad=True) +""" + From 8575a59771f3270e1dc3c048c96b47f9fecaacc9 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 22 Oct 2021 13:16:28 -0400 Subject: [PATCH 07/62] Add test --- tests/transforms/test_noise.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/transforms/test_noise.py b/tests/transforms/test_noise.py index 05089a52bda..c14ea8ff50f 100644 --- a/tests/transforms/test_noise.py +++ b/tests/transforms/test_noise.py @@ -20,7 +20,7 @@ from pennylane.tape import QuantumTape from pennylane.operation import Expectation -from pennylane.transforms.noise import add_noise_to_tape +from pennylane.transforms.noise import add_noise_to_tape, add_noise_to_qfunc class TestAddNoiseToTape: @@ -122,3 +122,32 @@ def test_end(self): assert tape.observables[0].name == ["PauliZ", "PauliZ"] assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation + + +def test_add_noise_to_qfunc(): + """Test that a QNode with the add_noise_to_qfunc decorator gives a different result than one + without.""" + dev = qml.device("default.mixed", wires=2) + + @qml.qnode(dev) + @add_noise_to_qfunc(qml.AmplitudeDamping, 0.2, position="end") + def f_noisy(w, x, y, z): + qml.RX(w, wires=0) + qml.RY(x, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(y, wires=0) + qml.RX(z, wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + @qml.qnode(dev) + def f(w, x, y, z): + qml.RX(w, wires=0) + qml.RY(x, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(y, wires=0) + qml.RX(z, wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + args = [0.1, 0.2, 0.3, 0.4] + + assert not np.isclose(f_noisy(*args), f(*args)) From c0ad944290a97153a7748bd272810b0c455cf69a Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 22 Oct 2021 14:56:03 -0400 Subject: [PATCH 08/62] Add state prep support --- pennylane/transforms/noise.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/noise.py index ff57ce5746e..c5b7b235edd 100644 --- a/pennylane/transforms/noise.py +++ b/pennylane/transforms/noise.py @@ -22,6 +22,7 @@ from pennylane.tape import QuantumTape from pennylane.transforms.qfunc_transforms import single_tape_transform, qfunc_transform from pennylane.ops.channel import __qubit_channels__ +from pennylane import QubitStateVector, BasisState @single_tape_transform @@ -49,6 +50,8 @@ def add_noise_to_tape(tape: QuantumTape, noisy_op: Type[Channel], noisy_op_args: ValueError: if the requested ``position`` argument is now ``'start'``, ``'end'`` or ``'all'`` ValueError: if the noisy operation passed in ``noisy_op`` is not a noisy channel + ValueError: if more than one state preparation is present in the tape, or if the preparation + is not at the start of the tape **Example:** @@ -82,13 +85,23 @@ def add_noise_to_tape(tape: QuantumTape, noisy_op: Type[Channel], noisy_op_args: if not isinstance(noisy_op_args, Sequence): noisy_op_args = [noisy_op_args] + preps = tuple(isinstance(o, (QubitStateVector, BasisState)) for o in tape.operations) + valid_preps = sum(preps) == 1 and preps[0] is True or sum(preps) == 0 + if not valid_preps: + raise ValueError("Only a single state preparation at the start of the circuit is supported") + + if sum(preps) == 1: + apply(tape.operations[0]) + start_pos = 1 + else: + start_pos = 0 + if position == "start": for w in tape.wires: noisy_op(*noisy_op_args, wires=w) - for op in tape.operations: + for i, op in enumerate(tape.operations[start_pos:]): apply(op) - if position == "all": for w in op.wires: noisy_op(*noisy_op_args, wires=w) From 887f878d878bc21120443d683c94a725a2b26aec Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 22 Oct 2021 15:04:57 -0400 Subject: [PATCH 09/62] Add tests --- pennylane/transforms/noise.py | 1 - tests/transforms/test_noise.py | 97 ++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/noise.py index c5b7b235edd..b6d6a112f27 100644 --- a/pennylane/transforms/noise.py +++ b/pennylane/transforms/noise.py @@ -164,4 +164,3 @@ def f(w, x, y, z): >>> f(0.9, 0.4, 0.5, 0.6) tensor(0.754847, requires_grad=True) """ - diff --git a/tests/transforms/test_noise.py b/tests/transforms/test_noise.py index c14ea8ff50f..1850f7ed352 100644 --- a/tests/transforms/test_noise.py +++ b/tests/transforms/test_noise.py @@ -34,6 +34,15 @@ class TestAddNoiseToTape: qml.RX(0.6, wires=1) qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + with QuantumTape() as tape_with_prep: + qml.QubitStateVector([1, 0], wires=0) + qml.RX(0.9, wires=0) + qml.RY(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(0.5, wires=0) + qml.RX(0.6, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + def test_multiwire_noisy_op(self): """Tests if a ValueError is raised when multiqubit channels are requested""" with pytest.raises(ValueError, match="Adding noise to the tape is only"): @@ -123,6 +132,94 @@ def test_end(self): assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation + def test_start_with_state_prep(self): + """Test if the expected tape is returned when the start position is requested in a tape + that has state preparation""" + tape = add_noise_to_tape(self.tape_with_prep, qml.AmplitudeDamping, 0.4, position="start") + + with QuantumTape() as tape_exp: + qml.QubitStateVector([1, 0], wires=0) + qml.AmplitudeDamping(0.4, wires=0) + qml.AmplitudeDamping(0.4, wires=1) + qml.RX(0.9, wires=0) + qml.RY(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(0.5, wires=0) + qml.RX(0.6, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(np.allclose(o1.parameters, o2.parameters) for o1, o2 in + zip(tape.operations, tape_exp.operations)) + assert len(tape.measurements) == 1 + assert tape.observables[0].name == ["PauliZ", "PauliZ"] + assert tape.observables[0].wires.tolist() == [0, 1] + assert tape.measurements[0].return_type is Expectation + + def test_all_with_state_prep(self): + """Test if the expected tape is returned when the all position is requested in a tape + that has state preparation""" + tape = add_noise_to_tape(self.tape_with_prep, qml.PhaseDamping, 0.4, position="all") + + with QuantumTape() as tape_exp: + qml.QubitStateVector([1, 0], wires=0) + qml.RX(0.9, wires=0) + qml.PhaseDamping(0.4, wires=0) + qml.RY(0.4, wires=1) + qml.PhaseDamping(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.PhaseDamping(0.4, wires=0) + qml.PhaseDamping(0.4, wires=1) + qml.RY(0.5, wires=0) + qml.PhaseDamping(0.4, wires=0) + qml.RX(0.6, wires=1) + qml.PhaseDamping(0.4, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(np.allclose(o1.parameters, o2.parameters) for o1, o2 in + zip(tape.operations, tape_exp.operations)) + assert len(tape.measurements) == 1 + assert tape.observables[0].name == ["PauliZ", "PauliZ"] + assert tape.observables[0].wires.tolist() == [0, 1] + assert tape.measurements[0].return_type is Expectation + + def test_end_with_state_prep(self): + """Test if the expected tape is returned when the end position is requested in a tape + that has state preparation""" + tape = add_noise_to_tape(self.tape_with_prep, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end") + + with QuantumTape() as tape_exp: + qml.QubitStateVector([1, 0], wires=0) + qml.RX(0.9, wires=0) + qml.RY(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(0.5, wires=0) + qml.RX(0.6, wires=1) + qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=0) + qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(np.allclose(o1.parameters, o2.parameters) for o1, o2 in + zip(tape.operations, tape_exp.operations)) + assert len(tape.measurements) == 1 + assert tape.observables[0].name == ["PauliZ", "PauliZ"] + assert tape.observables[0].wires.tolist() == [0, 1] + assert tape.measurements[0].return_type is Expectation + + def test_multiple_preparations(self): + """Tests if a ValueError is raised when multiple state preparations are present in the + tape""" + with QuantumTape() as tape: + qml.QubitStateVector([1, 0], wires=0) + qml.QubitStateVector([0, 1], wires=1) + with pytest.raises(ValueError, match="Only a single state preparation at the start of the"): + add_noise_to_tape(tape, qml.AmplitudeDamping, 0.4) + def test_add_noise_to_qfunc(): """Test that a QNode with the add_noise_to_qfunc decorator gives a different result than one From e94095ad5c4853d56101e12fd378c82c9a20dedd Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 22 Oct 2021 15:13:11 -0400 Subject: [PATCH 10/62] Add to changelog --- doc/releases/changelog-dev.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index e07b357563a..cc0b698c31e 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -387,6 +387,8 @@

Improvements

+* The `add_noise_to_tape` and `add_noise_to_qfunc` transforms have now been added, providing a simple way to add noise to a quantum circuit. + * Operators now have a `label` method to determine how they are drawn. This will eventually override the `RepresentationResolver` class. [(#1678)](https://github.com/PennyLaneAI/pennylane/pull/1678) @@ -675,6 +677,6 @@ This release contains contributions from (in alphabetical order): -Utkarsh Azad, Akash Narayanan B, Olivia Di Matteo, Andrew Gardhouse, David Ittah, Josh Izaac, Christina Lee, +Utkarsh Azad, Akash Narayanan B, Thomas Bromley, Olivia Di Matteo, Andrew Gardhouse, David Ittah, Josh Izaac, Christina Lee, Romain Moyard, Carrie-Anne Rubidge, Maria Schuld, Rishabh Singh, Ingrid Strandberg, Antal Száva, Cody Wang, David Wierichs, Moritz Willmann. From f738e398488a38b03454a788ca83e3e5bfc7c212 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 22 Oct 2021 15:26:58 -0400 Subject: [PATCH 11/62] Update changelog --- doc/releases/changelog-dev.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 2834984bcf4..4c696ae0bea 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -387,7 +387,36 @@

Improvements

-* The `add_noise_to_tape` and `add_noise_to_qfunc` transforms have now been added, providing a simple way to add noise to a quantum circuit. +* The `add_noise_to_tape` and `add_noise_to_qfunc` transforms have now been added, providing a + simple way to add noise to a quantum circuit. + [(#1795)](https://github.com/PennyLaneAI/pennylane/pull/1795) + + The following QNode can be transformed to add noise to the circuit: + + ```python + from pennylane.transforms import add_noise_to_qfunc + + dev = qml.device("default.mixed", wires=2) + + @qml.qnode(dev) + @add_noise_to_qfunc(qml.AmplitudeDamping, 0.2, position="end") + def f(w, x, y, z): + qml.RX(w, wires=0) + qml.RY(x, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(y, wires=0) + qml.RX(z, wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + ``` + + Executions of this circuit will differ from the noise-free value: + + >>> f(0.9, 0.4, 0.5, 0.6) + tensor(0.754847, requires_grad=True) + + >>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6)) + 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.2)──╭┤ ⟨Z ⊗ Z⟩ + 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.2)──╰┤ ⟨Z ⊗ Z⟩ * Templates are now top level imported and can be used directly e.g. `qml.QFT(wires=0)`. [(#1779)](https://github.com/PennyLaneAI/pennylane/pull/1779) From 8e2ef87f2dcbe2712c073a03c1029ccd2f0ee7ff Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 22 Oct 2021 15:28:25 -0400 Subject: [PATCH 12/62] Fix changelog --- doc/releases/changelog-dev.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 4c696ae0bea..c1ab5f63ac8 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -410,13 +410,14 @@ ``` Executions of this circuit will differ from the noise-free value: - + + ```pycon >>> f(0.9, 0.4, 0.5, 0.6) tensor(0.754847, requires_grad=True) - >>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6)) 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.2)──╭┤ ⟨Z ⊗ Z⟩ - 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.2)──╰┤ ⟨Z ⊗ Z⟩ + 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.2)──╰┤ ⟨Z ⊗ Z⟩ + ``` * Templates are now top level imported and can be used directly e.g. `qml.QFT(wires=0)`. [(#1779)](https://github.com/PennyLaneAI/pennylane/pull/1779) From 73dbc9f43d219d74ce9cc51c014c9a80065eeb7d Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 22 Oct 2021 15:32:44 -0400 Subject: [PATCH 13/62] Fix codefactor --- pennylane/transforms/noise.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/noise.py index b6d6a112f27..7bdf26c1da5 100644 --- a/pennylane/transforms/noise.py +++ b/pennylane/transforms/noise.py @@ -14,7 +14,7 @@ """ Provides transforms for adding simple noise models to quantum circuits. """ -from collections import Sequence +from collections.abc import Sequence from typing import Union, Type from pennylane import apply @@ -100,7 +100,7 @@ def add_noise_to_tape(tape: QuantumTape, noisy_op: Type[Channel], noisy_op_args: for w in tape.wires: noisy_op(*noisy_op_args, wires=w) - for i, op in enumerate(tape.operations[start_pos:]): + for op in tape.operations[start_pos:]: apply(op) if position == "all": for w in op.wires: From 89feb7ae0ef42e228cee6a3a4d2d878a8a9086c3 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 22 Oct 2021 15:34:52 -0400 Subject: [PATCH 14/62] Fix CI --- pennylane/transforms/noise.py | 24 ++++++++++------ tests/transforms/test_noise.py | 50 ++++++++++++++++++++++------------ 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/noise.py index 7bdf26c1da5..172dde0ee19 100644 --- a/pennylane/transforms/noise.py +++ b/pennylane/transforms/noise.py @@ -15,18 +15,22 @@ Provides transforms for adding simple noise models to quantum circuits. """ from collections.abc import Sequence -from typing import Union, Type +from typing import Type, Union -from pennylane import apply +from pennylane import BasisState, QubitStateVector, apply from pennylane.operation import Channel -from pennylane.tape import QuantumTape -from pennylane.transforms.qfunc_transforms import single_tape_transform, qfunc_transform from pennylane.ops.channel import __qubit_channels__ -from pennylane import QubitStateVector, BasisState +from pennylane.tape import QuantumTape +from pennylane.transforms.qfunc_transforms import qfunc_transform, single_tape_transform @single_tape_transform -def add_noise_to_tape(tape: QuantumTape, noisy_op: Type[Channel], noisy_op_args: Union[tuple, float], position: str = "all") -> QuantumTape: +def add_noise_to_tape( + tape: QuantumTape, + noisy_op: Type[Channel], + noisy_op_args: Union[tuple, float], + position: str = "all", +) -> QuantumTape: r"""Add noisy operations to an input tape. The tape will be updated to have noisy gates, specified by the ``noisy_op`` argument, added @@ -76,11 +80,15 @@ def add_noise_to_tape(tape: QuantumTape, noisy_op: Type[Channel], noisy_op_args: 1: ──AmplitudeDamping(0.05)──RY(0.4)──╰X──RX(0.6)──╰┤ ⟨Z ⊗ Z⟩ """ if noisy_op.num_wires != 1: - raise ValueError("Adding noise to the tape is only supported for single-qubit noisy operations") + raise ValueError( + "Adding noise to the tape is only supported for single-qubit noisy operations" + ) if position not in ("start", "end", "all"): raise ValueError("Position must be either 'start', 'end', or 'all' (default)") if noisy_op.__name__ not in __qubit_channels__: - raise ValueError("The noisy_op argument must be a noisy operation such as qml.AmplitudeDamping") + raise ValueError( + "The noisy_op argument must be a noisy operation such as qml.AmplitudeDamping" + ) if not isinstance(noisy_op_args, Sequence): noisy_op_args = [noisy_op_args] diff --git a/tests/transforms/test_noise.py b/tests/transforms/test_noise.py index 1850f7ed352..b1b209d4f4a 100644 --- a/tests/transforms/test_noise.py +++ b/tests/transforms/test_noise.py @@ -16,11 +16,11 @@ """ import numpy as np import pytest + import pennylane as qml -from pennylane.tape import QuantumTape from pennylane.operation import Expectation - -from pennylane.transforms.noise import add_noise_to_tape, add_noise_to_qfunc +from pennylane.tape import QuantumTape +from pennylane.transforms.noise import add_noise_to_qfunc, add_noise_to_tape class TestAddNoiseToTape: @@ -75,8 +75,10 @@ def test_start(self): assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all(np.allclose(o1.parameters, o2.parameters) for o1, o2 in - zip(tape.operations, tape_exp.operations)) + assert all( + np.allclose(o1.parameters, o2.parameters) + for o1, o2 in zip(tape.operations, tape_exp.operations) + ) assert len(tape.measurements) == 1 assert tape.observables[0].name == ["PauliZ", "PauliZ"] assert tape.observables[0].wires.tolist() == [0, 1] @@ -102,8 +104,10 @@ def test_all(self): assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all(np.allclose(o1.parameters, o2.parameters) for o1, o2 in - zip(tape.operations, tape_exp.operations)) + assert all( + np.allclose(o1.parameters, o2.parameters) + for o1, o2 in zip(tape.operations, tape_exp.operations) + ) assert len(tape.measurements) == 1 assert tape.observables[0].name == ["PauliZ", "PauliZ"] assert tape.observables[0].wires.tolist() == [0, 1] @@ -111,7 +115,9 @@ def test_all(self): def test_end(self): """Test if the expected tape is returned when the end position is requested""" - tape = add_noise_to_tape(self.tape, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end") + tape = add_noise_to_tape( + self.tape, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end" + ) with QuantumTape() as tape_exp: qml.RX(0.9, wires=0) @@ -125,8 +131,10 @@ def test_end(self): assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all(np.allclose(o1.parameters, o2.parameters) for o1, o2 in - zip(tape.operations, tape_exp.operations)) + assert all( + np.allclose(o1.parameters, o2.parameters) + for o1, o2 in zip(tape.operations, tape_exp.operations) + ) assert len(tape.measurements) == 1 assert tape.observables[0].name == ["PauliZ", "PauliZ"] assert tape.observables[0].wires.tolist() == [0, 1] @@ -150,8 +158,10 @@ def test_start_with_state_prep(self): assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all(np.allclose(o1.parameters, o2.parameters) for o1, o2 in - zip(tape.operations, tape_exp.operations)) + assert all( + np.allclose(o1.parameters, o2.parameters) + for o1, o2 in zip(tape.operations, tape_exp.operations) + ) assert len(tape.measurements) == 1 assert tape.observables[0].name == ["PauliZ", "PauliZ"] assert tape.observables[0].wires.tolist() == [0, 1] @@ -179,8 +189,10 @@ def test_all_with_state_prep(self): assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all(np.allclose(o1.parameters, o2.parameters) for o1, o2 in - zip(tape.operations, tape_exp.operations)) + assert all( + np.allclose(o1.parameters, o2.parameters) + for o1, o2 in zip(tape.operations, tape_exp.operations) + ) assert len(tape.measurements) == 1 assert tape.observables[0].name == ["PauliZ", "PauliZ"] assert tape.observables[0].wires.tolist() == [0, 1] @@ -189,7 +201,9 @@ def test_all_with_state_prep(self): def test_end_with_state_prep(self): """Test if the expected tape is returned when the end position is requested in a tape that has state preparation""" - tape = add_noise_to_tape(self.tape_with_prep, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end") + tape = add_noise_to_tape( + self.tape_with_prep, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end" + ) with QuantumTape() as tape_exp: qml.QubitStateVector([1, 0], wires=0) @@ -204,8 +218,10 @@ def test_end_with_state_prep(self): assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all(np.allclose(o1.parameters, o2.parameters) for o1, o2 in - zip(tape.operations, tape_exp.operations)) + assert all( + np.allclose(o1.parameters, o2.parameters) + for o1, o2 in zip(tape.operations, tape_exp.operations) + ) assert len(tape.measurements) == 1 assert tape.observables[0].name == ["PauliZ", "PauliZ"] assert tape.observables[0].wires.tolist() == [0, 1] From 9433575dfb1cf47f9af2e9f54b3d8599ef52170e Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 22 Oct 2021 16:04:33 -0400 Subject: [PATCH 15/62] Update docstrings --- pennylane/transforms/noise.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/noise.py index 172dde0ee19..f5f87e0ec4a 100644 --- a/pennylane/transforms/noise.py +++ b/pennylane/transforms/noise.py @@ -31,7 +31,8 @@ def add_noise_to_tape( noisy_op_args: Union[tuple, float], position: str = "all", ) -> QuantumTape: - r"""Add noisy operations to an input tape. + r"""test() + Add noisy operations to an input tape. The tape will be updated to have noisy gates, specified by the ``noisy_op`` argument, added according to the positioning specified in the ``position`` argument. @@ -74,10 +75,10 @@ def add_noise_to_tape( We can add the :class:`~.AmplitudeDamping` channel to the start of the circuit using: >>> from pennylane.transforms import add_noise_to_tape - >>> noisy_tape = add_noise_to_tape(tape, qml.AmplitudeDamping, 0.05, position="start") + >>> noisy_tape = add_noise_to_tape(tape, qml.AmplitudeDamping, 0.05, position="end") >>> print(noisy_tape.draw()) - 0: ──AmplitudeDamping(0.05)──RX(0.9)──╭C──RY(0.5)──╭┤ ⟨Z ⊗ Z⟩ - 1: ──AmplitudeDamping(0.05)──RY(0.4)──╰X──RX(0.6)──╰┤ ⟨Z ⊗ Z⟩ + 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.05)──╭┤ ⟨Z ⊗ Z⟩ + 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.05)──╰┤ ⟨Z ⊗ Z⟩ """ if noisy_op.num_wires != 1: raise ValueError( @@ -130,7 +131,7 @@ def add_noise_to_tape( Args: fn (Callable): the quantum function - noisy_op (Type[Channel]): the noisy operation to be added at positions within the tape + noisy_op (Type[Channel]): the noisy operation to be added at positions within the function noisy_op_args (tuple or float): the arguments fed to the noisy operation, or a single float specifying the noise strength position (str): Specification of where to add noise. Should be one of: ``"all"`` to add @@ -146,6 +147,8 @@ def add_noise_to_tape( ValueError: if the requested ``position`` argument is now ``'start'``, ``'end'`` or ``'all'`` ValueError: if the noisy operation passed in ``noisy_op`` is not a noisy channel + ValueError: if more than one state preparation is present in the function, or if the + preparation is not at the start of the function **Example:** @@ -171,4 +174,7 @@ def f(w, x, y, z): >>> f(0.9, 0.4, 0.5, 0.6) tensor(0.754847, requires_grad=True) + >>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6)) + 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.2)──╭┤ ⟨Z ⊗ Z⟩ + 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.2)──╰┤ ⟨Z ⊗ Z⟩ """ From 1ade5b34f5ae1fb085fe82aec83a41aafe8becdb Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 22 Oct 2021 16:05:42 -0400 Subject: [PATCH 16/62] Fix --- pennylane/transforms/noise.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/noise.py index f5f87e0ec4a..492e7bba501 100644 --- a/pennylane/transforms/noise.py +++ b/pennylane/transforms/noise.py @@ -31,8 +31,7 @@ def add_noise_to_tape( noisy_op_args: Union[tuple, float], position: str = "all", ) -> QuantumTape: - r"""test() - Add noisy operations to an input tape. + r"""Add noisy operations to an input tape. The tape will be updated to have noisy gates, specified by the ``noisy_op`` argument, added according to the positioning specified in the ``position`` argument. From a4d64f3bb5d22fa363e0a605952bfc379778db46 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 25 Oct 2021 08:32:19 -0400 Subject: [PATCH 17/62] Rename --- doc/releases/changelog-dev.md | 4 ++-- pennylane/transforms/__init__.py | 4 ++-- pennylane/transforms/noise.py | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index c1ab5f63ac8..e9c7e5037cf 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -394,12 +394,12 @@ The following QNode can be transformed to add noise to the circuit: ```python - from pennylane.transforms import add_noise_to_qfunc + from pennylane.transforms import add_noise dev = qml.device("default.mixed", wires=2) @qml.qnode(dev) - @add_noise_to_qfunc(qml.AmplitudeDamping, 0.2, position="end") + @add_noise(qml.AmplitudeDamping, 0.2, position="end") def f(w, x, y, z): qml.RX(w, wires=0) qml.RY(x, wires=1) diff --git a/pennylane/transforms/__init__.py b/pennylane/transforms/__init__.py index 62ef663280f..f267213209f 100644 --- a/pennylane/transforms/__init__.py +++ b/pennylane/transforms/__init__.py @@ -49,7 +49,7 @@ ~ctrl ~apply_controlled_Q ~quantum_monte_carlo - ~transforms.add_noise_to_qfunc + ~transforms.add_noise Transforms for circuit compilation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -119,7 +119,7 @@ from .hamiltonian_expand import hamiltonian_expand from .measurement_grouping import measurement_grouping from .metric_tensor import metric_tensor -from .noise import add_noise_to_tape, add_noise_to_qfunc +from .noise import add_noise_to_tape, add_noise from .optimization import ( cancel_inverses, commute_controlled, diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/noise.py index 492e7bba501..bc9e100349a 100644 --- a/pennylane/transforms/noise.py +++ b/pennylane/transforms/noise.py @@ -122,8 +122,8 @@ def add_noise_to_tape( apply(m) -add_noise_to_qfunc = qfunc_transform(add_noise_to_tape) -add_noise_to_qfunc.__doc__ = """Add noisy operations to an input quantum function. +add_noise = qfunc_transform(add_noise_to_tape) +add_noise.__doc__ = """Add noisy operations to an input quantum function. The function will be updated to have noisy gates, specified by the ``noisy_op`` argument, added according to the positioning specified in the ``position`` argument. @@ -155,12 +155,12 @@ def add_noise_to_tape( .. code-block:: python3 - from pennylane.transforms import add_noise_to_qfunc + from pennylane.transforms import add_noise dev = qml.device("default.mixed", wires=2) @qml.qnode(dev) - @add_noise_to_qfunc(qml.AmplitudeDamping, 0.2, position="end") + @add_noise(qml.AmplitudeDamping, 0.2, position="end") def f(w, x, y, z): qml.RX(w, wires=0) qml.RY(x, wires=1) From 2f1bbd8dc2a1598a39bc5b1c1b4a8ebeb7af5b99 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 25 Oct 2021 08:48:30 -0400 Subject: [PATCH 18/62] Add to init --- pennylane/transforms/__init__.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pennylane/transforms/__init__.py b/pennylane/transforms/__init__.py index f267213209f..af170be9b93 100644 --- a/pennylane/transforms/__init__.py +++ b/pennylane/transforms/__init__.py @@ -88,6 +88,17 @@ ~transforms.measurement_grouping ~transforms.hamiltonian_expand +Transforms that act on devices +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These transforms apply to circuits just before they are +executed on the device. + +.. autosummary:: + :toctree: api + + ~transforms.add_noise_to_dev + Decorators and utility functions -------------------------------- @@ -119,7 +130,7 @@ from .hamiltonian_expand import hamiltonian_expand from .measurement_grouping import measurement_grouping from .metric_tensor import metric_tensor -from .noise import add_noise_to_tape, add_noise +from .noise import add_noise_to_tape, add_noise, add_noise_to_dev from .optimization import ( cancel_inverses, commute_controlled, From 5940470ef2f0d2f8c3b577db05c32bb92a6c8374 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 25 Oct 2021 09:12:47 -0400 Subject: [PATCH 19/62] Improve docstrings --- pennylane/transforms/noise.py | 80 ++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/noise.py index bc9e100349a..3477a7faa57 100644 --- a/pennylane/transforms/noise.py +++ b/pennylane/transforms/noise.py @@ -17,7 +17,7 @@ from collections.abc import Sequence from typing import Type, Union -from pennylane import BasisState, QubitStateVector, apply +from pennylane import BasisState, QubitStateVector, apply, Device from pennylane.operation import Channel from pennylane.ops.channel import __qubit_channels__ from pennylane.tape import QuantumTape @@ -177,3 +177,81 @@ def f(w, x, y, z): 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.2)──╭┤ ⟨Z ⊗ Z⟩ 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.2)──╰┤ ⟨Z ⊗ Z⟩ """ + + +def add_noise_to_dev( + device: Device, + noisy_op: Type[Channel], + noisy_op_args: Union[tuple, float], + position: str = "all", +): + """Add noisy operations to an input device. + + After applying this transform, circuits executed on the device will have noisy gates added. + The gates are specified by the ``noisy_op`` argument and positioned according to the + ``position`` argument. The device is transformed in-place. + + This transform is only compatible with devices that support noisy operations (of type + :class:`~.Channel`), such as ``default.mixed``. + + .. warning:: + + This device transform is a beta feature. Use the :class:`~.beta.QNode` decorator to create + compatible QNodes and use :func:`~.batch.execute` to execute quantum tapes. + + Args: + device (Device): the device to be transformed + noisy_op (Type[Channel]): the noisy operation to be added at positions within the function + noisy_op_args (tuple or float): the arguments fed to the noisy operation, or a single float + specifying the noise strength + position (str): Specification of where to add noise. Should be one of: ``"all"`` to add + the noisy operation after all gates; ``"start"`` to add the noisy operation to all wires + at the start of the circuit; ``"end"`` to add the noisy operation to all wires at the + end of the circuit. + + Raises: + ValueError: if the noisy operation passed in ``noisy_op`` applies to more than one wire + ValueError: if the requested ``position`` argument is now ``'start'``, ``'end'`` or + ``'all'`` + ValueError: if the noisy operation passed in ``noisy_op`` is not a noisy channel + ValueError: if more than one state preparation is present in the function, or if the + preparation is not at the start of the function + + **Example:** + + Consider the following QNode: + + .. code-block:: python3 + + from pennylane.beta import qnode + + dev = qml.device("default.mixed", wires=4) + + @qnode(dev) + def f(w, x, y, z): + qml.RX(w, wires=0) + qml.RY(x, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(y, wires=0) + qml.RX(z, wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + Execution of the circuit on ``dev`` will be noise-free: + + >>> f(0.9, 0.4, 0.5, 0.6) + tensor(0.86243536, requires_grad=True) + + However, noise can be easily added to the device: + + >>> qml.transforms.add_noise_to_dev(dev, qml.AmplitudeDamping, 0.2) + >>> f(0.9, 0.4, 0.5, 0.6) + tensor(0.72945434, requires_grad=True) + """ + # TODO: Remove warning in docstrings once new QNode replaces the old + original_expand_fn = device.expand_fn + + def new_expand_fn(circuit, max_expansion=10): + new_tape = add_noise_to_tape(circuit, noisy_op, noisy_op_args, position) + return original_expand_fn(new_tape, max_expansion=max_expansion) + + device.expand_fn = new_expand_fn From 807b33aaecc070ba7dc4b96180886d4c46dd8dda Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 25 Oct 2021 09:12:57 -0400 Subject: [PATCH 20/62] Add to changelog --- doc/releases/changelog-dev.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index e9c7e5037cf..9df9981b37b 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -387,8 +387,8 @@

Improvements

-* The `add_noise_to_tape` and `add_noise_to_qfunc` transforms have now been added, providing a - simple way to add noise to a quantum circuit. +* The `add_noise`, `add_noise_to_tape` and `add_noise_to_dev` transforms have now been added, + providing a way to add simple noise to a quantum circuit. [(#1795)](https://github.com/PennyLaneAI/pennylane/pull/1795) The following QNode can be transformed to add noise to the circuit: From 31e5b2ce1f72c38340d105983dbe84538e36438f Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 25 Oct 2021 09:18:19 -0400 Subject: [PATCH 21/62] Add to docstring --- pennylane/transforms/noise.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/noise.py index 3477a7faa57..adfd3e18aee 100644 --- a/pennylane/transforms/noise.py +++ b/pennylane/transforms/noise.py @@ -196,8 +196,8 @@ def add_noise_to_dev( .. warning:: - This device transform is a beta feature. Use the :class:`~.beta.QNode` decorator to create - compatible QNodes and use :func:`~.batch.execute` to execute quantum tapes. + This device transform is a beta feature. Use the :class:`pennylane.beta.QNode` decorator to + create compatible QNodes and use :func:`~.execute` to execute quantum tapes. Args: device (Device): the device to be transformed From 7aed33c1e502784d806525d362fa19bee2fc5d5e Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 25 Oct 2021 09:31:24 -0400 Subject: [PATCH 22/62] Add test for add_noise_to_dev --- tests/transforms/test_noise.py | 57 +++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/tests/transforms/test_noise.py b/tests/transforms/test_noise.py index b1b209d4f4a..61f7b3eea4d 100644 --- a/tests/transforms/test_noise.py +++ b/tests/transforms/test_noise.py @@ -20,7 +20,7 @@ import pennylane as qml from pennylane.operation import Expectation from pennylane.tape import QuantumTape -from pennylane.transforms.noise import add_noise_to_qfunc, add_noise_to_tape +from pennylane.transforms.noise import add_noise, add_noise_to_tape, add_noise_to_dev class TestAddNoiseToTape: @@ -237,13 +237,13 @@ def test_multiple_preparations(self): add_noise_to_tape(tape, qml.AmplitudeDamping, 0.4) -def test_add_noise_to_qfunc(): - """Test that a QNode with the add_noise_to_qfunc decorator gives a different result than one +def test_add_noise(): + """Test that a QNode with the add_noise decorator gives a different result than one without.""" dev = qml.device("default.mixed", wires=2) @qml.qnode(dev) - @add_noise_to_qfunc(qml.AmplitudeDamping, 0.2, position="end") + @add_noise(qml.AmplitudeDamping, 0.2, position="end") def f_noisy(w, x, y, z): qml.RX(w, wires=0) qml.RY(x, wires=1) @@ -264,3 +264,52 @@ def f(w, x, y, z): args = [0.1, 0.2, 0.3, 0.4] assert not np.isclose(f_noisy(*args), f(*args)) + + +def test_add_noise_to_dev(mocker): + """Test if a device transformed by the add_noise_to_dev does successfully add noise to + subsequent circuit executions""" + with QuantumTape() as in_tape: + qml.RX(0.9, wires=0) + qml.RY(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(0.5, wires=0) + qml.RX(0.6, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + dev = qml.device("default.mixed", wires=2) + res_without_noise = qml.execute([in_tape], dev, qml.gradients.param_shift) + + spy = mocker.spy(dev, "expand_fn") + + add_noise_to_dev(dev, qml.PhaseDamping, 0.4) + + res_with_noise = qml.execute([in_tape], dev, qml.gradients.param_shift) + tape = spy.call_args[0][0] + + with QuantumTape() as tape_exp: + qml.RX(0.9, wires=0) + qml.PhaseDamping(0.4, wires=0) + qml.RY(0.4, wires=1) + qml.PhaseDamping(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.PhaseDamping(0.4, wires=0) + qml.PhaseDamping(0.4, wires=1) + qml.RY(0.5, wires=0) + qml.PhaseDamping(0.4, wires=0) + qml.RX(0.6, wires=1) + qml.PhaseDamping(0.4, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all( + np.allclose(o1.parameters, o2.parameters) + for o1, o2 in zip(tape.operations, tape_exp.operations) + ) + assert len(tape.measurements) == 1 + assert tape.observables[0].name == ["PauliZ", "PauliZ"] + assert tape.observables[0].wires.tolist() == [0, 1] + assert tape.measurements[0].return_type is Expectation + + assert not np.allclose(res_without_noise, res_with_noise) From 59b6f9e72a22c2032ffe319ca7e52840f3b49a61 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 25 Oct 2021 09:32:08 -0400 Subject: [PATCH 23/62] Fix CI --- pennylane/transforms/noise.py | 5 +++-- tests/transforms/test_noise.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/noise.py index adfd3e18aee..906015115d5 100644 --- a/pennylane/transforms/noise.py +++ b/pennylane/transforms/noise.py @@ -17,11 +17,12 @@ from collections.abc import Sequence from typing import Type, Union -from pennylane import BasisState, QubitStateVector, apply, Device +from pennylane import BasisState, Device, QubitStateVector, apply from pennylane.operation import Channel from pennylane.ops.channel import __qubit_channels__ from pennylane.tape import QuantumTape -from pennylane.transforms.qfunc_transforms import qfunc_transform, single_tape_transform +from pennylane.transforms.qfunc_transforms import (qfunc_transform, + single_tape_transform) @single_tape_transform diff --git a/tests/transforms/test_noise.py b/tests/transforms/test_noise.py index 61f7b3eea4d..f4b8b15a849 100644 --- a/tests/transforms/test_noise.py +++ b/tests/transforms/test_noise.py @@ -20,7 +20,8 @@ import pennylane as qml from pennylane.operation import Expectation from pennylane.tape import QuantumTape -from pennylane.transforms.noise import add_noise, add_noise_to_tape, add_noise_to_dev +from pennylane.transforms.noise import (add_noise, add_noise_to_dev, + add_noise_to_tape) class TestAddNoiseToTape: From d06d66fa0096743dfa444a5a403e3205ef4f49f1 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 25 Oct 2021 11:14:47 -0400 Subject: [PATCH 24/62] Attempt --- doc/releases/changelog-dev.md | 2 +- pennylane/transforms/__init__.py | 3 +- pennylane/transforms/noise.py | 156 +++---- pennylane/transforms/qfunc_transforms.py | 15 +- tests/transforms/test_noise.py | 544 +++++++++++------------ 5 files changed, 348 insertions(+), 372 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 9df9981b37b..541ed4eb747 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -387,7 +387,7 @@

Improvements

-* The `add_noise`, `add_noise_to_tape` and `add_noise_to_dev` transforms have now been added, +* The `add_noise` and `add_noise_to_dev` transforms have now been added, providing a way to add simple noise to a quantum circuit. [(#1795)](https://github.com/PennyLaneAI/pennylane/pull/1795) diff --git a/pennylane/transforms/__init__.py b/pennylane/transforms/__init__.py index af170be9b93..03f4571b43b 100644 --- a/pennylane/transforms/__init__.py +++ b/pennylane/transforms/__init__.py @@ -84,7 +84,6 @@ .. autosummary:: :toctree: api - ~transforms.add_noise_to_tape ~transforms.measurement_grouping ~transforms.hamiltonian_expand @@ -130,7 +129,7 @@ from .hamiltonian_expand import hamiltonian_expand from .measurement_grouping import measurement_grouping from .metric_tensor import metric_tensor -from .noise import add_noise_to_tape, add_noise, add_noise_to_dev +from .noise import add_noise, add_noise_to_dev from .optimization import ( cancel_inverses, commute_controlled, diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/noise.py index 906015115d5..9de287b37a6 100644 --- a/pennylane/transforms/noise.py +++ b/pennylane/transforms/noise.py @@ -25,21 +25,22 @@ single_tape_transform) +@qfunc_transform @single_tape_transform -def add_noise_to_tape( - tape: QuantumTape, +def add_noise( + circuit: Union[callable, QuantumTape], noisy_op: Type[Channel], noisy_op_args: Union[tuple, float], position: str = "all", -) -> QuantumTape: - r"""Add noisy operations to an input tape. +) -> Union[callable, QuantumTape]: + """Add noisy operations to an input circuit. - The tape will be updated to have noisy gates, specified by the ``noisy_op`` argument, added + The circuit will be updated to have noisy gates, specified by the ``noisy_op`` argument, added according to the positioning specified in the ``position`` argument. Args: - tape (QuantumTape): the input tape - noisy_op (Type[Channel]): the noisy operation to be added at positions within the tape + circuit (callable or QuantumTape): the input circuit + noisy_op (Type[Channel]): the noisy operation to be added at positions within the circuit noisy_op_args (tuple or float): the arguments fed to the noisy operation, or a single float specifying the noise strength position (str): Specification of where to add noise. Should be one of: ``"all"`` to add @@ -48,41 +49,71 @@ def add_noise_to_tape( end of the circuit. Returns: - QuantumTape: a noisy version of the input tape + callable or QuantumTape: a noisy version of the input circuit Raises: ValueError: if the noisy operation passed in ``noisy_op`` applies to more than one wire ValueError: if the requested ``position`` argument is now ``'start'``, ``'end'`` or ``'all'`` ValueError: if the noisy operation passed in ``noisy_op`` is not a noisy channel - ValueError: if more than one state preparation is present in the tape, or if the preparation - is not at the start of the tape + ValueError: if more than one state preparation is present in the circuit, or if the + preparation is not at the start of the circuit - **Example:** + .. UsageDetails:: - Consider the following tape: + **Transforming tapes:** - .. code-block:: python3 + Consider the following tape: - with qml.tape.QuantumTape() as tape: - qml.RX(0.9, wires=0) - qml.RY(0.4, wires=1) - qml.CNOT(wires=[0, 1]) - qml.RY(0.5, wires=0) - qml.RX(0.6, wires=1) - qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + .. code-block:: python3 + + with qml.tape.QuantumTape() as tape: + qml.RX(0.9, wires=0) + qml.RY(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(0.5, wires=0) + qml.RX(0.6, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + We can add the :class:`~.AmplitudeDamping` channel to the start of the circuit using: + + >>> from pennylane.transforms import add_noise + >>> noisy_tape = add_noise(tape, qml.AmplitudeDamping, 0.05, position="end") + >>> print(noisy_tape.draw()) + 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.05)──╭┤ ⟨Z ⊗ Z⟩ + 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.05)──╰┤ ⟨Z ⊗ Z⟩ + + **Transforming QNodes:** + + The following QNode can be transformed to add noise to the circuit: + + .. code-block:: python3 + + from pennylane.transforms import add_noise + + dev = qml.device("default.mixed", wires=2) + + @qml.qnode(dev) + @add_noise(qml.AmplitudeDamping, 0.2, position="end") + def f(w, x, y, z): + qml.RX(w, wires=0) + qml.RY(x, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(y, wires=0) + qml.RX(z, wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - We can add the :class:`~.AmplitudeDamping` channel to the start of the circuit using: + Executions of this circuit will differ from the noise-free value: - >>> from pennylane.transforms import add_noise_to_tape - >>> noisy_tape = add_noise_to_tape(tape, qml.AmplitudeDamping, 0.05, position="end") - >>> print(noisy_tape.draw()) - 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.05)──╭┤ ⟨Z ⊗ Z⟩ - 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.05)──╰┤ ⟨Z ⊗ Z⟩ + >>> f(0.9, 0.4, 0.5, 0.6) + tensor(0.754847, requires_grad=True) + >>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6)) + 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.2)──╭┤ ⟨Z ⊗ Z⟩ + 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.2)──╰┤ ⟨Z ⊗ Z⟩ """ if noisy_op.num_wires != 1: raise ValueError( - "Adding noise to the tape is only supported for single-qubit noisy operations" + "Adding noise to the circuit is only supported for single-qubit noisy operations" ) if position not in ("start", "end", "all"): raise ValueError("Position must be either 'start', 'end', or 'all' (default)") @@ -94,92 +125,35 @@ def add_noise_to_tape( if not isinstance(noisy_op_args, Sequence): noisy_op_args = [noisy_op_args] - preps = tuple(isinstance(o, (QubitStateVector, BasisState)) for o in tape.operations) + preps = tuple(isinstance(o, (QubitStateVector, BasisState)) for o in circuit.operations) valid_preps = sum(preps) == 1 and preps[0] is True or sum(preps) == 0 if not valid_preps: raise ValueError("Only a single state preparation at the start of the circuit is supported") if sum(preps) == 1: - apply(tape.operations[0]) + apply(circuit.operations[0]) start_pos = 1 else: start_pos = 0 if position == "start": - for w in tape.wires: + for w in circuit.wires: noisy_op(*noisy_op_args, wires=w) - for op in tape.operations[start_pos:]: + for op in circuit.operations[start_pos:]: apply(op) if position == "all": for w in op.wires: noisy_op(*noisy_op_args, wires=w) if position == "end": - for w in tape.wires: + for w in circuit.wires: noisy_op(*noisy_op_args, wires=w) - for m in tape.measurements: + for m in circuit.measurements: apply(m) -add_noise = qfunc_transform(add_noise_to_tape) -add_noise.__doc__ = """Add noisy operations to an input quantum function. - - The function will be updated to have noisy gates, specified by the ``noisy_op`` argument, added - according to the positioning specified in the ``position`` argument. - - Args: - fn (Callable): the quantum function - noisy_op (Type[Channel]): the noisy operation to be added at positions within the function - noisy_op_args (tuple or float): the arguments fed to the noisy operation, or a single float - specifying the noise strength - position (str): Specification of where to add noise. Should be one of: ``"all"`` to add - the noisy operation after all gates; ``"start"`` to add the noisy operation to all wires - at the start of the circuit; ``"end"`` to add the noisy operation to all wires at the - end of the circuit. - - Returns: - Callable: a noisy version of the input function - - Raises: - ValueError: if the noisy operation passed in ``noisy_op`` applies to more than one wire - ValueError: if the requested ``position`` argument is now ``'start'``, ``'end'`` or - ``'all'`` - ValueError: if the noisy operation passed in ``noisy_op`` is not a noisy channel - ValueError: if more than one state preparation is present in the function, or if the - preparation is not at the start of the function - - **Example:** - - The following QNode can be transformed to add noise to the circuit: - - .. code-block:: python3 - - from pennylane.transforms import add_noise - - dev = qml.device("default.mixed", wires=2) - - @qml.qnode(dev) - @add_noise(qml.AmplitudeDamping, 0.2, position="end") - def f(w, x, y, z): - qml.RX(w, wires=0) - qml.RY(x, wires=1) - qml.CNOT(wires=[0, 1]) - qml.RY(y, wires=0) - qml.RX(z, wires=1) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - - Executions of this circuit will differ from the noise-free value: - - >>> f(0.9, 0.4, 0.5, 0.6) - tensor(0.754847, requires_grad=True) - >>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6)) - 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.2)──╭┤ ⟨Z ⊗ Z⟩ - 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.2)──╰┤ ⟨Z ⊗ Z⟩ -""" - - def add_noise_to_dev( device: Device, noisy_op: Type[Channel], @@ -252,7 +226,7 @@ def f(w, x, y, z): original_expand_fn = device.expand_fn def new_expand_fn(circuit, max_expansion=10): - new_tape = add_noise_to_tape(circuit, noisy_op, noisy_op_args, position) + new_tape = add_noise(circuit, noisy_op, noisy_op_args, position) return original_expand_fn(new_tape, max_expansion=max_expansion) device.expand_fn = new_expand_fn diff --git a/pennylane/transforms/qfunc_transforms.py b/pennylane/transforms/qfunc_transforms.py index b631f39c963..b4d8f3d752c 100644 --- a/pennylane/transforms/qfunc_transforms.py +++ b/pennylane/transforms/qfunc_transforms.py @@ -177,6 +177,8 @@ def _create_qfunc_internal_wrapper(fn, tape_transform, transform_args, transform f"The qfunc to transform, {fn}, does not appear " "to be a valid Python function or callable." ) + if isinstance(fn, qml.tape.QuantumTape): + return tape_transform(fn, *transform_args, **transform_kwargs) @functools.wraps(fn) def internal_wrapper(*args, **kwargs): @@ -359,11 +361,11 @@ def new_qfunc(*args, **kwargs): the queueing logic required under steps (1) and (3), so that it does not need to be repeated and tested for every new qfunc transform. """ - if not callable(tape_transform): - raise ValueError( - "The qfunc_transform decorator can only be applied " - "to single tape transform functions." - ) + # if not callable(tape_transform): + # raise ValueError( + # "The qfunc_transform decorator can only be applied " + # "to single tape transform functions." + # ) if not isinstance(tape_transform, single_tape_transform): tape_transform = single_tape_transform(tape_transform) @@ -381,7 +383,8 @@ def wrapper(fn): wrapper.tape_fn = functools.partial(tape_transform, *targs, **tkwargs) return wrapper - + # if tape_transform.__name__ == "add_noise": + # print(len(params)) elif len(params) == 1: @functools.wraps(tape_transform) diff --git a/tests/transforms/test_noise.py b/tests/transforms/test_noise.py index f4b8b15a849..c74fa1aefb1 100644 --- a/tests/transforms/test_noise.py +++ b/tests/transforms/test_noise.py @@ -20,12 +20,11 @@ import pennylane as qml from pennylane.operation import Expectation from pennylane.tape import QuantumTape -from pennylane.transforms.noise import (add_noise, add_noise_to_dev, - add_noise_to_tape) +from pennylane.transforms.noise import (add_noise, add_noise_to_dev) -class TestAddNoiseToTape: - """Tests for the add_noise_to_tape function""" +class TestAddNoise: + """Tests for the add_noise function""" with QuantumTape() as tape: qml.RX(0.9, wires=0) @@ -46,271 +45,272 @@ class TestAddNoiseToTape: def test_multiwire_noisy_op(self): """Tests if a ValueError is raised when multiqubit channels are requested""" - with pytest.raises(ValueError, match="Adding noise to the tape is only"): - add_noise_to_tape(self.tape, qml.QubitChannel, []) - - def test_invalid_position(self): - """Test if a ValueError is raised when an invalid position is requested""" - with pytest.raises(ValueError, match="Position must be either 'start', 'end', or 'all'"): - add_noise_to_tape(self.tape, qml.AmplitudeDamping, 0.4, position="ABC") - - def test_not_noisy(self): - """Test if a ValueError is raised when something that is not a noisy channel is fed to the - noisy_op argument""" - with pytest.raises(ValueError, match="The noisy_op argument must be a noisy operation"): - add_noise_to_tape(self.tape, qml.PauliX, 0.4) - - def test_start(self): - """Test if the expected tape is returned when the start position is requested""" - tape = add_noise_to_tape(self.tape, qml.AmplitudeDamping, 0.4, position="start") - - with QuantumTape() as tape_exp: - qml.AmplitudeDamping(0.4, wires=0) - qml.AmplitudeDamping(0.4, wires=1) - qml.RX(0.9, wires=0) - qml.RY(0.4, wires=1) - qml.CNOT(wires=[0, 1]) - qml.RY(0.5, wires=0) - qml.RX(0.6, wires=1) - qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - - assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all( - np.allclose(o1.parameters, o2.parameters) - for o1, o2 in zip(tape.operations, tape_exp.operations) - ) - assert len(tape.measurements) == 1 - assert tape.observables[0].name == ["PauliZ", "PauliZ"] - assert tape.observables[0].wires.tolist() == [0, 1] - assert tape.measurements[0].return_type is Expectation - - def test_all(self): - """Test if the expected tape is returned when the all position is requested""" - tape = add_noise_to_tape(self.tape, qml.PhaseDamping, 0.4, position="all") - - with QuantumTape() as tape_exp: - qml.RX(0.9, wires=0) - qml.PhaseDamping(0.4, wires=0) - qml.RY(0.4, wires=1) - qml.PhaseDamping(0.4, wires=1) - qml.CNOT(wires=[0, 1]) - qml.PhaseDamping(0.4, wires=0) - qml.PhaseDamping(0.4, wires=1) - qml.RY(0.5, wires=0) - qml.PhaseDamping(0.4, wires=0) - qml.RX(0.6, wires=1) - qml.PhaseDamping(0.4, wires=1) - qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - - assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all( - np.allclose(o1.parameters, o2.parameters) - for o1, o2 in zip(tape.operations, tape_exp.operations) - ) - assert len(tape.measurements) == 1 - assert tape.observables[0].name == ["PauliZ", "PauliZ"] - assert tape.observables[0].wires.tolist() == [0, 1] - assert tape.measurements[0].return_type is Expectation - - def test_end(self): - """Test if the expected tape is returned when the end position is requested""" - tape = add_noise_to_tape( - self.tape, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end" - ) - - with QuantumTape() as tape_exp: - qml.RX(0.9, wires=0) - qml.RY(0.4, wires=1) - qml.CNOT(wires=[0, 1]) - qml.RY(0.5, wires=0) - qml.RX(0.6, wires=1) - qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=0) - qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=1) - qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - - assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all( - np.allclose(o1.parameters, o2.parameters) - for o1, o2 in zip(tape.operations, tape_exp.operations) - ) - assert len(tape.measurements) == 1 - assert tape.observables[0].name == ["PauliZ", "PauliZ"] - assert tape.observables[0].wires.tolist() == [0, 1] - assert tape.measurements[0].return_type is Expectation - - def test_start_with_state_prep(self): - """Test if the expected tape is returned when the start position is requested in a tape - that has state preparation""" - tape = add_noise_to_tape(self.tape_with_prep, qml.AmplitudeDamping, 0.4, position="start") - - with QuantumTape() as tape_exp: - qml.QubitStateVector([1, 0], wires=0) - qml.AmplitudeDamping(0.4, wires=0) - qml.AmplitudeDamping(0.4, wires=1) - qml.RX(0.9, wires=0) - qml.RY(0.4, wires=1) - qml.CNOT(wires=[0, 1]) - qml.RY(0.5, wires=0) - qml.RX(0.6, wires=1) - qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - - assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all( - np.allclose(o1.parameters, o2.parameters) - for o1, o2 in zip(tape.operations, tape_exp.operations) - ) - assert len(tape.measurements) == 1 - assert tape.observables[0].name == ["PauliZ", "PauliZ"] - assert tape.observables[0].wires.tolist() == [0, 1] - assert tape.measurements[0].return_type is Expectation - - def test_all_with_state_prep(self): - """Test if the expected tape is returned when the all position is requested in a tape - that has state preparation""" - tape = add_noise_to_tape(self.tape_with_prep, qml.PhaseDamping, 0.4, position="all") - - with QuantumTape() as tape_exp: - qml.QubitStateVector([1, 0], wires=0) - qml.RX(0.9, wires=0) - qml.PhaseDamping(0.4, wires=0) - qml.RY(0.4, wires=1) - qml.PhaseDamping(0.4, wires=1) - qml.CNOT(wires=[0, 1]) - qml.PhaseDamping(0.4, wires=0) - qml.PhaseDamping(0.4, wires=1) - qml.RY(0.5, wires=0) - qml.PhaseDamping(0.4, wires=0) - qml.RX(0.6, wires=1) - qml.PhaseDamping(0.4, wires=1) - qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - - assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all( - np.allclose(o1.parameters, o2.parameters) - for o1, o2 in zip(tape.operations, tape_exp.operations) - ) - assert len(tape.measurements) == 1 - assert tape.observables[0].name == ["PauliZ", "PauliZ"] - assert tape.observables[0].wires.tolist() == [0, 1] - assert tape.measurements[0].return_type is Expectation - - def test_end_with_state_prep(self): - """Test if the expected tape is returned when the end position is requested in a tape - that has state preparation""" - tape = add_noise_to_tape( - self.tape_with_prep, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end" - ) - - with QuantumTape() as tape_exp: - qml.QubitStateVector([1, 0], wires=0) - qml.RX(0.9, wires=0) - qml.RY(0.4, wires=1) - qml.CNOT(wires=[0, 1]) - qml.RY(0.5, wires=0) - qml.RX(0.6, wires=1) - qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=0) - qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=1) - qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - - assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all( - np.allclose(o1.parameters, o2.parameters) - for o1, o2 in zip(tape.operations, tape_exp.operations) - ) - assert len(tape.measurements) == 1 - assert tape.observables[0].name == ["PauliZ", "PauliZ"] - assert tape.observables[0].wires.tolist() == [0, 1] - assert tape.measurements[0].return_type is Expectation - - def test_multiple_preparations(self): - """Tests if a ValueError is raised when multiple state preparations are present in the - tape""" - with QuantumTape() as tape: - qml.QubitStateVector([1, 0], wires=0) - qml.QubitStateVector([0, 1], wires=1) - with pytest.raises(ValueError, match="Only a single state preparation at the start of the"): - add_noise_to_tape(tape, qml.AmplitudeDamping, 0.4) - - -def test_add_noise(): - """Test that a QNode with the add_noise decorator gives a different result than one - without.""" - dev = qml.device("default.mixed", wires=2) - - @qml.qnode(dev) - @add_noise(qml.AmplitudeDamping, 0.2, position="end") - def f_noisy(w, x, y, z): - qml.RX(w, wires=0) - qml.RY(x, wires=1) - qml.CNOT(wires=[0, 1]) - qml.RY(y, wires=0) - qml.RX(z, wires=1) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - - @qml.qnode(dev) - def f(w, x, y, z): - qml.RX(w, wires=0) - qml.RY(x, wires=1) - qml.CNOT(wires=[0, 1]) - qml.RY(y, wires=0) - qml.RX(z, wires=1) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - - args = [0.1, 0.2, 0.3, 0.4] - - assert not np.isclose(f_noisy(*args), f(*args)) - - -def test_add_noise_to_dev(mocker): - """Test if a device transformed by the add_noise_to_dev does successfully add noise to - subsequent circuit executions""" - with QuantumTape() as in_tape: - qml.RX(0.9, wires=0) - qml.RY(0.4, wires=1) - qml.CNOT(wires=[0, 1]) - qml.RY(0.5, wires=0) - qml.RX(0.6, wires=1) - qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - - dev = qml.device("default.mixed", wires=2) - res_without_noise = qml.execute([in_tape], dev, qml.gradients.param_shift) - - spy = mocker.spy(dev, "expand_fn") - - add_noise_to_dev(dev, qml.PhaseDamping, 0.4) - - res_with_noise = qml.execute([in_tape], dev, qml.gradients.param_shift) - tape = spy.call_args[0][0] - - with QuantumTape() as tape_exp: - qml.RX(0.9, wires=0) - qml.PhaseDamping(0.4, wires=0) - qml.RY(0.4, wires=1) - qml.PhaseDamping(0.4, wires=1) - qml.CNOT(wires=[0, 1]) - qml.PhaseDamping(0.4, wires=0) - qml.PhaseDamping(0.4, wires=1) - qml.RY(0.5, wires=0) - qml.PhaseDamping(0.4, wires=0) - qml.RX(0.6, wires=1) - qml.PhaseDamping(0.4, wires=1) - qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - - assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) - assert all( - np.allclose(o1.parameters, o2.parameters) - for o1, o2 in zip(tape.operations, tape_exp.operations) - ) - assert len(tape.measurements) == 1 - assert tape.observables[0].name == ["PauliZ", "PauliZ"] - assert tape.observables[0].wires.tolist() == [0, 1] - assert tape.measurements[0].return_type is Expectation - - assert not np.allclose(res_without_noise, res_with_noise) + # with pytest.raises(ValueError, match="Adding noise to the tape is only"): + r = add_noise(self.tape, qml.QubitChannel, [])(self.tape) + # print(add_noise) + +# def test_invalid_position(self): +# """Test if a ValueError is raised when an invalid position is requested""" +# with pytest.raises(ValueError, match="Position must be either 'start', 'end', or 'all'"): +# add_noise(self.tape, qml.AmplitudeDamping, 0.4, position="ABC") +# +# def test_not_noisy(self): +# """Test if a ValueError is raised when something that is not a noisy channel is fed to the +# noisy_op argument""" +# with pytest.raises(ValueError, match="The noisy_op argument must be a noisy operation"): +# add_noise(self.tape, qml.PauliX, 0.4) +# +# def test_start(self): +# """Test if the expected tape is returned when the start position is requested""" +# tape = add_noise(self.tape, qml.AmplitudeDamping, 0.4, position="start") +# +# with QuantumTape() as tape_exp: +# qml.AmplitudeDamping(0.4, wires=0) +# qml.AmplitudeDamping(0.4, wires=1) +# qml.RX(0.9, wires=0) +# qml.RY(0.4, wires=1) +# qml.CNOT(wires=[0, 1]) +# qml.RY(0.5, wires=0) +# qml.RX(0.6, wires=1) +# qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) +# +# assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) +# assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) +# assert all( +# np.allclose(o1.parameters, o2.parameters) +# for o1, o2 in zip(tape.operations, tape_exp.operations) +# ) +# assert len(tape.measurements) == 1 +# assert tape.observables[0].name == ["PauliZ", "PauliZ"] +# assert tape.observables[0].wires.tolist() == [0, 1] +# assert tape.measurements[0].return_type is Expectation +# +# def test_all(self): +# """Test if the expected tape is returned when the all position is requested""" +# tape = add_noise(self.tape, qml.PhaseDamping, 0.4, position="all") +# +# with QuantumTape() as tape_exp: +# qml.RX(0.9, wires=0) +# qml.PhaseDamping(0.4, wires=0) +# qml.RY(0.4, wires=1) +# qml.PhaseDamping(0.4, wires=1) +# qml.CNOT(wires=[0, 1]) +# qml.PhaseDamping(0.4, wires=0) +# qml.PhaseDamping(0.4, wires=1) +# qml.RY(0.5, wires=0) +# qml.PhaseDamping(0.4, wires=0) +# qml.RX(0.6, wires=1) +# qml.PhaseDamping(0.4, wires=1) +# qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) +# +# assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) +# assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) +# assert all( +# np.allclose(o1.parameters, o2.parameters) +# for o1, o2 in zip(tape.operations, tape_exp.operations) +# ) +# assert len(tape.measurements) == 1 +# assert tape.observables[0].name == ["PauliZ", "PauliZ"] +# assert tape.observables[0].wires.tolist() == [0, 1] +# assert tape.measurements[0].return_type is Expectation +# +# def test_end(self): +# """Test if the expected tape is returned when the end position is requested""" +# tape = add_noise( +# self.tape, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end" +# ) +# +# with QuantumTape() as tape_exp: +# qml.RX(0.9, wires=0) +# qml.RY(0.4, wires=1) +# qml.CNOT(wires=[0, 1]) +# qml.RY(0.5, wires=0) +# qml.RX(0.6, wires=1) +# qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=0) +# qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=1) +# qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) +# +# assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) +# assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) +# assert all( +# np.allclose(o1.parameters, o2.parameters) +# for o1, o2 in zip(tape.operations, tape_exp.operations) +# ) +# assert len(tape.measurements) == 1 +# assert tape.observables[0].name == ["PauliZ", "PauliZ"] +# assert tape.observables[0].wires.tolist() == [0, 1] +# assert tape.measurements[0].return_type is Expectation +# +# def test_start_with_state_prep(self): +# """Test if the expected tape is returned when the start position is requested in a tape +# that has state preparation""" +# tape = add_noise(self.tape_with_prep, qml.AmplitudeDamping, 0.4, position="start") +# +# with QuantumTape() as tape_exp: +# qml.QubitStateVector([1, 0], wires=0) +# qml.AmplitudeDamping(0.4, wires=0) +# qml.AmplitudeDamping(0.4, wires=1) +# qml.RX(0.9, wires=0) +# qml.RY(0.4, wires=1) +# qml.CNOT(wires=[0, 1]) +# qml.RY(0.5, wires=0) +# qml.RX(0.6, wires=1) +# qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) +# +# assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) +# assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) +# assert all( +# np.allclose(o1.parameters, o2.parameters) +# for o1, o2 in zip(tape.operations, tape_exp.operations) +# ) +# assert len(tape.measurements) == 1 +# assert tape.observables[0].name == ["PauliZ", "PauliZ"] +# assert tape.observables[0].wires.tolist() == [0, 1] +# assert tape.measurements[0].return_type is Expectation +# +# def test_all_with_state_prep(self): +# """Test if the expected tape is returned when the all position is requested in a tape +# that has state preparation""" +# tape = add_noise(self.tape_with_prep, qml.PhaseDamping, 0.4, position="all") +# +# with QuantumTape() as tape_exp: +# qml.QubitStateVector([1, 0], wires=0) +# qml.RX(0.9, wires=0) +# qml.PhaseDamping(0.4, wires=0) +# qml.RY(0.4, wires=1) +# qml.PhaseDamping(0.4, wires=1) +# qml.CNOT(wires=[0, 1]) +# qml.PhaseDamping(0.4, wires=0) +# qml.PhaseDamping(0.4, wires=1) +# qml.RY(0.5, wires=0) +# qml.PhaseDamping(0.4, wires=0) +# qml.RX(0.6, wires=1) +# qml.PhaseDamping(0.4, wires=1) +# qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) +# +# assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) +# assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) +# assert all( +# np.allclose(o1.parameters, o2.parameters) +# for o1, o2 in zip(tape.operations, tape_exp.operations) +# ) +# assert len(tape.measurements) == 1 +# assert tape.observables[0].name == ["PauliZ", "PauliZ"] +# assert tape.observables[0].wires.tolist() == [0, 1] +# assert tape.measurements[0].return_type is Expectation +# +# def test_end_with_state_prep(self): +# """Test if the expected tape is returned when the end position is requested in a tape +# that has state preparation""" +# tape = add_noise( +# self.tape_with_prep, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end" +# ) +# +# with QuantumTape() as tape_exp: +# qml.QubitStateVector([1, 0], wires=0) +# qml.RX(0.9, wires=0) +# qml.RY(0.4, wires=1) +# qml.CNOT(wires=[0, 1]) +# qml.RY(0.5, wires=0) +# qml.RX(0.6, wires=1) +# qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=0) +# qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=1) +# qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) +# +# assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) +# assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) +# assert all( +# np.allclose(o1.parameters, o2.parameters) +# for o1, o2 in zip(tape.operations, tape_exp.operations) +# ) +# assert len(tape.measurements) == 1 +# assert tape.observables[0].name == ["PauliZ", "PauliZ"] +# assert tape.observables[0].wires.tolist() == [0, 1] +# assert tape.measurements[0].return_type is Expectation +# +# def test_multiple_preparations(self): +# """Tests if a ValueError is raised when multiple state preparations are present in the +# tape""" +# with QuantumTape() as tape: +# qml.QubitStateVector([1, 0], wires=0) +# qml.QubitStateVector([0, 1], wires=1) +# with pytest.raises(ValueError, match="Only a single state preparation at the start of the"): +# add_noise(tape, qml.AmplitudeDamping, 0.4) +# +# +# def test_add_noise_integration(): +# """Test that a QNode with the add_noise decorator gives a different result than one +# without.""" +# dev = qml.device("default.mixed", wires=2) +# +# @qml.qnode(dev) +# @add_noise(qml.AmplitudeDamping, 0.2, position="end") +# def f_noisy(w, x, y, z): +# qml.RX(w, wires=0) +# qml.RY(x, wires=1) +# qml.CNOT(wires=[0, 1]) +# qml.RY(y, wires=0) +# qml.RX(z, wires=1) +# return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) +# +# @qml.qnode(dev) +# def f(w, x, y, z): +# qml.RX(w, wires=0) +# qml.RY(x, wires=1) +# qml.CNOT(wires=[0, 1]) +# qml.RY(y, wires=0) +# qml.RX(z, wires=1) +# return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) +# +# args = [0.1, 0.2, 0.3, 0.4] +# +# assert not np.isclose(f_noisy(*args), f(*args)) +# +# +# def test_add_noise_to_dev(mocker): +# """Test if a device transformed by the add_noise_to_dev does successfully add noise to +# subsequent circuit executions""" +# with QuantumTape() as in_tape: +# qml.RX(0.9, wires=0) +# qml.RY(0.4, wires=1) +# qml.CNOT(wires=[0, 1]) +# qml.RY(0.5, wires=0) +# qml.RX(0.6, wires=1) +# qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) +# +# dev = qml.device("default.mixed", wires=2) +# res_without_noise = qml.execute([in_tape], dev, qml.gradients.param_shift) +# +# spy = mocker.spy(dev, "expand_fn") +# +# add_noise_to_dev(dev, qml.PhaseDamping, 0.4) +# +# res_with_noise = qml.execute([in_tape], dev, qml.gradients.param_shift) +# tape = spy.call_args[0][0] +# +# with QuantumTape() as tape_exp: +# qml.RX(0.9, wires=0) +# qml.PhaseDamping(0.4, wires=0) +# qml.RY(0.4, wires=1) +# qml.PhaseDamping(0.4, wires=1) +# qml.CNOT(wires=[0, 1]) +# qml.PhaseDamping(0.4, wires=0) +# qml.PhaseDamping(0.4, wires=1) +# qml.RY(0.5, wires=0) +# qml.PhaseDamping(0.4, wires=0) +# qml.RX(0.6, wires=1) +# qml.PhaseDamping(0.4, wires=1) +# qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) +# +# assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) +# assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) +# assert all( +# np.allclose(o1.parameters, o2.parameters) +# for o1, o2 in zip(tape.operations, tape_exp.operations) +# ) +# assert len(tape.measurements) == 1 +# assert tape.observables[0].name == ["PauliZ", "PauliZ"] +# assert tape.observables[0].wires.tolist() == [0, 1] +# assert tape.measurements[0].return_type is Expectation +# +# assert not np.allclose(res_without_noise, res_with_noise) From de8594d7fce403306b92b3ab9db8a53fcc159898 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 25 Oct 2021 11:23:56 -0400 Subject: [PATCH 25/62] Revert qfunc changes --- pennylane/transforms/qfunc_transforms.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/pennylane/transforms/qfunc_transforms.py b/pennylane/transforms/qfunc_transforms.py index b4d8f3d752c..b631f39c963 100644 --- a/pennylane/transforms/qfunc_transforms.py +++ b/pennylane/transforms/qfunc_transforms.py @@ -177,8 +177,6 @@ def _create_qfunc_internal_wrapper(fn, tape_transform, transform_args, transform f"The qfunc to transform, {fn}, does not appear " "to be a valid Python function or callable." ) - if isinstance(fn, qml.tape.QuantumTape): - return tape_transform(fn, *transform_args, **transform_kwargs) @functools.wraps(fn) def internal_wrapper(*args, **kwargs): @@ -361,11 +359,11 @@ def new_qfunc(*args, **kwargs): the queueing logic required under steps (1) and (3), so that it does not need to be repeated and tested for every new qfunc transform. """ - # if not callable(tape_transform): - # raise ValueError( - # "The qfunc_transform decorator can only be applied " - # "to single tape transform functions." - # ) + if not callable(tape_transform): + raise ValueError( + "The qfunc_transform decorator can only be applied " + "to single tape transform functions." + ) if not isinstance(tape_transform, single_tape_transform): tape_transform = single_tape_transform(tape_transform) @@ -383,8 +381,7 @@ def wrapper(fn): wrapper.tape_fn = functools.partial(tape_transform, *targs, **tkwargs) return wrapper - # if tape_transform.__name__ == "add_noise": - # print(len(params)) + elif len(params) == 1: @functools.wraps(tape_transform) From 71a7075efa0b12178c547a9b216e0795403d23e6 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 25 Oct 2021 11:28:34 -0400 Subject: [PATCH 26/62] Update tests --- pennylane/transforms/noise.py | 2 +- tests/transforms/test_noise.py | 539 ++++++++++++++++----------------- 2 files changed, 270 insertions(+), 271 deletions(-) diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/noise.py index 9de287b37a6..8d923bb2a5c 100644 --- a/pennylane/transforms/noise.py +++ b/pennylane/transforms/noise.py @@ -226,7 +226,7 @@ def f(w, x, y, z): original_expand_fn = device.expand_fn def new_expand_fn(circuit, max_expansion=10): - new_tape = add_noise(circuit, noisy_op, noisy_op_args, position) + new_tape = add_noise.tape_fn(circuit, noisy_op, noisy_op_args, position) return original_expand_fn(new_tape, max_expansion=max_expansion) device.expand_fn = new_expand_fn diff --git a/tests/transforms/test_noise.py b/tests/transforms/test_noise.py index c74fa1aefb1..bcdda81f94a 100644 --- a/tests/transforms/test_noise.py +++ b/tests/transforms/test_noise.py @@ -24,7 +24,7 @@ class TestAddNoise: - """Tests for the add_noise function""" + """Tests for the add_noise function using input tapes""" with QuantumTape() as tape: qml.RX(0.9, wires=0) @@ -45,272 +45,271 @@ class TestAddNoise: def test_multiwire_noisy_op(self): """Tests if a ValueError is raised when multiqubit channels are requested""" - # with pytest.raises(ValueError, match="Adding noise to the tape is only"): - r = add_noise(self.tape, qml.QubitChannel, [])(self.tape) - # print(add_noise) - -# def test_invalid_position(self): -# """Test if a ValueError is raised when an invalid position is requested""" -# with pytest.raises(ValueError, match="Position must be either 'start', 'end', or 'all'"): -# add_noise(self.tape, qml.AmplitudeDamping, 0.4, position="ABC") -# -# def test_not_noisy(self): -# """Test if a ValueError is raised when something that is not a noisy channel is fed to the -# noisy_op argument""" -# with pytest.raises(ValueError, match="The noisy_op argument must be a noisy operation"): -# add_noise(self.tape, qml.PauliX, 0.4) -# -# def test_start(self): -# """Test if the expected tape is returned when the start position is requested""" -# tape = add_noise(self.tape, qml.AmplitudeDamping, 0.4, position="start") -# -# with QuantumTape() as tape_exp: -# qml.AmplitudeDamping(0.4, wires=0) -# qml.AmplitudeDamping(0.4, wires=1) -# qml.RX(0.9, wires=0) -# qml.RY(0.4, wires=1) -# qml.CNOT(wires=[0, 1]) -# qml.RY(0.5, wires=0) -# qml.RX(0.6, wires=1) -# qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) -# -# assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) -# assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) -# assert all( -# np.allclose(o1.parameters, o2.parameters) -# for o1, o2 in zip(tape.operations, tape_exp.operations) -# ) -# assert len(tape.measurements) == 1 -# assert tape.observables[0].name == ["PauliZ", "PauliZ"] -# assert tape.observables[0].wires.tolist() == [0, 1] -# assert tape.measurements[0].return_type is Expectation -# -# def test_all(self): -# """Test if the expected tape is returned when the all position is requested""" -# tape = add_noise(self.tape, qml.PhaseDamping, 0.4, position="all") -# -# with QuantumTape() as tape_exp: -# qml.RX(0.9, wires=0) -# qml.PhaseDamping(0.4, wires=0) -# qml.RY(0.4, wires=1) -# qml.PhaseDamping(0.4, wires=1) -# qml.CNOT(wires=[0, 1]) -# qml.PhaseDamping(0.4, wires=0) -# qml.PhaseDamping(0.4, wires=1) -# qml.RY(0.5, wires=0) -# qml.PhaseDamping(0.4, wires=0) -# qml.RX(0.6, wires=1) -# qml.PhaseDamping(0.4, wires=1) -# qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) -# -# assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) -# assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) -# assert all( -# np.allclose(o1.parameters, o2.parameters) -# for o1, o2 in zip(tape.operations, tape_exp.operations) -# ) -# assert len(tape.measurements) == 1 -# assert tape.observables[0].name == ["PauliZ", "PauliZ"] -# assert tape.observables[0].wires.tolist() == [0, 1] -# assert tape.measurements[0].return_type is Expectation -# -# def test_end(self): -# """Test if the expected tape is returned when the end position is requested""" -# tape = add_noise( -# self.tape, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end" -# ) -# -# with QuantumTape() as tape_exp: -# qml.RX(0.9, wires=0) -# qml.RY(0.4, wires=1) -# qml.CNOT(wires=[0, 1]) -# qml.RY(0.5, wires=0) -# qml.RX(0.6, wires=1) -# qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=0) -# qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=1) -# qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) -# -# assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) -# assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) -# assert all( -# np.allclose(o1.parameters, o2.parameters) -# for o1, o2 in zip(tape.operations, tape_exp.operations) -# ) -# assert len(tape.measurements) == 1 -# assert tape.observables[0].name == ["PauliZ", "PauliZ"] -# assert tape.observables[0].wires.tolist() == [0, 1] -# assert tape.measurements[0].return_type is Expectation -# -# def test_start_with_state_prep(self): -# """Test if the expected tape is returned when the start position is requested in a tape -# that has state preparation""" -# tape = add_noise(self.tape_with_prep, qml.AmplitudeDamping, 0.4, position="start") -# -# with QuantumTape() as tape_exp: -# qml.QubitStateVector([1, 0], wires=0) -# qml.AmplitudeDamping(0.4, wires=0) -# qml.AmplitudeDamping(0.4, wires=1) -# qml.RX(0.9, wires=0) -# qml.RY(0.4, wires=1) -# qml.CNOT(wires=[0, 1]) -# qml.RY(0.5, wires=0) -# qml.RX(0.6, wires=1) -# qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) -# -# assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) -# assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) -# assert all( -# np.allclose(o1.parameters, o2.parameters) -# for o1, o2 in zip(tape.operations, tape_exp.operations) -# ) -# assert len(tape.measurements) == 1 -# assert tape.observables[0].name == ["PauliZ", "PauliZ"] -# assert tape.observables[0].wires.tolist() == [0, 1] -# assert tape.measurements[0].return_type is Expectation -# -# def test_all_with_state_prep(self): -# """Test if the expected tape is returned when the all position is requested in a tape -# that has state preparation""" -# tape = add_noise(self.tape_with_prep, qml.PhaseDamping, 0.4, position="all") -# -# with QuantumTape() as tape_exp: -# qml.QubitStateVector([1, 0], wires=0) -# qml.RX(0.9, wires=0) -# qml.PhaseDamping(0.4, wires=0) -# qml.RY(0.4, wires=1) -# qml.PhaseDamping(0.4, wires=1) -# qml.CNOT(wires=[0, 1]) -# qml.PhaseDamping(0.4, wires=0) -# qml.PhaseDamping(0.4, wires=1) -# qml.RY(0.5, wires=0) -# qml.PhaseDamping(0.4, wires=0) -# qml.RX(0.6, wires=1) -# qml.PhaseDamping(0.4, wires=1) -# qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) -# -# assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) -# assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) -# assert all( -# np.allclose(o1.parameters, o2.parameters) -# for o1, o2 in zip(tape.operations, tape_exp.operations) -# ) -# assert len(tape.measurements) == 1 -# assert tape.observables[0].name == ["PauliZ", "PauliZ"] -# assert tape.observables[0].wires.tolist() == [0, 1] -# assert tape.measurements[0].return_type is Expectation -# -# def test_end_with_state_prep(self): -# """Test if the expected tape is returned when the end position is requested in a tape -# that has state preparation""" -# tape = add_noise( -# self.tape_with_prep, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end" -# ) -# -# with QuantumTape() as tape_exp: -# qml.QubitStateVector([1, 0], wires=0) -# qml.RX(0.9, wires=0) -# qml.RY(0.4, wires=1) -# qml.CNOT(wires=[0, 1]) -# qml.RY(0.5, wires=0) -# qml.RX(0.6, wires=1) -# qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=0) -# qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=1) -# qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) -# -# assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) -# assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) -# assert all( -# np.allclose(o1.parameters, o2.parameters) -# for o1, o2 in zip(tape.operations, tape_exp.operations) -# ) -# assert len(tape.measurements) == 1 -# assert tape.observables[0].name == ["PauliZ", "PauliZ"] -# assert tape.observables[0].wires.tolist() == [0, 1] -# assert tape.measurements[0].return_type is Expectation -# -# def test_multiple_preparations(self): -# """Tests if a ValueError is raised when multiple state preparations are present in the -# tape""" -# with QuantumTape() as tape: -# qml.QubitStateVector([1, 0], wires=0) -# qml.QubitStateVector([0, 1], wires=1) -# with pytest.raises(ValueError, match="Only a single state preparation at the start of the"): -# add_noise(tape, qml.AmplitudeDamping, 0.4) -# -# -# def test_add_noise_integration(): -# """Test that a QNode with the add_noise decorator gives a different result than one -# without.""" -# dev = qml.device("default.mixed", wires=2) -# -# @qml.qnode(dev) -# @add_noise(qml.AmplitudeDamping, 0.2, position="end") -# def f_noisy(w, x, y, z): -# qml.RX(w, wires=0) -# qml.RY(x, wires=1) -# qml.CNOT(wires=[0, 1]) -# qml.RY(y, wires=0) -# qml.RX(z, wires=1) -# return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) -# -# @qml.qnode(dev) -# def f(w, x, y, z): -# qml.RX(w, wires=0) -# qml.RY(x, wires=1) -# qml.CNOT(wires=[0, 1]) -# qml.RY(y, wires=0) -# qml.RX(z, wires=1) -# return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) -# -# args = [0.1, 0.2, 0.3, 0.4] -# -# assert not np.isclose(f_noisy(*args), f(*args)) -# -# -# def test_add_noise_to_dev(mocker): -# """Test if a device transformed by the add_noise_to_dev does successfully add noise to -# subsequent circuit executions""" -# with QuantumTape() as in_tape: -# qml.RX(0.9, wires=0) -# qml.RY(0.4, wires=1) -# qml.CNOT(wires=[0, 1]) -# qml.RY(0.5, wires=0) -# qml.RX(0.6, wires=1) -# qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) -# -# dev = qml.device("default.mixed", wires=2) -# res_without_noise = qml.execute([in_tape], dev, qml.gradients.param_shift) -# -# spy = mocker.spy(dev, "expand_fn") -# -# add_noise_to_dev(dev, qml.PhaseDamping, 0.4) -# -# res_with_noise = qml.execute([in_tape], dev, qml.gradients.param_shift) -# tape = spy.call_args[0][0] -# -# with QuantumTape() as tape_exp: -# qml.RX(0.9, wires=0) -# qml.PhaseDamping(0.4, wires=0) -# qml.RY(0.4, wires=1) -# qml.PhaseDamping(0.4, wires=1) -# qml.CNOT(wires=[0, 1]) -# qml.PhaseDamping(0.4, wires=0) -# qml.PhaseDamping(0.4, wires=1) -# qml.RY(0.5, wires=0) -# qml.PhaseDamping(0.4, wires=0) -# qml.RX(0.6, wires=1) -# qml.PhaseDamping(0.4, wires=1) -# qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) -# -# assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) -# assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) -# assert all( -# np.allclose(o1.parameters, o2.parameters) -# for o1, o2 in zip(tape.operations, tape_exp.operations) -# ) -# assert len(tape.measurements) == 1 -# assert tape.observables[0].name == ["PauliZ", "PauliZ"] -# assert tape.observables[0].wires.tolist() == [0, 1] -# assert tape.measurements[0].return_type is Expectation -# -# assert not np.allclose(res_without_noise, res_with_noise) + with pytest.raises(ValueError, match="Adding noise to the circuit is only"): + add_noise.tape_fn(self.tape, qml.QubitChannel, []) + + def test_invalid_position(self): + """Test if a ValueError is raised when an invalid position is requested""" + with pytest.raises(ValueError, match="Position must be either 'start', 'end', or 'all'"): + add_noise.tape_fn(self.tape, qml.AmplitudeDamping, 0.4, position="ABC") + + def test_not_noisy(self): + """Test if a ValueError is raised when something that is not a noisy channel is fed to the + noisy_op argument""" + with pytest.raises(ValueError, match="The noisy_op argument must be a noisy operation"): + add_noise.tape_fn(self.tape, qml.PauliX, 0.4) + + def test_start(self): + """Test if the expected tape is returned when the start position is requested""" + tape = add_noise.tape_fn(self.tape, qml.AmplitudeDamping, 0.4, position="start") + + with QuantumTape() as tape_exp: + qml.AmplitudeDamping(0.4, wires=0) + qml.AmplitudeDamping(0.4, wires=1) + qml.RX(0.9, wires=0) + qml.RY(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(0.5, wires=0) + qml.RX(0.6, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all( + np.allclose(o1.parameters, o2.parameters) + for o1, o2 in zip(tape.operations, tape_exp.operations) + ) + assert len(tape.measurements) == 1 + assert tape.observables[0].name == ["PauliZ", "PauliZ"] + assert tape.observables[0].wires.tolist() == [0, 1] + assert tape.measurements[0].return_type is Expectation + + def test_all(self): + """Test if the expected tape is returned when the all position is requested""" + tape = add_noise.tape_fn(self.tape, qml.PhaseDamping, 0.4, position="all") + + with QuantumTape() as tape_exp: + qml.RX(0.9, wires=0) + qml.PhaseDamping(0.4, wires=0) + qml.RY(0.4, wires=1) + qml.PhaseDamping(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.PhaseDamping(0.4, wires=0) + qml.PhaseDamping(0.4, wires=1) + qml.RY(0.5, wires=0) + qml.PhaseDamping(0.4, wires=0) + qml.RX(0.6, wires=1) + qml.PhaseDamping(0.4, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all( + np.allclose(o1.parameters, o2.parameters) + for o1, o2 in zip(tape.operations, tape_exp.operations) + ) + assert len(tape.measurements) == 1 + assert tape.observables[0].name == ["PauliZ", "PauliZ"] + assert tape.observables[0].wires.tolist() == [0, 1] + assert tape.measurements[0].return_type is Expectation + + def test_end(self): + """Test if the expected tape is returned when the end position is requested""" + tape = add_noise.tape_fn( + self.tape, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end" + ) + + with QuantumTape() as tape_exp: + qml.RX(0.9, wires=0) + qml.RY(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(0.5, wires=0) + qml.RX(0.6, wires=1) + qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=0) + qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all( + np.allclose(o1.parameters, o2.parameters) + for o1, o2 in zip(tape.operations, tape_exp.operations) + ) + assert len(tape.measurements) == 1 + assert tape.observables[0].name == ["PauliZ", "PauliZ"] + assert tape.observables[0].wires.tolist() == [0, 1] + assert tape.measurements[0].return_type is Expectation + + def test_start_with_state_prep(self): + """Test if the expected tape is returned when the start position is requested in a tape + that has state preparation""" + tape = add_noise.tape_fn(self.tape_with_prep, qml.AmplitudeDamping, 0.4, position="start") + + with QuantumTape() as tape_exp: + qml.QubitStateVector([1, 0], wires=0) + qml.AmplitudeDamping(0.4, wires=0) + qml.AmplitudeDamping(0.4, wires=1) + qml.RX(0.9, wires=0) + qml.RY(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(0.5, wires=0) + qml.RX(0.6, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all( + np.allclose(o1.parameters, o2.parameters) + for o1, o2 in zip(tape.operations, tape_exp.operations) + ) + assert len(tape.measurements) == 1 + assert tape.observables[0].name == ["PauliZ", "PauliZ"] + assert tape.observables[0].wires.tolist() == [0, 1] + assert tape.measurements[0].return_type is Expectation + + def test_all_with_state_prep(self): + """Test if the expected tape is returned when the all position is requested in a tape + that has state preparation""" + tape = add_noise.tape_fn(self.tape_with_prep, qml.PhaseDamping, 0.4, position="all") + + with QuantumTape() as tape_exp: + qml.QubitStateVector([1, 0], wires=0) + qml.RX(0.9, wires=0) + qml.PhaseDamping(0.4, wires=0) + qml.RY(0.4, wires=1) + qml.PhaseDamping(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.PhaseDamping(0.4, wires=0) + qml.PhaseDamping(0.4, wires=1) + qml.RY(0.5, wires=0) + qml.PhaseDamping(0.4, wires=0) + qml.RX(0.6, wires=1) + qml.PhaseDamping(0.4, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all( + np.allclose(o1.parameters, o2.parameters) + for o1, o2 in zip(tape.operations, tape_exp.operations) + ) + assert len(tape.measurements) == 1 + assert tape.observables[0].name == ["PauliZ", "PauliZ"] + assert tape.observables[0].wires.tolist() == [0, 1] + assert tape.measurements[0].return_type is Expectation + + def test_end_with_state_prep(self): + """Test if the expected tape is returned when the end position is requested in a tape + that has state preparation""" + tape = add_noise.tape_fn( + self.tape_with_prep, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end" + ) + + with QuantumTape() as tape_exp: + qml.QubitStateVector([1, 0], wires=0) + qml.RX(0.9, wires=0) + qml.RY(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(0.5, wires=0) + qml.RX(0.6, wires=1) + qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=0) + qml.GeneralizedAmplitudeDamping(0.4, 0.5, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all( + np.allclose(o1.parameters, o2.parameters) + for o1, o2 in zip(tape.operations, tape_exp.operations) + ) + assert len(tape.measurements) == 1 + assert tape.observables[0].name == ["PauliZ", "PauliZ"] + assert tape.observables[0].wires.tolist() == [0, 1] + assert tape.measurements[0].return_type is Expectation + + def test_multiple_preparations(self): + """Tests if a ValueError is raised when multiple state preparations are present in the + tape""" + with QuantumTape() as tape: + qml.QubitStateVector([1, 0], wires=0) + qml.QubitStateVector([0, 1], wires=1) + with pytest.raises(ValueError, match="Only a single state preparation at the start of the"): + add_noise.tape_fn(tape, qml.AmplitudeDamping, 0.4) + + +def test_add_noise_integration(): + """Test that a QNode with the add_noise decorator gives a different result than one + without.""" + dev = qml.device("default.mixed", wires=2) + + @qml.qnode(dev) + @add_noise(qml.AmplitudeDamping, 0.2, position="end") + def f_noisy(w, x, y, z): + qml.RX(w, wires=0) + qml.RY(x, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(y, wires=0) + qml.RX(z, wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + @qml.qnode(dev) + def f(w, x, y, z): + qml.RX(w, wires=0) + qml.RY(x, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(y, wires=0) + qml.RX(z, wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + args = [0.1, 0.2, 0.3, 0.4] + + assert not np.isclose(f_noisy(*args), f(*args)) + + +def test_add_noise_to_dev(mocker): + """Test if a device transformed by the add_noise_to_dev does successfully add noise to + subsequent circuit executions""" + with QuantumTape() as in_tape: + qml.RX(0.9, wires=0) + qml.RY(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(0.5, wires=0) + qml.RX(0.6, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + dev = qml.device("default.mixed", wires=2) + res_without_noise = qml.execute([in_tape], dev, qml.gradients.param_shift) + + spy = mocker.spy(dev, "expand_fn") + + add_noise_to_dev(dev, qml.PhaseDamping, 0.4) + + res_with_noise = qml.execute([in_tape], dev, qml.gradients.param_shift) + tape = spy.call_args[0][0] + + with QuantumTape() as tape_exp: + qml.RX(0.9, wires=0) + qml.PhaseDamping(0.4, wires=0) + qml.RY(0.4, wires=1) + qml.PhaseDamping(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.PhaseDamping(0.4, wires=0) + qml.PhaseDamping(0.4, wires=1) + qml.RY(0.5, wires=0) + qml.PhaseDamping(0.4, wires=0) + qml.RX(0.6, wires=1) + qml.PhaseDamping(0.4, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all( + np.allclose(o1.parameters, o2.parameters) + for o1, o2 in zip(tape.operations, tape_exp.operations) + ) + assert len(tape.measurements) == 1 + assert tape.observables[0].name == ["PauliZ", "PauliZ"] + assert tape.observables[0].wires.tolist() == [0, 1] + assert tape.measurements[0].return_type is Expectation + + assert not np.allclose(res_without_noise, res_with_noise) From 7406919ac9034574e71560b70ef9a7ca90ec40f4 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 25 Oct 2021 12:35:14 -0400 Subject: [PATCH 27/62] Fix CI --- tests/transforms/test_noise.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/transforms/test_noise.py b/tests/transforms/test_noise.py index bcdda81f94a..ab77d3cd402 100644 --- a/tests/transforms/test_noise.py +++ b/tests/transforms/test_noise.py @@ -20,7 +20,7 @@ import pennylane as qml from pennylane.operation import Expectation from pennylane.tape import QuantumTape -from pennylane.transforms.noise import (add_noise, add_noise_to_dev) +from pennylane.transforms.noise import add_noise, add_noise_to_dev class TestAddNoise: From b4e512a25a0ec848b5ffe80ced3a2e3e049277e0 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 25 Oct 2021 12:42:06 -0400 Subject: [PATCH 28/62] Reword changelog --- doc/releases/changelog-dev.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 1219febd17d..aa168f7efdd 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -389,7 +389,7 @@

Improvements

* The `add_noise` and `add_noise_to_dev` transforms have now been added, - providing a way to add simple noise to a quantum circuit. + providing a way to insert simple noise into a quantum circuit. [(#1795)](https://github.com/PennyLaneAI/pennylane/pull/1795) The following QNode can be transformed to add noise to the circuit: From 27e466605698cf85309295a2154d9e5207e8e2a9 Mon Sep 17 00:00:00 2001 From: trbromley Date: Mon, 25 Oct 2021 12:53:34 -0400 Subject: [PATCH 29/62] Fix CI --- pennylane/transforms/noise.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/noise.py index 8d923bb2a5c..a7e5f03fb0f 100644 --- a/pennylane/transforms/noise.py +++ b/pennylane/transforms/noise.py @@ -21,8 +21,7 @@ from pennylane.operation import Channel from pennylane.ops.channel import __qubit_channels__ from pennylane.tape import QuantumTape -from pennylane.transforms.qfunc_transforms import (qfunc_transform, - single_tape_transform) +from pennylane.transforms.qfunc_transforms import qfunc_transform, single_tape_transform @qfunc_transform @@ -75,10 +74,10 @@ def add_noise( qml.RX(0.6, wires=1) qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - We can add the :class:`~.AmplitudeDamping` channel to the start of the circuit using: + We can add the :class:`~.AmplitudeDamping` channel to the end of the circuit using: >>> from pennylane.transforms import add_noise - >>> noisy_tape = add_noise(tape, qml.AmplitudeDamping, 0.05, position="end") + >>> noisy_tape = add_noise.tape_fn(tape, qml.AmplitudeDamping, 0.05, position="end") >>> print(noisy_tape.draw()) 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.05)──╭┤ ⟨Z ⊗ Z⟩ 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.05)──╰┤ ⟨Z ⊗ Z⟩ @@ -200,7 +199,7 @@ def add_noise_to_dev( from pennylane.beta import qnode - dev = qml.device("default.mixed", wires=4) + dev = qml.device("default.mixed", wires=2) @qnode(dev) def f(w, x, y, z): From 7777df6c83fd42d7c2ae5ff88d806144e2c3cd38 Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 07:18:33 -0400 Subject: [PATCH 30/62] Rename module from noise to insert --- pennylane/transforms/__init__.py | 2 +- pennylane/transforms/{noise.py => insert.py} | 0 tests/transforms/test_noise.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename pennylane/transforms/{noise.py => insert.py} (100%) diff --git a/pennylane/transforms/__init__.py b/pennylane/transforms/__init__.py index 03f4571b43b..1973f4351bc 100644 --- a/pennylane/transforms/__init__.py +++ b/pennylane/transforms/__init__.py @@ -129,7 +129,7 @@ from .hamiltonian_expand import hamiltonian_expand from .measurement_grouping import measurement_grouping from .metric_tensor import metric_tensor -from .noise import add_noise, add_noise_to_dev +from .insert import add_noise, add_noise_to_dev from .optimization import ( cancel_inverses, commute_controlled, diff --git a/pennylane/transforms/noise.py b/pennylane/transforms/insert.py similarity index 100% rename from pennylane/transforms/noise.py rename to pennylane/transforms/insert.py diff --git a/tests/transforms/test_noise.py b/tests/transforms/test_noise.py index ab77d3cd402..f7d91ffd207 100644 --- a/tests/transforms/test_noise.py +++ b/tests/transforms/test_noise.py @@ -20,7 +20,7 @@ import pennylane as qml from pennylane.operation import Expectation from pennylane.tape import QuantumTape -from pennylane.transforms.noise import add_noise, add_noise_to_dev +from pennylane.transforms.insert import add_noise, add_noise_to_dev class TestAddNoise: From dfb0cc13e8e551c5d6586d7d33ce53101e37f92e Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 07:19:33 -0400 Subject: [PATCH 31/62] Rename add_noise to insert --- doc/releases/changelog-dev.md | 6 +++--- pennylane/transforms/__init__.py | 4 ++-- pennylane/transforms/insert.py | 8 ++++---- tests/transforms/test_noise.py | 28 ++++++++++++++-------------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index aa168f7efdd..053c51c2e70 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -388,19 +388,19 @@

Improvements

-* The `add_noise` and `add_noise_to_dev` transforms have now been added, +* The `insert` and `add_noise_to_dev` transforms have now been added, providing a way to insert simple noise into a quantum circuit. [(#1795)](https://github.com/PennyLaneAI/pennylane/pull/1795) The following QNode can be transformed to add noise to the circuit: ```python - from pennylane.transforms import add_noise + from pennylane.transforms import insert dev = qml.device("default.mixed", wires=2) @qml.qnode(dev) - @add_noise(qml.AmplitudeDamping, 0.2, position="end") + @insert(qml.AmplitudeDamping, 0.2, position="end") def f(w, x, y, z): qml.RX(w, wires=0) qml.RY(x, wires=1) diff --git a/pennylane/transforms/__init__.py b/pennylane/transforms/__init__.py index 1973f4351bc..04a2c7a09d1 100644 --- a/pennylane/transforms/__init__.py +++ b/pennylane/transforms/__init__.py @@ -49,7 +49,7 @@ ~ctrl ~apply_controlled_Q ~quantum_monte_carlo - ~transforms.add_noise + ~transforms.insert Transforms for circuit compilation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -129,7 +129,7 @@ from .hamiltonian_expand import hamiltonian_expand from .measurement_grouping import measurement_grouping from .metric_tensor import metric_tensor -from .insert import add_noise, add_noise_to_dev +from .insert import insert, add_noise_to_dev from .optimization import ( cancel_inverses, commute_controlled, diff --git a/pennylane/transforms/insert.py b/pennylane/transforms/insert.py index a7e5f03fb0f..ca8d2f731ee 100644 --- a/pennylane/transforms/insert.py +++ b/pennylane/transforms/insert.py @@ -26,7 +26,7 @@ @qfunc_transform @single_tape_transform -def add_noise( +def insert( circuit: Union[callable, QuantumTape], noisy_op: Type[Channel], noisy_op_args: Union[tuple, float], @@ -76,8 +76,8 @@ def add_noise( We can add the :class:`~.AmplitudeDamping` channel to the end of the circuit using: - >>> from pennylane.transforms import add_noise - >>> noisy_tape = add_noise.tape_fn(tape, qml.AmplitudeDamping, 0.05, position="end") + >>> from pennylane.transforms import insert + >>> noisy_tape = insert.tape_fn(tape, qml.AmplitudeDamping, 0.05, position="end") >>> print(noisy_tape.draw()) 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.05)──╭┤ ⟨Z ⊗ Z⟩ 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.05)──╰┤ ⟨Z ⊗ Z⟩ @@ -225,7 +225,7 @@ def f(w, x, y, z): original_expand_fn = device.expand_fn def new_expand_fn(circuit, max_expansion=10): - new_tape = add_noise.tape_fn(circuit, noisy_op, noisy_op_args, position) + new_tape = insert.tape_fn(circuit, noisy_op, noisy_op_args, position) return original_expand_fn(new_tape, max_expansion=max_expansion) device.expand_fn = new_expand_fn diff --git a/tests/transforms/test_noise.py b/tests/transforms/test_noise.py index f7d91ffd207..8e22ba5f748 100644 --- a/tests/transforms/test_noise.py +++ b/tests/transforms/test_noise.py @@ -20,11 +20,11 @@ import pennylane as qml from pennylane.operation import Expectation from pennylane.tape import QuantumTape -from pennylane.transforms.insert import add_noise, add_noise_to_dev +from pennylane.transforms.insert import insert, add_noise_to_dev class TestAddNoise: - """Tests for the add_noise function using input tapes""" + """Tests for the insert function using input tapes""" with QuantumTape() as tape: qml.RX(0.9, wires=0) @@ -46,22 +46,22 @@ class TestAddNoise: def test_multiwire_noisy_op(self): """Tests if a ValueError is raised when multiqubit channels are requested""" with pytest.raises(ValueError, match="Adding noise to the circuit is only"): - add_noise.tape_fn(self.tape, qml.QubitChannel, []) + insert.tape_fn(self.tape, qml.QubitChannel, []) def test_invalid_position(self): """Test if a ValueError is raised when an invalid position is requested""" with pytest.raises(ValueError, match="Position must be either 'start', 'end', or 'all'"): - add_noise.tape_fn(self.tape, qml.AmplitudeDamping, 0.4, position="ABC") + insert.tape_fn(self.tape, qml.AmplitudeDamping, 0.4, position="ABC") def test_not_noisy(self): """Test if a ValueError is raised when something that is not a noisy channel is fed to the noisy_op argument""" with pytest.raises(ValueError, match="The noisy_op argument must be a noisy operation"): - add_noise.tape_fn(self.tape, qml.PauliX, 0.4) + insert.tape_fn(self.tape, qml.PauliX, 0.4) def test_start(self): """Test if the expected tape is returned when the start position is requested""" - tape = add_noise.tape_fn(self.tape, qml.AmplitudeDamping, 0.4, position="start") + tape = insert.tape_fn(self.tape, qml.AmplitudeDamping, 0.4, position="start") with QuantumTape() as tape_exp: qml.AmplitudeDamping(0.4, wires=0) @@ -86,7 +86,7 @@ def test_start(self): def test_all(self): """Test if the expected tape is returned when the all position is requested""" - tape = add_noise.tape_fn(self.tape, qml.PhaseDamping, 0.4, position="all") + tape = insert.tape_fn(self.tape, qml.PhaseDamping, 0.4, position="all") with QuantumTape() as tape_exp: qml.RX(0.9, wires=0) @@ -115,7 +115,7 @@ def test_all(self): def test_end(self): """Test if the expected tape is returned when the end position is requested""" - tape = add_noise.tape_fn( + tape = insert.tape_fn( self.tape, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end" ) @@ -143,7 +143,7 @@ def test_end(self): def test_start_with_state_prep(self): """Test if the expected tape is returned when the start position is requested in a tape that has state preparation""" - tape = add_noise.tape_fn(self.tape_with_prep, qml.AmplitudeDamping, 0.4, position="start") + tape = insert.tape_fn(self.tape_with_prep, qml.AmplitudeDamping, 0.4, position="start") with QuantumTape() as tape_exp: qml.QubitStateVector([1, 0], wires=0) @@ -170,7 +170,7 @@ def test_start_with_state_prep(self): def test_all_with_state_prep(self): """Test if the expected tape is returned when the all position is requested in a tape that has state preparation""" - tape = add_noise.tape_fn(self.tape_with_prep, qml.PhaseDamping, 0.4, position="all") + tape = insert.tape_fn(self.tape_with_prep, qml.PhaseDamping, 0.4, position="all") with QuantumTape() as tape_exp: qml.QubitStateVector([1, 0], wires=0) @@ -201,7 +201,7 @@ def test_all_with_state_prep(self): def test_end_with_state_prep(self): """Test if the expected tape is returned when the end position is requested in a tape that has state preparation""" - tape = add_noise.tape_fn( + tape = insert.tape_fn( self.tape_with_prep, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end" ) @@ -234,16 +234,16 @@ def test_multiple_preparations(self): qml.QubitStateVector([1, 0], wires=0) qml.QubitStateVector([0, 1], wires=1) with pytest.raises(ValueError, match="Only a single state preparation at the start of the"): - add_noise.tape_fn(tape, qml.AmplitudeDamping, 0.4) + insert.tape_fn(tape, qml.AmplitudeDamping, 0.4) def test_add_noise_integration(): - """Test that a QNode with the add_noise decorator gives a different result than one + """Test that a QNode with the insert decorator gives a different result than one without.""" dev = qml.device("default.mixed", wires=2) @qml.qnode(dev) - @add_noise(qml.AmplitudeDamping, 0.2, position="end") + @insert(qml.AmplitudeDamping, 0.2, position="end") def f_noisy(w, x, y, z): qml.RX(w, wires=0) qml.RY(x, wires=1) From f0854e0667d37c1a3319b77a4f7b7a7bb30820bb Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 07:20:54 -0400 Subject: [PATCH 32/62] Rename add_noise_to_dev to insert_in_dev --- doc/releases/changelog-dev.md | 2 +- pennylane/transforms/__init__.py | 4 ++-- pennylane/transforms/insert.py | 4 ++-- tests/transforms/test_noise.py | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 053c51c2e70..7ac2f268e5a 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -388,7 +388,7 @@

Improvements

-* The `insert` and `add_noise_to_dev` transforms have now been added, +* The `insert` and `insert_in_dev` transforms have now been added, providing a way to insert simple noise into a quantum circuit. [(#1795)](https://github.com/PennyLaneAI/pennylane/pull/1795) diff --git a/pennylane/transforms/__init__.py b/pennylane/transforms/__init__.py index 04a2c7a09d1..6b9c8b9840e 100644 --- a/pennylane/transforms/__init__.py +++ b/pennylane/transforms/__init__.py @@ -96,7 +96,7 @@ .. autosummary:: :toctree: api - ~transforms.add_noise_to_dev + ~transforms.insert_in_dev Decorators and utility functions -------------------------------- @@ -129,7 +129,7 @@ from .hamiltonian_expand import hamiltonian_expand from .measurement_grouping import measurement_grouping from .metric_tensor import metric_tensor -from .insert import insert, add_noise_to_dev +from .insert import insert, insert_in_dev from .optimization import ( cancel_inverses, commute_controlled, diff --git a/pennylane/transforms/insert.py b/pennylane/transforms/insert.py index ca8d2f731ee..63b586ae457 100644 --- a/pennylane/transforms/insert.py +++ b/pennylane/transforms/insert.py @@ -153,7 +153,7 @@ def f(w, x, y, z): apply(m) -def add_noise_to_dev( +def insert_in_dev( device: Device, noisy_op: Type[Channel], noisy_op_args: Union[tuple, float], @@ -217,7 +217,7 @@ def f(w, x, y, z): However, noise can be easily added to the device: - >>> qml.transforms.add_noise_to_dev(dev, qml.AmplitudeDamping, 0.2) + >>> qml.transforms.insert_in_dev(dev, qml.AmplitudeDamping, 0.2) >>> f(0.9, 0.4, 0.5, 0.6) tensor(0.72945434, requires_grad=True) """ diff --git a/tests/transforms/test_noise.py b/tests/transforms/test_noise.py index 8e22ba5f748..94b62f9cc42 100644 --- a/tests/transforms/test_noise.py +++ b/tests/transforms/test_noise.py @@ -20,7 +20,7 @@ import pennylane as qml from pennylane.operation import Expectation from pennylane.tape import QuantumTape -from pennylane.transforms.insert import insert, add_noise_to_dev +from pennylane.transforms.insert import insert, insert_in_dev class TestAddNoise: @@ -267,7 +267,7 @@ def f(w, x, y, z): def test_add_noise_to_dev(mocker): - """Test if a device transformed by the add_noise_to_dev does successfully add noise to + """Test if a device transformed by the insert_in_dev does successfully add noise to subsequent circuit executions""" with QuantumTape() as in_tape: qml.RX(0.9, wires=0) @@ -282,7 +282,7 @@ def test_add_noise_to_dev(mocker): spy = mocker.spy(dev, "expand_fn") - add_noise_to_dev(dev, qml.PhaseDamping, 0.4) + insert_in_dev(dev, qml.PhaseDamping, 0.4) res_with_noise = qml.execute([in_tape], dev, qml.gradients.param_shift) tape = spy.call_args[0][0] From 21cc485b284bbddc361eccc19d64698a61b0ab5d Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 08:19:02 -0400 Subject: [PATCH 33/62] Update docstrings --- pennylane/transforms/insert.py | 188 +++++++++++++++++++-------------- 1 file changed, 111 insertions(+), 77 deletions(-) diff --git a/pennylane/transforms/insert.py b/pennylane/transforms/insert.py index 63b586ae457..cd8cb41a37a 100644 --- a/pennylane/transforms/insert.py +++ b/pennylane/transforms/insert.py @@ -12,14 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Provides transforms for adding simple noise models to quantum circuits. +Provides transforms for inserting operations into quantum circuits. """ from collections.abc import Sequence from typing import Type, Union from pennylane import BasisState, Device, QubitStateVector, apply -from pennylane.operation import Channel -from pennylane.ops.channel import __qubit_channels__ +from pennylane.operation import Operation from pennylane.tape import QuantumTape from pennylane.transforms.qfunc_transforms import qfunc_transform, single_tape_transform @@ -28,72 +27,90 @@ @single_tape_transform def insert( circuit: Union[callable, QuantumTape], - noisy_op: Type[Channel], - noisy_op_args: Union[tuple, float], + op: Union[callable, Type[Operation]], + op_args: Union[tuple, float], position: str = "all", ) -> Union[callable, QuantumTape]: - """Add noisy operations to an input circuit. + """Insert an operation into specified points in an input circuit. - The circuit will be updated to have noisy gates, specified by the ``noisy_op`` argument, added - according to the positioning specified in the ``position`` argument. + The circuit will be updated to have the operation, specified by the ``op`` argument, added + according to the positioning specified in the ``position`` argument. Only single qubit + operations are permitted. + + The type of ``op`` can be either a single operation or a quantum function. A quantum function + can be used to specify a sequence of operations acting on a single qubit, see the usage details + for more information. Args: circuit (callable or QuantumTape): the input circuit - noisy_op (Type[Channel]): the noisy operation to be added at positions within the circuit - noisy_op_args (tuple or float): the arguments fed to the noisy operation, or a single float - specifying the noise strength - position (str): Specification of where to add noise. Should be one of: ``"all"`` to add - the noisy operation after all gates; ``"start"`` to add the noisy operation to all wires - at the start of the circuit; ``"end"`` to add the noisy operation to all wires at the + op (callable or Type[Operation]): the single-qubit operation, or sequence of operations + acting on a single qubit, to be inserted into the circuit + op_args (tuple or float): the arguments fed to the operation, either as a tuple or a single + float + position (str): Specification of where to add the operation. Should be one of: ``"all"`` to + add the operation after all gates; ``"start"`` to add the operation to all wires + at the start of the circuit; ``"end"`` to add the operation to all wires at the end of the circuit. Returns: - callable or QuantumTape: a noisy version of the input circuit + callable or QuantumTape: the updated version of the input circuit Raises: - ValueError: if the noisy operation passed in ``noisy_op`` applies to more than one wire + ValueError: if a single multiqubit operation is passed to ``op`` ValueError: if the requested ``position`` argument is now ``'start'``, ``'end'`` or ``'all'`` - ValueError: if the noisy operation passed in ``noisy_op`` is not a noisy channel ValueError: if more than one state preparation is present in the circuit, or if the preparation is not at the start of the circuit - .. UsageDetails:: + **Example:** - **Transforming tapes:** + The following QNode can be transformed to add noise to the circuit: - Consider the following tape: + .. code-block:: python3 - .. code-block:: python3 + from pennylane.transforms import insert - with qml.tape.QuantumTape() as tape: - qml.RX(0.9, wires=0) - qml.RY(0.4, wires=1) - qml.CNOT(wires=[0, 1]) - qml.RY(0.5, wires=0) - qml.RX(0.6, wires=1) - qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + dev = qml.device("default.mixed", wires=2) - We can add the :class:`~.AmplitudeDamping` channel to the end of the circuit using: + @qml.qnode(dev) + @insert(qml.AmplitudeDamping, 0.2, position="end") + def f(w, x, y, z): + qml.RX(w, wires=0) + qml.RY(x, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(y, wires=0) + qml.RX(z, wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - >>> from pennylane.transforms import insert - >>> noisy_tape = insert.tape_fn(tape, qml.AmplitudeDamping, 0.05, position="end") - >>> print(noisy_tape.draw()) - 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.05)──╭┤ ⟨Z ⊗ Z⟩ - 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.05)──╰┤ ⟨Z ⊗ Z⟩ + Executions of this circuit will differ from the noise-free value: + + >>> f(0.9, 0.4, 0.5, 0.6) + tensor(0.754847, requires_grad=True) + >>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6)) + 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.2)──╭┤ ⟨Z ⊗ Z⟩ + 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.2)──╰┤ ⟨Z ⊗ Z⟩ + + .. UsageDetails:: - **Transforming QNodes:** + **Specifying the operation as a quantum function:** - The following QNode can be transformed to add noise to the circuit: + Instead of specifying ``op`` as a single :class:`~.Operation`, we can instead define a + quantum function. For example: .. code-block:: python3 - from pennylane.transforms import add_noise + def op(x, y, wires): + qml.RX(x, wires=wires) + qml.PhaseShift(y, wires=wires) - dev = qml.device("default.mixed", wires=2) + This operation can be inserted into the following circuit: + + .. code-block:: python3 + + dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) - @add_noise(qml.AmplitudeDamping, 0.2, position="end") + @insert(op, [0.2, 0.3], position="end") def f(w, x, y, z): qml.RX(w, wires=0) qml.RY(x, wires=1) @@ -102,27 +119,41 @@ def f(w, x, y, z): qml.RX(z, wires=1) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - Executions of this circuit will differ from the noise-free value: + To check this, let's print out the circuit: - >>> f(0.9, 0.4, 0.5, 0.6) - tensor(0.754847, requires_grad=True) >>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6)) - 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.2)──╭┤ ⟨Z ⊗ Z⟩ - 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.2)──╰┤ ⟨Z ⊗ Z⟩ + + **Transforming tapes:** + + Consider the following tape: + + .. code-block:: python3 + + with qml.tape.QuantumTape() as tape: + qml.RX(0.9, wires=0) + qml.RY(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(0.5, wires=0) + qml.RX(0.6, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + We can add the :class:`~.AmplitudeDamping` channel to the end of the circuit using: + + >>> from pennylane.transforms import insert + >>> noisy_tape = insert.tape_fn(tape, qml.AmplitudeDamping, 0.05, position="end") + >>> print(noisy_tape.draw()) + 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.05)──╭┤ ⟨Z ⊗ Z⟩ + 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.05)──╰┤ ⟨Z ⊗ Z⟩ """ - if noisy_op.num_wires != 1: + if isinstance(op, Operation) and op.num_wires != 1: raise ValueError( - "Adding noise to the circuit is only supported for single-qubit noisy operations" + "Only single-qubit operations can be inserted into the circuit" ) if position not in ("start", "end", "all"): raise ValueError("Position must be either 'start', 'end', or 'all' (default)") - if noisy_op.__name__ not in __qubit_channels__: - raise ValueError( - "The noisy_op argument must be a noisy operation such as qml.AmplitudeDamping" - ) - if not isinstance(noisy_op_args, Sequence): - noisy_op_args = [noisy_op_args] + if not isinstance(op_args, Sequence): + op_args = [op_args] preps = tuple(isinstance(o, (QubitStateVector, BasisState)) for o in circuit.operations) valid_preps = sum(preps) == 1 and preps[0] is True or sum(preps) == 0 @@ -137,17 +168,17 @@ def f(w, x, y, z): if position == "start": for w in circuit.wires: - noisy_op(*noisy_op_args, wires=w) + op(*op_args, wires=w) - for op in circuit.operations[start_pos:]: - apply(op) + for circuit_op in circuit.operations[start_pos:]: + apply(circuit_op) if position == "all": - for w in op.wires: - noisy_op(*noisy_op_args, wires=w) + for w in circuit_op.wires: + op(*op_args, wires=w) if position == "end": for w in circuit.wires: - noisy_op(*noisy_op_args, wires=w) + op(*op_args, wires=w) for m in circuit.measurements: apply(m) @@ -155,18 +186,18 @@ def f(w, x, y, z): def insert_in_dev( device: Device, - noisy_op: Type[Channel], - noisy_op_args: Union[tuple, float], + op: Union[callable, Type[Operation]], + op_args: Union[tuple, float], position: str = "all", ): - """Add noisy operations to an input device. + """Insert an operation into specified points in circuits during device execution. - After applying this transform, circuits executed on the device will have noisy gates added. - The gates are specified by the ``noisy_op`` argument and positioned according to the - ``position`` argument. The device is transformed in-place. + After applying this transform, circuits executed on the device will have operations inserted. + The operations are specified by the ``op`` argument and positioned according to the + ``position`` argument. Only single qubit operations are permitted. - This transform is only compatible with devices that support noisy operations (of type - :class:`~.Channel`), such as ``default.mixed``. + The type of ``op`` can be either a single operation or a quantum function. A quantum function + can be used to specify a sequence of operations acting on a single qubit. .. warning:: @@ -175,21 +206,24 @@ def insert_in_dev( Args: device (Device): the device to be transformed - noisy_op (Type[Channel]): the noisy operation to be added at positions within the function - noisy_op_args (tuple or float): the arguments fed to the noisy operation, or a single float - specifying the noise strength - position (str): Specification of where to add noise. Should be one of: ``"all"`` to add - the noisy operation after all gates; ``"start"`` to add the noisy operation to all wires - at the start of the circuit; ``"end"`` to add the noisy operation to all wires at the + op (callable or Type[Operation]): the single-qubit operation, or sequence of operations + acting on a single qubit, to be inserted into circuits + op_args (tuple or float): the arguments fed to the operation, either as a tuple or a single + float + position (str): Specification of where to add the operation. Should be one of: ``"all"`` to + add the operation after all gates; ``"start"`` to add the operation to all wires + at the start of the circuit; ``"end"`` to add the operation to all wires at the end of the circuit. + Returns: + Device: the updated device + Raises: - ValueError: if the noisy operation passed in ``noisy_op`` applies to more than one wire + ValueError: if a single multiqubit operation is passed to ``op`` ValueError: if the requested ``position`` argument is now ``'start'``, ``'end'`` or ``'all'`` - ValueError: if the noisy operation passed in ``noisy_op`` is not a noisy channel - ValueError: if more than one state preparation is present in the function, or if the - preparation is not at the start of the function + ValueError: if more than one state preparation is present in the circuit, or if the + preparation is not at the start of the circuit **Example:** @@ -225,7 +259,7 @@ def f(w, x, y, z): original_expand_fn = device.expand_fn def new_expand_fn(circuit, max_expansion=10): - new_tape = insert.tape_fn(circuit, noisy_op, noisy_op_args, position) + new_tape = insert.tape_fn(circuit, op, op_args, position) return original_expand_fn(new_tape, max_expansion=max_expansion) device.expand_fn = new_expand_fn From 7989ed08bb439bd792c35b2cf9774b8ec4a150eb Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 08:19:21 -0400 Subject: [PATCH 34/62] Rename test --- tests/transforms/{test_noise.py => test_insert.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/transforms/{test_noise.py => test_insert.py} (100%) diff --git a/tests/transforms/test_noise.py b/tests/transforms/test_insert.py similarity index 100% rename from tests/transforms/test_noise.py rename to tests/transforms/test_insert.py From ca834bf074bcd67e47cc27b0998272546d3f56b2 Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 08:27:41 -0400 Subject: [PATCH 35/62] Fix tests --- pennylane/transforms/insert.py | 3 ++- tests/transforms/test_insert.py | 18 ++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/pennylane/transforms/insert.py b/pennylane/transforms/insert.py index cd8cb41a37a..9d6d89d7e40 100644 --- a/pennylane/transforms/insert.py +++ b/pennylane/transforms/insert.py @@ -15,6 +15,7 @@ Provides transforms for inserting operations into quantum circuits. """ from collections.abc import Sequence +from types import FunctionType from typing import Type, Union from pennylane import BasisState, Device, QubitStateVector, apply @@ -145,7 +146,7 @@ def f(w, x, y, z): 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.05)──╭┤ ⟨Z ⊗ Z⟩ 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.05)──╰┤ ⟨Z ⊗ Z⟩ """ - if isinstance(op, Operation) and op.num_wires != 1: + if not isinstance(op, FunctionType) and op.num_wires != 1: raise ValueError( "Only single-qubit operations can be inserted into the circuit" ) diff --git a/tests/transforms/test_insert.py b/tests/transforms/test_insert.py index 94b62f9cc42..b110d8688bc 100644 --- a/tests/transforms/test_insert.py +++ b/tests/transforms/test_insert.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Tests for the noise-adding transforms. +Tests for the insert transforms. """ import numpy as np import pytest @@ -23,7 +23,7 @@ from pennylane.transforms.insert import insert, insert_in_dev -class TestAddNoise: +class TestInsert: """Tests for the insert function using input tapes""" with QuantumTape() as tape: @@ -45,20 +45,14 @@ class TestAddNoise: def test_multiwire_noisy_op(self): """Tests if a ValueError is raised when multiqubit channels are requested""" - with pytest.raises(ValueError, match="Adding noise to the circuit is only"): - insert.tape_fn(self.tape, qml.QubitChannel, []) + with pytest.raises(ValueError, match="Only single-qubit operations can be inserted into"): + insert.tape_fn(self.tape, qml.CNOT, []) def test_invalid_position(self): """Test if a ValueError is raised when an invalid position is requested""" with pytest.raises(ValueError, match="Position must be either 'start', 'end', or 'all'"): insert.tape_fn(self.tape, qml.AmplitudeDamping, 0.4, position="ABC") - def test_not_noisy(self): - """Test if a ValueError is raised when something that is not a noisy channel is fed to the - noisy_op argument""" - with pytest.raises(ValueError, match="The noisy_op argument must be a noisy operation"): - insert.tape_fn(self.tape, qml.PauliX, 0.4) - def test_start(self): """Test if the expected tape is returned when the start position is requested""" tape = insert.tape_fn(self.tape, qml.AmplitudeDamping, 0.4, position="start") @@ -237,7 +231,7 @@ def test_multiple_preparations(self): insert.tape_fn(tape, qml.AmplitudeDamping, 0.4) -def test_add_noise_integration(): +def test_insert_integration(): """Test that a QNode with the insert decorator gives a different result than one without.""" dev = qml.device("default.mixed", wires=2) @@ -266,7 +260,7 @@ def f(w, x, y, z): assert not np.isclose(f_noisy(*args), f(*args)) -def test_add_noise_to_dev(mocker): +def test_insert_in_dev(mocker): """Test if a device transformed by the insert_in_dev does successfully add noise to subsequent circuit executions""" with QuantumTape() as in_tape: From fad9028dc0496aa9c1ee3b906b9801a173d26bd8 Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 08:36:32 -0400 Subject: [PATCH 36/62] Polish docstrings --- pennylane/transforms/insert.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pennylane/transforms/insert.py b/pennylane/transforms/insert.py index 9d6d89d7e40..61f70369bfd 100644 --- a/pennylane/transforms/insert.py +++ b/pennylane/transforms/insert.py @@ -57,8 +57,8 @@ def insert( callable or QuantumTape: the updated version of the input circuit Raises: - ValueError: if a single multiqubit operation is passed to ``op`` - ValueError: if the requested ``position`` argument is now ``'start'``, ``'end'`` or + ValueError: if a single operation acting on multiple wires is passed to ``op`` + ValueError: if the requested ``position`` argument is not ``'start'``, ``'end'`` or ``'all'`` ValueError: if more than one state preparation is present in the circuit, or if the preparation is not at the start of the circuit @@ -69,12 +69,10 @@ def insert( .. code-block:: python3 - from pennylane.transforms import insert - dev = qml.device("default.mixed", wires=2) @qml.qnode(dev) - @insert(qml.AmplitudeDamping, 0.2, position="end") + @qml.transforms.insert(qml.AmplitudeDamping, 0.2, position="end") def f(w, x, y, z): qml.RX(w, wires=0) qml.RY(x, wires=1) @@ -123,6 +121,8 @@ def f(w, x, y, z): To check this, let's print out the circuit: >>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6)) + 0: ──RX(0.9)──╭C──RY(0.5)──RX(0.2)──Rϕ(0.3)──╭┤ ⟨Z ⊗ Z⟩ + 1: ──RY(0.4)──╰X──RX(0.6)──RX(0.2)──Rϕ(0.3)──╰┤ ⟨Z ⊗ Z⟩ **Transforming tapes:** @@ -220,8 +220,8 @@ def insert_in_dev( Device: the updated device Raises: - ValueError: if a single multiqubit operation is passed to ``op`` - ValueError: if the requested ``position`` argument is now ``'start'``, ``'end'`` or + ValueError: if a single operation acting on multiple wires is passed to ``op`` + ValueError: if the requested ``position`` argument is not ``'start'``, ``'end'`` or ``'all'`` ValueError: if more than one state preparation is present in the circuit, or if the preparation is not at the start of the circuit From 9fadacdbef5e1fda2f11f18c82eda3154ae5c34b Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 08:49:30 -0400 Subject: [PATCH 37/62] Update to not modify dev in place --- pennylane/transforms/insert.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pennylane/transforms/insert.py b/pennylane/transforms/insert.py index 61f70369bfd..e766814a3cc 100644 --- a/pennylane/transforms/insert.py +++ b/pennylane/transforms/insert.py @@ -15,6 +15,7 @@ Provides transforms for inserting operations into quantum circuits. """ from collections.abc import Sequence +from copy import deepcopy from types import FunctionType from typing import Type, Union @@ -190,7 +191,7 @@ def insert_in_dev( op: Union[callable, Type[Operation]], op_args: Union[tuple, float], position: str = "all", -): +) -> Device: """Insert an operation into specified points in circuits during device execution. After applying this transform, circuits executed on the device will have operations inserted. @@ -257,10 +258,14 @@ def f(w, x, y, z): tensor(0.72945434, requires_grad=True) """ # TODO: Remove warning in docstrings once new QNode replaces the old - original_expand_fn = device.expand_fn + + new_dev = deepcopy(device) + original_expand_fn = new_dev.expand_fn def new_expand_fn(circuit, max_expansion=10): new_tape = insert.tape_fn(circuit, op, op_args, position) return original_expand_fn(new_tape, max_expansion=max_expansion) - device.expand_fn = new_expand_fn + new_dev.expand_fn = new_expand_fn + + return new_dev From 64c2cc2d48eabeb2cb5106ada19cb3b81942c47a Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 09:13:23 -0400 Subject: [PATCH 38/62] Fix test --- pennylane/transforms/__init__.py | 2 +- .../transforms/{insert.py => insert_ops.py} | 0 tests/transforms/test_insert.py | 18 +++++++++++++----- 3 files changed, 14 insertions(+), 6 deletions(-) rename pennylane/transforms/{insert.py => insert_ops.py} (100%) diff --git a/pennylane/transforms/__init__.py b/pennylane/transforms/__init__.py index 6b9c8b9840e..c1cba208c1e 100644 --- a/pennylane/transforms/__init__.py +++ b/pennylane/transforms/__init__.py @@ -129,7 +129,7 @@ from .hamiltonian_expand import hamiltonian_expand from .measurement_grouping import measurement_grouping from .metric_tensor import metric_tensor -from .insert import insert, insert_in_dev +from .insert_ops import insert, insert_in_dev from .optimization import ( cancel_inverses, commute_controlled, diff --git a/pennylane/transforms/insert.py b/pennylane/transforms/insert_ops.py similarity index 100% rename from pennylane/transforms/insert.py rename to pennylane/transforms/insert_ops.py diff --git a/tests/transforms/test_insert.py b/tests/transforms/test_insert.py index b110d8688bc..d13e6eeb077 100644 --- a/tests/transforms/test_insert.py +++ b/tests/transforms/test_insert.py @@ -14,13 +14,15 @@ """ Tests for the insert transforms. """ +from copy import deepcopy import numpy as np import pytest import pennylane as qml from pennylane.operation import Expectation from pennylane.tape import QuantumTape -from pennylane.transforms.insert import insert, insert_in_dev +from pennylane.transforms.insert_ops import insert, insert_in_dev +import pennylane.transforms.insert_ops class TestInsert: @@ -260,7 +262,7 @@ def f(w, x, y, z): assert not np.isclose(f_noisy(*args), f(*args)) -def test_insert_in_dev(mocker): +def test_insert_in_dev(mocker, monkeypatch): """Test if a device transformed by the insert_in_dev does successfully add noise to subsequent circuit executions""" with QuantumTape() as in_tape: @@ -274,11 +276,17 @@ def test_insert_in_dev(mocker): dev = qml.device("default.mixed", wires=2) res_without_noise = qml.execute([in_tape], dev, qml.gradients.param_shift) - spy = mocker.spy(dev, "expand_fn") + def patched_deepcopy(dev): + global spy + copied = deepcopy(dev) + spy = mocker.spy(copied, "expand_fn") + return copied - insert_in_dev(dev, qml.PhaseDamping, 0.4) + with monkeypatch.context() as m: + m.setattr(pennylane.transforms.insert_ops, "deepcopy", patched_deepcopy) + new_dev = insert_in_dev(dev, qml.PhaseDamping, 0.4) - res_with_noise = qml.execute([in_tape], dev, qml.gradients.param_shift) + res_with_noise = qml.execute([in_tape], new_dev, qml.gradients.param_shift) tape = spy.call_args[0][0] with QuantumTape() as tape_exp: From c9517faea0732baeff21b7534d04a0fa41936d42 Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 09:15:27 -0400 Subject: [PATCH 39/62] Move from improvements to new features --- doc/releases/changelog-dev.md | 64 +++++++++++++++++------------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 3a5371b0529..397f2c79a81 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -4,6 +4,38 @@

New features since last release

+* The `insert` and `insert_in_dev` transforms have now been added, + providing a way to insert simple noise into a quantum circuit. + [(#1795)](https://github.com/PennyLaneAI/pennylane/pull/1795) + + The following QNode can be transformed to add noise to the circuit: + + ```python + from pennylane.transforms import insert + + dev = qml.device("default.mixed", wires=2) + + @qml.qnode(dev) + @insert(qml.AmplitudeDamping, 0.2, position="end") + def f(w, x, y, z): + qml.RX(w, wires=0) + qml.RY(x, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(y, wires=0) + qml.RX(z, wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + ``` + + Executions of this circuit will differ from the noise-free value: + + ```pycon + >>> f(0.9, 0.4, 0.5, 0.6) + tensor(0.754847, requires_grad=True) + >>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6)) + 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.2)──╭┤ ⟨Z ⊗ Z⟩ + 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.2)──╰┤ ⟨Z ⊗ Z⟩ + ``` + * Common tape expansion functions are now available in `qml.transforms`, alongside a new `create_expand_fn` function for easily creating expansion functions from stopping criteria. @@ -413,38 +445,6 @@

Improvements

-* The `insert` and `insert_in_dev` transforms have now been added, - providing a way to insert simple noise into a quantum circuit. - [(#1795)](https://github.com/PennyLaneAI/pennylane/pull/1795) - - The following QNode can be transformed to add noise to the circuit: - - ```python - from pennylane.transforms import insert - - dev = qml.device("default.mixed", wires=2) - - @qml.qnode(dev) - @insert(qml.AmplitudeDamping, 0.2, position="end") - def f(w, x, y, z): - qml.RX(w, wires=0) - qml.RY(x, wires=1) - qml.CNOT(wires=[0, 1]) - qml.RY(y, wires=0) - qml.RX(z, wires=1) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - ``` - - Executions of this circuit will differ from the noise-free value: - - ```pycon - >>> f(0.9, 0.4, 0.5, 0.6) - tensor(0.754847, requires_grad=True) - >>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6)) - 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.2)──╭┤ ⟨Z ⊗ Z⟩ - 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.2)──╰┤ ⟨Z ⊗ Z⟩ - ``` - * The `ApproxTimeEvolution` template can now be used with Hamiltonians that have trainable coefficients. [(#1789)](https://github.com/PennyLaneAI/pennylane/pull/1789) From c9ff0374fd1eaffae2b455154f38df5539595d61 Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 09:29:01 -0400 Subject: [PATCH 40/62] State prep update --- pennylane/transforms/insert_ops.py | 18 ++++-------------- tests/transforms/test_insert.py | 9 --------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/pennylane/transforms/insert_ops.py b/pennylane/transforms/insert_ops.py index e766814a3cc..a89dd6ec7c3 100644 --- a/pennylane/transforms/insert_ops.py +++ b/pennylane/transforms/insert_ops.py @@ -61,8 +61,6 @@ def insert( ValueError: if a single operation acting on multiple wires is passed to ``op`` ValueError: if the requested ``position`` argument is not ``'start'``, ``'end'`` or ``'all'`` - ValueError: if more than one state preparation is present in the circuit, or if the - preparation is not at the start of the circuit **Example:** @@ -157,22 +155,16 @@ def f(w, x, y, z): if not isinstance(op_args, Sequence): op_args = [op_args] - preps = tuple(isinstance(o, (QubitStateVector, BasisState)) for o in circuit.operations) - valid_preps = sum(preps) == 1 and preps[0] is True or sum(preps) == 0 - if not valid_preps: - raise ValueError("Only a single state preparation at the start of the circuit is supported") + num_preps = sum(isinstance(o, (QubitStateVector, BasisState)) for o in circuit.operations) - if sum(preps) == 1: - apply(circuit.operations[0]) - start_pos = 1 - else: - start_pos = 0 + for i in range(num_preps): + apply(circuit.operations[i]) if position == "start": for w in circuit.wires: op(*op_args, wires=w) - for circuit_op in circuit.operations[start_pos:]: + for circuit_op in circuit.operations[num_preps:]: apply(circuit_op) if position == "all": for w in circuit_op.wires: @@ -224,8 +216,6 @@ def insert_in_dev( ValueError: if a single operation acting on multiple wires is passed to ``op`` ValueError: if the requested ``position`` argument is not ``'start'``, ``'end'`` or ``'all'`` - ValueError: if more than one state preparation is present in the circuit, or if the - preparation is not at the start of the circuit **Example:** diff --git a/tests/transforms/test_insert.py b/tests/transforms/test_insert.py index d13e6eeb077..dcc38247aab 100644 --- a/tests/transforms/test_insert.py +++ b/tests/transforms/test_insert.py @@ -223,15 +223,6 @@ def test_end_with_state_prep(self): assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation - def test_multiple_preparations(self): - """Tests if a ValueError is raised when multiple state preparations are present in the - tape""" - with QuantumTape() as tape: - qml.QubitStateVector([1, 0], wires=0) - qml.QubitStateVector([0, 1], wires=1) - with pytest.raises(ValueError, match="Only a single state preparation at the start of the"): - insert.tape_fn(tape, qml.AmplitudeDamping, 0.4) - def test_insert_integration(): """Test that a QNode with the insert decorator gives a different result than one From 441d6ed34f618af0c3e00fbd2d6f654715d1b704 Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 09:34:45 -0400 Subject: [PATCH 41/62] Fix CI --- pennylane/transforms/insert_ops.py | 4 +--- tests/transforms/test_insert.py | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pennylane/transforms/insert_ops.py b/pennylane/transforms/insert_ops.py index a89dd6ec7c3..52d052a3e48 100644 --- a/pennylane/transforms/insert_ops.py +++ b/pennylane/transforms/insert_ops.py @@ -146,9 +146,7 @@ def f(w, x, y, z): 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.05)──╰┤ ⟨Z ⊗ Z⟩ """ if not isinstance(op, FunctionType) and op.num_wires != 1: - raise ValueError( - "Only single-qubit operations can be inserted into the circuit" - ) + raise ValueError("Only single-qubit operations can be inserted into the circuit") if position not in ("start", "end", "all"): raise ValueError("Position must be either 'start', 'end', or 'all' (default)") diff --git a/tests/transforms/test_insert.py b/tests/transforms/test_insert.py index dcc38247aab..e69c6f9488f 100644 --- a/tests/transforms/test_insert.py +++ b/tests/transforms/test_insert.py @@ -15,14 +15,15 @@ Tests for the insert transforms. """ from copy import deepcopy + import numpy as np import pytest import pennylane as qml +import pennylane.transforms.insert_ops from pennylane.operation import Expectation from pennylane.tape import QuantumTape from pennylane.transforms.insert_ops import insert, insert_in_dev -import pennylane.transforms.insert_ops class TestInsert: From c48ed3a268c2238b09641261640f160603413095 Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 09:40:41 -0400 Subject: [PATCH 42/62] Provide single dispatch in qfunc_transform --- pennylane/transforms/qfunc_transforms.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pennylane/transforms/qfunc_transforms.py b/pennylane/transforms/qfunc_transforms.py index b631f39c963..6394c0d1106 100644 --- a/pennylane/transforms/qfunc_transforms.py +++ b/pennylane/transforms/qfunc_transforms.py @@ -172,6 +172,9 @@ def __call__(self, tape, *args, **kwargs): def _create_qfunc_internal_wrapper(fn, tape_transform, transform_args, transform_kwargs): """Convenience function to create the internal wrapper function generated by the qfunc_transform decorator""" + if isinstance(fn, qml.tape.QuantumTape): + return tape_transform(fn, *transform_args, **transform_kwargs) + if not callable(fn): raise ValueError( f"The qfunc to transform, {fn}, does not appear " From ec83d760deaa6f9f6f46ccd9d5eeaeb93435e060 Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 09:42:04 -0400 Subject: [PATCH 43/62] Update to avoid using tape_fn --- pennylane/transforms/insert_ops.py | 4 ++-- tests/transforms/test_insert.py | 20 +++++++++----------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/pennylane/transforms/insert_ops.py b/pennylane/transforms/insert_ops.py index 52d052a3e48..fb58a090e5b 100644 --- a/pennylane/transforms/insert_ops.py +++ b/pennylane/transforms/insert_ops.py @@ -140,7 +140,7 @@ def f(w, x, y, z): We can add the :class:`~.AmplitudeDamping` channel to the end of the circuit using: >>> from pennylane.transforms import insert - >>> noisy_tape = insert.tape_fn(tape, qml.AmplitudeDamping, 0.05, position="end") + >>> noisy_tape = insert(qml.AmplitudeDamping, 0.05, position="end")(tape) >>> print(noisy_tape.draw()) 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.05)──╭┤ ⟨Z ⊗ Z⟩ 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.05)──╰┤ ⟨Z ⊗ Z⟩ @@ -251,7 +251,7 @@ def f(w, x, y, z): original_expand_fn = new_dev.expand_fn def new_expand_fn(circuit, max_expansion=10): - new_tape = insert.tape_fn(circuit, op, op_args, position) + new_tape = insert(op, op_args, position)(circuit) return original_expand_fn(new_tape, max_expansion=max_expansion) new_dev.expand_fn = new_expand_fn diff --git a/tests/transforms/test_insert.py b/tests/transforms/test_insert.py index e69c6f9488f..cfef037a8b8 100644 --- a/tests/transforms/test_insert.py +++ b/tests/transforms/test_insert.py @@ -49,16 +49,16 @@ class TestInsert: def test_multiwire_noisy_op(self): """Tests if a ValueError is raised when multiqubit channels are requested""" with pytest.raises(ValueError, match="Only single-qubit operations can be inserted into"): - insert.tape_fn(self.tape, qml.CNOT, []) + insert(qml.CNOT, [])(self.tape) def test_invalid_position(self): """Test if a ValueError is raised when an invalid position is requested""" with pytest.raises(ValueError, match="Position must be either 'start', 'end', or 'all'"): - insert.tape_fn(self.tape, qml.AmplitudeDamping, 0.4, position="ABC") + insert(qml.AmplitudeDamping, 0.4, position="ABC")(self.tape) def test_start(self): """Test if the expected tape is returned when the start position is requested""" - tape = insert.tape_fn(self.tape, qml.AmplitudeDamping, 0.4, position="start") + tape = insert(qml.AmplitudeDamping, 0.4, position="start")(self.tape) with QuantumTape() as tape_exp: qml.AmplitudeDamping(0.4, wires=0) @@ -83,7 +83,7 @@ def test_start(self): def test_all(self): """Test if the expected tape is returned when the all position is requested""" - tape = insert.tape_fn(self.tape, qml.PhaseDamping, 0.4, position="all") + tape = insert(qml.PhaseDamping, 0.4, position="all")(self.tape) with QuantumTape() as tape_exp: qml.RX(0.9, wires=0) @@ -112,9 +112,7 @@ def test_all(self): def test_end(self): """Test if the expected tape is returned when the end position is requested""" - tape = insert.tape_fn( - self.tape, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end" - ) + tape = insert(qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end")(self.tape) with QuantumTape() as tape_exp: qml.RX(0.9, wires=0) @@ -140,7 +138,7 @@ def test_end(self): def test_start_with_state_prep(self): """Test if the expected tape is returned when the start position is requested in a tape that has state preparation""" - tape = insert.tape_fn(self.tape_with_prep, qml.AmplitudeDamping, 0.4, position="start") + tape = insert(qml.AmplitudeDamping, 0.4, position="start")(self.tape_with_prep) with QuantumTape() as tape_exp: qml.QubitStateVector([1, 0], wires=0) @@ -167,7 +165,7 @@ def test_start_with_state_prep(self): def test_all_with_state_prep(self): """Test if the expected tape is returned when the all position is requested in a tape that has state preparation""" - tape = insert.tape_fn(self.tape_with_prep, qml.PhaseDamping, 0.4, position="all") + tape = insert(qml.PhaseDamping, 0.4, position="all")(self.tape_with_prep) with QuantumTape() as tape_exp: qml.QubitStateVector([1, 0], wires=0) @@ -198,8 +196,8 @@ def test_all_with_state_prep(self): def test_end_with_state_prep(self): """Test if the expected tape is returned when the end position is requested in a tape that has state preparation""" - tape = insert.tape_fn( - self.tape_with_prep, qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end" + tape = insert(qml.GeneralizedAmplitudeDamping, [0.4, 0.5], position="end")( + self.tape_with_prep ) with QuantumTape() as tape_exp: From 55a733b0d7811ee1af7dc8621945778b4db3b427 Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 09:49:06 -0400 Subject: [PATCH 44/62] Changelog --- doc/releases/changelog-dev.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 397f2c79a81..24e053ca179 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -5,7 +5,7 @@

New features since last release

* The `insert` and `insert_in_dev` transforms have now been added, - providing a way to insert simple noise into a quantum circuit. + providing a way to insert single-qubit operations into a quantum circuit. [(#1795)](https://github.com/PennyLaneAI/pennylane/pull/1795) The following QNode can be transformed to add noise to the circuit: From 6c5bfbac1d2633129047e08e28ff649fe55ab3be Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 09:49:32 -0400 Subject: [PATCH 45/62] Rename --- tests/transforms/{test_insert.py => test_insert_ops.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/transforms/{test_insert.py => test_insert_ops.py} (100%) diff --git a/tests/transforms/test_insert.py b/tests/transforms/test_insert_ops.py similarity index 100% rename from tests/transforms/test_insert.py rename to tests/transforms/test_insert_ops.py From ae204f8ef41d5e16c93b1441955f421368e25744 Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 09:53:09 -0400 Subject: [PATCH 46/62] Add qfunc test --- tests/transforms/test_insert_ops.py | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/transforms/test_insert_ops.py b/tests/transforms/test_insert_ops.py index cfef037a8b8..f95ef6eaa51 100644 --- a/tests/transforms/test_insert_ops.py +++ b/tests/transforms/test_insert_ops.py @@ -222,6 +222,38 @@ def test_end_with_state_prep(self): assert tape.observables[0].wires.tolist() == [0, 1] assert tape.measurements[0].return_type is Expectation + def test_with_qfunc_op(self): + """Test if the transform works as expected if the operation is a qfunc rather than single + operation""" + def op(x, y, wires): + qml.RX(x, wires=wires) + qml.PhaseShift(y, wires=wires) + + tape = insert(op, [0.4, 0.5], position="end")(self.tape) + + with QuantumTape() as tape_exp: + qml.RX(0.9, wires=0) + qml.RY(0.4, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(0.5, wires=0) + qml.RX(0.6, wires=1) + qml.RX(0.4, wires=0) + qml.PhaseShift(0.5, wires=0) + qml.RX(0.4, wires=1) + qml.PhaseShift(0.5, wires=1) + qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + assert all(o1.name == o2.name for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all(o1.wires == o2.wires for o1, o2 in zip(tape.operations, tape_exp.operations)) + assert all( + np.allclose(o1.parameters, o2.parameters) + for o1, o2 in zip(tape.operations, tape_exp.operations) + ) + assert len(tape.measurements) == 1 + assert tape.observables[0].name == ["PauliZ", "PauliZ"] + assert tape.observables[0].wires.tolist() == [0, 1] + assert tape.measurements[0].return_type is Expectation + def test_insert_integration(): """Test that a QNode with the insert decorator gives a different result than one From 1e3b18ab29bfc24a02f56eadebf9ff69e581c9f2 Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 09:58:32 -0400 Subject: [PATCH 47/62] Fix CI --- tests/transforms/test_insert_ops.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/transforms/test_insert_ops.py b/tests/transforms/test_insert_ops.py index f95ef6eaa51..a41c1f87ab9 100644 --- a/tests/transforms/test_insert_ops.py +++ b/tests/transforms/test_insert_ops.py @@ -225,6 +225,7 @@ def test_end_with_state_prep(self): def test_with_qfunc_op(self): """Test if the transform works as expected if the operation is a qfunc rather than single operation""" + def op(x, y, wires): qml.RX(x, wires=wires) qml.PhaseShift(y, wires=wires) From a3b32488837c771bdc7d23fa2093c2ed80505728 Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 20:22:55 -0400 Subject: [PATCH 48/62] Remove single_tape_transform --- pennylane/transforms/insert_ops.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pennylane/transforms/insert_ops.py b/pennylane/transforms/insert_ops.py index fb58a090e5b..74b90a4236b 100644 --- a/pennylane/transforms/insert_ops.py +++ b/pennylane/transforms/insert_ops.py @@ -26,7 +26,6 @@ @qfunc_transform -@single_tape_transform def insert( circuit: Union[callable, QuantumTape], op: Union[callable, Type[Operation]], From 4f71fb9d3e7210f98f1282b53f80e41530ea8e6d Mon Sep 17 00:00:00 2001 From: Tom Bromley <49409390+trbromley@users.noreply.github.com> Date: Tue, 26 Oct 2021 20:24:17 -0400 Subject: [PATCH 49/62] Update pennylane/transforms/insert_ops.py Co-authored-by: Josh Izaac --- pennylane/transforms/insert_ops.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pennylane/transforms/insert_ops.py b/pennylane/transforms/insert_ops.py index 74b90a4236b..470fa2ca614 100644 --- a/pennylane/transforms/insert_ops.py +++ b/pennylane/transforms/insert_ops.py @@ -38,8 +38,9 @@ def insert( according to the positioning specified in the ``position`` argument. Only single qubit operations are permitted. - The type of ``op`` can be either a single operation or a quantum function. A quantum function - can be used to specify a sequence of operations acting on a single qubit, see the usage details + The type of ``op`` can be either a single operation or a quantum + function acting on a single wire. A quantum function can be used + to specify a sequence of operations acting on a single qubit, see the usage details for more information. Args: From dda77e384ccaec043df5b9e09d7b0f06d23f469d Mon Sep 17 00:00:00 2001 From: Tom Bromley <49409390+trbromley@users.noreply.github.com> Date: Tue, 26 Oct 2021 20:25:02 -0400 Subject: [PATCH 50/62] Update pennylane/transforms/insert_ops.py Co-authored-by: Josh Izaac --- pennylane/transforms/insert_ops.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane/transforms/insert_ops.py b/pennylane/transforms/insert_ops.py index 470fa2ca614..913e63b2ad2 100644 --- a/pennylane/transforms/insert_ops.py +++ b/pennylane/transforms/insert_ops.py @@ -147,6 +147,7 @@ def f(w, x, y, z): """ if not isinstance(op, FunctionType) and op.num_wires != 1: raise ValueError("Only single-qubit operations can be inserted into the circuit") + if position not in ("start", "end", "all"): raise ValueError("Position must be either 'start', 'end', or 'all' (default)") From 5dead2739fd1fb61b9fe5f3f7572d64add91e078 Mon Sep 17 00:00:00 2001 From: Tom Bromley <49409390+trbromley@users.noreply.github.com> Date: Tue, 26 Oct 2021 20:26:47 -0400 Subject: [PATCH 51/62] Update pennylane/transforms/insert_ops.py Co-authored-by: Josh Izaac --- pennylane/transforms/insert_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/transforms/insert_ops.py b/pennylane/transforms/insert_ops.py index 913e63b2ad2..4edd700fa53 100644 --- a/pennylane/transforms/insert_ops.py +++ b/pennylane/transforms/insert_ops.py @@ -154,7 +154,7 @@ def f(w, x, y, z): if not isinstance(op_args, Sequence): op_args = [op_args] - num_preps = sum(isinstance(o, (QubitStateVector, BasisState)) for o in circuit.operations) + num_preps = sum(isinstance(o, qml.tape.tape.STATE_PREP_OPS) for o in circuit.operations) for i in range(num_preps): apply(circuit.operations[i]) From 171f6ef641f78de9b657d5939a9fe264f9cda40e Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 20:39:30 -0400 Subject: [PATCH 52/62] Update --- pennylane/transforms/insert_ops.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pennylane/transforms/insert_ops.py b/pennylane/transforms/insert_ops.py index 4edd700fa53..df32166d900 100644 --- a/pennylane/transforms/insert_ops.py +++ b/pennylane/transforms/insert_ops.py @@ -19,10 +19,11 @@ from types import FunctionType from typing import Type, Union -from pennylane import BasisState, Device, QubitStateVector, apply +from pennylane import Device, apply from pennylane.operation import Operation from pennylane.tape import QuantumTape -from pennylane.transforms.qfunc_transforms import qfunc_transform, single_tape_transform +from pennylane.transforms.qfunc_transforms import qfunc_transform +from pennylane.tape.tape import STATE_PREP_OPS @qfunc_transform @@ -154,7 +155,7 @@ def f(w, x, y, z): if not isinstance(op_args, Sequence): op_args = [op_args] - num_preps = sum(isinstance(o, qml.tape.tape.STATE_PREP_OPS) for o in circuit.operations) + num_preps = sum(isinstance(o, STATE_PREP_OPS) for o in circuit.operations) for i in range(num_preps): apply(circuit.operations[i]) @@ -226,7 +227,6 @@ def insert_in_dev( dev = qml.device("default.mixed", wires=2) - @qnode(dev) def f(w, x, y, z): qml.RX(w, wires=0) qml.RY(x, wires=1) @@ -235,15 +235,18 @@ def f(w, x, y, z): qml.RX(z, wires=1) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + qnode = QNode(f, dev) + Execution of the circuit on ``dev`` will be noise-free: - >>> f(0.9, 0.4, 0.5, 0.6) + >>> qnode(0.9, 0.4, 0.5, 0.6) tensor(0.86243536, requires_grad=True) However, noise can be easily added to the device: - >>> qml.transforms.insert_in_dev(dev, qml.AmplitudeDamping, 0.2) - >>> f(0.9, 0.4, 0.5, 0.6) + >>> dev_noisy = qml.transforms.insert_in_dev(dev, qml.AmplitudeDamping, 0.2) + >>> qnode_noisy = QNode(f, dev_noisy) + >>> qnode_noisy(0.9, 0.4, 0.5, 0.6) tensor(0.72945434, requires_grad=True) """ # TODO: Remove warning in docstrings once new QNode replaces the old From eca476b5af98be5850180ebe988be9bd43e52de6 Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 20:53:09 -0400 Subject: [PATCH 53/62] Add single dispatch --- pennylane/transforms/__init__.py | 4 ++-- pennylane/transforms/insert_ops.py | 9 ++++++--- tests/transforms/test_insert_ops.py | 6 +++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pennylane/transforms/__init__.py b/pennylane/transforms/__init__.py index c1cba208c1e..e675357539f 100644 --- a/pennylane/transforms/__init__.py +++ b/pennylane/transforms/__init__.py @@ -96,7 +96,7 @@ .. autosummary:: :toctree: api - ~transforms.insert_in_dev + ~transforms.insert Decorators and utility functions -------------------------------- @@ -129,7 +129,7 @@ from .hamiltonian_expand import hamiltonian_expand from .measurement_grouping import measurement_grouping from .metric_tensor import metric_tensor -from .insert_ops import insert, insert_in_dev +from .insert_ops import insert from .optimization import ( cancel_inverses, commute_controlled, diff --git a/pennylane/transforms/insert_ops.py b/pennylane/transforms/insert_ops.py index df32166d900..221727817aa 100644 --- a/pennylane/transforms/insert_ops.py +++ b/pennylane/transforms/insert_ops.py @@ -16,16 +16,18 @@ """ from collections.abc import Sequence from copy import deepcopy +from functools import singledispatch from types import FunctionType from typing import Type, Union from pennylane import Device, apply from pennylane.operation import Operation from pennylane.tape import QuantumTape -from pennylane.transforms.qfunc_transforms import qfunc_transform from pennylane.tape.tape import STATE_PREP_OPS +from pennylane.transforms.qfunc_transforms import qfunc_transform +@singledispatch @qfunc_transform def insert( circuit: Union[callable, QuantumTape], @@ -178,7 +180,8 @@ def f(w, x, y, z): apply(m) -def insert_in_dev( +@insert.register +def _( device: Device, op: Union[callable, Type[Operation]], op_args: Union[tuple, float], @@ -244,7 +247,7 @@ def f(w, x, y, z): However, noise can be easily added to the device: - >>> dev_noisy = qml.transforms.insert_in_dev(dev, qml.AmplitudeDamping, 0.2) + >>> dev_noisy = qml.transforms.insert(dev, qml.AmplitudeDamping, 0.2) >>> qnode_noisy = QNode(f, dev_noisy) >>> qnode_noisy(0.9, 0.4, 0.5, 0.6) tensor(0.72945434, requires_grad=True) diff --git a/tests/transforms/test_insert_ops.py b/tests/transforms/test_insert_ops.py index a41c1f87ab9..7fc542a7b46 100644 --- a/tests/transforms/test_insert_ops.py +++ b/tests/transforms/test_insert_ops.py @@ -23,7 +23,7 @@ import pennylane.transforms.insert_ops from pennylane.operation import Expectation from pennylane.tape import QuantumTape -from pennylane.transforms.insert_ops import insert, insert_in_dev +from pennylane.transforms.insert_ops import insert class TestInsert: @@ -286,7 +286,7 @@ def f(w, x, y, z): def test_insert_in_dev(mocker, monkeypatch): - """Test if a device transformed by the insert_in_dev does successfully add noise to + """Test if a device transformed by the insert function does successfully add noise to subsequent circuit executions""" with QuantumTape() as in_tape: qml.RX(0.9, wires=0) @@ -307,7 +307,7 @@ def patched_deepcopy(dev): with monkeypatch.context() as m: m.setattr(pennylane.transforms.insert_ops, "deepcopy", patched_deepcopy) - new_dev = insert_in_dev(dev, qml.PhaseDamping, 0.4) + new_dev = insert(dev, qml.PhaseDamping, 0.4) res_with_noise = qml.execute([in_tape], new_dev, qml.gradients.param_shift) tape = spy.call_args[0][0] From 540f253408cdb5f4395ac0b6122486f78bf8f97f Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 20:54:12 -0400 Subject: [PATCH 54/62] Update changelog --- doc/releases/changelog-dev.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 24e053ca179..bd361b9f2c9 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -4,7 +4,7 @@

New features since last release

-* The `insert` and `insert_in_dev` transforms have now been added, +* The `insert` transform have now been added, providing a way to insert single-qubit operations into a quantum circuit. [(#1795)](https://github.com/PennyLaneAI/pennylane/pull/1795) From 94c69dbd79b3a75c15ca32349b985449aec7704c Mon Sep 17 00:00:00 2001 From: trbromley Date: Tue, 26 Oct 2021 20:58:23 -0400 Subject: [PATCH 55/62] Work on docstrings --- pennylane/transforms/insert_ops.py | 99 ++++++++++++------------------ 1 file changed, 40 insertions(+), 59 deletions(-) diff --git a/pennylane/transforms/insert_ops.py b/pennylane/transforms/insert_ops.py index 221727817aa..d1c43cb6137 100644 --- a/pennylane/transforms/insert_ops.py +++ b/pennylane/transforms/insert_ops.py @@ -47,7 +47,8 @@ def insert( for more information. Args: - circuit (callable or QuantumTape): the input circuit + circuit (callable or QuantumTape or Device): the input circuit to be transformed, or a + device TODO op (callable or Type[Operation]): the single-qubit operation, or sequence of operations acting on a single qubit, to be inserted into the circuit op_args (tuple or float): the arguments fed to the operation, either as a tuple or a single @@ -147,6 +148,44 @@ def f(w, x, y, z): >>> print(noisy_tape.draw()) 0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.05)──╭┤ ⟨Z ⊗ Z⟩ 1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.05)──╰┤ ⟨Z ⊗ Z⟩ + + **Transforming devices:** + + .. warning:: + + Using this transform on devices is a beta feature. Use the :class:`pennylane.beta.QNode` + decorator to create compatible QNodes and use :func:`~.execute` to execute quantum + tapes. + + Consider the following QNode: + + .. code-block:: python3 + + from pennylane.beta import qnode + + dev = qml.device("default.mixed", wires=2) + + def f(w, x, y, z): + qml.RX(w, wires=0) + qml.RY(x, wires=1) + qml.CNOT(wires=[0, 1]) + qml.RY(y, wires=0) + qml.RX(z, wires=1) + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + qnode = QNode(f, dev) + + Execution of the circuit on ``dev`` will be noise-free: + + >>> qnode(0.9, 0.4, 0.5, 0.6) + tensor(0.86243536, requires_grad=True) + + However, noise can be easily added to the device: + + >>> dev_noisy = qml.transforms.insert(dev, qml.AmplitudeDamping, 0.2) + >>> qnode_noisy = QNode(f, dev_noisy) + >>> qnode_noisy(0.9, 0.4, 0.5, 0.6) + tensor(0.72945434, requires_grad=True) """ if not isinstance(op, FunctionType) and op.num_wires != 1: raise ValueError("Only single-qubit operations can be inserted into the circuit") @@ -195,65 +234,7 @@ def _( The type of ``op`` can be either a single operation or a quantum function. A quantum function can be used to specify a sequence of operations acting on a single qubit. - - .. warning:: - - This device transform is a beta feature. Use the :class:`pennylane.beta.QNode` decorator to - create compatible QNodes and use :func:`~.execute` to execute quantum tapes. - - Args: - device (Device): the device to be transformed - op (callable or Type[Operation]): the single-qubit operation, or sequence of operations - acting on a single qubit, to be inserted into circuits - op_args (tuple or float): the arguments fed to the operation, either as a tuple or a single - float - position (str): Specification of where to add the operation. Should be one of: ``"all"`` to - add the operation after all gates; ``"start"`` to add the operation to all wires - at the start of the circuit; ``"end"`` to add the operation to all wires at the - end of the circuit. - - Returns: - Device: the updated device - - Raises: - ValueError: if a single operation acting on multiple wires is passed to ``op`` - ValueError: if the requested ``position`` argument is not ``'start'``, ``'end'`` or - ``'all'`` - - **Example:** - - Consider the following QNode: - - .. code-block:: python3 - - from pennylane.beta import qnode - - dev = qml.device("default.mixed", wires=2) - - def f(w, x, y, z): - qml.RX(w, wires=0) - qml.RY(x, wires=1) - qml.CNOT(wires=[0, 1]) - qml.RY(y, wires=0) - qml.RX(z, wires=1) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - - qnode = QNode(f, dev) - - Execution of the circuit on ``dev`` will be noise-free: - - >>> qnode(0.9, 0.4, 0.5, 0.6) - tensor(0.86243536, requires_grad=True) - - However, noise can be easily added to the device: - - >>> dev_noisy = qml.transforms.insert(dev, qml.AmplitudeDamping, 0.2) - >>> qnode_noisy = QNode(f, dev_noisy) - >>> qnode_noisy(0.9, 0.4, 0.5, 0.6) - tensor(0.72945434, requires_grad=True) """ - # TODO: Remove warning in docstrings once new QNode replaces the old - new_dev = deepcopy(device) original_expand_fn = new_dev.expand_fn From c3cca257c39dbb44c89396bc4f67000252e51067 Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 27 Oct 2021 07:37:07 -0400 Subject: [PATCH 56/62] Add to changelog --- doc/releases/changelog-dev.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index bd361b9f2c9..402679deae8 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -4,8 +4,8 @@

New features since last release

-* The `insert` transform have now been added, - providing a way to insert single-qubit operations into a quantum circuit. +* The `insert` transform has now been added, providing a way to insert single-qubit operations into + a quantum circuit. The transform can apply to QNodes, tapes, and devices. [(#1795)](https://github.com/PennyLaneAI/pennylane/pull/1795) The following QNode can be transformed to add noise to the circuit: From e23ff9596c4c6bdbd49444cefa50a5a9b81b73a8 Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 27 Oct 2021 07:52:25 -0400 Subject: [PATCH 57/62] Add to tests --- tests/transforms/test_insert_ops.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/transforms/test_insert_ops.py b/tests/transforms/test_insert_ops.py index 7fc542a7b46..ec9e464cdb2 100644 --- a/tests/transforms/test_insert_ops.py +++ b/tests/transforms/test_insert_ops.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Tests for the insert transforms. +Tests for the insert transform. """ from copy import deepcopy @@ -46,8 +46,8 @@ class TestInsert: qml.RX(0.6, wires=1) qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - def test_multiwire_noisy_op(self): - """Tests if a ValueError is raised when multiqubit channels are requested""" + def test_multiwire_op(self): + """Tests if a ValueError is raised when multiqubit operations are requested""" with pytest.raises(ValueError, match="Only single-qubit operations can be inserted into"): insert(qml.CNOT, [])(self.tape) @@ -256,7 +256,7 @@ def op(x, y, wires): assert tape.measurements[0].return_type is Expectation -def test_insert_integration(): +def test_insert_qnode(): """Test that a QNode with the insert decorator gives a different result than one without.""" dev = qml.device("default.mixed", wires=2) @@ -285,7 +285,7 @@ def f(w, x, y, z): assert not np.isclose(f_noisy(*args), f(*args)) -def test_insert_in_dev(mocker, monkeypatch): +def test_insert_dev(mocker, monkeypatch): """Test if a device transformed by the insert function does successfully add noise to subsequent circuit executions""" with QuantumTape() as in_tape: From 503f2d20972aab4b4d9712123149795bdec02669 Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 27 Oct 2021 08:06:59 -0400 Subject: [PATCH 58/62] Update docstrings --- pennylane/transforms/insert_ops.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pennylane/transforms/insert_ops.py b/pennylane/transforms/insert_ops.py index d1c43cb6137..8103c060bf2 100644 --- a/pennylane/transforms/insert_ops.py +++ b/pennylane/transforms/insert_ops.py @@ -30,16 +30,16 @@ @singledispatch @qfunc_transform def insert( - circuit: Union[callable, QuantumTape], + circuit: Union[callable, QuantumTape, Device], op: Union[callable, Type[Operation]], op_args: Union[tuple, float], position: str = "all", ) -> Union[callable, QuantumTape]: """Insert an operation into specified points in an input circuit. - The circuit will be updated to have the operation, specified by the ``op`` argument, added - according to the positioning specified in the ``position`` argument. Only single qubit - operations are permitted. + Circuits passed through this transform will be updated to have the operation, specified by the + ``op`` argument, added according to the positioning specified in the ``position`` argument. Only + single qubit operations are permitted. The type of ``op`` can be either a single operation or a quantum function acting on a single wire. A quantum function can be used @@ -48,7 +48,7 @@ def insert( Args: circuit (callable or QuantumTape or Device): the input circuit to be transformed, or a - device TODO + device op (callable or Type[Operation]): the single-qubit operation, or sequence of operations acting on a single qubit, to be inserted into the circuit op_args (tuple or float): the arguments fed to the operation, either as a tuple or a single @@ -59,7 +59,8 @@ def insert( end of the circuit. Returns: - callable or QuantumTape: the updated version of the input circuit + callable or QuantumTape or device: the updated version of the input circuit or an updated + device which will transform circuits before execution Raises: ValueError: if a single operation acting on multiple wires is passed to ``op`` @@ -154,8 +155,7 @@ def f(w, x, y, z): .. warning:: Using this transform on devices is a beta feature. Use the :class:`pennylane.beta.QNode` - decorator to create compatible QNodes and use :func:`~.execute` to execute quantum - tapes. + decorator to create compatible QNodes. Consider the following QNode: From ee23212e89fb8e907816a0968723a818c0b2a1e3 Mon Sep 17 00:00:00 2001 From: Josh Izaac Date: Wed, 27 Oct 2021 20:36:07 +0800 Subject: [PATCH 59/62] Move device transform API into the `@qfunc_transform` decorator (#1809) * Move device transform API into the @qfunc_transform decorator * linting * add to changelog * support drawing expansions * typo * Apply suggestions from code review Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> * Update pennylane/transforms/draw.py Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> --- doc/releases/changelog-dev.md | 25 ++++++++++++++++++ pennylane/_device.py | 32 ++++++++++++++++++++++- pennylane/transforms/__init__.py | 11 -------- pennylane/transforms/draw.py | 21 +++++++++++++-- pennylane/transforms/insert_ops.py | 33 +----------------------- pennylane/transforms/qfunc_transforms.py | 12 +++++++++ tests/transforms/test_insert_ops.py | 11 ++------ 7 files changed, 90 insertions(+), 55 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 402679deae8..c5562bb723e 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -445,6 +445,31 @@

Improvements

+* Quantum function transforms can now be applied to devices. + Once applied to a device, any quantum function executed on the + modified device will be transformed prior to execution. + [(#1809)](https://github.com/PennyLaneAI/pennylane/pull/1809) + + ```python + dev = qml.device("default.mixed", wires=1) + dev = qml.transforms.merge_rotations()(dev) + + @qml.beta.qnode(dev) + def f(w, x, y, z): + qml.RX(w, wires=0) + qml.RX(x, wires=0) + qml.RX(y, wires=0) + qml.RX(z, wires=0) + return qml.expval(qml.PauliZ(0)) + ``` + + ```pycon + >>> print(f(0.9, 0.4, 0.5, 0.6)) + -0.7373937155412453 + >>> print(qml.draw(f, expansion_strategy="device")(0.9, 0.4, 0.5, 0.6)) + 0: ──RX(2.4)──┤ ⟨Z⟩ + ``` + * The `ApproxTimeEvolution` template can now be used with Hamiltonians that have trainable coefficients. [(#1789)](https://github.com/PennyLaneAI/pennylane/pull/1789) diff --git a/pennylane/_device.py b/pennylane/_device.py index 974bc9b18b3..6e2641bbf7e 100644 --- a/pennylane/_device.py +++ b/pennylane/_device.py @@ -16,6 +16,7 @@ """ # pylint: disable=too-many-format-args, use-maxsplit-arg import abc +import types import warnings from collections.abc import Iterable, Sequence from collections import OrderedDict, namedtuple @@ -140,6 +141,7 @@ def __init__(self, wires=1, shots=1000, *, analytic=None): self._parameters = None self.tracker = qml.Tracker() + self.expand_fn = self.default_expand_fn def __repr__(self): """String representation.""" @@ -571,7 +573,35 @@ def stopping_condition(self): and self.supports_operation(obj.name) ) - def expand_fn(self, circuit, max_expansion=10): + def custom_expand(self, fn): + """Register a custom expansion function for the device. + + **Example** + + .. code-block:: python + + dev = qml.device("default.qubit", wires=2) + + @dev.custom_expand + def my_expansion_function(self, tape, max_expansion=10): + ... + # can optionally call the default device expansion + tape = self.default_expand_fn(tape, max_expansion=max_expansion) + return tape + + The custom device expansion function must have arguments + ``self`` (the device object), ``tape`` (the input circuit + to transform and execute), and ``max_expansion`` (the number of + times the circuit should be expanded). + + The default :meth:`~.default_expand_fn` method of the original + device may be called. It is highly recommended to call this + before returning, to ensure that the expanded circuit is supported + on the device. + """ + self.expand_fn = types.MethodType(fn, self) + + def default_expand_fn(self, circuit, max_expansion=10): """Method for expanding or decomposing an input circuit. This method should be overwritten if custom expansion logic is required. diff --git a/pennylane/transforms/__init__.py b/pennylane/transforms/__init__.py index e675357539f..ed5df35b95d 100644 --- a/pennylane/transforms/__init__.py +++ b/pennylane/transforms/__init__.py @@ -87,17 +87,6 @@ ~transforms.measurement_grouping ~transforms.hamiltonian_expand -Transforms that act on devices -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -These transforms apply to circuits just before they are -executed on the device. - -.. autosummary:: - :toctree: api - - ~transforms.insert - Decorators and utility functions -------------------------------- diff --git a/pennylane/transforms/draw.py b/pennylane/transforms/draw.py index 2ce1f598f4b..5eaf7d6bed8 100644 --- a/pennylane/transforms/draw.py +++ b/pennylane/transforms/draw.py @@ -19,7 +19,7 @@ import pennylane as qml -def draw(qnode, charset="unicode", wire_order=None, show_all_wires=False): +def draw(qnode, charset="unicode", wire_order=None, show_all_wires=False, expansion_strategy=None): """Create a function that draws the given qnode. Args: @@ -28,6 +28,16 @@ def draw(qnode, charset="unicode", wire_order=None, show_all_wires=False): "ascii" are supported. wire_order (Sequence[Any]): the order (from top to bottom) to print the wires of the circuit show_all_wires (bool): If True, all wires, including empty wires, are printed. + expansion_strategy (str): The strategy to use when circuit expansions or decompositions + are required. + + - ``gradient``: The QNode will attempt to decompose + the internal circuit such that all circuit operations are supported by the gradient + method. + + - ``device``: The QNode will attempt to decompose the internal circuit + such that all circuit operations are natively supported by the device. + Returns: A function that has the same argument signature as ``qnode``. When called, @@ -88,7 +98,14 @@ def circuit(): @wraps(qnode) def wrapper(*args, **kwargs): - tapes = qnode.construct(args, kwargs) + original_expansion_strategy = getattr(qnode, "expansion_strategy", None) + + try: + qnode.expansion_strategy = expansion_strategy or original_expansion_strategy + tapes = qnode.construct(args, kwargs) + finally: + qnode.expansion_strategy = original_expansion_strategy + _wire_order = wire_order or qnode.device.wires _wire_order = qml.wires.Wires(_wire_order) diff --git a/pennylane/transforms/insert_ops.py b/pennylane/transforms/insert_ops.py index 8103c060bf2..99805219c9c 100644 --- a/pennylane/transforms/insert_ops.py +++ b/pennylane/transforms/insert_ops.py @@ -15,8 +15,6 @@ Provides transforms for inserting operations into quantum circuits. """ from collections.abc import Sequence -from copy import deepcopy -from functools import singledispatch from types import FunctionType from typing import Type, Union @@ -27,7 +25,6 @@ from pennylane.transforms.qfunc_transforms import qfunc_transform -@singledispatch @qfunc_transform def insert( circuit: Union[callable, QuantumTape, Device], @@ -182,7 +179,7 @@ def f(w, x, y, z): However, noise can be easily added to the device: - >>> dev_noisy = qml.transforms.insert(dev, qml.AmplitudeDamping, 0.2) + >>> dev_noisy = qml.transforms.insert(qml.AmplitudeDamping, 0.2)(dev) >>> qnode_noisy = QNode(f, dev_noisy) >>> qnode_noisy(0.9, 0.4, 0.5, 0.6) tensor(0.72945434, requires_grad=True) @@ -217,31 +214,3 @@ def f(w, x, y, z): for m in circuit.measurements: apply(m) - - -@insert.register -def _( - device: Device, - op: Union[callable, Type[Operation]], - op_args: Union[tuple, float], - position: str = "all", -) -> Device: - """Insert an operation into specified points in circuits during device execution. - - After applying this transform, circuits executed on the device will have operations inserted. - The operations are specified by the ``op`` argument and positioned according to the - ``position`` argument. Only single qubit operations are permitted. - - The type of ``op`` can be either a single operation or a quantum function. A quantum function - can be used to specify a sequence of operations acting on a single qubit. - """ - new_dev = deepcopy(device) - original_expand_fn = new_dev.expand_fn - - def new_expand_fn(circuit, max_expansion=10): - new_tape = insert(op, op_args, position)(circuit) - return original_expand_fn(new_tape, max_expansion=max_expansion) - - new_dev.expand_fn = new_expand_fn - - return new_dev diff --git a/pennylane/transforms/qfunc_transforms.py b/pennylane/transforms/qfunc_transforms.py index 6394c0d1106..0013bd31bdc 100644 --- a/pennylane/transforms/qfunc_transforms.py +++ b/pennylane/transforms/qfunc_transforms.py @@ -13,8 +13,10 @@ # limitations under the License. """Contains tools and decorators for registering qfunc transforms.""" # pylint: disable=too-few-public-methods +from copy import deepcopy import functools import inspect +import types import pennylane as qml @@ -172,6 +174,16 @@ def __call__(self, tape, *args, **kwargs): def _create_qfunc_internal_wrapper(fn, tape_transform, transform_args, transform_kwargs): """Convenience function to create the internal wrapper function generated by the qfunc_transform decorator""" + if isinstance(fn, qml.Device): + new_dev = deepcopy(fn) + + @new_dev.custom_expand + def new_expand_fn(self, tape, *args, **kwargs): + tape = tape_transform(tape, *transform_args, **transform_kwargs) + return self.default_expand_fn(tape, *args, **kwargs) + + return new_dev + if isinstance(fn, qml.tape.QuantumTape): return tape_transform(fn, *transform_args, **transform_kwargs) diff --git a/tests/transforms/test_insert_ops.py b/tests/transforms/test_insert_ops.py index ec9e464cdb2..14337234e37 100644 --- a/tests/transforms/test_insert_ops.py +++ b/tests/transforms/test_insert_ops.py @@ -299,15 +299,8 @@ def test_insert_dev(mocker, monkeypatch): dev = qml.device("default.mixed", wires=2) res_without_noise = qml.execute([in_tape], dev, qml.gradients.param_shift) - def patched_deepcopy(dev): - global spy - copied = deepcopy(dev) - spy = mocker.spy(copied, "expand_fn") - return copied - - with monkeypatch.context() as m: - m.setattr(pennylane.transforms.insert_ops, "deepcopy", patched_deepcopy) - new_dev = insert(dev, qml.PhaseDamping, 0.4) + new_dev = insert(qml.PhaseDamping, 0.4)(dev) + spy = mocker.spy(new_dev, "default_expand_fn") res_with_noise = qml.execute([in_tape], new_dev, qml.gradients.param_shift) tape = spy.call_args[0][0] From 788a68b48be5fa091f3de04d8b40ac3bd683e2fa Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 27 Oct 2021 08:50:57 -0400 Subject: [PATCH 60/62] Update docstrings --- pennylane/transforms/insert_ops.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pennylane/transforms/insert_ops.py b/pennylane/transforms/insert_ops.py index 99805219c9c..7bc685c03a2 100644 --- a/pennylane/transforms/insert_ops.py +++ b/pennylane/transforms/insert_ops.py @@ -36,7 +36,7 @@ def insert( Circuits passed through this transform will be updated to have the operation, specified by the ``op`` argument, added according to the positioning specified in the ``position`` argument. Only - single qubit operations are permitted. + single qubit operations are permitted to be inserted. The type of ``op`` can be either a single operation or a quantum function acting on a single wire. A quantum function can be used @@ -56,7 +56,7 @@ def insert( end of the circuit. Returns: - callable or QuantumTape or device: the updated version of the input circuit or an updated + callable or QuantumTape or Device: the updated version of the input circuit or an updated device which will transform circuits before execution Raises: @@ -110,7 +110,7 @@ def op(x, y, wires): dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) - @insert(op, [0.2, 0.3], position="end") + @qml.transforms.insert(op, [0.2, 0.3], position="end") def f(w, x, y, z): qml.RX(w, wires=0) qml.RY(x, wires=1) @@ -158,7 +158,7 @@ def f(w, x, y, z): .. code-block:: python3 - from pennylane.beta import qnode + from pennylane.beta import QNode dev = qml.device("default.mixed", wires=2) From 62c58b5270ac623c9b33adb15c1598dd1be89e18 Mon Sep 17 00:00:00 2001 From: Tom Bromley <49409390+trbromley@users.noreply.github.com> Date: Wed, 27 Oct 2021 10:12:14 -0400 Subject: [PATCH 61/62] Apply suggestions from code review Co-authored-by: Josh Izaac Co-authored-by: Olivia Di Matteo <2068515+glassnotes@users.noreply.github.com> --- doc/releases/changelog-dev.md | 2 +- pennylane/transforms/insert_ops.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 19e7982d5d5..0b3bdb30bab 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -5,7 +5,7 @@

New features since last release

* The `insert` transform has now been added, providing a way to insert single-qubit operations into - a quantum circuit. The transform can apply to QNodes, tapes, and devices. + a quantum circuit. The transform can apply to quantum functions, tapes, and devices. [(#1795)](https://github.com/PennyLaneAI/pennylane/pull/1795) The following QNode can be transformed to add noise to the circuit: diff --git a/pennylane/transforms/insert_ops.py b/pennylane/transforms/insert_ops.py index 7bc685c03a2..54583530e2b 100644 --- a/pennylane/transforms/insert_ops.py +++ b/pennylane/transforms/insert_ops.py @@ -40,8 +40,8 @@ def insert( The type of ``op`` can be either a single operation or a quantum function acting on a single wire. A quantum function can be used - to specify a sequence of operations acting on a single qubit, see the usage details - for more information. + to specify a sequence of operations acting on a single qubit (see the usage details + for more information). Args: circuit (callable or QuantumTape or Device): the input circuit to be transformed, or a From 96cb3d08f2eeb8878a8f2d786db449b7b0de200f Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 27 Oct 2021 10:16:50 -0400 Subject: [PATCH 62/62] Mention state preps --- pennylane/transforms/insert_ops.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/transforms/insert_ops.py b/pennylane/transforms/insert_ops.py index 54583530e2b..9cfbf3fa63b 100644 --- a/pennylane/transforms/insert_ops.py +++ b/pennylane/transforms/insert_ops.py @@ -51,9 +51,9 @@ def insert( op_args (tuple or float): the arguments fed to the operation, either as a tuple or a single float position (str): Specification of where to add the operation. Should be one of: ``"all"`` to - add the operation after all gates; ``"start"`` to add the operation to all wires - at the start of the circuit; ``"end"`` to add the operation to all wires at the - end of the circuit. + add the operation after all gates (except state preparations); ``"start"`` to add the + operation to all wires at the start of the circuit (but after state preparations); + ``"end"`` to add the operation to all wires at the end of the circuit. Returns: callable or QuantumTape or Device: the updated version of the input circuit or an updated