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,751 @@
1
+ # This code is part of Qiskit.
2
+ #
3
+ # (C) Copyright IBM 2022, 2023.
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
+ """
14
+ Estimator class.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from collections import defaultdict
20
+ from collections.abc import Sequence
21
+ from warnings import warn
22
+
23
+ import numpy as np
24
+ from qiskit.circuit import ParameterExpression, QuantumCircuit
25
+ from qiskit.compiler import transpile
26
+ from qiskit.primitives import BaseEstimatorV1, EstimatorResult
27
+ from qiskit.primitives.primitive_job import PrimitiveJob
28
+ from qiskit.providers import Options
29
+ from qiskit.quantum_info import Pauli, PauliList, SparsePauliOp
30
+ from qiskit.quantum_info.operators.base_operator import BaseOperator
31
+ from qiskit.quantum_info.operators.symplectic.base_pauli import BasePauli
32
+ from qiskit.result.models import ExperimentResult
33
+ from qiskit.transpiler import CouplingMap, PassManager
34
+ from qiskit.transpiler.passes import (
35
+ ApplyLayout,
36
+ EnlargeWithAncilla,
37
+ FullAncillaAllocation,
38
+ Optimize1qGatesDecomposition,
39
+ SetLayout,
40
+ )
41
+ from qiskit.utils import deprecate_func
42
+
43
+ from .. import AerError, AerSimulator
44
+ from .sampler import _circuit_key
45
+
46
+
47
+ def init_observable(observable: BaseOperator | str) -> SparsePauliOp:
48
+ """Initialize observable by converting the input to a :class:`~qiskit.quantum_info.SparsePauliOp`.
49
+
50
+ Args:
51
+ observable: The observable.
52
+
53
+ Returns:
54
+ The observable as :class:`~qiskit.quantum_info.SparsePauliOp`.
55
+
56
+ Raises:
57
+ AerError: when observable type cannot be converted to SparsePauliOp.
58
+ """
59
+
60
+ if isinstance(observable, SparsePauliOp):
61
+ return observable
62
+ elif isinstance(observable, BaseOperator) and not isinstance(observable, BasePauli):
63
+ raise AerError(f"observable type not supported: {type(observable)}")
64
+ else:
65
+ if isinstance(observable, PauliList):
66
+ raise AerError(f"observable type not supported: {type(observable)}")
67
+ return SparsePauliOp(observable)
68
+
69
+
70
+ def _observable_key(observable: SparsePauliOp) -> tuple:
71
+ """Private key function for SparsePauliOp.
72
+ Args:
73
+ observable: Input operator.
74
+
75
+ Returns:
76
+ Key for observables.
77
+ """
78
+ return (
79
+ observable.paulis.z.tobytes(),
80
+ observable.paulis.x.tobytes(),
81
+ observable.paulis.phase.tobytes(),
82
+ observable.coeffs.tobytes(),
83
+ )
84
+
85
+
86
+ class Estimator(BaseEstimatorV1):
87
+ """
88
+ Aer implementation of Estimator.
89
+
90
+ :Run Options:
91
+ - **shots** (None or int) --
92
+ The number of shots. If None and approximation is True, it calculates the exact
93
+ expectation values. Otherwise, it calculates expectation values with sampling.
94
+
95
+ - **seed** (int) --
96
+ Set a fixed seed for the sampling.
97
+
98
+ .. note::
99
+ Precedence of seeding for ``seed_simulator`` is as follows:
100
+
101
+ 1. ``seed_simulator`` in runtime (i.e. in :meth:`run`)
102
+ 2. ``seed`` in runtime (i.e. in :meth:`run`)
103
+ 3. ``seed_simulator`` of ``backend_options``.
104
+ 4. default.
105
+
106
+ ``seed`` is also used for sampling from a normal distribution when approximation is True.
107
+
108
+ When combined with the approximation option, we get the expectation values as follows:
109
+
110
+ * shots is None and approximation=False: Return an expectation value with sampling-noise w/
111
+ warning.
112
+ * shots is int and approximation=False: Return an expectation value with sampling-noise.
113
+ * shots is None and approximation=True: Return an exact expectation value.
114
+ * shots is int and approximation=True: Return expectation value with sampling-noise using a
115
+ normal distribution approximation.
116
+ """
117
+
118
+ def __init__(
119
+ self,
120
+ *,
121
+ backend_options: dict | None = None,
122
+ transpile_options: dict | None = None,
123
+ run_options: dict | None = None,
124
+ approximation: bool = False,
125
+ skip_transpilation: bool = False,
126
+ abelian_grouping: bool = True,
127
+ ):
128
+ """
129
+ Args:
130
+ backend_options: Options passed to AerSimulator.
131
+ transpile_options: Options passed to transpile.
132
+ run_options: Options passed to run.
133
+ approximation: If True, it calculates expectation values with normal distribution
134
+ approximation. Note that this appproximation ignores readout errors.
135
+ skip_transpilation: If True, transpilation is skipped.
136
+ abelian_grouping: Whether the observable should be grouped into commuting.
137
+ If approximation is True, this parameter is ignored and assumed to be False.
138
+ """
139
+ warn(
140
+ "Estimator has been deprecated as of Aer 0.15, please use EstimatorV2 instead.",
141
+ DeprecationWarning,
142
+ stacklevel=3,
143
+ )
144
+ super().__init__(options=run_options)
145
+ # These three private attributes used to be created by super, but were deprecated in Qiskit
146
+ # 0.46. See https://github.com/Qiskit/qiskit/pull/11051
147
+ self._circuits = []
148
+ self._parameters = []
149
+ self._observables = []
150
+
151
+ backend_options = {} if backend_options is None else backend_options
152
+ method = (
153
+ "density_matrix" if approximation and "noise_model" in backend_options else "automatic"
154
+ )
155
+ self._backend = AerSimulator(method=method)
156
+ self._backend.set_options(**backend_options)
157
+ self._transpile_options = Options()
158
+ if transpile_options is not None:
159
+ self._transpile_options.update_options(**transpile_options)
160
+ if not approximation:
161
+ warn(
162
+ "Option approximation=False is deprecated as of qiskit-aer 0.13. "
163
+ "It will be removed no earlier than 3 months after the release date. "
164
+ "Instead, use BackendEstimator from qiskit.primitives.",
165
+ DeprecationWarning,
166
+ stacklevel=3,
167
+ )
168
+ self._approximation = approximation
169
+ self._skip_transpilation = skip_transpilation
170
+ self._cache: dict[tuple[tuple[int], tuple[int], bool], tuple[dict, dict]] = {}
171
+ self._transpiled_circuits: dict[int, QuantumCircuit] = {}
172
+ self._layouts: dict[int, list[int]] = {}
173
+ self._circuit_ids: dict[tuple, int] = {}
174
+ self._observable_ids: dict[tuple, int] = {}
175
+ self._abelian_grouping = abelian_grouping
176
+
177
+ @property
178
+ @deprecate_func(
179
+ since="0.13",
180
+ package_name="qiskit-aer",
181
+ is_property=True,
182
+ )
183
+ def approximation(self):
184
+ """The approximation property"""
185
+ return self._approximation
186
+
187
+ @approximation.setter
188
+ @deprecate_func(
189
+ since="0.13",
190
+ package_name="qiskit-aer",
191
+ is_property=True,
192
+ )
193
+ def approximation(self, approximation):
194
+ """Setter for approximation"""
195
+ if not approximation:
196
+ warn(
197
+ "Option approximation=False is deprecated as of qiskit-aer 0.13. "
198
+ "It will be removed no earlier than 3 months after the release date. "
199
+ "Instead, use BackendEstimator from qiskit.primitives.",
200
+ DeprecationWarning,
201
+ stacklevel=3,
202
+ )
203
+ self._approximation = approximation
204
+
205
+ def _call(
206
+ self,
207
+ circuits: Sequence[int],
208
+ observables: Sequence[int],
209
+ parameter_values: Sequence[Sequence[float]],
210
+ **run_options,
211
+ ) -> EstimatorResult:
212
+ seed = run_options.pop("seed", None)
213
+ if seed is not None:
214
+ run_options.setdefault("seed_simulator", seed)
215
+
216
+ if self._approximation:
217
+ return self._compute_with_approximation(
218
+ circuits, observables, parameter_values, run_options, seed
219
+ )
220
+ else:
221
+ return self._compute(circuits, observables, parameter_values, run_options)
222
+
223
+ def _run(
224
+ self,
225
+ circuits: Sequence[QuantumCircuit],
226
+ observables: Sequence[BaseOperator],
227
+ parameter_values: Sequence[Sequence[float]],
228
+ **run_options,
229
+ ) -> PrimitiveJob:
230
+ circuit_indices: list = []
231
+ for circuit in circuits:
232
+ index = self._circuit_ids.get(_circuit_key(circuit))
233
+ if index is not None:
234
+ circuit_indices.append(index)
235
+ else:
236
+ circuit_indices.append(len(self._circuits))
237
+ self._circuit_ids[_circuit_key(circuit)] = len(self._circuits)
238
+ self._circuits.append(circuit)
239
+ self._parameters.append(circuit.parameters)
240
+ observable_indices: list = []
241
+ for observable in observables:
242
+ observable = init_observable(observable)
243
+ index = self._observable_ids.get(_observable_key(observable))
244
+ if index is not None:
245
+ observable_indices.append(index)
246
+ else:
247
+ observable_indices.append(len(self._observables))
248
+ self._observable_ids[_observable_key(observable)] = len(self._observables)
249
+ self._observables.append(observable)
250
+ job = PrimitiveJob(
251
+ self._call,
252
+ circuit_indices,
253
+ observable_indices,
254
+ parameter_values,
255
+ **run_options,
256
+ )
257
+ # The public submit method was removed in Qiskit 0.46
258
+ (job.submit if hasattr(job, "submit") else job._submit)() # pylint: disable=no-member
259
+ return job
260
+
261
+ def _compute(self, circuits, observables, parameter_values, run_options):
262
+ if "shots" in run_options and run_options["shots"] is None:
263
+ warn(
264
+ "If `shots` is None and `approximation` is False, "
265
+ "the number of shots is automatically set to backend options' "
266
+ f"shots={self._backend.options.shots}.",
267
+ RuntimeWarning,
268
+ )
269
+
270
+ # Key for cache
271
+ key = (tuple(circuits), tuple(observables), self._approximation)
272
+
273
+ # Create expectation value experiments.
274
+ if key in self._cache: # Use a cache
275
+ experiments_dict, obs_maps = self._cache[key]
276
+ exp_map = self._pre_process_params(circuits, observables, parameter_values, obs_maps)
277
+ experiments, parameter_binds = self._flatten(experiments_dict, exp_map)
278
+ post_processings = self._create_post_processing(
279
+ circuits, observables, parameter_values, obs_maps, exp_map
280
+ )
281
+ else:
282
+ self._transpile_circuits(circuits)
283
+ circ_obs_map = defaultdict(list)
284
+ # Aggregate observables
285
+ for circ_ind, obs_ind in zip(circuits, observables):
286
+ circ_obs_map[circ_ind].append(obs_ind)
287
+ experiments_dict = {}
288
+ obs_maps = {} # circ_ind => obs_ind => term_ind (Original Pauli) => basis_ind
289
+ # Group and create measurement circuit
290
+ for circ_ind, obs_indices in circ_obs_map.items():
291
+ pauli_list = sum(
292
+ self._observables[obs_ind].paulis for obs_ind in obs_indices
293
+ ).unique()
294
+ if self._abelian_grouping:
295
+ pauli_lists = pauli_list.group_commuting(qubit_wise=True)
296
+ else:
297
+ pauli_lists = [PauliList(pauli) for pauli in pauli_list]
298
+ obs_map = defaultdict(list)
299
+ for obs_ind in obs_indices:
300
+ for pauli in self._observables[obs_ind].paulis:
301
+ for basis_ind, pauli_list in enumerate(pauli_lists):
302
+ if pauli in pauli_list:
303
+ obs_map[obs_ind].append(basis_ind)
304
+ break
305
+ obs_maps[circ_ind] = obs_map
306
+ bases = [_paulis2basis(pauli_list) for pauli_list in pauli_lists]
307
+ if len(bases) == 1 and not bases[0].x.any() and not bases[0].z.any(): # identity
308
+ break
309
+ meas_circuits = [self._create_meas_circuit(basis, circ_ind) for basis in bases]
310
+ circuit = (
311
+ self._circuits[circ_ind]
312
+ if self._skip_transpilation
313
+ else self._transpiled_circuits[circ_ind]
314
+ )
315
+ experiments_dict[circ_ind] = self._combine_circs(circuit, meas_circuits)
316
+ self._cache[key] = experiments_dict, obs_maps
317
+
318
+ exp_map = self._pre_process_params(circuits, observables, parameter_values, obs_maps)
319
+
320
+ # Flatten
321
+ experiments, parameter_binds = self._flatten(experiments_dict, exp_map)
322
+
323
+ # Create PostProcessing
324
+ post_processings = self._create_post_processing(
325
+ circuits, observables, parameter_values, obs_maps, exp_map
326
+ )
327
+
328
+ # Run experiments
329
+ if experiments:
330
+ results = (
331
+ self._backend.run(
332
+ experiments,
333
+ parameter_binds=parameter_binds if any(parameter_binds) else None,
334
+ **run_options,
335
+ )
336
+ .result()
337
+ .results
338
+ )
339
+ else:
340
+ results = []
341
+
342
+ # Post processing (calculate expectation values)
343
+ expectation_values, metadata = zip(
344
+ *(post_processing.run(results) for post_processing in post_processings)
345
+ )
346
+ return EstimatorResult(np.real_if_close(expectation_values), list(metadata))
347
+
348
+ def _pre_process_params(self, circuits, observables, parameter_values, obs_maps):
349
+ exp_map = defaultdict(dict) # circ_ind => basis_ind => (parameter, parameter_values)
350
+ for circ_ind, obs_ind, param_val in zip(circuits, observables, parameter_values):
351
+ self._validate_parameter_length(param_val, circ_ind)
352
+ parameter = self._parameters[circ_ind]
353
+ for basis_ind in obs_maps[circ_ind][obs_ind]:
354
+ if (
355
+ circ_ind in exp_map
356
+ and basis_ind in exp_map[circ_ind]
357
+ and len(self._parameters[circ_ind]) > 0
358
+ ):
359
+ param_vals = exp_map[circ_ind][basis_ind][1]
360
+ if param_val not in param_vals:
361
+ param_vals.append(param_val)
362
+ else:
363
+ exp_map[circ_ind][basis_ind] = (parameter, [param_val])
364
+
365
+ return exp_map
366
+
367
+ @staticmethod
368
+ def _flatten(experiments_dict, exp_map):
369
+ experiments_list = []
370
+ parameter_binds = []
371
+ for circ_ind in experiments_dict:
372
+ experiments_list.extend(experiments_dict[circ_ind])
373
+ for _, (parameter, param_vals) in exp_map[circ_ind].items():
374
+ parameter_binds.extend(
375
+ [
376
+ {
377
+ param: [param_val[i] for param_val in param_vals]
378
+ for i, param in enumerate(parameter)
379
+ }
380
+ ]
381
+ )
382
+ return experiments_list, parameter_binds
383
+
384
+ def _create_meas_circuit(self, basis: Pauli, circuit_index: int):
385
+ qargs = np.arange(basis.num_qubits)[basis.z | basis.x]
386
+ meas_circuit = QuantumCircuit(basis.num_qubits, len(qargs))
387
+ for clbit, qarg in enumerate(qargs):
388
+ if basis.x[qarg]:
389
+ if basis.z[qarg]:
390
+ meas_circuit.sdg(qarg)
391
+ meas_circuit.h(qarg)
392
+ meas_circuit.measure(qarg, clbit)
393
+ meas_circuit.metadata = {"basis": basis}
394
+
395
+ if self._skip_transpilation:
396
+ return meas_circuit
397
+
398
+ layout = self._layouts[circuit_index]
399
+ passmanager = PassManager([SetLayout(layout)])
400
+ opt1q = Optimize1qGatesDecomposition(target=self._backend.target)
401
+ passmanager.append(opt1q)
402
+ if isinstance(self._backend.coupling_map, CouplingMap):
403
+ coupling_map = self._backend.coupling_map
404
+ passmanager.append(FullAncillaAllocation(coupling_map))
405
+ passmanager.append(EnlargeWithAncilla())
406
+ passmanager.append(ApplyLayout())
407
+ return passmanager.run(meas_circuit)
408
+
409
+ @staticmethod
410
+ def _combine_circs(circuit: QuantumCircuit, meas_circuits: list[QuantumCircuit]):
411
+ circs = []
412
+ for meas_circuit in meas_circuits:
413
+ new_circ = circuit.copy()
414
+ for creg in meas_circuit.cregs:
415
+ new_circ.add_register(creg)
416
+ new_circ.compose(meas_circuit, inplace=True)
417
+ _update_metadata(new_circ, meas_circuit.metadata)
418
+ circs.append(new_circ)
419
+ return circs
420
+
421
+ @staticmethod
422
+ def _calculate_result_index(circ_ind, obs_ind, term_ind, param_val, obs_maps, exp_map) -> int:
423
+ basis_ind = obs_maps[circ_ind][obs_ind][term_ind]
424
+
425
+ result_index = 0
426
+ for _circ_ind, basis_map in exp_map.items():
427
+ for _basis_ind, (_, (_, param_vals)) in enumerate(basis_map.items()):
428
+ if circ_ind == _circ_ind and basis_ind == _basis_ind:
429
+ result_index += param_vals.index(param_val)
430
+ return result_index
431
+ result_index += len(param_vals)
432
+ raise AerError("Bug. Please report from issue: https://github.com/Qiskit/qiskit-aer/issues")
433
+
434
+ def _create_post_processing(
435
+ self, circuits, observables, parameter_values, obs_maps, exp_map
436
+ ) -> list[_PostProcessing]:
437
+ post_processings = []
438
+ for circ_ind, obs_ind, param_val in zip(circuits, observables, parameter_values):
439
+ result_indices: list[int | None] = []
440
+ paulis = []
441
+ coeffs = []
442
+ observable = self._observables[obs_ind]
443
+ for term_ind, (pauli, coeff) in enumerate(zip(observable.paulis, observable.coeffs)):
444
+ # Identity
445
+ if not pauli.x.any() and not pauli.z.any():
446
+ result_indices.append(None)
447
+ paulis.append(PauliList(pauli))
448
+ coeffs.append([coeff])
449
+ continue
450
+
451
+ result_index = self._calculate_result_index(
452
+ circ_ind, obs_ind, term_ind, param_val, obs_maps, exp_map
453
+ )
454
+ if result_index in result_indices:
455
+ i = result_indices.index(result_index)
456
+ paulis[i] += pauli
457
+ coeffs[i].append(coeff)
458
+ else:
459
+ result_indices.append(result_index)
460
+ paulis.append(PauliList(pauli))
461
+ coeffs.append([coeff])
462
+ post_processings.append(_PostProcessing(result_indices, paulis, coeffs))
463
+ return post_processings
464
+
465
+ def _compute_with_approximation(
466
+ self, circuits, observables, parameter_values, run_options, seed
467
+ ):
468
+ # Key for cache
469
+ key = (tuple(circuits), tuple(observables), self._approximation)
470
+ shots = run_options.pop("shots", None)
471
+
472
+ # Create expectation value experiments.
473
+ if key in self._cache: # Use a cache
474
+ parameter_binds = defaultdict(dict)
475
+ for i, j, value in zip(circuits, observables, parameter_values):
476
+ self._validate_parameter_length(value, i)
477
+ for k, v in zip(self._parameters[i], value):
478
+ if k in parameter_binds[(i, j)]:
479
+ parameter_binds[(i, j)][k].append(v)
480
+ else:
481
+ parameter_binds[(i, j)][k] = [v]
482
+ experiment_manager = self._cache[key]
483
+ experiment_manager.parameter_binds = list(parameter_binds.values())
484
+ else:
485
+ self._transpile_circuits(circuits)
486
+ experiment_manager = _ExperimentManager()
487
+ for i, j, value in zip(circuits, observables, parameter_values):
488
+ self._validate_parameter_length(value, i)
489
+ if (i, j) in experiment_manager.keys:
490
+ key_index = experiment_manager.keys.index((i, j))
491
+ circuit = experiment_manager.experiment_circuits[key_index]
492
+ else:
493
+ circuit = (
494
+ self._circuits[i].copy()
495
+ if self._skip_transpilation
496
+ else self._transpiled_circuits[i].copy()
497
+ )
498
+ observable = self._observables[j]
499
+ if shots is None:
500
+ circuit.save_expectation_value(observable, self._layouts[i])
501
+ else:
502
+ for term_ind, pauli in enumerate(observable.paulis):
503
+ circuit.save_expectation_value(
504
+ pauli, self._layouts[i], label=str(term_ind)
505
+ )
506
+ experiment_manager.append(
507
+ key=(i, j),
508
+ parameter_bind=dict(zip(self._parameters[i], value)),
509
+ experiment_circuit=circuit,
510
+ )
511
+
512
+ self._cache[key] = experiment_manager
513
+ result = self._backend.run(
514
+ experiment_manager.experiment_circuits,
515
+ parameter_binds=experiment_manager.parameter_binds,
516
+ **run_options,
517
+ ).result()
518
+
519
+ # Post processing (calculate expectation values)
520
+ if shots is None:
521
+ expectation_values = [
522
+ result.data(i)["expectation_value"] for i in experiment_manager.experiment_indices
523
+ ]
524
+ metadata = [
525
+ {"simulator_metadata": result.results[i].metadata}
526
+ for i in experiment_manager.experiment_indices
527
+ ]
528
+ else:
529
+ expectation_values = []
530
+ rng = np.random.default_rng(seed)
531
+ metadata = []
532
+ experiment_indices = experiment_manager.experiment_indices
533
+ for i in range(len(experiment_manager)):
534
+ combined_expval = 0.0
535
+ combined_var = 0.0
536
+ result_index = experiment_indices[i]
537
+ observable_key = experiment_manager.get_observable_key(i)
538
+ coeffs = np.real_if_close(self._observables[observable_key].coeffs)
539
+ for term_ind, expval in result.data(result_index).items():
540
+ var = 1 - expval**2
541
+ coeff = coeffs[int(term_ind)]
542
+ combined_expval += expval * coeff
543
+ combined_var += var * coeff**2
544
+ # Sampling from normal distribution
545
+ standard_error = np.sqrt(combined_var / shots)
546
+ expectation_values.append(rng.normal(combined_expval, standard_error))
547
+ metadata.append(
548
+ {
549
+ "variance": np.real_if_close(combined_var).item(),
550
+ "shots": shots,
551
+ "simulator_metadata": result.results[result_index].metadata,
552
+ }
553
+ )
554
+
555
+ return EstimatorResult(np.real_if_close(expectation_values), metadata)
556
+
557
+ def _validate_parameter_length(self, parameter, circuit_index):
558
+ if len(parameter) != len(self._parameters[circuit_index]):
559
+ raise ValueError(
560
+ f"The number of values ({len(parameter)}) does not match "
561
+ f"the number of parameters ({len(self._parameters[circuit_index])})."
562
+ )
563
+
564
+ def _transpile(self, circuits):
565
+ if self._skip_transpilation:
566
+ return circuits
567
+ return transpile(circuits, self._backend, **self._transpile_options.__dict__)
568
+
569
+ def _transpile_circuits(self, circuits):
570
+ if self._skip_transpilation:
571
+ for i in set(circuits):
572
+ num_qubits = self._circuits[i].num_qubits
573
+ self._layouts[i] = list(range(num_qubits))
574
+ return
575
+ for i in set(circuits):
576
+ if i not in self._transpiled_circuits:
577
+ circuit = self._circuits[i].copy()
578
+ circuit.measure_all()
579
+ num_qubits = circuit.num_qubits
580
+ self._backend.set_max_qubits(num_qubits)
581
+ circuit = self._transpile(circuit)
582
+ bit_map = {bit: index for index, bit in enumerate(circuit.qubits)}
583
+ layout = [bit_map[qr[0]] for _, qr, _ in circuit[-num_qubits:]]
584
+ circuit.remove_final_measurements()
585
+ self._transpiled_circuits[i] = circuit
586
+ self._layouts[i] = layout
587
+
588
+
589
+ def _expval_with_variance(counts) -> tuple[float, float]:
590
+ denom = 0
591
+ expval = 0.0
592
+ for bin_outcome, freq in counts.items():
593
+ outcome = int(bin_outcome, 16)
594
+ denom += freq
595
+ expval += freq * (-1) ** bin(outcome).count("1")
596
+ # Divide by total shots
597
+ expval /= denom
598
+ # Compute variance
599
+ variance = 1 - expval**2
600
+ return expval, variance
601
+
602
+
603
+ class _PostProcessing:
604
+ def __init__(
605
+ self,
606
+ result_indices: list[int],
607
+ paulis: list[PauliList],
608
+ coeffs: list[list[float]],
609
+ ):
610
+ self._result_indices = result_indices
611
+ self._paulis = paulis
612
+ self._coeffs = [np.array(c) for c in coeffs]
613
+
614
+ def run(self, results: list[ExperimentResult]) -> tuple[float, dict]:
615
+ """Coumpute expectation value.
616
+
617
+ Args:
618
+ results: list of ExperimentResult.
619
+
620
+ Returns:
621
+ tuple of an expectation value and metadata.
622
+ """
623
+ combined_expval = 0.0
624
+ combined_var = 0.0
625
+ simulator_metadata = []
626
+ for c_i, paulis, coeffs in zip(self._result_indices, self._paulis, self._coeffs):
627
+ if c_i is None:
628
+ # Observable is identity
629
+ expvals, variances = np.array([1], dtype=complex), np.array([0], dtype=complex)
630
+ shots = 0
631
+ else:
632
+ result = results[c_i]
633
+ count = result.data.counts
634
+ shots = sum(count.values())
635
+ # added for compatibility with qiskit 1.4 (metadata as attribute)
636
+ # and qiskit 2.0 (header as dict)
637
+ try:
638
+ basis = result.header.metadata["basis"]
639
+ except AttributeError:
640
+ basis = result.header["metadata"]["basis"]
641
+ indices = np.where(basis.z | basis.x)[0]
642
+ measured_paulis = PauliList.from_symplectic(
643
+ paulis.z[:, indices], paulis.x[:, indices], 0
644
+ )
645
+ expvals, variances = _pauli_expval_with_variance(count, measured_paulis)
646
+ simulator_metadata.append(result.metadata)
647
+ combined_expval += np.dot(expvals, coeffs)
648
+ combined_var += np.dot(variances, coeffs**2)
649
+ metadata = {
650
+ "shots": shots,
651
+ "variance": np.real_if_close(combined_var).item(),
652
+ "simulator_metadata": simulator_metadata,
653
+ }
654
+ return combined_expval, metadata
655
+
656
+
657
+ def _update_metadata(circuit: QuantumCircuit, metadata: dict) -> QuantumCircuit:
658
+ if circuit.metadata:
659
+ circuit.metadata.update(metadata)
660
+ else:
661
+ circuit.metadata = metadata
662
+ return circuit
663
+
664
+
665
+ def _pauli_expval_with_variance(counts: dict, paulis: PauliList) -> tuple[np.ndarray, np.ndarray]:
666
+ # Diag indices
667
+ size = len(paulis)
668
+ diag_inds = _paulis2inds(paulis)
669
+
670
+ expvals = np.zeros(size, dtype=float)
671
+ denom = 0 # Total shots for counts dict
672
+ for hex_outcome, freq in counts.items():
673
+ outcome = int(hex_outcome, 16)
674
+ denom += freq
675
+ for k in range(size):
676
+ coeff = (-1) ** _parity(diag_inds[k] & outcome)
677
+ expvals[k] += freq * coeff
678
+
679
+ # Divide by total shots
680
+ expvals /= denom
681
+
682
+ variances = 1 - expvals**2
683
+ return expvals, variances
684
+
685
+
686
+ def _paulis2inds(paulis: PauliList) -> list[int]:
687
+ nonid = paulis.z | paulis.x
688
+ packed_vals = np.packbits(nonid, axis=1, bitorder="little").astype( # pylint:disable=no-member
689
+ object
690
+ )
691
+ power_uint8 = 1 << (8 * np.arange(packed_vals.shape[1], dtype=object))
692
+ inds = packed_vals @ power_uint8
693
+ return inds.tolist()
694
+
695
+
696
+ def _parity(integer: int) -> int:
697
+ """Return the parity of an integer"""
698
+ return bin(integer).count("1") % 2
699
+
700
+
701
+ def _paulis2basis(paulis: PauliList) -> Pauli:
702
+ return Pauli(
703
+ (
704
+ np.logical_or.reduce(paulis.z), # pylint:disable=no-member
705
+ np.logical_or.reduce(paulis.x), # pylint:disable=no-member
706
+ )
707
+ )
708
+
709
+
710
+ class _ExperimentManager:
711
+ def __init__(self):
712
+ self.keys: list[tuple[int, int]] = []
713
+ self.experiment_circuits: list[QuantumCircuit] = []
714
+ self.parameter_binds: list[dict[ParameterExpression, list[float]]] = []
715
+ self._input_indices: list[list[int]] = []
716
+ self._num_experiment: int = 0
717
+
718
+ def __len__(self):
719
+ return self._num_experiment
720
+
721
+ @property
722
+ def experiment_indices(self):
723
+ """indices of experiments"""
724
+ return np.argsort(sum(self._input_indices, [])).tolist()
725
+
726
+ def append(
727
+ self,
728
+ key: tuple[int, int],
729
+ parameter_bind: dict[ParameterExpression, float],
730
+ experiment_circuit: QuantumCircuit,
731
+ ):
732
+ """append experiments"""
733
+ if key in self.keys and parameter_bind:
734
+ key_index = self.keys.index(key)
735
+ for k, vs in self.parameter_binds[key_index].items():
736
+ vs.append(parameter_bind[k])
737
+ self._input_indices[key_index].append(self._num_experiment)
738
+ else:
739
+ self.experiment_circuits.append(experiment_circuit)
740
+ self.keys.append(key)
741
+ self.parameter_binds.append({k: [v] for k, v in parameter_bind.items()})
742
+ self._input_indices.append([self._num_experiment])
743
+
744
+ self._num_experiment += 1
745
+
746
+ def get_observable_key(self, index):
747
+ """return key of observables"""
748
+ for i, inputs in enumerate(self._input_indices):
749
+ if index in inputs:
750
+ return self.keys[i][1]
751
+ raise AerError("Unexpected behavior.")