Repository: quantumlib/Cirq

Issue:
CircuitOperation with reps=0 still runs once
```python

def test_repeat_zero():
    import numpy as np
    q = cirq.LineQubit(0)
    op = cirq.CircuitOperation(cirq.FrozenCircuit(cirq.X(q)), repetitions=0)
    result = cirq.Simulator().simulate(cirq.Circuit(op))
    assert np.allclose(result.state_vector(), [1, 0])

>       assert np.allclose(result.state_vector(), [1, 0])
E       AssertionError: assert False
E        +  where False = <function allclose at 0x7f4c7e579430>(array([0.+0.j, 1.+0.j], dtype=complex64), [1, 0])
```

**Cirq version**
v 0.14 dev


Discussion:

Resolving patch:
diff --git a/cirq-core/cirq/circuits/circuit_operation.py b/cirq-core/cirq/circuits/circuit_operation.py
--- a/cirq-core/cirq/circuits/circuit_operation.py
+++ b/cirq-core/cirq/circuits/circuit_operation.py
@@ -45,7 +45,7 @@
 
 
 def default_repetition_ids(repetitions: int) -> Optional[List[str]]:
-    if abs(repetitions) > 1:
+    if abs(repetitions) != 1:
         return [str(i) for i in range(abs(repetitions))]
     return None
 
@@ -247,6 +247,8 @@ def mapped_circuit(self, deep: bool = False) -> 'cirq.Circuit':
             qubit mapping, parameterization, etc.) applied to it. This behaves
             like `cirq.decompose(self)`, but preserving moment structure.
         """
+        if self.repetitions == 0:
+            return circuits.Circuit()
         circuit = self.circuit.unfreeze()
         if self.qubit_map:
             circuit = circuit.transform_qubits(lambda q: self.qubit_map.get(q, q))
@@ -256,7 +258,7 @@ def mapped_circuit(self, deep: bool = False) -> 'cirq.Circuit':
             circuit = protocols.with_measurement_key_mapping(circuit, self.measurement_key_map)
         if self.param_resolver:
             circuit = protocols.resolve_parameters(circuit, self.param_resolver, recursive=False)
-        if self.repetition_ids:
+        if self.repetition_ids is not None:
             if not self.use_repetition_ids or not protocols.is_measurement(circuit):
                 circuit = circuit * abs(self.repetitions)
             else:


Test patch:
diff --git a/cirq-core/cirq/circuits/circuit_operation_test.py b/cirq-core/cirq/circuits/circuit_operation_test.py
--- a/cirq-core/cirq/circuits/circuit_operation_test.py
+++ b/cirq-core/cirq/circuits/circuit_operation_test.py
@@ -13,6 +13,7 @@
 # limitations under the License.
 from typing import Optional
 
+import numpy as np
 import pytest
 import sympy
 
@@ -310,6 +311,24 @@ def test_repeat(add_measurements, use_default_ids_for_initial_rep):
         _ = op_base.repeat(1.3)
 
 
+@pytest.mark.parametrize('add_measurements', [True, False])
+@pytest.mark.parametrize('use_repetition_ids', [True, False])
+@pytest.mark.parametrize('initial_reps', [0, 1, 2, 3])
+def test_repeat_zero_times(add_measurements, use_repetition_ids, initial_reps):
+    q = cirq.LineQubit(0)
+    subcircuit = cirq.Circuit(cirq.X(q))
+    if add_measurements:
+        subcircuit.append(cirq.measure(q))
+
+    op = cirq.CircuitOperation(
+        subcircuit.freeze(), repetitions=initial_reps, use_repetition_ids=use_repetition_ids
+    )
+    result = cirq.Simulator().simulate(cirq.Circuit(op))
+    assert np.allclose(result.state_vector(), [0, 1] if initial_reps % 2 else [1, 0])
+    result = cirq.Simulator().simulate(cirq.Circuit(op ** 0))
+    assert np.allclose(result.state_vector(), [1, 0])
+
+
 def test_qid_shape():
     circuit = cirq.FrozenCircuit(
         cirq.IdentityGate(qid_shape=(q.dimension,)).on(q)