When tasked with performing many finite element simulations, for example in optimization studies or performing parametric studies over several variables as described in this post, one can speed up the overall process significantly by performing the simulations in parallel. This post describes an automated way to do this using the Unix xargs tool with a bash command script.

It is of course easy to simply run simulations in sequence with for loops, but as modern machines often have two or more CPU cores one can significantly reduce the total simulation time running the jobs in parallel. The script described in the following will run on any Linux or Unix machine with bash and xargs installed including Windows systems with the Ubuntu on Windows shell (available with the latest update). Matlab or Octave is also a requirement of course.

In the setup and configuration of the script the total number of parallel runs and the number of simultaneous number of parallel processes is defined (the latter typically taken as the number or CPU cores available).

#!/bin/bash
n_parruns=16   # Total number of jobs/runs.
n_parproc=3    # Number of simultaneous parallel processes.

Also the path to the Octave or Matlab binary executable file has to be specified, as well as a Matlab m-script file to run, in this case simply called mscript.m.

export runcmd='/mnt/c/Octave/Octave-4.2.1/bin/octave-cli.exe'
export mscript=mscript.m

Next a global function is defined which will be called for each parallel run and start its own Matlab/Octave instance to run a simulation in.

function parfun
{
    echo parproc$1-$$: start   # Terminal output.

    # Write parproc file to temporary (current) directory.
    tempdir=$(pwd)
    echo > "$tempdir/parproc$1-$$"

    # Octave/Matlab command string.
    cmdstr="i_parrun = $1;
            $(sed -n '/exit/!p;//q' $mscript)
            delete([pwd,filesep,'parproc$1-$$']);"

    echo "$cmdstr" > mscript_$1.m
    eval $runcmd" mscript_$1.m"

    # Sleep while parproc file exists.
    while [ -f "$tempdir/parproc$1-$$" ]
    do
        sleep 1
    done

    # Cleanup.
    rm -f mscript_$1.m
    rm -f parproc$1-$$

    echo parproc$1-$$: end   # Terminal output.
}
export -f parfun

Lastly, xargs is called which automatically handles the batch processing, that is to run n_parproc m-scripts in parallel starting new jobs as running ones have finished.

eval "printf \"%i\n\" {1..$n_parruns}" | xargs -n 1 -P $n_parproc -I {} bash -c 'parfun "$@"' _ {}

The whole script supporting both Matlab and Octave calls can be downloaded from this link

Matlab and Octave Parallel Job Script


As for the m-script file, it can contain any valid Matlab code including FEATool functions (just make use Matlab/Octave can find FEATool in the paths). The important thing to note is that each instance has access to a variable i_parrun containing the parallel job number (1-n_parruns). This variable can in turn be used to select and set simulation and processing parameters.

For example, the following m-script can be used to run a parametric study of the hole in plate plane-stress model, varying both the hole diameter and plate thickness

disp(['parallel simulation run ',num2str(i_parrun)])

i_cnt = 0;
for diam = [0.01 0.02 0.03 0.05]
  for thick = [0.001 0.0015 0.002 0.004]

    i_cnt = i_cnt + 1;
    if( i_parrun==i_cnt )   % Only run the simulation for i_parrun
      [fea,out] = ex_planestress1( 'diam', diam, 'thick', thick, 'iplot', 0 );
      eval( ['fea',num2str(i_parrun),' = fea;'] )
      save( ['fea',num2str(i_parrun)], ['fea',num2str(i_parrun)] )
    end

  end
end

Once all the simulation runs have finished one can collect, postprocess and visualize the results, for example

diam  = [0.01 0.02 0.03 0.05];
thick = [0.001 0.0015 0.002 0.004];
s_sx  = '200e9/(1-0.3^2)*ux + 0.3*200e9*vy';
for i=1:length(diam)
  for j=1:length(thick)
    i_cnt = 4*(i-1) + j;

    load( ['fea',num2str(i_cnt)] )
    eval( ['fea = fea',num2str(i_cnt),';'] )

    [~,sx_max(i,j)] = minmaxsubd( s_sx, fea );
  end
end

bar3(sx_max*1e-6)
xlabel( 'Thickness [mm]' )
set( gca, 'xticklabel', regexp(num2str(thick*1e3),'\s+','split') )
ylabel( 'Diameter [mm]' )
set( gca, 'yticklabel', regexp(num2str(diam*1e3),'\s+','split') )
zlabel( 'Maximum stress [GPa]' )

FEATool Multiphysics - Parallel Parametric Stress Strain Analysis

In this particular case we can see that the maximum stress is found with the largest diameter hole and thinnest plate as expected due to the least amount of material left.