---
title: "Multimodel comparison workflows"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Multimodel comparison workflows}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

```{r setup}
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

```{r multimodel-plan, eval = FALSE}
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:

```{r param-extract, eval = FALSE}
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:

```{r within-list-piping, eval = FALSE}
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.
