Skip to content

Create blockcholesky.jl #126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Jun 16, 2022
Merged

Create blockcholesky.jl #126

merged 21 commits into from
Jun 16, 2022

Conversation

felixw98
Copy link
Contributor

No description provided.

@codecov
Copy link

codecov bot commented Jul 14, 2020

Codecov Report

Merging #126 (99f54d5) into master (0551a95) will increase coverage by 0.31%.
The diff coverage is 100.00%.

@@            Coverage Diff             @@
##           master     #126      +/-   ##
==========================================
+ Coverage   92.29%   92.61%   +0.31%     
==========================================
  Files          14       15       +1     
  Lines        1376     1435      +59     
==========================================
+ Hits         1270     1329      +59     
  Misses        106      106              
Impacted Files Coverage Δ
src/blockcholesky.jl 100.00% <100.00%> (ø)
src/blocklinalg.jl 96.53% <100.00%> (ø)
src/blockproduct.jl 93.10% <100.00%> (+0.51%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0551a95...99f54d5. Read the comment docs.

Copy link
Member

@dlfivefifty dlfivefifty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add unit tests by creating a file test/test_blockcholesky.jl

# Function for cholesky decomposition on block matries
# 'cholesky' is the build-in one in LAPACK

function Bcholesky(A::Symmetric{<:Any,<:BlockArray})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In src/BlockArrays.jl, add

import LinearAlgebra: cholesky, cholesky!

And then you can overload cholesky and cholesky!

chol_U = BlockArray{Float64}(zeros(size(A)), fill(size(A)[1]÷blocksize(A)[1], blocksize(A)[1]), fill(size(A)[1]÷blocksize(A)[1], blocksize(A)[1]))

# Initializing the first role of blocks
chol_U[Block(1,1)] = cholesky!(A[Block(1,1)]).U
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we change this to be properly in-place, we will want to replace this with cholesky!(Symmetric(getblock(parent(A),1,1))) which will modify A.

# Initializing the first role of blocks
chol_U[Block(1,1)] = cholesky!(A[Block(1,1)]).U
for j = 2:blocksize(A)[1]
chol_U[Block(1,j)] = chol_U[Block(1,1)]' \ A[Block(1,j)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this is in-place it will look something like:

P = parent(A)
ldiv!(UpperTriangular(getblock(P,1,1))', getblock(P,1,j))

for i = 2:blocksize(A)[1]
for j = i:blocksize(A)[1]
if j == i
chol_U[Block(i,j)] = cholesky!(A[Block(i,j)] - sum(chol_U[Block(k,j)]'chol_U[Block(k,j)] for k in 1:j-1)).U
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we will want to use mul! to do this in-place. Something like:

Pij = getblock(P,i,j)
for k = 1:j-1
    mul!(Pij,getblock(P,k,j)',getblock(P,k,j),-1.0,1.0)
end
cholesky!(Pij)


"""
Instructions and examples
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these strings included in the file? It's better to just add them as comments in the PR.

1.changed all computations into in-place but there are still many allocations
which is highly depended on the number of blocks.
For example, I took a block matrix A with n=5 and d=3 and it had 59 allocations. The build-in only have 5 instead.
I will focus on reducing these allocations.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note Julia v1.4 allocates for things that shouldn't, for example subviews, so you won't ever get 0 allocations.

This has been improved in Julia v1.5, but expecting 0 allocations is unrealistic at this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I have played around with these today, finding that my function uses less memory but slower. Is this a trade-off or a result of inefficient algorithm I adopted?

@dlfivefifty
Copy link
Member

I think the most important thing to add right now are unit tests.

@felixw98
Copy link
Contributor Author

I think the most important thing to add right now are unit tests.

Is that the test with build-in cholesky? I did that already on various block matrices and all passed.
These are the codes
A = BlockArray{Float64}(randn(100,100)+107I, fill(10,10), fill(10,10)); A = Symmetric(A)
MT = Matrix(A)

@test cholesky(A) ≈ cholesky(MT).U

@dlfivefifty
Copy link
Member

I mean add a new file test/test_cholesky.jl with the unit tests.

@dlfivefifty
Copy link
Member

Make sure you include the new test file in test/run tests.jl

@dlfivefifty
Copy link
Member

Unfortunately the 5-argument mul! is not available in Julia v1.1. The easiest solution is to change it to ArrayLayouts muladd!:
mul!(C, A, B, α, b) becomes muladd!(α, A, B, β, C).

We also need it to return a Cholesky object. I think this is as simple as changing the return to Cholesky(chol_P, A.uplo, 0).

Note we should also support the case of Symmetric(A, :L), which would give a lower Cholesky factorisation.

@felixw98 felixw98 closed this Jul 21, 2020
@dlfivefifty
Copy link
Member

Why did you close this?

@felixw98 felixw98 reopened this Jul 21, 2020
@felixw98
Copy link
Contributor Author

Why did you close this?

Sorry, I clicked the wrong one by mistake. What you mean Symmetric(A, :L), if we want a lower Cholesky factorization, we can quote by .L? Or we shall have another analogue function for lower cases?

@dlfivefifty
Copy link
Member

Yes. You would basically do the transpose of what we have, with Symmetric(getblock(...), :L) instead of Symmetric(getblock(...)).

See https://github.com/JuliaLang/julia/blob/07616a4e352de785db0a0a702c2a65fe905b864d/stdlib/LinearAlgebra/src/cholesky.jl#L217

end
end

return UpperTriangular(A)
Copy link
Member

@dlfivefifty dlfivefifty Jul 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please have this return Cholesky(A, :U, 0). The 0 should actually be info which is determined from the cholesky! call, so probably what we want is

info = 0
for ...
   ...
    info = cholesky!(Symmetric(Pii))
   iszero(info) || break
   ...
end
Cholesky(A, :U, info)

though we should double check what info actually means: that is, do we break and info indicates a problem, or do we continue as if it were SPD?

Similarly for lower below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the document of PosDefException(info), it says that the info field indicates the location of (one of) the eigenvalue(s) which is (are) less than/equal to 0. And the info is determined by the function LAPACK.potrf!(uplo, A). So we can do the check of SPD in a similar way with LinearAlgebra.cholesky

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you mean indicates that there exists a negative eigenvalue, not that it finds one. Here is the doc for LAPACK.potrf!: http://www.netlib.org/lapack/explore-html/d1/d7a/group__double_p_ocomputational_ga2f55f604a6003d03b5cd4a0adcfb74d6.html

INFO is INTEGER
          = 0:  successful exit
          < 0:  if INFO = -i, the i-th argument had an illegal value
          > 0:  if INFO = i, the leading minor of order i is not
                positive definite, and the factorization could not be
                completed.

So I think we want

if !iszero(info)
    @assert info > 0 # should never have illegal values
    info += sum(blockaxes(A,2)[Block.(1:i-1)]) # add columns before we got to block to indicate correct minor
    break
end


cholesky(A::Symmetric{<:Real,<:BlockArray},
::Val{false}=Val(false); check::Bool = true) = cholesky!(cholcopy(A); check = check)
::Val{false}=Val(false); check::Bool = false) = cholesky!(cholcopy(A); check = check)


function b_chol!(A::BlockArray{T}, ::Type{UpperTriangular}) where T<:Real
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convention in Julia is that internal routines start with _. So perhaps rename this function _block_chol!?


# For the left blocks
for i = 2:n
for i = 1:n
Pii = getblock(A,i,i)
for k = 1:i-1
muladd!(-1.0, getblock(A,i,k), getblock(A,i,k)', 1.0, Pii)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.0 should be one(T)

@@ -9,12 +9,20 @@ using BlockArrays, Test, LinearAlgebra
B = BlockArray{Float64}(randn(55,55)+100I, 1:10, 1:10); B = Symmetric(B)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add tests with Float32




function _blockbandedcholesky!(A::BlockArray{T}, ::Type{UpperTriangular}) where T<:Real
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why this is a separate function, not just block_chol! from beliw.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only difference is that I added a check of trivial blocks from backwards. It actually do the same thing on dense matrices. We can have another parameter for activating the 'check'.

@inbounds begin
for i = 1:n
Pii = getblock(A,i,i)
for k = 1:i-1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should loop over colsupport(A.blocks, i) ∩ 1:i-1 I think

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

colsupport(_, A, j) = axes(A,1)

""""
colsupport(A, j)

gives an iterator containing the possible non-zero entries in the j-th column of A.
"""
colsupport(A, j) = colsupport(MemoryLayout(typeof(A)), A, j)

The code of colsupport(A, j) is just the axes(A,1), so this may not be useful.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we only care about the blockbanded cases, maybe we can move the file into the package BlockBandedMatrices?
Then it will be convenient where all the prerequisite functions are included.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it should be here. It will do the right thing for both dense and banded formats.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing is that why LAPACK.potrf!() do not work in-place on BlockBandedMatrices?
It gives the right output but the block will not be replaced by the decomposed matrix.

end

k_start = n
while getblock(A,i,k_start) ≈ zeros(Float32, size(getblock(A,i,k_start))) && k_start > k_end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is strange

@felixw98
Copy link
Contributor Author

julia> M
3×3-blocked 9×9 BlockSkylineMatrix{Float32,Array{Float32,1},BlockBandedMatrices.BlockSkylineSizes{Tuple{BlockedUnitRange{Array{Int64,1}},BlockedUnitRange{Array{Int64,1}}},Fill{Int64,1,Tuple{Base.OneTo{Int64}}},Fill{Int64,1,Tuple{Base.OneTo{Int64}}},BandedMatri
x{Int64,Array{Int64,2},Base.OneTo{Int64}},Array{Int64,1}}}:
 64.0  16.0   0.0  │  16.0   0.0   0.0  │    ⋅     ⋅     ⋅
 16.0  64.0  16.0  │   0.0  16.0   0.0  │    ⋅     ⋅     ⋅
  0.0  16.0  64.0  │   0.0   0.0  16.0  │    ⋅     ⋅     ⋅
 ──────────────────┼────────────────────┼──────────────────
 16.0   0.0   0.0  │  64.0  16.0   0.0  │  16.0   0.0   0.0
  0.0  16.0   0.0  │  16.0  64.0  16.0  │   0.0  16.0   0.0
  0.0   0.0  16.0  │   0.0  16.0  64.0  │   0.0   0.0  16.0
 ──────────────────┼────────────────────┼──────────────────
   ⋅     ⋅     ⋅   │  16.0   0.0   0.0  │  64.0  16.0   0.0
   ⋅     ⋅     ⋅   │   0.0  16.0   0.0  │  16.0  64.0  16.0
   ⋅     ⋅     ⋅   │   0.0   0.0  16.0  │   0.0  16.0  64.0

The block banded matrix like this and

julia> LAPACK.potrf!('U',getblock(M,1,1))
(Float32[8.0 2.0 0.0; 16.0 7.745967 2.065591; 0.0 16.0 7.7287345], 0)

But matrix M isn't changed. This won't happen on general blockarrays.

@dlfivefifty
Copy link
Member

Can you try with view(M,Block(1,1))?

@felixw98
Copy link
Contributor Author

felixw98 commented Sep 1, 2020

Can you try with view(M,Block(1,1))?

This works.
But in the following link, the rdiv!() takes the type of StridedMatrix{T} where it is StridedVecOrMat{T} in ldiv!().
Shouldn't these be the same?
https://github.com/JuliaLang/julia/blob/697e782ab86bfcdd7fd15550241fe162c51d9f98/stdlib/LinearAlgebra/src/triangular.jl#L766-L784

So far, rdiv!() cannot take view() as input since it isn't a subtype of StridedMatrix{T}.

@dlfivefifty
Copy link
Member

Because you can't right-multiply a vector times a matrix

Just overload rdiv for BlockBandedBlock (defined here https://github.com/JuliaMatrices/BlockBandedMatrices.jl/blob/de57bfe664d62c6cbd354dd23d0c8b48a1aa8696/src/BlockSkylineMatrix.jl#L420)

But note I suggested you first try BlockArray{T,<:BandedMatrix} which is a different type than BlockBandedMatrix

@felixw98
Copy link
Contributor Author

felixw98 commented Sep 3, 2020

BlockArray{T,<:BandedMatrix}

There isn't a type of BlockArray{T,<:BandedMatrix}, the only one I can get is BlockArray{T,2,<:BandedMatrix}.

julia> BlockArray{T,<:BandedMatrix} where T <: Real
ERROR: TypeVar in Vararg length must have bounds Union{} and Any
Stacktrace:
[1] top-level scope at REPL[81]:1

And for `BlockArray{T,2,<:BandedMatrix}`, `muladd!()` will have an error eg.
julia> A = BlockArray(A,fill(3,3),fill(3,3))
3×3-blocked 9×9 BlockArray{Float32,2,Array{BandedMatrix{Float32,Array{Float32,2},Base.OneTo{Int64}},2},Tuple{BlockedUnitRange{Array{Int64,1}},BlockedUnitRange{Array{Int64,1}}}}:
 99.0099     0.595538     0.0       │    0.0        0.0         0.0       │   0.0         0.0         0.0
 -0.682299  99.6287      -0.254271  │    0.0        0.0         0.0       │   0.0         0.0         0.0
  0.0       -0.0365762  101.114     │    0.387621   0.0         0.0       │   0.0         0.0         0.0
 ───────────────────────────────────┼─────────────────────────────────────┼───────────────────────────────────
  0.0        0.0          0.887276  │  101.698     -1.04962     0.0       │   0.0         0.0         0.0
  0.0        0.0          0.0       │    0.335081  99.3336      1.1688    │   0.0         0.0         0.0
  0.0        0.0          0.0       │    0.0       -0.771624  100.44      │  -0.512122    0.0         0.0
 ───────────────────────────────────┼─────────────────────────────────────┼───────────────────────────────────
  0.0        0.0          0.0       │    0.0        0.0         0.944132  │  99.6787      0.122043    0.0
  0.0        0.0          0.0       │    0.0        0.0         0.0       │  -1.04835   101.11       -0.697174
  0.0        0.0          0.0       │    0.0        0.0         0.0       │   0.0        -0.331242  100.515
julia> ArrayLayouts.muladd!(-one(Float32), getblock(A,1,3)', getblock(A,1,3), one(Float32), getblock(A,3,3))
3×3 BandedMatrix{Float32,Array{Float32,2},Base.OneTo{Int64}}:
 0.0  0.0   ⋅
 0.0  0.0  0.0
  ⋅   0.0  0.0

while mul!() won't have this problem. The function replaces C with aAB instead of aAb + bC.

@dlfivefifty
Copy link
Member

Meant BlockMatrix{T,<:BandedMatrix{<:Matrix}}. Your second example is BlockMatrix{T,<:Matrix{<:BandedMatrix}}}

@felixw98
Copy link
Contributor Author

felixw98 commented Sep 4, 2020

Meant BlockMatrix{T,<:BandedMatrix{<:Matrix}}. Your second example is BlockMatrix{T,<:Matrix{<:BandedMatrix}}}

How to produce the first case, I have tried different ways but can only the later one.
And how to overload stride for BandedMatrix?

@dlfivefifty dlfivefifty merged commit 32990fc into JuliaArrays:master Jun 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants