Skip to contents

This module called macdonald was included in ramp.xds for historical reasons. In this form, the model is difficult to extend because of problems formulating the delay. The model, as presented here, has fixed parameter values. It can not be extended with forcing. To address these problems, we have developed a fully extensible delay differential equation model that extends macdonald, the generalized, non-autonomous Ross-Macdonald module GeRM.

Differential Equations

Macdonald’s Model

Macdonald’s model for the sporozoite rate, published in 19521 has played an important role in studies of malaria. Later that same year, Macdonald introduced a formula for the basic reproductive rate, now called R0.R_0.2 Macdonald gives credit to his colleague Armitage for the mathematics. Armitage’s paper would be published the next year,3 but that paper does not present the model as a system of differential equations.

A simple system of differential equations that is consistent with Macdonald’s model for the sporozoite rate has three parameters and one term:

  • the human blood feeding rate, aa

  • the extrinsic incubation period, τ\tau

  • the mosquito death rate, gg; or the probability of surviving one day, p=egp=e^{-g}, so g=lnpg=-\ln p

  • the fraction of bites on humans that infect a mosquito, κ\kappa

Let yy denote the fraction of mosquitoes that are infected. The dynamics are given by: dydt=aκ(1y)gy\frac{dy}{dt} = a \kappa (1-y) - g y Let zz denote the fraction of mosquitoes that are infectious. The model is a delay differential equation. Let yτy_\tau denote the value of yy at time tτ.t-\tau. If the parameters and terms are constant, then:
dzdt=egτaκ(1yτ)gz\frac{dz}{dt} = e^{-g\tau} a \kappa (1-y_\tau) - g z The model has a steady state for the fraction infected: y=aκaκ+g\bar y = \frac{a \kappa} {a \kappa + g} The fraction infectious, also called the sporozoite rate, is z=aκaκ+gegτ.\bar z = \frac{a \kappa} {a \kappa + g}e^{-g\tau}. Macdonald used pp so his formula was: z=aκaκlnpepτ\bar z = \frac{a \kappa} {a \kappa -\ln p}e^{-p\tau}

To generate the formula for R0,R_0, Macdonald introduces another variable and three additional parameters:

  • the ratio of mosquitoes to humans, mm

  • the rate infections clear, rr

  • the fraction of infectious bites that infect a human, bb

The fraction of infected and infectious humans, x,x, is given by the equation:

dxdt=maz(1y)rx\frac{dx}{dt} = m a z (1-y) - r x and the model assumes that κ=x.\kappa = x. The formula for R0R_0 in this system is: R0=mba2gregτ=mba2(lnp)repτR_0 = \frac{m b a^2}{gr} e^{-g\tau} = \frac{m b a^2}{(-\ln p)r} e^{-p\tau} In this form, the model is difficult to use or extend.

Aron & May

The mosquito module in ramp.xds called macdonald is based on a model first published in 1982 by Joan Aron and Robert May4. It includes state variables for total mosquito density MM, infected mosquito density YY, and infectious mosquito density ZZ. In this model, the blood feeding rate is split into an overall blood feeding rate, ff, and the human fraction, qq such that a=fq.a=fq. The Aron & May’s equations are: dMdt=Λ(t)gMdYdt=fqκ(MY)gYdZdt=egτfqκτ(MτYτ)gZ\begin{array}{rl} \frac{dM}{dt} &= \Lambda(t) - g M \\ \frac{dY}{dt} &= fq\kappa(M-Y) - g Y \\ \frac{dZ}{dt} &= e^{-g\tau}fq\kappa_\tau(M_\tau-Y_\tau) - g Z \\ \end{array}

Delay Differential Equations

The module called macdonald has been extended beyond the Aron & May formulation to include spatial dynamics and parity. To formulate the spatial model, a spatial domain is sub-divided into a set of patches. Variable and parameter names do not change, but they can now represent vectors of length np.n_p. To formulate the demographic matrix, denoted Ω,\Omega, that describes mosquito mortality, emigration, and other loss from the system. We let σ\sigma denote the emigration rate and $\cal K$ the mosquito dispersal matrix. We also introduce a parameter, μ\mu to model the fraction of mosquitoes that are lost to emigration from each patch. $$\Omega = \mbox{diag} \left(g\right) + \left(\mbox{diag} \left(1-\mu\right) - \cal K\right) \cdot \mbox{diag} \left(\sigma\right) $$

Dynamics

Ṁ=ΛΩMṖ=diag(f)(MP)ΩPẎ=diag(fqκ)(MY)ΩYŻ=Ż=eΩτdiag(fqκtτ)(MtτYtτ)ΩZ\begin{array}{rl} \dot{M} & = \Lambda - \Omega\cdot M \\ \dot{P} & = \mbox{diag}(f) \cdot (M-P) - \Omega \cdot P\\ \dot{Y} & = \mbox{diag}(fq\kappa) \cdot (M-Y) - \Omega \cdot Y \\ \dot{Z} & = \dot{Z} = e^{-\Omega \tau} \cdot \mbox{diag}(fq\kappa_{t-\tau}) \cdot (M_{t-\tau}-Y_{t-\tau}) - \Omega \cdot Z \end{array}

Ordinary Differential Equations

We note that the module SI provides a reasonably simple approximating model that has no delay, but in computing fqZ,fqZ, it includes mortality and dispersal that would have occurred during the EIP: Z=eΩτY Z = e^{-\Omega \tau} \cdot Y The implementation of SI is similar in spirit to the simple model presented in Smith & McKenzie (2004)5. in that mortality and dispersal over the EIP is accounted for, but the time lag is not. While transient dynamics of the ODE model will not equal the DDE model, they have the same equilibrium values, and so for numerical work requiring finding equilibrium points, the faster ODE model can be safely substituted.

Equilibrium solutions

There are two logical ways to begin solving the non-trivial equilibrium. The first assumes Λ\Lambda is known, which implies good knowledge of mosquito ecology. The second assumes ZZ is known, which implies knowledge of the biting rate on the human population. We show both below.

Starting with Λ\Lambda

Given Λ\Lambda we can solve:

M=Ω1Λ M = \Omega^{-1} \cdot \Lambda Then given MM we set Ẏ\dot{Y} to zero and factor out YY to get:

Y=(diag(fqκ)+Ω)1diag(fqκ)M Y = (\mbox{diag}(fq\kappa) + \Omega)^{-1} \cdot \mbox{diag}(fq\kappa) \cdot M We set Ż\dot{Z} to zero to get:

Z=Ω1eΩτdiag(fqκ)(MY) Z = \Omega^{-1} \cdot e^{-\Omega \tau} \cdot \mbox{diag}(fq\kappa) \cdot (M-Y)

Because the dynamics of PP are independent of the infection dynamics, we can solve it given MM as:

P=(Ω+diag(f))1diag(f)M P = (\Omega + \mbox{diag}(f))^{-1} \cdot \mbox{diag}(f) \cdot M

Starting with ZZ

It is more common that we start from an estimate of ZZ, perhaps derived from an estimated EIR (entomological inoculation rate). Given ZZ, we can calculate the other state variables and Λ\Lambda. For numerical implementation, note that (eΩτ)1=eΩτ(e^{-\Omega\tau})^{-1} = e^{\Omega\tau}.

MY=diag(1/fqκ)(eΩτ)1ΩZ M-Y = \mbox{diag}(1/fq\kappa) \cdot (e^{-\Omega\tau})^{-1} \cdot \Omega \cdot Z

Y=Ω1diag(fqκ)(MY) Y = \Omega^{-1} \cdot \mbox{diag}(fq\kappa) \cdot (M-Y)

M=(MY)+Y M = (M - Y) + Y

Λ=ΩM \Lambda = \Omega \cdot M We can use the same equation for PP as above.

Example

Here we show an example of starting and solving a model at equilibrium. Please note that this only runs this adult mosquito model and that most users should read our fully worked example to run a full simulation.

Ross-Macdonald

dydt=aκ(1y)gydzdt=egτqκτ[1yτ]gz \begin{array}{rl} \frac{\textstyle{dy}}{\textstyle{dt}} & = a \kappa \left(1 -y \right) - g y \\ \frac{\textstyle{dz}}{\textstyle{dt}} & = e^{-g\tau} q \kappa_\tau \left[1 - y_\tau \right] - g z \end{array}

The long way

Here we set up some parameters for a simulation with 3 patches.

HPop = rep(1, 3)
nPatches <- 3
f <- rep(0.3, nPatches)
q <- rep(0.9, nPatches)
g <- rep(1/20, nPatches)
sigma <- rep(1/10, nPatches)
mu <- rep(0, nPatches)
eip <- 12
nu <- 1/2
eggsPerBatch <- 30
MYZo = list(f=f,q=q,g=g,sigma=sigma,mu=mu,eip=eip,nu=nu,eggsPerBatch=eggsPerBatch)
calK <- matrix(0, nPatches, nPatches)
calK[1, 2:3] <- c(0.2, 0.8)
calK[2, c(1,3)] <- c(0.5, 0.5)
calK[3, 1:2] <- c(0.7, 0.3)
calK <- t(calK)

Omega <- compute_Omega_xde(g, sigma, mu, calK)
Upsilon <- expm::expm(-Omega * eip)

Now we set up the parameter environment with the correct class using ramp.xds::make_parameters_MYZ_RM_xde, noting that we will be solving as an ode.

Now we set the values of κ\kappa and Λ\Lambda and solve for the equilibrium values.

kappa <- c(0.1, 0.075, 0.025)
Xo = list(kappa=kappa)
Lambda <- c(5, 10, 8)
Lo = list(Lambda=Lambda)
Omega_inv <- solve(Omega)
M_eq <- as.vector(Omega_inv %*% Lambda)
P_eq <- as.vector(solve(diag(f, nPatches) + Omega) %*% diag(f, nPatches) %*% M_eq)
Y_eq <- as.vector(solve(diag(f*q*kappa) + Omega) %*% diag(f*q*kappa) %*% M_eq)
Z_eq <- as.vector(Omega_inv %*% Upsilon %*% diag(f*q*kappa) %*% (M_eq - Y_eq))
MYZo$M=M_eq
MYZo$P=P_eq
MYZo$Y=Y_eq
MYZo$Z=Z_eq

We use ramp.xds::make_inits_MYZ_RM_xde to store the initial values. These equations have been implemented to compute Υ\Upsilon dynamically, so we attach Upsilon as initial values:

params <- make_xds_template("dde", "mosy", nPatches, 1:3, 1:3)
params <- setup_MYZpar("macdonald", params, 1, MYZo)  
params <- setup_MYZinits(params, 1, MYZo)
params <- setup_Xpar("trivial", params, 1, Xo) 
params <- setup_Xinits(params, HPop, 1)
params <- setup_Lpar("trivial", params, 1, Lo)
params <- setup_Linits(params, 1, Lo)
params <- setup_Hpar_static(params, 1)

We set the indices with ramp.xds::make_indices.

params = make_indices(params)
params <- change_calK(calK, params, 1)
params$Lambda[[1]] = Lambda
params$kappa[[1]] = kappa 

Then we can set up the initial conditions vector and use deSolve::ode to solve the model. Normally these values would be computed within ramp.xds::xDE_diffeqn. Here, we set up a local version:

y0 = get_MYZinits(params, 1) 
y0 = as.vector(unlist(y0))
params <- MBionomics(0,y0,params, 1)

dMYZdt_local = func=function(t, y, pars) {
  list(dMYZdt(t, y, pars, 1))
}

out <- deSolve::dede(y = y0, times = 0:50, dMYZdt_local, parms=params, 
                    method = 'lsoda') 
out1 <- out

The output is plotted below. The flat lines shown here is a verification that the steady state solutions that we computed above match the steady states computed by solving the equations:

out = out[, 1:13]
colnames(out)[params$MYZpar$M_ix+1] <- paste0('M_', 1:params$nPatches)
colnames(out)[params$MYZpar$P_ix+1] <- paste0('P_', 1:params$nPatches)
colnames(out)[params$MYZpar$Y_ix+1] <- paste0('Y_', 1:params$nPatches)
colnames(out)[params$MYZpar$Z_ix+1] <- paste0('Z_', 1:params$nPatches)

out <- as.data.table(out)
out <- melt(out, id.vars = 'time')
out[, c("Component", "Patch") := tstrsplit(variable, '_', fixed = TRUE)]
out[, variable := NULL]

ggplot(data = out, mapping = aes(x = time, y = value, color = Patch)) +
  geom_line() +
  facet_wrap(. ~ Component, scales = 'free') +
  theme_bw()

Setup Utilities

In the vignette above, we set up a function to solve the differential equation. We hope this helps the end user to understand how ramp.xds works under the hood, but the point of ramp.xds is to lower the costs of building, analyzing, and using models. The functionality in ramp.xds can handle this case – we can set up and solve the same model using built-in setup utilities. Learning to use these utilities makes it very easy to set up other models without having to learn some internals.

To set up a model with the parameters above, we make three list with the named parameters and their values. We also attach to the list the initial values we want to use, if applicable. For the Ross-Macdonald adult mosquito model, we attach the parameter values:

Each one of the dynamical components has a configurable trivial algorithm that computes no derivatives, but passes its output as a parameter (see human-trivial.R. To configure Xpar, we attach the values of kappa to a list:

Xo = list(kappa =  c(0.1, 0.075, 0.025))

Similarly, we configure the trivial algorithm for aquatic mosquitoes (see aquatic-trivial.R).

Lo = list(Lambda = c(5, 10, 8))

To set up the model, we call xds_setup with

  • nPatches is set to 3

  • MYZname is set to “macdonald” to run the Ross-Macdonald model for adult mosquitoes; to pass our options, we write MYZopts = MYZo; and finally, the dispersal matrix calK is passed as calK=calK

  • Xname is set to “trivial” to run the trivial module for human infection dynamics

  • Lname is set to “trivial” to run the trivial module aquatic mosquitoes

Otherwise, setup takes care of all the internals:

xds_setup(MYZname = "macdonald", Xname = "trivial", Lname = "trivial",  
    nPatches=3, calK=calK, membership = c(1:3), 
    residence = c(1:3), HPop = HPop,
    MYZopts = MYZo, Xopts = Xo, Lopts = Lo) -> MYZeg

Now, we can solve the equations using xds_solve and compare the output to what we got above. If they are identical, the two objects should be identical, so can simply add the absolute value of their differences:

xds_solve(MYZeg, Tmax=50, dt=1) -> MYZeg 
out2 <- MYZeg$outputs$orbits$deout
sum(abs(out1-out2))==0 
#> [1] TRUE
rbind(tail(out2,1)[1 + 1:12],
c(M_eq, P_eq, Y_eq, Z_eq))
#>         [,1]     [,2]     [,3]     [,4]     [,5]     [,6]     [,7]     [,8]
#> [1,] 157.868 123.4518 178.6802 140.3517 98.87622 155.0578 48.09598 27.56429
#>          [,9]    [,10]    [,11]    [,12]
#> [1,] 41.03339 24.96933 13.31699 25.75651