Skip to content

Commit

Permalink
Restore accidently deleted scheduling passes (#11184)
Browse files Browse the repository at this point in the history
In #10754 3 legacy scheduling passes were accidently deleted. These
passes were incorrectly identified as deprecated, however they were
never marked as deprecating just pending future deprecation. They were
intended to be be promoted from a pending deprecation to a full
deprecation in #8023 but we never took that step because there were
objections at the time as they still served a purpose. #10754 likely
missed this as the only indication in the deprecation decorator was a
kwarg that said `pending=True`, and this was the only indication that
these passes weren't actually deprecated yet. This commit restores these
passes on the 0.45.0 branch in the interest of unblocking the 0.45.0
release ASAP. We can handle forward porting this PR to main as needed
after the 0.45.0 release is tagged.

(cherry picked from commit aa272e9)
  • Loading branch information
mtreinish authored and mergify[bot] committed Nov 3, 2023
1 parent 02cb814 commit 5222c13
Show file tree
Hide file tree
Showing 10 changed files with 2,020 additions and 26 deletions.
8 changes: 8 additions & 0 deletions qiskit/transpiler/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@
ValidatePulseGates
InstructionDurationCheck
SetIOLatency
ALAPSchedule
ASAPSchedule
DynamicalDecoupling
AlignMeasures
Circuit Analysis
================
Expand Down Expand Up @@ -267,6 +271,10 @@
from .scheduling import ConstrainedReschedule
from .scheduling import InstructionDurationCheck
from .scheduling import SetIOLatency
from .scheduling import ALAPSchedule
from .scheduling import ASAPSchedule
from .scheduling import DynamicalDecoupling
from .scheduling import AlignMeasures

# additional utility passes
from .utils import CheckMap
Expand Down
4 changes: 4 additions & 0 deletions qiskit/transpiler/passes/scheduling/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

"""Module containing circuit scheduling passes."""

from .alap import ALAPSchedule
from .asap import ASAPSchedule
from .dynamical_decoupling import DynamicalDecoupling
from .scheduling import ALAPScheduleAnalysis, ASAPScheduleAnalysis, SetIOLatency
from .time_unit_conversion import TimeUnitConversion
from .padding import PadDelay, PadDynamicalDecoupling
Expand All @@ -21,3 +24,4 @@
from . import alignments as instruction_alignments

# TODO Deprecated pass. Will be removed after deprecation period.
from .alignments import AlignMeasures
155 changes: 155 additions & 0 deletions qiskit/transpiler/passes/scheduling/alap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""ALAP Scheduling."""

from qiskit.circuit import Delay, Qubit, Measure
from qiskit.dagcircuit import DAGCircuit
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.utils.deprecation import deprecate_func

from .base_scheduler import BaseSchedulerTransform


class ALAPSchedule(BaseSchedulerTransform):
"""ALAP Scheduling pass, which schedules the **stop** time of instructions as late as possible.
See :class:`~qiskit.transpiler.passes.scheduling.base_scheduler.BaseSchedulerTransform` for the
detailed behavior of the control flow operation, i.e. ``c_if``.
"""

@deprecate_func(
additional_msg=(
"Instead, use :class:`~.ALAPScheduleAnalysis`, which is an "
"analysis pass that requires a padding pass to later modify the circuit."
),
since="0.21.0",
pending=True,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def run(self, dag):
"""Run the ALAPSchedule pass on `dag`.
Args:
dag (DAGCircuit): DAG to schedule.
Returns:
DAGCircuit: A scheduled 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")

time_unit = self.property_set["time_unit"]
new_dag = DAGCircuit()
for qreg in dag.qregs.values():
new_dag.add_qreg(qreg)
for creg in dag.cregs.values():
new_dag.add_creg(creg)

idle_before = {q: 0 for q in dag.qubits + dag.clbits}
for node in reversed(list(dag.topological_op_nodes())):
op_duration = self._get_node_duration(node, 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"Conditional instruction {node.op.name} is not supported in ALAP scheduler."
)

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 and self._delay_supported(dag.find_bit(bit).index):
new_dag.apply_operation_front(Delay(delta, time_unit), [bit], [], check=False)
idle_before[bit] = t1

new_dag.apply_operation_front(node.op, node.qargs, node.cargs, check=False)

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
if self._delay_supported(dag.find_bit(bit).index):
new_dag.apply_operation_front(Delay(delta, time_unit), [bit], [], check=False)

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
1 change: 1 addition & 0 deletions qiskit/transpiler/passes/scheduling/alignments/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,4 @@
from .check_durations import InstructionDurationCheck
from .pulse_gate_validation import ValidatePulseGates
from .reschedule import ConstrainedReschedule
from .align_measures import AlignMeasures
Loading

0 comments on commit 5222c13

Please sign in to comment.