Skip to content

Commit 038c020

Browse files
authored
Implement reverse Cholesky for symmetric Tridiagonal matrices (#179)
* Implement reverse Cholesky for symmetric Tridiagonal matrices * Avoid infinite loop if bN is somehow zero (if it can be?) * Address review * Fix tests
1 parent 344da69 commit 038c020

File tree

4 files changed

+195
-9
lines changed

4 files changed

+195
-9
lines changed

src/InfiniteLinearAlgebra.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ include("banded/infbanded.jl")
138138
include("blockbanded/blockbanded.jl")
139139
include("banded/infqltoeplitz.jl")
140140
include("banded/infreversecholeskytoeplitz.jl")
141+
include("banded/infreversecholeskytridiagonal.jl")
141142
include("infql.jl")
142143
include("infqr.jl")
143144
include("inful.jl")
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
const MAX_TRIDIAG_CHOL_N = 2^21 - 1 # Maximum allowable size of Cholesky factor before terminating to prevent OutOfMemory errors without convergence
2+
mutable struct LazySymTridiagonalReverseCholeskyFactors{T,M1,M2} <: LazyMatrix{T}
3+
const A::M1 # original matrix
4+
const L::M2 # LN
5+
const ε::T # adaptive tolerance
6+
N::Int # size of L used for approximating Ln
7+
n::Int # size of approximated finite section
8+
end # this should behave like a lower Bidiagonal matrix
9+
function LazySymTridiagonalReverseCholeskyFactors(A, N, n, L, ε)
10+
require_one_based_indexing(A)
11+
M1, M2 = typeof(A), typeof(L)
12+
T = eltype(L)
13+
return LazySymTridiagonalReverseCholeskyFactors{T,M1,M2}(A, L, convert(T, ε), N, n)
14+
end
15+
16+
function getproperty(C::ReverseCholesky{<:Any,<:LazySymTridiagonalReverseCholeskyFactors}, d::Symbol) # mimic getproperty(::ReverseCholesky{<:Any, <:Bidiagonal}, ::Symbol)
17+
Cfactors = getfield(C, :factors)
18+
#Cuplo = 'L'
19+
if d == :U
20+
return Cfactors'
21+
elseif d == :L || d == :UL
22+
return Cfactors
23+
else
24+
return getfield(C, d)
25+
end
26+
end
27+
MemoryLayout(::Type{<:LazySymTridiagonalReverseCholeskyFactors}) = BidiagonalLayout{LazyLayout,LazyLayout}()
28+
29+
size(L::LazySymTridiagonalReverseCholeskyFactors) = size(L.A)
30+
axes(L::LazySymTridiagonalReverseCholeskyFactors) = axes(L.A)
31+
Base.eltype(L::Type{LazySymTridiagonalReverseCholeskyFactors}) = eltype(L.L)
32+
33+
copy(L::LazySymTridiagonalReverseCholeskyFactors) = LazySymTridiagonalReverseCholeskyFactors(copy(L.A), copy(L.L), L.ε, L.N, L.n)
34+
copy(U::Adjoint{T,<:LazySymTridiagonalReverseCholeskyFactors}) where {T} = copy(parent(U))'
35+
36+
LazyBandedMatrices.bidiagonaluplo(L::LazySymTridiagonalReverseCholeskyFactors) = 'L'
37+
38+
"""
39+
InfiniteBoundsAccessError <: Exception
40+
41+
Struct for defining an error when accessing a `LazySymTridiagonalReverseCholeskyFactors` object outside of the
42+
maximum allowable finite section of size `$MAX_TRIDIAG_CHOL_N × $MAX_TRIDIAG_CHOL_N`.
43+
"""
44+
struct InfiniteBoundsAccessError <: Exception
45+
i::Int
46+
j::Int
47+
end
48+
function Base.showerror(io::IO, err::InfiniteBoundsAccessError)
49+
print(io, "InfiniteBoundsAccessError: Tried to index reverse Cholesky factory at index (", err.i, ", ", err.j)
50+
print(io, "), outside of the maximum allowable finite section of size (", MAX_TRIDIAG_CHOL_N, " × ", MAX_TRIDIAG_CHOL_N, ")")
51+
end
52+
53+
function getindex(L::LazySymTridiagonalReverseCholeskyFactors, i::Int, j::Int)
54+
max(i, j) > MAX_TRIDIAG_CHOL_N && throw(InfiniteBoundsAccessError(i, j))
55+
T = eltype(L)
56+
if j > i
57+
return zero(T)
58+
elseif max(i, j) > L.n
59+
_expand_factor!(L, max(i, j))
60+
return L.L[i, j]
61+
else
62+
return L.L[i, j]
63+
end
64+
end
65+
66+
function reversecholesky_layout(::SymTridiagonalLayout, ::NTuple{2,OneToInf{Int}}, A, ::NoPivot; kwds...)
67+
a, b = A.dv, A.ev
68+
T = promote_type(eltype(a), eltype(b), eltype(b[1] / a[1])) # could also use promote_op(/, eltype(a), eltype(b)), but promote_op is fragile apparently
69+
tol = eps(real(T)) # no good way to pass this as a keyword currently, so just hardcode it
70+
L = Bidiagonal([zero(T)], T[], :L)
71+
chol = LazySymTridiagonalReverseCholeskyFactors(A, 1, 1, L, tol)
72+
_expand_factor!(chol, 2^4) # initialise with 2^4
73+
return ReverseCholesky(chol, 'L', 0)
74+
end
75+
76+
function _expand_factor!(L::LazySymTridiagonalReverseCholeskyFactors, n)
77+
L.n n && return L
78+
return __expand_factor!(L::LazySymTridiagonalReverseCholeskyFactors, n)
79+
end
80+
81+
function compute_ξ(LL::LazySymTridiagonalReverseCholeskyFactors)
82+
#=
83+
We can show that ||LN' Pn inv(LN') Vb|| = |bN| ||ξ||, where
84+
ξₙ = LN[n, n]νₙ,
85+
ξᵢ = LN[i, i]νᵢ + LN[i+1, i]νᵢ₊₁, i = 1, 2, …, n-1, where
86+
νN = 1/LN[N, N]
87+
νᵢ = -(LN[i+1, i] / LN[i, i]) νᵢ₊₁, i = 1, 2, …, N-1.
88+
=#
89+
L, N, n = LL.L, LL.N, LL.n
90+
ν = inv(L[N, N])
91+
for i in (N-1):-1:n
92+
ν *= -(L[i+1, i] / L[i, i])
93+
end
94+
ξ = (L[n, n] * ν)^2
95+
for i in (n-1):-1:1
96+
ξ′ = L[i+1, i] * ν
97+
ν *= -(L[i+1, i] / L[i, i])
98+
ξ′ += L[i, i] * ν
99+
ξ += ξ′^2
100+
end
101+
bN = LL.A[N, N+1]
102+
scale = iszero(bN) ? one(bN) : abs(bN)
103+
return scale * sqrt(ξ) # could maybe just return sqrt(ξ), but maybe bN helps for scaling?
104+
end
105+
106+
function has_converged(LL::LazySymTridiagonalReverseCholeskyFactors)
107+
ξ = compute_ξ(LL)
108+
return ξ LL.ε
109+
end
110+
111+
function _resize_factor!(L::LazySymTridiagonalReverseCholeskyFactors, N=2L.N)
112+
L.N = N
113+
resize!(L.L.dv, L.N)
114+
resize!(L.L.ev, L.N - 1)
115+
return L
116+
end
117+
118+
function _finite_revchol!(L::LazySymTridiagonalReverseCholeskyFactors)
119+
# Computes the reverse Cholesky factorisation of L.A[1:L.N, 1:L.N]
120+
N = L.N
121+
a, b = L.A.dv, L.A.ev
122+
ℓa, ℓb = L.L.dv, L.L.ev
123+
ℓa[N] = sqrt(a[N])
124+
for i in (N-1):-1:1
125+
ℓb[i] = b[i] / ℓa[i+1]
126+
ℓa[i] = sqrt(a[i] - ℓb[i]^2)
127+
end
128+
return L
129+
end
130+
131+
function __expand_factor!(L::LazySymTridiagonalReverseCholeskyFactors, n)
132+
L.N > MAX_TRIDIAG_CHOL_N && return L
133+
L.n = n
134+
L.N < L.n && _resize_factor!(L, 2n)
135+
while !has_converged(L) && L.N MAX_TRIDIAG_CHOL_N
136+
_resize_factor!(L)
137+
_finite_revchol!(L)
138+
end
139+
!has_converged(L) && L.N > MAX_TRIDIAG_CHOL_N && @warn "Reverse Cholesky algorithm failed to converge. Returned results may not be accurate." maxlog = 1
140+
return L
141+
end

test/test_infbanded.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,13 @@ using Base: oneto
6363
T = LazyBandedMatrices.Tridiagonal(Fill(1,∞), Zeros(∞), Fill(3,∞))
6464
@test T[2:∞,3:∞] isa SubArray
6565
@test exp.(T) isa BroadcastMatrix
66-
@test exp.(T)[2:∞,3:∞] isa SubArray
66+
@test exp.(T)[2:∞,3:∞] isa BroadcastMatrix
6767
@test exp.(T[2:∞,3:∞]) isa BroadcastMatrix
6868

6969
B = LazyBandedMatrices.Bidiagonal(Fill(1,∞), Zeros(∞), :U)
7070
@test B[2:∞,3:∞] isa SubArray
7171
@test exp.(B) isa BroadcastMatrix
72-
@test exp.(B)[2:∞,3:∞] isa SubArray
72+
@test exp.(B)[2:∞,3:∞] isa BroadcastMatrix
7373

7474
@testset "algebra" begin
7575
T = Tridiagonal(Fill(1,∞), Fill(2,∞), Fill(3,∞))

test/test_infreversecholesky.jl

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,60 @@
1-
using InfiniteLinearAlgebra, MatrixFactorizations, ArrayLayouts, LinearAlgebra, Test
1+
using InfiniteLinearAlgebra, LazyBandedMatrices, FillArrays, MatrixFactorizations, ArrayLayouts, LinearAlgebra, Test, LazyArrays
22

3-
4-
5-
@testset "infreversecholesky" begin
3+
@testset "infreversecholeskytoeplitz" begin
64
@testset "Tri Toeplitz" begin
75
A = SymTridiagonal(Fill(3, ∞), Fill(1, ∞))
86
U, = reversecholesky(A)
9-
@test (U*U')[1:10,1:10] A[1:10,1:10]
7+
@test (U*U')[1:10, 1:10] A[1:10, 1:10]
108
end
119

1210
@testset "Pert Tri Toeplitz" begin
13-
A = SymTridiagonal([[4,5, 6]; Fill(3, ∞)], [[2,3]; Fill(1, ∞)])
14-
@test reversecholesky(A).U[1:100,1:100] reversecholesky(A[1:1000,1:1000]).U[1:100,1:100]
11+
A = SymTridiagonal([[4, 5, 6]; Fill(3, ∞)], [[2, 3]; Fill(1, ∞)])
12+
@test reversecholesky(A).U[1:100, 1:100] reversecholesky(A[1:1000, 1:1000]).U[1:100, 1:100]
13+
end
14+
end
15+
16+
@testset "infreversecholeskytridiagonal" begin
17+
local LL, L
18+
@testset "Test on Toeplitz example first" begin
19+
A = SymTridiagonal(Fill(3, ∞), Fill(1, ∞))
20+
L = reversecholesky(A)
21+
LL = InfiniteLinearAlgebra.reversecholesky_layout(SymTridiagonalLayout{LazyArrays.LazyLayout,LazyArrays.LazyLayout}(), axes(A), A, NoPivot())
22+
@test L.L[1:1000, 1:1000] LL.L[1:1000, 1:1000]
23+
@test (LL.L'*LL.L)[1:1000, 1:1000] == (LL.U*LL.L)[1:1000, 1:1000] A[1:1000, 1:1000]
24+
end
25+
26+
@testset "Basic tests" begin
27+
L = LL
28+
@test MemoryLayout(L.L) isa BidiagonalLayout
29+
@test L.U === L.L'
30+
@test L.uplo == 'L'
31+
@test L.info == 0
32+
@test size(L) == (∞, ∞)
33+
@test axes(L) == (1:∞, 1:∞)
34+
@test eltype(L) == Float64
35+
Lc = copy(L)
36+
@test !(Lc === L)
37+
@test !(Lc.U === L.U)
38+
@test Lc.L[1:1000, 1:1000] == L.L[1:1000, 1:1000]
39+
UUc = copy(L.L')
40+
@test !(UUc === L.U)
41+
@test UUc[1:1000, 1:1000] == L.U[1:1000, 1:1000]
42+
end
43+
44+
@testset "Errors" begin
45+
err = InfiniteLinearAlgebra.InfiniteBoundsAccessError(4, 6)
46+
@test_throws InfiniteLinearAlgebra.InfiniteBoundsAccessError throw(err)
47+
@test_throws InfiniteLinearAlgebra.InfiniteBoundsAccessError L.L[1, InfiniteLinearAlgebra.MAX_TRIDIAG_CHOL_N+1]
48+
@test_throws InfiniteLinearAlgebra.InfiniteBoundsAccessError L.L[InfiniteLinearAlgebra.MAX_TRIDIAG_CHOL_N+1, 1]
49+
@test_throws InfiniteLinearAlgebra.InfiniteBoundsAccessError L.L[InfiniteLinearAlgebra.MAX_TRIDIAG_CHOL_N+1, InfiniteLinearAlgebra.MAX_TRIDIAG_CHOL_N+1]
50+
end
51+
52+
@testset "Another example" begin
53+
A = LazyBandedMatrices.SymTridiagonal(Ones(∞), 1 ./ (2:∞))
54+
L = reversecholesky(A)
55+
@test (L.U*L.L)[1:1000, 1:1000] A[1:1000, 1:1000]
56+
A = 5I + LazyBandedMatrices.SymTridiagonal(1 ./ (2:∞), Ones(∞))
57+
L = reversecholesky(A)
58+
@test (L.U*L.L)[1:1000, 1:1000] A[1:1000, 1:1000]
1559
end
1660
end

0 commit comments

Comments
 (0)