From f2b98a63c138247772c91b6e0e0f938ecf8e0039 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Thu, 4 Apr 2024 17:45:18 -0700 Subject: [PATCH 01/46] 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 0538fe7512b9edfe098467bd4e77d342b16bb614 Mon Sep 17 00:00:00 2001 From: gabrielLydian Date: Tue, 9 Apr 2024 13:34:34 -0700 Subject: [PATCH 02/46] Added qutrit amplitude damping channel and tests --- pennylane/ops/qutrit/channel.py | 83 +++++++++++++++++++++ tests/ops/qutrit/test_qutrit_channel_ops.py | 66 ++++++++++++++++ 2 files changed, 149 insertions(+) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index fc537e05d40..309efe5f562 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -94,8 +94,91 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ return Ks +class QutritAmplitudeDampingChannel(Channel): + r""" + Single-qutrit amplitude damping error channel. + Interaction with the environment can lead to changes in the state populations of a qubit. + This is the phenomenon behind scattering, dissipation, attenuation, and spontaneous emission. + It can be modelled by the amplitude damping channel, with the following Kraus matrices: + .. math:: + K_0 = \begin{bmatrix} + 1 & 0 & 0\\ + 0 & \sqrt{1-\gamma_1} & 0 \\ + 0 & 0 & \sqrt{1-\gamma_2} + \end{bmatrix} + .. math:: + K_1 = \begin{bmatrix} + 0 & \sqrt{\gamma_1} & 0 \\ + 0 & 0 & 0 \\ + 0 & 0 & 0 + \end{bmatrix} + .. math:: + K_2 = \begin{bmatrix} + 0 & 0 & \sqrt{\gamma_2} \\ + 0 & 0 & 0 \\ + 0 & 0 & 0 + \end{bmatrix} + where :math:`\gamma \in [0, 1]` is the amplitude damping probability. + **Details:** + * Number of wires: 1 + * Number of parameters: 1 + Args: + gamma (float): amplitude damping probability + 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" + + def __init__(self, gamma_1, gamma_2, wires, id=None): + super().__init__(gamma_1, gamma_2, wires=wires, id=id) + + @staticmethod + def compute_kraus_matrices(gamma_1, gamma_2): # pylint:disable=arguments-differ + """Kraus matrices representing the AmplitudeDamping channel. + Args: + gamma_1 (float): amplitude damping probability #TODO + gamma_2 (float): amplitude damping probability + Returns: + list(array): list of Kraus matrices + **Example** + >>> qml.QutritAmplitudeDampingChannel.compute_kraus_matrices(0.25, 0.25) #TODO + [ + array([ [1. , 0. , 0. ], + [0. , 0.70710678, 0. ], + [0. , 0. , 0.8660254 ]]), + array([ [0. , 0.70710678, 0. ], + [0. , 0. , 0. ], + [0. , 0. , 0. ]]), + array([ [0. , 0. , 0.5 ], + [0. , 0. , 0. ], + [0. , 0. , 0. ]]) + ] + """ + if type(gamma_1) != type(gamma_2): + raise ValueError("p1, p2, and p3 should be of the same type") + + if not math.is_abstract(gamma_1): + for gamma in (gamma_1, gamma_2): + if not 0.0 <= gamma <= 1.0: + raise ValueError("Each probability must be in the interval [0,1]") + if not 0.0 <= gamma_1 + gamma_2 <= 1.0: + raise ValueError("Sum of probabilities must be in the interval [0,1]") + + K0 = math.diag([1, math.sqrt(1 - gamma_1 + math.eps), math.sqrt(1 - gamma_2 + math.eps)]) + K1 = math.sqrt(gamma_1 + math.eps) * math.convert_like( + math.cast_like(math.array([[0, 1, 0], [0, 0, 0], [0, 0, 0]]), gamma_1), gamma_1 + ) + K2 = math.sqrt(gamma_2 + math.eps) * math.convert_like( + math.cast_like(math.array([[0, 0, 1], [0, 0, 0], [0, 0, 0]]), gamma_2), gamma_2 + ) + return [K0, K1, K2] + + __qutrit_channels__ = { "QutritDepolarizingChannel", + "QutritAmplitudeDampingChannel", } __all__ = list(__qutrit_channels__) diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index c2fe3d95714..454ddef2a89 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -118,3 +118,69 @@ def test_kraus_jac_jax(self): 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)) + + +class TestQutritAmplitudeDamping: + """Tests for the qutrit quantum channel QutritAmplitudeDamping""" + + def test_gamma_zero(self, tol): + """Test gamma=0 gives correct Kraus matrices""" + op = channel.AmplitudeDamping + 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_gamma_arbitrary(self, tol): + """Test gamma=0.1 gives correct Kraus matrices""" + op = channel.AmplitudeDamping + expected = [ + np.array([[1.0, 0.0], [0.0, 0.9486833]]), + np.array([[0.0, 0.31622777], [0.0, 0.0]]), + ] + assert np.allclose(op(0.1, wires=0).kraus_matrices(), expected, atol=tol, rtol=0) + + def test_gamma_invalid_parameter(self): + with pytest.raises(ValueError, match="gamma must be in the interval"): + channel.AmplitudeDamping(1.5, wires=0).kraus_matrices() + + @staticmethod + def expected_jac_fn(gamma): + return [ + qml.math.array([[0, 0], [0, -1 / (2 * qml.math.sqrt(1 - gamma))]]), + qml.math.array([[0, 1 / (2 * qml.math.sqrt(gamma))], [0, 0]]), + ] + + @staticmethod + def kraus_fn(x): + return qml.math.stack(channel.AmplitudeDamping(x, wires=0).kraus_matrices()) + + @pytest.mark.autograd + def test_kraus_jac_autograd(self): + gamma = pnp.array(0.43, requires_grad=True) + jac = qml.jacobian(self.kraus_fn)(gamma) + assert qml.math.allclose(jac, self.expected_jac_fn(gamma)) + + @pytest.mark.torch + def test_kraus_jac_torch(self): + import torch + + gamma = torch.tensor(0.43, requires_grad=True) + jac = torch.autograd.functional.jacobian(self.kraus_fn, gamma) + assert qml.math.allclose(jac.detach().numpy(), self.expected_jac_fn(gamma.detach().numpy())) + + @pytest.mark.tf + def test_kraus_jac_tf(self): + import tensorflow as tf + + gamma = tf.Variable(0.43) + with tf.GradientTape() as tape: + out = self.kraus_fn(gamma) + jac = tape.jacobian(out, gamma) + assert qml.math.allclose(jac, self.expected_jac_fn(gamma)) + + @pytest.mark.jax + def test_kraus_jac_jax(self): + import jax + + gamma = jax.numpy.array(0.43) + jac = jax.jacobian(self.kraus_fn)(gamma) + assert qml.math.allclose(jac, self.expected_jac_fn(gamma)) From f56b406e6c387494ffd4f802ad054d287c4daf1b Mon Sep 17 00:00:00 2001 From: gabrielLydian Date: Wed, 10 Apr 2024 21:34:11 -0700 Subject: [PATCH 03/46] 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 04/46] 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 05/46] 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 b759317411ae38425ab162d8ed709209bb65c8ae Mon Sep 17 00:00:00 2001 From: gabrielLydian Date: Thu, 11 Apr 2024 16:34:56 -0700 Subject: [PATCH 06/46] Added small change to tests --- tests/ops/qutrit/test_qutrit_channel_ops.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 38e9b9858ec..1a4a632369c 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -125,13 +125,13 @@ class TestQutritAmplitudeDamping: def test_gamma_zero(self, tol): """Test gamma=0 gives correct Kraus matrices""" - op = channel.AmplitudeDamping + op = channel.QutritAmplitudeDamping 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_gamma_arbitrary(self, tol): """Test gamma=0.1 gives correct Kraus matrices""" - op = channel.AmplitudeDamping + op = channel.QutritAmplitudeDamping expected = [ np.array([[1.0, 0.0], [0.0, 0.9486833]]), np.array([[0.0, 0.31622777], [0.0, 0.0]]), @@ -140,7 +140,7 @@ def test_gamma_arbitrary(self, tol): def test_gamma_invalid_parameter(self): with pytest.raises(ValueError, match="gamma must be in the interval"): - channel.AmplitudeDamping(1.5, wires=0).kraus_matrices() + channel.QutritAmplitudeDamping(1.5, wires=0).kraus_matrices() @staticmethod def expected_jac_fn(gamma): @@ -151,7 +151,7 @@ def expected_jac_fn(gamma): @staticmethod def kraus_fn(x): - return qml.math.stack(channel.AmplitudeDamping(x, wires=0).kraus_matrices()) + return qml.math.stack(channel.QutritAmplitudeDamping(x, wires=0).kraus_matrices()) @pytest.mark.autograd def test_kraus_jac_autograd(self): From ca42c4745f816bb8264574890c53ad061a1f2ec3 Mon Sep 17 00:00:00 2001 From: gabrielLydian Date: Tue, 16 Apr 2024 19:42:13 -0700 Subject: [PATCH 07/46] 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 08/46] 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 09/46] 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 10/46] 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 11/46] 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 12/46] 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 13/46] 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 14/46] 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 15/46] 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 16/46] 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 17/46] 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 18/46] 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 19/46] 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 20/46] 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 21/46] 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 22/46] 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 23/46] 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 24/46] 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 25/46] 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 26/46] 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 27/46] 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 28/46] 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 29/46] 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 30/46] 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 31/46] Retrying CI From ca4ba24448eaa52a1288755669cfcc7c88630262 Mon Sep 17 00:00:00 2001 From: gabrielLydian Date: Tue, 9 Apr 2024 13:34:34 -0700 Subject: [PATCH 32/46] Added qutrit amplitude damping channel and tests --- pennylane/ops/qutrit/channel.py | 90 +++++++++++++++++++++ tests/ops/qutrit/test_qutrit_channel_ops.py | 66 +++++++++++++++ 2 files changed, 156 insertions(+) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index e860fa98eec..edf705d732d 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -221,3 +221,93 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ ) return [identity] + Ks + + +class QutritAmplitudeDampingChannel(Channel): + r""" + Single-qutrit amplitude damping error channel. + Interaction with the environment can lead to changes in the state populations of a qubit. + This is the phenomenon behind scattering, dissipation, attenuation, and spontaneous emission. + It can be modelled by the amplitude damping channel, with the following Kraus matrices: + .. math:: + K_0 = \begin{bmatrix} + 1 & 0 & 0\\ + 0 & \sqrt{1-\gamma_1} & 0 \\ + 0 & 0 & \sqrt{1-\gamma_2} + \end{bmatrix} + .. math:: + K_1 = \begin{bmatrix} + 0 & \sqrt{\gamma_1} & 0 \\ + 0 & 0 & 0 \\ + 0 & 0 & 0 + \end{bmatrix} + .. math:: + K_2 = \begin{bmatrix} + 0 & 0 & \sqrt{\gamma_2} \\ + 0 & 0 & 0 \\ + 0 & 0 & 0 + \end{bmatrix} + where :math:`\gamma \in [0, 1]` is the amplitude damping probability. + **Details:** + * Number of wires: 1 + * Number of parameters: 1 + Args: + gamma (float): amplitude damping probability + 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" + + def __init__(self, gamma_1, gamma_2, wires, id=None): + super().__init__(gamma_1, gamma_2, wires=wires, id=id) + + @staticmethod + def compute_kraus_matrices(gamma_1, gamma_2): # pylint:disable=arguments-differ + """Kraus matrices representing the AmplitudeDamping channel. + Args: + gamma_1 (float): amplitude damping probability #TODO + gamma_2 (float): amplitude damping probability + Returns: + list(array): list of Kraus matrices + **Example** + >>> qml.QutritAmplitudeDampingChannel.compute_kraus_matrices(0.25, 0.25) #TODO + [ + array([ [1. , 0. , 0. ], + [0. , 0.70710678, 0. ], + [0. , 0. , 0.8660254 ]]), + array([ [0. , 0.70710678, 0. ], + [0. , 0. , 0. ], + [0. , 0. , 0. ]]), + array([ [0. , 0. , 0.5 ], + [0. , 0. , 0. ], + [0. , 0. , 0. ]]) + ] + """ + if type(gamma_1) != type(gamma_2): + raise ValueError("p1, p2, and p3 should be of the same type") + + if not math.is_abstract(gamma_1): + for gamma in (gamma_1, gamma_2): + if not 0.0 <= gamma <= 1.0: + raise ValueError("Each probability must be in the interval [0,1]") + if not 0.0 <= gamma_1 + gamma_2 <= 1.0: + raise ValueError("Sum of probabilities must be in the interval [0,1]") + + K0 = math.diag([1, math.sqrt(1 - gamma_1 + math.eps), math.sqrt(1 - gamma_2 + math.eps)]) + K1 = math.sqrt(gamma_1 + math.eps) * math.convert_like( + math.cast_like(math.array([[0, 1, 0], [0, 0, 0], [0, 0, 0]]), gamma_1), gamma_1 + ) + K2 = math.sqrt(gamma_2 + math.eps) * math.convert_like( + math.cast_like(math.array([[0, 0, 1], [0, 0, 0], [0, 0, 0]]), gamma_2), gamma_2 + ) + return [K0, K1, K2] + + +__qutrit_channels__ = { + "QutritDepolarizingChannel", + "QutritAmplitudeDampingChannel", +} + +__all__ = list(__qutrit_channels__) diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index be1025d0c26..193d38ba977 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -172,3 +172,69 @@ def test_kraus_jac_jax(self): 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)) + + +class TestQutritAmplitudeDamping: + """Tests for the qutrit quantum channel QutritAmplitudeDamping""" + + def test_gamma_zero(self, tol): + """Test gamma=0 gives correct Kraus matrices""" + op = channel.AmplitudeDamping + 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_gamma_arbitrary(self, tol): + """Test gamma=0.1 gives correct Kraus matrices""" + op = channel.AmplitudeDamping + expected = [ + np.array([[1.0, 0.0], [0.0, 0.9486833]]), + np.array([[0.0, 0.31622777], [0.0, 0.0]]), + ] + assert np.allclose(op(0.1, wires=0).kraus_matrices(), expected, atol=tol, rtol=0) + + def test_gamma_invalid_parameter(self): + with pytest.raises(ValueError, match="gamma must be in the interval"): + channel.AmplitudeDamping(1.5, wires=0).kraus_matrices() + + @staticmethod + def expected_jac_fn(gamma): + return [ + qml.math.array([[0, 0], [0, -1 / (2 * qml.math.sqrt(1 - gamma))]]), + qml.math.array([[0, 1 / (2 * qml.math.sqrt(gamma))], [0, 0]]), + ] + + @staticmethod + def kraus_fn(x): + return qml.math.stack(channel.AmplitudeDamping(x, wires=0).kraus_matrices()) + + @pytest.mark.autograd + def test_kraus_jac_autograd(self): + gamma = pnp.array(0.43, requires_grad=True) + jac = qml.jacobian(self.kraus_fn)(gamma) + assert qml.math.allclose(jac, self.expected_jac_fn(gamma)) + + @pytest.mark.torch + def test_kraus_jac_torch(self): + import torch + + gamma = torch.tensor(0.43, requires_grad=True) + jac = torch.autograd.functional.jacobian(self.kraus_fn, gamma) + assert qml.math.allclose(jac.detach().numpy(), self.expected_jac_fn(gamma.detach().numpy())) + + @pytest.mark.tf + def test_kraus_jac_tf(self): + import tensorflow as tf + + gamma = tf.Variable(0.43) + with tf.GradientTape() as tape: + out = self.kraus_fn(gamma) + jac = tape.jacobian(out, gamma) + assert qml.math.allclose(jac, self.expected_jac_fn(gamma)) + + @pytest.mark.jax + def test_kraus_jac_jax(self): + import jax + + gamma = jax.numpy.array(0.43) + jac = jax.jacobian(self.kraus_fn)(gamma) + assert qml.math.allclose(jac, self.expected_jac_fn(gamma)) From 4c3a80a2621a9ad053afbb8017d4584a55bf8d14 Mon Sep 17 00:00:00 2001 From: gabrielLydian Date: Thu, 11 Apr 2024 16:34:56 -0700 Subject: [PATCH 33/46] Added small change to tests --- tests/ops/qutrit/test_qutrit_channel_ops.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 193d38ba977..b0cde457535 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -179,13 +179,13 @@ class TestQutritAmplitudeDamping: def test_gamma_zero(self, tol): """Test gamma=0 gives correct Kraus matrices""" - op = channel.AmplitudeDamping + op = channel.QutritAmplitudeDamping 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_gamma_arbitrary(self, tol): """Test gamma=0.1 gives correct Kraus matrices""" - op = channel.AmplitudeDamping + op = channel.QutritAmplitudeDamping expected = [ np.array([[1.0, 0.0], [0.0, 0.9486833]]), np.array([[0.0, 0.31622777], [0.0, 0.0]]), @@ -194,7 +194,7 @@ def test_gamma_arbitrary(self, tol): def test_gamma_invalid_parameter(self): with pytest.raises(ValueError, match="gamma must be in the interval"): - channel.AmplitudeDamping(1.5, wires=0).kraus_matrices() + channel.QutritAmplitudeDamping(1.5, wires=0).kraus_matrices() @staticmethod def expected_jac_fn(gamma): @@ -205,7 +205,7 @@ def expected_jac_fn(gamma): @staticmethod def kraus_fn(x): - return qml.math.stack(channel.AmplitudeDamping(x, wires=0).kraus_matrices()) + return qml.math.stack(channel.QutritAmplitudeDamping(x, wires=0).kraus_matrices()) @pytest.mark.autograd def test_kraus_jac_autograd(self): From 68f8d73e6ae10bd36f23cfb2599ac75567cf0775 Mon Sep 17 00:00:00 2001 From: gabrielLydian Date: Wed, 15 May 2024 14:45:47 -0700 Subject: [PATCH 34/46] Fixed test to work for qutrit version, fixed docs --- pennylane/ops/qutrit/__init__.py | 3 +- pennylane/ops/qutrit/channel.py | 44 ++++---- tests/ops/qutrit/test_qutrit_channel_ops.py | 118 ++++++++++++-------- 3 files changed, 94 insertions(+), 71 deletions(-) diff --git a/pennylane/ops/qutrit/__init__.py b/pennylane/ops/qutrit/__init__.py index 670f950ba45..da3ee27cacc 100644 --- a/pennylane/ops/qutrit/__init__.py +++ b/pennylane/ops/qutrit/__init__.py @@ -26,7 +26,7 @@ from .observables import * from .parametric_ops import * from .state_preparation import * -from .channel import QutritDepolarizingChannel +from .channel import * # TODO: Change `qml.Identity` for qutrit support or add `qml.TIdentity` for qutrits __ops__ = { @@ -50,6 +50,7 @@ } __channels__ = { "QutritDepolarizingChannel", + "QutritAmplitudeDamping", } __all__ = list(__ops__ | __obs__ | __channels__) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index edf705d732d..d6e1778b5d1 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -223,40 +223,45 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ return [identity] + Ks -class QutritAmplitudeDampingChannel(Channel): +class QutritAmplitudeDamping(Channel): r""" Single-qutrit amplitude damping error channel. - Interaction with the environment can lead to changes in the state populations of a qubit. + Interaction with the environment can lead to changes in the state populations of a qutrit. This is the phenomenon behind scattering, dissipation, attenuation, and spontaneous emission. It can be modelled by the amplitude damping channel, with the following Kraus matrices: + .. math:: K_0 = \begin{bmatrix} 1 & 0 & 0\\ 0 & \sqrt{1-\gamma_1} & 0 \\ 0 & 0 & \sqrt{1-\gamma_2} - \end{bmatrix} - .. math:: + \end{bmatrix}, \quad K_1 = \begin{bmatrix} 0 & \sqrt{\gamma_1} & 0 \\ 0 & 0 & 0 \\ 0 & 0 & 0 - \end{bmatrix} - .. math:: + \end{bmatrix}, \quad K_2 = \begin{bmatrix} 0 & 0 & \sqrt{\gamma_2} \\ 0 & 0 & 0 \\ 0 & 0 & 0 \end{bmatrix} - where :math:`\gamma \in [0, 1]` is the amplitude damping probability. + where :math:`\gamma_1 \in [0, 1]` and :math:`\gamma_2 \in [0, 1]` are the amplitude damping + probabilities for subspaces (0,1) and (0,2) respectively. + + .. note:: + The Kraus operators :math:`\{K_0, K_1, K_2\}` are adapted from [`1 `_] (Eq. 8). + **Details:** * Number of wires: 1 - * Number of parameters: 1 + * Number of parameters: 2 Args: - gamma (float): amplitude damping probability + gamma_1 (float): :math:`|1 \rangle \rightarrow |0 \rangle` amplitude damping probability. + gamma_2 (float): :math:`|2 \rangle \rightarrow |0 \rangle` amplitude damping probability. wires (Sequence[int] or int): the wire the channel acts on id (str or None): String representing the operation (optional) """ - num_params = 1 + num_params = 2 num_wires = 1 grad_method = "F" @@ -265,14 +270,14 @@ def __init__(self, gamma_1, gamma_2, wires, id=None): @staticmethod def compute_kraus_matrices(gamma_1, gamma_2): # pylint:disable=arguments-differ - """Kraus matrices representing the AmplitudeDamping channel. + r"""Kraus matrices representing the AmplitudeDamping channel. Args: - gamma_1 (float): amplitude damping probability #TODO - gamma_2 (float): amplitude damping probability + gamma_1 (float): :math:`|1\rangle \rightarrow |0\rangle` amplitude damping probability. + gamma_2 (float): :math:`|2\rangle \rightarrow |0\rangle` amplitude damping probability. Returns: list(array): list of Kraus matrices **Example** - >>> qml.QutritAmplitudeDampingChannel.compute_kraus_matrices(0.25, 0.25) #TODO + >>> qml.QutritAmplitudeDamping.compute_kraus_matrices(0.5, 0.25) [ array([ [1. , 0. , 0. ], [0. , 0.70710678, 0. ], @@ -285,9 +290,6 @@ def compute_kraus_matrices(gamma_1, gamma_2): # pylint:disable=arguments-differ [0. , 0. , 0. ]]) ] """ - if type(gamma_1) != type(gamma_2): - raise ValueError("p1, p2, and p3 should be of the same type") - if not math.is_abstract(gamma_1): for gamma in (gamma_1, gamma_2): if not 0.0 <= gamma <= 1.0: @@ -303,11 +305,3 @@ def compute_kraus_matrices(gamma_1, gamma_2): # pylint:disable=arguments-differ math.cast_like(math.array([[0, 0, 1], [0, 0, 0], [0, 0, 0]]), gamma_2), gamma_2 ) return [K0, K1, K2] - - -__qutrit_channels__ = { - "QutritDepolarizingChannel", - "QutritAmplitudeDampingChannel", -} - -__all__ = list(__qutrit_channels__) diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index b0cde457535..a067ca71f52 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -20,6 +20,7 @@ import pennylane as qml from pennylane import numpy as pnp +from pennylane import math from pennylane.ops.qutrit import channel QUDIT_DIM = 3 @@ -113,20 +114,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(p, wires=0).kraus_matrices()) + return math.stack(channel.QutritDepolarizingChannel(p, wires=0).kraus_matrices()) @staticmethod 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(p, wires=0).kraus_matrices()) + return math.real( + math.stack(channel.QutritDepolarizingChannel(p, wires=0).kraus_matrices()) ) @staticmethod 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(p, wires=0).kraus_matrices()) + return math.imag( + math.stack(channel.QutritDepolarizingChannel(p, wires=0).kraus_matrices()) ) @pytest.mark.autograd @@ -134,7 +135,7 @@ 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)) + assert math.allclose(jac, self.expected_jac_fn(p)) @pytest.mark.torch def test_kraus_jac_torch(self): @@ -144,7 +145,7 @@ def test_kraus_jac_torch(self): 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())) + assert math.allclose(jac, self.expected_jac_fn(p.detach().numpy())) @pytest.mark.tf def test_kraus_jac_tf(self): @@ -157,10 +158,10 @@ def test_kraus_jac_tf(self): 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) - imag_jac = qml.math.cast(imag_tape.jacobian(imag_out, p), complex) + real_jac = math.cast(real_tape.jacobian(real_out, p), complex) + imag_jac = 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)) + assert math.allclose(jac, self.expected_jac_fn(0.43)) @pytest.mark.jax def test_kraus_jac_jax(self): @@ -171,7 +172,7 @@ def test_kraus_jac_jax(self): 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)) + assert math.allclose(jac, self.expected_jac_fn(p)) class TestQutritAmplitudeDamping: @@ -179,62 +180,89 @@ class TestQutritAmplitudeDamping: def test_gamma_zero(self, tol): """Test gamma=0 gives correct Kraus matrices""" - op = channel.QutritAmplitudeDamping - 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_mats = qml.QutritAmplitudeDamping(0, 0, wires=0).kraus_matrices() + assert np.allclose(kraus_mats[0], np.eye(3), atol=tol, rtol=0) + assert np.allclose(kraus_mats[1], np.zeros((3, 3)), atol=tol, rtol=0) + assert np.allclose(kraus_mats[2], np.zeros((3, 3)), atol=tol, rtol=0) def test_gamma_arbitrary(self, tol): - """Test gamma=0.1 gives correct Kraus matrices""" - op = channel.QutritAmplitudeDamping - expected = [ - np.array([[1.0, 0.0], [0.0, 0.9486833]]), - np.array([[0.0, 0.31622777], [0.0, 0.0]]), - ] - assert np.allclose(op(0.1, wires=0).kraus_matrices(), expected, atol=tol, rtol=0) - - def test_gamma_invalid_parameter(self): - with pytest.raises(ValueError, match="gamma must be in the interval"): - channel.QutritAmplitudeDamping(1.5, wires=0).kraus_matrices() + """Test gamma_1=0.1, gamma_2=0.2 gives correct Kraus matrices""" + K_0 = np.diag((1, np.sqrt(0.9), np.sqrt(0.8))) + + K_1 = np.zeros((3, 3)) + K_1[0, 1] = np.sqrt(0.1) + + K_2 = np.zeros((3, 3)) + K_2[0, 2] = np.sqrt(0.2) + + expected = [K_0, K_1, K_2] + damping_channel = qml.QutritAmplitudeDamping(0.1, 0.2, wires=0) + assert np.allclose(damping_channel.kraus_matrices(), expected, atol=tol, rtol=0) + + @pytest.mark.parametrize("gamma1,gamma2", ((1.5, 0.), (0., 1.5), (0.75, 0.75), (1.0, math.eps))) + def test_gamma_invalid_parameter(self, gamma1, gamma2): + """Ensures that error is thrown when gamma_1, gamma_2, or their sum are outside [0,1]""" + with pytest.raises(ValueError, match="must be in the interval"): + channel.QutritAmplitudeDamping(gamma1, gamma2, wires=0).kraus_matrices() @staticmethod - def expected_jac_fn(gamma): - return [ - qml.math.array([[0, 0], [0, -1 / (2 * qml.math.sqrt(1 - gamma))]]), - qml.math.array([[0, 1 / (2 * qml.math.sqrt(gamma))], [0, 0]]), - ] + def expected_jac_fn(gamma_1, gamma_2): + """Gets the expected Jacobian of Kraus matrices""" + partial_1 = [math.zeros((3, 3)) for _ in range(3)] + partial_1[0][1, 1] = -1 / (2 * math.sqrt(1 - gamma_1)) + partial_1[1][0, 1] = 1 / (2 * math.sqrt(gamma_1)) + + partial_2 = [math.zeros((3, 3)) for _ in range(3)] + partial_2[0][2, 2] = -1 / (2 * math.sqrt(1 - gamma_2)) + partial_2[2][0, 2] = 1 / (2 * math.sqrt(gamma_2)) + + return [partial_1, partial_2] @staticmethod - def kraus_fn(x): - return qml.math.stack(channel.QutritAmplitudeDamping(x, wires=0).kraus_matrices()) + def kraus_fn(gamma_1, gamma_2): + """Gets the Kraus matrices of QutritAmplitudeDamping channel, used for differentiation.""" + damping_channel = qml.QutritAmplitudeDamping(gamma_1, gamma_2, wires=0) + return math.stack(damping_channel.kraus_matrices()) @pytest.mark.autograd def test_kraus_jac_autograd(self): - gamma = pnp.array(0.43, requires_grad=True) - jac = qml.jacobian(self.kraus_fn)(gamma) - assert qml.math.allclose(jac, self.expected_jac_fn(gamma)) + """Tests Jacobian of Kraus matrices using autograd.""" + gamma_1 = pnp.array(0.43, requires_grad=True) + gamma_2 = pnp.array(0.12, requires_grad=True) + jac = qml.jacobian(self.kraus_fn)(gamma_1, gamma_2) + assert math.allclose(jac, self.expected_jac_fn(gamma_1, gamma_2)) @pytest.mark.torch def test_kraus_jac_torch(self): + """Tests Jacobian of Kraus matrices using PyTorch.""" import torch - gamma = torch.tensor(0.43, requires_grad=True) - jac = torch.autograd.functional.jacobian(self.kraus_fn, gamma) - assert qml.math.allclose(jac.detach().numpy(), self.expected_jac_fn(gamma.detach().numpy())) + gamma_1 = torch.tensor(0.43, requires_grad=True) + gamma_2 = torch.tensor(0.12, requires_grad=True) + + jac = torch.autograd.functional.jacobian(self.kraus_fn, (gamma_1, gamma_2)) + expected = self.expected_jac_fn(gamma_1.detach().numpy(), gamma_2.detach().numpy()) + assert math.allclose(jac[0].detach().numpy(), expected[0]) + assert math.allclose(jac[1].detach().numpy(), expected[1]) @pytest.mark.tf def test_kraus_jac_tf(self): + """Tests Jacobian of Kraus matrices using TensorFlow.""" import tensorflow as tf - gamma = tf.Variable(0.43) + gamma_1 = tf.Variable(0.43) + gamma_2 = tf.Variable(0.12) with tf.GradientTape() as tape: - out = self.kraus_fn(gamma) - jac = tape.jacobian(out, gamma) - assert qml.math.allclose(jac, self.expected_jac_fn(gamma)) + out = self.kraus_fn(gamma_1, gamma_2) + jac = tape.jacobian(out, (gamma_1, gamma_2)) + assert math.allclose(jac, self.expected_jac_fn(gamma_1, gamma_2)) @pytest.mark.jax def test_kraus_jac_jax(self): + """Tests Jacobian of Kraus matrices using JAX.""" import jax - gamma = jax.numpy.array(0.43) - jac = jax.jacobian(self.kraus_fn)(gamma) - assert qml.math.allclose(jac, self.expected_jac_fn(gamma)) + gamma_1 = jax.numpy.array(0.43) + gamma_2 = jax.numpy.array(0.12) + jac = jax.jacobian(self.kraus_fn, argnums=[0, 1])(gamma_1, gamma_2) + assert math.allclose(jac, self.expected_jac_fn(gamma_1, gamma_2)) From 3645f4afe7897ac46401b93b11eaf52d274f897e Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Wed, 15 May 2024 15:15:36 -0700 Subject: [PATCH 35/46] Fixed depolarizing test to ensure it is using the correct method of differentiation --- 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 a067ca71f52..9c05efa8b6f 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -75,7 +75,7 @@ def test_grad_depolarizing(self, angle): dev = qml.device("default.qutrit.mixed") prob = pnp.array(0.5, requires_grad=True) - @qml.qnode(dev) + @qml.qnode(dev, diff_method="parameter-shift") def circuit(p): qml.TRX(angle, wires=0, subspace=(0, 1)) qml.TRX(angle, wires=0, subspace=(1, 2)) From 732c0c8612b08c03e94743dd06f7d96056993848 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Wed, 15 May 2024 15:21:10 -0700 Subject: [PATCH 36/46] Added PR to changelog and QutritAmplitudeDamping to introduction --- doc/introduction/operations.rst | 1 + doc/releases/changelog-dev.md | 3 +++ 2 files changed, 4 insertions(+) diff --git a/doc/introduction/operations.rst b/doc/introduction/operations.rst index 970e7b4ecd9..b4b9b0d3c4e 100644 --- a/doc/introduction/operations.rst +++ b/doc/introduction/operations.rst @@ -507,6 +507,7 @@ Qutrit noisy channels :nosignatures: ~pennylane.QutritDepolarizingChannel + ~pennylane.QutritAmplitudeDamping :html:`` diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 01db3008d03..ec4c8fa42d7 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -103,6 +103,9 @@ * ``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) +* ``qml.QutritAmplitudeDamping`` channel has been added, allowing for noises modeled ny amplitude damping to be simulated on the `default.qutrit.mixed` device. + [(#5503)](https://github.com/PennyLaneAI/pennylane/pull/5503) +

Deprecations 👋

* ``qml.transforms.map_batch_transform`` is deprecated, since a transform can be applied directly to a batch of tapes. From 99944d53ba1d40c1506cdab8a3a6036ab0e10725 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Wed, 15 May 2024 15:35:41 -0700 Subject: [PATCH 37/46] Reformatted and fixed doc spacing --- pennylane/ops/qutrit/channel.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 0bacc88d0a1..16aad7b1a55 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -251,17 +251,21 @@ class QutritAmplitudeDamping(Channel): probabilities for subspaces (0,1) and (0,2) respectively. .. note:: + The Kraus operators :math:`\{K_0, K_1, K_2\}` are adapted from [`1 `_] (Eq. 8). **Details:** + * Number of wires: 1 * Number of parameters: 2 + Args: gamma_1 (float): :math:`|1 \rangle \rightarrow |0 \rangle` amplitude damping probability. gamma_2 (float): :math:`|2 \rangle \rightarrow |0 \rangle` amplitude damping probability. wires (Sequence[int] or int): the wire the channel acts on id (str or None): String representing the operation (optional) """ + num_params = 2 num_wires = 1 grad_method = "F" @@ -272,12 +276,16 @@ def __init__(self, gamma_1, gamma_2, wires, id=None): @staticmethod def compute_kraus_matrices(gamma_1, gamma_2): # pylint:disable=arguments-differ r"""Kraus matrices representing the AmplitudeDamping channel. + Args: gamma_1 (float): :math:`|1\rangle \rightarrow |0\rangle` amplitude damping probability. gamma_2 (float): :math:`|2\rangle \rightarrow |0\rangle` amplitude damping probability. + Returns: list(array): list of Kraus matrices + **Example** + >>> qml.QutritAmplitudeDamping.compute_kraus_matrices(0.5, 0.25) [ array([ [1. , 0. , 0. ], From c20f0cb89fb471559c534180c0fd3d21c5fb23fa Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Wed, 15 May 2024 15:40:25 -0700 Subject: [PATCH 38/46] Reformatted tests --- tests/ops/qutrit/test_qutrit_channel_ops.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 9c05efa8b6f..8e7f6d5a521 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -119,16 +119,12 @@ def kraus_fn(p): @staticmethod def kraus_fn_real(p): """Gets a matrix of the real part of the Kraus matrices to be tested.""" - return math.real( - math.stack(channel.QutritDepolarizingChannel(p, wires=0).kraus_matrices()) - ) + return math.real(math.stack(channel.QutritDepolarizingChannel(p, wires=0).kraus_matrices())) @staticmethod def kraus_fn_imag(p): """Gets a matrix of the imaginary part of the Kraus matrices to be tested.""" - return math.imag( - math.stack(channel.QutritDepolarizingChannel(p, wires=0).kraus_matrices()) - ) + return math.imag(math.stack(channel.QutritDepolarizingChannel(p, wires=0).kraus_matrices())) @pytest.mark.autograd def test_kraus_jac_autograd(self): @@ -199,7 +195,9 @@ def test_gamma_arbitrary(self, tol): damping_channel = qml.QutritAmplitudeDamping(0.1, 0.2, wires=0) assert np.allclose(damping_channel.kraus_matrices(), expected, atol=tol, rtol=0) - @pytest.mark.parametrize("gamma1,gamma2", ((1.5, 0.), (0., 1.5), (0.75, 0.75), (1.0, math.eps))) + @pytest.mark.parametrize( + "gamma1,gamma2", ((1.5, 0.0), (0.0, 1.5), (0.75, 0.75), (1.0, math.eps)) + ) def test_gamma_invalid_parameter(self, gamma1, gamma2): """Ensures that error is thrown when gamma_1, gamma_2, or their sum are outside [0,1]""" with pytest.raises(ValueError, match="must be in the interval"): From a24b2aa442cc5f7a4eb6db716ea7a0b691aeb561 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Wed, 15 May 2024 15:46:15 -0700 Subject: [PATCH 39/46] ran isort on tests --- 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 8e7f6d5a521..312daafbe6d 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -19,8 +19,8 @@ from numpy.linalg import matrix_power import pennylane as qml -from pennylane import numpy as pnp from pennylane import math +from pennylane import numpy as pnp from pennylane.ops.qutrit import channel QUDIT_DIM = 3 From d9c108774d19ee571738e527ebe0ea1fa66cf543 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Wed, 15 May 2024 16:05:01 -0700 Subject: [PATCH 40/46] Removed requirement for sum of gammas to be less than 1. --- pennylane/ops/qutrit/channel.py | 11 +++++------ tests/ops/qutrit/test_qutrit_channel_ops.py | 19 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 16aad7b1a55..2e7a29f9a75 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -247,7 +247,8 @@ class QutritAmplitudeDamping(Channel): 0 & 0 & 0 \\ 0 & 0 & 0 \end{bmatrix} - where :math:`\gamma_1 \in [0, 1]` and :math:`\gamma_2 \in [0, 1]` are the amplitude damping + + Where :math:`\gamma_1 \in [0, 1]` and :math:`\gamma_2 \in [0, 1]` are the amplitude damping probabilities for subspaces (0,1) and (0,2) respectively. .. note:: @@ -275,7 +276,7 @@ def __init__(self, gamma_1, gamma_2, wires, id=None): @staticmethod def compute_kraus_matrices(gamma_1, gamma_2): # pylint:disable=arguments-differ - r"""Kraus matrices representing the AmplitudeDamping channel. + r"""Kraus matrices representing the QutritAmplitudeDamping channel. Args: gamma_1 (float): :math:`|1\rangle \rightarrow |0\rangle` amplitude damping probability. @@ -299,12 +300,10 @@ def compute_kraus_matrices(gamma_1, gamma_2): # pylint:disable=arguments-differ [0. , 0. , 0. ]]) ] """ - if not math.is_abstract(gamma_1): - for gamma in (gamma_1, gamma_2): + for gamma in (gamma_1, gamma_2): + if not (math.is_abstract(gamma_1) or math.is_abstract(gamma_2)): if not 0.0 <= gamma <= 1.0: raise ValueError("Each probability must be in the interval [0,1]") - if not 0.0 <= gamma_1 + gamma_2 <= 1.0: - raise ValueError("Sum of probabilities must be in the interval [0,1]") K0 = math.diag([1, math.sqrt(1 - gamma_1 + math.eps), math.sqrt(1 - gamma_2 + math.eps)]) K1 = math.sqrt(gamma_1 + math.eps) * math.convert_like( diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 312daafbe6d..e1043549bf9 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -181,26 +181,25 @@ def test_gamma_zero(self, tol): assert np.allclose(kraus_mats[1], np.zeros((3, 3)), atol=tol, rtol=0) assert np.allclose(kraus_mats[2], np.zeros((3, 3)), atol=tol, rtol=0) - def test_gamma_arbitrary(self, tol): - """Test gamma_1=0.1, gamma_2=0.2 gives correct Kraus matrices""" - K_0 = np.diag((1, np.sqrt(0.9), np.sqrt(0.8))) + @pytest.mark.parametrize("gamma1,gamma2", ((0.1, 0.2), (0.75, 0.75))) + def test_gamma_arbitrary(self, gamma1, gamma2, tol): + """Test the correct correct Kraus matrices are returned, also ensures that sum of gammas can be over 1.""" + K_0 = np.diag((1, np.sqrt(1 - gamma1), np.sqrt(1 - gamma2))) K_1 = np.zeros((3, 3)) - K_1[0, 1] = np.sqrt(0.1) + K_1[0, 1] = np.sqrt(gamma1) K_2 = np.zeros((3, 3)) - K_2[0, 2] = np.sqrt(0.2) + K_2[0, 2] = np.sqrt(gamma2) expected = [K_0, K_1, K_2] damping_channel = qml.QutritAmplitudeDamping(0.1, 0.2, wires=0) assert np.allclose(damping_channel.kraus_matrices(), expected, atol=tol, rtol=0) - @pytest.mark.parametrize( - "gamma1,gamma2", ((1.5, 0.0), (0.0, 1.5), (0.75, 0.75), (1.0, math.eps)) - ) + @pytest.mark.parametrize("gamma1,gamma2", ((1.5, 0.0), (0.0, 1.0 + math.eps))) def test_gamma_invalid_parameter(self, gamma1, gamma2): - """Ensures that error is thrown when gamma_1, gamma_2, or their sum are outside [0,1]""" - with pytest.raises(ValueError, match="must be in the interval"): + """Ensures that error is thrown when gamma_1 or gamma_2 are outside [0,1]""" + with pytest.raises(ValueError, match="Each probability must be in the interval"): channel.QutritAmplitudeDamping(gamma1, gamma2, wires=0).kraus_matrices() @staticmethod From f2b786c3c62d15a6d37e2032d13768d247159cf4 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Wed, 15 May 2024 16:23:24 -0700 Subject: [PATCH 41/46] Fixed changelog to flow better, changed docstring to be more specific for qutrits --- doc/releases/changelog-dev.md | 2 +- pennylane/ops/qutrit/channel.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index ec4c8fa42d7..29eb279b74c 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -103,7 +103,7 @@ * ``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) -* ``qml.QutritAmplitudeDamping`` channel has been added, allowing for noises modeled ny amplitude damping to be simulated on the `default.qutrit.mixed` device. +* ``qml.QutritAmplitudeDamping`` channel has been added, allowing for noise processes modelled by amplitude damping to be simulated on the `default.qutrit.mixed` device. [(#5503)](https://github.com/PennyLaneAI/pennylane/pull/5503)

Deprecations 👋

diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 2e7a29f9a75..73540cb0a43 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -228,8 +228,7 @@ class QutritAmplitudeDamping(Channel): r""" Single-qutrit amplitude damping error channel. Interaction with the environment can lead to changes in the state populations of a qutrit. - This is the phenomenon behind scattering, dissipation, attenuation, and spontaneous emission. - It can be modelled by the amplitude damping channel, with the following Kraus matrices: + This can be modelled for qutrits by the qutrit amplitude damping channel, with the following Kraus matrices: .. math:: K_0 = \begin{bmatrix} From 5289ca833a09dae1ea643468aa0e6a2d1d443f65 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Wed, 15 May 2024 16:25:56 -0700 Subject: [PATCH 42/46] Fixed arbitary gamma test --- tests/ops/qutrit/test_qutrit_channel_ops.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index e1043549bf9..58541d1a2af 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -183,7 +183,7 @@ def test_gamma_zero(self, tol): @pytest.mark.parametrize("gamma1,gamma2", ((0.1, 0.2), (0.75, 0.75))) def test_gamma_arbitrary(self, gamma1, gamma2, tol): - """Test the correct correct Kraus matrices are returned, also ensures that sum of gammas can be over 1.""" + """Test the correct correct Kraus matrices are returned, also ensures that the sum of gammas can be over 1.""" K_0 = np.diag((1, np.sqrt(1 - gamma1), np.sqrt(1 - gamma2))) K_1 = np.zeros((3, 3)) @@ -193,7 +193,7 @@ def test_gamma_arbitrary(self, gamma1, gamma2, tol): K_2[0, 2] = np.sqrt(gamma2) expected = [K_0, K_1, K_2] - damping_channel = qml.QutritAmplitudeDamping(0.1, 0.2, wires=0) + damping_channel = qml.QutritAmplitudeDamping(gamma1, gamma2, wires=0) assert np.allclose(damping_channel.kraus_matrices(), expected, atol=tol, rtol=0) @pytest.mark.parametrize("gamma1,gamma2", ((1.5, 0.0), (0.0, 1.0 + math.eps))) From 32b1c268bd796c67b3e63934cfc5225ad49d433c Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill <78718539+Gabriel-Bottrill@users.noreply.github.com> Date: Tue, 21 May 2024 16:01:45 -0700 Subject: [PATCH 43/46] Apply suggestions from code review Co-authored-by: Thomas R. Bromley <49409390+trbromley@users.noreply.github.com> Co-authored-by: Olivia Di Matteo <2068515+glassnotes@users.noreply.github.com> Co-authored-by: Mudit Pandey --- doc/releases/changelog-dev.md | 2 +- pennylane/ops/qutrit/channel.py | 9 +++++---- tests/ops/qutrit/test_qutrit_channel_ops.py | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 29eb279b74c..b6340f61eef 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -103,7 +103,7 @@ * ``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) -* ``qml.QutritAmplitudeDamping`` channel has been added, allowing for noise processes modelled by amplitude damping to be simulated on the `default.qutrit.mixed` device. +* `qml.QutritAmplitudeDamping` channel has been added, allowing for noise processes modelled by amplitude damping to be simulated on the `default.qutrit.mixed` device. [(#5503)](https://github.com/PennyLaneAI/pennylane/pull/5503)

Deprecations 👋

diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 73540cb0a43..fd46e614a8f 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -227,8 +227,9 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ class QutritAmplitudeDamping(Channel): r""" Single-qutrit amplitude damping error channel. + Interaction with the environment can lead to changes in the state populations of a qutrit. - This can be modelled for qutrits by the qutrit amplitude damping channel, with the following Kraus matrices: + This can be modelled by the qutrit amplitude damping channel with the following Kraus matrices: .. math:: K_0 = \begin{bmatrix} @@ -245,9 +246,9 @@ class QutritAmplitudeDamping(Channel): 0 & 0 & \sqrt{\gamma_2} \\ 0 & 0 & 0 \\ 0 & 0 & 0 - \end{bmatrix} + \end{bmatrix}, - Where :math:`\gamma_1 \in [0, 1]` and :math:`\gamma_2 \in [0, 1]` are the amplitude damping + where :math:`\gamma_1 \in [0, 1]` and :math:`\gamma_2 \in [0, 1]` are the amplitude damping probabilities for subspaces (0,1) and (0,2) respectively. .. note:: @@ -275,7 +276,7 @@ def __init__(self, gamma_1, gamma_2, wires, id=None): @staticmethod def compute_kraus_matrices(gamma_1, gamma_2): # pylint:disable=arguments-differ - r"""Kraus matrices representing the QutritAmplitudeDamping channel. + r"""Kraus matrices representing the ``QutritAmplitudeDamping`` channel. Args: gamma_1 (float): :math:`|1\rangle \rightarrow |0\rangle` amplitude damping probability. diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 58541d1a2af..585f623f499 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -175,7 +175,7 @@ class TestQutritAmplitudeDamping: """Tests for the qutrit quantum channel QutritAmplitudeDamping""" def test_gamma_zero(self, tol): - """Test gamma=0 gives correct Kraus matrices""" + """Test gamma_1=gamma_2=0 gives correct Kraus matrices""" kraus_mats = qml.QutritAmplitudeDamping(0, 0, wires=0).kraus_matrices() assert np.allclose(kraus_mats[0], np.eye(3), atol=tol, rtol=0) assert np.allclose(kraus_mats[1], np.zeros((3, 3)), atol=tol, rtol=0) @@ -183,7 +183,7 @@ def test_gamma_zero(self, tol): @pytest.mark.parametrize("gamma1,gamma2", ((0.1, 0.2), (0.75, 0.75))) def test_gamma_arbitrary(self, gamma1, gamma2, tol): - """Test the correct correct Kraus matrices are returned, also ensures that the sum of gammas can be over 1.""" + """Test the correct Kraus matrices are returned, also ensures that the sum of gammas can be over 1.""" K_0 = np.diag((1, np.sqrt(1 - gamma1), np.sqrt(1 - gamma2))) K_1 = np.zeros((3, 3)) From f09e9e8fe37e19129cb1c70cce2a9e4d9309c022 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill <78718539+Gabriel-Bottrill@users.noreply.github.com> Date: Tue, 21 May 2024 16:24:03 -0700 Subject: [PATCH 44/46] Removed comma at end of Kraus matrices. --- 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 fd46e614a8f..2f3c6d8c359 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -246,7 +246,7 @@ class QutritAmplitudeDamping(Channel): 0 & 0 & \sqrt{\gamma_2} \\ 0 & 0 & 0 \\ 0 & 0 & 0 - \end{bmatrix}, + \end{bmatrix} where :math:`\gamma_1 \in [0, 1]` and :math:`\gamma_2 \in [0, 1]` are the amplitude damping probabilities for subspaces (0,1) and (0,2) respectively. From 9dccfa72f08ab5dc3a0524cc61206a2526f08b05 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill <78718539+Gabriel-Bottrill@users.noreply.github.com> Date: Wed, 22 May 2024 13:04:26 -0700 Subject: [PATCH 45/46] Moved gamma_1 and gamma_2 verification to __init__ --- pennylane/ops/qutrit/channel.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 2f3c6d8c359..5dbb8e9138a 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -272,6 +272,12 @@ class QutritAmplitudeDamping(Channel): grad_method = "F" def __init__(self, gamma_1, gamma_2, wires, id=None): + # Verify gamma_1 and gamma_2 + for gamma in (gamma_1, gamma_2): + if not (math.is_abstract(gamma_1) or math.is_abstract(gamma_2)): + if not 0.0 <= gamma <= 1.0: + raise ValueError("Each probability must be in the interval [0,1]") + super().__init__(gamma_1, gamma_2, wires=wires, id=id) @staticmethod @@ -300,11 +306,6 @@ def compute_kraus_matrices(gamma_1, gamma_2): # pylint:disable=arguments-differ [0. , 0. , 0. ]]) ] """ - for gamma in (gamma_1, gamma_2): - if not (math.is_abstract(gamma_1) or math.is_abstract(gamma_2)): - if not 0.0 <= gamma <= 1.0: - raise ValueError("Each probability must be in the interval [0,1]") - K0 = math.diag([1, math.sqrt(1 - gamma_1 + math.eps), math.sqrt(1 - gamma_2 + math.eps)]) K1 = math.sqrt(gamma_1 + math.eps) * math.convert_like( math.cast_like(math.array([[0, 1, 0], [0, 0, 0], [0, 0, 0]]), gamma_1), gamma_1 From 04c0cb31f34e4913f92d2397b8b179a328952b6b Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill <78718539+Gabriel-Bottrill@users.noreply.github.com> Date: Wed, 22 May 2024 13:06:55 -0700 Subject: [PATCH 46/46] Removed trailing whitespace --- 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 5dbb8e9138a..c50572cf206 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -277,7 +277,6 @@ def __init__(self, gamma_1, gamma_2, wires, id=None): if not (math.is_abstract(gamma_1) or math.is_abstract(gamma_2)): if not 0.0 <= gamma <= 1.0: raise ValueError("Each probability must be in the interval [0,1]") - super().__init__(gamma_1, gamma_2, wires=wires, id=id) @staticmethod