Why solver choice matters in pharmacometrics
In pharmacometrics, the “best” ODE solver is rarely the highest-order method on paper. What usually matters more is whether the model is stiff, whether dosing creates sharp discontinuities, whether output is requested on a dense sampling grid, and whether you need reproducible fixed-step behavior for sensitivity or comparison studies.
rxode2 exposes a broad solver set because different
model classes benefit from different tradeoffs:
- classic PK or PK/PD models with mild stiffness often work well with
liblsoda,dop853, ordop54 - TMDD, mechanistic PD, QSP, and transit-heavy systems often need
stiffness-aware methods such as
liblsoda,ros4,ros43,sdirk43, orcvode - fixed-step methods such as
rk4,backwardEuler,gauss6, orradauiia5are useful when you want controlled stepping between events, method-comparison experiments, or deterministic solver behavior for debugging
The method argument to rxSolve() selects
the solver. The four historic and therefore most commonly used methods
are:
method |
Algorithm | Best for |
|---|---|---|
"liblsoda" |
Thread-safe C LSODA | Default. Parallel multi-subject simulations |
"lsoda" |
FORTRAN LSODA | Single-threaded; numerical comparison with deSolve |
"dop853" |
Dormand–Prince 8th-order RK | Smooth non-stiff ODEs; dense output |
"indLin" |
Inductive linearization + matrix exponential | Linear/mildly-nonlinear systems; estimation loops |
All methods accept NONMEM-format event tables and handle dosing discontinuities automatically. The sections below first cover these four methods with code examples, then give a practical pharmacometric view and a complete method inventory split by stiffness and adaptivity.
The four main methods
liblsoda (default)
liblsoda is a thread-safe C rewrite of the classic LSODA
algorithm. LSODA (Livermore Solver for Ordinary Differential Equations
with Automatic Switching) automatically detects stiffness and switches
between Adams (non-stiff) and BDF (stiff) integration formulas.
Because it is thread-safe, liblsoda runs in parallel
across subjects via OpenMP when cores > 1:
library(rxode2)
mod <- rxode2({
d/dt(depot) <- -ka * depot
d/dt(center) <- ka * depot - (CL / V) * center
Cp <- center / V
})
set.seed(42)
ev <- et(amt = 100) |> et(0:72)
theta <- c(ka = 1, CL = 0.1, V = 10)
omega <- lotri(eta.ka ~ 0.04, eta.cl ~ 0.09, eta.v ~ 0.04)
sim <- rxSolve(mod, theta, ev,
omega = omega,
nSub = 1000,
method = "liblsoda",
cores = 4)Tolerance is controlled with atol and rtol
(default 1e-8 and 1e-6). For estimation,
nlmixr2 uses liblsoda internally because it
supports sensitivity equations.
lsoda
The original FORTRAN LSODA from Hindmarsh (ODEPACK). Not thread-safe,
so it cannot exploit OpenMP for parallel subjects. For single-subject
use or when evaluating numerical agreement with deSolve (which also
wraps this FORTRAN code), lsoda is appropriate:
sol <- rxSolve(mod, theta, ev, method = "lsoda")
dop853
An explicit 8th-order Dormand–Prince Runge–Kutta method with 5th- and 3rd-order error estimates for step-size control. It does not switch to a stiff algorithm, so it is unsuitable for stiff systems. It is, however, very efficient for smooth problems and provides dense output (accurate solutions at any requested time point, not just the grid).
sol <- rxSolve(mod, theta, ev, method = "dop853")For non-stiff problems it is often faster than LSODA because it avoids the stiffness-detection overhead.
indLin
Inductive linearization with matrix exponential integration. Rather than advancing one time step at a time, the ODE is iteratively converted to a linear time-varying (LTV) system and solved exactly with the matrix exponential. Details and usage are covered in the companion article Matrix Exponentials and Inductive Linearization in rxode2.
Stiffness and Jacobians
Stiff systems contain processes operating on widely different time
scales. The default liblsoda handles stiffness
automatically. For very stiff problems you can improve performance and
stability by providing an analytic Jacobian with
df(state)/dy(variable):
vanPol <- rxode2({
mu <- 1000
d/dt(y) <- dy
d/dt(dy) <- mu * (1 - y^2) * dy - y
df(y)/dy(dy) <- 1
df(dy)/dy(y) <- -2 * dy * mu * y - 1
df(dy)/dy(dy) <- mu * (1 - y^2)
y(0) <- 2
dy(0) <- 0
})
sol <- rxSolve(vanPol, et(seq(0, 20, length.out = 500)), method = "lsoda")rxode2 can also compute the Jacobian symbolically via the
symengine CAS using calcJac = TRUE:
vanPol_auto <- rxode2(vanPol$symengineModelNoPrune, calcJac = TRUE)Practical pharmacometric starting points
| Modeling situation | Good rxode2 starting points | Why | Familiar names in other ecosystems |
|---|---|---|---|
| General PK/PD work, stiffness uncertain |
liblsoda, lsoda
|
Automatically switches between non-stiff and stiff behavior; very good default for mixed event schedules | ODEPACK/deSolve lsoda; Julia lsoda();
MATLAB has no direct LSODA wrapper and usually splits this choice
between ode45 and ode15s; Phoenix NLME closest
match is Auto-Detect (LSODA) |
| Smooth, mostly non-stiff PK/PD with many requested output times |
dop853, dop5, dop54,
t54
|
High-order explicit methods; efficient on smooth concentration-time problems |
dop54/dp54 is closest to MATLAB
ode45; t54 is the same method as Julia
Tsit5(); dop853 matches SciPy
DOP853; Julia DP5() maps to
dop5/dop54, Vern7() maps to
vern76; Phoenix NLME analogs are
Non-Stiff (DOPRI5) and Non-Stiff (DVERK)
|
| Moderate stiffness: TMDD, indirect response with fast turnover, enzyme models |
ros4, ros43, sdirk43
|
Stiff-aware one-step methods that often behave well across dosing discontinuities | MATLAB users can think ode23s / ode15s;
Julia analogs are Rosenbrock23(), Rodas4P(),
Rodas5P(); Phoenix NLME users usually compare against
Auto-Detect (LSODA) or Stiff (LSODE/BDF) |
| Clearly stiff or larger systems where linear solver choice matters | cvode |
CVODE gives Adams/BDF-style behavior with configurable linear
solvers; supports dense=TRUE for efficient dense output
using CVODE’s native CVodeGetDky interpolation |
Julia CVODE_Adams() / CVODE_BDF() in
Sundials; MATLAB users usually compare against ode15s;
Phoenix NLME analog is Stiff (LSODE/BDF) |
| Stiffness uncertain, want fast non-stiff speed with automatic fallback |
"t54+ros4", "t54+ros43",
"vern76+ros43", "vern76+sdirk43"
|
AutoSwitch: starts with the non-stiff solver; detects stiffness mid-solve and retries the interval with the implicit solver; switches back if the region becomes non-stiff again | Closest to Julia
AutoTsit5(RosShamp4())/AutoVern7(Rodas4P());
note Julia’s Rosenbrock23() has no exact rxode2 match (use
ros4 as the lowest-order Rosenbrock-family secondary); no
direct MATLAB or NONMEM analog |
| Fixed-step comparison runs or debugging around dose/event boundaries |
rk4, trapz, backwardEuler,
gauss6, iiic6, radauiia5,
ros6
|
Forces a controlled step size and makes solver-to-solver comparisons easier | Julia has direct families such as Heun(),
ImplicitEuler(), GaussLegendre,
LobattoIIIC, RadauIIA5(); MATLAB has fewer
built-in fixed-step counterparts |
| Mostly linear compartment structure with sparse nonlinear pieces | indLin |
Uses the specialized inductive linearization pathway instead of a general ODE stepper | Best compared with matrix-exponential or linearization workflows,
not with ode45/ode15s directly; Phoenix NLME
analogs are Matrix Exponent and
Exponent Higham
|
How rxode2 solvers split by stiffness and adaptivity
For pharmacometric work, the most useful split is:
adaptive and stiffness-aware or stiff-suitable
fixed-step and stiff-suitable
adaptive and primarily non-stiff
fixed-step and primarily non-stiff
pharmacometric special-case linearization
Some methods blur the boundary. For example, liblsoda
and lsoda switch automatically between non-stiff and stiff
formulas, and cvode can behave more like a non-stiff Adams
method or a stiff BDF method depending on the problem. In practice,
pharmacometric users usually reach for them when stiffness is
possible.
Adaptive, stiffness-aware, or stiff-suitable methods
These are the first methods to consider when a model has fast and slow time scales, strong feedback, binding kinetics, or many hidden turnover processes.
liblsoda,lsoda: automatic stiffness detection and switching; still the safest “start here” choice for many PK/PD modelscvode: Sundials CVODE backend; useful when you need a modern stiff solver with linear solver control (dense,band,gmres,bicgstab,tfqmr). At present,bandis kept for compatibility and aliases to the dense path.cvodesupportsdense=TRUE: the BDF or Adams history is preserved across consecutive observation points by usingCV_ONE_STEP+ CVODE’s nativeCVodeGetDkyinterpolation rather than reinitializing at each output time. This is particularly beneficial for BDF, which maintains a multi-step history polynomial and a Jacobian factorization that would otherwise be discarded on every reinitialize.ros4,ros43: adaptive Rosenbrock methods for stiff or moderately stiff systemssdirk43: adaptive singly diagonally implicit Runge-Kutta method
These are closest in spirit to MATLAB ode23s /
ode15s and to Julia methods such as
Rosenbrock23(), Rodas4P(),
Rodas5P(), and the Sundials CVODE_*
wrappers.
AutoSwitch composite methods
AutoSwitch methods let you combine a fast non-stiff primary solver
with a stiff fallback solver using a "primary+stiff"
string:
rxSolve(model, events, params, method = "t54+ros43")
rxSolve(model, events, params, method = "vern76+ros43")
rxSolve(model, events, params, method = "dop853+sdirk43")Within each ODE interval, rxode2 runs the primary
(non-stiff) solver first. If that interval appears stiff – either
because the solver failed, because DOP853’s internal stiffness test
fired, or because the Gershgorin spectral radius of the Jacobian exceeds
the method’s stability region – the interval is retried with the stiff
secondary solver. Once consecutive stiff detections cross a threshold,
the solver switches permanently to the stiff method for that subject. It
switches back if enough consecutive non-stiff intervals are
observed.
Why use AutoSwitch instead of liblsoda?
liblsoda already switches between Adams and BDF
internally, so it is already a good general-purpose default. AutoSwitch
complements it:
Speed on clearly non-stiff regions. The non-stiff primaries (
t54,vern76,dop853,bs32, etc.) are typically faster than the Adams method insideliblsodaon smooth, non-stiff intervals because they use higher-order explicit stages optimized for accuracy per function evaluation rather than for very broad order-adaptation.Explicit method choice. You decide which non-stiff solver to use as the primary (any thread-safe non-stiff method) and which stiff solver to use as the secondary (any implicit method except
cvodeandlsoda). This is useful when you already know from a preliminary solve that most of the dose interval is non-stiff and only specific regions – typically shortly after dosing in TMDD or competitive binding models – require implicit stepping.Matches Julia’s ecosystem. Pharmacometric models built and tested in Julia with
AutoTsit5(Rosenbrock23())orAutoVern7(Rodas5P())can be matched closely by"t54+ros43"or"vern76+ros43"inrxode2(though the switching methods are different).
When liblsoda is likely better
- When you genuinely do not know whether any part of the model is stiff and you want a proven general-purpose default without tuning.
- When the model changes stiffness many times per dosing interval, making the switching overhead significant.
- When reproducibility across platforms is more important than per-method speed.
Choosing a stiff secondary: ros4, ros43,
or sdirk43?
rxode2 has three main adaptive stiff secondaries:
-
ros4– Boost’s Kaps-Rentrop 4th-order Rosenbrock W-method. A-stable. This is the lightest-overhead Rosenbrock option and the closest available analog to Julia’s lower-order Rosenbrock family. rxode2 does not have a true 2nd/3rd-order Rosenbrock matching Julia’sRosenbrock23();ros4is the lowest-order adaptive Rosenbrock-family method available. -
ros43– libode’s GRK4A, a 4th-order A-stable Rosenbrock-Kaps-Rentrop method with an embedded 3rd-order error estimate. More sophisticated thanros4; analogous to Julia’sRodas4P(). -
sdirk43– SDIRK 4(3), L-stable. More robust for severely stiff problems (e.g., stiff oscillations, equilibrium reactions) where A-stability alone is not enough.
For AutoSwitch pairs, prefer ros4 when you want low
switching overhead, ros43 when you want reliable
moderate-stiffness handling, and sdirk43 when the stiff
regions are severe.
Recommended AutoSwitch pairs
rxode2 AutoSwitch method |
Closest Julia analog | Character |
|---|---|---|
"t54+ros4" |
AutoTsit5(RosShamp4()) |
Tsit5 primary, lightweight Rosenbrock W-method secondary; closest to
AutoTsit5(Rosenbrock23()) but ros4 is 4th
order – no exact 2nd-order Rosenbrock exists in rxode2 |
"t54+ros43" |
AutoTsit5(Rodas4P()) |
Tsit5 primary, GRK4A secondary; more robust stiff handling than
ros4
|
"t54+sdirk43" |
AutoTsit5(SDIRK43()) |
L-stable stiff secondary; best for severely stiff problems |
"vern65+ros4" |
– | Verner 6(5) primary with lightweight stiff secondary |
"vern65+ros43" |
– | Verner 6(5) primary; good accuracy-to-speed balance |
"vern76+ros4" |
AutoVern7(RosShamp4()) |
Higher-order primary with lighter stiff secondary |
"vern76+ros43" |
AutoVern7(Rodas4P()) |
Higher-order primary; efficient on smooth, low-error-tolerance simulations |
"vern76+sdirk43" |
AutoVern7(SDIRK43()) |
Higher-order primary with L-stable stiff backup |
"vern98+ros4" |
AutoVern9(RosShamp4()) |
Highest-order primary with lighter stiff secondary |
"vern98+ros43" |
AutoVern9(Rodas4P()) |
Highest-order primary; best on demanding tolerances |
"vern98+sdirk43" |
AutoVern9(SDIRK43()) |
High-order primary with L-stable stiff backup |
"dop853+ros4" |
– | DOP853 primary; lower-overhead stiff fallback; supports
dense=TRUE
|
"dop853+ros43" |
– | DOP853 primary; more robust stiff handling; supports
dense=TRUE
|
"bs32+ros4" |
AutoBS3(RosShamp4()) |
Low-order primary; useful for rough problems or coarser tolerances |
Note on Julia’s Rosenbrock23(): Julia’s
Rosenbrock23() is a 2nd/3rd-order Rosenbrock W-method
(Shampine, 1982). rxode2 does not have a 2nd/3rd-order adaptive
Rosenbrock. The ros4 method (Boost’s Kaps-Rentrop 4th-order
W-method) is the lowest-order adaptive Rosenbrock-family secondary
available, making "t54+ros4" the closest practical analog
to AutoTsit5(Rosenbrock23()).
Tuning AutoSwitch behavior
Six parameters control the switching logic, all passed directly to
rxSolve() or rxControl():
| Parameter | Default | Meaning |
|---|---|---|
autoSwitchNonstifftol |
0.9 |
Stiffness ratio threshold (in non-stiff mode) above which the interval is considered stiff and retried |
autoSwitchStifftol |
0.9 |
Non-stiffness ratio threshold (in stiff mode) below which the switch-back counter is incremented |
autoSwitchMaxStiff |
10L |
Consecutive stiff-detected intervals before permanently switching to the stiff solver |
autoSwitchMaxNonstiff |
3L |
Consecutive non-stiff intervals (while in stiff mode) before switching back to the primary |
autoSwitchDtfac |
2.0 |
Factor applied to the suggested step size when switching to stiff; divided when switching back |
autoSwitchStiffFirst |
FALSE |
Start with the stiff solver instead of the non-stiff primary |
For pharmacometric applications:
- The defaults work well for standard PK/PD and TMDD models.
- Setting
autoSwitchStiffFirst = TRUEis useful when you expect the early post-dose period to be stiff (e.g., rapid binding or target saturation) and want to avoid the overhead of a failed non-stiff attempt on the first few intervals. - Lowering
autoSwitchNonstifftol(e.g.,0.5) makes switching more aggressive; raising it toward1.0makes the solver more willing to stay with the non-stiff primary even in borderline intervals.
Fixed-step, stiff-suitable, or implicit methods
These are useful when you want implicit stepping but you also want to
control the internal step size directly through hmin.
-
iem: implicit Euler-style stepping for fixed-step studies -
ros6: fixed-step Rosenbrock method -
backwardEuler: first-order implicit baseline -
gauss6: fixed-step Gauss-Legendre IRK method -
iiic6: fixed-step Lobatto IIIC method -
radauiia5: fixed-step Radau IIA method -
geng5: fixed-step implicit method from the libode family
For pharmacometric model comparison work, these methods are especially useful when you want to answer questions like “is the behavior caused by stiffness or by adaptive step-size control?” rather than just “which solver is fastest?”
Adaptive, primarily non-stiff methods
These are the main choices for smooth PK, exposure-response, and simulation workflows where the system is not strongly stiff between events.
Most familiar adaptive non-stiff choices
-
dop853: a strong default for smooth, non-stiff pharmacometric systems with dense sampling grids -
dop5: adaptive Dormand-Prince 5th-order family -
dop54,dp54: the closest match in naming and behavior to MATLABode45 -
f32: closest in role to MATLABode23 -
bs: adaptive Bogacki-Shampine family -
vern65,v65e;vern76,v76e;vern98,v98e: Verner families, conceptually closest to Julia’sVern6,Vern7, andVern9
The full list of accepted names for these families appears in the Complete accepted solver names section.
Fixed-step, primarily non-stiff methods
These methods use hmin as the actual fixed step size
(rk4, trapz, ab,
abm, and related methods – see the complete names section
for the full list). They are usually chosen for comparison studies,
controlled co-simulation, or educational/debugging purposes rather than
for routine production solves.
Special pharmacometric pathway: indLin
indLin (see The four
main methods) is worth trying when:
- the model is close to a linear compartment system
- the nonlinear part is limited or localized
- event handling dominates cost more than the actual ODE algebra
- you want speedups for repeated simulation rather than a new solver family for a fundamentally nonlinear/stiff problem
Adaptive versus fixed-step behavior in rxode2
In rxode2, this is the practical rule of thumb:
- for fixed-step methods,
hminis the step size you are asking the solver to use - for adaptive methods,
hminis only the minimum step size, andhmaxbecomes important when events or dense output matter - for dense output workflows, pass
dense=TRUEtorxControl()orrxSolve(); the methods that support it aredop853,dop5,bs,ros4, andcvode
For pharmacometric workflows such as VPCs, trial simulation, and posterior predictive checks, dense output can matter as much as the integrator itself because it changes how often the expensive full solver state has to be reconstructed at requested sample times.
Solver tolerance
All numerical solvers respond to atol and
rtol:
sol <- rxSolve(mod, theta, ev,
method = "liblsoda",
atol = 1e-10,
rtol = 1e-8)Tighter tolerances increase accuracy at the cost of more function
evaluations. The defaults (atol = 1e-8,
rtol = 1e-6) match NONMEM’s recommended values for
ADVAN8/ADVAN13 and are tighter than MATLAB’s ode45 defaults
(RelTol = 1e-3, AbsTol = 1e-6).
MATLAB naming guide
MATLAB’s ODE suite uses a small number of solver names. The table
below gives the nearest rxode2 equivalent for each.
| MATLAB solver | Closest rxode2 method(s) | Notes |
|---|---|---|
ode23 |
bs32 |
Bogacki-Shampine 3(2) pair; same family |
ode45 |
dop54, dp54
|
Dormand-Prince 5(4) FSAL; same algorithm as MATLAB |
ode78 |
f78 |
Fehlberg 7(8) via Boost; same family |
ode89 |
v89, f89
|
Verner/Fehlberg 8(9) pairs from rklib |
ode113 |
ab, abm
|
Adams-Bashforth-Moulton predictor-corrector (variable order);
liblsoda covers Adams/BDF switching |
ode15s |
cvode, liblsoda
|
BDF stiff solver; cvode is the closest explicit
match |
ode23s |
ros43 |
Kaps-Rentrop Rosenbrock 4(3); structurally the same family as MATLAB
ode23s
|
ode23t |
trapz |
Trapezoidal rule, fixed-step |
ode23tb |
sdirk43 |
SDIRK / TR-BDF2 stiff pair |
ode15i |
(none) | DAE solver; reformulate algebraic constraints as explicit ODEs before using rxode2 |
Key differences from MATLAB
- Parallel subjects: rxode2 runs thousands of subjects in parallel via OpenMP. MATLAB requires explicit vectorization or Parallel Computing Toolbox.
-
Compiled model: rxode2 compiles the ODE to C once.
MATLAB’s
ode45calls an R/MATLAB function on every step unless you hand-code C withmex. - Event handling: rxode2 accepts NONMEM-format event tables (infusions, steady state, lag times) natively. MATLAB requires manual event functions.
-
Order: rxode2’s
dop853uses 8th-order steps;ode45uses 5th.dop853is typically more efficient per function evaluation on smooth problems. -
IndLin vs
ode45: Sharif et al. (2022) showed that IndLin with adaptive step size was approximately six times faster thanode45on a Michaelis–Menten example with comparable parameter estimate accuracy.
Julia naming guide
Julia’s DifferentialEquations.jl ecosystem has a large solver library with explicit names for many classical methods. The table below maps the most commonly used Julia solvers to their rxode2 equivalents.
| Julia (DifferentialEquations.jl) | Closest rxode2 method(s) | Notes |
|---|---|---|
Euler() |
euler |
Fixed-step explicit Euler, 1st-order (rklib) |
Midpoint() |
midpoint |
Fixed-step explicit midpoint, 2nd-order (rklib) |
Heun() |
heun, trapz
|
Fixed-step Heun / explicit trapezoid, 2nd-order |
RK4() |
rk4 |
Classical 4th-order Runge-Kutta (Boost) |
SSPRK33() |
ssp3, ssp33
|
Strong Stability-Preserving RK3 |
SSPRK43() |
ssp43 |
SSP 4(3) adaptive pair (rklib) |
BS3() |
bs32 |
Bogacki-Shampine 3(2) adaptive (rklib); same as MATLAB
ode23
|
Tsit5() |
t54 |
Tsitouras 5(4) FSAL adaptive (rklib); same Butcher tableau |
DP5() |
dop5, dop54, dp54
|
Dormand-Prince 5th-order; same as MATLAB ode45
|
TanYam7() |
tmy7 |
Tanaka-Muramatsu-Yamashita 7th-order (rklib) |
TsitPap8() |
tp86 |
Tsitouras-Papakostas 8(6) (rklib) |
DP8() |
dop87, dp87
|
Dormand-Prince 8(7) |
Vern6() |
vern65, v65e
|
Verner 6(5) efficient pair |
Vern7() |
vern76, v76e
|
Verner 7(6) efficient pair |
Vern8() |
v78, dverk78
|
Verner 7(8) pair |
Vern9() |
vern98, v98e
|
Verner 9(8) efficient pair |
Feagin10() |
f108 |
Feagin 10(8), 17 stages (rklib) |
Feagin12() |
f1210 |
Feagin 12(10), 25 stages (rklib) |
Feagin14() |
f1412 |
Feagin 14(12), 35 stages (rklib) |
ImplicitEuler() |
backwardEuler, iem
|
1st-order fully implicit |
Trapezoid() |
trapz |
Explicit trapezoidal rule, fixed-step |
Rosenbrock23() |
(none exact) | Julia’s 2nd/3rd-order Rosenbrock W-method (Shampine 1982); rxode2
has no direct 2nd/3rd-order Rosenbrock – ros4 (Boost
4th-order W-method) is the lowest-order adaptive Rosenbrock-family
analog available |
RosShamp4() |
ros4 |
Boost Kaps-Rentrop 4th-order Rosenbrock W-method; also the closest
available analog to Rosenbrock23() (rxode2 has no
2nd/3rd-order Rosenbrock) |
Rodas4P() |
ros43 |
GRK4A from libode – 4th-order A-stable Rosenbrock with 3rd-order error estimate |
Rodas5P() |
ros43 |
5th-order in Julia; closest rxode2 analog remains ros43
(4th-order) |
RadauIIA5() |
radauiia5 |
Radau IIA 5th-order, 3 stages |
GaussLegendre6() |
gauss6 |
Gauss-Legendre 6th-order |
LobattoIIIC6() |
iiic6 |
Lobatto IIIC 6th-order |
lsoda() |
liblsoda, lsoda
|
LSODA automatic Adams/BDF switching |
CVODE_Adams() |
cvode |
SUNDIALS CVODE in Adams mode |
CVODE_BDF() |
cvode |
SUNDIALS CVODE in BDF mode (default) |
AutoTsit5(Rosenbrock23()) |
"t54+ros4" (closest; see note) |
AutoSwitch: Tsitouras 5(4) + Boost Rosenbrock W-method;
ros4 is 4th order – rxode2 has no 2nd/3rd-order
Rosenbrock |
AutoTsit5(Rodas4P()) |
"t54+ros43" |
AutoSwitch: Tsitouras 5(4) with GRK4A (4th-order Rosenbrock) stiff fallback |
AutoTsit5(SDIRK43()) |
"t54+sdirk43" |
AutoSwitch: Tsitouras 5(4) with SDIRK 4(3) stiff fallback |
AutoVern6(Rosenbrock23()) |
"vern65+ros4" (closest; see note) |
AutoSwitch: Verner 6(5) with Boost Rosenbrock W-method |
AutoVern6(Rodas4P()) |
"vern65+ros43" |
AutoSwitch: Verner 6(5) with GRK4A stiff fallback |
AutoVern7(Rosenbrock23()) |
"vern76+ros4" (closest; see note) |
AutoSwitch: Verner 7(6) with Boost Rosenbrock W-method |
AutoVern7(Rodas4P()) |
"vern76+ros43" |
AutoSwitch: Verner 7(6) with GRK4A stiff fallback |
AutoVern7(SDIRK43()) |
"vern76+sdirk43" |
AutoSwitch: Verner 7(6) with SDIRK 4(3) stiff fallback |
AutoVern9(Rosenbrock23()) |
"vern98+ros4" (closest; see note) |
AutoSwitch: Verner 9(8) with Boost Rosenbrock W-method |
AutoVern9(Rodas4P()) |
"vern98+ros43" |
AutoSwitch: Verner 9(8) with GRK4A stiff fallback |
AutoVern9(SDIRK43()) |
"vern98+sdirk43" |
AutoSwitch: Verner 9(8) with SDIRK 4(3) stiff fallback |
Note: for many of the very high-order Runge-Kutta families in
rxode2, the closest naming culture is Julia’s
DifferentialEquations ecosystem rather than MATLAB’s built-in ODE suite.
In particular, t54 (Tsit5), tmy7
(TanYam7), tp86 (TsitPap8), and
the three Feagin methods (f108, f1210,
f1412) have direct Julia name equivalents that have no
MATLAB counterpart. The AutoSwitch composite methods are analogous to
Julia’s AutoTsit5, AutoVern7, and
AutoVern9 wrappers, with the caveat that rxode2 has no
2nd/3rd-order Rosenbrock matching Julia’s Rosenbrock23() –
use ros4 (4th-order Boost W-method) as the closest
available lower-overhead Rosenbrock-family secondary.
Key differences from Julia
- Language: rxode2 is R-native; the model language feels like writing NONMEM or ordinary PK equations. Julia models use Julia syntax and the ModelingToolkit.jl DSL.
- Solver breadth: Julia’s ecosystem offers dozens of solvers. rxode2 provides a large set focused on the most useful methods for pharmacometrics.
- Performance: Julia achieves near-C speeds after JIT compilation. rxode2 compiles models to C ahead of time, reaching comparable speeds for repeated evaluations. Julia has an advantage for single, long integrations; rxode2 has an advantage for large population simulations via OpenMP.
-
Sensitivity / gradients: Julia’s
sensealgsupports adjoint and forward sensitivity for large systems; rxode2 computes forward sensitivities (for nlmixr2 FOCEi) via symbolic differentiation. -
Interoperability: The
JuliaCallorJuliaConnectoRpackages allow calling Julia’s ODE solvers from R.
NONMEM, Monolix, and Phoenix crosswalk
For pharmacometric users, the most useful comparison is often not “which exact numerical method name matches?” but rather “which class of models and solver behavior am I used to from another platform?”
NONMEM ADVAN crosswalk
NONMEM’s ADVAN family mixes three distinct ideas:
- closed-form compartment models
- matrix-exponential linear systems
- general ODE/DAE solvers for nonlinear or stiff systems
That means the closest rxode2 counterpart is sometimes
not a general ODE solver at all.
| NONMEM family | What NONMEM uses | Closest rxode2 comparison | Practical pharmacometric note |
|---|---|---|---|
| ADVAN1, ADVAN2, ADVAN3, ADVAN4, ADVAN10, ADVAN11, ADVAN12 | Closed-form PK model families |
linCmt() first, then indLin or general ODE
methods if you need custom structure |
These are best compared to rxode2’s analytic or near-analytic PK
pathways, not to dop853 or cvode
|
| ADVAN5 | General linear model using a matrix exponential |
linCmt() when the structure fits; otherwise
indLin is usually the closest conceptual match |
Good match for larger linear compartment systems or PBPK-like linear flows |
| ADVAN7 | General linear model with real eigenvalues, more efficient than ADVAN5 when that assumption holds |
linCmt() / indLin
|
Again, this is closer to a linear algebra pathway than to a general adaptive ODE solver |
| ADVAN6 | General nonlinear model; NONMEM documents IMSL DVERK (Runge-Kutta-Verner 5/6) for nonstiff problems |
dop5, dop54, dop853, or
liblsoda
|
This is the closest NONMEM analog to rxode2’s adaptive explicit non-stiff families |
| ADVAN8 | General nonlinear stiff model; NONMEM documents IMSL DGEAR (Gear) |
ros4, ros43, sdirk43,
cvode, or liblsoda
|
Best comparison when TMDD/QSP turnover or multi-scale kinetics make the system clearly stiff |
| ADVAN9 | Differential-algebraic / equilibrium-compartment model; NONMEM documents LSODI with BDF for stiff problems |
cvode or stiff implicit methods in rxode2, but usually
after rewriting the model into explicit ODE form if possible |
Use this mental bucket for equilibrium or algebraic constraints rather than for ordinary PK models |
| ADVAN13, ADVAN14 | General nonlinear stiff/nonstiff equations in later NONMEM versions |
liblsoda first(ADVAN13), then
cvode(ADVAN14). You can also use ros4, or
dop853 depending on observed stiffness |
These are the closest “modern general-purpose ODE” ADVANs to rxode2’s default ODE workflows |
| ADVAN15 | Equilibrium-compartment extension of ADVAN13/14-style workflows | stiff implicit rxode2 workflows; sometimes custom model reformulation is the real comparison | Think “DAE-like/equilibrium-aware” rather than a one-name solver match |
| ADVAN16, ADVAN17 | Delay-equation models using RADAR5 | no direct ODE-solver equivalent in this vignette | This is beyond ordinary ODE comparison and should be treated separately |
| ADVAN18 | Delay-equation models using DDE solver | no direct ODE-solver equivalent in this vignette | Also outside the usual ODE-only solver menu |
The key practical mapping is:
- NONMEM analytic ADVANs ->
rxode2linCmt()/indLin - NONMEM ADVAN6 ->
rxode2non-stiff adaptive solvers such asdop54,dop5, ordop853 - NONMEM ADVAN8 ->
rxode2stiff-aware solvers such asros4,ros43,sdirk43 - NONMEM ADVAN13 ->
liblsodageneral-purpose adaptive workflow - NONMEM ADVAN14 ->
cvodegeneral-purpose adaptive workflows
Key differences from NONMEM
- Open source: rxode2 is GPL-3; NONMEM requires a commercial license.
-
Simulation speed: rxode2 simulates thousands of
subjects in seconds via OpenMP. NONMEM simulation (
$SIM) is single-threaded. -
Estimation: rxode2 itself is a simulator;
population estimation is done through
nlmixr2, which wraps rxode2. NONMEM integrates estimation and simulation in one tool. -
Tolerance: NONMEM’s
TOLis a single scalar; rxode2 exposes separateatolandrtol. -
Analytical solutions: Both tools provide analytical
compartment solutions. NONMEM uses
ADVAN1--4; rxode2 useslinCmt(), which supports 1–3 compartments and is compatible with stan-math gradients for FOCEi estimation in nlmixr2. - NONMEM datasets: rxode2 accepts NONMEM-compatible event data frames directly (EVID 0–4).
Translating ADVAN5/7 rate constants with matExp()
NONMEM ADVAN5/7 models specify the rate matrix via
K(i,j) scalars in $PK, where
K(i,j) is the transfer rate from
compartment i to compartment
j. rxode2’s matExp() declaration uses the
same source-first convention (k_from_to),
making translation direct and avoiding all symbolic manipulation. This
is the most numerically stable approach for linear compartmental
models:
threeComp <- rxode2({
matExp()
k_depot_central = ka
k_central_output = k20
k_central_periph = k23
k_periph_central = k32
Cp = central / Vc
})
sol <- rxSolve(threeComp, params, ev, method = "indLin")Compartments (depot, central,
periph) are auto-detected from the rate constant names; no
explicit declarations are needed.
See the companion article Matrix
Exponentials and Inductive Linearization for a full description of
matExp() syntax, the NONMEM index mapping, and the
indLin(state) forcing-term syntax.
Monolix crosswalk
Monolix documents two ODE solver modes:
-
odeType = nonStiff: Adams methods for non-stiff ODEs -
odeType = stiff: Backward Differentiation Formula (BDF) methods for stiff ODEs
That makes the practical rxode2 comparison fairly
direct:
| Monolix mode | Closest rxode2 comparison | Pharmacometric interpretation |
|---|---|---|
| nonStiff (Adams) |
ab, abm for an Adams predictor-corrector
match; liblsoda for automatic Adams/BDF switching;
dop54, dop5, dop853,
t54 as practical non-stiff Runge-Kutta alternatives |
Smooth PK/PD, transit, or exposure-response systems without severe time-scale separation |
| stiff (BDF) |
bdf, cvode, liblsoda,
ros4, ros43, or sdirk43
|
TMDD, target binding, indirect response with fast turnover, mechanistic PD, QSP-like stiffness |
Monolix’s nonStiff mode uses Adams (predictor-corrector)
methods internally. The structurally closest rxode2 analogs are
ab (Adams-Bashforth fixed-step) and abm
(Adams-Bashforth-Moulton fixed-step), or liblsoda which
runs Adams-style stepping in its non-stiff mode. In practice, many
pharmacometric users who were happy with Monolix nonStiff
will find that liblsoda, dop54,
t54, or dop853 give equally good results on
smooth PK/PD problems, with the Runge-Kutta families often being faster
on smooth outputs.
if you were happy with Monolix
nonStiffand want the closest Adams analog, tryaborabm; for general-purpose non-stiff use, tryliblsoda,dop54,t54, ordop853if you needed Monolix
stiff, start inrxode2withbdf,liblsoda,cvode,ros4, orros43
Key differences from Monolix
- Free and open source: rxode2 and nlmixr2 are freely available on CRAN; Monolix requires a commercial license (free academic licenses exist).
- Model language: Monolix uses a proprietary model language (Mlxtran); rxode2 uses an R-embedded mini-language that is a superset of common PK notation.
- Customization: rxode2 models can call arbitrary C/C++ functions registered at the R level; Monolix has limited extensibility.
- Ecosystem: nlmixr2 integrates with ggPMX, xpose4, and vpc for diagnostics; Monolix has Datxplore and Pkanalix (could use PKNCA instead).
-
Simulation platform:
rxode2/RxODEunderpinssimulx()via theRsSimulxR package (PKPD simulations driven by Monolix parameter estimates).
Phoenix WinNonlin / Phoenix NLME crosswalk
Phoenix WinNonlin’s built-in compartmental tools are best compared by model family. Phoenix NLME, however, does publish its named ODE choices, so those can be mapped more directly.
| Phoenix workflow / solver | Closest rxode2 comparison | Practical note |
|---|---|---|
| Phoenix WinNonlin built-in compartmental PK models |
linCmt() or simple rxode2() PK models |
Best match for standard 1-, 2-, and 3-compartment PK workflows |
Phoenix NLME Matrix Exponent
|
indLin or linCmt()
|
Matrix-exponential solve for linear ODE systems with a constant Jacobian; usually the closest speed-oriented comparison |
Phoenix NLME Exponent Higham
|
indLin |
Alternative matrix-exponential path with the same basic
pharmacometric role as Matrix Exponent
|
Phoenix NLME Non-Stiff (DVERK)
|
vern65, v65e, dop5,
dop54
|
Explicit non-stiff Runge-Kutta/Verner family behavior |
Phoenix NLME Non-Stiff (DOPRI5)
|
dop54, dp54, dop5
|
Closest Phoenix analog to the Dormand-Prince family most users
associate with ode45-like behavior |
Phoenix NLME Auto-Detect (LSODA) |
liblsoda, lsoda
|
Automatic Adams/BDF switching; strongest direct name-level match in rxode2 |
Phoenix NLME Stiff (LSODE/BDF) |
cvode, ros4, ros43,
sdirk43
|
Stiff-only pathway; closest to rxode2 stiff workflows rather than to one exact solver name |
Phoenix NLME also uses a three-level solving strategy:
- closed-form for supported standard compartmental models
- matrix exponentials for linear ODE systems with a constant Jacobian
- numerical ODE solvers for nonlinear systems
So, for Phoenix users moving to rxode2, the practical
sequence is:
- if the Phoenix model is a standard compartmental PK model, compare
first to
linCmt() - if the Phoenix model used
Matrix ExponentorExponent Higham, compare first toindLin - if it used
Auto-Detect, compare first toliblsoda - if it used
Stiff, comparecvode,ros4, orros43 - if it used
Non-Stiff (DOPRI5)orNon-Stiff (DVERK), comparedop54,dop5,vern65, ordop853,dverk65ordverk78
Summary
The table below compares rxode2 against other common pharmacometric and scientific computing platforms across key features.
| Feature | rxode2 | MATLAB | NONMEM | Monolix | Julia (SciML) |
|---|---|---|---|---|---|
| Open source | Yes (GPL-3) | No | No | No (free academic) | Yes (MIT) |
| Language | R + C | MATLAB | NONMEM7 | Mlxtran | Julia |
| Stiff solver | LSODA (auto) | ode15s | ADVAN8/13 | ode23s | CVODE_BDF, Rodas5P |
| Non-stiff solver | dop853, LSODA Adams | ode45 | ADVAN6/9 | ode45 | Tsit5, DP8 |
| Analytical PK | linCmt (1-3 cmt) | Manual | ADVAN1-4 | PKanalix | ModelingToolkit |
| Matrix exponential |
matExp() / indLin
|
expm() | ADVAN5/7 | – | LinearAlgebra.exp |
| Parallel subjects | OpenMP (built-in) | PCT add-on | – | – | Threads.@threads |
| NONMEM datasets | Native | Manual | Native | Via import | Manual |
| Population estimation | nlmixr2 | – | Native | Native | Turing.jl |
| Sensitivity equations | Forward (liblsoda) | – | – | – | ForwardDiff, adjoint |
Complete accepted solver names in rxode2
The accepted method names below cover the full current solver menu in
rxode2.
AutoSwitch composite methods
Use the "primary+stiff" syntax; any thread-safe
non-stiff primary with any thread-safe implicit secondary (excludes
lsoda, liblsoda, lsode,
bdf as primary). Recommended pairs and Julia analogs are in
the AutoSwitch composite
methods section above.
DLSODE-based fixed-method solvers (single-threaded)
-
lsode,bdf
These two methods use DLSODE (Hindmarsh 1983, ODEPACK) with a fixed
method flag – unlike lsoda, they do not
switch between Adams and BDF. Both use non-reentrant Fortran COMMON
blocks and therefore always run on a single core,
regardless of the cores setting.
-
lsode– DLSODE MF=10 (Adams, non-stiff), variable order 1-12; no Jacobian evaluation. Corresponds to deSolve’slsode(method="adams"). -
bdf– DLSODE MF=22 (BDF, stiff), variable order 1-5; internally generated dense Jacobian. Corresponds to deSolve’sbdf()/lsode(method="bdf").
Adaptive, primarily non-stiff: core explicit families
-
dop853,f78,ck54,dop5,bs,f32,rk43,dop54,dp54,vern65,v65e,vern76,v76e,dop87,dp87,vern98,v98e
Adaptive, primarily non-stiff: lower-order and SSP families
-
euler,midpoint,heun,ssp22,rk3,ssp53,bs32,ssp43,f45
Adaptive, primarily non-stiff: fourth- and fifth-order families
-
s4,r4,ls44,ls54,ssp54,s5,rk5,c5,l5,lk5a,lk5b,t54,s54,pp54,pp54b,bs54,ss54
What to try first
If you do not want to think about the entire menu, a good pharmacometric workflow is:
start with
liblsodaif the system is smooth and clearly non-stiff, compare against
dop853ordop54if the system is stiff or nearly stiff, compare against
ros4,ros43,sdirk43, orcvodeif stiffness is uncertain, try an AutoSwitch pair such as
"t54+ros4","t54+ros43", or"vern76+ros43"– see the AutoSwitch section for recommended pairs and Julia analogsif you need reproducible fixed internal stepping for diagnosis, try
rk4,backwardEuler,gauss6, orradauiia5if the model is mostly linear between events, try
indLin
That strategy is usually more valuable in pharmacometric practice than trying to optimize across every available high-order RK family from the start.
References
Sharif S, Hasegawa C, Duffull SB (2022). Exploring Inductive Linearization for simulation and estimation with an application to the Michaelis–Menten model. J Pharmacokinet Pharmacodyn 49:445–453.
Shampine LF, Reichelt MW (1997). The MATLAB ODE suite. SIAM J Sci Comput 18(1):1–22.
Hindmarsh AC (1983). ODEPACK, a systematized collection of ODE solvers. In Stepleman RS (ed), Scientific Computing, pp. 55–64.
Rackauckas C, Nie Q (2017). DifferentialEquations.jl – a performant and feature-rich ecosystem for solving differential equations in Julia. J Open Res Softw 5(1):15.
Hairer E, Norsett SP, Wanner G (1993). Solving Ordinary Differential Equations I: Nonstiff Problems. 2nd ed. Springer.
Beal S, Sheiner LB, Boeckmann A, Bauer RJ (2009). NONMEM Users Guides. (1989–2009). Icon Development Solutions, Ellicott City, MD.
