stats_claw/algorithms/decomposition/
mod.rs1mod factor_analysis;
16mod ica;
17mod lle;
18mod pca;
19mod tsne;
20mod umap;
21
22pub use factor_analysis::{FactorResult, factor_analysis};
23pub use ica::{IcaResult, fast_ica};
24pub use lle::lle;
25pub use pca::{PcaResult, pca};
26pub use tsne::tsne;
27pub use umap::umap;
28
29const JACOBI_SWEEPS: usize = 100;
31const JACOBI_TOL: f64 = 1e-14;
33
34#[must_use]
48pub fn count_to_f64(n: usize) -> f64 {
49 let wide = u64::try_from(n).unwrap_or(u64::MAX);
50 let hi = u32::try_from(wide >> 32).unwrap_or(0);
51 let lo = u32::try_from(wide & 0xFFFF_FFFF).unwrap_or(0);
52 f64::from(hi).mul_add(4_294_967_296.0, f64::from(lo))
53}
54
55#[must_use]
66pub fn column_means(data: &[Vec<f64>], dim: usize) -> Vec<f64> {
67 let mut sum = vec![0.0_f64; dim];
68 for row in data {
69 for (s, &v) in sum.iter_mut().zip(row) {
70 *s += v;
71 }
72 }
73 let n = count_to_f64(data.len());
74 if n > 0.0 {
75 for s in &mut sum {
76 *s /= n;
77 }
78 }
79 sum
80}
81
82#[must_use]
93pub fn mean_center(data: &[Vec<f64>], dim: usize) -> (Vec<Vec<f64>>, Vec<f64>) {
94 let means = column_means(data, dim);
95 let centered = data
96 .iter()
97 .map(|row| {
98 row.iter()
99 .zip(&means)
100 .map(|(&v, &m)| v - m)
101 .collect::<Vec<f64>>()
102 })
103 .collect();
104 (centered, means)
105}
106
107#[must_use]
120pub fn covariance(centered: &[Vec<f64>], dim: usize) -> Vec<f64> {
121 let mut cov = vec![0.0_f64; dim * dim];
122 for row in centered {
123 for i in 0..dim {
124 let ri = row.get(i).copied().unwrap_or(0.0);
125 for j in 0..dim {
126 let rj = row.get(j).copied().unwrap_or(0.0);
127 if let Some(slot) = cov.get_mut(i * dim + j) {
128 *slot = ri.mul_add(rj, *slot);
129 }
130 }
131 }
132 }
133 let denom = count_to_f64(centered.len()) - 1.0;
134 if denom > 0.0 {
135 for c in &mut cov {
136 *c /= denom;
137 }
138 }
139 cov
140}
141
142#[must_use]
144pub fn at(matrix: &[f64], n: usize, i: usize, j: usize) -> f64 {
145 matrix.get(i * n + j).copied().unwrap_or(0.0)
146}
147
148pub fn put(matrix: &mut [f64], n: usize, i: usize, j: usize, value: f64) {
150 if let Some(slot) = matrix.get_mut(i * n + j) {
151 *slot = value;
152 }
153}
154
155#[must_use]
157pub fn identity(n: usize) -> Vec<f64> {
158 let mut v = vec![0.0_f64; n * n];
159 for i in 0..n {
160 put(&mut v, n, i, i, 1.0);
161 }
162 v
163}
164
165#[must_use]
178pub fn jacobi_eigen(matrix: &[f64], n: usize) -> (Vec<f64>, Vec<f64>) {
179 let mut a = matrix.to_vec();
180 let mut v = identity(n);
181 for _ in 0..JACOBI_SWEEPS {
182 let mut off = 0.0_f64;
183 for p in 0..n {
184 for q in (p + 1)..n {
185 off += at(&a, n, p, q).abs();
186 }
187 }
188 if off < JACOBI_TOL {
189 break;
190 }
191 for p in 0..n {
192 for q in (p + 1)..n {
193 rotate(&mut a, &mut v, n, p, q);
194 }
195 }
196 }
197 let values = (0..n).map(|i| at(&a, n, i, i)).collect();
198 (values, v)
199}
200
201#[allow(clippy::many_single_char_names)]
208fn rotate(a: &mut [f64], v: &mut [f64], n: usize, p: usize, q: usize) {
209 let apq = at(a, n, p, q);
210 if apq.abs() < JACOBI_TOL {
211 return;
212 }
213 let app = at(a, n, p, p);
214 let aqq = at(a, n, q, q);
215 let theta = (aqq - app) / (2.0 * apq);
216 let t = theta.signum() / (theta.abs() + (theta * theta + 1.0).sqrt());
217 let c = 1.0 / (t * t + 1.0).sqrt();
218 let s = t * c;
219 for i in 0..n {
220 let aip = at(a, n, i, p);
221 let aiq = at(a, n, i, q);
222 put(a, n, i, p, c.mul_add(aip, -(s * aiq)));
223 put(a, n, i, q, s.mul_add(aip, c * aiq));
224 }
225 for i in 0..n {
226 let api = at(a, n, p, i);
227 let aqi = at(a, n, q, i);
228 put(a, n, p, i, c.mul_add(api, -(s * aqi)));
229 put(a, n, q, i, s.mul_add(api, c * aqi));
230 }
231 for i in 0..n {
232 let vip = at(v, n, i, p);
233 let viq = at(v, n, i, q);
234 put(v, n, i, p, c.mul_add(vip, -(s * viq)));
235 put(v, n, i, q, s.mul_add(vip, c * viq));
236 }
237}
238
239#[must_use]
249pub fn descending_order(values: &[f64]) -> Vec<usize> {
250 let mut order: Vec<usize> = (0..values.len()).collect();
251 order.sort_by(|&a, &b| {
252 let va = values.get(a).copied().unwrap_or(f64::NEG_INFINITY);
253 let vb = values.get(b).copied().unwrap_or(f64::NEG_INFINITY);
254 vb.partial_cmp(&va).unwrap_or(std::cmp::Ordering::Equal)
255 });
256 order
257}
258
259#[must_use]
274pub fn symmetric_inverse(matrix: &[f64], n: usize) -> Vec<f64> {
275 let (values, vectors) = jacobi_eigen(matrix, n);
276 let mut inv = vec![0.0_f64; n * n];
277 for i in 0..n {
278 for j in 0..n {
279 let mut acc = 0.0_f64;
280 for (k, &lambda) in values.iter().enumerate().take(n) {
281 let safe = if lambda.abs() < 1e-300 {
282 1e-300
283 } else {
284 lambda
285 };
286 acc += at(&vectors, n, i, k) * at(&vectors, n, j, k) / safe;
287 }
288 put(&mut inv, n, i, j, acc);
289 }
290 }
291 inv
292}
293
294#[must_use]
307pub fn reconstruction_error(original: &[Vec<f64>], reconstructed: &[Vec<f64>]) -> f64 {
308 let mut sum = 0.0_f64;
309 let mut count = 0_usize;
310 for (orig_row, rec_row) in original.iter().zip(reconstructed) {
311 for (&o, &r) in orig_row.iter().zip(rec_row) {
312 let d = o - r;
313 sum = d.mul_add(d, sum);
314 count += 1;
315 }
316 }
317 if count == 0 {
318 return 0.0;
319 }
320 sum / count_to_f64(count)
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 #[test]
328 fn mean_center_zeroes_the_column_means() {
329 let data = vec![vec![1.0, 10.0], vec![3.0, 20.0]];
330 let (centered, means) = mean_center(&data, 2);
331 assert!((means.first().copied().unwrap_or(0.0) - 2.0).abs() < 1e-12);
332 assert!((means.get(1).copied().unwrap_or(0.0) - 15.0).abs() < 1e-12);
333 let first = centered
334 .first()
335 .and_then(|r| r.first())
336 .copied()
337 .unwrap_or(0.0);
338 assert!((first + 1.0).abs() < 1e-12, "centered[0][0] was {first}");
339 }
340
341 #[test]
342 fn jacobi_recovers_diagonal_eigenvalues() {
343 let m = vec![3.0, 0.0, 0.0, 1.0];
345 let (values, _) = jacobi_eigen(&m, 2);
346 assert!(
347 values.iter().any(|v| (v - 3.0).abs() < 1e-9),
348 "missing eigenvalue 3: {values:?}"
349 );
350 assert!(
351 values.iter().any(|v| (v - 1.0).abs() < 1e-9),
352 "missing eigenvalue 1: {values:?}"
353 );
354 }
355
356 #[test]
357 fn covariance_of_centered_two_by_two() {
358 let centered = vec![vec![-1.0, 0.0], vec![1.0, 0.0]];
360 let cov = covariance(¢ered, 2);
361 assert!(
362 (at(&cov, 2, 0, 0) - 2.0).abs() < 1e-12,
363 "var_x = {}",
364 at(&cov, 2, 0, 0)
365 );
366 assert!(
367 at(&cov, 2, 1, 1).abs() < 1e-12,
368 "var_y = {}",
369 at(&cov, 2, 1, 1)
370 );
371 }
372
373 #[test]
374 fn reconstruction_error_is_zero_for_identical() {
375 let a = vec![vec![1.0, 2.0], vec![3.0, 4.0]];
376 assert!(reconstruction_error(&a, &a).abs() < 1e-12);
377 }
378
379 #[test]
380 fn descending_order_ranks_largest_first() {
381 assert_eq!(descending_order(&[1.0, 5.0, 3.0]), vec![1, 2, 0]);
382 }
383}