pruned venvs
This commit is contained in:
@@ -1,18 +0,0 @@
|
||||
"""
|
||||
Sparse Eigenvalue Solvers
|
||||
-------------------------
|
||||
|
||||
The submodules of sparse.linalg.eigen:
|
||||
1. lobpcg: Locally Optimal Block Preconditioned Conjugate Gradient Method
|
||||
|
||||
"""
|
||||
from __future__ import division, print_function, absolute_import
|
||||
|
||||
from .arpack import *
|
||||
from .lobpcg import *
|
||||
|
||||
__all__ = [s for s in dir() if not s.startswith('_')]
|
||||
|
||||
from scipy._lib._testutils import PytestTester
|
||||
test = PytestTester(__name__)
|
||||
del PytestTester
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
-45
@@ -1,45 +0,0 @@
|
||||
|
||||
BSD Software License
|
||||
|
||||
Pertains to ARPACK and P_ARPACK
|
||||
|
||||
Copyright (c) 1996-2008 Rice University.
|
||||
Developed by D.C. Sorensen, R.B. Lehoucq, C. Yang, and K. Maschhoff.
|
||||
All rights reserved.
|
||||
|
||||
Arpack has been renamed to arpack-ng.
|
||||
|
||||
Copyright (c) 2001-2011 - Scilab Enterprises
|
||||
Updated by Allan Cornet, Sylvestre Ledru.
|
||||
|
||||
Copyright (c) 2010 - Jordi Gutiérrez Hermoso (Octave patch)
|
||||
|
||||
Copyright (c) 2007 - Sébastien Fabbro (gentoo patch)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer listed
|
||||
in this license in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
- Neither the name of the copyright holders nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,22 +0,0 @@
|
||||
"""
|
||||
Eigenvalue solver using iterative methods.
|
||||
|
||||
Find k eigenvectors and eigenvalues of a matrix A using the
|
||||
Arnoldi/Lanczos iterative methods from ARPACK [1]_,[2]_.
|
||||
|
||||
These methods are most useful for large sparse matrices.
|
||||
|
||||
- eigs(A,k)
|
||||
- eigsh(A,k)
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] ARPACK Software, http://www.caam.rice.edu/software/ARPACK/
|
||||
.. [2] R. B. Lehoucq, D. C. Sorensen, and C. Yang, ARPACK USERS GUIDE:
|
||||
Solution of Large Scale Eigenvalue Problems by Implicitly Restarted
|
||||
Arnoldi Methods. SIAM, Philadelphia, PA, 1998.
|
||||
|
||||
"""
|
||||
from __future__ import division, print_function, absolute_import
|
||||
|
||||
from .arpack import *
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,41 +0,0 @@
|
||||
from __future__ import division, print_function, absolute_import
|
||||
|
||||
from os.path import join
|
||||
|
||||
|
||||
def configuration(parent_package='',top_path=None):
|
||||
from scipy._build_utils.system_info import get_info, NotFoundError
|
||||
from numpy.distutils.misc_util import Configuration
|
||||
from scipy._build_utils import get_g77_abi_wrappers
|
||||
|
||||
lapack_opt = get_info('lapack_opt')
|
||||
|
||||
config = Configuration('arpack', parent_package, top_path)
|
||||
|
||||
arpack_sources = [join('ARPACK','SRC', '*.f')]
|
||||
arpack_sources.extend([join('ARPACK','UTIL', '*.f')])
|
||||
|
||||
arpack_sources += get_g77_abi_wrappers(lapack_opt)
|
||||
|
||||
config.add_library('arpack_scipy', sources=arpack_sources,
|
||||
include_dirs=[join('ARPACK', 'SRC')])
|
||||
|
||||
ext_sources = ['arpack.pyf.src']
|
||||
config.add_extension('_arpack',
|
||||
sources=ext_sources,
|
||||
libraries=['arpack_scipy'],
|
||||
extra_info=lapack_opt,
|
||||
depends=arpack_sources,
|
||||
)
|
||||
|
||||
config.add_data_dir('tests')
|
||||
|
||||
# Add license files
|
||||
config.add_data_files('ARPACK/COPYING')
|
||||
|
||||
return config
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(**configuration(top_path='').todict())
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
-965
@@ -1,965 +0,0 @@
|
||||
from __future__ import division, print_function, absolute_import
|
||||
|
||||
__usage__ = """
|
||||
To run tests locally:
|
||||
python tests/test_arpack.py [-l<int>] [-v<int>]
|
||||
|
||||
"""
|
||||
|
||||
import threading
|
||||
|
||||
import numpy as np
|
||||
|
||||
from numpy.testing import (assert_allclose, assert_array_almost_equal_nulp,
|
||||
assert_equal, assert_array_equal)
|
||||
from pytest import raises as assert_raises
|
||||
import pytest
|
||||
|
||||
from numpy import dot, conj, random
|
||||
from scipy.linalg import eig, eigh, hilbert, svd
|
||||
from scipy.sparse import csc_matrix, csr_matrix, isspmatrix, diags
|
||||
from scipy.sparse.linalg import LinearOperator, aslinearoperator
|
||||
from scipy.sparse.linalg.eigen.arpack import eigs, eigsh, svds, \
|
||||
ArpackNoConvergence, arpack
|
||||
|
||||
from scipy._lib._gcutils import assert_deallocated, IS_PYPY
|
||||
from scipy._lib._numpy_compat import suppress_warnings
|
||||
|
||||
|
||||
# precision for tests
|
||||
_ndigits = {'f': 3, 'd': 11, 'F': 3, 'D': 11}
|
||||
|
||||
|
||||
def _get_test_tolerance(type_char, mattype=None):
|
||||
"""
|
||||
Return tolerance values suitable for a given test:
|
||||
|
||||
Parameters
|
||||
----------
|
||||
type_char : {'f', 'd', 'F', 'D'}
|
||||
Data type in ARPACK eigenvalue problem
|
||||
mattype : {csr_matrix, aslinearoperator, asarray}, optional
|
||||
Linear operator type
|
||||
|
||||
Returns
|
||||
-------
|
||||
tol
|
||||
Tolerance to pass to the ARPACK routine
|
||||
rtol
|
||||
Relative tolerance for outputs
|
||||
atol
|
||||
Absolute tolerance for outputs
|
||||
|
||||
"""
|
||||
|
||||
rtol = {'f': 3000 * np.finfo(np.float32).eps,
|
||||
'F': 3000 * np.finfo(np.float32).eps,
|
||||
'd': 2000 * np.finfo(np.float64).eps,
|
||||
'D': 2000 * np.finfo(np.float64).eps}[type_char]
|
||||
atol = rtol
|
||||
tol = 0
|
||||
|
||||
if mattype is aslinearoperator and type_char in ('f', 'F'):
|
||||
# iterative methods in single precision: worse errors
|
||||
# also: bump ARPACK tolerance so that the iterative method converges
|
||||
tol = 30 * np.finfo(np.float32).eps
|
||||
rtol *= 5
|
||||
|
||||
if mattype is csr_matrix and type_char in ('f', 'F'):
|
||||
# sparse in single precision: worse errors
|
||||
rtol *= 5
|
||||
|
||||
return tol, rtol, atol
|
||||
|
||||
|
||||
def generate_matrix(N, complex=False, hermitian=False,
|
||||
pos_definite=False, sparse=False):
|
||||
M = np.random.random((N,N))
|
||||
if complex:
|
||||
M = M + 1j * np.random.random((N,N))
|
||||
|
||||
if hermitian:
|
||||
if pos_definite:
|
||||
if sparse:
|
||||
i = np.arange(N)
|
||||
j = np.random.randint(N, size=N-2)
|
||||
i, j = np.meshgrid(i, j)
|
||||
M[i,j] = 0
|
||||
M = np.dot(M.conj(), M.T)
|
||||
else:
|
||||
M = np.dot(M.conj(), M.T)
|
||||
if sparse:
|
||||
i = np.random.randint(N, size=N * N // 4)
|
||||
j = np.random.randint(N, size=N * N // 4)
|
||||
ind = np.nonzero(i == j)
|
||||
j[ind] = (j[ind] + 1) % N
|
||||
M[i,j] = 0
|
||||
M[j,i] = 0
|
||||
else:
|
||||
if sparse:
|
||||
i = np.random.randint(N, size=N * N // 2)
|
||||
j = np.random.randint(N, size=N * N // 2)
|
||||
M[i,j] = 0
|
||||
return M
|
||||
|
||||
|
||||
def generate_matrix_symmetric(N, pos_definite=False, sparse=False):
|
||||
M = np.random.random((N, N))
|
||||
|
||||
M = 0.5 * (M + M.T) # Make M symmetric
|
||||
|
||||
if pos_definite:
|
||||
Id = N * np.eye(N)
|
||||
if sparse:
|
||||
M = csr_matrix(M)
|
||||
M += Id
|
||||
else:
|
||||
if sparse:
|
||||
M = csr_matrix(M)
|
||||
|
||||
return M
|
||||
|
||||
|
||||
def _aslinearoperator_with_dtype(m):
|
||||
m = aslinearoperator(m)
|
||||
if not hasattr(m, 'dtype'):
|
||||
x = np.zeros(m.shape[1])
|
||||
m.dtype = (m * x).dtype
|
||||
return m
|
||||
|
||||
|
||||
def assert_allclose_cc(actual, desired, **kw):
|
||||
"""Almost equal or complex conjugates almost equal"""
|
||||
try:
|
||||
assert_allclose(actual, desired, **kw)
|
||||
except AssertionError:
|
||||
assert_allclose(actual, conj(desired), **kw)
|
||||
|
||||
|
||||
def argsort_which(eval, typ, k, which,
|
||||
sigma=None, OPpart=None, mode=None):
|
||||
"""Return sorted indices of eigenvalues using the "which" keyword
|
||||
from eigs and eigsh"""
|
||||
if sigma is None:
|
||||
reval = np.round(eval, decimals=_ndigits[typ])
|
||||
else:
|
||||
if mode is None or mode == 'normal':
|
||||
if OPpart is None:
|
||||
reval = 1. / (eval - sigma)
|
||||
elif OPpart == 'r':
|
||||
reval = 0.5 * (1. / (eval - sigma)
|
||||
+ 1. / (eval - np.conj(sigma)))
|
||||
elif OPpart == 'i':
|
||||
reval = -0.5j * (1. / (eval - sigma)
|
||||
- 1. / (eval - np.conj(sigma)))
|
||||
elif mode == 'cayley':
|
||||
reval = (eval + sigma) / (eval - sigma)
|
||||
elif mode == 'buckling':
|
||||
reval = eval / (eval - sigma)
|
||||
else:
|
||||
raise ValueError("mode='%s' not recognized" % mode)
|
||||
|
||||
reval = np.round(reval, decimals=_ndigits[typ])
|
||||
|
||||
if which in ['LM', 'SM']:
|
||||
ind = np.argsort(abs(reval))
|
||||
elif which in ['LR', 'SR', 'LA', 'SA', 'BE']:
|
||||
ind = np.argsort(np.real(reval))
|
||||
elif which in ['LI', 'SI']:
|
||||
# for LI,SI ARPACK returns largest,smallest abs(imaginary) why?
|
||||
if typ.islower():
|
||||
ind = np.argsort(abs(np.imag(reval)))
|
||||
else:
|
||||
ind = np.argsort(np.imag(reval))
|
||||
else:
|
||||
raise ValueError("which='%s' is unrecognized" % which)
|
||||
|
||||
if which in ['LM', 'LA', 'LR', 'LI']:
|
||||
return ind[-k:]
|
||||
elif which in ['SM', 'SA', 'SR', 'SI']:
|
||||
return ind[:k]
|
||||
elif which == 'BE':
|
||||
return np.concatenate((ind[:k//2], ind[k//2-k:]))
|
||||
|
||||
|
||||
def eval_evec(symmetric, d, typ, k, which, v0=None, sigma=None,
|
||||
mattype=np.asarray, OPpart=None, mode='normal'):
|
||||
general = ('bmat' in d)
|
||||
|
||||
if symmetric:
|
||||
eigs_func = eigsh
|
||||
else:
|
||||
eigs_func = eigs
|
||||
|
||||
if general:
|
||||
err = ("error for %s:general, typ=%s, which=%s, sigma=%s, "
|
||||
"mattype=%s, OPpart=%s, mode=%s" % (eigs_func.__name__,
|
||||
typ, which, sigma,
|
||||
mattype.__name__,
|
||||
OPpart, mode))
|
||||
else:
|
||||
err = ("error for %s:standard, typ=%s, which=%s, sigma=%s, "
|
||||
"mattype=%s, OPpart=%s, mode=%s" % (eigs_func.__name__,
|
||||
typ, which, sigma,
|
||||
mattype.__name__,
|
||||
OPpart, mode))
|
||||
|
||||
a = d['mat'].astype(typ)
|
||||
ac = mattype(a)
|
||||
|
||||
if general:
|
||||
b = d['bmat'].astype(typ.lower())
|
||||
bc = mattype(b)
|
||||
|
||||
# get exact eigenvalues
|
||||
exact_eval = d['eval'].astype(typ.upper())
|
||||
ind = argsort_which(exact_eval, typ, k, which,
|
||||
sigma, OPpart, mode)
|
||||
exact_eval = exact_eval[ind]
|
||||
|
||||
# compute arpack eigenvalues
|
||||
kwargs = dict(which=which, v0=v0, sigma=sigma)
|
||||
if eigs_func is eigsh:
|
||||
kwargs['mode'] = mode
|
||||
else:
|
||||
kwargs['OPpart'] = OPpart
|
||||
|
||||
# compute suitable tolerances
|
||||
kwargs['tol'], rtol, atol = _get_test_tolerance(typ, mattype)
|
||||
|
||||
# on rare occasions, ARPACK routines return results that are proper
|
||||
# eigenvalues and -vectors, but not necessarily the ones requested in
|
||||
# the parameter which. This is inherent to the Krylov methods, and
|
||||
# should not be treated as a failure. If such a rare situation
|
||||
# occurs, the calculation is tried again (but at most a few times).
|
||||
ntries = 0
|
||||
while ntries < 5:
|
||||
# solve
|
||||
if general:
|
||||
try:
|
||||
eval, evec = eigs_func(ac, k, bc, **kwargs)
|
||||
except ArpackNoConvergence:
|
||||
kwargs['maxiter'] = 20*a.shape[0]
|
||||
eval, evec = eigs_func(ac, k, bc, **kwargs)
|
||||
else:
|
||||
try:
|
||||
eval, evec = eigs_func(ac, k, **kwargs)
|
||||
except ArpackNoConvergence:
|
||||
kwargs['maxiter'] = 20*a.shape[0]
|
||||
eval, evec = eigs_func(ac, k, **kwargs)
|
||||
|
||||
ind = argsort_which(eval, typ, k, which,
|
||||
sigma, OPpart, mode)
|
||||
eval = eval[ind]
|
||||
evec = evec[:,ind]
|
||||
|
||||
# check eigenvectors
|
||||
LHS = np.dot(a, evec)
|
||||
if general:
|
||||
RHS = eval * np.dot(b, evec)
|
||||
else:
|
||||
RHS = eval * evec
|
||||
|
||||
assert_allclose(LHS, RHS, rtol=rtol, atol=atol, err_msg=err)
|
||||
|
||||
try:
|
||||
# check eigenvalues
|
||||
assert_allclose_cc(eval, exact_eval, rtol=rtol, atol=atol,
|
||||
err_msg=err)
|
||||
break
|
||||
except AssertionError:
|
||||
ntries += 1
|
||||
|
||||
# check eigenvalues
|
||||
assert_allclose_cc(eval, exact_eval, rtol=rtol, atol=atol, err_msg=err)
|
||||
|
||||
|
||||
class DictWithRepr(dict):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s>" % self.name
|
||||
|
||||
|
||||
class SymmetricParams:
|
||||
def __init__(self):
|
||||
self.eigs = eigsh
|
||||
self.which = ['LM', 'SM', 'LA', 'SA', 'BE']
|
||||
self.mattypes = [csr_matrix, aslinearoperator, np.asarray]
|
||||
self.sigmas_modes = {None: ['normal'],
|
||||
0.5: ['normal', 'buckling', 'cayley']}
|
||||
|
||||
# generate matrices
|
||||
# these should all be float32 so that the eigenvalues
|
||||
# are the same in float32 and float64
|
||||
N = 6
|
||||
np.random.seed(2300)
|
||||
Ar = generate_matrix(N, hermitian=True,
|
||||
pos_definite=True).astype('f').astype('d')
|
||||
M = generate_matrix(N, hermitian=True,
|
||||
pos_definite=True).astype('f').astype('d')
|
||||
Ac = generate_matrix(N, hermitian=True, pos_definite=True,
|
||||
complex=True).astype('F').astype('D')
|
||||
v0 = np.random.random(N)
|
||||
|
||||
# standard symmetric problem
|
||||
SS = DictWithRepr("std-symmetric")
|
||||
SS['mat'] = Ar
|
||||
SS['v0'] = v0
|
||||
SS['eval'] = eigh(SS['mat'], eigvals_only=True)
|
||||
|
||||
# general symmetric problem
|
||||
GS = DictWithRepr("gen-symmetric")
|
||||
GS['mat'] = Ar
|
||||
GS['bmat'] = M
|
||||
GS['v0'] = v0
|
||||
GS['eval'] = eigh(GS['mat'], GS['bmat'], eigvals_only=True)
|
||||
|
||||
# standard hermitian problem
|
||||
SH = DictWithRepr("std-hermitian")
|
||||
SH['mat'] = Ac
|
||||
SH['v0'] = v0
|
||||
SH['eval'] = eigh(SH['mat'], eigvals_only=True)
|
||||
|
||||
# general hermitian problem
|
||||
GH = DictWithRepr("gen-hermitian")
|
||||
GH['mat'] = Ac
|
||||
GH['bmat'] = M
|
||||
GH['v0'] = v0
|
||||
GH['eval'] = eigh(GH['mat'], GH['bmat'], eigvals_only=True)
|
||||
|
||||
self.real_test_cases = [SS, GS]
|
||||
self.complex_test_cases = [SH, GH]
|
||||
|
||||
|
||||
class NonSymmetricParams:
|
||||
def __init__(self):
|
||||
self.eigs = eigs
|
||||
self.which = ['LM', 'LR', 'LI'] # , 'SM', 'LR', 'SR', 'LI', 'SI']
|
||||
self.mattypes = [csr_matrix, aslinearoperator, np.asarray]
|
||||
self.sigmas_OPparts = {None: [None],
|
||||
0.1: ['r'],
|
||||
0.1 + 0.1j: ['r', 'i']}
|
||||
|
||||
# generate matrices
|
||||
# these should all be float32 so that the eigenvalues
|
||||
# are the same in float32 and float64
|
||||
N = 6
|
||||
np.random.seed(2300)
|
||||
Ar = generate_matrix(N).astype('f').astype('d')
|
||||
M = generate_matrix(N, hermitian=True,
|
||||
pos_definite=True).astype('f').astype('d')
|
||||
Ac = generate_matrix(N, complex=True).astype('F').astype('D')
|
||||
v0 = np.random.random(N)
|
||||
|
||||
# standard real nonsymmetric problem
|
||||
SNR = DictWithRepr("std-real-nonsym")
|
||||
SNR['mat'] = Ar
|
||||
SNR['v0'] = v0
|
||||
SNR['eval'] = eig(SNR['mat'], left=False, right=False)
|
||||
|
||||
# general real nonsymmetric problem
|
||||
GNR = DictWithRepr("gen-real-nonsym")
|
||||
GNR['mat'] = Ar
|
||||
GNR['bmat'] = M
|
||||
GNR['v0'] = v0
|
||||
GNR['eval'] = eig(GNR['mat'], GNR['bmat'], left=False, right=False)
|
||||
|
||||
# standard complex nonsymmetric problem
|
||||
SNC = DictWithRepr("std-cmplx-nonsym")
|
||||
SNC['mat'] = Ac
|
||||
SNC['v0'] = v0
|
||||
SNC['eval'] = eig(SNC['mat'], left=False, right=False)
|
||||
|
||||
# general complex nonsymmetric problem
|
||||
GNC = DictWithRepr("gen-cmplx-nonsym")
|
||||
GNC['mat'] = Ac
|
||||
GNC['bmat'] = M
|
||||
GNC['v0'] = v0
|
||||
GNC['eval'] = eig(GNC['mat'], GNC['bmat'], left=False, right=False)
|
||||
|
||||
self.real_test_cases = [SNR, GNR]
|
||||
self.complex_test_cases = [SNC, GNC]
|
||||
|
||||
|
||||
def test_symmetric_modes():
|
||||
params = SymmetricParams()
|
||||
k = 2
|
||||
symmetric = True
|
||||
for D in params.real_test_cases:
|
||||
for typ in 'fd':
|
||||
for which in params.which:
|
||||
for mattype in params.mattypes:
|
||||
for (sigma, modes) in params.sigmas_modes.items():
|
||||
for mode in modes:
|
||||
eval_evec(symmetric, D, typ, k, which,
|
||||
None, sigma, mattype, None, mode)
|
||||
|
||||
|
||||
def test_hermitian_modes():
|
||||
params = SymmetricParams()
|
||||
k = 2
|
||||
symmetric = True
|
||||
for D in params.complex_test_cases:
|
||||
for typ in 'FD':
|
||||
for which in params.which:
|
||||
if which == 'BE':
|
||||
continue # BE invalid for complex
|
||||
for mattype in params.mattypes:
|
||||
for sigma in params.sigmas_modes:
|
||||
eval_evec(symmetric, D, typ, k, which,
|
||||
None, sigma, mattype)
|
||||
|
||||
|
||||
def test_symmetric_starting_vector():
|
||||
params = SymmetricParams()
|
||||
symmetric = True
|
||||
for k in [1, 2, 3, 4, 5]:
|
||||
for D in params.real_test_cases:
|
||||
for typ in 'fd':
|
||||
v0 = random.rand(len(D['v0'])).astype(typ)
|
||||
eval_evec(symmetric, D, typ, k, 'LM', v0)
|
||||
|
||||
|
||||
def test_symmetric_no_convergence():
|
||||
np.random.seed(1234)
|
||||
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, ncv=9)
|
||||
raise AssertionError("Spurious no-error exit")
|
||||
except ArpackNoConvergence as err:
|
||||
k = len(err.eigenvalues)
|
||||
if k <= 0:
|
||||
raise AssertionError("Spurious no-eigenvalues-found case")
|
||||
w, v = err.eigenvalues, err.eigenvectors
|
||||
assert_allclose(dot(m, v), w * v, rtol=rtol, atol=atol)
|
||||
|
||||
|
||||
def test_real_nonsymmetric_modes():
|
||||
params = NonSymmetricParams()
|
||||
k = 2
|
||||
symmetric = False
|
||||
for D in params.real_test_cases:
|
||||
for typ in 'fd':
|
||||
for which in params.which:
|
||||
for mattype in params.mattypes:
|
||||
for sigma, OPparts in params.sigmas_OPparts.items():
|
||||
for OPpart in OPparts:
|
||||
eval_evec(symmetric, D, typ, k, which,
|
||||
None, sigma, mattype, OPpart)
|
||||
|
||||
|
||||
def test_complex_nonsymmetric_modes():
|
||||
params = NonSymmetricParams()
|
||||
k = 2
|
||||
symmetric = False
|
||||
for D in params.complex_test_cases:
|
||||
for typ in 'DF':
|
||||
for which in params.which:
|
||||
for mattype in params.mattypes:
|
||||
for sigma in params.sigmas_OPparts:
|
||||
eval_evec(symmetric, D, typ, k, which,
|
||||
None, sigma, mattype)
|
||||
|
||||
|
||||
def test_standard_nonsymmetric_starting_vector():
|
||||
params = NonSymmetricParams()
|
||||
sigma = None
|
||||
symmetric = False
|
||||
for k in [1, 2, 3, 4]:
|
||||
for d in params.complex_test_cases:
|
||||
for typ in 'FD':
|
||||
A = d['mat']
|
||||
n = A.shape[0]
|
||||
v0 = random.rand(n).astype(typ)
|
||||
eval_evec(symmetric, d, typ, k, "LM", v0, sigma)
|
||||
|
||||
|
||||
def test_general_nonsymmetric_starting_vector():
|
||||
params = NonSymmetricParams()
|
||||
sigma = None
|
||||
symmetric = False
|
||||
for k in [1, 2, 3, 4]:
|
||||
for d in params.complex_test_cases:
|
||||
for typ in 'FD':
|
||||
A = d['mat']
|
||||
n = A.shape[0]
|
||||
v0 = random.rand(n).astype(typ)
|
||||
eval_evec(symmetric, d, typ, k, "LM", v0, sigma)
|
||||
|
||||
|
||||
def test_standard_nonsymmetric_no_convergence():
|
||||
np.random.seed(1234)
|
||||
m = generate_matrix(30, complex=True)
|
||||
tol, rtol, atol = _get_test_tolerance('d')
|
||||
try:
|
||||
w, v = eigs(m, 4, which='LM', v0=m[:, 0], maxiter=5, tol=tol)
|
||||
raise AssertionError("Spurious no-error exit")
|
||||
except ArpackNoConvergence as err:
|
||||
k = len(err.eigenvalues)
|
||||
if k <= 0:
|
||||
raise AssertionError("Spurious no-eigenvalues-found case")
|
||||
w, v = err.eigenvalues, err.eigenvectors
|
||||
for ww, vv in zip(w, v.T):
|
||||
assert_allclose(dot(m, vv), ww * vv, rtol=rtol, atol=atol)
|
||||
|
||||
|
||||
def test_eigen_bad_shapes():
|
||||
# A is not square.
|
||||
A = csc_matrix(np.zeros((2, 3)))
|
||||
assert_raises(ValueError, eigs, A)
|
||||
|
||||
|
||||
def test_eigen_bad_kwargs():
|
||||
# Test eigen on wrong keyword argument
|
||||
A = csc_matrix(np.zeros((8, 8)))
|
||||
assert_raises(ValueError, eigs, A, which='XX')
|
||||
|
||||
|
||||
def test_ticket_1459_arpack_crash():
|
||||
for dtype in [np.float32, np.float64]:
|
||||
# XXX: this test does not seem to catch the issue for float32,
|
||||
# but we made the same fix there, just to be sure
|
||||
|
||||
N = 6
|
||||
k = 2
|
||||
|
||||
np.random.seed(2301)
|
||||
A = np.random.random((N, N)).astype(dtype)
|
||||
v0 = np.array([-0.71063568258907849895, -0.83185111795729227424,
|
||||
-0.34365925382227402451, 0.46122533684552280420,
|
||||
-0.58001341115969040629, -0.78844877570084292984e-01],
|
||||
dtype=dtype)
|
||||
|
||||
# Should not crash:
|
||||
evals, evecs = eigs(A, k, v0=v0)
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# sparse SVD tests
|
||||
|
||||
def sorted_svd(m, k, which='LM'):
|
||||
# Compute svd of a dense matrix m, and return singular vectors/values
|
||||
# sorted.
|
||||
if isspmatrix(m):
|
||||
m = m.todense()
|
||||
u, s, vh = svd(m)
|
||||
if which == 'LM':
|
||||
ii = np.argsort(s)[-k:]
|
||||
elif which == 'SM':
|
||||
ii = np.argsort(s)[:k]
|
||||
else:
|
||||
raise ValueError("unknown which=%r" % (which,))
|
||||
|
||||
return u[:, ii], s[ii], vh[ii]
|
||||
|
||||
|
||||
def svd_estimate(u, s, vh):
|
||||
return np.dot(u, np.dot(np.diag(s), vh))
|
||||
|
||||
|
||||
def svd_test_input_check():
|
||||
x = np.array([[1, 2, 3],
|
||||
[3, 4, 3],
|
||||
[1, 0, 2],
|
||||
[0, 0, 1]], float)
|
||||
|
||||
assert_raises(ValueError, svds, x, k=-1)
|
||||
assert_raises(ValueError, svds, x, k=0)
|
||||
assert_raises(ValueError, svds, x, k=10)
|
||||
assert_raises(ValueError, svds, x, k=x.shape[0])
|
||||
assert_raises(ValueError, svds, x, k=x.shape[1])
|
||||
assert_raises(ValueError, svds, x.T, k=x.shape[0])
|
||||
assert_raises(ValueError, svds, x.T, k=x.shape[1])
|
||||
|
||||
|
||||
def test_svd_simple_real():
|
||||
x = np.array([[1, 2, 3],
|
||||
[3, 4, 3],
|
||||
[1, 0, 2],
|
||||
[0, 0, 1]], float)
|
||||
y = np.array([[1, 2, 3, 8],
|
||||
[3, 4, 3, 5],
|
||||
[1, 0, 2, 3],
|
||||
[0, 0, 1, 0]], float)
|
||||
z = csc_matrix(x)
|
||||
|
||||
for m in [x.T, x, y, z, z.T]:
|
||||
for k in range(1, min(m.shape)):
|
||||
u, s, vh = sorted_svd(m, k)
|
||||
su, ss, svh = svds(m, k)
|
||||
|
||||
m_hat = svd_estimate(u, s, vh)
|
||||
sm_hat = svd_estimate(su, ss, svh)
|
||||
|
||||
assert_array_almost_equal_nulp(m_hat, sm_hat, nulp=1000)
|
||||
|
||||
|
||||
def test_svd_simple_complex():
|
||||
x = np.array([[1, 2, 3],
|
||||
[3, 4, 3],
|
||||
[1 + 1j, 0, 2],
|
||||
[0, 0, 1]], complex)
|
||||
y = np.array([[1, 2, 3, 8 + 5j],
|
||||
[3 - 2j, 4, 3, 5],
|
||||
[1, 0, 2, 3],
|
||||
[0, 0, 1, 0]], complex)
|
||||
z = csc_matrix(x)
|
||||
|
||||
for m in [x, x.T.conjugate(), x.T, y, y.conjugate(), z, z.T]:
|
||||
for k in range(1, min(m.shape) - 1):
|
||||
u, s, vh = sorted_svd(m, k)
|
||||
su, ss, svh = svds(m, k)
|
||||
|
||||
m_hat = svd_estimate(u, s, vh)
|
||||
sm_hat = svd_estimate(su, ss, svh)
|
||||
|
||||
assert_array_almost_equal_nulp(m_hat, sm_hat, nulp=1000)
|
||||
|
||||
|
||||
def test_svd_maxiter():
|
||||
# check that maxiter works as expected
|
||||
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, ncv=3)
|
||||
# but 100 iterations should be more than enough
|
||||
u, s, vt = svds(x, 1, maxiter=100, ncv=3)
|
||||
assert_allclose(s, [1.7], atol=0.5)
|
||||
|
||||
|
||||
def test_svd_return():
|
||||
# check that the return_singular_vectors parameter works as expected
|
||||
x = hilbert(6)
|
||||
_, s, _ = sorted_svd(x, 2)
|
||||
ss = svds(x, 2, return_singular_vectors=False)
|
||||
assert_allclose(s, ss)
|
||||
|
||||
|
||||
def test_svd_which():
|
||||
# check that the which parameter works as expected
|
||||
x = hilbert(6)
|
||||
for which in ['LM', 'SM']:
|
||||
_, s, _ = sorted_svd(x, 2, which=which)
|
||||
ss = svds(x, 2, which=which, return_singular_vectors=False)
|
||||
ss.sort()
|
||||
assert_allclose(s, ss, atol=np.sqrt(1e-15))
|
||||
|
||||
|
||||
def test_svd_v0():
|
||||
# check that the v0 parameter works as expected
|
||||
x = np.array([[1, 2, 3, 4], [5, 6, 7, 8]], float)
|
||||
|
||||
u, s, vh = svds(x, 1)
|
||||
u2, s2, vh2 = svds(x, 1, v0=u[:,0])
|
||||
|
||||
assert_allclose(s, s2, atol=np.sqrt(1e-15))
|
||||
|
||||
|
||||
def _check_svds(A, k, U, s, VH):
|
||||
n, m = A.shape
|
||||
|
||||
# Check shapes.
|
||||
assert_equal(U.shape, (n, k))
|
||||
assert_equal(s.shape, (k,))
|
||||
assert_equal(VH.shape, (k, m))
|
||||
|
||||
# Check that the original matrix can be reconstituted.
|
||||
A_rebuilt = (U*s).dot(VH)
|
||||
assert_equal(A_rebuilt.shape, A.shape)
|
||||
assert_allclose(A_rebuilt, A)
|
||||
|
||||
# Check that U is a semi-orthogonal matrix.
|
||||
UH_U = np.dot(U.T.conj(), U)
|
||||
assert_equal(UH_U.shape, (k, k))
|
||||
assert_allclose(UH_U, np.identity(k), atol=1e-12)
|
||||
|
||||
# Check that V is a semi-orthogonal matrix.
|
||||
VH_V = np.dot(VH, VH.T.conj())
|
||||
assert_equal(VH_V.shape, (k, k))
|
||||
assert_allclose(VH_V, np.identity(k), atol=1e-12)
|
||||
|
||||
|
||||
def test_svd_LM_ones_matrix():
|
||||
# Check that svds can deal with matrix_rank less than k in LM mode.
|
||||
k = 3
|
||||
for n, m in (6, 5), (5, 5), (5, 6):
|
||||
for t in float, complex:
|
||||
A = np.ones((n, m), dtype=t)
|
||||
U, s, VH = svds(A, k)
|
||||
|
||||
# Check some generic properties of svd.
|
||||
_check_svds(A, k, U, s, VH)
|
||||
|
||||
# Check that the largest singular value is near sqrt(n*m)
|
||||
# and the other singular values have been forced to zero.
|
||||
assert_allclose(np.max(s), np.sqrt(n*m))
|
||||
assert_array_equal(sorted(s)[:-1], 0)
|
||||
|
||||
|
||||
def test_svd_LM_zeros_matrix():
|
||||
# Check that svds can deal with matrices containing only zeros.
|
||||
k = 1
|
||||
for n, m in (3, 4), (4, 4), (4, 3):
|
||||
for t in float, complex:
|
||||
A = np.zeros((n, m), dtype=t)
|
||||
U, s, VH = svds(A, k)
|
||||
|
||||
# Check some generic properties of svd.
|
||||
_check_svds(A, k, U, s, VH)
|
||||
|
||||
# Check that the singular values are zero.
|
||||
assert_array_equal(s, 0)
|
||||
|
||||
|
||||
def test_svd_LM_zeros_matrix_gh_3452():
|
||||
# Regression test for a github issue.
|
||||
# https://github.com/scipy/scipy/issues/3452
|
||||
# Note that for complex dype the size of this matrix is too small for k=1.
|
||||
n, m, k = 4, 2, 1
|
||||
A = np.zeros((n, m))
|
||||
U, s, VH = svds(A, k)
|
||||
|
||||
# Check some generic properties of svd.
|
||||
_check_svds(A, k, U, s, VH)
|
||||
|
||||
# Check that the singular values are zero.
|
||||
assert_array_equal(s, 0)
|
||||
|
||||
|
||||
class CheckingLinearOperator(LinearOperator):
|
||||
def __init__(self, A):
|
||||
self.A = A
|
||||
self.dtype = A.dtype
|
||||
self.shape = A.shape
|
||||
|
||||
def _matvec(self, x):
|
||||
assert_equal(max(x.shape), np.size(x))
|
||||
return self.A.dot(x)
|
||||
|
||||
def _rmatvec(self, x):
|
||||
assert_equal(max(x.shape), np.size(x))
|
||||
return self.A.T.conjugate().dot(x)
|
||||
|
||||
|
||||
def test_svd_linop():
|
||||
nmks = [(6, 7, 3),
|
||||
(9, 5, 4),
|
||||
(10, 8, 5)]
|
||||
|
||||
def reorder(args):
|
||||
U, s, VH = args
|
||||
j = np.argsort(s)
|
||||
return U[:,j], s[j], VH[j,:]
|
||||
|
||||
for n, m, k in nmks:
|
||||
# Test svds on a LinearOperator.
|
||||
A = np.random.RandomState(52).randn(n, m)
|
||||
L = CheckingLinearOperator(A)
|
||||
|
||||
v0 = np.ones(min(A.shape))
|
||||
|
||||
U1, s1, VH1 = reorder(svds(A, k, v0=v0))
|
||||
U2, s2, VH2 = reorder(svds(L, k, v0=v0))
|
||||
|
||||
assert_allclose(np.abs(U1), np.abs(U2))
|
||||
assert_allclose(s1, s2)
|
||||
assert_allclose(np.abs(VH1), np.abs(VH2))
|
||||
assert_allclose(np.dot(U1, np.dot(np.diag(s1), VH1)),
|
||||
np.dot(U2, np.dot(np.diag(s2), VH2)))
|
||||
|
||||
# Try again with which="SM".
|
||||
A = np.random.RandomState(1909).randn(n, m)
|
||||
L = CheckingLinearOperator(A)
|
||||
|
||||
U1, s1, VH1 = reorder(svds(A, k, which="SM"))
|
||||
U2, s2, VH2 = reorder(svds(L, k, which="SM"))
|
||||
|
||||
assert_allclose(np.abs(U1), np.abs(U2))
|
||||
assert_allclose(s1, s2)
|
||||
assert_allclose(np.abs(VH1), np.abs(VH2))
|
||||
assert_allclose(np.dot(U1, np.dot(np.diag(s1), VH1)),
|
||||
np.dot(U2, np.dot(np.diag(s2), VH2)))
|
||||
|
||||
if k < min(n, m) - 1:
|
||||
# Complex input and explicit which="LM".
|
||||
for (dt, eps) in [(complex, 1e-7), (np.complex64, 1e-3)]:
|
||||
rng = np.random.RandomState(1648)
|
||||
A = (rng.randn(n, m) + 1j * rng.randn(n, m)).astype(dt)
|
||||
L = CheckingLinearOperator(A)
|
||||
|
||||
U1, s1, VH1 = reorder(svds(A, k, which="LM"))
|
||||
U2, s2, VH2 = reorder(svds(L, k, which="LM"))
|
||||
|
||||
assert_allclose(np.abs(U1), np.abs(U2), rtol=eps)
|
||||
assert_allclose(s1, s2, rtol=eps)
|
||||
assert_allclose(np.abs(VH1), np.abs(VH2), rtol=eps)
|
||||
assert_allclose(np.dot(U1, np.dot(np.diag(s1), VH1)),
|
||||
np.dot(U2, np.dot(np.diag(s2), VH2)), rtol=eps)
|
||||
|
||||
|
||||
@pytest.mark.skipif(IS_PYPY, reason="Test not meaningful on PyPy")
|
||||
def test_linearoperator_deallocation():
|
||||
# Check that the linear operators used by the Arpack wrappers are
|
||||
# deallocatable by reference counting -- they are big objects, so
|
||||
# Python's cyclic GC may not collect them fast enough before
|
||||
# running out of memory if eigs/eigsh are called in a tight loop.
|
||||
|
||||
M_d = np.eye(10)
|
||||
M_s = csc_matrix(M_d)
|
||||
M_o = aslinearoperator(M_d)
|
||||
|
||||
with assert_deallocated(lambda: arpack.SpLuInv(M_s)):
|
||||
pass
|
||||
with assert_deallocated(lambda: arpack.LuInv(M_d)):
|
||||
pass
|
||||
with assert_deallocated(lambda: arpack.IterInv(M_s)):
|
||||
pass
|
||||
with assert_deallocated(lambda: arpack.IterOpInv(M_o, None, 0.3)):
|
||||
pass
|
||||
with assert_deallocated(lambda: arpack.IterOpInv(M_o, M_o, 0.3)):
|
||||
pass
|
||||
|
||||
|
||||
def test_svds_partial_return():
|
||||
x = np.array([[1, 2, 3],
|
||||
[3, 4, 3],
|
||||
[1, 0, 2],
|
||||
[0, 0, 1]], float)
|
||||
# test vertical matrix
|
||||
z = csr_matrix(x)
|
||||
vh_full = svds(z, 2)[-1]
|
||||
vh_partial = svds(z, 2, return_singular_vectors='vh')[-1]
|
||||
dvh = np.linalg.norm(np.abs(vh_full) - np.abs(vh_partial))
|
||||
if dvh > 1e-10:
|
||||
raise AssertionError('right eigenvector matrices differ when using return_singular_vectors parameter')
|
||||
if svds(z, 2, return_singular_vectors='vh')[0] is not None:
|
||||
raise AssertionError('left eigenvector matrix was computed when it should not have been')
|
||||
# test horizontal matrix
|
||||
z = csr_matrix(x.T)
|
||||
u_full = svds(z, 2)[0]
|
||||
u_partial = svds(z, 2, return_singular_vectors='vh')[0]
|
||||
du = np.linalg.norm(np.abs(u_full) - np.abs(u_partial))
|
||||
if du > 1e-10:
|
||||
raise AssertionError('left eigenvector matrices differ when using return_singular_vectors parameter')
|
||||
if svds(z, 2, return_singular_vectors='u')[-1] is not None:
|
||||
raise AssertionError('right eigenvector matrix was computed when it should not have been')
|
||||
|
||||
def test_svds_wrong_eigen_type():
|
||||
# Regression test for a github issue.
|
||||
# https://github.com/scipy/scipy/issues/4590
|
||||
# Function was not checking for eigenvalue type and unintended
|
||||
# values could be returned.
|
||||
x = np.array([[1, 2, 3],
|
||||
[3, 4, 3],
|
||||
[1, 0, 2],
|
||||
[0, 0, 1]], float)
|
||||
assert_raises(ValueError, svds, x, 1, which='LA')
|
||||
|
||||
|
||||
def test_parallel_threads():
|
||||
results = []
|
||||
v0 = np.random.rand(50)
|
||||
|
||||
def worker():
|
||||
x = diags([1, -2, 1], [-1, 0, 1], shape=(50, 50))
|
||||
w, v = eigs(x, k=3, v0=v0)
|
||||
results.append(w)
|
||||
|
||||
w, v = eigsh(x, k=3, v0=v0)
|
||||
results.append(w)
|
||||
|
||||
threads = [threading.Thread(target=worker) for k in range(10)]
|
||||
for t in threads:
|
||||
t.start()
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
worker()
|
||||
|
||||
for r in results:
|
||||
assert_allclose(r, results[-1])
|
||||
|
||||
|
||||
def test_reentering():
|
||||
# Just some linear operator that calls eigs recursively
|
||||
def A_matvec(x):
|
||||
x = diags([1, -2, 1], [-1, 0, 1], shape=(50, 50))
|
||||
w, v = eigs(x, k=1)
|
||||
return v / w[0]
|
||||
A = LinearOperator(matvec=A_matvec, dtype=float, shape=(50, 50))
|
||||
|
||||
# The Fortran code is not reentrant, so this fails (gracefully, not crashing)
|
||||
assert_raises(RuntimeError, eigs, A, k=1)
|
||||
assert_raises(RuntimeError, eigsh, A, k=1)
|
||||
|
||||
|
||||
def test_regression_arpackng_1315():
|
||||
# Check that issue arpack-ng/#1315 is not present.
|
||||
# Adapted from arpack-ng/TESTS/bug_1315_single.c
|
||||
# If this fails, then the installed ARPACK library is faulty.
|
||||
|
||||
for dtype in [np.float32, np.float64]:
|
||||
np.random.seed(1234)
|
||||
|
||||
w0 = np.arange(1, 1000+1).astype(dtype)
|
||||
A = diags([w0], [0], shape=(1000, 1000))
|
||||
|
||||
v0 = np.random.rand(1000).astype(dtype)
|
||||
w, v = eigs(A, k=9, ncv=2*9+1, which="LM", v0=v0)
|
||||
|
||||
assert_allclose(np.sort(w), np.sort(w0[-9:]),
|
||||
rtol=1e-4)
|
||||
|
||||
|
||||
def test_eigs_for_k_greater():
|
||||
# Test eigs() for k beyond limits.
|
||||
A_sparse = diags([1, -2, 1], [-1, 0, 1], shape=(4, 4)) # sparse
|
||||
A = generate_matrix(4, sparse=False)
|
||||
M_dense = np.random.random((4, 4))
|
||||
M_sparse = generate_matrix(4, sparse=True)
|
||||
M_linop = aslinearoperator(M_dense)
|
||||
eig_tuple1 = eig(A, b=M_dense)
|
||||
eig_tuple2 = eig(A, b=M_sparse)
|
||||
|
||||
with suppress_warnings() as sup:
|
||||
sup.filter(RuntimeWarning)
|
||||
|
||||
assert_equal(eigs(A, M=M_dense, k=3), eig_tuple1)
|
||||
assert_equal(eigs(A, M=M_dense, k=4), eig_tuple1)
|
||||
assert_equal(eigs(A, M=M_dense, k=5), eig_tuple1)
|
||||
assert_equal(eigs(A, M=M_sparse, k=5), eig_tuple2)
|
||||
|
||||
# M as LinearOperator
|
||||
assert_raises(TypeError, eigs, A, M=M_linop, k=3)
|
||||
|
||||
# Test 'A' for different types
|
||||
assert_raises(TypeError, eigs, aslinearoperator(A), k=3)
|
||||
assert_raises(TypeError, eigs, A_sparse, k=3)
|
||||
|
||||
|
||||
def test_eigsh_for_k_greater():
|
||||
# Test eigsh() for k beyond limits.
|
||||
A_sparse = diags([1, -2, 1], [-1, 0, 1], shape=(4, 4)) # sparse
|
||||
A = generate_matrix(4, sparse=False)
|
||||
M_dense = generate_matrix_symmetric(4, pos_definite=True)
|
||||
M_sparse = generate_matrix_symmetric(4, pos_definite=True, sparse=True)
|
||||
M_linop = aslinearoperator(M_dense)
|
||||
eig_tuple1 = eigh(A, b=M_dense)
|
||||
eig_tuple2 = eigh(A, b=M_sparse)
|
||||
|
||||
with suppress_warnings() as sup:
|
||||
sup.filter(RuntimeWarning)
|
||||
|
||||
assert_equal(eigsh(A, M=M_dense, k=4), eig_tuple1)
|
||||
assert_equal(eigsh(A, M=M_dense, k=5), eig_tuple1)
|
||||
assert_equal(eigsh(A, M=M_sparse, k=5), eig_tuple2)
|
||||
|
||||
# M as LinearOperator
|
||||
assert_raises(TypeError, eigsh, A, M=M_linop, k=4)
|
||||
|
||||
# Test 'A' for different types
|
||||
assert_raises(TypeError, eigsh, aslinearoperator(A), k=4)
|
||||
assert_raises(TypeError, eigsh, A_sparse, M=M_dense, k=4)
|
||||
@@ -1,18 +0,0 @@
|
||||
"""
|
||||
Locally Optimal Block Preconditioned Conjugate Gradient Method (LOBPCG)
|
||||
|
||||
LOBPCG is a preconditioned eigensolver for large symmetric positive definite
|
||||
(SPD) generalized eigenproblems.
|
||||
|
||||
Call the function lobpcg - see help for lobpcg.lobpcg.
|
||||
|
||||
"""
|
||||
from __future__ import division, print_function, absolute_import
|
||||
|
||||
from .lobpcg import *
|
||||
|
||||
__all__ = [s for s in dir() if not s.startswith('_')]
|
||||
|
||||
from scipy._lib._testutils import PytestTester
|
||||
test = PytestTester(__name__)
|
||||
del PytestTester
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -1,582 +0,0 @@
|
||||
"""
|
||||
Pure SciPy implementation of Locally Optimal Block Preconditioned Conjugate
|
||||
Gradient Method (LOBPCG), see
|
||||
https://bitbucket.org/joseroman/blopex
|
||||
|
||||
License: BSD
|
||||
|
||||
Authors: Robert Cimrman, Andrew Knyazev
|
||||
|
||||
Examples in tests directory contributed by Nils Wagner.
|
||||
"""
|
||||
|
||||
from __future__ import division, print_function, absolute_import
|
||||
import numpy as np
|
||||
from numpy.testing import assert_allclose
|
||||
from scipy._lib.six import xrange
|
||||
from scipy.linalg import inv, eigh, cho_factor, cho_solve, cholesky
|
||||
from scipy.sparse.linalg import aslinearoperator, LinearOperator
|
||||
|
||||
__all__ = ['lobpcg']
|
||||
|
||||
|
||||
def save(ar, fileName):
|
||||
# Used only when verbosity level > 10.
|
||||
from numpy import savetxt
|
||||
savetxt(fileName, ar)
|
||||
|
||||
|
||||
def _report_nonhermitian(M, a, b, name):
|
||||
"""
|
||||
Report if `M` is not a hermitian matrix given the tolerances `a`, `b`.
|
||||
"""
|
||||
from scipy.linalg import norm
|
||||
|
||||
md = M - M.T.conj()
|
||||
|
||||
nmd = norm(md, 1)
|
||||
tol = np.spacing(max(10**a, (10**b)*norm(M, 1)))
|
||||
if nmd > tol:
|
||||
print('matrix %s is not sufficiently Hermitian for a=%d, b=%d:'
|
||||
% (name, a, b))
|
||||
print('condition: %.e < %e' % (nmd, tol))
|
||||
|
||||
##
|
||||
# 21.05.2007, c
|
||||
|
||||
|
||||
def as2d(ar):
|
||||
"""
|
||||
If the input array is 2D return it, if it is 1D, append a dimension,
|
||||
making it a column vector.
|
||||
"""
|
||||
if ar.ndim == 2:
|
||||
return ar
|
||||
else: # Assume 1!
|
||||
aux = np.array(ar, copy=False)
|
||||
aux.shape = (ar.shape[0], 1)
|
||||
return aux
|
||||
|
||||
|
||||
def _makeOperator(operatorInput, expectedShape):
|
||||
"""Takes a dense numpy array or a sparse matrix or
|
||||
a function and makes an operator performing matrix * blockvector
|
||||
products.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> A = _makeOperator( arrayA, (n, n) )
|
||||
>>> vectorB = A( vectorX )
|
||||
|
||||
"""
|
||||
if operatorInput is None:
|
||||
def ident(x):
|
||||
return x
|
||||
operator = LinearOperator(expectedShape, ident, matmat=ident)
|
||||
else:
|
||||
operator = aslinearoperator(operatorInput)
|
||||
|
||||
if operator.shape != expectedShape:
|
||||
raise ValueError('operator has invalid shape')
|
||||
|
||||
return operator
|
||||
|
||||
|
||||
def _applyConstraints(blockVectorV, factYBY, blockVectorBY, blockVectorY):
|
||||
"""Changes blockVectorV in place."""
|
||||
gramYBV = np.dot(blockVectorBY.T.conj(), blockVectorV)
|
||||
tmp = cho_solve(factYBY, gramYBV)
|
||||
blockVectorV -= np.dot(blockVectorY, tmp)
|
||||
|
||||
|
||||
def _b_orthonormalize(B, blockVectorV, blockVectorBV=None, retInvR=False):
|
||||
if blockVectorBV is None:
|
||||
if B is not None:
|
||||
blockVectorBV = B(blockVectorV)
|
||||
else:
|
||||
blockVectorBV = blockVectorV # Shared data!!!
|
||||
gramVBV = np.dot(blockVectorV.T.conj(), blockVectorBV)
|
||||
gramVBV = cholesky(gramVBV)
|
||||
gramVBV = inv(gramVBV, overwrite_a=True)
|
||||
# gramVBV is now R^{-1}.
|
||||
blockVectorV = np.dot(blockVectorV, gramVBV)
|
||||
if B is not None:
|
||||
blockVectorBV = np.dot(blockVectorBV, gramVBV)
|
||||
|
||||
if retInvR:
|
||||
return blockVectorV, blockVectorBV, gramVBV
|
||||
else:
|
||||
return blockVectorV, blockVectorBV
|
||||
|
||||
def _get_indx(_lambda, num, largest):
|
||||
"""Get `num` indices into `_lambda` depending on `largest` option."""
|
||||
ii = np.argsort(_lambda)
|
||||
if largest:
|
||||
ii = ii[:-num-1:-1]
|
||||
else:
|
||||
ii = ii[:num]
|
||||
|
||||
return ii
|
||||
|
||||
def lobpcg(A, X,
|
||||
B=None, M=None, Y=None,
|
||||
tol=None, maxiter=20,
|
||||
largest=True, verbosityLevel=0,
|
||||
retLambdaHistory=False, retResidualNormsHistory=False):
|
||||
"""Locally Optimal Block Preconditioned Conjugate Gradient Method (LOBPCG)
|
||||
|
||||
LOBPCG is a preconditioned eigensolver for large symmetric positive
|
||||
definite (SPD) generalized eigenproblems.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
A : {sparse matrix, dense matrix, LinearOperator}
|
||||
The symmetric linear operator of the problem, usually a
|
||||
sparse matrix. Often called the "stiffness matrix".
|
||||
X : array_like
|
||||
Initial approximation to the k eigenvectors. If A has
|
||||
shape=(n,n) then X should have shape shape=(n,k).
|
||||
B : {dense matrix, sparse matrix, LinearOperator}, optional
|
||||
the right hand side operator in a generalized eigenproblem.
|
||||
by default, B = Identity
|
||||
often called the "mass matrix"
|
||||
M : {dense matrix, sparse matrix, LinearOperator}, optional
|
||||
preconditioner to A; by default M = Identity
|
||||
M should approximate the inverse of A
|
||||
Y : array_like, optional
|
||||
n-by-sizeY matrix of constraints, sizeY < n
|
||||
The iterations will be performed in the B-orthogonal complement
|
||||
of the column-space of Y. Y must be full rank.
|
||||
|
||||
Returns
|
||||
-------
|
||||
w : array
|
||||
Array of k eigenvalues
|
||||
v : array
|
||||
An array of k eigenvectors. V has the same shape as X.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
tol : scalar, optional
|
||||
Solver tolerance (stopping criterion)
|
||||
by default: tol=n*sqrt(eps)
|
||||
maxiter : integer, optional
|
||||
maximum number of iterations
|
||||
by default: maxiter=min(n,20)
|
||||
largest : bool, optional
|
||||
when True, solve for the largest eigenvalues, otherwise the smallest
|
||||
verbosityLevel : integer, optional
|
||||
controls solver output. default: verbosityLevel = 0.
|
||||
retLambdaHistory : boolean, optional
|
||||
whether to return eigenvalue history
|
||||
retResidualNormsHistory : boolean, optional
|
||||
whether to return history of residual norms
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Solve A x = lambda B x with constraints and preconditioning.
|
||||
|
||||
>>> from scipy.sparse import spdiags, issparse
|
||||
>>> from scipy.sparse.linalg import lobpcg, LinearOperator
|
||||
>>> n = 100
|
||||
>>> vals = [np.arange(n, dtype=np.float64) + 1]
|
||||
>>> A = spdiags(vals, 0, n, n)
|
||||
>>> A.toarray()
|
||||
array([[ 1., 0., 0., ..., 0., 0., 0.],
|
||||
[ 0., 2., 0., ..., 0., 0., 0.],
|
||||
[ 0., 0., 3., ..., 0., 0., 0.],
|
||||
...,
|
||||
[ 0., 0., 0., ..., 98., 0., 0.],
|
||||
[ 0., 0., 0., ..., 0., 99., 0.],
|
||||
[ 0., 0., 0., ..., 0., 0., 100.]])
|
||||
|
||||
Constraints.
|
||||
|
||||
>>> Y = np.eye(n, 3)
|
||||
|
||||
Initial guess for eigenvectors, should have linearly independent
|
||||
columns. Column dimension = number of requested eigenvalues.
|
||||
|
||||
>>> X = np.random.rand(n, 3)
|
||||
|
||||
Preconditioner -- inverse of A (as an abstract linear operator).
|
||||
|
||||
>>> invA = spdiags([1./vals[0]], 0, n, n)
|
||||
>>> def precond( x ):
|
||||
... return invA * x
|
||||
>>> M = LinearOperator(matvec=precond, shape=(n, n), dtype=float)
|
||||
|
||||
Here, ``invA`` could of course have been used directly as a preconditioner.
|
||||
Let us then solve the problem:
|
||||
|
||||
>>> eigs, vecs = lobpcg(A, X, Y=Y, M=M, tol=1e-4, maxiter=40, largest=False)
|
||||
>>> eigs
|
||||
array([ 4., 5., 6.])
|
||||
|
||||
Note that the vectors passed in Y are the eigenvectors of the 3 smallest
|
||||
eigenvalues. The results returned are orthogonal to those.
|
||||
|
||||
Notes
|
||||
-----
|
||||
If both retLambdaHistory and retResidualNormsHistory are True,
|
||||
the return tuple has the following format
|
||||
(lambda, V, lambda history, residual norms history).
|
||||
|
||||
In the following ``n`` denotes the matrix size and ``m`` the number
|
||||
of required eigenvalues (smallest or largest).
|
||||
|
||||
The LOBPCG code internally solves eigenproblems of the size 3``m`` on every
|
||||
iteration by calling the "standard" dense eigensolver, so if ``m`` is not
|
||||
small enough compared to ``n``, it does not make sense to call the LOBPCG
|
||||
code, but rather one should use the "standard" eigensolver,
|
||||
e.g. numpy or scipy function in this case.
|
||||
If one calls the LOBPCG algorithm for 5``m``>``n``,
|
||||
it will most likely break internally, so the code tries to call the standard
|
||||
function instead.
|
||||
|
||||
It is not that n should be large for the LOBPCG to work, but rather the
|
||||
ratio ``n``/``m`` should be large. It you call the LOBPCG code with ``m``=1
|
||||
and ``n``=10, it should work, though ``n`` is small. The method is intended
|
||||
for extremely large ``n``/``m``, see e.g., reference [28] in
|
||||
https://arxiv.org/abs/0705.2626
|
||||
|
||||
The convergence speed depends basically on two factors:
|
||||
|
||||
1. How well relatively separated the seeking eigenvalues are
|
||||
from the rest of the eigenvalues.
|
||||
One can try to vary ``m`` to make this better.
|
||||
|
||||
2. How well conditioned the problem is. This can be changed by using proper
|
||||
preconditioning. For example, a rod vibration test problem (under tests
|
||||
directory) is ill-conditioned for large ``n``, so convergence will be
|
||||
slow, unless efficient preconditioning is used.
|
||||
For this specific problem, a good simple preconditioner function would
|
||||
be a linear solve for A, which is easy to code since A is tridiagonal.
|
||||
|
||||
*Acknowledgements*
|
||||
|
||||
lobpcg.py code was written by Robert Cimrman.
|
||||
Many thanks belong to Andrew Knyazev, the author of the algorithm,
|
||||
for lots of advice and support.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] A. V. Knyazev (2001),
|
||||
Toward the Optimal Preconditioned Eigensolver: Locally Optimal
|
||||
Block Preconditioned Conjugate Gradient Method.
|
||||
SIAM Journal on Scientific Computing 23, no. 2,
|
||||
pp. 517-541. :doi:`10.1137/S1064827500366124`
|
||||
|
||||
.. [2] A. V. Knyazev, I. Lashuk, M. E. Argentati, and E. Ovchinnikov (2007),
|
||||
Block Locally Optimal Preconditioned Eigenvalue Xolvers (BLOPEX)
|
||||
in hypre and PETSc. https://arxiv.org/abs/0705.2626
|
||||
|
||||
.. [3] A. V. Knyazev's C and MATLAB implementations:
|
||||
https://bitbucket.org/joseroman/blopex
|
||||
|
||||
"""
|
||||
blockVectorX = X
|
||||
blockVectorY = Y
|
||||
residualTolerance = tol
|
||||
maxIterations = maxiter
|
||||
|
||||
if blockVectorY is not None:
|
||||
sizeY = blockVectorY.shape[1]
|
||||
else:
|
||||
sizeY = 0
|
||||
|
||||
# Block size.
|
||||
if len(blockVectorX.shape) != 2:
|
||||
raise ValueError('expected rank-2 array for argument X')
|
||||
|
||||
n, sizeX = blockVectorX.shape
|
||||
if sizeX > n:
|
||||
raise ValueError('X column dimension exceeds the row dimension')
|
||||
|
||||
A = _makeOperator(A, (n,n))
|
||||
B = _makeOperator(B, (n,n))
|
||||
M = _makeOperator(M, (n,n))
|
||||
|
||||
if (n - sizeY) < (5 * sizeX):
|
||||
# warn('The problem size is small compared to the block size.' \
|
||||
# ' Using dense eigensolver instead of LOBPCG.')
|
||||
|
||||
if blockVectorY is not None:
|
||||
raise NotImplementedError('The dense eigensolver '
|
||||
'does not support constraints.')
|
||||
|
||||
# Define the closed range of indices of eigenvalues to return.
|
||||
if largest:
|
||||
eigvals = (n - sizeX, n-1)
|
||||
else:
|
||||
eigvals = (0, sizeX-1)
|
||||
|
||||
A_dense = A(np.eye(n))
|
||||
B_dense = None if B is None else B(np.eye(n))
|
||||
|
||||
vals, vecs = eigh(A_dense, B_dense, eigvals=eigvals, check_finite=False)
|
||||
if largest:
|
||||
# Reverse order to be compatible with eigs() in 'LM' mode.
|
||||
vals = vals[::-1]
|
||||
vecs = vecs[:, ::-1]
|
||||
|
||||
return vals, vecs
|
||||
|
||||
if residualTolerance is None:
|
||||
residualTolerance = np.sqrt(1e-15) * n
|
||||
|
||||
maxIterations = min(n, maxIterations)
|
||||
|
||||
if verbosityLevel:
|
||||
aux = "Solving "
|
||||
if B is None:
|
||||
aux += "standard"
|
||||
else:
|
||||
aux += "generalized"
|
||||
aux += " eigenvalue problem with"
|
||||
if M is None:
|
||||
aux += "out"
|
||||
aux += " preconditioning\n\n"
|
||||
aux += "matrix size %d\n" % n
|
||||
aux += "block size %d\n\n" % sizeX
|
||||
if blockVectorY is None:
|
||||
aux += "No constraints\n\n"
|
||||
else:
|
||||
if sizeY > 1:
|
||||
aux += "%d constraints\n\n" % sizeY
|
||||
else:
|
||||
aux += "%d constraint\n\n" % sizeY
|
||||
print(aux)
|
||||
|
||||
##
|
||||
# Apply constraints to X.
|
||||
if blockVectorY is not None:
|
||||
|
||||
if B is not None:
|
||||
blockVectorBY = B(blockVectorY)
|
||||
else:
|
||||
blockVectorBY = blockVectorY
|
||||
|
||||
# gramYBY is a dense array.
|
||||
gramYBY = np.dot(blockVectorY.T.conj(), blockVectorBY)
|
||||
try:
|
||||
# gramYBY is a Cholesky factor from now on...
|
||||
gramYBY = cho_factor(gramYBY)
|
||||
except Exception:
|
||||
raise ValueError('cannot handle linearly dependent constraints')
|
||||
|
||||
_applyConstraints(blockVectorX, gramYBY, blockVectorBY, blockVectorY)
|
||||
|
||||
##
|
||||
# B-orthonormalize X.
|
||||
blockVectorX, blockVectorBX = _b_orthonormalize(B, blockVectorX)
|
||||
|
||||
##
|
||||
# Compute the initial Ritz vectors: solve the eigenproblem.
|
||||
blockVectorAX = A(blockVectorX)
|
||||
gramXAX = np.dot(blockVectorX.T.conj(), blockVectorAX)
|
||||
|
||||
_lambda, eigBlockVector = eigh(gramXAX, check_finite=False)
|
||||
ii = _get_indx(_lambda, sizeX, largest)
|
||||
_lambda = _lambda[ii]
|
||||
|
||||
eigBlockVector = np.asarray(eigBlockVector[:,ii])
|
||||
blockVectorX = np.dot(blockVectorX, eigBlockVector)
|
||||
blockVectorAX = np.dot(blockVectorAX, eigBlockVector)
|
||||
if B is not None:
|
||||
blockVectorBX = np.dot(blockVectorBX, eigBlockVector)
|
||||
|
||||
##
|
||||
# Active index set.
|
||||
activeMask = np.ones((sizeX,), dtype=bool)
|
||||
|
||||
lambdaHistory = [_lambda]
|
||||
residualNormsHistory = []
|
||||
|
||||
previousBlockSize = sizeX
|
||||
ident = np.eye(sizeX, dtype=A.dtype)
|
||||
ident0 = np.eye(sizeX, dtype=A.dtype)
|
||||
|
||||
##
|
||||
# Main iteration loop.
|
||||
|
||||
blockVectorP = None # set during iteration
|
||||
blockVectorAP = None
|
||||
blockVectorBP = None
|
||||
|
||||
for iterationNumber in xrange(maxIterations):
|
||||
if verbosityLevel > 0:
|
||||
print('iteration %d' % iterationNumber)
|
||||
|
||||
aux = blockVectorBX * _lambda[np.newaxis,:]
|
||||
blockVectorR = blockVectorAX - aux
|
||||
|
||||
aux = np.sum(blockVectorR.conjugate() * blockVectorR, 0)
|
||||
residualNorms = np.sqrt(aux)
|
||||
|
||||
residualNormsHistory.append(residualNorms)
|
||||
|
||||
ii = np.where(residualNorms > residualTolerance, True, False)
|
||||
activeMask = activeMask & ii
|
||||
if verbosityLevel > 2:
|
||||
print(activeMask)
|
||||
|
||||
currentBlockSize = activeMask.sum()
|
||||
if currentBlockSize != previousBlockSize:
|
||||
previousBlockSize = currentBlockSize
|
||||
ident = np.eye(currentBlockSize, dtype=A.dtype)
|
||||
|
||||
if currentBlockSize == 0:
|
||||
break
|
||||
|
||||
if verbosityLevel > 0:
|
||||
print('current block size:', currentBlockSize)
|
||||
print('eigenvalue:', _lambda)
|
||||
print('residual norms:', residualNorms)
|
||||
if verbosityLevel > 10:
|
||||
print(eigBlockVector)
|
||||
|
||||
activeBlockVectorR = as2d(blockVectorR[:,activeMask])
|
||||
|
||||
if iterationNumber > 0:
|
||||
activeBlockVectorP = as2d(blockVectorP[:,activeMask])
|
||||
activeBlockVectorAP = as2d(blockVectorAP[:,activeMask])
|
||||
activeBlockVectorBP = as2d(blockVectorBP[:,activeMask])
|
||||
|
||||
if M is not None:
|
||||
# Apply preconditioner T to the active residuals.
|
||||
activeBlockVectorR = M(activeBlockVectorR)
|
||||
|
||||
##
|
||||
# Apply constraints to the preconditioned residuals.
|
||||
if blockVectorY is not None:
|
||||
_applyConstraints(activeBlockVectorR,
|
||||
gramYBY, blockVectorBY, blockVectorY)
|
||||
|
||||
##
|
||||
# B-orthonormalize the preconditioned residuals.
|
||||
|
||||
aux = _b_orthonormalize(B, activeBlockVectorR)
|
||||
activeBlockVectorR, activeBlockVectorBR = aux
|
||||
|
||||
activeBlockVectorAR = A(activeBlockVectorR)
|
||||
|
||||
if iterationNumber > 0:
|
||||
aux = _b_orthonormalize(B, activeBlockVectorP,
|
||||
activeBlockVectorBP, retInvR=True)
|
||||
activeBlockVectorP, activeBlockVectorBP, invR = aux
|
||||
activeBlockVectorAP = np.dot(activeBlockVectorAP, invR)
|
||||
|
||||
##
|
||||
# Perform the Rayleigh Ritz Procedure:
|
||||
# Compute symmetric Gram matrices:
|
||||
|
||||
xaw = np.dot(blockVectorX.T.conj(), activeBlockVectorAR)
|
||||
waw = np.dot(activeBlockVectorR.T.conj(), activeBlockVectorAR)
|
||||
xbw = np.dot(blockVectorX.T.conj(), activeBlockVectorBR)
|
||||
|
||||
if iterationNumber > 0:
|
||||
xap = np.dot(blockVectorX.T.conj(), activeBlockVectorAP)
|
||||
wap = np.dot(activeBlockVectorR.T.conj(), activeBlockVectorAP)
|
||||
pap = np.dot(activeBlockVectorP.T.conj(), activeBlockVectorAP)
|
||||
xbp = np.dot(blockVectorX.T.conj(), activeBlockVectorBP)
|
||||
wbp = np.dot(activeBlockVectorR.T.conj(), activeBlockVectorBP)
|
||||
|
||||
gramA = np.bmat([[np.diag(_lambda), xaw, xap],
|
||||
[xaw.T.conj(), waw, wap],
|
||||
[xap.T.conj(), wap.T.conj(), pap]])
|
||||
|
||||
gramB = np.bmat([[ident0, xbw, xbp],
|
||||
[xbw.T.conj(), ident, wbp],
|
||||
[xbp.T.conj(), wbp.T.conj(), ident]])
|
||||
else:
|
||||
gramA = np.bmat([[np.diag(_lambda), xaw],
|
||||
[xaw.T.conj(), waw]])
|
||||
gramB = np.bmat([[ident0, xbw],
|
||||
[xbw.T.conj(), ident]])
|
||||
|
||||
if verbosityLevel > 0:
|
||||
_report_nonhermitian(gramA, 3, -1, 'gramA')
|
||||
_report_nonhermitian(gramB, 3, -1, 'gramB')
|
||||
|
||||
if verbosityLevel > 10:
|
||||
save(gramA, 'gramA')
|
||||
save(gramB, 'gramB')
|
||||
|
||||
# Solve the generalized eigenvalue problem.
|
||||
_lambda, eigBlockVector = eigh(gramA, gramB, check_finite=False)
|
||||
ii = _get_indx(_lambda, sizeX, largest)
|
||||
if verbosityLevel > 10:
|
||||
print(ii)
|
||||
|
||||
_lambda = _lambda[ii]
|
||||
eigBlockVector = eigBlockVector[:,ii]
|
||||
|
||||
lambdaHistory.append(_lambda)
|
||||
|
||||
if verbosityLevel > 10:
|
||||
print('lambda:', _lambda)
|
||||
## # Normalize eigenvectors!
|
||||
## aux = np.sum( eigBlockVector.conjugate() * eigBlockVector, 0 )
|
||||
## eigVecNorms = np.sqrt( aux )
|
||||
## eigBlockVector = eigBlockVector / eigVecNorms[np.newaxis,:]
|
||||
# eigBlockVector, aux = _b_orthonormalize( B, eigBlockVector )
|
||||
|
||||
if verbosityLevel > 10:
|
||||
print(eigBlockVector)
|
||||
|
||||
##
|
||||
# Compute Ritz vectors.
|
||||
if iterationNumber > 0:
|
||||
eigBlockVectorX = eigBlockVector[:sizeX]
|
||||
eigBlockVectorR = eigBlockVector[sizeX:sizeX+currentBlockSize]
|
||||
eigBlockVectorP = eigBlockVector[sizeX+currentBlockSize:]
|
||||
|
||||
pp = np.dot(activeBlockVectorR, eigBlockVectorR)
|
||||
pp += np.dot(activeBlockVectorP, eigBlockVectorP)
|
||||
|
||||
app = np.dot(activeBlockVectorAR, eigBlockVectorR)
|
||||
app += np.dot(activeBlockVectorAP, eigBlockVectorP)
|
||||
|
||||
bpp = np.dot(activeBlockVectorBR, eigBlockVectorR)
|
||||
bpp += np.dot(activeBlockVectorBP, eigBlockVectorP)
|
||||
else:
|
||||
eigBlockVectorX = eigBlockVector[:sizeX]
|
||||
eigBlockVectorR = eigBlockVector[sizeX:]
|
||||
|
||||
pp = np.dot(activeBlockVectorR, eigBlockVectorR)
|
||||
app = np.dot(activeBlockVectorAR, eigBlockVectorR)
|
||||
bpp = np.dot(activeBlockVectorBR, eigBlockVectorR)
|
||||
|
||||
if verbosityLevel > 10:
|
||||
print(pp)
|
||||
print(app)
|
||||
print(bpp)
|
||||
|
||||
blockVectorX = np.dot(blockVectorX, eigBlockVectorX) + pp
|
||||
blockVectorAX = np.dot(blockVectorAX, eigBlockVectorX) + app
|
||||
blockVectorBX = np.dot(blockVectorBX, eigBlockVectorX) + bpp
|
||||
|
||||
blockVectorP, blockVectorAP, blockVectorBP = pp, app, bpp
|
||||
|
||||
aux = blockVectorBX * _lambda[np.newaxis,:]
|
||||
blockVectorR = blockVectorAX - aux
|
||||
|
||||
aux = np.sum(blockVectorR.conjugate() * blockVectorR, 0)
|
||||
residualNorms = np.sqrt(aux)
|
||||
|
||||
if verbosityLevel > 0:
|
||||
print('final eigenvalue:', _lambda)
|
||||
print('final residual norms:', residualNorms)
|
||||
|
||||
if retLambdaHistory:
|
||||
if retResidualNormsHistory:
|
||||
return _lambda, blockVectorX, lambdaHistory, residualNormsHistory
|
||||
else:
|
||||
return _lambda, blockVectorX, lambdaHistory
|
||||
else:
|
||||
if retResidualNormsHistory:
|
||||
return _lambda, blockVectorX, residualNormsHistory
|
||||
else:
|
||||
return _lambda, blockVectorX
|
||||
@@ -1,15 +0,0 @@
|
||||
from __future__ import division, print_function, absolute_import
|
||||
|
||||
|
||||
def configuration(parent_package='',top_path=None):
|
||||
from numpy.distutils.misc_util import Configuration
|
||||
|
||||
config = Configuration('lobpcg',parent_package,top_path)
|
||||
config.add_data_dir('tests')
|
||||
|
||||
return config
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(**configuration(top_path='').todict())
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
-261
@@ -1,261 +0,0 @@
|
||||
""" Test functions for the sparse.linalg.eigen.lobpcg module
|
||||
"""
|
||||
from __future__ import division, print_function, absolute_import
|
||||
|
||||
import itertools
|
||||
|
||||
import numpy as np
|
||||
from numpy.testing import (assert_almost_equal, assert_equal,
|
||||
assert_allclose, assert_array_less)
|
||||
|
||||
from scipy import ones, rand, r_, diag, linalg, eye
|
||||
from scipy.linalg import eig, eigh, toeplitz
|
||||
import scipy.sparse
|
||||
from scipy.sparse.linalg.eigen.lobpcg import lobpcg
|
||||
from scipy.sparse.linalg import eigs
|
||||
from scipy.sparse import spdiags
|
||||
|
||||
import pytest
|
||||
|
||||
def ElasticRod(n):
|
||||
# Fixed-free elastic rod
|
||||
L = 1.0
|
||||
le = L/n
|
||||
rho = 7.85e3
|
||||
S = 1.e-4
|
||||
E = 2.1e11
|
||||
mass = rho*S*le/6.
|
||||
k = E*S/le
|
||||
A = k*(diag(r_[2.*ones(n-1),1])-diag(ones(n-1),1)-diag(ones(n-1),-1))
|
||||
B = mass*(diag(r_[4.*ones(n-1),2])+diag(ones(n-1),1)+diag(ones(n-1),-1))
|
||||
return A,B
|
||||
|
||||
|
||||
def MikotaPair(n):
|
||||
# Mikota pair acts as a nice test since the eigenvalues
|
||||
# are the squares of the integers n, n=1,2,...
|
||||
x = np.arange(1,n+1)
|
||||
B = diag(1./x)
|
||||
y = np.arange(n-1,0,-1)
|
||||
z = np.arange(2*n-1,0,-2)
|
||||
A = diag(z)-diag(y,-1)-diag(y,1)
|
||||
return A,B
|
||||
|
||||
|
||||
def compare_solutions(A,B,m):
|
||||
n = A.shape[0]
|
||||
|
||||
np.random.seed(0)
|
||||
|
||||
V = rand(n,m)
|
||||
X = linalg.orth(V)
|
||||
|
||||
eigs,vecs = lobpcg(A, X, B=B, tol=1e-5, maxiter=30, largest=False)
|
||||
eigs.sort()
|
||||
|
||||
w,v = eig(A,b=B)
|
||||
w.sort()
|
||||
|
||||
assert_almost_equal(w[:int(m/2)],eigs[:int(m/2)],decimal=2)
|
||||
|
||||
|
||||
def test_Small():
|
||||
A,B = ElasticRod(10)
|
||||
compare_solutions(A,B,10)
|
||||
A,B = MikotaPair(10)
|
||||
compare_solutions(A,B,10)
|
||||
|
||||
|
||||
def test_ElasticRod():
|
||||
A,B = ElasticRod(100)
|
||||
compare_solutions(A,B,20)
|
||||
|
||||
|
||||
def test_MikotaPair():
|
||||
A,B = MikotaPair(100)
|
||||
compare_solutions(A,B,20)
|
||||
|
||||
|
||||
def test_trivial():
|
||||
n = 5
|
||||
X = ones((n, 1))
|
||||
A = eye(n)
|
||||
compare_solutions(A, None, n)
|
||||
|
||||
|
||||
def test_regression():
|
||||
# https://mail.python.org/pipermail/scipy-user/2010-October/026944.html
|
||||
n = 10
|
||||
X = np.ones((n, 1))
|
||||
A = np.identity(n)
|
||||
w, V = lobpcg(A, X)
|
||||
assert_allclose(w, [1])
|
||||
|
||||
|
||||
def test_diagonal():
|
||||
# This test was moved from '__main__' in lobpcg.py.
|
||||
# Coincidentally or not, this is the same eigensystem
|
||||
# required to reproduce arpack bug
|
||||
# https://forge.scilab.org/p/arpack-ng/issues/1397/
|
||||
# even using the same n=100.
|
||||
|
||||
np.random.seed(1234)
|
||||
|
||||
# The system of interest is of size n x n.
|
||||
n = 100
|
||||
|
||||
# We care about only m eigenpairs.
|
||||
m = 4
|
||||
|
||||
# Define the generalized eigenvalue problem Av = cBv
|
||||
# where (c, v) is a generalized eigenpair,
|
||||
# and where we choose A to be the diagonal matrix whose entries are 1..n
|
||||
# and where B is chosen to be the identity matrix.
|
||||
vals = np.arange(1, n+1, dtype=float)
|
||||
A = scipy.sparse.diags([vals], [0], (n, n))
|
||||
B = scipy.sparse.eye(n)
|
||||
|
||||
# Let the preconditioner M be the inverse of A.
|
||||
M = scipy.sparse.diags([np.reciprocal(vals)], [0], (n, n))
|
||||
|
||||
# Pick random initial vectors.
|
||||
X = np.random.rand(n, m)
|
||||
|
||||
# Require that the returned eigenvectors be in the orthogonal complement
|
||||
# of the first few standard basis vectors.
|
||||
m_excluded = 3
|
||||
Y = np.eye(n, m_excluded)
|
||||
|
||||
eigs, vecs = lobpcg(A, X, B, M=M, Y=Y, tol=1e-4, maxiter=40, largest=False)
|
||||
|
||||
assert_allclose(eigs, np.arange(1+m_excluded, 1+m_excluded+m))
|
||||
_check_eigen(A, eigs, vecs, rtol=1e-3, atol=1e-3)
|
||||
|
||||
|
||||
def _check_eigen(M, w, V, rtol=1e-8, atol=1e-14):
|
||||
mult_wV = np.multiply(w, V)
|
||||
dot_MV = M.dot(V)
|
||||
assert_allclose(mult_wV, dot_MV, rtol=rtol, atol=atol)
|
||||
|
||||
|
||||
def _check_fiedler(n, p):
|
||||
# This is not necessarily the recommended way to find the Fiedler vector.
|
||||
np.random.seed(1234)
|
||||
col = np.zeros(n)
|
||||
col[1] = 1
|
||||
A = toeplitz(col)
|
||||
D = np.diag(A.sum(axis=1))
|
||||
L = D - A
|
||||
# Compute the full eigendecomposition using tricks, e.g.
|
||||
# http://www.cs.yale.edu/homes/spielman/561/2009/lect02-09.pdf
|
||||
tmp = np.pi * np.arange(n) / n
|
||||
analytic_w = 2 * (1 - np.cos(tmp))
|
||||
analytic_V = np.cos(np.outer(np.arange(n) + 1/2, tmp))
|
||||
_check_eigen(L, analytic_w, analytic_V)
|
||||
# Compute the full eigendecomposition using eigh.
|
||||
eigh_w, eigh_V = eigh(L)
|
||||
_check_eigen(L, eigh_w, eigh_V)
|
||||
# Check that the first eigenvalue is near zero and that the rest agree.
|
||||
assert_array_less(np.abs([eigh_w[0], analytic_w[0]]), 1e-14)
|
||||
assert_allclose(eigh_w[1:], analytic_w[1:])
|
||||
|
||||
# Check small lobpcg eigenvalues.
|
||||
X = analytic_V[:, :p]
|
||||
lobpcg_w, lobpcg_V = lobpcg(L, X, largest=False)
|
||||
assert_equal(lobpcg_w.shape, (p,))
|
||||
assert_equal(lobpcg_V.shape, (n, p))
|
||||
_check_eigen(L, lobpcg_w, lobpcg_V)
|
||||
assert_array_less(np.abs(np.min(lobpcg_w)), 1e-14)
|
||||
assert_allclose(np.sort(lobpcg_w)[1:], analytic_w[1:p])
|
||||
|
||||
# Check large lobpcg eigenvalues.
|
||||
X = analytic_V[:, -p:]
|
||||
lobpcg_w, lobpcg_V = lobpcg(L, X, largest=True)
|
||||
assert_equal(lobpcg_w.shape, (p,))
|
||||
assert_equal(lobpcg_V.shape, (n, p))
|
||||
_check_eigen(L, lobpcg_w, lobpcg_V)
|
||||
assert_allclose(np.sort(lobpcg_w), analytic_w[-p:])
|
||||
|
||||
# Look for the Fiedler vector using good but not exactly correct guesses.
|
||||
fiedler_guess = np.concatenate((np.ones(n//2), -np.ones(n-n//2)))
|
||||
X = np.vstack((np.ones(n), fiedler_guess)).T
|
||||
lobpcg_w, lobpcg_V = lobpcg(L, X, largest=False)
|
||||
# Mathematically, the smaller eigenvalue should be zero
|
||||
# and the larger should be the algebraic connectivity.
|
||||
lobpcg_w = np.sort(lobpcg_w)
|
||||
assert_allclose(lobpcg_w, analytic_w[:2], atol=1e-14)
|
||||
|
||||
|
||||
def test_fiedler_small_8():
|
||||
# This triggers the dense path because 8 < 2*5.
|
||||
_check_fiedler(8, 2)
|
||||
|
||||
|
||||
def test_fiedler_large_12():
|
||||
# This does not trigger the dense path, because 2*5 <= 12.
|
||||
_check_fiedler(12, 2)
|
||||
|
||||
|
||||
def test_hermitian():
|
||||
np.random.seed(1234)
|
||||
|
||||
sizes = [3, 10, 50]
|
||||
ks = [1, 3, 10, 50]
|
||||
gens = [True, False]
|
||||
|
||||
for size, k, gen in itertools.product(sizes, ks, gens):
|
||||
if k > size:
|
||||
continue
|
||||
|
||||
H = np.random.rand(size, size) + 1.j * np.random.rand(size, size)
|
||||
H = 10 * np.eye(size) + H + H.T.conj()
|
||||
|
||||
X = np.random.rand(size, k)
|
||||
|
||||
if not gen:
|
||||
B = np.eye(size)
|
||||
w, v = lobpcg(H, X, maxiter=5000)
|
||||
w0, v0 = eigh(H)
|
||||
else:
|
||||
B = np.random.rand(size, size) + 1.j * np.random.rand(size, size)
|
||||
B = 10 * np.eye(size) + B.dot(B.T.conj())
|
||||
w, v = lobpcg(H, X, B, maxiter=5000, largest=False)
|
||||
w0, v0 = eigh(H, B)
|
||||
|
||||
for wx, vx in zip(w, v.T):
|
||||
# Check eigenvector
|
||||
assert_allclose(np.linalg.norm(H.dot(vx) - B.dot(vx) * wx)
|
||||
/ np.linalg.norm(H.dot(vx)),
|
||||
0, atol=5e-4, rtol=0)
|
||||
|
||||
# Compare eigenvalues
|
||||
j = np.argmin(abs(w0 - wx))
|
||||
assert_allclose(wx, w0[j], rtol=1e-4)
|
||||
|
||||
# The n=5 case tests the alternative small matrix code path that uses eigh().
|
||||
@pytest.mark.parametrize('n, atol', [(20, 1e-3), (5, 1e-8)])
|
||||
def test_eigs_consistency(n, atol):
|
||||
vals = np.arange(1, n+1, dtype=np.float64)
|
||||
A = spdiags(vals, 0, n, n)
|
||||
np.random.seed(345678)
|
||||
X = np.random.rand(n, 2)
|
||||
lvals, lvecs = lobpcg(A, X, largest=True, maxiter=100)
|
||||
vals, vecs = eigs(A, k=2)
|
||||
|
||||
_check_eigen(A, lvals, lvecs, atol=atol, rtol=0)
|
||||
assert_allclose(np.sort(vals), np.sort(lvals), atol=1e-14)
|
||||
|
||||
def test_verbosity():
|
||||
"""Check that nonzero verbosity level code runs.
|
||||
"""
|
||||
A, B = ElasticRod(100)
|
||||
|
||||
n = A.shape[0]
|
||||
m = 20
|
||||
|
||||
np.random.seed(0)
|
||||
V = rand(n,m)
|
||||
X = linalg.orth(V)
|
||||
|
||||
eigs,vecs = lobpcg(A, X, B=B, tol=1e-5, maxiter=30, largest=False,
|
||||
verbosityLevel=11)
|
||||
@@ -1,17 +0,0 @@
|
||||
from __future__ import division, print_function, absolute_import
|
||||
|
||||
|
||||
def configuration(parent_package='',top_path=None):
|
||||
from numpy.distutils.misc_util import Configuration
|
||||
|
||||
config = Configuration('eigen',parent_package,top_path)
|
||||
|
||||
config.add_subpackage(('arpack'))
|
||||
config.add_subpackage(('lobpcg'))
|
||||
|
||||
return config
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(**configuration(top_path='').todict())
|
||||
Reference in New Issue
Block a user