Skip to content

Commit 246f5f3

Browse files
ElePTeliarbel
andauthored
Update transpiler pipeline to (only) use target internally (#12850)
* First attempt at supporting a virtual target (FakeTarget) with no basis gates in the preset pass managers. * Remove prints * Fix control-flow failures with fake target * Fix init stage, add instruction_supported method to FakeTarget * Tweak preset pass manager * Handle inst_map deprecation warning * Handle annotated operations * Do not skip stages with FakeTarget * Deprecate custom basis gates * Cleanup * Remove reno * Refactor creation of _FakeTarget, add tests * Fix handling of pre-optimization stage and gate direction passes * Restore cargo.lock * Address most lint complaints * Fix dt issue * Address HLS issue * Address GateDirection issue * Fix lint and improve style * Refactor plugin logic, handle target with no basis gates at the pass level (Python interface only) instead of pm level. * Remove deprecated arguments: instruction_durations, timing_constrants and custom basis_gates. Do not skip target unless dictated by backend. Add warning for backend + loose constraints and raise error if basis_gates contains 3q+ gates with a coupling map, as this generates a conflic in the model where it's not clear to which qubits the gates can apply. This is a limitation that comes from Target.from_configuration, but it's more user-friendly to raise the error from generate_preset_pass_manager. * Apply suggestions from Eli's code review Co-authored-by: Eli Arbel <46826214+eliarbel@users.noreply.github.com> * Clean up * Apply suggestions from Eli's code review. Add reno for API changes. Fix oversights * Fix merge conflict --------- Co-authored-by: Eli Arbel <46826214+eliarbel@users.noreply.github.com>
1 parent 2ec8f8f commit 246f5f3

23 files changed

Lines changed: 418 additions & 520 deletions

qiskit/compiler/transpiler.py

Lines changed: 23 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import logging
1717
from time import time
1818
from typing import List, Union, Dict, Callable, Any, Optional, TypeVar
19-
import warnings
2019

2120
from qiskit import user_config
2221
from qiskit.circuit.quantumcircuit import QuantumCircuit
@@ -25,35 +24,15 @@
2524
from qiskit.transpiler import Layout, CouplingMap, PropertySet
2625
from qiskit.transpiler.basepasses import BasePass
2726
from qiskit.transpiler.exceptions import TranspilerError, CircuitTooWideForTarget
28-
from qiskit.transpiler.instruction_durations import InstructionDurationsType
2927
from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig
3028
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
3129
from qiskit.transpiler.target import Target
32-
from qiskit.utils import deprecate_arg
3330

3431
logger = logging.getLogger(__name__)
3532

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

3835

39-
@deprecate_arg(
40-
name="instruction_durations",
41-
since="1.3",
42-
package_name="Qiskit",
43-
removal_timeline="in Qiskit 2.0",
44-
additional_msg="The `target` parameter should be used instead. You can build a `Target` instance "
45-
"with defined instruction durations with "
46-
"`Target.from_configuration(..., instruction_durations=...)`",
47-
)
48-
@deprecate_arg(
49-
name="timing_constraints",
50-
since="1.3",
51-
package_name="Qiskit",
52-
removal_timeline="in Qiskit 2.0",
53-
additional_msg="The `target` parameter should be used instead. You can build a `Target` instance "
54-
"with defined timing constraints with "
55-
"`Target.from_configuration(..., timing_constraints=...)`",
56-
)
5736
def transpile( # pylint: disable=too-many-return-statements
5837
circuits: _CircuitT,
5938
backend: Optional[Backend] = None,
@@ -64,10 +43,8 @@ def transpile( # pylint: disable=too-many-return-statements
6443
routing_method: Optional[str] = None,
6544
translation_method: Optional[str] = None,
6645
scheduling_method: Optional[str] = None,
67-
instruction_durations: Optional[InstructionDurationsType] = None,
6846
dt: Optional[float] = None,
6947
approximation_degree: Optional[float] = 1.0,
70-
timing_constraints: Optional[Dict[str, int]] = None,
7148
seed_transpiler: Optional[int] = None,
7249
optimization_level: Optional[int] = None,
7350
callback: Optional[Callable[[BasePass, DAGCircuit, float, PropertySet, int], Any]] = None,
@@ -90,8 +67,8 @@ def transpile( # pylint: disable=too-many-return-statements
9067
9168
The prioritization of transpilation target constraints works as follows: if a ``target``
9269
input is provided, it will take priority over any ``backend`` input or loose constraints
93-
(``basis_gates``, ``coupling_map``, ``instruction_durations``,
94-
``dt`` or ``timing_constraints``). If a ``backend`` is provided together with any loose constraint
70+
(``basis_gates``, ``coupling_map``, or ``dt``). If a ``backend`` is provided
71+
together with any loose constraint
9572
from the list above, the loose constraint will take priority over the corresponding backend
9673
constraint. This behavior is summarized in the table below. The first column
9774
in the table summarizes the potential user-provided constraints, and each cell shows whether
@@ -103,9 +80,7 @@ def transpile( # pylint: disable=too-many-return-statements
10380
============================ ========= ========================
10481
**basis_gates** target basis_gates
10582
**coupling_map** target coupling_map
106-
**instruction_durations** target instruction_durations
10783
**dt** target dt
108-
**timing_constraints** target timing_constraints
10984
============================ ========= ========================
11085
11186
Args:
@@ -176,40 +151,10 @@ def transpile( # pylint: disable=too-many-return-statements
176151
to use for the ``scheduling`` stage. You can see a list of installed plugins by
177152
using :func:`~.list_stage_plugins` with ``"scheduling"`` for the ``stage_name``
178153
argument.
179-
instruction_durations: Durations of instructions.
180-
Applicable only if scheduling_method is specified.
181-
The gate lengths defined in ``backend.properties`` are used as default.
182-
They are overwritten if this ``instruction_durations`` is specified.
183-
The format of ``instruction_durations`` must be as follows.
184-
The `instruction_durations` must be given as a list of tuples
185-
[(instruction_name, qubits, duration, unit), ...].
186-
| [('cx', [0, 1], 12.3, 'ns'), ('u3', [0], 4.56, 'ns')]
187-
| [('cx', [0, 1], 1000), ('u3', [0], 300)]
188-
If unit is omitted, the default is 'dt', which is a sample time depending on backend.
189-
If the time unit is 'dt', the duration must be an integer.
190154
dt: Backend sample time (resolution) in seconds.
191155
If ``None`` (default), ``backend.dt`` is used.
192156
approximation_degree (float): heuristic dial used for circuit approximation
193157
(1.0=no approximation, 0.0=maximal approximation)
194-
timing_constraints: An optional control hardware restriction on instruction time resolution.
195-
A quantum computer backend may report a set of restrictions, namely:
196-
197-
- granularity: An integer value representing minimum pulse gate
198-
resolution in units of ``dt``. A user-defined pulse gate should have
199-
duration of a multiple of this granularity value.
200-
- min_length: An integer value representing minimum pulse gate
201-
length in units of ``dt``. A user-defined pulse gate should be longer
202-
than this length.
203-
- pulse_alignment: An integer value representing a time resolution of gate
204-
instruction starting time. Gate instruction should start at time which
205-
is a multiple of the alignment value.
206-
- acquire_alignment: An integer value representing a time resolution of measure
207-
instruction starting time. Measure instruction should start at time which
208-
is a multiple of the alignment value.
209-
210-
This information will be provided by the backend configuration.
211-
If the backend doesn't have any restriction on the instruction time allocation,
212-
then ``timing_constraints`` is None and no adjustment will be performed.
213158
seed_transpiler: Sets random seed for the stochastic parts of the transpiler
214159
optimization_level: How much optimization to perform on the circuits.
215160
Higher levels generate more optimized circuits,
@@ -308,18 +253,6 @@ def callback_func(**kwargs):
308253
config = user_config.get_config()
309254
optimization_level = config.get("transpile_optimization_level", 2)
310255

311-
if (
312-
scheduling_method is not None
313-
and backend is None
314-
and target is None
315-
and not instruction_durations
316-
):
317-
warnings.warn(
318-
"When scheduling circuits without backend,"
319-
" 'instruction_durations' should be usually provided.",
320-
UserWarning,
321-
)
322-
323256
if not ignore_backend_supplied_default_methods:
324257
if scheduling_method is None and hasattr(backend, "get_scheduling_stage_plugin"):
325258
scheduling_method = backend.get_scheduling_stage_plugin()
@@ -333,43 +266,27 @@ def callback_func(**kwargs):
333266
# Edge cases require using the old model (loose constraints) instead of building a target,
334267
# but we don't populate the passmanager config with loose constraints unless it's one of
335268
# the known edge cases to control the execution path.
336-
# Filter instruction_durations and timing_constraints deprecation
337-
with warnings.catch_warnings():
338-
warnings.filterwarnings(
339-
"ignore",
340-
category=DeprecationWarning,
341-
message=".*``timing_constraints`` is deprecated as of Qiskit 1.3.*",
342-
module="qiskit",
343-
)
344-
warnings.filterwarnings(
345-
"ignore",
346-
category=DeprecationWarning,
347-
message=".*``instruction_durations`` is deprecated as of Qiskit 1.3.*",
348-
module="qiskit",
349-
)
350-
pm = generate_preset_pass_manager(
351-
optimization_level,
352-
target=target,
353-
backend=backend,
354-
basis_gates=basis_gates,
355-
coupling_map=coupling_map,
356-
instruction_durations=instruction_durations,
357-
timing_constraints=timing_constraints,
358-
initial_layout=initial_layout,
359-
layout_method=layout_method,
360-
routing_method=routing_method,
361-
translation_method=translation_method,
362-
scheduling_method=scheduling_method,
363-
approximation_degree=approximation_degree,
364-
seed_transpiler=seed_transpiler,
365-
unitary_synthesis_method=unitary_synthesis_method,
366-
unitary_synthesis_plugin_config=unitary_synthesis_plugin_config,
367-
hls_config=hls_config,
368-
init_method=init_method,
369-
optimization_method=optimization_method,
370-
dt=dt,
371-
qubits_initially_zero=qubits_initially_zero,
372-
)
269+
pm = generate_preset_pass_manager(
270+
optimization_level,
271+
target=target,
272+
backend=backend,
273+
basis_gates=basis_gates,
274+
coupling_map=coupling_map,
275+
initial_layout=initial_layout,
276+
layout_method=layout_method,
277+
routing_method=routing_method,
278+
translation_method=translation_method,
279+
scheduling_method=scheduling_method,
280+
approximation_degree=approximation_degree,
281+
seed_transpiler=seed_transpiler,
282+
unitary_synthesis_method=unitary_synthesis_method,
283+
unitary_synthesis_plugin_config=unitary_synthesis_plugin_config,
284+
hls_config=hls_config,
285+
init_method=init_method,
286+
optimization_method=optimization_method,
287+
dt=dt,
288+
qubits_initially_zero=qubits_initially_zero,
289+
)
373290

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

qiskit/transpiler/passes/basis/basis_translator.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,16 @@ def __init__(self, equivalence_library, target_basis, target=None, min_qubits=0)
9898
min_qubits (int): The minimum number of qubits for operations in the input
9999
dag to translate.
100100
"""
101-
102101
super().__init__()
103102
self._equiv_lib = equivalence_library
104103
self._target_basis = target_basis
105-
self._target = target
104+
# Bypass target if it doesn't contain any basis gates (i.e. it's a _FakeTarget), as this
105+
# not part of the official target model.
106+
self._target = target if target is not None and len(target.operation_names) > 0 else None
106107
self._non_global_operations = None
107108
self._qargs_with_non_global_operation = {}
108109
self._min_qubits = min_qubits
109-
if target is not None:
110+
if self._target is not None:
110111
self._non_global_operations = self._target.get_non_global_operation_names()
111112
self._qargs_with_non_global_operation = defaultdict(set)
112113
for gate in self._non_global_operations:

qiskit/transpiler/passes/layout/vf2_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def build_average_error_map(target, coupling_map):
177177
# running the fallback heuristic
178178
if not built and target is not None and coupling_map is None:
179179
coupling_map = target.build_coupling_map()
180-
if not built and coupling_map is not None:
180+
if not built and coupling_map is not None and num_qubits is not None:
181181
for qubit in range(num_qubits):
182182
avg_map.add_error(
183183
(qubit, qubit),

qiskit/transpiler/passes/optimization/consolidate_blocks.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ def __init__(
9191
"""
9292
super().__init__()
9393
self.basis_gates = None
94-
self.target = target
94+
# Bypass target if it doesn't contain any basis gates (i.e. it's a _FakeTarget), as this
95+
# not part of the official target model.
96+
self.target = target if target is not None and len(target.operation_names) > 0 else None
9597
if basis_gates is not None:
9698
self.basis_gates = set(basis_gates)
9799
self.force_consolidate = force_consolidate

qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,17 +80,19 @@ def __init__(self, basis=None, target=None):
8080
"""
8181
super().__init__()
8282

83-
if basis:
83+
if basis and len(basis) > 0:
8484
self._basis_gates = set(basis)
8585
else:
8686
self._basis_gates = None
87-
self._target = target
87+
# Bypass target if it doesn't contain any basis gates (i.e. it's a _FakeTarget), as this
88+
# not part of the official target model.
89+
self._target = target if target is not None and len(target.operation_names) > 0 else None
8890
self._global_decomposers = None
8991
self._local_decomposers_cache = {}
9092

91-
if basis:
93+
if self._basis_gates:
9294
self._global_decomposers = _possible_decomposers(set(basis))
93-
elif target is None:
95+
elif target is None or len(target.operation_names) == 0:
9496
self._global_decomposers = _possible_decomposers(None)
9597
self._basis_gates = None
9698

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

115117
def _get_decomposer(self, qubit=None):
116118
# include path for when target exists but target.num_qubits is None (BasicSimulator)
117-
if self._target is not None and self._target.num_qubits is not None:
119+
if (
120+
self._target is not None
121+
and self._target.num_qubits is not None
122+
and len(self._target.operation_names) > 0
123+
):
118124
if qubit is not None:
119125
qubits_tuple = (qubit,)
120126
else:

qiskit/transpiler/passes/synthesis/high_level_synthesis.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,10 @@ def __init__(
234234
if target is not None:
235235
coupling_map = target.build_coupling_map()
236236

237-
unroll_definitions = not (basis_gates is None and target is None)
237+
unroll_definitions = not (
238+
(basis_gates is None or len(basis_gates) == 0)
239+
and (target is None or len(target.operation_names) == 0)
240+
)
238241

239242
# include path for when target exists but target.num_qubits is None (BasicSimulator)
240243
if unroll_definitions and (target is None or target.num_qubits is None):

qiskit/transpiler/passes/synthesis/unitary_synthesis.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,11 @@ def __init__(
138138
self._pulse_optimize = pulse_optimize
139139
self._natural_direction = natural_direction
140140
self._plugin_config = plugin_config
141-
self._target = target
141+
# Bypass target if it doesn't contain any basis gates (i.e it's _FakeTarget), as this
142+
# not part of the official target model.
143+
self._target = target if target is not None and len(target.operation_names) > 0 else None
142144
if target is not None:
143-
self._coupling_map = self._target.build_coupling_map()
145+
self._coupling_map = target.build_coupling_map()
144146
if synth_gates:
145147
self._synth_gates = synth_gates
146148
else:

qiskit/transpiler/passes/utils/check_gate_direction.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,16 @@ def run(self, dag):
4545
Args:
4646
dag (DAGCircuit): DAG to check.
4747
"""
48-
self.property_set["is_direction_mapped"] = (
49-
check_gate_direction_target(dag, self.target)
50-
if self.target
51-
else check_gate_direction_coupling(dag, set(self.coupling_map.get_edges()))
52-
)
48+
# Only use "check_gate_direction_target" if a target exists and target.operation_names
49+
# is not empty, else use "check_gate_direction_coupling".
50+
if self.target is None:
51+
self.property_set["is_direction_mapped"] = check_gate_direction_coupling(
52+
dag, set(self.coupling_map.get_edges())
53+
)
54+
elif len(self.target.operation_names) == 0:
55+
# A _FakeTarget path, no basis gates, just use the coupling map
56+
self.property_set["is_direction_mapped"] = check_gate_direction_coupling(
57+
dag, set(self.target.build_coupling_map().get_edges())
58+
)
59+
else:
60+
self.property_set["is_direction_mapped"] = check_gate_direction_target(dag, self.target)

qiskit/transpiler/passes/utils/gate_direction.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@ def run(self, dag):
8181
TranspilerError: If the circuit cannot be mapped just by flipping the
8282
cx nodes.
8383
"""
84+
# Only use "fix_gate_direction_target" if a target exists and target.operation_names
85+
# is not empty, else use "fix_gate_direction_coupling".
8486
if self.target is None:
8587
return fix_gate_direction_coupling(dag, set(self.coupling_map.get_edges()))
88+
elif len(self.target.operation_names) == 0:
89+
# A _FakeTarget path, no basis gates, just use the coupling map
90+
return fix_gate_direction_coupling(
91+
dag, set(self.target.build_coupling_map().get_edges())
92+
)
8693
return fix_gate_direction_target(dag, self.target)

0 commit comments

Comments
 (0)