Skip to content

Commit 6898d15

Browse files
authored
Merge pull request #38 from scaleway/qio-upgrade
Qio upgrade
2 parents 201403b + ed71c6a commit 6898d15

File tree

4 files changed

+103
-344
lines changed

4 files changed

+103
-344
lines changed

qiskit_scaleway/backends/aqt/backend.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
1514
from qiskit.providers import Options
1615

1716
from qiskit_scaleway.backends import BaseBackend
Lines changed: 51 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2024 Scaleway
1+
# Copyright 2025 Scaleway
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -12,32 +12,31 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
import time
15-
import zlib
1615
import httpx
17-
import json
18-
import numpy as np
1916
import randomname
2017

2118
from typing import List, Union, Optional, Dict
2219

23-
from qiskit import qasm3, QuantumCircuit
20+
from qiskit import QuantumCircuit
2421
from qiskit.result import Result
2522
from qiskit.providers import JobV1
2623
from qiskit.providers import JobError, JobTimeoutError, JobStatus
2724

2825
from qiskit_scaleway.versions import USER_AGENT
2926

27+
from qio.core import (
28+
QuantumProgram,
29+
QuantumProgramResult,
30+
QuantumComputationModel,
31+
QuantumComputationParameters,
32+
QuantumNoiseModel,
33+
BackendData,
34+
ClientData,
35+
)
36+
3037
from scaleway_qaas_client.v1alpha1 import (
3138
QaaSClient,
3239
QaaSJobResult,
33-
QaaSJobData,
34-
QaaSJobClientData,
35-
QaaSCircuitData,
36-
QaaSJobRunData,
37-
QaaSJobBackendData,
38-
QaaSCircuitSerializationFormat,
39-
QaaSNoiseModelData,
40-
QaaSNoiseModelSerializationFormat,
4140
)
4241

4342

@@ -55,6 +54,7 @@ def __init__(
5554
self._client = client
5655
self._circuits = circuits
5756
self._config = config
57+
self._last_progress_message = ""
5858

5959
@property
6060
def name(self):
@@ -69,6 +69,9 @@ def status(self) -> JobStatus:
6969
"completed": JobStatus.DONE,
7070
}
7171

72+
if job.progress_message is not None:
73+
self._last_progress_message = job.progress_message
74+
7275
return status_mapping.get(job.status, JobStatus.ERROR)
7376

7477
def submit(self, session_id: str) -> None:
@@ -78,69 +81,46 @@ def submit(self, session_id: str) -> None:
7881
options = self._config.copy()
7982
shots = options.pop("shots")
8083

81-
run_data = QaaSJobRunData(
82-
options={
83-
"shots": shots,
84-
"memory": options.pop("memory", False),
85-
},
86-
circuits=list(
87-
map(
88-
lambda c: QaaSCircuitData(
89-
serialization_format=QaaSCircuitSerializationFormat.QASM_V3,
90-
circuit_serialization=qasm3.dumps(c),
91-
),
92-
self._circuits,
93-
)
94-
),
95-
)
84+
programs = map(lambda c: QuantumProgram.from_qiskit_circuit(c), self._circuits)
9685

9786
noise_model = options.pop("noise_model", None)
9887
if noise_model:
99-
noise_model_dict = _encode_numpy_complex(noise_model.to_dict(False))
100-
noise_model = QaaSNoiseModelData(
101-
serialization_format=QaaSNoiseModelSerializationFormat.AER_COMPRESSED_JSON,
102-
noise_model_serialization=zlib.compress(
103-
json.dumps(noise_model_dict).encode()
104-
),
105-
)
106-
### Uncomment to use standard JSON serialization, provided there is no more issue with AER deserialization logic
107-
# noise_model = QaaSNoiseModelData(
108-
# serialization_format = QaaSNoiseModelSerializationFormat.JSON,
109-
# noise_model_serialization = json.dumps(noise_model.to_dict(True)).encode()
110-
# )
111-
###
112-
113-
backend_data = QaaSJobBackendData(
88+
noise_model = QuantumNoiseModel.from_qiskit_aer_noise_model(noise_model)
89+
90+
backend_data = BackendData(
11491
name=self.backend().name,
11592
version=self.backend().version,
11693
options=options,
11794
)
11895

119-
client_data = QaaSJobClientData(
96+
client_data = ClientData(
12097
user_agent=USER_AGENT,
12198
)
12299

123-
data = QaaSJobData.schema().dumps(
124-
QaaSJobData(
125-
backend=backend_data,
126-
run=run_data,
127-
client=client_data,
128-
noise_model=noise_model,
129-
)
130-
)
100+
computation_model_json = QuantumComputationModel(
101+
programs=programs,
102+
backend=backend_data,
103+
client=client_data,
104+
noise_model=noise_model,
105+
).to_json_str()
106+
107+
computation_parameters_json = QuantumComputationParameters(
108+
shots=shots,
109+
).to_json_str()
131110

132111
model = self._client.create_model(
133-
payload=data,
112+
payload=computation_model_json,
134113
)
135114

136115
if not model:
137116
raise RuntimeError("Failed to push circuit data")
138117

118+
self._last_progress_message = ""
139119
self._job_id = self._client.create_job(
140120
name=self._name,
141121
session_id=session_id,
142122
model_id=model.id,
143-
parameters={"shots": shots},
123+
parameters=computation_parameters_json,
144124
).id
145125

146126
def result(
@@ -151,37 +131,26 @@ def result(
151131

152132
job_results = self._wait_for_result(timeout, fetch_interval)
153133

154-
def __make_result_from_payload(payload: str) -> Result:
155-
payload_dict = json.loads(payload)
156-
157-
return Result.from_dict(
158-
{
159-
"results": payload_dict["results"],
160-
"backend_name": self.backend().name,
161-
"backend_version": self.backend().version,
162-
"job_id": self._job_id,
163-
"qobj_id": ", ".join(x.name for x in self._circuits),
164-
"success": payload_dict["success"],
165-
"header": payload_dict.get("header"),
166-
"metadata": payload_dict.get("metadata"),
167-
}
168-
)
169-
170-
qiskit_results = list(
134+
program_results = list(
171135
map(
172-
lambda r: __make_result_from_payload(
173-
self._extract_payload_from_response(r)
136+
lambda r: self._extract_payload_from_response(r).to_qiskit_result(
137+
backend_name=self.backend().name,
138+
backend_version=self.backend().version,
139+
job_id=self._job_id,
140+
qobj_id=", ".join(x.name for x in self._circuits),
174141
),
175142
job_results,
176143
)
177144
)
178145

179-
if len(qiskit_results) == 1:
180-
return qiskit_results[0]
146+
if len(program_results) == 1:
147+
return program_results[0]
181148

182-
return qiskit_results
149+
return program_results
183150

184-
def _extract_payload_from_response(self, job_result: QaaSJobResult) -> str:
151+
def _extract_payload_from_response(
152+
self, job_result: QaaSJobResult
153+
) -> QuantumProgramResult:
185154
result = job_result.result
186155

187156
if result is None or result == "":
@@ -190,12 +159,11 @@ def _extract_payload_from_response(self, job_result: QaaSJobResult) -> str:
190159
if url is not None:
191160
resp = httpx.get(url)
192161
resp.raise_for_status()
193-
194-
return resp.text
162+
result = resp.text
195163
else:
196-
raise Exception("Got result with empty data and url fields")
197-
else:
198-
return result
164+
raise RuntimeError("Got result with empty data and url fields")
165+
166+
return QuantumProgramResult.from_json_str(result)
199167

200168
def _wait_for_result(
201169
self, timeout: Optional[int], fetch_interval: int
@@ -214,28 +182,6 @@ def _wait_for_result(
214182
return self._client.list_job_results(self._job_id)
215183

216184
if status == JobStatus.ERROR:
217-
raise JobError("Job error")
185+
raise JobError(f"Job failed: {self._last_progress_message}")
218186

219187
time.sleep(fetch_interval)
220-
221-
222-
def _encode_numpy_complex(obj):
223-
"""
224-
Recursively traverses a structure and converts numpy arrays and
225-
complex numbers into a JSON-serializable format.
226-
"""
227-
if isinstance(obj, np.ndarray):
228-
return {
229-
"__ndarray__": True,
230-
"data": _encode_numpy_complex(obj.tolist()), # Recursively encode data
231-
"dtype": obj.dtype.name,
232-
"shape": obj.shape,
233-
}
234-
elif isinstance(obj, (complex, np.complex128)):
235-
return {"__complex__": True, "real": obj.real, "imag": obj.imag}
236-
elif isinstance(obj, dict):
237-
return {key: _encode_numpy_complex(value) for key, value in obj.items()}
238-
elif isinstance(obj, (list, tuple)):
239-
return [_encode_numpy_complex(item) for item in obj]
240-
else:
241-
return obj

0 commit comments

Comments
 (0)