Repository: Qiskit/qiskit

Issue:
Terra's statevector class gives different probabilities than Aer's simulators
<!-- ⚠️ 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**: qiskit 0.25.1
- **Python version**:
- **Operating system**:

### What is the current behavior?

Terra's `Statevector` class gives different probabilities than Aer's simulators, when the qubits are not ordered.

### Steps to reproduce the problem

```
from qiskit import transpile, assemble
from qiskit.providers.aer import AerSimulator
from qiskit.quantum_info import Statevector
from qiskit.quantum_info.random import random_clifford

num_qubits = 4
cliff = random_clifford(num_qubits, seed=1234)
qargs = [2, 1, 3]  # but also this qargs gives wrong results: [0, 3, 1]
qc = cliff.to_circuit()
target = Statevector(qc).probabilities(qargs, decimals=2)
target_dict = Statevector(qc).probabilities_dict(qargs, decimals=2)
print(cliff, num_qubits, qargs)
print("qi statevector", target_dict, target)

for method in ['stabilizer', 'statevector']:
    SIMULATOR = AerSimulator(method=method)
    label = 'probs'
    circ = transpile(qc, SIMULATOR)
    circ.save_probabilities(qargs, label=label)
    qobj = assemble(circ)
    result = SIMULATOR.run(qobj).result()
    output = result.data(0)[label]
    print("Aer", method, output)
```

The output is:

```
Clifford: Stabilizer = ['-ZZZZ', '-ZYIY', '+IIZI', '+YIZX'], Destabilizer = ['-XIZZ', '+YZIX', '-ZYYI', '+XXII'] 4 [2, 1, 3]
qi statevector {'000': 0.25, '010': 0.25, '100': 0.25, '110': 0.25} [0.25 0.   0.25 0.   0.25 0.   0.25 0.  ]
Aer stabilizer [0.25 0.25 0.   0.   0.25 0.25 0.   0.  ]
Aer statevector [2.50000000e-01 2.50000000e-01 5.87817838e-35 9.92970390e-33
 2.50000000e-01 2.50000000e-01 1.66922573e-32 3.94060394e-33]

```

### What is the expected behavior?

Terra's `Statevector` class should give same probabilities as Aer's simulators.
Moreover, it would be good if the tests in Aer could catch such bugs:
https://github.com/Qiskit/qiskit-aer/blob/master/test/terra/backends/qasm_simulator/qasm_save_probabilities.py

### Suggested solutions

Please fix it, so that I can finish my `StabilizerState` class (PR #5907) which seems to agree with Aer's simulators on the probabilities (but not with the `Statevector` class)



Discussion:

Resolving patch:
diff --git a/qiskit/quantum_info/states/quantum_state.py b/qiskit/quantum_info/states/quantum_state.py
--- a/qiskit/quantum_info/states/quantum_state.py
+++ b/qiskit/quantum_info/states/quantum_state.py
@@ -439,43 +439,6 @@ def _matrix_to_dict(mat, dims, decimals=None, string_labels=False):
         return {(tuple(ket), tuple(bra)): val for ket, bra, val in zip(
             kets, bras, vals[inds_row, inds_col])}
 
-    @staticmethod
-    def _accumulate_dims(dims, qargs):
-        """Flatten subsystem dimensions for unspecified qargs.
-
-        This has the potential to reduce the number of subsystems
-        by combining consecutive subsystems between the specified
-        qargs. For example, if we had a 5-qubit system with
-        ``dims = (2, 2, 2, 2, 2)``, and ``qargs=[0, 4]``, then the
-        flattened system will have dimensions ``new_dims = (2, 8, 2)``
-        and qargs ``new_qargs = [0, 2]``.
-
-        Args:
-            dims (tuple): subsystem dimensions.
-            qargs (list): qargs list.
-
-        Returns:
-            tuple: the pair (new_dims, new_qargs).
-        """
-
-        qargs_map = {}
-        new_dims = []
-
-        # Accumulate subsystems that can be combined
-        accum = []
-        for i, dim in enumerate(dims):
-            if i in qargs:
-                if accum:
-                    new_dims.append(np.product(accum))
-                    accum = []
-                new_dims.append(dim)
-                qargs_map[i] = len(new_dims) - 1
-            else:
-                accum.append(dim)
-        if accum:
-            new_dims.append(np.product(accum))
-        return tuple(new_dims), [qargs_map[i] for i in qargs]
-
     @staticmethod
     def _subsystem_probabilities(probs, dims, qargs=None):
         """Marginalize a probability vector according to subsystems.
@@ -491,30 +454,21 @@ def _subsystem_probabilities(probs, dims, qargs=None):
             np.array: the marginalized probability vector flattened
                       for the specified qargs.
         """
-
         if qargs is None:
             return probs
-
-        # Accumulate dimensions to trace over
-        accum_dims, accum_qargs = QuantumState._accumulate_dims(
-            dims, qargs)
-
-        # Get sum axis for maginalized subsystems
-        n_qargs = len(accum_dims)
-        axis = list(range(n_qargs))
-        for i in accum_qargs:
-            axis.remove(n_qargs - 1 - i)
-
-        # Reshape the probability to a tensor and sum over maginalized axes
-        new_probs = np.sum(np.reshape(probs, list(reversed(accum_dims))),
-                           axis=tuple(axis))
-
-        # Transpose output probs based on order of qargs
-        if sorted(accum_qargs) != accum_qargs:
-            axes = np.argsort(accum_qargs)
-            return np.ravel(np.transpose(new_probs, axes=axes))
-
-        return np.ravel(new_probs)
+        # Convert qargs to tensor axes
+        probs_tens = np.reshape(probs, dims)
+        ndim = probs_tens.ndim
+        qargs_axes = [ndim - 1 - i for i in reversed(qargs)]
+        # Get sum axis for marginalized subsystems
+        sum_axis = tuple(i for i in range(ndim) if i not in qargs_axes)
+        if sum_axis:
+            probs_tens = np.sum(probs_tens, axis=sum_axis)
+            qargs_axes = np.argsort(np.argsort(qargs_axes))
+        # Permute probability vector for desired qargs order
+        probs_tens = np.transpose(probs_tens, axes=qargs_axes)
+        new_probs = np.reshape(probs_tens, (probs_tens.size,))
+        return new_probs
 
     # Overloads
     def __and__(self, other):


Test patch:
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
@@ -15,6 +15,7 @@
 
 import unittest
 import logging
+from itertools import permutations
 from ddt import ddt, data
 import numpy as np
 from numpy.testing import assert_allclose
@@ -24,6 +25,7 @@
 from qiskit import QuantumRegister, QuantumCircuit
 from qiskit import transpile
 from qiskit.circuit.library import HGate, QFT
+from qiskit.providers.basicaer import QasmSimulatorPy
 
 from qiskit.quantum_info.random import (
     random_unitary, random_statevector, random_pauli)
@@ -947,6 +949,35 @@ def test_expval_pauli_qargs(self, qubits):
         expval = state.expectation_value(op, qubits)
         self.assertAlmostEqual(expval, target)
 
+    @data(*[qargs for i in range(4) for qargs in permutations(range(4), r=i+1)])
+    def test_probabilities_qargs(self, qargs):
+        """Test probabilities method with qargs"""
+        # Get initial state
+        nq = 4
+        nc = len(qargs)
+        state_circ = QuantumCircuit(nq, nc)
+        for i in range(nq):
+            state_circ.ry((i + 1) * np.pi / (nq + 1), i)
+
+        # Get probabilities
+        state = Statevector(state_circ)
+        probs = state.probabilities(qargs)
+
+        # Estimate target probs from simulator measurement
+        sim = QasmSimulatorPy()
+        shots = 5000
+        seed = 100
+        circ = transpile(state_circ, sim)
+        circ.measure(qargs, range(nc))
+        result = sim.run(
+            circ, shots=shots, seed_simulator=seed).result()
+        target = np.zeros(2 ** nc, dtype=float)
+        for i, p in result.get_counts(0).int_outcomes().items():
+            target[i] = p / shots
+        # Compare
+        delta = np.linalg.norm(probs - target)
+        self.assertLess(delta, 0.05)
+
     def test_global_phase(self):
         """Test global phase is handled correctly when evolving statevector."""