Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate PauliTransform #5498

Merged
merged 7 commits into from
Jun 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cirq-core/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-core/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