Source code for mimiqcircuits.operations.noisechannel.standards.depolarizing

#
# Copyright © 2022-2023 University of Strasbourg. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#


import mimiqcircuits as mc
import numpy as np
from itertools import product
import symengine as se
from mimiqcircuits.operations.krauschannel import krauschannel
import sympy as sp


[docs] class Depolarizing(krauschannel): r"""N-qubit depolarizing noise channel. The Kraus operators for the depolarizing channel are given by: .. math:: E_1 = \sqrt{1-p} I_N, \quad E_i = \sqrt{p/(4^N-1)} P_i, where :math:`p \in [0,1]` is a probability, and :math:`P_i` is an :math:`N`-qubit Pauli string operator, i.e., a tensor product of one-qubit Pauli operators. There is exactly one Kraus operator :math:`E_{i>1}` for each distinct combination of Pauli operators :math:`P_i`, except for the :math:`N`-qubit identity :math:`I_N = I \otimes I \otimes I \otimes \dots`. For example: - For one qubit, we have 3 operators :math:`P_i \in \{X, Y, Z\}`. - For two qubits, we have 15 operators :math:`P_i \in \{ I\otimes X, I\otimes Y, I\otimes Z, X\otimes I, Y\otimes I, Z\otimes I, X\otimes X, X\otimes Y, X\otimes Z, Y\otimes X, Y\otimes Y, Y\otimes Z, Z\otimes X, Z\otimes Y, Z\otimes Z \}`. This channel is a mixed unitary channel (see :func:`ismixedunitary`), and is a special case of :class:`PauliNoise`. See Also: :class:`PauliString` :class:`PauliNoise` Parameters: N (int): Number of qubits. p (float): Probability of error, i.e., the probability of not applying the identity. Examples: Depolarizing channels can be defined for any number of qubits: >>> from mimiqcircuits import * >>> c = Circuit() >>> c.push(Depolarizing(1, 0.1), 1) 2-qubit circuit with 1 instructions: └── Depolarizing(0.1) @ q[1] <BLANKLINE> >>> c.push(Depolarizing(5, 0.1), 1, 2, 3, 4, 5) 6-qubit circuit with 2 instructions: ├── Depolarizing(0.1) @ q[1] └── Depolarizing(0.1) @ q[1,2,3,4,5] <BLANKLINE> For one and two qubits, you can use the shorthand notation: >>> c.push(Depolarizing(1, 0.1), 1) 6-qubit circuit with 3 instructions: ├── Depolarizing(0.1) @ q[1] ├── Depolarizing(0.1) @ q[1,2,3,4,5] └── Depolarizing(0.1) @ q[1] <BLANKLINE> >>> c.push(Depolarizing(2, 0.1), 1, 2) 6-qubit circuit with 4 instructions: ├── Depolarizing(0.1) @ q[1] ├── Depolarizing(0.1) @ q[1,2,3,4,5] ├── Depolarizing(0.1) @ q[1] └── Depolarizing(0.1) @ q[1,2] <BLANKLINE> """ _name = "Depolarizing" _parnames = () _num_qubits = None _parnames = () def __init__(self, N, p): if N < 1: raise ValueError("Cannot define a 0-qubit depolarizing noise channel") if not isinstance (p, (se.Basic, sp.Basic)) and (p < 0 or p > 1): raise ValueError("Probability p needs to be between 0 and 1.") self.N = N self.p = p self._num_qubits = self.N self._parnames = ("N", "p") @property def parnames(self): return self._parnames @property def num_qubits(self): return self._num_qubits
[docs] def evaluate(self, d={}): if hasattr(self.p, 'subs'): substituted_p = self.p.subs(d) evaluated_p = float(substituted_p.evalf()) if substituted_p.is_number else substituted_p else: evaluated_p = self.p if isinstance(evaluated_p, (float, int)) and not (0 <= evaluated_p <= 1): raise ValueError("Probability after evaluation must be between 0 and 1") return Depolarizing(self.N, evaluated_p)
[docs] def probabilities(self): N = self.N return np.concatenate(([1 - self.p], np.repeat(self.p / (4**N - 1), 4**N - 1)))
[docs] def unitarygates(self): N = self.N paulis = ["I", "X", "Y", "Z"] combinations = ["".join(p) for p in product(paulis, repeat=N)] return [mc.PauliString(comb) for comb in combinations]
[docs] def unitarymatrices(self): return [gate.matrix() for gate in self.unitarygates()]
[docs] def krausmatrices(self): probabilities = self.probabilities() unitary_matrices = self.unitarymatrices() sqrt_probs = [se.sqrt(p) for p in probabilities] return [ sqrt_p * u_matrix for sqrt_p, u_matrix in zip(sqrt_probs, unitary_matrices) ]
[docs] def krausoperators(self): return [mc.Operator(Ek) for Ek in self.krausmatrices()]
[docs] def iswrapper(self): pass
[docs] @classmethod def ismixedunitary(self): return True
def __str__(self): return f"{self._name}({self.p})"
[docs] class Depolarizing1(krauschannel): def __new__(self, p): return Depolarizing(1, p)
[docs] class Depolarizing2(krauschannel): def __new__(self, p): return Depolarizing(2, p)
__all__ = ["Depolarizing", "Depolarizing1", "Depolarizing2"]