Skip to content

Commit a890d3c

Browse files
authored
Merge pull request #36 from scaleway/349-update_to_qiskit_2.1
349 update to qiskit 2.1
2 parents 6916c31 + 8f7aaa2 commit a890d3c

File tree

12 files changed

+180
-64
lines changed

12 files changed

+180
-64
lines changed

examples/run_aqt_estimator.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from qiskit.circuit.library import n_local
2020

2121
from qiskit_scaleway import ScalewayProvider
22-
from qiskit_scaleway.primitives import EstimatorV1
22+
from qiskit_scaleway.primitives import Estimator
2323

2424
provider = ScalewayProvider(
2525
project_id="<your-scaleway-project-id>",
@@ -33,7 +33,7 @@
3333
session_id = backend.start_session(name="test-session", deduplication_id="my-workshop")
3434

3535
# Create an estimator (v1) with the backend and the session
36-
estimator = EstimatorV1(backend=backend, session_id=session_id)
36+
estimator = Estimator(backend=backend, session_id=session_id)
3737

3838
# Specify the problem Hamiltonian
3939
hamiltonian = SparsePauliOp.from_list(
@@ -56,15 +56,15 @@ def cost_function(
5656
params,
5757
ansatz: QuantumCircuit,
5858
hamiltonian: BaseOperator,
59-
estimator: EstimatorV1,
59+
estimator: Estimator,
6060
) -> float:
6161
"""Cost function for the VQE.
6262
6363
Return the estimated expectation value of the Hamiltonian
6464
on the state prepared by the Ansatz circuit.
6565
"""
6666
return float(
67-
estimator.run(ansatz, hamiltonian, parameter_values=params).result().values[0]
67+
estimator.run([(ansatz, hamiltonian, params)]).result().values[0]
6868
)
6969

7070

qiskit_scaleway/backends/aer/backend.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
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-
from qiskit.providers import Options, convert_to_target
14+
from typing import List
15+
from qiskit.providers import Options
16+
from qiskit.transpiler import Target
1517

1618
from qiskit_aer.backends.aer_simulator import BASIS_GATES, AerBackendConfiguration
1719
from qiskit_aer.backends.aerbackend import NAME_MAPPING
@@ -49,9 +51,8 @@ def __init__(self, provider, client: QaaSClient, platform: QaaSPlatform):
4951
}
5052
)
5153
self._properties = None
52-
self._target = convert_to_target(
53-
self._configuration, self._properties, None, NAME_MAPPING
54-
)
54+
55+
self._target = self._convert_to_target()
5556

5657
def __repr__(self) -> str:
5758
return f"<AerBackend(name={self.name},num_qubits={self.num_qubits},platform_id={self.id})>"
@@ -71,6 +72,7 @@ def _default_options(self):
7172
shots=1000,
7273
memory=False,
7374
seed_simulator=None,
75+
noise_model=None,
7476
method="automatic",
7577
precision="double",
7678
max_shot_size=None,
@@ -105,3 +107,23 @@ def _default_options(self):
105107
fusion_max_qubit=None,
106108
fusion_threshold=None,
107109
)
110+
111+
def _convert_to_target(self) -> Target:
112+
args_lis: List[str] = [
113+
"basis_gates",
114+
"num_qubits",
115+
"coupling_map",
116+
"instruction_durations",
117+
"concurrent_measurements",
118+
"dt",
119+
"timing_constraints",
120+
"custom_name_mapping",
121+
]
122+
123+
conf_dict = self._configuration.to_dict()
124+
if conf_dict.get("custom_name_mapping") is None:
125+
conf_dict["custom_name_mapping"] = NAME_MAPPING
126+
127+
return Target.from_configuration(
128+
**{k: conf_dict[k] for k in args_lis if k in conf_dict}
129+
)

qiskit_scaleway/backends/base_job.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
import time
15+
import zlib
1516
import httpx
1617
import json
18+
import numpy as np
1719
import randomname
1820

1921
from typing import List, Union, Optional, Dict
@@ -34,6 +36,8 @@
3436
QaaSJobRunData,
3537
QaaSJobBackendData,
3638
QaaSCircuitSerializationFormat,
39+
QaaSNoiseModelData,
40+
QaaSNoiseModelSerializationFormat,
3741
)
3842

3943

@@ -90,6 +94,22 @@ def submit(self, session_id: str) -> None:
9094
),
9195
)
9296

97+
noise_model = options.pop("noise_model", None)
98+
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+
93113
backend_data = QaaSJobBackendData(
94114
name=self.backend().name,
95115
version=self.backend().version,
@@ -105,6 +125,7 @@ def submit(self, session_id: str) -> None:
105125
backend=backend_data,
106126
run=run_data,
107127
client=client_data,
128+
noise_model=noise_model,
108129
)
109130
)
110131

@@ -196,3 +217,25 @@ def _wait_for_result(
196217
raise JobError("Job error")
197218

198219
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

qiskit_scaleway/primitives/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,4 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
from .estimator import Estimator
15-
from .estimator_v1 import Estimator as EstimatorV1
1615
from .sampler import Sampler

qiskit_scaleway/primitives/estimator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
from qiskit.primitives import BackendEstimatorV2
15-
from qiskit.primitives.backend_estimator import (
15+
from qiskit.primitives.backend_estimator_v2 import (
1616
_prepare_counts,
1717
_run_circuits,
1818
)

qiskit_scaleway/primitives/estimator_v1.py

Lines changed: 0 additions & 41 deletions
This file was deleted.

requirements.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
qiskit==1.4.2
2-
qiskit-aer==0.17
1+
qiskit~=2.1
2+
qiskit-aer~=0.17
33
randomname>=0.2.1
44
dataclasses-json>=0.6.4
55
dataclasses>=0.6
6-
scaleway-qaas-client>=0.1.16
6+
scaleway-qaas-client>=0.1.23

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
setup(
2424
name="qiskit_scaleway",
25-
version="0.2.12",
25+
version="0.3.0",
2626
project_urls={
2727
"Documentation": "https://www.scaleway.com/en/quantum-as-a-service/",
2828
"Source": "https://github.com/scaleway/qiskit-scaleway",

tests/test_aer_multiple_circuits.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,16 @@ def _random_qiskit_circuit(size: int) -> QuantumCircuit:
4747

4848

4949
def test_aer_multiple_circuits():
50+
5051
provider = ScalewayProvider(
5152
project_id=os.environ["QISKIT_SCALEWAY_PROJECT_ID"],
5253
secret_key=os.environ["QISKIT_SCALEWAY_SECRET_KEY"],
5354
url=os.getenv("QISKIT_SCALEWAY_API_URL"),
5455
)
5556

56-
backend = provider.get_backend("aer_simulation_pop_c16m128")
57+
backend = provider.get_backend(
58+
os.getenv("QISKIT_SCALEWAY_BACKEND_NAME", "aer_simulation_pop_c16m128")
59+
)
5760

5861
assert backend is not None
5962

@@ -86,3 +89,89 @@ def test_aer_multiple_circuits():
8689
assert result.success
8790
finally:
8891
backend.delete_session(session_id)
92+
93+
94+
def _get_noise_model():
95+
import qiskit_aer.noise as noise
96+
97+
# Error probabilities (exaggerated to get a noticeable effect for demonstration)
98+
prob_1 = 0.01 # 1-qubit gate
99+
prob_2 = 0.1 # 2-qubit gate
100+
101+
# Depolarizing quantum errors
102+
error_1 = noise.depolarizing_error(prob_1, 1)
103+
error_2 = noise.depolarizing_error(prob_2, 2)
104+
105+
# Add errors to noise model
106+
noise_model = noise.NoiseModel()
107+
noise_model.add_all_qubit_quantum_error(error_1, ["rz", "sx", "x"])
108+
noise_model.add_all_qubit_quantum_error(error_2, ["cx"])
109+
110+
return noise_model
111+
112+
113+
def _bell_state_circuit():
114+
qc = QuantumCircuit(2, 2)
115+
qc.h(0)
116+
qc.cx(0, 1)
117+
qc.measure_all()
118+
return qc
119+
120+
121+
def _simple_one_state_circuit():
122+
qc = QuantumCircuit(1, 1)
123+
qc.x(0)
124+
qc.measure_all()
125+
return qc
126+
127+
128+
def test_aer_with_noise_model():
129+
130+
provider = ScalewayProvider(
131+
project_id=os.environ["QISKIT_SCALEWAY_PROJECT_ID"],
132+
secret_key=os.environ["QISKIT_SCALEWAY_SECRET_KEY"],
133+
url=os.getenv("QISKIT_SCALEWAY_API_URL"),
134+
)
135+
136+
backend = provider.get_backend(
137+
os.getenv("QISKIT_SCALEWAY_BACKEND_NAME", "aer_simulation_pop_c16m128")
138+
)
139+
140+
assert backend is not None
141+
142+
session_id = backend.start_session(
143+
name="my-aer-session-autotest",
144+
deduplication_id=f"my-aer-session-autotest-{random.randint(1, 1000)}",
145+
max_duration="15m",
146+
)
147+
148+
assert session_id is not None
149+
150+
try:
151+
qc1 = _bell_state_circuit()
152+
qc2 = _simple_one_state_circuit()
153+
154+
run_ideal_result = backend.run(
155+
[qc1, qc2],
156+
shots=1000,
157+
max_parallel_experiments=0,
158+
session_id=session_id,
159+
).result()
160+
161+
run_noisy_result = backend.run(
162+
[qc1, qc2],
163+
shots=1000,
164+
max_parallel_experiments=0,
165+
session_id=session_id,
166+
noise_model=_get_noise_model(),
167+
).result()
168+
169+
ideal_results = run_ideal_result.results
170+
noisy_results = run_noisy_result.results
171+
172+
assert len(ideal_results) == len(noisy_results) == 2
173+
174+
for i, ideal_result in enumerate(ideal_results):
175+
assert len(ideal_result.data.counts) < len(noisy_results[i].data.counts)
176+
finally:
177+
backend.delete_session(session_id)

tests/test_estimator.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ def test_estimator():
3232
url=os.getenv("QISKIT_SCALEWAY_API_URL"),
3333
)
3434

35-
backend = provider.get_backend("aer_simulation_pop_c16m128")
35+
backend = provider.get_backend(
36+
os.getenv("QISKIT_SCALEWAY_BACKEND_NAME", "aer_simulation_pop_c16m128")
37+
)
3638

3739
assert backend is not None
3840

0 commit comments

Comments
 (0)