Skip to content

Commit a0761a8

Browse files
committed
Add meas_level, meas_return, and noise_model options to BackendSamaplerV2
This change adds support to the `BackendSamplerV2` class so that it will pass through the `meas_level`, `meas_return`, and `noise_model` options passed to it through to the underlying `BackendV2`'s `run()` method. For the sake of compatibility with backends that might not expect those options, it does not pass default values for them (like the previously defined options for the class) if the default `None` value is not overridden. Additionally, to support `meas_level=1`, the results processing code checks the `meas_level` option and handles `meas_level=1` data appropriately, rather than always assuming the returned data is level 2.
1 parent a9b8f4a commit a0761a8

File tree

1 file changed

+54
-15
lines changed

1 file changed

+54
-15
lines changed

qiskit/primitives/backend_sampler_v2.py

+54-15
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
import warnings
1818
from collections import defaultdict
1919
from dataclasses import dataclass
20-
from typing import Iterable
20+
from typing import Any, Iterable
2121

2222
import numpy as np
2323
from numpy.typing import NDArray
2424

2525
from qiskit.circuit import QuantumCircuit
26+
from qiskit.exceptions import QiskitError
2627
from qiskit.primitives.backend_estimator import _run_circuits
2728
from qiskit.primitives.base import BaseSamplerV2
2829
from qiskit.primitives.containers import (
@@ -53,6 +54,21 @@ class Options:
5354
Default: None.
5455
"""
5556

57+
noise_model: Any | None = None
58+
"""A ``NoiseModel`` to pass to pass through a simulator backend (like ones from qiskit-aer)
59+
Default: None (option not passed to backend's ``run`` method)
60+
"""
61+
62+
meas_level: int | None = None
63+
"""Measurement level for the backend to return.
64+
Default: None (option not passed to backend's ``run`` method)
65+
"""
66+
67+
meas_return: str | None = None
68+
"""Measurement return format for the backend to return.
69+
Default: None (option not passed to backend's ``run`` method)
70+
"""
71+
5672

5773
@dataclass
5874
class _MeasureInfo:
@@ -165,13 +181,21 @@ def _run_pubs(self, pubs: list[SamplerPub], shots: int) -> list[SamplerPubResult
165181
for circuits in bound_circuits:
166182
flatten_circuits.extend(np.ravel(circuits).tolist())
167183

184+
# Put options in dict to unpacked below so that unset options are left
185+
# out rather than being passed as None
186+
run_opts = {
187+
k: getattr(self._options, k)
188+
for k in ("noise_model", "meas_return", "meas_level")
189+
if getattr(self._options, k) is not None
190+
}
168191
# run circuits
169192
results, _ = _run_circuits(
170193
flatten_circuits,
171194
self._backend,
172195
memory=True,
173196
shots=shots,
174197
seed_simulator=self._options.seed_simulator,
198+
**run_opts,
175199
)
176200
result_memory = _prepare_memory(results)
177201

@@ -189,6 +213,7 @@ def _run_pubs(self, pubs: list[SamplerPub], shots: int) -> list[SamplerPubResult
189213
meas_info,
190214
max_num_bytes,
191215
pub.circuit.metadata,
216+
meas_level=self._options.meas_level,
192217
)
193218
)
194219
start = end
@@ -203,22 +228,36 @@ def _postprocess_pub(
203228
meas_info: list[_MeasureInfo],
204229
max_num_bytes: int,
205230
circuit_metadata: dict,
231+
meas_level: int | None = None,
206232
) -> SamplerPubResult:
207-
"""Converts the memory data into an array of bit arrays with the shape of the pub."""
208-
arrays = {
209-
item.creg_name: np.zeros(shape + (shots, item.num_bytes), dtype=np.uint8)
210-
for item in meas_info
211-
}
212-
memory_array = _memory_array(result_memory, max_num_bytes)
213-
214-
for samples, index in zip(memory_array, np.ndindex(*shape)):
215-
for item in meas_info:
216-
ary = _samples_to_packed_array(samples, item.num_bits, item.start)
217-
arrays[item.creg_name][index] = ary
233+
"""Converts the memory data into a sampler pub result
218234
219-
meas = {
220-
item.creg_name: BitArray(arrays[item.creg_name], item.num_bits) for item in meas_info
221-
}
235+
For level 2 data, the memory data are stored in an array of bit arrays
236+
with the shape of the pub. For level 1 data, the data are stored in a
237+
complex numpy array.
238+
"""
239+
if meas_level == 2 or meas_level is None:
240+
arrays = {
241+
item.creg_name: np.zeros(shape + (shots, item.num_bytes), dtype=np.uint8)
242+
for item in meas_info
243+
}
244+
memory_array = _memory_array(result_memory, max_num_bytes)
245+
246+
for samples, index in zip(memory_array, np.ndindex(*shape)):
247+
for item in meas_info:
248+
ary = _samples_to_packed_array(samples, item.num_bits, item.start)
249+
arrays[item.creg_name][index] = ary
250+
251+
meas = {
252+
item.creg_name: BitArray(arrays[item.creg_name], item.num_bits) for item in meas_info
253+
}
254+
elif meas_level == 1:
255+
raw = np.array(result_memory)
256+
cplx = raw[..., 0] + 1j * raw[..., 1]
257+
cplx = np.reshape(cplx, (*shape, *cplx.shape[1:]))
258+
meas = {item.creg_name: cplx for item in meas_info}
259+
else:
260+
raise QiskitError(f"Unsupported meas_level: {meas_level}")
222261
return SamplerPubResult(
223262
DataBin(**meas, shape=shape),
224263
metadata={"shots": shots, "circuit_metadata": circuit_metadata},

0 commit comments

Comments
 (0)