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,159 @@
|
|
|
1
|
+
# This code is part of Qiskit.
|
|
2
|
+
#
|
|
3
|
+
# (C) Copyright IBM 2024.
|
|
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
|
+
"""Estimator V2 implementation for Aer."""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from collections.abc import Iterable
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
from qiskit.primitives.base import BaseEstimatorV2
|
|
22
|
+
from qiskit.primitives.containers import DataBin, EstimatorPubLike, PrimitiveResult, PubResult
|
|
23
|
+
from qiskit.primitives.containers.estimator_pub import EstimatorPub
|
|
24
|
+
from qiskit.primitives.primitive_job import PrimitiveJob
|
|
25
|
+
from qiskit.quantum_info import Pauli
|
|
26
|
+
|
|
27
|
+
from qiskit_aer import AerSimulator
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class Options:
|
|
32
|
+
"""Options for :class:`~.EstimatorV2`."""
|
|
33
|
+
|
|
34
|
+
default_precision: float = 0.0
|
|
35
|
+
"""The default precision to use if none are specified in :meth:`~run`.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
backend_options: dict = field(default_factory=dict)
|
|
39
|
+
"""backend_options: Options passed to AerSimulator."""
|
|
40
|
+
|
|
41
|
+
run_options: dict = field(default_factory=dict)
|
|
42
|
+
"""run_options: Options passed to run."""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class EstimatorV2(BaseEstimatorV2):
|
|
46
|
+
"""Evaluates expectation values for provided quantum circuit and observable combinations.
|
|
47
|
+
|
|
48
|
+
Run a fast simulation using Aer.
|
|
49
|
+
Sampling from a normal distribution ``N(expval, precison)`` when set to ``precision``.
|
|
50
|
+
|
|
51
|
+
* ``default_precision``: The default precision to use if none are specified in :meth:`~run`.
|
|
52
|
+
Default: 0.0.
|
|
53
|
+
|
|
54
|
+
* ``backend_options``: Options passed to AerSimulator.
|
|
55
|
+
Default: {}.
|
|
56
|
+
|
|
57
|
+
* ``run_options``: Options passed to :meth:`AerSimulator.run`.
|
|
58
|
+
Default: {}.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
*,
|
|
64
|
+
options: dict | None = None,
|
|
65
|
+
):
|
|
66
|
+
"""
|
|
67
|
+
Args:
|
|
68
|
+
options: The options to control the default precision (``default_precision``),
|
|
69
|
+
the backend options (``backend_options``), and
|
|
70
|
+
the runtime options (``run_options``).
|
|
71
|
+
"""
|
|
72
|
+
self._options = Options(**options) if options else Options()
|
|
73
|
+
self._backend = AerSimulator(**self.options.backend_options)
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def from_backend(cls, backend, **options):
|
|
77
|
+
"""make new sampler that uses external backend"""
|
|
78
|
+
estimator = cls(**options)
|
|
79
|
+
if isinstance(backend, AerSimulator):
|
|
80
|
+
estimator._backend = backend
|
|
81
|
+
else:
|
|
82
|
+
estimator._backend = AerSimulator.from_backend(backend)
|
|
83
|
+
return estimator
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def options(self) -> Options:
|
|
87
|
+
"""Return the options"""
|
|
88
|
+
return self._options
|
|
89
|
+
|
|
90
|
+
def run(
|
|
91
|
+
self, pubs: Iterable[EstimatorPubLike], *, precision: float | None = None
|
|
92
|
+
) -> PrimitiveJob[PrimitiveResult[PubResult]]:
|
|
93
|
+
if precision is None:
|
|
94
|
+
precision = self._options.default_precision
|
|
95
|
+
coerced_pubs = [EstimatorPub.coerce(pub, precision) for pub in pubs]
|
|
96
|
+
self._validate_pubs(coerced_pubs)
|
|
97
|
+
job = PrimitiveJob(self._run, coerced_pubs)
|
|
98
|
+
job._submit()
|
|
99
|
+
return job
|
|
100
|
+
|
|
101
|
+
def _validate_pubs(self, pubs: list[EstimatorPub]):
|
|
102
|
+
for i, pub in enumerate(pubs):
|
|
103
|
+
if pub.precision < 0.0:
|
|
104
|
+
raise ValueError(
|
|
105
|
+
f"The {i}-th pub has precision less than 0 ({pub.precision}). ",
|
|
106
|
+
"But precision should be equal to or larger than 0.",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def _run(self, pubs: list[EstimatorPub]) -> PrimitiveResult[PubResult]:
|
|
110
|
+
return PrimitiveResult([self._run_pub(pub) for pub in pubs], metadata={"version": 2})
|
|
111
|
+
|
|
112
|
+
def _run_pub(self, pub: EstimatorPub) -> PubResult:
|
|
113
|
+
circuit = pub.circuit.copy()
|
|
114
|
+
observables = pub.observables
|
|
115
|
+
parameter_values = pub.parameter_values
|
|
116
|
+
precision = pub.precision
|
|
117
|
+
|
|
118
|
+
# calculate broadcasting of parameters and observables
|
|
119
|
+
param_shape = parameter_values.shape
|
|
120
|
+
param_indices = np.fromiter(np.ndindex(param_shape), dtype=object).reshape(param_shape)
|
|
121
|
+
bc_param_ind, bc_obs = np.broadcast_arrays(param_indices, observables)
|
|
122
|
+
|
|
123
|
+
parameter_binds = {}
|
|
124
|
+
param_array = parameter_values.as_array(circuit.parameters)
|
|
125
|
+
parameter_binds = {p: param_array[..., i].ravel() for i, p in enumerate(circuit.parameters)}
|
|
126
|
+
|
|
127
|
+
# save expval
|
|
128
|
+
paulis = {pauli for obs_dict in observables.ravel() for pauli in obs_dict.keys()}
|
|
129
|
+
for pauli in paulis:
|
|
130
|
+
circuit.save_expectation_value(
|
|
131
|
+
Pauli(pauli), qubits=range(circuit.num_qubits), label=pauli
|
|
132
|
+
)
|
|
133
|
+
result = self._backend.run(
|
|
134
|
+
circuit, parameter_binds=[parameter_binds], **self.options.run_options
|
|
135
|
+
).result()
|
|
136
|
+
|
|
137
|
+
# calculate expectation values (evs) and standard errors (stds)
|
|
138
|
+
flat_indices = list(param_indices.ravel())
|
|
139
|
+
evs = np.zeros_like(bc_param_ind, dtype=float)
|
|
140
|
+
stds = np.full(bc_param_ind.shape, precision)
|
|
141
|
+
for index in np.ndindex(*bc_param_ind.shape):
|
|
142
|
+
param_index = bc_param_ind[index]
|
|
143
|
+
flat_index = flat_indices.index(param_index)
|
|
144
|
+
for pauli, coeff in bc_obs[index].items():
|
|
145
|
+
expval = result.data(flat_index)[pauli]
|
|
146
|
+
evs[index] += expval * coeff
|
|
147
|
+
if precision > 0:
|
|
148
|
+
rng = np.random.default_rng(self.options.run_options.get("seed_simulator"))
|
|
149
|
+
if not np.all(np.isreal(evs)):
|
|
150
|
+
raise ValueError("Given operator is not Hermitian and noise cannot be added.")
|
|
151
|
+
evs = rng.normal(evs, precision, evs.shape)
|
|
152
|
+
return PubResult(
|
|
153
|
+
DataBin(evs=evs, stds=stds, shape=evs.shape),
|
|
154
|
+
metadata={
|
|
155
|
+
"target_precision": precision,
|
|
156
|
+
"circuit_metadata": pub.circuit.metadata,
|
|
157
|
+
"simulator_metadata": result.metadata,
|
|
158
|
+
},
|
|
159
|
+
)
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
# This code is part of Qiskit.
|
|
2
|
+
#
|
|
3
|
+
# (C) Copyright IBM 2022.
|
|
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
|
+
Sampler class.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from collections.abc import Sequence, Iterable
|
|
20
|
+
|
|
21
|
+
from warnings import warn
|
|
22
|
+
import numpy as np
|
|
23
|
+
from qiskit.circuit import ParameterExpression, QuantumCircuit, Qubit
|
|
24
|
+
from qiskit.circuit.library.data_preparation import Initialize
|
|
25
|
+
from qiskit.compiler import transpile
|
|
26
|
+
from qiskit.exceptions import QiskitError
|
|
27
|
+
from qiskit.primitives import BaseSamplerV1, SamplerResult
|
|
28
|
+
from qiskit.quantum_info import Statevector
|
|
29
|
+
from qiskit.result import QuasiDistribution
|
|
30
|
+
|
|
31
|
+
from .. import AerSimulator
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def init_circuit(state: QuantumCircuit | Statevector) -> QuantumCircuit:
|
|
35
|
+
"""Initialize state by converting the input to a quantum circuit.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
state: The state as quantum circuit or statevector.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
The state as quantum circuit.
|
|
42
|
+
"""
|
|
43
|
+
if isinstance(state, QuantumCircuit):
|
|
44
|
+
return state
|
|
45
|
+
if not isinstance(state, Statevector):
|
|
46
|
+
state = Statevector(state)
|
|
47
|
+
qc = QuantumCircuit(state.num_qubits)
|
|
48
|
+
qc.append(Initialize(state), qargs=range(state.num_qubits))
|
|
49
|
+
return qc
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def final_measurement_mapping(circuit: QuantumCircuit) -> dict[int, int]:
|
|
53
|
+
"""Return the final measurement mapping for the circuit.
|
|
54
|
+
|
|
55
|
+
Dict keys label measured qubits, whereas the values indicate the
|
|
56
|
+
classical bit onto which that qubits measurement result is stored.
|
|
57
|
+
|
|
58
|
+
Parameters:
|
|
59
|
+
circuit: Input quantum circuit.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Mapping of qubits to classical bits for final measurements.
|
|
63
|
+
"""
|
|
64
|
+
active_qubits = list(range(circuit.num_qubits))
|
|
65
|
+
active_cbits = list(range(circuit.num_clbits))
|
|
66
|
+
|
|
67
|
+
# Find final measurements starting in back
|
|
68
|
+
mapping = {}
|
|
69
|
+
for item in circuit._data[::-1]:
|
|
70
|
+
if item.operation.name == "measure":
|
|
71
|
+
cbit = circuit.find_bit(item.clbits[0]).index
|
|
72
|
+
qbit = circuit.find_bit(item.qubits[0]).index
|
|
73
|
+
if cbit in active_cbits and qbit in active_qubits:
|
|
74
|
+
mapping[qbit] = cbit
|
|
75
|
+
active_cbits.remove(cbit)
|
|
76
|
+
active_qubits.remove(qbit)
|
|
77
|
+
elif item.operation.name not in ["barrier", "delay"]:
|
|
78
|
+
for qq in item.qubits:
|
|
79
|
+
_temp_qubit = circuit.find_bit(qq).index
|
|
80
|
+
if _temp_qubit in active_qubits:
|
|
81
|
+
active_qubits.remove(_temp_qubit)
|
|
82
|
+
|
|
83
|
+
if not active_cbits or not active_qubits:
|
|
84
|
+
break
|
|
85
|
+
|
|
86
|
+
# Sort so that classical bits are in numeric order low->high.
|
|
87
|
+
mapping = dict(sorted(mapping.items(), key=lambda item: item[1]))
|
|
88
|
+
return mapping
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _bits_key(bits: tuple[Qubit, ...], circuit: QuantumCircuit) -> tuple:
|
|
92
|
+
return tuple(
|
|
93
|
+
(
|
|
94
|
+
circuit.find_bit(bit).index,
|
|
95
|
+
tuple((reg[0].size, reg[0].name, reg[1]) for reg in circuit.find_bit(bit).registers),
|
|
96
|
+
)
|
|
97
|
+
for bit in bits
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _format_params(param):
|
|
102
|
+
if isinstance(param, np.ndarray):
|
|
103
|
+
return param.data.tobytes()
|
|
104
|
+
elif isinstance(param, QuantumCircuit):
|
|
105
|
+
return _circuit_key(param)
|
|
106
|
+
elif isinstance(param, Iterable):
|
|
107
|
+
return tuple(param)
|
|
108
|
+
return param
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _circuit_key(circuit: QuantumCircuit, functional: bool = True) -> tuple:
|
|
112
|
+
"""Private key function for QuantumCircuit.
|
|
113
|
+
|
|
114
|
+
This is the workaround until :meth:`QuantumCircuit.__hash__` will be introduced.
|
|
115
|
+
If key collision is found, please add elements to avoid it.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
circuit: Input quantum circuit.
|
|
119
|
+
functional: If True, the returned key only includes functional data (i.e. execution related).
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Composite key for circuit.
|
|
123
|
+
"""
|
|
124
|
+
functional_key: tuple = (
|
|
125
|
+
circuit.num_qubits,
|
|
126
|
+
circuit.num_clbits,
|
|
127
|
+
circuit.num_parameters,
|
|
128
|
+
tuple( # circuit.data
|
|
129
|
+
(
|
|
130
|
+
_bits_key(data.qubits, circuit), # qubits
|
|
131
|
+
_bits_key(data.clbits, circuit), # clbits
|
|
132
|
+
data.operation.name, # operation.name
|
|
133
|
+
tuple(_format_params(param) for param in data.operation.params), # operation.params
|
|
134
|
+
)
|
|
135
|
+
for data in circuit.data
|
|
136
|
+
),
|
|
137
|
+
None if circuit._op_start_times is None else tuple(circuit._op_start_times),
|
|
138
|
+
)
|
|
139
|
+
if functional:
|
|
140
|
+
return functional_key
|
|
141
|
+
return (
|
|
142
|
+
circuit.name,
|
|
143
|
+
*functional_key,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class Sampler(BaseSamplerV1):
|
|
148
|
+
"""
|
|
149
|
+
Aer implementation of Sampler class.
|
|
150
|
+
|
|
151
|
+
:Run Options:
|
|
152
|
+
|
|
153
|
+
- **shots** (None or int) --
|
|
154
|
+
The number of shots. If None, it calculates the probabilities exactly.
|
|
155
|
+
Otherwise, it samples from multinomial distributions.
|
|
156
|
+
|
|
157
|
+
- **seed** (int) --
|
|
158
|
+
Set a fixed seed for ``seed_simulator``. If shots is None, this option is ignored.
|
|
159
|
+
|
|
160
|
+
.. note::
|
|
161
|
+
Precedence of seeding is as follows:
|
|
162
|
+
|
|
163
|
+
1. ``seed_simulator`` in runtime (i.e. in :meth:`__call__`)
|
|
164
|
+
2. ``seed`` in runtime (i.e. in :meth:`__call__`)
|
|
165
|
+
3. ``seed_simulator`` of ``backend_options``.
|
|
166
|
+
4. default.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
def __init__(
|
|
170
|
+
self,
|
|
171
|
+
*,
|
|
172
|
+
backend_options: dict | None = None,
|
|
173
|
+
transpile_options: dict | None = None,
|
|
174
|
+
run_options: dict | None = None,
|
|
175
|
+
skip_transpilation: bool = False,
|
|
176
|
+
):
|
|
177
|
+
"""
|
|
178
|
+
Args:
|
|
179
|
+
backend_options: Options passed to AerSimulator.
|
|
180
|
+
transpile_options: Options passed to transpile.
|
|
181
|
+
run_options: Options passed to run.
|
|
182
|
+
skip_transpilation: if True, transpilation is skipped.
|
|
183
|
+
"""
|
|
184
|
+
warn(
|
|
185
|
+
"Sampler has been deprecated as of Aer 0.15, please use SamplerV2 instead.",
|
|
186
|
+
DeprecationWarning,
|
|
187
|
+
stacklevel=3,
|
|
188
|
+
)
|
|
189
|
+
super().__init__(options=run_options)
|
|
190
|
+
# These two private attributes used to be created by super, but were deprecated in Qiskit
|
|
191
|
+
# 0.46. See https://github.com/Qiskit/qiskit/pull/11051
|
|
192
|
+
self._circuits = []
|
|
193
|
+
self._parameters = []
|
|
194
|
+
|
|
195
|
+
self._backend = AerSimulator()
|
|
196
|
+
backend_options = {} if backend_options is None else backend_options
|
|
197
|
+
self._backend.set_options(**backend_options)
|
|
198
|
+
self._transpile_options = {} if transpile_options is None else transpile_options
|
|
199
|
+
self._skip_transpilation = skip_transpilation
|
|
200
|
+
|
|
201
|
+
self._transpiled_circuits = {}
|
|
202
|
+
self._circuit_ids = {}
|
|
203
|
+
|
|
204
|
+
def _call(
|
|
205
|
+
self,
|
|
206
|
+
circuits: Sequence[int],
|
|
207
|
+
parameter_values: Sequence[Sequence[float]],
|
|
208
|
+
**run_options,
|
|
209
|
+
) -> SamplerResult:
|
|
210
|
+
seed = run_options.pop("seed", None)
|
|
211
|
+
if seed is not None:
|
|
212
|
+
run_options.setdefault("seed_simulator", seed)
|
|
213
|
+
|
|
214
|
+
is_shots_none = "shots" in run_options and run_options["shots"] is None
|
|
215
|
+
self._transpile(circuits, is_shots_none)
|
|
216
|
+
|
|
217
|
+
experiment_manager = _ExperimentManager()
|
|
218
|
+
for i, value in zip(circuits, parameter_values):
|
|
219
|
+
if len(value) != len(self._parameters[i]):
|
|
220
|
+
raise QiskitError(
|
|
221
|
+
f"The number of values ({len(value)}) does not match "
|
|
222
|
+
f"the number of parameters ({len(self._parameters[i])})."
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
experiment_manager.append(
|
|
226
|
+
key=i,
|
|
227
|
+
parameter_bind=dict(zip(self._parameters[i], value)),
|
|
228
|
+
experiment_circuit=self._transpiled_circuits[(i, is_shots_none)],
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
result = self._backend.run(
|
|
232
|
+
experiment_manager.experiment_circuits,
|
|
233
|
+
parameter_binds=experiment_manager.parameter_binds,
|
|
234
|
+
**run_options,
|
|
235
|
+
).result()
|
|
236
|
+
|
|
237
|
+
# Postprocessing
|
|
238
|
+
metadata = []
|
|
239
|
+
quasis = []
|
|
240
|
+
for i in experiment_manager.experiment_indices:
|
|
241
|
+
if is_shots_none:
|
|
242
|
+
probabilities = result.data(i)["probabilities"]
|
|
243
|
+
num_qubits = result.results[i].metadata["num_qubits"]
|
|
244
|
+
quasi_dist = QuasiDistribution(
|
|
245
|
+
{f"{k:0{num_qubits}b}": v for k, v in probabilities.items()}
|
|
246
|
+
)
|
|
247
|
+
quasis.append(quasi_dist)
|
|
248
|
+
metadata.append({"shots": None, "simulator_metadata": result.results[i].metadata})
|
|
249
|
+
else:
|
|
250
|
+
counts = result.get_counts(i)
|
|
251
|
+
shots = sum(counts.values())
|
|
252
|
+
quasis.append(
|
|
253
|
+
QuasiDistribution(
|
|
254
|
+
{k.replace(" ", ""): v / shots for k, v in counts.items()},
|
|
255
|
+
shots=shots,
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
metadata.append({"shots": shots, "simulator_metadata": result.results[i].metadata})
|
|
259
|
+
|
|
260
|
+
return SamplerResult(quasis, metadata)
|
|
261
|
+
|
|
262
|
+
def _run(
|
|
263
|
+
self,
|
|
264
|
+
circuits: Sequence[QuantumCircuit],
|
|
265
|
+
parameter_values: Sequence[Sequence[float]],
|
|
266
|
+
**run_options,
|
|
267
|
+
):
|
|
268
|
+
# pylint: disable=no-name-in-module, import-error, import-outside-toplevel, no-member
|
|
269
|
+
from typing import List
|
|
270
|
+
|
|
271
|
+
from qiskit.primitives.primitive_job import PrimitiveJob
|
|
272
|
+
|
|
273
|
+
circuit_indices: List[int] = []
|
|
274
|
+
for circuit in circuits:
|
|
275
|
+
index = self._circuit_ids.get(_circuit_key(circuit))
|
|
276
|
+
if index is not None:
|
|
277
|
+
circuit_indices.append(index)
|
|
278
|
+
else:
|
|
279
|
+
circuit_indices.append(len(self._circuits))
|
|
280
|
+
self._circuit_ids[_circuit_key(circuit)] = len(self._circuits)
|
|
281
|
+
self._circuits.append(circuit)
|
|
282
|
+
self._parameters.append(circuit.parameters)
|
|
283
|
+
job = PrimitiveJob(self._call, circuit_indices, parameter_values, **run_options)
|
|
284
|
+
# The public submit method was removed in Qiskit 0.46
|
|
285
|
+
(job.submit if hasattr(job, "submit") else job._submit)() # pylint: disable=no-member
|
|
286
|
+
return job
|
|
287
|
+
|
|
288
|
+
@staticmethod
|
|
289
|
+
def _preprocess_circuit(circuit: QuantumCircuit):
|
|
290
|
+
circuit = init_circuit(circuit)
|
|
291
|
+
q_c_mapping = final_measurement_mapping(circuit)
|
|
292
|
+
if set(range(circuit.num_clbits)) != set(q_c_mapping.values()):
|
|
293
|
+
raise QiskitError(
|
|
294
|
+
"Some classical bits are not used for measurements. "
|
|
295
|
+
f"The number of classical bits {circuit.num_clbits}, "
|
|
296
|
+
f"the used classical bits {set(q_c_mapping.values())}."
|
|
297
|
+
)
|
|
298
|
+
c_q_mapping = sorted((c, q) for q, c in q_c_mapping.items())
|
|
299
|
+
qargs = [q for _, q in c_q_mapping]
|
|
300
|
+
circuit = circuit.remove_final_measurements(inplace=False)
|
|
301
|
+
circuit.save_probabilities_dict(qargs)
|
|
302
|
+
return circuit
|
|
303
|
+
|
|
304
|
+
def _transpile_circuit(self, circuit):
|
|
305
|
+
self._backend.set_max_qubits(circuit.num_qubits)
|
|
306
|
+
transpiled = transpile(
|
|
307
|
+
circuit,
|
|
308
|
+
self._backend,
|
|
309
|
+
**self._transpile_options,
|
|
310
|
+
)
|
|
311
|
+
return transpiled
|
|
312
|
+
|
|
313
|
+
def _transpile(self, circuit_indices: Sequence[int], is_shots_none: bool):
|
|
314
|
+
to_handle = [
|
|
315
|
+
i for i in set(circuit_indices) if (i, is_shots_none) not in self._transpiled_circuits
|
|
316
|
+
]
|
|
317
|
+
if to_handle:
|
|
318
|
+
circuits = (self._circuits[i] for i in to_handle)
|
|
319
|
+
if is_shots_none:
|
|
320
|
+
circuits = (self._preprocess_circuit(circ) for circ in circuits)
|
|
321
|
+
if not self._skip_transpilation:
|
|
322
|
+
circuits = (self._transpile_circuit(circ) for circ in circuits)
|
|
323
|
+
for i, circuit in zip(to_handle, circuits):
|
|
324
|
+
self._transpiled_circuits[(i, is_shots_none)] = circuit
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class _ExperimentManager:
|
|
328
|
+
def __init__(self):
|
|
329
|
+
self.keys: list[int] = []
|
|
330
|
+
self.experiment_circuits: list[QuantumCircuit] = []
|
|
331
|
+
self.parameter_binds: list[dict[ParameterExpression, list[float]]] = []
|
|
332
|
+
self._input_indices: list[list[int]] = []
|
|
333
|
+
self._num_experiment: int = 0
|
|
334
|
+
|
|
335
|
+
def __len__(self):
|
|
336
|
+
return self._num_experiment
|
|
337
|
+
|
|
338
|
+
@property
|
|
339
|
+
def experiment_indices(self):
|
|
340
|
+
"""indices of experiments"""
|
|
341
|
+
return np.argsort(sum(self._input_indices, [])).tolist()
|
|
342
|
+
|
|
343
|
+
def append(
|
|
344
|
+
self,
|
|
345
|
+
key: tuple[int, int],
|
|
346
|
+
parameter_bind: dict[ParameterExpression, float],
|
|
347
|
+
experiment_circuit: QuantumCircuit,
|
|
348
|
+
):
|
|
349
|
+
"""append experiments"""
|
|
350
|
+
if parameter_bind and key in self.keys:
|
|
351
|
+
key_index = self.keys.index(key)
|
|
352
|
+
for k, vs in self.parameter_binds[key_index].items():
|
|
353
|
+
vs.append(parameter_bind[k])
|
|
354
|
+
self._input_indices[key_index].append(self._num_experiment)
|
|
355
|
+
else:
|
|
356
|
+
self.experiment_circuits.append(experiment_circuit)
|
|
357
|
+
self.keys.append(key)
|
|
358
|
+
self.parameter_binds.append({k: [v] for k, v in parameter_bind.items()})
|
|
359
|
+
self._input_indices.append([self._num_experiment])
|
|
360
|
+
|
|
361
|
+
self._num_experiment += 1
|