Repository: Qiskit/qiskit

Issue:
`Stabilizer.expectation_value()` does not give the correct result
### Environment

- **Qiskit Terra version**: 0.19.1
- **Python version**: 3.9.5
- **Operating system**: Win 10


### What is happening?

I wrote a script for generating random Clifford circuit with respect to certain layout. Then I convert it to `StabilizerState` and compute the expectation value of its stabilizers with `StabilizerState.expectation_value()`, which should be one. The stabilizers are obtained with `StabilizerState.clifford.to_dict()`. But some expectation values are zero, instead of one. 

### How can we reproduce the issue?

The following code can be used to reproduce the issue.
```python
from qiskit.quantum_info import Pauli, random_clifford, StabilizerState
from qiskit import QuantumCircuit
from numpy.random import RandomState

n_qubits = 3
circ = QuantumCircuit(n_qubits)

compute_seed = RandomState(0).choice(10, size = (1, n_qubits))

for i in range(n_qubits): 
    gate = random_clifford(1, compute_seed[0, i])
    circ.compose(gate.to_circuit(), [i], inplace = True)

print(circ)
s = StabilizerState(circ)
stab_gen = s.clifford.to_dict()["stabilizer"]
print(stab_gen)

for i in range(n_qubits):
    print(s.expectation_value(Pauli(stab_gen[i]))) # should be 1
```
The printed result of last line is 
```python
1
1
0
```

### What should happen?

The values should all be one. Actually, if we run the following code
```python
stab_sim = Aer.get_backend("aer_simulator_stabilizer")
for i in range(3):
    circ.save_expectation_value(Pauli(stab_gen[i]), circ.qubits)
    result = execute(circ, stab_sim).result()
    print(result.data(circ))
    circ.data.pop(-1)
```
Then, the printed results are all `{'expectation_value': 1.0}`.

### Any suggestions?

_No response_


Discussion:
I think that the bug is when the Pauli operator has a negative sign, continuing the above example:

```
s = StabilizerState(circ)
vec = Statevector(circ)
print (s)
print (s.expectation_value(Pauli("-YII")))
print (vec.expectation_value(Pauli("-YII")))
```
gives:
```
StabilizerState(StabilizerTable: ['-IIY', '+IXI', '-YII'])
0
1
```

Resolving patch:
diff --git a/qiskit/quantum_info/states/stabilizerstate.py b/qiskit/quantum_info/states/stabilizerstate.py
--- a/qiskit/quantum_info/states/stabilizerstate.py
+++ b/qiskit/quantum_info/states/stabilizerstate.py
@@ -196,15 +196,21 @@ def evolve(self, other, qargs=None):
         return ret
 
     def expectation_value(self, oper, qargs=None):
-        """Compute the expectation value of an operator.
+        """Compute the expectation value of a Pauli operator.
 
         Args:
-            oper (BaseOperator): an operator to evaluate expval.
+            oper (Pauli): a Pauli operator to evaluate expval.
             qargs (None or list): subsystems to apply the operator on.
 
         Returns:
-            complex: the expectation value (only 0 or 1 or -1).
+            complex: the expectation value (only 0 or 1 or -1 or i or -i).
+
+        Raises:
+            QiskitError: if oper is not a Pauli operator.
         """
+        if not isinstance(oper, Pauli):
+            raise QiskitError("Operator for expectation value is not a Pauli operator.")
+
         num_qubits = self.clifford.num_qubits
         if qargs is None:
             qubits = range(num_qubits)
@@ -214,19 +220,12 @@ def expectation_value(self, oper, qargs=None):
         # Construct Pauli on num_qubits
         pauli = Pauli(num_qubits * "I")
         phase = 0
+        pauli_phase = (-1j) ** oper.phase if oper.phase else 1
 
         for pos, qubit in enumerate(qubits):
-            pauli_pos = (oper.to_label())[len(oper) - 1 - pos]
-            if pauli_pos == "X":
-                pauli.x[qubit] = 1
-            elif pauli_pos == "Y":
-                pauli.x[qubit] = 1
-                pauli.z[qubit] = 1
-                phase += 1
-            elif pauli_pos == "Z":
-                pauli.z[qubit] = 1
-            else:
-                pass
+            pauli.x[qubit] = oper.x[pos]
+            pauli.z[qubit] = oper.z[pos]
+            phase += pauli.x[qubit] & pauli.z[qubit]
 
         # Check if there is a stabilizer that anti-commutes with an odd number of qubits
         # If so the expectation value is 0
@@ -258,10 +257,11 @@ def expectation_value(self, oper, qargs=None):
             phase += 2 * np.count_nonzero(pauli_z & stab.X[p])
             pauli_z = pauli_z ^ stab.Z[p]
 
+        # For valid stabilizers, `phase` can only be 0 (= 1) or 2 (= -1) at this point.
         if phase % 4 != 0:
-            return -1
+            return -pauli_phase
 
-        return 1
+        return pauli_phase
 
     def probabilities(self, qargs=None, decimals=None):
         """Return the subsystem measurement probability vector.


Test patch:
diff --git a/test/python/quantum_info/states/test_stabilizerstate.py b/test/python/quantum_info/states/test_stabilizerstate.py
--- a/test/python/quantum_info/states/test_stabilizerstate.py
+++ b/test/python/quantum_info/states/test_stabilizerstate.py
@@ -16,7 +16,7 @@
 import unittest
 from test import combine
 import logging
-from ddt import ddt
+from ddt import ddt, data, unpack
 
 import numpy as np
 
@@ -525,228 +525,21 @@ def test_probs_random_subsystem(self, num_qubits):
                 self.assertTrue(np.allclose(probs, target))
                 self.assertDictAlmostEqual(probs_dict, target_dict)
 
-    def test_expval_single_qubit(self):
-        """Test expectation_value method of a single qubit"""
-
-        num_qubits = 1
-
-        qc = QuantumCircuit(num_qubits)
-        stab = StabilizerState(qc)
-        pairs = [("Z", 1), ("X", 0), ("Y", 0), ("I", 1)]
-        for label, target in pairs:
-            with self.subTest(msg=f"<{label}>"):
-                op = Pauli(label)
-                expval = stab.expectation_value(op)
-                self.assertEqual(expval, target)
-
-        qc = QuantumCircuit(num_qubits)
-        qc.x(0)
-        stab = StabilizerState(qc)
-        pairs = [("Z", -1), ("X", 0), ("Y", 0), ("I", 1)]
-        for label, target in pairs:
-            with self.subTest(msg=f"<{label}>"):
-                op = Pauli(label)
-                expval = stab.expectation_value(op)
-                self.assertEqual(expval, target)
-
-        qc = QuantumCircuit(num_qubits)
-        qc.h(0)
-        stab = StabilizerState(qc)
-        pairs = [("Z", 0), ("X", 1), ("Y", 0), ("I", 1)]
-        for label, target in pairs:
-            with self.subTest(msg=f"<{label}>"):
-                op = Pauli(label)
-                expval = stab.expectation_value(op)
-                self.assertEqual(expval, target)
-
-    def test_expval_two_qubits(self):
-        """Test expectation_value method of two qubits"""
-
-        num_qubits = 2
-
-        qc = QuantumCircuit(num_qubits)
-        stab = StabilizerState(qc)
-        pairs = [
-            ("II", 1),
-            ("XX", 0),
-            ("YY", 0),
-            ("ZZ", 1),
-            ("IX", 0),
-            ("IY", 0),
-            ("IZ", 1),
-            ("XY", 0),
-            ("XZ", 0),
-            ("YZ", 0),
-        ]
-        for label, target in pairs:
-            with self.subTest(msg=f"<{label}>"):
-                op = Pauli(label)
-                expval = stab.expectation_value(op)
-                self.assertEqual(expval, target)
-
-        qc = QuantumCircuit(num_qubits)
-        qc.x(0)
-        qc.x(1)
-        stab = StabilizerState(qc)
-        pairs = [
-            ("II", 1),
-            ("XX", 0),
-            ("YY", 0),
-            ("ZZ", 1),
-            ("IX", 0),
-            ("IY", 0),
-            ("IZ", -1),
-            ("XY", 0),
-            ("XZ", 0),
-            ("YZ", 0),
-        ]
-        for label, target in pairs:
-            with self.subTest(msg=f"<{label}>"):
-                op = Pauli(label)
-                expval = stab.expectation_value(op)
-                self.assertEqual(expval, target)
-
-        qc = QuantumCircuit(num_qubits)
-        qc.h(0)
-        qc.h(1)
-        stab = StabilizerState(qc)
-        pairs = [
-            ("II", 1),
-            ("XX", 1),
-            ("YY", 0),
-            ("ZZ", 0),
-            ("IX", 1),
-            ("IY", 0),
-            ("IZ", 0),
-            ("XY", 0),
-            ("XZ", 0),
-            ("YZ", 0),
-        ]
-        for label, target in pairs:
-            with self.subTest(msg=f"<{label}>"):
-                op = Pauli(label)
-                expval = stab.expectation_value(op)
-                self.assertEqual(expval, target)
-
-        qc = QuantumCircuit(num_qubits)
-        qc.x(0)
-        qc.h(1)
-        stab = StabilizerState(qc)
-        pairs = [
-            ("II", 1),
-            ("XX", 0),
-            ("YY", 0),
-            ("ZZ", 0),
-            ("IX", 0),
-            ("IY", 0),
-            ("IZ", -1),
-            ("XY", 0),
-            ("XZ", -1),
-            ("YZ", 0),
-        ]
-        for label, target in pairs:
-            with self.subTest(msg=f"<{label}>"):
-                op = Pauli(label)
-                expval = stab.expectation_value(op)
-                self.assertEqual(expval, target)
-
-        qc = QuantumCircuit(num_qubits)
-        qc.h(0)
-        qc.cx(0, 1)
-        stab = StabilizerState(qc)
-        pairs = [
-            ("II", 1),
-            ("XX", 1),
-            ("YY", -1),
-            ("ZZ", 1),
-            ("IX", 0),
-            ("IY", 0),
-            ("IZ", 0),
-            ("XY", 0),
-            ("XZ", 0),
-            ("YZ", 0),
-        ]
-        for label, target in pairs:
-            with self.subTest(msg=f"<{label}>"):
-                op = Pauli(label)
-                expval = stab.expectation_value(op)
-                self.assertEqual(expval, target)
-
-        qc = QuantumCircuit(num_qubits)
-        qc.h(0)
-        qc.x(1)
-        qc.cx(0, 1)
-        stab = StabilizerState(qc)
-        pairs = [
-            ("II", 1),
-            ("XX", 1),
-            ("YY", 1),
-            ("ZZ", -1),
-            ("IX", 0),
-            ("IY", 0),
-            ("IZ", 0),
-            ("XY", 0),
-            ("XZ", 0),
-            ("YZ", 0),
-        ]
-        for label, target in pairs:
-            with self.subTest(msg=f"<{label}>"):
-                op = Pauli(label)
-                expval = stab.expectation_value(op)
-                self.assertEqual(expval, target)
-
-        qc = QuantumCircuit(2)
-        qc.h(0)
-        qc.cx(0, 1)
-        qc.sdg(0)
-        qc.sdg(1)
-        qc.h(0)
-        qc.h(1)
-        stab = StabilizerState(qc)
-        pairs = [
-            ("II", 1),
-            ("XX", 1),
-            ("YY", 1),
-            ("ZZ", -1),
-            ("IX", 0),
-            ("IY", 0),
-            ("IZ", 0),
-            ("XY", 0),
-            ("XZ", 0),
-            ("YZ", 0),
-        ]
-        for label, target in pairs:
-            with self.subTest(msg=f"<{label}>"):
-                op = Pauli(label)
-                expval = stab.expectation_value(op)
-                self.assertEqual(expval, target)
-
     @combine(num_qubits=[2, 3, 4, 5])
-    def test_expval_random(self, num_qubits):
-        """Test expectation_value method of random Cliffords"""
+    def test_expval_from_random_clifford(self, num_qubits):
+        """Test that the expectation values for a random Clifford,
+        where the Pauli operators are all its stabilizers,
+        are equal to 1."""
 
         for _ in range(self.samples):
             cliff = random_clifford(num_qubits, seed=self.rng)
-            op = random_pauli(num_qubits, seed=self.rng)
             qc = cliff.to_circuit()
-            stab = StabilizerState(cliff)
-            exp_val = stab.expectation_value(op)
-            target = Statevector(qc).expectation_value(op)
-            self.assertAlmostEqual(exp_val, target)
-
-    @combine(num_qubits=[2, 3, 4, 5])
-    def test_expval_random_subsystem(self, num_qubits):
-        """Test expectation_value method of random Cliffords and a subsystem"""
-
-        for _ in range(self.samples):
-            cliff = random_clifford(num_qubits, seed=self.rng)
-            op = random_pauli(2, seed=self.rng)
-            qargs = np.random.choice(num_qubits, size=2, replace=False)
-            qc = cliff.to_circuit()
-            stab = StabilizerState(cliff)
-            exp_val = stab.expectation_value(op, qargs)
-            target = Statevector(qc).expectation_value(op, qargs)
-            self.assertAlmostEqual(exp_val, target)
+            stab = StabilizerState(qc)
+            stab_gen = stab.clifford.to_dict()["stabilizer"]
+            for i in range(num_qubits):
+                op = Pauli(stab_gen[i])
+                exp_val = stab.expectation_value(op)
+                self.assertEqual(exp_val, 1)
 
     def test_sample_counts_reset_bell(self):
         """Test sample_counts after reset for Bell state"""
@@ -880,5 +673,269 @@ def test_sample_counts_memory_superposition(self):
                 self.assertEqual(set(memory), set(target))
 
 
+@ddt
+class TestStabilizerStateExpectationValue(QiskitTestCase):
+    """Tests for StabilizerState.expectation_value method."""
+
+    rng = np.random.default_rng(12345)
+    samples = 10
+    shots = 1000
+    threshold = 0.1 * shots
+
+    @data(("Z", 1), ("X", 0), ("Y", 0), ("I", 1), ("Z", 1), ("-Z", -1), ("iZ", 1j), ("-iZ", -1j))
+    @unpack
+    def test_expval_single_qubit_0(self, label, target):
+        """Test expectation_value method of a single qubit on |0>"""
+        qc = QuantumCircuit(1)
+        stab = StabilizerState(qc)
+        op = Pauli(label)
+        expval = stab.expectation_value(op)
+        self.assertEqual(expval, target)
+
+    @data(("Z", -1), ("X", 0), ("Y", 0), ("I", 1))
+    @unpack
+    def test_expval_single_qubit_1(self, label, target):
+        """Test expectation_value method of a single qubit on |1>"""
+        qc = QuantumCircuit(1)
+        qc.x(0)
+        stab = StabilizerState(qc)
+        op = Pauli(label)
+        expval = stab.expectation_value(op)
+        self.assertEqual(expval, target)
+
+    @data(("Z", 0), ("X", 1), ("Y", 0), ("I", 1), ("X", 1), ("-X", -1), ("iX", 1j), ("-iX", -1j))
+    @unpack
+    def test_expval_single_qubit_plus(self, label, target):
+        """Test expectation_value method of a single qubit on |+>"""
+        qc = QuantumCircuit(1)
+        qc.h(0)
+        stab = StabilizerState(qc)
+        op = Pauli(label)
+        expval = stab.expectation_value(op)
+        self.assertEqual(expval, target)
+
+    @data(
+        ("II", 1),
+        ("XX", 0),
+        ("YY", 0),
+        ("ZZ", 1),
+        ("IX", 0),
+        ("IY", 0),
+        ("IZ", 1),
+        ("XY", 0),
+        ("XZ", 0),
+        ("YZ", 0),
+        ("-ZZ", -1),
+        ("iZZ", 1j),
+        ("-iZZ", -1j),
+    )
+    @unpack
+    def test_expval_two_qubits_00(self, label, target):
+        """Test expectation_value method of two qubits in |00>"""
+
+        num_qubits = 2
+
+        qc = QuantumCircuit(num_qubits)
+        stab = StabilizerState(qc)
+
+        op = Pauli(label)
+        expval = stab.expectation_value(op)
+        self.assertEqual(expval, target)
+
+    @data(
+        ("II", 1),
+        ("XX", 0),
+        ("YY", 0),
+        ("ZZ", 1),
+        ("IX", 0),
+        ("IY", 0),
+        ("IZ", -1),
+        ("XY", 0),
+        ("XZ", 0),
+        ("YZ", 0),
+    )
+    @unpack
+    def test_expval_two_qubits_11(self, label, target):
+        """Test expectation_value method of two qubits in |11>"""
+        qc = QuantumCircuit(2)
+        qc.x(0)
+        qc.x(1)
+        stab = StabilizerState(qc)
+        op = Pauli(label)
+        expval = stab.expectation_value(op)
+        self.assertEqual(expval, target)
+
+    @data(
+        ("II", 1),
+        ("XX", 1),
+        ("YY", 0),
+        ("ZZ", 0),
+        ("IX", 1),
+        ("IY", 0),
+        ("IZ", 0),
+        ("XY", 0),
+        ("XZ", 0),
+        ("YZ", 0),
+        ("-XX", -1),
+        ("iXX", 1j),
+        ("-iXX", -1j),
+    )
+    @unpack
+    def test_expval_two_qubits_plusplus(self, label, target):
+        """Test expectation_value method of two qubits in |++>"""
+        qc = QuantumCircuit(2)
+        qc.h(0)
+        qc.h(1)
+        stab = StabilizerState(qc)
+
+        op = Pauli(label)
+        expval = stab.expectation_value(op)
+        self.assertEqual(expval, target)
+
+    @data(
+        ("II", 1),
+        ("XX", 0),
+        ("YY", 0),
+        ("ZZ", 0),
+        ("IX", 0),
+        ("IY", 0),
+        ("IZ", -1),
+        ("XY", 0),
+        ("XZ", -1),
+        ("YZ", 0),
+    )
+    @unpack
+    def test_expval_two_qubits_plus1(self, label, target):
+        """Test expectation_value method of two qubits in |+1>"""
+        qc = QuantumCircuit(2)
+        qc.x(0)
+        qc.h(1)
+        stab = StabilizerState(qc)
+
+        op = Pauli(label)
+        expval = stab.expectation_value(op)
+        self.assertEqual(expval, target)
+
+    @data(
+        ("II", 1),
+        ("XX", 1),
+        ("YY", -1),
+        ("ZZ", 1),
+        ("IX", 0),
+        ("IY", 0),
+        ("IZ", 0),
+        ("XY", 0),
+        ("XZ", 0),
+        ("YZ", 0),
+        ("-YY", 1),
+        ("iYY", -1j),
+        ("-iYY", 1j),
+    )
+    @unpack
+    def test_expval_two_qubits_bell_phi_plus(self, label, target):
+        """Test expectation_value method of two qubits in bell phi plus"""
+        qc = QuantumCircuit(2)
+        qc.h(0)
+        qc.cx(0, 1)
+        stab = StabilizerState(qc)
+
+        op = Pauli(label)
+        expval = stab.expectation_value(op)
+        self.assertEqual(expval, target)
+
+    @data(
+        ("II", 1),
+        ("XX", 1),
+        ("YY", 1),
+        ("ZZ", -1),
+        ("IX", 0),
+        ("IY", 0),
+        ("IZ", 0),
+        ("XY", 0),
+        ("XZ", 0),
+        ("YZ", 0),
+        ("-XX", -1),
+        ("-YY", -1),
+        ("iXX", 1j),
+        ("iYY", 1j),
+        ("-iXX", -1j),
+        ("-iYY", -1j),
+    )
+    @unpack
+    def test_expval_two_qubits_bell_phi_minus(self, label, target):
+        """Test expectation_value method of two qubits in bell phi minus"""
+
+        qc = QuantumCircuit(2)
+        qc.h(0)
+        qc.x(1)
+        qc.cx(0, 1)
+        stab = StabilizerState(qc)
+
+        op = Pauli(label)
+        expval = stab.expectation_value(op)
+        self.assertEqual(expval, target)
+
+    @data(
+        ("II", 1),
+        ("XX", 1),
+        ("YY", 1),
+        ("ZZ", -1),
+        ("IX", 0),
+        ("IY", 0),
+        ("IZ", 0),
+        ("XY", 0),
+        ("XZ", 0),
+        ("YZ", 0),
+        ("-XX", -1),
+        ("-YY", -1),
+        ("iXX", 1j),
+        ("iYY", 1j),
+        ("-iXX", -1j),
+        ("-iYY", -1j),
+    )
+    @unpack
+    def test_expval_two_qubits_bell_sdg_h(self, label, target):
+        """Test expectation_value method of two qubits in bell followed by sdg and h"""
+        qc = QuantumCircuit(2)
+        qc.h(0)
+        qc.cx(0, 1)
+        qc.sdg(0)
+        qc.sdg(1)
+        qc.h(0)
+        qc.h(1)
+        stab = StabilizerState(qc)
+
+        op = Pauli(label)
+        expval = stab.expectation_value(op)
+        self.assertEqual(expval, target)
+
+    @combine(num_qubits=[2, 3, 4, 5])
+    def test_expval_random(self, num_qubits):
+        """Test expectation_value method of random Cliffords"""
+
+        for _ in range(self.samples):
+            cliff = random_clifford(num_qubits, seed=self.rng)
+            op = random_pauli(num_qubits, group_phase=True, seed=self.rng)
+            qc = cliff.to_circuit()
+            stab = StabilizerState(cliff)
+            exp_val = stab.expectation_value(op)
+            target = Statevector(qc).expectation_value(op)
+            self.assertAlmostEqual(exp_val, target)
+
+    @combine(num_qubits=[2, 3, 4, 5])
+    def test_expval_random_subsystem(self, num_qubits):
+        """Test expectation_value method of random Cliffords and a subsystem"""
+
+        for _ in range(self.samples):
+            cliff = random_clifford(num_qubits, seed=self.rng)
+            op = random_pauli(2, group_phase=True, seed=self.rng)
+            qargs = np.random.choice(num_qubits, size=2, replace=False)
+            qc = cliff.to_circuit()
+            stab = StabilizerState(cliff)
+            exp_val = stab.expectation_value(op, qargs)
+            target = Statevector(qc).expectation_value(op, qargs)
+            self.assertAlmostEqual(exp_val, target)
+
+
 if __name__ == "__main__":
     unittest.main()