Fit a nonlinear mixed effects model
Description
Fits a NLME model using FOCE or FOCEI estimation with a Rust backend.
Usage
ferx_fit(
model,
data = NULL,
method = NULL,
covariance = NULL,
verbose = NULL,
bloq_method = NULL,
threads = NULL,
mu_referencing = NULL,
sir = NULL,
gradient = NULL,
optimizer_trace = FALSE,
scale_params = FALSE,
inits_from_nca = FALSE,
fd_hessian_step = NULL,
settings = NULL,
output = NULL,
include_data = FALSE,
ignore = NULL,
accept = NULL,
ignore_ids = NULL,
...
)Arguments
model: Path to a.ferxmodel file, or a[ferx_model](ferx_model.qmd)object.data: Path to a NONMEM-format CSV file. Required columns: ID, TIME, DV, EVID, AMT, CMT. Optional columns recognised by the engine: RATE (infusion rate;RATE = -1infusesAMTat a modeled rate given by a per-subject parameterR{n}, andRATE = -2over a modeled duration given byD{n}, on dose compartmentn), MDV (missing-DV flag), II (dosing interval, required when SS > 0), SS (steady-state flag: 1 = pre-dose at steady state, 2 = add SS concentration to current state), CENS (LOQ censoring flag for M3 method:1below LLOQ,-1above ULOQ), OCC (occasion index for IOV), and any covariate columns referenced in the model. See the steady-state section below for SS/II details.method: Estimation method(s).NULL(the default) uses whatever the model file’s[fit_options] methodspecifies, falling back to"focei"if the model file sets none (the engine warns when it defaults). Pass a value here to override the model file: either a single string or a character vector of methods to run in sequence. Each stage is seeded with the previous stage’s converged parameters, and only the final stage produces the reported covariance/diagnostics. Supported methods:"foce","focei","saem","gn"(Gauss-Newton / BHHH),"gn_hybrid"(Gauss-Newton followed by a FOCEI polish step),"imp"(also accepted as"importance_sampling"or"importance-sampling"; the NONMEMMETHOD=IMPimportance-sampling Monte-Carlo EM estimator), or"impmap"(also accepted as"importance_sampling_map"; Importance Sampling assisted by Mode A Posteriori, the NONMEMMETHOD=IMPMAPMonte-Carlo EM estimator)."imp"is an estimator by default (it updates parameters) and may run standalone (method = "imp"), lead, or sit mid-chain. Setsettings = list(imp_eval_only = TRUE)(NONMEMEONLY=1) to make it instead evaluate the marginal-2 log Lat the fixed input parameters; in that mode it must be the last entry of the chain, e.g.c("focei", "imp"). Plain"imp"re-centers its proposal from the previous iteration’s samples and so is fragile on rich data; prefer"impmap"or warm-start withc("focei", "imp")there."impmap"is a full estimator: it may run standalone (method = "impmap") or as a chain stage (c("focei", "impmap")), requires a mu-referenced parameterization, and does not yet support IOV. Example chain:c("saem", "focei")."bayes"(also accepted as"bayesian"or"mcmc") runs full MCMC Bayesian estimation (Gibbs-within-HMC, NONMEMMETHOD=BAYESparity): it returns posterior means with credible intervals and convergence diagnostics on$bayesrather than a point estimate, and runs standalone. Supports BSV and zero-mean inter-occasion variability (per-occasionkappa); the IOV variance posterior appears asOMEGA_IOV(...)in$bayes. SAEM fully supports inter-occasion variability (IOV / kappa) models.covariance: Logical, orNULL(the default). Whether to compute the covariance step for standard errors.NULLuses the model file’s[fit_options] covariance(engine defaultTRUEwhen unset); a logical overrides the model file. Previously defaulted toTRUEand silently overrode a model file that setcovariance = false(#558).verbose: Logical, orNULL(the default). Print progress during estimation.NULLuses the model file’s[fit_options] verbose(engine defaultTRUEwhen unset); a logical overrides it.bloq_method: Handling of observations outside quantification limits.NULL(default) keeps whatever the model file specified;"m3"enables Beal’s M3 likelihood (requires aCENScolumn in the data, withDVcarrying the limit value: LLOQ onCENS=1rows and ULOQ onCENS=-1rows);"drop"disables M3 and treats censored rows as ordinary observations.threads: Number of worker threads for the per-subject parallel loops in the Rust backend (inner EBE search, SAEM, SIR).NULL(default) leaves the backend’s thread pool at its default of one worker per logical CPU. Pass an integer to cap the pool - useful on shared machines, in CI, or to avoid SMT-induced contention (tryparallel::detectCores(logical = FALSE)). The setting is per-call, so successive fits in the same R session can use different values.mu_referencing: Logical, orNULL(the default). WhenTRUE, automatically detects mu-referencing from the model structure for faster and more accurate convergence.NULLuses the model file’s[fit_options] mu_referencing(engine defaultTRUEwhen unset). Applies to all estimation methods. Set toFALSEto disable for comparison purposes. Detection works automatically for standard parameterizations such asPARAM = THETA * exp(ETA); unusual parameterizations fall back silently to zero-centred ETA initialisation with no error. No changes to the.ferxmodel file are needed. Checkfit$warningsto see which ETAs were detected.sir: Logical, orNULL(the default); run Sampling Importance Resampling after the fit to produce non-parametric parameter uncertainty intervals. Requirescovariance = TRUE.NULLuses the model file’s[fit_options] sir(engine defaultFALSEwhen unset). Tuning knobs (sir_samples,sir_resamples,sir_seed) still flow throughsettings.gradient: Inner-loop (per-subject EBE) gradient method. One of"auto"or"fd", orNULL(the default) to use the model file’s[fit_options] gradient(engine default"auto"when unset). The legacy"ad"token is accepted by older model files but should not be used for new code.The inner optimizer is BFGS; what differs is how the gradient of the individual NLL w.r.t. ETA is computed:"fd": central finite differences,2 * n_etaforward evaluations per gradient call. Always available."auto"(default): let the engine choose the best available gradient path for the model. This is the right choice for almost all uses.When to set"fd"explicitly. Mainly for validation or for reproducibility against a known finite-difference baseline.SetFERX_TIME_GRADIENTS=1in the environment to print per-gradient-call timings at the end of a fit, which is the easiest way to check which method is faster on your specific model/data.optimizer_trace: Logical. IfTRUE, write a per-iteration CSV trace to a temporary file and store its path infit$trace_path. Pass the result to[ferx_trace](ferx_trace.qmd)or[ferx_plot_trace](ferx_plot_trace.qmd)to inspect optimizer progress. DefaultFALSE.scale_params: Logical. IfTRUE, apply a per-coordinate scaling layer on top of the existing log/Cholesky parameterization, dividing each transformed coordinate by|x0[i]|(when|x0[i]| > 0.1, otherwise by1.0) so the outer optimizer works in a near-unit-magnitude space. DefaultFALSE.Why it defaults toFALSE. The scaling is not trajectory-transparent. Although it leaves the OFV value unchanged at any fixed point, it rescales the gradient the optimizer sees, and that gradient feeds the SLSQP overshoot cap, the quasi-Newton Hessian estimate, and the xtol/ftol termination - all of which act in the scaled coordinate system. The scaling layer only ever runs on log/Cholesky-packed coordinates (it auto-disables when any identity-packed theta is present), and for those, dividing by|log value|is counterproductive: a coordinate likelog(V) = log(20) ~ 3gets scale 3, so the optimizer’s unit step becomes a 3-unit move in log space - ane^3 ~ 20xmultiplicative jump in V. That large step both overshoots and, through the uniform gradient cap, starves the step in every other dimension (notably OMEGA), so the fit can halt well short of the minimum. See ferx-core issue #99. TheFALSEdefault reproduces the well-tested pre-scaling-layer behaviour.When to setTRUE. Left as an opt-in for experimentation - e.g. to A/B the scaled vs unscaled trajectory on a specific model. When enabled it applies to all outer optimizers: NLopt FOCE/FOCEI (BOBYQA, SLSQP, L-BFGS, MMA), the hand-rolled BFGS, Gauss-Newton / BHHH, and the SAEM M-step.inits_from_nca: Derive NCA-based starting values from the data before the optimizer runs, overriding the model file’s defaults. Either a logical (FALSE, the default, disables it;TRUEis an alias for"nca_sweep") or one of"nca","nca_sweep","nca_ebe"to pick a strategy explicitly. Most useful withsettings = list(optimizer = "trust_region")ormethod = "gn", where bad starting values can stall the optimizer. See[ferx_inits_from_nca](ferx_inits_from_nca.qmd)to inspect the values without fitting.fd_hessian_step: Positive finite numeric. Relative step size for the finite-difference Hessian used in the covariance step (default0.01). The actual perturbation for parameter i isfd_hessian_step * (1 + |x_hat[i]|). Increase (e.g.0.1) when the fit warns about ill-conditioned Hessian entries; decrease (e.g.1e-3) on smooth OFV surfaces where FD noise is the primary concern. Has no effect whencovariance = FALSE.settings: Optional named list of fine-grained options forwarded to the RustFitOptions. Use this to tune knobs that do not have a dedicatedferx_fit()argument. Keys are validated: values that duplicate a dedicated argument (method,covariance,verbose,bloq_method,threads,sir) are rejected – pass them via the dedicated argument. Unknown keys and malformed values also raise an error.Precedence: dedicatedferx_fit()arguments win oversettings, which in turn win over the model file’s[fit_options]block. Awarning()is issued whenever a call-time value overrides a different value from[fit_options]. Inspectfit$model_file_settingsandfit$call_settingsto audit the full picture.Shared options (FOCE / FOCEI / GN / GN-hybrid / SAEM)inner_maxiter: Per-subject EBE iteration cap (default 200).inner_tol: Convergence tolerance for the inner EBE loop (default1e-4).fd_hessian_step: Also available as the dedicatedfd_hessian_stepargument above.covariance_method: Covariance estimator, mirroring NONMEM$COV MATRIX=:"r"(inverse Hessian, the default),"s"(score cross-product), or"rsr"(the Huber-White sandwich, robust to model misspecification). Validated for FOCEI;"s"and"rsr"are rejected under non-interaction FOCE. No effect whencovariance = FALSE.covariance_fallback:"none"(default) or"sir". When the finite-difference Hessian is not positive definite,"sir"runs SIR with an absolute-eigenvalue-rectified proposal instead of leaving the covariance step failed;covariance_statusis then"sir_fallback"and SIR-based credible intervals are reported.ODE models: RK45 solver toleranceode_reltol: RK45 relative tolerance for ODE models (default1e-4; ignored for analytical PK). The default reproduces analytical closed forms in PRED to about1e-4, but the FOCE objective amplifies solver error, so an ODE-form model’s OFV can differ from its analytical equivalent by several units. Set tighter (e.g.1e-10) when the ODE-form OFV must match an analytical reference; expect slower fits. Can also be set in the model file’s[fit_options]block.ode_abstol: RK45 absolute tolerance for ODE models (default1e-6).ode_max_steps: Maximum RK45 steps per integration segment (default10000). Raise if a tightode_reltolexhausts the step budget on stiff multi-compartment systems.FOCE / FOCEI / GN / GN-hybrid: iteration capmaxiter: Maximum outer-optimizer iterations (default 500). Not applicable to SAEM, which controls iterations vian_explorationandn_convergence.FOCE / FOCEI / GN-hybrid: outer optimizeroptimizer: Population-level optimizer. One of"auto"(default; picks"nlopt_lbfgs"when the exact analytic FOCE/FOCEI gradient is available and"bobyqa"when only finite differences are – the fit reports the resolved pick as"auto (<resolved>)"),"bobyqa"(derivative-free, robust),"slsqp"(sequential quadratic programming, gradient-based),"lbfgs"/"nlopt_lbfgs"(limited-memory BFGS via NLopt, gradient-based),"mma"(method of moving asymptotes, gradient-based),"bfgs"(hand-rolled BFGS, gradient-based), or"trust_region"(trust-region Newton, gradient-based). Gradient-based optimizers use the inner gradient method set by thegradientargument;"bobyqa"does not. Not accepted by pure"gn".outer_xtol: Relative step tolerance for the derivative-free"bobyqa"outer optimizer (NLoptxtol_rel; default1e-4). Gradient-based optimizers use their own fixed internal tolerances. Can also be set in the model file’s[fit_options].outer_ftol: Relative objective tolerance for the derivative-free"bobyqa"outer optimizer (NLoptftol_rel). Unset = auto:1e-8for a pure time-to-event model (its hazard objective is evaluated exactly) and1e-6otherwise. The TTE tightening lands the frailty variance on the NONMEM/nlmixr2 minimum across a near-flat omega-squared ridge (ferx-core #469); the1e-6floor elsewhere avoids grinding on noisy ODE / FD-inner objectives, where1e-8is unreachable. Set an explicit value to pin it for every model.global_search: Logical. WhenTRUE, run a global search phase before local refinement (defaultFALSE). Not accepted by pure"gn".global_maxeval: Function evaluations budget for the global search phase (default0, i.e. disabled whenglobal_search = FALSE). Not accepted by pure"gn".stagnation_guard: Logical (defaultTRUE). Terminates the NLopt outer loop early when the OFV plateau is numerically flat. SetFALSEto let SLSQP / L-BFGS run to their own xtol/ftol ormaxiter. Not accepted by pure"gn".reconverge_gradient_interval: Integer (default0). How often to re-solve each subject’s inner EBE during the population gradient, instead of holding it fixed.0= never (cheap fixed-EBE gradient);1= every gradient evaluation;N= everyN-th. Reconverging recovers the full gradient surface at roughly 5-6x the per-call cost and can help gradient-based optimizers (e.g.slsqp) past ill-conditioned non-IOV plateaus. IOV models always reconverge and ignore this setting. Not accepted by pure"gn".Trust-region optimizer (optimizer = "trust_region")steihaug_max_iters: Conjugate-gradient iteration budget for the trust-region subproblem (default chosen by the engine). Only consumed whenoptimizer = "trust_region"is set under FOCE / FOCEI / GN-hybrid. Increase if the subproblem solver exits too early on ill-conditioned problems.SAEMn_exploration: Stochastic exploration phase iterations (default 150).n_convergence: Averaging / convergence phase iterations (default 250).n_mh_steps: Metropolis-Hastings steps per subject per iteration (default 10).adapt_interval: How often (in iterations) the MH proposal covariance is adapted (default 50).omega_burnin: Initial iterations during which Omega is held fixed while the sampler warms up (default 20). Guards against Omega collapse on sparse data. Set to0to disable.seed/saem_seed: RNG seed for the SAEM Metropolis-Hastings sampler (default 12345). Independent ofmulti_start_seed.n_leapfrog/saem_n_leapfrog: Leapfrog steps for HMC proposals (default0= Metropolis-Hastings). A positive value replaces MH with one HMC proposal per subject per iteration.Bayes ("bayes")bayes_warmup: Warmup (burn-in + adaptation) sweeps per chain, discarded from the posterior (default1000).bayes_iters: Retained sampling sweeps per chain, before thinning (default1000).bayes_chains: Number of independent chains (default4); used for split-R-hat.bayes_thin: Keep everybayes_thin-th sampling draw (default1).bayes_seed: Base RNG seed for the Bayes sampler. Independent ofseed/saem_seed.Gauss-Newton ("gn"/"gn_hybrid")gn_lambda: Levenberg-Marquardt damping factor (default0.01). Larger values make steps more conservative. Accepted by both"gn"and"gn_hybrid".Importance Sampling ("imp")By default"imp"is a Monte-Carlo EM estimator (NONMEMMETHOD=IMP); setimp_eval_only = TRUEto evaluate the marginal-2 log Lat fixed parameters instead (NONMEMEONLY=1).imp_eval_only: Logical;TRUEevaluates-2 log Lat the fixed input parameters without estimating (NONMEMEONLY=1; must be the terminal chain stage).FALSE(default) estimates.imp_iterations: Number of Monte-Carlo EM iterations, ignored whenimp_eval_only(default 200).imp_averaging: Number of final iterations whose parameters are averaged into the reported estimate, ignored whenimp_eval_only(default 50).imp_samples: Importance samples drawn per subject (default 1000). Halving the Monte-Carlo SE requires quadrupling this value.imp_proposal_df: Degrees of freedom for the Student-t proposal distribution (default5); the string"normal"(or"mvn") selects a multivariate-normal proposal.imp_seed: RNG seed for the IS step (default chosen by the engine).imp_low_ess_threshold: ESS fraction below which a subject is flagged infit$importance_sampling$low_ess_subject_ids(default0.1, i.e.10%ofimp_samples).IMPMAP ("impmap"estimator)impmap_iterations: Number of Monte-Carlo EM iterations (default 200).impmap_samples: Importance samples drawn per subject per iteration (default 300).impmap_proposal_df: Proposal degrees of freedom. The string"normal"(default) selects a multivariate-normal proposal (the NONMEM default); a number>= 1selects a heavier-tailed Student-t.impmap_averaging: Number of final iterations whose parameters are averaged into the reported estimate (default 50).impmap_seed: RNG seed for the IMPMAP sampling (default chosen by the engine).impmap_low_ess_threshold: ESS fraction below which a subject is flagged as poorly sampled (default0.1).impmap_trace: Logical; whenTRUE, collect per-iteration parameter values intofit$impmap_trace(analogous to NONMEM.extoutput). DefaultFALSE.SIR (Sampling Importance Resampling)sir_samples: Candidate draws for SIR (default 1000).sir_resamples: Resampled draws retained for CI computation (default 250).sir_seed: RNG seed for the SIR step (default chosen by the engine). Independent ofseed/saem_seed.Multi-start optimizationn_starts: Number of optimizer starts (default1, i.e. single start). When> 1, starts 2..n are initialized from log-space perturbations of the model’s initial values; the best OFV wins.start_sigma: Perturbation spread in log-space (default0.3, approx.30%CV). Log-packed thetas are multiplied byexp(N(0, start_sigma)); identity-packed thetas are shifted bystart_sigma * N(0,1).multi_start_seed: RNG seed for the start-point perturbation (default42). Independent ofseed/saem_seed.output: Optional path to a.fitrxfile. When non-NULL,[ferx_save_fit](ferx_save_fit.qmd)is invoked on the result so the fit is persisted to disk in a portable, cross-language bundle (zip of JSON + CSV) before the function returns. Equivalent to callingferx_save_fit(fit, output)after the fit. See the format reference in the ferx-core docs for the on-disk schema.include_data: Logical. Only meaningful withoutput. WhenTRUE, embeds the inputdataCSV verbatim inside the.fitrxbundle so the file is self-contained. DefaultFALSE.ignore: Character vector of filter expressions that exclude a record when the expression evaluates to true (NONMEM$DATA IGNORE=equivalent). Expressions use standard comparison operators (==,!=,<,<=,>,>=) on column names. Use&&within one expression for AND; for OR use multiple elements. These conditions are merged with any[data_selection] ignorerules in the model file. Example:ignore = c("DV < 0.001", "EVID != 0 && MDV == 1").accept: Character vector of filter expressions. A record is kept only when all conditions pass; excluded if any fail (NONMEM$DATA ACCEPT=equivalent). Merged with model-fileacceptrules.ignore_ids: Numeric or character vector of subject IDs to exclude entirely. Sugar forignore = "ID == <id>"applied per-subject. Merged with[data_selection] ignore_subjectsfrom the model file....: Reserved for future use. Unrecognised arguments raise an error.
Process noise (SDE / diffusion)
ODE-based PK/PD models occasionally produce autocorrelated IWRES when the structural model is misspecified (missing compartment, unmodelled feedback). Adding continuous within-subject process noise via an SDE framework - solved by an Extended Kalman Filter (EKF) - absorbs this drift and yields a better-calibrated likelihood. Add a [diffusion] block to the .ferx model file to enable SDE mode. Each line declares the diffusion variance for one ODE state:
[diffusion]
central ~ 0.5 # initial estimate for DIFF_CENTRAL (variance units)
Key points:
- The declared value and the fitted estimate are variances, not standard deviations. A value of 0.5 means \(\sigma^2 = 0.5\), not \(\sigma = 0.5\).
- Each diffusion parameter appears in
fit$thetaasDIFF_<STATE>(e.g.DIFF_CENTRAL). Standard errors andferx_estimates()treat them as regular thetas. - SDE models use finite differences for the EKF covariance propagation.
- SAEM is not supported with SDE models; a hard error is raised.
fit$uses_sdeisTRUEwhenever a[diffusion]block was present.
c(“## Unit conversion and scaling (an optional [scaling] block to the .ferx model file tomodel predictions before comparing them to the observations in DV.is useful when the model is parameterised in one unit (e.g. amounts in) but DV is recorded in another (e.g. concentrations in ng/mL).engine divides each prediction by the scale factor before computing, so the fitted sigma is on the DV scale.Form A – scalar divisor (safe with AD):\n[scaling]\n obs_scale = 1000 # divide every predicted value by 1000\nForm B – expression divisor (forces gradient = fd):\n[scaling]\n obs_scale = V # divide by the individual volume (theta/eta expression)\nmay reference thetas, etas, and [individual_parameters]variables. AD gradients are not supported for Form B; addgradient = fd to [fit_options] or the engine will error.Per-CMT scaling (Form A or B per compartment):\n[scaling]\n obs_scale[CMT=1] = 1000 # CMT 1 in ng/mL, dose in ug\n obs_scale[CMT=2] = 1 # CMT 2 already in correct units\nForm C – ODE readout expression (forces gradient = fd):For ODE models, y = <expr> defines the observation as a function ofstate variables, thetas, etas, and individual parameters. This replacesobs_cmt= argument in [structural_model] and is requiredthe observation is not a raw compartment amount:\n[structural_model]\n ode(states = [depot, central], ...) # no obs_cmt= here\n\n[scaling]\n y = central / V # observation = central compartment / volume\n-CMT Form C: y[CMT=1] = central / V1, y[CMT=2] = central2 / V2.Constraints:[scaling] is not yet supported on SDE ([diffusion]) models.Forms B, per-CMT, and Form C force finite-difference gradients; addgradient = fd to [fit_options] explicitly, or theraises an informative error.”, ”## list("[scaling]")an optional [scaling] block to the .ferx model file tomodel predictions before comparing them to the observations in DV.is useful when the model is parameterised in one unit (e.g. amounts in) but DV is recorded in another (e.g. concentrations in ng/mL).engine divides each prediction by the scale factor before computing, so the fitted sigma is on the DV scale.Form A – scalar divisor (safe with AD):\n[scaling]\n obs_scale = 1000 # divide every predicted value by 1000\nForm B – expression divisor (forces gradient = fd):\n[scaling]\n obs_scale = V # divide by the individual volume (theta/eta expression)\nmay reference thetas, etas, and [individual_parameters]variables. AD gradients are not supported for Form B; addgradient = fd to [fit_options] or the engine will error.Per-CMT scaling (Form A or B per compartment):\n[scaling]\n obs_scale[CMT=1] = 1000 # CMT 1 in ng/mL, dose in ug\n obs_scale[CMT=2] = 1 # CMT 2 already in correct units\nForm C – ODE readout expression (forces gradient = fd):For ODE models, y = <expr> defines the observation as a function ofstate variables, thetas, etas, and individual parameters. This replacesobs_cmt= argument in [structural_model] and is requiredthe observation is not a raw compartment amount:\n[structural_model]\n ode(states = [depot, central], ...) # no obs_cmt= here\n\n[scaling]\n y = central / V # observation = central compartment / volume\n-CMT Form C: y[CMT=1] = central / V1, y[CMT=2] = central2 / V2.Constraints:[scaling] is not yet supported on SDE ([diffusion]) models.Forms B, per-CMT, and Form C force finite-difference gradients; addgradient = fd to [fit_options] explicitly, or theraises an informative error.”, “## block)an optional [scaling] block to the .ferx model file tomodel predictions before comparing them to the observations in DV.is useful when the model is parameterised in one unit (e.g. amounts in) but DV is recorded in another (e.g. concentrations in ng/mL).engine divides each prediction by the scale factor before computing, so the fitted sigma is on the DV scale.Form A – scalar divisor (safe with AD):\n[scaling]\n obs_scale = 1000 # divide every predicted value by 1000\nForm B – expression divisor (forces gradient = fd):\n[scaling]\n obs_scale = V # divide by the individual volume (theta/eta expression)\nmay reference thetas, etas, and [individual_parameters]variables. AD gradients are not supported for Form B; addgradient = fd to [fit_options] or the engine will error.Per-CMT scaling (Form A or B per compartment):\n[scaling]\n obs_scale[CMT=1] = 1000 # CMT 1 in ng/mL, dose in ug\n obs_scale[CMT=2] = 1 # CMT 2 already in correct units\nForm C – ODE readout expression (forces gradient = fd):For ODE models, y = <expr> defines the observation as a function ofstate variables, thetas, etas, and individual parameters. This replacesobs_cmt= argument in [structural_model] and is requiredthe observation is not a raw compartment amount:\n[structural_model]\n ode(states = [depot, central], ...) # no obs_cmt= here\n\n[scaling]\n y = central / V # observation = central compartment / volume\n-CMT Form C: y[CMT=1] = central / V1, y[CMT=2] = central2 / V2.Constraints:[scaling] is not yet supported on SDE ([diffusion]) models.Forms B, per-CMT, and Form C force finite-difference gradients; addgradient = fd to [fit_options] explicitly, or theraises an informative error.” )c(”## Parameter declaration syntax (Theta (fixed effects):\ntheta CL(0.134, 0.001, 10.0) # (initial, lower, upper)\n theta CL(0.1, 0.001) # lower-bound only (no upper bound)\n theta CL(0.134, 0.001, 10.0) (FIX) # FIX at end (traditional)\n theta CL (FIX) (0.134, 0.001, 10.0) # FIX anywhere (flexible placement)\nOmega (inter-individual variability):\nomega ETA_CL ~ 0.07 # variance parameterisation (default)\n omega ETA_CL ~ 0.07 (FIX) # fixed omega, FIX at end\n omega ETA_CL (FIX) ~ 0.07 # fixed omega, FIX before the tilde\nsame flexible (FIX) placement applies to sigma andkappa (IOV) declarations.Unused-parameter warning: a warning severity message with\"unused_parameter\" is emitted by the parser when ais declared in [parameters] but never referenced in[individual_parameters] or [error_model]. This usuallya commented-out expression or a typo in the parameter name.ferx_warnings(fit) or check ferx_model_validate()before fitting.”, “## list("[parameters]")Theta (fixed effects):\ntheta CL(0.134, 0.001, 10.0) # (initial, lower, upper)\n theta CL(0.1, 0.001) # lower-bound only (no upper bound)\n theta CL(0.134, 0.001, 10.0) (FIX) # FIX at end (traditional)\n theta CL (FIX) (0.134, 0.001, 10.0) # FIX anywhere (flexible placement)\nOmega (inter-individual variability):\nomega ETA_CL ~ 0.07 # variance parameterisation (default)\n omega ETA_CL ~ 0.07 (FIX) # fixed omega, FIX at end\n omega ETA_CL (FIX) ~ 0.07 # fixed omega, FIX before the tilde\nsame flexible (FIX) placement applies to sigma andkappa (IOV) declarations.Unused-parameter warning:* a warning severity message with\"unused_parameter\" is emitted by the parser when ais declared in [parameters] but never referenced in[individual_parameters] or [error_model]. This usuallya commented-out expression or a typo in the parameter name.ferx_warnings(fit) or check ferx_model_validate()before fitting.”, “## block)Theta (fixed effects):\ntheta CL(0.134, 0.001, 10.0) # (initial, lower, upper)\n theta CL(0.1, 0.001) # lower-bound only (no upper bound)\n theta CL(0.134, 0.001, 10.0) (FIX) # FIX at end (traditional)\n theta CL (FIX) (0.134, 0.001, 10.0) # FIX anywhere (flexible placement)\nOmega (inter-individual variability):\nomega ETA_CL ~ 0.07 # variance parameterisation (default)\n omega ETA_CL ~ 0.07 (FIX) # fixed omega, FIX at end\n omega ETA_CL (FIX) ~ 0.07 # fixed omega, FIX before the tilde\nsame flexible (FIX) placement applies to sigma andkappa (IOV) declarations.Unused-parameter warning:* a warning severity message with\"unused_parameter\" is emitted by the parser when ais declared in [parameters] but never referenced in[individual_parameters] or [error_model]. This usuallya commented-out expression or a typo in the parameter name.ferx_warnings(fit) or check ferx_model_validate()before fitting.” )c(“## Steady-state dosing (a dosing row as a steady-state dose by setting SS = 1 (orSS = 2) in the NONMEM CSV and providing the dosing intervalII (in the same time units as TIME).SS = 1: the subject is assumed to be at steadybefore this dose. The engine computes the steady-stateconditions analytically (for 1/2/3-cpt models) or viaexpansion (for ODE models) and uses them as the startingstate.SS = 2: the steady-state concentration is addedthe current compartment state (superposition).II: dosing interval (required when SS > 0).match the units of TIME. Rows with SS = 0 or missingSS ignore II.changes to the .ferx model file are needed. Steady-stateis purely data-driven and works for all PK model families(1-cpt, 2-cpt, 3-cpt oral/IV/infusion, ODE-based).”, “## list("SS")a dosing row as a steady-state dose by setting SS = 1 (orSS = 2) in the NONMEM CSV and providing the dosing intervalII (in the same time units as TIME).SS = 1: the subject is assumed to be at steadybefore this dose. The engine computes the steady-stateconditions analytically (for 1/2/3-cpt models) or viaexpansion (for ODE models) and uses them as the startingstate.SS = 2: the steady-state concentration is addedthe current compartment state (superposition).II: dosing interval (required when SS > 0).match the units of TIME. Rows with SS = 0 or missingSS ignore II.changes to the .ferx model file are needed. Steady-stateis purely data-driven and works for all PK model families(1-cpt, 2-cpt, 3-cpt oral/IV/infusion, ODE-based).”, “## anda dosing row as a steady-state dose by setting SS = 1 (orSS = 2) in the NONMEM CSV and providing the dosing intervalII (in the same time units as TIME).SS = 1: the subject is assumed to be at steadybefore this dose. The engine computes the steady-stateconditions analytically (for 1/2/3-cpt models) or viaexpansion (for ODE models) and uses them as the startingstate.SS = 2: the steady-state concentration is addedthe current compartment state (superposition).II: dosing interval (required when SS > 0).match the units of TIME. Rows with SS = 0 or missingSS ignore II.changes to the .ferx model file are needed. Steady-stateis purely data-driven and works for all PK model families(1-cpt, 2-cpt, 3-cpt oral/IV/infusion, ODE-based).”, “## list("II")a dosing row as a steady-state dose by setting SS = 1 (orSS = 2) in the NONMEM CSV and providing the dosing intervalII (in the same time units as TIME).SS = 1: the subject is assumed to be at steadybefore this dose. The engine computes the steady-stateconditions analytically (for 1/2/3-cpt models) or viaexpansion (for ODE models) and uses them as the startingstate.SS = 2: the steady-state concentration is addedthe current compartment state (superposition).II: dosing interval (required when SS > 0).match the units of TIME. Rows with SS = 0 or missingSS ignore II.changes to the .ferx model file are needed. Steady-stateis purely data-driven and works for all PK model families(1-cpt, 2-cpt, 3-cpt oral/IV/infusion, ODE-based).”, “## columns)a dosing row as a steady-state dose by setting SS = 1 (orSS = 2) in the NONMEM CSV and providing the dosing intervalII (in the same time units as TIME).SS = 1: the subject is assumed to be at steadybefore this dose. The engine computes the steady-stateconditions analytically (for 1/2/3-cpt models) or viaexpansion (for ODE models) and uses them as the startingstate.SS = 2: the steady-state concentration is addedthe current compartment state (superposition).II: dosing interval (required when SS > 0).match the units of TIME. Rows with SS = 0 or missingSS ignore II.changes to the .ferx model file are needed. Steady-stateis purely data-driven and works for all PK model families(1-cpt, 2-cpt, 3-cpt oral/IV/infusion, ODE-based).” )## Specifying model and data
There are two equivalent ways to supply the model and data: 1. Path strings (classic style):
ferx_fit("pk.ferx", "data.csv")
ferx_fit(model = "pk.ferx", data = "data.csv")
2. ferx_model object (pipe style):
"data.csv" |> ferx_model("pk.ferx") |> ferx_fit()
Both dispatch to the same Rust backend. The ferx_model form is convenient when combined with [ferx_set_section](ferx_set_section.qmd) to modify model options in the same chain (see Examples). The data path stored in the ferx_model object can always be overridden by supplying dataexplicitly to ferx_fit().
Controlling estimation
Estimation method - pass a single method or a vector to chain methods in sequence (each stage seeds the next with its converged parameters):
ferx_fit(m, d, method = "focei")
ferx_fit(m, d, method = c("saem", "focei")) # SAEM warm-start, FOCEI polish
Standard errors - the covariance step is on by default:
ferx_fit(m, d, covariance = TRUE) # default: produces SE /
ferx_fit(m, d, covariance = FALSE) # skip for speed during development
# Tune the FD step for the Hessian (default 0.01):
ferx_fit(m, d, fd_hessian_step = 0.1) # increase when Hessian is ill-conditioned
ferx_fit(m, d, fd_hessian_step = 1e-3) # decrease for very smooth surfaces
Parallelism - cap the Rust thread pool:
ferx_fit(m, d, threads = parallel::detectCores(logical = FALSE))
LOQ censoring:
ferx_fit(m, d, bloq_method = "m3") # M3 likelihood (CENS: 1=LLOQ, -1=ULOQ)
ferx_fit(m, d, bloq_method = "drop") # disable M3; use rows as observed
Gradient method for the inner EBE loop:
ferx_fit(m, d, gradient = "auto") # default: engine-selected
ferx_fit(m, d, gradient = "fd") # force finite differences
Optimizer trace - write a per-iteration CSV for convergence diagnostics:
fit <- ferx_fit(m, d, optimizer_trace = TRUE)
ferx_plot_trace(fit)
c(“## Fine-tuning withsettings argument forwards a named list of low-level options toRust backend without requiring a new wrapper release. Arguments withparameters (e.g. method, covariance) cannot bein settings - pass them via the named argument.the complete settings reference - including which options apply to which, valid combinations, and a convergence troubleshooting guide - see(https://ferx-nlme.github.io/model-dsl/fit-options.html).Outer optimizer selection:\nferx_fit(m, d, settings = list(optimizer = \"auto\")) # default\nferx_fit(m, d, settings = list(optimizer = \"bobyqa\")) # derivative-free\nferx_fit(m, d, settings = list(optimizer = \"slsqp\")) # gradient-based\nferx_fit(m, d, settings = list(optimizer = \"lbfgs\"))\nferx_fit(m, d, settings = list(optimizer = \"bfgs\"))\nferx_fit(m, d, settings = list(optimizer = \"trust_region\"))\nferx_fit(m, d, settings = list(optimizer = \"mma\"))\nIteration cap and inner loop:\nferx_fit(m, d, settings = list(\n maxiter = 500L,\n inner_maxiter = 100L,\n inner_tol = 1e-6\n))\nSAEM tuning:\nferx_fit(m, d, method = \"saem\", settings = list(\n n_exploration = 200,\n n_convergence = 400,\n n_mh_steps = 3,\n omega_burnin = 20L,\n seed = 42L\n))\n\n# HMC proposals:\nferx_fit(m, d, method = \"saem\", settings = list(n_leapfrog = 3L))\nSIR uncertainty (requires sir = TRUE, covariance = TRUE):\nferx_fit(m, d, sir = TRUE, settings = list(\n sir_samples = 2000L,\n sir_resamples = 500L,\n sir_seed = 1L\n))\nGlobal search before local refinement:\nferx_fit(m, d, settings = list(\n global_search = TRUE,\n global_maxeval = 1000L\n))\nTrust-region CG budget:\nferx_fit(m, d, settings = list(\n optimizer = \"trust_region\",\n steihaug_max_iters = 100L\n))\nConvergence tolerance for difficult subjects:max_unconverged_frac (numeric in [0, 1]) flags the fit as convergedif up to this fraction of subjects failed their inner EBE loop, insteadraising an error - useful for large datasets with a handful of difficult. min_obs_for_convergence_check (non-negative integer) is thenumber of observations a subject must have before its inner-loopcounts toward that fraction; sparser subjects are excluded.\nferx_fit(m, d, settings = list(\n max_unconverged_frac = 0.1,\n min_obs_for_convergence_check = 2L\n))\nStagnation guard (NLopt-based optimizers):NLopt-based outer optimizers (BOBYQA, SLSQP, L-BFGS, MMA) short-circuit byonce recent evaluations show no OFV improvement above 1e-3 over awindow, letting them terminate quickly instead of burning throughmaxiter at full inner-loop cost. Set stagnation_guard = FALSEto disable this and let the optimizer run to its natural termination- useful when debugging or for problems with genuinely-but-real OFV improvements.\nferx_fit(m, d, settings = list(stagnation_guard = FALSE))\nMulti-start optimization:Runs n_starts independent fits from perturbed initial values in(via rayon) and returns the converged result with the lowest OFV.n_starts = 1 disables multi-start (single run, no overhead).for models prone to local minima: Michaelis-Menten elimination,-block omega, or many covariate parameters. On an 8-core machinen_starts = 8 costs approximately the same wall-clock time as a single run. always uses the exact initial values from the model file...n apply a log-space perturbation of size start_sigma(default 0.3) to each theta.\n# 8 parallel starts (approx. same wall time as one run on 8 cores)\nferx_fit(m, d, settings = list(n_starts = 8L))\n\n# Wider perturbation for ridge-shaped surfaces (e.g. Michaelis-Menten):\nferx_fit(m, d, settings = list(n_starts = 8L, start_sigma = 0.5))\n\n# Pin the perturbation RNG seed for reproducibility (independent of the SAEM seed):\nferx_fit(m, d, settings = list(n_starts = 8L, multi_start_seed = 123L))\n”, ”## list("settings")settings argument forwards a named list of low-level options toRust backend without requiring a new wrapper release. Arguments withparameters (e.g. method, covariance) cannot bein settings - pass them via the named argument.the complete settings reference - including which options apply to which, valid combinations, and a convergence troubleshooting guide - see(https://ferx-nlme.github.io/model-dsl/fit-options.html).Outer optimizer selection:\nferx_fit(m, d, settings = list(optimizer = \"auto\")) # default\nferx_fit(m, d, settings = list(optimizer = \"bobyqa\")) # derivative-free\nferx_fit(m, d, settings = list(optimizer = \"slsqp\")) # gradient-based\nferx_fit(m, d, settings = list(optimizer = \"lbfgs\"))\nferx_fit(m, d, settings = list(optimizer = \"bfgs\"))\nferx_fit(m, d, settings = list(optimizer = \"trust_region\"))\nferx_fit(m, d, settings = list(optimizer = \"mma\"))\nIteration cap and inner loop:\nferx_fit(m, d, settings = list(\n maxiter = 500L,\n inner_maxiter = 100L,\n inner_tol = 1e-6\n))\nSAEM tuning:\nferx_fit(m, d, method = \"saem\", settings = list(\n n_exploration = 200,\n n_convergence = 400,\n n_mh_steps = 3,\n omega_burnin = 20L,\n seed = 42L\n))\n\n# HMC proposals:\nferx_fit(m, d, method = \"saem\", settings = list(n_leapfrog = 3L))\nSIR uncertainty (requires sir = TRUE, covariance = TRUE):\nferx_fit(m, d, sir = TRUE, settings = list(\n sir_samples = 2000L,\n sir_resamples = 500L,\n sir_seed = 1L\n))\nGlobal search before local refinement:\nferx_fit(m, d, settings = list(\n global_search = TRUE,\n global_maxeval = 1000L\n))\nTrust-region CG budget:\nferx_fit(m, d, settings = list(\n optimizer = \"trust_region\",\n steihaug_max_iters = 100L\n))\nConvergence tolerance for difficult subjects:max_unconverged_frac (numeric in [0, 1]) flags the fit as convergedif up to this fraction of subjects failed their inner EBE loop, insteadraising an error - useful for large datasets with a handful of difficult. min_obs_for_convergence_check (non-negative integer) is thenumber of observations a subject must have before its inner-loopcounts toward that fraction; sparser subjects are excluded.\nferx_fit(m, d, settings = list(\n max_unconverged_frac = 0.1,\n min_obs_for_convergence_check = 2L\n))\nStagnation guard (NLopt-based optimizers):NLopt-based outer optimizers (BOBYQA, SLSQP, L-BFGS, MMA) short-circuit byonce recent evaluations show no OFV improvement above 1e-3 over awindow, letting them terminate quickly instead of burning throughmaxiter at full inner-loop cost. Set stagnation_guard = FALSEto disable this and let the optimizer run to its natural termination- useful when debugging or for problems with genuinely-but-real OFV improvements.\nferx_fit(m, d, settings = list(stagnation_guard = FALSE))\nMulti-start optimization:Runs n_starts independent fits from perturbed initial values in(via rayon) and returns the converged result with the lowest OFV.n_starts = 1 disables multi-start (single run, no overhead).for models prone to local minima: Michaelis-Menten elimination,-block omega, or many covariate parameters. On an 8-core machinen_starts = 8 costs approximately the same wall-clock time as a single run. always uses the exact initial values from the model file...n apply a log-space perturbation of size start_sigma(default 0.3) to each theta.\n# 8 parallel starts (approx. same wall time as one run on 8 cores)\nferx_fit(m, d, settings = list(n_starts = 8L))\n\n# Wider perturbation for ridge-shaped surfaces (e.g. Michaelis-Menten):\nferx_fit(m, d, settings = list(n_starts = 8L, start_sigma = 0.5))\n\n# Pin the perturbation RNG seed for reproducibility (independent of the SAEM seed):\nferx_fit(m, d, settings = list(n_starts = 8L, multi_start_seed = 123L))\n” )## Post-fit outputs and pipe extensions
ferx_fit() returns a ferx_fit object. All of the following work both as standalone calls and as the tail of a |> pipe:
fit |> print() # full parameter table
fit |> summary() # compact diagnostic summary
fit |> ferx_estimates() # tidy data frame: theta / omega / sigma + SE /
fit |> ferx_cor_matrix() # parameter correlation matrix (needs covariance = TRUE)
fit |> ferx_model_inspect() # model structure auto-derived by the engine
fit |> ferx_plot_trace() # convergence trace (needs optimizer_trace = TRUE)
Diagnostics data frame (PRED, IPRED, CWRES, ETAs, …) lives in fit$sdtab and can be used directly:
fit$sdtab
fit$ebe_etas
fit$individual_estimates
Seealso
- Fit options reference - full documentation of every
[fit_options]key andsettingsknob, organised by method with a convergence-troubleshooting guide and a method x option compatibility table. [ferx_fit_async](ferx_fit_async.qmd)- non-blocking version for long runs.[ferx_check_init](ferx_check_init.qmd)- 5-iteration pilot to validate starting values before a full run.[ferx_inits_from_nca](ferx_inits_from_nca.qmd)- NCA-derived starting values.[ferx_warnings](ferx_warnings.qmd)- structured warnings from the fit.[ferx_estimates](ferx_estimates.qmd)- tidy parameter table with SE /
[ferx_plot_trace](ferx_plot_trace.qmd)- convergence trace plot.
Other fitting: [ferx_check_init](ferx_check_init.qmd)(), [ferx_collect](ferx_collect.qmd)(), [ferx_fit_async](ferx_fit_async.qmd)(), [ferx_inits_from_nca](ferx_inits_from_nca.qmd)(), [ferx_sir](ferx_sir.qmd)()
Concept
fitting
Value
A named list. Where to find commonly-needed quantities:
| What you want | Accessor | Shape | When present |
| Residuals, PRED, IPRED, diagnostics | fit$sdtab |
one row per observation | always |
Covariates echoed per observation (via [output]) |
fit$sdtab |
one row per observation, LOCF | declared in [output] |
| Raw covariate values for all dataset records | fit$covtab |
one row per dataset record (doses + obs) | model has [covariates] block |
| ETA / EBE values per observation row | fit$sdtab (ETA_CL, ETA_V, …) |
one row per observation, value repeated | always |
| ETA / EBE values per subject | fit$ebe_etas |
one row per subject | model declares etas |
| Individual PK parameters per subject | fit$individual_estimates |
one row per subject | always |
Full slot descriptions:
Examples
ex <- ferx_example("warfarin")
fit <- ferx_fit(ex$model, ex$data, method = "gn", covariance = FALSE)
fit$theta
fit$ofv
ex <- ferx_example("warfarin")
# -- Classic style (path strings) ------------------------------------------
# Minimal call: FOCEI with default optimizer, covariance step on
fit <- ferx_fit(ex$model, ex$data)
# Named arguments (fully equivalent)
fit <- ferx_fit(model = ex$model, data = ex$data, method = "focei")
# All common options at once
fit <- ferx_fit(ex$model, ex$data,
method = "focei",
covariance = TRUE,
threads = 4L,
gradient = "auto",
verbose = TRUE,
scale_params = FALSE
)
# SAEM warm-start, then FOCEI polish
fit <- ferx_fit(ex$model, ex$data, method = c("saem", "focei"))
# Fine-tune via settings
fit <- ferx_fit(ex$model, ex$data,
method = "focei",
settings = list(
optimizer = "slsqp",
maxiter = 500L,
inner_maxiter = 100L,
inner_tol = 1e-6
)
)
# LOQ-censored observations (M3 method)
bloq <- ferx_example("warfarin_bloq")
fit <- ferx_fit(bloq$model, bloq$data, method = "focei", bloq_method = "m3")
# SIR parameter uncertainty
fit <- ferx_fit(ex$model, ex$data,
sir = TRUE,
settings = list(sir_samples = 2000L, sir_resamples = 500L)
)
# -- Pipe style (ferx_model object) -----------------------------------------
# Basic pipe: data flows into ferx_model() which bundles the model path
ex$data |>
ferx_model(ex$model) |>
ferx_fit(method = "focei", covariance = TRUE)
# Modify a model section, then fit
ex$data |>
ferx_model(ex$model) |>
ferx_set_section("fit_options", c(
" method = focei",
" maxiter = 500",
" covariance = true"
)) |>
ferx_fit()
# Full pipeline including post-fit outputs
ex$data |>
ferx_model(ex$model) |>
ferx_set_section("fit_options", c(" method = focei")) |>
ferx_fit(covariance = TRUE, threads = 4L,
settings = list(optimizer = "slsqp")) |>
summary()
# Inspect then continue (ferx_get_section returns the ferx_model invisibly)
ex$data |>
ferx_model(ex$model) |>
ferx_get_section("parameters") |>
ferx_fit() |>
ferx_estimates()
# Override data stored in ferx_model at fit time
# (substitute the path to your own dataset for "other_cohort.csv")
m <- ferx_model(ex$data, ex$model)
ferx_fit(m, data = "other_cohort.csv", method = "focei")
# -- Post-fit outputs -------------------------------------------------------
fit <- ferx_fit(ex$model, ex$data, covariance = TRUE, optimizer_trace = TRUE)
summary(fit) # compact diagnostic table
ferx_estimates(fit) # tidy data frame with SE and %RSE
ferx_cor_matrix(fit) # parameter correlation matrix
ferx_model_inspect(fit) # model structure auto-derived by the engine
ferx_plot_trace(fit) # convergence plot (optimizer_trace = TRUE required)
fit$sdtab # per-observation diagnostics (PRED, IPRED, CWRES, etc.)
fit$ebe_etas # per-subject empirical Bayes ETAs
fit$individual_estimates # per-subject individual PK parameters
fit$eigenvalues # sorted eigenvalues of parameter correlation matrix
fit$condition_number # > 1000 flags potential ill-conditioning
# Covariance diagnostics in a pipe
ferx_fit(ex$model, ex$data, covariance = TRUE) |> ferx_cor_matrix()
# -- SDE model (Extended Kalman Filter) -------------------------------------
# The [diffusion] block in the .ferx file enables SDE mode.
# DIFF_CENTRAL is a within-subject process-noise variance on the central
# compartment. SDE models use finite differences for this step.
sde <- ferx_example("warfarin_sde")
fit_sde <- ferx_fit(sde$model, sde$data)
fit_sde$uses_sde # TRUE
fit_sde$theta["DIFF_CENTRAL"] # fitted diffusion variance
ferx_estimates(fit_sde) # DIFF_CENTRAL appears in theta block
# -- Multi-start (avoid local minima) ----------------------------------------
ex <- ferx_example("warfarin")
fit_ms <- ferx_fit(ex$model, ex$data, settings = list(n_starts = 4L))
fit_ms$ofv
# -- NCA-based starting values (inits_from_nca) ------------------------------
# `inits_from_nca` is designed to mitigate stalling and local-minimum traps
# in **gradient-based estimation methods**, which navigate the likelihood
# surface using a local Jacobian / Hessian and so are sensitive to where the
# starting thetas sit. In ferx those are:
# - method = "gn" (FOCE Gauss-Newton)
# - method = "gn_hybrid" (FOCE-GN with SLSQP polish)
# - method = "foce" / "focei" run with settings = list(optimizer =
# "trust_region") or settings = list(optimizer = "slsqp") (the
# default "auto" resolves to derivative-free "bobyqa" on these
# poorly-started ODE/PD fits, which tolerates poor starts the best;
# switch to slsqp / trust_region for gradient-based behaviour, then
# NCA-derived starts help most)
# For these, NCA-derived starts often turn a non-converging or stagnating
# fit into a clean convergence.
#
# Stochastic / sampling-based methods ("saem", "imp") explore the space
# globally and are far less sensitive to starting values, so `inits_from_nca`
# gives a smaller benefit. The better tool for them is multi-start (run
# several fits from perturbed starts and keep the lowest OFV) - see the
# Multi-start section above (`settings = list(n_starts = 4L)`), or chain
# SAEM into FOCEI: `method = c("saem", "focei")`.
# TRUE = default strategy ("nca_sweep"); rescues a trust_region fit that
# would otherwise stall on poor defaults.
fit <- ferx_fit(ex$model, ex$data,
settings = list(optimizer = "trust_region"),
inits_from_nca = TRUE
)
# Pick a strategy explicitly. "nca_ebe" handles large between-subject
# variability better than "nca_sweep" but falls back to it for ODE models.
fit <- ferx_fit(ex$model, ex$data, method = "gn",
inits_from_nca = "nca_ebe")
# Fastest variant: NCA arithmetic only, no grid sweep.
fit <- ferx_fit(ex$model, ex$data, inits_from_nca = "nca")
# Same flag from the model file's [fit_options] (the call-time arg wins
# and warns on conflict). Inspect what NCA produces without fitting via
# `ferx_inits_from_nca()` -- see ?ferx_inits_from_nca.
#
# Combine with multi-start when even good inits aren't enough - good starts
# PLUS a small perturbed ensemble is often the most robust setup.
fit <- ferx_fit(ex$model, ex$data,
method = "gn",
inits_from_nca = "nca_sweep",
settings = list(n_starts = 4L)
)
# -- Deep Compartment Model (covariate neural network) -----------------------
# Requires ferx-r built with the `nn` cargo feature.
# The [covariate_nn TYPICAL_PK] block replaces analytical covariate functions
# with a small MLP: WT + CRCL -> multiplicative modulator on TVCL/TVV1/etc.
# ferx_fit() call is identical to any other model.
dcm <- ferx_example("warfarin_dcm")
fit_dcm <- ferx_fit(dcm$model, dcm$data, method = "focei")
# NN metadata is in fit$neural_networks (one sub-list per [covariate_nn] block)
fit_dcm$neural_networks[[1]]$name # "TYPICAL_PK"
fit_dcm$neural_networks[[1]]$shape # e.g. c(2, 8, 8, 5)
fit_dcm$neural_networks[[1]]$n_weights # total weight + bias count
fit_dcm$neural_networks[[1]]$input_names # c("WT", "CRCL")
fit_dcm$neural_networks[[1]]$output_names # c("CL", "V1", "Q", "V2", "KA")
# NN weight thetas are suppressed from the THETA table in print();
# a NEURAL NETWORKS summary block is shown instead.
print(fit_dcm)
# See inst/examples/ex_warfarin_dcm.R for a full worked example including
# interpretability heuristics and comparison against a no-covariate baseline.