Source code for divi.circuits._qasm_template

# SPDX-FileCopyrightText: 2025-2026 Qoro Quantum Ltd <divi@qoroquantum.de>
#
# SPDX-License-Identifier: Apache-2.0

"""Pre-split QASM templates for fast parameter substitution."""

import re
from typing import NamedTuple


[docs] class TemplateEntry(NamedTuple): """One parametric circuit ready for backend-side substitution. Carries the full QASM 2.0 source (preamble + parametric body + measurement) with named-symbol placeholders, the ordered tuple of placeholder names, and the per-parameter-set values labelled with the pipeline-assigned circuit labels. Produced by the pipeline's compilation pass and consumed by backends that implement :class:`~divi.backends.SupportsCircuitTemplates`. The same ``parameter_sets`` rows generate one resolved circuit each on the backend, labelled with the supplied ``label`` so that pipeline result routing continues to work unchanged. """ template_qasm: str parameter_names: tuple[str, ...] parameter_sets: tuple[tuple[str, tuple[float, ...]], ...]
[docs] class QASMTemplate(NamedTuple): """Pre-split QASM body for fast parameter substitution. Instead of scanning a full QASM string with a regex on every parameter binding call, the string is split once at symbol boundaries into ``fragments`` and ``slot_indices``. Rendering then reduces to interleaving fragments with looked-up values — no regex needed. ``fragments[i]`` is the literal QASM text between the (i-1)-th and i-th symbol occurrence. ``slot_indices[i]`` is the index into the parameter values array for the i-th slot. Invariant: ``len(fragments) == len(slot_indices) + 1`` Example:: Given QASM body ``"rx(w_0) q[0];\\nrz(w_1) q[1];\\n"`` and symbols ``("w_0", "w_1")``: fragments = ("rx(", ") q[0];\\nrz(", ") q[1];\\n") slot_indices = (0, 1) Rendering with values ``("1.5", "2.7")`` produces:: "rx(" + "1.5" + ") q[0];\\nrz(" + "2.7" + ") q[1];\\n" = "rx(1.5) q[0];\\nrz(2.7) q[1];\\n" """ fragments: tuple[str, ...] slot_indices: tuple[int, ...]
[docs] def build_template(qasm_body: str, symbol_names: tuple[str, ...]) -> QASMTemplate: """Split a QASM string at symbol boundaries into a :class:`QASMTemplate`. Symbol names are matched longest-first to avoid partial-match issues (e.g., ``w_1`` matching inside ``w_10``). Matches require non-identifier characters on both sides, so a parameter named ``x`` does NOT match the ``x`` inside ``cx q[0],q[1];``. Args: qasm_body: The QASM body string containing symbolic parameter names. symbol_names: Ordered tuple of symbol name strings. The index in this tuple becomes the slot index used during rendering. Returns: A :class:`QASMTemplate` ready for :func:`render_template`. """ if not symbol_names: return QASMTemplate(fragments=(qasm_body,), slot_indices=()) name_to_idx = {name: i for i, name in enumerate(symbol_names)} escaped = sorted((re.escape(name) for name in symbol_names), key=len, reverse=True) pattern = re.compile( r"(?<![A-Za-z0-9_])(?:" + "|".join(escaped) + r")(?![A-Za-z0-9_])" ) fragments: list[str] = [] slot_indices: list[int] = [] last_end = 0 for match in pattern.finditer(qasm_body): fragments.append(qasm_body[last_end : match.start()]) slot_indices.append(name_to_idx[match.group(0)]) last_end = match.end() fragments.append(qasm_body[last_end:]) return QASMTemplate( fragments=tuple(fragments), slot_indices=tuple(slot_indices), )
[docs] def render_template(template: QASMTemplate, formatted_values: tuple[str, ...]) -> str: """Render a :class:`QASMTemplate` with concrete parameter values. Args: template: The pre-split template. formatted_values: Formatted parameter strings, indexed by the slot indices stored in *template*. Returns: The fully-bound QASM body string. """ fragments = template.fragments slot_indices = template.slot_indices parts: list[str] = [] for i, slot_idx in enumerate(slot_indices): parts.append(fragments[i]) parts.append(formatted_values[slot_idx]) parts.append(fragments[-1]) return "".join(parts)