Skip to content

Commit

Permalink
Fix ignore_measurement_results=True for subcircuits (quantumlib#4760)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
daxfohl authored and rht committed May 1, 2023
1 parent a5b1524 commit e86d92c
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 8 deletions.
21 changes: 19 additions & 2 deletions cirq-core/cirq/sim/act_on_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.
Expand All @@ -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)
Expand All @@ -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.
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion cirq-core/cirq/sim/act_on_density_matrix_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions cirq-core/cirq/sim/density_matrix_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
25 changes: 25 additions & 0 deletions cirq-core/cirq/sim/density_matrix_simulator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
5 changes: 0 additions & 5 deletions cirq-core/cirq/sim/simulator_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,18 +207,13 @@ 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
if isinstance(op.gate, ops.MeasurementGate):
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)
Expand Down

0 comments on commit e86d92c

Please sign in to comment.