Source code for mimiqcircuits.decomposition.rules.special_angle

#
# Copyright © 2022-2024 University of Strasbourg. All Rights Reserved.
# Copyright © 2023-2025 QPerfect. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Special angle rewrite rule for Clifford+T decomposition."""

from __future__ import annotations

import math
from dataclasses import dataclass
from typing import TYPE_CHECKING, Sequence

from mimiqcircuits.decomposition.abstract import DecompositionError, RewriteRule

if TYPE_CHECKING:
    from mimiqcircuits import Circuit, Operation


def _get_pi_factor(val) -> int | None:
    """Check if value is a multiple of pi/4 and return the factor k in {0,1,...,7}.

    Args:
        val: The angle value to check.

    Returns:
        The factor k if val = k * pi/4, or None if not a special angle.
    """
    import symengine as se
    import sympy as sp

    # Check for symbolic values
    if isinstance(val, (se.Basic, sp.Basic)):
        try:
            val = complex(val).real
        except (TypeError, ValueError):
            return None

    # Check for multiples of pi/4
    factor = val / (math.pi / 4)
    if not math.isfinite(factor):
        return None
    k = round(factor)
    if math.isclose(val, k * (math.pi / 4), abs_tol=1e-10):
        return k % 8
    return None


[docs] @dataclass(frozen=True, slots=True) class SpecialAngleRewrite(RewriteRule): """Rewrite rule for decomposing rotations with special angles to Clifford+T. Decomposes single-qubit rotations whose angle is a multiple of pi/4 into explicit Clifford or Clifford+T gates. This rule only matches rotations with angles k*pi/4 (or k*pi/2 in Clifford-only mode). Generic rotations with arbitrary angles are not matched. Args: only_cliffords: If True, only match angles k*pi/2 (Clifford gates only). If False (default), match angles k*pi/4 (Clifford+T). Supported operations: - ``GateRX(theta)`` where theta = k*pi/4 - ``GateRY(theta)`` where theta = k*pi/4 - ``GateRZ(lambda)`` where lambda = k*pi/4 Example: >>> from mimiqcircuits import GateRZ >>> from mimiqcircuits.decomposition import SpecialAngleRewrite >>> from symengine import pi >>> rule = SpecialAngleRewrite() >>> rule.matches(GateRZ(pi/4)) # True, decomposes to T True >>> rule.matches(GateRZ(0.123)) # False, not a special angle False """ only_cliffords: bool = False
[docs] def matches(self, op: Operation) -> bool: """Check if this rule can decompose the operation. Args: op: The operation to check. Returns: True if the operation is a rotation with a special angle. """ import mimiqcircuits as mc if isinstance(op, mc.GateRX): k = _get_pi_factor(op.theta) elif isinstance(op, mc.GateRY): k = _get_pi_factor(op.theta) elif isinstance(op, mc.GateRZ): k = _get_pi_factor(op.lmbda) else: return False if k is None: return False # In Clifford-only mode, only match even multiples (k*pi/2) if self.only_cliffords: return k % 2 == 0 return True
[docs] def decompose_step( self, op: Operation, qubits: Sequence[int], bits: Sequence[int], zvars: Sequence[int], ) -> Circuit: """Decompose the rotation to Clifford+T gates. Args: op: The rotation operation to decompose. qubits: Qubit indices for the operation. bits: Classical bit indices (unused). zvars: Z-variable indices (unused). Returns: A Circuit containing the decomposed instructions. Raises: DecompositionError: If the operation cannot be decomposed. """ import mimiqcircuits as mc circ = mc.Circuit() q = qubits[0] if isinstance(op, mc.GateRZ): k = _get_pi_factor(op.lmbda) if k is None: raise DecompositionError( f"SpecialAngleRewrite: RZ angle {op.lmbda} is not a multiple of pi/4" ) self._decompose_rz(circ, q, k) elif isinstance(op, mc.GateRX): k = _get_pi_factor(op.theta) if k is None: raise DecompositionError( f"SpecialAngleRewrite: RX angle {op.theta} is not a multiple of pi/4" ) self._decompose_rx(circ, q, k) elif isinstance(op, mc.GateRY): k = _get_pi_factor(op.theta) if k is None: raise DecompositionError( f"SpecialAngleRewrite: RY angle {op.theta} is not a multiple of pi/4" ) self._decompose_ry(circ, q, k) else: raise DecompositionError( f"SpecialAngleRewrite cannot decompose {type(op).__name__}" ) return circ
def _decompose_rz(self, circ: Circuit, q: int, k: int) -> None: """Decompose RZ(k*pi/4) to Clifford+T gates.""" import mimiqcircuits as mc # RZ decomposition table for k in {0,1,...,7}: # k=0: identity # k=1: T # k=2: S # k=3: S*T # k=4: Z # k=5: Z*T = S*S*T # k=6: Sdg = S*S*S # k=7: Tdg match k: case 0: pass # identity case 1: circ.push(mc.GateT(), q) case 2: circ.push(mc.GateS(), q) case 3: circ.push(mc.GateS(), q) circ.push(mc.GateT(), q) case 4: circ.push(mc.GateZ(), q) case 5: circ.push(mc.GateZ(), q) circ.push(mc.GateT(), q) case 6: circ.push(mc.GateSDG(), q) case 7: circ.push(mc.GateTDG(), q) def _decompose_rx(self, circ: Circuit, q: int, k: int) -> None: """Decompose RX(k*pi/4) to Clifford+T gates.""" import mimiqcircuits as mc # TX = RX(pi/4) = H*T*H def push_tx(): circ.push(mc.GateH(), q) circ.push(mc.GateT(), q) circ.push(mc.GateH(), q) # TXdg = RX(-pi/4) = H*Tdg*H def push_txdg(): circ.push(mc.GateH(), q) circ.push(mc.GateTDG(), q) circ.push(mc.GateH(), q) # RX decomposition table for k in {0,1,...,7}: # k=0: identity # k=1: TX # k=2: SX # k=3: SX*TX # k=4: X # k=5: X*TX # k=6: SXdg # k=7: TXdg match k: case 0: pass # identity case 1: push_tx() case 2: circ.push(mc.GateSX(), q) case 3: circ.push(mc.GateSX(), q) push_tx() case 4: circ.push(mc.GateX(), q) case 5: circ.push(mc.GateX(), q) push_tx() case 6: circ.push(mc.GateSXDG(), q) case 7: push_txdg() def _decompose_ry(self, circ: Circuit, q: int, k: int) -> None: """Decompose RY(k*pi/4) to Clifford+T gates.""" import mimiqcircuits as mc # TY = RY(pi/4) = Sdg*H*T*H*S def push_ty(): circ.push(mc.GateSDG(), q) circ.push(mc.GateH(), q) circ.push(mc.GateT(), q) circ.push(mc.GateH(), q) circ.push(mc.GateS(), q) # TYdg = RY(-pi/4) = Sdg*H*Tdg*H*S def push_tydg(): circ.push(mc.GateSDG(), q) circ.push(mc.GateH(), q) circ.push(mc.GateTDG(), q) circ.push(mc.GateH(), q) circ.push(mc.GateS(), q) # RY decomposition table for k in {0,1,...,7}: # k=0: identity # k=1: TY # k=2: SY # k=3: SY*TY # k=4: Y # k=5: Y*TY # k=6: SYdg # k=7: TYdg match k: case 0: pass # identity case 1: push_ty() case 2: circ.push(mc.GateSY(), q) case 3: circ.push(mc.GateSY(), q) push_ty() case 4: circ.push(mc.GateY(), q) case 5: circ.push(mc.GateY(), q) push_ty() case 6: circ.push(mc.GateSYDG(), q) case 7: push_tydg()
__all__ = ["SpecialAngleRewrite"]