Source code for mimiqcircuits.decomposition.basis.qasm

#
# 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.
#
"""OpenQASM 2.0 decomposition basis."""

from __future__ import annotations

from functools import lru_cache
from typing import TYPE_CHECKING, Sequence

from mimiqcircuits.decomposition.abstract import DecompositionBasis, DecompositionError

if TYPE_CHECKING:
    from mimiqcircuits import Circuit, Operation


# Terminality per operation type, for the types where the type alone decides
# it. Filled on first use and shared by all instances.
_TERMINAL_BY_TYPE: dict[type, bool] = {}


@lru_cache(maxsize=None)
def _qasm_terminal_types():
    import mimiqcircuits as mc

    return (
        # OpenQASM 2.0 built-ins
        mc.GateU,
        mc.GateCX,
        # qelib1.inc, single qubit
        mc.GateU3,
        mc.GateU2,
        mc.GateU1,
        mc.GateID,
        mc.GateX,
        mc.GateY,
        mc.GateZ,
        mc.GateH,
        mc.GateS,
        mc.GateSDG,
        mc.GateT,
        mc.GateTDG,
        mc.GateRX,
        mc.GateRY,
        mc.GateRZ,
        # qelib1.inc, two qubits
        mc.GateCY,
        mc.GateCZ,
        mc.GateCH,
        mc.GateCRX,
        mc.GateCRY,
        mc.GateCRZ,
        mc.GateSWAP,
        mc.GateRXX,
        mc.GateRZZ,
        # qelib1.inc, three and four qubits
        mc.GateCCX,
        mc.GateCSWAP,
        mc.GateC3X,
        mc.Barrier,
    )


[docs] class QASMBasis(DecompositionBasis): """Decomposition basis targeting the OpenQASM 2.0 gate library. The terminal gates are the OpenQASM 2.0 built-ins and the gates of the ``qelib1.inc`` include file that have a direct MIMIQ counterpart, so a decomposed circuit can be written as OpenQASM 2.0 and read back by any parser that ships the standard ``qelib1.inc``. Terminal Operations ------------------- **Fundamental gates (OpenQASM 2.0 built-in):** - ``GateU``, ``GateCX`` **Single-qubit gates from qelib1.inc:** - Legacy U gates: ``GateU3``, ``GateU2``, ``GateU1`` - Pauli: ``GateID``, ``GateX``, ``GateY``, ``GateZ`` - Hadamard: ``GateH`` - Phase: ``GateS``, ``GateSDG``, ``GateT``, ``GateTDG`` - Rotations: ``GateRX``, ``GateRY``, ``GateRZ`` **Two-qubit gates from qelib1.inc:** - Controlled Paulis and Hadamard: ``GateCY``, ``GateCZ``, ``GateCH`` - Controlled rotations: ``GateCRX``, ``GateCRY``, ``GateCRZ`` - ``GateSWAP`` - Ising couplings: ``GateRXX``, ``GateRZZ`` **Three- and four-qubit gates from qelib1.inc:** - ``GateCCX``, ``GateCSWAP``, ``GateC3X`` **Non-unitary operations:** - ``Measure``, ``Reset``, ``Barrier`` - ``IfStatement`` whose inner operation is terminal **Custom gates:** - ``GateCall`` whose declaration contains only terminal operations Every other operation is decomposed through :class:`CanonicalRewrite`. Operations that OpenQASM 2.0 cannot express and that have no decomposition into the gates above (noise channels, classical operations, annotations, Z-register operations, ...) raise :class:`DecompositionError`. ``MeasureReset`` decomposes into ``Measure`` followed by ``Reset``. This leaves the same state and bit as its canonical decomposition, a measurement and an ``X`` conditioned on its outcome, and unlike that conditional it can be written for a circuit of any number of bits. OpenQASM 2.0 has no loops, so ``WhileStatement`` raises :class:`DecompositionError`. Example: >>> from mimiqcircuits import Circuit, GateH, GateCCX >>> from mimiqcircuits.decomposition import decompose, QASMBasis >>> c = Circuit() >>> c.push(GateH(), 0) 1-qubit circuit with 1 instruction: └── H @ q[0] <BLANKLINE> >>> c.push(GateCCX(), 0, 1, 2) 3-qubit circuit with 2 instructions: ├── H @ q[0] └── C₂X @ q[0:1], q[2] <BLANKLINE> >>> # Decompose to QASM-compatible gates >>> qasm_circuit = decompose(c, QASMBasis()) See Also: :class:`CanonicalBasis`, :class:`CliffordTBasis`, :class:`StimBasis` """
[docs] def isterminal(self, op: Operation) -> bool: """Check if an operation is terminal in the OpenQASM 2.0 basis. Args: op: The operation to check. Returns: True if the operation is in the QASM gate set. """ # Terminality depends only on the operation's type, except for gate # calls and conditionals, which are decided by what they wrap. Caching the type-only answers skips the chain of # isinstance checks, each of which goes through ABCMeta. t = type(op) known = _TERMINAL_BY_TYPE.get(t) if known is not None: return known import mimiqcircuits as mc # Measure and Reset are matched exactly: their X/Y-basis variants are # not part of OpenQASM 2.0 and must be decomposed. if type(op) in (mc.Measure, mc.Reset): return True if isinstance(op, mc.GateCall): return all( self.isterminal(inst.get_operation()) for inst in op.decl.circuit ) if isinstance(op, mc.IfStatement): inner_op = op.get_operation() return inner_op is not None and self.isterminal(inner_op) known = isinstance(op, _qasm_terminal_types()) _TERMINAL_BY_TYPE[t] = known return known
[docs] def decompose( self, op: Operation, qubits: Sequence[int], bits: Sequence[int], zvars: Sequence[int], ) -> Circuit: """Decompose a non-terminal operation using canonical rewrite. Args: op: The operation to decompose. qubits: Qubit indices for the operation. bits: Classical bit indices for the operation. zvars: Z-variable indices for the operation. Returns: A Circuit containing the decomposed instructions. Raises: DecompositionError: If the operation cannot be decomposed. """ import mimiqcircuits as mc from mimiqcircuits.decomposition.rules import CanonicalRewrite if type(op) is mc.MeasureReset: c = mc.Circuit() c.push(mc.Measure(), qubits[0], bits[0]) c.push(mc.Reset(), qubits[0]) return c if isinstance(op, mc.WhileStatement): raise DecompositionError( "WhileStatement cannot be decomposed into the OpenQASM 2.0 basis, " "which has no loops." ) rule = CanonicalRewrite() if rule.matches(op): return rule.decompose_step(op, qubits, bits, zvars) raise DecompositionError( f"Operation {op.name} cannot be decomposed into the OpenQASM 2.0 basis." )
__all__ = ["QASMBasis"]