fdars_core/basis/constant.rs
1//! Constant (intercept) basis function.
2//!
3//! Provides the trivial single-function basis whose only member is the
4//! constant function `f(t) = 1`. Its evaluation on a grid `t` is a
5//! column-major `m × 1` matrix of ones — the intercept column of a
6//! regression design matrix. This mirrors R's `create.constant.basis`
7//! (`fda`) and `fdata.usc`'s constant basis.
8
9/// Evaluate the constant basis on a grid of evaluation points.
10///
11/// Returns a column-major `m × 1` matrix (`m = t.len()`) of ones, stored as a
12/// flat `Vec<f64>` — matching the `Vec<f64>`-returning, column-major convention
13/// of [`bspline_basis`](crate::basis::bspline_basis) and
14/// [`fourier_basis`](crate::basis::fourier_basis). The single basis function is
15/// the constant `1`, so every entry is `1.0`. Used as the intercept column of a
16/// regression design matrix.
17///
18/// This constructor is infallible: an empty grid yields an empty matrix.
19///
20/// # Examples
21///
22/// ```
23/// use fdars_core::basis::constant::constant_basis;
24///
25/// let t = vec![0.0, 0.5, 1.0];
26/// let basis = constant_basis(&t);
27/// // Column-major: m x 1 all-ones column
28/// assert_eq!(basis.len(), 3);
29/// assert!(basis.iter().all(|&v| (v - 1.0).abs() < 1e-12));
30/// ```
31pub fn constant_basis(t: &[f64]) -> Vec<f64> {
32 vec![1.0; t.len()]
33}
34
35#[cfg(test)]
36mod tests {
37 use super::*;
38
39 #[test]
40 fn ones_column_shape() {
41 let t = vec![0.0, 0.5, 1.0];
42 let basis = constant_basis(&t);
43 // m x 1 column-major → length m, every entry 1.0
44 assert_eq!(basis.len(), 3);
45 for &v in &basis {
46 assert!((v - 1.0).abs() < 1e-12);
47 }
48 }
49
50 #[test]
51 fn empty_input_no_panic() {
52 let basis = constant_basis(&[]);
53 assert_eq!(basis.len(), 0);
54 }
55
56 #[test]
57 fn intercept_only_fit_reproduces_mean() {
58 // Design matrix X = constant_basis(t) is an m x 1 ones column.
59 // The least-squares solution of X beta = y for the single-column X is
60 // beta = (Xᵀy) / (XᵀX) = sum(y) / m = mean(y). An intercept-only fit
61 // must therefore reproduce the response mean.
62 let t = vec![0.0, 0.25, 0.5, 0.75, 1.0];
63 let y = vec![3.0, -1.0, 7.5, 2.0, 0.5];
64 let x = constant_basis(&t);
65 assert_eq!(x.len(), y.len());
66
67 let xty: f64 = x.iter().zip(&y).map(|(&xi, &yi)| xi * yi).sum();
68 let xtx: f64 = x.iter().map(|&xi| xi * xi).sum();
69 let beta = xty / xtx;
70
71 let mean_y = y.iter().sum::<f64>() / y.len() as f64;
72 assert!((beta - mean_y).abs() < 1e-10, "beta={beta}, mean={mean_y}");
73 }
74}