---
title: "Carboplatin (Zandvliet 2008)"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Carboplatin (Zandvliet 2008)}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

# Model and source

* Citation: `r rxode2::rxode2(readModelDb("Zandvliet_2008_carboplatin"))$reference`
* Description: `r rxode2::rxode2(readModelDb("Zandvliet_2008_carboplatin"))$description`
* Article: <https://doi.org/10.1111/j.1365-2125.2008.03230.x>

# Population

Zandvliet 2008 fitted the carboplatin PK in n = 16 adult cancer patients with
refractory solid tumours enrolled in a Phase I dose-escalation study of
indisulam + carboplatin combination chemotherapy. Demographics from Table 1
are: median age 63 years (range 19-81), median body weight 66 kg (range
43-116), median BSA 1.73 m^2 (range 1.36-2.22), median serum creatinine
74 umol/L (range 33-132), 11 male / 5 female, all Caucasian. Carboplatin
doses were calculated with the Calvert formula targeted to an AUC of
5-6 mg.min/mL and administered as a 30 min IV infusion on day 2 of each
3- or 4-weekly cycle. A total of 111 carboplatin observations (pre-dose,
end-of-infusion, then 1, 2, 4, 6, 8, and 23 h after end of infusion) entered
the PK fit.

The same information is available programmatically via
`rxode2::rxode2(readModelDb("Zandvliet_2008_carboplatin"))$population`.

# Source trace

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

| Equation / parameter | Value | Source location |
|----------------------|-------|-----------------|
| `cl_renal_fraction` (theta1) | 0.76 (RSE 0.05) | Table 2 row 1; Equation 2 |
| `lcl_nonrenal` (theta2, fixed) | log(1.5) | Table 2 footnote **; Calvert 1989 (ref [16]) |
| `lvc` (V1) | log(15.5) | Table 2 row 2 (V_central = 15.5 L) |
| `lq` (Q) | log(3.46) | Table 2 row 3 (Q = 3.46 L/h) |
| `lvp` (V2) | log(9.86) | Table 2 row 4 (V_peripheral = 9.86 L) |
| `etalcl` (CL IIV 13%) | log(0.13^2 + 1) | Table 2 row 1 IIV column |
| `etalvc` (V1 IIV 54%) | log(0.54^2 + 1) | Table 2 row 2 IIV column |
| `etalq`  (Q  IIV 46%) | log(0.46^2 + 1) | Table 2 row 3 IIV column |
| `etalvp` (V2 IIV 31%) | log(0.31^2 + 1) | Table 2 row 4 IIV column |
| `propSd` (8.2%) | 0.082 | Table 2 row 5 |
| Cockcroft-Gault `crcl_cg` | `(140 - AGE) * WT * (1 - 0.15 * SEXF) * 0.074 / CREAT` | Equation 1 |
| CL covariate model | `(theta1 * crcl_cg + theta2) * exp(eta_CL)` | Equation 2 |
| 2-compartment ODE | `d/dt(central) = -kel * central - k12 * central + k21 * peripheral1` | Methods, Figure 1 (CARBOPLATIN block) |

# Virtual cohort

Original observed data are not publicly available. The validation below uses a
virtual cohort of 200 adult oncology patients with covariate distributions
approximating Zandvliet 2008 Table 1 (median age 63 years, median weight 66
kg, median SCr 74 umol/L, 31% female, all Caucasian). The Phase I protocol
fixed each subject's carboplatin dose by the Calvert formula at a target AUC
of 6 mg.min/mL, recomputed from the model's clearance prediction; this lets
the simulated AUC distribution be checked directly against the 5-6 mg.min/mL
target.

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

n_subj <- 200L

# Log-normal sampling around the published medians, with CVs chosen so the
# central 95% spans the reported ranges. Hard clipped to the published min/max.
clip <- function(x, lo, hi) pmin(pmax(x, lo), hi)

cohort <- tibble::tibble(
  id    = seq_len(n_subj),
  AGE   = clip(round(rnorm(n_subj, mean = 60, sd = 14)),    19,   81),
  WT    = clip(round(exp(rnorm(n_subj, log(66), 0.22)), 1), 43, 116),
  SEXF  = as.integer(runif(n_subj) < 0.3125),                            # 5 / 16
  CREAT = clip(round(exp(rnorm(n_subj, log(74), 0.30)), 0), 33, 132)
)

# Per-subject Cockcroft-Gault CLcr (L/h), per Zandvliet 2008 Equation 1.
cohort$crcl_cg <- with(cohort,
  (140 - AGE) * WT * (1 - 0.15 * SEXF) * 0.074 / CREAT)

# Calvert dose (mg) at AUC = 6 mg.min/mL using the typical-value CL prediction
# from this paper (CL_typ = 0.76 * CrCl_CG + 1.5 L/h). Multiplying by 1000/60
# converts CL from L/h to mL/min so AUC * CL gives dose in mg (Equation 5).
target_auc <- 6
cohort$dose_mg <- round(target_auc * (cohort$crcl_cg * 0.76 + 1.5) * 1000 / 60)

# Build the event table: a single 30 min IV infusion into the central
# compartment, then observations at the Zandvliet 2008 sampling schedule
# (pre-dose, end of infusion, then 1, 2, 4, 6, 8, 23 h after end of infusion),
# plus a denser grid up to 36 h so the PKNCA AUCinf extrapolation has enough
# tail samples.
inf_dur <- 0.5     # hours
obs_times <- sort(unique(c(
  0,
  seq(0.1, inf_dur, length.out = 4),       # during infusion
  inf_dur + c(0.01, 0.5, 1, 2, 4, 6, 8, 23, 30, 36)
)))

events <- cohort |>
  dplyr::mutate(rate = dose_mg / inf_dur) |>     # mg/h
  dplyr::rowwise() |>
  dplyr::do({
    subj <- as.data.frame(.)
    dose_row <- tibble::tibble(
      id   = subj$id,
      time = 0,
      amt  = subj$dose_mg,
      rate = subj$rate,
      evid = 1L,
      cmt  = "central",
      AGE   = subj$AGE,  WT = subj$WT,
      SEXF  = subj$SEXF, CREAT = subj$CREAT,
      cohort = "AUC6"
    )
    obs_rows <- tibble::tibble(
      id   = subj$id,
      time = obs_times,
      amt  = NA_real_,
      rate = 0,
      evid = 0L,
      cmt  = "central",
      AGE   = subj$AGE,  WT = subj$WT,
      SEXF  = subj$SEXF, CREAT = subj$CREAT,
      cohort = "AUC6"
    )
    dplyr::bind_rows(dose_row, obs_rows)
  }) |>
  dplyr::ungroup() |>
  dplyr::arrange(id, time, dplyr::desc(evid))
```

# Simulation

```{r simulate}
mod <- readModelDb("Zandvliet_2008_carboplatin")
sim <- rxode2::rxSolve(mod, events = events,
                       keep = c("AGE", "WT", "SEXF", "CREAT", "cohort")) |>
  as.data.frame()
```

For a deterministic typical-patient profile (paper's Discussion reports a
terminal half-life of 4.4 h for a 60 year old, 75 kg male, SCr 90 umol/L),
zero out the random effects and simulate one subject with those covariates:

```{r typical}
typical_subj <- tibble::tibble(
  id = 1L, AGE = 60, WT = 75, SEXF = 0L, CREAT = 90
)
typical_subj$crcl_cg <- with(typical_subj,
  (140 - AGE) * WT * (1 - 0.15 * SEXF) * 0.074 / CREAT)
typical_subj$dose_mg <- round(
  target_auc * (typical_subj$crcl_cg * 0.76 + 1.5) * 1000 / 60)

typ_events <- dplyr::bind_rows(
  typical_subj |>
    dplyr::mutate(time = 0, amt = dose_mg, rate = dose_mg / inf_dur,
                  evid = 1L, cmt = "central"),
  typical_subj |>
    tidyr::expand_grid(time = sort(unique(c(0.01, 0.1, 0.25, 0.5,
                                            seq(0.5, 36, by = 0.25))))) |>
    dplyr::mutate(amt = NA_real_, rate = 0, evid = 0L, cmt = "central")
) |>
  dplyr::select(id, time, amt, rate, evid, cmt, AGE, WT, SEXF, CREAT) |>
  dplyr::arrange(id, time, dplyr::desc(evid))

mod_typ <- mod |> rxode2::zeroRe()
sim_typ <- rxode2::rxSolve(mod_typ, events = typ_events) |>
  as.data.frame()
```

# Replicate published figures

The published Figure 2c (carboplatin pharmacokinetic profile) shows the
characteristic biphasic decline. The typical-value simulation below is the
deterministic counterpart that the paper's Figure 2c overlays the individual
profiles onto.

```{r figure-2c, fig.width = 6, fig.height = 4}
ggplot(sim_typ |> dplyr::filter(time > 0),
       aes(x = time, y = Cc)) +
  geom_line() +
  scale_y_log10() +
  labs(x = "Time after start of infusion (h)",
       y = "Free (ultrafilterable) carboplatin (mg/L)",
       title = "Typical patient: 60 y, 75 kg male, SCr 90 umol/L",
       caption = "Deterministic profile from Zandvliet 2008 carboplatin PK; companion to Figure 2c.")
```

A stochastic version showing the per-subject variability across the cohort:

```{r figure-2c-vpc, fig.width = 7, fig.height = 4}
sim |>
  dplyr::filter(time > 0) |>
  dplyr::group_by(time) |>
  dplyr::summarise(
    Q05 = quantile(Cc, 0.05, na.rm = TRUE),
    Q50 = quantile(Cc, 0.50, na.rm = TRUE),
    Q95 = quantile(Cc, 0.95, na.rm = TRUE),
    .groups = "drop"
  ) |>
  ggplot(aes(x = time, y = Q50)) +
  geom_ribbon(aes(ymin = Q05, ymax = Q95), alpha = 0.25) +
  geom_line() +
  scale_y_log10() +
  labs(x = "Time after start of infusion (h)",
       y = "Free carboplatin (mg/L)",
       title = "Median + 5-95% prediction interval across 200 simulated subjects",
       caption = "Calvert-dosed to AUC = 6 mg.min/mL using each subject's Zandvliet-CL prediction.")
```

# PKNCA validation

```{r pknca, message = FALSE, warning = FALSE}
sim_nca <- sim |>
  dplyr::filter(!is.na(Cc)) |>
  dplyr::select(id, time, Cc, cohort)

# Guarantee a time = 0 row per (id, cohort); IV bolus / infusion starts at C = 0.
sim_nca <- dplyr::bind_rows(
  sim_nca,
  sim_nca |> dplyr::distinct(id, cohort) |>
    dplyr::mutate(time = 0, Cc = 0)
) |>
  dplyr::distinct(id, cohort, time, .keep_all = TRUE) |>
  dplyr::arrange(id, cohort, time)

conc_obj <- PKNCA::PKNCAconc(sim_nca, Cc ~ time | cohort + id)

dose_df <- events |>
  dplyr::filter(evid == 1L) |>
  dplyr::select(id, time, amt, cohort) |>
  dplyr::mutate(duration = inf_dur, route = "intravascular")

dose_obj <- PKNCA::PKNCAdose(dose_df, amt ~ time | cohort + id,
                             route = "route", duration = "duration")

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

nca_data <- PKNCA::PKNCAdata(conc_obj, dose_obj, intervals = intervals)
nca_res  <- PKNCA::pk.nca(nca_data)
```

## Comparison against published NCA

The paper itself does not tabulate a PK NCA summary in the article; the
single quantitative PK-NCA-style statement is in the Discussion (terminal
half-life 4.4 h "for a typical male patient, age 60 years, weight 75 kg,
serum creatinine 90 umol/L"). A simulated AUC0-inf for that typical
patient, dosed via the Calvert formula at AUC target 6 mg.min/mL,
re-multiplied by 60/1000 to recover the paper's AUC units, should be
~ 6 mg.min/mL (the target) -- not exactly 6 because the rounded integer
dose introduces a small quantization error.

```{r nca_compare}
published <- tibble::tribble(
  ~cohort, ~half.life,
  "AUC6",  4.4
)

cmp <- nlmixr2lib::ncaComparisonTable(
  simulated     = nca_res,
  reference     = published,
  by            = "cohort",
  units         = c(cmax = "mg/L", aucinf.obs = "mg*h/L",
                    tmax = "h", half.life = "h"),
  tolerance_pct = 20
)

knitr::kable(
  cmp,
  caption = "Simulated cohort NCA vs Zandvliet 2008 typical-patient terminal half-life.  Stars mark any row where simulated and reference differ by > 20%.",
  align   = c("l", "l", "r", "r", "r")
)
```

The terminal half-life check should match within rounding: with
CL = 0.76 * 4.93 + 1.5 = 5.25 L/h, V1 = 15.5 L, Q = 3.46 L/h, V2 = 9.86 L
(plugging the typical-patient covariates AGE = 60, WT = 75, SEXF = 0,
CREAT = 90 umol/L into the model), the analytical 2-compartment terminal
half-life is

```{r analytical_t12, echo = FALSE}
CL <- 5.25;  V1 <- 15.5;  Q <- 3.46;  V2 <- 9.86
k10 <- CL / V1
k12 <- Q  / V1
k21 <- Q  / V2
sum_k <- k10 + k12 + k21
disc  <- sqrt(sum_k^2 - 4 * k10 * k21)
alpha <- (sum_k + disc) / 2
beta  <- (sum_k - disc) / 2
sprintf("Analytical terminal half-life ln(2) / beta = %.2f h", log(2) / beta)
```

which matches the paper's 4.4 h. The simulated cohort median (Calvert-dosed
to AUC = 6 mg.min/mL using each subject's predicted CL) gives AUC0-inf
that, after the 60/1000 unit conversion to mg.min/mL, should distribute
tightly around 6.

```{r auc_check, fig.width = 6, fig.height = 3.5}
auc_summary <- as.data.frame(nca_res$result) |>
  dplyr::filter(PPTESTCD == "aucinf.obs")

# Convert from PKNCA's default mg*h/L to the paper's mg*min/mL:
#   X mg*h/L * 60 min/h * 1 L/1000 mL = X * 60/1000 = X * 0.06 mg*min/mL
auc_summary$AUC_mg_min_mL <- auc_summary$PPORRES * 60 / 1000

ggplot(auc_summary, aes(x = AUC_mg_min_mL)) +
  geom_histogram(bins = 30, fill = "grey70", colour = "black") +
  geom_vline(xintercept = target_auc, colour = "red", linetype = "dashed") +
  labs(x = "Simulated AUC0-inf (mg*min/mL)",
       y = "Count",
       title = sprintf("Cohort AUC after Calvert dosing at target = %d mg*min/mL",
                       target_auc),
       caption = sprintf("Median %.2f, IQR [%.2f, %.2f]; target = 6 mg.min/mL.",
                         median(auc_summary$AUC_mg_min_mL),
                         quantile(auc_summary$AUC_mg_min_mL, 0.25),
                         quantile(auc_summary$AUC_mg_min_mL, 0.75)))
```

# Assumptions and deviations

- **PD layer not extracted.** Zandvliet 2008 is primarily a PK-PD combination
  paper covering carboplatin PK + Friberg-style myelosuppression PD for
  indisulam + carboplatin combination chemotherapy. Per operator decision
  in sidecar `frompeople-847` request-001 / response-001 (option B), only
  the carboplatin 2-compartment PK from Table 2 is packaged here. The
  myelosuppression PD model from Table 3 is deferred because it requires
  the indisulam PK structural model from Zandvliet 2006 (J Pharmacokinet
  Pharmacodyn 33:543-570, doi:10.1007/s10928-006-9021-5) and Zandvliet 2007
  (Clin Cancer Res 13:2970-2976, doi:10.1158/1078-0432.CCR-06-2978), neither
  of which is on disk; that PK is fixed (not re-estimated) in Zandvliet 2008.

- **Cohort distribution.** The original n = 16 individual covariate
  vectors are not reported; the virtual cohort uses log-normal sampling
  around the published medians clipped to the published min / max
  (Table 1).

- **Non-renal CL fixed at Calvert 1989.** theta2 = 1.5 L/h is fixed
  (Table 2 footnote **). Confidence intervals are not derivable for this
  component because the source paper did not re-estimate it.

- **Residual error.** Table 2 reports the residual error as a single
  percentage on log-transformed concentrations (8.2%, RSE 0.11). This is
  encoded as a proportional error `propSd = 0.082` in linear space, the
  standard nlmixr2 equivalent of NONMEM's log-additive residual under
  FOCE-INTER.

- **Carboplatin concentration units.** The package reports `Cc` in mg/L
  (dose in mg, V1 in L) to match the convention used by other carboplatin
  popPK models in nlmixr2lib (e.g. `Ekhart_2008_carboplatin.R`). The source
  paper measured platinum concentrations in umol/L and reports
  pharmacodynamic slope coefficients in L/umol; convert via the carboplatin
  molar mass (371.25 g/mol; 1 mg/L = 2.694 umol/L) when comparing.

- **No published per-group NCA.** Zandvliet 2008 does not tabulate Cmax /
  Tmax / AUC by dose group. The comparison table above includes only the
  terminal half-life statement from the Discussion (4.4 h for a typical
  60 y, 75 kg male, SCr 90 umol/L).
