Repository: Qiskit/qiskit

Issue:
NumPyEigensolver and NumPyMinimumEigensolver return wrong results for 1-qubit operators
<!-- ⚠️ 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**: latest
- **Python version**: 3.8.5
- **Operating system**: Ubuntu 20.04.1

### What is the current behavior?

`NumPyEigensolver` and `NumPyMinimumEigensolver` returns wrong results when 1 qubit operators are given.

### Steps to reproduce the problem

```python
from qiskit.algorithms import NumPyEigensolver, NumPyMinimumEigensolver
from qiskit.opflow import PauliSumOp

def test_exact_eigen(op):
    print(op)
    solver = NumPyMinimumEigensolver()
    print(solver.compute_minimum_eigenvalue(op).eigenvalue)

    solver = NumPyEigensolver(k=2)
    print(solver.compute_eigenvalues(op).eigenvalues)

    solver = NumPyEigensolver(k=1)
    print(solver.compute_eigenvalues(op).eigenvalues)
    print()

op = PauliSumOp.from_list([('XI', 1)])
test_exact_eigen(op)

op = PauliSumOp.from_list([('X', 1)])
test_exact_eigen(op)
```

output:
```
1.0 * XI
(-0.9999999999999999-4.129073125279104e-18j)  # OK
[-1.-1.79927237e-18j -1.-1.41733575e-18j]  # OK
[-1.+2.60286226e-18j]  # OK

1.0 * X
(0.9999999999999996+0j)   # NG, wrong answer, should be -1
[-1.+0.j  1.+0.j]  # OK
[ 1.+0.j -1.+0.j]  # NG, size is not correct, should be an array with size 1
```


### What is the expected behavior?

output:
```
1.0 * XI
(-0.9999999999999999-4.129073125279104e-18j)
[-1.-1.79927237e-18j -1.-1.41733575e-18j]
[-1.+2.60286226e-18j]

1.0 * X
(-1)
[-1.+0.j  1.+0.j]
[-1]
```

### Suggested solutions

There are two reasons as follows.
1. use of numpy.linalg.eig https://github.com/Qiskit/qiskit-terra/blob/f53a4b0bd1c951b6b1ccc3c8c33eec6e9cf3288e/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py#L120
[The documentations](https://numpy.org/doc/stable/reference/generated/numpy.linalg.eig.html) says that
> The eigenvalues are not necessarily ordered.

2. the current code does not truncate result with k==1 https://github.com/Qiskit/qiskit-terra/blob/f53a4b0bd1c951b6b1ccc3c8c33eec6e9cf3288e/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py#L124

I propose to use [`eigh`](https://numpy.org/doc/stable/reference/generated/numpy.linalg.eigh.html) that guarantees the ascending order of results.



Discussion:

Resolving patch:
diff --git a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py b/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py
--- a/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py
+++ b/qiskit/algorithms/eigen_solvers/numpy_eigen_solver.py
@@ -1,6 +1,6 @@
 # This code is part of Qiskit.
 #
-# (C) Copyright IBM 2018, 2020.
+# (C) Copyright IBM 2018, 2021.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
@@ -12,8 +12,9 @@
 
 """The Eigensolver algorithm."""
 
-from typing import List, Optional, Union, Tuple, Callable
 import logging
+from typing import List, Optional, Union, Tuple, Callable
+
 import numpy as np
 from scipy import sparse as scisparse
 
@@ -96,8 +97,8 @@ def supports_aux_operators(cls) -> bool:
 
     def _check_set_k(self, operator: OperatorBase) -> None:
         if operator is not None:
-            if self._in_k > 2**(operator.num_qubits):
-                self._k = 2**(operator.num_qubits)
+            if self._in_k > 2 ** operator.num_qubits:
+                self._k = 2 ** operator.num_qubits
                 logger.debug("WARNING: Asked for %s eigenvalues but max possible is %s.",
                              self._in_k, self._k)
             else:
@@ -109,22 +110,21 @@ def _solve(self,
         # If matrix is diagonal, the elements on the diagonal are the eigenvalues. Solve by sorting.
         if scisparse.csr_matrix(sp_mat.diagonal()).nnz == sp_mat.nnz:
             diag = sp_mat.diagonal()
-            eigval = np.sort(diag)[:self._k]
-            temp = np.argsort(diag)[:self._k]
+            indices = np.argsort(diag)[:self._k]
+            eigval = diag[indices]
             eigvec = np.zeros((sp_mat.shape[0], self._k))
-            for i, idx in enumerate(temp):
+            for i, idx in enumerate(indices):
                 eigvec[idx, i] = 1.0
         else:
-            if self._k >= 2 ** (operator.num_qubits) - 1:
+            if self._k >= 2 ** operator.num_qubits - 1:
                 logger.debug("SciPy doesn't support to get all eigenvalues, using NumPy instead.")
                 eigval, eigvec = np.linalg.eig(operator.to_matrix())
             else:
                 eigval, eigvec = scisparse.linalg.eigs(operator.to_spmatrix(),
                                                        k=self._k, which='SR')
-        if self._k > 1:
-            idx = eigval.argsort()
-            eigval = eigval[idx]
-            eigvec = eigvec[:, idx]
+            indices = np.argsort(eigval)[:self._k]
+            eigval = eigval[indices]
+            eigvec = eigvec[:, indices]
         self._ret.eigenvalues = eigval
         self._ret.eigenstates = eigvec.T
 
@@ -190,7 +190,7 @@ def compute_eigenvalues(
         k_orig = self._k
         if self._filter_criterion:
             # need to consider all elements if a filter is set
-            self._k = 2**operator.num_qubits
+            self._k = 2 ** operator.num_qubits
 
         self._ret = EigensolverResult()
         self._solve(operator)


Test patch:
diff --git a/test/python/algorithms/test_numpy_eigen_solver.py b/test/python/algorithms/test_numpy_eigen_solver.py
--- a/test/python/algorithms/test_numpy_eigen_solver.py
+++ b/test/python/algorithms/test_numpy_eigen_solver.py
@@ -1,6 +1,6 @@
 # This code is part of Qiskit.
 #
-# (C) Copyright IBM 2018, 2020.
+# (C) Copyright IBM 2018, 2021.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
@@ -14,12 +14,15 @@
 
 import unittest
 from test.python.algorithms import QiskitAlgorithmsTestCase
+
 import numpy as np
+from ddt import data, ddt
 
 from qiskit.algorithms import NumPyEigensolver
-from qiskit.opflow import PauliSumOp
+from qiskit.opflow import PauliSumOp, X, Y, Z
 
 
+@ddt
 class TestNumPyEigensolver(QiskitAlgorithmsTestCase):
     """ Test NumPy Eigen solver """
 
@@ -77,6 +80,20 @@ def criterion(x, v, a_v):
         self.assertEqual(len(result.eigenvalues), 0)
         self.assertEqual(len(result.eigenstates), 0)
 
+    @data(X, Y, Z)
+    def test_ce_k1_1q(self, op):
+        """ Test for 1 qubit operator """
+        algo = NumPyEigensolver(k=1)
+        result = algo.compute_eigenvalues(operator=op)
+        np.testing.assert_array_almost_equal(result.eigenvalues, [-1])
+
+    @data(X, Y, Z)
+    def test_ce_k2_1q(self, op):
+        """ Test for 1 qubit operator """
+        algo = NumPyEigensolver(k=2)
+        result = algo.compute_eigenvalues(operator=op)
+        np.testing.assert_array_almost_equal(result.eigenvalues, [-1, 1])
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/test/python/algorithms/test_numpy_minimum_eigen_solver.py b/test/python/algorithms/test_numpy_minimum_eigen_solver.py
--- a/test/python/algorithms/test_numpy_minimum_eigen_solver.py
+++ b/test/python/algorithms/test_numpy_minimum_eigen_solver.py
@@ -1,6 +1,6 @@
 # This code is part of Qiskit.
 #
-# (C) Copyright IBM 2020.
+# (C) Copyright IBM 2020, 2021.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
@@ -14,12 +14,15 @@
 
 import unittest
 from test.python.algorithms import QiskitAlgorithmsTestCase
+
 import numpy as np
+from ddt import ddt, data
 
 from qiskit.algorithms import NumPyMinimumEigensolver
-from qiskit.opflow import PauliSumOp
+from qiskit.opflow import PauliSumOp, X, Y, Z
 
 
+@ddt
 class TestNumPyMinimumEigensolver(QiskitAlgorithmsTestCase):
     """ Test NumPy Minimum Eigensolver """
 
@@ -121,6 +124,13 @@ def criterion(x, v, a_v):
         self.assertEqual(result.eigenstate, None)
         self.assertEqual(result.aux_operator_eigenvalues, None)
 
+    @data(X, Y, Z)
+    def test_cme_1q(self, op):
+        """ Test for 1 qubit operator """
+        algo = NumPyMinimumEigensolver()
+        result = algo.compute_minimum_eigenvalue(operator=op)
+        self.assertAlmostEqual(result.eigenvalue, -1)
+
 
 if __name__ == '__main__':
     unittest.main()