---
title: "Lampalizumab in cynomolgus monkeys (Le 2015)"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Lampalizumab in cynomolgus monkeys (Le 2015)}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

# Model and source

* Citation: Le KN, Gibiansky L, Good J, Davancaze T, van Lookeren Campagne M,
  Loyet KM, Morimoto A, Jin JY, Damico-Beyer LA, Hanley WD. A Mechanistic
  Pharmacokinetic/Pharmacodynamic Model of Factor D Inhibition in Cynomolgus
  Monkeys by Lampalizumab for the Treatment of Geographic Atrophy. J Pharmacol
  Exp Ther. 2015;355(2):288-296.
* Article: <https://doi.org/10.1124/jpet.115.227223>
* PMID: 26359312.

`Le_2015_lampalizumab_cyno` is a preclinical semi-mechanistic ocular-systemic
target-mediated drug-disposition (TMDD) PK/PD model for lampalizumab (an
antigen-binding fragment of a humanized anti-complement factor D monoclonal
antibody) in cynomolgus monkeys. The published human analog by the same first
author is packaged as `Le_2015_lampalizumab` (see PMID 26535160); the two
files differ in structural complexity because the monkey model has invasive
vitreous, aqueous humor, and retinal sampling that the human dataset does
not.

The model uses a quasi-steady-state approximation (Gibiansky 2008) so that
each anatomical compartment carries a total lampalizumab pool and a total
complement factor D (CFD) pool related by the equilibrium constant
`K_ss = 11.7 pM`. Six ordinary differential equations describe the vitreous,
serum, and drug-peripheral compartments for lampalizumab, plus the vitreous,
serum, and CFD-peripheral compartments for CFD. Aqueous humor and retinal
lampalizumab observations are algebraic partitions of the vitreous total
concentration. Because this is a preclinical naive-pool analysis, the model
carries no inter-individual variability.

# Population

The model was estimated from three GLP studies (08-1021, 08-0782, 08-0783)
conducted at Covance (Madison, WI) in healthy cynomolgus monkeys weighing 2
to 4 kg. Study 08-1021 (n = 29) delivered single i.v. doses of 0.2, 2, or 20
mg per animal (3 male animals per dose) plus bilateral intravitreal (ITV) 1
or 10 mg per eye doses (10 male animals per dose group). Study 08-0782 (n =
8) delivered bilateral ITV 1, 3, 5, or 10 mg per eye (2 female animals per
dose). Study 08-0783 (n = 82) delivered bilateral ITV 1, 5, or 10 mg per
eye (5 male + 5 female animals per dose group). Samples were collected from
serum (all studies), vitreous humor (08-1021), aqueous humor (08-1021), and
retinal tissue (08-1021 retinal PK subgroup) per the schedule in Le 2015
Table 1.

The programmatic metadata is available via
`readModelDb("Le_2015_lampalizumab_cyno")()$population`.

# Source trace

Every value in `ini()` and every equation in `model()` is anchored to a
specific location in Le 2015. The table below is a single-page reference for
reviewers; the same information also lives as an in-file comment next to
each parameter and ODE line in
`inst/modeldb/specificDrugs/Le_2015_lampalizumab_cyno.R`.

| Symbol | Value | Units | Source location |
|--------|-------|-------|-----------------|
| `V_VITR` | 2.2 | mL | Table 2 row `V_VITR` (RSE 7.5%) |
| `k_out` | 0.24 | 1/day | Table 2 row `k_out` (RSE 2.1%) |
| `k_outC` | 0.75 | 1/day | Table 2 row `k_outC` (RSE 17%) |
| `k_outT` | 0.30 FIX | 1/day | Table 2 row `k_outT`; fixed from unpublished in-house vitreous CFD half-life |
| `k_inC` | 0 FIX | 1/day | Table 2 row `K_inC`; complex influx assumed negligible |
| `lambda_VA` | 4.4 | unitless | Table 2 row `lambda_VA` (RSE 12%) |
| `lambda_TVA` | 0.47 | unitless | Table 2 row `lambda_TVA` (RSE 24%) |
| `lambda_VR` | 7.3 | unitless | Table 2 row `lambda_VR` (RSE 15%) |
| `F1` | 0.84 | unitless | Table 2 row `F1` (RSE 1.2%) |
| `V_ser` | 130 | mL | Table 2 row `V_ser` (RSE 5.8%) |
| `k` | 22 | 1/day | Table 2 row `k` (RSE 6.6%; Results text reports 21.3 /day for the derived 0.8 h half-life) |
| `k_deg` | 96 | 1/day | Table 2 row `k_deg` (RSE 10%) |
| `k_int` | 4.2 | 1/day | Table 2 row `k_int` (RSE 5.6%; paper prose uses `k_inT`) |
| `k_syn` | 2.6 | nmol/mL/day | Table 2 row `k_syn` (RSE 12%) |
| `k_SV` | 0.00032 | 1/day | Table 2 row `k_sv` (RSE 22%) |
| `k_12` | 3.4 | 1/day | Table 2 row `k_12` (RSE 12%) |
| `k_21` | 1.8 | 1/day | Table 2 row `k_21` (RSE 8.2%) |
| `k_t12 = k_t21` | 24 | 1/day | Table 2 row `k_t12 = k_t21` (RSE 54%; symmetric distribution constrained by paper) |
| `K_ss` | 1.17e-5 | nmol/mL | Text page 290; fixed to Loyet 2014 in-vitro K_D = 11.7 pM = 1.17e-5 nmol/mL |
| d/dt vitreous drug | Eq. 1 | -- | Page 290, Equation 1 |
| d/dt vitreous CFD | Eq. 2 | -- | Page 290, Equation 2 |
| d/dt serum drug | Eq. 3 | -- | Page 290, Equation 3 (factor of 2 for bilateral ITV) |
| d/dt serum CFD | Eq. 4 | -- | Page 290, Equation 4 (factor of 2 for bilateral ITV) |
| d/dt peripheral drug | Eq. 5 | -- | Page 290, Equation 5 |
| d/dt peripheral CFD | Eq. 6 | -- | Page 290, Equation 6 |
| QSS unbound drug (vitreous, serum) | Eqs. 7-8 | -- | Page 290 |
| Aqueous drug = vitreous / lambda_VA | Eq. 9 | -- | Page 290 |
| Aqueous CFD = vitreous / lambda_TVA | Eq. 10 | -- | Page 290 |
| Retinal drug = vitreous / lambda_VR | Eq. 11 | -- | Page 290 |

# Units and dose conversion

The model runs on internal units of **nmol** for amounts, **nmol/mL** for
concentrations, **mL** for volumes, and **days** for time. Lampalizumab is
an approximately 48 kDa Fab (Loyet 2014); dose amounts in the paper are
reported in mg. The helper below converts mg to nmol using MW = 48 kDa.

```{r units}
MW_lampalizumab_kDa <- 48
mg_to_nmol <- function(mg, MW_kDa = MW_lampalizumab_kDa) {
  # 1 mg / (MW g/mmol) = mg / (MW*1000 mg/mmol) mmol; *1e6 to convert mmol -> nmol.
  mg * 1e6 / (MW_kDa * 1000)
}
# Sanity check: 1 mg lampalizumab (48 kDa) = 1e-3 g / 48000 g/mol = 2.083e-8 mol
# = 20.833 nmol.
stopifnot(abs(mg_to_nmol(1) - 1000 / 48) < 1e-6)
```

Concentrations are reported in the paper's figures in nM
(nanomolar = nmol/L). One nmol/mL is 1000 nM, so simulated `nmol/mL` values
are multiplied by 1000 to overlay the paper's y-axis.

# Load the model

```{r load-model}
mod <- readModelDb("Le_2015_lampalizumab_cyno")
mod_typ <- rxode2::zeroRe(mod)  # deterministic (all residual SDs are already fixed to 0)
```

# 1. Baseline steady-state check (no drug)

Before dosing, the model should hold CFD at its baseline in every
compartment indefinitely. The paper's baseline values follow from the mass
balance (`ksyn - kdeg * R_ser = 0` in serum, `k_SV * R_ser * V_ser / V_VITR =
k_outT * R_vitr` in vitreous, and `k_t12 = k_t21` so peripheral matches
serum):

* Serum CFD baseline: `k_syn / k_deg = 2.6 / 96 = 0.02708 nmol/mL = 27.08 nM`.
* Vitreous CFD baseline: `k_SV * R_ser_ss * V_ser / (k_outT * V_VITR) =
  0.00032 * 0.02708 * 130 / (0.30 * 2.2) = 0.001707 nmol/mL = 1.71 nM`.
* Peripheral CFD baseline: equals serum baseline (27.08 nM).

```{r steady-state}
# Multi-output residual-error model requires observation rows to route to a
# specific residual-error stream (dvid). Using the observable name (CSER,
# CVITR, etc.) is the canonical routing for multi-DV nlmixr2 models; the
# residual-error slots are already committed by the model's `Cc ~ prop()`
# declarations, so the event-table reference does not renumber compartments.
ev_ss <- rxode2::et(amt = 0, cmt = "central", time = 0, evid = 1) |>
  rxode2::et(time = seq(0, 30, by = 1), cmt = "CSER")
sim_ss <- rxode2::rxSolve(mod_typ, ev_ss)

ss_table <- tibble(
  compartment = c("Serum CFD", "Vitreous CFD", "Peripheral CFD"),
  simulated_nM = c(
    signif(sim_ss$total_target_central[nrow(sim_ss)] * 1000, 4),
    signif(sim_ss$total_target[nrow(sim_ss)] * 1000, 4),
    signif(sim_ss$total_target_peripheral1[nrow(sim_ss)] * 1000, 4)
  ),
  paper_analytic_nM = c(27.08, 1.71, 27.08)
)
knitr::kable(ss_table, caption = "CFD steady-state check at t = 30 days (no drug). Simulated and paper-derived analytic baseline agree to four significant figures.")

stopifnot(all.equal(diff(range(sim_ss$total_target)), 0, tolerance = 1e-10))
stopifnot(all.equal(diff(range(sim_ss$total_target_central)), 0, tolerance = 1e-10))
stopifnot(all.equal(diff(range(sim_ss$total_target_peripheral1)), 0, tolerance = 1e-10))
```

# 2. Intravenous 20 mg bolus (Le 2015 Figure 2A)

A single 20 mg i.v. dose replicates the top-right panel of Figure 2A.
Serum concentrations of both lampalizumab and total CFD are reported. The
model predicts a bi-phasic disposition of drug (distribution then flip-flop
elimination) and a rapid drop of free CFD followed by a rebound in total
CFD (because the drug-CFD complex clears more slowly than free CFD).

```{r iv-20mg}
iv_dose_nmol <- mg_to_nmol(20)

obs_grid_iv <- c(0.083, 0.5, 1, 2, 3, 5, 8, 24, 34, 48, 96, 168) / 24
ev_iv <- rxode2::et(amt = iv_dose_nmol, cmt = "central", time = 0, evid = 1) |>
  rxode2::et(time = obs_grid_iv, cmt = "CSER") |>
  rxode2::et(time = obs_grid_iv, cmt = "RSER")

sim_iv <- rxode2::rxSolve(mod_typ, ev_iv) |>
  as.data.frame() |>
  filter(time > 0) |>
  transmute(
    time_days = time,
    time_hours = time * 24,
    serum_lamp_nM = CSER * 1000,
    serum_cfd_nM = RSER * 1000
  )

sim_iv_long <- sim_iv |>
  pivot_longer(c(serum_lamp_nM, serum_cfd_nM), names_to = "species", values_to = "conc_nM") |>
  mutate(species = recode(species,
                          serum_lamp_nM = "Total lampalizumab",
                          serum_cfd_nM  = "Total CFD"))

ggplot(sim_iv_long, aes(time_days, conc_nM, colour = species)) +
  geom_line(linewidth = 1) +
  scale_y_log10() +
  scale_colour_manual(values = c("Total lampalizumab" = "red3", "Total CFD" = "steelblue")) +
  labs(x = "Time (days)", y = "Concentration (nM, log scale)", colour = NULL,
       title = "IV 20 mg per animal, serum time course (Le 2015 Fig. 2A right panel)") +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom")
```

The systemic elimination half-life derived from the serum lampalizumab
decline (post-distribution, from ~2 h onwards) is close to the paper's
reported 0.8 h. A simple log-linear fit to the mono-exponential tail:

```{r iv-halflife}
tail_iv <- sim_iv |> filter(time_hours >= 2, time_hours <= 6)
if (nrow(tail_iv) >= 3) {
  fit <- lm(log(serum_lamp_nM) ~ time_hours, data = tail_iv)
  t_half_h_iv <- log(2) / (-coef(fit)[2])
  cat(sprintf("Simulated systemic t1/2 (2-6 h): %.2f h; paper: ~0.8 h (k = 21.3 /day derived value)\n",
              t_half_h_iv))
}
```

# 3. Intravitreal 10 mg per eye bolus (Le 2015 Figure 2B-E)

ITV administration is bilateral (both eyes get the same dose). The model
represents one eye's vitreous, so an ITV event splits into two rows in the
event table:

1. `cmt = "depot", amt = per_eye_dose_nmol` -- the bioavailability `F1 = 0.84`
   in the model routes this to the vitreous.
2. `cmt = "central", amt = 2 * (1 - F1) * per_eye_dose_nmol` -- the paper's
   "fast microcirculation absorption" leak. The factor `2 *` reflects that
   BOTH eyes contribute this leak into the shared systemic compartment.

```{r itv-10mg}
per_eye_mg <- 10
per_eye_nmol <- mg_to_nmol(per_eye_mg)
F1 <- 0.84
serum_leak_nmol <- 2 * (1 - F1) * per_eye_nmol

# Observation grid taken from Le 2015 Table 1 for study 08-1021 ITV arms
# (10 h onwards; the paper's ocular sampling times).
obs_days_serum   <- c(0, 0.75, 2, 6, 10, 24, 34, 48, 96, 120, 154, 192, 288, 384) / 24
obs_days_ocular  <- c(10, 24, 34, 48, 96, 120, 154, 192, 288, 384) / 24
obs_days_retina  <- c(24, 48, 120, 192, 384) / 24

ev_itv <- rxode2::et(amt = per_eye_nmol,   cmt = "depot",   time = 0, evid = 1) |>
  rxode2::et(amt = serum_leak_nmol,       cmt = "central", time = 0, evid = 1) |>
  rxode2::et(time = obs_days_serum,  cmt = "CSER") |>
  rxode2::et(time = obs_days_ocular, cmt = "CVITR") |>
  rxode2::et(time = obs_days_ocular, cmt = "CAQ") |>
  rxode2::et(time = obs_days_retina, cmt = "CRET") |>
  rxode2::et(time = obs_days_serum,  cmt = "RSER") |>
  rxode2::et(time = obs_days_ocular, cmt = "RVITR") |>
  rxode2::et(time = obs_days_ocular, cmt = "RAQ")

sim_itv <- rxode2::rxSolve(mod_typ, ev_itv) |> as.data.frame() |> filter(time > 0)
```

## Serum profiles (Figure 2B)

```{r itv-serum-plot}
sim_serum <- sim_itv |>
  transmute(time,
            `Total lampalizumab` = CSER * 1000,
            `Total CFD`          = RSER * 1000) |>
  pivot_longer(-time, names_to = "species", values_to = "conc_nM")

ggplot(sim_serum, aes(time, conc_nM, colour = species)) +
  geom_line(linewidth = 1) +
  scale_y_log10() +
  scale_colour_manual(values = c("Total lampalizumab" = "red3", "Total CFD" = "steelblue")) +
  labs(x = "Time (days)", y = "Concentration (nM, log scale)", colour = NULL,
       title = "ITV 10 mg/eye bilateral - serum (Le 2015 Fig. 2B, 10 mg column)") +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom")
```

## Vitreous, aqueous, and retinal profiles (Figure 2C-E)

```{r itv-ocular-plot}
sim_ocular <- sim_itv |>
  transmute(time,
            Vitreous_lamp = CVITR * 1000,
            Aqueous_lamp  = CAQ   * 1000,
            Retina_lamp   = CRET  * 1000,
            Vitreous_CFD  = RVITR * 1000,
            Aqueous_CFD   = RAQ   * 1000) |>
  pivot_longer(-time, names_to = "series", values_to = "conc_nM") |>
  mutate(
    tissue  = case_when(grepl("Vitreous", series) ~ "Vitreous",
                        grepl("Aqueous",  series) ~ "Aqueous",
                        grepl("Retina",   series) ~ "Retina"),
    species = if_else(grepl("lamp", series), "Total lampalizumab", "Total CFD")
  )

ggplot(sim_ocular, aes(time, conc_nM, colour = species)) +
  geom_line(linewidth = 1) +
  scale_y_log10() +
  scale_colour_manual(values = c("Total lampalizumab" = "red3", "Total CFD" = "steelblue")) +
  facet_wrap(~ tissue, ncol = 3) +
  labs(x = "Time (days)", y = "Concentration (nM, log scale)", colour = NULL,
       title = "ITV 10 mg/eye bilateral - ocular (Le 2015 Fig. 2C-E, 10 mg column)") +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom")
```

The vitreous log-linear elimination slope is `-k_out = -0.24 /day`
(half-life 2.9 days per paper). The retina and aqueous humor track
vitreous by constant partition coefficients (`lambda_VR = 7.3`,
`lambda_VA = 4.4`); this is why the aqueous and retinal curves parallel
the vitreous curve with a vertical offset.

```{r itv-vitreous-halflife}
vitreous_tail <- sim_itv |> filter(time >= 3, time <= 16, CVITR > 0)
fit_vitr <- lm(log(CVITR) ~ time, data = vitreous_tail)
t_half_vitr_d <- log(2) / (-coef(fit_vitr)[2])
cat(sprintf(
  "Simulated vitreous t1/2 (3-16 d): %.2f days (paper: 2.9 days, k_out = 0.24 /day)\n",
  t_half_vitr_d
))
```

# 4. Target occupancy dose-response (Le 2015 Figure 5)

Figure 5 of Le 2015 shows the time course of CFD target occupancy in
vitreous and serum for ITV 1 and 10 mg per eye. Target occupancy is the
fraction of total CFD that is drug-bound: `TO = 1 - R_free / R_total`.
Within the QSS approximation, `R_free / R_total = K_ss / (K_ss + C_free)`,
so `TO = C_free / (K_ss + C_free)`.

```{r target-occupancy}
occupancy_arm <- function(per_eye_mg, id) {
  per_eye_nmol <- mg_to_nmol(per_eye_mg)
  leak_nmol    <- 2 * (1 - F1) * per_eye_nmol
  obs_grid <- seq(0, 60, by = 0.25)
  ev <- rxode2::et(amt = per_eye_nmol,   cmt = "depot",   time = 0, evid = 1, id = id) |>
    rxode2::et(amt = leak_nmol,          cmt = "central", time = 0, evid = 1, id = id) |>
    rxode2::et(time = obs_grid, cmt = "CVITR", id = id) |>
    rxode2::et(time = obs_grid, cmt = "CSER",  id = id)
  ev
}

ev_to <- bind_rows(
  as.data.frame(occupancy_arm(1L,  id = 1L)) |> mutate(dose_mg = 1L),
  as.data.frame(occupancy_arm(10L, id = 2L)) |> mutate(dose_mg = 10L)
)
sim_to <- rxode2::rxSolve(mod_typ, events = ev_to, keep = "dose_mg") |>
  as.data.frame() |>
  filter(time > 0) |>
  mutate(
    Kss = 1.17e-5,
    vitreous_TO = Cvu / (Kss + Cvu),
    serum_TO    = Csu / (Kss + Csu),
    dose_label = sprintf("Dose/eye = %d mg", dose_mg)
  )

sim_to_long <- sim_to |>
  transmute(time, dose_label,
            `Vitreous CFD` = vitreous_TO,
            `Serum CFD`    = serum_TO) |>
  pivot_longer(c(`Vitreous CFD`, `Serum CFD`), names_to = "compartment", values_to = "TO")

ggplot(sim_to_long, aes(time, TO, colour = compartment)) +
  geom_line(linewidth = 1) +
  facet_wrap(~ dose_label, ncol = 2) +
  scale_colour_manual(values = c("Vitreous CFD" = "black", "Serum CFD" = "red3")) +
  scale_y_continuous(limits = c(0, 1), breaks = seq(0, 1, 0.25)) +
  labs(x = "Time (days)", y = "Target occupancy (fraction bound)", colour = NULL,
       title = "CFD target occupancy after ITV lampalizumab (Le 2015 Fig. 5)") +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom")
```

The paper reports vitreous target occupancy remaining above 95% for 34
days (1 mg) and 44 days (10 mg), and serum target occupancy peaking at
41% (1 mg) and 89% (10 mg) within the first day post-dose.

```{r target-occupancy-summary}
occupancy_summary <- sim_to |>
  group_by(dose_label) |>
  summarise(
    max_serum_TO      = max(serum_TO),
    days_vitr_TO_gt95 = if (any(vitreous_TO < 0.95)) {
      max(time[vitreous_TO >= 0.95])
    } else {
      max(time)
    }
  )
knitr::kable(occupancy_summary,
             col.names = c("ITV dose", "Peak serum TO", "Days vitreous TO >= 95%"),
             caption = "Simulated summary vs Le 2015 Fig. 5 (paper: 34 d and 44 d above 95% vitreous TO for 1 and 10 mg; 41% and 89% peak serum TO).")
```

# 5. Perturbation-recovery of CFD baseline

If the CFD compartments are displaced from their steady state and the
system is run forward without dosing, the baseline should re-emerge. This
is the classic endogenous-turnover check for the target sub-model.

```{r perturbation}
ev_pert <- rxode2::et(amt = 0, cmt = "central", time = 0, evid = 1) |>
  rxode2::et(time = seq(0, 20, by = 0.1), cmt = "RSER")

ss_serum   <- 2.6 / 96
ss_vitr    <- 0.00032 * ss_serum * 130 / (0.30 * 2.2)
init_perturbed <- c(
  total_target             = 2 * ss_vitr,
  total_target_central     = 0.5 * ss_serum,
  total_target_peripheral1 = 0.5 * ss_serum
)
sim_pert <- rxode2::rxSolve(mod_typ, ev_pert, inits = init_perturbed) |> as.data.frame()

ggplot(sim_pert |>
         transmute(time,
                   `Vitreous CFD` = total_target * 1000,
                   `Serum CFD`    = total_target_central * 1000,
                   `Peripheral CFD` = total_target_peripheral1 * 1000) |>
         pivot_longer(-time, names_to = "compartment", values_to = "conc_nM"),
       aes(time, conc_nM, colour = compartment)) +
  geom_line(linewidth = 1) +
  labs(x = "Time (days)", y = "Total CFD (nM)", colour = NULL,
       title = "CFD baseline recovery after perturbation (2x vitreous, 0.5x serum/peripheral)") +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom")

final_nM <- tail(sim_pert, 1)
stopifnot(abs(final_nM$total_target_central       - ss_serum) / ss_serum < 1e-3)
stopifnot(abs(final_nM$total_target_peripheral1   - ss_serum) / ss_serum < 1e-3)
stopifnot(abs(final_nM$total_target               - ss_vitr)  / ss_vitr  < 1e-3)
```

All three CFD compartments return to within 0.1% of the analytic baseline
by day 20.

# Assumptions and deviations

* **Residual error not reported.** Le 2015 describes the residual-error
  model qualitatively ("proportional error model") but does not report
  numeric variance for any of the seven observation streams. Per the skill
  policy for unreported RUV with structural values present, each
  proportional residual SD is encoded as `fixed(0)`. Deterministic
  simulation therefore reproduces the paper's typical-value trajectories
  exactly; downstream users who need a residual-noise structure for a
  stochastic VPC should override these SDs with an assumed value (a
  Genentech internal analysis or a reasonable ~25% CV) and document that
  choice.
* **Molecular weight assumed 48 kDa.** Lampalizumab is described in the
  paper as an antigen-binding fragment (Fab). Loyet 2014 (`Loyet et al.
  2014`), cited by the paper for the in-vitro K_D, reports lampalizumab as
  ~48 kDa. This MW is used only to convert user-supplied mg doses into the
  internal nmol amount. Amounts and concentrations in Table 2 are already
  in molar units, so estimated parameters do not depend on this MW.
* **Bilateral ITV encoded as one representative vitreous.** The paper's
  ODEs represent one eye's vitreous, with a factor of 2 on the vitreous-
  to-serum flow terms to account for bilateral dosing. This is faithful
  to the paper's formulation; a user simulating a monocular ITV
  administration should modify the model to remove the factor of 2 in the
  serum ODEs, or dose only one virtual eye and post-process
  accordingly.
* **The 2*(1-F1) serum leak is a user-side dose split.** The paper
  encodes the serum leak as a source of drug that enters serum at the
  same instant as the ITV vitreous dose. The model's bioavailability
  `f(depot) <- F1` handles the vitreous side; the serum leak is delivered
  via a second event-table row targeting `central` with amount
  `2 * (1 - F1) * per_eye_dose`. IV bolus users target `central` directly
  with a single event (F = 1).
* **Equation 2, first term.** The paper's Equation 2 uses `(K_ss + C_vu)`
  in the denominator of the `k_SV` serum-to-vitreous CFD flux term
  (rather than `(K_ss + C_su)`). This is transcribed as written; because
  `K_ss` (1.17e-5 nmol/mL) is orders of magnitude smaller than either
  `C_vu` or `C_su` at pharmacologic drug levels, the numerical
  consequence is small, but the transcription is faithful to the paper.
* **No IIV.** The paper uses a naive-pool approach so no eta parameters
  are declared. `covariateData` is empty. Users may add IIV externally if
  running the model in a nlmixr2 estimation loop against new data.
* **Vitreous CFD steady state.** The Discussion section (page 293)
  reports serum synthesis as 7.8 mg/day and cites literature systemic
  CFD as ~42 nM for a 3-kg monkey. The model's serum SS is
  `k_syn / k_deg = 27.08 nM`, close to but not identical with the
  literature value. The 42 nM figure is a literature reference (Barnum
  1984, Pascual 1988, Loyet 2012); the 27.08 nM SS is what the fitted
  `k_syn` and `k_deg` predict. No parameter tuning was attempted.
* **Species and dosing setup.** This is a preclinical cynomolgus monkey
  model with per-eye ITV doses reported in mg. Attempts to scale this
  model directly to humans without re-fitting are not supported by the
  paper; the paper's cited human PK/PD analog (Le 2015 CPT PSP) is
  packaged separately as `Le_2015_lampalizumab`.

# Session info

```{r sessioninfo}
sessionInfo()
```
