Skip to main content

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}