1# Copyright 2018 The Cirq Developers
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Quantum channels that are commonly used in the literature."""
16
17import itertools
18from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Union, TYPE_CHECKING
19
20import numpy as np
21
22from cirq import protocols, value
23from cirq.ops import raw_types, common_gates, pauli_gates, gate_features, identity
24
25if TYPE_CHECKING:
26    import cirq
27
28
29@value.value_equality
30class AsymmetricDepolarizingChannel(gate_features.SingleQubitGate):
31    """A channel that depolarizes asymmetrically along different directions."""
32
33    def __init__(
34        self,
35        p_x: Optional[float] = None,
36        p_y: Optional[float] = None,
37        p_z: Optional[float] = None,
38        error_probabilities: Optional[Dict[str, float]] = None,
39        tol: float = 1e-8,
40    ) -> None:
41        r"""The asymmetric depolarizing channel.
42
43        This channel applies one of 4**n disjoint possibilities: nothing (the
44        identity channel) or one of the 4**n - 1 pauli gates.
45
46        This channel evolves a density matrix via
47
48            $$
49            \sum_i p_i Pi \rho Pi
50            $$
51
52        where i varies from 0 to 4**n-1 and Pi represents n-qubit Pauli operator
53        (including identity). The input $\rho$ is the density matrix before the
54        depolarization.
55
56        Args:
57            p_x: The probability that a Pauli X and no other gate occurs.
58            p_y: The probability that a Pauli Y and no other gate occurs.
59            p_z: The probability that a Pauli Z and no other gate occurs.
60            error_probabilities: Dictionary of string (Pauli operator) to its
61                probability. If the identity is missing from the list, it will
62                be added so that the total probability mass is 1.
63            tol: The tolerance used making sure the total probability mass is
64                equal to 1.
65
66        Examples of calls:
67            * Single qubit: AsymmetricDepolarizingChannel(0.2, 0.1, 0.3)
68            * Single qubit: AsymmetricDepolarizingChannel(p_z=0.3)
69            * Two qubits: AsymmetricDepolarizingChannel(
70                                error_probabilities={'XX': 0.2})
71
72        Raises:
73            ValueError: if the args or the sum of args are not probabilities.
74        """
75        if error_probabilities:
76            num_qubits = len(list(error_probabilities)[0])
77            for k in error_probabilities.keys():
78                if not set(k).issubset({'I', 'X', 'Y', 'Z'}):
79                    raise ValueError(f"{k} is not made solely of I, X, Y, Z.")
80                if len(k) != num_qubits:
81                    raise ValueError(f"{k} must have {num_qubits} Pauli gates.")
82            for k, v in error_probabilities.items():
83                value.validate_probability(v, f"p({k})")
84            sum_probs = sum(error_probabilities.values())
85            identity = 'I' * num_qubits
86            if sum_probs < 1.0 - tol and identity not in error_probabilities:
87                error_probabilities[identity] = 1.0 - sum_probs
88            elif abs(sum_probs - 1.0) > tol:
89                raise ValueError(f"Probabilities do not add up to 1 but to {sum_probs}")
90            self._num_qubits = num_qubits
91            self._error_probabilities = error_probabilities
92        else:
93            p_x = 0.0 if p_x is None else p_x
94            p_y = 0.0 if p_y is None else p_y
95            p_z = 0.0 if p_z is None else p_z
96
97            p_x = value.validate_probability(p_x, 'p_x')
98            p_y = value.validate_probability(p_y, 'p_y')
99            p_z = value.validate_probability(p_z, 'p_z')
100            p_i = 1 - value.validate_probability(p_x + p_y + p_z, 'p_x + p_y + p_z')
101
102            self._num_qubits = 1
103            self._error_probabilities = {'I': p_i, 'X': p_x, 'Y': p_y, 'Z': p_z}
104
105    def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]:
106        ps = []
107        for pauli in self._error_probabilities:
108            Pi = np.identity(1)
109            for gate in pauli:
110                if gate == 'I':
111                    Pi = np.kron(Pi, protocols.unitary(identity.I))
112                elif gate == 'X':
113                    Pi = np.kron(Pi, protocols.unitary(pauli_gates.X))
114                elif gate == 'Y':
115                    Pi = np.kron(Pi, protocols.unitary(pauli_gates.Y))
116                elif gate == 'Z':
117                    Pi = np.kron(Pi, protocols.unitary(pauli_gates.Z))
118            ps.append(Pi)
119        return tuple(zip(self._error_probabilities.values(), ps))
120
121    def _num_qubits_(self) -> int:
122        return self._num_qubits
123
124    def _has_mixture_(self) -> bool:
125        return True
126
127    def _value_equality_values_(self):
128        return self._num_qubits, hash(tuple(sorted(self._error_probabilities.items())))
129
130    def __repr__(self) -> str:
131        return 'cirq.asymmetric_depolarize(' + f"error_probabilities={self._error_probabilities})"
132
133    def __str__(self) -> str:
134        return 'asymmetric_depolarize(' + f"error_probabilities={self._error_probabilities})"
135
136    def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str:
137        if self._num_qubits == 1:
138            if args.precision is not None:
139                return (
140                    f"A({self.p_x:.{args.precision}g},"
141                    + f"{self.p_y:.{args.precision}g},"
142                    + f"{self.p_z:.{args.precision}g})"
143                )
144            return f"A({self.p_x},{self.p_y},{self.p_z})"
145        if args.precision is not None:
146            error_probabilities = [
147                f"{pauli}:{p:.{args.precision}g}" for pauli, p in self._error_probabilities.items()
148            ]
149        else:
150            error_probabilities = [f"{pauli}:{p}" for pauli, p in self._error_probabilities.items()]
151        return f"A({', '.join(error_probabilities)})"
152
153    @property
154    def p_i(self) -> float:
155        """The probability that an Identity I and no other gate occurs."""
156        if self._num_qubits != 1:
157            raise ValueError('num_qubits should be 1')
158        return self._error_probabilities.get('I', 0.0)
159
160    @property
161    def p_x(self) -> float:
162        """The probability that a Pauli X and no other gate occurs."""
163        if self._num_qubits != 1:
164            raise ValueError('num_qubits should be 1')
165        return self._error_probabilities.get('X', 0.0)
166
167    @property
168    def p_y(self) -> float:
169        """The probability that a Pauli Y and no other gate occurs."""
170        if self._num_qubits != 1:
171            raise ValueError('num_qubits should be 1')
172        return self._error_probabilities.get('Y', 0.0)
173
174    @property
175    def p_z(self) -> float:
176        """The probability that a Pauli Z and no other gate occurs."""
177        if self._num_qubits != 1:
178            raise ValueError('num_qubits should be 1')
179        return self._error_probabilities.get('Z', 0.0)
180
181    @property
182    def num_qubits(self) -> int:
183        """The number of qubits"""
184        return self._num_qubits
185
186    @property
187    def error_probabilities(self) -> Dict[str, float]:
188        """A dictionary from Pauli gates to probability"""
189        return self._error_probabilities
190
191    def _json_dict_(self) -> Dict[str, Any]:
192        return protocols.obj_to_dict_helper(self, ['error_probabilities'])
193
194    def _approx_eq_(self, other: Any, atol: float) -> bool:
195        return (
196            self.num_qubits == other.num_qubits
197            and np.isclose(self.p_i, other.p_i, atol=atol).item()
198            and np.isclose(self.p_x, other.p_x, atol=atol).item()
199            and np.isclose(self.p_y, other.p_y, atol=atol).item()
200            and np.isclose(self.p_z, other.p_z, atol=atol).item()
201        )
202
203
204def asymmetric_depolarize(
205    p_x: Optional[float] = None,
206    p_y: Optional[float] = None,
207    p_z: Optional[float] = None,
208    error_probabilities: Optional[Dict[str, float]] = None,
209    tol: float = 1e-8,
210) -> AsymmetricDepolarizingChannel:
211    r"""Returns a AsymmetricDepolarizingChannel with given parameter.
212
213        This channel applies one of 4**n disjoint possibilities: nothing (the
214        identity channel) or one of the 4**n - 1 pauli gates.
215
216        This channel evolves a density matrix via
217
218            $$
219            \sum_i p_i Pi \rho Pi
220            $$
221
222        where i varies from 0 to 4**n-1 and Pi represents n-qubit Pauli operator
223        (including identity). The input $\rho$ is the density matrix before the
224        depolarization.
225
226        Args:
227            p_x: The probability that a Pauli X and no other gate occurs.
228            p_y: The probability that a Pauli Y and no other gate occurs.
229            p_z: The probability that a Pauli Z and no other gate occurs.
230            error_probabilities: Dictionary of string (Pauli operator) to its
231                probability. If the identity is missing from the list, it will
232                be added so that the total probability mass is 1.
233            tol: The tolerance used making sure the total probability mass is
234                equal to 1.
235
236        Examples of calls:
237            * Single qubit: AsymmetricDepolarizingChannel(0.2, 0.1, 0.3)
238            * Single qubit: AsymmetricDepolarizingChannel(p_z=0.3)
239            * Two qubits: AsymmetricDepolarizingChannel(
240                                error_probabilities={'XX': 0.2})
241
242    Raises:
243        ValueError: if the args or the sum of the args are not probabilities.
244    """
245    return AsymmetricDepolarizingChannel(p_x, p_y, p_z, error_probabilities, tol)
246
247
248@value.value_equality
249class DepolarizingChannel(raw_types.Gate):
250    """A channel that depolarizes one or several qubits."""
251
252    def __init__(self, p: float, n_qubits: int = 1) -> None:
253        r"""The symmetric depolarizing channel.
254
255        This channel applies one of 4**n disjoint possibilities: nothing (the
256        identity channel) or one of the 4**n - 1 pauli gates. The disjoint
257        probabilities of the non-identity Pauli gates are all the same,
258        p / (4**n - 1), and the identity is done with probability 1 - p. The
259        supplied probability must be a valid probability or else this
260        constructor will raise a ValueError.
261
262
263        This channel evolves a density matrix via
264
265            $$
266            \rho \rightarrow (1 - p) \rho + p / (4**n - 1) \sum _i P_i \rho P_i
267            $$
268
269        where $P_i$ are the $4^n - 1$ Pauli gates (excluding the identity).
270
271        Args:
272            p: The probability that one of the Pauli gates is applied. Each of
273                the Pauli gates is applied independently with probability
274                p / (4**n - 1).
275            n_qubits: the number of qubits.
276
277        Raises:
278            ValueError: if p is not a valid probability.
279        """
280
281        error_probabilities = {}
282
283        p_depol = p / (4 ** n_qubits - 1)
284        p_identity = 1.0 - p
285        for pauli_tuple in itertools.product(['I', 'X', 'Y', 'Z'], repeat=n_qubits):
286            pauli_string = ''.join(pauli_tuple)
287            if pauli_string == 'I' * n_qubits:
288                error_probabilities[pauli_string] = p_identity
289            else:
290                error_probabilities[pauli_string] = p_depol
291
292        self._p = p
293        self._n_qubits = n_qubits
294
295        self._delegate = AsymmetricDepolarizingChannel(error_probabilities=error_probabilities)
296
297    def _qid_shape_(self):
298        return (2,) * self._n_qubits
299
300    def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]:
301        return self._delegate._mixture_()
302
303    def _has_mixture_(self) -> bool:
304        return True
305
306    def _value_equality_values_(self):
307        return self._p
308
309    def __repr__(self) -> str:
310        if self._n_qubits == 1:
311            return f"cirq.depolarize(p={self._p})"
312        return f"cirq.depolarize(p={self._p},n_qubits={self._n_qubits})"
313
314    def __str__(self) -> str:
315        if self._n_qubits == 1:
316            return f"depolarize(p={self._p})"
317        return f"depolarize(p={self._p},n_qubits={self._n_qubits})"
318
319    def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']) -> bool:
320        from cirq.sim import clifford
321
322        if isinstance(args, clifford.ActOnCliffordTableauArgs):
323            if args.prng.random() < self._p:
324                gate = args.prng.choice([pauli_gates.X, pauli_gates.Y, pauli_gates.Z])
325                protocols.act_on(gate, args, qubits)
326            return True
327        return NotImplemented
328
329    def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> Tuple[str, ...]:
330        result: Tuple[str, ...]
331        if args.precision is not None:
332            result = (f"D({self._p:.{args.precision}g})",)
333        else:
334            result = (f"D({self._p})",)
335        while len(result) < self.num_qubits():
336            result += (f"#{len(result) + 1}",)
337        return result
338
339    @property
340    def p(self) -> float:
341        """The probability that one of the Pauli gates is applied.
342
343        Each of the Pauli gates is applied independently with probability
344        p / (4**n_qubits - 1).
345        """
346        return self._p
347
348    @property
349    def n_qubits(self) -> int:
350        """The number of qubits"""
351        return self._n_qubits
352
353    def _json_dict_(self) -> Dict[str, Any]:
354        if self._n_qubits == 1:
355            return protocols.obj_to_dict_helper(self, ['p'])
356        return protocols.obj_to_dict_helper(self, ['p', 'n_qubits'])
357
358    def _approx_eq_(self, other: Any, atol: float) -> bool:
359        return np.isclose(self.p, other.p, atol=atol).item() and self.n_qubits == other.n_qubits
360
361
362def depolarize(p: float, n_qubits: int = 1) -> DepolarizingChannel:
363    r"""Returns a DepolarizingChannel with given probability of error.
364
365    This channel applies one of 4**n disjoint possibilities: nothing (the
366    identity channel) or one of the 4**n - 1 pauli gates. The disjoint
367    probabilities of the non-identity Pauli gates are all the same,
368    p / (4**n - 1), and the identity is done with probability 1 - p. The
369    supplied probability must be a valid probability or else this constructor
370    will raise a ValueError.
371
372    This channel evolves a density matrix via
373
374        $$
375        \rho \rightarrow (1 - p) \rho + p / (4**n - 1) \sum _i P_i \rho P_i
376        $$
377
378    where $P_i$ are the $4^n - 1$ Pauli gates (excluding the identity).
379
380    Args:
381        p: The probability that one of the Pauli gates is applied. Each of
382            the Pauli gates is applied independently with probability
383            p / (4**n - 1).
384        n_qubits: The number of qubits.
385
386    Raises:
387        ValueError: if p is not a valid probability.
388    """
389    return DepolarizingChannel(p, n_qubits)
390
391
392@value.value_equality
393class GeneralizedAmplitudeDampingChannel(gate_features.SingleQubitGate):
394    """Dampen qubit amplitudes through non ideal dissipation.
395
396    This channel models the effect of energy dissipation into the environment
397    as well as the environment depositing energy into the system.
398    """
399
400    def __init__(self, p: float, gamma: float) -> None:
401        r"""The generalized amplitude damping channel.
402
403        Construct a channel to model energy dissipation into the environment
404        as well as the environment depositing energy into the system. The
405        probabilities with which the energy exchange occur are given by `gamma`,
406        and the probability of the environment being not excited is given by
407        `p`.
408
409        The stationary state of this channel is the diagonal density matrix
410        with probability `p` of being |0⟩ and probability `1-p` of being |1⟩.
411
412        This channel evolves a density matrix via
413
414            $$
415            \rho \rightarrow M_0 \rho M_0^\dagger
416                           + M_1 \rho M_1^\dagger
417                           + M_2 \rho M_2^\dagger
418                           + M_3 \rho M_3^\dagger
419            $$
420
421        With
422
423            $$
424            \begin{aligned}
425            M_0 =& \sqrt{p} \begin{bmatrix}
426                                1 & 0  \\
427                                0 & \sqrt{1 - \gamma}
428                            \end{bmatrix}
429            \\
430            M_1 =& \sqrt{p} \begin{bmatrix}
431                                0 & \sqrt{\gamma} \\
432                                0 & 0
433                           \end{bmatrix}
434            \\
435            M_2 =& \sqrt{1-p} \begin{bmatrix}
436                                \sqrt{1-\gamma} & 0 \\
437                                 0 & 1
438                              \end{bmatrix}
439            \\
440            M_3 =& \sqrt{1-p} \begin{bmatrix}
441                                 0 & 0 \\
442                                 \sqrt{\gamma} & 0
443                             \end{bmatrix}
444            \end{aligned}
445            $$
446
447        Args:
448            p: the probability of the environment being not excited
449            gamma: the probability of energy transfer
450
451        Raises:
452            ValueError: if gamma or p is not a valid probability.
453        """
454        self._p = value.validate_probability(p, 'p')
455        self._gamma = value.validate_probability(gamma, 'gamma')
456
457    def _kraus_(self) -> Iterable[np.ndarray]:
458        p0 = np.sqrt(self._p)
459        p1 = np.sqrt(1.0 - self._p)
460        sqrt_g = np.sqrt(self._gamma)
461        sqrt_g1 = np.sqrt(1.0 - self._gamma)
462        return (
463            p0 * np.array([[1.0, 0.0], [0.0, sqrt_g1]]),
464            p0 * np.array([[0.0, sqrt_g], [0.0, 0.0]]),
465            p1 * np.array([[sqrt_g1, 0.0], [0.0, 1.0]]),
466            p1 * np.array([[0.0, 0.0], [sqrt_g, 0.0]]),
467        )
468
469    def _has_kraus_(self) -> bool:
470        return True
471
472    def _value_equality_values_(self):
473        return self._p, self._gamma
474
475    def __repr__(self) -> str:
476        return f'cirq.generalized_amplitude_damp(p={self._p!r},gamma={self._gamma!r})'
477
478    def __str__(self) -> str:
479        return f'generalized_amplitude_damp(p={self._p!r},gamma={self._gamma!r})'
480
481    def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str:
482        if args.precision is not None:
483            f = '{:.' + str(args.precision) + 'g}'
484            return f'GAD({f},{f})'.format(self._p, self._gamma)
485        return f'GAD({self._p!r},{self._gamma!r})'
486
487    @property
488    def p(self) -> float:
489        """The probability of the environment being not excited."""
490        return self._p
491
492    @property
493    def gamma(self) -> float:
494        """The probability of energy transfer."""
495        return self._gamma
496
497    def _json_dict_(self) -> Dict[str, Any]:
498        return protocols.obj_to_dict_helper(self, ['p', 'gamma'])
499
500    def _approx_eq_(self, other: Any, atol: float) -> bool:
501        return (
502            np.isclose(self.gamma, other.gamma, atol=atol).item()
503            and np.isclose(self.p, other.p, atol=atol).item()
504        )
505
506
507# TODO(#3388) Add summary line to docstring.
508# pylint: disable=docstring-first-line-empty
509def generalized_amplitude_damp(p: float, gamma: float) -> GeneralizedAmplitudeDampingChannel:
510    r"""
511    Returns a GeneralizedAmplitudeDampingChannel with the given
512    probabilities gamma and p.
513
514    This channel evolves a density matrix via:
515
516        $$
517        \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
518              + M_2 \rho M_2^\dagger + M_3 \rho M_3^\dagger
519        $$
520
521    With:
522
523        $$
524        \begin{aligned}
525        M_0 =& \sqrt{p} \begin{bmatrix}
526                            1 & 0  \\
527                            0 & \sqrt{1 - \gamma}
528                       \end{bmatrix}
529        \\
530        M_1 =& \sqrt{p} \begin{bmatrix}
531                            0 & \sqrt{\gamma} \\
532                            0 & 0
533                       \end{bmatrix}
534        \\
535        M_2 =& \sqrt{1-p} \begin{bmatrix}
536                            \sqrt{1-\gamma} & 0 \\
537                             0 & 1
538                          \end{bmatrix}
539        \\
540        M_3 =& \sqrt{1-p} \begin{bmatrix}
541                             0 & 0 \\
542                             \sqrt{\gamma} & 0
543                         \end{bmatrix}
544        \end{aligned}
545        $$
546
547    Args:
548        gamma: the probability of the interaction being dissipative.
549        p: the probability of the qubit and environment exchanging energy.
550
551    Raises:
552        ValueError: gamma or p is not a valid probability.
553    """
554    return GeneralizedAmplitudeDampingChannel(p, gamma)
555
556
557# pylint: enable=docstring-first-line-empty
558@value.value_equality
559class AmplitudeDampingChannel(gate_features.SingleQubitGate):
560    """Dampen qubit amplitudes through dissipation.
561
562    This channel models the effect of energy dissipation to the
563    surrounding environment.
564    """
565
566    def __init__(self, gamma: float) -> None:
567        r"""The amplitude damping channel.
568
569        Construct a channel that dissipates energy. The probability of
570        energy exchange occurring is given by gamma.
571
572        This channel evolves a density matrix as follows:
573
574            $$
575            \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
576            $$
577
578        With:
579
580            $$
581            \begin{aligned}
582            M_0 =& \begin{bmatrix}
583                    1 & 0  \\
584                    0 & \sqrt{1 - \gamma}
585                  \end{bmatrix}
586            \\
587            M_1 =& \begin{bmatrix}
588                    0 & \sqrt{\gamma} \\
589                    0 & 0
590                  \end{bmatrix}
591            \end{aligned}
592            $$
593
594        Args:
595            gamma: the probability of the interaction being dissipative.
596
597        Raises:
598            ValueError: is gamma is not a valid probability.
599        """
600        self._gamma = value.validate_probability(gamma, 'gamma')
601        self._delegate = GeneralizedAmplitudeDampingChannel(1.0, self._gamma)
602
603    def _kraus_(self) -> Iterable[np.ndarray]:
604        # just return first two kraus ops, we don't care about
605        # the last two.
606        return list(self._delegate._kraus_())[:2]
607
608    def _has_kraus_(self) -> bool:
609        return True
610
611    def _value_equality_values_(self):
612        return self._gamma
613
614    def __repr__(self) -> str:
615        return f'cirq.amplitude_damp(gamma={self._gamma!r})'
616
617    def __str__(self) -> str:
618        return f'amplitude_damp(gamma={self._gamma!r})'
619
620    def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str:
621        if args.precision is not None:
622            f = '{:.' + str(args.precision) + 'g}'
623            return f'AD({f})'.format(self._gamma)
624        return f'AD({self._gamma!r})'
625
626    @property
627    def gamma(self) -> float:
628        """The probability of the interaction being dissipative."""
629        return self._gamma
630
631    def _json_dict_(self) -> Dict[str, Any]:
632        return protocols.obj_to_dict_helper(self, ['gamma'])
633
634    def _approx_eq_(self, other: Any, atol: float) -> bool:
635        return np.isclose(self.gamma, other.gamma, atol=atol).item()
636
637
638def amplitude_damp(gamma: float) -> AmplitudeDampingChannel:
639    r"""Returns an AmplitudeDampingChannel with the given probability gamma.
640
641    This channel evolves a density matrix via:
642
643        $$
644        \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
645        $$
646
647    With:
648
649        $$
650        \begin{aligned}
651        M_0 =& \begin{bmatrix}
652                1 & 0  \\
653                0 & \sqrt{1 - \gamma}
654              \end{bmatrix}
655        \\
656        M_1 =& \begin{bmatrix}
657                0 & \sqrt{\gamma} \\
658                0 & 0
659              \end{bmatrix}
660        \end{aligned}
661        $$
662
663    Args:
664        gamma: the probability of the interaction being dissipative.
665
666    Raises:
667        ValueError: if gamma is not a valid probability.
668    """
669    return AmplitudeDampingChannel(gamma)
670
671
672@value.value_equality
673class ResetChannel(gate_features.SingleQubitGate):
674    """Reset a qubit back to its |0⟩ state.
675
676    The reset channel is equivalent to performing an unobserved measurement
677    which then controls a bit flip onto the targeted qubit.
678    """
679
680    def __init__(self, dimension: int = 2) -> None:
681        r"""The reset channel.
682
683        Construct a channel that resets the qubit.
684
685        This channel evolves a density matrix as follows:
686
687            $$
688            \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
689            $$
690
691        With:
692
693            $$
694            \begin{aligned}
695            M_0 =& \begin{bmatrix}
696                    1 & 0  \\
697                    0 & 0
698                  \end{bmatrix}
699            \\
700            M_1 =& \begin{bmatrix}
701                    0 & 1 \\
702                    0 & 0
703                  \end{bmatrix}
704            \end{aligned}
705            $$
706
707        Args:
708            dimension: Specify this argument when resetting a qudit.  There will
709                be `dimension` number of dimension by dimension matrices
710                describing the channel, each with a 1 at a different position in
711                the top row.
712        """
713        self._dimension = dimension
714
715    def _has_stabilizer_effect_(self) -> Optional[bool]:
716        return True
717
718    def _qasm_(self, args: 'cirq.QasmArgs', qubits: Tuple['cirq.Qid', ...]) -> Optional[str]:
719        args.validate_version('2.0')
720        return args.format('reset {0};\n', qubits[0])
721
722    def _qid_shape_(self):
723        return (self._dimension,)
724
725    def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']):
726        from cirq import sim, ops
727
728        if isinstance(args, sim.ActOnStabilizerCHFormArgs):
729            axe = args.qubit_map[qubits[0]]
730            if args.state._measure(axe, args.prng):
731                ops.X._act_on_(args, qubits)
732            return True
733
734        if isinstance(args, sim.ActOnStateVectorArgs):
735            # Do a silent measurement.
736            axes = args.get_axes(qubits)
737            measurements, _ = sim.measure_state_vector(
738                args.target_tensor,
739                axes,
740                out=args.target_tensor,
741                qid_shape=args.target_tensor.shape,
742            )
743            result = measurements[0]
744
745            # Use measurement result to zero the qid.
746            if result:
747                zero = args.subspace_index(axes, 0)
748                other = args.subspace_index(axes, result)
749                args.target_tensor[zero] = args.target_tensor[other]
750                args.target_tensor[other] = 0
751
752            return True
753
754        return NotImplemented
755
756    def _kraus_(self) -> Iterable[np.ndarray]:
757        # The first axis is over the list of channel matrices
758        channel = np.zeros((self._dimension,) * 3, dtype=np.complex64)
759        channel[:, 0, :] = np.eye(self._dimension)
760        return channel
761
762    def _has_kraus_(self) -> bool:
763        return True
764
765    def _value_equality_values_(self):
766        return self._dimension
767
768    def __repr__(self) -> str:
769        if self._dimension == 2:
770            return 'cirq.ResetChannel()'
771        else:
772            return f'cirq.ResetChannel(dimension={self._dimension!r})'
773
774    def __str__(self) -> str:
775        return 'reset'
776
777    def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str:
778        return 'R'
779
780    @property
781    def dimension(self) -> int:
782        """The dimension of the qudit being reset."""
783        return self._dimension
784
785    def _json_dict_(self) -> Dict[str, Any]:
786        return protocols.obj_to_dict_helper(self, ['dimension'])
787
788
789def reset(qubit: 'cirq.Qid') -> raw_types.Operation:
790    """Returns a `ResetChannel` on the given qubit."""
791    return ResetChannel(qubit.dimension).on(qubit)
792
793
794def reset_each(*qubits: 'cirq.Qid') -> List[raw_types.Operation]:
795    """Returns a list of `ResetChannel` instances on the given qubits."""
796    return [ResetChannel(q.dimension).on(q) for q in qubits]
797
798
799@value.value_equality
800class PhaseDampingChannel(gate_features.SingleQubitGate):
801    """Dampen qubit phase.
802
803    This channel models phase damping which is the loss of quantum
804    information without the loss of energy.
805    """
806
807    def __init__(self, gamma: float) -> None:
808        r"""The phase damping channel.
809
810        Construct a channel that enacts a phase damping constant gamma.
811
812        This channel evolves a density matrix via:
813
814            $$
815            \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
816            $$
817
818        With:
819
820            $$
821            \begin{aligned}
822            M_0 =& \begin{bmatrix}
823                    1 & 0 \\
824                    0 & \sqrt{1 - \gamma}
825                  \end{bmatrix}
826            \\
827            M_1 =& \begin{bmatrix}
828                    0 & 0 \\
829                    0 & \sqrt{\gamma}
830                  \end{bmatrix}
831            \end{aligned}
832            $$
833
834        Args:
835            gamma: The damping constant.
836
837        Raises:
838            ValueError: if gamma is not a valid probability.
839        """
840        self._gamma = value.validate_probability(gamma, 'gamma')
841
842    def _kraus_(self) -> Iterable[np.ndarray]:
843        return (
844            np.array([[1.0, 0.0], [0.0, np.sqrt(1.0 - self._gamma)]]),
845            np.array([[0.0, 0.0], [0.0, np.sqrt(self._gamma)]]),
846        )
847
848    def _has_kraus_(self) -> bool:
849        return True
850
851    def _value_equality_values_(self):
852        return self._gamma
853
854    def __repr__(self) -> str:
855        return f'cirq.phase_damp(gamma={self._gamma!r})'
856
857    def __str__(self) -> str:
858        return f'phase_damp(gamma={self._gamma!r})'
859
860    def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str:
861        if args.precision is not None:
862            f = '{:.' + str(args.precision) + 'g}'
863            return f'PD({f})'.format(self._gamma)
864        return f'PD({self._gamma!r})'
865
866    @property
867    def gamma(self) -> float:
868        """The damping constant."""
869        return self._gamma
870
871    def _json_dict_(self) -> Dict[str, Any]:
872        return protocols.obj_to_dict_helper(self, ['gamma'])
873
874    def _approx_eq_(self, other: Any, atol: float) -> bool:
875        return np.isclose(self._gamma, other._gamma, atol=atol).item()
876
877
878def phase_damp(gamma: float) -> PhaseDampingChannel:
879    r"""Creates a PhaseDampingChannel with damping constant gamma.
880
881    This channel evolves a density matrix via:
882
883        $$
884        \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
885        $$
886
887    With:
888
889        $$
890        \begin{aligned}
891        M_0 =& \begin{bmatrix}
892                1 & 0  \\
893                0 & \sqrt{1 - \gamma}
894              \end{bmatrix}
895        \\
896        M_1 =& \begin{bmatrix}
897                0 & 0 \\
898                0 & \sqrt{\gamma}
899              \end{bmatrix}
900        \end{aligned}
901        $$
902
903    Args:
904        gamma: The damping constant.
905
906    Raises:
907        ValueError: is gamma is not a valid probability.
908    """
909    return PhaseDampingChannel(gamma)
910
911
912@value.value_equality
913class PhaseFlipChannel(gate_features.SingleQubitGate):
914    """Probabilistically flip the sign of the phase of a qubit."""
915
916    def __init__(self, p: float) -> None:
917        r"""The phase flip channel.
918
919        Construct a channel to flip the phase with probability p.
920
921        This channel evolves a density matrix via:
922
923            $$
924            \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
925            $$
926
927        With:
928
929            $$
930            \begin{aligned}
931            M_0 =& \sqrt{1 - p} \begin{bmatrix}
932                                1 & 0  \\
933                                0 & 1
934                            \end{bmatrix}
935            \\
936            M_1 =& \sqrt{p} \begin{bmatrix}
937                                1 & 0 \\
938                                0 & -1
939                            \end{bmatrix}
940            \end{aligned}
941            $$
942
943        Args:
944            p: the probability of a phase flip.
945
946        Raises:
947            ValueError: if p is not a valid probability.
948        """
949        self._p = value.validate_probability(p, 'p')
950        self._delegate = AsymmetricDepolarizingChannel(0.0, 0.0, p)
951
952    def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]:
953        mixture = self._delegate._mixture_()
954        # just return identity and z term
955        return (mixture[0], mixture[3])
956
957    def _has_mixture_(self) -> bool:
958        return True
959
960    def _value_equality_values_(self):
961        return self._p
962
963    def __repr__(self) -> str:
964        return f'cirq.phase_flip(p={self._p!r})'
965
966    def __str__(self) -> str:
967        return f'phase_flip(p={self._p!r})'
968
969    def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str:
970        if args.precision is not None:
971            f = '{:.' + str(args.precision) + 'g}'
972            return f'PF({f})'.format(self._p)
973        return f'PF({self._p!r})'
974
975    @property
976    def p(self) -> float:
977        """The probability of a phase flip."""
978        return self._p
979
980    def _json_dict_(self) -> Dict[str, Any]:
981        return protocols.obj_to_dict_helper(self, ['p'])
982
983    def _approx_eq_(self, other: Any, atol: float) -> bool:
984        return np.isclose(self.p, other.p, atol=atol).item()
985
986
987def _phase_flip_Z() -> common_gates.ZPowGate:
988    """Returns a cirq.Z which corresponds to a guaranteed phase flip."""
989    return common_gates.ZPowGate()
990
991
992def _phase_flip(p: float) -> PhaseFlipChannel:
993    r"""Returns a PhaseFlipChannel that flips a qubit's phase with probability p.
994
995    This channel evolves a density matrix via:
996
997        $$
998        \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
999        $$
1000
1001    With:
1002
1003        $$
1004        \begin{aligned}
1005        M_0 =& \sqrt{p} \begin{bmatrix}
1006                            1 & 0  \\
1007                            0 & 1
1008                       \end{bmatrix}
1009        \\
1010        M_1 =& \sqrt{1-p} \begin{bmatrix}
1011                            1 & 0 \\
1012                            0 & -1
1013                         \end{bmatrix}
1014        \end{aligned}
1015        $$
1016
1017    Args:
1018        p: the probability of a phase flip.
1019
1020    Raises:
1021        ValueError: if p is not a valid probability.
1022    """
1023    return PhaseFlipChannel(p)
1024
1025
1026# TODO(#3388) Add summary line to docstring.
1027# pylint: disable=docstring-first-line-empty
1028def phase_flip(p: Optional[float] = None) -> Union[common_gates.ZPowGate, PhaseFlipChannel]:
1029    r"""
1030    Returns a PhaseFlipChannel that flips a qubit's phase with probability p
1031    if p is None, return a guaranteed phase flip in the form of a Z operation.
1032
1033    This channel evolves a density matrix via:
1034
1035        $$
1036        \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
1037        $$
1038
1039    With:
1040
1041        $$
1042        \begin{aligned}
1043        M_0 =& \sqrt{p} \begin{bmatrix}
1044                            1 & 0  \\
1045                            0 & 1
1046                       \end{bmatrix}
1047        \\
1048        M_1 =& \sqrt{1-p} \begin{bmatrix}
1049                            1 & 0 \\
1050                            0 & -1
1051                         \end{bmatrix}
1052        \end{aligned}
1053        $$
1054
1055    Args:
1056        p: the probability of a phase flip.
1057
1058    Raises:
1059        ValueError: if p is not a valid probability.
1060    """
1061    if p is None:
1062        return _phase_flip_Z()
1063
1064    return _phase_flip(p)
1065
1066
1067# pylint: enable=docstring-first-line-empty
1068@value.value_equality
1069class BitFlipChannel(gate_features.SingleQubitGate):
1070    r"""Probabilistically flip a qubit from 1 to 0 state or vice versa."""
1071
1072    def __init__(self, p: float) -> None:
1073        r"""The bit flip channel.
1074
1075        Construct a channel that flips a qubit with probability p.
1076
1077        This channel evolves a density matrix via:
1078
1079            $$
1080            \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
1081            $$
1082
1083        With:
1084
1085            $$
1086            \begin{aligned}
1087            M_0 =& \sqrt{1 - p} \begin{bmatrix}
1088                                1 & 0  \\
1089                                0 & 1
1090                           \end{bmatrix}
1091            \\
1092            M_1 =& \sqrt{p} \begin{bmatrix}
1093                                0 & 1 \\
1094                                1 & 0
1095                             \end{bmatrix}
1096            \end{aligned}
1097            $$
1098
1099        Args:
1100            p: the probability of a bit flip.
1101
1102        Raises:
1103            ValueError: if p is not a valid probability.
1104        """
1105        self._p = value.validate_probability(p, 'p')
1106        self._delegate = AsymmetricDepolarizingChannel(p, 0.0, 0.0)
1107
1108    def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]:
1109        mixture = self._delegate._mixture_()
1110        # just return identity and x term
1111        return (mixture[0], mixture[1])
1112
1113    def _has_mixture_(self) -> bool:
1114        return True
1115
1116    def _value_equality_values_(self):
1117        return self._p
1118
1119    def __repr__(self) -> str:
1120        return f'cirq.bit_flip(p={self._p!r})'
1121
1122    def __str__(self) -> str:
1123        return f'bit_flip(p={self._p!r})'
1124
1125    def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str:
1126        if args.precision is not None:
1127            f = '{:.' + str(args.precision) + 'g}'
1128            return f'BF({f})'.format(self._p)
1129        return f'BF({self._p!r})'
1130
1131    @property
1132    def p(self) -> float:
1133        """The probability of a bit flip."""
1134        return self._p
1135
1136    def _json_dict_(self) -> Dict[str, Any]:
1137        return protocols.obj_to_dict_helper(self, ['p'])
1138
1139    def _approx_eq_(self, other: Any, atol: float) -> bool:
1140        return np.isclose(self._p, other._p, atol=atol).item()
1141
1142
1143# TODO(#3388) Add summary line to docstring.
1144# pylint: disable=docstring-first-line-empty
1145def _bit_flip(p: float) -> BitFlipChannel:
1146    r"""
1147    Construct a BitFlipChannel that flips a qubit state
1148    with probability of a flip given by p.
1149
1150    This channel evolves a density matrix via:
1151
1152        $$
1153        \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
1154        $$
1155
1156    With:
1157
1158        $$
1159        \begin{aligned}
1160        M_0 =& \sqrt{p} \begin{bmatrix}
1161                            1 & 0 \\
1162                            0 & 1
1163                       \end{bmatrix}
1164        \\
1165        M_1 =& \sqrt{1-p} \begin{bmatrix}
1166                            0 & 1 \\
1167                            1 & -0
1168                         \end{bmatrix}
1169        \end{aligned}
1170        $$
1171
1172    Args:
1173        p: the probability of a bit flip.
1174
1175    Raises:
1176        ValueError: if p is not a valid probability.
1177    """
1178    return BitFlipChannel(p)
1179
1180
1181# TODO(#3388) Add summary line to docstring.
1182def bit_flip(p: Optional[float] = None) -> Union[common_gates.XPowGate, BitFlipChannel]:
1183    r"""
1184    Construct a BitFlipChannel that flips a qubit state
1185    with probability of a flip given by p. If p is None, return
1186    a guaranteed flip in the form of an X operation.
1187
1188    This channel evolves a density matrix via
1189
1190        $$
1191        \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
1192        $$
1193
1194    With
1195
1196        $$
1197        \begin{aligned}
1198        M_0 =& \sqrt{p} \begin{bmatrix}
1199                            1 & 0 \\
1200                            0 & 1
1201                       \end{bmatrix}
1202        \\
1203        M_1 =& \sqrt{1-p} \begin{bmatrix}
1204                            0 & 1 \\
1205                            1 & -0
1206                         \end{bmatrix}
1207        \end{aligned}
1208        $$
1209
1210    Args:
1211        p: the probability of a bit flip.
1212
1213    Raises:
1214        ValueError: if p is not a valid probability.
1215    """
1216    if p is None:
1217        return pauli_gates.X
1218
1219    return _bit_flip(p)
1220
1221
1222# pylint: enable=docstring-first-line-empty
1223