Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "MatrixAlgebraKit"
uuid = "6c742aac-3347-4629-af66-fc926824e5e4"
authors = ["Jutho <[email protected]> and contributors"]
version = "0.2.4"
version = "0.2.5"

[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Expand Down
3 changes: 3 additions & 0 deletions src/MatrixAlgebraKit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ using LinearAlgebra: Diagonal, diag, diagind
using LinearAlgebra: UpperTriangular, LowerTriangular
using LinearAlgebra: BlasFloat, BlasReal, BlasComplex, BlasInt, triu!, tril!, rdiv!, ldiv!

export isisometry, isunitary

export qr_compact, qr_full, qr_null, lq_compact, lq_full, lq_null
export qr_compact!, qr_full!, qr_null!, lq_compact!, lq_full!, lq_null!
export svd_compact, svd_full, svd_vals, svd_trunc
Expand Down Expand Up @@ -40,6 +42,7 @@ include("common/pullbacks.jl")
include("common/safemethods.jl")
include("common/view.jl")
include("common/regularinv.jl")
include("common/matrixproperties.jl")

include("yalapack.jl")
include("algorithms.jl")
Expand Down
59 changes: 59 additions & 0 deletions src/common/matrixproperties.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
isisometry(A; side=:left, isapprox_kwargs...) -> Bool

Test whether a linear map is an isometry, where the type of isometry is controlled by `kind`:

- `side = :left` : `A' * A ≈ I`.
- `side = :right` : `A * A` ≈ I`.

The `isapprox_kwargs` are passed on to `isapprox` to control the tolerances.

New specializations should overload [`is_left_isometry`](@ref) and [`is_right_isometry`](@ref).

See also [`isunitary`](@ref).
"""
function isisometry(A; side::Symbol=:left, isapprox_kwargs...)
side === :left && return is_left_isometry(A; isapprox_kwargs...)
side === :right && return is_right_isometry(A; isapprox_kwargs...)

throw(ArgumentError(lazy"Invalid isometry side: $side"))

Check warning on line 19 in src/common/matrixproperties.jl

View check run for this annotation

Codecov / codecov/patch

src/common/matrixproperties.jl#L19

Added line #L19 was not covered by tests
end

"""
isunitary(A; isapprox_kwargs...)

Test whether a linear map is unitary, i.e. `A * A' ≈ I ≈ A' * A`.
The `isapprox_kwargs` are passed on to `isapprox` to control the tolerances.

See also [`isisometry`](@ref).
"""
function isunitary(A; isapprox_kwargs...)
return is_left_isometry(A; isapprox_kwargs...) &&
is_right_isometry(A; isapprox_kwargs...)
end

@doc """
is_left_isometry(A; isapprox_kwargs...) -> Bool

Test whether a linear map is a left isometry, i.e. `A' * A ≈ I`.
The `isapprox_kwargs` can be used to control the tolerances of the equality.

See also [`isisometry`](@ref) and [`is_right_isometry`](@ref).
""" is_left_isometry

function is_left_isometry(A::AbstractMatrix; isapprox_kwargs...)
return isapprox(A' * A, LinearAlgebra.I; isapprox_kwargs...)
end

@doc """
is_right_isometry(A; isapprox_kwargs...) -> Bool

Test whether a linear map is a right isometry, i.e. `A * A' ≈ I`.
The `isapprox_kwargs` can be used to control the tolerances of the equality.

See also [`isisometry`](@ref) and [`is_left_isometry`](@ref).
""" is_right_isometry

function is_right_isometry(A::AbstractMatrix; isapprox_kwargs...)
return isapprox(A * A', LinearAlgebra.I; isapprox_kwargs...)
end
7 changes: 3 additions & 4 deletions test/eigh.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ using MatrixAlgebraKit: TruncatedAlgorithm, diagview

D, V = @constinferred eigh_full(A; alg)
@test A * V ≈ V * D
@test V' * V ≈ I
@test V * V' ≈ I
@test isunitary(V)
@test all(isreal, D)

D2, V2 = eigh_full!(copy(A), (D, V), alg)
Expand Down Expand Up @@ -47,14 +46,14 @@ end

D1, V1 = @constinferred eigh_trunc(A; alg, trunc=truncrank(r))
@test length(diagview(D1)) == r
@test V1' * V1 ≈ I
@test isisometry(V1)
@test A * V1 ≈ V1 * D1
@test LinearAlgebra.opnorm(A - V1 * D1 * V1') ≈ D₀[r + 1]

trunc = trunctol(s * D₀[r + 1])
D2, V2 = @constinferred eigh_trunc(A; alg, trunc)
@test length(diagview(D2)) == r
@test V2' * V2 ≈ I
@test isisometry(V2)
@test A * V2 ≈ V2 * D2

# test for same subspace
Expand Down
28 changes: 14 additions & 14 deletions test/lq.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ using LinearAlgebra: diag, I
@test L isa Matrix{T} && size(L) == (m, minmn)
@test Q isa Matrix{T} && size(Q) == (minmn, n)
@test L * Q ≈ A
@test Q * Q' ≈ I
@test isisometry(Q; side=:right)
Nᴴ = @constinferred lq_null(A)
@test Nᴴ isa Matrix{T} && size(Nᴴ) == (n - minmn, n)
@test maximum(abs, A * Nᴴ') < eps(real(T))^(2 / 3)
@test Nᴴ * Nᴴ' ≈ I
@test isisometry(Nᴴ; side=:right)

Ac = similar(A)
L2, Q2 = @constinferred lq_compact!(copy!(Ac, A), (L, Q))
Expand All @@ -35,12 +35,12 @@ using LinearAlgebra: diag, I
# unblocked algorithm
lq_compact!(copy!(Ac, A), (L, Q); blocksize=1)
@test L * Q ≈ A
@test Q * Q' ≈ I
@test isisometry(Q; side=:right)
lq_compact!(copy!(Ac, A), (noL, Q2); blocksize=1)
@test Q == Q2
lq_null!(copy!(Ac, A), Nᴴ; blocksize=1)
@test maximum(abs, A * Nᴴ') < eps(real(T))^(2 / 3)
@test Nᴴ * Nᴴ' ≈ I
@test isisometry(Nᴴ; side=:right)
if m <= n
lq_compact!(copy!(Q2, A), (noL, Q2); blocksize=1) # in-place Q
@test Q ≈ Q2
Expand All @@ -50,25 +50,25 @@ using LinearAlgebra: diag, I
end
lq_compact!(copy!(Ac, A), (L, Q); blocksize=8)
@test L * Q ≈ A
@test Q * Q' ≈ I
@test isisometry(Q; side=:right)
lq_compact!(copy!(Ac, A), (noL, Q2); blocksize=8)
@test Q == Q2
lq_null!(copy!(Ac, A), Nᴴ; blocksize=8)
@test maximum(abs, A * Nᴴ') < eps(real(T))^(2 / 3)
@test Nᴴ * Nᴴ' ≈ I
@test isisometry(Nᴴ; side=:right)
# pivoted
@test_throws ArgumentError lq_compact!(copy!(Ac, A), (L, Q); pivoted=true)
# positive
lq_compact!(copy!(Ac, A), (L, Q); positive=true)
@test L * Q ≈ A
@test Q * Q' ≈ I
@test isisometry(Q; side=:right)
@test all(>=(zero(real(T))), real(diag(L)))
lq_compact!(copy!(Ac, A), (noL, Q2); positive=true)
@test Q == Q2
# positive and blocksize 1
lq_compact!(copy!(Ac, A), (L, Q); positive=true, blocksize=1)
@test L * Q ≈ A
@test Q * Q' ≈ I
@test isisometry(Q; side=:right)
@test all(>=(zero(real(T))), real(diag(L)))
lq_compact!(copy!(Ac, A), (noL, Q2); positive=true, blocksize=1)
@test Q == Q2
Expand All @@ -85,14 +85,14 @@ end
@test L isa Matrix{T} && size(L) == (m, n)
@test Q isa Matrix{T} && size(Q) == (n, n)
@test L * Q ≈ A
@test Q * Q' ≈ I
@test isunitary(Q)

Ac = similar(A)
L2, Q2 = @constinferred lq_full!(copy!(Ac, A), (L, Q))
@test L2 === L
@test Q2 === Q
@test L * Q ≈ A
@test Q * Q' ≈ I
@test isunitary(Q)

noL = similar(A, 0, n)
Q2 = similar(Q)
Expand All @@ -102,7 +102,7 @@ end
# unblocked algorithm
lq_full!(copy!(Ac, A), (L, Q); blocksize=1)
@test L * Q ≈ A
@test Q * Q' ≈ I
@test isunitary(Q)
lq_full!(copy!(Ac, A), (noL, Q2); blocksize=1)
@test Q == Q2
if n == m
Expand All @@ -112,22 +112,22 @@ end
# # other blocking
lq_full!(copy!(Ac, A), (L, Q); blocksize=18)
@test L * Q ≈ A
@test Q * Q' ≈ I
@test isunitary(Q)
lq_full!(copy!(Ac, A), (noL, Q2); blocksize=18)
@test Q == Q2
# pivoted
@test_throws ArgumentError lq_full!(copy!(Ac, A), (L, Q); pivoted=true)
# positive
lq_full!(copy!(Ac, A), (L, Q); positive=true)
@test L * Q ≈ A
@test Q * Q' ≈ I
@test isunitary(Q)
@test all(>=(zero(real(T))), real(diag(L)))
lq_full!(copy!(Ac, A), (noL, Q2); positive=true)
@test Q == Q2
# positive and blocksize 1
lq_full!(copy!(Ac, A), (L, Q); positive=true, blocksize=1)
@test L * Q ≈ A
@test Q * Q' ≈ I
@test isunitary(Q)
@test all(>=(zero(real(T))), real(diag(L)))
lq_full!(copy!(Ac, A), (noL, Q2); positive=true, blocksize=1)
@test Q == Q2
Expand Down
Loading