diff --git a/qiskit/opflow/expectations/aer_pauli_expectation.py b/qiskit/opflow/expectations/aer_pauli_expectation.py index 5b3accff7ac..fdcb635d5f5 100644 --- a/qiskit/opflow/expectations/aer_pauli_expectation.py +++ b/qiskit/opflow/expectations/aer_pauli_expectation.py @@ -52,7 +52,6 @@ def convert(self, operator: OperatorBase) -> OperatorBase: else: return operator - # pylint: disable=inconsistent-return-statements @classmethod def _replace_pauli_sums(cls, operator): try: @@ -67,6 +66,8 @@ def _replace_pauli_sums(cls, operator): # CircuitSampler will look for it to know that the circuit is a Expectation # measurement, and not simply a # circuit to replace with a DictStateFn + if operator.__class__ == ListOp: + return operator.traverse(cls._replace_pauli_sums) if isinstance(operator, PauliSumOp): paulis = [(meas[1], meas[0]) for meas in operator.primitive.to_list()] @@ -74,7 +75,7 @@ def _replace_pauli_sums(cls, operator): return CircuitStateFn(snapshot_instruction, coeff=operator.coeff, is_measurement=True) # Change to Pauli representation if necessary - if not {"Pauli"} == operator.primitive_strings(): + if {"Pauli"} != operator.primitive_strings(): logger.warning( "Measured Observable is not composed of only Paulis, converting to " "Pauli representation, which can be expensive." @@ -93,8 +94,10 @@ def _replace_pauli_sums(cls, operator): snapshot_instruction = SnapshotExpectationValue("expval_measurement", paulis) snapshot_op = CircuitStateFn(snapshot_instruction, is_measurement=True) return snapshot_op - if isinstance(operator, ListOp): - return operator.traverse(cls._replace_pauli_sums) + + raise TypeError( + f"Conversion of OperatorStateFn of {operator.__class__.__name__} is not defined." + ) def compute_variance(self, exp_op: OperatorBase) -> Union[list, float]: r""" @@ -116,5 +119,6 @@ def sum_variance(operator): return 0.0 elif isinstance(operator, ListOp): return operator._combo_fn([sum_variance(op) for op in operator.oplist]) + raise TypeError(f"Variance cannot be computed for {operator.__class__.__name__}.") return sum_variance(exp_op) diff --git a/qiskit/opflow/expectations/pauli_expectation.py b/qiskit/opflow/expectations/pauli_expectation.py index 545c0044c8e..207687ed564 100644 --- a/qiskit/opflow/expectations/pauli_expectation.py +++ b/qiskit/opflow/expectations/pauli_expectation.py @@ -61,12 +61,15 @@ def convert(self, operator: OperatorBase) -> OperatorBase: Returns: The converted operator. """ + if isinstance(operator, ListOp): + return operator.traverse(self.convert).reduce() + if isinstance(operator, OperatorStateFn) and operator.is_measurement: # Change to Pauli representation if necessary if ( isinstance(operator.primitive, (ListOp, PrimitiveOp)) and not isinstance(operator.primitive, PauliSumOp) - and {"Pauli"} != operator.primitive_strings() + and {"Pauli", "SparsePauliOp"} < operator.primitive_strings() ): logger.warning( "Measured Observable is not composed of only Paulis, converting to " @@ -86,9 +89,6 @@ def convert(self, operator: OperatorBase) -> OperatorBase: cob = PauliBasisChange(replacement_fn=PauliBasisChange.measurement_replacement_fn) return cob.convert(operator).reduce() - if isinstance(operator, ListOp): - return operator.traverse(self.convert).reduce() - return operator def compute_variance(self, exp_op: OperatorBase) -> Union[list, float, np.ndarray]: diff --git a/releasenotes/notes/bugfix-pauli-expectation-warning-bdba193ace0f012e.yaml b/releasenotes/notes/bugfix-pauli-expectation-warning-bdba193ace0f012e.yaml new file mode 100644 index 00000000000..2fe175bb866 --- /dev/null +++ b/releasenotes/notes/bugfix-pauli-expectation-warning-bdba193ace0f012e.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixes the problem that when users convert a ListOp of PauliSumOp using + PauliExpectation and AerPauliExpectation, it will convert it to ListOp + of SummedOp of PauliOp and users will get a warning. + The new version will convert it as PauliSumOp, that is more efficient. diff --git a/test/python/opflow/test_aer_pauli_expectation.py b/test/python/opflow/test_aer_pauli_expectation.py index dcd236d5391..05ef1b64a6b 100644 --- a/test/python/opflow/test_aer_pauli_expectation.py +++ b/test/python/opflow/test_aer_pauli_expectation.py @@ -17,27 +17,28 @@ from test.python.opflow import QiskitOpflowTestCase import numpy as np +from qiskit.circuit.library import RealAmplitudes from qiskit.utils import QuantumInstance from qiskit.opflow import ( - X, - Y, - Z, - I, CX, + AerPauliExpectation, + CircuitSampler, + CircuitStateFn, H, - S, + I, ListOp, - Zero, + Minus, One, + PauliExpectation, + PauliSumOp, Plus, - Minus, + S, StateFn, - AerPauliExpectation, - CircuitSampler, - CircuitStateFn, - PauliExpectation, + X, + Y, + Z, + Zero, ) -from qiskit.circuit.library import RealAmplitudes class TestAerPauliExpectation(QiskitOpflowTestCase): @@ -243,6 +244,14 @@ def test_pauli_expectation_param_qobj(self): np.testing.assert_array_almost_equal(val1, val3, decimal=2) np.testing.assert_array_almost_equal([val1] * 2, val4, decimal=2) + def test_list_pauli_sum(self): + """Test AerPauliExpectation for ListOp[PauliSumOp]""" + test_op = ListOp([PauliSumOp.from_list([("XX", 1), ("ZI", 3), ("ZZ", 5)])]) + observable = AerPauliExpectation().convert(~StateFn(test_op)) + self.assertIsInstance(observable, ListOp) + self.assertIsInstance(observable[0], CircuitStateFn) + self.assertTrue(observable[0].is_measurement) + def test_pauli_expectation_non_hermite_op(self): """Test PauliExpectation for non hermitian operator""" exp = ~StateFn(1j * Z) @ One diff --git a/test/python/opflow/test_pauli_expectation.py b/test/python/opflow/test_pauli_expectation.py index 42e6ed4f484..39af9223d6f 100644 --- a/test/python/opflow/test_pauli_expectation.py +++ b/test/python/opflow/test_pauli_expectation.py @@ -12,32 +12,32 @@ """ Test PauliExpectation """ +import itertools import unittest from test.python.opflow import QiskitOpflowTestCase -import itertools + import numpy as np -from qiskit.utils import QuantumInstance -from qiskit.utils import algorithm_globals +from qiskit import BasicAer from qiskit.opflow import ( - X, - Y, - Z, - I, CX, + CircuitSampler, H, - S, + I, ListOp, - Zero, + Minus, One, + PauliExpectation, + PauliSumOp, Plus, - Minus, + S, StateFn, - PauliExpectation, - CircuitSampler, + X, + Y, + Z, + Zero, ) - -from qiskit import BasicAer +from qiskit.utils import QuantumInstance, algorithm_globals # pylint: disable=invalid-name @@ -246,6 +246,14 @@ def test_pauli_expectation_non_hermite_op(self): exp = ~StateFn(1j * Z) @ One self.assertEqual(self.expect.convert(exp).eval(), 1j) + def test_list_pauli_sum_op(self): + """Test PauliExpectation for List[PauliSumOp]""" + test_op = ListOp([~StateFn(PauliSumOp.from_list([("XX", 1), ("ZI", 3), ("ZZ", 5)]))]) + observable = self.expect.convert(test_op) + self.assertIsInstance(observable, ListOp) + self.assertIsInstance(observable[0][0][0].primitive, PauliSumOp) + self.assertIsInstance(observable[0][1][0].primitive, PauliSumOp) + if __name__ == "__main__": unittest.main()