Skip to contents

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.

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

mod <- readModelDb("Le_2015_lampalizumab_cyno")
mod_typ <- rxode2::zeroRe(mod)  # deterministic (all residual SDs are already fixed to 0)
#> Warning: No omega parameters in the model

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).
# 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.")
CFD steady-state check at t = 30 days (no drug). Simulated and paper-derived analytic baseline agree to four significant figures.
compartment simulated_nM paper_analytic_nM
Serum CFD 27.080 27.08
Vitreous CFD 1.707 1.71
Peripheral CFD 27.080 27.08

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).

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:

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))
}
#> Simulated systemic t1/2 (2-6 h): 2.12 h; paper: ~0.8 h (k = 21.3 /day derived value)

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.
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)

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)

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.

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
))
#> Simulated vitreous t1/2 (3-16 d): 2.89 days (paper: 2.9 days, k_out = 0.24 /day)

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).

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)
  )
#> Warning: multi-subject simulation without without 'omega'

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.

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).")
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).
ITV dose Peak serum TO Days vitreous TO >= 95%
Dose/eye = 1 mg 0.3867021 42.75
Dose/eye = 10 mg 0.8340717 52.25

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.

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

sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.4 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
#>  [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
#>  [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
#> [10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   
#> 
#> time zone: UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] ggplot2_4.0.3         tidyr_1.3.2           dplyr_1.2.1          
#> [4] rxode2_5.1.2          nlmixr2lib_0.3.2.9000
#> 
#> loaded via a namespace (and not attached):
#>  [1] gtable_0.3.6          xfun_0.60             bslib_0.11.0         
#>  [4] lattice_0.22-9        vctrs_0.7.3           tools_4.6.1          
#>  [7] generics_0.1.4        parallel_4.6.1        tibble_3.3.1         
#> [10] symengine_0.2.13      pkgconfig_2.0.3       data.table_1.18.4    
#> [13] checkmate_2.3.4       RColorBrewer_1.1-3    S7_0.2.2             
#> [16] desc_1.4.3            RcppParallel_5.1.11-2 lifecycle_1.0.5      
#> [19] compiler_4.6.1        farver_2.1.2          textshaping_1.0.5    
#> [22] fontawesome_0.5.3     htmltools_0.5.9       sys_3.4.3            
#> [25] sass_0.4.10           yaml_2.3.12           crayon_1.5.3         
#> [28] pillar_1.11.1         pkgdown_2.2.1         jquerylib_0.1.4      
#> [31] whisker_0.4.1         openssl_2.4.2         cachem_1.1.0         
#> [34] nlme_3.1-169          qs2_0.2.2             tidyselect_1.2.1     
#> [37] digest_0.6.39         lotri_1.0.4           purrr_1.2.2          
#> [40] labeling_0.4.3        rxode2ll_2.0.14       fastmap_1.2.0        
#> [43] grid_4.6.1            cli_3.6.6             dparser_1.3.1-13     
#> [46] magrittr_2.0.5        withr_3.0.3           scales_1.4.0         
#> [49] backports_1.5.1       rmarkdown_2.31        otel_0.2.0           
#> [52] askpass_1.2.1         ragg_1.5.2            stringfish_0.19.0    
#> [55] memoise_2.0.1         evaluate_1.0.5        knitr_1.51           
#> [58] rex_1.2.2             PreciseSums_0.7       rlang_1.3.0          
#> [61] downlit_0.4.5         Rcpp_1.1.2            glue_1.8.1           
#> [64] xml2_1.6.0            jsonlite_2.0.0        R6_2.6.1             
#> [67] systemfonts_1.3.2     fs_2.1.0