JuliaStats / PDMats.jl

Uniform Interface for positive definite matrices of various structures
Other
104 stars 43 forks source link

Avoid copy in `chol_lower` #144

Closed st-- closed 2 years ago

st-- commented 2 years ago

Fixes #143

st-- commented 2 years ago

Side note: chol_lower(::CholTypeSparse) seems to be working fine, but I'm not sure I understand enough about the implementation details of SuiteSparse to guarantee this.

st-- commented 2 years ago

I agree that CholType is redundant but please make that change in a separate PR.

Done, see #145

st-- commented 2 years ago

I think you can just make this chol_lower(a::Matrix) = cholesky(Hermitian(a, :L)).L.

Writing it in terms of chol_lower(cholesky(a)) seems cleaner to me, and it should compile to the same code; what benefits do you see in writing it as this ?

andreasnoack commented 2 years ago

and it should compile to the same code

No. The default is upper

julia> cholesky(A).uplo
'U': ASCII/Unicode U+0055 (category Lu: Letter, uppercase)

julia> cholesky(Hermitian(A, :L)).uplo
'L': ASCII/Unicode U+004C (category Lu: Letter, uppercase)
st-- commented 2 years ago

Ok, not literally the same code, but chol_lower(cholesky(A)) which is equivalent to cholesky(A).U' is no less efficient (more efficient, actually) than introducing the Hermitian:

julia> using LinearAlgebra, PDMats

julia> A=rand(100,100); C=A'A;

julia> @allocated cholesky(Hermitian(C, :L)).L;  # compile

julia> @allocated cholesky(Hermitian(C, :L)).L
80208

julia> @allocated cholesky(C).U';  # compile

julia> @allocated cholesky(C).U'
80192

julia> @allocated PDMats.chol_lower(cholesky(C));  # compile

julia> @allocated PDMats.chol_lower(cholesky(C))
80192
andreasnoack commented 2 years ago

It's just an artifact of the not wrapping it in a function

julia> chol_lower_an(A::Matrix) = cholesky(Hermitian(A, :L)).L;

julia> @allocated chol_lower_an(A);

julia> @allocated chol_lower_an(A)
80144

It's also best to avoid wrapper types when possible.

st-- commented 2 years ago

It's just an artifact of the not wrapping it in a function

Ah, thanks for pointing that out.

It's also best to avoid wrapper types when possible.

Could you expand on what you mean ?

andreasnoack commented 2 years ago

Could you expand on what you mean ?

Sure. The more types layers you have the less likely it is that a specific method has been written for it and you risk hitting a less optimized fallback. The compiler can also sometimes completely remove these wrapper types but I believe that the more layers you have, the harder it is for the compiler to do so.

st-- commented 2 years ago

Ok, so do I understand correctly, you would prefer

-chol_lower(a::Matrix) = chol_lower(cholesky(a))
+chol_lower(a::Matrix) = cholesky(Hermitian(a, :L)).L

because the former returns a LowerTriangular{Adjoint{Matrix}}, whereas the latter returns a LowerTriangular{Matrix} directly?

andreasnoack commented 2 years ago

That is correct

st-- commented 2 years ago

Thanks, that makes sense. I've changed it accordingly:)

codecov-commenter commented 2 years ago

Codecov Report

Merging #144 (1ce765d) into master (a0e7191) will not change coverage. The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master     #144   +/-   ##
=======================================
  Coverage   88.62%   88.62%           
=======================================
  Files           8        8           
  Lines         378      378           
=======================================
  Hits          335      335           
  Misses         43       43           
Impacted Files Coverage Δ
src/chol.jl 100.00% <100.00%> (ø)

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 a0e7191...1ce765d. Read the comment docs.

st-- commented 2 years ago

Downside of merging it in with Hermitian() is that this breaks AD :S

andreasnoack commented 2 years ago

Oh no. Do you have an example?

st-- commented 2 years ago

Yes, it turns out to be a Zygote issue, I've made a PR to fix it here: https://github.com/FluxML/Zygote.jl/pull/1114

st-- commented 2 years ago

@andreasnoack looks like the Zygote issue might be a bit more complex to resolve; how about (at least temporarily) changing it from Hermitian to Symmetric?

andreasnoack commented 2 years ago

Given that we have this restriction https://github.com/JuliaStats/PDMats.jl/blob/635a957abc71aa9b63d276f96a185db1bed9de74/src/PDMats.jl#L36 it should be fine