Skip to content

Commit f4b5d36

Browse files
committed
Make symtriUpper! rely on a QL style algorithm instead of a transposed LQ
This allows for working on columns instead of rows of the matrix which is faster. Also, simplify the implementations to rely on exsiting linear algebra routines. This also fixed a bug. Remove the AbstractMatrix sybtyping of EigenQ in alignment with the changes to LinearAlgebra.
1 parent 9e5cb8b commit f4b5d36

File tree

2 files changed

+109
-114
lines changed

2 files changed

+109
-114
lines changed

src/eigenSelfAdjoint.jl

+99-109
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ using LinearAlgebra
33
using LinearAlgebra: givensAlgorithm
44

55
## EigenQ
6-
struct EigenQ{T} <: AbstractMatrix{T}
6+
struct EigenQ{T}
77
uplo::Char
88
factors::Matrix{T}
99
τ::Vector{T}
@@ -23,35 +23,35 @@ end
2323
function LinearAlgebra.lmul!(Q::EigenQ, B::StridedVecOrMat)
2424
m, n = size(B)
2525
if size(Q, 1) != m
26-
throw(DimensionMismatch(""))
26+
throw(DimensionMismatch("first dimension of second argument matrix doesn't match the size of the first argument reflectors"))
2727
end
2828
if Q.uplo == 'L'
29-
@inbounds for k = length(Q.τ):-1:1
30-
for j = 1:size(B, 2)
31-
vb = B[k+1, j]
32-
for i = (k+2):m
33-
vb += Q.factors[i, k]'B[i, j]
34-
end
35-
τkvb = Q.τ[k]'vb
36-
B[k+1, j] -= τkvb
37-
for i = (k+2):m
38-
B[i, j] -= Q.factors[i, k] * τkvb
29+
for k = length(Q.τ):-1:1
30+
h = view(Q.factors, (k + 1):m, k)
31+
τ = Q.τ[k]
32+
tmp = h[1]
33+
h[1] = 1
34+
for j = 1:n
35+
tmp = τ * (h' * view(B, (k + 1):m, j))
36+
for i = 1:(m - k)
37+
B[k + i, j] -= h[i] * tmp
3938
end
4039
end
40+
h[1] = tmp
4141
end
4242
elseif Q.uplo == 'U'
43-
@inbounds for k = length(Q.τ):-1:1
44-
for j = 1:size(B, 2)
45-
vb = B[k+1, j]
46-
for i = (k+2):m
47-
vb += Q.factors[k, i] * B[i, j]
48-
end
49-
τkvb = Q.τ[k]'vb
50-
B[k+1, j] -= τkvb
51-
for i = (k+2):m
52-
B[i, j] -= Q.factors[k, i]' * τkvb
43+
for k = length(Q.τ):-1:1
44+
h = view(Q.factors, 1:(m - k), m - k + 1)
45+
τ = Q.τ[k]
46+
tmp = h[end]
47+
h[end] = 1
48+
for j = 1:n
49+
tmp = τ * (h' * view(B, 1:(n - k), j))
50+
for i = 1:(n - k)
51+
B[i, j] -= h[i] * tmp
5352
end
5453
end
54+
h[end] = tmp
5555
end
5656
else
5757
throw(ArgumentError("Q.uplo is neither 'L' or 'U'. This should never happen."))
@@ -62,45 +62,43 @@ end
6262
function LinearAlgebra.rmul!(A::StridedMatrix, Q::EigenQ)
6363
m, n = size(A)
6464
if size(Q, 1) != n
65-
throw(DimensionMismatch(""))
65+
throw(DimensionMismatch("second dimension of first argument matrix doesn't match the size of the second argument reflectors"))
6666
end
6767
if Q.uplo == 'L'
6868
for k = 1:length(Q.τ)
69-
for i = 1:size(A, 1)
70-
a = view(A, i, :)
71-
va = A[i, k+1]
72-
for j = (k+2):n
73-
va += A[i, j] * Q.factors[j, k]
74-
end
75-
τkva = va * Q.τ[k]'
76-
A[i, k+1] -= τkva
77-
for j = (k+2):n
78-
A[i, j] -= τkva * Q.factors[j, k]'
69+
h = view(Q.factors, (k + 1):n, k)
70+
τ = Q.τ[k]
71+
tmp = h[1]
72+
h[1] = 1
73+
for i = 1:m
74+
tmp = transpose(view(A, i, (k + 1):n)) * h * τ
75+
for j = 1:(n - k)
76+
A[i, k + j] -= tmp * h[j]'
7977
end
8078
end
79+
h[1] = tmp
8180
end
82-
elseif Q.uplo == 'U' # FixMe! Should consider reordering loops
81+
elseif Q.uplo == 'U'
8382
for k = 1:length(Q.τ)
84-
for i = 1:size(A, 1)
85-
a = view(A, i, :)
86-
va = A[i, k+1]
87-
for j = (k+2):n
88-
va += A[i, j] * Q.factors[k, j]'
89-
end
90-
τkva = va * Q.τ[k]'
91-
A[i, k+1] -= τkva
92-
for j = (k+2):n
93-
A[i, j] -= τkva * Q.factors[k, j]
83+
h = view(Q.factors, 1:(n - k), n - k + 1)
84+
τ = Q.τ[k]
85+
tmp = h[end]
86+
h[end] = 1
87+
for i = 1:m
88+
tmp = transpose(view(A, i, 1:(n - k))) * h * τ
89+
for j = 1:(n - k)
90+
A[i, j] -= tmp * h[j]'
9491
end
9592
end
93+
h[end] = tmp
9694
end
9795
else
9896
throw(ArgumentError("Q.uplo is neither 'L' or 'U'. This should never happen."))
9997
end
10098
return A
10199
end
102100

103-
Base.Array(Q::EigenQ) = lmul!(Q, Matrix{eltype(Q)}(I, size(Q, 1), size(Q, 1)))
101+
Base.Array(Q::EigenQ{T}) where T = lmul!(Q, Matrix{T}(I, size(Q, 1), size(Q, 1)))
104102

105103

106104
## SymmetricTridiagonalFactorization
@@ -463,49 +461,37 @@ function symtriLower!(
463461

464462
@inbounds for k = 1:(n-2+!(T <: Real))
465463

466-
τk = LinearAlgebra.reflector!(view(AS, (k+1):n, k))
464+
x = view(AS, (k + 1):n, k)
465+
466+
τk = LinearAlgebra.reflector!(x)
467467
τ[k] = τk
468468

469-
for i = (k+1):n
470-
u[i] = AS[i, k+1]
471-
end
472-
for j = (k+2):n
473-
ASjk = AS[j, k]
474-
for i = j:n
475-
u[i] += AS[i, j] * ASjk
476-
end
477-
end
478-
for j = (k+1):(n-1)
479-
tmp = zero(T)
480-
for i = j+1:n
481-
tmp += AS[i, j]'AS[i, k]
482-
end
483-
u[j] += tmp
484-
end
469+
# Temporarily store the implicit 1
470+
tmp = x[1]
471+
x[1] = 1
485472

486-
vcAv = u[k+1]
487-
for i = (k+2):n
488-
vcAv += AS[i, k]'u[i]
489-
end
490-
ξτ2 = real(vcAv) * abs2(τk) / 2
473+
= view(AS, (k + 1):n, (k + 1):n)
474+
= view(u, (k + 1):n)
491475

492-
u[k+1] = u[k+1] * τk - ξτ2
493-
for i = (k+2):n
494-
u[i] = u[i] * τk - ξτ2 * AS[i, k]
495-
end
476+
# Form Ãvτ
477+
mul!(ũ, Hermitian(Ã, :L), x, τk, zero(τk))
496478

497-
AS[k+1, k+1] -= 2real(u[k+1])
498-
for i = (k+2):n
499-
AS[i, k+1] -= u[i] + AS[i, k] * u[k+1]'
500-
end
501-
for j = (k+2):n
502-
ASjk = AS[j, k]
503-
uj = u[j]
504-
AS[j, j] -= 2real(uj * ASjk')
505-
for i = (j+1):n
506-
AS[i, j] -= u[i] * ASjk' + AS[i, k] * uj'
479+
# Form τx'Ãvτ (which is real except for rounding)
480+
ξ = real(τk'*(x'*ũ))
481+
482+
# Compute the rank up and down grade
483+
# Ã = Ã + τx'Ãvτ - τx'Ã - Ãvτ
484+
for j in 1:(n - k)
485+
xⱼ = x[j]
486+
ũⱼ = ũ[j]
487+
ξxⱼ = ξ * xⱼ
488+
for i in j:(n - k)
489+
AS[k + i, k + j] += x[i] * ξxⱼ' - x[i] * ũⱼ' - ũ[i] * xⱼ'
507490
end
508491
end
492+
493+
# Restore element
494+
x[1] = tmp
509495
end
510496
SymmetricTridiagonalFactorization(
511497
EigenQ(
@@ -531,37 +517,41 @@ function symtriUpper!(
531517
end
532518

533519
@inbounds for k = 1:(n-2+!(T <: Real))
534-
# This is a bit convoluted method to get the conjugated vector but conjugation is required for correctness of arrays of quaternions. Eventually, it should be sufficient to write vec(x') but it currently (July 10, 2018) hits a bug in LinearAlgebra
535-
τk = LinearAlgebra.reflector!(vec(transpose(view(AS, k, (k+1):n)')))
536-
τ[k] = τk'
537-
538-
for j = (k+1):n
539-
tmp = AS[k+1, j]
540-
for i = (k+2):j
541-
tmp += AS[k, i] * AS[i, j]
542-
end
543-
for i = (j+1):n
544-
tmp += AS[k, i] * AS[j, i]'
545-
end
546-
u[j] = τk' * tmp
547-
end
520+
# LAPACK allows the reflection element to be chosen freely whereas
521+
# LinearAlgebra's reflector! assumes it is the first element in the vector
522+
# Maybe we should implement a method similar to the LAPACK version
523+
x = view(AS, 1:(n - k), n - k + 1)
524+
xᵣ = view(AS, (n - k):-1:1, n - k + 1)
548525

549-
vcAv = u[k+1]
550-
for i = (k+2):n
551-
vcAv += u[i] * AS[k, i]'
552-
end
553-
ξ = real(vcAv * τk)
554-
555-
for j = (k+1):n
556-
ujt = u[j]
557-
hjt = j > (k + 1) ? AS[k, j] : one(ujt)
558-
ξhjt = ξ * hjt
559-
for i = (k+1):(j-1)
560-
hit = i > (k + 1) ? AS[k, i] : one(ujt)
561-
AS[i, j] -= hit' * ujt + u[i]' * hjt - hit' * ξhjt
526+
τk = LinearAlgebra.reflector!(xᵣ)
527+
τ[k] = τk
528+
529+
# Temporarily store the implicit 1
530+
tmp = x[end]
531+
x[end] = 1
532+
533+
= view(AS, 1:(n - k), 1:(n - k))
534+
= view(u, 1:(n - k))
535+
536+
# Form Ãvτ
537+
mul!(ũ, Hermitian(Ã, :U), x, τk, zero(τk))
538+
539+
# Form τx'Ãvτ (which is real except for rounding)
540+
ξ = real(τk'*(x'*ũ))
541+
542+
# Compute the rank up and down grade
543+
# Ã = Ã + τx'Ãvτ - τx'Ã - Ãvτ
544+
for j in 1:(n - k)
545+
xⱼ = x[j]
546+
ũⱼ = ũ[j]
547+
ξxⱼ = ξ * xⱼ
548+
for i in 1:j
549+
AS[i, j] += x[i] * ξxⱼ' - x[i] * ũⱼ' - ũ[i] * xⱼ'
562550
end
563-
AS[j, j] -= 2 * real(hjt' * ujt) - abs2(hjt) * ξ
564551
end
552+
553+
# Restore element
554+
x[end] = tmp
565555
end
566556
SymmetricTridiagonalFactorization(
567557
EigenQ(

test/eigenselfadjoint.jl

+10-5
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,17 @@ using Test, GenericLinearAlgebra, LinearAlgebra, Quaternions
5858
vals, vecs = eigen(A)
5959
@test issorted(vals)
6060

61+
@testset "symtri!" begin
62+
T = GenericLinearAlgebra.symtri!(copy(A))
63+
Q = Array(T.reflectors)
64+
@test Q == lmul!(T.reflectors, Matrix{eltype(A)}(I, size(A)...))
65+
@test Q rmul!(Matrix{eltype(A)}(I, size(A)...), T.reflectors)
66+
@test Q'Q Q*Q' I
67+
@test Q' * A * Q T.diagonals
68+
end
69+
6170
@testset "default" begin
62-
if uplo == :L # FixMe! Probably an conjugation is off somewhere. Don't have time to check now.
63-
@test_broken vecs' * A * vecs diagm(0 => vals)
64-
else
65-
@test vecs' * A * vecs diagm(0 => vals)
66-
end
71+
@test A * vecs vecs * Diagonal(vals)
6772
@test eigvals(A) vals
6873
@test vals λ rtol = 1e-13 * n
6974
@test vecs'vecs Matrix(I, n, n)

0 commit comments

Comments
 (0)