Skip to content

Commit

Permalink
Fix 6159 (Remove Unexpected Warning of PauliExpectation and AerPauliE…
Browse files Browse the repository at this point in the history
…xpectation) (#6281)

* fix #6159

* add release note

* black

* raise error for undefined cases

* revert unnecessary changes

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed May 9, 2021
1 parent 91007dc commit 77da2b5
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 34 deletions.
12 changes: 8 additions & 4 deletions qiskit/opflow/expectations/aer_pauli_expectation.py
Expand Up @@ -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:
Expand All @@ -67,14 +66,16 @@ 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()]
snapshot_instruction = SnapshotExpectationValue("expval_measurement", paulis)
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."
Expand All @@ -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"""
Expand All @@ -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)
8 changes: 4 additions & 4 deletions qiskit/opflow/expectations/pauli_expectation.py
Expand Up @@ -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 "
Expand All @@ -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]:
Expand Down
@@ -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.
33 changes: 21 additions & 12 deletions test/python/opflow/test_aer_pauli_expectation.py
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
36 changes: 22 additions & 14 deletions test/python/opflow/test_pauli_expectation.py
Expand Up @@ -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
Expand Down Expand Up @@ -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()

0 comments on commit 77da2b5

Please sign in to comment.