---
title: "Validation of SMC using a Kalman filter"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Validation of SMC using a Kalman filter}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 5)
lang_output <- function(x, lang) {
  cat(c(sprintf("```%s", lang), x, "```"), sep = "\n")
}
cc_output <- function(x) lang_output(x, "cc")
in_pkgdown <- identical(Sys.getenv("IN_PKGDOWN"), "true")
```

We can test that the SMC algorithm implemented in `mcstate::particle_filter()`
gives an unbiased estimate of the likelihood by comparing a simple model where
the likelihood can be calculated exactly. The volatility model included in the
dust package fulfils this criteria:

```{r volatility_code, echo = FALSE, results = "asis"}
path_volatility <- system.file("examples/volatility.cpp", package = "dust",
                               mustWork = TRUE)
cc_output(readLines(path_volatility))
```

This is a [`dust`](https://mrc-ide.github.io/dust) model; refer to the documentation there for details.  This file can be compiled with `dust::dust`, but here we use the version bundled with `dust`

```{r volatility_create}
volatility <- dust::dust_example("volatility")
```

We can simulate some data from the model:

```{r volatility_data_create, include = FALSE}
data <- local({
  mod <- volatility$new(list(alpha = 0.91, sigma = 1), 0, 1L)
  mod$update_state(state = matrix(rnorm(1L, 0, 1L), 1))
  t <- seq(0, 100, by = 1)
  res <- mod$simulate(t)
  observed <- res[1, 1, -1] + rnorm(length(t) - 1, 0, 1)
  data.frame(t = t[-1], value = observed)
})
```

```{r volatility_data_show}
head(data)
plot(value ~ t, data, type = "o", pch = 19, las = 1)
```

In order to estimate the parameters of the process that might have generated this dataset, we need, in addition to our model, an observation/comparison function.  In this case, given that we observe some `state`:

```{r, volatility_compare}
volatility_compare <- function(state, observed, pars) {
  dnorm(observed$value, pars$gamma * drop(state), pars$tau, log = TRUE)
}
```

We also model the initialisation process:

```{r, volatility_initial}
volatility_initial <- function(info, n_particles, pars) {
  matrix(rnorm(n_particles, 0, pars$sd), 1)
}

pars <- list(
  # Generation process
  alpha = 0.91,
  sigma = 1,
  # Observation process
  gamma = 1,
  tau = 1,
  # Initial condition
  sd = 1)
```

and preprocess the data into the correct format:

```{r}
volatility_data <- mcstate::particle_filter_data(data, "t", 1)
head(volatility_data)
```

The particle filter can run with more or fewer particles - this will trade-off runtime with the variance in the estimate, though in a fairly non-linear manner:

```{r, include = in_pkgdown, eval = in_pkgdown}
n_particles <- 1000
```

```{r, include = !in_pkgdown, eval = !in_pkgdown}
n_particles <- 100
```


With all these pieces we can create our particle filter object with `r n_particles` particles

```{r}
filter <- mcstate::particle_filter$new(volatility_data, volatility, n_particles,
                                       compare = volatility_compare,
                                       initial = volatility_initial)
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}
filter$run(pars)
```

This is stochastic and each time you run it, the estimate will differ:

```{r}
filter$run(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(alpha, sigma, gamma, tau, data) {
  y <- data$value

  mu <- 0
  s <- 1
  log_likelihood <- 0

  for (t in seq_along(y)) {
    mu <- alpha * mu
    s <- alpha^2 * s + sigma^2
    m <- gamma * mu

    S <- gamma^2 * s + tau^2
    K <- gamma * s / S

    mu <- mu + K * (y[t] - m)
    s <- s - gamma * K * s

    log_likelihood <- log_likelihood + dnorm(y[t], m, sqrt(S), log = TRUE)
  }

  log_likelihood
}
ll_k <- kalman_filter(pars$alpha, pars$sigma, pars$gamma, pars$tau,
                      volatility_data)
ll_k
```

Unlike the particle filter the Kalman filter is deterministic:

```{r}
kalman_filter(pars$alpha, pars$sigma, pars$gamma, pars$tau, volatility_data)
```

However, the particle filter, run multiple times, will create a distribution centred on this likelihood:

```{r, include = in_pkgdown, eval = in_pkgdown}
n_replicates <- 200
```

```{r, include = !in_pkgdown, eval = !in_pkgdown}
n_replicates <- 20
```

```{r}
ll <- replicate(n_replicates, filter$run(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

```{r}
filter2 <- mcstate::particle_filter$new(volatility_data, volatility,
                                        n_particles / 10,
                                        compare = volatility_compare,
                                        initial = volatility_initial)
ll2 <- replicate(n_replicates, filter2$run(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:s

```{r}
filter$run(pars, save_history = TRUE)
dim(filter$history())
```

This is a _N_ state (here 1) x _N_ particles (1000) x _N_ time steps (100) 3d array, but we will drop the first rank of this for plotting

```{r}
matplot(0:100, t(drop(filter$history())), xlab = "Time", ylab = "Value",
        las = 1, type = "l", lty = 1, col = "#00000002")
points(value ~ t, data, col = "steelblue3", pch = 19)
```

