Repository: scipy/scipy

Issue:
sparse.linalg factorizations slow for small k due to default ncv value
`eigs`, `eigsh`, `svds` all default to using `2 * k + 1` for the number of lanczos vectors `ncv`. This is fine for `k > ~5` or so but drastically slows the algorithm down for small `k` (e.g. k=2 or 1 slower than k=6!) and often means the convergence fails. The problem is worse for dense matrices (these sparse methods are actually very useful for dense matrices as well). 

The optimum value depends on `n, k,`, the sparsity and other factors, but should generally should be `>= 10`. Matlab apparently uses 20 as its minimum for equivalent functions but this might be a bit high for small sparse matrices. 

There might be a method of choosing `ncv` intelligently? But setting it to at minimum `10` would be a quick fix.



Discussion:
As noted in my (duplicate) bug report (see above :), the current ncv default can also result in _wrong_ results.


Resolving patch:
diff --git a/scipy/sparse/linalg/eigen/arpack/arpack.py b/scipy/sparse/linalg/eigen/arpack/arpack.py
--- a/scipy/sparse/linalg/eigen/arpack/arpack.py
+++ b/scipy/sparse/linalg/eigen/arpack/arpack.py
@@ -297,6 +297,14 @@ def __init__(self, msg, eigenvalues, eigenvectors):
         self.eigenvectors = eigenvectors
 
 
+def choose_ncv(k):
+    """
+    Choose number of lanczos vectors based on target number
+    of singular/eigen values and vectors to compute, k.
+    """
+    return max(2 * k + 1, 20)
+
+
 class _ArpackParams(object):
     def __init__(self, n, k, tp, mode=1, sigma=None,
                  ncv=None, v0=None, maxiter=None, which="LM", tol=0):
@@ -327,7 +335,7 @@ def __init__(self, n, k, tp, mode=1, sigma=None,
             self.sigma = sigma
 
         if ncv is None:
-            ncv = 2 * k + 1
+            ncv = choose_ncv(k)
         ncv = min(ncv, n)
 
         self.v = np.zeros((n, ncv), tp)  # holds Ritz vectors
@@ -1125,7 +1133,7 @@ def eigs(A, k=6, M=None, sigma=None, which='LM', v0=None,
     ncv : int, optional
         The number of Lanczos vectors generated
         `ncv` must be greater than `k`; it is recommended that ``ncv > 2*k``.
-        Default: ``min(n, 2*k + 1)``
+        Default: ``min(n, max(2*k + 1, 20))``
     which : str, ['LM' | 'SM' | 'LR' | 'SR' | 'LI' | 'SI'], optional
         Which `k` eigenvectors and eigenvalues to find:
 
@@ -1371,7 +1379,7 @@ def eigsh(A, k=6, M=None, sigma=None, which='LM', v0=None,
     ncv : int, optional
         The number of Lanczos vectors generated ncv must be greater than k and
         smaller than n; it is recommended that ``ncv > 2*k``.
-        Default: ``min(n, 2*k + 1)``
+        Default: ``min(n, max(2*k + 1, 20))``
     which : str ['LM' | 'SM' | 'LA' | 'SA' | 'BE']
         If A is a complex hermitian matrix, 'BE' is invalid.
         Which `k` eigenvectors and eigenvalues to find:
@@ -1638,7 +1646,7 @@ def svds(A, k=6, ncv=None, tol=0, which='LM', v0=None,
         The number of Lanczos vectors generated
         ncv must be greater than k+1 and smaller than n;
         it is recommended that ncv > 2*k
-        Default: ``min(n, 2*k + 1)``
+        Default: ``min(n, max(2*k + 1, 20))``
     tol : float, optional
         Tolerance for singular values. Zero (default) means machine precision.
     which : str, ['LM' | 'SM'], optional


Test patch:
diff --git a/scipy/sparse/linalg/eigen/arpack/tests/test_arpack.py b/scipy/sparse/linalg/eigen/arpack/tests/test_arpack.py
--- a/scipy/sparse/linalg/eigen/arpack/tests/test_arpack.py
+++ b/scipy/sparse/linalg/eigen/arpack/tests/test_arpack.py
@@ -423,7 +423,7 @@ def test_symmetric_no_convergence():
     m = generate_matrix(30, hermitian=True, pos_definite=True)
     tol, rtol, atol = _get_test_tolerance('d')
     try:
-        w, v = eigsh(m, 4, which='LM', v0=m[:, 0], maxiter=5, tol=tol)
+        w, v = eigsh(m, 4, which='LM', v0=m[:, 0], maxiter=5, tol=tol, ncv=9)
         raise AssertionError("Spurious no-error exit")
     except ArpackNoConvergence as err:
         k = len(err.eigenvalues)
@@ -620,9 +620,9 @@ def test_svd_maxiter():
     x = hilbert(6)
     # ARPACK shouldn't converge on such an ill-conditioned matrix with just
     # one iteration
-    assert_raises(ArpackNoConvergence, svds, x, 1, maxiter=1)
+    assert_raises(ArpackNoConvergence, svds, x, 1, maxiter=1, ncv=3)
     # but 100 iterations should be more than enough
-    u, s, vt = svds(x, 1, maxiter=100)
+    u, s, vt = svds(x, 1, maxiter=100, ncv=3)
     assert_allclose(s, [1.7], atol=0.5)