qiskit-aer 0.17.2__cp314-cp314-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. qiskit_aer/VERSION.txt +1 -0
  2. qiskit_aer/__init__.py +89 -0
  3. qiskit_aer/aererror.py +30 -0
  4. qiskit_aer/aerprovider.py +119 -0
  5. qiskit_aer/backends/__init__.py +20 -0
  6. qiskit_aer/backends/aer_compiler.py +1085 -0
  7. qiskit_aer/backends/aer_simulator.py +1025 -0
  8. qiskit_aer/backends/aerbackend.py +679 -0
  9. qiskit_aer/backends/backend_utils.py +567 -0
  10. qiskit_aer/backends/backendconfiguration.py +395 -0
  11. qiskit_aer/backends/backendproperties.py +590 -0
  12. qiskit_aer/backends/compatibility.py +287 -0
  13. qiskit_aer/backends/controller_wrappers.cp314-win_amd64.pyd +0 -0
  14. qiskit_aer/backends/libopenblas.dll +0 -0
  15. qiskit_aer/backends/name_mapping.py +306 -0
  16. qiskit_aer/backends/qasm_simulator.py +925 -0
  17. qiskit_aer/backends/statevector_simulator.py +330 -0
  18. qiskit_aer/backends/unitary_simulator.py +316 -0
  19. qiskit_aer/jobs/__init__.py +35 -0
  20. qiskit_aer/jobs/aerjob.py +143 -0
  21. qiskit_aer/jobs/utils.py +66 -0
  22. qiskit_aer/library/__init__.py +204 -0
  23. qiskit_aer/library/control_flow_instructions/__init__.py +16 -0
  24. qiskit_aer/library/control_flow_instructions/jump.py +47 -0
  25. qiskit_aer/library/control_flow_instructions/mark.py +30 -0
  26. qiskit_aer/library/control_flow_instructions/store.py +29 -0
  27. qiskit_aer/library/default_qubits.py +44 -0
  28. qiskit_aer/library/instructions_table.csv +21 -0
  29. qiskit_aer/library/save_instructions/__init__.py +44 -0
  30. qiskit_aer/library/save_instructions/save_amplitudes.py +168 -0
  31. qiskit_aer/library/save_instructions/save_clifford.py +63 -0
  32. qiskit_aer/library/save_instructions/save_data.py +129 -0
  33. qiskit_aer/library/save_instructions/save_density_matrix.py +91 -0
  34. qiskit_aer/library/save_instructions/save_expectation_value.py +257 -0
  35. qiskit_aer/library/save_instructions/save_matrix_product_state.py +71 -0
  36. qiskit_aer/library/save_instructions/save_probabilities.py +156 -0
  37. qiskit_aer/library/save_instructions/save_stabilizer.py +70 -0
  38. qiskit_aer/library/save_instructions/save_state.py +79 -0
  39. qiskit_aer/library/save_instructions/save_statevector.py +120 -0
  40. qiskit_aer/library/save_instructions/save_superop.py +62 -0
  41. qiskit_aer/library/save_instructions/save_unitary.py +63 -0
  42. qiskit_aer/library/set_instructions/__init__.py +19 -0
  43. qiskit_aer/library/set_instructions/set_density_matrix.py +78 -0
  44. qiskit_aer/library/set_instructions/set_matrix_product_state.py +83 -0
  45. qiskit_aer/library/set_instructions/set_stabilizer.py +77 -0
  46. qiskit_aer/library/set_instructions/set_statevector.py +78 -0
  47. qiskit_aer/library/set_instructions/set_superop.py +78 -0
  48. qiskit_aer/library/set_instructions/set_unitary.py +78 -0
  49. qiskit_aer/noise/__init__.py +265 -0
  50. qiskit_aer/noise/device/__init__.py +25 -0
  51. qiskit_aer/noise/device/models.py +397 -0
  52. qiskit_aer/noise/device/parameters.py +202 -0
  53. qiskit_aer/noise/errors/__init__.py +30 -0
  54. qiskit_aer/noise/errors/base_quantum_error.py +119 -0
  55. qiskit_aer/noise/errors/pauli_error.py +283 -0
  56. qiskit_aer/noise/errors/pauli_lindblad_error.py +363 -0
  57. qiskit_aer/noise/errors/quantum_error.py +451 -0
  58. qiskit_aer/noise/errors/readout_error.py +355 -0
  59. qiskit_aer/noise/errors/standard_errors.py +498 -0
  60. qiskit_aer/noise/noise_model.py +1231 -0
  61. qiskit_aer/noise/noiseerror.py +30 -0
  62. qiskit_aer/noise/passes/__init__.py +18 -0
  63. qiskit_aer/noise/passes/local_noise_pass.py +160 -0
  64. qiskit_aer/noise/passes/relaxation_noise_pass.py +137 -0
  65. qiskit_aer/primitives/__init__.py +44 -0
  66. qiskit_aer/primitives/estimator.py +751 -0
  67. qiskit_aer/primitives/estimator_v2.py +159 -0
  68. qiskit_aer/primitives/sampler.py +361 -0
  69. qiskit_aer/primitives/sampler_v2.py +256 -0
  70. qiskit_aer/quantum_info/__init__.py +32 -0
  71. qiskit_aer/quantum_info/states/__init__.py +16 -0
  72. qiskit_aer/quantum_info/states/aer_densitymatrix.py +313 -0
  73. qiskit_aer/quantum_info/states/aer_state.py +525 -0
  74. qiskit_aer/quantum_info/states/aer_statevector.py +302 -0
  75. qiskit_aer/utils/__init__.py +44 -0
  76. qiskit_aer/utils/noise_model_inserter.py +66 -0
  77. qiskit_aer/utils/noise_transformation.py +431 -0
  78. qiskit_aer/version.py +86 -0
  79. qiskit_aer-0.17.2.dist-info/METADATA +209 -0
  80. qiskit_aer-0.17.2.dist-info/RECORD +83 -0
  81. qiskit_aer-0.17.2.dist-info/WHEEL +5 -0
  82. qiskit_aer-0.17.2.dist-info/licenses/LICENSE.txt +203 -0
  83. qiskit_aer-0.17.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,679 @@
1
+ # This code is part of Qiskit.
2
+ #
3
+ # (C) Copyright IBM 2018, 2019.
4
+ #
5
+ # This code is licensed under the Apache License, Version 2.0. You may
6
+ # obtain a copy of this license in the LICENSE.txt file in the root directory
7
+ # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8
+ #
9
+ # Any modifications or derivative works of this code must retain this
10
+ # copyright notice, and modified files need to carry a notice indicating
11
+ # that they have been altered from the originals.
12
+ """
13
+ Aer qasm simulator backend.
14
+ """
15
+
16
+ import copy
17
+ import datetime
18
+ import logging
19
+ import time
20
+ import uuid
21
+ import warnings
22
+ from abc import ABC, abstractmethod
23
+
24
+ from qiskit.circuit import QuantumCircuit, ParameterExpression, Delay
25
+ from qiskit.providers import BackendV2 as Backend
26
+ from qiskit.result import Result
27
+ from qiskit.transpiler import CouplingMap
28
+ from qiskit.transpiler.target import Target
29
+ from ..aererror import AerError
30
+ from ..jobs import AerJob
31
+ from ..noise.noise_model import NoiseModel, QuantumErrorLocation
32
+ from ..noise.errors.base_quantum_error import QuantumChannelInstruction
33
+ from .aer_compiler import compile_circuit, assemble_circuits, generate_aer_config
34
+ from .backend_utils import format_save_type, circuit_optypes
35
+ from .name_mapping import NAME_MAPPING
36
+
37
+ # pylint: disable=import-error, no-name-in-module, abstract-method
38
+ from .controller_wrappers import AerConfig
39
+
40
+ # Logger
41
+ logger = logging.getLogger(__name__)
42
+
43
+
44
+ class AerBackend(Backend, ABC):
45
+ """Aer Backend class."""
46
+
47
+ def __init__(
48
+ self, configuration, properties=None, provider=None, target=None, backend_options=None
49
+ ):
50
+ """Aer class for backends.
51
+
52
+ This method should initialize the module and its configuration, and
53
+ raise an exception if a component of the module is
54
+ not available.
55
+
56
+ Args:
57
+ configuration (AerBackendConfiguration): backend configuration.
58
+ properties (AerBackendProperties or None): Optional, backend properties.
59
+ provider (Provider): Optional, provider responsible for this backend.
60
+ target (Target): initial target for backend
61
+ backend_options (dict or None): Optional set custom backend options.
62
+
63
+ Raises:
64
+ AerError: if there is no name in the configuration
65
+ """
66
+ # Init configuration and provider in Backend
67
+ super().__init__(
68
+ provider=provider,
69
+ name=configuration.backend_name,
70
+ description=configuration.description,
71
+ backend_version=configuration.backend_version,
72
+ )
73
+
74
+ # Initialize backend configuration
75
+ self._properties = properties
76
+ self._configuration = configuration
77
+
78
+ # Custom option values for config
79
+ self._options_configuration = {}
80
+ self._options_properties = {}
81
+ self._target = target
82
+ self._mapping = NAME_MAPPING
83
+ if target is not None:
84
+ self._from_backend = True
85
+ else:
86
+ self._from_backend = False
87
+
88
+ # Set options from backend_options dictionary
89
+ if backend_options is not None:
90
+ self.set_options(**backend_options)
91
+
92
+ # build coupling map
93
+ if self.configuration().coupling_map is not None:
94
+ self._coupling_map = CouplingMap(self.configuration().coupling_map)
95
+
96
+ def _convert_circuit_binds(self, circuit, binds, idx_map):
97
+ parameterizations = []
98
+
99
+ def append_param_values(index, bind_pos, param):
100
+ if param in binds:
101
+ parameterizations.append([(index, bind_pos), binds[param]])
102
+ elif isinstance(param, ParameterExpression):
103
+ # If parameter expression has no unbound parameters
104
+ # it's already bound and should be skipped
105
+ if not param.parameters:
106
+ return
107
+ if not binds:
108
+ raise AerError("The element of parameter_binds is empty.")
109
+ len_vals = len(next(iter(binds.values())))
110
+ bind_list = [
111
+ {
112
+ parameter: binds[parameter][i]
113
+ for parameter in param.parameters & binds.keys()
114
+ }
115
+ for i in range(len_vals)
116
+ ]
117
+ bound_values = [float(param.bind(x)) for x in bind_list]
118
+ parameterizations.append([(index, bind_pos), bound_values])
119
+
120
+ append_param_values(AerConfig.GLOBAL_PHASE_POS, -1, circuit.global_phase)
121
+
122
+ for index, instruction in enumerate(circuit.data):
123
+ if instruction.operation.is_parameterized():
124
+ for bind_pos, param in enumerate(instruction.operation.params):
125
+ append_param_values(idx_map[index] if idx_map else index, bind_pos, param)
126
+ return parameterizations
127
+
128
+ def _convert_binds(self, circuits, parameter_binds, idx_maps=None):
129
+ if isinstance(circuits, QuantumCircuit):
130
+ if len(parameter_binds) > 1:
131
+ raise AerError("More than 1 parameter table provided for a single circuit")
132
+
133
+ return [self._convert_circuit_binds(circuits, parameter_binds[0], None)]
134
+ elif len(parameter_binds) != len(circuits):
135
+ raise AerError(
136
+ "Number of input circuits does not match number of input "
137
+ "parameter bind dictionaries"
138
+ )
139
+ parameterizations = [
140
+ self._convert_circuit_binds(circuit, parameter_binds[idx], idx_maps[idx])
141
+ for idx, circuit in enumerate(circuits)
142
+ ]
143
+ return parameterizations
144
+
145
+ # pylint: disable=arguments-renamed
146
+ def run(self, circuits, parameter_binds=None, **run_options):
147
+ """Run circuits on the backend.
148
+
149
+ Args:
150
+ circuits (QuantumCircuit or list): The QuantumCircuit (or list
151
+ of QuantumCircuit objects) to run
152
+ parameter_binds (list): A list of parameter binding dictionaries.
153
+ See additional information (default: None).
154
+ run_options (kwargs): additional run time backend options.
155
+
156
+ Returns:
157
+ AerJob: The simulation job.
158
+
159
+ Raises:
160
+ TypeError: If ``parameter_binds`` is specified with an input or
161
+ has a length mismatch with the number of circuits.
162
+
163
+ Additional Information:
164
+ * Each parameter binding dictionary is of the form::
165
+
166
+ {
167
+ param_a: [val_1, val_2],
168
+ param_b: [val_3, val_1],
169
+ }
170
+
171
+ for all parameters in that circuit. The length of the value
172
+ list must be the same for all parameters, and the number of
173
+ parameter dictionaries in the list must match the length of
174
+ ``circuits`` (if ``circuits`` is a single ``QuantumCircuit``
175
+ object it should a list of length 1).
176
+ * kwarg options specified in ``run_options`` will temporarily override
177
+ any set options of the same name for the current run.
178
+
179
+ Raises:
180
+ ValueError: if run is not implemented
181
+ """
182
+ if isinstance(circuits, QuantumCircuit):
183
+ circuits = [circuits]
184
+
185
+ return self._run_circuits(circuits, parameter_binds, **run_options)
186
+
187
+ def _run_circuits(self, circuits, parameter_binds, **run_options):
188
+ """Run circuits by generating native circuits."""
189
+ # Submit job
190
+ job_id = str(uuid.uuid4())
191
+ aer_job = AerJob(
192
+ self,
193
+ job_id,
194
+ self._execute_circuits_job,
195
+ parameter_binds=parameter_binds,
196
+ circuits=circuits,
197
+ run_options=run_options,
198
+ )
199
+ aer_job.submit()
200
+
201
+ return aer_job
202
+
203
+ def configuration(self):
204
+ """Return the simulator backend configuration.
205
+
206
+ Returns:
207
+ BackendConfiguration: the configuration for the backend.
208
+ """
209
+ config = copy.copy(self._configuration)
210
+ for key, val in self._options_configuration.items():
211
+ setattr(config, key, val)
212
+ # If config has custom instructions add them to
213
+ # basis gates to include them for the qiskit transpiler
214
+ if hasattr(config, "custom_instructions"):
215
+ config.basis_gates = config.basis_gates + config.custom_instructions
216
+ return config
217
+
218
+ def properties(self):
219
+ """Return the simulator backend properties if set.
220
+
221
+ Returns:
222
+ BackendProperties: The backend properties or ``None`` if the
223
+ backend does not have properties set.
224
+ """
225
+ properties = copy.copy(self._properties)
226
+ for key, val in self._options_properties.items():
227
+ setattr(properties, key, val)
228
+ return properties
229
+
230
+ @property
231
+ def max_circuits(self):
232
+ if hasattr(self.configuration(), "max_experiments"):
233
+ return self.configuration().max_experiments
234
+ else:
235
+ return None
236
+
237
+ @property
238
+ def target(self):
239
+ if self._from_backend:
240
+ return self._target
241
+
242
+ # make target for AerBackend
243
+
244
+ # importing packages where they are needed, to avoid cyclic-import.
245
+ # pylint: disable=cyclic-import
246
+ from qiskit.transpiler.target import InstructionProperties
247
+ from qiskit.circuit.controlflow import ForLoopOp, IfElseOp, SwitchCaseOp, WhileLoopOp
248
+ from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping
249
+ from qiskit.circuit.parameter import Parameter
250
+ from qiskit.circuit.gate import Gate
251
+ from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES
252
+ from qiskit.providers.backend import QubitProperties
253
+
254
+ required = ["measure", "delay"]
255
+
256
+ configuration = self.configuration()
257
+ properties = self.properties()
258
+
259
+ # Load Qiskit object representation
260
+ qiskit_inst_mapping = get_standard_gate_name_mapping()
261
+ qiskit_inst_mapping.update(NAME_MAPPING)
262
+
263
+ qiskit_control_flow_mapping = {
264
+ "if_else": IfElseOp,
265
+ "while_loop": WhileLoopOp,
266
+ "for_loop": ForLoopOp,
267
+ "switch_case": SwitchCaseOp,
268
+ }
269
+ in_data = {"num_qubits": configuration.num_qubits}
270
+
271
+ # Parse global configuration properties
272
+ if hasattr(configuration, "dt"):
273
+ in_data["dt"] = configuration.dt
274
+ if hasattr(configuration, "timing_constraints"):
275
+ in_data.update(configuration.timing_constraints)
276
+
277
+ # Create instruction property placeholder from backend configuration
278
+ basis_gates = set(getattr(configuration, "basis_gates", []))
279
+ supported_instructions = set(getattr(configuration, "supported_instructions", []))
280
+ gate_configs = {gate.name: gate for gate in configuration.gates}
281
+ all_instructions = set.union(
282
+ basis_gates, set(required), supported_instructions.intersection(CONTROL_FLOW_OP_NAMES)
283
+ )
284
+ inst_name_map = {} # type: Dict[str, Instruction]
285
+
286
+ faulty_ops = set()
287
+ faulty_qubits = set()
288
+ unsupported_instructions = []
289
+
290
+ # Create name to Qiskit instruction object repr mapping
291
+ for name in all_instructions:
292
+ if name in qiskit_control_flow_mapping:
293
+ continue
294
+ if name in qiskit_inst_mapping:
295
+ inst_name_map[name] = qiskit_inst_mapping[name]
296
+ elif name in gate_configs:
297
+ this_config = gate_configs[name]
298
+ params = list(map(Parameter, getattr(this_config, "parameters", [])))
299
+ coupling_map = getattr(this_config, "coupling_map", [])
300
+ inst_name_map[name] = Gate(
301
+ name=name,
302
+ num_qubits=len(coupling_map[0]) if coupling_map else 0,
303
+ params=params,
304
+ )
305
+ else:
306
+ warnings.warn(
307
+ f"No gate definition for {name} can be found and is being excluded "
308
+ "from the generated target.",
309
+ RuntimeWarning,
310
+ )
311
+ unsupported_instructions.append(name)
312
+
313
+ for name in unsupported_instructions:
314
+ all_instructions.remove(name)
315
+
316
+ # Create inst properties placeholder
317
+ # Without any assignment, properties value is None,
318
+ # which defines a global instruction that can be applied to any qubit(s).
319
+ # The None value behaves differently from an empty dictionary.
320
+ # See API doc of Target.add_instruction for details.
321
+ prop_name_map = dict.fromkeys(all_instructions)
322
+ for name in all_instructions:
323
+ if name in gate_configs:
324
+ if coupling_map := getattr(gate_configs[name], "coupling_map", None):
325
+ # Respect operational qubits that gate configuration defines
326
+ # This ties instruction to particular qubits even without properties information.
327
+ # Note that each instruction is considered to be ideal unless
328
+ # its spec (e.g. error, duration) is bound by the properties object.
329
+ prop_name_map[name] = dict.fromkeys(map(tuple, coupling_map))
330
+
331
+ # Populate instruction properties
332
+ if properties:
333
+
334
+ def _get_value(prop_dict, prop_name):
335
+ if ndval := prop_dict.get(prop_name, None):
336
+ return ndval[0]
337
+ return None
338
+
339
+ # is_qubit_operational is a bit of expensive operation so precache the value
340
+ faulty_qubits = {
341
+ q for q in range(configuration.num_qubits) if not properties.is_qubit_operational(q)
342
+ }
343
+
344
+ qubit_properties = []
345
+ for qi in range(0, configuration.num_qubits):
346
+ # TODO faulty qubit handling might be needed since
347
+ # faulty qubit reporting qubit properties doesn't make sense.
348
+ try:
349
+ prop_dict = properties.qubit_property(qubit=qi)
350
+ except KeyError:
351
+ continue
352
+ qubit_properties.append(
353
+ QubitProperties(
354
+ t1=prop_dict.get("T1", (None, None))[0],
355
+ t2=prop_dict.get("T2", (None, None))[0],
356
+ frequency=prop_dict.get("frequency", (None, None))[0],
357
+ )
358
+ )
359
+ in_data["qubit_properties"] = qubit_properties
360
+
361
+ for name in all_instructions:
362
+ for qubits, params in properties.gate_property(name).items():
363
+ if set.intersection(
364
+ faulty_qubits, qubits
365
+ ) or not properties.is_gate_operational(name, qubits):
366
+ try:
367
+ # Qubits might be pre-defined by the gate config
368
+ # However properties objects says the qubits is non-operational
369
+ del prop_name_map[name][qubits]
370
+ except KeyError:
371
+ pass
372
+ faulty_ops.add((name, qubits))
373
+ continue
374
+ if prop_name_map[name] is None:
375
+ prop_name_map[name] = {}
376
+ prop_name_map[name][qubits] = InstructionProperties(
377
+ error=_get_value(params, "gate_error"),
378
+ duration=_get_value(params, "gate_length"),
379
+ )
380
+ if isinstance(prop_name_map[name], dict) and any(
381
+ v is None for v in prop_name_map[name].values()
382
+ ):
383
+ # Properties provides gate properties only for subset of qubits
384
+ # Associated qubit set might be defined by the gate config here
385
+ logger.info(
386
+ "Gate properties of instruction %s are not provided for every qubits. "
387
+ "This gate is ideal for some qubits and the rest is with finite error. "
388
+ "Created backend target may confuse error-aware circuit optimization.",
389
+ name,
390
+ )
391
+
392
+ # Measure instruction property is stored in qubit property
393
+ prop_name_map["measure"] = {}
394
+
395
+ for qubit_idx in range(configuration.num_qubits):
396
+ if qubit_idx in faulty_qubits:
397
+ continue
398
+ qubit_prop = properties.qubit_property(qubit_idx)
399
+ prop_name_map["measure"][(qubit_idx,)] = InstructionProperties(
400
+ error=_get_value(qubit_prop, "readout_error"),
401
+ duration=_get_value(qubit_prop, "readout_length"),
402
+ )
403
+
404
+ for op in required:
405
+ # Map required ops to each operational qubit
406
+ if prop_name_map[op] is None:
407
+ prop_name_map[op] = {
408
+ (q,): None for q in range(configuration.num_qubits) if q not in faulty_qubits
409
+ }
410
+
411
+ # Add parsed properties to target
412
+ target = Target(**in_data)
413
+ for inst_name in all_instructions:
414
+ if inst_name in qiskit_control_flow_mapping:
415
+ # Control flow operator doesn't have gate property.
416
+ target.add_instruction(
417
+ instruction=qiskit_control_flow_mapping[inst_name],
418
+ name=inst_name,
419
+ )
420
+ else:
421
+ target.add_instruction(
422
+ instruction=inst_name_map[inst_name],
423
+ properties=prop_name_map.get(inst_name, None),
424
+ name=inst_name,
425
+ )
426
+
427
+ if self._coupling_map is not None:
428
+ target._coupling_graph = self._coupling_map.graph.copy()
429
+ return target
430
+
431
+ def set_max_qubits(self, max_qubits):
432
+ """Set maximun number of qubits to be used for this backend."""
433
+ if not self._from_backend:
434
+ self._configuration.n_qubits = max_qubits
435
+ self._set_configuration_option("n_qubits", max_qubits)
436
+
437
+ def clear_options(self):
438
+ """Reset the simulator options to default values."""
439
+ self._options = self._default_options()
440
+ self._options_configuration = {}
441
+ self._options_properties = {}
442
+
443
+ def _execute_circuits_job(
444
+ self, circuits, parameter_binds, run_options, job_id="", format_result=True
445
+ ):
446
+ """Run a job"""
447
+ # Start timer
448
+ start = time.time()
449
+
450
+ # Compile circuits
451
+ circuits, noise_model = self._compile(circuits, **run_options)
452
+
453
+ if self._target is not None:
454
+ aer_circuits, idx_maps = assemble_circuits(circuits, self.configuration().basis_gates)
455
+ else:
456
+ aer_circuits, idx_maps = assemble_circuits(circuits)
457
+ if parameter_binds:
458
+ run_options["parameterizations"] = self._convert_binds(
459
+ circuits, parameter_binds, idx_maps
460
+ )
461
+ elif not all(len(circuit.parameters) == 0 for circuit in circuits):
462
+ raise AerError("circuits have parameters but parameter_binds is not specified.")
463
+
464
+ for circ_id, aer_circuit in enumerate(aer_circuits):
465
+ aer_circuit.circ_id = circ_id
466
+
467
+ config = generate_aer_config(circuits, self.options, **run_options)
468
+
469
+ # Run simulation
470
+ metadata_map = {
471
+ aer_circuit.circ_id: circuit.metadata
472
+ for aer_circuit, circuit in zip(aer_circuits, circuits)
473
+ }
474
+ output = self._execute_circuits(aer_circuits, noise_model, config)
475
+
476
+ # Validate output
477
+ if not isinstance(output, dict):
478
+ logger.error("%s: simulation failed.", self.name)
479
+ if output:
480
+ logger.error("Output: %s", output)
481
+ raise AerError("simulation terminated without returning valid output.")
482
+
483
+ # Format results
484
+ output["job_id"] = job_id
485
+ output["date"] = datetime.datetime.now().isoformat()
486
+ output["backend_name"] = self.name
487
+ output["backend_version"] = self.configuration().backend_version
488
+
489
+ # Push metadata to experiment headers
490
+ for result in output["results"]:
491
+ if "header" not in result:
492
+ continue
493
+ result["header"]["metadata"] = metadata_map[result.pop("circ_id")]
494
+
495
+ # Add execution time
496
+ output["time_taken"] = time.time() - start
497
+
498
+ # Display warning if simulation failed
499
+ if not output.get("success", False):
500
+ msg = "Simulation failed"
501
+ if "status" in output:
502
+ msg += f" and returned the following error message:\n{output['status']}"
503
+ logger.warning(msg)
504
+ if format_result:
505
+ return self._format_results(output)
506
+ return output
507
+
508
+ @staticmethod
509
+ def _format_results(output):
510
+ """Format C++ simulator output for constructing Result"""
511
+ for result in output["results"]:
512
+ data = result.get("data", {})
513
+ metadata = result.get("metadata", {})
514
+ save_types = metadata.get("result_types", {})
515
+ save_subtypes = metadata.get("result_subtypes", {})
516
+ for key, val in data.items():
517
+ if key in save_types:
518
+ data[key] = format_save_type(val, save_types[key], save_subtypes[key])
519
+ return Result.from_dict(output)
520
+
521
+ def _compile(self, circuits, **run_options):
522
+ """Compile circuits and noise model"""
523
+ if isinstance(circuits, QuantumCircuit):
524
+ circuits = [circuits]
525
+ optypes = [circuit_optypes(circ) for circ in circuits]
526
+
527
+ # Compile Qasm3 instructions
528
+ circuits, optypes = compile_circuit(circuits, optypes=optypes)
529
+
530
+ # run option noise model
531
+ circuits, noise_model, run_options = self._assemble_noise_model(
532
+ circuits, optypes, **run_options
533
+ )
534
+
535
+ return circuits, noise_model
536
+
537
+ def _assemble_noise_model(self, circuits, optypes, **run_options):
538
+ """Move quantum error instructions from circuits to noise model"""
539
+ # Make a shallow copy so we can modify list elements if required
540
+ run_circuits = copy.copy(circuits)
541
+
542
+ # Flag for if we need to make a deep copy of the noise model
543
+ # This avoids unnecessarily copying the noise model for circuits
544
+ # that do not contain a quantum error
545
+ updated_noise = False
546
+ noise_model = run_options.get("noise_model", getattr(self.options, "noise_model", None))
547
+
548
+ # Add custom pass noise only to QuantumCircuit objects that contain delay
549
+ # instructions since this is the only instruction handled by the noise pass
550
+ # at present
551
+ if noise_model and all(isinstance(circ, QuantumCircuit) for circ in run_circuits):
552
+ npm = noise_model._pass_manager()
553
+ if npm is not None:
554
+ # Get indicies of circuits that need noise transpiling
555
+ transpile_idxs = [idx for idx, optype in enumerate(optypes) if Delay in optype]
556
+
557
+ # Transpile only the required circuits
558
+ transpiled_circuits = npm.run([run_circuits[i] for i in transpile_idxs])
559
+ if isinstance(transpiled_circuits, QuantumCircuit):
560
+ transpiled_circuits = [transpiled_circuits]
561
+
562
+ # Update the circuits with transpiled ones
563
+ for idx, circ in zip(transpile_idxs, transpiled_circuits):
564
+ run_circuits[idx] = circ
565
+ optypes[idx] = circuit_optypes(circ)
566
+
567
+ # Check if circuits contain quantum error instructions
568
+ for idx, circ in enumerate(run_circuits):
569
+ if QuantumChannelInstruction in optypes[idx]:
570
+ updated_circ = False
571
+ new_data = []
572
+ for datum in circ.data:
573
+ inst, qargs, cargs = datum.operation, datum.qubits, datum.clbits
574
+ if isinstance(inst, QuantumChannelInstruction):
575
+ updated_circ = True
576
+ if not updated_noise:
577
+ # Deep copy noise model on first update
578
+ if noise_model is None:
579
+ noise_model = NoiseModel()
580
+ else:
581
+ noise_model = copy.deepcopy(noise_model)
582
+ updated_noise = True
583
+ # Extract error and replace with place holder
584
+ qerror = inst._quantum_error
585
+ qerror_loc = QuantumErrorLocation(qerror)
586
+ new_data.append((qerror_loc, qargs, cargs))
587
+ optypes[idx].add(QuantumErrorLocation)
588
+ # Add error to noise model
589
+ if qerror.id not in noise_model._default_quantum_errors:
590
+ noise_model.add_all_qubit_quantum_error(qerror, qerror.id)
591
+ else:
592
+ new_data.append((inst, qargs, cargs))
593
+ if updated_circ:
594
+ new_circ = circ.copy()
595
+ new_circ.data = new_data
596
+ run_circuits[idx] = new_circ
597
+ optypes[idx].discard(QuantumChannelInstruction)
598
+
599
+ # Return the possibly updated circuits and noise model
600
+ return run_circuits, noise_model, run_options
601
+
602
+ def _get_executor(self, **run_options):
603
+ """Get the executor"""
604
+ if "executor" in run_options:
605
+ return run_options["executor"]
606
+ else:
607
+ return getattr(self._options, "executor", None)
608
+
609
+ @abstractmethod
610
+ def _execute_circuits(self, aer_circuits, noise_model, config):
611
+ """Execute aer circuits on the backend.
612
+
613
+ Args:
614
+ aer_circuits (List of AerCircuit): simulator input.
615
+ noise_model (NoiseModel): noise model
616
+ config (Dict): configuration for simulation
617
+
618
+ Returns:
619
+ dict: return a dictionary of results.
620
+ """
621
+ pass
622
+
623
+ def set_option(self, key, value):
624
+ """Special handling for setting backend options.
625
+
626
+ This method should be extended by sub classes to
627
+ update special option values.
628
+
629
+ Args:
630
+ key (str): key to update
631
+ value (any): value to update.
632
+
633
+ Raises:
634
+ AerError: if key is 'method' and val isn't in available methods.
635
+ """
636
+ # Add all other options to the options dict
637
+ # TODO: in the future this could be replaced with an options class
638
+ # for the simulators like configuration/properties to show all
639
+ # available options
640
+ if hasattr(self._configuration, key):
641
+ self._set_configuration_option(key, value)
642
+ elif hasattr(self._properties, key):
643
+ self._set_properties_option(key, value)
644
+ else:
645
+ if not hasattr(self._options, key):
646
+ raise AerError(f"Invalid option {key}")
647
+ if value is not None:
648
+ # Only add an option if its value is not None
649
+ setattr(self._options, key, value)
650
+ else:
651
+ # If setting an existing option to None reset it to default
652
+ # this is for backwards compatibility when setting it to None would
653
+ # remove it from the options dict
654
+ setattr(self._options, key, getattr(self._default_options(), key))
655
+
656
+ def set_options(self, **fields):
657
+ """Set the simulator options"""
658
+ for key, value in fields.items():
659
+ self.set_option(key, value)
660
+
661
+ def _set_configuration_option(self, key, value):
662
+ """Special handling for setting backend configuration options."""
663
+ if value is not None:
664
+ self._options_configuration[key] = value
665
+ elif key in self._options_configuration:
666
+ self._options_configuration.pop(key)
667
+
668
+ def _set_properties_option(self, key, value):
669
+ """Special handling for setting backend properties options."""
670
+ if value is not None:
671
+ self._options_properties[key] = value
672
+ elif key in self._options_properties:
673
+ self._options_properties.pop(key)
674
+
675
+ def __repr__(self):
676
+ """String representation of an AerBackend."""
677
+ name = self.__class__.__name__
678
+ display = f"'{self.name}'"
679
+ return f"{name}({display})"