Nonracemic amisulpride (Hopkins 2024)
Source:vignettes/articles/Hopkins_2024_amisulpride.Rmd
Hopkins_2024_amisulpride.RmdModel and source
ui <- rxode2::rxode(readModelDb("Hopkins_2024_amisulpride"))
#> ℹ parameter labels from comments will be replaced by 'label()'
#> Warning: some etas defaulted to non-mu referenced, possible parsing error: etaltlag3, etaiov_lcl_1, etaiov_lcl_2, etaiov_lcl_3, etaiov_lcl_4, etaiov_lcl_5, etaiov_lcl_6, etaiov_lcl_7, etaiov_lq_1, etaiov_lq_2, etaiov_lq_3, etaiov_lq_4, etaiov_lq_5, etaiov_lq_6, etaiov_lq_7, etaiov_ltlag2_1, etaiov_ltlag2_2, etaiov_ltlag2_3, etaiov_ltlag2_4, etaiov_ltlag2_5, etaiov_ltlag2_6, etaiov_ltlag2_7, etaiov_ltlag3_1, etaiov_ltlag3_2, etaiov_ltlag3_3, etaiov_ltlag3_4, etaiov_ltlag3_5, etaiov_ltlag3_6, etaiov_ltlag3_7, etaiov_lf1_1, etaiov_lf1_2, etaiov_lf1_3, etaiov_lf1_4, etaiov_lf1_5, etaiov_lf1_6, etaiov_lf1_7, etaiov_lf2_1, etaiov_lf2_2, etaiov_lf2_3, etaiov_lf2_4, etaiov_lf2_5, etaiov_lf2_6, etaiov_lf2_7, etaiov_lf3_1, etaiov_lf3_2, etaiov_lf3_3, etaiov_lf3_4, etaiov_lf3_5, etaiov_lf3_6, etaiov_lf3_7
#> as a work-around try putting the mu-referenced expression on a simple line- Citation: Hopkins SC, Toongsuwan S, Corriveau TJ, Watanabe T, Tsushima Y, Asada T, Lew R, Shi L, Zann V, Snowden TJ, van der Graaf PH, Darpo B, Searle GE, Rabiner EA, Wilding I, Szabo ST, Galluppi GR, Koblan KS. Discovery and Model-Informed Drug Development of a Controlled-Release Formulation of Nonracemic Amisulpride that Reduces Plasma Exposure but Achieves Pharmacodynamic Bioequivalence in the Brain. Clin Pharmacol Ther. 2024;116(6):1553-1562. doi:10.1002/cpt.3311.
- Article: https://doi.org/10.1002/cpt.3311
Joint PK/PD ‘Distribution Model’ for nonracemic amisulpride (SEP-4199, a fixed 85:15 ratio of aramisulpride to esamisulpride) and its brain dopamine D2 receptor occupancy in healthy adults (Hopkins 2024). Absorption is a three-parallel-depot construct with staggered lag times, representing regiospecific uptake along the gastrointestinal tract; each depot’s first-order rate constant is scaled by a shared Weibull in vitro dissolution term fitted separately per formulation prototype, so a single model spans oral solution, immediate-release tablet, five controlled-release tablet strengths (10/15/20/25/40 percent rate-controlling polymer, one of them fed) and two multiparticulate (MUPS) capsules. Plasma disposition of total amisulpride enantiomers is two compartmental with linear clearance. The pharmacodynamic layer is the paper’s named contribution: unbound esamisulpride distributes from plasma into a first brain compartment (plausibly the blood-brain / blood-CSF barrier), transits to a second brain compartment (plausibly parenchyma), and D2 receptor occupancy is an Emax-type function of the second compartment with a fixed binding affinity. This two-step distribution is what simultaneously reproduces the three observations that defeated conventional PK/PD forms: plasma-brain hysteresis (24 h plasma washout vs 5 day occupancy washout), a dose-response that does not saturate over the tested range, and an absence of occupancy accumulation over 7 daily doses. The model supported the model-informed decision to carry a controlled-release formulation into Phase III as dose-equivalent to the immediate-release form despite not being bioequivalent in plasma, because the reduced peak plasma concentrations lower QT prolongation while brain occupancy is preserved.
Population
pop <- ui$population
tibble::tibble(
Field = names(pop),
Value = vapply(pop, function(x) paste(
if (is.null(names(x))) as.character(x) else paste0(names(x), ": ", x),
collapse = "; "), character(1))
) |>
knitr::kable(caption = "Population metadata recorded with the model.")| Field | Value |
|---|---|
| species | human |
| n_subjects | 181 |
| n_studies | 6 |
| age_range | 32-39 years (per-study means; Table 1) |
| disease_state | healthy volunteers |
| race_ethnicity | White: 68; Black: 19; Other: 14 |
| dose_range | 25-700 mg single oral doses, plus 200 and 400 mg once daily for 7 days |
| regions | UK (Studies 1, 3, 4, 5 conducted under MHRA Clinical Trial Authorization) |
| notes | Table 1 of the paper gives baseline demographics for the four PET / translational-pharmaceutics studies (Study 1 N = 6, Study 3 N = 11, Study 4 N = 11, Study 5 Part 1 N = 17 and Part 2 N = 18, imaging arm N = 37). Supplement Table 1 lists all six studies pooled for the PK/PD analysis, adding Study 2 (N = 33, aramisulpride polysomnography) and an ascending single oral dose study (N = 48) in Caucasian and Japanese subjects, for the stated total of 181 subjects, 11,626 plasma samples and 157 D2 receptor occupancy measurements. Sex percentages are given per study rather than pooled; Studies 1 and 3 were all male, Study 4 was 64 percent male and Study 5 was 53 and 44 percent male in Parts 1 and 2. The race percentages recorded here are those of the N = 37 imaging arm, the only pooled column in Table 1. sex_female_pct is deliberately absent: Table 1 reports sex per study rather than pooled, the ascending-single-dose study enrolled male subjects only per its title, and no sex breakdown is published for Study 2 or for Study 5 as a whole, so no pooled female percentage across the 181 subjects can be computed. Women were enrolled (Study 4 was 36 percent female and Study 5 Parts 1 and 2 were 47 and 56 percent female), so recording 0 would be wrong. |
The six pooled Phase I studies are listed in Table 1 of the supplement. Table 1 of the paper gives baseline demographics for the four studies with PET imaging. The analysis dataset comprises 181 subjects, 11,626 plasma samples and 157 D2 receptor occupancy measurements.
Model structure
The paper’s named contribution is the Distribution Model: a two-step brain distribution cascade placed downstream of an ordinary plasma PK model. Supplement Table 2 of the paper (reproduced as the “Distribution Model” row of its Table 2) shows that no conventional PD form reproduces all three governing observations simultaneously; the two-step cascade does.
Absorption is three parallel depots with staggered lag times, representing regiospecific uptake along the gastrointestinal tract. Each depot’s first-order rate constant is multiplied by a shared Weibull dissolution term whose two parameters were fitted outside NONMEM to the measured in vitro dissolution curve of each formulation prototype, so one model spans an oral solution, an immediate-release tablet, five controlled-release tablet strengths and two multiparticulate capsules.
cat(paste(src[block_span("^ model\\(\\{$")], collapse = "\n"))
model({
# -------------------------------------------------------------------
# 1. Formulation-derived terms.
#
# The ten regimen indicators are mutually exclusive and exhaustive, so
# each sum below selects exactly one value.
# -------------------------------------------------------------------
is_solution <- FORM_SEP4199_SOLUTION
is_capsule <- FORM_SEP4199_MUPS30 + FORM_SEP4199_MUPS225
is_tablet <- 1 - is_solution - is_capsule
ra <-
exp(lra_cr10) * FORM_SEP4199_CR10 +
exp(lra_cr25) * FORM_SEP4199_CR25 +
exp(lra_ir) * FORM_SEP4199_IR +
exp(lra_solution) * FORM_SEP4199_SOLUTION +
exp(lra_cr15) * FORM_SEP4199_CR15 +
exp(lra_cr25fed) * FORM_SEP4199_CR25_FED +
exp(lra_mups30) * FORM_SEP4199_MUPS30 +
exp(lra_mups225) * FORM_SEP4199_MUPS225 +
exp(lra_cr20) * FORM_SEP4199_CR20 +
exp(lra_cr40) * FORM_SEP4199_CR40
gam1 <-
exp(lgam1_cr10) * FORM_SEP4199_CR10 +
exp(lgam1_cr25) * FORM_SEP4199_CR25 +
exp(lgam1_ir) * FORM_SEP4199_IR +
exp(lgam1_solution) * FORM_SEP4199_SOLUTION +
exp(lgam1_cr15) * FORM_SEP4199_CR15 +
exp(lgam1_cr25fed) * FORM_SEP4199_CR25_FED +
exp(lgam1_mups30) * FORM_SEP4199_MUPS30 +
exp(lgam1_mups225) * FORM_SEP4199_MUPS225 +
exp(lgam1_cr20) * FORM_SEP4199_CR20 +
exp(lgam1_cr40) * FORM_SEP4199_CR40
# -------------------------------------------------------------------
# 2. Occasion indicators and the inter-occasion variability terms.
# OCC = 0 (or any value outside 1..7) zeroes every indicator and
# leaves only the IIV contribution, matching the NONMEM $ABBR
# REPLACE mapping.
# -------------------------------------------------------------------
oc1 <- (OCC == 1)
oc2 <- (OCC == 2)
oc3 <- (OCC == 3)
oc4 <- (OCC == 4)
oc5 <- (OCC == 5)
oc6 <- (OCC == 6)
oc7 <- (OCC == 7)
iov_lcl <- oc1 * etaiov_lcl_1 + oc2 * etaiov_lcl_2 + oc3 * etaiov_lcl_3 +
oc4 * etaiov_lcl_4 + oc5 * etaiov_lcl_5 + oc6 * etaiov_lcl_6 + oc7 * etaiov_lcl_7
iov_lq <- oc1 * etaiov_lq_1 + oc2 * etaiov_lq_2 + oc3 * etaiov_lq_3 +
oc4 * etaiov_lq_4 + oc5 * etaiov_lq_5 + oc6 * etaiov_lq_6 + oc7 * etaiov_lq_7
iov_ltlag2 <- oc1 * etaiov_ltlag2_1 + oc2 * etaiov_ltlag2_2 + oc3 * etaiov_ltlag2_3 +
oc4 * etaiov_ltlag2_4 + oc5 * etaiov_ltlag2_5 + oc6 * etaiov_ltlag2_6 + oc7 * etaiov_ltlag2_7
iov_ltlag3 <- oc1 * etaiov_ltlag3_1 + oc2 * etaiov_ltlag3_2 + oc3 * etaiov_ltlag3_3 +
oc4 * etaiov_ltlag3_4 + oc5 * etaiov_ltlag3_5 + oc6 * etaiov_ltlag3_6 + oc7 * etaiov_ltlag3_7
iov_lf1 <- oc1 * etaiov_lf1_1 + oc2 * etaiov_lf1_2 + oc3 * etaiov_lf1_3 +
oc4 * etaiov_lf1_4 + oc5 * etaiov_lf1_5 + oc6 * etaiov_lf1_6 + oc7 * etaiov_lf1_7
iov_lf2 <- oc1 * etaiov_lf2_1 + oc2 * etaiov_lf2_2 + oc3 * etaiov_lf2_3 +
oc4 * etaiov_lf2_4 + oc5 * etaiov_lf2_5 + oc6 * etaiov_lf2_6 + oc7 * etaiov_lf2_7
iov_lf3 <- oc1 * etaiov_lf3_1 + oc2 * etaiov_lf3_2 + oc3 * etaiov_lf3_3 +
oc4 * etaiov_lf3_4 + oc5 * etaiov_lf3_5 + oc6 * etaiov_lf3_6 + oc7 * etaiov_lf3_7
# -------------------------------------------------------------------
# 3. Individual parameters.
# -------------------------------------------------------------------
cl <- exp(lcl + etalcl + iov_lcl)
q <- exp(lq + etalq + iov_lq)
vc <- exp(lvc + etalvc)
vp <- exp(lvp + etalvp)
# $PK: TAL2 = THETA(5)*EXP(...); TAL3 = TAL2 + THETA(6)*EXP(...)
tlag2 <- exp(ltlag2 + etaltlag2 + iov_ltlag2)
tlag3 <- tlag2 + exp(ltlag3 + etaltlag3 + iov_ltlag3)
kamax1 <- exp(lkamax1_sol) * is_solution + exp(lkamax1_tab) * is_tablet + exp(lkamax1_cap) * is_capsule
kamax2 <- exp(lkamax2_sol) * is_solution + exp(lkamax2_tab) * is_tablet + exp(lkamax2_cap) * is_capsule
kamax3 <- exp(lkamax3_sol) * is_solution + exp(lkamax3_tab) * is_tablet + exp(lkamax3_cap) * is_capsule
f1 <- exp(iov_lf1) * (
exp(lf1_cr10) * FORM_SEP4199_CR10 +
exp(lf1_cr25) * FORM_SEP4199_CR25 +
exp(lf1_ir) * FORM_SEP4199_IR +
exp(lf1_solution) * FORM_SEP4199_SOLUTION +
exp(lf1_cr15) * FORM_SEP4199_CR15 +
exp(lf1_cr25fed) * FORM_SEP4199_CR25_FED +
exp(lf1_mups30) * FORM_SEP4199_MUPS30 +
exp(lf1_mups225) * FORM_SEP4199_MUPS225 +
exp(lf1_cr20) * FORM_SEP4199_CR20 +
exp(lf1_cr40) * FORM_SEP4199_CR40)
f2 <- exp(iov_lf2) * (
exp(lf2_cr10) * FORM_SEP4199_CR10 +
exp(lf2_cr25) * FORM_SEP4199_CR25 +
exp(lf2_ir) * FORM_SEP4199_IR +
exp(lf2_solution) * FORM_SEP4199_SOLUTION +
exp(lf2_cr15) * FORM_SEP4199_CR15 +
exp(lf2_cr25fed) * FORM_SEP4199_CR25_FED +
exp(lf2_mups30) * FORM_SEP4199_MUPS30 +
exp(lf2_mups225) * FORM_SEP4199_MUPS225 +
exp(lf2_cr20) * FORM_SEP4199_CR20 +
exp(lf2_cr40) * FORM_SEP4199_CR40)
f3 <- exp(iov_lf3) * (
exp(lf3_cr10) * FORM_SEP4199_CR10 +
exp(lf3_cr25) * FORM_SEP4199_CR25 +
exp(lf3_ir) * FORM_SEP4199_IR +
exp(lf3_solution) * FORM_SEP4199_SOLUTION +
exp(lf3_cr15) * FORM_SEP4199_CR15 +
exp(lf3_cr25fed) * FORM_SEP4199_CR25_FED +
exp(lf3_mups30) * FORM_SEP4199_MUPS30 +
exp(lf3_mups225) * FORM_SEP4199_MUPS225 +
exp(lf3_cr20) * FORM_SEP4199_CR20 +
exp(lf3_cr40) * FORM_SEP4199_CR40)
kinf_effect1 <- exp(lkinf_effect1 + etalkinf_effect1)
keff_effect1 <- exp(lkeff_effect1 + etalkeff_effect1)
kinf_effect2 <- exp(lkinf_effect2 + etalkinf_effect2)
keff_effect2 <- exp(lkeff_effect2 + etalkeff_effect2)
# -------------------------------------------------------------------
# 4. Micro-constants.
# -------------------------------------------------------------------
kel <- cl / vc
k12 <- q / vc
k21 <- q / vp
# -------------------------------------------------------------------
# 5. Weibull dissolution scaling of the three absorption rate
# constants, WB = 1 - exp(-(KAW * TAD)^GAMAW) in $PK. TAD is time
# after the most recent dose; depot1 takes the dose with no lag, so
# tad(depot1) is the NONMEM TAD column. The occupancy control stream
# sets WB = 1 for the oral solution, which has no dissolution step.
# -------------------------------------------------------------------
wb <- is_solution + (1 - is_solution) * (1 - exp(-(ra * tad(depot1))^gam1))
# $ERROR computes CT = A(4)/V4 directly, which means the NONMEM dataset
# carried dose amounts in ug: the assay curve range and every reported
# concentration are ng/mL (equivalently ug/L), and 289 L is an apparent
# central volume in litres. Doses here are declared in mg to match the
# published dose levels, so the quotient central/vc is in mg/L and needs
# the 1000-fold conversion to ng/mL. The factor is a pure unit
# conversion, not a fitted scaling parameter. It is applied before the
# effect compartments because their influx is driven by the plasma
# concentration on the ng/mL scale, the same scale as kd = 9 ng/mL.
Cc <- 1000 * central / vc
# -------------------------------------------------------------------
# 6. ODE system. Compartments 1-5 are $DES of the PK script; 6 and 7
# are the Distribution Model states added by the occupancy script.
# -------------------------------------------------------------------
d/dt(depot1) <- -kamax1 * wb * depot1
d/dt(depot2) <- -kamax2 * wb * depot2
d/dt(depot3) <- -kamax3 * wb * depot3
d/dt(central) <- kamax1 * wb * depot1 + kamax2 * wb * depot2 + kamax3 * wb * depot3 +
k21 * peripheral1 - (kel + k12) * central
d/dt(peripheral1) <- k12 * central - k21 * peripheral1
# DADT(6) = KE1*FU*SP*(A(4)/V4) - KE2*A(6) - KT*A(6)
d/dt(effect1) <- kinf_effect1 * fu * f_esamisulpride * Cc - keff_effect1 * effect1 - kinf_effect2 * effect1
# DADT(7) = KT*A(6) - KE3*A(7)
d/dt(effect2) <- kinf_effect2 * effect1 - keff_effect2 * effect2
# -------------------------------------------------------------------
# 7. Bioavailability and lag times.
# -------------------------------------------------------------------
f(depot1) <- f1
f(depot2) <- f2
f(depot3) <- f3
alag(depot2) <- tlag2
alag(depot3) <- tlag3
# -------------------------------------------------------------------
# 8. Observations. $ERROR: CT = A(4)/V4 with a combined error model,
# and D2P = (A(7)/(KD + A(7)))*100 with an additive error model.
# -------------------------------------------------------------------
D2RO <- 100 * effect2 / (kd + effect2)
Cc ~ prop(propSd) + add(addSd)
D2RO ~ add(addSd_D2RO)
})Source trace
Every ini() entry in the model file carries an inline
comment naming the table, $THETA line or
$OMEGA line it came from. The table below is parsed
directly out of the installed model file, so it cannot drift from the
code.
ini_span <- block_span("^ ini\\(\\{$")
ini_lines <- src[ini_span[-c(1, length(ini_span))]]
keep <- grepl("(<-|~)", ini_lines) & grepl("#", ini_lines)
trace <- tibble::tibble(raw = trimws(ini_lines[keep])) |>
mutate(
Parameter = trimws(sub("\\s*(<-|~).*$", "", raw)),
Definition = trimws(sub("^.*?(<-|~)\\s*", "", sub("\\s*#.*$", "", raw))),
Source = trimws(sub("^[^#]*#\\s*", "", raw))
) |>
select(Parameter, Definition, Source)
knitr::kable(trace, caption = paste0(
"Source trace for all ", nrow(trace), " ini() entries, parsed from the model file."))| Parameter | Definition | Source |
|---|---|---|
| lcl | fixed(log(106)); label(“Apparent clearance from the central compartment CL/F (L/h)”) | PK $THETA TH1 = 106 FIX |
| lq | fixed(log(62)); label(“Apparent intercompartmental clearance Q/F (L/h)”) | PK $THETA TH2 = 62 FIX |
| lvc | fixed(log(289)); label(“Apparent central volume of distribution Vc/F (L)”) | PK $THETA TH3 = 289 FIX |
| lvp | fixed(log(981)); label(“Apparent peripheral volume of distribution Vp/F (L)”) | PK $THETA TH4 = 981 FIX |
| ltlag2 | fixed(log(0.241)); label(“Lag time before absorption compartment 2 opens (h)”) | PK $THETA TH5 = 0.241 FIX |
| ltlag3 | fixed(log(2.4)); label(“Additional lag of absorption compartment 3 beyond compartment 2 (h)”) | PK $THETA TH6 = 2.4 FIX |
| lkamax1_sol | fixed(log(0.657)); label(“Absorption rate constant, compartment 1, oral solution (1/h)”) | PK $THETA TH7 = 0.657 FIX |
| lkamax1_tab | fixed(log(0.214)); label(“Absorption rate constant, compartment 1, tablet (1/h)”) | PK $THETA TH8 = 0.214 FIX |
| lkamax1_cap | fixed(log(0.197)); label(“Absorption rate constant, compartment 1, MUPS capsule (1/h)”) | PK $THETA TH9 = 0.197 FIX |
| lkamax2_sol | fixed(log(0.283)); label(“Absorption rate constant, compartment 2, oral solution (1/h)”) | PK $THETA TH10 = 0.283 FIX |
| lkamax2_tab | fixed(log(1.33)); label(“Absorption rate constant, compartment 2, tablet (1/h)”) | PK $THETA TH11 = 1.33 FIX |
| lkamax2_cap | fixed(log(0.433)); label(“Absorption rate constant, compartment 2, MUPS capsule (1/h)”) | PK $THETA TH12 = 0.433 FIX |
| lkamax3_sol | fixed(log(8.05)); label(“Absorption rate constant, compartment 3, oral solution (1/h)”) | PK $THETA TH13 = 8.05 FIX |
| lkamax3_tab | fixed(log(1.36)); label(“Absorption rate constant, compartment 3, tablet (1/h)”) | PK $THETA TH14 = 1.36 FIX |
| lkamax3_cap | fixed(log(1.3)); label(“Absorption rate constant, compartment 3, MUPS capsule (1/h)”) | PK $THETA TH15 = 1.3 FIX |
| lf1_cr10 | fixed(log(1.39)); label(“Fraction absorbed via process 1, 10% CR tablet (unitless)”) | PK $THETA TH16 = 1.39 FIX |
| lf1_cr25 | fixed(log(0.759)); label(“Fraction absorbed via process 1, 25% CR tablet (unitless)”) | PK $THETA TH17 = 0.759 FIX |
| lf1_ir | fixed(log(1.39)); label(“Fraction absorbed via process 1, IR tablet (unitless)”) | PK $THETA TH18 = 1.39 FIX |
| lf1_solution | fixed(log(1.99)); label(“Fraction absorbed via process 1, oral solution (unitless)”) | PK $THETA TH19 = 1.99 FIX |
| lf1_cr15 | fixed(log(0.807)); label(“Fraction absorbed via process 1, 15% CR tablet (unitless)”) | PK $THETA TH20 = 0.807 FIX |
| lf1_cr25fed | fixed(log(1.04)); label(“Fraction absorbed via process 1, 25% CR tablet fed (unitless)”) | PK $THETA TH21 = 1.04 FIX |
| lf1_mups30 | fixed(log(0.0092)); label(“Fraction absorbed via process 1, MUPS capsule 30% polymer (unitless)”) | PK $THETA TH22 = 0.0092 FIX |
| lf1_mups225 | fixed(log(1.07)); label(“Fraction absorbed via process 1, MUPS capsule 22.5% polymer (unitless)”) | PK $THETA TH23 = 1.07 FIX |
| lf1_cr20 | fixed(log(0.942)); label(“Fraction absorbed via process 1, 20% CR tablet (unitless)”) | PK $THETA TH24 = 0.942 FIX |
| lf1_cr40 | fixed(log(0.638)); label(“Fraction absorbed via process 1, 40% CR tablet (unitless)”) | PK $THETA TH25 = 0.638 FIX |
| lf2_cr10 | fixed(log(0.0599)); label(“Fraction absorbed via process 2, 10% CR tablet (unitless)”) | PK $THETA TH26 = 0.0599 FIX |
| lf2_cr25 | fixed(log(0.36)); label(“Fraction absorbed via process 2, 25% CR tablet (unitless)”) | PK $THETA TH27 = 0.36 FIX |
| lf2_ir | fixed(log(0.224)); label(“Fraction absorbed via process 2, IR tablet (unitless)”) | PK $THETA TH28 = 0.224 FIX |
| lf2_solution | fixed(log(0.198)); label(“Fraction absorbed via process 2, oral solution (unitless)”) | PK $THETA TH29 = 0.198 FIX |
| lf2_cr15 | fixed(log(0.201)); label(“Fraction absorbed via process 2, 15% CR tablet (unitless)”) | PK $THETA TH30 = 0.201 FIX |
| lf2_cr25fed | fixed(log(2.21e-7)); label(“Fraction absorbed via process 2, 25% CR tablet fed (unitless)”) | PK $THETA TH31 = 0.000000221 FIX |
| lf2_mups30 | fixed(log(0.98)); label(“Fraction absorbed via process 2, MUPS capsule 30% polymer (unitless)”) | PK $THETA TH32 = 0.98 FIX |
| lf2_mups225 | fixed(log(0.299)); label(“Fraction absorbed via process 2, MUPS capsule 22.5% polymer (unitless)”) | PK $THETA TH33 = 0.299 FIX |
| lf2_cr20 | fixed(log(0.251)); label(“Fraction absorbed via process 2, 20% CR tablet (unitless)”) | PK $THETA TH34 = 0.251 FIX |
| lf2_cr40 | fixed(log(0.439)); label(“Fraction absorbed via process 2, 40% CR tablet (unitless)”) | PK $THETA TH35 = 0.439 FIX |
| lf3_cr10 | fixed(log(0.542)); label(“Fraction absorbed via process 3, 10% CR tablet (unitless)”) | PK $THETA TH36 = 0.542 FIX |
| lf3_cr25 | fixed(log(0.287)); label(“Fraction absorbed via process 3, 25% CR tablet (unitless)”) | PK $THETA TH37 = 0.287 FIX |
| lf3_ir | fixed(log(0.392)); label(“Fraction absorbed via process 3, IR tablet (unitless)”) | PK $THETA TH38 = 0.392 FIX |
| lf3_solution | fixed(log(0.00842)); label(“Fraction absorbed via process 3, oral solution (unitless)”) | PK $THETA TH39 = 0.00842 FIX |
| lf3_cr15 | fixed(log(0.521)); label(“Fraction absorbed via process 3, 15% CR tablet (unitless)”) | PK $THETA TH40 = 0.521 FIX |
| lf3_cr25fed | fixed(log(0.234)); label(“Fraction absorbed via process 3, 25% CR tablet fed (unitless)”) | PK $THETA TH41 = 0.234 FIX |
| lf3_mups30 | fixed(log(1.15)); label(“Fraction absorbed via process 3, MUPS capsule 30% polymer (unitless)”) | PK $THETA TH42 = 1.15 FIX |
| lf3_mups225 | fixed(log(0.885)); label(“Fraction absorbed via process 3, MUPS capsule 22.5% polymer (unitless)”) | PK $THETA TH43 = 0.885 FIX |
| lf3_cr20 | fixed(log(0.491)); label(“Fraction absorbed via process 3, 20% CR tablet (unitless)”) | PK $THETA TH44 = 0.491 FIX |
| lf3_cr40 | fixed(log(0.393)); label(“Fraction absorbed via process 3, 40% CR tablet (unitless)”) | PK $THETA TH45 = 0.393 FIX |
| lra_cr10 | fixed(log(1.6536)); label(“Weibull dissolution rate scaling, 10% CR tablet (1/h)”) | PK $PK IF (RGN.EQ.1) TKA1 = 1.6536 |
| lra_cr25 | fixed(log(0.1824)); label(“Weibull dissolution rate scaling, 25% CR tablet (1/h)”) | PK $PK IF (RGN.EQ.2) TKA1 = 0.1824 |
| lra_ir | fixed(log(5.7466)); label(“Weibull dissolution rate scaling, IR tablet (1/h)”) | PK $PK IF (RGN.EQ.3) TKA1 = 5.7466 |
| lra_solution | fixed(log(7.98)); label(“Weibull dissolution rate scaling, oral solution (1/h)”) | PK $PK IF (RGN.EQ.4) TKA1 = 7.98 (unused; solution bypasses the term) |
| lra_cr15 | fixed(log(0.3276)); label(“Weibull dissolution rate scaling, 15% CR tablet (1/h)”) | PK $PK IF (RGN.EQ.5) TKA1 = 0.3276 |
| lra_cr25fed | fixed(log(1.1305)); label(“Weibull dissolution rate scaling, 25% CR tablet fed (1/h)”) | PK $PK IF (RGN.EQ.6) TKA1 = 1.1305 (interpolated) |
| lra_mups30 | fixed(log(0.18)); label(“Weibull dissolution rate scaling, MUPS capsule 30% polymer (1/h)”) | PK $PK IF (RGN.EQ.7) TKA1 = 0.1800 |
| lra_mups225 | fixed(log(0.687)); label(“Weibull dissolution rate scaling, MUPS capsule 22.5% polymer (1/h)”) | PK $PK IF (RGN.EQ.8) TKA1 = 0.6870 |
| lra_cr20 | fixed(log(0.8457)); label(“Weibull dissolution rate scaling, 20% CR tablet (1/h)”) | PK $PK IF (RGN.EQ.9) TKA1 = 0.8457 |
| lra_cr40 | fixed(log(0.1411)); label(“Weibull dissolution rate scaling, 40% CR tablet (1/h)”) | PK $PK IF (RGN.EQ.10) TKA1 = 0.1411 |
| lgam1_cr10 | fixed(log(1.3732)); label(“Weibull dissolution shape, 10% CR tablet (unitless)”) | PK $PK IF (RGN.EQ.1) TGAMA1 = 1.3732 |
| lgam1_cr25 | fixed(log(1.1305)); label(“Weibull dissolution shape, 25% CR tablet (unitless)”) | PK $PK IF (RGN.EQ.2) TGAMA1 = 1.1305 |
| lgam1_ir | fixed(log(1.4364)); label(“Weibull dissolution shape, IR tablet (unitless)”) | PK $PK IF (RGN.EQ.3) TGAMA1 = 1.4364 |
| lgam1_solution | fixed(log(0.905)); label(“Weibull dissolution shape, oral solution (unitless)”) | PK $PK IF (RGN.EQ.4) TGAMA1 = 0.905 (unused; solution bypasses the term) |
| lgam1_cr15 | fixed(log(1.0949)); label(“Weibull dissolution shape, 15% CR tablet (unitless)”) | PK $PK IF (RGN.EQ.5) TGAMA1 = 1.0949 |
| lgam1_cr25fed | fixed(log(5.7466)); label(“Weibull dissolution shape, 25% CR tablet fed (unitless)”) | PK $PK IF (RGN.EQ.6) TGAMA1 = 5.7466 (interpolated) |
| lgam1_mups30 | fixed(log(0.6988)); label(“Weibull dissolution shape, MUPS capsule 30% polymer (unitless)”) | PK $PK IF (RGN.EQ.7) TGAMA1 = 0.6988 |
| lgam1_mups225 | fixed(log(1.0411)); label(“Weibull dissolution shape, MUPS capsule 22.5% polymer (unitless)”) | PK $PK IF (RGN.EQ.8) TGAMA1 = 1.0411 |
| lgam1_cr20 | fixed(log(0.5496)); label(“Weibull dissolution shape, 20% CR tablet (unitless)”) | PK $PK IF (RGN.EQ.9) TGAMA1 = 0.5496 |
| lgam1_cr40 | fixed(log(0.7764)); label(“Weibull dissolution shape, 40% CR tablet (unitless)”) | PK $PK IF (RGN.EQ.10) TGAMA1 = 0.7764 |
| lkinf_effect1 | log(0.0221); label(“Influx rate constant from plasma into brain compartment 1, KE1 (1/h)”) | Supplement Table 3: KE1 = 0.0221 |
| lkeff_effect1 | log(0.0685); label(“Efflux rate constant out of brain compartment 1 back to plasma, KE2 (1/h)”) | Supplement Table 3: KE2 = 0.0685 |
| lkinf_effect2 | log(0.42); label(“Distribution rate constant from brain compartment 1 to compartment 2, KT (1/h)”) | Supplement Table 3: KT = 0.42 |
| lkeff_effect2 | log(0.0602); label(“Efflux rate constant out of brain compartment 2, KE3 (1/h)”) | Supplement Table 3: KE3 = 0.0602 |
| kd | fixed(9); label(“D2 receptor binding affinity in brain compartment 2 units, KD (ng/mL)”) | Supplement Table 3: KD = 9 (FIX); occupancy control stream $THETA TH5 9 FIX |
| fu | fixed(0.83); label(“Fraction of amisulpride unbound in plasma, FU (unitless)”) | Supplement Table 3: FU = 0.83 (FIX); occupancy control stream $THETA TH6 0.83 FIX |
| f_esamisulpride | fixed(0.15); label(“Fraction of total measured amisulpride that is the D2-active enantiomer esamisulpride, SP (unitless)”) | Supplement Table 3: SP = 0.15 (FIX); the fixed 85:15 aramisulpride:esamisulpride ratio of SEP-4199 (Methods, Drugs) |
| etalcl | 0.105 | PK $OMEGA ETA1 - CL IIV = 0.105 FIX |
| etalq | 0.719 | PK $OMEGA ETA2 - Q IIV = 0.719 FIX |
| etalvc | 0.431 | PK $OMEGA ETA3 - Vcen IIV = 0.431 FIX |
| etalvp | 0.389 | PK $OMEGA ETA4 - Vperi IIV = 0.389 FIX |
| etaltlag2 | 0.01 | PK $OMEGA ETA5 - ALAG2 IIV = 0.01 FIX |
| etaltlag3 | 0.01 | PK $OMEGA ETA6 - ALAG3 IIV = 0.01 FIX |
| etalkinf_effect1 | 0.0314 | Supplement Table 3: KE1 - IIV = 0.0314 |
| etalkeff_effect1 | 1e-06 | Supplement Table 3: KE2 - IIV = 1.00E-06 (effectively no IIV, but not fixed to zero) |
| etalkinf_effect2 | 0.706 | Supplement Table 3: KT - IIV = 0.706 |
| etalkeff_effect2 | 0.0341 | Supplement Table 3: KE3 - IIV = 0.0341 |
| etaiov_lcl_1 | 0.048 | PK $OMEGA BLOCK(7) element (1,1) = 0.048, CL IOV |
| etaiov_lcl_2 | fixed(0.048) | BLOCK(7) SAME |
| etaiov_lcl_3 | fixed(0.048) | BLOCK(7) SAME |
| etaiov_lcl_4 | fixed(0.048) | BLOCK(7) SAME |
| etaiov_lcl_5 | fixed(0.048) | BLOCK(7) SAME |
| etaiov_lcl_6 | fixed(0.048) | BLOCK(7) SAME |
| etaiov_lcl_7 | fixed(0.048) | BLOCK(7) SAME |
| etaiov_lq_1 | 0.293 | PK $OMEGA BLOCK(7) element (2,2) = 0.293, Q IOV |
| etaiov_lq_2 | fixed(0.293) | BLOCK(7) SAME |
| etaiov_lq_3 | fixed(0.293) | BLOCK(7) SAME |
| etaiov_lq_4 | fixed(0.293) | BLOCK(7) SAME |
| etaiov_lq_5 | fixed(0.293) | BLOCK(7) SAME |
| etaiov_lq_6 | fixed(0.293) | BLOCK(7) SAME |
| etaiov_lq_7 | fixed(0.293) | BLOCK(7) SAME |
| etaiov_ltlag2_1 | 0.0103 | PK $OMEGA BLOCK(7) element (3,3) = 0.0103, ALAG2 IOV |
| etaiov_ltlag2_2 | fixed(0.0103) | BLOCK(7) SAME |
| etaiov_ltlag2_3 | fixed(0.0103) | BLOCK(7) SAME |
| etaiov_ltlag2_4 | fixed(0.0103) | BLOCK(7) SAME |
| etaiov_ltlag2_5 | fixed(0.0103) | BLOCK(7) SAME |
| etaiov_ltlag2_6 | fixed(0.0103) | BLOCK(7) SAME |
| etaiov_ltlag2_7 | fixed(0.0103) | BLOCK(7) SAME |
| etaiov_ltlag3_1 | 0.00964 | PK $OMEGA BLOCK(7) element (4,4) = 0.00964, ALAG3 IOV |
| etaiov_ltlag3_2 | fixed(0.00964) | BLOCK(7) SAME |
| etaiov_ltlag3_3 | fixed(0.00964) | BLOCK(7) SAME |
| etaiov_ltlag3_4 | fixed(0.00964) | BLOCK(7) SAME |
| etaiov_ltlag3_5 | fixed(0.00964) | BLOCK(7) SAME |
| etaiov_ltlag3_6 | fixed(0.00964) | BLOCK(7) SAME |
| etaiov_ltlag3_7 | fixed(0.00964) | BLOCK(7) SAME |
| etaiov_lf1_1 | 0.165 | PK $OMEGA BLOCK(7) element (5,5) = 0.165, F1 IOV |
| etaiov_lf1_2 | fixed(0.165) | BLOCK(7) SAME |
| etaiov_lf1_3 | fixed(0.165) | BLOCK(7) SAME |
| etaiov_lf1_4 | fixed(0.165) | BLOCK(7) SAME |
| etaiov_lf1_5 | fixed(0.165) | BLOCK(7) SAME |
| etaiov_lf1_6 | fixed(0.165) | BLOCK(7) SAME |
| etaiov_lf1_7 | fixed(0.165) | BLOCK(7) SAME |
| etaiov_lf2_1 | 0.05 | PK $OMEGA BLOCK(7) element (6,6) = 0.05, F2 IOV |
| etaiov_lf2_2 | fixed(0.05) | BLOCK(7) SAME |
| etaiov_lf2_3 | fixed(0.05) | BLOCK(7) SAME |
| etaiov_lf2_4 | fixed(0.05) | BLOCK(7) SAME |
| etaiov_lf2_5 | fixed(0.05) | BLOCK(7) SAME |
| etaiov_lf2_6 | fixed(0.05) | BLOCK(7) SAME |
| etaiov_lf2_7 | fixed(0.05) | BLOCK(7) SAME |
| etaiov_lf3_1 | 0.2 | PK $OMEGA BLOCK(7) element (7,7) = 0.2, F3 IOV |
| etaiov_lf3_2 | fixed(0.2) | BLOCK(7) SAME |
| etaiov_lf3_3 | fixed(0.2) | BLOCK(7) SAME |
| etaiov_lf3_4 | fixed(0.2) | BLOCK(7) SAME |
| etaiov_lf3_5 | fixed(0.2) | BLOCK(7) SAME |
| etaiov_lf3_6 | fixed(0.2) | BLOCK(7) SAME |
| etaiov_lf3_7 | fixed(0.2) | BLOCK(7) SAME |
| propSd | sqrt(0.15); label(“Proportional residual error on plasma concentration (fraction)”) | PK / occupancy $SIGMA EPS(1) variance 0.15 -> SD 0.3873 |
| addSd | sqrt(10); label(“Additive residual error on plasma concentration (ng/mL)”) | PK / occupancy $SIGMA EPS(2) variance 10 -> SD 3.1623 |
| addSd_D2RO | sqrt(52.2); label(“Additive residual error on D2 receptor occupancy (percent)”) | Occupancy $SIGMA EPS(3) variance 52.2 -> SD 7.2250 |
Structural equations trace as follows.
| Model equation | Source |
|---|---|
d/dt(depot1..3) <- -kamax_i * wb * depot_i |
Supplement, PK Fitting NONMEM Script, $DES
DADT(1)-(3) |
d/dt(central) (2-compartment with three absorption
inputs) |
Supplement, PK script $DES DADT(4) |
d/dt(peripheral1) <- k12 * central - k21 * peripheral1 |
Supplement, PK script $DES DADT(5) |
wb <- 1 - exp(-(ra * tad(depot1))^gam1) |
Supplement, PK script $PK:
WB = 1-EXP((-(KAW*TAD)**GAMAW))
|
solution bypasses the Weibull term (wb = 1) |
Supplement, D2 occupancy script $PK:
IF (RGN.EQ.4) WB = 1
|
tlag3 <- tlag2 + exp(ltlag3 + ...) |
Supplement, PK script $PK:
TAL3 = TAL2 + TAL3T
|
d/dt(effect1) <- kinf_effect1 * fu * f_esamisulpride * Cc - keff_effect1 * effect1 - kinf_effect2 * effect1 |
Supplement, D2 occupancy script $DES DADT(6) |
d/dt(effect2) <- kinf_effect2 * effect1 - keff_effect2 * effect2 |
Supplement, D2 occupancy script $DES DADT(7) |
D2RO <- 100 * effect2 / (kd + effect2) |
Supplement, D2 occupancy script $ERROR:
D2P = (A(7)/(KD + A(7)))*100; main text Table 2,
Distribution Model row |
Cc ~ prop(propSd) + add(addSd) |
Supplement, $ERROR:
Y = IPRE + IPRE*EPS(1) + EPS(2)
|
D2RO ~ add(addSd_D2RO) |
Supplement, D2 occupancy script $ERROR:
IF (CMT.EQ.8) Y = IPRE + EPS(3)
|
Virtual cohort
Three single-dose 200 mg arms are simulated: the oral solution used by the PET studies, the immediate-release tablet that was carried into Phase II, and the 20% controlled-release tablet, which of the five CR prototypes lies closest to the peak concentration reported for the CR tablet selected in Study 5.
Each subject receives one dose, so a single occasion is modelled and
the inter-occasion variability terms all draw on
OCC = 1.
form_cols <- c(
"FORM_SEP4199_CR10", "FORM_SEP4199_CR25", "FORM_SEP4199_IR",
"FORM_SEP4199_SOLUTION", "FORM_SEP4199_CR15", "FORM_SEP4199_CR25_FED",
"FORM_SEP4199_MUPS30", "FORM_SEP4199_MUPS225", "FORM_SEP4199_CR20",
"FORM_SEP4199_CR40"
)
#' Build an event data frame for one formulation arm.
#'
#' Doses go simultaneously into all three parallel absorption compartments, as
#' in the NONMEM dataset; f() and alag() inside the model apply the
#' per-formulation bioavailability fractions and lag times.
make_events <- function(form, dose = 200, n_sub = 1L, times = seq(0, 48, by = 0.1),
addl = 0L, ii = 24) {
stopifnot(form %in% form_cols)
ev <- rxode2::et(amt = dose, cmt = "depot1", time = 0, addl = addl, ii = ii) |>
rxode2::et(amt = dose, cmt = "depot2", time = 0, addl = addl, ii = ii) |>
rxode2::et(amt = dose, cmt = "depot3", time = 0, addl = addl, ii = ii) |>
rxode2::et(times)
if (n_sub > 1L) ev <- rxode2::et(ev, id = seq_len(n_sub))
dat <- as.data.frame(ev)
# Two endpoints are declared, so observation rows need a dvid; dvid = 1 on
# every observation returns both Cc and D2RO as columns.
dat$dvid <- ifelse(dat$evid == 0, 1L, NA_integer_)
for (nm in form_cols) dat[[nm]] <- 0
dat[[form]] <- 1
dat$OCC <- 1
dat
}
#' Solve, keeping the arm label. useLinCmt = FALSE is required for multi-output
#' models (rxode2's automatic ODE-to-linCmt conversion corrupts the dvid map).
solve_arm <- function(mod, form, label, ...) {
out <- rxode2::rxSolve(mod, make_events(form, ...), returnType = "data.frame",
useLinCmt = FALSE)
if (is.null(out$id)) out$id <- 1L
out$treatment <- label
out
}
arms <- c(Solution = "FORM_SEP4199_SOLUTION",
`IR tablet` = "FORM_SEP4199_IR",
`CR tablet (20%)` = "FORM_SEP4199_CR20")
n_per_arm <- 60L
# Re-seed before EACH arm so all three draw the identical sequence of etas.
# That makes subject i the same virtual person in every arm, which turns the
# across-arm contrasts below into paired comparisons. Seeding once outside the
# loop would give each arm a different cohort and silently unpair them.
cohort <- lapply(names(arms), function(lbl) {
rxode2::rxSetSeed(20240501)
solve_arm(ui, arms[[lbl]], lbl, n_sub = n_per_arm)
}) |>
bind_rows() |>
mutate(treatment = factor(treatment, levels = names(arms)))
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
# Confirm the arms really are paired: with common random numbers the individual
# clearance draws must be identical subject-by-subject across arms.
cl_by_arm <- cohort |>
distinct(treatment, id, cl) |>
tidyr::pivot_wider(names_from = treatment, values_from = cl)
stopifnot(
nrow(cl_by_arm) == n_per_arm,
isTRUE(all.equal(cl_by_arm[[2]], cl_by_arm[[3]])),
isTRUE(all.equal(cl_by_arm[[2]], cl_by_arm[[4]]))
)
obs <- cohort |> filter(time > 0)
stopifnot(nrow(obs) > 0, !anyNA(obs$Cc), !anyNA(obs$D2RO), all(obs$Cc >= 0))Typical-value profiles use rxode2::zeroRe() so the
curves are the population prediction rather than one realisation.
tv <- rxode2::zeroRe(ui)
#> Warning: some etas defaulted to non-mu referenced, possible parsing error: etaltlag3, etaiov_lcl_1, etaiov_lcl_2, etaiov_lcl_3, etaiov_lcl_4, etaiov_lcl_5, etaiov_lcl_6, etaiov_lcl_7, etaiov_lq_1, etaiov_lq_2, etaiov_lq_3, etaiov_lq_4, etaiov_lq_5, etaiov_lq_6, etaiov_lq_7, etaiov_ltlag2_1, etaiov_ltlag2_2, etaiov_ltlag2_3, etaiov_ltlag2_4, etaiov_ltlag2_5, etaiov_ltlag2_6, etaiov_ltlag2_7, etaiov_ltlag3_1, etaiov_ltlag3_2, etaiov_ltlag3_3, etaiov_ltlag3_4, etaiov_ltlag3_5, etaiov_ltlag3_6, etaiov_ltlag3_7, etaiov_lf1_1, etaiov_lf1_2, etaiov_lf1_3, etaiov_lf1_4, etaiov_lf1_5, etaiov_lf1_6, etaiov_lf1_7, etaiov_lf2_1, etaiov_lf2_2, etaiov_lf2_3, etaiov_lf2_4, etaiov_lf2_5, etaiov_lf2_6, etaiov_lf2_7, etaiov_lf3_1, etaiov_lf3_2, etaiov_lf3_3, etaiov_lf3_4, etaiov_lf3_5, etaiov_lf3_6, etaiov_lf3_7
#> as a work-around try putting the mu-referenced expression on a simple lineReplicating the published observations
Hysteresis (Figure 1a)
Plasma concentrations wash out within about 24 h while D2 receptor occupancy needs about 5 days. Both series are scaled to their own peak, as in the source figure.
hyst <- solve_arm(tv, "FORM_SEP4199_IR", "IR tablet", times = seq(0, 144, by = 0.25)) |>
filter(time > 0) |>
transmute(time,
`Plasma concentration` = Cc / max(Cc),
`D2 receptor occupancy` = D2RO / max(D2RO)) |>
pivot_longer(-time, names_to = "series", values_to = "fraction")
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalq', 'etalvc', 'etalvp', 'etaltlag2', 'etaltlag3', 'etalkinf_effect1', 'etalkeff_effect1', 'etalkinf_effect2', 'etalkeff_effect2', 'etaiov_lcl_1', 'etaiov_lcl_2', 'etaiov_lcl_3', 'etaiov_lcl_4', 'etaiov_lcl_5', 'etaiov_lcl_6', 'etaiov_lcl_7', 'etaiov_lq_1', 'etaiov_lq_2', 'etaiov_lq_3', 'etaiov_lq_4', 'etaiov_lq_5', 'etaiov_lq_6', 'etaiov_lq_7', 'etaiov_ltlag2_1', 'etaiov_ltlag2_2', 'etaiov_ltlag2_3', 'etaiov_ltlag2_4', 'etaiov_ltlag2_5', 'etaiov_ltlag2_6', 'etaiov_ltlag2_7', 'etaiov_ltlag3_1', 'etaiov_ltlag3_2', 'etaiov_ltlag3_3', 'etaiov_ltlag3_4', 'etaiov_ltlag3_5', 'etaiov_ltlag3_6', 'etaiov_ltlag3_7', 'etaiov_lf1_1', 'etaiov_lf1_2', 'etaiov_lf1_3', 'etaiov_lf1_4', 'etaiov_lf1_5', 'etaiov_lf1_6', 'etaiov_lf1_7', 'etaiov_lf2_1', 'etaiov_lf2_2', 'etaiov_lf2_3', 'etaiov_lf2_4', 'etaiov_lf2_5', 'etaiov_lf2_6', 'etaiov_lf2_7', 'etaiov_lf3_1', 'etaiov_lf3_2', 'etaiov_lf3_3', 'etaiov_lf3_4', 'etaiov_lf3_5', 'etaiov_lf3_6', 'etaiov_lf3_7'
ggplot(hyst, aes(time, fraction, colour = series)) +
geom_line(linewidth = 0.9) +
scale_x_continuous(breaks = seq(0, 144, by = 24)) +
labs(x = "Time (h)", y = "Fraction of own peak", colour = NULL,
title = "Replicates Figure 1a of Hopkins 2024",
subtitle = "200 mg IR tablet, typical-value prediction") +
theme_bw() + theme(legend.position = "bottom")
h <- solve_arm(tv, "FORM_SEP4199_IR", "IR tablet", times = seq(0, 144, by = 0.25)) |>
filter(time > 0)
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalq', 'etalvc', 'etalvp', 'etaltlag2', 'etaltlag3', 'etalkinf_effect1', 'etalkeff_effect1', 'etalkinf_effect2', 'etalkeff_effect2', 'etaiov_lcl_1', 'etaiov_lcl_2', 'etaiov_lcl_3', 'etaiov_lcl_4', 'etaiov_lcl_5', 'etaiov_lcl_6', 'etaiov_lcl_7', 'etaiov_lq_1', 'etaiov_lq_2', 'etaiov_lq_3', 'etaiov_lq_4', 'etaiov_lq_5', 'etaiov_lq_6', 'etaiov_lq_7', 'etaiov_ltlag2_1', 'etaiov_ltlag2_2', 'etaiov_ltlag2_3', 'etaiov_ltlag2_4', 'etaiov_ltlag2_5', 'etaiov_ltlag2_6', 'etaiov_ltlag2_7', 'etaiov_ltlag3_1', 'etaiov_ltlag3_2', 'etaiov_ltlag3_3', 'etaiov_ltlag3_4', 'etaiov_ltlag3_5', 'etaiov_ltlag3_6', 'etaiov_ltlag3_7', 'etaiov_lf1_1', 'etaiov_lf1_2', 'etaiov_lf1_3', 'etaiov_lf1_4', 'etaiov_lf1_5', 'etaiov_lf1_6', 'etaiov_lf1_7', 'etaiov_lf2_1', 'etaiov_lf2_2', 'etaiov_lf2_3', 'etaiov_lf2_4', 'etaiov_lf2_5', 'etaiov_lf2_6', 'etaiov_lf2_7', 'etaiov_lf3_1', 'etaiov_lf3_2', 'etaiov_lf3_3', 'etaiov_lf3_4', 'etaiov_lf3_5', 'etaiov_lf3_6', 'etaiov_lf3_7'
at <- function(v, tt) v[h$time == tt]
tmax_pk <- h$time[which.max(h$Cc)]
tmax_ro <- h$time[which.max(h$D2RO)]
pk_frac <- h$Cc / max(h$Cc)
ro_frac <- h$D2RO / max(h$D2RO)
c(tmax_pk = tmax_pk, tmax_ro = tmax_ro,
pk_frac_at_24h = at(pk_frac, 24),
ro_frac_at_8h = at(ro_frac, 8), ro_frac_at_24h = at(ro_frac, 24),
ro_frac_at_27h = at(ro_frac, 27), ro_frac_at_120h = at(ro_frac, 120))
#> tmax_pk tmax_ro pk_frac_at_24h ro_frac_at_8h ro_frac_at_24h
#> 3.50000000 13.50000000 0.08326670 0.85071460 0.84940965
#> ro_frac_at_27h ro_frac_at_120h
#> 0.78835145 0.03176387
stopifnot(
# Occupancy peaks well after the plasma peak -- the definition of hysteresis.
tmax_ro > 3 * tmax_pk,
# "the plasma PK (24-hour washout)": plasma is essentially gone at 24 h.
at(pk_frac, 24) < 0.10,
# "maximal D2R receptor occupancies were achieved by 8 hours and maintained
# out to 27 hours postdose" -- occupancy is within 20% of its own peak across
# that whole window while plasma falls by more than an order of magnitude.
at(ro_frac, 8) > 0.80, at(ro_frac, 24) > 0.80, at(ro_frac, 27) > 0.75,
# "up to 5 days were required for complete washout of D2R occupancy levels."
at(ro_frac, 120) < 0.05
)Lack of accumulation over 7 daily doses (Figure 1c)
md <- lapply(c("IR tablet", "CR tablet (20%)"), function(lbl) {
solve_arm(tv, arms[[lbl]], lbl, times = seq(0, 192, by = 0.25), addl = 6L, ii = 24)
}) |>
bind_rows() |>
filter(time > 0)
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalq', 'etalvc', 'etalvp', 'etaltlag2', 'etaltlag3', 'etalkinf_effect1', 'etalkeff_effect1', 'etalkinf_effect2', 'etalkeff_effect2', 'etaiov_lcl_1', 'etaiov_lcl_2', 'etaiov_lcl_3', 'etaiov_lcl_4', 'etaiov_lcl_5', 'etaiov_lcl_6', 'etaiov_lcl_7', 'etaiov_lq_1', 'etaiov_lq_2', 'etaiov_lq_3', 'etaiov_lq_4', 'etaiov_lq_5', 'etaiov_lq_6', 'etaiov_lq_7', 'etaiov_ltlag2_1', 'etaiov_ltlag2_2', 'etaiov_ltlag2_3', 'etaiov_ltlag2_4', 'etaiov_ltlag2_5', 'etaiov_ltlag2_6', 'etaiov_ltlag2_7', 'etaiov_ltlag3_1', 'etaiov_ltlag3_2', 'etaiov_ltlag3_3', 'etaiov_ltlag3_4', 'etaiov_ltlag3_5', 'etaiov_ltlag3_6', 'etaiov_ltlag3_7', 'etaiov_lf1_1', 'etaiov_lf1_2', 'etaiov_lf1_3', 'etaiov_lf1_4', 'etaiov_lf1_5', 'etaiov_lf1_6', 'etaiov_lf1_7', 'etaiov_lf2_1', 'etaiov_lf2_2', 'etaiov_lf2_3', 'etaiov_lf2_4', 'etaiov_lf2_5', 'etaiov_lf2_6', 'etaiov_lf2_7', 'etaiov_lf3_1', 'etaiov_lf3_2', 'etaiov_lf3_3', 'etaiov_lf3_4', 'etaiov_lf3_5', 'etaiov_lf3_6', 'etaiov_lf3_7'
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalq', 'etalvc', 'etalvp', 'etaltlag2', 'etaltlag3', 'etalkinf_effect1', 'etalkeff_effect1', 'etalkinf_effect2', 'etalkeff_effect2', 'etaiov_lcl_1', 'etaiov_lcl_2', 'etaiov_lcl_3', 'etaiov_lcl_4', 'etaiov_lcl_5', 'etaiov_lcl_6', 'etaiov_lcl_7', 'etaiov_lq_1', 'etaiov_lq_2', 'etaiov_lq_3', 'etaiov_lq_4', 'etaiov_lq_5', 'etaiov_lq_6', 'etaiov_lq_7', 'etaiov_ltlag2_1', 'etaiov_ltlag2_2', 'etaiov_ltlag2_3', 'etaiov_ltlag2_4', 'etaiov_ltlag2_5', 'etaiov_ltlag2_6', 'etaiov_ltlag2_7', 'etaiov_ltlag3_1', 'etaiov_ltlag3_2', 'etaiov_ltlag3_3', 'etaiov_ltlag3_4', 'etaiov_ltlag3_5', 'etaiov_ltlag3_6', 'etaiov_ltlag3_7', 'etaiov_lf1_1', 'etaiov_lf1_2', 'etaiov_lf1_3', 'etaiov_lf1_4', 'etaiov_lf1_5', 'etaiov_lf1_6', 'etaiov_lf1_7', 'etaiov_lf2_1', 'etaiov_lf2_2', 'etaiov_lf2_3', 'etaiov_lf2_4', 'etaiov_lf2_5', 'etaiov_lf2_6', 'etaiov_lf2_7', 'etaiov_lf3_1', 'etaiov_lf3_2', 'etaiov_lf3_3', 'etaiov_lf3_4', 'etaiov_lf3_5', 'etaiov_lf3_6', 'etaiov_lf3_7'
pet <- md |> filter(time %in% c(27, 27 + 144))
ggplot(md, aes(time, D2RO, colour = treatment)) +
geom_line(linewidth = 0.8) +
geom_point(data = pet, size = 2.6) +
scale_x_continuous(breaks = seq(0, 192, by = 24)) +
labs(x = "Time (h)", y = "D2 receptor occupancy (%)", colour = NULL,
title = "Replicates Figure 1c of Hopkins 2024",
subtitle = paste("200 mg once daily for 7 days; points are the PET scan times",
"(27 h after dose 1 and after dose 7)")) +
theme_bw() + theme(legend.position = "bottom")
acc <- pet |>
mutate(scan = ifelse(time == 27, "day1", "day7")) |>
select(treatment, scan, D2RO) |>
pivot_wider(names_from = scan, values_from = D2RO) |>
mutate(ratio = day7 / day1)
knitr::kable(acc, digits = 2,
caption = "Occupancy 27 h after the first and the seventh daily dose.")| treatment | day1 | day7 | ratio |
|---|---|---|---|
| IR tablet | 26.68 | 34.07 | 1.28 |
| CR tablet (20%) | 22.62 | 30.60 | 1.35 |
stopifnot(
# The paper's claim is that occupancy does not accumulate SUBSTANTIALLY. The
# initial indirect-response model it replaced predicted the day-7 scan roughly
# double the day-1 scan (gray curves of Figure 1c); the Distribution Model
# keeps the rise modest.
all(acc$ratio > 1), all(acc$ratio < 1.5),
# Both formulations reach a similar steady-state occupancy: the paper's
# "pharmacodynamic bioequivalence in the brain" claim.
abs(diff(acc$day7)) / mean(acc$day7) < 0.25
)Dose-response without receptor saturation (Figure 1b)
dr <- lapply(c(200, 300, 400, 600, 700), function(d) {
s <- solve_arm(tv, "FORM_SEP4199_IR", "IR tablet", dose = d, times = seq(0, 48, by = 0.25))
tibble::tibble(dose = d, rod2_27h = s$D2RO[s$time == 27])
}) |>
bind_rows()
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalq', 'etalvc', 'etalvp', 'etaltlag2', 'etaltlag3', 'etalkinf_effect1', 'etalkeff_effect1', 'etalkinf_effect2', 'etalkeff_effect2', 'etaiov_lcl_1', 'etaiov_lcl_2', 'etaiov_lcl_3', 'etaiov_lcl_4', 'etaiov_lcl_5', 'etaiov_lcl_6', 'etaiov_lcl_7', 'etaiov_lq_1', 'etaiov_lq_2', 'etaiov_lq_3', 'etaiov_lq_4', 'etaiov_lq_5', 'etaiov_lq_6', 'etaiov_lq_7', 'etaiov_ltlag2_1', 'etaiov_ltlag2_2', 'etaiov_ltlag2_3', 'etaiov_ltlag2_4', 'etaiov_ltlag2_5', 'etaiov_ltlag2_6', 'etaiov_ltlag2_7', 'etaiov_ltlag3_1', 'etaiov_ltlag3_2', 'etaiov_ltlag3_3', 'etaiov_ltlag3_4', 'etaiov_ltlag3_5', 'etaiov_ltlag3_6', 'etaiov_ltlag3_7', 'etaiov_lf1_1', 'etaiov_lf1_2', 'etaiov_lf1_3', 'etaiov_lf1_4', 'etaiov_lf1_5', 'etaiov_lf1_6', 'etaiov_lf1_7', 'etaiov_lf2_1', 'etaiov_lf2_2', 'etaiov_lf2_3', 'etaiov_lf2_4', 'etaiov_lf2_5', 'etaiov_lf2_6', 'etaiov_lf2_7', 'etaiov_lf3_1', 'etaiov_lf3_2', 'etaiov_lf3_3', 'etaiov_lf3_4', 'etaiov_lf3_5', 'etaiov_lf3_6', 'etaiov_lf3_7'
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalq', 'etalvc', 'etalvp', 'etaltlag2', 'etaltlag3', 'etalkinf_effect1', 'etalkeff_effect1', 'etalkinf_effect2', 'etalkeff_effect2', 'etaiov_lcl_1', 'etaiov_lcl_2', 'etaiov_lcl_3', 'etaiov_lcl_4', 'etaiov_lcl_5', 'etaiov_lcl_6', 'etaiov_lcl_7', 'etaiov_lq_1', 'etaiov_lq_2', 'etaiov_lq_3', 'etaiov_lq_4', 'etaiov_lq_5', 'etaiov_lq_6', 'etaiov_lq_7', 'etaiov_ltlag2_1', 'etaiov_ltlag2_2', 'etaiov_ltlag2_3', 'etaiov_ltlag2_4', 'etaiov_ltlag2_5', 'etaiov_ltlag2_6', 'etaiov_ltlag2_7', 'etaiov_ltlag3_1', 'etaiov_ltlag3_2', 'etaiov_ltlag3_3', 'etaiov_ltlag3_4', 'etaiov_ltlag3_5', 'etaiov_ltlag3_6', 'etaiov_ltlag3_7', 'etaiov_lf1_1', 'etaiov_lf1_2', 'etaiov_lf1_3', 'etaiov_lf1_4', 'etaiov_lf1_5', 'etaiov_lf1_6', 'etaiov_lf1_7', 'etaiov_lf2_1', 'etaiov_lf2_2', 'etaiov_lf2_3', 'etaiov_lf2_4', 'etaiov_lf2_5', 'etaiov_lf2_6', 'etaiov_lf2_7', 'etaiov_lf3_1', 'etaiov_lf3_2', 'etaiov_lf3_3', 'etaiov_lf3_4', 'etaiov_lf3_5', 'etaiov_lf3_6', 'etaiov_lf3_7'
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalq', 'etalvc', 'etalvp', 'etaltlag2', 'etaltlag3', 'etalkinf_effect1', 'etalkeff_effect1', 'etalkinf_effect2', 'etalkeff_effect2', 'etaiov_lcl_1', 'etaiov_lcl_2', 'etaiov_lcl_3', 'etaiov_lcl_4', 'etaiov_lcl_5', 'etaiov_lcl_6', 'etaiov_lcl_7', 'etaiov_lq_1', 'etaiov_lq_2', 'etaiov_lq_3', 'etaiov_lq_4', 'etaiov_lq_5', 'etaiov_lq_6', 'etaiov_lq_7', 'etaiov_ltlag2_1', 'etaiov_ltlag2_2', 'etaiov_ltlag2_3', 'etaiov_ltlag2_4', 'etaiov_ltlag2_5', 'etaiov_ltlag2_6', 'etaiov_ltlag2_7', 'etaiov_ltlag3_1', 'etaiov_ltlag3_2', 'etaiov_ltlag3_3', 'etaiov_ltlag3_4', 'etaiov_ltlag3_5', 'etaiov_ltlag3_6', 'etaiov_ltlag3_7', 'etaiov_lf1_1', 'etaiov_lf1_2', 'etaiov_lf1_3', 'etaiov_lf1_4', 'etaiov_lf1_5', 'etaiov_lf1_6', 'etaiov_lf1_7', 'etaiov_lf2_1', 'etaiov_lf2_2', 'etaiov_lf2_3', 'etaiov_lf2_4', 'etaiov_lf2_5', 'etaiov_lf2_6', 'etaiov_lf2_7', 'etaiov_lf3_1', 'etaiov_lf3_2', 'etaiov_lf3_3', 'etaiov_lf3_4', 'etaiov_lf3_5', 'etaiov_lf3_6', 'etaiov_lf3_7'
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalq', 'etalvc', 'etalvp', 'etaltlag2', 'etaltlag3', 'etalkinf_effect1', 'etalkeff_effect1', 'etalkinf_effect2', 'etalkeff_effect2', 'etaiov_lcl_1', 'etaiov_lcl_2', 'etaiov_lcl_3', 'etaiov_lcl_4', 'etaiov_lcl_5', 'etaiov_lcl_6', 'etaiov_lcl_7', 'etaiov_lq_1', 'etaiov_lq_2', 'etaiov_lq_3', 'etaiov_lq_4', 'etaiov_lq_5', 'etaiov_lq_6', 'etaiov_lq_7', 'etaiov_ltlag2_1', 'etaiov_ltlag2_2', 'etaiov_ltlag2_3', 'etaiov_ltlag2_4', 'etaiov_ltlag2_5', 'etaiov_ltlag2_6', 'etaiov_ltlag2_7', 'etaiov_ltlag3_1', 'etaiov_ltlag3_2', 'etaiov_ltlag3_3', 'etaiov_ltlag3_4', 'etaiov_ltlag3_5', 'etaiov_ltlag3_6', 'etaiov_ltlag3_7', 'etaiov_lf1_1', 'etaiov_lf1_2', 'etaiov_lf1_3', 'etaiov_lf1_4', 'etaiov_lf1_5', 'etaiov_lf1_6', 'etaiov_lf1_7', 'etaiov_lf2_1', 'etaiov_lf2_2', 'etaiov_lf2_3', 'etaiov_lf2_4', 'etaiov_lf2_5', 'etaiov_lf2_6', 'etaiov_lf2_7', 'etaiov_lf3_1', 'etaiov_lf3_2', 'etaiov_lf3_3', 'etaiov_lf3_4', 'etaiov_lf3_5', 'etaiov_lf3_6', 'etaiov_lf3_7'
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> Warning: 'ii' requires non zero additional doses ('addl') or steady state
#> dosing ('ii': 24.000000, 'ss': 0; 'addl': 0), reset 'ii' to zero
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalq', 'etalvc', 'etalvp', 'etaltlag2', 'etaltlag3', 'etalkinf_effect1', 'etalkeff_effect1', 'etalkinf_effect2', 'etalkeff_effect2', 'etaiov_lcl_1', 'etaiov_lcl_2', 'etaiov_lcl_3', 'etaiov_lcl_4', 'etaiov_lcl_5', 'etaiov_lcl_6', 'etaiov_lcl_7', 'etaiov_lq_1', 'etaiov_lq_2', 'etaiov_lq_3', 'etaiov_lq_4', 'etaiov_lq_5', 'etaiov_lq_6', 'etaiov_lq_7', 'etaiov_ltlag2_1', 'etaiov_ltlag2_2', 'etaiov_ltlag2_3', 'etaiov_ltlag2_4', 'etaiov_ltlag2_5', 'etaiov_ltlag2_6', 'etaiov_ltlag2_7', 'etaiov_ltlag3_1', 'etaiov_ltlag3_2', 'etaiov_ltlag3_3', 'etaiov_ltlag3_4', 'etaiov_ltlag3_5', 'etaiov_ltlag3_6', 'etaiov_ltlag3_7', 'etaiov_lf1_1', 'etaiov_lf1_2', 'etaiov_lf1_3', 'etaiov_lf1_4', 'etaiov_lf1_5', 'etaiov_lf1_6', 'etaiov_lf1_7', 'etaiov_lf2_1', 'etaiov_lf2_2', 'etaiov_lf2_3', 'etaiov_lf2_4', 'etaiov_lf2_5', 'etaiov_lf2_6', 'etaiov_lf2_7', 'etaiov_lf3_1', 'etaiov_lf3_2', 'etaiov_lf3_3', 'etaiov_lf3_4', 'etaiov_lf3_5', 'etaiov_lf3_6', 'etaiov_lf3_7'
ggplot(dr, aes(dose, rod2_27h)) +
geom_line(linewidth = 0.8) + geom_point(size = 2.5) +
ylim(0, 100) +
labs(x = "SEP-4199 dose (mg)", y = "D2 receptor occupancy at 27 h (%)",
title = "Replicates the dose-response of Figure 1b of Hopkins 2024",
subtitle = "IR tablet, typical-value prediction; Study 3 spanned 200-700 mg") +
theme_bw()
| Dose (mg) | Occupancy at 27 h (%) |
|---|---|
| 200 | 23.3 |
| 300 | 31.3 |
| 400 | 37.8 |
| 600 | 47.6 |
| 700 | 51.5 |
stopifnot(
# Monotone in dose.
all(diff(dr$rod2_27h) > 0),
# Not saturated: the paper states receptor is not saturated at the tested dose
# levels, and targets occupancy below 50% at the 200/400 mg therapeutic doses.
max(dr$rod2_27h) < 70,
dr$rod2_27h[dr$dose == 400] < 50,
# A 3.5-fold dose increase still produces a substantial occupancy increase,
# i.e. the curve has not flattened.
dr$rod2_27h[dr$dose == 700] / dr$rod2_27h[dr$dose == 200] > 1.8
)Controlled release lowers peak plasma concentration (Figures 4b and 5)
med <- obs |>
group_by(treatment, time) |>
summarise(median = median(Cc),
lo = quantile(Cc, 0.1), hi = quantile(Cc, 0.9), .groups = "drop")
ggplot(med, aes(time, median, colour = treatment, fill = treatment)) +
geom_ribbon(aes(ymin = lo, ymax = hi), alpha = 0.15, colour = NA) +
geom_line(linewidth = 0.9) +
coord_cartesian(xlim = c(0, 24)) +
labs(x = "Time (h)", y = "Total amisulpride (ng/mL)", colour = NULL, fill = NULL,
title = "Replicates Figure 4b / Figure 5 (PK panel) of Hopkins 2024",
subtitle = paste0("200 mg single dose, ", n_per_arm,
" subjects per arm; median with 10th-90th percentile band")) +
theme_bw() + theme(legend.position = "bottom")
Noncompartmental analysis
# Built from `cohort`, NOT from `obs`: `obs` drops time == 0, and a PKNCA
# concentration frame with no time-zero record triggers "Requesting an AUC range
# starting (0) before the first measurement" once per subject.
conc_data <- cohort |>
filter(!is.na(Cc)) |>
select(id, time, Cc, treatment)
stopifnot(all(tapply(conc_data$time, conc_data$treatment, min) == 0))
dose_data <- conc_data |>
distinct(id, treatment) |>
mutate(time = 0, dose = 200)
o_conc <- PKNCA::PKNCAconc(conc_data, Cc ~ time | treatment + id,
concu = "ng/mL", timeu = "h")
o_dose <- PKNCA::PKNCAdose(dose_data, dose ~ time | treatment + id, doseu = "mg")
o_data <- PKNCA::PKNCAdata(
o_conc, o_dose,
intervals = data.frame(start = 0, end = 24,
cmax = TRUE, tmax = TRUE, auclast = TRUE, half.life = TRUE)
)
nca_raw <- suppressWarnings(PKNCA::pk.nca(o_data))
nca_res <- as.data.frame(nca_raw) |>
filter(!is.na(PPORRES)) |>
group_by(treatment, PPTESTCD) |>
summarise(value = ifelse(unique(PPTESTCD) == "tmax",
median(PPORRES), exp(mean(log(PPORRES)))),
.groups = "drop") |>
pivot_wider(names_from = PPTESTCD, values_from = value)
stopifnot(nrow(nca_res) == length(arms), all(c("cmax", "tmax", "auclast") %in% names(nca_res)))
knitr::kable(nca_res, digits = c(0, 1, 2, 0, 2),
caption = "Simulated NCA: geometric mean (median for tmax) per arm.")| treatment | adj.r.squared | auclast | clast.pred | cmax | half.life | lambda.z | lambda.z.n.points | lambda.z.time.first | lambda.z.time.last | r.squared | span.ratio | tlast | tmax |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Solution | 1 | 3187.34 | 20 | 524.70 | 14 | 0.1 | 34.98 | 17 | 24 | 1 | 0.2 | 24 | 2 |
| IR tablet | 1 | 2900.95 | 25 | 355.39 | 10 | 0.1 | 25.75 | 19 | 24 | 1 | 0.3 | 24 | 4 |
| CR tablet (20%) | 1 | 2442.01 | 22 | 313.82 | 9 | 0.1 | 26.30 | 19 | 24 | 1 | 0.3 | 24 | 4 |
Comparison against the published values
The paper reports three summary statistics that this model can be scored against: the median AUC0-24 of the 200 mg liquid arms of Studies 3 and 4 (Figure 3 caption), and the geometric mean peak concentrations of the immediate-release and controlled-release tablets in Study 5 (Figure 5). Tmax and half-life are not reported numerically, so they are omitted from the comparison rather than compared against a value read off a figure.
published <- tibble::tribble(
~treatment, ~cmax, ~auclast,
"Solution", NA, 3000,
"IR tablet", 599, NA,
"CR tablet (20%)", 314, NA
)
cmp <- nlmixr2lib::ncaComparisonTable(
simulated = nca_res,
reference = published,
by = "treatment",
units = c(cmax = "ng/mL", auclast = "ng*h/mL"),
tolerance_pct = 20
)
# ncaComparisonTable() already names the header column "NCA parameter" and
# renders an unmatched cell as a dash, so no renaming is needed here.
knitr::kable(cmp,
caption = paste("Simulated vs published.",
"* marks a difference above 20%."),
align = c("l", "l", "r", "r", "r"))| NCA parameter | treatment | Reference | Simulated | % diff |
|---|---|---|---|---|
| Cmax (ng/mL) | Solution | — | 525 | — |
| Cmax (ng/mL) | IR tablet | 599 | 355 | -40.7%* |
| Cmax (ng/mL) | CR tablet (20%) | 314 | 314 | -0.1% |
| AUClast (ng*h/mL) | Solution | 3000 | 3190 | +6.2% |
| AUClast (ng*h/mL) | IR tablet | — | 2900 | — |
| AUClast (ng*h/mL) | CR tablet (20%) | — | 2440 | — |
gm <- function(tr, pp) nca_res[[pp]][nca_res$treatment == tr]
sol_auc <- gm("Solution", "auclast")
ir_cmax <- gm("IR tablet", "cmax")
cr_cmax <- gm("CR tablet (20%)", "cmax")
c(solution_auc0_24 = sol_auc, ir_cmax = ir_cmax, cr_cmax = cr_cmax,
cr_over_ir = cr_cmax / ir_cmax)
#> solution_auc0_24 ir_cmax cr_cmax cr_over_ir
#> 3187.337125 355.393957 313.824960 0.883034
stopifnot(
# Exposure is the parameter the model reproduces well: the 200 mg liquid arm
# lands within 20% of the published median AUC0-24 of 3000 ng*h/mL.
abs(sol_auc / 3000 - 1) < 0.20,
# The CR peak concentration matches the published geometric mean closely.
abs(cr_cmax / 314 - 1) < 0.20,
# The IR peak is under-predicted -- a limitation the supplement itself records
# ("slight under-prediction of the highest concentrations was somewhat
# apparent"). This gate pins the size of that gap so it cannot silently drift.
ir_cmax / 599 > 0.55, ir_cmax / 599 < 0.80,
# Controlled release still lowers the peak relative to immediate release,
# which is the finding the formulation programme rested on.
cr_cmax < ir_cmax
)Per-subject paired checks are more robust than cohort extremes, because the absorption terms carry large inter-individual and inter-occasion variability and the extreme of a random cohort is not reproducible across rxode2 versions.
paired <- obs |>
group_by(treatment, id) |>
summarise(cmax = max(Cc), tmax_pk = time[which.max(Cc)],
tmax_ro = time[which.max(D2RO)], .groups = "drop")
# Hysteresis holds subject by subject, not just on average.
stopifnot(all(paired$tmax_ro - paired$tmax_pk > 2))
cr_vs_ir <- paired |>
filter(treatment != "Solution") |>
select(treatment, id, cmax) |>
pivot_wider(names_from = treatment, values_from = cmax) |>
mutate(ratio = `CR tablet (20%)` / `IR tablet`)
quantile(cr_vs_ir$ratio, c(0.1, 0.5, 0.9))
#> 10% 50% 90%
#> 0.7919407 0.8884166 0.9902275
stopifnot(
# Centre, not extreme: the controlled-release tablet lowers the peak
# concentration in the typical subject.
median(cr_vs_ir$ratio) < 0.95,
# Robust envelope rather than max(): the extreme of a random cohort is not
# reproducible across rxode2 versions, so the 90th percentile is asserted
# instead. Even the least-affected decile is not materially above parity.
quantile(cr_vs_ir$ratio, 0.9) < 1.10
)The direction of the controlled-release effect is reproduced, but its magnitude is not: the simulated median CR-to-IR peak ratio is about 0.89 against the 0.52 implied by the published geometric means of 314 and 599 ng/mL. The gap is entirely on the immediate-release side, whose peak this model under-predicts by about 40% (cohort geometric mean 356 against a published 599 ng/mL) for the reason recorded in the first Errata bullet below; the CR peak itself lands within a percent of the published value (314.3 against 314 ng/mL). Nothing was adjusted to narrow it.
Assumptions and deviations
-
Which published parameter set the model encodes.
The supplement reports the PK layer twice and the two reports disagree.
Its Table 2 (“NONMEM PK Fitting Results”) gives CL 128 L/h, Vc 203 L,
ALAG2 0.97 h and a distinct IIV / IOV structure; the PK control stream
printed in the same supplement carries
$THETA ... FIXvalues of CL 106 L/h, Vc 289 L, ALAG2 0.241 h with a different IIV / IOV structure. This model encodes the control stream, for four reasons. (i) Table 2’s IIV column is not usable: it reports variances up to 39.1 on parameters with exponential inter-individual variability, which corresponds to a coefficient of variation of order 1e8 and cannot be simulated; the supplement itself notes that “obtaining full convergence was typically not feasible”. The control stream’s variances (0.105 on CL, i.e. 33% CV) are ordinary. (ii) The control stream is the executable specification whose$TABLEwrites exactly the per-subject columns that the D2 receptor occupancy script reads in$INPUT, so it is the PK that underlies the published Distribution Model results. (iii) It is internally self-consistent across$THETA,$OMEGAand the IOV block. (iv) Scored against the paper’s own published anchors it fits better than Table 2: median AUC0-24 for the 200 mg liquid arm 3395 vs 3588 ng*h/mL against a published 3000, and IR typical-value Cmax 381 vs 323 ng/mL against a published 599. Nothing was tuned; both sets were simulated as printed and compared. -
ALAG3is an increment, not an absolute lag. The control stream computesTAL3 = TAL2 + TAL3T, soTHETA(6)is the additional delay of the third absorption compartment beyond the second. The absolute lag is 0.241 + 2.4 = 2.641 h. ReadingTHETA(6)as an absolute lag would place the third absorption wave an hour and a half too early. -
Unit conversion on the plasma concentration.
$ERRORcomputesCT = A(4)/V4with no scaling, which means the NONMEM dataset carried dose amounts in ug. Doses are declared here in mg so that the published dose levels can be entered directly, somodel()applies an explicit factor of 1000 to convert mg/L to ng/mL. It is a unit conversion, not a fitted parameter, and it applies before the effect compartments because their influx is driven by the plasma concentration on the same ng/mL scale askd = 9 ng/mL. -
PD parameters come from Table 3, not from the occupancy
control stream. That script’s
$THETAentries (KE1 0.0723, KE2 0.221, KE3 0.0605, KT 0.0741) are bounded initial estimates for the run; the final values are supplement Table 3 (KE1 0.0221, KE2 0.0685, KE3 0.0602, KT 0.42). The fixed KD, FU and SP agree between the two. -
The Weibull dissolution constants come from the executed
$PKblock. Supplement Table 5 rounds two of them differently from the code that was run (RGN9TKA1printed as 0.80 against 0.8457 in$PK, RGN10TGAMA1printed as 0.70 against 0.7764). The four-decimal values in$PKare used. The supplement also states that the pairs for the fed 25% CR tablet and for the solution were interpolated rather than fitted to a dissolution curve. -
The oral solution bypasses the Weibull term. The PK
script applies a nominal fast Weibull pair to the solution while the D2
occupancy script sets
WB = 1for it. This model follows the occupancy script, which is the final integrated model and is also the physically sensible reading (a solution has no solid-state dissolution step). The numerical difference is confined to the first half hour after a solution dose. -
Zero-variance IIV terms are omitted. The control
stream fixes the inter-individual variances of
KA1-KA3andF1-F3to exactly zero. No eta is carried for them, because a zero-variance eta makes OMEGA singular andchol()fails during sampling. The inter-occasion variability onF1-F3is non-zero and is retained. -
Which CR prototype Study 5 selected is not stated.
The paper says a single CR prototype was selected “based on PK
parameters evaluated in previous cohorts” but does not name it. The 20%
CR tablet is used here because its simulated peak concentration is the
closest of the five CR prototypes to the published 314 ng/mL. All ten
formulations are available in the model through their
FORM_SEP4199_*indicators. - The IR peak concentration is under-predicted by about 40%. The cohort geometric mean is 356 ng/mL against a published 599, and the typical-value prediction is 381 ng/mL, i.e. about a third low. This is a property of the published model, not of the transcription: the supplement records that “slight under-prediction of the highest concentrations was somewhat apparent”, and the main text emphasises the “short-lasting peak concentrations with the IR formulation” that a three-depot Weibull absorption model necessarily smooths. Exposure (AUC) and the CR peak are reproduced within 20%. No parameter was adjusted to close the gap.
-
Sex and food are carried as
covariatesDataExcluded.SEXandFEDappear in the NONMEM$INPUTrecords but are never referenced in$PKor$DES. The single fed arm is carried structurally instead, as its own regimen indicator with its own bioavailability triple and Weibull pair. -
sex_female_pctis deliberately absent frompopulation. Table 1 reports sex per study rather than pooled; the ascending-single-dose study enrolled male subjects only per its title; and no sex breakdown is published for Study 2 or for Study 5 as a whole. A pooled female percentage across the 181 subjects therefore cannot be computed. Women were enrolled – Study 4 was 36% female and Study 5 Parts 1 and 2 were 47% and 56% female – so recording 0 would assert an all-male cohort that the source contradicts. -
Models the paper rejected are not extracted. Table
2 of the paper lists five alternative PD forms (linear direct effect,
saturated Emax, effect compartment, concentration-difference, ratio)
with their equations but no parameter estimates, and the earlier
indirect-response model built on single-dose data is described without
published parameters. None of them can be encoded without inventing
values, so only the Distribution Model is packaged. The concentration-QT
relationship of Figure 4c and the
Cmax-occupancy regression of Figure 3b are likewise reported graphically without parameter values.