Skip to content
Open
11 changes: 6 additions & 5 deletions qiskit_ibm_runtime/transpiler/passes/scheduling/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.transpiler.passmanager import PassManager

from qiskit_ibm_runtime.transpiler.passes.scheduling import DynamicCircuitInstructionDurations
from qiskit_ibm_runtime.transpiler.passes.scheduling import ALAPScheduleAnalysis
from qiskit_ibm_runtime.transpiler.passes.scheduling import PadDelay
from qiskit_ibm_runtime.fake_provider import FakeJakartaV2
Expand All @@ -68,11 +67,13 @@

# Use this duration class to get appropriate durations for dynamic
# circuit backend scheduling
durations = DynamicCircuitInstructionDurations.from_backend(backend)
# Generate the main Qiskit transpile passes.
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
# Configure the as-late-as-possible scheduling pass
pm.scheduling = PassManager([ALAPScheduleAnalysis(durations), PadDelay(durations)])
pm.scheduling = PassManager([
ALAPScheduleAnalysis(target=backend.target),
PadDelay(target=backend.target)]
)

qr = QuantumRegister(3)
crz = ClassicalRegister(1, name="crz")
Expand Down Expand Up @@ -117,8 +118,8 @@
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
pm.scheduling = PassManager(
[
ALAPScheduleAnalysis(durations),
PadDynamicalDecoupling(durations, dd_sequence),
ALAPScheduleAnalysis(target=backend.target),
PadDynamicalDecoupling(target=backend.target, dd_sequences=dd_sequence),
]
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.converters import dag_to_circuit
from qiskit.dagcircuit import DAGCircuit, DAGNode, DAGOpNode
from qiskit.transpiler import Target
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.circuit.controlflow import condition_resources
Expand Down Expand Up @@ -68,6 +69,7 @@ def __init__(
self,
schedule_idle_qubits: bool = False,
block_ordering_callable: Optional[BlockOrderingCallableType] = None,
target: Optional[Target] = None,
) -> None:

self._node_start_time = None
Expand Down Expand Up @@ -99,6 +101,7 @@ def __init__(
block_order_op_nodes if block_ordering_callable is None else block_ordering_callable
)

self._target = target
super().__init__()

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

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

duration = self._durations.get(node.op, indices, unit="dt")
if node.name == "delay":
duration = node.op.duration
elif node.name == "barrier":
duration = 0
elif self._target:
props_dict = self._target.get(node.name)
if not props_dict:
duration = None
props = props_dict.get(tuple(indices))
if not props:
duration = None
if self._target.dt is None:
duration = props.duration
else:
duration = self._target.seconds_to_dt(props.duration)
else:
duration = self._durations.get(node.op, indices, unit="dt")

if isinstance(duration, ParameterExpression):
raise TranspilerError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from qiskit.circuit.reset import Reset
from qiskit.dagcircuit import DAGCircuit, DAGNode, DAGInNode, DAGOpNode
from qiskit.quantum_info.operators.predicates import matrix_equal
from qiskit.transpiler import Target
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.instruction_durations import InstructionDurations
from qiskit.transpiler.passes.optimization import Optimize1qGates
Expand Down Expand Up @@ -119,8 +120,8 @@ def uhrig_pulse_location(k):

def __init__(
self,
durations: InstructionDurations,
dd_sequences: Union[List[Gate], List[List[Gate]]],
durations: InstructionDurations = None,
dd_sequences: Union[List[Gate], List[List[Gate]]] = None,
qubits: Optional[List[int]] = None,
spacings: Optional[Union[List[List[float]], List[float]]] = None,
skip_reset_qubits: bool = True,
Expand All @@ -133,6 +134,7 @@ def __init__(
schedule_idle_qubits: bool = False,
dd_barrier: Optional[str] = None,
block_ordering_callable: Optional[BlockOrderingCallableType] = None,
target: Optional[Target] = None,
):
"""Dynamical decoupling initializer.

Expand Down Expand Up @@ -199,11 +201,21 @@ def __init__(
TranspilerError: When the coupling map is not supported (i.e., if degree > 3)
"""

if durations:
warnings.warn(
"The `durations` input argument of `PadDynamicalDecoupling` is deprecated "
"as of qiskit_ibm_runtime v0.43.0 and will be removed in a future release. "
"Provide a `target` instance instead ex: PadDynamicalDecoupling(target=backend.target).",
DeprecationWarning,
stacklevel=2,
)

super().__init__(
schedule_idle_qubits=schedule_idle_qubits,
block_ordering_callable=block_ordering_callable,
)
self._durations = durations
self._target = target

# Enforce list of DD sequences
if dd_sequences:
Expand Down Expand Up @@ -348,7 +360,18 @@ def _pre_runhook(self, dag: DAGCircuit) -> None:
continue

for index, gate in enumerate(seq):
gate_length = self._durations.get(gate, physical_index)
if self._target:
try:
gate_length = self._target[gate.name].get((physical_index,)).duration
except: # pylint: disable=bare-except
gate_length = None
else:
gate_length = self._durations.get(gate, physical_index)

if gate_length is None:
raise TranspilerError(
f"Duration of {gate} on qubits {physical_index} is not found."
)
seq_length_.append(gate_length)
# Update gate duration.
# This is necessary for current timeline drawer, i.e. scheduled.
Expand Down Expand Up @@ -543,6 +566,11 @@ def _constrained_length(values: np.array) -> np.array:
# Interleave delays with DD sequence operations
for tau_idx, tau in enumerate(taus):
if tau > 0:
# Delay only accept integer durations if the unit is 'dt',
# but the tau calculation can result in floating-point
# rounding errors (like 4.99999 instead of 5).
if self._dag.unit == "dt":
tau = round(tau)
self._apply_scheduled_op(
block_idx, idle_after, Delay(tau, self._dag.unit), qubit
)
Expand Down
16 changes: 15 additions & 1 deletion qiskit_ibm_runtime/transpiler/passes/scheduling/pad_delay.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
"""Padding pass to insert Delay into empty timeslots for dynamic circuit backends."""

from typing import Optional
import warnings

from qiskit.circuit import Qubit
from qiskit.circuit.delay import Delay
from qiskit.dagcircuit import DAGNode, DAGOutNode
from qiskit.transpiler import Target
from qiskit.transpiler.instruction_durations import InstructionDurations

from .block_base_padder import BlockBasePadder
Expand Down Expand Up @@ -56,10 +58,11 @@ class PadDelay(BlockBasePadder):

def __init__(
self,
durations: InstructionDurations,
durations: InstructionDurations = None,
fill_very_end: bool = True,
schedule_idle_qubits: bool = False,
block_ordering_callable: Optional[BlockOrderingCallableType] = None,
target: Target = None,
):
"""Create new padding delay pass.

Expand All @@ -73,11 +76,22 @@ def __init__(
the number of blocks needed. If not provided, :func:`~block_order_op_nodes` will be
used.
"""

if durations:
warnings.warn(
"The `durations` input argument of `PadDelay` is deprecated "
"as of qiskit_ibm_runtime v0.43.0 and will be removed in a future release. "
"Provide a `target` instance instead ex: PadDelay(target=backend.target).",
DeprecationWarning,
stacklevel=2,
)

super().__init__(
schedule_idle_qubits=schedule_idle_qubits,
block_ordering_callable=block_ordering_callable,
)
self._durations = durations
self._target = target
self.fill_very_end = fill_very_end

def _pad(
Expand Down
68 changes: 43 additions & 25 deletions qiskit_ibm_runtime/transpiler/passes/scheduling/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@
from abc import abstractmethod
from typing import Dict, List, Optional, Union, Set, Tuple
import itertools
import warnings

import qiskit
from qiskit.circuit import Bit
from qiskit.circuit import Bit, Barrier, Clbit, ControlFlowOp, Measure, Qubit, Reset
from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.converters import circuit_to_dag
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.passes.scheduling.time_unit_conversion import TimeUnitConversion

from qiskit.circuit import Barrier, Clbit, ControlFlowOp, Measure, Qubit, Reset
from qiskit.dagcircuit import DAGCircuit, DAGNode
from qiskit.transpiler import Target
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.passes.scheduling.time_unit_conversion import TimeUnitConversion

from .utils import BlockOrderingCallableType, block_order_op_nodes

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

def __init__(
self,
durations: qiskit.transpiler.instruction_durations.InstructionDurations,
durations: Optional[qiskit.transpiler.instruction_durations.InstructionDurations] = None,
block_ordering_callable: Optional[BlockOrderingCallableType] = None,
target: Optional[Target] = None,
) -> None:
"""Scheduler for dynamic circuit backends.

Expand All @@ -61,7 +62,19 @@ def __init__(
the number of blocks needed. If not provided, :func:`~block_order_op_nodes` will be
used.
"""

if durations:
warnings.warn(
"The `durations` input argument of `BaseDynamicCircuitAnalysis` is deprecated "
"as of qiskit_ibm_runtime v0.43.0 and will be removed in a future release. "
"Provide a `target` instance instead ex: "
"BaseDynamicCircuitAnalysis(target=backend.target).",
DeprecationWarning,
stacklevel=2,
)

self._durations = durations
self._target = target
self._block_ordering_callable = (
block_order_op_nodes if block_ordering_callable is None else block_ordering_callable
)
Expand Down Expand Up @@ -201,18 +214,33 @@ def _init_run(self, dag: DAGCircuit) -> None:
self._node_tied_to = {}
self._bit_indices = {q: index for index, q in enumerate(dag.qubits)}

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

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

# Fall back to current block dag if not specified.
dag = dag or self._block_dag

duration = self._durations.get(node.op, indices, unit="dt")
if node.name == "delay":
duration = node.op.duration
elif node.name == "barrier":
duration = 0
elif self._target:
props_dict = self._target.get(node.name)
if not props_dict:
duration = None
else:
props = props_dict.get(tuple(indices))
if not props:
duration = None
else:
if self._target.dt is None:
duration = props.duration
else:
duration = self._target.seconds_to_dt(props.duration)
else:
duration = self._durations.get(node.op, indices, unit="dt")

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

for measure in self._current_block_measures:
t0 = t0q # pylint: disable=invalid-name
bit_indices = {bit: index for index, bit in enumerate(self._block_dag.qubits)}
measure_duration = self._durations.get(
Measure(),
[bit_indices[qarg] for qarg in self._map_qubits(measure)],
unit="dt",
)
measure_duration = self._get_duration(measure)
t1 = t0 + measure_duration # pylint: disable=invalid-name
self._update_bit_times(measure, t0, t1)

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

for measure in self._current_block_measures:
t0 = t0q # pylint: disable=invalid-name
bit_indices = {bit: index for index, bit in enumerate(self._block_dag.qubits)}
measure_duration = self._durations.get(
Measure(),
[bit_indices[qarg] for qarg in self._map_qubits(measure)],
unit="dt",
)
measure_duration = self._get_duration(measure)
t1 = t0 + measure_duration # pylint: disable=invalid-name
self._update_bit_times(measure, t0, t1)

Expand Down Expand Up @@ -573,7 +591,7 @@ def order_ops(item: Tuple[DAGNode, Tuple[int, int]]) -> Tuple[int, int, bool, in
item[1][0],
-item[1][1],
not isinstance(item[0].op, Barrier),
self._get_duration(item[0], dag=self._block_idx_dag_map[item[1][0]]),
self._get_duration(item[0]),
)

iterate_nodes = sorted(self._node_stop_time.items(), key=order_ops)
Expand Down Expand Up @@ -606,7 +624,7 @@ def _update_time(
new_node_start_time[node] = (block, new_time)
new_node_stop_time[node] = (
block,
new_time + self._get_duration(node, dag=self._block_idx_dag_map[block]),
new_time + self._get_duration(node),
)

# Update available times by bit
Expand Down
11 changes: 11 additions & 0 deletions qiskit_ibm_runtime/transpiler/passes/scheduling/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,17 @@ def __init__(
enable_patching: bool = True,
):
"""Dynamic circuit instruction durations."""
warnings.warn(
"The DynamicCircuitInstructionDurations class is deprecated "
"as of qiskit_ibm_runtime v0.43.0 "
"and will be removed in a future release. If you are using "
"one of the scheduling passes defined "
"in qiskit_ibm_runtime, provide a `target` instance instead. "
"ex: PadDelay(target=backend.target).",
DeprecationWarning,
stacklevel=2,
)

self._enable_patching = enable_patching
super().__init__(instruction_durations=instruction_durations, dt=dt)

Expand Down
13 changes: 13 additions & 0 deletions release-notes/unreleased/2403.deprecation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

Added a new ``target`` argument to the initializer following transpiler analysis and padding passes:
:class:`.ALAPScheduleAnalysis`, :class:`.PadDelay`, :class:`.PadDynamicalDecoupling`, :class:`.BlockBasePadder`.
This change aligns these passes with the broader Qiskit transpiler architecture, and supersedes the use of the
``durations`` argument.

The :class:`.DynamicCircuitInstructionDurations` class, used in custom schedulin passes, has been deprecated as of
``qiskit-ibm-runtime`` v0.43. This class was optimized for scheduling operations on Eagle processors, and it
has fallen out of date with the current offering of Heron processors. This class was used to define the ```durations``
argument in the scheduling passes listed above
(:class:`.ALAPScheduleAnalysis`, :class:`.PadDelay`, :class:`.PadDynamicalDecoupling`, :class:`.BlockBasePadder`).
The argument is also deprecated and will be removed in a future release. Users are encouraged to migrate to
the ``target`` argument.
Loading