Skip to content

Commit b1a5d23

Browse files
authored
Deprecate PauliTransform (#5498)
- PauliTransform is only used with SingleQubitCliffordGate, is not easily extensible for multi-qubit gates, and has been obsoleted by DensePauliString Fixes: #4088
1 parent 68fcde7 commit b1a5d23

File tree

4 files changed

+127
-114
lines changed

4 files changed

+127
-114
lines changed

cirq-core/cirq/contrib/paulistring/clifford_optimize.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ def try_merge_clifford(cliff_op: ops.GateOperation, start_i: int) -> bool:
7777
for pauli, quarter_turns in reversed(
7878
cast(ops.SingleQubitCliffordGate, cliff_op.gate).decompose_rotation()
7979
):
80-
trans = remaining_cliff_gate.transform(pauli)
81-
pauli = trans.to
82-
quarter_turns *= -1 if trans.flip else 1
80+
trans = remaining_cliff_gate.pauli_tuple(pauli)
81+
pauli = trans[0]
82+
quarter_turns *= -1 if trans[1] else 1
8383
string_op = ops.PauliStringPhasor(
8484
ops.PauliString(pauli(cliff_op.qubits[0])), exponent_neg=quarter_turns / 2
8585
)

cirq-core/cirq/ops/clifford_gate.py

+78-68
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,13 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from typing import (
16-
Any,
17-
cast,
18-
Dict,
19-
List,
20-
NamedTuple,
21-
Optional,
22-
Sequence,
23-
Tuple,
24-
TYPE_CHECKING,
25-
Union,
26-
)
15+
import dataclasses
16+
from typing import Any, cast, Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union
17+
2718

2819
import numpy as np
2920

30-
from cirq import protocols, value, linalg, qis
31-
from cirq._doc import document
21+
from cirq import _compat, protocols, value, linalg, qis
3222
from cirq._import import LazyLoader
3323
from cirq.ops import common_gates, identity, named_qubit, raw_types, pauli_gates, phased_x_z_gate
3424
from cirq.ops.pauli_gates import Pauli
@@ -42,29 +32,33 @@
4232
sim = LazyLoader("sim", globals(), "cirq.sim")
4333
transformers = LazyLoader("transformers", globals(), "cirq.transformers")
4434

45-
PauliTransform = NamedTuple('PauliTransform', [('to', Pauli), ('flip', bool)])
46-
document(PauliTransform, """+X, -X, +Y, -Y, +Z, or -Z.""")
35+
36+
@_compat.deprecated_class(deadline='v0.16', fix='Use DensePauliString instead.')
37+
@dataclasses.dataclass
38+
class PauliTransform:
39+
to: Pauli
40+
flip: bool
4741

4842

49-
def _to_pauli_transform(matrix: np.ndarray) -> Optional[PauliTransform]:
43+
def _to_pauli_tuple(matrix: np.ndarray) -> Optional[Tuple[Pauli, bool]]:
5044
"""Converts matrix to PauliTransform.
5145
5246
If matrix is not ±Pauli matrix, returns None.
5347
"""
5448
for pauli in Pauli._XYZ:
5549
p = protocols.unitary(pauli)
5650
if np.allclose(matrix, p):
57-
return PauliTransform(pauli, False)
51+
return (pauli, False)
5852
if np.allclose(matrix, -p):
59-
return PauliTransform(pauli, True)
53+
return (pauli, True)
6054
return None
6155

6256

6357
def _to_clifford_tableau(
64-
rotation_map: Optional[Dict[Pauli, PauliTransform]] = None,
58+
rotation_map: Optional[Dict[Pauli, Tuple[Pauli, bool]]] = None,
6559
*,
66-
x_to: Optional[PauliTransform] = None,
67-
z_to: Optional[PauliTransform] = None,
60+
x_to: Optional[Tuple[Pauli, bool]] = None,
61+
z_to: Optional[Tuple[Pauli, bool]] = None,
6862
) -> qis.CliffordTableau:
6963
"""Transfer the rotation map to clifford tableau representation"""
7064
if x_to is None and z_to is None and rotation_map is None:
@@ -79,13 +73,13 @@ def _to_clifford_tableau(
7973
assert x_to is not None and z_to is not None, "Both x_to and z_to have to be provided."
8074

8175
clifford_tableau = qis.CliffordTableau(num_qubits=1)
82-
clifford_tableau.xs[0, 0] = x_to.to in (pauli_gates.X, pauli_gates.Y)
83-
clifford_tableau.zs[0, 0] = x_to.to in (pauli_gates.Y, pauli_gates.Z)
76+
clifford_tableau.xs[0, 0] = x_to[0] in (pauli_gates.X, pauli_gates.Y)
77+
clifford_tableau.zs[0, 0] = x_to[0] in (pauli_gates.Y, pauli_gates.Z)
8478

85-
clifford_tableau.xs[1, 0] = z_to.to in (pauli_gates.X, pauli_gates.Y)
86-
clifford_tableau.zs[1, 0] = z_to.to in (pauli_gates.Y, pauli_gates.Z)
79+
clifford_tableau.xs[1, 0] = z_to[0] in (pauli_gates.X, pauli_gates.Y)
80+
clifford_tableau.zs[1, 0] = z_to[0] in (pauli_gates.Y, pauli_gates.Z)
8781

88-
clifford_tableau.rs = (x_to.flip, z_to.flip)
82+
clifford_tableau.rs = (x_to[1], z_to[1])
8983
return clifford_tableau
9084

9185

@@ -101,7 +95,7 @@ def _validate_map_input(
10195
x_to: Optional[Tuple[Pauli, bool]],
10296
y_to: Optional[Tuple[Pauli, bool]],
10397
z_to: Optional[Tuple[Pauli, bool]],
104-
) -> Dict[Pauli, PauliTransform]:
98+
) -> Dict[Pauli, Tuple[Pauli, bool]]:
10599
if pauli_map_to is None:
106100
xyz_to = {pauli_gates.X: x_to, pauli_gates.Y: y_to, pauli_gates.Z: z_to}
107101
pauli_map_to = {cast(Pauli, p): trans for p, trans in xyz_to.items() if trans is not None}
@@ -121,7 +115,7 @@ def _validate_map_input(
121115
)
122116
if len(set((to for to, _ in pauli_map_to.values()))) != len(pauli_map_to):
123117
raise ValueError('A rotation cannot map two Paulis to the same')
124-
return {frm: PauliTransform(to, flip) for frm, (to, flip) in pauli_map_to.items()}
118+
return {frm: (to, flip) for frm, (to, flip) in pauli_map_to.items()}
125119

126120

127121
def _pad_tableau(
@@ -505,7 +499,7 @@ def from_xz_map(
505499
z_to: Which Pauli to transform Z to and if it should negate.
506500
"""
507501
return SingleQubitCliffordGate.from_clifford_tableau(
508-
_to_clifford_tableau(x_to=PauliTransform(*x_to), z_to=PauliTransform(*z_to))
502+
_to_clifford_tableau(x_to=x_to, z_to=z_to)
509503
)
510504

511505
@staticmethod
@@ -538,7 +532,7 @@ def from_single_map(
538532
trans_from2 = trans_to
539533
trans_to2 = trans_from
540534
flip2 = not flip
541-
rotation_map[trans_from2] = PauliTransform(trans_to2, flip2)
535+
rotation_map[trans_from2] = (trans_to2, flip2)
542536
return SingleQubitCliffordGate.from_double_map(
543537
cast(Dict[Pauli, Tuple[Pauli, bool]], rotation_map)
544538
)
@@ -566,9 +560,9 @@ def from_double_map(
566560
rotation_map = _validate_map_input(2, pauli_map_to, x_to=x_to, y_to=y_to, z_to=z_to)
567561
(from1, trans1), (from2, trans2) = tuple(rotation_map.items())
568562
from3 = from1.third(from2)
569-
to3 = trans1.to.third(trans2.to)
570-
flip3 = trans1.flip ^ trans2.flip ^ ((from1 < from2) != (trans1.to < trans2.to))
571-
rotation_map[from3] = PauliTransform(to3, flip3)
563+
to3 = trans1[0].third(trans2[0])
564+
flip3 = trans1[1] ^ trans2[1] ^ ((from1 < from2) != (trans1[0] < trans2[0]))
565+
rotation_map[from3] = (to3, flip3)
572566

573567
return SingleQubitCliffordGate.from_clifford_tableau(_to_clifford_tableau(rotation_map))
574568

@@ -578,15 +572,15 @@ def from_pauli(pauli: Pauli, sqrt: bool = False) -> 'SingleQubitCliffordGate':
578572
next_pauli = Pauli.by_relative_index(pauli, 1)
579573
if sqrt:
580574
rotation_map = {
581-
prev_pauli: PauliTransform(next_pauli, True),
582-
pauli: PauliTransform(pauli, False),
583-
next_pauli: PauliTransform(prev_pauli, False),
575+
prev_pauli: (next_pauli, True),
576+
pauli: (pauli, False),
577+
next_pauli: (prev_pauli, False),
584578
}
585579
else:
586580
rotation_map = {
587-
prev_pauli: PauliTransform(prev_pauli, True),
588-
pauli: PauliTransform(pauli, False),
589-
next_pauli: PauliTransform(next_pauli, True),
581+
prev_pauli: (prev_pauli, True),
582+
pauli: (pauli, False),
583+
next_pauli: (next_pauli, True),
590584
}
591585
return SingleQubitCliffordGate.from_clifford_tableau(_to_clifford_tableau(rotation_map))
592586

@@ -618,15 +612,22 @@ def from_unitary(u: np.ndarray) -> Optional['SingleQubitCliffordGate']:
618612
return None
619613
x = protocols.unitary(pauli_gates.X)
620614
z = protocols.unitary(pauli_gates.Z)
621-
x_to = _to_pauli_transform(u @ x @ u.conj().T)
622-
z_to = _to_pauli_transform(u @ z @ u.conj().T)
615+
x_to = _to_pauli_tuple(u @ x @ u.conj().T)
616+
z_to = _to_pauli_tuple(u @ z @ u.conj().T)
623617
if x_to is None or z_to is None:
624618
return None
625619
return SingleQubitCliffordGate.from_clifford_tableau(
626620
_to_clifford_tableau(x_to=x_to, z_to=z_to)
627621
)
628622

629-
def transform(self, pauli: Pauli) -> PauliTransform:
623+
def pauli_tuple(self, pauli: Pauli) -> Tuple[Pauli, bool]:
624+
"""Returns a tuple of a Pauli operator and a boolean.
625+
626+
The pauli is the operator of the transform and the boolean
627+
determines whether the operator should be flipped. For instance,
628+
it is True if the coefficient is -1, and False if the coefficient
629+
is 1.
630+
"""
630631
x_to = self._clifford_tableau.destabilizers()[0]
631632
z_to = self._clifford_tableau.stabilizers()[0]
632633
if pauli == pauli_gates.X:
@@ -638,7 +639,19 @@ def transform(self, pauli: Pauli) -> PauliTransform:
638639
to._coefficient *= 1j
639640
# pauli_mask returns a value between 0 and 4 for [I, X, Y, Z].
640641
to_gate = Pauli._XYZ[to.pauli_mask[0] - 1]
641-
return PauliTransform(to=to_gate, flip=bool(to.coefficient != 1.0))
642+
return (to_gate, bool(to.coefficient != 1.0))
643+
644+
def dense_pauli_string(self, pauli: Pauli) -> 'cirq.DensePauliString':
645+
from cirq.ops import dense_pauli_string
646+
647+
pauli_tuple = self.pauli_tuple(pauli)
648+
coefficient = -1 if pauli_tuple[1] else 1
649+
return dense_pauli_string.DensePauliString(str(pauli_tuple[0]), coefficient=coefficient)
650+
651+
@_compat.deprecated(deadline='v0.16', fix='Use pauli_tuple() or dense_pauli_string() instead')
652+
def transform(self, pauli: Pauli) -> PauliTransform:
653+
pauli_tuple = self.pauli_tuple(pauli)
654+
return PauliTransform(to=pauli_tuple[0], flip=pauli_tuple[1])
642655

643656
def to_phased_xz_gate(self) -> phased_x_z_gate.PhasedXZGate:
644657
"""Convert this gate to a PhasedXZGate instance.
@@ -739,7 +752,7 @@ def commutes_with_single_qubit_gate(self, gate: 'SingleQubitCliffordGate') -> bo
739752
return self_then_gate == gate_then_self
740753

741754
def commutes_with_pauli(self, pauli: Pauli) -> bool:
742-
to, flip = self.transform(pauli)
755+
to, flip = self.pauli_tuple(pauli)
743756
return to == pauli and not flip
744757

745758
def merged_with(self, second: 'SingleQubitCliffordGate') -> 'SingleQubitCliffordGate':
@@ -764,16 +777,16 @@ def decompose_rotation(self) -> Sequence[Tuple[Pauli, int]]:
764777
"""Returns ((first_rotation_axis, first_rotation_quarter_turns), ...)
765778
766779
This is a sequence of zero, one, or two rotations."""
767-
x_rot = self.transform(pauli_gates.X)
768-
y_rot = self.transform(pauli_gates.Y)
769-
z_rot = self.transform(pauli_gates.Z)
780+
x_rot = self.pauli_tuple(pauli_gates.X)
781+
y_rot = self.pauli_tuple(pauli_gates.Y)
782+
z_rot = self.pauli_tuple(pauli_gates.Z)
770783
whole_arr = (
771-
x_rot.to == pauli_gates.X,
772-
y_rot.to == pauli_gates.Y,
773-
z_rot.to == pauli_gates.Z,
784+
x_rot[0] == pauli_gates.X,
785+
y_rot[0] == pauli_gates.Y,
786+
z_rot[0] == pauli_gates.Z,
774787
)
775788
num_whole = sum(whole_arr)
776-
flip_arr = (x_rot.flip, y_rot.flip, z_rot.flip)
789+
flip_arr = (x_rot[1], y_rot[1], z_rot[1])
777790
num_flip = sum(flip_arr)
778791
if num_whole == 3:
779792
if num_flip == 0:
@@ -793,7 +806,7 @@ def decompose_rotation(self) -> Sequence[Tuple[Pauli, int]]:
793806
# 180 degree rotation
794807
output.append((next_pauli, 2))
795808
# 90 degree rotation about some axis
796-
if self.transform(next_pauli).flip:
809+
if self.pauli_tuple(next_pauli)[1]:
797810
# Negative 90 degree rotation
798811
output.append((pauli, -1))
799812
else:
@@ -802,16 +815,13 @@ def decompose_rotation(self) -> Sequence[Tuple[Pauli, int]]:
802815
return output
803816
elif num_whole == 0:
804817
# Gate is a 120 degree rotation
805-
if x_rot.to == pauli_gates.Y:
818+
if x_rot[0] == pauli_gates.Y:
806819
return [
807-
(pauli_gates.X, -1 if y_rot.flip else 1),
808-
(pauli_gates.Z, -1 if x_rot.flip else 1),
820+
(pauli_gates.X, -1 if y_rot[1] else 1),
821+
(pauli_gates.Z, -1 if x_rot[1] else 1),
809822
]
810823

811-
return [
812-
(pauli_gates.Z, 1 if y_rot.flip else -1),
813-
(pauli_gates.X, 1 if z_rot.flip else -1),
814-
]
824+
return [(pauli_gates.Z, 1 if y_rot[1] else -1), (pauli_gates.X, 1 if z_rot[1] else -1)]
815825
# coverage: ignore
816826
assert (
817827
False
@@ -824,15 +834,15 @@ def equivalent_gate_before(self, after: 'SingleQubitCliffordGate') -> 'SingleQub
824834
return self.merged_with(after).merged_with(self**-1)
825835

826836
def __repr__(self) -> str:
827-
x = self.transform(pauli_gates.X)
828-
y = self.transform(pauli_gates.Y)
829-
z = self.transform(pauli_gates.Z)
830-
x_sign = '-' if x.flip else '+'
831-
y_sign = '-' if y.flip else '+'
832-
z_sign = '-' if z.flip else '+'
837+
x = self.pauli_tuple(pauli_gates.X)
838+
y = self.pauli_tuple(pauli_gates.Y)
839+
z = self.pauli_tuple(pauli_gates.Z)
840+
x_sign = '-' if x[1] else '+'
841+
y_sign = '-' if y[1] else '+'
842+
z_sign = '-' if z[1] else '+'
833843
return (
834-
f'cirq.SingleQubitCliffordGate(X:{x_sign}{x.to!s}, '
835-
f'Y:{y_sign}{y.to!s}, Z:{z_sign}{z.to!s})'
844+
f'cirq.SingleQubitCliffordGate(X:{x_sign}{x[0]!s}, '
845+
f'Y:{y_sign}{y[0]!s}, Z:{z_sign}{z[0]!s})'
836846
)
837847

838848
def _circuit_diagram_info_(

0 commit comments

Comments
 (0)