We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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)
```



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `cirq-core/cirq/sim/act_on_args_container.py`
Content:
```
1 # Copyright 2021 The Cirq Developers
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     https://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 from collections import abc
16 from typing import (
17     Dict,
18     TYPE_CHECKING,
19     Generic,
20     Sequence,
21     Optional,
22     Iterator,
23     Any,
24     Tuple,
25     List,
26     Union,
27 )
28 
29 import numpy as np
30 
31 from cirq import ops, protocols
32 from cirq.sim.operation_target import OperationTarget
33 from cirq.sim.simulator import (
34     TActOnArgs,
35 )
36 
37 if TYPE_CHECKING:
38     import cirq
39 
40 
41 class ActOnArgsContainer(
42     Generic[TActOnArgs],
43     OperationTarget[TActOnArgs],
44     abc.Mapping,
45 ):
46     """A container for a `Qid`-to-`ActOnArgs` dictionary."""
47 
48     def __init__(
49         self,
50         args: Dict[Optional['cirq.Qid'], TActOnArgs],
51         qubits: Sequence['cirq.Qid'],
52         split_untangled_states: bool,
53         log_of_measurement_results: Dict[str, Any],
54     ):
55         """Initializes the class.
56 
57         Args:
58             args: The `ActOnArgs` dictionary. This will not be copied; the
59                 original reference will be kept here.
60             qubits: The canonical ordering of qubits.
61             split_untangled_states: If True, optimizes operations by running
62                 unentangled qubit sets independently and merging those states
63                 at the end.
64             log_of_measurement_results: A mutable object that measurements are
65                 being recorded into.
66         """
67         self.args = args
68         self._qubits = tuple(qubits)
69         self.split_untangled_states = split_untangled_states
70         self._log_of_measurement_results = log_of_measurement_results
71 
72     def create_merged_state(self) -> TActOnArgs:
73         if not self.split_untangled_states:
74             return self.args[None]
75         final_args = self.args[None]
76         for args in set([self.args[k] for k in self.args.keys() if k is not None]):
77             final_args = final_args.kronecker_product(args)
78         return final_args.transpose_to_qubit_order(self.qubits)
79 
80     def _act_on_fallback_(
81         self,
82         action: Union['cirq.Operation', 'cirq.Gate'],
83         qubits: Sequence['cirq.Qid'],
84         allow_decompose: bool = True,
85     ) -> bool:
86         gate = action.gate if isinstance(action, ops.Operation) else action
87 
88         if isinstance(gate, ops.IdentityGate):
89             return True
90 
91         if isinstance(gate, ops.SwapPowGate) and gate.exponent % 2 == 1 and gate.global_shift == 0:
92             q0, q1 = qubits
93             args0 = self.args[q0]
94             args1 = self.args[q1]
95             if args0 is args1:
96                 args0.swap(q0, q1, inplace=True)
97             else:
98                 self.args[q0] = args1.rename(q1, q0, inplace=True)
99                 self.args[q1] = args0.rename(q0, q1, inplace=True)
100             return True
101 
102         # Go through the op's qubits and join any disparate ActOnArgs states
103         # into a new combined state.
104         op_args_opt: Optional[TActOnArgs] = None
105         for q in qubits:
106             if op_args_opt is None:
107                 op_args_opt = self.args[q]
108             elif q not in op_args_opt.qubits:
109                 op_args_opt = op_args_opt.kronecker_product(self.args[q])
110         op_args = op_args_opt or self.args[None]
111 
112         # (Backfill the args map with the new value)
113         for q in op_args.qubits:
114             self.args[q] = op_args
115 
116         # Act on the args with the operation
117         act_on_qubits = qubits if isinstance(action, ops.Gate) else None
118         protocols.act_on(action, op_args, act_on_qubits, allow_decompose=allow_decompose)
119 
120         # Decouple any measurements or resets
121         if self.split_untangled_states and isinstance(
122             gate, (ops.MeasurementGate, ops.ResetChannel)
123         ):
124             for q in qubits:
125                 q_args, op_args = op_args.factor((q,), validate=False)
126                 self.args[q] = q_args
127 
128             # (Backfill the args map with the new value)
129             for q in op_args.qubits:
130                 self.args[q] = op_args
131         return True
132 
133     def copy(self) -> 'cirq.ActOnArgsContainer[TActOnArgs]':
134         logs = self.log_of_measurement_results.copy()
135         copies = {a: a.copy() for a in set(self.args.values())}
136         for copy in copies.values():
137             copy._log_of_measurement_results = logs
138         args = {q: copies[a] for q, a in self.args.items()}
139         return ActOnArgsContainer(args, self.qubits, self.split_untangled_states, logs)
140 
141     @property
142     def qubits(self) -> Tuple['cirq.Qid', ...]:
143         return self._qubits
144 
145     @property
146     def log_of_measurement_results(self) -> Dict[str, Any]:
147         return self._log_of_measurement_results
148 
149     def sample(
150         self,
151         qubits: List['cirq.Qid'],
152         repetitions: int = 1,
153         seed: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None,
154     ) -> np.ndarray:
155         columns = []
156         selected_order: List[ops.Qid] = []
157         q_set = set(qubits)
158         for v in dict.fromkeys(self.args.values()):
159             qs = [q for q in v.qubits if q in q_set]
160             if any(qs):
161                 column = v.sample(qs, repetitions, seed)
162                 columns.append(column)
163                 selected_order += qs
164         stacked = np.column_stack(columns)
165         qubit_map = {q: i for i, q in enumerate(selected_order)}
166         index_order = [qubit_map[q] for q in qubits]
167         return stacked[:, index_order]
168 
169     def __getitem__(self, item: Optional['cirq.Qid']) -> TActOnArgs:
170         return self.args[item]
171 
172     def __len__(self) -> int:
173         return len(self.args)
174 
175     def __iter__(self) -> Iterator[Optional['cirq.Qid']]:
176         return iter(self.args)
177
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
         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)

{"golden_diff": "diff --git a/cirq-core/cirq/sim/act_on_args_container.py b/cirq-core/cirq/sim/act_on_args_container.py\n--- a/cirq-core/cirq/sim/act_on_args_container.py\n+++ b/cirq-core/cirq/sim/act_on_args_container.py\n@@ -118,8 +118,9 @@\n         protocols.act_on(action, op_args, act_on_qubits, allow_decompose=allow_decompose)\n \n         # Decouple any measurements or resets\n-        if self.split_untangled_states and isinstance(\n-            gate, (ops.MeasurementGate, ops.ResetChannel)\n+        if self.split_untangled_states and (\n+            isinstance(gate, ops.ResetChannel)\n+            or (isinstance(gate, ops.MeasurementGate) and not op_args.ignore_measurement_results)\n         ):\n             for q in qubits:\n                 q_args, op_args = op_args.factor((q,), validate=False)\n", "issue": "Substates are separated after measurements, when measurements are ignored\n**Description of the issue**\r\n\r\nDensity 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.\r\n\r\nFor 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.\r\n\r\n\r\n**How to reproduce the issue**\r\n\r\n```python\r\ndef test_ignore_measurements_remains_entangled():\r\n    q0, q1 = cirq.LineQubit.range(2)\r\n    simulator1 = cirq.DensityMatrixSimulator(\r\n        ignore_measurement_results=True, split_untangled_states=False\r\n    )\r\n    simulator2 = cirq.DensityMatrixSimulator(\r\n        ignore_measurement_results=True, split_untangled_states=True\r\n    )\r\n    circuit = cirq.Circuit(\r\n        cirq.H(q0),\r\n        cirq.CX(q0, q1),\r\n        cirq.measure(q0),\r\n    )\r\n    result1 = simulator1.simulate(circuit)\r\n    result2 = simulator2.simulate(circuit)\r\n    np.testing.assert_almost_equal(result2.final_density_matrix, result1.final_density_matrix)\r\n```\r\n\r\n\n", "before_files": [{"content": "# Copyright 2021 The Cirq Developers\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom collections import abc\nfrom typing import (\n    Dict,\n    TYPE_CHECKING,\n    Generic,\n    Sequence,\n    Optional,\n    Iterator,\n    Any,\n    Tuple,\n    List,\n    Union,\n)\n\nimport numpy as np\n\nfrom cirq import ops, protocols\nfrom cirq.sim.operation_target import OperationTarget\nfrom cirq.sim.simulator import (\n    TActOnArgs,\n)\n\nif TYPE_CHECKING:\n    import cirq\n\n\nclass ActOnArgsContainer(\n    Generic[TActOnArgs],\n    OperationTarget[TActOnArgs],\n    abc.Mapping,\n):\n    \"\"\"A container for a `Qid`-to-`ActOnArgs` dictionary.\"\"\"\n\n    def __init__(\n        self,\n        args: Dict[Optional['cirq.Qid'], TActOnArgs],\n        qubits: Sequence['cirq.Qid'],\n        split_untangled_states: bool,\n        log_of_measurement_results: Dict[str, Any],\n    ):\n        \"\"\"Initializes the class.\n\n        Args:\n            args: The `ActOnArgs` dictionary. This will not be copied; the\n                original reference will be kept here.\n            qubits: The canonical ordering of qubits.\n            split_untangled_states: If True, optimizes operations by running\n                unentangled qubit sets independently and merging those states\n                at the end.\n            log_of_measurement_results: A mutable object that measurements are\n                being recorded into.\n        \"\"\"\n        self.args = args\n        self._qubits = tuple(qubits)\n        self.split_untangled_states = split_untangled_states\n        self._log_of_measurement_results = log_of_measurement_results\n\n    def create_merged_state(self) -> TActOnArgs:\n        if not self.split_untangled_states:\n            return self.args[None]\n        final_args = self.args[None]\n        for args in set([self.args[k] for k in self.args.keys() if k is not None]):\n            final_args = final_args.kronecker_product(args)\n        return final_args.transpose_to_qubit_order(self.qubits)\n\n    def _act_on_fallback_(\n        self,\n        action: Union['cirq.Operation', 'cirq.Gate'],\n        qubits: Sequence['cirq.Qid'],\n        allow_decompose: bool = True,\n    ) -> bool:\n        gate = action.gate if isinstance(action, ops.Operation) else action\n\n        if isinstance(gate, ops.IdentityGate):\n            return True\n\n        if isinstance(gate, ops.SwapPowGate) and gate.exponent % 2 == 1 and gate.global_shift == 0:\n            q0, q1 = qubits\n            args0 = self.args[q0]\n            args1 = self.args[q1]\n            if args0 is args1:\n                args0.swap(q0, q1, inplace=True)\n            else:\n                self.args[q0] = args1.rename(q1, q0, inplace=True)\n                self.args[q1] = args0.rename(q0, q1, inplace=True)\n            return True\n\n        # Go through the op's qubits and join any disparate ActOnArgs states\n        # into a new combined state.\n        op_args_opt: Optional[TActOnArgs] = None\n        for q in qubits:\n            if op_args_opt is None:\n                op_args_opt = self.args[q]\n            elif q not in op_args_opt.qubits:\n                op_args_opt = op_args_opt.kronecker_product(self.args[q])\n        op_args = op_args_opt or self.args[None]\n\n        # (Backfill the args map with the new value)\n        for q in op_args.qubits:\n            self.args[q] = op_args\n\n        # Act on the args with the operation\n        act_on_qubits = qubits if isinstance(action, ops.Gate) else None\n        protocols.act_on(action, op_args, act_on_qubits, allow_decompose=allow_decompose)\n\n        # Decouple any measurements or resets\n        if self.split_untangled_states and isinstance(\n            gate, (ops.MeasurementGate, ops.ResetChannel)\n        ):\n            for q in qubits:\n                q_args, op_args = op_args.factor((q,), validate=False)\n                self.args[q] = q_args\n\n            # (Backfill the args map with the new value)\n            for q in op_args.qubits:\n                self.args[q] = op_args\n        return True\n\n    def copy(self) -> 'cirq.ActOnArgsContainer[TActOnArgs]':\n        logs = self.log_of_measurement_results.copy()\n        copies = {a: a.copy() for a in set(self.args.values())}\n        for copy in copies.values():\n            copy._log_of_measurement_results = logs\n        args = {q: copies[a] for q, a in self.args.items()}\n        return ActOnArgsContainer(args, self.qubits, self.split_untangled_states, logs)\n\n    @property\n    def qubits(self) -> Tuple['cirq.Qid', ...]:\n        return self._qubits\n\n    @property\n    def log_of_measurement_results(self) -> Dict[str, Any]:\n        return self._log_of_measurement_results\n\n    def sample(\n        self,\n        qubits: List['cirq.Qid'],\n        repetitions: int = 1,\n        seed: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None,\n    ) -> np.ndarray:\n        columns = []\n        selected_order: List[ops.Qid] = []\n        q_set = set(qubits)\n        for v in dict.fromkeys(self.args.values()):\n            qs = [q for q in v.qubits if q in q_set]\n            if any(qs):\n                column = v.sample(qs, repetitions, seed)\n                columns.append(column)\n                selected_order += qs\n        stacked = np.column_stack(columns)\n        qubit_map = {q: i for i, q in enumerate(selected_order)}\n        index_order = [qubit_map[q] for q in qubits]\n        return stacked[:, index_order]\n\n    def __getitem__(self, item: Optional['cirq.Qid']) -> TActOnArgs:\n        return self.args[item]\n\n    def __len__(self) -> int:\n        return len(self.args)\n\n    def __iter__(self) -> Iterator[Optional['cirq.Qid']]:\n        return iter(self.args)\n", "path": "cirq-core/cirq/sim/act_on_args_container.py"}], "after_files": [{"content": "# Copyright 2021 The Cirq Developers\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom collections import abc\nfrom typing import (\n    Dict,\n    TYPE_CHECKING,\n    Generic,\n    Sequence,\n    Optional,\n    Iterator,\n    Any,\n    Tuple,\n    List,\n    Union,\n)\n\nimport numpy as np\n\nfrom cirq import ops, protocols\nfrom cirq.sim.operation_target import OperationTarget\nfrom cirq.sim.simulator import (\n    TActOnArgs,\n)\n\nif TYPE_CHECKING:\n    import cirq\n\n\nclass ActOnArgsContainer(\n    Generic[TActOnArgs],\n    OperationTarget[TActOnArgs],\n    abc.Mapping,\n):\n    \"\"\"A container for a `Qid`-to-`ActOnArgs` dictionary.\"\"\"\n\n    def __init__(\n        self,\n        args: Dict[Optional['cirq.Qid'], TActOnArgs],\n        qubits: Sequence['cirq.Qid'],\n        split_untangled_states: bool,\n        log_of_measurement_results: Dict[str, Any],\n    ):\n        \"\"\"Initializes the class.\n\n        Args:\n            args: The `ActOnArgs` dictionary. This will not be copied; the\n                original reference will be kept here.\n            qubits: The canonical ordering of qubits.\n            split_untangled_states: If True, optimizes operations by running\n                unentangled qubit sets independently and merging those states\n                at the end.\n            log_of_measurement_results: A mutable object that measurements are\n                being recorded into.\n        \"\"\"\n        self.args = args\n        self._qubits = tuple(qubits)\n        self.split_untangled_states = split_untangled_states\n        self._log_of_measurement_results = log_of_measurement_results\n\n    def create_merged_state(self) -> TActOnArgs:\n        if not self.split_untangled_states:\n            return self.args[None]\n        final_args = self.args[None]\n        for args in set([self.args[k] for k in self.args.keys() if k is not None]):\n            final_args = final_args.kronecker_product(args)\n        return final_args.transpose_to_qubit_order(self.qubits)\n\n    def _act_on_fallback_(\n        self,\n        action: Union['cirq.Operation', 'cirq.Gate'],\n        qubits: Sequence['cirq.Qid'],\n        allow_decompose: bool = True,\n    ) -> bool:\n        gate = action.gate if isinstance(action, ops.Operation) else action\n\n        if isinstance(gate, ops.IdentityGate):\n            return True\n\n        if isinstance(gate, ops.SwapPowGate) and gate.exponent % 2 == 1 and gate.global_shift == 0:\n            q0, q1 = qubits\n            args0 = self.args[q0]\n            args1 = self.args[q1]\n            if args0 is args1:\n                args0.swap(q0, q1, inplace=True)\n            else:\n                self.args[q0] = args1.rename(q1, q0, inplace=True)\n                self.args[q1] = args0.rename(q0, q1, inplace=True)\n            return True\n\n        # Go through the op's qubits and join any disparate ActOnArgs states\n        # into a new combined state.\n        op_args_opt: Optional[TActOnArgs] = None\n        for q in qubits:\n            if op_args_opt is None:\n                op_args_opt = self.args[q]\n            elif q not in op_args_opt.qubits:\n                op_args_opt = op_args_opt.kronecker_product(self.args[q])\n        op_args = op_args_opt or self.args[None]\n\n        # (Backfill the args map with the new value)\n        for q in op_args.qubits:\n            self.args[q] = op_args\n\n        # Act on the args with the operation\n        act_on_qubits = qubits if isinstance(action, ops.Gate) else None\n        protocols.act_on(action, op_args, act_on_qubits, allow_decompose=allow_decompose)\n\n        # Decouple any measurements or resets\n        if self.split_untangled_states and (\n            isinstance(gate, ops.ResetChannel)\n            or (isinstance(gate, ops.MeasurementGate) and not op_args.ignore_measurement_results)\n        ):\n            for q in qubits:\n                q_args, op_args = op_args.factor((q,), validate=False)\n                self.args[q] = q_args\n\n            # (Backfill the args map with the new value)\n            for q in op_args.qubits:\n                self.args[q] = op_args\n        return True\n\n    def copy(self) -> 'cirq.ActOnArgsContainer[TActOnArgs]':\n        logs = self.log_of_measurement_results.copy()\n        copies = {a: a.copy() for a in set(self.args.values())}\n        for copy in copies.values():\n            copy._log_of_measurement_results = logs\n        args = {q: copies[a] for q, a in self.args.items()}\n        return ActOnArgsContainer(args, self.qubits, self.split_untangled_states, logs)\n\n    @property\n    def qubits(self) -> Tuple['cirq.Qid', ...]:\n        return self._qubits\n\n    @property\n    def log_of_measurement_results(self) -> Dict[str, Any]:\n        return self._log_of_measurement_results\n\n    def sample(\n        self,\n        qubits: List['cirq.Qid'],\n        repetitions: int = 1,\n        seed: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None,\n    ) -> np.ndarray:\n        columns = []\n        selected_order: List[ops.Qid] = []\n        q_set = set(qubits)\n        for v in dict.fromkeys(self.args.values()):\n            qs = [q for q in v.qubits if q in q_set]\n            if any(qs):\n                column = v.sample(qs, repetitions, seed)\n                columns.append(column)\n                selected_order += qs\n        stacked = np.column_stack(columns)\n        qubit_map = {q: i for i, q in enumerate(selected_order)}\n        index_order = [qubit_map[q] for q in qubits]\n        return stacked[:, index_order]\n\n    def __getitem__(self, item: Optional['cirq.Qid']) -> TActOnArgs:\n        return self.args[item]\n\n    def __len__(self) -> int:\n        return len(self.args)\n\n    def __iter__(self) -> Iterator[Optional['cirq.Qid']]:\n        return iter(self.args)\n", "path": "cirq-core/cirq/sim/act_on_args_container.py"}]}