---
title: "nlmixr2plot events for loggers"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{nlmixr2plot events for loggers}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
has_bus <- exists("rxEventListen", asNamespace("rxode2"))
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 4.5,
  eval = has_bus
)
```

```{r, echo = FALSE, eval = !has_bus, results = "asis"}
cat("> **Note:** the installed `rxode2` has no event bus",
    "(`rxode2::rxEventListen()` does not exist), so the code in this",
    "vignette is shown but not run.  `nlmixr2plot` then emits nothing and",
    "behaves exactly as before.\n")
```

`rxode2` provides a small event bus: packages that finish a top-level piece of
work announce it with `rxode2::rxEventEmit()`, and any package can listen with
`rxode2::rxEventListen()`.  The main consumer is
`nlmixr2log` (in development), which records fits,
simulations and their results, but you can write your own listener.  See
`?rxode2::rxEventListen` for the full API.

Two rules of the bus matter here:

- Events are delivered only from the *outermost* operation.  Every emitting
  function opens an operation scope, and an event is delivered only when no
  scope is active, so work done inside another operation is silent.
- Emitting is a no-op when no listener is registered, and `nlmixr2plot`
  checks that the bus exists before using it, so with an older `rxode2`
  nothing changes.

This vignette describes what `nlmixr2plot` itself emits.

## What nlmixr2plot emits

`nlmixr2plot` emits a single event, `fitResult`, when one of these functions
returns successfully:

| Function | `kind` | `fit` | `data` |
|---|---|---|---|
| `plot()` of a fit (`plot.nlmixr2FitData()`) | `"plot"` | the fit | `NULL` |
| `vpcPlot()` | `"vpcPlot"` | the fit (see below) | `NULL` |
| `plot()` of an `augPred()` object (`plot.nlmixr2AugPred()`) | `"augPredPlot"` | `NULL` | the `augPred()` data |

The `fitResult` payload fields are:

| Field | Contents |
|---|---|
| `fit` | The fit the plots belong to, or `NULL` for `plot(augPred(...))`. |
| `result` | The value the function returns: the `ggtibble::gglist` of plots from `plot(fit)` or `plot(augPred(...))`, or the `ggplot` from `vpcPlot()` (the vpc database with `vpcdb = TRUE`). |
| `kind` | `"plot"`, `"vpcPlot"` or `"augPredPlot"`. |
| `call` | The call, normalized by `rxode2` so its head is the `kind` (e.g. `plot(fit)`, `vpcPlot(fit, n = 5)` or `augPredPlot(ap)`) and large inlined values are replaced by markers. |
| `data` | For `"augPredPlot"` only, the `augPred()` data that was plotted; otherwise `NULL`. |

Details:

- **One event per call.**  The event is emitted when the function exits, after
  all the plots are built.
- **`vpcPlot()` and supplied simulations.**  When `vpcPlot()` is given a
  `vpcSim()` result instead of a fit, the `fit` field is the fit that
  simulation came from, not the simulation.
- **`plot(augPred(...))` has no fit.**  An `augPred()` object does not carry its
  fit, so `fit` is `NULL` and the plotted data is sent as `data`; a logger
  such as `nlmixr2log` uses it to find the run it belongs to.
- **Nothing on error or empty results.**  If the function fails, or returns
  `NULL`, no event is emitted.

## What is not emitted

Everything that happens *inside* these functions is silent:

- The simulation that `vpcPlot()` runs (through `nlmixr2est::vpcSim()` and
  `rxode2::rxSolve()`) emits no `solveComplete`.
- Plots made internally, such as the `traceplot()` inside `plot(fit)`, emit no
  events of their own.
- Calling these functions from inside another operation scope (for example,
  from a function wrapped in `rxode2::rxEventScope()`) emits nothing; only the
  outermost operation reports.

Other events you may see around plotting come from other packages:
`nlmixr2est` emits `fitComplete` when `nlmixr2()` finishes a fit and
`solveComplete` from `augPred()`, `simulate()`, `predict()` and `vpcSim()`
called at the top level.  Those require an `nlmixr2est` with event hooks;
`nlmixr2plot` does not emit them.

## Listening to the events

A listener is a function `function(event, ...)`; the payload fields arrive as
named arguments.  Here is a small listener that records what it sees.  First
fit a small model:

```{r, results = "hide"}
library(nlmixr2plot)
library(nlmixr2est)

one.cmt <- function() {
  ini({
    tka <- log(1.57); tcl <- log(2.72); tv <- log(31.5)
    eta.ka ~ 0.6; eta.cl ~ 0.3; eta.v ~ 0.1
    add.sd <- 0.7
  })
  model({
    ka <- exp(tka + eta.ka)
    cl <- exp(tcl + eta.cl)
    v <- exp(tv + eta.v)
    linCmt() ~ add(add.sd)
  })
}

fit <- suppressMessages(
  nlmixr2(one.cmt, nlmixr2data::theo_sd, est = "posthoc",
          control = list(print = 0))
)
```

Then register a listener and make some plots:

```{r}
seen <- list()
rxode2::rxEventListen("demo", function(event, ...) {
  p <- list(...)
  seen[[length(seen) + 1L]] <<- data.frame(
    event = event,
    kind = if (is.null(p$kind)) NA_character_ else p$kind,
    hasFit = !is.null(p$fit),
    resultClass = class(p$result)[1],
    call = paste(deparse(p$call), collapse = " ")
  )
})

p <- plot(fit)
v <- suppressMessages(suppressWarnings(vpcPlot(fit, n = 50)))
ap <- suppressMessages(augPred(fit))
a <- plot(ap)

rxode2::rxEventUnlisten("demo")
do.call(rbind, seen)
```

`plot(fit)` and `vpcPlot()` each report one `fitResult`, and the simulation
inside `vpcPlot()` stays silent.  `plot(ap)` reports a `fitResult` with no fit.
With an `nlmixr2est` that has event hooks, the top-level `augPred()` call also
reports a `solveComplete` (emitted by `nlmixr2est`).

After `rxEventUnlisten()`, plotting reports nothing:

```{r}
n <- length(seen)
p <- plot(fit)
length(seen) == n
```

## Full logging

For a complete record of fits, simulations, updates and the plots made from
them, use `nlmixr2log` (in development), which listens to
all of these events (including `fitResult` from `nlmixr2plot`) and stores the
plots with the run they belong to.
