Suppose I have two very large lists {a1, a2, …} and {b1, b2, …} where all ai and bj are large sparse arrays. For the sake of memory efficiency I store each list as one comprehensive sparse array.
Now I would like to compute some function f on all possible pairs of ai and bj where each result f[ai, bj] is a sparse array again. All these sparse arrays have the same dimensions, by the way.
While
Flatten[Outer[f, {a1, a2, ...}, {b1, b2, ...}, 1], 1]
returns the desired result (in principle) it appears to consume excessive amounts of memory. Not the least because the return value is a list of sparse arrays whereas one comprehensive sparse array turns out much more efficient in my cases of interest.
Is there an efficient alternative to the above use of Outer
?
More specific example:
{SparseArray[{{1, 1, 1, 1} -> 1, {2, 2, 2, 2} -> 1}],
SparseArray[{{1, 1, 1, 2} -> 1, {2, 2, 2, 1} -> 1}],
SparseArray[{{1, 1, 2, 1} -> 1, {2, 2, 1, 2} -> 1}],
SparseArray[{{1, 1, 2, 2} -> -1, {2, 2, 1, 1} -> 1}],
SparseArray[{{1, 2, 1, 1} -> 1, {2, 1, 2, 2} -> 1}],
SparseArray[{{1, 2, 1, 2} -> 1, {2, 1, 2, 1} -> 1}],
SparseArray[{{1, 2, 2, 1} -> -1, {2, 1, 1, 2} -> 1}],
SparseArray[{{1, 2, 2, 2} -> 1, {2, 1, 1, 1} -> 1}]};
ByteCount[%]
list = SparseArray[%%]
ByteCount[%]
Flatten[Outer[Dot, list, list, 1], 1];
ByteCount[%]
list1x2 = SparseArray[%%]
ByteCount[%]
Flatten[Outer[Dot, list1x2, list, 1], 1];
ByteCount[%]
list1x3 = SparseArray[%%]
ByteCount[%]
etc. Not only are the raw intermediate results of Outer
(lists of sparse arrays) extremely inefficient, Outer
seems to consume way too much memory during the computation itself, too.
I will propose a solution which is rather complex but allows one to only use about twice as much memory during the computation as is needed to store the final result as a
SparseArray
. The price to pay for this will be a much slower execution.The code
Sparse array construction / deconstruction API
Here is the code. First, a slightly modified (to address higher-dimensional sparse arrays) sparse array construction - deconstruction API, taken from this answer:
Iterators
The following functions produce iterators. Iterators are a good way to encapsulate the iteration process.
Note that I could have implemented the above function more memory - efficiently and not use
Outer
in it, but for our purposes this won't be the major concern.Here is a more specialized version, which produces interators for pairs of 2-dimensional indices.
Here is how this works:
We can also use this to get batch results:
, and we will be using this second form.
SparseArray - building function
This function will build a
SparseArray
object iteratively, by getting chunks of data (also inSparseArray
form) and gluing them together. It is basically code used in this answer, packaged into a function. It accepts the code piece used to produce the next chunk of data, wrapped inHold
(I could alternatively make itHoldAll
)Putting it all together
This function is the main one, putting it all together:
Here, we first produce the iterator which will give us on demand portions of index pair list, used to extract the elements (also
SparseArrays
). Note that we will generally extract more than one pair of elements from two large inputSparseArray
-s at a time, to speed up the code. How many pairs we process at once is governed by the optionalchunkSize
parameter, which defaults to100
. We then construct the code to process these elements and put the result back intoSparseArray
, where we use an auxiliary functionwrapperF
. The use of iterators wasn't absolutely necessary (could useReap
-Sow
instead, as with other answers), but allowed me to decouple the logic of iteration from the logic of generic accumulation of sparse arrays.Benchmarks
First we prepare large sparse arrays and test our functionality:
Now we do the power tests
For this particular example, the suggested method is 5 times more memory-efficient than the direct use of
Outer
, but about 15 times slower. I had to tweak thechunksize
parameter (default is100
, but for the above I used2000
, to get the optimal speed / memory use combination). My method only used as a peak value twice as much memory as needed to store the final result. The degree of memory-savings as compared toOuter
- based method will depend on the sparse arrays in question.If
lst1
andlst2
are your lists,does the job and may be more memory-efficient. On the other hand, maybe not. Nasser is right, an explicit example would be useful.
EDIT: Using Nasser's randomly-generated arrays, and for
len=200
,MaxMemoryUsed[]
indicates that this form needs 170MB while theOuter
form in the question takes 435MB.Using your example
list
data, I believe that you will find the ability toAppend
to aSparseArray
quite helpful.I need
Rest
to drop the first zero-filled tensor in the result. The second argument of the seedSparseArray
must be the dimensions of each of your elements with a prefixed1
. You may need to explicitly specify a background for the seedSparseArray
to optimize performance.