Reference: R.J. Boys, D.J. Wilkinson and T.B.L. Kirkwood. Bayesian inference for a discretely observed stochastic kinetic model. Statistics and Computing (2008) 18:125-135.
The continuous-time Lotka-Volterra Markov jump process describes the evolution of two species \(X_{1}(t)\) (prey) and \(X_{2}(t)\) (predator) at time \(t\). Let \(dt\) be an infinitesimal interval. The process evolves as
\[\Pr(X_1(t+dt)=x_1(t)+1,X_2(t+dt)=x_2(t)|x_1(t),x_2(t))=c_1x_1(t)dt+o(dt)\]
\[\Pr(X_1(t+dt)=x_1(t)-1,X_2(t+dt)=x_2(t)+1|x_1(t),x_2(t))=c_2x_1(t)x_2(t)dt+o(dt)\]
\[\Pr(X_1(t+dt)=x_1(t),X_2(t+dt)=x_2(t)-1|x_1(t),x_2(t))=c_3 x_2(t)dt+o(dt)\]
where \(c_1=0.5\), \(c_2=0.0025\) and \(c_3=0.3\).
Forward simulation can be done using the Gillespie algorithm. We additionally assume that we observe at some time \(t=1,2,\ldots,t_{\max}\) the number of preys with some noise
\[ Y(t)=X_1(t) + \epsilon(t), ~~\epsilon(t)\sim\mathcal N(0,\sigma^2) \]
Content of the file 'stoch_kinetic_gill.bug'
:
model_file = 'stoch_kinetic_gill.bug' # BUGS model filename
cat(readLines(model_file), sep = "\n")
# Stochastic kinetic predator-prey model
# cf Boys, Wilkinson and Kirkwood
# Bayesian inference for a discretely observed stochastic kinetic model
var x_true[2,t_max], x[2,t_max], y[t_max], c[3]
data
{
x_true[,1] ~ LV(x_init,c[1],c[2],c[3],1)
y[1] ~ dnorm(x_true[1,1], 1/sigma^2)
for (t in 2:t_max)
{
x_true[1:2, t] ~ LV(x_true[1:2,t-1],c[1],c[2],c[3],1)
y[t] ~ dnorm(x_true[1,t], 1/sigma^2)
}
}
model
{
x[,1] ~ LV(x_init,c[1],c[2],c[3],1)
y[1] ~ dnorm(x[1,1], 1/sigma^2)
for (t in 2:t_max)
{
x[,t] ~ LV(x[,t-1],c[1],c[2],c[3],1)
y[t] ~ dnorm(x[1,t], 1/sigma^2)
}
}
lotka_volterra_gillespie <- function(x, c1, c2, c3, dt) {
# Simulation from a Lotka-Volterra model with the Gillepsie algorithm
# x1 is the number of prey
# x2 is the number of predator
# R1: (x1,x2) -> (x1+1,x2) At rate c1x1
# R2: (x1,x2) -> (x1-1,x2+1) At rate c2x1x2
# R3: (x1,x2) -> (x1,x2-1) At rate c3xx2
z = matrix(c(1, -1, 0, 0, 1, -1), nrow=2, byrow=TRUE)
t = 0
while (TRUE) {
rate = c(c1*x[1], c2*x[1]*x[2], c3*x[2])
sum_rate = sum(rate);
t = t - log(runif(1))/sum_rate # Sample next event from an exponential distribution
ind = which((sum_rate*runif(1))<=cumsum(rate))[1] # Sample the type of event
if (t>dt)
break
x = x + z[,ind]
}
return(x)
}
lotka_volterra_dim <- function(x_dim, c1_dim, c2_dim, c3_dim, dt_dim) { c(2,1) }
require(rbiips)
par(bty='l')
light_blue = rgb(.7, .7, 1)
light_red = rgb(1, .7, .7)
dark_blue = rgb(0, 0, .5)
dark_red = rgb(.5, 0, 0)
Set the random numbers generator seed for reproducibility
set.seed(0)
fun_bugs = 'LV'; fun_nb_inputs = 5
fun_dim = lotka_volterra_dim; fun_sample = lotka_volterra_gillespie
biips_add_distribution(fun_bugs, fun_nb_inputs, fun_dim, fun_sample)
* Added distribution LV
t_max = 40
x_init = c(100, 100)
c = c(.5, .0025, .3)
sigma = 10
data = list(t_max=t_max, c=c, x_init=x_init, sigma=sigma)
sample_data = TRUE # Boolean
model = biips_model(model_file, data, sample_data=sample_data) # Create Biips model and sample data
* Parsing model in: stoch_kinetic_gill.bug
* Compiling data graph
Declaring variables
Resolving undeclared variables
Allocating nodes
Graph size: 131
Sampling data
Reading data back into data table
* Compiling model graph
Declaring variables
Resolving undeclared variables
Allocating nodes
Graph size: 132
data = model$data()
plot(1:t_max, data$x_true[1,],
type='l', col='blue', lwd=2,
xlab='Time',
ylab='Number of individuals',
ylim=c(0, 450))
lines(1:t_max, data$x_true[2,], col='red', lwd=2)
points(1:t_max, data$y, pch=8, col='green', lwd=2)
legend('topright', leg=c('Prey', 'Predator', 'Measurements'),
col=c('blue', 'red', 'green'), lwd=2, lty=c(1,1,NA), pch=c(NA,NA,8),
bty='n')
data
n_part = 10000 # Number of particles
variables = c('x') # Variables to be monitored
out_smc = biips_smc_samples(model, variables, n_part, type='fs')
* Assigning node samplers
* Running SMC forward sampler with 10000 particles
|--------------------------------------------------| 100%
|**************************************************| 40 iterations in 1310.88 s
summ_smc = biips_summary(out_smc, probs=c(.025, .975))
plot(out_smc$x$s$ess[1,], type='l', log='y', col='blue', lwd=2,
xlab='Time', ylab='SESS',
ylim=c(10, n_part))
lines(1:t_max, rep(30,t_max), lwd=2, lty=2)
SMC: SESS
x_smc_mean = summ_smc$x$s$mean
x_smc_quant = summ_smc$x$s$quant
xx = c(1:t_max, t_max:1)
yy = c(x_smc_quant[[1]][1,], rev(x_smc_quant[[2]][1,]))
plot(xx, yy, type='n', xlab='Time', ylab='Number of individuals',
ylim=c(0, 750))
polygon(xx, yy, col=light_blue, border=NA)
yy = c(x_smc_quant[[1]][2,], rev(x_smc_quant[[2]][2,]))
polygon(xx, yy, col=light_red, border=NA)
lines(1:t_max, x_smc_mean[1,], col='blue', lwd=3)
lines(1:t_max, data$x_true[1,], col=dark_blue, lwd=2, lty=2)
lines(1:t_max, x_smc_mean[2,], col='red', lwd=3)
lines(1:t_max, data$x_true[2,], col=dark_red, lwd=2, lty=2)
legend('topright', leg=c('95% credible interval (prey)',
'SMC mean estimate (prey)',
'True number of preys',
'95% credible interval (predator)',
'SMC mean estimate (predator)',
'True number of predators'),
col=c(light_blue,'blue',dark_blue,light_red,'red',dark_red),
lwd=c(NA,3,2,NA,3,2),
pch=c(15,NA,NA,15,NA,NA),
lty=c(NA,1,2,NA,1,2),
pt.cex=c(2,1,1,2,1,1), bty='n')
SMC: Posterior mean and quantiles
kde_smc = biips_density(out_smc)
time_index = c(5, 10, 15)
par(mfrow=c(2,2))
for (k in 1:length(time_index)) {
tk = time_index[k]
plot(kde_smc$x[[2,tk]], col=c('blue', 'red'), lwd=2,
xlab=paste('x[2,', tk,']', sep=''), ylab='Posterior density',
main=paste('Predator at t=', tk, sep=''))
points(data$x_true[tk], 0, col='green', pch=8, lwd=2)
}
plot(0, type='n', bty='n', xaxt='n', yaxt='n', xlab="", ylab="")
legend('center', leg=c('Filtering density', 'Smoothing density', 'True value'),
col=c('blue', 'red', 'green'), pch=c(NA,NA,8), lty=c(1,1,NA), lwd=2,
bty='n')
par(mfrow=c(1,1))
SMC: Marginal posteriors
table_smc = biips_table(out_smc)
time_index = c(5, 10, 15)
par(mfrow=c(2,2))
for (k in 1:length(time_index)) {
tk = time_index[k]
plot(table_smc$x[[2,tk]], col=c('blue', 'red'), lwd=2,
xlab=paste('x[2,', tk,']', sep=''), ylab='Posterior probability mass',
main=paste('Predator at t=', tk, sep=''))
points(data$x_true[tk], 0, col='green', pch=8, lwd=2)
}
plot(0, type='n', bty='n', xaxt='n', yaxt='n', xlab="", ylab="")
legend('center', leg=c('Filtering probability mass', 'Smoothing probability mass', 'True value'),
col=c('blue', 'red', 'green'), pch=c(NA,NA,8), lty=c(1,1,NA), lwd=2,
bty='n')
par(mfrow=c(1,1))
SMC: Marginal posteriors