Skip to contents

Model and source

  • Citation: Fuhr LM, Marok FZ, Mees M, Mahfoud F, Selzer D, Lehr T. (2022). A Physiologically Based Pharmacokinetic and Pharmacodynamic Model of the CYP3A4 Substrate Felodipine for Drug-Drug Interaction Modeling. Pharmaceutics 14(7):1474. doi:10.3390/pharmaceutics14071474. Circadian baseline structure and amplitudes from Chae D, Kim Y, Park K (diastolic blood pressure) and Lott D et al. (heart rate), as cited by Fuhr 2022 (references 28 and 29).
  • Description: Direct-effect pharmacodynamic model of felodipine on diastolic blood pressure (DBP) and heart rate (HR) in healthy and hypertensive adults, the PD layer of the Fuhr 2022 parent-metabolite PBPK/PD model. Each endpoint rides a cosinor circadian baseline (DBP: 24 h + 12 h harmonics after Chae et al.; HR: one 24 h harmonic after Lott et al.), scaled multiplicatively by the 24 h mean, from which felodipine subtracts a hyperbolic Emax effect (DBP) or to which it adds a sigmoid Emax effect (HR), both driven directly by the felodipine plasma concentration with no effect delay. Only this PD layer is reproduced here: the PBPK layer is a PK-Sim / MoBi whole-body model whose organ volumes, blood flows, partition coefficients and CYP3A4 expression profile are platform database outputs the paper does not print, so the felodipine plasma concentration is supplied by the user as the canonical driver covariate CEFFECT (umol/L) rather than generated by a PK model in this file. The 24 h means, circadian phases and amplitudes are the cross-study summary values of Table 3; the paper re-optimised the means and phases for every simulated study (supplementary Table S5). No inter-individual variability and no residual-error model are reported, so both are zero.
  • Article: https://doi.org/10.3390/pharmaceutics14071474 (open access, PMC9322514)
  • Supplement: https://www.mdpi.com/article/10.3390/pharmaceutics14071474/s1 (reference manual; Section 1.6 gives the PD equations S16-S22, Tables S5 and S6 the PD parameters)

Felodipine is a dihydropyridine calcium-channel blocker whose blood-pressure effect tracks its plasma concentration directly. Fuhr 2022 built a parent-metabolite PBPK model of felodipine and dehydrofelodipine in PK-Sim / MoBi (Open Systems Pharmacology Suite 9.1) and extended it with a PD model of diastolic blood pressure (DBP) and heart rate (HR). The PBPK model was then used to predict CYP3A4 drug-drug interactions with erythromycin, itraconazole, carbamazepine and phenytoin.

Only the PD layer is packaged here. The PBPK layer is a platform whole-body model: its organ volumes, blood flows, tissue partition coefficients (Rodgers-Rowland / Schmitt methods) and CYP3A4 expression profile are PK-Sim database outputs that the paper does not tabulate, so it cannot be rebuilt from the publication. The PD layer, by contrast, is printed in full (supplementary Equations S16-S22, main-text Table 3). Its only input from the PBPK layer is the felodipine plasma concentration. Here that concentration is supplied by the user as the time-varying covariate CEFFECT (umol/L).

Population

The PD layer was built from 30 DBP-time and 22 HR-time profiles taken from 17 published clinical studies (Results 3.2; Table 1). Of these, 18 DBP and 18 HR profiles came from healthy volunteers and 12 DBP and 4 HR profiles from patients with essential hypertension, many of whom were also taking other antihypertensives (supplementary Table S1, footnote c). Felodipine was given as 1-3 mg intravenous infusions or as 0.83-40 mg oral solution, immediate-release tablet or extended-release tablet, in single or multiple doses. All data are means digitised from the literature. No individual data were used, so the total number of individuals is not reported. The healthy-volunteer cohorts in Table S1 are mostly young men (per-study mean ages 24-33 years); the hypertensive cohorts are older.

The same information is available programmatically via readModelDb("Fuhr_2022_felodipine")()$population.

Source trace

Equation / parameter Value Source location
circ_dbp (24 h + 12 h cosinor) n/a Supplement Eq. S16 (structure from Chae et al., ref. 28)
dbp = e0_dbp * (1 + circ_dbp) - eff_dbp n/a Supplement Eq. S17 and S21
circ_hr (24 h cosinor) n/a Supplement Eq. S18 (structure from Lott et al., ref. 29)
hr = e0_hr * (1 + circ_hr) + eff_hr n/a Supplement Eq. S19 and S22
eff = emax * C^h / (ec50^h + C^h) n/a Main-text Eq. 1 = supplement Eq. S20
lemax_dbp log(56.18 mmHg) Table 3 (and Table S6), DBP Emax
lec50_dbp log(0.04 umol/L) Table 3, DBP EC50; “40 nmol/L” in the Discussion
DBP Hill coefficient 1 (not a parameter) Table 3 lists h for the HR model only
amp24_dbp 0.0214 (fixed) Table 3, amp24 = 2.14 % (literature, ref. 28)
amp12_dbp 0.0593 (fixed) Table 3, amp12 = 5.93 % (literature, ref. 28)
phase24_dbp 5.28 h Table 3, mean (SD 2.10) of per-study values
phase12_dbp 0.16 h Table 3, mean (SD 2.10) of per-study values
le0_dbp log(70.4 mmHg) Table 3, BPmean, healthy individuals
e_hypert_e0_dbp log(91.9 / 70.4) Table 3, BPmean for hypertensive individuals (footnote b)
lemax_hr log(39.71 bpm) Table 3, HR Emax
lec50_hr log(0.05 umol/L) Table 3, HR EC50
lhill_hr log(1.40) Table 3, HR Hill coefficient
amp_hr 0.063 (fixed) Table 3, amp = 6.3 % (literature, ref. 29)
phase_hr 11.8 h Table 3, mean (SD 5.13) of per-study values
le0_hr log(62.2 bpm) Table 3, HRmean, healthy individuals
e_hypert_e0_hr log(66.4 / 62.2) Table 3, HRmean for hypertensive individuals (footnote b)
addSd_dbp, addSd_hr 0 (fixed) No residual-error model reported
Per-study BPmean, HRmean and phases Table S5 Used below to replicate Figure 4
Felodipine molecular weight 384.25 g/mol Table 2 (converts ng/mL to umol/L)

Deterministic structure checks

The model has no random effects, so every check below is exact.

mod <- readModelDb("Fuhr_2022_felodipine")
mw <- 384.25 # g/mol, Fuhr 2022 Table 2

grid <- expand.grid(
  time = seq(0, 24, by = 0.25),
  CEFFECT = c(0, 0.04, 0.05),
  DIS_HYPERT = c(0, 1)
)
ev_grid <- grid |>
  mutate(id = as.integer(interaction(CEFFECT, DIS_HYPERT, drop = TRUE)), evid = 0, amt = 0) |>
  arrange(id, time)
sim_grid <- as.data.frame(rxSolve(mod, ev_grid, keep = c("DIS_HYPERT")))
#> Warning: multi-subject simulation without without 'omega'

# Placebo 24 h means equal the Table 3 BPmean / HRmean values: the cosine
# harmonics average to zero over a whole 24 h period.
placebo_means <- sim_grid |>
  filter(CEFFECT == 0, time < 24) |>
  group_by(DIS_HYPERT) |>
  summarise(dbp_mean = mean(dbp), hr_mean = mean(hr), .groups = "drop")
knitr::kable(placebo_means, digits = 3)
DIS_HYPERT dbp_mean hr_mean
0 70.4 62.2
1 91.9 66.4
stopifnot(
  abs(placebo_means$dbp_mean[placebo_means$DIS_HYPERT == 0] - 70.4) < 1e-6,
  abs(placebo_means$dbp_mean[placebo_means$DIS_HYPERT == 1] - 91.9) < 1e-6,
  abs(placebo_means$hr_mean[placebo_means$DIS_HYPERT == 0] - 62.2) < 1e-6,
  abs(placebo_means$hr_mean[placebo_means$DIS_HYPERT == 1] - 66.4) < 1e-6
)

# At CEFFECT = EC50 the drug effect is exactly Emax / 2 at every time point.
eff <- sim_grid |>
  filter(CEFFECT > 0) |>
  select(time, CEFFECT, DIS_HYPERT, eff_dbp, eff_hr)
stopifnot(
  all(abs(eff$eff_dbp[eff$CEFFECT == 0.04] - 56.18 / 2) < 1e-8),
  all(abs(eff$eff_hr[eff$CEFFECT == 0.05] - 39.71 / 2) < 1e-8)
)

Exposure-response with the Table 3 typical values

conc_nM <- c(0, 10^seq(-1, 3, length.out = 120))
ev_er <- data.frame(
  id = seq_along(conc_nM), time = 0, evid = 0, amt = 0,
  CEFFECT = conc_nM / 1000, DIS_HYPERT = 0
)
sim_er <- as.data.frame(rxSolve(mod, ev_er))
#> Warning: multi-subject simulation without without 'omega'
sim_er |>
  mutate(conc_nM = CEFFECT * 1000) |>
  filter(conc_nM > 0) |>
  select(conc_nM, `DBP decrease (mmHg)` = eff_dbp, `HR increase (beats/min)` = eff_hr) |>
  pivot_longer(-conc_nM) |>
  ggplot(aes(conc_nM, value)) +
  geom_line() +
  geom_vline(
    data = data.frame(
      name = c("DBP decrease (mmHg)", "HR increase (beats/min)"),
      ec50 = c(40, 50)
    ),
    aes(xintercept = ec50), linetype = "dashed"
  ) +
  facet_wrap(~name, scales = "free_y") +
  scale_x_log10() +
  labs(
    x = "Felodipine plasma concentration (nmol/L)", y = "Drug effect",
    caption = "Dashed lines: EC50 (Fuhr 2022 Table 3)."
  ) +
  theme_bw()

At 20 nmol/L (about 7.7 ng/mL), a typical peak after a 10-20 mg extended-release tablet, the model predicts a DBP decrease of 18.7 mmHg and an HR increase of 8.6 beats/min.

Replicating Figure 4

Figure 4 of Fuhr 2022 shows, for three studies, the PBPK-predicted felodipine plasma concentration (panels a-c) together with the predicted DBP (d-f) and HR (g-i) curves. Panels a, d-i are vector graphics in the article PDF. The maintainers extracted the model-prediction lines from the vector paths, with each axis calibrated on its tick marks, and resampled them onto a common time grid. Panels b and c are embedded raster images and were not digitised. The curves below are therefore the paper’s own predictions, not observed data.

Fuhr 2022 re-optimised the 24 h means and phases for every simulated study. Each replication therefore overrides the Table 3 means with that study’s Table S5 values:

  • Edgar 1987 (healthy, oral solution 5 / 15 / 40 mg): BPmean 69.57 mmHg, phase12 0.95 h, phase24 6.07 h; HRmean 56.81 bpm, phaseHR 18.48 h.
  • Blychert 1990 (hypertensive, 20 mg extended-release tablet once daily, day 15): BPmean 95.88 mmHg, phase12 1.01 h, phase24 6.13 h; HRmean 60.92 bpm, phaseHR 15.24 h.
  • Hedner 1986 (hypertensive, 5 then 10 mg tablet twice daily, day 28): BPmean 87.70 mmHg, phase12 -0.02 h, phase24 5.10 h; HRmean 66.13 bpm, phaseHR 13.63 h.

Because these per-study means replace le0_dbp and le0_hr directly, DIS_HYPERT is kept at 0 in every replication. Setting it to 1 would apply the Table 3 hypertensive/healthy ratio a second time.

edgar <- data.frame(
  time = c(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, 10.5, 11.0),
  conc_5 = c(0.00, 1.16, 3.83, 5.25, 5.55, 5.27, 4.77, 4.23, 3.73, 3.31, 2.96, 2.68, 2.45, 2.27, 2.11, 1.99, 1.88, 1.78, 1.70, 1.62, 1.55, 1.49, 1.42, 1.37, 1.32, 1.27, 1.22, 1.17, 1.13, 1.09, 1.05, 0.88, 0.75, 0.65, 0.56, 0.49, 0.43, 0.39, 0.35, 0.31, 0.29, 0.26, 0.24, 0.22, 0.21, 0.19, 0.18),
  conc_15 = c(0.00, 2.96, 10.77, 16.28, 17.51, 16.71, 15.14, 13.42, 11.82, 10.46, 9.33, 8.43, 7.70, 7.12, 6.63, 6.23, 5.88, 5.58, 5.32, 5.08, 4.86, 4.65, 4.47, 4.29, 4.12, 3.96, 3.82, 3.67, 3.54, 3.41, 3.29, 2.76, 2.35, 2.02, 1.75, 1.54, 1.36, 1.21, 1.09, 0.99, 0.90, 0.82, 0.76, 0.70, 0.65, 0.60, 0.56),
  conc_40 = c(0.00, 3.74, 15.03, 26.08, 35.68, 43.42, 48.53, 46.84, 40.45, 34.60, 29.91, 26.29, 23.54, 21.42, 19.75, 18.41, 17.31, 16.37, 15.55, 14.83, 14.17, 13.57, 13.01, 12.49, 12.00, 11.53, 11.09, 10.68, 10.29, 9.91, 9.55, 8.01, 6.79, 5.82, 5.04, 4.41, 3.90, 3.47, 3.12, 2.82, 2.56, 2.34, 2.15, 1.98, 1.83, 1.70, 1.57),
  dbp_placebo = c(73.16, 73.30, 73.44, 73.55, 73.66, 73.76, 73.85, 73.93, 74.00, 74.05, 74.09, 74.13, 74.15, 74.15, 74.15, 74.14, 74.11, 74.07, 74.03, 73.97, 73.90, 73.82, 73.73, 73.63, 73.52, 73.40, 73.28, 73.15, 73.01, 72.86, 72.70, 71.84, 70.90, 69.94, 69.03, 68.24, 67.62, 67.23, 67.07, 67.17, 67.51, 68.07, 68.80, 69.65, 70.54, 71.41, 72.19),
  dbp_5 = c(73.16, 69.79, 63.03, 60.14, 59.66, 60.30, 61.42, 62.64, 63.83, 64.86, 65.75, 66.48, 67.08, 67.56, 67.97, 68.29, 68.56, 68.79, 68.98, 69.13, 69.26, 69.36, 69.44, 69.51, 69.55, 69.58, 69.59, 69.59, 69.57, 69.54, 69.50, 69.14, 68.59, 67.95, 67.30, 66.72, 66.28, 66.02, 65.99, 66.20, 66.63, 67.27, 68.06, 68.96, 69.90, 70.82, 71.64),
  dbp_15 = c(73.16, 64.96, 51.21, 45.48, 44.54, 45.31, 46.82, 48.61, 50.48, 52.23, 53.80, 55.16, 56.32, 57.30, 58.13, 58.85, 59.46, 60.00, 60.47, 60.89, 61.27, 61.62, 61.93, 62.21, 62.47, 62.70, 62.91, 63.10, 63.27, 63.42, 63.55, 63.98, 64.10, 64.01, 63.83, 63.64, 63.52, 63.55, 63.76, 64.16, 64.77, 65.56, 66.48, 67.51, 68.55, 69.57, 70.47),
  dbp_40 = c(73.16, 63.11, 46.51, 38.83, 34.88, 32.66, 31.53, 31.99, 33.70, 35.64, 37.54, 39.29, 40.83, 42.16, 43.30, 44.29, 45.15, 45.91, 46.60, 47.23, 47.81, 48.34, 48.85, 49.34, 49.79, 50.22, 50.64, 51.03, 51.40, 51.76, 52.09, 53.54, 54.61, 55.41, 56.03, 56.55, 57.07, 57.65, 58.34, 59.18, 60.16, 61.29, 62.53, 63.82, 65.12, 66.36, 67.48),
  hr_5 = c(57.25, 57.88, 60.62, 62.22, 62.50, 62.06, 61.36, 60.62, 59.93, 59.35, 58.86, 58.46, 58.12, 57.83, 57.58, 57.36, 57.16, 56.97, 56.80, 56.64, 56.49, 56.34, 56.20, 56.07, 55.94, 55.81, 55.69, 55.57, 55.46, 55.34, 55.24, 54.75, 54.35, 54.02, 53.75, 53.56, 53.44, 53.38, 53.39, 53.47, 53.61, 53.80, 54.05, 54.36, 54.70, 55.09, 55.50),
  hr_15 = c(57.25, 59.70, 68.74, 73.88, 74.78, 74.05, 72.63, 70.96, 69.28, 67.75, 66.41, 65.28, 64.34, 63.55, 62.88, 62.30, 61.79, 61.34, 60.93, 60.54, 60.19, 59.86, 59.55, 59.26, 58.97, 58.70, 58.45, 58.20, 57.97, 57.74, 57.53, 56.58, 55.83, 55.22, 54.75, 54.40, 54.15, 53.99, 53.92, 53.93, 54.01, 54.16, 54.37, 54.64, 54.96, 55.32, 55.71),
  hr_40 = c(57.25, 60.61, 72.90, 80.36, 84.27, 86.38, 87.41, 86.97, 85.34, 83.43, 81.52, 79.73, 78.14, 76.74, 75.52, 74.46, 73.52, 72.67, 71.89, 71.16, 70.48, 69.83, 69.21, 68.61, 68.04, 67.48, 66.93, 66.42, 65.91, 65.42, 64.95, 62.83, 61.07, 59.64, 58.49, 57.58, 56.87, 56.35, 55.97, 55.73, 55.60, 55.58, 55.64, 55.77, 55.97, 56.23, 56.54)
)
blychert <- data.frame(
  time = c(336.0, 336.5, 337.0, 337.5, 338.0, 338.5, 339.0, 339.5, 340.0, 340.5, 341.0, 341.5, 342.0, 342.5, 343.0, 343.5, 344.0, 344.5, 345.0, 345.5, 346.0, 346.5, 347.0, 347.5, 348.0, 348.5, 349.0, 349.5, 350.0, 350.5, 351.0, 351.5, 352.0, 352.5, 353.0, 353.5, 354.0, 354.5, 355.0, 355.5, 356.0, 356.5, 357.0, 357.5, 358.0, 358.5, 359.0, 359.5, 360.0, 360.5, 361.0, 361.5, 362.0, 362.5, 363.0, 363.5, 364.0, 364.5, 365.0),
  dbp_placebo = c(100.71, 101.59, 102.09, 102.19, 101.90, 101.26, 100.32, 99.16, 97.87, 96.54, 95.27, 94.16, 93.28, 92.70, 92.45, 92.55, 92.98, 93.72, 94.70, 95.85, 97.08, 98.28, 99.37, 100.26, 100.87, 101.14, 101.04, 100.57, 99.73, 98.57, 97.17, 95.59, 93.95, 92.33, 90.85, 89.60, 88.66, 88.10, 87.94, 88.22, 88.90, 89.96, 91.33, 92.92, 94.63, 96.37, 98.03, 99.51, 100.71, 101.59, 102.09, 102.19, 101.90, 101.26, 100.32, 99.16, 97.87, 96.54, 95.27),
  dbp_drug = c(95.86, 93.53, 88.89, 86.00, 84.09, 82.61, 81.36, 80.26, 79.33, 78.56, 78.01, 77.71, 77.68, 77.95, 78.54, 79.42, 80.59, 81.99, 83.57, 85.25, 86.95, 88.58, 90.05, 91.28, 92.19, 92.75, 92.90, 92.66, 92.05, 91.09, 89.88, 88.49, 87.02, 85.57, 84.25, 83.15, 82.36, 81.93, 81.92, 82.32, 83.14, 84.33, 85.81, 87.53, 89.36, 91.21, 92.97, 94.56, 95.86, 96.84, 97.43, 97.62, 97.44, 96.92, 96.10, 95.04, 93.84, 92.60, 91.42),
  hr_placebo = c(58.38, 58.02, 57.72, 57.47, 57.28, 57.15, 57.09, 57.09, 57.15, 57.28, 57.48, 57.73, 58.04, 58.39, 58.79, 59.22, 59.69, 60.17, 60.67, 61.17, 61.67, 62.16, 62.62, 63.05, 63.45, 63.81, 64.11, 64.36, 64.55, 64.68, 64.75, 64.74, 64.68, 64.55, 64.35, 64.10, 63.80, 63.44, 63.04, 62.61, 62.14, 61.66, 61.16, 60.66, 60.16, 59.68, 59.21, 58.78, 58.38, 58.03, 57.72, 57.47, 57.28, 57.15, 57.09, 57.09, 57.15, 57.28, 57.48),
  hr_drug = c(59.52, 60.44, 62.85, 64.52, 65.48, 65.97, 66.15, 66.09, 65.89, 65.60, 65.28, 64.96, 64.68, 64.47, 64.34, 64.30, 64.33, 64.44, 64.61, 64.82, 65.07, 65.34, 65.62, 65.89, 66.14, 66.37, 66.56, 66.70, 66.80, 66.84, 66.82, 66.74, 66.60, 66.40, 66.14, 65.83, 65.47, 65.06, 64.61, 64.12, 63.61, 63.08, 62.53, 61.99, 61.45, 60.92, 60.42, 59.95, 59.52, 59.13, 58.80, 58.52, 58.29, 58.12, 58.02, 57.99, 58.02, 58.12, 58.29)
)
hedner <- data.frame(
  time = c(647.0, 647.5, 648.0, 648.5, 649.0, 649.5, 650.0, 650.5, 651.0, 651.5, 652.0, 652.5, 653.0, 653.5, 654.0, 654.5, 655.0, 655.5, 656.0, 656.5, 657.0, 657.5, 658.0, 658.5, 659.0, 659.5, 660.0, 660.5, 661.0, 661.5, 662.0, 662.5, 663.0, 663.5, 664.0, 664.5, 665.0, 665.5, 666.0, 666.5, 667.0, 667.5, 668.0, 668.5, 669.0, 669.5, 670.0, 670.5, 671.0, 671.5, 672.0, 672.5, 673.0),
  dbp_placebo = c(92.17, 92.96, 93.40, 93.47, 93.19, 92.59, 91.72, 90.65, 89.46, 88.25, 87.09, 86.09, 85.30, 84.77, 84.56, 84.67, 85.08, 85.77, 86.68, 87.73, 88.86, 89.95, 90.94, 91.75, 92.29, 92.52, 92.41, 91.96, 91.18, 90.11, 88.81, 87.36, 85.86, 84.38, 83.04, 81.91, 81.07, 80.57, 80.45, 80.72, 81.37, 82.35, 83.61, 85.08, 86.65, 88.24, 89.74, 91.08, 92.18, 92.96, 93.40, 93.47, 93.19),
  dbp_drug = c(89.31, 90.26, 90.85, 84.40, 77.32, 75.46, 75.54, 76.12, 76.65, 77.02, 77.23, 77.39, 77.58, 77.89, 78.37, 79.07, 79.99, 81.10, 82.38, 83.75, 85.16, 86.50, 87.71, 88.71, 89.43, 89.82, 89.87, 89.55, 88.90, 87.95, 86.77, 85.43, 84.02, 82.64, 81.38, 80.33, 79.57, 79.15, 79.10, 79.44, 80.15, 81.19, 82.51, 84.03, 85.65, 87.29, 88.84, 90.23, 91.36, 92.18, 92.66, 92.77, 92.52),
  hr_placebo = c(62.91, 62.59, 62.34, 62.14, 62.02, 61.96, 61.98, 62.07, 62.23, 62.45, 62.74, 63.08, 63.48, 63.92, 64.40, 64.91, 65.44, 65.98, 66.53, 67.07, 67.59, 68.08, 68.55, 68.97, 69.34, 69.66, 69.92, 70.11, 70.24, 70.29, 70.27, 70.19, 70.03, 69.80, 69.52, 69.17, 68.78, 68.33, 67.85, 67.34, 66.81, 66.27, 65.73, 65.19, 64.67, 64.17, 63.71, 63.29, 62.91, 62.59, 62.34, 62.14, 62.02),
  hr_drug = c(63.45, 63.08, 62.79, 65.02, 68.85, 69.67, 69.02, 68.02, 67.11, 66.44, 66.00, 65.78, 65.74, 65.83, 66.03, 66.32, 66.67, 67.06, 67.48, 67.92, 68.36, 68.78, 69.18, 69.55, 69.88, 70.15, 70.37, 70.53, 70.62, 70.65, 70.61, 70.49, 70.32, 70.07, 69.77, 69.40, 68.99, 68.53, 68.04, 67.52, 66.97, 66.42, 65.87, 65.32, 64.79, 64.28, 63.81, 63.38, 63.00, 62.68, 62.42, 62.22, 62.09)
)
# Override the cross-study Table 3 means with one study's Table S5 values.
study_model <- function(bp_mean, phase12, phase24, hr_mean, phase_hr) {
  rxode2::ini(
    mod,
    le0_dbp = log(bp_mean), phase12_dbp = phase12, phase24_dbp = phase24,
    le0_hr = log(hr_mean), phase_hr = phase_hr
  )
}
mod_edgar <- study_model(69.57, 0.95, 6.07, 56.81, 18.48)
#> ℹ change initial estimate of `le0_dbp` to `4.24233343993555`
#> ℹ change initial estimate of `phase12_dbp` to `0.95`
#> ℹ change initial estimate of `phase24_dbp` to `6.07`
#> ℹ change initial estimate of `le0_hr` to `4.03971236656904`
#> ℹ change initial estimate of `phase_hr` to `18.48`
mod_blychert <- study_model(95.88, 1.01, 6.13, 60.92, 15.24)
#> ℹ change initial estimate of `le0_dbp` to `4.56309740956618`
#> ℹ change initial estimate of `phase12_dbp` to `1.01`
#> ℹ change initial estimate of `phase24_dbp` to `6.13`
#> ℹ change initial estimate of `le0_hr` to `4.10956152802696`
#> ℹ change initial estimate of `phase_hr` to `15.24`
mod_hedner <- study_model(87.70, -0.02, 5.10, 66.13, 13.63)
#> ℹ change initial estimate of `le0_dbp` to `4.47392189937814`
#> ℹ change initial estimate of `phase12_dbp` to `-0.02`
#> ℹ change initial estimate of `phase24_dbp` to `5.1`
#> ℹ change initial estimate of `le0_hr` to `4.19162250168657`
#> ℹ change initial estimate of `phase_hr` to `13.63`

Healthy volunteers: Edgar 1987 (panels a, d, g)

The digitised predicted plasma concentrations of panel a (ng/mL, converted to umol/L with the Table 2 molecular weight) drive the model directly. Placebo is CEFFECT = 0. DIS_HYPERT stays 0 here because the study-specific means already replace the Table 3 values.

edgar_long <- edgar |>
  pivot_longer(-time, names_to = c("var", "arm"), names_sep = "_") |>
  pivot_wider(names_from = var, values_from = value)
arms <- c("placebo", "5", "15", "40")
# The paper's curves are kept out of the event table so that their dbp / hr
# columns cannot be mistaken for model inputs.
ev_edgar <- edgar_long |>
  transmute(
    id = match(arm, arms), time,
    CEFFECT = ifelse(arm == "placebo", 0, conc / mw),
    evid = 0, amt = 0, DIS_HYPERT = 0
  ) |>
  arrange(id, time)
sim_edgar <- as.data.frame(rxSolve(mod_edgar, ev_edgar)) |>
  mutate(arm = arms[id])
#> Warning: multi-subject simulation without without 'omega'

cmp_edgar <- edgar_long |>
  select(arm, time, dbp_paper = dbp, hr_paper = hr) |>
  left_join(select(sim_edgar, arm, time, dbp, hr), by = c("arm", "time")) |>
  mutate(dbp_diff = dbp - dbp_paper, hr_diff = hr - hr_paper)
stopifnot(nrow(cmp_edgar) == nrow(ev_edgar), !anyNA(cmp_edgar$dbp))

# Panel g draws no HR placebo curve, so that arm has no HR comparison.
max_abs <- function(x) if (all(is.na(x))) NA_real_ else max(abs(x), na.rm = TRUE)
edgar_summary <- cmp_edgar |>
  mutate(arm = factor(arm, levels = arms)) |>
  group_by(arm) |>
  summarise(
    `DBP max abs diff (mmHg)` = max_abs(dbp_diff),
    `DBP median diff (mmHg)` = median(dbp_diff),
    `HR max abs diff (bpm)` = max_abs(hr_diff),
    `HR median diff (bpm)` = median(hr_diff, na.rm = TRUE),
    .groups = "drop"
  ) |>
  rename(`Dose (mg)` = arm)
knitr::kable(edgar_summary, digits = 2)
Dose (mg) DBP max abs diff (mmHg) DBP median diff (mmHg) HR max abs diff (bpm) HR median diff (bpm)
placebo 0.19 -0.09 NA NA
5 0.91 -0.56 0.32 0.06
15 0.99 -0.89 0.67 0.23
40 1.09 -0.87 0.67 0.53
cmp_edgar |>
  select(arm, time, dbp, dbp_paper, hr, hr_paper) |>
  pivot_longer(c(dbp, dbp_paper, hr, hr_paper)) |>
  filter(!is.na(value)) |>
  mutate(
    endpoint = ifelse(grepl("^dbp", name), "Diastolic BP (mmHg)", "Heart rate (beats/min)"),
    source = ifelse(grepl("paper", name), "Fuhr 2022 Figure 4 (digitised)", "nlmixr2lib")
  ) |>
  ggplot(aes(time, value, colour = arm, linetype = source)) +
  geom_line() +
  facet_wrap(~endpoint, ncol = 1, scales = "free_y") +
  labs(x = "Time (h)", y = NULL, colour = "Dose (mg)", linetype = NULL) +
  theme_bw() +
  theme(legend.position = "bottom", legend.box = "vertical")

Replicates Figure 4 panels d and g of Fuhr 2022.

The packaged model reproduces the paper’s DBP curves to within about 1 mmHg and its HR curves to within about 0.7 beats/min. The DBP curves carry a small systematic offset (the packaged model predicts slightly more decrease) that is absent from the placebo curve, so it comes from the drug term. The likely cause is rounding: Table 3 prints EC50 as 0.04 umol/L. Refitting EC50 to the digitised curves with Emax held at 56.18 gives about 0.044 umol/L, inside the rounding interval 0.035-0.045. The maintainers did not adjust any value; the printed parameters are packaged as published. The same refit for HR recovers h = 1.40, EC50 = 0.052 umol/L and Emax = 39.6 beats/min, all consistent with Table 3. Using h = 1.4 for DBP as well would make the DBP curves miss by up to 4.5 mmHg. That supports reading Table 3 as having no Hill coefficient for DBP (h = 1).

# Deterministic: the only differences are digitisation error and the 2-dp
# rounding of EC50. Measured maxima 1.10 mmHg (DBP) and 0.67 bpm (HR). A
# wrong Hill coefficient (h = 1.4 for DBP) gives 4.5 mmHg, and a
# percent-vs-fraction slip on an amplitude gives tens of mmHg.
stopifnot(
  max(abs(cmp_edgar$dbp_diff)) < 2,
  max(abs(cmp_edgar$dbp_diff[cmp_edgar$arm == "placebo"])) < 0.5,
  sum(!is.na(cmp_edgar$hr_diff)) == 3 * nrow(edgar), # three dosed HR arms
  max(abs(cmp_edgar$hr_diff), na.rm = TRUE) < 1.5
)

Hypertensive patients: Blychert 1990 and Hedner 1986 (panels e, f, h, i)

Panels b and c (the plasma concentrations for these two studies) are raster images, so the drug curves cannot be driven from a digitised concentration. Two checks remain:

  1. Placebo curves, which depend only on the circadian layer and the Table S5 parameters (CEFFECT = 0).
  2. Cross-endpoint consistency. The drug-minus-placebo DBP difference of panel e (or f) is inverted through the DBP Emax function to the plasma concentration it implies. That concentration then drives the HR model, and the result is compared with the drug HR curve of panel h (or i). This tests whether the DBP and HR curves come from one concentration through the Table 3 Emax parameters, and whether hypertensive patients share those parameters.
check_ht <- function(dig, study_mod, study) {
  e_dbp <- pmax(dig$dbp_placebo - dig$dbp_drug, 0)
  conc_implied <- 0.04 * e_dbp / (56.18 - e_dbp) # umol/L, inverse of Eq. S20
  ev <- bind_rows(
    data.frame(id = 1L, time = dig$time, CEFFECT = 0),
    data.frame(id = 2L, time = dig$time, CEFFECT = conc_implied)
  ) |>
    # DIS_HYPERT stays 0: the study-specific Table S5 means passed to
    # study_model() already are the hypertensive values, and setting it to 1
    # would apply the Table 3 hypertensive ratio on top of them.
    mutate(evid = 0, amt = 0, DIS_HYPERT = 0)
  sim <- as.data.frame(rxSolve(study_mod, ev))
  stopifnot(nrow(sim) == 2 * nrow(dig))
  data.frame(
    study = study,
    time = dig$time,
    conc_implied_ngmL = conc_implied * mw,
    dbp_placebo_paper = dig$dbp_placebo,
    dbp_placebo = sim$dbp[sim$id == 1],
    hr_placebo_paper = dig$hr_placebo,
    hr_placebo = sim$hr[sim$id == 1],
    hr_drug_paper = dig$hr_drug,
    hr_drug = sim$hr[sim$id == 2]
  )
}
cmp_ht <- bind_rows(
  check_ht(blychert, mod_blychert, "Blychert 1990"),
  check_ht(hedner, mod_hedner, "Hedner 1986")
)
#> Warning: multi-subject simulation without without 'omega'
#> Warning: multi-subject simulation without without 'omega'
ht_summary <- cmp_ht |>
  group_by(study) |>
  summarise(
    `Placebo DBP max abs diff (mmHg)` = max(abs(dbp_placebo - dbp_placebo_paper)),
    `Placebo HR max abs diff (bpm)` = max(abs(hr_placebo - hr_placebo_paper)),
    `Drug HR max abs diff (bpm)` = max(abs(hr_drug - hr_drug_paper)),
    `Implied peak conc (ng/mL)` = max(conc_implied_ngmL),
    .groups = "drop"
  ) |>
  rename(Study = study)
knitr::kable(ht_summary, digits = 2)
Study Placebo DBP max abs diff (mmHg) Placebo HR max abs diff (bpm) Drug HR max abs diff (bpm) Implied peak conc (ng/mL)
Blychert 1990 0.26 0.01 0.26 7.83
Hedner 1986 0.25 0.01 0.26 6.74

# Deterministic. Measured maxima: placebo DBP 0.26 mmHg, placebo HR 0.01 bpm,
# drug HR 0.26 bpm.
stopifnot(
  all(ht_summary$`Placebo DBP max abs diff (mmHg)` < 0.5),
  all(ht_summary$`Placebo HR max abs diff (bpm)` < 0.5),
  all(ht_summary$`Drug HR max abs diff (bpm)` < 1)
)
cmp_ht |>
  select(study, time, dbp_placebo, dbp_placebo_paper, hr_placebo, hr_placebo_paper, hr_drug, hr_drug_paper) |>
  pivot_longer(-c(study, time)) |>
  mutate(
    source = ifelse(grepl("paper", name), "Fuhr 2022 Figure 4 (digitised)", "nlmixr2lib"),
    curve = sub("_paper", "", name)
  ) |>
  ggplot(aes(time, value, colour = curve, linetype = source)) +
  geom_line() +
  facet_wrap(~study, scales = "free", ncol = 1) +
  labs(x = "Time (h)", y = "mmHg or beats/min", colour = NULL, linetype = NULL) +
  theme_bw() +
  theme(legend.position = "bottom", legend.box = "vertical")

Replicates the placebo curves of Figure 4 panels e, f, h and i, and the drug curves of panels h and i using the concentration implied by panels e and f. The implied peak concentrations (about 7.8 ng/mL for Blychert 1990 and 6.7 ng/mL for Hedner 1986) agree with the predicted peaks shown in panels b and c (about 8 and 7 ng/mL). Both checks pass: the circadian layer is reproduced exactly, and the DBP and HR models are mutually consistent in hypertensive patients with the shared Table 3 Emax parameters.

Non-compartmental analysis

Not applicable. The packaged model has no PK layer and no dose records: felodipine concentration is an input (CEFFECT), and the paper reports no NCA of the PD endpoints. The deterministic checks and the Figure 4 replication above take the place of the PKNCA comparison.

Assumptions and deviations

  • PD layer only. The PK-Sim / MoBi whole-body PBPK layer (Table 2) is not reproduced, because its organ volumes, blood flows, partition coefficients and CYP3A4 expression profile are platform database outputs the paper does not print. The felodipine plasma concentration must be supplied as CEFFECT in umol/L. Divide ng/mL by 384.25 (Table 2 molecular weight), or nmol/L by 1000. The DDI scenarios of the paper (Section 3.3) act through the PBPK layer. With this model they are reproduced only by supplying the DDI-altered plasma concentrations.
  • Hill coefficient for DBP. Equation 1 / S20 is written with a Hill coefficient h, but Table 3 and Table S6 list h for the HR model only. The DBP Emax function is therefore hyperbolic (h = 1). The Figure 4 replication above supports this reading: h = 1 reproduces panel d to within about 1 mmHg, h = 1.4 misses by up to 4.5 mmHg.
  • Amplitudes are fractions. Table 3 reports the amplitudes in percent (2.14 %, 5.93 %, 6.3 %). Equations S17 and S19 multiply the 24 h mean by (1 + circ), so they are entered as fractions (0.0214, 0.0593, 0.063). The exact match to the placebo curves confirms the scale.
  • Time origin of the phases. Fuhr 2022 optimised the circadian phases against each study’s own simulation time (Figure 4 time axes run from the first dose, e.g. 336-366 h for day 15). time in this model is that simulation clock in hours, and the phases are relative to its origin, not to midnight. The published phases are not clock times. The Table 3 typical phases (5.28 h, 0.16 h and 11.8 h) are means over studies whose dosing clock times differed, so they describe a generic study only. To reproduce a specific study, override them with its Table S5 values, as done above.
  • Study-specific circadian parameters. The 24 h means and phases were re-optimised for every simulated study (Table S5); Table 3 reports their means (SD). The packaged typical values are the Table 3 means. The SDs (e.g. 6.76 mmHg for BPmean) describe between-study spread of the optimised values, not between-subject variability, and are not encoded as random effects. The Table 3 means could not be recomputed exactly from the Table S5 rows (for example, the healthy-volunteer BPmean rows average 70.9 mmHg against the printed 70.4 mmHg), probably because the summary also covers simulations not listed in Table S5. The printed Table 3 values are used.
  • Hypertension. Table 3 gives separate 24 h means for healthy and hypertensive individuals (footnote b). These are encoded through the DIS_HYPERT indicator as log-ratio shifts that reproduce the printed values exactly. The paper also reduced CYP3A4 kcat by 32 % on average in hypertensive individuals (Results 3.1). That is a PBPK-layer effect and reaches this model only through CEFFECT.
  • No variability. No inter-individual variability and no residual-error model are reported. Both are zero (addSd_dbp and addSd_hr fixed at 0), so the model gives typical-value predictions of mean DBP and HR.
  • No tolerance. The paper notes that HR tolerance during multiple dosing could not be modelled for lack of data (Discussion). The HR effect is therefore the same on every dosing day.
  • Systolic blood pressure was not modelled by the paper (Discussion).
  • Digitisation. The Figure 4 curves were extracted from the article’s vector graphics, with each axis calibrated on its tick marks. Digitisation error is a small fraction of 1 mmHg / 1 beat per minute, judged by the 0.01 bpm agreement on the HR placebo curves.