Compiled Code
Compiled Code
Compiled Code
Languages
Abstract
This document describes how to use the deSolve package (Soetaert, Petzoldt, and
Setzer 2010a) to solve models that are written in FORTRAN or C.
1. Introduction
deSolve (Soetaert et al. 2010a; Soetaert, Petzoldt, and Setzer 2010b), the successor of R
package odesolve (Setzer 2001) is a package to solve ordinary differential equations (ODE),
differential algebraic equations (DAE) and partial differential equations (PDE). One of the
prominent features of deSolve is that it allows specifying the differential equations either as:
In what follows, these implementations will be referred to as R models and DLL models
respectively. Whereas R models are easy to implement, they allow simple interactive devel-
opment, produce highly readible code and access to Rs high-level procedures, DLL models
have the benefit of increased simulation speed. Depending on the problem, there may be a
gain of up to several orders of magnitude computing time when using compiled code.
Here are some rules of thumb when it is worthwhile or not to switch to DLL models:
• As long as one makes use only of Rs high-level commands, the time gain will be modest.
This was demonstrated in Soetaert et al. (2010a), where a formulation of two interacting
populations dispersing on a 1-dimensional or a 2-dimensional grid led to a time gain of
a factor two only when using DLL models.
• Generally, the more statements in the model, the higher will be the gain of using com-
piled code. Thus, in the same paper (Soetaert et al. 2010a), a very simple, 0-D, Lotka-
Volterrra type of model describing only 2 state variables was solved 50 times faster when
using compiled code.
2 R Package deSolve, Writing Code in Compiled Languages
• As even R models are quite performant, the time gain induced by compiled code will
often not be discernible when the model is only solved once (who can grasp the difference
between a run taking 0.001 or 0.05 seconds to finish). However, if the model is to be
applied multiple times, e.g. because the model is to be fitted to data, or its sensitivity is
to be tested, then it may be worthwhile to implement the model in a compiled language.
Starting from deSolve version 1.4, it is now also possible to use forcing functions in compiled
code. These forcing functions are automatically updated by the integrators. See last chapter.
dy1
= −k1 · y1 + k2 · y2 · y3
dt
dy2
= k1 · y1 − k2 · y2 · y3 − k3 · y2 · y2
dt
dy3
= k3 · y2 · y2
dt
where t is the current time point in the integration, y is the current estimate of the variables in
the ODE system, and parms is a vector or list containing the parameter values. The optional
dots argument (...) can be used to pass any other arguments to the function. The return
value of func should be a list, whose first element is a vector containing the derivatives of y
with respect to time, and whose next elements contain output variables that are required at
each point in time.
The R implementation of the simple ODE is given below:
+
+ list(c(dy1, dy2, dy3))
+ })
+ }
′
The Jacobian ( ∂y
∂y ) associated to the above example is:
/* file mymod.c */
#include <R.h>
static double parms[3];
4 R Package deSolve, Writing Code in Compiled Languages
#define k1 parms[0]
#define k2 parms[1]
#define k3 parms[2]
/* initializer */
void initmod(void (* odeparms)(int *, double *))
{
int N=3;
odeparms(&N, parms);
}
yout[0] = y[0]+y[1]+y[2];
}
1. After defining the parameters in global C-variables, through the use of #define state-
ments, a function called initmod initialises the parameter values, passed from the R-
code.
This function has as its sole argument a pointer to C-function odeparms that fills a
double array with double precision values, to copy the parameter values into the global
variable.
Karline Soetaert, Thomas Petzoldt, R. Woodrow Setzer 5
2. Function derivs then calculates the values of the derivatives. The derivative function
is defined as:
void derivs (int *neq, double *t, double *y, double *ydot,
double *yout, int *ip)
where *neq is the number of equations, *t is the value of the independent variable, *y
points to a double precision array of length *neq that contains the current value of the
state variables, and *ydot points to an array that will contain the calculated derivatives.
*yout points to a double precision vector whose first nout values are other output
variables (different from the state variables y), and the next values are double precision
values as passed by parameter rpar when calling the integrator. The key to the elements
of *yout is set in *ip
*ip points to an integer vector whose length is at least 3; the first element (ip[0])
contains the number of output values (which should be equal or larger than nout),
its second element contains the length of *yout, and the third element contains the
length of *ip; next are integer values, as passed by parameter ipar when calling the
integrator.1
Note that, in function derivs, we start by checking whether enough memory is allo-
cated for the output variables (if (ip[0] < 1)), else an error is passed to R and the
integration is stopped.
where *ml and *mu are the number of non-zero bands below and above the diagonal of
the Jacobian respectively. These integers are only relevant if the option of a banded
Jacobian is selected. *nrow contains the number of rows of the Jacobian. Only for full
Jacobian matrices, is this equal to *neq. In case the Jacobian is banded, the size of
*nrowpd depends on the integrator. If the method is one of lsode, lsoda, vode, then
*nrowpd will be equal to *mu + 2 * *ml + 1, where the last *ml rows should be filled
with 0s.
For radau, *nrowpd will be equal to *mu + *ml + 1
See example “odeband” in the directory doc/examples/dynload, and chapter 4.6.
c file mymodf.f
subroutine initmod(odeparms)
external odeparms
double precision parms(3)
common /myparms/parms
pd(1,1) = -k1
pd(2,1) = k1
pd(3,1) = 0.0
pd(1,2) = k2*y(3)
pd(2,2) = -k2*y(3) - 2*k3*y(2)
pd(3,2) = 2*k3*y(2)
pd(1,3) = k2*y(2)
pd(2,3) = -k2*y(2)
pd(3,3) = 0.0
return
end
c end of file mymodf.f
In FORTRAN, parameters may be stored in a common block (here called myparms). During
the initialisation, this common block is defined to consist of a 3-valued vector (unnamed), but
in the subroutines derivs and jac, the parameters are given a name (k1, ...).
Karline Soetaert, Thomas Petzoldt, R. Woodrow Setzer 7
dyn.load("mymod.dll")
and in unix:
dyn.load("mymod.so")
The integration routine (here ode) recognizes that the model is specified as a DLL due to
the fact that arguments func and jacfunc are not regular R-functions but character strings.
Thus, the integrator will check whether the function is loaded in the DLL with name mymod.
Note that mymod, as specified by dllname gives the name of the shared library without exten-
sion. This DLL should contain all the compiled function or subroutine definitions referred to
in func, jacfunc and initfunc.
Also, if func is specified in compiled code, then jacfunc and initfunc (if present) should
also be specified in a compiled language. It is not allowed to mix R-functions and compiled
functions.
2
This requires a correctly installed GNU compiler, see above.
8 R Package deSolve, Writing Code in Compiled Languages
Note also that, when invoking the integrator, we have to specify the number of ordinary
output variables, nout. This is because the integration routine has to allocate memory to
pass these output variables back to R. There is no way to check for the number of output
variables in a DLL automatically. If in the calling of the integration routine the number
of output variables is too low, then R may freeze and need to be terminated! Therefore it
is advised that one checks in the code whether nout has been specified correctly. In the
FORTRAN example above, the statement if (ip(1) < 1) call rexit("nout should be
at least 1") does this. Note that it is not an error (just a waste of memory) to set nout to
a too large value.
Finally, in order to label the output matrix, the names of the ordinary output variables have
to be passed explicitly (outnames). This is not necessary for the state variables, as their
names are known through their initial condition (y).
That is, initmod has a single argument, a pointer to a function that has as arguments a
pointer to an int and a pointer to a double. In FORTRAN, the initialization routine has a
single argument, a subroutine declared to be external. The name of the initialization function
is passed as an argument to the deSolve solver functions.
In C, two approaches are available for making the values passed in parms visible to the model
routines, while only the simpler approach is available in FORTRAN. The simpler requires
that parms be a numeric vector. In C, the function passed from deSolve to the initialization
function (called odeparms in the example) copies the values from the parameter vector to a
static array declared globally in the file where the model is defined. In FORTRAN, the values
are copied into a COMMON block.
It is possible to pass more complicated structures to C functions. Here is an example, an ini-
tializer called deltamethrin from a model describing the pharmacokinetics of that pesticide:
#include <R.h>
#include <Rinternals.h>
Karline Soetaert, Thomas Petzoldt, R. Woodrow Setzer 9
#include <R_ext/Rdynload.h>
#include "deltamethrin.h"
/* initializer */
void deltamethrin(void(* odeparms)(int *, double *))
{
int Nparms;
DL_FUNC get_deSolve_gparms;
SEXP gparms;
get_deSolve_gparms = R_GetCCallable("deSolve","get_deSolve_gparms");
gparms = get_deSolve_gparms();
Nparms = LENGTH(gparms);
if (Nparms != N_PARMS) {
PROBLEM "Confusion over the length of parms"
ERROR;
} else {
_RDy_deltamethrin_parms = REAL(gparms);
}
}
#define N_PARMS 63
static double *_RDy_deltamethrin_parms;
The critical element of this method is the function R_GetCCallable which returns a function
(called get_deSolve_gparms in this implementation) that returns the parms argument as a
SEXP data type. In this example, parms was just a real vector, but in principle, this method
can handle arbitrarily complex objects. For more detail on handling R objects in native code,
see R Development Core Team (2008).
• vode, zvode,
• daspk,
• radau,
For some of these solvers the interface is slightly different (e.g. zvode, daspk), while in
others (lsodar, lsodes) different functions can be defined. How this is implemented in a
compiled language is discussed next.
dz
=i·z
dt
dw
= −i · w · w · z
dt
where
w(0) = 1/2.1 + 0i
z(0) = 1i
YDOT(1) = CMP*Y(1)
YDOT(2) = -CMP*Y(2)*Y(2)*Y(1)
RETURN
END
CMP = DCMPLX(0.0D0,1.0D0)
PD(2,3) = -2.0D0*CMP*Y(1)*Y(2)
PD(2,1) = -CMP*Y(2)*Y(2)
PD(1,1) = CMP
RETURN
END
Assuming this code has been compiled and is in a DLL called "zvodedll.dll", this model is
solved in R as follows:
dyn.load("zvodedll.dll")
outF <- zvode(func = "fex", jacfunc = "jex", y = yini, parms = NULL,
times = times, atol = 1e-10, rtol = 1e-10, dllname = "zvodedll",
initfunc = NULL)
Note that in R names of FORTRAN DLL functions (e.g. for func and jacfunc) have to be
given in lowercase letters, even if they are defined upper case in FORTRAN.
Also, there is no initialiser function here (initfunc = NULL).
0 = F (t, y, y ′ , p)
i.e. the DAE function (passed via argument res) specifies the “residuals” rather than the
derivatives (as for ODEs).
Consequently the DAE function specification in a compiled language is also different. For
code written in C, the calling sequence for res must be:
where *t is the value of the independent variable, *y points to a double precision vector that
contains the current value of the state variables, *ydot points to an array that will contain the
derivatives, *delta points to a vector that will contain the calculated residuals. *cj points
to a scalar, which is normally proportional to the inverse of the stepsize, while *ires points
to an integer (not used). *yout points to any other output variables (different from the state
variables y), followed by the double precision values as passed via argument rpar; finally *ip
is an integer vector containing at least 3 elements, its first value (*ip[0]) equals the number
of output variables, calculated in the function (and which should be equal to nout), its second
element equals the total length of *yout, its third element equals the total length of *ip, and
finally come the integer values as passed via argument ipar.
For code written in FORTRAN, the calling sequence for res must be as in the following
example:
12 R Package deSolve, Writing Code in Compiled Languages
Similarly as for the ODE model discussed above, the parameters are kept in a common block
which is initialised by an initialiser subroutine:
subroutine initpar(daspkparms)
external daspkparms
integer, parameter :: N = 4
double precision parms(N)
common /myparms/parms
call daspkparms(N, parms)
return
end
void derivs (int *neq, double *t, double *y, double *ydot,
double *yout, int *ip)
and
[,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
[1,] 1 0 0 0 0e+00 0e+00 0e+00 0e+00 0 0
[2,] 0 1 0 0 0e+00 0e+00 0e+00 0e+00 0 0
[3,] 0 0 1 0 0e+00 0e+00 0e+00 0e+00 0 0
[4,] 0 0 0 1 0e+00 0e+00 0e+00 0e+00 0 0
[5,] 0 0 0 0 5e-04 0e+00 0e+00 0e+00 0 0
[6,] 0 0 0 0 0e+00 5e-04 0e+00 0e+00 0 0
[7,] 0 0 0 0 0e+00 0e+00 5e-04 0e+00 0 0
[8,] 0 0 0 0 0e+00 0e+00 0e+00 5e-04 0 0
[9,] 0 0 0 0 0e+00 0e+00 0e+00 0e+00 0 0
[10,] 0 0 0 0 0e+00 0e+00 0e+00 0e+00 0 0
c----------------------------------------------------------------
c Initialiser for parameter common block
c----------------------------------------------------------------
subroutine initcaraxis(daeparms)
Karline Soetaert, Thomas Petzoldt, R. Woodrow Setzer 15
xl yl
0.5000
0.04
0.4985
0.00
−0.04
0.4970
0.0 0.5 1.0 1.5 2.0 2.5 3.0 0.0 0.5 1.0 1.5 2.0 2.5 3.0
time time
xr yr
0.60
1.02
0.50
0.98
0.40
0.94
0.0 0.5 1.0 1.5 2.0 2.5 3.0 0.0 0.5 1.0 1.5 2.0 2.5 3.0
time time
external daeparms
integer, parameter :: N = 8
double precision parms(N)
common /myparms/parms
c----------------------------------------------------------------
c rate of change
c----------------------------------------------------------------
subroutine caraxis(neq, t, y, ydot, out, ip)
implicit none
integer neq, IP(*)
double precision t, y(neq), ydot(neq), out(*)
double precision eps, M, k, L, L0, r, w, g
common /myparms/ eps, M, k, L, L0, r, w, g
double precision xl, yl, xr, yr, ul, vl, ur, vr, lam1, lam2
double precision yb, xb, Ll, Lr, dxl, dyl, dxr, dyr
double precision dul, dvl, dur, dvr, c1, c2
ur = y(7)
vr = y(8)
lam1 = y(9)
lam2 = y(10)
yb = r * sin(w * t)
xb = sqrt(L * L - yb * yb)
Ll = sqrt(xl**2 + yl**2)
Lr = sqrt((xr - xb)**2 + (yr - yb)**2)
dxl = ul
dyl = vl
dxr = ur
dyr = vr
c1 = xb * xl + yb * yl
c2 = (xl - xr)**2 + (yl - yr)**2 - L * L
Assuming that the code is in file “radaudae.f”, this model is compiled, loaded and solved in
R as:
outDLL <- radau(y = yini, mass = Mass, times = times, func = "caraxis",
initfunc = "initcaraxis", parms = parameter,
dllname = "radaudae", nind = index)
Karline Soetaert, Thomas Petzoldt, R. Woodrow Setzer 17
void myroot(int *neq, double *t, double *y, int *ng, double *gout,
double *out, int *ip )
where *neq and *ng are the number of state variables and root functions respectively, *t is
the value of the independent variable, y points to a double precision array that contains the
current value of the state variables, and gout points to an array that will contain the values
of the constraint function whose root is sought. *out and *ip are a double precision and
integer vector respectively, as described in the ODE example above.
For code written in FORTRAN, the calling sequence for rootfunc must be as in following
example:
return
end
c Rate of change
subroutine derivsband (neq, t, y, ydot,out,IP)
integer neq, IP(*)
DOUBLE PRECISION T, Y(5), YDOT(5), out(*)
PD(:,:) = 0.D0
PD(1,1) = 0.D0
PD(1,2) = -.02D0
PD(1,3) = -.02D0
PD(1,4) = -.02D0
PD(1,5) = -.02D0
PD(2,:) = 0.1D0
PD(3,1) = -0.3D0
PD(3,2) = -0.3D0
PD(3,3) = -0.3D0
PD(3,4) = -0.3D0
PD(3,5) = 0.D0
RETURN
END
Assuming that this code is in file "odeband.f", we compile from within R and load the shared
library (assuming the working directory holds the source file) with:
This will work both for the lsode family as for radau. In the first case, when entering
subroutine jacband, nrowpd will have the value 5, in the second case, it will be equal to 4.
20 R Package deSolve, Writing Code in Compiled Languages
• DLLfunc to test the implementation of the derivative function as used in ODEs. This
function returns the derivative dy
dt and the output variables.
• DLLres to test the implementation of the residual function as used in DAEs. This
function returns the residual function dy
dt − f (y, t) and the output variables.
These functions serve no other purpose than to test whether the compiled code returns what
it should.
5.1. DLLfunc
We test whether the ccl4 model, which is part of deSolve package, returns the proper rates
of changes. (Note: see example(ccl4model) for a more comprehensive implementation)
$dy
AI AAM AT AF AL
-20.0582048 6.2842256 9.4263383 0.9819102 2.9457307
CLT AM
0.0000000 0.0000000
$var
DOSE MASS CP
1.758626 0.000000 922.727067
5.2. DLLres
The deSolve package contains a FORTRAN implementation of the chemical model described
above (section 4.1), where the production rate is included as a forcing function (see next
section).
Here we use DLLres to test it:
$delta
A B D.K
0.00 -3.00 0.12
$var
CONC Prod
11.00 0.12
R>
• They are initialised by means of an initialiser function. Its name should be passed to
the solver via argument initforc.
Similar as the parameter initialiser function, the function denoted by initforc has as
its sole argument a pointer to the vector that contains the forcing funcion values in the
compiled code. In case of C code, this will be a global vector; in case of FORTRAN,
this will be a vector in a common block.
The solver puts a pointer to this vector and updates the forcing functions in this memory
area at each time step. Hence, within the compiled code, forcing functions can be
assessed as if they are parameters (although, in contrast to the latter, their values will
generally change). No need to update the values for the current time step; this has been
done before entering the derivs function.
• The forcing function data series are passed to the integrator, via argument forcings;
if there is only one forcing function data set, then a 2-columned matrix (time, value)
will do; else the data should be passed as a list, containing (time, value) matrices with
the individual forcing function data sets. Note that the data sets in this list should be
in the same ordering as the declaration of the forcings in the compiled code.
A number of options allow to finetune certain settings. They are in a list called fcontrol
which can be supplied as argument when calling the solvers. The options are similar to the
arguments from R function approx, howevers the default settings are often different.
The following options can be specified:
22 R Package deSolve, Writing Code in Compiled Languages
• method specifies the interpolation method to be used. Choices are "linear" or "constant",
the default is "linear", which means linear interpolation (same as approx)
• rule, an integer describing how interpolation is to take place outside the interval
[min(times), max(times)]. If rule is 1 then an error will be triggered and the cal-
culation will stop if extrapolation is necessary. If it is 2, the default, the value at the
closest data extreme is used, a warning will be printed if verbose is TRUE.
Note that the default differs from the approx default.
• ties, the handling of tied times values. Either a function with a single vector argument
returning a single number result or the string "ordered".
Note that the default is "ordered", hence the existence of ties will NOT be investigated;
in practice this means that, if ties exist, the first value will be used; if the dataset is not
ordered, then nonsense will be produced.
Alternative values for ties are mean, min etc... which will average, or take the minimal
value if multiple values exist at one time level.
dC
= F luxt − k · C
dt
with initial condition C(t = 0) = C0 ; the latter is estimated as the mean of the flux divided
by the decay rate.
The FORTRAN code looks like this:
external odeparms
integer N
double precision parms(2)
common /myparms/parms
N = 1
call odeparms(N, parms)
return
end
external odeforcs
integer N
double precision forcs(1)
common /myforcs/forcs
N = 1
call odeforcs(N, forcs)
return
end
out(1)= k*y(1)
out(2)= depo
return
end
Here the subroutine scocpar is business as usual; it initialises the parameter common block
(there is only one parameter). Subroutine odeforcs does the same for the forcing function,
which is also positioned in a common block, called myforcs. This common block is made
available in the derivative subroutine (here called scocder), where the forcing function is
24 R Package deSolve, Writing Code in Compiled Languages
named depo.
At each time step, the integrator updates the value of this forcing function to the correct time
point. In this way, the forcing functions can be used as if they are (time-varying) parameters.
All that’s left to do is to pass the forcing function data set and the name of the forcing
function initialiser routine. This is how to do it in R.
First the data are inputted:
[,1] [,2]
[1,] 1 0.654
[2,] 11 0.167
[3,] 21 0.060
[4,] 41 0.070
[5,] 73 0.277
[6,] 83 0.186
The initial condition Yini is estimated as the annual mean of the Flux and divided by the
decay rate (parameter).
After defining the output times, the model is run, using integration routine ode.
The name of the derivate function "scocder", of the dll "deSolve"4 and of the initialiser
function "scocpar" are passed, as in previous examples.
In addition, the forcing function data set is also passed (forcings=Flux) as is the name of
the forcing initialisation function (initforc="scocforc").
Now, the way the forcing functions are interpolated are changed: Rather than linear interpo-
lation, constant (block, step) interpolation is used.
6.2. An example in C
Consider the following R-code which implements a resource-producer-consumer Lotka-Volterra
type of model in R (it is a modified version of the example of function ode):
method='linear'
Flux
Mineralisation
1.5
mmol C/m2/ d
1.0
0.5
0.0
days
Figure 2: Solution of the SCOC model, implemented in compiled code, and including a forcing
function - see text for R-code
+ })
+ }
R> ## The parameters
R> parms <- c(b = 0.1, c = 0.1, d = 0.1, e = 0.1, f = 0.1, g = 0.0)
R> ## vector of timesteps
R> times <- seq(0, 100, by=0.1)
R> ## external signal with several rectangle impulses
R> signal <- as.data.frame(list(times = times,
+ import = rep(0, length(times))))
R> signal$import <- ifelse((trunc(signal$times) %% 2 == 0), 0, 1)
R> sigimp <- approxfun(signal$times, signal$import, rule = 2)
R> ## Start values for steady state
R> xstart <- c(S = 1, P = 1, C = 1)
R> ## Solve model
R> print (system.time(
+ out <- ode(y = xstart,times = times,
+ func = SPCmod, parms, input = sigimp)
+ ))
R> plot(out)
S P
2.5
5
2.0
4
3
1.5
2
1.0
1
0 20 40 60 80 100 0 20 40 60 80 100
time time
C signal
5
0.8
4
3
0.4
2
0.0
1
0 20 40 60 80 100 0 20 40 60 80 100
time time
Figure 3: Solution of the Lotka-Volterra resource (S)-producer (P) - consumer (C) model
with time-variable input (signal) - see text for R-code
28 R Package deSolve, Writing Code in Compiled Languages
After defining the parameter and forcing vectors, and giving them comprehensible names, the
parameter and forcing initialiser functions are defined (parmsc and forcc respectively). Next
is the derivative function, derivsc.
#include <R.h>
/* initializers: */
void odec(void (* odeparms)(int *, double *))
{
int N=6;
odeparms(&N, parms);
}
/* derivative function */
void derivsc(int *neq, double *t, double *y, double *ydot,
double *yout, int*ip)
{
if (ip[0] <2) error("nout should be at least 2");
ydot[0] = import - b*y[0]*y[1] + g*y[2];
ydot[1] = c*y[0]*y[1] - d*y[2]*y[1];
ydot[2] = e*y[1]*y[2] - f*y[2];
After defining the forcing function time series, which is to be interpolated by the integration
routine, and loading the DLL, the model is run:
dyn.load("Forcing_lv.dll")
out <- ode(y=xstart, times, func = "derivsc",
parms = parms, dllname = "Forcing_lv",initforc = "forcc",
forcings=forcings, initfunc = "parmsc", nout = 2,
outnames = c("Sum","signal"), method = rkMethod("rk34f"))
dyn.unload("Forcing_lv.dll")
dyn.load("Forcing_lv.dll")
out2 <- ode(y = y, times, func = "derivsc",
parms = parms, dllname = "Forcing_lv", initforc="forcc",
forcings = forcings, initfunc = "parmsc", nout = 2,
outnames = c("Sum", "signal"), events=list(data=eventdata))
dyn.unload("Forcing_lv.dll")
plot(out2, which = c("S","P","C"), type = "l")
Here n is the length of the state variable vector y. and is then solved as:
dyn.load("Forcing_lv.dll")
out3 <- ode(y = y, times, func = "derivsc",
parms = parms, dllname = "Forcing_lv", initforc="forcc",
forcings = forcings, initfunc = "parmsc", nout = 2,
outnames = c("Sum", "signal"),
events = list(func="event",time=seq(10,90,10)))
dyn.unload("Forcing_lv.dll")
S P
3.5
3.5
3.0
3.0
2.5
2.5
2.0
2.0
1.5
1.5
1.0
1.0
0 20 40 60 80 100 0 20 40 60 80 100
time time
C
4
3
2
1
0 20 40 60 80 100
time
Figure 4: Solution of the Lotka-Volterra resource (S) – producer (P) – consumer (C) model
with time-variable input (signal) and with half of the consumer removed every 10 days - see
text for R-code
32 R Package deSolve, Writing Code in Compiled Languages
Here T is the time at which the value needs to be retrieved, nr is an integer that defines the
number of the state variable or its derivative whose delay we want, N is the total number of
state variabes and ytau will have the result.
We start with an example, a Lotka-Volterra system with delay, that we will implement in
Fortran (you will find this example in the package directory inst/doc/dynload-dede, in file
dede_lvF.f
The R-code would be:
! file dede_lfF.f
! Initializer for parameter common block
Karline Soetaert, Thomas Petzoldt, R. Woodrow Setzer 33
subroutine initmod(odeparms)
external odeparms
double precision parms(5)
common /myparms/parms
N = y(1)
P = y(2)
nr(1) = 0
nr(2) = 1
ytau(1) = 1.0
ytau(2) = 1.0
tlag = t - tau
if (tlag .GT. 0.0) call lagvalue(tlag, nr, 2, ytau)
ydot(1) = f * N - g * N * P
ydot(2) = e * g * ytau(1) * ytau(2) - m * P
yout(1) = ytau(1)
yout(2) = ytau(2)
return
end
During compilation, we need to also compile the file dedeUtils.c. Assuming that the above
Fortran code is in file dede_lvF.f, which is found in the working directory that also contains
file dedeUtils.c, the problem is compiled and run as:
#include <R.h>
#include <Rinternals.h>
#include <Rdefines.h>
#include <R_ext/Rdynload.h>
/* Initializer */
void initmod(void (* odeparms)(int *, double *)) {
int N = 5;
odeparms(&N, parms);
}
/* Derivatives */
void derivs (int *neq, double *t, double *y, double *ydot,
double *yout, int *ip) {
double N = y[0];
Karline Soetaert, Thomas Petzoldt, R. Woodrow Setzer 35
double P = y[1];
double T = *t - tau;
ydot[0] = f * N - g * N * P;
ydot[1] = e * g * ytau[0] * ytau[1] - m * P;
yout[0] = ytau[0];
yout[1] = ytau[1];
Assuming this code is in a file called dede_lv.c, which is in the working directory, this file is
then compiled and run as:
out <- ode (func = Parasite, y = c(P = 0.5, H = 0.5), times = 0:50,
parms = ks, method = "iteration")
Note that the function returns the updated value of the state variables rather than the rate
of change (derivative). The method “iteration” does not perform any integration.
The implementation in FORTRAN consists of an initialisation function to pass the parame-
ter values (initparms) and the "update" function that returns the new values of the state
variables (parasite):
subroutine initparms(odeparms)
external odeparms
double precision parms(3)
common /myparms/parms
call odeparms(3, parms)
return
end
P = y(1)
H = y(2)
f = A * P / (ks + H)
end
require(deSolve)
rH <- 2.82 # rate of increase
A <- 100 # attack rate
ks <- 15. # half-saturation density
References
Mazzia F, Magherini C (2008). Test Set for Initial Value Problem Solvers, release 2.4.
Department of Mathematics, University of Bari, Italy. Report 4/2008, URL http:
//pitagora.dm.uniba.it/~testset.
R Development Core Team (2008). R: A Language and Environment for Statistical Computing.
R Foundation for Statistical Computing, Vienna, Austria. ISBN 3-900051-07-0, URL http:
//www.R-project.org.
Setzer RW (2001). The odesolve Package: Solvers for Ordinary Differential Equations. R
package version 0.1-1.
Soetaert K, Petzoldt T, Setzer RW (2010b). deSolve: General solvers for initial value problems
of ordinary differential equations (ODE), partial differential equations (PDE), differential
algebraic equations (DAE) and delay differential equations (DDE). R package version 1.8.
Affiliation:
Karline Soetaert
Royal Netherlands Institute of Sea Research (NIOZ)
4401 NT Yerseke, Netherlands
E-mail: karline.soetaert@nioz.nl
URL: https://www.nioz.nl
Thomas Petzoldt
Institut für Hydrobiologie
Technische Universität Dresden
01062 Dresden, Germany
E-mail: thomas.petzoldt@tu-dresden.de
URL: https://tu-dresden.de/Members/thomas.petzoldt/
R. Woodrow Setzer
National Center for Computational Toxicology
US Environmental Protection Agency