From 4da5dc15db1469fcebbe4bbcd24878fbd2a891fe 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/amd/orthnull.jl | 285 ------------------------------------ test/cuda/orthnull.jl | 264 --------------------------------- test/orthnull.jl | 234 +++-------------------------- test/runtests.jl | 12 +- test/testsuite/TestSuite.jl | 1 + test/testsuite/orthnull.jl | 268 +++++++++++++++++++++++++++++++++ 6 files changed, 293 insertions(+), 771 deletions(-) delete mode 100644 test/amd/orthnull.jl delete mode 100644 test/cuda/orthnull.jl create mode 100644 test/testsuite/orthnull.jl diff --git a/test/amd/orthnull.jl b/test/amd/orthnull.jl deleted file mode 100644 index 3223979c..00000000 --- a/test/amd/orthnull.jl +++ /dev/null @@ -1,285 +0,0 @@ -using MatrixAlgebraKit -using Test -using TestExtras -using StableRNGs -using LinearAlgebra: LinearAlgebra, I, mul!, diagm, norm -using MatrixAlgebraKit: GPU_SVDAlgorithm, check_input, copy_input, default_svd_algorithm, - initialize_output, AbstractAlgorithm -using AMDGPU - -# testing non-AbstractArray codepaths: -include(joinpath("..", "linearmap.jl")) - -eltypes = (Float32, Float64, ComplexF32, ComplexF64) - -@testset "left_orth and left_null for T = $T" for T in eltypes - rng = StableRNG(123) - m = 54 - @testset for n in (37, m, 63) - minmn = min(m, n) - A = ROCArray(randn(rng, T, m, n)) - V, C = @constinferred left_orth(A) - N = @constinferred left_null(A) - @test V isa ROCMatrix{T} && size(V) == (m, minmn) - @test C isa ROCMatrix{T} && size(C) == (minmn, n) - @test N isa ROCMatrix{T} && 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) - hV = collect(V) - hN = collect(N) - @test hV * hV' + hN * hN' ≈ I - - M = LinearMap(A) - VM, CM = @constinferred left_orth(M; alg = :svd) - @test parent(VM) * parent(CM) ≈ A - - if m > n - nullity = 5 - V, C = @constinferred left_orth(A) - AMDGPU.@allowscalar begin - N = @constinferred left_null(A; trunc = (; maxnullity = nullity)) - end - @test V isa ROCMatrix{T} && size(V) == (m, minmn) - @test C isa ROCMatrix{T} && size(C) == (minmn, n) - @test N isa ROCMatrix{T} && 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 = @constinferred left_orth(A; alg = :qr, positive = true) - N = @constinferred left_null(A; alg = :qr, positive = true) - @test V isa ROCMatrix{T} && size(V) == (m, minmn) - @test C isa ROCMatrix{T} && size(C) == (minmn, n) - @test N isa ROCMatrix{T} && 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) - hV = collect(V) - hN = collect(N) - @test hV * hV' + hN * hN' ≈ I - - # passing an algorithm - V, C = @constinferred left_orth(A; alg = CUSOLVER_HouseholderQR()) - N = @constinferred left_null(A; alg = :qr, positive = true) - @test V isa ROCMatrix{T} && size(V) == (m, minmn) - @test C isa ROCMatrix{T} && size(C) == (minmn, n) - @test N isa ROCMatrix{T} && 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) - hV = collect(V) - hN = collect(N) - @test hV * hV' + hN * hN' ≈ I - - Ac = similar(A) - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C)) - N2 = @constinferred left_null!(copy!(Ac, A), N) - @test V2 === V - @test C2 === C - @test N2 === N - @test V2 * C2 ≈ A - @test isisometric(V2) - @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(N2) - hV2 = collect(V2) - hN2 = collect(N2) - @test hV2 * hV2' + hN2 * hN2' ≈ I - - atol = eps(real(T)) - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C); trunc = (; atol = atol)) - AMDGPU.@allowscalar begin - N2 = @constinferred left_null!(copy!(Ac, A), N; trunc = (; atol = atol)) - end - @test V2 * C2 ≈ A - @test isisometric(V2) - @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(N2) - hV2 = collect(V2) - hN2 = collect(N2) - @test hV2 * hV2' + hN2 * hN2' ≈ I - - rtol = eps(real(T)) - for (trunc_orth, trunc_null) in ( - ((; rtol = rtol), (; rtol = rtol)), - (trunctol(; rtol), trunctol(; rtol, keep_below = true)), - ) - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C); trunc = trunc_orth) - AMDGPU.@allowscalar begin - N2 = @constinferred left_null!(copy!(Ac, A), N; trunc = trunc_null) - end - @test V2 * C2 ≈ A - @test isisometric(V2) - @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(N2) - hV2 = collect(V2) - hN2 = collect(N2) - @test hV2 * hV2' + hN2 * hN2' ≈ I - end - - @testset for alg in (:qr, :polar, :svd) # explicit alg kwarg - m < n && alg == :polar && continue - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C); alg = $(QuoteNode(alg))) - @test V2 * C2 ≈ A - @test isisometric(V2) - if alg != :polar - N2 = @constinferred left_null!(copy!(Ac, A), N; alg) - @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(N2) - hV2 = collect(V2) - hN2 = collect(N2) - @test hV2 * hV2' + hN2 * hN2' ≈ I - end - - # with alg and tol kwargs - if alg == :svd - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C); alg = $(QuoteNode(alg)), trunc = (; atol)) - AMDGPU.@allowscalar begin - N2 = @constinferred left_null!(copy!(Ac, A), N; alg, trunc = (; atol)) - end - @test V2 * C2 ≈ A - @test isisometric(V2) - @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(N2) - hV2 = collect(V2) - hN2 = collect(N2) - @test hV2 * hV2' + hN2 * hN2' ≈ I - - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C); alg = $(QuoteNode(alg)), trunc = (; rtol)) - AMDGPU.@allowscalar begin - N2 = @constinferred left_null!(copy!(Ac, A), N; alg, trunc = (; rtol)) - end - @test V2 * C2 ≈ A - @test isisometric(V2) - @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(N2) - hV2 = collect(V2) - hN2 = collect(N2) - @test hV2 * hV2' + hN2 * hN2' ≈ I - 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 - -@testset "right_orth and right_null for T = $T" for T in eltypes - rng = StableRNG(123) - m = 54 - @testset for n in (37, m, 63) - minmn = min(m, n) - A = ROCArray(randn(rng, T, m, n)) - C, Vᴴ = @constinferred right_orth(A) - Nᴴ = @constinferred right_null(A) - @test C isa ROCMatrix{T} && size(C) == (m, minmn) - @test Vᴴ isa ROCMatrix{T} && size(Vᴴ) == (minmn, n) - @test Nᴴ isa ROCMatrix{T} && 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) - hVᴴ = collect(Vᴴ) - hNᴴ = collect(Nᴴ) - @test hVᴴ' * hVᴴ + hNᴴ' * hNᴴ ≈ I - - M = LinearMap(A) - CM, VMᴴ = @constinferred right_orth(M; alg = :svd) - @test parent(CM) * parent(VMᴴ) ≈ A - - Ac = similar(A) - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ)) - Nᴴ2 = @constinferred 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) - hVᴴ2 = collect(Vᴴ2) - hNᴴ2 = collect(Nᴴ2) - @test hVᴴ2' * hVᴴ2 + hNᴴ2' * hNᴴ2 ≈ I - - atol = eps(real(T)) - rtol = eps(real(T)) - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ); trunc = (; atol = atol)) - AMDGPU.@allowscalar begin - Nᴴ2 = @constinferred right_null!(copy!(Ac, A), Nᴴ; trunc = (; atol = atol)) - end - @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) - hVᴴ2 = collect(Vᴴ2) - hNᴴ2 = collect(Nᴴ2) - @test hVᴴ2' * hVᴴ2 + hNᴴ2' * hNᴴ2 ≈ I - - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ); trunc = (; rtol = rtol)) - AMDGPU.@allowscalar begin - Nᴴ2 = @constinferred right_null!(copy!(Ac, A), Nᴴ; trunc = (; rtol = rtol)) - end - @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) - hVᴴ2 = collect(Vᴴ2) - hNᴴ2 = collect(Nᴴ2) - @test hVᴴ2' * hVᴴ2 + hNᴴ2' * hNᴴ2 ≈ I - - - @testset "alg = $alg" for alg in (:lq, :polar, :svd) - n < m && alg == :polar && continue - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ); alg = $(QuoteNode(alg))) - @test C2 * Vᴴ2 ≈ A - @test isisometric(Vᴴ2; side = :right) - if alg != :polar - Nᴴ2 = @constinferred right_null!(copy!(Ac, A), Nᴴ; alg = $(QuoteNode(alg))) - @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(Nᴴ2; side = :right) - hVᴴ2 = collect(Vᴴ2) - hNᴴ2 = collect(Nᴴ2) - @test hVᴴ2' * hVᴴ2 + hNᴴ2' * hNᴴ2 ≈ I - end - - if alg == :svd - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ); alg = $(QuoteNode(alg)), trunc = (; atol)) - AMDGPU.@allowscalar begin - Nᴴ2 = @constinferred right_null!(copy!(Ac, A), Nᴴ; alg = $(QuoteNode(alg)), trunc = (; atol)) - end - @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) - hVᴴ2 = collect(Vᴴ2) - hNᴴ2 = collect(Nᴴ2) - @test hVᴴ2' * hVᴴ2 + hNᴴ2' * hNᴴ2 ≈ I - - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ); alg = $(QuoteNode(alg)), trunc = (; rtol)) - AMDGPU.@allowscalar begin - Nᴴ2 = @constinferred right_null!(copy!(Ac, A), Nᴴ; alg = $(QuoteNode(alg)), trunc = (; rtol)) - end - @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) - hVᴴ2 = collect(Vᴴ2) - hNᴴ2 = collect(Nᴴ2) - @test hVᴴ2' * hVᴴ2 + hNᴴ2' * hNᴴ2 ≈ I - 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 diff --git a/test/cuda/orthnull.jl b/test/cuda/orthnull.jl deleted file mode 100644 index 2a2a26f6..00000000 --- a/test/cuda/orthnull.jl +++ /dev/null @@ -1,264 +0,0 @@ -using MatrixAlgebraKit -using Test -using TestExtras -using StableRNGs -using LinearAlgebra: LinearAlgebra, I, mul!, diagm, norm -using MatrixAlgebraKit: GPU_SVDAlgorithm, check_input, copy_input, default_svd_algorithm, - initialize_output, AbstractAlgorithm -using CUDA - -# testing non-AbstractArray codepaths: -include(joinpath("..", "linearmap.jl")) - -eltypes = (Float32, Float64, ComplexF32, ComplexF64) - -@testset "left_orth and left_null for T = $T" for T in eltypes - rng = StableRNG(123) - m = 54 - @testset for n in (37, m, 63) - minmn = min(m, n) - A = CuArray(randn(rng, T, m, n)) - V, C = @constinferred left_orth(A) - N = @constinferred left_null(A) - @test V isa CuMatrix{T} && size(V) == (m, minmn) - @test C isa CuMatrix{T} && size(C) == (minmn, n) - @test N isa CuMatrix{T} && size(N) == (m, m - minmn) - @test V * C ≈ A - @test isisometric(V) - @test norm(A' * N) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(N) - hV = collect(V) - hN = collect(N) - @test hV * hV' + hN * hN' ≈ I - - M = LinearMap(A) - VM, CM = @constinferred left_orth(M; alg = :svd) - @test parent(VM) * parent(CM) ≈ A - - if m > n - nullity = 5 - V, C = @constinferred left_orth(A) - CUDA.@allowscalar begin - N = @constinferred left_null(A; trunc = (; maxnullity = nullity)) - end - @test V isa CuMatrix{T} && size(V) == (m, minmn) - @test C isa CuMatrix{T} && size(C) == (minmn, n) - @test N isa CuMatrix{T} && 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 = @constinferred left_orth(A; alg = :qr, positive = true) - N = @constinferred left_null(A; alg = :qr, positive = true) - @test V isa CuMatrix{T} && size(V) == (m, minmn) - @test C isa CuMatrix{T} && size(C) == (minmn, n) - @test N isa CuMatrix{T} && 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) - hV = collect(V) - hN = collect(N) - @test hV * hV' + hN * hN' ≈ I - - # passing an algorithm - V, C = @constinferred left_orth(A; alg = CUSOLVER_HouseholderQR()) - N = @constinferred left_null(A; alg = :qr, positive = true) - @test V isa CuMatrix{T} && size(V) == (m, minmn) - @test C isa CuMatrix{T} && size(C) == (minmn, n) - @test N isa CuMatrix{T} && 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) - hV = collect(V) - hN = collect(N) - @test hV * hV' + hN * hN' ≈ I - - Ac = similar(A) - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C)) - N2 = @constinferred 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) - hV2 = collect(V2) - hN2 = collect(N2) - @test hV2 * hV2' + hN2 * hN2' ≈ I - - atol = eps(real(T)) - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C); trunc = (; atol = atol)) - N2 = @constinferred 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) - hV2 = collect(V2) - hN2 = collect(N2) - @test hV2 * hV2' + hN2 * hN2' ≈ I - - rtol = eps(real(T)) - for (trunc_orth, trunc_null) in ( - ((; rtol = rtol), (; rtol = rtol)), - (trunctol(; rtol), trunctol(; rtol, keep_below = true)), - ) - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C); trunc = trunc_orth) - N2 = @constinferred 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) - hV2 = collect(V2) - hN2 = collect(N2) - @test hV2 * hV2' + hN2 * hN2' ≈ I - end - - @testset for alg in (:qr, :polar, :svd) # explicit alg kwarg - m < n && alg == :polar && continue - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C); alg = $(QuoteNode(alg))) - @test V2 * C2 ≈ A - @test isisometric(V2) - if alg != :polar - N2 = @constinferred left_null!(copy!(Ac, A), N; alg) - @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(N2) - hV2 = collect(V2) - hN2 = collect(N2) - @test hV2 * hV2' + hN2 * hN2' ≈ I - end - - # with alg and tol kwargs - if alg == :svd - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C); alg = $(QuoteNode(alg)), trunc = (; atol)) - N2 = @constinferred 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) - hV2 = collect(V2) - hN2 = collect(N2) - @test hV2 * hV2' + hN2 * hN2' ≈ I - - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C); alg = $(QuoteNode(alg)), trunc = (; rtol)) - N2 = @constinferred 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) - hV2 = collect(V2) - hN2 = collect(N2) - @test hV2 * hV2' + hN2 * hN2' ≈ I - 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 - -@testset "right_orth and right_null for T = $T" for T in eltypes - rng = StableRNG(123) - m = 54 - @testset for n in (37, m, 63) - minmn = min(m, n) - A = CuArray(randn(rng, T, m, n)) - C, Vᴴ = @constinferred right_orth(A) - Nᴴ = @constinferred right_null(A) - @test C isa CuMatrix{T} && size(C) == (m, minmn) - @test Vᴴ isa CuMatrix{T} && size(Vᴴ) == (minmn, n) - @test Nᴴ isa CuMatrix{T} && 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) - hVᴴ = collect(Vᴴ) - hNᴴ = collect(Nᴴ) - @test hVᴴ' * hVᴴ + hNᴴ' * hNᴴ ≈ I - - M = LinearMap(A) - CM, VMᴴ = @constinferred right_orth(M; alg = :svd) - @test parent(CM) * parent(VMᴴ) ≈ A - - Ac = similar(A) - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ)) - Nᴴ2 = @constinferred 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) - hVᴴ2 = collect(Vᴴ2) - hNᴴ2 = collect(Nᴴ2) - @test hVᴴ2' * hVᴴ2 + hNᴴ2' * hNᴴ2 ≈ I - - atol = eps(real(T)) - rtol = eps(real(T)) - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ); trunc = (; atol = atol)) - Nᴴ2 = @constinferred right_null!(copy!(Ac, A), Nᴴ; trunc = (; atol = 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) - hVᴴ2 = collect(Vᴴ2) - hNᴴ2 = collect(Nᴴ2) - @test hVᴴ2' * hVᴴ2 + hNᴴ2' * hNᴴ2 ≈ I - - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ); trunc = (; rtol = rtol)) - Nᴴ2 = @constinferred right_null!(copy!(Ac, A), Nᴴ; trunc = (; rtol = 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) - hVᴴ2 = collect(Vᴴ2) - hNᴴ2 = collect(Nᴴ2) - @test hVᴴ2' * hVᴴ2 + hNᴴ2' * hNᴴ2 ≈ I - - @testset "alg = $alg" for alg in (:lq, :polar, :svd) - n < m && alg == :polar && continue - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ); alg = $(QuoteNode(alg))) - @test C2 * Vᴴ2 ≈ A - @test isisometric(Vᴴ2; side = :right) - if alg != :polar - Nᴴ2 = @constinferred right_null!(copy!(Ac, A), Nᴴ; alg = $(QuoteNode(alg))) - @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(Nᴴ2; side = :right) - hVᴴ2 = collect(Vᴴ2) - hNᴴ2 = collect(Nᴴ2) - @test hVᴴ2' * hVᴴ2 + hNᴴ2' * hNᴴ2 ≈ I - end - - if alg == :svd - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ); alg = $(QuoteNode(alg)), trunc = (; atol)) - Nᴴ2 = @constinferred right_null!(copy!(Ac, A), Nᴴ; alg = $(QuoteNode(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) - hVᴴ2 = collect(Vᴴ2) - hNᴴ2 = collect(Nᴴ2) - @test hVᴴ2' * hVᴴ2 + hNᴴ2' * hNᴴ2 ≈ I - - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ); alg = $(QuoteNode(alg)), trunc = (; rtol)) - Nᴴ2 = @constinferred right_null!(copy!(Ac, A), Nᴴ; alg = $(QuoteNode(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) - hVᴴ2 = collect(Vᴴ2) - hNᴴ2 = collect(Nᴴ2) - @test hVᴴ2' * hVᴴ2 + hNᴴ2' * hNᴴ2 ≈ I - 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 diff --git a/test/orthnull.jl b/test/orthnull.jl index ce742e8f..874971ec 100644 --- a/test/orthnull.jl +++ b/test/orthnull.jl @@ -2,225 +2,33 @@ using MatrixAlgebraKit using Test using TestExtras using StableRNGs -using LinearAlgebra: LinearAlgebra, I +using LinearAlgebra: LinearAlgebra, I, Diagonal +using CUDA, AMDGPU -# testing non-AbstractArray codepaths: -include("linearmap.jl") +BLASFloats = (Float32, Float64, ComplexF32, ComplexF64) +GenericFloats = (Float16, BigFloat, Complex{BigFloat}) -eltypes = (Float32, Float64, ComplexF32, ComplexF64) -@testset "left_orth and left_null for T = $T" for T in eltypes - rng = StableRNG(123) - m = 54 - for n in (37, m, 63) - minmn = min(m, n) - A = randn(rng, T, m, n) - V, C = @constinferred left_orth(A) - N = @constinferred left_null(A) - @test V isa Matrix{T} && size(V) == (m, minmn) - @test C isa Matrix{T} && size(C) == (minmn, n) - @test N isa Matrix{T} && 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 V * V' + N * N' ≈ I +@isdefined(TestSuite) || include("testsuite/TestSuite.jl") +using .TestSuite - M = LinearMap(A) - VM, CM = @constinferred left_orth(M; alg = :svd) - @test parent(VM) * parent(CM) ≈ A +is_buildkite = get(ENV, "BUILDKITE", "false") == "true" - if m > n - nullity = 5 - V, C = @constinferred left_orth(A) - N = @constinferred left_null(A; trunc = (; maxnullity = nullity)) - @test V isa Matrix{T} && size(V) == (m, minmn) - @test C isa Matrix{T} && size(C) == (minmn, n) - @test N isa Matrix{T} && size(N) == (m, nullity) - @test V * C ≈ A - @test isisometric(V) - @test LinearAlgebra.norm(A' * N) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(N) +m = 54 +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) end - - # passing a kind and some kwargs - V, C = @constinferred left_orth(A; alg = :qr, positive = true) - N = @constinferred left_null(A; alg = :qr, positive = true) - @test V isa Matrix{T} && size(V) == (m, minmn) - @test C isa Matrix{T} && size(C) == (minmn, n) - @test N isa Matrix{T} && 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 V * V' + N * N' ≈ I - - # passing an algorithm - V, C = @constinferred left_orth(A; alg = LAPACK_HouseholderQR()) - N = @constinferred left_null(A; alg = :qr, positive = true) - @test V isa Matrix{T} && size(V) == (m, minmn) - @test C isa Matrix{T} && size(C) == (minmn, n) - @test N isa Matrix{T} && 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 V * V' + N * N' ≈ I - - Ac = similar(A) - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C)) - N2 = @constinferred 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 V2 * V2' + N2 * N2' ≈ I - - atol = eps(real(T)) - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C); trunc = (; atol = atol)) - N2 = @constinferred 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 V2 * V2' + N2 * N2' ≈ I - - rtol = eps(real(T)) - for (trunc_orth, trunc_null) in ( - ((; rtol = rtol), (; rtol = rtol)), - (trunctol(; rtol), trunctol(; rtol, keep_below = true)), - ) - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C); trunc = trunc_orth) - N2 = @constinferred 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 V2 * V2' + N2 * N2' ≈ I - end - - for alg in (:qr, :polar, :svd) # explicit kind kwarg - m < n && alg === :polar && continue - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C); alg = $(QuoteNode(alg))) - @test V2 * C2 ≈ A - @test isisometric(V2) - if alg != :polar - N2 = @constinferred left_null!(copy!(Ac, A), N; alg) - @test LinearAlgebra.norm(A' * N2) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(N2) - @test V2 * V2' + N2 * N2' ≈ I - end - - # with kind and tol kwargs - if alg == :svd - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C); alg = $(QuoteNode(alg)), trunc = (; atol)) - N2 = @constinferred 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 V2 * V2' + N2 * N2' ≈ I - - V2, C2 = @constinferred left_orth!(copy!(Ac, A), (V, C); alg = $(QuoteNode(alg)), trunc = (; rtol)) - N2 = @constinferred 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 V2 * V2' + N2 * N2' ≈ I - 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 + if AMDGPU.functional() + TestSuite.test_orthnull(ROCMatrix{T}, (m, n)) + n == m && TestSuite.test_orthnull(Diagonal{T, ROCVector{T}}, m) end end -end - -@testset "right_orth and right_null for T = $T" for T in eltypes - rng = StableRNG(123) - m = 54 - for n in (37, m, 63) - minmn = min(m, n) - A = randn(rng, T, m, n) - C, Vᴴ = @constinferred right_orth(A) - Nᴴ = @constinferred right_null(A) - @test C isa Matrix{T} && size(C) == (m, minmn) - @test Vᴴ isa Matrix{T} && size(Vᴴ) == (minmn, n) - @test Nᴴ isa Matrix{T} && 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 Vᴴ' * Vᴴ + Nᴴ' * Nᴴ ≈ I - - M = LinearMap(A) - CM, VMᴴ = @constinferred right_orth(M; alg = :svd) - @test parent(CM) * parent(VMᴴ) ≈ A - - Ac = similar(A) - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ)) - Nᴴ2 = @constinferred 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 Vᴴ2' * Vᴴ2 + Nᴴ2' * Nᴴ2 ≈ I - - atol = eps(real(T)) - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ); trunc = (; atol)) - Nᴴ2 = @constinferred 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 Vᴴ2' * Vᴴ2 + Nᴴ2' * Nᴴ2 ≈ I - - rtol = eps(real(T)) - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ); trunc = (; rtol)) - Nᴴ2 = @constinferred 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 Vᴴ2' * Vᴴ2 + Nᴴ2' * Nᴴ2 ≈ I - - for alg in (:lq, :polar, :svd) - n < m && alg == :polar && continue - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ); alg = $(QuoteNode(alg))) - @test C2 * Vᴴ2 ≈ A - @test isisometric(Vᴴ2; side = :right) - if alg != :polar - Nᴴ2 = @constinferred right_null!(copy!(Ac, A), Nᴴ; alg = $(QuoteNode(alg))) - @test LinearAlgebra.norm(A * adjoint(Nᴴ2)) ≈ 0 atol = MatrixAlgebraKit.defaulttol(T) - @test isisometric(Nᴴ2; side = :right) - @test Vᴴ2' * Vᴴ2 + Nᴴ2' * Nᴴ2 ≈ I - end - - if alg == :svd - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ); alg = $(QuoteNode(alg)), trunc = (; atol)) - Nᴴ2 = @constinferred right_null!(copy!(Ac, A), Nᴴ; alg = $(QuoteNode(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 Vᴴ2' * Vᴴ2 + Nᴴ2' * Nᴴ2 ≈ I - - C2, Vᴴ2 = @constinferred right_orth!(copy!(Ac, A), (C, Vᴴ); alg = $(QuoteNode(alg)), trunc = (; rtol)) - Nᴴ2 = @constinferred right_null!(copy!(Ac, A), Nᴴ; alg = $(QuoteNode(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 Vᴴ2' * Vᴴ2 + Nᴴ2' * Nᴴ2 ≈ I - 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 + if !is_buildkite + TestSuite.test_orthnull(T, (m, n)) + AT = Diagonal{T, Vector{T}} + TestSuite.test_orthnull(AT, m) end end diff --git a/test/runtests.jl b/test/runtests.jl index baeb77bb..9fe64002 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -16,9 +16,6 @@ if !is_buildkite @safetestset "Generalized Eigenvalue Decomposition" begin include("gen_eig.jl") end - @safetestset "Image and Null Space" begin - include("orthnull.jl") - end @safetestset "Mooncake" begin include("mooncake.jl") end @@ -63,15 +60,15 @@ end @safetestset "Hermitian Eigenvalue Decomposition" begin include("eigh.jl") end +@safetestset "Image and Null Space" begin + include("orthnull.jl") +end using CUDA if CUDA.functional() @safetestset "CUDA SVD" begin include("cuda/svd.jl") end - @safetestset "CUDA Image and Null Space" begin - include("cuda/orthnull.jl") - end end using AMDGPU @@ -79,7 +76,4 @@ if AMDGPU.functional() @safetestset "AMDGPU SVD" begin include("amd/svd.jl") end - @safetestset "AMDGPU Image and Null Space" begin - include("amd/orthnull.jl") - end end diff --git a/test/testsuite/TestSuite.jl b/test/testsuite/TestSuite.jl index 25da777a..f288eff5 100644 --- a/test/testsuite/TestSuite.jl +++ b/test/testsuite/TestSuite.jl @@ -76,5 +76,6 @@ include("projections.jl") include("schur.jl") include("eig.jl") include("eigh.jl") +include("orthnull.jl") 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 1eb79ef94bdc01ecfd2c362abdbdfec11f2e1262 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 874971ec..5db9d2de 100644 --- a/test/orthnull.jl +++ b/test/orthnull.jl @@ -27,8 +27,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 964bf1e6b9d3c0b7247b244ff87a7b20e35ab9a9 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 23 Dec 2025 08:47:16 -0500 Subject: [PATCH 3/8] Fix linearmap --- test/linearmap.jl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/linearmap.jl b/test/linearmap.jl index bbf84e5e..ad6102d7 100644 --- a/test/linearmap.jl +++ b/test/linearmap.jl @@ -3,7 +3,7 @@ module LinearMaps export LinearMap using MatrixAlgebraKit - using MatrixAlgebraKit: AbstractAlgorithm + using MatrixAlgebraKit: AbstractAlgorithm, DiagonalAlgorithm import MatrixAlgebraKit as MAK using LinearAlgebra: LinearAlgebra, lmul!, rmul! @@ -32,6 +32,12 @@ module LinearMaps LinearMap.(MAK.initialize_output($f!, parent(A), alg)) @eval MAK.$f!(A::LinearMap, F, alg::AbstractAlgorithm) = LinearMap.(MAK.$f!(parent(A), parent.(F), alg)) + @eval MAK.check_input(::typeof($f!), A::LinearMap, F, alg::DiagonalAlgorithm) = + MAK.check_input($f!, parent(A), parent.(F), alg) + @eval MAK.initialize_output(::typeof($f!), A::LinearMap, alg::DiagonalAlgorithm) = + LinearMap.(MAK.initialize_output($f!, parent(A), alg)) + @eval MAK.$f!(A::LinearMap, F, alg::DiagonalAlgorithm) = + LinearMap.(MAK.$f!(parent(A), parent.(F), alg)) end for f in (:qr, :lq, :svd) From 9240221f93e5be06a5a686a5bbea59bd719d398a Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 23 Dec 2025 09:15:31 -0500 Subject: [PATCH 4/8] Fix AMD --- .../MatrixAlgebraKitAMDGPUExt.jl | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/ext/MatrixAlgebraKitAMDGPUExt/MatrixAlgebraKitAMDGPUExt.jl b/ext/MatrixAlgebraKitAMDGPUExt/MatrixAlgebraKitAMDGPUExt.jl index fd0c1604..abfa6353 100644 --- a/ext/MatrixAlgebraKitAMDGPUExt/MatrixAlgebraKitAMDGPUExt.jl +++ b/ext/MatrixAlgebraKitAMDGPUExt/MatrixAlgebraKitAMDGPUExt.jl @@ -159,45 +159,6 @@ function MatrixAlgebraKit._avgdiff!(A::StridedROCMatrix, B::StridedROCMatrix) return A, B end -function MatrixAlgebraKit.truncate( - ::typeof(left_null!), US::Tuple{TU, TS}, strategy::TruncationStrategy - ) where {TU <: ROCMatrix, TS} - # TODO: avoid allocation? - U, S = US - extended_S = vcat(diagview(S), zeros(eltype(S), max(0, size(S, 1) - size(S, 2)))) - ind = MatrixAlgebraKit.findtruncated(extended_S, strategy) - trunc_cols = collect(1:size(U, 2))[ind] - Utrunc = U[:, trunc_cols] - return Utrunc, ind -end -function MatrixAlgebraKit.truncate( - ::typeof(right_null!), SVᴴ::Tuple{TS, TVᴴ}, strategy::TruncationStrategy - ) where {TS, TVᴴ <: ROCMatrix} - # TODO: avoid allocation? - S, Vᴴ = SVᴴ - extended_S = vcat(diagview(S), zeros(eltype(S), max(0, size(S, 2) - size(S, 1)))) - ind = MatrixAlgebraKit.findtruncated(extended_S, strategy) - trunc_rows = collect(1:size(Vᴴ, 1))[ind] - Vᴴtrunc = Vᴴ[trunc_rows, :] - return Vᴴtrunc, ind -end - -# disambiguate: -function MatrixAlgebraKit.truncate( - ::typeof(left_null!), (U, S)::Tuple{TU, TS}, ::NoTruncation - ) where {TU <: ROCMatrix, TS} - m, n = size(S) - ind = (n + 1):m - return U[:, ind], ind -end -function MatrixAlgebraKit.truncate( - ::typeof(right_null!), (S, Vᴴ)::Tuple{TS, TVᴴ}, ::NoTruncation - ) where {TS, TVᴴ <: ROCMatrix} - m, n = size(S) - ind = (m + 1):n - return Vᴴ[ind, :], ind -end - # avoids calling the BlasMat specialization that assumes syrk! or herk! is called # TODO: remove once syrk! or herk! is defined function MatrixAlgebraKit._mul_herm!(C::StridedROCMatrix{T}, A::StridedROCMatrix{T}) where {T <: BlasFloat} From 24bc6d2dc73b8869545c4184c6416dcff1c623be Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Sat, 27 Dec 2025 20:05:33 +0100 Subject: [PATCH 5/8] Support GenericFloats too --- ext/MatrixAlgebraKitGenericLinearAlgebraExt.jl | 3 +++ test/linearmap.jl | 7 ++++++- test/orthnull.jl | 6 ++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ext/MatrixAlgebraKitGenericLinearAlgebraExt.jl b/ext/MatrixAlgebraKitGenericLinearAlgebraExt.jl index 14c63a4e..b78b38a0 100644 --- a/ext/MatrixAlgebraKitGenericLinearAlgebraExt.jl +++ b/ext/MatrixAlgebraKitGenericLinearAlgebraExt.jl @@ -2,6 +2,7 @@ module MatrixAlgebraKitGenericLinearAlgebraExt using MatrixAlgebraKit using MatrixAlgebraKit: sign_safe, check_input, diagview, gaugefix!, one!, default_fixgauge +using MatrixAlgebraKit: left_orth_alg using GenericLinearAlgebra: svd!, svdvals!, eigen!, eigvals!, Hermitian, qr! using LinearAlgebra: I, Diagonal, lmul! @@ -133,4 +134,6 @@ function MatrixAlgebraKit.default_lq_algorithm(::Type{T}; kwargs...) where {T <: return MatrixAlgebraKit.LQViaTransposedQR(GLA_HouseholderQR(; kwargs...)) end +MatrixAlgebraKit.left_orth_alg(alg::GLA_HouseholderQR) = MatrixAlgebraKit.LeftOrthViaQR(alg) + end diff --git a/test/linearmap.jl b/test/linearmap.jl index ad6102d7..a7adaae7 100644 --- a/test/linearmap.jl +++ b/test/linearmap.jl @@ -3,7 +3,8 @@ module LinearMaps export LinearMap using MatrixAlgebraKit - using MatrixAlgebraKit: AbstractAlgorithm, DiagonalAlgorithm + using MatrixAlgebraKit: AbstractAlgorithm, DiagonalAlgorithm, GLA_QRIteration + using GenericLinearAlgebra import MatrixAlgebraKit as MAK using LinearAlgebra: LinearAlgebra, lmul!, rmul! @@ -30,8 +31,12 @@ module LinearMaps MAK.check_input($f!, parent(A), parent.(F), alg) @eval MAK.initialize_output(::typeof($f!), A::LinearMap, alg::AbstractAlgorithm) = LinearMap.(MAK.initialize_output($f!, parent(A), alg)) + @eval MAK.initialize_output(::typeof($f!), A::LinearMap, alg::GLA_QRIteration) = + (nothing, nothing, nothing) @eval MAK.$f!(A::LinearMap, F, alg::AbstractAlgorithm) = LinearMap.(MAK.$f!(parent(A), parent.(F), alg)) + @eval MAK.$f!(A::LinearMap, F, alg::GLA_QRIteration) = + LinearMap.(MAK.$f!(parent(A), F, alg)) @eval MAK.check_input(::typeof($f!), A::LinearMap, F, alg::DiagonalAlgorithm) = MAK.check_input($f!, parent(A), parent.(F), alg) @eval MAK.initialize_output(::typeof($f!), A::LinearMap, alg::DiagonalAlgorithm) = diff --git a/test/orthnull.jl b/test/orthnull.jl index 5db9d2de..f2e0e611 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 @@ -27,9 +27,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 dd09fe22f77fb2b3470d6c18d7a1f75609614714 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Wed, 31 Dec 2025 11:04:14 +0100 Subject: [PATCH 6/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 04d929e783896bb1a9d7a0f1f27fc5f39397db49 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Mon, 5 Jan 2026 14:07:45 +0100 Subject: [PATCH 7/8] Split up tests and address other comments --- src/interface/orthnull.jl | 2 + test/orthnull.jl | 12 +- test/testsuite/TestSuite.jl | 9 +- test/testsuite/orthnull.jl | 296 +++++++++++++++++++++--------------- 4 files changed, 188 insertions(+), 131 deletions(-) diff --git a/src/interface/orthnull.jl b/src/interface/orthnull.jl index 64acb509..301aef8a 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 f2e0e611..dec12946 100644 --- a/test/orthnull.jl +++ b/test/orthnull.jl @@ -18,17 +18,17 @@ 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 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/TestSuite.jl b/test/testsuite/TestSuite.jl index f288eff5..734d48fd 100644 --- a/test/testsuite/TestSuite.jl +++ b/test/testsuite/TestSuite.jl @@ -11,7 +11,7 @@ module TestSuite using Test using MatrixAlgebraKit using MatrixAlgebraKit: diagview -using LinearAlgebra: Diagonal, norm, istriu, istril +using LinearAlgebra: Diagonal, norm, istriu, istril, I using Random, StableRNGs using AMDGPU, CUDA @@ -69,6 +69,13 @@ is_positive(alg::MatrixAlgebraKit.ROCSOLVER_HouseholderQR) = alg.positive is_positive(alg::MatrixAlgebraKit.LQViaTransposedQR) = is_positive(alg.qr_alg) 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)) +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)) + include("qr.jl") include("lq.jl") include("polar.jl") 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 9b450bd328177c6c86eb2d6d9a5c098142fa8df2 Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Tue, 6 Jan 2026 10:19:40 +0100 Subject: [PATCH 8/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 301aef8a..64acb509 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) """