Source code for qiskit_qm_provider.providers.qm_saas_provider

# Copyright 2026 Arthur Strauss
#
# 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.

"""QM SaaS provider: connect to Quantum Machines cloud (QmSaas) and get backends.

Requires the ``qm-saas`` optional dependency group::

    pip install qiskit-qm-provider[qm_saas]

Author: Arthur Strauss
Date: 2026-02-08
"""

from __future__ import annotations
from pathlib import Path
from typing import Optional, TYPE_CHECKING, Type

from ..backend import QMBackend

if TYPE_CHECKING:
    from qm import SimulationConfig
    from qm_saas import QmSaas, QOPVersion, QmSaasInstance
    from quam.core import QuamRoot


[docs] class QmSaasProvider: """Provider for the Quantum Machines SaaS simulation platform. Connects to the QM SaaS cloud to simulate QUA programs. Like :class:`QMProvider`, users should supply their own ``quam_cls`` and ``backend_cls`` to match their hardware. Requires the ``qm_saas`` extras (``pip install qiskit-qm-provider[qm_saas]``). Args: email: QM SaaS account email. password: QM SaaS account password. host: QM SaaS platform host URL. version: QOP version string (defaults to the latest available). """ def __init__( self, email: Optional[str] = None, password: Optional[str] = None, host: Optional[str] = None, version: Optional[str] = None, ): from qm_saas import QmSaas, QOPVersion if email is None or password is None or host is None: import json try: path = Path.home() / "qm_saas_config.json" with open(path, "r") as f: config = json.load(f) email = config["email"] password = config["password"] host = config["host"] except FileNotFoundError: raise FileNotFoundError("QM Saas config file not found. Please provide email, password, and host.") self.email = email self.password = password self.host = host self._client = QmSaas(email=email, password=password, host=host) self._version = QOPVersion(version) if version is not None else self.client.latest_version() self._instance = self._client.simulator(version=self.version)
[docs] def get_machine( self, quam_state_folder_path: Optional[str] = None, quam_cls: Type[QuamRoot] | None = None, ) -> QuamRoot: """Load a QuAM machine. Args: quam_state_folder_path: Path to the QuAM state folder. quam_cls: :class:`~quam.core.QuamRoot` subclass. Falls back to ``FluxTunableQuam`` from *quam-builder* when omitted, but users are encouraged to provide their own. Returns: The loaded QuAM machine instance. """ if quam_cls is None: from quam_builder.architecture.superconducting.qpu.flux_tunable_quam import ( FluxTunableQuam, ) quam_cls = FluxTunableQuam if quam_state_folder_path is not None: return quam_cls.load(quam_state_folder_path) else: return quam_cls.load()
[docs] def get_backend( self, quam_state_folder_path: Optional[str] = None, simulation_config: Optional[SimulationConfig] = None, quam_cls: Type[QuamRoot] | None = None, backend_cls: Type[QMBackend] | None = None, ) -> QMBackend: """Create a backend connected to a SaaS simulator instance. Args: quam_state_folder_path: Path to the QuAM state folder. simulation_config: Simulation configuration (defaults to 10 000 clock cycles). quam_cls: :class:`~quam.core.QuamRoot` subclass for the machine. backend_cls: :class:`QMBackend` subclass to instantiate. Defaults to the base :class:`QMBackend`; pass e.g. ``FluxTunableTransmonBackend`` or a custom subclass to match your hardware. Returns: A :class:`QMBackend` instance connected to the SaaS simulator. """ from qm import QuantumMachinesManager, SimulationConfig machine = self.get_machine(quam_state_folder_path, quam_cls) self.instance.spawn() qmm = QuantumMachinesManager( host=self.instance.host, port=self.instance.port, connection_headers=self.instance.default_connection_headers, ) if simulation_config is None: simulation_config = SimulationConfig(duration=10000) if backend_cls is None: backend_cls = QMBackend return backend_cls(machine, provider=self, qmm=qmm, simulate=simulation_config)
@property def client(self) -> QmSaas: """The underlying :class:`QmSaas` client.""" return self._client @property def version(self) -> QOPVersion: """The QOP version used by this provider.""" return self._version @property def instance(self) -> QmSaasInstance: """The active SaaS simulator instance.""" return self._instance
[docs] def close_all(self): """Close all SaaS instances and QuantumMachinesManager connections.""" self._client.close_all()
[docs] def spawn(self): """Spawn a new SaaS simulator instance.""" self._instance.spawn()