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

import logging
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 .device import QuantinuumDevice

if TYPE_CHECKING:
    from pytket.backends.backendinfo import BackendInfo

logger = logging.getLogger(__name__)


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

    return qnx.devices.get_all(issuers=[qnx.devices.IssuerEnum.QUANTINUUM]).df()


def _get_backend_info(device_name: str) -> BackendInfo:
    """Fetch the pytket BackendInfo for a specific Quantinuum device."""
    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]["backend_info"]


def _build_profile(device_id: str, backend_info: BackendInfo) -> 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,
        # Quantinuum emulator/simulator device names contain "E" (e.g. "H1-1E").
        simulator="E" in device_id.upper(),
        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``.
        backend_info=backend_info,
    )


[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() backend_info = _get_backend_info(device_id) return QuantinuumDevice(profile=_build_profile(device_id, backend_info)) @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"]), ) for _, row in df.iterrows() ]