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

Qutrit channel amplitude damping #5503

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
f2b98a6
Added Depolarizing channel and structure of tests
Gabriel-Bottrill Apr 5, 2024
d6b4821
Merge branch 'PennyLaneAI:master' into qutrit_channel_depolarizing
Gabriel-Bottrill Apr 9, 2024
0538fe7
Added qutrit amplitude damping channel and tests
Gabriel-Bottrill Apr 9, 2024
f56b406
Added channels to ops.qutrit
Gabriel-Bottrill Apr 11, 2024
ed7b30b
Updated tests to use QutritDepolarizingChannel
Gabriel-Bottrill Apr 11, 2024
203bbf4
Merge branch 'PennyLaneAI:master' into qutrit_channel_depolarizing
Gabriel-Bottrill Apr 11, 2024
034a03b
Fixed location of channel
Gabriel-Bottrill Apr 11, 2024
8bb59fe
Merge branch 'qutrit_channel_depolarizing' into qutrit_channel_amplit…
Gabriel-Bottrill Apr 11, 2024
b759317
Added small change to tests
Gabriel-Bottrill Apr 11, 2024
250e1c2
Merge branch 'master' into qutrit_channel_depolarizing
Gabriel-Bottrill Apr 17, 2024
ca42c47
Fixed ops __init__.py and dependencies
Gabriel-Bottrill Apr 17, 2024
35ee6e7
Merge branch 'master' into qutrit_channel_depolarizing
Gabriel-Bottrill Apr 20, 2024
0aec151
Fixed compute_kraus_matrices
Gabriel-Bottrill Apr 20, 2024
e22dc2d
Merge branch 'qutrit_channel_depolarizing' of github.com:QSAR-UBC/pen…
Gabriel-Bottrill Apr 20, 2024
404e544
Fixed merge conflicts in inits
Gabriel-Bottrill May 7, 2024
81d560f
Fixed stopping condition test names, added QutritDepolarizingChannel …
Gabriel-Bottrill May 7, 2024
4e7a42b
Fixed integration test, changed DefaultQutritMixed to get channel lis…
Gabriel-Bottrill May 7, 2024
85fc4b5
Added options for docstring
Gabriel-Bottrill May 7, 2024
998d21d
Reformatted
Gabriel-Bottrill May 7, 2024
e098c4e
Merge branch 'master' into qutrit_channel_depolarizing
glassnotes May 7, 2024
2059437
Fixed docstring readded formula for parameter-shift
Gabriel-Bottrill May 8, 2024
f5e7102
Fixed probabilities to make p describe probability of error occuring,…
Gabriel-Bottrill May 8, 2024
c48291c
Re-ordered Kraus matrices
Gabriel-Bottrill May 8, 2024
27a69f0
Finishing touches
Gabriel-Bottrill May 8, 2024
fc33fea
Added changelog comments
Gabriel-Bottrill May 8, 2024
62bf03a
Fix imports for pylint
Gabriel-Bottrill May 8, 2024
f7d15a1
Merge branch 'master' into qutrit_channel_depolarizing
Gabriel-Bottrill May 8, 2024
3bb26c7
Merge branch 'master' into qutrit_channel_depolarizing
Gabriel-Bottrill May 9, 2024
fe2131b
Merge branch 'master' into qutrit_channel_depolarizing
glassnotes May 9, 2024
a057c76
Apply suggestions from code review
Gabriel-Bottrill May 13, 2024
0cd047c
Try fixing imports ordering again
Gabriel-Bottrill May 8, 2024
7058694
Added most of requested changes
Gabriel-Bottrill May 13, 2024
35d0d86
Finished most of suggested comments
Gabriel-Bottrill May 13, 2024
c3c5857
Reformatted
Gabriel-Bottrill May 13, 2024
c294a3f
Merge branch 'master' into qutrit_channel_depolarizing
Gabriel-Bottrill May 13, 2024
c30ea09
Attempted fixing imports
Gabriel-Bottrill May 13, 2024
e137c43
Fixed spacing for QutritDepolarizingChannel docs
Gabriel-Bottrill May 13, 2024
909bf99
Added Qutrit Noisy Channels to Introduction
Gabriel-Bottrill May 13, 2024
f2b56db
Removed power of 4 on omega since it is equivalent to power of one
Gabriel-Bottrill May 13, 2024
e3922b8
Ran isort on failing files
Gabriel-Bottrill May 13, 2024
c895b70
used isort on tests imports
Gabriel-Bottrill May 13, 2024
a37c0e6
Added suggestions for docs
Gabriel-Bottrill May 14, 2024
1b7f58b
Merge branch 'master' into qutrit_channel_depolarizing
Gabriel-Bottrill May 14, 2024
8a0d01d
Retrying CI
Gabriel-Bottrill May 14, 2024
ca4ba24
Added qutrit amplitude damping channel and tests
Gabriel-Bottrill Apr 9, 2024
4c3a80a
Added small change to tests
Gabriel-Bottrill Apr 11, 2024
68f8d73
Fixed test to work for qutrit version, fixed docs
Gabriel-Bottrill May 15, 2024
570083f
Fixed merge conflicts
Gabriel-Bottrill May 15, 2024
6a0abf6
Merge branch 'master' into qutrit_channel_amplitude_damping
Gabriel-Bottrill May 15, 2024
3645f4a
Fixed depolarizing test to ensure it is using the correct method of d…
Gabriel-Bottrill May 15, 2024
732c0c8
Added PR to changelog and QutritAmplitudeDamping to introduction
Gabriel-Bottrill May 15, 2024
99944d5
Reformatted and fixed doc spacing
Gabriel-Bottrill May 15, 2024
c20f0cb
Reformatted tests
Gabriel-Bottrill May 15, 2024
a24b2aa
ran isort on tests
Gabriel-Bottrill May 15, 2024
d9c1087
Removed requirement for sum of gammas to be less than 1.
Gabriel-Bottrill May 15, 2024
f2b786c
Fixed changelog to flow better, changed docstring to be more specific…
Gabriel-Bottrill May 15, 2024
5289ca8
Fixed arbitary gamma test
Gabriel-Bottrill May 15, 2024
67d7ce9
Merge branch 'master' into qutrit_channel_amplitude_damping
glassnotes May 16, 2024
32b1c26
Apply suggestions from code review
Gabriel-Bottrill May 21, 2024
f09e9e8
Removed comma at end of Kraus matrices.
Gabriel-Bottrill May 21, 2024
922c2a7
Merge branch 'master' into qutrit_channel_amplitude_damping
Gabriel-Bottrill May 22, 2024
49e1cce
Merge branch 'master' into qutrit_channel_amplitude_damping
Gabriel-Bottrill May 22, 2024
9dccfa7
Moved gamma_1 and gamma_2 verification to __init__
Gabriel-Bottrill May 22, 2024
04c0cb3
Removed trailing whitespace
Gabriel-Bottrill May 22, 2024
c4955ac
Merge branch 'master' into qutrit_channel_amplitude_damping
Gabriel-Bottrill May 23, 2024
033eb34
Merge branch 'master' into qutrit_channel_amplitude_damping
Gabriel-Bottrill May 23, 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 @@ -507,6 +507,7 @@ Qutrit noisy channels
:nosignatures:

~pennylane.QutritDepolarizingChannel
~pennylane.QutritAmplitudeDamping

:html:`</div>`

Expand Down
3 changes: 3 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@
returning a list of `QuantumTape`s and a post-processing function instead of simply the transformed circuit.
[(#5693)](https://github.com/PennyLaneAI/pennylane/pull/5693)

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

<h3>Deprecations 👋</h3>

* The `simplify` argument in `qml.Hamiltonian` and `qml.ops.LinearCombination` is deprecated.
Expand Down
3 changes: 2 additions & 1 deletion pennylane/ops/qutrit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__ = {
Expand All @@ -50,6 +50,7 @@
}
__channels__ = {
"QutritDepolarizingChannel",
"QutritAmplitudeDamping",
}

__all__ = list(__ops__ | __obs__ | __channels__)
91 changes: 91 additions & 0 deletions pennylane/ops/qutrit/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,94 @@ def compute_kraus_matrices(p): # pylint:disable=arguments-differ
)

return [identity] + Ks


class QutritAmplitudeDamping(Channel):
r"""
Single-qutrit amplitude damping error channel.
glassnotes marked this conversation as resolved.
Show resolved Hide resolved
Gabriel-Bottrill marked this conversation as resolved.
Show resolved Hide resolved

Interaction with the environment can lead to changes in the state populations of a qutrit.
This can be modelled by the qutrit 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}, \quad
K_1 = \begin{bmatrix}
0 & \sqrt{\gamma_1} & 0 \\
0 & 0 & 0 \\
0 & 0 & 0
\end{bmatrix}, \quad
K_2 = \begin{bmatrix}
0 & 0 & \sqrt{\gamma_2} \\
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
probabilities for subspaces (0,1) and (0,2) respectively.

.. note::

The Kraus operators :math:`\{K_0, K_1, K_2\}` are adapted from [`1 <https://doi.org/10.48550/arXiv.1905.10481>`_] (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"
Gabriel-Bottrill marked this conversation as resolved.
Show resolved Hide resolved

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
def compute_kraus_matrices(gamma_1, gamma_2): # 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.

Returns:
list(array): list of Kraus matrices

**Example**

>>> qml.QutritAmplitudeDamping.compute_kraus_matrices(0.5, 0.25)
[
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. ]])
]
"""
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]
119 changes: 105 additions & 14 deletions tests/ops/qutrit/test_qutrit_channel_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from numpy.linalg import matrix_power

import pennylane as qml
from pennylane import math
from pennylane import numpy as pnp
from pennylane.ops.qutrit import channel

Expand Down Expand Up @@ -74,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")
Gabriel-Bottrill marked this conversation as resolved.
Show resolved Hide resolved
def circuit(p):
qml.TRX(angle, wires=0, subspace=(0, 1))
qml.TRX(angle, wires=0, subspace=(1, 2))
Expand Down Expand Up @@ -113,28 +114,24 @@ 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())
Gabriel-Bottrill marked this conversation as resolved.
Show resolved Hide resolved
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
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):
Expand All @@ -144,7 +141,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):
Expand All @@ -157,10 +154,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):
Expand All @@ -171,4 +168,98 @@ 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:
Gabriel-Bottrill marked this conversation as resolved.
Show resolved Hide resolved
"""Tests for the qutrit quantum channel QutritAmplitudeDamping"""

def test_gamma_zero(self, tol):
"""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)
assert np.allclose(kraus_mats[2], np.zeros((3, 3)), atol=tol, rtol=0)

@pytest.mark.parametrize("gamma1,gamma2", ((0.1, 0.2), (0.75, 0.75)))
def test_gamma_arbitrary(self, gamma1, gamma2, tol):
"""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))
K_1[0, 1] = np.sqrt(gamma1)

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

expected = [K_0, K_1, K_2]
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)))
def test_gamma_invalid_parameter(self, gamma1, gamma2):
"""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
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))
Gabriel-Bottrill marked this conversation as resolved.
Show resolved Hide resolved
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(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):
"""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_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_1 = tf.Variable(0.43)
gamma_2 = tf.Variable(0.12)
with tf.GradientTape() as tape:
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_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))
Loading