-
Notifications
You must be signed in to change notification settings - Fork 33
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
Conversation
Codecov Report
@@ 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
Continue to review full report at Codecov.
|
There was a problem hiding this 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
src/blockcholesky.jl
Outdated
# Function for cholesky decomposition on block matries | ||
# 'cholesky' is the build-in one in LAPACK | ||
|
||
function Bcholesky(A::Symmetric{<:Any,<:BlockArray}) |
There was a problem hiding this comment.
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!
src/blockcholesky.jl
Outdated
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 |
There was a problem hiding this comment.
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
.
src/blockcholesky.jl
Outdated
# 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)] |
There was a problem hiding this comment.
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))
src/blockcholesky.jl
Outdated
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 |
There was a problem hiding this comment.
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)
src/blockcholesky.jl
Outdated
|
||
""" | ||
Instructions and examples | ||
""" |
There was a problem hiding this comment.
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.
src/blockcholesky.jl
Outdated
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
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. @test cholesky(A) ≈ cholesky(MT).U |
I mean add a new file test/test_cholesky.jl with the unit tests. |
Make sure you include the new test file in test/run tests.jl |
Unfortunately the 5-argument We also need it to return a Note we should also support the case of |
Why did you close this? |
Sorry, I clicked the wrong one by mistake. What you mean |
Yes. You would basically do the transpose of what we have, with |
src/blockcholesky.jl
Outdated
end | ||
end | ||
|
||
return UpperTriangular(A) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
src/blockcholesky.jl
Outdated
|
||
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 |
There was a problem hiding this comment.
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!
?
src/blockcholesky.jl
Outdated
|
||
# 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) |
There was a problem hiding this comment.
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)
test/test_cholesky.jl
Outdated
@@ -9,12 +9,20 @@ using BlockArrays, Test, LinearAlgebra | |||
B = BlockArray{Float64}(randn(55,55)+100I, 1:10, 1:10); B = Symmetric(B) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add tests with Float32
src/blockbandedcholesky.jl
Outdated
|
||
|
||
|
||
function _blockbandedcholesky!(A::BlockArray{T}, ::Type{UpperTriangular}) where T<:Real |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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'.
src/blockbandedcholesky.jl
Outdated
@inbounds begin | ||
for i = 1:n | ||
Pii = getblock(A,i,i) | ||
for k = 1:i-1 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's overloaded for banded matrices: https://github.com/JuliaMatrices/BandedMatrices.jl/blob/3eb9c0d77f471db67b87b7dc5f9832c1d0beb0b2/src/generic/AbstractBandedMatrix.jl#L89
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/blockbandedcholesky.jl
Outdated
end | ||
|
||
k_start = n | ||
while getblock(A,i,k_start) ≈ zeros(Float32, size(getblock(A,i,k_start))) && k_start > k_end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is strange
The block banded matrix like this and
But matrix M isn't changed. This won't happen on general blockarrays. |
Can you try with |
This works. So far, |
Because you can't right-multiply a vector times a matrix Just overload But note I suggested you first try |
There isn't a type of
julia> BlockArray{T,<:BandedMatrix} where T <: Real
while |
Meant |
How to produce the first case, I have tried different ways but can only the later one. |
No description provided.