From 9ce3529f7a15034da77c5349fa96e976ae9f1336 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 23 Dec 2025 03:08:52 -0500 Subject: [PATCH 1/8] Use TestSuite for orthnull --- test/orthnull.jl | 14 +- test/testsuite/orthnull.jl | 268 +++++++++++++++++++++++++++++++++++++ 2 files changed, 276 insertions(+), 6 deletions(-) create mode 100644 test/testsuite/orthnull.jl diff --git a/test/orthnull.jl b/test/orthnull.jl index dec12946..c867a715 100644 --- a/test/orthnull.jl +++ b/test/orthnull.jl @@ -6,7 +6,7 @@ using LinearAlgebra: LinearAlgebra, I, Diagonal using CUDA, AMDGPU BLASFloats = (Float32, Float64, ComplexF32, ComplexF64) -GenericFloats = (BigFloat, Complex{BigFloat}) +GenericFloats = (Float16, BigFloat, Complex{BigFloat}) @isdefined(TestSuite) || include("testsuite/TestSuite.jl") using .TestSuite @@ -18,17 +18,19 @@ for T in (BLASFloats..., GenericFloats...), n in (37, m, 63) TestSuite.seed_rng!(123) if T ∈ BLASFloats if CUDA.functional() - TestSuite.test_orthnull(CuMatrix{T}, (m, n); test_nullity = false) - n == m && TestSuite.test_orthnull(Diagonal{T, CuVector{T}}, m; test_orthnull = false) + TestSuite.test_orthnull(CuMatrix{T}, (m, n)) + n == m && TestSuite.test_orthnull(Diagonal{T, CuVector{T}}, m) + end + if AMDGPU.functional() + TestSuite.test_orthnull(ROCMatrix{T}, (m, n)) + n == m && TestSuite.test_orthnull(Diagonal{T, ROCVector{T}}, m) end if AMDGPU.functional() - TestSuite.test_orthnull(ROCMatrix{T}, (m, n); test_nullity = false) - n == m && TestSuite.test_orthnull(Diagonal{T, ROCVector{T}}, m; test_orthnull = false) end end if !is_buildkite TestSuite.test_orthnull(T, (m, n)) AT = Diagonal{T, Vector{T}} - TestSuite.test_orthnull(AT, m; test_orthnull = false) + TestSuite.test_orthnull(AT, m) end end diff --git a/test/testsuite/orthnull.jl b/test/testsuite/orthnull.jl new file mode 100644 index 00000000..fa09214e --- /dev/null +++ b/test/testsuite/orthnull.jl @@ -0,0 +1,268 @@ +using TestExtras +using LinearAlgebra + +include("../linearmap.jl") + +function test_orthnull(T::Type, sz; kwargs...) + summary_str = testargs_summary(T, sz) + return @testset "orthnull $summary_str" begin + test_left_orthnull(T, sz; kwargs...) + test_right_orthnull(T, sz; kwargs...) + end +end + +function test_left_orthnull( + T::Type, sz; + atol::Real = 0, rtol::Real = precision(T), + kwargs... + ) + summary_str = testargs_summary(T, sz) + return @testset "left_orth! and left_null! $summary_str" begin + A = instantiate_matrix(T, sz) + Ac = deepcopy(A) + V, C = @testinferred left_orth(A) + N = @testinferred left_null(A) + m, n = size(A) + minmn = min(m, n) + @test V isa typeof(A) && size(V) == (m, minmn) + @test C isa typeof(A) && size(C) == (minmn, n) + @test eltype(N) == eltype(A) && size(N) == (m, m - minmn) + @test V * C ≈ A + @test isisometric(V) + @test LinearAlgebra.norm(A' * N) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(N) + @test collect(V) * collect(V)' + collect(N) * collect(N)' ≈ I + + M = LinearMap(A) + # broken + #VM, CM = @testinferred left_orth(M; alg = :svd) + VM, CM = left_orth(M; alg = :svd) + @test parent(VM) * parent(CM) ≈ A + + if m > n && (T <: Number || T <: Diagonal{<:Number, <:Vector}) + nullity = 5 + V, C = @testinferred left_orth(A) + N = @testinferred left_null(A; trunc = (; maxnullity = nullity)) + @test V isa typeof(A) && size(V) == (m, minmn) + @test C isa typeof(A) && size(C) == (minmn, n) + @test eltype(N) == eltype(A) && size(N) == (m, nullity) + @test V * C ≈ A + @test isisometric(V) + @test LinearAlgebra.norm(A' * N) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(N) + end + + # passing a kind and some kwargs + # broken + # V, C = @testinferred left_orth(A; alg = :qr, positive = true) + V, C = left_orth(A; alg = :qr, positive = true) + N = @testinferred left_null(A; alg = :qr, positive = true) + @test V isa typeof(A) && size(V) == (m, minmn) + @test C isa typeof(A) && size(C) == (minmn, n) + @test eltype(N) == eltype(A) && size(N) == (m, m - minmn) + @test V * C ≈ A + @test isisometric(V) + @test LinearAlgebra.norm(A' * N) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(N) + @test collect(V) * collect(V)' + collect(N) * collect(N)' ≈ I + + # passing an algorithm + if !isa(A, Diagonal) + V, C = @testinferred left_orth(A; alg = MatrixAlgebraKit.default_qr_algorithm(A)) + N = @testinferred left_null(A; alg = :qr, positive = true) + @test V isa typeof(A) && size(V) == (m, minmn) + @test C isa typeof(A) && size(C) == (minmn, n) + @test eltype(N) == eltype(A) && size(N) == (m, m - minmn) + @test V * C ≈ A + @test isisometric(V) + @test LinearAlgebra.norm(A' * N) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(N) + @test collect(V) * collect(V)' + collect(N) * collect(N)' ≈ I + end + + Ac = similar(A) + V2, C2 = @testinferred left_orth!(copy!(Ac, A), (V, C)) + N2 = @testinferred left_null!(copy!(Ac, A), N) + @test V2 * C2 ≈ A + @test isisometric(V2) + @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(N2) + @test collect(V2) * collect(V2)' + collect(N2) * collect(N2)' ≈ I + + # doesn't work on AMD... + atol = eps(real(eltype(T))) + V2, C2 = @testinferred left_orth!(copy!(Ac, A), (V, C); trunc = (; atol = atol)) + N2 = @testinferred left_null!(copy!(Ac, A), N; trunc = (; atol = atol)) + @test V2 * C2 ≈ A + @test isisometric(V2) + @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(N2) + @test collect(V2) * collect(V2)' + collect(N2) * collect(N2)' ≈ I + + if (T <: Number || T <: Diagonal{<:Number, <:Vector}) + rtol = eps(real(eltype(T))) + for (trunc_orth, trunc_null) in ( + ((; rtol = rtol), (; rtol = rtol)), + (trunctol(; rtol), trunctol(; rtol, keep_below = true)), + ) + V2, C2 = @testinferred left_orth!(copy!(Ac, A), (V, C); trunc = trunc_orth) + N2 = @testinferred left_null!(copy!(Ac, A), N; trunc = trunc_null) + @test V2 * C2 ≈ A + @test isisometric(V2) + @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(N2) + @test collect(V2) * collect(V2)' + collect(N2) * collect(N2)' ≈ I + end + end + + for alg in (:qr, :polar, :svd) # explicit kind kwarg + m < n && alg === :polar && continue + # broken + # V2, C2 = @testinferred left_orth!(copy!(Ac, A), (V, C); alg = alg) + V2, C2 = left_orth!(copy!(Ac, A), (V, C); alg = alg) + @test V2 * C2 ≈ A + @test isisometric(V2) + if alg != :polar + N2 = @testinferred left_null!(copy!(Ac, A), N; alg) + @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(N2) + @test collect(V2) * collect(V2)' + collect(N2) * collect(N2)' ≈ I + end + + # with kind and tol kwargs + if alg == :svd + if (T <: Number || T <: Diagonal{<:Number, <:Vector}) + # broken + # V2, C2 = @testinferred left_orth!(copy!(Ac, A), (V, C); alg = alg, trunc = (; atol)) + V2, C2 = left_orth!(copy!(Ac, A), (V, C); alg = alg, trunc = (; atol)) + N2 = @testinferred left_null!(copy!(Ac, A), N; alg, trunc = (; atol)) + @test V2 * C2 ≈ A + @test isisometric(V2) + @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(N2) + @test collect(V2) * collect(V2)' + collect(N2) * collect(N2)' ≈ I + + # broken + # V2, C2 = @testinferred left_orth!(copy!(Ac, A), (V, C); alg = alg, trunc = (; rtol)) + V2, C2 = left_orth!(copy!(Ac, A), (V, C); alg = alg, trunc = (; rtol)) + N2 = @testinferred left_null!(copy!(Ac, A), N; alg, trunc = (; rtol)) + @test V2 * C2 ≈ A + @test isisometric(V2) + @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(N2) + @test collect(V2) * collect(V2)' + collect(N2) * collect(N2)' ≈ I + end + else + @test_throws ArgumentError left_orth!(copy!(Ac, A), (V, C); alg, trunc = (; atol)) + @test_throws ArgumentError left_orth!(copy!(Ac, A), (V, C); alg, trunc = (; rtol)) + alg == :polar && continue + @test_throws ArgumentError left_null!(copy!(Ac, A), N; alg, trunc = (; atol)) + @test_throws ArgumentError left_null!(copy!(Ac, A), N; alg, trunc = (; rtol)) + end + end + end +end + +function test_right_orthnull( + T::Type, sz; + atol::Real = 0, rtol::Real = precision(T), + kwargs... + ) + summary_str = testargs_summary(T, sz) + return @testset "right_orth! and right_null! $summary_str" begin + A = instantiate_matrix(T, sz) + m, n = size(A) + minmn = min(m, n) + Ac = deepcopy(A) + C, Vᴴ = @testinferred right_orth(A) + Nᴴ = @testinferred right_null(A) + @test C isa typeof(A) && size(C) == (m, minmn) + @test Vᴴ isa typeof(A) && size(Vᴴ) == (minmn, n) + @test eltype(Nᴴ) == eltype(A) && size(Nᴴ) == (n - minmn, n) + @test C * Vᴴ ≈ A + @test isisometric(Vᴴ; side = :right) + @test LinearAlgebra.norm(A * adjoint(Nᴴ)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(Nᴴ; side = :right) + @test collect(Vᴴ)' * collect(Vᴴ) + collect(Nᴴ)' * collect(Nᴴ) ≈ I + + M = LinearMap(A) + # broken + #CM, VMᴴ = @testinferred right_orth(M; alg = :svd) + CM, VMᴴ = right_orth(M; alg = :svd) + @test parent(CM) * parent(VMᴴ) ≈ A + + Ac = similar(A) + C2, Vᴴ2 = @testinferred right_orth!(copy!(Ac, A), (C, Vᴴ)) + Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ) + @test C2 * Vᴴ2 ≈ A + @test isisometric(Vᴴ2; side = :right) + @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(Nᴴ; side = :right) + @test collect(Vᴴ2)' * collect(Vᴴ2) + collect(Nᴴ2)' * collect(Nᴴ2) ≈ I + + if (T <: Number || T <: Diagonal{<:Number, <:Vector}) + atol = eps(real(eltype(T))) + C2, Vᴴ2 = @testinferred right_orth!(copy!(Ac, A), (C, Vᴴ); trunc = (; atol)) + Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ; trunc = (; atol)) + @test C2 * Vᴴ2 ≈ A + @test isisometric(Vᴴ2; side = :right) + @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(Nᴴ; side = :right) + @test collect(Vᴴ2)' * collect(Vᴴ2) + collect(Nᴴ2)' * collect(Nᴴ2) ≈ I + + rtol = eps(real(eltype(T))) + C2, Vᴴ2 = @testinferred right_orth!(copy!(Ac, A), (C, Vᴴ); trunc = (; rtol)) + Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ; trunc = (; rtol)) + @test C2 * Vᴴ2 ≈ A + @test isisometric(Vᴴ2; side = :right) + @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(Nᴴ2; side = :right) + @test collect(Vᴴ2)' * collect(Vᴴ2) + collect(Nᴴ2)' * collect(Nᴴ2) ≈ I + end + + for alg in (:lq, :polar, :svd) + n < m && alg == :polar && continue + # broken + #C2, Vᴴ2 = @testinferred right_orth!(copy!(Ac, A), (C, Vᴴ); alg = alg) + C2, Vᴴ2 = right_orth!(copy!(Ac, A), (C, Vᴴ); alg = alg) + @test C2 * Vᴴ2 ≈ A + @test isisometric(Vᴴ2; side = :right) + if alg != :polar + Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ; alg = alg) + @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(Nᴴ2; side = :right) + @test collect(Vᴴ2)' * collect(Vᴴ2) + collect(Nᴴ2)' * collect(Nᴴ2) ≈ I + end + + if alg == :svd + if (T <: Number || T <: Diagonal{<:Number, <:Vector}) + # broken + #C2, Vᴴ2 = @testinferred right_orth!(copy!(Ac, A), (C, Vᴴ); alg = alg, trunc = (; atol)) + C2, Vᴴ2 = right_orth!(copy!(Ac, A), (C, Vᴴ); alg = alg, trunc = (; atol)) + Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ; alg = alg, trunc = (; atol)) + @test C2 * Vᴴ2 ≈ A + @test isisometric(Vᴴ2; side = :right) + @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(Nᴴ2; side = :right) + @test collect(Vᴴ2)' * collect(Vᴴ2) + collect(Nᴴ2)' * collect(Nᴴ2) ≈ I + + # broken + #C2, Vᴴ2 = @testinferred right_orth!(copy!(Ac, A), (C, Vᴴ); alg = alg, trunc = (; rtol)) + C2, Vᴴ2 = right_orth!(copy!(Ac, A), (C, Vᴴ); alg = alg, trunc = (; rtol)) + Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ; alg = alg, trunc = (; rtol)) + @test C2 * Vᴴ2 ≈ A + @test isisometric(Vᴴ2; side = :right) + @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(Nᴴ2; side = :right) + @test collect(Vᴴ2)' * collect(Vᴴ2) + collect(Nᴴ2)' * collect(Nᴴ2) ≈ I + end + else + @test_throws ArgumentError right_orth!(copy!(Ac, A), (C, Vᴴ); alg, trunc = (; atol)) + @test_throws ArgumentError right_orth!(copy!(Ac, A), (C, Vᴴ); alg, trunc = (; rtol)) + alg == :polar && continue + @test_throws ArgumentError right_null!(copy!(Ac, A), Nᴴ; alg, trunc = (; atol)) + @test_throws ArgumentError right_null!(copy!(Ac, A), Nᴴ; alg, trunc = (; rtol)) + end + end + end +end From 276c001b212d12fe0d35411e94695c9254678b33 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 23 Dec 2025 11:22:52 +0100 Subject: [PATCH 2/8] Don't test nonworking stuff --- test/orthnull.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/orthnull.jl b/test/orthnull.jl index c867a715..04aec40f 100644 --- a/test/orthnull.jl +++ b/test/orthnull.jl @@ -29,8 +29,10 @@ for T in (BLASFloats..., GenericFloats...), n in (37, m, 63) end end if !is_buildkite - TestSuite.test_orthnull(T, (m, n)) - AT = Diagonal{T, Vector{T}} - TestSuite.test_orthnull(AT, m) + if T ∈ BLASFloats # no qr_null or lq_null for GenericFloats + TestSuite.test_orthnull(T, (m, n)) + end + #AT = Diagonal{T, Vector{T}} + #TestSuite.test_orthnull(AT, m) # not supported end end From bb04fcc899b2ab01df9acb817daa0845b149bfb9 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Sat, 27 Dec 2025 20:05:33 +0100 Subject: [PATCH 3/8] Support GenericFloats too --- test/orthnull.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/orthnull.jl b/test/orthnull.jl index 04aec40f..0c8abf2d 100644 --- a/test/orthnull.jl +++ b/test/orthnull.jl @@ -6,7 +6,7 @@ using LinearAlgebra: LinearAlgebra, I, Diagonal using CUDA, AMDGPU BLASFloats = (Float32, Float64, ComplexF32, ComplexF64) -GenericFloats = (Float16, BigFloat, Complex{BigFloat}) +GenericFloats = (BigFloat, Complex{BigFloat}) @isdefined(TestSuite) || include("testsuite/TestSuite.jl") using .TestSuite @@ -29,9 +29,7 @@ for T in (BLASFloats..., GenericFloats...), n in (37, m, 63) end end if !is_buildkite - if T ∈ BLASFloats # no qr_null or lq_null for GenericFloats - TestSuite.test_orthnull(T, (m, n)) - end + TestSuite.test_orthnull(T, (m, n)) #AT = Diagonal{T, Vector{T}} #TestSuite.test_orthnull(AT, m) # not supported end From 3417648b6b2ebcc4f2ccf11692f8575479139fae Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 31 Dec 2025 11:04:14 +0100 Subject: [PATCH 4/8] Try to fix testinferred with algs --- test/testsuite/orthnull.jl | 62 +++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/test/testsuite/orthnull.jl b/test/testsuite/orthnull.jl index fa09214e..4a3c7b0e 100644 --- a/test/testsuite/orthnull.jl +++ b/test/testsuite/orthnull.jl @@ -3,6 +3,20 @@ using LinearAlgebra include("../linearmap.jl") +_left_orth_svd(x; kwargs...) = left_orth(x; alg = :svd, kwargs...) +_left_orth_svd!(x, VC; kwargs...) = left_orth!(x, VC; alg = :svd, kwargs...) +_left_orth_qr(x; kwargs...) = left_orth(x; alg = :qr, kwargs...) +_left_orth_qr!(x, VC; kwargs...) = left_orth!(x, VC; alg = :qr, kwargs...) +_left_orth_polar(x; kwargs...) = left_orth(x; alg = :polar, kwargs...) +_left_orth_polar!(x, VC; kwargs...) = left_orth!(x, VC; alg = :polar, kwargs...) + +_right_orth_svd(x; kwargs...) = right_orth(x; alg = :svd, kwargs...) +_right_orth_svd!(x, CVᴴ; kwargs...) = right_orth!(x, CVᴴ; alg = :svd, kwargs...) +_right_orth_lq(x; kwargs...) = right_orth(x; alg = :lq, kwargs...) +_right_orth_lq!(x, CVᴴ; kwargs...) = right_orth!(x, CVᴴ; alg = :lq, kwargs...) +_right_orth_polar(x; kwargs...) = right_orth(x; alg = :polar, kwargs...) +_right_orth_polar!(x, CVᴴ; kwargs...) = right_orth!(x, CVᴴ; alg = :polar, kwargs...) + function test_orthnull(T::Type, sz; kwargs...) summary_str = testargs_summary(T, sz) return @testset "orthnull $summary_str" begin @@ -34,9 +48,7 @@ function test_left_orthnull( @test collect(V) * collect(V)' + collect(N) * collect(N)' ≈ I M = LinearMap(A) - # broken - #VM, CM = @testinferred left_orth(M; alg = :svd) - VM, CM = left_orth(M; alg = :svd) + VM, CM = @testinferred _left_orth_svd(M) @test parent(VM) * parent(CM) ≈ A if m > n && (T <: Number || T <: Diagonal{<:Number, <:Vector}) @@ -53,9 +65,7 @@ function test_left_orthnull( end # passing a kind and some kwargs - # broken - # V, C = @testinferred left_orth(A; alg = :qr, positive = true) - V, C = left_orth(A; alg = :qr, positive = true) + V, C = @testinferred _left_orth_qr(A; positive = true) N = @testinferred left_null(A; alg = :qr, positive = true) @test V isa typeof(A) && size(V) == (m, minmn) @test C isa typeof(A) && size(C) == (minmn, n) @@ -117,9 +127,13 @@ function test_left_orthnull( for alg in (:qr, :polar, :svd) # explicit kind kwarg m < n && alg === :polar && continue - # broken - # V2, C2 = @testinferred left_orth!(copy!(Ac, A), (V, C); alg = alg) - V2, C2 = left_orth!(copy!(Ac, A), (V, C); alg = alg) + if alg == :svd + V2, C2 = @testinferred _left_orth_svd!(copy!(Ac, A), (V, C)) + elseif alg == :qr + V2, C2 = @testinferred _left_orth_qr!(copy!(Ac, A), (V, C)) + elseif alg == :polar + V2, C2 = @testinferred _left_orth_polar!(copy!(Ac, A), (V, C)) + end @test V2 * C2 ≈ A @test isisometric(V2) if alg != :polar @@ -132,9 +146,7 @@ function test_left_orthnull( # with kind and tol kwargs if alg == :svd if (T <: Number || T <: Diagonal{<:Number, <:Vector}) - # broken - # V2, C2 = @testinferred left_orth!(copy!(Ac, A), (V, C); alg = alg, trunc = (; atol)) - V2, C2 = left_orth!(copy!(Ac, A), (V, C); alg = alg, trunc = (; atol)) + V2, C2 = @testinferred _left_orth_svd!(copy!(Ac, A), (V, C); trunc = (; atol)) N2 = @testinferred left_null!(copy!(Ac, A), N; alg, trunc = (; atol)) @test V2 * C2 ≈ A @test isisometric(V2) @@ -142,9 +154,7 @@ function test_left_orthnull( @test isisometric(N2) @test collect(V2) * collect(V2)' + collect(N2) * collect(N2)' ≈ I - # broken - # V2, C2 = @testinferred left_orth!(copy!(Ac, A), (V, C); alg = alg, trunc = (; rtol)) - V2, C2 = left_orth!(copy!(Ac, A), (V, C); alg = alg, trunc = (; rtol)) + V2, C2 = @testinferred _left_orth_svd!(copy!(Ac, A), (V, C); trunc = (; rtol)) N2 = @testinferred left_null!(copy!(Ac, A), N; alg, trunc = (; rtol)) @test V2 * C2 ≈ A @test isisometric(V2) @@ -186,9 +196,7 @@ function test_right_orthnull( @test collect(Vᴴ)' * collect(Vᴴ) + collect(Nᴴ)' * collect(Nᴴ) ≈ I M = LinearMap(A) - # broken - #CM, VMᴴ = @testinferred right_orth(M; alg = :svd) - CM, VMᴴ = right_orth(M; alg = :svd) + CM, VMᴴ = @testinferred _right_orth_svd(M) @test parent(CM) * parent(VMᴴ) ≈ A Ac = similar(A) @@ -222,9 +230,13 @@ function test_right_orthnull( for alg in (:lq, :polar, :svd) n < m && alg == :polar && continue - # broken - #C2, Vᴴ2 = @testinferred right_orth!(copy!(Ac, A), (C, Vᴴ); alg = alg) - C2, Vᴴ2 = right_orth!(copy!(Ac, A), (C, Vᴴ); alg = alg) + if alg == :lq + C2, Vᴴ2 = @testinferred _right_orth_lq!(copy!(Ac, A), (C, Vᴴ)) + elseif alg == :polar + C2, Vᴴ2 = @testinferred _right_orth_polar!(copy!(Ac, A), (C, Vᴴ)) + elseif alg == :svd + C2, Vᴴ2 = @testinferred _right_orth_svd!(copy!(Ac, A), (C, Vᴴ)) + end @test C2 * Vᴴ2 ≈ A @test isisometric(Vᴴ2; side = :right) if alg != :polar @@ -236,9 +248,7 @@ function test_right_orthnull( if alg == :svd if (T <: Number || T <: Diagonal{<:Number, <:Vector}) - # broken - #C2, Vᴴ2 = @testinferred right_orth!(copy!(Ac, A), (C, Vᴴ); alg = alg, trunc = (; atol)) - C2, Vᴴ2 = right_orth!(copy!(Ac, A), (C, Vᴴ); alg = alg, trunc = (; atol)) + C2, Vᴴ2 = @testinferred _right_orth_svd!(copy!(Ac, A), (C, Vᴴ); trunc = (; atol)) Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ; alg = alg, trunc = (; atol)) @test C2 * Vᴴ2 ≈ A @test isisometric(Vᴴ2; side = :right) @@ -246,9 +256,7 @@ function test_right_orthnull( @test isisometric(Nᴴ2; side = :right) @test collect(Vᴴ2)' * collect(Vᴴ2) + collect(Nᴴ2)' * collect(Nᴴ2) ≈ I - # broken - #C2, Vᴴ2 = @testinferred right_orth!(copy!(Ac, A), (C, Vᴴ); alg = alg, trunc = (; rtol)) - C2, Vᴴ2 = right_orth!(copy!(Ac, A), (C, Vᴴ); alg = alg, trunc = (; rtol)) + C2, Vᴴ2 = @testinferred _right_orth_svd!(copy!(Ac, A), (C, Vᴴ); trunc = (; rtol)) Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ; alg = alg, trunc = (; rtol)) @test C2 * Vᴴ2 ≈ A @test isisometric(Vᴴ2; side = :right) From 19e20a74d000976383a0d4f4a3d4f1b624bbb9de Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 5 Jan 2026 14:07:45 +0100 Subject: [PATCH 5/8] Split up tests and address other comments --- src/interface/orthnull.jl | 2 + test/orthnull.jl | 12 +- test/testsuite/orthnull.jl | 296 +++++++++++++++++++++---------------- 3 files changed, 180 insertions(+), 130 deletions(-) diff --git a/src/interface/orthnull.jl b/src/interface/orthnull.jl index b20a841d..93c806db 100644 --- a/src/interface/orthnull.jl +++ b/src/interface/orthnull.jl @@ -443,6 +443,7 @@ left_orth_alg(alg::LeftOrthAlgorithm) = alg left_orth_alg(alg::QRAlgorithms) = LeftOrthViaQR(alg) left_orth_alg(alg::PolarAlgorithms) = LeftOrthViaPolar(alg) left_orth_alg(alg::SVDAlgorithms) = LeftOrthViaSVD(alg) +left_orth_alg(alg::DiagonalAlgorithm) = LeftOrthViaSVD(alg) left_orth_alg(alg::TruncatedAlgorithm{<:SVDAlgorithms}) = LeftOrthViaSVD(alg) """ @@ -478,6 +479,7 @@ right_orth_alg(alg::RightOrthAlgorithm) = alg right_orth_alg(alg::LQAlgorithms) = RightOrthViaLQ(alg) right_orth_alg(alg::PolarAlgorithms) = RightOrthViaPolar(alg) right_orth_alg(alg::SVDAlgorithms) = RightOrthViaSVD(alg) +right_orth_alg(alg::DiagonalAlgorithm) = RightOrthViaSVD(alg) right_orth_alg(alg::TruncatedAlgorithm{<:SVDAlgorithms}) = RightOrthViaSVD(alg) """ diff --git a/test/orthnull.jl b/test/orthnull.jl index 0c8abf2d..a3078152 100644 --- a/test/orthnull.jl +++ b/test/orthnull.jl @@ -18,19 +18,19 @@ for T in (BLASFloats..., GenericFloats...), n in (37, m, 63) TestSuite.seed_rng!(123) if T ∈ BLASFloats if CUDA.functional() - TestSuite.test_orthnull(CuMatrix{T}, (m, n)) - n == m && TestSuite.test_orthnull(Diagonal{T, CuVector{T}}, m) + TestSuite.test_orthnull(CuMatrix{T}, (m, n); test_nullity = false) + n == m && TestSuite.test_orthnull(Diagonal{T, CuVector{T}}, m; test_orthnull = false) end if AMDGPU.functional() - TestSuite.test_orthnull(ROCMatrix{T}, (m, n)) - n == m && TestSuite.test_orthnull(Diagonal{T, ROCVector{T}}, m) + TestSuite.test_orthnull(ROCMatrix{T}, (m, n); test_nullity = false) + n == m && TestSuite.test_orthnull(Diagonal{T, ROCVector{T}}, m; test_orthnull = false) end if AMDGPU.functional() end end if !is_buildkite TestSuite.test_orthnull(T, (m, n)) - #AT = Diagonal{T, Vector{T}} - #TestSuite.test_orthnull(AT, m) # not supported + AT = Diagonal{T, Vector{T}} + TestSuite.test_orthnull(AT, m; test_orthnull = false) end end diff --git a/test/testsuite/orthnull.jl b/test/testsuite/orthnull.jl index 4a3c7b0e..79349d2a 100644 --- a/test/testsuite/orthnull.jl +++ b/test/testsuite/orthnull.jl @@ -17,11 +17,83 @@ _right_orth_lq!(x, CVᴴ; kwargs...) = right_orth!(x, CVᴴ; alg = :lq, kwargs.. _right_orth_polar(x; kwargs...) = right_orth(x; alg = :polar, kwargs...) _right_orth_polar!(x, CVᴴ; kwargs...) = right_orth!(x, CVᴴ; alg = :polar, kwargs...) -function test_orthnull(T::Type, sz; kwargs...) +function test_orthnull(T::Type, sz; test_nullity = true, test_orthnull = true, kwargs...) summary_str = testargs_summary(T, sz) return @testset "orthnull $summary_str" begin - test_left_orthnull(T, sz; kwargs...) - test_right_orthnull(T, sz; kwargs...) + test_orthnull && test_left_orthnull(T, sz; kwargs...) + test_nullity && test_left_nullity(T, sz; kwargs...) + test_orthnull && test_right_orthnull(T, sz; kwargs...) + test_nullity && test_right_nullity(T, sz; kwargs...) + end +end + +function test_left_nullity( + T::Type, sz; + atol::Real = 0, rtol::Real = precision(T), + kwargs... + ) + summary_str = testargs_summary(T, sz) + return @testset "left_nullity $summary_str" begin + A = instantiate_matrix(T, sz) + Ac = deepcopy(A) + m, n = size(A) + minmn = min(m, n) + if m > n + nullity = 5 + V, C = @testinferred left_orth(A) + N = @testinferred left_null(A; trunc = (; maxnullity = nullity)) + @test V isa typeof(A) && size(V) == (m, minmn) + @test C isa typeof(A) && size(C) == (minmn, n) + @test eltype(N) == eltype(A) && size(N) == (m, nullity) + @test V * C ≈ A + @test isisometric(V) + @test LinearAlgebra.norm(A' * N) ≈ 0 atol = MatrixAlgebraKit.defaulttol(eltype(T)) + @test isisometric(N) + end + + rtol = eps(real(eltype(T))) + for (trunc_orth, trunc_null) in ( + ((; rtol = rtol), (; rtol = rtol)), + (trunctol(; rtol), trunctol(; rtol, keep_below = true)), + ) + V, C = left_orth(A) + N = left_null(A) + V2, C2 = @testinferred left_orth!(copy!(Ac, A), (V, C); trunc = trunc_orth) + N2 = @testinferred left_null!(copy!(Ac, A), N; trunc = trunc_null) + @test V2 * C2 ≈ A + @test isisometric(V2) + @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(eltype(T)) + @test isisometric(N2) + @test isleftcomplete(V2, N2) + end + + alg = :svd + V2, C2 = @testinferred _left_orth_svd!(copy!(Ac, A), (V, C); trunc = (; atol)) + N2 = @testinferred left_null!(copy!(Ac, A), N; alg, trunc = (; atol)) + @test V2 * C2 ≈ A + @test isisometric(V2) + @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(eltype(T)) + @test isisometric(N2) + @test isleftcomplete(V2, N2) + + V2, C2 = @testinferred _left_orth_svd!(copy!(Ac, A), (V, C); trunc = (; rtol)) + N2 = @testinferred left_null!(copy!(Ac, A), N; alg, trunc = (; rtol)) + @test V2 * C2 ≈ A + @test isisometric(V2) + @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(eltype(T)) + @test isisometric(N2) + @test isleftcomplete(V2, N2) + + # doesn't work on AMD... + atol = eps(real(eltype(T))) + V2, C2 = @testinferred left_orth!(copy!(Ac, A), (V, C); trunc = (; atol = atol)) + N2 = @testinferred left_null!(copy!(Ac, A), N; trunc = (; atol = atol)) + @test V2 * C2 ≈ A + @test isisometric(V2) + @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(N2) + @test isleftcomplete(V2, N2) + end end @@ -45,25 +117,12 @@ function test_left_orthnull( @test isisometric(V) @test LinearAlgebra.norm(A' * N) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) @test isisometric(N) - @test collect(V) * collect(V)' + collect(N) * collect(N)' ≈ I + @test isleftcomplete(V, N) M = LinearMap(A) VM, CM = @testinferred _left_orth_svd(M) @test parent(VM) * parent(CM) ≈ A - if m > n && (T <: Number || T <: Diagonal{<:Number, <:Vector}) - nullity = 5 - V, C = @testinferred left_orth(A) - N = @testinferred left_null(A; trunc = (; maxnullity = nullity)) - @test V isa typeof(A) && size(V) == (m, minmn) - @test C isa typeof(A) && size(C) == (minmn, n) - @test eltype(N) == eltype(A) && size(N) == (m, nullity) - @test V * C ≈ A - @test isisometric(V) - @test LinearAlgebra.norm(A' * N) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(N) - end - # passing a kind and some kwargs V, C = @testinferred _left_orth_qr(A; positive = true) N = @testinferred left_null(A; alg = :qr, positive = true) @@ -74,56 +133,27 @@ function test_left_orthnull( @test isisometric(V) @test LinearAlgebra.norm(A' * N) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) @test isisometric(N) - @test collect(V) * collect(V)' + collect(N) * collect(N)' ≈ I + @test isleftcomplete(V, N) # passing an algorithm - if !isa(A, Diagonal) - V, C = @testinferred left_orth(A; alg = MatrixAlgebraKit.default_qr_algorithm(A)) - N = @testinferred left_null(A; alg = :qr, positive = true) - @test V isa typeof(A) && size(V) == (m, minmn) - @test C isa typeof(A) && size(C) == (minmn, n) - @test eltype(N) == eltype(A) && size(N) == (m, m - minmn) - @test V * C ≈ A - @test isisometric(V) - @test LinearAlgebra.norm(A' * N) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(N) - @test collect(V) * collect(V)' + collect(N) * collect(N)' ≈ I - end + V, C = @testinferred left_orth(A; alg = MatrixAlgebraKit.default_qr_algorithm(A)) + N = @testinferred left_null(A; alg = :qr, positive = true) + @test V isa typeof(A) && size(V) == (m, minmn) + @test C isa typeof(A) && size(C) == (minmn, n) + @test eltype(N) == eltype(A) && size(N) == (m, m - minmn) + @test V * C ≈ A + @test isisometric(V) + @test LinearAlgebra.norm(A' * N) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(N) + @test isleftcomplete(V, N) - Ac = similar(A) V2, C2 = @testinferred left_orth!(copy!(Ac, A), (V, C)) N2 = @testinferred left_null!(copy!(Ac, A), N) @test V2 * C2 ≈ A @test isisometric(V2) @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) @test isisometric(N2) - @test collect(V2) * collect(V2)' + collect(N2) * collect(N2)' ≈ I - - # doesn't work on AMD... - atol = eps(real(eltype(T))) - V2, C2 = @testinferred left_orth!(copy!(Ac, A), (V, C); trunc = (; atol = atol)) - N2 = @testinferred left_null!(copy!(Ac, A), N; trunc = (; atol = atol)) - @test V2 * C2 ≈ A - @test isisometric(V2) - @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(N2) - @test collect(V2) * collect(V2)' + collect(N2) * collect(N2)' ≈ I - - if (T <: Number || T <: Diagonal{<:Number, <:Vector}) - rtol = eps(real(eltype(T))) - for (trunc_orth, trunc_null) in ( - ((; rtol = rtol), (; rtol = rtol)), - (trunctol(; rtol), trunctol(; rtol, keep_below = true)), - ) - V2, C2 = @testinferred left_orth!(copy!(Ac, A), (V, C); trunc = trunc_orth) - N2 = @testinferred left_null!(copy!(Ac, A), N; trunc = trunc_null) - @test V2 * C2 ≈ A - @test isisometric(V2) - @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(N2) - @test collect(V2) * collect(V2)' + collect(N2) * collect(N2)' ≈ I - end - end + @test isleftcomplete(V2, N2) for alg in (:qr, :polar, :svd) # explicit kind kwarg m < n && alg === :polar && continue @@ -140,29 +170,11 @@ function test_left_orthnull( N2 = @testinferred left_null!(copy!(Ac, A), N; alg) @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) @test isisometric(N2) - @test collect(V2) * collect(V2)' + collect(N2) * collect(N2)' ≈ I + @test isleftcomplete(V2, N2) end # with kind and tol kwargs - if alg == :svd - if (T <: Number || T <: Diagonal{<:Number, <:Vector}) - V2, C2 = @testinferred _left_orth_svd!(copy!(Ac, A), (V, C); trunc = (; atol)) - N2 = @testinferred left_null!(copy!(Ac, A), N; alg, trunc = (; atol)) - @test V2 * C2 ≈ A - @test isisometric(V2) - @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(N2) - @test collect(V2) * collect(V2)' + collect(N2) * collect(N2)' ≈ I - - V2, C2 = @testinferred _left_orth_svd!(copy!(Ac, A), (V, C); trunc = (; rtol)) - N2 = @testinferred left_null!(copy!(Ac, A), N; alg, trunc = (; rtol)) - @test V2 * C2 ≈ A - @test isisometric(V2) - @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(N2) - @test collect(V2) * collect(V2)' + collect(N2) * collect(N2)' ≈ I - end - else + if alg != :svd @test_throws ArgumentError left_orth!(copy!(Ac, A), (V, C); alg, trunc = (; atol)) @test_throws ArgumentError left_orth!(copy!(Ac, A), (V, C); alg, trunc = (; rtol)) alg == :polar && continue @@ -173,6 +185,57 @@ function test_left_orthnull( end end +function test_right_nullity( + T::Type, sz; + atol::Real = 0, rtol::Real = precision(T), + kwargs... + ) + summary_str = testargs_summary(T, sz) + return @testset "right_nullity $summary_str" begin + A = instantiate_matrix(T, sz) + Ac = deepcopy(A) + m, n = size(A) + minmn = min(m, n) + + C, Vᴴ = @testinferred right_orth(A) + Nᴴ = @testinferred right_null(A) + atol = eps(real(eltype(T))) + C2, Vᴴ2 = @testinferred right_orth!(copy!(Ac, A), (C, Vᴴ); trunc = (; atol)) + Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ; trunc = (; atol)) + @test C2 * Vᴴ2 ≈ A + @test isisometric(Vᴴ2; side = :right) + @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(eltype(T)) + @test isisometric(Nᴴ; side = :right) + @test isrightcomplete(Vᴴ2, Nᴴ2) + + rtol = eps(real(eltype(T))) + C2, Vᴴ2 = @testinferred right_orth!(copy!(Ac, A), (C, Vᴴ); trunc = (; rtol)) + Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ; trunc = (; rtol)) + @test C2 * Vᴴ2 ≈ A + @test isisometric(Vᴴ2; side = :right) + @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(eltype(T)) + @test isisometric(Nᴴ2; side = :right) + @test isrightcomplete(Vᴴ2, Nᴴ2) + + alg = :svd + C2, Vᴴ2 = @testinferred _right_orth_svd!(copy!(Ac, A), (C, Vᴴ); trunc = (; atol)) + Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ; alg = alg, trunc = (; atol)) + @test C2 * Vᴴ2 ≈ A + @test isisometric(Vᴴ2; side = :right) + @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(Nᴴ2; side = :right) + @test isrightcomplete(Vᴴ2, Nᴴ2) + + C2, Vᴴ2 = @testinferred _right_orth_svd!(copy!(Ac, A), (C, Vᴴ); trunc = (; rtol)) + Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ; alg = alg, trunc = (; rtol)) + @test C2 * Vᴴ2 ≈ A + @test isisometric(Vᴴ2; side = :right) + @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test isisometric(Nᴴ2; side = :right) + @test isrightcomplete(Vᴴ2, Nᴴ2) + end +end + function test_right_orthnull( T::Type, sz; atol::Real = 0, rtol::Real = precision(T), @@ -191,42 +254,45 @@ function test_right_orthnull( @test eltype(Nᴴ) == eltype(A) && size(Nᴴ) == (n - minmn, n) @test C * Vᴴ ≈ A @test isisometric(Vᴴ; side = :right) - @test LinearAlgebra.norm(A * adjoint(Nᴴ)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test LinearAlgebra.norm(A * adjoint(Nᴴ)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(eltype(T)) @test isisometric(Nᴴ; side = :right) - @test collect(Vᴴ)' * collect(Vᴴ) + collect(Nᴴ)' * collect(Nᴴ) ≈ I + @test isrightcomplete(Vᴴ, Nᴴ) M = LinearMap(A) CM, VMᴴ = @testinferred _right_orth_svd(M) @test parent(CM) * parent(VMᴴ) ≈ A - Ac = similar(A) + # passing a kind and some kwargs + C, Vᴴ = @testinferred _right_orth_lq(A; positive = true) + Nᴴ = @testinferred right_null(A; alg = :lq, positive = true) + @test C isa typeof(A) && size(C) == (m, minmn) + @test Vᴴ isa typeof(A) && size(Vᴴ) == (minmn, n) + @test eltype(Nᴴ) == eltype(A) && size(Nᴴ) == (n - minmn, n) + @test C * Vᴴ ≈ A + @test isisometric(Vᴴ; side = :right) + @test LinearAlgebra.norm(A * adjoint(Nᴴ)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(eltype(T)) + @test isisometric(Nᴴ; side = :right) + @test isrightcomplete(Vᴴ, Nᴴ) + + # passing an algorithm + C, Vᴴ = @testinferred right_orth(A; alg = MatrixAlgebraKit.default_lq_algorithm(A)) + Nᴴ = @testinferred right_null(A; alg = :lq, positive = true) + @test C isa typeof(A) && size(C) == (m, minmn) + @test Vᴴ isa typeof(A) && size(Vᴴ) == (minmn, n) + @test eltype(Nᴴ) == eltype(A) && size(Nᴴ) == (n - minmn, n) + @test C * Vᴴ ≈ A + @test isisometric(Vᴴ; side = :right) + @test LinearAlgebra.norm(A * adjoint(Nᴴ)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(eltype(T)) + @test isisometric(Nᴴ; side = :right) + @test isrightcomplete(Vᴴ, Nᴴ) + C2, Vᴴ2 = @testinferred right_orth!(copy!(Ac, A), (C, Vᴴ)) Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ) @test C2 * Vᴴ2 ≈ A @test isisometric(Vᴴ2; side = :right) - @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(eltype(T)) @test isisometric(Nᴴ; side = :right) - @test collect(Vᴴ2)' * collect(Vᴴ2) + collect(Nᴴ2)' * collect(Nᴴ2) ≈ I - - if (T <: Number || T <: Diagonal{<:Number, <:Vector}) - atol = eps(real(eltype(T))) - C2, Vᴴ2 = @testinferred right_orth!(copy!(Ac, A), (C, Vᴴ); trunc = (; atol)) - Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ; trunc = (; atol)) - @test C2 * Vᴴ2 ≈ A - @test isisometric(Vᴴ2; side = :right) - @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(Nᴴ; side = :right) - @test collect(Vᴴ2)' * collect(Vᴴ2) + collect(Nᴴ2)' * collect(Nᴴ2) ≈ I - - rtol = eps(real(eltype(T))) - C2, Vᴴ2 = @testinferred right_orth!(copy!(Ac, A), (C, Vᴴ); trunc = (; rtol)) - Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ; trunc = (; rtol)) - @test C2 * Vᴴ2 ≈ A - @test isisometric(Vᴴ2; side = :right) - @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(Nᴴ2; side = :right) - @test collect(Vᴴ2)' * collect(Vᴴ2) + collect(Nᴴ2)' * collect(Nᴴ2) ≈ I - end + @test isrightcomplete(Vᴴ2, Nᴴ2) for alg in (:lq, :polar, :svd) n < m && alg == :polar && continue @@ -241,30 +307,12 @@ function test_right_orthnull( @test isisometric(Vᴴ2; side = :right) if alg != :polar Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ; alg = alg) - @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) + @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(eltype(T)) @test isisometric(Nᴴ2; side = :right) - @test collect(Vᴴ2)' * collect(Vᴴ2) + collect(Nᴴ2)' * collect(Nᴴ2) ≈ I + @test isrightcomplete(Vᴴ2, Nᴴ2) end - if alg == :svd - if (T <: Number || T <: Diagonal{<:Number, <:Vector}) - C2, Vᴴ2 = @testinferred _right_orth_svd!(copy!(Ac, A), (C, Vᴴ); trunc = (; atol)) - Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ; alg = alg, trunc = (; atol)) - @test C2 * Vᴴ2 ≈ A - @test isisometric(Vᴴ2; side = :right) - @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(Nᴴ2; side = :right) - @test collect(Vᴴ2)' * collect(Vᴴ2) + collect(Nᴴ2)' * collect(Nᴴ2) ≈ I - - C2, Vᴴ2 = @testinferred _right_orth_svd!(copy!(Ac, A), (C, Vᴴ); trunc = (; rtol)) - Nᴴ2 = @testinferred right_null!(copy!(Ac, A), Nᴴ; alg = alg, trunc = (; rtol)) - @test C2 * Vᴴ2 ≈ A - @test isisometric(Vᴴ2; side = :right) - @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(Nᴴ2; side = :right) - @test collect(Vᴴ2)' * collect(Vᴴ2) + collect(Nᴴ2)' * collect(Nᴴ2) ≈ I - end - else + if alg != :svd @test_throws ArgumentError right_orth!(copy!(Ac, A), (C, Vᴴ); alg, trunc = (; atol)) @test_throws ArgumentError right_orth!(copy!(Ac, A), (C, Vᴴ); alg, trunc = (; rtol)) alg == :polar && continue From 5587bcf03ffab6e4e9e2a99f971ca1e0e12b9c84 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 6 Jan 2026 10:19:40 +0100 Subject: [PATCH 6/8] Remove extraneous lines --- src/interface/orthnull.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/interface/orthnull.jl b/src/interface/orthnull.jl index 93c806db..b20a841d 100644 --- a/src/interface/orthnull.jl +++ b/src/interface/orthnull.jl @@ -443,7 +443,6 @@ left_orth_alg(alg::LeftOrthAlgorithm) = alg left_orth_alg(alg::QRAlgorithms) = LeftOrthViaQR(alg) left_orth_alg(alg::PolarAlgorithms) = LeftOrthViaPolar(alg) left_orth_alg(alg::SVDAlgorithms) = LeftOrthViaSVD(alg) -left_orth_alg(alg::DiagonalAlgorithm) = LeftOrthViaSVD(alg) left_orth_alg(alg::TruncatedAlgorithm{<:SVDAlgorithms}) = LeftOrthViaSVD(alg) """ @@ -479,7 +478,6 @@ right_orth_alg(alg::RightOrthAlgorithm) = alg right_orth_alg(alg::LQAlgorithms) = RightOrthViaLQ(alg) right_orth_alg(alg::PolarAlgorithms) = RightOrthViaPolar(alg) right_orth_alg(alg::SVDAlgorithms) = RightOrthViaSVD(alg) -right_orth_alg(alg::DiagonalAlgorithm) = RightOrthViaSVD(alg) right_orth_alg(alg::TruncatedAlgorithm{<:SVDAlgorithms}) = RightOrthViaSVD(alg) """ From 93b0f2bcbde9fb0ffe7ac8f9aff2bf4c8d5cd6c6 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 6 Jan 2026 11:16:05 +0100 Subject: [PATCH 7/8] Support orthnull for Diagonal --- src/interface/orthnull.jl | 4 ++++ test/orthnull.jl | 6 +++--- test/testsuite/orthnull.jl | 8 ++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/interface/orthnull.jl b/src/interface/orthnull.jl index b20a841d..ad60648a 100644 --- a/src/interface/orthnull.jl +++ b/src/interface/orthnull.jl @@ -443,7 +443,9 @@ left_orth_alg(alg::LeftOrthAlgorithm) = alg left_orth_alg(alg::QRAlgorithms) = LeftOrthViaQR(alg) left_orth_alg(alg::PolarAlgorithms) = LeftOrthViaPolar(alg) left_orth_alg(alg::SVDAlgorithms) = LeftOrthViaSVD(alg) +left_orth_alg(alg::DiagonalAlgorithm) = LeftOrthViaQR(alg) left_orth_alg(alg::TruncatedAlgorithm{<:SVDAlgorithms}) = LeftOrthViaSVD(alg) +left_orth_alg(alg::TruncatedAlgorithm{DiagonalAlgorithm}) = LeftOrthViaSVD(alg) """ right_orth_alg(alg::AbstractAlgorithm) -> RightOrthAlgorithm @@ -478,7 +480,9 @@ right_orth_alg(alg::RightOrthAlgorithm) = alg right_orth_alg(alg::LQAlgorithms) = RightOrthViaLQ(alg) right_orth_alg(alg::PolarAlgorithms) = RightOrthViaPolar(alg) right_orth_alg(alg::SVDAlgorithms) = RightOrthViaSVD(alg) +right_orth_alg(alg::DiagonalAlgorithm) = RightOrthViaLQ(alg) right_orth_alg(alg::TruncatedAlgorithm{<:SVDAlgorithms}) = RightOrthViaSVD(alg) +right_orth_alg(alg::TruncatedAlgorithm{DiagonalAlgorithm}) = RightOrthViaSVD(alg) """ left_null_alg(alg::AbstractAlgorithm) -> LeftNullAlgorithm diff --git a/test/orthnull.jl b/test/orthnull.jl index a3078152..26c81747 100644 --- a/test/orthnull.jl +++ b/test/orthnull.jl @@ -19,11 +19,11 @@ for T in (BLASFloats..., GenericFloats...), n in (37, m, 63) if T ∈ BLASFloats if CUDA.functional() TestSuite.test_orthnull(CuMatrix{T}, (m, n); test_nullity = false) - n == m && TestSuite.test_orthnull(Diagonal{T, CuVector{T}}, m; test_orthnull = false) + n == m && TestSuite.test_orthnull(Diagonal{T, CuVector{T}}, m) end if AMDGPU.functional() TestSuite.test_orthnull(ROCMatrix{T}, (m, n); test_nullity = false) - n == m && TestSuite.test_orthnull(Diagonal{T, ROCVector{T}}, m; test_orthnull = false) + n == m && TestSuite.test_orthnull(Diagonal{T, ROCVector{T}}, m) end if AMDGPU.functional() end @@ -31,6 +31,6 @@ for T in (BLASFloats..., GenericFloats...), n in (37, m, 63) if !is_buildkite TestSuite.test_orthnull(T, (m, n)) AT = Diagonal{T, Vector{T}} - TestSuite.test_orthnull(AT, m; test_orthnull = false) + TestSuite.test_orthnull(AT, m) end end diff --git a/test/testsuite/orthnull.jl b/test/testsuite/orthnull.jl index 79349d2a..16b86c82 100644 --- a/test/testsuite/orthnull.jl +++ b/test/testsuite/orthnull.jl @@ -17,12 +17,12 @@ _right_orth_lq!(x, CVᴴ; kwargs...) = right_orth!(x, CVᴴ; alg = :lq, kwargs.. _right_orth_polar(x; kwargs...) = right_orth(x; alg = :polar, kwargs...) _right_orth_polar!(x, CVᴴ; kwargs...) = right_orth!(x, CVᴴ; alg = :polar, kwargs...) -function test_orthnull(T::Type, sz; test_nullity = true, test_orthnull = true, kwargs...) +function test_orthnull(T::Type, sz; test_nullity = true, kwargs...) summary_str = testargs_summary(T, sz) return @testset "orthnull $summary_str" begin - test_orthnull && test_left_orthnull(T, sz; kwargs...) + test_left_orthnull(T, sz; kwargs...) test_nullity && test_left_nullity(T, sz; kwargs...) - test_orthnull && test_right_orthnull(T, sz; kwargs...) + test_right_orthnull(T, sz; kwargs...) test_nullity && test_right_nullity(T, sz; kwargs...) end end @@ -276,9 +276,9 @@ function test_right_orthnull( # passing an algorithm C, Vᴴ = @testinferred right_orth(A; alg = MatrixAlgebraKit.default_lq_algorithm(A)) - Nᴴ = @testinferred right_null(A; alg = :lq, positive = true) @test C isa typeof(A) && size(C) == (m, minmn) @test Vᴴ isa typeof(A) && size(Vᴴ) == (minmn, n) + Nᴴ = @testinferred right_null(A; alg = :lq, positive = true) @test eltype(Nᴴ) == eltype(A) && size(Nᴴ) == (n - minmn, n) @test C * Vᴴ ≈ A @test isisometric(Vᴴ; side = :right) From 96ff3c8656d5dccdf724f130dbcb3506d837a766 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 3 Mar 2026 15:31:40 +0100 Subject: [PATCH 8/8] Try collecting Diagonals --- test/testsuite/TestSuite.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/testsuite/TestSuite.jl b/test/testsuite/TestSuite.jl index 361f125b..95fec775 100644 --- a/test/testsuite/TestSuite.jl +++ b/test/testsuite/TestSuite.jl @@ -73,9 +73,11 @@ is_pivoted(alg::MatrixAlgebraKit.LQViaTransposedQR) = is_pivoted(alg.qr_alg) isleftcomplete(V, N) = V * V' + N * N' ≈ I isleftcomplete(V::AnyCuMatrix, N::AnyCuMatrix) = isleftcomplete(collect(V), collect(N)) isleftcomplete(V::AnyROCMatrix, N::AnyROCMatrix) = isleftcomplete(collect(V), collect(N)) +isleftcomplete(V::Diagonal{TV, <:AnyROCVector}, N::Diagonal{TN, <:AnyROCVector}) where {TV, TN} = isleftcomplete(Diagonal(collect(V.diag)), Diagonal(collect(N.diag))) isrightcomplete(Vᴴ, Nᴴ) = Vᴴ' * Vᴴ + Nᴴ' * Nᴴ ≈ I isrightcomplete(V::AnyCuMatrix, N::AnyCuMatrix) = isrightcomplete(collect(V), collect(N)) isrightcomplete(V::AnyROCMatrix, N::AnyROCMatrix) = isrightcomplete(collect(V), collect(N)) +isrightcomplete(V::Diagonal{TV, <:AnyROCVector}, N::Diagonal{TN, <:AnyROCVector}) where {TV, TN} = isrightcomplete(Diagonal(collect(V.diag)), Diagonal(collect(N.diag))) instantiate_unitary(T, A, sz) = qr_compact(randn!(similar(A, eltype(T), sz, sz)))[1] # AMDGPU can't generate ComplexF32 random numbers