Data Format
ferx-core reads data in NONMEM-compatible CSV format. This is the standard format used across population PK tools.
Required Columns
| Column | Type | Description |
|---|---|---|
ID |
string/numeric | Subject identifier |
TIME |
numeric | Observation/event time on the data clock |
DV |
numeric | Dependent variable (observed concentration) |
TIMEneed not start at zero. A subject’sTIMEcolumn may begin at any value (e.g. a calendar/clock time). Integration starts at each subject’s first record — matching NONMEM — so an off-zero start is not integrated over a phantom[0, first record]window.TIMEis used exactly as written everywhere it is visible: the modelTIME/Tbuiltin,[derived]columns, the survival left-truncationTENTRY, and the sdtab /predict()/simulate()output. ferx does not re-baseTIMEonto a per-subject elapsed clock (the one exception is stacked reset occasions — see System Resets).
Optional Standard Columns
| Column | Type | Default | Description |
|---|---|---|---|
EVID |
integer | 0 | Event ID: 0 = observation, 1 = dose, 2 = other event, 3 = system reset, 4 = reset + dose. If the column is omitted, the record type is inferred from AMT — see Inferring doses without an EVID column. |
AMT |
numeric | 0 | Dose amount (for EVID=1/EVID=4; also the dose-inference signal when EVID is absent) |
CMT |
integer | 1 | Compartment number (1-indexed). A float-formatted integer (2.0, as pandas writes a whole column once any cell in it is blank) reads as that integer. A missing (./blank/NA) or unreadable cell, and an absent column, fall back to compartment 1 — reported as W_CMT_DEFAULTED whenever CMT selects something on that model, whether a dose’s compartment or an observation’s readout, scale or error model; see CMT on [odes] models. |
RATE |
numeric | 0 | Infusion rate. 0 = bolus, >0 = constant-rate infusion (duration = AMT/RATE). NONMEM’s coded values -1 (modeled rate via an R{cmt} $PK parameter) and -2 (modeled duration via a D{cmt} $PK parameter) are supported on both analytical and ODE models — see Infusion Doses. |
MDV |
integer | 0 | Missing DV flag. 1 = DV should be ignored (row excluded from the likelihood) |
II |
numeric | 0 | Interdose interval for repeated dosing |
SS |
integer | 0 | Steady-state flag. 1 = assume steady state |
CENS |
integer | 0 | Censoring flag. 1 = below LLOQ and DV carries the LLOQ; -1 = above ULOQ and DV carries the ULOQ; 0 = quantified. Paired with bloq_method = m3 in [fit_options] to enable likelihood-based handling — see BLOQ example. Any other whole number is read by its sign and warned about; a cell that is not a whole number (abc) is an error — see out-of-domain values. |
L2 |
integer | 0 (ungrouped) | Level-2 grouping id (NONMEM data item). Observation rows sharing an L2 value within a subject form one correlated observation unit and pick up the block_sigma cross covariance — e.g. the total and unbound rows of one blood draw. 0 / absent = ungrouped. Only affects models with a block_sigma correlated residual; see Covariate-selected error models. |
An integer column may be float-formatted. pandas writes a whole integer column as
1.0,2.0, … once any cell in it is blank, and ferx’s own sdtab writesCENSas1.000000. Every integer column above,ADDL, the occasion column andFREMTYPEread such a cell as the whole number it spells, as NONMEM does:ADDL=2.0is two additional doses,MDV=1.0excludes the row,CENS=1.0is below the LLOQ, and a[data_selection]rule such asignore = CENS == 1removes it (#1496).
Missing DV on observation rows. An
EVID=0row contributes to the likelihood only when itsDVis present. If theDVis missing (.,NA, or blank), mark the rowMDV=1. As a safety net, ferx also treats anEVID=0row with a missingDVasMDV=1even when the flag is absent — the row is skipped rather than scored asDV=0— and emits a singleW_MISSING_DVwarning reporting how many rows were skipped. SetMDV=1explicitly to silence it, or fix the data if the missing values are an error.Simulation reads it the other way round. When the dataset is a design being simulated from, the
DVis the column about to be produced, soDV = .at every sampling time means “simulate here” rather than “nothing to score here”.simulate()fed a population read byread_population_for_simulation()keeps those rows and emits one simulated record per sampling time; no placeholder number is needed in theDVcolumn. The kept rows are reported once asW_DESIGN_DV, so a simulation run off an observed dataset shows that it kept rows a fit would have skipped.MDV=1still excludes the record on both paths — that is the explicit statement that the row is not an observation. Such a population is for simulation only: passing it tofit()is rejected withE_NONFINITE_DV.
A cell that is not a number
A cell ferx cannot read — abc, a stray unit (5 mg), or a fraction in a whole-number column (EVID=1.5) — is an error on a record that reads that column, as it is in NONMEM. Measured on NONMEM 7.5.1: abc in any of the columns below stops NM-TRAN with (DATA ERROR) … ITEM IS NOT A NUMBER, MDV=1.5 with INVALID MDV FIELD, and EVID=1.5, EVID=-1 and ADDL=1.5 stop the run with EVENT IDENTIFICATION DATA ITEM HAS INAPPROPRIATE VALUE / ADDITIONAL DOSES DATA ITEM MUST BE A NON-NEGATIVE INTEGER. One difference: NONMEM rejects abc on every record it keeps, a dose record’s DV included, while ferx rejects it only on a record that reads the column. Before #1501 ferx read such a cell as 0 without a warning: DV = abc was scored as a measured 0.0, EVID = abc turned a dose into an observation, ADDL = abc dropped the additional doses.
| Column | Must be | Read on |
|---|---|---|
TIME, EVID |
a number; a whole number from 0 to 4294967295 for EVID |
every record |
AMT |
a number | a dose record — and every record when the dataset has no EVID column, since the record type is then inferred from AMT |
MDV |
a non-negative whole number | an EVID=0 record |
RATE, II, SS |
a number | a dose record (SS other than 0 or 1 is rejected as before) |
ADDL |
a non-negative whole number | a dose record |
DV |
a number | an observation record (EVID=0, MDV=0), every endpoint |
TENTRY |
a number | a time-to-event observation record |
FREMTYPE |
a whole number from 0 to 65535 | a Gaussian observation record |
CENS |
a whole number | a Gaussian observation record — see out-of-domain values |
The message names the subject, the record’s TIME (or its position among the subject’s records, when TIME is the unreadable cell), the column and the cell. Three things are unchanged:
- A missing cell (
., blank,NA,NaN) keeps the column’s default in the table above; only a present cell that is not a number is an error. - A record the
[data_selection]block removes is not read, so a cell on it is not checked — NONMEM behaves the same way withIGNORE. The rule that removes the record must not decide it on the unreadable column itself:ignore = CENS == 0onCENS = abcwould decide the record on a value the dataset does not hold, so it is an error too, naming the rule. The same holds for a rule that keeps the record on that column. A rule decides on the column only when its other terms hold:ignore = EVID == 2 && RATE == 0on an observation withRATE = abcis decided byEVIDand reads without error. NONMEM 7.5.1 agrees on both sides of the single-term case:IGNORE=(TIME.EQ.2)removes aCENS = abcrecord without error, whileIGNORE=(CENS.EQ.0)on it is a(DATA ERROR). See missing and unreadable cells. CMT, the occasion column andL2keep their own handling: an unreadableCMTfalls back to compartment 1 and is counted inW_CMT_DEFAULTED, an unreadable occasion is counted inW_IOV_OCC_MISSING, and an unreadableL2is ungrouped.
Why an unknown whole number is an error in SS but a warning in CENS: SS=2 has a NONMEM meaning (superimpose the steady state) that ferx would run as something else (SS=1, reset), so the fit would be wrong; a CENS flag is only ever read by its sign, so under bloq_method = m3 CENS=7 is fitted as CENS=1, and the W_CENS_UNEXPECTED warning says how each method reads it.
Inferring doses without an EVID column
EVID is optional. When the column is absent, ferx infers the record type from AMT, exactly as NONMEM does: a row with a nonzero AMT is treated as a dose (EVID=1), and every other row as an observation (EVID=0). This lets legacy NONMEM datasets that omit EVID — marking dose rows only by a nonzero AMT (often with MDV=1) — administer their doses instead of silently dropping every AMT row and fitting a degenerate, dose-free model.
ID,TIME,DV,MDV,AMT
1,0,.,1,100
1,1,9.5,0,.
1,2,7.3,0,.
Here the first row (AMT=100) is inferred as a dose; the others are observations. Inference keys on AMT only — a dose always carries a nonzero amount (for infusions, AMT is the amount and RATE the rate). When an EVID column is present, its values govern and nothing is inferred.
One hard error and three warnings guard against a silently dose-free (or zero-dose) fit:
E_DOSE_NO_AMT(error) — the dataset has dose rows (EVID=1/4) but noAMTcolumn, so every dose amount is zero and the fit would run with no drug in the system (a flat objective that pins every parameter at its initial estimate). This usually means the amount column is named something other thanAMT(e.g. a NONMEM export usingDOSE); rename it toAMTin the CSV header or via a rename in the[data]block.W_ALL_DOSES_ZERO— anAMTcolumn is present but every parsed dose hasAMT = 0(e.g. a placebo-only typo or a mis-scaled amount column). Same flat-objective symptom as the error above, but the column exists so it is a warning rather than a hard stop.W_AMT_NOT_DOSED— one or more non-observation rows carryAMT != 0but were not treated as doses: anEVIDcolumn is present and codes a dose row as something other than1/4(e.g. a dose row mistypedEVID=0withMDV=1). TheirAMTwas ignored. A scored observation (MDV=0) that merely carries a redundant or forward-filledAMTdoes not trigger this.W_NO_DOSES— the dataset parsed zero dose events even though scored observations are present (usually a missingAMT/EVID). Not emitted for time-to-event/survival datasets, which legitimately have no PK doses.
Occasion Column (IOV)
When using Inter-Occasion Variability (IOV), add an occasion-index column to the dataset and specify its name with iov_column in [fit_options]. The column:
- Contains integer occasion indices (e.g. 1, 2, 3…) — one per row
- Applies to both dose rows and observation rows
- Is excluded from covariate auto-detection
Example dataset with OCC column:
ID,TIME,DV,EVID,AMT,CMT,MDV,OCC
1,0,.,1,100,1,1,1
1,1,9.5,0,.,.,0,1
1,2,7.3,0,.,.,0,1
1,24,.,1,100,1,1,2
1,25,10.1,0,.,.,0,2
1,26,8.2,0,.,.,0,2
The occasion index can be any positive integer; they do not need to start at 1 or be consecutive, but a different number means a different occasion with its own kappa EBE.
See IOV documentation for full details.
Covariate Columns
By default, any column not in the standard set above is automatically treated as a covariate. Alternatively, declare covariates explicitly with a [covariates] block in the model file; when present it is authoritative (only listed columns are covariates) and enables type tagging, validation, and the covariate output table. Covariate values are:
- Time-constant: The first non-missing value for each subject is used
- Time-varying: If values change over time for a subject, Last Observation Carried Forward (LOCF) is applied per event (NONMEM-equivalent:
[individual_parameters]is re-evaluated at each dose and observation row using that row’s covariate values)
Covariate names are case-sensitive: a column name in the data file must match the name used in [individual_parameters] expressions (and in a [covariates] block) exactly. (Standard NONMEM columns like ID/TIME are matched case-insensitively; covariate columns are not.)
Time-varying covariate scope
Time-varying covariates are supported for all analytical structural models and ODE-defined models:
- 1-compartment IV (
one_cpt_iv) — bolus and/or infusion per dose - 1-compartment oral (
one_cpt_oral) - 2-compartment IV (
two_cpt_iv) — bolus and/or infusion per dose - 2-compartment oral (
two_cpt_oral) - 3-compartment IV (
three_cpt_iv) — bolus and/or infusion per dose - 3-compartment oral (
three_cpt_oral) - All ODE-defined models (via
[odes])
For oral models, the bolus dose into compartment 1 is interpreted as the depot (NONMEM ADVAN2/ADVAN4/ADVAN12 convention) and observation read-out reads the central compartment.
The analytic Dual2 gradient path is event-driven for all analytical models; time-varying-covariate subjects fall back to finite differences for both the inner-loop gradient and the H-matrix Jacobian (a planned analytic-sensitivity extension).
Infusion routing on the event-driven path:
- IV models: central infusion (cmt=1) for all 1/2/3-cpt; peripheral infusion for 2-cpt (cmt=2) and 3-cpt (cmt=2 → periph1, cmt=3 → periph2). Steady-state amounts per channel are computed by linear superposition over the channels.
- Oral models: depot infusion (cmt=1, a zero-order release then first-order
KAabsorption), central infusion (cmt=2, depot-bypassing), and — since #375 — peripheral infusion (cmt=3 ontwo_cpt_oral; cmt=3 → periph1, cmt=4 → periph2 onthree_cpt_oral). The oral propagators reuse the IV forced response: nothing flows back into the depot, so a rate into a peripheral drives exactly the central/peripheral sub-system the IV model has.
Every compartment of the six analytical disposition models is therefore infusable. What is still rejected is CMT=0 on an infusion (NONMEM’s default dose compartment is defined for a bolus, not for a zero-order input — name the compartment explicitly) and any CMT past the end of the model’s state vector. Transit and inverse-Gaussian absorption models are dosed through the depot (CMT=1) only.
CMT on [odes] models
Both rules above apply unchanged to a model defined with an [odes] block, reading the block’s declared states=[…] list instead of a built-in model’s compartment list:
CMTis 1-based against the declared states.states=[depot, central]acceptsCMT=1(depot) andCMT=2(central);CMT=3is an error naming the declared states. Before #899 such a dose was silently dropped — the fit converged and reported a finite OFV having ignored it.CMT=0is NONMEM’s default dose compartment and resolves to the first declared state, exactly as on the analytical engine. An infusion withCMT=0is rejected, for the same reason as above.- A dataset that does not say gets compartment 1, and says so. An absent
CMTcolumn, a missing cell (./blank/NA) and an unreadable one all fall back to compartment 1, and every dose or observation row resolved that way is counted into oneW_CMT_DEFAULTEDwarning. This is where NONMEM and ferx genuinely differ:$MODELcan declareDEFDOSEon any compartment, so NM-TRAN resolves an undecorated dose against that, while ferx has no such declaration to fall back on. A translated model whoseDEFDOSEis not the first declared state therefore gets its drug in the wrong place — measured on a 1-cpt oral model writtenstates=[central, depot], an FOCEI OFV of 154050.6 where NONMEM reads −182.667 (#1009). Give the dataset aCMTcolumn ordered likestates=[…], map an existing header onto it withCMT = <header>in[data], or writeCMT=1explicitly to say you meant the first state. When a defaulted dose row carriesADDL, the message also reports the expanded dose count — one cell can deliver thirty-one doses to the guessed compartment.
Unlike the analytical engine there is no infusable subset: every declared state accepts both a bolus and an infusion. A compartment fed by a built-in absorption function (transit(), first_order(), …) is the one exception to how the dose arrives — it is delivered as an input rate over time rather than as an instantaneous bolus — but it is dosed by naming its CMT like any other.
When a defaulted CMT is reported (W_CMT_DEFAULTED)
CMT addresses three things, and the warning covers all of them.
The first is which compartment a dose lands in — and that is not only an [odes] question. An oral model’s CMT=2 is the depot-bypassing central bolus and a 2-cpt IV model’s CMT=2 is a peripheral bolus, both routed through the event-driven walk and both anchored against NONMEM (ADVAN2 central, ADVAN3/ADVAN4 peripheral). So a dataset that meant one of those and lost its CMT column silently gets a compartment-1 bolus instead, exactly as a translated DEFDOSE model does. The warning therefore fires for any model with more than one compartment a dose can reach, and stays silent for the models where there is nothing to choose: one_cpt_iv, a one-state [odes] model, a compartment-free model, and the transit / inverse-Gaussian absorption models, whose closed form absorbs every dose through the depot whatever CMT says.
The second is which readout, scale or error model an observation uses. This one is engine-independent: a per-CMT [scaling] block parses on a pk model, and its validation only checks that the observed compartments have entries, so a CMT-less dataset keys every row to 1, {1} is a subset of {1, 2}, and nothing complains. Measured on pk one_cpt_iv with obs_scale[CMT=1] = 1000 and obs_scale[CMT=2] = 1: the same data spelled with CMT=2 gives OFV 0.0357, with the column dropped 6097015712.1246. The same holds for per-CMT y[CMT=N] readouts and CMT=N: error models — on a one-state [odes] model with y[CMT=1] = central/V and y[CMT=2] = central/V*1000, OFV 10028.0940 with the column against 0.0357 without.
A model with a declared endpoint (survival, binary, categorical, CTMM) is reported too, and on an absent CMT column that duplicates E_ENDPOINT_NO_RECORDS. The duplication is deliberate: an endpoint model routes its rows by CMT, so a compartment the reader invented picks the endpoint. Suppressing the warning here on the grounds that E_ENDPOINT_NO_RECORDS already names the missing column was tried and reverted, because it covers only the absent-column case — a missing or unparseable cell leaves every endpoint with rows, so that error cannot fire, and the defaulted row is silently re-routed between endpoints instead. Measured on a competing-risks model with its endpoints at cmt = 1 and cmt = 2 (not the shipped example, whose endpoints are 2 and 3), one event row’s cell spelled x rather than 2: the event moves from cause_b to cause_a, OFV 27.8497 against 28.3610, with no diagnostic at all while the warning was suppressed.
Since #1409 this reads the model’s declared endpoints rather than the shape of its error-model table, so an endpoint model that also declares an [error_model] is covered — it gets ErrorSpec::Single, and keying on the empty per-CMT error map used to miss it entirely. Measured on examples/tte_exponential.ferx, where a bad CMT cell moves an event row into the Gaussian grid for an OFV of 254.5871 against 166.4395: silent before, reported now. The mirror limit is closed too: an endpoint-only model whose only endpoint sits at cmt = 1 is no longer reported, because the reader’s fallback is that endpoint and a row explicitly assigned any other compartment falls into the Gaussian vectors, where the empty per-CMT error map stops it with E_PER_CMT_ERROR_MODEL — there is nothing to guess wrong, and nothing silent about getting it wrong. The same endpoint on a model that also scores ordinary observations still reports, since a defaulted row there picks between the endpoint and the Gaussian grid.
A [data_selection] clause that compares CMT is the third thing the column addresses, and is likewise covered. The filter is handed the compartment the reader resolved, so a guess can make a clause stop matching (a row you meant to drop is kept) or start matching (a row you meant to keep is deleted). Rows deleted that way are counted and named separately in the message, because they never reach the dose / observation counts; see Data selection.
Event Types (EVID)
| EVID | Meaning |
|---|---|
| 0 | Observation record. DV is used for estimation. |
| 1 | Dosing record. AMT is administered to compartment CMT. |
| 2 | Other event (typically a covariate-change marker). The compartment state is unchanged but the rate matrix is refreshed from this row’s covariate values — matching NONMEM’s $PK runs at every record semantic. Only meaningful when at least one covariate is time-varying; for time-constant data EVID=2 rows are skipped (would be no-ops). |
| 3 | System reset. All compartment amounts are set to zero at this time, and any ongoing infusion is turned off. No dose is given and DV is ignored. |
| 4 | Reset and dose. Like EVID=3 (zero every compartment, stop ongoing infusions) followed immediately by a dose of AMT into compartment CMT. |
System Resets (EVID=3 / EVID=4)
A reset record empties every compartment at its TIME, as if the subject’s drug history started over from that point. EVID=3 is a pure reset; EVID=4 resets and then administers the row’s dose into the freshly emptied system. This matches NONMEM’s reset-event semantics and is useful for, e.g., modelling washout between treatment cycles or re-using one subject record for independent dosing episodes.
ID,TIME,DV,EVID,AMT,CMT,MDV
1,0,.,1,100,1,1
1,1,9.5,0,.,.,0
1,4,6.1,0,.,.,0
1,24,.,3,.,.,1
1,24,.,1,100,1,1
1,25,9.4,0,.,.,0
Stacked occasions with a restarting clock
A common NONMEM idiom is to stack several independent dosing episodes under one subject ID, each opened by an EVID=4 record whose TIME restarts at 0 (so the records are not globally time-ordered):
ID,TIME,DV,EVID,AMT,RATE,MDV
1,0,.,4,100,100,1
1,1,9.4,0,.,.,0
1,8,2.1,0,.,.,0
1,0,.,4,100,100,1
1,1,9.6,0,.,.,0
1,8,2.0,0,.,.,0
NONMEM processes records sequentially, so the second EVID=4 begins a fresh occasion that re-uses the first occasion’s wall-clock. ferx-core reproduces this: each restarting occasion is shifted onto a single monotonic internal timeline (the reset zeros every compartment at the boundary, so no drug carries across), and the subject keeps one shared set of random effects across the occasions — exactly matching NONMEM’s EVID=4 semantics. The two occasions are not merged or double-dosed.
Diagnostics are reported on the raw data clock: the TIME column of {model}-sdtab.csv (and {model}-covtab.csv) echoes the value you wrote, and TAFD / TAD reset per occasion (time after that occasion’s first / most recent dose). The monotonic shift is purely internal to the prediction engine.
[derived]integrals and stacked resets. Integral columns with an absolute window ([from, to]or a periodicanchor) are evaluated per occasion in rawTIMEcoordinates. Each occasion is integrated independently, so crossover designs produce correct per-occasion AUC values. Occasions with no observations inside the window returnNaNfor that row.Grid-based integrals (
step = <dt>orstep = auto) evaluate on the internal shifted clock rather than rawTIME, so expressions referencingTIMEinside a grid integral will see the shifted value. Use observation-based integrals (step = obsordata_based = true) when rawTIMEmust appear inside the integrand expression.
Notes:
- Resets force the event-driven analytical / ODE prediction path — dose superposition cannot express a mid-record reset — so any analytical or ODE model supports them with no configuration.
- Reset-bearing subjects use the analytic gradient where in scope and otherwise fall back to finite-difference gradients; results are unaffected, only the gradient method.
- Resets are not supported on the EKF/SDE path (
[diffusion]models). A reset row on an SDE model emits a warning and is ignored.
Example Dataset
ID,TIME,DV,EVID,AMT,CMT,RATE,MDV,WT,CRCL
1,0,.,1,100,1,0,1,70,95
1,0.5,9.49,0,.,.,.,0,70,95
1,1,14.42,0,.,.,.,0,70,95
1,2,17.56,0,.,.,.,0,70,95
1,4,15.23,0,.,.,.,0,70,95
1,8,10.15,0,.,.,.,0,70,95
2,0,.,1,150,1,0,1,85,110
2,0.5,14.2,0,.,.,.,0,85,110
2,1,21.3,0,.,.,.,0,85,110
Key points: - Dose records (EVID=1) have MDV=1 and DV=. (missing) - Observation records (EVID=0) have MDV=0 and a valid DV - Covariates (WT, CRCL) are included as extra columns - Missing values can be represented as . or left empty
Infusion Doses
For IV infusions, set RATE to the infusion rate (amount per time unit):
ID,TIME,DV,EVID,AMT,CMT,RATE,MDV
1,0,.,1,500,1,50,1
This administers 500 units at a rate of 50 units/hour (duration = 10 hours).
NONMEM coded RATE values
NONMEM overloads the RATE column with negative codes that change its meaning:
RATE |
NONMEM meaning | ferx-core |
|---|---|---|
0 |
Bolus — route set by the dose compartment | ✅ supported |
> 0 |
Constant-rate infusion (duration = AMT/RATE) |
✅ supported |
-1 |
Infusion rate is modeled — defined by R{cmt} in $PK |
✅ supported on ODE and analytical models (#324) |
-2 |
Infusion duration is modeled — defined by D{cmt} in $PK |
✅ supported on ODE and analytical models (#324, #394) |
RATE = -2 makes the infusion duration a model parameter: declare an individual parameter D{cmt} (D1 for a dose into compartment 1, etc.) and ferx infuses AMT over that duration — rate AMT / D{cmt}, evaluated per iteration and occasion. Supported on both the analytical pk(...) engine and ode(...) models; see Modeled infusion duration for the DSL and semantics. A RATE=-2 dose with no matching D{cmt} parameter (on either engine) is a loud error — never a silent bolus.
On an analytical oral model (pk one_cpt_oral / two_cpt_oral / three_cpt_oral), a D1 into the depot (compartment 1) gives a zero-order absorption model: drug is released into the depot at a constant rate over D1, then absorbed first-order into central via KA (#400). D2 into the same model is a depot-bypassing infusion straight into central. Both stay on the closed-form analytical engine — no ode(...) block needed. (Compartment amounts in sdtab/[derived] are not available for analytical depot-zero-order subjects; the predictions themselves are exact — use an ode(...) model if you need the per-compartment amounts.) D3 on a two_cpt_oral model (and D3/D4 on three_cpt_oral) infuses a peripheral compartment, also on the closed-form engine since #375.
RATE = -1 makes the infusion rate a model parameter: declare an individual parameter R{cmt} (R1 for a dose into compartment 1, etc.) and ferx infuses AMT at that rate — duration AMT / R{cmt}, evaluated per iteration and occasion (the mirror of -2). Supported on both engines; a RATE=-1 dose with no matching R{cmt} is the same loud E_MODELED_RATE_NO_PARAM error as the duration case. R{cmt}/D{cmt} are recognised compartment-indexed parameter names (like NONMEM’s reserved $PK names); recognising one only reserves it when a coded RATE actually targets that compartment.
Bioavailability and infusion shape (
F ≠ 1). ferx appliesFto an infusion the NONMEM way (#419), holding whichever quantity you specified and scaling the other so total exposure isF·AMTeither way:
- rate-defined (
RATE>0data andRATE=-1→R{cmt}): the rate is held and the duration is scaled toF·AMT/RATE. So aRATE=-1dose behaves exactly like its explicitRATE = R{cmt}twin.- duration-defined (
RATE=-2→D{cmt}): the duration is held atD{cmt}and the rate is scaled toF·AMT/D{cmt}.The two modes therefore produce a different infusion shape under
F ≠ 1(same total exposure). AtF = 1they coincide. (Earlier versions scaled the rate for every infusion, which diverged from NONMEM for rate-defined infusions; #419.)
Any other negative or non-finite RATE on a dose row is rejected. Earlier versions silently treated all coded forms as a bolus, producing wrong predictions with no warning (#324). Note -1/-2 are driven by $PK parameters — not a separate DURATION data column.
A runnable demo of the supported forms — a bolus (RATE=0) and a constant-rate infusion (RATE>0) mixed in one dataset — is in examples/dose_rate.ferx (data: data/dose_rate.csv).
Steady-State Dosing
For steady-state simulations, set SS=1 and II to the dosing interval:
ID,TIME,DV,EVID,AMT,CMT,SS,II,MDV
1,0,.,1,100,1,1,12,1
1,0.5,25.3,0,.,.,.,.,0
This assumes the subject has reached steady state with 100 units every 12 hours before the observation at TIME=0.5.
SS=1 is supported on every prediction path: analytical (1-/2-/3-cpt with or without time-varying covariates) and ODE. SS=1 also composes with LAGTIME — the lagged SS curve at time t equals the un-lagged curve at t - lagtime. See Steady-State Doses for the full reference, including the data-validation warnings emitted for malformed rows (missing II, overlapping infusions).
Multiple Doses
Multiple doses are supported as separate rows:
ID,TIME,DV,EVID,AMT,CMT,MDV
1,0,.,1,100,1,1
1,0.5,9.49,0,.,.,0
1,12,.,1,100,1,1
1,12.5,15.2,0,.,.,0
1,24,.,1,100,1,1
1,24.5,18.1,0,.,.,0
Column Name Case
Column names are case-insensitive. ID, Id, and id are all recognized. Covariate columns preserve their case as declared in the CSV header.