From 0b90ed6febe0818de2111015293fd49e722302b4 Mon Sep 17 00:00:00 2001 From: Noureldin Date: Fri, 10 Nov 2023 16:33:54 -0800 Subject: [PATCH] Increase coverage of clifford protocols to parity_gates (#6338) --- cirq-core/cirq/ops/parity_gates.py | 80 +++++++++++++++++++------ cirq-core/cirq/ops/parity_gates_test.py | 23 +++++++ 2 files changed, 85 insertions(+), 18 deletions(-) diff --git a/cirq-core/cirq/ops/parity_gates.py b/cirq-core/cirq/ops/parity_gates.py index 24ed09a5eb5..52f9a79a374 100644 --- a/cirq-core/cirq/ops/parity_gates.py +++ b/cirq-core/cirq/ops/parity_gates.py @@ -14,7 +14,7 @@ """Quantum gates that phase with respect to product-of-pauli observables.""" -from typing import Any, Dict, List, Optional, Tuple, Union, TYPE_CHECKING +from typing import Any, Dict, List, Optional, Tuple, Union, TYPE_CHECKING, Sequence from typing_extensions import Self import numpy as np @@ -22,7 +22,15 @@ from cirq import protocols, value from cirq._compat import proper_repr from cirq._doc import document -from cirq.ops import gate_features, eigen_gate, common_gates, pauli_gates +from cirq.ops import ( + gate_features, + eigen_gate, + common_gates, + pauli_gates, + clifford_gate, + pauli_interaction_gate, +) + if TYPE_CHECKING: import cirq @@ -87,25 +95,29 @@ def _trace_distance_bound_(self) -> Optional[float]: return abs(np.sin(self._exponent * 0.5 * np.pi)) def _decompose_into_clifford_with_qubits_(self, qubits): - from cirq.ops.clifford_gate import SingleQubitCliffordGate - from cirq.ops.pauli_interaction_gate import PauliInteractionGate - if self.exponent % 2 == 0: return [] if self.exponent % 2 == 0.5: return [ - PauliInteractionGate(pauli_gates.X, False, pauli_gates.X, False).on(*qubits), - SingleQubitCliffordGate.X_sqrt.on_each(*qubits), + pauli_interaction_gate.PauliInteractionGate( + pauli_gates.X, False, pauli_gates.X, False + ).on(*qubits), + clifford_gate.SingleQubitCliffordGate.X_sqrt.on_each(*qubits), ] if self.exponent % 2 == 1: - return [SingleQubitCliffordGate.X.on_each(*qubits)] + return [clifford_gate.SingleQubitCliffordGate.X.on_each(*qubits)] if self.exponent % 2 == 1.5: return [ - PauliInteractionGate(pauli_gates.X, False, pauli_gates.X, False).on(*qubits), - SingleQubitCliffordGate.X_nsqrt.on_each(*qubits), + pauli_interaction_gate.PauliInteractionGate( + pauli_gates.X, False, pauli_gates.X, False + ).on(*qubits), + clifford_gate.SingleQubitCliffordGate.X_nsqrt.on_each(*qubits), ] return NotImplemented + def _has_stabilizer_effect_(self) -> bool: + return self.exponent % 2 in (0, 0.5, 1, 1.5) + def _decompose_(self, qubits: Tuple['cirq.Qid', ...]) -> 'cirq.OP_TREE': yield common_gates.YPowGate(exponent=-0.5).on_each(*qubits) yield ZZPowGate(exponent=self.exponent, global_shift=self.global_shift)(*qubits) @@ -192,25 +204,29 @@ def _trace_distance_bound_(self) -> Optional[float]: return abs(np.sin(self._exponent * 0.5 * np.pi)) def _decompose_into_clifford_with_qubits_(self, qubits): - from cirq.ops.clifford_gate import SingleQubitCliffordGate - from cirq.ops.pauli_interaction_gate import PauliInteractionGate - if self.exponent % 2 == 0: return [] if self.exponent % 2 == 0.5: return [ - PauliInteractionGate(pauli_gates.Y, False, pauli_gates.Y, False).on(*qubits), - SingleQubitCliffordGate.Y_sqrt.on_each(*qubits), + pauli_interaction_gate.PauliInteractionGate( + pauli_gates.Y, False, pauli_gates.Y, False + ).on(*qubits), + clifford_gate.SingleQubitCliffordGate.Y_sqrt.on_each(*qubits), ] if self.exponent % 2 == 1: - return [SingleQubitCliffordGate.Y.on_each(*qubits)] + return [clifford_gate.SingleQubitCliffordGate.Y.on_each(*qubits)] if self.exponent % 2 == 1.5: return [ - PauliInteractionGate(pauli_gates.Y, False, pauli_gates.Y, False).on(*qubits), - SingleQubitCliffordGate.Y_nsqrt.on_each(*qubits), + pauli_interaction_gate.PauliInteractionGate( + pauli_gates.Y, False, pauli_gates.Y, False + ).on(*qubits), + clifford_gate.SingleQubitCliffordGate.Y_nsqrt.on_each(*qubits), ] return NotImplemented + def _has_stabilizer_effect_(self) -> bool: + return self.exponent % 2 in (0, 0.5, 1, 1.5) + def _decompose_(self, qubits: Tuple['cirq.Qid', ...]) -> 'cirq.OP_TREE': yield common_gates.XPowGate(exponent=0.5).on_each(*qubits) yield ZZPowGate(exponent=self.exponent, global_shift=self.global_shift)(*qubits) @@ -265,6 +281,34 @@ def _decompose_(self, qubits): exponent=-2 * self.exponent, global_shift=-self.global_shift / 2 )(qubits[0], qubits[1]) + def _decompose_into_clifford_with_qubits_( + self, qubits: Sequence['cirq.Qid'] + ) -> Sequence[Union['cirq.Operation', Sequence['cirq.Operation']]]: + if not self._has_stabilizer_effect_(): + return NotImplemented + if self.exponent % 2 == 0: + return [] + if self.exponent % 2 == 1: + return clifford_gate.SingleQubitCliffordGate.Z.on_each(*qubits) + + if self.exponent % 2 == 0.5: + return [ + pauli_interaction_gate.PauliInteractionGate( + pauli_gates.Z, False, pauli_gates.Z, False + ).on(*qubits), + clifford_gate.SingleQubitCliffordGate.Z_sqrt.on_each(*qubits), + ] + else: + return [ + pauli_interaction_gate.PauliInteractionGate( + pauli_gates.Z, False, pauli_gates.Z, False + ).on(*qubits), + clifford_gate.SingleQubitCliffordGate.Z_nsqrt.on_each(*qubits), + ] + + def _has_stabilizer_effect_(self) -> bool: + return self.exponent % 2 in (0, 0.5, 1, 1.5) + def _eigen_components(self) -> List[Tuple[float, np.ndarray]]: return [(0, np.diag([1, 0, 0, 1])), (1, np.diag([0, 1, 1, 0]))] diff --git a/cirq-core/cirq/ops/parity_gates_test.py b/cirq-core/cirq/ops/parity_gates_test.py index bdeba3c7275..384483ff22a 100644 --- a/cirq-core/cirq/ops/parity_gates_test.py +++ b/cirq-core/cirq/ops/parity_gates_test.py @@ -332,3 +332,26 @@ def custom_resolver(cirq_type: str): json_text=cirq.to_json(cirq.ms(np.pi / 2)), resolvers=[custom_resolver] ) == cirq.ms(np.pi / 2) assert custom_resolver('X') is None + + +@pytest.mark.parametrize('gate_cls', (cirq.XXPowGate, cirq.YYPowGate, cirq.ZZPowGate)) +@pytest.mark.parametrize( + 'exponent,is_clifford', + ((0, True), (0.5, True), (0.75, False), (1, True), (1.5, True), (-1.5, True)), +) +def test_clifford_protocols(gate_cls: type[cirq.EigenGate], exponent: float, is_clifford: bool): + gate = gate_cls(exponent=exponent) + assert hasattr(gate, '_decompose_into_clifford_with_qubits_') + if is_clifford: + clifford_decomposition = cirq.Circuit( + gate._decompose_into_clifford_with_qubits_(cirq.LineQubit.range(2)) + ) + assert cirq.has_stabilizer_effect(gate) + assert cirq.has_stabilizer_effect(clifford_decomposition) + if exponent == 0: + assert clifford_decomposition == cirq.Circuit() + else: + np.testing.assert_allclose(cirq.unitary(gate), cirq.unitary(clifford_decomposition)) + else: + assert not cirq.has_stabilizer_effect(gate) + assert gate._decompose_into_clifford_with_qubits_(cirq.LineQubit.range(2)) is NotImplemented