-
Notifications
You must be signed in to change notification settings - Fork 42
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
Only store non-zero blocks in BlockMap #84
base: master
Are you sure you want to change the base?
Conversation
Codecov Report
@@ Coverage Diff @@
## master #84 +/- ##
==========================================
- Coverage 97.67% 90.86% -6.82%
==========================================
Files 10 10
Lines 732 799 +67
==========================================
+ Hits 715 726 +11
- Misses 17 73 +56
Continue to review full report at Codecov.
|
Also, as a humble suggestion, maybe we could avoid having the type of I would prefer to either have |
Thanks for your contribution, @jagot! Unfortunately, I'm pretty busy this and the next week, as the semester is closing and the exam session is starting. I'm not sure about the |
No worries; I can continue working with my branch. Regarding the storage type for My use-case is this: I have an integral operator which I represent as a subtype I will play around a bit more. |
Regardless of details, I like your generalization, and obviously, you come with a use case. Does mat-vec-multiply work with your (generalized) BlockMaps and our current |
Yes, as far as I can tell, i.e. |
For the same reason, I would actually like to relax the type requirement of |
I'm afraid I don't follow. IMHO, it doesn't get any more type stable if you have all the types in the hierarchy available as type parameters. Also, it's not going to be julia> using LinearMaps, LinearAlgebra
julia> A = LinearMap(rand(3,3));
julia> B = LinearMap(I,3);
julia> C = [A, B]
2-element Array{LinearMap,1}:
LinearMaps.WrappedMap{Float64,Array{Float64,2}}([0.1334265214134871 0.192274602216711 0.019312426807825966; 0.6276101396807128 0.684224418730355 0.02245764986996379; 0.8164557288364198 0.9256657613490129 0.7130602030248578], false, false, false)
LinearMaps.UniformScalingMap{Bool}(true, 3)
julia> eltype(C)
LinearMap at which point all type information about the constituents of I understand that having tuples of length on the order of thousands may not be helpful for the compiler (I think I found that earlier with |
Sorry, I was not being very clear. Imagine I have a concrete subtype
julia> L = LowerTriangular(reshape(3*(one(T):n^2), n, n))
6×6 LowerTriangular{Int64,Base.ReshapedArray{Int64,2,StepRange{Int64,Int64},Tuple{}}}:
3 ⋅ ⋅ ⋅ ⋅ ⋅
6 24 ⋅ ⋅ ⋅ ⋅
9 27 45 ⋅ ⋅ ⋅
12 30 48 66 ⋅ ⋅
15 33 51 69 87 ⋅
18 36 54 72 90 108
julia> L2 = LowerTriangular(reshape(2*(one(T):n^2), n, n))
6×6 LowerTriangular{Int64,Base.ReshapedArray{Int64,2,StepRange{Int64,Int64},Tuple{}}}:
2 ⋅ ⋅ ⋅ ⋅ ⋅
4 16 ⋅ ⋅ ⋅ ⋅
6 18 30 ⋅ ⋅ ⋅
8 20 32 44 ⋅ ⋅
10 22 34 46 58 ⋅
12 24 36 48 60 72
julia> [LinearMap(L), LinearMap(L2)]
2-element Array{LinearMaps.WrappedMap{Int64,LowerTriangular{Int64,Base.ReshapedArray{Int64,2,StepRange{Int64,Int64},Tuple{}}}},1}:
LinearMaps.WrappedMap{Int64,LowerTriangular{Int64,Base.ReshapedArray{Int64,2,StepRange{Int64,Int64},Tuple{}}}}([3 0 … 0 0; 6 24 … 0 0; … ; 15 33 … 87 0; 18 36 … 90 108], false, false, false)
LinearMaps.WrappedMap{Int64,LowerTriangular{Int64,Base.ReshapedArray{Int64,2,StepRange{Int64,Int64},Tuple{}}}}([2 0 … 0 0; 4 16 … 0 0; … ; 10 22 … 58 0; 12 24 … 60 72], false, false, false) EDIT: To illustrate: by relaxing the type requirement like so struct LinearCombination{T, As} <: LinearMap{T}
# Everything else unchanged
end we can do this julia> n = 6
6
julia> T = Int
Int64
julia> D = Diagonal(3ones(T, n))
6×6 Diagonal{Int64,Array{Int64,1}}:
3 ⋅ ⋅ ⋅ ⋅ ⋅
⋅ 3 ⋅ ⋅ ⋅ ⋅
⋅ ⋅ 3 ⋅ ⋅ ⋅
⋅ ⋅ ⋅ 3 ⋅ ⋅
⋅ ⋅ ⋅ ⋅ 3 ⋅
⋅ ⋅ ⋅ ⋅ ⋅ 3
julia> L = LowerTriangular(reshape(one(T):n^2, n, n))
6×6 LowerTriangular{Int64,Base.ReshapedArray{Int64,2,UnitRange{Int64},Tuple{}}}:
1 ⋅ ⋅ ⋅ ⋅ ⋅
2 8 ⋅ ⋅ ⋅ ⋅
3 9 15 ⋅ ⋅ ⋅
4 10 16 22 ⋅ ⋅
5 11 17 23 29 ⋅
6 12 18 24 30 36
julia> typeof(LinearMap(D) + LinearMap(L)) # Yields same type as before
LinearMaps.LinearCombination{Int64,Tuple{LinearMaps.WrappedMap{Int64,Diagonal{Int64,Array{Int64,1}}},LinearMaps.WrappedMap{Int64,LowerTriangular{Int64,Base.ReshapedArray{Int64,2,UnitRange{Int64},Tuple{}}}}}}
julia> typeof(LinearMaps.LinearCombination{Int}([LinearMap(D)])) # Yields new type, but still type-stable
LinearMaps.LinearCombination{Int64,Array{LinearMaps.WrappedMap{Int64,Diagonal{Int64,Array{Int64,1}}},1}}
julia> typeof(LinearMaps.LinearCombination{Int}([LinearMap(D),LinearMap(D)])) # Same type as one-term case
LinearMaps.LinearCombination{Int64,Array{LinearMaps.WrappedMap{Int64,Diagonal{Int64,Array{Int64,1}}},1}} |
Hi @jagot and @dkarrasch . I've quickly read through this thread, so I might be misinterpreting somethings, but here are some suggestions:
|
Thanks @Jutho for you input! I went ahead and pushed to type requirement relaxation, and then I can do the following: julia> n = 6
6
julia> T = Int
Int64
julia> D = Diagonal(3ones(T, n))
6×6 Diagonal{Int64,Array{Int64,1}}:
3 ⋅ ⋅ ⋅ ⋅ ⋅
⋅ 3 ⋅ ⋅ ⋅ ⋅
⋅ ⋅ 3 ⋅ ⋅ ⋅
⋅ ⋅ ⋅ 3 ⋅ ⋅
⋅ ⋅ ⋅ ⋅ 3 ⋅
⋅ ⋅ ⋅ ⋅ ⋅ 3
julia> A = LinearMaps.LinearCombination{Int}(repeat([LinearMap(D)],inner=40))
LinearMaps.LinearCombination((LinearMaps.WrappedMap{Int64,Diagonal{Int64,Array{Int64,1}}}([3 0 … 0 0; 0 3 … 0 0; … ; 0 0 … 3 0; 0 0 … 0 3], true, true, true), LinearMaps.WrappedMap{Int64,Diagonal{Int64,Array{Int64,1}}}([3 0 … 0 0; 0 3 … 0 0; … ; 0 0 … 3 0; 0 0 … 0 3], true, true, true), LinearMaps.WrappedMap{Int64,Diagonal{Int64,Array{Int64,1}}}([3 0 … 0 0; 0 3 … 0 0; … ; 0 0 … 3 0; 0 0 … 0 3], true, true, true), LinearMaps.WrappedMap{Int64,Diagonal{Int64,Array{Int64,1}}}([3 0 … 0 0; 0 3 … 0 0; … ; 0 0 … 3 0; 0 0 … 0 3], true, true, true), LinearMaps.WrappedMap{Int64,Diagonal{Int64,Array{Int64,1}}}([3 0 … 0 0; 0 3 … 0 0; … ; 0 0 … 3 0; 0 0 … 0 3], true, true, true), …, LinearMaps.WrappedMap{Int64,Diagonal{Int64,Array{Int64,1}}}([3 0 … 0 0; 0 3 … 0 0; … ; 0 0 … 3 0; 0 0 … 0 3], true, true, true), LinearMaps.WrappedMap{Int64,Diagonal{Int64,Array{Int64,1}}}([3 0 … 0 0; 0 3 … 0 0; … ; 0 0 … 3 0; 0 0 … 0 3], true, true, true), LinearMaps.WrappedMap{Int64,Diagonal{Int64,Array{Int64,1}}}([3 0 … 0 0; 0 3 … 0 0; … ; 0 0 … 3 0; 0 0 … 0 3], true, true, true), LinearMaps.WrappedMap{Int64,Diagonal{Int64,Array{Int64,1}}}([3 0 … 0 0; 0 3 … 0 0; … ; 0 0 … 3 0; 0 0 … 0 3], true, true, true), LinearMaps.WrappedMap{Int64,Diagonal{Int64,Array{Int64,1}}}([3 0 … 0 0; 0 3 … 0 0; … ; 0 0 … 3 0; 0 0 … 0 3], true, true, true), LinearMaps.WrappedMap{Int64,Diagonal{Int64,Array{Int64,1}}}([3 0 … 0 0; 0 3 … 0 0; … ; 0 0 … 3 0; 0 0 … 0 3], true, true, true)))
julia> typeof(A)
LinearMaps.LinearCombination{Int64,Array{LinearMaps.WrappedMap{Int64,Diagonal{Int64,Array{Int64,1}}},1}}
julia> eye = Matrix{Int}(I, n, n)
6×6 Array{Int64,2}:
1 0 0 0 0 0
0 1 0 0 0 0
0 0 1 0 0 0
0 0 0 1 0 0
0 0 0 0 1 0
0 0 0 0 0 1
julia> out = similar(eye)
6×6 Array{Int64,2}:
1 6 12 18 26 32
2 7 13 19 27 33
35 8 14 20 28 34
3 9 15 23 29 21
4 10 16 24 30 22
5 11 17 25 31 4421842032
julia> @benchmark mul!(out, A, eye)
BenchmarkTools.Trial:
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 8.125 μs (0.00% GC)
median time: 8.804 μs (0.00% GC)
mean time: 9.611 μs (0.00% GC)
maximum time: 46.923 μs (0.00% GC)
--------------
samples: 10000
evals/sample: 3 i.e. the storage is fully type-resolved and there is no allocation (provided that the multiplication of the individual maps are non-allocating, of course). Regarding a special |
@jagot I had another look at your PR (finally!). I think this is really a great extension. So, just for myself to clarify, this addresses the case when the LinearMaps are arranged in a "lattice", right? With possible zero blocks. In some sense, this shares a lot with #65, like have LinearMaps "floating around", while only specifying the origin and target ranges. The challenge in #65 was that if you have one block surrounded by "zero blocks", then multiplying a vector needs to set some indices in |
You're right, that's a case I did not think of. That should be easy enough to add. I myself had to abandon this approach since it was too slow for my purposes, so I had to roll my own specialized block multiplier, which I could also efficiently multithread. I'll explain the problem, and how I solved it, and maybe we could generalize the idea such that it could find its way into LinearMaps.jl at some point. A big part of my calculations is computing the action on a vector of the exponential of the exchange interaction between electrons, i.e.
Now it turns out that a lot of the Poisson problems are actually used more than once, so they can be solved once (and in parallel). Then the resultant potentials applied to the various Sorry for the long-wound explanation. Do you think providing for these kind of blocked operators with a lot of data reuse has a place in LinearMaps.jl, or is it too specialized. I must concede that it is not immediately obvious to me how to generalize this easily. |
Thanks, @jagot, for the update. I'm not sure I understand every detail, but I'm sure you're well familiar with what the community has to offer in that direction (like DiffEq stuff, and @Jutho's KrylovKit.jl, for instance). For the time being, I'd like to extract some parts of this PR, if I may (and have you a co-author of the commit, of course). I particularly like the |
@jagot , I'll pick up on this PR, if that's Ok with you? |
By all means 👍 |
I generate quite large block-maps with sparse block occupancy, so creating
BlockMap
s via*cat
operations is cumbersome. In addition, I wanted to avoid having to fill out the zero blocks withLinearMap(false*I, n)
entries if possible (even though the multiplication overhead may be negligible). Therefore, I changed such thatBlockMap.rows
may have vanishing entries, i.e. representing rows without any blocks, added explicit dimensions of theBlockMap
, and a new constructor where the non-vanishing blocks along with their coordinates are explicitly given.By changing the materialization methods to not use
hvcat
(which does not work when vanishing blocks are not stored),Matrix(B)
actually returns aMatrix
now, even if some of theLinearMap
s are e.g.Diagonal
matrices.hvcat
returns aSparseMatrixCSC
if concatenating non-dense matrices.I did not add any tests yet, since I wanted to know if you like the idea first.
Example usage:
EDIT: I just noticed that the heuristic to decide how many columns the
BlockMap
consists of does not really work when no row has entries in all columns:4×3-blocked 24×24 BlockMap{Int64}