CasADi codegen and S-Functions
Estimated reading time: 3 minutesWhile 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
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.