Skip to content

Commit

Permalink
Deprecate PauliTransform (quantumlib#5498)
Browse files Browse the repository at this point in the history
- PauliTransform is only used with SingleQubitCliffordGate,
is not easily extensible for multi-qubit gates, and has
been obsoleted by DensePauliString

Fixes: quantumlib#4088
  • Loading branch information
dstrain115 authored Jun 16, 2022
1 parent 260bd29 commit 795860a
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 114 deletions.
6 changes: 3 additions & 3 deletions cirq/contrib/paulistring/clifford_optimize.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ def try_merge_clifford(cliff_op: ops.GateOperation, start_i: int) -> bool:
for pauli, quarter_turns in reversed(
cast(ops.SingleQubitCliffordGate, cliff_op.gate).decompose_rotation()
):
trans = remaining_cliff_gate.transform(pauli)
pauli = trans.to
quarter_turns *= -1 if trans.flip else 1
trans = remaining_cliff_gate.pauli_tuple(pauli)
pauli = trans[0]
quarter_turns *= -1 if trans[1] else 1
string_op = ops.PauliStringPhasor(
ops.PauliString(pauli(cliff_op.qubits[0])), exponent_neg=quarter_turns / 2
)
Expand Down
146 changes: 78 additions & 68 deletions cirq/ops/clifford_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import (
Any,
cast,
Dict,
List,
NamedTuple,
Optional,
Sequence,
Tuple,
TYPE_CHECKING,
Union,
)
import dataclasses
from typing import Any, cast, Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union


import numpy as np

from cirq import protocols, value, linalg, qis
from cirq._doc import document
from cirq import _compat, protocols, value, linalg, qis
from cirq._import import LazyLoader
from cirq.ops import common_gates, identity, named_qubit, raw_types, pauli_gates, phased_x_z_gate
from cirq.ops.pauli_gates import Pauli
Expand All @@ -42,29 +32,33 @@
sim = LazyLoader("sim", globals(), "cirq.sim")
transformers = LazyLoader("transformers", globals(), "cirq.transformers")

PauliTransform = NamedTuple('PauliTransform', [('to', Pauli), ('flip', bool)])
document(PauliTransform, """+X, -X, +Y, -Y, +Z, or -Z.""")

@_compat.deprecated_class(deadline='v0.16', fix='Use DensePauliString instead.')
@dataclasses.dataclass
class PauliTransform:
to: Pauli
flip: bool


def _to_pauli_transform(matrix: np.ndarray) -> Optional[PauliTransform]:
def _to_pauli_tuple(matrix: np.ndarray) -> Optional[Tuple[Pauli, bool]]:
"""Converts matrix to PauliTransform.
If matrix is not ±Pauli matrix, returns None.
"""
for pauli in Pauli._XYZ:
p = protocols.unitary(pauli)
if np.allclose(matrix, p):
return PauliTransform(pauli, False)
return (pauli, False)
if np.allclose(matrix, -p):
return PauliTransform(pauli, True)
return (pauli, True)
return None


def _to_clifford_tableau(
rotation_map: Optional[Dict[Pauli, PauliTransform]] = None,
rotation_map: Optional[Dict[Pauli, Tuple[Pauli, bool]]] = None,
*,
x_to: Optional[PauliTransform] = None,
z_to: Optional[PauliTransform] = None,
x_to: Optional[Tuple[Pauli, bool]] = None,
z_to: Optional[Tuple[Pauli, bool]] = None,
) -> qis.CliffordTableau:
"""Transfer the rotation map to clifford tableau representation"""
if x_to is None and z_to is None and rotation_map is None:
Expand All @@ -79,13 +73,13 @@ def _to_clifford_tableau(
assert x_to is not None and z_to is not None, "Both x_to and z_to have to be provided."

clifford_tableau = qis.CliffordTableau(num_qubits=1)
clifford_tableau.xs[0, 0] = x_to.to in (pauli_gates.X, pauli_gates.Y)
clifford_tableau.zs[0, 0] = x_to.to in (pauli_gates.Y, pauli_gates.Z)
clifford_tableau.xs[0, 0] = x_to[0] in (pauli_gates.X, pauli_gates.Y)
clifford_tableau.zs[0, 0] = x_to[0] in (pauli_gates.Y, pauli_gates.Z)

clifford_tableau.xs[1, 0] = z_to.to in (pauli_gates.X, pauli_gates.Y)
clifford_tableau.zs[1, 0] = z_to.to in (pauli_gates.Y, pauli_gates.Z)
clifford_tableau.xs[1, 0] = z_to[0] in (pauli_gates.X, pauli_gates.Y)
clifford_tableau.zs[1, 0] = z_to[0] in (pauli_gates.Y, pauli_gates.Z)

clifford_tableau.rs = (x_to.flip, z_to.flip)
clifford_tableau.rs = (x_to[1], z_to[1])
return clifford_tableau


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


def _pad_tableau(
Expand Down Expand Up @@ -505,7 +499,7 @@ def from_xz_map(
z_to: Which Pauli to transform Z to and if it should negate.
"""
return SingleQubitCliffordGate.from_clifford_tableau(
_to_clifford_tableau(x_to=PauliTransform(*x_to), z_to=PauliTransform(*z_to))
_to_clifford_tableau(x_to=x_to, z_to=z_to)
)

@staticmethod
Expand Down Expand Up @@ -538,7 +532,7 @@ def from_single_map(
trans_from2 = trans_to
trans_to2 = trans_from
flip2 = not flip
rotation_map[trans_from2] = PauliTransform(trans_to2, flip2)
rotation_map[trans_from2] = (trans_to2, flip2)
return SingleQubitCliffordGate.from_double_map(
cast(Dict[Pauli, Tuple[Pauli, bool]], rotation_map)
)
Expand Down Expand Up @@ -566,9 +560,9 @@ def from_double_map(
rotation_map = _validate_map_input(2, pauli_map_to, x_to=x_to, y_to=y_to, z_to=z_to)
(from1, trans1), (from2, trans2) = tuple(rotation_map.items())
from3 = from1.third(from2)
to3 = trans1.to.third(trans2.to)
flip3 = trans1.flip ^ trans2.flip ^ ((from1 < from2) != (trans1.to < trans2.to))
rotation_map[from3] = PauliTransform(to3, flip3)
to3 = trans1[0].third(trans2[0])
flip3 = trans1[1] ^ trans2[1] ^ ((from1 < from2) != (trans1[0] < trans2[0]))
rotation_map[from3] = (to3, flip3)

return SingleQubitCliffordGate.from_clifford_tableau(_to_clifford_tableau(rotation_map))

Expand All @@ -578,15 +572,15 @@ def from_pauli(pauli: Pauli, sqrt: bool = False) -> 'SingleQubitCliffordGate':
next_pauli = Pauli.by_relative_index(pauli, 1)
if sqrt:
rotation_map = {
prev_pauli: PauliTransform(next_pauli, True),
pauli: PauliTransform(pauli, False),
next_pauli: PauliTransform(prev_pauli, False),
prev_pauli: (next_pauli, True),
pauli: (pauli, False),
next_pauli: (prev_pauli, False),
}
else:
rotation_map = {
prev_pauli: PauliTransform(prev_pauli, True),
pauli: PauliTransform(pauli, False),
next_pauli: PauliTransform(next_pauli, True),
prev_pauli: (prev_pauli, True),
pauli: (pauli, False),
next_pauli: (next_pauli, True),
}
return SingleQubitCliffordGate.from_clifford_tableau(_to_clifford_tableau(rotation_map))

Expand Down Expand Up @@ -618,15 +612,22 @@ def from_unitary(u: np.ndarray) -> Optional['SingleQubitCliffordGate']:
return None
x = protocols.unitary(pauli_gates.X)
z = protocols.unitary(pauli_gates.Z)
x_to = _to_pauli_transform(u @ x @ u.conj().T)
z_to = _to_pauli_transform(u @ z @ u.conj().T)
x_to = _to_pauli_tuple(u @ x @ u.conj().T)
z_to = _to_pauli_tuple(u @ z @ u.conj().T)
if x_to is None or z_to is None:
return None
return SingleQubitCliffordGate.from_clifford_tableau(
_to_clifford_tableau(x_to=x_to, z_to=z_to)
)

def transform(self, pauli: Pauli) -> PauliTransform:
def pauli_tuple(self, pauli: Pauli) -> Tuple[Pauli, bool]:
"""Returns a tuple of a Pauli operator and a boolean.
The pauli is the operator of the transform and the boolean
determines whether the operator should be flipped. For instance,
it is True if the coefficient is -1, and False if the coefficient
is 1.
"""
x_to = self._clifford_tableau.destabilizers()[0]
z_to = self._clifford_tableau.stabilizers()[0]
if pauli == pauli_gates.X:
Expand All @@ -638,7 +639,19 @@ def transform(self, pauli: Pauli) -> PauliTransform:
to._coefficient *= 1j
# pauli_mask returns a value between 0 and 4 for [I, X, Y, Z].
to_gate = Pauli._XYZ[to.pauli_mask[0] - 1]
return PauliTransform(to=to_gate, flip=bool(to.coefficient != 1.0))
return (to_gate, bool(to.coefficient != 1.0))

def dense_pauli_string(self, pauli: Pauli) -> 'cirq.DensePauliString':
from cirq.ops import dense_pauli_string

pauli_tuple = self.pauli_tuple(pauli)
coefficient = -1 if pauli_tuple[1] else 1
return dense_pauli_string.DensePauliString(str(pauli_tuple[0]), coefficient=coefficient)

@_compat.deprecated(deadline='v0.16', fix='Use pauli_tuple() or dense_pauli_string() instead')
def transform(self, pauli: Pauli) -> PauliTransform:
pauli_tuple = self.pauli_tuple(pauli)
return PauliTransform(to=pauli_tuple[0], flip=pauli_tuple[1])

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

def commutes_with_pauli(self, pauli: Pauli) -> bool:
to, flip = self.transform(pauli)
to, flip = self.pauli_tuple(pauli)
return to == pauli and not flip

def merged_with(self, second: 'SingleQubitCliffordGate') -> 'SingleQubitCliffordGate':
Expand All @@ -764,16 +777,16 @@ def decompose_rotation(self) -> Sequence[Tuple[Pauli, int]]:
"""Returns ((first_rotation_axis, first_rotation_quarter_turns), ...)
This is a sequence of zero, one, or two rotations."""
x_rot = self.transform(pauli_gates.X)
y_rot = self.transform(pauli_gates.Y)
z_rot = self.transform(pauli_gates.Z)
x_rot = self.pauli_tuple(pauli_gates.X)
y_rot = self.pauli_tuple(pauli_gates.Y)
z_rot = self.pauli_tuple(pauli_gates.Z)
whole_arr = (
x_rot.to == pauli_gates.X,
y_rot.to == pauli_gates.Y,
z_rot.to == pauli_gates.Z,
x_rot[0] == pauli_gates.X,
y_rot[0] == pauli_gates.Y,
z_rot[0] == pauli_gates.Z,
)
num_whole = sum(whole_arr)
flip_arr = (x_rot.flip, y_rot.flip, z_rot.flip)
flip_arr = (x_rot[1], y_rot[1], z_rot[1])
num_flip = sum(flip_arr)
if num_whole == 3:
if num_flip == 0:
Expand All @@ -793,7 +806,7 @@ def decompose_rotation(self) -> Sequence[Tuple[Pauli, int]]:
# 180 degree rotation
output.append((next_pauli, 2))
# 90 degree rotation about some axis
if self.transform(next_pauli).flip:
if self.pauli_tuple(next_pauli)[1]:
# Negative 90 degree rotation
output.append((pauli, -1))
else:
Expand All @@ -802,16 +815,13 @@ def decompose_rotation(self) -> Sequence[Tuple[Pauli, int]]:
return output
elif num_whole == 0:
# Gate is a 120 degree rotation
if x_rot.to == pauli_gates.Y:
if x_rot[0] == pauli_gates.Y:
return [
(pauli_gates.X, -1 if y_rot.flip else 1),
(pauli_gates.Z, -1 if x_rot.flip else 1),
(pauli_gates.X, -1 if y_rot[1] else 1),
(pauli_gates.Z, -1 if x_rot[1] else 1),
]

return [
(pauli_gates.Z, 1 if y_rot.flip else -1),
(pauli_gates.X, 1 if z_rot.flip else -1),
]
return [(pauli_gates.Z, 1 if y_rot[1] else -1), (pauli_gates.X, 1 if z_rot[1] else -1)]
# coverage: ignore
assert (
False
Expand All @@ -824,15 +834,15 @@ def equivalent_gate_before(self, after: 'SingleQubitCliffordGate') -> 'SingleQub
return self.merged_with(after).merged_with(self**-1)

def __repr__(self) -> str:
x = self.transform(pauli_gates.X)
y = self.transform(pauli_gates.Y)
z = self.transform(pauli_gates.Z)
x_sign = '-' if x.flip else '+'
y_sign = '-' if y.flip else '+'
z_sign = '-' if z.flip else '+'
x = self.pauli_tuple(pauli_gates.X)
y = self.pauli_tuple(pauli_gates.Y)
z = self.pauli_tuple(pauli_gates.Z)
x_sign = '-' if x[1] else '+'
y_sign = '-' if y[1] else '+'
z_sign = '-' if z[1] else '+'
return (
f'cirq.SingleQubitCliffordGate(X:{x_sign}{x.to!s}, '
f'Y:{y_sign}{y.to!s}, Z:{z_sign}{z.to!s})'
f'cirq.SingleQubitCliffordGate(X:{x_sign}{x[0]!s}, '
f'Y:{y_sign}{y[0]!s}, Z:{z_sign}{z[0]!s})'
)

def _circuit_diagram_info_(
Expand Down
Loading

0 comments on commit 795860a

Please sign in to comment.