From d86e708f11db2cc2df94a80a27f7c4b0e70f65ad Mon Sep 17 00:00:00 2001 From: Raghav <83136390+Raghav-Bell@users.noreply.github.com> Date: Wed, 18 Oct 2023 17:57:04 +0530 Subject: [PATCH] Removed code deprecated in qiskit-terra 0.21, released on June 2022 (#10754) * Removed code and tests deprecated in 0.21 * Undone removal of optimizers/spsa. * undo changes in spsa * Added release note. * Removed deprecated classes from docstrings. * Improved release note. * Added removed files to resolve conflict. * Removed the conflicting files * Added conflicting file but with no code * Align measure class file * Removed alap dynamical coupling class * Resolving colflict with dynamical coupling * Deleted dynamical coupling * Fixed release note. * Apply suggestions from code review * Update qiskit/execute_function.py * recover benchmark test --------- Co-authored-by: Luciano Bello Co-authored-by: Luciano Bello --- qiskit/execute_function.py | 17 - qiskit/pulse/transforms/alignments.py | 25 +- qiskit/qpy/interface.py | 2 - qiskit/transpiler/passes/__init__.py | 12 - .../transpiler/passes/scheduling/__init__.py | 4 - qiskit/transpiler/passes/scheduling/alap.py | 155 ---- .../passes/scheduling/alignments/__init__.py | 1 - .../scheduling/alignments/align_measures.py | 256 ------ qiskit/transpiler/passes/scheduling/asap.py | 177 ---- .../passes/scheduling/dynamical_decoupling.py | 293 ------- qiskit/transpiler/passes/utils/__init__.py | 2 - .../passes/utils/check_cx_direction.py | 27 - .../transpiler/passes/utils/cx_direction.py | 27 - ...oved_deprecated_0.21-6c93f7bbc50ae40e.yaml | 44 + test/benchmarks/scheduling_passes.py | 45 +- .../test_instruction_alignments.py | 310 +------ .../legacy_scheduling/test_scheduling_pass.py | 811 ------------------ .../transpiler/test_instruction_alignments.py | 503 +---------- 18 files changed, 79 insertions(+), 2632 deletions(-) delete mode 100644 qiskit/transpiler/passes/scheduling/alap.py delete mode 100644 qiskit/transpiler/passes/scheduling/alignments/align_measures.py delete mode 100644 qiskit/transpiler/passes/scheduling/asap.py delete mode 100644 qiskit/transpiler/passes/scheduling/dynamical_decoupling.py delete mode 100644 qiskit/transpiler/passes/utils/check_cx_direction.py delete mode 100644 qiskit/transpiler/passes/utils/cx_direction.py create mode 100644 releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml delete mode 100644 test/python/transpiler/legacy_scheduling/test_scheduling_pass.py diff --git a/qiskit/execute_function.py b/qiskit/execute_function.py index 0706b106bd..c50b6dcf2a 100644 --- a/qiskit/execute_function.py +++ b/qiskit/execute_function.py @@ -26,7 +26,6 @@ from qiskit.compiler import transpile, schedule from qiskit.providers.backend import Backend from qiskit.pulse import Schedule, ScheduleBlock from qiskit.exceptions import QiskitError -from qiskit.utils.deprecation import deprecate_arg logger = logging.getLogger(__name__) @@ -36,8 +35,6 @@ def _log_submission_time(start_time, end_time): logger.info(log_msg) -@deprecate_arg("qobj_id", since="0.21.0", additional_msg="This argument has no effect anymore.") -@deprecate_arg("qobj_header", since="0.21.0", additional_msg="This argument has no effect anymore.") def execute( experiments, backend, @@ -48,8 +45,6 @@ def execute( seed_transpiler=None, optimization_level=None, pass_manager=None, - qobj_id=None, - qobj_header=None, shots=None, # common run options memory=None, seed_simulator=None, @@ -159,16 +154,6 @@ def execute( arg is present, auto-selection of pass manager based on the transpile options will be turned off and this pass manager will be used directly. - qobj_id (str): DEPRECATED: String identifier to annotate the Qobj. This has no effect - and the :attr:`~.QuantumCircuit.name` attribute of the input circuit(s) should be used - instead. - - qobj_header (QobjHeader or dict): DEPRECATED: User input that will be inserted in Qobj - header, and will also be copied to the corresponding :class:`qiskit.result.Result` - header. Headers do not affect the run. Headers do not affect the run. This kwarg - has no effect anymore and the :attr:`~.QuantumCircuit.metadata` attribute of the - input circuit(s) should be used instead. - shots (int): Number of repetitions of each circuit, for sampling. Default: 1024 memory (bool): If True, per-shot measurement bitstrings are returned as well @@ -279,8 +264,6 @@ def execute( job = execute(qc, backend, shots=4321) """ - del qobj_id - del qobj_header if isinstance(experiments, (Schedule, ScheduleBlock)) or ( isinstance(experiments, list) and isinstance(experiments[0], (Schedule, ScheduleBlock)) ): diff --git a/qiskit/pulse/transforms/alignments.py b/qiskit/pulse/transforms/alignments.py index d19def4f34..d5a1f62e8d 100644 --- a/qiskit/pulse/transforms/alignments.py +++ b/qiskit/pulse/transforms/alignments.py @@ -12,7 +12,7 @@ """A collection of passes to reallocate the timeslots of instructions according to context.""" import abc -from typing import Callable, Dict, Any, Union, Tuple +from typing import Callable, Union, Tuple import numpy as np @@ -20,7 +20,6 @@ from qiskit.circuit.parameterexpression import ParameterExpression, ParameterVal from qiskit.pulse.exceptions import PulseError from qiskit.pulse.schedule import Schedule, ScheduleComponent from qiskit.pulse.utils import instruction_duration_validation -from qiskit.utils.deprecation import deprecate_func class AlignmentKind(abc.ABC): @@ -45,11 +44,6 @@ class AlignmentKind(abc.ABC): """ pass - @deprecate_func(since="0.21") - def to_dict(self) -> Dict[str, Any]: - """Returns dictionary to represent this alignment.""" - return {"alignment": self.__class__.__name__} - @property @abc.abstractmethod def is_sequential(self) -> bool: @@ -330,11 +324,6 @@ class AlignEquispaced(AlignmentKind): return aligned - @deprecate_func(since="0.21") - def to_dict(self) -> Dict[str, Any]: - """Returns dictionary to represent this alignment.""" - return {"alignment": self.__class__.__name__, "duration": self.duration} - class AlignFunc(AlignmentKind): """Allocate instructions at position specified by callback function. @@ -415,15 +404,3 @@ class AlignFunc(AlignmentKind): aligned.insert(_t0, child, inplace=True) return aligned - - @deprecate_func(since="0.21") - def to_dict(self) -> Dict[str, Any]: - """Returns dictionary to represent this alignment. - - .. note:: ``func`` is not presented in this dictionary. Just name. - """ - return { - "alignment": self.__class__.__name__, - "duration": self.duration, - "func": self.func.__name__, - } diff --git a/qiskit/qpy/interface.py b/qiskit/qpy/interface.py index c50bce14c6..9886160dac 100644 --- a/qiskit/qpy/interface.py +++ b/qiskit/qpy/interface.py @@ -25,7 +25,6 @@ from qiskit.exceptions import QiskitError from qiskit.qpy import formats, common, binary_io, type_keys from qiskit.qpy.exceptions import QpyError from qiskit.version import __version__ -from qiskit.utils.deprecation import deprecate_arg # pylint: disable=invalid-name @@ -72,7 +71,6 @@ VERSION_PATTERN = ( VERSION_PATTERN_REGEX = re.compile(VERSION_PATTERN, re.VERBOSE | re.IGNORECASE) -@deprecate_arg("circuits", new_alias="programs", since="0.21.0") def dump( programs: Union[List[QPY_SUPPORTED_TYPES], QPY_SUPPORTED_TYPES], file_obj: BinaryIO, diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 4e13d4d3a9..f461f33537 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -114,13 +114,9 @@ Scheduling PadDynamicalDecoupling PadDelay ConstrainedReschedule - AlignMeasures ValidatePulseGates InstructionDurationCheck SetIOLatency - ALAPSchedule - ASAPSchedule - DynamicalDecoupling Circuit Analysis ================ @@ -164,9 +160,7 @@ Additional Passes :toctree: ../stubs/ CheckMap - CheckCXDirection CheckGateDirection - CXDirection GateDirection MergeAdjacentBarriers RemoveBarriers @@ -267,11 +261,7 @@ from .calibration import RXCalibrationBuilder from .scheduling import TimeUnitConversion from .scheduling import ALAPScheduleAnalysis from .scheduling import ASAPScheduleAnalysis -from .scheduling import ALAPSchedule -from .scheduling import ASAPSchedule from .scheduling import PadDynamicalDecoupling -from .scheduling import DynamicalDecoupling -from .scheduling import AlignMeasures # Deprecated from .scheduling import ValidatePulseGates from .scheduling import PadDelay from .scheduling import ConstrainedReschedule @@ -280,8 +270,6 @@ from .scheduling import SetIOLatency # additional utility passes from .utils import CheckMap -from .utils import CheckCXDirection # Deprecated -from .utils import CXDirection # Deprecated from .utils import CheckGateDirection from .utils import GateDirection from .utils import BarrierBeforeFinalMeasurements diff --git a/qiskit/transpiler/passes/scheduling/__init__.py b/qiskit/transpiler/passes/scheduling/__init__.py index 6283faff00..69485e88e4 100644 --- a/qiskit/transpiler/passes/scheduling/__init__.py +++ b/qiskit/transpiler/passes/scheduling/__init__.py @@ -12,9 +12,6 @@ """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 @@ -24,4 +21,3 @@ from .alignments import InstructionDurationCheck, ValidatePulseGates, Constraine from . import alignments as instruction_alignments # TODO Deprecated pass. Will be removed after deprecation period. -from .alignments import AlignMeasures diff --git a/qiskit/transpiler/passes/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/alap.py deleted file mode 100644 index 9ee0f4988b..0000000000 --- a/qiskit/transpiler/passes/scheduling/alap.py +++ /dev/null @@ -1,155 +0,0 @@ -# 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 diff --git a/qiskit/transpiler/passes/scheduling/alignments/__init__.py b/qiskit/transpiler/passes/scheduling/alignments/__init__.py index 513144937a..a25ec01bc1 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/__init__.py +++ b/qiskit/transpiler/passes/scheduling/alignments/__init__.py @@ -78,4 +78,3 @@ Minimum pulse length constraint from .check_durations import InstructionDurationCheck from .pulse_gate_validation import ValidatePulseGates from .reschedule import ConstrainedReschedule -from .align_measures import AlignMeasures diff --git a/qiskit/transpiler/passes/scheduling/alignments/align_measures.py b/qiskit/transpiler/passes/scheduling/alignments/align_measures.py deleted file mode 100644 index 668d65f6ab..0000000000 --- a/qiskit/transpiler/passes/scheduling/alignments/align_measures.py +++ /dev/null @@ -1,256 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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. - -"""Align measurement instructions.""" -from __future__ import annotations -import itertools -import warnings -from collections import defaultdict -from collections.abc import Iterable -from typing import Type - -from qiskit.circuit.quantumcircuit import ClbitSpecifier, QubitSpecifier - -from qiskit.circuit.delay import Delay -from qiskit.circuit.measure import Measure -from qiskit.circuit.parameterexpression import ParameterExpression -from qiskit.dagcircuit import DAGCircuit -from qiskit.transpiler.basepasses import TransformationPass -from qiskit.transpiler.exceptions import TranspilerError -from qiskit.utils.deprecation import deprecate_func - - -class AlignMeasures(TransformationPass): - """Measurement alignment. - - This is a control electronics aware optimization pass. - - In many quantum computing architectures gates (instructions) are implemented with - shaped analog stimulus signals. These signals are digitally stored in the - waveform memory of the control electronics and converted into analog voltage signals - by electronic components called digital to analog converters (DAC). - - In a typical hardware implementation of superconducting quantum processors, - a single qubit instruction is implemented by a - microwave signal with the duration of around several tens of ns with a per-sample - time resolution of ~0.1-10ns, as reported by ``backend.configuration().dt``. - In such systems requiring higher DAC bandwidth, control electronics often - defines a `pulse granularity`, in other words a data chunk, to allow the DAC to - perform the signal conversion in parallel to gain the bandwidth. - - Measurement alignment is required if a backend only allows triggering ``measure`` - instructions at a certain multiple value of this pulse granularity. - This value is usually provided by ``backend.configuration().timing_constraints``. - - In Qiskit SDK, the duration of delay can take arbitrary value in units of ``dt``, - thus circuits involving delays may violate the above alignment constraint (i.e. misalignment). - This pass shifts measurement instructions to a new time position to fix the misalignment, - by inserting extra delay right before the measure instructions. - The input of this pass should be scheduled :class:`~qiskit.dagcircuit.DAGCircuit`, - thus one should select one of the scheduling passes - (:class:`~qiskit.transpiler.passes.ALAPSchedule` or - :class:`~qiskit.trasnpiler.passes.ASAPSchedule`) before calling this. - - Examples: - We assume executing the following circuit on a backend with ``alignment=16``. - - .. parsed-literal:: - - ┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├ - └───┘└────────────────┘└╥┘ - c: 1/════════════════════════╩═ - 0 - - Note that delay of 100 dt induces a misalignment of 4 dt at the measurement. - This pass appends an extra 12 dt time shift to the input circuit. - - .. parsed-literal:: - - ┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(112[dt]) ├┤M├ - └───┘└────────────────┘└╥┘ - c: 1/════════════════════════╩═ - 0 - - This pass always inserts a positive delay before measurements - rather than reducing other delays. - - Notes: - The Backend may allow users to execute circuits violating the alignment constraint. - However, it may return meaningless measurement data mainly due to the phase error. - """ - - @deprecate_func( - additional_msg=( - "Instead, use :class:`~.ConstrainedReschedule`, which performs the same function " - "but also supports aligning to additional timing constraints." - ), - since="0.21.0", - pending=True, - ) - def __init__(self, alignment: int = 1): - """Create new pass. - - Args: - alignment: Integer number representing the minimum time resolution to - trigger measure instruction in units of ``dt``. This value depends on - the control electronics of your quantum processor. - """ - super().__init__() - self.alignment = alignment - - def run(self, dag: DAGCircuit): - """Run the measurement alignment pass on `dag`. - - Args: - dag (DAGCircuit): DAG to be checked. - - Returns: - DAGCircuit: DAG with consistent timing and op nodes annotated with duration. - - Raises: - TranspilerError: If circuit is not scheduled. - """ - time_unit = self.property_set["time_unit"] - - if not _check_alignment_required(dag, self.alignment, Measure): - # return input as-is to avoid unnecessary scheduling. - # because following procedure regenerate new DAGCircuit, - # we should avoid continuing if not necessary from performance viewpoint. - return dag - - # if circuit is not yet scheduled, schedule with ALAP method - if dag.duration is None: - raise TranspilerError( - f"This circuit {dag.name} may involve a delay instruction violating the " - "pulse controller alignment. To adjust instructions to " - "right timing, you should call one of scheduling passes first. " - "This is usually done by calling transpiler with scheduling_method='alap'." - ) - - # the following lines are basically copied from ASAPSchedule pass - # - # * some validations for non-scheduled nodes are dropped, since we assume scheduled input - # * pad_with_delay is called only with non-delay node to avoid consecutive delay - new_dag = dag.copy_empty_like() - - qubit_time_available: dict[QubitSpecifier, int] = defaultdict(int) # to track op start time - qubit_stop_times: dict[QubitSpecifier, int] = defaultdict( - int - ) # to track delay start time for padding - clbit_readable: dict[ClbitSpecifier, int] = defaultdict(int) - clbit_writeable: dict[ClbitSpecifier, int] = defaultdict(int) - - def pad_with_delays(qubits: Iterable[QubitSpecifier], until, unit) -> None: - """Pad idle time-slots in ``qubits`` with delays in ``unit`` until ``until``.""" - for q in qubits: - if qubit_stop_times[q] < until: - idle_duration = until - qubit_stop_times[q] - new_dag.apply_operation_back(Delay(idle_duration, unit), (q,), check=False) - - for node in dag.topological_op_nodes(): - # 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 = node.op.duration if isinstance(node.op, Measure) else 0 - start_time = max( - itertools.chain( - (qubit_time_available[q] for q in node.qargs), - ( - clbit_time_available[c] - delta - for c in node.cargs + tuple(node.op.condition_bits) - ), - ) - ) - - if isinstance(node.op, Measure): - if start_time % self.alignment != 0: - start_time = ((start_time // self.alignment) + 1) * self.alignment - - if not isinstance(node.op, Delay): # exclude delays for combining consecutive delays - pad_with_delays(node.qargs, until=start_time, unit=time_unit) - new_dag.apply_operation_back(node.op, node.qargs, node.cargs, check=False) - - stop_time = start_time + node.op.duration - # update time table - for q in node.qargs: - qubit_time_available[q] = stop_time - if not isinstance(node.op, Delay): - qubit_stop_times[q] = stop_time - for c in node.cargs: # measure - clbit_writeable[c] = clbit_readable[c] = stop_time - for c in node.op.condition_bits: # conditional op - clbit_writeable[c] = max(start_time, clbit_writeable[c]) - - 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) - - new_dag.name = dag.name - new_dag.metadata = dag.metadata - - # set circuit duration and unit to indicate it is scheduled - new_dag.duration = circuit_duration - new_dag.unit = time_unit - - return new_dag - - -def _check_alignment_required( - dag: DAGCircuit, - alignment: int, - instructions: Type | list[Type], -) -> bool: - """Check DAG nodes and return a boolean representing if instruction scheduling is necessary. - - Args: - dag: DAG circuit to check. - alignment: Instruction alignment condition. - instructions: Target instructions. - - Returns: - If instruction scheduling is necessary. - """ - if not isinstance(instructions, list): - instructions = [instructions] - - if alignment == 1: - # disable alignment if arbitrary t0 value can be used - return False - - if all(len(dag.op_nodes(inst)) == 0 for inst in instructions): - # disable alignment if target instruction is not involved - return False - - # check delay durations - for delay_node in dag.op_nodes(Delay): - duration = delay_node.op.duration - if isinstance(duration, ParameterExpression): - # duration is parametrized: - # raise user warning if backend alignment is not 1. - warnings.warn( - f"Parametrized delay with {repr(duration)} is found in circuit {dag.name}. " - f"This backend requires alignment={alignment}. " - "Please make sure all assigned values are multiple values of the alignment.", - UserWarning, - ) - else: - # duration is bound: - # check duration and trigger alignment if it violates constraint - if duration % alignment != 0: - return True - - # disable alignment if all delays are multiple values of the alignment - return False diff --git a/qiskit/transpiler/passes/scheduling/asap.py b/qiskit/transpiler/passes/scheduling/asap.py deleted file mode 100644 index cebc32af71..0000000000 --- a/qiskit/transpiler/passes/scheduling/asap.py +++ /dev/null @@ -1,177 +0,0 @@ -# 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. - -"""ASAP 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 ASAPSchedule(BaseSchedulerTransform): - """ASAP Scheduling pass, which schedules the start time of instructions as early as possible.. - - See :class:`~qiskit.transpiler.passes.scheduling.base_scheduler.BaseSchedulerTransform` for the - detailed behavior of the control flow operation, i.e. ``c_if``. - - .. note:: - - This base class has been superseded by :class:`~.ASAPScheduleAnalysis` and - the new scheduling workflow. It will be deprecated and subsequently - removed in a future release. - """ - - @deprecate_func( - additional_msg=( - "Instead, use :class:`~.ASAPScheduleAnalysis`, 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 ASAPSchedule 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("ASAP 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_after = {q: 0 for q in dag.qubits + dag.clbits} - for node in 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 - if isinstance(node.op, self.CONDITIONAL_SUPPORTED): - t0q = max(idle_after[q] for q in node.qargs) - if node.op.condition_bits: - # conditional is bit tricky due to conditional_latency - t0c = max(idle_after[bit] for bit in node.op.condition_bits) - if t0q > t0c: - # This is situation something like below - # - # |t0q - # Q ▒▒▒▒▒▒▒▒▒░░ - # C ▒▒▒░░░░░░░░ - # |t0c - # - # In this case, you can insert readout access before tq0 - # - # |t0q - # Q ▒▒▒▒▒▒▒▒▒▒▒ - # C ▒▒▒░░░▒▒░░░ - # |t0q - conditional_latency - # - t0c = max(t0q - self.conditional_latency, t0c) - t1c = t0c + self.conditional_latency - for bit in node.op.condition_bits: - # Lock clbit until state is read - idle_after[bit] = t1c - # It starts after register read access - t0 = max(t0q, t1c) - 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 ASAP scheduler." - ) - - if isinstance(node.op, Measure): - # measure instruction handling is bit tricky due to clbit_write_latency - t0q = max(idle_after[q] for q in node.qargs) - t0c = max(idle_after[c] for c in node.cargs) - # Assume following case (t0c > t0q) - # - # |t0q - # Q ▒▒▒▒░░░░░░░░░░░░ - # C ▒▒▒▒▒▒▒▒░░░░░░░░ - # |t0c - # - # In this case, there is no actual clbit access until clbit_write_latency. - # The node t0 can be push backward by this amount. - # - # |t0q' = t0c - clbit_write_latency - # Q ▒▒▒▒░░▒▒▒▒▒▒▒▒▒▒ - # C ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - # |t0c' = t0c - # - # rather than naively doing - # - # |t0q' = t0c - # Q ▒▒▒▒░░░░▒▒▒▒▒▒▒▒ - # C ▒▒▒▒▒▒▒▒░░░▒▒▒▒▒ - # |t0c' = t0c + clbit_write_latency - # - t0 = max(t0q, t0c - self.clbit_write_latency) - t1 = t0 + op_duration - for clbit in node.cargs: - idle_after[clbit] = t1 - else: - # It happens to be directives such as barrier - t0 = max(idle_after[bit] for bit in node.qargs + node.cargs) - t1 = t0 + op_duration - - # Add delay to qubit wire - for bit in node.qargs: - delta = t0 - idle_after[bit] - if ( - delta > 0 - and isinstance(bit, Qubit) - and self._delay_supported(dag.find_bit(bit).index) - ): - new_dag.apply_operation_back(Delay(delta, time_unit), [bit], []) - idle_after[bit] = t1 - - new_dag.apply_operation_back(node.op, node.qargs, node.cargs) - - circuit_duration = max(idle_after.values()) - for bit, after in idle_after.items(): - delta = circuit_duration - after - if not (delta > 0 and isinstance(bit, Qubit)): - continue - if self._delay_supported(dag.find_bit(bit).index): - new_dag.apply_operation_back(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 diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py deleted file mode 100644 index 87bbdbff91..0000000000 --- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +++ /dev/null @@ -1,293 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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. - -"""Dynamical Decoupling insertion pass.""" - -import itertools - -import numpy as np -from qiskit.circuit import Gate, Delay, Reset -from qiskit.circuit.library.standard_gates import IGate, UGate, U3Gate -from qiskit.dagcircuit import DAGOpNode, DAGInNode -from qiskit.quantum_info.operators.predicates import matrix_equal -from qiskit.quantum_info.synthesis import OneQubitEulerDecomposer -from qiskit.transpiler.passes.optimization import Optimize1qGates -from qiskit.transpiler.basepasses import TransformationPass -from qiskit.transpiler.exceptions import TranspilerError -from qiskit.utils.deprecation import deprecate_func - - -class DynamicalDecoupling(TransformationPass): - """Dynamical decoupling insertion pass. - - This pass works on a scheduled, physical circuit. It scans the circuit for - idle periods of time (i.e. those containing delay instructions) and inserts - a DD sequence of gates in those spots. These gates amount to the identity, - so do not alter the logical action of the circuit, but have the effect of - mitigating decoherence in those idle periods. - - As a special case, the pass allows a length-1 sequence (e.g. [XGate()]). - In this case the DD insertion happens only when the gate inverse can be - absorbed into a neighboring gate in the circuit (so we would still be - replacing Delay with something that is equivalent to the identity). - This can be used, for instance, as a Hahn echo. - - This pass ensures that the inserted sequence preserves the circuit exactly - (including global phase). - - .. plot:: - :include-source: - - import numpy as np - from qiskit.circuit import QuantumCircuit - from qiskit.circuit.library import XGate - from qiskit.transpiler import PassManager, InstructionDurations - from qiskit.transpiler.passes import ALAPSchedule, DynamicalDecoupling - from qiskit.visualization import timeline_drawer - - # Because the legacy passes do not propagate the scheduling information correctly, it is - # necessary to run a no-op "re-schedule" before the output circuits can be drawn. - def draw(circuit): - from qiskit import transpile - - scheduled = transpile( - circuit, - optimization_level=0, - instruction_durations=InstructionDurations(), - scheduling_method="alap", - ) - return timeline_drawer(scheduled) - - circ = QuantumCircuit(4) - circ.h(0) - circ.cx(0, 1) - circ.cx(1, 2) - circ.cx(2, 3) - circ.measure_all() - durations = InstructionDurations( - [("h", 0, 50), ("cx", [0, 1], 700), ("reset", None, 10), - ("cx", [1, 2], 200), ("cx", [2, 3], 300), - ("x", None, 50), ("measure", None, 1000)] - ) - # balanced X-X sequence on all qubits - dd_sequence = [XGate(), XGate()] - pm = PassManager([ALAPSchedule(durations), - DynamicalDecoupling(durations, dd_sequence)]) - circ_dd = pm.run(circ) - draw(circ_dd) - - # Uhrig sequence on qubit 0 - n = 8 - dd_sequence = [XGate()] * n - def uhrig_pulse_location(k): - return np.sin(np.pi * (k + 1) / (2 * n + 2)) ** 2 - spacing = [] - for k in range(n): - spacing.append(uhrig_pulse_location(k) - sum(spacing)) - spacing.append(1 - sum(spacing)) - pm = PassManager( - [ - ALAPSchedule(durations), - DynamicalDecoupling(durations, dd_sequence, qubits=[0], spacing=spacing), - ] - ) - circ_dd = pm.run(circ) - draw(circ_dd) - """ - - @deprecate_func( - additional_msg=( - "Instead, use :class:`~.PadDynamicalDecoupling`, which performs the same " - "function but requires scheduling and alignment analysis passes to run prior to it." - ), - since="0.21.0", - pending=True, - ) - def __init__( - self, durations, dd_sequence, qubits=None, spacing=None, skip_reset_qubits=True, target=None - ): - """Dynamical decoupling initializer. - - Args: - durations (InstructionDurations): Durations of instructions to be - used in scheduling. - dd_sequence (list[Gate]): sequence of gates to apply in idle spots. - qubits (list[int]): physical qubits on which to apply DD. - If None, all qubits will undergo DD (when possible). - spacing (list[float]): a list of spacings between the DD gates. - The available slack will be divided according to this. - The list length must be one more than the length of dd_sequence, - and the elements must sum to 1. If None, a balanced spacing - will be used [d/2, d, d, ..., d, d, d/2]. - skip_reset_qubits (bool): if True, does not insert DD on idle - periods that immediately follow initialized/reset qubits (as - qubits in the ground state are less susceptile to decoherence). - target (Target): The :class:`~.Target` representing the target backend, if both - ``durations`` and this are specified then this argument will take - precedence and ``durations`` will be ignored. - """ - super().__init__() - self._durations = durations - self._dd_sequence = dd_sequence - self._qubits = qubits - self._spacing = spacing - self._skip_reset_qubits = skip_reset_qubits - self._target = target - if target is not None: - self._durations = target.durations() - for gate in dd_sequence: - if gate.name not in target.operation_names: - raise TranspilerError( - f"{gate.name} in dd_sequence is not supported in the target" - ) - - def run(self, dag): - """Run the DynamicalDecoupling pass on dag. - - Args: - dag (DAGCircuit): a scheduled DAG. - - Returns: - DAGCircuit: equivalent circuit with delays interrupted by DD, - where possible. - - Raises: - TranspilerError: if the circuit is not mapped on physical qubits. - """ - if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: - raise TranspilerError("DD runs on physical circuits only.") - - if dag.duration is None: - raise TranspilerError("DD runs after circuit is scheduled.") - - num_pulses = len(self._dd_sequence) - sequence_gphase = 0 - if num_pulses != 1: - if num_pulses % 2 != 0: - raise TranspilerError("DD sequence must contain an even number of gates (or 1).") - noop = np.eye(2) - for gate in self._dd_sequence: - noop = noop.dot(gate.to_matrix()) - if not matrix_equal(noop, IGate().to_matrix(), ignore_phase=True): - raise TranspilerError("The DD sequence does not make an identity operation.") - sequence_gphase = np.angle(noop[0][0]) - - if self._qubits is None: - self._qubits = set(range(dag.num_qubits())) - else: - self._qubits = set(self._qubits) - - if self._spacing: - if sum(self._spacing) != 1 or any(a < 0 for a in self._spacing): - raise TranspilerError( - "The spacings must be given in terms of fractions " - "of the slack period and sum to 1." - ) - else: # default to balanced spacing - mid = 1 / num_pulses - end = mid / 2 - self._spacing = [end] + [mid] * (num_pulses - 1) + [end] - - for qarg in list(self._qubits): - for gate in self._dd_sequence: - if not self.__gate_supported(gate, qarg): - self._qubits.discard(qarg) - break - - index_sequence_duration_map = {} - for physical_qubit in self._qubits: - dd_sequence_duration = 0 - for index, gate in enumerate(self._dd_sequence): - gate = gate.to_mutable() - self._dd_sequence[index] = gate - gate.duration = self._durations.get(gate, physical_qubit) - - dd_sequence_duration += gate.duration - index_sequence_duration_map[physical_qubit] = dd_sequence_duration - - new_dag = dag.copy_empty_like() - - for nd in dag.topological_op_nodes(): - if not isinstance(nd.op, Delay): - new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) - continue - - dag_qubit = nd.qargs[0] - physical_qubit = dag.find_bit(dag_qubit).index - if physical_qubit not in self._qubits: # skip unwanted qubits - new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) - continue - - pred = next(dag.predecessors(nd)) - succ = next(dag.successors(nd)) - if self._skip_reset_qubits: # discount initial delays - if isinstance(pred, DAGInNode) or isinstance(pred.op, Reset): - new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) - continue - - dd_sequence_duration = index_sequence_duration_map[physical_qubit] - slack = nd.op.duration - dd_sequence_duration - if slack <= 0: # dd doesn't fit - new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) - continue - - if num_pulses == 1: # special case of using a single gate for DD - u_inv = self._dd_sequence[0].inverse().to_matrix() - theta, phi, lam, phase = OneQubitEulerDecomposer().angles_and_phase(u_inv) - # absorb the inverse into the successor (from left in circuit) - if isinstance(succ, DAGOpNode) and isinstance(succ.op, (UGate, U3Gate)): - theta_r, phi_r, lam_r = succ.op.params - succ.op.params = Optimize1qGates.compose_u3( - theta_r, phi_r, lam_r, theta, phi, lam - ) - sequence_gphase += phase - # absorb the inverse into the predecessor (from right in circuit) - elif isinstance(pred, DAGOpNode) and isinstance(pred.op, (UGate, U3Gate)): - theta_l, phi_l, lam_l = pred.op.params - pred.op.params = Optimize1qGates.compose_u3( - theta, phi, lam, theta_l, phi_l, lam_l - ) - sequence_gphase += phase - # don't do anything if there's no single-qubit gate to absorb the inverse - else: - new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) - continue - - # insert the actual DD sequence - taus = [int(slack * a) for a in self._spacing] - unused_slack = slack - sum(taus) # unused, due to rounding to int multiples of dt - middle_index = int((len(taus) - 1) / 2) # arbitrary: redistribute to middle - taus[middle_index] += unused_slack # now we add up to original delay duration - - for tau, gate in itertools.zip_longest(taus, self._dd_sequence): - if tau > 0: - new_dag.apply_operation_back(Delay(tau), [dag_qubit], check=False) - if gate is not None: - new_dag.apply_operation_back(gate, [dag_qubit], check=False) - - new_dag.global_phase = _mod_2pi(new_dag.global_phase + sequence_gphase) - - return new_dag - - def __gate_supported(self, gate: Gate, qarg: int) -> bool: - """A gate is supported on the qubit (qarg) or not.""" - if self._target is None or self._target.instruction_supported(gate.name, qargs=(qarg,)): - return True - return False - - -def _mod_2pi(angle: float, atol: float = 0): - """Wrap angle into interval [-π,π). If within atol of the endpoint, clamp to -π""" - wrapped = (angle + np.pi) % (2 * np.pi) - np.pi - if abs(wrapped - np.pi) < atol: - wrapped = -np.pi - return wrapped diff --git a/qiskit/transpiler/passes/utils/__init__.py b/qiskit/transpiler/passes/utils/__init__.py index c974d292fb..c5d605f7d7 100644 --- a/qiskit/transpiler/passes/utils/__init__.py +++ b/qiskit/transpiler/passes/utils/__init__.py @@ -13,8 +13,6 @@ """Utility passes and functions used for other main passes.""" from .check_map import CheckMap -from .check_cx_direction import CheckCXDirection # Deprecated -from .cx_direction import CXDirection # Deprecated from .check_gate_direction import CheckGateDirection from .gate_direction import GateDirection from .barrier_before_final_measurements import BarrierBeforeFinalMeasurements diff --git a/qiskit/transpiler/passes/utils/check_cx_direction.py b/qiskit/transpiler/passes/utils/check_cx_direction.py deleted file mode 100644 index 2726d2e26e..0000000000 --- a/qiskit/transpiler/passes/utils/check_cx_direction.py +++ /dev/null @@ -1,27 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# 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. - -"""Check if the CNOTs follow the right direction with respect to the coupling map..""" - -from qiskit.transpiler.passes.utils.check_gate_direction import CheckGateDirection -from qiskit.utils.deprecation import deprecate_func - - -class CheckCXDirection(CheckGateDirection): - """Deprecated: use :class:`qiskit.transpiler.passes.CheckGateDirection` pass instead.""" - - @deprecate_func( - additional_msg="Instead, use the more generic :class:`~.CheckGateDirection` pass.", - since="0.21.0", - ) - def __init__(self, coupling_map=None, target=None): - super().__init__(coupling_map=coupling_map, target=target) diff --git a/qiskit/transpiler/passes/utils/cx_direction.py b/qiskit/transpiler/passes/utils/cx_direction.py deleted file mode 100644 index e93bf0d69e..0000000000 --- a/qiskit/transpiler/passes/utils/cx_direction.py +++ /dev/null @@ -1,27 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2018. -# -# 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. - -"""Rearrange the direction of the cx nodes to match the directed coupling map.""" - -from qiskit.transpiler.passes.utils.gate_direction import GateDirection -from qiskit.utils.deprecation import deprecate_func - - -class CXDirection(GateDirection): - """Deprecated: use :class:`qiskit.transpiler.passes.GateDirection` pass instead.""" - - @deprecate_func( - additional_msg="Instead, use the more generic :class:`~.GateDirection` pass.", - since="0.21.0", - ) - def __init__(self, coupling_map): - super().__init__(coupling_map) diff --git a/releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml b/releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml new file mode 100644 index 0000000000..5f609ad91b --- /dev/null +++ b/releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml @@ -0,0 +1,44 @@ +--- +upgrade: + - | + The function :func:`~qiskit.execute_function.execute` + does not accept the arguments `qobj_id` and `qobj_header` any more. + Their use was deprecated in Qiskit 0.37 (with Terra 0.21), released on June 2022. + - | + The transpilation pass ``qiskit.transpiler.passes.ALAPSchedule`` is removed. It use was deprecated + in Qiskit 0.37 (with Terra 0.21), released on June 2022 and replaced by + :class:`~.transpiler.passes.ALAPScheduleAnalysis`, which is an + analysis pass. + - | + The transpilation pass ``qiskit.transpiler.passes.ASAPSchedule`` is removed. It use was deprecated + in Qiskit 0.37 (with Terra 0.21), released on June 2022. It has been superseded by + :class:`~.ASAPScheduleAnalysis` and the new scheduling workflow. + - | + The transpilation pass ``qiskit.transpiler.passes.DynamicalDecoupling`` is removed. It use was deprecated + in Qiskit 0.37 (with Terra 0.21), released on June 2022. + Instead, use :class:`~.transpiler.passes.PadDynamicalDecoupling`, which performs the same + function but requires scheduling and alignment analysis passes to run prior to it. + - | + The transpilation pass ``qiskit.transpiler.passes.AlignMeasures`` is removed. It use was deprecated + in Qiskit 0.37 (with Terra 0.21), released on June 2022. + Instead, use :class:`~.ConstrainedReschedule`, which performs the same function + and also supports aligning to additional timing constraints. + - | + The transpilation pass ``qiskit.transpiler.passes.CXDirection`` is removed. It use was deprecated + in Qiskit 0.37 (with Terra 0.21), released on June 2022. + Instead, use the more generic :class:`~.GateDirection` pass. + - | + The transpilation pass ``qiskit.transpiler.passes.CheckCXDirection`` is removed. It use was deprecated + in Qiskit 0.37 (with Terra 0.21), released on June 2022. + Instead, use the more generic :class:`~.CheckGateDirection` pass. + - | + The methods ``to_dict`` in the classes :class:`.pulse.transforms.AlignmentKind`, + `.pulse.transforms.AlignEquispaced`, and :class:`.pulse.transforms.AlignFunc` are removed. + They were deprecated + in Qiskit 0.37 (with Terra 0.21), released on June 2022. + - | + The argument ``circuits`` in the method :meth:`qiskit.qpy.interface.dump` + is removed as its usage was deprecated + in Qiskit 0.37 (with Terra 0.21), released on June 2022. + Instead, use the argument ``programs``, which behaves identically. + diff --git a/test/benchmarks/scheduling_passes.py b/test/benchmarks/scheduling_passes.py index 3d97ecdf67..a4c25dc46b 100644 --- a/test/benchmarks/scheduling_passes.py +++ b/test/benchmarks/scheduling_passes.py @@ -15,13 +15,13 @@ from qiskit import transpile from qiskit.circuit.library.standard_gates import XGate -from qiskit.transpiler import CouplingMap +from qiskit.transpiler import CouplingMap, PassManager from qiskit.transpiler import InstructionDurations from qiskit.transpiler.passes import ( TimeUnitConversion, - ASAPSchedule, - ALAPSchedule, - DynamicalDecoupling, + ASAPScheduleAnalysis, + ALAPScheduleAnalysis, + PadDynamicalDecoupling, ) from qiskit.converters import circuit_to_dag @@ -109,22 +109,39 @@ class SchedulingPassBenchmarks: dt=1e-9, ) self.timed_dag = TimeUnitConversion(self.durations).run(self.dag) - _pass = ALAPSchedule(self.durations) - _pass.property_set["time_unit"] = "dt" - self.scheduled_dag = _pass.run(self.timed_dag) + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ALAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling(self.durations, dd_sequence), + ] + ) + self.scheduled_dag = pm.run(self.timed_dag) def time_time_unit_conversion_pass(self, _, __): TimeUnitConversion(self.durations).run(self.dag) def time_alap_schedule_pass(self, _, __): - _pass = ALAPSchedule(self.durations) - _pass.property_set["time_unit"] = "dt" - _pass.run(self.timed_dag) + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ALAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling(self.durations, dd_sequence), + ] + ) + pm.run(self.timed_dag) def time_asap_schedule_pass(self, _, __): - _pass = ASAPSchedule(self.durations) - _pass.property_set["time_unit"] = "dt" - _pass.run(self.timed_dag) + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling(self.durations, dd_sequence), + ] + ) + pm.run(self.timed_dag) def time_dynamical_decoupling_pass(self, _, __): - DynamicalDecoupling(self.durations, dd_sequence=[XGate(), XGate()]).run(self.scheduled_dag) + PadDynamicalDecoupling(self.durations, dd_sequence=[XGate(), XGate()]).run( + self.scheduled_dag + ) diff --git a/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py b/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py index efd3b86ee3..feb50bab80 100644 --- a/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py +++ b/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py @@ -14,316 +14,8 @@ from qiskit import QuantumCircuit, pulse from qiskit.test import QiskitTestCase -from qiskit.transpiler import InstructionDurations from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.passes import ( - AlignMeasures, - ValidatePulseGates, - ALAPSchedule, - TimeUnitConversion, -) - - -class TestAlignMeasures(QiskitTestCase): - """A test for measurement alignment pass.""" - - def setUp(self): - super().setUp() - instruction_durations = InstructionDurations() - instruction_durations.update( - [ - ("rz", (0,), 0), - ("rz", (1,), 0), - ("x", (0,), 160), - ("x", (1,), 160), - ("sx", (0,), 160), - ("sx", (1,), 160), - ("cx", (0, 1), 800), - ("cx", (1, 0), 800), - ("measure", None, 1600), - ] - ) - self.time_conversion_pass = TimeUnitConversion(inst_durations=instruction_durations) - # reproduce old behavior of 0.20.0 before #7655 - # currently default write latency is 0 - self.scheduling_pass = ALAPSchedule( - durations=instruction_durations, - clbit_write_latency=1600, - conditional_latency=0, - ) - self.align_measure_pass = AlignMeasures(alignment=16) - - def test_t1_experiment_type(self): - """Test T1 experiment type circuit. - - (input) - - ┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├ - └───┘└────────────────┘└╥┘ - c: 1/════════════════════════╩═ - 0 - - (aligned) - - ┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(112[dt]) ├┤M├ - └───┘└────────────────┘└╥┘ - c: 1/════════════════════════╩═ - 0 - - This type of experiment slightly changes delay duration of interest. - However the quantization error should be less than alignment * dt. - """ - circuit = QuantumCircuit(1, 1) - circuit.x(0) - circuit.delay(100, 0, unit="dt") - circuit.measure(0, 0) - - timed_circuit = self.time_conversion_pass(circuit) - scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) - aligned_circuit = self.align_measure_pass( - scheduled_circuit, property_set={"time_unit": "dt"} - ) - - ref_circuit = QuantumCircuit(1, 1) - ref_circuit.x(0) - ref_circuit.delay(112, 0, unit="dt") - ref_circuit.measure(0, 0) - - self.assertEqual(aligned_circuit, ref_circuit) - - def test_hanh_echo_experiment_type(self): - """Test Hahn echo experiment type circuit. - - (input) - - ┌────┐┌────────────────┐┌───┐┌────────────────┐┌────┐┌─┐ - q_0: ┤ √X ├┤ Delay(100[dt]) ├┤ X ├┤ Delay(100[dt]) ├┤ √X ├┤M├ - └────┘└────────────────┘└───┘└────────────────┘└────┘└╥┘ - c: 1/══════════════════════════════════════════════════════╩═ - 0 - - (output) - - ┌────┐┌────────────────┐┌───┐┌────────────────┐┌────┐┌──────────────┐┌─┐ - q_0: ┤ √X ├┤ Delay(100[dt]) ├┤ X ├┤ Delay(100[dt]) ├┤ √X ├┤ Delay(8[dt]) ├┤M├ - └────┘└────────────────┘└───┘└────────────────┘└────┘└──────────────┘└╥┘ - c: 1/══════════════════════════════════════════════════════════════════════╩═ - 0 - - This type of experiment doesn't change duration of interest (two in the middle). - However induces slight delay less than alignment * dt before measurement. - This might induce extra amplitude damping error. - """ - circuit = QuantumCircuit(1, 1) - circuit.sx(0) - circuit.delay(100, 0, unit="dt") - circuit.x(0) - circuit.delay(100, 0, unit="dt") - circuit.sx(0) - circuit.measure(0, 0) - - timed_circuit = self.time_conversion_pass(circuit) - scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) - aligned_circuit = self.align_measure_pass( - scheduled_circuit, property_set={"time_unit": "dt"} - ) - - ref_circuit = QuantumCircuit(1, 1) - ref_circuit.sx(0) - ref_circuit.delay(100, 0, unit="dt") - ref_circuit.x(0) - ref_circuit.delay(100, 0, unit="dt") - ref_circuit.sx(0) - ref_circuit.delay(8, 0, unit="dt") - ref_circuit.measure(0, 0) - - self.assertEqual(aligned_circuit, ref_circuit) - - def test_mid_circuit_measure(self): - """Test circuit with mid circuit measurement. - - (input) - - ┌───┐┌────────────────┐┌─┐┌───────────────┐┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├┤ Delay(10[dt]) ├┤ X ├┤ Delay(120[dt]) ├┤M├ - └───┘└────────────────┘└╥┘└───────────────┘└───┘└────────────────┘└╥┘ - c: 2/════════════════════════╩══════════════════════════════════════════╩═ - 0 1 - - (output) - - ┌───┐┌────────────────┐┌─┐┌───────────────┐┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(112[dt]) ├┤M├┤ Delay(10[dt]) ├┤ X ├┤ Delay(134[dt]) ├┤M├ - └───┘└────────────────┘└╥┘└───────────────┘└───┘└────────────────┘└╥┘ - c: 2/════════════════════════╩══════════════════════════════════════════╩═ - 0 1 - - Extra delay is always added to the existing delay right before the measurement. - Delay after measurement is unchanged. - """ - circuit = QuantumCircuit(1, 2) - circuit.x(0) - circuit.delay(100, 0, unit="dt") - circuit.measure(0, 0) - circuit.delay(10, 0, unit="dt") - circuit.x(0) - circuit.delay(120, 0, unit="dt") - circuit.measure(0, 1) - - timed_circuit = self.time_conversion_pass(circuit) - scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) - aligned_circuit = self.align_measure_pass( - scheduled_circuit, property_set={"time_unit": "dt"} - ) - - ref_circuit = QuantumCircuit(1, 2) - ref_circuit.x(0) - ref_circuit.delay(112, 0, unit="dt") - ref_circuit.measure(0, 0) - ref_circuit.delay(10, 0, unit="dt") - ref_circuit.x(0) - ref_circuit.delay(134, 0, unit="dt") - ref_circuit.measure(0, 1) - - self.assertEqual(aligned_circuit, ref_circuit) - - def test_mid_circuit_multiq_gates(self): - """Test circuit with mid circuit measurement and multi qubit gates. - - (input) - - ┌───┐┌────────────────┐┌─┐ ┌─┐ - q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├──■───────■──┤M├ - └───┘└────────────────┘└╥┘┌─┴─┐┌─┐┌─┴─┐└╥┘ - q_1: ────────────────────────╫─┤ X ├┤M├┤ X ├─╫─ - ║ └───┘└╥┘└───┘ ║ - c: 2/════════════════════════╩═══════╩═══════╩═ - 0 1 0 - - (output) - - ┌───┐ ┌────────────────┐┌─┐ ┌─────────────────┐ ┌─┐» - q_0: ───────┤ X ├───────┤ Delay(112[dt]) ├┤M├──■──┤ Delay(1600[dt]) ├──■──┤M├» - ┌──────┴───┴──────┐└────────────────┘└╥┘┌─┴─┐└───────┬─┬───────┘┌─┴─┐└╥┘» - q_1: ┤ Delay(1872[dt]) ├───────────────────╫─┤ X ├────────┤M├────────┤ X ├─╫─» - └─────────────────┘ ║ └───┘ └╥┘ └───┘ ║ » - c: 2/══════════════════════════════════════╩═══════════════╩═══════════════╩═» - 0 1 0 » - « - «q_0: ─────────────────── - « ┌─────────────────┐ - «q_1: ┤ Delay(1600[dt]) ├ - « └─────────────────┘ - «c: 2/═══════════════════ - « - - Delay for the other channel paired by multi-qubit instruction is also scheduled. - Delay (1872dt) = X (160dt) + Delay (100dt + extra 12dt) + Measure (1600dt). - """ - circuit = QuantumCircuit(2, 2) - circuit.x(0) - circuit.delay(100, 0, unit="dt") - circuit.measure(0, 0) - circuit.cx(0, 1) - circuit.measure(1, 1) - circuit.cx(0, 1) - circuit.measure(0, 0) - - timed_circuit = self.time_conversion_pass(circuit) - scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) - aligned_circuit = self.align_measure_pass( - scheduled_circuit, property_set={"time_unit": "dt"} - ) - - ref_circuit = QuantumCircuit(2, 2) - ref_circuit.x(0) - ref_circuit.delay(112, 0, unit="dt") - ref_circuit.measure(0, 0) - ref_circuit.delay(160 + 112 + 1600, 1, unit="dt") - ref_circuit.cx(0, 1) - ref_circuit.delay(1600, 0, unit="dt") - ref_circuit.measure(1, 1) - ref_circuit.cx(0, 1) - ref_circuit.delay(1600, 1, unit="dt") - ref_circuit.measure(0, 0) - - self.assertEqual(aligned_circuit, ref_circuit) - - def test_alignment_is_not_processed(self): - """Test avoid pass processing if delay is aligned.""" - circuit = QuantumCircuit(2, 2) - circuit.x(0) - circuit.delay(160, 0, unit="dt") - circuit.measure(0, 0) - circuit.cx(0, 1) - circuit.measure(1, 1) - circuit.cx(0, 1) - circuit.measure(0, 0) - - # pre scheduling is not necessary because alignment is skipped - # this is to minimize breaking changes to existing code. - transpiled = self.align_measure_pass(circuit, property_set={"time_unit": "dt"}) - - self.assertEqual(transpiled, circuit) - - def test_circuit_using_clbit(self): - """Test a circuit with instructions using a common clbit. - - (input) - ┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├────────────── - └───┘└────────────────┘└╥┘ ┌───┐ - q_1: ────────────────────────╫────┤ X ├────── - ║ └─╥─┘ ┌─┐ - q_2: ────────────────────────╫──────╫─────┤M├ - ║ ┌────╨────┐└╥┘ - c: 1/════════════════════════╩═╡ c_0 = T ╞═╩═ - 0 └─────────┘ 0 - - (aligned) - ┌───┐ ┌────────────────┐┌─┐┌────────────────┐ - q_0: ───────┤ X ├───────┤ Delay(112[dt]) ├┤M├┤ Delay(160[dt]) ├─── - ┌──────┴───┴──────┐└────────────────┘└╥┘└─────┬───┬──────┘ - q_1: ┤ Delay(1872[dt]) ├───────────────────╫───────┤ X ├────────── - └┬────────────────┤ ║ └─╥─┘ ┌─┐ - q_2: ─┤ Delay(432[dt]) ├───────────────────╫─────────╫─────────┤M├ - └────────────────┘ ║ ┌────╨────┐ └╥┘ - c: 1/══════════════════════════════════════╩════╡ c_0 = T ╞═════╩═ - 0 └─────────┘ 0 - - Looking at the q_0, the total schedule length T becomes - 160 (x) + 112 (aligned delay) + 1600 (measure) + 160 (delay) = 2032. - The last delay comes from ALAP scheduling called before the AlignMeasure pass, - which aligns stop times as late as possible, so the start time of x(1).c_if(0) - and the stop time of measure(0, 0) become T - 160. - """ - circuit = QuantumCircuit(3, 1) - circuit.x(0) - circuit.delay(100, 0, unit="dt") - circuit.measure(0, 0) - circuit.x(1).c_if(0, 1) - circuit.measure(2, 0) - - timed_circuit = self.time_conversion_pass(circuit) - scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) - aligned_circuit = self.align_measure_pass( - scheduled_circuit, property_set={"time_unit": "dt"} - ) - self.assertEqual(aligned_circuit.duration, 2032) - - ref_circuit = QuantumCircuit(3, 1) - ref_circuit.x(0) - ref_circuit.delay(112, 0, unit="dt") - ref_circuit.delay(1872, 1, unit="dt") # 2032 - 160 - ref_circuit.delay(432, 2, unit="dt") # 2032 - 1600 - ref_circuit.measure(0, 0) - ref_circuit.x(1).c_if(0, 1) - ref_circuit.delay(160, 0, unit="dt") - ref_circuit.measure(2, 0) - - self.assertEqual(aligned_circuit, ref_circuit) +from qiskit.transpiler.passes import ValidatePulseGates class TestPulseGateValidation(QiskitTestCase): diff --git a/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py b/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py deleted file mode 100644 index 2f375c46f6..0000000000 --- a/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py +++ /dev/null @@ -1,811 +0,0 @@ -# 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. - -"""Test the legacy Scheduling passes""" - -import unittest - -from ddt import ddt, data, unpack - -from qiskit import QuantumCircuit -from qiskit.circuit import Delay, Parameter -from qiskit.circuit.library.standard_gates import XGate, YGate, CXGate -from qiskit.test import QiskitTestCase -from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.instruction_durations import InstructionDurations -from qiskit.transpiler.passes import ASAPSchedule, ALAPSchedule, DynamicalDecoupling -from qiskit.transpiler.passmanager import PassManager -from qiskit.transpiler.target import Target, InstructionProperties - - -@ddt -class TestSchedulingPass(QiskitTestCase): - """Tests the Scheduling passes""" - - def test_alap_agree_with_reverse_asap_reverse(self): - """Test if ALAP schedule agrees with doubly-reversed ASAP schedule.""" - qc = QuantumCircuit(2) - qc.h(0) - qc.delay(500, 1) - qc.cx(0, 1) - qc.measure_all() - - durations = InstructionDurations( - [("h", 0, 200), ("cx", [0, 1], 700), ("measure", None, 1000)] - ) - - pm = PassManager(ALAPSchedule(durations)) - alap_qc = pm.run(qc) - - pm = PassManager(ASAPSchedule(durations)) - new_qc = pm.run(qc.reverse_ops()) - new_qc = new_qc.reverse_ops() - new_qc.name = new_qc.name - - self.assertEqual(alap_qc, new_qc) - - @data(ALAPSchedule, ASAPSchedule) - def test_classically_controlled_gate_after_measure(self, schedule_pass): - """Test if ALAP/ASAP schedules circuits with c_if after measure with a common clbit. - See: https://github.com/Qiskit/qiskit-terra/issues/7654 - - (input) - ┌─┐ - q_0: ┤M├─────────── - └╥┘ ┌───┐ - q_1: ─╫────┤ X ├─── - ║ └─╥─┘ - ║ ┌────╨────┐ - c: 1/═╩═╡ c_0 = T ╞ - 0 └─────────┘ - - (scheduled) - ┌─┐┌────────────────┐ - q_0: ───────────────────┤M├┤ Delay(200[dt]) ├ - ┌─────────────────┐└╥┘└─────┬───┬──────┘ - q_1: ┤ Delay(1000[dt]) ├─╫───────┤ X ├─────── - └─────────────────┘ ║ └─╥─┘ - ║ ┌────╨────┐ - c: 1/════════════════════╩════╡ c_0=0x1 ╞════ - 0 └─────────┘ - """ - qc = QuantumCircuit(2, 1) - qc.measure(0, 0) - qc.x(1).c_if(0, True) - - durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - pm = PassManager(schedule_pass(durations)) - scheduled = pm.run(qc) - - expected = QuantumCircuit(2, 1) - expected.measure(0, 0) - expected.delay(1000, 1) # x.c_if starts after measure - expected.x(1).c_if(0, True) - expected.delay(200, 0) - - self.assertEqual(expected, scheduled) - - @data(ALAPSchedule, ASAPSchedule) - def test_measure_after_measure(self, schedule_pass): - """Test if ALAP/ASAP schedules circuits with measure after measure with a common clbit. - See: https://github.com/Qiskit/qiskit-terra/issues/7654 - - (input) - ┌───┐┌─┐ - q_0: ┤ X ├┤M├─── - └───┘└╥┘┌─┐ - q_1: ──────╫─┤M├ - ║ └╥┘ - c: 1/══════╩══╩═ - 0 0 - - (scheduled) - ┌───┐ ┌─┐┌─────────────────┐ - q_0: ───────┤ X ├───────┤M├┤ Delay(1000[dt]) ├ - ┌──────┴───┴──────┐└╥┘└───────┬─┬───────┘ - q_1: ┤ Delay(1200[dt]) ├─╫─────────┤M├──────── - └─────────────────┘ ║ └╥┘ - c: 1/════════════════════╩══════════╩═════════ - 0 0 - """ - qc = QuantumCircuit(2, 1) - qc.x(0) - qc.measure(0, 0) - qc.measure(1, 0) - - durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - pm = PassManager(schedule_pass(durations)) - scheduled = pm.run(qc) - - expected = QuantumCircuit(2, 1) - expected.x(0) - expected.measure(0, 0) - expected.delay(1200, 1) - expected.measure(1, 0) - expected.delay(1000, 0) - - self.assertEqual(expected, scheduled) - - @data(ALAPSchedule, ASAPSchedule) - def test_c_if_on_different_qubits(self, schedule_pass): - """Test if ALAP/ASAP schedules circuits with `c_if`s on different qubits. - - (input) - ┌─┐ - q_0: ┤M├────────────────────── - └╥┘ ┌───┐ - q_1: ─╫────┤ X ├────────────── - ║ └─╥─┘ ┌───┐ - q_2: ─╫──────╫────────┤ X ├─── - ║ ║ └─╥─┘ - ║ ┌────╨────┐┌────╨────┐ - c: 1/═╩═╡ c_0 = T ╞╡ c_0 = T ╞ - 0 └─────────┘└─────────┘ - - (scheduled) - - ┌─┐┌────────────────┐ - q_0: ───────────────────┤M├┤ Delay(200[dt]) ├─────────── - ┌─────────────────┐└╥┘└─────┬───┬──────┘ - q_1: ┤ Delay(1000[dt]) ├─╫───────┤ X ├────────────────── - ├─────────────────┤ ║ └─╥─┘ ┌───┐ - q_2: ┤ Delay(1000[dt]) ├─╫─────────╫────────────┤ X ├─── - └─────────────────┘ ║ ║ └─╥─┘ - ║ ┌────╨────┐ ┌────╨────┐ - c: 1/════════════════════╩════╡ c_0=0x1 ╞════╡ c_0=0x1 ╞ - 0 └─────────┘ └─────────┘ - """ - qc = QuantumCircuit(3, 1) - qc.measure(0, 0) - qc.x(1).c_if(0, True) - qc.x(2).c_if(0, True) - - durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - pm = PassManager(schedule_pass(durations)) - scheduled = pm.run(qc) - - expected = QuantumCircuit(3, 1) - expected.measure(0, 0) - expected.delay(1000, 1) - expected.delay(1000, 2) - expected.x(1).c_if(0, True) - expected.x(2).c_if(0, True) - expected.delay(200, 0) - - self.assertEqual(expected, scheduled) - - @data(ALAPSchedule, ASAPSchedule) - def test_shorter_measure_after_measure(self, schedule_pass): - """Test if ALAP/ASAP schedules circuits with shorter measure after measure with a common clbit. - - (input) - ┌─┐ - q_0: ┤M├─── - └╥┘┌─┐ - q_1: ─╫─┤M├ - ║ └╥┘ - c: 1/═╩══╩═ - 0 0 - - (scheduled) - ┌─┐┌────────────────┐ - q_0: ───────────────────┤M├┤ Delay(700[dt]) ├ - ┌─────────────────┐└╥┘└──────┬─┬───────┘ - q_1: ┤ Delay(1000[dt]) ├─╫────────┤M├──────── - └─────────────────┘ ║ └╥┘ - c: 1/════════════════════╩═════════╩═════════ - 0 0 - """ - qc = QuantumCircuit(2, 1) - qc.measure(0, 0) - qc.measure(1, 0) - - durations = InstructionDurations([("measure", [0], 1000), ("measure", [1], 700)]) - pm = PassManager(schedule_pass(durations)) - scheduled = pm.run(qc) - - expected = QuantumCircuit(2, 1) - expected.measure(0, 0) - expected.delay(1000, 1) - expected.measure(1, 0) - expected.delay(700, 0) - - self.assertEqual(expected, scheduled) - - @data(ALAPSchedule, ASAPSchedule) - def test_measure_after_c_if(self, schedule_pass): - """Test if ALAP/ASAP schedules circuits with c_if after measure with a common clbit. - - (input) - ┌─┐ - q_0: ┤M├────────────── - └╥┘ ┌───┐ - q_1: ─╫────┤ X ├────── - ║ └─╥─┘ ┌─┐ - q_2: ─╫──────╫─────┤M├ - ║ ┌────╨────┐└╥┘ - c: 1/═╩═╡ c_0 = T ╞═╩═ - 0 └─────────┘ 0 - - (scheduled) - ┌─┐┌─────────────────┐ - q_0: ───────────────────┤M├┤ Delay(1000[dt]) ├────────────────── - ┌─────────────────┐└╥┘└──────┬───┬──────┘┌────────────────┐ - q_1: ┤ Delay(1000[dt]) ├─╫────────┤ X ├───────┤ Delay(800[dt]) ├ - ├─────────────────┤ ║ └─╥─┘ └──────┬─┬───────┘ - q_2: ┤ Delay(1000[dt]) ├─╫──────────╫────────────────┤M├──────── - └─────────────────┘ ║ ┌────╨────┐ └╥┘ - c: 1/════════════════════╩═════╡ c_0=0x1 ╞════════════╩═════════ - 0 └─────────┘ 0 - """ - qc = QuantumCircuit(3, 1) - qc.measure(0, 0) - qc.x(1).c_if(0, 1) - qc.measure(2, 0) - - durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - pm = PassManager(schedule_pass(durations)) - scheduled = pm.run(qc) - - expected = QuantumCircuit(3, 1) - expected.delay(1000, 1) - expected.delay(1000, 2) - expected.measure(0, 0) - expected.x(1).c_if(0, 1) - expected.measure(2, 0) - expected.delay(1000, 0) - expected.delay(800, 1) - - self.assertEqual(expected, scheduled) - - def test_parallel_gate_different_length(self): - """Test circuit having two parallel instruction with different length. - - (input) - ┌───┐┌─┐ - q_0: ┤ X ├┤M├─── - ├───┤└╥┘┌─┐ - q_1: ┤ X ├─╫─┤M├ - └───┘ ║ └╥┘ - c: 2/══════╩══╩═ - 0 1 - - (expected, ALAP) - ┌────────────────┐┌───┐┌─┐ - q_0: ┤ Delay(200[dt]) ├┤ X ├┤M├ - └─────┬───┬──────┘└┬─┬┘└╥┘ - q_1: ──────┤ X ├────────┤M├──╫─ - └───┘ └╥┘ ║ - c: 2/════════════════════╩═══╩═ - 1 0 - - (expected, ASAP) - ┌───┐┌─┐┌────────────────┐ - q_0: ┤ X ├┤M├┤ Delay(200[dt]) ├ - ├───┤└╥┘└──────┬─┬───────┘ - q_1: ┤ X ├─╫────────┤M├──────── - └───┘ ║ └╥┘ - c: 2/══════╩═════════╩═════════ - 0 1 - - """ - qc = QuantumCircuit(2, 2) - qc.x(0) - qc.x(1) - qc.measure(0, 0) - qc.measure(1, 1) - - durations = InstructionDurations( - [("x", [0], 200), ("x", [1], 400), ("measure", None, 1000)] - ) - pm = PassManager(ALAPSchedule(durations)) - qc_alap = pm.run(qc) - - alap_expected = QuantumCircuit(2, 2) - alap_expected.delay(200, 0) - alap_expected.x(0) - alap_expected.x(1) - alap_expected.measure(0, 0) - alap_expected.measure(1, 1) - - self.assertEqual(qc_alap, alap_expected) - - pm = PassManager(ASAPSchedule(durations)) - qc_asap = pm.run(qc) - - asap_expected = QuantumCircuit(2, 2) - asap_expected.x(0) - asap_expected.x(1) - asap_expected.measure(0, 0) # immediately start after X gate - asap_expected.measure(1, 1) - asap_expected.delay(200, 0) - - self.assertEqual(qc_asap, asap_expected) - - def test_parallel_gate_different_length_with_barrier(self): - """Test circuit having two parallel instruction with different length with barrier. - - (input) - ┌───┐┌─┐ - q_0: ┤ X ├┤M├─── - ├───┤└╥┘┌─┐ - q_1: ┤ X ├─╫─┤M├ - └───┘ ║ └╥┘ - c: 2/══════╩══╩═ - 0 1 - - (expected, ALAP) - ┌────────────────┐┌───┐ ░ ┌─┐ - q_0: ┤ Delay(200[dt]) ├┤ X ├─░─┤M├─── - └─────┬───┬──────┘└───┘ ░ └╥┘┌─┐ - q_1: ──────┤ X ├─────────────░──╫─┤M├ - └───┘ ░ ║ └╥┘ - c: 2/═══════════════════════════╩══╩═ - 0 1 - - (expected, ASAP) - ┌───┐┌────────────────┐ ░ ┌─┐ - q_0: ┤ X ├┤ Delay(200[dt]) ├─░─┤M├─── - ├───┤└────────────────┘ ░ └╥┘┌─┐ - q_1: ┤ X ├───────────────────░──╫─┤M├ - └───┘ ░ ║ └╥┘ - c: 2/═══════════════════════════╩══╩═ - 0 1 - """ - qc = QuantumCircuit(2, 2) - qc.x(0) - qc.x(1) - qc.barrier() - qc.measure(0, 0) - qc.measure(1, 1) - - durations = InstructionDurations( - [("x", [0], 200), ("x", [1], 400), ("measure", None, 1000)] - ) - pm = PassManager(ALAPSchedule(durations)) - qc_alap = pm.run(qc) - - alap_expected = QuantumCircuit(2, 2) - alap_expected.delay(200, 0) - alap_expected.x(0) - alap_expected.x(1) - alap_expected.barrier() - alap_expected.measure(0, 0) - alap_expected.measure(1, 1) - - self.assertEqual(qc_alap, alap_expected) - - pm = PassManager(ASAPSchedule(durations)) - qc_asap = pm.run(qc) - - asap_expected = QuantumCircuit(2, 2) - asap_expected.x(0) - asap_expected.delay(200, 0) - asap_expected.x(1) - asap_expected.barrier() - asap_expected.measure(0, 0) - asap_expected.measure(1, 1) - - self.assertEqual(qc_asap, asap_expected) - - def test_measure_after_c_if_on_edge_locking(self): - """Test if ALAP/ASAP schedules circuits with c_if after measure with a common clbit. - - The scheduler is configured to reproduce behavior of the 0.20.0, - in which clbit lock is applied to the end-edge of measure instruction. - See https://github.com/Qiskit/qiskit-terra/pull/7655 - - (input) - ┌─┐ - q_0: ┤M├────────────── - └╥┘ ┌───┐ - q_1: ─╫────┤ X ├────── - ║ └─╥─┘ ┌─┐ - q_2: ─╫──────╫─────┤M├ - ║ ┌────╨────┐└╥┘ - c: 1/═╩═╡ c_0 = T ╞═╩═ - 0 └─────────┘ 0 - - (ASAP scheduled) - ┌─┐┌────────────────┐ - q_0: ───────────────────┤M├┤ Delay(200[dt]) ├───────────────────── - ┌─────────────────┐└╥┘└─────┬───┬──────┘ - q_1: ┤ Delay(1000[dt]) ├─╫───────┤ X ├──────────────────────────── - └─────────────────┘ ║ └─╥─┘ ┌─┐┌────────────────┐ - q_2: ────────────────────╫─────────╫─────────┤M├┤ Delay(200[dt]) ├ - ║ ┌────╨────┐ └╥┘└────────────────┘ - c: 1/════════════════════╩════╡ c_0=0x1 ╞═════╩═══════════════════ - 0 └─────────┘ 0 - - (ALAP scheduled) - ┌─┐┌────────────────┐ - q_0: ───────────────────┤M├┤ Delay(200[dt]) ├─── - ┌─────────────────┐└╥┘└─────┬───┬──────┘ - q_1: ┤ Delay(1000[dt]) ├─╫───────┤ X ├────────── - └┬────────────────┤ ║ └─╥─┘ ┌─┐ - q_2: ─┤ Delay(200[dt]) ├─╫─────────╫─────────┤M├ - └────────────────┘ ║ ┌────╨────┐ └╥┘ - c: 1/════════════════════╩════╡ c_0=0x1 ╞═════╩═ - 0 └─────────┘ 0 - - """ - qc = QuantumCircuit(3, 1) - qc.measure(0, 0) - qc.x(1).c_if(0, 1) - qc.measure(2, 0) - - durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - - # lock at the end edge - actual_asap = PassManager(ASAPSchedule(durations, clbit_write_latency=1000)).run(qc) - actual_alap = PassManager(ALAPSchedule(durations, clbit_write_latency=1000)).run(qc) - - # start times of 2nd measure depends on ASAP/ALAP - expected_asap = QuantumCircuit(3, 1) - expected_asap.measure(0, 0) - expected_asap.delay(1000, 1) - expected_asap.x(1).c_if(0, 1) - expected_asap.measure(2, 0) - expected_asap.delay(200, 0) - expected_asap.delay(200, 2) - self.assertEqual(expected_asap, actual_asap) - - expected_alap = QuantumCircuit(3, 1) - expected_alap.measure(0, 0) - expected_alap.delay(1000, 1) - expected_alap.x(1).c_if(0, 1) - expected_alap.delay(200, 2) - expected_alap.measure(2, 0) - expected_alap.delay(200, 0) - self.assertEqual(expected_alap, actual_alap) - - @data([100, 200], [500, 0], [1000, 200]) - @unpack - def test_active_reset_circuit(self, write_lat, cond_lat): - """Test practical example of reset circuit. - - Because of the stimulus pulse overlap with the previous XGate on the q register, - measure instruction is always triggered after XGate regardless of write latency. - Thus only conditional latency matters in the scheduling. - - (input) - ┌─┐ ┌───┐ ┌─┐ ┌───┐ ┌─┐ ┌───┐ - q: ┤M├───┤ X ├───┤M├───┤ X ├───┤M├───┤ X ├─── - └╥┘ └─╥─┘ └╥┘ └─╥─┘ └╥┘ └─╥─┘ - ║ ┌────╨────┐ ║ ┌────╨────┐ ║ ┌────╨────┐ - c: 1/═╩═╡ c_0=0x1 ╞═╩═╡ c_0=0x1 ╞═╩═╡ c_0=0x1 ╞ - 0 └─────────┘ 0 └─────────┘ 0 └─────────┘ - - """ - qc = QuantumCircuit(1, 1) - qc.measure(0, 0) - qc.x(0).c_if(0, 1) - qc.measure(0, 0) - qc.x(0).c_if(0, 1) - qc.measure(0, 0) - qc.x(0).c_if(0, 1) - - durations = InstructionDurations([("x", None, 100), ("measure", None, 1000)]) - actual_asap = PassManager( - ASAPSchedule(durations, clbit_write_latency=write_lat, conditional_latency=cond_lat) - ).run(qc) - actual_alap = PassManager( - ALAPSchedule(durations, clbit_write_latency=write_lat, conditional_latency=cond_lat) - ).run(qc) - - expected = QuantumCircuit(1, 1) - expected.measure(0, 0) - if cond_lat > 0: - expected.delay(cond_lat, 0) - expected.x(0).c_if(0, 1) - expected.measure(0, 0) - if cond_lat > 0: - expected.delay(cond_lat, 0) - expected.x(0).c_if(0, 1) - expected.measure(0, 0) - if cond_lat > 0: - expected.delay(cond_lat, 0) - expected.x(0).c_if(0, 1) - - self.assertEqual(expected, actual_asap) - self.assertEqual(expected, actual_alap) - - def test_random_complicated_circuit(self): - """Test scheduling complicated circuit with control flow. - - (input) - ┌────────────────┐ ┌───┐ ░ ┌───┐ » - q_0: ┤ Delay(100[dt]) ├───┤ X ├────░──────────────────┤ X ├───» - └────────────────┘ └─╥─┘ ░ ┌───┐ └─╥─┘ » - q_1: ───────────────────────╫──────░───────┤ X ├────────╫─────» - ║ ░ ┌─┐ └─╥─┘ ║ » - q_2: ───────────────────────╫──────░─┤M├─────╫──────────╫─────» - ┌────╨────┐ ░ └╥┘┌────╨────┐┌────╨────┐» - c: 1/══════════════════╡ c_0=0x1 ╞════╩═╡ c_0=0x0 ╞╡ c_0=0x0 ╞» - └─────────┘ 0 └─────────┘└─────────┘» - « ┌────────────────┐┌───┐ - «q_0: ┤ Delay(300[dt]) ├┤ X ├─────■───── - « └────────────────┘└───┘ ┌─┴─┐ - «q_1: ────────■─────────────────┤ X ├─── - « ┌─┴─┐ ┌─┐ └─╥─┘ - «q_2: ──────┤ X ├────────┤M├──────╫───── - « └───┘ └╥┘ ┌────╨────┐ - «c: 1/════════════════════╩══╡ c_0=0x0 ╞ - « 0 └─────────┘ - - (ASAP scheduled) duration = 2800 dt - ┌────────────────┐┌────────────────┐ ┌───┐ ░ ┌─────────────────┐» - q_0: ┤ Delay(100[dt]) ├┤ Delay(100[dt]) ├───┤ X ├────░─┤ Delay(1400[dt]) ├» - ├────────────────┤└────────────────┘ └─╥─┘ ░ ├─────────────────┤» - q_1: ┤ Delay(300[dt]) ├───────────────────────╫──────░─┤ Delay(1200[dt]) ├» - ├────────────────┤ ║ ░ └───────┬─┬───────┘» - q_2: ┤ Delay(300[dt]) ├───────────────────────╫──────░─────────┤M├────────» - └────────────────┘ ┌────╨────┐ ░ └╥┘ » - c: 1/════════════════════════════════════╡ c_0=0x1 ╞════════════╩═════════» - └─────────┘ 0 » - « ┌───┐ ┌────────────────┐» - «q_0: ────────────────────────────────┤ X ├───┤ Delay(300[dt]) ├» - « ┌───┐ └─╥─┘ └────────────────┘» - «q_1: ───┤ X ├──────────────────────────╫─────────────■─────────» - « └─╥─┘ ┌────────────────┐ ║ ┌─┴─┐ » - «q_2: ─────╫─────┤ Delay(300[dt]) ├─────╫───────────┤ X ├───────» - « ┌────╨────┐└────────────────┘┌────╨────┐ └───┘ » - «c: 1/╡ c_0=0x0 ╞══════════════════╡ c_0=0x0 ╞══════════════════» - « └─────────┘ └─────────┘ » - « ┌───┐ ┌────────────────┐ - «q_0: ──────┤ X ├────────────■─────┤ Delay(700[dt]) ├ - « ┌─────┴───┴──────┐ ┌─┴─┐ ├────────────────┤ - «q_1: ┤ Delay(400[dt]) ├───┤ X ├───┤ Delay(700[dt]) ├ - « ├────────────────┤ └─╥─┘ └──────┬─┬───────┘ - «q_2: ┤ Delay(300[dt]) ├─────╫────────────┤M├──────── - « └────────────────┘┌────╨────┐ └╥┘ - «c: 1/══════════════════╡ c_0=0x0 ╞════════╩═════════ - « └─────────┘ 0 - - (ALAP scheduled) duration = 3100 - ┌────────────────┐┌────────────────┐ ┌───┐ ░ ┌─────────────────┐» - q_0: ┤ Delay(100[dt]) ├┤ Delay(100[dt]) ├───┤ X ├────░─┤ Delay(1400[dt]) ├» - ├────────────────┤└────────────────┘ └─╥─┘ ░ ├─────────────────┤» - q_1: ┤ Delay(300[dt]) ├───────────────────────╫──────░─┤ Delay(1200[dt]) ├» - ├────────────────┤ ║ ░ └───────┬─┬───────┘» - q_2: ┤ Delay(300[dt]) ├───────────────────────╫──────░─────────┤M├────────» - └────────────────┘ ┌────╨────┐ ░ └╥┘ » - c: 1/════════════════════════════════════╡ c_0=0x1 ╞════════════╩═════════» - └─────────┘ 0 » - « ┌───┐ ┌────────────────┐» - «q_0: ────────────────────────────────┤ X ├───┤ Delay(300[dt]) ├» - « ┌───┐ ┌────────────────┐ └─╥─┘ └────────────────┘» - «q_1: ───┤ X ├───┤ Delay(300[dt]) ├─────╫─────────────■─────────» - « └─╥─┘ ├────────────────┤ ║ ┌─┴─┐ » - «q_2: ─────╫─────┤ Delay(600[dt]) ├─────╫───────────┤ X ├───────» - « ┌────╨────┐└────────────────┘┌────╨────┐ └───┘ » - «c: 1/╡ c_0=0x0 ╞══════════════════╡ c_0=0x0 ╞══════════════════» - « └─────────┘ └─────────┘ » - « ┌───┐ ┌────────────────┐ - «q_0: ──────┤ X ├────────────■─────┤ Delay(700[dt]) ├ - « ┌─────┴───┴──────┐ ┌─┴─┐ ├────────────────┤ - «q_1: ┤ Delay(100[dt]) ├───┤ X ├───┤ Delay(700[dt]) ├ - « └──────┬─┬───────┘ └─╥─┘ └────────────────┘ - «q_2: ───────┤M├─────────────╫─────────────────────── - « └╥┘ ┌────╨────┐ - «c: 1/════════╩═════════╡ c_0=0x0 ╞══════════════════ - « 0 └─────────┘ - - """ - qc = QuantumCircuit(3, 1) - qc.delay(100, 0) - qc.x(0).c_if(0, 1) - qc.barrier() - qc.measure(2, 0) - qc.x(1).c_if(0, 0) - qc.x(0).c_if(0, 0) - qc.delay(300, 0) - qc.cx(1, 2) - qc.x(0) - qc.cx(0, 1).c_if(0, 0) - qc.measure(2, 0) - - durations = InstructionDurations( - [("x", None, 100), ("measure", None, 1000), ("cx", None, 200)] - ) - - actual_asap = PassManager( - ASAPSchedule(durations, clbit_write_latency=100, conditional_latency=200) - ).run(qc) - actual_alap = PassManager( - ALAPSchedule(durations, clbit_write_latency=100, conditional_latency=200) - ).run(qc) - - expected_asap = QuantumCircuit(3, 1) - expected_asap.delay(100, 0) - expected_asap.delay(100, 0) # due to conditional latency of 200dt - expected_asap.delay(300, 1) - expected_asap.delay(300, 2) - expected_asap.x(0).c_if(0, 1) - expected_asap.barrier() - expected_asap.delay(1400, 0) - expected_asap.delay(1200, 1) - expected_asap.measure(2, 0) - expected_asap.x(1).c_if(0, 0) - expected_asap.x(0).c_if(0, 0) - expected_asap.delay(300, 0) - expected_asap.x(0) - expected_asap.delay(300, 2) - expected_asap.cx(1, 2) - expected_asap.delay(400, 1) - expected_asap.cx(0, 1).c_if(0, 0) - expected_asap.delay(700, 0) # creg is released at t0 of cx(0,1).c_if(0,0) - expected_asap.delay( - 700, 1 - ) # no creg write until 100dt. thus measure can move left by 300dt. - expected_asap.delay(300, 2) - expected_asap.measure(2, 0) - self.assertEqual(expected_asap, actual_asap) - self.assertEqual(actual_asap.duration, 3100) - - expected_alap = QuantumCircuit(3, 1) - expected_alap.delay(100, 0) - expected_alap.delay(100, 0) # due to conditional latency of 200dt - expected_alap.delay(300, 1) - expected_alap.delay(300, 2) - expected_alap.x(0).c_if(0, 1) - expected_alap.barrier() - expected_alap.delay(1400, 0) - expected_alap.delay(1200, 1) - expected_alap.measure(2, 0) - expected_alap.x(1).c_if(0, 0) - expected_alap.x(0).c_if(0, 0) - expected_alap.delay(300, 0) - expected_alap.x(0) - expected_alap.delay(300, 1) - expected_alap.delay(600, 2) - expected_alap.cx(1, 2) - expected_alap.delay(100, 1) - expected_alap.cx(0, 1).c_if(0, 0) - expected_alap.measure(2, 0) - expected_alap.delay(700, 0) - expected_alap.delay(700, 1) - self.assertEqual(expected_alap, actual_alap) - self.assertEqual(actual_alap.duration, 3100) - - def test_dag_introduces_extra_dependency_between_conditionals(self): - """Test dependency between conditional operations in the scheduling. - - In the below example circuit, the conditional x on q1 could start at time 0, - however it must be scheduled after the conditional x on q0 in ASAP scheduling. - That is because circuit model used in the transpiler passes (DAGCircuit) - interprets instructions acting on common clbits must be run in the order - given by the original circuit (QuantumCircuit). - - (input) - ┌────────────────┐ ┌───┐ - q_0: ┤ Delay(100[dt]) ├───┤ X ├─── - └─────┬───┬──────┘ └─╥─┘ - q_1: ──────┤ X ├────────────╫───── - └─╥─┘ ║ - ┌────╨────┐ ┌────╨────┐ - c: 1/═══╡ c_0=0x1 ╞════╡ c_0=0x1 ╞ - └─────────┘ └─────────┘ - - (ASAP scheduled) - ┌────────────────┐ ┌───┐ - q_0: ┤ Delay(100[dt]) ├───┤ X ├────────────── - ├────────────────┤ └─╥─┘ ┌───┐ - q_1: ┤ Delay(100[dt]) ├─────╫────────┤ X ├─── - └────────────────┘ ║ └─╥─┘ - ┌────╨────┐┌────╨────┐ - c: 1/══════════════════╡ c_0=0x1 ╞╡ c_0=0x1 ╞ - └─────────┘└─────────┘ - """ - qc = QuantumCircuit(2, 1) - qc.delay(100, 0) - qc.x(0).c_if(0, True) - qc.x(1).c_if(0, True) - - durations = InstructionDurations([("x", None, 160)]) - pm = PassManager(ASAPSchedule(durations)) - scheduled = pm.run(qc) - - expected = QuantumCircuit(2, 1) - expected.delay(100, 0) - expected.delay(100, 1) # due to extra dependency on clbits - expected.x(0).c_if(0, True) - expected.x(1).c_if(0, True) - - self.assertEqual(expected, scheduled) - - @data(ALAPSchedule, ASAPSchedule) - def test_respect_target_instruction_constraints(self, schedule_pass): - """Test if ALAP/ASAP does not pad delays for qubits that do not support delay instructions. - See: https://github.com/Qiskit/qiskit-terra/issues/9993 - """ - target = Target(dt=1) - target.add_instruction(XGate(), {(1,): InstructionProperties(duration=200)}) - # delays are not supported - - qc = QuantumCircuit(2) - qc.x(1) - - pm = PassManager(schedule_pass(target=target)) - scheduled = pm.run(qc) - - expected = QuantumCircuit(2) - expected.x(1) - # no delay on qubit 0 - - self.assertEqual(expected, scheduled) - - def test_dd_respect_target_instruction_constraints(self): - """Test if DD pass does not pad delays for qubits that do not support delay instructions - and does not insert DD gates for qubits that do not support necessary gates. - See: https://github.com/Qiskit/qiskit-terra/issues/9993 - """ - qc = QuantumCircuit(3) - qc.cx(0, 1) - qc.cx(1, 2) - - target = Target(dt=1) - # Y is partially supported (not supported on qubit 2) - target.add_instruction( - XGate(), {(q,): InstructionProperties(duration=100) for q in range(2)} - ) - target.add_instruction( - CXGate(), - { - (0, 1): InstructionProperties(duration=1000), - (1, 2): InstructionProperties(duration=1000), - }, - ) - # delays are not supported - - # No DD instructions nor delays are padded due to no delay support in the target - pm_xx = PassManager( - [ - ALAPSchedule(target=target), - DynamicalDecoupling(durations=None, dd_sequence=[XGate(), XGate()], target=target), - ] - ) - scheduled = pm_xx.run(qc) - self.assertEqual(qc, scheduled) - - # Fails since Y is not supported in the target - with self.assertRaises(TranspilerError): - PassManager( - [ - ALAPSchedule(target=target), - DynamicalDecoupling( - durations=None, - dd_sequence=[XGate(), YGate(), XGate(), YGate()], - target=target, - ), - ] - ) - - # Add delay support to the target - target.add_instruction(Delay(Parameter("t")), {(q,): None for q in range(3)}) - # No error but no DD on qubit 2 (just delay is padded) since X is not supported on it - scheduled = pm_xx.run(qc) - - expected = QuantumCircuit(3) - expected.delay(1000, [2]) - expected.cx(0, 1) - expected.cx(1, 2) - expected.delay(200, [0]) - expected.x([0]) - expected.delay(400, [0]) - expected.x([0]) - expected.delay(200, [0]) - self.assertEqual(expected, scheduled) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/transpiler/test_instruction_alignments.py b/test/python/transpiler/test_instruction_alignments.py index 74daf8d67a..7e2fb6e050 100644 --- a/test/python/transpiler/test_instruction_alignments.py +++ b/test/python/transpiler/test_instruction_alignments.py @@ -14,508 +14,9 @@ from qiskit import QuantumCircuit, pulse from qiskit.test import QiskitTestCase -from qiskit.transpiler import InstructionDurations, PassManager +from qiskit.transpiler import PassManager from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.passes import ( - AlignMeasures, - InstructionDurationCheck, - ConstrainedReschedule, - ValidatePulseGates, - ALAPScheduleAnalysis, - ASAPScheduleAnalysis, - ALAPSchedule, - PadDelay, - SetIOLatency, -) - - -class TestAlignMeasures(QiskitTestCase): - """A test for measurement alignment pass.""" - - def setUp(self): - super().setUp() - - self.instruction_durations = InstructionDurations( - [ - ("rz", (0,), 0), - ("rz", (1,), 0), - ("x", (0,), 160), - ("x", (1,), 160), - ("sx", (0,), 160), - ("sx", (1,), 160), - ("cx", (0, 1), 800), - ("cx", (1, 0), 800), - ("measure", None, 1600), - ] - ) - - def test_t1_experiment_type(self): - """Test T1 experiment type circuit. - - (input) - - ┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├ - └───┘└────────────────┘└╥┘ - c: 1/════════════════════════╩═ - 0 - - (aligned) - - ┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(112[dt]) ├┤M├ - └───┘└────────────────┘└╥┘ - c: 1/════════════════════════╩═ - 0 - - This type of experiment slightly changes delay duration of interest. - However the quantization error should be less than alignment * dt. - """ - circuit = QuantumCircuit(1, 1) - circuit.x(0) - circuit.delay(100, 0, unit="dt") - circuit.measure(0, 0) - - pm = PassManager( - [ - # reproduce old behavior of 0.20.0 before #7655 - # currently default write latency is 0 - SetIOLatency(clbit_write_latency=1600, conditional_latency=0), - ALAPScheduleAnalysis(durations=self.instruction_durations), - ConstrainedReschedule(acquire_alignment=16), - PadDelay(), - ] - ) - - aligned_circuit = pm.run(circuit) - - ref_circuit = QuantumCircuit(1, 1) - ref_circuit.x(0) - ref_circuit.delay(112, 0, unit="dt") - ref_circuit.measure(0, 0) - - self.assertEqual(aligned_circuit, ref_circuit) - - def test_hanh_echo_experiment_type(self): - """Test Hahn echo experiment type circuit. - - (input) - - ┌────┐┌────────────────┐┌───┐┌────────────────┐┌────┐┌─┐ - q_0: ┤ √X ├┤ Delay(100[dt]) ├┤ X ├┤ Delay(100[dt]) ├┤ √X ├┤M├ - └────┘└────────────────┘└───┘└────────────────┘└────┘└╥┘ - c: 1/══════════════════════════════════════════════════════╩═ - 0 - - (output) - - ┌────┐┌────────────────┐┌───┐┌────────────────┐┌────┐┌──────────────┐┌─┐ - q_0: ┤ √X ├┤ Delay(100[dt]) ├┤ X ├┤ Delay(100[dt]) ├┤ √X ├┤ Delay(8[dt]) ├┤M├ - └────┘└────────────────┘└───┘└────────────────┘└────┘└──────────────┘└╥┘ - c: 1/══════════════════════════════════════════════════════════════════════╩═ - 0 - - This type of experiment doesn't change duration of interest (two in the middle). - However induces slight delay less than alignment * dt before measurement. - This might induce extra amplitude damping error. - """ - circuit = QuantumCircuit(1, 1) - circuit.sx(0) - circuit.delay(100, 0, unit="dt") - circuit.x(0) - circuit.delay(100, 0, unit="dt") - circuit.sx(0) - circuit.measure(0, 0) - - pm = PassManager( - [ - # reproduce old behavior of 0.20.0 before #7655 - # currently default write latency is 0 - SetIOLatency(clbit_write_latency=1600, conditional_latency=0), - ALAPScheduleAnalysis(durations=self.instruction_durations), - ConstrainedReschedule(acquire_alignment=16), - PadDelay(), - ] - ) - - aligned_circuit = pm.run(circuit) - - ref_circuit = QuantumCircuit(1, 1) - ref_circuit.sx(0) - ref_circuit.delay(100, 0, unit="dt") - ref_circuit.x(0) - ref_circuit.delay(100, 0, unit="dt") - ref_circuit.sx(0) - ref_circuit.delay(8, 0, unit="dt") - ref_circuit.measure(0, 0) - - self.assertEqual(aligned_circuit, ref_circuit) - - def test_mid_circuit_measure(self): - """Test circuit with mid circuit measurement. - - (input) - - ┌───┐┌────────────────┐┌─┐┌───────────────┐┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├┤ Delay(10[dt]) ├┤ X ├┤ Delay(120[dt]) ├┤M├ - └───┘└────────────────┘└╥┘└───────────────┘└───┘└────────────────┘└╥┘ - c: 2/════════════════════════╩══════════════════════════════════════════╩═ - 0 1 - - (output) - - ┌───┐┌────────────────┐┌─┐┌───────────────┐┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(112[dt]) ├┤M├┤ Delay(10[dt]) ├┤ X ├┤ Delay(134[dt]) ├┤M├ - └───┘└────────────────┘└╥┘└───────────────┘└───┘└────────────────┘└╥┘ - c: 2/════════════════════════╩══════════════════════════════════════════╩═ - 0 1 - - Extra delay is always added to the existing delay right before the measurement. - Delay after measurement is unchanged. - """ - circuit = QuantumCircuit(1, 2) - circuit.x(0) - circuit.delay(100, 0, unit="dt") - circuit.measure(0, 0) - circuit.delay(10, 0, unit="dt") - circuit.x(0) - circuit.delay(120, 0, unit="dt") - circuit.measure(0, 1) - - pm = PassManager( - [ - # reproduce old behavior of 0.20.0 before #7655 - # currently default write latency is 0 - SetIOLatency(clbit_write_latency=1600, conditional_latency=0), - ALAPScheduleAnalysis(durations=self.instruction_durations), - ConstrainedReschedule(acquire_alignment=16), - PadDelay(), - ] - ) - - aligned_circuit = pm.run(circuit) - - ref_circuit = QuantumCircuit(1, 2) - ref_circuit.x(0) - ref_circuit.delay(112, 0, unit="dt") - ref_circuit.measure(0, 0) - ref_circuit.delay(10, 0, unit="dt") - ref_circuit.x(0) - ref_circuit.delay(134, 0, unit="dt") - ref_circuit.measure(0, 1) - - self.assertEqual(aligned_circuit, ref_circuit) - - def test_mid_circuit_multiq_gates(self): - """Test circuit with mid circuit measurement and multi qubit gates. - - (input) - - ┌───┐┌────────────────┐┌─┐ ┌─┐ - q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├──■───────■──┤M├ - └───┘└────────────────┘└╥┘┌─┴─┐┌─┐┌─┴─┐└╥┘ - q_1: ────────────────────────╫─┤ X ├┤M├┤ X ├─╫─ - ║ └───┘└╥┘└───┘ ║ - c: 2/════════════════════════╩═══════╩═══════╩═ - 0 1 0 - - (output) - - ┌───┐ ┌────────────────┐┌─┐ ┌─────────────────┐ ┌─┐» - q_0: ───────┤ X ├───────┤ Delay(112[dt]) ├┤M├──■──┤ Delay(1600[dt]) ├──■──┤M├» - ┌──────┴───┴──────┐└────────────────┘└╥┘┌─┴─┐└───────┬─┬───────┘┌─┴─┐└╥┘» - q_1: ┤ Delay(1872[dt]) ├───────────────────╫─┤ X ├────────┤M├────────┤ X ├─╫─» - └─────────────────┘ ║ └───┘ └╥┘ └───┘ ║ » - c: 2/══════════════════════════════════════╩═══════════════╩═══════════════╩═» - 0 1 0 » - « - «q_0: ─────────────────── - « ┌─────────────────┐ - «q_1: ┤ Delay(1600[dt]) ├ - « └─────────────────┘ - «c: 2/═══════════════════ - « - - Delay for the other channel paired by multi-qubit instruction is also scheduled. - Delay (1872dt) = X (160dt) + Delay (100dt + extra 12dt) + Measure (1600dt). - """ - circuit = QuantumCircuit(2, 2) - circuit.x(0) - circuit.delay(100, 0, unit="dt") - circuit.measure(0, 0) - circuit.cx(0, 1) - circuit.measure(1, 1) - circuit.cx(0, 1) - circuit.measure(0, 0) - - pm = PassManager( - [ - # reproduce old behavior of 0.20.0 before #7655 - # currently default write latency is 0 - SetIOLatency(clbit_write_latency=1600, conditional_latency=0), - ALAPScheduleAnalysis(durations=self.instruction_durations), - ConstrainedReschedule(acquire_alignment=16), - PadDelay(), - ] - ) - - aligned_circuit = pm.run(circuit) - - ref_circuit = QuantumCircuit(2, 2) - ref_circuit.x(0) - ref_circuit.delay(112, 0, unit="dt") - ref_circuit.measure(0, 0) - ref_circuit.delay(160 + 112 + 1600, 1, unit="dt") - ref_circuit.cx(0, 1) - ref_circuit.delay(1600, 0, unit="dt") - ref_circuit.measure(1, 1) - ref_circuit.cx(0, 1) - ref_circuit.delay(1600, 1, unit="dt") - ref_circuit.measure(0, 0) - - self.assertEqual(aligned_circuit, ref_circuit) - - def test_alignment_is_not_processed(self): - """Test avoid pass processing if delay is aligned.""" - circuit = QuantumCircuit(2, 2) - circuit.x(0) - circuit.delay(160, 0, unit="dt") - circuit.measure(0, 0) - circuit.cx(0, 1) - circuit.measure(1, 1) - circuit.cx(0, 1) - circuit.measure(0, 0) - - # pre scheduling is not necessary because alignment is skipped - # this is to minimize breaking changes to existing code. - pm = PassManager() - - pm.append(InstructionDurationCheck(acquire_alignment=16)) - pm.run(circuit) - - self.assertFalse(pm.property_set["reschedule_required"]) - - def test_circuit_using_clbit(self): - """Test a circuit with instructions using a common clbit. - - (input) - ┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├────────────── - └───┘└────────────────┘└╥┘ ┌───┐ - q_1: ────────────────────────╫────┤ X ├────── - ║ └─╥─┘ ┌─┐ - q_2: ────────────────────────╫──────╫─────┤M├ - ║ ┌────╨────┐└╥┘ - c: 1/════════════════════════╩═╡ c_0 = T ╞═╩═ - 0 └─────────┘ 0 - - (aligned) - ┌───┐ ┌────────────────┐┌─┐┌────────────────┐ - q_0: ───────┤ X ├───────┤ Delay(112[dt]) ├┤M├┤ Delay(160[dt]) ├─── - ┌──────┴───┴──────┐└────────────────┘└╥┘└─────┬───┬──────┘ - q_1: ┤ Delay(1872[dt]) ├───────────────────╫───────┤ X ├────────── - └┬────────────────┤ ║ └─╥─┘ ┌─┐ - q_2: ─┤ Delay(432[dt]) ├───────────────────╫─────────╫─────────┤M├ - └────────────────┘ ║ ┌────╨────┐ └╥┘ - c: 1/══════════════════════════════════════╩════╡ c_0 = T ╞═════╩═ - 0 └─────────┘ 0 - - Looking at the q_0, the total schedule length T becomes - 160 (x) + 112 (aligned delay) + 1600 (measure) + 160 (delay) = 2032. - The last delay comes from ALAP scheduling called before the AlignMeasure pass, - which aligns stop times as late as possible, so the start time of x(1).c_if(0) - and the stop time of measure(0, 0) become T - 160. - """ - circuit = QuantumCircuit(3, 1) - circuit.x(0) - circuit.delay(100, 0, unit="dt") - circuit.measure(0, 0) - circuit.x(1).c_if(0, 1) - circuit.measure(2, 0) - - pm = PassManager( - [ - # reproduce old behavior of 0.20.0 before #7655 - # currently default write latency is 0 - SetIOLatency(clbit_write_latency=1600, conditional_latency=0), - ALAPScheduleAnalysis(durations=self.instruction_durations), - ConstrainedReschedule(acquire_alignment=16), - PadDelay(fill_very_end=False), - ] - ) - - aligned_circuit = pm.run(circuit) - - self.assertEqual(aligned_circuit.duration, 2032) - - ref_circuit = QuantumCircuit(3, 1) - ref_circuit.x(0) - ref_circuit.delay(112, 0, unit="dt") - ref_circuit.delay(1872, 1, unit="dt") # 2032 - 160 - ref_circuit.delay(432, 2, unit="dt") # 2032 - 1600 - ref_circuit.measure(0, 0) - ref_circuit.x(1).c_if(0, 1) - ref_circuit.measure(2, 0) - - self.assertEqual(aligned_circuit, ref_circuit) - - def test_programmed_delay_preserved(self): - """Intentionally programmed delay will be kept after reschedule. - - No delay - ++++++++ - - (input) - ┌────────────────┐┌───┐ ░ ┌───┐ - q_0: ┤ Delay(100[dt]) ├┤ X ├─░─┤ X ├ - ├────────────────┤└───┘ ░ └───┘ - q_1: ┤ Delay(272[dt]) ├──────░────── - └────────────────┘ ░ - - (aligned) - ┌────────────────┐┌───┐ ░ ┌───┐ - q_0: ┤ Delay(112[dt]) ├┤ X ├─░─┤ X ├ - ├────────────────┤└───┘ ░ └───┘ - q_1: ┤ Delay(272[dt]) ├──────░────── - └────────────────┘ ░ - - With delay (intentional post buffer) - ++++++++++++++++++++++++++++++++++++ - - (input) ... this is identical to no delay pattern without reschedule - ┌────────────────┐┌───┐┌───────────────┐ ░ ┌───┐ - q_0: ┤ Delay(100[dt]) ├┤ X ├┤ Delay(10[dt]) ├─░─┤ X ├ - ├────────────────┤└───┘└───────────────┘ ░ └───┘ - q_1: ┤ Delay(272[dt]) ├───────────────────────░────── - └────────────────┘ ░ - - (aligned) - ┌────────────────┐┌───┐┌───────────────┐ ░ ┌──────────────┐┌───┐ - q_0: ┤ Delay(112[dt]) ├┤ X ├┤ Delay(10[dt]) ├─░─┤ Delay(6[dt]) ├┤ X ├ - ├────────────────┤└───┘└───────────────┘ ░ └──────────────┘└───┘ - q_1: ┤ Delay(282[dt]) ├───────────────────────░────────────────────── - └────────────────┘ ░ - - """ - - pm = PassManager( - [ - ASAPScheduleAnalysis(durations=self.instruction_durations), - ConstrainedReschedule(pulse_alignment=16), - PadDelay(fill_very_end=False), - ] - ) - - pm_only_schedule = PassManager( - [ - ASAPScheduleAnalysis(durations=self.instruction_durations), - PadDelay(fill_very_end=False), - ] - ) - - circuit_no_delay = QuantumCircuit(2) - circuit_no_delay.delay(100, 0, unit="dt") - circuit_no_delay.x(0) # q0 ends here at t = 260, t = 260 - 272 is free - circuit_no_delay.delay(160 + 112, 1, unit="dt") - circuit_no_delay.barrier() # q0 and q1 is aligned here at t = 272 dt - circuit_no_delay.x(0) - - ref_no_delay = QuantumCircuit(2) - ref_no_delay.delay(112, 0, unit="dt") - ref_no_delay.x(0) - ref_no_delay.delay(160 + 100 + 12, 1, unit="dt") # this t0 doesn't change - ref_no_delay.barrier() - ref_no_delay.x(0) # no buffer - - self.assertEqual(pm.run(circuit_no_delay), ref_no_delay) - - circuit_with_delay = QuantumCircuit(2) - circuit_with_delay.delay(100, 0, unit="dt") - circuit_with_delay.x(0) # q0 ends here at t = 260 - circuit_with_delay.delay(10, 0, unit="dt") # intentional post buffer of 10 dt to next X(0) - circuit_with_delay.delay(160 + 112, 1, unit="dt") # q0 and q1 is aligned here at t = 272 dt - circuit_with_delay.barrier() - circuit_with_delay.x(0) - - ref_with_delay = QuantumCircuit(2) - ref_with_delay.delay(112, 0, unit="dt") - ref_with_delay.x(0) - ref_with_delay.delay(10, 0, unit="dt") # this delay survive - ref_with_delay.delay(160 + 100 + 12 + 10, 1, unit="dt") - ref_with_delay.barrier() - ref_with_delay.delay(6, 0, unit="dt") # extra delay for next X0 - ref_with_delay.x(0) # at least 10dt buffer is preserved - - self.assertEqual(pm.run(circuit_with_delay), ref_with_delay) - - # check if circuit is identical without reschedule - self.assertEqual( - pm_only_schedule.run(circuit_no_delay), - pm_only_schedule.run(circuit_with_delay), - ) - - def test_both_pulse_and_acquire_alignment(self): - """Test when both acquire and pulse alignment are specified. - - (input) - ┌────────────────┐┌───┐┌───────────────┐┌─┐ - q: ┤ Delay(100[dt]) ├┤ X ├┤ Delay(10[dt]) ├┤M├ - └────────────────┘└───┘└───────────────┘└╥┘ - c: 1/═════════════════════════════════════════╩═ - 0 - - (aligned) - ┌────────────────┐┌───┐┌───────────────┐┌─┐ - q: ┤ Delay(112[dt]) ├┤ X ├┤ Delay(16[dt]) ├┤M├ - └────────────────┘└───┘└───────────────┘└╥┘ - c: 1/═════════════════════════════════════════╩═ - 0 - """ - pm = PassManager( - [ - ALAPScheduleAnalysis(durations=self.instruction_durations), - ConstrainedReschedule(pulse_alignment=16, acquire_alignment=16), - PadDelay(fill_very_end=False), - ] - ) - - circuit = QuantumCircuit(1, 1) - circuit.delay(100, 0, unit="dt") - circuit.x(0) - circuit.delay(10, 0, unit="dt") - circuit.measure(0, 0) - - ref_circ = QuantumCircuit(1, 1) - ref_circ.delay(112, 0, unit="dt") - ref_circ.x(0) - ref_circ.delay(16, 0, unit="dt") - ref_circ.measure(0, 0) - - self.assertEqual(pm.run(circuit), ref_circ) - - def test_deprecated_align_measure(self): - """Test if old AlignMeasures can be still used and warning is raised.""" - circuit = QuantumCircuit(1, 1) - circuit.x(0) - circuit.delay(100) - circuit.measure(0, 0) - - with self.assertWarns(PendingDeprecationWarning): - pm_old = PassManager( - [ - ALAPSchedule(durations=self.instruction_durations), - AlignMeasures(alignment=16), - ] - ) - - pm_new = PassManager( - [ - ALAPSchedule(durations=self.instruction_durations), - AlignMeasures(alignment=16), - ] - ) - - self.assertEqual(pm_old.run(circuit), pm_new.run(circuit)) +from qiskit.transpiler.passes import ValidatePulseGates class TestPulseGateValidation(QiskitTestCase):