Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade EstimatorResult dataclass #8105

Closed
wants to merge 9 commits into from
5 changes: 4 additions & 1 deletion qiskit/primitives/estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
32 changes: 23 additions & 9 deletions qiskit/primitives/estimator_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
126 changes: 74 additions & 52 deletions test/python/primitives/test_estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -49,25 +60,25 @@ 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"""
observable = PauliSumOp.from_list([("XX", 1), ("YY", 2), ("ZZ", 3)])
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"""
Expand All @@ -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"""
Expand All @@ -93,32 +104,34 @@ 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"""
with Estimator([self.ansatz], [self.observable]) as est:
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"""
Expand All @@ -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"""
Expand All @@ -150,35 +163,40 @@ def test_estimator_example(self):

# calculate [ <psi1(theta1)|op1|psi1(theta1)> ]
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 [ <psi1(theta1)|op2|psi1(theta1)>, <psi1(theta1)|op3|psi1(theta1)> ]
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 [ <psi2(theta2)|op2|psi2(theta2)> ]
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 [ <psi1(theta1)|op1|psi1(theta1)>, <psi1(theta3)|op1|psi1(theta3)> ]
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 [ <psi1(theta1)|op1|psi1(theta1)>,
# <psi2(theta2)|op2|psi2(theta2)>,
# <psi1(theta3)|op3|psi1(theta3)> ]
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)

Expand All @@ -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)"""
Expand All @@ -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"""
Expand Down Expand Up @@ -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):
Expand All @@ -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__":
Expand Down