Skip to content

Commit 97bdeed

Browse files
authored
Deprecate DynamicCircuitInstructionDurations and provide alternative via Target (#2403)
* Add target as input to scheduling passes, adapt test to use target as input, deprecate DynamicCircuitInstructionDurations * Restore file * Fix lint * Fix lint * Add deprecation warnings to transpiler inputs * Fix lint * Remove oversight * Address rounding errors caught in docs evaluation * Add reno
1 parent d2ea2db commit 97bdeed

File tree

11 files changed

+3042
-953
lines changed

11 files changed

+3042
-953
lines changed

qiskit_ibm_runtime/transpiler/passes/scheduling/__init__.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@
5959
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
6060
from qiskit.transpiler.passmanager import PassManager
6161
62-
from qiskit_ibm_runtime.transpiler.passes.scheduling import DynamicCircuitInstructionDurations
6362
from qiskit_ibm_runtime.transpiler.passes.scheduling import ALAPScheduleAnalysis
6463
from qiskit_ibm_runtime.transpiler.passes.scheduling import PadDelay
6564
from qiskit_ibm_runtime.fake_provider import FakeJakartaV2
@@ -68,11 +67,13 @@
6867
6968
# Use this duration class to get appropriate durations for dynamic
7069
# circuit backend scheduling
71-
durations = DynamicCircuitInstructionDurations.from_backend(backend)
7270
# Generate the main Qiskit transpile passes.
7371
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
7472
# Configure the as-late-as-possible scheduling pass
75-
pm.scheduling = PassManager([ALAPScheduleAnalysis(durations), PadDelay(durations)])
73+
pm.scheduling = PassManager([
74+
ALAPScheduleAnalysis(target=backend.target),
75+
PadDelay(target=backend.target)]
76+
)
7677
7778
qr = QuantumRegister(3)
7879
crz = ClassicalRegister(1, name="crz")
@@ -117,8 +118,8 @@
117118
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
118119
pm.scheduling = PassManager(
119120
[
120-
ALAPScheduleAnalysis(durations),
121-
PadDynamicalDecoupling(durations, dd_sequence),
121+
ALAPScheduleAnalysis(target=backend.target),
122+
PadDynamicalDecoupling(target=backend.target, dd_sequences=dd_sequence),
122123
]
123124
)
124125

qiskit_ibm_runtime/transpiler/passes/scheduling/block_base_padder.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from qiskit.circuit.parameterexpression import ParameterExpression
3131
from qiskit.converters import dag_to_circuit
3232
from qiskit.dagcircuit import DAGCircuit, DAGNode, DAGOpNode
33+
from qiskit.transpiler import Target
3334
from qiskit.transpiler.basepasses import TransformationPass
3435
from qiskit.transpiler.exceptions import TranspilerError
3536
from qiskit.circuit.controlflow import condition_resources
@@ -68,6 +69,7 @@ def __init__(
6869
self,
6970
schedule_idle_qubits: bool = False,
7071
block_ordering_callable: Optional[BlockOrderingCallableType] = None,
72+
target: Optional[Target] = None,
7173
) -> None:
7274

7375
self._node_start_time = None
@@ -99,6 +101,7 @@ def __init__(
99101
block_order_op_nodes if block_ordering_callable is None else block_ordering_callable
100102
)
101103

104+
self._target = target
102105
super().__init__()
103106

104107
def run(self, dag: DAGCircuit) -> DAGCircuit:
@@ -270,7 +273,23 @@ def _get_node_duration(self, node: DAGNode) -> int:
270273

271274
indices = [self._bit_indices[qarg] for qarg in self._map_wires(node.qargs)]
272275

273-
duration = self._durations.get(node.op, indices, unit="dt")
276+
if node.name == "delay":
277+
duration = node.op.duration
278+
elif node.name == "barrier":
279+
duration = 0
280+
elif self._target:
281+
props_dict = self._target.get(node.name)
282+
if not props_dict:
283+
duration = None
284+
props = props_dict.get(tuple(indices))
285+
if not props:
286+
duration = None
287+
if self._target.dt is None:
288+
duration = props.duration
289+
else:
290+
duration = self._target.seconds_to_dt(props.duration)
291+
else:
292+
duration = self._durations.get(node.op, indices, unit="dt")
274293

275294
if isinstance(duration, ParameterExpression):
276295
raise TranspilerError(

qiskit_ibm_runtime/transpiler/passes/scheduling/dynamical_decoupling.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from qiskit.circuit.reset import Reset
2424
from qiskit.dagcircuit import DAGCircuit, DAGNode, DAGInNode, DAGOpNode
2525
from qiskit.quantum_info.operators.predicates import matrix_equal
26+
from qiskit.transpiler import Target
2627
from qiskit.transpiler.exceptions import TranspilerError
2728
from qiskit.transpiler.instruction_durations import InstructionDurations
2829
from qiskit.transpiler.passes.optimization import Optimize1qGates
@@ -119,8 +120,8 @@ def uhrig_pulse_location(k):
119120

120121
def __init__(
121122
self,
122-
durations: InstructionDurations,
123-
dd_sequences: Union[List[Gate], List[List[Gate]]],
123+
durations: InstructionDurations = None,
124+
dd_sequences: Union[List[Gate], List[List[Gate]]] = None,
124125
qubits: Optional[List[int]] = None,
125126
spacings: Optional[Union[List[List[float]], List[float]]] = None,
126127
skip_reset_qubits: bool = True,
@@ -133,6 +134,7 @@ def __init__(
133134
schedule_idle_qubits: bool = False,
134135
dd_barrier: Optional[str] = None,
135136
block_ordering_callable: Optional[BlockOrderingCallableType] = None,
137+
target: Optional[Target] = None,
136138
):
137139
"""Dynamical decoupling initializer.
138140
@@ -199,11 +201,21 @@ def __init__(
199201
TranspilerError: When the coupling map is not supported (i.e., if degree > 3)
200202
"""
201203

204+
if durations:
205+
warnings.warn(
206+
"The `durations` input argument of `PadDynamicalDecoupling` is deprecated "
207+
"as of qiskit_ibm_runtime v0.43.0 and will be removed in a future release. "
208+
"Provide a `target` instance instead ex: PadDynamicalDecoupling(target=backend.target).",
209+
DeprecationWarning,
210+
stacklevel=2,
211+
)
212+
202213
super().__init__(
203214
schedule_idle_qubits=schedule_idle_qubits,
204215
block_ordering_callable=block_ordering_callable,
205216
)
206217
self._durations = durations
218+
self._target = target
207219

208220
# Enforce list of DD sequences
209221
if dd_sequences:
@@ -348,7 +360,18 @@ def _pre_runhook(self, dag: DAGCircuit) -> None:
348360
continue
349361

350362
for index, gate in enumerate(seq):
351-
gate_length = self._durations.get(gate, physical_index)
363+
if self._target:
364+
try:
365+
gate_length = self._target[gate.name].get((physical_index,)).duration
366+
except: # pylint: disable=bare-except
367+
gate_length = None
368+
else:
369+
gate_length = self._durations.get(gate, physical_index)
370+
371+
if gate_length is None:
372+
raise TranspilerError(
373+
f"Duration of {gate} on qubits {physical_index} is not found."
374+
)
352375
seq_length_.append(gate_length)
353376
# Update gate duration.
354377
# This is necessary for current timeline drawer, i.e. scheduled.
@@ -543,6 +566,11 @@ def _constrained_length(values: np.array) -> np.array:
543566
# Interleave delays with DD sequence operations
544567
for tau_idx, tau in enumerate(taus):
545568
if tau > 0:
569+
# Delay only accept integer durations if the unit is 'dt',
570+
# but the tau calculation can result in floating-point
571+
# rounding errors (like 4.99999 instead of 5).
572+
if self._dag.unit == "dt":
573+
tau = round(tau)
546574
self._apply_scheduled_op(
547575
block_idx, idle_after, Delay(tau, self._dag.unit), qubit
548576
)

qiskit_ibm_runtime/transpiler/passes/scheduling/pad_delay.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313
"""Padding pass to insert Delay into empty timeslots for dynamic circuit backends."""
1414

1515
from typing import Optional
16+
import warnings
1617

1718
from qiskit.circuit import Qubit
1819
from qiskit.circuit.delay import Delay
1920
from qiskit.dagcircuit import DAGNode, DAGOutNode
21+
from qiskit.transpiler import Target
2022
from qiskit.transpiler.instruction_durations import InstructionDurations
2123

2224
from .block_base_padder import BlockBasePadder
@@ -56,10 +58,11 @@ class PadDelay(BlockBasePadder):
5658

5759
def __init__(
5860
self,
59-
durations: InstructionDurations,
61+
durations: InstructionDurations = None,
6062
fill_very_end: bool = True,
6163
schedule_idle_qubits: bool = False,
6264
block_ordering_callable: Optional[BlockOrderingCallableType] = None,
65+
target: Target = None,
6366
):
6467
"""Create new padding delay pass.
6568
@@ -73,11 +76,22 @@ def __init__(
7376
the number of blocks needed. If not provided, :func:`~block_order_op_nodes` will be
7477
used.
7578
"""
79+
80+
if durations:
81+
warnings.warn(
82+
"The `durations` input argument of `PadDelay` is deprecated "
83+
"as of qiskit_ibm_runtime v0.43.0 and will be removed in a future release. "
84+
"Provide a `target` instance instead ex: PadDelay(target=backend.target).",
85+
DeprecationWarning,
86+
stacklevel=2,
87+
)
88+
7689
super().__init__(
7790
schedule_idle_qubits=schedule_idle_qubits,
7891
block_ordering_callable=block_ordering_callable,
7992
)
8093
self._durations = durations
94+
self._target = target
8195
self.fill_very_end = fill_very_end
8296

8397
def _pad(

qiskit_ibm_runtime/transpiler/passes/scheduling/scheduler.py

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@
1515
from abc import abstractmethod
1616
from typing import Dict, List, Optional, Union, Set, Tuple
1717
import itertools
18+
import warnings
1819

1920
import qiskit
20-
from qiskit.circuit import Bit
21+
from qiskit.circuit import Bit, Barrier, Clbit, ControlFlowOp, Measure, Qubit, Reset
2122
from qiskit.circuit.parameterexpression import ParameterExpression
2223
from qiskit.converters import circuit_to_dag
23-
from qiskit.transpiler.basepasses import TransformationPass
24-
from qiskit.transpiler.passes.scheduling.time_unit_conversion import TimeUnitConversion
25-
26-
from qiskit.circuit import Barrier, Clbit, ControlFlowOp, Measure, Qubit, Reset
2724
from qiskit.dagcircuit import DAGCircuit, DAGNode
25+
from qiskit.transpiler import Target
2826
from qiskit.transpiler.exceptions import TranspilerError
27+
from qiskit.transpiler.basepasses import TransformationPass
28+
from qiskit.transpiler.passes.scheduling.time_unit_conversion import TimeUnitConversion
2929

3030
from .utils import BlockOrderingCallableType, block_order_op_nodes
3131

@@ -50,8 +50,9 @@ class BaseDynamicCircuitAnalysis(TransformationPass):
5050

5151
def __init__(
5252
self,
53-
durations: qiskit.transpiler.instruction_durations.InstructionDurations,
53+
durations: Optional[qiskit.transpiler.instruction_durations.InstructionDurations] = None,
5454
block_ordering_callable: Optional[BlockOrderingCallableType] = None,
55+
target: Optional[Target] = None,
5556
) -> None:
5657
"""Scheduler for dynamic circuit backends.
5758
@@ -61,7 +62,19 @@ def __init__(
6162
the number of blocks needed. If not provided, :func:`~block_order_op_nodes` will be
6263
used.
6364
"""
65+
66+
if durations:
67+
warnings.warn(
68+
"The `durations` input argument of `BaseDynamicCircuitAnalysis` is deprecated "
69+
"as of qiskit_ibm_runtime v0.43.0 and will be removed in a future release. "
70+
"Provide a `target` instance instead ex: "
71+
"BaseDynamicCircuitAnalysis(target=backend.target).",
72+
DeprecationWarning,
73+
stacklevel=2,
74+
)
75+
6476
self._durations = durations
77+
self._target = target
6578
self._block_ordering_callable = (
6679
block_order_op_nodes if block_ordering_callable is None else block_ordering_callable
6780
)
@@ -201,18 +214,33 @@ def _init_run(self, dag: DAGCircuit) -> None:
201214
self._node_tied_to = {}
202215
self._bit_indices = {q: index for index, q in enumerate(dag.qubits)}
203216

204-
def _get_duration(self, node: DAGNode, dag: Optional[DAGCircuit] = None) -> int:
217+
def _get_duration(self, node: DAGNode) -> int:
205218
if isinstance(node.op, ControlFlowOp):
206219
# As we cannot currently schedule through conditionals model
207220
# as zero duration to avoid padding.
208221
return 0
209222

210223
indices = [self._bit_indices[qarg] for qarg in self._map_qubits(node)]
211224

212-
# Fall back to current block dag if not specified.
213-
dag = dag or self._block_dag
214-
215-
duration = self._durations.get(node.op, indices, unit="dt")
225+
if node.name == "delay":
226+
duration = node.op.duration
227+
elif node.name == "barrier":
228+
duration = 0
229+
elif self._target:
230+
props_dict = self._target.get(node.name)
231+
if not props_dict:
232+
duration = None
233+
else:
234+
props = props_dict.get(tuple(indices))
235+
if not props:
236+
duration = None
237+
else:
238+
if self._target.dt is None:
239+
duration = props.duration
240+
else:
241+
duration = self._target.seconds_to_dt(props.duration)
242+
else:
243+
duration = self._durations.get(node.op, indices, unit="dt")
216244

217245
if isinstance(duration, ParameterExpression):
218246
raise TranspilerError(
@@ -383,12 +411,7 @@ def _visit_measure(self, node: DAGNode) -> None:
383411

384412
for measure in self._current_block_measures:
385413
t0 = t0q # pylint: disable=invalid-name
386-
bit_indices = {bit: index for index, bit in enumerate(self._block_dag.qubits)}
387-
measure_duration = self._durations.get(
388-
Measure(),
389-
[bit_indices[qarg] for qarg in self._map_qubits(measure)],
390-
unit="dt",
391-
)
414+
measure_duration = self._get_duration(measure)
392415
t1 = t0 + measure_duration # pylint: disable=invalid-name
393416
self._update_bit_times(measure, t0, t1)
394417

@@ -514,12 +537,7 @@ def _visit_measure(self, node: DAGNode) -> None:
514537

515538
for measure in self._current_block_measures:
516539
t0 = t0q # pylint: disable=invalid-name
517-
bit_indices = {bit: index for index, bit in enumerate(self._block_dag.qubits)}
518-
measure_duration = self._durations.get(
519-
Measure(),
520-
[bit_indices[qarg] for qarg in self._map_qubits(measure)],
521-
unit="dt",
522-
)
540+
measure_duration = self._get_duration(measure)
523541
t1 = t0 + measure_duration # pylint: disable=invalid-name
524542
self._update_bit_times(measure, t0, t1)
525543

@@ -573,7 +591,7 @@ def order_ops(item: Tuple[DAGNode, Tuple[int, int]]) -> Tuple[int, int, bool, in
573591
item[1][0],
574592
-item[1][1],
575593
not isinstance(item[0].op, Barrier),
576-
self._get_duration(item[0], dag=self._block_idx_dag_map[item[1][0]]),
594+
self._get_duration(item[0]),
577595
)
578596

579597
iterate_nodes = sorted(self._node_stop_time.items(), key=order_ops)
@@ -606,7 +624,7 @@ def _update_time(
606624
new_node_start_time[node] = (block, new_time)
607625
new_node_stop_time[node] = (
608626
block,
609-
new_time + self._get_duration(node, dag=self._block_idx_dag_map[block]),
627+
new_time + self._get_duration(node),
610628
)
611629

612630
# Update available times by bit

qiskit_ibm_runtime/transpiler/passes/scheduling/utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,17 @@ def __init__(
156156
enable_patching: bool = True,
157157
):
158158
"""Dynamic circuit instruction durations."""
159+
warnings.warn(
160+
"The DynamicCircuitInstructionDurations class is deprecated "
161+
"as of qiskit_ibm_runtime v0.43.0 "
162+
"and will be removed in a future release. If you are using "
163+
"one of the scheduling passes defined "
164+
"in qiskit_ibm_runtime, provide a `target` instance instead. "
165+
"ex: PadDelay(target=backend.target).",
166+
DeprecationWarning,
167+
stacklevel=2,
168+
)
169+
159170
self._enable_patching = enable_patching
160171
super().__init__(instruction_durations=instruction_durations, dt=dt)
161172

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
Added a new ``target`` argument to the initializer following transpiler analysis and padding passes:
3+
:class:`.ALAPScheduleAnalysis`, :class:`.PadDelay`, :class:`.PadDynamicalDecoupling`, :class:`.BlockBasePadder`.
4+
This change aligns these passes with the broader Qiskit transpiler architecture, and supersedes the use of the
5+
``durations`` argument.
6+
7+
The :class:`.DynamicCircuitInstructionDurations` class, used in custom schedulin passes, has been deprecated as of
8+
``qiskit-ibm-runtime`` v0.43. This class was optimized for scheduling operations on Eagle processors, and it
9+
has fallen out of date with the current offering of Heron processors. This class was used to define the ```durations``
10+
argument in the scheduling passes listed above
11+
(:class:`.ALAPScheduleAnalysis`, :class:`.PadDelay`, :class:`.PadDynamicalDecoupling`, :class:`.BlockBasePadder`).
12+
The argument is also deprecated and will be removed in a future release. Users are encouraged to migrate to
13+
the ``target`` argument.

0 commit comments

Comments
 (0)