pub struct SmoothTerm {
pub name: String,
pub coeff_range: Range<usize>,
pub shape: ShapeConstraint,
pub penalties_local: Vec<Array2<f64>>,
pub nullspace_dims: Vec<usize>,
pub penaltyinfo_local: Vec<PenaltyInfo>,
pub metadata: BasisMetadata,
pub lower_bounds_local: Option<Array1<f64>>,
pub linear_constraints_local: Option<LinearInequalityConstraints>,
pub kronecker_factored: Option<KroneckerFactoredBasis>,
pub joint_null_rotation: Option<JointNullRotation>,
pub unabsorbed_global_orthogonality: Option<Array2<f64>>,
}Fields§
§name: String§coeff_range: Range<usize>§shape: ShapeConstraint§penalties_local: Vec<Array2<f64>>§nullspace_dims: Vec<usize>§penaltyinfo_local: Vec<PenaltyInfo>§metadata: BasisMetadata§lower_bounds_local: Option<Array1<f64>>Optional term-local lower bounds for constrained coefficients.
-inf means unconstrained.
linear_constraints_local: Option<LinearInequalityConstraints>Optional term-local inequality constraints in local coefficient coordinates.
A_local * beta_local >= b_local.
kronecker_factored: Option<KroneckerFactoredBasis>Optional factored tensor-product representation preserved for operator-backed assembly in the main design builder.
joint_null_rotation: Option<JointNullRotation>Joint-null absorption rotation. Some(Q) records the orthonormal
(p_local × p_local) matrix that was applied to this term’s design
and per-block penalties at construction time:
term_design ← X_raw · Q, penalties_local[k] ← Qᵀ · S_raw · Q.
The smooth’s coefficient vector therefore lives in the rotated
(γ) coordinate system, with β_raw = Q · γ recovering the raw
pre-rotation parameterization. None means either no joint null
space (penalty already full-rank) or rotation was suppressed —
suppression fires when the smooth carries shape constraints
(lower bounds or local linear inequalities) that would lose their
cone geometry under a general orthogonal rotation.
Prediction-side replay: callers building a new-data design X_new_raw
from the raw basis must call SmoothTerm::apply_rotation_to_predict
(or equivalent) to obtain X_new = X_new_raw · Q matching this
term’s coefficient system.
Persistence replay: freeze_term_collection_from_design copies this
rotation into SmoothTermSpec, which is serialized with fitted-model
payloads and reused by the predict-time basis builder. Saved models
therefore replay the same X_new_raw · Q transform as in-memory
prediction.
unabsorbed_global_orthogonality: Option<Array2<f64>>Global-orthogonality transform that apply_global_smooth_identifiability
applied to this term’s design but could NOT embed into metadata
(factor-smooth kinds: sz metadata is per-marginal, fs metadata has
no transform slot — #978). freeze_term_collection_from_design copies
it onto the term’s basis spec (frozen_global_orthogonality) so the
predict-side rebuild replays it instead of emitting the unresidualized
(wider) design that the fitted coefficients no longer match.
Chart convention is per kind: post-Q Z for sz (the raw rebuild
reapplies Q itself, #700), full Q·Z chart for fs.
Implementations§
Source§impl SmoothTerm
impl SmoothTerm
Sourcepub fn apply_rotation_to_predict(
&self,
x_new_raw: Array2<f64>,
) -> Result<Array2<f64>, BasisError>
pub fn apply_rotation_to_predict( &self, x_new_raw: Array2<f64>, ) -> Result<Array2<f64>, BasisError>
Apply the joint-null absorption rotation to a raw new-data design
matrix, returning X_new_raw · Q when this term was rotated at
fit time, or X_new_raw unchanged when no rotation was applied.
Callers in the prediction path: after building the smooth’s basis
at new data via the raw basis builder (the same builder used at
fit time, applied to x_new instead of the training rows), call
this method on the resulting matrix before forming X · β. The
fitted β lives in γ-coordinates if Q was applied; multiplying
the un-rotated X_new_raw by β would give a wrong η.
Returns an error if the raw design’s column count does not match
the rotation’s p_local. The width invariant must hold: the raw
basis builder MUST emit the same p_local columns that the
fit-time builder did, and the rotation is (p_local × p_local).
Sourcepub fn wald_unpenalized_dim(&self) -> usize
pub fn wald_unpenalized_dim(&self) -> usize
Dimension of the joint null space of this term’s active penalties:
the coefficient directions penalized by no penalty. The smooth-component
Wald test (crate::inference::smooth_test::wood_smooth_test) treats this
many leading coefficients as genuine unpenalized fixed effects and tests
them at full rank; the remainder is the penalized sub-block tested with a
rank-≈EDF truncated pseudo-inverse.
Because every penalty block S_k is positive semi-definite,
vᵀ(Σ_k S_k)v = Σ_k vᵀ S_k v = 0 iff S_k v = 0 for every k; the
joint null space is therefore exactly null(Σ_k S_k), of dimension
p_local − rank(Σ_k S_k). This is the intersection of the per-penalty
null spaces, not their sum.
Summing the per-penalty nullspace_dims instead (the historical defect
behind #1360) unions the null spaces and badly over-counts: a
double-penalty smooth carries a bending penalty (null space = its
polynomial part) plus a complementary null-space ridge (which penalizes
exactly that polynomial part), so the two null spaces are disjoint and the
joint null space is empty — yet the per-penalty dims sum to nearly
p_local. Feeding that inflated count to the Wald test makes it test
almost the whole shrunk block at full rank, manufacturing overwhelming
“significance” for a term the fit drove to ~0 EDF.
Trait Implementations§
Source§impl Clone for SmoothTerm
impl Clone for SmoothTerm
Source§fn clone(&self) -> SmoothTerm
fn clone(&self) -> SmoothTerm
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl !Freeze for SmoothTerm
impl RefUnwindSafe for SmoothTerm
impl Send for SmoothTerm
impl Sync for SmoothTerm
impl Unpin for SmoothTerm
impl UnsafeUnpin for SmoothTerm
impl UnwindSafe for SmoothTerm
Blanket Implementations§
impl<T> Allocation for T
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<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
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> 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.