Skip to content

Commit

Permalink
Ensure translation stage always outputs ISA circuits
Browse files Browse the repository at this point in the history
This shifts the responsibility of the `translation` stage slightly to
always output ISA circuits.  It previously was not 100% required that
they did this, at least implicitly because Qiskit's built-in plugins
didn't always respect 2q direction.

The builtin translation plugins now always respect 2q direction, which
removes the need for the `pre_optimization` implicit stage, freeing it
up for better user customisation.

This (in theory) shouldn't have runtime impacts because the optimisation
loop was already having to do this afterwards anyway.  For potential
plugins that _do_ respect gate direction (like the upcoming
`BasisConstructor`), it can be a speedup in the default pipeline, since
they won't need to run the gate-direction check any more.
  • Loading branch information
jakelishman committed Feb 5, 2025
1 parent 397e66e commit 0caf0c9
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 49 deletions.
9 changes: 0 additions & 9 deletions qiskit/transpiler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,15 +615,6 @@
the ISA only contains a ``cz`` operation on those qubits, the translation stage must find a way of
representing the ``cx`` gate using the ``cz`` and available one-qubit gates.
.. note::
In Qiskit 1.x, translation plugins need not output gates with the correct
directionality, provided the gate exists with opposite directionality on the given qubit pair.
For example, if ``cx(0, 1)`` is ISA-supported, the translation stage can output
``cx(1, 0)``.
This is likely to change in later versions of Qiskit.
The translation stage is called before entering the optimization stage. Optimization plugins
(including Qiskit's built-in plugins) may also use the translation stage as a "fixup" stage after
the optimization loop, if the optimization loop returns a circuit that includes non-ISA gates. This
Expand Down
47 changes: 45 additions & 2 deletions qiskit/transpiler/preset_passmanagers/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
from qiskit.transpiler.passes.layout.vf2_post_layout import VF2PostLayoutStopReason
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.layout import Layout
from qiskit.utils import deprecate_func
from qiskit.utils.deprecate_pulse import deprecate_pulse_arg


Expand Down Expand Up @@ -379,11 +380,19 @@ def filter_fn(node):
return routing


@deprecate_func(
since="2.0",
additional_msg=(
"Translation plugins are now required to respect ISA directionality,"
" so typically no replacement is necessary."
),
removal_timeline="in Qiskit 3.0",
)
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
map is respected.
Args:
target (Target): the :class:`~.Target` object representing the backend
Expand Down Expand Up @@ -458,6 +467,7 @@ def generate_translation_passmanager(
TranspilerError: If the ``method`` kwarg is not a valid value
"""
if method == "translator":
translator = BasisTranslator(sel, basis_gates, target)
unroll = [
# Use unitary synthesis for basis aware decomposition of
# UnitaryGates before custom unrolling
Expand All @@ -479,8 +489,9 @@ def generate_translation_passmanager(
basis_gates=basis_gates,
qubits_initially_zero=qubits_initially_zero,
),
BasisTranslator(sel, basis_gates, target),
translator,
]
fix_1q = [translator]
elif method == "synthesis":
unroll = [
# # Use unitary synthesis for basis aware decomposition of
Expand Down Expand Up @@ -528,8 +539,40 @@ def generate_translation_passmanager(
qubits_initially_zero=qubits_initially_zero,
),
]
fix_1q = [
Collect1qRuns(),
ConsolidateBlocks(
basis_gates=basis_gates, target=target, approximation_degree=approximation_degree
),
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(f"Invalid translation method {method}.")
# Our built-ins don't 100% guarantee that 2q gate direction is respected, so we might need to
# run a little bit of fix up on them. `GateDirection` doesn't guarantee that 1q gates are
# ISA safe after it runs, so we need another run too.
if (coupling_map and not coupling_map.is_symmetric) or (
target is not None and target.get_non_global_operation_names(strict_direction=True)
):
unroll.append(CheckGateDirection(coupling_map, target=target))

def _direction_condition(property_set):
return not property_set["is_direction_mapped"]

unroll.append(
ConditionalController(
[GateDirection(coupling_map, target=target)] + fix_1q,
condition=_direction_condition,
)
)
return PassManager(unroll)


Expand Down
9 changes: 0 additions & 9 deletions qiskit/transpiler/preset_passmanagers/level0.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,6 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa
"translation", translation_method, pass_manager_config, optimization_level=0
)

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 = plugin_manager.get_passmanager_stage(
"scheduling", scheduling_method, pass_manager_config, optimization_level=0
)
Expand All @@ -107,7 +99,6 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa
layout=layout,
routing=routing,
translation=translation,
pre_optimization=pre_opt,
optimization=optimization,
scheduling=sched,
)
10 changes: 0 additions & 10 deletions qiskit/transpiler/preset_passmanagers/level1.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,6 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa
"translation", translation_method, pass_manager_config, optimization_level=1
)

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_optimization = common.generate_pre_op_passmanager(
target, coupling_map, remove_reset_in_zero=False
)
else:
pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=False)

optimization = plugin_manager.get_passmanager_stage(
"optimization", optimization_method, pass_manager_config, optimization_level=1
)
Expand Down Expand Up @@ -114,7 +105,6 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa
layout=layout,
routing=routing,
translation=translation,
pre_optimization=pre_optimization,
optimization=optimization,
scheduling=sched,
)
10 changes: 0 additions & 10 deletions qiskit/transpiler/preset_passmanagers/level2.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,6 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa
"translation", translation_method, pass_manager_config, optimization_level=2
)

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_optimization = common.generate_pre_op_passmanager(
target, coupling_map, remove_reset_in_zero=False
)
else:
pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=False)

optimization = plugin_manager.get_passmanager_stage(
"optimization", optimization_method, pass_manager_config, optimization_level=2
)
Expand Down Expand Up @@ -113,7 +104,6 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa
layout=layout,
routing=routing,
translation=translation,
pre_optimization=pre_optimization,
optimization=optimization,
scheduling=sched,
)
9 changes: 0 additions & 9 deletions qiskit/transpiler/preset_passmanagers/level3.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,6 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa
optimization = plugin_manager.get_passmanager_stage(
"optimization", optimization_method, pass_manager_config, optimization_level=3
)
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_optimization = common.generate_pre_op_passmanager(
target, coupling_map, remove_reset_in_zero=False
)
else:
pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=False)

sched = plugin_manager.get_passmanager_stage(
"scheduling", scheduling_method, pass_manager_config, optimization_level=3
Expand All @@ -113,7 +105,6 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa
layout=layout,
routing=routing,
translation=translation,
pre_optimization=pre_optimization,
optimization=optimization,
scheduling=sched,
)
39 changes: 39 additions & 0 deletions releasenotes/notes/translation-direction-40059e267f77e178.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
upgrade_transpiler:
- |
Plugins for the :ref:`translation stage of the preset compiler <transpiler-preset-stage-translation>`
are now required to respect gate directionality in the :class:`.Target` in their output.
Previously, :func:`.transpile` and :func:`.generate_preset_pass_manager` would generate a
:class:`.PassManager` that contained fix-up passes if needed. You must now include these in
your own custom stage, if your stage does not guarantee that it respects directionality.
You can use the :class:`.GateDirection` pass to perform the same fix-ups that Qiskit used to do.
For example::
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import GateDirection
from qiskit.transpiler.preset_passmanagers.plugin import PassManagerStagePlugin
class YourTranslationPlugin(PassManagerStagePlugin):
def pass_manager(self, pass_manager_config, optimization_level):
pm = PassManager([
# ... whatever your current setup is ...
])
# Add the two-qubit directionality-fixing pass.
pm.append(GateDirection(
pass_manager_config.coupling_map,
pass_manager_config.target,
))
return pm
- |
The :ref:`preset pass managers <transpiler-preset>` no longer populate the implicit ``pre_optimization``
stage of their output :class:`.StagedPassManager`. You can now safely assign your own
:class:`.PassManager` to this field. You could previously only append to the existing
:class:`.PassManager`.
deprecations_transpiler:
- |
The function :func:`.generate_pre_op_passmanager` is deprecated. It is no longer used in the
Qiskit preset pass managers, and its purpose is defunct; it originally generated a fix-up stage
for translation plugins that did not respect ISA directionality. Translation stages are now
required to respect directionality, so the functionality is not needed, and most likely,
no replacement is required.

0 comments on commit 0caf0c9

Please sign in to comment.