Repository: quantumlib/Cirq

Issue:
Incorrect Behavior of Transformer `eject_phased_paulis` with Mid-Circuit Measurements
**Description of the issue**

The `eject_phased_paulis` transformer exhibits incorrect behavior when interacting with X or Y gates preceding measurements. Specifically, the transformer erroneously merges these gates into measurements, as demonstrated by the following transformation:

Original circuit:
```
0: ───X───M('c_0')───M('c_1')───
```

Transformed circuit:
```
0: ───!M('c_0')───M('c_1')───
```
This issue notably affects the measurement outcome of `c_1`, potentially altering the execution results of circuits incorporating mid-circuit measurements. A temporary solution might involve verifying the position of the measurement within the circuit, ensuring it only merges operations when measurements are at the end of the circuit.

**How to reproduce the issue**


```python
import cirq

circuit = cirq.Circuit(
    cirq.X(cirq.LineQubit(0)),
    cirq.measure(cirq.LineQubit(0), key='c_0'),
    cirq.measure(cirq.LineQubit(0), key='c_1'),
)
circuit_trans = cirq.eject_phased_paulis(circuit)

print(circuit)
print(circuit_trans)
```

<details>

Execution result:
```
0: ───X───M('c_0')───M('c_1')───
0: ───!M('c_0')───M('c_1')───
```

</details>

**Cirq version**
1.3.0




Discussion:

Patch:
diff --git a/cirq-core/cirq/transformers/eject_phased_paulis.py b/cirq-core/cirq/transformers/eject_phased_paulis.py
index 5a40ca4fe92..36d66045ae4 100644
--- a/cirq-core/cirq/transformers/eject_phased_paulis.py
+++ b/cirq-core/cirq/transformers/eject_phased_paulis.py
@@ -40,9 +40,8 @@ def eject_phased_paulis(
     """Transformer pass to push X, Y, PhasedX & (certain) PhasedXZ gates to the end of the circuit.
 
     As the gates get pushed, they may absorb Z gates, cancel against other
-    X, Y, or PhasedX gates with exponent=1, get merged into measurements (as
-    output bit flips), and cause phase kickback operations across CZs (which can
-    then be removed by the `cirq.eject_z` transformation).
+    X, Y, or PhasedX gates with exponent=1, and cause phase kickback operations
+    across CZs (which can then be removed by the `cirq.eject_z` transformation).
 
     `cirq.PhasedXZGate` with `z_exponent=0` (i.e. equivalent to PhasedXPow) or with `x_exponent=0`
     and `axis_phase_exponent=0` (i.e. equivalent to ZPowGate) are also supported.
@@ -85,10 +84,6 @@ def map_func(op: cirq.Operation, _: int) -> cirq.OP_TREE:
         if t is not None:
             return _absorb_z_into_w(op, held_w_phases)
 
-        # Dump coherent flips into measurement bit flips.
-        if isinstance(op.gate, ops.MeasurementGate):
-            return _dump_into_measurement(op, held_w_phases)
-
         # Cross CZs using kickback.
         if _try_get_known_cz_half_turns(op, no_symbolic=not eject_parameterized) is not None:
             return (
@@ -140,18 +135,6 @@ def _dump_held(
         held_w_phases.pop(q, None)
 
 
-def _dump_into_measurement(
-    op: ops.Operation, held_w_phases: dict[ops.Qid, value.TParamVal]
-) -> cirq.OP_TREE:
-    measurement = cast(ops.MeasurementGate, cast(ops.GateOperation, op).gate)
-    new_measurement = measurement.with_bits_flipped(
-        *[i for i, q in enumerate(op.qubits) if q in held_w_phases]
-    ).on(*op.qubits)
-    for q in op.qubits:
-        held_w_phases.pop(q, None)
-    return new_measurement
-
-
 def _potential_cross_whole_w(
     op: ops.Operation, atol: float, held_w_phases: dict[ops.Qid, value.TParamVal]
 ) -> cirq.OP_TREE:


Test patch:
diff --git a/cirq-core/cirq/transformers/eject_phased_paulis_test.py b/cirq-core/cirq/transformers/eject_phased_paulis_test.py
index c147149a2c6..bfbcd0bb45d 100644
--- a/cirq-core/cirq/transformers/eject_phased_paulis_test.py
+++ b/cirq-core/cirq/transformers/eject_phased_paulis_test.py
@@ -235,60 +235,12 @@ def test_crosses_czs() -> None:
     )
 
 
-def test_toggles_measurements() -> None:
+def test_doesnt_change_measurements() -> None:
     a = cirq.NamedQubit('a')
-    b = cirq.NamedQubit('b')
-    x = sympy.Symbol('x')
 
-    # Single.
-    assert_optimizes(
-        before=quick_circuit(
-            [cirq.PhasedXPowGate(phase_exponent=0.25).on(a)], [cirq.measure(a, b)]
-        ),
-        expected=quick_circuit([cirq.measure(a, b, invert_mask=(True,))]),
-    )
     assert_optimizes(
-        before=quick_circuit(
-            [cirq.PhasedXPowGate(phase_exponent=0.25).on(b)], [cirq.measure(a, b)]
-        ),
-        expected=quick_circuit([cirq.measure(a, b, invert_mask=(False, True))]),
-    )
-    assert_optimizes(
-        before=quick_circuit([cirq.PhasedXPowGate(phase_exponent=x).on(b)], [cirq.measure(a, b)]),
-        expected=quick_circuit([cirq.measure(a, b, invert_mask=(False, True))]),
-        eject_parameterized=True,
-    )
-
-    # Multiple.
-    assert_optimizes(
-        before=quick_circuit(
-            [cirq.PhasedXPowGate(phase_exponent=0.25).on(a)],
-            [cirq.PhasedXPowGate(phase_exponent=0.25).on(b)],
-            [cirq.measure(a, b)],
-        ),
-        expected=quick_circuit([cirq.measure(a, b, invert_mask=(True, True))]),
-    )
-
-    # Xmon.
-    assert_optimizes(
-        before=quick_circuit(
-            [cirq.PhasedXPowGate(phase_exponent=0.25).on(a)], [cirq.measure(a, b, key='t')]
-        ),
-        expected=quick_circuit([cirq.measure(a, b, invert_mask=(True,), key='t')]),
-    )
-
-    # CCOs
-    assert_optimizes(
-        before=quick_circuit(
-            [cirq.PhasedXPowGate(phase_exponent=0.25).on(a)],
-            [cirq.measure(a, key="m")],
-            [cirq.X(b).with_classical_controls("m")],
-        ),
-        expected=quick_circuit(
-            [cirq.measure(a, invert_mask=(True,), key="m")],
-            [cirq.X(b).with_classical_controls("m")],
-        ),
-        compare_unitaries=False,
+        before=quick_circuit([cirq.PhasedXPowGate(phase_exponent=0.25).on(a)], [cirq.measure(a)]),
+        expected=quick_circuit([cirq.PhasedXPowGate(phase_exponent=0.25).on(a)], [cirq.measure(a)]),
     )