Skip to content

Commit e28aec4

Browse files
Update metadata of Primitives V2 (#12784) (#12857)
* update metadata of primitives v2 * remove `shots` and add reno * add version * add shots to SamplerV2 metadata * udpate reno --------- Co-authored-by: Elena Peña Tapia <[email protected]> (cherry picked from commit 68687d3) Co-authored-by: Takashi Imamichi <[email protected]>
1 parent 927d592 commit e28aec4

9 files changed

+116
-13
lines changed

qiskit/primitives/backend_estimator_v2.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def _run(self, pubs: list[EstimatorPub]) -> PrimitiveResult[PubResult]:
190190
# reconstruct the result of pubs
191191
for i, pub_result in zip(lst, pub_results):
192192
results[i] = pub_result
193-
return PrimitiveResult(results)
193+
return PrimitiveResult(results, metadata={"version": 2})
194194

195195
def _run_pubs(self, pubs: list[EstimatorPub], shots: int) -> list[PubResult]:
196196
"""Compute results for pubs that all require the same value of ``shots``."""
@@ -238,7 +238,6 @@ def _preprocess_pub(self, pub: EstimatorPub) -> _PreprocessedData:
238238
param_indices = np.fromiter(np.ndindex(param_shape), dtype=object).reshape(param_shape)
239239
bc_param_ind, bc_obs = np.broadcast_arrays(param_indices, observables)
240240

241-
# calculate expectation values for each pair of parameter value set and pauli
242241
param_obs_map = defaultdict(set)
243242
for index in np.ndindex(*bc_param_ind.shape):
244243
param_index = bc_param_ind[index]
@@ -275,7 +274,14 @@ def _postprocess_pub(
275274
variances[index] += np.abs(coeff) * variance**0.5
276275
stds = variances / np.sqrt(shots)
277276
data_bin = DataBin(evs=evs, stds=stds, shape=evs.shape)
278-
return PubResult(data_bin, metadata={"target_precision": pub.precision})
277+
return PubResult(
278+
data_bin,
279+
metadata={
280+
"target_precision": pub.precision,
281+
"shots": shots,
282+
"circuit_metadata": pub.circuit.metadata,
283+
},
284+
)
279285

280286
def _bind_and_add_measurements(
281287
self,

qiskit/primitives/backend_sampler_v2.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def _run(self, pubs: list[SamplerPub]) -> PrimitiveResult[SamplerPubResult]:
155155
# reconstruct the result of pubs
156156
for i, pub_result in zip(lst, pub_results):
157157
results[i] = pub_result
158-
return PrimitiveResult(results)
158+
return PrimitiveResult(results, metadata={"version": 2})
159159

160160
def _run_pubs(self, pubs: list[SamplerPub], shots: int) -> list[SamplerPubResult]:
161161
"""Compute results for pubs that all require the same value of ``shots``."""
@@ -183,7 +183,12 @@ def _run_pubs(self, pubs: list[SamplerPub], shots: int) -> list[SamplerPubResult
183183
end = start + bound.size
184184
results.append(
185185
self._postprocess_pub(
186-
result_memory[start:end], shots, bound.shape, meas_info, max_num_bytes
186+
result_memory[start:end],
187+
shots,
188+
bound.shape,
189+
meas_info,
190+
max_num_bytes,
191+
pub.circuit.metadata,
187192
)
188193
)
189194
start = end
@@ -197,6 +202,7 @@ def _postprocess_pub(
197202
shape: tuple[int, ...],
198203
meas_info: list[_MeasureInfo],
199204
max_num_bytes: int,
205+
circuit_metadata: dict,
200206
) -> SamplerPubResult:
201207
"""Converts the memory data into an array of bit arrays with the shape of the pub."""
202208
arrays = {
@@ -213,7 +219,10 @@ def _postprocess_pub(
213219
meas = {
214220
item.creg_name: BitArray(arrays[item.creg_name], item.num_bits) for item in meas_info
215221
}
216-
return SamplerPubResult(DataBin(**meas, shape=shape), metadata={})
222+
return SamplerPubResult(
223+
DataBin(**meas, shape=shape),
224+
metadata={"shots": shots, "circuit_metadata": circuit_metadata},
225+
)
217226

218227

219228
def _analyze_circuit(circuit: QuantumCircuit) -> tuple[list[_MeasureInfo], int]:

qiskit/primitives/statevector_estimator.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def run(
136136
return job
137137

138138
def _run(self, pubs: list[EstimatorPub]) -> PrimitiveResult[PubResult]:
139-
return PrimitiveResult([self._run_pub(pub) for pub in pubs])
139+
return PrimitiveResult([self._run_pub(pub) for pub in pubs], metadata={"version": 2})
140140

141141
def _run_pub(self, pub: EstimatorPub) -> PubResult:
142142
rng = np.random.default_rng(self._seed)
@@ -162,4 +162,6 @@ def _run_pub(self, pub: EstimatorPub) -> PubResult:
162162
evs[index] = expectation_value
163163

164164
data = DataBin(evs=evs, stds=stds, shape=evs.shape)
165-
return PubResult(data, metadata={"precision": precision})
165+
return PubResult(
166+
data, metadata={"target_precision": precision, "circuit_metadata": pub.circuit.metadata}
167+
)

qiskit/primitives/statevector_sampler.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ def run(
171171

172172
def _run(self, pubs: Iterable[SamplerPub]) -> PrimitiveResult[SamplerPubResult]:
173173
results = [self._run_pub(pub) for pub in pubs]
174-
return PrimitiveResult(results)
174+
return PrimitiveResult(results, metadata={"version": 2})
175175

176176
def _run_pub(self, pub: SamplerPub) -> SamplerPubResult:
177177
circuit, qargs, meas_info = _preprocess_circuit(pub.circuit)
@@ -197,7 +197,10 @@ def _run_pub(self, pub: SamplerPub) -> SamplerPubResult:
197197
meas = {
198198
item.creg_name: BitArray(arrays[item.creg_name], item.num_bits) for item in meas_info
199199
}
200-
return SamplerPubResult(DataBin(**meas, shape=pub.shape), metadata={"shots": pub.shots})
200+
return SamplerPubResult(
201+
DataBin(**meas, shape=pub.shape),
202+
metadata={"shots": pub.shots, "circuit_metadata": pub.circuit.metadata},
203+
)
201204

202205

203206
def _preprocess_circuit(circuit: QuantumCircuit):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
features_primitives:
3+
- |
4+
The metadata of Primitives V2 implementations, i.e., :class:`.StatevectorSampler`,
5+
:class:`.StatevectorEstimator`, :class:`.BackendSamplerV2` and :class:`.BackendEstimatorV2`,
6+
has been updated to match that of IBM quantum devices.
7+
8+
* ``version`` and ``circuit_metadata`` are added for all V2 implementations
9+
* ``shots`` is added for :class:`.BackendSamplerV2` and :class:`.BackendEstimatorV2`
10+
* ``precision`` is renamed with ``target_precision`` for :class:`.StatevectorEstimator`
11+
12+
Note that metadata of :class:`.StatevectorEstimator` does not have ``shots`` because
13+
the class computes expectation values with :class:`.Statevector` and shots are not used.

test/python/primitives/test_backend_estimator_v2.py

+24
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,30 @@ def test_iter_pub(self):
474474
np.testing.assert_allclose(result[0].data.evs, [-1.284366511861733], rtol=self._rtol)
475475
np.testing.assert_allclose(result[1].data.evs, [-1.284366511861733], rtol=self._rtol)
476476

477+
def test_metadata(self):
478+
"""Test for metadata"""
479+
qc = QuantumCircuit(2)
480+
qc2 = QuantumCircuit(2)
481+
qc2.metadata = {"a": 1}
482+
backend = BasicSimulator()
483+
estimator = BackendEstimatorV2(backend=backend)
484+
pm = generate_preset_pass_manager(optimization_level=0, backend=backend)
485+
qc, qc2 = pm.run([qc, qc2])
486+
op = SparsePauliOp("ZZ").apply_layout(qc.layout)
487+
op2 = SparsePauliOp("ZZ").apply_layout(qc2.layout)
488+
result = estimator.run([(qc, op), (qc2, op2)], precision=0.1).result()
489+
490+
self.assertEqual(len(result), 2)
491+
self.assertEqual(result.metadata, {"version": 2})
492+
self.assertEqual(
493+
result[0].metadata,
494+
{"target_precision": 0.1, "shots": 100, "circuit_metadata": qc.metadata},
495+
)
496+
self.assertEqual(
497+
result[1].metadata,
498+
{"target_precision": 0.1, "shots": 100, "circuit_metadata": qc2.metadata},
499+
)
500+
477501

478502
if __name__ == "__main__":
479503
unittest.main()

test/python/primitives/test_backend_sampler_v2.py

+15
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,21 @@ def test_iter_pub(self):
748748
self._assert_allclose(result[0].data.meas, np.array({0: self._shots}))
749749
self._assert_allclose(result[1].data.meas, np.array({1: self._shots}))
750750

751+
def test_metadata(self):
752+
"""Test for metadata"""
753+
qc = QuantumCircuit(2)
754+
qc.measure_all()
755+
qc2 = qc.copy()
756+
qc2.metadata = {"a": 1}
757+
backend = BasicSimulator()
758+
sampler = BackendSamplerV2(backend=backend)
759+
result = sampler.run([(qc, None, 10), (qc2, None, 20)]).result()
760+
761+
self.assertEqual(len(result), 2)
762+
self.assertEqual(result.metadata, {"version": 2})
763+
self.assertEqual(result[0].metadata, {"shots": 10, "circuit_metadata": qc.metadata})
764+
self.assertEqual(result[1].metadata, {"shots": 20, "circuit_metadata": qc2.metadata})
765+
751766

752767
if __name__ == "__main__":
753768
unittest.main()

test/python/primitives/test_statevector_estimator.py

+20-3
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def test_run_single_circuit_observable(self):
129129
self.subTest(f"{val}")
130130
result = est.run([(qc, op, val)]).result()
131131
np.testing.assert_allclose(result[0].data.evs, target)
132-
self.assertEqual(result[0].metadata["precision"], 0)
132+
self.assertEqual(result[0].metadata["target_precision"], 0)
133133

134134
with self.subTest("One parameter"):
135135
param = Parameter("x")
@@ -145,7 +145,7 @@ def test_run_single_circuit_observable(self):
145145
self.subTest(f"{val}")
146146
result = est.run([(qc, op, val)]).result()
147147
np.testing.assert_allclose(result[0].data.evs, target)
148-
self.assertEqual(result[0].metadata["precision"], 0)
148+
self.assertEqual(result[0].metadata["target_precision"], 0)
149149

150150
with self.subTest("More than one parameter"):
151151
qc = self.psi[0]
@@ -162,7 +162,7 @@ def test_run_single_circuit_observable(self):
162162
self.subTest(f"{val}")
163163
result = est.run([(qc, op, val)]).result()
164164
np.testing.assert_allclose(result[0].data.evs, target)
165-
self.assertEqual(result[0].metadata["precision"], 0)
165+
self.assertEqual(result[0].metadata["target_precision"], 0)
166166

167167
def test_run_1qubit(self):
168168
"""Test for 1-qubit cases"""
@@ -290,6 +290,23 @@ def test_iter_pub(self):
290290
np.testing.assert_allclose(result[0].data.evs, [-1.284366511861733])
291291
np.testing.assert_allclose(result[1].data.evs, [-1.284366511861733])
292292

293+
def test_metadata(self):
294+
"""Test for metadata"""
295+
qc = QuantumCircuit(2)
296+
qc2 = QuantumCircuit(2)
297+
qc2.metadata = {"a": 1}
298+
estimator = StatevectorEstimator()
299+
result = estimator.run([(qc, "ZZ"), (qc2, "ZZ")], precision=0.1).result()
300+
301+
self.assertEqual(len(result), 2)
302+
self.assertEqual(result.metadata, {"version": 2})
303+
self.assertEqual(
304+
result[0].metadata, {"target_precision": 0.1, "circuit_metadata": qc.metadata}
305+
)
306+
self.assertEqual(
307+
result[1].metadata, {"target_precision": 0.1, "circuit_metadata": qc2.metadata}
308+
)
309+
293310

294311
if __name__ == "__main__":
295312
unittest.main()

test/python/primitives/test_statevector_sampler.py

+14
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,20 @@ def test_iter_pub(self):
648648
self._assert_allclose(result[0].data.meas, np.array({0: self._shots}))
649649
self._assert_allclose(result[1].data.meas, np.array({1: self._shots}))
650650

651+
def test_metadata(self):
652+
"""Test for metadata"""
653+
qc = QuantumCircuit(2)
654+
qc.measure_all()
655+
qc2 = qc.copy()
656+
qc2.metadata = {"a": 1}
657+
sampler = StatevectorSampler()
658+
result = sampler.run([(qc, None, 10), (qc2, None, 20)]).result()
659+
660+
self.assertEqual(len(result), 2)
661+
self.assertEqual(result.metadata, {"version": 2})
662+
self.assertEqual(result[0].metadata, {"shots": 10, "circuit_metadata": qc.metadata})
663+
self.assertEqual(result[1].metadata, {"shots": 20, "circuit_metadata": qc2.metadata})
664+
651665

652666
if __name__ == "__main__":
653667
unittest.main()

0 commit comments

Comments
 (0)