# Source code for cirq.ops.common_channels

# Copyright 2018 The Cirq Developers
#
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# Unless required by applicable law or agreed to in writing, software
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and

"""Quantum channels that are commonly used in the literature."""

from typing import (Any, Dict, Iterable, Optional, Sequence, Tuple, Union,
TYPE_CHECKING)

import numpy as np

from cirq import protocols, value
from cirq.ops import (raw_types, common_gates, pauli_gates, gate_features,
identity)

if TYPE_CHECKING:
import cirq

[docs]@value.value_equality class AsymmetricDepolarizingChannel(gate_features.SingleQubitGate): """A channel that depolarizes asymmetrically along different directions."""
[docs] def __init__(self, p_x: float, p_y: float, p_z: float) -> None: r"""The asymmetric depolarizing channel. This channel applies one of four disjoint possibilities: nothing (the identity channel) or one of the three pauli gates. The disjoint probabilities of the three gates are p_x, p_y, and p_z and the identity is done with probability 1 - p_x - p_y - p_z. The supplied probabilities must be valid probabilities and the sum p_x + p_y + p_z must be a valid probability or else this constructor will raise a ValueError. This channel evolves a density matrix via $$\rho \rightarrow (1 - p_x - p_y - p_z) \rho + p_x X \rho X + p_y Y \rho Y + p_z Z \rho Z$$ Args: p_x: The probability that a Pauli X and no other gate occurs. p_y: The probability that a Pauli Y and no other gate occurs. p_z: The probability that a Pauli Z and no other gate occurs. Raises: ValueError: if the args or the sum of args are not probabilities. """ self._p_x = value.validate_probability(p_x, 'p_x') self._p_y = value.validate_probability(p_y, 'p_y') self._p_z = value.validate_probability(p_z, 'p_z') self._p_i = 1 - value.validate_probability(p_x + p_y + p_z, 'p_x + p_y + p_z')
def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]: return ((self._p_i, protocols.unitary(identity.I)), (self._p_x, protocols.unitary(pauli_gates.X)), (self._p_y, protocols.unitary(pauli_gates.Y)), (self._p_z, protocols.unitary(pauli_gates.Z))) def _has_mixture_(self) -> bool: return True def _value_equality_values_(self): return self._p_x, self._p_y, self._p_z def __repr__(self) -> str: return 'cirq.asymmetric_depolarize(p_x={!r},p_y={!r},p_z={!r})'.format( self._p_x, self._p_y, self._p_z ) def __str__(self) -> str: return 'asymmetric_depolarize(p_x={!r},p_y={!r},p_z={!r})'.format( self._p_x, self._p_y, self._p_z ) def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str: if args.precision is not None: f = '{:.' + str(args.precision) + 'g}' return 'A({},{},{})'.format(f, f, f).format(self._p_x, self._p_y, self._p_z) return 'A({!r},{!r},{!r})'.format(self._p_x, self._p_y, self._p_z) @property def p_x(self) -> float: """The probability that a Pauli X and no other gate occurs.""" return self._p_x @property def p_y(self) -> float: """The probability that a Pauli Y and no other gate occurs.""" return self._p_y @property def p_z(self) -> float: """The probability that a Pauli Z and no other gate occurs.""" return self._p_z def _json_dict_(self) -> Dict[str, Any]: return protocols.obj_to_dict_helper(self, ['p_x', 'p_y', 'p_z'])
[docs]def asymmetric_depolarize( p_x: float, p_y: float, p_z: float ) -> AsymmetricDepolarizingChannel: r"""Returns a AsymmetricDepolarizingChannel with given parameter. This channel evolves a density matrix via $$\rho \rightarrow (1 - p_x - p_y - p_z) \rho + p_x X \rho X + p_y Y \rho Y + p_z Z \rho Z$$ Args: p_x: The probability that a Pauli X and no other gate occurs. p_y: The probability that a Pauli Y and no other gate occurs. p_z: The probability that a Pauli Z and no other gate occurs. Raises: ValueError: if the args or the sum of the args are not probabilities. """ return AsymmetricDepolarizingChannel(p_x, p_y, p_z)
[docs]@value.value_equality class DepolarizingChannel(gate_features.SingleQubitGate): """A channel that depolarizes a qubit."""
[docs] def __init__(self, p: float) -> None: r"""The symmetric depolarizing channel. This channel applies one of four disjoint possibilities: nothing (the identity channel) or one of the three pauli gates. The disjoint probabilities of the three gates are all the same, p / 3, and the identity is done with probability 1 - p. The supplied probability must be a valid probability or else this constructor will raise a ValueError. This channel evolves a density matrix via $$\rho \rightarrow (1 - p) \rho + (p / 3) X \rho X + (p / 3) Y \rho Y + (p / 3) Z \rho Z$$ Args: p: The probability that one of the Pauli gates is applied. Each of the Pauli gates is applied independently with probability p / 3. Raises: ValueError: if p is not a valid probability. """ self._p = p self._delegate = AsymmetricDepolarizingChannel(p / 3, p / 3, p / 3)
def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]: return self._delegate._mixture_() def _has_mixture_(self) -> bool: return True def _value_equality_values_(self): return self._p def __repr__(self) -> str: return 'cirq.depolarize(p={!r})'.format(self._p) def __str__(self) -> str: return 'depolarize(p={!r})'.format(self._p) def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str: if args.precision is not None: f = '{:.' + str(args.precision) + 'g}' return 'D({})'.format(f).format(self._p) return 'D({!r})'.format(self._p) @property def p(self) -> float: """The probability that one of the Pauli gates is applied. Each of the Pauli gates is applied independently with probability p / 3. """ return self._p def _json_dict_(self) -> Dict[str, Any]: return protocols.obj_to_dict_helper(self, ['p'])
[docs]def depolarize(p: float) -> DepolarizingChannel: r"""Returns a DepolarizingChannel with given probability of error. This channel applies one of four disjoint possibilities: nothing (the identity channel) or one of the three pauli gates. The disjoint probabilities of the three gates are all the same, p / 3, and the identity is done with probability 1 - p. The supplied probability must be a valid probability or else this constructor will raise a ValueError. This channel evolves a density matrix via $$\rho \rightarrow (1 - p) \rho + (p / 3) X \rho X + (p / 3) Y \rho Y + (p / 3) Z \rho Z$$ Args: p: The probability that one of the Pauli gates is applied. Each of the Pauli gates is applied independently with probability p / 3. Raises: ValueError: if p is not a valid probability. """ return DepolarizingChannel(p)
[docs]@value.value_equality class GeneralizedAmplitudeDampingChannel(gate_features.SingleQubitGate): """Dampen qubit amplitudes through non ideal dissipation. This channel models the effect of energy dissipation into the environment as well as the environment depositing energy into the system. """
[docs] def __init__(self, p: float, gamma: float) -> None: r"""The generalized amplitude damping channel. Construct a channel to model energy dissipation into the environment as well as the environment depositing energy into the system. The probabilities with which the energy exchange occur are given by gamma, and the probability of the environment being not excited is given by p. The stationary state of this channel is the diagonal density matrix with probability p of being |0⟩ and probability 1-p of being |1⟩. This channel evolves a density matrix via $$\rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger + M_2 \rho M_2^\dagger + M_3 \rho M_3^\dagger$$ With \begin{aligned} M_0 =& \sqrt{p} \begin{bmatrix} 1 & 0 \\ 0 & \sqrt{1 - \gamma} \end{bmatrix} \\ M_1 =& \sqrt{p} \begin{bmatrix} 0 & \sqrt{\gamma} \\ 0 & 0 \end{bmatrix} \\ M_2 =& \sqrt{1-p} \begin{bmatrix} \sqrt{1-\gamma} & 0 \\ 0 & 1 \end{bmatrix} \\ M_3 =& \sqrt{1-p} \begin{bmatrix} 0 & 0 \\ \sqrt{\gamma} & 0 \end{bmatrix} \end{aligned} Args: gamma: the probability of the interaction being dissipative. p: the probability of the qubit and environment exchanging energy. Raises: ValueError: if gamma or p is not a valid probability. """ self._gamma = value.validate_probability(gamma, 'gamma') self._p = value.validate_probability(p, 'p')
def _channel_(self) -> Iterable[np.ndarray]: p0 = np.sqrt(self._p) p1 = np.sqrt(1. - self._p) sqrt_g = np.sqrt(self._gamma) sqrt_g1 = np.sqrt(1. - self._gamma) return ( p0 * np.array([[1., 0.], [0., sqrt_g1]]), p0 * np.array([[0., sqrt_g], [0., 0.]]), p1 * np.array([[sqrt_g1, 0.], [0., 1.]]), p1 * np.array([[0., 0.], [sqrt_g, 0.]]), ) def _has_channel_(self) -> bool: return True def _value_equality_values_(self): return self._p, self._gamma def __repr__(self) -> str: return 'cirq.generalized_amplitude_damp(p={!r},gamma={!r})'.format( self._p, self._gamma ) def __str__(self) -> str: return 'generalized_amplitude_damp(p={!r},gamma={!r})'.format( self._p, self._gamma ) def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str: if args.precision is not None: f = '{:.' + str(args.precision) + 'g}' return 'GAD({},{})'.format(f, f).format(self._p, self._gamma) return 'GAD({!r},{!r})'.format(self._p, self._gamma) @property def p(self) -> float: """The probability of the qubit and environment exchanging energy.""" return self._p @property def gamma(self) -> float: """The probability of the interaction being dissipative.""" return self._gamma def _json_dict_(self) -> Dict[str, Any]: return protocols.obj_to_dict_helper(self, ['p', 'gamma'])
[docs]def generalized_amplitude_damp( p: float, gamma: float ) -> GeneralizedAmplitudeDampingChannel: r""" Returns a GeneralizedAmplitudeDampingChannel with the given probabilities gamma and p. This channel evolves a density matrix via: $$\rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger + M_2 \rho M_2^\dagger + M_3 \rho M_3^\dagger$$ With: \begin{aligned} M_0 =& \sqrt{p} \begin{bmatrix} 1 & 0 \\ 0 & \sqrt{1 - \gamma} \end{bmatrix} \\ M_1 =& \sqrt{p} \begin{bmatrix} 0 & \sqrt{\gamma} \\ 0 & 0 \end{bmatrix} \\ M_2 =& \sqrt{1-p} \begin{bmatrix} \sqrt{1-\gamma} & 0 \\ 0 & 1 \end{bmatrix} \\ M_3 =& \sqrt{1-p} \begin{bmatrix} 0 & 0 \\ \sqrt{\gamma} & 0 \end{bmatrix} \end{aligned} Args: gamma: the probability of the interaction being dissipative. p: the probability of the qubit and environment exchanging energy. Raises: ValueError: gamma or p is not a valid probability. """ return GeneralizedAmplitudeDampingChannel(p, gamma)
[docs]@value.value_equality class AmplitudeDampingChannel(gate_features.SingleQubitGate): """Dampen qubit amplitudes through dissipation. This channel models the effect of energy dissipation to the surrounding environment. """
[docs] def __init__(self, gamma: float) -> None: r"""The amplitude damping channel. Construct a channel that dissipates energy. The probability of energy exchange occurring is given by gamma. This channel evolves a density matrix as follows: $$\rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger$$ With: \begin{aligned} M_0 =& \begin{bmatrix} 1 & 0 \\ 0 & \sqrt{1 - \gamma} \end{bmatrix} \\ M_1 =& \begin{bmatrix} 0 & \sqrt{\gamma} \\ 0 & 0 \end{bmatrix} \end{aligned} Args: gamma: the probability of the interaction being dissipative. Raises: ValueError: is gamma is not a valid probability. """ self._gamma = value.validate_probability(gamma, 'gamma') self._delegate = GeneralizedAmplitudeDampingChannel(1.0, self._gamma)
def _channel_(self) -> Iterable[np.ndarray]: # just return first two kraus ops, we don't care about # the last two. return list(self._delegate._channel_())[:2] def _has_channel_(self) -> bool: return True def _value_equality_values_(self): return self._gamma def __repr__(self) -> str: return 'cirq.amplitude_damp(gamma={!r})'.format(self._gamma) def __str__(self) -> str: return 'amplitude_damp(gamma={!r})'.format(self._gamma) def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str: if args.precision is not None: f = '{:.' + str(args.precision) + 'g}' return 'AD({})'.format(f).format(self._gamma) return 'AD({!r})'.format(self._gamma) @property def gamma(self) -> float: """The probability of the interaction being dissipative.""" return self._gamma def _json_dict_(self) -> Dict[str, Any]: return protocols.obj_to_dict_helper(self, ['gamma'])
[docs]def amplitude_damp(gamma: float) -> AmplitudeDampingChannel: r""" Returns an AmplitudeDampingChannel with the given probability gamma. This channel evolves a density matrix via: $$\rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger$$ With: \begin{aligned} M_0 =& \begin{bmatrix} 1 & 0 \\ 0 & \sqrt{1 - \gamma} \end{bmatrix} \\ M_1 =& \begin{bmatrix} 0 & \sqrt{\gamma} \\ 0 & 0 \end{bmatrix} \end{aligned} Args: gamma: the probability of the interaction being dissipative. Raises: ValueError: if gamma is not a valid probability. """ return AmplitudeDampingChannel(gamma)
[docs]@value.value_equality class ResetChannel(gate_features.SingleQubitGate): """Reset a qubit back to its |0⟩ state. The reset channel is equivalent to performing an unobserved measurement which then controls a bit flip onto the targeted qubit. """
[docs] def __init__(self, dimension: int = 2) -> None: r"""The reset channel. Construct a channel that resets the qubit. This channel evolves a density matrix as follows: $$\rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger$$ With: \begin{aligned} M_0 =& \begin{bmatrix} 1 & 0 \\ 0 & 0 \end{bmatrix} \\ M_1 =& \begin{bmatrix} 0 & 1 \\ 0 & 0 \end{bmatrix} \end{aligned} Args: dimension: Specify this argument when resetting a qudit. There will be dimension number of dimension by dimension matrices describing the channel, each with a 1 at a different position in the top row. """ self._dimension = dimension
def _qid_shape_(self): return (self._dimension,) def _channel_(self) -> Iterable[np.ndarray]: # The first axis is over the list of channel matrices channel = np.zeros((self._dimension,) * 3, dtype=np.complex64) channel[:, 0, :] = np.eye(self._dimension) return channel def _has_channel_(self) -> bool: return True def _value_equality_values_(self): return self._dimension def __repr__(self) -> str: if self._dimension == 2: return 'cirq.ResetChannel()' else: return 'cirq.ResetChannel(dimension={!r})'.format(self._dimension) def __str__(self) -> str: return 'reset' def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str: return 'R' @property def dimension(self) -> int: """The dimension of the qudit being reset.""" return self._dimension def _json_dict_(self) -> Dict[str, Any]: return protocols.obj_to_dict_helper(self, ['dimension'])
[docs]def reset(qubit: 'cirq.Qid') -> raw_types.Operation: """Returns a ResetChannel on the given qubit. """ return ResetChannel(qubit.dimension).on(qubit)
[docs]@value.value_equality class PhaseDampingChannel(gate_features.SingleQubitGate): """Dampen qubit phase. This channel models phase damping which is the loss of quantum information without the loss of energy. """
[docs] def __init__(self, gamma: float) -> None: r"""The phase damping channel. Construct a channel that enacts a phase damping constant gamma. This channel evolves a density matrix via: $$\rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger$$ With: \begin{aligned} M_0 =& \begin{bmatrix} 1 & 0 \\ 0 & \sqrt{1 - \gamma} \end{bmatrix} \\ M_1 =& \begin{bmatrix} 0 & 0 \\ 0 & \sqrt{\gamma} \end{bmatrix} \end{aligned} Args: gamma: The damping constant. Raises: ValueError: if gamma is not a valid probability. """ self._gamma = value.validate_probability(gamma, 'gamma')
def _channel_(self) -> Iterable[np.ndarray]: return ( np.array([[1., 0.], [0., np.sqrt(1. - self._gamma)]]), np.array([[0., 0.], [0., np.sqrt(self._gamma)]]), ) def _has_channel_(self) -> bool: return True def _value_equality_values_(self): return self._gamma def __repr__(self) -> str: return 'cirq.phase_damp(gamma={!r})'.format(self._gamma) def __str__(self) -> str: return 'phase_damp(gamma={!r})'.format(self._gamma) def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str: if args.precision is not None: f = '{:.' + str(args.precision) + 'g}' return 'PD({})'.format(f).format(self._gamma) return 'PD({!r})'.format(self._gamma) @property def gamma(self) -> float: """The damping constant.""" return self._gamma def _json_dict_(self) -> Dict[str, Any]: return protocols.obj_to_dict_helper(self, ['gamma'])
[docs]def phase_damp(gamma: float) -> PhaseDampingChannel: r""" Creates a PhaseDampingChannel with damping constant gamma. This channel evolves a density matrix via: $$\rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger$$ With: \begin{aligned} M_0 =& \begin{bmatrix} 1 & 0 \\ 0 & \sqrt{1 - \gamma} \end{bmatrix} \\ M_1 =& \begin{bmatrix} 0 & 0 \\ 0 & \sqrt{\gamma} \end{bmatrix} \end{aligned} Args: gamma: The damping constant. Raises: ValueError: is gamma is not a valid probability. """ return PhaseDampingChannel(gamma)
[docs]@value.value_equality class PhaseFlipChannel(gate_features.SingleQubitGate): """Probabilistically flip the sign of the phase of a qubit."""
[docs] def __init__(self, p: float) -> None: r"""The phase flip channel. Construct a channel to flip the phase with probability p. This channel evolves a density matrix via: $$\rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger$$ With: \begin{aligned} M_0 =& \sqrt{1 - p} \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \\ M_1 =& \sqrt{p} \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix} \end{aligned} Args: p: the probability of a phase flip. Raises: ValueError: if p is not a valid probability. """ self._p = value.validate_probability(p, 'p') self._delegate = AsymmetricDepolarizingChannel(0., 0., p)
def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]: mixture = self._delegate._mixture_() # just return identity and z term return (mixture[0], mixture[3]) def _has_mixture_(self) -> bool: return True def _value_equality_values_(self): return self._p def __repr__(self) -> str: return 'cirq.phase_flip(p={!r})'.format(self._p) def __str__(self) -> str: return 'phase_flip(p={!r})'.format(self._p) def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str: if args.precision is not None: f = '{:.' + str(args.precision) + 'g}' return 'PF({})'.format(f).format(self._p) return 'PF({!r})'.format(self._p) @property def p(self) -> float: """The probability of a phase flip.""" return self._p def _json_dict_(self) -> Dict[str, Any]: return protocols.obj_to_dict_helper(self, ['p'])
def _phase_flip_Z() -> common_gates.ZPowGate: """ Returns a cirq.Z which corresponds to a guaranteed phase flip. """ return common_gates.ZPowGate() def _phase_flip(p: float) -> PhaseFlipChannel: r""" Returns a PhaseFlipChannel that flips a qubit's phase with probability p. This channel evolves a density matrix via: $$\rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger$$ With: \begin{aligned} M_0 =& \sqrt{p} \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \\ M_1 =& \sqrt{1-p} \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix} \end{aligned} Args: p: the probability of a phase flip. Raises: ValueError: if p is not a valid probability. """ return PhaseFlipChannel(p)
[docs]def phase_flip( p: Optional[float] = None ) -> Union[common_gates.ZPowGate, PhaseFlipChannel]: r""" Returns a PhaseFlipChannel that flips a qubit's phase with probability p if p is None, return a guaranteed phase flip in the form of a Z operation. This channel evolves a density matrix via: $$\rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger$$ With: \begin{aligned} M_0 =& \sqrt{p} \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \\ M_1 =& \sqrt{1-p} \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix} \end{aligned} Args: p: the probability of a phase flip. Raises: ValueError: if p is not a valid probability. """ if p is None: return _phase_flip_Z() return _phase_flip(p)
[docs]@value.value_equality class BitFlipChannel(gate_features.SingleQubitGate): r"""Probabilistically flip a qubit from 1 to 0 state or vice versa."""
[docs] def __init__(self, p: float) -> None: r"""The bit flip channel. Construct a channel that flips a qubit with probability p. This channel evolves a density matrix via: $$\rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger$$ With: \begin{aligned} M_0 =& \sqrt{1 - p} \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \\ M_1 =& \sqrt{p} \begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix} \end{aligned} Args: p: the probability of a bit flip. Raises: ValueError: if p is not a valid probability. """ self._p = value.validate_probability(p, 'p') self._delegate = AsymmetricDepolarizingChannel(p, 0., 0.)
def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]: mixture = self._delegate._mixture_() # just return identity and x term return (mixture[0], mixture[1]) def _has_mixture_(self) -> bool: return True def _value_equality_values_(self): return self._p def __repr__(self) -> str: return 'cirq.bit_flip(p={!r})'.format(self._p) def __str__(self) -> str: return 'bit_flip(p={!r})'.format(self._p) def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str: if args.precision is not None: f = '{:.' + str(args.precision) + 'g}' return 'BF({})'.format(f).format(self._p) return 'BF({!r})'.format(self._p) @property def p(self) -> float: """The probability of a bit flip.""" return self._p def _json_dict_(self) -> Dict[str, Any]: return protocols.obj_to_dict_helper(self, ['p'])
def _bit_flip(p: float) -> BitFlipChannel: r""" Construct a BitFlipChannel that flips a qubit state with probability of a flip given by p. This channel evolves a density matrix via: $$\rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger$$ With: \begin{aligned} M_0 =& \sqrt{p} \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \\ M_1 =& \sqrt{1-p} \begin{bmatrix} 0 & 1 \\ 1 & -0 \end{bmatrix} \end{aligned} Args: p: the probability of a bit flip. Raises: ValueError: if p is not a valid probability. """ return BitFlipChannel(p)
[docs]def bit_flip( p: Optional[float] = None ) -> Union[common_gates.XPowGate, BitFlipChannel]: r""" Construct a BitFlipChannel that flips a qubit state with probability of a flip given by p. If p is None, return a guaranteed flip in the form of an X operation. This channel evolves a density matrix via $$\rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger$$ With \begin{aligned} M_0 =& \sqrt{p} \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \\ M_1 =& \sqrt{1-p} \begin{bmatrix} 0 & 1 \\ 1 & -0 \end{bmatrix} \end{aligned} Args: p: the probability of a bit flip. Raises: ValueError: if p is not a valid probability. """ if p is None: return pauli_gates.X return _bit_flip(p)