Skip to contents

Model and source

  • Citation: Hirt D, Urien S, Jullien V, Firtion G, Rey E, Pons G, Blanche S, Treluyer JM. Age-related effects on nelfinavir and M8 pharmacokinetics: a population study with 182 children. Antimicrob Agents Chemother. 2006;50(3):910-916.
  • Article: https://doi.org/10.1128/aac.50.3.910-916.2006

The model describes oral nelfinavir and its active metabolite M8 (hydroxy- tert-butylamide) in 182 pediatric HIV-1 infected children. Structure (paper Figure 1 and Appendix): one-compartment nelfinavir (depot + central) with first-order absorption and linear elimination via apparent total clearance CL_T/F. The active metabolite M8 is described by a single compartment whose apparent volume Vm is FIXED to 1 L because Vm and the elimination rate constant KM0 are not jointly identifiable; KM0 absorbs the physical M8 volume. Only the small fraction F_MT (~2.5%) of nelfinavir total clearance becomes M8; the remaining 97.5% is lost to non-M8 elimination pathways.

Population

The model was fit to 742 nelfinavir + 557 M8 plasma concentrations from 182 HIV-1 infected children (95 boys, 87 girls) routinely monitored by therapeutic drug monitoring at Paris-area pediatric HIV clinics including Hopital Cochin-Saint-Vincent-de-Paul and Hopital Necker-Enfants Malades. Median age 8.2 years (range 3 days to 17 years), median body weight 21 kg (range 1.7 to 70 kg). The paper stratifies the cohort into three age groups for the FDA dose-recommendation evaluation: < 2 months (n = 25), 2 months to 2 years (n = 36), and 2 to 13 years (n = 121). Nelfinavir was administered only as 250-mg tablets (crumbled in water and added to milk or food for children unable to swallow); the powder formulation was not used due to administration-volume and dissolution difficulties.

Coadministered antiretrovirals tested for covariate effects: efavirenz (n = 10 subjects, 53 obs), nevirapine (n = 33 / 133), ritonavir (n = 3 / 11; excluded from the final analysis), saquinavir (n = 10 / 48; tested, not significant). The model retains only CONMED_NNRTI_IND as the pooled enzyme-inducing NNRTI indicator (efavirenz OR nevirapine; the two drugs were never administered simultaneously and their inducer effects on KM0 were not significantly different).

The same information is available programmatically via readModelDb("Hirt_2006_nelfinavir")$population.

Source trace

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

Equation / parameter Value Source location
lka log(0.48 1/h) Table 2 row Ka (mean 0.48)
lcl log(0.93 L/h/kg) Table 2 row CL_T (mean 0.93); per-kg at AGE = 8.2 yr
lvc log(6.86 L/kg) Table 2 row V (mean 6.86); per-kg at AGE = 8.2 yr
lfmt_m8 log(0.025) Table 2 row F_MT (mean 0.025)
lkel_m8 log(1.88 1/h) Table 2 row K_M0 (mean 1.88)
lvc_m8 log(1 L) FIXED Appendix: “K_M0 = CLm0/Vm with Vm = 1”
e_age_cl_vc -0.29 Table 2 row “CL_T and V, theta_AGE” (mean -0.29)
e_conmed_nnrti_ind_kel_m8 +0.91 Table 2 row “K_M0, theta_INN” (mean 0.91)
etalvc omega^2 = 0.7831 Table 2 row “omega(V)” 109% -> log(1 + 1.09^2)
etalcl omega^2 = 0.1423 Table 2 row “omega(CL_T)” 39.1% -> log(1 + 0.391^2)
etalkel_m8 omega^2 = 0.2169 Table 2 row “omega(K_M0)” 49.2% -> log(1 + 0.492^2)
cov(etalcl, etalkel_m8) 0.0790 Table 2 row “r(CL_T, K_M0) = 0.45” -> 0.45 * sqrt(0.1423 * 0.2169)
addSd 1.65 mg/L Table 2 row “sigma_NELFI” (mean 1.65 ug/mL)
addSd_m8 0.63 mg/L Table 2 row “sigma_M8” (mean 0.63 ug/mL)
d/dt(depot) -ka * depot rxode2 explicit form of the Ka * G input (Appendix)
d/dt(central) ka * depot - kel * central Appendix equation for nelfinavir
d/dt(central_m8) fmt_m8 * kel * central - kel_m8 * central_m8 Appendix equation for M8 (with Vm = 1)

Body-weight scaling: linear (per-kg parameterisation; allometric exponents 0.75 on CL and 1 on V tested and rejected by the paper). Age effect: shared power exponent on CL/F and V/F, with reference age 8.2 yr.

Virtual cohort

Original observed data are not publicly available. The cohort below uses the three age strata reported in the FDA-recommendation evaluation (Figures 5-7 of Hirt 2006): younger than 2 months, 2 months to 2 years, and 2 to 13 years. Per-cohort sample size is 80 subjects; body weight is sampled from a log-normal distribution whose median brackets the cohort’s typical pediatric weight. The NNRTI-inducer indicator (CONMED_NNRTI_IND = 0 for this cohort, matching the no-NNRTI reference; a separate sensitivity panel below toggles it to 1).

set.seed(20060306)
n_per_cohort <- 80L

# WHO / CDC-style typical weights at the cohort midpoints. Ranges
# bracket what Hirt 2006 Table 1 reports for the age strata
# (overall cohort weight 1.7-70 kg; age 3 days to 17 years).
make_cohort <- function(n, age_yr_mean, age_yr_sd, wt_kg_median, wt_kg_sd,
                        cohort_label, id_offset = 0L) {
  ages <- pmin(pmax(rnorm(n, age_yr_mean, age_yr_sd), 0.01), 17)
  wts  <- pmin(pmax(rlnorm(n, log(wt_kg_median), wt_kg_sd), 1.5), 80)
  tibble::tibble(
    id = id_offset + seq_len(n),
    AGE = round(ages, 3),
    WT = round(wts, 1),
    CONMED_NNRTI_IND = 0L,
    cohort = cohort_label
  )
}

subj <- dplyr::bind_rows(
  make_cohort(n_per_cohort, age_yr_mean = 0.08, age_yr_sd = 0.04,
              wt_kg_median = 3.5, wt_kg_sd = 0.15,
              cohort_label = "<2 mo", id_offset = 0L),
  make_cohort(n_per_cohort, age_yr_mean = 1.0, age_yr_sd = 0.5,
              wt_kg_median = 9, wt_kg_sd = 0.20,
              cohort_label = "2 mo-2 yr", id_offset = n_per_cohort),
  make_cohort(n_per_cohort, age_yr_mean = 7.5, age_yr_sd = 3.0,
              wt_kg_median = 25, wt_kg_sd = 0.30,
              cohort_label = "2-13 yr", id_offset = 2L * n_per_cohort)
)
stopifnot(!anyDuplicated(subj$id))

Each subject receives a steady-state TID regimen tailored to their cohort (per the doses the paper evaluates: 50 mg/kg TID in the youngest cohort, 40 mg/kg TID in the middle cohort, 25 mg/kg TID in the older cohort). Simulation runs for 5 days with observations every 30 min.

# Build per-cohort dose-per-administration based on the paper's evaluated
# TID doses (Figures 5-7 of Hirt 2006).
mg_per_kg_tid <- c("<2 mo" = 50, "2 mo-2 yr" = 40, "2-13 yr" = 25)

tau <- 8L                       # TID -> 8-h dosing interval (h)
n_doses <- 15L                  # 5 days at TID; reaches steady state by ~24 h
obs_grid <- seq(0, n_doses * tau, by = 0.5)

build_events <- function(row) {
  amt_mg <- row$WT * mg_per_kg_tid[[row$cohort]]
  dose_times <- seq(0, by = tau, length.out = n_doses)
  ev <- rxode2::et(amt = amt_mg, cmt = "depot", time = dose_times)
  ev <- rxode2::et(ev, time = obs_grid, cmt = "Cc")
  ev <- as.data.frame(ev)
  ev$id <- row$id
  ev$AGE <- row$AGE
  ev$WT <- row$WT
  ev$CONMED_NNRTI_IND <- row$CONMED_NNRTI_IND
  ev$cohort <- row$cohort
  ev$mg_per_kg <- mg_per_kg_tid[[row$cohort]]
  ev
}

events <- do.call(
  rbind,
  lapply(seq_len(nrow(subj)), function(i) build_events(subj[i, ]))
)
stopifnot(!anyDuplicated(unique(events[, c("id", "time", "evid")])))

Simulation

mod <- readModelDb("Hirt_2006_nelfinavir")

sim <- rxode2::rxSolve(
  mod,
  events = events,
  keep = c("AGE", "WT", "CONMED_NNRTI_IND", "cohort", "mg_per_kg")
) |>
  as.data.frame()
#> ℹ parameter labels from comments will be replaced by 'label()'

Replicate published figures

Figure 3 – age effect on apparent CL/F

Hirt 2006 Figure 3 shows individual apparent clearance estimates against age, with the predicted curve CL_T/F = 0.92 * (AGE / 8.2)^(-0.29) (the covariate equation reported on page 912; the value 0.92 is the pre-final estimate, with the final-model value rounded to 0.93). The typical-value relationship is reproduced below by evaluating the model’s clearance equation across an age grid spanning the cohort.

mod_typ <- mod |> rxode2::zeroRe()
#> ℹ parameter labels from comments will be replaced by 'label()'

age_grid <- seq(0.01, 17, length.out = 100)
ref_wt <- 1  # plot CL/F per kg so the curve is comparable to Figure 3

events_age <- {
  ev <- rxode2::et(amt = 100, cmt = "depot", time = 0)
  ev <- rxode2::et(ev, time = c(0.1, 1, 2, 4, 6, 8, 12, 24), cmt = "Cc")
  ev <- as.data.frame(ev)
  out <- do.call(rbind, lapply(seq_along(age_grid), function(i) {
    e <- ev
    e$id <- i
    e$AGE <- age_grid[i]
    e$WT <- ref_wt
    e$CONMED_NNRTI_IND <- 0L
    e
  }))
  out
}

# Derive CL/F per kg from the typical-value model parameters
typ_pars <- tibble::tibble(
  AGE = age_grid,
  cl_per_kg = exp(0) * 0.93 * (age_grid / 8.2)^(-0.29)
)

ggplot(typ_pars, aes(AGE, cl_per_kg)) +
  geom_line() +
  geom_vline(xintercept = 8.2, linetype = "dashed", colour = "grey60") +
  scale_x_continuous(breaks = c(0.1, 0.5, 1, 2, 5, 8.2, 13, 17),
                     trans = "log10") +
  labs(
    x = "Age (years, log scale)",
    y = "Apparent nelfinavir CL/F per kg (L/h/kg)",
    title = "Figure 3 replication -- typical-value CL/F vs age",
    caption = paste(
      "Reproduces Hirt 2006 Figure 3 covariate curve",
      "CL_T/F = 0.93 * (AGE/8.2)^-0.29. Dashed line = reference age 8.2 yr."
    )
  )

Steady-state nelfinavir and M8 profiles by cohort

sim_long <- sim |>
  dplyr::filter(time > 0) |>
  dplyr::select(id, time, cohort, Cc, Cc_m8) |>
  tidyr::pivot_longer(
    cols = c(Cc, Cc_m8),
    names_to = "analyte",
    values_to = "conc"
  ) |>
  dplyr::mutate(analyte = factor(
    analyte,
    levels = c("Cc", "Cc_m8"),
    labels = c("Nelfinavir", "M8")
  ))

vpc_summary <- sim_long |>
  dplyr::group_by(time, cohort, analyte) |>
  dplyr::summarise(
    Q05 = quantile(conc, 0.05, na.rm = TRUE),
    Q50 = quantile(conc, 0.50, na.rm = TRUE),
    Q95 = quantile(conc, 0.95, na.rm = TRUE),
    .groups = "drop"
  )

ggplot(vpc_summary, aes(time, Q50, colour = cohort, fill = cohort)) +
  geom_ribbon(aes(ymin = Q05, ymax = Q95), alpha = 0.15, colour = NA) +
  geom_line() +
  geom_hline(yintercept = 0.8, linetype = "dotted", colour = "grey40") +
  facet_wrap(~analyte, scales = "free_y") +
  scale_y_log10() +
  labs(
    x = "Time after first dose (h)",
    y = "Plasma concentration (mg/L)",
    colour = "Cohort", fill = "Cohort",
    title = "Stochastic VPC by age cohort (TID dosing at the paper's evaluated mg/kg)",
    caption = "Dotted horizontal line = 0.8 mg/L target trough (Hirt 2006)."
  )

NNRTI-inducer effect on M8

The model’s pooled NNRTI-inducer indicator (CONMED_NNRTI_IND) drives a 1.91-fold increase in the M8 elimination rate KM0; the paper reports M8 concentrations were 1.9-fold lower in patients on efavirenz or nevirapine.

events_nni_on <- events |>
  dplyr::filter(cohort == "2-13 yr") |>
  dplyr::mutate(CONMED_NNRTI_IND = 1L)

sim_nni_on <- rxode2::rxSolve(
  mod, events = events_nni_on,
  keep = c("cohort", "CONMED_NNRTI_IND")
) |>
  as.data.frame()
#> ℹ parameter labels from comments will be replaced by 'label()'

sim_nni_off <- sim |>
  dplyr::filter(cohort == "2-13 yr") |>
  dplyr::mutate(CONMED_NNRTI_IND = 0L)

m8_compare <- dplyr::bind_rows(
  sim_nni_off |> dplyr::transmute(time, Cc_m8, regimen = "No NNRTI"),
  sim_nni_on  |> dplyr::transmute(time, Cc_m8, regimen = "EFV or NVP")
) |>
  dplyr::filter(time > 24) |>
  dplyr::group_by(time, regimen) |>
  dplyr::summarise(
    Q50 = quantile(Cc_m8, 0.50, na.rm = TRUE),
    .groups = "drop"
  )

ggplot(m8_compare, aes(time, Q50, colour = regimen)) +
  geom_line() +
  scale_y_log10() +
  labs(
    x = "Time after first dose (h)",
    y = "M8 median concentration (mg/L)",
    colour = NULL,
    title = "M8 concentration: NNRTI-naive vs. EFV/NVP co-administration",
    caption = "Population median M8 in the 2-13 yr cohort. CONMED_NNRTI_IND = 1 inflates KM0 by 1.91x; the paper reports a 1.9-fold reduction in M8."
  )

Approximate steady-state fold reduction in median M8 attributable to the inducer:

ratio_df <- m8_compare |>
  tidyr::pivot_wider(names_from = regimen, values_from = Q50) |>
  dplyr::mutate(fold_reduction = `No NNRTI` / `EFV or NVP`) |>
  dplyr::filter(time >= max(time) - 24)

cat(sprintf(
  "Median M8 fold-reduction (last 24 h of simulation): %.2f (paper reports ~1.9)\n",
  median(ratio_df$fold_reduction, na.rm = TRUE)
))
#> Median M8 fold-reduction (last 24 h of simulation): 1.79 (paper reports ~1.9)

PKNCA validation

PKNCA is run separately for the two analytes. The treatment grouping variable is cohort so per-cohort steady-state NCA can be compared against the trough-target evaluation reported in Figures 5-7 of the source.

# Steady-state interval: the last dosing interval (covers tau = 8 h
# starting at the time of the final dose).
last_dose_time <- (n_doses - 1L) * tau
ss_start <- last_dose_time
ss_end <- last_dose_time + tau

sim_nca_n <- sim |>
  dplyr::filter(!is.na(Cc), time >= ss_start - 0.01, time <= ss_end + 0.01) |>
  dplyr::transmute(id, time, Cc, cohort)

dose_df <- events |>
  dplyr::filter(evid == 1) |>
  dplyr::transmute(id, time, amt, cohort)

conc_obj_n <- PKNCA::PKNCAconc(sim_nca_n, Cc ~ time | cohort + id,
                               concu = "mg/L", timeu = "h")
dose_obj <- PKNCA::PKNCAdose(dose_df, amt ~ time | cohort + id,
                             doseu = "mg")

intervals_ss <- data.frame(
  start = ss_start,
  end = ss_end,
  cmax = TRUE,
  cmin = TRUE,
  tmax = TRUE,
  auclast = TRUE,
  cav = TRUE
)

nca_n <- PKNCA::pk.nca(
  PKNCA::PKNCAdata(conc_obj_n, dose_obj, intervals = intervals_ss)
)
summary_n <- as.data.frame(nca_n$result) |>
  dplyr::filter(PPTESTCD %in% c("cmin", "cmax", "cav", "auclast")) |>
  dplyr::group_by(cohort, PPTESTCD) |>
  dplyr::summarise(
    median = median(PPORRES, na.rm = TRUE),
    q05 = quantile(PPORRES, 0.05, na.rm = TRUE),
    q95 = quantile(PPORRES, 0.95, na.rm = TRUE),
    .groups = "drop"
  )
knitr::kable(
  summary_n,
  digits = 3,
  caption = paste(
    "Simulated steady-state NCA for nelfinavir (Cc) by age cohort.",
    "Cmin compares against the 0.8 mg/L target trough."
  )
)
Simulated steady-state NCA for nelfinavir (Cc) by age cohort. Cmin compares against the 0.8 mg/L target trough.
cohort PPTESTCD median q05 q95
2 mo-2 yr auclast 21.580 10.770 34.324
2 mo-2 yr cav 2.697 1.346 4.290
2 mo-2 yr cmax 3.199 1.757 5.623
2 mo-2 yr cmin 1.888 0.325 3.579
2-13 yr auclast 25.502 13.437 41.835
2-13 yr cav 3.188 1.680 5.229
2-13 yr cmax 3.927 2.217 6.939
2-13 yr cmin 2.031 0.438 4.362
<2 mo auclast 14.727 6.964 23.568
<2 mo cav 1.841 0.871 2.946
<2 mo cmax 2.229 1.098 3.686
<2 mo cmin 1.229 0.237 2.339
sim_nca_m <- sim |>
  dplyr::filter(!is.na(Cc_m8), time >= ss_start - 0.01,
                time <= ss_end + 0.01) |>
  dplyr::transmute(id, time, Cc = Cc_m8, cohort)

conc_obj_m <- PKNCA::PKNCAconc(sim_nca_m, Cc ~ time | cohort + id,
                               concu = "mg/L", timeu = "h")

nca_m <- PKNCA::pk.nca(
  PKNCA::PKNCAdata(conc_obj_m, dose_obj, intervals = intervals_ss)
)
summary_m <- as.data.frame(nca_m$result) |>
  dplyr::filter(PPTESTCD %in% c("cmin", "cmax", "cav", "auclast")) |>
  dplyr::group_by(cohort, PPTESTCD) |>
  dplyr::summarise(
    median = median(PPORRES, na.rm = TRUE),
    q05 = quantile(PPORRES, 0.05, na.rm = TRUE),
    q95 = quantile(PPORRES, 0.95, na.rm = TRUE),
    .groups = "drop"
  )
knitr::kable(
  summary_m,
  digits = 3,
  caption = "Simulated steady-state NCA for the M8 metabolite (Cc_m8) by age cohort."
)
Simulated steady-state NCA for the M8 metabolite (Cc_m8) by age cohort.
cohort PPTESTCD median q05 q95
2 mo-2 yr auclast 4.930 2.409 9.957
2 mo-2 yr cav 0.616 0.301 1.245
2 mo-2 yr cmax 0.759 0.365 1.526
2 mo-2 yr cmin 0.506 0.155 1.044
2-13 yr auclast 7.264 3.288 21.218
2-13 yr cav 0.908 0.411 2.652
2-13 yr cmax 1.168 0.497 3.793
2-13 yr cmin 0.625 0.171 1.981
<2 mo auclast 2.258 1.127 5.054
<2 mo cav 0.282 0.141 0.632
<2 mo cmax 0.372 0.186 0.826
<2 mo cmin 0.210 0.050 0.556

Comparison against published target-trough success rates

Hirt 2006 reports (Results, “Relevance of FDA recommendations”) the percentage of children in each cohort whose minimum plasma nelfinavir concentration reached the 0.8 mg/L target under the FDA-recommended TID regimens. The simulated success rates from this validation are computed below.

sim_trough <- sim |>
  dplyr::filter(time >= ss_start - 0.01, time <= ss_end + 0.01) |>
  dplyr::group_by(id, cohort) |>
  dplyr::summarise(cmin = min(Cc, na.rm = TRUE), .groups = "drop") |>
  dplyr::group_by(cohort) |>
  dplyr::summarise(
    pct_target = round(100 * mean(cmin >= 0.8), 1),
    median_cmin = round(median(cmin), 2),
    .groups = "drop"
  )

published <- tibble::tibble(
  cohort = c("<2 mo", "2 mo-2 yr", "2-13 yr"),
  paper_dose_mg_per_kg_tid = c(50, 40, 25),
  paper_pct_target = c(88, 100, 96)  # Hirt 2006 Results paragraph
)

comparison <- published |>
  dplyr::left_join(sim_trough, by = "cohort")
knitr::kable(
  comparison,
  caption = paste(
    "Percentage of virtual children reaching the 0.8 mg/L target trough",
    "under the paper's TID dose recommendation, simulated vs. Hirt 2006",
    "Results paragraph (Figures 5-7)."
  )
)
Percentage of virtual children reaching the 0.8 mg/L target trough under the paper’s TID dose recommendation, simulated vs. Hirt 2006 Results paragraph (Figures 5-7).
cohort paper_dose_mg_per_kg_tid paper_pct_target pct_target median_cmin
<2 mo 50 88 68.8 1.23
2 mo-2 yr 40 100 86.2 1.89
2-13 yr 25 96 82.5 2.03

Assumptions and deviations

  • Virtual cohort weights and ages were sampled from log-normal distributions whose medians bracket the cohort midpoints reported in Table 1 of Hirt 2006. Exact per-subject demographics from the source TDM database are not publicly available; the simulated success-rate agreement is therefore expected to track the paper’s published percentages within a few percentage points, not exactly.
  • CONMED_NNRTI_IND = 0 everywhere in the main cohort. A subset of the source cohort (n = 10 on EFV, n = 33 on NVP) had inducer co-administration; the main figures simulate the NNRTI-naive case to match the typical-patient interpretation of the FDA dose recommendation evaluation, and a separate NNRTI-effect panel toggles the indicator to 1 in the 2-13 yr cohort to demonstrate the model’s M8 reduction.
  • F_MT apparent formation clearance fraction (0.025). No inter-individual variability per the source (Results section: ISV on F_MT was not significant and excluded). The point estimate is treated as a typical-value structural parameter without an eta in this file.
  • M8 apparent volume FIXED to 1 L because K_M0 and Vm are not jointly identifiable from the data. K_M0 absorbs the physical M8 volume; the model reproduces observed Cc_m8 numerically but the per-subject “amount” in the M8 compartment carries no physiological interpretation in isolation.
  • NNRTI pooling. The source paper pools efavirenz and nevirapine under a single theta_NNI because the two drugs’ inducer effects on M8 were not significantly different and the two were never administered simultaneously in this cohort. This pooling is preserved in the model via the canonical covariate CONMED_NNRTI_IND. A future paper that separately identifies EFV vs NVP effects should use CONMED_EFV and a sibling CONMED_NVP rather than this pooled indicator.
  • Ritonavir and saquinavir were tested by the source paper but not retained (ritonavir excluded due to unstable model with n = 3; saquinavir not statistically significant). The model file does not carry these as covariates; a paper that requires them should extend the covariate set.
  • Steady-state simulation depth. The vignette simulates 5 days of TID dosing (15 doses) to reach steady state; nelfinavir’s half-life in the model is ~5.1 h at the median age, so the last dosing interval is a reliable steady-state window.