From a3c43fe659b77a3f8fe4a67e1a485794965d601f Mon Sep 17 00:00:00 2001 From: Raghav-Bell <raghavsingla327@gmail.com> Date: Fri, 1 Sep 2023 12:11:22 +0530 Subject: [PATCH 01/17] Removed code and tests deprecated in 0.21 --- qiskit/algorithms/optimizers/spsa.py | 35 +- qiskit/execute_function.py | 19 +- qiskit/pulse/transforms/alignments.py | 25 +- qiskit/qpy/interface.py | 2 - qiskit/transpiler/passes/__init__.py | 6 - .../transpiler/passes/scheduling/__init__.py | 5 +- 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 | 276 ------ qiskit/transpiler/passes/utils/__init__.py | 2 - .../passes/utils/check_cx_direction.py | 27 - .../transpiler/passes/utils/cx_direction.py | 27 - test/benchmarks/scheduling_passes.py | 105 +-- .../test_instruction_alignments.py | 310 +------ .../legacy_scheduling/test_scheduling_pass.py | 811 ------------------ .../transpiler/test_instruction_alignments.py | 503 +---------- 18 files changed, 9 insertions(+), 2733 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 delete mode 100644 test/python/transpiler/legacy_scheduling/test_scheduling_pass.py diff --git a/qiskit/algorithms/optimizers/spsa.py b/qiskit/algorithms/optimizers/spsa.py index 0226f8a00a1e..6b5098e5cb49 100644 --- a/qiskit/algorithms/optimizers/spsa.py +++ b/qiskit/algorithms/optimizers/spsa.py @@ -27,7 +27,7 @@ import numpy as np from qiskit.utils import algorithm_globals -from qiskit.utils.deprecation import deprecate_func + from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT @@ -649,39 +649,6 @@ def get_support_level(self): } # pylint: disable=bad-docstring-quotes - @deprecate_func( - additional_msg=( - "Instead, use ``SPSA.minimize`` as a replacement, which supports the same arguments " - "but follows the interface of scipy.optimize and returns a complete result object " - "containing additional information." - ), - since="0.21.0", - ) - def optimize( - self, - num_vars, # pylint: disable=unused-argument - objective_function, - gradient_function=None, # pylint: disable=unused-argument - variable_bounds=None, # pylint: disable=unused-argument - initial_point=None, - ): - """Perform optimization. - - Args: - num_vars (int): Number of parameters to be optimized. - objective_function (callable): A function that computes the objective function. - gradient_function (callable): Not supported for SPSA. - variable_bounds (list[(float, float)]): Not supported for SPSA. - initial_point (numpy.ndarray[float]): Initial point. - - Returns: - tuple: point, value, nfev - point: is a 1D numpy.ndarray[float] containing the solution - value: is a float with the objective function value - nfev: number of objective function calls made if available or None - """ - result = self.minimize(objective_function, initial_point) - return result.x, result.fun, result.nfev def bernoulli_perturbation(dim, perturbation_dims=None): diff --git a/qiskit/execute_function.py b/qiskit/execute_function.py index 51ecc68246b5..c1407a67fa11 100644 --- a/qiskit/execute_function.py +++ b/qiskit/execute_function.py @@ -26,7 +26,7 @@ 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 +36,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 +46,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, @@ -160,16 +156,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 @@ -280,8 +266,7 @@ 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 d19def4f34b8..d5a1f62e8dce 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.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 @@ def align(self, schedule: Schedule) -> Schedule: """ 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 @@ def align(self, schedule: Schedule) -> Schedule: 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 @@ def align(self, schedule: Schedule) -> Schedule: 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 a22ce5f20db3..a8fd9380bdbc 100644 --- a/qiskit/qpy/interface.py +++ b/qiskit/qpy/interface.py @@ -25,7 +25,6 @@ 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_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 73e6ea055165..0487dfef892e 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -263,11 +263,7 @@ 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 @@ -276,8 +272,6 @@ # 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 6283faff0001..4f217f9fd531 100644 --- a/qiskit/transpiler/passes/scheduling/__init__.py +++ b/qiskit/transpiler/passes/scheduling/__init__.py @@ -12,9 +12,7 @@ """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 +22,3 @@ 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 063b94611bdc..000000000000 --- 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], []) - idle_before[bit] = t1 - - new_dag.apply_operation_front(node.op, node.qargs, node.cargs) - - 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], []) - - 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 513144937ab5..a25ec01bc1cf 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/__init__.py +++ b/qiskit/transpiler/passes/scheduling/alignments/__init__.py @@ -78,4 +78,3 @@ 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 164e9557fbb7..000000000000 --- 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]) - - 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) - - 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 cebc32af71a8..000000000000 --- 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 5b17ca52d190..000000000000 --- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +++ /dev/null @@ -1,276 +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 - 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) - timeline_drawer(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) - timeline_drawer(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 gate in self._dd_sequence: - 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) - 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) - 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) - 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) - 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) - 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]) - if gate is not None: - new_dag.apply_operation_back(gate, [dag_qubit]) - - 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 c974d292fb02..c5d605f7d732 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 2726d2e26e74..000000000000 --- 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 e93bf0d69e79..000000000000 --- 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/test/benchmarks/scheduling_passes.py b/test/benchmarks/scheduling_passes.py index 3d97ecdf67d3..baa7105ffbad 100644 --- a/test/benchmarks/scheduling_passes.py +++ b/test/benchmarks/scheduling_passes.py @@ -13,19 +13,8 @@ # pylint: disable=invalid-name,missing-docstring # pylint: disable=attribute-defined-outside-init -from qiskit import transpile -from qiskit.circuit.library.standard_gates import XGate -from qiskit.transpiler import CouplingMap -from qiskit.transpiler import InstructionDurations -from qiskit.transpiler.passes import ( - TimeUnitConversion, - ASAPSchedule, - ALAPSchedule, - DynamicalDecoupling, -) -from qiskit.converters import circuit_to_dag -from .utils import random_circuit +from qiskit.transpiler.passes import TimeUnitConversion class SchedulingPassBenchmarks: @@ -34,97 +23,5 @@ class SchedulingPassBenchmarks: param_names = ["n_qubits", "depth"] timeout = 300 - def setup(self, n_qubits, depth): - seed = 42 - self.circuit = random_circuit( - n_qubits, depth, measure=True, conditional=True, reset=True, seed=seed, max_operands=2 - ) - self.basis_gates = ["rz", "sx", "x", "cx", "id", "reset"] - self.cmap = [ - [0, 1], - [1, 0], - [1, 2], - [1, 6], - [2, 1], - [2, 3], - [3, 2], - [3, 4], - [3, 8], - [4, 3], - [5, 6], - [5, 10], - [6, 1], - [6, 5], - [6, 7], - [7, 6], - [7, 8], - [7, 12], - [8, 3], - [8, 7], - [8, 9], - [9, 8], - [9, 14], - [10, 5], - [10, 11], - [11, 10], - [11, 12], - [11, 16], - [12, 7], - [12, 11], - [12, 13], - [13, 12], - [13, 14], - [13, 18], - [14, 9], - [14, 13], - [15, 16], - [16, 11], - [16, 15], - [16, 17], - [17, 16], - [17, 18], - [18, 13], - [18, 17], - [18, 19], - [19, 18], - ] - self.coupling_map = CouplingMap(self.cmap) - self.transpiled_circuit = transpile( - self.circuit, - basis_gates=self.basis_gates, - coupling_map=self.coupling_map, - optimization_level=1, - ) - self.dag = circuit_to_dag(self.transpiled_circuit) - self.durations = InstructionDurations( - [ - ("rz", None, 0), - ("id", None, 160), - ("sx", None, 160), - ("x", None, 160), - ("cx", None, 800), - ("measure", None, 3200), - ("reset", None, 3600), - ], - 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) - 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) - - def time_asap_schedule_pass(self, _, __): - _pass = ASAPSchedule(self.durations) - _pass.property_set["time_unit"] = "dt" - _pass.run(self.timed_dag) - - def time_dynamical_decoupling_pass(self, _, __): - DynamicalDecoupling(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 efd3b86ee393..feb50bab8002 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 2f375c46f67b..000000000000 --- 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 74daf8d67ab4..7e2fb6e05003 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): From 3d902401e5ffc15b67fa005bd010d2ee788163f0 Mon Sep 17 00:00:00 2001 From: Raghav-Bell <raghavsingla327@gmail.com> Date: Fri, 1 Sep 2023 22:45:53 +0530 Subject: [PATCH 02/17] Undone removal of optimizers/spsa. --- qiskit/algorithms/optimizers/spsa.py | 33 +++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/qiskit/algorithms/optimizers/spsa.py b/qiskit/algorithms/optimizers/spsa.py index 6b5098e5cb49..eb107c98a216 100644 --- a/qiskit/algorithms/optimizers/spsa.py +++ b/qiskit/algorithms/optimizers/spsa.py @@ -27,7 +27,7 @@ import numpy as np from qiskit.utils import algorithm_globals - +from qiskit.utils.deprecation import deprecate_func from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT @@ -649,6 +649,37 @@ def get_support_level(self): } # pylint: disable=bad-docstring-quotes + @deprecate_func( + additional_msg=( + "Instead, use ``SPSA.minimize`` as a replacement, which supports the same arguments " + "but follows the interface of scipy.optimize and returns a complete result object " + "containing additional information." + ), + since="0.21.0", + ) + def optimize( + self, + num_vars, # pylint: disable=unused-argument + objective_function, + gradient_function=None, # pylint: disable=unused-argument + variable_bounds=None, # pylint: disable=unused-argument + initial_point=None, + ): + """Perform optimization. + Args: + num_vars (int): Number of parameters to be optimized. + objective_function (callable): A function that computes the objective function. + gradient_function (callable): Not supported for SPSA. + variable_bounds (list[(float, float)]): Not supported for SPSA. + initial_point (numpy.ndarray[float]): Initial point. + Returns: + tuple: point, value, nfev + point: is a 1D numpy.ndarray[float] containing the solution + value: is a float with the objective function value + nfev: number of objective function calls made if available or None + """ + result = self.minimize(objective_function, initial_point) + return result.x, result.fun, result.nfev def bernoulli_perturbation(dim, perturbation_dims=None): From 6e2b394fb90c4defb944c0592e2d25259efc2c4c Mon Sep 17 00:00:00 2001 From: Luciano Bello <bel@zurich.ibm.com> Date: Sat, 2 Sep 2023 23:08:25 +0200 Subject: [PATCH 03/17] undo changes in spsa --- qiskit/algorithms/optimizers/spsa.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit/algorithms/optimizers/spsa.py b/qiskit/algorithms/optimizers/spsa.py index eb107c98a216..0226f8a00a1e 100644 --- a/qiskit/algorithms/optimizers/spsa.py +++ b/qiskit/algorithms/optimizers/spsa.py @@ -666,12 +666,14 @@ def optimize( initial_point=None, ): """Perform optimization. + Args: num_vars (int): Number of parameters to be optimized. objective_function (callable): A function that computes the objective function. gradient_function (callable): Not supported for SPSA. variable_bounds (list[(float, float)]): Not supported for SPSA. initial_point (numpy.ndarray[float]): Initial point. + Returns: tuple: point, value, nfev point: is a 1D numpy.ndarray[float] containing the solution From 2e20b6b0d20ded2ad306f0fa470f83522cde5c29 Mon Sep 17 00:00:00 2001 From: Raghav-Bell <raghavsingla327@gmail.com> Date: Tue, 5 Sep 2023 00:19:42 +0530 Subject: [PATCH 04/17] Added release note. --- ...oved_deprecated_0.21-6c93f7bbc50ae40e.yaml | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml 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 000000000000..f6414edaf7ac --- /dev/null +++ b/releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml @@ -0,0 +1,48 @@ +--- +upgrade: + - | + Deprecated code and tests in 0.21 are removed. + :func:`~qiskit.execute_function.execute` doesn't support + `qobj_id` and `qobj_header` arguments. + + - | + Deprecated code and tests in 0.21 are removed. + :class:`.transpiler.passes.ALAPSchedule` is removed instead + use :class:`.transpiler.passes.ALAPScheduleAnalysis`. + + - | + Deprecated code and tests in 0.21 are removed. + :class:`.transpiler.passes.ASAPSchedule` is removed instead + use :class:`.transpiler.passes.ASAPScheduleAnalysis`. + + - | + Deprecated code and tests in 0.21 are removed. + :class:`.transpiler.passes.DynamicalDecoupling` is removed instead + use :class:`.transpiler.passes.PadDynamicalDecoupling`. + + - | + Deprecated code and tests in 0.21 are removed. + :class:`.transpiler.passes.AlignMeasures` is removed instead + use :class:`.transpiler.passes.ConstrainedReschedule`. + + - | + Deprecated code and tests in 0.21 are removed. + :class:`.transpiler.passes.CXDirection` is removed instead + use generic :class:`.transpiler.passes.GateDirection`. + + - | + Deprecated code and tests in 0.21 are removed. + :class:`.transpiler.passes.CheckCXDirection` is removed instead + use generic :class:`.transpiler.passes.CheckGateDirection`. + + - | + Deprecated code and tests in 0.21 are removed. + Classes :class:`.pulse.transforms.AlignmentKind`, :class:`.pulse.transforms.AlignEquispaced`, + and :class:`.pulse.transforms.AlignFunc` don't support :meth:`to_dict`. + + - | + Deprecated code and tests in 0.21 are removed. + In :func:`~qiskit.qpy.dump` argument `circuits` is aliased with `programs`. + Refer to `#10748 <https://github.com/Qiskit/qiskit/issues/10748>` + + From 3621d5567629ce6a9bc675be7a4270bd401003e7 Mon Sep 17 00:00:00 2001 From: Raghav-Bell <raghavsingla327@gmail.com> Date: Wed, 6 Sep 2023 00:21:03 +0530 Subject: [PATCH 05/17] Removed deprecated classes from docstrings. --- qiskit/transpiler/passes/__init__.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 0487dfef892e..d984211332fd 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -112,13 +112,10 @@ PadDynamicalDecoupling PadDelay ConstrainedReschedule - AlignMeasures ValidatePulseGates InstructionDurationCheck SetIOLatency - ALAPSchedule - ASAPSchedule - DynamicalDecoupling + Circuit Analysis ================ @@ -162,9 +159,7 @@ :toctree: ../stubs/ CheckMap - CheckCXDirection CheckGateDirection - CXDirection GateDirection MergeAdjacentBarriers RemoveBarriers From d39ea20e7c65556fd73200061d360ec2c16d6753 Mon Sep 17 00:00:00 2001 From: Raghav-Bell <raghavsingla327@gmail.com> Date: Sat, 16 Sep 2023 23:13:02 +0530 Subject: [PATCH 06/17] Improved release note. --- .../removed_deprecated_0.21-6c93f7bbc50ae40e.yaml | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml b/releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml index f6414edaf7ac..e879ef4968fb 100644 --- a/releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml +++ b/releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml @@ -6,43 +6,30 @@ upgrade: `qobj_id` and `qobj_header` arguments. - | - Deprecated code and tests in 0.21 are removed. :class:`.transpiler.passes.ALAPSchedule` is removed instead use :class:`.transpiler.passes.ALAPScheduleAnalysis`. - | - Deprecated code and tests in 0.21 are removed. :class:`.transpiler.passes.ASAPSchedule` is removed instead use :class:`.transpiler.passes.ASAPScheduleAnalysis`. - | - Deprecated code and tests in 0.21 are removed. :class:`.transpiler.passes.DynamicalDecoupling` is removed instead use :class:`.transpiler.passes.PadDynamicalDecoupling`. - | - Deprecated code and tests in 0.21 are removed. :class:`.transpiler.passes.AlignMeasures` is removed instead use :class:`.transpiler.passes.ConstrainedReschedule`. - | - Deprecated code and tests in 0.21 are removed. :class:`.transpiler.passes.CXDirection` is removed instead use generic :class:`.transpiler.passes.GateDirection`. - | - Deprecated code and tests in 0.21 are removed. :class:`.transpiler.passes.CheckCXDirection` is removed instead use generic :class:`.transpiler.passes.CheckGateDirection`. - | - Deprecated code and tests in 0.21 are removed. - Classes :class:`.pulse.transforms.AlignmentKind`, :class:`.pulse.transforms.AlignEquispaced`, + :class:`.pulse.transforms.AlignmentKind`, `.pulse.transforms.AlignEquispaced`, and :class:`.pulse.transforms.AlignFunc` don't support :meth:`to_dict`. - - - | - Deprecated code and tests in 0.21 are removed. - In :func:`~qiskit.qpy.dump` argument `circuits` is aliased with `programs`. - Refer to `#10748 <https://github.com/Qiskit/qiskit/issues/10748>` - From 534d12ebe11829ee1fd1d38a977e89757e958297 Mon Sep 17 00:00:00 2001 From: Raghav-Bell <raghavsingla327@gmail.com> Date: Sun, 17 Sep 2023 10:07:15 +0530 Subject: [PATCH 07/17] Added removed files to resolve conflict. --- qiskit/transpiler/passes/scheduling/alap.py | 155 ++++++++++ .../scheduling/alignments/align_measures.py | 256 ++++++++++++++++ .../passes/scheduling/dynamical_decoupling.py | 276 ++++++++++++++++++ 3 files changed, 687 insertions(+) create mode 100644 qiskit/transpiler/passes/scheduling/alap.py create mode 100644 qiskit/transpiler/passes/scheduling/alignments/align_measures.py create mode 100644 qiskit/transpiler/passes/scheduling/dynamical_decoupling.py diff --git a/qiskit/transpiler/passes/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/alap.py new file mode 100644 index 000000000000..9ee0f4988b4a --- /dev/null +++ b/qiskit/transpiler/passes/scheduling/alap.py @@ -0,0 +1,155 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""ALAP Scheduling.""" + +from qiskit.circuit import Delay, Qubit, Measure +from qiskit.dagcircuit import DAGCircuit +from qiskit.transpiler.exceptions import TranspilerError +from qiskit.utils.deprecation import deprecate_func + +from .base_scheduler import BaseSchedulerTransform + + +class ALAPSchedule(BaseSchedulerTransform): + """ALAP Scheduling pass, which schedules the **stop** time of instructions as late as possible. + + See :class:`~qiskit.transpiler.passes.scheduling.base_scheduler.BaseSchedulerTransform` for the + detailed behavior of the control flow operation, i.e. ``c_if``. + """ + + @deprecate_func( + additional_msg=( + "Instead, use :class:`~.ALAPScheduleAnalysis`, which is an " + "analysis pass that requires a padding pass to later modify the circuit." + ), + since="0.21.0", + pending=True, + ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def run(self, dag): + """Run the ALAPSchedule pass on `dag`. + + Args: + dag (DAGCircuit): DAG to schedule. + + Returns: + DAGCircuit: A scheduled DAG. + + Raises: + TranspilerError: if the circuit is not mapped on physical qubits. + TranspilerError: if conditional bit is added to non-supported instruction. + """ + if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: + raise TranspilerError("ALAP schedule runs on physical circuits only") + + time_unit = self.property_set["time_unit"] + new_dag = DAGCircuit() + for qreg in dag.qregs.values(): + new_dag.add_qreg(qreg) + for creg in dag.cregs.values(): + new_dag.add_creg(creg) + + idle_before = {q: 0 for q in dag.qubits + dag.clbits} + for node in reversed(list(dag.topological_op_nodes())): + op_duration = self._get_node_duration(node, dag) + + # compute t0, t1: instruction interval, note that + # t0: start time of instruction + # t1: end time of instruction + + # since this is alap scheduling, node is scheduled in reversed topological ordering + # and nodes are packed from the very end of the circuit. + # the physical meaning of t0 and t1 is flipped here. + if isinstance(node.op, self.CONDITIONAL_SUPPORTED): + t0q = max(idle_before[q] for q in node.qargs) + if node.op.condition_bits: + # conditional is bit tricky due to conditional_latency + t0c = max(idle_before[c] for c in node.op.condition_bits) + # Assume following case (t0c > t0q): + # + # |t0q + # Q ░░░░░░░░░░░░░▒▒▒ + # C ░░░░░░░░▒▒▒▒▒▒▒▒ + # |t0c + # + # In this case, there is no actual clbit read before gate. + # + # |t0q' = t0c - conditional_latency + # Q ░░░░░░░░▒▒▒░░▒▒▒ + # C ░░░░░░▒▒▒▒▒▒▒▒▒▒ + # |t1c' = t0c + conditional_latency + # + # rather than naively doing + # + # |t1q' = t0c + duration + # Q ░░░░░▒▒▒░░░░░▒▒▒ + # C ░░▒▒░░░░▒▒▒▒▒▒▒▒ + # |t1c' = t0c + duration + conditional_latency + # + t0 = max(t0q, t0c - op_duration) + t1 = t0 + op_duration + for clbit in node.op.condition_bits: + idle_before[clbit] = t1 + self.conditional_latency + else: + t0 = t0q + t1 = t0 + op_duration + else: + if node.op.condition_bits: + raise TranspilerError( + f"Conditional instruction {node.op.name} is not supported in ALAP scheduler." + ) + + if isinstance(node.op, Measure): + # clbit time is always right (alap) justified + t0 = max(idle_before[bit] for bit in node.qargs + node.cargs) + t1 = t0 + op_duration + # + # |t1 = t0 + duration + # Q ░░░░░▒▒▒▒▒▒▒▒▒▒▒ + # C ░░░░░░░░░▒▒▒▒▒▒▒ + # |t0 + (duration - clbit_write_latency) + # + for clbit in node.cargs: + idle_before[clbit] = t0 + (op_duration - self.clbit_write_latency) + else: + # It happens to be directives such as barrier + t0 = max(idle_before[bit] for bit in node.qargs + node.cargs) + t1 = t0 + op_duration + + for bit in node.qargs: + delta = t0 - idle_before[bit] + if delta > 0 and self._delay_supported(dag.find_bit(bit).index): + new_dag.apply_operation_front(Delay(delta, time_unit), [bit], [], check=False) + idle_before[bit] = t1 + + new_dag.apply_operation_front(node.op, node.qargs, node.cargs, check=False) + + circuit_duration = max(idle_before.values()) + for bit, before in idle_before.items(): + delta = circuit_duration - before + if not (delta > 0 and isinstance(bit, Qubit)): + continue + if self._delay_supported(dag.find_bit(bit).index): + new_dag.apply_operation_front(Delay(delta, time_unit), [bit], [], check=False) + + new_dag.name = dag.name + new_dag.metadata = dag.metadata + new_dag.calibrations = dag.calibrations + + # set circuit duration and unit to indicate it is scheduled + new_dag.duration = circuit_duration + new_dag.unit = time_unit + + return new_dag diff --git a/qiskit/transpiler/passes/scheduling/alignments/align_measures.py b/qiskit/transpiler/passes/scheduling/alignments/align_measures.py new file mode 100644 index 000000000000..668d65f6abd5 --- /dev/null +++ b/qiskit/transpiler/passes/scheduling/alignments/align_measures.py @@ -0,0 +1,256 @@ +# 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/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py new file mode 100644 index 000000000000..7e5245b2e3fc --- /dev/null +++ b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py @@ -0,0 +1,276 @@ +# 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 + 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) + timeline_drawer(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) + timeline_drawer(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 gate in self._dd_sequence: + 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 From d169e7291e8c37e93f0a83362c60877b028b3b8e Mon Sep 17 00:00:00 2001 From: Raghav-Bell <raghavsingla327@gmail.com> Date: Sun, 17 Sep 2023 10:13:33 +0530 Subject: [PATCH 08/17] Removed the conflicting files --- qiskit/transpiler/passes/scheduling/alap.py | 155 ---------- .../scheduling/alignments/align_measures.py | 256 ---------------- .../passes/scheduling/dynamical_decoupling.py | 276 ------------------ 3 files changed, 687 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/dynamical_decoupling.py diff --git a/qiskit/transpiler/passes/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/alap.py deleted file mode 100644 index 9ee0f4988b4a..000000000000 --- 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/align_measures.py b/qiskit/transpiler/passes/scheduling/alignments/align_measures.py deleted file mode 100644 index 668d65f6abd5..000000000000 --- 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/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py deleted file mode 100644 index 7e5245b2e3fc..000000000000 --- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +++ /dev/null @@ -1,276 +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 - 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) - timeline_drawer(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) - timeline_drawer(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 gate in self._dd_sequence: - 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 From 79aa95e9f780010ad3a38905a3782990131b8a88 Mon Sep 17 00:00:00 2001 From: Raghav-Bell <raghavsingla327@gmail.com> Date: Tue, 19 Sep 2023 23:19:55 +0530 Subject: [PATCH 09/17] Added conflicting file but with no code --- qiskit/transpiler/passes/scheduling/alap.py | 0 qiskit/transpiler/passes/scheduling/alignments/align_measures.py | 0 qiskit/transpiler/passes/scheduling/dynamical_decoupling.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 qiskit/transpiler/passes/scheduling/alap.py create mode 100644 qiskit/transpiler/passes/scheduling/alignments/align_measures.py create mode 100644 qiskit/transpiler/passes/scheduling/dynamical_decoupling.py diff --git a/qiskit/transpiler/passes/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/alap.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/qiskit/transpiler/passes/scheduling/alignments/align_measures.py b/qiskit/transpiler/passes/scheduling/alignments/align_measures.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py new file mode 100644 index 000000000000..e69de29bb2d1 From a2b745e4710e0f68859f16a9a6cfacc046ffd418 Mon Sep 17 00:00:00 2001 From: Raghav-Bell <raghavsingla327@gmail.com> Date: Tue, 19 Sep 2023 23:44:32 +0530 Subject: [PATCH 10/17] Align measure class file --- qiskit/transpiler/passes/scheduling/alignments/align_measures.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 qiskit/transpiler/passes/scheduling/alignments/align_measures.py 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 e69de29bb2d1..000000000000 From 2eb2d256c01611f0052526a2d8b8949ea7c997c8 Mon Sep 17 00:00:00 2001 From: Raghav-Bell <raghavsingla327@gmail.com> Date: Tue, 19 Sep 2023 23:50:06 +0530 Subject: [PATCH 11/17] Removed alap dynamical coupling class --- qiskit/transpiler/passes/scheduling/alap.py | 0 qiskit/transpiler/passes/scheduling/dynamical_decoupling.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 qiskit/transpiler/passes/scheduling/alap.py delete mode 100644 qiskit/transpiler/passes/scheduling/dynamical_decoupling.py diff --git a/qiskit/transpiler/passes/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/alap.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py deleted file mode 100644 index e69de29bb2d1..000000000000 From f249e6a2b844bf3f6bc060c7206e93c2e36332a9 Mon Sep 17 00:00:00 2001 From: Raghav-Bell <raghavsingla327@gmail.com> Date: Wed, 20 Sep 2023 10:33:17 +0530 Subject: [PATCH 12/17] Resolving colflict with dynamical coupling --- .../passes/scheduling/dynamical_decoupling.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 qiskit/transpiler/passes/scheduling/dynamical_decoupling.py diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py new file mode 100644 index 000000000000..5766cbe7ca1b --- /dev/null +++ b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py @@ -0,0 +1,12 @@ +# 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. + From c46e974c5bcb987f72d16589526161c793aff847 Mon Sep 17 00:00:00 2001 From: Raghav-Bell <raghavsingla327@gmail.com> Date: Wed, 20 Sep 2023 10:35:10 +0530 Subject: [PATCH 13/17] Deleted dynamical coupling --- .../passes/scheduling/dynamical_decoupling.py | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 qiskit/transpiler/passes/scheduling/dynamical_decoupling.py diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py deleted file mode 100644 index 5766cbe7ca1b..000000000000 --- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +++ /dev/null @@ -1,12 +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. - From f91648bd11aedb280a0cac5546f15b285338932a Mon Sep 17 00:00:00 2001 From: Raghav-Bell <raghavsingla327@gmail.com> Date: Sat, 23 Sep 2023 12:58:43 +0530 Subject: [PATCH 14/17] Fixed release note. --- ...oved_deprecated_0.21-6c93f7bbc50ae40e.yaml | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml b/releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml index e879ef4968fb..5f6fb2dac798 100644 --- a/releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml +++ b/releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml @@ -1,35 +1,52 @@ --- upgrade: - | - Deprecated code and tests in 0.21 are removed. - :func:`~qiskit.execute_function.execute` doesn't support - `qobj_id` and `qobj_header` arguments. + 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. - | - :class:`.transpiler.passes.ALAPSchedule` is removed instead - use :class:`.transpiler.passes.ALAPScheduleAnalysis`. + 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. - | - :class:`.transpiler.passes.ASAPSchedule` is removed instead - use :class:`.transpiler.passes.ASAPScheduleAnalysis`. + 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. - | - :class:`.transpiler.passes.DynamicalDecoupling` is removed instead - use :class:`.transpiler.passes.PadDynamicalDecoupling`. + 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. - | - :class:`.transpiler.passes.AlignMeasures` is removed instead - use :class:`.transpiler.passes.ConstrainedReschedule`. + 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. - | - :class:`.transpiler.passes.CXDirection` is removed instead - use generic :class:`.transpiler.passes.GateDirection`. + 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. - | - :class:`.transpiler.passes.CheckCXDirection` is removed instead - use generic :class:`.transpiler.passes.CheckGateDirection`. + 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. - | - :class:`.pulse.transforms.AlignmentKind`, `.pulse.transforms.AlignEquispaced`, - and :class:`.pulse.transforms.AlignFunc` don't support :meth:`to_dict`. + 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. From 267a43de6f769bbe9bb11e39567a41995a480ffb Mon Sep 17 00:00:00 2001 From: Luciano Bello <bel@zurich.ibm.com> Date: Wed, 18 Oct 2023 12:48:29 +0200 Subject: [PATCH 15/17] Apply suggestions from code review --- qiskit/execute_function.py | 1 - qiskit/transpiler/passes/__init__.py | 1 - qiskit/transpiler/passes/scheduling/__init__.py | 1 - .../notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml | 8 -------- 4 files changed, 11 deletions(-) diff --git a/qiskit/execute_function.py b/qiskit/execute_function.py index b7f747331069..b9dcb41118a1 100644 --- a/qiskit/execute_function.py +++ b/qiskit/execute_function.py @@ -265,7 +265,6 @@ def execute( job = execute(qc, backend, shots=4321) """ - if isinstance(experiments, (Schedule, ScheduleBlock)) or ( isinstance(experiments, list) and isinstance(experiments[0], (Schedule, ScheduleBlock)) ): diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index f081ed379798..f461f335373e 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -117,7 +117,6 @@ ValidatePulseGates InstructionDurationCheck SetIOLatency - Circuit Analysis ================ diff --git a/qiskit/transpiler/passes/scheduling/__init__.py b/qiskit/transpiler/passes/scheduling/__init__.py index 4f217f9fd531..69485e88e4cf 100644 --- a/qiskit/transpiler/passes/scheduling/__init__.py +++ b/qiskit/transpiler/passes/scheduling/__init__.py @@ -12,7 +12,6 @@ """Module containing circuit scheduling passes.""" - from .scheduling import ALAPScheduleAnalysis, ASAPScheduleAnalysis, SetIOLatency from .time_unit_conversion import TimeUnitConversion from .padding import PadDelay, PadDynamicalDecoupling diff --git a/releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml b/releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml index 5f6fb2dac798..5f609ad91b89 100644 --- a/releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml +++ b/releasenotes/notes/removed_deprecated_0.21-6c93f7bbc50ae40e.yaml @@ -4,46 +4,38 @@ 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 From d95b3a2947aec5d92344c14bd256d9a7461f87e6 Mon Sep 17 00:00:00 2001 From: Luciano Bello <bel@zurich.ibm.com> Date: Wed, 18 Oct 2023 12:48:51 +0200 Subject: [PATCH 16/17] Update qiskit/execute_function.py --- qiskit/execute_function.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/execute_function.py b/qiskit/execute_function.py index b9dcb41118a1..c50b6dcf2a50 100644 --- a/qiskit/execute_function.py +++ b/qiskit/execute_function.py @@ -27,7 +27,6 @@ from qiskit.pulse import Schedule, ScheduleBlock from qiskit.exceptions import QiskitError - logger = logging.getLogger(__name__) From bb1ddf578e956ef770ac059a410dcb8076452b90 Mon Sep 17 00:00:00 2001 From: Luciano Bello <luciano@debian.org> Date: Wed, 18 Oct 2023 13:00:53 +0200 Subject: [PATCH 17/17] recover benchmark test --- test/benchmarks/scheduling_passes.py | 122 ++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/test/benchmarks/scheduling_passes.py b/test/benchmarks/scheduling_passes.py index baa7105ffbad..a4c25dc46bcb 100644 --- a/test/benchmarks/scheduling_passes.py +++ b/test/benchmarks/scheduling_passes.py @@ -13,8 +13,19 @@ # pylint: disable=invalid-name,missing-docstring # pylint: disable=attribute-defined-outside-init +from qiskit import transpile +from qiskit.circuit.library.standard_gates import XGate +from qiskit.transpiler import CouplingMap, PassManager +from qiskit.transpiler import InstructionDurations +from qiskit.transpiler.passes import ( + TimeUnitConversion, + ASAPScheduleAnalysis, + ALAPScheduleAnalysis, + PadDynamicalDecoupling, +) +from qiskit.converters import circuit_to_dag -from qiskit.transpiler.passes import TimeUnitConversion +from .utils import random_circuit class SchedulingPassBenchmarks: @@ -23,5 +34,114 @@ class SchedulingPassBenchmarks: param_names = ["n_qubits", "depth"] timeout = 300 + def setup(self, n_qubits, depth): + seed = 42 + self.circuit = random_circuit( + n_qubits, depth, measure=True, conditional=True, reset=True, seed=seed, max_operands=2 + ) + self.basis_gates = ["rz", "sx", "x", "cx", "id", "reset"] + self.cmap = [ + [0, 1], + [1, 0], + [1, 2], + [1, 6], + [2, 1], + [2, 3], + [3, 2], + [3, 4], + [3, 8], + [4, 3], + [5, 6], + [5, 10], + [6, 1], + [6, 5], + [6, 7], + [7, 6], + [7, 8], + [7, 12], + [8, 3], + [8, 7], + [8, 9], + [9, 8], + [9, 14], + [10, 5], + [10, 11], + [11, 10], + [11, 12], + [11, 16], + [12, 7], + [12, 11], + [12, 13], + [13, 12], + [13, 14], + [13, 18], + [14, 9], + [14, 13], + [15, 16], + [16, 11], + [16, 15], + [16, 17], + [17, 16], + [17, 18], + [18, 13], + [18, 17], + [18, 19], + [19, 18], + ] + self.coupling_map = CouplingMap(self.cmap) + self.transpiled_circuit = transpile( + self.circuit, + basis_gates=self.basis_gates, + coupling_map=self.coupling_map, + optimization_level=1, + ) + self.dag = circuit_to_dag(self.transpiled_circuit) + self.durations = InstructionDurations( + [ + ("rz", None, 0), + ("id", None, 160), + ("sx", None, 160), + ("x", None, 160), + ("cx", None, 800), + ("measure", None, 3200), + ("reset", None, 3600), + ], + dt=1e-9, + ) + self.timed_dag = TimeUnitConversion(self.durations).run(self.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, _, __): + 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, _, __): + 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, _, __): + PadDynamicalDecoupling(self.durations, dd_sequence=[XGate(), XGate()]).run( + self.scheduled_dag + )