Repository: quantumlib/Cirq

Issue:
Using `def _channel_(...)` to specify channels was changed to `def _kraus_(...)` without a deprecation period
This caused sudden breakages internally when we updated.

https://github.com/quantumlib/Cirq/commit/bc4123ef2a85c6a61667dc268554429690d2838d

Repro:

```
import cirq
import numpy as np
class MeasurementDiscard(cirq.SingleQubitGate):
    def _channel_(self):
        def delta(i):
            result = np.zeros((2, 2))
            result[i][i] = 1
            return result
        return tuple(delta(i) for i in range(2))

    def _has_channel_(self):
        return True
cirq.DensityMatrixSimulator().simulate(cirq.Circuit(MeasurementDiscard().on(cirq.LineQubit(0))))
```

Results in:

```
TypeError: DensityMatrixSimulator doesn't support <__main__.MeasurementDiscard object at 0x7fb222e0dc70>.on(cirq.LineQubit(0))
```



Discussion:

Resolving patch:
diff --git a/cirq-core/cirq/ops/gate_operation.py b/cirq-core/cirq/ops/gate_operation.py
--- a/cirq-core/cirq/ops/gate_operation.py
+++ b/cirq-core/cirq/ops/gate_operation.py
@@ -201,6 +201,18 @@ def _mixture_(self) -> Sequence[Tuple[float, Any]]:
             return getter()
         return NotImplemented
 
+    def _has_channel_(self) -> bool:
+        getter = getattr(self.gate, '_has_channel_', None)
+        if getter is not None:
+            return getter()
+        return NotImplemented
+
+    def _channel_(self) -> Union[Tuple[np.ndarray], NotImplementedType]:
+        getter = getattr(self.gate, '_channel_', None)
+        if getter is not None:
+            return getter()
+        return NotImplemented
+
     def _has_kraus_(self) -> bool:
         getter = getattr(self.gate, '_has_kraus_', None)
         if getter is not None:


Test patch:
diff --git a/cirq-core/cirq/ops/gate_operation_test.py b/cirq-core/cirq/ops/gate_operation_test.py
--- a/cirq-core/cirq/ops/gate_operation_test.py
+++ b/cirq-core/cirq/ops/gate_operation_test.py
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+from typing import Tuple
 
 import numpy as np
 import pytest
@@ -433,3 +434,27 @@ def _is_parameterized_(self):
     assert not cirq.is_parameterized(No1().on(q))
     assert not cirq.is_parameterized(No2().on(q))
     assert cirq.is_parameterized(Yes().on(q))
+
+
+def test_channel_propagates_to_gate():
+    class TestGate(cirq.SingleQubitGate):
+        def _channel_(self) -> np.ndarray:
+            return (np.eye(2),)
+
+        def _has_channel_(self) -> bool:
+            return True
+
+    def assert_kraus_eq(ks1: Tuple[np.ndarray, ...], ks2: Tuple[np.ndarray, ...]) -> None:
+        assert len(ks1) == len(ks2)
+        for k1, k2 in zip(ks1, ks2):
+            assert np.all(k1 == k2)
+
+    identity_kraus = (np.eye(2),)
+    q = cirq.LineQubit(0)
+    gate = TestGate()
+    gate_op = TestGate().on(q)
+    with cirq.testing.assert_deprecated(deadline='v0.13', count=None):
+        assert cirq.has_channel(gate)
+        assert cirq.has_channel(gate_op)
+        assert_kraus_eq(cirq.channel(gate), identity_kraus)
+        assert_kraus_eq(cirq.channel(gate_op), identity_kraus)