Inside [odes] RHS expressions four time variables are available: TIME (= T) is the ODE solver clock, TAFD is time after first dose, and TAD is time after the most recent dose. This example uses them to build conditional accumulator compartments alongside a standard 1-cpt oral ODE, and then computes post-fit summaries with [derived].
# model: warfarin_ode_time.ferx
# Warfarin ODE -- demonstrates TIME/T/TAFD/TAD in [odes] RHS expressions
[parameters]
theta TVCL(0.2, 0.001, 10.0)
theta TVV(10.0, 0.1, 500.0)
theta TVKA(1.5, 0.01, 50.0)
omega ETA_CL ~ 0.09
omega ETA_V ~ 0.04
omega ETA_KA ~ 0.30
sigma PROP_ERR ~ 0.02 (sd)
[individual_parameters]
CL = TVCL * exp(ETA_CL)
V = TVV * exp(ETA_V)
KA = TVKA * exp(ETA_KA)
[structural_model]
ode(obs_cmt=central, states=[depot, central, AUC_D1, TAM_INT])
[odes]
d/dt(depot) = -KA * depot
d/dt(central) = KA * depot - CL/V * central
# TIME and T are synonymous -- both are the ODE solver time axis.
# TAFD and TAD are available with the same SS-aware semantics as [derived].
d/dt(AUC_D1) = if (TIME < 24) central/V else 0.0
d/dt(TAM_INT) = if (central/V > 0.5 && TAD < 24) 1.0 else 0.0
[scaling]
obs_scale = V
[error_model]
DV ~ proportional(PROP_ERR)
[derived]
KE = CL / V
T_HALF = 0.6931472 / KE
CMAX = max(IPRED)
AUC_72 = integral(IPRED, from=0, to=72, step=0.2)
[output]
CL V KA
[fit_options]
method = foce
maxiter = 300
Key points:
TIME and T are synonymous — both track the ODE solver time axis.
TAFD resets at the first dose; TAD resets at each dose event.
d/dt(AUC_D1) = if (TIME < 24) central/V else 0.0 accumulates AUC only during the first 24 h. The inline if fires at every ODE solver step, so the integral is exact (no grid approximation needed).
d/dt(TAM_INT) = if (central/V > 0.5 && TAD < 24) 1.0 else 0.0 counts hours above 0.5 mg/L within each dosing interval. TAD < 24 resets the window at every dose; the condition fires whenever concentration exceeds the threshold.
[scaling] obs_scale = V maps the central amount to concentration.
[derived] adds KE, T_HALF, CMAX, and AUC_72 to sdtab.
Note
ODE accumulator states vs sdtab.AUC_D1 and TAM_INT accumulate correctly during integration and can drive feedback dynamics (e.g., cumulative exposure affecting tolerance). Raw ODE state values are not written to fit$sdtab automatically. To report an integral in sdtab, use [derived] integral(IPRED, ...) — the two approaches are complementary: use ODE accumulators when the value feeds back into the RHS, and use [derived] for post-fit sdtab columns.
Fit
fit <-ferx_fit(ex$model, ex$data, verbose =FALSE)print(fit)
All three variables are available in [odes]. Choosing between them:
Variable
Resets at
Use for
TIME / T
Never
Absolute clock; calendar-time conditions
TAFD
First dose
Duration since study start
TAD
Every dose
Within-interval conditions; trough detection
A common pattern using TAD for within-interval integration:
d/dt(AUC_interval) = if (TAD < 24) central/V else 0.0
And using TIME for day-1 vs later conditions:
d/dt(AUC_D1) = if (TIME < 24) central/V else 0.0
[scaling] with ODE models
The obs_scale = V declaration divides every model prediction by V before the residual error is applied, so the DV column should be in concentration units (amount/volume) while the ODE state central is in amount units. This avoids putting V in the ODE RHS for the prediction step, keeping the ODE equations simpler.