Skip to content

Commit e0358d6

Browse files
nquetschlichflowerthrowerburgholzer
authored
✨ Qiskit v2 Support (#406)
<!--- This file has been generated from an external template. Please do not modify it directly. --> <!--- Changes should be contributed to https://github.com/munich-quantum-toolkit/templates. --> ## Description This PR allows to support Qiskit v2. Furthermore, it adds `rich` to the dependencies and fixes an inconsistency in the documentation. ## Checklist: - [x] The pull request only contains commits that are focused and relevant to this change. - [x] I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals. - [x] The changes follow the project's style guidelines and introduce no new warnings. - [x] The changes are fully tested and pass the CI checks. - [x] I have reviewed my own code changes. --------- Signed-off-by: Nils Quetschlich <[email protected]> Co-authored-by: Patrick Hopf <[email protected]> Co-authored-by: Lukas Burgholzer <[email protected]>
1 parent 90acf10 commit e0358d6

File tree

5 files changed

+103
-79
lines changed

5 files changed

+103
-79
lines changed

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ This project adheres to [Semantic Versioning], with the exception that minor rel
1515

1616
### Changed
1717

18-
- ✨ Improved the ML part and its usability ([#403]) ([**@nquetschlich**])
19-
- 📝 Migrated the documentation from .rst to .md files ([#403]) ([**@nquetschlich**])
18+
- 🎨 Adjust the ESP reward calculation to become Qiskit v2 compatible ([#406]) ([**@nquetschlich**])
19+
- ✨ Improve the ML part and its usability ([#403]) ([**@nquetschlich**])
20+
- 📝 Migrate the documentation from .rst to .md files ([#403]) ([**@nquetschlich**])
2021
- ✨ Improve RL action handling by using dataclasses ([#401]) ([**@nquetschlich**])
2122
- ✨ Support MQT Bench v2 and use Qiskit's Target to represent quantum devices ([#393]) ([**@nquetschlich**])
2223

@@ -31,6 +32,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool
3132

3233
<!-- PR links -->
3334

35+
[#406]: https://github.com/munich-quantum-toolkit/predictor/pull/406
3436
[#405]: https://github.com/munich-quantum-toolkit/predictor/pull/405
3537
[#403]: https://github.com/munich-quantum-toolkit/predictor/pull/403
3638
[#401]: https://github.com/munich-quantum-toolkit/predictor/pull/401

docs/setup.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ For each device to be considered, a dedicated reinforcement learning (RL) model
3535

3636
```python
3737
from mqt.predictor.rl import Predictor as RL_Predictor
38-
from mqt.bench.targets import get_target
38+
from mqt.bench.targets import get_device
3939

40-
device = get_target("ibm_falcon_27")
40+
device = get_device("ibm_falcon_27")
4141
rl_pred = RL_Predictor(device=device, figure_of_merit="expected_fidelity")
4242
rl_pred.train_model(timesteps=100000, model_name="ibm_falcon_27_model")
4343
```

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,12 @@ dynamic = ["version"]
3030

3131
dependencies = [
3232
"mqt.bench>=2.0.0",
33-
"qiskit!=1.3.2, <2", # explicitly upper cap Qiskit 2.0 as it is not supported yet. 1.3.2 causes a Qiskit error when using the CommutativeInverseCancellation pass, see https://github.com/Qiskit/qiskit/issues/13742
33+
"qiskit!=1.3.2", # 1.3.2 causes a Qiskit error when using the CommutativeInverseCancellation pass, see https://github.com/Qiskit/qiskit/issues/13742
3434
"pytket>=1.29.0", # lowest version that supports the used pytket AutoRebase pass instead of auto_rebase
3535
"pytket_qiskit>=0.60.0",
3636
"sb3_contrib>=2.0.0",
3737
"tqdm>=4.66.0",
38+
"rich>=12.6.0",
3839
"scikit-learn>=1.5.1",
3940
"tensorboard>=2.17.0",
4041
"bqskit>=1.2.0",

src/mqt/predictor/reward.py

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
import numpy as np
1717
from joblib import load
18+
from qiskit import __version__ as qiskit_version
19+
from qiskit import transpile
1820

1921
from mqt.predictor.hellinger import calc_device_specific_features, get_hellinger_model_path
2022
from mqt.predictor.utils import calc_supermarq_features
@@ -111,11 +113,8 @@ def estimated_success_probability(qc: QuantumCircuit, device: Target, precision:
111113
Returns:
112114
The expected success probability of the given quantum circuit on the given device.
113115
"""
114-
# lazy import of qiskit transpiler
115-
from qiskit.transpiler import InstructionDurations, Layout, PassManager, passes # noqa: PLC0415
116-
from qiskit.transpiler.passes import ApplyLayout, SetLayout # noqa: PLC0415
116+
exec_time_per_qubit = dict.fromkeys(range(device.num_qubits), 0.0)
117117

118-
# collect gate and measurement durations for active qubits
119118
op_times, active_qubits = [], set()
120119
for instr in qc.data:
121120
instruction = instr.operation
@@ -138,30 +137,48 @@ def estimated_success_probability(qc: QuantumCircuit, device: Target, precision:
138137
duration,
139138
"s",
140139
))
140+
exec_time_per_qubit[first_qubit_idx] += duration
141141
else: # multi-qubit gate
142142
second_qubit_idx = calc_qubit_index(qargs, qc.qregs, 1)
143143
active_qubits.add(second_qubit_idx)
144144
duration = device[gate_type][first_qubit_idx, second_qubit_idx].duration
145145
op_times.append((gate_type, [first_qubit_idx, second_qubit_idx], duration, "s"))
146-
147-
# check whether the circuit was transformed by tket (i.e. changed register name)
148-
# qiskit ASAPScheduleAnalysis expects all qubit registers to be named 'q'
149-
if qc.qregs[0].name != "q":
150-
# create a layout that maps the (tket) 'node' registers to the (qiskit) 'q' registers
151-
layouts = [SetLayout(Layout({node_qubit: i for i, node_qubit in enumerate(node_reg)})) for node_reg in qc.qregs]
152-
# create a pass manager with the SetLayout and ApplyLayout passes
153-
pm = PassManager(list(layouts))
154-
pm.append(ApplyLayout())
155-
156-
# replace the 'node' register with the 'q' register in the circuit
157-
qc = pm.run(qc)
158-
assert qc.qregs[0].name == "q"
159-
160-
# associate gate and idle (delay) times for each qubit through asap scheduling
161-
sched_pass = passes.ASAPScheduleAnalysis(InstructionDurations(op_times))
162-
delay_pass = passes.PadDelay()
163-
pm = PassManager([sched_pass, delay_pass])
164-
scheduled_circ = pm.run(qc)
146+
exec_time_per_qubit[first_qubit_idx] += duration
147+
exec_time_per_qubit[second_qubit_idx] += duration
148+
149+
if qiskit_version < "2.0.0":
150+
from qiskit.transpiler import InstructionDurations, Layout, PassManager, passes # noqa: PLC0415
151+
from qiskit.transpiler.passes import ApplyLayout, SetLayout # noqa: PLC0415
152+
153+
if qc.qregs[0].name != "q":
154+
# create a layout that maps the (tket) 'node' registers to the (qiskit) 'q' registers
155+
layouts = [
156+
SetLayout(Layout({node_qubit: i for i, node_qubit in enumerate(node_reg)})) for node_reg in qc.qregs
157+
]
158+
# create a pass manager with the SetLayout and ApplyLayout passes
159+
pm = PassManager(list(layouts))
160+
pm.append(ApplyLayout())
161+
162+
# replace the 'node' register with the 'q' register in the circuit
163+
qc = pm.run(qc)
164+
assert qc.qregs[0].name == "q"
165+
166+
sched_pass = passes.ASAPScheduleAnalysis(InstructionDurations(op_times))
167+
delay_pass = passes.PadDelay()
168+
pm = PassManager([sched_pass, delay_pass])
169+
scheduled_circ = pm.run(qc)
170+
171+
else:
172+
scheduled_circ = transpile(
173+
qc,
174+
target=device,
175+
scheduling_method="asap",
176+
optimization_level=0,
177+
initial_layout=None,
178+
routing_method=None,
179+
layout_method=None,
180+
)
181+
overall_estimated_duration = scheduled_circ.estimate_duration(target=device)
165182

166183
res = 1.0
167184
for instr in scheduled_circ.data:
@@ -179,8 +196,9 @@ def estimated_success_probability(qc: QuantumCircuit, device: Target, precision:
179196
if gate_type == "measure":
180197
res *= 1 - device[gate_type][first_qubit_idx,].error
181198
continue
182-
183199
if gate_type == "delay":
200+
if qiskit_version < "2.0.0":
201+
continue
184202
# only consider active qubits
185203
if first_qubit_idx not in active_qubits:
186204
continue
@@ -190,12 +208,18 @@ def estimated_success_probability(qc: QuantumCircuit, device: Target, precision:
190208
/ min(device.qubit_properties[first_qubit_idx].t1, device.qubit_properties[first_qubit_idx].t2)
191209
)
192210
continue
193-
194211
res *= 1 - device[gate_type][first_qubit_idx,].error
195212
else:
196213
second_qubit_idx = calc_qubit_index(qargs, qc.qregs, 1)
197214
res *= 1 - device[gate_type][first_qubit_idx, second_qubit_idx].error
198215

216+
if qiskit_version >= "2.0.0":
217+
for i in range(device.num_qubits):
218+
qubit_execution_time = exec_time_per_qubit[i]
219+
if qubit_execution_time == 0:
220+
continue
221+
idle_time = overall_estimated_duration - qubit_execution_time
222+
res *= np.exp(-idle_time / min(device.qubit_properties[i].t1, device.qubit_properties[i].t2))
199223
return float(np.round(res, precision).item())
200224

201225

0 commit comments

Comments
 (0)