Skip to content

Commit 9581e65

Browse files
authored
Fix ignore_measurement_results=True for subcircuits (#4760)
Density matrix simulator has a bug that if `ignore_measurement_results` is set, that setting is overlooked within subcircuits. The reason is that this setting check is done in `SimulatorBase.core_iterator`, but that only iterates the outermost circuit. The fix is to move the check into `ActOnArgs.measure`, to ensure that any measurement is replaced with a dephase operation. Surprisingly we did not even have a test for the non-subcircuit case. This PR adds a test for that and the subcircuit case.
1 parent 9aede58 commit 9581e65

5 files changed

+51
-8
lines changed

cirq-core/cirq/sim/act_on_args.py

+19-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
import numpy as np
3131

32-
from cirq import protocols
32+
from cirq import protocols, ops
3333
from cirq.protocols.decompose_protocol import _try_decompose_into_operations_and_qubits
3434
from cirq.sim.operation_target import OperationTarget
3535

@@ -47,6 +47,7 @@ def __init__(
4747
prng: np.random.RandomState = None,
4848
qubits: Sequence['cirq.Qid'] = None,
4949
log_of_measurement_results: Dict[str, List[int]] = None,
50+
ignore_measurement_results: bool = False,
5051
):
5152
"""Inits ActOnArgs.
5253
@@ -58,6 +59,10 @@ def __init__(
5859
ordering of the computational basis states.
5960
log_of_measurement_results: A mutable object that measurements are
6061
being recorded into.
62+
ignore_measurement_results: If True, then the simulation
63+
will treat measurement as dephasing instead of collapsing
64+
process, and not log the result. This is only applicable to
65+
simulators that can represent mixed states.
6166
"""
6267
if prng is None:
6368
prng = cast(np.random.RandomState, np.random)
@@ -68,13 +73,18 @@ def __init__(
6873
self._set_qubits(qubits)
6974
self.prng = prng
7075
self._log_of_measurement_results = log_of_measurement_results
76+
self._ignore_measurement_results = ignore_measurement_results
7177

7278
def _set_qubits(self, qubits: Sequence['cirq.Qid']):
7379
self._qubits = tuple(qubits)
7480
self.qubit_map = {q: i for i, q in enumerate(self.qubits)}
7581

7682
def measure(self, qubits: Sequence['cirq.Qid'], key: str, invert_mask: Sequence[bool]):
77-
"""Adds a measurement result to the log.
83+
"""Measures the qubits and records to `log_of_measurement_results`.
84+
85+
Any bitmasks will be applied to the measurement record. If
86+
`self._ignore_measurement_results` is set, it dephases instead of
87+
measuring, and no measurement result will be logged.
7888
7989
Args:
8090
qubits: The qubits to measure.
@@ -86,6 +96,9 @@ def measure(self, qubits: Sequence['cirq.Qid'], key: str, invert_mask: Sequence[
8696
Raises:
8797
ValueError: If a measurement key has already been logged to a key.
8898
"""
99+
if self.ignore_measurement_results:
100+
self._act_on_fallback_(ops.phase_damp(1), qubits)
101+
return
89102
bits = self._perform_measurement(qubits)
90103
corrected = [bit ^ (bit < 2 and mask) for bit, mask in zip(bits, invert_mask)]
91104
if key in self._log_of_measurement_results:
@@ -184,6 +197,10 @@ def _on_transpose_to_qubit_order(self: TSelf, qubits: Sequence['cirq.Qid'], targ
184197
def log_of_measurement_results(self) -> Dict[str, List[int]]:
185198
return self._log_of_measurement_results
186199

200+
@property
201+
def ignore_measurement_results(self) -> bool:
202+
return self._ignore_measurement_results
203+
187204
@property
188205
def qubits(self) -> Tuple['cirq.Qid', ...]:
189206
return self._qubits

cirq-core/cirq/sim/act_on_density_matrix_args.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def __init__(
4141
prng: np.random.RandomState = None,
4242
log_of_measurement_results: Dict[str, Any] = None,
4343
qubits: Sequence['cirq.Qid'] = None,
44+
ignore_measurement_results: bool = False,
4445
):
4546
"""Inits ActOnDensityMatrixArgs.
4647
@@ -60,8 +61,12 @@ def __init__(
6061
effects.
6162
log_of_measurement_results: A mutable object that measurements are
6263
being recorded into.
64+
ignore_measurement_results: If True, then the simulation
65+
will treat measurement as dephasing instead of collapsing
66+
process. This is only applicable to simulators that can
67+
model dephasing.
6368
"""
64-
super().__init__(prng, qubits, log_of_measurement_results)
69+
super().__init__(prng, qubits, log_of_measurement_results, ignore_measurement_results)
6570
self.target_tensor = target_tensor
6671
self.available_buffer = available_buffer
6772
self.qid_shape = qid_shape

cirq-core/cirq/sim/density_matrix_simulator.py

+1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ def _create_partial_act_on_args(
209209
qid_shape=qid_shape,
210210
prng=self._prng,
211211
log_of_measurement_results=logs,
212+
ignore_measurement_results=self._ignore_measurement_results,
212213
)
213214

214215
def _can_be_in_run_prefix(self, val: Any):

cirq-core/cirq/sim/density_matrix_simulator_test.py

+25
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,31 @@ def test_simulate(dtype: Type[np.number], split: bool):
514514
assert len(result.measurements) == 0
515515

516516

517+
@pytest.mark.parametrize('split', [True, False])
518+
def test_simulate_ignore_measurements(split: bool):
519+
q0 = cirq.LineQubit(0)
520+
simulator = cirq.DensityMatrixSimulator(
521+
split_untangled_states=split, ignore_measurement_results=True
522+
)
523+
circuit = cirq.Circuit(cirq.H(q0), cirq.measure(q0))
524+
result = simulator.simulate(circuit)
525+
np.testing.assert_almost_equal(result.final_density_matrix, np.eye(2) * 0.5)
526+
assert len(result.measurements) == 0
527+
528+
529+
@pytest.mark.parametrize('split', [True, False])
530+
def test_simulate_ignore_measurements_subcircuits(split: bool):
531+
q0 = cirq.LineQubit(0)
532+
simulator = cirq.DensityMatrixSimulator(
533+
split_untangled_states=split, ignore_measurement_results=True
534+
)
535+
circuit = cirq.Circuit(cirq.H(q0), cirq.measure(q0))
536+
circuit = cirq.Circuit(cirq.CircuitOperation(circuit.freeze()))
537+
result = simulator.simulate(circuit)
538+
np.testing.assert_almost_equal(result.final_density_matrix, np.eye(2) * 0.5)
539+
assert len(result.measurements) == 0
540+
541+
517542
@pytest.mark.parametrize('dtype', [np.complex64, np.complex128])
518543
@pytest.mark.parametrize('split', [True, False])
519544
def test_simulate_qudits(dtype: Type[np.number], split: bool):

cirq-core/cirq/sim/simulator_base.py

-5
Original file line numberDiff line numberDiff line change
@@ -207,18 +207,13 @@ def _core_iterator(
207207
for moment in noisy_moments:
208208
for op in ops.flatten_to_ops(moment):
209209
try:
210-
# TODO: support more general measurements.
211-
# Github issue: https://github.com/quantumlib/Cirq/issues/3566
212-
213210
# Preprocess measurements
214211
if all_measurements_are_terminal and measured[op.qubits]:
215212
continue
216213
if isinstance(op.gate, ops.MeasurementGate):
217214
measured[op.qubits] = True
218215
if all_measurements_are_terminal:
219216
continue
220-
if self._ignore_measurement_results:
221-
op = ops.phase_damp(1).on(*op.qubits)
222217

223218
# Simulate the operation
224219
protocols.act_on(op, sim_state)

0 commit comments

Comments
 (0)