Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update transpiler pipeline to (only) use target internally #12850

Merged
merged 33 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8eea4cf
First attempt at supporting a virtual target (FakeTarget) with no bas…
ElePT Oct 17, 2024
6204af4
Remove prints
ElePT Oct 17, 2024
77eff6e
Fix control-flow failures with fake target
ElePT Oct 24, 2024
efca6d2
Fix init stage, add instruction_supported method to FakeTarget
ElePT Oct 24, 2024
91947e4
Tweak preset pass manager
ElePT Oct 24, 2024
0c771e6
Merge branch 'main' of https://github.com/Qiskit/qiskit into transpil…
ElePT Nov 1, 2024
1ac1f59
Handle inst_map deprecation warning
ElePT Nov 1, 2024
472cf64
Handle annotated operations
ElePT Nov 1, 2024
1ba5381
Do not skip stages with FakeTarget
ElePT Nov 1, 2024
c3a2573
Deprecate custom basis gates
ElePT Nov 1, 2024
cbd0973
Cleanup
ElePT Nov 1, 2024
2df8a27
Remove reno
ElePT Nov 5, 2024
e79f06c
Refactor creation of _FakeTarget, add tests
ElePT Nov 5, 2024
ab1bc10
Merge branch 'main' of https://github.com/Qiskit/qiskit into transpil…
ElePT Nov 5, 2024
75a5535
Fix handling of pre-optimization stage and gate direction passes
ElePT Nov 5, 2024
390a322
* Merge branch 'main' of https://github.com/Qiskit/qiskit into transp…
ElePT Jan 10, 2025
33dc0c4
Restore cargo.lock
ElePT Jan 10, 2025
0008e52
Address most lint complaints
ElePT Jan 10, 2025
a46ec06
Fix dt issue
ElePT Jan 13, 2025
dafa66b
Merge branch 'main' of https://github.com/Qiskit/qiskit into transpil…
ElePT Jan 14, 2025
570c1e8
Address HLS issue
ElePT Jan 14, 2025
c11219b
Address GateDirection issue
ElePT Jan 14, 2025
321ce00
Fix lint and improve style
ElePT Jan 14, 2025
92fad86
Refactor plugin logic, handle target with no basis gates at the pass …
ElePT Jan 15, 2025
10affb9
Merge branch 'main' of https://github.com/Qiskit/qiskit into transpil…
ElePT Jan 20, 2025
f8afe3b
Merge branch 'main' of https://github.com/Qiskit/qiskit into transpil…
ElePT Mar 4, 2025
1d449ea
Remove deprecated arguments: instruction_durations, timing_constrants…
ElePT Mar 5, 2025
d1dc700
Merge branch 'main' of https://github.com/Qiskit/qiskit into transpil…
ElePT Mar 6, 2025
8528114
Apply suggestions from Eli's code review
ElePT Mar 6, 2025
881cb26
Merge branch 'transpile-only-target-pipeline' of https://github.com/E…
ElePT Mar 6, 2025
6a53a58
Clean up
ElePT Mar 6, 2025
591664c
Apply suggestions from Eli's code review. Add reno for API changes. F…
ElePT Mar 6, 2025
2b12e65
Fix merge conflict
ElePT Mar 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 23 additions & 106 deletions qiskit/compiler/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import logging
from time import time
from typing import List, Union, Dict, Callable, Any, Optional, TypeVar
import warnings

from qiskit import user_config
from qiskit.circuit.quantumcircuit import QuantumCircuit
Expand All @@ -25,35 +24,15 @@
from qiskit.transpiler import Layout, CouplingMap, PropertySet
from qiskit.transpiler.basepasses import BasePass
from qiskit.transpiler.exceptions import TranspilerError, CircuitTooWideForTarget
from qiskit.transpiler.instruction_durations import InstructionDurationsType
from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.transpiler.target import Target
from qiskit.utils import deprecate_arg

logger = logging.getLogger(__name__)

_CircuitT = TypeVar("_CircuitT", bound=Union[QuantumCircuit, List[QuantumCircuit]])


@deprecate_arg(
name="instruction_durations",
since="1.3",
package_name="Qiskit",
removal_timeline="in Qiskit 2.0",
additional_msg="The `target` parameter should be used instead. You can build a `Target` instance "
"with defined instruction durations with "
"`Target.from_configuration(..., instruction_durations=...)`",
)
@deprecate_arg(
name="timing_constraints",
since="1.3",
package_name="Qiskit",
removal_timeline="in Qiskit 2.0",
additional_msg="The `target` parameter should be used instead. You can build a `Target` instance "
"with defined timing constraints with "
"`Target.from_configuration(..., timing_constraints=...)`",
)
def transpile( # pylint: disable=too-many-return-statements
circuits: _CircuitT,
backend: Optional[Backend] = None,
Expand All @@ -64,10 +43,8 @@ def transpile( # pylint: disable=too-many-return-statements
routing_method: Optional[str] = None,
translation_method: Optional[str] = None,
scheduling_method: Optional[str] = None,
instruction_durations: Optional[InstructionDurationsType] = None,
dt: Optional[float] = None,
approximation_degree: Optional[float] = 1.0,
timing_constraints: Optional[Dict[str, int]] = None,
seed_transpiler: Optional[int] = None,
optimization_level: Optional[int] = None,
callback: Optional[Callable[[BasePass, DAGCircuit, float, PropertySet, int], Any]] = None,
Expand All @@ -90,8 +67,8 @@ def transpile( # pylint: disable=too-many-return-statements

The prioritization of transpilation target constraints works as follows: if a ``target``
input is provided, it will take priority over any ``backend`` input or loose constraints
(``basis_gates``, ``coupling_map``, ``instruction_durations``,
``dt`` or ``timing_constraints``). If a ``backend`` is provided together with any loose constraint
(``basis_gates``, ``coupling_map``, or ``dt``). If a ``backend`` is provided
together with any loose constraint
from the list above, the loose constraint will take priority over the corresponding backend
constraint. This behavior is summarized in the table below. The first column
in the table summarizes the potential user-provided constraints, and each cell shows whether
Expand All @@ -103,9 +80,7 @@ def transpile( # pylint: disable=too-many-return-statements
============================ ========= ========================
**basis_gates** target basis_gates
**coupling_map** target coupling_map
**instruction_durations** target instruction_durations
**dt** target dt
**timing_constraints** target timing_constraints
============================ ========= ========================

Args:
Expand Down Expand Up @@ -176,40 +151,10 @@ def transpile( # pylint: disable=too-many-return-statements
to use for the ``scheduling`` stage. You can see a list of installed plugins by
using :func:`~.list_stage_plugins` with ``"scheduling"`` for the ``stage_name``
argument.
instruction_durations: Durations of instructions.
Applicable only if scheduling_method is specified.
The gate lengths defined in ``backend.properties`` are used as default.
They are overwritten if this ``instruction_durations`` is specified.
The format of ``instruction_durations`` must be as follows.
The `instruction_durations` must be given as a list of tuples
[(instruction_name, qubits, duration, unit), ...].
| [('cx', [0, 1], 12.3, 'ns'), ('u3', [0], 4.56, 'ns')]
| [('cx', [0, 1], 1000), ('u3', [0], 300)]
If unit is omitted, the default is 'dt', which is a sample time depending on backend.
If the time unit is 'dt', the duration must be an integer.
dt: Backend sample time (resolution) in seconds.
If ``None`` (default), ``backend.dt`` is used.
approximation_degree (float): heuristic dial used for circuit approximation
(1.0=no approximation, 0.0=maximal approximation)
timing_constraints: An optional control hardware restriction on instruction time resolution.
A quantum computer backend may report a set of restrictions, namely:

- granularity: An integer value representing minimum pulse gate
resolution in units of ``dt``. A user-defined pulse gate should have
duration of a multiple of this granularity value.
- min_length: An integer value representing minimum pulse gate
length in units of ``dt``. A user-defined pulse gate should be longer
than this length.
- pulse_alignment: An integer value representing a time resolution of gate
instruction starting time. Gate instruction should start at time which
is a multiple of the alignment value.
- acquire_alignment: An integer value representing a time resolution of measure
instruction starting time. Measure instruction should start at time which
is a multiple of the alignment value.

This information will be provided by the backend configuration.
If the backend doesn't have any restriction on the instruction time allocation,
then ``timing_constraints`` is None and no adjustment will be performed.
seed_transpiler: Sets random seed for the stochastic parts of the transpiler
optimization_level: How much optimization to perform on the circuits.
Higher levels generate more optimized circuits,
Expand Down Expand Up @@ -308,18 +253,6 @@ def callback_func(**kwargs):
config = user_config.get_config()
optimization_level = config.get("transpile_optimization_level", 2)

if (
scheduling_method is not None
and backend is None
and target is None
and not instruction_durations
):
warnings.warn(
"When scheduling circuits without backend,"
" 'instruction_durations' should be usually provided.",
UserWarning,
)

if not ignore_backend_supplied_default_methods:
if scheduling_method is None and hasattr(backend, "get_scheduling_stage_plugin"):
scheduling_method = backend.get_scheduling_stage_plugin()
Expand All @@ -333,43 +266,27 @@ def callback_func(**kwargs):
# Edge cases require using the old model (loose constraints) instead of building a target,
# but we don't populate the passmanager config with loose constraints unless it's one of
# the known edge cases to control the execution path.
# Filter instruction_durations and timing_constraints deprecation
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore",
category=DeprecationWarning,
message=".*``timing_constraints`` is deprecated as of Qiskit 1.3.*",
module="qiskit",
)
warnings.filterwarnings(
"ignore",
category=DeprecationWarning,
message=".*``instruction_durations`` is deprecated as of Qiskit 1.3.*",
module="qiskit",
)
pm = generate_preset_pass_manager(
optimization_level,
target=target,
backend=backend,
basis_gates=basis_gates,
coupling_map=coupling_map,
instruction_durations=instruction_durations,
timing_constraints=timing_constraints,
initial_layout=initial_layout,
layout_method=layout_method,
routing_method=routing_method,
translation_method=translation_method,
scheduling_method=scheduling_method,
approximation_degree=approximation_degree,
seed_transpiler=seed_transpiler,
unitary_synthesis_method=unitary_synthesis_method,
unitary_synthesis_plugin_config=unitary_synthesis_plugin_config,
hls_config=hls_config,
init_method=init_method,
optimization_method=optimization_method,
dt=dt,
qubits_initially_zero=qubits_initially_zero,
)
pm = generate_preset_pass_manager(
optimization_level,
target=target,
backend=backend,
basis_gates=basis_gates,
coupling_map=coupling_map,
initial_layout=initial_layout,
layout_method=layout_method,
routing_method=routing_method,
translation_method=translation_method,
scheduling_method=scheduling_method,
approximation_degree=approximation_degree,
seed_transpiler=seed_transpiler,
unitary_synthesis_method=unitary_synthesis_method,
unitary_synthesis_plugin_config=unitary_synthesis_plugin_config,
hls_config=hls_config,
init_method=init_method,
optimization_method=optimization_method,
dt=dt,
qubits_initially_zero=qubits_initially_zero,
)

out_circuits = pm.run(circuits, callback=callback, num_processes=num_processes)

Expand Down
7 changes: 4 additions & 3 deletions qiskit/transpiler/passes/basis/basis_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,16 @@ def __init__(self, equivalence_library, target_basis, target=None, min_qubits=0)
min_qubits (int): The minimum number of qubits for operations in the input
dag to translate.
"""

super().__init__()
self._equiv_lib = equivalence_library
self._target_basis = target_basis
self._target = target
# Bypass target if it doesn't contain any basis gates (i.e. it's a _FakeTarget), as this
# not part of the official target model.
self._target = target if target is not None and len(target.operation_names) > 0 else None
self._non_global_operations = None
self._qargs_with_non_global_operation = {}
self._min_qubits = min_qubits
if target is not None:
if self._target is not None:
self._non_global_operations = self._target.get_non_global_operation_names()
self._qargs_with_non_global_operation = defaultdict(set)
for gate in self._non_global_operations:
Expand Down
2 changes: 1 addition & 1 deletion qiskit/transpiler/passes/layout/vf2_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def build_average_error_map(target, coupling_map):
# running the fallback heuristic
if not built and target is not None and coupling_map is None:
coupling_map = target.build_coupling_map()
if not built and coupling_map is not None:
if not built and coupling_map is not None and num_qubits is not None:
for qubit in range(num_qubits):
avg_map.add_error(
(qubit, qubit),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ def __init__(
"""
super().__init__()
self.basis_gates = None
self.target = target
# Bypass target if it doesn't contain any basis gates (i.e. it's a _FakeTarget), as this
# not part of the official target model.
self.target = target if target is not None and len(target.operation_names) > 0 else None
if basis_gates is not None:
self.basis_gates = set(basis_gates)
self.force_consolidate = force_consolidate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,19 @@ def __init__(self, basis=None, target=None):
"""
super().__init__()

if basis:
if basis and len(basis) > 0:
self._basis_gates = set(basis)
else:
self._basis_gates = None
self._target = target
# Bypass target if it doesn't contain any basis gates (i.e. it's a _FakeTarget), as this
# not part of the official target model.
self._target = target if target is not None and len(target.operation_names) > 0 else None
self._global_decomposers = None
self._local_decomposers_cache = {}

if basis:
if self._basis_gates:
self._global_decomposers = _possible_decomposers(set(basis))
elif target is None:
elif target is None or len(target.operation_names) == 0:
self._global_decomposers = _possible_decomposers(None)
self._basis_gates = None

Expand All @@ -114,7 +116,11 @@ def _build_error_map(self):

def _get_decomposer(self, qubit=None):
# include path for when target exists but target.num_qubits is None (BasicSimulator)
if self._target is not None and self._target.num_qubits is not None:
if (
self._target is not None
and self._target.num_qubits is not None
and len(self._target.operation_names) > 0
):
if qubit is not None:
qubits_tuple = (qubit,)
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,10 @@ def __init__(
if target is not None:
coupling_map = target.build_coupling_map()

unroll_definitions = not (basis_gates is None and target is None)
unroll_definitions = not (
(basis_gates is None or len(basis_gates) == 0)
and (target is None or len(target.operation_names) == 0)
)

# include path for when target exists but target.num_qubits is None (BasicSimulator)
if unroll_definitions and (target is None or target.num_qubits is None):
Expand Down
6 changes: 4 additions & 2 deletions qiskit/transpiler/passes/synthesis/unitary_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,11 @@ def __init__(
self._pulse_optimize = pulse_optimize
self._natural_direction = natural_direction
self._plugin_config = plugin_config
self._target = target
# Bypass target if it doesn't contain any basis gates (i.e it's _FakeTarget), as this
# not part of the official target model.
self._target = target if target is not None and len(target.operation_names) > 0 else None
if target is not None:
self._coupling_map = self._target.build_coupling_map()
self._coupling_map = target.build_coupling_map()
if synth_gates:
self._synth_gates = synth_gates
else:
Expand Down
18 changes: 13 additions & 5 deletions qiskit/transpiler/passes/utils/check_gate_direction.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,16 @@ def run(self, dag):
Args:
dag (DAGCircuit): DAG to check.
"""
self.property_set["is_direction_mapped"] = (
check_gate_direction_target(dag, self.target)
if self.target
else check_gate_direction_coupling(dag, set(self.coupling_map.get_edges()))
)
# Only use "check_gate_direction_target" if a target exists and target.operation_names
# is not empty, else use "check_gate_direction_coupling".
if self.target is None:
self.property_set["is_direction_mapped"] = check_gate_direction_coupling(
dag, set(self.coupling_map.get_edges())
)
elif len(self.target.operation_names) == 0:
# A _FakeTarget path, no basis gates, just use the coupling map
self.property_set["is_direction_mapped"] = check_gate_direction_coupling(
dag, set(self.target.build_coupling_map().get_edges())
)
else:
self.property_set["is_direction_mapped"] = check_gate_direction_target(dag, self.target)
7 changes: 7 additions & 0 deletions qiskit/transpiler/passes/utils/gate_direction.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ def run(self, dag):
TranspilerError: If the circuit cannot be mapped just by flipping the
cx nodes.
"""
# Only use "fix_gate_direction_target" if a target exists and target.operation_names
# is not empty, else use "fix_gate_direction_coupling".
if self.target is None:
return fix_gate_direction_coupling(dag, set(self.coupling_map.get_edges()))
elif len(self.target.operation_names) == 0:
# A _FakeTarget path, no basis gates, just use the coupling map
return fix_gate_direction_coupling(
dag, set(self.target.build_coupling_map().get_edges())
)
return fix_gate_direction_target(dag, self.target)
Loading