--- 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`.