-
-
Notifications
You must be signed in to change notification settings - Fork 92
Add EigenvalueProblem support with dense, Arpack, ArnoldiMethod, KrylovKit, and JacobiDavidson backends #1071
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
Open
AJ0070
wants to merge
9
commits into
SciML:main
Choose a base branch
from
AJ0070:fix/143-1
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
c5606b5
Add EigenvalueProblem support with dense, Arpack, ArnoldiMethod, Kryl…
AJ0070 1ebe83d
fixed runic failures
AJ0070 7ff3af4
fixed runic failure
AJ0070 6a066b6
Merge branch 'main' into fix/143-1
AJ0070 6722261
addressed reviews
AJ0070 f58e980
Merge remote-tracking branch 'origin/fix/143-1' into fix/143-1
AJ0070 8020b29
addressed reviews
AJ0070 c9f0bea
fix LTS: rename EigenvalueSolution type param T→Tv to avoid ambiguity…
AJ0070 1426a70
updated compat
AJ0070 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| module LinearSolveArnoldiMethodExt | ||
|
|
||
| using LinearAlgebra | ||
| using LinearSolve | ||
| import ArnoldiMethod: partialschur, partialeigen, LM, LR, SR, LI, SI | ||
| using SciMLBase: SciMLBase, ReturnCode | ||
|
|
||
| function SciMLBase.solve( | ||
| prob::LinearSolve.EigenvalueProblem, | ||
| alg::LinearSolve.ArnoldiMethodJL, | ||
| args...; kwargs... | ||
| ) | ||
| prob.B === nothing || | ||
| error("ArnoldiMethod backend currently supports standard eigenvalue problems only.") | ||
| nev = LinearSolve.default_num_eigenpairs(prob) | ||
| which = prob.shift === nothing ? _arnoldi_target(prob.eigentarget) : LM() | ||
| A = prob.shift === nothing ? prob.A : _shift_invert_operator(prob.A, prob.shift) | ||
| kw = (; nev, which, prob.kwargs..., alg.kwargs..., kwargs...) | ||
| decomp, history = partialschur(A; kw...) | ||
| values, vectors = partialeigen(decomp) | ||
| if prob.shift !== nothing | ||
| values = prob.shift .+ inv.(values) | ||
| end | ||
| values, vectors = LinearSolve._select_eigenpairs( | ||
| values, vectors, nev, prob.eigentarget, prob.shift | ||
| ) | ||
| retcode = history.converged ? ReturnCode.Success : ReturnCode.ConvergenceFailure | ||
| return LinearSolve.build_eigenvalue_solution( | ||
| prob, alg, values, vectors; retcode, stats = history | ||
| ) | ||
| end | ||
|
|
||
| # ArnoldiMethod exposes its own `Target` types (preferred over ARPACK-style | ||
| # symbols per its own documentation) for all but smallest-magnitude, which it | ||
| # does not support at all. | ||
| function _arnoldi_target(w::LinearSolve.EigenvalueTarget.T) | ||
| T = LinearSolve.EigenvalueTarget | ||
| return w == T.LargestMagnitude ? LM() : | ||
| w == T.LargestRealPart ? LR() : | ||
| w == T.SmallestRealPart ? SR() : | ||
| w == T.LargestImaginaryPart ? LI() : | ||
| w == T.SmallestImaginaryPart ? SI() : | ||
| throw(ArgumentError("ArnoldiMethod does not support `eigentarget = EigenvalueTarget.SmallestMagnitude`; use a different backend (e.g. `KrylovKitEigen()` or `ArpackJL()`) or supply `shift` for shift-and-invert.")) | ||
| end | ||
|
|
||
| function _shift_invert_operator(A, shift) | ||
| F = factorize(A - shift * I) | ||
| T = promote_type(eltype(A), typeof(shift)) | ||
| return ShiftInvertMap{typeof(F), T}(F, size(A, 1)) | ||
| end | ||
|
|
||
| struct ShiftInvertMap{F, T} | ||
| F::F | ||
| n::Int | ||
| end | ||
|
|
||
| Base.size(A::ShiftInvertMap) = (A.n, A.n) | ||
| Base.size(A::ShiftInvertMap, dim::Integer) = dim <= 2 ? A.n : 1 | ||
| Base.eltype(::Type{<:ShiftInvertMap{F, T}}) where {F, T} = T | ||
|
|
||
| function LinearAlgebra.mul!(y, A::ShiftInvertMap, x) | ||
| copyto!(y, A.F \ x) | ||
| return y | ||
| end | ||
|
|
||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| module LinearSolveArpackExt | ||
|
|
||
| using LinearSolve | ||
| using Arpack | ||
| using SciMLBase: SciMLBase, ReturnCode | ||
|
|
||
| function SciMLBase.solve( | ||
| prob::LinearSolve.EigenvalueProblem, | ||
| alg::LinearSolve.ArpackJL, | ||
| args...; kwargs... | ||
| ) | ||
| nev = LinearSolve.default_num_eigenpairs(prob) | ||
| base = (; nev, which = _arpack_which(prob.eigentarget)) | ||
| if prob.shift !== nothing | ||
| base = (; base..., sigma = prob.shift) | ||
| end | ||
| kw = (; base..., prob.kwargs..., alg.kwargs..., kwargs...) | ||
| # `Arpack.eigs` takes the generalized-problem matrix `B` positionally, not | ||
| # as a keyword argument. | ||
| values, vectors, nconv, niter, nmult, resid = if prob.B === nothing | ||
| Arpack.eigs(prob.A; kw...) | ||
| else | ||
| Arpack.eigs(prob.A, prob.B; kw...) | ||
| end | ||
| retcode = nconv >= length(values) ? ReturnCode.Success : ReturnCode.ConvergenceFailure | ||
| stats = (; nconv, niter, nmult) | ||
| return LinearSolve.build_eigenvalue_solution( | ||
| prob, alg, values, vectors; retcode, resid, stats | ||
| ) | ||
| end | ||
|
|
||
| # Arpack.eigs requires a raw ARPACK-style Symbol for `which`; this mapping is | ||
| # purely a private adapter to that third-party API, not a general LinearSolve | ||
| # concept. | ||
| function _arpack_which(w::LinearSolve.EigenvalueTarget.T) | ||
| T = LinearSolve.EigenvalueTarget | ||
| return w == T.LargestMagnitude ? :LM : | ||
| w == T.SmallestMagnitude ? :SM : | ||
| w == T.LargestRealPart ? :LR : | ||
| w == T.SmallestRealPart ? :SR : | ||
| w == T.LargestImaginaryPart ? :LI : | ||
| :SI | ||
| end | ||
|
|
||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| module LinearSolveJacobiDavidsonExt | ||
|
|
||
| using LinearSolve | ||
| using LinearAlgebra | ||
| using JacobiDavidson | ||
| using SciMLBase: SciMLBase, ReturnCode | ||
|
|
||
| function SciMLBase.solve( | ||
| prob::LinearSolve.EigenvalueProblem, | ||
| alg::LinearSolve.JacobiDavidsonJL, | ||
| args...; kwargs... | ||
| ) | ||
| # JacobiDavidson.jl's `jdqz` (generalized solver) is broken upstream: it | ||
| # references an undefined `verbose` variable that is absent from its | ||
| # signature. Until that is fixed, only the standard solver `jdqr` is wired | ||
| # up here; point users at the backends that do support generalized problems. | ||
| prob.B === nothing || | ||
| error("The JacobiDavidson backend currently supports standard eigenvalue problems only. Use `ArpackJL()` or `KrylovKitEigen()` for generalized problems.") | ||
|
|
||
| n = size(prob.A, 2) | ||
| nev = LinearSolve.default_num_eigenpairs(prob) | ||
| target = _jd_target(prob) | ||
| # Search-subspace bounds, capped at the problem size. Users may override | ||
| # `subspace_dimensions` (and any other jdqr keyword) via the algorithm. | ||
| hi = min(max(2 * nev + 10, 20), n) | ||
| lo = min(max(nev + 2, 8), hi) | ||
| defaults = (; pairs = nev, target = target, subspace_dimensions = lo:hi) | ||
| kw = (; defaults..., prob.kwargs..., alg.kwargs..., kwargs...) | ||
|
|
||
| out = JacobiDavidson.jdqr(prob.A; kw...) | ||
| values, vectors = _jd_standard_pairs(prob.A, out[1]) | ||
|
|
||
| values, vectors = LinearSolve._select_eigenpairs( | ||
| values, vectors, nev, prob.eigentarget, prob.shift | ||
| ) | ||
| retcode = length(values) >= nev ? ReturnCode.Success : ReturnCode.ConvergenceFailure | ||
| return LinearSolve.build_eigenvalue_solution( | ||
| prob, alg, values, vectors; retcode, stats = out[end] | ||
| ) | ||
| end | ||
|
|
||
| # Map the problem's spectral selector onto a JacobiDavidson `Target`. A supplied | ||
| # `shift` is the natural interior target (`Near`), which is Jacobi-Davidson's | ||
| # strength; otherwise `eigentarget` selects an extremal target. | ||
| function _jd_target(prob) | ||
| prob.shift !== nothing && return JacobiDavidson.Near(ComplexF64(prob.shift)) | ||
| T = LinearSolve.EigenvalueTarget | ||
| w = prob.eigentarget | ||
| return w == T.LargestMagnitude ? JacobiDavidson.LargestMagnitude(0.0 + 0.0im) : | ||
| w == T.SmallestMagnitude ? JacobiDavidson.SmallestMagnitude(0.0 + 0.0im) : | ||
| w == T.LargestRealPart ? JacobiDavidson.LargestRealPart(0.0 + 0.0im) : | ||
| w == T.SmallestRealPart ? JacobiDavidson.SmallestRealPart(0.0 + 0.0im) : | ||
| w == T.LargestImaginaryPart ? JacobiDavidson.LargestImaginaryPart(0.0 + 0.0im) : | ||
| JacobiDavidson.SmallestImaginaryPart(0.0 + 0.0im) | ||
| end | ||
|
|
||
| # jdqr yields a partial Schur decomposition `A*Q = Q*R`. Eigenpairs are recovered | ||
| # from the small projected `R = Q'AQ`: if `R*y = λ*y` then `A*(Q*y) = λ*(Q*y)`. | ||
| function _jd_standard_pairs(A, pschur) | ||
| T = complex(float(eltype(A))) | ||
| k = length(pschur.values) | ||
| k == 0 && return (T[], Matrix{T}(undef, size(A, 1), 0)) | ||
| Q = pschur.Q[:, 1:k] | ||
| F = eigen(Q' * (A * Q)) | ||
| return (F.values, Q * F.vectors) | ||
| end | ||
|
|
||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 a doc poge about the eigenvalue solvers, and a tutorial