Skip to content

Commit 6e56195

Browse files
Implement BidiagonalConjugationData (#186)
* Implement BidiagonalConjugationData * Redesign * Fix import and make infiniterandomarrays an extra * typo * Tests * Redesign * Function barriers * BidiagonalConjugationBand should not be <: AbstractCachedVector * oops * Changes * Make copy a no-op * Cleanup * Fix resize --------- Co-authored-by: Sheehan Olver <[email protected]>
1 parent 3e81f50 commit 6e56195

File tree

5 files changed

+243
-8
lines changed

5 files changed

+243
-8
lines changed

Project.toml

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "InfiniteLinearAlgebra"
22
uuid = "cde9dba0-b1de-11e9-2c62-0bab9446c55c"
3-
version = "0.8.3"
3+
version = "0.8.4"
44

55
[deps]
66
ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a"
@@ -23,8 +23,9 @@ BandedMatrices = "1.7.2"
2323
BlockArrays = "1.0"
2424
BlockBandedMatrices = "0.13"
2525
FillArrays = "1.0"
26-
Infinities = "0.1"
2726
InfiniteArrays = "0.14"
27+
InfiniteRandomArrays = "0.2"
28+
Infinities = "0.1"
2829
LazyArrays = "2.0"
2930
LazyBandedMatrices = "0.10"
3031
LinearAlgebra = "1"
@@ -39,9 +40,10 @@ julia = "1.10"
3940
[extras]
4041
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
4142
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
43+
InfiniteRandomArrays = "2bc77966-89c7-476d-a40f-269028fac4a9"
4244
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
4345
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
4446
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
4547

4648
[targets]
47-
test = ["Aqua", "Test", "Random", "SpecialFunctions", "StaticArrays"]
49+
test = ["Aqua", "Test", "Random", "InfiniteRandomArrays", "SpecialFunctions", "StaticArrays"]

src/InfiniteLinearAlgebra.jl

+4-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import ArrayLayouts: AbstractBandedLayout, AbstractQLayout, AdjQRPackedQLayout,
2121

2222
import BandedMatrices: BandedColumns, BandedMatrix, BandedMatrix, _BandedMatrix, AbstractBandedMatrix,
2323
_BandedMatrix, _BandedMatrix, _banded_qr, _banded_qr!, _default_banded_broadcast, banded_chol!,
24-
banded_similar, bandedcolumns, bandeddata, bandwidths, bandwidths
24+
banded_similar, bandedcolumns, bandeddata, bandwidths
2525

2626
import BlockArrays: AbstractBlockLayout, BlockLayout, BlockSlice, BlockSlice1, BlockedOneTo,
2727
blockcolsupport, sizes_from_blocks, OneToCumsum, AbstractBlockedUnitRange
@@ -38,9 +38,9 @@ import Infinities: InfiniteCardinal, Infinity
3838

3939
import LazyArrays: AbstractCachedMatrix, AbstractCachedVector, AbstractLazyLayout, ApplyArray, ApplyLayout, ApplyMatrix,
4040
CachedArray, CachedLayout, CachedMatrix, CachedVector, LazyArrayStyle, LazyLayout,
41-
LazyLayouts, LazyMatrix, AbstractPaddedLayout, PaddedColumns, _broadcast_sub_arguments,
41+
LazyLayouts, LazyMatrix, LazyVector, AbstractPaddedLayout, PaddedColumns, _broadcast_sub_arguments,
4242
applybroadcaststyle, applylayout, arguments, cacheddata, paddeddata, resizedata!, simplifiable,
43-
simplify, islazy, islazy_layout
43+
simplify, islazy, islazy_layout, cache_getindex
4444

4545
import LazyBandedMatrices: AbstractLazyBandedBlockBandedLayout, AbstractLazyBandedLayout, ApplyBandedLayout, BlockVec,
4646
BroadcastBandedLayout, KronTravBandedBlockBandedLayout, LazyBandedLayout,
@@ -143,5 +143,6 @@ include("infql.jl")
143143
include("infqr.jl")
144144
include("inful.jl")
145145
include("infcholesky.jl")
146+
include("banded/bidiagonalconjugation.jl")
146147

147148
end # module

src/banded/bidiagonalconjugation.jl

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
@inline function _to_uplo(char::Symbol)
2+
if char == :U
3+
'U'
4+
elseif char == :L
5+
'L'
6+
else
7+
_throw_uplo()
8+
end
9+
end
10+
@inline function _to_uplo(char::Char)
11+
if char ('L', 'U')
12+
char
13+
else
14+
_throw_uplo()
15+
end
16+
end
17+
@noinline _throw_uplo() = throw(ArgumentError("uplo argument must be either :U (upper) or :L (lower)"))
18+
19+
mutable struct BidiagonalConjugationData{T}
20+
const U::AbstractMatrix{T} # Typing these concretely prevents the use of Bidiagonal, unless we want LazyBandedMatrices.Bidiagonal
21+
const C::AbstractMatrix{T} # Function barriers help to minimise the penalty from this when resizing anyway.
22+
const dv::Vector{T}
23+
const ev::Vector{T}
24+
const uplo::Char
25+
datasize::Int # Number of columns
26+
end
27+
function BidiagonalConjugationData(U, X, V, uplo::Char)
28+
C = X * V
29+
T = promote_type(typeof(inv(U[1, 1])), eltype(U), eltype(C)) # include inv so that we can't get Ints
30+
dv, ev = T[], T[]
31+
return BidiagonalConjugationData(U, C, dv, ev, uplo, 0)
32+
end
33+
34+
function copy(data::BidiagonalConjugationData)
35+
U, C, dv, ev, uplo, datasize = data.U, data.C, data.dv, data.ev, data.uplo, data.datasize
36+
return BidiagonalConjugationData(copy(U), copy(C), copy(dv), copy(ev), uplo, datasize)
37+
end
38+
39+
function _compute_column_up!(data::BidiagonalConjugationData, U, C, i)
40+
dv, ev = data.dv, data.ev
41+
if i == 1
42+
dv[i] = C[1, 1] / U[1, 1]
43+
else
44+
uᵢ₋₁ᵢ₋₁, uᵢᵢ₋₁, uᵢ₋₁ᵢ, uᵢᵢ = U[i-1, i-1], U[i, i-1], U[i-1, i], U[i, i]
45+
cᵢ₋₁ᵢ, cᵢᵢ = C[i-1, i], C[i, i]
46+
Uᵢ⁻¹ = inv(uᵢ₋₁ᵢ₋₁ * uᵢᵢ - uᵢ₋₁ᵢ * uᵢᵢ₋₁)
47+
dv[i] = Uᵢ⁻¹ * (uᵢ₋₁ᵢ₋₁ * cᵢᵢ - uᵢᵢ₋₁ * cᵢ₋₁ᵢ)
48+
ev[i-1] = Uᵢ⁻¹ * (uᵢᵢ * cᵢ₋₁ᵢ - uᵢ₋₁ᵢ * cᵢᵢ)
49+
end
50+
return data
51+
end
52+
53+
function _compute_column_lo!(data::BidiagonalConjugationData, U, C, i)
54+
dv, ev = data.dv, data.ev
55+
uᵢᵢ, uᵢ₊₁ᵢ, uᵢᵢ₊₁, uᵢ₊₁ᵢ₊₁ = U[i, i], U[i+1, i], U[i, i+1], U[i+1, i+1]
56+
cᵢᵢ, cᵢ₊₁ᵢ = C[i, i], C[i+1, i]
57+
Uᵢ⁻¹ = inv(uᵢᵢ * uᵢ₊₁ᵢ₊₁ - uᵢᵢ₊₁ * uᵢ₊₁ᵢ)
58+
dv[i] = Uᵢ⁻¹ * (uᵢ₊₁ᵢ₊₁ * cᵢᵢ - uᵢᵢ₊₁ * cᵢ₊₁ᵢ)
59+
ev[i] = Uᵢ⁻¹ * (uᵢᵢ * cᵢ₊₁ᵢ - uᵢ₊₁ᵢ * cᵢᵢ)
60+
return data
61+
end
62+
63+
function _compute_columns!(data::BidiagonalConjugationData, i)
64+
U, C = data.U, data.C # Treat _compute_column_(up/lo) as function barriers and take these out early
65+
return __compute_columns!(data, U, C, i)
66+
end
67+
function __compute_columns!(data::BidiagonalConjugationData, U, C, i)
68+
ds = data.datasize
69+
up = data.uplo == 'U'
70+
for j in (ds+1):i
71+
up ? _compute_column_up!(data, U, C, j) : _compute_column_lo!(data, U, C, j)
72+
end
73+
data.datasize = i
74+
return data
75+
end
76+
77+
function resizedata!(data::BidiagonalConjugationData, n)
78+
n 0 && return data
79+
v = data.datasize
80+
n = max(v, n)
81+
dv, ev = data.dv, data.ev
82+
if n > length(ev) # Avoid O(n²) growing. Note min(length(dv), length(ev)) == length(ev)
83+
resize!(dv, 2n + 1)
84+
resize!(ev, 2n)
85+
end
86+
n > v && _compute_columns!(data, n)
87+
return data
88+
end
89+
90+
struct BidiagonalConjugationBand{T} <: LazyVector{T}
91+
data::BidiagonalConjugationData{T}
92+
diag::Bool # true => diagonal, false => offdiagonal
93+
end
94+
@inline size(::BidiagonalConjugationBand) = (ℵ₀,)
95+
@inline resizedata!(A::BidiagonalConjugationBand, n) = resizedata!(A.data, n)
96+
97+
function _bcb_getindex(band::BidiagonalConjugationBand, I)
98+
resizedata!(band, maximum(I) + 1)
99+
if band.diag
100+
return band.data.dv[I]
101+
else
102+
return band.data.ev[I]
103+
end
104+
end
105+
106+
@inline getindex(band::BidiagonalConjugationBand, I::Integer) = _bcb_getindex(band, I)
107+
@inline getindex(band::BidiagonalConjugationBand, I::AbstractVector) = _bcb_getindex(band, I)
108+
109+
copy(band::BidiagonalConjugationBand) = band
110+
111+
const BidiagonalConjugation{T} = Bidiagonal{T,BidiagonalConjugationBand{T}}
112+
113+
"""
114+
BidiagonalConjugation(U, X, V, uplo)
115+
116+
Efficiently compute the projection of the matrix product
117+
`inv(U)XV` onto a bidiagonal matrix. The `uplo` argument
118+
specifies whether the projection is upper (`uplo = 'U'`)
119+
or lower (`uplo = 'L'`) bidiagonal.
120+
121+
The computation is returned as a `Bidiagonal` matrix whose
122+
diagonal and off-diagonal vectors are computed lazily.
123+
"""
124+
function BidiagonalConjugation(U, X, V, uplo)
125+
_uplo = _to_uplo(uplo)
126+
data = BidiagonalConjugationData(U, X, V, _uplo)
127+
return _BidiagonalConjugation(data, _uplo)
128+
end
129+
130+
function _BidiagonalConjugation(data, uplo) # need uplo argument so that we can take transposes
131+
dv = BidiagonalConjugationBand(data, true)
132+
ev = BidiagonalConjugationBand(data, false)
133+
return Bidiagonal(dv, ev, uplo)
134+
end
135+
136+
copy(A::BidiagonalConjugation) = A # no-op
137+
138+
LazyBandedMatrices.Bidiagonal(A::BidiagonalConjugation) = LazyBandedMatrices.Bidiagonal(A.dv, A.ev, A.uplo)

test/runtests.jl

+6-2
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@ using InfiniteLinearAlgebra, BlockBandedMatrices, BlockArrays, BandedMatrices, I
33
import InfiniteLinearAlgebra: qltail, toeptail, tailiterate, tailiterate!, tail_de, ql_X!,
44
InfToeplitz, PertToeplitz, TriToeplitz, InfBandedMatrix, InfBandCartesianIndices,
55
rightasymptotics, QLHessenberg, ConstRows, PertConstRows, chop, chop!, pad,
6-
BandedToeplitzLayout, PertToeplitzLayout, TridiagonalToeplitzLayout, BidiagonalToeplitzLayout
6+
BandedToeplitzLayout, PertToeplitzLayout, TridiagonalToeplitzLayout, BidiagonalToeplitzLayout,
7+
BidiagonalConjugation
78
import Base: BroadcastStyle, oneto
89
import BlockArrays: _BlockArray, blockcolsupport
910
import BlockBandedMatrices: isblockbanded, _BlockBandedMatrix
1011
import MatrixFactorizations: QLPackedQ
1112
import BandedMatrices: bandeddata, _BandedMatrix, BandedStyle
12-
import LazyArrays: colsupport, MemoryLayout, ApplyLayout, LazyArrayStyle, arguments, paddeddata, PaddedColumns
13+
import LazyArrays: colsupport, MemoryLayout, ApplyLayout, LazyArrayStyle, arguments, paddeddata, PaddedColumns, LazyLayout
1314
import InfiniteArrays: OneToInf, oneto, RealInfinity
1415
import LazyBandedMatrices: BroadcastBandedBlockBandedLayout, BroadcastBandedLayout, LazyBandedLayout, BlockVec
16+
import InfiniteRandomArrays: InfRandTridiagonal, InfRandBidiagonal
17+
import ArrayLayouts: diagonaldata, supdiagonaldata, subdiagonaldata
1518

1619
using Aqua
1720
@testset "Project quality" begin
@@ -476,3 +479,4 @@ include("test_inful.jl")
476479
include("test_infcholesky.jl")
477480
include("test_periodic.jl")
478481
include("test_infreversecholesky.jl")
482+
include("test_bidiagonalconjugation.jl")

test/test_bidiagonalconjugation.jl

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using InfiniteLinearAlgebra, InfiniteRandomArrays, BandedMatrices, LazyArrays, LazyBandedMatrices, InfiniteArrays, ArrayLayouts, Test
2+
using InfiniteLinearAlgebra: BidiagonalConjugation, OneToInf
3+
using ArrayLayouts: supdiagonaldata, subdiagonaldata, diagonaldata
4+
using LinearAlgebra
5+
using LazyArrays: LazyLayout
6+
7+
@testset "BidiagonalConjugationData" begin
8+
@test InfiniteLinearAlgebra._to_uplo('U') == 'U'
9+
@test InfiniteLinearAlgebra._to_uplo('L') == 'L'
10+
@test_throws ArgumentError InfiniteLinearAlgebra._to_uplo('a')
11+
@test InfiniteLinearAlgebra._to_uplo(:U) == 'U'
12+
@test InfiniteLinearAlgebra._to_uplo(:L) == 'L'
13+
@test_throws ArgumentError InfiniteLinearAlgebra._to_uplo(:a)
14+
15+
for _ in 1:3
16+
V1 = InfRandTridiagonal()
17+
A1 = InfRandBidiagonal('U')
18+
X1 = brand(∞, 0, 2)
19+
U1 = X1 * V1 * ApplyArray(inv, A1)
20+
B1 = BidiagonalConjugation(U1, X1, V1, 'U');
21+
22+
V2 = brand(∞, 0, 1)
23+
A2 = LazyBandedMatrices.Bidiagonal(Fill(0.2, ∞), 2.0 ./ (1.0 .+ (1:∞)), 'L') # LinearAlgebra.Bidiagonal not playing nice for this case
24+
X2 = InfRandBidiagonal('L')
25+
U2 = X2 * V2 * ApplyArray(inv, A2)
26+
B2 = BidiagonalConjugation(U2, X2, V2, :L);
27+
28+
for (A, B, uplo) in ((A1, B1, 'U'), (A2, B2, 'L'))
29+
@test B.dv.data === B.ev.data
30+
@test MemoryLayout(B) isa BidiagonalLayout{LazyLayout,LazyLayout}
31+
@test diagonaldata(B) === B.dv
32+
if uplo == 'U'
33+
@test supdiagonaldata(B) === B.ev
34+
@test_throws ArgumentError subdiagonaldata(B)
35+
@test bandwidths(B) == (0, 1)
36+
else
37+
@test subdiagonaldata(B) === B.ev
38+
@test_throws ArgumentError supdiagonaldata(B)
39+
@test bandwidths(B) == (1, 0)
40+
end
41+
@test size(B) == (ℵ₀, ℵ₀)
42+
@test axes(B) == (OneToInf(), OneToInf())
43+
@test eltype(B) == Float64
44+
for _B in (B, B')
45+
BB = copy(_B)
46+
@test BB.dv.data === BB.ev.data
47+
@test parent(BB).dv.data.datasize == parent(_B).dv.data.datasize
48+
# @test !(BB === B) && !(parent(BB).dv.data === parent(B).dv.data) # copy is a no-op
49+
@test BB[1:100, 1:100] == _B[1:100, 1:100]
50+
@test BB[1:2:50, 1:3:40] == _B[1:2:50, 1:3:40]
51+
@test view(BB, [1, 3, 7, 10], 1:10) == _B[[1, 3, 7, 10], 1:10]
52+
end
53+
@test LazyBandedMatrices.bidiagonaluplo(B) == uplo
54+
@test LazyBandedMatrices.Bidiagonal(B) === LazyBandedMatrices.Bidiagonal(B.dv, B.ev, Symbol(uplo))
55+
@test B[1:10, 1:10] A[1:10, 1:10]
56+
@test B[230, 230] A[230, 230]
57+
@test B[102, 102] A[102, 102] # make sure we compute intermediate columns correctly when skipping
58+
@test B[band(0)][1:100] == B.dv[1:100]
59+
if uplo == 'U'
60+
@test B[band(1)][1:100] == B.ev[1:100]
61+
# @test B[band(-1)][1:100] == zeros(100) # This test requires that we define a
62+
# convert(::Type{BidiagonalConjugationBand{T}}, ::Zeros{V, 1, Tuple{OneToInf{Int}}}) where {T, V} method,
63+
# which we probably don't need beyond this test
64+
else
65+
@test B[band(-1)][1:100] == B.ev[1:100]
66+
# @test B[band(1)][1:100] == zeros(100)
67+
end
68+
@test B.dv[500] == B.dv.data.dv[500]
69+
@test B.dv.data.datasize == 501
70+
@test B.ev[1005] == B.ev.data.ev[1005]
71+
@test B.ev.data.datasize == 1006
72+
@test ApplyArray(inv, B)[1:100, 1:100] ApplyArray(inv, A)[1:100, 1:100] # need to somehow let inv (or even ApplyArray(inv, )) work
73+
@test (B+B)[1:100, 1:100] 2A[1:100, 1:100] 2B[1:100, 1:100]
74+
@test (B*I)[1:100, 1:100] B[1:100, 1:100]
75+
# @test (B*Diagonal(1:∞))[1:100, 1:100] ≈ B[1:100, 1:100] * Diagonal(1:100) # Uncomment once https://github.com/JuliaLinearAlgebra/ArrayLayouts.jl/pull/241 is registered
76+
77+
# Pointwise tests
78+
for i in 1:10
79+
for j in 1:10
80+
@test B[i, j] A[i, j]
81+
@test B'[i, j] A[j, i]
82+
end
83+
end
84+
@inferred B[5, 5]
85+
86+
# Make sure that, when indexing the transpose, B expands correctly
87+
@test B'[3000:3005, 2993:3006] A[2993:3006, 3000:3005]'
88+
end
89+
end
90+
end

0 commit comments

Comments
 (0)