Source code for qbraid.runtime.quantinuum.provider

# Copyright 2026 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 defining Quantinuum provider class.

"""
from __future__ import annotations

from typing import TYPE_CHECKING

from qbraid._caching import cached_method
from qbraid.programs import ExperimentType, ProgramSpec
from qbraid.runtime.exceptions import ResourceNotFoundError
from qbraid.runtime.profile import TargetProfile
from qbraid.runtime.provider import QuantumProvider

from ._transport import ensure_bounded_client
from .device import QuantinuumDevice

if TYPE_CHECKING:
    from pytket.backends.backendinfo import BackendInfo


def _fetch_quantinuum_devices_df():
    """Return the NEXUS Quantinuum devices dataframe."""
    # pylint: disable-next=import-outside-toplevel
    import qnexus as qnx

    # Device enumeration is often the first qnexus call a process makes, so it
    # must apply the client bound itself rather than rely on a device or job
    # call having run first.
    ensure_bounded_client()
    return qnx.devices.get_all(issuers=[qnx.devices.IssuerEnum.QUANTINUUM]).df()


def _get_device_entry(device_name: str):
    """Fetch a device's entry (name, backend info, hosting) from the NEXUS device list.

    Returns the matching row of the qnexus devices dataframe.
    """
    df = _fetch_quantinuum_devices_df()
    matching_rows = df.loc[df["device_name"] == device_name]

    if matching_rows.empty:
        available_devices = df["device_name"].tolist()
        raise ResourceNotFoundError(
            f"Device '{device_name}' not found in Quantinuum device list. "
            f"Available devices: {available_devices}"
        )

    return matching_rows.iloc[0]


def _is_simulator(device_name: str, nexus_hosted: bool) -> bool:
    """Return whether a Quantinuum device name refers to a simulator.

    Quantinuum names hardware as ``<family>-<n>`` (``H1-1``, ``H2-1``,
    ``Helios-1``) and derives the non-hardware targets from it with a suffix:
    ``E`` for the device-hosted emulator, ``LE`` for the Nexus-hosted one, and
    ``SC`` for the syntax checker. Everything Nexus hosts itself is a simulator
    by definition.

    The suffix has to be matched at the end, not anywhere in the name: a
    substring test for "E" also matches ``Helios-1``, silently billing a QPU
    as a simulator and letting it past simulator-only guards.
    """
    if nexus_hosted:
        return True
    name = device_name.upper()
    return name.endswith(("E", "SC")) or "EMULATOR" in name


def _build_profile(device_id: str, backend_info: BackendInfo, nexus_hosted: bool) -> TargetProfile:
    """Build a TargetProfile from a Quantinuum device name and backend info."""
    # pylint: disable-next=import-outside-toplevel
    from pytket import Circuit

    return TargetProfile(
        device_id=device_id,
        simulator=_is_simulator(device_id, nexus_hosted),
        experiment_type=ExperimentType.GATE_MODEL,
        num_qubits=len(backend_info.architecture.nodes),
        program_spec=ProgramSpec(Circuit, alias="pytket"),
        provider_name="quantinuum",
        # Extras: accessible via ``device.profile.backend_info`` etc.
        backend_info=backend_info,
        # Cloud-hosted (Nexus-hosted) devices have no machine status endpoint
        # and are assumed always online.
        nexus_hosted=nexus_hosted,
    )


[docs] class QuantinuumProvider(QuantumProvider): """Quantinuum NEXUS provider class.""" def __hash__(self) -> int: # Authentication is handled implicitly by qnexus (via ``qnx login``), # so a single process-wide provider instance is effectively stateless # from qBraid's perspective. return hash(("quantinuum", id(self))) @cached_method def get_device(self, device_id: str) -> QuantinuumDevice: """Get a specific Quantinuum device.""" device_id = device_id.strip() entry = _get_device_entry(device_id) return QuantinuumDevice( profile=_build_profile(device_id, entry["backend_info"], bool(entry["nexus_hosted"])) ) @cached_method def get_devices(self) -> list[QuantinuumDevice]: # pylint: disable=arguments-differ """Get a list of available Quantinuum devices. Issues a single call to ``qnx.devices.get_all`` and builds each :class:`QuantinuumDevice` from the returned dataframe rows, avoiding the N+1 pattern that would otherwise re-fetch the device list inside :meth:`get_device` for each entry. """ df = _fetch_quantinuum_devices_df() return [ QuantinuumDevice( profile=_build_profile( row["device_name"], row["backend_info"], bool(row["nexus_hosted"]) ), ) for _, row in df.iterrows() ]