Skip to content

Commit

Permalink
Sync _has_stabilizer_effect_ implementations and use that for Cliffor…
Browse files Browse the repository at this point in the history
…d checks (#3656)

Brings has_stabilizer_effect protocol in line with actual implementations so that it can be used in all Clifford eligibility checks instead of a patchwork of custom conditions.

#2423
  • Loading branch information
smitsanghavi authored Feb 1, 2021
1 parent fa9d941 commit 97e9a91
Show file tree
Hide file tree
Showing 9 changed files with 40 additions and 73 deletions.
28 changes: 14 additions & 14 deletions cirq/ops/common_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand Down
44 changes: 4 additions & 40 deletions cirq/ops/common_gates_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions cirq/ops/global_phase_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions cirq/ops/global_phase_op_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions cirq/ops/measurement_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 5 additions & 0 deletions cirq/ops/measurement_gate_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
8 changes: 4 additions & 4 deletions cirq/protocols/has_stabilizer_effect_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 4 additions & 1 deletion cirq/protocols/has_stabilizer_effect_protocol_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 2 additions & 14 deletions cirq/sim/clifford/clifford_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 97e9a91

Please sign in to comment.