---
title: "Debugging a gadget3 model"
output:
  html_document:
    toc: true
    theme: null
vignette: >
  %\VignetteIndexEntry{Model Debugging}
  %\VignetteEngine{knitr::rmarkdown}
  \usepackage[utf8]{inputenc}
---

```{r, message=FALSE, echo=FALSE}
library(gadget3)
library(magrittr)
if (nzchar(Sys.getenv('G3_TEST_TMB'))) options(gadget3.tmb.work_dir = gadget3:::vignette_base_dir('work_dir'))
```

## Debugging the R model

In theory, the TMB and R output of gadget3 should be identically-behaving
functions, so instead of trying to decipher what the TMB model is doing wrong,
you can use R function instead in a more familiar environment, for example
using standard tools such as ``options(error=recover)``.

You can also use ``edit()`` to edit the R function directly and re-run the
model:

```{r, eval=FALSE}
# Model setup will look something like this
ling_model <- g3_to_r(...)

ling_model <- edit(ling_model) ; ling_model(ling_param)
```

...this is useful if you want to add trace ``print()`` statements around a
particular part of the model that's failing, e.g. or insert a breakpoint by
adding a ``recover()`` line.

## Debugging the TMB model

It's possible to have a working R model that doesn't compile using TMB, due to
the relative strictness of the Eigen array library in comparison to R arrays,
for example. In which case you'll need to debug the TMB version.

### Preserving your R session whilst building

If the model crashes whilst forming the TMB ADFun object, then it takes your R
session with it. To prevent this, wrap ``g3_tmb_adfun()`` with
``TMB::gdbsource()`` as follows:

```{r, eval=FALSE}
# Model setup will look something like this
tmb_ling <- g3_to_tmb(...)
tmb_param <- attr(tmb_ling, 'parameter_template')

writeLines(TMB::gdbsource(g3_tmb_adfun(
    tmb_ling,
    tmb_param,
    compile_flags = "-g",
    output_script = TRUE)))
```

``output_script = TRUE`` tells ``g3_tmb_adfun()`` to, after compilation, write
a temporary R script that will build the TMB ADFun object (and presumably crash
in the process). ``TMB::gdbsource()`` in turn runs a provided R script in a
fresh R session wrapped in gdb. By default it will print a stacktrace and quit,
which should show you where the crash occured.

### Editing C++ code

As with the R model you can edit the raw C++ source before building:

```{r, eval=FALSE}
tmb_ling <- edit(tmb_ling)
writeLines(TMB::gdbsource(g3_tmb_adfun(
    tmb_ling,
    tmb_param,
    compile_flags = "-g",
    output_script = TRUE)))
```

Through this you can...

* Print the contents of single values with ``std::cout << ling__Linf << std::endl;``
* Use ``().cols()`` and ``().rows()`` to get the size of an array expression
* Print the contents of array objects with ``ling_imm__consratio.print()``

### Interactive debugging

In theory you can use ``interactive = TRUE`` with ``TMB::gdbsource()``, however
as this eats error messages it's better to do this by hand:

```
> g3_tmb_adfun(tmb_ling, tmb_param, compile_flags = "-g", output_script = TRUE)
[1] "/tmp/RtmpysTVvW/file3da4a6f13a80c.R"

R -d gdb
(gdb) run --vanilla < /tmp/RtmpysTVvW/file3da4a6f13a80c.R
 . . . Compilation, crash at some point . . .
(gdb) up
(gdb) up
(gdb) up
(gdb) call ling_imm__consratio.print()
Array dim: 35 1 8
Array val: -nan -nan -nan -nan
(gdb) call ling_imm__num.print()
Array dim: 35 1 8
Array val: -nan -nan -nan -nan -nan

(gdb) print cur_time
$5 = 0
```

Note that for the ``.print()`` method to be available for arrays, it has to be
referenced at least once in the model source, otherwise it won't be compiled
in. Use ``edit(tmb_ling)`` to add it somewhere first.

### Random effects

Some notes on debugging errors with random effects models.

#### Tracing inner model

Control arguments for the inner ``TMB:::newton()`` model can be provided to ``g3_tmb_adfun()``,
e.g. to add tracing:

```{r, eval=FALSE}
obj.fn <- g3_tmb_adfun(bounded_code, params.in, inner.control = list(trace = 3, maxit = 100))
```

#### Missing value for ``m``

The error:

```
Error in if (m < 0) { : missing value where TRUE/FALSE needed
```

...comes from TMB's newton optimiser, and essentially says there is `NaN` in the hessian matrix.
``m`` in this case is equivalent to:

```{r, eval=FALSE}
local({ min(diag(spHess(random = TRUE, set_tail = random[1]))) }, envir = obj.fn$env)
```

#### Missing value par - parold

```
Error in if (norm(par - parold) < step.tol) { :
  missing value where TRUE/FALSE needed
```

The ``par`` list of parameters to optimise became NaN, for various reasons, but likely ``solveCholesky()`` failed.

#### Separate netwton optimisation

Generally, ``TMB:::newton()`` is called from ``obj.fn$env$ff``.
You can extract the arguments provided by editing this function:

```{r, eval=FALSE}
obj.fn$env$ff <- edit(obj.fn$env$ff)
```

Around line 16, add code to extract the arguments provided, e.g:

```{r, eval=FALSE}
        assign("newt.args", c(list(par = eval(random.start),
            fn = f0, gr = function(x) f0(x, order = 1), he = H0,
            env = env), inner.control), env = globalenv())
```

Then you can perform a single run to extract arguments:

```{r, eval=FALSE}
obj.fn$fn()
do.call(TMB:::newton, newt.args)
```

...or modify the newton function:

```{r, eval=FALSE}
newt <- TMB:::newton
newt <- edit(newt)
do.call(newt, newt.args)
```