diff --git a/qiskit/primitives/estimator.py b/qiskit/primitives/estimator.py index 6e9a08c86edc..37719a9a2522 100644 --- a/qiskit/primitives/estimator.py +++ b/qiskit/primitives/estimator.py @@ -109,7 +109,10 @@ def __call__( ) expectation_values.append(Statevector(circ).expectation_value(obs)) - return EstimatorResult(np.real_if_close(expectation_values), [{}] * len(expectation_values)) + expectation_values = np.real_if_close(expectation_values).tolist() + variances = [0.0] * len(expectation_values) + metadata = [{}] * len(expectation_values) + return EstimatorResult(tuple(expectation_values), tuple(variances), tuple(metadata)) def close(self): self._is_closed = True diff --git a/qiskit/primitives/estimator_result.py b/qiskit/primitives/estimator_result.py index e26d9848c676..3457ab277ad1 100644 --- a/qiskit/primitives/estimator_result.py +++ b/qiskit/primitives/estimator_result.py @@ -17,9 +17,9 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any +from warnings import warn -if TYPE_CHECKING: - import numpy as np +from numpy import array, ndarray @dataclass(frozen=True) @@ -31,14 +31,28 @@ class EstimatorResult: result = estimator(circuits, observables, params) where the i-th elements of ``result`` correspond to the circuit and observable given by - ``circuit_indices[i]``, ``observable_indices[i]``, and the parameter values bounds by ``params[i]``. - For example, ``results.values[i]`` gives the expectation value, and ``result.metadata[i]`` - is a metadata dictionary for this circuit and parameters. + ``circuit_indices[i]``, ``observable_indices[i]``, and the parameter values bounds by + ``params[i]``. For example, ``results.expectation_values[i]`` gives the expectation value, + ``results.variances[i]`` the variance, and ``result.metadata[i]`` is a metadata dictionary for + this circuit and parameters. Args: - values (np.ndarray): The array of the expectation values. - metadata (list[dict]): List of the metadata. + expectation_values (tuple[float, ...]): Tuple of expectation values. + variances (tuple[float, ...]): Tuple of variances associated to each expectation value. + metadata (tuple[dict, ...]): Tuple of metadata. """ - values: "np.ndarray[Any, np.dtype[np.float64]]" - metadata: list[dict[str, Any]] + expectation_values: tuple[float, ...] + variances: tuple[float, ...] + metadata: tuple[dict[str, Any], ...] + + @property + def values(self) -> ndarray: + warn( + "The EstimatorResult.values field is deprecated as of 0.21.0, and will be" + "removed no earlier than 3 months after that release date. You should use the" + "EstimatorResult.expectation_values field instead.", + DeprecationWarning, + stacklevel=2, + ) + return array(self.expectation_values) diff --git a/test/python/primitives/test_estimator.py b/test/python/primitives/test_estimator.py index 0e3b0bf2bdaf..395debd4ed02 100644 --- a/test/python/primitives/test_estimator.py +++ b/test/python/primitives/test_estimator.py @@ -41,6 +41,17 @@ def setUp(self): ] ) + def assertEstimatorResultType(self, result): + self.assertIsInstance(result, EstimatorResult) + self.assertIsInstance(result.expectation_values, tuple) + self.assertTrue(all(isinstance(ev, float) for ev in result.expectation_values)) + self.assertIsInstance(result.variances, tuple) + self.assertTrue(all(isinstance(var, float) for var in result.variances)) + self.assertIsInstance(result.metadata, tuple) + self.assertTrue(all(isinstance(md, dict) for md in result.metadata)) + with self.assertWarns(DeprecationWarning): + self.assertIsInstance(result.values, np.ndarray) + def test_estimator(self): """test for a simple use case""" lst = [("XX", 1), ("YY", 2), ("ZZ", 3)] @@ -49,16 +60,16 @@ def test_estimator(self): ansatz = RealAmplitudes(num_qubits=2, reps=2) with Estimator([ansatz], [observable]) as est: result = est(parameter_values=[0, 1, 1, 2, 3, 5]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [1.84209213]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [1.84209213]) with self.subTest("SparsePauliOp"): observable = SparsePauliOp.from_list(lst) ansatz = RealAmplitudes(num_qubits=2, reps=2) with Estimator([ansatz], [observable]) as est: result = est(parameter_values=[0, 1, 1, 2, 3, 5]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [1.84209213]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [1.84209213]) def test_estimator_param_reverse(self): """test for the reverse parameter""" @@ -66,8 +77,8 @@ def test_estimator_param_reverse(self): ansatz = RealAmplitudes(num_qubits=2, reps=2) with Estimator([ansatz], [observable], [ansatz.parameters[::-1]]) as est: result = est(parameter_values=[0, 1, 1, 2, 3, 5][::-1]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [1.84209213]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [1.84209213]) def test_init_from_statevector(self): """test initialization from statevector""" @@ -77,8 +88,8 @@ def test_init_from_statevector(self): self.assertIsInstance(est.circuits[0], QuantumCircuit) np.testing.assert_allclose(est.circuits[0][0][0].params, vector) result = est() - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [-0.88272215]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [-0.88272215]) def test_init_observable_from_operator(self): """test for evaluate without parameters""" @@ -93,15 +104,15 @@ def test_init_observable_from_operator(self): ) with Estimator([circuit], [matrix]) as est: result = est() - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [-1.284366511861733]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [-1.284366511861733]) def test_evaluate(self): """test for evaluate""" with Estimator([self.ansatz], [self.observable]) as est: result = est(parameter_values=[0, 1, 1, 2, 3, 5]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [-1.284366511861733]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [-1.284366511861733]) def test_evaluate_multi_params(self): """test for evaluate with multiple parameters""" @@ -109,16 +120,18 @@ def test_evaluate_multi_params(self): result = est( [0] * 2, [0] * 2, parameter_values=[[0, 1, 1, 2, 3, 5], [1, 1, 2, 3, 5, 8]] ) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [-1.284366511861733, -1.3187526349078742]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose( + result.expectation_values, [-1.284366511861733, -1.3187526349078742] + ) def test_evaluate_no_params(self): """test for evaluate without parameters""" circuit = self.ansatz.bind_parameters([0, 1, 1, 2, 3, 5]) with Estimator([circuit], [self.observable]) as est: result = est() - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [-1.284366511861733]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [-1.284366511861733]) def test_run_with_multiple_observables_and_none_parameters(self): """test for evaluate without parameters""" @@ -128,8 +141,8 @@ def test_run_with_multiple_observables_and_none_parameters(self): circuit.cx(1, 2) with Estimator(circuit, ["ZZZ", "III"]) as est: result = est(circuit_indices=[0, 0], observable_indices=[0, 1]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [0.0, 1.0]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [0.0, 1.0]) def test_estimator_example(self): """test for Estimator example""" @@ -150,35 +163,40 @@ def test_estimator_example(self): # calculate [ ] result = est([0], [0], [theta1]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [1.5555572817900956]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [1.5555572817900956]) self.assertEqual(len(result.metadata), 1) # calculate [ , ] result = est([0, 0], [1, 2], [theta1] * 2) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [-0.5516530027638437, 0.07535238795415422]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose( + result.expectation_values, [-0.5516530027638437, 0.07535238795415422] + ) self.assertEqual(len(result.metadata), 2) # calculate [ ] result = est([1], [1], [theta2]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [0.17849238433885167]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [0.17849238433885167]) self.assertEqual(len(result.metadata), 1) # calculate [ , ] result = est([0, 0], [0, 0], [theta1, theta3]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [1.5555572817900956, 1.0656325933346835]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose( + result.expectation_values, [1.5555572817900956, 1.0656325933346835] + ) self.assertEqual(len(result.metadata), 2) # calculate [ , # , # ] result = est([0, 1, 0], [0, 1, 2], [theta1, theta2, theta3]) - self.assertIsInstance(result, EstimatorResult) + self.assertEstimatorResultType(result) np.testing.assert_allclose( - result.values, [1.5555572817900956, 0.17849238433885167, -1.0876631752254926] + result.expectation_values, + [1.5555572817900956, 0.17849238433885167, -1.0876631752254926], ) self.assertEqual(len(result.metadata), 3) @@ -193,20 +211,20 @@ def test_1qubit(self): with Estimator([qc, qc2], [op, op2], [[]] * 2) as est: result = est([0], [0], [[]]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [1]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [1]) result = est([0], [1], [[]]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [1]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [1]) result = est([1], [0], [[]]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [1]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [1]) result = est([1], [1], [[]]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [-1]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [-1]) def test_2qubits(self): """Test for 2-qubit cases (to check endian)""" @@ -220,28 +238,28 @@ def test_2qubits(self): with Estimator([qc, qc2], [op, op2, op3], [[]] * 2) as est: result = est([0], [0], [[]]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [1]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [1]) result = est([1], [0], [[]]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [1]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [1]) result = est([0], [1], [[]]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [1]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [1]) result = est([1], [1], [[]]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [1]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [1]) result = est([0], [2], [[]]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [1]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [1]) result = est([1], [2], [[]]) - self.assertIsInstance(result, EstimatorResult) - np.testing.assert_allclose(result.values, [-1]) + self.assertEstimatorResultType(result) + np.testing.assert_allclose(result.expectation_values, [-1]) def test_errors(self): """Test for errors""" @@ -273,12 +291,14 @@ def test_empty_parameter(self): with Estimator(circuits=[qc] * 10, observables=[op] * 10) as estimator: with self.subTest("one circuit"): result = estimator([0], [1], shots=1000) - np.testing.assert_allclose(result.values, [1]) + np.testing.assert_allclose(result.expectation_values, [1]) + self.assertEqual(len(result.variances), 1) self.assertEqual(len(result.metadata), 1) with self.subTest("two circuits"): result = estimator([2, 4], [3, 5], shots=1000) - np.testing.assert_allclose(result.values, [1, 1]) + np.testing.assert_allclose(result.expectation_values, [1, 1]) + self.assertEqual(len(result.variances), 2) self.assertEqual(len(result.metadata), 2) def test_numpy_params(self): @@ -295,12 +315,14 @@ def test_numpy_params(self): with self.subTest("ndarrary"): result = estimator([0] * k, [0] * k, params_array) self.assertEqual(len(result.metadata), k) - np.testing.assert_allclose(result.values, target.values) + self.assertEqual(len(result.variances), k) + np.testing.assert_allclose(result.expectation_values, target.expectation_values) with self.subTest("list of ndarray"): result = estimator([0] * k, [0] * k, params_list_array) self.assertEqual(len(result.metadata), k) - np.testing.assert_allclose(result.values, target.values) + self.assertEqual(len(result.variances), k) + np.testing.assert_allclose(result.expectation_values, target.expectation_values) if __name__ == "__main__":