ghostflow_ml/
manifold.rs

1//! Manifold Learning - t-SNE, UMAP, Isomap, LLE, MDS
2
3use ghostflow_core::Tensor;
4use rand::prelude::*;
5
6/// t-Distributed Stochastic Neighbor Embedding
7pub struct TSNE {
8    pub n_components: usize,
9    pub perplexity: f32,
10    pub learning_rate: f32,
11    pub n_iter: usize,
12    pub early_exaggeration: f32,
13    pub metric: TSNEMetric,
14    embedding_: Option<Vec<f32>>,
15    kl_divergence_: Option<f32>,
16}
17
18#[derive(Clone, Copy, Debug)]
19pub enum TSNEMetric {
20    Euclidean,
21    Cosine,
22    Manhattan,
23}
24
25impl TSNE {
26    pub fn new(n_components: usize) -> Self {
27        TSNE {
28            n_components,
29            perplexity: 30.0,
30            learning_rate: 200.0,
31            n_iter: 1000,
32            early_exaggeration: 12.0,
33            metric: TSNEMetric::Euclidean,
34            embedding_: None,
35            kl_divergence_: None,
36        }
37    }
38
39    pub fn perplexity(mut self, p: f32) -> Self {
40        self.perplexity = p;
41        self
42    }
43
44    pub fn learning_rate(mut self, lr: f32) -> Self {
45        self.learning_rate = lr;
46        self
47    }
48
49    pub fn n_iter(mut self, n: usize) -> Self {
50        self.n_iter = n;
51        self
52    }
53
54    fn distance(&self, a: &[f32], b: &[f32]) -> f32 {
55        match self.metric {
56            TSNEMetric::Euclidean => {
57                a.iter().zip(b.iter()).map(|(&ai, &bi)| (ai - bi).powi(2)).sum::<f32>().sqrt()
58            }
59            TSNEMetric::Cosine => {
60                let dot: f32 = a.iter().zip(b.iter()).map(|(&ai, &bi)| ai * bi).sum();
61                let norm_a: f32 = a.iter().map(|&x| x * x).sum::<f32>().sqrt();
62                let norm_b: f32 = b.iter().map(|&x| x * x).sum::<f32>().sqrt();
63                1.0 - dot / (norm_a * norm_b + 1e-10)
64            }
65            TSNEMetric::Manhattan => {
66                a.iter().zip(b.iter()).map(|(&ai, &bi)| (ai - bi).abs()).sum()
67            }
68        }
69    }
70
71    fn compute_pairwise_distances(&self, x: &[f32], n_samples: usize, n_features: usize) -> Vec<Vec<f32>> {
72        let mut distances = vec![vec![0.0f32; n_samples]; n_samples];
73        
74        for i in 0..n_samples {
75            for j in (i + 1)..n_samples {
76                let xi = &x[i * n_features..(i + 1) * n_features];
77                let xj = &x[j * n_features..(j + 1) * n_features];
78                let d = self.distance(xi, xj);
79                distances[i][j] = d;
80                distances[j][i] = d;
81            }
82        }
83        
84        distances
85    }
86
87    fn compute_perplexity_row(&self, distances: &[f32], target_perplexity: f32, i: usize) -> (Vec<f32>, f32) {
88        let n = distances.len();
89        let target_entropy = target_perplexity.ln();
90        
91        let mut beta = 1.0f32;
92        let mut beta_min = f32::NEG_INFINITY;
93        let mut beta_max = f32::INFINITY;
94        
95        let mut p = vec![0.0f32; n];
96        
97        for _ in 0..50 {
98            // Compute probabilities
99            let mut sum_p = 0.0f32;
100            for j in 0..n {
101                if i != j {
102                    p[j] = (-beta * distances[j] * distances[j]).exp();
103                    sum_p += p[j];
104                } else {
105                    p[j] = 0.0;
106                }
107            }
108            
109            // Normalize
110            if sum_p > 0.0 {
111                for pj in &mut p {
112                    *pj /= sum_p;
113                }
114            }
115            
116            // Compute entropy
117            let mut entropy = 0.0f32;
118            for j in 0..n {
119                if p[j] > 1e-10 {
120                    entropy -= p[j] * p[j].ln();
121                }
122            }
123            
124            // Binary search for beta
125            let diff = entropy - target_entropy;
126            if diff.abs() < 1e-5 {
127                break;
128            }
129            
130            if diff > 0.0 {
131                beta_min = beta;
132                beta = if beta_max.is_infinite() { beta * 2.0 } else { (beta + beta_max) / 2.0 };
133            } else {
134                beta_max = beta;
135                beta = if beta_min.is_infinite() { beta / 2.0 } else { (beta + beta_min) / 2.0 };
136            }
137        }
138        
139        (p, beta)
140    }
141
142    fn compute_joint_probabilities(&self, distances: &[Vec<f32>], n_samples: usize) -> Vec<Vec<f32>> {
143        let mut p = vec![vec![0.0f32; n_samples]; n_samples];
144        
145        // Compute conditional probabilities
146        for i in 0..n_samples {
147            let (p_row, _) = self.compute_perplexity_row(&distances[i], self.perplexity, i);
148            for j in 0..n_samples {
149                p[i][j] = p_row[j];
150            }
151        }
152        
153        // Symmetrize
154        for i in 0..n_samples {
155            for j in (i + 1)..n_samples {
156                let pij = (p[i][j] + p[j][i]) / (2.0 * n_samples as f32);
157                p[i][j] = pij.max(1e-12);
158                p[j][i] = pij.max(1e-12);
159            }
160        }
161        
162        p
163    }
164
165    fn compute_q_distribution(&self, y: &[f32], n_samples: usize) -> Vec<Vec<f32>> {
166        let mut q = vec![vec![0.0f32; n_samples]; n_samples];
167        let mut sum_q = 0.0f32;
168        
169        for i in 0..n_samples {
170            for j in (i + 1)..n_samples {
171                let mut dist_sq = 0.0f32;
172                for d in 0..self.n_components {
173                    let diff = y[i * self.n_components + d] - y[j * self.n_components + d];
174                    dist_sq += diff * diff;
175                }
176                
177                // Student-t distribution with 1 degree of freedom
178                let qij = 1.0 / (1.0 + dist_sq);
179                q[i][j] = qij;
180                q[j][i] = qij;
181                sum_q += 2.0 * qij;
182            }
183        }
184        
185        // Normalize
186        if sum_q > 0.0 {
187            for i in 0..n_samples {
188                for j in 0..n_samples {
189                    q[i][j] = (q[i][j] / sum_q).max(1e-12);
190                }
191            }
192        }
193        
194        q
195    }
196
197    pub fn fit_transform(&mut self, x: &Tensor) -> Tensor {
198        let x_data = x.data_f32();
199        let n_samples = x.dims()[0];
200        let n_features = x.dims()[1];
201
202        // Compute pairwise distances
203        let distances = self.compute_pairwise_distances(&x_data, n_samples, n_features);
204        
205        // Compute joint probabilities P
206        let mut p = self.compute_joint_probabilities(&distances, n_samples);
207        
208        // Early exaggeration
209        for i in 0..n_samples {
210            for j in 0..n_samples {
211                p[i][j] *= self.early_exaggeration;
212            }
213        }
214        
215        // Initialize embedding randomly
216        let mut rng = thread_rng();
217        let mut y: Vec<f32> = (0..n_samples * self.n_components)
218            .map(|_| rng.gen::<f32>() * 0.0001)
219            .collect();
220        
221        // Gradient descent with momentum
222        let mut velocity = vec![0.0f32; n_samples * self.n_components];
223        let momentum = 0.5f32;
224        let final_momentum = 0.8f32;
225        let momentum_switch_iter = 250;
226        
227        for iter in 0..self.n_iter {
228            // Remove early exaggeration after some iterations
229            if iter == 100 {
230                for i in 0..n_samples {
231                    for j in 0..n_samples {
232                        p[i][j] /= self.early_exaggeration;
233                    }
234                }
235            }
236            
237            // Compute Q distribution
238            let q = self.compute_q_distribution(&y, n_samples);
239            
240            // Compute gradients
241            let mut grad = vec![0.0f32; n_samples * self.n_components];
242            
243            for i in 0..n_samples {
244                for j in 0..n_samples {
245                    if i != j {
246                        let mut dist_sq = 0.0f32;
247                        for d in 0..self.n_components {
248                            let diff = y[i * self.n_components + d] - y[j * self.n_components + d];
249                            dist_sq += diff * diff;
250                        }
251                        
252                        let pq_diff = p[i][j] - q[i][j];
253                        let mult = 4.0 * pq_diff / (1.0 + dist_sq);
254                        
255                        for d in 0..self.n_components {
256                            let diff = y[i * self.n_components + d] - y[j * self.n_components + d];
257                            grad[i * self.n_components + d] += mult * diff;
258                        }
259                    }
260                }
261            }
262            
263            // Update with momentum
264            let current_momentum = if iter < momentum_switch_iter { momentum } else { final_momentum };
265            
266            for i in 0..n_samples * self.n_components {
267                velocity[i] = current_momentum * velocity[i] - self.learning_rate * grad[i];
268                y[i] += velocity[i];
269            }
270            
271            // Center the embedding
272            let mut mean = vec![0.0f32; self.n_components];
273            for i in 0..n_samples {
274                for d in 0..self.n_components {
275                    mean[d] += y[i * self.n_components + d];
276                }
277            }
278            for d in 0..self.n_components {
279                mean[d] /= n_samples as f32;
280            }
281            for i in 0..n_samples {
282                for d in 0..self.n_components {
283                    y[i * self.n_components + d] -= mean[d];
284                }
285            }
286        }
287        
288        // Compute final KL divergence
289        let q = self.compute_q_distribution(&y, n_samples);
290        let mut kl = 0.0f32;
291        for i in 0..n_samples {
292            for j in 0..n_samples {
293                if i != j && p[i][j] > 1e-12 {
294                    kl += p[i][j] * (p[i][j] / q[i][j]).ln();
295                }
296            }
297        }
298        
299        self.embedding_ = Some(y.clone());
300        self.kl_divergence_ = Some(kl);
301        
302        Tensor::from_slice(&y, &[n_samples, self.n_components]).unwrap()
303    }
304}
305
306
307/// Multidimensional Scaling
308pub struct MDS {
309    pub n_components: usize,
310    pub metric: bool,
311    pub n_init: usize,
312    pub max_iter: usize,
313    pub eps: f32,
314    pub dissimilarity: Dissimilarity,
315    embedding_: Option<Vec<f32>>,
316    stress_: Option<f32>,
317}
318
319#[derive(Clone, Copy, Debug)]
320pub enum Dissimilarity {
321    Euclidean,
322    Precomputed,
323}
324
325impl MDS {
326    pub fn new(n_components: usize) -> Self {
327        MDS {
328            n_components,
329            metric: true,
330            n_init: 4,
331            max_iter: 300,
332            eps: 1e-3,
333            dissimilarity: Dissimilarity::Euclidean,
334            embedding_: None,
335            stress_: None,
336        }
337    }
338
339    pub fn metric(mut self, metric: bool) -> Self {
340        self.metric = metric;
341        self
342    }
343
344    fn compute_distances(&self, x: &[f32], n_samples: usize, n_features: usize) -> Vec<Vec<f32>> {
345        let mut distances = vec![vec![0.0f32; n_samples]; n_samples];
346        
347        for i in 0..n_samples {
348            for j in (i + 1)..n_samples {
349                let mut dist_sq = 0.0f32;
350                for k in 0..n_features {
351                    let diff = x[i * n_features + k] - x[j * n_features + k];
352                    dist_sq += diff * diff;
353                }
354                let dist = dist_sq.sqrt();
355                distances[i][j] = dist;
356                distances[j][i] = dist;
357            }
358        }
359        
360        distances
361    }
362
363    fn compute_stress(&self, y: &[f32], distances: &[Vec<f32>], n_samples: usize) -> f32 {
364        let mut stress = 0.0f32;
365        let mut norm = 0.0f32;
366        
367        for i in 0..n_samples {
368            for j in (i + 1)..n_samples {
369                let mut dist_sq = 0.0f32;
370                for d in 0..self.n_components {
371                    let diff = y[i * self.n_components + d] - y[j * self.n_components + d];
372                    dist_sq += diff * diff;
373                }
374                let dist = dist_sq.sqrt();
375                
376                let diff = distances[i][j] - dist;
377                stress += diff * diff;
378                norm += distances[i][j] * distances[i][j];
379            }
380        }
381        
382        (stress / norm.max(1e-10)).sqrt()
383    }
384
385    pub fn fit_transform(&mut self, x: &Tensor) -> Tensor {
386        let x_data = x.data_f32();
387        let n_samples = x.dims()[0];
388        let n_features = x.dims()[1];
389
390        // Compute distance matrix
391        let distances = match self.dissimilarity {
392            Dissimilarity::Euclidean => self.compute_distances(&x_data, n_samples, n_features),
393            Dissimilarity::Precomputed => {
394                // Assume x is already a distance matrix
395                let mut d = vec![vec![0.0f32; n_samples]; n_samples];
396                for i in 0..n_samples {
397                    for j in 0..n_samples {
398                        d[i][j] = x_data[i * n_samples + j];
399                    }
400                }
401                d
402            }
403        };
404
405        // Classical MDS using double centering
406        let mut best_y = None;
407        let mut best_stress = f32::INFINITY;
408
409        for _ in 0..self.n_init {
410            // Double centering: B = -0.5 * J * D^2 * J where J = I - 1/n * 11^T
411            let mut b = vec![vec![0.0f32; n_samples]; n_samples];
412            
413            // Compute D^2
414            for i in 0..n_samples {
415                for j in 0..n_samples {
416                    b[i][j] = -0.5 * distances[i][j] * distances[i][j];
417                }
418            }
419            
420            // Row means
421            let row_means: Vec<f32> = (0..n_samples)
422                .map(|i| b[i].iter().sum::<f32>() / n_samples as f32)
423                .collect();
424            
425            // Column means
426            let col_means: Vec<f32> = (0..n_samples)
427                .map(|j| (0..n_samples).map(|i| b[i][j]).sum::<f32>() / n_samples as f32)
428                .collect();
429            
430            // Grand mean
431            let grand_mean: f32 = row_means.iter().sum::<f32>() / n_samples as f32;
432            
433            // Double centering
434            for i in 0..n_samples {
435                for j in 0..n_samples {
436                    b[i][j] = b[i][j] - row_means[i] - col_means[j] + grand_mean;
437                }
438            }
439            
440            // Eigendecomposition using power iteration
441            let mut y = vec![0.0f32; n_samples * self.n_components];
442            let mut rng = thread_rng();
443            
444            // Flatten B for matrix operations
445            let mut b_flat: Vec<f32> = b.iter().flat_map(|row| row.clone()).collect();
446            
447            for comp in 0..self.n_components {
448                // Power iteration
449                let mut v: Vec<f32> = (0..n_samples).map(|_| rng.gen::<f32>()).collect();
450                let norm: f32 = v.iter().map(|&x| x * x).sum::<f32>().sqrt();
451                for vi in &mut v {
452                    *vi /= norm;
453                }
454                
455                for _ in 0..100 {
456                    // w = B * v
457                    let mut w = vec![0.0f32; n_samples];
458                    for i in 0..n_samples {
459                        for j in 0..n_samples {
460                            w[i] += b_flat[i * n_samples + j] * v[j];
461                        }
462                    }
463                    
464                    // Normalize
465                    let norm: f32 = w.iter().map(|&x| x * x).sum::<f32>().sqrt();
466                    if norm < 1e-10 {
467                        break;
468                    }
469                    for wi in &mut w {
470                        *wi /= norm;
471                    }
472                    
473                    // Check convergence
474                    let diff: f32 = v.iter().zip(w.iter()).map(|(&vi, &wi)| (vi - wi).abs()).sum();
475                    v = w;
476                    
477                    if diff < 1e-6 {
478                        break;
479                    }
480                }
481                
482                // Compute eigenvalue
483                let mut eigenvalue = 0.0f32;
484                for i in 0..n_samples {
485                    let mut bv = 0.0f32;
486                    for j in 0..n_samples {
487                        bv += b_flat[i * n_samples + j] * v[j];
488                    }
489                    eigenvalue += v[i] * bv;
490                }
491                
492                // Store component
493                let scale = eigenvalue.max(0.0).sqrt();
494                for i in 0..n_samples {
495                    y[i * self.n_components + comp] = v[i] * scale;
496                }
497                
498                // Deflate B
499                for i in 0..n_samples {
500                    for j in 0..n_samples {
501                        b_flat[i * n_samples + j] -= eigenvalue * v[i] * v[j];
502                    }
503                }
504            }
505            
506            let stress = self.compute_stress(&y, &distances, n_samples);
507            if stress < best_stress {
508                best_stress = stress;
509                best_y = Some(y);
510            }
511        }
512
513        let y = best_y.unwrap();
514        self.embedding_ = Some(y.clone());
515        self.stress_ = Some(best_stress);
516        
517        Tensor::from_slice(&y, &[n_samples, self.n_components]).unwrap()
518    }
519}
520
521/// Isomap - Isometric Mapping
522pub struct Isomap {
523    pub n_components: usize,
524    pub n_neighbors: usize,
525    embedding_: Option<Vec<f32>>,
526    reconstruction_error_: Option<f32>,
527}
528
529impl Isomap {
530    pub fn new(n_components: usize) -> Self {
531        Isomap {
532            n_components,
533            n_neighbors: 5,
534            embedding_: None,
535            reconstruction_error_: None,
536        }
537    }
538
539    pub fn n_neighbors(mut self, n: usize) -> Self {
540        self.n_neighbors = n;
541        self
542    }
543
544    fn compute_knn_graph(&self, x: &[f32], n_samples: usize, n_features: usize) -> Vec<Vec<(usize, f32)>> {
545        let mut graph = vec![Vec::new(); n_samples];
546        
547        for i in 0..n_samples {
548            let mut distances: Vec<(usize, f32)> = (0..n_samples)
549                .filter(|&j| j != i)
550                .map(|j| {
551                    let mut dist_sq = 0.0f32;
552                    for k in 0..n_features {
553                        let diff = x[i * n_features + k] - x[j * n_features + k];
554                        dist_sq += diff * diff;
555                    }
556                    (j, dist_sq.sqrt())
557                })
558                .collect();
559            
560            distances.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
561            graph[i] = distances.into_iter().take(self.n_neighbors).collect();
562        }
563        
564        graph
565    }
566
567    fn floyd_warshall(&self, graph: &[Vec<(usize, f32)>], n_samples: usize) -> Vec<Vec<f32>> {
568        let mut dist = vec![vec![f32::INFINITY; n_samples]; n_samples];
569        
570        // Initialize with graph edges
571        for i in 0..n_samples {
572            dist[i][i] = 0.0;
573            for &(j, d) in &graph[i] {
574                dist[i][j] = d;
575                dist[j][i] = d;  // Make symmetric
576            }
577        }
578        
579        // Floyd-Warshall
580        for k in 0..n_samples {
581            for i in 0..n_samples {
582                for j in 0..n_samples {
583                    if dist[i][k] + dist[k][j] < dist[i][j] {
584                        dist[i][j] = dist[i][k] + dist[k][j];
585                    }
586                }
587            }
588        }
589        
590        dist
591    }
592
593    pub fn fit_transform(&mut self, x: &Tensor) -> Tensor {
594        let x_data = x.data_f32();
595        let n_samples = x.dims()[0];
596        let n_features = x.dims()[1];
597
598        // Build k-NN graph
599        let graph = self.compute_knn_graph(&x_data, n_samples, n_features);
600        
601        // Compute geodesic distances using Floyd-Warshall
602        let geodesic_dist = self.floyd_warshall(&graph, n_samples);
603        
604        // Apply classical MDS to geodesic distances
605        let mut mds = MDS::new(self.n_components);
606        mds.dissimilarity = Dissimilarity::Precomputed;
607        
608        // Flatten geodesic distances
609        let dist_flat: Vec<f32> = geodesic_dist.iter().flat_map(|row| row.clone()).collect();
610        let dist_tensor = Tensor::from_slice(&dist_flat, &[n_samples, n_samples]).unwrap();
611        
612        let embedding = mds.fit_transform(&dist_tensor);
613        
614        self.embedding_ = Some(embedding.data_f32());
615        self.reconstruction_error_ = mds.stress_;
616        
617        embedding
618    }
619}
620
621
622/// Locally Linear Embedding
623pub struct LocallyLinearEmbedding {
624    pub n_components: usize,
625    pub n_neighbors: usize,
626    pub reg: f32,
627    pub method: LLEMethod,
628    embedding_: Option<Vec<f32>>,
629    #[allow(dead_code)]
630    reconstruction_error_: Option<f32>,
631}
632
633#[derive(Clone, Copy, Debug)]
634pub enum LLEMethod {
635    Standard,
636    Modified,
637    HLLE,
638    LTSA,
639}
640
641impl LocallyLinearEmbedding {
642    pub fn new(n_components: usize) -> Self {
643        LocallyLinearEmbedding {
644            n_components,
645            n_neighbors: 5,
646            reg: 1e-3,
647            method: LLEMethod::Standard,
648            embedding_: None,
649            reconstruction_error_: None,
650        }
651    }
652
653    pub fn n_neighbors(mut self, n: usize) -> Self {
654        self.n_neighbors = n;
655        self
656    }
657
658    fn find_neighbors(&self, x: &[f32], n_samples: usize, n_features: usize) -> Vec<Vec<usize>> {
659        let mut neighbors = vec![Vec::new(); n_samples];
660        
661        for i in 0..n_samples {
662            let mut distances: Vec<(usize, f32)> = (0..n_samples)
663                .filter(|&j| j != i)
664                .map(|j| {
665                    let mut dist_sq = 0.0f32;
666                    for k in 0..n_features {
667                        let diff = x[i * n_features + k] - x[j * n_features + k];
668                        dist_sq += diff * diff;
669                    }
670                    (j, dist_sq)
671                })
672                .collect();
673            
674            distances.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
675            neighbors[i] = distances.into_iter().take(self.n_neighbors).map(|(j, _)| j).collect();
676        }
677        
678        neighbors
679    }
680
681    fn compute_weights(&self, x: &[f32], neighbors: &[Vec<usize>], n_samples: usize, n_features: usize) -> Vec<Vec<f32>> {
682        let mut weights = vec![vec![0.0f32; n_samples]; n_samples];
683        
684        for i in 0..n_samples {
685            let k = neighbors[i].len();
686            if k == 0 {
687                continue;
688            }
689            
690            // Build local covariance matrix
691            let mut c = vec![0.0f32; k * k];
692            
693            for (a, &ja) in neighbors[i].iter().enumerate() {
694                for (b, &jb) in neighbors[i].iter().enumerate() {
695                    let mut dot = 0.0f32;
696                    for f in 0..n_features {
697                        let diff_a = x[ja * n_features + f] - x[i * n_features + f];
698                        let diff_b = x[jb * n_features + f] - x[i * n_features + f];
699                        dot += diff_a * diff_b;
700                    }
701                    c[a * k + b] = dot;
702                }
703            }
704            
705            // Add regularization
706            let trace: f32 = (0..k).map(|a| c[a * k + a]).sum();
707            let reg = self.reg * trace / k as f32;
708            for a in 0..k {
709                c[a * k + a] += reg;
710            }
711            
712            // Solve C * w = 1 for w
713            let ones = vec![1.0f32; k];
714            let w = self.solve_linear_system(&c, &ones, k);
715            
716            // Normalize weights
717            let sum: f32 = w.iter().sum();
718            for (a, &ja) in neighbors[i].iter().enumerate() {
719                weights[i][ja] = w[a] / sum.max(1e-10);
720            }
721        }
722        
723        weights
724    }
725
726    fn solve_linear_system(&self, a: &[f32], b: &[f32], n: usize) -> Vec<f32> {
727        // Simple Gaussian elimination
728        let mut aug = vec![0.0f32; n * (n + 1)];
729        for i in 0..n {
730            for j in 0..n {
731                aug[i * (n + 1) + j] = a[i * n + j];
732            }
733            aug[i * (n + 1) + n] = b[i];
734        }
735        
736        // Forward elimination
737        for i in 0..n {
738            // Find pivot
739            let mut max_row = i;
740            for k in (i + 1)..n {
741                if aug[k * (n + 1) + i].abs() > aug[max_row * (n + 1) + i].abs() {
742                    max_row = k;
743                }
744            }
745            
746            // Swap rows
747            for j in 0..=n {
748                let tmp = aug[i * (n + 1) + j];
749                aug[i * (n + 1) + j] = aug[max_row * (n + 1) + j];
750                aug[max_row * (n + 1) + j] = tmp;
751            }
752            
753            // Eliminate
754            let pivot = aug[i * (n + 1) + i];
755            if pivot.abs() < 1e-10 {
756                continue;
757            }
758            
759            for k in (i + 1)..n {
760                let factor = aug[k * (n + 1) + i] / pivot;
761                for j in i..=n {
762                    aug[k * (n + 1) + j] -= factor * aug[i * (n + 1) + j];
763                }
764            }
765        }
766        
767        // Back substitution
768        let mut x = vec![0.0f32; n];
769        for i in (0..n).rev() {
770            let mut sum = aug[i * (n + 1) + n];
771            for j in (i + 1)..n {
772                sum -= aug[i * (n + 1) + j] * x[j];
773            }
774            let pivot = aug[i * (n + 1) + i];
775            x[i] = if pivot.abs() > 1e-10 { sum / pivot } else { 0.0 };
776        }
777        
778        x
779    }
780
781    pub fn fit_transform(&mut self, x: &Tensor) -> Tensor {
782        let x_data = x.data_f32();
783        let n_samples = x.dims()[0];
784        let n_features = x.dims()[1];
785
786        // Find neighbors
787        let neighbors = self.find_neighbors(&x_data, n_samples, n_features);
788        
789        // Compute reconstruction weights
790        let weights = self.compute_weights(&x_data, &neighbors, n_samples, n_features);
791        
792        // Build M = (I - W)^T (I - W)
793        let mut m = vec![0.0f32; n_samples * n_samples];
794        
795        for i in 0..n_samples {
796            for j in 0..n_samples {
797                let mut val = 0.0f32;
798                
799                for k in 0..n_samples {
800                    let ik = if i == k { 1.0 } else { 0.0 } - weights[k][i];
801                    let jk = if j == k { 1.0 } else { 0.0 } - weights[k][j];
802                    val += ik * jk;
803                }
804                
805                m[i * n_samples + j] = val;
806            }
807        }
808        
809        // Find smallest eigenvectors of M (skip the first one which is constant)
810        let mut y = vec![0.0f32; n_samples * self.n_components];
811        let mut rng = thread_rng();
812        
813        // Use inverse power iteration to find smallest eigenvalues
814        // First, shift M to make smallest eigenvalues largest: M' = max_eigenvalue * I - M
815        let max_eig = 2.0f32;  // Upper bound estimate
816        for i in 0..n_samples {
817            m[i * n_samples + i] = max_eig - m[i * n_samples + i];
818            for j in 0..n_samples {
819                if i != j {
820                    m[i * n_samples + j] = -m[i * n_samples + j];
821                }
822            }
823        }
824        
825        // Skip first eigenvector (constant), find next n_components
826        for comp in 0..(self.n_components + 1) {
827            let mut v: Vec<f32> = (0..n_samples).map(|_| rng.gen::<f32>()).collect();
828            let norm: f32 = v.iter().map(|&x| x * x).sum::<f32>().sqrt();
829            for vi in &mut v {
830                *vi /= norm;
831            }
832            
833            for _ in 0..100 {
834                let mut w = vec![0.0f32; n_samples];
835                for i in 0..n_samples {
836                    for j in 0..n_samples {
837                        w[i] += m[i * n_samples + j] * v[j];
838                    }
839                }
840                
841                let norm: f32 = w.iter().map(|&x| x * x).sum::<f32>().sqrt();
842                if norm < 1e-10 {
843                    break;
844                }
845                for wi in &mut w {
846                    *wi /= norm;
847                }
848                
849                let diff: f32 = v.iter().zip(w.iter()).map(|(&vi, &wi)| (vi - wi).abs()).sum();
850                v = w;
851                
852                if diff < 1e-6 {
853                    break;
854                }
855            }
856            
857            // Store component (skip first)
858            if comp > 0 {
859                for i in 0..n_samples {
860                    y[i * self.n_components + (comp - 1)] = v[i];
861                }
862            }
863            
864            // Deflate
865            let mut eigenvalue = 0.0f32;
866            for i in 0..n_samples {
867                let mut mv = 0.0f32;
868                for j in 0..n_samples {
869                    mv += m[i * n_samples + j] * v[j];
870                }
871                eigenvalue += v[i] * mv;
872            }
873            
874            for i in 0..n_samples {
875                for j in 0..n_samples {
876                    m[i * n_samples + j] -= eigenvalue * v[i] * v[j];
877                }
878            }
879        }
880        
881        self.embedding_ = Some(y.clone());
882        
883        Tensor::from_slice(&y, &[n_samples, self.n_components]).unwrap()
884    }
885}
886
887#[cfg(test)]
888mod tests {
889    use super::*;
890
891    #[test]
892    fn test_tsne() {
893        let x = Tensor::from_slice(&[0.0f32, 0.0,
894            0.1, 0.1,
895            5.0, 5.0,
896            5.1, 5.1,
897        ], &[4, 2]).unwrap();
898
899        let mut tsne = TSNE::new(2).perplexity(2.0).n_iter(100);
900        let embedding = tsne.fit_transform(&x);
901        
902        assert_eq!(embedding.dims(), &[4, 2]);
903    }
904
905    #[test]
906    fn test_mds() {
907        let x = Tensor::from_slice(&[0.0f32, 0.0,
908            1.0, 0.0,
909            0.0, 1.0,
910            1.0, 1.0,
911        ], &[4, 2]).unwrap();
912
913        let mut mds = MDS::new(2);
914        let embedding = mds.fit_transform(&x);
915        
916        assert_eq!(embedding.dims(), &[4, 2]);
917    }
918
919    #[test]
920    fn test_isomap() {
921        let x = Tensor::from_slice(&[0.0f32, 0.0,
922            1.0, 0.0,
923            2.0, 0.0,
924            0.0, 1.0,
925            1.0, 1.0,
926        ], &[5, 2]).unwrap();
927
928        let mut isomap = Isomap::new(2).n_neighbors(3);
929        let embedding = isomap.fit_transform(&x);
930        
931        assert_eq!(embedding.dims(), &[5, 2]);
932    }
933}
934
935