New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Eigenvalue Range Functionality for Symmetric Eigenvalue Problems #6510
Changes from all commits
48cf326
21d97f6
73bba9f
bd80c76
b66593a
ca11239
da1f57e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
# additions by Bart Vandereycken, June 2006 | ||
# additions by Andrew D Straw, May 2007 | ||
# additions by Tiziano Zito, November 2008 | ||
# additions by Alex Papanicolaou, August 2016 | ||
# | ||
# April 2010: Functions for LU, QR, SVD, Schur and Cholesky decompositions were | ||
# moved to their own files. Still in this file are functions for eigenstuff | ||
|
@@ -240,7 +241,7 @@ def eig(a, b=None, left=False, right=True, overwrite_a=False, | |
|
||
def eigh(a, b=None, lower=True, eigvals_only=False, overwrite_a=False, | ||
overwrite_b=False, turbo=True, eigvals=None, type=1, | ||
check_finite=True): | ||
check_finite=True, eigrng=None): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer to write |
||
""" | ||
Solve an ordinary or generalized eigenvalue problem for a complex | ||
Hermitian or real symmetric matrix. | ||
|
@@ -272,7 +273,8 @@ def eigh(a, b=None, lower=True, eigvals_only=False, overwrite_a=False, | |
eigvals : tuple (lo, hi), optional | ||
Indexes of the smallest and largest (in ascending order) eigenvalues | ||
and corresponding eigenvectors to be returned: 0 <= lo <= hi <= M-1. | ||
If omitted, all eigenvalues and eigenvectors are returned. | ||
If eigvals and eigrng are omitted, all eigenvalues and eigenvectors | ||
are returned. | ||
type : int, optional | ||
Specifies the problem type to be solved: | ||
|
||
|
@@ -289,6 +291,10 @@ def eigh(a, b=None, lower=True, eigvals_only=False, overwrite_a=False, | |
Whether to check that the input matrices contain only finite numbers. | ||
Disabling may give a performance gain, but may result in problems | ||
(crashes, non-termination) if the inputs do contain infinities or NaNs. | ||
eigrng : tuple (vl, vu), optional | ||
The half-open interval (vl,vu] on which eigenvalues and corresponding | ||
eigenvectors are to be returned: vl < vu. If eigrng and eigvals are | ||
omitted, all eigenvalues and eigenvectors are returned. | ||
|
||
Returns | ||
------- | ||
|
@@ -352,14 +358,21 @@ def eigh(a, b=None, lower=True, eigvals_only=False, overwrite_a=False, | |
_job = (eigvals_only and 'N') or 'V' | ||
|
||
# port eigenvalue range from python to fortran convention | ||
if eigvals is not None and eigrng is not None: | ||
raise ValueError("Only one of eigvals or eigrng may be set") | ||
if eigvals is not None: | ||
lo, hi = eigvals | ||
if lo < 0 or hi >= a1.shape[0]: | ||
raise ValueError('The eigenvalue range specified is not valid.\n' | ||
raise ValueError('The eigenvalue index range specified is not valid.\n' | ||
'Valid range is [%s,%s]' % (0, a1.shape[0]-1)) | ||
lo += 1 | ||
hi += 1 | ||
eigvals = (lo, hi) | ||
if eigrng is not None: | ||
vl, vu = eigrng | ||
if vl >= vu: | ||
raise ValueError('The eigenvalue value range specified is not valid:\n' | ||
'(%s,%s]' % (vl, vu)) | ||
|
||
# set lower | ||
if lower: | ||
|
@@ -379,14 +392,21 @@ def eigh(a, b=None, lower=True, eigvals_only=False, overwrite_a=False, | |
# for all lapack routines | ||
if b1 is None: | ||
(evr,) = get_lapack_funcs((pfx+'evr',), (a1,)) | ||
if eigvals is None: | ||
w, v, info = evr(a1, uplo=uplo, jobz=_job, range="A", il=1, | ||
iu=a1.shape[0], overwrite_a=overwrite_a) | ||
else: | ||
if eigvals is not None: | ||
(lo, hi) = eigvals | ||
w_tot, v, info = evr(a1, uplo=uplo, jobz=_job, range="I", | ||
il=lo, iu=hi, overwrite_a=overwrite_a) | ||
w = w_tot[0:hi-lo+1] | ||
elif eigrng is not None: | ||
(vl, vu) = eigrng | ||
w_full, v_full, info = evr(a1, uplo=uplo, jobz=_job, range="V", vl=vl, | ||
vu=vu, overwrite_a=overwrite_a) | ||
ix = nonzero(w_full)[0].tolist() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if there are zero eigenvalues? Is the number of eigenvalues found returned by the LAPACK routine in the The nonzero and tolist are probably not needed. |
||
w = w_full[ix] | ||
v = v_full[:, ix] | ||
else: | ||
w, v, info = evr(a1, uplo=uplo, jobz=_job, range="A", il=1, | ||
iu=a1.shape[0], overwrite_a=overwrite_a) | ||
|
||
# Generalized Eigenvalue Problem | ||
else: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -404,6 +404,73 @@ | |
_flapack.sgegv = sgegv | ||
_flapack.zgegv = zgegv | ||
|
||
# Patching EVR Methods to handle value ranges | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should say that this is used to retain backward compatibility. |
||
_evr_doc = """ | ||
w,z,info = {name}(a,[jobz,range,uplo,il,iu,lwork,overwrite_a,vl,vu]) | ||
|
||
Wrapper for ``{name}``. | ||
|
||
Parameters | ||
---------- | ||
a : input rank-2 array({type!r}) with bounds (n,n) | ||
|
||
Other Parameters | ||
---------------- | ||
jobz : input string(len=1), optional | ||
Default: 'V' | ||
range : input string(len=1), optional | ||
Default: 'A' | ||
uplo : input string(len=1), optional | ||
Default: 'L' | ||
overwrite_a : input int, optional | ||
Default: 0 | ||
il : input int, optional | ||
Default: 1 | ||
iu : input int, optional | ||
Default: n | ||
lwork : input int, optional | ||
Default: 26*n | ||
vl : input float, optional | ||
Default: 0 | ||
vu : input float, optional | ||
Default: 1 | ||
|
||
Returns | ||
------- | ||
w : rank-1 array({w_type!r}) with bounds (n) | ||
z : rank-2 array({type!r}) with bounds (n,m) | ||
info : int | ||
""" | ||
|
||
|
||
def evr_decorator(evr_fun, name, type): | ||
|
||
_arg_list = ['a', 'jobz', 'range', 'uplo', 'il', | ||
'iu', 'lwork', 'overwrite_a', 'vl', 'vu'] | ||
|
||
def wrapper(*args, **kwargs): | ||
_new_kwargs = kwargs.copy() | ||
for k, arg in zip(_arg_list, args): | ||
_new_kwargs[k] = arg | ||
# Fix bug in -EVR where il/iu are not ignored | ||
if _new_kwargs['range'] != 'I': | ||
_new_kwargs['il'] = 1 | ||
_new_kwargs['iu'] = min(_new_kwargs['a'].shape) | ||
return evr_fun(**_new_kwargs) | ||
|
||
wrapper.__module__ = 'scipy.linalg._flapack' | ||
wrapper.__doc__ = _evr_doc.format( | ||
name=name, w_type=type.lower(), type=type) | ||
wrapper.__name__ = name | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should also copy the |
||
|
||
return wrapper | ||
|
||
ssyevr = evr_decorator(ssyevr, 'ssyevr', 'f') | ||
dsyevr = evr_decorator(dsyevr, 'dsyevr', 'd') | ||
cheevr = evr_decorator(cheevr, 'cheevr', 'F') | ||
zheevr = evr_decorator(zheevr, 'zheevr', 'D') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need also replace the function in
|
||
|
||
|
||
# some convenience alias for complex functions | ||
_lapack_alias = { | ||
'corghr': 'cunghr', 'zorghr': 'zunghr', | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -653,6 +653,25 @@ def test_eigh(): | |
turbo, eigenvalues) | ||
|
||
|
||
def test_eigh_rng(): | ||
DIM = 6 | ||
v = {'dim': (DIM,), | ||
'dtype': ('f','d','F','D'), | ||
'overwrite': (True, False), | ||
'lower': (True, False), | ||
'eigrng': (None, (-.5, .5), (-.75, .75))} | ||
|
||
for dim in v['dim']: | ||
for typ in v['dtype']: | ||
for overwrite in v['overwrite']: | ||
for eigenrange in v['eigrng']: | ||
for lower in v['lower']: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that itertools.product can be used to avoid deeply nested loops:
|
||
yield (eigenhproblem_range, | ||
'ordinary', | ||
dim, typ, overwrite, lower, | ||
eigenrange) | ||
|
||
|
||
def test_eigh_of_sparse(): | ||
# This tests the rejection of inputs that eigh cannot currently handle. | ||
import scipy.sparse | ||
|
@@ -715,6 +734,37 @@ def eigenhproblem_general(desc, dim, dtype, | |
assert_array_almost_equal(diag2_, ones(diag2_.shape[0]), DIGITS[dtype]) | ||
|
||
|
||
def eigenhproblem_range(desc, dim, dtype, | ||
overwrite, lower, | ||
eigenrange): | ||
"""Solve a standard eigenvalue problem.""" | ||
if iscomplex(empty(1, dtype=dtype)): | ||
a = _complex_symrand(dim, dtype) | ||
else: | ||
a = symrand(dim).astype(dtype) | ||
|
||
if overwrite: | ||
a_c = a.copy() | ||
else: | ||
a_c = a | ||
w, z = eigh(a, overwrite_a=overwrite, lower=lower, eigrng=eigenrange) | ||
assert_dtype_equal(z.dtype, dtype) | ||
assert_equal(len(np.nonzero(w)[0]), len(w)) | ||
assert_equal(z.shape[1], len(w)) | ||
w = w.astype(dtype) | ||
diag_ = diag(dot(z.T.conj(), dot(a_c, z))).real | ||
assert_array_almost_equal(diag_, w, DIGITS[dtype]) | ||
|
||
|
||
def test_eigh_range_invalid(): | ||
# This tests the rejection of inputs that eigh cannot currently handle. | ||
dim = 6 | ||
dtype = 'd' | ||
a = symrand(dim).astype(dtype) | ||
assert_raises(ValueError, eigh, a, eigvals=True, eigrng=True) | ||
assert_raises(ValueError, eigh, a, eigrng=(1, 0)) | ||
|
||
|
||
def test_eigh_integer(): | ||
a = array([[1,2],[2,7]]) | ||
b = array([[3,1],[1,5]]) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't necessarily need to add a comment here --- who modified what is kept track of by the version contorl.
In particular, the file has been modified by many people since 2008.