fdars_core/basis/
projection.rs1use super::bspline::bspline_basis;
4use super::fourier::fourier_basis;
5use super::helpers::svd_pseudoinverse;
6use crate::iter_maybe_parallel;
7use crate::matrix::FdMatrix;
8use nalgebra::DMatrix;
9#[cfg(feature = "parallel")]
10use rayon::iter::ParallelIterator;
11
12#[derive(Debug, Clone, Copy, PartialEq)]
18#[non_exhaustive]
19pub enum ProjectionBasisType {
20 Bspline,
22 Fourier,
24}
25
26impl ProjectionBasisType {
27 #[must_use]
32 pub fn from_i32(value: i32) -> Self {
33 if value == 1 {
34 Self::Fourier
35 } else {
36 Self::Bspline
37 }
38 }
39
40 #[must_use]
42 pub fn to_i32(self) -> i32 {
43 match self {
44 Self::Bspline => 0,
45 Self::Fourier => 1,
46 }
47 }
48}
49
50#[derive(Debug, Clone)]
52#[non_exhaustive]
53pub struct BasisProjectionResult {
54 pub coefficients: FdMatrix,
56 pub n_basis: usize,
58}
59
60fn evaluate_projection_basis(
62 argvals: &[f64],
63 nbasis: usize,
64 basis_type: ProjectionBasisType,
65) -> Vec<f64> {
66 match basis_type {
67 ProjectionBasisType::Fourier => fourier_basis(argvals, nbasis),
68 ProjectionBasisType::Bspline => {
69 bspline_basis(argvals, nbasis.saturating_sub(4).max(2), 4)
71 }
72 }
73}
74
75pub fn fdata_to_basis(
99 data: &FdMatrix,
100 argvals: &[f64],
101 nbasis: usize,
102 basis_type: ProjectionBasisType,
103) -> Option<BasisProjectionResult> {
104 let n = data.nrows();
105 let m = data.ncols();
106 if n == 0 || m == 0 || argvals.len() != m || nbasis < 2 {
107 return None;
108 }
109
110 let basis = evaluate_projection_basis(argvals, nbasis, basis_type);
111
112 let actual_nbasis = basis.len() / m;
113 let b_mat = DMatrix::from_column_slice(m, actual_nbasis, &basis);
114
115 let btb = &b_mat.transpose() * &b_mat;
116 let btb_inv = svd_pseudoinverse(&btb)?;
117 let proj = btb_inv * b_mat.transpose();
118
119 let coefs: Vec<f64> = iter_maybe_parallel!(0..n)
120 .flat_map(|i| {
121 let curve: Vec<f64> = (0..m).map(|j| data[(i, j)]).collect();
122 (0..actual_nbasis)
123 .map(|k| {
124 let mut sum = 0.0;
125 for j in 0..m {
126 sum += proj[(k, j)] * curve[j];
127 }
128 sum
129 })
130 .collect::<Vec<_>>()
131 })
132 .collect();
133
134 Some(BasisProjectionResult {
135 coefficients: FdMatrix::from_column_major(coefs, n, actual_nbasis)
136 .expect("dimension invariant: data.len() == n * m"),
137 n_basis: actual_nbasis,
138 })
139}
140
141pub fn fdata_to_basis_1d(
149 data: &FdMatrix,
150 argvals: &[f64],
151 nbasis: usize,
152 basis_type: i32,
153) -> Option<BasisProjectionResult> {
154 fdata_to_basis(
155 data,
156 argvals,
157 nbasis,
158 ProjectionBasisType::from_i32(basis_type),
159 )
160}
161
162pub fn basis_to_fdata(
170 coefs: &FdMatrix,
171 argvals: &[f64],
172 nbasis: usize,
173 basis_type: ProjectionBasisType,
174) -> FdMatrix {
175 let n = coefs.nrows();
176 let coefs_ncols = coefs.ncols();
177 let m = argvals.len();
178 if n == 0 || m == 0 || nbasis < 2 {
179 return FdMatrix::zeros(0, 0);
180 }
181
182 let basis = evaluate_projection_basis(argvals, nbasis, basis_type);
183
184 let actual_nbasis = basis.len() / m;
185
186 let flat: Vec<f64> = iter_maybe_parallel!(0..n)
187 .flat_map(|i| {
188 (0..m)
189 .map(|j| {
190 let mut sum = 0.0;
191 for k in 0..actual_nbasis.min(coefs_ncols) {
192 sum += coefs[(i, k)] * basis[j + k * m];
193 }
194 sum
195 })
196 .collect::<Vec<_>>()
197 })
198 .collect();
199
200 FdMatrix::from_column_major(flat, n, m).expect("dimension invariant: data.len() == n * m")
201}
202
203pub fn basis_to_fdata_1d(
205 coefs: &FdMatrix,
206 argvals: &[f64],
207 nbasis: usize,
208 basis_type: i32,
209) -> FdMatrix {
210 basis_to_fdata(
211 coefs,
212 argvals,
213 nbasis,
214 ProjectionBasisType::from_i32(basis_type),
215 )
216}