Structural Model

Maturity: stable — see Feature Maturity for what this means.

The [structural_model] block specifies the pharmacokinetic model used to generate predictions.

Analytical PK Models

For standard compartmental models, use the pk keyword with a model function:

pk MODEL_NAME(param=VALUE, param=VALUE, ...)

Each VALUE is either a parameter defined in [individual_parameters], a numeric constant (e.g. ka=1.0 fixes absorption at 1.0), or the event-time built-in TIME/time. Referencing any other undefined name is a parse error — ferx does not silently default the slot to 0.0 (which previously produced a “converged” but structurally broken fit, #261). An unrecognized param key (e.g. the typo clx=) is likewise rejected.

Available Models

Model Function Compartments Route Required Parameters
one_cpt_iv 1 IV (bolus and/or infusion) cl, v
one_cpt_oral 1 Oral cl, v, ka
one_cpt_transit 1 Oral (Savic transit, analytic closed form) cl, v, n, mtt
two_cpt_iv 2 IV (bolus and/or infusion) cl, v1, q, v2
two_cpt_oral 2 Oral cl, v1, q, v2, ka
three_cpt_iv 3 IV (bolus and/or infusion) cl, v1, q2, v2, q3, v3
three_cpt_oral 3 Oral cl, v1, q2, v2, q3, v3, ka

Each model has a *_compartment_* long-form alias (e.g. three_compartment_iv); the short and long names are interchangeable.

Every parameter in the Required Parameters column must be mapped on the pk(...) line. Omitting one is a parse error (issue #309) — ferx will not silently default the missing slot to 0.0, which would otherwise yield a structurally broken fit (e.g. a missing ka means no absorption, so every prediction floors to the log constant). Bioavailability f and lagtime (alias alag) are optional and default to 1.0 and 0.0 respectively.

Conversely, mapping a parameter the chosen model does not use — e.g. ka on an IV model (no absorption), or q/v2 on a one-compartment model — is accepted but emits a parse warning, since the mapping has no effect. lagtime and f (bioavailability) are never flagged: every model applies both to the dose — lagtime shifts the dose time, and f scales the bioavailable amount for IV bolus, infusion, and oral routes alike (#327).

In the other direction, an individual parameter that is declared but never used — neither mapped into the pk(...) line nor referenced in any other block — is also flagged, since it is computed but has no effect. The common case is declaring F to estimate bioavailability but forgetting to add f=F to the pk(...) line: analytical models bind F (and lagtime) only through an explicit f=/lagtime= mapping.

There is no separate bolus or infusion variant: every IV model selects the closed form per dose from the RATE column (RATE=0 ⇒ bolus, RATE>0 ⇒ infusion). A single subject can mix the two. This matches NONMEM, nlmixr2, and Monolix.

The earlier *_iv_bolus and *_infusion model names were retired in #176. The parser now rejects them with a migration message pointing at the unified *_iv name.

Examples

One-compartment oral:

[structural_model]
  pk one_cpt_oral(cl=CL, v=V, ka=KA)

Two-compartment IV (bolus, infusion, or a mix — driven by RATE):

[structural_model]
  pk two_cpt_iv(cl=CL, v1=V1, q=Q, v2=V2)

Two-compartment oral:

[structural_model]
  pk two_cpt_oral(cl=CL, v1=V1, q=Q, v2=V2, ka=KA)

Three-compartment IV (note that q2/q3 and v2/v3 distinguish the two peripheral compartments):

[structural_model]
  pk three_cpt_iv(cl=CL, v1=V1, q2=Q2, v2=V2, q3=Q3, v3=V3)

Direct event-time mappings are accepted when the pk(...) grammar only needs a single value:

[structural_model]
  pk one_cpt_iv(cl=CL, v=time)

For more complex time-dependent relationships, define the expression in [individual_parameters] and map the resulting parameter name here.

Bioavailability

Bioavailability (F) defaults to 1.0. To estimate it, define an F parameter in [individual_parameters] and map it on the pk(...) line with f=F. It scales the bioavailable amount on every route — oral depot absorption, IV bolus (F · AMT), and infusion (F · RATE, with the duration preserved) — matching NONMEM’s F1. Before #327 the analytical path applied F only to oral depot doses, silently dropping it on IV bolus and infusion; it now applies on all routes.

This applies to ODE models too: an F parameter is applied when the dose enters the compartment (F · AMT), matching NONMEM and the analytical PK functions. Do not also multiply by F in the ODE right-hand side.

Lagtime

All pk models accept an optional lagtime= parameter (or its NONMEM-style alias alag=) that delays the effective start of every dose record by the parameter’s value. Defaults to 0.0 when omitted, so existing models behave identically. See Lagtime for semantics, examples, and limitations.

Dose Handling

Analytical IV models support: - Bolus doses: Instantaneous input (when RATE=0 in data) - Infusions: Zero-order input (when RATE>0 in data) - Mixed: A single subject may receive both bolus and infusion doses; the route is read per event from RATE - Steady-state: Pre-computed steady-state concentrations (when SS=1 and II>0 in data) - Dose superposition: Multiple doses are handled by summing contributions from each dose event

Numerical Stability

The analytical solutions include special handling for: - Near-equal rate constants: When absorption and elimination rates are similar (KA ~ k), L’Hopital’s rule is used to avoid division by zero - Two-compartment eigenvalues: Vieta’s formula is used for robust computation of alpha and beta

ODE Models

For non-standard kinetics (e.g., saturable elimination), use the ODE specification:

[structural_model]
  ode(obs_cmt=COMPARTMENT_NAME, states=[state1, state2, ...])

When the observable is a derived quantity (e.g. amount / V), use the amount-only ODE form and supply [scaling] y = <expr>:

[structural_model]
  ode(states=[depot, central])

[scaling]
  y = central / V

As with analytical models, an individual parameter that is declared but never used — never referenced in the [odes] right-hand side (nor in [scaling]/[derived]/[output]) — is flagged with a parse warning, since it is computed but has no effect (issue #315). The exceptions are the engine-applied F (bioavailability) and lagtime (alias alag): they act on the dose without appearing in the RHS (see Bioavailability above), so they are never flagged.

Generating a standard disposition — ode_template

To get the explicit ODE form of a standard PK model without writing out the states and equations by hand, use ode_template NAME(...):

[structural_model]
  ode_template two_cpt_oral(cl=CL, v1=V1, q=Q, v2=V2, ka=KA)

ferx generates the same disposition ODE (states, micro-constant RHS, and obs_scale) that the analytical pk two_cpt_oral(...) solves in closed form, using the same parameters. You can then re-declare any d/dt(X) in [odes] to override that compartment — the standard way to attach a built-in absorption input such as transit(...). See Built-in Absorption Models for ode_template, override semantics, and the rule that an ODE-only absorption function on an analytical pk model is an error.

See ODE Models for full ODE syntax and Scaling for the [scaling] block.