-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
ENH: scipy.linalg.eigsh cannot get all eigenvalues #7004
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
Conversation
perimosocordiae
left a comment
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.
This PR broke the test_arpack.test_eigen_bad_kwargs test.
My other comments are in-line.
| eigsh(A, k=4) | ||
| eigsh(A, k=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.
Each of these tests should check the returned values of eigs/eigsh for correctness.
| if k >= n: | ||
| import warnings | ||
| warnings.warn('k greater than/equal to the order of the square ' | ||
| 'matrix. Using scipy.linalg.eig instead.') |
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.
This should use a SparseEfficiencyWarning rather than a generic UserWarning.
| import warnings | ||
| warnings.warn('k greater than/equal to the order of the square ' | ||
| 'matrix. Using scipy.linalg.eig instead.') | ||
| if return_eigenvectors is True: |
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.
The is True isn't needed here.
| if k >= n-1: | ||
| import warnings | ||
| warnings.warn('k greater than/equal to N - 1 . ' | ||
| 'Using scipy.linalg.eig instead.') |
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.
This warning doesn't explain what N is.
|
The reason |
|
@amanp10: correct, |
|
@perimosocordiae please have a look. |
perimosocordiae
left a comment
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.
Looks fine to me now.
This may have backwards-compatibility implications, though, so it should probably get a mention in the release notes.
| if return_eigenvectors: | ||
| return eig(A.todense()) | ||
| else: | ||
| return eig(A.todense(), right=False) |
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.
These can probably be condensed into one return:
return eig(A.todense(), right=return_eigenvectors)(Same for the similar lines below.)
|
Am I supposed to mention it in the release notes or the core-devs do it? |
|
@amanp10: note that you should not do |
|
@pv I will take care from now on. Thanks for pointing out. |
| warnings.warn('k greater than/equal to N - 1 for N * N square matrix. ' | ||
| 'Using scipy.linalg.eig instead.', | ||
| SparseEfficiencyWarning) | ||
| return eig(A.todense(), right=return_eigenvectors) |
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.
This should also check if M is given, and if yes, solve the generalized eigenvalue problem. (Add also a test.)
Additionally A may be a LinearOperator or a dense matrix, which don't have a todense method.
|
@pv Sorry for the late commit. I got busy with class tests. Please have a look |
| if not isdense(A): | ||
| A = A.todense() | ||
|
|
||
| return eig(A, b=M, right=return_eigenvectors) |
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.
This should be eigh, right?
My bad, this is fine.
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.
Sorry for the mistake, I will correct it.
|
I was wondering if it would be necessary to add tests for complex or complex-hermitian matrix as well? |
|
|
||
| n = A.shape[0] | ||
|
|
||
| if k >= n - 1: |
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.
This condition should be run after the k <= 0 or k >= n check.
| import warnings | ||
| warnings.warn('k greater than/equal to N - 1 for N * N square matrix. ' | ||
| 'Using scipy.linalg.eig instead.', | ||
| SparseEfficiencyWarning) |
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.
A SparseEfficiencyWarning doesn't make sense if A isn't sparse.
| raise ValueError("'M' cannot be a LinearOperator since" | ||
| "scipy.linalg.eig is being used.") | ||
| if not isdense(A): | ||
| A = A.todense() |
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.
I'd prefer raising an error if A is sparse, with a hint to call .toarray() first if that's actually what the user wants.
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.
I think the docs need to be changed as they mention A as A : ndarray, sparse matrix or LinearOperator. Or should we let it be with errors and warnings wherever necessary?
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.
No, we still need to support all three input types. I'm suggesting that be behavior for non-ndarray types (spmatrix, LinearOperator) remain as it was before this PR, but with an informative error message so that the user knows how to proceed.
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.
Okay. I get it.
| if k >= n: | ||
| import warnings | ||
| warnings.warn('k greater than/equal to N for N * N square matrix. ' | ||
| 'Using scipy.linalg.eig instead.', |
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.
scipy.linalg.eigh, here and in the error messages below.
| "scipy.linalg.eig is being used.") | ||
| if isinstance(M, LinearOperator): | ||
| raise ValueError("'M' cannot be a LinearOperator since" | ||
| "scipy.linalg.eig is being used.") |
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.
These errors should explain that k >= n is why we can't use eigsh here.
| j = np.random.randint(N, size=N * N // 2) | ||
| M[i,j] = 0 | ||
|
|
||
| M = 0.5 * (M + M.T) # Make M symmetric |
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.
This is likely to destroy whatever sparsity we just created. I'd symmetrize first, then set (symmetric) zeros.
| if pos_definite: | ||
| M = M + N * np.eye(N) | ||
|
|
||
| return M |
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.
Need to create an actual sparse matrix if sparse=True.
| raise ValueError("k=%d must be greater than 0." % (k, n - 1)) | ||
|
|
||
| if k >= n - 1: | ||
| if not isdense(A): |
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.
Why not use if issparse(A) here?
| from scipy.linalg import svd, hilbert | ||
|
|
||
| from scipy._lib._gcutils import assert_deallocated | ||
| #from warnings import catch_warnings, simplefilter |
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.
Remove this line, please.
| i = np.random.randint(N, size=N * N // 2) | ||
| j = np.random.randint(N, size=N * N // 2) | ||
| M[i, j] = 0 | ||
| M = M + Id |
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.
M += Id
| j = np.random.randint(N, size=N * N // 2) | ||
| M[i, j] = 0 | ||
|
|
||
| return M |
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.
if sparse:
M = scipy.sparse.csr_matrix(M)
return M| A = generate_matrix(4, sparse=False) | ||
| M1 = np.random.random((4, 4)) | ||
| M2 = generate_matrix(4, sparse=True) | ||
| M3 = aslinearoperator(M1) |
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.
Might be clearer to use M_dense, M_sparse, and M_linop.
| raise ValueError("k=%d must be between 1 and ndim(A)-1=%d" | ||
| % (k, n - 1)) | ||
| if k <= 0: | ||
| raise ValueError("k=%d must be greater than 0." % (k, n - 1)) |
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're giving too many values to the format string here. We aren't using n - 1 anymore.
| "A with k >= N - 1.") | ||
| if isinstance(M, LinearOperator): | ||
| raise TypeError("Cannot use scipy.linalg.eig for LinearOperator " | ||
| "M with k >= N - 1.") |
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.
This LinearOperator checks need to happen before the warning.
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.
I think we should inform the user that we are using or trying to use scipy.linalg.eig right after the check k >= n(or k >= n - 1). Or maybe we can put it in the docs somewhere.
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.
Ok, I see what you're saying here. (Sorry for the long wait!) Can you move the warning up before the TypeError checks here? And maybe have it say "Attempting to use" instead of "Using"?
|
I need some help here. I cant figure out why the build is failing. |
|
THe build is failing due to issues we've been having with our OSX image on TravisCI. I think the problem is unrelated to this PR. |
|
@perimosocordiae I have made the changes please have a look. |
|
Looks good now, thanks @amanp10! I'll wait a bit to let others comment before merging. |
|
Thank you for all the reviews. |
| raise TypeError("Cannot use scipy.linalg.eig for LinearOperator " | ||
| "M with k >= N - 1.") | ||
|
|
||
| import warnings |
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 just need to import warnings module at the top of the file and you can use it in any function. Also with
from .misc import LinAlgWarning
you can raise a LinAlgWarning which I guess is more relevant instead of a UserWarning. Also you already know the values of k and N so the message can use those values
warnings.warn("{0} >= {1} for {1}x{1} square matrix. "
"Using scipy.linalg.eigh instead.".format(k, N),
LinAlgWarning, stacklevel=3)
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.
I cant find LinAlgWarning because this branch has not been updated to v1.1. Should I merge this branch with the latest master branch to get LinAlgWarning?
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.
Then I guess the regular RuntimeWarning would suffice. At some point we will sweep all the warnings anyhow.
| raise TypeError("Cannot use scipy.linalg.eigh for LinearOperator " | ||
| "M with k >= N.") | ||
|
|
||
| import warnings |
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.
same import issue as above
|
@pv @perimosocordiae Any reservations left for this PR? |
|
Sorry for the extreme slowness on my part. I left one comment, and there appears to be a merge conflict to resolve, but after those are handled I will definitely merge this quickly. |
|
I am unable to understand why the Travis-Ci build is failing. Need some help here. |
|
Just ignore those TravisCI failures, that's due to some issues unrelated to this PR |
|
Merged, at long last. Thanks, @amanp10 ! |
|
I am happy I contributed something. |
ENH: scipy.linalg.eigsh cannot get all eigenvalues
Fixes: #6430
unit tests and warnings added along with the changes.