Removed code deprecated in qiskit-terra 0.21, released on June 2022 (#10754)

* Removed code and tests deprecated in 0.21

* Undone removal of optimizers/spsa.

* undo changes in spsa

* Added release note.

* Removed deprecated classes from docstrings.

* Improved release note.

* Added removed files to resolve conflict.

* Removed the conflicting files

* Added conflicting file but with no code

* Align measure class file

* Removed alap dynamical coupling class

* Resolving colflict with dynamical coupling

* Deleted dynamical coupling

* Fixed release note.

* Apply suggestions from code review

* Update qiskit/execute_function.py

* recover benchmark test

---------

Co-authored-by: Luciano Bello <bel@zurich.ibm.com>
Co-authored-by: Luciano Bello <luciano@debian.org>
This commit is contained in:
Raghav 2023-10-18 17:57:04 +05:30 committed by GitHub
parent 998b5595df
commit d86e708f11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 79 additions and 2632 deletions

View File

@ -26,7 +26,6 @@ from qiskit.compiler import transpile, schedule
from qiskit.providers.backend import Backend
from qiskit.pulse import Schedule, ScheduleBlock
from qiskit.exceptions import QiskitError
from qiskit.utils.deprecation import deprecate_arg
logger = logging.getLogger(__name__)
@ -36,8 +35,6 @@ def _log_submission_time(start_time, end_time):
logger.info(log_msg)
@deprecate_arg("qobj_id", since="0.21.0", additional_msg="This argument has no effect anymore.")
@deprecate_arg("qobj_header", since="0.21.0", additional_msg="This argument has no effect anymore.")
def execute(
experiments,
backend,
@ -48,8 +45,6 @@ def execute(
seed_transpiler=None,
optimization_level=None,
pass_manager=None,
qobj_id=None,
qobj_header=None,
shots=None, # common run options
memory=None,
seed_simulator=None,
@ -159,16 +154,6 @@ def execute(
arg is present, auto-selection of pass manager based on the transpile options
will be turned off and this pass manager will be used directly.
qobj_id (str): DEPRECATED: String identifier to annotate the Qobj. This has no effect
and the :attr:`~.QuantumCircuit.name` attribute of the input circuit(s) should be used
instead.
qobj_header (QobjHeader or dict): DEPRECATED: User input that will be inserted in Qobj
header, and will also be copied to the corresponding :class:`qiskit.result.Result`
header. Headers do not affect the run. Headers do not affect the run. This kwarg
has no effect anymore and the :attr:`~.QuantumCircuit.metadata` attribute of the
input circuit(s) should be used instead.
shots (int): Number of repetitions of each circuit, for sampling. Default: 1024
memory (bool): If True, per-shot measurement bitstrings are returned as well
@ -279,8 +264,6 @@ def execute(
job = execute(qc, backend, shots=4321)
"""
del qobj_id
del qobj_header
if isinstance(experiments, (Schedule, ScheduleBlock)) or (
isinstance(experiments, list) and isinstance(experiments[0], (Schedule, ScheduleBlock))
):

View File

@ -12,7 +12,7 @@
"""A collection of passes to reallocate the timeslots of instructions according to context."""
import abc
from typing import Callable, Dict, Any, Union, Tuple
from typing import Callable, Union, Tuple
import numpy as np
@ -20,7 +20,6 @@ from qiskit.circuit.parameterexpression import ParameterExpression, ParameterVal
from qiskit.pulse.exceptions import PulseError
from qiskit.pulse.schedule import Schedule, ScheduleComponent
from qiskit.pulse.utils import instruction_duration_validation
from qiskit.utils.deprecation import deprecate_func
class AlignmentKind(abc.ABC):
@ -45,11 +44,6 @@ class AlignmentKind(abc.ABC):
"""
pass
@deprecate_func(since="0.21")
def to_dict(self) -> Dict[str, Any]:
"""Returns dictionary to represent this alignment."""
return {"alignment": self.__class__.__name__}
@property
@abc.abstractmethod
def is_sequential(self) -> bool:
@ -330,11 +324,6 @@ class AlignEquispaced(AlignmentKind):
return aligned
@deprecate_func(since="0.21")
def to_dict(self) -> Dict[str, Any]:
"""Returns dictionary to represent this alignment."""
return {"alignment": self.__class__.__name__, "duration": self.duration}
class AlignFunc(AlignmentKind):
"""Allocate instructions at position specified by callback function.
@ -415,15 +404,3 @@ class AlignFunc(AlignmentKind):
aligned.insert(_t0, child, inplace=True)
return aligned
@deprecate_func(since="0.21")
def to_dict(self) -> Dict[str, Any]:
"""Returns dictionary to represent this alignment.
.. note:: ``func`` is not presented in this dictionary. Just name.
"""
return {
"alignment": self.__class__.__name__,
"duration": self.duration,
"func": self.func.__name__,
}

View File

@ -25,7 +25,6 @@ from qiskit.exceptions import QiskitError
from qiskit.qpy import formats, common, binary_io, type_keys
from qiskit.qpy.exceptions import QpyError
from qiskit.version import __version__
from qiskit.utils.deprecation import deprecate_arg
# pylint: disable=invalid-name
@ -72,7 +71,6 @@ VERSION_PATTERN = (
VERSION_PATTERN_REGEX = re.compile(VERSION_PATTERN, re.VERBOSE | re.IGNORECASE)
@deprecate_arg("circuits", new_alias="programs", since="0.21.0")
def dump(
programs: Union[List[QPY_SUPPORTED_TYPES], QPY_SUPPORTED_TYPES],
file_obj: BinaryIO,

View File

@ -114,13 +114,9 @@ Scheduling
PadDynamicalDecoupling
PadDelay
ConstrainedReschedule
AlignMeasures
ValidatePulseGates
InstructionDurationCheck
SetIOLatency
ALAPSchedule
ASAPSchedule
DynamicalDecoupling
Circuit Analysis
================
@ -164,9 +160,7 @@ Additional Passes
:toctree: ../stubs/
CheckMap
CheckCXDirection
CheckGateDirection
CXDirection
GateDirection
MergeAdjacentBarriers
RemoveBarriers
@ -267,11 +261,7 @@ from .calibration import RXCalibrationBuilder
from .scheduling import TimeUnitConversion
from .scheduling import ALAPScheduleAnalysis
from .scheduling import ASAPScheduleAnalysis
from .scheduling import ALAPSchedule
from .scheduling import ASAPSchedule
from .scheduling import PadDynamicalDecoupling
from .scheduling import DynamicalDecoupling
from .scheduling import AlignMeasures # Deprecated
from .scheduling import ValidatePulseGates
from .scheduling import PadDelay
from .scheduling import ConstrainedReschedule
@ -280,8 +270,6 @@ from .scheduling import SetIOLatency
# additional utility passes
from .utils import CheckMap
from .utils import CheckCXDirection # Deprecated
from .utils import CXDirection # Deprecated
from .utils import CheckGateDirection
from .utils import GateDirection
from .utils import BarrierBeforeFinalMeasurements

View File

@ -12,9 +12,6 @@
"""Module containing circuit scheduling passes."""
from .alap import ALAPSchedule
from .asap import ASAPSchedule
from .dynamical_decoupling import DynamicalDecoupling
from .scheduling import ALAPScheduleAnalysis, ASAPScheduleAnalysis, SetIOLatency
from .time_unit_conversion import TimeUnitConversion
from .padding import PadDelay, PadDynamicalDecoupling
@ -24,4 +21,3 @@ from .alignments import InstructionDurationCheck, ValidatePulseGates, Constraine
from . import alignments as instruction_alignments
# TODO Deprecated pass. Will be removed after deprecation period.
from .alignments import AlignMeasures

View File

@ -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

View File

@ -78,4 +78,3 @@ Minimum pulse length constraint
from .check_durations import InstructionDurationCheck
from .pulse_gate_validation import ValidatePulseGates
from .reschedule import ConstrainedReschedule
from .align_measures import AlignMeasures

View File

@ -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

View File

@ -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

View File

@ -1,293 +0,0 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Dynamical Decoupling insertion pass."""
import itertools
import numpy as np
from qiskit.circuit import Gate, Delay, Reset
from qiskit.circuit.library.standard_gates import IGate, UGate, U3Gate
from qiskit.dagcircuit import DAGOpNode, DAGInNode
from qiskit.quantum_info.operators.predicates import matrix_equal
from qiskit.quantum_info.synthesis import OneQubitEulerDecomposer
from qiskit.transpiler.passes.optimization import Optimize1qGates
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.utils.deprecation import deprecate_func
class DynamicalDecoupling(TransformationPass):
"""Dynamical decoupling insertion pass.
This pass works on a scheduled, physical circuit. It scans the circuit for
idle periods of time (i.e. those containing delay instructions) and inserts
a DD sequence of gates in those spots. These gates amount to the identity,
so do not alter the logical action of the circuit, but have the effect of
mitigating decoherence in those idle periods.
As a special case, the pass allows a length-1 sequence (e.g. [XGate()]).
In this case the DD insertion happens only when the gate inverse can be
absorbed into a neighboring gate in the circuit (so we would still be
replacing Delay with something that is equivalent to the identity).
This can be used, for instance, as a Hahn echo.
This pass ensures that the inserted sequence preserves the circuit exactly
(including global phase).
.. plot::
:include-source:
import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.circuit.library import XGate
from qiskit.transpiler import PassManager, InstructionDurations
from qiskit.transpiler.passes import ALAPSchedule, DynamicalDecoupling
from qiskit.visualization import timeline_drawer
# Because the legacy passes do not propagate the scheduling information correctly, it is
# necessary to run a no-op "re-schedule" before the output circuits can be drawn.
def draw(circuit):
from qiskit import transpile
scheduled = transpile(
circuit,
optimization_level=0,
instruction_durations=InstructionDurations(),
scheduling_method="alap",
)
return timeline_drawer(scheduled)
circ = QuantumCircuit(4)
circ.h(0)
circ.cx(0, 1)
circ.cx(1, 2)
circ.cx(2, 3)
circ.measure_all()
durations = InstructionDurations(
[("h", 0, 50), ("cx", [0, 1], 700), ("reset", None, 10),
("cx", [1, 2], 200), ("cx", [2, 3], 300),
("x", None, 50), ("measure", None, 1000)]
)
# balanced X-X sequence on all qubits
dd_sequence = [XGate(), XGate()]
pm = PassManager([ALAPSchedule(durations),
DynamicalDecoupling(durations, dd_sequence)])
circ_dd = pm.run(circ)
draw(circ_dd)
# Uhrig sequence on qubit 0
n = 8
dd_sequence = [XGate()] * n
def uhrig_pulse_location(k):
return np.sin(np.pi * (k + 1) / (2 * n + 2)) ** 2
spacing = []
for k in range(n):
spacing.append(uhrig_pulse_location(k) - sum(spacing))
spacing.append(1 - sum(spacing))
pm = PassManager(
[
ALAPSchedule(durations),
DynamicalDecoupling(durations, dd_sequence, qubits=[0], spacing=spacing),
]
)
circ_dd = pm.run(circ)
draw(circ_dd)
"""
@deprecate_func(
additional_msg=(
"Instead, use :class:`~.PadDynamicalDecoupling`, which performs the same "
"function but requires scheduling and alignment analysis passes to run prior to it."
),
since="0.21.0",
pending=True,
)
def __init__(
self, durations, dd_sequence, qubits=None, spacing=None, skip_reset_qubits=True, target=None
):
"""Dynamical decoupling initializer.
Args:
durations (InstructionDurations): Durations of instructions to be
used in scheduling.
dd_sequence (list[Gate]): sequence of gates to apply in idle spots.
qubits (list[int]): physical qubits on which to apply DD.
If None, all qubits will undergo DD (when possible).
spacing (list[float]): a list of spacings between the DD gates.
The available slack will be divided according to this.
The list length must be one more than the length of dd_sequence,
and the elements must sum to 1. If None, a balanced spacing
will be used [d/2, d, d, ..., d, d, d/2].
skip_reset_qubits (bool): if True, does not insert DD on idle
periods that immediately follow initialized/reset qubits (as
qubits in the ground state are less susceptile to decoherence).
target (Target): The :class:`~.Target` representing the target backend, if both
``durations`` and this are specified then this argument will take
precedence and ``durations`` will be ignored.
"""
super().__init__()
self._durations = durations
self._dd_sequence = dd_sequence
self._qubits = qubits
self._spacing = spacing
self._skip_reset_qubits = skip_reset_qubits
self._target = target
if target is not None:
self._durations = target.durations()
for gate in dd_sequence:
if gate.name not in target.operation_names:
raise TranspilerError(
f"{gate.name} in dd_sequence is not supported in the target"
)
def run(self, dag):
"""Run the DynamicalDecoupling pass on dag.
Args:
dag (DAGCircuit): a scheduled DAG.
Returns:
DAGCircuit: equivalent circuit with delays interrupted by DD,
where possible.
Raises:
TranspilerError: if the circuit is not mapped on physical qubits.
"""
if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None:
raise TranspilerError("DD runs on physical circuits only.")
if dag.duration is None:
raise TranspilerError("DD runs after circuit is scheduled.")
num_pulses = len(self._dd_sequence)
sequence_gphase = 0
if num_pulses != 1:
if num_pulses % 2 != 0:
raise TranspilerError("DD sequence must contain an even number of gates (or 1).")
noop = np.eye(2)
for gate in self._dd_sequence:
noop = noop.dot(gate.to_matrix())
if not matrix_equal(noop, IGate().to_matrix(), ignore_phase=True):
raise TranspilerError("The DD sequence does not make an identity operation.")
sequence_gphase = np.angle(noop[0][0])
if self._qubits is None:
self._qubits = set(range(dag.num_qubits()))
else:
self._qubits = set(self._qubits)
if self._spacing:
if sum(self._spacing) != 1 or any(a < 0 for a in self._spacing):
raise TranspilerError(
"The spacings must be given in terms of fractions "
"of the slack period and sum to 1."
)
else: # default to balanced spacing
mid = 1 / num_pulses
end = mid / 2
self._spacing = [end] + [mid] * (num_pulses - 1) + [end]
for qarg in list(self._qubits):
for gate in self._dd_sequence:
if not self.__gate_supported(gate, qarg):
self._qubits.discard(qarg)
break
index_sequence_duration_map = {}
for physical_qubit in self._qubits:
dd_sequence_duration = 0
for index, gate in enumerate(self._dd_sequence):
gate = gate.to_mutable()
self._dd_sequence[index] = gate
gate.duration = self._durations.get(gate, physical_qubit)
dd_sequence_duration += gate.duration
index_sequence_duration_map[physical_qubit] = dd_sequence_duration
new_dag = dag.copy_empty_like()
for nd in dag.topological_op_nodes():
if not isinstance(nd.op, Delay):
new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False)
continue
dag_qubit = nd.qargs[0]
physical_qubit = dag.find_bit(dag_qubit).index
if physical_qubit not in self._qubits: # skip unwanted qubits
new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False)
continue
pred = next(dag.predecessors(nd))
succ = next(dag.successors(nd))
if self._skip_reset_qubits: # discount initial delays
if isinstance(pred, DAGInNode) or isinstance(pred.op, Reset):
new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False)
continue
dd_sequence_duration = index_sequence_duration_map[physical_qubit]
slack = nd.op.duration - dd_sequence_duration
if slack <= 0: # dd doesn't fit
new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False)
continue
if num_pulses == 1: # special case of using a single gate for DD
u_inv = self._dd_sequence[0].inverse().to_matrix()
theta, phi, lam, phase = OneQubitEulerDecomposer().angles_and_phase(u_inv)
# absorb the inverse into the successor (from left in circuit)
if isinstance(succ, DAGOpNode) and isinstance(succ.op, (UGate, U3Gate)):
theta_r, phi_r, lam_r = succ.op.params
succ.op.params = Optimize1qGates.compose_u3(
theta_r, phi_r, lam_r, theta, phi, lam
)
sequence_gphase += phase
# absorb the inverse into the predecessor (from right in circuit)
elif isinstance(pred, DAGOpNode) and isinstance(pred.op, (UGate, U3Gate)):
theta_l, phi_l, lam_l = pred.op.params
pred.op.params = Optimize1qGates.compose_u3(
theta, phi, lam, theta_l, phi_l, lam_l
)
sequence_gphase += phase
# don't do anything if there's no single-qubit gate to absorb the inverse
else:
new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False)
continue
# insert the actual DD sequence
taus = [int(slack * a) for a in self._spacing]
unused_slack = slack - sum(taus) # unused, due to rounding to int multiples of dt
middle_index = int((len(taus) - 1) / 2) # arbitrary: redistribute to middle
taus[middle_index] += unused_slack # now we add up to original delay duration
for tau, gate in itertools.zip_longest(taus, self._dd_sequence):
if tau > 0:
new_dag.apply_operation_back(Delay(tau), [dag_qubit], check=False)
if gate is not None:
new_dag.apply_operation_back(gate, [dag_qubit], check=False)
new_dag.global_phase = _mod_2pi(new_dag.global_phase + sequence_gphase)
return new_dag
def __gate_supported(self, gate: Gate, qarg: int) -> bool:
"""A gate is supported on the qubit (qarg) or not."""
if self._target is None or self._target.instruction_supported(gate.name, qargs=(qarg,)):
return True
return False
def _mod_2pi(angle: float, atol: float = 0):
"""Wrap angle into interval [-π,π). If within atol of the endpoint, clamp to -π"""
wrapped = (angle + np.pi) % (2 * np.pi) - np.pi
if abs(wrapped - np.pi) < atol:
wrapped = -np.pi
return wrapped

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1,44 @@
---
upgrade:
- |
The function :func:`~qiskit.execute_function.execute`
does not accept the arguments `qobj_id` and `qobj_header` any more.
Their use was deprecated in Qiskit 0.37 (with Terra 0.21), released on June 2022.
- |
The transpilation pass ``qiskit.transpiler.passes.ALAPSchedule`` is removed. It use was deprecated
in Qiskit 0.37 (with Terra 0.21), released on June 2022 and replaced by
:class:`~.transpiler.passes.ALAPScheduleAnalysis`, which is an
analysis pass.
- |
The transpilation pass ``qiskit.transpiler.passes.ASAPSchedule`` is removed. It use was deprecated
in Qiskit 0.37 (with Terra 0.21), released on June 2022. It has been superseded by
:class:`~.ASAPScheduleAnalysis` and the new scheduling workflow.
- |
The transpilation pass ``qiskit.transpiler.passes.DynamicalDecoupling`` is removed. It use was deprecated
in Qiskit 0.37 (with Terra 0.21), released on June 2022.
Instead, use :class:`~.transpiler.passes.PadDynamicalDecoupling`, which performs the same
function but requires scheduling and alignment analysis passes to run prior to it.
- |
The transpilation pass ``qiskit.transpiler.passes.AlignMeasures`` is removed. It use was deprecated
in Qiskit 0.37 (with Terra 0.21), released on June 2022.
Instead, use :class:`~.ConstrainedReschedule`, which performs the same function
and also supports aligning to additional timing constraints.
- |
The transpilation pass ``qiskit.transpiler.passes.CXDirection`` is removed. It use was deprecated
in Qiskit 0.37 (with Terra 0.21), released on June 2022.
Instead, use the more generic :class:`~.GateDirection` pass.
- |
The transpilation pass ``qiskit.transpiler.passes.CheckCXDirection`` is removed. It use was deprecated
in Qiskit 0.37 (with Terra 0.21), released on June 2022.
Instead, use the more generic :class:`~.CheckGateDirection` pass.
- |
The methods ``to_dict`` in the classes :class:`.pulse.transforms.AlignmentKind`,
`.pulse.transforms.AlignEquispaced`, and :class:`.pulse.transforms.AlignFunc` are removed.
They were deprecated
in Qiskit 0.37 (with Terra 0.21), released on June 2022.
- |
The argument ``circuits`` in the method :meth:`qiskit.qpy.interface.dump`
is removed as its usage was deprecated
in Qiskit 0.37 (with Terra 0.21), released on June 2022.
Instead, use the argument ``programs``, which behaves identically.

View File

@ -15,13 +15,13 @@
from qiskit import transpile
from qiskit.circuit.library.standard_gates import XGate
from qiskit.transpiler import CouplingMap
from qiskit.transpiler import CouplingMap, PassManager
from qiskit.transpiler import InstructionDurations
from qiskit.transpiler.passes import (
TimeUnitConversion,
ASAPSchedule,
ALAPSchedule,
DynamicalDecoupling,
ASAPScheduleAnalysis,
ALAPScheduleAnalysis,
PadDynamicalDecoupling,
)
from qiskit.converters import circuit_to_dag
@ -109,22 +109,39 @@ class SchedulingPassBenchmarks:
dt=1e-9,
)
self.timed_dag = TimeUnitConversion(self.durations).run(self.dag)
_pass = ALAPSchedule(self.durations)
_pass.property_set["time_unit"] = "dt"
self.scheduled_dag = _pass.run(self.timed_dag)
dd_sequence = [XGate(), XGate()]
pm = PassManager(
[
ALAPScheduleAnalysis(self.durations),
PadDynamicalDecoupling(self.durations, dd_sequence),
]
)
self.scheduled_dag = pm.run(self.timed_dag)
def time_time_unit_conversion_pass(self, _, __):
TimeUnitConversion(self.durations).run(self.dag)
def time_alap_schedule_pass(self, _, __):
_pass = ALAPSchedule(self.durations)
_pass.property_set["time_unit"] = "dt"
_pass.run(self.timed_dag)
dd_sequence = [XGate(), XGate()]
pm = PassManager(
[
ALAPScheduleAnalysis(self.durations),
PadDynamicalDecoupling(self.durations, dd_sequence),
]
)
pm.run(self.timed_dag)
def time_asap_schedule_pass(self, _, __):
_pass = ASAPSchedule(self.durations)
_pass.property_set["time_unit"] = "dt"
_pass.run(self.timed_dag)
dd_sequence = [XGate(), XGate()]
pm = PassManager(
[
ASAPScheduleAnalysis(self.durations),
PadDynamicalDecoupling(self.durations, dd_sequence),
]
)
pm.run(self.timed_dag)
def time_dynamical_decoupling_pass(self, _, __):
DynamicalDecoupling(self.durations, dd_sequence=[XGate(), XGate()]).run(self.scheduled_dag)
PadDynamicalDecoupling(self.durations, dd_sequence=[XGate(), XGate()]).run(
self.scheduled_dag
)

View File

@ -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]) MM
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):

View File

@ -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()

View File

@ -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]) MM
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):