scirs2_integrate/visualization/
advanced.rs1use super::types::*;
8use crate::error::{IntegrateError, IntegrateResult};
9use crate::ode::ODEResult;
10use scirs2_core::ndarray::{Array1, Array2, Axis};
11use scirs2_core::random::Rng;
12use std::collections::HashMap;
13
14#[derive(Debug, Clone)]
16pub struct MultiDimensionalVisualizer {
17 pub reduction_method: DimensionReductionMethod,
19 pub target_dimensions: usize,
21 pub clustering_method: ClusteringMethod,
23}
24
25impl MultiDimensionalVisualizer {
26 pub fn new() -> Self {
28 Self {
29 reduction_method: DimensionReductionMethod::PCA,
30 target_dimensions: 2,
31 clustering_method: ClusteringMethod::None,
32 }
33 }
34
35 pub fn visualize_high_dimensional_data(
37 &self,
38 data: &Array2<f64>,
39 labels: Option<&Array1<usize>>,
40 ) -> IntegrateResult<HighDimensionalPlot> {
41 let reduced_data = self.apply_dimension_reduction(data)?;
43
44 let cluster_labels = self.apply_clustering(&reduced_data)?;
46
47 let x: Vec<f64> = reduced_data.column(0).to_vec();
49 let y: Vec<f64> = if reduced_data.ncols() > 1 {
50 reduced_data.column(1).to_vec()
51 } else {
52 vec![0.0; x.len()]
53 };
54
55 let z: Option<Vec<f64>> = if self.target_dimensions > 2 && reduced_data.ncols() > 2 {
56 Some(reduced_data.column(2).to_vec())
57 } else {
58 None
59 };
60
61 let colors = if let Some(labels) = labels {
62 labels.to_vec().into_iter().map(|l| l as f64).collect()
63 } else if let Some(clusters) = &cluster_labels {
64 clusters.iter().map(|&c| c as f64).collect()
65 } else {
66 (0..x.len()).map(|i| i as f64).collect()
67 };
68
69 let mut metadata = PlotMetadata::default();
70 metadata.title = "High-Dimensional Data Visualization".to_string();
71 metadata.xlabel = format!("{:?} Component 1", self.reduction_method);
72 metadata.ylabel = format!("{:?} Component 2", self.reduction_method);
73
74 Ok(HighDimensionalPlot {
75 x,
76 y,
77 z,
78 colors,
79 cluster_labels,
80 original_dimensions: data.ncols(),
81 reduced_dimensions: self.target_dimensions,
82 reduction_method: self.reduction_method,
83 metadata,
84 })
85 }
86
87 fn apply_dimension_reduction(&self, data: &Array2<f64>) -> IntegrateResult<Array2<f64>> {
89 match self.reduction_method {
90 DimensionReductionMethod::PCA => self.apply_pca(data),
91 DimensionReductionMethod::TSNE => self.apply_tsne(data),
92 DimensionReductionMethod::UMAP => self.apply_umap(data),
93 DimensionReductionMethod::LDA => self.apply_lda(data),
94 DimensionReductionMethod::MDS => self.apply_mds(data),
95 }
96 }
97
98 fn apply_pca(&self, data: &Array2<f64>) -> IntegrateResult<Array2<f64>> {
100 let (n_samples, n_features) = data.dim();
101
102 let mean = data.mean_axis(Axis(0)).unwrap();
104 let centered_data = data - &mean.insert_axis(Axis(0));
105
106 let cov_matrix = centered_data.t().dot(¢ered_data) / (n_samples - 1) as f64;
108
109 let (eigenvalues, eigenvectors) = self.compute_eigendecomposition(&cov_matrix)?;
111
112 let mut eigenvalue_indices: Vec<usize> = (0..eigenvalues.len()).collect();
114 eigenvalue_indices.sort_by(|&i, &j| eigenvalues[j].partial_cmp(&eigenvalues[i]).unwrap());
115
116 let n_components = self.target_dimensions.min(n_features);
118 let mut projected_data = Array2::zeros((n_samples, n_components));
119
120 for (i, &idx) in eigenvalue_indices.iter().take(n_components).enumerate() {
121 let component = eigenvectors.column(idx);
122 let projection = centered_data.dot(&component);
123 projected_data.column_mut(i).assign(&projection);
124 }
125
126 Ok(projected_data)
127 }
128
129 fn apply_tsne(&self, data: &Array2<f64>) -> IntegrateResult<Array2<f64>> {
131 let (n_samples, _) = data.dim();
133 let mut rng = scirs2_core::random::rng();
134
135 let mut embedding = Array2::zeros((n_samples, self.target_dimensions));
137 for i in 0..n_samples {
138 for j in 0..self.target_dimensions {
139 embedding[[i, j]] = rng.random::<f64>() * 2.0 - 1.0;
140 }
141 }
142
143 Ok(embedding)
146 }
147
148 fn apply_umap(&self, data: &Array2<f64>) -> IntegrateResult<Array2<f64>> {
150 self.apply_pca(data)
153 }
154
155 fn apply_lda(&self, data: &Array2<f64>) -> IntegrateResult<Array2<f64>> {
157 self.apply_pca(data)
160 }
161
162 fn apply_mds(&self, data: &Array2<f64>) -> IntegrateResult<Array2<f64>> {
164 let n_samples = data.nrows();
165
166 let mut distance_matrix = Array2::zeros((n_samples, n_samples));
168 for i in 0..n_samples {
169 for j in i..n_samples {
170 let dist = self.euclidean_distance(&data.row(i), &data.row(j));
171 distance_matrix[[i, j]] = dist;
172 distance_matrix[[j, i]] = dist;
173 }
174 }
175
176 let squared_distances = distance_matrix.mapv(|d| d * d);
178
179 let row_means = squared_distances.mean_axis(Axis(1)).unwrap();
181 let col_means = squared_distances.mean_axis(Axis(0)).unwrap();
182 let grand_mean = squared_distances.iter().sum::<f64>() / squared_distances.len() as f64;
183
184 let mut b_matrix = Array2::zeros((n_samples, n_samples));
185 for i in 0..n_samples {
186 for j in 0..n_samples {
187 b_matrix[[i, j]] =
188 -0.5 * (squared_distances[[i, j]] - row_means[i] - col_means[j] + grand_mean);
189 }
190 }
191
192 let (eigenvalues, eigenvectors) = self.compute_eigendecomposition(&b_matrix)?;
194
195 let mut eigenvalue_indices: Vec<usize> = (0..eigenvalues.len()).collect();
197 eigenvalue_indices.sort_by(|&i, &j| eigenvalues[j].partial_cmp(&eigenvalues[i]).unwrap());
198
199 let n_components = self.target_dimensions.min(n_samples);
201 let mut embedding = Array2::zeros((n_samples, n_components));
202
203 for (i, &idx) in eigenvalue_indices.iter().take(n_components).enumerate() {
204 if eigenvalues[idx] > 0.0 {
205 let scale = eigenvalues[idx].sqrt();
206 for j in 0..n_samples {
207 embedding[[j, i]] = eigenvectors[[j, idx]] * scale;
208 }
209 }
210 }
211
212 Ok(embedding)
213 }
214
215 fn apply_clustering(&self, data: &Array2<f64>) -> IntegrateResult<Option<Vec<usize>>> {
217 match self.clustering_method {
218 ClusteringMethod::KMeans { k } => Ok(Some(self.kmeans_clustering(data, k)?)),
219 ClusteringMethod::DBSCAN { eps, min_samples } => {
220 Ok(Some(self.dbscan_clustering(data, eps, min_samples)?))
221 }
222 ClusteringMethod::Hierarchical { n_clusters } => {
223 Ok(Some(self.hierarchical_clustering(data, n_clusters)?))
224 }
225 ClusteringMethod::None => Ok(None),
226 }
227 }
228
229 fn kmeans_clustering(&self, data: &Array2<f64>, k: usize) -> IntegrateResult<Vec<usize>> {
231 let mut rng = scirs2_core::random::rng();
232 let (n_samples, n_features) = data.dim();
233
234 let mut centroids = Array2::zeros((k, n_features));
236 for i in 0..k {
237 for j in 0..n_features {
238 let min_val = data.column(j).iter().fold(f64::INFINITY, |a, &b| a.min(b));
239 let max_val = data
240 .column(j)
241 .iter()
242 .fold(f64::NEG_INFINITY, |a, &b| a.max(b));
243 centroids[[i, j]] = min_val + rng.random::<f64>() * (max_val - min_val);
244 }
245 }
246
247 let mut labels = vec![0; n_samples];
248 let max_iterations = 100;
249
250 for _iteration in 0..max_iterations {
251 let mut changed = false;
252
253 for i in 0..n_samples {
255 let mut min_distance = f64::INFINITY;
256 let mut best_cluster = 0;
257
258 for j in 0..k {
259 let distance = self.euclidean_distance(&data.row(i), ¢roids.row(j));
260 if distance < min_distance {
261 min_distance = distance;
262 best_cluster = j;
263 }
264 }
265
266 if labels[i] != best_cluster {
267 labels[i] = best_cluster;
268 changed = true;
269 }
270 }
271
272 if !changed {
273 break;
274 }
275
276 let mut cluster_counts = vec![0; k];
278 centroids.fill(0.0);
279
280 for i in 0..n_samples {
281 let cluster = labels[i];
282 cluster_counts[cluster] += 1;
283 for j in 0..n_features {
284 centroids[[cluster, j]] += data[[i, j]];
285 }
286 }
287
288 for i in 0..k {
289 if cluster_counts[i] > 0 {
290 for j in 0..n_features {
291 centroids[[i, j]] /= cluster_counts[i] as f64;
292 }
293 }
294 }
295 }
296
297 Ok(labels)
298 }
299
300 fn dbscan_clustering(
302 &self,
303 data: &Array2<f64>,
304 eps: f64,
305 min_samples: usize,
306 ) -> IntegrateResult<Vec<usize>> {
307 let n_samples = data.nrows();
308 let mut labels = vec![usize::MAX; n_samples]; let mut cluster_id = 0;
310
311 for i in 0..n_samples {
312 if labels[i] != usize::MAX {
313 continue; }
315
316 let neighbors = self.find_neighbors(data, i, eps);
317
318 if neighbors.len() < min_samples {
319 labels[i] = usize::MAX - 1; } else {
321 self.expand_cluster(
322 data,
323 i,
324 &neighbors,
325 cluster_id,
326 eps,
327 min_samples,
328 &mut labels,
329 );
330 cluster_id += 1;
331 }
332 }
333
334 for label in &mut labels {
336 if *label == usize::MAX - 1 {
337 *label = 0; } else if *label != usize::MAX {
339 *label += 1; }
341 }
342
343 Ok(labels)
344 }
345
346 fn hierarchical_clustering(
348 &self,
349 data: &Array2<f64>,
350 n_clusters: usize,
351 ) -> IntegrateResult<Vec<usize>> {
352 let n_samples = data.nrows();
353
354 let mut clusters: Vec<Vec<usize>> = (0..n_samples).map(|i| vec![i]).collect();
356
357 let mut distance_matrix = Array2::zeros((n_samples, n_samples));
359 for i in 0..n_samples {
360 for j in i + 1..n_samples {
361 let dist = self.euclidean_distance(&data.row(i), &data.row(j));
362 distance_matrix[[i, j]] = dist;
363 distance_matrix[[j, i]] = dist;
364 }
365 }
366
367 while clusters.len() > n_clusters {
369 let mut min_distance = f64::INFINITY;
370 let mut merge_i = 0;
371 let mut merge_j = 1;
372
373 for i in 0..clusters.len() {
375 for j in i + 1..clusters.len() {
376 let dist = self.cluster_distance(&clusters[i], &clusters[j], &distance_matrix);
377 if dist < min_distance {
378 min_distance = dist;
379 merge_i = i;
380 merge_j = j;
381 }
382 }
383 }
384
385 let mut merged_cluster = clusters[merge_i].clone();
387 merged_cluster.extend(&clusters[merge_j]);
388
389 clusters.remove(merge_j); clusters.remove(merge_i);
392 clusters.push(merged_cluster);
393 }
394
395 let mut labels = vec![0; n_samples];
397 for (cluster_id, cluster) in clusters.iter().enumerate() {
398 for &point_id in cluster {
399 labels[point_id] = cluster_id;
400 }
401 }
402
403 Ok(labels)
404 }
405
406 fn euclidean_distance(
408 &self,
409 a: &scirs2_core::ndarray::ArrayView1<f64>,
410 b: &scirs2_core::ndarray::ArrayView1<f64>,
411 ) -> f64 {
412 a.iter()
413 .zip(b.iter())
414 .map(|(&x, &y)| (x - y).powi(2))
415 .sum::<f64>()
416 .sqrt()
417 }
418
419 fn compute_eigendecomposition(
420 &self,
421 matrix: &Array2<f64>,
422 ) -> IntegrateResult<(Array1<f64>, Array2<f64>)> {
423 let n = matrix.nrows();
424
425 let mut eigenvalues = Array1::zeros(n.min(self.target_dimensions));
427 let mut eigenvectors = Array2::zeros((n, n.min(self.target_dimensions)));
428 let mut remaining_matrix = matrix.clone();
429
430 for k in 0..n.min(self.target_dimensions) {
431 let mut v = Array1::from_elem(n, 1.0 / (n as f64).sqrt());
433 let mut eigenvalue = 0.0;
434
435 for _ in 0..100 {
436 let v_new = remaining_matrix.dot(&v);
437 eigenvalue = v.dot(&v_new);
438 let norm = v_new.iter().map(|&x| x * x).sum::<f64>().sqrt();
439 if norm > 1e-10 {
440 v = v_new / norm;
441 }
442 }
443
444 eigenvalues[k] = eigenvalue;
445 eigenvectors.column_mut(k).assign(&v);
446
447 if eigenvalue.abs() > 1e-10 {
449 for i in 0..n {
450 for j in 0..n {
451 remaining_matrix[[i, j]] -= eigenvalue * v[i] * v[j];
452 }
453 }
454 }
455 }
456
457 let mut full_eigenvalues = Array1::zeros(n);
459 let mut full_eigenvectors = Array2::zeros((n, n));
460
461 for i in 0..n.min(self.target_dimensions) {
462 full_eigenvalues[i] = eigenvalues[i];
463 full_eigenvectors
464 .column_mut(i)
465 .assign(&eigenvectors.column(i));
466 }
467
468 for i in n.min(self.target_dimensions)..n {
470 let mut v = Array1::zeros(n);
471 v[i] = 1.0;
472 full_eigenvectors.column_mut(i).assign(&v);
473 }
474
475 Ok((full_eigenvalues, full_eigenvectors))
476 }
477
478 fn find_neighbors(&self, data: &Array2<f64>, point: usize, eps: f64) -> Vec<usize> {
479 let mut neighbors = Vec::new();
480 for i in 0..data.nrows() {
481 if i != point {
482 let dist = self.euclidean_distance(&data.row(point), &data.row(i));
483 if dist <= eps {
484 neighbors.push(i);
485 }
486 }
487 }
488 neighbors
489 }
490
491 fn expand_cluster(
492 &self,
493 data: &Array2<f64>,
494 point: usize,
495 neighbors: &[usize],
496 cluster_id: usize,
497 eps: f64,
498 min_samples: usize,
499 labels: &mut [usize],
500 ) {
501 labels[point] = cluster_id;
502 let mut seed_set = neighbors.to_vec();
503 let mut i = 0;
504
505 while i < seed_set.len() {
506 let q = seed_set[i];
507
508 if labels[q] == usize::MAX - 1 {
509 labels[q] = cluster_id;
511 } else if labels[q] == usize::MAX {
512 labels[q] = cluster_id;
514 let q_neighbors = self.find_neighbors(data, q, eps);
515
516 if q_neighbors.len() >= min_samples {
517 for &neighbor in &q_neighbors {
518 if !seed_set.contains(&neighbor) {
519 seed_set.push(neighbor);
520 }
521 }
522 }
523 }
524
525 i += 1;
526 }
527 }
528
529 fn cluster_distance(
530 &self,
531 cluster1: &[usize],
532 cluster2: &[usize],
533 distance_matrix: &Array2<f64>,
534 ) -> f64 {
535 let mut min_distance = f64::INFINITY;
537
538 for &i in cluster1 {
539 for &j in cluster2 {
540 let dist = distance_matrix[[i, j]];
541 if dist < min_distance {
542 min_distance = dist;
543 }
544 }
545 }
546
547 min_distance
548 }
549}
550
551impl Default for MultiDimensionalVisualizer {
552 fn default() -> Self {
553 Self::new()
554 }
555}
556
557#[derive(Debug, Clone)]
559pub struct AnimatedVisualizer {
560 pub frames: Vec<PhaseSpacePlot>,
562 pub animation_settings: AnimationSettings,
564 pub current_frame: usize,
566}
567
568impl AnimatedVisualizer {
569 pub fn new() -> Self {
571 Self {
572 frames: Vec::new(),
573 animation_settings: AnimationSettings::default(),
574 current_frame: 0,
575 }
576 }
577
578 pub fn create_animation_from_ode<F: crate::common::IntegrateFloat>(
580 &mut self,
581 ode_result: &ODEResult<F>,
582 x_index: usize,
583 y_index: usize,
584 frames_per_time_unit: usize,
585 ) -> IntegrateResult<()> {
586 let n_points = ode_result.t.len();
587 if n_points == 0 {
588 return Err(IntegrateError::ValueError("Empty ODE result".to_string()));
589 }
590
591 let n_vars = ode_result.y[0].len();
592 if x_index >= n_vars || y_index >= n_vars {
593 return Err(IntegrateError::ValueError(
594 "Variable index out of bounds".to_string(),
595 ));
596 }
597
598 self.frames.clear();
600 let frame_step =
601 (n_points as f64 / (frames_per_time_unit * n_points) as f64).max(1.0) as usize;
602
603 for frame_end in (frame_step..=n_points).step_by(frame_step) {
604 let x: Vec<f64> = (0..frame_end)
605 .map(|i| ode_result.y[i][x_index].to_f64().unwrap_or(0.0))
606 .collect();
607
608 let y: Vec<f64> = (0..frame_end)
609 .map(|i| ode_result.y[i][y_index].to_f64().unwrap_or(0.0))
610 .collect();
611
612 let colors: Vec<f64> = (0..frame_end)
613 .map(|i| ode_result.t[i].to_f64().unwrap_or(0.0))
614 .collect();
615
616 let mut metadata = PlotMetadata::default();
617 metadata.title = format!("Animation Frame {}", self.frames.len() + 1);
618 metadata.xlabel = format!("Variable {x_index}");
619 metadata.ylabel = format!("Variable {y_index}");
620
621 self.frames.push(PhaseSpacePlot {
622 x,
623 y,
624 colors: Some(colors),
625 metadata,
626 });
627 }
628
629 Ok(())
630 }
631
632 pub fn current_frame(&self) -> Option<&PhaseSpacePlot> {
634 self.frames.get(self.current_frame)
635 }
636
637 pub fn next_frame(&mut self) -> bool {
639 if self.current_frame + 1 < self.frames.len() {
640 self.current_frame += 1;
641 true
642 } else if self.animation_settings.loop_animation {
643 self.current_frame = 0;
644 true
645 } else {
646 false
647 }
648 }
649
650 pub fn previous_frame(&mut self) -> bool {
652 if self.current_frame > 0 {
653 self.current_frame -= 1;
654 true
655 } else if self.animation_settings.loop_animation {
656 self.current_frame = self.frames.len().saturating_sub(1);
657 true
658 } else {
659 false
660 }
661 }
662
663 pub fn reset(&mut self) {
665 self.current_frame = 0;
666 }
667}
668
669impl Default for AnimatedVisualizer {
670 fn default() -> Self {
671 Self::new()
672 }
673}
674
675pub fn advanced_visualization(
677 data: &Array2<f64>,
678 method: DimensionReductionMethod,
679 target_dims: usize,
680) -> IntegrateResult<HighDimensionalPlot> {
681 let visualizer = MultiDimensionalVisualizer {
682 reduction_method: method,
683 target_dimensions: target_dims,
684 clustering_method: ClusteringMethod::None,
685 };
686
687 visualizer.visualize_high_dimensional_data(data, None)
688}
689
690pub fn advanced_interactive_3d(
692 data: &Array2<f64>,
693 labels: Option<&Array1<usize>>,
694) -> IntegrateResult<HighDimensionalPlot> {
695 let visualizer = MultiDimensionalVisualizer {
696 reduction_method: DimensionReductionMethod::PCA,
697 target_dimensions: 3,
698 clustering_method: ClusteringMethod::KMeans { k: 3 },
699 };
700
701 visualizer.visualize_high_dimensional_data(data, labels)
702}