Skip to content
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
1 change: 1 addition & 0 deletions cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@
PhaseGradientGate,
PhasedISwapPowGate,
PhasedXPowGate,
PhasedXZGate,
PhaseFlipChannel,
QFT,
Qid,
Expand Down
3 changes: 3 additions & 0 deletions cirq/ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@
from cirq.ops.phased_x_gate import (
PhasedXPowGate,)

from cirq.ops.phased_x_z_gate import (
PhasedXZGate,)

from cirq.ops.raw_types import (
Gate,
Operation,
Expand Down
184 changes: 184 additions & 0 deletions cirq/ops/phased_x_z_gate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import numbers
from typing import Union, TYPE_CHECKING, Tuple, Optional, Sequence

import numpy as np
import sympy

from cirq import value, ops, protocols, linalg
from cirq.ops import gate_features
from cirq._compat import proper_repr

if TYPE_CHECKING:
import cirq


@value.value_equality(approximate=True)
class PhasedXZGate(gate_features.SingleQubitGate):
"""A single qubit operation expressed as $Z^z Z^a X^x Z^{-a}$.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This expression is matrix multiplication with time going to the left? Would be nice to make this clear so readers don't misinterpret this as a "circuit" with time going to the right. (This is implicit in the constructor docstring for z_exponent, but would be good to be clear up front.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarified.


The above expression is a matrix multiplication with time going to the left.
In quantum circuit notation, this operation decomposes into this circuit:

───Z^(-a)──X^x──Z^a────Z^z───$

The axis phase exponent (a) decides which axis in the XY plane to rotate
around. The amount of rotation around that axis is decided by the x
exponent (x). Then the z exponent (z) decides how much to phase the qubit.
"""

def __init__(self, *, x_exponent: Union[numbers.Real, sympy.Basic],
z_exponent: Union[numbers.Real, sympy.Basic],
axis_phase_exponent: Union[numbers.Real, sympy.Basic]) -> None:
"""
Args:
x_exponent: Determines how much to rotate during the
axis-in-XY-plane rotation. The $x$ in $Z^z Z^a X^x Z^{-a}$.
z_exponent: The amount of phasing to apply after the
axis-in-XY-plane rotation. The $z$ in $Z^z Z^a X^x Z^{-a}$.
axis_phase_exponent: Determines which axis to rotate around during
the axis-in-XY-plane rotation. The $a$ in $Z^z Z^a X^x Z^{-a}$.
"""
self._x_exponent = x_exponent
self._z_exponent = z_exponent
self._axis_phase_exponent = axis_phase_exponent

def _canonical(self) -> 'cirq.PhasedXZGate':
x = self.x_exponent
z = self.z_exponent
a = self.axis_phase_exponent

# Canonicalize X exponent into (-1, +1].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also canonicalize x to [0, 1] and adjust the axis phase accordingly if needed. This is generally how we do things internally so that the pulse amplitude is always a positive number.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged. The exact canonicalization is not too important; it's only really relevant for equality. The user doesn't see it.

if isinstance(x, numbers.Real):
x %= 2
if x > 1:
x -= 2
# Axis phase exponent is irrelevant if there is no X exponent.
if x == 0:
a = 0
# For 180 degree X rotations, the axis phase and z exponent overlap.
if x == 1 and z != 0:
a -= z / 2
z = 0

# Canonicalize Z exponent into (-1, +1].
if isinstance(z, numbers.Real):
z %= 2
if z > 1:
z -= 2

# Canonicalize axis phase exponent into (-0.5, +0.5].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, it would match our internal convention more closely if we instead canonicalize x to [0, 1] and a to (-1, 1].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged. The exact canonicalization is not too important; it's only really relevant for equality. The user doesn't see it.

if isinstance(a, numbers.Real):
a %= 2
if a > 1:
a -= 2
if a <= -0.5:
a += 1
x = -x
elif a > 0.5:
a -= 1
x = -x

return PhasedXZGate(x_exponent=x, z_exponent=z, axis_phase_exponent=a)

@property
def x_exponent(self) -> Union[numbers.Real, sympy.Basic]:
return self._x_exponent

@property
def z_exponent(self) -> Union[numbers.Real, sympy.Basic]:
return self._z_exponent

@property
def axis_phase_exponent(self) -> Union[numbers.Real, sympy.Basic]:
return self._axis_phase_exponent

def _value_equality_values_(self):
c = self._canonical()
return (
value.PeriodicValue(c._x_exponent, 2),
value.PeriodicValue(c._z_exponent, 2),
value.PeriodicValue(c._axis_phase_exponent, 2),
)

@staticmethod
def from_matrix(mat: np.array) -> 'cirq.PhasedXZGate':
pre_phase, rotation, post_phase = (
linalg.deconstruct_single_qubit_matrix_into_angles(mat))
pre_phase /= np.pi
post_phase /= np.pi
rotation /= np.pi
pre_phase -= 0.5
post_phase += 0.5
return PhasedXZGate(x_exponent=rotation,
axis_phase_exponent=-pre_phase,
z_exponent=post_phase + pre_phase)._canonical()

def _qasm_(self, args: 'cirq.QasmArgs',
qubits: Tuple['cirq.Qid', ...]) -> Optional[str]:
from cirq.circuits import qasm_output
qasm_gate = qasm_output.QasmUGate(lmda=0.5 - self._axis_phase_exponent,
theta=self._x_exponent,
phi=self._z_exponent +
self._axis_phase_exponent - 0.5)
return protocols.qasm(qasm_gate, args=args, qubits=qubits)

def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE':
q = qubits[0]
yield ops.Z(q)**-self._axis_phase_exponent
yield ops.X(q)**self._x_exponent
yield ops.Z(q)**(self._axis_phase_exponent + self._z_exponent)

def __pow__(self, exponent: Union[float, int]) -> 'PhasedXZGate':
if exponent == 1:
return self
if exponent == -1:
return PhasedXZGate(
x_exponent=-self._x_exponent,
z_exponent=-self._z_exponent,
axis_phase_exponent=self._z_exponent + self.axis_phase_exponent,
)
return NotImplemented

def _is_parameterized_(self) -> bool:
"""See `cirq.SupportsParameterization`."""
return (protocols.is_parameterized(self._x_exponent) or
protocols.is_parameterized(self._z_exponent) or
protocols.is_parameterized(self._axis_phase_exponent))

def _resolve_parameters_(self, param_resolver) -> 'cirq.PhasedXZGate':
"""See `cirq.SupportsParameterization`."""
return PhasedXZGate(
z_exponent=param_resolver.value_of(self._z_exponent),
x_exponent=param_resolver.value_of(self._x_exponent),
axis_phase_exponent=param_resolver.value_of(
self._axis_phase_exponent),
)

def _phase_by_(self, phase_turns, qubit_index) -> 'cirq.PhasedXZGate':
"""See `cirq.SupportsPhase`."""
assert qubit_index == 0
return PhasedXZGate(x_exponent=self._x_exponent,
z_exponent=self._z_exponent,
axis_phase_exponent=self._axis_phase_exponent +
phase_turns * 2)

def _circuit_diagram_info_(self,
args: 'cirq.CircuitDiagramInfoArgs') -> str:
"""See `cirq.SupportsCircuitDiagramInfo`."""
return (f'PhXZ('
f'a={args.format_real(self._axis_phase_exponent)},'
f'x={args.format_real(self._x_exponent)},'
f'z={args.format_real(self._z_exponent)})')

def __str__(self):
return protocols.circuit_diagram_info(self).wire_symbols[0]

def __repr__(self):
return (f'cirq.PhasedXZGate('
f'axis_phase_exponent={proper_repr(self._axis_phase_exponent)},'
f' x_exponent={proper_repr(self._x_exponent)}, '
f'z_exponent={proper_repr(self._z_exponent)})')

def _json_dict_(self):
return protocols.obj_to_dict_helper(
self, ['axis_phase_exponent', 'x_exponent', 'z_exponent'])
Loading