Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically change for-loops in MATLAB

When setting up for-loops, I want to be able to loop over an unknown number of parameters.

Through an input file, the user can set up as many or as few looping parameters as they desire, and I want to be able to loop over them regardless of the number of parameters they set up. Loop Input Example: (Note that my inputs can be lists of strings and number combinations as well)

Case 1:

  • Weight 45000:5000:75000
  • Altitude 10000
  • Speed 0.2:0.1:0.9

Case 2:

  • Weight 30000
  • Altitude 1000:1000:10000
  • Flaps 10, 20
  • Temperature -10:1:10

The length of the list can differ, and can include anywhere from 0-15 variables. I know the workaround, but it's really messy to use a bunch of nested for-loops to do this. I'm looking for a way to set up a method, maybe with Recursion, where the code will set up an appropriate for-loop system regardless of the number of parameters involved, and still keeping track of those variables.

like image 544
arthream Avatar asked Feb 01 '16 20:02

arthream


4 Answers

The code-generation solution

Well, you already have a number of pretty good solutions. I'll just toss one in there that involves code generation. MATLAB doesn't really have much in the way of those kind of tools, but you can fake it with a few loops and fprintf. Here's my code generation script:

s = struct() ;
s.weight = 45000:5000:75000 ;
s.altitude = 10000 ;
s.engine = {'ge','rolsroyce'} ;

h = fopen('thisrun.m','w+') ;

mydisp = @(varargin)disp(transpose(varargin(:))) ; % dummy function body

vars = fields(s) ;
nv = numel(vars) ;

for ii = 1:nv
    if isnumeric(s.(vars{ii}))
        lb = '(' ;
        rb = ')' ;
    else
        lb = '{' ;
        rb = '}' ;
    end
    fprintf(h,'for i%g = 1:numel(s.(vars{%g})) \n',ii,ii) ;
    fprintf(h,'i%gval = s.(vars{%g})%si%g%s ; \n',ii,ii,lb,ii,rb) ;
end

fprintf(h,'mydisp(') ;
for ii = 1:numel(vars)
    fprintf(h,'i%gval',ii) ;
    if ii<nv
        fprintf(h,',') ;
    end
end
fprintf(h,') ; \n') ;

for ii = 1:nv
    fprintf(h,'end \n') ;
end

fclose(h) ;
run thisrun.m

Generated code (thisrun.m):

for i1 = 1:numel(s.(vars{1}))
    i1val = s.(vars{1})(i1) ;
    for i2 = 1:numel(s.(vars{2}))
        i2val = s.(vars{2})(i2) ;
        for i3 = 1:numel(s.(vars{3}))
            i3val = s.(vars{3}){i3} ;
            mydisp(i1val,i2val,i3val) ;
        end
    end
end

Result of running generated code:

>>
    [45000]    [10000]    'ge'

    [45000]    [10000]    'rolsroyce'

    [50000]    [10000]    'ge'

    [50000]    [10000]    'rolsroyce'

    [55000]    [10000]    'ge'

    [55000]    [10000]    'rolsroyce'

    [60000]    [10000]    'ge'

    [60000]    [10000]    'rolsroyce'

    [65000]    [10000]    'ge'

    [65000]    [10000]    'rolsroyce'

    [70000]    [10000]    'ge'

    [70000]    [10000]    'rolsroyce'

    [75000]    [10000]    'ge'

    [75000]    [10000]    'rolsroyce'

The code generation takes time, but if you need to run the file many times it might still be an efficient solution.

like image 168
transversality condition Avatar answered Nov 08 '22 05:11

transversality condition


Recursion.

You haven't put any speed requirements on this. This answer is slow, uses memory poorly, but is the simplest possible implementation of this idea. Many better implementations exist that are more complicated, use much less memory, and are significantly faster. Depends on how tight you need the inner loops to be...

parms.weight = [45000:5000:75000];
parms.alt    = 10000;
parms.Speed  = 0.2:0.1:0.9;

Then, define your simulator, as something like:

function result = simulation(parms)
    fieldNames = fieldnames(parms)
    result = [];
    for ix = 1 : numel(fieldNames)
       if 1 < numel(parms.(fieldNames{ix})) 
           list = parms.(fieldNames{ix});
           for jx = 1 : numel(list)
                tmpParms = parms;
                tmpParms.(fieldNames{ix}) = list(jx);
                tmpResult = simulation(tmpParms);
                result = [result; tmpResult];
           end
           return;
        end
     end

     if 0 == numel(result)
         % Do the real simulation here.
     end
like image 35
John Avatar answered Nov 08 '22 05:11

John


Doing nested for-loops for all parameters is the simple solution, constant parameters will simply result in a single iteration for that specific loop.

You could also use ndgrid to create a set of multi-dimensional arrays that are the parameters for each call of your computeFunction(...), and iterate over the ngrid's output instead of building nested for loops for each potentially variable parameter, if you expect to change the number of parameters.

However, be aware that it might come at a performance cost, as depending on what exactly you do in which matlab version, it might not benefit from the builtin optimizations the 'simple' for-loop version gets.

like image 41
dognotdog Avatar answered Nov 08 '22 06:11

dognotdog


Here's a recursive function that produces all possible sets of parameters as column vectors of an array:

function idx=dynloop(varargin)
    if nargin==1
        idx=varargin{1};
    else
        idx=dynloop(varargin{2:end});
        idx=[repelem(varargin{1},size(idx,2));
             repmat(idx,[1,length(varargin{1})])];
    end
return

Example output: loop over 5:7, then 8, then [2,3,5,7]:

 >> idx = dynloop(5:7,8,[2,3,5,7])

idx =

     5     5     5     5     6     6     6     6     7     7     7     7
     8     8     8     8     8     8     8     8     8     8     8     8
     2     3     5     7     2     3     5     7     2     3     5     7

The output is equivalent to that of ndgrid, (except in my opinion more intuitively shaped). (Also some preliminary benchmarking showed this may be slightly faster than ndgrid...?! To be determined)

Main loop: Now just use one loop to iterate through the columns of idx. Note this solution concept is similar to this solution, as commented by @Daniel on the OP.

Weight   = 45000:5000:75000;
Altitude = 10000;
Speed    = 0.2:0.1:0.9;

idx = dynloop(Weight,Altitude,Speed);

for ii=1:size(idx,2)
    params = idx(:,ii);

    %// do stuff with params! 
    %// ...

end

EDIT: to handle both string and numeric inputs

A simple mod to the original recursive function I posted allows it to return indices, rather than actual elements, so string cells are fine:

function idx=dynloop(varargin)
    if nargin==1
        idx=1:length(varargin{1});
    else
        idx=dynloop(varargin{2:end});
        idx=[repelem(1:length(varargin{1}),size(idx,2));
             repmat(idx,[1,length(varargin{1})]);];
    end
return

So altogether, your function would presumably operate like this:

function yourMainFcn(varargin)
    idx=dynloop(varargin{:});
    for ii=1:size(idx,2)
        params = cellfun(@(x,k) numORcell(x,k),...
                 varargin,num2cell(idx(:,ii).'),... %//'
                 'UniformOutput',0);

        %// do stuff with params! 
        %// ...
        disp(params)

    end
end

where the function numORcell appropriately parses numeric versus cell data:

function y=numORcell(x,k)
    if iscell(x)
        y=x{k};
    else
        y=x(k);
    end
end

Example with strings:

Weight   = 45000:5000:75000;
Altitude = 10000;
Speed    = 0.2:0.1:0.9;
Names    = {'Foo','Bar'};

>> yourMainFcn(Names,Altitude,Weight)
'Foo'    [10000]    [45000]

'Foo'    [10000]    [50000]

'Foo'    [10000]    [55000]

'Foo'    [10000]    [60000]

'Foo'    [10000]    [65000]

'Foo'    [10000]    [70000]

'Foo'    [10000]    [75000]

'Bar'    [10000]    [45000]

'Bar'    [10000]    [50000]

'Bar'    [10000]    [55000]

'Bar'    [10000]    [60000]

'Bar'    [10000]    [65000]

'Bar'    [10000]    [70000]

'Bar'    [10000]    [75000]

or instead:

>> yourMainFcn(Names,Names,Speed,Names)
'Foo'    'Foo'    [0.2]    'Foo'

'Foo'    'Foo'    [0.2]    'Bar'

'Foo'    'Foo'    [0.3]    'Foo'

'Foo'    'Foo'    [0.3]    'Bar'

'Foo'    'Foo'    [0.4]    'Foo'

...

'Foo'    'Foo'    [0.9]    'Bar'

'Foo'    'Bar'    [0.2]    'Foo'

...

'Foo'    'Bar'    [0.9]    'Bar'

'Bar'    'Foo'    [0.2]    'Foo'

...

'Bar'    'Foo'    [0.9]    'Bar'

...

'Bar'    'Bar'    [0.8]    'Foo'

'Bar'    'Bar'    [0.8]    'Bar'

'Bar'    'Bar'    [0.9]    'Foo'

'Bar'    'Bar'    [0.9]    'Bar'

Exercise left to the reader: If storing all the indices in idx is a memory issue, you must be doing some pretty heavy looping. Nonetheless, you can create a function that determines from the current index set the next lexicographical index set, meaning you would only have to store one set of indices and iterate at the end of each loop.

like image 42
Geoff Avatar answered Nov 08 '22 05:11

Geoff