Skip to content
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

Extend Clifford protocol for 2+ qubit operations through analytical check and falling back to decompose #6332

Merged
merged 9 commits into from
Oct 30, 2023
58 changes: 52 additions & 6 deletions cirq-core/cirq/protocols/has_stabilizer_effect_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,18 @@
from typing import Any, Optional

from cirq.ops.clifford_gate import SingleQubitCliffordGate
from cirq.ops.dense_pauli_string import DensePauliString
from cirq._import import LazyLoader
import cirq.protocols.unitary_protocol as unitary_protocol
import cirq.protocols.has_unitary_protocol as has_unitary_protocol
import cirq.protocols.qid_shape_protocol as qid_shape_protocol
import cirq.protocols.decompose_protocol as decompose_protocol

from cirq import protocols
pauli_string_decomposition = LazyLoader(
"pauli_string_decomposition",
globals(),
"cirq.transformers.analytical_decompositions.pauli_string_decomposition",
)


def has_stabilizer_effect(val: Any) -> bool:
Expand All @@ -29,6 +39,7 @@ def has_stabilizer_effect(val: Any) -> bool:
_strat_has_stabilizer_effect_from_has_stabilizer_effect,
_strat_has_stabilizer_effect_from_gate,
_strat_has_stabilizer_effect_from_unitary,
_strat_has_stabilizer_effect_from_decompose,
]
for strat in strats:
result = strat(val)
Expand Down Expand Up @@ -62,9 +73,44 @@ def _strat_has_stabilizer_effect_from_unitary(val: Any) -> Optional[bool]:
2x2 unitaries.
"""
# Do not try this strategy if there is no unitary or if the number of
# qubits is not 1 since that would be expensive.
qid_shape = protocols.qid_shape(val, default=None)
if qid_shape is None or len(qid_shape) != 1 or not protocols.has_unitary(val):
# qubits is greater than 3 since that would be expensive.
qid_shape = qid_shape_protocol.qid_shape(val, default=None)
if (
qid_shape is None
or len(qid_shape) > 3
or qid_shape != (2,) * len(qid_shape)
or not has_unitary_protocol.has_unitary(val)
):
return None
unitary = protocols.unitary(val)
return SingleQubitCliffordGate.from_unitary(unitary) is not None
unitary = unitary_protocol.unitary(val)
if len(qid_shape) == 1:
return SingleQubitCliffordGate.from_unitary(unitary) is not None

# Check if the action of the unitary on each single qubit pauli string leads to a pauli product.
# Source: https://quantumcomputing.stackexchange.com/a/13158
for q_idx in range(len(qid_shape)):
for g in 'XZ':
pauli_string = ['I'] * len(qid_shape)
pauli_string[q_idx] = g
ps = DensePauliString(pauli_string)
p = ps._unitary_()
if not pauli_string_decomposition.unitary_to_pauli_string(
(unitary @ p @ unitary.T.conj())
):
return False
return True


def _strat_has_stabilizer_effect_from_decompose(val: Any) -> Optional[bool]:
qid_shape = qid_shape_protocol.qid_shape(val, default=None)
if qid_shape is None or len(qid_shape) <= 3:
return None

decomposition = decompose_protocol.decompose_once(val, default=None)
if decomposition is None:
return None
for op in decomposition:
res = has_stabilizer_effect(op)
if not res:
return res
return True
25 changes: 20 additions & 5 deletions cirq-core/cirq/protocols/has_stabilizer_effect_protocol_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ def __init__(self, unitary):
def _unitary_(self):
return self.unitary

@property
def qubits(self):
return cirq.LineQubit.range(self.unitary.shape[0].bit_length() - 1)


def test_inconclusive():
assert not cirq.has_stabilizer_effect(object())
Expand Down Expand Up @@ -125,9 +129,20 @@ def test_via_unitary():
op3 = OpWithUnitary(np.array([[1, 0], [0, np.sqrt(1j)]]))
assert not cirq.has_stabilizer_effect(op3)

# 2+ qubit cliffords
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
# 2+ qubit cliffords
# 2 qubit cliffords

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it works up to 3 qubit operations like CCNOTs

Copy link
Collaborator

Choose a reason for hiding this comment

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

The 3 asserts for cliffords are only for 2 qubit operations, but lgtm

assert cirq.has_stabilizer_effect(cirq.CNOT)
assert cirq.has_stabilizer_effect(cirq.XX)
assert cirq.has_stabilizer_effect(cirq.ZZ)

# Non Cliffords
assert not cirq.has_stabilizer_effect(cirq.T)
assert not cirq.has_stabilizer_effect(cirq.CCNOT)
assert not cirq.has_stabilizer_effect(cirq.CCZ)


def test_via_unitary_not_supported():
# Unitaries larger than 2x2 are not yet supported.
op = OpWithUnitary(cirq.unitary(cirq.CNOT))
assert not cirq.has_stabilizer_effect(op)
assert not cirq.has_stabilizer_effect(op)
def test_via_decompose():
assert cirq.has_stabilizer_effect(cirq.Circuit(cirq.H.on_each(cirq.LineQubit.range(4))))
assert not cirq.has_stabilizer_effect(cirq.Circuit(cirq.T.on_each(cirq.LineQubit.range(4))))
assert not cirq.has_stabilizer_effect(
OpWithUnitary(cirq.unitary(cirq.Circuit(cirq.T.on_each(cirq.LineQubit.range(4)))))
)
2 changes: 1 addition & 1 deletion cirq-ft/cirq_ft/infra/t_complexity_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def _is_clifford_or_t(stc: Any, fail_quietly: bool) -> Optional[TComplexity]:
if isinstance(stc, cirq.ClassicallyControlledOperation):
stc = stc.without_classical_controls()

if cirq.has_stabilizer_effect(stc):
if cirq.num_qubits(stc) <= 2 and cirq.has_stabilizer_effect(stc):
# Clifford operation.
return TComplexity(clifford=1)
Comment on lines +84 to 86
Copy link
Collaborator

Choose a reason for hiding this comment

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

I like this change; it also makes reporting the TComplexity more accurate since we won't count large cliffords as a single clifford operation.


Expand Down