-
Notifications
You must be signed in to change notification settings - Fork 162
/
backend_converter.py
359 lines (316 loc) · 14.4 KB
/
backend_converter.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
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022, 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.
"""Converters for migration from IBM Quantum BackendV1 to BackendV2."""
from __future__ import annotations
import logging
import warnings
from typing import Any, Dict, List
from qiskit.circuit.controlflow import (
CONTROL_FLOW_OP_NAMES,
ForLoopOp,
IfElseOp,
SwitchCaseOp,
WhileLoopOp,
)
from qiskit.circuit.gate import Gate
from qiskit.circuit.library.standard_gates import (
get_standard_gate_name_mapping,
RZGate,
U1Gate,
PhaseGate,
)
from qiskit.circuit.delay import Delay
from qiskit.circuit.parameter import Parameter
from qiskit.providers.backend import QubitProperties
from qiskit.providers.exceptions import BackendPropertyError
from qiskit.transpiler.target import InstructionProperties, Target
from ..models import BackendConfiguration, BackendProperties, PulseDefaults
logger = logging.getLogger(__name__)
def convert_to_target(
configuration: BackendConfiguration,
properties: BackendProperties = None,
defaults: PulseDefaults = None,
*,
include_control_flow: bool = True,
include_fractional_gates: bool = True,
) -> Target:
"""Decode transpiler target from backend data set.
This function generates :class:`.Target`` instance from intermediate
legacy objects such as :class:`.BackendProperties` and :class:`.PulseDefaults`.
These objects are usually components of the legacy :class:`.BackendV1` model.
Args:
configuration: Backend configuration as ``BackendConfiguration``
properties: Backend property dictionary or ``BackendProperties``
defaults: Backend pulse defaults dictionary or ``PulseDefaults``
include_control_flow: Set True to include control flow instructions.
include_fractional_gates: Set True to include fractioanl gates.
Returns:
A ``Target`` instance.
"""
add_delay = True
filter_faulty = True
required = ["measure", "delay", "reset"]
# Load Qiskit object representation
qiskit_inst_mapping = get_standard_gate_name_mapping()
qiskit_control_flow_mapping = {
"if_else": IfElseOp,
"while_loop": WhileLoopOp,
"for_loop": ForLoopOp,
"switch_case": SwitchCaseOp,
}
in_data = {"num_qubits": configuration.n_qubits}
# Parse global configuration properties
if hasattr(configuration, "dt"):
in_data["dt"] = configuration.dt # type: ignore[assignment]
if hasattr(configuration, "timing_constraints"):
in_data.update(configuration.timing_constraints)
# Create instruction property placeholder from backend configuration
basis_gates = set(getattr(configuration, "basis_gates", []))
supported_instructions = set(getattr(configuration, "supported_instructions", []))
gate_configs = {gate.name: gate for gate in configuration.gates}
all_instructions = set.union(
basis_gates, set(required), supported_instructions.intersection(CONTROL_FLOW_OP_NAMES)
)
inst_name_map = {}
faulty_ops = set()
faulty_qubits = set()
unsupported_instructions = []
# Create name to Qiskit instruction object repr mapping
for name in all_instructions:
if name in qiskit_control_flow_mapping:
if not include_control_flow:
# Remove name if this is control flow and dynamic circuits feature is disabled.
logger.info(
"Control flow %s is found but the dynamic circuits are disabled for this backend. "
"This instruction is excluded from the backend Target.",
name,
)
unsupported_instructions.append(name)
continue
if name in qiskit_inst_mapping:
qiskit_gate = qiskit_inst_mapping[name]
if (not include_fractional_gates) and is_fractional_gate(qiskit_gate):
# Remove name if this is fractional gate and fractional gate feature is disabled.
logger.info(
"Gate %s is found but the fractional gates are disabled for this backend. "
"This gate is excluded from the backend Target.",
name,
)
unsupported_instructions.append(name)
continue
inst_name_map[name] = qiskit_gate
elif name in gate_configs:
# GateConfig model is a translator of QASM opcode.
# This doesn't have quantum definition, so Qiskit transpiler doesn't perform
# any optimization in quantum domain.
# Usually GateConfig counterpart should exist in Qiskit namespace so this is rarely called.
this_config = gate_configs[name]
params = list(map(Parameter, getattr(this_config, "parameters", [])))
coupling_map = getattr(this_config, "coupling_map", [])
inst_name_map[name] = Gate(
name=name,
num_qubits=len(coupling_map[0]) if coupling_map else 0,
params=params,
)
else:
warnings.warn(
f"No gate definition for {name} can be found and is being excluded "
"from the generated target. You can use `custom_name_mapping` to provide "
"a definition for this operation.",
RuntimeWarning,
)
unsupported_instructions.append(name)
for name in unsupported_instructions:
all_instructions.remove(name)
# Create inst properties placeholder
# Without any assignment, properties value is None,
# which defines a global instruction that can be applied to any qubit(s).
# The None value behaves differently from an empty dictionary.
# See API doc of Target.add_instruction for details.
prop_name_map = dict.fromkeys(all_instructions)
for name in all_instructions:
if name in gate_configs:
if coupling_map := getattr(gate_configs[name], "coupling_map", None):
# Respect operational qubits that gate configuration defines
# This ties instruction to particular qubits even without properties information.
# Note that each instruction is considered to be ideal unless
# its spec (e.g. error, duration) is bound by the properties object.
prop_name_map[name] = dict.fromkeys(map(tuple, coupling_map))
# Populate instruction properties
if properties:
def _get_value(prop_dict: Dict, prop_name: str) -> Any:
if ndval := prop_dict.get(prop_name, None):
return ndval[0]
return None
# is_qubit_operational is a bit of expensive operation so precache the value
faulty_qubits = {
q for q in range(configuration.num_qubits) if not properties.is_qubit_operational(q)
}
qubit_properties = []
for qi in range(0, configuration.num_qubits):
# TODO faulty qubit handling might be needed since
# faulty qubit reporting qubit properties doesn't make sense.
try:
prop_dict = properties.qubit_property(qubit=qi)
except KeyError:
continue
qubit_properties.append(
QubitProperties(
t1=prop_dict.get("T1", (None, None))[0], # type: ignore[arg-type, union-attr]
t2=prop_dict.get("T2", (None, None))[0], # type: ignore[arg-type, union-attr]
frequency=prop_dict.get( # type: ignore[arg-type, union-attr]
"frequency", (None, None)
)[0],
)
)
in_data["qubit_properties"] = qubit_properties # type: ignore[assignment]
for name in all_instructions:
try:
for qubits, param_dict in properties.gate_property(
name
).items(): # type: ignore[arg-type, union-attr]
if filter_faulty and (
set.intersection(faulty_qubits, qubits)
or not properties.is_gate_operational(name, qubits)
):
try:
# Qubits might be pre-defined by the gate config
# However properties objects says the qubits is non-operational
del prop_name_map[name][qubits]
except KeyError:
pass
faulty_ops.add((name, qubits))
continue
if prop_name_map[name] is None:
# This instruction is tied to particular qubits
# i.e. gate config is not provided, and instruction has been globally defined.
prop_name_map[name] = {}
prop_name_map[name][qubits] = InstructionProperties(
error=_get_value(param_dict, "gate_error"), # type: ignore[arg-type]
duration=_get_value(param_dict, "gate_length"), # type: ignore[arg-type]
)
if isinstance(prop_name_map[name], dict) and any(
v is None for v in prop_name_map[name].values()
):
# Properties provides gate properties only for subset of qubits
# Associated qubit set might be defined by the gate config here
logger.info(
"Gate properties of instruction %s are not provided for every qubits. "
"This gate is ideal for some qubits and the rest is with finite error. "
"Created backend target may confuse error-aware circuit optimization.",
name,
)
except BackendPropertyError:
# This gate doesn't report any property
continue
# Measure instruction property is stored in qubit property
prop_name_map["measure"] = {}
for qubit_idx in range(configuration.num_qubits):
if filter_faulty and (qubit_idx in faulty_qubits):
continue
qubit_prop = properties.qubit_property(qubit_idx)
prop_name_map["measure"][(qubit_idx,)] = InstructionProperties(
error=_get_value(qubit_prop, "readout_error"), # type: ignore[arg-type]
duration=_get_value(qubit_prop, "readout_length"), # type: ignore[arg-type]
)
for op in required:
# Map required ops to each operational qubit
if prop_name_map[op] is None:
prop_name_map[op] = {
(q,): None
for q in range(configuration.num_qubits)
if not filter_faulty or (q not in faulty_qubits)
}
if defaults:
inst_sched_map = defaults.instruction_schedule_map
for name in inst_sched_map.instructions:
for qubits in inst_sched_map.qubits_with_instruction(name):
if not isinstance(qubits, tuple):
qubits = (qubits,)
if (
name not in all_instructions
or name not in prop_name_map
or prop_name_map[name] is None
or qubits not in prop_name_map[name]
):
logger.debug(
"Gate calibration for instruction %s on qubits %s is found "
"in the PulseDefaults payload. However, this entry is not defined in "
"the gate mapping of Target. This calibration is ignored.",
name,
qubits,
)
continue
if (name, qubits) in faulty_ops:
continue
entry = inst_sched_map._get_calibration_entry(name, qubits)
try:
prop_name_map[name][qubits].calibration = entry
except AttributeError:
logger.info(
"The PulseDefaults payload received contains an instruction %s on "
"qubits %s which is not present in the configuration or properties payload.",
name,
qubits,
)
# Add parsed properties to target
target = Target(**in_data)
for inst_name in all_instructions:
if inst_name == "delay" and not add_delay:
continue
if inst_name in qiskit_control_flow_mapping:
# Control flow operator doesn't have gate property.
target.add_instruction(
instruction=qiskit_control_flow_mapping[inst_name],
name=inst_name,
)
else:
target.add_instruction(
instruction=inst_name_map[inst_name],
properties=prop_name_map.get(inst_name, None),
name=inst_name,
)
return target
def qubit_props_list_from_props(
properties: BackendProperties,
) -> List[QubitProperties]:
"""Uses BackendProperties to construct
and return a list of QubitProperties.
"""
qubit_props: List[QubitProperties] = []
for qubit, _ in enumerate(properties.qubits):
try:
t_1 = properties.t1(qubit)
except BackendPropertyError:
t_1 = None
try:
t_2 = properties.t2(qubit)
except BackendPropertyError:
t_2 = None
try:
frequency = properties.frequency(qubit)
except BackendPropertyError:
frequency = None
qubit_props.append(
QubitProperties( # type: ignore[no-untyped-call]
t1=t_1,
t2=t_2,
frequency=frequency,
)
)
return qubit_props
def is_fractional_gate(gate: Gate) -> bool:
"""Test if gate is fractional gate familiy."""
# In IBM architecture these gates are virtual-Z and delay,
# which don't change control parameter with its gate parameter.
exclude_list = (RZGate, PhaseGate, U1Gate, Delay)
return len(gate.params) > 0 and not isinstance(gate, exclude_list)