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,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.")
|