Repository: quantumlib/Cirq

Issue:
Library of useful noise models
After #925  we will need actual implementations of some useful channels

- depolarizing
- amplitude damping
- dephasing
- over or under rotation gates


Discussion:
@dabacon I wanna work on these, that cool ? 

Resolving patch:
diff --git a/cirq/__init__.py b/cirq/__init__.py
--- a/cirq/__init__.py
+++ b/cirq/__init__.py
@@ -89,8 +89,12 @@
 )
 
 from cirq.ops import (
+    amplitude_damp,
+    AmplitudeDampingChannel,
     asymmetric_depolarize,
     AsymmetricDepolarizingChannel,
+    bit_flip,
+    BitFlipChannel,
     CCX,
     CCZ,
     SingleQubitCliffordGate,
@@ -108,6 +112,8 @@
     freeze_op_tree,
     Gate,
     GateOperation,
+    generalized_amplitude_damp,
+    GeneralizedAmplitudeDampingChannel,
     H,
     HPowGate,
     InterchangeableQubitsGate,
@@ -123,11 +129,17 @@
     PauliInteractionGate,
     PauliString,
     PauliTransform,
+    phase_damp,
+    PhaseDampingChannel,
+    phase_flip,
+    PhaseFlipChannel,
     PhasedXPowGate,
     QubitId,
     QubitOrder,
     QubitOrderOrList,
     ReversibleCompositeGate,
+    rotation_error,
+    RotationErrorChannel,
     XPowGate,
     YPowGate,
     ZPowGate,
diff --git a/cirq/ops/__init__.py b/cirq/ops/__init__.py
--- a/cirq/ops/__init__.py
+++ b/cirq/ops/__init__.py
@@ -20,10 +20,22 @@
     PauliTransform,
 )
 from cirq.ops.common_channels import (
+    amplitude_damp,
+    AmplitudeDampingChannel,
     asymmetric_depolarize,
     AsymmetricDepolarizingChannel,
+    bit_flip,
+    BitFlipChannel,
     depolarize,
-    DepolarizingChannel
+    DepolarizingChannel,
+    generalized_amplitude_damp,
+    GeneralizedAmplitudeDampingChannel,
+    phase_damp,
+    PhaseDampingChannel,
+    phase_flip,
+    PhaseFlipChannel,
+    rotation_error,
+    RotationErrorChannel
 )
 from cirq.ops.common_gates import (
     CNOT,
diff --git a/cirq/ops/common_channels.py b/cirq/ops/common_channels.py
--- a/cirq/ops/common_channels.py
+++ b/cirq/ops/common_channels.py
@@ -14,13 +14,12 @@
 
 """Quantum channels that are commonly used in the literature."""
 
-from typing import Iterable
+from typing import Iterable, Optional, Union
 
 import numpy as np
 
 from cirq import protocols
-from cirq.ops import raw_types
-
+from cirq.ops import common_gates, raw_types
 
 class AsymmetricDepolarizingChannel(raw_types.Gate):
     """A channel that depolarizes asymmetrically along different directions."""
@@ -46,14 +45,16 @@ def __init__(self, p_x: float, p_y: float, p_z: float) -> None:
             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.
+            ValueError: if the args or the sum of args are not probabilities.
         """
+
         def validate_probability(p, p_str):
             if p < 0:
                 raise ValueError('{} was less than 0.'.format(p_str))
             elif p > 1:
                 raise ValueError('{} was greater than 1.'.format(p_str))
             return p
+
         self._p_x = validate_probability(p_x, 'p_x')
         self._p_y = validate_probability(p_y, 'p_y')
         self._p_z = validate_probability(p_z, 'p_z')
@@ -64,40 +65,42 @@ def _channel_(self) -> Iterable[np.ndarray]:
             np.sqrt(self._p_i) * np.eye(2),
             np.sqrt(self._p_x) * np.array([[0, 1], [1, 0]]),
             np.sqrt(self._p_y) * np.array([[0, -1j], [1j, 0]]),
-            np.sqrt(self._p_z) * np.array([[1, 0], [0, -1]])
+            np.sqrt(self._p_z) * np.array([[1, 0], [0, -1]]),
         )
 
     def _eq_tuple(self):
-        return (AsymmetricDepolarizingChannel,
-                self._p_x, self._p_y, self._p_z)
+        return (AsymmetricDepolarizingChannel, self._p_x, self._p_y, self._p_z)
 
     def __eq__(self, other):
         if not isinstance(other, type(self)):
             return NotImplemented
         return self._eq_tuple() == other._eq_tuple()
 
+    def __hash__(self):
+        return hash(self._eq_tuple())
+
     def __ne__(self, other):
         return not self == other
 
     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)
+        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))
+        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:
+    def _circuit_diagram_info_(
+        self, args: protocols.CircuitDiagramInfoArgs
+    ) -> str:
         return 'A({!r},{!r},{!r})'.format(self._p_x, self._p_y, self._p_z)
 
 
 def asymmetric_depolarize(
-    p_x: float,
-    p_y: float,
-    p_z: float) -> AsymmetricDepolarizingChannel:
+    p_x: float, p_y: float, p_z: float
+) -> AsymmetricDepolarizingChannel:
     """Returns a AsymmetricDepolarizingChannel with given parameter.
 
     This channel evolves a density matrix via
@@ -110,7 +113,7 @@ def asymmetric_depolarize(
         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.
+        ValueError: if the args or the sum of the args are not probabilities.
     """
     return AsymmetricDepolarizingChannel(p_x, p_y, p_z)
 
@@ -137,20 +140,26 @@ def __init__(self, p) -> None:
                 the Pauli gates is applied independently with probability p / 3.
 
         Raises:
-            ValueError if p is not a valid probability.
+            ValueError: if p is not a valid probability.
         """
 
         self._p = p
-        self._delegate = AsymmetricDepolarizingChannel(p / 3, p / 3, p /3)
+        self._delegate = AsymmetricDepolarizingChannel(p / 3, p / 3, p / 3)
 
     def _channel_(self) -> Iterable[np.ndarray]:
         return self._delegate._channel_()
 
+    def _eq_tuple(self):
+        return (DepolarizingChannel, self._p)
+
     def __eq__(self, other):
         if not isinstance(other, type(self)):
             return NotImplemented
         return self._p == other._p
 
+    def __hash__(self):
+        return hash(self._eq_tuple())
+
     def __ne__(self, other):
         return not self == other
 
@@ -160,8 +169,9 @@ def __repr__(self) -> str:
     def __str__(self) -> str:
         return 'depolarize(p={!r})'.format(self._p)
 
-    def _circuit_diagram_info_(self,
-        args: protocols.CircuitDiagramInfoArgs) -> str:
+    def _circuit_diagram_info_(
+        self, args: protocols.CircuitDiagramInfoArgs
+    ) -> str:
         return 'D({!r})'.format(self._p)
 
 
@@ -184,6 +194,717 @@ def depolarize(p: float) -> DepolarizingChannel:
             the Pauli gates is applied independently with probability p / 3.
 
     Raises:
-        ValueError if p is not a valid probability.
+        ValueError: if p is not a valid probability.
     """
     return DepolarizingChannel(p)
+
+
+class GeneralizedAmplitudeDampingChannel(raw_types.Gate):
+    """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.
+    """
+
+    def __init__(self, p, gamma) -> None:
+        """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 -> 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
+            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}
+
+        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.
+        """
+
+        def validate_probability(p, p_str):
+            if p < 0:
+                raise ValueError('{} was less than 0.'.format(p_str))
+            elif p > 1:
+                raise ValueError('{} was greater than 1.'.format(p_str))
+            return p
+
+        self._gamma = validate_probability(gamma, 'gamma')
+        self._p = 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 _eq_tuple(self):
+        return (GeneralizedAmplitudeDampingChannel, self._p, self._gamma)
+
+    def __eq__(self, other):
+        if not isinstance(other, type(self)):
+            return NotImplemented
+        return self._eq_tuple() == other._eq_tuple()
+
+    def __hash__(self):
+        return hash(self._eq_tuple())
+
+    def __ne__(self, other):
+        return not self == other
+
+    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:
+        return 'GAD({!r},{!r})'.format(self._p, self._gamma)
+
+
+def generalized_amplitude_damp(
+    p: float, gamma: float
+) -> GeneralizedAmplitudeDampingChannel:
+    """
+    Returns a GeneralizedAmplitudeDampingChannel with the given
+    probabilities gamma and p.
+
+    This channel evolves a density matrix via
+            \rho -> 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
+        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}
+
+    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)
+
+
+class AmplitudeDampingChannel(raw_types.Gate):
+    """Dampen qubit amplitudes through dissipation.
+
+    This channel models the effect of energy dissipation to the
+    surrounding environment.
+    """
+
+    def __init__(self, gamma) -> None:
+        """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 via
+            \rho -> M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
+
+        With
+            M_0 = \begin{bmatrix}
+                    1 & 0  \\
+                    0 & \sqrt{1 - \gamma}
+                  \end{bmatrix}
+            M_1 = \begin{bmatrix}
+                    0 & \sqrt{\gamma} \\
+                    0 & 0
+                  \end{bmatrix}
+
+        Args:
+            gamma: the probability of the interaction being dissipative.
+
+        Raises:
+            ValueError: is gamma is not a valid probability.
+        """
+
+        def validate_probability(p, p_str):
+            if p < 0:
+                raise ValueError('{} was less than 0.'.format(p_str))
+            elif p > 1:
+                raise ValueError('{} was greater than 1.'.format(p_str))
+            return p
+
+        self._gamma = 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 _eq_tuple(self):
+        return (AmplitudeDampingChannel, self._gamma)
+
+    def __eq__(self, other):
+        if not isinstance(other, type(self)):
+            return NotImplemented
+        return self._eq_tuple() == other._eq_tuple()
+
+    def __hash__(self):
+        return hash(self._eq_tuple())
+
+    def __ne__(self, other):
+        return not self == other
+
+    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:
+        return 'AD({!r})'.format(self._gamma)
+
+
+def amplitude_damp(gamma: float) -> AmplitudeDampingChannel:
+    """
+    Returns an AmplitudeDampingChannel with the given probability gamma.
+
+    This channel evolves a density matrix via
+            \rho -> M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
+
+    With
+        M_0 = \begin{bmatrix}
+                1 & 0  \\
+                0 & \sqrt{1 - \gamma}
+              \end{bmatrix}
+        M_1 = \begin{bmatrix}
+                0 & \sqrt{\gamma} \\
+                0 & 0
+              \end{bmatrix}
+
+    Args:
+        gamma: the probability of the interaction being dissipative.
+
+    Raises:
+        ValueError: if gamma is not a valid probability.
+    """
+    return AmplitudeDampingChannel(gamma)
+
+
+class PhaseDampingChannel(raw_types.Gate):
+    """Dampen qubit phase.
+
+    This channel models phase damping which is the loss of quantum
+    information without the loss of energy.
+    """
+
+    def __init__(self, gamma) -> None:
+        """The phase damping channel.
+
+        Construct a channel that enacts a phase damping constant gamma.
+
+        This channel evolves a density matrix via
+            \rho -> M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
+
+        With
+            M_0 = \begin{bmatrix}
+                    1 & 0 \\
+                    0 & \sqrt{1 - \gamma}
+                  \end{bmatrix}
+            M_1 = \begin{bmatrix}
+                    0 & 0 \\
+                    0 & \sqrt{\gamma}
+                  \end{bmatrix}
+
+        Args:
+            gamma: The damping constant.
+
+        Raises:
+            ValueError: if gamma is not a valid probability.
+        """
+
+        def validate_probability(p, p_str):
+            if p < 0:
+                raise ValueError('{} was less than 0.'.format(p_str))
+            elif p > 1:
+                raise ValueError('{} was greater than 1.'.format(p_str))
+            return p
+
+        self._gamma = 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 _eq_tuple(self):
+        return (PhaseDampingChannel, self._gamma)
+
+    def __eq__(self, other):
+        if not isinstance(other, type(self)):
+            return NotImplemented
+        return self._eq_tuple() == other._eq_tuple()
+
+    def __hash__(self):
+        return hash(self._eq_tuple())
+
+    def __ne__(self, other):
+        return not self == other
+
+    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:
+        return 'PD({!r})'.format(self._gamma)
+
+
+def phase_damp(gamma: float) -> PhaseDampingChannel:
+    """
+    Creates a PhaseDampingChannel with damping constant gamma.
+
+    This channel evolves a density matrix via
+           \rho -> M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
+
+    With
+        M_0 = \begin{bmatrix}
+                1 & 0  \\
+                0 & \sqrt{1 - \gamma}
+              \end{bmatrix}
+        M_1 = \begin{bmatrix}
+                0 & 0 \\
+                0 & \sqrt{\gamma}
+              \end{bmatrix}
+
+    Args:
+        gamma: The damping constant.
+
+    Raises:
+        ValueError: is gamma is not a valid probability.
+    """
+    return PhaseDampingChannel(gamma)
+
+
+class PhaseFlipChannel(raw_types.Gate):
+    """Probabilistically flip the sign of the phase of a qubit."""
+
+    def __init__(self, p) -> None:
+        """The phase flip channel.
+
+        Construct a channel to flip the phase with probability p.
+
+        This channel evolves a density matrix via
+            \rho -> M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
+
+        With
+            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}
+
+        Args:
+            p: the probability of a phase flip.
+
+        Raises:
+            ValueError: if p is not a valid probability.
+        """
+
+        def validate_probability(p, p_str):
+            if p < 0:
+                raise ValueError('{} was less than 0.'.format(p_str))
+            elif p > 1:
+                raise ValueError('{} was greater than 1.'.format(p_str))
+            return p
+
+        self._p = validate_probability(p, 'p')
+        self._delegate = AsymmetricDepolarizingChannel(0., 0., 1. - p)
+
+    def _channel_(self) -> Iterable[np.ndarray]:
+        kraus_ops = list(self._delegate._channel_())
+        # just return identity and z term
+        return (kraus_ops[0], kraus_ops[3])
+
+    def _eq_tuple(self):
+        return (PhaseFlipChannel, self._p)
+
+    def __eq__(self, other):
+        if not isinstance(other, type(self)):
+            return NotImplemented
+        return self._eq_tuple() == other._eq_tuple()
+
+    def __hash__(self):
+        return hash(self._eq_tuple())
+
+    def __ne__(self, other):
+        return not self == other
+
+    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:
+        return 'PF({!r})'.format(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:
+    """
+    Returns a PhaseFlipChannel that flips a qubit's phase with probability p.
+
+    This channel evolves a density matrix via
+           \rho -> M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
+
+    With
+        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}
+
+    Args:
+        p: the probability of a phase flip.
+
+    Raises:
+        ValueError: if p is not a valid probability.
+    """
+    return PhaseFlipChannel(p)
+
+
+def phase_flip(
+    p: Optional[float] = None
+) -> Union[common_gates.ZPowGate, PhaseFlipChannel]:
+    """
+    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 -> M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
+
+    With
+        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}
+
+    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)
+
+
+class BitFlipChannel(raw_types.Gate):
+    """Probabilistically flip a qubit from 1 to 0 state or vice versa."""
+
+    def __init__(self, p) -> None:
+        """The bit flip channel.
+
+        Construct a channel that flips a qubit with probability p.
+
+        This channel evolves a density matrix via
+            \rho -> M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
+
+        With
+            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}
+
+        Args:
+            p: the probability of a bit flip.
+
+        Raises:
+            ValueError: if p is not a valid probability.
+        """
+
+        def validate_probability(p, p_str):
+            if p < 0:
+                raise ValueError('{} was less than 0.'.format(p_str))
+            elif p > 1:
+                raise ValueError('{} was greater than 1.'.format(p_str))
+            return p
+
+        self._p = validate_probability(p, 'p')
+        self._delegate = AsymmetricDepolarizingChannel(1. - p, 0., 0.)
+
+    def _channel_(self) -> Iterable[np.ndarray]:
+        # Return just the I and X pieces.
+        return list(self._delegate._channel_())[:2]
+
+    def _eq_tuple(self):
+        return (BitFlipChannel, self._p)
+
+    def __eq__(self, other):
+        if not isinstance(other, type(self)):
+            return NotImplemented
+        return self._eq_tuple() == other._eq_tuple()
+
+    def __hash__(self):
+        return hash(self._eq_tuple())
+
+    def __ne__(self, other):
+        return not self == other
+
+    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:
+        return 'BF({!r})'.format(self._p)
+
+
+def _bit_flip_X() -> common_gates.XPowGate:
+    """
+    Returns a cirq.X with guarantees a bit flip.
+    """
+    return common_gates.XPowGate()
+
+
+def _bit_flip(p: float) -> BitFlipChannel:
+    """
+    Construct a BitFlipChannel that flips a qubit state
+    with probability of a flip given by p.
+
+    This channel evolves a density matrix via
+            \rho -> M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
+
+    With
+        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}
+
+    Args:
+        p: the probability of a bit flip.
+
+    Raises:
+        ValueError: if p is not a valid probability.
+    """
+    return BitFlipChannel(p)
+
+
+def bit_flip(
+    p: Optional[float] = None
+) -> Union[common_gates.XPowGate, BitFlipChannel]:
+    """
+    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 -> M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger
+
+    With
+        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}
+
+    Args:
+        p: the probability of a bit flip.
+
+    Raises:
+        ValueError: if p is not a valid probability.
+    """
+    if p is None:
+        return _bit_flip_X()
+
+    return _bit_flip(p)
+
+
+class RotationErrorChannel(raw_types.Gate):
+    """Channel to introduce rotation error in X, Y, Z."""
+
+    def __init__(self, eps_x, eps_y, eps_z) -> None:
+        """The rotation error channel.
+
+        This channel introduces rotation error by epsilon for
+        rotations in X, Y and Z that are constant in time.
+
+        This channel evolves a density matrix via
+            \rho ->
+           \exp{-i \epsilon_x \frac{X}{2}} \rho \exp{i \epsilon_x \frac{X}{2}}
+          + \exp{-i \epsilon_y \frac{Y}{2}} \rho \exp{i \epsilon_y \frac{Y}{2}}
+          + \exp{-i \epsilon_z \frac{Z}{2}} \rho \exp{i \epsilon_z \frac{Z}{2}}
+
+        Args:
+            eps_x: angle to over rotate in x.
+            eps_y: angle to over rotate in y.
+            eps_z: angle to over rotate in z.
+        """
+
+        # Angles could be anything, so no validation nescessary ?
+        self._eps_x = eps_x
+        self._eps_y = eps_y
+        self._eps_z = eps_z
+
+    def _channel_(self) -> Iterable[np.ndarray]:
+        return (
+            np.exp(
+                0.5
+                * (0. - 1.0j)
+                * self._eps_x
+                * np.array([[0., 1.], [1., 0.]])
+            ),
+            np.exp(
+                0.5
+                * (0. - 1.0j)
+                * self._eps_y
+                * np.array([[0., (0. - 1.0j)], [(0. + 1.0j), 0.]])
+            ),
+            np.exp(
+                0.5
+                * (0. - 1.0j)
+                * self._eps_z
+                * np.array([[1., 0.], [0., -1.]])
+            ),
+        )
+
+    def _eq_tuple(self):
+        return (RotationErrorChannel, self._eps_x, self._eps_y, self._eps_z)
+
+    def __eq__(self, other):
+        if not isinstance(other, type(self)):
+            return NotImplemented
+        return self._eq_tuple() == other._eq_tuple()
+
+    def __hash__(self):
+        return hash(self._eq_tuple())
+
+    def __ne__(self, other):
+        return not self == other
+
+    def __repr__(self) -> str:
+        return 'cirq.rotation_error(eps_x={!r},eps_y={!r},eps_z={!r})'.format(
+            self._eps_x, self._eps_y, self._eps_z
+        )
+
+    def __str__(self) -> str:
+        return 'rotation_error(eps_x={!r},eps_y={!r},eps_z={!r})'.format(
+            self._eps_x, self._eps_y, self._eps_z
+        )
+
+    def _circuit_diagram_info_(
+        self, args: protocols.CircuitDiagramInfoArgs
+    ) -> str:
+        return 'RE({!r},{!r},{!r})'.format(
+            self._eps_x, self._eps_y, self._eps_z
+        )
+
+
+def rotation_error(
+    eps_x: float, eps_y: float, eps_z: float
+) -> RotationErrorChannel:
+
+    """
+    Constructs a RotationErrorChannel that can over/under rotate
+    a qubit in X, Y, Z by given error angles.
+
+    This channel evolves a density matrix via
+        \rho ->
+        \exp{-i \epsilon_x \frac{X}{2}} \rho \exp{i \epsilon_x \frac{X}{2}}
+        + \exp{-i \epsilon_y \frac{Y}{2}} \rho \exp{i \epsilon_y \frac{Y}{2}}
+        + \exp{-i \epsilon_z \frac{Z}{2}} \rho \exp{i \epsilon_z \frac{Z}{2}}
+
+    Args:
+        eps_x: angle to over rotate in x.
+        eps_y: angle to over rotate in y.
+        eps_z: angle to over rotate in z.
+    """
+
+    return RotationErrorChannel(eps_x, eps_y, eps_z)


Test patch:
diff --git a/cirq/ops/common_channels_test.py b/cirq/ops/common_channels_test.py
--- a/cirq/ops/common_channels_test.py
+++ b/cirq/ops/common_channels_test.py
@@ -119,3 +119,261 @@ def test_depolarizing_channel_text_diagram():
             == cirq.CircuitDiagramInfo(wire_symbols=('D(0.3)',)))
 
 
+def test_generalized_amplitude_damping_channel():
+    d = cirq.generalized_amplitude_damp(0.1, 0.3)
+    np.testing.assert_almost_equal(cirq.channel(d),
+              (np.sqrt(0.1) * np.array([[1., 0.], [0., np.sqrt(1.-0.3)]]),
+               np.sqrt(0.1) * np.array([[0., np.sqrt(0.3)], [0., 0.]]),
+               np.sqrt(0.9) * np.array([[np.sqrt(1. - 0.3), 0.], [0., 1.]]),
+               np.sqrt(0.9) * np.array([[0., 0.], [np.sqrt(0.3), 0.]])))
+
+
+def test_generalized_amplitude_damping_repr():
+    cirq.testing.assert_equivalent_repr(
+        cirq.GeneralizedAmplitudeDampingChannel(0.1, 0.3))
+
+def test_generalized_amplitude_damping_str():
+    assert (str(cirq.generalized_amplitude_damp(0.1, 0.3))
+            == 'generalized_amplitude_damp(p=0.1,gamma=0.3)')
+
+
+def test_generalized_amplitude_damping_channel_eq():
+    et = cirq.testing.EqualsTester()
+    c = cirq.generalized_amplitude_damp(0.0, 0.0)
+    et.make_equality_group(lambda: c)
+    et.add_equality_group(cirq.generalized_amplitude_damp(0.1, 0.0))
+    et.add_equality_group(cirq.generalized_amplitude_damp(0.0, 0.1))
+    et.add_equality_group(cirq.generalized_amplitude_damp(0.6, 0.4))
+    et.add_equality_group(cirq.generalized_amplitude_damp(0.8, 0.2))
+
+
+
+@pytest.mark.parametrize('p, gamma', (
+    (-0.1, 0.0),
+    (0.0, -0.1),
+    (0.1, -0.1),
+    (-0.1, 0.1)))
+def test_generalized_amplitude_damping_channel_negative_probability(p, gamma):
+    with pytest.raises(ValueError, match='was less than 0'):
+        cirq.generalized_amplitude_damp(p, gamma)
+
+
+
+@pytest.mark.parametrize('p,gamma', (
+    (1.1, 0.0),
+    (0.0, 1.1),
+    (1.1, 1.1)))
+def test_generalized_amplitude_damping_channel_bigly_probability(p, gamma):
+    with pytest.raises(ValueError, match='was greater than 1'):
+        cirq.generalized_amplitude_damp(p, gamma)
+
+
+
+def test_generalized_amplitude_damping_channel_text_diagram():
+    a= cirq.generalized_amplitude_damp(0.1, 0.3)
+    assert (cirq.circuit_diagram_info(a) == cirq.CircuitDiagramInfo(
+        wire_symbols=('GAD(0.1,0.3)',)))
+
+
+def test_amplitude_damping_channel():
+    d = cirq.amplitude_damp(0.3)
+    np.testing.assert_almost_equal(cirq.channel(d),
+                              (np.array([[1., 0.], [0., np.sqrt(1. - 0.3)]]),
+                               np.array([[0., np.sqrt(0.3)], [0., 0.]])))
+
+
+def test_amplitude_damping_channel_repr():
+    cirq.testing.assert_equivalent_repr(
+        cirq.AmplitudeDampingChannel(0.3))
+
+
+def test_amplitude_damping_channel_str():
+    assert (str(cirq.amplitude_damp(0.3))
+            == 'amplitude_damp(gamma=0.3)')
+
+
+def test_amplitude_damping_channel_eq():
+    et = cirq.testing.EqualsTester()
+    c = cirq.amplitude_damp(0.0)
+    et.make_equality_group(lambda: c)
+    et.add_equality_group(cirq.amplitude_damp(0.1))
+    et.add_equality_group(cirq.amplitude_damp(0.4))
+    et.add_equality_group(cirq.amplitude_damp(0.6))
+    et.add_equality_group(cirq.amplitude_damp(0.8))
+
+def test_amplitude_damping_channel_invalid_probability():
+    with pytest.raises(ValueError, match='was less than 0'):
+        cirq.amplitude_damp(-0.1)
+    with pytest.raises(ValueError, match='was greater than 1'):
+        cirq.amplitude_damp(1.1)
+
+
+def test_amplitude_damping_channel_text_diagram():
+    assert (cirq.circuit_diagram_info(cirq.amplitude_damp(0.3))
+            == cirq.CircuitDiagramInfo(wire_symbols=('AD(0.3)',)))
+
+
+def test_phase_damping_channel():
+    d = cirq.phase_damp(0.3)
+    np.testing.assert_almost_equal(cirq.channel(d),
+                              (np.array([[1.0, 0.], [0., np.sqrt(1 - 0.3)]]),
+                               np.array([[0., 0.], [0., np.sqrt(0.3)]])))
+
+
+def test_phase_damping_channel_repr():
+    cirq.testing.assert_equivalent_repr(
+        cirq.PhaseDampingChannel(0.3))
+
+
+def test_phase_damping_channel_str():
+    assert (str(cirq.phase_damp(0.3))
+            == 'phase_damp(gamma=0.3)')
+
+
+def test_phase_damping_channel_eq():
+    et = cirq.testing.EqualsTester()
+    c = cirq.phase_damp(0.0)
+    et.make_equality_group(lambda: c)
+    et.add_equality_group(cirq.phase_damp(0.1))
+    et.add_equality_group(cirq.phase_damp(0.4))
+    et.add_equality_group(cirq.phase_damp(0.6))
+    et.add_equality_group(cirq.phase_damp(0.8))
+
+
+def test_phase_damping_channel_invalid_probability():
+    with pytest.raises(ValueError, match='was less than 0'):
+        cirq.phase_damp(-0.1)
+    with pytest.raises(ValueError, match='was greater than 1'):
+        cirq.phase_damp(1.1)
+
+
+def test_phase_damping_channel_text_diagram():
+    assert (cirq.circuit_diagram_info(cirq.phase_damp(0.3))
+            == cirq.CircuitDiagramInfo(wire_symbols=('PD(0.3)',)))
+
+
+def test_phase_flip_channel():
+    d = cirq.phase_flip(0.3)
+    np.testing.assert_almost_equal(cirq.channel(d),
+                                  (np.sqrt(0.3) * np.eye(2),
+                                   np.sqrt(1.-0.3) * Z))
+
+
+def test_phase_flip_overload():
+    d = cirq.phase_flip()
+    d2 = cirq.phase_flip(0.3)
+    assert str(d) == 'Z'
+    assert str(d2) == 'phase_flip(p=0.3)'
+
+def test_phase_flip_channel_repr():
+    cirq.testing.assert_equivalent_repr(
+        cirq.PhaseFlipChannel(0.3))
+
+
+def test_phase_flip_channel_str():
+    assert (str(cirq.phase_flip(0.3))
+            == 'phase_flip(p=0.3)')
+
+
+def test_phase_flip_channel_eq():
+    et = cirq.testing.EqualsTester()
+    c = cirq.phase_flip(0.0)
+    et.make_equality_group(lambda: c)
+    et.add_equality_group(cirq.phase_flip(0.1))
+    et.add_equality_group(cirq.phase_flip(0.4))
+    et.add_equality_group(cirq.phase_flip(0.6))
+    et.add_equality_group(cirq.phase_flip(0.8))
+
+
+
+def test_phase_flip_channel_invalid_probability():
+    with pytest.raises(ValueError, match='was less than 0'):
+        cirq.phase_flip(-0.1)
+    with pytest.raises(ValueError, match='was greater than 1'):
+        cirq.phase_flip(1.1)
+
+
+def test_phase_flip_channel_text_diagram():
+    assert (cirq.circuit_diagram_info(cirq.phase_flip(0.3))
+            == cirq.CircuitDiagramInfo(wire_symbols=('PF(0.3)',)))
+
+
+def test_bit_flip_channel():
+    d = cirq.bit_flip(0.3)
+    np.testing.assert_almost_equal(cirq.channel(d),
+                                  (np.sqrt(0.3) * np.eye(2),
+                                   np.sqrt(1.0 - 0.3) * X))
+
+
+def test_bit_flip_overload():
+    d = cirq.bit_flip()
+    d2 = cirq.bit_flip(0.3)
+    assert str(d) == 'X'
+    assert str(d2) == 'bit_flip(p=0.3)'
+
+
+def test_bit_flip_channel_repr():
+    cirq.testing.assert_equivalent_repr(
+        cirq.BitFlipChannel(0.3))
+
+
+def test_bit_flip_channel_str():
+    assert (str(cirq.bit_flip(0.3))
+            == 'bit_flip(p=0.3)')
+
+
+def test_bit_flip_channel_eq():
+    et = cirq.testing.EqualsTester()
+    c = cirq.bit_flip(0.0)
+    et.make_equality_group(lambda: c)
+    et.add_equality_group(cirq.bit_flip(0.1))
+    et.add_equality_group(cirq.bit_flip(0.4))
+    et.add_equality_group(cirq.bit_flip(0.6))
+    et.add_equality_group(cirq.bit_flip(0.8))
+
+def test_bit_flip_channel_invalid_probability():
+    with pytest.raises(ValueError, match='was less than 0'):
+        cirq.bit_flip(-0.1)
+    with pytest.raises(ValueError, match='was greater than 1'):
+        cirq.bit_flip(1.1)
+
+
+
+def test_bit_flip_channel_text_diagram():
+    assert (cirq.circuit_diagram_info(cirq.bit_flip(0.3))
+            == cirq.CircuitDiagramInfo(wire_symbols=('BF(0.3)',)))
+
+
+def test_rotation_error_channel():
+    d = cirq.rotation_error(np.pi / 6., np.pi/12., np.pi/8.)
+    np.testing.assert_almost_equal(cirq.channel(d),
+                               (np.exp( X * 0.5 * (0.0 - 1.0j) * np.pi / 6.),
+                                np.exp( Y * 0.5 * (0.0 - 1.0j) * np.pi / 12.),
+                                np.exp( Z * 0.5 * (0.0 - 1.0j) * np.pi / 8.)))
+
+
+def test_rotation_error_channel_repr():
+    cirq.testing.assert_equivalent_repr(
+        cirq.RotationErrorChannel(np.pi / 6., np.pi / 12., np.pi / 8.))
+
+
+def test_rotation_error_channel_str():
+    assert (str(cirq.rotation_error(0.3, 0.4, 0.5))
+            == 'rotation_error(eps_x=0.3,eps_y=0.4,eps_z=0.5)')
+
+
+def test_rotation_error_channel_eq():
+    et = cirq.testing.EqualsTester()
+    c = cirq.rotation_error(0.0, 0.0, 0.0)
+    et.make_equality_group(lambda: c)
+    et.add_equality_group(cirq.rotation_error(0.1, 0.1, 0.1))
+    et.add_equality_group(cirq.rotation_error(0.9, 0.9, 0.9))
+    et.add_equality_group(cirq.rotation_error(0.3, 0.4, 0.5))
+    et.add_equality_group(cirq.rotation_error(0.0, 0.0, 0.1))
+    et.add_equality_group(cirq.rotation_error(0.0, 0.1, 0.0))
+    et.add_equality_group(cirq.rotation_error(0.1, 0.0, 0.0))
+
+
+def test_rotation_error_channel_text_diagram():
+    assert (cirq.circuit_diagram_info(cirq.rotation_error(0.3,0.4,0.5))
+            == cirq.CircuitDiagramInfo(wire_symbols=('RE(0.3,0.4,0.5)',)))