scirs2_stats/functional/
types.rs1use scirs2_core::ndarray::{Array1, Array2};
8
9#[derive(Debug, Clone)]
15pub struct FunctionalData {
16 pub grid: Vec<f64>,
18 pub observations: Vec<Vec<f64>>,
20}
21
22impl FunctionalData {
23 pub fn new(grid: Vec<f64>, observations: Vec<Vec<f64>>) -> crate::error::StatsResult<Self> {
31 if grid.is_empty() {
32 return Err(crate::error::StatsError::InvalidInput(
33 "Grid must not be empty".to_string(),
34 ));
35 }
36 if observations.is_empty() {
37 return Err(crate::error::StatsError::InvalidInput(
38 "Observations must not be empty".to_string(),
39 ));
40 }
41 let t = grid.len();
42 for (i, obs) in observations.iter().enumerate() {
43 if obs.len() != t {
44 return Err(crate::error::StatsError::DimensionMismatch(format!(
45 "Observation {} has length {}, expected {} (grid length)",
46 i,
47 obs.len(),
48 t
49 )));
50 }
51 }
52 Ok(Self { grid, observations })
53 }
54
55 pub fn n_curves(&self) -> usize {
57 self.observations.len()
58 }
59
60 pub fn n_grid(&self) -> usize {
62 self.grid.len()
63 }
64}
65
66#[derive(Debug, Clone)]
68#[non_exhaustive]
69pub enum BasisType {
70 BSpline {
72 n_basis: usize,
74 degree: usize,
76 },
77 Fourier {
80 n_basis: usize,
82 },
83 Polynomial {
85 degree: usize,
87 },
88}
89
90#[derive(Debug, Clone)]
92pub struct FunctionalConfig {
93 pub basis: BasisType,
95 pub smoothing_param: Option<f64>,
97 pub n_components: usize,
99}
100
101impl Default for FunctionalConfig {
102 fn default() -> Self {
103 Self {
104 basis: BasisType::BSpline {
105 n_basis: 15,
106 degree: 3,
107 },
108 smoothing_param: None,
109 n_components: 3,
110 }
111 }
112}
113
114#[derive(Debug, Clone)]
116pub struct FPCAResult {
117 pub eigenvalues: Array1<f64>,
119 pub eigenfunctions: Array2<f64>,
121 pub scores: Array2<f64>,
123 pub variance_explained: Array1<f64>,
125 pub grid: Vec<f64>,
127}
128
129#[derive(Debug, Clone)]
131pub struct SoFResult {
132 pub beta: Array1<f64>,
134 pub intercept: f64,
136 pub beta_coefficients: Array1<f64>,
138 pub basis: BasisType,
140 pub grid: Vec<f64>,
142 pub lambda: f64,
144 pub fitted_values: Array1<f64>,
146 pub r_squared: f64,
148}
149
150#[derive(Debug, Clone)]
152pub struct FoFResult {
153 pub beta_surface: Array2<f64>,
156 pub beta_coefficients: Array1<f64>,
158 pub predictor_basis: BasisType,
160 pub response_basis: BasisType,
162 pub predictor_grid: Vec<f64>,
164 pub response_grid: Vec<f64>,
166 pub lambda: f64,
168 pub fitted_curves: Array2<f64>,
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_functional_data_valid() {
178 let grid = vec![0.0, 0.5, 1.0];
179 let obs = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]];
180 let data = FunctionalData::new(grid, obs).expect("Should succeed");
181 assert_eq!(data.n_curves(), 2);
182 assert_eq!(data.n_grid(), 3);
183 }
184
185 #[test]
186 fn test_functional_data_empty_grid() {
187 let result = FunctionalData::new(vec![], vec![vec![1.0]]);
188 assert!(result.is_err());
189 }
190
191 #[test]
192 fn test_functional_data_empty_observations() {
193 let result = FunctionalData::new(vec![0.0, 1.0], vec![]);
194 assert!(result.is_err());
195 }
196
197 #[test]
198 fn test_functional_data_dimension_mismatch() {
199 let result = FunctionalData::new(vec![0.0, 1.0], vec![vec![1.0, 2.0, 3.0]]);
200 assert!(result.is_err());
201 }
202
203 #[test]
204 fn test_functional_config_default() {
205 let config = FunctionalConfig::default();
206 assert!(config.smoothing_param.is_none());
207 assert_eq!(config.n_components, 3);
208 match &config.basis {
209 BasisType::BSpline { n_basis, degree } => {
210 assert_eq!(*n_basis, 15);
211 assert_eq!(*degree, 3);
212 }
213 _ => panic!("Default should be BSpline"),
214 }
215 }
216}