Check Report (ferx check --json)
ferx check <model.ferx> [--data <data.csv>] --json emits a structured JSON report describing every validation finding, instead of the human-readable summary. The report is designed to be consumed programmatically — by editor tooling, CI, or a coding agent authoring model files — so findings carry a stable machine-readable code rather than only prose.
This is the same data the Rust API returns from validate_model_file; see also the CLI reference for the human-readable form and exit codes.
Top-level shape
{
"valid": false,
"model": "mymodel",
"data": "data/mydata.csv",
"diagnostics": [
{
"severity": "error",
"code": "E_MISSING_COVARIATE",
"message": "Model references covariate(s) not found in data (case-sensitive): WGT. Available covariate columns: (none).",
"suggestion": "available covariate columns: (none)"
}
]
}| Field | Type | Notes |
|---|---|---|
valid |
bool | true when there are no error-severity diagnostics (warnings alone keep it true). |
model |
string | Model file stem. |
data |
string | Present only when --data was supplied. |
diagnostics |
array | Every finding; see below. May be empty. |
Diagnostic object
| Field | Type | Notes |
|---|---|---|
severity |
"error" | "warning" |
Only error affects valid and the exit code. |
code |
string | Stable identifier (see table below). |
message |
string | Human-readable description. |
block |
string | Optional. Owning model block, e.g. "error_model". Omitted when not attributable to one block. |
line |
integer | Optional. 1-based source line — see the caveat below. |
suggestion |
string | Optional. Actionable hint. |
Optional fields are omitted entirely when absent (not emitted as null).
Error / warning codes
Grouped by what the finding is about. Codes are stable; new ones may be added over time. Treat an unrecognised code as a generic finding of its given severity.
- Model-file structure
- Parameters and random effects
- Method and optimizer compatibility
- Data and dosing
- Scaling, output and derived columns
- Experimental and degraded paths
Model-file structure
The file could not be read as a ferx model at all, or names a block ferx does not have.
[fit_options] and [error_model] lines are consumed end to end or reported as E_PARSE with the offending line quoted. A [fit_options] line that is not a key = value pair names the repair (`method saem` is not a `key = value` pair — did you mean `method = saem`?); an [error_model] line matching no statement form lists the accepted forms. Before 0.4.0 both were dropped without a diagnostic, and the method case is the sharp one — the fit then ran the default estimator while ferx check reported the file VALID, so a run the author read as SAEM was FOCEI and its objective was not comparable to the one they asked for. See #1390, Fit options and Error model.
| Code | Severity | Meaning |
|---|---|---|
E_PARSE |
error | The model file failed to parse (catch-all for parser errors). |
E_MISSING_BLOCK |
error | A required [block] is absent. block names which one. |
E_UNKNOWN_BLOCK |
error | A [block] header is not a recognised block name. The message lists every offending header with its line, the valid set this build accepts, and a did-you-mean when there is a near match; suggestion carries the near match on its own. See #1040. |
E_DEPRECATED_BLOCK |
error | A [block] that was ferx syntax and is no longer read — currently [initial_values], whose contents moved inline into [parameters]. Reported separately from E_UNKNOWN_BLOCK so the message can name the replacement instead of offering a did-you-mean that does not exist. |
E_BLOCK_INSTANCE_NAME |
error | A [block NAME] instance name is given for a block that takes none (e.g. [fit_options DOSE]), or omitted for one that requires it ([covariate_nn NAME]). |
E_BLOCK_FEATURE_DISABLED |
error | A recognised block needs a cargo feature this binary was not built with — [event_model] / [binary_model] need --features survival, [markov_model] needs --features markov. ([covariate_nn] has its own E_NN_FEATURE_DISABLED.) |
E_NN_FEATURE_DISABLED |
error | A [covariate_nn] block requires building with --features nn. |
Parameters and random effects
A theta, eta, kappa or sigma the model uses is not declared, is declared in the wrong order, or has no data to bind to.
A [parameters] line must also be consumed end to end by exactly one declaration form, at both ends: text before or after a complete declaration is reported as E_PARSE with the offending line quoted, and so is a line matching no form at all. The one shape with a code of its own is a scale tag on a block_* declaration, below. Before 0.4.0 all of it was dropped without a diagnostic — including leading text, which could silently turn block_sigma PROP ~ 0.04 into a diagonal sigma.
| Code | Severity | Meaning |
|---|---|---|
E_ETA_NOT_DECLARED |
error | An unresolved identifier reads as a random effect (ETA… / KAPPA…) but no omega / kappa declaration defines it, and the data carries no such column. Usually a deleted omega line whose exp(ETA_…) term was left behind. |
W_ETA_NOT_DECLARED |
warning | As E_ETA_NOT_DECLARED, but reported without --data, where ferx cannot yet know whether the dataset supplies the column. |
E_SIGMA_ORDER_MISMATCH |
error | A single-endpoint [error_model] names its sigmas in an order other than the [parameters] declaration order. These sigmas are consumed positionally, so the written names must match the declared order. Fix by reordering the sigma declarations (permuting a block_sigma’s lower triangle with them) — reordering the arguments also parses but swaps the proportional and additive roles, i.e. changes the model. See #1001 and Error model. |
E_BLOCK_VARIANCE_ONLY |
error | A scale tag ((sd), (variance) or (var)) on a block_omega, block_sigma or block_kappa declaration. The lower triangle mixes variances and covariances, so one tag cannot say which entry is on which scale. The repair depends on the tag, and the report’s suggestion carries whichever applies: after (sd), square each SD into a variance and write the off-diagonals as covariances; after (variance) or (var), delete the tag - the lower triangle was already on that scale, so the tag said nothing and nothing else needs changing. The code is raised only when deleting the tag leaves a declaration that parses, so the repair is always sufficient; a tag alongside other stray text is E_PARSE. Until 0.4.0 the tag was dropped in silence and the file reported VALID, so an SD-coded block fitted from squared-wrong initial estimates. See #1377 and Parameters. |
E_IOV_MISSING_OCC |
error | Model declares kappa (IOV) parameters but no occasion labels were found in the dataset. Set iov_column in [fit_options]. |
W_IOV_OCC_MISSING |
warning | Some rows in the IOV occasion column had missing or unparseable values; those rows were assigned occasion=0. |
Initial estimates against the optimizer box
Every initial estimate is packed and compared against the box the optimizer will search inside, before the first objective evaluation. All of the codes below are data-independent, so ferx check model.ferx reports them without a --data file. The two that are not skipped when maxiter = 0 are called out where they appear; for the rest the exemption is because an evaluation-only run clamps the start like any other, it just does not hold the clamped value through a search.
Against the box the optimizer searches
| Code | Severity | Meaning |
|---|---|---|
E_OMEGA_INIT_AT_RAIL |
error | A free omega / kappa / [mixture] omega(k) variance whose initial value packs onto the optimizer’s -6 lower rail — every variance ≤ 6.1e-6, and ~ 0.0 in particular (a declared zero is regularised to 1e-8, packing to ln(L) = -9.21). The start is clamped onto the rail and the coordinate cannot be estimated from there: it stays collapsed or runs away to the opposite rail, moving the θ estimates with it. Either FIX it (that is the supported way to declare no variability) or start it at ≥ 1e-5. NM-TRAN rejects the same stream with error 76, INITIAL ESTIMATE OF VARIANCE CANNOT BE ZERO UNLESS FIXED. Σ is not included — a free sigma ~ 0.0 (sd) is clamped onto its own -8 rail and recovers the optimum. Only reported when an optimizer will actually search: maxiter = 0 (NONMEM MAXEVAL=0, as used by ferx gam --no-fit) is not reported — it clamps the start like any other run, but produces one objective and stops, so nothing is trapped on the rail; note that it therefore evaluates a free ~ 0.0 at exp(-12) rather than at the declared value, which FIX avoids. saem / imp / impmap / bayes carry their own iteration counts and are still checked. A model used only with predict() / simulate() is not exempt on the check path: ferx check reports what fit() would refuse, so such a model is reported here while still simulating fine. Add maxiter = 0 to [fit_options] if the file is genuinely simulation-only. The message is chosen per eta, not per matrix: an eta declared inside a block_omega / block_kappa is told its block is near-singular (in a block it is the correlations that drive L_ii to zero, so raising a declared variance is not the fix), while a diagonally declared one is told to write ~ 0.0 FIX. Mixing a block and diagonal omega lines in the same model — the whole Ω then packs as one Cholesky — does not change what the diagonal eta is told. A one-eta block_omega (NAME) = [...] is correlated with nothing, so it gets the declared-zero explanation rather than the near-singular one, spelled in its own form (write `block_omega (NAME) = [0.0] FIX`) (#1394). See #1229 and Parameters. |
E_THETA_INIT_OUTSIDE_BOUNDS |
error | A theta whose initial value is strictly outside its own declared range. The optimizer clamps the start into its box, so theta TVCL(0.05, 0.1, 10.0) fits from 0.1 — a factor of two — silently, on every run. NM-TRAN refuses the identical stream before any estimation (error 24). Judged against the declared numbers, not the packed ones: theta TVCL(-5.0, 0.0, 10.0) is reported even though -5 and the declared lower 0 both pack onto the same 1e-10 floor, and the message names where the fit really begins (1e-10, which is neither the declared value nor the declared bound). A start exactly on a bound is not reported: there the clamp is a no-op, so nothing moves. Shares E_OMEGA_INIT_AT_RAIL’s maxiter = 0 exemption. See #1251. |
W_INIT_OUTSIDE_BOUNDS |
warning | An initial estimate strictly outside one of ferx’s internal rails rather than a declared bound: the hidden 1e9 θ cap, the Ω ±6 (variance ≤ 1.6e5) / off-diagonal ±10 guards, or the Σ [-8, 5] guard. Clamped there before the first objective evaluation. The Σ rails are on the SD scale, which is how ferx stores σ — a plain sigma X ~ v declares the variance v and the message quotes sqrt(v), so sigma PROP_ERR ~ 1e6 reads an SD of 1.000e3. A warning and not an error because these recover: omega ~ 1e8 on examples/warfarin.ferx reaches the base optimum under all eight optimizer × method arms. The Ω lower rail is E_OMEGA_INIT_AT_RAIL instead, where the fit is genuinely trapped. There is no 1e-10 θ counterpart here: a start below the floor packs equal to its floored bound, so it is W_INIT_NOT_REPRESENTABLE below instead, and a range lying entirely below it is E_INIT_BOUNDS_INVERTED. |
When the box is empty
| Code | Severity | Meaning |
|---|---|---|
E_INIT_BOUNDS_INVERTED |
error | A coordinate whose packed box is empty, so no start can be placed in it at all. Only a theta can reach this through the parser — every other segment’s bounds are constants — but the code is not θ-specific, and a non-θ coordinate is reported the same way. Three causes: the declared bounds are swapped (theta TVCL(1.0, 5.0, 2.0)); the declared range lies entirely above ferx’s 1e9 packing cap; or it lies entirely below the 1e-10 floor — theta TVCL(1e-12, 1e-13, 1e-11), an ordinary small parameter, is the last. Reported even at maxiter = 0, unlike every other code in this table: an empty box is not clamped, it aborts the clamp, and an evaluation-only run clamps too. Only the affected coordinate is silenced — every other parameter in the file is still reported in the same pass. |
When the packed space cannot represent the declared value
A different question from the three above, and the one they are structurally blind to: pack_params applies its own guards before any box exists, and the box is then built from the already-moved value — so a coordinate reported here is, by construction, perfectly in-box.
| Code | Severity | Meaning |
|---|---|---|
W_INIT_NOT_REPRESENTABLE |
warning | A declared value the packer itself altered, before any box existed (#1307): the 1e-10 log floor on a θ / Ω-diagonal / Σ / [mixture] override, or the Fisher-z ±3 rail on a free block_sigma correlation. The message names the declared value and the value the optimizer actually sees. A separate code from W_INIT_OUTSIDE_BOUNDS because every coordinate it names is, by construction, inside its box — the box is built from the moved value — so a silent W_INIT_OUTSIDE_BOUNDS does not mean the declared values reached the optimizer. Not exempt at maxiter = 0: an evaluation-only run scores the substituted value too. A FIXed correlation is never reported, since #1307 holds it exactly. |
Method and optimizer compatibility
The chosen estimation method or optimizer cannot serve this model.
| Code | Severity | Meaning |
|---|---|---|
E_SAEM_NO_RANDOM_EFFECTS |
error | method = saem (anywhere in a method chain) on a model with no random effects (n_eta = 0). SAEM is an EM over the random effects; use foce / focei / laplace. |
E_METHOD_NO_RANDOM_EFFECTS |
error | method = imp, impmap or bayes (anywhere in a method chain) on a model with no random effects (n_eta = 0). Each integrates over the random effects, so with none declared the marginal collapses to the observation likelihood; use foce / focei / laplace. See #1007. |
W_GN_NO_RANDOM_EFFECTS |
warning | method = gn as the last estimating stage on a model with no random effects (n_eta = 0). Pure Gauss-Newton is start-sensitive there — with no inner EBE loop the BHHH step can collapse far from the optimum. Prefer gn_hybrid or focei. Not reported when a later stage re-optimises the GN result (gn_hybrid, or a methods = [gn, focei] chain). See #1006. |
W_AUTO_OPTIMIZER_FOLLOWS_GRADIENT |
warning | gradient = fd set while optimizer is left at auto, on a model whose analytic outer gradient is in scope. auto resolves off the gradient’s availability, so the one line moved the optimizer (to bobyqa) as well as the gradient. The message names both the optimizer that ran and the one an unforced arm would use; pin optimizer in both arms of a comparison. See #1381 and gradient = fd moves the optimizer too. |
E_IMP_CHAIN |
error | imp is mis-placed in a method chain — repeated, or (with imp_eval_only = true) not the terminal stage. The estimating imp may sit anywhere; an evaluation-only imp must be terminal. |
E_OPTIMIZER_IOV |
error | optimizer = trust_region used with an IOV model (n_kappa > 0). |
E_OPTIMIZER_AGQ |
error | optimizer = trust_region used with a quadrature stage (method = laplace, or method = focei with n_agq > 1). The trust region scores the quadrature objective but its gradient is the FOCE/Laplace closed form, so it would converge to the FOCE optimum while reporting quadrature OFVs. |
E_SDE_INCOMPATIBLE |
error | An SDE ([diffusion]) model used with an incompatible method (saem, gn, gn_hybrid) or gradient setting. |
Data and dosing
The dataset could not be read, or a dose / observation record is inconsistent with what the model expects.
Records and observations
| Code | Severity | Meaning |
|---|---|---|
E_DATA |
error | The --data file could not be read or parsed. |
E_MISSING_COVARIATE |
error | The model references a covariate not present in the data (case-sensitive). |
E_NONFINITE_DV |
error | A subject carries a non-finite (NaN/Inf) observation. Usually a population read by read_population_for_simulation() — a simulation input — passed to fit(). |
W_MISSING_DV |
warning | One or more EVID=0 rows had a missing DV (./NA/blank) but were not marked MDV=1; they were skipped rather than scored as DV=0. |
W_DESIGN_DV |
warning | Simulation reader only: one or more EVID=0 rows had a missing DV and were kept as design points to simulate at, so the dataset contributes more rows than a fit of the same file would. |
W_CENS_UNEXPECTED |
warning | An observation row’s CENS cell is a whole number other than -1 (above ULOQ), 0 (quantified) or 1 (below LLOQ), such as 7 or -2. Every engine reads only the flag’s sign, so under bloq_method = m3 a positive flag is scored on the lower tail, like 1, and a negative one on the upper tail, like -1; under bloq_method = drop the row is scored as an ordinary observation. The message quotes the cell as written (200, not the 127 it saturates to) and is reported once per subject for each sign. It is not raised on a simulation design row with no DV, which is never scored. A cell that is not a whole number (1.5, abc, inf) is not this warning but a read error, reported as E_DATA, on a Gaussian observation row that carries a DV and that [data_selection] keeps; on a dose row, a row whose DV is missing, or a row the filter removes, the cell is not read (#1496). |
W_FILTER_COLUMN_ABSENT |
warning | A [data_selection] condition names a column the dataset does not have. The comparison never matches, so the condition does not select what was meant — and what that costs depends on the key it was written under: an ignore excludes nothing, an accept excludes every row. The message names the missing column(s) and lists the headers the file does have, so a typo (ignore = Coment) is visible rather than silent. ferx check --data reports it since #1465; before that the check never compiled the clauses, so only a fit could raise it. |
Dose coverage
Whether the dataset delivers any drug at all. These read the dose records as a set, so they say nothing about an individual row — for that see Dose records below.
| Code | Severity | Meaning |
|---|---|---|
W_AMT_NOT_DOSED |
warning | Record(s) carry AMT != 0 but were not read as dose events, because EVID is neither 1 nor 4; their AMT was ignored. In a dataset with no EVID column a nonzero AMT is enough to infer a dose, so this is a file that has one. Reported with the record and subject counts. It wins over W_NO_DOSES, which is the generic backstop for a dataset carrying no AMT signal at all. See #262. |
W_NO_DOSES |
warning | Zero dose events parsed across every subject, although scored observations are present. Silent when there are no scored observations (an all-EVID=2, covariate-only file is not a fit), and silent when any subject carries a non-Gaussian observation — a TTE / survival or discrete endpoint legitimately has no PK dose, so warning there would be a false positive. See #262. |
W_ALL_DOSES_ZERO |
warning | An AMT column is present and at least one dose event was parsed, but every parsed amount is exactly zero. No drug enters the system, so the objective is flat and the parameters will not move from their initial values. A warning rather than an error because the column is there and a genuinely dose-free-but-AMT-columned dataset is conceivable. See #753. |
Dose records
| Code | Severity | Meaning |
|---|---|---|
W_ADDL_MISSING_II |
warning | ADDL > 0 on a dose row but II is zero or missing; additional doses were not expanded. |
W_COMPARTMENT_FREE_DOSES |
warning | The dataset carries dose records but the [structural_model] is compartment-free ($PRED-equivalent), so no dose is applied and AMT / RATE / SS / CMT on those rows have no effect on the prediction. Reported once, with the dose-event count (after ADDL expansion) and the number of dosed subjects. Every dose-level check that would read the model’s compartments is skipped for this class — a coded RATE=-1 / -2 row is not E_MODELED_*_NO_PARAM (a D{n} / R{n} the model cannot consume) and a CMT=2 row is not E_DOSE_CMT_OUT_OF_RANGE (the placeholder one-compartment topology the parser installs) — so the rows land here instead (#1443). |
W_CMT_DEFAULTED |
warning | Dose and observation rows were assigned compartment 1 because the dataset did not say which one — no CMT column, or a cell that is missing (./blank/NA) or not a compartment index. The message gives the row counts (plus the ADDL-expanded dose count when a defaulted row carried ADDL) and, for a present column, the offending spellings. Reported whenever CMT selects something on the model: more than one compartment a dose can reach — more than one [odes] state, or an analytical model whose CMT=2 is a real target (an oral model’s depot-bypassing central bolus, a 2-cpt model’s peripheral) — or an observation-side dispatcher, a per-CMT [scaling] block, a CMT=N: error model, or a per-CMT y[CMT=N] readout on either engine, or a declared endpoint the row routes to (unless the model is endpoint-only and that endpoint is compartment 1, where the fallback is provably right), or a [data_selection] clause comparing CMT. Silent where CMT chooses nothing: one_cpt_iv, a one-state [odes] model, a compartment-free model, the transit / inverse-Gaussian absorption models, and a [data_selection] clause on any other column. Rows a CMT-reading filter clause removed are counted and named separately, since they never reach the dose / observation counts (#1409). Reporting a declared endpoint duplicates E_ENDPOINT_NO_RECORDS on an absent column, deliberately: an endpoint routes rows by CMT, and suppressing the warning hid a silent re-routing between endpoints on a bad cell, where that error cannot fire. NONMEM has no equivalent finding because $MODEL lets it declare DEFDOSE; ferx has no such declaration, so an absent column is a genuine ambiguity rather than a default (#1009). |
W_STEADY_STATE_II |
warning | SS=1 doses with missing / non-positive II (treated as non-SS). |
W_STEADY_STATE_INFUSION |
warning | SS=1 infusion whose length after bioavailability exceeds II (overlapping pulses; SS skipped) — the run-in’s own test (#1281). |
W_STEADY_STATE_ABSOLUTE_TIME |
warning | An SS=1 dose (II > 0) on a model whose [odes] right-hand side reads an absolute clock — TAFD, T/t, or the bare TIME built-in. The run-in standing in for the infinite past expands the dose train on a clock local to each cycle, so an absolute clock has no periodic steady state to converge to: T/TIME return a finite number that matches NONMEM’s own steady-state routine but is 67 % from the same model’s explicit dose train, and TAFD reads NaN whatever coefficient it carries. TAD is not included — it is bounded inside one dosing interval, so the run-in does reproduce its train and is anchored against NONMEM (#1139). Counted per dose that actually reaches the run-in, so an SS infusion the run-in itself skips — one whose length after bioavailability exceeds its own II — or a dose on a compartment outside the state vector is not swept up. Asked of the PK block only, so a TIME-reading joint PK-TTE hazard does not trigger it, and TIME read from [individual_parameters] is out of scope; one shape over-reports, see Steady state with TAFD or TIME. |
Dose attributes
Bioavailability F, lag time ALAG/LAGTIME, and the modeled infusion duration D{n} / rate R{n} behind a coded RATE — the values the engine applies at a dose event.
| Code | Severity | Meaning |
|---|---|---|
E_DOSE_ATTR_DOUBLE_USE |
error | A dose attribute the engine applies at the dose event is also read by the model, so its value is applied twice. Bioavailability and lag are caught at parse time on both engines (message names [odes], [scaling] or [adaptive_dosing]) — on ODE models via the name (F, LAGTIME/ALAG, F{n}/ALAG{n}/LAGTIME{n}), on analytical models via the pk(..., f=…) / lagtime=… mapping, where the remedy is to drop the mapping rather than rename. D{n}/R{n} are reported only when a coded RATE=-2/-1 dose in the data lands on them. Initial conditions are exempt on both engines — [odes] init(state) = … and [initial_conditions] seed the state with the raw value and never consult a dose attribute, so a seed that reads one applies it exactly once (#1046). See #993 and #1004. |
W_NEGATIVE_LAGTIME |
warning | Lag time is negative at the initial typical-value point. NONMEM aborts on this (PK PARAMETER FOR ABSORPTION LAG IS NEGATIVE, PROGRAM TERMINATED BY OBJ); ferx warns and starts the subject’s timeline at the earlier onset, identically on every engine (#1189). |
E_DOSE_ATTR_NONFINITE |
error | A dose attribute the engine applies at the dose event — lag time (lagtime / ALAG{n}), bioavailability (F / F{n}), or a modeled infusion duration D{n} / rate R{n} behind a coded RATE — is NaN or infinite at typical values for some subject. Checked at every dose record’s own snapshot: that record’s covariate values and its TIME, which is where the engine reads the attribute (#1235). The message names the subject, the compartment, the attribute and the record. Usually a covariate relationship undefined for that subject; the common one is an exponential model on an unscaled covariate (ALAG1 = TVLAG*exp(WT)), since exp is the one DSL function with no domain guard — /, ln and sqrt are all floored. Every attribute now has the same consequence: the subject’s predictions come back NaN, which the estimator reads as a diverged solve. For a lag or F that has always been so — they make dose.time + lag, and every break derived from it, non-finite, so the timeline cannot be ordered. A non-finite D{n}/R{n} used to be the quiet exception, and it was the dangerous one: it never reached you as a NaN at all, because an infinite D{n} gives rate = amt / D = 0 (not an infusion at all) and a NaN was clamped to the transient-D ≤ 0 floor, so the dose was served as an instantaneous bolus and the fit returned finite but silently wrong numbers — measured 2.03x high against the correct infusion at one elimination half-time. Since #1284 a non-finite D{n}/R{n} repels the subject like every other attribute, so this check is the fit-init front door rather than the only line of defence. See #1189. |
Endpoint routing
| Code | Severity | Meaning |
|---|---|---|
E_ENDPOINT_UNROUTED |
error | A compartment the model declares as a non-Gaussian endpoint ([event_model], [binary_model], Markov) carries Gaussian observations: the population was read without endpoint routing (read_nonmem_csv() instead of read_population_for()), so the endpoint’s rows would be scored as concentrations and its likelihood would contribute nothing. Raised by fit(), simulate(), predict(), predict_categorical(), run_covariance() and run_sir() (#1199). |
E_ENDPOINT_NO_RECORDS |
error | A declared non-Gaussian endpoint has no row routed to it anywhere in the data — typically the CMT column is missing, so every row reads as CMT 1. Raised by the entry points that run the likelihood — fit(), ferx check, run_covariance(), run_sir() — not by simulate() / predict(): a simulation template has no event rows by construction (#1199). |
Scaling, output and derived columns
An observed compartment is missing a scaling or error-model entry, a declared per-CMT entry matches no observation, or an [output] / [derived] name does not resolve.
| Code | Severity | Meaning |
|---|---|---|
E_PER_CMT_SCALING |
error | An observed compartment lacks a per-CMT scaling entry. |
E_PER_CMT_ERROR_MODEL |
error | An observed compartment lacks a per-CMT [error_model] entry. |
W_PER_CMT_UNMATCHED |
warning | The other direction of the two above: a compartment the model declares an entry for, that no observation is recorded on. One code for all three per-CMT channels — [scaling] obs_scale[CMT=N], [error_model] CMT=N:, and a y[CMT=N] readout on either engine — with the channel and the dead compartments named in the message. A warning rather than an error because the entry is inert rather than wrong: nothing goes NaN, and a scaling block shared across studies may legitimately declare more compartments than one dataset exercises. The usual cause is on the data side, a missing or mis-mapped CMT column keying every observation to compartment 1; on a column-less dataset W_CMT_DEFAULTED also fires, for the defaulting itself, while this code names the entries the defaulting left dead. That reading is offered only when the observed set is consistent with it (every observation on compartment 1), so a model observed on compartment 2 is not sent to audit a column that is present and correct. [data_selection] is named as a candidate cause — rather than suppressing the finding, since a filter on any column can remove exactly the rows an entry needed — but only for the dead compartments the clause actually emptied, which ExclusionSummary::obs_cmts_excluded records; a clause that removed rows on some other compartment is not blamed. Silent on an observation-free population, so ferx check without --data does not report every declared entry as dead (#1405). |
E_DERIVED_NAME_CONFLICT |
error | A [derived] name clashes with a built-in sdtab column, theta, eta, or individual-parameter name. |
W_DERIVED_COVARIATE_SHADOW |
warning | A [derived] name shadows a covariate (allowed but may be confusing). |
W_DERIVED_STEP_IGNORED |
warning | step= given for a DV-based integral (ignored; DV integrals always use observation times). |
E_OUTPUT_UNKNOWN_COLUMN |
error | A name in [output] is not recognised as a covariate, individual parameter, or derived expression. |
W_OUTPUT_DUPLICATE |
warning | A name in [output] is already written to sdtab automatically (e.g. TAFD, TAD, an eta name). |
Experimental and degraded paths
The model runs, but on a path that is experimental or that quietly drops a capability.
| Code | Severity | Meaning |
|---|---|---|
W_EXPERIMENTAL_SDE |
warning | The model uses stochastic differential equations ([diffusion]), an experimental feature. The filter is covariance-only — the state mean is never corrected by the data — so see What the filter does not do. |
W_EXPERIMENTAL_NN |
warning | The model uses neural-network components ([covariate_nn]), an experimental feature. |
W_SDE_RESET |
warning | EVID=3/4 resets under an SDE [diffusion] model are not honoured. |
W_ABSORPTION_TWIN_DECLINED |
warning | An analytic transit / inverse-Gaussian model’s ODE twin could not be built, so the model stays closed-form with no ODE fallback. Subjects needing the fallback (time-varying covariates, a TIME-dependent parameter, IOV, SS or infusion doses, the flip-flop regime) are then rejected with an explicit error. The message carries the twin parser’s own reason — most often an individual parameter named after a twin state (CENTRAL, PERIPH). See #1008. |
Line-number caveat
line is currently block-level: when present it points at the [block] header that owns the finding, not the exact offending token or column. A finding that is not attributable to a single block (for example a missing-covariate error, where the reference may appear in several blocks) omits line. A missing required block omits line too, since the block has no header in the source. Token/column-level spans are a possible future enhancement.