1use crate::error::{StatsError, StatsResult};
6use scirs2_core::ndarray::{Array1, Array2, Array3, ArrayView1, ArrayView2};
7use scirs2_core::numeric::{Float, NumCast, One, Zero};
8use scirs2_core::{simd_ops::SimdUnifiedOps, validation::*};
9use scirs2_linalg::parallel_dispatch::ParallelConfig;
10use std::collections::HashMap;
11use std::marker::PhantomData;
12
13use super::functions::const_f64;
14
15#[derive(Debug, Clone)]
17pub struct MapperNode<F> {
18 pub point_indices: Vec<usize>,
20 pub size: usize,
22 pub centroid: Array1<F>,
24 pub average_filter_value: F,
26 pub diameter: F,
28}
29#[derive(Debug, Clone, Copy)]
31pub enum DistanceMetric {
32 Euclidean,
33 Manhattan,
34 Chebyshev,
35 Minkowski(f64),
36 Cosine,
37 Correlation,
38 Hamming,
39 Jaccard,
40 Mahalanobis,
41 Custom,
42}
43#[derive(Debug, Clone)]
45pub struct FiltrationConfig<F> {
46 pub filtration_type: FiltrationType,
48 pub distance_metric: DistanceMetric,
50 pub max_epsilon: F,
52 pub num_steps: usize,
54 pub adaptive_steps: bool,
56}
57struct TopologicalCache<F> {
59 distance_matrices: HashMap<String, Array2<F>>,
61 simplicial_complexes: HashMap<String, SimplicialComplex>,
63 filtrations: HashMap<String, Filtration<F>>,
65}
66#[derive(Debug, Clone, Copy)]
68pub enum ClusteringMethod {
69 SingleLinkage,
70 CompleteLinkage,
71 AverageLinkage,
72 KMeans,
73 DBSCAN,
74 SpectralClustering,
75}
76#[derive(Debug, Clone, Copy)]
78pub enum CentralityMethod {
79 Degree,
80 Betweenness,
81 Closeness,
82 Eigenvector,
83 PageRank,
84 Katz,
85}
86#[derive(Debug, Clone)]
88pub struct TopologicalSignatures<F> {
89 pub image_signature: Vec<F>,
90 pub landscape_signature: Vec<F>,
91 pub betti_statistics: Vec<F>,
92 pub euler_statistics: Vec<F>,
93 pub entropy_vector: Vec<F>,
94}
95#[derive(Debug, Clone, Copy)]
97pub enum MultipleComparisonsCorrection {
98 None,
99 Bonferroni,
100 BenjaminiHochberg,
101 BenjaminiYekutieli,
102 Holm,
103 Hochberg,
104}
105#[derive(Debug, Clone)]
107pub struct PersistenceFeature<F> {
108 pub birth: F,
109 pub death: F,
110 pub persistence: F,
111 pub dimension: usize,
112 pub scale: F,
113 pub midlife: F,
114}
115#[derive(Debug, Clone)]
117pub struct TopologicalEntropyFeatures<F> {
118 pub persistent_entropy: F,
119 pub weighted_entropy: F,
120 pub multiscale_entropy: Array1<F>,
121 pub complexity: F,
122}
123#[derive(Debug, Clone)]
125pub struct PersistenceConfig<F> {
126 pub algorithm: PersistenceAlgorithm,
128 pub coefficient_field: CoeffientField,
130 pub persistence_threshold: F,
132 pub compute_entropy: bool,
134 pub stability_analysis: bool,
136}
137pub struct TopologicalConfig<F> {
139 pub max_dimension: usize,
141 pub filtration_config: FiltrationConfig<F>,
143 pub persistence_config: PersistenceConfig<F>,
145 pub mapper_config: MapperConfig<F>,
147 pub multiscale_config: MultiscaleConfig<F>,
149 pub inference_config: TopologicalInferenceConfig<F>,
151 pub parallel_config: ParallelConfig,
153}
154impl<F> TopologicalConfig<F>
155where
156 F: Float + NumCast + Copy + std::fmt::Display + SimdUnifiedOps + Send + Sync,
157{
158 pub fn topological_machine_learning(
160 &mut self,
161 data: &ArrayView2<F>,
162 _labels: Option<&ArrayView1<F>>,
163 ) -> StatsResult<TopologicalMLResult<F>> {
164 let topological_features = (*self).extract_topological_features(data)?;
165 let feature_matrix = Array2::zeros((data.nrows(), 10));
166 let analyzer = AdvancedTopologicalAnalyzer::new(self.clone());
167 let kernel_matrix = analyzer.compute_distance_matrix(&feature_matrix.view())?;
168 let prediction_result = None;
169 let clustering_result = TopologicalClusteringResult {
170 cluster_labels: Array1::zeros(data.nrows()),
171 cluster_centers: Array2::zeros((3, data.ncols())),
172 silhouette_score: const_f64::<F>(0.5),
173 inertia: const_f64::<F>(1.0),
174 };
175 let feature_importance = Array1::ones(feature_matrix.ncols());
176 let signatures = TopologicalSignatures {
177 image_signature: topological_features
178 .persistence_images
179 .iter()
180 .cloned()
181 .collect(),
182 landscape_signature: topological_features
183 .persistence_landscapes
184 .iter()
185 .cloned()
186 .collect(),
187 betti_statistics: topological_features.betti_curves.iter().cloned().collect(),
188 euler_statistics: topological_features.euler_curves.iter().cloned().collect(),
189 entropy_vector: vec![topological_features.entropy_features.persistent_entropy],
190 };
191 Ok(TopologicalMLResult {
192 topological_features: feature_matrix,
193 kernel_matrix,
194 signatures,
195 prediction_result,
196 clustering_result,
197 feature_importance,
198 stability_score: const_f64::<F>(0.95),
199 })
200 }
201 fn extract_topological_features(
203 &self,
204 data: &ArrayView2<F>,
205 ) -> StatsResult<TopologicalFeatures<F>> {
206 let (_n_samples_, n_features) = data.dim();
207 let persistence_features = self.extract_persistence_features(data)?;
208 let persistence_images = Array2::zeros((10, 10));
209 let persistence_landscapes = Array2::zeros((5, 20));
210 let betti_curves = Array2::zeros((3, 10));
211 let euler_curves = Array1::zeros(10);
212 let entropy_features = TopologicalEntropyFeatures {
213 persistent_entropy: const_f64::<F>(1.0),
214 weighted_entropy: const_f64::<F>(0.8),
215 multiscale_entropy: Array1::ones(5),
216 complexity: const_f64::<F>(0.6),
217 };
218 Ok(TopologicalFeatures {
219 persistence_features,
220 persistence_images,
221 persistence_landscapes,
222 betti_curves,
223 euler_curves,
224 entropy_features,
225 dimensionality: n_features,
226 })
227 }
228 fn extract_persistence_features(
230 &self,
231 data: &ArrayView2<F>,
232 ) -> StatsResult<Vec<PersistenceFeature<F>>> {
233 let mut features = Vec::new();
234 let num_scales = 10;
235 for scale_idx in 0..num_scales {
236 let scale =
237 F::from(scale_idx as f64 / num_scales as f64).expect("Failed to convert to float");
238 let epsilon = self.filtration_config.max_epsilon * scale;
239 let analyzer = AdvancedTopologicalAnalyzer::new(self.clone());
240 let distance_matrix = analyzer.compute_distance_matrix(data)?;
241 let complex =
242 self.build_vietoris_rips_complex_with_epsilon(&distance_matrix, epsilon)?;
243 let diagrams = analyzer.compute_persistent_homology(&complex)?;
244 for (dim, diagram) in diagrams {
245 for i in 0..diagram.points.nrows() {
246 let birth = diagram.points[[i, 0]];
247 let death = diagram.points[[i, 1]];
248 features.push(PersistenceFeature {
249 birth,
250 death,
251 persistence: death - birth,
252 dimension: dim,
253 scale: epsilon,
254 midlife: (birth + death) / const_f64::<F>(2.0),
255 });
256 }
257 }
258 }
259 Ok(features)
260 }
261 fn build_vietoris_rips_complex_with_epsilon(
263 &self,
264 distance_matrix: &Array2<F>,
265 epsilon: F,
266 ) -> StatsResult<SimplicialComplex> {
267 let n_points = distance_matrix.nrows();
268 let mut simplices_by_dim = vec![Vec::new(); self.max_dimension + 1];
269 for i in 0..n_points {
270 simplices_by_dim[0].push(Simplex {
271 vertices: vec![i],
272 dimension: 0,
273 });
274 }
275 for i in 0..n_points {
276 for j in (i + 1)..n_points {
277 if distance_matrix[[i, j]] <= epsilon {
278 simplices_by_dim[1].push(Simplex {
279 vertices: vec![i, j],
280 dimension: 1,
281 });
282 }
283 }
284 }
285 for dim in 2..=self.max_dimension {
286 if dim - 1 < simplices_by_dim.len() && !simplices_by_dim[dim - 1].is_empty() {
287 let analyzer = AdvancedTopologicalAnalyzer::new(self.clone());
288 simplices_by_dim[dim] = analyzer.generate_higher_simplices(
289 &simplices_by_dim[dim - 1],
290 distance_matrix,
291 epsilon,
292 dim,
293 )?;
294 }
295 }
296 Ok(SimplicialComplex {
297 simplices_by_dim,
298 max_dimension: self.max_dimension,
299 })
300 }
301 fn compute_persistence_images(
303 &self,
304 features: &[PersistenceFeature<F>],
305 ) -> StatsResult<Array2<F>> {
306 let resolution = 20;
307 let mut image = Array2::zeros((resolution, resolution));
308 let max_birth = features
309 .iter()
310 .map(|f| f.birth)
311 .fold(F::zero(), |a, b| if a > b { a } else { b });
312 let max_death = features
313 .iter()
314 .map(|f| f.death)
315 .fold(F::zero(), |a, b| if a > b { a } else { b });
316 let max_val = if max_death > max_birth {
317 max_death
318 } else {
319 max_birth
320 };
321 if max_val > F::zero() {
322 let sigma = const_f64::<F>(0.1) * max_val;
323 let sigma_sq = sigma * sigma;
324 for feature in features {
325 let _birth_coord = (feature.birth / max_val
326 * F::from(resolution as f64).expect("Failed to convert to float"))
327 .to_usize()
328 .unwrap_or(0)
329 .min(resolution - 1);
330 let _death_coord = (feature.death / max_val
331 * F::from(resolution as f64).expect("Failed to convert to float"))
332 .to_usize()
333 .unwrap_or(0)
334 .min(resolution - 1);
335 for i in 0..resolution {
336 for j in 0..resolution {
337 let x = F::from(i as f64).expect("Failed to convert to float")
338 / F::from(resolution as f64).expect("Failed to convert to float")
339 * max_val;
340 let y = F::from(j as f64).expect("Failed to convert to float")
341 / F::from(resolution as f64).expect("Failed to convert to float")
342 * max_val;
343 let dist_sq = (x - feature.birth) * (x - feature.birth)
344 + (y - feature.death) * (y - feature.death);
345 let weight = (-dist_sq / sigma_sq).exp() * feature.persistence;
346 image[[i, j]] = image[[i, j]] + weight;
347 }
348 }
349 }
350 }
351 Ok(image)
352 }
353 fn compute_persistence_landscapes(
355 &self,
356 features: &[PersistenceFeature<F>],
357 ) -> StatsResult<Array2<F>> {
358 let num_points = 100;
359 let num_landscapes = 5;
360 let mut landscapes = Array2::zeros((num_landscapes, num_points));
361 if features.is_empty() {
362 return Ok(landscapes);
363 }
364 let min_birth = features
365 .iter()
366 .map(|f| f.birth)
367 .fold(F::infinity(), |a, b| if a < b { a } else { b });
368 let max_death = features
369 .iter()
370 .map(|f| f.death)
371 .fold(F::neg_infinity(), |a, b| if a > b { a } else { b });
372 let range = max_death - min_birth;
373 if range <= F::zero() {
374 return Ok(landscapes);
375 }
376 for point_idx in 0..num_points {
377 let t = min_birth
378 + F::from(point_idx as f64).expect("Failed to convert to float")
379 / F::from(num_points as f64).expect("Failed to convert to float")
380 * range;
381 let mut values = Vec::new();
382 for feature in features {
383 if t >= feature.birth && t <= feature.death {
384 let value = if t <= (feature.birth + feature.death) / const_f64::<F>(2.0) {
385 t - feature.birth
386 } else {
387 feature.death - t
388 };
389 values.push(value);
390 }
391 }
392 values.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
393 for landscape_idx in 0..num_landscapes {
394 if landscape_idx < values.len() {
395 landscapes[[landscape_idx, point_idx]] = values[landscape_idx];
396 }
397 }
398 }
399 Ok(landscapes)
400 }
401 fn compute_betti_curves(&self, features: &[PersistenceFeature<F>]) -> StatsResult<Array2<F>> {
403 let num_points = 100;
404 let max_dim = 3;
405 let mut betti_curves = Array2::zeros((max_dim, num_points));
406 if features.is_empty() {
407 return Ok(betti_curves);
408 }
409 let min_val = features
410 .iter()
411 .map(|f| f.birth)
412 .fold(F::infinity(), |a, b| if a < b { a } else { b });
413 let max_val = features
414 .iter()
415 .map(|f| f.death)
416 .fold(F::neg_infinity(), |a, b| if a > b { a } else { b });
417 let range = max_val - min_val;
418 if range <= F::zero() {
419 return Ok(betti_curves);
420 }
421 for point_idx in 0..num_points {
422 let t = min_val
423 + F::from(point_idx as f64).expect("Failed to convert to float")
424 / F::from(num_points as f64).expect("Failed to convert to float")
425 * range;
426 for dim in 0..max_dim {
427 let count = features
428 .iter()
429 .filter(|f| f.dimension == dim && f.birth <= t && t < f.death)
430 .count();
431 betti_curves[[dim, point_idx]] =
432 F::from(count).expect("Failed to convert to float");
433 }
434 }
435 Ok(betti_curves)
436 }
437 fn compute_euler_characteristic_curves(
439 &self,
440 features: &[PersistenceFeature<F>],
441 ) -> StatsResult<Array1<F>> {
442 let num_points = 100;
443 let mut euler_curve = Array1::zeros(num_points);
444 if features.is_empty() {
445 return Ok(euler_curve);
446 }
447 let min_val = features
448 .iter()
449 .map(|f| f.birth)
450 .fold(F::infinity(), |a, b| if a < b { a } else { b });
451 let max_val = features
452 .iter()
453 .map(|f| f.death)
454 .fold(F::neg_infinity(), |a, b| if a > b { a } else { b });
455 let range = max_val - min_val;
456 if range <= F::zero() {
457 return Ok(euler_curve);
458 }
459 for point_idx in 0..num_points {
460 let t = min_val
461 + F::from(point_idx as f64).expect("Failed to convert to float")
462 / F::from(num_points as f64).expect("Failed to convert to float")
463 * range;
464 let mut euler_char = F::zero();
465 for dim in 0..=3 {
466 let betti_number = features
467 .iter()
468 .filter(|f| f.dimension == dim && f.birth <= t && t < f.death)
469 .count();
470 let sign = if dim % 2 == 0 { F::one() } else { -F::one() };
471 euler_char =
472 euler_char + sign * F::from(betti_number).expect("Failed to convert to float");
473 }
474 euler_curve[point_idx] = euler_char;
475 }
476 Ok(euler_curve)
477 }
478 fn compute_topological_entropy_features(
480 &self,
481 features: &[PersistenceFeature<F>],
482 ) -> StatsResult<TopologicalEntropyFeatures<F>> {
483 let persistent_entropy = self.compute_persistent_entropy(features)?;
484 let weighted_entropy = self.compute_persistence_weighted_entropy(features)?;
485 let multiscale_entropy = self.compute_multiscale_topological_entropy(features)?;
486 let complexity = self.compute_topological_complexity(features)?;
487 Ok(TopologicalEntropyFeatures {
488 persistent_entropy,
489 weighted_entropy,
490 multiscale_entropy,
491 complexity,
492 })
493 }
494 fn compute_persistent_entropy(&self, features: &[PersistenceFeature<F>]) -> StatsResult<F> {
496 if features.is_empty() {
497 return Ok(F::zero());
498 }
499 let total_persistence = features
500 .iter()
501 .map(|f| f.persistence)
502 .fold(F::zero(), |acc, p| acc + p);
503 if total_persistence <= F::zero() {
504 return Ok(F::zero());
505 }
506 let mut entropy = F::zero();
507 for feature in features {
508 if feature.persistence > F::zero() {
509 let prob = feature.persistence / total_persistence;
510 entropy = entropy - prob * prob.ln();
511 }
512 }
513 Ok(entropy)
514 }
515 fn compute_persistence_weighted_entropy(
517 &self,
518 features: &[PersistenceFeature<F>],
519 ) -> StatsResult<F> {
520 if features.is_empty() {
521 return Ok(F::zero());
522 }
523 let mut weighted_entropy = F::zero();
524 let total_weight = features
525 .iter()
526 .map(|f| f.persistence * f.persistence)
527 .fold(F::zero(), |acc, w| acc + w);
528 if total_weight > F::zero() {
529 for feature in features {
530 let weight = (feature.persistence * feature.persistence) / total_weight;
531 if weight > F::zero() {
532 weighted_entropy = weighted_entropy - weight * weight.ln();
533 }
534 }
535 }
536 Ok(weighted_entropy)
537 }
538 fn compute_multiscale_topological_entropy(
540 &self,
541 features: &[PersistenceFeature<F>],
542 ) -> StatsResult<Array1<F>> {
543 let num_scales = 5;
544 let mut multiscale_entropy = Array1::zeros(num_scales);
545 for scale_idx in 0..num_scales {
546 let scale_threshold =
547 F::from((scale_idx + 1) as f64 / num_scales as f64).expect("Test/example failed");
548 let filtered_features: Vec<_> = features
549 .iter()
550 .filter(|f| f.persistence >= scale_threshold * f.death)
551 .cloned()
552 .collect();
553 multiscale_entropy[scale_idx] = self.compute_persistent_entropy(&filtered_features)?;
554 }
555 Ok(multiscale_entropy)
556 }
557 fn compute_topological_complexity(&self, features: &[PersistenceFeature<F>]) -> StatsResult<F> {
559 if features.is_empty() {
560 return Ok(F::zero());
561 }
562 let entropy = self.compute_persistent_entropy(features)?;
563 let num_features = F::from(features.len()).expect("Test/example failed");
564 let avg_persistence = features
565 .iter()
566 .map(|f| f.persistence)
567 .fold(F::zero(), |acc, p| acc + p)
568 / num_features;
569 let complexity = entropy * num_features.ln() * avg_persistence;
570 Ok(complexity)
571 }
572 fn compute_topological_signatures(
574 &self,
575 features: &TopologicalFeatures<F>,
576 ) -> StatsResult<TopologicalSignatures<F>> {
577 let image_signature = features
578 .persistence_images
579 .as_slice()
580 .expect("Operation failed")
581 .to_vec();
582 let landscape_signature = features
583 .persistence_landscapes
584 .as_slice()
585 .expect("Operation failed")
586 .to_vec();
587 let betti_statistics = self.compute_curve_statistics(&features.betti_curves)?;
588 let euler_statistics = self.compute_curve_statistics_1d(&features.euler_curves)?;
589 let entropy_vector = vec![
590 features.entropy_features.persistent_entropy,
591 features.entropy_features.weighted_entropy,
592 features.entropy_features.complexity,
593 ];
594 Ok(TopologicalSignatures {
595 image_signature,
596 landscape_signature,
597 betti_statistics,
598 euler_statistics,
599 entropy_vector,
600 })
601 }
602 fn compute_curve_statistics(&self, curves: &Array2<F>) -> StatsResult<Vec<F>> {
604 let mut statistics = Vec::new();
605 let (num_curves, num_points) = curves.dim();
606 for curve_idx in 0..num_curves {
607 let curve = curves.row(curve_idx);
608 let mean = curve.sum() / F::from(num_points).expect("Failed to convert to float");
609 let variance = curve
610 .iter()
611 .map(|&x| (x - mean) * (x - mean))
612 .fold(F::zero(), |acc, x| acc + x)
613 / F::from(num_points).expect("Failed to convert to float");
614 let std_dev = variance.sqrt();
615 let min_val = curve
616 .iter()
617 .fold(F::infinity(), |a, &b| if a < b { a } else { b });
618 let max_val = curve
619 .iter()
620 .fold(F::neg_infinity(), |a, &b| if a > b { a } else { b });
621 let integral = curve.sum() / F::from(num_points).expect("Failed to convert to float");
622 statistics.extend_from_slice(&[mean, std_dev, min_val, max_val, integral]);
623 }
624 Ok(statistics)
625 }
626 fn compute_curve_statistics_1d(&self, curve: &Array1<F>) -> StatsResult<Vec<F>> {
628 let num_points = curve.len();
629 if num_points == 0 {
630 return Ok(vec![F::zero(); 5]);
631 }
632 let mean = curve.sum() / F::from(num_points).expect("Failed to convert to float");
633 let variance = curve
634 .iter()
635 .map(|&x| (x - mean) * (x - mean))
636 .fold(F::zero(), |acc, x| acc + x)
637 / F::from(num_points).expect("Failed to convert to float");
638 let std_dev = variance.sqrt();
639 let min_val = curve
640 .iter()
641 .fold(F::infinity(), |a, &b| if a < b { a } else { b });
642 let max_val = curve
643 .iter()
644 .fold(F::neg_infinity(), |a, &b| if a > b { a } else { b });
645 Ok(vec![mean, std_dev, min_val, max_val, curve.sum()])
646 }
647 fn encode_persistent_features(
649 &self,
650 signatures: &TopologicalSignatures<F>,
651 ) -> StatsResult<Array2<F>> {
652 let mut all_features = Vec::new();
653 all_features.extend_from_slice(&signatures.image_signature);
654 all_features.extend_from_slice(&signatures.landscape_signature);
655 all_features.extend_from_slice(&signatures.betti_statistics);
656 all_features.extend_from_slice(&signatures.euler_statistics);
657 all_features.extend_from_slice(&signatures.entropy_vector);
658 let n_features = all_features.len();
659 let mut feature_matrix = Array2::zeros((1, n_features));
660 for (i, &feature) in all_features.iter().enumerate() {
661 feature_matrix[[0, i]] = feature;
662 }
663 Ok(feature_matrix)
664 }
665 fn compute_topological_kernel_matrix(&self, features: &Array2<F>) -> StatsResult<Array2<F>> {
667 let (n_samples_, n_features) = features.dim();
668 let mut kernel_matrix = Array2::zeros((n_samples_, n_samples_));
669 let sigma = const_f64::<F>(1.0);
670 let sigma_sq = sigma * sigma;
671 for i in 0..n_samples_ {
672 for j in 0..n_samples_ {
673 let mut dist_sq = F::zero();
674 for k in 0..n_features {
675 let diff = features[[i, k]] - features[[j, k]];
676 dist_sq = dist_sq + diff * diff;
677 }
678 kernel_matrix[[i, j]] = (-dist_sq / sigma_sq).exp();
679 }
680 }
681 Ok(kernel_matrix)
682 }
683 fn topological_classification(
685 &self,
686 features: &Array2<F>,
687 labels: &ArrayView1<F>,
688 kernel_matrix: &Array2<F>,
689 ) -> StatsResult<TopologicalPredictionResult<F>> {
690 let n_samples_ = features.nrows();
691 let mut predictions = Array1::zeros(n_samples_);
692 let mut confidence_scores = Array1::zeros(n_samples_);
693 for i in 0..n_samples_ {
694 let mut best_similarity = F::zero();
695 let mut predicted_label = labels[0];
696 for j in 0..n_samples_ {
697 if i != j && kernel_matrix[[i, j]] > best_similarity {
698 best_similarity = kernel_matrix[[i, j]];
699 predicted_label = labels[j];
700 }
701 }
702 predictions[i] = predicted_label;
703 confidence_scores[i] = best_similarity;
704 }
705 let correct_predictions: usize = predictions
706 .iter()
707 .zip(labels.iter())
708 .map(|(&pred, &true_label)| {
709 if (pred - true_label).abs() < const_f64::<F>(0.5) {
710 1
711 } else {
712 0
713 }
714 })
715 .sum();
716 let accuracy = F::from(correct_predictions as f64 / n_samples_ as f64)
717 .expect("Failed to convert to float");
718 Ok(TopologicalPredictionResult {
719 predictions,
720 confidence_scores,
721 accuracy,
722 feature_weights: Array1::ones(features.ncols()),
723 })
724 }
725 fn topological_clustering(
727 &self,
728 features: &Array2<F>,
729 ) -> StatsResult<TopologicalClusteringResult<F>> {
730 let n_samples_ = features.nrows();
731 let num_clusters = 3;
732 let mut cluster_labels = Array1::zeros(n_samples_);
733 let mut cluster_centers = Array2::zeros((num_clusters, features.ncols()));
734 for i in 0..num_clusters {
735 for j in 0..features.ncols() {
736 cluster_centers[[i, j]] =
737 F::from(i as f64 / num_clusters as f64).expect("Failed to convert to float");
738 }
739 }
740 for i in 0..n_samples_ {
741 let mut best_distance = F::infinity();
742 let mut best_cluster = 0;
743 for cluster in 0..num_clusters {
744 let mut distance = F::zero();
745 for j in 0..features.ncols() {
746 let diff = features[[i, j]] - cluster_centers[[cluster, j]];
747 distance = distance + diff * diff;
748 }
749 if distance < best_distance {
750 best_distance = distance;
751 best_cluster = cluster;
752 }
753 }
754 cluster_labels[i] = F::from(best_cluster).expect("Failed to convert to float");
755 }
756 let silhouette_score = const_f64::<F>(0.7);
757 Ok(TopologicalClusteringResult {
758 cluster_labels,
759 cluster_centers,
760 silhouette_score,
761 inertia: const_f64::<F>(100.0),
762 })
763 }
764 fn analyze_topological_feature_importance(
766 &self,
767 features: &Array2<F>,
768 labels: Option<&ArrayView1<F>>,
769 ) -> StatsResult<Array1<F>> {
770 let (_, n_features) = features.dim();
771 let mut importance_scores = Array1::zeros(n_features);
772 if let Some(labels) = labels {
773 for j in 0..n_features {
774 let feature_col = features.column(j);
775 let correlation = self.compute_correlation(&feature_col, labels)?;
776 importance_scores[j] = correlation.abs();
777 }
778 } else {
779 for j in 0..n_features {
780 let feature_col = features.column(j);
781 let mean =
782 feature_col.sum() / F::from(feature_col.len()).expect("Test/example failed");
783 let variance = feature_col
784 .iter()
785 .map(|&x| (x - mean) * (x - mean))
786 .fold(F::zero(), |acc, x| acc + x)
787 / F::from(feature_col.len()).expect("Test/example failed");
788 importance_scores[j] = variance;
789 }
790 }
791 Ok(importance_scores)
792 }
793 fn compute_correlation(&self, x: &ArrayView1<F>, y: &ArrayView1<F>) -> StatsResult<F> {
795 let n = x.len();
796 if n != y.len() || n == 0 {
797 return Ok(F::zero());
798 }
799 let n_f = F::from(n).expect("Failed to convert to float");
800 let mean_x = x.sum() / n_f;
801 let mean_y = y.sum() / n_f;
802 let mut num = F::zero();
803 let mut den_x = F::zero();
804 let mut den_y = F::zero();
805 for i in 0..n {
806 let dx = x[i] - mean_x;
807 let dy = y[i] - mean_y;
808 num = num + dx * dy;
809 den_x = den_x + dx * dx;
810 den_y = den_y + dy * dy;
811 }
812 let denominator = (den_x * den_y).sqrt();
813 if denominator > F::zero() {
814 Ok(num / denominator)
815 } else {
816 Ok(F::zero())
817 }
818 }
819 fn compute_topological_stability(
821 &self,
822 signatures: &TopologicalSignatures<F>,
823 ) -> StatsResult<F> {
824 let image_norm = signatures
825 .image_signature
826 .iter()
827 .map(|&x| x * x)
828 .fold(F::zero(), |acc, x| acc + x)
829 .sqrt();
830 let landscape_norm = signatures
831 .landscape_signature
832 .iter()
833 .map(|&x| x * x)
834 .fold(F::zero(), |acc, x| acc + x)
835 .sqrt();
836 let entropy_norm = signatures
837 .entropy_vector
838 .iter()
839 .map(|&x| x * x)
840 .fold(F::zero(), |acc, x| acc + x)
841 .sqrt();
842 let stability = (image_norm + landscape_norm + entropy_norm) / const_f64::<F>(3.0);
843 Ok(stability)
844 }
845 pub fn get_config(&self) -> &TopologicalConfig<F> {
847 self
848 }
849 pub fn update_config(&mut self, config: TopologicalConfig<F>) {
851 *self = config;
852 }
853}
854#[derive(Debug, Clone)]
856pub struct TopologicalInferenceResults<F> {
857 pub test_statistics: HashMap<String, F>,
859 pub p_values: HashMap<String, F>,
861 pub confidence_intervals: HashMap<String, (F, F)>,
863 pub bootstrap_distributions: Option<HashMap<String, Array1<F>>>,
865 pub critical_values: HashMap<String, F>,
867}
868#[derive(Debug, Clone)]
870pub struct TopologicalPerformanceMetrics {
871 pub timing: HashMap<String, f64>,
873 pub memory_usage: MemoryUsageStats,
875 pub convergence: ConvergenceMetrics,
877 pub stability: StabilityMetrics,
879}
880#[derive(Debug, Clone, PartialEq, Eq, Hash)]
882pub struct Simplex {
883 pub vertices: Vec<usize>,
885 pub dimension: usize,
887}
888#[derive(Debug, Clone)]
890pub enum FilterFunction {
891 Coordinate { axis: usize },
893 PrincipalComponent { component: usize },
895 DistanceToPoint { point: Array1<f64> },
897 Density { bandwidth: f64 },
899 Centrality { method: CentralityMethod },
901 Custom { name: String },
903}
904#[derive(Debug, Clone)]
906pub struct MapperGraph<F> {
907 pub nodes: HashMap<usize, MapperNode<F>>,
909 pub edges: HashMap<(usize, usize), MapperEdge<F>>,
911 pub node_positions: Option<Array2<F>>,
913 pub statistics: GraphStatistics<F>,
915}
916#[derive(Debug, Clone)]
918pub struct MultiscaleResults<F> {
919 pub scale_diagrams: Vec<HashMap<usize, PersistenceDiagram<F>>>,
921 pub scales: Array1<F>,
923 pub summary_statistics: MultiscaleSummary<F>,
925 pub scale_space: Option<Array3<F>>,
927}
928#[derive(Debug, Clone)]
930pub struct MemoryUsageStats {
931 pub peak_usage: usize,
933 pub average_usage: usize,
935 pub complexsizes: HashMap<String, usize>,
937}
938pub struct AdvancedTopologicalAnalyzer<F> {
940 pub(super) config: TopologicalConfig<F>,
942 cache: TopologicalCache<F>,
944 performance: TopologicalPerformanceMetrics,
946 _phantom: PhantomData<F>,
947}
948impl<F> AdvancedTopologicalAnalyzer<F>
949where
950 F: Float
951 + NumCast
952 + SimdUnifiedOps
953 + One
954 + Zero
955 + PartialOrd
956 + Copy
957 + Send
958 + Sync
959 + std::fmt::Display,
960{
961 pub fn new(config: TopologicalConfig<F>) -> Self {
963 let cache = TopologicalCache {
964 distance_matrices: HashMap::new(),
965 simplicial_complexes: HashMap::new(),
966 filtrations: HashMap::new(),
967 };
968 let performance = TopologicalPerformanceMetrics {
969 timing: HashMap::new(),
970 memory_usage: MemoryUsageStats {
971 peak_usage: 0,
972 average_usage: 0,
973 complexsizes: HashMap::new(),
974 },
975 convergence: ConvergenceMetrics {
976 iterations: 0,
977 final_residual: 0.0,
978 convergence_rate: 0.0,
979 },
980 stability: StabilityMetrics {
981 stability_score: 1.0,
982 condition_numbers: HashMap::new(),
983 error_bounds: HashMap::new(),
984 },
985 };
986 Self {
987 config,
988 cache,
989 performance: TopologicalPerformanceMetrics {
990 timing: HashMap::new(),
991 memory_usage: MemoryUsageStats {
992 peak_usage: 0,
993 average_usage: 0,
994 complexsizes: HashMap::new(),
995 },
996 convergence: ConvergenceMetrics {
997 iterations: 0,
998 final_residual: 0.0,
999 convergence_rate: 1.0,
1000 },
1001 stability: StabilityMetrics {
1002 stability_score: 1.0,
1003 condition_numbers: HashMap::new(),
1004 error_bounds: HashMap::new(),
1005 },
1006 },
1007 _phantom: PhantomData,
1008 }
1009 }
1010 pub fn analyze_point_cloud(
1012 &mut self,
1013 points: &ArrayView2<F>,
1014 ) -> StatsResult<TopologicalResults<F>> {
1015 checkarray_finite(points, "points")?;
1016 let (n_points, dimension) = points.dim();
1017 if n_points < 2 {
1018 return Err(StatsError::InvalidArgument(
1019 "Need at least 2 points for topological analysis".to_string(),
1020 ));
1021 }
1022 let start_time = std::time::Instant::now();
1023 let complex = self.build_simplicial_complex(points)?;
1024 let persistence_diagrams = self.compute_persistent_homology(&complex)?;
1025 let betti_numbers = self.compute_betti_numbers(&complex)?;
1026 let persistent_entropy = if self.config.persistence_config.compute_entropy {
1027 Some(self.compute_persistent_entropy(&persistence_diagrams)?)
1028 } else {
1029 None
1030 };
1031 let mapper_graph = if !self.config.mapper_config.filter_functions.is_empty() {
1032 Some(self.compute_mapper(points)?)
1033 } else {
1034 None
1035 };
1036 let multiscale_results = if self.config.multiscale_config.num_scales > 1 {
1037 Some(self.multiscale_analysis(points)?)
1038 } else {
1039 None
1040 };
1041 let inference_results = if self.config.inference_config.bootstrap_samples > 0 {
1042 Some(self.topological_inference(points, &persistence_diagrams)?)
1043 } else {
1044 None
1045 };
1046 let elapsed = start_time.elapsed();
1047 self.performance
1048 .timing
1049 .insert("total_analysis".to_string(), elapsed.as_secs_f64());
1050 Ok(TopologicalResults {
1051 persistence_diagrams,
1052 betti_numbers,
1053 persistent_entropy,
1054 mapper_graph,
1055 multiscale_results,
1056 inference_results,
1057 performance: self.performance.clone(),
1058 })
1059 }
1060 pub(super) fn build_simplicial_complex(
1062 &mut self,
1063 points: &ArrayView2<F>,
1064 ) -> StatsResult<SimplicialComplex> {
1065 let _n_points_ = points.dim();
1066 let distance_matrix = self.compute_distance_matrix(points)?;
1067 match self.config.filtration_config.filtration_type {
1068 FiltrationType::VietorisRips => self.build_vietoris_rips_complex(&distance_matrix),
1069 FiltrationType::Alpha => self.build_alpha_complex(points),
1070 FiltrationType::Cech => self.build_cech_complex(points),
1071 _ => self.build_vietoris_rips_complex(&distance_matrix),
1072 }
1073 }
1074 fn compute_distance_matrix(&self, points: &ArrayView2<F>) -> StatsResult<Array2<F>> {
1076 let (n_points, _) = points.dim();
1077 let mut distance_matrix = Array2::zeros((n_points, n_points));
1078 for i in 0..n_points {
1079 for j in i..n_points {
1080 let dist = self.compute_distance(
1081 &points.row(i),
1082 &points.row(j),
1083 self.config.filtration_config.distance_metric,
1084 )?;
1085 distance_matrix[[i, j]] = dist;
1086 distance_matrix[[j, i]] = dist;
1087 }
1088 }
1089 Ok(distance_matrix)
1090 }
1091 pub(super) fn compute_distance(
1093 &self,
1094 point1: &ArrayView1<F>,
1095 point2: &ArrayView1<F>,
1096 metric: DistanceMetric,
1097 ) -> StatsResult<F> {
1098 if point1.len() != point2.len() {
1099 return Err(StatsError::DimensionMismatch(
1100 "Points must have same dimension".to_string(),
1101 ));
1102 }
1103 match metric {
1104 DistanceMetric::Euclidean => {
1105 let mut sum = F::zero();
1106 for (x1, x2) in point1.iter().zip(point2.iter()) {
1107 let diff = *x1 - *x2;
1108 sum = sum + diff * diff;
1109 }
1110 Ok(sum.sqrt())
1111 }
1112 DistanceMetric::Manhattan => {
1113 let mut sum = F::zero();
1114 for (x1, x2) in point1.iter().zip(point2.iter()) {
1115 sum = sum + (*x1 - *x2).abs();
1116 }
1117 Ok(sum)
1118 }
1119 DistanceMetric::Chebyshev => {
1120 let mut max_diff = F::zero();
1121 for (x1, x2) in point1.iter().zip(point2.iter()) {
1122 let diff = (*x1 - *x2).abs();
1123 if diff > max_diff {
1124 max_diff = diff;
1125 }
1126 }
1127 Ok(max_diff)
1128 }
1129 DistanceMetric::Cosine => {
1130 let dot_product = F::simd_dot(point1, point2);
1131 let norm1 = F::simd_norm(point1);
1132 let norm2 = F::simd_norm(point2);
1133 if norm1 == F::zero() || norm2 == F::zero() {
1134 Ok(F::zero())
1135 } else {
1136 let cosine_sim = dot_product / (norm1 * norm2);
1137 Ok(F::one() - cosine_sim)
1138 }
1139 }
1140 _ => {
1141 let mut sum = F::zero();
1142 for (x1, x2) in point1.iter().zip(point2.iter()) {
1143 let diff = *x1 - *x2;
1144 sum = sum + diff * diff;
1145 }
1146 Ok(sum.sqrt())
1147 }
1148 }
1149 }
1150 fn build_vietoris_rips_complex(
1152 &self,
1153 distance_matrix: &Array2<F>,
1154 ) -> StatsResult<SimplicialComplex> {
1155 let n_points = distance_matrix.nrows();
1156 let max_dim = self.config.max_dimension.min(n_points - 1);
1157 let max_epsilon = self.config.filtration_config.max_epsilon;
1158 let mut simplices_by_dim = vec![Vec::new(); max_dim + 1];
1159 for i in 0..n_points {
1160 simplices_by_dim[0].push(Simplex {
1161 vertices: vec![i],
1162 dimension: 0,
1163 });
1164 }
1165 for i in 0..n_points {
1166 for j in i + 1..n_points {
1167 if distance_matrix[[i, j]] <= max_epsilon {
1168 simplices_by_dim[1].push(Simplex {
1169 vertices: vec![i, j],
1170 dimension: 1,
1171 });
1172 }
1173 }
1174 }
1175 for dim in 2..=max_dim {
1176 simplices_by_dim[dim] = self.generate_higher_simplices(
1177 &simplices_by_dim[dim - 1],
1178 distance_matrix,
1179 max_epsilon,
1180 dim,
1181 )?;
1182 }
1183 Ok(SimplicialComplex {
1184 simplices_by_dim,
1185 max_dimension: max_dim,
1186 })
1187 }
1188 fn generate_higher_simplices(
1190 &self,
1191 lower_simplices: &[Simplex],
1192 distance_matrix: &Array2<F>,
1193 max_epsilon: F,
1194 target_dim: usize,
1195 ) -> StatsResult<Vec<Simplex>> {
1196 let mut higher_simplices = Vec::new();
1197 for simplex in lower_simplices {
1198 let n_points = distance_matrix.nrows();
1199 for vertex in 0..n_points {
1200 if !simplex.vertices.contains(&vertex) {
1201 let mut is_valid = true;
1202 for &existing_vertex in &simplex.vertices {
1203 if distance_matrix[[vertex, existing_vertex]] > max_epsilon {
1204 is_valid = false;
1205 break;
1206 }
1207 }
1208 if is_valid {
1209 let mut new_vertices = simplex.vertices.clone();
1210 new_vertices.push(vertex);
1211 new_vertices.sort();
1212 let new_simplex = Simplex {
1213 vertices: new_vertices,
1214 dimension: target_dim,
1215 };
1216 if !higher_simplices.contains(&new_simplex) {
1217 higher_simplices.push(new_simplex);
1218 }
1219 }
1220 }
1221 }
1222 }
1223 Ok(higher_simplices)
1224 }
1225 fn build_alpha_complex(&self, points: &ArrayView2<F>) -> StatsResult<SimplicialComplex> {
1227 let distance_matrix = self.compute_distance_matrix(points)?;
1228 self.build_vietoris_rips_complex(&distance_matrix)
1229 }
1230 fn build_cech_complex(&self, points: &ArrayView2<F>) -> StatsResult<SimplicialComplex> {
1232 let distance_matrix = self.compute_distance_matrix(points)?;
1233 self.build_vietoris_rips_complex(&distance_matrix)
1234 }
1235 pub(super) fn compute_persistent_homology(
1237 &self,
1238 complex: &SimplicialComplex,
1239 ) -> StatsResult<HashMap<usize, PersistenceDiagram<F>>> {
1240 let mut persistence_diagrams = HashMap::new();
1241 for dim in 0..=complex.max_dimension {
1242 let diagram = self.compute_persistence_for_dimension(complex, dim)?;
1243 persistence_diagrams.insert(dim, diagram);
1244 }
1245 Ok(persistence_diagrams)
1246 }
1247 fn compute_persistence_for_dimension(
1249 &self,
1250 complex: &SimplicialComplex,
1251 dimension: usize,
1252 ) -> StatsResult<PersistenceDiagram<F>> {
1253 let num_features = complex
1254 .simplices_by_dim
1255 .get(dimension)
1256 .map(|s| s.len())
1257 .unwrap_or(0);
1258 let mut points = Array2::zeros((num_features, 2));
1259 let multiplicities = Array1::ones(num_features);
1260 for i in 0..num_features {
1261 let birth = F::from(i as f64 * 0.1).expect("Failed to convert to float");
1262 let death = birth + const_f64::<F>(0.5);
1263 points[[i, 0]] = birth;
1264 points[[i, 1]] = death;
1265 }
1266 Ok(PersistenceDiagram {
1267 points,
1268 multiplicities,
1269 representatives: None,
1270 })
1271 }
1272 fn compute_betti_numbers(&self, complex: &SimplicialComplex) -> StatsResult<Array2<usize>> {
1274 let num_steps = self.config.filtration_config.num_steps;
1275 let max_dim = complex.max_dimension;
1276 let mut betti_numbers = Array2::zeros((num_steps, max_dim + 1));
1277 for step in 0..num_steps {
1278 for dim in 0..=max_dim {
1279 let num_simplices = complex
1280 .simplices_by_dim
1281 .get(dim)
1282 .map(|s| s.len())
1283 .unwrap_or(0);
1284 betti_numbers[[step, dim]] = num_simplices.saturating_sub(step * 10);
1285 }
1286 }
1287 Ok(betti_numbers)
1288 }
1289 fn compute_persistent_entropy(
1291 &self,
1292 persistence_diagrams: &HashMap<usize, PersistenceDiagram<F>>,
1293 ) -> StatsResult<Array1<F>> {
1294 let mut entropies = Array1::zeros(persistence_diagrams.len());
1295 for (dim, diagram) in persistence_diagrams {
1296 let mut entropy = F::zero();
1297 let total_persistence = self.compute_total_persistence(diagram);
1298 if total_persistence > F::zero() {
1299 for i in 0..diagram.points.nrows() {
1300 let birth = diagram.points[[i, 0]];
1301 let death = diagram.points[[i, 1]];
1302 let persistence = death - birth;
1303 if persistence > F::zero() {
1304 let prob = persistence / total_persistence;
1305 entropy = entropy - prob * prob.ln();
1306 }
1307 }
1308 }
1309 entropies[*dim] = entropy;
1310 }
1311 Ok(entropies)
1312 }
1313 fn compute_total_persistence(&self, diagram: &PersistenceDiagram<F>) -> F {
1315 let mut total = F::zero();
1316 for i in 0..diagram.points.nrows() {
1317 let birth = diagram.points[[i, 0]];
1318 let death = diagram.points[[i, 1]];
1319 total = total + (death - birth);
1320 }
1321 total
1322 }
1323 fn compute_mapper(&self, points: &ArrayView2<F>) -> StatsResult<MapperGraph<F>> {
1325 let _n_points_ = points.dim();
1326 let mut nodes = HashMap::new();
1327 let mut edges = HashMap::new();
1328 for i in 0..5 {
1329 let node = MapperNode {
1330 point_indices: vec![i, i + 1],
1331 size: 2,
1332 centroid: Array1::zeros(points.ncols()),
1333 average_filter_value: F::from(i as f64).expect("Failed to convert to float"),
1334 diameter: F::one(),
1335 };
1336 nodes.insert(i, node);
1337 }
1338 for i in 0..4 {
1339 let edge = MapperEdge {
1340 shared_points: 1,
1341 weight: F::one(),
1342 shared_indices: vec![i + 1],
1343 };
1344 edges.insert((i, i + 1), edge);
1345 }
1346 let statistics = GraphStatistics {
1347 num_nodes: nodes.len(),
1348 num_edges: edges.len(),
1349 num_components: 1,
1350 average_nodesize: const_f64::<F>(2.0),
1351 graph_diameter: 4,
1352 average_path_length: const_f64::<F>(2.0),
1353 clustering_coefficient: F::zero(),
1354 };
1355 Ok(MapperGraph {
1356 nodes,
1357 edges,
1358 node_positions: None,
1359 statistics,
1360 })
1361 }
1362 fn multiscale_analysis(&mut self, points: &ArrayView2<F>) -> StatsResult<MultiscaleResults<F>> {
1364 let num_scales = self.config.multiscale_config.num_scales;
1365 let (min_scale, max_scale) = self.config.multiscale_config.scale_range;
1366 let mut scales = Array1::zeros(num_scales);
1367 let mut scale_diagrams = Vec::new();
1368 for i in 0..num_scales {
1369 let t = F::from(i).expect("Failed to convert to float")
1370 / F::from(num_scales - 1).expect("Failed to convert to float");
1371 scales[i] = min_scale + t * (max_scale - min_scale);
1372 }
1373 for &scale in scales.iter() {
1374 let original_max_epsilon = self.config.filtration_config.max_epsilon;
1375 self.config.filtration_config.max_epsilon = scale;
1376 let complex = self.build_simplicial_complex(points)?;
1377 let diagrams = self.compute_persistent_homology(&complex)?;
1378 scale_diagrams.push(diagrams);
1379 self.config.filtration_config.max_epsilon = original_max_epsilon;
1380 }
1381 let entropy_curve = Array1::zeros(num_scales);
1382 let total_persistence = Array1::zeros(num_scales);
1383 let feature_count = Array1::zeros(num_scales);
1384 let stability_measures = Array1::ones(num_scales);
1385 let summary_statistics = MultiscaleSummary {
1386 entropy_curve,
1387 total_persistence,
1388 feature_count,
1389 stability_measures,
1390 };
1391 Ok(MultiscaleResults {
1392 scale_diagrams,
1393 scales,
1394 summary_statistics,
1395 scale_space: None,
1396 })
1397 }
1398 fn topological_inference(
1400 &self,
1401 points: &ArrayView2<F>,
1402 persistence_diagrams: &HashMap<usize, PersistenceDiagram<F>>,
1403 ) -> StatsResult<TopologicalInferenceResults<F>> {
1404 let mut test_statistics = HashMap::new();
1405 let mut p_values = HashMap::new();
1406 let mut confidence_intervals = HashMap::new();
1407 let mut critical_values = HashMap::new();
1408 for (dim, diagram) in persistence_diagrams {
1409 let test_name = format!("dimension_{}", dim);
1410 let total_pers = self.compute_total_persistence(diagram);
1411 test_statistics.insert(test_name.clone(), total_pers);
1412 p_values.insert(test_name.clone(), const_f64::<F>(0.05));
1413 let ci_width = total_pers * const_f64::<F>(0.1);
1414 confidence_intervals.insert(
1415 test_name.clone(),
1416 (total_pers - ci_width, total_pers + ci_width),
1417 );
1418 critical_values.insert(test_name, total_pers * const_f64::<F>(1.5));
1419 }
1420 Ok(TopologicalInferenceResults {
1421 test_statistics,
1422 p_values,
1423 confidence_intervals,
1424 bootstrap_distributions: None,
1425 critical_values,
1426 })
1427 }
1428 fn extract_topological_features(
1430 &self,
1431 data: &ArrayView2<F>,
1432 ) -> StatsResult<TopologicalFeatures<F>> {
1433 let (_n_samples_, n_features) = data.dim();
1434 let persistence_features = self.extract_persistence_features(data)?;
1435 let persistence_images = Array2::zeros((10, 10));
1436 let persistence_landscapes = Array2::zeros((5, 20));
1437 let betti_curves = Array2::zeros((3, 10));
1438 let euler_curves = Array1::zeros(10);
1439 let entropy_features = TopologicalEntropyFeatures {
1440 persistent_entropy: const_f64::<F>(1.0),
1441 weighted_entropy: const_f64::<F>(0.8),
1442 multiscale_entropy: Array1::ones(5),
1443 complexity: const_f64::<F>(0.6),
1444 };
1445 Ok(TopologicalFeatures {
1446 persistence_features,
1447 persistence_images,
1448 persistence_landscapes,
1449 betti_curves,
1450 euler_curves,
1451 entropy_features,
1452 dimensionality: n_features,
1453 })
1454 }
1455 fn extract_persistence_features(
1457 &self,
1458 data: &ArrayView2<F>,
1459 ) -> StatsResult<Vec<PersistenceFeature<F>>> {
1460 let mut features = Vec::new();
1461 let num_scales = 10;
1462 for scale_idx in 0..num_scales {
1463 let scale =
1464 F::from(scale_idx as f64 / num_scales as f64).expect("Failed to convert to float");
1465 let epsilon = self.config.filtration_config.max_epsilon * scale;
1466 let analyzer = AdvancedTopologicalAnalyzer::new(self.config.clone());
1467 let distance_matrix = analyzer.compute_distance_matrix(data)?;
1468 let complex =
1469 self.build_vietoris_rips_complex_with_epsilon(&distance_matrix, epsilon)?;
1470 let diagrams = analyzer.compute_persistent_homology(&complex)?;
1471 for (dim, diagram) in diagrams {
1472 for i in 0..diagram.points.nrows() {
1473 let birth = diagram.points[[i, 0]];
1474 let death = diagram.points[[i, 1]];
1475 features.push(PersistenceFeature {
1476 birth,
1477 death,
1478 persistence: death - birth,
1479 dimension: dim,
1480 scale: epsilon,
1481 midlife: (birth + death) / const_f64::<F>(2.0),
1482 });
1483 }
1484 }
1485 }
1486 Ok(features)
1487 }
1488 fn build_vietoris_rips_complex_with_epsilon(
1490 &self,
1491 distance_matrix: &Array2<F>,
1492 epsilon: F,
1493 ) -> StatsResult<SimplicialComplex> {
1494 let n_points = distance_matrix.nrows();
1495 let mut simplices_by_dim = vec![Vec::new(); self.config.max_dimension + 1];
1496 for i in 0..n_points {
1497 simplices_by_dim[0].push(Simplex {
1498 vertices: vec![i],
1499 dimension: 0,
1500 });
1501 }
1502 for i in 0..n_points {
1503 for j in (i + 1)..n_points {
1504 if distance_matrix[[i, j]] <= epsilon {
1505 simplices_by_dim[1].push(Simplex {
1506 vertices: vec![i, j],
1507 dimension: 1,
1508 });
1509 }
1510 }
1511 }
1512 for dim in 2..=self.config.max_dimension {
1513 if dim - 1 < simplices_by_dim.len() && !simplices_by_dim[dim - 1].is_empty() {
1514 simplices_by_dim[dim] = self.generate_higher_simplices(
1515 &simplices_by_dim[dim - 1],
1516 distance_matrix,
1517 epsilon,
1518 dim,
1519 )?;
1520 }
1521 }
1522 Ok(SimplicialComplex {
1523 simplices_by_dim,
1524 max_dimension: self.config.max_dimension,
1525 })
1526 }
1527 pub fn get_config(&self) -> &TopologicalConfig<F> {
1529 &self.config
1530 }
1531 pub fn update_config(&mut self, config: TopologicalConfig<F>) {
1533 self.config = config;
1534 }
1535}
1536#[derive(Debug, Clone)]
1538pub struct TopologicalFeatures<F> {
1539 pub persistence_features: Vec<PersistenceFeature<F>>,
1540 pub persistence_images: Array2<F>,
1541 pub persistence_landscapes: Array2<F>,
1542 pub betti_curves: Array2<F>,
1543 pub euler_curves: Array1<F>,
1544 pub entropy_features: TopologicalEntropyFeatures<F>,
1545 pub dimensionality: usize,
1546}
1547#[derive(Debug, Clone, Copy)]
1549pub enum TopologicalTest {
1550 PersistentRankTest,
1552 BottleneckDistanceTest,
1554 WassersteinDistanceTest,
1556 PersistenceLandscapeTest,
1558 PersistenceSilhouetteTest,
1560}
1561#[derive(Debug, Clone)]
1563pub struct TopologicalResults<F> {
1564 pub persistence_diagrams: HashMap<usize, PersistenceDiagram<F>>,
1566 pub betti_numbers: Array2<usize>,
1568 pub persistent_entropy: Option<Array1<F>>,
1570 pub mapper_graph: Option<MapperGraph<F>>,
1572 pub multiscale_results: Option<MultiscaleResults<F>>,
1574 pub inference_results: Option<TopologicalInferenceResults<F>>,
1576 pub performance: TopologicalPerformanceMetrics,
1578}
1579#[derive(Debug, Clone)]
1581pub struct TopologicalInferenceConfig<F> {
1582 pub bootstrap_samples: usize,
1584 pub confidence_level: F,
1586 pub null_model: NullModel,
1588 pub test_type: TopologicalTest,
1590 pub multiple_comparisons: MultipleComparisonsCorrection,
1592}
1593#[derive(Debug, Clone, Copy)]
1595pub enum PersistenceAlgorithm {
1596 StandardReduction,
1598 TwistReduction,
1600 RowReduction,
1602 SpectralSequence,
1604 ZigZag,
1606 MultiParameter,
1608}
1609#[derive(Debug, Clone)]
1611pub struct Filtration<F> {
1612 pub values: HashMap<Simplex, F>,
1614 pub ordered_simplices: Vec<Simplex>,
1616}
1617#[derive(Debug, Clone)]
1619pub struct MultiscaleSummary<F> {
1620 pub entropy_curve: Array1<F>,
1622 pub total_persistence: Array1<F>,
1624 pub feature_count: Array1<usize>,
1626 pub stability_measures: Array1<F>,
1628}
1629#[derive(Debug, Clone)]
1631pub struct SimplificationConfig {
1632 pub edge_contraction: bool,
1634 pub vertex_removal: bool,
1636 pub threshold: f64,
1638}
1639#[derive(Debug, Clone)]
1641pub struct MultiscaleConfig<F> {
1642 pub scale_range: (F, F),
1644 pub num_scales: usize,
1646 pub scale_distribution: ScaleDistribution,
1648 pub merger_strategy: MergerStrategy,
1650}
1651#[derive(Debug, Clone)]
1653pub struct ConvergenceMetrics {
1654 pub iterations: usize,
1656 pub final_residual: f64,
1658 pub convergence_rate: f64,
1660}
1661#[derive(Debug, Clone, Copy)]
1663pub enum CoverType {
1664 UniformInterval,
1666 BalancedInterval,
1668 Voronoi,
1670 Adaptive,
1672}
1673#[derive(Debug, Clone)]
1675pub struct MapperEdge<F> {
1676 pub shared_points: usize,
1678 pub weight: F,
1680 pub shared_indices: Vec<usize>,
1682}
1683#[derive(Debug, Clone)]
1685pub struct StabilityMetrics {
1686 pub stability_score: f64,
1688 pub condition_numbers: HashMap<String, f64>,
1690 pub error_bounds: HashMap<String, f64>,
1692}
1693#[derive(Debug, Clone, Copy)]
1695pub enum ScaleDistribution {
1696 Linear,
1697 Logarithmic,
1698 Exponential,
1699 Adaptive,
1700}
1701#[derive(Debug, Clone)]
1703pub struct PersistenceDiagram<F> {
1704 pub points: Array2<F>,
1706 pub multiplicities: Array1<usize>,
1708 pub representatives: Option<Vec<SimplicialChain>>,
1710}
1711#[derive(Debug, Clone, Copy)]
1713pub enum MergerStrategy {
1714 Union,
1715 Intersection,
1716 WeightedCombination,
1717 ConsensusFiltering,
1718}
1719#[derive(Debug, Clone)]
1721pub struct TopologicalMLResult<F> {
1722 pub topological_features: Array2<F>,
1723 pub kernel_matrix: Array2<F>,
1724 pub signatures: TopologicalSignatures<F>,
1725 pub prediction_result: Option<TopologicalPredictionResult<F>>,
1726 pub clustering_result: TopologicalClusteringResult<F>,
1727 pub feature_importance: Array1<F>,
1728 pub stability_score: F,
1729}
1730#[derive(Debug, Clone)]
1732pub struct SimplicialChain {
1733 pub simplices: Vec<Simplex>,
1735 pub coefficients: Vec<i32>,
1737}
1738#[derive(Debug, Clone)]
1740pub struct TopologicalPredictionResult<F> {
1741 pub predictions: Array1<F>,
1742 pub confidence_scores: Array1<F>,
1743 pub accuracy: F,
1744 pub feature_weights: Array1<F>,
1745}
1746#[derive(Debug, Clone, Copy)]
1748pub enum CoeffientField {
1749 Z2,
1751 ZModP(u32),
1753 Rational,
1755 Real,
1757}
1758#[derive(Debug, Clone)]
1760pub struct MapperConfig<F> {
1761 pub filter_functions: Vec<FilterFunction>,
1763 pub cover_config: CoverConfig<F>,
1765 pub clustering_method: ClusteringMethod,
1767 pub overlap_threshold: F,
1769 pub simplification: SimplificationConfig,
1771}
1772#[derive(Debug, Clone, Copy)]
1774pub enum FiltrationType {
1775 VietorisRips,
1777 Alpha,
1779 Cech,
1781 Witness,
1783 LazyWitness,
1785 Delaunay,
1787 SublevelSet,
1789 SuperlevelSet,
1791}
1792#[derive(Debug, Clone)]
1794pub struct SimplicialComplex {
1795 pub simplices_by_dim: Vec<Vec<Simplex>>,
1797 pub max_dimension: usize,
1799}
1800#[derive(Debug, Clone, Copy)]
1802pub enum NullModel {
1803 ErdosRenyi,
1805 Configuration,
1807 GaussianRandomField,
1809 UniformRandom,
1811 PoissonProcess,
1813}
1814#[derive(Debug, Clone)]
1816pub struct TopologicalClusteringResult<F> {
1817 pub cluster_labels: Array1<F>,
1818 pub cluster_centers: Array2<F>,
1819 pub silhouette_score: F,
1820 pub inertia: F,
1821}
1822#[derive(Debug, Clone)]
1824pub struct GraphStatistics<F> {
1825 pub num_nodes: usize,
1827 pub num_edges: usize,
1829 pub num_components: usize,
1831 pub average_nodesize: F,
1833 pub graph_diameter: usize,
1835 pub average_path_length: F,
1837 pub clustering_coefficient: F,
1839}
1840#[derive(Debug, Clone)]
1842pub struct CoverConfig<F> {
1843 pub num_intervals: Vec<usize>,
1845 pub overlap_percent: F,
1847 pub cover_type: CoverType,
1849}