How to Estimate The Parameters of ODE using Bayesian Inference
We can easily solve an ordinary differential equation given the parameters and boundary conditions. For example, given the SIR model as described below
function sir!(du, u, p, t)
S, I, R = u
β, γ = p
N = S + I + R
a = β * S * I / N
b = γ * I
du[1] = -a
du[2] = a - b
du[3] = b
return nothing
end
and its parameters, timeframe and boundary condition,
tmax = 50.0
tspan = (0.0,tmax)
obstimes = 1.0:1.0:tmax
u0 = [990.0,10.0,0.0] # S,I.R
p = [0.5,0.25]; # β,γ
we can solve it and get the usual curves.
prob_ode = ODEProblem(sir!,u0,tspan,p);
sol_ode = solve(prob_ode,
Tsit5(),
saveat = 1.0);
plot(sol_ode; label=["S(t)" "I(t)" "R(t)"], legend=:outertopright)
sir_newcases = abs.(diff(sol_ode(0:40; idxs=1).u))
noisy_newcases = rand.(Poisson.(sir_newcases));
bar(noisy_newcases; label="observed")
plot!(sir_newcases; label="model",marker=:circle)
We can construct a bayesian model using Turing.jl
@model function sir_model(t, y, ::Type{T}=Float64) where {T}
# sample the parameters: uniform priors for `i₀`, `β` and `γ`
i₀ ~ Uniform(0.0, 1.0)
β ~ Uniform(0.0, 1.0)
γ ~ Uniform(0.0, 1.0)
# simulate the SIR model and save the number of infected
# individuals at times `t`
I₀ = 1_000 * i₀
prob = ODEProblem{true}(sir!, T[1_000 - I₀, I₀, 0], (0.0, last(t)), (; β, γ))
sol = solve(prob, Tsit5(); saveat=t, save_idxs=1)
# ensure that the simulation was successful
if sol.retcode !== :Success
Turing.@addlogprob! -Inf
else
# compute new cases
cases = map(abs, diff(Array(sol)))
# noisy observations
for i in 1:length(y)
y[i] ~ Poisson(cases[i])
end
end
return (; i₀, β, γ, y)
end;
And use MCMC to estimate the parameters
sir_nuts = sample(
sir_model(0:40, noisy_newcases),
NUTS(0.65),
MCMCThreads(),
1_000,
3;
thinning=10,
discard_initial=100,
)
And we can see that mean of the transimission rate is 0.5 and the recovery rate is 0.2437, which is very close to the values we used initially.
plot(sir_nuts)