Skip to content

Sync _has_stabilizer_effect_ implementations and use that for Clifford checks #3656

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 1, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct? I had a vague impression that measurements, being non-unitary, could not be stabilizers, but it's possible I'm thinking of a different property.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that would be the Clifford group. Stabilizer = Clifford+measurement
Reference: https://www.scottaaronson.com/papers/chp6.pdf Fig 1 and the paragraph below it. Was initially just following the StabilizerState paper but this one seems to have a more succinct definition.


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