forked from Qiskit/qiskit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtranspiler.py
375 lines (329 loc) · 17.6 KB
/
transpiler.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2024.
#
# 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-sequence-index
"""Circuit transpile function"""
import logging
from time import time
from typing import List, Union, Dict, Callable, Any, Optional, TypeVar
from qiskit import user_config
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.dagcircuit import DAGCircuit
from qiskit.providers.backend import Backend
from qiskit.transpiler import Layout, CouplingMap, PropertySet
from qiskit.transpiler.basepasses import BasePass
from qiskit.transpiler.exceptions import TranspilerError, CircuitTooWideForTarget
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
logger = logging.getLogger(__name__)
_CircuitT = TypeVar("_CircuitT", bound=Union[QuantumCircuit, List[QuantumCircuit]])
def transpile( # pylint: disable=too-many-return-statements
circuits: _CircuitT,
backend: Optional[Backend] = None,
basis_gates: Optional[List[str]] = None,
coupling_map: Optional[Union[CouplingMap, List[List[int]]]] = None,
initial_layout: Optional[Union[Layout, Dict, List]] = None,
layout_method: Optional[str] = None,
routing_method: Optional[str] = None,
translation_method: Optional[str] = None,
scheduling_method: Optional[str] = None,
dt: Optional[float] = None,
approximation_degree: Optional[float] = 1.0,
seed_transpiler: Optional[int] = None,
optimization_level: Optional[int] = None,
callback: Optional[Callable[[BasePass, DAGCircuit, float, PropertySet, int], Any]] = None,
output_name: Optional[Union[str, List[str]]] = None,
unitary_synthesis_method: str = "default",
unitary_synthesis_plugin_config: Optional[dict] = None,
target: Optional[Target] = None,
hls_config: Optional[HLSConfig] = None,
init_method: Optional[str] = None,
optimization_method: Optional[str] = None,
ignore_backend_supplied_default_methods: bool = False,
num_processes: Optional[int] = None,
qubits_initially_zero: bool = True,
) -> _CircuitT:
"""Transpile one or more circuits, according to some desired transpilation targets.
Transpilation is potentially done in parallel using multiprocessing when ``circuits``
is a list with > 1 :class:`~.QuantumCircuit` object, depending on the local environment
and configuration.
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``, 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
the priority is assigned to that specific constraint input or another input
(`target`/`backend(V2)`).
============================ ========= ========================
User Provided target backend(V2)
============================ ========= ========================
**basis_gates** target basis_gates
**coupling_map** target coupling_map
**dt** target dt
============================ ========= ========================
Args:
circuits: Circuit(s) to transpile
backend: If set, the transpiler will compile the input circuit to this target
device. If any other option is explicitly set (e.g., ``coupling_map``), it
will override the backend's.
basis_gates: List of basis gate names to unroll to
(e.g: ``['u1', 'u2', 'u3', 'cx']``). If ``None``, do not unroll.
coupling_map: Directed coupling map (perhaps custom) to target in mapping. If
the coupling map is symmetric, both directions need to be specified.
Multiple formats are supported:
#. ``CouplingMap`` instance
#. List, must be given as an adjacency matrix, where each entry
specifies all directed two-qubit interactions supported by backend,
e.g: ``[[0, 1], [0, 3], [1, 2], [1, 5], [2, 5], [4, 1], [5, 3]]``
initial_layout: Initial position of virtual qubits on physical qubits.
If this layout makes the circuit compatible with the coupling_map
constraints, it will be used. The final layout is not guaranteed to be the same,
as the transpiler may permute qubits through swaps or other means.
Multiple formats are supported:
#. ``Layout`` instance
#. Dict
* virtual to physical::
{qr[0]: 0,
qr[1]: 3,
qr[2]: 5}
* physical to virtual::
{0: qr[0],
3: qr[1],
5: qr[2]}
#. List
* virtual to physical::
[0, 3, 5] # virtual qubits are ordered (in addition to named)
* physical to virtual::
[qr[0], None, None, qr[1], None, qr[2]]
layout_method: Name of layout selection pass ('trivial', 'dense', 'sabre').
This can also be the external plugin name to use for the ``layout`` stage.
You can see a list of installed plugins by using :func:`~.list_stage_plugins` with
``"layout"`` for the ``stage_name`` argument.
routing_method: Name of routing pass
('basic', 'lookahead', 'stochastic', 'sabre', 'none'). Note
This can also be the external plugin name to use for the ``routing`` stage.
You can see a list of installed plugins by using :func:`~.list_stage_plugins` with
``"routing"`` for the ``stage_name`` argument.
translation_method: Name of translation pass (``"default"``, ``"translator"`` or
``"synthesis"``). This can also be the external plugin name to use for the
``translation`` stage. You can see a list of installed plugins by using
:func:`~.list_stage_plugins` with ``"translation"`` for the ``stage_name`` argument.
scheduling_method: Name of scheduling pass.
* ``'as_soon_as_possible'``: Schedule instructions greedily, as early as possible
on a qubit resource. (alias: ``'asap'``)
* ``'as_late_as_possible'``: Schedule instructions late, i.e. keeping qubits
in the ground state when possible. (alias: ``'alap'``)
If ``None``, no scheduling will be done. This can also be the external plugin name
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.
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)
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,
at the expense of longer transpilation time.
* 0: no optimization
* 1: light optimization
* 2: heavy optimization
* 3: even heavier optimization
If ``None``, level 2 will be chosen as default.
callback: A callback function that will be called after each
pass execution. The function will be called with 5 keyword
arguments,
| ``pass_``: the pass being run.
| ``dag``: the dag output of the pass.
| ``time``: the time to execute the pass.
| ``property_set``: the property set.
| ``count``: the index for the pass execution.
The exact arguments passed expose the internals of the pass manager,
and are subject to change as the pass manager internals change. If
you intend to reuse a callback function over multiple releases, be
sure to check that the arguments being passed are the same.
To use the callback feature, define a function that will
take in kwargs dict and access the variables. For example::
def callback_func(**kwargs):
pass_ = kwargs['pass_']
dag = kwargs['dag']
time = kwargs['time']
property_set = kwargs['property_set']
count = kwargs['count']
...
transpile(circ, callback=callback_func)
output_name: A list with strings to identify the output circuits. The length of
the list should be exactly the length of the ``circuits`` parameter.
unitary_synthesis_method (str): The name of the unitary synthesis
method to use. By default ``'default'`` is used. You can see a list of installed
plugins with :func:`.unitary_synthesis_plugin_names`.
unitary_synthesis_plugin_config: An optional configuration dictionary
that will be passed directly to the unitary synthesis plugin. By
default this setting will have no effect as the default unitary
synthesis method does not take custom configuration. This should
only be necessary when a unitary synthesis plugin is specified with
the ``unitary_synthesis_method`` argument. As this is custom for each
unitary synthesis plugin refer to the plugin documentation for how
to use this option.
target: A backend transpiler target. Normally this is specified as part of
the ``backend`` argument, but if you have manually constructed a
:class:`~qiskit.transpiler.Target` object you can specify it manually here.
This will override the target from ``backend``.
hls_config: An optional configuration class
:class:`~qiskit.transpiler.passes.synthesis.HLSConfig` that will be passed directly
to :class:`~qiskit.transpiler.passes.synthesis.HighLevelSynthesis` transformation pass.
This configuration class allows to specify for various high-level objects the lists of
synthesis algorithms and their parameters.
init_method: The plugin name to use for the ``init`` stage. By default an external
plugin is not used. You can see a list of installed plugins by
using :func:`~.list_stage_plugins` with ``"init"`` for the stage
name argument.
optimization_method: The plugin name to use for the
``optimization`` stage. By default an external
plugin is not used. You can see a list of installed plugins by
using :func:`~.list_stage_plugins` with ``"optimization"`` for the
``stage_name`` argument.
ignore_backend_supplied_default_methods: If set to ``True`` any default methods specified by
a backend will be ignored. Some backends specify alternative default methods
to support custom compilation target-specific passes/plugins which support
backend-specific compilation techniques. If you'd prefer that these defaults were
not used this option is used to disable those backend-specific defaults.
num_processes: The maximum number of parallel processes to launch for this call to
transpile if parallel execution is enabled. This argument overrides
``num_processes`` in the user configuration file, and the ``QISKIT_NUM_PROCS``
environment variable. If set to ``None`` the system default or local user configuration
will be used.
qubits_initially_zero: Indicates whether the input circuit is zero-initialized.
Returns:
The transpiled circuit(s).
Raises:
TranspilerError: in case of bad inputs to transpiler (like conflicting parameters)
or errors in passes
"""
arg_circuits_list = isinstance(circuits, list)
circuits = circuits if arg_circuits_list else [circuits]
if not circuits:
return []
start_time = time()
if optimization_level is None:
# Take optimization level from the configuration or 1 as default.
config = user_config.get_config()
optimization_level = config.get("transpile_optimization_level", 2)
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()
if translation_method is None and hasattr(backend, "get_translation_stage_plugin"):
translation_method = backend.get_translation_stage_plugin()
output_name = _parse_output_name(output_name, circuits)
coupling_map = _parse_coupling_map(coupling_map)
_check_circuits_coupling_map(circuits, coupling_map, backend)
# 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.
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)
for name, circ in zip(output_name, out_circuits):
circ.name = name
end_time = time()
_log_transpile_time(start_time, end_time)
if arg_circuits_list:
return out_circuits
else:
return out_circuits[0]
def _check_circuits_coupling_map(circuits, cmap, backend):
# Check circuit width against number of qubits in coupling_map(s)
max_qubits = None
if cmap is not None:
max_qubits = cmap.size()
elif backend is not None:
max_qubits = backend.num_qubits
for circuit in circuits:
# If coupling_map is not None or num_qubits == 1
num_qubits = len(circuit.qubits)
if max_qubits is not None and (num_qubits > max_qubits):
raise CircuitTooWideForTarget(
f"Number of qubits ({num_qubits}) in {circuit.name} "
f"is greater than maximum ({max_qubits}) in the coupling_map"
)
def _log_transpile_time(start_time, end_time):
log_msg = f"Total Transpile Time - {((end_time - start_time) * 1000):.5f} (ms)"
logger.info(log_msg)
def _parse_coupling_map(coupling_map):
# coupling_map could be None, or a list of lists, e.g. [[0, 1], [2, 1]]
if isinstance(coupling_map, list) and all(
isinstance(i, list) and len(i) == 2 for i in coupling_map
):
return CouplingMap(coupling_map)
elif isinstance(coupling_map, list):
raise TranspilerError(
"Only a single input coupling map can be used with transpile() if you need to "
"target different coupling maps for different circuits you must call transpile() "
"multiple times"
)
else:
return coupling_map
def _parse_output_name(output_name, circuits):
# naming and returning circuits
# output_name could be either a string or a list
if output_name is not None:
if isinstance(output_name, str):
# single circuit
if len(circuits) == 1:
return [output_name]
# multiple circuits
else:
raise TranspilerError(
"Expected a list object of length equal "
+ "to that of the number of circuits "
+ "being transpiled"
)
elif isinstance(output_name, list):
if len(circuits) == len(output_name) and all(
isinstance(name, str) for name in output_name
):
return output_name
else:
raise TranspilerError(
"The length of output_name list "
"must be equal to the number of "
"transpiled circuits and the output_name "
"list should be strings."
)
else:
raise TranspilerError(
"The parameter output_name should be a string or a"
f"list of strings: {type(output_name)} was used."
)
else:
return [circuit.name for circuit in circuits]