---
title: "Initial conditions cheatsheet"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Initial conditions cheatsheet}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

```{r setup}
library(nlmixr2targets)
```

# Two forms accepted inside `nlmixr2targets`

The native nlmixr2 DSL syntax for a compartment initial value is
`cmt(0) <- value` inside a `model({...})` block. Inside
`tar_nlmixr()` and `tar_nlmixr_multimodel()` you may also write
`cmt(initial) <- value`, which **`nlmixr2targets` translates back to
`cmt(0) <- value` before nlmixr2 ever sees the model**. The
`cmt(initial)` form is a workaround that exists only for use through
`nlmixr2targets`; it is **not** a form that nlmixr2 itself understands,
so it will fail if you call `nlmixr2est::nlmixr()` directly on a model
that still contains `cmt(initial)`.

Both forms below produce the same fit when routed through
`tar_nlmixr()`:

```{r both-forms, eval = FALSE}
# Form 1: native nlmixr2 DSL syntax (works in nlmixr2 directly and
# inside nlmixr2targets)
model({
  d / dt(central) <- -kel * central
  central(0) <- 0          # initial value at time 0
  cp <- central / vc
  cp ~ add(cpaddSd)
})

# Form 2: `cmt(initial) <- value` -- a `nlmixr2targets`-only
# workaround. Use this only when something prevents the natural
# `central(0) <- 0` form from being walked by `targets` (see the
# "Known limitation" section below). `nlmixr2targets` rewrites this
# back to `central(0) <- 0` before passing the model to nlmixr2.
model({
  d / dt(central) <- -kel * central
  # nlmixr2targets-only; not understood by bare nlmixr2
  central(initial) <- 0
  cp <- central / vc
  cp ~ add(cpaddSd)
})
```

# What `tar_nlmixr()` does behind the scenes

`targets` walks every function in the user's environment with
`codetools::findGlobals()` and rejects replacement-style assignments
whose target is not a symbol -- which is exactly what `central(0) <- 0`
looks like. To allow the natural DSL syntax, `tar_nlmixr()` walks the
captured `object` expression at construction time and rewrites every
`cmt(0) <- value` inside `model({...})` to `cmt(initial) <- value`. The
rewrite is reversed at runtime before the model is handed to nlmixr2, so
the fit you get is identical.

The rewrite mutates the function binding **in place** in the
environment where it was found, so if you later print

```{r print-body, eval = FALSE}
body(my_model)
```

at the REPL after calling `tar_nlmixr()`, you will see
`central(initial) <- 0` rather than the originally-typed
`central(0) <- 0`. This is the only user-visible side effect.

# Pipe forms

Both pipe forms below also work, including with `cmt(0)`:

```{r pipe-forms, eval = FALSE}
tar_nlmixr(
  name = m1,
  object = pheno |> model({
    central(0) <- 0
  }, append = TRUE),
  data  = nlmixr2data::pheno_sd,
  est   = "saem"
)

tar_nlmixr(
  name = m2,
  object = pheno |> ini(cpaddSd = 0.3),
  data  = nlmixr2data::pheno_sd,
  est   = "saem"
)
```

A runtime delayed-eval wrapper restores `cmt(0)` before nlmixr2's pipe
handlers see the parsed body.

# Known limitation: env functions never routed through `tar_nlmixr()`

`targets` walks **every** function in the analysed environment, not just
the ones referenced by a target. If you define

```{r unused-function, eval = FALSE}
sketch <- function() {
  ini({
    a <- 1
  })
  model({
    central(0) <- 0
  })
}
```

in the same environment but never pass `sketch` to `tar_nlmixr()`,
`targets`' static analysis will still try to walk its body and will
report a `codetools::findGlobals()` error. The workaround is either:

- pass the function through `tar_nlmixr()` (the construction-time
  rewriter cleans it up), or
- write the manual `central(initial) <- 0` form by hand. Because
  `cmt(initial)` is only meaningful when `nlmixr2targets` is the
  one parsing the model, a function written this way is **not
  usable in a bare nlmixr2 workflow** -- it only fits if it is
  routed through `tar_nlmixr()` / `tar_nlmixr_multimodel()`.

This case is pinned by a test in `tests/testthat/test-tar_nlmixr.R`.

# Summary

- `cmt(0) <- value` is the native nlmixr2 DSL syntax. Prefer it.
- `cmt(initial) <- value` is a `nlmixr2targets`-only workaround:
  `nlmixr2targets` rewrites it back to `cmt(0) <- value` before
  nlmixr2 sees the model. Bare nlmixr2 does **not** understand
  `cmt(initial)`; a model written that way only fits when routed
  through `tar_nlmixr()` / `tar_nlmixr_multimodel()`.
- Inside `tar_nlmixr()`, the package quietly rewrites `cmt(0)`
  forms in env to `cmt(initial)` (to survive `targets`' static
  analysis) and back again at evaluation time.
- After `tar_nlmixr()`, `body(my_model)` will show the rewritten
  form; this is cosmetic and reversed at runtime.
- Pipe forms work too via a runtime wrapper.
- Functions in env that contain `cmt(0)` but are never routed
  through `tar_nlmixr()` need the manual `cmt(initial)` workaround
  -- with the caveat that they then only fit through
  `nlmixr2targets`.
