This is the standalone Julia library of the dynamically-structured matrix population model sPop2. This version implements both age-dependent and accumulative processes.
Just type this in Julia:
using Pkg
"Population") Pkg.add(
Alternatively, one could clone or download and extract the development version from this GitHub repository, and use the package as follows.
using Pkg
"Absolute path to the Population.jl directory")
Pkg.add(
using Population
The following creates a pseudo-structured population with 10 individuals and iterates it one step with 0 mortality and an Erlang-distributed development time of 20 ± 5 steps.
= sPop2(PopDataSto())
pop , AccErlang())
AddProcess(pop
, 10)
AddPop(pop= (devmn=20.0, devsd=5.0)
pr , completed, poptabledone = StepPop(pop, pr) size
See section Usage examples for further examples.
Let’s begin with a canonical example. Arguably, the straightforward way to model insect development is to use an ordinary differential equations system with exponentially distributed transition times.
Let’s assume that experimental observations of larva development yielded a round figure of 20 days as average development time. This is usually translated to the differential equations reals as an instantaneous rate of α = 1/20.
using DifferentialEquations
function develop!(du,u,p,t)
1] = -(1.0/20.0)*u[1]
du[2] = (1.0/20.0)*u[1]
du[end
= [100.0; 0.0;]
u0 = (0.0, 100.0)
tspan = ODEProblem(develop!,u0,tspan)
prob = solve(prob) sol
Expectedly, this implies that larvae begin developing by the time they emerge from eggs, and keep producing pupae at the same constant rate until a negligible number of larvae is left.
An alternative representation with a structured population can be constructed. For this, we need to know not the rate of pupa production, but the average duration of the larva stage and its variation. Let’s assume that stage duration follows a gamma distribution (Erlang to be precise), which implies that all individuals race to develop out of the larva stage, while some are faster and even more of them are slower.
= sPop2(PopDataDet())
pop , AccErlang())
AddProcess(pop
, 10.0)
AddPop(pop= [0 10.0 0.0]
out for n in 1:50
= (devmn=20.0, devsd=5.0)
pr1 = StepPop(pop, pr1)
ret = vcat(out, [n ret[1] ret[2]])
out end
With a simple modification, we can simulate how the dynamics vary when each individual is given a daily chance of completing its development based on the Erlang-distributed development time assumption.
= sPop2(PopDataSto())
pop , AccErlang())
AddProcess(pop
, 10)
AddPop(pop= [0 10 0.0]
outst for n in 1:50
= (devmn=20.0, devsd=5.0)
pr1 = StepPop(pop, pr1)
ret = vcat(outst, [n ret[1] ret[2]])
outst end
The sPop2 framework allows for age-dependent and accumulative development times.
Process | Parameters | Definition |
---|---|---|
AccErlang | devmn, devsd | Erlang-distributed accumulative process |
AccPascal | devmn, devsd | Pascal-distributed accumulative process |
AccFixed | devmn | Fixed-duration accumulative process |
AgeFixed | devmn | Pascal-distributed age-dependent process |
AgeConst | prob | Constant-rate age-dependent process |
AgeGamma | devmn, devsd | Gamma-distributed age-dependent process |
AgeNbinom | devmn, devsd | Negative binomial-distributed age-dependent process |
Age-dependent | Accumulative |
---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Multiple processes can be added to an sPop2 to represent more complex dynamics. For instance, we can represent survival with a daily constant rate and development with an Erlang-distributed accumulative process. The processes are executed in the order they are added to the sPop2.
= sPop2(PopDataDet())
a , AgeConst(), AccErlang())
AddProcess(a, 100.0)
AddPop(a= [0 GetPop(a) 0.0 0.0]
ret for i in 0:100
= (prob=1.0/60.0,)
pr1 = (devmn=30.0, devsd=5.0)
pr2 = StepPop(a, pr1, pr2)
out = vcat(ret, [i+1 out[1] out[2][1] out[2][2]])
ret end
The above instruction represent a life stage with 0.017 daily mortality and 30 ± 5 days of development time.
In this section, we will attempt to reproduce Figure 1 in the original age-dependent sPop model. We will represent an adult female mosquito with a lifetime of 20 ± 2 days. The female enters a cyclic process of obtaining bloodmeal and egg development, which takes about 2 days. However, at the end of each ovipositioning, her expected lifetime decreases by 2 days. I assure you that the actual physiology is far more complicated than this!
In order to represent the lifetime of this female mosquito, we will employ 3 processes:
# Declare an sPop2 population with deterministic dynamics
= sPop2(PopDataDet())
a # Define three processes in this order: Mortality, Gonotropic cycle, Ovipositioning
, AgeCustom(custom, AgeStepper), AgeGamma(), AgeDummy()) AddProcess(a
The 3rd process is a dummy (AgeDummy), which does not affect the population or even does not posess a time counter. The 2nd process is the regular age-dependent gamma-distributed development process (AgeGamma).
The mortality process, on the other hand, is defined with a custom function and uses the AgeStepper. We included this stepper in process declaration, because we require that the status indicator be an age counter, i.e. the number of steps the sPop2 is iterated is kept in the status indicator (see qkey below). We declare the custom function as the following.
function custom(heval::Function, d::Number, q::Number, k::Number, theta::Number, qkey::Tuple)
= 480.0 - (qkey[3] > 4 ? 240.0 : 48.0 * qkey[3])
devmn = 0.1 * devmn
devsd = (devmn=devmn, devsd=devsd)
pr , theta, stay = sPop2.age_gamma_pars(pr)
kreturn sPop2.age_hazard_calc(sPop2.age_gamma_haz, 0, qkey[1], k, theta, qkey)
end
With this function, we override an internal mechanism used by all other processes to calculate the probability of exit (need this be due to mortality, development, or something else) from the sPop2 population. This generic functional form takes the following parameters.
Of all these, we use the status indicators to calculate the mean and standard deviation of mortality based on the third (dummy) process.
= 480.0 - (qkey[3] > 4 ? 240.0 : 48.0 * qkey[3]) # qkey[3]: Ovipositioning
devmn = 0.1 * devmn
devsd = (devmn=devmn, devsd=devsd) pr
Then, we calculate the parameters of the corresponding gamma distribution using the internal function
, theta, stay = sPop2.age_gamma_pars(pr) k
and, with these, we calculate the cumulative probability of daily mortality
, 0, qkey[1], k, theta, qkey) # qkey[1]: Mortality sPop2.age_hazard_calc(sPop2.age_gamma_haz
Overall, the script to model the dynamics is given below.
function custom(heval::Function, d::Number, q::Number, k::Number, theta::Number, qkey::Tuple)
= 480.0 - (qkey[3] > 4 ? 240.0 : 48.0 * qkey[3])
devmn = 0.1 * devmn
devsd = (devmn=devmn, devsd=devsd)
pr , theta, stay = sPop2.age_gamma_pars(pr)
kreturn sPop2.age_hazard_calc(sPop2.age_gamma_haz, 0, qkey[1], k, theta, qkey)
end
= sPop2(PopDataDet())
a # Mortality, Gonotropic cycle, Ovipositioning
, AgeCustom(custom, AgeStepper), AgeGamma(), AgeDummy())
AddProcess(a,1000.0)
AddPop(a= [0 0.0]
ret for i in 0:480
= NamedTuple{}()
pr1 = (devmn=50.0, devsd=10.0)
pr2 = StepPop(a, pr1, pr2, pr1)
out for (q,n) in out[3][2]
# Please note that this step is essential!
, n, q.key[1], 0, q.key[3]+one(q.key[3]))
AddPop(aend
= vcat(ret, [i+1 out[2][2]])
ret end
return ret
At each step, StepPop returns the following tuple
The script adds all individuals completing a gonotrophic cycle (out[3][2]) back to the population one by one. While doing so, their status indicators are updated manually.