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.
1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414import time
15- import zlib
1615import httpx
17- import json
18- import numpy as np
1916import randomname
2017
2118from typing import List , Union , Optional , Dict
2219
23- from qiskit import qasm3 , QuantumCircuit
20+ from qiskit import QuantumCircuit
2421from qiskit .result import Result
2522from qiskit .providers import JobV1
2623from qiskit .providers import JobError , JobTimeoutError , JobStatus
2724
2825from 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+
3037from 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
@@ -78,59 +77,39 @@ def submit(self, session_id: str) -> None:
7877 options = self ._config .copy ()
7978 shots = options .pop ("shots" )
8079
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- )
80+ programs = map (lambda c : QuantumProgram .from_qiskit_circuit (c ), self ._circuits )
9681
9782 noise_model = options .pop ("noise_model" , None )
9883 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 (
84+ noise_model = QuantumNoiseModel .from_qiskit_aer_noise_model (noise_model )
85+
86+ backend_data = BackendData (
11487 name = self .backend ().name ,
11588 version = self .backend ().version ,
11689 options = options ,
90+ frozenset = True ,
11791 )
11892
119- client_data = QaaSJobClientData (
93+ client_data = ClientData (
12094 user_agent = USER_AGENT ,
95+ frozenset = True ,
12196 )
12297
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- )
98+ computation_model_dict = QuantumComputationModel (
99+ programs = programs ,
100+ backend = backend_data ,
101+ client = client_data ,
102+ noise_model = noise_model ,
103+ frozenset = True ,
104+ ).to_dict ()
105+
106+ computation_parameters_dict = QuantumComputationParameters (
107+ shots = shots ,
108+ frozenset = True ,
109+ ).to_dict ()
131110
132111 model = self ._client .create_model (
133- payload = data ,
112+ payload = computation_model_dict ,
134113 )
135114
136115 if not model :
@@ -140,7 +119,7 @@ def submit(self, session_id: str) -> None:
140119 name = self ._name ,
141120 session_id = session_id ,
142121 model_id = model .id ,
143- parameters = { "shots" : shots } ,
122+ parameters = computation_parameters_dict ,
144123 ).id
145124
146125 def result (
@@ -151,37 +130,28 @@ def result(
151130
152131 job_results = self ._wait_for_result (timeout , fetch_interval )
153132
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 (
133+ program_results = list (
171134 map (
172- lambda r : __make_result_from_payload (
173- self ._extract_payload_from_response (r )
135+ lambda r : self ._extract_payload_from_response (r ).to_qiskit_result (
136+ ** {
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 ),
141+ }
174142 ),
175143 job_results ,
176144 )
177145 )
178146
179- if len (qiskit_results ) == 1 :
180- return qiskit_results [0 ]
147+ if len (program_results ) == 1 :
148+ return program_results [0 ]
181149
182- return qiskit_results
150+ return program_results
183151
184- def _extract_payload_from_response (self , job_result : QaaSJobResult ) -> str :
152+ def _extract_payload_from_response (
153+ self , job_result : QaaSJobResult
154+ ) -> QuantumProgramResult :
185155 result = job_result .result
186156
187157 if result is None or result == "" :
@@ -190,12 +160,11 @@ def _extract_payload_from_response(self, job_result: QaaSJobResult) -> str:
190160 if url is not None :
191161 resp = httpx .get (url )
192162 resp .raise_for_status ()
193-
194- return resp .text
163+ result = resp .text
195164 else :
196- raise Exception ("Got result with empty data and url fields" )
197- else :
198- return result
165+ raise RuntimeError ("Got result with empty data and url fields" )
166+
167+ return QuantumProgramResult . from_json ( result )
199168
200169 def _wait_for_result (
201170 self , timeout : Optional [int ], fetch_interval : int
@@ -217,25 +186,3 @@ def _wait_for_result(
217186 raise JobError ("Job error" )
218187
219188 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