-
Notifications
You must be signed in to change notification settings - Fork 40
Add opnorm implementation #375
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
base: main
Are you sure you want to change the base?
Changes from all commits
eeed18a
a67ac20
13c04c7
f2c3d06
cf1031a
88b5df5
e29a76e
4fce741
3b17e79
67aeac9
829a552
8b0da8c
2d7ba5d
5af8752
a3d545f
a672263
779431f
b4dd14d
ad73692
50b27b6
7111af1
15b6dbd
accfea2
15ae552
a1d8bcc
755f82f
12deb75
1565241
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 |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| module LinearOperatorsOpNormExt | ||
|
|
||
| using LinearOperators | ||
| using Arpack | ||
| using TSVD | ||
| using LinearAlgebra | ||
| using GenericLinearAlgebra | ||
| import Arpack: eigs, svds | ||
|
|
||
| import LinearOperators: estimate_opnorm | ||
|
|
||
| function estimate_opnorm(B::AbstractLinearOperator; kwargs...) | ||
| _estimate_opnorm(B, eltype(B); kwargs...) | ||
| end | ||
|
|
||
| # Fallback for non-standard number types (uses TSVD) | ||
| function _estimate_opnorm(B, ::Type{T}; kwargs...) where {T} | ||
| _, s, _ = tsvd(B, 1; kwargs...) | ||
| return s[1], true | ||
| end | ||
|
|
||
| # Optimized path for standard FloatingPoint/Complex types | ||
| # Note: Arpack strictly only supports Float32, Float64, ComplexF32, and ComplexF64. | ||
| # Any other type (like BigFloat or Float16) must use the TSVD fallback above. | ||
| function _estimate_opnorm( | ||
| B, | ||
| ::Type{T}; | ||
| kwargs... | ||
| ) where {T <: Union{Float32, Float64, ComplexF32, ComplexF64}} | ||
| if ishermitian(B) | ||
| return opnorm_eig(B; kwargs...) | ||
| else | ||
| return opnorm_svd(B; kwargs...) | ||
| end | ||
| end | ||
|
|
||
| function opnorm_eig(B; max_attempts::Int = 3, tiny_dense_threshold = 5, kwargs...) | ||
| n = size(B, 1) | ||
|
|
||
| # Check if we can use direct dense methods (only if B supports conversion to Matrix) | ||
| if n ≤ tiny_dense_threshold | ||
| try | ||
| return maximum(abs, eigen(Matrix(B)).values), true | ||
| catch | ||
| # If Matrix(B) fails or isn't efficient, fall through to iterative | ||
| end | ||
| end | ||
|
|
||
| nev = 1 | ||
| ncv = max(20, 2*nev + 1) | ||
|
|
||
| for attempt = 1:max_attempts | ||
| try | ||
| d, nconv, _, _, _ = eigs(B; nev = nev, ncv = ncv, which = :LM, ritzvec = false, check = 1, kwargs...) | ||
|
|
||
| if nconv == 1 | ||
| return abs(d[1]), true | ||
| end | ||
|
|
||
| catch e | ||
| if e isa Arpack.ARPACKException || | ||
| occursin("ARPACK", string(e)) || | ||
| occursin("AUPD", string(e)) | ||
| if ncv >= n | ||
| @warn "ARPACK failed and NCV cannot be increased further." exception=e | ||
| rethrow(e) | ||
| end | ||
| else | ||
| rethrow(e) | ||
| end | ||
| end | ||
|
|
||
| if attempt < max_attempts | ||
| old_ncv = ncv | ||
| ncv = min(2 * ncv, n) | ||
| if ncv > old_ncv | ||
| @warn "opnorm_eig: increasing NCV from $old_ncv to $ncv and retrying." | ||
| else | ||
| break | ||
| end | ||
| end | ||
| end | ||
|
|
||
| return NaN, false | ||
| end | ||
|
|
||
| function opnorm_svd(B; max_attempts::Int = 3, tiny_dense_threshold = 5, kwargs...) | ||
| m, n = size(B) | ||
|
|
||
| if min(m, n) ≤ tiny_dense_threshold | ||
| try | ||
| return maximum(svd(Matrix(B)).S), true | ||
| catch | ||
| # Fallback | ||
| end | ||
| end | ||
|
|
||
| min_dim = min(m, n) | ||
|
|
||
| nsv = 1 | ||
| ncv = 10 | ||
|
|
||
| for attempt = 1:max_attempts | ||
| try | ||
| s, nconv, _, _, _ = svds(B; nsv = nsv, ncv = ncv, ritzvec = false, check = 1, kwargs...) | ||
|
|
||
| if nconv >= 1 | ||
| return maximum(s.S), true | ||
| end | ||
|
|
||
| catch e | ||
| if e isa Arpack.ARPACKException || | ||
| occursin("ARPACK", string(e)) || | ||
| occursin("AUPD", string(e)) | ||
| if ncv >= min_dim | ||
| @warn "ARPACK failed and NCV cannot be increased further." exception=e | ||
| rethrow(e) | ||
| end | ||
| else | ||
| rethrow(e) | ||
| end | ||
| end | ||
|
|
||
| if attempt < max_attempts | ||
| old_ncv = ncv | ||
| ncv = min(2 * ncv, min_dim) | ||
| if ncv > old_ncv | ||
| @warn "opnorm_svd: increasing NCV from $old_ncv to $ncv and retrying." | ||
| else | ||
| break | ||
| end | ||
| end | ||
| end | ||
|
|
||
| return NaN, false | ||
| end | ||
|
|
||
| end | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,5 @@ | ||||||||
| export check_ctranspose, | ||||||||
| check_hermitian, check_positive_definite, normest, solve_shifted_system!, ldiv! | ||||||||
| check_hermitian, check_positive_definite, normest, solve_shifted_system!, ldiv!, estimate_opnorm | ||||||||
| import LinearAlgebra.ldiv! | ||||||||
|
|
||||||||
| """ | ||||||||
|
|
@@ -261,8 +261,7 @@ Solves the linear system Bx = b. | |||||||
|
|
||||||||
| - `x::AbstractVector{T}`: The modified solution vector containing the solution to the linear system. | ||||||||
|
|
||||||||
| ### Examples: | ||||||||
|
|
||||||||
| ### Example: | ||||||||
|
Member
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.
Suggested change
|
||||||||
| ```julia | ||||||||
|
|
||||||||
|
farhadrclass marked this conversation as resolved.
|
||||||||
| # Create an L-BFGS operator | ||||||||
|
|
@@ -276,6 +275,7 @@ b = rand(10) | |||||||
| ldiv!(x, B, b) | ||||||||
|
|
||||||||
| # The vector `x` now contains the solution | ||||||||
| ``` | ||||||||
| """ | ||||||||
|
|
||||||||
| function ldiv!( | ||||||||
|
|
@@ -287,3 +287,28 @@ function ldiv!( | |||||||
| solve_shifted_system!(x, B, b, T(0.0)) | ||||||||
| return x | ||||||||
| end | ||||||||
|
|
||||||||
|
Member
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.
Suggested change
|
||||||||
|
|
||||||||
| """ | ||||||||
| estimate_opnorm(B::AbstractLinearOperator; kwargs...) | ||||||||
|
|
||||||||
| Compute the estimate of the operator 2-norm (largest singular value) of a linear operator `B`. | ||||||||
|
Member
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. "the" estimate ? or "an" estimate? |
||||||||
|
|
||||||||
| This method dispatches to efficient algorithms depending on the properties and size of `B`. | ||||||||
| If `B` wraps a small dense matrix, it may use direct LAPACK routines. For larger operators, | ||||||||
| it uses iterative methods (ARPACK or TSVD) to estimate the norm efficiently. | ||||||||
|
Comment on lines
+297
to
+299
Member
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. It lacks a little bit information on what is exactly done here. In which case you do what |
||||||||
|
|
||||||||
| **Note:** This function allocates memory. It requires `Arpack.jl` and `TSVD.jl` | ||||||||
| (and `GenericLinearAlgebra.jl` for generic types) to be loaded. | ||||||||
|
|
||||||||
| # Arguments | ||||||||
| - `B::AbstractLinearOperator`: The linear operator to analyze. | ||||||||
| - `kwargs...`: Optional keyword arguments passed to the underlying norm estimation routines | ||||||||
| (e.g., `max_attempts`, `tiny_dense_threshold`). | ||||||||
|
Comment on lines
+306
to
+307
Member
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. To document keyword arguments, it might be better to add a new section here |
||||||||
|
|
||||||||
| # Returns | ||||||||
| - A tuple `(norm, success)` where: | ||||||||
| - `norm` is the estimated operator 2-norm of `B` (largest singular value or eigenvalue in absolute value). | ||||||||
| - `success` is a boolean indicating whether the iterative method (if used) reported successful convergence. | ||||||||
| """ | ||||||||
| function estimate_opnorm end | ||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| using TSVD | ||
| using Arpack | ||
| using GenericLinearAlgebra | ||
|
|
||
| @testset "estimate_opnorm type stability and dispatch" begin | ||
| @testset "Type $T" for T in [Float32, Float64, ComplexF32, ComplexF64] | ||
| # A) Rectangular Matrix (Forces SVD path) | ||
| J_mat = zeros(T, 3, 2) | ||
| J_mat[1, 1] = 3.0 | ||
| J_mat[2, 2] = 1.0 | ||
| J_op = LinearOperator(J_mat) | ||
|
|
||
| σ, ok = estimate_opnorm(J_op) | ||
| @test ok | ||
| @test isapprox(σ, 3.0; rtol = 1e-5) | ||
|
|
||
| # B) Hermitian Wrapper (Forces Eig path via dispatch) | ||
| H_data = rand(T, 10, 10) | ||
| H_data = H_data + H_data' | ||
| H = Hermitian(H_data) | ||
|
|
||
| H_op = LinearOperator(H) | ||
|
|
||
| exact_norm = opnorm(H) # Uses standard LinearAlgebra | ||
| est_norm, ok = estimate_opnorm(H_op) # Uses our extension | ||
|
|
||
| @test ok | ||
| @test isapprox(est_norm, exact_norm; rtol = 1e-5) | ||
|
|
||
| # C) Symmetric Wrapper (Dispatch varies by Real vs Complex) | ||
| S_data = rand(T, 10, 10) | ||
| S_data = S_data + transpose(S_data) | ||
| S = Symmetric(S_data) | ||
|
|
||
| S_op = LinearOperator(S) | ||
|
|
||
| exact_norm_S = opnorm(Matrix(S)) | ||
| est_norm_S, ok_S = estimate_opnorm(S_op) | ||
|
|
||
| @test ok_S | ||
| @test isapprox(est_norm_S, exact_norm_S; rtol = 1e-5) | ||
|
|
||
| # C.1) Specific check: Ensure Symmetric{Complex} works | ||
| # (This is NOT Hermitian, so it must successfully fall back to the SVD path) | ||
| if T <: Complex | ||
| # Ensure the operator didn't falsely flag itself as Hermitian | ||
| @test !ishermitian(S_op) | ||
| end | ||
| end | ||
|
|
||
| @testset "BigFloat (Generic)" begin | ||
| # ------------------------- | ||
| B_mat = Matrix{BigFloat}([2.0 0.0; 0.0 -1.0]) | ||
| B_op = LinearOperator(B_mat) | ||
|
|
||
| λ_bf, ok_bf = estimate_opnorm(B_op) | ||
|
|
||
| @test ok_bf | ||
| @test isapprox(λ_bf, BigFloat(2); rtol = 1e-12) | ||
| end | ||
| end |
Uh oh!
There was an error while loading. Please reload this page.