Skip to content

Commit b38df0e

Browse files
lkdvosJutho
andauthored
Add isisometry function and integrate into tests (#38)
* Add `isisometry` * Use `isisometry` in tests * export function * Bump v0.2.5 * Update interface based on discussions * Update tests accordingly * Change `kind` to `side` * Update src/common/matrixproperties.jl Co-authored-by: Jutho <[email protected]> --------- Co-authored-by: Jutho <[email protected]>
1 parent dfa334d commit b38df0e

File tree

10 files changed

+140
-80
lines changed

10 files changed

+140
-80
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "MatrixAlgebraKit"
22
uuid = "6c742aac-3347-4629-af66-fc926824e5e4"
33
authors = ["Jutho <[email protected]> and contributors"]
4-
version = "0.2.4"
4+
version = "0.2.5"
55

66
[deps]
77
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

src/MatrixAlgebraKit.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ using LinearAlgebra: Diagonal, diag, diagind
99
using LinearAlgebra: UpperTriangular, LowerTriangular
1010
using LinearAlgebra: BlasFloat, BlasReal, BlasComplex, BlasInt, triu!, tril!, rdiv!, ldiv!
1111

12+
export isisometry, isunitary
13+
1214
export qr_compact, qr_full, qr_null, lq_compact, lq_full, lq_null
1315
export qr_compact!, qr_full!, qr_null!, lq_compact!, lq_full!, lq_null!
1416
export svd_compact, svd_full, svd_vals, svd_trunc
@@ -40,6 +42,7 @@ include("common/pullbacks.jl")
4042
include("common/safemethods.jl")
4143
include("common/view.jl")
4244
include("common/regularinv.jl")
45+
include("common/matrixproperties.jl")
4346

4447
include("yalapack.jl")
4548
include("algorithms.jl")

src/common/matrixproperties.jl

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""
2+
isisometry(A; side=:left, isapprox_kwargs...) -> Bool
3+
4+
Test whether a linear map is an isometry, where the type of isometry is controlled by `kind`:
5+
6+
- `side = :left` : `A' * A ≈ I`.
7+
- `side = :right` : `A * A' ≈ I`.
8+
9+
The `isapprox_kwargs` are passed on to `isapprox` to control the tolerances.
10+
11+
New specializations should overload [`is_left_isometry`](@ref) and [`is_right_isometry`](@ref).
12+
13+
See also [`isunitary`](@ref).
14+
"""
15+
function isisometry(A; side::Symbol=:left, isapprox_kwargs...)
16+
side === :left && return is_left_isometry(A; isapprox_kwargs...)
17+
side === :right && return is_right_isometry(A; isapprox_kwargs...)
18+
19+
throw(ArgumentError(lazy"Invalid isometry side: $side"))
20+
end
21+
22+
"""
23+
isunitary(A; isapprox_kwargs...)
24+
25+
Test whether a linear map is unitary, i.e. `A * A' ≈ I ≈ A' * A`.
26+
The `isapprox_kwargs` are passed on to `isapprox` to control the tolerances.
27+
28+
See also [`isisometry`](@ref).
29+
"""
30+
function isunitary(A; isapprox_kwargs...)
31+
return is_left_isometry(A; isapprox_kwargs...) &&
32+
is_right_isometry(A; isapprox_kwargs...)
33+
end
34+
35+
@doc """
36+
is_left_isometry(A; isapprox_kwargs...) -> Bool
37+
38+
Test whether a linear map is a left isometry, i.e. `A' * A ≈ I`.
39+
The `isapprox_kwargs` can be used to control the tolerances of the equality.
40+
41+
See also [`isisometry`](@ref) and [`is_right_isometry`](@ref).
42+
""" is_left_isometry
43+
44+
function is_left_isometry(A::AbstractMatrix; isapprox_kwargs...)
45+
return isapprox(A' * A, LinearAlgebra.I; isapprox_kwargs...)
46+
end
47+
48+
@doc """
49+
is_right_isometry(A; isapprox_kwargs...) -> Bool
50+
51+
Test whether a linear map is a right isometry, i.e. `A * A' I`.
52+
The `isapprox_kwargs` can be used to control the tolerances of the equality.
53+
54+
See also [`isisometry`](@ref) and [`is_left_isometry`](@ref).
55+
""" is_right_isometry
56+
57+
function is_right_isometry(A::AbstractMatrix; isapprox_kwargs...)
58+
return isapprox(A * A', LinearAlgebra.I; isapprox_kwargs...)
59+
end

test/eigh.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ using MatrixAlgebraKit: TruncatedAlgorithm, diagview
1717
1818
D, V = @constinferred eigh_full(A; alg)
1919
@test A * V ≈ V * D
20-
@test V' * V I
21-
@test V * V' ≈ I
20+
@test isunitary(V)
2221
@test all(isreal, D)
2322
2423
D2, V2 = eigh_full!(copy(A), (D, V), alg)
@@ -47,14 +46,14 @@ end
4746
4847
D1, V1 = @constinferred eigh_trunc(A; alg, trunc=truncrank(r))
4948
@test length(diagview(D1)) == r
50-
@test V1' * V1 I
49+
@test isisometry(V1)
5150
@test A * V1 ≈ V1 * D1
5251
@test LinearAlgebra.opnorm(A - V1 * D1 * V1') D₀[r + 1]
5352

5453
trunc = trunctol(s * D₀[r + 1])
5554
D2, V2 = @constinferred eigh_trunc(A; alg, trunc)
5655
@test length(diagview(D2)) == r
57-
@test V2' * V2 I
56+
@test isisometry(V2)
5857
@test A * V2 V2 * D2
5958

6059
# test for same subspace

test/lq.jl

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ using LinearAlgebra: diag, I
1414
@test L isa Matrix{T} && size(L) == (m, minmn)
1515
@test Q isa Matrix{T} && size(Q) == (minmn, n)
1616
@test L * Q A
17-
@test Q * Q' ≈ I
17+
@test isisometry(Q; side=:right)
1818
Nᴴ = @constinferred lq_null(A)
1919
@test Nᴴ isa Matrix{T} && size(Nᴴ) == (n - minmn, n)
2020
@test maximum(abs, A * Nᴴ') < eps(real(T))^(2 / 3)
21-
@test Nᴴ * Nᴴ' ≈ I
21+
@test isisometry(Nᴴ; side=:right)
2222
2323
Ac = similar(A)
2424
L2, Q2 = @constinferred lq_compact!(copy!(Ac, A), (L, Q))
@@ -35,12 +35,12 @@ using LinearAlgebra: diag, I
3535
# unblocked algorithm
3636
lq_compact!(copy!(Ac, A), (L, Q); blocksize=1)
3737
@test L * Q ≈ A
38-
@test Q * Q' I
38+
@test isisometry(Q; side=:right)
3939
lq_compact!(copy!(Ac, A), (noL, Q2); blocksize=1)
4040
@test Q == Q2
4141
lq_null!(copy!(Ac, A), Nᴴ; blocksize=1)
4242
@test maximum(abs, A * Nᴴ') < eps(real(T))^(2 / 3)
43-
@test Nᴴ * Nᴴ' I
43+
@test isisometry(Nᴴ; side=:right)
4444
if m <= n
4545
lq_compact!(copy!(Q2, A), (noL, Q2); blocksize=1) # in-place Q
4646
@test Q Q2
@@ -50,25 +50,25 @@ using LinearAlgebra: diag, I
5050
end
5151
lq_compact!(copy!(Ac, A), (L, Q); blocksize=8)
5252
@test L * Q A
53-
@test Q * Q' ≈ I
53+
@test isisometry(Q; side=:right)
5454
lq_compact!(copy!(Ac, A), (noL, Q2); blocksize=8)
5555
@test Q == Q2
5656
lq_null!(copy!(Ac, A), Nᴴ; blocksize=8)
5757
@test maximum(abs, A * Nᴴ') < eps(real(T))^(2 / 3)
58-
@test Nᴴ * Nᴴ' ≈ I
58+
@test isisometry(Nᴴ; side=:right)
5959
# pivoted
6060
@test_throws ArgumentError lq_compact!(copy!(Ac, A), (L, Q); pivoted=true)
6161
# positive
6262
lq_compact!(copy!(Ac, A), (L, Q); positive=true)
6363
@test L * Q ≈ A
64-
@test Q * Q' I
64+
@test isisometry(Q; side=:right)
6565
@test all(>=(zero(real(T))), real(diag(L)))
6666
lq_compact!(copy!(Ac, A), (noL, Q2); positive=true)
6767
@test Q == Q2
6868
# positive and blocksize 1
6969
lq_compact!(copy!(Ac, A), (L, Q); positive=true, blocksize=1)
7070
@test L * Q ≈ A
71-
@test Q * Q' ≈ I
71+
@test isisometry(Q; side=:right)
7272
@test all(>=(zero(real(T))), real(diag(L)))
7373
lq_compact!(copy!(Ac, A), (noL, Q2); positive=true, blocksize=1)
7474
@test Q == Q2
@@ -85,14 +85,14 @@ end
8585
@test L isa Matrix{T} && size(L) == (m, n)
8686
@test Q isa Matrix{T} && size(Q) == (n, n)
8787
@test L * Q ≈ A
88-
@test Q * Q' I
88+
@test isunitary(Q)
8989
9090
Ac = similar(A)
9191
L2, Q2 = @constinferred lq_full!(copy!(Ac, A), (L, Q))
9292
@test L2 === L
9393
@test Q2 === Q
9494
@test L * Q ≈ A
95-
@test Q * Q' ≈ I
95+
@test isunitary(Q)
9696
9797
noL = similar(A, 0, n)
9898
Q2 = similar(Q)
@@ -102,7 +102,7 @@ end
102102
# unblocked algorithm
103103
lq_full!(copy!(Ac, A), (L, Q); blocksize=1)
104104
@test L * Q ≈ A
105-
@test Q * Q' I
105+
@test isunitary(Q)
106106
lq_full!(copy!(Ac, A), (noL, Q2); blocksize=1)
107107
@test Q == Q2
108108
if n == m
@@ -112,22 +112,22 @@ end
112112
# # other blocking
113113
lq_full!(copy!(Ac, A), (L, Q); blocksize=18)
114114
@test L * Q ≈ A
115-
@test Q * Q' ≈ I
115+
@test isunitary(Q)
116116
lq_full!(copy!(Ac, A), (noL, Q2); blocksize=18)
117117
@test Q == Q2
118118
# pivoted
119119
@test_throws ArgumentError lq_full!(copy!(Ac, A), (L, Q); pivoted=true)
120120
# positive
121121
lq_full!(copy!(Ac, A), (L, Q); positive=true)
122122
@test L * Q ≈ A
123-
@test Q * Q' I
123+
@test isunitary(Q)
124124
@test all(>=(zero(real(T))), real(diag(L)))
125125
lq_full!(copy!(Ac, A), (noL, Q2); positive=true)
126126
@test Q == Q2
127127
# positive and blocksize 1
128128
lq_full!(copy!(Ac, A), (L, Q); positive=true, blocksize=1)
129129
@test L * Q ≈ A
130-
@test Q * Q' ≈ I
130+
@test isunitary(Q)
131131
@test all(>=(zero(real(T))), real(diag(L)))
132132
lq_full!(copy!(Ac, A), (noL, Q2); positive=true, blocksize=1)
133133
@test Q == Q2

0 commit comments

Comments
 (0)