BMS-911543 in vitro CYP1A2 metabolism and time-dependent inactivation (Zhou 2015)
Source:vignettes/articles/Zhou_2015_bms911543_invitro.Rmd
Zhou_2015_bms911543_invitro.RmdModel and source
- Citation: Zhou L, Gan J, Yoshitsugu H, Gu X, Lutz JD, Masson E, Humphreys WG. Integration of Physiologically-Based Pharmacokinetic Modeling into Early Clinical Development: An Investigation of the Pharmacokinetic Nonlinearity. CPT Pharmacometrics Syst Pharmacol. 2015;4(5):286-294. doi:10.1002/psp4.35. PMCID: PMC4452934. Vmax = 48.1 pmol/min/mg protein and Km = 1.9 uM are the annotations on the ‘HLM kinetics’ panel of Figure 1. Incubation design (0.25 mg/mL human liver microsomes, 1 mM NADPH, pH 7.4 phosphate buffer, 37 C, 10 min, substrate 0.1-10 uM, triplicate, nonlinear fit in GraphPad Prism): Methods, ‘BMS-911543 metabolism’. The fraction unbound in incubation (fumic = 0.78, predicted rather than measured) and the metabolic scaling factor of 4 applied to Km inside the Simcyp model: Methods, ‘PBPK modeling and simulation’. The statement that M1 was the only drug-related component detected in pooled HLM and in cDNA-expressed CYP enzymes, and that CYP1A2 is the primary enzyme with CYP3A4 and CYP2J2 minor: Results, ‘BMS-911543 metabolism’.
- Article: https://doi.org/10.1002/psp4.35
- PubMed Central open-access copy: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4452934/
This paper contributes five model files, one per in vitro system the authors characterised:
mod_hlm <- rxode2::rxode(readModelDb("Zhou_2015_bms911543_hlm"))
mod_1a2 <- rxode2::rxode(readModelDb("Zhou_2015_bms911543_rcyp1a2"))
mod_3a4 <- rxode2::rxode(readModelDb("Zhou_2015_bms911543_rcyp3a4"))
mod_2j2 <- rxode2::rxode(readModelDb("Zhou_2015_bms911543_rcyp2j2"))
mod_tdi <- rxode2::rxode(readModelDb("Zhou_2015_bms911543_cyp1a2_tdi"))
# Fixed and estimated ini() values are not returned as columns by rxSolve(), so
# read them straight off the packaged model when a check needs one.
ini_val <- function(mod, nm) {
stopifnot(nm %in% mod$iniDf$name)
mod$iniDf$est[mod$iniDf$name == nm]
}Scope: what this paper does and does not contribute
Zhou 2015 has two halves, and only one of them is reproducible.
The first half is the authors’ own bench work: a Michaelis-Menten characterisation of the formation of metabolite M1 from BMS-911543 in pooled human liver microsomes and in three recombinant CYP systems (Figure 1), and a mechanism-based inactivation study of CYP1A2 by the same compound using phenacetin O-deethylation as the activity probe (Figure 2). Both fitted equations are printed, every constant is printed, the incubation designs are described in the Methods, and the two studies are the reverse-translation step the paper is written around: they were run because the first physiologically based model, built on the pre-clinical package, could not reproduce the dose-dependent and time-dependent nonlinearity BMS-911543 showed in its first-in-human study. That is the layer this vignette validates and that the five model files carry.
The second half is a Simcyp V12 whole-body physiologically based model that consumes those constants to simulate plasma profiles across nine dose levels. That layer is a vendor platform model and is not extracted. The paper reports no tissue volumes, no organ blood flows, no MPPGL, no liver weight and no hepatic CYP abundances; the whole-body ODE system is Simcyp’s rather than the authors’; and no project file is deposited. Reconstructing it would require substituting physiology from outside the source, which is exactly the substitution the library’s PBPK sourcing rule forbids. The section “Published results not reproduced here” below records what that half produced, so a reader can see precisely what was left on the table and why.
Two consequences of that split are load-bearing for the in vitro layer and are called out where they arise:
- The authors deliberately changed two of the in vitro constants when they moved them into the platform model, to close an in-vitro-to-in-vivo gap. A metabolic scaling factor of 4 was applied to every Km (entered as Km/4), and the CYP1A2 inactivation constant KI was raised from the measured 2.9 uM to 11.2 uM. The model files carry the measured values, because they are the in vitro models. The paper’s own Discussion concedes that the two adjusted inputs “are not identifiable”.
- Three quantities the platform half needed are not in the source at all: the CYP1A2 degradation rate constant kdeg (the authors used the Simcyp default and never print it), and the maximal fold induction and EC50 of CYP1A2 autoinduction (taken from Supplementary Table S3, which is not on disk). Nothing in the five model files depends on them.
In vitro systems
| Assay | System | Buffer | Design | Readout | Fit |
|---|---|---|---|---|---|
| M1 formation kinetics | Pooled human liver microsomes at 0.25 mg/mL, and cDNA-expressed recombinant CYP1A2, CYP3A4 and CYP2J2 (enzyme content not reported) | pH 7.4 phosphate, 1 mM NADPH, 37 C | 10 min incubation; BMS-911543 0.1-10 uM; triplicate | M1 formation velocity by LC/MS/MS | Nonlinear fitting, GraphPad Prism |
| CYP1A2 inactivation | Human liver microsomes at 1.0 mg/mL | 100 mM potassium phosphate pH 7.4, 1 mM NADPH, 37 C | 5 min preincubation, then 3, 10, 20 or 30 min inactivation without phenacetin; 10-fold dilution into 450 uM phenacetin for 13.5 min; BMS-911543 0 to 25 uM; triplicate | Acetaminophen formation from phenacetin, as percent of the no-inhibitor control | Nonlinear regression, GraphPad Prism v.5 |
The two assays run at different microsomal protein concentrations, 0.25 and 1.0 mg/mL, so their fractions unbound in incubation are not interchangeable and only the metabolism arm has a reported one.
The clinical study that motivated the work is a first-in-human open-label dose escalation in patients with cancer (NCT01236352): 5 to 240 mg across nine dose levels, a single dose on day 1 followed by a 24 h washout, then twice-daily dosing for two weeks; 833 plasma timepoints from 72 subjects. No population PK model is reported for it – the only clinical parameter the paper prints is a “preliminary population PK analysis” estimate of Vss/F of about 50 L – so there is no clinical model to extract either.
Source trace
Every value in the five model files, with the location it came from.
| Model | Quantity | Value | Source location |
|---|---|---|---|
| Zhou_2015_bms911543_hlm | km_hlm | 1.9 uM | Figure 1, ‘HLM kinetics’ panel annotation |
| Zhou_2015_bms911543_hlm | vmax_hlm | 48.1 pmol/min/mg protein | Figure 1, ‘HLM kinetics’ panel annotation |
| Zhou_2015_bms911543_hlm | prot_inc (fixed) | 0.25 mg/mL | Methods, ‘BMS-911543 metabolism’ |
| Zhou_2015_bms911543_hlm | fumic (fixed) | 0.78 | Methods, ‘PBPK modeling and simulation’ (Simcyp built-in PREDICTION, not measured) |
| Zhou_2015_bms911543_rcyp1a2 | km_cyp1a2 | 2.1 uM | Figure 1, ‘CYP1A2 kinetics’ panel annotation |
| Zhou_2015_bms911543_rcyp1a2 | vmax_cyp1a2 | 3.2 pmol/min/pmol protein | Figure 1, ‘CYP1A2 kinetics’ panel annotation |
| Zhou_2015_bms911543_rcyp3a4 | km_cyp3a4 | 1.4 uM | Figure 1, ‘CYP3A4 kinetics’ panel annotation |
| Zhou_2015_bms911543_rcyp3a4 | vmax_cyp3a4 | 0.048 pmol/min/pmol protein | Figure 1, ‘CYP3A4 kinetics’ panel annotation |
| Zhou_2015_bms911543_rcyp2j2 | km_cyp2j2 | 1.3 uM | Figure 1, ‘CYP2J2 kinetics’ panel annotation |
| Zhou_2015_bms911543_rcyp2j2 | vmax_cyp2j2 | 0.5 pmol/min/pmol protein | Figure 1, ‘CYP2J2 kinetics’ panel annotation |
| Zhou_2015_bms911543_cyp1a2_tdi | ki_1a2 | 2.9 uM | Figure 2b annotation and Results, ‘CYP1A2 TDI by BMS-911543’ (2.9 +/- 0.9 uM) |
| Zhou_2015_bms911543_cyp1a2_tdi | kinact_1a2 | 1.4 /h | Figure 2b annotation and Results, ‘CYP1A2 TDI by BMS-911543’ (1.4 +/- 0.1 per h) |
| all five | addSd (fixed at 0) | 0 | Not reported anywhere in the source; see Assumptions |
| Michaelis-Menten form | v = Vmax*S/(Km+S) | - | Methods, ‘BMS-911543 metabolism’; the fitted curves of Figure 1 |
| Inactivation form (Eq. 1) | vI = v0exp(-kt) | - | Methods, ‘CYP1A2 time-dependent inhibition’ |
| Inactivation form (Eq. 2) | k = kinact*I/(KI+I) | - | Methods, ‘CYP1A2 time-dependent inhibition’ |
All eight Michaelis-Menten constants live in the annotations of Figure 1, not in any table or in the body text. The Results section says only that “the observed enzyme kinetic values for Vmax and Km are listed in Figure 1”. Those annotations are rendered text inside the figure image and are therefore invisible to a PDF-to-text converter; they were read off the rendered figure panel. The values are printed by the authors and are not digitised measurements from the plotted points, so their precision is the authors’ own, but the reading step is recorded here as non-text provenance.
Dimensional analysis
The one unit bridge in the package, in the human liver microsome model, is worth checking explicitly. Getting it wrong misstates every rate by a factor of 1000 and nothing else in the model catches it.
# Vmax [pmol/(min*mg protein)] * protein [mg/mL] = pmol/(min*mL)
# 1 pmol/mL = 1 nmol/L = 0.001 umol/L = 0.001 uM, hence the /1000.
vmax_hlm <- ini_val(mod_hlm, "vmax_hlm")
km_hlm <- ini_val(mod_hlm, "km_hlm")
prot_inc <- ini_val(mod_hlm, "prot_inc")
rate_at_vmax_uM_per_min <- vmax_hlm * prot_inc / 1000
c(vmax_pmol_min_mg = vmax_hlm, protein_mg_mL = prot_inc,
max_rate_uM_per_min = rate_at_vmax_uM_per_min)
#> vmax_pmol_min_mg protein_mg_mL max_rate_uM_per_min
#> 48.100000 0.250000 0.012025
# Vmax/Km with Vmax in pmol/min/mg and Km in uM (= pmol/uL) is uL/min/mg.
c(clint_hlm_uL_min_mg = vmax_hlm / km_hlm)
#> clint_hlm_uL_min_mg
#> 25.31579Simulation grid
The in vitro “cohort” is a grid of incubation concentrations, not a set of subjects; there is no inter-individual variability anywhere in this paper. The grids below are well under the 200-per-arm cap.
# Log-spaced concentrations spanning the 0.1-10 uM experimental range of the
# metabolism assay, plus an exact point at each system's own Km.
conc_grid <- sort(unique(c(
0,
exp(seq(log(0.05), log(20), length.out = 60)),
ini_val(mod_1a2, "km_cyp1a2"),
ini_val(mod_3a4, "km_cyp3a4"),
ini_val(mod_2j2, "km_cyp2j2"),
km_hlm,
10
)))
length(conc_grid)
#> [1] 66Replicating Figure 1: Michaelis-Menten kinetics of M1 formation
The three recombinant models are static concentration-response models and solve from a one-row-per-concentration frame. The human liver microsome model is an ODE, so its initial velocity is read at time zero after dosing the substrate into the incubation.
solve_static <- function(mod, grid) {
rxode2::rxSolve(
mod,
data.frame(id = seq_along(grid), time = 0, CP_BMS911543_UM = grid),
returnType = "data.frame"
) |>
dplyr::transmute(conc = CP_BMS911543_UM, velocity = vM1)
}
# The HLM model is dosed: the substrate is spiked into the incubation at t = 0,
# so the initial velocity is vM1 on the t = 0 record. Observation rows are placed
# on the `bms911543` ODE state, never on the algebraic observable.
ev_hlm_initial <- do.call(rbind, lapply(seq_along(conc_grid), function(i) {
rbind(
data.frame(id = i, time = 0, amt = conc_grid[i], evid = 1L, cmt = "bms911543"),
data.frame(id = i, time = 0, amt = NA_real_, evid = 0L, cmt = "bms911543")
)
}))
hlm_initial <-
rxode2::rxSolve(mod_hlm, ev_hlm_initial, returnType = "data.frame") |>
dplyr::transmute(conc = Cbms911543, velocity = vM1)
#> Warning: multi-subject simulation without without 'omega'
fig1 <- dplyr::bind_rows(
dplyr::mutate(hlm_initial, panel = "HLM (pmol/min/mg protein)"),
dplyr::mutate(solve_static(mod_1a2, conc_grid), panel = "rCYP1A2 (pmol/min/pmol protein)"),
dplyr::mutate(solve_static(mod_3a4, conc_grid), panel = "rCYP3A4 (pmol/min/pmol protein)"),
dplyr::mutate(solve_static(mod_2j2, conc_grid), panel = "rCYP2J2 (pmol/min/pmol protein)")
)
#> Warning: multi-subject simulation without without 'omega'
#> Warning: multi-subject simulation without without 'omega'
#> Warning: multi-subject simulation without without 'omega'
ggplot2::ggplot(fig1, ggplot2::aes(conc, velocity)) +
ggplot2::geom_line(linewidth = 0.7) +
ggplot2::facet_wrap(~panel, scales = "free_y") +
ggplot2::coord_cartesian(xlim = c(0, 15)) +
ggplot2::labs(x = "Substrate concentration (uM)", y = "M1 formation velocity") +
ggplot2::theme_bw()
Replicates Figure 1 of Zhou 2015: M1 formation velocity against BMS-911543 concentration in pooled human liver microsomes and in three recombinant CYP systems. Each panel has its own velocity scale, as in the source.
Structural identities
These hold exactly, from the printed constants alone, and are the strongest available check that the eight Figure 1 annotations were transcribed correctly into the right slots: a Km and a Vmax swapped between panels, or a decimal point moved, breaks them immediately.
mm_check <- function(mod, km_name, vmax_name, label) {
km <- ini_val(mod, km_name)
vmax <- ini_val(mod, vmax_name)
got <- solve_static(mod, c(0, km, 1e6))
tibble::tibble(
system = label,
km = km,
vmax = vmax,
v_at_zero = got$velocity[1],
v_at_km = got$velocity[2],
half_vmax = vmax / 2,
v_at_infinity = got$velocity[3],
clint = vmax / km
)
}
mm <- dplyr::bind_rows(
mm_check(mod_1a2, "km_cyp1a2", "vmax_cyp1a2", "rCYP1A2"),
mm_check(mod_3a4, "km_cyp3a4", "vmax_cyp3a4", "rCYP3A4"),
mm_check(mod_2j2, "km_cyp2j2", "vmax_cyp2j2", "rCYP2J2")
)
#> Warning: multi-subject simulation without without 'omega'
#> Warning: multi-subject simulation without without 'omega'
#> Warning: multi-subject simulation without without 'omega'
stopifnot(
# No substrate, no product.
all(mm$v_at_zero == 0),
# The defining property of the Michaelis constant.
all(abs(mm$v_at_km - mm$half_vmax) < 1e-10),
# The velocity saturates at Vmax.
all(abs(mm$v_at_infinity - mm$vmax) / mm$vmax < 1e-4)
)
# The same three identities in the dosed HLM model.
hlm_id <- rxode2::rxSolve(
mod_hlm,
do.call(rbind, lapply(seq_along(c(km_hlm, 1e6)), function(i) {
s <- c(km_hlm, 1e6)[i]
rbind(data.frame(id = i, time = 0, amt = s, evid = 1L, cmt = "bms911543"),
data.frame(id = i, time = 0, amt = NA_real_, evid = 0L, cmt = "bms911543"))
})),
returnType = "data.frame"
)
#> Warning: multi-subject simulation without without 'omega'
stopifnot(
abs(hlm_id$vM1[1] - vmax_hlm / 2) < 1e-8,
abs(hlm_id$vM1[2] - vmax_hlm) / vmax_hlm < 1e-4
)
knitr::kable(mm, digits = 4, caption = "Michaelis-Menten structural identities in the three recombinant systems.")| system | km | vmax | v_at_zero | v_at_km | half_vmax | v_at_infinity | clint |
|---|---|---|---|---|---|---|---|
| rCYP1A2 | 2.1 | 3.200 | 0 | 1.600 | 1.600 | 3.200 | 1.5238 |
| rCYP3A4 | 1.4 | 0.048 | 0 | 0.024 | 0.024 | 0.048 | 0.0343 |
| rCYP2J2 | 1.3 | 0.500 | 0 | 0.250 | 0.250 | 0.500 | 0.3846 |
Coarse cross-check against the plotted points
The Figure 1 panels also carry the measured points the curves were fitted to. These were read off the rendered figure and are not a source of any model value; they are recorded here only to confirm that the curve the model draws is the curve the source drew, at the concentration where the panels are easiest to read. The tolerance is deliberately loose, because the uncertainty here is in the reading, not in the model.
digitised_at_10uM <- tibble::tribble(
~panel_source, ~observed_read_off_figure,
"HLM", 38.5,
"rCYP1A2", 2.75,
"rCYP3A4", 0.0465,
"rCYP2J2", 0.44
)
predicted_at_10uM <- tibble::tibble(
panel_source = c("HLM", "rCYP1A2", "rCYP3A4", "rCYP2J2"),
predicted = c(
vmax_hlm * 10 / (km_hlm + 10),
ini_val(mod_1a2, "vmax_cyp1a2") * 10 / (ini_val(mod_1a2, "km_cyp1a2") + 10),
ini_val(mod_3a4, "vmax_cyp3a4") * 10 / (ini_val(mod_3a4, "km_cyp3a4") + 10),
ini_val(mod_2j2, "vmax_cyp2j2") * 10 / (ini_val(mod_2j2, "km_cyp2j2") + 10)
)
)
cmp10 <- dplyr::left_join(predicted_at_10uM, digitised_at_10uM, by = "panel_source") |>
dplyr::mutate(pct_diff = 100 * (observed_read_off_figure - predicted) / predicted)
# Centre and spread, not the extreme of the four readings.
stopifnot(
abs(median(cmp10$pct_diff)) < 15,
max(abs(cmp10$pct_diff)) < 30
)
knitr::kable(cmp10, digits = 4,
caption = "Model velocity at 10 uM against the topmost plotted point of each Figure 1 panel, read off the rendered figure.")| panel_source | predicted | observed_read_off_figure | pct_diff |
|---|---|---|---|
| HLM | 40.4202 | 38.5000 | -4.7505 |
| rCYP1A2 | 2.6446 | 2.7500 | 3.9844 |
| rCYP3A4 | 0.0421 | 0.0465 | 10.4375 |
| rCYP2J2 | 0.4425 | 0.4400 | -0.5600 |
Cross-system comparison
Two free comparisons the source makes itself, reproduced from the packaged constants.
cross <- tibble::tibble(
system = c("HLM", "rCYP1A2", "rCYP3A4", "rCYP2J2"),
km_uM = c(km_hlm, ini_val(mod_1a2, "km_cyp1a2"),
ini_val(mod_3a4, "km_cyp3a4"), ini_val(mod_2j2, "km_cyp2j2")),
vmax = c(vmax_hlm, ini_val(mod_1a2, "vmax_cyp1a2"),
ini_val(mod_3a4, "vmax_cyp3a4"), ini_val(mod_2j2, "vmax_cyp2j2"))
) |>
dplyr::mutate(clint = vmax / km_uM)
# 1. All four Michaelis constants sit inside a narrow band, consistent with one
# dominant binding mode for the reaction across the four systems.
km_spread <- max(cross$km_uM) / min(cross$km_uM)
stopifnot(km_spread < 2)
# 2. The three recombinant systems share a per-pmol-enzyme denominator, so their
# Vmax/Km values are directly comparable and must rank CYP1A2 first, which is
# the paper's own reaction-phenotyping conclusion ("CYP1A2 was the primary
# enzyme mediating the formation of M1 ... CYP3A4 and CYP2J2 played a minor
# role", Results).
rec <- dplyr::filter(cross, system != "HLM")
stopifnot(
rec$system[which.max(rec$clint)] == "rCYP1A2",
rec$system[which.min(rec$clint)] == "rCYP3A4"
)
c(km_fold_spread = km_spread,
clint_1a2_over_2j2 = rec$clint[rec$system == "rCYP1A2"] / rec$clint[rec$system == "rCYP2J2"],
clint_1a2_over_3a4 = rec$clint[rec$system == "rCYP1A2"] / rec$clint[rec$system == "rCYP3A4"])
#> km_fold_spread clint_1a2_over_2j2 clint_1a2_over_3a4
#> 1.615385 3.961905 44.444444
knitr::kable(cross, digits = 4,
caption = "Michaelis constants and per-system intrinsic clearances. The HLM clint is uL/min/mg microsomal protein; the three recombinant clints are uL/min/pmol enzyme and are comparable only with each other.")| system | km_uM | vmax | clint |
|---|---|---|---|
| HLM | 1.9 | 48.100 | 25.3158 |
| rCYP1A2 | 2.1 | 3.200 | 1.5238 |
| rCYP3A4 | 1.4 | 0.048 | 0.0343 |
| rCYP2J2 | 1.3 | 0.500 | 0.3846 |
Note what this comparison cannot do. Turning the per-enzyme ranking into a fraction metabolised requires the hepatic abundance of each isoform, an intersystem extrapolation factor and MPPGL, none of which the source reports. The Simcyp model’s predicted 96 percent fraction metabolised by CYP1A2 is therefore a platform output, not something reproducible here, and it is listed below rather than asserted.
The human liver microsome incubation as a dynamic system
Because the microsomal protein concentration of the metabolism assay is reported, that one arm supports a full substrate-depletion ODE. Two exact checks follow from it.
# The concentrations the authors used, over the 10 min incubation of the Methods.
s0 <- c(0.1, 0.2, 0.5, 1, 2, 5, 10)
ev_hlm <- do.call(rbind, lapply(seq_along(s0), function(i) {
rbind(
data.frame(id = i, time = 0, amt = s0[i], evid = 1L, cmt = "bms911543"),
data.frame(id = i, time = seq(0, 10, by = 0.25), amt = NA_real_, evid = 0L, cmt = "bms911543")
)
}))
hlm_run <- rxode2::rxSolve(mod_hlm, ev_hlm, returnType = "data.frame") |>
dplyr::mutate(s0 = s0[id])
#> Warning: multi-subject simulation without without 'omega'Mass balance. The only routes in the model are substrate to M1, so the sum of the two states must equal the spiked concentration at every time, exactly.
mb <- hlm_run |>
dplyr::mutate(total = bms911543 + m1,
rel_err = abs(total - s0) / s0)
stopifnot(max(mb$rel_err) < 1e-8)
c(max_relative_mass_balance_error = max(mb$rel_err))
#> max_relative_mass_balance_error
#> 1.24345e-15Initial-rate conditions. The Methods report a single 10 min incubation and fit initial-rate Michaelis-Menten kinetics to it, which is only valid if substrate depletion stays small. The model says by how much, at every concentration the authors used – and the worst case is the lowest concentration, because that is where the fractional turnover is largest.
depletion <- hlm_run |>
dplyr::filter(time == 10) |>
dplyr::transmute(s0, remaining = bms911543,
pct_consumed = 100 * (s0 - bms911543) / s0)
# The conventional initial-rate criterion is under 20 percent turnover.
stopifnot(max(depletion$pct_consumed) < 20)
knitr::kable(depletion, digits = 4,
caption = "Substrate consumed over the 10 min human liver microsome incubation, by starting concentration.")| s0 | remaining | pct_consumed |
|---|---|---|
| 0.1 | 0.0942 | 5.8437 |
| 0.2 | 0.1888 | 5.5799 |
| 0.5 | 0.4754 | 4.9117 |
| 1.0 | 0.9591 | 4.0902 |
| 2.0 | 1.9388 | 3.0600 |
| 5.0 | 4.9131 | 1.7385 |
| 10.0 | 9.8990 | 1.0097 |
ggplot2::ggplot(hlm_run, ggplot2::aes(time, bms911543 / s0, group = factor(s0), colour = factor(s0))) +
ggplot2::geom_line(linewidth = 0.7) +
ggplot2::scale_y_continuous(limits = c(0.9, 1)) +
ggplot2::labs(x = "Incubation time (min)", y = "Fraction of spiked substrate remaining",
colour = "Spiked (uM)") +
ggplot2::theme_bw()
Substrate depletion over the 10 min human liver microsome incubation of Zhou 2015 Methods. The initial-rate assumption behind the Figure 1 fit holds at every concentration used.
Replicating Figure 2: time-dependent inactivation of CYP1A2
The inactivation model runs on an hour timescale, because the reported kinact is in per-hour units while the assay times are in minutes.
inh_conc <- c(0, 0.39, 0.78, 1.56, 3.125, 6.25, 12.5, 25.0) # Methods
tdi_times_min <- seq(0, 30, by = 0.5)
ev_tdi <- do.call(rbind, lapply(seq_along(inh_conc), function(i) {
data.frame(id = i, time = tdi_times_min / 60, CP_BMS911543_UM = inh_conc[i])
}))
tdi_run <- rxode2::rxSolve(mod_tdi, ev_tdi, returnType = "data.frame") |>
dplyr::mutate(time_min = time * 60, inhibitor = CP_BMS911543_UM)
#> Warning: multi-subject simulation without without 'omega'
ggplot2::ggplot(tdi_run,
ggplot2::aes(time_min, pctActivity_1a2,
group = factor(inhibitor), colour = factor(inhibitor))) +
ggplot2::geom_line(linewidth = 0.7) +
ggplot2::scale_y_log10(limits = c(50, 140)) +
ggplot2::labs(x = "Preincubation time (min)", y = "Percent CYP1A2 activity remaining",
colour = "BMS-911543 (uM)") +
ggplot2::theme_bw()
Replicates Figure 2a of Zhou 2015: percent CYP1A2 activity remaining against preincubation time, one line per BMS-911543 concentration, on a log activity scale.
lambda_curve <- rxode2::rxSolve(
mod_tdi,
data.frame(id = seq_len(101), time = 0,
CP_BMS911543_UM = seq(0, 30, length.out = 101)),
returnType = "data.frame"
)
#> Warning: multi-subject simulation without without 'omega'
ggplot2::ggplot(lambda_curve,
ggplot2::aes(CP_BMS911543_UM, lambda_1a2_permin)) +
ggplot2::geom_line(linewidth = 0.7) +
ggplot2::coord_cartesian(ylim = c(0, 0.03)) +
ggplot2::labs(x = "BMS-911543 concentration (uM)",
y = "Inactivation rate constant lambda (1/min)") +
ggplot2::theme_bw()
Replicates Figure 2b of Zhou 2015: the first-order inactivation rate constant against the initial BMS-911543 concentration, plotted in the source’s per-minute units.
Structural identities of the inactivation model
ki <- ini_val(mod_tdi, "ki_1a2")
kinact <- ini_val(mod_tdi, "kinact_1a2")
probe <- rxode2::rxSolve(
mod_tdi,
data.frame(id = 1:3, time = 0, CP_BMS911543_UM = c(0, ki, 1e7)),
returnType = "data.frame"
)
#> Warning: multi-subject simulation without without 'omega'
stopifnot(
# Eq. 2 at I = 0: no inhibitor, no inactivation.
probe$kobs_1a2[1] == 0,
# The defining property of KI: half the maximum rate of inactivation.
abs(probe$kobs_1a2[2] - kinact / 2) < 1e-10,
# The plateau of Figure 2b is kinact.
abs(probe$kobs_1a2[3] - kinact) / kinact < 1e-5,
# Eq. 1 at I = 0: the no-inhibitor control holds at 100 percent for all time.
all(tdi_run$pctActivity_1a2[tdi_run$inhibitor == 0] == 100)
)
# Eq. 1 is a first-order decay, so the ODE solution must equal exp(-k*t) to
# solver tolerance at every concentration and every time. This is the check that
# the ODE encoding of the published closed form is faithful.
closed_form <- tdi_run |>
dplyr::mutate(analytic = exp(-kobs_1a2 * time),
abs_err = abs(enzyme_1a2 - analytic))
stopifnot(max(closed_form$abs_err) < 1e-6)
c(max_abs_error_vs_published_closed_form = max(closed_form$abs_err))
#> max_abs_error_vs_published_closed_form
#> 2.766371e-07Coarse cross-check against Figure 2
As with Figure 1, these readings come off the rendered figure and are used only to confirm the model draws the curve the source drew.
# Readings taken from the FITTED CURVE of Figure 2b (more legible than the
# scattered points) and from the lowest line of Figure 2a at 30 min.
lambda_at_25 <- lambda_curve$lambda_1a2_permin[which.min(abs(lambda_curve$CP_BMS911543_UM - 25))]
lambda_at_25_read <- 0.0205
activity_at_25_30min <- tdi_run$pctActivity_1a2[tdi_run$inhibitor == 25 & tdi_run$time_min == 30]
activity_at_25_30min_read <- 57
tibble::tibble(
quantity = c("lambda at 25 uM (1/min), Figure 2b curve",
"percent activity at 25 uM and 30 min, Figure 2a"),
model = c(lambda_at_25, activity_at_25_30min),
read_off_figure = c(lambda_at_25_read, activity_at_25_30min_read)
) |>
knitr::kable(digits = 4, caption = "Model against readings from the Figure 2 panels.")| quantity | model | read_off_figure |
|---|---|---|
| lambda at 25 uM (1/min), Figure 2b curve | 0.0209 | 0.0205 |
| percent activity at 25 uM and 30 min, Figure 2a | 53.4064 | 57.0000 |
stopifnot(
abs(lambda_at_25 - lambda_at_25_read) < 0.005,
abs(activity_at_25_30min - activity_at_25_30min_read) < 10
)The Figure 2a reading sits a few percentage points above the model. That is expected rather than a discrepancy: the lines of Figure 2a are the individual log-linear regressions at each concentration, whereas the model’s rate constant comes from the hyperbola fitted through all of them in Figure 2b, and the two need not coincide at any single concentration.
Why there is no NCA section
PKNCA validation is not applicable to this package. None of the five models has a dosing route, a volume of distribution or a plasma concentration-time profile: four are enzyme-kinetic velocity models of a microsomal or recombinant incubation, and the fifth is an enzyme-inactivation model whose state is a dimensionless activity fraction. The equivalent checks for this model class – dimensional analysis, mass balance, structural identities and agreement with the published closed form – are the sections above.
Published results not reproduced here
Everything below is an output of the Simcyp V12 whole-body model, which is not extracted. It is recorded so a reader can see the full scope of the paper and what would be needed to close each gap.
| Published result | Value | Why it is not reproduced |
|---|---|---|
| Fraction of BMS-911543 metabolised by CYP1A2 | 96 percent of total clearance | Needs hepatic abundances of CYP1A2, CYP3A4 and CYP2J2, an intersystem extrapolation factor and MPPGL. None is reported. |
| Absorption rate constant | ka = 4.138 /h | A Simcyp ADAM absorption-model output; the underlying solubility, particle-size and gastrointestinal transit inputs are in Supplementary Tables S1 and S3, which are not on disk. |
| Fraction of dose absorbed | Fa falls from 1 to 0.42 over 5-240 mg | Same as above; the dose-dependence is a solubility-limited absorption prediction of the platform model. |
| Fraction escaping gut-wall metabolism | Fg = 1 | A platform output; no intestinal physiology is reported. |
| Adjusted apparent clearance | Falls from 11.3 to 7.3 L/h over the dose range | Derived as Fa * dose / AUC(day 1) using the platform’s own Fa and the observed AUCs of Figure 4; both inputs are unavailable as numbers. |
| Hepatic CYP1A2 activity remaining at day 15 | 62 percent with induction, 54 percent without, so induction attenuates the inactivation by 17 percent | Requires the enzyme degradation rate constant kdeg (Simcyp default, never printed), the autoinduction Emax and EC50 (Supplementary Table S3, not on disk), and a liver unbound-concentration profile from the whole-body model. |
| Volume of distribution at steady state | 0.26 L/kg (18.2 L at 70 kg) from rat tissue-to-plasma ratios; about 2 L/kg from in silico partition coefficients | The rat tissue distribution study’s partition coefficients are not tabulated in the paper, and no whole-body physiology is reported to combine them with. |
| Clinical population PK | Vss/F about 50 L, described as a preliminary analysis | A single parameter with no clearance, no absorption and no structural model; not an extractable model. |
| Clinical half-life by dose | 2.0-2.8 h at 5-40 mg; 2.7-5.7 h at 80-240 mg | Non-compartmental summaries of the first-in-human study, reported without the underlying concentration data (Supplementary Table S2 is not on disk). |
Assumptions and deviations
- The Simcyp V12 whole-body PBPK model is not extracted. The paper reports no tissue volumes, no organ blood flows, no MPPGL, no liver weight and no hepatic CYP abundances; the whole-body ODEs belong to the platform; and no project file is deposited. Reconstructing it would require importing physiology from outside the source. The table above records what that half produced.
-
The measured KI of 2.9 uM is carried, not the 11.2 uM used
in the platform model. The authors substituted the higher value
to fit the clinical data and say so explicitly, giving a best-fit
clinical value of 11 +/- 3.4 uM in the Discussion and conceding that Km
and KI “are not identifiable”. These are the in vitro models, so they
carry the in vitro measurement. A user who wants the platform’s
behaviour should set
ki_1a2to 11.2. - The metabolic scaling factor of 4 is not applied. Inside the platform model every Km was entered as Km/4 to close an in-vitro-to-in-vivo extrapolation gap, a fitting device chosen by “the best fitting to the FIH day 1 plasma concentration data”. The Km values here are the ones Figure 1 reports.
- All eight Michaelis-Menten constants were read off the Figure 1 panel annotations, because the source reports them nowhere else – the Results text says only that they are “listed in Figure 1”. They are the authors’ printed values, not digitised from the plotted points, but the reading step is non-text provenance and is recorded as such in the source-trace table.
-
No residual-error model, and no variability of any kind, is
reported. The source fitted every curve in GraphPad Prism and
reports point estimates only. Per the standing policy on unreported
residual error,
addSdisfixed(0)in all five files, so each model returns the deterministic published curve. The+/- 0.9 uMand+/- 0.1 per hprinted alongside the inactivation constants are standard errors of the nonlinear regression, not between-subject or between-donor variability, and are deliberately not encoded as an omega. No SE is printed for any of the eight Michaelis-Menten constants. - The recombinant models are static velocity models, not depletion ODEs. The recombinant CYP content of those incubations is not reported – the 0.25 mg/mL in the same Methods sentence is the human liver microsome protein concentration – so a volumetric rate cannot be formed without inventing a number. The published fit is the velocity curve, so the static form is also the faithful one. The human liver microsome model does carry the depletion ODE, because its protein concentration is reported. This asymmetry between siblings is deliberate and reflects exactly what each arm of the source supports.
-
fumic = 0.78is a Simcyp prediction, not a measurement. The paper says it “was predicted to be 0.78 using the built-in method”. It is carriedfixed()in the human liver microsome model so the unbound Michaelis constant is visible and auditable, and so a user with a measured value can override it. Nothing in the Figure 1 fit depends on it. - The CYP1A2 autoinduction arm is absent. Its maximal fold induction and EC50 were taken from experimental values reported in Supplementary Table S3, which is not on disk. That supplement is immaterial to everything extracted here: all eight metabolism constants are in Figure 1 and both inactivation constants are in Figure 2 and in the Results text.
- No enzyme resynthesis term. The inactivation model describes the inactivation limb of a microsomal assay, in which no enzyme is made. Extending it to the in-vivo turnover balance would need kdeg, which the authors say they took from the Simcyp default and never print.
-
The observation-name convention warning is accepted, not
silenced.
checkModelConventions()warns that the four metabolism models’ single outputvM1is not the canonicalCc. The endpoint is a reaction velocity in pmol/min per mg or per pmol of protein; renaming it toCcwould misstate its units and falsify theunits$concentrationmetadata. This is the same disposition taken forvUK408027inHyland_2008_maraviroc_hlm.R. The inactivation model has no such warning: its output is the registered canonical compartmentenzyme_1a2. - The digitised figure readings are cross-checks only. No model value comes from them, the assertions on them are on the centre and a loose bound rather than on any single extreme reading, and each is labelled at the point of use.
Session information
sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.5 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.8 nlmixr2lib_0.3.2.9000
#>
#> loaded via a namespace (and not attached):
#> [1] generics_0.1.4 sass_0.4.10 xml2_1.6.0
#> [4] digest_0.6.39 magrittr_2.0.5 RColorBrewer_1.1-3
#> [7] evaluate_1.0.5 grid_4.6.1 fastmap_1.2.0
#> [10] lotri_1.0.5 jsonlite_2.0.0 whisker_0.4.1
#> [13] rxode2ll_2.0.18 backports_1.5.1 purrr_1.2.2
#> [16] scales_1.4.0 textshaping_1.0.5 jquerylib_0.1.4
#> [19] cli_3.6.6 crayon_1.5.3 symengine_0.2.13
#> [22] rlang_1.3.0 withr_3.0.3 cachem_1.1.0
#> [25] yaml_2.3.12 otel_0.2.0 tools_4.6.1
#> [28] parallel_4.6.1 memoise_2.0.1 checkmate_2.3.4
#> [31] rxode2lincmt_0.1.0 vctrs_0.7.3 R6_2.6.1
#> [34] lifecycle_1.0.5 fs_2.1.0 ragg_1.5.2
#> [37] PreciseSums_0.7 fontawesome_0.5.3 pkgconfig_2.0.3
#> [40] desc_1.4.3 rex_1.2.2 pkgdown_2.2.1
#> [43] pillar_1.11.1 bslib_0.12.0 gtable_0.3.6
#> [46] glue_1.8.1 data.table_1.18.6.1 Rcpp_1.1.2
#> [49] systemfonts_1.3.2 tidyselect_1.2.1 xfun_0.61
#> [52] tibble_3.3.1 sys_3.4.3 knitr_1.52
#> [55] farver_2.1.2 dparser_1.3.1-13 htmltools_0.5.9
#> [58] labeling_0.4.3 rmarkdown_2.32 compiler_4.6.1
#> [61] S7_0.2.2 downlit_0.4.5 askpass_1.2.1
#> [64] openssl_2.4.2