# How to implement a depolarizing noise channel for qutrits using cirq?

Anyone have any idea how I can implement a depolarizing noise channel for qutrits using cirq? Say using the kraus operators within a class inheriting from cirq.Gate or so?

The depolarizing channel can be represented as a probabilistic mixture of unitaries. You can represent mixtures in Cirq by implementing the _mixture_ and _has_mixture_ magic methods on a class.

I'm following https://arxiv.org/pdf/1101.3986 for the Kraus operators (which are unitary):

I reimplemented the DepolarizingChannel but only for a single qutrit as an example:


SHIFT = np.array([[0,1,0],
[0,0,1],
[1,0,0]])
ω = np.exp(1j * 2/3 * np.pi)
CLOCK = np.array([[1,0,0],
[0,ω,0],
[0,0,ω**2]])

class QutritDepolarizingChannel(cirq.Gate):
r"""A channel that depolarizes one qutrit.
"""

def __init__(self, p: float) -> None:
"""Constructs a depolarization channel on a qutrit.

Args:
p: The probability that one of the shift/clock matrices is applied. Each of
the 8 shift/clock gates is applied independently with probability
$$p / 8$$.
n_qubits: the number of qubits.

Raises:
ValueError: if p is not a valid probability.
"""

error_probabilities = {}

p_depol = p / 8
p_identity = 1.0 - p
for gate_pows in itertools.product(range(3), range(3)):
if gate_pows == (0,0):
error_probabilities[gate_pows] = p_identity
else:
error_probabilities[gate_pows] = p_depol
self.error_probabilities = error_probabilities
self._p = p

def _qid_shape_(self):
return (3,)

def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]:
op = lambda shift_pow, clock_pow: np.linalg.matrix_power(SHIFT, shift_pow) @ np.linalg.matrix_power(CLOCK, clock_pow)
return [(self.error_probabilities[(shift_pow, clock_pow)], op(shift_pow, clock_pow))
for (shift_pow, clock_pow) in self.error_probabilities.keys()]

def _has_mixture_(self) -> bool:
return True

def _value_equality_values_(self):
return self._p

def _circuit_diagram_info_(self, args):
if args.precision is not None:
return (f"D3({self._p:.{args.precision}g})",)
else:
return (f"D3({self._p})",)
return result

@property
def p(self) -> float:
"""The probability that one of the qutrit gates is applied.

Each of the 8 Pauli gates is applied independently with probability
$$p / 8$$.
"""
return self._p

@property
def n_qubits(self) -> int:
"""The number of qubits"""
return 1


Then you can use it as any other channel/gate:

t = cirq.NamedQid("my qutrit", 3)
c = cirq.Circuit(QutritDepolarizingChannel(0.12)(t), cirq.measure(t))
print(c)
cirq.DensityMatrixSimulator().run(c, repetitions=100)


Results in:

my qutrit (d=3): ───D3(0.12)───M───
my qutrit (d=3)=0000000000000000100000000100010000000020000000000000000000000000000000000000000000000000000000000100

• Are you not missing a square root when putting your probabilities in your dictionary, as technically the coefficients to the Kraus operators are not just probabilites, right? Or does cirq specifically need probabilities and operators, and then it fills out the rest? Commented May 2 at 22:48
• Also I don't think you can use the cirq.DensityMatrixSimulator() after using this channel. Commented May 2 at 23:50
• You don't need square root when doing a probabilistic mixture of unitaries. Why do you think you can't use the density matrix simulator? Commented May 3 at 20:12
• added a colab link to the bottom, also showed a DensityMatrixSimulator execution of 100 trials, which shows nicely the roughly 12% non-zero measurement results starting from |0> Commented May 3 at 21:37
• Oh I see, I had a typo when implementing my mixture method, I wasn't returning a list of tuples, but it seems to work with the simulators now. I was trying to generalize the noise channel to a d-dimensional qudit, which has now worked. Thank you for the help! But I'm a bit curious as to how the mixture methods work with the circuit and simulators, how is the noise actually being implemented within the cirq framework? I assume the mixture of unitaries and corresponding probabilities represent the kraus operators in some capacity. Commented May 4 at 7:43