Main Content

Using Dynamic Memory Allocation for an Atoms Simulation

This example shows how to generate code for a MATLAB algorithm that runs a simulation of bouncing "atoms" and returns the result after a number of iterations. There are no upper bounds on the number of atoms that the algorithm accepts, so this example takes advantage of dynamic memory allocation.

Prerequisites

There are no prerequisites for this example.

About the run_atoms Function

The run_atoms.m function runs a simulation of bouncing atoms (also applying gravity and energy loss).

help run_atoms
  atoms = run_atoms(atoms,n)
  atoms = run_atoms(atoms,n,iter)
  Where 'atoms' the initial and final state of atoms (can be empty)
        'n' is the number of atoms to simulate.
        'iter' is the number of iterations for the simulation
           (if omitted it is defaulted to 3000 iterations.)

Set Up Code Generation Options

Create a code generation configuration object

cfg = coder.config;
% Enable dynamic memory allocation for variable size matrices.
cfg.DynamicMemoryAllocation = 'AllVariableSizeArrays';

Set Up Example Inputs

Create a template structure 'Atom' to provide the compiler with the necessary information about input parameter types. An atom is a structure with four fields (x,y,vx,vy) specifying position and velocity in Cartesian coordinates.

atom = struct('x', 0, 'y', 0, 'vx', 0, 'vy', 0);

Generate a MEX Function for Testing

Use the command codegen with the following arguments:

-args {coder.typeof(atom, [1 Inf]),0,0} indicates that the first argument is a row vector of atoms where the number of columns is potentially infinite. The second and third arguments are scalar double values.

-config cfg enables dynamic memory allocation, defined by workspace variable cfg

codegen run_atoms -args {coder.typeof(atom, [1 Inf]),0,0} -config cfg -o run_atoms_mex

Run the MEX Function

The MEX function simulates 10000 atoms in approximately 1000 iteration steps given an empty list of atoms. The return value is the state of all the atoms after simulation is complete.

atoms = repmat(atom,1,0); 
atoms = run_atoms_mex(atoms,10000,1000)
Iteration: 50
Iteration: 100
Iteration: 150
Iteration: 200
Iteration: 250
Iteration: 300
Iteration: 350
Iteration: 400
Iteration: 450
Iteration: 500
Iteration: 550
Iteration: 600
Iteration: 650
Iteration: 700
Iteration: 750
Iteration: 800
Iteration: 850
Iteration: 900
Iteration: 950
Iteration: 1000
Completed iterations: 1000
atoms=1×10000 struct array with fields:
    x
    y
    vx
    vy

Run the MEX Function Again

Continue the simulation with another 500 iteration steps

atoms = run_atoms_mex(atoms,10000,500)
Iteration: 50
Iteration: 100
Iteration: 150
Iteration: 200
Iteration: 250
Iteration: 300
Iteration: 350
Iteration: 400
Iteration: 450
Iteration: 500
Completed iterations: 500
atoms=1×10000 struct array with fields:
    x
    y
    vx
    vy

Generate a Standalone C Code Library

To generate a C library, create a standard configuration object for libraries:

cfg = coder.config('lib');

Enable dynamic memory allocation

cfg.DynamicMemoryAllocation = 'AllVariableSizeArrays';

In MATLAB the default data type is double. However, integers are usually used in C code, so pass int32 integer example values to represent the number of atoms and iterations.

codegen run_atoms -args {coder.typeof(atom, [1 Inf]),int32(0),int32(0)} -config cfg

Inspect Generated Code

When creating a library the code is generated in the folder codegen/lib/run_atoms/. The code in this folder is self contained. To interface with the compiled C code you need only the generated header files and the library file.

dir codegen/lib/run_atoms
.                       rtGetNaN.o              run_atoms_emxAPI.o      
..                      rt_nonfinite.c          run_atoms_emxutil.c     
.gitignore              rt_nonfinite.h          run_atoms_emxutil.h     
buildInfo.mat           rt_nonfinite.o          run_atoms_emxutil.o     
codeInfo.mat            rtw_proj.tmw            run_atoms_initialize.c  
codedescriptor.dmr      rtwtypes.h              run_atoms_initialize.h  
compileInfo.mat         run_atoms.a             run_atoms_initialize.o  
defines.txt             run_atoms.c             run_atoms_ref.rsp       
examples                run_atoms.h             run_atoms_rtw.mk        
interface               run_atoms.o             run_atoms_terminate.c   
rtGetInf.c              run_atoms_data.c        run_atoms_terminate.h   
rtGetInf.h              run_atoms_data.h        run_atoms_terminate.o   
rtGetInf.o              run_atoms_data.o        run_atoms_types.h       
rtGetNaN.c              run_atoms_emxAPI.c      
rtGetNaN.h              run_atoms_emxAPI.h      

Write a C Main Function

Typically, the main function is platform-dependent code that performs rendering or some other processing. In this example, a pure ANSI-C function produces a file run_atoms_state.m which (when run) contains the final state of the atom simulation.

type run_atoms_main.c
/* Include standard C libraries */
#include <stdio.h>

/* The interface to the main function we compiled. */
#include "codegen/exe/run_atoms/run_atoms.h"

/* The interface to EMX data structures. */
#include "codegen/exe/run_atoms/run_atoms_emxAPI.h"

int main(int argc, char **argv)
{
    FILE *fid;
    int i;
    emxArray_Atom *atoms;
 
    /* Main arguments unused */
    (void) argc;
    (void) argv;
    
    /* Initially create an empty row vector of atoms (1 row, 0 columns) */
    atoms = emxCreate_Atom(1, 0);
    
    /* Call the function to simulate 10000 atoms in 1000 iteration steps */
    run_atoms(atoms, 10000, 1000);
    
    /* Call the function again to do another 500 iteration steps */
    run_atoms(atoms, 10000, 500);
    
    /* Print the result to a file */
    fid = fopen("atoms_state.txt", "w");
    for (i = 0; i < atoms->size[1]; i++) {
        fprintf(fid, "%f %f %f %f\n",
            atoms->data[i].x, atoms->data[i].y, atoms->data[i].vx, atoms->data[i].vy);
    }
    
    /* Close the file */
    fclose(fid);
    
    /* Free memory */
    emxDestroyArray_Atom(atoms);
    return(0);
}

Create a Configuration Object for Executables

cfg = coder.config('exe');
cfg.DynamicMemoryAllocation = 'AllVariableSizeArrays';

Generate a Standalone Executable

You must pass the function (run_atoms.m) as well as custom C code (run_atoms_main.c) The codegen command automatically generates C code from the MATLAB code, then calls the C compiler to bundle this generated code with the custom C code (run_atoms_main.c).

codegen run_atoms run_atoms_main.c -args {coder.typeof(atom, [1 Inf]),int32(0),int32(0)} -config cfg

Run the Executable

After simulation is complete, this produces the file atoms_state.txt. The TXT file is a 10000x4 matrix, where each row is the position and velocity of an atom (x, y, vx, vy) representing the current state of the whole system.

system(['.' filesep 'run_atoms']);

Fetch the State

Running the executable produced atoms_state.txt. Now, recreate the structure array from the saved matrix:

load atoms_state.txt -ascii
clear atoms
for i = 1:size(atoms_state,1)
    atoms(1,i).x  = atoms_state(i,1);
    atoms(1,i).y  = atoms_state(i,2);
    atoms(1,i).vx = atoms_state(i,3);
    atoms(1,i).vy = atoms_state(i,4);
end

Render the State

Call run_atoms_mex with zero iterations to render only.

run_atoms_mex(atoms, 10000, 0);