Source code for mqt.bench.targets.gatesets

# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
# All rights reserved.
#
# SPDX-License-Identifier: MIT
#
# Licensed under the MIT License

"""Handles the available native gatesets."""

# ruff: file-ignore[non-empty-init-module]

from __future__ import annotations

import copy
import importlib
import importlib.resources as ir
import inspect
from functools import cache
from typing import TYPE_CHECKING

from qiskit.circuit import Parameter
from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping
from qiskit.providers.fake_provider import GenericBackendV2

from ._registry import gateset_names, get_gateset_by_name, register_gateset

if TYPE_CHECKING:
    from collections.abc import Callable

    from qiskit.circuit import Gate, Instruction
    from qiskit.transpiler import Target

_DISCOVERED_MODULES: set[str] = {
    entry.name.removesuffix(".py")
    for entry in ir.files(__name__).iterdir()
    if entry.is_file() and entry.name.endswith(".py") and not entry.name.startswith("_")
}

_IMPORTED_MODULES: set[str] = set()

__all__ = [
    "get_available_gateset_names",
    "get_gateset",
    "get_target_for_gateset",
    "register_gateset",
]

_SPECIAL_NAME_TO_MODULE = {
    "clifford+t": "clifford_t",
    "clifford+t+rotations": "clifford_t",
}


def _module_from_gateset_name(gateset_name: str) -> str:
    """Map a gateset name like ``ibm_falcon`` to the module ``ibm``.

    The rule is the same as for devices: take everything before the first
    underscore (``ibm_falcon`` → ``ibm``).  If no underscore is present, the
    whole name is assumed to be the module.
    """
    if gateset_name in _SPECIAL_NAME_TO_MODULE:
        return _SPECIAL_NAME_TO_MODULE[gateset_name]

    return gateset_name.split("_", 1)[0]


def _ensure_loaded(gateset_name: str) -> None:
    """Import the module that should register gateset_name."""
    if gateset_name in gateset_names():
        return  # already present

    module_name = _module_from_gateset_name(gateset_name)

    if module_name not in _DISCOVERED_MODULES:
        msg = f"'{gateset_name}' is not a supported gateset. Known modules: {sorted(_DISCOVERED_MODULES)}"
        raise ValueError(msg)

    if module_name not in _IMPORTED_MODULES:
        importlib.import_module(f"{__package__}.{module_name}")
        _IMPORTED_MODULES.add(module_name)


[docs] def get_available_gateset_names() -> list[str]: """Return a list of available gateset names.""" for module in _DISCOVERED_MODULES - _IMPORTED_MODULES: importlib.import_module(f"{__package__}.{module}") _IMPORTED_MODULES.add(module) return sorted(gateset_names()).copy()
@cache def _get_gateset(gateset_name: str) -> list[str]: """Internal cacheable access. Ensures *lazy* loading.""" _ensure_loaded(gateset_name) return get_gateset_by_name(gateset_name)
[docs] def get_gateset(gateset_name: str) -> list[str]: """Return the basis-gate list for gateset_name.""" return _get_gateset(gateset_name).copy()
def _lazy_custom_gates() -> dict[str, Callable[[], Gate | type[Instruction]]]: """Import custom gates only when needed.""" from .ionq import GPI2Gate, GPIGate, MSGate, ZZGate # ruff:ignore[import-outside-top-level] from .rigetti import RXPI2DgGate, RXPI2Gate, RXPIGate # ruff:ignore[import-outside-top-level] return { "gpi": lambda: GPIGate(Parameter("alpha")), "gpi2": lambda: GPI2Gate(Parameter("alpha")), "ms": lambda: MSGate(Parameter("alpha"), Parameter("beta"), Parameter("gamma")), "zz": lambda: ZZGate(Parameter("alpha")), "rxpi": lambda: RXPIGate, "rxpi2": lambda: RXPI2Gate, "rxpi2dg": lambda: RXPI2DgGate, } @cache def _get_target_for_gateset(gateset_name: str, num_qubits: int) -> Target: """Return the Target object for a given native gateset name.""" gates = get_gateset(gateset_name) standard_gates = [] other_gates = [] for gate in gates: if gate in get_standard_gate_name_mapping(): standard_gates.append(gate) else: other_gates.append(gate) backend = GenericBackendV2(num_qubits=num_qubits, basis_gates=standard_gates, control_flow=True, seed=10) target = backend.target target.description = gateset_name custom_factory = _lazy_custom_gates() for gate_name in other_gates: if gate_name not in custom_factory: msg = f"Gate '{gate_name}' not found in available custom gates." raise ValueError(msg) # Classes (like control-flow operations) must have their name manually specified; instances derive their name automatically instruction = custom_factory[gate_name]() target.add_instruction(instruction, name=gate_name if inspect.isclass(instruction) else None) return target
[docs] def get_target_for_gateset(name: str, num_qubits: int) -> Target: """Return a deepcopy of a Target object for a given native gateset name.""" return copy.deepcopy(_get_target_for_gateset(name, num_qubits))