1use crate::error::PaCMAPError;
7use faer::{Mat, linalg::solvers::Svd};
8
9pub fn pca_init(data: &[f32], n: usize, d: usize) -> Result<Vec<[f32; 2]>, PaCMAPError> {
14 debug_assert_eq!(data.len(), n * d);
15
16 let mut mean = vec![0.0_f32; d];
18 for row in data.chunks_exact(d) {
19 for (j, &v) in row.iter().enumerate() {
20 mean[j] += v;
21 }
22 }
23 for m in &mut mean {
24 *m /= n as f32;
25 }
26
27 let mut cov = Mat::<f32>::zeros(d, d);
30 for row in data.chunks_exact(d) {
31 for i in 0..d {
32 let xi = row[i] - mean[i];
33 for j in i..d {
34 let xj = row[j] - mean[j];
35 cov[(i, j)] += xi * xj;
36 }
37 }
38 }
39 let inv_n = 1.0 / n as f32;
40 for i in 0..d {
41 for j in i..d {
42 cov[(i, j)] *= inv_n;
43 if i != j {
44 cov[(j, i)] = cov[(i, j)];
45 }
46 }
47 }
48
49 let svd = Svd::<f32>::new(cov.as_ref())
52 .map_err(|e| PaCMAPError::Pca(format!("{e:?}")))?;
53
54 let u = svd.U();
55 let pc1: Vec<f32> = (0..d).map(|r| *u.get(r, 0)).collect();
57 let pc2: Vec<f32> = if d >= 2 { (0..d).map(|r| *u.get(r, 1)).collect() } else { vec![0.0; d] };
58
59 let mut embedding = Vec::with_capacity(n);
61 for row in data.chunks_exact(d) {
62 let mut s1 = 0.0_f32;
63 let mut s2 = 0.0_f32;
64 for j in 0..d {
65 let v = row[j] - mean[j];
66 s1 += v * pc1[j];
67 s2 += v * pc2[j];
68 }
69 embedding.push([s1, s2]);
70 }
71
72 Ok(embedding)
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn pca_separates_axis_aligned_clusters() {
81 let mut data: Vec<f32> = Vec::new();
83 for _ in 0..50 {
84 data.extend_from_slice(&[0.0_f32, 0.0]);
85 }
86 for _ in 0..50 {
87 data.extend_from_slice(&[10.0_f32, 0.0]);
88 }
89 let emb = pca_init(&data, 100, 2).unwrap();
90 let mean_left_x = emb[..50].iter().map(|p| p[0]).sum::<f32>() / 50.0;
92 let mean_right_x = emb[50..].iter().map(|p| p[0]).sum::<f32>() / 50.0;
93 assert!(
94 (mean_left_x - mean_right_x).abs() > 1.0,
95 "PCA should separate the two clusters along PC1"
96 );
97 }
98}