Defining Models

To define a model you need to edit various functions in a standard template file. You may also need to use some standard functions. This section explains these. The following table summarises what you need to know about for each class of models that the package can deal with (SDDE stands for delay differential equations with switches):

Functions to Define ODE DDE SDDE
initcons() Y Y Y
switchfunctions() - - Y
map() - - Y
grad() Y Y Y
storehistory() - Y Y
initialstate() Y Y Y
statescale() Y Y Y
initout() Y Y Y
Functions to use
pastvalue() - Y Y
pastgradient() - Y Y

State variables and constants

The programme aims to solve problems of the general form:
s0' = g0(s(t), s(t-t1), s(t-t2), ..., s(t-tm), t)
s1' = g1(s(t), s(t-t1), s(t-t2), ..., s(t-tm), t)
...
sn' = gn(s(t), s(t-t1), s(t-t2), ..., s(t-tm), t)

subject to initial conditions on the si's, and with the possible addition of some discontinuities in the si's. The si's are the state variables of the problem and s(t) is used to mean the vector of all state variables at time t, so s(t-tj) is the vector of state variables time tj ago. To define a model the user must specify the functions gi which return the gradients of si w.r.t. time given the state variables at time t and at a series of lagged times. The user must also specify the starting values for all state variables.

Within the program the state variables are passed to and from functions as an array s[]. The functions defining the gradients of the state variables may depend on various user defined constants (as may the initial values) - these constants are passed around in an array c[]. All arrays start at element 0, not element 1. Elements of s[] must not be modified anywhere, except in map() and initstate(). If you modify elements of c[] then the changes will be permanent until you next change them.

initcons(no_vars, no_cons)

The arguments of this function are all pointers to double, except t, the time, which is a double. sw is an array in which to return switchfunction values; s[] is the array of state variables; c[] is the array of constants. Elements of s[] must not be altered in this function.

Switches are discontinuous changes in state variables. Switchfunctions are functions that pass through zero, with a negative time derivative at the time that a switch is to occur. Switches have an index (starting at zero). Users define the switchfunctions in the switchfunctions() routine by setting the values of the switchfunctions in the array sw[]. For example, if the 0th switch is to occur every 33.2 time units starting at time 11.1 the following line could be used:

sw[0]= - sin(6.28318530718*(t-11.1)/33.2);

As another example, if switch 1 is to occur just once at time 55.67 then the following line is appropriate:
sw[1]=55.67-t;
Switchfunctions can depend on constants and state variables, although some care may be required to ensure a well defined model when switchfunctions are state variable dependent.

When the ith switchfunction passes through zero, from positive to negative, then integration is suspended (in an orderly fashion) and the routine map() is called with i passed as argument swno. map() is where the user defines switches.

map(s,c,t,swno)

The arguments of this function are (pointer to double) s[] the array of state variables; (pointer to double) c[] the array of constants; (double)t the time; (integer) swno, the index of the switch whose switchfunction is passing through zero (with negative slope).

map() is where you must define the switches, i.e. the discontinuous changes in state variables s[] triggered by a switchfunction descending through zero ( swichfunctions() above). For example, in the following code state variables 0 and 1 are doubled by switch 0 every time it is triggered, while state variable 0 is multiplied by c[0]/t by switch 1 whenever it is triggered:

if (swno==0) {s[0]*=2;s[1]*=2;} else
if (swno==1) {s[0]*=c[0]/t;}

grad(g,s,c,t,pastvalue)

The arguments of this function are all pointers to double, except time t, which is a double; s[] is the state variable array; c[] is the array of model constants; g[] is the array in which the user must return the time derivatives of the state variables. Elements of s[] must not be altered in this function. It is very unlikely to be a good idea to change elements of c[] in this function.

There is an additional argument passed to the DDESolve version of grad, a pointer to the pastvalue() function. This is because all symbols must be defined in the dynamic library.

This is the routine in which the user specifies the gradients of the state variables at time t, given the the state variables at time t (supplied in s[]), the model constants, and (optionally) lagged values of the state variables (or lagged functions of state variables). For each state variable s[i] you must supply a value in g[i]. For example if the equation for the gradient of state variable 0 is:

s0' = c0 s0 - c1 s0 s1

then the corresponding line in the routine grad() would be:
g[0]=c[0]*s[0]-c[1]*s[0]*s[1];

Lagged variables are accessed using the routinespastvalue() and (occasionally) pastgradient(). These are described later, but for completeness an example of their use is given here. Suppose that you want to code the equation:
s0' = { c0s0(0) t< c1
c0s0(t-c1) t &ge c1
subject to the initial condition s0(0)=c2. Assuming that the 0th history variable (see storehistory() and pastvalue(), below) contains the lagged values of s[0] then the appropriate code would be:

if (t < c[1]) g[0]=c[0]*c[2];
else g[0]=c[0]*pastvalue(0,t-c[1],0);
Notes
  1. If the gradient changes discontinuously, then it is often good practice to specify a switch at the time of the discontinuity: this forces the integration to step to exactly the point of discontinuity, before continuing, which ensures that the continuity assumptions of the integrator are not violated (the specified switch should leave all state variables unchanged).
  2. The specification of your model should never involve writing to global variables from within grad(): grad() is called multiple times per step of the integrator, and t may increase or decrease between calls. Similarly writing to static variables is very rarely appropriate as part of model specification.
  3. g[] contains meaningless values on entry to the function.

storehistory(his,ghis,g,s,c,t)

The arguments of this function are all pointers to double except for time t which is a double. g[] and s[] contain the current values of the gradients of the state variables (w.r.t. time) and the state variables; their elements must not be altered by this function. c[] is the array of model constants. his[] and ghis[] are arrays in which you should store the value and time derivative, at time t, of any quantity that you want to use as a lagged variable within your model.

If your model equations depend on lagged quantities then these must be stored. storehistory() is the function that allows you to do this. The lagged variables (or ``history variables'') are stored only at discrete times. Interpolation is necessary to estimate the values of lagged variables between these storage times. In order to ensure correct adaptive stepping for integration this interpolation must be performed to a higher order of accuracy than the integration. To achieve this requires that both the values and time derivatives of the state variables be stored1. The lagged values and gradients for each history variable are stored in a ringbuffer for access by pastvalue() or occasionally pastgradient() (see below).

As an example, if your model is the simple DDE:
s0' = { c0s0(0) t< c1
c0s0(t-c1) t &ge c1
then history variable 0 would be defined as s0. The appropriate piece of code within storehistory() would be:

his[0]=s[0];
ghis[0]=g[0];
and the within grad() the 0th state variable would be used to specify the gradient of s0 as follows:
if (t<c[1]) g[0]=c[0]*c[2];
else g[0]=c[0]*pastvalue(0,t-c[1],0);

History variables do not have to be state variables themselves (although it's usually easiest to set models up that way), for example if history variable 1 was to be exp(s0) + 2s1 then the appropriate code in storehistory() would be:

his[1]=exp(s[0])+2*s[1];
ghis[1]=g[0]*exp(s[0])+2*g[1];

If you must write to global variables, this routine is a reasonable place to do it, but be aware that you may compromise the accuracy of numerical model solution (since the adaptive stepping algorithm has no way of `seeing' this variable).

initst(s,c,t)

This function is called initstate in solv95: however, this clashes with a Unix library routine. Hence, it has been renamed initst, which is in line with the usage in ddefit.

The arguments of this routine are: (pointer to double) s, the state variable array; (pointer to double) c the constant array; (double) t the time at start of model integration.

This function is where the initial values of all state variables must be set by the user. For example to set state variable 0 to c3 and state variable 1 to 3.45, you would use the following code:

s[0]=c[3];
s[1]=3.45;
It is quite safe to modify global variables and elements of c[] within this function.

statescale(double *scale)

DDESolve controls integration error by specifying that it should be less than some specified proportion of each state variable. This can cause difficulties when some state variables are in the vicinity of zero. For example, if a state variable is at zero, but about to leave that state, then the integrator may attempt to estimate that variable with zero error - which requires a zero timestep! The solution adopted is to get the user to specify a small number to be added to the magnitude of each state variable when estimating proportianal integration error. These numbers (one for each state variable) must be supplied by the user in array scale[].

Any elements of scale[] that are unspecified are taken as zero.

initout(usercontrol *out)

This routine is where you provide various initialization information for setting up the model and the display of the model's solutions. The routine is used to fill out the structure out: there is a large amount to specify, but it is straightforward to do so. The following sections cover the elements of out: any element marked by a * is optional.

Model control constants and defaults

constants c[]

State variable labels and other information

Windowed output

DDESolve allows the user to control how many windows output will be displayed in, as well as how many and which state variables will be displayed in each window.

File output

A text output file can be produced, it's columns are time (or whatever variable is being integrated with respect to) followed by those state variables specified for output.

When you run the model, you will be presented wth a save file panel to select the output file name. If this annoys you every time you run a model, set out->fileno = 0. You can still save model results using the Model->Write Results... menu in DDESolve.

pastvalue(i,t,j)

This function is called by the user to access history variables at lagged times. i is the index of the history variable (starting at zero); t is the (double) time at which the history variable is to be evaluated; j is the index of the history buffer location marker being used at this call. The function returns a double. Examples of the use of this function are given in the sections on storehistory() and grad() above, so here a slightly more complicated example is given. Consider the contrived situation of a population that produces half its offspring in an environment promoting fast maturation and half in an environment promoting slow maturation, suppose also that the maturation time varies throughout the year. Suitable equations might be:
s0' =  (1/2)c0s0(t-T0) + (1/2)c0s0(t-T1) - c1s0(y) 
T0 = 20 + 10 sin(2pi(t-90)/365)
T1 = 2T0 
Suitable code for this in grad() might be:
. 
. 
    T0 = 20.0+10.0*sin(6.28318530718*(t-90.0)/365.0);
    T1 = 2*T0;
    if (t>T1) {
        g[0]=pastvalue(0,t-T1,0)*0.5*c[0] + pastvalue(0,t-T2,1)*0.5*c[0] - c[1]*s[0];
    } else {
.
.
Note the important point that you can not request lagged variables from before the start of integration - instead you have to define your model in such a way that it does not require such values: this is always possible for a well defined model.

pastgradient(i,t,j)

This is exactly the same as pastvalue() except that it returns the time derivative of the lagged variable - its use is somewhat dubious, since the order of approximation of the interpolated gradients is lower than the order of approximation of the integrator which may lead to infelicities in the numerical solution of models using this function.

Differences between the Mac and Windows versions

Coding hints

Back to index


  1. This consistency of integrator and interpolator is solv95's chief advantage over other DDE solving packages