Output Files

Each model run produces three output files (plus a fourth, {model}-covtab.csv, when the model declares a [covariates] block).

Quick reference: where to find what

Different quantities live in different outputs — this table is the fastest way to find what you need.

What you want Where it lives Shape When present
Covariates in sdtab (via [output]) {model}-sdtab.csv one row per observation declared in [output]
Raw covariate values for all dataset records {model}-covtab.csv one row per dataset record (doses + obs) model has [covariates] block
ETA / EBE values per subject ebes.csv inside .fitrx one row per subject always
[derived] computed columns {model}-sdtab.csv one row per observation model has [derived] block
[output] declared columns (covariates, individual PK parameters) {model}-sdtab.csv one row per observation declared in [output]

Key distinctions:

  • [output] covariates in the sdtab use LOCF — each observation row carries the covariate value that was active at that time. The covtab carries the covariate exactly as read from each row of the input dataset, including dose and event rows where sdtab has no entry.
  • ETA / EBE values are not written to sdtab. They live in fit$ebe_etas (and ebes.csv in the .fitrx bundle) — one row per subject. ETAs are available as context variables in [derived] expressions (e.g. KE = CL / V can reference ETA_CL), but are not columns in the sdtab itself.

sdtab CSV ({model}-sdtab.csv)

A CSV file with per-observation diagnostics, one row per observation per subject.

Columns

Column Description
ID Subject identifier
TIME Observation time
DV Observed value
CENS Censoring flag (0, 1, or -1); omitted when no censored observations
OCC Occasion label; omitted when model has no IOV block
CMT Observation compartment; omitted for single-compartment models
PRED Population prediction (eta = 0)
IPRED Individual prediction (eta = EBE)
CWRES Conditional weighted residual
IWRES Individual weighted residual
EBE_OFV Each subject’s contribution to the total OFV
N_OBS Number of observations for the subject
TAFD Time after first dose
TAD Time after most recent dose (SS-aware)
[derived] names One column per expression in the [derived] block
[output] names Covariates and individual parameters declared in [output]

Residual Definitions

IWRES (Individual Weighted Residual): \[ \text{IWRES}_j = \frac{y_j - \text{IPRED}_j}{\sqrt{V_j}} \] where \(V_j\) is the residual variance evaluated at the individual prediction.

CWRES (Conditional Weighted Residual): \[ \text{CWRES}_j = \frac{y_j - f_{0,j}}{\sqrt{\tilde{R}_{jj}}} \] where \(f_0 = f(\hat{\eta}) - H\hat{\eta}\) is the linearized population prediction and \(\tilde{R} = H\Omega H^T + R\) is the conditional variance.

Example

ID,TIME,DV,PRED,IPRED,CWRES,IWRES,EBE_OFV,N_OBS,TAFD,TAD
1,0.5,9.49,10.12,9.55,-0.23,-0.06,2.14,8,0.5,0.5
1,1.0,14.42,14.87,14.35,0.18,0.05,2.14,8,1.0,1.0

covtab CSV ({model}-covtab.csv)

Written only when the model declares a [covariates] block. Unlike sdtab (observation rows only), it echoes the declared covariate columns with one row per input dataset record, including dose and other-event rows. Missing values are written as empty cells. It is also available programmatically as FitResult::covariate_table.

Columns

Column Description
ID Subject identifier
TIME Record time
EVID Event ID of the source row (0=obs, 1=dose, 2=other, 3=reset, 4=reset+dose)
declared covariates One column per covariate in the [covariates] block, in declaration order

Example

ID,TIME,EVID,WT,CRCL
1,0.000000,1,70.600000,73.700000
1,0.500000,0,70.600000,73.700000
1,1.000000,0,70.600000,73.700000

Fit YAML ({model}-fit.yaml)

A YAML file containing parameter estimates, standard errors, and model diagnostics.

Structure

model:
  converged: true
  method: FOCE
objective_function:
  ofv: -280.1838
  aic: -266.1838
  bic: -247.2804
data:
  n_subjects: 10
  n_observations: 110
  n_parameters: 7
theta:
  TVCL:
    estimate: 0.132735
    se: 0.014549
    rse_pct: 11.0
  TVV:
    estimate: 7.694842
    se: 0.293028
    rse_pct: 3.8
omega:
  omega_11:
    variance: 0.028584
    cv_pct: 16.9
    se: 0.006394
  omega_22:
    variance: 0.009613
    cv_pct: 9.8
    se: 0.002165
sigma:
  sigma_1:
    estimate: 0.010638
    se: 0.000788
covariance_matrix:
  # optimizer parameterization: theta log-transformed when lower bound >= 0
  # (identity otherwise), sigma log-transformed, omega/kappa Cholesky-factored
  parameters: [TVCL, TVV, log_chol_ETA_CL, log_chol_ETA_V, sigma_1]
  rows:
    TVCL:            [1.234567e-4, 2.345678e-5, 0.000000e+0, 0.000000e+0, 0.000000e+0]
    TVV:             [2.345678e-5, 1.456789e-3, 0.000000e+0, 0.000000e+0, 0.000000e+0]
    log_chol_ETA_CL: [0.000000e+0, 0.000000e+0, 5.678901e-5, 0.000000e+0, 0.000000e+0]
    log_chol_ETA_V:  [0.000000e+0, 0.000000e+0, 0.000000e+0, 2.345678e-6, 0.000000e+0]
    sigma_1:         [0.000000e+0, 0.000000e+0, 0.000000e+0, 0.000000e+0, 7.890123e-7]

The covariance_matrix: block is only present when the covariance step ran successfully or was regularised. The values are in optimizer space — thetas and sigma are on the log scale (or identity scale when the lower bound is negative), omega and kappa are Cholesky-factored. The parameters: list gives the canonical column order; rows: keys match that order. Omega and kappa diagonal entries appear as log_chol_<eta> — the packed value is log(L_ii) where omega = L Lᵀ (log of the Cholesky diagonal, not the variance). Off-diagonal Cholesky entries appear as chol_<eta_row>_<eta_col> (L_ij, not log-transformed).

Key Fields

  • ofv: Objective Function Value (-2 log-likelihood)
  • aic: Akaike Information Criterion (OFV + 2p)
  • bic: Bayesian Information Criterion (OFV + p * ln(n))
  • se: Standard error from the covariance step
  • rse_pct: Relative standard error as percentage (SE/estimate * 100)
  • cv_pct: Coefficient of variation for omega (sqrt(variance) * 100)

FitResult Fields (Rust API / console)

The following fields are populated on the FitResult struct returned by fit() and printed by print_results(). They are not currently written to the fit YAML — read them programmatically or from the console summary.

Shrinkage

Two shrinkage metrics are reported after every fit:

ETA shrinkage (per random effect — shrinkage_eta: Vec<f64>): \[ \text{shrinkage}_k = 1 - \frac{\sqrt{\frac{1}{n}\sum_i \hat{\eta}_{k,i}^2}}{\sqrt{\omega_{kk}}} \]

A value near 1 means individual EBEs are all pulled toward zero — the data are not informative about that random effect. A value near 0 means the ETAs are spread consistent with the prior omega.

EPS shrinkage (scalar — shrinkage_eps: f64): \[ \text{shrinkage}_\varepsilon = 1 - \sqrt{\frac{1}{n}\sum_j \text{IWRES}_j^2} \]

Both formulas use the uncentered second moment with n divisor, matching the NONMEM / PsN / Monolix convention — the population model assumes E[η]=0 and E[IWRES²]=1, so the natural estimator is √(Σx²/n) rather than the unbiased sample SD (which centers on the sample mean and divides by n-1). The unbiased form would inflate SD by √(n/(n-1)) and routinely produce spurious negative shrinkage on small samples.

Negative shrinkage_eps is mathematically possible and meaningful: it indicates mean(IWRES²) > 1, i.e. the residual error model does not absorb the residuals seen at the final EBE etas. Common causes include SAEM converging to a local optimum with under-fit sigma (often resolved by polishing with method = [saem, focei] or trying a different start), model misspecification for a subset of subjects (check the IWRES distribution in the sdtab for outliers), or sigma at a bound. When shrinkage_eps < -5%, ferx emits a warning to FitResult.warnings; the raw value is retained for parity with NONMEM/PsN.

Values of NaN indicate a zero-variance omega component (ETA) or fewer than two valid residuals (EPS).

Kappa shrinkage (IOV models only — shrinkage_kappa: Vec<f64> and shrinkage_kappa_by_occ: Vec<Vec<f64>>):

When the model contains kappa or block_kappa declarations, two additional shrinkage metrics are computed using the same uncentered-moment convention.

Pooled — one value per kappa parameter j, averaged over all N_\text{pairs} (subject, occasion) pairs: \[ \text{shrinkage}_{\kappa,j} = 1 - \frac{\sqrt{\frac{1}{N_{\text{pairs}}}\sum_i \sum_{q} \hat{\kappa}_{iqj}^2}}{\sqrt{\omega_{\text{iov},jj}}} \] where q indexes occasions and N_\text{pairs} = \sum_i K_i (total subject-occasion pairs; equals N_\text{subj} \cdot K only for balanced designs).

Per-occasion slot — the same formula restricted to occasion slot occ_idx, stored in shrinkage_kappa_by_occ[occ_idx][kappa_idx]. Only reported when two or more occasions are present. Useful for identifying sparse occasions (high shrinkage in one slot suggests that occasion has little information on kappa).

Note on unbalanced designs: occ_idx is the 0-based position within each subject’s own occasion list (order of first appearance in that subject’s rows), not the raw OCC column value. When subjects have different OCC sequences (e.g., a late-entry subject whose data begins at OCC 2), a given slot may pool kappas from different occasions across subjects. In that case, use the pooled shrinkage_kappa and interpret per-slot values with caution.

Both metrics are NaN when omega_iov diagonal is zero or fewer than two subject-occasion observations are available for that slot.

IWRES Autocorrelation

Two pooled autocorrelation diagnostics are reported after every fit:

iwres_lag1_r — pooled lag-1 Pearson correlation of IWRES across subjects. Values near 0 indicate no serial dependence; values approaching ±1 indicate strong autocorrelation.

dw_statistic — pooled Durbin-Watson statistic: \[ \text{DW} = \frac{\sum_i \sum_t (e_{i,t} - e_{i,t-1})^2}{\sum_i \sum_t e_{i,t}^2} \]

DW range Interpretation
≈ 2.0 No autocorrelation
< 1.5 Positive autocorrelation — structural model likely missing dynamics
> 2.5 Negative autocorrelation — possible over-parameterization or misspecified error model

Subjects with fewer than 2 finite IWRES values are excluded from both statistics. Both fields are NaN when no subject qualifies.

When dw_statistic < 1.5, ferx emits a warning suggesting a transit absorption model, additional compartment, or IOV on ka/F (plus SDE process noise for ODE models). When dw_statistic > 2.5, the warning suggests over-parameterization or a misspecified error model.

Covariance Status

covariance_status: CovarianceStatus takes one of three values:

Value Meaning
Computed Covariance step succeeded; SE values are valid
Failed Hessian was singular or inversion failed; SE fields are None
NotRequested covariance = false was set; SE fields are None

Run Record Fields

Field Description
model_name Name from the .ferx file (or "Unnamed")
ferx_version Version of ferx-core that produced the result
wall_time_secs Wall-clock time for the complete fit (seconds)
gradient_method_inner Gradient method used in the inner (EBE) loop, e.g. analytic (Dual2) or finite differences
gradient_method_outer Gradient method used in the outer loop, e.g. finite differences
uses_ode_solver true if the model uses the ODE solver, false for analytical PK
n_threads_used Number of Rayon threads used during estimation
nlopt_missing_algorithms NLopt algorithms that were requested but unavailable in this build (empty when all available)
covariance_n_evals_estimated Estimated number of OFV evaluations the covariance step will run, populated only when run_covariance_step = true and n_parameters > 30

EBE Convergence Diagnostics

Counters from the inner-loop (EBE) optimizer, useful for diagnosing problematic fits. Always 0 for SAEM (which uses MH sampling rather than EBE optimization).

Field Description
ebe_convergence_warnings Number of outer iterations in which at least one subject had an unconverged EBE
max_unconverged_subjects Worst-case unconverged-subject count seen in a single outer iteration
total_ebe_fallbacks Total number of times the Nelder-Mead fallback was invoked across all subjects and outer iterations

Timing File ({model}-timing.txt)

A single-line text file with the wall-clock estimation time:

elapsed_seconds=0.496000

This measures only the estimation step (not parsing or data reading).

Optimizer Trace CSV

When optimizer_trace = true is set in [fit_options], a CSV is written to /tmp/ferx_trace_<pid>_<ts>.csv. The path is also stored in FitResult::trace_path.

Each row is one outer iteration. See the fit-options trace table for the full column reference.

Example use in R (with the ferx package):

fit <- ferx_fit("model.ferx", "data.csv", optimizer_trace = TRUE)
trace <- read.csv(fit$trace_path)
plot(trace$iter, trace$ofv, type = "l", xlab = "Iteration", ylab = "OFV")