library(shiny)
library(bslib)
library(mrgsolve)
library(dplyr)
library(ggplot2)
library(tidyr)
# =============================================================================
# Losartan & EXP-3174 Parent-Metabolite PopPK Model
# Sinusoidal Gastric Emptying (Karatza & Karalis, Basic Clin Pharmacol Toxicol 2020)
#
# Losartan: 2-compartment with pre-absorption (stomach + small intestine)
# EXP-3174: 1-compartment with delayed first-order formation from losartan central
# Gastric emptying: kg(t) = a*sin(b*t) when sin(b*t) > 0, else 0
# =============================================================================
model_code <- '
$PARAM @annotated
a : 4.56 : GE amplitude (1/h)
b : 3.95 : GE frequency 2pi/period (1/h)
ka : 1.93 : Absorption rate constant (1/h)
Vp1 : 43.7 : Losartan central volume/F (L)
Vp2 : 1160 : Losartan peripheral volume/F (L)
Qp : 129 : Losartan intercompartmental CL/F (L/h)
CLp : 184 : Losartan clearance/F (L/h)
km : 0.452 : Metabolite formation rate (1/h)
Tlag : 0.271 : Delay on metabolite formation (h)
Vm : 9.66 : EXP-3174 volume/F (L)
CLm : 5.5 : EXP-3174 clearance/F (L/h)
$CMT @annotated
STOMACH : Stomach (nmol)
SI : Small intestine (nmol)
CENT : Losartan central (nmol)
PERI : Losartan peripheral (nmol)
TR1 : Transit 1 for delay (nmol)
TR2 : Transit 2 for delay (nmol)
TR3 : Transit 3 for delay (nmol)
TR4 : Transit 4 for delay (nmol)
TR5 : Transit 5 for delay (nmol)
MET : EXP-3174 central (nmol)
$ODE
// Sinusoidal gastric emptying: kg(t) = a*sin(b*t) when positive, else 0
double sinval = sin(b * SOLVERTIME);
double kg = (sinval > 0) ? a * sinval : 0;
// Micro-rate constants
double kelp = CLp / Vp1;
double k12 = Qp / Vp1;
double k21 = Qp / Vp2;
double kelm = CLm / Vm;
double ktr = 5.0 / Tlag; // 5 transit compartments for delay approximation
// Stomach -> Small intestine (pulsatile via sinusoidal GE)
dxdt_STOMACH = -kg * STOMACH;
// Small intestine -> Central (first-order absorption)
dxdt_SI = kg * STOMACH - ka * SI;
// Losartan central: absorption in, distribution, metabolism, elimination
dxdt_CENT = ka * SI - km * CENT - k12 * CENT + k21 * PERI - kelp * CENT;
// Losartan peripheral
dxdt_PERI = k12 * CENT - k21 * PERI;
// Transit chain approximating the metabolite formation delay T
dxdt_TR1 = km * CENT - ktr * TR1;
dxdt_TR2 = ktr * TR1 - ktr * TR2;
dxdt_TR3 = ktr * TR2 - ktr * TR3;
dxdt_TR4 = ktr * TR3 - ktr * TR4;
dxdt_TR5 = ktr * TR4 - ktr * TR5;
// EXP-3174 central compartment
dxdt_MET = ktr * TR5 - kelm * MET;
$TABLE
// Concentrations in ng/mL
// CENT/Vp1 = nmol/L = pmol/mL; multiply by MW/1000 to get ng/mL
double CP_los = CENT / Vp1 * 422.9 / 1000.0;
double CP_met = MET / Vm * 436.9 / 1000.0;
$CAPTURE CP_los CP_met
'
mod <- mcode("losartan_exp3174", model_code)
# --- Theme ---
app_theme <- bs_theme(
version = 5, bootswatch = "flatly", primary = "#8b5cf6"
) |> bs_add_rules("
.metric-card { background: #f8f9fa; border-radius: 8px; padding: 15px; margin: 5px; text-align: center; border: 1px solid #dee2e6; }
.metric-value { font-size: 24px; font-weight: bold; color: #2c3e50; }
.metric-label { font-size: 12px; color: #7f8c8d; }
.metric-success .metric-value { color: #10b981; }
.metric-warning .metric-value { color: #f59e0b; }
.metric-primary .metric-value { color: #8b5cf6; }
.metric-info .metric-value { color: #0dcaf0; }
.ref-box { background: #f0f4ff; border-left: 4px solid #8b5cf6; padding: 12px 16px; border-radius: 4px; margin-top: 10px; font-size: 13px; }
.ref-box a { color: #8b5cf6; }
")
# --- UI ---
ui <- page_sidebar(
title = "Losartan & EXP-3174 PK Simulator",
theme = app_theme,
sidebar = sidebar(
title = "Simulation Settings", width = 340,
h6("Dosing"),
selectInput("dose", "Dose (mg losartan potassium)",
choices = c("25 mg" = 25, "50 mg" = 50, "100 mg" = 100), selected = 100),
selectInput("interval", "Dosing Frequency",
choices = c("Once daily (QD)" = 24, "Twice daily (BID)" = 12), selected = 24),
numericInput("n_days", "Duration (days)", value = 1, min = 1, max = 7),
hr(), h6("Gastric Emptying"),
sliderInput("ge_amp", HTML("GE Amplitude (h−1)"),
min = 0.5, max = 10, value = 4.56, step = 0.1),
sliderInput("ge_period", "GE Cycle Period (h)",
min = 0.5, max = 4, value = round(2 * pi / 3.95, 2), step = 0.05),
helpText("Adjust gastric emptying parameters to explore oscillatory PK behavior.
Higher amplitude = stronger GE pulses. Longer period = wider spacing between peaks."),
hr(),
checkboxInput("show_ge", "Show Gastric Emptying Rate", value = FALSE),
checkboxInput("log_scale", "Log Scale (Y-axis)", value = FALSE)
),
navset_card_tab(
title = "Losartan & EXP-3174 PK Simulator",
full_screen = TRUE,
nav_panel("Simulation",
layout_column_wrap(
width = 1/4,
fill = FALSE,
div(class = "metric-card metric-success",
div(class = "metric-value", textOutput("cmax_los")),
div(class = "metric-label", "Losartan Cmax (ng/mL)")),
div(class = "metric-card metric-info",
div(class = "metric-value", textOutput("cmax_met")),
div(class = "metric-label", "EXP-3174 Cmax (ng/mL)")),
div(class = "metric-card metric-primary",
div(class = "metric-value", textOutput("auc_los")),
div(class = "metric-label", HTML("Losartan AUC0-τ (ng·h/mL)"))),
div(class = "metric-card metric-warning",
div(class = "metric-value", textOutput("auc_met")),
div(class = "metric-label", HTML("EXP-3174 AUC0-τ (ng·h/mL)")))
),
plotOutput("pkPlot", height = "500px"),
conditionalPanel(
condition = "input.show_ge",
plotOutput("gePlot", height = "200px")
)
),
nav_panel("Model Information", markdown("
## Losartan & EXP-3174 — Parent-Metabolite PopPK Model with Sinusoidal Gastric Emptying
**Drug:** Losartan potassium (angiotensin II receptor blocker / ARB)
**Active metabolite:** EXP-3174 (10× more potent than losartan)
**Study:** 31 healthy volunteers, single 100 mg oral dose, fasted state (crossover bioequivalence study)
### Model Structure
- **Losartan:** 2-compartment model with pre-absorption compartment (stomach → small intestine → central ↔ peripheral)
- **EXP-3174:** 1-compartment model with delayed first-order formation from losartan central compartment
- **Gastric emptying:** Sinusoidal function: kg(t) = a·sin(b·t) when sin(b·t) > 0, otherwise 0 (closed pyloric valve)
### Key Parameters (Population Estimates)
| Parameter | Value | Description |
|-----------|-------|-------------|
| a | 4.56 h−1 | GE amplitude |
| b | 3.95 h−1 | GE frequency (2π/period) |
| ka | 1.93 h−1 | Absorption rate constant |
| Vp1/F | 43.7 L | Losartan central volume of distribution |
| Vp2/F | 1160 L | Losartan peripheral volume of distribution |
| Q/F | 129 L/h | Losartan inter-compartmental clearance |
| CL/F | 184 L/h | Losartan apparent clearance |
| km | 0.452 h−1 | Metabolite formation rate constant |
| T | 0.271 h | Delay on metabolite formation |
| Vm/F | 9.66 L | EXP-3174 volume of distribution |
| CLm/F | 5.5 L/h | EXP-3174 apparent clearance |
### Sinusoidal Gastric Emptying
The hallmark of this model is the use of a sinusoidal function to describe pulsatile gastric emptying in the fasted state.
The gastric emptying rate constant oscillates between 0 and **a** (amplitude) with a cycle period of **2π/b** (≈ 1.59 h).
During the positive phase of the sine wave, the pyloric valve opens and drug empties from the stomach into the small intestine.
During the negative phase, the valve is closed (kg = 0).
This mechanism explains the **multiple peaks** (secondary maxima) observed in losartan plasma concentration profiles
after oral administration in the fasted state — a phenomenon driven by the migrating motor complex (MMC).
### Clinical Context
- **Indication:** Hypertension, diabetic nephropathy, stroke risk reduction
- **Oral bioavailability:** ~33% (extensive first-pass metabolism)
- **~14% of dose** converted to EXP-3174 via CYP2C9 and CYP3A4
- **BCS Class I** — high solubility, high permeability (GE is rate-determining for absorption)
- **Protein binding:** 98.7% (losartan), 99.8% (EXP-3174)
### Implementation Note
The metabolite formation delay (T = 0.271 h) is approximated using a 5-compartment transit chain in the mrgsolve implementation,
as delay differential equations are not natively supported. This provides a close approximation of the original DDE-based model.
")),
nav_panel("References", div(class = "ref-box",
tags$h5(HTML("📚 Key References")),
tags$ol(
tags$li("Karatza E, Karalis V. (2020) Modelling gastric emptying: A pharmacokinetic model simultaneously describing distribution of losartan and its active metabolite EXP-3174. ",
tags$em("Basic Clin Pharmacol Toxicol"), " 126:193-202. ",
tags$a(href = "https://doi.org/10.1111/bcpt.13321", target = "_blank", "doi:10.1111/bcpt.13321")),
tags$li("Lo MW et al. (1995) Pharmacokinetics of losartan, an angiotensin II receptor antagonist, and its active metabolite EXP3174 in humans. ",
tags$em("Clin Pharmacol Ther"), " 58:641-649."),
tags$li("Sica DA et al. (2005) Clinical pharmacokinetics of losartan. ",
tags$em("Clin Pharmacokinet"), " 44:797-814.")
),
tags$h5(HTML("💊 Therapeutic Context")),
tags$ul(
tags$li(tags$strong("Class:"), " Angiotensin II Receptor Blocker (ARB)"),
tags$li(tags$strong("Doses:"), " 25-100 mg once daily"),
tags$li(tags$strong("Route:"), " Oral"),
tags$li(tags$strong("Metabolism:"), " CYP2C9 (primary), CYP3A4 → EXP-3174 (active metabolite, 10× more potent)"),
tags$li(tags$strong("Protein binding:"), " 98.7% (losartan), 99.8% (EXP-3174)"),
tags$li(tags$strong("Half-life:"), " ~2 h (losartan), ~6-9 h (EXP-3174)"),
tags$li(tags$strong("Excretion:"), " ~35% urine, ~58% feces")
)
))
),
div(style = "text-align: center; padding: 20px; margin-top: 30px; border-top: 1px solid #e9ecef; color: #6c757d; font-size: 12px;",
"Powered by ", tags$a(href = "https://www.pkpdbuilder.com", target = "_blank", style = "color: #8b5cf6; font-weight: 500;", "PKPDBuilder.com"),
" • Built by Sunny ☀️ (Husain Attarwala's AI Assistant)", br(),
tags$span(style = "font-size: 10px;", "For research and educational purposes only. Not for clinical decision-making."))
)
# --- Server ---
server <- function(input, output, session) {
sim_data <- reactive({
dose_mg <- as.numeric(input$dose)
interval <- as.numeric(input$interval)
dose_nmol <- dose_mg * 1e6 / 461.01
b_val <- 2 * pi / input$ge_period
n_doses <- max(1, round(input$n_days * 24 / interval))
ev1 <- ev(amt = dose_nmol, cmt = 1, ii = interval, addl = n_doses - 1)
mod %>%
param(a = input$ge_amp, b = b_val) %>%
ev(ev1) %>%
mrgsim(end = input$n_days * 24, delta = 0.05, maxsteps = 50000) %>%
as.data.frame() %>%
mutate(time_h = time)
})
# --- Metric cards ---
output$cmax_los <- renderText({
d <- sim_data()
sprintf("%.1f", max(d$CP_los, na.rm = TRUE))
})
output$cmax_met <- renderText({
d <- sim_data()
sprintf("%.1f", max(d$CP_met, na.rm = TRUE))
})
output$auc_los <- renderText({
d <- sim_data()
interval <- as.numeric(input$interval)
# Use last dosing interval for steady-state metrics, or full profile for single dose
if (input$n_days == 1 && interval >= 24) {
last <- d
} else {
last_start <- (input$n_days - 1) * 24
last <- d %>% filter(time >= last_start, time <= last_start + interval)
}
if (nrow(last) < 2) return("--")
auc <- sum(diff(last$time) * (head(last$CP_los, -1) + tail(last$CP_los, -1)) / 2)
sprintf("%.0f", auc)
})
output$auc_met <- renderText({
d <- sim_data()
interval <- as.numeric(input$interval)
if (input$n_days == 1 && interval >= 24) {
last <- d
} else {
last_start <- (input$n_days - 1) * 24
last <- d %>% filter(time >= last_start, time <= last_start + interval)
}
if (nrow(last) < 2) return("--")
auc <- sum(diff(last$time) * (head(last$CP_met, -1) + tail(last$CP_met, -1)) / 2)
sprintf("%.0f", auc)
})
# --- PK Plot ---
output$pkPlot <- renderPlot({
d <- sim_data()
d_long <- d %>%
select(time_h, Losartan = CP_los, `EXP-3174` = CP_met) %>%
pivot_longer(-time_h, names_to = "Compound", values_to = "Concentration")
p <- ggplot(d_long, aes(x = time_h, y = Concentration, color = Compound, linetype = Compound)) +
geom_line(linewidth = 0.9) +
scale_color_manual(values = c("Losartan" = "#8b5cf6", "EXP-3174" = "#14b8a6")) +
scale_linetype_manual(values = c("Losartan" = "solid", "EXP-3174" = "longdash")) +
labs(
x = "Time (hours)",
y = "Concentration (ng/mL)",
title = paste0("Losartan ", input$dose, " mg — ",
ifelse(as.numeric(input$interval) == 24, "QD", "BID"),
" (", input$n_days, ifelse(input$n_days == 1, " day)", " days)")),
color = NULL, linetype = NULL
) +
theme_minimal(base_size = 14) +
theme(
legend.position = "top",
legend.text = element_text(size = 12),
plot.title = element_text(face = "bold")
)
if (input$log_scale) {
p <- p + scale_y_log10()
}
p
})
# --- GE Rate Plot ---
output$gePlot <- renderPlot({
d <- sim_data()
ggplot(d, aes(x = time_h, y = kg_rate)) +
geom_area(fill = "#f59e0b", alpha = 0.25) +
geom_line(color = "#f59e0b", linewidth = 0.6) +
labs(
x = "Time (hours)",
y = expression(paste("GE Rate (", h^{-1}, ")")),
title = paste0("Gastric Emptying Rate — Period: ", input$ge_period, "h, Amplitude: ", input$ge_amp, " h\u207B\u00B9")
) +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold", size = 13))
})
}
shinyApp(ui = ui, server = server)