Skip to content

Commit

Permalink
Improve variable names for QutritAmplitudeDamping channel (#5799)
Browse files Browse the repository at this point in the history
**Context:**
The QutritAmplitudeDamping channel #5503 and #5757 variable names are
named with numbers, when making the TritFlip channel #5784 I realized
that it would be better to specify subspaces for clarity. This PR is
meant to change the name of QutritAmplitudeDamping's variables.

**Description of the Change:**
Change the name of the QutritAmplitudeDamping channel's variables from
gamma_1, gamma_2, and gamma_3 to gamma_01, gamma_02, and gamma_12,
respectively.

**Benefits:**
Makes the code more clear for which variables affect which subspace,
reducing the need to check the docs.

**Possible Drawbacks:**
Changes the name of variables of development code.

**Related GitHub Issues:**
N/A

---------

Co-authored-by: Gabriel Bottrill <bottrill@student.ubc.ca>
Co-authored-by: Olivia Di Matteo <2068515+glassnotes@users.noreply.github.com>
  • Loading branch information
3 people authored Jun 6, 2024
1 parent 7384db3 commit 087c1c8
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 70 deletions.
1 change: 1 addition & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
* `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)
[(#5757)](https://github.com/PennyLaneAI/pennylane/pull/5757)
[(#5799)](https://github.com/PennyLaneAI/pennylane/pull/5799)

<h3>Breaking changes 💔</h3>

Expand Down
56 changes: 28 additions & 28 deletions pennylane/ops/qutrit/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_{10}} & 0 \\
0 & 0 & \sqrt{1-(\gamma_{20}+\gamma_{21})}
\end{bmatrix}
.. math::
K_1 = \begin{bmatrix}
0 & \sqrt{\gamma_1} & 0 \\
0 & \sqrt{\gamma_{10}} & 0 \\
0 & 0 & 0 \\
0 & 0 & 0
\end{bmatrix}, \quad
K_2 = \begin{bmatrix}
0 & 0 & \sqrt{\gamma_2} \\
0 & 0 & \sqrt{\gamma_{20}} \\
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_{21}} \\
0 & 0 & 0
\end{bmatrix}
where :math:`\gamma_1, \gamma_2, \gamma_3 \in [0, 1]` are the amplitude damping
where :math:`\gamma_{10}, \gamma_{20}, \gamma_{21} \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_{21}=0` then Kraus operators :math:`\{K_0, K_1, K_2\}` are adapted from
[`1 <https://doi.org/10.48550/arXiv.1905.10481>`_] (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 <https://arxiv.org/abs/2003.03307>`_] (Sec II.A).
To maintain normalization :math:`\gamma_2 + \gamma_3 \leq 1`.
To maintain normalization :math:`\gamma_{20} + \gamma_{21} \leq 1`.
**Details:**
Expand All @@ -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_10 (float): :math:`|1 \rangle \rightarrow |0 \rangle` amplitude damping probability.
gamma_20 (float): :math:`|2 \rangle \rightarrow |0 \rangle` amplitude damping probability.
gamma_21 (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)
"""
Expand All @@ -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_10, gamma_20, gamma_21, wires, id=None):
# Verify input
for gamma in (gamma_1, gamma_2, gamma_3):
for gamma in (gamma_10, gamma_20, gamma_21):
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_20) or math.is_abstract(gamma_21)):
if not 0.0 <= gamma_20 + gamma_21 <= 1.0:
raise ValueError(r"\gamma_{20}+\gamma_{21} must be in the interval [0,1]")
super().__init__(gamma_10, gamma_20, gamma_21, wires=wires, id=id)

@staticmethod
def compute_kraus_matrices(gamma_1, gamma_2, gamma_3): # pylint:disable=arguments-differ
def compute_kraus_matrices(gamma_10, gamma_20, gamma_21): # 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_10 (float): :math:`|1\rangle \rightarrow |0\rangle` amplitude damping probability.
gamma_20 (float): :math:`|2\rangle \rightarrow |0\rangle` amplitude damping probability.
gamma_21 (float): :math:`|2\rangle \rightarrow |1\rangle` amplitude damping probability.
Returns:
list(array): list of Kraus matrices
Expand All @@ -328,15 +328,15 @@ 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_10 + math.eps), math.sqrt(1 - gamma_20 - gamma_21 + 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_10 + math.eps) * math.convert_like(
math.cast_like(math.array([[0, 1, 0], [0, 0, 0], [0, 0, 0]]), gamma_10), gamma_10
)
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_20 + math.eps) * math.convert_like(
math.cast_like(math.array([[0, 0, 1], [0, 0, 0], [0, 0, 0]]), gamma_20), gamma_20
)
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_21 + math.eps) * math.convert_like(
math.cast_like(math.array([[0, 0, 0], [0, 0, 1], [0, 0, 0]]), gamma_21), gamma_21
)
return [K0, K1, K2, K3]
84 changes: 42 additions & 42 deletions tests/ops/qutrit/test_qutrit_channel_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,89 +175,89 @@ 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_10=gamma_20=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:]:
assert np.allclose(kraus_mat, np.zeros((3, 3)), atol=tol, rtol=0)

@pytest.mark.parametrize("gamma1,gamma2,gamma3", ((0.1, 0.2, 0.3), (0.75, 0.75, 0.25)))
def test_gamma_arbitrary(self, gamma1, gamma2, gamma3, tol):
@pytest.mark.parametrize("gamma_10,gamma_20,gamma_21", ((0.1, 0.2, 0.3), (0.75, 0.75, 0.25)))
def test_gamma_arbitrary(self, gamma_10, gamma_20, gamma_21, tol):
"""Test the correct Kraus matrices are returned."""
K_0 = np.diag((1, np.sqrt(1 - gamma1), np.sqrt(1 - gamma2 - gamma3)))
K_0 = np.diag((1, np.sqrt(1 - gamma_10), np.sqrt(1 - gamma_20 - gamma_21)))

K_1 = np.zeros((3, 3))
K_1[0, 1] = np.sqrt(gamma1)
K_1[0, 1] = np.sqrt(gamma_10)

K_2 = np.zeros((3, 3))
K_2[0, 2] = np.sqrt(gamma2)
K_2[0, 2] = np.sqrt(gamma_20)

K_3 = np.zeros((3, 3))
K_3[1, 2] = np.sqrt(gamma3)
K_3[1, 2] = np.sqrt(gamma_21)

expected = [K_0, K_1, K_2, K_3]
damping_channel = qml.QutritAmplitudeDamping(gamma1, gamma2, gamma3, wires=0)
damping_channel = qml.QutritAmplitudeDamping(gamma_10, gamma_20, gamma_21, wires=0)
assert np.allclose(damping_channel.kraus_matrices(), expected, atol=tol, rtol=0)

@pytest.mark.parametrize(
"gamma1,gamma2,gamma3",
"gamma_10,gamma_20,gamma_21",
(
(1.5, 0.0, 0.0),
(0.0, 1.0 + math.eps, 0.0),
(0.0, 0.0, 1.1),
(0.0, 0.33, 0.67 + math.eps),
),
)
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]"""
def test_gamma_invalid_parameter(self, gamma_10, gamma_20, gamma_21):
"""Ensures that error is thrown when gamma_10, gamma_20, gamma_21, or (gamma_20 + gamma_21) are outside [0,1]"""
with pytest.raises(ValueError, match="must be in the interval"):
channel.QutritAmplitudeDamping(gamma1, gamma2, gamma3, wires=0).kraus_matrices()
channel.QutritAmplitudeDamping(gamma_10, gamma_20, gamma_21, wires=0).kraus_matrices()

@staticmethod
def expected_jac_fn(gamma_1, gamma_2, gamma_3):
def expected_jac_fn(gamma_10, gamma_20, gamma_21):
"""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_10))
partial_1[1][0, 1] = 1 / (2 * math.sqrt(gamma_10))

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_20 - gamma_21))
partial_2[2][0, 2] = 1 / (2 * math.sqrt(gamma_20))

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_20 - gamma_21))
partial_3[3][1, 2] = 1 / (2 * math.sqrt(gamma_21))

return [partial_1, partial_2, partial_3]

@staticmethod
def kraus_fn(gamma_1, gamma_2, gamma_3):
def kraus_fn(gamma_10, gamma_20, gamma_21):
"""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_10, gamma_20, gamma_21, 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_10 = pnp.array(0.43, requires_grad=True)
gamma_20 = pnp.array(0.12, requires_grad=True)
gamma_21 = 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_10, gamma_20, gamma_21)
assert math.allclose(jac, self.expected_jac_fn(gamma_10, gamma_20, gamma_21))

@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_10 = torch.tensor(0.43, requires_grad=True)
gamma_20 = torch.tensor(0.12, requires_grad=True)
gamma_21 = 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_10, gamma_20, gamma_21))
expected = self.expected_jac_fn(
gamma_1.detach().numpy(), gamma_2.detach().numpy(), gamma_3.detach().numpy()
gamma_10.detach().numpy(), gamma_20.detach().numpy(), gamma_21.detach().numpy()
)

for res_partial, exp_partial in zip(jac, expected):
Expand All @@ -268,23 +268,23 @@ 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_10 = tf.Variable(0.43)
gamma_20 = tf.Variable(0.12)
gamma_21 = 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_10, gamma_20, gamma_21)
jac = tape.jacobian(out, (gamma_10, gamma_20, gamma_21))
assert math.allclose(jac, self.expected_jac_fn(gamma_10, gamma_20, gamma_21))

@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_10 = jax.numpy.array(0.43)
gamma_20 = jax.numpy.array(0.12)
gamma_21 = 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_10, gamma_20, gamma_21)
assert math.allclose(jac, self.expected_jac_fn(gamma_10, gamma_20, gamma_21))

0 comments on commit 087c1c8

Please sign in to comment.