Source code for qbraid.transpiler.conversions.qasm3.qasm3_to_braket

# Copyright 2025 qBraid
#
# 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.

"""
Module for converting Braket circuits to/from OpenQASM 3

"""
from __future__ import annotations

from typing import TYPE_CHECKING

import pyqasm
from openqasm3 import ast
from openqasm3.visitor import QASMVisitor
from pyqasm.exceptions import ValidationError
from qbraid_core._import import LazyLoader

from qbraid.passes.qasm.compat import (
    remove_stdgates_include,
    replace_gate_names,
)
from qbraid.programs.exceptions import QasmError
from qbraid.transpiler.annotations import weight

braket_circuits = LazyLoader("braket_circuits", globals(), "braket.circuits")
braket_openqasm = LazyLoader("braket_openqasm", globals(), "braket.ir.openqasm")

if TYPE_CHECKING:
    import braket.circuits

    from qbraid.programs.typer import Qasm3StringType


class _DelayFinder(QASMVisitor):
    """Records whether a program contains a ``delay`` instruction."""

    def __init__(self):
        self.found = False

    # pylint: disable-next=invalid-name,unused-argument
    def visit_DelayInstruction(self, node: ast.DelayInstruction, context=None) -> None:
        """Record that the program contains a delay instruction."""
        self.found = True


def contains_delay(program: ast.Program) -> bool:
    """Check whether a parsed OpenQASM 3 program contains a ``delay`` instruction.

    Traverses the AST directly so nested delays (e.g. inside a ``box``) are caught too.

    Args:
        program: A parsed OpenQASM 3 AST (e.g. ``pyqasm.loads(qasm).original_program``).

    Returns:
        True if the program contains a delay instruction, False otherwise.
    """
    finder = _DelayFinder()
    finder.visit(program)
    return finder.found


def transform_notation(qasm3: str) -> str:
    """
    Process an OpenQASM 3 program that was generated by
    an external tool to make it compatible with Amazon Braket.

    """
    replacements = {
        "cx": "cnot",
        "ccx": "ccnot",
        "sdg": "si",
        "tdg": "ti",
        "sx": "v",
        "id": "i",
        "sxdg": "vi",
        "p": "phaseshift",
        "cp": "cphaseshift",
    }

    qasm3 = remove_stdgates_include(qasm3)
    qasm3 = replace_gate_names(qasm3, replacements)
    return qasm3


[docs] @weight(1) def qasm3_to_braket(qasm: Qasm3StringType) -> braket.circuits.Circuit: """Converts an OpenQASM 3.0 string to a ``braket.circuits.Circuit``. Args: qasm: OpenQASM 3 string Returns: The Amazon Braket circuit equivalent to the input OpenQASM 3.0 string Raises: QasmError: If qasm to braket conversion fails, or if the program contains a delay instruction, which Braket cannot represent. """ prior_errors: list[tuple[str, BaseException]] = [] try: module = pyqasm.loads(qasm) except ValidationError as err: module = None prior_errors.append(("PyQASM validation", err)) # Braket has no gate-level delay and silently drops delay instructions inside # Circuit.from_ir, producing a circuit that runs but measures the wrong thing. Detect # delays up front and fail loudly. ``original_program`` is populated by ``loads`` even # when a later ``unroll`` would reject the program (e.g. a verbatim box), so this reuses # the parse pyqasm already did without a second one and still catches nested delays. if module is not None and contains_delay(module.original_program): raise QasmError("Delay instructions are not supported by Amazon Braket.") if module is not None: try: module.unroll() qasm = pyqasm.dumps(module) except ValidationError as err: prior_errors.append(("PyQASM validation", err)) try: qasm = transform_notation(qasm) except Exception as err: # pylint: disable=broad-exception-caught prior_errors.append(("Transform", err)) try: program = braket_openqasm.Program(source=qasm) return braket_circuits.Circuit.from_ir(source=program.source, inputs=program.inputs) except Exception as err: msg = f"Error converting qasm3 string to braket circuit: {err}" if prior_errors: msg += ". Prior errors: " + "; ".join(f"{label}: {e}" for label, e in prior_errors) raise QasmError(msg) from err