library(odin2)
library(dust2)
7 Order of events for discrete-time stochastic models
Here we have a discrete-time stochastic model
<- odin({
sir update(S) <- S - n_SI
update(I) <- I + n_SI - n_IR
update(R) <- R + n_IR
update(incidence) <- incidence + n_SI
<- interpolate(beta_time, beta_value, "linear")
beta <- parameter(constant = TRUE)
beta_time <- parameter(constant = TRUE)
beta_value dim(beta_time, beta_value) <- parameter(rank = 1)
<- 1 - exp(-beta * I / N * dt)
p_SI <- 1 - exp(-gamma * dt)
p_IR <- Binomial(S, p_SI)
n_SI <- Binomial(I, p_IR)
n_IR
initial(S) <- N - I0
initial(I) <- I0
initial(R) <- 0
initial(incidence, zero_every = 1) <- 0
<- parameter(1000)
N <- parameter(10)
I0 <- parameter(0.1)
gamma
## Comparison to data
<- interpolate(phi_time, phi_value, "constant")
phi <- parameter(constant = TRUE)
phi_time <- parameter(constant = TRUE)
phi_value dim(phi_time, phi_value) <- parameter(rank = 1)
<- data()
cases <- Exponential(1e6)
exp_noise ~ Poisson(phi * incidence + exp_noise)
cases
})
this model includes many components we have introduced in preceding sections, such as variables that reset periodically, use of interpolation functions and comparison to data.
It is useful to understand the order in which events will be evaluated as the model updates from time = t0
to time = t0 + dt
. There will first be a series of updates to the variables, followed by a comparison to data (assuming you are comparing the model to data).
7.1 Updates
At the beginning of the update we have time = t0
and then the updates proceed in the following order:
7.1.1 Reset any variables that use zero_every
Any variable that uses zero_every = x
will be reset to 0 if t0
is a multiple of x
. In our model we have
initial(incidence, zero_every = 1) <- 0
so incidence
will reset to 0 whenever t0
is an integer.
7.1.2 Read from variables
All variables will be read at their current values. In our example these are S
, I
, R
and incidence
.
7.1.3 Look up interpolation
Any uses of interpolate
relating to updates are evaluated using time = t0
. Thus in our example
<- interpolate(beta_time, beta_value, "linear") beta
is evaluated to give beta
its value at time = t0
.
7.1.4 Evaluate assignments
Assignment equations should have a logical order of evaluation (circularity should prevent compilation). For instance in our model it would naturally evaluate
<- 1 - exp(-beta * I / N * dt)
p_SI <- 1 - exp(-gamma * dt) p_IR
and then
<- Binomial(S, p_SI)
n_SI <- Binomial(I, p_IR) n_IR
Equations will be evaluated using the variables that were read in earlier (note that this is after any variables that use zero_every
have been reset to 0). As with interpolation, any equations that use time
(see Section 5.1) will use time = t0
.
7.1.5 Write out new values of state
Here we write out new values of variables as given by update
equations. In our example these are
update(S) <- S - n_SI
update(I) <- I + n_SI - n_IR
update(R) <- R + n_IR
update(incidence) <- incidence + n_SI
As with the other assignment equations, these are evaluated using values of the variables that were read in earlier. Thus in the equation
update(S) <- S - n_SI
it is telling us the value we will assign to S
for time = t0 + dt
, but on the right hand side it will use the value of S
at time = t0
.
You should be careful to avoid introducing lags between state variables in your update
equations.
For example for this simple model
\[\begin{gather*} X(t + dt) = X(t) + Normal(0, 1)\\ Y(t + dt) = Y(t) + Normal(0, 1)\\ Z(t) = X(t) + Y(t) \end{gather*}\]
it would be incorrect to code this as
<- odin({
m update(X) <- X + dX
update(Y) <- Y + dY
update(Z) <- X + Y
<- Normal(0, 1)
dX <- Normal(0, 1)
dY
initial(X) <- 0
initial(Y) <- 0
initial(Z) <- 0
})
as we can see that running this results in Z
being lagged by a time-step relative to X
and Y
:
<- dust_system_create(m, list(), seed = 1)
sys dust_system_set_state_initial(sys)
<- seq(0, 5)
t <- dust_system_simulate(sys, t)
y <- dust_unpack_state(sys, y)
y
$X + y$Y
y#> [1] 0.000000 1.666908 1.358213 4.669377 4.191150 6.221572
$Z
y#> [1] 0.000000 0.000000 1.666908 1.358213 4.669377 4.191150
This is because
update(Z) <- X + Y
corresponds to \(Z(t + dt) = X(t) + Y(t)\) and not \(Z(t) = X(t) + Y(t)\). Thus we need to account for the updates to X and Y on the right-hand side.
A correct way to write it could be
update(Z) <- X + dX + Y + dY
however in more complex models such an approach may become unwieldy, and further changes to updates for X
and Y
must be replicated in the update to Z
.
A safer approach is to introduce intermediate variables representing the updated values of X
and Y
<- odin({
m update(X) <- X_new
update(Y) <- Y_new
update(Z) <- X_new + Y_new
<- Normal(0, 1)
dX <- X + dX
X_new <- Normal(0, 1)
dY <- Y + dY
Y_new
initial(X) <- 0
initial(Y) <- 0
initial(Z) <- 0
})
which means that subsequent changes to how X
updates are automatically factored into how Z
updates. We can see this version no longer results in a lag:
<- dust_system_create(m, list(), seed = 1)
sys dust_system_set_state_initial(sys)
<- seq(0, 5)
t <- dust_system_simulate(sys, t)
y <- dust_unpack_state(sys, y)
y
$X + y$Y
y#> [1] 0.000000 1.666908 1.358213 4.669377 4.191150 6.221572
$Z
y#> [1] 0.000000 1.666908 1.358213 4.669377 4.191150 6.221572
7.1.6 Update time to t0 + dt
The series of updates ends with time
being increased by dt
.
7.2 Comparison to data
The comparison to data follows on from the above series of updates, and as such it is common that all equations relating to the comparison to data appear in a block at the end.
Equations relating to comparison to data are evaluated after the update of time to t0 + dt
and will only be evaluated if you have any data at time = t0 + dt
.
7.2.1 Read from variables
All variables will be read at their current values, which are the values corresponding to time = t0 + dt
. Again, in our example the variables are S
, I
, R
and incidence
, although only incidence
is used in the comparison to data
7.2.2 Look up interpolation
Any uses of interpolate
that are used in the comparison to data will be evaluated using time = t0 + dt
(not using time = t0
as was used earlier in the “update” step). Compilation of odin
code will detect which interpolations are used in the comparison to data.
In our example we
<- interpolate(phi_time, phi_value, "constant") phi
where phi
might represent a time-varying ascertainment rate of cases (which could be affected by e.g. whether schools are open, or day of the week effects).
7.2.3 Evaluate assignments
As with interpolation, compilation of odin
code will detect which assignments are used only in the comparison to data. For instance in our example this would be
<- Exponential(1e6) exp_noise
Any use of variables in these equations on the right-hand side will be evaluated using their values at time = t0 + dt
.
7.2.4 Compare to data
Finally we evaluate the density given by any relationship equations. In our example this is
~ Poisson(phi * incidence + exp_noise) cases
As with assignments in the comparison to data step, any use of variables in relationship equations on the right-hand side will be evaluated using their values at time = t0 + dt
. No actual assignment is done in relationship equations, but the density accumulates over all non-NA data entries.