From eeed18aeffc7175b16c80ee1f5f402d91ab10393 Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Thu, 21 Aug 2025 17:06:13 -0400 Subject: [PATCH 01/26] Add robust opnorm implementation and tests Introduces a new opnorm function that efficiently computes the operator 2-norm for matrices and linear operators, dispatching to LAPACK, ARPACK, or TSVD as appropriate. Adds comprehensive tests for opnorm covering both Float64 and BigFloat types, and integrates the new test file into the test suite. Update Project.toml --- Project.toml | 4 ++ src/utilities.jl | 122 ++++++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 1 + test/test_opnorm.jl | 31 +++++++++++ 4 files changed, 158 insertions(+) create mode 100644 test/test_opnorm.jl diff --git a/Project.toml b/Project.toml index 6f31907b..0bdd1f2a 100644 --- a/Project.toml +++ b/Project.toml @@ -4,10 +4,12 @@ version = "2.11.0" [deps] FastClosures = "9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a" +GenericLinearAlgebra = "14197337-ba66-59df-a3e3-ca00e7dcff7a" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Requires = "ae029012-a4dd-5104-9daa-d747884805df" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +TSVD = "9449cd9e-2762-5aa3-a617-5413e99d722e" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" [weakdeps] @@ -31,6 +33,7 @@ AMDGPU = "2" CUDA = "5" ChainRulesCore = "1" FastClosures = "0.2, 0.3" +GenericLinearAlgebra = "0.3.18" JLArrays = "0.1, 0.2" LDLFactorizations = "0.9, 0.10" LinearAlgebra = "1" @@ -38,6 +41,7 @@ Metal = "1.1" Printf = "1" Requires = "1" SparseArrays = "1" +TSVD = "0.4.4" TimerOutputs = "^0.5" julia = "^1.6.0" diff --git a/src/utilities.jl b/src/utilities.jl index 1252d959..111a5205 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -1,6 +1,11 @@ export check_ctranspose, check_hermitian, check_positive_definite, normest, solve_shifted_system!, ldiv! import LinearAlgebra.ldiv! +import LinearAlgebra: opnorm +using GenericLinearAlgebra +using TSVD + +export opnorm """ normest(S) estimates the matrix 2-norm of S. @@ -287,3 +292,120 @@ function ldiv!( solve_shifted_system!(x, B, b, T(0.0)) return x end + +""" + opnorm(B; kwargs...) + +Compute the operator 2-norm (largest singular value) of a matrix or linear operator `B`. +This method dispatches to efficient algorithms depending on the type and size of `B`: +for small dense matrices, it uses direct LAPACK routines; for larger or structured operators, +it uses iterative methods (ARPACK or TSVD) to estimate the norm efficiently. + +# Arguments +- `B`: A matrix or linear operator. +- `kwargs...`: Optional keyword arguments passed to the underlying norm estimation routines. + +# Returns +- The estimated operator 2-norm of `B` (largest singular value or eigenvalue in absolute value). +""" + +function LinearAlgebra.opnorm(B; kwargs...) + _opnorm(B, eltype(B); kwargs...) +end + +# This method will be picked if eltype is one of the four types Arpack supports +# (Float32, Float64, ComplexF32, ComplexF64). +function _opnorm( + B, + ::Type{T}; + kwargs..., +) where {T <: Union{Float32, Float64, ComplexF32, ComplexF64}} + m, n = size(B) + return (m == n ? opnorm_eig : opnorm_svd)(B; kwargs...) +end + +# Fallback for everything else +function _opnorm(B, ::Type{T}; kwargs...) where {T} + _, s, _ = tsvd(B) + return s[1], true # return largest singular value +end + +function opnorm_eig(B; max_attempts::Int = 3) + n = size(B, 1) + # 1) tiny dense Float64: direct LAPACK + if n ≤ 5 + return maximum(abs, eigen(Matrix(B)).values), true + end + + # 2) iterative ARPACK + nev, ncv = 1, max(20, 2*1 + 1) + attempt, λ, have_eig = 0, zero(eltype(B)), false + + while !(have_eig || attempt >= max_attempts) + attempt += 1 + try + # Estimate largest eigenvalue in absolute value + d, nconv, niter, nmult, resid = + eigs(B; nev = nev, ncv = ncv, which = :LM, ritzvec = false, check = 1) + + # Check if eigenvalue has converged + have_eig = nconv == 1 + if have_eig + λ = abs(d[1]) # Take absolute value of the largest eigenvalue + break # Exit loop if successful + else + # Increase NCV for the next attempt if convergence wasn't achieved + ncv = min(2 * ncv, n) + end + catch e + if occursin("XYAUPD_Exception", string(e)) && ncv < n + @warn "Arpack error: $e. Increasing NCV to $ncv and retrying." + ncv = min(2 * ncv, n) # Increase NCV but don't exceed matrix size + else + rethrow(e) # Re-raise if it's a different error + end + end + end + + return λ, have_eig +end + +function opnorm_svd(J; max_attempts::Int = 3) + m, n = size(J) + # 1) tiny dense Float64: direct LAPACK + if min(m, n) ≤ 5 + return maximum(svd(Matrix(J)).S), true + end + + # 2) iterative ARPACK‐SVD + nsv, ncv = 1, 10 + attempt, σ, have_svd = 0, zero(eltype(J)), false + n = min(m, n) + + while !(have_svd || attempt >= max_attempts) + attempt += 1 + try + # Estimate largest singular value + s, nconv, niter, nmult, resid = svds(J; nsv = nsv, ncv = ncv, ritzvec = false, check = 1) + + # Check if singular value has converged + have_svd = nconv >= 1 + if have_svd + σ = maximum(s.S) # Take the largest singular value + break # Exit loop if successful + else + # Increase NCV for the next attempt if convergence wasn't achieved + ncv = min(2 * ncv, n) + end + catch e + if occursin("XYAUPD_Exception", string(e)) && ncv < n + @warn "Arpack error: $e. Increasing NCV to $ncv and retrying." + ncv = min(2 * ncv, n) # Increase NCV but don't exceed matrix size + else + rethrow(e) # Re-raise if it's a different error + end + end + end + + return σ, have_svd +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 2c8e6d1a..196aecb8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -13,6 +13,7 @@ include("test_kron.jl") include("test_callable.jl") include("test_deprecated.jl") include("test_normest.jl") +include("test_opnorm.jl") include("test_diag.jl") include("test_chainrules.jl") include("test_solve_shifted_system.jl") diff --git a/test/test_opnorm.jl b/test/test_opnorm.jl new file mode 100644 index 00000000..f610f048 --- /dev/null +++ b/test/test_opnorm.jl @@ -0,0 +1,31 @@ +function test_opnorm() + @testset "opnorm and TSVD tests (LinearOperators)" begin + # 1) Square Float64 via direct LAPACK or ARPACK + A_mat = [2.0 0.0; 0.0 -1.0] + A_op = LinearOperator(A_mat) + λ, ok = opnorm(A_op) + @test ok == true + @test isapprox(λ, 2.0; rtol=1e-12) + + # 2) Rectangular Float64 via direct LAPACK or ARPACK SVD + J_mat = [3.0 0.0 0.0; 0.0 1.0 0.0] + J_op = LinearOperator(J_mat) + σ, ok_sv = opnorm(J_op) + @test ok_sv == true + @test isapprox(σ, 3.0; rtol=1e-12) + + # 3) Square BigFloat via TSVD + B_mat = Matrix{BigFloat}([2.0 0.0; 0.0 -1.0]) + B_op = LinearOperator(B_mat) + λ_bf, ok_bf = opnorm(B_op) + @test ok_bf == true + @test isapprox(λ_bf, BigFloat(2); rtol=1e-12) + + # 4) Rectangular BigFloat via rectangular TSVD + JR_mat = Matrix{BigFloat}([3.0 0.0 0.0; 0.0 1.0 0.0]) + JR_op = LinearOperator(JR_mat) + σ_bf, ok_bf2 = opnorm(JR_op) + @test ok_bf2 == true + @test isapprox(σ_bf, BigFloat(3); rtol=1e-12) + end +end \ No newline at end of file From a67ac201806101a4676524fe5d3e90ff283e00da Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Thu, 21 Aug 2025 18:34:53 -0400 Subject: [PATCH 02/26] Update test_opnorm.jl --- test/test_opnorm.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_opnorm.jl b/test/test_opnorm.jl index f610f048..c178ab6f 100644 --- a/test/test_opnorm.jl +++ b/test/test_opnorm.jl @@ -28,4 +28,6 @@ function test_opnorm() @test ok_bf2 == true @test isapprox(σ_bf, BigFloat(3); rtol=1e-12) end -end \ No newline at end of file +end + +test_opnorm() \ No newline at end of file From 13c04c77326f0316807ac60775376688b052d83b Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:55:44 -0500 Subject: [PATCH 03/26] Add Arpack as dependency and import in utilities.jl Added Arpack to Project.toml dependencies and compat section. Updated utilities.jl to import Arpack, preparing for usage of its functionality. --- Project.toml | 2 ++ src/utilities.jl | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 0bdd1f2a..76cafe07 100644 --- a/Project.toml +++ b/Project.toml @@ -3,6 +3,7 @@ uuid = "5c8ed15e-5a4c-59e4-a42b-c7e8811fb125" version = "2.11.0" [deps] +Arpack = "7d9fca2a-8960-54d3-9f78-7d1dccf2cb97" FastClosures = "9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a" GenericLinearAlgebra = "14197337-ba66-59df-a3e3-ca00e7dcff7a" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" @@ -30,6 +31,7 @@ LinearOperatorsMetalExt = "Metal" [compat] AMDGPU = "2" +Arpack = "0.5.4" CUDA = "5" ChainRulesCore = "1" FastClosures = "0.2, 0.3" diff --git a/src/utilities.jl b/src/utilities.jl index 111a5205..b104ab85 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -4,7 +4,7 @@ import LinearAlgebra.ldiv! import LinearAlgebra: opnorm using GenericLinearAlgebra using TSVD - +using Arpack export opnorm """ From f2c3d061eb5d57b247ebbba2fe3c5acb91e2e7a0 Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Thu, 21 Aug 2025 17:06:13 -0400 Subject: [PATCH 04/26] Add robust opnorm implementation and tests Introduces a new opnorm function that efficiently computes the operator 2-norm for matrices and linear operators, dispatching to LAPACK, ARPACK, or TSVD as appropriate. Adds comprehensive tests for opnorm covering both Float64 and BigFloat types, and integrates the new test file into the test suite. Update Project.toml --- Project.toml | 31 +++++++---- src/utilities.jl | 122 ++++++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 1 + test/test_opnorm.jl | 31 +++++++++++ 4 files changed, 176 insertions(+), 9 deletions(-) create mode 100644 test/test_opnorm.jl diff --git a/Project.toml b/Project.toml index 10193bdf..c20c76c6 100644 --- a/Project.toml +++ b/Project.toml @@ -4,9 +4,11 @@ version = "2.11.0" [deps] FastClosures = "9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a" +GenericLinearAlgebra = "14197337-ba66-59df-a3e3-ca00e7dcff7a" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +TSVD = "9449cd9e-2762-5aa3-a617-5413e99d722e" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" [weakdeps] @@ -29,12 +31,23 @@ LinearOperatorsMetalExt = "Metal" AMDGPU = "2.2.0" CUDA = "5.9.6" ChainRulesCore = "1" -FastClosures = "0.3" -JLArrays = "0.3" -LDLFactorizations = "0.10" -LinearAlgebra = "1.10" -Metal = "1.9.1" -Printf = "1.10" -SparseArrays = "1.10" -TimerOutputs = "0.5" -julia = "1.10" +FastClosures = "0.2, 0.3" +GenericLinearAlgebra = "0.3.18" +JLArrays = "0.1, 0.2" +LDLFactorizations = "0.9, 0.10" +LinearAlgebra = "1" +Metal = "1.1" +Printf = "1" +Requires = "1" +SparseArrays = "1" +TSVD = "0.4.4" +TimerOutputs = "^0.5" +julia = "^1.6.0" + +[extras] +AMDGPU = "21141c5a-9bdb-4563-92ae-f87d6854732e" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" +JLArrays = "27aeb0d3-9eb9-45fb-866b-73c2ecf80fcb" +LDLFactorizations = "40e66cde-538c-5869-a4ad-c39174c6795b" +Metal = "dde4c033-4e86-420c-a63e-0dd931031962" diff --git a/src/utilities.jl b/src/utilities.jl index 1252d959..111a5205 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -1,6 +1,11 @@ export check_ctranspose, check_hermitian, check_positive_definite, normest, solve_shifted_system!, ldiv! import LinearAlgebra.ldiv! +import LinearAlgebra: opnorm +using GenericLinearAlgebra +using TSVD + +export opnorm """ normest(S) estimates the matrix 2-norm of S. @@ -287,3 +292,120 @@ function ldiv!( solve_shifted_system!(x, B, b, T(0.0)) return x end + +""" + opnorm(B; kwargs...) + +Compute the operator 2-norm (largest singular value) of a matrix or linear operator `B`. +This method dispatches to efficient algorithms depending on the type and size of `B`: +for small dense matrices, it uses direct LAPACK routines; for larger or structured operators, +it uses iterative methods (ARPACK or TSVD) to estimate the norm efficiently. + +# Arguments +- `B`: A matrix or linear operator. +- `kwargs...`: Optional keyword arguments passed to the underlying norm estimation routines. + +# Returns +- The estimated operator 2-norm of `B` (largest singular value or eigenvalue in absolute value). +""" + +function LinearAlgebra.opnorm(B; kwargs...) + _opnorm(B, eltype(B); kwargs...) +end + +# This method will be picked if eltype is one of the four types Arpack supports +# (Float32, Float64, ComplexF32, ComplexF64). +function _opnorm( + B, + ::Type{T}; + kwargs..., +) where {T <: Union{Float32, Float64, ComplexF32, ComplexF64}} + m, n = size(B) + return (m == n ? opnorm_eig : opnorm_svd)(B; kwargs...) +end + +# Fallback for everything else +function _opnorm(B, ::Type{T}; kwargs...) where {T} + _, s, _ = tsvd(B) + return s[1], true # return largest singular value +end + +function opnorm_eig(B; max_attempts::Int = 3) + n = size(B, 1) + # 1) tiny dense Float64: direct LAPACK + if n ≤ 5 + return maximum(abs, eigen(Matrix(B)).values), true + end + + # 2) iterative ARPACK + nev, ncv = 1, max(20, 2*1 + 1) + attempt, λ, have_eig = 0, zero(eltype(B)), false + + while !(have_eig || attempt >= max_attempts) + attempt += 1 + try + # Estimate largest eigenvalue in absolute value + d, nconv, niter, nmult, resid = + eigs(B; nev = nev, ncv = ncv, which = :LM, ritzvec = false, check = 1) + + # Check if eigenvalue has converged + have_eig = nconv == 1 + if have_eig + λ = abs(d[1]) # Take absolute value of the largest eigenvalue + break # Exit loop if successful + else + # Increase NCV for the next attempt if convergence wasn't achieved + ncv = min(2 * ncv, n) + end + catch e + if occursin("XYAUPD_Exception", string(e)) && ncv < n + @warn "Arpack error: $e. Increasing NCV to $ncv and retrying." + ncv = min(2 * ncv, n) # Increase NCV but don't exceed matrix size + else + rethrow(e) # Re-raise if it's a different error + end + end + end + + return λ, have_eig +end + +function opnorm_svd(J; max_attempts::Int = 3) + m, n = size(J) + # 1) tiny dense Float64: direct LAPACK + if min(m, n) ≤ 5 + return maximum(svd(Matrix(J)).S), true + end + + # 2) iterative ARPACK‐SVD + nsv, ncv = 1, 10 + attempt, σ, have_svd = 0, zero(eltype(J)), false + n = min(m, n) + + while !(have_svd || attempt >= max_attempts) + attempt += 1 + try + # Estimate largest singular value + s, nconv, niter, nmult, resid = svds(J; nsv = nsv, ncv = ncv, ritzvec = false, check = 1) + + # Check if singular value has converged + have_svd = nconv >= 1 + if have_svd + σ = maximum(s.S) # Take the largest singular value + break # Exit loop if successful + else + # Increase NCV for the next attempt if convergence wasn't achieved + ncv = min(2 * ncv, n) + end + catch e + if occursin("XYAUPD_Exception", string(e)) && ncv < n + @warn "Arpack error: $e. Increasing NCV to $ncv and retrying." + ncv = min(2 * ncv, n) # Increase NCV but don't exceed matrix size + else + rethrow(e) # Re-raise if it's a different error + end + end + end + + return σ, have_svd +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 2c8e6d1a..196aecb8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -13,6 +13,7 @@ include("test_kron.jl") include("test_callable.jl") include("test_deprecated.jl") include("test_normest.jl") +include("test_opnorm.jl") include("test_diag.jl") include("test_chainrules.jl") include("test_solve_shifted_system.jl") diff --git a/test/test_opnorm.jl b/test/test_opnorm.jl new file mode 100644 index 00000000..f610f048 --- /dev/null +++ b/test/test_opnorm.jl @@ -0,0 +1,31 @@ +function test_opnorm() + @testset "opnorm and TSVD tests (LinearOperators)" begin + # 1) Square Float64 via direct LAPACK or ARPACK + A_mat = [2.0 0.0; 0.0 -1.0] + A_op = LinearOperator(A_mat) + λ, ok = opnorm(A_op) + @test ok == true + @test isapprox(λ, 2.0; rtol=1e-12) + + # 2) Rectangular Float64 via direct LAPACK or ARPACK SVD + J_mat = [3.0 0.0 0.0; 0.0 1.0 0.0] + J_op = LinearOperator(J_mat) + σ, ok_sv = opnorm(J_op) + @test ok_sv == true + @test isapprox(σ, 3.0; rtol=1e-12) + + # 3) Square BigFloat via TSVD + B_mat = Matrix{BigFloat}([2.0 0.0; 0.0 -1.0]) + B_op = LinearOperator(B_mat) + λ_bf, ok_bf = opnorm(B_op) + @test ok_bf == true + @test isapprox(λ_bf, BigFloat(2); rtol=1e-12) + + # 4) Rectangular BigFloat via rectangular TSVD + JR_mat = Matrix{BigFloat}([3.0 0.0 0.0; 0.0 1.0 0.0]) + JR_op = LinearOperator(JR_mat) + σ_bf, ok_bf2 = opnorm(JR_op) + @test ok_bf2 == true + @test isapprox(σ_bf, BigFloat(3); rtol=1e-12) + end +end \ No newline at end of file From cf1031a21ca366b5e7e0d9afd3bff6db70c78db9 Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Thu, 21 Aug 2025 18:34:53 -0400 Subject: [PATCH 05/26] Update test_opnorm.jl --- test/test_opnorm.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_opnorm.jl b/test/test_opnorm.jl index f610f048..c178ab6f 100644 --- a/test/test_opnorm.jl +++ b/test/test_opnorm.jl @@ -28,4 +28,6 @@ function test_opnorm() @test ok_bf2 == true @test isapprox(σ_bf, BigFloat(3); rtol=1e-12) end -end \ No newline at end of file +end + +test_opnorm() \ No newline at end of file From 88b5df594204ecac36a5fa882fa75584909b848d Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:55:44 -0500 Subject: [PATCH 06/26] Add Arpack as dependency and import in utilities.jl Added Arpack to Project.toml dependencies and compat section. Updated utilities.jl to import Arpack, preparing for usage of its functionality. --- Project.toml | 1 + src/utilities.jl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index c20c76c6..60059b34 100644 --- a/Project.toml +++ b/Project.toml @@ -3,6 +3,7 @@ uuid = "5c8ed15e-5a4c-59e4-a42b-c7e8811fb125" version = "2.11.0" [deps] +Arpack = "7d9fca2a-8960-54d3-9f78-7d1dccf2cb97" FastClosures = "9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a" GenericLinearAlgebra = "14197337-ba66-59df-a3e3-ca00e7dcff7a" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" diff --git a/src/utilities.jl b/src/utilities.jl index 111a5205..b104ab85 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -4,7 +4,7 @@ import LinearAlgebra.ldiv! import LinearAlgebra: opnorm using GenericLinearAlgebra using TSVD - +using Arpack export opnorm """ From e29a76e66f69ac74afed3d3c3a7f2e7007f429b6 Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Tue, 20 Jan 2026 12:20:30 -0500 Subject: [PATCH 07/26] Rename opnorm to estimate_opnorm and update tests Replaces the opnorm function with estimate_opnorm throughout the codebase for clarity. Updates all relevant documentation, exports, and test files to use the new function name, and renames the test file accordingly. Update utilities.jl Update test_estimate_opnorm.jl --- src/utilities.jl | 55 +++++++++++++++--------------------- test/runtests.jl | 2 +- test/test_estimate_opnorm.jl | 29 +++++++++++++++++++ test/test_opnorm.jl | 33 ---------------------- 4 files changed, 52 insertions(+), 67 deletions(-) create mode 100644 test/test_estimate_opnorm.jl delete mode 100644 test/test_opnorm.jl diff --git a/src/utilities.jl b/src/utilities.jl index b104ab85..3f9e43b5 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -1,11 +1,10 @@ export check_ctranspose, check_hermitian, check_positive_definite, normest, solve_shifted_system!, ldiv! import LinearAlgebra.ldiv! -import LinearAlgebra: opnorm using GenericLinearAlgebra using TSVD using Arpack -export opnorm +export estimate_opnorm """ normest(S) estimates the matrix 2-norm of S. @@ -175,7 +174,7 @@ The method uses a two-loop recursion-like approach with modifications to handle ### Example -```julia + using Random # Problem setup @@ -203,7 +202,6 @@ result = solve_shifted_system!(x, B, b, σ) # Check that the solution is close enough (residual test) @assert norm(B * x + σ * x - b) / norm(b) < 1e-8 -``` ### References @@ -268,7 +266,6 @@ Solves the linear system Bx = b. ### Examples: -```julia # Create an L-BFGS operator B = LBFGSOperator(10) @@ -294,9 +291,9 @@ function ldiv!( end """ - opnorm(B; kwargs...) + estimate_opnorm(B; kwargs...) -Compute the operator 2-norm (largest singular value) of a matrix or linear operator `B`. +Compute the estimate of the operator 2-norm (largest singular value) of a matrix or linear operator `B`. This method dispatches to efficient algorithms depending on the type and size of `B`: for small dense matrices, it uses direct LAPACK routines; for larger or structured operators, it uses iterative methods (ARPACK or TSVD) to estimate the norm efficiently. @@ -309,13 +306,13 @@ it uses iterative methods (ARPACK or TSVD) to estimate the norm efficiently. - The estimated operator 2-norm of `B` (largest singular value or eigenvalue in absolute value). """ -function LinearAlgebra.opnorm(B; kwargs...) - _opnorm(B, eltype(B); kwargs...) +function estimate_opnorm(B; kwargs...) + _estimate_opnorm(B, eltype(B); kwargs...) end # This method will be picked if eltype is one of the four types Arpack supports # (Float32, Float64, ComplexF32, ComplexF64). -function _opnorm( +function _estimate_opnorm( B, ::Type{T}; kwargs..., @@ -324,16 +321,15 @@ function _opnorm( return (m == n ? opnorm_eig : opnorm_svd)(B; kwargs...) end -# Fallback for everything else -function _opnorm(B, ::Type{T}; kwargs...) where {T} +function _estimate_opnorm(B, ::Type{T}; kwargs...) where {T} _, s, _ = tsvd(B) - return s[1], true # return largest singular value + return s[1], true end -function opnorm_eig(B; max_attempts::Int = 3) +function opnorm_eig(B; max_attempts::Int = 3, tiny_dense_threshold = 5) n = size(B, 1) # 1) tiny dense Float64: direct LAPACK - if n ≤ 5 + if n ≤ tiny_dense_threshold return maximum(abs, eigen(Matrix(B)).values), true end @@ -344,25 +340,22 @@ function opnorm_eig(B; max_attempts::Int = 3) while !(have_eig || attempt >= max_attempts) attempt += 1 try - # Estimate largest eigenvalue in absolute value d, nconv, niter, nmult, resid = eigs(B; nev = nev, ncv = ncv, which = :LM, ritzvec = false, check = 1) - # Check if eigenvalue has converged have_eig = nconv == 1 if have_eig - λ = abs(d[1]) # Take absolute value of the largest eigenvalue - break # Exit loop if successful + λ = abs(d[1]) + break else - # Increase NCV for the next attempt if convergence wasn't achieved ncv = min(2 * ncv, n) end catch e if occursin("XYAUPD_Exception", string(e)) && ncv < n @warn "Arpack error: $e. Increasing NCV to $ncv and retrying." - ncv = min(2 * ncv, n) # Increase NCV but don't exceed matrix size + ncv = min(2 * ncv, n) else - rethrow(e) # Re-raise if it's a different error + rethrow(e) end end end @@ -370,10 +363,10 @@ function opnorm_eig(B; max_attempts::Int = 3) return λ, have_eig end -function opnorm_svd(J; max_attempts::Int = 3) +function opnorm_svd(J; max_attempts::Int = 3, tiny_dense_threshold = 5) m, n = size(J) # 1) tiny dense Float64: direct LAPACK - if min(m, n) ≤ 5 + if min(m, n) ≤ tiny_dense_threshold return maximum(svd(Matrix(J)).S), true end @@ -385,27 +378,23 @@ function opnorm_svd(J; max_attempts::Int = 3) while !(have_svd || attempt >= max_attempts) attempt += 1 try - # Estimate largest singular value s, nconv, niter, nmult, resid = svds(J; nsv = nsv, ncv = ncv, ritzvec = false, check = 1) - - # Check if singular value has converged have_svd = nconv >= 1 if have_svd - σ = maximum(s.S) # Take the largest singular value - break # Exit loop if successful + σ = maximum(s.S) + break else - # Increase NCV for the next attempt if convergence wasn't achieved ncv = min(2 * ncv, n) end catch e if occursin("XYAUPD_Exception", string(e)) && ncv < n @warn "Arpack error: $e. Increasing NCV to $ncv and retrying." - ncv = min(2 * ncv, n) # Increase NCV but don't exceed matrix size + ncv = min(2 * ncv, n) else - rethrow(e) # Re-raise if it's a different error + rethrow(e) end end end return σ, have_svd -end \ No newline at end of file +end diff --git a/test/runtests.jl b/test/runtests.jl index 196aecb8..4e4ed8bd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -13,7 +13,7 @@ include("test_kron.jl") include("test_callable.jl") include("test_deprecated.jl") include("test_normest.jl") -include("test_opnorm.jl") +include("test_estimate_opnorm.jl") include("test_diag.jl") include("test_chainrules.jl") include("test_solve_shifted_system.jl") diff --git a/test/test_estimate_opnorm.jl b/test/test_estimate_opnorm.jl new file mode 100644 index 00000000..198bfc85 --- /dev/null +++ b/test/test_estimate_opnorm.jl @@ -0,0 +1,29 @@ +@testset "estimate_opnorm and TSVD tests (LinearOperators)" begin + # 1) Square Float64 via direct LAPACK or ARPACK + A_mat = [2.0 0.0; 0.0 -1.0] + A_op = LinearOperator(A_mat) + λ, ok = estimate_opnorm(A_op) + @test ok == true + @test isapprox(λ, 2.0; rtol=1e-12) + + # 2) Rectangular Float64 via direct LAPACK or ARPACK SVD + J_mat = [3.0 0.0 0.0; 0.0 1.0 0.0] + J_op = LinearOperator(J_mat) + σ, ok_sv = estimate_opnorm(J_op) + @test ok_sv == true + @test isapprox(σ, 3.0; rtol=1e-12) + + # 3) Square BigFloat via TSVD + 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 == true + @test isapprox(λ_bf, BigFloat(2); rtol=1e-12) + + # 4) Rectangular BigFloat via rectangular TSVD + JR_mat = Matrix{BigFloat}([3.0 0.0 0.0; 0.0 1.0 0.0]) + JR_op = LinearOperator(JR_mat) + σ_bf, ok_bf2 = estimate_opnorm(JR_op) + @test ok_bf2 == true + @test isapprox(σ_bf, BigFloat(3); rtol=1e-12) +end \ No newline at end of file diff --git a/test/test_opnorm.jl b/test/test_opnorm.jl deleted file mode 100644 index c178ab6f..00000000 --- a/test/test_opnorm.jl +++ /dev/null @@ -1,33 +0,0 @@ -function test_opnorm() - @testset "opnorm and TSVD tests (LinearOperators)" begin - # 1) Square Float64 via direct LAPACK or ARPACK - A_mat = [2.0 0.0; 0.0 -1.0] - A_op = LinearOperator(A_mat) - λ, ok = opnorm(A_op) - @test ok == true - @test isapprox(λ, 2.0; rtol=1e-12) - - # 2) Rectangular Float64 via direct LAPACK or ARPACK SVD - J_mat = [3.0 0.0 0.0; 0.0 1.0 0.0] - J_op = LinearOperator(J_mat) - σ, ok_sv = opnorm(J_op) - @test ok_sv == true - @test isapprox(σ, 3.0; rtol=1e-12) - - # 3) Square BigFloat via TSVD - B_mat = Matrix{BigFloat}([2.0 0.0; 0.0 -1.0]) - B_op = LinearOperator(B_mat) - λ_bf, ok_bf = opnorm(B_op) - @test ok_bf == true - @test isapprox(λ_bf, BigFloat(2); rtol=1e-12) - - # 4) Rectangular BigFloat via rectangular TSVD - JR_mat = Matrix{BigFloat}([3.0 0.0 0.0; 0.0 1.0 0.0]) - JR_op = LinearOperator(JR_mat) - σ_bf, ok_bf2 = opnorm(JR_op) - @test ok_bf2 == true - @test isapprox(σ_bf, BigFloat(3); rtol=1e-12) - end -end - -test_opnorm() \ No newline at end of file From 4fce7417d992ba67b344094ee504698aa19111f2 Mon Sep 17 00:00:00 2001 From: Farhad Rahbarnia <31899325+farhadrclass@users.noreply.github.com> Date: Tue, 20 Jan 2026 12:45:46 -0500 Subject: [PATCH 08/26] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/utilities.jl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/utilities.jl b/src/utilities.jl index 3f9e43b5..53b64e2e 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -303,7 +303,9 @@ it uses iterative methods (ARPACK or TSVD) to estimate the norm efficiently. - `kwargs...`: Optional keyword arguments passed to the underlying norm estimation routines. # Returns -- The estimated operator 2-norm of `B` (largest singular value or eigenvalue in absolute value). +- 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(B; kwargs...) @@ -322,7 +324,8 @@ function _estimate_opnorm( end function _estimate_opnorm(B, ::Type{T}; kwargs...) where {T} - _, s, _ = tsvd(B) + # Use rank-1 truncated SVD to get only the largest singular value + _, s, _ = tsvd(B, 1) return s[1], true end @@ -334,7 +337,7 @@ function opnorm_eig(B; max_attempts::Int = 3, tiny_dense_threshold = 5) end # 2) iterative ARPACK - nev, ncv = 1, max(20, 2*1 + 1) + nev, ncv = 1, max(20, 2*nev + 1) attempt, λ, have_eig = 0, zero(eltype(B)), false while !(have_eig || attempt >= max_attempts) @@ -396,5 +399,8 @@ function opnorm_svd(J; max_attempts::Int = 3, tiny_dense_threshold = 5) end end + if !have_svd + error("opnorm_svd failed to converge after $max_attempts attempts.") + end return σ, have_svd end From 3b17e797f625529182b67776dfe47b13e2dbb0bd Mon Sep 17 00:00:00 2001 From: Farhad Rahbarnia <31899325+farhadrclass@users.noreply.github.com> Date: Tue, 20 Jan 2026 12:55:01 -0500 Subject: [PATCH 09/26] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/utilities.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/utilities.jl b/src/utilities.jl index 53b64e2e..77d70537 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -4,6 +4,7 @@ import LinearAlgebra.ldiv! using GenericLinearAlgebra using TSVD using Arpack +import Arpack: eigs, svds export estimate_opnorm """ @@ -174,7 +175,7 @@ The method uses a two-loop recursion-like approach with modifications to handle ### Example - +```julia using Random # Problem setup @@ -331,7 +332,7 @@ end function opnorm_eig(B; max_attempts::Int = 3, tiny_dense_threshold = 5) n = size(B, 1) - # 1) tiny dense Float64: direct LAPACK + # 1) tiny dense matrix: direct LAPACK if n ≤ tiny_dense_threshold return maximum(abs, eigen(Matrix(B)).values), true end @@ -373,7 +374,7 @@ function opnorm_svd(J; max_attempts::Int = 3, tiny_dense_threshold = 5) return maximum(svd(Matrix(J)).S), true end - # 2) iterative ARPACK‐SVD + # 2) iterative ARPACK-SVD nsv, ncv = 1, 10 attempt, σ, have_svd = 0, zero(eltype(J)), false n = min(m, n) From 67aeac9ce27a5155a1687b2415d29e828216be9e Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:18:57 -0500 Subject: [PATCH 10/26] Update utilities.jl --- src/utilities.jl | 141 ++++++++++++++++++++++++++++++----------------- 1 file changed, 89 insertions(+), 52 deletions(-) diff --git a/src/utilities.jl b/src/utilities.jl index 77d70537..79fc7eac 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -203,6 +203,7 @@ result = solve_shifted_system!(x, B, b, σ) # Check that the solution is close enough (residual test) @assert norm(B * x + σ * x - b) / norm(b) < 1e-8 +``` ### References @@ -265,8 +266,8 @@ Solves the linear system Bx = b. - `x::AbstractVector{T}`: The modified solution vector containing the solution to the linear system. -### Examples: - +### Example: +```julia # Create an L-BFGS operator B = LBFGSOperator(10) @@ -279,6 +280,7 @@ b = rand(10) ldiv!(x, B, b) # The vector `x` now contains the solution +``` """ function ldiv!( @@ -313,95 +315,130 @@ function estimate_opnorm(B; kwargs...) _estimate_opnorm(B, eltype(B); kwargs...) end -# This method will be picked if eltype is one of the four types Arpack supports -# (Float32, Float64, ComplexF32, ComplexF64). +# 1. Fallback for Integer/Generic types (Uses TSVD) +function _estimate_opnorm(B, ::Type{T}; kwargs...) where {T} + _, s, _ = tsvd(B, 1) + return s[1], true +end + +# 2. Optimized Dispatch for Float/Complex (Uses ARPACK) function _estimate_opnorm( B, ::Type{T}; kwargs..., ) where {T <: Union{Float32, Float64, ComplexF32, ComplexF64}} - m, n = size(B) - return (m == n ? opnorm_eig : opnorm_svd)(B; kwargs...) -end -function _estimate_opnorm(B, ::Type{T}; kwargs...) where {T} - # Use rank-1 truncated SVD to get only the largest singular value - _, s, _ = tsvd(B, 1) - return s[1], true + # Only use Eigenvalue solver if we are CERTAIN B is Hermitian. + # Otherwise, we must use SVD to be mathematically correct. + 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) n = size(B, 1) - # 1) tiny dense matrix: direct LAPACK + + # Tiny dense optimization if n ≤ tiny_dense_threshold return maximum(abs, eigen(Matrix(B)).values), true end - # 2) iterative ARPACK - nev, ncv = 1, max(20, 2*nev + 1) - attempt, λ, have_eig = 0, zero(eltype(B)), false + # Setup ARPACK parameters + nev = 1 + ncv = max(20, 2*nev + 1) - while !(have_eig || attempt >= max_attempts) - attempt += 1 + for attempt = 1:max_attempts try - d, nconv, niter, nmult, resid = - eigs(B; nev = nev, ncv = ncv, which = :LM, ritzvec = false, check = 1) + d, nconv, _, _, _ = eigs(B; nev = nev, ncv = ncv, which = :LM, ritzvec = false, check = 1) - have_eig = nconv == 1 - if have_eig - λ = abs(d[1]) - break - else - ncv = min(2 * ncv, n) + # SUCCESS: If converged, return immediately + if nconv == 1 + return abs(d[1]), true end + + # FAILURE (Silent): If we get here, nconv != 1. + # We simply fall through to the "Retry Logic" at the bottom. + catch e - if occursin("XYAUPD_Exception", string(e)) && ncv < n - @warn "Arpack error: $e. Increasing NCV to $ncv and retrying." - ncv = min(2 * ncv, n) + # FAILURE (Exception): Check if it's an ARPACK error we can retry + 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 + # If we can retry, we swallow the error and fall through to "Retry Logic" else rethrow(e) end end + + # --- SHARED RETRY LOGIC --- + # Both "Silent Failure" and "Exception" end up here. + 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 # Cannot increase NCV further, stop trying + end + end end - return λ, have_eig + return NaN, false end -function opnorm_svd(J; max_attempts::Int = 3, tiny_dense_threshold = 5) - m, n = size(J) +function opnorm_svd(B; max_attempts::Int = 3, tiny_dense_threshold = 5) + m, n = size(B) + # 1) tiny dense Float64: direct LAPACK if min(m, n) ≤ tiny_dense_threshold - return maximum(svd(Matrix(J)).S), true + return maximum(svd(Matrix(B)).S), true end - # 2) iterative ARPACK-SVD - nsv, ncv = 1, 10 - attempt, σ, have_svd = 0, zero(eltype(J)), false - n = min(m, n) + min_dim = min(m, n) + + # Setup ARPACK parameters + nsv = 1 + ncv = 10 - while !(have_svd || attempt >= max_attempts) - attempt += 1 + for attempt = 1:max_attempts try - s, nconv, niter, nmult, resid = svds(J; nsv = nsv, ncv = ncv, ritzvec = false, check = 1) - have_svd = nconv >= 1 - if have_svd - σ = maximum(s.S) - break - else - ncv = min(2 * ncv, n) + s, nconv, _, _, _ = svds(B; nsv = nsv, ncv = ncv, ritzvec = false, check = 1) + + if nconv >= 1 + return maximum(s.S), true end + catch e - if occursin("XYAUPD_Exception", string(e)) && ncv < n - @warn "Arpack error: $e. Increasing NCV to $ncv and retrying." - ncv = min(2 * ncv, n) + # Robust Error Check + 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 - end - if !have_svd - error("opnorm_svd failed to converge after $max_attempts attempts.") + # Retry Logic + 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 σ, have_svd + + return NaN, false end From 829a55221706e14f76edaae10533a2289cdb75fd Mon Sep 17 00:00:00 2001 From: Farhad Rahbarnia <31899325+farhadrclass@users.noreply.github.com> Date: Wed, 21 Jan 2026 09:21:11 -0500 Subject: [PATCH 11/26] Update src/utilities.jl Co-authored-by: Dominique --- src/utilities.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utilities.jl b/src/utilities.jl index 79fc7eac..c9231b26 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -298,7 +298,7 @@ end Compute the estimate of the operator 2-norm (largest singular value) of a matrix or linear operator `B`. This method dispatches to efficient algorithms depending on the type and size of `B`: -for small dense matrices, it uses direct LAPACK routines; for larger or structured operators, +for small dense matrices, it uses direct LAPACK routines; for larger matrices or abstract operators, it uses iterative methods (ARPACK or TSVD) to estimate the norm efficiently. # Arguments From 8b0da8cbaf3308fabc27c842868ffbfdfe5964ee Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Fri, 23 Jan 2026 15:59:54 -0500 Subject: [PATCH 12/26] Update utilities.jl --- src/utilities.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utilities.jl b/src/utilities.jl index c9231b26..12060b26 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -315,7 +315,7 @@ function estimate_opnorm(B; kwargs...) _estimate_opnorm(B, eltype(B); kwargs...) end -# 1. Fallback for Integer/Generic types (Uses TSVD) +# 1. Fallback for Generic types (Uses TSVD) function _estimate_opnorm(B, ::Type{T}; kwargs...) where {T} _, s, _ = tsvd(B, 1) return s[1], true From 2d7ba5d72d6fa56bcc8bf8e91a51669888ab5f91 Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Fri, 23 Jan 2026 16:23:56 -0500 Subject: [PATCH 13/26] Update Project.toml --- Project.toml | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/Project.toml b/Project.toml index 60059b34..dda06eb6 100644 --- a/Project.toml +++ b/Project.toml @@ -3,13 +3,10 @@ uuid = "5c8ed15e-5a4c-59e4-a42b-c7e8811fb125" version = "2.11.0" [deps] -Arpack = "7d9fca2a-8960-54d3-9f78-7d1dccf2cb97" FastClosures = "9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a" -GenericLinearAlgebra = "14197337-ba66-59df-a3e3-ca00e7dcff7a" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" -TSVD = "9449cd9e-2762-5aa3-a617-5413e99d722e" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" [weakdeps] @@ -32,23 +29,12 @@ LinearOperatorsMetalExt = "Metal" AMDGPU = "2.2.0" CUDA = "5.9.6" ChainRulesCore = "1" -FastClosures = "0.2, 0.3" -GenericLinearAlgebra = "0.3.18" -JLArrays = "0.1, 0.2" -LDLFactorizations = "0.9, 0.10" -LinearAlgebra = "1" -Metal = "1.1" -Printf = "1" -Requires = "1" -SparseArrays = "1" -TSVD = "0.4.4" -TimerOutputs = "^0.5" -julia = "^1.6.0" - -[extras] -AMDGPU = "21141c5a-9bdb-4563-92ae-f87d6854732e" -CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" -ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -JLArrays = "27aeb0d3-9eb9-45fb-866b-73c2ecf80fcb" -LDLFactorizations = "40e66cde-538c-5869-a4ad-c39174c6795b" -Metal = "dde4c033-4e86-420c-a63e-0dd931031962" +FastClosures = "0.3" +JLArrays = "0.3" +LDLFactorizations = "0.10" +LinearAlgebra = "1.10" +Metal = "1.9.1" +Printf = "1.10" +SparseArrays = "1.10" +TimerOutputs = "0.5" +julia = "1.10" \ No newline at end of file From 5af8752075900a4e13d61b378e5b64f37150ca3c Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Fri, 23 Jan 2026 16:24:19 -0500 Subject: [PATCH 14/26] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index dda06eb6..10193bdf 100644 --- a/Project.toml +++ b/Project.toml @@ -37,4 +37,4 @@ Metal = "1.9.1" Printf = "1.10" SparseArrays = "1.10" TimerOutputs = "0.5" -julia = "1.10" \ No newline at end of file +julia = "1.10" From a3d545f10c441a68341f69e1ff679554d3a2a032 Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Fri, 23 Jan 2026 16:27:03 -0500 Subject: [PATCH 15/26] Add new dependencies and update opnorm tests Added Arpack, GenericLinearAlgebra, and TSVD as dependencies in Project.toml with corresponding compat entries. Updated test_estimate_opnorm.jl to use consistent spacing and formatting in test assertions. --- Project.toml | 6 +++++ test/test_estimate_opnorm.jl | 50 ++++++++++++++++++------------------ 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/Project.toml b/Project.toml index 10193bdf..5778181b 100644 --- a/Project.toml +++ b/Project.toml @@ -3,10 +3,13 @@ uuid = "5c8ed15e-5a4c-59e4-a42b-c7e8811fb125" version = "2.11.0" [deps] +Arpack = "7d9fca2a-8960-54d3-9f78-7d1dccf2cb97" FastClosures = "9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a" +GenericLinearAlgebra = "14197337-ba66-59df-a3e3-ca00e7dcff7a" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +TSVD = "9449cd9e-2762-5aa3-a617-5413e99d722e" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" [weakdeps] @@ -27,14 +30,17 @@ LinearOperatorsMetalExt = "Metal" [compat] AMDGPU = "2.2.0" +Arpack = "0.5.4" CUDA = "5.9.6" ChainRulesCore = "1" FastClosures = "0.3" +GenericLinearAlgebra = "0.3.19" JLArrays = "0.3" LDLFactorizations = "0.10" LinearAlgebra = "1.10" Metal = "1.9.1" Printf = "1.10" SparseArrays = "1.10" +TSVD = "0.4.4" TimerOutputs = "0.5" julia = "1.10" diff --git a/test/test_estimate_opnorm.jl b/test/test_estimate_opnorm.jl index 198bfc85..f55f7776 100644 --- a/test/test_estimate_opnorm.jl +++ b/test/test_estimate_opnorm.jl @@ -1,29 +1,29 @@ @testset "estimate_opnorm and TSVD tests (LinearOperators)" begin - # 1) Square Float64 via direct LAPACK or ARPACK - A_mat = [2.0 0.0; 0.0 -1.0] - A_op = LinearOperator(A_mat) - λ, ok = estimate_opnorm(A_op) - @test ok == true - @test isapprox(λ, 2.0; rtol=1e-12) + # 1) Square Float64 via direct LAPACK or ARPACK + A_mat = [2.0 0.0; 0.0 -1.0] + A_op = LinearOperator(A_mat) + λ, ok = estimate_opnorm(A_op) + @test ok == true + @test isapprox(λ, 2.0; rtol = 1e-12) - # 2) Rectangular Float64 via direct LAPACK or ARPACK SVD - J_mat = [3.0 0.0 0.0; 0.0 1.0 0.0] - J_op = LinearOperator(J_mat) - σ, ok_sv = estimate_opnorm(J_op) - @test ok_sv == true - @test isapprox(σ, 3.0; rtol=1e-12) + # 2) Rectangular Float64 via direct LAPACK or ARPACK SVD + J_mat = [3.0 0.0 0.0; 0.0 1.0 0.0] + J_op = LinearOperator(J_mat) + σ, ok_sv = estimate_opnorm(J_op) + @test ok_sv == true + @test isapprox(σ, 3.0; rtol = 1e-12) - # 3) Square BigFloat via TSVD - 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 == true - @test isapprox(λ_bf, BigFloat(2); rtol=1e-12) + # 3) Square BigFloat via TSVD + 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 == true + @test isapprox(λ_bf, BigFloat(2); rtol = 1e-12) - # 4) Rectangular BigFloat via rectangular TSVD - JR_mat = Matrix{BigFloat}([3.0 0.0 0.0; 0.0 1.0 0.0]) - JR_op = LinearOperator(JR_mat) - σ_bf, ok_bf2 = estimate_opnorm(JR_op) - @test ok_bf2 == true - @test isapprox(σ_bf, BigFloat(3); rtol=1e-12) -end \ No newline at end of file + # 4) Rectangular BigFloat via rectangular TSVD + JR_mat = Matrix{BigFloat}([3.0 0.0 0.0; 0.0 1.0 0.0]) + JR_op = LinearOperator(JR_mat) + σ_bf, ok_bf2 = estimate_opnorm(JR_op) + @test ok_bf2 == true + @test isapprox(σ_bf, BigFloat(3); rtol = 1e-12) +end From a672263cc96987527867b416688dc613d7b4505c Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Fri, 30 Jan 2026 13:58:18 -0500 Subject: [PATCH 16/26] Refactor opnorm estimation dispatch and improve tests Refactored _estimate_opnorm to add a specialized dispatch for Hermitian and Symmetric matrices with Float/Complex types, improving clarity and correctness. Simplified comments and retry logic in opnorm_eig and opnorm_svd. Expanded and restructured tests to cover type stability, dispatch paths, and edge cases for various matrix types and element types. --- src/utilities.jl | 28 +++++--------- test/test_estimate_opnorm.jl | 72 +++++++++++++++++++++++------------- 2 files changed, 55 insertions(+), 45 deletions(-) diff --git a/src/utilities.jl b/src/utilities.jl index 12060b26..cd18e9d9 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -315,21 +315,25 @@ function estimate_opnorm(B; kwargs...) _estimate_opnorm(B, eltype(B); kwargs...) end -# 1. Fallback for Generic types (Uses TSVD) function _estimate_opnorm(B, ::Type{T}; kwargs...) where {T} _, s, _ = tsvd(B, 1) return s[1], true end -# 2. Optimized Dispatch for Float/Complex (Uses ARPACK) +function _estimate_opnorm( + B::Union{Hermitian, Symmetric{<:Real}}, + ::Type{T}; + kwargs..., +) where {T <: Union{Float32, Float64, ComplexF32, ComplexF64}} + return opnorm_eig(B; kwargs...) +end + function _estimate_opnorm( B, ::Type{T}; kwargs..., ) where {T <: Union{Float32, Float64, ComplexF32, ComplexF64}} - # Only use Eigenvalue solver if we are CERTAIN B is Hermitian. - # Otherwise, we must use SVD to be mathematically correct. if ishermitian(B) return opnorm_eig(B; kwargs...) else @@ -340,12 +344,10 @@ end function opnorm_eig(B; max_attempts::Int = 3, tiny_dense_threshold = 5) n = size(B, 1) - # Tiny dense optimization if n ≤ tiny_dense_threshold return maximum(abs, eigen(Matrix(B)).values), true end - # Setup ARPACK parameters nev = 1 ncv = max(20, 2*nev + 1) @@ -353,16 +355,11 @@ function opnorm_eig(B; max_attempts::Int = 3, tiny_dense_threshold = 5) try d, nconv, _, _, _ = eigs(B; nev = nev, ncv = ncv, which = :LM, ritzvec = false, check = 1) - # SUCCESS: If converged, return immediately if nconv == 1 return abs(d[1]), true end - # FAILURE (Silent): If we get here, nconv != 1. - # We simply fall through to the "Retry Logic" at the bottom. - catch e - # FAILURE (Exception): Check if it's an ARPACK error we can retry if e isa Arpack.ARPACKException || occursin("ARPACK", string(e)) || occursin("AUPD", string(e)) @@ -370,21 +367,18 @@ function opnorm_eig(B; max_attempts::Int = 3, tiny_dense_threshold = 5) @warn "ARPACK failed and NCV cannot be increased further." exception=e rethrow(e) end - # If we can retry, we swallow the error and fall through to "Retry Logic" else rethrow(e) end end - # --- SHARED RETRY LOGIC --- - # Both "Silent Failure" and "Exception" end up here. 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 # Cannot increase NCV further, stop trying + break end end end @@ -395,14 +389,12 @@ end function opnorm_svd(B; max_attempts::Int = 3, tiny_dense_threshold = 5) m, n = size(B) - # 1) tiny dense Float64: direct LAPACK if min(m, n) ≤ tiny_dense_threshold return maximum(svd(Matrix(B)).S), true end min_dim = min(m, n) - # Setup ARPACK parameters nsv = 1 ncv = 10 @@ -415,7 +407,6 @@ function opnorm_svd(B; max_attempts::Int = 3, tiny_dense_threshold = 5) end catch e - # Robust Error Check if e isa Arpack.ARPACKException || occursin("ARPACK", string(e)) || occursin("AUPD", string(e)) @@ -428,7 +419,6 @@ function opnorm_svd(B; max_attempts::Int = 3, tiny_dense_threshold = 5) end end - # Retry Logic if attempt < max_attempts old_ncv = ncv ncv = min(2 * ncv, min_dim) diff --git a/test/test_estimate_opnorm.jl b/test/test_estimate_opnorm.jl index f55f7776..e6cd2525 100644 --- a/test/test_estimate_opnorm.jl +++ b/test/test_estimate_opnorm.jl @@ -1,29 +1,49 @@ -@testset "estimate_opnorm and TSVD tests (LinearOperators)" begin - # 1) Square Float64 via direct LAPACK or ARPACK - A_mat = [2.0 0.0; 0.0 -1.0] - A_op = LinearOperator(A_mat) - λ, ok = estimate_opnorm(A_op) - @test ok == true - @test isapprox(λ, 2.0; rtol = 1e-12) +@testset "estimate_opnorm type stability and dispatch" begin + for T in [Float32, Float64, ComplexF32, ComplexF64] + @testset "Type $T" begin + + # 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) - # 2) Rectangular Float64 via direct LAPACK or ARPACK SVD - J_mat = [3.0 0.0 0.0; 0.0 1.0 0.0] - J_op = LinearOperator(J_mat) - σ, ok_sv = estimate_opnorm(J_op) - @test ok_sv == true - @test isapprox(σ, 3.0; rtol = 1e-12) + # B) Hermitian Wrapper (Forces Eig path via dispatch) + H_data = rand(T, 10, 10) + H_data = H_data + H_data' + H = Hermitian(H_data) + + exact_norm = opnorm(H) + est_norm, ok = estimate_opnorm(H) + @test ok + @test isapprox(est_norm, exact_norm; rtol=1e-5) - # 3) Square BigFloat via TSVD - 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 == true - @test isapprox(λ_bf, BigFloat(2); rtol = 1e-12) + # 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) - # 4) Rectangular BigFloat via rectangular TSVD - JR_mat = Matrix{BigFloat}([3.0 0.0 0.0; 0.0 1.0 0.0]) - JR_op = LinearOperator(JR_mat) - σ_bf, ok_bf2 = estimate_opnorm(JR_op) - @test ok_bf2 == true - @test isapprox(σ_bf, BigFloat(3); rtol = 1e-12) -end + exact_norm_S = opnorm(Matrix(S)) + est_norm_S, ok_S = estimate_opnorm(S) + @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 + @test !ishermitian(S) + end + 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 \ No newline at end of file From 779431fc3a9e0dfe01e363f0aa614d8defcc4758 Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:54:03 -0500 Subject: [PATCH 17/26] Add Arpack/TSVD weakdeps extension Move Arpack and TSVD out of mandatory deps into weakdeps and add an extension (ext/LinearOperatorsArpackTSVDExt.jl) that provides estimate_opnorm methods when those packages are available. The new extension implements opnorm estimation via ARPACK eigs/svds and TSVD (with small-matrix dense fallbacks, retry/NCV growth logic and ARPACK error handling). The original implementation in src/utilities.jl was removed (left as a stub) and tests/Project.toml/tests updated to load TSVD and Arpack for the test suite. --- Project.toml | 7 +- ext/LinearOperatorsOpNormExt.jl | 134 ++++++++++++++++++++++++++++++++ src/utilities.jl | 129 +----------------------------- test/Project.toml | 5 ++ test/test_estimate_opnorm.jl | 3 + 5 files changed, 149 insertions(+), 129 deletions(-) create mode 100644 ext/LinearOperatorsOpNormExt.jl diff --git a/Project.toml b/Project.toml index 5778181b..97726bb8 100644 --- a/Project.toml +++ b/Project.toml @@ -3,25 +3,26 @@ uuid = "5c8ed15e-5a4c-59e4-a42b-c7e8811fb125" version = "2.11.0" [deps] -Arpack = "7d9fca2a-8960-54d3-9f78-7d1dccf2cb97" FastClosures = "9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a" -GenericLinearAlgebra = "14197337-ba66-59df-a3e3-ca00e7dcff7a" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" -TSVD = "9449cd9e-2762-5aa3-a617-5413e99d722e" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" [weakdeps] AMDGPU = "21141c5a-9bdb-4563-92ae-f87d6854732e" +Arpack = "7d9fca2a-8960-54d3-9f78-7d1dccf2cb97" +GenericLinearAlgebra = "14197337-ba66-59df-a3e3-ca00e7dcff7a" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" JLArrays = "27aeb0d3-9eb9-45fb-866b-73c2ecf80fcb" LDLFactorizations = "40e66cde-538c-5869-a4ad-c39174c6795b" Metal = "dde4c033-4e86-420c-a63e-0dd931031962" +TSVD = "9449cd9e-2762-5aa3-a617-5413e99d722e" [extensions] LinearOperatorsAMDGPUExt = "AMDGPU" +LinearOperatorsOpNormExt = ["Arpack", "TSVD", "GenericLinearAlgebra"] LinearOperatorsCUDAExt = "CUDA" LinearOperatorsChainRulesCoreExt = "ChainRulesCore" LinearOperatorsJLArraysExt = "JLArrays" diff --git a/ext/LinearOperatorsOpNormExt.jl b/ext/LinearOperatorsOpNormExt.jl new file mode 100644 index 00000000..33f2424d --- /dev/null +++ b/ext/LinearOperatorsOpNormExt.jl @@ -0,0 +1,134 @@ +module LinearOperatorsOpNormExt + +using LinearOperators +using Arpack +using TSVD +using LinearAlgebra +using GenericLinearAlgebra +import Arpack: eigs, svds + +# Import the function from the main package to add methods to it +import LinearOperators: estimate_opnorm + +function estimate_opnorm(B; kwargs...) + _estimate_opnorm(B, eltype(B); kwargs...) +end + +function _estimate_opnorm(B, ::Type{T}; kwargs...) where {T} + _, s, _ = tsvd(B, 1) + return s[1], true +end + +function _estimate_opnorm( + B::Union{Hermitian, Symmetric{<:Real}}, + ::Type{T}; + kwargs..., +) where {T <: Union{Float32, Float64, ComplexF32, ComplexF64}} + return opnorm_eig(B; kwargs...) +end + +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) + n = size(B, 1) + + if n ≤ tiny_dense_threshold + return maximum(abs, eigen(Matrix(B)).values), true + 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) + + 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) + m, n = size(B) + + if min(m, n) ≤ tiny_dense_threshold + return maximum(svd(Matrix(B)).S), true + 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) + + 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 diff --git a/src/utilities.jl b/src/utilities.jl index cd18e9d9..6dc05fd2 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -1,10 +1,6 @@ export check_ctranspose, check_hermitian, check_positive_definite, normest, solve_shifted_system!, ldiv! import LinearAlgebra.ldiv! -using GenericLinearAlgebra -using TSVD -using Arpack -import Arpack: eigs, svds export estimate_opnorm """ @@ -301,6 +297,8 @@ This method dispatches to efficient algorithms depending on the type and size of for small dense matrices, it uses direct LAPACK routines; for larger matrices or abstract operators, it uses iterative methods (ARPACK or TSVD) to estimate the norm efficiently. +**Note:** This function requires `Arpack.jl` and `TSVD.jl` to be loaded. + # Arguments - `B`: A matrix or linear operator. - `kwargs...`: Optional keyword arguments passed to the underlying norm estimation routines. @@ -310,125 +308,4 @@ it uses iterative methods (ARPACK or TSVD) to estimate the norm efficiently. - `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(B; kwargs...) - _estimate_opnorm(B, eltype(B); kwargs...) -end - -function _estimate_opnorm(B, ::Type{T}; kwargs...) where {T} - _, s, _ = tsvd(B, 1) - return s[1], true -end - -function _estimate_opnorm( - B::Union{Hermitian, Symmetric{<:Real}}, - ::Type{T}; - kwargs..., -) where {T <: Union{Float32, Float64, ComplexF32, ComplexF64}} - return opnorm_eig(B; kwargs...) -end - -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) - n = size(B, 1) - - if n ≤ tiny_dense_threshold - return maximum(abs, eigen(Matrix(B)).values), true - 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) - - 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) - m, n = size(B) - - if min(m, n) ≤ tiny_dense_threshold - return maximum(svd(Matrix(B)).S), true - 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) - - 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 +function estimate_opnorm end \ No newline at end of file diff --git a/test/Project.toml b/test/Project.toml index 82deeebc..f4187e11 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,5 +1,7 @@ [deps] Arpack = "7d9fca2a-8960-54d3-9f78-7d1dccf2cb97" +TSVD = "9449cd9e-2762-5aa3-a617-5413e99d722e" +GenericLinearAlgebra = "14197337-ba66-59df-a3e3-ca00e7dcff7a" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" FastClosures = "9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a" JLArrays = "27aeb0d3-9eb9-45fb-866b-73c2ecf80fcb" @@ -13,8 +15,11 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" + [compat] Arpack = "0.5" +TSVD = "0.4.4" +GenericLinearAlgebra = "0.3.19" ChainRulesCore = "1" FastClosures = "0.3" JLArrays = "0.3" diff --git a/test/test_estimate_opnorm.jl b/test/test_estimate_opnorm.jl index e6cd2525..42649ba7 100644 --- a/test/test_estimate_opnorm.jl +++ b/test/test_estimate_opnorm.jl @@ -1,3 +1,6 @@ +using TSVD +using Arpack +using GenericLinearAlgebra @testset "estimate_opnorm type stability and dispatch" begin for T in [Float32, Float64, ComplexF32, ComplexF64] @testset "Type $T" begin From b4dd14dbc2f18f3016ca159de1718fb7c18c72ce Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:57:14 -0500 Subject: [PATCH 18/26] Update utilities.jl --- src/utilities.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utilities.jl b/src/utilities.jl index 6dc05fd2..e10fcb9a 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -297,7 +297,7 @@ This method dispatches to efficient algorithms depending on the type and size of for small dense matrices, it uses direct LAPACK routines; for larger matrices or abstract operators, it uses iterative methods (ARPACK or TSVD) to estimate the norm efficiently. -**Note:** This function requires `Arpack.jl` and `TSVD.jl` to be loaded. +**Note:** This function allocates memory. It requires `Arpack.jl` and `TSVD.jl` (and `GenericLinearAlgebra.jl` for generic types) to be loaded. # Arguments - `B`: A matrix or linear operator. From 15b6dbd7b009b0fbd2c614ee0d783b4ad255fdd1 Mon Sep 17 00:00:00 2001 From: Farhad Rahbarnia <31899325+farhadrclass@users.noreply.github.com> Date: Wed, 25 Feb 2026 12:49:40 -0500 Subject: [PATCH 19/26] Support estimate_opnorm for AbstractLinearOperator Dispatch estimate_opnorm on AbstractLinearOperator and add robust small-matrix fallbacks. Changed the external extension to accept AbstractLinearOperator, provide a generic TSVD fallback for nonstandard types, and use try/catch when converting small operators to Matrix before calling dense LAPACK routines. Exported and added a docstring stub for estimate_opnorm in utilities.jl. Tests updated to exercise the operator wrapper (LinearOperator) and BigFloat paths; an old redundant test file was removed. --- ext/LinearOperatorsOpNormExt.jl | 26 ++++++++++++++------------ src/utilities.jl | 28 +++++++++++++++++++++++++++- test/test_estimate_opnorm.jl | 20 ++++++++++++++++---- test/test_opnorm.jl | 33 --------------------------------- 4 files changed, 57 insertions(+), 50 deletions(-) delete mode 100644 test/test_opnorm.jl diff --git a/ext/LinearOperatorsOpNormExt.jl b/ext/LinearOperatorsOpNormExt.jl index 33f2424d..0fc2c96c 100644 --- a/ext/LinearOperatorsOpNormExt.jl +++ b/ext/LinearOperatorsOpNormExt.jl @@ -7,26 +7,19 @@ using LinearAlgebra using GenericLinearAlgebra import Arpack: eigs, svds -# Import the function from the main package to add methods to it import LinearOperators: estimate_opnorm -function estimate_opnorm(B; kwargs...) +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) return s[1], true end -function _estimate_opnorm( - B::Union{Hermitian, Symmetric{<:Real}}, - ::Type{T}; - kwargs..., -) where {T <: Union{Float32, Float64, ComplexF32, ComplexF64}} - return opnorm_eig(B; kwargs...) -end - +# Optimized path for standard FloatingPoint/Complex types function _estimate_opnorm( B, ::Type{T}; @@ -42,8 +35,13 @@ end function opnorm_eig(B; max_attempts::Int = 3, tiny_dense_threshold = 5) n = size(B, 1) + # Check if we can use direct dense methods (only if B supports conversion to Matrix) if n ≤ tiny_dense_threshold - return maximum(abs, eigen(Matrix(B)).values), true + 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 @@ -88,7 +86,11 @@ function opnorm_svd(B; max_attempts::Int = 3, tiny_dense_threshold = 5) m, n = size(B) if min(m, n) ≤ tiny_dense_threshold - return maximum(svd(Matrix(B)).S), true + try + return maximum(svd(Matrix(B)).S), true + catch + # Fallback + end end min_dim = min(m, n) diff --git a/src/utilities.jl b/src/utilities.jl index ff1f7a78..1fa0c194 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -1,7 +1,8 @@ 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! + """ normest(S) estimates the matrix 2-norm of S. This function is an adaptation of Matlab's built-in NORMEST. @@ -287,3 +288,28 @@ function ldiv!( solve_shifted_system!(x, B, b, T(0.0)) return x end + + +""" + estimate_opnorm(B::AbstractLinearOperator; kwargs...) + +Compute the estimate of the operator 2-norm (largest singular value) of a linear operator `B`. + +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. + +**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`). + +# 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 diff --git a/test/test_estimate_opnorm.jl b/test/test_estimate_opnorm.jl index 42649ba7..061f97a2 100644 --- a/test/test_estimate_opnorm.jl +++ b/test/test_estimate_opnorm.jl @@ -1,6 +1,7 @@ using TSVD using Arpack using GenericLinearAlgebra + @testset "estimate_opnorm type stability and dispatch" begin for T in [Float32, Float64, ComplexF32, ComplexF64] @testset "Type $T" begin @@ -20,8 +21,11 @@ using GenericLinearAlgebra H_data = H_data + H_data' H = Hermitian(H_data) - exact_norm = opnorm(H) - est_norm, ok = estimate_opnorm(H) + 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) @@ -29,23 +33,31 @@ using GenericLinearAlgebra 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) + 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 - @test !ishermitian(S) + # Ensure the operator didn't falsely flag itself as Hermitian + @test !ishermitian(S_op) end 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 diff --git a/test/test_opnorm.jl b/test/test_opnorm.jl deleted file mode 100644 index c178ab6f..00000000 --- a/test/test_opnorm.jl +++ /dev/null @@ -1,33 +0,0 @@ -function test_opnorm() - @testset "opnorm and TSVD tests (LinearOperators)" begin - # 1) Square Float64 via direct LAPACK or ARPACK - A_mat = [2.0 0.0; 0.0 -1.0] - A_op = LinearOperator(A_mat) - λ, ok = opnorm(A_op) - @test ok == true - @test isapprox(λ, 2.0; rtol=1e-12) - - # 2) Rectangular Float64 via direct LAPACK or ARPACK SVD - J_mat = [3.0 0.0 0.0; 0.0 1.0 0.0] - J_op = LinearOperator(J_mat) - σ, ok_sv = opnorm(J_op) - @test ok_sv == true - @test isapprox(σ, 3.0; rtol=1e-12) - - # 3) Square BigFloat via TSVD - B_mat = Matrix{BigFloat}([2.0 0.0; 0.0 -1.0]) - B_op = LinearOperator(B_mat) - λ_bf, ok_bf = opnorm(B_op) - @test ok_bf == true - @test isapprox(λ_bf, BigFloat(2); rtol=1e-12) - - # 4) Rectangular BigFloat via rectangular TSVD - JR_mat = Matrix{BigFloat}([3.0 0.0 0.0; 0.0 1.0 0.0]) - JR_op = LinearOperator(JR_mat) - σ_bf, ok_bf2 = opnorm(JR_op) - @test ok_bf2 == true - @test isapprox(σ_bf, BigFloat(3); rtol=1e-12) - end -end - -test_opnorm() \ No newline at end of file From accfea25761f58d3028bca7591db74b5744cac1f Mon Sep 17 00:00:00 2001 From: Farhad Rahbarnia <31899325+farhadrclass@users.noreply.github.com> Date: Thu, 26 Feb 2026 18:34:16 -0500 Subject: [PATCH 20/26] Update test/Project.toml Co-authored-by: Tangi Migot --- test/Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Project.toml b/test/Project.toml index f4187e11..a8021d48 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -15,7 +15,6 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" - [compat] Arpack = "0.5" TSVD = "0.4.4" From a1d8bccc44fe8089f8e76d639fa0c82ee5f6cf19 Mon Sep 17 00:00:00 2001 From: Farhad Rahbarnia <31899325+farhadrclass@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:12:03 -0400 Subject: [PATCH 21/26] Update test/test_estimate_opnorm.jl Co-authored-by: Tangi Migot --- test/test_estimate_opnorm.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_estimate_opnorm.jl b/test/test_estimate_opnorm.jl index 061f97a2..049db84a 100644 --- a/test/test_estimate_opnorm.jl +++ b/test/test_estimate_opnorm.jl @@ -3,8 +3,7 @@ using Arpack using GenericLinearAlgebra @testset "estimate_opnorm type stability and dispatch" begin - for T in [Float32, Float64, ComplexF32, ComplexF64] - @testset "Type $T" begin + @testset "Type $T" for T in [Float32, Float64, ComplexF32, ComplexF64] # A) Rectangular Matrix (Forces SVD path) J_mat = zeros(T, 3, 2) From 755f82ff8b00ad216d003c4245d104bb6e6e7d23 Mon Sep 17 00:00:00 2001 From: Farhad Rahbarnia <31899325+farhadrclass@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:12:42 -0400 Subject: [PATCH 22/26] Update src/utilities.jl Co-authored-by: Tangi Migot --- src/utilities.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utilities.jl b/src/utilities.jl index 1fa0c194..4e415874 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -2,7 +2,6 @@ export check_ctranspose, check_hermitian, check_positive_definite, normest, solve_shifted_system!, ldiv!, estimate_opnorm import LinearAlgebra.ldiv! - """ normest(S) estimates the matrix 2-norm of S. This function is an adaptation of Matlab's built-in NORMEST. From 12deb757db2a08b860943051a47ebda90220cba1 Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:20:51 -0400 Subject: [PATCH 23/26] Edits based on Tangi's review --- Project.toml | 3 --- ext/LinearOperatorsOpNormExt.jl | 14 ++++++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Project.toml b/Project.toml index d37576a4..9de8b3c4 100644 --- a/Project.toml +++ b/Project.toml @@ -3,13 +3,10 @@ uuid = "5c8ed15e-5a4c-59e4-a42b-c7e8811fb125" version = "2.13.0" [deps] -Arpack = "7d9fca2a-8960-54d3-9f78-7d1dccf2cb97" FastClosures = "9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a" -GenericLinearAlgebra = "14197337-ba66-59df-a3e3-ca00e7dcff7a" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" -TSVD = "9449cd9e-2762-5aa3-a617-5413e99d722e" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" [weakdeps] diff --git a/ext/LinearOperatorsOpNormExt.jl b/ext/LinearOperatorsOpNormExt.jl index 0fc2c96c..1e4ad4e2 100644 --- a/ext/LinearOperatorsOpNormExt.jl +++ b/ext/LinearOperatorsOpNormExt.jl @@ -15,11 +15,13 @@ end # Fallback for non-standard number types (uses TSVD) function _estimate_opnorm(B, ::Type{T}; kwargs...) where {T} - _, s, _ = tsvd(B, 1) + _, 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}; @@ -32,7 +34,7 @@ function _estimate_opnorm( end end -function opnorm_eig(B; max_attempts::Int = 3, tiny_dense_threshold = 5) +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) @@ -49,7 +51,7 @@ function opnorm_eig(B; max_attempts::Int = 3, tiny_dense_threshold = 5) for attempt = 1:max_attempts try - d, nconv, _, _, _ = eigs(B; nev = nev, ncv = ncv, which = :LM, ritzvec = false, check = 1) + d, nconv, _, _, _ = eigs(B; nev = nev, ncv = ncv, which = :LM, ritzvec = false, check = 1, kwargs...) if nconv == 1 return abs(d[1]), true @@ -82,7 +84,7 @@ function opnorm_eig(B; max_attempts::Int = 3, tiny_dense_threshold = 5) return NaN, false end -function opnorm_svd(B; max_attempts::Int = 3, tiny_dense_threshold = 5) +function opnorm_svd(B; max_attempts::Int = 3, tiny_dense_threshold = 5, kwargs...) m, n = size(B) if min(m, n) ≤ tiny_dense_threshold @@ -100,7 +102,7 @@ function opnorm_svd(B; max_attempts::Int = 3, tiny_dense_threshold = 5) for attempt = 1:max_attempts try - s, nconv, _, _, _ = svds(B; nsv = nsv, ncv = ncv, ritzvec = false, check = 1) + s, nconv, _, _, _ = svds(B; nsv = nsv, ncv = ncv, ritzvec = false, check = 1, kwargs...) if nconv >= 1 return maximum(s.S), true @@ -133,4 +135,4 @@ function opnorm_svd(B; max_attempts::Int = 3, tiny_dense_threshold = 5) return NaN, false end -end +end \ No newline at end of file From 156524167bd0f224655960a42ab7f1100c7b3be0 Mon Sep 17 00:00:00 2001 From: farhadrclass <31899325+farhadrclass@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:33:11 -0400 Subject: [PATCH 24/26] extra end --- ext/LinearOperatorsOpNormExt.jl | 2 +- test/test_estimate_opnorm.jl | 92 ++++++++++++++++----------------- 2 files changed, 46 insertions(+), 48 deletions(-) diff --git a/ext/LinearOperatorsOpNormExt.jl b/ext/LinearOperatorsOpNormExt.jl index 1e4ad4e2..09577b42 100644 --- a/ext/LinearOperatorsOpNormExt.jl +++ b/ext/LinearOperatorsOpNormExt.jl @@ -25,7 +25,7 @@ end function _estimate_opnorm( B, ::Type{T}; - kwargs..., + kwargs... ) where {T <: Union{Float32, Float64, ComplexF32, ComplexF64}} if ishermitian(B) return opnorm_eig(B; kwargs...) diff --git a/test/test_estimate_opnorm.jl b/test/test_estimate_opnorm.jl index 049db84a..2698c5d4 100644 --- a/test/test_estimate_opnorm.jl +++ b/test/test_estimate_opnorm.jl @@ -4,49 +4,47 @@ 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 + # 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 @@ -54,10 +52,10 @@ using GenericLinearAlgebra # ------------------------- 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) + @test isapprox(λ_bf, BigFloat(2); rtol = 1e-12) end -end \ No newline at end of file +end From 9d68e4a88f9fb405a8cf33eefc54a83f6d799289 Mon Sep 17 00:00:00 2001 From: Farhad Rahbarnia <31899325+farhadrclass@users.noreply.github.com> Date: Thu, 23 Apr 2026 11:44:46 -0400 Subject: [PATCH 25/26] Update src/utilities.jl Co-authored-by: Tangi Migot --- src/utilities.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utilities.jl b/src/utilities.jl index 4e415874..83810f46 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -288,7 +288,6 @@ function ldiv!( return x end - """ estimate_opnorm(B::AbstractLinearOperator; kwargs...) From d97f699d380fe2acdc46ae18fcc7af99890919ca Mon Sep 17 00:00:00 2001 From: Farhad Rahbarnia <31899325+farhadrclass@users.noreply.github.com> Date: Thu, 23 Apr 2026 11:53:06 -0400 Subject: [PATCH 26/26] Update utilities.jl --- src/utilities.jl | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/utilities.jl b/src/utilities.jl index 83810f46..29e637d7 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -291,19 +291,25 @@ end """ estimate_opnorm(B::AbstractLinearOperator; kwargs...) -Compute the estimate of the operator 2-norm (largest singular value) of a linear operator `B`. +Compute an estimate of the operator 2-norm (largest singular value) of a linear operator `B`. -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. +This method dispatches to efficient algorithms depending on the properties and size of `B`: +- **Non-standard element types** (e.g., `BigFloat`, `Float16`): Always uses `TSVD.tsvd` as the fallback, since ARPACK only supports standard 32-bit and 64-bit floating-point types. +- **Small operators** (dimension ≤ `tiny_dense_threshold`): Attempts to convert `B` to a dense `Matrix` and uses direct LAPACK routines (`LinearAlgebra.eigen` if Hermitian, `LinearAlgebra.svd` otherwise). +- **Large, standard-type operators**: + - If `ishermitian(B)`: Uses `Arpack.eigs` to find the eigenvalue with the largest magnitude. + - Otherwise: Uses `Arpack.svds` to find the largest singular value. **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`). + +# Keyword Arguments +- `max_attempts::Int=3`: Maximum number of retries for iterative solvers if convergence fails, doubling the Krylov subspace dimension (`ncv`) each time. +- `tiny_dense_threshold::Int=5`: If the minimum dimension of `B` is less than or equal to this threshold, it attempts to use direct dense LAPACK routines. +- `kwargs...`: Additional optional keyword arguments passed to the underlying iterative routines (`Arpack.eigs`, `Arpack.svds`, or `TSVD.tsvd`). # Returns - A tuple `(norm, success)` where: