pub enum FittedModel {
Standard {
payload: FittedModelPayload,
},
LocationScale {
payload: FittedModelPayload,
},
MarginalSlope {
payload: FittedModelPayload,
},
Survival {
payload: FittedModelPayload,
},
TransformationNormal {
payload: FittedModelPayload,
},
}Variants§
Standard
Fields
payload: FittedModelPayloadLocationScale
Fields
payload: FittedModelPayloadMarginalSlope
Fields
payload: FittedModelPayloadSurvival
Fields
payload: FittedModelPayloadTransformationNormal
Fields
payload: FittedModelPayloadImplementations§
Source§impl FittedModel
impl FittedModel
Sourcepub fn axis_clip_to_training_ranges(
&self,
data: ArrayBase<ViewRepr<&f64>, Dim<[usize; 2]>>,
col_map: &HashMap<String, usize>,
) -> Option<ArrayBase<OwnedRepr<f64>, Dim<[usize; 2]>>>
pub fn axis_clip_to_training_ranges( &self, data: ArrayBase<ViewRepr<&f64>, Dim<[usize; 2]>>, col_map: &HashMap<String, usize>, ) -> Option<ArrayBase<OwnedRepr<f64>, Dim<[usize; 2]>>>
Axis-clip each continuous new-data column to the (min, max) range
observed in training. Categorical and binary columns are left
untouched so unseen levels surface rather than being silently remapped
onto seen ones. Returns Some(clipped_copy) only if at least one
value was actually clipped; otherwise None so callers can avoid
owning a redundant copy. Pre-2026-04-29 model JSONs that lack the
training_feature_ranges field deserialize to None and pass through
unchanged.
pub fn from_payload(payload: FittedModelPayload) -> FittedModel
pub fn payload(&self) -> &FittedModelPayload
pub fn likelihood(&self) -> LikelihoodSpec
Sourcepub fn display_family_name(&self) -> String
pub fn display_family_name(&self) -> String
The family label every summary surface reports (#913).
For most model classes this is the likelihood’s pretty name — "Gaussian Identity", "Tweedie Log", "Royston Parmar".
A location-scale model is the exception, and reporting the likelihood’s
name there is a genuine loss of information rather than a shorter
spelling: FittedFamily::LocationScale carries only the MEAN channel’s
law, so a two-channel dispersion GAMLSS (family="gamma" plus a
noise_formula, which routes through
materialize_location_scale/DispersionLocationScaleFitRequest) and the
mean-only Gamma GLM both report "Gamma Log" and are indistinguishable
on the summary surface. The fine-grained tag the fit actually used is
already persisted in payload.family by assemble_location_scale_payload
— "gaussian-location-scale", "binomial-location-scale",
"gamma-location-scale", "negbin-location-scale",
"beta-location-scale", "tweedie-location-scale" — and the CLI’s own
fit line already prints exactly that tag (run_fit.rs,
family={kind.family_tag()}). Reading it here is what makes the summary
agree with the CLI (SPEC rule 9) instead of contradicting it.
The persisted tag is only trusted when it is non-empty, so a payload written before the tag existed degrades to the likelihood name rather than to a blank family.
pub fn estimator(&self) -> FittedEstimator
Sourcepub fn prediction_required_columns(&self) -> Result<BTreeSet<String>, String>
pub fn prediction_required_columns(&self) -> Result<BTreeSet<String>, String>
Columns this model consumes from a prediction frame — its input contract.
Every variable named by the main formula (features, interaction margins,
random-effect groups, and a smooth’s by= column), the survival
entry/exit columns or the transformation-normal response, the auxiliary
noise / logslope formula columns, and the offset / noise-offset /
latent-z columns. The event-indicator and the plain response of a
standard model are deliberately excluded: they are not needed to form
a prediction (the conformal-calibration fold layers the response back on
separately).
This is the single authority shared by the CLI and PyFFI predict paths. A prediction frame column that is not in this set is irrelevant to the model and must be ignored rather than strict-encoded against the training schema — otherwise an unrelated ID/label column with a held-out categorical level aborts predict (#840).
Sourcepub fn diagnostic_extra_columns(&self) -> Result<Vec<String>, String>
pub fn diagnostic_extra_columns(&self) -> Result<Vec<String>, String>
Columns a post-fit diagnostic command (diagnose / sample / report)
needs beyond Self::prediction_required_columns.
Prediction deliberately drops a standard GAM’s bare response so a
prediction frame may omit it (#840 / #864). Diagnostics are statements
about that observed response — residuals, R², posterior likelihoods,
leave-one-out — so the response must be present. This returns the bare
response column when the prediction projection would otherwise drop it,
and nothing when the response is already prediction-required (survival
Surv(...) time/event columns, the transformation-normal response) or
is not a plain data column.
Centralising the intent here is what makes it structurally impossible
for a diagnostic command to silently drop the response: callers use
load_dataset…_for_diagnostics, which always folds these in, instead of
each remembering to thread an extra_required response by hand.
pub fn predict_model_class(&self) -> PredictModelClass
pub fn saved_link_wiggle( &self, ) -> Result<Option<SavedLinkWiggleRuntime>, FittedModelError>
pub fn saved_baseline_time_wiggle( &self, ) -> Result<Option<SavedBaselineTimeWiggleRuntime>, FittedModelError>
Sourcepub fn has_link_wiggle(&self) -> bool
pub fn has_link_wiggle(&self) -> bool
Whether this model has a link wiggle component with complete metadata.
Sourcepub fn has_baseline_time_wiggle(&self) -> bool
pub fn has_baseline_time_wiggle(&self) -> bool
Whether this model has a baseline-time wiggle component with complete metadata.
Sourcepub fn prediction_uses_posterior_mean(&self) -> bool
pub fn prediction_uses_posterior_mean(&self) -> bool
Whether the default point prediction must integrate the inverse link
over the coefficient posterior — reporting the posterior mean
E[g⁻¹(Xβ)] — rather than plugging in the posterior mode g⁻¹(Xβ̂).
SPEC (issue #960): the posterior mean is always the default point
estimate (never MAP). It is observably distinct from the plug-in exactly
when the inverse link is curved over the posterior’s uncertainty, so
E[g⁻¹(η)] ≠ g⁻¹(E[η]) by Jensen. The curvature-based classification is:
- all log-link families (Poisson / Gamma / Tweedie / NegativeBinomial):
E[exp η] = exp(η + se²/2) ≠ exp(η)(log-normal MGF); - all Binomial links (logit / probit / cloglog / SAS / BetaLogistic / Mixture / LatentCLogLog): bounded sigmoidal inverse links;
- Beta (logit link):
E[σ(η)] ≠ σ(E[η]); - Royston–Parmar (curved survival-probability inverse link).
The integral collapses to the plug-in (so the cheaper plug-in path is
exact and taken instead) only for the effectively-linear identity-link
Gaussian. Any model carrying a link wiggle or baseline-time wiggle is
curved regardless of family. This curvature partition mirrors
families::family_runtime::posterior_mean, the compute path that produces the corrected mean for each of these families.
This is the single source of truth shared by the CLI (gam predict)
and the Python FFI prediction path so the two can never drift on which
models receive the posterior-mean correction.
pub fn saved_prediction_runtime( &self, ) -> Result<SavedPredictionRuntime, FittedModelError>
pub fn saved_sas_state(&self) -> Result<Option<SasLinkState>, FittedModelError>
pub fn saved_beta_logistic_state( &self, ) -> Result<Option<SasLinkState>, FittedModelError>
pub fn saved_mixture_state( &self, ) -> Result<Option<MixtureLinkState>, FittedModelError>
pub fn saved_latent_cloglog_state( &self, ) -> Result<Option<LatentCLogLogState>, FittedModelError>
pub fn resolved_inverse_link( &self, ) -> Result<Option<InverseLink>, FittedModelError>
Sourcepub fn measure_jet_extrapolation_variance(
&self,
data: ArrayBase<ViewRepr<&f64>, Dim<[usize; 2]>>,
col_map: &HashMap<String, usize>,
) -> Result<Option<ArrayBase<OwnedRepr<f64>, Dim<[usize; 1]>>>, FittedModelError>
pub fn measure_jet_extrapolation_variance( &self, data: ArrayBase<ViewRepr<&f64>, Dim<[usize; 2]>>, col_map: &HashMap<String, usize>, ) -> Result<Option<ArrayBase<OwnedRepr<f64>, Dim<[usize; 1]>>>, FittedModelError>
V∞ §5 producer: per-row measure-jet extrapolation variance on the η
scale for a prediction batch (docs/measure_jet_v_infinity.md).
For every frozen measure-jet term in resolved_termspec this prices
the off-support ignorance of the fitted multiscale spectrum at each
query row: support curve from the frozen nodes/masses/band
(gam_terms::basis::measure_jet_support_curve), fitted per-scale
amplitudes λ̂_ℓ read from the fit’s lambdas through the replayed
design’s penalty layout, folded through
gam_terms::basis::measure_jet_extrapolation_variance and scaled by
the fit’s coefficient-covariance scale φ̂ so the result sits on Vp’s
η-variance scale. Terms not yet frozen (no frozen_quadrature or
non-UserProvided centers) are skipped with a warning. Returns
Ok(None) when no measure-jet term contributes, so callers leave
PredictUncertaintyOptions::extrapolation_variance untouched.
data must be the RAW (unclipped) prediction matrix in prediction
column order — clipping to the training ranges would freeze the
distance signal at the hull and defeat the honesty contract — and
col_map the prediction header → column map (the same map handed to
the design builder). This is the minimal-plumbing producer seam: the
option-building callers (CLI predict, FFI) hold exactly
(model, data, col_map) at the point where they assemble
PredictUncertaintyOptions, and the fusion in
predict_gamwith_uncertainty adds the array AFTER its multiplicative
inflations: Var_total = Var_Vp·inflation + Var_extrap.
Sourcepub fn unified(&self) -> Option<&UnifiedFitResult>
pub fn unified(&self) -> Option<&UnifiedFitResult>
Access the unified fit result, if stored.
pub fn load_from_path(path: &Path) -> Result<FittedModel, FittedModelError>
pub fn save_to_path(&self, path: &Path) -> Result<(), FittedModelError>
pub fn require_data_schema(&self) -> Result<&DataSchema, FittedModelError>
Sourcepub fn saved_spline_scan(
&self,
) -> Result<Option<(&str, SplineScanFit)>, FittedModelError>
pub fn saved_spline_scan( &self, ) -> Result<Option<(&str, SplineScanFit)>, FittedModelError>
Restore the exact in-memory spline-scan fit from a scan-bearing
payload (#1030/#1034). Ok(None) for dense models; the returned
predict replays the training Gaussian bridge bit-for-bit.
Sourcepub fn saved_residual_cascade(
&self,
) -> Result<Option<(&[String], ResidualCascadeFit)>, FittedModelError>
pub fn saved_residual_cascade( &self, ) -> Result<Option<(&[String], ResidualCascadeFit)>, FittedModelError>
Restore the in-memory residual-cascade fit from a cascade-bearing
payload (#1032). Ok(None) for non-cascade models; the returned fit
replays the multilevel Wendland-frame posterior for the d ∈ {2, 3}
feature columns at each predict point.
Sourcepub fn random_effect_group_columns(&self) -> HashSet<String>
pub fn random_effect_group_columns(&self) -> HashSet<String>
Grouping columns eligible for LENIENT unseen-level encoding at predict time (the held-out-group policy: an unseen group is encoded as an out-of-vocabulary code and shrunk toward the population mean).
This is the whitelist the predict/check encode paths pass to
UnseenCategoryPolicy::encode_unknown_for_columns. It intentionally
covers ONLY genuine random effects (group(g)/re(g)/s(g, bs="re")).
A FIXED categorical factor — a bare + g OR an explicit factor(g) —
is auto-promoted to a penalized random block internally but is still a
fixed parametric factor: an unseen level of it must reach the strict
schema encode and raise a SchemaMismatchError rather than be silently
averaged to the factor’s centering point (#2102/#2137). Such terms carry
lenient_unseen == false and are excluded here so they hit the strict
UnseenCategoryPolicy::Error arm.
Sourcepub fn numeric_fixed_factor_vocabularies(&self) -> Vec<(String, HashSet<u64>)>
pub fn numeric_fixed_factor_vocabularies(&self) -> Vec<(String, HashSet<u64>)>
Frozen level vocabularies for FIXED-factor terms (factor(g) or a bare
+ g, i.e. lenient_unseen == false) whose feature column is numeric
in the data schema.
A string factor is a Categorical schema column, so the strict schema
re-encode already rejects (and check reports) an out-of-vocabulary
label. A numeric-coded factor(year), however, reaches the model as a
Continuous/Binary column with no categorical schema, so the encode
path has no level set to validate against — the unseen-level guard is
silently skipped (#2137). This exposes each such column’s frozen numeric
vocabulary (canonical f64 bit patterns, signed-zero/NaN normalized) so
the check/predict schema layer can enforce the same fixed-factor
contract the design operator (build_random_effect_block) enforces.
Only terms with concrete frozen_levels (captured at fit) and the full
one-hot block (!drop_first_level, so the frozen set is the complete
training vocabulary) are returned, matching the operator’s strict gate.
pub fn validate_for_persistence(&self) -> Result<(), FittedModelError>
Sourcepub fn validate_persisted_form_parses_back(
&self,
) -> Result<(), FittedModelError>
pub fn validate_persisted_form_parses_back( &self, ) -> Result<(), FittedModelError>
Refuse to persist a model that cannot be read back.
Self::validate_numeric_finiteness below is a hand-maintained
enumeration of roughly forty named fields. It is incomplete by
construction: every field added after it was written is unguarded, and
the omission is invisible until a LOAD fails somewhere else entirely.
That is how a non-finite f64 reaches disk. serde_json renders
f64::NAN and ±inf as JSON null, so the value serialises silently
and then fails deserialisation as invalid type: null, expected f64
(#2601) — in a different session, with no field name and no way back to
the fit that produced it.
This states the same demand against the SERIALISATION instead of against
a list of names, so it covers every field that exists or will exist.
Self::load_from_path is serde_json::from_str::<Self>, so a model
that fails this check is already unloadable — refusing to write it
cannot lose anything that could have been recovered, and it moves the
error to the fit that caused it.
The null-valued paths are reported alongside serde’s own message because serde names a line and column in a document nobody kept.
pub fn validate_numeric_finiteness(&self) -> Result<(), FittedModelError>
Source§impl FittedModel
impl FittedModel
Sourcepub fn extend_with_group(
&mut self,
request: ExtendGroupRequest,
) -> Result<(), String>
pub fn extend_with_group( &mut self, request: ExtendGroupRequest, ) -> Result<(), String>
Extend this model in place with the requested random-effect levels.
On success the model has passed both save-time gates
(validate_for_persistence and validate_numeric_finiteness), so any
caller may persist or predict with it directly. On failure the model is
left partially mutated and must be discarded — callers that need the
original should clone before calling.
Methods from Deref<Target = FittedModelPayload>§
pub fn set_training_feature_metadata( &mut self, headers: Vec<String>, feature_ranges: Vec<(f64, f64)>, )
Sourcepub fn apply_survival_time_basis(&mut self, snapshot: &SavedSurvivalTimeBasis)
pub fn apply_survival_time_basis(&mut self, snapshot: &SavedSurvivalTimeBasis)
Write the persistable time-basis snapshot for a survival model.
This is the only path that should populate the survival_time_*
fields used by the loader. Routing every FFI builder through this
helper guarantees no builder can silently drop a field — the
marginal-slope save→load bug was a builder that
missed survival_time_basis.
Trait Implementations§
Source§impl Clone for FittedModel
impl Clone for FittedModel
Source§fn clone(&self) -> FittedModel
fn clone(&self) -> FittedModel
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Deref for FittedModel
impl Deref for FittedModel
Source§type Target = FittedModelPayload
type Target = FittedModelPayload
Source§impl DerefMut for FittedModel
impl DerefMut for FittedModel
Source§impl<'de> Deserialize<'de> for FittedModel
impl<'de> Deserialize<'de> for FittedModel
Source§fn deserialize<__D>(
__deserializer: __D,
) -> Result<FittedModel, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(
__deserializer: __D,
) -> Result<FittedModel, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
Source§impl Serialize for FittedModel
impl Serialize for FittedModel
Source§fn serialize<__S>(
&self,
__serializer: __S,
) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>where
__S: Serializer,
fn serialize<__S>(
&self,
__serializer: __S,
) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>where
__S: Serializer,
Auto Trait Implementations§
impl !RefUnwindSafe for FittedModel
impl !UnwindSafe for FittedModel
impl Freeze for FittedModel
impl Send for FittedModel
impl Sync for FittedModel
impl Unpin for FittedModel
impl UnsafeUnpin for FittedModel
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<C> CloneExpand for Cwhere
C: Clone,
impl<C> CloneExpand for Cwhere
C: Clone,
fn __expand_clone_method(&self, _scope: &mut Scope) -> C
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> DistributionExt for Twhere
T: ?Sized,
impl<T> DistributionExt for Twhere
T: ?Sized,
impl<T, U> Imply<T> for U
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> Pointable for T
impl<T> Pointable for T
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
Source§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
Source§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
self from the equivalent element of its
superset. Read moreSource§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
self is actually part of its subset T (and can be converted to it).Source§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
self.to_subset but without any property checks. Always succeeds.Source§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
self to the equivalent element of its superset.Source§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
Source§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
self from the equivalent element of its
superset. Read moreSource§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
self is actually part of its subset T (and can be converted to it).Source§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
self.to_subset but without any property checks. Always succeeds.Source§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
self to the equivalent element of its superset.