---
title: "Nonlinear model approximation"
output:
  rmarkdown::html_vignette:
  rmarkdown::pdf_document:
vignette: >
  %\VignetteIndexEntry{Nonlinear model approximation}
  %\VignetteEncoding{UTF-8}
  %\VignetteEngine{knitr::rmarkdown}
header-includes:
  - \newcommand{\bm}[1]{\boldsymbol{#1}}
  - \newcommand{\wt}[1]{\widetilde{#1}}
  - \newcommand{\ol}[1]{\overline{#1}}
  - \newcommand{\wh}[1]{\widehat{#1}}
  - \DeclareMathOperator*{\argmax}{arg\,max}
  - \newcommand{\proper}[1]{\mathsf{#1}}
  - \newcommand{\pExp}{\proper{Exp}}
  - \newcommand{\pN}{\proper{N}}
  - \newcommand{\pPo}{\proper{Po}}
  - \newcommand{\pGa}{\proper{Ga}}
  - \newcommand{\pE}{\proper{E}}
  - \newcommand{\pP}{\proper{P}}
  - \newcommand{\pVar}{\proper{Var}}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  dev = "png",
  dev.args = list(type = "cairo-png"),
  fig.width = 7,
  fig.height = 5
)
```

```{r setup, include = FALSE}
library(inlabru)
library(ggplot2)
```

See the `method` vignette for details of the general `inlabru` method.
Here, we'll take a look at a small toy example to see the difference between
a few posterior approximation methods.

Note: This vignette is not completed yet.

## A small toy problem

Hierarchical model:
$$
\begin{aligned}
\lambda &\sim \pExp(\gamma) \\
(y_i|\lambda) &\sim \pPo(\lambda), \text{ independent across $i=1,\dots,n$}
\end{aligned}
$$
With $\ol{y}=\frac{1}{n}\sum_{i=1}^n y_i$, the posterior density is
$$
\begin{aligned}
p(\lambda | \{y_i\}) &\propto p(\lambda, y_1,\dots,y_n) \\
&\propto \exp(-\gamma\lambda) \exp(-n\lambda) \lambda^{n\ol{y}} \\
&= \exp\{-(\gamma+n)\lambda\} \lambda^{n\ol{y}},
\end{aligned}
$$
which is the density of a $\pGa(\alpha = 1+n\ol{y}, \beta = \gamma+n)$ distribution.

## Latent Gaussian predictor version

Introducing a latent Gaussian variable $u\sim\pN(0,1)$, the model can be
reformulated as
$$
\begin{aligned}
\lambda(u) &=-\ln\{1-\Phi(u)\}/\gamma \\
(y_i|u) &\sim \pPo(\lambda(u))
\end{aligned}
$$
We will need some derivatives of $\lambda$ with respect to $u$:
$$
\begin{aligned}
\frac{\partial\lambda(u)}{\partial u} &= \frac{1}{\gamma}\frac{\phi(u)}{1-\Phi(u)}
  = \lambda'(u) \\
\frac{\partial^2\lambda(u)}{\partial u^2} &=
-
\frac{1}{\gamma}\frac{\phi(u)}{1-\Phi(u)}
\left(
u + \frac{\phi(u)}{1-\Phi(u)}
\right)
=
-\lambda'(u)\left\{u-\gamma\lambda'(u)\right\}
\\
\frac{\partial\ln\lambda(u)}{\partial u} &=
  \frac{1}{\lambda(u)} \frac{\partial\lambda(u)}{\partial u}
  =\frac{1}{-\ln\{1-\Phi(u)\}} \frac{\phi(u)}{1-\Phi(u)}
  = \frac{\lambda'(u)}{\lambda(u)} \\
\frac{\partial^2\ln\lambda(u)}{\partial u^2} &=
  \frac{1}{\lambda(u)} \frac{\partial^2\lambda(u)}{\partial u^2}
  -\frac{1}{\lambda(u)^2} \left(\frac{\partial\lambda(u)}{\partial u}\right)^2
  = \frac{-\lambda'(u)\{u - \gamma\lambda'(u)\}}{\lambda(u)}
  - \frac{\lambda'(u)^2}{\lambda(u)^2}\\
  &= -\frac{\lambda'(u)}{\lambda(u)}\left\{
  u - \gamma\lambda'(u) +\frac{\lambda'(u)}{\lambda(u)}
  \right\}
\end{aligned}
$$
```{r}
lambda <- function(u, gamma) {
  -pnorm(u, lower.tail = FALSE, log.p = TRUE) / gamma
}
lambda_inv <- function(lambda, gamma) {
  qnorm(-lambda * gamma, lower.tail = FALSE, log.p = TRUE)
}
D1lambda <- function(u, gamma) {
  exp(dnorm(u, log = TRUE) - pnorm(u, lower.tail = FALSE, log.p = TRUE)) / gamma
}
D2lambda <- function(u, gamma) {
  D1L <- D1lambda(u, gamma)
  -D1L * (u - gamma * D1L)
}
D1log_lambda <- function(u, gamma) {
  D1lambda(u, gamma) / lambda(u, gamma)
}
D2log_lambda <- function(u, gamma) {
  D1logL <- D1log_lambda(u, gamma)
  -D1logL * (u - gamma * D1lambda(u, gamma = gamma) + D1logL)
}
```

## Latent Gaussian posterior approximations

A basic approximation of the posterior distribution for $\lambda$ given $y$
can be defined as a deterministic transformation of a Gaussian distribution
obtained from a 2nd order Taylor approximation of $\ln p(u|\{y_i\})$ at the posterior
mode $u_0$ of $p(u|\{y_i\})$. The needed derivatives are
$$
\begin{aligned}
\frac{\partial\ln p(u|\{y_i\})}{\partial u} &= \frac{\partial\ln\phi(u)}{\partial u}
- n\lambda'(u) + n\ol{y}\frac{\lambda'(u)}{\lambda(u)}
= -u + n\frac{\lambda'(u)}{\lambda(u)}\left\{
\ol{y} - \lambda(u)
\right\}
\\
\frac{\partial^2\ln p(u|\{y_i\})}{\partial u^2}
&=
-1
-
n
\frac{\lambda'(u)}{\lambda(u)}\left\{
u - \gamma\lambda'(u) + \frac{\lambda'(u)}{\lambda(u)}
\right\}
\left\{
\ol{y} - \lambda(u)
\right\}
-
n
\frac{\lambda'(u)^2}{\lambda(u)}
\end{aligned}
$$
At the mode $u_0$, the first order derivative is zero, and
$$
\begin{aligned}
\left.\frac{\partial^2\ln p(u|\{y_i\})}{\partial u^2}\right|_{u=u_0}
&=
-1
-
\left\{
u_0 - \gamma\lambda'(u_0) + \frac{\lambda'(u_0)}{\lambda(u_0)}
\right\}
u_0
-
n
\frac{\lambda'(u_0)^2}{\lambda(u_0)} .
\end{aligned}
$$
The quadratic approximation of the log-posterior density at the mode $u_0$ is then
$$
\ln \breve{p}(u|\{y_i\}) = \text{const} - \frac{(u-u_0)^2}{2}
\left[
-
\left.\frac{\partial^2\ln p(u|\{y_i\})}{\partial u^2}\right|_{u=u_0}
\right]
$$
In `inlabru`, the approximation first linearises $\ln \lambda(u)$ at $u_0$ before applying
the Taylor approximation of $\ln p(u|\{y_i\})$. The linearised log-predictor is
$$
\ln \ol{\lambda}(u) = \ln \lambda(u_0) + \frac{\lambda'(u_0)}{\lambda(u_0)}(u - u_0)
$$
so that
$$
\ol{\lambda}'(u) = \frac{\lambda'(u_0)}{\lambda(u_0)} \ol{\lambda}(u)
$$
and the second order derivative of the linearised log-posterior density is
$$
\begin{aligned}
\left.\frac{\partial^2\ln \ol{p}(u|\{y_i\})}{\partial u^2}\right|_{u=u_0}
&=
-1
-
n
\frac{\lambda'(u_0)^2}{\lambda(u_0)} .
\end{aligned}
$$

```{r}
log_p <- function(u, y, gamma) {
  L <- lambda(u, gamma)
  n <- length(y)
  dnorm(u, log = TRUE) - n * L + n * mean(y) * log(L) - sum(lgamma(y + 1))
}
D1log_p <- function(u, y, gamma) {
  n <- length(y)
  -u + n * D1log_lambda(u, gamma) * (mean(y) - lambda(u, gamma))
}
D2log_p <- function(u, y, gamma) {
  n <- length(y)
  -1 +
    n * D2log_lambda(u, gamma) * (mean(y) - lambda(u, gamma)) -
    n * D1log_lambda(u, gamma) * D1lambda(u, gamma)
}
```

```{r}
g <- 1
y <- c(0, 1, 2)
y <- c(0, 0, 0, 0, 0)
y <- rpois(5, 5)
mu_quad <- uniroot(
  D1log_p,
  lambda_inv((1 + sum(y)) / (g + length(y)) * c(1 / 10, 10), gamma = g),
  y = y, gamma = g
)$root
sd_quad <- (-D2log_p(mu_quad, y = y, gamma = g))^-0.5
sd_lin <- (1 + length(y) * D1log_lambda(mu_quad, gamma = g)^2 * lambda(mu_quad, gamma = g))^-0.5
lambda0 <- lambda(mu_quad, gamma = g)
```

### Posterior densities


```{r}
ggplot() +
  xlim(
    lambda(mu_quad - 4 * sd_quad, gamma = g),
    lambda(mu_quad + 4 * sd_quad, gamma = g)
  ) +
  xlab("lambda") +
  ylab("Density") +
  geom_function(
    fun = function(x) {
      exp(log_p(lambda_inv(x, gamma = g), y = y, gamma = g)) /
        D1lambda(lambda_inv(x, gamma = g), gamma = g) / (
          exp(log_p(lambda_inv(lambda0, gamma = g), y = y, gamma = g)) /
            D1lambda(lambda_inv(lambda0, gamma = g), gamma = g)
        ) *
        dgamma(lambda0, shape = 1 + sum(y), rate = g + length(y))
    },
    mapping = aes(col = "Theory"),
    n = 1000
  ) +
  geom_function(
    fun = dgamma,
    args = list(shape = 1 + sum(y), rate = g + length(y)),
    mapping = aes(col = "Theory"),
    n = 1000
  ) +
  geom_function(
    fun = function(x) {
      dnorm(lambda_inv(x, gamma = g), mean = mu_quad, sd = sd_quad) /
        D1lambda(lambda_inv(x, gamma = g), gamma = g)
    },
    mapping = aes(col = "Quadratic"),
    n = 1000
  ) +
  geom_function(
    fun = function(x) {
      dnorm(lambda_inv(x, gamma = g), mean = mu_quad, sd = sd_lin) /
        D1lambda(lambda_inv(x, gamma = g), gamma = g)
    },
    mapping = aes(col = "Linearised"),
    n = 1000
  ) +
  geom_vline(mapping = aes(
    xintercept = (1 + sum(y)) / (g + length(y)),
    lty = "Bayes mean"
  )) +
  geom_vline(mapping = aes(xintercept = lambda0, lty = "Bayes mode")) +
  geom_vline(mapping = aes(xintercept = mean(y), lty = "Plain mean"))
```
```{r}
ggplot() +
  xlim(
    lambda_inv(lambda0, gamma = g) - 4 * sd_quad,
    lambda_inv(lambda0, gamma = g) + 4 * sd_quad
  ) +
  xlab("u") +
  ylab("Density") +
  geom_function(
    fun = function(x) {
      exp(log_p(x, y = y, gamma = g) -
        log_p(lambda_inv(lambda0, gamma = g), y = y, gamma = g)) *
        (dgamma(lambda0, shape = 1 + sum(y), rate = g + length(y)) *
          D1lambda(lambda_inv(lambda0, gamma = g), gamma = g))
    },
    mapping = aes(col = "Theory"),
    n = 1000
  ) +
  geom_function(
    fun = function(x) {
      dgamma(lambda(x, gamma = g), shape = 1 + sum(y), rate = g + length(y)) *
        D1lambda(x, gamma = g)
    },
    mapping = aes(col = "Theory"),
    n = 1000
  ) +
  geom_function(
    fun = dnorm,
    args = list(mean = mu_quad, sd = sd_quad),
    mapping = aes(col = "Quadratic"),
    n = 1000
  ) +
  geom_function(
    fun = dnorm,
    args = list(mean = mu_quad, sd = sd_lin),
    mapping = aes(col = "Linearised"),
    n = 1000
  ) +
  geom_vline(mapping = aes(xintercept = lambda_inv((1 + sum(y)) / (g + length(y)),
    gamma = g
  ), lty = "Bayes mean")) +
  geom_vline(mapping = aes(xintercept = lambda_inv(lambda0, gamma = g), lty = "Bayes mode")) +
  geom_vline(mapping = aes(xintercept = lambda_inv(mean(y), gamma = g), lty = "Plain mean"))
```

### Posterior CDFs

```{r}
ggplot() +
  xlim(
    lambda(mu_quad - 4 * sd_quad, gamma = g),
    lambda(mu_quad + 4 * sd_quad, gamma = g)
  ) +
  xlab("lambda") +
  ylab("CDF") +
  geom_function(
    fun = pgamma,
    args = list(shape = 1 + sum(y), rate = g + length(y)),
    mapping = aes(col = "Theory"),
    n = 1000
  ) +
  geom_function(
    fun = function(x) {
      pnorm(lambda_inv(x, gamma = g), mean = mu_quad, sd = sd_quad)
    },
    mapping = aes(col = "Quadratic"),
    n = 1000
  ) +
  geom_function(
    fun = function(x) {
      pnorm(lambda_inv(x, gamma = g), mean = mu_quad, sd = sd_lin)
    },
    mapping = aes(col = "Linearised"),
    n = 1000
  ) +
  geom_vline(mapping = aes(
    xintercept = (1 + sum(y)) / (g + length(y)),
    lty = "Bayes mean"
  )) +
  geom_vline(mapping = aes(xintercept = lambda0, lty = "Bayes mode")) +
  geom_vline(mapping = aes(xintercept = mean(y), lty = "Plain mean"))
```
```{r}
ggplot() +
  xlim(
    lambda_inv(lambda0, gamma = g) - 4 * sd_quad,
    lambda_inv(lambda0, gamma = g) + 4 * sd_quad
  ) +
  xlab("u") +
  ylab("CDF") +
  geom_function(
    fun = function(x) {
      pgamma(lambda(x, gamma = g), shape = 1 + sum(y), rate = g + length(y))
    },
    mapping = aes(col = "Theory"),
    n = 1000
  ) +
  geom_function(
    fun = pnorm,
    args = list(mean = mu_quad, sd = sd_quad),
    mapping = aes(col = "Quadratic"),
    n = 1000
  ) +
  geom_function(
    fun = pnorm,
    args = list(mean = mu_quad, sd = sd_lin),
    mapping = aes(col = "Linearised"),
    n = 1000
  ) +
  geom_vline(mapping = aes(
    xintercept = lambda_inv((1 + sum(y)) / (g + length(y)),
      gamma = g
    ),
    lty = "Bayes mean"
  )) +
  geom_vline(mapping = aes(
    xintercept = lambda_inv(lambda0, gamma = g),
    lty = "Bayes mode"
  )) +
  geom_vline(mapping = aes(
    xintercept = lambda_inv(mean(y), gamma = g),
    lty = "Plain mean"
  ))
```
