Skip to content

Commit 34e8dab

Browse files
Extend Clifford protocol for 2+ qubit operations through analytical check and falling back to decompose (#6332)
1 parent 17313e7 commit 34e8dab

File tree

3 files changed

+73
-12
lines changed

3 files changed

+73
-12
lines changed

cirq-core/cirq/protocols/has_stabilizer_effect_protocol.py

+52-6
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,18 @@
1515
from typing import Any, Optional
1616

1717
from cirq.ops.clifford_gate import SingleQubitCliffordGate
18+
from cirq.ops.dense_pauli_string import DensePauliString
19+
from cirq._import import LazyLoader
20+
import cirq.protocols.unitary_protocol as unitary_protocol
21+
import cirq.protocols.has_unitary_protocol as has_unitary_protocol
22+
import cirq.protocols.qid_shape_protocol as qid_shape_protocol
23+
import cirq.protocols.decompose_protocol as decompose_protocol
1824

19-
from cirq import protocols
25+
pauli_string_decomposition = LazyLoader(
26+
"pauli_string_decomposition",
27+
globals(),
28+
"cirq.transformers.analytical_decompositions.pauli_string_decomposition",
29+
)
2030

2131

2232
def has_stabilizer_effect(val: Any) -> bool:
@@ -29,6 +39,7 @@ def has_stabilizer_effect(val: Any) -> bool:
2939
_strat_has_stabilizer_effect_from_has_stabilizer_effect,
3040
_strat_has_stabilizer_effect_from_gate,
3141
_strat_has_stabilizer_effect_from_unitary,
42+
_strat_has_stabilizer_effect_from_decompose,
3243
]
3344
for strat in strats:
3445
result = strat(val)
@@ -62,9 +73,44 @@ def _strat_has_stabilizer_effect_from_unitary(val: Any) -> Optional[bool]:
6273
2x2 unitaries.
6374
"""
6475
# Do not try this strategy if there is no unitary or if the number of
65-
# qubits is not 1 since that would be expensive.
66-
qid_shape = protocols.qid_shape(val, default=None)
67-
if qid_shape is None or len(qid_shape) != 1 or not protocols.has_unitary(val):
76+
# qubits is greater than 3 since that would be expensive.
77+
qid_shape = qid_shape_protocol.qid_shape(val, default=None)
78+
if (
79+
qid_shape is None
80+
or len(qid_shape) > 3
81+
or qid_shape != (2,) * len(qid_shape)
82+
or not has_unitary_protocol.has_unitary(val)
83+
):
6884
return None
69-
unitary = protocols.unitary(val)
70-
return SingleQubitCliffordGate.from_unitary(unitary) is not None
85+
unitary = unitary_protocol.unitary(val)
86+
if len(qid_shape) == 1:
87+
return SingleQubitCliffordGate.from_unitary(unitary) is not None
88+
89+
# Check if the action of the unitary on each single qubit pauli string leads to a pauli product.
90+
# Source: https://quantumcomputing.stackexchange.com/a/13158
91+
for q_idx in range(len(qid_shape)):
92+
for g in 'XZ':
93+
pauli_string = ['I'] * len(qid_shape)
94+
pauli_string[q_idx] = g
95+
ps = DensePauliString(pauli_string)
96+
p = ps._unitary_()
97+
if not pauli_string_decomposition.unitary_to_pauli_string(
98+
(unitary @ p @ unitary.T.conj())
99+
):
100+
return False
101+
return True
102+
103+
104+
def _strat_has_stabilizer_effect_from_decompose(val: Any) -> Optional[bool]:
105+
qid_shape = qid_shape_protocol.qid_shape(val, default=None)
106+
if qid_shape is None or len(qid_shape) <= 3:
107+
return None
108+
109+
decomposition = decompose_protocol.decompose_once(val, default=None)
110+
if decomposition is None:
111+
return None
112+
for op in decomposition:
113+
res = has_stabilizer_effect(op)
114+
if not res:
115+
return res
116+
return True

cirq-core/cirq/protocols/has_stabilizer_effect_protocol_test.py

+20-5
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ def __init__(self, unitary):
9292
def _unitary_(self):
9393
return self.unitary
9494

95+
@property
96+
def qubits(self):
97+
return cirq.LineQubit.range(self.unitary.shape[0].bit_length() - 1)
98+
9599

96100
def test_inconclusive():
97101
assert not cirq.has_stabilizer_effect(object())
@@ -125,9 +129,20 @@ def test_via_unitary():
125129
op3 = OpWithUnitary(np.array([[1, 0], [0, np.sqrt(1j)]]))
126130
assert not cirq.has_stabilizer_effect(op3)
127131

132+
# 2+ qubit cliffords
133+
assert cirq.has_stabilizer_effect(cirq.CNOT)
134+
assert cirq.has_stabilizer_effect(cirq.XX)
135+
assert cirq.has_stabilizer_effect(cirq.ZZ)
136+
137+
# Non Cliffords
138+
assert not cirq.has_stabilizer_effect(cirq.T)
139+
assert not cirq.has_stabilizer_effect(cirq.CCNOT)
140+
assert not cirq.has_stabilizer_effect(cirq.CCZ)
141+
128142

129-
def test_via_unitary_not_supported():
130-
# Unitaries larger than 2x2 are not yet supported.
131-
op = OpWithUnitary(cirq.unitary(cirq.CNOT))
132-
assert not cirq.has_stabilizer_effect(op)
133-
assert not cirq.has_stabilizer_effect(op)
143+
def test_via_decompose():
144+
assert cirq.has_stabilizer_effect(cirq.Circuit(cirq.H.on_each(cirq.LineQubit.range(4))))
145+
assert not cirq.has_stabilizer_effect(cirq.Circuit(cirq.T.on_each(cirq.LineQubit.range(4))))
146+
assert not cirq.has_stabilizer_effect(
147+
OpWithUnitary(cirq.unitary(cirq.Circuit(cirq.T.on_each(cirq.LineQubit.range(4)))))
148+
)

cirq-ft/cirq_ft/infra/t_complexity_protocol.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def _is_clifford_or_t(stc: Any, fail_quietly: bool) -> Optional[TComplexity]:
8181
if isinstance(stc, cirq.ClassicallyControlledOperation):
8282
stc = stc.without_classical_controls()
8383

84-
if cirq.has_stabilizer_effect(stc):
84+
if cirq.num_qubits(stc) <= 2 and cirq.has_stabilizer_effect(stc):
8585
# Clifford operation.
8686
return TComplexity(clifford=1)
8787

0 commit comments

Comments
 (0)