Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added TritFlip #5784

Merged
merged 24 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
74a4e39
Added TritFlip
Gabriel-Bottrill Jun 1, 2024
bae24ea
Added changelog entry
Gabriel-Bottrill Jun 1, 2024
0e4b268
Fixed tests and added output of Kraus matrices
Gabriel-Bottrill Jun 1, 2024
a863acf
Reformatted init
Gabriel-Bottrill Jun 1, 2024
a638d05
Fixed the expected_jac_fn for computing Jacobians
Gabriel-Bottrill Jun 1, 2024
082b455
Fixed documentation and added example link to qutrit misclassification
Gabriel-Bottrill Jun 1, 2024
4b8bb7f
Fixed tests, interface tests for multiple parameters and arbitrary in…
Gabriel-Bottrill Jun 1, 2024
1933b76
Fixed JAX test
Gabriel-Bottrill Jun 1, 2024
d4ed816
Merge branch 'master' into qutrit_channel_tritflip
glassnotes Jun 3, 2024
899e425
Merge branch 'master' into qutrit_channel_tritflip
Gabriel-Bottrill Jun 3, 2024
493993a
Changed amplitude damping gamma_x to gamma_xy for clarity
Gabriel-Bottrill Jun 3, 2024
27a198e
Added suggested doc changes.
Gabriel-Bottrill Jun 3, 2024
54cc695
Apply suggestions from code review
Gabriel-Bottrill Jun 4, 2024
e5ec16a
Fixed pylint error
Gabriel-Bottrill Jun 4, 2024
e160944
Removed changes to gamma
Gabriel-Bottrill Jun 4, 2024
7b197ba
Merge branch 'master' into qutrit_channel_tritflip
Gabriel-Bottrill Jun 4, 2024
2ee59c0
Merge branch 'master' into qutrit_channel_tritflip
glassnotes Jun 5, 2024
c6dd33d
Merge branch 'master' into qutrit_channel_tritflip
Gabriel-Bottrill Jun 5, 2024
3c3e5f6
Merge branch 'master' into qutrit_channel_tritflip
Gabriel-Bottrill Jun 5, 2024
77c83ec
Merge branch 'master' into qutrit_channel_tritflip
Gabriel-Bottrill Jun 6, 2024
0e390f6
Merge branch 'master' into qutrit_channel_tritflip
glassnotes Jun 6, 2024
6b3ee25
Merge branch 'master' into qutrit_channel_tritflip
Gabriel-Bottrill Jun 6, 2024
f73f345
Merge branch 'master' into qutrit_channel_tritflip
Gabriel-Bottrill Jun 6, 2024
3c3894f
Merge branch 'master' into qutrit_channel_tritflip
Gabriel-Bottrill Jun 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/introduction/operations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,7 @@ Qutrit noisy channels

~pennylane.QutritDepolarizingChannel
~pennylane.QutritAmplitudeDamping
~pennylane.TritFlip

:html:`</div>`

Expand Down
8 changes: 6 additions & 2 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,14 +213,18 @@

* 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.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)

* `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)

<h3>Breaking changes 💔</h3>

Expand Down
5 changes: 1 addition & 4 deletions pennylane/ops/qutrit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@
"THermitian",
"GellMann",
}
__channels__ = {
"QutritDepolarizingChannel",
"QutritAmplitudeDamping",
}
__channels__ = {"QutritDepolarizingChannel", "QutritAmplitudeDamping", "TritFlip"}

__all__ = list(__ops__ | __obs__ | __channels__)
125 changes: 121 additions & 4 deletions pennylane/ops/qutrit/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
"""

Expand All @@ -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
Expand Down Expand Up @@ -278,8 +278,8 @@ class QutritAmplitudeDamping(Channel):
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)
wires (Sequence[int] or int): the wire the channel acts on.
id (str or None): String representing the operation (optional).
"""

num_params = 3
Expand Down Expand Up @@ -340,3 +340,120 @@ def compute_kraus_matrices(gamma_10, gamma_20, gamma_21): # pylint:disable=argu
math.cast_like(math.array([[0, 0, 0], [0, 0, 1], [0, 0, 0]]), gamma_21), gamma_21
)
return [K0, K1, K2, K3]


class TritFlip(Channel):
Gabriel-Bottrill marked this conversation as resolved.
Show resolved Hide resolved
r"""
Single-qutrit trit flip error channel, used for applying "bit flips" on each qutrit subspace.

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 "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
`BitFlip <https://docs.pennylane.ai/en/stable/code/api/pennylane.BitFlip.html>`_ channel's Kraus operators.

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 <https://arxiv.org/abs/2309.11303>`_] (Fig 1a).

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` 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)
"""

num_params = 3
num_wires = 1
grad_method = "F"
Gabriel-Bottrill marked this conversation as resolved.
Show resolved Hide resolved

def __init__(self, p_01, p_02, p_12, wires, id=None):
Gabriel-Bottrill marked this conversation as resolved.
Show resolved Hide resolved
# Verify input
Gabriel-Bottrill marked this conversation as resolved.
Show resolved Hide resolved
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
r"""Kraus matrices representing the TritFlip channel.

Args:
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

**Example**

>>> 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
)
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]
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ def test_measurement_is_swapped_out(self, mp_fn, mp_cls, shots):
(qml.TRX(1.1, 0), True),
(qml.QutritDepolarizingChannel(0.4, 0), True),
(qml.QutritAmplitudeDamping(0.1, 0.2, 0.12, 0), True),
Gabriel-Bottrill marked this conversation as resolved.
Show resolved Hide resolved
(qml.TritFlip(0.4, 0.1, 0.02, 0), True),
],
)
def test_accepted_operator(self, op, expected):
Expand Down
109 changes: 106 additions & 3 deletions tests/ops/qutrit/test_qutrit_channel_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Gabriel-Bottrill marked this conversation as resolved.
Show resolved Hide resolved
"""Tests Jacobian of Kraus matrices using PyTorch."""
import torch

p = torch.tensor(0.43, requires_grad=True)
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -288,3 +288,106 @@ def test_kraus_jac_jax(self):

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))


class TestTritFlip:
"""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)]
)
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, 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(
"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(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]])
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_01, p_02, p_12))

@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))
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):
"""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.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))
Loading