Vectorized solution to replace for loops in nested combinations

10 views (last 30 days)
I currently have a Matlab program which uses for loops and I would like to vectorize as much of it as possible to increase performance (the actual calcultion in the innermost loop could benefit from vectorization, I hope... it involves multiple arithmetic and elementwise operations aggregated by a sum of sum of sums...). The loops are, in the specific case N=2:
for t=1:T, for t1=0:t, for m1=0:t1, for m2=0:t-t1
. In other words, there is a variable t which goes from 1 to T and I am interested, for each value of t, in getting a partition t1+t2=t and selecting all possible 0<=m1<=t1 and 0<=m2<=t2. I was wondering if there was a way to generate all these combinations of t1, t2, m1 and m2, but in general for N>2, say N=5 for example?
One approach would be to use ndgrid followed by a selection of those variables which actually fit the criteria; unfortunately the size of resulting matrices quickly becomes unwieldy. Also, this is overkill since most portions of those matrices don't fit the criteria. So although the brute force approach is highly combinatorial, the final result is not so highly combinatorial that it becomes impossible to manage in my situation (say T about 50 and N between 5 and 10, depending on what the actual numbers are I might have to adapt so that this all fits in memory...).
Here is what the non-vectorized code could look like for N=2 and N=3:
function [ combisN2, combisN3 ] = testCombis()
T = 7; % just so this goes faster, but ideally I want to go to T=50, or perhaps more...
%% loops for N=2
N = 2;
nb2 = zeros( T, 1 );
for t = 1:T
% parfor t = 1:T % toggle comments with previous line to use parallel loops
for t1 = 0:t
t2 = t - t1;
for m1 = 0:t1
for m2 = 0:t2
nb2( t ) = nb2( t ) + 1;
end
end
end
end
data2 = cell( T, 1 );
for t = 1:T
% parfor t = 1:T % toggle comments with previous line to use parallel loops
counter2 = 0;
data2{ t, 1 } = uint8( nan( nb2( t ), N * 2 ) );
for t1 = 0:t
t2 = t - t1;
for m1 = 0:t1
for m2 = 0:t2
counter2 = counter2 + 1;
data2{ t, 1 }( counter2, : ) = [ t1, m1, t2, m2 ];
end
end
end
end
combisN2 = cell2mat( data2 );
save( 'combisN2.mat', 'combisN2' );
%% loops for N=3
N = 3;
nb3 = zeros( T, 1 );
for t = 1:T
% parfor t = 1:T % toggle comments with previous line to use parallel loops
for t1 = 0:t
for t2 = 0:t-t1
t3 = t - t1 - t2;
for m1 = 0:t1
for m2 = 0:t2
for m3 = 0:t3
nb3( t ) = nb3( t ) + 1;
end
end
end
end
end
end
data3 = cell( T, 1 );
for t = 1:T
% parfor t = 1:T % toggle comments with previous line to use parallel loops
data3{ t, 1 } = uint8( nan( nb3( t ), N * 2 ) );
counter3 = 0;
for t1 = 0:t
for t2 = 0:t-t1
t3 = t - t1 - t2;
for m1 = 0:t1
for m2 = 0:t2
for m3 = 0:t3
counter3 = counter3 + 1;
data3{ t, 1 }( counter3, : ) = [ t1, m1, t2, m2, t3, m3 ];
end
end
end
end
end
end
combisN3 = cell2mat( data3 );
save( 'combisN3.mat', 'combisN3' );
end
The code returns the list of possible values taken by t and m for all indices 1:N in columns. The limitation here will likely be on the number of rows such a matrix can hold in memory.
(Side note: the code above assumes parallel for loops in its design, although the line itself is commented to enable execution on your end if you do not have the parallel toolbox; if it were only for non-parallel calculation, the structure I would have chosen for the data would likely have been different, for example avoiding cell arrays as they currently are... Also the code was originally meant for calculating the size of the resulting matrix, and I decided to simply add the actual desired result as well. I am not so much looking for comments on how to improve the for loops as I am to avoid them completely and wish to obtain a vectorized solution which provides the same results.)
  1 Comment
Stepp Gyogi
Stepp Gyogi on 9 Feb 2023
I forgot to mention why such a program would be useful, so I am adding this bit of info here in case someone looks it up: these combinations represent the universe, for each t=0:T, of a multinomial distribution with t trials and N categories.

Sign in to comment.

Accepted Answer

Matt J
Matt J on 9 Feb 2023
Edited: Matt J on 9 Feb 2023
Well, you can at least reduce it to 2 loops:
T=50;
nb2 = zeros( T, 1 );
tic
for t = 1:T
t1=0:t;
for i=1:numel(t1)
[M1,M2]=ndgrid(0:t1(i), 0:(t-t1(i)) );%all M1,M2 combinations for fixed t,t1
nb2( t ) = nb2( t ) + numel(M1);
end
end
toc
Elapsed time is 0.025991 seconds.
nb2
nb2 = 50×1
4 10 20 35 56 84 120 165 220 286
  14 Comments
Matt J
Matt J on 9 Feb 2023
Edited: Matt J on 9 Feb 2023
Something like this, I would guess.
T=7;
N=2;
tcombos=num2cell(allVL1(N,T,'<='));
nn=size(tcombos,1);
result=cell(nn,1);
for i=1:nn
mranges=cellfun(@(j)0:j, tcombos(i,:),'un',0);
arg=[tcombos(i,:);mranges];
[d{1:2*N}]=ndgrid(arg{:});
result{i}=reshape( cat(2*N+1,d{:}) ,[],2*N);
end
result=array2table( vertcat(result{:}),'Var', ["t";"m"]+(1:N))
result = 330×4 table
t1 m1 t2 m2 __ __ __ __ 0 0 0 0 0 0 1 0 0 0 1 1 1 0 0 0 1 1 0 0 0 0 2 0 0 0 2 1 0 0 2 2 1 0 1 0 1 1 1 0 1 0 1 1 1 1 1 1 2 0 0 0 2 1 0 0 2 2 0 0 0 0 3 0
Stepp Gyogi
Stepp Gyogi on 10 Feb 2023
This works (though I do not know as of now if it improves performance vs for loops, it definitely enables generalizing in a way my loops did not). Thanks!

Sign in to comment.

More Answers (0)

Categories

Find more on Loops and Conditional Statements in Help Center and File Exchange

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!