-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #98 from mrc-ide/mrc-5865
Import and adapt docs from dust1
- Loading branch information
Showing
17 changed files
with
1,236 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ Suggests: | |
callr, | ||
cpp11, | ||
decor, | ||
fs, | ||
glue, | ||
knitr, | ||
mockery, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,35 @@ | ||
Blackmann | ||
CMD | ||
CMRG | ||
CRAN's | ||
Dormand | ||
HPC | ||
Kalman | ||
L'Ecuyer | ||
Makevars | ||
Mersenne | ||
ODEs | ||
OpenMP | ||
Poisson | ||
R's | ||
SEIR | ||
Vigna | ||
codecov | ||
cpp | ||
etc | ||
io | ||
lfloor | ||
monty | ||
odin | ||
parallelisable | ||
pkgload | ||
resettings | ||
rfloor | ||
rng | ||
roxygen | ||
stochasticity | ||
th | ||
truthy | ||
unfilter | ||
unitless | ||
xoshiro |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
--- | ||
title: "Comparing dust systems to data" | ||
output: rmarkdown::html_vignette | ||
vignette: > | ||
%\VignetteIndexEntry{Comparing dust systems to data} | ||
%\VignetteEngine{knitr::rmarkdown} | ||
%\VignetteEncoding{UTF-8} | ||
--- | ||
|
||
```{r, include = FALSE} | ||
source("support.R") | ||
set.seed(1) | ||
``` | ||
One of our aims with `dust` was to enable the creation of fast particle filters. This vignette outlines the steps in implementing the comparison directly as part of the model, and compares the result against a known deterministic result. | ||
|
||
```{r} | ||
library(dust2) | ||
``` | ||
|
||
We start with a simple example, a model of volatility. For completeness (and because the maths around this will need adding later!) we show the full code here: | ||
|
||
```{r echo = FALSE, results = "asis"} | ||
volatility_code <- readLines("examples/volatility.cpp") | ||
cc_output(volatility_code) | ||
``` | ||
|
||
```{r} | ||
volatility <- dust_compile("examples/volatility.cpp", quiet = TRUE) | ||
``` | ||
|
||
To demonstrate the approach, we simulate some data from the model itself. We have to include the simulation of the observation process here which adds an additional sample from the normal distribution. | ||
|
||
These are the parameters we will use: | ||
|
||
```{r} | ||
pars <- list( | ||
# Generation process | ||
alpha = 0.91, | ||
sigma = 1, | ||
# Observation process | ||
gamma = 1, | ||
tau = 1) | ||
``` | ||
|
||
```{r} | ||
data <- local({ | ||
sys <- dust_system_create(volatility(), pars) | ||
dust_system_set_state_initial(sys) | ||
times <- seq(1, 100, by = 1) | ||
observed <- drop(dust_system_simulate(sys, times)) + rnorm(length(times)) | ||
data.frame(time = times, observed = observed) | ||
}) | ||
head(data) | ||
plot(observed ~ time, data, type = "o", pch = 19, las = 1) | ||
``` | ||
|
||
Now, we construct a particle filter: | ||
|
||
|
||
```{r} | ||
filter <- dust_filter_create(volatility(), 0, data, n_particles = 1000) | ||
filter | ||
``` | ||
|
||
Running the particle filter simulates the process on all $10^3$ particles and compares at each timestep the simulated data with your observed data using the provided comparison function. It returns the log-likelihood: | ||
|
||
```{r} | ||
dust_likelihood_run(filter, pars) | ||
``` | ||
|
||
This is stochastic and each time you run it, the estimate will differ: | ||
|
||
```{r} | ||
dust_likelihood_run(filter, pars) | ||
``` | ||
|
||
In this case the model is simple enough that we can use a [Kalman Filter](https://en.wikipedia.org/wiki/Kalman_filter) to calculate the likelihood exactly: | ||
|
||
```{r volatility_kalman} | ||
kalman_filter <- function(pars, data) { | ||
y <- data$observed | ||
mu <- 0 | ||
s <- 1 | ||
log_likelihood <- 0 | ||
for (t in seq_along(y)) { | ||
mu <- pars$alpha * mu | ||
s <- pars$alpha^2 * s + pars$sigma^2 | ||
m <- pars$gamma * mu | ||
S <- pars$gamma^2 * s + pars$tau^2 | ||
K <- pars$gamma * s / S | ||
mu <- mu + K * (y[t] - m) | ||
s <- s - pars$gamma * K * s | ||
log_likelihood <- log_likelihood + dnorm(y[t], m, sqrt(S), log = TRUE) | ||
} | ||
log_likelihood | ||
} | ||
ll_k <- kalman_filter(pars, data) | ||
ll_k | ||
``` | ||
|
||
Unlike the particle filter the Kalman filter is deterministic: | ||
|
||
```{r} | ||
kalman_filter(pars, data) | ||
``` | ||
|
||
```{r} | ||
ll <- replicate(200, dust_likelihood_run(filter, pars)) | ||
hist(ll, col = "steelblue3") | ||
abline(v = ll_k, col = "red", lty = 2, lwd = 2) | ||
``` | ||
|
||
As the number of particles used changes, the variance of this estimate will change. For example, here is a filter with only 100 particles (1/10th the number as in the previous). We plot the range of observed likelihoods from the larger filter as orange vertical lines. | ||
|
||
```{r} | ||
filter2 <- dust_filter_create(volatility(), 0, data, n_particles = 100) | ||
ll2 <- replicate(200, dust_likelihood_run(filter2, pars)) | ||
hist(ll2, col = "steelblue3") | ||
abline(v = ll_k, col = "red", lty = 2, lwd = 2) | ||
abline(v = range(ll), col = "orange", lty = 3, lwd = 2) | ||
``` | ||
|
||
If you run a particle filter with `save_history = TRUE`, it will record the (filtered) trajectories, which you can then extract with `dust_filter_last_history()`: | ||
|
||
```{r} | ||
dust_likelihood_run(filter, pars, save_history = TRUE) | ||
history <- dust_likelihood_last_history(filter) | ||
dim(history) | ||
``` | ||
|
||
This is a `n_state` (here 1) x `n_particles` (1000) x `n_time` (100) 3d array, but we will drop the first rank of this for plotting, and transpose so that time is the first axis: | ||
|
||
```{r} | ||
matplot(data$time, t(drop(history)), xlab = "Time", ylab = "Value", | ||
las = 1, type = "l", lty = 1, col = "#00000002") | ||
points(observed ~ time, data, col = "red", pch = 19) | ||
``` | ||
|
||
Here you can see the sampled trajectories fitting the observed data, with fewer extant trajectories the further back in time you go (fewer, thicker, lines due to the sampling process). |
Oops, something went wrong.