Skip to content

Add has_stabilizer_effect protocol and support native Clifford simulator gates #2760

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 17 commits into from
Mar 28, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@
has_channel,
has_mixture,
has_mixture_channel,
has_stabilizer_effect,
has_unitary,
inverse,
is_measurement,
Expand Down
30 changes: 30 additions & 0 deletions cirq/ops/common_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ def _phase_by_(self, phase_turns, qubit_index):
exponent=self._exponent,
phase_exponent=phase_turns * 2)

def _has_stabilizer_effect_(self) -> Optional[bool]:
if self._is_parameterized_():
return None
return self.exponent % 1 == 0

def __str__(self) -> str:
if self._global_shift == -0.5:
if self._exponent == 1:
Expand Down Expand Up @@ -330,6 +335,11 @@ def _phase_by_(self, phase_turns, qubit_index):
exponent=self._exponent,
phase_exponent=0.5 + phase_turns * 2)

def _has_stabilizer_effect_(self) -> Optional[bool]:
if self._is_parameterized_():
return None
return self.exponent % 1 == 0

def __str__(self) -> str:
if self._global_shift == -0.5:
if self._exponent == 1:
Expand Down Expand Up @@ -474,6 +484,11 @@ def _pauli_expansion_(self) -> value.LinearDict[str]:
def _phase_by_(self, phase_turns: float, qubit_index: int):
return self

def _has_stabilizer_effect_(self) -> Optional[bool]:
if self._is_parameterized_():
return None
return self.exponent % 0.5 == 0

def _circuit_diagram_info_(self, args: 'cirq.CircuitDiagramInfoArgs'
) -> Union[str, 'protocols.CircuitDiagramInfo']:
if self._global_shift == -0.5:
Expand Down Expand Up @@ -662,6 +677,11 @@ def _qasm_(self, args: 'cirq.QasmArgs',
'rx({1:half_turns}) {3};\n'
'ry({2:half_turns}) {3};\n', 0.25, self._exponent, -0.25, qubits[0])

def _has_stabilizer_effect_(self) -> Optional[bool]:
if self._is_parameterized_():
return None
return self.exponent % 1 == 0

def __str__(self):
if self._exponent == 1:
return 'H'
Expand Down Expand Up @@ -794,6 +814,11 @@ def _qasm_(self, args: 'cirq.QasmArgs',
args.validate_version('2.0')
return args.format('cz {0},{1};\n', qubits[0], qubits[1])

def _has_stabilizer_effect_(self) -> Optional[bool]:
if self._is_parameterized_():
return None
return self.exponent % 1 == 0

def __str__(self) -> str:
if self._exponent == 1:
return 'CZ'
Expand Down Expand Up @@ -954,6 +979,11 @@ def _qasm_(self, args: 'cirq.QasmArgs',
args.validate_version('2.0')
return args.format('cx {0},{1};\n', qubits[0], qubits[1])

def _has_stabilizer_effect_(self) -> Optional[bool]:
if self._is_parameterized_():
return None
return self.exponent % 1 == 0

def __str__(self) -> str:
if self._exponent == 1:
return 'CNOT'
Expand Down
72 changes: 72 additions & 0 deletions cirq/ops/common_gates_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,78 @@ def test_rz_unitary():
np.array([[1j, 0], [0, -1j]]))


def test_x_stabilizer():
gate = cirq.X
assert cirq.has_stabilizer_effect(gate)
assert not cirq.has_stabilizer_effect(gate**0.5)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be true not false.

assert cirq.has_stabilizer_effect(gate**0)
assert not cirq.has_stabilizer_effect(gate**-0.5)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be true not false.

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)
Copy link
Contributor

Choose a reason for hiding this comment

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

Square root of Y is a stabilizer gate.

Copy link
Collaborator Author

@smitsanghavi smitsanghavi Mar 18, 2020

Choose a reason for hiding this comment

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

As I mentioned in #2760 (comment), I'm just adding support for the operations that CliffordState supports right now (just like it was before this) and does not try to decompose. Y**0.5 is a stabilizer gate and so is iSwap but just restricting myself to the original 7 gates before #2803 adds all of them to CliffordState since right now the simulator would just balk at Y**0.5.

If you feel we should add all these gates to the protocol at once, I can wait for the #2803 to go in. I can then have the protocol just do what is_supported_gate doing now as described in https://github.com/quantumlib/Cirq/pull/2803/files#r390152850. It would save on redundant work and definitely seems like a better option than having to define _has_stabilizer_effect_ on all gates.

assert cirq.has_stabilizer_effect(gate**0)
assert not cirq.has_stabilizer_effect(gate**-0.5)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be true not false.

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
assert cirq.has_stabilizer_effect(gate)
assert cirq.has_stabilizer_effect(gate**0.5)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be true not false.

assert cirq.has_stabilizer_effect(gate**0)
assert cirq.has_stabilizer_effect(gate**-0.5)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be true not false.

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_h_stabilizer():
gate = cirq.H
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_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)


Copy link
Contributor

Choose a reason for hiding this comment

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

You should also test that measurement is a stabilizer effect, and that gates like CCZ and the depolarizing channel are not.

def test_cnot_stabilizer():
gate = cirq.CNOT
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)


@pytest.mark.parametrize('rads', (-1, -0.3, 0.1, 1))
def test_deprecated_rxyz_rotations(rads):
with capture_logging():
Expand Down
3 changes: 3 additions & 0 deletions cirq/ops/raw_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ def controlled(self,
def _backwards_compatibility_num_qubits(self) -> int:
return protocols.num_qubits(self)

def _has_stabilizer_effect_(self) -> Optional[bool]:
return NotImplemented

@value.alternative(requires='_num_qubits_',
implementation=_backwards_compatibility_num_qubits)
def num_qubits(self) -> int:
Expand Down
2 changes: 2 additions & 0 deletions cirq/protocols/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
equal_up_to_global_phase,
SupportsEqualUpToGlobalPhase,
)
from cirq.protocols.has_stabilizer_effect_protocol import (
has_stabilizer_effect,)
from cirq.protocols.has_unitary_protocol import (
has_unitary,
SupportsExplicitHasUnitary,
Expand Down
61 changes: 61 additions & 0 deletions cirq/protocols/has_stabilizer_effect_protocol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Copyright 2018 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import (
Any,
Optional,
)


def has_stabilizer_effect(val: Any) -> bool:
"""
Returns whether the input has a stabilizer effect. Currently only limits to
Pauli, H, S, CNOT and CZ gates and their Operations. Does not attempt to
decompose a gate into supported gates. For e.g. iSWAP or X**0.5 gate will
return False.
"""
strats = [
_strat_has_stabilizer_effect_from_has_stabilizer_effect,
_strat_has_stabilizer_effect_from_gate
]
for strat in strats:
result = strat(val)
if result is not None:
return result

# If you can't determine if it has stabilizer effect, it does not.
return False


def _strat_has_stabilizer_effect_from_has_stabilizer_effect(val: Any
) -> Optional[bool]:
"""
Attempts to infer whether val has stabilzer effect via its
_has_stabilizer_effect_ method.
"""
if hasattr(val, '_has_stabilizer_effect_'):
result = val._has_stabilizer_effect_()
if result is not NotImplemented and result is not None:
return result
return None


def _strat_has_stabilizer_effect_from_gate(val: Any) -> Optional[bool]:
"""
Attempts to infer whether val has stabilzer effect via the value of
_has_stabilizer_effect_ method of its constituent gate.
"""
if hasattr(val, 'gate'):
return _strat_has_stabilizer_effect_from_has_stabilizer_effect(val.gate)
return None
113 changes: 113 additions & 0 deletions cirq/protocols/has_stabilizer_effect_protocol_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Copyright 2018 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import cirq


class No:
pass


class No1:

def _has_stabilizer_effect_(self):
return NotImplemented


class No2:

def _has_stabilizer_effect_(self):
return None


class No3:

def _has_stabilizer_effect_(self):
return False


class Yes:

def _has_stabilizer_effect_(self):
return True


class EmptyOp(cirq.Operation):
"""A trivial operation."""

@property
def qubits(self):
# coverage: ignore
return ()

def with_qubits(self, *new_qubits):
# coverage: ignore
return self


class NoOp(EmptyOp):

@property
def gate(self):
return No()


class NoOp1(EmptyOp):

@property
def gate(self):
return No1()


class NoOp2(EmptyOp):

@property
def gate(self):
return No2()


class NoOp3(EmptyOp):

@property
def gate(self):
return No3()


class YesOp(EmptyOp):

@property
def gate(self):
return Yes()


def test_inconclusive():
assert not cirq.has_stabilizer_effect(object())
assert not cirq.has_stabilizer_effect('boo')
assert not cirq.has_stabilizer_effect(cirq.SingleQubitGate())
assert not cirq.has_stabilizer_effect(No())
assert not cirq.has_stabilizer_effect(NoOp())


def test_via_has_stabilizer_effect_method():
assert not cirq.has_stabilizer_effect(No1())
assert not cirq.has_stabilizer_effect(No2())
assert not cirq.has_stabilizer_effect(No3())
assert cirq.has_stabilizer_effect(Yes())


def test_via_gate_of_op():
assert cirq.has_stabilizer_effect(YesOp())
assert not cirq.has_stabilizer_effect(NoOp1())
assert not cirq.has_stabilizer_effect(NoOp2())
assert not cirq.has_stabilizer_effect(NoOp3())
4 changes: 0 additions & 4 deletions cirq/sim/clifford/clifford_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,6 @@ def __init__(self, seed: value.RANDOM_STATE_LIKE = None):
self.init = True
self._prng = value.parse_random_state(seed)

@staticmethod
def get_supported_gates() -> List['cirq.Gate']:
return [cirq.X, cirq.Y, cirq.Z, cirq.H, cirq.S, cirq.CNOT, cirq.CZ]

def _base_iterator(self, circuit: circuits.Circuit,
qubit_order: ops.QubitOrderOrList, initial_state: int
) -> Iterator['cirq.CliffordSimulatorStepResult']:
Expand Down
8 changes: 3 additions & 5 deletions cirq/sim/mux.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,9 @@


def _is_clifford_circuit(program: 'cirq.Circuit') -> bool:
supported_ops = clifford_simulator.CliffordSimulator.get_supported_gates()
# TODO: Have this method check the decomposition of the circuit into
# clifford operations.
return all(op.gate in supported_ops or protocols.is_measurement(op)
for op in program.all_operations())
return all(
protocols.has_stabilizer_effect(op) or protocols.is_measurement(op)
for op in program.all_operations())


def sample(program: 'cirq.Circuit',
Expand Down
1 change: 1 addition & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ the magic methods that can be implemented.
cirq.has_channel
cirq.has_mixture
cirq.has_mixture_channel
cirq.has_stabilizer_effect
cirq.has_unitary
cirq.inverse
cirq.is_measurement
Expand Down