Source code for qtest.qasm

"""Load circuits from OpenQASM so they can be tested directly.

A common workflow is to receive a circuit as an OpenQASM program — emitted by a
compiler, exported from another tool, or checked into a fixtures directory — and
want to assert something about it. These helpers parse OpenQASM 2.0 / 3.0 into a
``qiskit.QuantumCircuit`` that plugs straight into qtest's assertions::

    from qtest import load_qasm, assert_state_close

    qc = load_qasm(qasm_program)          # version auto-detected from the header
    assert_state_close(qc, "bell")

OpenQASM 2.0 loading uses Qiskit's built-in parser. OpenQASM 3.0 loading
additionally requires the ``qiskit-qasm3-import`` package (the optional
``qtest[qasm3]`` extra); a clear error is raised if it is missing.

Imports are lazy, so importing :mod:`qtest.qasm` (or :mod:`qtest`) does not
require Qiskit.
"""

from __future__ import annotations

import re
from pathlib import Path
from typing import Any

_QASM3_MISSING_MSG = (
    "Loading OpenQASM 3.0 requires the 'qiskit-qasm3-import' package. "
    "Install it with:\n  pip install 'qtest[qasm3]'"
)

# Matches the version on the mandatory ``OPENQASM <major>.<minor>;`` header line.
_HEADER_RE = re.compile(r"OPENQASM\s+(\d+)(?:\.\d+)?\s*;", re.IGNORECASE)

_VALID_VERSIONS = frozenset({"auto", "2", "3"})


def _detect_version(program: str) -> str:
    """Return ``"2"`` or ``"3"`` by inspecting the ``OPENQASM`` header."""
    match = _HEADER_RE.search(program)
    if match is None:
        raise ValueError(
            "Could not find an 'OPENQASM <version>;' header; pass version='2' or "
            "version='3' explicitly."
        )
    major = match.group(1)
    if major not in ("2", "3"):
        raise ValueError(f"Unsupported OpenQASM major version {major!r}; expected 2 or 3.")
    return major


[docs] def load_qasm(program: str, version: str = "auto") -> Any: """Parse an OpenQASM *program* string into a ``qiskit.QuantumCircuit``. Parameters ---------- program The OpenQASM source as a string. version ``"auto"`` (default) detects the version from the ``OPENQASM`` header; pass ``"2"`` or ``"3"`` to force a parser. Returns ------- qiskit.QuantumCircuit Raises ------ ValueError If *program* is empty, the version cannot be determined, or it is unsupported. ImportError If OpenQASM 3.0 loading is requested but ``qiskit-qasm3-import`` is not installed. """ if not isinstance(program, str) or not program.strip(): raise ValueError("program must be a non-empty OpenQASM string") if version not in _VALID_VERSIONS: raise ValueError(f"version must be one of {sorted(_VALID_VERSIONS)}, got {version!r}") resolved = _detect_version(program) if version == "auto" else version if resolved == "2": from qiskit import qasm2 return qasm2.loads(program) from qiskit import qasm3 try: return qasm3.loads(program) except Exception as exc: # qiskit raises MissingOptionalLibraryError (an ImportError) if isinstance(exc, ImportError): raise ImportError(_QASM3_MISSING_MSG) from exc raise
[docs] def load_qasm_file(path: str | Path, version: str = "auto") -> Any: """Read an OpenQASM file and parse it into a ``qiskit.QuantumCircuit``. Parameters ---------- path Path to a ``.qasm`` file. version See :func:`load_qasm`. Returns ------- qiskit.QuantumCircuit """ file_path = Path(path) if not file_path.is_file(): raise FileNotFoundError(f"OpenQASM file not found: {file_path}") return load_qasm(file_path.read_text(encoding="utf-8"), version=version)
__all__ = ["load_qasm", "load_qasm_file"]