From f2b98a63c138247772c91b6e0e0f938ecf8e0039 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Thu, 4 Apr 2024 17:45:18 -0700 Subject: [PATCH 01/31] Added Depolarizing channel and structure of tests --- pennylane/ops/__init__.py | 3 +- pennylane/ops/qutrit/channel.py | 101 ++++++++++++++++ tests/ops/qutrit/test_qutrit_channel_ops.py | 120 ++++++++++++++++++++ 3 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 pennylane/ops/qutrit/channel.py create mode 100644 tests/ops/qutrit/test_qutrit_channel_ops.py diff --git a/pennylane/ops/__init__.py b/pennylane/ops/__init__.py index fc7acfa5ef1..86f1066a2ba 100644 --- a/pennylane/ops/__init__.py +++ b/pennylane/ops/__init__.py @@ -32,6 +32,7 @@ from .qutrit import __all__ as _qutrit__all__ from .qutrit import __ops__ as _qutrit__ops__ from .qutrit import __obs__ as _qutrit__obs__ +from .qutrit.channel import __all__ as _qutrit__channel__ops__ from .channel import __all__ as _channel__ops__ @@ -47,6 +48,6 @@ _qubit__all__ = _qubit__all__ + list(_controlled_qubit__ops__) -__all__ = _cv__all__ + _qubit__all__ + _qutrit__all__ + _channel__ops__ +__all__ = _cv__all__ + _qubit__all__ + _qutrit__all__ + _channel__ops__ + _qutrit__channel__ops__ __all_ops__ = list(_cv__ops__ | _qubit__ops__ | _qutrit__ops__) __all_obs__ = list(_cv__obs__ | _qubit__obs__ | _qutrit__obs__) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py new file mode 100644 index 00000000000..fc537e05d40 --- /dev/null +++ b/pennylane/ops/qutrit/channel.py @@ -0,0 +1,101 @@ +# Copyright 2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# pylint: disable=too-many-arguments +""" +This module contains the available built-in noisy +quantum channels supported by PennyLane, as well as their conventions. +""" +from pennylane import math +from pennylane.operation import Channel + + +class QutritDepolarizingChannel(Channel): + r""" + Single-qutrit symmetrically depolarizing error channel. + This channel is modelled by the following Kraus matrices: + where :math:`p \in [0, 1]` is the depolarization probability and is equally + divided in the application of all Pauli operations. + .. note:: + Multiple equivalent definitions of the Kraus operators :math:`\{K_0 \ldots K_3\}` exist in + the literature [`1 `_] (Eqs. 8.102-103). Here, we adopt the + one from Eq. 8.103, which is also presented in [`2 `_] (Eq. 3.85). + For this definition, please make a note of the following: + * For :math:`p = 0`, the channel will be an Identity channel, i.e., a noise-free channel. + * For :math:`p = \frac{3}{4}`, the channel will be a fully depolarizing channel. + * For :math:`p = 1`, the channel will be a uniform Pauli error channel. + **Details:** + * Number of wires: 1 + * Number of parameters: 1 + Args: + p (float): Each Pauli gate is applied with probability :math:`\frac{p}{3}` + wires (Sequence[int] or int): the wire the channel acts on + id (str or None): String representing the operation (optional) + """ + num_params = 1 + num_wires = 1 + grad_method = "A" + grad_recipe = ([[1, 0, 1], [-1, 0, 0]],) # TODO + + def __init__(self, p, wires, id=None): + super().__init__(p, wires=wires, id=id) + + @staticmethod + def compute_kraus_matrices(p): # pylint:disable=arguments-differ + r"""Kraus matrices representing the depolarizing channel. + Args: + p (float): each Pauli gate is applied with probability :math:`\frac{p}{3}` + Returns: + list (array): list of Kraus matrices + **Example** + >>> qml.DepolarizingChannel.compute_kraus_matrices(0.5) + [array([[0.70710678, 0. ], [0. , 0.70710678]]), + array([[0. , 0.40824829], [0.40824829, 0. ]]), + array([[0.+0.j , 0.-0.40824829j], [0.+0.40824829j, 0.+0.j ]]), + array([[ 0.40824829, 0. ], [ 0. , -0.40824829]])] + """ + if not math.is_abstract(p) and not 0.0 <= p <= 1.0: + raise ValueError("p must be in the interval [0,1]") + + if math.get_interface(p) == "tensorflow": + p = math.cast_like(p, 1j) + + Z0 = math.eye(3) + Z1 = math.diag([1, math.exp(2j * math.pi / 3), math.exp(4j * math.pi / 3)]) + Z2 = math.diag([1, math.exp(4j * math.pi / 3), math.exp(8j * math.pi / 3)]) + + X0 = math.eye(3) + X1 = math.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) + X2 = math.array([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) + + Ks = [ + math.sqrt(1 - (8 * p / 9) + math.eps) * math.convert_like(math.eye(2, dtype=complex), p) + ] + + for i, Z in enumerate((Z0, Z1, Z2)): + for j, X in enumerate((X0, X1, X2)): + if i == 0 and j == 0: + continue + Ks.append( + math.sqrt(p / 9 + math.eps) + * math.convert_like(math.array(X @ Z, dtype=complex), p) + ) + + return Ks + + +__qutrit_channels__ = { + "QutritDepolarizingChannel", +} + +__all__ = list(__qutrit_channels__) diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py new file mode 100644 index 00000000000..c2fe3d95714 --- /dev/null +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -0,0 +1,120 @@ +# Copyright 2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Unit tests for the available built-in qutrit quantum channels. +""" +import pytest +import numpy as np +import pennylane as qml +from pennylane import numpy as pnp +from pennylane.ops import channel + + +class TestQutritDepolarizingChannel: + """Tests for the qutrit quantum channel QutritDepolarizingChannel""" + + def test_p_zero(self, tol): + """Test p=0 gives correct Kraus matrices""" + op = qml.DepolarizingChannel + assert np.allclose(op(0, wires=0).kraus_matrices()[0], np.eye(2), atol=tol, rtol=0) + assert np.allclose(op(0, wires=0).kraus_matrices()[1], np.zeros((2, 2)), atol=tol, rtol=0) + + def test_p_arbitrary(self, tol): + """Test p=0.1 gives correct Kraus matrices""" + p = 0.1 + op = qml.DepolarizingChannel + expected = np.sqrt(p / 3) * X + assert np.allclose(op(0.1, wires=0).kraus_matrices()[1], expected, atol=tol, rtol=0) + + @pytest.mark.parametrize("angle", np.linspace(0, 2 * np.pi, 7)) + def test_grad_depolarizing(self, angle): + """Test that analytical gradient is computed correctly for different states. Channel + grad recipes are independent of channel parameter""" + + dev = qml.device("default.mixed", wires=1) + prob = pnp.array(0.5, requires_grad=True) + + @qml.qnode(dev) + def circuit(p): + qml.RX(angle, wires=0) + qml.DepolarizingChannel(p, wires=0) + return qml.expval(qml.PauliZ(0)) + + gradient = np.squeeze(qml.grad(circuit)(prob)) + assert np.allclose(gradient, circuit(1) - circuit(0)) + assert np.allclose(gradient, -(4 / 3) * np.cos(angle)) + + def test_p_invalid_parameter(self): + with pytest.raises(ValueError, match="p must be in the interval"): + qml.DepolarizingChannel(1.5, wires=0).kraus_matrices() + + @staticmethod + def expected_jac_fn(p): + return [ + -1 / (2 * qml.math.sqrt(1 - p)) * qml.math.eye(2), + 1 / (6 * qml.math.sqrt(p / 3)) * qml.math.array([[0, 1], [1, 0]]), + 1 / (6 * qml.math.sqrt(p / 3)) * qml.math.array([[0, -1j], [1j, 0]]), + 1 / (6 * qml.math.sqrt(p / 3)) * qml.math.diag([1, -1]), + ] + + @staticmethod + def kraus_fn(x): + return qml.math.stack(channel.DepolarizingChannel(x, wires=0).kraus_matrices()) + + @staticmethod + def kraus_fn_real(x): + return qml.math.real( + qml.math.stack(channel.DepolarizingChannel(x, wires=0).kraus_matrices()) + ) + + @staticmethod + def kraus_fn_imag(x): + return qml.math.imag( + qml.math.stack(channel.DepolarizingChannel(x, wires=0).kraus_matrices()) + ) + + @pytest.mark.autograd + def test_kraus_jac_autograd(self): + p = pnp.array(0.43, requires_grad=True) + jac = qml.jacobian(self.kraus_fn_real)(p) + 1j * qml.jacobian(self.kraus_fn_imag)(p) + assert qml.math.allclose(jac, self.expected_jac_fn(p)) + + @pytest.mark.torch + def test_kraus_jac_torch(self): + import torch + + p = torch.tensor(0.43, requires_grad=True) + jacobian = torch.autograd.functional.jacobian + jac = jacobian(self.kraus_fn_real, p) + 1j * jacobian(self.kraus_fn_imag, p) + assert qml.math.allclose(jac, self.expected_jac_fn(p.detach().numpy())) + + @pytest.mark.tf + def test_kraus_jac_tf(self): + import tensorflow as tf + + p = tf.Variable(0.43) + with tf.GradientTape() as tape: + out = self.kraus_fn(p) + jac = tape.jacobian(out, p) + assert qml.math.allclose(jac, self.expected_jac_fn(p)) + + @pytest.mark.jax + def test_kraus_jac_jax(self): + import jax + + jax.config.update("jax_enable_x64", True) + + p = jax.numpy.array(0.43, dtype=jax.numpy.complex128) + jac = jax.jacobian(self.kraus_fn, holomorphic=True)(p) + assert qml.math.allclose(jac, self.expected_jac_fn(p)) From f56b406e6c387494ffd4f802ad054d287c4daf1b Mon Sep 17 00:00:00 2001 From: gabrielLydian Date: Wed, 10 Apr 2024 21:34:11 -0700 Subject: [PATCH 02/31] Added channels to ops.qutrit --- pennylane/ops/qutrit/__init__.py | 1 + pennylane/ops/qutrit/channel.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pennylane/ops/qutrit/__init__.py b/pennylane/ops/qutrit/__init__.py index c4f3596caf9..69f588bfe93 100644 --- a/pennylane/ops/qutrit/__init__.py +++ b/pennylane/ops/qutrit/__init__.py @@ -26,6 +26,7 @@ from .parametric_ops import * from .state_preparation import * from ..identity import Identity +from .channel import QutritDepolarizingChannel # TODO: Change `qml.Identity` for qutrit support or add `qml.TIdentity` for qutrits __ops__ = { diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index fc537e05d40..984676ce6a3 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -29,7 +29,7 @@ class QutritDepolarizingChannel(Channel): .. note:: Multiple equivalent definitions of the Kraus operators :math:`\{K_0 \ldots K_3\}` exist in the literature [`1 `_] (Eqs. 8.102-103). Here, we adopt the - one from Eq. 8.103, which is also presented in [`2 `_] (Eq. 3.85). + one from Eq. 8.103, which is also presented in [`2 `_] (Eq. 3.85). # TODO change sources For this definition, please make a note of the following: * For :math:`p = 0`, the channel will be an Identity channel, i.e., a noise-free channel. * For :math:`p = \frac{3}{4}`, the channel will be a fully depolarizing channel. From ed7b30b02e29d7d23750f7f8c2fdd636cedb4f9c Mon Sep 17 00:00:00 2001 From: gabrielLydian Date: Wed, 10 Apr 2024 21:34:56 -0700 Subject: [PATCH 03/31] Updated tests to use QutritDepolarizingChannel --- tests/ops/qutrit/test_qutrit_channel_ops.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index c2fe3d95714..2038fb3c2fd 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -26,14 +26,14 @@ class TestQutritDepolarizingChannel: def test_p_zero(self, tol): """Test p=0 gives correct Kraus matrices""" - op = qml.DepolarizingChannel + op = qml.QutritDepolarizingChannel assert np.allclose(op(0, wires=0).kraus_matrices()[0], np.eye(2), atol=tol, rtol=0) assert np.allclose(op(0, wires=0).kraus_matrices()[1], np.zeros((2, 2)), atol=tol, rtol=0) def test_p_arbitrary(self, tol): """Test p=0.1 gives correct Kraus matrices""" p = 0.1 - op = qml.DepolarizingChannel + op = qml.QutritDepolarizingChannel expected = np.sqrt(p / 3) * X assert np.allclose(op(0.1, wires=0).kraus_matrices()[1], expected, atol=tol, rtol=0) @@ -48,7 +48,7 @@ def test_grad_depolarizing(self, angle): @qml.qnode(dev) def circuit(p): qml.RX(angle, wires=0) - qml.DepolarizingChannel(p, wires=0) + qml.QutritDepolarizingChannel(p, wires=0) return qml.expval(qml.PauliZ(0)) gradient = np.squeeze(qml.grad(circuit)(prob)) @@ -57,7 +57,7 @@ def circuit(p): def test_p_invalid_parameter(self): with pytest.raises(ValueError, match="p must be in the interval"): - qml.DepolarizingChannel(1.5, wires=0).kraus_matrices() + qml.QutritDepolarizingChannel(1.5, wires=0).kraus_matrices() @staticmethod def expected_jac_fn(p): @@ -70,18 +70,18 @@ def expected_jac_fn(p): @staticmethod def kraus_fn(x): - return qml.math.stack(channel.DepolarizingChannel(x, wires=0).kraus_matrices()) + return qml.math.stack(channel.QutritDepolarizingChannel(x, wires=0).kraus_matrices()) @staticmethod def kraus_fn_real(x): return qml.math.real( - qml.math.stack(channel.DepolarizingChannel(x, wires=0).kraus_matrices()) + qml.math.stack(channel.QutritDepolarizingChannel(x, wires=0).kraus_matrices()) ) @staticmethod def kraus_fn_imag(x): return qml.math.imag( - qml.math.stack(channel.DepolarizingChannel(x, wires=0).kraus_matrices()) + qml.math.stack(channel.QutritDepolarizingChannel(x, wires=0).kraus_matrices()) ) @pytest.mark.autograd From 034a03b3f0dd90012f98d230cda52ee30ac9b2f0 Mon Sep 17 00:00:00 2001 From: gabrielLydian Date: Thu, 11 Apr 2024 16:32:04 -0700 Subject: [PATCH 04/31] Fixed location of channel --- tests/ops/qutrit/test_qutrit_channel_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 2038fb3c2fd..009e579b78b 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -18,7 +18,7 @@ import numpy as np import pennylane as qml from pennylane import numpy as pnp -from pennylane.ops import channel +from pennylane.ops.qutrit import channel class TestQutritDepolarizingChannel: From ca42c4745f816bb8264574890c53ad061a1f2ec3 Mon Sep 17 00:00:00 2001 From: gabrielLydian Date: Tue, 16 Apr 2024 19:42:13 -0700 Subject: [PATCH 05/31] Fixed ops __init__.py and dependencies --- pennylane/ops/__init__.py | 4 ++-- pennylane/ops/qutrit/__init__.py | 5 ++++- pennylane/ops/qutrit/channel.py | 7 ------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/pennylane/ops/__init__.py b/pennylane/ops/__init__.py index 86f1066a2ba..28ab7ed95f1 100644 --- a/pennylane/ops/__init__.py +++ b/pennylane/ops/__init__.py @@ -32,7 +32,7 @@ from .qutrit import __all__ as _qutrit__all__ from .qutrit import __ops__ as _qutrit__ops__ from .qutrit import __obs__ as _qutrit__obs__ -from .qutrit.channel import __all__ as _qutrit__channel__ops__ +from .qutrit import __channels__ as _qutrit__channel__ops__ from .channel import __all__ as _channel__ops__ @@ -48,6 +48,6 @@ _qubit__all__ = _qubit__all__ + list(_controlled_qubit__ops__) -__all__ = _cv__all__ + _qubit__all__ + _qutrit__all__ + _channel__ops__ + _qutrit__channel__ops__ +__all__ = _cv__all__ + _qubit__all__ + _qutrit__all__ + _channel__ops__ __all_ops__ = list(_cv__ops__ | _qubit__ops__ | _qutrit__ops__) __all_obs__ = list(_cv__obs__ | _qubit__obs__ | _qutrit__obs__) diff --git a/pennylane/ops/qutrit/__init__.py b/pennylane/ops/qutrit/__init__.py index 69f588bfe93..caefafbec3b 100644 --- a/pennylane/ops/qutrit/__init__.py +++ b/pennylane/ops/qutrit/__init__.py @@ -48,5 +48,8 @@ "THermitian", "GellMann", } +__channels__ = { + "QutritDepolarizingChannel", +} -__all__ = list(__ops__ | __obs__) +__all__ = list(__ops__ | __obs__ | __channels__) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 984676ce6a3..16dd82fd163 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -92,10 +92,3 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ ) return Ks - - -__qutrit_channels__ = { - "QutritDepolarizingChannel", -} - -__all__ = list(__qutrit_channels__) From 0aec1516a41218e9964cbdcd07579d4943eeed33 Mon Sep 17 00:00:00 2001 From: gabrielLydian Date: Sat, 20 Apr 2024 00:22:29 -0700 Subject: [PATCH 06/31] Fixed compute_kraus_matrices --- pennylane/ops/qutrit/channel.py | 101 +++++++++++++------- tests/ops/qutrit/test_qutrit_channel_ops.py | 91 ++++++++++++++---- 2 files changed, 141 insertions(+), 51 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 16dd82fd163..778ee6d24dc 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -16,36 +16,38 @@ This module contains the available built-in noisy quantum channels supported by PennyLane, as well as their conventions. """ +import numpy as np from pennylane import math from pennylane.operation import Channel +QUDIT_DIM = 3 + class QutritDepolarizingChannel(Channel): r""" Single-qutrit symmetrically depolarizing error channel. This channel is modelled by the following Kraus matrices: where :math:`p \in [0, 1]` is the depolarization probability and is equally - divided in the application of all Pauli operations. + divided in the application of all TODO operations. .. note:: - Multiple equivalent definitions of the Kraus operators :math:`\{K_0 \ldots K_3\}` exist in + Multiple equivalent definitions of the Kraus operators :math:`\{K_0 \ldots K_8\}` exist in #TODO fix liturature the literature [`1 `_] (Eqs. 8.102-103). Here, we adopt the one from Eq. 8.103, which is also presented in [`2 `_] (Eq. 3.85). # TODO change sources For this definition, please make a note of the following: * For :math:`p = 0`, the channel will be an Identity channel, i.e., a noise-free channel. - * For :math:`p = \frac{3}{4}`, the channel will be a fully depolarizing channel. - * For :math:`p = 1`, the channel will be a uniform Pauli error channel. + * For :math:`p = TODO \frac{3}{4}`, the channel will be a fully depolarizing channel. + * For :math:`p = 1`, the channel will be a uniform TODO error channel. **Details:** * Number of wires: 1 * Number of parameters: 1 Args: - p (float): Each Pauli gate is applied with probability :math:`\frac{p}{3}` + p (float): Each TODO gate is applied with probability :math:`\frac{p}{3}` wires (Sequence[int] or int): the wire the channel acts on id (str or None): String representing the operation (optional) """ num_params = 1 num_wires = 1 - grad_method = "A" - grad_recipe = ([[1, 0, 1], [-1, 0, 0]],) # TODO + grad_method = "F" def __init__(self, p, wires, id=None): super().__init__(p, wires=wires, id=id) @@ -54,41 +56,76 @@ def __init__(self, p, wires, id=None): def compute_kraus_matrices(p): # pylint:disable=arguments-differ r"""Kraus matrices representing the depolarizing channel. Args: - p (float): each Pauli gate is applied with probability :math:`\frac{p}{3}` + p (float): each TODO gate is applied with probability :math:`\frac{p}{9}` Returns: list (array): list of Kraus matrices **Example** - >>> qml.DepolarizingChannel.compute_kraus_matrices(0.5) - [array([[0.70710678, 0. ], [0. , 0.70710678]]), - array([[0. , 0.40824829], [0.40824829, 0. ]]), - array([[0.+0.j , 0.-0.40824829j], [0.+0.40824829j, 0.+0.j ]]), - array([[ 0.40824829, 0. ], [ 0. , -0.40824829]])] + >>> qml.QutritDepolarizingChannel.compute_kraus_matrices(0.5) + [array([[0.74535599+0.j, 0. +0.j, 0. +0.j], + [0. +0.j, 0.74535599+0.j, 0. +0.j], + [0. +0.j, 0. +0.j, 0.74535599+0.j]]), array([[0. , 0.23570226, 0. ], + [0. , 0. , 0.23570226], + [0.23570226, 0. , 0. ]]), array([[0. , 0. , 0.23570226], + [0.23570226, 0. , 0. ], + [0. , 0.23570226, 0. ]]), array([[ 0.23570226+0.j , 0. +0.j , + 0. +0.j ], + [ 0. +0.j , -0.11785113+0.20412415j, + 0. +0.j ], + [ 0. +0.j , 0. +0.j , + -0.11785113-0.20412415j]]), array([[ 0. +0.j , -0.11785113+0.20412415j, + 0. +0.j ], + [ 0. +0.j , 0. +0.j , + -0.11785113-0.20412415j], + [ 0.23570226+0.j , 0. +0.j , + 0. +0.j ]]), array([[ 0. +0.j , 0. +0.j , + -0.11785113-0.20412415j], + [ 0.23570226+0.j , 0. +0.j , + 0. +0.j ], + [ 0. +0.j , -0.11785113+0.20412415j, + 0. +0.j ]]), array([[ 0.23570226+0.j , 0. +0.j , + 0. +0.j ], + [ 0. +0.j , -0.11785113-0.20412415j, + 0. +0.j ], + [ 0. +0.j , 0. +0.j , + -0.11785113+0.20412415j]]), array([[ 0. +0.j , -0.11785113-0.20412415j, + 0. +0.j ], + [ 0. +0.j , 0. +0.j , + -0.11785113+0.20412415j], + [ 0.23570226+0.j , 0. +0.j , + 0. +0.j ]]), array([[ 0. +0.j , 0. +0.j , + -0.11785113+0.20412415j], + [ 0.23570226+0.j , 0. +0.j , + 0. +0.j ], + [ 0. +0.j , -0.11785113-0.20412415j, + 0. +0.j ]])] """ if not math.is_abstract(p) and not 0.0 <= p <= 1.0: raise ValueError("p must be in the interval [0,1]") - if math.get_interface(p) == "tensorflow": + interface = math.get_interface(p) + w = math.exp(2j * np.pi / 3) + one = 1 + z = 0 + if interface == "tensorflow": p = math.cast_like(p, 1j) + w = math.cast_like(w, p) + one = math.cast_like(one, p) + z = math.cast_like(z, p) - Z0 = math.eye(3) - Z1 = math.diag([1, math.exp(2j * math.pi / 3), math.exp(4j * math.pi / 3)]) - Z2 = math.diag([1, math.exp(4j * math.pi / 3), math.exp(8j * math.pi / 3)]) + depolarizing_mats = [ + [[z, one, z], [z, z, one], [one, z, z]], + [[z, z, one], [one, z, z], [z, one, z]], - X0 = math.eye(3) - X1 = math.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) - X2 = math.array([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) + [[one, z, z], [z, w, z], [z, z, w ** 2]], + [[z, w, 0j], [z, z, w**2], [one, z, z]], + [[z, z, w**2], [one, z, z], [z, w, z]], - Ks = [ - math.sqrt(1 - (8 * p / 9) + math.eps) * math.convert_like(math.eye(2, dtype=complex), p) + [[one, z, z], [z, w ** 2, z], [z, z, w ** 4]], + [[z, w ** 2, z], [z, z, w ** 4], [one, z, z]], + [[z, z, w**4], [one, z, z], [z, w**2, z]] ] + normalization = math.sqrt(p / 9 + math.eps) + Ks = [normalization * math.array(m, like=interface) for m in depolarizing_mats] + identity = math.sqrt(1 - (8 * p / 9) + math.eps) * math.array(math.eye(QUDIT_DIM, dtype=complex), like=interface) - for i, Z in enumerate((Z0, Z1, Z2)): - for j, X in enumerate((X0, X1, X2)): - if i == 0 and j == 0: - continue - Ks.append( - math.sqrt(p / 9 + math.eps) - * math.convert_like(math.array(X @ Z, dtype=complex), p) - ) - - return Ks + return [identity] + Ks diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 009e579b78b..97904271e7f 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -20,78 +20,125 @@ from pennylane import numpy as pnp from pennylane.ops.qutrit import channel +QUDIT_DIM = 3 + class TestQutritDepolarizingChannel: """Tests for the qutrit quantum channel QutritDepolarizingChannel""" + @staticmethod + def get_expected_kraus_matrices(p): + """Gets the expected Kraus matrices given probability p.""" + w = np.exp(2j * np.pi / 3) + + Z0 = np.eye(QUDIT_DIM) + Z1 = np.diag([1, w, w ** 2]) + Z2 = np.diag([1, w ** 2, w ** 4]) + + X0 = np.eye(QUDIT_DIM) + X1 = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) + X2 = np.array([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) + + Ks = [np.sqrt(1 - (8 * p / 9)) * np.eye(QUDIT_DIM)] + for i, Z in enumerate((Z0, Z1, Z2)): + for j, X in enumerate((X0, X1, X2)): + if i == 0 and j == 0: + continue + Ks.append(np.sqrt(p / 9) * X @ Z) + + return Ks + def test_p_zero(self, tol): """Test p=0 gives correct Kraus matrices""" op = qml.QutritDepolarizingChannel - assert np.allclose(op(0, wires=0).kraus_matrices()[0], np.eye(2), atol=tol, rtol=0) - assert np.allclose(op(0, wires=0).kraus_matrices()[1], np.zeros((2, 2)), atol=tol, rtol=0) + kraus_matrices = op(0, wires=0).kraus_matrices() + + assert len(kraus_matrices) == 9 + assert np.allclose(kraus_matrices[0], np.eye(QUDIT_DIM), atol=tol, rtol=0) + assert np.allclose(np.array(kraus_matrices[1:]), 0, atol=tol, rtol=0) def test_p_arbitrary(self, tol): """Test p=0.1 gives correct Kraus matrices""" p = 0.1 - op = qml.QutritDepolarizingChannel - expected = np.sqrt(p / 3) * X - assert np.allclose(op(0.1, wires=0).kraus_matrices()[1], expected, atol=tol, rtol=0) + kraus_matrices = qml.QutritDepolarizingChannel(p, wires=0).kraus_matrices() + expected_matrices = self.get_expected_kraus_matrices(p) + for krause_matrix, expected_matrix in zip(kraus_matrices, expected_matrices): + assert np.allclose(krause_matrix, expected_matrix, atol=tol, rtol=0) @pytest.mark.parametrize("angle", np.linspace(0, 2 * np.pi, 7)) def test_grad_depolarizing(self, angle): """Test that analytical gradient is computed correctly for different states. Channel grad recipes are independent of channel parameter""" - dev = qml.device("default.mixed", wires=1) + dev = qml.device("default.qutrit.mixed") prob = pnp.array(0.5, requires_grad=True) @qml.qnode(dev) def circuit(p): - qml.RX(angle, wires=0) + qml.TRX(angle, wires=0) qml.QutritDepolarizingChannel(p, wires=0) - return qml.expval(qml.PauliZ(0)) + return qml.expval(qml.GellMann(0, 3) + qml.GellMann(0, 8)) gradient = np.squeeze(qml.grad(circuit)(prob)) assert np.allclose(gradient, circuit(1) - circuit(0)) assert np.allclose(gradient, -(4 / 3) * np.cos(angle)) def test_p_invalid_parameter(self): + """Test that error is raised given an inappropriate p value.""" with pytest.raises(ValueError, match="p must be in the interval"): qml.QutritDepolarizingChannel(1.5, wires=0).kraus_matrices() @staticmethod def expected_jac_fn(p): - return [ - -1 / (2 * qml.math.sqrt(1 - p)) * qml.math.eye(2), - 1 / (6 * qml.math.sqrt(p / 3)) * qml.math.array([[0, 1], [1, 0]]), - 1 / (6 * qml.math.sqrt(p / 3)) * qml.math.array([[0, -1j], [1j, 0]]), - 1 / (6 * qml.math.sqrt(p / 3)) * qml.math.diag([1, -1]), - ] + """Gets the expected Jacobian of Kraus matrices given probability p.""" + w = np.exp(2j * np.pi / 3) + + Z0 = np.eye(QUDIT_DIM) + Z1 = np.diag([1, w, w ** 2]) + Z2 = np.diag([1, w ** 2, w ** 4]) + + X0 = np.eye(QUDIT_DIM) + X1 = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) + X2 = np.array([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) + + jacs = [-(4 / 9) / np.sqrt(1 - (8 * p / 9)) * np.eye(QUDIT_DIM)] + for i, Z in enumerate((Z0, Z1, Z2)): + for j, X in enumerate((X0, X1, X2)): + if i == 0 and j == 0: + continue + jacs.append(1 / (18 * np.sqrt(p / 9)) * X @ Z) + + return jacs @staticmethod def kraus_fn(x): + """Gets a matrix of the Kraus matrices to be tested.""" return qml.math.stack(channel.QutritDepolarizingChannel(x, wires=0).kraus_matrices()) @staticmethod def kraus_fn_real(x): + """Gets a matrix of the real part of the Kraus matrices to be tested.""" return qml.math.real( qml.math.stack(channel.QutritDepolarizingChannel(x, wires=0).kraus_matrices()) ) @staticmethod def kraus_fn_imag(x): + """Gets a matrix of the imaginary part of the Kraus matrices to be tested.""" return qml.math.imag( qml.math.stack(channel.QutritDepolarizingChannel(x, wires=0).kraus_matrices()) ) @pytest.mark.autograd def test_kraus_jac_autograd(self): + """Tests Jacobian of Kraus matrices using autograd.""" p = pnp.array(0.43, requires_grad=True) jac = qml.jacobian(self.kraus_fn_real)(p) + 1j * qml.jacobian(self.kraus_fn_imag)(p) assert qml.math.allclose(jac, self.expected_jac_fn(p)) @pytest.mark.torch def test_kraus_jac_torch(self): + """Tests Jacobian of Kraus matrices using torch.""" import torch p = torch.tensor(0.43, requires_grad=True) @@ -101,16 +148,22 @@ def test_kraus_jac_torch(self): @pytest.mark.tf def test_kraus_jac_tf(self): + """Tests Jacobian of Kraus matrices using tensorflow.""" import tensorflow as tf - p = tf.Variable(0.43) - with tf.GradientTape() as tape: - out = self.kraus_fn(p) - jac = tape.jacobian(out, p) - assert qml.math.allclose(jac, self.expected_jac_fn(p)) + with tf.GradientTape() as real_tape: + real_out = self.kraus_fn_real(p) + with tf.GradientTape() as imag_tape: + imag_out = self.kraus_fn_imag(p) + + real_jac = qml.math.cast(real_tape.jacobian(real_out, p), complex) + imaj_jac = qml.math.cast(imag_tape.jacobian(imag_out, p), complex) + jac = real_jac + 1j * imaj_jac + assert qml.math.allclose(jac, self.expected_jac_fn(0.43)) @pytest.mark.jax def test_kraus_jac_jax(self): + """Tests Jacobian of Kraus matrices using jax.""" import jax jax.config.update("jax_enable_x64", True) From 81d560fe898f03540ebd94cfa913ebd49b631b40 Mon Sep 17 00:00:00 2001 From: gabri Date: Tue, 7 May 2024 15:40:08 -0400 Subject: [PATCH 07/31] Fixed stopping condition test names, added QutritDepolarizingChannel to checked operator and observable lists --- .../devices/qutrit_mixed/test_qutrit_mixed_preprocessing.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/devices/qutrit_mixed/test_qutrit_mixed_preprocessing.py b/tests/devices/qutrit_mixed/test_qutrit_mixed_preprocessing.py index 27bf1331d17..9574fdbec25 100644 --- a/tests/devices/qutrit_mixed/test_qutrit_mixed_preprocessing.py +++ b/tests/devices/qutrit_mixed/test_qutrit_mixed_preprocessing.py @@ -125,9 +125,10 @@ def test_measurement_is_swapped_out(self, mp_fn, mp_cls, shots): (qml.GellMann(0, 1), False), (qml.Snapshot(), True), (qml.TRX(1.1, 0), True), + (qml.QutritDepolarizingChannel(0.4, 0), True), ], ) - def test_accepted_observables(self, op, expected): + def test_accepted_operator(self, op, expected): """Test that stopping_condition works correctly""" res = stopping_condition(op) assert res == expected @@ -136,6 +137,7 @@ def test_accepted_observables(self, op, expected): "obs, expected", [ (qml.TShift(0), False), + (qml.QutritDepolarizingChannel(0.4, 0), False), (qml.GellMann(0, 1), True), (qml.Snapshot(), False), (qml.operation.Tensor(qml.GellMann(0, 1), qml.GellMann(3, 3)), True), @@ -144,7 +146,7 @@ def test_accepted_observables(self, op, expected): (qml.ops.op_math.Prod(qml.GellMann(0, 1), qml.GellMann(3, 3)), True), ], ) - def test_accepted_operator(self, obs, expected): + def test_accepted_observable(self, obs, expected): """Test that observable_stopping_condition works correctly""" res = observable_stopping_condition(obs) assert res == expected From 4e7a42b018874f37a5a0009a29c594dab7bd940c Mon Sep 17 00:00:00 2001 From: gabri Date: Tue, 7 May 2024 15:44:08 -0400 Subject: [PATCH 08/31] Fixed integration test, changed DefaultQutritMixed to get channel list from file --- pennylane/devices/default_qutrit_mixed.py | 3 ++- tests/ops/qutrit/test_qutrit_channel_ops.py | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pennylane/devices/default_qutrit_mixed.py b/pennylane/devices/default_qutrit_mixed.py index 7f98a9497c0..a6d5905e2ba 100644 --- a/pennylane/devices/default_qutrit_mixed.py +++ b/pennylane/devices/default_qutrit_mixed.py @@ -25,6 +25,7 @@ from pennylane.tape import QuantumTape from pennylane.transforms.core import TransformProgram from pennylane.typing import Result, ResultBatch +from pennylane.ops import _qutrit__channel__ops__ as channels from . import Device from .default_qutrit import DefaultQutrit @@ -39,6 +40,7 @@ ) from .qutrit_mixed.simulate import simulate + logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) @@ -49,7 +51,6 @@ # always a function from a resultbatch to either a result or a result batch PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] -channels = set() observables = { "THermitian", "GellMann", diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 97904271e7f..1a5b9923f72 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -75,13 +75,17 @@ def test_grad_depolarizing(self, angle): @qml.qnode(dev) def circuit(p): - qml.TRX(angle, wires=0) + qml.TRX(angle, wires=0, subspace=(0,1)) + qml.TRX(angle, wires=0, subspace=(1,2)) qml.QutritDepolarizingChannel(p, wires=0) return qml.expval(qml.GellMann(0, 3) + qml.GellMann(0, 8)) + expected_errorless = (np.sqrt(3)-3)*(1-np.cos(2*angle))/24 -2/np.sqrt(3)*np.sin(angle/2)**4+(np.sqrt(1/3)+1)*np.cos(angle/2)**2 + assert np.allclose(circuit(prob), prob * expected_errorless) + gradient = np.squeeze(qml.grad(circuit)(prob)) assert np.allclose(gradient, circuit(1) - circuit(0)) - assert np.allclose(gradient, -(4 / 3) * np.cos(angle)) + assert np.allclose(gradient, -expected_errorless) def test_p_invalid_parameter(self): """Test that error is raised given an inappropriate p value.""" From 85fc4b51566b7a4a43771b8bb0ddd7fddab19272 Mon Sep 17 00:00:00 2001 From: gabri Date: Tue, 7 May 2024 17:12:03 -0400 Subject: [PATCH 09/31] Added options for docstring --- pennylane/ops/qutrit/channel.py | 179 ++++++++++++++++++++++++-------- 1 file changed, 133 insertions(+), 46 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 778ee6d24dc..4b8a05ffec0 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -27,21 +27,110 @@ class QutritDepolarizingChannel(Channel): r""" Single-qutrit symmetrically depolarizing error channel. This channel is modelled by the following Kraus matrices: + + .. math:: + K_0 = \sqrt{1-p} \begin{bmatrix} + 1 & 0 & 0\\ + 0 & 1 & 0\\ + 0 & 0 & 1 + \end{bmatrix} + + .. math:: + K_1 = \sqrt{p/9}\begin{bmatrix} + 0 & 1 & 0 \\ + 0 & 0 & 1 \\ + 1 & 0 & 0 + \end{bmatrix} + + .. math:: + K_2 = \sqrt{p/9}\begin{bmatrix} + 0 & 0 & 1 \\ + 1 & 0 & 0 \\ + 0 & 1 & 0 + \end{bmatrix} + + .. math:: + K_3 = \sqrt{p/9}\begin{bmatrix} + 1 & 0 & 0\\ + 0 & \omega & 0\\ + 0 & 0 & \omega^2 + \end{bmatrix} + .. math:: + K_4 = \sqrt{p/9}\begin{bmatrix} + 1 & 0 & 0\\ + 0 & \omega^2 & 0\\ + 0 & 0 & \omega^4 + \end{bmatrix} + + .. math:: + K_5 = \sqrt{p/9}\begin{bmatrix} + 0 & \omega & 0 \\ + 0 & 0 & \omega^2 \\ + 1 & 0 & 0 + \end{bmatrix} + + .. math:: + K_6 = \sqrt{p/9}\begin{bmatrix} + 0 & 0 & omega^2 \\ + 1 & 0 & 0 \\ + 0 & omega & 0 + \end{bmatrix} + + .. math:: + K_7 = \sqrt{p/9}\begin{bmatrix} + 0 & \omega^2 & 0 \\ + 0 & 0 & \omega \\ + 1 & 0 & 0 + \end{bmatrix} + + .. math:: + K_8 = \sqrt{p/9}\begin{bmatrix} + 0 & 0 & \omega \\ + 1 & 0 & 0 \\ + 0 & \omega^2 & 0 + \end{bmatrix} + + OR: + .. math:: + X = \sqrt{p/9}\begin{bmatrix} + 0 & 1 & 0 \\ + 0 & 0 & 1 \\ + 1 & 0 & 0 + \end{bmatrix} + + .. math:: + Z = \sqrt{p/9}\begin{bmatrix} + 1 & 0 & 0\\ + 0 & \omega & 0\\ + 0 & 0 & \omega^2 + \end{bmatrix} + + .. math:: + K_0 = \sqrt{1-p} \begin{bmatrix} + 1 & 0 & 0\\ + 0 & 1 & 0\\ + 0 & 0 & 1 + \end{bmatrix} + + .. math:: + K_{i,j} = \sqrt{p/9}X^iZ^j, \{i,j\} \neq \{0,0\} + + Where :math:`\omega=\exp(\frac{2\pi}{3})` is the phase defining the third root of identity. where :math:`p \in [0, 1]` is the depolarization probability and is equally - divided in the application of all TODO operations. + divided in the application of all qutrit Pauli operators. .. note:: - Multiple equivalent definitions of the Kraus operators :math:`\{K_0 \ldots K_8\}` exist in #TODO fix liturature - the literature [`1 `_] (Eqs. 8.102-103). Here, we adopt the - one from Eq. 8.103, which is also presented in [`2 `_] (Eq. 3.85). # TODO change sources + The Kraus operators :math:`\{K_0 \ldots K_8\} used are the representations of the single qutrit Pauli group. + These Kraus operators are defined in [`1 `_] (Eq. 5). + [`2 `_] (Eq. 3.2) For this definition, please make a note of the following: * For :math:`p = 0`, the channel will be an Identity channel, i.e., a noise-free channel. - * For :math:`p = TODO \frac{3}{4}`, the channel will be a fully depolarizing channel. - * For :math:`p = 1`, the channel will be a uniform TODO error channel. + * For :math:`p = \frac{8}{9}`, the channel will be a fully depolarizing channel. + * For :math:`p = 1`, the channel will be a uniform error channel. **Details:** * Number of wires: 1 * Number of parameters: 1 Args: - p (float): Each TODO gate is applied with probability :math:`\frac{p}{3}` + p (float): Each gate is applied with probability :math:`\frac{p}{9}` wires (Sequence[int] or int): the wire the channel acts on id (str or None): String representing the operation (optional) """ @@ -56,48 +145,46 @@ def __init__(self, p, wires, id=None): def compute_kraus_matrices(p): # pylint:disable=arguments-differ r"""Kraus matrices representing the depolarizing channel. Args: - p (float): each TODO gate is applied with probability :math:`\frac{p}{9}` + p (float): each gate is applied with probability :math:`\frac{p}{9}` Returns: list (array): list of Kraus matrices **Example** - >>> qml.QutritDepolarizingChannel.compute_kraus_matrices(0.5) - [array([[0.74535599+0.j, 0. +0.j, 0. +0.j], - [0. +0.j, 0.74535599+0.j, 0. +0.j], - [0. +0.j, 0. +0.j, 0.74535599+0.j]]), array([[0. , 0.23570226, 0. ], - [0. , 0. , 0.23570226], - [0.23570226, 0. , 0. ]]), array([[0. , 0. , 0.23570226], - [0.23570226, 0. , 0. ], - [0. , 0.23570226, 0. ]]), array([[ 0.23570226+0.j , 0. +0.j , - 0. +0.j ], - [ 0. +0.j , -0.11785113+0.20412415j, - 0. +0.j ], - [ 0. +0.j , 0. +0.j , - -0.11785113-0.20412415j]]), array([[ 0. +0.j , -0.11785113+0.20412415j, - 0. +0.j ], - [ 0. +0.j , 0. +0.j , - -0.11785113-0.20412415j], - [ 0.23570226+0.j , 0. +0.j , - 0. +0.j ]]), array([[ 0. +0.j , 0. +0.j , - -0.11785113-0.20412415j], - [ 0.23570226+0.j , 0. +0.j , - 0. +0.j ], - [ 0. +0.j , -0.11785113+0.20412415j, - 0. +0.j ]]), array([[ 0.23570226+0.j , 0. +0.j , - 0. +0.j ], - [ 0. +0.j , -0.11785113-0.20412415j, - 0. +0.j ], - [ 0. +0.j , 0. +0.j , - -0.11785113+0.20412415j]]), array([[ 0. +0.j , -0.11785113-0.20412415j, - 0. +0.j ], - [ 0. +0.j , 0. +0.j , - -0.11785113+0.20412415j], - [ 0.23570226+0.j , 0. +0.j , - 0. +0.j ]]), array([[ 0. +0.j , 0. +0.j , - -0.11785113+0.20412415j], - [ 0.23570226+0.j , 0. +0.j , - 0. +0.j ], - [ 0. +0.j , -0.11785113-0.20412415j, - 0. +0.j ]])] + >>> np.round(qml.QutritDepolarizingChannel.compute_kraus_matrices(0.5), 3) + array([[[ 0.745+0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , 0.745+0.j , 0. +0.j ], + [ 0. +0.j , 0. +0.j , 0.745+0.j ]], + + [[ 0. +0.j , 0.236+0.j , 0. +0.j ], + [ 0. +0.j , 0. +0.j , 0.236+0.j ], + [ 0.236+0.j , 0. +0.j , 0. +0.j ]], + + [[ 0. +0.j , 0. +0.j , 0.236+0.j ], + [ 0.236+0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , 0.236+0.j , 0. +0.j ]], + + [[ 0.236+0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.118+0.204j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.118-0.204j]], + + [[ 0. +0.j , -0.118+0.204j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.118-0.204j], + [ 0.236+0.j , 0. +0.j , 0. +0.j ]], + + [[ 0. +0.j , 0. +0.j , -0.118-0.204j], + [ 0.236+0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.118+0.204j, 0. +0.j ]], + + [[ 0.236+0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.118-0.204j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.118+0.204j]], + + [[ 0. +0.j , -0.118-0.204j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.118+0.204j], + [ 0.236+0.j , 0. +0.j , 0. +0.j ]], + + [[ 0. +0.j , 0. +0.j , -0.118+0.204j], + [ 0.236+0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.118-0.204j, 0. +0.j ]]]) """ if not math.is_abstract(p) and not 0.0 <= p <= 1.0: raise ValueError("p must be in the interval [0,1]") From 998d21d3b55c8b9bff0c5586a40f376edf0b652b Mon Sep 17 00:00:00 2001 From: gabri Date: Tue, 7 May 2024 17:48:26 -0400 Subject: [PATCH 10/31] Reformatted --- pennylane/ops/qutrit/channel.py | 97 +++++++++++---------- tests/ops/qutrit/test_qutrit_channel_ops.py | 19 ++-- 2 files changed, 61 insertions(+), 55 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 4b8a05ffec0..be188a9fe51 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -134,6 +134,7 @@ class QutritDepolarizingChannel(Channel): wires (Sequence[int] or int): the wire the channel acts on id (str or None): String representing the operation (optional) """ + num_params = 1 num_wires = 1 grad_method = "F" @@ -144,47 +145,47 @@ def __init__(self, p, wires, id=None): @staticmethod def compute_kraus_matrices(p): # pylint:disable=arguments-differ r"""Kraus matrices representing the depolarizing channel. - Args: - p (float): each gate is applied with probability :math:`\frac{p}{9}` - Returns: - list (array): list of Kraus matrices - **Example** - >>> np.round(qml.QutritDepolarizingChannel.compute_kraus_matrices(0.5), 3) - array([[[ 0.745+0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , 0.745+0.j , 0. +0.j ], - [ 0. +0.j , 0. +0.j , 0.745+0.j ]], - - [[ 0. +0.j , 0.236+0.j , 0. +0.j ], - [ 0. +0.j , 0. +0.j , 0.236+0.j ], - [ 0.236+0.j , 0. +0.j , 0. +0.j ]], - - [[ 0. +0.j , 0. +0.j , 0.236+0.j ], - [ 0.236+0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , 0.236+0.j , 0. +0.j ]], - - [[ 0.236+0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.118+0.204j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.118-0.204j]], - - [[ 0. +0.j , -0.118+0.204j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.118-0.204j], - [ 0.236+0.j , 0. +0.j , 0. +0.j ]], - - [[ 0. +0.j , 0. +0.j , -0.118-0.204j], - [ 0.236+0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.118+0.204j, 0. +0.j ]], - - [[ 0.236+0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.118-0.204j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.118+0.204j]], - - [[ 0. +0.j , -0.118-0.204j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.118+0.204j], - [ 0.236+0.j , 0. +0.j , 0. +0.j ]], - - [[ 0. +0.j , 0. +0.j , -0.118+0.204j], - [ 0.236+0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.118-0.204j, 0. +0.j ]]]) + Args: + p (float): each gate is applied with probability :math:`\frac{p}{9}` + Returns: + list (array): list of Kraus matrices + **Example** + >>> np.round(qml.QutritDepolarizingChannel.compute_kraus_matrices(0.5), 3) + array([[[ 0.745+0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , 0.745+0.j , 0. +0.j ], + [ 0. +0.j , 0. +0.j , 0.745+0.j ]], + + [[ 0. +0.j , 0.236+0.j , 0. +0.j ], + [ 0. +0.j , 0. +0.j , 0.236+0.j ], + [ 0.236+0.j , 0. +0.j , 0. +0.j ]], + + [[ 0. +0.j , 0. +0.j , 0.236+0.j ], + [ 0.236+0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , 0.236+0.j , 0. +0.j ]], + + [[ 0.236+0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.118+0.204j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.118-0.204j]], + + [[ 0. +0.j , -0.118+0.204j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.118-0.204j], + [ 0.236+0.j , 0. +0.j , 0. +0.j ]], + + [[ 0. +0.j , 0. +0.j , -0.118-0.204j], + [ 0.236+0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.118+0.204j, 0. +0.j ]], + + [[ 0.236+0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.118-0.204j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.118+0.204j]], + + [[ 0. +0.j , -0.118-0.204j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.118+0.204j], + [ 0.236+0.j , 0. +0.j , 0. +0.j ]], + + [[ 0. +0.j , 0. +0.j , -0.118+0.204j], + [ 0.236+0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.118-0.204j, 0. +0.j ]]]) """ if not math.is_abstract(p) and not 0.0 <= p <= 1.0: raise ValueError("p must be in the interval [0,1]") @@ -202,17 +203,17 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ depolarizing_mats = [ [[z, one, z], [z, z, one], [one, z, z]], [[z, z, one], [one, z, z], [z, one, z]], - - [[one, z, z], [z, w, z], [z, z, w ** 2]], + [[one, z, z], [z, w, z], [z, z, w**2]], [[z, w, 0j], [z, z, w**2], [one, z, z]], [[z, z, w**2], [one, z, z], [z, w, z]], - - [[one, z, z], [z, w ** 2, z], [z, z, w ** 4]], - [[z, w ** 2, z], [z, z, w ** 4], [one, z, z]], - [[z, z, w**4], [one, z, z], [z, w**2, z]] + [[one, z, z], [z, w**2, z], [z, z, w**4]], + [[z, w**2, z], [z, z, w**4], [one, z, z]], + [[z, z, w**4], [one, z, z], [z, w**2, z]], ] normalization = math.sqrt(p / 9 + math.eps) Ks = [normalization * math.array(m, like=interface) for m in depolarizing_mats] - identity = math.sqrt(1 - (8 * p / 9) + math.eps) * math.array(math.eye(QUDIT_DIM, dtype=complex), like=interface) + identity = math.sqrt(1 - (8 * p / 9) + math.eps) * math.array( + math.eye(QUDIT_DIM, dtype=complex), like=interface + ) return [identity] + Ks diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 1a5b9923f72..6bc2f5c8091 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -32,8 +32,8 @@ def get_expected_kraus_matrices(p): w = np.exp(2j * np.pi / 3) Z0 = np.eye(QUDIT_DIM) - Z1 = np.diag([1, w, w ** 2]) - Z2 = np.diag([1, w ** 2, w ** 4]) + Z1 = np.diag([1, w, w**2]) + Z2 = np.diag([1, w**2, w**4]) X0 = np.eye(QUDIT_DIM) X1 = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) @@ -75,12 +75,16 @@ def test_grad_depolarizing(self, angle): @qml.qnode(dev) def circuit(p): - qml.TRX(angle, wires=0, subspace=(0,1)) - qml.TRX(angle, wires=0, subspace=(1,2)) + qml.TRX(angle, wires=0, subspace=(0, 1)) + qml.TRX(angle, wires=0, subspace=(1, 2)) qml.QutritDepolarizingChannel(p, wires=0) return qml.expval(qml.GellMann(0, 3) + qml.GellMann(0, 8)) - expected_errorless = (np.sqrt(3)-3)*(1-np.cos(2*angle))/24 -2/np.sqrt(3)*np.sin(angle/2)**4+(np.sqrt(1/3)+1)*np.cos(angle/2)**2 + expected_errorless = ( + (np.sqrt(3) - 3) * (1 - np.cos(2 * angle)) / 24 + - 2 / np.sqrt(3) * np.sin(angle / 2) ** 4 + + (np.sqrt(1 / 3) + 1) * np.cos(angle / 2) ** 2 + ) assert np.allclose(circuit(prob), prob * expected_errorless) gradient = np.squeeze(qml.grad(circuit)(prob)) @@ -98,8 +102,8 @@ def expected_jac_fn(p): w = np.exp(2j * np.pi / 3) Z0 = np.eye(QUDIT_DIM) - Z1 = np.diag([1, w, w ** 2]) - Z2 = np.diag([1, w ** 2, w ** 4]) + Z1 = np.diag([1, w, w**2]) + Z2 = np.diag([1, w**2, w**4]) X0 = np.eye(QUDIT_DIM) X1 = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) @@ -154,6 +158,7 @@ def test_kraus_jac_torch(self): def test_kraus_jac_tf(self): """Tests Jacobian of Kraus matrices using tensorflow.""" import tensorflow as tf + p = tf.Variable(0.43) with tf.GradientTape() as real_tape: real_out = self.kraus_fn_real(p) From 2059437d8baa2fc71f564a4d2d57edb04e2fefc5 Mon Sep 17 00:00:00 2001 From: gabri Date: Tue, 7 May 2024 22:18:36 -0400 Subject: [PATCH 11/31] Fixed docstring readded formula for parameter-shift --- pennylane/ops/qutrit/channel.py | 130 ++++++++++++++++---------------- 1 file changed, 63 insertions(+), 67 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index be188a9fe51..117ddd852b5 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -26,7 +26,19 @@ class QutritDepolarizingChannel(Channel): r""" Single-qutrit symmetrically depolarizing error channel. - This channel is modelled by the following Kraus matrices: + This channel is modelled by the Kraus matrices generated by the following relationship: + + .. math:: + X = \sqrt{p/9}\begin{bmatrix} + 0 & 1 & 0 \\ + 0 & 0 & 1 \\ + 1 & 0 & 0 + \end{bmatrix},\; + Z = \sqrt{p/9}\begin{bmatrix} + 1 & 0 & 0\\ + 0 & \omega & 0\\ + 0 & 0 & \omega^2 + \end{bmatrix} .. math:: K_0 = \sqrt{1-p} \begin{bmatrix} @@ -36,99 +48,80 @@ class QutritDepolarizingChannel(Channel): \end{bmatrix} .. math:: - K_1 = \sqrt{p/9}\begin{bmatrix} - 0 & 1 & 0 \\ - 0 & 0 & 1 \\ - 1 & 0 & 0 - \end{bmatrix} + K_{i,j} = \sqrt{p/9}X^iZ^j, \{i,j\} \neq \{0,0\} - .. math:: - K_2 = \sqrt{p/9}\begin{bmatrix} - 0 & 0 & 1 \\ - 1 & 0 & 0 \\ - 0 & 1 & 0 - \end{bmatrix} + These relations create the following Kraus matrices: .. math:: - K_3 = \sqrt{p/9}\begin{bmatrix} + \begin{matrix} + K_0 = \sqrt{1-p} \begin{bmatrix} + 1 & 0 & 0\\ + 0 & 1 & 0\\ + 0 & 0 & 1 + \end{bmatrix}& + K_3 = \sqrt{p/9}\begin{bmatrix} 1 & 0 & 0\\ 0 & \omega & 0\\ 0 & 0 & \omega^2 - \end{bmatrix} - .. math:: - K_4 = \sqrt{p/9}\begin{bmatrix} + \end{bmatrix}& + K_6 = \sqrt{p/9}\begin{bmatrix} 1 & 0 & 0\\ 0 & \omega^2 & 0\\ 0 & 0 & \omega^4 - \end{bmatrix} - - .. math:: - K_5 = \sqrt{p/9}\begin{bmatrix} + \end{bmatrix}\\ + K_1 = \sqrt{p/9}\begin{bmatrix} + 0 & 1 & 0 \\ + 0 & 0 & 1 \\ + 1 & 0 & 0 + \end{bmatrix}& + K_4 = \sqrt{p/9}\begin{bmatrix} 0 & \omega & 0 \\ 0 & 0 & \omega^2 \\ 1 & 0 & 0 - \end{bmatrix} - - .. math:: - K_6 = \sqrt{p/9}\begin{bmatrix} - 0 & 0 & omega^2 \\ - 1 & 0 & 0 \\ - 0 & omega & 0 - \end{bmatrix} - - .. math:: - K_7 = \sqrt{p/9}\begin{bmatrix} + \end{bmatrix}& + K_7 = \sqrt{p/9}\begin{bmatrix} 0 & \omega^2 & 0 \\ 0 & 0 & \omega \\ 1 & 0 & 0 - \end{bmatrix} - - .. math:: - K_8 = \sqrt{p/9}\begin{bmatrix} + \end{bmatrix}\\ + K_2 = \sqrt{p/9}\begin{bmatrix} + 0 & 0 & 1 \\ + 1 & 0 & 0 \\ + 0 & 1 & 0 + \end{bmatrix}& + K_5 = \sqrt{p/9}\begin{bmatrix} + 0 & 0 & \omega^2 \\ + 1 & 0 & 0 \\ + 0 & \omega & 0 + \end{bmatrix}& + K_8 = \sqrt{p/9}\begin{bmatrix} 0 & 0 & \omega \\ 1 & 0 & 0 \\ 0 & \omega^2 & 0 \end{bmatrix} + \end{matrix} - OR: - .. math:: - X = \sqrt{p/9}\begin{bmatrix} - 0 & 1 & 0 \\ - 0 & 0 & 1 \\ - 1 & 0 & 0 - \end{bmatrix} - - .. math:: - Z = \sqrt{p/9}\begin{bmatrix} - 1 & 0 & 0\\ - 0 & \omega & 0\\ - 0 & 0 & \omega^2 - \end{bmatrix} - - .. math:: - K_0 = \sqrt{1-p} \begin{bmatrix} - 1 & 0 & 0\\ - 0 & 1 & 0\\ - 0 & 0 & 1 - \end{bmatrix} - - .. math:: - K_{i,j} = \sqrt{p/9}X^iZ^j, \{i,j\} \neq \{0,0\} Where :math:`\omega=\exp(\frac{2\pi}{3})` is the phase defining the third root of identity. where :math:`p \in [0, 1]` is the depolarization probability and is equally divided in the application of all qutrit Pauli operators. + .. note:: - The Kraus operators :math:`\{K_0 \ldots K_8\} used are the representations of the single qutrit Pauli group. - These Kraus operators are defined in [`1 `_] (Eq. 5). - [`2 `_] (Eq. 3.2) + + The Kraus operators :math:`\{K_0 \ldots K_8\}` used are the representations of the single qutrit Pauli group. + These Pauli group operators are defined in [`1 `_] (Eq. 5). + The Kraus Matrices we use are adapted from [`2 `_] (Eq. 5). For this definition, please make a note of the following: + * For :math:`p = 0`, the channel will be an Identity channel, i.e., a noise-free channel. * For :math:`p = \frac{8}{9}`, the channel will be a fully depolarizing channel. * For :math:`p = 1`, the channel will be a uniform error channel. + **Details:** + * Number of wires: 1 * Number of parameters: 1 + Args: p (float): Each gate is applied with probability :math:`\frac{p}{9}` wires (Sequence[int] or int): the wire the channel acts on @@ -137,7 +130,8 @@ class QutritDepolarizingChannel(Channel): num_params = 1 num_wires = 1 - grad_method = "F" + grad_method = "A" + grad_recipe = ([[1, 0, 1], [-1, 0, 0]],) def __init__(self, p, wires, id=None): super().__init__(p, wires=wires, id=id) @@ -149,7 +143,9 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ p (float): each gate is applied with probability :math:`\frac{p}{9}` Returns: list (array): list of Kraus matrices + **Example** + >>> np.round(qml.QutritDepolarizingChannel.compute_kraus_matrices(0.5), 3) array([[[ 0.745+0.j , 0. +0.j , 0. +0.j ], [ 0. +0.j , 0.745+0.j , 0. +0.j ], @@ -204,11 +200,11 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ [[z, one, z], [z, z, one], [one, z, z]], [[z, z, one], [one, z, z], [z, one, z]], [[one, z, z], [z, w, z], [z, z, w**2]], - [[z, w, 0j], [z, z, w**2], [one, z, z]], + [[z, w, z], [z, z, w**2], [one, z, z]], [[z, z, w**2], [one, z, z], [z, w, z]], - [[one, z, z], [z, w**2, z], [z, z, w**4]], - [[z, w**2, z], [z, z, w**4], [one, z, z]], - [[z, z, w**4], [one, z, z], [z, w**2, z]], + [[one, z, z], [z, w**2, z], [z, z, w]], + [[z, w**2, z], [z, z, w], [one, z, z]], + [[z, z, w], [one, z, z], [z, w**2, z]], ] normalization = math.sqrt(p / 9 + math.eps) Ks = [normalization * math.array(m, like=interface) for m in depolarizing_mats] From f5e71028f16fe6c1fcc006d72a3eabf12f81a5c0 Mon Sep 17 00:00:00 2001 From: gabri Date: Wed, 8 May 2024 14:32:09 -0400 Subject: [PATCH 12/31] Fixed probabilities to make p describe probability of error occuring, fixed tests --- pennylane/ops/qutrit/channel.py | 105 ++++++++++---------- tests/ops/qutrit/test_qutrit_channel_ops.py | 14 +-- 2 files changed, 61 insertions(+), 58 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 117ddd852b5..283e86d582b 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -29,12 +29,12 @@ class QutritDepolarizingChannel(Channel): This channel is modelled by the Kraus matrices generated by the following relationship: .. math:: - X = \sqrt{p/9}\begin{bmatrix} + X = \begin{bmatrix} 0 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{bmatrix},\; - Z = \sqrt{p/9}\begin{bmatrix} + Z = \begin{bmatrix} 1 & 0 & 0\\ 0 & \omega & 0\\ 0 & 0 & \omega^2 @@ -48,7 +48,7 @@ class QutritDepolarizingChannel(Channel): \end{bmatrix} .. math:: - K_{i,j} = \sqrt{p/9}X^iZ^j, \{i,j\} \neq \{0,0\} + K_{i,j} = \sqrt{\frac{p}{8}}X^iZ^j, \{i,j\} \neq \{0,0\} These relations create the following Kraus matrices: @@ -59,42 +59,42 @@ class QutritDepolarizingChannel(Channel): 0 & 1 & 0\\ 0 & 0 & 1 \end{bmatrix}& - K_3 = \sqrt{p/9}\begin{bmatrix} + K_3 = \sqrt{\frac{p}{8}}\begin{bmatrix} 1 & 0 & 0\\ 0 & \omega & 0\\ 0 & 0 & \omega^2 \end{bmatrix}& - K_6 = \sqrt{p/9}\begin{bmatrix} + K_6 = \sqrt{\frac{p}{8}}\begin{bmatrix} 1 & 0 & 0\\ 0 & \omega^2 & 0\\ 0 & 0 & \omega^4 \end{bmatrix}\\ - K_1 = \sqrt{p/9}\begin{bmatrix} + K_1 = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{bmatrix}& - K_4 = \sqrt{p/9}\begin{bmatrix} + K_4 = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & \omega & 0 \\ 0 & 0 & \omega^2 \\ 1 & 0 & 0 \end{bmatrix}& - K_7 = \sqrt{p/9}\begin{bmatrix} + K_7 = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & \omega^2 & 0 \\ 0 & 0 & \omega \\ 1 & 0 & 0 \end{bmatrix}\\ - K_2 = \sqrt{p/9}\begin{bmatrix} + K_2 = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 0 \end{bmatrix}& - K_5 = \sqrt{p/9}\begin{bmatrix} + K_5 = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & 0 & \omega^2 \\ 1 & 0 & 0 \\ 0 & \omega & 0 \end{bmatrix}& - K_8 = \sqrt{p/9}\begin{bmatrix} + K_8 = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & 0 & \omega \\ 1 & 0 & 0 \\ 0 & \omega^2 & 0 @@ -139,49 +139,49 @@ def __init__(self, p, wires, id=None): @staticmethod def compute_kraus_matrices(p): # pylint:disable=arguments-differ r"""Kraus matrices representing the depolarizing channel. - Args: - p (float): each gate is applied with probability :math:`\frac{p}{9}` - Returns: - list (array): list of Kraus matrices + Args: + p (float): each gate is applied with probability :math:`\frac{p}{9}` + Returns: + list (array): list of Kraus matrices - **Example** + **Example** - >>> np.round(qml.QutritDepolarizingChannel.compute_kraus_matrices(0.5), 3) - array([[[ 0.745+0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , 0.745+0.j , 0. +0.j ], - [ 0. +0.j , 0. +0.j , 0.745+0.j ]], + >>> np.round(qml.QutritDepolarizingChannel.compute_kraus_matrices(0.5), 3) + array([[[ 0.707+0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , 0.707+0.j , 0. +0.j ], + [ 0. +0.j , 0. +0.j , 0.707+0.j ]], - [[ 0. +0.j , 0.236+0.j , 0. +0.j ], - [ 0. +0.j , 0. +0.j , 0.236+0.j ], - [ 0.236+0.j , 0. +0.j , 0. +0.j ]], + [[ 0. +0.j , 0.25 +0.j , 0. +0.j ], + [ 0. +0.j , 0. +0.j , 0.25 +0.j ], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], - [[ 0. +0.j , 0. +0.j , 0.236+0.j ], - [ 0.236+0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , 0.236+0.j , 0. +0.j ]], + [[ 0. +0.j , 0. +0.j , 0.25 +0.j ], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , 0.25 +0.j , 0. +0.j ]], - [[ 0.236+0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.118+0.204j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.118-0.204j]], + [[ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.125+0.217j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.125-0.217j]], - [[ 0. +0.j , -0.118+0.204j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.118-0.204j], - [ 0.236+0.j , 0. +0.j , 0. +0.j ]], + [[ 0. +0.j , -0.125+0.217j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.125-0.217j], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], - [[ 0. +0.j , 0. +0.j , -0.118-0.204j], - [ 0.236+0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.118+0.204j, 0. +0.j ]], + [[ 0. +0.j , 0. +0.j , -0.125-0.217j], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.125+0.217j, 0. +0.j ]], - [[ 0.236+0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.118-0.204j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.118+0.204j]], + [[ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.125-0.217j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.125+0.217j]], - [[ 0. +0.j , -0.118-0.204j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.118+0.204j], - [ 0.236+0.j , 0. +0.j , 0. +0.j ]], + [[ 0. +0.j , -0.125-0.217j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.125+0.217j], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], - [[ 0. +0.j , 0. +0.j , -0.118+0.204j], - [ 0.236+0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.118-0.204j, 0. +0.j ]]]) + [[ 0. +0.j , 0. +0.j , -0.125+0.217j], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.125-0.217j, 0. +0.j ]]]) """ if not math.is_abstract(p) and not 0.0 <= p <= 1.0: raise ValueError("p must be in the interval [0,1]") @@ -195,20 +195,21 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ w = math.cast_like(w, p) one = math.cast_like(one, p) z = math.cast_like(z, p) + w2 = w**2 depolarizing_mats = [ [[z, one, z], [z, z, one], [one, z, z]], [[z, z, one], [one, z, z], [z, one, z]], - [[one, z, z], [z, w, z], [z, z, w**2]], - [[z, w, z], [z, z, w**2], [one, z, z]], - [[z, z, w**2], [one, z, z], [z, w, z]], - [[one, z, z], [z, w**2, z], [z, z, w]], - [[z, w**2, z], [z, z, w], [one, z, z]], - [[z, z, w], [one, z, z], [z, w**2, z]], + [[one, z, z], [z, w, z], [z, z, w2]], + [[z, w, z], [z, z, w2], [one, z, z]], + [[z, z, w2], [one, z, z], [z, w, z]], + [[one, z, z], [z, w2, z], [z, z, w]], + [[z, w2, z], [z, z, w], [one, z, z]], + [[z, z, w], [one, z, z], [z, w2, z]], ] - normalization = math.sqrt(p / 9 + math.eps) + normalization = math.sqrt(p / 8 + math.eps) Ks = [normalization * math.array(m, like=interface) for m in depolarizing_mats] - identity = math.sqrt(1 - (8 * p / 9) + math.eps) * math.array( + identity = math.sqrt(1 - p + math.eps) * math.array( math.eye(QUDIT_DIM, dtype=complex), like=interface ) diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 6bc2f5c8091..9c696f198ef 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -39,12 +39,12 @@ def get_expected_kraus_matrices(p): X1 = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) X2 = np.array([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) - Ks = [np.sqrt(1 - (8 * p / 9)) * np.eye(QUDIT_DIM)] + Ks = [np.sqrt(1 - p) * np.eye(QUDIT_DIM)] for i, Z in enumerate((Z0, Z1, Z2)): for j, X in enumerate((X0, X1, X2)): if i == 0 and j == 0: continue - Ks.append(np.sqrt(p / 9) * X @ Z) + Ks.append(np.sqrt(p / 8) * X @ Z) return Ks @@ -85,11 +85,13 @@ def circuit(p): - 2 / np.sqrt(3) * np.sin(angle / 2) ** 4 + (np.sqrt(1 / 3) + 1) * np.cos(angle / 2) ** 2 ) - assert np.allclose(circuit(prob), prob * expected_errorless) + + assert np.allclose(circuit(prob), ((prob - (1 / 9)) / (8 / 9)) * expected_errorless) gradient = np.squeeze(qml.grad(circuit)(prob)) + print(circuit(prob) / gradient) assert np.allclose(gradient, circuit(1) - circuit(0)) - assert np.allclose(gradient, -expected_errorless) + assert np.allclose(gradient, -(9 / 8) * expected_errorless) def test_p_invalid_parameter(self): """Test that error is raised given an inappropriate p value.""" @@ -109,12 +111,12 @@ def expected_jac_fn(p): X1 = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) X2 = np.array([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) - jacs = [-(4 / 9) / np.sqrt(1 - (8 * p / 9)) * np.eye(QUDIT_DIM)] + jacs = [-1 / (2 * np.sqrt(1 - p)) * np.eye(QUDIT_DIM)] for i, Z in enumerate((Z0, Z1, Z2)): for j, X in enumerate((X0, X1, X2)): if i == 0 and j == 0: continue - jacs.append(1 / (18 * np.sqrt(p / 9)) * X @ Z) + jacs.append(np.sqrt(1 / (32 * p)) * X @ Z) return jacs From c48291cc08fa0b45badf33cf835697c0c7504d25 Mon Sep 17 00:00:00 2001 From: gabri Date: Wed, 8 May 2024 15:02:48 -0400 Subject: [PATCH 13/31] Re-ordered Kraus matrices --- pennylane/ops/qutrit/channel.py | 88 ++++++++++----------- tests/ops/qutrit/test_qutrit_channel_ops.py | 32 +++----- 2 files changed, 55 insertions(+), 65 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 283e86d582b..cae6958126c 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -59,17 +59,17 @@ class QutritDepolarizingChannel(Channel): 0 & 1 & 0\\ 0 & 0 & 1 \end{bmatrix}& - K_3 = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_1 = \sqrt{\frac{p}{8}}\begin{bmatrix} 1 & 0 & 0\\ 0 & \omega & 0\\ 0 & 0 & \omega^2 \end{bmatrix}& - K_6 = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_2 = \sqrt{\frac{p}{8}}\begin{bmatrix} 1 & 0 & 0\\ 0 & \omega^2 & 0\\ 0 & 0 & \omega^4 \end{bmatrix}\\ - K_1 = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_3 = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 @@ -79,17 +79,17 @@ class QutritDepolarizingChannel(Channel): 0 & 0 & \omega^2 \\ 1 & 0 & 0 \end{bmatrix}& - K_7 = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_5 = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & \omega^2 & 0 \\ 0 & 0 & \omega \\ 1 & 0 & 0 \end{bmatrix}\\ - K_2 = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_6 = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 0 \end{bmatrix}& - K_5 = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_7 = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & 0 & \omega^2 \\ 1 & 0 & 0 \\ 0 & \omega & 0 @@ -123,7 +123,7 @@ class QutritDepolarizingChannel(Channel): * Number of parameters: 1 Args: - p (float): Each gate is applied with probability :math:`\frac{p}{9}` + p (float): Each gate is applied with probability :math:`\frac{p}{8}` wires (Sequence[int] or int): the wire the channel acts on id (str or None): String representing the operation (optional) """ @@ -139,49 +139,49 @@ def __init__(self, p, wires, id=None): @staticmethod def compute_kraus_matrices(p): # pylint:disable=arguments-differ r"""Kraus matrices representing the depolarizing channel. - Args: - p (float): each gate is applied with probability :math:`\frac{p}{9}` - Returns: - list (array): list of Kraus matrices + Args: + p (float): each gate is applied with probability :math:`\frac{p}{9}` + Returns: + list (array): list of Kraus matrices - **Example** + **Example** - >>> np.round(qml.QutritDepolarizingChannel.compute_kraus_matrices(0.5), 3) - array([[[ 0.707+0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , 0.707+0.j , 0. +0.j ], - [ 0. +0.j , 0. +0.j , 0.707+0.j ]], + >>> np.round(qml.QutritDepolarizingChannel.compute_kraus_matrices(0.5), 3) + array([[[ 0.707+0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , 0.707+0.j , 0. +0.j ], + [ 0. +0.j , 0. +0.j , 0.707+0.j ]], - [[ 0. +0.j , 0.25 +0.j , 0. +0.j ], - [ 0. +0.j , 0. +0.j , 0.25 +0.j ], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], + [[ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.125+0.217j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.125-0.217j]], - [[ 0. +0.j , 0. +0.j , 0.25 +0.j ], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , 0.25 +0.j , 0. +0.j ]], + [[ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.125-0.217j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.125+0.217j]], - [[ 0.25 +0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.125+0.217j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.125-0.217j]], + [[ 0. +0.j , 0.25 +0.j , 0. +0.j ], + [ 0. +0.j , 0. +0.j , 0.25 +0.j ], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], - [[ 0. +0.j , -0.125+0.217j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.125-0.217j], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], + [[ 0. +0.j , -0.125+0.217j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.125-0.217j], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], - [[ 0. +0.j , 0. +0.j , -0.125-0.217j], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.125+0.217j, 0. +0.j ]], + [[ 0. +0.j , -0.125-0.217j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.125+0.217j], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], - [[ 0.25 +0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.125-0.217j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.125+0.217j]], + [[ 0. +0.j , 0. +0.j , 0.25 +0.j ], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , 0.25 +0.j , 0. +0.j ]], - [[ 0. +0.j , -0.125-0.217j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.125+0.217j], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], + [[ 0. +0.j , 0. +0.j , -0.125-0.217j], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.125+0.217j, 0. +0.j ]], - [[ 0. +0.j , 0. +0.j , -0.125+0.217j], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.125-0.217j, 0. +0.j ]]]) + [[ 0. +0.j , 0. +0.j , -0.125+0.217j], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.125-0.217j, 0. +0.j ]]]) """ if not math.is_abstract(p) and not 0.0 <= p <= 1.0: raise ValueError("p must be in the interval [0,1]") @@ -198,13 +198,13 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ w2 = w**2 depolarizing_mats = [ - [[z, one, z], [z, z, one], [one, z, z]], - [[z, z, one], [one, z, z], [z, one, z]], [[one, z, z], [z, w, z], [z, z, w2]], - [[z, w, z], [z, z, w2], [one, z, z]], - [[z, z, w2], [one, z, z], [z, w, z]], [[one, z, z], [z, w2, z], [z, z, w]], + [[z, one, z], [z, z, one], [one, z, z]], + [[z, w, z], [z, z, w2], [one, z, z]], [[z, w2, z], [z, z, w], [one, z, z]], + [[z, z, one], [one, z, z], [z, one, z]], + [[z, z, w2], [one, z, z], [z, w, z]], [[z, z, w], [one, z, z], [z, w2, z]], ] normalization = math.sqrt(p / 8 + math.eps) diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 9c696f198ef..5b64e7c3db4 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -16,6 +16,7 @@ """ import pytest import numpy as np +from numpy.linalg import matrix_power import pennylane as qml from pennylane import numpy as pnp from pennylane.ops.qutrit import channel @@ -31,21 +32,15 @@ def get_expected_kraus_matrices(p): """Gets the expected Kraus matrices given probability p.""" w = np.exp(2j * np.pi / 3) - Z0 = np.eye(QUDIT_DIM) - Z1 = np.diag([1, w, w**2]) - Z2 = np.diag([1, w**2, w**4]) - - X0 = np.eye(QUDIT_DIM) - X1 = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) - X2 = np.array([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) + X = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) + Z = np.diag([1, w, w**2]) Ks = [np.sqrt(1 - p) * np.eye(QUDIT_DIM)] - for i, Z in enumerate((Z0, Z1, Z2)): - for j, X in enumerate((X0, X1, X2)): + for i in range(3): + for j in range(3): if i == 0 and j == 0: continue - Ks.append(np.sqrt(p / 8) * X @ Z) - + Ks.append(np.sqrt(p / 8) * matrix_power(X, i) @ matrix_power(Z, j)) return Ks def test_p_zero(self, tol): @@ -103,20 +98,15 @@ def expected_jac_fn(p): """Gets the expected Jacobian of Kraus matrices given probability p.""" w = np.exp(2j * np.pi / 3) - Z0 = np.eye(QUDIT_DIM) - Z1 = np.diag([1, w, w**2]) - Z2 = np.diag([1, w**2, w**4]) - - X0 = np.eye(QUDIT_DIM) - X1 = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) - X2 = np.array([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) + X = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) + Z = np.diag([1, w, w**2]) jacs = [-1 / (2 * np.sqrt(1 - p)) * np.eye(QUDIT_DIM)] - for i, Z in enumerate((Z0, Z1, Z2)): - for j, X in enumerate((X0, X1, X2)): + for i in range(3): + for j in range(3): if i == 0 and j == 0: continue - jacs.append(np.sqrt(1 / (32 * p)) * X @ Z) + jacs.append(np.sqrt(1 / (32 * p)) * matrix_power(X, i) @ matrix_power(Z, j)) return jacs From 27a69f0d262c2f701d0321e8bf96e3a68cf249a0 Mon Sep 17 00:00:00 2001 From: gabri Date: Wed, 8 May 2024 15:08:46 -0400 Subject: [PATCH 14/31] Finishing touches --- pennylane/ops/qutrit/channel.py | 2 +- tests/ops/qutrit/test_qutrit_channel_ops.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index cae6958126c..d058dc2df9b 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -1,4 +1,4 @@ -# Copyright 2024 Xanadu Quantum Technologies Inc. +# Copyright 2018-2024 Xanadu Quantum Technologies Inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 5b64e7c3db4..2574bd20814 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -57,8 +57,8 @@ def test_p_arbitrary(self, tol): p = 0.1 kraus_matrices = qml.QutritDepolarizingChannel(p, wires=0).kraus_matrices() expected_matrices = self.get_expected_kraus_matrices(p) - for krause_matrix, expected_matrix in zip(kraus_matrices, expected_matrices): - assert np.allclose(krause_matrix, expected_matrix, atol=tol, rtol=0) + for kraus_matrix, expected_matrix in zip(kraus_matrices, expected_matrices): + assert np.allclose(kraus_matrix, expected_matrix, atol=tol, rtol=0) @pytest.mark.parametrize("angle", np.linspace(0, 2 * np.pi, 7)) def test_grad_depolarizing(self, angle): From fc33fead2f8587521bfb6990ff532d05f9e8578c Mon Sep 17 00:00:00 2001 From: gabri Date: Wed, 8 May 2024 15:13:39 -0400 Subject: [PATCH 15/31] Added changelog comments --- doc/releases/changelog-dev.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 0b381609d9a..42bd78dfc20 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -71,6 +71,11 @@ * ``qml.load`` has been removed in favour of more specific functions, such as ``qml.from_qiskit``, etc. [(#5654)](https://github.com/PennyLaneAI/pennylane/pull/5654) +

Community contributions 🥳

+ +* ``qml.QutritDepolarizingChannel`` has been added, allowing for depolarizing noise to be simulated on the `default.qutrit.mixed` device. + [(#5502)](https://github.com/PennyLaneAI/pennylane/pull/5502) +

Deprecations 👋

Documentation 📝

From 62bf03a60d71146fc4ecd08293cd23255580142e Mon Sep 17 00:00:00 2001 From: gabri Date: Wed, 8 May 2024 15:26:16 -0400 Subject: [PATCH 16/31] Fix imports for pylint --- pennylane/devices/default_qutrit_mixed.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pennylane/devices/default_qutrit_mixed.py b/pennylane/devices/default_qutrit_mixed.py index a6d5905e2ba..a9f1bd5c127 100644 --- a/pennylane/devices/default_qutrit_mixed.py +++ b/pennylane/devices/default_qutrit_mixed.py @@ -13,7 +13,6 @@ # limitations under the License. """The default.qutrit.mixed device is PennyLane's standard qutrit simulator for mixed-state computations.""" - import inspect import logging from dataclasses import replace From a057c76a9ebda1be3e74e2da8b3e3a957c134302 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill <78718539+Gabriel-Bottrill@users.noreply.github.com> Date: Mon, 13 May 2024 12:14:06 -0400 Subject: [PATCH 17/31] Apply suggestions from code review Adding suggestions Co-authored-by: Olivia Di Matteo <2068515+glassnotes@users.noreply.github.com> --- pennylane/ops/qutrit/channel.py | 15 ++++++++++----- tests/ops/qutrit/test_qutrit_channel_ops.py | 7 +++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index d058dc2df9b..aca353ac085 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -13,7 +13,7 @@ # limitations under the License. # pylint: disable=too-many-arguments """ -This module contains the available built-in noisy +This module contains the available built-in noisy qutrit quantum channels supported by PennyLane, as well as their conventions. """ import numpy as np @@ -102,7 +102,7 @@ class QutritDepolarizingChannel(Channel): \end{matrix} - Where :math:`\omega=\exp(\frac{2\pi}{3})` is the phase defining the third root of identity. + Where :math:`\omega=\exp(\frac{2\pi}{3})` is the third root of unity. where :math:`p \in [0, 1]` is the depolarization probability and is equally divided in the application of all qutrit Pauli operators. @@ -123,7 +123,7 @@ class QutritDepolarizingChannel(Channel): * Number of parameters: 1 Args: - p (float): Each gate is applied with probability :math:`\frac{p}{8}` + p (float): Each qutrit Pauli operator is applied with probability :math:`\frac{p}{8}` wires (Sequence[int] or int): the wire the channel acts on id (str or None): String representing the operation (optional) """ @@ -138,9 +138,11 @@ def __init__(self, p, wires, id=None): @staticmethod def compute_kraus_matrices(p): # pylint:disable=arguments-differ - r"""Kraus matrices representing the depolarizing channel. + r"""Kraus matrices representing the qutrit depolarizing channel. + Args: - p (float): each gate is applied with probability :math:`\frac{p}{9}` + p (float): each qutrit Pauli gate is applied with probability :math:`\frac{p}{8}` + Returns: list (array): list of Kraus matrices @@ -187,14 +189,17 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ raise ValueError("p must be in the interval [0,1]") interface = math.get_interface(p) + w = math.exp(2j * np.pi / 3) one = 1 z = 0 + if interface == "tensorflow": p = math.cast_like(p, 1j) w = math.cast_like(w, p) one = math.cast_like(one, p) z = math.cast_like(z, p) + w2 = w**2 depolarizing_mats = [ diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 2574bd20814..bd78f7ed466 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -84,7 +84,6 @@ def circuit(p): assert np.allclose(circuit(prob), ((prob - (1 / 9)) / (8 / 9)) * expected_errorless) gradient = np.squeeze(qml.grad(circuit)(prob)) - print(circuit(prob) / gradient) assert np.allclose(gradient, circuit(1) - circuit(0)) assert np.allclose(gradient, -(9 / 8) * expected_errorless) @@ -111,7 +110,7 @@ def expected_jac_fn(p): return jacs @staticmethod - def kraus_fn(x): + def kraus_fn(p): """Gets a matrix of the Kraus matrices to be tested.""" return qml.math.stack(channel.QutritDepolarizingChannel(x, wires=0).kraus_matrices()) @@ -158,8 +157,8 @@ def test_kraus_jac_tf(self): imag_out = self.kraus_fn_imag(p) real_jac = qml.math.cast(real_tape.jacobian(real_out, p), complex) - imaj_jac = qml.math.cast(imag_tape.jacobian(imag_out, p), complex) - jac = real_jac + 1j * imaj_jac + imag_jac = qml.math.cast(imag_tape.jacobian(imag_out, p), complex) + jac = real_jac + 1j * imag_jac assert qml.math.allclose(jac, self.expected_jac_fn(0.43)) @pytest.mark.jax From 0cd047ce0001a9be5691f1104b0db52a884e86d6 Mon Sep 17 00:00:00 2001 From: gabri Date: Wed, 8 May 2024 17:17:36 -0400 Subject: [PATCH 18/31] Try fixing imports ordering again --- pennylane/devices/default_qutrit_mixed.py | 1 - pennylane/ops/qutrit/channel.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/devices/default_qutrit_mixed.py b/pennylane/devices/default_qutrit_mixed.py index a9f1bd5c127..e8c1a890e4c 100644 --- a/pennylane/devices/default_qutrit_mixed.py +++ b/pennylane/devices/default_qutrit_mixed.py @@ -17,7 +17,6 @@ import logging from dataclasses import replace from typing import Callable, Optional, Sequence, Tuple, Union - import numpy as np import pennylane as qml diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index aca353ac085..b7ce668ae75 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -17,6 +17,7 @@ quantum channels supported by PennyLane, as well as their conventions. """ import numpy as np + from pennylane import math from pennylane.operation import Channel From 7058694fbaf35ba1df3a4296fabd6db5103c0f4d Mon Sep 17 00:00:00 2001 From: gabri Date: Mon, 13 May 2024 14:04:19 -0400 Subject: [PATCH 19/31] Added most of requested changes --- doc/releases/changelog-dev.md | 1 + pennylane/ops/qutrit/channel.py | 37 ++++++++++----------- tests/ops/qutrit/test_qutrit_channel_ops.py | 10 +++--- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 3a4491943a6..f437484e5f3 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -105,6 +105,7 @@ This release contains contributions from (in alphabetical order): +Gabriel Bottrill, Pietropaolo Frisoni, Soran Jahangiri, Christina Lee, diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index b7ce668ae75..ead9be91b50 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -29,6 +29,15 @@ class QutritDepolarizingChannel(Channel): Single-qutrit symmetrically depolarizing error channel. This channel is modelled by the Kraus matrices generated by the following relationship: + .. math:: + K_0 = \sqrt{1-p} \begin{bmatrix} + 1 & 0 & 0\\ + 0 & 1 & 0\\ + 0 & 0 & 1 + \end{bmatrix}, \quad + K_{i,j} = \sqrt{\frac{p}{8}}X^iZ^j + + Where: .. math:: X = \begin{bmatrix} 0 & 1 & 0 \\ @@ -39,17 +48,8 @@ class QutritDepolarizingChannel(Channel): 1 & 0 & 0\\ 0 & \omega & 0\\ 0 & 0 & \omega^2 - \end{bmatrix} - - .. math:: - K_0 = \sqrt{1-p} \begin{bmatrix} - 1 & 0 & 0\\ - 0 & 1 & 0\\ - 0 & 0 & 1 - \end{bmatrix} + \end{bmatrix}, \text{and} \{i,j\} \neq \{0,0\} - .. math:: - K_{i,j} = \sqrt{\frac{p}{8}}X^iZ^j, \{i,j\} \neq \{0,0\} These relations create the following Kraus matrices: @@ -60,49 +60,48 @@ class QutritDepolarizingChannel(Channel): 0 & 1 & 0\\ 0 & 0 & 1 \end{bmatrix}& - K_1 = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_{0,1} = \sqrt{\frac{p}{8}}\begin{bmatrix} 1 & 0 & 0\\ 0 & \omega & 0\\ 0 & 0 & \omega^2 \end{bmatrix}& - K_2 = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_{0,2} = \sqrt{\frac{p}{8}}\begin{bmatrix} 1 & 0 & 0\\ 0 & \omega^2 & 0\\ 0 & 0 & \omega^4 \end{bmatrix}\\ - K_3 = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_{1,0} = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{bmatrix}& - K_4 = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_{1,1} = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & \omega & 0 \\ 0 & 0 & \omega^2 \\ 1 & 0 & 0 \end{bmatrix}& - K_5 = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_{1,2} = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & \omega^2 & 0 \\ 0 & 0 & \omega \\ 1 & 0 & 0 \end{bmatrix}\\ - K_6 = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_{2,0} = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 0 \end{bmatrix}& - K_7 = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_{2,1} = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & 0 & \omega^2 \\ 1 & 0 & 0 \\ 0 & \omega & 0 \end{bmatrix}& - K_8 = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_{2,2} = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & 0 & \omega \\ 1 & 0 & 0 \\ 0 & \omega^2 & 0 \end{bmatrix} \end{matrix} - Where :math:`\omega=\exp(\frac{2\pi}{3})` is the third root of unity. where :math:`p \in [0, 1]` is the depolarization probability and is equally divided in the application of all qutrit Pauli operators. diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index bd78f7ed466..21d0fe1f45f 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -60,6 +60,11 @@ def test_p_arbitrary(self, tol): for kraus_matrix, expected_matrix in zip(kraus_matrices, expected_matrices): assert np.allclose(kraus_matrix, expected_matrix, atol=tol, rtol=0) + def test_p_invalid_parameter(self): + """Test that error is raised given an inappropriate p value.""" + with pytest.raises(ValueError, match="p must be in the interval"): + qml.QutritDepolarizingChannel(1.5, wires=0).kraus_matrices() + @pytest.mark.parametrize("angle", np.linspace(0, 2 * np.pi, 7)) def test_grad_depolarizing(self, angle): """Test that analytical gradient is computed correctly for different states. Channel @@ -87,11 +92,6 @@ def circuit(p): assert np.allclose(gradient, circuit(1) - circuit(0)) assert np.allclose(gradient, -(9 / 8) * expected_errorless) - def test_p_invalid_parameter(self): - """Test that error is raised given an inappropriate p value.""" - with pytest.raises(ValueError, match="p must be in the interval"): - qml.QutritDepolarizingChannel(1.5, wires=0).kraus_matrices() - @staticmethod def expected_jac_fn(p): """Gets the expected Jacobian of Kraus matrices given probability p.""" From 35d0d8658896f1d3bc45ba7144c523d3150b8982 Mon Sep 17 00:00:00 2001 From: gabri Date: Mon, 13 May 2024 14:50:26 -0400 Subject: [PATCH 20/31] Finished most of suggested comments --- pennylane/ops/qutrit/channel.py | 75 +++++++++++---------- tests/ops/qutrit/test_qutrit_channel_ops.py | 10 +-- 2 files changed, 45 insertions(+), 40 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index ead9be91b50..6afafff5d36 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -16,6 +16,8 @@ This module contains the available built-in noisy qutrit quantum channels supported by PennyLane, as well as their conventions. """ +from itertools import product + import numpy as np from pennylane import math @@ -60,42 +62,42 @@ class QutritDepolarizingChannel(Channel): 0 & 1 & 0\\ 0 & 0 & 1 \end{bmatrix}& - K_{0,1} = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_1 = K_{0,1} = \sqrt{\frac{p}{8}}\begin{bmatrix} 1 & 0 & 0\\ 0 & \omega & 0\\ 0 & 0 & \omega^2 \end{bmatrix}& - K_{0,2} = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_2 = K_{0,2} = \sqrt{\frac{p}{8}}\begin{bmatrix} 1 & 0 & 0\\ 0 & \omega^2 & 0\\ 0 & 0 & \omega^4 \end{bmatrix}\\ - K_{1,0} = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_3 = K_{1,0} = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{bmatrix}& - K_{1,1} = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_4 = K_{1,1} = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & \omega & 0 \\ 0 & 0 & \omega^2 \\ 1 & 0 & 0 \end{bmatrix}& - K_{1,2} = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_5 = K_{1,2} = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & \omega^2 & 0 \\ 0 & 0 & \omega \\ 1 & 0 & 0 \end{bmatrix}\\ - K_{2,0} = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_6 = K_{2,0} = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 0 \end{bmatrix}& - K_{2,1} = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_7 = K_{2,1} = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & 0 & \omega^2 \\ 1 & 0 & 0 \\ 0 & \omega & 0 \end{bmatrix}& - K_{2,2} = \sqrt{\frac{p}{8}}\begin{bmatrix} + K_8 = K_{2,2} = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & 0 & \omega \\ 1 & 0 & 0 \\ 0 & \omega^2 & 0 @@ -150,40 +152,40 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ >>> np.round(qml.QutritDepolarizingChannel.compute_kraus_matrices(0.5), 3) array([[[ 0.707+0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , 0.707+0.j , 0. +0.j ], - [ 0. +0.j , 0. +0.j , 0.707+0.j ]], + [ 0. +0.j , 0.707+0.j , 0. +0.j ], + [ 0. +0.j , 0. +0.j , 0.707+0.j ]], - [[ 0.25 +0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.125+0.217j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.125-0.217j]], + [[ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.125+0.217j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.125-0.217j]], - [[ 0.25 +0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.125-0.217j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.125+0.217j]], + [[ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.125-0.217j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.125+0.217j]], - [[ 0. +0.j , 0.25 +0.j , 0. +0.j ], - [ 0. +0.j , 0. +0.j , 0.25 +0.j ], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], + [[ 0. +0.j , 0.25 +0.j , 0. +0.j ], + [ 0. +0.j , 0. +0.j , 0.25 +0.j ], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], - [[ 0. +0.j , -0.125+0.217j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.125-0.217j], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], + [[ 0. +0.j , -0.125+0.217j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.125-0.217j], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], - [[ 0. +0.j , -0.125-0.217j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.125+0.217j], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], + [[ 0. +0.j , -0.125-0.217j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.125+0.217j], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], - [[ 0. +0.j , 0. +0.j , 0.25 +0.j ], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , 0.25 +0.j , 0. +0.j ]], + [[ 0. +0.j , 0. +0.j , 0.25 +0.j ], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , 0.25 +0.j , 0. +0.j ]], - [[ 0. +0.j , 0. +0.j , -0.125-0.217j], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.125+0.217j, 0. +0.j ]], + [[ 0. +0.j , 0. +0.j , -0.125-0.217j], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.125+0.217j, 0. +0.j ]], - [[ 0. +0.j , 0. +0.j , -0.125+0.217j], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.125-0.217j, 0. +0.j ]]]) + [[ 0. +0.j , 0. +0.j , -0.125+0.217j], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.125-0.217j, 0. +0.j ]]]) """ if not math.is_abstract(p) and not 0.0 <= p <= 1.0: raise ValueError("p must be in the interval [0,1]") @@ -200,7 +202,7 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ one = math.cast_like(one, p) z = math.cast_like(z, p) - w2 = w**2 + w2 = w ** 2 depolarizing_mats = [ [[one, z, z], [z, w, z], [z, z, w2]], @@ -212,6 +214,7 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ [[z, z, w2], [one, z, z], [z, w, z]], [[z, z, w], [one, z, z], [z, w2, z]], ] + normalization = math.sqrt(p / 8 + math.eps) Ks = [normalization * math.array(m, like=interface) for m in depolarizing_mats] identity = math.sqrt(1 - p + math.eps) * math.array( @@ -219,3 +222,5 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ ) return [identity] + Ks + + diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 21d0fe1f45f..0a278fd7ea2 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -112,20 +112,20 @@ def expected_jac_fn(p): @staticmethod def kraus_fn(p): """Gets a matrix of the Kraus matrices to be tested.""" - return qml.math.stack(channel.QutritDepolarizingChannel(x, wires=0).kraus_matrices()) + return qml.math.stack(channel.QutritDepolarizingChannel(p, wires=0).kraus_matrices()) @staticmethod - def kraus_fn_real(x): + def kraus_fn_real(p): """Gets a matrix of the real part of the Kraus matrices to be tested.""" return qml.math.real( - qml.math.stack(channel.QutritDepolarizingChannel(x, wires=0).kraus_matrices()) + qml.math.stack(channel.QutritDepolarizingChannel(p, wires=0).kraus_matrices()) ) @staticmethod - def kraus_fn_imag(x): + def kraus_fn_imag(p): """Gets a matrix of the imaginary part of the Kraus matrices to be tested.""" return qml.math.imag( - qml.math.stack(channel.QutritDepolarizingChannel(x, wires=0).kraus_matrices()) + qml.math.stack(channel.QutritDepolarizingChannel(p, wires=0).kraus_matrices()) ) @pytest.mark.autograd From c3c5857f21cacba8cbc0eed019f99ff16ef979ac Mon Sep 17 00:00:00 2001 From: gabri Date: Mon, 13 May 2024 14:59:34 -0400 Subject: [PATCH 21/31] Reformatted --- pennylane/ops/qutrit/channel.py | 72 ++++++++++++++++----------------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 6afafff5d36..31d899b268f 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -16,8 +16,6 @@ This module contains the available built-in noisy qutrit quantum channels supported by PennyLane, as well as their conventions. """ -from itertools import product - import numpy as np from pennylane import math @@ -142,50 +140,50 @@ def __init__(self, p, wires, id=None): def compute_kraus_matrices(p): # pylint:disable=arguments-differ r"""Kraus matrices representing the qutrit depolarizing channel. - Args: - p (float): each qutrit Pauli gate is applied with probability :math:`\frac{p}{8}` + Args: + p (float): each qutrit Pauli gate is applied with probability :math:`\frac{p}{8}` - Returns: - list (array): list of Kraus matrices + Returns: + list (array): list of Kraus matrices - **Example** + **Example** - >>> np.round(qml.QutritDepolarizingChannel.compute_kraus_matrices(0.5), 3) - array([[[ 0.707+0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , 0.707+0.j , 0. +0.j ], - [ 0. +0.j , 0. +0.j , 0.707+0.j ]], + >>> np.round(qml.QutritDepolarizingChannel.compute_kraus_matrices(0.5), 3) + array([[[ 0.707+0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , 0.707+0.j , 0. +0.j ], + [ 0. +0.j , 0. +0.j , 0.707+0.j ]], - [[ 0.25 +0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.125+0.217j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.125-0.217j]], + [[ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.125+0.217j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.125-0.217j]], - [[ 0.25 +0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.125-0.217j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.125+0.217j]], + [[ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.125-0.217j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.125+0.217j]], - [[ 0. +0.j , 0.25 +0.j , 0. +0.j ], - [ 0. +0.j , 0. +0.j , 0.25 +0.j ], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], + [[ 0. +0.j , 0.25 +0.j , 0. +0.j ], + [ 0. +0.j , 0. +0.j , 0.25 +0.j ], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], - [[ 0. +0.j , -0.125+0.217j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.125-0.217j], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], + [[ 0. +0.j , -0.125+0.217j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.125-0.217j], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], - [[ 0. +0.j , -0.125-0.217j, 0. +0.j ], - [ 0. +0.j , 0. +0.j , -0.125+0.217j], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], + [[ 0. +0.j , -0.125-0.217j, 0. +0.j ], + [ 0. +0.j , 0. +0.j , -0.125+0.217j], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ]], - [[ 0. +0.j , 0. +0.j , 0.25 +0.j ], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , 0.25 +0.j , 0. +0.j ]], + [[ 0. +0.j , 0. +0.j , 0.25 +0.j ], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , 0.25 +0.j , 0. +0.j ]], - [[ 0. +0.j , 0. +0.j , -0.125-0.217j], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.125+0.217j, 0. +0.j ]], + [[ 0. +0.j , 0. +0.j , -0.125-0.217j], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.125+0.217j, 0. +0.j ]], - [[ 0. +0.j , 0. +0.j , -0.125+0.217j], - [ 0.25 +0.j , 0. +0.j , 0. +0.j ], - [ 0. +0.j , -0.125-0.217j, 0. +0.j ]]]) + [[ 0. +0.j , 0. +0.j , -0.125+0.217j], + [ 0.25 +0.j , 0. +0.j , 0. +0.j ], + [ 0. +0.j , -0.125-0.217j, 0. +0.j ]]]) """ if not math.is_abstract(p) and not 0.0 <= p <= 1.0: raise ValueError("p must be in the interval [0,1]") @@ -202,7 +200,7 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ one = math.cast_like(one, p) z = math.cast_like(z, p) - w2 = w ** 2 + w2 = w**2 depolarizing_mats = [ [[one, z, z], [z, w, z], [z, z, w2]], @@ -222,5 +220,3 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ ) return [identity] + Ks - - From c30ea09716582a862831b116fd271a8e52f5b54d Mon Sep 17 00:00:00 2001 From: gabri Date: Mon, 13 May 2024 15:15:12 -0400 Subject: [PATCH 22/31] Attempted fixing imports --- pennylane/ops/qutrit/channel.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 31d899b268f..8bd203f696a 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -17,7 +17,6 @@ quantum channels supported by PennyLane, as well as their conventions. """ import numpy as np - from pennylane import math from pennylane.operation import Channel From e137c431c413d4d2a7e068927b8bd14f647f9071 Mon Sep 17 00:00:00 2001 From: gabri Date: Mon, 13 May 2024 15:25:47 -0400 Subject: [PATCH 23/31] Fixed spacing for QutritDepolarizingChannel docs --- pennylane/ops/qutrit/channel.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 8bd203f696a..4ea32949c2b 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -37,17 +37,18 @@ class QutritDepolarizingChannel(Channel): K_{i,j} = \sqrt{\frac{p}{8}}X^iZ^j Where: + .. math:: X = \begin{bmatrix} 0 & 1 & 0 \\ 0 & 0 & 1 \\ 1 & 0 & 0 - \end{bmatrix},\; + \end{bmatrix}, \quad Z = \begin{bmatrix} 1 & 0 & 0\\ 0 & \omega & 0\\ 0 & 0 & \omega^2 - \end{bmatrix}, \text{and} \{i,j\} \neq \{0,0\} + \end{bmatrix}, \quad \text{and} \{i,j\} \neq \{0,0\} These relations create the following Kraus matrices: From 909bf99ac8c8311c07f6872c654bc5f5293c2ed1 Mon Sep 17 00:00:00 2001 From: gabri Date: Mon, 13 May 2024 16:47:48 -0400 Subject: [PATCH 24/31] Added Qutrit Noisy Channels to Introduction --- doc/introduction/operations.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/introduction/operations.rst b/doc/introduction/operations.rst index 9666fc58184..50eb65950b0 100644 --- a/doc/introduction/operations.rst +++ b/doc/introduction/operations.rst @@ -495,6 +495,21 @@ Qutrit State preparation :html:`` +.. _intro_ref_ops_qutrit_channels: + +Qutrit Noisy channels +^^^^^^^^^^^^^^^^^^^^^^^^ + + +:html:`
` + +.. autosummary:: + :nosignatures: + + ~pennylane.QutritDepolarizingChannel + +:html:`
` + .. _intro_ref_ops_qutrit_obs: Qutrit Observables From f2b56dbeb2eecbb4ba0c02203efcf38f6abdf81e Mon Sep 17 00:00:00 2001 From: gabri Date: Mon, 13 May 2024 16:50:38 -0400 Subject: [PATCH 25/31] Removed power of 4 on omega since it is equivalent to power of one --- pennylane/ops/qutrit/channel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 4ea32949c2b..7bcac08048a 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -68,7 +68,7 @@ class QutritDepolarizingChannel(Channel): K_2 = K_{0,2} = \sqrt{\frac{p}{8}}\begin{bmatrix} 1 & 0 & 0\\ 0 & \omega^2 & 0\\ - 0 & 0 & \omega^4 + 0 & 0 & \omega \end{bmatrix}\\ K_3 = K_{1,0} = \sqrt{\frac{p}{8}}\begin{bmatrix} 0 & 1 & 0 \\ From e3922b8b47845193309aeab7707f52ef6ebc1782 Mon Sep 17 00:00:00 2001 From: gabri Date: Mon, 13 May 2024 17:14:10 -0400 Subject: [PATCH 26/31] Ran isort on failing files --- pennylane/devices/default_qutrit_mixed.py | 4 ++-- pennylane/ops/qutrit/channel.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pennylane/devices/default_qutrit_mixed.py b/pennylane/devices/default_qutrit_mixed.py index e8c1a890e4c..7c264a9d785 100644 --- a/pennylane/devices/default_qutrit_mixed.py +++ b/pennylane/devices/default_qutrit_mixed.py @@ -17,13 +17,14 @@ import logging from dataclasses import replace from typing import Callable, Optional, Sequence, Tuple, Union + import numpy as np import pennylane as qml +from pennylane.ops import _qutrit__channel__ops__ as channels from pennylane.tape import QuantumTape from pennylane.transforms.core import TransformProgram from pennylane.typing import Result, ResultBatch -from pennylane.ops import _qutrit__channel__ops__ as channels from . import Device from .default_qutrit import DefaultQutrit @@ -38,7 +39,6 @@ ) from .qutrit_mixed.simulate import simulate - logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 7bcac08048a..301cfef5f41 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -17,6 +17,7 @@ quantum channels supported by PennyLane, as well as their conventions. """ import numpy as np + from pennylane import math from pennylane.operation import Channel From c895b70930d0a427c8d1a46222408bb35d8b78d7 Mon Sep 17 00:00:00 2001 From: gabri Date: Mon, 13 May 2024 17:44:30 -0400 Subject: [PATCH 27/31] used isort on tests imports --- tests/ops/qutrit/test_qutrit_channel_ops.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 0a278fd7ea2..be1025d0c26 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -14,9 +14,10 @@ """ Unit tests for the available built-in qutrit quantum channels. """ -import pytest import numpy as np +import pytest from numpy.linalg import matrix_power + import pennylane as qml from pennylane import numpy as pnp from pennylane.ops.qutrit import channel From a37c0e6e5415231eafa8fd59e6eacc2005f58d81 Mon Sep 17 00:00:00 2001 From: gabrielLydian Date: Tue, 14 May 2024 14:45:55 -0700 Subject: [PATCH 28/31] Added suggestions for docs --- doc/introduction/operations.rst | 2 +- pennylane/ops/qutrit/channel.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/introduction/operations.rst b/doc/introduction/operations.rst index 50eb65950b0..970e7b4ecd9 100644 --- a/doc/introduction/operations.rst +++ b/doc/introduction/operations.rst @@ -497,7 +497,7 @@ Qutrit State preparation .. _intro_ref_ops_qutrit_channels: -Qutrit Noisy channels +Qutrit noisy channels ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 301cfef5f41..e860fa98eec 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -30,7 +30,7 @@ class QutritDepolarizingChannel(Channel): This channel is modelled by the Kraus matrices generated by the following relationship: .. math:: - K_0 = \sqrt{1-p} \begin{bmatrix} + K_0 = K_{0,0} = \sqrt{1-p} \begin{bmatrix} 1 & 0 & 0\\ 0 & 1 & 0\\ 0 & 0 & 1 @@ -49,14 +49,14 @@ class QutritDepolarizingChannel(Channel): 1 & 0 & 0\\ 0 & \omega & 0\\ 0 & 0 & \omega^2 - \end{bmatrix}, \quad \text{and} \{i,j\} \neq \{0,0\} + \end{bmatrix} These relations create the following Kraus matrices: .. math:: \begin{matrix} - K_0 = \sqrt{1-p} \begin{bmatrix} + K_0 = K_{0,0} = \sqrt{1-p} \begin{bmatrix} 1 & 0 & 0\\ 0 & 1 & 0\\ 0 & 0 & 1 From 8a0d01d95cf994edd95dac9328e13234b616c8e6 Mon Sep 17 00:00:00 2001 From: gabrielLydian Date: Tue, 14 May 2024 16:45:55 -0700 Subject: [PATCH 29/31] Retrying CI From 9b7073c89dbff397a8c20760c9d64df07dda13bf Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill <78718539+Gabriel-Bottrill@users.noreply.github.com> Date: Wed, 15 May 2024 12:19:19 -0700 Subject: [PATCH 30/31] Apply suggestions from code review Co-authored-by: Olivia Di Matteo <2068515+glassnotes@users.noreply.github.com> --- pennylane/ops/qutrit/channel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index e860fa98eec..2cbfcc32ca1 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -103,14 +103,14 @@ class QutritDepolarizingChannel(Channel): \end{bmatrix} \end{matrix} - Where :math:`\omega=\exp(\frac{2\pi}{3})` is the third root of unity. - where :math:`p \in [0, 1]` is the depolarization probability and is equally + Where :math:`\omega=\exp(\frac{2\pi}{3})` is the third root of unity, + and :math:`p \in [0, 1]` is the depolarization probability, equally divided in the application of all qutrit Pauli operators. .. note:: The Kraus operators :math:`\{K_0 \ldots K_8\}` used are the representations of the single qutrit Pauli group. - These Pauli group operators are defined in [`1 `_] (Eq. 5). + These Pauli group operators are defined in [`1 `_] (Eq. 5). The Kraus Matrices we use are adapted from [`2 `_] (Eq. 5). For this definition, please make a note of the following: From 727e8854b0f63426769d00eb3d37fdb3b5e960e7 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Wed, 15 May 2024 12:28:54 -0700 Subject: [PATCH 31/31] Added dev comment on why matrices are explicitly written --- pennylane/ops/qutrit/channel.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 2cbfcc32ca1..8443fd2de44 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -203,6 +203,7 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ w2 = w**2 + # The matrices are explicitly written, not generated to ensure PyTorch differentiation. depolarizing_mats = [ [[one, z, z], [z, w, z], [z, z, w2]], [[one, z, z], [z, w2, z], [z, z, w]],