Stochastic Differential Equations (SDE)
This example extends a one-compartment ODE model with a [diffusion] block that adds continuous process noise (SDE / EKF). The complete model file is examples/warfarin_sde.ferx.
When to use
Add [diffusion] process noise when: - IWRES shows autocorrelation that cannot be absorbed by the residual error alone (Durbin-Watson < 1.5) - Structural misspecification is suspected (e.g. unmodelled absorption variability, time-varying elimination) - You want a data-driven stochastic extension rather than adding a transit compartment or a new mechanistic model
The SDE approach inflates the residual variance at each observation by the integrated process covariance (V_total = P[state] + σ²(PRED)), estimated via an Extended Kalman Filter (EKF). DIFF_CENTRAL captures the diffusion coefficient on the central compartment state; it is estimated on the variance scale.
Dataset
Same as the standard warfarin ODE example — data/warfarin_sde.csv uses the standard warfarin format:
ID,TIME,DV,EVID,AMT,CMT,RATE,MDV
1,0,.,1,100,1,0,1
1,0.5,5.37,0,.,1,0,0
...
Model file
This is the contents of examples/warfarin_sde.ferx:
[parameters]
theta TVCL(0.134, 0.001, 10.0)
theta TVV(8.1, 0.1, 500.0)
theta TVKA(1.0, 0.01, 50.0)
omega ETA_CL ~ 0.07
omega ETA_V ~ 0.02
omega ETA_KA ~ 0.40
sigma PROP_ERR ~ 0.01 (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])
[odes]
d/dt(depot) = -KA * depot
d/dt(central) = KA * depot / V - (CL / V) * central
[diffusion]
central ~ 0.01
[error_model]
DV ~ proportional(PROP_ERR)
[fit_options]
method = foce
maxiter = 300
covariance = true
gradient_method = fd
The [diffusion] block declares central ~ 0.01, meaning the initial variance estimate for the diffusion coefficient on the central compartment is 0.01 (variance scale, not SD). The EKF propagates this uncertainty forward in time between observations.
Important: SDE uses finite-difference gradients (gradient_method = fd); the analytic Dual2 sensitivity path does not cover the EKF. This makes SDE fits slower than equivalent analytical models; the EKF evaluation itself is also more expensive than a plain ODE step.
Running the fit
ferx examples/warfarin_sde.ferx --data data/warfarin_sde.csvOr via the Rust API:
let result = fit_from_files("examples/warfarin_sde.ferx", "data/warfarin_sde.csv")?;
println!("DIFF_CENTRAL = {:.4}", result.diffusion["central"].estimate);Interpreting output
The fit YAML gains a diffusion: section:
diffusion:
central:
estimate: 0.008432
se: 0.001102A small DIFF_CENTRAL (near zero) indicates the process noise is negligible and the standard ODE model is adequate. A large value relative to the mean compartment level suggests real stochastic variability; check whether the DW statistic improves (moves toward 2.0) compared to the deterministic ODE.
Tips
- Compare OFV: fit the deterministic ODE first. The SDE model adds one parameter (
DIFF_CENTRAL); a Δ OFV > 3.84 (χ²₁ at 5%) justifies the addition. Note that the parameter is at a lower boundary (variance ≥ 0), so the asymptotic null is a 50:50 mixture of χ²₀ and χ²₁ — use a Δ OFV > 2.71 as the 5% threshold. - Multiple diffusion states: add one
state ~ init_varianceline per ODE state. In practice only the observed-compartment state benefits from diffusion; depot-compartment diffusion is rarely estimable from concentration data. - Speed: SDE fits are ~5–10× slower than equivalent analytical models. Use a release build (
cargo build --release) and allow more outer iterations. - SDE + ADDL: steady-state dosing (
SS=1) andADDLare supported with the SDE solver; the EKF is reset to the analytical steady-state covariance at each SS dose event.