Repository: Qiskit/qiskit

Issue:
Statevector and DensityMatrix expectation value ignores qargs for Pauli ops
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: 0.17.0
- **Python version**: 
- **Operating system**:

### What is the current behavior?

The `expectation_value` method of `Statevector` and `DensityMatrix` returns the incorrect value for a Pauli or SparsePauliOp operator if non-trivial qargs are used.

### Steps to reproduce the problem

Compare the expectation value for a `Pauli` op to `Pauli.to_matrix()` computed via the `Operator` method.

### What is the expected behavior?

The operator and Pauli methods should be equal for equivalent operators.

### Suggested solutions

This is because the qargs are not used in the internal pauli expval function


Discussion:

Resolving patch:
diff --git a/qiskit/quantum_info/states/densitymatrix.py b/qiskit/quantum_info/states/densitymatrix.py
--- a/qiskit/quantum_info/states/densitymatrix.py
+++ b/qiskit/quantum_info/states/densitymatrix.py
@@ -345,17 +345,21 @@ def reverse_qargs(self):
         ret._op_shape = self._op_shape.reverse()
         return ret
 
-    def _expectation_value_pauli(self, pauli):
+    def _expectation_value_pauli(self, pauli, qargs=None):
         """Compute the expectation value of a Pauli.
 
             Args:
                 pauli (Pauli): a Pauli operator to evaluate expval of.
+                qargs (None or list): subsystems to apply operator on.
 
             Returns:
                 complex: the expectation value.
             """
         n_pauli = len(pauli)
-        qubits = np.arange(n_pauli)
+        if qargs is None:
+            qubits = np.arange(n_pauli)
+        else:
+            qubits = np.array(qargs)
         x_mask = np.dot(1 << qubits, pauli.x)
         z_mask = np.dot(1 << qubits, pauli.z)
         pauli_phase = (-1j) ** pauli.phase if pauli.phase else 1
@@ -383,10 +387,10 @@ def expectation_value(self, oper, qargs=None):
             complex: the expectation value.
         """
         if isinstance(oper, Pauli):
-            return self._expectation_value_pauli(oper)
+            return self._expectation_value_pauli(oper, qargs)
 
         if isinstance(oper, SparsePauliOp):
-            return sum([coeff * self._expectation_value_pauli(Pauli((z, x)))
+            return sum([coeff * self._expectation_value_pauli(Pauli((z, x)), qargs)
                         for z, x, coeff in zip(oper.table.Z, oper.table.X, oper.coeffs)])
 
         if not isinstance(oper, Operator):
diff --git a/qiskit/quantum_info/states/statevector.py b/qiskit/quantum_info/states/statevector.py
--- a/qiskit/quantum_info/states/statevector.py
+++ b/qiskit/quantum_info/states/statevector.py
@@ -370,17 +370,22 @@ def reverse_qargs(self):
         ret._op_shape = self._op_shape.reverse()
         return ret
 
-    def _expectation_value_pauli(self, pauli):
+    def _expectation_value_pauli(self, pauli, qargs=None):
         """Compute the expectation value of a Pauli.
 
             Args:
                 pauli (Pauli): a Pauli operator to evaluate expval of.
+                qargs (None or list): subsystems to apply operator on.
 
             Returns:
                 complex: the expectation value.
-            """
+        """
         n_pauli = len(pauli)
-        qubits = np.arange(n_pauli)
+        if qargs is None:
+            qubits = np.arange(n_pauli)
+        else:
+            qubits = np.array(qargs)
+
         x_mask = np.dot(1 << qubits, pauli.x)
         z_mask = np.dot(1 << qubits, pauli.z)
         pauli_phase = (-1j) ** pauli.phase if pauli.phase else 1
@@ -408,10 +413,10 @@ def expectation_value(self, oper, qargs=None):
             complex: the expectation value.
         """
         if isinstance(oper, Pauli):
-            return self._expectation_value_pauli(oper)
+            return self._expectation_value_pauli(oper, qargs)
 
         if isinstance(oper, SparsePauliOp):
-            return sum([coeff * self._expectation_value_pauli(Pauli((z, x)))
+            return sum([coeff * self._expectation_value_pauli(Pauli((z, x)), qargs)
                         for z, x, coeff in zip(oper.table.Z, oper.table.X, oper.coeffs)])
 
         val = self.evolve(oper, qargs=qargs)


Test patch:
diff --git a/test/python/quantum_info/states/test_densitymatrix.py b/test/python/quantum_info/states/test_densitymatrix.py
--- a/test/python/quantum_info/states/test_densitymatrix.py
+++ b/test/python/quantum_info/states/test_densitymatrix.py
@@ -23,7 +23,8 @@
 from qiskit import QuantumRegister, QuantumCircuit
 from qiskit.circuit.library import HGate, QFT
 
-from qiskit.quantum_info.random import random_unitary, random_density_matrix
+from qiskit.quantum_info.random import (
+    random_unitary, random_density_matrix, random_pauli)
 from qiskit.quantum_info.states import DensityMatrix, Statevector
 from qiskit.quantum_info.operators.operator import Operator
 from qiskit.quantum_info.operators.symplectic import Pauli, SparsePauliOp
@@ -952,6 +953,16 @@ def test_expval_pauli_c_contiguous(self, pauli):
         expval = rho.expectation_value(op)
         self.assertAlmostEqual(expval, target)
 
+    @data([0, 1], [0, 2], [1, 0], [1, 2], [2, 0], [2, 1])
+    def test_expval_pauli_qargs(self, qubits):
+        """Test expectation_value method for Pauli op"""
+        seed = 1020
+        op = random_pauli(2, seed=seed)
+        state = random_density_matrix(2**3, seed=seed)
+        target = state.expectation_value(op.to_matrix(), qubits)
+        expval = state.expectation_value(op, qubits)
+        self.assertAlmostEqual(expval, target)
+
     def test_reverse_qargs(self):
         """Test reverse_qargs method"""
         circ1 = QFT(5)
diff --git a/test/python/quantum_info/states/test_statevector.py b/test/python/quantum_info/states/test_statevector.py
--- a/test/python/quantum_info/states/test_statevector.py
+++ b/test/python/quantum_info/states/test_statevector.py
@@ -25,7 +25,8 @@
 from qiskit import transpile
 from qiskit.circuit.library import HGate, QFT
 
-from qiskit.quantum_info.random import random_unitary, random_statevector
+from qiskit.quantum_info.random import (
+    random_unitary, random_statevector, random_pauli)
 from qiskit.quantum_info.states import Statevector
 from qiskit.quantum_info.operators.operator import Operator
 from qiskit.quantum_info.operators.symplectic import Pauli, SparsePauliOp
@@ -936,6 +937,16 @@ def test_expval_pauli(self, pauli):
         expval = state.expectation_value(op)
         self.assertAlmostEqual(expval, target)
 
+    @data([0, 1], [0, 2], [1, 0], [1, 2], [2, 0], [2, 1])
+    def test_expval_pauli_qargs(self, qubits):
+        """Test expectation_value method for Pauli op"""
+        seed = 1020
+        op = random_pauli(2, seed=seed)
+        state = random_statevector(2**3, seed=seed)
+        target = state.expectation_value(op.to_matrix(), qubits)
+        expval = state.expectation_value(op, qubits)
+        self.assertAlmostEqual(expval, target)
+
     def test_global_phase(self):
         """Test global phase is handled correctly when evolving statevector."""