# Multimodel comparison workflows

    library(nlmixr2targets)

# Why a dedicated multimodel factory?

`tar_nlmixr_multimodel()` lets you declare several candidate models for the same dataset in one call. Each model gets its own simplify/fit/relabel chain, but the data-simplification step is shared when possible, and within-list piping (`models[["A"]] |> ini(...)` referenced from another entry) is resolved into a dependency on the prior fit so the data does not have to be re-prepared.

The output is a list of targets you can drop into your `_targets.R` plan.

# Minimal comparison plan

    library(targets)
    library(tarchetypes)
    library(nlmixr2targets)

    pheno_base <- function() {
      ini({
        lcl <- log(0.008); label("Typical clearance")
        lvc <- log(0.6); label("Typical volume of distribution")
        etalcl + etalvc ~ c(1, 0.01, 1)
        cpaddSd <- 0.1; label("Additive residual SD")
      })
      model({
        cl <- exp(lcl + etalcl)
        vc <- exp(lvc + etalvc)
        kel <- cl / vc
        d / dt(central) <- -kel * central
        cp <- central / vc
        cp ~ add(cpaddSd)
      })
    }

    list(
      tar_nlmixr_multimodel(
        name = candidate_fits,
        data = nlmixr2data::pheno_sd,
        est  = "saem",
        "Base"                              = pheno_base,
        "Base + tighter residual prior"     = pheno_base |> ini(cpaddSd = 0.05),
        "Base + alternate residual"         = pheno_base |> model({
          cp ~ prop(cpaddSd)
        }, append = TRUE)
      ),
      tar_target(
        aic_table,
        data.frame(
          model = names(candidate_fits),
          aic   = vapply(candidate_fits, AIC, numeric(1)),
          bic   = vapply(candidate_fits, BIC, numeric(1)),
          ofv   = vapply(candidate_fits, function(f) f$objDf$OBJF[1], numeric(1))
        )
      )
    )

After `tar_make()`, the `aic_table` target gives you a compact summary of the candidates. Add `dAIC` or weight columns to taste.

# Extracting parameter estimates across models

A common pattern is to pull a particular fixed effect out of every candidate fit to compare:

    tar_target(
      clearance_estimates,
      data.frame(
        model = names(candidate_fits),
        lcl   = vapply(
          candidate_fits,
          function(f) f$ui$iniDf$est[f$ui$iniDf$name == "lcl"],
          numeric(1)
        )
      )
    )

This works because `tar_nlmixr_multimodel()` returns a target whose value is a named list of fits.

# Within-list piping for nested edits

If the second model is an edit of the first, refer to the first by its key inside the same `tar_nlmixr_multimodel()` call:

    tar_nlmixr_multimodel(
      name = candidate_fits,
      data = nlmixr2data::pheno_sd,
      est  = "saem",
      "Base"            = pheno_base,
      "Tighter residual" =
        candidate_fits[["Base"]] |> ini(cpaddSd = 0.05)
    )

Behind the scenes, `nlmixr2targets` rewrites `candidate_fits[["Base"]]` to a dependency on the base model’s `_fit_simple` target. Iteratively resolving these references is what makes within-list piping work without forcing the entire pipeline to re-run when one model changes.

If you write a circular reference (A piping from B and B piping from A) the function errors out at construction time with a clear message.

# Tips for large model libraries

- Group models by data flavor. Each call to `tar_nlmixr_multimodel()` is scoped to one dataset. If you have several datasets, use one call per dataset (with distinct `name`s) and combine the resulting lists at the plan level.
- Use `tar_outdated()` aggressively when iterating. Cosmetic edits (labels, metadata) do not invalidate `_fit_simple` thanks to the strip-restore behaviour. See `vignette("caching", package = "nlmixr2targets")`.
- Watch the indirect cache. Iterative model edits leave orphaned cache entries. Use `nlmixr2targets_cache_status()` and `nlmixr2targets_cache_prune()` periodically.