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.
- qiskit_aer/VERSION.txt +1 -0
- qiskit_aer/__init__.py +89 -0
- qiskit_aer/aererror.py +30 -0
- qiskit_aer/aerprovider.py +119 -0
- qiskit_aer/backends/__init__.py +20 -0
- qiskit_aer/backends/aer_compiler.py +1085 -0
- qiskit_aer/backends/aer_simulator.py +1025 -0
- qiskit_aer/backends/aerbackend.py +679 -0
- qiskit_aer/backends/backend_utils.py +567 -0
- qiskit_aer/backends/backendconfiguration.py +395 -0
- qiskit_aer/backends/backendproperties.py +590 -0
- qiskit_aer/backends/compatibility.py +287 -0
- qiskit_aer/backends/controller_wrappers.cp314-win_amd64.pyd +0 -0
- qiskit_aer/backends/libopenblas.dll +0 -0
- qiskit_aer/backends/name_mapping.py +306 -0
- qiskit_aer/backends/qasm_simulator.py +925 -0
- qiskit_aer/backends/statevector_simulator.py +330 -0
- qiskit_aer/backends/unitary_simulator.py +316 -0
- qiskit_aer/jobs/__init__.py +35 -0
- qiskit_aer/jobs/aerjob.py +143 -0
- qiskit_aer/jobs/utils.py +66 -0
- qiskit_aer/library/__init__.py +204 -0
- qiskit_aer/library/control_flow_instructions/__init__.py +16 -0
- qiskit_aer/library/control_flow_instructions/jump.py +47 -0
- qiskit_aer/library/control_flow_instructions/mark.py +30 -0
- qiskit_aer/library/control_flow_instructions/store.py +29 -0
- qiskit_aer/library/default_qubits.py +44 -0
- qiskit_aer/library/instructions_table.csv +21 -0
- qiskit_aer/library/save_instructions/__init__.py +44 -0
- qiskit_aer/library/save_instructions/save_amplitudes.py +168 -0
- qiskit_aer/library/save_instructions/save_clifford.py +63 -0
- qiskit_aer/library/save_instructions/save_data.py +129 -0
- qiskit_aer/library/save_instructions/save_density_matrix.py +91 -0
- qiskit_aer/library/save_instructions/save_expectation_value.py +257 -0
- qiskit_aer/library/save_instructions/save_matrix_product_state.py +71 -0
- qiskit_aer/library/save_instructions/save_probabilities.py +156 -0
- qiskit_aer/library/save_instructions/save_stabilizer.py +70 -0
- qiskit_aer/library/save_instructions/save_state.py +79 -0
- qiskit_aer/library/save_instructions/save_statevector.py +120 -0
- qiskit_aer/library/save_instructions/save_superop.py +62 -0
- qiskit_aer/library/save_instructions/save_unitary.py +63 -0
- qiskit_aer/library/set_instructions/__init__.py +19 -0
- qiskit_aer/library/set_instructions/set_density_matrix.py +78 -0
- qiskit_aer/library/set_instructions/set_matrix_product_state.py +83 -0
- qiskit_aer/library/set_instructions/set_stabilizer.py +77 -0
- qiskit_aer/library/set_instructions/set_statevector.py +78 -0
- qiskit_aer/library/set_instructions/set_superop.py +78 -0
- qiskit_aer/library/set_instructions/set_unitary.py +78 -0
- qiskit_aer/noise/__init__.py +265 -0
- qiskit_aer/noise/device/__init__.py +25 -0
- qiskit_aer/noise/device/models.py +397 -0
- qiskit_aer/noise/device/parameters.py +202 -0
- qiskit_aer/noise/errors/__init__.py +30 -0
- qiskit_aer/noise/errors/base_quantum_error.py +119 -0
- qiskit_aer/noise/errors/pauli_error.py +283 -0
- qiskit_aer/noise/errors/pauli_lindblad_error.py +363 -0
- qiskit_aer/noise/errors/quantum_error.py +451 -0
- qiskit_aer/noise/errors/readout_error.py +355 -0
- qiskit_aer/noise/errors/standard_errors.py +498 -0
- qiskit_aer/noise/noise_model.py +1231 -0
- qiskit_aer/noise/noiseerror.py +30 -0
- qiskit_aer/noise/passes/__init__.py +18 -0
- qiskit_aer/noise/passes/local_noise_pass.py +160 -0
- qiskit_aer/noise/passes/relaxation_noise_pass.py +137 -0
- qiskit_aer/primitives/__init__.py +44 -0
- qiskit_aer/primitives/estimator.py +751 -0
- qiskit_aer/primitives/estimator_v2.py +159 -0
- qiskit_aer/primitives/sampler.py +361 -0
- qiskit_aer/primitives/sampler_v2.py +256 -0
- qiskit_aer/quantum_info/__init__.py +32 -0
- qiskit_aer/quantum_info/states/__init__.py +16 -0
- qiskit_aer/quantum_info/states/aer_densitymatrix.py +313 -0
- qiskit_aer/quantum_info/states/aer_state.py +525 -0
- qiskit_aer/quantum_info/states/aer_statevector.py +302 -0
- qiskit_aer/utils/__init__.py +44 -0
- qiskit_aer/utils/noise_model_inserter.py +66 -0
- qiskit_aer/utils/noise_transformation.py +431 -0
- qiskit_aer/version.py +86 -0
- qiskit_aer-0.17.2.dist-info/METADATA +209 -0
- qiskit_aer-0.17.2.dist-info/RECORD +83 -0
- qiskit_aer-0.17.2.dist-info/WHEEL +5 -0
- qiskit_aer-0.17.2.dist-info/licenses/LICENSE.txt +203 -0
- 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})"
|