# Initial conditions cheatsheet

    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()`:

    # 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

    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)`:

    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

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