Repository: quantumlib/Cirq

Issue:
Substates are separated after measurements, when measurements are ignored
**Description of the issue**

Density matrix separates qubit states after measurement when split_untanged_states=True. However when ignore_measurement_results=True, this should not happen, as the this changes the measurement into a dephase and does not make the state separable.

For instance, when measuring a Bell state, the resulting DM should be 0.5 |00> + 0.5 |11>. However, separating those states (partial tracing each qubit) and re-kronning them gives 0.25 of each; i.e. it causes each qubit to be 0.5 |0> and 0.5 |1> independently. Therefore we need to avoid separating states after measurements if ignore_measurement_results=True.


**How to reproduce the issue**

```python
def test_ignore_measurements_remains_entangled():
    q0, q1 = cirq.LineQubit.range(2)
    simulator1 = cirq.DensityMatrixSimulator(
        ignore_measurement_results=True, split_untangled_states=False
    )
    simulator2 = cirq.DensityMatrixSimulator(
        ignore_measurement_results=True, split_untangled_states=True
    )
    circuit = cirq.Circuit(
        cirq.H(q0),
        cirq.CX(q0, q1),
        cirq.measure(q0),
    )
    result1 = simulator1.simulate(circuit)
    result2 = simulator2.simulate(circuit)
    np.testing.assert_almost_equal(result2.final_density_matrix, result1.final_density_matrix)
```




Discussion:

Resolving patch:
diff --git a/cirq-core/cirq/sim/act_on_args_container.py b/cirq-core/cirq/sim/act_on_args_container.py
--- a/cirq-core/cirq/sim/act_on_args_container.py
+++ b/cirq-core/cirq/sim/act_on_args_container.py
@@ -118,8 +118,9 @@ def _act_on_fallback_(
         protocols.act_on(action, op_args, act_on_qubits, allow_decompose=allow_decompose)
 
         # Decouple any measurements or resets
-        if self.split_untangled_states and isinstance(
-            gate, (ops.MeasurementGate, ops.ResetChannel)
+        if self.split_untangled_states and (
+            isinstance(gate, ops.ResetChannel)
+            or (isinstance(gate, ops.MeasurementGate) and not op_args.ignore_measurement_results)
         ):
             for q in qubits:
                 q_args, op_args = op_args.factor((q,), validate=False)


Test patch:
diff --git a/cirq-core/cirq/sim/density_matrix_simulator_test.py b/cirq-core/cirq/sim/density_matrix_simulator_test.py
--- a/cirq-core/cirq/sim/density_matrix_simulator_test.py
+++ b/cirq-core/cirq/sim/density_matrix_simulator_test.py
@@ -571,6 +571,28 @@ def test_reset_one_qubit_does_not_affect_partial_trace_of_other_qubits(
     np.testing.assert_almost_equal(result.final_density_matrix, expected)
 
 
+def test_ignore_measurements_remains_entangled():
+    q0, q1 = cirq.LineQubit.range(2)
+    simulator1 = cirq.DensityMatrixSimulator(
+        ignore_measurement_results=True, split_untangled_states=False
+    )
+    simulator2 = cirq.DensityMatrixSimulator(
+        ignore_measurement_results=True, split_untangled_states=True
+    )
+    circuit = cirq.Circuit(
+        cirq.H(q0),
+        cirq.CX(q0, q1),
+        cirq.measure(q0),
+    )
+    result1 = simulator1.simulate(circuit)
+    result2 = simulator2.simulate(circuit)
+    np.testing.assert_almost_equal(result2.final_density_matrix, result1.final_density_matrix)
+    expected = np.zeros((4, 4))
+    expected[0, 0] = 0.5
+    expected[3, 3] = 0.5
+    np.testing.assert_almost_equal(result2.final_density_matrix, expected)
+
+
 @pytest.mark.parametrize(
     'dtype,circuit',
     itertools.product(