Skip to content

Commit

Permalink
Fix ASAP/ALAP scheduling pass (#7655)
Browse files Browse the repository at this point in the history
* fix scheduling pass

This commit updates both ASAP and ALAP passes not to allow measurement instructions to simultaneously write in the same register.
In addition, delay appended after end of circuit is removed since this instruction has no effect.

* Update behavior of passes

Added `BaseScheduler` as a parent class of ASAP and ALAP passes. These scheduler can take two control parameters `clbit_write_latency` and `conditional_latency`. These represent I/O latency of clbits.
In addition, delays in the very end of the scheduled circuit is readded because Dynamical Decoupling passes inserts echo sequence there. More unittests and reno are also added.

* fix ASAP conditional bug

The conditional bit start time was only looking cregs. But this should start right before the gate.

Co-authored-by: Toshinari Itoko <itoko@jp.ibm.com>

* respond to review comments
- fix typo
- update model drawing in comment
- more comment

* update documentation and add todo comment

* update logic to insert conditional bit assert

* add more docs on topological ordering

* Update qiskit/transpiler/passes/scheduling/base_scheduler.py

Co-authored-by: Toshinari Itoko <15028342+itoko@users.noreply.github.com>

* lint fix

Co-authored-by: Toshinari Itoko <itoko@jp.ibm.com>
Co-authored-by: Toshinari Itoko <15028342+itoko@users.noreply.github.com>
Co-authored-by: Ikko Hamamura <ikkoham@users.noreply.github.com>
  • Loading branch information
4 people authored Feb 22, 2022
1 parent 6eb1296 commit a6cb1a3
Show file tree
Hide file tree
Showing 6 changed files with 1,030 additions and 204 deletions.
166 changes: 85 additions & 81 deletions qiskit/transpiler/passes/scheduling/alap.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,20 @@
# that they have been altered from the originals.

"""ALAP Scheduling."""
import itertools
from collections import defaultdict
from typing import List

from qiskit.circuit import Delay, Measure
from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.circuit import Delay, Qubit, Measure
from qiskit.dagcircuit import DAGCircuit
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.passes.scheduling.time_unit_conversion import TimeUnitConversion

from .base_scheduler import BaseScheduler

class ALAPSchedule(TransformationPass):
"""ALAP Scheduling pass, which schedules the **stop** time of instructions as late as possible.

For circuits with instructions writing or reading clbits (e.g. measurements, conditional gates),
the scheduler assumes clbits I/O operations take no time, ``measure`` locks clbits to be written
at its end and ``c_if`` locks clbits to be read at its beginning.
class ALAPSchedule(BaseScheduler):
"""ALAP Scheduling pass, which schedules the **stop** time of instructions as late as possible.
Notes:
The ALAP scheduler may not schedule a circuit exactly the same as any real backend does
when the circuit contains control flows (e.g. conditional instructions).
See :class:`~qiskit.transpiler.passes.scheduling.base_scheduler.BaseScheduler` for the
detailed behavior of the control flow operation, i.e. ``c_if``.
"""

def __init__(self, durations):
"""ALAPSchedule initializer.
Args:
durations (InstructionDurations): Durations of instructions to be used in scheduling
"""
super().__init__()
self.durations = durations
# ensure op node durations are attached and in consistent unit
self.requires.append(TimeUnitConversion(durations))

def run(self, dag):
"""Run the ALAPSchedule pass on `dag`.
Expand All @@ -57,6 +36,7 @@ def run(self, dag):
Raises:
TranspilerError: if the circuit is not mapped on physical qubits.
TranspilerError: if conditional bit is added to non-supported instruction.
"""
if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None:
raise TranspilerError("ALAP schedule runs on physical circuits only")
Expand All @@ -68,71 +48,95 @@ def run(self, dag):
for creg in dag.cregs.values():
new_dag.add_creg(creg)

qubit_time_available = defaultdict(int)
clbit_readable = defaultdict(int)
clbit_writeable = defaultdict(int)

def pad_with_delays(qubits: List[int], until, unit) -> None:
"""Pad idle time-slots in ``qubits`` with delays in ``unit`` until ``until``."""
for q in qubits:
if qubit_time_available[q] < until:
idle_duration = until - qubit_time_available[q]
new_dag.apply_operation_front(Delay(idle_duration, unit), [q], [])

idle_before = {q: 0 for q in dag.qubits + dag.clbits}
bit_indices = {bit: index for index, bit in enumerate(dag.qubits)}
for node in reversed(list(dag.topological_op_nodes())):
# validate node.op.duration
if node.op.duration is None:
indices = [bit_indices[qarg] for qarg in node.qargs]
if dag.has_calibration_for(node):
node.op.duration = dag.calibrations[node.op.name][
(tuple(indices), tuple(float(p) for p in node.op.params))
].duration

if node.op.duration is None:
op_duration = self._get_node_duration(node, bit_indices, dag)

# compute t0, t1: instruction interval, note that
# t0: start time of instruction
# t1: end time of instruction

# since this is alap scheduling, node is scheduled in reversed topological ordering
# and nodes are packed from the very end of the circuit.
# the physical meaning of t0 and t1 is flipped here.
if isinstance(node.op, self.CONDITIONAL_SUPPORTED):
t0q = max(idle_before[q] for q in node.qargs)
if node.op.condition_bits:
# conditional is bit tricky due to conditional_latency
t0c = max(idle_before[c] for c in node.op.condition_bits)
# Assume following case (t0c > t0q):
#
# |t0q
# Q ░░░░░░░░░░░░░▒▒▒
# C ░░░░░░░░▒▒▒▒▒▒▒▒
# |t0c
#
# In this case, there is no actual clbit read before gate.
#
# |t0q' = t0c - conditional_latency
# Q ░░░░░░░░▒▒▒░░▒▒▒
# C ░░░░░░▒▒▒▒▒▒▒▒▒▒
# |t1c' = t0c + conditional_latency
#
# rather than naively doing
#
# |t1q' = t0c + duration
# Q ░░░░░▒▒▒░░░░░▒▒▒
# C ░░▒▒░░░░▒▒▒▒▒▒▒▒
# |t1c' = t0c + duration + conditional_latency
#
t0 = max(t0q, t0c - op_duration)
t1 = t0 + op_duration
for clbit in node.op.condition_bits:
idle_before[clbit] = t1 + self.conditional_latency
else:
t0 = t0q
t1 = t0 + op_duration
else:
if node.op.condition_bits:
raise TranspilerError(
f"Duration of {node.op.name} on qubits {indices} is not found."
f"Conditional instruction {node.op.name} is not supported in ALAP scheduler."
)
if isinstance(node.op.duration, ParameterExpression):
indices = [bit_indices[qarg] for qarg in node.qargs]
raise TranspilerError(
f"Parameterized duration ({node.op.duration}) "
f"of {node.op.name} on qubits {indices} is not bounded."
)
# choose appropriate clbit available time depending on op
clbit_time_available = (
clbit_writeable if isinstance(node.op, Measure) else clbit_readable
)
# correction to change clbit start time to qubit start time
delta = 0 if isinstance(node.op, Measure) else node.op.duration
# must wait for op.condition_bits as well as node.cargs
start_time = max(
itertools.chain(
(qubit_time_available[q] for q in node.qargs),
(clbit_time_available[c] - delta for c in node.cargs + node.op.condition_bits),
)
)

pad_with_delays(node.qargs, until=start_time, unit=time_unit)

new_dag.apply_operation_front(node.op, node.qargs, node.cargs)
if isinstance(node.op, Measure):
# clbit time is always right (alap) justified
t0 = max(idle_before[bit] for bit in node.qargs + node.cargs)
t1 = t0 + op_duration
#
# |t1 = t0 + duration
# Q ░░░░░▒▒▒▒▒▒▒▒▒▒▒
# C ░░░░░░░░░▒▒▒▒▒▒▒
# |t0 + (duration - clbit_write_latency)
#
for clbit in node.cargs:
idle_before[clbit] = t0 + (op_duration - self.clbit_write_latency)
else:
# It happens to be directives such as barrier
t0 = max(idle_before[bit] for bit in node.qargs + node.cargs)
t1 = t0 + op_duration

for bit in node.qargs:
delta = t0 - idle_before[bit]
if delta > 0:
new_dag.apply_operation_front(Delay(delta, time_unit), [bit], [])
idle_before[bit] = t1

stop_time = start_time + node.op.duration
# update time table
for q in node.qargs:
qubit_time_available[q] = stop_time
for c in node.cargs: # measure
clbit_writeable[c] = clbit_readable[c] = start_time
for c in node.op.condition_bits: # conditional op
clbit_writeable[c] = max(stop_time, clbit_writeable[c])
new_dag.apply_operation_front(node.op, node.qargs, node.cargs)

working_qubits = qubit_time_available.keys()
circuit_duration = max(qubit_time_available[q] for q in working_qubits)
pad_with_delays(new_dag.qubits, until=circuit_duration, unit=time_unit)
circuit_duration = max(idle_before.values())
for bit, before in idle_before.items():
delta = circuit_duration - before
if not (delta > 0 and isinstance(bit, Qubit)):
continue
new_dag.apply_operation_front(Delay(delta, time_unit), [bit], [])

new_dag.name = dag.name
new_dag.metadata = dag.metadata
new_dag.calibrations = dag.calibrations

# set circuit duration and unit to indicate it is scheduled
new_dag.duration = circuit_duration
new_dag.unit = time_unit

return new_dag
Loading

0 comments on commit a6cb1a3

Please sign in to comment.