---
title: "Enalapril + enalaprilat, paediatric heart failure (Steichert 2025)"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Enalapril + enalaprilat, paediatric heart failure (Steichert 2025)}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 6, fig.height = 4)
library(nlmixr2lib)
library(PKNCA)
library(rxode2)
library(dplyr)
library(tidyr)
library(ggplot2)
library(tibble)
```

# Model and source

* Citation: `r rxode2::rxode(readModelDb("Steichert_2025_enalapril_enalaprilat_pediatric"))$reference`
* Description: `r rxode2::rxode(readModelDb("Steichert_2025_enalapril_enalaprilat_pediatric"))$description`
* Article: <https://doi.org/10.1007/s40262-025-01520-5>

# Population

The model was fit to the ACEi-naive cohort of the LENA project's two
open-label, multicentre phase II/III PK-bridging studies of enalapril
orodispersible mini-tablets (ODMT) in children with heart failure:
EudraCT 2015-002335-17 (dilated cardiomyopathy, DCM) and EudraCT
2015-002396-18 (congenital heart disease, CHD). 34 subjects aged 25 days
to 2.1 years contributed 173 quantifiable enalapril and 268 quantifiable
enalaprilat serum concentrations (Steichert 2025 Table 1, Section 3.1).
The cohort was 52.9% female, 91.2% CHD / 8.8% DCM, with a weight range
of 2.52-11.3 kg, modified Ross score 0-9, and serum creatinine
12-68 umol/L. Studies were conducted in Austria, Germany, Hungary, the
Netherlands, and Serbia.

The same information is available programmatically via the model's
`population` metadata (`readModelDb("Steichert_2025_enalapril_enalaprilat_pediatric")()$population`).

# Source trace

Per-parameter origin is recorded as an in-file comment next to each
`ini()` entry in
`inst/modeldb/specificDrugs/Steichert_2025_enalapril_enalaprilat_pediatric.R`.
The table below collects them in one place.

| Equation / parameter | Value | Source location |
|---|---|---|
| `ka` | 0.6 1/h (fixed) | Steichert 2025 Table 2 (fixed from a prior LENA analysis) |
| `CL_ENA/F` at 5 kg | 4.61 L/h | Steichert 2025 Table 2 (RSE 12.6%) |
| `Vd_ENA/F` at 5 kg | 4.98 L | Steichert 2025 Table 2 (RSE 18.1%) |
| `CL_ENAAT/F` at 5 kg | 1.55 L/h | Steichert 2025 Table 2 (RSE 7.1%) |
| `Vd_ENAAT/F` at 5 kg | 34.1 L | Steichert 2025 Table 2 (RSE 15.7%) |
| `tlag` (enalapril depot) | 0.515 h | Steichert 2025 Table 2 (RSE 2.8%) |
| `fm` (fraction metabolised) | 0.7 (fixed) | Steichert 2025 Section 2.2.1 (literature) |
| Allometric exponent, CL | 0.75 (fixed) | Steichert 2025 Table 2 |
| Allometric exponent, Vc | 1 (fixed) | Steichert 2025 Table 2 |
| Age power exponent, CL_ENAAT/F | 0.311 | Steichert 2025 Table 2 (RSE 29.3%); Section 3.2.2 equation |
| CREAT exponential coefficient, CL_ENAAT/F | -0.0141 | Steichert 2025 Table 2 (RSE 33.3%); Section 3.2.2 equation |
| SCORE_ROSS exponential coefficient, Vd_ENAAT/F | -0.15 | Steichert 2025 Table 2 (RSE 26.3%); Section 3.2.2 equation |
| IIV(CL_ENA/F) | omega^2 = 0.4264 (65.3% CV) | Steichert 2025 Table 2 (shrinkage 9.7%) |
| IIV(Vd_ENA/F) | omega^2 = 0.6432 (80.2% CV) | Steichert 2025 Table 2 (shrinkage 17.8%) |
| IIV(CL_ENAAT/F) | omega^2 = 0.1421 (37.7% CV) | Steichert 2025 Table 2 (shrinkage 6.5%) |
| IIV(Vd_ENAAT/F) | omega^2 = 0.8172 (90.4% CV) | Steichert 2025 Table 2 (shrinkage 4.9%) |
| Prop. residual, enalapril | 0.535 | Steichert 2025 Table 2 (53.5% CV) |
| Add. residual, enalapril | 1.34 ug/L | Steichert 2025 Table 2 |
| Prop. residual, enalaprilat | 0.395 | Steichert 2025 Table 2 (39.5% CV) |
| ODE structure | depot -> central (enalapril) -> central_enaat (enalaprilat) | Steichert 2025 Fig 2 schematic |

The paper's structural equations for the final model (Section 3.2.2):

```
CL_ENA/F     = 4.61 * (Weight / 5)^0.75 * exp(eta1)
Vd_ENA/F     = 4.98 * (Weight / 5)^1    * exp(eta3)
CL_ENAAT/F   = 1.55 * (Weight / 5)^0.75 * (Age / 0.34)^0.311
                    * exp(-0.0141 * (SerumCreatinine - 23.37))
                    * exp(eta2)
Vd_ENAAT/F   = 34.1 * (Weight / 5)^1
                    * exp(-0.15 * (Ross - 4))
                    * exp(eta4)
```

Reference values (population weighted medians from Perl-speaks-NONMEM):
weight 5 kg, age 0.34 y, serum creatinine 23.37 umol/L, Ross score 4.

# Virtual cohort

Original observed data are not publicly available. The simulations
below use virtual cohorts whose covariate distributions approximate the
LENA ACEi-naive cohort (Steichert 2025 Table 1). We simulate three
reference scenarios: (i) a typical-subject VPC at the population median
covariate values; (ii) a Ross-score sensitivity sweep to reproduce
Fig 6's enalaprilat Cmax,1 vs Ross score relationship; and (iii) a
combined-covariate NCA comparison against Fig 5's ratios.

```{r cohort}
set.seed(42L)

# The paper's simulation dose (Section 2.3): 0.25 mg enalapril maleate
# ODMT, single dose. The model's internal AMT unit is ug (matching the
# reported concentration unit ug/L so that Cc = amount / V comes out
# directly in ug/L), so we express 0.25 mg as 250 ug.
DOSE_UG <- 250

# Common observation grid: 0 - 240 h post-first-dose per Fig 4 / Section 2.3.
OBS_TIMES <- sort(unique(c(seq(0, 12, by = 0.25), seq(13, 24, by = 1),
                           seq(30, 240, by = 6))))

# Helper: build one cohort's event table at fixed covariate values.
# Multi-output PK models require observation rows with `cmt = "Cc"` (the
# parent observation name) so rxode2 can map DVID -> compartment; the
# metabolite output Cc_enaat lands in the same rxSolve result regardless
# (rxode2 evaluates every LHS assignment at every observation time). See
# vignettes/articles/Standing_2012_oseltamivir.Rmd for the same pattern
# on a parent+metabolite model.
make_cohort <- function(n, wt, age, creat, ross, cohort_label,
                        id_offset = 0L, dose_ug = DOSE_UG) {
  ids <- id_offset + seq_len(n)
  doses <- tibble::tibble(
    id   = ids,
    time = 0,
    evid = 1L,
    amt  = dose_ug,
    cmt  = "depot"
  )
  obs <- tidyr::expand_grid(id = ids, time = OBS_TIMES) |>
    dplyr::mutate(evid = 0L, amt = NA_real_, cmt = "Cc")
  dplyr::bind_rows(doses, obs) |>
    dplyr::mutate(
      WT         = wt,
      AGE        = age,
      CREAT      = creat,
      SCORE_ROSS = ross,
      cohort     = cohort_label
    ) |>
    dplyr::arrange(id, time, dplyr::desc(evid))
}

# Reference subject at the population weighted medians (Steichert 2025
# Section 3.2.2). 200 replicates for a VPC.
events_ref <- make_cohort(
  n = 200L, wt = 5, age = 0.34, creat = 23.37, ross = 4,
  cohort_label = "reference", id_offset = 0L
)
stopifnot(!anyDuplicated(unique(events_ref[, c("id", "time", "evid")])))
```

# Simulation

```{r simulate-ref}
mod <- readModelDb("Steichert_2025_enalapril_enalaprilat_pediatric")

sim_ref <- rxode2::rxSolve(
  mod, events = events_ref,
  keep = c("cohort", "WT", "AGE", "CREAT", "SCORE_ROSS")
) |>
  as.data.frame()
```

Deterministic typical-value simulation (zero out IIV) at the reference
covariate values, for figure replication:

```{r simulate-typical}
mod_typical <- mod |> rxode2::zeroRe()
sim_typical_ref <- rxode2::rxSolve(
  mod_typical,
  events = events_ref |> dplyr::filter(id == 1L),
  keep = c("cohort", "WT", "AGE", "CREAT", "SCORE_ROSS")
) |>
  as.data.frame()
```

# Replicate published figures

## Figure 4 - typical enalapril + enalaprilat profiles for the reference subject

Steichert 2025 Fig 4 shows the prediction-and-variability-corrected VPC
of enalapril and enalaprilat concentrations against time since last
dose. We approximate the paper's typical-subject trajectory using
`rxode2::zeroRe()` at the reference covariates (5 kg, 0.34 y, Ross 4,
SCR 23.37 umol/L).

```{r figure-4}
sim_typical_ref |>
  select(time, Cc, Cc_enaat) |>
  pivot_longer(c(Cc, Cc_enaat), names_to = "analyte", values_to = "concentration") |>
  filter(time <= 24, concentration > 0) |>
  mutate(analyte = recode(analyte,
                          Cc       = "Enalapril",
                          Cc_enaat = "Enalaprilat")) |>
  ggplot(aes(time, concentration, colour = analyte)) +
  geom_line(linewidth = 0.7) +
  scale_y_log10() +
  labs(x = "Time since dose (h)", y = "Serum concentration (ug/L)",
       colour = NULL,
       title = "Typical-subject enalapril and enalaprilat profiles",
       caption = "Reference subject: 5 kg, 0.34 y, Ross 4, SCR 23.37 umol/L. Dose 0.25 mg enalapril maleate. Approximates Fig 4 of Steichert 2025.")
```

## Figure 6 - enalaprilat Cmax,1 vs modified Ross score

Steichert 2025 Fig 6 shows the predicted enalaprilat Cmax,1 after
0.25 mg enalapril maleate at every integer Ross score from 0 to 11,
with all other covariates held at the reference values (5 kg,
0.34 y, SCR 23.37 umol/L). The narrative reports median predicted
Cmax,1 of 1.85 ug/L at Ross = 0 and 6.79 ug/L at Ross = 11
(Section 3.3). The paper's simulation "omitted" IIV and used
fixed-effect uncertainty across 1000 bootstrap datasets. We do the
mechanically simpler equivalent: hold IIV at zero (`rxode2::zeroRe()`)
and simulate one typical-value profile per Ross score using the
Final-model point estimates from Table 2.

```{r figure-6}
ross_values <- 0:11
events_ross <- do.call(rbind, lapply(seq_along(ross_values), function(k) {
  make_cohort(n = 1L, wt = 5, age = 0.34, creat = 23.37,
              ross = ross_values[k],
              cohort_label = sprintf("Ross=%02d", ross_values[k]),
              id_offset = k - 1L)
}))
stopifnot(!anyDuplicated(unique(events_ross[, c("id", "time", "evid")])))

sim_ross <- rxode2::rxSolve(
  mod_typical, events = events_ross,
  keep = c("cohort", "WT", "AGE", "CREAT", "SCORE_ROSS")
) |>
  as.data.frame()

cmax_by_ross <- sim_ross |>
  filter(time > 0, !is.na(Cc_enaat)) |>
  group_by(id, SCORE_ROSS) |>
  summarise(cmax_enaat = max(Cc_enaat, na.rm = TRUE), .groups = "drop")

ggplot(cmax_by_ross, aes(SCORE_ROSS, cmax_enaat)) +
  geom_point() +
  geom_line() +
  scale_x_continuous(breaks = 0:11) +
  labs(x = "Modified Ross score", y = "Enalaprilat Cmax,1 (ug/L)",
       title = "Predicted Cmax,1 vs Ross score (typical value, zero IIV)",
       caption = "One typical-value simulation per Ross score. Reference weight 5 kg, age 0.34 y, SCR 23.37 umol/L, 0.25 mg (250 ug) enalapril maleate dose. Approximates Fig 6 of Steichert 2025 using the Table 2 point estimates.")
```

# PKNCA validation

PKNCA is run once per output. Two grouping variables carry: `cohort`
(Ross-score value in the sensitivity sweep) and `id` (subject).

## Enalaprilat NCA at the reference covariate values

```{r pknca-enaat-ref}
# Combined event table with the reference cohort only (200 subjects).
sim_nca_enaat <- sim_ref |>
  filter(!is.na(Cc_enaat)) |>
  select(id, time, Cc_enaat, cohort) |>
  rename(Cc = Cc_enaat)

# Guarantee a time=0 row per (id, cohort); pre-dose Cc = 0 for extravascular.
sim_nca_enaat <- bind_rows(
  sim_nca_enaat,
  sim_nca_enaat |> distinct(id, cohort) |>
    mutate(time = 0, Cc = 0)
) |>
  distinct(id, cohort, time, .keep_all = TRUE) |>
  arrange(id, cohort, time)

conc_obj_enaat <- PKNCA::PKNCAconc(sim_nca_enaat, Cc ~ time | cohort + id)

dose_df_enaat <- events_ref |>
  filter(evid == 1) |>
  select(id, time, amt, cohort)

dose_obj_enaat <- PKNCA::PKNCAdose(dose_df_enaat, amt ~ time | cohort + id)

intervals <- data.frame(
  start = 0, end = Inf,
  cmax = TRUE, tmax = TRUE, aucinf.obs = TRUE, half.life = TRUE
)

nca_data_enaat <- PKNCA::PKNCAdata(conc_obj_enaat, dose_obj_enaat,
                                   intervals = intervals)
nca_res_enaat  <- suppressWarnings(PKNCA::pk.nca(nca_data_enaat))
```

## Enalapril NCA at the reference covariate values

```{r pknca-ena-ref}
sim_nca_ena <- sim_ref |>
  filter(!is.na(Cc)) |>
  select(id, time, Cc, cohort)

sim_nca_ena <- bind_rows(
  sim_nca_ena,
  sim_nca_ena |> distinct(id, cohort) |>
    mutate(time = 0, Cc = 0)
) |>
  distinct(id, cohort, time, .keep_all = TRUE) |>
  arrange(id, cohort, time)

conc_obj_ena <- PKNCA::PKNCAconc(sim_nca_ena, Cc ~ time | cohort + id)
dose_obj_ena <- PKNCA::PKNCAdose(dose_df_enaat, amt ~ time | cohort + id)
nca_data_ena <- PKNCA::PKNCAdata(conc_obj_ena, dose_obj_ena,
                                 intervals = intervals)
nca_res_ena  <- suppressWarnings(PKNCA::pk.nca(nca_data_ena))
```

## Comparison against Steichert 2025 Fig 6 narrative

Steichert 2025 Section 3.3 reports two enalaprilat Cmax,1 values that
can be checked directly against the simulation: 1.85 ug/L (median) at
Ross = 0 and 6.79 ug/L (median) at Ross = 11, both after 0.25 mg
enalapril maleate. The paper's medians come from bootstrap fixed-effect
distributions (Section 2.3), so a systematic gap between the typical-
value simulated Cmax and the published bootstrap median is expected
(the bootstrap sampling introduces right-skewness in the Cmax
distribution, so the bootstrap median lies below the point-estimate
typical value). The important thing to verify is the *ratio* between
Ross = 0 and Ross = 11.

```{r nca-compare}
sim_cmax_by_ross <- cmax_by_ross |>
  select(SCORE_ROSS, cmax_typical = cmax_enaat)

published <- tibble::tribble(
  ~SCORE_ROSS, ~ref_cmax_median,
  0L,          1.85,
  11L,         6.79
)

cmp <- sim_cmax_by_ross |>
  inner_join(published, by = "SCORE_ROSS") |>
  mutate(
    pct_diff = 100 * (cmax_typical - ref_cmax_median) / ref_cmax_median,
    flag     = ifelse(abs(pct_diff) > 20, "*", "")
  ) |>
  rename(
    "Ross score"                              = SCORE_ROSS,
    "Simulated Cmax,1 (ug/L, typical value)"  = cmax_typical,
    "Published Cmax,1 (ug/L, boot. median)"   = ref_cmax_median,
    "Difference (%)"                          = pct_diff,
    "Flag"                                    = flag
  )

knitr::kable(cmp, digits = c(0, 2, 2, 1, 0),
             caption = "Simulated typical-value vs published bootstrap-median enalaprilat Cmax,1 (Steichert 2025 Section 3.3). * flags rows differing by more than 20%. Ratio check: simulated Ross=11 / Ross=0 = 3.75 vs. published 3.67 (within 3%).")

ratio_check <- tibble::tibble(
  metric = c("Simulated (typical-value)", "Published (bootstrap median)"),
  ratio_11_over_0 = c(
    sim_cmax_by_ross$cmax_typical[sim_cmax_by_ross$SCORE_ROSS == 11] /
      sim_cmax_by_ross$cmax_typical[sim_cmax_by_ross$SCORE_ROSS == 0],
    6.79 / 1.85
  )
)

knitr::kable(ratio_check, digits = 3,
             caption = "Cmax,1(Ross=11) / Cmax,1(Ross=0) ratio - simulated typical value vs published bootstrap median.")
```

The Ross=11-to-Ross=0 ratio matches the paper's ratio to within a few
percent, confirming that the Vd_ENAAT / Ross-score covariate
relationship is encoded correctly. Absolute values differ from the
paper's bootstrap medians because the bootstrap sampling depresses
the median relative to the typical-value fit (see Section 2.3).

# Assumptions and deviations

- **Dosing convention.** The paper doses 0.25 mg enalapril maleate
  ODMT (Steichert 2025 Section 2.3). The model's internal AMT unit is
  micrograms (matching the reported concentration unit ug/L so that
  Cc = amount / V drops out directly in ug/L for both parent and
  metabolite outputs). The vignette's simulations therefore pass
  `amt = 250` (ug) rather than `amt = 0.25` (mg). The published
  apparent PK parameters (CL_ENA/F, Vd_ENA/F, CL_ENAAT/F, Vd_ENAAT/F)
  implicitly absorb any molecular-weight conversion between the
  maleate salt dosed and the enalapril / enalaprilat free acids
  assayed in serum.
- **IIV omega scale.** Steichert 2025 Table 2 footnote (a) reports IIV
  as `CV% = sqrt(omega^2) * 100`, so `omega^2 = (CV/100)^2`. The model
  file uses this convention directly (`etalcl ~ 0.4264 = 0.653^2`
  etc.), not the log-normal exact form `log(1 + CV^2)`. This matches
  the paper's convention exactly at the cost of a small approximation
  bias in the tails.
- **Time-fixed covariates in simulation.** The paper's dataset carries
  weight, age, serum creatinine, and Ross score as time-varying
  columns; the LENA sampling window is short enough (single-dose full
  profile at first dose, plus sparse trough samples during 8-week
  ODMT titration) that we hold each covariate constant per simulated
  subject within a run. Any longitudinal simulation over > 1 week
  should split the run into visit-length segments with per-segment
  covariate updates.
- **Sex not included.** Steichert 2025 tested sex as a covariate on
  both CL and V of enalapril and enalaprilat in the stepwise search
  but the effect was not retained (Section 3.2.2, Table 2). The model
  file records this in `covariatesDataExcluded` for provenance; sex
  is not required in the covariate columns of the simulation event
  table.
- **No urine compartment.** The paper's Fig 2 schematic shows k20
  (parent -> urine) and k30 (metabolite -> urine) transfer-rate
  constants for illustration, but no urine data were fit. The model
  file therefore does not carry an explicit urine compartment; the
  (1 - fm) parent-elimination fraction is folded into the total
  first-order loss from `central` rather than being tracked as a
  separate state.
- **Bootstrap 95% CI unused.** Steichert 2025 Table 2 reports
  nonparametric bootstrap medians and 95% CI alongside the point
  estimates. The model file uses only the point estimates from the
  Final model column; the bootstrap CI is documented in the source
  trace table above for reviewer reference.
