From 984c756f93925629a69b9de0d26f4ed1e8043f37 Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Mon, 6 Nov 2023 13:32:18 -0800 Subject: [PATCH 1/3] Increase coverage of clifford protocols to parity_gates --- cirq-core/cirq/ops/parity_gates.py | 78 ++++++++++++++++++++------ cirq-core/cirq/ops/pauli_gates_test.py | 21 +++++++ 2 files changed, 81 insertions(+), 18 deletions(-) diff --git a/cirq-core/cirq/ops/parity_gates.py b/cirq-core/cirq/ops/parity_gates.py index 24ed09a5eb5..29527239d31 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,32 @@ 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['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/pauli_gates_test.py b/cirq-core/cirq/ops/pauli_gates_test.py index c6a03e3f3f5..ae8221c6352 100644 --- a/cirq-core/cirq/ops/pauli_gates_test.py +++ b/cirq-core/cirq/ops/pauli_gates_test.py @@ -220,3 +220,24 @@ def test_powers(): assert isinstance(cirq.X**1, cirq.Pauli) assert isinstance(cirq.Y**1, cirq.Pauli) assert isinstance(cirq.Z**1, cirq.Pauli) + + +@pytest.mark.parametrize('gate_cls', (cirq.XXPowGate, cirq.YYPowGate, cirq.ZZPowGate)) +@pytest.mark.parametrize('exponent', (0, 0.5, 0.75, 1, 1.5)) +def test_clifford_protocols(gate_cls: type[cirq.EigenGate], exponent: float): + gate = gate_cls(exponent=exponent) + assert hasattr(gate, '_decompose_into_clifford_with_qubits_') + is_clifford = exponent % 2 in (0, 0.5, 1, 1.5) + 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 From 7448188a18acea1701e9dd93a02b56e931a3cb4e Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Fri, 10 Nov 2023 16:15:49 -0800 Subject: [PATCH 2/3] address comments --- cirq-core/cirq/ops/parity_gates.py | 12 +++++++----- cirq-core/cirq/ops/parity_gates_test.py | 23 +++++++++++++++++++++++ cirq-core/cirq/ops/pauli_gates_test.py | 21 --------------------- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/cirq-core/cirq/ops/parity_gates.py b/cirq-core/cirq/ops/parity_gates.py index 29527239d31..0411c150050 100644 --- a/cirq-core/cirq/ops/parity_gates.py +++ b/cirq-core/cirq/ops/parity_gates.py @@ -283,7 +283,7 @@ def _decompose_(self, qubits): def _decompose_into_clifford_with_qubits_( self, qubits: Sequence['cirq.Qid'] - ) -> Sequence['cirq.Operation']: + ) -> Sequence['cirq.Operation' | Sequence['cirq.Operation']]: if not self._has_stabilizer_effect_(): return NotImplemented if self.exponent % 2 == 0: @@ -295,14 +295,16 @@ def _decompose_into_clifford_with_qubits_( return [ pauli_interaction_gate.PauliInteractionGate( pauli_gates.Z, False, pauli_gates.Z, False - ).on(*qubits) - ] + clifford_gate.SingleQubitCliffordGate.Z_sqrt.on_each(*qubits) + ).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) + ).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) 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 diff --git a/cirq-core/cirq/ops/pauli_gates_test.py b/cirq-core/cirq/ops/pauli_gates_test.py index ae8221c6352..c6a03e3f3f5 100644 --- a/cirq-core/cirq/ops/pauli_gates_test.py +++ b/cirq-core/cirq/ops/pauli_gates_test.py @@ -220,24 +220,3 @@ def test_powers(): assert isinstance(cirq.X**1, cirq.Pauli) assert isinstance(cirq.Y**1, cirq.Pauli) assert isinstance(cirq.Z**1, cirq.Pauli) - - -@pytest.mark.parametrize('gate_cls', (cirq.XXPowGate, cirq.YYPowGate, cirq.ZZPowGate)) -@pytest.mark.parametrize('exponent', (0, 0.5, 0.75, 1, 1.5)) -def test_clifford_protocols(gate_cls: type[cirq.EigenGate], exponent: float): - gate = gate_cls(exponent=exponent) - assert hasattr(gate, '_decompose_into_clifford_with_qubits_') - is_clifford = exponent % 2 in (0, 0.5, 1, 1.5) - 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 From a40cc50be4cc270591474c50a4f154c8c7b1ca0f Mon Sep 17 00:00:00 2001 From: Nour Yosri Date: Fri, 10 Nov 2023 16:19:26 -0800 Subject: [PATCH 3/3] nit --- cirq-core/cirq/ops/parity_gates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/ops/parity_gates.py b/cirq-core/cirq/ops/parity_gates.py index 0411c150050..52f9a79a374 100644 --- a/cirq-core/cirq/ops/parity_gates.py +++ b/cirq-core/cirq/ops/parity_gates.py @@ -283,7 +283,7 @@ def _decompose_(self, qubits): def _decompose_into_clifford_with_qubits_( self, qubits: Sequence['cirq.Qid'] - ) -> Sequence['cirq.Operation' | Sequence['cirq.Operation']]: + ) -> Sequence[Union['cirq.Operation', Sequence['cirq.Operation']]]: if not self._has_stabilizer_effect_(): return NotImplemented if self.exponent % 2 == 0: