Source code for pprop.gates.rotation

"""
This submodule defines :class:`RotationGate`, the base class for single-qubit
parametrised Pauli rotation gates, and the concrete gates :class:`RX`,
:class:`RY`, and :class:`RZ`.
"""
from typing import Dict, List, Tuple

from numpy import cos, integer, intp, sin
from pennylane import RX as qmlRX
from pennylane import RY as qmlRY
from pennylane import RZ as qmlRZ

from ..pauli.op import PauliOp
from ..pauli.sentence import CoeffTerms, PauliDict
from .base import Gate
from .utils import get_frequency

# Rule type: maps a single-qubit Pauli label to (output_label, sign).
# Absent labels commute with the rotation axis and pass through unchanged.
EvolutionRule = Dict[str, Tuple[str, int]]


[docs] class RotationGate(Gate): """ Base class for single-qubit parametrised Pauli rotation gates. A rotation gate :math:`R_P(\\theta) = e^{-i\\theta P/2}` conjugates a Pauli word :math:`Q` according to: .. math:: R_P^\\dagger\\, Q\\, R_P = \\begin{cases} Q & \\text{if } [Q, P] = 0 \\\\ \\cos(\\theta)\\, Q + \\sigma \\sin(\\theta)\\, Q' & \\text{if } \\{Q, P\\} = 0 \\end{cases} where :math:`Q'` is the Pauli obtained by applying the gate rule and :math:`\\sigma \\in \\{+1, -1\\}` is the sign given by the commutation relation. The trigonometric factors are encoded by appending the parameter index to the ``cos_idx`` or ``sin_idx`` lists of each :data:`CoeffTerm`. Parameters ---------- wires : list[int] Qubit on which the gate acts. qml_gate : pennylane.operation.Operator Corresponding PennyLane gate class, used for circuit drawing. parameter : float, int Index of :math:`\\theta` in the global parameter vector if int. Actual value of the rotation if float. rule : EvolutionRule Dict mapping a single-qubit Pauli label (``"X"``, ``"Y"``, or ``"Z"``) to a ``(output_label, sign)`` tuple for Paulis that anti-commute with the rotation axis. Labels absent from the dict commute and pass through unchanged. Attributes ---------- rule : EvolutionRule The Heisenberg evolution rule for this gate. """ def __init__( self, wires, qml_gate, parameter, rule: EvolutionRule, ) -> None: super().__init__(wires=wires, qml_gate=qml_gate, parameter=parameter) self.rule = rule
[docs] def evolve(self, word: Tuple[PauliOp, CoeffTerms], k1, k2) -> PauliDict: """ Heisenberg-evolve a Pauli word through this rotation gate. For a Pauli :math:`Q` that anti-commutes with the rotation axis, the evolution produces two branches: .. math:: Q \\;\\mapsto\\; \\cos(\\theta)\\, Q \\;+\\; \\sigma\\sin(\\theta)\\, Q' Each branch is implemented by appending the gate's ``parameter`` to the ``cos_idx`` (cosine branch, same Pauli) or ``sin_idx`` (sine branch, new Pauli) of every existing :data:`CoeffTerm`. If the frequency cutoff ``k2`` is set and any term already has frequency :math:`\\geq k2`, the word is discarded entirely (returning an empty :class:`~pprop.pauli.sentence.PauliDict`) since appending one more trig factor would exceed the cutoff. Parameters ---------- word : tuple[PauliOp, CoeffTerms] ``(pauliop, coeff_terms)`` pair to evolve. k1 : int or None Pauli weight cutoff (unused, rotation gates do not change weight). k2 : int or None Frequency cutoff. Terms at or above this frequency are discarded. Returns ------- PauliDict Empty if truncated by ``k2``; one entry if the word commutes with the gate; two entries (cos and sin branches) otherwise. """ op, coeff_terms = word wire = self.wires[0] pauli = op[wire] rule = self.rule.get(pauli, None) # If no rule exists this Pauli commutes with the gate, pass through unchanged. if rule is None: return PauliDict({op: coeff_terms}) output_label, sign = rule new_op = op.copy() new_op.set(wire, output_label) if isinstance(self.parameter, (integer, intp)): # Discard if adding one more trig factor would exceed the frequency cutoff. if k2 is not None and get_frequency(coeff_terms[0]) >= k2: return PauliDict() # Cosine branch: original Pauli survives, each term gains a cos(θ) factor. cos_terms: CoeffTerms = [ (c, list(s), list(cc) + [self.parameter]) for c, s, cc in coeff_terms ] # Sine branch: new Pauli appears, each term gains a sign * sin(θ) factor. sin_terms: CoeffTerms = [ (sign * c, list(s) + [self.parameter], list(cc)) for c, s, cc in coeff_terms ] else: # Cosine branch: original Pauli survives, each term gains a cos(θ) factor. cos_terms: CoeffTerms = [ (c * cos(self.parameter), list(s), list(cc)) for c, s, cc in coeff_terms ] # Sine branch: new Pauli appears, each term gains a sign * sin(θ) factor. sin_terms: CoeffTerms = [ (sign * c * sin(self.parameter), list(s), list(cc)) for c, s, cc in coeff_terms ] return PauliDict({op: cos_terms, new_op: sin_terms})
[docs] class RX(RotationGate): r""" The single-qubit parametrised X rotation gate. .. math:: R_x(\phi) = e^{-i\phi\,\sigma_x/2} Heisenberg evolution rules: .. math:: Y \mapsto -\sin(\phi)\,Z + \cos(\phi)\,Y, \quad Z \mapsto +\sin(\phi)\,Y + \cos(\phi)\,Z, \quad X \mapsto X Parameters ---------- wires : list[int] Qubit on which the gate acts. parameter : int Index of :math:`\phi` in the global parameter vector. """ def __init__(self, wires: List[int], parameter: int) -> None: rule: EvolutionRule = { "Y": ("Z", -1), "Z": ("Y", +1), # X commutes with RX, no rule needed. } super().__init__(wires, qmlRX, parameter, rule)
[docs] class RY(RotationGate): r""" The single-qubit parametrised Y rotation gate. .. math:: R_y(\phi) = e^{-i\phi\,\sigma_y/2} Heisenberg evolution rules: .. math:: X \mapsto +\sin(\phi)\,Z + \cos(\phi)\,X, \quad Z \mapsto -\sin(\phi)\,X + \cos(\phi)\,Z, \quad Y \mapsto Y Parameters ---------- wires : list[int] Qubit on which the gate acts. parameter : int Index of :math:`\phi` in the global parameter vector. """ def __init__(self, wires: List[int], parameter: int) -> None: rule: EvolutionRule = { "X": ("Z", +1), "Z": ("X", -1), # Y commutes with RY, no rule needed. } super().__init__(wires, qmlRY, parameter, rule)
[docs] class RZ(RotationGate): r""" The single-qubit parametrised Z rotation gate. .. math:: R_z(\phi) = e^{-i\phi\,\sigma_z/2} Heisenberg evolution rules: .. math:: X \mapsto -\sin(\phi)\,Y + \cos(\phi)\,X, \quad Y \mapsto +\sin(\phi)\,X + \cos(\phi)\,Y, \quad Z \mapsto Z Parameters ---------- wires : list[int] Qubit on which the gate acts. parameter : int Index of :math:`\phi` in the global parameter vector. """ def __init__(self, wires: List[int], parameter: int) -> None: rule: EvolutionRule = { "X": ("Y", -1), "Y": ("X", +1), # Z commutes with RZ, no rule needed. } super().__init__(wires, qmlRZ, parameter, rule)