Skip to content

Commit 0b90ed6

Browse files
Increase coverage of clifford protocols to parity_gates (#6338)
1 parent 11ae0bd commit 0b90ed6

File tree

2 files changed

+85
-18
lines changed

2 files changed

+85
-18
lines changed

Diff for: cirq-core/cirq/ops/parity_gates.py

+62-18
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,23 @@
1414

1515
"""Quantum gates that phase with respect to product-of-pauli observables."""
1616

17-
from typing import Any, Dict, List, Optional, Tuple, Union, TYPE_CHECKING
17+
from typing import Any, Dict, List, Optional, Tuple, Union, TYPE_CHECKING, Sequence
1818
from typing_extensions import Self
1919

2020
import numpy as np
2121

2222
from cirq import protocols, value
2323
from cirq._compat import proper_repr
2424
from cirq._doc import document
25-
from cirq.ops import gate_features, eigen_gate, common_gates, pauli_gates
25+
from cirq.ops import (
26+
gate_features,
27+
eigen_gate,
28+
common_gates,
29+
pauli_gates,
30+
clifford_gate,
31+
pauli_interaction_gate,
32+
)
33+
2634

2735
if TYPE_CHECKING:
2836
import cirq
@@ -87,25 +95,29 @@ def _trace_distance_bound_(self) -> Optional[float]:
8795
return abs(np.sin(self._exponent * 0.5 * np.pi))
8896

8997
def _decompose_into_clifford_with_qubits_(self, qubits):
90-
from cirq.ops.clifford_gate import SingleQubitCliffordGate
91-
from cirq.ops.pauli_interaction_gate import PauliInteractionGate
92-
9398
if self.exponent % 2 == 0:
9499
return []
95100
if self.exponent % 2 == 0.5:
96101
return [
97-
PauliInteractionGate(pauli_gates.X, False, pauli_gates.X, False).on(*qubits),
98-
SingleQubitCliffordGate.X_sqrt.on_each(*qubits),
102+
pauli_interaction_gate.PauliInteractionGate(
103+
pauli_gates.X, False, pauli_gates.X, False
104+
).on(*qubits),
105+
clifford_gate.SingleQubitCliffordGate.X_sqrt.on_each(*qubits),
99106
]
100107
if self.exponent % 2 == 1:
101-
return [SingleQubitCliffordGate.X.on_each(*qubits)]
108+
return [clifford_gate.SingleQubitCliffordGate.X.on_each(*qubits)]
102109
if self.exponent % 2 == 1.5:
103110
return [
104-
PauliInteractionGate(pauli_gates.X, False, pauli_gates.X, False).on(*qubits),
105-
SingleQubitCliffordGate.X_nsqrt.on_each(*qubits),
111+
pauli_interaction_gate.PauliInteractionGate(
112+
pauli_gates.X, False, pauli_gates.X, False
113+
).on(*qubits),
114+
clifford_gate.SingleQubitCliffordGate.X_nsqrt.on_each(*qubits),
106115
]
107116
return NotImplemented
108117

118+
def _has_stabilizer_effect_(self) -> bool:
119+
return self.exponent % 2 in (0, 0.5, 1, 1.5)
120+
109121
def _decompose_(self, qubits: Tuple['cirq.Qid', ...]) -> 'cirq.OP_TREE':
110122
yield common_gates.YPowGate(exponent=-0.5).on_each(*qubits)
111123
yield ZZPowGate(exponent=self.exponent, global_shift=self.global_shift)(*qubits)
@@ -192,25 +204,29 @@ def _trace_distance_bound_(self) -> Optional[float]:
192204
return abs(np.sin(self._exponent * 0.5 * np.pi))
193205

194206
def _decompose_into_clifford_with_qubits_(self, qubits):
195-
from cirq.ops.clifford_gate import SingleQubitCliffordGate
196-
from cirq.ops.pauli_interaction_gate import PauliInteractionGate
197-
198207
if self.exponent % 2 == 0:
199208
return []
200209
if self.exponent % 2 == 0.5:
201210
return [
202-
PauliInteractionGate(pauli_gates.Y, False, pauli_gates.Y, False).on(*qubits),
203-
SingleQubitCliffordGate.Y_sqrt.on_each(*qubits),
211+
pauli_interaction_gate.PauliInteractionGate(
212+
pauli_gates.Y, False, pauli_gates.Y, False
213+
).on(*qubits),
214+
clifford_gate.SingleQubitCliffordGate.Y_sqrt.on_each(*qubits),
204215
]
205216
if self.exponent % 2 == 1:
206-
return [SingleQubitCliffordGate.Y.on_each(*qubits)]
217+
return [clifford_gate.SingleQubitCliffordGate.Y.on_each(*qubits)]
207218
if self.exponent % 2 == 1.5:
208219
return [
209-
PauliInteractionGate(pauli_gates.Y, False, pauli_gates.Y, False).on(*qubits),
210-
SingleQubitCliffordGate.Y_nsqrt.on_each(*qubits),
220+
pauli_interaction_gate.PauliInteractionGate(
221+
pauli_gates.Y, False, pauli_gates.Y, False
222+
).on(*qubits),
223+
clifford_gate.SingleQubitCliffordGate.Y_nsqrt.on_each(*qubits),
211224
]
212225
return NotImplemented
213226

227+
def _has_stabilizer_effect_(self) -> bool:
228+
return self.exponent % 2 in (0, 0.5, 1, 1.5)
229+
214230
def _decompose_(self, qubits: Tuple['cirq.Qid', ...]) -> 'cirq.OP_TREE':
215231
yield common_gates.XPowGate(exponent=0.5).on_each(*qubits)
216232
yield ZZPowGate(exponent=self.exponent, global_shift=self.global_shift)(*qubits)
@@ -265,6 +281,34 @@ def _decompose_(self, qubits):
265281
exponent=-2 * self.exponent, global_shift=-self.global_shift / 2
266282
)(qubits[0], qubits[1])
267283

284+
def _decompose_into_clifford_with_qubits_(
285+
self, qubits: Sequence['cirq.Qid']
286+
) -> Sequence[Union['cirq.Operation', Sequence['cirq.Operation']]]:
287+
if not self._has_stabilizer_effect_():
288+
return NotImplemented
289+
if self.exponent % 2 == 0:
290+
return []
291+
if self.exponent % 2 == 1:
292+
return clifford_gate.SingleQubitCliffordGate.Z.on_each(*qubits)
293+
294+
if self.exponent % 2 == 0.5:
295+
return [
296+
pauli_interaction_gate.PauliInteractionGate(
297+
pauli_gates.Z, False, pauli_gates.Z, False
298+
).on(*qubits),
299+
clifford_gate.SingleQubitCliffordGate.Z_sqrt.on_each(*qubits),
300+
]
301+
else:
302+
return [
303+
pauli_interaction_gate.PauliInteractionGate(
304+
pauli_gates.Z, False, pauli_gates.Z, False
305+
).on(*qubits),
306+
clifford_gate.SingleQubitCliffordGate.Z_nsqrt.on_each(*qubits),
307+
]
308+
309+
def _has_stabilizer_effect_(self) -> bool:
310+
return self.exponent % 2 in (0, 0.5, 1, 1.5)
311+
268312
def _eigen_components(self) -> List[Tuple[float, np.ndarray]]:
269313
return [(0, np.diag([1, 0, 0, 1])), (1, np.diag([0, 1, 1, 0]))]
270314

Diff for: cirq-core/cirq/ops/parity_gates_test.py

+23
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,26 @@ def custom_resolver(cirq_type: str):
332332
json_text=cirq.to_json(cirq.ms(np.pi / 2)), resolvers=[custom_resolver]
333333
) == cirq.ms(np.pi / 2)
334334
assert custom_resolver('X') is None
335+
336+
337+
@pytest.mark.parametrize('gate_cls', (cirq.XXPowGate, cirq.YYPowGate, cirq.ZZPowGate))
338+
@pytest.mark.parametrize(
339+
'exponent,is_clifford',
340+
((0, True), (0.5, True), (0.75, False), (1, True), (1.5, True), (-1.5, True)),
341+
)
342+
def test_clifford_protocols(gate_cls: type[cirq.EigenGate], exponent: float, is_clifford: bool):
343+
gate = gate_cls(exponent=exponent)
344+
assert hasattr(gate, '_decompose_into_clifford_with_qubits_')
345+
if is_clifford:
346+
clifford_decomposition = cirq.Circuit(
347+
gate._decompose_into_clifford_with_qubits_(cirq.LineQubit.range(2))
348+
)
349+
assert cirq.has_stabilizer_effect(gate)
350+
assert cirq.has_stabilizer_effect(clifford_decomposition)
351+
if exponent == 0:
352+
assert clifford_decomposition == cirq.Circuit()
353+
else:
354+
np.testing.assert_allclose(cirq.unitary(gate), cirq.unitary(clifford_decomposition))
355+
else:
356+
assert not cirq.has_stabilizer_effect(gate)
357+
assert gate._decompose_into_clifford_with_qubits_(cirq.LineQubit.range(2)) is NotImplemented

0 commit comments

Comments
 (0)