Repository: Qiskit/qiskit

Issue:
``CVaRMeasurement`` does not check ``PauliSumOp``s efficiently for diagonality
### Environment

- **Qiskit Terra version**: main @ fe5acd4d1
- **Python version**: 3.7
- **Operating system**: macOS


### What is happening?

The CVaR measurement is only defined for diagonal observables and the code checks that. It tries to do that efficiently (meaning w/o a conversion to a matrix) but that didn't cover the case for `PauliSumOp`s. Luckily, there's a check in place that raises an error if a large operator is converted to a matrix so this expensive calculation doesn't happen silently.

This issue was seen with the Qiskit QAOA Runtime since there CVaR expectation makes sense to use _and_ we have large qubit numbers.

### How can we reproduce the issue?

```python
from qiskit.opflow import PauliSumOp, CVaRMeasurement

op = PauliSumOp.from_list([("Z" * 30, 1)])
_ = CVaRMeasurement(op)
```

### What should happen?

`PauliSumOp`s should be checked efficiently.

### Any suggestions?

See upcoming PR


Discussion:

Resolving patch:
diff --git a/qiskit/opflow/state_fns/cvar_measurement.py b/qiskit/opflow/state_fns/cvar_measurement.py
--- a/qiskit/opflow/state_fns/cvar_measurement.py
+++ b/qiskit/opflow/state_fns/cvar_measurement.py
@@ -21,7 +21,7 @@
 from qiskit.opflow.exceptions import OpflowError
 from qiskit.opflow.list_ops import ListOp, SummedOp, TensoredOp
 from qiskit.opflow.operator_base import OperatorBase
-from qiskit.opflow.primitive_ops import PauliOp
+from qiskit.opflow.primitive_ops import PauliOp, PauliSumOp
 from qiskit.opflow.state_fns.circuit_state_fn import CircuitStateFn
 from qiskit.opflow.state_fns.dict_state_fn import DictStateFn
 from qiskit.opflow.state_fns.operator_state_fn import OperatorStateFn
@@ -360,24 +360,22 @@ def _check_is_diagonal(operator: OperatorBase) -> bool:
     """
     if isinstance(operator, PauliOp):
         # every X component must be False
-        if not np.any(operator.primitive.x):
-            return True
-        return False
+        return not np.any(operator.primitive.x)
 
-    if isinstance(operator, SummedOp):
-        # cover the case of sums of diagonal paulis, but don't raise since there might be summands
-        # canceling the non-diagonal parts
+    # For sums (PauliSumOp and SummedOp), we cover the case of sums of diagonal paulis, but don't
+    # raise since there might be summand canceling the non-diagonal parts. That case is checked
+    # in the inefficient matrix check at the bottom.
+    if isinstance(operator, PauliSumOp):
+        if not np.any(operator.primitive.paulis.x):
+            return True
 
-        # ignoring mypy since we know that all operators are PauliOps
+    elif isinstance(operator, SummedOp):
         if all(isinstance(op, PauliOp) and not np.any(op.primitive.x) for op in operator.oplist):
             return True
 
-    if isinstance(operator, ListOp):
+    elif isinstance(operator, ListOp):
         return all(operator.traverse(_check_is_diagonal))
 
     # cannot efficiently check if a operator is diagonal, converting to matrix
     matrix = operator.to_matrix()
-
-    if np.all(matrix == np.diag(np.diagonal(matrix))):
-        return True
-    return False
+    return np.all(matrix == np.diag(np.diagonal(matrix)))


Test patch:
diff --git a/test/python/opflow/test_cvar.py b/test/python/opflow/test_cvar.py
--- a/test/python/opflow/test_cvar.py
+++ b/test/python/opflow/test_cvar.py
@@ -18,6 +18,7 @@
 from ddt import ddt, data
 
 from qiskit import QuantumCircuit
+from qiskit.utils import algorithm_globals
 from qiskit.opflow import (
     CVaRMeasurement,
     StateFn,
@@ -26,6 +27,7 @@
     X,
     Y,
     Plus,
+    PauliSumOp,
     PauliExpectation,
     MatrixExpectation,
     CVaRExpectation,
@@ -70,6 +72,10 @@ def expected_cvar(self, statevector, operator, alpha):
 
         return result / alpha
 
+    def cleanup_algorithm_globals(self, massive):
+        """Method used to reset the values of algorithm_globals."""
+        algorithm_globals.massive = massive
+
     def test_cvar_simple(self):
         """Test a simple case with a single Pauli."""
         theta = 1.2
@@ -161,6 +167,26 @@ def test_unsupported_operations(self):
             with self.assertRaises(OpflowError):
                 cvar.adjoint()
 
+    def test_cvar_on_paulisumop(self):
+        """Test a large PauliSumOp is checked for diagonality efficiently.
+
+        Regression test for Qiskit/qiskit-terra#7573.
+        """
+        op = PauliSumOp.from_list([("Z" * 30, 1)])
+        # assert global algorithm settings do not have massive calculations turned on
+        # -- which is the default, but better to be sure in the test!
+        # also add a cleanup so we're sure to reset to the original value after the test, even if
+        # the test would fail
+        self.addCleanup(self.cleanup_algorithm_globals, algorithm_globals.massive)
+        algorithm_globals.massive = False
+
+        cvar = CVaRMeasurement(op, alpha=0.1)
+        fake_probabilities = [0.2, 0.8]
+        fake_energies = [1, 2]
+
+        expectation = cvar.compute_cvar(fake_energies, fake_probabilities)
+        self.assertEqual(expectation, 1)
+
 
 @ddt
 class TestCVaRExpectation(QiskitOpflowTestCase):