diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py
index d6d584de811e..ccaec1f4d224 100644
--- a/qiskit/transpiler/__init__.py
+++ b/qiskit/transpiler/__init__.py
@@ -595,6 +595,74 @@
+.. dropdown:: Working with preset :class:`~.PassManager`
+ :animate: fade-in-slide-down
+
+ By default Qiskit includes functions to build preset :class:`~.PassManager` objects.
+ These preset passmangers are what get used by the :func:`~.transpile` function
+ for each optimization level. If you'd like to work directly with a
+ preset pass manager you can use the :func:`~.generate_preset_pass_manager`
+ function to easily generate one. For example:
+
+ .. code-block:: python
+
+ from qiskit.transpiler.preset_passmanager import generate_preset_pass_manager
+ from qiskit.providers.fake_provider import FakeLagosV2
+
+ backend = FakeLagosV2()
+ pass_manager = generate_preset_pass_manager(3, backend)
+
+ which will generate a :class:`~.StagedPassManager` object for optimization level 3
+ targetting the :class:`~.FakeLagosV2` backend (equivalent to what is used internally
+ by :func:`~.transpile` with ``backend=FakeLagosV2()`` and ``optimization_level=3``).
+ You can use this just like working with any other :class:`~.PassManager`. However,
+ because it is a :class:`~.StagedPassManager` it also makes it easy to compose and/or
+ replace stages of the pipeline. For example, if you wanted to run a custom scheduling
+ stage using dynamical decoupling (via the :class:`~.PadDynamicalDecoupling` pass) and
+ also add initial logical optimization prior to routing you would do something like
+ (building off the previous example):
+
+ .. code-block:: python
+
+ from qiskit.circuit.library import XGate, HGate, RXGate, PhaseGate, TGate, TdgGate
+ from qiskit.transpiler import PassManager
+ from qiskit.transpiler.passes import ALAPScheduleAnalysis, PadDynamicalDecoupling
+ from qiskit.transpiler.passes import CXCancellation, InverseCancellation
+
+ backend_durations = backend.target.durations()
+ dd_sequence = [XGate(), XGate()]
+ scheduling_pm = PassManager([
+ ALAPScheduleAnalysis(backend_durations),
+ PadDynamicalDecoupling(backend_durations, dd_sequence),
+ ])
+ inverse_gate_list = [
+ HGate(),
+ (RXGate(np.pi / 4), RXGate(-np.pi / 4)),
+ (PhaseGate(np.pi / 4), PhaseGate(-np.pi / 4)),
+ (TGate(), TdgGate()),
+
+ ])
+ logical_opt = PassManager([
+ CXCancellation(),
+ InverseCancellation([HGate(), (RXGate(np.pi / 4), RXGate(-np.pi / 4))
+ ])
+
+
+ # Add pre-layout stage to run extra logical optimization
+ pass_manager.pre_layout = logical_opt
+ # Set scheduling stage to custom pass manager
+ pass_manager.scheduling = scheduling_pm
+
+
+ Then when :meth:`~.StagedPassManager.run` is called on ``pass_manager`` the
+ ``logical_opt`` :class:`~.PassManager` will be called prior to the ``layout`` stage
+ and for the ``scheduling`` stage our custom :class:`~.PassManager`
+ ``scheduling_pm`` will be used.
+
+ .. raw:: html
+
+
+
Transpiler API
==============
@@ -614,6 +682,7 @@
.. autosummary::
:toctree: ../stubs/
+ StagedPassManager
PassManager
PassManagerConfig
PropertySet
@@ -669,6 +738,7 @@
from .runningpassmanager import FlowController, ConditionalController, DoWhileController
from .passmanager import PassManager
from .passmanager_config import PassManagerConfig
+from .passmanager import StagedPassManager
from .propertyset import PropertySet
from .exceptions import TranspilerError, TranspilerAccessError
from .fencedobjs import FencedDAGCircuit, FencedPropertySet
diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py
index 90c0858705bb..c9f2d540b60e 100644
--- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py
+++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py
@@ -117,7 +117,7 @@ def run(self, dag):
# If 1q runs are collected before consolidate those too
runs = self.property_set["run_list"] or []
for run in runs:
- if run[0] in all_block_gates:
+ if any(gate in all_block_gates for gate in run):
continue
if len(run) == 1 and not self._check_not_in_basis(
run[0].name, run[0].qargs, global_index_map
@@ -135,6 +135,11 @@ def run(self, dag):
continue
unitary = UnitaryGate(operator)
dag.replace_block_with_op(run, unitary, {qubit: 0}, cycle_check=False)
+ # Clear collected blocks and runs as they are no longer valid after consolidation
+ if "run_list" in self.property_set:
+ del self.property_set["run_list"]
+ if "block_list" in self.property_set:
+ del self.property_set["block_list"]
return dag
def _check_not_in_basis(self, gate_name, qargs, global_index_map):
diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py
index a206af3cf0ab..064579624c91 100644
--- a/qiskit/transpiler/passmanager.py
+++ b/qiskit/transpiler/passmanager.py
@@ -12,6 +12,8 @@
"""Manager for a set of Passes and their scheduling during transpilation."""
+import io
+import re
from typing import Union, List, Callable, Dict, Any
import dill
@@ -319,3 +321,185 @@ def passes(self) -> List[Dict[str, BasePass]]:
item["flow_controllers"] = {}
ret.append(item)
return ret
+
+
+class StagedPassManager(PassManager):
+ """A Pass manager pipeline built up of individual stages
+
+ This class enables building a compilation pipeline out of fixed stages.
+ Each ``StagedPassManager`` defines a list of stages which are executed in
+ a fixed order, and each stage is defined as a standalone :class:`~.PassManager`
+ instance. There are also ``pre_`` and ``post_`` stages for each defined stage.
+ This enables easily composing and replacing different stages and also adding
+ hook points to enable programmtic modifications to a pipeline. When using a staged
+ pass manager you are not able to modify the individual passes and are only able
+ to modify stages.
+
+ By default instances of StagedPassManager define a typical full compilation
+ pipeline from an abstract virtual circuit to one that is optimized and
+ capable of running on the specified backend. The default pre-defined stages are:
+
+ #. ``init`` - any initial passes that are run before we start embedding the circuit to the backend
+ #. ``layout`` - This stage runs layout and maps the virtual qubits in the
+ circuit to the physical qubits on a backend
+ #. ``routing`` - This stage runs after a layout has been run and will insert any
+ necessary gates to move the qubit states around until it can be run on
+ backend's compuling map.
+ #. ``translation`` - Perform the basis gate translation, in other words translate the gates
+ in the circuit to the target backend's basis set
+ #. ``optimization`` - The main optimization loop, this will typically run in a loop trying to
+ optimize the circuit until a condtion (such as fixed depth) is reached.
+ #. ``scheduling`` - Any hardware aware scheduling passes
+
+ .. note::
+
+ For backwards compatibility the relative positioning of these default
+ stages will remain stable moving forward. However, new stages may be
+ added to the default stage list in between current stages. For example,
+ in a future release a new phase, something like ``logical_optimization``, could be added
+ immediately after the existing ``init`` stage in the default stage list.
+ This would preserve compatibility for pre-existing ``StagedPassManager``
+ users as the relative positions of the stage are preserved so the behavior
+ will not change between releases.
+
+ These stages will be executed in order and any stage set to ``None`` will be skipped. If
+ a :class:`~qiskit.transpiler.PassManager` input is being used for more than 1 stage here
+ (for example in the case of a :class:`~.Pass` that covers both Layout and Routing) you will want
+ to set that to the earliest stage in sequence that it covers.
+ """
+
+ invalid_stage_regex = re.compile(
+ r"\s|\+|\-|\*|\/|\\|\%|\<|\>|\@|\!|\~|\^|\&|\:|\[|\]|\{|\}|\(|\)"
+ )
+
+ def __init__(self, stages=None, **kwargs):
+ """Initialize a new StagedPassManager object
+
+ Args:
+ stages (List[str]): An optional list of stages to use for this
+ instance. If this is not specified the default stages list
+ ``['init', 'layout', 'routing', 'translation', 'optimization', 'scheduling']`` is
+ used
+ kwargs: The initial :class:`~.PassManager` values for any stages
+ defined in ``stages``. If a argument is not defined the
+ stages will default to ``None`` indicating an empty/undefined
+ stage.
+
+ Raises:
+ AttributeError: If a stage in the input keyword arguments is not defined.
+ ValueError: If an invalid stage name is specified.
+ """
+ if stages is None:
+ self.stages = [
+ "init",
+ "layout",
+ "routing",
+ "translation",
+ "optimization",
+ "scheduling",
+ ]
+ else:
+ invalid_stages = [
+ stage for stage in stages if self.invalid_stage_regex.search(stage) is not None
+ ]
+ if invalid_stages:
+ with io.StringIO() as msg:
+ msg.write(f"The following stage names are not valid: {invalid_stages[0]}")
+ for invalid_stage in invalid_stages[1:]:
+ msg.write(f", {invalid_stage}")
+ raise ValueError(msg.getvalue())
+
+ self.stages = stages
+ super().__init__()
+ for stage in self.stages:
+ pre_stage = "pre_" + stage
+ post_stage = "post_" + stage
+ setattr(self, pre_stage, None)
+ setattr(self, stage, None)
+ setattr(self, post_stage, None)
+ for stage, pm in kwargs.items():
+ if (
+ stage not in self.stages
+ and not (stage.startswith("pre_") and stage[4:] in self.stages)
+ and not (stage.startswith("post_") and stage[5:] in self.stages)
+ ):
+ raise AttributeError(f"{stage} is not a valid stage.")
+ setattr(self, stage, pm)
+ self._update_passmanager()
+
+ def _update_passmanager(self):
+ self._pass_sets = []
+ for stage in self.stages:
+ # Add pre-stage PM
+ pre_stage_pm = getattr(self, "pre_" + stage, None)
+ if pre_stage_pm is not None:
+ self._pass_sets.extend(pre_stage_pm._pass_sets)
+ # Add stage PM
+ stage_pm = getattr(self, stage, None)
+ if stage_pm is not None:
+ self._pass_sets.extend(stage_pm._pass_sets)
+ # Add post-stage PM
+ post_stage_pm = getattr(self, "post_" + stage, None)
+ if post_stage_pm is not None:
+ self._pass_sets.extend(post_stage_pm._pass_sets)
+
+ def __setattr__(self, attr, value):
+ super().__setattr__(attr, value)
+ if (
+ attr in self.stages
+ or (attr.startswith("pre_") and attr[4:] not in self.stages)
+ or (attr.startswith("post_") and attr[5:] not in self.stages)
+ ):
+ self._update_passmanager()
+
+ def append(
+ self,
+ passes: Union[BasePass, List[BasePass]],
+ max_iteration: int = None,
+ **flow_controller_conditions: Any,
+ ) -> None:
+ raise NotImplementedError
+
+ def replace(
+ self,
+ index: int,
+ passes: Union[BasePass, List[BasePass]],
+ max_iteration: int = None,
+ **flow_controller_conditions: Any,
+ ) -> None:
+ raise NotImplementedError
+
+ # Raise NotImplemntedError on individual pass manipulation
+ def remove(self, index: int) -> None:
+ raise NotImplementedError
+
+ def __getitem(self, index):
+ self._update_passmanager()
+ return super().__getitem__(index)
+
+ def __len__(self):
+ self._update_passmanager()
+ return super().__len__()
+
+ def __setitem__(self, index, item):
+ raise NotImplementedError
+
+ def __add__(self, other):
+ raise NotImplementedError
+
+ def _create_running_passmanager(self) -> RunningPassManager:
+ self._update_passmanager()
+ return super()._create_running_passmanager()
+
+ def passes(self) -> List[Dict[str, BasePass]]:
+ self._update_passmanager()
+ return super().passes()
+
+ def run(
+ self,
+ circuits: Union[QuantumCircuit, List[QuantumCircuit]],
+ output_name: str = None,
+ callback: Callable = None,
+ ) -> Union[QuantumCircuit, List[QuantumCircuit]]:
+ self._update_passmanager()
+ return super().run(circuits, output_name, callback)
diff --git a/qiskit/transpiler/preset_passmanagers/__init__.py b/qiskit/transpiler/preset_passmanagers/__init__.py
index 45ddada26c71..eb2f0d2aed39 100644
--- a/qiskit/transpiler/preset_passmanagers/__init__.py
+++ b/qiskit/transpiler/preset_passmanagers/__init__.py
@@ -136,7 +136,7 @@ def generate_preset_pass_manager(
to use this option.
Returns:
- PassManager: The preset pass manager for the given options
+ StagedPassManager: The preset pass manager for the given options
Raises:
ValueError: if an invalid value for ``optimization_level`` is passed in.
diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py
new file mode 100644
index 000000000000..ce01dce6a26e
--- /dev/null
+++ b/qiskit/transpiler/preset_passmanagers/common.py
@@ -0,0 +1,388 @@
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2022.
+#
+# 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.
+
+# pylint: disable=invalid-name
+
+"""Common preset passmanager generators."""
+
+from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel
+
+from qiskit.transpiler.passmanager import PassManager
+from qiskit.transpiler.passes import Unroller
+from qiskit.transpiler.passes import BasisTranslator
+from qiskit.transpiler.passes import UnrollCustomDefinitions
+from qiskit.transpiler.passes import Unroll3qOrMore
+from qiskit.transpiler.passes import Collect2qBlocks
+from qiskit.transpiler.passes import Collect1qRuns
+from qiskit.transpiler.passes import ConsolidateBlocks
+from qiskit.transpiler.passes import UnitarySynthesis
+from qiskit.transpiler.passes import CheckMap
+from qiskit.transpiler.passes import GateDirection
+from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements
+from qiskit.transpiler.passes import CheckGateDirection
+from qiskit.transpiler.passes import TimeUnitConversion
+from qiskit.transpiler.passes import ALAPScheduleAnalysis
+from qiskit.transpiler.passes import ASAPScheduleAnalysis
+from qiskit.transpiler.passes import FullAncillaAllocation
+from qiskit.transpiler.passes import EnlargeWithAncilla
+from qiskit.transpiler.passes import ApplyLayout
+from qiskit.transpiler.passes import RemoveResetInZeroState
+from qiskit.transpiler.passes import ValidatePulseGates
+from qiskit.transpiler.passes import PadDelay
+from qiskit.transpiler.passes import InstructionDurationCheck
+from qiskit.transpiler.passes import ConstrainedReschedule
+from qiskit.transpiler.passes import PulseGates
+from qiskit.transpiler.passes import ContainsInstruction
+from qiskit.transpiler.passes import VF2PostLayout
+from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason
+from qiskit.transpiler.passes.layout.vf2_post_layout import VF2PostLayoutStopReason
+from qiskit.transpiler.exceptions import TranspilerError
+
+
+def generate_unroll_3q(
+ target,
+ basis_gates=None,
+ approximation_degree=None,
+ unitary_synthesis_method="default",
+ unitary_synthesis_plugin_config=None,
+):
+ """Generate an unroll >3q :class:`~qiskit.transpiler.PassManager`
+
+ Args:
+ target (Target): the :class:`~.Target` object representing the backend
+ basis_gates (list): A list of str gate names that represent the basis
+ gates on the backend target
+ approximation_degree (float): The heuristic approximation degree to
+ use. Can be between 0 and 1.
+ unitary_synthesis_method (str): The unitary synthesis method to use
+ unitary_synthesis_plugin_config (dict): The optional dictionary plugin
+ configuration, this is plugin specific refer to the specified plugin's
+ documenation for how to use.
+
+ Returns:
+ PassManager: The unroll 3q or more pass manager
+ """
+ unroll_3q = PassManager()
+ unroll_3q.append(
+ UnitarySynthesis(
+ basis_gates,
+ approximation_degree=approximation_degree,
+ method=unitary_synthesis_method,
+ min_qubits=3,
+ plugin_config=unitary_synthesis_plugin_config,
+ target=target,
+ )
+ )
+ unroll_3q.append(Unroll3qOrMore(target=target, basis_gates=basis_gates))
+ return unroll_3q
+
+
+def generate_embed_passmanager(coupling_map):
+ """Generate a layout embedding :class:`~qiskit.transpiler.PassManager`
+
+ This is used to generate a :class:`~qiskit.transpiler.PassManager` object
+ that can be used to expand and apply an initial layout to a circuit
+
+ Args:
+ coupling_map (CouplingMap): The coupling map for the backend to embed
+ the circuit to.
+ Returns:
+ PassManager: The embedding passmanager that assumes the layout property
+ set has been set in earlier stages
+ """
+ return PassManager([FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()])
+
+
+def _trivial_not_perfect(property_set):
+ # Verify that a trivial layout is perfect. If trivial_layout_score > 0
+ # the layout is not perfect. The layout is unconditionally set by trivial
+ # layout so we need to clear it before contuing.
+ return (
+ property_set["trivial_layout_score"] is not None
+ and property_set["trivial_layout_score"] != 0
+ )
+
+
+def _apply_post_layout_condition(property_set):
+ # if VF2 Post layout found a solution we need to re-apply the better
+ # layout. Otherwise we can skip apply layout.
+ return (
+ property_set["VF2PostLayout_stop_reason"] is not None
+ and property_set["VF2PostLayout_stop_reason"] is VF2PostLayoutStopReason.SOLUTION_FOUND
+ )
+
+
+def generate_routing_passmanager(
+ routing_pass,
+ target,
+ coupling_map=None,
+ vf2_call_limit=None,
+ backend_properties=None,
+ seed_transpiler=None,
+ check_trivial=False,
+ use_barrier_before_measurement=True,
+):
+ """Generate a routing :class:`~qiskit.transpiler.PassManager`
+
+ Args:
+ routing_pass (TransformationPass): The pass which will perform the
+ routing
+ target (Target): the :class:`~.Target` object representing the backend
+ coupling_map (CouplingMap): The coupling map of the backend to route
+ for
+ vf2_call_limit (int): The internal call limit for the vf2 post layout
+ pass. If this is ``None`` the vf2 post layout will not be run.
+ backend_properties (BackendProperties): Properties of a backend to
+ synthesize for (e.g. gate fidelities).
+ seed_transpiler (int): Sets random seed for the stochastic parts of
+ the transpiler.
+ check_trivial (bool): If set to true this will condition running the
+ :class:`~.VF2PostLayout` pass after routing on whether a trivial
+ layout was tried and was found to not be perfect. This is only
+ needed if the constructed pass manager runs :class:`~.TrivialLayout`
+ as a first layout attempt and uses it if it's a perfect layout
+ (as is the case with preset pass manager level 1).
+ use_barrier_before_measurement (bool): If true (the default) the
+ :class:`~.BarrierBeforeFinalMeasurements` transpiler pass will be run prior to the
+ specified pass in the ``routing_pass`` argument.
+ Returns:
+ PassManager: The routing pass manager
+ """
+
+ def _run_post_layout_condition(property_set):
+ # If we check trivial layout and the found trivial layout was not perfect also
+ # ensure VF2 initial layout was not used before running vf2 post layout
+ if not check_trivial or _trivial_not_perfect(property_set):
+ vf2_stop_reason = property_set["VF2Layout_stop_reason"]
+ if vf2_stop_reason is None or vf2_stop_reason != VF2LayoutStopReason.SOLUTION_FOUND:
+ return True
+ return False
+
+ routing = PassManager()
+ routing.append(CheckMap(coupling_map))
+
+ def _swap_condition(property_set):
+ return not property_set["is_swap_mapped"]
+
+ if use_barrier_before_measurement:
+ routing.append([BarrierBeforeFinalMeasurements(), routing_pass], condition=_swap_condition)
+ else:
+ routing.append([routing_pass], condition=_swap_condition)
+
+ if (target is not None or backend_properties is not None) and vf2_call_limit is not None:
+ routing.append(
+ VF2PostLayout(
+ target,
+ coupling_map,
+ backend_properties,
+ seed_transpiler,
+ call_limit=vf2_call_limit,
+ strict_direction=False,
+ ),
+ condition=_run_post_layout_condition,
+ )
+ routing.append(ApplyLayout(), condition=_apply_post_layout_condition)
+
+ return routing
+
+
+def generate_pre_op_passmanager(target=None, coupling_map=None, remove_reset_in_zero=False):
+ """Generate a pre-optimization loop :class:`~qiskit.transpiler.PassManager`
+
+ This pass manager will check to ensure that directionality from the coupling
+ map is respected
+
+ Args:
+ target (Target): the :class:`~.Target` object representing the backend
+ coupling_map (CouplingMap): The coupling map to use
+ remove_reset_in_zero (bool): If ``True`` include the remove reset in
+ zero pass in the generated PassManager
+ Returns:
+ PassManager: The pass manager
+
+ """
+ pre_opt = PassManager()
+ if coupling_map:
+ pre_opt.append(CheckGateDirection(coupling_map, target=target))
+
+ def _direction_condition(property_set):
+ return not property_set["is_direction_mapped"]
+
+ pre_opt.append([GateDirection(coupling_map, target=target)], condition=_direction_condition)
+ if remove_reset_in_zero:
+ pre_opt.append(RemoveResetInZeroState())
+ return pre_opt
+
+
+def generate_translation_passmanager(
+ target,
+ basis_gates=None,
+ method="translator",
+ approximation_degree=None,
+ coupling_map=None,
+ backend_props=None,
+ unitary_synthesis_method="default",
+ unitary_synthesis_plugin_config=None,
+):
+ """Generate a basis translation :class:`~qiskit.transpiler.PassManager`
+
+ Args:
+ target (Target): the :class:`~.Target` object representing the backend
+ basis_gates (list): A list of str gate names that represent the basis
+ gates on the backend target
+ method (str): The basis translation method to use
+ approximation_degree (float): The heuristic approximation degree to
+ use. Can be between 0 and 1.
+ coupling_map (CouplingMap): the coupling map of the backend
+ in case synthesis is done on a physical circuit. The
+ directionality of the coupling_map will be taken into
+ account if pulse_optimize is True/None and natural_direction
+ is True/None.
+ unitary_synthesis_plugin_config (dict): The optional dictionary plugin
+ configuration, this is plugin specific refer to the specified plugin's
+ documenation for how to use.
+ backend_props (BackendProperties): Properties of a backend to
+ synthesize for (e.g. gate fidelities).
+ unitary_synthesis_method (str): The unitary synthesis method to use
+
+ Returns:
+ PassManager: The basis translation pass manager
+
+ Raises:
+ TranspilerError: If the ``method`` kwarg is not a valid value
+ """
+ if method == "unroller":
+ unroll = [Unroller(basis_gates)]
+ elif method == "translator":
+ unroll = [
+ # Use unitary synthesis for basis aware decomposition of
+ # UnitaryGates before custom unrolling
+ UnitarySynthesis(
+ basis_gates,
+ approximation_degree=approximation_degree,
+ coupling_map=coupling_map,
+ backend_props=backend_props,
+ plugin_config=unitary_synthesis_plugin_config,
+ method=unitary_synthesis_method,
+ target=target,
+ ),
+ UnrollCustomDefinitions(sel, basis_gates),
+ BasisTranslator(sel, basis_gates, target),
+ ]
+ elif method == "synthesis":
+ unroll = [
+ # # Use unitary synthesis for basis aware decomposition of
+ # UnitaryGates > 2q before collection
+ UnitarySynthesis(
+ basis_gates,
+ approximation_degree=approximation_degree,
+ coupling_map=coupling_map,
+ backend_props=backend_props,
+ plugin_config=unitary_synthesis_plugin_config,
+ method=unitary_synthesis_method,
+ min_qubits=3,
+ target=target,
+ ),
+ Unroll3qOrMore(target=target, basis_gates=basis_gates),
+ Collect2qBlocks(),
+ Collect1qRuns(),
+ ConsolidateBlocks(basis_gates=basis_gates, target=target),
+ UnitarySynthesis(
+ basis_gates=basis_gates,
+ approximation_degree=approximation_degree,
+ coupling_map=coupling_map,
+ backend_props=backend_props,
+ plugin_config=unitary_synthesis_plugin_config,
+ method=unitary_synthesis_method,
+ target=target,
+ ),
+ ]
+ else:
+ raise TranspilerError("Invalid translation method %s." % method)
+ return PassManager(unroll)
+
+
+def generate_scheduling(instruction_durations, scheduling_method, timing_constraints, inst_map):
+ """Generate a post optimization scheduling :class:`~qiskit.transpiler.PassManager`
+
+ Args:
+ instruction_durations (dict): The dictionary of instruction durations
+ scheduling_method (str): The scheduling method to use, can either be
+ ``'asap'``/``'as_soon_as_possible'`` or
+ ``'alap'``/``'as_late_as_possible'``
+ timing_constraints (TimingConstraints): Hardware time alignment restrictions.
+ inst_map (InstructionScheduleMap): Mapping object that maps gate to schedule.
+
+ Returns:
+ PassManager: The scheduling pass manager
+
+ Raises:
+ TranspilerError: If the ``scheduling_method`` kwarg is not a valid value
+ """
+ scheduling = PassManager()
+ if inst_map and inst_map.has_custom_gate():
+ scheduling.append(PulseGates(inst_map=inst_map))
+ if scheduling_method:
+ # Do scheduling after unit conversion.
+ scheduler = {
+ "alap": ALAPScheduleAnalysis,
+ "as_late_as_possible": ALAPScheduleAnalysis,
+ "asap": ASAPScheduleAnalysis,
+ "as_soon_as_possible": ASAPScheduleAnalysis,
+ }
+ scheduling.append(TimeUnitConversion(instruction_durations))
+ try:
+ scheduling.append(scheduler[scheduling_method](instruction_durations))
+ except KeyError as ex:
+ raise TranspilerError("Invalid scheduling method %s." % scheduling_method) from ex
+ elif instruction_durations:
+ # No scheduling. But do unit conversion for delays.
+ def _contains_delay(property_set):
+ return property_set["contains_delay"]
+
+ scheduling.append(ContainsInstruction("delay"))
+ scheduling.append(TimeUnitConversion(instruction_durations), condition=_contains_delay)
+ if (
+ timing_constraints.granularity != 1
+ or timing_constraints.min_length != 1
+ or timing_constraints.acquire_alignment != 1
+ or timing_constraints.pulse_alignment != 1
+ ):
+ # Run alignment analysis regardless of scheduling.
+
+ def _require_alignment(property_set):
+ return property_set["reschedule_required"]
+
+ scheduling.append(
+ InstructionDurationCheck(
+ acquire_alignment=timing_constraints.acquire_alignment,
+ pulse_alignment=timing_constraints.pulse_alignment,
+ )
+ )
+ scheduling.append(
+ ConstrainedReschedule(
+ acquire_alignment=timing_constraints.acquire_alignment,
+ pulse_alignment=timing_constraints.pulse_alignment,
+ ),
+ condition=_require_alignment,
+ )
+ scheduling.append(
+ ValidatePulseGates(
+ granularity=timing_constraints.granularity,
+ min_length=timing_constraints.min_length,
+ )
+ )
+ if scheduling_method:
+ # Call padding pass if circuit is scheduled
+ scheduling.append(PadDelay())
+
+ return scheduling
diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py
index 3c881cc1b306..37aadc1c4452 100644
--- a/qiskit/transpiler/preset_passmanagers/level0.py
+++ b/qiskit/transpiler/preset_passmanagers/level0.py
@@ -18,47 +18,24 @@
from qiskit.transpiler.passmanager_config import PassManagerConfig
from qiskit.transpiler.timing_constraints import TimingConstraints
from qiskit.transpiler.passmanager import PassManager
+from qiskit.transpiler.passmanager import StagedPassManager
-from qiskit.transpiler.passes import Unroller
-from qiskit.transpiler.passes import BasisTranslator
-from qiskit.transpiler.passes import UnrollCustomDefinitions
-from qiskit.transpiler.passes import Unroll3qOrMore
-from qiskit.transpiler.passes import CheckMap
-from qiskit.transpiler.passes import GateDirection
from qiskit.transpiler.passes import SetLayout
from qiskit.transpiler.passes import TrivialLayout
from qiskit.transpiler.passes import DenseLayout
from qiskit.transpiler.passes import NoiseAdaptiveLayout
from qiskit.transpiler.passes import SabreLayout
-from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements
from qiskit.transpiler.passes import BasicSwap
from qiskit.transpiler.passes import LookaheadSwap
from qiskit.transpiler.passes import StochasticSwap
from qiskit.transpiler.passes import SabreSwap
-from qiskit.transpiler.passes import FullAncillaAllocation
-from qiskit.transpiler.passes import EnlargeWithAncilla
-from qiskit.transpiler.passes import ApplyLayout
-from qiskit.transpiler.passes import CheckGateDirection
-from qiskit.transpiler.passes import Collect2qBlocks
-from qiskit.transpiler.passes import Collect1qRuns
-from qiskit.transpiler.passes import ConsolidateBlocks
-from qiskit.transpiler.passes import UnitarySynthesis
-from qiskit.transpiler.passes import TimeUnitConversion
-from qiskit.transpiler.passes import ALAPScheduleAnalysis
-from qiskit.transpiler.passes import ASAPScheduleAnalysis
-from qiskit.transpiler.passes import ConstrainedReschedule
-from qiskit.transpiler.passes import InstructionDurationCheck
-from qiskit.transpiler.passes import ValidatePulseGates
-from qiskit.transpiler.passes import PulseGates
-from qiskit.transpiler.passes import PadDelay
from qiskit.transpiler.passes import Error
-from qiskit.transpiler.passes import ContainsInstruction
-
+from qiskit.transpiler.preset_passmanagers import common
from qiskit.transpiler import TranspilerError
from qiskit.utils.optionals import HAS_TOQM
-def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
+def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassManager:
"""Level 0 pass manager: no explicit optimization other than mapping to backend.
This pass manager applies the user-given initial layout. If none is given, a trivial
@@ -68,10 +45,6 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
The pass manager then unrolls the circuit to the desired basis, and transforms the
circuit to match the coupling map.
- Note:
- In simulators where ``coupling_map=None``, only the unrolling and
- optimization stages are done.
-
Args:
pass_manager_config: configuration of the pass manager.
@@ -98,21 +71,7 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config
target = pass_manager_config.target
- # 1. Decompose so only 1-qubit and 2-qubit gates remain
- _unroll3q = [
- # Use unitary synthesis for basis aware decomposition of UnitaryGates
- UnitarySynthesis(
- basis_gates,
- approximation_degree=approximation_degree,
- method=unitary_synthesis_method,
- min_qubits=3,
- plugin_config=unitary_synthesis_plugin_config,
- target=target,
- ),
- Unroll3qOrMore(target=target, basis_gates=basis_gates),
- ]
-
- # 2. Choose an initial layout if not set by user (default: trivial layout)
+ # Choose an initial layout if not set by user (default: trivial layout)
_given_layout = SetLayout(initial_layout)
def _choose_layout_condition(property_set):
@@ -129,27 +88,16 @@ def _choose_layout_condition(property_set):
else:
raise TranspilerError("Invalid layout method %s." % layout_method)
- # 3. Extend dag/layout with ancillas using the full coupling map
- _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()]
-
- # 4. Swap to fit the coupling map
- _swap_check = CheckMap(coupling_map)
-
- def _swap_condition(property_set):
- return not property_set["is_swap_mapped"]
-
- def _swap_needs_basis(property_set):
- return _swap_condition(property_set) and routing_method == "toqm"
-
- _swap = [BarrierBeforeFinalMeasurements()]
+ toqm_pass = False
+ # Choose routing pass
if routing_method == "basic":
- _swap += [BasicSwap(coupling_map)]
+ routing_pass = BasicSwap(coupling_map)
elif routing_method == "stochastic":
- _swap += [StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)]
+ routing_pass = StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)
elif routing_method == "lookahead":
- _swap += [LookaheadSwap(coupling_map, search_depth=2, search_width=2)]
+ routing_pass = LookaheadSwap(coupling_map, search_depth=2, search_width=2)
elif routing_method == "sabre":
- _swap += [SabreSwap(coupling_map, heuristic="basic", seed=seed_transpiler)]
+ routing_pass = SabreSwap(coupling_map, heuristic="basic", seed=seed_transpiler)
elif routing_method == "toqm":
HAS_TOQM.require_now("TOQM-based routing")
from qiskit_toqm import ToqmSwap, ToqmStrategyO0, latencies_from_target
@@ -157,162 +105,81 @@ def _swap_needs_basis(property_set):
if initial_layout:
raise TranspilerError("Initial layouts are not supported with TOQM-based routing.")
+ toqm_pass = True
# Note: BarrierBeforeFinalMeasurements is skipped intentionally since ToqmSwap
# does not yet support barriers.
- _swap = [
- ToqmSwap(
- coupling_map,
- strategy=ToqmStrategyO0(
- latencies_from_target(
- coupling_map, instruction_durations, basis_gates, backend_properties, target
- )
- ),
- )
- ]
+ routing_pass = ToqmSwap(
+ coupling_map,
+ strategy=ToqmStrategyO0(
+ latencies_from_target(
+ coupling_map, instruction_durations, basis_gates, backend_properties, target
+ )
+ ),
+ )
elif routing_method == "none":
- _swap += [
- Error(
- msg=(
- "No routing method selected, but circuit is not routed to device. "
- "CheckMap Error: {check_map_msg}"
- ),
- action="raise",
- )
- ]
+ routing_pass = Error(
+ msg="No routing method selected, but circuit is not routed to device. "
+ "CheckMap Error: {check_map_msg}",
+ action="raise",
+ )
else:
raise TranspilerError("Invalid routing method %s." % routing_method)
- # 5. Unroll to the basis
- if translation_method == "unroller":
- _unroll = [Unroller(basis_gates)]
- elif translation_method == "translator":
- from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel
-
- _unroll = [
- UnitarySynthesis(
- basis_gates,
- approximation_degree=approximation_degree,
- coupling_map=coupling_map,
- backend_props=backend_properties,
- method=unitary_synthesis_method,
- plugin_config=unitary_synthesis_plugin_config,
- target=target,
- ),
- UnrollCustomDefinitions(sel, basis_gates),
- BasisTranslator(sel, basis_gates, target),
- ]
- elif translation_method == "synthesis":
- _unroll = [
- UnitarySynthesis(
- basis_gates,
- approximation_degree=approximation_degree,
- coupling_map=coupling_map,
- backend_props=backend_properties,
- method=unitary_synthesis_method,
- min_qubits=3,
- plugin_config=unitary_synthesis_plugin_config,
- target=target,
- ),
- Unroll3qOrMore(target=target, basis_gates=basis_gates),
- Collect2qBlocks(),
- Collect1qRuns(),
- ConsolidateBlocks(basis_gates=basis_gates, target=target),
- UnitarySynthesis(
- basis_gates,
- approximation_degree=approximation_degree,
- coupling_map=coupling_map,
- backend_props=backend_properties,
- method=unitary_synthesis_method,
- plugin_config=unitary_synthesis_plugin_config,
- target=target,
- ),
- ]
- else:
- raise TranspilerError("Invalid translation method %s." % translation_method)
-
- # 6. Fix any bad CX directions
- _direction_check = [CheckGateDirection(coupling_map, target)]
-
- def _direction_condition(property_set):
- return not property_set["is_direction_mapped"]
-
- _direction = [GateDirection(coupling_map, target)]
-
+ unroll_3q = None
# Build pass manager
- pm0 = PassManager()
if coupling_map or initial_layout:
- pm0.append(_given_layout)
- pm0.append(_unroll3q)
- pm0.append(_choose_layout, condition=_choose_layout_condition)
- pm0.append(_embed)
- pm0.append(_swap_check)
- pm0.append(_unroll, condition=_swap_needs_basis)
- pm0.append(_swap, condition=_swap_condition)
- pm0.append(_unroll)
- if (coupling_map and not coupling_map.is_symmetric) or (
- target is not None and target.get_non_global_operation_names(strict_direction=True)
- ):
- pm0.append(_direction_check)
- pm0.append(_direction, condition=_direction_condition)
- pm0.append(_unroll)
- if inst_map and inst_map.has_custom_gate():
- pm0.append(PulseGates(inst_map=inst_map))
-
- # 7. Unify all durations (either SI, or convert to dt if known)
- # Schedule the circuit only when scheduling_method is supplied
- # Apply alignment analysis regardless of scheduling for delay validation.
- if scheduling_method:
- # Do scheduling after unit conversion.
- scheduler = {
- "alap": ALAPScheduleAnalysis,
- "as_late_as_possible": ALAPScheduleAnalysis,
- "asap": ASAPScheduleAnalysis,
- "as_soon_as_possible": ASAPScheduleAnalysis,
- }
- pm0.append(TimeUnitConversion(instruction_durations))
- try:
- pm0.append(scheduler[scheduling_method](instruction_durations))
- except KeyError as ex:
- raise TranspilerError("Invalid scheduling method %s." % scheduling_method) from ex
- elif instruction_durations:
- # No scheduling. But do unit conversion for delays.
- def _contains_delay(property_set):
- return property_set["contains_delay"]
-
- pm0.append(ContainsInstruction("delay"))
- pm0.append(TimeUnitConversion(instruction_durations), condition=_contains_delay)
- if (
- timing_constraints.granularity != 1
- or timing_constraints.min_length != 1
- or timing_constraints.acquire_alignment != 1
- or timing_constraints.pulse_alignment != 1
- ):
- # Run alignment analysis regardless of scheduling.
-
- def _require_alignment(property_set):
- return property_set["reschedule_required"]
-
- pm0.append(
- InstructionDurationCheck(
- acquire_alignment=timing_constraints.acquire_alignment,
- pulse_alignment=timing_constraints.pulse_alignment,
- )
- )
- pm0.append(
- ConstrainedReschedule(
- acquire_alignment=timing_constraints.acquire_alignment,
- pulse_alignment=timing_constraints.pulse_alignment,
- ),
- condition=_require_alignment,
+ unroll_3q = common.generate_unroll_3q(
+ target,
+ basis_gates,
+ approximation_degree,
+ unitary_synthesis_method,
+ unitary_synthesis_plugin_config,
)
- pm0.append(
- ValidatePulseGates(
- granularity=timing_constraints.granularity,
- min_length=timing_constraints.min_length,
- )
+ layout = PassManager()
+ layout.append(_given_layout)
+ layout.append(_choose_layout, condition=_choose_layout_condition)
+ layout += common.generate_embed_passmanager(coupling_map)
+ routing = common.generate_routing_passmanager(
+ routing_pass,
+ target,
+ coupling_map=coupling_map,
+ seed_transpiler=seed_transpiler,
+ use_barrier_before_measurement=not toqm_pass,
)
- if scheduling_method:
- # Call padding pass if circuit is scheduled
- pm0.append(PadDelay())
+ else:
+ layout = None
+ routing = None
+ translation = common.generate_translation_passmanager(
+ target,
+ basis_gates,
+ translation_method,
+ approximation_degree,
+ coupling_map,
+ backend_properties,
+ unitary_synthesis_method,
+ unitary_synthesis_plugin_config,
+ )
+ pre_routing = None
+ if toqm_pass:
+ pre_routing = translation
- return pm0
+ if (coupling_map and not coupling_map.is_symmetric) or (
+ target is not None and target.get_non_global_operation_names(strict_direction=True)
+ ):
+ pre_opt = common.generate_pre_op_passmanager(target, coupling_map)
+ pre_opt += translation
+ else:
+ pre_opt = None
+ sched = common.generate_scheduling(
+ instruction_durations, scheduling_method, timing_constraints, inst_map
+ )
+
+ return StagedPassManager(
+ init=unroll_3q,
+ layout=layout,
+ pre_routing=pre_routing,
+ routing=routing,
+ translation=translation,
+ pre_optimization=pre_opt,
+ scheduling=sched,
+ )
diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py
index e24b40f7d875..5da52fb23052 100644
--- a/qiskit/transpiler/preset_passmanagers/level1.py
+++ b/qiskit/transpiler/preset_passmanagers/level1.py
@@ -18,57 +18,33 @@
from qiskit.transpiler.passmanager_config import PassManagerConfig
from qiskit.transpiler.timing_constraints import TimingConstraints
from qiskit.transpiler.passmanager import PassManager
+from qiskit.transpiler.passmanager import StagedPassManager
-from qiskit.transpiler.passes import Unroller
-from qiskit.transpiler.passes import BasisTranslator
-from qiskit.transpiler.passes import UnrollCustomDefinitions
-from qiskit.transpiler.passes import Unroll3qOrMore
from qiskit.transpiler.passes import CXCancellation
-from qiskit.transpiler.passes import CheckMap
-from qiskit.transpiler.passes import GateDirection
from qiskit.transpiler.passes import SetLayout
from qiskit.transpiler.passes import VF2Layout
-from qiskit.transpiler.passes import VF2PostLayout
from qiskit.transpiler.passes import TrivialLayout
from qiskit.transpiler.passes import DenseLayout
from qiskit.transpiler.passes import NoiseAdaptiveLayout
from qiskit.transpiler.passes import SabreLayout
-from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements
-from qiskit.transpiler.passes import Layout2qDistance
from qiskit.transpiler.passes import BasicSwap
from qiskit.transpiler.passes import LookaheadSwap
from qiskit.transpiler.passes import StochasticSwap
from qiskit.transpiler.passes import SabreSwap
-from qiskit.transpiler.passes import FullAncillaAllocation
-from qiskit.transpiler.passes import EnlargeWithAncilla
from qiskit.transpiler.passes import FixedPoint
from qiskit.transpiler.passes import Depth
from qiskit.transpiler.passes import Size
-from qiskit.transpiler.passes import RemoveResetInZeroState
from qiskit.transpiler.passes import Optimize1qGatesDecomposition
-from qiskit.transpiler.passes import ApplyLayout
-from qiskit.transpiler.passes import CheckGateDirection
-from qiskit.transpiler.passes import Collect2qBlocks
-from qiskit.transpiler.passes import ConsolidateBlocks
-from qiskit.transpiler.passes import UnitarySynthesis
-from qiskit.transpiler.passes import TimeUnitConversion
-from qiskit.transpiler.passes import ALAPScheduleAnalysis
-from qiskit.transpiler.passes import ASAPScheduleAnalysis
-from qiskit.transpiler.passes import ConstrainedReschedule
-from qiskit.transpiler.passes import InstructionDurationCheck
-from qiskit.transpiler.passes import ValidatePulseGates
-from qiskit.transpiler.passes import PulseGates
-from qiskit.transpiler.passes import PadDelay
+from qiskit.transpiler.passes import Layout2qDistance
from qiskit.transpiler.passes import Error
-from qiskit.transpiler.passes import ContainsInstruction
+from qiskit.transpiler.preset_passmanagers import common
from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason
-from qiskit.transpiler.passes.layout.vf2_post_layout import VF2PostLayoutStopReason
from qiskit.transpiler import TranspilerError
from qiskit.utils.optionals import HAS_TOQM
-def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
+def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassManager:
"""Level 1 pass manager: light optimization by simple adjacent gate collapsing.
This pass manager applies the user-given initial layout. If none is given,
@@ -80,10 +56,6 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
circuit to match the coupling map. Finally, optimizations in the form of adjacent
gate collapse and redundant reset removal are performed.
- Note:
- In simulators where ``coupling_map=None``, only the unrolling and
- optimization stages are done.
-
Args:
pass_manager_config: configuration of the pass manager.
@@ -110,7 +82,7 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
timing_constraints = pass_manager_config.timing_constraints or TimingConstraints()
target = pass_manager_config.target
- # 1. Use trivial layout if no layout given if that isn't perfect use vf2 layout
+ # Use trivial layout if no layout given
_given_layout = SetLayout(initial_layout)
def _choose_layout_condition(property_set):
@@ -127,6 +99,7 @@ def _trivial_not_perfect(property_set):
return True
return False
+ # Use a better layout on densely connected qubits, if circuit needs swaps
def _vf2_match_not_found(property_set):
# If a layout hasn't been set by the time we run vf2 layout we need to
# run layout
@@ -162,21 +135,6 @@ def _vf2_match_not_found(property_set):
)
)
- # 2. Decompose so only 1-qubit and 2-qubit gates remain
- _unroll3q = [
- # Use unitary synthesis for basis aware decomposition of UnitaryGates
- UnitarySynthesis(
- basis_gates,
- approximation_degree=approximation_degree,
- method=unitary_synthesis_method,
- min_qubits=3,
- plugin_config=unitary_synthesis_plugin_config,
- target=target,
- ),
- Unroll3qOrMore(target=target, basis_gates=basis_gates),
- ]
-
- # 3. Use a better layout on densely connected qubits, if circuit needs swaps
if layout_method == "trivial":
_improve_layout = TrivialLayout(coupling_map)
elif layout_method == "dense":
@@ -188,27 +146,15 @@ def _vf2_match_not_found(property_set):
else:
raise TranspilerError("Invalid layout method %s." % layout_method)
- # 4. Extend dag/layout with ancillas using the full coupling map
- _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()]
-
- # 5. Swap to fit the coupling map
- _swap_check = CheckMap(coupling_map)
-
- def _swap_condition(property_set):
- return not property_set["is_swap_mapped"]
-
- def _swap_needs_basis(property_set):
- return _swap_condition(property_set) and routing_method == "toqm"
-
- _swap = [BarrierBeforeFinalMeasurements()]
+ toqm_pass = False
if routing_method == "basic":
- _swap += [BasicSwap(coupling_map)]
+ routing_pass = BasicSwap(coupling_map)
elif routing_method == "stochastic":
- _swap += [StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)]
+ routing_pass = StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)
elif routing_method == "lookahead":
- _swap += [LookaheadSwap(coupling_map, search_depth=4, search_width=4)]
+ routing_pass = LookaheadSwap(coupling_map, search_depth=4, search_width=4)
elif routing_method == "sabre":
- _swap += [SabreSwap(coupling_map, heuristic="lookahead", seed=seed_transpiler)]
+ routing_pass = SabreSwap(coupling_map, heuristic="lookahead", seed=seed_transpiler)
elif routing_method == "toqm":
HAS_TOQM.require_now("TOQM-based routing")
from qiskit_toqm import ToqmSwap, ToqmStrategyO1, latencies_from_target
@@ -216,94 +162,28 @@ def _swap_needs_basis(property_set):
if initial_layout:
raise TranspilerError("Initial layouts are not supported with TOQM-based routing.")
+ toqm_pass = True
# Note: BarrierBeforeFinalMeasurements is skipped intentionally since ToqmSwap
# does not yet support barriers.
- _swap = [
- ToqmSwap(
- coupling_map,
- strategy=ToqmStrategyO1(
- latencies_from_target(
- coupling_map, instruction_durations, basis_gates, backend_properties, target
- )
- ),
- )
- ]
+ routing_pass = ToqmSwap(
+ coupling_map,
+ strategy=ToqmStrategyO1(
+ latencies_from_target(
+ coupling_map, instruction_durations, basis_gates, backend_properties, target
+ )
+ ),
+ )
elif routing_method == "none":
- _swap += [
- Error(
- msg=(
- "No routing method selected, but circuit is not routed to device. "
- "CheckMap Error: {check_map_msg}"
- ),
- action="raise",
- )
- ]
+ routing_pass = Error(
+ msg="No routing method selected, but circuit is not routed to device. "
+ "CheckMap Error: {check_map_msg}",
+ action="raise",
+ )
else:
raise TranspilerError("Invalid routing method %s." % routing_method)
- # 6. Unroll to the basis
- if translation_method == "unroller":
- _unroll = [Unroller(basis_gates)]
- elif translation_method == "translator":
- from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel
-
- _unroll = [
- # Use unitary synthesis for basis aware decomposition of UnitaryGates before
- # custom unrolling
- UnitarySynthesis(
- basis_gates,
- approximation_degree=approximation_degree,
- coupling_map=coupling_map,
- method=unitary_synthesis_method,
- backend_props=backend_properties,
- plugin_config=unitary_synthesis_plugin_config,
- target=target,
- ),
- UnrollCustomDefinitions(sel, basis_gates),
- BasisTranslator(sel, basis_gates, target),
- ]
- elif translation_method == "synthesis":
- _unroll = [
- # Use unitary synthesis for basis aware decomposition of UnitaryGates before
- # collection
- UnitarySynthesis(
- basis_gates,
- approximation_degree=approximation_degree,
- coupling_map=coupling_map,
- method=unitary_synthesis_method,
- backend_props=backend_properties,
- min_qubits=3,
- target=target,
- ),
- Unroll3qOrMore(target=target, basis_gates=basis_gates),
- Collect2qBlocks(),
- ConsolidateBlocks(basis_gates=basis_gates, target=target),
- UnitarySynthesis(
- basis_gates,
- approximation_degree=approximation_degree,
- coupling_map=coupling_map,
- method=unitary_synthesis_method,
- backend_props=backend_properties,
- plugin_config=unitary_synthesis_plugin_config,
- target=target,
- ),
- ]
- else:
- raise TranspilerError("Invalid translation method %s." % translation_method)
-
- # 7. Fix any bad CX directions
- _direction_check = [CheckGateDirection(coupling_map, target)]
-
- def _direction_condition(property_set):
- return not property_set["is_direction_mapped"]
-
- _direction = [GateDirection(coupling_map, target)]
-
- # 8. Remove zero-state reset
- _reset = RemoveResetInZeroState()
-
- # 9. Merge 1q rotations and cancel CNOT gates iteratively until no more change in depth
- # or size of circuit
+ # Build optimization loop: merge 1q rotations and cancel CNOT gates iteratively
+ # until no more change in depth
_depth_check = [Depth(), FixedPoint("depth")]
_size_check = [Size(), FixedPoint("size")]
@@ -312,125 +192,74 @@ def _opt_control(property_set):
_opt = [Optimize1qGatesDecomposition(basis_gates), CXCancellation()]
- # Build pass manager
- pm1 = PassManager()
+ unroll_3q = None
+ # Build full pass manager
if coupling_map or initial_layout:
- pm1.append(_given_layout)
- pm1.append(_unroll3q)
- pm1.append(_choose_layout_0, condition=_choose_layout_condition)
- pm1.append(_choose_layout_1, condition=_trivial_not_perfect)
- pm1.append(_improve_layout, condition=_vf2_match_not_found)
- pm1.append(_embed)
- pm1.append(_swap_check)
- pm1.append(_unroll, condition=_swap_needs_basis)
- pm1.append(_swap, condition=_swap_condition)
- if (
- (coupling_map and backend_properties)
- and initial_layout is None
- and pass_manager_config.layout_method is None
- ):
-
- def _run_post_layout_condition(property_set):
- if _trivial_not_perfect(property_set):
- vf2_stop_reason = property_set["VF2Layout_stop_reason"]
- if (
- vf2_stop_reason is None
- or vf2_stop_reason != VF2LayoutStopReason.SOLUTION_FOUND
- ):
- return True
- return False
-
- def _apply_post_layout_condition(property_set):
- # if VF2 Post layout found a solution we need to re-apply the better
- # layout. Otherwise we can skip apply layout.
- if (
- property_set["VF2PostLayout_stop_reason"] is not None
- and property_set["VF2PostLayout_stop_reason"]
- is VF2PostLayoutStopReason.SOLUTION_FOUND
- ):
- return True
- return False
+ unroll_3q = common.generate_unroll_3q(
+ target,
+ basis_gates,
+ approximation_degree,
+ unitary_synthesis_method,
+ unitary_synthesis_plugin_config,
+ )
+ layout = PassManager()
+ layout.append(_given_layout)
+ layout.append(_choose_layout_0, condition=_choose_layout_condition)
+ layout.append(_choose_layout_1, condition=_trivial_not_perfect)
+ layout.append(_improve_layout, condition=_vf2_match_not_found)
+ layout += common.generate_embed_passmanager(coupling_map)
+ vf2_call_limit = None
+ if pass_manager_config.layout_method is None and pass_manager_config.initial_layout is None:
+ vf2_call_limit = int(5e4) # Set call limit to ~100ms with retworkx 0.10.2
+ routing = common.generate_routing_passmanager(
+ routing_pass,
+ target,
+ coupling_map,
+ vf2_call_limit=vf2_call_limit,
+ backend_properties=backend_properties,
+ seed_transpiler=seed_transpiler,
+ check_trivial=True,
+ use_barrier_before_measurement=not toqm_pass,
+ )
+ else:
+ layout = None
+ routing = None
+ translation = common.generate_translation_passmanager(
+ target,
+ basis_gates,
+ translation_method,
+ approximation_degree,
+ coupling_map,
+ backend_properties,
+ unitary_synthesis_method,
+ unitary_synthesis_plugin_config,
+ )
+ pre_routing = None
+ if toqm_pass:
+ pre_routing = translation
- pm1.append(
- VF2PostLayout(
- target,
- coupling_map,
- backend_properties,
- seed_transpiler,
- call_limit=int(5e4), # Set call limit to ~100ms with retworkx 0.10.2
- strict_direction=False,
- ),
- condition=_run_post_layout_condition,
- )
- pm1.append(ApplyLayout(), condition=_apply_post_layout_condition)
- pm1.append(_unroll)
if (coupling_map and not coupling_map.is_symmetric) or (
target is not None and target.get_non_global_operation_names(strict_direction=True)
):
- pm1.append(_direction_check)
- pm1.append(_direction, condition=_direction_condition)
- pm1.append(_reset)
- pm1.append(_depth_check + _size_check)
- pm1.append(_opt + _unroll + _depth_check + _size_check, do_while=_opt_control)
-
- if inst_map and inst_map.has_custom_gate():
- pm1.append(PulseGates(inst_map=inst_map))
-
- # 10. Unify all durations (either SI, or convert to dt if known)
- # Schedule the circuit only when scheduling_method is supplied
- # Apply alignment analysis regardless of scheduling for delay validation.
- if scheduling_method:
- # Do scheduling after unit conversion.
- scheduler = {
- "alap": ALAPScheduleAnalysis,
- "as_late_as_possible": ALAPScheduleAnalysis,
- "asap": ASAPScheduleAnalysis,
- "as_soon_as_possible": ASAPScheduleAnalysis,
- }
- pm1.append(TimeUnitConversion(instruction_durations))
- try:
- pm1.append(scheduler[scheduling_method](instruction_durations))
- except KeyError as ex:
- raise TranspilerError("Invalid scheduling method %s." % scheduling_method) from ex
- elif instruction_durations:
- # No scheduling. But do unit conversion for delays.
- def _contains_delay(property_set):
- return property_set["contains_delay"]
-
- pm1.append(ContainsInstruction("delay"))
- pm1.append(TimeUnitConversion(instruction_durations), condition=_contains_delay)
- if (
- timing_constraints.granularity != 1
- or timing_constraints.min_length != 1
- or timing_constraints.acquire_alignment != 1
- or timing_constraints.pulse_alignment != 1
- ):
- # Run alignment analysis regardless of scheduling.
-
- def _require_alignment(property_set):
- return property_set["reschedule_required"]
-
- pm1.append(
- InstructionDurationCheck(
- acquire_alignment=timing_constraints.acquire_alignment,
- pulse_alignment=timing_constraints.pulse_alignment,
- )
- )
- pm1.append(
- ConstrainedReschedule(
- acquire_alignment=timing_constraints.acquire_alignment,
- pulse_alignment=timing_constraints.pulse_alignment,
- ),
- condition=_require_alignment,
- )
- pm1.append(
- ValidatePulseGates(
- granularity=timing_constraints.granularity,
- min_length=timing_constraints.min_length,
- )
- )
- if scheduling_method:
- # Call padding pass if circuit is scheduled
- pm1.append(PadDelay())
+ pre_optimization = common.generate_pre_op_passmanager(target, coupling_map, True)
+ else:
+ pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=True)
+ optimization = PassManager()
+ unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]]
+ optimization.append(_depth_check + _size_check)
+ opt_loop = _opt + unroll + _depth_check + _size_check
+ optimization.append(opt_loop, do_while=_opt_control)
+ sched = common.generate_scheduling(
+ instruction_durations, scheduling_method, timing_constraints, inst_map
+ )
- return pm1
+ return StagedPassManager(
+ init=unroll_3q,
+ layout=layout,
+ pre_routing=pre_routing,
+ routing=routing,
+ translation=translation,
+ pre_optimization=pre_optimization,
+ optimization=optimization,
+ scheduling=sched,
+ )
diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py
index 5c0705745584..c2ee13ad8500 100644
--- a/qiskit/transpiler/preset_passmanagers/level2.py
+++ b/qiskit/transpiler/preset_passmanagers/level2.py
@@ -19,56 +19,32 @@
from qiskit.transpiler.passmanager_config import PassManagerConfig
from qiskit.transpiler.timing_constraints import TimingConstraints
from qiskit.transpiler.passmanager import PassManager
+from qiskit.transpiler.passmanager import StagedPassManager
-from qiskit.transpiler.passes import Unroller
-from qiskit.transpiler.passes import BasisTranslator
-from qiskit.transpiler.passes import UnrollCustomDefinitions
-from qiskit.transpiler.passes import Unroll3qOrMore
-from qiskit.transpiler.passes import CheckMap
-from qiskit.transpiler.passes import GateDirection
from qiskit.transpiler.passes import SetLayout
from qiskit.transpiler.passes import VF2Layout
-from qiskit.transpiler.passes import VF2PostLayout
from qiskit.transpiler.passes import TrivialLayout
from qiskit.transpiler.passes import DenseLayout
from qiskit.transpiler.passes import NoiseAdaptiveLayout
from qiskit.transpiler.passes import SabreLayout
-from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements
from qiskit.transpiler.passes import BasicSwap
from qiskit.transpiler.passes import LookaheadSwap
from qiskit.transpiler.passes import StochasticSwap
from qiskit.transpiler.passes import SabreSwap
-from qiskit.transpiler.passes import FullAncillaAllocation
-from qiskit.transpiler.passes import EnlargeWithAncilla
from qiskit.transpiler.passes import FixedPoint
from qiskit.transpiler.passes import Depth
from qiskit.transpiler.passes import Size
-from qiskit.transpiler.passes import RemoveResetInZeroState
from qiskit.transpiler.passes import Optimize1qGatesDecomposition
from qiskit.transpiler.passes import CommutativeCancellation
-from qiskit.transpiler.passes import ApplyLayout
-from qiskit.transpiler.passes import CheckGateDirection
-from qiskit.transpiler.passes import Collect2qBlocks
-from qiskit.transpiler.passes import ConsolidateBlocks
-from qiskit.transpiler.passes import UnitarySynthesis
-from qiskit.transpiler.passes import TimeUnitConversion
-from qiskit.transpiler.passes import ALAPScheduleAnalysis
-from qiskit.transpiler.passes import ASAPScheduleAnalysis
-from qiskit.transpiler.passes import ConstrainedReschedule
-from qiskit.transpiler.passes import InstructionDurationCheck
-from qiskit.transpiler.passes import ValidatePulseGates
-from qiskit.transpiler.passes import PulseGates
-from qiskit.transpiler.passes import PadDelay
from qiskit.transpiler.passes import Error
-from qiskit.transpiler.passes import ContainsInstruction
+from qiskit.transpiler.preset_passmanagers import common
from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason
-from qiskit.transpiler.passes.layout.vf2_post_layout import VF2PostLayoutStopReason
from qiskit.transpiler import TranspilerError
from qiskit.utils.optionals import HAS_TOQM
-def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
+def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassManager:
"""Level 2 pass manager: medium optimization by initial layout selection and
gate cancellation using commutativity rules.
@@ -82,10 +58,6 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
Finally, optimizations in the form of commutative gate cancellation and redundant
reset removal are performed.
- Note:
- In simulators where ``coupling_map=None``, only the unrolling and
- optimization stages are done.
-
Args:
pass_manager_config: configuration of the pass manager.
@@ -112,21 +84,7 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config
target = pass_manager_config.target
- # 1. Unroll to 1q or 2q gates
- _unroll3q = [
- # Use unitary synthesis for basis aware decomposition of UnitaryGates
- UnitarySynthesis(
- basis_gates,
- approximation_degree=approximation_degree,
- method=unitary_synthesis_method,
- min_qubits=3,
- plugin_config=unitary_synthesis_plugin_config,
- target=target,
- ),
- Unroll3qOrMore(target=target, basis_gates=basis_gates),
- ]
-
- # 2. Search for a perfect layout, or choose a dense layout, if no layout given
+ # Search for a perfect layout, or choose a dense layout, if no layout given
_given_layout = SetLayout(initial_layout)
def _choose_layout_condition(property_set):
@@ -147,7 +105,7 @@ def _vf2_match_not_found(property_set):
return True
return False
- # 2a. Try using VF2 layout to find a perfect layout
+ # Try using VF2 layout to find a perfect layout
_choose_layout_0 = (
[]
if pass_manager_config.layout_method
@@ -160,7 +118,6 @@ def _vf2_match_not_found(property_set):
)
)
- # 2b. if VF2 layout doesn't converge on a solution use layout_method (dense) to get a layout
if layout_method == "trivial":
_choose_layout_1 = TrivialLayout(coupling_map)
elif layout_method == "dense":
@@ -172,123 +129,44 @@ def _vf2_match_not_found(property_set):
else:
raise TranspilerError("Invalid layout method %s." % layout_method)
- # 3. Extend dag/layout with ancillas using the full coupling map
- _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()]
-
- # 4. Swap to fit the coupling map
- _swap_check = CheckMap(coupling_map)
-
- def _swap_condition(property_set):
- return not property_set["is_swap_mapped"]
-
- def _swap_needs_basis(property_set):
- return _swap_condition(property_set) and routing_method == "toqm"
-
- _swap = [BarrierBeforeFinalMeasurements()]
+ toqm_pass = False
if routing_method == "basic":
- _swap += [BasicSwap(coupling_map)]
+ routing_pass = BasicSwap(coupling_map)
elif routing_method == "stochastic":
- _swap += [StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)]
+ routing_pass = StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)
elif routing_method == "lookahead":
- _swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=5)]
+ routing_pass = LookaheadSwap(coupling_map, search_depth=5, search_width=5)
elif routing_method == "sabre":
- _swap += [SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler)]
+ routing_pass = SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler)
elif routing_method == "toqm":
HAS_TOQM.require_now("TOQM-based routing")
from qiskit_toqm import ToqmSwap, ToqmStrategyO2, latencies_from_target
if initial_layout:
raise TranspilerError("Initial layouts are not supported with TOQM-based routing.")
+ toqm_pass = True
# Note: BarrierBeforeFinalMeasurements is skipped intentionally since ToqmSwap
# does not yet support barriers.
- _swap = [
- ToqmSwap(
- coupling_map,
- strategy=ToqmStrategyO2(
- latencies_from_target(
- coupling_map, instruction_durations, basis_gates, backend_properties, target
- )
- ),
- )
- ]
+ routing_pass = ToqmSwap(
+ coupling_map,
+ strategy=ToqmStrategyO2(
+ latencies_from_target(
+ coupling_map, instruction_durations, basis_gates, backend_properties, target
+ )
+ ),
+ )
elif routing_method == "none":
- _swap += [
- Error(
- msg=(
- "No routing method selected, but circuit is not routed to device. "
- "CheckMap Error: {check_map_msg}"
- ),
- action="raise",
- )
- ]
+ routing_pass = Error(
+ msg="No routing method selected, but circuit is not routed to device. "
+ "CheckMap Error: {check_map_msg}",
+ action="raise",
+ )
else:
raise TranspilerError("Invalid routing method %s." % routing_method)
- # 5. Unroll to the basis
- if translation_method == "unroller":
- _unroll = [Unroller(basis_gates)]
- elif translation_method == "translator":
- from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel
-
- _unroll = [
- # Use unitary synthesis for basis aware decomposition of UnitaryGates before
- # custom unrolling
- UnitarySynthesis(
- basis_gates,
- approximation_degree=approximation_degree,
- coupling_map=coupling_map,
- backend_props=backend_properties,
- method=unitary_synthesis_method,
- plugin_config=unitary_synthesis_plugin_config,
- target=target,
- ),
- UnrollCustomDefinitions(sel, basis_gates),
- BasisTranslator(sel, basis_gates, target),
- ]
- elif translation_method == "synthesis":
- _unroll = [
- # Use unitary synthesis for basis aware decomposition of UnitaryGates before
- # collection
- UnitarySynthesis(
- basis_gates,
- approximation_degree=approximation_degree,
- coupling_map=coupling_map,
- backend_props=backend_properties,
- method=unitary_synthesis_method,
- plugin_config=unitary_synthesis_plugin_config,
- min_qubits=3,
- target=target,
- ),
- Unroll3qOrMore(target=target, basis_gates=basis_gates),
- Collect2qBlocks(),
- ConsolidateBlocks(basis_gates=basis_gates, target=target),
- UnitarySynthesis(
- basis_gates,
- approximation_degree=approximation_degree,
- coupling_map=coupling_map,
- backend_props=backend_properties,
- method=unitary_synthesis_method,
- plugin_config=unitary_synthesis_plugin_config,
- target=target,
- ),
- ]
- else:
- raise TranspilerError("Invalid translation method %s." % translation_method)
-
- # 6. Fix any bad CX directions
- _direction_check = [CheckGateDirection(coupling_map, target)]
-
- def _direction_condition(property_set):
- return not property_set["is_direction_mapped"]
-
- _direction = [GateDirection(coupling_map, target)]
-
- # 7. Remove zero-state reset
- _reset = RemoveResetInZeroState()
-
- # 8. 1q rotation merge and commutative cancellation iteratively until no more change in depth
- # and size
+ # Build optimization loop: 1q rotation merge and commutative cancellation iteratively until
+ # no more change in depth
_depth_check = [Depth(), FixedPoint("depth")]
_size_check = [Size(), FixedPoint("size")]
@@ -300,125 +178,70 @@ def _opt_control(property_set):
CommutativeCancellation(basis_gates=basis_gates),
]
+ unroll_3q = None
# Build pass manager
- pm2 = PassManager()
if coupling_map or initial_layout:
- pm2.append(_given_layout)
- pm2.append(_unroll3q)
- pm2.append(_choose_layout_0, condition=_choose_layout_condition)
- pm2.append(_choose_layout_1, condition=_vf2_match_not_found)
- pm2.append(_embed)
- pm2.append(_swap_check)
- pm2.append(_unroll, condition=_swap_needs_basis)
- pm2.append(_swap, condition=_swap_condition)
- if (
- (coupling_map and backend_properties)
- and initial_layout is None
- and pass_manager_config.layout_method is None
- ):
-
- def _run_post_layout_condition(property_set):
- vf2_stop_reason = property_set["VF2Layout_stop_reason"]
- if (
- vf2_stop_reason is not None
- and vf2_stop_reason == VF2LayoutStopReason.SOLUTION_FOUND
- ):
- return False
- return True
-
- def _apply_post_layout_condition(property_set):
- # if VF2 Post layout found a solution we need to re-apply the better
- # layout. Otherwise we can skip apply layout.
- if (
- property_set["VF2PostLayout_stop_reason"] is not None
- and property_set["VF2PostLayout_stop_reason"]
- is VF2PostLayoutStopReason.SOLUTION_FOUND
- ):
- return True
- return False
-
- pm2.append(
- VF2PostLayout(
- target,
- coupling_map,
- backend_properties,
- seed_transpiler,
- call_limit=int(5e6), # Set call limit to ~10 sec with retworkx 0.10.2
- strict_direction=False,
- ),
- condition=_run_post_layout_condition,
- )
- pm2.append(ApplyLayout(), condition=_apply_post_layout_condition)
-
- pm2.append(_unroll)
+ unroll_3q = common.generate_unroll_3q(
+ target,
+ basis_gates,
+ approximation_degree,
+ unitary_synthesis_method,
+ unitary_synthesis_plugin_config,
+ )
+ layout = PassManager()
+ layout.append(_given_layout)
+ layout.append(_choose_layout_0, condition=_choose_layout_condition)
+ layout.append(_choose_layout_1, condition=_vf2_match_not_found)
+ layout += common.generate_embed_passmanager(coupling_map)
+ vf2_call_limit = None
+ if pass_manager_config.layout_method is None and pass_manager_config.initial_layout is None:
+ vf2_call_limit = int(5e6) # Set call limit to ~10 sec with retworkx 0.10.2
+ routing = common.generate_routing_passmanager(
+ routing_pass,
+ target,
+ coupling_map=coupling_map,
+ vf2_call_limit=vf2_call_limit,
+ backend_properties=backend_properties,
+ seed_transpiler=seed_transpiler,
+ use_barrier_before_measurement=not toqm_pass,
+ )
+ else:
+ layout = None
+ routing = None
+ translation = common.generate_translation_passmanager(
+ target,
+ basis_gates,
+ translation_method,
+ approximation_degree,
+ coupling_map,
+ backend_properties,
+ unitary_synthesis_method,
+ unitary_synthesis_plugin_config,
+ )
+ pre_routing = None
+ if toqm_pass:
+ pre_routing = translation
if (coupling_map and not coupling_map.is_symmetric) or (
target is not None and target.get_non_global_operation_names(strict_direction=True)
):
- pm2.append(_direction_check)
- pm2.append(_direction, condition=_direction_condition)
- pm2.append(_reset)
-
- pm2.append(_depth_check + _size_check)
- pm2.append(_opt + _unroll + _depth_check + _size_check, do_while=_opt_control)
-
- if inst_map and inst_map.has_custom_gate():
- pm2.append(PulseGates(inst_map=inst_map))
-
- # 9. Unify all durations (either SI, or convert to dt if known)
- # Schedule the circuit only when scheduling_method is supplied
- # Apply alignment analysis regardless of scheduling for delay validation.
- if scheduling_method:
- # Do scheduling after unit conversion.
- scheduler = {
- "alap": ALAPScheduleAnalysis,
- "as_late_as_possible": ALAPScheduleAnalysis,
- "asap": ASAPScheduleAnalysis,
- "as_soon_as_possible": ASAPScheduleAnalysis,
- }
- pm2.append(TimeUnitConversion(instruction_durations))
- try:
- pm2.append(scheduler[scheduling_method](instruction_durations))
- except KeyError as ex:
- raise TranspilerError("Invalid scheduling method %s." % scheduling_method) from ex
- elif instruction_durations:
- # No scheduling. But do unit conversion for delays.
- def _contains_delay(property_set):
- return property_set["contains_delay"]
-
- pm2.append(ContainsInstruction("delay"))
- pm2.append(TimeUnitConversion(instruction_durations), condition=_contains_delay)
- if (
- timing_constraints.granularity != 1
- or timing_constraints.min_length != 1
- or timing_constraints.acquire_alignment != 1
- or timing_constraints.pulse_alignment != 1
- ):
- # Run alignment analysis regardless of scheduling.
-
- def _require_alignment(property_set):
- return property_set["reschedule_required"]
-
- pm2.append(
- InstructionDurationCheck(
- acquire_alignment=timing_constraints.acquire_alignment,
- pulse_alignment=timing_constraints.pulse_alignment,
- )
- )
- pm2.append(
- ConstrainedReschedule(
- acquire_alignment=timing_constraints.acquire_alignment,
- pulse_alignment=timing_constraints.pulse_alignment,
- ),
- condition=_require_alignment,
- )
- pm2.append(
- ValidatePulseGates(
- granularity=timing_constraints.granularity,
- min_length=timing_constraints.min_length,
- )
- )
- if scheduling_method:
- # Call padding pass if circuit is scheduled
- pm2.append(PadDelay())
-
- return pm2
+ pre_optimization = common.generate_pre_op_passmanager(target, coupling_map, True)
+ else:
+ pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=True)
+ optimization = PassManager()
+ unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]]
+ optimization.append(_depth_check + _size_check)
+ opt_loop = _opt + unroll + _depth_check + _size_check
+ optimization.append(opt_loop, do_while=_opt_control)
+ sched = common.generate_scheduling(
+ instruction_durations, scheduling_method, timing_constraints, inst_map
+ )
+ return StagedPassManager(
+ init=unroll_3q,
+ layout=layout,
+ pre_routing=pre_routing,
+ routing=routing,
+ translation=translation,
+ pre_optimization=pre_optimization,
+ optimization=optimization,
+ scheduling=sched,
+ )
diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py
index 0f678c902cd2..7a0f22319ecb 100644
--- a/qiskit/transpiler/preset_passmanagers/level3.py
+++ b/qiskit/transpiler/preset_passmanagers/level3.py
@@ -20,27 +20,18 @@
from qiskit.transpiler.passmanager_config import PassManagerConfig
from qiskit.transpiler.timing_constraints import TimingConstraints
from qiskit.transpiler.passmanager import PassManager
+from qiskit.transpiler.passmanager import StagedPassManager
-from qiskit.transpiler.passes import Unroller
-from qiskit.transpiler.passes import BasisTranslator
-from qiskit.transpiler.passes import UnrollCustomDefinitions
-from qiskit.transpiler.passes import Unroll3qOrMore
-from qiskit.transpiler.passes import CheckMap
-from qiskit.transpiler.passes import GateDirection
from qiskit.transpiler.passes import SetLayout
from qiskit.transpiler.passes import VF2Layout
-from qiskit.transpiler.passes import VF2PostLayout
from qiskit.transpiler.passes import TrivialLayout
from qiskit.transpiler.passes import DenseLayout
from qiskit.transpiler.passes import NoiseAdaptiveLayout
from qiskit.transpiler.passes import SabreLayout
-from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements
from qiskit.transpiler.passes import BasicSwap
from qiskit.transpiler.passes import LookaheadSwap
from qiskit.transpiler.passes import StochasticSwap
from qiskit.transpiler.passes import SabreSwap
-from qiskit.transpiler.passes import FullAncillaAllocation
-from qiskit.transpiler.passes import EnlargeWithAncilla
from qiskit.transpiler.passes import FixedPoint
from qiskit.transpiler.passes import Depth
from qiskit.transpiler.passes import Size
@@ -52,26 +43,15 @@
from qiskit.transpiler.passes import Collect2qBlocks
from qiskit.transpiler.passes import ConsolidateBlocks
from qiskit.transpiler.passes import UnitarySynthesis
-from qiskit.transpiler.passes import ApplyLayout
-from qiskit.transpiler.passes import CheckGateDirection
-from qiskit.transpiler.passes import TimeUnitConversion
-from qiskit.transpiler.passes import ALAPScheduleAnalysis
-from qiskit.transpiler.passes import ASAPScheduleAnalysis
-from qiskit.transpiler.passes import ConstrainedReschedule
-from qiskit.transpiler.passes import InstructionDurationCheck
-from qiskit.transpiler.passes import ValidatePulseGates
-from qiskit.transpiler.passes import PulseGates
-from qiskit.transpiler.passes import PadDelay
from qiskit.transpiler.passes import Error
-from qiskit.transpiler.passes import ContainsInstruction
+from qiskit.transpiler.preset_passmanagers import common
from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason
-from qiskit.transpiler.passes.layout.vf2_post_layout import VF2PostLayoutStopReason
from qiskit.transpiler import TranspilerError
from qiskit.utils.optionals import HAS_TOQM
-def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
+def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassManager:
"""Level 3 pass manager: heavy optimization by noise adaptive qubit mapping and
gate cancellation using commutativity rules and unitary synthesis.
@@ -85,10 +65,6 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
Finally, optimizations in the form of commutative gate cancellation, resynthesis
of two-qubit unitary blocks, and redundant reset removal are performed.
- Note:
- In simulators where ``coupling_map=None``, only the unrolling and
- optimization stages are done.
-
Args:
pass_manager_config: configuration of the pass manager.
@@ -115,21 +91,7 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config
target = pass_manager_config.target
- # 1. Unroll to 1q or 2q gates
- _unroll3q = [
- # Use unitary synthesis for basis aware decomposition of UnitaryGates
- UnitarySynthesis(
- basis_gates,
- approximation_degree=approximation_degree,
- method=unitary_synthesis_method,
- plugin_config=unitary_synthesis_plugin_config,
- min_qubits=3,
- target=target,
- ),
- Unroll3qOrMore(target=target, basis_gates=basis_gates),
- ]
-
- # 2. Layout on good qubits if calibration info available, otherwise on dense links
+ # Layout on good qubits if calibration info available, otherwise on dense links
_given_layout = SetLayout(initial_layout)
def _choose_layout_condition(property_set):
@@ -174,27 +136,15 @@ def _vf2_match_not_found(property_set):
else:
raise TranspilerError("Invalid layout method %s." % layout_method)
- # 3. Extend dag/layout with ancillas using the full coupling map
- _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()]
-
- # 4. Swap to fit the coupling map
- _swap_check = CheckMap(coupling_map)
-
- def _swap_condition(property_set):
- return not property_set["is_swap_mapped"]
-
- def _swap_needs_basis(property_set):
- return _swap_condition(property_set) and routing_method == "toqm"
-
- _swap = [BarrierBeforeFinalMeasurements()]
+ toqm_pass = False
if routing_method == "basic":
- _swap += [BasicSwap(coupling_map)]
+ routing_pass = BasicSwap(coupling_map)
elif routing_method == "stochastic":
- _swap += [StochasticSwap(coupling_map, trials=200, seed=seed_transpiler)]
+ routing_pass = StochasticSwap(coupling_map, trials=200, seed=seed_transpiler)
elif routing_method == "lookahead":
- _swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=6)]
+ routing_pass = LookaheadSwap(coupling_map, search_depth=5, search_width=6)
elif routing_method == "sabre":
- _swap += [SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler)]
+ routing_pass = SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler)
elif routing_method == "toqm":
HAS_TOQM.require_now("TOQM-based routing")
from qiskit_toqm import ToqmSwap, ToqmStrategyO3, latencies_from_target
@@ -202,86 +152,26 @@ def _swap_needs_basis(property_set):
if initial_layout:
raise TranspilerError("Initial layouts are not supported with TOQM-based routing.")
+ toqm_pass = True
# Note: BarrierBeforeFinalMeasurements is skipped intentionally since ToqmSwap
# does not yet support barriers.
- _swap = [
- ToqmSwap(
- coupling_map,
- strategy=ToqmStrategyO3(
- latencies_from_target(
- coupling_map, instruction_durations, basis_gates, backend_properties, target
- )
- ),
- )
- ]
+ routing_pass = ToqmSwap(
+ coupling_map,
+ strategy=ToqmStrategyO3(
+ latencies_from_target(
+ coupling_map, instruction_durations, basis_gates, backend_properties, target
+ )
+ ),
+ )
elif routing_method == "none":
- _swap += [
- Error(
- msg=(
- "No routing method selected, but circuit is not routed to device. "
- "CheckMap Error: {check_map_msg}"
- ),
- action="raise",
- )
- ]
+ routing_pass = Error(
+ msg="No routing method selected, but circuit is not routed to device. "
+ "CheckMap Error: {check_map_msg}",
+ action="raise",
+ )
else:
raise TranspilerError("Invalid routing method %s." % routing_method)
- # 5. Unroll to the basis
- if translation_method == "unroller":
- _unroll = [Unroller(basis_gates)]
- elif translation_method == "translator":
- from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel
-
- _unroll = [
- UnitarySynthesis(
- basis_gates,
- approximation_degree=approximation_degree,
- coupling_map=coupling_map,
- backend_props=backend_properties,
- plugin_config=unitary_synthesis_plugin_config,
- method=unitary_synthesis_method,
- target=target,
- ),
- UnrollCustomDefinitions(sel, basis_gates),
- BasisTranslator(sel, basis_gates, target),
- ]
- elif translation_method == "synthesis":
- _unroll = [
- UnitarySynthesis(
- basis_gates,
- approximation_degree=approximation_degree,
- coupling_map=coupling_map,
- backend_props=backend_properties,
- method=unitary_synthesis_method,
- plugin_config=unitary_synthesis_plugin_config,
- min_qubits=3,
- target=target,
- ),
- Unroll3qOrMore(target=target, basis_gates=basis_gates),
- Collect2qBlocks(),
- ConsolidateBlocks(basis_gates=basis_gates, target=target),
- UnitarySynthesis(
- basis_gates,
- approximation_degree=approximation_degree,
- coupling_map=coupling_map,
- backend_props=backend_properties,
- method=unitary_synthesis_method,
- plugin_config=unitary_synthesis_plugin_config,
- target=target,
- ),
- ]
- else:
- raise TranspilerError("Invalid translation method %s." % translation_method)
-
- # 6. Fix any CX direction mismatch
- _direction_check = [CheckGateDirection(coupling_map, target)]
-
- def _direction_condition(property_set):
- return not property_set["is_direction_mapped"]
-
- _direction = [GateDirection(coupling_map, target)]
-
# 8. Optimize iteratively until no more change in depth. Removes useless gates
# after reset and before measure, commutes gates and optimizes contiguous blocks.
_depth_check = [Depth(), FixedPoint("depth")]
@@ -290,10 +180,6 @@ def _direction_condition(property_set):
def _opt_control(property_set):
return (not property_set["depth_fixed_point"]) or (not property_set["size_fixed_point"])
- _reset = [RemoveResetInZeroState()]
-
- _meas = [OptimizeSwapBeforeMeasure(), RemoveDiagonalGatesBeforeMeasure()]
-
_opt = [
Collect2qBlocks(),
ConsolidateBlocks(basis_gates=basis_gates, target=target),
@@ -311,137 +197,87 @@ def _opt_control(property_set):
]
# Build pass manager
- pm3 = PassManager()
- pm3.append(_unroll3q)
- pm3.append(_reset + _meas)
+ init = common.generate_unroll_3q(
+ target,
+ basis_gates,
+ approximation_degree,
+ unitary_synthesis_method,
+ unitary_synthesis_plugin_config,
+ )
+ init.append(RemoveResetInZeroState())
+ init.append(OptimizeSwapBeforeMeasure())
+ init.append(RemoveDiagonalGatesBeforeMeasure())
if coupling_map or initial_layout:
- pm3.append(_given_layout)
- pm3.append(_choose_layout_0, condition=_choose_layout_condition)
- pm3.append(_choose_layout_1, condition=_vf2_match_not_found)
- pm3.append(_embed)
- pm3.append(_swap_check)
- pm3.append(_unroll, condition=_swap_needs_basis)
- pm3.append(_swap, condition=_swap_condition)
- if (
- (coupling_map and backend_properties)
- and initial_layout is None
- and pass_manager_config.layout_method is None
- ):
-
- def _run_post_layout_condition(property_set):
- vf2_stop_reason = property_set["VF2Layout_stop_reason"]
- if (
- vf2_stop_reason is not None
- and vf2_stop_reason == VF2LayoutStopReason.SOLUTION_FOUND
- ):
- return False
- return True
-
- def _apply_post_layout_condition(property_set):
- # if VF2 Post layout found a solution we need to re-apply the better
- # layout. Otherwise we can skip apply layout.
- if (
- property_set["VF2PostLayout_stop_reason"] is not None
- and property_set["VF2PostLayout_stop_reason"]
- is VF2PostLayoutStopReason.SOLUTION_FOUND
- ):
- return True
- return False
-
- pm3.append(
- VF2PostLayout(
- target,
- coupling_map,
- backend_properties,
- seed_transpiler,
- call_limit=int(3e7), # Set call limit to ~60 sec with retworkx 0.10.2
- strict_direction=False,
- ),
- condition=_run_post_layout_condition,
- )
- pm3.append(ApplyLayout(), condition=_apply_post_layout_condition)
-
- pm3.append(_unroll)
+ layout = PassManager()
+ layout.append(_given_layout)
+ layout.append(_choose_layout_0, condition=_choose_layout_condition)
+ layout.append(_choose_layout_1, condition=_vf2_match_not_found)
+ layout += common.generate_embed_passmanager(coupling_map)
+ vf2_call_limit = None
+ if pass_manager_config.layout_method is None and pass_manager_config.initial_layout is None:
+ vf2_call_limit = int(3e7) # Set call limit to ~60 sec with retworkx 0.10.2
+ routing = common.generate_routing_passmanager(
+ routing_pass,
+ target,
+ coupling_map=coupling_map,
+ vf2_call_limit=vf2_call_limit,
+ backend_properties=backend_properties,
+ seed_transpiler=seed_transpiler,
+ use_barrier_before_measurement=not toqm_pass,
+ )
+ else:
+ layout = None
+ routing = None
+ translation = common.generate_translation_passmanager(
+ target,
+ basis_gates,
+ translation_method,
+ approximation_degree,
+ coupling_map,
+ backend_properties,
+ unitary_synthesis_method,
+ unitary_synthesis_plugin_config,
+ )
+ pre_routing = None
+ if toqm_pass:
+ pre_routing = translation
+ optimization = PassManager()
+ unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]]
+ optimization.append(_depth_check + _size_check)
if (coupling_map and not coupling_map.is_symmetric) or (
target is not None and target.get_non_global_operation_names(strict_direction=True)
):
- pm3.append(_direction_check)
- pm3.append(_direction, condition=_direction_condition)
- pm3.append(_reset)
- pm3.append(_depth_check + _size_check)
+ pre_optimization = common.generate_pre_op_passmanager(target, coupling_map, True)
+ _direction = [
+ pass_
+ for x in common.generate_pre_op_passmanager(target, coupling_map).passes()
+ for pass_ in x["passes"]
+ ]
# For transpiling to a target we need to run GateDirection in the
# optimization loop to correct for incorrect directions that might be
# inserted by UnitarySynthesis which is direction aware but only via
# the coupling map which with a target doesn't give a full picture
if target is not None:
- pm3.append(
- _opt + _unroll + _depth_check + _size_check + _direction, do_while=_opt_control
+ optimization.append(
+ _opt + unroll + _depth_check + _size_check + _direction, do_while=_opt_control
)
else:
- pm3.append(_opt + _unroll + _depth_check + _size_check, do_while=_opt_control)
+ optimization.append(_opt + unroll + _depth_check + _size_check, do_while=_opt_control)
else:
- pm3.append(_reset)
- pm3.append(_depth_check + _size_check)
- pm3.append(_opt + _unroll + _depth_check + _size_check, do_while=_opt_control)
-
- if inst_map and inst_map.has_custom_gate():
- pm3.append(PulseGates(inst_map=inst_map))
-
- # 9. Unify all durations (either SI, or convert to dt if known)
- # Schedule the circuit only when scheduling_method is supplied
- # Apply alignment analysis regardless of scheduling for delay validation.
- if scheduling_method:
- # Do scheduling after unit conversion.
- scheduler = {
- "alap": ALAPScheduleAnalysis,
- "as_late_as_possible": ALAPScheduleAnalysis,
- "asap": ASAPScheduleAnalysis,
- "as_soon_as_possible": ASAPScheduleAnalysis,
- }
- pm3.append(TimeUnitConversion(instruction_durations))
- try:
- pm3.append(scheduler[scheduling_method](instruction_durations))
- except KeyError as ex:
- raise TranspilerError("Invalid scheduling method %s." % scheduling_method) from ex
- elif instruction_durations:
- # No scheduling. But do unit conversion for delays.
- def _contains_delay(property_set):
- return property_set["contains_delay"]
-
- pm3.append(ContainsInstruction("delay"))
- pm3.append(TimeUnitConversion(instruction_durations), condition=_contains_delay)
- if (
- timing_constraints.granularity != 1
- or timing_constraints.min_length != 1
- or timing_constraints.acquire_alignment != 1
- or timing_constraints.pulse_alignment != 1
- ):
- # Run alignment analysis regardless of scheduling.
-
- def _require_alignment(property_set):
- return property_set["reschedule_required"]
-
- pm3.append(
- InstructionDurationCheck(
- acquire_alignment=timing_constraints.acquire_alignment,
- pulse_alignment=timing_constraints.pulse_alignment,
- )
- )
- pm3.append(
- ConstrainedReschedule(
- acquire_alignment=timing_constraints.acquire_alignment,
- pulse_alignment=timing_constraints.pulse_alignment,
- ),
- condition=_require_alignment,
- )
- pm3.append(
- ValidatePulseGates(
- granularity=timing_constraints.granularity,
- min_length=timing_constraints.min_length,
- )
- )
- if scheduling_method:
- # Call padding pass if circuit is scheduled
- pm3.append(PadDelay())
-
- return pm3
+ pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=True)
+ optimization.append(_opt + unroll + _depth_check + _size_check, do_while=_opt_control)
+ opt_loop = _depth_check + _opt + unroll
+ optimization.append(opt_loop, do_while=_opt_control)
+ sched = common.generate_scheduling(
+ instruction_durations, scheduling_method, timing_constraints, inst_map
+ )
+ return StagedPassManager(
+ init=init,
+ layout=layout,
+ pre_routing=pre_routing,
+ routing=routing,
+ translation=translation,
+ pre_optimization=pre_optimization,
+ optimization=optimization,
+ scheduling=sched,
+ )
diff --git a/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml b/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml
new file mode 100644
index 000000000000..be59f4545743
--- /dev/null
+++ b/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml
@@ -0,0 +1,19 @@
+---
+features:
+ - |
+ Added a new class, :class:`qiskit.transpiler.StagedPassManager`, which is
+ a :class:`~qiskit.transpiler.PassManager` subclass that has a pipeline
+ with defined phases to perform circuit compilation. Each phase is a
+ :class:`~qiskit.transpiler.PassManager` object that will get executed
+ in a fixed order. For example::
+
+ from qiskit.transpiler.passes import *
+ from qiskit.transpiler import PassManager, StagedPassManager
+
+ basis_gates = ['rx', 'ry', 'rxx']
+ init = PassManager([UnitarySynthesis(basis_gates, min_qubits=3), Unroll3qOrMore()])
+ translate = PassManager([Collect2qBlocks(),
+ ConsolidateBlocks(basis_gates=basis_gates),
+ UnitarySynthesis(basis_gates)])
+
+ staged_pm = StagedPassManager(stages=['init', 'translation'], init=init, translation=translate)
diff --git a/test/python/transpiler/test_staged_passmanager.py b/test/python/transpiler/test_staged_passmanager.py
new file mode 100644
index 000000000000..79045b2cd86d
--- /dev/null
+++ b/test/python/transpiler/test_staged_passmanager.py
@@ -0,0 +1,94 @@
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2022
+#
+# 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.
+
+# pylint: disable=missing-function-docstring,missing-class-docstring
+
+"""Test the staged passmanager logic"""
+
+from qiskit.transpiler import PassManager, StagedPassManager
+from qiskit.transpiler.passes import Optimize1qGates, Unroller, Depth
+from qiskit.test import QiskitTestCase
+
+
+class TestStagedPassManager(QiskitTestCase):
+ def test_default_stages(self):
+ spm = StagedPassManager()
+ self.assertEqual(
+ spm.stages, ["init", "layout", "routing", "translation", "optimization", "scheduling"]
+ )
+ spm = StagedPassManager(
+ init=PassManager([Optimize1qGates()]),
+ routing=PassManager([Unroller(["u", "cx"])]),
+ scheduling=PassManager([Depth()]),
+ )
+ self.assertEqual(
+ [x.__class__.__name__ for passes in spm.passes() for x in passes["passes"]],
+ ["Optimize1qGates", "Unroller", "Depth"],
+ )
+
+ def test_inplace_edit(self):
+ spm = StagedPassManager(stages=["single_stage"])
+ spm.single_stage = PassManager([Optimize1qGates(), Depth()])
+ self.assertEqual(
+ [x.__class__.__name__ for passes in spm.passes() for x in passes["passes"]],
+ ["Optimize1qGates", "Depth"],
+ )
+ spm.single_stage.append(Unroller(["u"]))
+ spm.single_stage.append(Depth())
+ self.assertEqual(
+ [x.__class__.__name__ for passes in spm.passes() for x in passes["passes"]],
+ ["Optimize1qGates", "Depth", "Unroller", "Depth"],
+ )
+
+ def test_invalid_stage(self):
+ with self.assertRaises(AttributeError):
+ StagedPassManager(stages=["init"], translation=PassManager())
+
+ def test_pre_phase_is_valid_stage(self):
+ spm = StagedPassManager(stages=["init"], pre_init=PassManager([Depth()]))
+ self.assertEqual(
+ [x.__class__.__name__ for passes in spm.passes() for x in passes["passes"]],
+ ["Depth"],
+ )
+
+ def test_append_extend_not_implemented(self):
+ spm = StagedPassManager()
+ with self.assertRaises(NotImplementedError):
+ spm.append(Depth())
+ with self.assertRaises(NotImplementedError):
+ spm += PassManager()
+
+ def test_invalid_stages(self):
+ invalid_stages = [
+ "two words",
+ "two-words",
+ "two+words",
+ "two&words",
+ "[two_words]",
+ "",
+ "{two_words}",
+ "(two_words)",
+ "two^words",
+ "two_words!",
+ "^two_words",
+ "@two_words",
+ "two~words",
+ r"two\words",
+ "two/words",
+ ]
+ all_stages = invalid_stages + ["two_words", "init"]
+
+ with self.assertRaises(ValueError) as err:
+ StagedPassManager(all_stages)
+ message = str(err.exception)
+ for stage in invalid_stages:
+ self.assertIn(stage, message)
diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py
index 3956da41737f..4a2f47bcdd91 100755
--- a/test/qpy_compat/test_qpy.py
+++ b/test/qpy_compat/test_qpy.py
@@ -57,7 +57,7 @@ def generate_full_circuit():
def generate_unitary_gate_circuit():
"""Generate a circuit with a unitary gate."""
- unitary_circuit = QuantumCircuit(5)
+ unitary_circuit = QuantumCircuit(5, name="unitary_circuit")
unitary_circuit.unitary(random_unitary(32, seed=100), [0, 1, 2, 3, 4])
unitary_circuit.measure_all()
return unitary_circuit
@@ -67,7 +67,7 @@ def generate_random_circuits():
"""Generate multiple random circuits."""
random_circuits = []
for i in range(1, 15):
- qc = QuantumCircuit(i)
+ qc = QuantumCircuit(i, name=f"random_circuit-{i}")
qc.h(0)
if i > 1:
for j in range(i - 1):
@@ -84,7 +84,9 @@ def generate_random_circuits():
def generate_string_parameters():
"""Generate a circuit from pauli tensor opflow."""
- return (X ^ Y ^ Z).to_circuit_op().to_circuit()
+ op_circuit = (X ^ Y ^ Z).to_circuit_op().to_circuit()
+ op_circuit.name = "X^Y^Z"
+ return op_circuit
def generate_register_edge_cases():
@@ -92,7 +94,7 @@ def generate_register_edge_cases():
register_edge_cases = []
# Circuit with shared bits in a register
qubits = [Qubit() for _ in range(5)]
- shared_qc = QuantumCircuit()
+ shared_qc = QuantumCircuit(name="shared_bits")
shared_qc.add_bits(qubits)
shared_qr = QuantumRegister(bits=qubits)
shared_qc.add_register(shared_qr)
@@ -109,7 +111,7 @@ def generate_register_edge_cases():
qr = QuantumRegister(name="bar", bits=qr[:3] + [Qubit(), Qubit()])
cr = ClassicalRegister(5, "foo")
cr = ClassicalRegister(name="classical_bar", bits=cr[:3] + [Clbit(), Clbit()])
- hybrid_qc = QuantumCircuit(qr, cr)
+ hybrid_qc = QuantumCircuit(qr, cr, name="mix_standalone_bits_registers")
hybrid_qc.h(0)
hybrid_qc.cx(0, 1)
hybrid_qc.cx(0, 2)
@@ -120,7 +122,7 @@ def generate_register_edge_cases():
# Circuit with mixed standalone and shared registers
qubits = [Qubit() for _ in range(5)]
clbits = [Clbit() for _ in range(5)]
- mixed_qc = QuantumCircuit()
+ mixed_qc = QuantumCircuit(name="mix_standalone_bits_with_registers")
mixed_qc.add_bits(qubits)
mixed_qc.add_bits(clbits)
qr = QuantumRegister(bits=qubits)
@@ -140,7 +142,7 @@ def generate_register_edge_cases():
qr_standalone = QuantumRegister(2, "standalone")
qubits = [Qubit() for _ in range(5)]
clbits = [Clbit() for _ in range(5)]
- ooo_qc = QuantumCircuit()
+ ooo_qc = QuantumCircuit(name="out_of_order_bits")
ooo_qc.add_bits(qubits)
ooo_qc.add_bits(clbits)
random.seed(42)
@@ -166,7 +168,7 @@ def generate_register_edge_cases():
def generate_parameterized_circuit():
"""Generate a circuit with parameters and parameter expressions."""
- param_circuit = QuantumCircuit(1)
+ param_circuit = QuantumCircuit(1, name="parameterized")
theta = Parameter("theta")
lam = Parameter("λ")
theta_pi = 3.14159 * theta
@@ -194,7 +196,7 @@ def generate_qft_circuit():
)
qubits = 3
- qft_circ = QuantumCircuit(qubits, qubits)
+ qft_circ = QuantumCircuit(qubits, qubits, name="QFT")
qft_circ.initialize(state)
qft_circ.append(QFT(qubits), range(qubits))
qft_circ.measure(range(qubits), range(qubits))
@@ -208,7 +210,7 @@ def generate_param_phase():
theta = Parameter("theta")
phi = Parameter("phi")
sum_param = theta + phi
- qc = QuantumCircuit(5, 1, global_phase=sum_param)
+ qc = QuantumCircuit(5, 1, global_phase=sum_param, name="parameter_phase")
qc.h(0)
for i in range(4):
qc.cx(i, i + 1)
@@ -224,7 +226,7 @@ def generate_param_phase():
output_circuits.append(qc)
# Generate circuit with Parameter global phase
theta = Parameter("theta")
- bell_qc = QuantumCircuit(2, global_phase=theta)
+ bell_qc = QuantumCircuit(2, global_phase=theta, name="bell_param_global_phase")
bell_qc.h(0)
bell_qc.cx(0, 1)
bell_qc.measure_all()
@@ -246,7 +248,7 @@ def generate_single_clbit_condition_teleportation(): # pylint: disable=invalid-
def generate_parameter_vector():
"""Generate tests for parameter vector element ordering."""
- qc = QuantumCircuit(11)
+ qc = QuantumCircuit(11, name="parameter_vector")
input_params = ParameterVector("x_par", 11)
user_params = ParameterVector("θ_par", 11)
for i, param in enumerate(user_params):
@@ -258,7 +260,7 @@ def generate_parameter_vector():
def generate_parameter_vector_expression(): # pylint: disable=invalid-name
"""Generate tests for parameter vector element ordering."""
- qc = QuantumCircuit(7)
+ qc = QuantumCircuit(7, name="vector_expansion")
entanglement = [[i, i + 1] for i in range(7 - 1)]
input_params = ParameterVector("x_par", 14)
user_params = ParameterVector("\u03B8_par", 1)
@@ -284,7 +286,7 @@ def generate_evolution_gate():
synthesis = SuzukiTrotter()
evo = PauliEvolutionGate([(Z ^ I) + (I ^ Z)] * 5, time=2.0, synthesis=synthesis)
- qc = QuantumCircuit(2)
+ qc = QuantumCircuit(2, name="pauli_evolution_circuit")
qc.append(evo, range(2))
return qc
@@ -295,7 +297,7 @@ def generate_control_flow_circuits():
# If instruction
circuits = []
- qc = QuantumCircuit(2, 2)
+ qc = QuantumCircuit(2, 2, name="control_flow")
qc.h(0)
qc.measure(0, 0)
true_body = QuantumCircuit(1)
@@ -305,7 +307,7 @@ def generate_control_flow_circuits():
qc.measure(1, 1)
circuits.append(qc)
# If else instruction
- qc = QuantumCircuit(2, 2)
+ qc = QuantumCircuit(2, 2, name="if_else")
qc.h(0)
qc.measure(0, 0)
false_body = QuantumCircuit(1)
@@ -315,7 +317,7 @@ def generate_control_flow_circuits():
qc.measure(1, 1)
circuits.append(qc)
# While loop
- qc = QuantumCircuit(2, 1)
+ qc = QuantumCircuit(2, 1, name="while_loop")
block = QuantumCircuit(2, 1)
block.h(0)
block.cx(0, 1)
@@ -324,7 +326,7 @@ def generate_control_flow_circuits():
qc.append(while_loop, [0, 1], [0])
circuits.append(qc)
# for loop range
- qc = QuantumCircuit(2, 1)
+ qc = QuantumCircuit(2, 1, name="for_loop")
body = QuantumCircuit(2, 1)
body.h(0)
body.cx(0, 1)
@@ -334,7 +336,7 @@ def generate_control_flow_circuits():
qc.append(for_loop_op, [0, 1], [0])
circuits.append(qc)
# For loop iterator
- qc = QuantumCircuit(2, 1)
+ qc = QuantumCircuit(2, 1, name="for_loop_iterator")
for_loop_op = ForLoopOp(iter(range(5)), None, body=body)
qc.append(for_loop_op, [0, 1], [0])
circuits.append(qc)
@@ -427,7 +429,7 @@ def assert_equal(reference, qpy, count, bind=None):
sys.exit(1)
# Don't compare name on bound circuits
if bind is None and reference.name != qpy.name:
- msg = f"Circuit {count} name mismatch {reference.name} != {qpy.name}"
+ msg = f"Circuit {count} name mismatch {reference.name} != {qpy.name}\n{reference}\n{qpy}"
sys.stderr.write(msg)
sys.exit(2)
if reference.metadata != qpy.metadata: