# 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()
]