-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
backend_compat.py
284 lines (257 loc) · 11 KB
/
backend_compat.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
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# 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.
"""Backend abstract interface for providers."""
from __future__ import annotations
from typing import List, Iterable, Any, Dict, Optional
from qiskit.exceptions import QiskitError
from qiskit.providers.backend import BackendV1, BackendV2
from qiskit.providers.backend import QubitProperties
from qiskit.utils.units import apply_prefix
from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping
from qiskit.circuit.measure import Measure
from qiskit.providers.models.backendconfiguration import BackendConfiguration
from qiskit.providers.models.backendproperties import BackendProperties
from qiskit.providers.models.pulsedefaults import PulseDefaults
from qiskit.providers.options import Options
from qiskit.providers.exceptions import BackendPropertyError
def convert_to_target(
configuration: BackendConfiguration,
properties: BackendProperties = None,
defaults: PulseDefaults = None,
custom_name_mapping: Optional[Dict[str, Any]] = None,
add_delay: bool = False,
):
"""Uses configuration, properties and pulse defaults
to construct and return Target class.
"""
# pylint: disable=cyclic-import
from qiskit.transpiler.target import (
Target,
InstructionProperties,
)
# Standard gates library mapping, multicontrolled gates not included since they're
# variable width
name_mapping = get_standard_gate_name_mapping()
target = None
if custom_name_mapping is not None:
name_mapping.update(custom_name_mapping)
# Parse from properties if it exsits
if properties is not None:
qubit_properties = qubit_props_list_from_props(properties=properties)
target = Target(num_qubits=configuration.n_qubits, qubit_properties=qubit_properties)
# Parse instructions
gates: Dict[str, Any] = {}
for gate in properties.gates:
name = gate.gate
if name in name_mapping:
if name not in gates:
gates[name] = {}
else:
raise QiskitError(
f"Operation name {name} does not have a known mapping. Use "
"custom_name_mapping to map this name to an Operation object"
)
qubits = tuple(gate.qubits)
gate_props = {}
for param in gate.parameters:
if param.name == "gate_error":
gate_props["error"] = param.value
if param.name == "gate_length":
gate_props["duration"] = apply_prefix(param.value, param.unit)
gates[name][qubits] = InstructionProperties(**gate_props)
for gate, props in gates.items():
inst = name_mapping[gate]
target.add_instruction(inst, props)
# Create measurement instructions:
measure_props = {}
for qubit, _ in enumerate(properties.qubits):
measure_props[(qubit,)] = InstructionProperties(
duration=properties.readout_length(qubit),
error=properties.readout_error(qubit),
)
target.add_instruction(Measure(), measure_props)
# Parse from configuration because properties doesn't exist
else:
target = Target(num_qubits=configuration.n_qubits)
for gate in configuration.gates:
name = gate.name
gate_props = (
{tuple(x): None for x in gate.coupling_map} # type: ignore[misc]
if hasattr(gate, "coupling_map")
else {None: None}
)
if name in name_mapping:
target.add_instruction(name_mapping[name], gate_props)
else:
raise QiskitError(
f"Operation name {name} does not have a known mapping. "
"Use custom_name_mapping to map this name to an Operation object"
)
target.add_instruction(Measure())
# parse global configuration properties
if hasattr(configuration, "dt"):
target.dt = configuration.dt
if hasattr(configuration, "timing_constraints"):
target.granularity = configuration.timing_constraints.get("granularity")
target.min_length = configuration.timing_constraints.get("min_length")
target.pulse_alignment = configuration.timing_constraints.get("pulse_alignment")
target.aquire_alignment = configuration.timing_constraints.get("acquire_alignment")
# If a pulse defaults exists use that as the source of truth
if defaults is not None:
inst_map = defaults.instruction_schedule_map
for inst in inst_map.instructions:
for qarg in inst_map.qubits_with_instruction(inst):
try:
qargs = tuple(qarg)
except TypeError:
qargs = (qarg,)
# Do NOT call .get method. This parses Qpbj immediately.
# This operation is computationally expensive and should be bypassed.
calibration_entry = inst_map._get_calibration_entry(inst, qargs)
if inst in target:
if inst == "measure":
for qubit in qargs:
target[inst][(qubit,)].calibration = calibration_entry
elif qargs in target[inst]:
target[inst][qargs].calibration = calibration_entry
combined_global_ops = set()
if configuration.basis_gates:
combined_global_ops.update(configuration.basis_gates)
for op in combined_global_ops:
if op not in target:
if op in name_mapping:
target.add_instruction(
name_mapping[op], {(bit,): None for bit in range(target.num_qubits)}
)
else:
raise QiskitError(
f"Operation name '{op}' does not have a known mapping. Use "
"custom_name_mapping to map this name to an Operation object"
)
if add_delay and "delay" not in target:
target.add_instruction(
name_mapping["delay"], {(bit,): None for bit in range(target.num_qubits)}
)
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
class BackendV2Converter(BackendV2):
"""A converter class that takes a :class:`~.BackendV1` instance and wraps it in a
:class:`~.BackendV2` interface.
This class implements the :class:`~.BackendV2` interface and is used to enable
common access patterns between :class:`~.BackendV1` and :class:`~.BackendV2`. This
class should only be used if you need a :class:`~.BackendV2` and still need
compatibility with :class:`~.BackendV1`.
"""
def __init__(
self,
backend: BackendV1,
name_mapping: Optional[Dict[str, Any]] = None,
add_delay: bool = False,
):
"""Initialize a BackendV2 converter instance based on a BackendV1 instance.
Args:
backend: The input :class:`~.BackendV1` based backend to wrap in a
:class:`~.BackendV2` interface
name_mapping: An optional dictionary that maps custom gate/operation names in
``backend`` to an :class:`~.Operation` object representing that
gate/operation. By default most standard gates names are mapped to the
standard gate object from :mod:`qiskit.circuit.library` this only needs
to be specified if the input ``backend`` defines gates in names outside
that set.
add_delay: If set to true a :class:`~qiskit.circuit.Delay` operation
will be added to the target as a supported operation for all
qubits
"""
self._backend = backend
self._config = self._backend.configuration()
super().__init__(
provider=backend.provider,
name=backend.name(),
description=self._config.description,
online_date=self._config.online_date,
backend_version=self._config.backend_version,
)
self._options = self._backend._options
self._properties = None
if hasattr(self._backend, "properties"):
self._properties = self._backend.properties()
self._defaults = None
self._target = None
self._name_mapping = name_mapping
self._add_delay = add_delay
@property
def target(self):
"""A :class:`qiskit.transpiler.Target` object for the backend.
:rtype: Target
"""
if self._target is None:
if self._defaults is None and hasattr(self._backend, "defaults"):
self._defaults = self._backend.defaults()
if self._properties is None and hasattr(self._backend, "properties"):
self._properties = self._backend.properties()
self._target = convert_to_target(
self._config,
self._properties,
self._defaults,
custom_name_mapping=self._name_mapping,
add_delay=self._add_delay,
)
return self._target
@property
def max_circuits(self):
return self._config.max_experiments
@classmethod
def _default_options(cls):
return Options()
@property
def dtm(self) -> float:
return self._config.dtm
@property
def meas_map(self) -> List[List[int]]:
return self._config.dt
def drive_channel(self, qubit: int):
self._config.drive(qubit)
def measure_channel(self, qubit: int):
self._config.measure(qubit)
def acquire_channel(self, qubit: int):
self._config.acquire(qubit)
def control_channel(self, qubits: Iterable[int]):
self._config.control(qubits)
def run(self, run_input, **options):
return self._backend.run(run_input, **options)