diff --git a/cirq/ops/common_gates.py b/cirq/ops/common_gates.py index 50e004957ec..73023248043 100644 --- a/cirq/ops/common_gates.py +++ b/cirq/ops/common_gates.py @@ -96,7 +96,7 @@ def _act_on_(self, args: Any): from cirq.sim import clifford if isinstance(args, clifford.ActOnCliffordTableauArgs): - if protocols.is_parameterized(self) or self.exponent % 0.5 != 0: + if not protocols.has_stabilizer_effect(self): return NotImplemented tableau = args.tableau q = args.axes[0] @@ -112,7 +112,7 @@ def _act_on_(self, args: Any): return True if isinstance(args, clifford.ActOnStabilizerCHFormArgs): - if protocols.is_parameterized(self) or self.exponent % 0.5 != 0: + if not protocols.has_stabilizer_effect(self): return NotImplemented _act_with_gates(args, H, ZPowGate(exponent=self._exponent), H) # Adjust the global phase based on the global_shift parameter. @@ -251,7 +251,7 @@ def _phase_by_(self, phase_turns, qubit_index): def _has_stabilizer_effect_(self) -> Optional[bool]: if self._is_parameterized_(): return None - return self.exponent % 1 == 0 + return self.exponent % 0.5 == 0 def __str__(self) -> str: if self._global_shift == -0.5: @@ -319,7 +319,7 @@ def _act_on_(self, args: Any): from cirq.sim import clifford if isinstance(args, clifford.ActOnCliffordTableauArgs): - if protocols.is_parameterized(self) or self.exponent % 0.5 != 0: + if not protocols.has_stabilizer_effect(self): return NotImplemented tableau = args.tableau q = args.axes[0] @@ -341,7 +341,7 @@ def _act_on_(self, args: Any): return True if isinstance(args, clifford.ActOnStabilizerCHFormArgs): - if protocols.is_parameterized(self) or self.exponent % 0.5 != 0: + if not protocols.has_stabilizer_effect(self): return NotImplemented effective_exponent = self._exponent % 2 state = args.state @@ -442,7 +442,7 @@ def _phase_by_(self, phase_turns, qubit_index): def _has_stabilizer_effect_(self) -> Optional[bool]: if self._is_parameterized_(): return None - return self.exponent % 1 == 0 + return self.exponent % 0.5 == 0 def __str__(self) -> str: if self._global_shift == -0.5: @@ -508,7 +508,7 @@ def _act_on_(self, args: Any): from cirq.sim import clifford if isinstance(args, clifford.ActOnCliffordTableauArgs): - if protocols.is_parameterized(self) or self.exponent % 0.5 != 0: + if not protocols.has_stabilizer_effect(self): return NotImplemented tableau = args.tableau q = args.axes[0] @@ -524,7 +524,7 @@ def _act_on_(self, args: Any): return True if isinstance(args, clifford.ActOnStabilizerCHFormArgs): - if protocols.is_parameterized(self) or self.exponent % 0.5 != 0: + if not protocols.has_stabilizer_effect(self): return NotImplemented q = args.axes[0] effective_exponent = self._exponent % 2 @@ -795,7 +795,7 @@ def _act_on_(self, args: Any): from cirq.sim import clifford if isinstance(args, clifford.ActOnCliffordTableauArgs): - if protocols.is_parameterized(self) or self.exponent % 1 != 0: + if not protocols.has_stabilizer_effect(self): return NotImplemented tableau = args.tableau q = args.axes[0] @@ -808,7 +808,7 @@ def _act_on_(self, args: Any): return True if isinstance(args, clifford.ActOnStabilizerCHFormArgs): - if protocols.is_parameterized(self) or self.exponent % 1 != 0: + if not protocols.has_stabilizer_effect(self): return NotImplemented q = args.axes[0] state = args.state @@ -958,7 +958,7 @@ def _act_on_(self, args: Any): from cirq.sim import clifford if isinstance(args, clifford.ActOnCliffordTableauArgs): - if protocols.is_parameterized(self) or self.exponent % 1 != 0: + if not protocols.has_stabilizer_effect(self): return NotImplemented tableau = args.tableau q1 = args.axes[0] @@ -981,7 +981,7 @@ def _act_on_(self, args: Any): return True if isinstance(args, clifford.ActOnStabilizerCHFormArgs): - if protocols.is_parameterized(self) or self.exponent % 1 != 0: + if not protocols.has_stabilizer_effect(self): return NotImplemented q1 = args.axes[0] q2 = args.axes[1] @@ -1181,7 +1181,7 @@ def _act_on_(self, args: Any): from cirq.sim import clifford if isinstance(args, clifford.ActOnCliffordTableauArgs): - if protocols.is_parameterized(self) or self.exponent % 1 != 0: + if not protocols.has_stabilizer_effect(self): return NotImplemented tableau = args.tableau q1 = args.axes[0] @@ -1197,7 +1197,7 @@ def _act_on_(self, args: Any): return True if isinstance(args, clifford.ActOnStabilizerCHFormArgs): - if protocols.is_parameterized(self) or self.exponent % 1 != 0: + if not protocols.has_stabilizer_effect(self): return NotImplemented q1 = args.axes[0] q2 = args.axes[1] diff --git a/cirq/ops/common_gates_test.py b/cirq/ops/common_gates_test.py index e6deab595ed..75e0729f354 100644 --- a/cirq/ops/common_gates_test.py +++ b/cirq/ops/common_gates_test.py @@ -917,32 +917,8 @@ def test_parameterized_cphase(): assert cirq.cphase(sympy.pi / 2) == cirq.CZ ** 0.5 -def test_x_stabilizer(): - gate = cirq.X - assert cirq.has_stabilizer_effect(gate) - assert not cirq.has_stabilizer_effect(gate ** 0.5) - assert cirq.has_stabilizer_effect(gate ** 0) - assert not cirq.has_stabilizer_effect(gate ** -0.5) - assert cirq.has_stabilizer_effect(gate ** 4) - assert not cirq.has_stabilizer_effect(gate ** 1.2) - foo = sympy.Symbol('foo') - assert not cirq.has_stabilizer_effect(gate ** foo) - - -def test_y_stabilizer(): - gate = cirq.Y - assert cirq.has_stabilizer_effect(gate) - assert not cirq.has_stabilizer_effect(gate ** 0.5) - assert cirq.has_stabilizer_effect(gate ** 0) - assert not cirq.has_stabilizer_effect(gate ** -0.5) - assert cirq.has_stabilizer_effect(gate ** 4) - assert not cirq.has_stabilizer_effect(gate ** 1.2) - foo = sympy.Symbol('foo') - assert not cirq.has_stabilizer_effect(gate ** foo) - - -def test_z_stabilizer(): - gate = cirq.Z +@pytest.mark.parametrize('gate', [cirq.X, cirq.Y, cirq.Z]) +def test_x_y_z_stabilizer(gate): assert cirq.has_stabilizer_effect(gate) assert cirq.has_stabilizer_effect(gate ** 0.5) assert cirq.has_stabilizer_effect(gate ** 0) @@ -965,20 +941,8 @@ def test_h_stabilizer(): assert not cirq.has_stabilizer_effect(gate ** foo) -def test_cz_stabilizer(): - gate = cirq.CZ - assert cirq.has_stabilizer_effect(gate) - assert not cirq.has_stabilizer_effect(gate ** 0.5) - assert cirq.has_stabilizer_effect(gate ** 0) - assert not cirq.has_stabilizer_effect(gate ** -0.5) - assert cirq.has_stabilizer_effect(gate ** 4) - assert not cirq.has_stabilizer_effect(gate ** 1.2) - foo = sympy.Symbol('foo') - assert not cirq.has_stabilizer_effect(gate ** foo) - - -def test_cnot_stabilizer(): - gate = cirq.CNOT +@pytest.mark.parametrize('gate', [cirq.CX, cirq.CZ]) +def test_cx_cz_stabilizer(gate): assert cirq.has_stabilizer_effect(gate) assert not cirq.has_stabilizer_effect(gate ** 0.5) assert cirq.has_stabilizer_effect(gate ** 0) diff --git a/cirq/ops/global_phase_op.py b/cirq/ops/global_phase_op.py index d8964931a2e..0586b42771e 100644 --- a/cirq/ops/global_phase_op.py +++ b/cirq/ops/global_phase_op.py @@ -57,6 +57,9 @@ def _apply_unitary_(self, args) -> np.ndarray: args.target_tensor *= self.coefficient return args.target_tensor + def _has_stabilizer_effect_(self) -> bool: + return True + def _act_on_(self, args: Any): from cirq.sim import clifford diff --git a/cirq/ops/global_phase_op_test.py b/cirq/ops/global_phase_op_test.py index 9468c6c0151..6b2a0df2688 100644 --- a/cirq/ops/global_phase_op_test.py +++ b/cirq/ops/global_phase_op_test.py @@ -23,6 +23,7 @@ def test_init(): assert op.coefficient == 1j assert op.qubits == () assert op.with_qubits() is op + assert cirq.has_stabilizer_effect(op) with pytest.raises(ValueError, match='not unitary'): _ = cirq.GlobalPhaseOperation(2) diff --git a/cirq/ops/measurement_gate.py b/cirq/ops/measurement_gate.py index a8dfc29293e..e7ebc38198b 100644 --- a/cirq/ops/measurement_gate.py +++ b/cirq/ops/measurement_gate.py @@ -217,6 +217,9 @@ def _from_json_dict_(cls, num_qubits, key, invert_mask, qid_shape=None, **kwargs qid_shape=None if qid_shape is None else tuple(qid_shape), ) + def _has_stabilizer_effect_(self) -> Optional[bool]: + return True + def _act_on_(self, args: Any) -> bool: from cirq import sim diff --git a/cirq/ops/measurement_gate_test.py b/cirq/ops/measurement_gate_test.py index a3c7fda2d5d..e1bce7c7411 100644 --- a/cirq/ops/measurement_gate_test.py +++ b/cirq/ops/measurement_gate_test.py @@ -34,6 +34,11 @@ def test_measure_init(num_qubits): cirq.MeasurementGate() +@pytest.mark.parametrize('num_qubits', [1, 2, 4]) +def test_has_stabilizer_effect(num_qubits): + assert cirq.has_stabilizer_effect(cirq.MeasurementGate(num_qubits)) + + def test_measurement_eq(): eq = cirq.testing.EqualsTester() eq.make_equality_group( diff --git a/cirq/protocols/has_stabilizer_effect_protocol.py b/cirq/protocols/has_stabilizer_effect_protocol.py index 48bedb55c53..ba71aa1771b 100644 --- a/cirq/protocols/has_stabilizer_effect_protocol.py +++ b/cirq/protocols/has_stabilizer_effect_protocol.py @@ -69,9 +69,9 @@ def _strat_has_stabilizer_effect_from_unitary(val: Any) -> Optional[bool]: Returns whether unitary of `val` normalizes the Pauli group. Works only for 2x2 unitaries. """ - if not protocols.has_unitary(val): + # Do not try this strategy if there is no unitary or if the number of + # qubits is not 1 since that would be expensive. + if not protocols.has_unitary(val) or protocols.num_qubits(val) != 1: return None unitary = protocols.unitary(val) - if unitary.shape == (2, 2): - return SingleQubitCliffordGate.from_unitary(unitary) is not None - return None + return SingleQubitCliffordGate.from_unitary(unitary) is not None diff --git a/cirq/protocols/has_stabilizer_effect_protocol_test.py b/cirq/protocols/has_stabilizer_effect_protocol_test.py index 5e83a4087af..321b73359a1 100644 --- a/cirq/protocols/has_stabilizer_effect_protocol_test.py +++ b/cirq/protocols/has_stabilizer_effect_protocol_test.py @@ -41,13 +41,16 @@ def _has_stabilizer_effect_(self): return True +q = cirq.LineQubit(0) + + class EmptyOp(cirq.Operation): """A trivial operation.""" @property def qubits(self): # coverage: ignore - return () + return (q,) def with_qubits(self, *new_qubits): # coverage: ignore diff --git a/cirq/sim/clifford/clifford_simulator.py b/cirq/sim/clifford/clifford_simulator.py index 1acf924a5b9..a407f5bae19 100644 --- a/cirq/sim/clifford/clifford_simulator.py +++ b/cirq/sim/clifford/clifford_simulator.py @@ -32,13 +32,11 @@ from typing import Any, Dict, List, Iterator, Sequence import numpy as np -from cirq.ops.global_phase_op import GlobalPhaseOperation import cirq from cirq import circuits, study, ops, protocols, value -from cirq.ops.clifford_gate import SingleQubitCliffordGate from cirq.ops.dense_pauli_string import DensePauliString -from cirq.protocols import act_on, unitary +from cirq.protocols import act_on from cirq.sim import clifford, simulator from cirq._compat import deprecated, deprecated_parameter from cirq.sim.simulator import check_all_resolved @@ -60,17 +58,7 @@ def __init__(self, seed: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None): def is_supported_operation(op: 'cirq.Operation') -> bool: """Checks whether given operation can be simulated by this simulator.""" # TODO: support more general Pauli measurements - if isinstance(op.gate, cirq.MeasurementGate): - return True - if isinstance(op, GlobalPhaseOperation): - return True - if not protocols.has_unitary(op): - return False - if len(op.qubits) == 1: - u = unitary(op) - return SingleQubitCliffordGate.from_unitary(u) is not None - else: - return op.gate in [cirq.CNOT, cirq.CZ] + return protocols.has_stabilizer_effect(op) def _base_iterator( self, circuit: circuits.Circuit, qubit_order: ops.QubitOrderOrList, initial_state: int