Nothing
rxTest({
# -- Analytical background --------------------------------------------------
# dur(depot) <- dur0 + state sets the infusion DURATION directly (rate=-2).
# The infusion rate is derived as: R = amt / (dur0 + state_at_start)
#
# For a depot with first-order drain rate ka and constant state S0:
#
# D = dur0 + S0 (infusion duration)
# R = amt / D (derived rate)
#
# During infusion (0 <= t <= D):
# depot(t) = (R/ka) * (1 - exp(-ka*t))
#
# After infusion (t > D):
# depot(t) = depot(D) * exp(-ka*(t-D))
# = (R/ka) * (1-exp(-ka*D)) * exp(-ka*(t-D))
# -- Helper -----------------------------------------------------------------
.depotAnalytic <- function(t, R, D, ka) {
vapply(t, function(ti) {
if (ti <= 0) return(0)
if (ti <= D) {
(R / ka) * (1 - exp(-ka * ti))
} else {
depot_D <- (R / ka) * (1 - exp(-ka * D))
depot_D * exp(-ka * (ti - D))
}
}, numeric(1))
}
# -- Test 1 -----------------------------------------------------------------
# state = 0 throughout -> dur(depot) = dur0 exactly.
# Infusion duration = dur0; rate = amt/dur0; trajectory is the standard
# fixed-duration analytical solution.
test_that("state-dep dur with state=0 uses exactly dur0 (no state shift)", {
mod <- rxode2({
# Infusion duration is modelled as dur0 + state.
# With state held at 0 this collapses to the fixed-duration case.
dur(depot) <- dur0 + state
d/dt(depot) <- -ka * depot
d/dt(state) <- 0 # constant state; initial value is the only value
})
amt <- 100
dur0 <- 10
ka <- 0.5
D <- dur0 # state=0 -> effective duration = dur0
R <- amt / D # derived rate
times <- seq(0, 30, by = 0.5)
et <- eventTable() |>
add.dosing(dose = amt, rate = -2) |> # rate=-2 -> use modelled duration
add.sampling(times)
s <- solve(mod, et,
inits = c(state = 0, depot = 0),
params = c(dur0 = dur0, ka = ka))
expected <- .depotAnalytic(times, R, D, ka)
# Every sampled depot value must match the analytical trajectory computed
# with duration = dur0. A mismatch means the solver used a wrong duration.
expect_equal(s$depot[s$time %in% times], expected,
tolerance = 1e-3,
label = "depot matches dur0-only analytical solution when state=0")
})
# -- Test 2 -----------------------------------------------------------------
# state = S0 (constant) -> dur(depot) = dur0 + S0; rate = amt/(dur0+S0).
# The full depot trajectory must match the closed-form solution for that rate.
test_that("state-dep dur shifts infusion duration by state value: dur = dur0 + state_at_start", {
mod <- rxode2({
# Duration includes a state-variable contribution.
# With d/dt(state)=0 the state stays at its initial value; the expected
# duration is analytically known.
dur(depot) <- dur0 + state
d/dt(depot) <- -ka * depot
d/dt(state) <- 0
})
amt <- 100
dur0 <- 10
ka <- 0.5
times <- seq(0, 40, by = 0.5)
for (S0 in c(2, 5, -3)) {
D <- dur0 + S0 # effective duration at the moment the infusion starts
R <- amt / D # derived infusion rate
et <- eventTable() |>
add.dosing(dose = amt, rate = -2) |>
add.sampling(times)
s <- solve(mod, et,
inits = c(state = S0, depot = 0),
params = c(dur0 = dur0, ka = ka))
expected <- .depotAnalytic(times, R, D, ka)
# The solver must use duration = dur0 + S0, not dur0 alone.
# Verifying the full trajectory confirms both the duration magnitude and
# the derived rate (R = amt/(dur0+S0)).
expect_equal(s$depot[s$time %in% times], expected,
tolerance = 1e-3,
label = paste0("depot matches analytic solution for dur=",
D, " (S0=", S0, ")"))
# Confirm the trajectory with S0!=0 differs from the S0=0 baseline,
# i.e., the state contribution to duration is not silently ignored.
if (S0 != 0) {
expected_s0 <- .depotAnalytic(times, amt / dur0, dur0, ka)
expect_false(isTRUE(all.equal(expected, expected_s0)),
label = paste0("S0=", S0,
" gives a different trajectory than state=0"))
}
}
})
# -- Test 3 -----------------------------------------------------------------
# Infusion end time must be exactly dur0 + state_at_start.
# We verify this by checking that depot(D) matches the analytical peak value
# (R/ka)*(1-exp(-ka*D)). A wrong duration would shift the peak.
test_that("state-dep dur: infusion ends at exactly dur0 + state_at_start", {
mod <- rxode2({
dur(depot) <- dur0 + state
d/dt(depot) <- -ka * depot
d/dt(state) <- 0
})
amt <- 100
dur0 <- 5
ka <- 0.3
for (S0 in c(0, 3, -1)) {
D <- dur0 + S0
R <- amt / D
# Include the exact stop time D in the sampling grid
times <- sort(unique(c(seq(0, D + 8, by = 0.1), D)))
et <- eventTable() |>
add.dosing(dose = amt, rate = -2) |>
add.sampling(times)
s <- solve(mod, et,
inits = c(state = S0, depot = 0),
params = c(dur0 = dur0, ka = ka))
# Analytical depot value at the exact infusion stop time
depot_at_D_expected <- (R / ka) * (1 - exp(-ka * D))
# Observed depot at the sample nearest D
obs_at_D <- s$depot[which.min(abs(s$time - D))]
# If the duration was wrong the infusion would stop at the wrong time,
# yielding a depot value that is too large or too small.
expect_equal(obs_at_D, depot_at_D_expected,
tolerance = 1e-3,
label = paste0("depot at infusion stop D=", round(D, 4),
" matches analytic (S0=", S0, ")"))
}
})
# -- Test 4 -----------------------------------------------------------------
# Multiple doses: each dose's duration is evaluated at the state present when
# THAT dose's infusion begins. With decaying state, consecutive doses get
# shorter (or longer) durations.
# Verify no NAs and that the trajectory differs from the state=0 baseline.
test_that("state-dep dur: consecutive doses evaluated at their own start state", {
mod <- rxode2({
# Duration depends on a decaying state variable, so each dose receives a
# different infusion duration if spaced far enough apart.
dur(depot) <- dur0 + state
d/dt(depot) <- -ka * depot
d/dt(state) <- -kstate * state
})
et <- et() |>
et(amt = 50, rate = -2, time = 0) |> # dose 1 - state is large
et(amt = 50, rate = -2, time = 20) |> # dose 2 - state has decayed
et(seq(0, 35, by = 0.5))
s <- rxSolve(mod, et,
params = c(dur0 = 5, ka = 0.4, kstate = 0.2),
inits = c(depot = 0, state = 3))
# No numerical failures
expect_false(any(is.na(s$depot)),
label = "no NAs in depot with two state-dep dur doses")
# The trajectory must differ from a state=0 run (different duration per dose)
s0 <- rxSolve(mod, et,
params = c(dur0 = 5, ka = 0.4, kstate = 0.2),
inits = c(depot = 0, state = 0))
expect_false(isTRUE(all.equal(s$depot, s0$depot)),
label = "nonzero initial state gives different depot trajectory than state=0")
})
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.