diff --git a/cirq/ops/common_gates_test.py b/cirq/ops/common_gates_test.py index f1d4950e874..e6deab595ed 100644 --- a/cirq/ops/common_gates_test.py +++ b/cirq/ops/common_gates_test.py @@ -575,7 +575,12 @@ def test_act_on_ch_form(input_gate_sequence, outcome): else: assert num_qubits == 2 axes = [0, 1] - args = cirq.ActOnStabilizerCHFormArgs(state=original_state.copy(), axes=axes) + args = cirq.ActOnStabilizerCHFormArgs( + state=original_state.copy(), + axes=axes, + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) flipped_state = cirq.StabilizerStateChForm(num_qubits=5, initial_state=23) diff --git a/cirq/ops/global_phase_op_test.py b/cirq/ops/global_phase_op_test.py index dd310c6252e..9468c6c0151 100644 --- a/cirq/ops/global_phase_op_test.py +++ b/cirq/ops/global_phase_op_test.py @@ -50,7 +50,12 @@ def test_act_on_tableau(phase): @pytest.mark.parametrize('phase', [1, 1j, -1]) def test_act_on_ch_form(phase): state = cirq.StabilizerStateChForm(0) - args = cirq.ActOnStabilizerCHFormArgs(state, []) + args = cirq.ActOnStabilizerCHFormArgs( + state, + [], + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) cirq.act_on(cirq.GlobalPhaseOperation(phase), args, allow_decompose=False) assert state.state_vector() == [[phase]] diff --git a/cirq/ops/measurement_gate.py b/cirq/ops/measurement_gate.py index f553fdd7dff..a8dfc29293e 100644 --- a/cirq/ops/measurement_gate.py +++ b/cirq/ops/measurement_gate.py @@ -242,6 +242,13 @@ def _act_on_(self, args: Any) -> bool: args.record_measurement_result(self.key, corrected) return True + if isinstance(args, sim.clifford.ActOnStabilizerCHFormArgs): + invert_mask = self.full_invert_mask() + bits = [args.state._measure(q, args.prng) for q in args.axes] + corrected = [bit ^ (bit < 2 and mask) for bit, mask in zip(bits, invert_mask)] + args.record_measurement_result(self.key, corrected) + return True + return NotImplemented diff --git a/cirq/ops/measurement_gate_test.py b/cirq/ops/measurement_gate_test.py index 223979e6074..a3c7fda2d5d 100644 --- a/cirq/ops/measurement_gate_test.py +++ b/cirq/ops/measurement_gate_test.py @@ -344,6 +344,47 @@ def test_act_on_clifford_tableau(): cirq.act_on(m, args) +def test_act_on_stabilizer_ch_form(): + a, b = cirq.LineQubit.range(2) + m = cirq.measure(a, b, key='out', invert_mask=(True,)) + # The below assertion does not fail since it ignores non-unitary operations + cirq.testing.assert_all_implemented_act_on_effects_match_unitary(m) + + with pytest.raises(TypeError, match="Failed to act"): + cirq.act_on(m, object()) + + args = cirq.ActOnStabilizerCHFormArgs( + state=cirq.StabilizerStateChForm(num_qubits=5, initial_state=0), + axes=[3, 1], + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) + cirq.act_on(m, args) + assert args.log_of_measurement_results == {'out': [1, 0]} + + args = cirq.ActOnStabilizerCHFormArgs( + state=cirq.StabilizerStateChForm(num_qubits=5, initial_state=8), + axes=[3, 1], + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) + + cirq.act_on(m, args) + assert args.log_of_measurement_results == {'out': [1, 1]} + + args = cirq.ActOnStabilizerCHFormArgs( + state=cirq.StabilizerStateChForm(num_qubits=5, initial_state=10), + axes=[3, 1], + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) + cirq.act_on(m, args) + assert args.log_of_measurement_results == {'out': [0, 1]} + + with pytest.raises(ValueError, match="already logged to key"): + cirq.act_on(m, args) + + def test_act_on_qutrit(): a, b = cirq.LineQid.range(2, dimension=3) m = cirq.measure(a, b, key='out', invert_mask=(True,)) diff --git a/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index 450a3743970..8d9ec2a868b 100644 --- a/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Iterable, TYPE_CHECKING +from typing import Any, Dict, Iterable, TYPE_CHECKING import numpy as np @@ -31,19 +31,43 @@ class ActOnStabilizerCHFormArgs: To act on this object, directly edit the `state` property, which is storing the stabilizer state of the quantum system with one axis per qubit. - Measurements are currently not supported on this object. """ - def __init__(self, state: StabilizerStateChForm, axes: Iterable[int]): + def __init__( + self, + state: StabilizerStateChForm, + axes: Iterable[int], + prng: np.random.RandomState, + log_of_measurement_results: Dict[str, Any], + ): """Initializes with the given state and the axes for the operation. Args: state: The StabilizerStateChForm to act on. Operations are expected to perform inplace edits of this object. axes: The indices of axes corresponding to the qubits that the operation is supposed to act upon. + prng: The pseudo random number generator to use for probabilistic + effects. + log_of_measurement_results: A mutable object that measurements are + being recorded into. Edit it easily by calling + `ActOnStabilizerCHFormArgs.record_measurement_result`. """ self.state = state self.axes = tuple(axes) + self.prng = prng + self.log_of_measurement_results = log_of_measurement_results + + def record_measurement_result(self, key: str, value: Any): + """Adds a measurement result to the log. + Args: + key: The key the measurement result should be logged under. Note + that operations should only store results under keys they have + declared in a `_measurement_keys_` method. + value: The value to log for the measurement. + """ + if key in self.log_of_measurement_results: + raise ValueError(f"Measurement already logged to key {key!r}") + self.log_of_measurement_results[key] = value def _act_on_fallback_(self, action: Any, allow_decompose: bool): strats = [] diff --git a/cirq/sim/clifford/act_on_stabilizer_ch_form_args_test.py b/cirq/sim/clifford/act_on_stabilizer_ch_form_args_test.py index f85c5840aa0..8f3963cd4ce 100644 --- a/cirq/sim/clifford/act_on_stabilizer_ch_form_args_test.py +++ b/cirq/sim/clifford/act_on_stabilizer_ch_form_args_test.py @@ -22,7 +22,12 @@ def test_cannot_act(): class NoDetails(cirq.SingleQubitGate): pass - args = cirq.ActOnStabilizerCHFormArgs(state=cirq.StabilizerStateChForm(num_qubits=3), axes=[1]) + args = cirq.ActOnStabilizerCHFormArgs( + state=cirq.StabilizerStateChForm(num_qubits=3), + axes=[1], + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) with pytest.raises(TypeError, match="Failed to act"): cirq.act_on(NoDetails(), args) @@ -37,7 +42,12 @@ def _act_on_(self, args): return True state = cirq.StabilizerStateChForm(num_qubits=3) - args = cirq.ActOnStabilizerCHFormArgs(state=state, axes=[1]) + args = cirq.ActOnStabilizerCHFormArgs( + state=state, + axes=[1], + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) cirq.act_on(CustomGate(), args) @@ -54,9 +64,19 @@ def _unitary_(self): original_state = cirq.StabilizerStateChForm(num_qubits=3) - args = cirq.ActOnStabilizerCHFormArgs(state=original_state.copy(), axes=[1]) + args = cirq.ActOnStabilizerCHFormArgs( + state=original_state.copy(), + axes=[1], + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) cirq.act_on(UnitaryYGate(), args) - expected_args = cirq.ActOnStabilizerCHFormArgs(state=original_state.copy(), axes=[1]) + expected_args = cirq.ActOnStabilizerCHFormArgs( + state=original_state.copy(), + axes=[1], + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) cirq.act_on(cirq.Y, expected_args) np.testing.assert_allclose(args.state.state_vector(), expected_args.state.state_vector()) @@ -71,8 +91,18 @@ def _unitary_(self): original_state = cirq.StabilizerStateChForm(num_qubits=3) - args = cirq.ActOnStabilizerCHFormArgs(state=original_state.copy(), axes=[1]) + args = cirq.ActOnStabilizerCHFormArgs( + state=original_state.copy(), + axes=[1], + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) cirq.act_on(UnitaryHGate(), args) - expected_args = cirq.ActOnStabilizerCHFormArgs(state=original_state.copy(), axes=[1]) + expected_args = cirq.ActOnStabilizerCHFormArgs( + state=original_state.copy(), + axes=[1], + prng=np.random.RandomState(), + log_of_measurement_results={}, + ) cirq.act_on(cirq.H, expected_args) np.testing.assert_allclose(args.state.state_vector(), expected_args.state.state_vector()) diff --git a/cirq/sim/clifford/clifford_simulator.py b/cirq/sim/clifford/clifford_simulator.py index f38c0e6d179..549a77c8edb 100644 --- a/cirq/sim/clifford/clifford_simulator.py +++ b/cirq/sim/clifford/clifford_simulator.py @@ -335,7 +335,7 @@ def apply_unitary(self, op: 'cirq.Operation'): self.tableau, [self.qubit_map[i] for i in op.qubits], np.random.RandomState(), {} ) ch_form_args = clifford.ActOnStabilizerCHFormArgs( - self.ch_form, [self.qubit_map[i] for i in op.qubits] + self.ch_form, [self.qubit_map[i] for i in op.qubits], np.random.RandomState(), {} ) try: act_on(op, tableau_args) diff --git a/cirq/sim/clifford/stabilizer_state_ch_form.py b/cirq/sim/clifford/stabilizer_state_ch_form.py index 7e1b8ba8bcd..219dedf5a02 100644 --- a/cirq/sim/clifford/stabilizer_state_ch_form.py +++ b/cirq/sim/clifford/stabilizer_state_ch_form.py @@ -61,7 +61,10 @@ def __init__(self, num_qubits: int, initial_state: int = 0) -> None: big_endian_int_to_digits(initial_state, digit_count=num_qubits, base=2) ): if val: - protocols.act_on(pauli_gates.X, clifford.ActOnStabilizerCHFormArgs(self, [i])) + protocols.act_on( + pauli_gates.X, + clifford.ActOnStabilizerCHFormArgs(self, [i], np.random.RandomState(), {}), + ) def _json_dict_(self) -> Dict[str, Any]: return protocols.obj_to_dict_helper(self, ['n', 'G', 'F', 'M', 'gamma', 'v', 's', 'omega']) diff --git a/cirq/testing/consistent_act_on.py b/cirq/testing/consistent_act_on.py index eaf1890d99b..76a3a2f5813 100644 --- a/cirq/testing/consistent_act_on.py +++ b/cirq/testing/consistent_act_on.py @@ -190,7 +190,10 @@ def _final_stabilizer_state_ch_form( for op in circuit.all_operations(): try: args = act_on_stabilizer_ch_form_args.ActOnStabilizerCHFormArgs( - state=stabilizer_ch_form, axes=[qubit_map[qid] for qid in op.qubits] + state=stabilizer_ch_form, + axes=[qubit_map[qid] for qid in op.qubits], + prng=np.random.RandomState(), + log_of_measurement_results={}, ) protocols.act_on(op, args, allow_decompose=True) except TypeError: