From e86d92c4ab3fe40b0b96022c0292ebbe443d0451 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Thu, 23 Dec 2021 15:18:24 -0800 Subject: [PATCH] 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. --- cirq-core/cirq/sim/act_on_args.py | 21 ++++++++++++++-- .../cirq/sim/act_on_density_matrix_args.py | 7 +++++- .../cirq/sim/density_matrix_simulator.py | 1 + .../cirq/sim/density_matrix_simulator_test.py | 25 +++++++++++++++++++ cirq-core/cirq/sim/simulator_base.py | 5 ---- 5 files changed, 51 insertions(+), 8 deletions(-) diff --git a/cirq-core/cirq/sim/act_on_args.py b/cirq-core/cirq/sim/act_on_args.py index 5a21502cb8f..672dc35d959 100644 --- a/cirq-core/cirq/sim/act_on_args.py +++ b/cirq-core/cirq/sim/act_on_args.py @@ -29,7 +29,7 @@ import numpy as np -from cirq import protocols +from cirq import protocols, ops from cirq.protocols.decompose_protocol import _try_decompose_into_operations_and_qubits from cirq.sim.operation_target import OperationTarget @@ -47,6 +47,7 @@ def __init__( prng: np.random.RandomState = None, qubits: Sequence['cirq.Qid'] = None, log_of_measurement_results: Dict[str, List[int]] = None, + ignore_measurement_results: bool = False, ): """Inits ActOnArgs. @@ -58,6 +59,10 @@ def __init__( ordering of the computational basis states. log_of_measurement_results: A mutable object that measurements are being recorded into. + ignore_measurement_results: If True, then the simulation + will treat measurement as dephasing instead of collapsing + process, and not log the result. This is only applicable to + simulators that can represent mixed states. """ if prng is None: prng = cast(np.random.RandomState, np.random) @@ -68,13 +73,18 @@ def __init__( self._set_qubits(qubits) self.prng = prng self._log_of_measurement_results = log_of_measurement_results + self._ignore_measurement_results = ignore_measurement_results def _set_qubits(self, qubits: Sequence['cirq.Qid']): self._qubits = tuple(qubits) self.qubit_map = {q: i for i, q in enumerate(self.qubits)} def measure(self, qubits: Sequence['cirq.Qid'], key: str, invert_mask: Sequence[bool]): - """Adds a measurement result to the log. + """Measures the qubits and records to `log_of_measurement_results`. + + Any bitmasks will be applied to the measurement record. If + `self._ignore_measurement_results` is set, it dephases instead of + measuring, and no measurement result will be logged. Args: qubits: The qubits to measure. @@ -86,6 +96,9 @@ def measure(self, qubits: Sequence['cirq.Qid'], key: str, invert_mask: Sequence[ Raises: ValueError: If a measurement key has already been logged to a key. """ + if self.ignore_measurement_results: + self._act_on_fallback_(ops.phase_damp(1), qubits) + return bits = self._perform_measurement(qubits) corrected = [bit ^ (bit < 2 and mask) for bit, mask in zip(bits, invert_mask)] 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 def log_of_measurement_results(self) -> Dict[str, List[int]]: return self._log_of_measurement_results + @property + def ignore_measurement_results(self) -> bool: + return self._ignore_measurement_results + @property def qubits(self) -> Tuple['cirq.Qid', ...]: return self._qubits diff --git a/cirq-core/cirq/sim/act_on_density_matrix_args.py b/cirq-core/cirq/sim/act_on_density_matrix_args.py index e32e558cebf..fe4792553a7 100644 --- a/cirq-core/cirq/sim/act_on_density_matrix_args.py +++ b/cirq-core/cirq/sim/act_on_density_matrix_args.py @@ -41,6 +41,7 @@ def __init__( prng: np.random.RandomState = None, log_of_measurement_results: Dict[str, Any] = None, qubits: Sequence['cirq.Qid'] = None, + ignore_measurement_results: bool = False, ): """Inits ActOnDensityMatrixArgs. @@ -60,8 +61,12 @@ def __init__( effects. log_of_measurement_results: A mutable object that measurements are being recorded into. + ignore_measurement_results: If True, then the simulation + will treat measurement as dephasing instead of collapsing + process. This is only applicable to simulators that can + model dephasing. """ - super().__init__(prng, qubits, log_of_measurement_results) + super().__init__(prng, qubits, log_of_measurement_results, ignore_measurement_results) self.target_tensor = target_tensor self.available_buffer = available_buffer self.qid_shape = qid_shape diff --git a/cirq-core/cirq/sim/density_matrix_simulator.py b/cirq-core/cirq/sim/density_matrix_simulator.py index 70b223e28a2..3b4dc88d9c9 100644 --- a/cirq-core/cirq/sim/density_matrix_simulator.py +++ b/cirq-core/cirq/sim/density_matrix_simulator.py @@ -209,6 +209,7 @@ def _create_partial_act_on_args( qid_shape=qid_shape, prng=self._prng, log_of_measurement_results=logs, + ignore_measurement_results=self._ignore_measurement_results, ) def _can_be_in_run_prefix(self, val: Any): diff --git a/cirq-core/cirq/sim/density_matrix_simulator_test.py b/cirq-core/cirq/sim/density_matrix_simulator_test.py index 3e03cc19a05..a378c132bef 100644 --- a/cirq-core/cirq/sim/density_matrix_simulator_test.py +++ b/cirq-core/cirq/sim/density_matrix_simulator_test.py @@ -514,6 +514,31 @@ def test_simulate(dtype: Type[np.number], split: bool): assert len(result.measurements) == 0 +@pytest.mark.parametrize('split', [True, False]) +def test_simulate_ignore_measurements(split: bool): + q0 = cirq.LineQubit(0) + simulator = cirq.DensityMatrixSimulator( + split_untangled_states=split, ignore_measurement_results=True + ) + circuit = cirq.Circuit(cirq.H(q0), cirq.measure(q0)) + result = simulator.simulate(circuit) + np.testing.assert_almost_equal(result.final_density_matrix, np.eye(2) * 0.5) + assert len(result.measurements) == 0 + + +@pytest.mark.parametrize('split', [True, False]) +def test_simulate_ignore_measurements_subcircuits(split: bool): + q0 = cirq.LineQubit(0) + simulator = cirq.DensityMatrixSimulator( + split_untangled_states=split, ignore_measurement_results=True + ) + circuit = cirq.Circuit(cirq.H(q0), cirq.measure(q0)) + circuit = cirq.Circuit(cirq.CircuitOperation(circuit.freeze())) + result = simulator.simulate(circuit) + np.testing.assert_almost_equal(result.final_density_matrix, np.eye(2) * 0.5) + assert len(result.measurements) == 0 + + @pytest.mark.parametrize('dtype', [np.complex64, np.complex128]) @pytest.mark.parametrize('split', [True, False]) def test_simulate_qudits(dtype: Type[np.number], split: bool): diff --git a/cirq-core/cirq/sim/simulator_base.py b/cirq-core/cirq/sim/simulator_base.py index 1dbd1e8d43e..cf21815caef 100644 --- a/cirq-core/cirq/sim/simulator_base.py +++ b/cirq-core/cirq/sim/simulator_base.py @@ -207,9 +207,6 @@ def _core_iterator( for moment in noisy_moments: for op in ops.flatten_to_ops(moment): try: - # TODO: support more general measurements. - # Github issue: https://github.com/quantumlib/Cirq/issues/3566 - # Preprocess measurements if all_measurements_are_terminal and measured[op.qubits]: continue @@ -217,8 +214,6 @@ def _core_iterator( measured[op.qubits] = True if all_measurements_are_terminal: continue - if self._ignore_measurement_results: - op = ops.phase_damp(1).on(*op.qubits) # Simulate the operation protocols.act_on(op, sim_state)