Skip to content

Commit

Permalink
Update metadata of Primitives V2 (#12784) (#12857)
Browse files Browse the repository at this point in the history
* 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 <57907331+ElePT@users.noreply.github.com>
(cherry picked from commit 68687d3)

Co-authored-by: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com>
  • Loading branch information
mergify[bot] and t-imamichi authored Jul 30, 2024
1 parent 927d592 commit e28aec4
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 13 deletions.
12 changes: 9 additions & 3 deletions qiskit/primitives/backend_estimator_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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``."""
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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,
Expand Down
15 changes: 12 additions & 3 deletions qiskit/primitives/backend_sampler_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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``."""
Expand Down Expand Up @@ -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
Expand All @@ -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 = {
Expand All @@ -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]:
Expand Down
6 changes: 4 additions & 2 deletions qiskit/primitives/statevector_estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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}
)
7 changes: 5 additions & 2 deletions qiskit/primitives/statevector_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
24 changes: 24 additions & 0 deletions test/python/primitives/test_backend_estimator_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
15 changes: 15 additions & 0 deletions test/python/primitives/test_backend_sampler_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
23 changes: 20 additions & 3 deletions test/python/primitives/test_statevector_estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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]
Expand All @@ -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"""
Expand Down Expand Up @@ -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()
14 changes: 14 additions & 0 deletions test/python/primitives/test_statevector_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,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()

0 comments on commit e28aec4

Please sign in to comment.