The different ways of coding user functions used by CVODE
To solve a problem with CVODE different functions have to be provided. For example the right hand side of the ode has to be computed by the user and is the only mandatory function. Providing the Jacobian always help the solver when the BDF method is used, for example when the ode is stiff. Two other optional functions allow to handle events and intermediate callbacks (at each user prescribed or internal solver step). The usual way is to write functions in the Scilab language but if speed is a concern, entrypoints of dynamically linked shared libraries (DLL, built from C,C++ of Fortran) should be used instead, as at least one order of magnitude in speed is typically achieved that way (examples below use C). In that case all arguments are given via their address (Fortran convention).
Extra parameters can be passed to user Scilab functions or entrypoints by replacing the corresponding argument or option by a list, where the first element is the usual argument (a function identifier or a string with the entrypoint name) and the subsequent elements are the parameters to be passed after the mandatory arguments. Note that DLL entrypoints accept only one extra parameter as a double array.
In the call cvode(f,tspan,y0)
the first argument f
can be:
a function with prototype dydt=f(t,y,...)
function dydt=f(t, y) dydt = -y+1; end function dydt=fp(t, y, a, b) dydt = a*y+b; end [t,y] = cvode(f,[0 10],1); [t,y] = cvode(list(fp,-1,1),[0 10],1); | ![]() | ![]() |
a string, for example "dynrhs"
defining the name of a DLL entrypoint
with C prototype
void dynrhs(int *n,double *t,double y[],double dydt[])
The following example shows the complete workflow up to the final solver call:
rhscode=["void dynrhs(int *n,double *t,double y[],double ydot[])" "{" "ydot[0] = -y[0];" "}" "void dynrhspar(int *n,double *t,double y[],double ydot[], double par[])" "{" "ydot[0] = par[0]*y[0];" "}"] cd(TMPDIR) mputl(rhscode,"dynrhs.c") //create the C file ilib_for_link(["dynrhs","dynrhspar],"dynrhs.c",[],"c");//compile exec("loader.sce",-1) [t,y] = cvode("dynrhs",[0 10], 1); [t,y] = cvode(list("dynrhspar",-1),[0 10], 1); | ![]() | ![]() |
When the BDF method is chosen, Newton iterations are used and the Jacobian of the equation system is by default
approximated by finite differences. Iterations can be accelerated and precision can be improved by giving the true
Jacobian of the right hand side with respect to y with the jacobian
option. In the call
cvode(f,tspan,y0,jacobian=df)
, df
can be:
a function with prototype jac=df(t,y,...)
. For example for the Van Der Pol equation:
a matrix, which allows to easily consider the case of a linear ode, e.g.
function dydt=f(t, y, a) dydt=a*y; end A = [0 1;-1 0]; [t,y]=cvode(list(f,A),[0 10],[1;0],method="bdf",jacobian=A) | ![]() | ![]() |
a string giving the name of a DLL entrypoint with C prototype
void dynjac(int *n,double *t,double y[],double ydot[],double J[])
The current derivative ydot=f(t,y)
is passed by convenience as it may be usefull in the
computation. The Jacobian has to be stored column-wise in the J
array. Here is
a full example with right hand side and Jacobian entrypoints:
vdpcode=["void dynrhs(int *n,double *t,double *y,double *ydot,double *mu)" "{" "ydot[0] = y[1];" "ydot[1] = mu[0]*(1-y[0]*y[0])*y[1]-y[0];" "}" "void dynjac(int *n,double *t,double *y,double *ydot, double *J, double *mu)" "{" "J[0]=0; J[1]=-2.0*mu[0]*y[0]*y[1]-1.0;" "J[2]=1.0; J[3]=mu[0]*(1.0-y[0]*y[0]);" "}"]; cd(TMPDIR) mputl(vdpcode,"vdp.c") ilib_for_link(["dynrhs","dynjac"],"vdp.c",[],"c"); exec("loader.sce",-1) [t,y]=cvode(list("dynrhs",1),[0 10],[2;1],method="bdf",jacobian=list("dynjac",1)); | ![]() | ![]() |
In the call cvode(f,tspan,y0,events=g)
, g
can be:
a function with prototype [eq,term,dir] = g(t,y,...)
, as described in the Events section of the CVODE main page.
a string giving the name of a DLL entrypoint with C prototype
void dynevent(int *n,double *t,double y[], int *ng, double g[], int term[], int dir[])
When initializing the solver the entrypoint is called with *ng == 0
and *ng
must be set to the number of events before exiting. When g==NULL
the two arrays term
and dir
must be filled to define the termination
conditions and events directions (see the relevant values on the Events section of the
CVODE main page). Otherwise the entrypoint must return the event vector in the array g
.
See the example below:
eventcode=["void dynevent(int *n, double *t, double y[], int *ng, double g[], int term[], int dir[])" "{ " "if (*ng == 0) {" " *ng = 1; " "} else if (g == 0) {" " term[0] = 1; dir[0] = 1;" "} else {" " g[0] = y[0]-1.7;" "}}"]; cd(TMPDIR) mputl(eventcode,"dynevent.c") //create the C file ilib_for_link("dynevent","dynevent.c",[],"c");//compile exec("loader.sce",-1) [t,y,te,ye] = cvode(%cvode_vdp1,[0 10],[2;1],events="dynevent"); | ![]() | ![]() |
CVODE can call a Scilab function or DLL entryoint after every successfull internal or user prescribed step. In the
call cvode(f,tspan,y0, intcb=callback)
, callback
can be:
a function with prototype term = callback(t,y,flag,stats,...)
where flag
can take the values
"init"
, "step"
or "done"
and stats
is a structure gathering the solver statistics. The function has to return %f
to continue
integration whereas returning %t
will stop the solver. Here is a basic example displaying
the current time and solver step.
function out=report(t, y, flag, stats) mprintf("t=%f, %s, step=%f\n",t,flag,stats.hlast); out = %f; end [t,v] = cvode(%cvode_vdp1, [0 1], [0; 2], intcb=report); | ![]() | ![]() |
a string giving the name of a DLL entrypoint with prototype
int dyncb(int *n,double *t,double y[],int* flag)
Here is an example:
code=["#include ""sciprint.h""" "void dynrhs(int *n,double *t,double y[],double ydot[])" "{" "ydot[0] = -y[0]*y[0];" "}" "int dyncb(int *n,double *t,double y[],int *flag)" "{" "sciprint(""t=%f, y=%f, flag=%d\n"",*t, y[0], *flag);" "return 0;" "}"]; cd(TMPDIR) mputl(code,"dynrhscb.c") //create the C file ilib_for_link(["dynrhs","dyncb"],"dynrhscb.c",[],"c");//compile exec("loader.sce",-1) [t,y] = cvode("dynrhs",[0 1],1,intcb="dyncb"); | ![]() | ![]() |
The entrypoint has to return 0 or 1, for normal continuation or solver termination, respectively. The flag argument takes values -1,0 or 1 for "init", "step" and "done" solver phases.