From 74a4e391bc8aaeea1b6552c2bf1b5573e40dc244 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Fri, 31 May 2024 22:40:54 -0700 Subject: [PATCH 01/13] Added TritFlip --- doc/introduction/operations.rst | 1 + pennylane/ops/qutrit/__init__.py | 1 + pennylane/ops/qutrit/channel.py | 103 +++++++++++++++++ .../test_qutrit_mixed_preprocessing.py | 1 + tests/ops/qutrit/test_qutrit_channel_ops.py | 106 +++++++++++++++++- 5 files changed, 209 insertions(+), 3 deletions(-) diff --git a/doc/introduction/operations.rst b/doc/introduction/operations.rst index b4b9b0d3c4e..ac7e23efcf3 100644 --- a/doc/introduction/operations.rst +++ b/doc/introduction/operations.rst @@ -508,6 +508,7 @@ Qutrit noisy channels ~pennylane.QutritDepolarizingChannel ~pennylane.QutritAmplitudeDamping + ~pennylane.TritFlip :html:`` diff --git a/pennylane/ops/qutrit/__init__.py b/pennylane/ops/qutrit/__init__.py index da3ee27cacc..9b84aca3335 100644 --- a/pennylane/ops/qutrit/__init__.py +++ b/pennylane/ops/qutrit/__init__.py @@ -51,6 +51,7 @@ __channels__ = { "QutritDepolarizingChannel", "QutritAmplitudeDamping", + "TritFlip" } __all__ = list(__ops__ | __obs__ | __channels__) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index c50572cf206..cf049edc44c 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -313,3 +313,106 @@ 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] + + +class TritFlip(Channel): + r""" + Single-qutrit parameter flipping error channel, similar to (GellMann :math:`\{\gamma_1, \gamma_4, \gamma_6\}`). + + This channel is modelled by the following Kraus matrices: + + .. math:: + K_0 = \sqrt{1-(p_{01} + p_{02} + p_{12})} \begin{bmatrix} + 1 & 0 & 0 \\ + 0 & 1 & 0 \\ + 0 & 0 & 1 + \end{bmatrix} + + .. math:: + K_1 = \sqrt{p_{01}}\begin{bmatrix} + 0 & 1 & 0 \\ + 1 & 0 & 0 \\ + 0 & 0 & 1 + \end{bmatrix}, \quad + K_2 = \sqrt{p_{02}}\begin{bmatrix} + 0 & 0 & 1 \\ + 0 & 1 & 0 \\ + 1 & 0 & 0 + \end{bmatrix}, \quad + K_3 = \sqrt{p_{12}}\begin{bmatrix} + 1 & 0 & 0 \\ + 0 & 0 & 1 \\ + 0 & 1 & 0 + \end{bmatrix} + + where :math:`p_{01}, p_{02}, p_{12} \in [0, 1]` is the probability of a parameter flipping for subspaces (0,1), + (0,2), and (1,2) respectively. + + .. note:: + The Kraus operators :math:`\{K_0, K_1, K_2, K_3\}` are adapted from The BitFlip channel's Kraus operators. + + This channel is primarily meant to characterize the misclasification inherent to measurements on some platforms. + An example of this can be seen in [`2 `_] (TODO). + + To maintain normalization :math:`p_01 + p_02 + p_12 \leq 1`. + + + **Details:** + + * Number of wires: 1 + * Number of parameters: 3 + + Args: + p_01 (float): The probability that a :math:`|0 \rangle \leftrightarrow |1 \rangle` parameter flip error occurs. + p_02 (float): The probability that a :math:`|0 \rangle \leftrightarrow |2 \rangle` parameter flip error occurs. + p_12 (float): The probability that a :math:`|1 \rangle \leftrightarrow |2 \rangle` parameter flip error occurs. + wires (Sequence[int] or int): the wire the channel acts on + id (str or None): String representing the operation (optional) + """ + + num_params = 3 + num_wires = 1 + grad_method = "F" + + def __init__(self, p_01, p_02, p_12, wires, id=None): + # Verify input + ps = (p_01, p_02, p_12) + for p in ps: + if not math.is_abstract(p) and not 0.0 <= p <= 1.0: + raise ValueError("All probabilities must be in the interval [0,1]") + if not any(math.is_abstract(p) for p in ps): + if not 0.0 <= sum(ps) <= 1.0: + raise ValueError("The sum of probabilities must be in the interval [0,1]") + + super().__init__(p_01, p_02, p_12, wires=wires, id=id) + + @staticmethod + def compute_kraus_matrices(p_01, p_02, p_12): # pylint:disable=arguments-differ + """Kraus matrices representing the TritFlip channel. + + Args: + p_01 (float): The probability that a :math:`|0 \rangle \leftrightarrow |1 \rangle` parameter flip error occurs. + p_02 (float): The probability that a :math:`|0 \rangle \leftrightarrow |2 \rangle` parameter flip error occurs. + p_12 (float): The probability that a :math:`|1 \rangle \leftrightarrow |2 \rangle` parameter flip error occurs. + + Returns: + list (array): list of Kraus matrices + + **Example** + + >>> qml.TritFlip.compute_kraus_matrices(0.5) + TODO + """ + K0 = math.sqrt(1 - (p_01 + p_02 + p_12) + math.eps) * math.convert_like( + math.cast_like(np.eye(3), p_01), p_01 + ) + K1 = math.sqrt(p_01 + math.eps) * math.convert_like( + math.cast_like(math.array([[0, 1, 0], [1, 0, 0], [0, 0, 1]]), p_01), p_01 + ) + K2 = math.sqrt(p_02 + math.eps) * math.convert_like( + math.cast_like(math.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]]), p_02), p_02 + ) + K3 = math.sqrt(p_12 + math.eps) * math.convert_like( + math.cast_like(math.array([[1, 0, 0], [0, 0, 1], [0, 1, 0]]), p_12), p_12 + ) + return [K0, K1, K2, K3] diff --git a/tests/devices/qutrit_mixed/test_qutrit_mixed_preprocessing.py b/tests/devices/qutrit_mixed/test_qutrit_mixed_preprocessing.py index 9574fdbec25..9e716a0fe19 100644 --- a/tests/devices/qutrit_mixed/test_qutrit_mixed_preprocessing.py +++ b/tests/devices/qutrit_mixed/test_qutrit_mixed_preprocessing.py @@ -126,6 +126,7 @@ def test_measurement_is_swapped_out(self, mp_fn, mp_cls, shots): (qml.Snapshot(), True), (qml.TRX(1.1, 0), True), (qml.QutritDepolarizingChannel(0.4, 0), True), + (qml.TritFlip(0.4, 0.1, 0.02, 0), True), ], ) def test_accepted_operator(self, op, expected): diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 585f623f499..7ac2f79518f 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -135,7 +135,7 @@ def test_kraus_jac_autograd(self): @pytest.mark.torch def test_kraus_jac_torch(self): - """Tests Jacobian of Kraus matrices using torch.""" + """Tests Jacobian of Kraus matrices using PyTorch.""" import torch p = torch.tensor(0.43, requires_grad=True) @@ -145,7 +145,7 @@ def test_kraus_jac_torch(self): @pytest.mark.tf def test_kraus_jac_tf(self): - """Tests Jacobian of Kraus matrices using tensorflow.""" + """Tests Jacobian of Kraus matrices using TensorFlow.""" import tensorflow as tf p = tf.Variable(0.43) @@ -161,7 +161,7 @@ def test_kraus_jac_tf(self): @pytest.mark.jax def test_kraus_jac_jax(self): - """Tests Jacobian of Kraus matrices using jax.""" + """Tests Jacobian of Kraus matrices using JAX.""" import jax jax.config.update("jax_enable_x64", True) @@ -263,3 +263,103 @@ def test_kraus_jac_jax(self): 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)) + + +class TestTritFlip: + """Tests for the quantum channel TritFlipChannel""" + + @pytest.mark.parametrize( + "ps", [(0, 0, 0), (0.1, 0.12, 0.3), (0.5, 0.4, 0.1), (1, 0, 0), (0, 1, 0), (0, 0, 1)] + ) + def test_ps_arbitrary(self, ps, tol): + """Test that various values of p give correct Kraus matrices""" + kraus_mats = qml.TritFlip(*ps, wires=0).kraus_matrices() + + expected_K0 = np.sqrt(1 - sum(ps)) * np.eye(3) + assert np.allclose(kraus_mats[0], expected_K0, atol=tol, rtol=0) + + Ks = [ + [[0, 1, 0], [1, 0, 0], [0, 0, 1]], + [[0, 0, 1], [0, 1, 0], [1, 0, 0]], + [[1, 0, 0], [0, 0, 1], [0, 1, 0]], + ] + + for p, K, res in zip(ps[1:], Ks, kraus_mats[1:]): + expected_K = np.sqrt(p) * K + assert np.allclose(res, expected_K, atol=tol, rtol=0) + + @pytest.mark.parametrize( + "p_01,p_02,p_12", + [(1.2, 0, 0), (0, -0.3, 0.5), (0, 0, 1 + math.eps), (1, math.eps, 0), (0.3, 0.4, 0.4)], + ) + def test_p_invalid_parameter(self, p_01, p_02, p_12): + """Ensures that error is thrown when p_01, p_02, p_12, or their sum are outside [0,1]""" + with pytest.raises(ValueError, match="must be in the interval"): + qml.TritFlip(p_01, p_02, p_12, wires=0).kraus_matrices() + + @staticmethod + def expected_jac_fn(p_01, p_02, p_12): + """Gets the expected Jacobian of Kraus matrices""" + # Set up the 3 partial derivatives of the 4 3x3 Kraus Matrices + partials = math.zeros((3, 4, 3, 3)) + + # All 3 partials have the same first Kraus Operator output + partials[:, 0] = -1 / (2 * math.sqrt(1 - (p_01 + p_02 + p_12))) * math.eye() + + # Set the matrix defined by each partials parameter, the rest are 0 + partials[0, 1] = 1 / (2 * math.sqrt(p_01)) * math.array([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) + partials[1, 2] = 1 / (2 * math.sqrt(p_02)) * math.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) + partials[2, 3] = 1 / (2 * math.sqrt(p_12)) * math.array([[1, 0, 0], [0, 0, 1], [0, 1, 0]]) + return partials + + @staticmethod + def kraus_fn(p_01, p_02, p_12): + """Gets a matrix of the Kraus matrices to be tested.""" + return qml.math.stack(qml.TritFlip(p_01, p_02, p_12, wires=0).kraus_matrices()) + + @pytest.mark.autograd + def test_kraus_jac_autograd(self): + """Tests Jacobian of Kraus matrices using autograd.""" + + p_01 = pnp.array(0.14, requires_grad=True) + p_02 = pnp.array(0.04, requires_grad=True) + p_12 = pnp.array(0.23, requires_grad=True) + jac = qml.jacobian(self.kraus_fn)(p_01, p_02, p_12) + 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 PyTorch.""" + import torch + + ps = [0.14, 0.04, 0.23] + + p_01 = torch.tensor(ps[0], requires_grad=True) + p_02 = torch.tensor(ps[1], requires_grad=True) + p_12 = torch.tensor(ps[2], requires_grad=True) + jac = torch.autograd.functional.jacobian(self.kraus_fn, p_01, p_02, p_12) + assert qml.math.allclose(jac.detach().numpy(), self.expected_jac_fn(*ps)) + + @pytest.mark.tf + def test_kraus_jac_tf(self): + """Tests Jacobian of Kraus matrices using TensorFlow.""" + import tensorflow as tf + + p_01 = tf.Variable(0.14) + p_02 = tf.Variable(0.04) + p_12 = tf.Variable(0.23) + with tf.GradientTape() as tape: + out = self.kraus_fn(p_01, p_02, p_12) + jac = tape.jacobian(out, p_01, p_02, p_12) + assert qml.math.allclose(jac, self.expected_jac_fn(p_01, p_02, p_12)) + + @pytest.mark.jax + def test_kraus_jac_jax(self): + """Tests Jacobian of Kraus matrices using JAX.""" + import jax + + p_01 = jax.numpy.array(0.14) + p_02 = jax.numpy.array(0.0) + p_12 = jax.numpy.array(0.23) + jac = jax.jacobian(self.kraus_fn)(p_01, p_02, p_12) + assert qml.math.allclose(jac, self.expected_jac_fn(p_01, p_02, p_12)) From bae24ea9053752e11f2b88c7277cbf464997fa9d Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Fri, 31 May 2024 22:49:04 -0700 Subject: [PATCH 02/13] Added changelog entry --- doc/releases/changelog-dev.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 3d90194d325..b43e49c13e2 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -148,9 +148,12 @@ * Implemented kwargs (`check_interface`, `check_trainability`, `rtol` and `atol`) support in `qml.equal` for the operators `Pow`, `Adjoint`, `Exp`, and `SProd`. [(#5668)](https://github.com/PennyLaneAI/pennylane/issues/5668) -* ``qml.QutritDepolarizingChannel`` has been added, allowing for depolarizing noise to be simulated on the `default.qutrit.mixed` device. +* `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.TritFlip` has been added, allowing for parameter flipping errors, such as misclassification, to be simulated on the `default.qutrit.mixed` device. + [(#5784)](https://github.com/PennyLaneAI/pennylane/pull/5784) +

Breaking changes 💔

* A custom decomposition can no longer be provided to `QDrift`. Instead, apply the operations in your custom From 0e4b26887cc7533e60cb79b9840d324f35479990 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Fri, 31 May 2024 22:59:04 -0700 Subject: [PATCH 03/13] Fixed tests and added output of Kraus matrices --- pennylane/ops/qutrit/channel.py | 19 ++++++++++++++++--- tests/ops/qutrit/test_qutrit_channel_ops.py | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index cf049edc44c..f8067a3764d 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -388,7 +388,7 @@ def __init__(self, p_01, p_02, p_12, wires, id=None): @staticmethod def compute_kraus_matrices(p_01, p_02, p_12): # pylint:disable=arguments-differ - """Kraus matrices representing the TritFlip channel. + r"""Kraus matrices representing the TritFlip channel. Args: p_01 (float): The probability that a :math:`|0 \rangle \leftrightarrow |1 \rangle` parameter flip error occurs. @@ -400,8 +400,21 @@ def compute_kraus_matrices(p_01, p_02, p_12): # pylint:disable=arguments-differ **Example** - >>> qml.TritFlip.compute_kraus_matrices(0.5) - TODO + >>> qml.TritFlip.compute_kraus_matrices(0.05, 0.01, 0.10) + [ + array([ [0.91651514, 0. , 0. ], + [0. , 0.91651514, 0. ], + [0. , 0. , 0.91651514]]), + array([ [0. , 0.2236068 , 0. ], + [0.2236068 , 0. , 0. ], + [0. , 0. , 0.2236068]]), + array([ [0. , 0. , 0.1 ], + [0. , 0.1 , 0. ], + [0.1 , 0. , 0. ]]), + array([ [0.31622777, 0. , 0. ], + [0. , 0. , 0.31622777], + [0. , 0.31622777, 0. ]]) + ] """ K0 = math.sqrt(1 - (p_01 + p_02 + p_12) + math.eps) * math.convert_like( math.cast_like(np.eye(3), p_01), p_01 diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 7ac2f79518f..af50414fc61 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -325,7 +325,7 @@ def test_kraus_jac_autograd(self): p_02 = pnp.array(0.04, requires_grad=True) p_12 = pnp.array(0.23, requires_grad=True) jac = qml.jacobian(self.kraus_fn)(p_01, p_02, p_12) - assert qml.math.allclose(jac, self.expected_jac_fn(p)) + assert qml.math.allclose(jac, self.expected_jac_fn(p_01, p_02, p_12)) @pytest.mark.torch def test_kraus_jac_torch(self): From a863acf529bdbf1c05f62d66c2ab5987c560b980 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Fri, 31 May 2024 23:00:05 -0700 Subject: [PATCH 04/13] Reformatted init --- pennylane/ops/qutrit/__init__.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pennylane/ops/qutrit/__init__.py b/pennylane/ops/qutrit/__init__.py index 9b84aca3335..7dbc601a62d 100644 --- a/pennylane/ops/qutrit/__init__.py +++ b/pennylane/ops/qutrit/__init__.py @@ -48,10 +48,6 @@ "THermitian", "GellMann", } -__channels__ = { - "QutritDepolarizingChannel", - "QutritAmplitudeDamping", - "TritFlip" -} +__channels__ = {"QutritDepolarizingChannel", "QutritAmplitudeDamping", "TritFlip"} __all__ = list(__ops__ | __obs__ | __channels__) From a638d05f981f0aa8dbeedd9f2dcc709a2aeeea2b Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Fri, 31 May 2024 23:12:25 -0700 Subject: [PATCH 05/13] Fixed the expected_jac_fn for computing Jacobians --- 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 af50414fc61..9848d380e23 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -304,7 +304,7 @@ def expected_jac_fn(p_01, p_02, p_12): partials = math.zeros((3, 4, 3, 3)) # All 3 partials have the same first Kraus Operator output - partials[:, 0] = -1 / (2 * math.sqrt(1 - (p_01 + p_02 + p_12))) * math.eye() + partials[:, 0] = -1 / (2 * math.sqrt(1 - (p_01 + p_02 + p_12))) * math.eye(3) # Set the matrix defined by each partials parameter, the rest are 0 partials[0, 1] = 1 / (2 * math.sqrt(p_01)) * math.array([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) From 082b455fd3321c0c964e48121910fad356646181 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Fri, 31 May 2024 23:22:01 -0700 Subject: [PATCH 06/13] Fixed documentation and added example link to qutrit misclassification --- pennylane/ops/qutrit/channel.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index f8067a3764d..97e618469c1 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -317,7 +317,7 @@ def compute_kraus_matrices(gamma_1, gamma_2): # pylint:disable=arguments-differ class TritFlip(Channel): r""" - Single-qutrit parameter flipping error channel, similar to (GellMann :math:`\{\gamma_1, \gamma_4, \gamma_6\}`). + Single-qutrit parameter flipping error channel, similar to (GellMann :math:`\{\lambda_1, \lambda_4, \lambda_6\}`). This channel is modelled by the following Kraus matrices: @@ -351,10 +351,10 @@ class TritFlip(Channel): .. note:: The Kraus operators :math:`\{K_0, K_1, K_2, K_3\}` are adapted from The BitFlip channel's Kraus operators. - This channel is primarily meant to characterize the misclasification inherent to measurements on some platforms. - An example of this can be seen in [`2 `_] (TODO). + This channel is primarily meant to characterize the misclassification inherent to measurements on some platforms. + An example of of a measurement with misclassification can be seen in [`1 `_] (Fig 1a). - To maintain normalization :math:`p_01 + p_02 + p_12 \leq 1`. + To maintain normalization :math:`p_{01} + p_{02} + p_{12} \leq 1`. **Details:** From 4b8bb7f5a1d0cd7c6510bb0b37d9b27c8d8313c6 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Fri, 31 May 2024 23:51:53 -0700 Subject: [PATCH 07/13] Fixed tests, interface tests for multiple parameters and arbitrary input test --- tests/ops/qutrit/test_qutrit_channel_ops.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 9848d380e23..786c53c1c40 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -284,8 +284,8 @@ def test_ps_arbitrary(self, ps, tol): [[1, 0, 0], [0, 0, 1], [0, 1, 0]], ] - for p, K, res in zip(ps[1:], Ks, kraus_mats[1:]): - expected_K = np.sqrt(p) * K + for p, K, res in zip(ps, Ks, kraus_mats[1:]): + expected_K = np.sqrt(p) * np.array(K) assert np.allclose(res, expected_K, atol=tol, rtol=0) @pytest.mark.parametrize( @@ -337,8 +337,11 @@ def test_kraus_jac_torch(self): p_01 = torch.tensor(ps[0], requires_grad=True) p_02 = torch.tensor(ps[1], requires_grad=True) p_12 = torch.tensor(ps[2], requires_grad=True) - jac = torch.autograd.functional.jacobian(self.kraus_fn, p_01, p_02, p_12) - assert qml.math.allclose(jac.detach().numpy(), self.expected_jac_fn(*ps)) + + jac = torch.autograd.functional.jacobian(self.kraus_fn, (p_01, p_02, p_12)) + expected_jac = self.expected_jac_fn(*ps) + for j, exp in zip(jac, expected_jac): + assert qml.math.allclose(j.detach().numpy(), exp) @pytest.mark.tf def test_kraus_jac_tf(self): @@ -350,7 +353,7 @@ def test_kraus_jac_tf(self): p_12 = tf.Variable(0.23) with tf.GradientTape() as tape: out = self.kraus_fn(p_01, p_02, p_12) - jac = tape.jacobian(out, p_01, p_02, p_12) + jac = tape.jacobian(out, (p_01, p_02, p_12)) assert qml.math.allclose(jac, self.expected_jac_fn(p_01, p_02, p_12)) @pytest.mark.jax @@ -361,5 +364,5 @@ def test_kraus_jac_jax(self): p_01 = jax.numpy.array(0.14) p_02 = jax.numpy.array(0.0) p_12 = jax.numpy.array(0.23) - jac = jax.jacobian(self.kraus_fn)(p_01, p_02, p_12) + jac = jax.jacobian(self.kraus_fn, argnums=[0, 1, 2])(p_01, p_02, p_12) assert qml.math.allclose(jac, self.expected_jac_fn(p_01, p_02, p_12)) From 1933b76cd8c6150da920886a2f6c7a3a4e2645b4 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Sat, 1 Jun 2024 00:13:29 -0700 Subject: [PATCH 08/13] Fixed JAX test --- 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 786c53c1c40..7e0a7eb33a7 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -362,7 +362,7 @@ def test_kraus_jac_jax(self): import jax p_01 = jax.numpy.array(0.14) - p_02 = jax.numpy.array(0.0) + p_02 = jax.numpy.array(0.04) p_12 = jax.numpy.array(0.23) jac = jax.jacobian(self.kraus_fn, argnums=[0, 1, 2])(p_01, p_02, p_12) assert qml.math.allclose(jac, self.expected_jac_fn(p_01, p_02, p_12)) From 493993ada312d00fa0d6fea7e632bbff0a18fd49 Mon Sep 17 00:00:00 2001 From: gabrielLydian Date: Mon, 3 Jun 2024 14:20:53 -0700 Subject: [PATCH 09/13] Changed amplitude damping gamma_x to gamma_xy for clarity --- pennylane/ops/qutrit/channel.py | 57 +++++++++--------- tests/ops/qutrit/test_qutrit_channel_ops.py | 65 +++++++++++---------- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index f07811086bc..92643481d51 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -234,39 +234,39 @@ class QutritAmplitudeDamping(Channel): .. math:: K_0 = \begin{bmatrix} 1 & 0 & 0\\ - 0 & \sqrt{1-\gamma_1} & 0 \\ - 0 & 0 & \sqrt{1-(\gamma_2+\gamma_3)} + 0 & \sqrt{1-\gamma_{01}} & 0 \\ + 0 & 0 & \sqrt{1-(\gamma_{02}+\gamma_{12})} \end{bmatrix} .. math:: K_1 = \begin{bmatrix} - 0 & \sqrt{\gamma_1} & 0 \\ + 0 & \sqrt{\gamma_{01}} & 0 \\ 0 & 0 & 0 \\ 0 & 0 & 0 \end{bmatrix}, \quad K_2 = \begin{bmatrix} - 0 & 0 & \sqrt{\gamma_2} \\ + 0 & 0 & \sqrt{\gamma_{02}} \\ 0 & 0 & 0 \\ 0 & 0 & 0 \end{bmatrix}, \quad K_3 = \begin{bmatrix} 0 & 0 & 0 \\ - 0 & 0 & \sqrt{\gamma_3} \\ + 0 & 0 & \sqrt{\gamma_{12}} \\ 0 & 0 & 0 \end{bmatrix} - where :math:`\gamma_1, \gamma_2, \gamma_3 \in [0, 1]` are the amplitude damping + where :math:`\gamma_{01}, \gamma_{02}, \gamma_{12} \in [0, 1]` are the amplitude damping probabilities for subspaces (0,1), (0,2), and (1,2) respectively. .. note:: - When :math:`\gamma_3=0` then Kraus operators :math:`\{K_0, K_1, K_2\}` are adapted from + When :math:`\gamma_{12}=0` then Kraus operators :math:`\{K_0, K_1, K_2\}` are adapted from [`1 `_] (Eq. 8). The Kraus operator :math:`K_3` represents the :math:`|2 \rangle \rightarrow |1 \rangle` transition which is more likely on some devices [`2 `_] (Sec II.A). - To maintain normalization :math:`\gamma_2 + \gamma_3 \leq 1`. + To maintain normalization :math:`\gamma_{02} + \gamma_{12} \leq 1`. **Details:** @@ -275,9 +275,9 @@ class QutritAmplitudeDamping(Channel): * Number of parameters: 3 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. - gamma_3 (float): :math:`|2 \rangle \rightarrow |1 \rangle` amplitude damping probability. + gamma_01 (float): :math:`|1 \rangle \rightarrow |0 \rangle` amplitude damping probability. + gamma_02 (float): :math:`|2 \rangle \rightarrow |0 \rangle` amplitude damping probability. + gamma_12 (float): :math:`|2 \rangle \rightarrow |1 \rangle` amplitude damping probability. wires (Sequence[int] or int): the wire the channel acts on id (str or None): String representing the operation (optional) """ @@ -286,25 +286,25 @@ class QutritAmplitudeDamping(Channel): num_wires = 1 grad_method = "F" - def __init__(self, gamma_1, gamma_2, gamma_3, wires, id=None): + def __init__(self, gamma_01, gamma_02, gamma_12, wires, id=None): # Verify input - for gamma in (gamma_1, gamma_2, gamma_3): + for gamma in (gamma_01, gamma_02, gamma_12): if not math.is_abstract(gamma): if not 0.0 <= gamma <= 1.0: raise ValueError("Each probability must be in the interval [0,1]") - if not (math.is_abstract(gamma_2) or math.is_abstract(gamma_3)): - if not 0.0 <= gamma_2 + gamma_3 <= 1.0: - raise ValueError(r"\gamma_2+\gamma_3 must be in the interval [0,1]") - super().__init__(gamma_1, gamma_2, gamma_3, wires=wires, id=id) + if not (math.is_abstract(gamma_02) or math.is_abstract(gamma_12)): + if not 0.0 <= gamma_02 + gamma_12 <= 1.0: + raise ValueError(r"\gamma_{02}+\gamma_{12} must be in the interval [0,1]") + super().__init__(gamma_01, gamma_02, gamma_12, wires=wires, id=id) @staticmethod - def compute_kraus_matrices(gamma_1, gamma_2, gamma_3): # pylint:disable=arguments-differ + def compute_kraus_matrices(gamma_01, gamma_02, gamma_12): # pylint:disable=arguments-differ r"""Kraus matrices representing the ``QutritAmplitudeDamping`` 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. - gamma_3 (float): :math:`|2\rangle \rightarrow |1\rangle` amplitude damping probability. + gamma_01 (float): :math:`|1\rangle \rightarrow |0\rangle` amplitude damping probability. + gamma_02 (float): :math:`|2\rangle \rightarrow |0\rangle` amplitude damping probability. + gamma_12 (float): :math:`|2\rangle \rightarrow |1\rangle` amplitude damping probability. Returns: list(array): list of Kraus matrices @@ -328,21 +328,20 @@ def compute_kraus_matrices(gamma_1, gamma_2, gamma_3): # pylint:disable=argumen ] """ K0 = math.diag( - [1, math.sqrt(1 - gamma_1 + math.eps), math.sqrt(1 - gamma_2 - gamma_3 + math.eps)] + [1, math.sqrt(1 - gamma_01 + math.eps), math.sqrt(1 - gamma_02 - gamma_12 + 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 + K1 = math.sqrt(gamma_01 + math.eps) * math.convert_like( + math.cast_like(math.array([[0, 1, 0], [0, 0, 0], [0, 0, 0]]), gamma_01), gamma_01 ) - 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 + K2 = math.sqrt(gamma_02 + math.eps) * math.convert_like( + math.cast_like(math.array([[0, 0, 1], [0, 0, 0], [0, 0, 0]]), gamma_02), gamma_02 ) - K3 = math.sqrt(gamma_3 + math.eps) * math.convert_like( - math.cast_like(math.array([[0, 0, 0], [0, 0, 1], [0, 0, 0]]), gamma_3), gamma_3 + K3 = math.sqrt(gamma_12 + math.eps) * math.convert_like( + math.cast_like(math.array([[0, 0, 0], [0, 0, 1], [0, 0, 0]]), gamma_12), gamma_12 ) return [K0, K1, K2, K3] - class TritFlip(Channel): r""" Single-qutrit parameter flipping error channel, similar to (GellMann :math:`\{\lambda_1, \lambda_4, \lambda_6\}`). diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index 68536a72dc3..fc0c1b2d429 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_1=gamma_2=0 gives correct Kraus matrices""" + """Test gamma_01=gamma_02=0 gives correct Kraus matrices""" kraus_mats = qml.QutritAmplitudeDamping(0, 0, 0, wires=0).kraus_matrices() assert np.allclose(kraus_mats[0], np.eye(3), atol=tol, rtol=0) for kraus_mat in kraus_mats[1:]: @@ -209,55 +209,56 @@ def test_gamma_arbitrary(self, gamma1, gamma2, gamma3, tol): ), ) def test_gamma_invalid_parameter(self, gamma1, gamma2, gamma3): - """Ensures that error is thrown when gamma_1, gamma_2, gamma_3, or (gamma_2 + gamma_3) are outside [0,1]""" + """Ensures that error is thrown when + gamma_01, gamma_02, gamma_12, or (gamma_02 + gamma_12) are outside [0,1]""" with pytest.raises(ValueError, match="must be in the interval"): channel.QutritAmplitudeDamping(gamma1, gamma2, gamma3, wires=0).kraus_matrices() @staticmethod - def expected_jac_fn(gamma_1, gamma_2, gamma_3): + def expected_jac_fn(gamma_01, gamma_02, gamma_12): """Gets the expected Jacobian of Kraus matrices""" partial_1 = [math.zeros((3, 3)) for _ in range(4)] - partial_1[0][1, 1] = -1 / (2 * math.sqrt(1 - gamma_1)) - partial_1[1][0, 1] = 1 / (2 * math.sqrt(gamma_1)) + partial_1[0][1, 1] = -1 / (2 * math.sqrt(1 - gamma_01)) + partial_1[1][0, 1] = 1 / (2 * math.sqrt(gamma_01)) partial_2 = [math.zeros((3, 3)) for _ in range(4)] - partial_2[0][2, 2] = -1 / (2 * math.sqrt(1 - gamma_2 - gamma_3)) - partial_2[2][0, 2] = 1 / (2 * math.sqrt(gamma_2)) + partial_2[0][2, 2] = -1 / (2 * math.sqrt(1 - gamma_02 - gamma_12)) + partial_2[2][0, 2] = 1 / (2 * math.sqrt(gamma_02)) partial_3 = [math.zeros((3, 3)) for _ in range(4)] - partial_3[0][2, 2] = -1 / (2 * math.sqrt(1 - gamma_2 - gamma_3)) - partial_3[3][1, 2] = 1 / (2 * math.sqrt(gamma_3)) + partial_3[0][2, 2] = -1 / (2 * math.sqrt(1 - gamma_02 - gamma_12)) + partial_3[3][1, 2] = 1 / (2 * math.sqrt(gamma_12)) return [partial_1, partial_2, partial_3] @staticmethod - def kraus_fn(gamma_1, gamma_2, gamma_3): + def kraus_fn(gamma_01, gamma_02, gamma_12): """Gets the Kraus matrices of QutritAmplitudeDamping channel, used for differentiation.""" - damping_channel = qml.QutritAmplitudeDamping(gamma_1, gamma_2, gamma_3, wires=0) + damping_channel = qml.QutritAmplitudeDamping(gamma_01, gamma_02, gamma_12, wires=0) return math.stack(damping_channel.kraus_matrices()) @pytest.mark.autograd def test_kraus_jac_autograd(self): """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) - gamma_3 = pnp.array(0.35, requires_grad=True) + gamma_01 = pnp.array(0.43, requires_grad=True) + gamma_02 = pnp.array(0.12, requires_grad=True) + gamma_12 = pnp.array(0.35, requires_grad=True) - jac = qml.jacobian(self.kraus_fn)(gamma_1, gamma_2, gamma_3) - assert math.allclose(jac, self.expected_jac_fn(gamma_1, gamma_2, gamma_3)) + jac = qml.jacobian(self.kraus_fn)(gamma_01, gamma_02, gamma_12) + assert math.allclose(jac, self.expected_jac_fn(gamma_01, gamma_02, gamma_12)) @pytest.mark.torch def test_kraus_jac_torch(self): """Tests Jacobian of Kraus matrices using PyTorch.""" import torch - gamma_1 = torch.tensor(0.43, requires_grad=True) - gamma_2 = torch.tensor(0.12, requires_grad=True) - gamma_3 = torch.tensor(0.35, requires_grad=True) + gamma_01 = torch.tensor(0.43, requires_grad=True) + gamma_02 = torch.tensor(0.12, requires_grad=True) + gamma_12 = torch.tensor(0.35, requires_grad=True) - jac = torch.autograd.functional.jacobian(self.kraus_fn, (gamma_1, gamma_2, gamma_3)) + jac = torch.autograd.functional.jacobian(self.kraus_fn, (gamma_01, gamma_02, gamma_12)) expected = self.expected_jac_fn( - gamma_1.detach().numpy(), gamma_2.detach().numpy(), gamma_3.detach().numpy() + gamma_01.detach().numpy(), gamma_02.detach().numpy(), gamma_12.detach().numpy() ) for res_partial, exp_partial in zip(jac, expected): @@ -268,26 +269,26 @@ def test_kraus_jac_tf(self): """Tests Jacobian of Kraus matrices using TensorFlow.""" import tensorflow as tf - gamma_1 = tf.Variable(0.43) - gamma_2 = tf.Variable(0.12) - gamma_3 = tf.Variable(0.35) + gamma_01 = tf.Variable(0.43) + gamma_02 = tf.Variable(0.12) + gamma_12 = tf.Variable(0.35) with tf.GradientTape() as tape: - out = self.kraus_fn(gamma_1, gamma_2, gamma_3) - jac = tape.jacobian(out, (gamma_1, gamma_2, gamma_3)) - assert math.allclose(jac, self.expected_jac_fn(gamma_1, gamma_2, gamma_3)) + out = self.kraus_fn(gamma_01, gamma_02, gamma_12) + jac = tape.jacobian(out, (gamma_01, gamma_02, gamma_12)) + assert math.allclose(jac, self.expected_jac_fn(gamma_01, gamma_02, gamma_12)) @pytest.mark.jax def test_kraus_jac_jax(self): """Tests Jacobian of Kraus matrices using JAX.""" import jax - gamma_1 = jax.numpy.array(0.43) - gamma_2 = jax.numpy.array(0.12) - gamma_3 = jax.numpy.array(0.35) + gamma_01 = jax.numpy.array(0.43) + gamma_02 = jax.numpy.array(0.12) + gamma_12 = jax.numpy.array(0.35) - jac = jax.jacobian(self.kraus_fn, argnums=[0, 1, 2])(gamma_1, gamma_2, gamma_3) - assert math.allclose(jac, self.expected_jac_fn(gamma_1, gamma_2, gamma_3)) + jac = jax.jacobian(self.kraus_fn, argnums=[0, 1, 2])(gamma_01, gamma_02, gamma_12) + assert math.allclose(jac, self.expected_jac_fn(gamma_01, gamma_02, gamma_12)) class TestTritFlip: From 27a198e940f34f87e71c535682f93e77c9f7e0c6 Mon Sep 17 00:00:00 2001 From: gabrielLydian Date: Mon, 3 Jun 2024 14:52:45 -0700 Subject: [PATCH 10/13] Added suggested doc changes. --- doc/releases/changelog-dev.md | 3 +- pennylane/ops/qutrit/channel.py | 33 +++++++++++---------- tests/ops/qutrit/test_qutrit_channel_ops.py | 2 +- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 2e363a0a67a..2a23f2e6ed3 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -156,7 +156,8 @@ [(#5503)](https://github.com/PennyLaneAI/pennylane/pull/5503) [(#5757)](https://github.com/PennyLaneAI/pennylane/pull/5757) -* `qml.TritFlip` has been added, allowing for parameter flipping errors, such as misclassification, to be simulated on the `default.qutrit.mixed` device. +* `qml.TritFlip` has been added, allowing for trit flip errors, such as misclassification, + to be simulated on the `default.qutrit.mixed` device. [(#5784)](https://github.com/PennyLaneAI/pennylane/pull/5784) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 92643481d51..b8df5425ba7 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -125,7 +125,7 @@ class QutritDepolarizingChannel(Channel): Args: 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 + wires (Sequence[int] or int): The wire the channel acts on id (str or None): String representing the operation (optional) """ @@ -142,7 +142,7 @@ 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}` + p (float): Each qutrit Pauli gate is applied with probability :math:`\frac{p}{8}` Returns: list (array): list of Kraus matrices @@ -278,7 +278,7 @@ class QutritAmplitudeDamping(Channel): gamma_01 (float): :math:`|1 \rangle \rightarrow |0 \rangle` amplitude damping probability. gamma_02 (float): :math:`|2 \rangle \rightarrow |0 \rangle` amplitude damping probability. gamma_12 (float): :math:`|2 \rangle \rightarrow |1 \rangle` amplitude damping probability. - wires (Sequence[int] or int): the wire the channel acts on + wires (Sequence[int] or int): The wire the channel acts on id (str or None): String representing the operation (optional) """ @@ -344,7 +344,7 @@ def compute_kraus_matrices(gamma_01, gamma_02, gamma_12): # pylint:disable=argu class TritFlip(Channel): r""" - Single-qutrit parameter flipping error channel, similar to (GellMann :math:`\{\lambda_1, \lambda_4, \lambda_6\}`). + Single-qutrit trit flip error channel. Applying "bit flips" on each qutrit subspace. This channel is modelled by the following Kraus matrices: @@ -372,14 +372,15 @@ class TritFlip(Channel): 0 & 1 & 0 \end{bmatrix} - where :math:`p_{01}, p_{02}, p_{12} \in [0, 1]` is the probability of a parameter flipping for subspaces (0,1), - (0,2), and (1,2) respectively. + where :math:`p_{01}, p_{02}, p_{12} \in [0, 1]` is the probability of two qutrit state-coefficients + flipping for subspaces (0,1), (0,2), and (1,2) respectively. .. note:: - The Kraus operators :math:`\{K_0, K_1, K_2, K_3\}` are adapted from The BitFlip channel's Kraus operators. + The Kraus operators :math:`\{K_0, K_1, K_2, K_3\}` are adapted from the + `BitFlip `_ channel's Kraus operators. - This channel is primarily meant to characterize the misclassification inherent to measurements on some platforms. - An example of of a measurement with misclassification can be seen in [`1 `_] (Fig 1a). + This channel is primarily meant to simulate the misclassification inherent to measurements on some platforms. + An example of a measurement with misclassification can be seen in [`1 `_] (Fig 1a). To maintain normalization :math:`p_{01} + p_{02} + p_{12} \leq 1`. @@ -390,10 +391,10 @@ class TritFlip(Channel): * Number of parameters: 3 Args: - p_01 (float): The probability that a :math:`|0 \rangle \leftrightarrow |1 \rangle` parameter flip error occurs. - p_02 (float): The probability that a :math:`|0 \rangle \leftrightarrow |2 \rangle` parameter flip error occurs. - p_12 (float): The probability that a :math:`|1 \rangle \leftrightarrow |2 \rangle` parameter flip error occurs. - wires (Sequence[int] or int): the wire the channel acts on + p_01 (float): The probability that a :math:`|0 \rangle \leftrightarrow |1 \rangle` trit flip error occurs. + p_02 (float): The probability that a :math:`|0 \rangle \leftrightarrow |2 \rangle` trit flip error occurs. + p_12 (float): The probability that a :math:`|1 \rangle \leftrightarrow |2 \rangle` trit flip error occurs. + wires (Sequence[int] or int): The wire the channel acts on id (str or None): String representing the operation (optional) """ @@ -418,9 +419,9 @@ def compute_kraus_matrices(p_01, p_02, p_12): # pylint:disable=arguments-differ r"""Kraus matrices representing the TritFlip channel. Args: - p_01 (float): The probability that a :math:`|0 \rangle \leftrightarrow |1 \rangle` parameter flip error occurs. - p_02 (float): The probability that a :math:`|0 \rangle \leftrightarrow |2 \rangle` parameter flip error occurs. - p_12 (float): The probability that a :math:`|1 \rangle \leftrightarrow |2 \rangle` parameter flip error occurs. + p_01 (float): The probability that a :math:`|0 \rangle \leftrightarrow |1 \rangle` trit flip error occurs. + p_02 (float): The probability that a :math:`|0 \rangle \leftrightarrow |2 \rangle` trit flip error occurs. + p_12 (float): The probability that a :math:`|1 \rangle \leftrightarrow |2 \rangle` trit flip error occurs. Returns: list (array): list of Kraus matrices diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index fc0c1b2d429..fd967872943 100644 --- a/tests/ops/qutrit/test_qutrit_channel_ops.py +++ b/tests/ops/qutrit/test_qutrit_channel_ops.py @@ -292,7 +292,7 @@ def test_kraus_jac_jax(self): class TestTritFlip: - """Tests for the quantum channel TritFlipChannel""" + """Tests for the quantum channel TritFlip""" @pytest.mark.parametrize( "ps", [(0, 0, 0), (0.1, 0.12, 0.3), (0.5, 0.4, 0.1), (1, 0, 0), (0, 1, 0), (0, 0, 1)] From 54cc69582c4054b7cbc912e31cdeb08f85487593 Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill <78718539+Gabriel-Bottrill@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:11:44 -0700 Subject: [PATCH 11/13] Apply suggestions from code review Co-authored-by: Olivia Di Matteo <2068515+glassnotes@users.noreply.github.com> --- pennylane/ops/qutrit/channel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index b8df5425ba7..3d640634d1b 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -344,7 +344,7 @@ def compute_kraus_matrices(gamma_01, gamma_02, gamma_12): # pylint:disable=argu class TritFlip(Channel): r""" - Single-qutrit trit flip error channel. Applying "bit flips" on each qutrit subspace. + Single-qutrit trit flip error channel, used for applying "bit flips" on each qutrit subspace. This channel is modelled by the following Kraus matrices: @@ -372,8 +372,8 @@ class TritFlip(Channel): 0 & 1 & 0 \end{bmatrix} - where :math:`p_{01}, p_{02}, p_{12} \in [0, 1]` is the probability of two qutrit state-coefficients - flipping for subspaces (0,1), (0,2), and (1,2) respectively. + where :math:`p_{01}, p_{02}, p_{12} \in [0, 1]` is the probability of a "trit flip" occurring + within subspaces (0,1), (0,2), and (1,2) respectively. .. note:: The Kraus operators :math:`\{K_0, K_1, K_2, K_3\}` are adapted from the From e5ec16aee9d73baa62d4d967d62af8c361d75f2d Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Tue, 4 Jun 2024 15:23:07 -0700 Subject: [PATCH 12/13] Fixed pylint error --- 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 3d640634d1b..996fd6fa4ae 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -372,7 +372,7 @@ class TritFlip(Channel): 0 & 1 & 0 \end{bmatrix} - where :math:`p_{01}, p_{02}, p_{12} \in [0, 1]` is the probability of a "trit flip" occurring + where :math:`p_{01}, p_{02}, p_{12} \in [0, 1]` is the probability of a "trit flip" occurring within subspaces (0,1), (0,2), and (1,2) respectively. .. note:: From e160944d6efa6654174a93d9da13fd617136c9bb Mon Sep 17 00:00:00 2001 From: Gabriel Bottrill Date: Tue, 4 Jun 2024 15:30:57 -0700 Subject: [PATCH 13/13] Removed changes to gamma --- pennylane/ops/qutrit/channel.py | 56 +++++++++--------- tests/ops/qutrit/test_qutrit_channel_ops.py | 64 ++++++++++----------- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/pennylane/ops/qutrit/channel.py b/pennylane/ops/qutrit/channel.py index 996fd6fa4ae..6bbaacc15e2 100644 --- a/pennylane/ops/qutrit/channel.py +++ b/pennylane/ops/qutrit/channel.py @@ -234,39 +234,39 @@ class QutritAmplitudeDamping(Channel): .. math:: K_0 = \begin{bmatrix} 1 & 0 & 0\\ - 0 & \sqrt{1-\gamma_{01}} & 0 \\ - 0 & 0 & \sqrt{1-(\gamma_{02}+\gamma_{12})} + 0 & \sqrt{1-\gamma_1} & 0 \\ + 0 & 0 & \sqrt{1-(\gamma_2+\gamma_3)} \end{bmatrix} .. math:: K_1 = \begin{bmatrix} - 0 & \sqrt{\gamma_{01}} & 0 \\ + 0 & \sqrt{\gamma_1} & 0 \\ 0 & 0 & 0 \\ 0 & 0 & 0 \end{bmatrix}, \quad K_2 = \begin{bmatrix} - 0 & 0 & \sqrt{\gamma_{02}} \\ + 0 & 0 & \sqrt{\gamma_2} \\ 0 & 0 & 0 \\ 0 & 0 & 0 \end{bmatrix}, \quad K_3 = \begin{bmatrix} 0 & 0 & 0 \\ - 0 & 0 & \sqrt{\gamma_{12}} \\ + 0 & 0 & \sqrt{\gamma_3} \\ 0 & 0 & 0 \end{bmatrix} - where :math:`\gamma_{01}, \gamma_{02}, \gamma_{12} \in [0, 1]` are the amplitude damping + where :math:`\gamma_1, \gamma_2, \gamma_3 \in [0, 1]` are the amplitude damping probabilities for subspaces (0,1), (0,2), and (1,2) respectively. .. note:: - When :math:`\gamma_{12}=0` then Kraus operators :math:`\{K_0, K_1, K_2\}` are adapted from + When :math:`\gamma_3=0` then Kraus operators :math:`\{K_0, K_1, K_2\}` are adapted from [`1 `_] (Eq. 8). The Kraus operator :math:`K_3` represents the :math:`|2 \rangle \rightarrow |1 \rangle` transition which is more likely on some devices [`2 `_] (Sec II.A). - To maintain normalization :math:`\gamma_{02} + \gamma_{12} \leq 1`. + To maintain normalization :math:`\gamma_2 + \gamma_3 \leq 1`. **Details:** @@ -275,9 +275,9 @@ class QutritAmplitudeDamping(Channel): * Number of parameters: 3 Args: - gamma_01 (float): :math:`|1 \rangle \rightarrow |0 \rangle` amplitude damping probability. - gamma_02 (float): :math:`|2 \rangle \rightarrow |0 \rangle` amplitude damping probability. - gamma_12 (float): :math:`|2 \rangle \rightarrow |1 \rangle` 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. + gamma_3 (float): :math:`|2 \rangle \rightarrow |1 \rangle` amplitude damping probability. wires (Sequence[int] or int): The wire the channel acts on id (str or None): String representing the operation (optional) """ @@ -286,25 +286,25 @@ class QutritAmplitudeDamping(Channel): num_wires = 1 grad_method = "F" - def __init__(self, gamma_01, gamma_02, gamma_12, wires, id=None): + def __init__(self, gamma_1, gamma_2, gamma_3, wires, id=None): # Verify input - for gamma in (gamma_01, gamma_02, gamma_12): + for gamma in (gamma_1, gamma_2, gamma_3): if not math.is_abstract(gamma): if not 0.0 <= gamma <= 1.0: raise ValueError("Each probability must be in the interval [0,1]") - if not (math.is_abstract(gamma_02) or math.is_abstract(gamma_12)): - if not 0.0 <= gamma_02 + gamma_12 <= 1.0: - raise ValueError(r"\gamma_{02}+\gamma_{12} must be in the interval [0,1]") - super().__init__(gamma_01, gamma_02, gamma_12, wires=wires, id=id) + if not (math.is_abstract(gamma_2) or math.is_abstract(gamma_3)): + if not 0.0 <= gamma_2 + gamma_3 <= 1.0: + raise ValueError(r"\gamma_2+\gamma_3 must be in the interval [0,1]") + super().__init__(gamma_1, gamma_2, gamma_3, wires=wires, id=id) @staticmethod - def compute_kraus_matrices(gamma_01, gamma_02, gamma_12): # pylint:disable=arguments-differ + def compute_kraus_matrices(gamma_1, gamma_2, gamma_3): # pylint:disable=arguments-differ r"""Kraus matrices representing the ``QutritAmplitudeDamping`` channel. Args: - gamma_01 (float): :math:`|1\rangle \rightarrow |0\rangle` amplitude damping probability. - gamma_02 (float): :math:`|2\rangle \rightarrow |0\rangle` amplitude damping probability. - gamma_12 (float): :math:`|2\rangle \rightarrow |1\rangle` 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. + gamma_3 (float): :math:`|2\rangle \rightarrow |1\rangle` amplitude damping probability. Returns: list(array): list of Kraus matrices @@ -328,16 +328,16 @@ def compute_kraus_matrices(gamma_01, gamma_02, gamma_12): # pylint:disable=argu ] """ K0 = math.diag( - [1, math.sqrt(1 - gamma_01 + math.eps), math.sqrt(1 - gamma_02 - gamma_12 + math.eps)] + [1, math.sqrt(1 - gamma_1 + math.eps), math.sqrt(1 - gamma_2 - gamma_3 + math.eps)] ) - K1 = math.sqrt(gamma_01 + math.eps) * math.convert_like( - math.cast_like(math.array([[0, 1, 0], [0, 0, 0], [0, 0, 0]]), gamma_01), gamma_01 + 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_02 + math.eps) * math.convert_like( - math.cast_like(math.array([[0, 0, 1], [0, 0, 0], [0, 0, 0]]), gamma_02), gamma_02 + 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 ) - K3 = math.sqrt(gamma_12 + math.eps) * math.convert_like( - math.cast_like(math.array([[0, 0, 0], [0, 0, 1], [0, 0, 0]]), gamma_12), gamma_12 + K3 = math.sqrt(gamma_3 + math.eps) * math.convert_like( + math.cast_like(math.array([[0, 0, 0], [0, 0, 1], [0, 0, 0]]), gamma_3), gamma_3 ) return [K0, K1, K2, K3] diff --git a/tests/ops/qutrit/test_qutrit_channel_ops.py b/tests/ops/qutrit/test_qutrit_channel_ops.py index fd967872943..ee79c64c622 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_01=gamma_02=0 gives correct Kraus matrices""" + """Test gamma_1=gamma_2=0 gives correct Kraus matrices""" kraus_mats = qml.QutritAmplitudeDamping(0, 0, 0, wires=0).kraus_matrices() assert np.allclose(kraus_mats[0], np.eye(3), atol=tol, rtol=0) for kraus_mat in kraus_mats[1:]: @@ -210,55 +210,55 @@ def test_gamma_arbitrary(self, gamma1, gamma2, gamma3, tol): ) def test_gamma_invalid_parameter(self, gamma1, gamma2, gamma3): """Ensures that error is thrown when - gamma_01, gamma_02, gamma_12, or (gamma_02 + gamma_12) are outside [0,1]""" + gamma_1, gamma_2, gamma_3, or (gamma_2 + gamma_3) are outside [0,1]""" with pytest.raises(ValueError, match="must be in the interval"): channel.QutritAmplitudeDamping(gamma1, gamma2, gamma3, wires=0).kraus_matrices() @staticmethod - def expected_jac_fn(gamma_01, gamma_02, gamma_12): + def expected_jac_fn(gamma_1, gamma_2, gamma_3): """Gets the expected Jacobian of Kraus matrices""" partial_1 = [math.zeros((3, 3)) for _ in range(4)] - partial_1[0][1, 1] = -1 / (2 * math.sqrt(1 - gamma_01)) - partial_1[1][0, 1] = 1 / (2 * math.sqrt(gamma_01)) + 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(4)] - partial_2[0][2, 2] = -1 / (2 * math.sqrt(1 - gamma_02 - gamma_12)) - partial_2[2][0, 2] = 1 / (2 * math.sqrt(gamma_02)) + partial_2[0][2, 2] = -1 / (2 * math.sqrt(1 - gamma_2 - gamma_3)) + partial_2[2][0, 2] = 1 / (2 * math.sqrt(gamma_2)) partial_3 = [math.zeros((3, 3)) for _ in range(4)] - partial_3[0][2, 2] = -1 / (2 * math.sqrt(1 - gamma_02 - gamma_12)) - partial_3[3][1, 2] = 1 / (2 * math.sqrt(gamma_12)) + partial_3[0][2, 2] = -1 / (2 * math.sqrt(1 - gamma_2 - gamma_3)) + partial_3[3][1, 2] = 1 / (2 * math.sqrt(gamma_3)) return [partial_1, partial_2, partial_3] @staticmethod - def kraus_fn(gamma_01, gamma_02, gamma_12): + def kraus_fn(gamma_1, gamma_2, gamma_3): """Gets the Kraus matrices of QutritAmplitudeDamping channel, used for differentiation.""" - damping_channel = qml.QutritAmplitudeDamping(gamma_01, gamma_02, gamma_12, wires=0) + damping_channel = qml.QutritAmplitudeDamping(gamma_1, gamma_2, gamma_3, wires=0) return math.stack(damping_channel.kraus_matrices()) @pytest.mark.autograd def test_kraus_jac_autograd(self): """Tests Jacobian of Kraus matrices using autograd.""" - gamma_01 = pnp.array(0.43, requires_grad=True) - gamma_02 = pnp.array(0.12, requires_grad=True) - gamma_12 = pnp.array(0.35, requires_grad=True) + gamma_1 = pnp.array(0.43, requires_grad=True) + gamma_2 = pnp.array(0.12, requires_grad=True) + gamma_3 = pnp.array(0.35, requires_grad=True) - jac = qml.jacobian(self.kraus_fn)(gamma_01, gamma_02, gamma_12) - assert math.allclose(jac, self.expected_jac_fn(gamma_01, gamma_02, gamma_12)) + jac = qml.jacobian(self.kraus_fn)(gamma_1, gamma_2, gamma_3) + assert math.allclose(jac, self.expected_jac_fn(gamma_1, gamma_2, gamma_3)) @pytest.mark.torch def test_kraus_jac_torch(self): """Tests Jacobian of Kraus matrices using PyTorch.""" import torch - gamma_01 = torch.tensor(0.43, requires_grad=True) - gamma_02 = torch.tensor(0.12, requires_grad=True) - gamma_12 = torch.tensor(0.35, requires_grad=True) + gamma_1 = torch.tensor(0.43, requires_grad=True) + gamma_2 = torch.tensor(0.12, requires_grad=True) + gamma_3 = torch.tensor(0.35, requires_grad=True) - jac = torch.autograd.functional.jacobian(self.kraus_fn, (gamma_01, gamma_02, gamma_12)) + jac = torch.autograd.functional.jacobian(self.kraus_fn, (gamma_1, gamma_2, gamma_3)) expected = self.expected_jac_fn( - gamma_01.detach().numpy(), gamma_02.detach().numpy(), gamma_12.detach().numpy() + gamma_1.detach().numpy(), gamma_2.detach().numpy(), gamma_3.detach().numpy() ) for res_partial, exp_partial in zip(jac, expected): @@ -269,26 +269,26 @@ def test_kraus_jac_tf(self): """Tests Jacobian of Kraus matrices using TensorFlow.""" import tensorflow as tf - gamma_01 = tf.Variable(0.43) - gamma_02 = tf.Variable(0.12) - gamma_12 = tf.Variable(0.35) + gamma_1 = tf.Variable(0.43) + gamma_2 = tf.Variable(0.12) + gamma_3 = tf.Variable(0.35) with tf.GradientTape() as tape: - out = self.kraus_fn(gamma_01, gamma_02, gamma_12) - jac = tape.jacobian(out, (gamma_01, gamma_02, gamma_12)) - assert math.allclose(jac, self.expected_jac_fn(gamma_01, gamma_02, gamma_12)) + out = self.kraus_fn(gamma_1, gamma_2, gamma_3) + jac = tape.jacobian(out, (gamma_1, gamma_2, gamma_3)) + assert math.allclose(jac, self.expected_jac_fn(gamma_1, gamma_2, gamma_3)) @pytest.mark.jax def test_kraus_jac_jax(self): """Tests Jacobian of Kraus matrices using JAX.""" import jax - gamma_01 = jax.numpy.array(0.43) - gamma_02 = jax.numpy.array(0.12) - gamma_12 = jax.numpy.array(0.35) + gamma_1 = jax.numpy.array(0.43) + gamma_2 = jax.numpy.array(0.12) + gamma_3 = jax.numpy.array(0.35) - jac = jax.jacobian(self.kraus_fn, argnums=[0, 1, 2])(gamma_01, gamma_02, gamma_12) - assert math.allclose(jac, self.expected_jac_fn(gamma_01, gamma_02, gamma_12)) + jac = jax.jacobian(self.kraus_fn, argnums=[0, 1, 2])(gamma_1, gamma_2, gamma_3) + assert math.allclose(jac, self.expected_jac_fn(gamma_1, gamma_2, gamma_3)) class TestTritFlip: