odinodin2odin\[\begin{gather*} \frac{dS}{dt} = -\beta S \frac{I}{N}\\ \frac{dI}{dt} = \beta S \frac{I}{N} - \gamma I\\ \frac{dR}{dt} = \gamma I \end{gather*}\]
In the book: Getting started with odin
Things to note:
initial and deriv pairparameter(), default values can be given in brackets though not requiredodin2
You can also compile code from a file by its path e.g. odin("models/sir-basic.R").
In the book: Getting started with odin
dust2
The output has dimensions number of states x number of timepoints
In the book: Getting started with odin
Output can be nicely unpacked into the different states using dust_unpack_state
In the book: Getting started with odin
Upon creation all systems start with a state full of zeros.
Typically we need to set the state. We used
to set the state according to the initial conditions defined in the odin code. We can also set the state using a new state vector e.g.
In the book: Getting started with odin
The order of states in a system can be found via
or
In the book: Getting started with odin
pars)pars = list() - an empty listpars = list(beta = 0.35, gamma = 0.15) would use those values for beta and gamma and the default values for all other parametersIn the book: Getting started with odin
update(S) <- S - n_SI
update(I) <- I + n_SI - n_IR
update(R) <- R + n_IR
initial(S) <- N - I0
initial(I) <- I0
initial(R) <- 0
p_SI <- 1 - exp(-beta * I / N * dt)
p_IR <- 1 - exp(-gamma * dt)
n_SI <- Binomial(S, p_SI)
n_IR <- Binomial(I, p_IR)
N <- parameter(1000)
I0 <- parameter(10)
beta <- parameter(0.2)
gamma <- parameter(0.1)\[\begin{gather*} S(t + \Delta t) = S(t) - n_{SI}\\ I(t + \Delta t) = I(t) + n_{SI} - n_{IR}\\ R(t + \Delta t) = R(t) + n_{IR} \end{gather*}\]
dt is a special parameterinitial and update pairIn the book: Stochasticity
update(S) <- S - n_SI
update(I) <- I + n_SI - n_IR
update(R) <- R + n_IR
initial(S) <- N - I0
initial(I) <- I0
initial(R) <- 0
p_SI <- 1 - exp(-beta * I / N * dt)
p_IR <- 1 - exp(-gamma * dt)
n_SI <- Binomial(S, p_SI)
n_IR <- Binomial(I, p_IR)
N <- parameter(1000)
I0 <- parameter(10)
beta <- parameter(0.2)
gamma <- parameter(0.1)In the book: Stochasticity
odin2sir <- odin({
update(S) <- S - n_SI
update(I) <- I + n_SI - n_IR
update(R) <- R + n_IR
initial(S) <- N - I0
initial(I) <- I0
initial(R) <- 0
p_SI <- 1 - exp(-beta * I / N * dt)
p_IR <- 1 - exp(-gamma * dt)
n_SI <- Binomial(S, p_SI)
n_IR <- Binomial(I, p_IR)
N <- parameter(1000)
I0 <- parameter(10)
beta <- parameter(0.2)
gamma <- parameter(0.1)
})In the book: Stochasticity
odin2exp()odin2 - a full list can be found hereBinomial(S, p_SI) for a Binomial random draw with size = S and prob = p_SItime in odintime is a special (continuous) variable in odin modelstime is updated in increments of size dtIn the book: On the nature of time
In the book: Stochasticity
In the book: Stochasticity
zero_everysir <- odin({
update(S) <- S - n_SI
update(I) <- I + n_SI - n_IR
update(R) <- R + n_IR
update(incidence) <- incidence + n_SI
p_SI <- 1 - exp(-beta * I / N * dt)
p_IR <- 1 - exp(-gamma * dt)
n_SI <- Binomial(S, p_SI)
n_IR <- Binomial(I, p_IR)
initial(S) <- N - I0
initial(I) <- I0
initial(R) <- 0
initial(incidence, zero_every = 1) <- 0
N <- parameter(1000)
I0 <- parameter(10)
beta <- parameter(0.2)
gamma <- parameter(0.1)
})In the book: On the nature of time
sys <- dust_system_create(sir, pars = list(), dt = 1 / 128)
dust_system_set_state_initial(sys)
t <- seq(0, 20, by = 1 / 128)
y <- dust_system_simulate(sys, t)
y <- dust_unpack_state(sys, y)
plot(t[t %% 1 == 0], y$incidence[t %% 1 == 0], type = "o", pch = 19,
ylab = "Infection incidence", xlab = "Time")
lines(t, y$incidence, col = "red")
In the book: On the nature of time
timesir <- odin({
update(S) <- S - n_SI
update(I) <- I + n_SI - n_IR
update(R) <- R + n_IR
update(incidence) <- incidence + n_SI
seed <- if (time == seed_time) seed_size else 0
p_SI <- 1 - exp(-beta * I / N * dt)
p_IR <- 1 - exp(-gamma * dt)
n_SI <- min(seed + Binomial(S, p_SI), S)
n_IR <- Binomial(I, p_IR)
initial(S) <- N
initial(I) <- 0
initial(R) <- 0
initial(incidence, zero_every = 1) <- 0
N <- parameter(1000)
beta <- parameter(0.2)
gamma <- parameter(0.1)
seed_time <- parameter()
seed_size <- parameter()
})In the book: Inputs that vary over time
timeIn the book: Inputs that vary over time
interpolateThe interpolate function in odin can be used for time-varying parameters, with specification of
In the book: Inputs that vary over time
interpolatesir <- odin({
update(S) <- S - n_SI
update(I) <- I + n_SI - n_IR
update(R) <- R + n_IR
update(incidence) <- incidence + n_SI
beta <- interpolate(beta_time, beta_value, "constant")
beta_time <- parameter()
beta_value <- parameter()
dim(beta_time, beta_value) <- parameter(rank = 1)
p_SI <- 1 - exp(-beta * I / N * dt)
p_IR <- 1 - exp(-gamma * dt)
n_SI <- Binomial(S, p_SI)
n_IR <- Binomial(I, p_IR)
initial(S) <- N - I0
initial(I) <- I0
initial(R) <- 0
initial(incidence, zero_every = 1) <- 0
N <- parameter(1000)
I0 <- parameter(10)
gamma <- parameter(0.1)
})In the book: Inputs that vary over time
interpolatepars <- list(beta_time = c(0, 30), beta_value = c(0.2, 1))
sys <- dust_system_create(sir, pars = pars, dt = 0.25)
dust_system_set_state_initial(sys)
t <- seq(0, 100)
y <- dust_system_simulate(sys, t)
y <- dust_unpack_state(sys, y)
plot(t, y$I, type = "l", xlab = "Time", ylab = "Infected population")
abline(v = 30, lty = 3)
In the book: Inputs that vary over time
sir_age <- odin({
# Equations for transitions between compartments by age group
update(S[]) <- S[i] - n_SI[i]
update(I[]) <- I[i] + n_SI[i] - n_IR[i]
update(R[]) <- R[i] + n_IR[i]
update(incidence) <- incidence + sum(n_SI)
# Individual probabilities of transition:
p_SI[] <- 1 - exp(-lambda[i] * dt) # S to I
p_IR <- 1 - exp(-gamma * dt) # I to R
# Calculate force of infection
# age-structured contact matrix: m[i, j] is mean number
# of contacts an individual in group i has with an
# individual in group j per time unit
m <- parameter()
# here s_ij[i, j] gives the mean number of contacts an
# individual in group i will have with the currently
# infectious individuals of group j
s_ij[, ] <- m[i, j] * I[j]
# lambda[i] is the total force of infection on an
# individual in group i
lambda[] <- beta * sum(s_ij[i, ])
# Draws from binomial distributions for numbers
# changing between compartments:
n_SI[] <- Binomial(S[i], p_SI[i])
n_IR[] <- Binomial(I[i], p_IR)
initial(S[]) <- S0[i]
initial(I[]) <- I0[i]
initial(R[]) <- 0
initial(incidence, zero_every = 1) <- 0
S0 <- parameter()
I0 <- parameter()
beta <- parameter(0.2)
gamma <- parameter(0.1)
n_age <- parameter()
dim(S, S0, n_SI, p_SI) <- n_age
dim(I, I0, n_IR) <- n_age
dim(R) <- n_age
dim(m, s_ij) <- c(n_age, n_age)
dim(lambda) <- n_age
})In the book: Arrays
Here m[i, j] is the mean number of contacts per time unit for an individual in group i has with an individual in group j
In the book: Arrays
sys <- dust_system_create(sir_age, pars = pars, dt = 0.25)
dust_system_set_state_initial(sys)
t <- seq(0, 100)
y <- dust_system_simulate(sys, t)
y <- dust_unpack_state(sys, y)
matplot(t, t(y$I), type = "l", lty = 1, col = c("red", "blue"),
xlab = "Time", ylab = "Infected population")
legend("topright", c("children", "adults"), col = c("red", "blue"), lty = 1)
In the book: Arrays
dim equationi, j etc on LHS - indexing on the LHS is impliciti, j, k, l, i5, i6, i7, i8sum, prod, min, max - can be applied over entire array or slicesIn the book: Arrays
sir_age_vax <- odin({
# Equations for transitions between compartments by age group
update(S[, ]) <- new_S[i, j]
update(I[, ]) <- I[i, j] + n_SI[i, j] - n_IR[i, j]
update(R[, ]) <- R[i, j] + n_IR[i, j]
update(incidence) <- incidence + sum(n_SI)
# Individual probabilities of transition:
p_SI[, ] <- 1 - exp(-rel_susceptibility[j] * lambda[i] * dt) # S to I
p_IR <- 1 - exp(-gamma * dt) # I to R
p_vax[, ] <- 1 - exp(-eta[i, j] * dt)
# Force of infection
m <- parameter() # age-structured contact matrix
s_ij[, ] <- m[i, j] * sum(I[j, ])
lambda[] <- beta * sum(s_ij[i, ])
# Draws from binomial distributions for numbers changing between
# compartments:
n_SI[, ] <- Binomial(S[i, j], p_SI[i, j])
n_IR[, ] <- Binomial(I[i, j], p_IR)
# Nested binomial draw for vaccination in S
# Assume you cannot move vaccine class and get infected in same step
n_S_vax[, ] <- Binomial(S[i, j] - n_SI[i, j], p_vax[i, j])
new_S[, 1] <- S[i, j] - n_SI[i, j] - n_S_vax[i, j] + n_S_vax[i, n_vax]
new_S[, 2:n_vax] <- S[i, j] - n_SI[i, j] - n_S_vax[i, j] + n_S_vax[i, j - 1]
initial(S[, ]) <- S0[i, j]
initial(I[, ]) <- I0[i, j]
initial(R[, ]) <- 0
initial(incidence, zero_every = 1) <- 0
# User defined parameters - default in parentheses:
S0 <- parameter()
I0 <- parameter()
beta <- parameter(0.0165)
gamma <- parameter(0.1)
eta <- parameter()
rel_susceptibility <- parameter()
# Dimensions of arrays
n_age <- parameter()
n_vax <- parameter()
dim(S, S0, n_SI, p_SI) <- c(n_age, n_vax)
dim(I, I0, n_IR) <- c(n_age, n_vax)
dim(R) <- c(n_age, n_vax)
dim(m, s_ij) <- c(n_age, n_age)
dim(lambda) <- n_age
dim(eta) <- c(n_age, n_vax)
dim(rel_susceptibility) <- c(n_vax)
dim(p_vax, n_S_vax, new_S) <- c(n_age, n_vax)
})In the book: Arrays
Multiline equations can be used to deal with boundary conditions, e.g. we have
which we could also write as
or another way of writing this would be to use if ... else
In the book: Arrays
Dimensions can be hard-coded:
or can be soft-coded via parameters:
In the book: Arrays
Array parameters can have dimensions explicitly declared
or detected when the parameters are passed into the system - but a dim equation is needed where you must explicitly state the rank:
In the book: Arrays
Dimensions of one array can be declared as the same as the dimensions of another:
or dimensions of arrays that have the same dimensions can be declared collectively:
In the book: Arrays
Let’s revisit the stochastic SIR model, which compiles successfully:
sir <- odin({
update(S) <- S - n_SI
update(I) <- I + n_SI - n_IR
update(R) <- R + n_IR
update(incidence) <- incidence + n_SI
p_SI <- 1 - exp(-beta * I / N * dt)
p_IR <- 1 - exp(-gamma * dt)
n_SI <- Binomial(S, p_SI)
n_IR <- Binomial(I, p_IR)
initial(S) <- N - I0
initial(I) <- I0
initial(R) <- 0
initial(incidence, zero_every = 1) <- 0
N <- parameter(1000)
I0 <- parameter(10)
beta <- parameter(0.2)
gamma <- parameter(0.1)
})
sir
#>
#> ── <dust_system_generator: odin_system> ────────────────────────────────────────
#> ℹ This system runs in discrete time with a default dt of 1
#> ℹ This system has 4 parameters
#> → 'N', 'I0', 'beta', and 'gamma'
#> ℹ Use dust2::dust_system_create() (`?dust2::dust_system_create()`) to create a system with this generator
#> ℹ Use coef() (`?stats::coef()`) to get more information on parametersIn the book: Debugging
Now let’s remove the initial(R) equation
sir <- odin({
update(S) <- S - n_SI
update(I) <- I + n_SI - n_IR
update(R) <- R + n_IR
update(incidence) <- incidence + n_SI
p_SI <- 1 - exp(-beta * I / N * dt)
p_IR <- 1 - exp(-gamma * dt)
n_SI <- Binomial(S, p_SI)
n_IR <- Binomial(I, p_IR)
initial(S) <- N - I0
initial(I) <- I0
initial(incidence, zero_every = 1) <- 0
N <- parameter(1000)
I0 <- parameter(10)
beta <- parameter(0.2)
gamma <- parameter(0.1)
})
#> Error in `odin()`:
#> ! Variables used in 'update()' do not have 'initial()' calls
#> ✖ Did not find 'initial()' calls for 'R'
#> → Context:
#> update(R) <- R + n_IR
#> ℹ For more information, run `odin2::odin_error_explain("E2003")`In the book: Debugging
We can run odin_error_explain() with the corresponding error code as suggested (clickable in RStudio) for more information
odin_error_explain("E2003")
#>
#> ── E2003 ───────────────────────────────────────────────────────────────────────
#> Variables are missing initial conditions.
#>
#> All variables used in `deriv()` or `update()` require a corresponding entry in
#> `initial()` to set their initial conditions. The error will highlight all
#> lines that have a `deriv()` or `update()` call that lacks a call to
#> `initial()`. This can sometimes be because you have a spelling error in your
#> call to `initial()`.
#> In the book: Debugging
Here’s a model that compiles but has a bug
sir <- odin({
update(S) <- S - n_SI
update(I) <- I + n_SI
update(R) <- R + n_IR
update(incidence) <- incidence + n_SI
p_SI <- 1 - exp(-beta * I / N * dt)
p_IR <- 1 - exp(-gamma * dt)
n_SI <- Binomial(S, p_SI)
n_IR <- Binomial(I, p_IR)
initial(S) <- N - I0
initial(I) <- I0
initial(R) <- 0
initial(incidence, zero_every = 1) <- 0
N <- parameter(1000)
I0 <- parameter(10)
beta <- parameter(0.2)
gamma <- parameter(0.1)
})
sir
#> ── <dust_system_generator: odin_system> ────────────────────────────────────────
#> ℹ This system runs in discrete time with a default dt of 1
#> ℹ This system has 4 parameters
#> → 'N', 'I0', 'beta', and 'gamma'
#> ℹ Use dust2::dust_system_create() (`?dust2::dust_system_create()`) to create a system with this generator
#> ℹ Use coef() (`?stats::coef()`) to get more information on parametersIn the book: Debugging
Let’s run it
The number of individuals in the R compartment has exceeded N!
In the book: Debugging
You can stare at the code and hope to spot what’s wrong, but that can be like trying to find a needle in a haystack, especially for larger models.
So there are tools to help you:
print() the values of variables in your model as it runsbrowser() function in the same way as you would in R.In the book: Debugging
print()With print() we can tell it what to print and (optionally) conditions for when to print.
sir <- odin({
update(S) <- S - n_SI
update(I) <- I + n_SI
update(R) <- R + n_IR
update(incidence) <- incidence + n_SI
p_SI <- 1 - exp(-beta * I / N * dt)
p_IR <- 1 - exp(-gamma * dt)
n_SI <- Binomial(S, p_SI)
n_IR <- Binomial(I, p_IR)
initial(S) <- N - I0
initial(I) <- I0
initial(R) <- 0
initial(incidence, zero_every = 1) <- 0
N <- parameter(1000)
I0 <- parameter(10)
beta <- parameter(0.2)
gamma <- parameter(0.1)
print("S + I: {S + I}, R: {R}", when = S + I + R > N && time > 5)
})In the book: Debugging
print()Then when we run it prints what we wanted with the times in square brackets:
This may help us spot that we’re not removing individuals from I when they move to R!
In the book: Debugging
browser()With the browser() we need to declare the phase for inserting (deriv for ODE models, update for stochastic models) and optionally when to trigger it.
sir <- odin({
update(S) <- S - n_SI
update(I) <- I + n_SI
update(R) <- R + n_IR
update(incidence) <- incidence + n_SI
p_SI <- 1 - exp(-beta * I / N * dt)
p_IR <- 1 - exp(-gamma * dt)
n_SI <- Binomial(S, p_SI)
n_IR <- Binomial(I, p_IR)
initial(S) <- N - I0
initial(I) <- I0
initial(R) <- 0
initial(incidence, zero_every = 1) <- 0
N <- parameter(1000)
I0 <- parameter(10)
beta <- parameter(0.2)
gamma <- parameter(0.1)
browser(phase = "update", when = S + I + R > N)
})In the book: Debugging
browser()When we try to run it will pause during evaluation:
You can list all the variables
In the book: Debugging
browser()You can inspect values and perform calculations
In the book: Debugging
browser()c or n, to continue until the next time the browser() is triggeredn, you would need to type message(n) to see its valueQ to immediately exit to the console with an error.dust_browser_continue() to cancel all future browser() calls and continue running the modelIn the book: Debugging
odin models with montydelay() for delay differential equations