-
Notifications
You must be signed in to change notification settings - Fork 35
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.
|
dlfivefifty
left a comment
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
| ::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 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
|
|
||
| # Generating random positive definite and symmetric matrices | ||
| A = BlockArray{Float64}(randn(9,9)+100I, fill(3,3), fill(3,3)); A = Symmetric(A) | ||
| 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.