CasADi codegen and S-Functions

Estimated reading time: 3 minutes

While the user guide does explain code-generation in full detail, it is handy to have a demonstration in a real environment like Matlab's S-functions.

The problem

We will design a Simulink block that implements a nonlinear mapping from ($\mathbf{R}^2$, $\mathbf{R}$) to ($\mathbf{R}$,$\mathbf{R}^2$):

import casadi.*

x = MX.sym('x',2);
y = MX.sym('y');

w = dot(x,y*x);
z = sin(x)+y+w;

f = Function('f',{x,y},{w,z});

Code-generating

You may generate code from this with:

f.generate('f.c')

However, we'll use the more advanced syntax since we have advanced requirements. In particular, we will use Matlab's data-types for real and integer numbers, requiring us to include a header:

cg_options = struct;
cg_options.casadi_real = 'real_T';
cg_options.casadi_int = 'int_T';
cg_options.with_header = true;
cg = CodeGenerator('f',cg_options);
cg.add_include('simstruc.h');
cg.add(f);
cg.generate();

This will create f.c, and also f.h (since we set the option with_header).

S-Function initialize routine

Our S-Function code should include the header f.h. With it, we have access to the problem dimensions:

int_T n_in  = f_n_in();
int_T n_out = f_n_out();

Next, we can set the block's input/output port dimensions:

int_T i;
if (!ssSetNumInputPorts(S, n_in)) return;
for (i=0;i<n_in;++i) {
  const int_T* sp = f_sparsity_in(i);
  /* Dense vector inputs assumed here */
  ssSetInputPortWidth(S, i, sp[0]);
  ssSetInputPortDirectFeedThrough(S, i, 1);
}

if (!ssSetNumOutputPorts(S, n_out)) return;
for (i=0;i<n_out;++i) {
  const int_T* sp = f_sparsity_out(i);
  /* Dense vector outputs assumed here */
  ssSetOutputPortWidth(S, i, sp[0]);
}

CaADi codegenerated functions require working memory to evaluate. We can query the size requirements with:

int_T sz_arg, sz_res, sz_iw, sz_w;
f_work(&sz_arg, &sz_res, &sz_iw, &sz_w);

Our notion of workspaces maps easily to Simulink's:

ssSetNumRWork(S, sz_w);
ssSetNumIWork(S, sz_iw);
ssSetNumPWork(S, sz_arg+sz_res);

The only issue is that we differentiate between pointers for the arguments and the results, while they are combined into a generic void* buffer in Simulink.

S-Function output routine

First, we need access to the working memory. Simple enough for the real and integer workspaces:

    real_T* w = ssGetRWork(S);
    int_T* iw = ssGetIWork(S);

For arg and res we have to perform arithmatic and casting from void** to the desired types:

    void** p = ssGetPWork(S);
    const real_T** arg = (const real_T**) p;
    p += sz_arg;
    real_T** res = (real_T**) p;

Next, make arg point to the input data:

    for (i=0; i<n_in;++i) {
      arg[i] = *ssGetInputPortRealSignalPtrs(S,i);
    }

Make res point to the output data:

    for (i=0; i<n_out;++i) {
      res[i] = ssGetOutputPortRealSignal(S,i);
    }

Finally, run the CasADi function:

    f(arg,res,iw,w,0);

Running

To actually run the block, we need to compile our S-Function:

mex s_function.c f.c

View of simulink diagram with our S-Function block in it

Numeric simulation result

Download code: do_demo.m, s_function.c, demo.slx.

In summary, we've shown how to use CasADi codegen in general, and in the setting of Simulink S-Functions specifically.