diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..e3ad2d1 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,13 @@ +FROM --platform=x86_64 python:3.11-bookworm + +ENV TZ="Europe/Berlin" + +RUN apt-get update -y && \ + apt-get autoremove -y +RUN apt-get install -y software-properties-common bash-completion cmake build-essential iputils-ping rsync p7zip-full libuv1-dev git nlohmann-json3-dev librabbitmq-dev libboost-dev libgtest-dev pybind11-dev + +RUN pip install uv + +WORKDIR /home + +CMD ["bash"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..1766630 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,9 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/debian +{ + "name": "MQSS Qiskit Adapter Development", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "build": { + "dockerfile": "Dockerfile" + } +} diff --git a/mqss/__init__.py b/mqss/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/mqss/qiskit_adapter/adapter.py b/mqss/qiskit_adapter/adapter.py index 909474a..f35f4a8 100644 --- a/mqss/qiskit_adapter/adapter.py +++ b/mqss/qiskit_adapter/adapter.py @@ -21,7 +21,7 @@ import os from typing import List, Optional -from mqss_client import MQSSClient # type: ignore +from mqss.client import MQSSClient # type: ignore from .backend import MQSSQiskitBackend @@ -39,18 +39,20 @@ def __init__( self, token: str, *, - hpcqc: Optional[bool] = None, - base_url: Optional[str] = None, + hpcqc: Optional[bool] = False, + base_url: Optional[str] = "", ) -> None: + is_hpcqc_env = os.getenv("MQSS_HPCQC_ENV", "False").lower() in [ "true", "1", "t", ] # hpcqc gets priority over the environment variable + self.client = MQSSClient( token=token, - base_url=base_url, + url_or_queue=base_url, is_hpc=hpcqc if hpcqc is not None else is_hpcqc_env, ) @@ -78,14 +80,14 @@ def backends( Returns: List[MQSSQiskitBackend]: List of backend instances """ - resources = self.client.get_all_resources() + resources = self.client.resources if resources is None: return [] - if name is not None and name not in resources: + if name is not None and not any([name==resource.name for resource in resources]): raise ValueError(f"{name} is not available. ") return [ - MQSSQiskitBackend(self.client, _name, resources[_name]) - for _name in resources - if (not online or resources[_name].online) - and (name is None or name == _name) + MQSSQiskitBackend(self.client, resource.name, resource) + for resource in resources + if (not online or resource.online) + and (name is None or name == resource.name) ] diff --git a/mqss/qiskit_adapter/backend.py b/mqss/qiskit_adapter/backend.py index 6f6bb08..e095821 100644 --- a/mqss/qiskit_adapter/backend.py +++ b/mqss/qiskit_adapter/backend.py @@ -20,7 +20,7 @@ from typing import List, Optional, Union -from mqss_client import CircuitJobRequest, MQSSClient, ResourceInfo # type: ignore +from mqss.client import MQSSClient, Resource, CircuitJobRequest # type: ignore from qiskit.circuit import QuantumCircuit # type: ignore from qiskit.providers import BackendV2, Options # type: ignore from qiskit.qasm2 import dumps as qasm2_str # type: ignore @@ -42,22 +42,20 @@ def __init__( self, client: MQSSClient, name: Optional[str] = None, - resource_info: Optional[ResourceInfo] = None, + resource: Optional[Resource] = None, **kwargs, ): super().__init__(**kwargs) self.name = name self.client = client - _resource_info = resource_info or ( - self.client.get_resource_info(self.name) if name else None - ) + resource = resource or (self.client.resource(self.name) if name else None) self._coupling_map = None self._target = None - if _resource_info is not None: - self._coupling_map = get_coupling_map(_resource_info) - self._target = get_target(_resource_info) + if resource is not None: + self._coupling_map = get_coupling_map(resource) + self._target = get_target(resource) - if self.name is not None and _resource_info is None: + if self.name is not None and resource is None: raise ValueError(f"{self.name} is not available. ") @classmethod @@ -107,7 +105,7 @@ def num_pending_jobs(self) -> int: Returns: Number of pending jobs """ - return self.client.get_num_pending_jobs(self.name) + return self.client.pending_job_count(self.name) def run( self, @@ -134,7 +132,7 @@ def run( if isinstance(run_input, QuantumCircuit): _circuits = ( - str([qasm3_str(run_input)]) if qasm3 else str([qasm2_str(run_input)]) + str(qasm3_str(run_input)) if qasm3 else str(qasm2_str(run_input)) ) else: _circuits = ( @@ -145,7 +143,7 @@ def run( _circuit_format = "qasm3" if qasm3 else "qasm" job_request = CircuitJobRequest( - circuits=_circuits, + circuit=_circuits, circuit_format=_circuit_format, resource_name=self.name, shots=shots, diff --git a/mqss/qiskit_adapter/job.py b/mqss/qiskit_adapter/job.py index d59bdf5..133b7c8 100644 --- a/mqss/qiskit_adapter/job.py +++ b/mqss/qiskit_adapter/job.py @@ -21,9 +21,7 @@ cancellation, status retrieval, and result fetching for MQSS backends using the MQSSClient. """ -from mqss_client import CircuitJobRequest # type: ignore -from mqss_client import MQSSClient # type: ignore -from mqss_client import JobStatus as MQSSJobStatus # type: ignore +from mqss.client import MQSSClient, CircuitJobRequest from qiskit.providers import JobStatus # type: ignore from qiskit.providers import Backend, JobV1 # type: ignore from qiskit.result import Counts, Result # type: ignore @@ -56,16 +54,16 @@ def status(self) -> JobStatus: ([JobStatus](https://qiskit.org/documentation/stubs/qiskit.providers.JobStatus.html)). """ - mqss_status = self.client.job_status(self.job_id(), self.job_request) - if mqss_status == MQSSJobStatus.PENDING: + mqss_status = self.client.job_status(self.job_request) + if mqss_status == "PENDING": return JobStatus.INITIALIZING - if mqss_status == MQSSJobStatus.WAITING: + if mqss_status == "WAITING": return JobStatus.QUEUED - if mqss_status == MQSSJobStatus.CANCELLED: + if mqss_status == "CANCELLED": return JobStatus.CANCELLED - if mqss_status == MQSSJobStatus.FAILED: + if mqss_status == "FAILED": return JobStatus.ERROR - if mqss_status == MQSSJobStatus.COMPLETED: + if mqss_status == "COMPLETED": return JobStatus.DONE raise RuntimeWarning(f"Unknown job status: {mqss_status}.") @@ -76,11 +74,11 @@ def result(self) -> Result: [Result](https://qiskit.org/documentation/stubs/qiskit.result.Result.html) object for the job. """ - res = self.client.wait_for_job_result(self.job_id(), self.job_request) - if isinstance(res.counts, list): - res_counts = res.counts + res = self.client.job_results(self.job_request, True) + if isinstance(res.results, list): + res_counts = res.results else: - res_counts = [res.counts] + res_counts = [res.results] result_dict = { "backend_name": self.backend().name, "backend_version": None, diff --git a/mqss/qiskit_adapter/mqss_resources.py b/mqss/qiskit_adapter/mqss_resources.py index aad702c..3001f29 100644 --- a/mqss/qiskit_adapter/mqss_resources.py +++ b/mqss/qiskit_adapter/mqss_resources.py @@ -18,7 +18,7 @@ """MQP Resources""" -from mqss_client import ResourceInfo # type: ignore +from mqss.client import Resource # type: ignore from qiskit.circuit.library import Measure # type: ignore from qiskit.circuit.library import RXGate # type: ignore from qiskit.circuit.library import ( # type: ignore @@ -38,35 +38,40 @@ from qiskit.transpiler import CouplingMap, Target # type: ignore -def get_coupling_map(resource_info: ResourceInfo): +def get_coupling_map(resource: Resource): """Return CouplingMap for the backend""" return ( - CouplingMap(couplinglist=resource_info.connectivity) - if resource_info is not None and resource_info.connectivity is not None + CouplingMap(couplinglist=resource.coupling_map) + if resource is not None and resource.coupling_map is not None and len(resource.coupling_map) != 0 else None ) -def get_target(resource_info: ResourceInfo): +def get_target(resource: Resource): """Return Target for the backend""" + + if resource is None or not resource.native_gateset: + return None - target = ( - Target(num_qubits=resource_info.qubits) - if resource_info is not None and resource_info.instructions is not None - else None - ) + target = Target(num_qubits=resource.qubit_count) + + for gate in resource.native_gateset: + connections = ( + {None: None} + if not gate.supported_qubits + else {tuple(qubits): None for qubits in gate.supported_qubits} + ) + + instruction_factory = instruction_map.get(gate.name) - if resource_info is not None and resource_info.instructions is not None: - assert target is not None + if instruction_factory is None: + print( + f"Warning: Instruction '{gate.name}' not found in the instruction_map." + ) + continue - for _instruction, _connections in resource_info.instructions: - try: - target.add_instruction(instruction_map[_instruction](), _connections) - except KeyError: - print( - f"Warning: Instruction '{_instruction}' not found in the instruction_map." - ) + target.add_instruction(instruction_factory(), connections) return target diff --git a/noxfile.py b/noxfile.py index 40266b4..7eb166f 100644 --- a/noxfile.py +++ b/noxfile.py @@ -12,6 +12,7 @@ def test_backend(session: Session, qiskit: str) -> None: session.run("rm", "-rf", ".venv", external=True) session.run("uv", "lock", "--upgrade-package", f"qiskit=={qiskit}", external=True) session.run("uv", "sync", external=True) + session.run("uv", "pip", "install", "../MQSS-Client", external=True) session.run("uv", "run", "pytest", "-v", "-s", "-m", "backend", external=True) @@ -24,4 +25,5 @@ def test_job(session: Session, qiskit: str) -> None: session.run("rm", "-rf", ".venv", external=True) session.run("uv", "lock", "--upgrade-package", f"qiskit=={qiskit}", external=True) session.run("uv", "sync", external=True) + session.run("uv", "pip", "install", "../MQSS-Client", external=True) session.run("uv", "run", "pytest", "-v", "-s", "-m", "job", external=True) diff --git a/pyproject.toml b/pyproject.toml index b7303ee..d8a2afe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,6 @@ dependencies = [ "requests>=2.32.3", "python-decouple>=3.8", "qiskit-qasm3-import>=0.5.1", - "mqss-client>=0.2.0", ] # Sub-Dependency SciPy 1.9 supports Python 3.8-3.11 requires-python = ">=3.9,<3.14" diff --git a/test/conftest.py b/test/conftest.py index 1fe2f02..e543f03 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -33,7 +33,7 @@ # NOTE: change to current backend names BACKENDS = ["QExa20"] -URL = "https://portal.quantum.lrz.de:4000" +URL = "https://portal.quantum.lrz.de:4000/v1/" @pytest.mark.skipif(TOKEN is None, reason="MQSS token not provided") diff --git a/test/test_adapter.py b/test/test_adapter.py index f62ec8d..d745554 100644 --- a/test/test_adapter.py +++ b/test/test_adapter.py @@ -19,7 +19,7 @@ """Module to test the MQSS Qiskit Adapter""" import pytest -from mqss_client import MQSSClient # type: ignore +from mqss.client import MQSSClient # type: ignore from mqss.qiskit_adapter import MQSSQiskitBackend # type: ignore diff --git a/test/test_backend.py b/test/test_backend.py index f2e1ffc..177b753 100644 --- a/test/test_backend.py +++ b/test/test_backend.py @@ -19,7 +19,7 @@ """Module to test the MQSS Qiskit Backend.""" import pytest -from mqss_client import MQSSClient # type: ignore +from mqss.client import MQSSClient # type: ignore from qiskit import compiler # type: ignore from qiskit.providers import JobStatus as QiskitJobStatus # type: ignore