Skip to content

Commit d30ad01

Browse files
Merge pull request #74 from JuliaLinearAlgebra/rms-lulinv
Add the A = LUL⁻¹ factorization
2 parents c684abe + ddbefdf commit d30ad01

File tree

12 files changed

+373
-50
lines changed

12 files changed

+373
-50
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "MatrixFactorizations"
22
uuid = "a3b82374-2e81-5b9e-98ce-41277c0e4c87"
3-
version = "3.1"
3+
version = "3.1.1"
44

55
[deps]
66
ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a"

src/MatrixFactorizations.jl

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
module MatrixFactorizations
22
using Base, LinearAlgebra, ArrayLayouts
3-
import Base: axes, axes1, getproperty, iterate, tail, oneto
3+
import Base: axes, axes1, getproperty, propertynames, iterate, tail, oneto
44
import LinearAlgebra: BlasInt, BlasReal, BlasFloat, BlasComplex, axpy!,
55
copy_oftype, checksquare, adjoint, transpose, AdjOrTrans, HermOrSym,
66
det, logdet, logabsdet, isposdef
77
import LinearAlgebra.LAPACK: chkuplo, chktrans
88
import LinearAlgebra: cholesky, cholesky!, norm, diag, eigvals!, eigvals, eigen!, eigen,
99
qr, axpy!, ldiv!, rdiv!, mul!, lu, lu!, ldlt, ldlt!, AbstractTriangular, inv,
1010
chkstride1, kron, lmul!, rmul!, factorize, StructuredMatrixStyle, det, logabsdet,
11-
AbstractQ, _zeros, _cut_B, _ret_size, require_one_based_indexing, checksquare,
11+
AbstractQ, _zeros, _cut_B, _ret_size, require_one_based_indexing,
1212
checknonsingular, ipiv2perm, copytri!, issuccess, RealHermSymComplexHerm,
1313
cholcopy, checkpositivedefinite, char_uplo, copymutable_oftype, copy_similar, choltype
1414

@@ -32,8 +32,9 @@ import ArrayLayouts: reflector!, reflectorApply!, materialize!, @_layoutlmul, @_
3232
layout_getindex, rowsupport, colsupport
3333

3434

35-
export ul, ul!, ql, ql!, qrunblocked, qrunblocked!, UL, QL, reversecholesky, reversecholesky!, ReverseCholesky
36-
35+
export ul, ul!, ql, ql!, qrunblocked, qrunblocked!, UL, QL,
36+
reversecholesky, reversecholesky!, ReverseCholesky,
37+
lulinv, lulinv!, LULinv
3738

3839
const AdjointQtype = isdefined(LinearAlgebra, :AdjointQ) ? LinearAlgebra.AdjointQ : Adjoint
3940
const AbstractQtype = AbstractQ <: AbstractMatrix ? AbstractMatrix : AbstractQ
@@ -123,5 +124,6 @@ include("ql.jl")
123124
include("rq.jl")
124125
include("polar.jl")
125126
include("reversecholesky.jl")
127+
include("lulinv.jl")
126128

127129
end #module

src/choleskyinv.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ end
2323
factorization.
2424
2525
The two factorizations are obtained in one pass and this is faster
26-
then calling Julia's [chelosky](https://docs.julialang.org/en/v1/stdlib/LinearAlgebra/#LinearAlgebra.cholesky)
26+
then calling Julia's [cholesky](https://docs.julialang.org/en/v1/stdlib/LinearAlgebra/#LinearAlgebra.cholesky)
2727
function and inverting the lower factor for small matrices.
2828
2929
Input matrix `P` may be of type `Matrix` or `Hermitian`. Since only the
@@ -118,7 +118,7 @@ function choleskyinv!(P::Matrix{T};
118118
tol::Real = eps(real(T))) where T<:Union{Real, Complex}
119119
LinearAlgebra.require_one_based_indexing(P)
120120
n = LinearAlgebra.checksquare(P)
121-
121+
122122
@inbounds for j=1:n-1
123123
check && abs2(P[j, j])<tol && throw(LinearAlgebra.PosDefException(1))
124124
for i=j+1:n

src/lulinv.jl

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
"""
2+
LULinv <: Factorization
3+
4+
Matrix factorization type of the `LUL⁻¹` factorization of a square matrix `A`. This
5+
is the return type of [`lulinv`](@ref), the corresponding matrix factorization function.
6+
7+
The individual components of the factorization `F::LULinv` can be accessed via [`getproperty`](@ref):
8+
9+
| Component | Description |
10+
|:----------|:--------------------------------------------|
11+
| `F.L` | `L` (unit lower triangular) part of `LUL⁻¹` |
12+
| `F.U` | `U` (upper triangular) part of `LUL⁻¹` |
13+
14+
Iterating the factorization produces the components `F.L` and `F.U`.
15+
16+
# Examples
17+
```jldoctest
18+
julia> A = [4 3; 6 3]
19+
2×2 Array{Int64,2}:
20+
4 3
21+
6 3
22+
23+
julia> F = lulinv(A)
24+
LULinv{Float64, Matrix{Float64}}
25+
L factor:
26+
2×2 Matrix{Float64}:
27+
1.0 0.0
28+
-1.59067 1.0
29+
U factor:
30+
2×2 Matrix{Float64}:
31+
-0.772002 3.0
32+
0.0 7.772
33+
34+
julia> F.L * F.U / F.L ≈ A
35+
true
36+
37+
julia> l, u = lulinv(A); # destructuring via iteration
38+
39+
julia> l == F.L && u == F.U
40+
true
41+
42+
julia> A = [-150 334 778; -89 195 464; 5 -10 -27]
43+
3×3 Matrix{Int64}:
44+
-150 334 778
45+
-89 195 464
46+
5 -10 -27
47+
48+
julia> F = lulinv(A, [17, -2, 3//1]) # can input rational eigenvalues directly
49+
LULinv{Rational{Int64}, Matrix{Rational{Int64}}}
50+
L factor:
51+
3×3 Matrix{Rational{Int64}}:
52+
1 0 0
53+
1//2 1 0
54+
0 -2//5 1
55+
U factor:
56+
3×3 Matrix{Rational{Int64}}:
57+
17 114//5 778
58+
0 -2 75
59+
0 0 3
60+
61+
julia> F.L * F.U / F.L == A
62+
true
63+
```
64+
"""
65+
struct LULinv{T, S <: AbstractMatrix{T}} <: Factorization{T}
66+
factors::S
67+
function LULinv{T, S}(factors) where {T, S <: AbstractMatrix{T}}
68+
require_one_based_indexing(factors)
69+
new{T, S}(factors)
70+
end
71+
end
72+
73+
74+
LULinv(factors::AbstractMatrix{T}) where T = LULinv{T, typeof(factors)}(factors)
75+
LULinv{T}(factors::AbstractMatrix) where T = LULinv(convert(AbstractMatrix{T}, factors))
76+
LULinv{T}(F::LULinv) where T = LULinv{T}(F.factors)
77+
78+
iterate(F::LULinv) = (F.L, Val(:U))
79+
iterate(F::LULinv, ::Val{:U}) = (F.U, Val(:done))
80+
iterate(F::LULinv, ::Val{:done}) = nothing
81+
82+
83+
function lulinvtype(T::Type)
84+
# In generic_ulfact!, the elements of the lower part of the matrix are
85+
# obtained using the division of two matrix elements. Hence their type can
86+
# be different (e.g. the division of two types with the same unit is a type
87+
# without unit).
88+
# The elements of the upper part are obtained by U - L * U / L
89+
# where U is an upper part element and L is a lower part element.
90+
# Therefore, the types LT, UT should be invariant under the map:
91+
# (LT, UT) -> begin
92+
# L = oneunit(UT) / oneunit(UT)
93+
# U = oneunit(UT) - L * oneunit(UT) / L
94+
# typeof(L), typeof(U)
95+
# end
96+
# The following should handle most cases
97+
UT = typeof(oneunit(T) - (oneunit(T) / (oneunit(T) + zero(T)) * oneunit(T) * (oneunit(T) + zero(T)) / oneunit(T)))
98+
LT = typeof(oneunit(UT) / oneunit(UT))
99+
S = promote_type(T, LT, UT)
100+
end
101+
102+
103+
size(A::LULinv) = size(getfield(A, :factors))
104+
size(A::LULinv, i) = size(getfield(A, :factors), i)
105+
106+
function getL(F::LULinv{T}) where T
107+
n = size(F.factors, 1)
108+
L = tril!(getindex(getfield(F, :factors), 1:n, 1:n))
109+
for i in 1:n L[i, i] = one(T) end
110+
return L
111+
end
112+
113+
function getU(F::LULinv)
114+
n = size(F.factors, 1)
115+
triu!(getindex(getfield(F, :factors), 1:n, 1:n))
116+
end
117+
118+
function getproperty(F::LULinv{T, <: AbstractMatrix}, d::Symbol) where T
119+
if d === :L
120+
return getL(F)
121+
elseif d === :U
122+
return getU(F)
123+
else
124+
getfield(F, d)
125+
end
126+
end
127+
128+
propertynames(F::LULinv, private::Bool=false) =
129+
(:L, :U, (private ? fieldnames(typeof(F)) : ())...)
130+
131+
function show(io::IO, mime::MIME{Symbol("text/plain")}, F::LULinv)
132+
summary(io, F); println(io)
133+
println(io, "L factor:")
134+
show(io, mime, F.L)
135+
println(io, "\nU factor:")
136+
show(io, mime, F.U)
137+
end
138+
139+
140+
lulinv(A::AbstractMatrix; kwds...) = lulinv(A, eigvals(A); kwds...)
141+
function lulinv(A::AbstractMatrix{T}, λ::AbstractVector{T}; kwds...) where T
142+
S = lulinvtype(T)
143+
lulinv!(copy_oftype(A, S), copy_oftype(λ, S); kwds...)
144+
end
145+
function lulinv(A::AbstractMatrix{T1}, λ::AbstractVector{T2}; kwds...) where {T1, T2}
146+
T = promote_type(T1, T2)
147+
S = lulinvtype(T)
148+
lulinv!(copy_oftype(A, S), copy_oftype(λ, S); kwds...)
149+
end
150+
151+
function lulinv!(A::Matrix{T}, λ::Vector{T}; rtol::Real = size(A, 1)*eps(real(float(oneunit(T))))) where T
152+
n = checksquare(A)
153+
n == length(λ) || throw(ArgumentError("Eigenvalue count does not match matrix dimensions."))
154+
v = zeros(T, n)
155+
for i in 1:n-1
156+
# We must find an eigenvector with nonzero "first" entry. A failed UL factorization of A-λI reveals this vector provided L₁₁ == 0.
157+
for j in 1:length(λ)
158+
F = ul!(view(A, i:n, i:n) - λ[j]*I; check=false)
159+
nrm = norm(F.L)
160+
if norm(F.L[1]) rtol*nrm # v[i] is a free parameter; we set it to 1.
161+
fill!(v, zero(T))
162+
v[i] = one(T)
163+
# Next, we must scan the remaining free parameters, set them to 0, so that we find a nonsingular lower-triangular linear system for the nontrivial remaining part of the eigenvector.
164+
idx = Int[]
165+
for k in 2:n+1-i
166+
if norm(F.L[k, k]) > rtol*nrm
167+
push!(idx, k)
168+
end
169+
end
170+
if !isempty(idx)
171+
v[idx.+(i-1)] .= -F.L[idx, 1]
172+
ldiv!(LowerTriangular(view(F.L, idx, idx)), view(v, idx.+(i-1)))
173+
end
174+
deleteat!(λ, j)
175+
break
176+
end
177+
end
178+
for k in 1:n
179+
for j in i+1:n
180+
A[k, i] += A[k, j]*v[j]
181+
end
182+
end
183+
for j in i:n
184+
for k in i+1:n
185+
A[k, j] -= A[i, j]*v[k]
186+
end
187+
end
188+
for k in i+1:n
189+
A[k, i] = v[k]
190+
end
191+
end
192+
return LULinv(A)
193+
end
194+
195+
function ldiv!(F::LULinv, B::AbstractVecOrMat)
196+
L = UnitLowerTriangular(F.factors)
197+
return lmul!(L, ldiv!(UpperTriangular(F.factors), ldiv!(L, B)))
198+
end
199+
200+
function rdiv!(B::AbstractVecOrMat, F::LULinv)
201+
L = UnitLowerTriangular(F.factors)
202+
return rdiv!(rdiv!(rmul!(B, L), UpperTriangular(F.factors)), L)
203+
end
204+
205+
det(F::LULinv) = det(UpperTriangular(F.factors))
206+
logabsdet(F::LULinv) = logabsdet(UpperTriangular(F.factors))

src/polar.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ function PolarDecomposition{T}(U::AbstractArray{T}, H::AbstractArray{T}, niter=1
6565
PolarDecomposition{T,typeof(U),typeof(H)}(U,H,niter,converged)
6666
end
6767

68-
Base.iterate(P::PolarDecomposition) = (P.U, Val(:U))
69-
Base.iterate(P::PolarDecomposition, ::Val{:U}) = (P.H, Val(:done))
70-
Base.iterate(P::PolarDecomposition, ::Val{:done}) = nothing
68+
iterate(P::PolarDecomposition) = (P.U, Val(:U))
69+
iterate(P::PolarDecomposition, ::Val{:U}) = (P.H, Val(:done))
70+
iterate(P::PolarDecomposition, ::Val{:done}) = nothing
7171

7272
module Polar
7373
using LinearAlgebra

src/ql.jl

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ function QL{T}(factors::AbstractMatrix, τ::AbstractVector) where {T}
4949
end
5050

5151
# iteration for destructuring into components
52-
Base.iterate(S::QL) = (S.Q, Val(:L))
53-
Base.iterate(S::QL, ::Val{:L}) = (S.L, Val(:done))
54-
Base.iterate(S::QL, ::Val{:done}) = nothing
52+
iterate(S::QL) = (S.Q, Val(:L))
53+
iterate(S::QL, ::Val{:L}) = (S.L, Val(:done))
54+
iterate(S::QL, ::Val{:done}) = nothing
5555

5656
function generic_qlfactUnblocked!(A::AbstractMatrix{T}) where {T}
5757
require_one_based_indexing(A)
@@ -211,7 +211,7 @@ function show(io::IO, mime::MIME{Symbol("text/plain")}, F::QL)
211211
show(io, mime, F.L)
212212
end
213213

214-
@inline function getL(F::QL, _)
214+
@inline function getL(F::QL, _)
215215
m, n = size(F)
216216
tril!(getfield(F, :factors)[end-min(m,n)+1:end, 1:n], max(n-m,0))
217217
end
@@ -233,7 +233,7 @@ function getproperty(F::QL, d::Symbol)
233233
end
234234
end
235235

236-
Base.propertynames(F::QL, private::Bool=false) =
236+
propertynames(F::QL, private::Bool=false) =
237237
(:L, :Q, (private ? fieldnames(typeof(F)) : ())...)
238238

239239

@@ -295,20 +295,20 @@ abstract type AbstractQLLayout <: MemoryLayout end
295295
"""
296296
QLPackedLayout{SLAY,TLAY}()
297297
298-
represents a Packed QL factorization whose
298+
represents a Packed QL factorization whose
299299
factors are stored with layout SLAY and τ stored with layout TLAY
300300
"""
301301
struct QLPackedLayout{SLAY,TLAY} <: AbstractQLLayout end
302302
struct QLPackedQLayout{SLAY,TLAY} <: AbstractQLayout end
303303
struct AdjQLPackedQLayout{SLAY,TLAY} <: AbstractQLayout end
304304

305-
MemoryLayout(::Type{<:QL{<:Any,Mat,Tau}}) where {Mat,Tau} =
305+
MemoryLayout(::Type{<:QL{<:Any,Mat,Tau}}) where {Mat,Tau} =
306306
QLPackedLayout{typeof(MemoryLayout(Mat)),typeof(MemoryLayout(Tau))}()
307307

308308

309309
adjointlayout(::Type, ::QLPackedQLayout{SLAY,TLAY}) where {SLAY,TLAY} = AdjQLPackedQLayout{SLAY,TLAY}()
310310

311-
MemoryLayout(::Type{<:QLPackedQ{<:Any,Mat,Tau}}) where {Mat,Tau} =
311+
MemoryLayout(::Type{<:QLPackedQ{<:Any,Mat,Tau}}) where {Mat,Tau} =
312312
QLPackedQLayout{typeof(MemoryLayout(Mat)),typeof(MemoryLayout(Tau))}()
313313

314314
colsupport(::QLPackedQLayout, Q, j) = minimum(colsupport(Q.factors, j)):size(Q,1)
@@ -378,7 +378,7 @@ end
378378

379379

380380
### QBc/QcBc
381-
function materialize!(M::Rmul{<:Any,<:QLPackedQLayout})
381+
function materialize!(M::Rmul{<:Any,<:QLPackedQLayout})
382382
A,Q = M.A, M.B
383383
mQ, nQ = size(Q.factors)
384384
mA, nA = size(A,1), size(A,2)
@@ -406,8 +406,8 @@ function materialize!(M::Rmul{<:Any,<:QLPackedQLayout})
406406
end
407407

408408
### AQc
409-
function materialize!(M::Rmul{<:Any,<:AdjQLPackedQLayout})
410-
A,adjQ = M.A, M.B
409+
function materialize!(M::Rmul{<:Any,<:AdjQLPackedQLayout})
410+
A,adjQ = M.A, M.B
411411
Q = parent(adjQ)
412412
mQ, nQ = size(Q.factors)
413413
mA, nA = size(A,1), size(A,2)
@@ -525,5 +525,3 @@ function (\)(A::QL{T}, BIn::VecOrMat{Complex{T}}) where T<:BlasReal
525525
XX = reshape(collect(reinterpret(Complex{T}, copy(transpose(reshape(X, div(length(X), 2), 2))))), _ret_size(A, BIn))
526526
return _cut_B(XX, 1:n)
527527
end
528-
529-

src/qr.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ QR{T}(factors::AbstractMatrix, τ::AbstractVector) where {T} =
5252
QR(F::LinearAlgebra.QR) = QR(F.factors, F.τ)
5353

5454
# iteration for destructuring into components
55-
Base.iterate(S::QR) = (S.Q, Val(:R))
56-
Base.iterate(S::QR, ::Val{:R}) = (S.R, Val(:done))
57-
Base.iterate(S::QR, ::Val{:done}) = nothing
55+
iterate(S::QR) = (S.Q, Val(:R))
56+
iterate(S::QR, ::Val{:R}) = (S.R, Val(:done))
57+
iterate(S::QR, ::Val{:done}) = nothing
5858

5959

6060
function _qrfactUnblocked!(_, _, A::AbstractMatrix{T}, τ::AbstractVector) where {T}
@@ -138,7 +138,7 @@ function getproperty(F::QR, d::Symbol)
138138
end
139139
end
140140

141-
Base.propertynames(F::QR, private::Bool=false) =
141+
propertynames(F::QR, private::Bool=false) =
142142
(:R, :Q, (private ? fieldnames(typeof(F)) : ())...)
143143

144144
ldiv!(F::QR, B::AbstractVecOrMat) = ArrayLayouts.ldiv!(F, B)

0 commit comments

Comments
 (0)