diff --git a/qiskit/primitives/backend_estimator_v2.py b/qiskit/primitives/backend_estimator_v2.py index 777375a30e4c..47352dd3de22 100644 --- a/qiskit/primitives/backend_estimator_v2.py +++ b/qiskit/primitives/backend_estimator_v2.py @@ -190,7 +190,7 @@ def _run(self, pubs: list[EstimatorPub]) -> PrimitiveResult[PubResult]: # reconstruct the result of pubs for i, pub_result in zip(lst, pub_results): results[i] = pub_result - return PrimitiveResult(results) + return PrimitiveResult(results, metadata={"version": 2}) def _run_pubs(self, pubs: list[EstimatorPub], shots: int) -> list[PubResult]: """Compute results for pubs that all require the same value of ``shots``.""" @@ -238,7 +238,6 @@ def _preprocess_pub(self, pub: EstimatorPub) -> _PreprocessedData: param_indices = np.fromiter(np.ndindex(param_shape), dtype=object).reshape(param_shape) bc_param_ind, bc_obs = np.broadcast_arrays(param_indices, observables) - # calculate expectation values for each pair of parameter value set and pauli param_obs_map = defaultdict(set) for index in np.ndindex(*bc_param_ind.shape): param_index = bc_param_ind[index] @@ -275,7 +274,14 @@ def _postprocess_pub( variances[index] += np.abs(coeff) * variance**0.5 stds = variances / np.sqrt(shots) data_bin = DataBin(evs=evs, stds=stds, shape=evs.shape) - return PubResult(data_bin, metadata={"target_precision": pub.precision}) + return PubResult( + data_bin, + metadata={ + "target_precision": pub.precision, + "shots": shots, + "circuit_metadata": pub.circuit.metadata, + }, + ) def _bind_and_add_measurements( self, diff --git a/qiskit/primitives/backend_sampler_v2.py b/qiskit/primitives/backend_sampler_v2.py index ff7a32580fe3..bac0bec5eaed 100644 --- a/qiskit/primitives/backend_sampler_v2.py +++ b/qiskit/primitives/backend_sampler_v2.py @@ -155,7 +155,7 @@ def _run(self, pubs: list[SamplerPub]) -> PrimitiveResult[SamplerPubResult]: # reconstruct the result of pubs for i, pub_result in zip(lst, pub_results): results[i] = pub_result - return PrimitiveResult(results) + return PrimitiveResult(results, metadata={"version": 2}) def _run_pubs(self, pubs: list[SamplerPub], shots: int) -> list[SamplerPubResult]: """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 end = start + bound.size results.append( self._postprocess_pub( - result_memory[start:end], shots, bound.shape, meas_info, max_num_bytes + result_memory[start:end], + shots, + bound.shape, + meas_info, + max_num_bytes, + pub.circuit.metadata, ) ) start = end @@ -197,6 +202,7 @@ def _postprocess_pub( shape: tuple[int, ...], meas_info: list[_MeasureInfo], max_num_bytes: int, + circuit_metadata: dict, ) -> SamplerPubResult: """Converts the memory data into an array of bit arrays with the shape of the pub.""" arrays = { @@ -213,7 +219,10 @@ def _postprocess_pub( meas = { item.creg_name: BitArray(arrays[item.creg_name], item.num_bits) for item in meas_info } - return SamplerPubResult(DataBin(**meas, shape=shape), metadata={}) + return SamplerPubResult( + DataBin(**meas, shape=shape), + metadata={"shots": shots, "circuit_metadata": circuit_metadata}, + ) def _analyze_circuit(circuit: QuantumCircuit) -> tuple[list[_MeasureInfo], int]: diff --git a/qiskit/primitives/statevector_estimator.py b/qiskit/primitives/statevector_estimator.py index 722291bcf42f..c57f2c5b77d5 100644 --- a/qiskit/primitives/statevector_estimator.py +++ b/qiskit/primitives/statevector_estimator.py @@ -136,7 +136,7 @@ def run( return job def _run(self, pubs: list[EstimatorPub]) -> PrimitiveResult[PubResult]: - return PrimitiveResult([self._run_pub(pub) for pub in pubs]) + return PrimitiveResult([self._run_pub(pub) for pub in pubs], metadata={"version": 2}) def _run_pub(self, pub: EstimatorPub) -> PubResult: rng = np.random.default_rng(self._seed) @@ -162,4 +162,6 @@ def _run_pub(self, pub: EstimatorPub) -> PubResult: evs[index] = expectation_value data = DataBin(evs=evs, stds=stds, shape=evs.shape) - return PubResult(data, metadata={"precision": precision}) + return PubResult( + data, metadata={"target_precision": precision, "circuit_metadata": pub.circuit.metadata} + ) diff --git a/qiskit/primitives/statevector_sampler.py b/qiskit/primitives/statevector_sampler.py index 7488faa284de..79fc34b379e8 100644 --- a/qiskit/primitives/statevector_sampler.py +++ b/qiskit/primitives/statevector_sampler.py @@ -171,7 +171,7 @@ def run( def _run(self, pubs: Iterable[SamplerPub]) -> PrimitiveResult[SamplerPubResult]: results = [self._run_pub(pub) for pub in pubs] - return PrimitiveResult(results) + return PrimitiveResult(results, metadata={"version": 2}) def _run_pub(self, pub: SamplerPub) -> SamplerPubResult: circuit, qargs, meas_info = _preprocess_circuit(pub.circuit) @@ -197,7 +197,10 @@ def _run_pub(self, pub: SamplerPub) -> SamplerPubResult: meas = { item.creg_name: BitArray(arrays[item.creg_name], item.num_bits) for item in meas_info } - return SamplerPubResult(DataBin(**meas, shape=pub.shape), metadata={"shots": pub.shots}) + return SamplerPubResult( + DataBin(**meas, shape=pub.shape), + metadata={"shots": pub.shots, "circuit_metadata": pub.circuit.metadata}, + ) def _preprocess_circuit(circuit: QuantumCircuit): diff --git a/releasenotes/notes/update-primitive-v2-metadata-cf1226e2d6477688.yaml b/releasenotes/notes/update-primitive-v2-metadata-cf1226e2d6477688.yaml new file mode 100644 index 000000000000..570f5211b080 --- /dev/null +++ b/releasenotes/notes/update-primitive-v2-metadata-cf1226e2d6477688.yaml @@ -0,0 +1,13 @@ +--- +features_primitives: + - | + The metadata of Primitives V2 implementations, i.e., :class:`.StatevectorSampler`, + :class:`.StatevectorEstimator`, :class:`.BackendSamplerV2` and :class:`.BackendEstimatorV2`, + has been updated to match that of IBM quantum devices. + + * ``version`` and ``circuit_metadata`` are added for all V2 implementations + * ``shots`` is added for :class:`.BackendSamplerV2` and :class:`.BackendEstimatorV2` + * ``precision`` is renamed with ``target_precision`` for :class:`.StatevectorEstimator` + + Note that metadata of :class:`.StatevectorEstimator` does not have ``shots`` because + the class computes expectation values with :class:`.Statevector` and shots are not used. diff --git a/test/python/primitives/test_backend_estimator_v2.py b/test/python/primitives/test_backend_estimator_v2.py index 319fd846ee95..ad6b4c6abfdb 100644 --- a/test/python/primitives/test_backend_estimator_v2.py +++ b/test/python/primitives/test_backend_estimator_v2.py @@ -474,6 +474,30 @@ def test_iter_pub(self): np.testing.assert_allclose(result[0].data.evs, [-1.284366511861733], rtol=self._rtol) np.testing.assert_allclose(result[1].data.evs, [-1.284366511861733], rtol=self._rtol) + def test_metadata(self): + """Test for metadata""" + qc = QuantumCircuit(2) + qc2 = QuantumCircuit(2) + qc2.metadata = {"a": 1} + backend = BasicSimulator() + estimator = BackendEstimatorV2(backend=backend) + pm = generate_preset_pass_manager(optimization_level=0, backend=backend) + qc, qc2 = pm.run([qc, qc2]) + op = SparsePauliOp("ZZ").apply_layout(qc.layout) + op2 = SparsePauliOp("ZZ").apply_layout(qc2.layout) + result = estimator.run([(qc, op), (qc2, op2)], precision=0.1).result() + + self.assertEqual(len(result), 2) + self.assertEqual(result.metadata, {"version": 2}) + self.assertEqual( + result[0].metadata, + {"target_precision": 0.1, "shots": 100, "circuit_metadata": qc.metadata}, + ) + self.assertEqual( + result[1].metadata, + {"target_precision": 0.1, "shots": 100, "circuit_metadata": qc2.metadata}, + ) + if __name__ == "__main__": unittest.main() diff --git a/test/python/primitives/test_backend_sampler_v2.py b/test/python/primitives/test_backend_sampler_v2.py index 632ed1984d2d..6e899dca58f9 100644 --- a/test/python/primitives/test_backend_sampler_v2.py +++ b/test/python/primitives/test_backend_sampler_v2.py @@ -748,6 +748,21 @@ def test_iter_pub(self): self._assert_allclose(result[0].data.meas, np.array({0: self._shots})) self._assert_allclose(result[1].data.meas, np.array({1: self._shots})) + def test_metadata(self): + """Test for metadata""" + qc = QuantumCircuit(2) + qc.measure_all() + qc2 = qc.copy() + qc2.metadata = {"a": 1} + backend = BasicSimulator() + sampler = BackendSamplerV2(backend=backend) + result = sampler.run([(qc, None, 10), (qc2, None, 20)]).result() + + self.assertEqual(len(result), 2) + self.assertEqual(result.metadata, {"version": 2}) + self.assertEqual(result[0].metadata, {"shots": 10, "circuit_metadata": qc.metadata}) + self.assertEqual(result[1].metadata, {"shots": 20, "circuit_metadata": qc2.metadata}) + if __name__ == "__main__": unittest.main() diff --git a/test/python/primitives/test_statevector_estimator.py b/test/python/primitives/test_statevector_estimator.py index 1ed2d42e0e3b..4eaa70e07a07 100644 --- a/test/python/primitives/test_statevector_estimator.py +++ b/test/python/primitives/test_statevector_estimator.py @@ -129,7 +129,7 @@ def test_run_single_circuit_observable(self): self.subTest(f"{val}") result = est.run([(qc, op, val)]).result() np.testing.assert_allclose(result[0].data.evs, target) - self.assertEqual(result[0].metadata["precision"], 0) + self.assertEqual(result[0].metadata["target_precision"], 0) with self.subTest("One parameter"): param = Parameter("x") @@ -145,7 +145,7 @@ def test_run_single_circuit_observable(self): self.subTest(f"{val}") result = est.run([(qc, op, val)]).result() np.testing.assert_allclose(result[0].data.evs, target) - self.assertEqual(result[0].metadata["precision"], 0) + self.assertEqual(result[0].metadata["target_precision"], 0) with self.subTest("More than one parameter"): qc = self.psi[0] @@ -162,7 +162,7 @@ def test_run_single_circuit_observable(self): self.subTest(f"{val}") result = est.run([(qc, op, val)]).result() np.testing.assert_allclose(result[0].data.evs, target) - self.assertEqual(result[0].metadata["precision"], 0) + self.assertEqual(result[0].metadata["target_precision"], 0) def test_run_1qubit(self): """Test for 1-qubit cases""" @@ -290,6 +290,23 @@ def test_iter_pub(self): np.testing.assert_allclose(result[0].data.evs, [-1.284366511861733]) np.testing.assert_allclose(result[1].data.evs, [-1.284366511861733]) + def test_metadata(self): + """Test for metadata""" + qc = QuantumCircuit(2) + qc2 = QuantumCircuit(2) + qc2.metadata = {"a": 1} + estimator = StatevectorEstimator() + result = estimator.run([(qc, "ZZ"), (qc2, "ZZ")], precision=0.1).result() + + self.assertEqual(len(result), 2) + self.assertEqual(result.metadata, {"version": 2}) + self.assertEqual( + result[0].metadata, {"target_precision": 0.1, "circuit_metadata": qc.metadata} + ) + self.assertEqual( + result[1].metadata, {"target_precision": 0.1, "circuit_metadata": qc2.metadata} + ) + if __name__ == "__main__": unittest.main() diff --git a/test/python/primitives/test_statevector_sampler.py b/test/python/primitives/test_statevector_sampler.py index c065871025d7..493b7dc332e4 100644 --- a/test/python/primitives/test_statevector_sampler.py +++ b/test/python/primitives/test_statevector_sampler.py @@ -636,6 +636,20 @@ def test_iter_pub(self): self._assert_allclose(result[0].data.meas, np.array({0: self._shots})) self._assert_allclose(result[1].data.meas, np.array({1: self._shots})) + def test_metadata(self): + """Test for metadata""" + qc = QuantumCircuit(2) + qc.measure_all() + qc2 = qc.copy() + qc2.metadata = {"a": 1} + sampler = StatevectorSampler() + result = sampler.run([(qc, None, 10), (qc2, None, 20)]).result() + + self.assertEqual(len(result), 2) + self.assertEqual(result.metadata, {"version": 2}) + self.assertEqual(result[0].metadata, {"shots": 10, "circuit_metadata": qc.metadata}) + self.assertEqual(result[1].metadata, {"shots": 20, "circuit_metadata": qc2.metadata}) + if __name__ == "__main__": unittest.main()