Repository: quantumlib/Cirq

Issue:
Add Reset channel
The reset channel is equivalent to performing an unobserved measurement which then controls a bit flip onto the targeted qubit. It is also equivalent to tracing out a qubit and then swapping a fresh 0 qubit into the system.


Discussion:
I'd like to work on this (I need this for a simulation I'm doing anyway). 
I looked at the channel protocol and I think that I have an idea on how to implement this.

My idea is the following:
The measurement Kraus operators are `[[1,0],[0,0]]` and `[[0,0],[0,1]]`, where the latter corresponds to the 1 state. If we measure 1, we apply a Y-gate with the unitary `[[0,1],[-1,0]]`, i.e. the second Kraus operator becomes `[[0,1],[-1,0]] * [[0,0],[0,1]] = [[0,1],[0,0]]`. So to implement this in Cirq, I just make a class analogous to one of the common channels, but with the `_channel_` method returning `(np.array([[1, 0], [0,0]]), np.array([[0, 1], [0, 0]]))`. Which is equivalent to an amplitude damping channel with probability 1.

Does this sound reasonable?
What is a good circuit diagram symbol in this case?


That is the correct channel (although applying X also works).  I would suggest implementing this like `AmplitudeDampingChannel` or `GeneralizedAmplitudeDampingChannel`.

@dabacon might have ideas for a diagram symbol.  `R` makes sense but might be confused with rotation.  `|0⟩` (and `|0>` for ASCII mode) could work.

Resolving patch:
diff --git a/cirq/__init__.py b/cirq/__init__.py
--- a/cirq/__init__.py
+++ b/cirq/__init__.py
@@ -168,6 +168,8 @@
     Qid,
     QubitOrder,
     QubitOrderOrList,
+    reset,
+    ResetChannel,
     Rx,
     Ry,
     Rz,
diff --git a/cirq/ops/__init__.py b/cirq/ops/__init__.py
--- a/cirq/ops/__init__.py
+++ b/cirq/ops/__init__.py
@@ -35,6 +35,8 @@
     phase_flip,
     PhaseDampingChannel,
     PhaseFlipChannel,
+    reset,
+    ResetChannel,
 )
 
 from cirq.ops.common_gates import (
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
@@ -446,6 +446,67 @@ def amplitude_damp(gamma: float) -> AmplitudeDampingChannel:
     return AmplitudeDampingChannel(gamma)
 
 
+@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.
+    """
+
+    def __init__(self) -> 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}
+            $$
+
+        """
+        self._delegate = AmplitudeDampingChannel(1.0)
+
+    def _channel_(self) -> Iterable[np.ndarray]:
+        return self._delegate._channel_()
+
+    def _has_channel_(self) -> bool:
+        return True
+
+    def _value_equality_values_(self):
+        return None
+
+    def __repr__(self) -> str:
+        return 'cirq.reset'
+
+    def __str__(self) -> str:
+        return 'reset'
+
+    def _circuit_diagram_info_(self,
+                               args: protocols.CircuitDiagramInfoArgs) -> str:
+        return 'R'
+
+
+reset = ResetChannel()
+
+
 @value.value_equality
 class PhaseDampingChannel(gate_features.SingleQubitGate):
     """Dampen qubit phase.


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
@@ -242,6 +242,31 @@ def test_amplitude_damping_channel_text_diagram():
             == cirq.CircuitDiagramInfo(wire_symbols=('AD(0.3)',)))
 
 
+def test_reset_channel():
+    np.testing.assert_almost_equal(
+        cirq.channel(cirq.reset),
+        (np.array([[1., 0.], [0., 0]]), np.array([[0., 1.], [0., 0.]])))
+    assert cirq.has_channel(cirq.reset)
+    assert not cirq.has_mixture_channel(cirq.reset)
+
+
+def test_reset_channel_equality():
+    assert cirq.reset == cirq.ResetChannel()
+
+
+def test_reset_channel_repr():
+    cirq.testing.assert_equivalent_repr(cirq.reset)
+
+
+def test_reset_channel_str():
+    assert (str(cirq.reset) == 'reset')
+
+
+def test_reset_channel_text_diagram():
+    assert (cirq.circuit_diagram_info(
+        cirq.reset) == cirq.CircuitDiagramInfo(wire_symbols=('R',)))
+
+
 def test_phase_damping_channel():
     d = cirq.phase_damp(0.3)
     np.testing.assert_almost_equal(cirq.channel(d),