Chapter 2 PSPMdemo: demographic analysis

2.1 Model formulation and ingredients

The core of a linear PSPM consists of a model of the individual life history that is based on the following assumptions:

  • Individuals are characterized by their individual or i-state, which is a (finite) set of physiological characteristics (traits such as age, size, sex, energy reserves): \[\boldsymbol{\chi} = \left(\chi_1,\ldots,\chi_k\right) \in \Omega \subset \mathbb{R}^\mathrm{k}\]

  • Individuals are born with an i-state \(\boldsymbol{\chi}_b\) that is one of a finite set of possible states at birth: \[\boldsymbol{\chi}_b \in \left\{\boldsymbol{\phi}_1,\ldots,\boldsymbol{\phi}_m\right\}\] with each potential state at birth \(\boldsymbol{\phi}_j\) a valid i-state: \[\boldsymbol{\phi}_j=\left(\phi_{j1},\ldots,\phi_{jk}\right) \in \Omega \subset \mathbb{R}^\mathrm{k}\]

  • Development follows a deterministic process that is continuous in time: \[\dfrac{d\boldsymbol{\chi}}{da}\;=\;g(\boldsymbol{\chi}, \boldsymbol{\chi}_b)\] The development rate \(g(\boldsymbol{\chi}, \boldsymbol{\chi}_b)\) is a function of the individual state and the individual’s state at birth

  • Reproduction is modeled by a per-capita offspring production rate (or fecundity) \(\beta(\boldsymbol{\chi}, \boldsymbol{\chi}_b)\), dependent on the individual state and the individual’s state at birth

  • Mortality is modeled by a per-capita death rate \(\mu(\boldsymbol{\chi}, \boldsymbol{\chi}_b)\), dependent on the individual state and the individual’s state at birth

All assumptions above are characteristic for the general class of PSPMs. The most restrictive of these assumptions concerns the deterministic development process. Biologically, this assumption implies that all individuals that are born with the same state at birth will remain identical throughout their life and will hence not diverge in their i-state characteristics. Reproduction and mortality on the other hand are at an individual level considered as stochastic processes, which translate to per-capita rate functions at the population level, given that it is assumed that the number of individuals is large (technically speaking the number of individuals for every possible i-state).

2.2 An example model for demographic analysis

The steps needed for the implementation of a particular PSPM will be discussed using a simple model for the life history of the Mediterranean fruit fly, which is also discussed in De Roos (2008). The individual life history in this model is only age-dependent with both age-dependent birth and mortality rates. The PSPM for this model can be described by the following partial differential equation (PDE) for the population age-distribution \(n(t,a)\):

\[\begin{align*} &\dfrac{\partial n}{\partial t}\:+\:\dfrac{\partial n}{\partial a}\;=\;-\mu(a)\,n(t,a)\\[2ex] &n(t, 0)\;=\;\int_{A_j}^{\infty} \beta(a)\,n(t,a)\,da\\[4ex] &\beta(a)\,=\,\beta_0\,e^{-\beta_1(a-A_j)},\qquad\qquad\textrm{if $a>A_j$}\qquad\qquad\qquad\qquad\qquad\\[1ex] &\mu(a)\,=\,\mu_0\,e^{\mu_1a} \end{align*}\]

The first, partial differential equation above describes the changes in the population age-distribution \(n(t,a)\) through aging (\(\partial n/\partial a\)) and mortality, which is modeled by the mortality rate \(\mu(a)\). The second equation, representing the boundary condition for the partial differential equation, describes the total population reproduction rate \(n(t,0)\), which equals the cumulative fecundity of all individuals older than \(A_j\), the age at maturation. The mortality rate \(\mu(a)\) is an exponentially increasing function of age, whereas the fecundity \(\beta(a)\) is highest for just maturing individuals (\(a=A_j\)) and decreases exponentially with age afterward.

As listed in section 2.1, individuals are assumed to be born with an i-state \(\boldsymbol{\chi}_b\) that is one of a finite set of possible states-at-birth, each of which is a valid i-state: \[\boldsymbol{\chi}_b \in \left\{\boldsymbol{\phi}_1,\ldots,\boldsymbol{\phi}_m\right\},\qquad \boldsymbol{\phi}_j=\left(\phi_{j1},\ldots,\phi_{jk}\right) \in\Omega \subset\mathbb{R}^\mathrm{k}\] Given that individual age is the only i-state variable in the Medfly model, all individuals have the same state at birth and hence \(m=1\). The option to specify multiple states-at-birth is hence not relevant for the example model discussed in this implementation chapter. This might hold more generally; most if not all physiologically structured population models that have been reported on in the literature so far are characterized by such a unique state-at-birth for all individuals. Nonetheless, the option to define multiple states-at-birth opens up some interesting research possibilities, which are discussed further in section 9.1.

Since models involving multiple states-at-birth are not very common, information that relates to this option will be distinguished in the text by setting them apart in paragraphs like this one. The index \(j\) will be used to refer to the index of a particular state-at-birth in the set \(\left\{\boldsymbol{\phi}_1,\ldots,\boldsymbol{\phi}_m\right\}\). The number \(m\) of possible states-at-birth is set dynamically in the model file.

2.3 Implementation of the example model

2.3.1 Implementation of the model in R

The implementation of this model, which I will refer to as the Medfly model requires the specification of 3 constants and 3 functions describing the life history. The necessary pieces of R-code are discussed in detail in the next subsections. The code can be found in the file Medfly.R, which can be opened by executing the command showpspm("Medfly.R") at the command line. To implement your own model it is advisable to use one of the example models, which can be listed using the utility function showpspm(), as a basis for the implementation. To do so, you can copy the contents of the file Medfly.R to a new file in Rstudio’s built-in editor and save this new file with a new name. The extension of your model-specific file should however remain '.R'.

2.3.1.1 Problem dimensions

The first variable to define, PSPMdimensions, is a vector with the named elements PopulationNr, IStateDimension and LifeHistoryStages that specify the dimensions of the model:

Code block 2.3.1.1
PSPMdimensions <- c(PopulationNr = 1, IStateDimension = 1, LifeHistoryStages = 2)

The software can simultaneously compute the population growth of more than a single population. The vector element PopulationNr of PSPMdimensions has to be defined equal to the number of structured populations accounted for in the model. For the Medfly example this is obviously equal to 1. The vector element IStateDimension of PSPMdimensions defines the dimension of the individual state. As only age characterizes the individuals in the Medfly model, this variable is defined equal to 1. Finally, the element LifeHistoryStages of PSPMdimensions has to be defined equal to the number of life stages that can be distinguished in the individual life history. While integrating the ODEs for the individual life history numerical problems may occur when the right hand side of the ODEs changes abruptly in value at a certain threshold value of the individual state, as a consequence of discontinuities in the development rate, the mortality rate or the fecundity. Each of such thresholds in the individual life history should be distinguished as a stage boundary. In the Medfly model the fecundity \(\beta(a)\) changes from \(0\) just before \(a=A_J\) to \(\beta_0\) at \(a=A_J\) and \(\beta_0\exp(-\beta_1(a-A_j))\) at larger ages. At \(a=A_j\) \(\beta(a)\) thus exhibits a discontinuity, which separates the juvenile and the adult stage from each other. The element LifeHistoryStages of PSPMdimensions is therefore set equal to 2.

2.3.1.2 Optional numerical settings

The next variable specified in the Medfly.R file is the vector NumericalOptions, which can contain a variable number of named vector elements:

Code block 2.3.1.2
NumericalOptions <- c(MIN_SURVIVAL  = 1.0E-9,   # Survival at which individual is considered dead
                      MAX_AGE       = 100000,   # Absolute maximum individual age
                      DYTOL         = 1.0E-7,   # Variable tolerance
                      RHSTOL        = 1.0E-8)   # Function tolerance

The specification of NumericalOptions is optional and can be left out, if default values are acceptable. A list of all possible vector elements that can be included in the NumericalOptions variable is provided in chapter 8.

The vector element MIN_SURVIVAL of NumericalOptions determines the threshold of the survival probability below which an individual is considered dead. The integration over the individual life history stops whenever the survival probability falls below this threshold value. In the code above the minimum survival is set to \(10^{-9}\), which is in fact the default value and is hence superfluous. Note that the value of MIN_SURVIVAL can not be set equal to \(0\). As an alternative to using \(0\) MIN_SURVIVAL can be set to a very small value like \(10^{-100}\).

The vector element MAX_AGE of NumericalOptions can be used as an alternative to determine the end of an individual life and to stop the integration over the individual life history. In the Medfly model there is no maximum individual age and hence the variable is set to a very high value (100000), which the individuals will never reach, because before that age their survival probability has already dropped below its threshold value (\(10^{-9}\)).

The remaining two vector elements DYTOL and RHSTOL of NumericalOptions determine whether a solution has been found. In general, both demographic analysis as well as equilibrium analysis of PSPMs boils down to solving a system of nonlinear equations that can be represented as \(G(y)=0\) for a set of unknowns \(y\) in an iterative manner. The subsequent estimates of the solution in the Newton iterations can be labeled as \(y_p\) and \(y_{p+1}\). A solution is now considered to be located if both of the following conditions hold:

\[\begin{align*} &\|{y_{p+1}-y_p}\| < \epsilon_y\\[2ex] &\|{G(y_{p+1})}\| < \epsilon_G \end{align*}\]

where \(\|.\|\) refers to the Euclidean norm. DYTOL and RHSTOL are the quantities \(\epsilon_y\) and \(\epsilon_G\), respectively. Increasing (decreasing) their value leads to easier (harder) acceptance of a set of unknowns as a solution to the system of equations \(G(y)=0\). The definition of these two accuracies in the code box above is in fact superfluous as they are defined equal to their default values (see chapter 8).

2.3.1.3 Default parameters

The last variable to be defined is the vector DefaultParameters, which should contain named vector elements that specify the name and default value of all parameters in the model:

Code block 2.3.1.3
DefaultParameters <- c(Beta0 = 47.0, Beta1 = 0.04, Aj = 11.0, Mu0 = 0.00095, Mu1 = 0.0581)

The names of the vector elements (parameters) can be used in the programming of the life history functions of the model and are furthermore used to make the output files produced by the program more readable. These output files contain a small header text indicating among other details which parameter values were used for the computation of the results contained in the output file. In this report the parameter names are listed together with their value.

2.3.1.4 States at birth

The first function to be implemented for a particular life history model should be called StateAtBirth() and should define for every population in the model the actual value of the different individual state variables \(\boldsymbol{\phi}_j=\left(\boldsymbol{\phi}_{j1},\ldots,\boldsymbol{\phi}_{jk}\right)\) for every possible state-at-birth that an individual can be born with (i.e. the set \(\left\{\boldsymbol{\phi}_1,\ldots,\boldsymbol{\phi}_m\right\}\)).

The function should return a vector with as many named vector elements as there are i-state variables. Each vector element should specify the name of the particular i-state variable and the numeric value with which the individual is born. The names of the vector elements can be used conveniently in the functions below that define the life history processes.

If individuals can differ in their individual state at birth this function should return a matrix with the number of rows equal to the number of possible states at birth and the number of columns equal to the number of i-state variables. Each row then specifies the value of the individual state variable of the particular state at birth. In case the model accounts for multiple, structured populations this function should return a matrix with the number of rows equal to the number of structured populations in the problem and the number of columns equal to the number of i-state variables.

In case the model accounts for multiple, structured populations and individuals can differ in their individual state at birth this function should return a 3-dimensional array with the first dimension having a length equal to the number of structured populations in the problem, the second dimension equal to the number of possible states at birth and the third dimension equal to the number of i-state variables.

For the Medfly model age is the only individual state variable and its value at birth is of course \(0\):

Code block 2.3.1.4
StateAtBirth <- function(E, pars)
{
  with(as.list(c(pars)),{
        # We model a single structured population with a single i-state variable (age)
        c(Age = 0.0)
      })
}

Notice that the arguments of the function StateAtBirth() contain a vector E in addition to the vector with parameter values pars. The vector E will contain the values of the environment variables during equilibrium computations of PSPMs (see sections 3.1 to 3.4). In demographic analysis of PSPMs this variable is non-functional and is best ignored, using it in a statement inside the routine may even cause the program to crash. The only reason for the presence of this variable among the function arguments is to keep the function declaration the same for both demographic and equilibrium analysis computations. In principle, the same model-specific file can hence be used for both types of analysis. The variable E will for the same reasons also be part of the headers of the next 2 routines.

2.3.1.5 Boundaries between consecutive stages

The next function LifeStageEndings() determines the boundaries between consecutive stages in the individual life history. It should return a variable named maturation, the value of which specifies the threshold value at which the current life stage of the individual ends and the individual matures to the next life history stage. The life stage that the individual is in at the moment this routine is called, is determined by the function argument lifestage, which has a value of 1 if the individual is in the first life stage and a value equal to PSPMdimensions["LifeHistoryStages"] if it is in the last life stage. The end of the current life history stage, as indicated by lifestage, occurs when this threshold value becomes \(0\) and switches sign from negative to positive. For the end of the last life stage the death of old age, either by reaching the maximum age (NumericalOptions["MAX_AGE"]) or by reaching the minimum survival threshold (NumericalOptions["MIN_SURVIVAL"]), does not have to be specified separately, the program takes care of that automatically. For the final life stage hence return a constant negative value (for example, -1). In case the model accounts for multiple, structured populations the return variable maturation is a vector with the number of elements equal to the number of structured populations in the problem, while the argument lifestage is also a vector of a length equal to the number of structured populations in the model.

In the Medfly model only the end of the larval stage has to be specified, as shown below:

Code block 2.3.1.5
LifeStageEndings <- function(lifestage, istate, birthstate, BirthStateNr, E, pars) {
  with(as.list(c(E, pars, istate)),{
        maturation  = switch(lifestage, Age - Aj, -1)
      })
}

The threshold value returned to the program in maturation will in general depend on the individual state variables, possibly on the individual’s state-at-birth and will be different for individuals in different life stages. For this reason, the function LifeStageEndings() has as arguments lifestage, specifying the life stage that the individual is currently in, istate, the individual state, and birthstate, the individual’s state-at-birth in addition to the arguments E and pars that have the same interpretation as discussed above for the function StateAtBirth().

This routine will be called as many times as there are possible states-at-birth, because the state-at-birth may influence the threshold between consecutive life stages. The same holds for the next 2 routines discussed, which define changes in the i-state variables, the fecundity and the mortality of individuals. In essence, individuals with different states-at-birth are treated as constituting subpopulations within the same structured population. Because of the possible dependence on the state-at-birth the variables birthstate and BirthStateNr are passed as arguments to the function LifeStageEndings() as well as to the 2 functions discussed below. These arguments contain the values of the i-state variables and the index in the set \(\left\{\boldsymbol{\phi}_1,\ldots,\boldsymbol{\phi}_m\right\}\), respectively, for which the routine is invoked and for which the threshold between consecutive life stages has to be evaluated.

2.3.1.6 Life history rates

The next function, named LifeHistoryRates(), specifies the life history rates of an individual. The function should return a list with 3 components, named development, fecundity and mortality. The components should have the following structure:

  • development: This component of the returned list specifies the right-hand side of the ODE: \[\dfrac{d\boldsymbol{\chi}}{da}\;=\;g(\boldsymbol{\chi}, \boldsymbol{\chi}_b)\] determining the continuous development of the individual state variables during the life history. It should be a vector of length equal to the number of i-state variables. Each element specifies the rate of development for the particular i-state variable.

    Notice that the development rate may differ in different life stages, for example growth in body size may be different for juveniles and adults in case adults invest a lot of energy into reproduction. The development rates should then be specified dependent on the current life stage the individual is in, which is determined by the function argument lifestage. The development rate may furthermore depend on the individual state variables and on the parameters, i.e. the values of the arguments istate and pars, respectively, but possibly also on the individual’s state-at-birth, the values and index of which are specified by the arguments birthstate and BirthStateNr, respectively.

    In case the model accounts for multiple, structured populations this component should be a matrix with the number of rows equal to the number of structured populations in the problem and the number of columns equal to the number of i-state variables. In this case, the value of development[p,i] determines for each structured population p the development in the individual state variable i.

  • fecundity: This component of the returned list specifies the current fecundity of the individual. In the most common case of a unique state-at-birth and a single structured population, like in the PNAS2002 model, the component fecundity should be a single value.

    The fecundity will certainly depend on the life stage that the individual is in (only adults reproduce), which is contained in the function argument lifestage, on the individual state variables and on the parameters, i.e. the values of the arguments istate and pars, respectively, but possibly also on the individual’s state-at-birth, the values and index of which are specified by the arguments birthstate and BirthStateNr, respectively.

    In case the model accounts for multiple, structured populations this component should be a matrix of fecundities with the number of rows equal to the number of structured populations in the problem and a single column. In case individuals can be born with different states at birth the component should have a number of columns equal to the number of states at birth. In this latter case not only the fecundity (i.e. the number of offspring produced per unit time) has to be specified, but also the state-at-birth of the produced offspring. Therefore, this function has to assign values to the matrix fecundity[p,b], which determines for the population with index p the number of offspring produced per unit time with state-at-birth with index b in the set \(\left\{\boldsymbol{\phi}_1,\ldots,\boldsymbol{\phi}_m\right\}\). Each column should hence specify the number of offspring produced with the particular state at birth.

  • mortality: A single value specifying the current mortality rate that the individual experiences, possibly dependent on the life stage the individual is in at the moment this routine is called (given in the function argument lifestage), the current values of the individual state variables and parameters, i.e. the values of the arguments istate and pars, respectively, and the individual’s state-at-birth (current values and index in the set \(\left\{\boldsymbol{\phi}_1,\ldots,\boldsymbol{\phi}_m\right\}\) given by birthstate and BirthStateNr, respectively).

    In case the model accounts for multiple, structured populations this argument is a vector of mortality rates with the number of elements equal to the number of structured populations in the problem.

For the Medfly model the function LifeHistoryRates() is specified as follows:

Code block 2.3.1.6
LifeHistoryRates <- function(lifestage, istate, birthstate, BirthStateNr, E, pars) {
  with(as.list(c(pars, istate)),{
        list(
            # We model a single structured population (nrow=1) with a single i-state variable (age)
            development = 1.0,
            fecundity   = switch(lifestage, 0, Beta0*exp(-Beta1*(Age - Aj))),
            mortality   = Mu0*exp(Mu1*Age)
        )
      })
}

In the Medfly model the specification of the development rate is obviously trivial, as age is the only i-state variable. Furthermore, the code fragment above implements the function \(\beta(a)=\beta_0e^{-\beta_1(a-A_j)}\) for fecundity and the function \(\mu(a)=\mu_0e^{\mu_1a}\) for the mortality, which is not influenced by the life stage specifically and is only age-dependent.

Refer to the remarks in the discussion of the function LifeStageEndings() concerning the dependence on the individual’s state-at-birth.

2.3.1.7 Optional discrete changes at stage boundaries

Even though not listed among the basic assumptions of the PSPM in the beginning of this chapter, it is permissible to have discrete changes or jumps in the individual state variables at the transition between two consecutive life stages. If these occur, they should be specified in the function DiscreteChanges(). This function is not relevant in case of the Medfly model, in which case it can simply be left away or commented out.

Code block 2.3.1.7
DiscreteChanges <- function(lifestage, istate, birthstate, BirthStateNr, E, pars) {
  with(as.list(c(E, pars)),{
       # No discrete changes in this problem, function is commented out, which 
       # would be equivalent to returning a copy of the input argument 'istate'
       istate
     })
}

If defined, the function DiscreteChanges() is called whenever a transition between two consecutive life stages is reached during the integration over the individual life history. The function should return a vector of length equal to the number of i-state variables. Each element should specify the value of the particular i-state variable after the transition to the current state. In case the model accounts for multiple, structured populations this function should return a matrix with the number of rows equal to the number of structured populations in the problem and the number of columns equal to the number of i-state variables.

It should be noted that the value of the variable lifestage indicates the life stage that is entered, that is, following the current stage boundary. This routine will hence never be called with a value of one of the elements lifestage equal to 1. The discrete changes in the individual state variables have to be implemented by assigning new values to the variables istate. These assignments may as before depend on the life stage that is entered, as specified by the variable lifestage, the (old values) of the individual state variables, contained in the argument istate, and possibly on the individual’s state-at-birth, specified in the argument birthstate. If no assignment of a value to istate is implemented, that particular individual state variable will keep its current value.

2.3.2 Implementation of the model in C

The implementation of the Medfly model in C requires the specification of 10 pieces of C-code that can be subdivided into two different groups:

  • Problem dimensions, numerical settings and model parameters

  • Definition of the individual life history functions, such as development (growth), fecundity and mortality.

The pieces of C-code are discussed in detail in the next 10 subsections. The code can be found in the file Medfly.h, which can be opened by executing the command showpspm("Medfly.h"). To implement your own model you only need a basic understanding of C, which programming language I will not further discuss here. It is advisable to use one of the example models, which can be listed using the utility function showpspm(), as a basis for the implementation. To do so, you can copy the contents of the file Medfly.h to a new file in Rstudio’s built-in editor and save this new file with a new name. The extension of your model-specific file should however remain '.h'.

The software allows for the analysis of models with multiple structured populations, each of which consists of individuals that are characterized by a finite number of individual state variables. The number of state variables characterizing an individual should, however, be the same for each of the structured populations in the model. Furthermore, at birth individuals may have one of a finite number of states-at-birth. To distinguish between populations, between individual state variables and between different states-at-birth, in the following the index \(p\) will consistently refer to the index of the structured population in the model. Because the dimension setting POPULATION_NR is used to specify the number of populations in the model (see the next section), \(p\) takes on values in the range \(0, 1,\ldots,\) POPULATION_NR-1. Similarly, the index \(i\) will consistently refer to the index of a particular individual state variable, which should always take values in the range \(0, 1,\ldots,\) I_STATE_DIM-1, given that the dimension setting I_STATE_DIM determines the number of individual state variables (see the next section).

2.3.2.1 Definition of problem dimensions and optional numerical settings

The code box below defines the different dimensions of the model and the numerical settings to be used in the computations.

Code block 2.3.2.1
// Dimension settings: Required
#define POPULATION_NR       1               // Structured consumer population
#define STAGES              2               // Juvenile & adult
#define I_STATE_DIM         1               // See below
#define PARAMETER_NR        5

// Numerical settings: Optional (default values adopted otherwise)
#define MIN_SURVIVAL        1.0E-9          // Survival at which individual is considered dead
#define MAX_AGE             100000          // Give some absolute maximum for individual age

#define DYTOL               1.0E-7          // Variable tolerance
#define RHSTOL              1.0E-8          // Function tolerance

The software can simultaneously compute the population growth of more than a single population. At the start of the problem file the variable POPULATION_NR has to be defined equal to the number of structured populations accounted for in the model. For the Medfly example this is obviously equal to 1 (line 2 in the code box above).

The variable STAGES has to be defined equal to the number of life stages that can be distinguished in the individual life history (line 3 in the code box above). While integrating the ODEs for the individual life history numerical problems may occur when the right hand side of the ODEs changes abruptly in value at a certain threshold value of the individual state, as a consequence of discontinuities in the development rate, the mortality rate or the fecundity. Each of such thresholds in the individual life history should be distinguished as a stage boundary. In the Medfly model the fecundity \(\beta(a)\) changes from \(0\) just before \(a=A_J\) to \(\beta_0\) at \(a=A_J\) and \(\beta_0\exp(-\beta_1(a-A_j))\) at larger ages. At \(a=A_j\) \(\beta(a)\) thus exhibits a discontinuity, which separates the juvenile and the adult stage from each other. The variable STAGES is therefore set equal to 2.

The variable I_STATE_DIM (line 4 in the code box above) defines the dimension of the individual state. As only age characterizes the individuals in the Medfly model, this variable is defined equal to 1.

The last required parameter that has to be specified is the number of parameters in the model (set in line 5 in the code box above). In the Medfly model this equals 5 (\(\beta_0\), \(\beta_1\), \(A_j\), \(\mu_0\) and \(\mu_1\)).

The remaining definitions in the code box are all optional and can be left away. A list of all possible variables that can be changed by a definition in this code section is provided in chapter 8. The variable MIN_SURVIVAL determines the threshold of the survival probability below which an individual is considered dead. The integration over the individual life history stops whenever the survival probability falls below this threshold value. In the code above (line 8) the minimum survival is set to \(10^{-9}\), which is in fact the default value and is hence superfluous. Note that the value of MIN_SURVIVAL can not be set equal to \(0\). As an alternative to using \(0\) MIN_SURVIVAL can be set to a very small value like \(10^{-100}\).

The variable MAX_AGE (line 9 in the code box above) can be used as an alternative to determine the end of an individual life and to stop the integration over the individual life history. In the Medfly model there is no maximum individual age and hence the variable is set to a very high value (100000), which the individuals will never reach, because before that age their survival probability has already dropped below its threshold value (\(10^{-9}\)).

The remaining two quantities DYTOL and RHSTOL determine whether a solution has been found. In general, both demographic analysis as well as equilibrium analysis of PSPMs boils down to solving a system of nonlinear equations that can be represented as \(G(y)=0\) for a set of unknowns \(y\) in an iterative manner. The subsequent estimates of the solution in the Newton iterations can be labeled as \(y_p\) and \(y_{p+1}\). A solution is now considered to be located if both of the following conditions hold:

\[\begin{align*} &\|{y_{p+1}-y_p}\| < \epsilon_y\\[2ex] &\|{G(y_{p+1})}\| < \epsilon_G \end{align*}\]

where \(\|.\|\) refers to the Euclidean norm. DYTOL and RHSTOL are the quantities \(\epsilon_y\) and \(\epsilon_G\), respectively. Increasing (decreasing) their value leads to easier (harder) acceptance of a set of unknowns as a solution to the system of equations \(G(y)=0\). The definition of these two accuracies in the code box above is in fact superfluous as they are defined equal to their default values (see chapter 8).

2.3.2.2 Definition of parameter names and values

The code box below assigns each of the model parameters a meaningful name and a default value.

Code block 2.3.2.2
// Descriptive names of parameters in parameter array (at least two parameters are required)
char  *parameternames[PARAMETER_NR] =
    { "Beta0", "Beta1", "AJ", "Mu0", "Mu1"};

// Default values of all parameters
double  parameter[PARAMETER_NR] =
    {47.0, 0.04, 11.0, 0.00095, 0.0581};

Model parameter values are stored by the program in the vector variable parameter. The lines 2-3 above assign each of the elements this vector a more meaningful, model-specific name. These name strings can not be used in the remaining parts of the model implementation, they only serve to make the output files produced by the program more readable. These output files contain a small header text indicating among other details which parameter values were used for the computation of the results contained in the output file. In this report the parameter names are listed together with their value. To adapt the above code to a different model, the code on line 2 of the code box above should remain the same, only change line 3 as needed (possibly extending it over multiple lines in case there are many parameters).

The default values to use for the model parameters are specified by the declaration of the vector parameter[PARAMETR_NR] on line 6-7 of the previous code box. The values should be specified as a comma-separated array of values within braces (don’t forget the closing semi-colon at the end of the statement!). To adapt the above code to a different model, the code on line 6 of the code box above should remain the same, only change line 7 as needed (possibly extending it over multiple lines in case there are many parameters).

2.3.2.3 Definition of aliases to simplify implementation

The code box below defines aliases for program variables used in the C-implementation of the model, such that they are more easily identified with the model ingredients. Defining these aliases is optional but strongly advised as it makes model implementation more straightforward.

Code block 2.3.2.3
// Aliases definitions for all istate variables
#define AGE                 istate[0][0]

// Aliases definitions for all parameters
#define BETA0               parameter[ 0]   // Default: 47.0
#define BETA1               parameter[ 1]   // Default: 0.04
#define AJ                  parameter[ 2]   // Default: 11.0
#define MU0                 parameter[ 3]   // Default: 0.00095
#define MU1                 parameter[ 4]   // Default: 0.0581

The developmental rates in individual state, fecundity and mortality in any model depend on the individual state itself, on the individual’s state at birth and on model parameters. The value of the individual state variables at a particular age are always referred to with the program variable istate[\(p\)][\(i\)], where the index \(p\) refers to the number of the population and the index \(i\) refers to the number of the individual state variables. Notice that in C array indices run from \(0\) (as opposed to 1 like in R)! Similarly, the value of the individual’s state variables at birth are always referred to with the program variable birthstate[\(p\)][\(i\)]. In case there are multiple populations and/or more than a single individual state variable, it is up to the user to keep track of which index pertains to which population or individual state variable. In the Medfly model there is only a single population and a single individual state variable, while the state at birth is rather irrelevant as it equals age \(0\). Therefore, istate[0][0] is the only program quantity to give a more meaningful name (line 2 in the code box above).

As discussed in the previous section all model parameters are contained in a vector named parameter in the code. Which element of this vector represents which model-specific parameter is up to the user. To prevent mixing up the interpretation of the different vector elements and hence to prevent mistakes, it is strongly advised to define meaningful, model-specific aliases for each of the elements of the vector parameter as is illustrated in lines 5-9 in the code box above. It is best to avoid completely the direct use of the program variable parameter in any part of the model specification and only use the models-specific aliases.

As can be seen in the code block above all aliases for program variables used in the C-implementation of the model are names in capitals. It is advisable to use only capitals when introducing these aliases (or global variables if they are needed) to avoid any conflict between these aliases and variables that defined elsewhere in any of the C files with numerical routines that are included in the package.

2.3.2.4 Specifying the number of possible states-at-birth

The first routine to be implemented for a particular life history model defines for every population in the model the number of possible states-at-birth that an individual can be born with (i.e. the value of the size \(m\) of the set \(\left\{\boldsymbol{\phi}_1,\ldots,\boldsymbol{\phi}_m\right\}\)).

Code block 2.3.2.4
/*
 * Specify the number of states at birth for the individuals in all structured
 * populations in the problem in the vector BirthStates[].
 */

void SetBirthStates(int BirthStates[POPULATION_NR], double E[])
{
  BirthStates[0] = 1;

  return;
}

For each population with index \(p\) the variable BirthStates[\(p\)] has to be set to the number of possible states at birth. Because individual age is the only i-state variable the Medfly model, the state-at-birth is unique and hence BirthStates[\(0\)] is set to 1.

Note that different populations may have different numbers of states-at-birth.

BirthStates[\(p\)] hence does not need to be the same for all \(p\).

2.3.2.5 Specifying the value of all possible states-at-birth

The next routine to implement defines for every possible state-at-birth with index \(j\) the actual value of the different individual state variables at birth \(\boldsymbol{\phi}_j=\left(\boldsymbol{\phi}_{j1},\ldots,\boldsymbol{\phi}_{jk}\right)\):

Code block 2.3.2.5
/*
 * Specify all the possible states at birth for all individuals in all
 * structured populations in the problem. BirthStateNr represents the index of
 * the state of birth to be specified. Each state at birth should be a single,
 * constant value for each i-state variable.
 *
 * Notice that the first index of the variable 'istate[][]' refers to the
 * number of the structured population, the second index refers to the
 * number of the individual state variable. The interpretation of the latter
 * is up to the user.
 */

void StateAtBirth(double *istate[POPULATION_NR], int BirthStateNr, double E[])
{
  AGE    = 0.0;

  return;
}

For every population (\(p=0, 1,\ldots,\) POPULATION_NR-1) the value of each individual state variable istate[\(p\)][\(i\)] (\(i=0, 1,\ldots,\) I_STATE_DIM-1) has to be assigned a unique value, from which individual development will start at age \(0\). As shown in the example of the Medfly model, if the life history depends on the age of the individual, age should be explicitly included as one of the individual state variables. The program does not automatically include individual age in its characterization of the individual state, even though integration over the entire life history (as a function of age) is carried out. For the Medfly model age is the only individual state variables and set to \(0\) at birth.

The routine StateAtBirth() will be called as many times as there are possible states-at-birth. The variable BirthStateNr indicates the index \(j\) of the state-at-birth in the set \(\left\{\boldsymbol{\phi}_1,\ldots,\boldsymbol{\phi}_m\right\}\) for which the values have to be set in the current invocation of the routine. The routine will thus be called with BirthStateNr set equal to a value in \(0, 1, \dots, m-1\) (Remember the starting index \(0\) in C!). If there are multiple states-at-birth (BirthStates[\(p\)] > 1) the definition of the values of the i-state variables has to depend explicitly on the index BirthStateNr to make the states-at-birth different from each other. Furthermore, if the problem involves multiple structured populations the number of possible states-at-birth can be different for each of them, which might lead to a situation that the routine above is called with a value of the index BirthStateNr that is larger than the maximum number of states-at-birth for a particular population (BirthStateNr \(\geq\) BirthStates[\(p\)]). The program safely ignores such inappropriate state-at-birth specifications.

2.3.2.6 Definition of boundaries between discrete stages

The next routine determines the boundaries between consecutive stages in the individual life history.

Code block 2.3.2.6
/*
 * Specify the threshold determining the end point of each discrete life
 * stage in individual life history as function of the i-state variables and
 * the individual's state at birth for all populations in every life stage.
 *
 * Notice that the first index of the variable 'istate[][]' refers to the
 * number of the structured population, the second index refers to the
 * number of the individual state variable. The interpretation of the latter
 * is up to the user.
 */

void IntervalLimit(int lifestage[POPULATION_NR], double *istate[POPULATION_NR],
                   double *birthstate[POPULATION_NR], int BirthStateNr, double E[],
                   double limit[POPULATION_NR])
{
  if (lifestage[0] == 0)
    limit[0] = AGE - AJ;

  return;
}

In this routine the variable limit[\(p\)] has to be defined, which has as many elements as there are populations (\(p=0\ldots\) POPULATION_NR-1). The life stage that the individual is in at the moment this routine is called, is determined by the variable lifestage[\(p\)], which has a value of \(0\) if the individual is in the first life stage and a value of STAGES-1 if it is in the last life stage. The element limit[\(p\)] should now indicate when the current life stage as given in lifestage[\(p\)] ends. In particular, the program considers the current life stage to end when limit[\(p\)] turns from negative to positive. For the end of the last life stage the death of old age, either by reaching the maximum age MAX_AGE or by reaching the minimum survival threshold MIN_SURVIVAL, does not have to be specified separately, the program takes care of that automatically. In the Medfly model therefore only the end of the larval stage has to be specified, as expressed in lines 16-17 of the code box above.

The threshold value that has to be stored and returned to the program in limit[\(p\)] will depend on the individual state variables, possibly on the individual’s state-at-birth and will be different for individuals in different life stages. For this reason, the routine IntervalLimit() has as arguments lifestage[], specifying the life stage that the individual is currently in, istate[][], the individual state, and birthstate[][], the individual’s state-at-birth.

Like the previous routine, this routine will be called as many times as there are possible states-at-birth, because the state-at-birth may influence the threshold between consecutive life stages. The same holds for the routines discussed in sections 2.3.2.7-2.3.2.10 below, which define changes in the i-state variables, the fecundity and the mortality of individuals, respectively. In essence, individuals with different states-at-birth are treated as constituting subpopulations within the same structured population. Because of the possible dependence on the state-at-birth the variables birthstate[][] and BirthStateNr are passed as arguments to this routine and the once discussed in sections 2.3.2.7-2.3.2.10. These arguments contain the values of the i-state variables and the index in the set \(\left\{\boldsymbol{\phi}_1,\ldots,\boldsymbol{\phi}_m\right\}\), respectively, for which the routine is invoked and for which the threshold between consecutive life stages has to be evaluated.

If the problem involves multiple structured populations and the number of possible states-at-birth differs among them, the routine above may be called with a value of the index BirthStateNr that is larger than the maximum number of states-at-birth for a particular population (BirthStateNr \(\geq\) BirthStates[\(p\)]). Although this circumstance may seem confusing, the user does not have to worry about it, as the program is designed to safely ignore such assignments of thresholds between consecutive life stages, changes in the i-state variables, fecundity and mortality of individuals for states-at-birth with index BirthStateNr \(\geq\) BirthStates[\(p\)]that are inappropriate for the structured population with index \(p\).

Notice that the function header shown in the code box above also contains an array E[] as a variable. This array will contain the values of the environment variables during equilibrium computations of PSPMs (see sections 3.1 to 3.4). In demographic analysis of PSPMs this variable is non-functional and is best ignored, using it in a statement inside the routine may even cause the program to crash. The only reason for the presence of this variable in the function header is to keep the function declaration the same for both demographic and equilibrium analysis computations. In principle, the same model-specific file can hence be used for both types of analysis. The variable E[] will for the same reasons also be part of the headers of the next 4 routines.

Tip: The more advanced user who wants to perform both demographic and equilibrium analysis using the same model-specific file should notice that the array of environment variables E[] can in principle be used inside all the routines, if the dimension ENVIRON_DIM determining the number of environment variables has been set (see code box 3.3.2.1 for details). The appropriate value to use for the environment variables should be assigned to the elements E[\(e\)] in the routine StateAtBirth() (see code box 2.3.2.5), after which it will keep the same value throughout all the subsequent routines.

2.3.2.7 Specification of continuous individual state development

Code block 2.3.2.7
/*
 * Specify the development of individuals as a function of the i-state
 * variables and the individual's state at birth for all populations in every
 * life stage.
 *
 * Notice that the first index of the variables 'istate[][]' and 'development[][]'
 * refers to the number of the structured population, the second index refers
 * to the number of the individual state variable. The interpretation of the
 * latter is up to the user.
 */

void Development(int lifestage[POPULATION_NR], double *istate[POPULATION_NR],
                 double *birthstate[POPULATION_NR], int BirthStateNr, double E[],
                 double development[POPULATION_NR][I_STATE_DIM])
{
  development[0][0] = 1.0;

  return;
}

This routine specifies the right-hand side of the ODE: \[\dfrac{d\boldsymbol{\chi}}{da}\;=\;g(\boldsymbol{\chi}, \boldsymbol{\chi}_b)\] that determines the continuous development of the individual state variables during the life history. In the Medfly model the specification is obviously trivial. More generally, the value of development[\(p\)][\(i\)] determines for each structured population \(p\) the development in the individual state variable \(i\). Notice that these development rates may differ in different life stages, for example growth in body size may be different for juveniles and adults in case adults invest a lot of energy into reproduction. The development rates should then be specified dependent on the current life stage the individual is in. This current life stage at the moment the routine is evaluated is contained in the variable lifestage[\(p\)]. The development rate may furthermore depend on the individual state variables and possibly on the individual’s state-at-birth, which is the reason for istate[][], the individual state, and birthstate[][], the individual’s state-at-birth, as arguments to this routine.

Refer to the remarks in section 2.3.2.6 concerning the dependence on the individual’s state-at-birth.

2.3.2.8 Specification of discrete individual changes at stage transitions

Even though not listed among the basic assumptions of the PSPM in the beginning of this chapter, it is permissible to have discrete changes or jumps in the individual state variables at the transition between two consecutive life stages. If these occur, they should be programmed in the following routine.

Code block 2.3.2.8
/*
 * Specify the possible discrete changes (jumps) in the individual state
 * variables when ENTERING the stage specified by 'lifestage[]'.
 *
 * Notice that the first index of the variable 'istate[][]' refers to the
 * number of the structured population, the second index refers to the
 * number of the individual state variable. The interpretation of the latter
 * is up to the user.
 */

void DiscreteChanges(int lifestage[POPULATION_NR], double *istate[POPULATION_NR],
                     double *birthstate[POPULATION_NR], int BirthStateNr, double E[])
{
  return;
}

This routine is not relevant in case of the Medfly model and hence its contents are empty (apart for the necessary return; statement).

This routine is called whenever a transition between two consecutive life stages is reached during the integration over the individual life history. It should be noted that the value of the variable lifestage[\(p\)] indicates the life stage that is entered, that is, following the current stage boundary. This routine will hence never be called with a value of one of the elements lifestage[\(p\)] equal to \(0\). The discrete changes in the individual state variables have to be implemented by assigning new values to the variables istate[\(p\)][\(i\)]. These assignments may as before depend on the life stage that is entered, as specified by the variable lifestage[], the (old values) of the individual state variables, contained in the argument istate[][], and possibly on the individual’s state-at-birth, specified in the argument birthstate[][]. If no assignment of a value to istate[\(p\)][\(i\)] is implemented, that particular individual state variable will keep its current value.

Refer also to the remarks in section 2.3.2.6 concerning the dependence on the individual’s state-at-birth.

2.3.2.9 Specification of fecundity

The following routine specifies the fecundity as a function of the individual state. The code fragment below implements the function \(\beta(a)=\beta_0e^{-\beta_1(a-A_j)}\) for the Medfly model. It provides a good example of how to assign a different value for a particular life history rate dependent on the life stage that an individual is in. The same approach can also be used in the other routines specifying the life history rates of individuals.

Code block 2.3.2.9
/*
 * Specify the fecundity of individuals as a function of the i-state
 * variables and the individual's state at birth for all populations in every
 * life stage.
 *
 * The number of offspring produced has to be specified for every possible
 * state at birth in the variable 'fecundity[][]'. The first index of this
 * variable refers to the number of the structured population, the second
 * index refers to the number of the birth state.
 *
 * Notice that the first index of the variable 'istate[][]' refers to the
 * number of the structured population, the second index refers to the
 * number of the individual state variable. The interpretation of the latter
 * is up to the user.
 */

void Fecundity(int lifestage[POPULATION_NR], double *istate[POPULATION_NR],
               double *birthstate[POPULATION_NR], int BirthStateNr, double E[],
               double *fecundity[POPULATION_NR])
{
 if (lifestage[0] == 1)             // Only for adults
    {
     fecundity[0][0]  = BETA0*exp(-BETA1*(AGE - AJ));
    }
  else
    fecundity[0][0] = 0;

  return;
}

In this routine not only the fecundity (i.e. the number of offspring produced per unit time) has to be specified, but also the state-at-birth of the produced offspring. Therefore, this routine has to assign values to the matrix fecundity[\(p\)][\(j\)], which determines for the population with index \(p\) the number of offspring produced per unit time with state-at-birth with index \(j\) in the set \(\left\{\boldsymbol{\phi}_1,\ldots,\boldsymbol{\phi}_m\right\}\). This fecundity will certainly depend on the life stage that the individual is in (only adults reproduce), which is contained in the argument lifestage[], and on the individual state variables, i.e. the values of the argument istate[][]), but possibly also on the individual’s state-at-birth, the values and index of which are specified by the arguments birthstate[][] and BirthStateNr, respectively.

In the most common case of a unique state-at-birth and a single structured population, like in the Medfly model, the only valid indices are \(p=0\) and \(j=0\) and hence only the variable fecundity[0][0] has to be assigned.

For more detailed remarks about models with multiple states-at-birth consult section 2.3.2.6.

2.3.2.10 Specification of mortality

The last routine specifies the mortality as a function of the individual state.

Code block 2.3.2.10
/*
 * Specify the mortality of individuals as a function of the i-state
 * variables and the individual's state at birth for all populations in every
 * life stage.
 *
 * Notice that the first index of the variable 'istate[][]' refers to the
 * number of the structured population, the second index refers to the
 * number of the individual state variable. The interpretation of the latter
 * is up to the user.
 */

void Mortality(int lifestage[POPULATION_NR], double *istate[POPULATION_NR],
               double *birthstate[POPULATION_NR], int BirthStateNr, double E[],
               double mortality[POPULATION_NR])
{
  mortality[0] = MU0*exp(MU1*AGE);

  return;
}

For each population the corresponding element of the array mortality[\(p\)] should be assigned the mortality rate, possibly dependent on the life stage the individual is in at the moment this routine is called (given in argument lifestage[]), the current i-state of the individual (given in argument istate[][]) and the individual’s state-at-birth (current values and index in the set \(\left\{\boldsymbol{\phi}_1,\ldots,\boldsymbol{\phi}_m\right\}\) given by birthstate[][] and BirthStateNr, respectively).

In the Medfly model the mortality is not influenced by the life stage specifically and is only age-dependent. The line 16 in the code box above implements the function \(\mu(a)=\mu_0e^{\mu_1a}\).

Refer to the remarks in section 2.3.2.6 concerning the dependence on the individual’s state-at-birth.

2.4 Model analysis

2.4.1 Executing the PSPMdemo function

Once the model has been implemented, you can proceed carrying out its analysis, which in the simplest approach is performed by calling the function PSPMdemo with the name of the file specifying the PSPM passed as a string argument. It is unnecessary to include the extension '.R' or '.h' as part of the file name, the PSPMdemo function will automatically try to locate the appropriate file, checking first for a file implemented in C (with an extension '.h') and subsequently for a file implemented in R (with an extension '.R'). If both a file with an extension '.h' and a file with an extension '.R' are found, the program will use the first one. The program can be forced to use the file with an extension '.R' by including the extension explicitly as part of the file name. Therefore, the invocation PSPMdemo("Medfly") is identical to PSPMdemo("Medfly.h") if the model is implemented in C and to PSPMdemo("Medfly.R") if the model is implemented in R. Furthermore, if the file specifying the PSPM can not be found in the current directory, the PSPMdemo function will ask the user to search in the package directory for a model file with the specified name.

These two calls of the PSPMdemo-function will give the same output as the following invocation of the function with the two optional arguments clean=TRUE and force=TRUE:

Command box 2.4.1.A
> PSPMdemo("Medfly", clean=TRUE, force=TRUE)

Building executable  /Users/andre/programs/PSPM analysis/Tests/Medflydemo.so ...

<...compilation output suppressed in this box...>

#
# Executing : PSPMdemo("Medfly", NULL, NULL, NULL)
#
# Parameter values  : 
#
#   Beta0     :  47             Beta1     :  0.04           AJ        :  11           
#   Mu0       :  0.00095        Mu1       :  0.0581       
#
#       1:PGR[0]       2:Tc[0]    3:S[0][0]     4:S[0][1]     5:S[0][2]     6:S[0][3]     7:S[0][4]
#
      0.41905662   13.16725978   0.00161586   -0.16459366   -0.03198198   -1.52635956   -0.01132532

The PSPMdemo function first compiles the model-specific file using the R command R CMD SHLIB into a dynamically loadable library file, which can subsequently be executed. The output of this compilation step is system specific and hence suppressed in the command box above. The compilation step is only carried out when the executable (on Mac OS X and Linux systems called Medflydemo.so) does not exist, or when the model-specific has been changed since the last compilation of the executable. Furthermore, the compilation step is forced by the invocation of PSPMdemo with the additional argument force=TRUE as in the command box shown above.

When the PSPMdemo function is invoked in the way shown above the output of the computation is only printed to the console, the function does not return any variables or results (as is clear from the boxed material above). Apart from printing the exact command-line that has been used to start the computation, the values of the parameters are printed using the meaningful, model-specific names that are used as labels of the vector elements of the variable DefaultParameters when the model is implemented in R (see section 2.3.1.3) or when the model is implemented in C that are defined as the string elements in the variable parameternames (see section 2.3.2.2). Notice that 3 additional but optional arguments to the function PSPMdemo are reported as being set to NULL, meaning they were not defined.

The numerical output generated by the model is printed as a single line of numbers. The first column of this output contains the computed population growth rate. The second column contains the generation time in the stable population state, which corresponds to the average age at reproduction in the exponentially growing population and is defined as:

\[\int_0^\infty a\,e^{-ra}\beta(\boldsymbol{\chi}(a))\,\mathcal{F}(a)\,da\]

in which \(r\) represents the population growth rate, \(\beta(\boldsymbol{\chi}(a))\) the fecundity of an individual with individual state \(\boldsymbol{\chi}(a)\) at age \(a\) and \(\mathcal{F}(a)\) the probability that an individual survived up to age \(a\). The following columns show the sensitivity of the population growth rate with respect to the model parameters in the order as they are defined in the variable DefaultParameters when the model is implemented in R (see section 2.3.1.3) or in the variable parameter (see section 2.3.2.2) when the model is defined in C. For the Medfly model these are the sensitivities to the 5 model parameters that are printed directly above.

The second method to invoke the PSPMdemo function is with an additional arguments to calculate the population growth rate as a function of one of the model parameters for a range of values of this parameter. This can be achieved by passing as an additional argument to the function a vector of 5 elements of the following form:

c(\(<\)index\(>\),\(<\)starting value\(>\),\(<\)step size\(>\),\(<\)minimum value\(>\),\(<\)maximum value\(>\))

The first element indicates the index of the parameter in the vector DefaultParameters (see section 2.3.1.3) or in the array parameter (see section 2.3.2.2) to vary, the second element of the array indicates its starting value from which to compute the curve of the population growth rate as a function of the parameter, the third value indicates the step size in the parameter along this curve (which can be either positive or negative), while the final two elements of the array indicate the minimum and maximum value of the parameter. The computation of the curve of the population growth rate as a function of the model parameter stops, whenever the minimum or maximum parameter value is reached.

When the model is implemented in R the name of the vector element in the vector DefaultParameters can be used (passed as a string) to identify the parameter to vary, instead of its index.

The following R code illustrates this use of the PSPMdemo function for the Medfly model by computing the population growth rate as a function of \(A_j\) (parameter[2] in C, DefaultParameters[3] in R), starting at the initial and default value of \(A_j=11\) and computing the growth rate for increasing values of the parameter with step size \(0.1\), while limiting the computation to the interval \(11\leq A_j\leq20\). Notice that in C array indices start at the value \(0\), whereas in R vector indices start at 1. The code below provides the command-line for the Medfly model implemented in C.

Command box 2.4.1.B
> output <- PSPMdemo("Medfly.h", c(2, 11, 0.1, 11, 20), c(47, 0.04, 11, 0.00095, 0.0581), c("isort", "0"),
                     clean=TRUE, force=TRUE, debug=FALSE, silent=FALSE)

Building executable  /Users/andre/programs/PSPM analysis/Tests/Medflydemo.so ...

<...compilation output suppressed in this box...>

  1.10000000E+01, 4.19056620E-01
  1.11000000E+01, 4.15884247E-01
  1.12000000E+01, 4.12762685E-01
<...output lines suppressed in this box...>
  1.98000000E+01, 2.53879994E-01
  1.99000000E+01, 2.52772251E-01
  2.00000000E+01, 2.51674454E-01

> cat(output$curvedesc)
 #
 # Executing : PSPMdemo("Medfly", c(2, 11, 0.1, 11, 20), c(47, 0.04, 11, 0.00095, 0.0581), c("isort", "0"))
 #
 # Parameter values  : 
 #
 #  Beta0     :  47             Beta1     :  0.04           AJ        :  11           
 #  Mu0       :  0.00095        Mu1       :  0.0581       
 #
 # Index and name of bifurcation parameter #1                   :  2 (AJ)
 #
 #        1:AJ     2:PGR[0]      3:Tc[0]    4:S[0][0]    5:S[0][1]    6:S[0][2]    7:S[0][3]    8:S[0][4]

> output$curvepoints
        AJ    PGR[0]    Tc[0]      S[0][0]    S[0][1]     S[0][2]   S[0][3]     S[0][4]
 [1,] 11.0 0.4190566 13.16726 0.0016158600 -0.1645937 -0.03198198 -1.526360 -0.01132532
 [2,] 11.1 0.4158843 13.28218 0.0016018800 -0.1642926 -0.03146749 -1.532324 -0.01148047
 [3,] 11.2 0.4127627 13.39705 0.0015881500 -0.1639943 -0.03096572 -1.538315 -0.01163686
<...output lines suppressed in this box...>
[89,] 19.8 0.2538800 23.15013 0.0009190646 -0.1447124 -0.01112761 -2.172537 -0.03082490
[90,] 19.9 0.2527722 23.26222 0.0009146362 -0.1445347 -0.01102745 -2.181510 -0.03112948
[91,] 20.0 0.2516744 23.37427 0.0009102513 -0.1443576 -0.01092869 -2.190525 -0.03143628

Some of the intermediate lines of output generated by R in this case are suppressed for brevity. When the PSPMdemo function is invoked in this way to compute parameter dependence, it generates a single list as output (assigned to the variable output in the command box above), which contains two elements, called curvedesc and curvepoints. The variable output$curvedesc contains the description of the executed calculation, which is the textual information that is also printed to the R console at the end of calculations. In fact, the PSPMdemo function prints its report on the calculations by execution of the statement cat(output$curvedesc, sep=' ').

The output variable output$curvepoints is a two-dimensional array containing columns of computed output with as a first column the value of the parameter, the second column the value of the population growth rate for that particular parameter value and the third column the generation time (the average age at reproduction) in the stable population during the exponential growth phase. The subsequent columns represent the sensitivities of the population growth rate to all model parameters, as discussed before. The output of output$curvepoints in the box above shows that the data indeed start at \(A_j=11\) and that the computation is terminated when a value of \(A_j\) is reached that exceeds the maximum parameter value specified. The data contained in the output variable can subsequently be used for plotting or for further calculations.

2.4.2 Output files generated by the PSPMdemo function

The PSPMdemo function and module generates 2 output files when the function is only performing a single population growth rate calculation and 3 output files when the population growth rate is computed as a function of a model parameter. The name of these files is always of the form <Modelname>-PGR-<NNNN>.<ext>, in which <Modelname> is the same as the name of the file specifying the model excluding its '.h' or '.R' extension, <NNNN> is a 4-digit number that is unique for the current computation and .<ext> is the extension, which can be either .err, .csb or .out. Hence, the invocation of the PSPMdemo function for the Medfly model, as shown in command box 2.4.1.A, generates the output files Medfly-PGR-0000.err and Medfly-PGR-0000.csb, while the invocation of the PSPMdemo function for the Medfly model, as shown in command box 2.4.1.B, generates three output files: Medfly-PGR-0001.err, Medfly-PGR-0001.csb and Medfly-PGR-0001.out. For the 4-digit number <NNNN> in the file name, the program always finds the lowest positive value that is not in use yet. However, whenever the PSPMdemo function is invoked with the (optional) argument clean=TRUE, the PSPMdemo function deletes all output files that have been generated for the particular model studied (all files called <Modelname>-PGR-<NNNN>.err, <Modelname>-PGR-<NNNN>.csb and <Modelname>-PGR-<NNNN>.out, and hence the 4-digit file identification number will restart at 0000 again. Deleting all the output files from previous computations and/or the compiled program executables that the package has generated can also be done separately. The package implements a function PSPMclean(), taking no arguments, to delete all .bif, .err, .csb and .out files and/or all executable files that are present in the current working directory.

The file called <Modelname>-PGR-<NNNN>.err contains information about the numerical progress of the computation. It reports details on the steps take during the Newton iteration, the convergence to the solution, as well as information about the steps taken along the curve that is being computed. This file can be informative in case the computation of a particular curve stops for unknown reasons, but is otherwise of little use.

The file called <Modelname>-PGR-<NNNN>.out contains the same information as is contained in the output variables output$curvedesc and output$curvepoints returned by the PSPMdemo function. The first lines of this file all start with a '#' sign and contain the information about the run performed, which is also contained in output$curvedesc and can be listed by the statement cat(output$curvedesc, sep='\n') (see the command boxes in the previous section). Following this descriptive header the file contains columns with computational results that are also contained in the variable output$curvepoints, that is, the parameter values, population growth rates, generation times and sensitivities of the population growth rate to all model parameters. Command box 2.4.1.B provides an example of the type of output generated by the computational module.

The last output file generated during the population growth rate has a name of the form <Modelname>-PGR-<NNNN>.csb and contains information on the stable population distribution for every parameter value for which the population growth rate is computed. This is a binary file, the content of which can be accessed from R using the function csbread. For example, the file contents of the file Medfly-PGR-0000.csb generated by the computation in command box 2.4.1.B can be listed by:

Command box 2.4.2.A
> csbread("Medfly-PGR-0000.csb")

States in file Medfly-PGR-0000.csb:

    1: State-1.100000E+01
    2: State-1.110000E+01
    3: State-1.120000E+01
<...output lines suppressed in this box...>
   89: State-1.980000E+01
   90: State-1.990000E+01
   91: State-2.000000E+01

Invoking the function csbread with only the file name as argument provides a listing of all the population state stored in the file, one for each of the parameter value for which the population growth rate has been computed. The contents each of these population states can be listed by providing as a second argument to the function csbread either the index of the particular population state in the file or a string with its descriptive name. Therefore, the commands csbread("Medfly-PGR-0000.csb", 3) and csbread("Medfly-PGR-0000.csb", "State-1.120000E+01") are equivalent, producing the following output:

Command box 2.4.2.B
> csbread("Medfly-PGR-0000.csb", 3)
$BifPars
[1] 11.2

$Parameters
[1] 47.00000  0.04000 11.20000  0.00095  0.05810

$PGR
[1] 0.4127627

$Pop00_BirthStates
     Istate00
[1,]        0

$Pop00
         StableDist   Istate00   ReproVal
  [1,] 1.000000e+00  0.0000000   1.000000
  [2,] 8.129859e-01  0.5004307   1.230034
  [3,] 6.609367e-01  1.0008614   1.513004
<...output lines suppressed in this box...>
 [98,] 1.535699e-09 48.5417781   8.421419
 [99,] 1.239033e-09 49.0422089   4.603061
[100,] 1.000000e-09 49.5413326   0.000000

This population state, with index 3 and descriptive name State_1_120000E01, pertains to the parameter value \(A_j=11.2\) as its name suggests. The state is returned by the function csbread as a list, which can hence be assigned to a variable in R with the command state<-csbread("Medfly-PGR-0000.csb", 3). The first element of this list (called $BifPars) contains the value of the bifurcation parameter for this particular state. The second element, an array called $Parameters, contains the values of all the model parameters for which the population growth rate has been computed, while the third member of the list contains the computed population growth rate. In the case of the Medfly model this is a single scalar value, but if the population growth rate is computed for more than one population at a time, the population growth rate values are making up an array as well. The two subsequent elements characterize the stable population distribution, of which the first (called $Pop00_BirthStates) specifies the state at birth of the individuals. The other (called $Pop00) is a two-dimensional array containing in the first column the density profile of the stable population, in the second column the individual state variable and the reproductive value of the individuals in the last column, as shown in the box above.

In the Medfly model individuals are only characterized by their age and hence their is only a single column with individual state variables. If individuals are characterized by more than a single individual state variable the values of these follow in additional columns of the two-dimensional array $Pop00. The last column of this array always contains the reproductive value of an individual. For an explanation of the reproductive value and its computation I refer to De Roos (2008).

2.4.3 Required and optional arguments of PSPMdemo

As shown in R command boxes in section 2.4.1 at least one argument has to be passed to the PSPMdemo function, the base name of the file with the model specification, that is without its '.h' or '.R' extension. It is the only obligatory argument, all other arguments that can be passed to the PSPMdemo function are optional. If the model name is the only argument, the function computes the population growth rate for the default parameter set defined in the '.h' or '.R' file (see command box 2.4.1.A).

The optional second argument to the PSPMdemo function is used to compute the population growth rate over a range of a particular model parameter, as shown in and discussed following command box 2.4.1.B.

The optional third argument of the PSPMdemo function is a 1-dimensional array of model parameter values. When used, this array should have the same length as the number of parameters in the model (the length of the variable DefaultParameters when the model is implemented in R or the value of the constant PARAMETER_NR when implemented in C). When of this length the values will replace the default values of the parameters that are listed in the model specification file. If the array used for this third argument is not of the correct length, it will simply be ignored.

The optional fourth argument of the PSPMdemo function is a vector containing possible options that modify the behavior of the computational module. A useful option is the "test" option, which can be passed to the computational module by using the argument c("test") as fourth argument to the PSPMdemo function. This invokes the computational module in testing mode, which implies that only a single integration of the individual life history is carried out and no iteration to locate the population growth rate is performed. In testing mode the computational module reports on the dynamics of the individual state variables, the survival and the expected number of offspring produced by an individual during its different life stage as well as over its entire life. Testing mode is very useful to discover whether or not the model implementation gives sensible results or not.

Another possible element of the option vector that modifies the behavior of the computational module is the "isort" option, which can be passed to the computational module by using for example c("isort", "1") as fourth argument to the PSPMdemo function (as shown in command box 2.4.1.B). This option modifies the population state output that is stored in the output file, which when using the package in R is a binary file with a name of the form <Modelname>-PGR-<NNNN>.csb (see above). By default the computational module reports the information about the stable population state distribution and the reproductive value for 100 equidistant values of the first individual state variable. More specifically, the range of the first individual state variable that is covered during the entire life of an individual until the moment that it is considered dead (i.e. the maximum age or the minimum survival threshold has been reached) is subdivided into 100 equidistant intervals and the population density function, individual state variables and reproductive value are computed at each of these 100 nodal values of the first state variable. By using the option "isort" the default choice to use the first individual state variable for this subdivision can be changed to the second, third, and so on. Notice though, that the obligatory index value that has to be passed together with the use of the "isort" option follows the C-convention of ordering arrays starting at \(0\) (as opposed to R where array indices start at 1). Therefore, passing c("isort", "0") as option array to the PSPMdemo function is the same as the default behavior: the first individual state variable is used for the subdivision and ordering of the population state distribution, while passing c("isort", "1") would use the second individual state variable for this purpose. Also notice that the default number of subdivisions of the individual state variable and hence the number of nodal values for which the population state distribution is reported can be changed by including a statement of the form

#define COHORT_NR                200

among the definitions of the numerical settings in the model specification when the model is implemented in C (see section 2.3.2.1 and chapter 8) or as one of the elements of the vector NumericalOptions:

NumericalOptions <- c(...
                      COHORT_NR     = 200,
                      ...)

if the model is implemented in R (see section 2.3.1.2 and chapter 8).

The last possible element of the option vector that modifies the behavior of the computational module is the "report" option, which can be passed to the computational module by using for example c("report", "10") as fourth argument to the PSPMdemo function. This option determines how much output the computational module reports to the console. The default value of "report" equals 1, which implies that the software writes the values of every new solution point that it has computed to the R console. A value of 2 for the "report" option means that every other computed solution point is written to the R console, whereas the specification of c("report", "10") as fourth argument to the PSPMdemo function implies that every 10th solution point that is computed is written to the R console.

If necessary the options "test", "isort" and "report" can be combined in arbitrary order, for example, as

c("test", "isort", "1", "report", "10")

Or equivalently,

c("isort", "1", "report", "10", "test")

Four other optional arguments can be passed to the PSPMdemo function: clean, force, debug and silent. These are all boolean arguments that hence have to be passed to the PSPMdemo function as \(<\)option name\(>\)=TRUE or \(<\)option name\(>\)=FALSE, the latter being the default value of all options (Specifying these options as argument is hence only useful when setting them equal to TRUE). Unlike the previous arguments, which all modify the computations to be performed, these options modify the behavior of the PSPMdemo function itself, in particular the compilation of the model specific file into a dynamic library module that can be executed from R. Also unlike all the previous arguments that can be passed, these arguments can be passed in any order and at any position, the PSPMdemo function will filter these 3 optional arguments from the argument list before passing the filtered argument list to the computational routine.

  • Option clean: When clean=TRUE is passed as argument, this argument instructs the PSPMdemo function to delete all result files that have been generated during previous calculations with the model. These result files have names of the form <Modelname>-<Type>-<NNNN>.err, <Modelname>-<Type>-<NNNN>.csb and <Modelname>-<Type>-<NNNN>.out, in which <Modelname> refers to the name of the model (i.e. Medfly in the example model presented in previous sections), <Type> refers to the type of computation that has been performed, which in the case of PSPMdemo equals PGR, and <NNNN> is a unique number that distinguishes consecutive computations of the same type of curve with the same model. Deleting all the output files from previous computations and/or the compiled program executables that the package has generated can also be done separately. The package implements a function PSPMclean(), to delete all .bif, .err, .csb and .out files and/or all executable files that are present in the current working directory.

  • Option force: When force=TRUE is passed as argument, it instructs the PSPMdemo function to force re-compilation of the model specific file into a dynamic library module that can be executed by R. This option will usually not be needed by normal users, as the PSPMdemo function automatically recompiles the computational module when the model specific file with an '.h' or '.R' extension is more recently changed than the compiled dynamic library file. However, if for some unclear reason this automatic recompilation fails, the force option can be used to initiate re-compilation.

  • Option debug: When debug=TRUE is passed as argument, it instructs the PSPMdemo function to turn on debugging flags while compiling the model specific file into a dynamic library module. This option can be useful to detect programming mistakes in the model-specific file that are otherwise hard to track down. The downside is that depending on the version of R that is used, turning on debugging flags during compilation may generate a lot of output, including warnings about standard files of the operating system that are perfectly correct. It is hence not so easy to spot among all these messages the warnings that relate to the model-specific code that has been implemented.

  • Option silent: When silent=TRUE is passed as argument, it instructs the PSPMdemo function to suppress all messages from the compilation of the model specific file into a dynamic library module. This option is useful to prevent cluttering the console with superfluous messages once a model specific file has been tested sufficiently and functions without problems.