This repository has been archived by the owner on Jul 24, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 67
/
json_decoder.py
420 lines (353 loc) · 14.9 KB
/
json_decoder.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
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# 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.
"""Custom JSON decoder."""
from typing import Dict, Tuple, Union, List, Any, Optional
import json
import logging
import dateutil.parser
from qiskit.providers.models import (
QasmBackendConfiguration,
PulseBackendConfiguration,
PulseDefaults,
BackendProperties,
Command,
)
from qiskit.providers.models.backendproperties import Gate as GateSchema
from qiskit.circuit.controlflow import IfElseOp, WhileLoopOp, ForLoopOp
from qiskit.circuit.gate import Gate, Instruction
from qiskit.circuit.parameter import Parameter
from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping
from qiskit.pulse.calibration_entries import PulseQobjDef
from qiskit.transpiler.target import Target, InstructionProperties
from qiskit.qobj.pulse_qobj import PulseLibraryItem
from qiskit.qobj.converters.pulse_instruction import QobjToInstructionConverter
from qiskit.utils import apply_prefix
from .converters import utc_to_local, utc_to_local_all
from ..ibm_qubit_properties import IBMQubitProperties
logger = logging.getLogger(__name__)
def defaults_from_server_data(defaults: Dict) -> PulseDefaults:
"""Decode pulse defaults data.
Args:
defaults: Raw pulse defaults data.
Returns:
A ``PulseDefaults`` instance.
"""
for item in defaults["pulse_library"]:
_decode_pulse_library_item(item)
for cmd in defaults["cmd_def"]:
if "sequence" in cmd:
for instr in cmd["sequence"]:
_decode_pulse_qobj_instr(instr)
return PulseDefaults.from_dict(defaults)
def properties_from_server_data(properties: Dict) -> BackendProperties:
"""Decode backend properties.
Args:
properties: Raw properties data.
Returns:
A ``BackendProperties`` instance.
"""
properties["last_update_date"] = dateutil.parser.isoparse(
properties["last_update_date"]
)
for qubit in properties["qubits"]:
for nduv in qubit:
nduv["date"] = dateutil.parser.isoparse(nduv["date"])
for gate in properties["gates"]:
for param in gate["parameters"]:
param["date"] = dateutil.parser.isoparse(param["date"])
for gen in properties["general"]:
gen["date"] = dateutil.parser.isoparse(gen["date"])
properties = utc_to_local_all(properties)
return BackendProperties.from_dict(properties)
def target_from_server_data(
configuration: Union[QasmBackendConfiguration, PulseBackendConfiguration],
pulse_defaults: Optional[Dict] = None,
properties: Optional[Dict] = None,
) -> Target:
"""Decode transpiler target from backend data set.
This function directly generate ``Target`` instance without generate
intermediate legacy objects such as ``BackendProperties`` and ``PulseDefaults``.
Args:
configuration: Backend configuration.
pulse_defaults: Backend pulse defaults dictionary.
properties: Backend property dictionary.
Returns:
A ``Target`` instance.
"""
required = ["measure", "delay"]
# 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,
}
in_data = {"num_qubits": configuration.n_qubits}
# Parse global configuration properties
if hasattr(configuration, "dt"):
in_data["dt"] = configuration.dt
if hasattr(configuration, "timing_constraints"):
in_data.update(configuration.timing_constraints)
# Create instruction property placeholder from backend configuration
supported_instructions = set(getattr(configuration, "supported_instructions", []))
basis_gates = set(getattr(configuration, "basis_gates", []))
gate_configs = {gate.name: gate for gate in configuration.gates}
inst_name_map = {} # type: Dict[str, Instruction]
prop_name_map = {} # type: Dict[str, Dict[Tuple[int, ...], InstructionProperties]]
all_instructions = set.union(supported_instructions, basis_gates, set(required))
faulty_qubits = set()
faulty_ops = set()
# Create name to Qiskit instruction object repr mapping
for name in all_instructions:
if name in qiskit_control_flow_mapping:
continue
if name in qiskit_inst_mapping:
inst_name_map[name] = qiskit_inst_mapping[name]
elif name in gate_configs:
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:
logger.warning(
"Definition of instruction %s is not found in the Qiskit namespace and "
"GateConfig is not provided by the BackendConfiguration payload. "
"Qiskit Gate model cannot be instantiated for this instruction and "
"this instruction is silently excluded from the Target. "
"Please add new gate class to Qiskit or provide GateConfig for this name.",
name,
)
all_instructions.remove(name)
continue
# Create empty inst properties from gate configs
for name, spec in gate_configs.items():
if hasattr(spec, "coupling_map"):
coupling_map = spec.coupling_map
prop_name_map[name] = dict.fromkeys(map(tuple, coupling_map))
else:
prop_name_map[name] = None
# Populate instruction properties
if properties:
qubit_properties = list(map(_decode_qubit_property, properties["qubits"]))
in_data["qubit_properties"] = qubit_properties
faulty_qubits = set(
q for q, prop in enumerate(qubit_properties) if not prop.operational
)
for gate_spec in map(GateSchema.from_dict, properties["gates"]):
name = gate_spec.gate
qubits = tuple(gate_spec.qubits)
if name not in all_instructions:
logger.info(
"Gate property for instruction %s on qubits %s is found "
"in the BackendProperties payload. However, this gate is not included in the "
"basis_gates or supported_instructions, or maybe the gate model "
"is not defined in the Qiskit namespace. This gate is ignored.",
name,
qubits,
)
continue
inst_prop, operational = _decode_instruction_property(gate_spec)
if set.intersection(faulty_qubits, qubits) or not operational:
faulty_ops.add((name, qubits))
try:
del prop_name_map[name][qubits]
except KeyError:
pass
continue
if prop_name_map[name] is None:
prop_name_map[name] = {}
prop_name_map[name][qubits] = inst_prop
# Measure instruction property is stored in qubit property in IBM
measure_props = list(map(_decode_measure_property, properties["qubits"]))
prop_name_map["measure"] = {}
for qubit, measure_prop in enumerate(measure_props):
if qubit in faulty_qubits:
continue
qubits = (qubit,)
prop_name_map["measure"][qubits] = measure_prop
# Special case for real IBM backend. They don't have delay in gate configuration.
if "delay" not in prop_name_map:
prop_name_map["delay"] = {
(q,): None
for q in range(configuration.num_qubits)
if q not in faulty_qubits
}
# Define pulse qobj converter and command sequence for lazy conversion
if pulse_defaults:
pulse_lib = list(
map(PulseLibraryItem.from_dict, pulse_defaults["pulse_library"])
)
converter = QobjToInstructionConverter(pulse_lib)
for cmd in map(Command.from_dict, pulse_defaults["cmd_def"]):
name = cmd.name
qubits = tuple(cmd.qubits)
if (name, qubits) in faulty_ops:
continue
if name not in all_instructions or qubits not in prop_name_map[name]:
logger.info(
"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
entry = PulseQobjDef(converter=converter, name=cmd.name)
entry.define(cmd.sequence)
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 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),
)
return target
def decode_pulse_qobj(pulse_qobj: Dict) -> None:
"""Decode a pulse Qobj.
Args:
pulse_qobj: Qobj to be decoded.
"""
for item in pulse_qobj["config"]["pulse_library"]:
_decode_pulse_library_item(item)
for exp in pulse_qobj["experiments"]:
for instr in exp["instructions"]:
_decode_pulse_qobj_instr(instr)
def decode_backend_configuration(config: Dict) -> None:
"""Decode backend configuration.
Args:
config: A ``QasmBackendConfiguration`` or ``PulseBackendConfiguration``
in dictionary format.
"""
config["online_date"] = dateutil.parser.isoparse(config["online_date"])
if "u_channel_lo" in config:
for u_channel_list in config["u_channel_lo"]:
for u_channel_lo in u_channel_list:
u_channel_lo["scale"] = _to_complex(u_channel_lo["scale"])
def decode_result(result: str, result_decoder: Any) -> Dict:
"""Decode result data.
Args:
result: Run result in string format.
result_decoder: A decoder class for loading the json
"""
result_dict = json.loads(result, cls=result_decoder)
if "date" in result_dict:
if isinstance(result_dict["date"], str):
result_dict["date"] = dateutil.parser.isoparse(result_dict["date"])
result_dict["date"] = utc_to_local(result_dict["date"])
return result_dict
def _to_complex(value: Union[List[float], complex]) -> complex:
"""Convert the input value to type ``complex``.
Args:
value: Value to be converted.
Returns:
Input value in ``complex``.
Raises:
TypeError: If the input value is not in the expected format.
"""
if isinstance(value, list) and len(value) == 2:
return complex(value[0], value[1])
elif isinstance(value, complex):
return value
raise TypeError("{} is not in a valid complex number format.".format(value))
def _decode_pulse_library_item(pulse_library_item: Dict) -> None:
"""Decode a pulse library item.
Args:
pulse_library_item: A ``PulseLibraryItem`` in dictionary format.
"""
pulse_library_item["samples"] = [
_to_complex(sample) for sample in pulse_library_item["samples"]
]
def _decode_pulse_qobj_instr(pulse_qobj_instr: Dict) -> None:
"""Decode a pulse Qobj instruction.
Args:
pulse_qobj_instr: A ``PulseQobjInstruction`` in dictionary format.
"""
if "val" in pulse_qobj_instr:
pulse_qobj_instr["val"] = _to_complex(pulse_qobj_instr["val"])
if "parameters" in pulse_qobj_instr and "amp" in pulse_qobj_instr["parameters"]:
pulse_qobj_instr["parameters"]["amp"] = _to_complex(
pulse_qobj_instr["parameters"]["amp"]
)
def _decode_qubit_property(qubit_specs: List[Dict]) -> IBMQubitProperties:
"""Decode qubit property data to generate IBMQubitProperty instance.
Args:
qubit_specs: List of qubit property dictionary.
Returns:
An ``IBMQubitProperty`` instance.
"""
in_data = {}
for spec in qubit_specs:
name = (spec["name"]).lower()
if name == "operational":
in_data[name] = bool(spec["value"])
elif name in IBMQubitProperties.__slots__:
in_data[name] = apply_prefix(
value=spec["value"], unit=spec.get("unit", None)
)
return IBMQubitProperties(**in_data) # type: ignore[no-untyped-call]
def _decode_instruction_property(
gate_spec: GateSchema,
) -> Tuple[InstructionProperties, bool]:
"""Decode gate property data to generate InstructionProperties instance.
Args:
gate_spec: List of gate property dictionary.
Returns:
An ``InstructionProperties`` instance and a boolean value representing
if this gate is operational.
"""
in_data = {}
operational = True
for param in gate_spec.parameters:
if param.name == "gate_error":
in_data["error"] = param.value
if param.name == "gate_length":
in_data["duration"] = apply_prefix(value=param.value, unit=param.unit)
if param.name == "operational" and not param.value:
operational = bool(param.value)
return InstructionProperties(**in_data), operational
def _decode_measure_property(qubit_specs: List[Dict]) -> InstructionProperties:
"""Decode qubit property data to generate InstructionProperties instance.
Args:
qubit_specs: List of qubit property dictionary.
Returns:
An ``InstructionProperties`` instance.
"""
in_data = {}
for spec in qubit_specs:
name = spec["name"]
if name == "readout_error":
in_data["error"] = spec["value"]
if name == "readout_length":
in_data["duration"] = apply_prefix(
value=spec["value"], unit=spec.get("unit", None)
)
return InstructionProperties(**in_data)