use crate::error::{StatsError, StatsResult};
use scirs2_core::ndarray::{Array1, Array2, Array3, ArrayView1, ArrayView2};
use scirs2_core::numeric::{Float, NumCast, One, Zero};
use scirs2_core::{simd_ops::SimdUnifiedOps, validation::*};
use scirs2_linalg::parallel_dispatch::ParallelConfig;
use std::collections::HashMap;
use std::marker::PhantomData;
use super::functions::const_f64;
#[derive(Debug, Clone)]
pub struct MapperNode<F> {
pub point_indices: Vec<usize>,
pub size: usize,
pub centroid: Array1<F>,
pub average_filter_value: F,
pub diameter: F,
}
#[derive(Debug, Clone, Copy)]
pub enum DistanceMetric {
Euclidean,
Manhattan,
Chebyshev,
Minkowski(f64),
Cosine,
Correlation,
Hamming,
Jaccard,
Mahalanobis,
Custom,
}
#[derive(Debug, Clone)]
pub struct FiltrationConfig<F> {
pub filtration_type: FiltrationType,
pub distance_metric: DistanceMetric,
pub max_epsilon: F,
pub num_steps: usize,
pub adaptive_steps: bool,
}
struct TopologicalCache<F> {
distance_matrices: HashMap<String, Array2<F>>,
simplicial_complexes: HashMap<String, SimplicialComplex>,
filtrations: HashMap<String, Filtration<F>>,
}
#[derive(Debug, Clone, Copy)]
pub enum ClusteringMethod {
SingleLinkage,
CompleteLinkage,
AverageLinkage,
KMeans,
DBSCAN,
SpectralClustering,
}
#[derive(Debug, Clone, Copy)]
pub enum CentralityMethod {
Degree,
Betweenness,
Closeness,
Eigenvector,
PageRank,
Katz,
}
#[derive(Debug, Clone)]
pub struct TopologicalSignatures<F> {
pub image_signature: Vec<F>,
pub landscape_signature: Vec<F>,
pub betti_statistics: Vec<F>,
pub euler_statistics: Vec<F>,
pub entropy_vector: Vec<F>,
}
#[derive(Debug, Clone, Copy)]
pub enum MultipleComparisonsCorrection {
None,
Bonferroni,
BenjaminiHochberg,
BenjaminiYekutieli,
Holm,
Hochberg,
}
#[derive(Debug, Clone)]
pub struct PersistenceFeature<F> {
pub birth: F,
pub death: F,
pub persistence: F,
pub dimension: usize,
pub scale: F,
pub midlife: F,
}
#[derive(Debug, Clone)]
pub struct TopologicalEntropyFeatures<F> {
pub persistent_entropy: F,
pub weighted_entropy: F,
pub multiscale_entropy: Array1<F>,
pub complexity: F,
}
#[derive(Debug, Clone)]
pub struct PersistenceConfig<F> {
pub algorithm: PersistenceAlgorithm,
pub coefficient_field: CoeffientField,
pub persistence_threshold: F,
pub compute_entropy: bool,
pub stability_analysis: bool,
}
pub struct TopologicalConfig<F> {
pub max_dimension: usize,
pub filtration_config: FiltrationConfig<F>,
pub persistence_config: PersistenceConfig<F>,
pub mapper_config: MapperConfig<F>,
pub multiscale_config: MultiscaleConfig<F>,
pub inference_config: TopologicalInferenceConfig<F>,
pub parallel_config: ParallelConfig,
}
impl<F> TopologicalConfig<F>
where
F: Float + NumCast + Copy + std::fmt::Display + SimdUnifiedOps + Send + Sync,
{
pub fn topological_machine_learning(
&mut self,
data: &ArrayView2<F>,
_labels: Option<&ArrayView1<F>>,
) -> StatsResult<TopologicalMLResult<F>> {
let topological_features = (*self).extract_topological_features(data)?;
let feature_matrix = Array2::zeros((data.nrows(), 10));
let analyzer = AdvancedTopologicalAnalyzer::new(self.clone());
let kernel_matrix = analyzer.compute_distance_matrix(&feature_matrix.view())?;
let prediction_result = None;
let clustering_result = TopologicalClusteringResult {
cluster_labels: Array1::zeros(data.nrows()),
cluster_centers: Array2::zeros((3, data.ncols())),
silhouette_score: const_f64::<F>(0.5),
inertia: const_f64::<F>(1.0),
};
let feature_importance = Array1::ones(feature_matrix.ncols());
let signatures = TopologicalSignatures {
image_signature: topological_features
.persistence_images
.iter()
.cloned()
.collect(),
landscape_signature: topological_features
.persistence_landscapes
.iter()
.cloned()
.collect(),
betti_statistics: topological_features.betti_curves.iter().cloned().collect(),
euler_statistics: topological_features.euler_curves.iter().cloned().collect(),
entropy_vector: vec![topological_features.entropy_features.persistent_entropy],
};
Ok(TopologicalMLResult {
topological_features: feature_matrix,
kernel_matrix,
signatures,
prediction_result,
clustering_result,
feature_importance,
stability_score: const_f64::<F>(0.95),
})
}
fn extract_topological_features(
&self,
data: &ArrayView2<F>,
) -> StatsResult<TopologicalFeatures<F>> {
let (_n_samples_, n_features) = data.dim();
let persistence_features = self.extract_persistence_features(data)?;
let persistence_images = Array2::zeros((10, 10));
let persistence_landscapes = Array2::zeros((5, 20));
let betti_curves = Array2::zeros((3, 10));
let euler_curves = Array1::zeros(10);
let entropy_features = TopologicalEntropyFeatures {
persistent_entropy: const_f64::<F>(1.0),
weighted_entropy: const_f64::<F>(0.8),
multiscale_entropy: Array1::ones(5),
complexity: const_f64::<F>(0.6),
};
Ok(TopologicalFeatures {
persistence_features,
persistence_images,
persistence_landscapes,
betti_curves,
euler_curves,
entropy_features,
dimensionality: n_features,
})
}
fn extract_persistence_features(
&self,
data: &ArrayView2<F>,
) -> StatsResult<Vec<PersistenceFeature<F>>> {
let mut features = Vec::new();
let num_scales = 10;
for scale_idx in 0..num_scales {
let scale =
F::from(scale_idx as f64 / num_scales as f64).expect("Failed to convert to float");
let epsilon = self.filtration_config.max_epsilon * scale;
let analyzer = AdvancedTopologicalAnalyzer::new(self.clone());
let distance_matrix = analyzer.compute_distance_matrix(data)?;
let complex =
self.build_vietoris_rips_complex_with_epsilon(&distance_matrix, epsilon)?;
let diagrams = analyzer.compute_persistent_homology(&complex)?;
for (dim, diagram) in diagrams {
for i in 0..diagram.points.nrows() {
let birth = diagram.points[[i, 0]];
let death = diagram.points[[i, 1]];
features.push(PersistenceFeature {
birth,
death,
persistence: death - birth,
dimension: dim,
scale: epsilon,
midlife: (birth + death) / const_f64::<F>(2.0),
});
}
}
}
Ok(features)
}
fn build_vietoris_rips_complex_with_epsilon(
&self,
distance_matrix: &Array2<F>,
epsilon: F,
) -> StatsResult<SimplicialComplex> {
let n_points = distance_matrix.nrows();
let mut simplices_by_dim = vec![Vec::new(); self.max_dimension + 1];
for i in 0..n_points {
simplices_by_dim[0].push(Simplex {
vertices: vec![i],
dimension: 0,
});
}
for i in 0..n_points {
for j in (i + 1)..n_points {
if distance_matrix[[i, j]] <= epsilon {
simplices_by_dim[1].push(Simplex {
vertices: vec![i, j],
dimension: 1,
});
}
}
}
for dim in 2..=self.max_dimension {
if dim - 1 < simplices_by_dim.len() && !simplices_by_dim[dim - 1].is_empty() {
let analyzer = AdvancedTopologicalAnalyzer::new(self.clone());
simplices_by_dim[dim] = analyzer.generate_higher_simplices(
&simplices_by_dim[dim - 1],
distance_matrix,
epsilon,
dim,
)?;
}
}
Ok(SimplicialComplex {
simplices_by_dim,
max_dimension: self.max_dimension,
})
}
fn compute_persistence_images(
&self,
features: &[PersistenceFeature<F>],
) -> StatsResult<Array2<F>> {
let resolution = 20;
let mut image = Array2::zeros((resolution, resolution));
let max_birth = features
.iter()
.map(|f| f.birth)
.fold(F::zero(), |a, b| if a > b { a } else { b });
let max_death = features
.iter()
.map(|f| f.death)
.fold(F::zero(), |a, b| if a > b { a } else { b });
let max_val = if max_death > max_birth {
max_death
} else {
max_birth
};
if max_val > F::zero() {
let sigma = const_f64::<F>(0.1) * max_val;
let sigma_sq = sigma * sigma;
for feature in features {
let _birth_coord = (feature.birth / max_val
* F::from(resolution as f64).expect("Failed to convert to float"))
.to_usize()
.unwrap_or(0)
.min(resolution - 1);
let _death_coord = (feature.death / max_val
* F::from(resolution as f64).expect("Failed to convert to float"))
.to_usize()
.unwrap_or(0)
.min(resolution - 1);
for i in 0..resolution {
for j in 0..resolution {
let x = F::from(i as f64).expect("Failed to convert to float")
/ F::from(resolution as f64).expect("Failed to convert to float")
* max_val;
let y = F::from(j as f64).expect("Failed to convert to float")
/ F::from(resolution as f64).expect("Failed to convert to float")
* max_val;
let dist_sq = (x - feature.birth) * (x - feature.birth)
+ (y - feature.death) * (y - feature.death);
let weight = (-dist_sq / sigma_sq).exp() * feature.persistence;
image[[i, j]] = image[[i, j]] + weight;
}
}
}
}
Ok(image)
}
fn compute_persistence_landscapes(
&self,
features: &[PersistenceFeature<F>],
) -> StatsResult<Array2<F>> {
let num_points = 100;
let num_landscapes = 5;
let mut landscapes = Array2::zeros((num_landscapes, num_points));
if features.is_empty() {
return Ok(landscapes);
}
let min_birth = features
.iter()
.map(|f| f.birth)
.fold(F::infinity(), |a, b| if a < b { a } else { b });
let max_death = features
.iter()
.map(|f| f.death)
.fold(F::neg_infinity(), |a, b| if a > b { a } else { b });
let range = max_death - min_birth;
if range <= F::zero() {
return Ok(landscapes);
}
for point_idx in 0..num_points {
let t = min_birth
+ F::from(point_idx as f64).expect("Failed to convert to float")
/ F::from(num_points as f64).expect("Failed to convert to float")
* range;
let mut values = Vec::new();
for feature in features {
if t >= feature.birth && t <= feature.death {
let value = if t <= (feature.birth + feature.death) / const_f64::<F>(2.0) {
t - feature.birth
} else {
feature.death - t
};
values.push(value);
}
}
values.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
for landscape_idx in 0..num_landscapes {
if landscape_idx < values.len() {
landscapes[[landscape_idx, point_idx]] = values[landscape_idx];
}
}
}
Ok(landscapes)
}
fn compute_betti_curves(&self, features: &[PersistenceFeature<F>]) -> StatsResult<Array2<F>> {
let num_points = 100;
let max_dim = 3;
let mut betti_curves = Array2::zeros((max_dim, num_points));
if features.is_empty() {
return Ok(betti_curves);
}
let min_val = features
.iter()
.map(|f| f.birth)
.fold(F::infinity(), |a, b| if a < b { a } else { b });
let max_val = features
.iter()
.map(|f| f.death)
.fold(F::neg_infinity(), |a, b| if a > b { a } else { b });
let range = max_val - min_val;
if range <= F::zero() {
return Ok(betti_curves);
}
for point_idx in 0..num_points {
let t = min_val
+ F::from(point_idx as f64).expect("Failed to convert to float")
/ F::from(num_points as f64).expect("Failed to convert to float")
* range;
for dim in 0..max_dim {
let count = features
.iter()
.filter(|f| f.dimension == dim && f.birth <= t && t < f.death)
.count();
betti_curves[[dim, point_idx]] =
F::from(count).expect("Failed to convert to float");
}
}
Ok(betti_curves)
}
fn compute_euler_characteristic_curves(
&self,
features: &[PersistenceFeature<F>],
) -> StatsResult<Array1<F>> {
let num_points = 100;
let mut euler_curve = Array1::zeros(num_points);
if features.is_empty() {
return Ok(euler_curve);
}
let min_val = features
.iter()
.map(|f| f.birth)
.fold(F::infinity(), |a, b| if a < b { a } else { b });
let max_val = features
.iter()
.map(|f| f.death)
.fold(F::neg_infinity(), |a, b| if a > b { a } else { b });
let range = max_val - min_val;
if range <= F::zero() {
return Ok(euler_curve);
}
for point_idx in 0..num_points {
let t = min_val
+ F::from(point_idx as f64).expect("Failed to convert to float")
/ F::from(num_points as f64).expect("Failed to convert to float")
* range;
let mut euler_char = F::zero();
for dim in 0..=3 {
let betti_number = features
.iter()
.filter(|f| f.dimension == dim && f.birth <= t && t < f.death)
.count();
let sign = if dim % 2 == 0 { F::one() } else { -F::one() };
euler_char =
euler_char + sign * F::from(betti_number).expect("Failed to convert to float");
}
euler_curve[point_idx] = euler_char;
}
Ok(euler_curve)
}
fn compute_topological_entropy_features(
&self,
features: &[PersistenceFeature<F>],
) -> StatsResult<TopologicalEntropyFeatures<F>> {
let persistent_entropy = self.compute_persistent_entropy(features)?;
let weighted_entropy = self.compute_persistence_weighted_entropy(features)?;
let multiscale_entropy = self.compute_multiscale_topological_entropy(features)?;
let complexity = self.compute_topological_complexity(features)?;
Ok(TopologicalEntropyFeatures {
persistent_entropy,
weighted_entropy,
multiscale_entropy,
complexity,
})
}
fn compute_persistent_entropy(&self, features: &[PersistenceFeature<F>]) -> StatsResult<F> {
if features.is_empty() {
return Ok(F::zero());
}
let total_persistence = features
.iter()
.map(|f| f.persistence)
.fold(F::zero(), |acc, p| acc + p);
if total_persistence <= F::zero() {
return Ok(F::zero());
}
let mut entropy = F::zero();
for feature in features {
if feature.persistence > F::zero() {
let prob = feature.persistence / total_persistence;
entropy = entropy - prob * prob.ln();
}
}
Ok(entropy)
}
fn compute_persistence_weighted_entropy(
&self,
features: &[PersistenceFeature<F>],
) -> StatsResult<F> {
if features.is_empty() {
return Ok(F::zero());
}
let mut weighted_entropy = F::zero();
let total_weight = features
.iter()
.map(|f| f.persistence * f.persistence)
.fold(F::zero(), |acc, w| acc + w);
if total_weight > F::zero() {
for feature in features {
let weight = (feature.persistence * feature.persistence) / total_weight;
if weight > F::zero() {
weighted_entropy = weighted_entropy - weight * weight.ln();
}
}
}
Ok(weighted_entropy)
}
fn compute_multiscale_topological_entropy(
&self,
features: &[PersistenceFeature<F>],
) -> StatsResult<Array1<F>> {
let num_scales = 5;
let mut multiscale_entropy = Array1::zeros(num_scales);
for scale_idx in 0..num_scales {
let scale_threshold =
F::from((scale_idx + 1) as f64 / num_scales as f64).expect("Test/example failed");
let filtered_features: Vec<_> = features
.iter()
.filter(|f| f.persistence >= scale_threshold * f.death)
.cloned()
.collect();
multiscale_entropy[scale_idx] = self.compute_persistent_entropy(&filtered_features)?;
}
Ok(multiscale_entropy)
}
fn compute_topological_complexity(&self, features: &[PersistenceFeature<F>]) -> StatsResult<F> {
if features.is_empty() {
return Ok(F::zero());
}
let entropy = self.compute_persistent_entropy(features)?;
let num_features = F::from(features.len()).expect("Test/example failed");
let avg_persistence = features
.iter()
.map(|f| f.persistence)
.fold(F::zero(), |acc, p| acc + p)
/ num_features;
let complexity = entropy * num_features.ln() * avg_persistence;
Ok(complexity)
}
fn compute_topological_signatures(
&self,
features: &TopologicalFeatures<F>,
) -> StatsResult<TopologicalSignatures<F>> {
let image_signature = features
.persistence_images
.as_slice()
.expect("Operation failed")
.to_vec();
let landscape_signature = features
.persistence_landscapes
.as_slice()
.expect("Operation failed")
.to_vec();
let betti_statistics = self.compute_curve_statistics(&features.betti_curves)?;
let euler_statistics = self.compute_curve_statistics_1d(&features.euler_curves)?;
let entropy_vector = vec![
features.entropy_features.persistent_entropy,
features.entropy_features.weighted_entropy,
features.entropy_features.complexity,
];
Ok(TopologicalSignatures {
image_signature,
landscape_signature,
betti_statistics,
euler_statistics,
entropy_vector,
})
}
fn compute_curve_statistics(&self, curves: &Array2<F>) -> StatsResult<Vec<F>> {
let mut statistics = Vec::new();
let (num_curves, num_points) = curves.dim();
for curve_idx in 0..num_curves {
let curve = curves.row(curve_idx);
let mean = curve.sum() / F::from(num_points).expect("Failed to convert to float");
let variance = curve
.iter()
.map(|&x| (x - mean) * (x - mean))
.fold(F::zero(), |acc, x| acc + x)
/ F::from(num_points).expect("Failed to convert to float");
let std_dev = variance.sqrt();
let min_val = curve
.iter()
.fold(F::infinity(), |a, &b| if a < b { a } else { b });
let max_val = curve
.iter()
.fold(F::neg_infinity(), |a, &b| if a > b { a } else { b });
let integral = curve.sum() / F::from(num_points).expect("Failed to convert to float");
statistics.extend_from_slice(&[mean, std_dev, min_val, max_val, integral]);
}
Ok(statistics)
}
fn compute_curve_statistics_1d(&self, curve: &Array1<F>) -> StatsResult<Vec<F>> {
let num_points = curve.len();
if num_points == 0 {
return Ok(vec![F::zero(); 5]);
}
let mean = curve.sum() / F::from(num_points).expect("Failed to convert to float");
let variance = curve
.iter()
.map(|&x| (x - mean) * (x - mean))
.fold(F::zero(), |acc, x| acc + x)
/ F::from(num_points).expect("Failed to convert to float");
let std_dev = variance.sqrt();
let min_val = curve
.iter()
.fold(F::infinity(), |a, &b| if a < b { a } else { b });
let max_val = curve
.iter()
.fold(F::neg_infinity(), |a, &b| if a > b { a } else { b });
Ok(vec![mean, std_dev, min_val, max_val, curve.sum()])
}
fn encode_persistent_features(
&self,
signatures: &TopologicalSignatures<F>,
) -> StatsResult<Array2<F>> {
let mut all_features = Vec::new();
all_features.extend_from_slice(&signatures.image_signature);
all_features.extend_from_slice(&signatures.landscape_signature);
all_features.extend_from_slice(&signatures.betti_statistics);
all_features.extend_from_slice(&signatures.euler_statistics);
all_features.extend_from_slice(&signatures.entropy_vector);
let n_features = all_features.len();
let mut feature_matrix = Array2::zeros((1, n_features));
for (i, &feature) in all_features.iter().enumerate() {
feature_matrix[[0, i]] = feature;
}
Ok(feature_matrix)
}
fn compute_topological_kernel_matrix(&self, features: &Array2<F>) -> StatsResult<Array2<F>> {
let (n_samples_, n_features) = features.dim();
let mut kernel_matrix = Array2::zeros((n_samples_, n_samples_));
let sigma = const_f64::<F>(1.0);
let sigma_sq = sigma * sigma;
for i in 0..n_samples_ {
for j in 0..n_samples_ {
let mut dist_sq = F::zero();
for k in 0..n_features {
let diff = features[[i, k]] - features[[j, k]];
dist_sq = dist_sq + diff * diff;
}
kernel_matrix[[i, j]] = (-dist_sq / sigma_sq).exp();
}
}
Ok(kernel_matrix)
}
fn topological_classification(
&self,
features: &Array2<F>,
labels: &ArrayView1<F>,
kernel_matrix: &Array2<F>,
) -> StatsResult<TopologicalPredictionResult<F>> {
let n_samples_ = features.nrows();
let mut predictions = Array1::zeros(n_samples_);
let mut confidence_scores = Array1::zeros(n_samples_);
for i in 0..n_samples_ {
let mut best_similarity = F::zero();
let mut predicted_label = labels[0];
for j in 0..n_samples_ {
if i != j && kernel_matrix[[i, j]] > best_similarity {
best_similarity = kernel_matrix[[i, j]];
predicted_label = labels[j];
}
}
predictions[i] = predicted_label;
confidence_scores[i] = best_similarity;
}
let correct_predictions: usize = predictions
.iter()
.zip(labels.iter())
.map(|(&pred, &true_label)| {
if (pred - true_label).abs() < const_f64::<F>(0.5) {
1
} else {
0
}
})
.sum();
let accuracy = F::from(correct_predictions as f64 / n_samples_ as f64)
.expect("Failed to convert to float");
Ok(TopologicalPredictionResult {
predictions,
confidence_scores,
accuracy,
feature_weights: Array1::ones(features.ncols()),
})
}
fn topological_clustering(
&self,
features: &Array2<F>,
) -> StatsResult<TopologicalClusteringResult<F>> {
let n_samples_ = features.nrows();
let num_clusters = 3;
let mut cluster_labels = Array1::zeros(n_samples_);
let mut cluster_centers = Array2::zeros((num_clusters, features.ncols()));
for i in 0..num_clusters {
for j in 0..features.ncols() {
cluster_centers[[i, j]] =
F::from(i as f64 / num_clusters as f64).expect("Failed to convert to float");
}
}
for i in 0..n_samples_ {
let mut best_distance = F::infinity();
let mut best_cluster = 0;
for cluster in 0..num_clusters {
let mut distance = F::zero();
for j in 0..features.ncols() {
let diff = features[[i, j]] - cluster_centers[[cluster, j]];
distance = distance + diff * diff;
}
if distance < best_distance {
best_distance = distance;
best_cluster = cluster;
}
}
cluster_labels[i] = F::from(best_cluster).expect("Failed to convert to float");
}
let silhouette_score = const_f64::<F>(0.7);
Ok(TopologicalClusteringResult {
cluster_labels,
cluster_centers,
silhouette_score,
inertia: const_f64::<F>(100.0),
})
}
fn analyze_topological_feature_importance(
&self,
features: &Array2<F>,
labels: Option<&ArrayView1<F>>,
) -> StatsResult<Array1<F>> {
let (_, n_features) = features.dim();
let mut importance_scores = Array1::zeros(n_features);
if let Some(labels) = labels {
for j in 0..n_features {
let feature_col = features.column(j);
let correlation = self.compute_correlation(&feature_col, labels)?;
importance_scores[j] = correlation.abs();
}
} else {
for j in 0..n_features {
let feature_col = features.column(j);
let mean =
feature_col.sum() / F::from(feature_col.len()).expect("Test/example failed");
let variance = feature_col
.iter()
.map(|&x| (x - mean) * (x - mean))
.fold(F::zero(), |acc, x| acc + x)
/ F::from(feature_col.len()).expect("Test/example failed");
importance_scores[j] = variance;
}
}
Ok(importance_scores)
}
fn compute_correlation(&self, x: &ArrayView1<F>, y: &ArrayView1<F>) -> StatsResult<F> {
let n = x.len();
if n != y.len() || n == 0 {
return Ok(F::zero());
}
let n_f = F::from(n).expect("Failed to convert to float");
let mean_x = x.sum() / n_f;
let mean_y = y.sum() / n_f;
let mut num = F::zero();
let mut den_x = F::zero();
let mut den_y = F::zero();
for i in 0..n {
let dx = x[i] - mean_x;
let dy = y[i] - mean_y;
num = num + dx * dy;
den_x = den_x + dx * dx;
den_y = den_y + dy * dy;
}
let denominator = (den_x * den_y).sqrt();
if denominator > F::zero() {
Ok(num / denominator)
} else {
Ok(F::zero())
}
}
fn compute_topological_stability(
&self,
signatures: &TopologicalSignatures<F>,
) -> StatsResult<F> {
let image_norm = signatures
.image_signature
.iter()
.map(|&x| x * x)
.fold(F::zero(), |acc, x| acc + x)
.sqrt();
let landscape_norm = signatures
.landscape_signature
.iter()
.map(|&x| x * x)
.fold(F::zero(), |acc, x| acc + x)
.sqrt();
let entropy_norm = signatures
.entropy_vector
.iter()
.map(|&x| x * x)
.fold(F::zero(), |acc, x| acc + x)
.sqrt();
let stability = (image_norm + landscape_norm + entropy_norm) / const_f64::<F>(3.0);
Ok(stability)
}
pub fn get_config(&self) -> &TopologicalConfig<F> {
self
}
pub fn update_config(&mut self, config: TopologicalConfig<F>) {
*self = config;
}
}
#[derive(Debug, Clone)]
pub struct TopologicalInferenceResults<F> {
pub test_statistics: HashMap<String, F>,
pub p_values: HashMap<String, F>,
pub confidence_intervals: HashMap<String, (F, F)>,
pub bootstrap_distributions: Option<HashMap<String, Array1<F>>>,
pub critical_values: HashMap<String, F>,
}
#[derive(Debug, Clone)]
pub struct TopologicalPerformanceMetrics {
pub timing: HashMap<String, f64>,
pub memory_usage: MemoryUsageStats,
pub convergence: ConvergenceMetrics,
pub stability: StabilityMetrics,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Simplex {
pub vertices: Vec<usize>,
pub dimension: usize,
}
#[derive(Debug, Clone)]
pub enum FilterFunction {
Coordinate { axis: usize },
PrincipalComponent { component: usize },
DistanceToPoint { point: Array1<f64> },
Density { bandwidth: f64 },
Centrality { method: CentralityMethod },
Custom { name: String },
}
#[derive(Debug, Clone)]
pub struct MapperGraph<F> {
pub nodes: HashMap<usize, MapperNode<F>>,
pub edges: HashMap<(usize, usize), MapperEdge<F>>,
pub node_positions: Option<Array2<F>>,
pub statistics: GraphStatistics<F>,
}
#[derive(Debug, Clone)]
pub struct MultiscaleResults<F> {
pub scale_diagrams: Vec<HashMap<usize, PersistenceDiagram<F>>>,
pub scales: Array1<F>,
pub summary_statistics: MultiscaleSummary<F>,
pub scale_space: Option<Array3<F>>,
}
#[derive(Debug, Clone)]
pub struct MemoryUsageStats {
pub peak_usage: usize,
pub average_usage: usize,
pub complexsizes: HashMap<String, usize>,
}
pub struct AdvancedTopologicalAnalyzer<F> {
pub(super) config: TopologicalConfig<F>,
cache: TopologicalCache<F>,
performance: TopologicalPerformanceMetrics,
_phantom: PhantomData<F>,
}
impl<F> AdvancedTopologicalAnalyzer<F>
where
F: Float
+ NumCast
+ SimdUnifiedOps
+ One
+ Zero
+ PartialOrd
+ Copy
+ Send
+ Sync
+ std::fmt::Display,
{
pub fn new(config: TopologicalConfig<F>) -> Self {
let cache = TopologicalCache {
distance_matrices: HashMap::new(),
simplicial_complexes: HashMap::new(),
filtrations: HashMap::new(),
};
let performance = TopologicalPerformanceMetrics {
timing: HashMap::new(),
memory_usage: MemoryUsageStats {
peak_usage: 0,
average_usage: 0,
complexsizes: HashMap::new(),
},
convergence: ConvergenceMetrics {
iterations: 0,
final_residual: 0.0,
convergence_rate: 0.0,
},
stability: StabilityMetrics {
stability_score: 1.0,
condition_numbers: HashMap::new(),
error_bounds: HashMap::new(),
},
};
Self {
config,
cache,
performance: TopologicalPerformanceMetrics {
timing: HashMap::new(),
memory_usage: MemoryUsageStats {
peak_usage: 0,
average_usage: 0,
complexsizes: HashMap::new(),
},
convergence: ConvergenceMetrics {
iterations: 0,
final_residual: 0.0,
convergence_rate: 1.0,
},
stability: StabilityMetrics {
stability_score: 1.0,
condition_numbers: HashMap::new(),
error_bounds: HashMap::new(),
},
},
_phantom: PhantomData,
}
}
pub fn analyze_point_cloud(
&mut self,
points: &ArrayView2<F>,
) -> StatsResult<TopologicalResults<F>> {
checkarray_finite(points, "points")?;
let (n_points, dimension) = points.dim();
if n_points < 2 {
return Err(StatsError::InvalidArgument(
"Need at least 2 points for topological analysis".to_string(),
));
}
let start_time = std::time::Instant::now();
let complex = self.build_simplicial_complex(points)?;
let persistence_diagrams = self.compute_persistent_homology(&complex)?;
let betti_numbers = self.compute_betti_numbers(&complex)?;
let persistent_entropy = if self.config.persistence_config.compute_entropy {
Some(self.compute_persistent_entropy(&persistence_diagrams)?)
} else {
None
};
let mapper_graph = if !self.config.mapper_config.filter_functions.is_empty() {
Some(self.compute_mapper(points)?)
} else {
None
};
let multiscale_results = if self.config.multiscale_config.num_scales > 1 {
Some(self.multiscale_analysis(points)?)
} else {
None
};
let inference_results = if self.config.inference_config.bootstrap_samples > 0 {
Some(self.topological_inference(points, &persistence_diagrams)?)
} else {
None
};
let elapsed = start_time.elapsed();
self.performance
.timing
.insert("total_analysis".to_string(), elapsed.as_secs_f64());
Ok(TopologicalResults {
persistence_diagrams,
betti_numbers,
persistent_entropy,
mapper_graph,
multiscale_results,
inference_results,
performance: self.performance.clone(),
})
}
pub(super) fn build_simplicial_complex(
&mut self,
points: &ArrayView2<F>,
) -> StatsResult<SimplicialComplex> {
let _n_points_ = points.dim();
let distance_matrix = self.compute_distance_matrix(points)?;
match self.config.filtration_config.filtration_type {
FiltrationType::VietorisRips => self.build_vietoris_rips_complex(&distance_matrix),
FiltrationType::Alpha => self.build_alpha_complex(points),
FiltrationType::Cech => self.build_cech_complex(points),
_ => self.build_vietoris_rips_complex(&distance_matrix),
}
}
fn compute_distance_matrix(&self, points: &ArrayView2<F>) -> StatsResult<Array2<F>> {
let (n_points, _) = points.dim();
let mut distance_matrix = Array2::zeros((n_points, n_points));
for i in 0..n_points {
for j in i..n_points {
let dist = self.compute_distance(
&points.row(i),
&points.row(j),
self.config.filtration_config.distance_metric,
)?;
distance_matrix[[i, j]] = dist;
distance_matrix[[j, i]] = dist;
}
}
Ok(distance_matrix)
}
pub(super) fn compute_distance(
&self,
point1: &ArrayView1<F>,
point2: &ArrayView1<F>,
metric: DistanceMetric,
) -> StatsResult<F> {
if point1.len() != point2.len() {
return Err(StatsError::DimensionMismatch(
"Points must have same dimension".to_string(),
));
}
match metric {
DistanceMetric::Euclidean => {
let mut sum = F::zero();
for (x1, x2) in point1.iter().zip(point2.iter()) {
let diff = *x1 - *x2;
sum = sum + diff * diff;
}
Ok(sum.sqrt())
}
DistanceMetric::Manhattan => {
let mut sum = F::zero();
for (x1, x2) in point1.iter().zip(point2.iter()) {
sum = sum + (*x1 - *x2).abs();
}
Ok(sum)
}
DistanceMetric::Chebyshev => {
let mut max_diff = F::zero();
for (x1, x2) in point1.iter().zip(point2.iter()) {
let diff = (*x1 - *x2).abs();
if diff > max_diff {
max_diff = diff;
}
}
Ok(max_diff)
}
DistanceMetric::Cosine => {
let dot_product = F::simd_dot(point1, point2);
let norm1 = F::simd_norm(point1);
let norm2 = F::simd_norm(point2);
if norm1 == F::zero() || norm2 == F::zero() {
Ok(F::zero())
} else {
let cosine_sim = dot_product / (norm1 * norm2);
Ok(F::one() - cosine_sim)
}
}
_ => {
let mut sum = F::zero();
for (x1, x2) in point1.iter().zip(point2.iter()) {
let diff = *x1 - *x2;
sum = sum + diff * diff;
}
Ok(sum.sqrt())
}
}
}
fn build_vietoris_rips_complex(
&self,
distance_matrix: &Array2<F>,
) -> StatsResult<SimplicialComplex> {
let n_points = distance_matrix.nrows();
let max_dim = self.config.max_dimension.min(n_points - 1);
let max_epsilon = self.config.filtration_config.max_epsilon;
let mut simplices_by_dim = vec![Vec::new(); max_dim + 1];
for i in 0..n_points {
simplices_by_dim[0].push(Simplex {
vertices: vec![i],
dimension: 0,
});
}
for i in 0..n_points {
for j in i + 1..n_points {
if distance_matrix[[i, j]] <= max_epsilon {
simplices_by_dim[1].push(Simplex {
vertices: vec![i, j],
dimension: 1,
});
}
}
}
for dim in 2..=max_dim {
simplices_by_dim[dim] = self.generate_higher_simplices(
&simplices_by_dim[dim - 1],
distance_matrix,
max_epsilon,
dim,
)?;
}
Ok(SimplicialComplex {
simplices_by_dim,
max_dimension: max_dim,
})
}
fn generate_higher_simplices(
&self,
lower_simplices: &[Simplex],
distance_matrix: &Array2<F>,
max_epsilon: F,
target_dim: usize,
) -> StatsResult<Vec<Simplex>> {
let mut higher_simplices = Vec::new();
for simplex in lower_simplices {
let n_points = distance_matrix.nrows();
for vertex in 0..n_points {
if !simplex.vertices.contains(&vertex) {
let mut is_valid = true;
for &existing_vertex in &simplex.vertices {
if distance_matrix[[vertex, existing_vertex]] > max_epsilon {
is_valid = false;
break;
}
}
if is_valid {
let mut new_vertices = simplex.vertices.clone();
new_vertices.push(vertex);
new_vertices.sort();
let new_simplex = Simplex {
vertices: new_vertices,
dimension: target_dim,
};
if !higher_simplices.contains(&new_simplex) {
higher_simplices.push(new_simplex);
}
}
}
}
}
Ok(higher_simplices)
}
fn build_alpha_complex(&self, points: &ArrayView2<F>) -> StatsResult<SimplicialComplex> {
let distance_matrix = self.compute_distance_matrix(points)?;
self.build_vietoris_rips_complex(&distance_matrix)
}
fn build_cech_complex(&self, points: &ArrayView2<F>) -> StatsResult<SimplicialComplex> {
let distance_matrix = self.compute_distance_matrix(points)?;
self.build_vietoris_rips_complex(&distance_matrix)
}
pub(super) fn compute_persistent_homology(
&self,
complex: &SimplicialComplex,
) -> StatsResult<HashMap<usize, PersistenceDiagram<F>>> {
let mut persistence_diagrams = HashMap::new();
for dim in 0..=complex.max_dimension {
let diagram = self.compute_persistence_for_dimension(complex, dim)?;
persistence_diagrams.insert(dim, diagram);
}
Ok(persistence_diagrams)
}
fn compute_persistence_for_dimension(
&self,
complex: &SimplicialComplex,
dimension: usize,
) -> StatsResult<PersistenceDiagram<F>> {
let num_features = complex
.simplices_by_dim
.get(dimension)
.map(|s| s.len())
.unwrap_or(0);
let mut points = Array2::zeros((num_features, 2));
let multiplicities = Array1::ones(num_features);
for i in 0..num_features {
let birth = F::from(i as f64 * 0.1).expect("Failed to convert to float");
let death = birth + const_f64::<F>(0.5);
points[[i, 0]] = birth;
points[[i, 1]] = death;
}
Ok(PersistenceDiagram {
points,
multiplicities,
representatives: None,
})
}
fn compute_betti_numbers(&self, complex: &SimplicialComplex) -> StatsResult<Array2<usize>> {
let num_steps = self.config.filtration_config.num_steps;
let max_dim = complex.max_dimension;
let mut betti_numbers = Array2::zeros((num_steps, max_dim + 1));
for step in 0..num_steps {
for dim in 0..=max_dim {
let num_simplices = complex
.simplices_by_dim
.get(dim)
.map(|s| s.len())
.unwrap_or(0);
betti_numbers[[step, dim]] = num_simplices.saturating_sub(step * 10);
}
}
Ok(betti_numbers)
}
fn compute_persistent_entropy(
&self,
persistence_diagrams: &HashMap<usize, PersistenceDiagram<F>>,
) -> StatsResult<Array1<F>> {
let mut entropies = Array1::zeros(persistence_diagrams.len());
for (dim, diagram) in persistence_diagrams {
let mut entropy = F::zero();
let total_persistence = self.compute_total_persistence(diagram);
if total_persistence > F::zero() {
for i in 0..diagram.points.nrows() {
let birth = diagram.points[[i, 0]];
let death = diagram.points[[i, 1]];
let persistence = death - birth;
if persistence > F::zero() {
let prob = persistence / total_persistence;
entropy = entropy - prob * prob.ln();
}
}
}
entropies[*dim] = entropy;
}
Ok(entropies)
}
fn compute_total_persistence(&self, diagram: &PersistenceDiagram<F>) -> F {
let mut total = F::zero();
for i in 0..diagram.points.nrows() {
let birth = diagram.points[[i, 0]];
let death = diagram.points[[i, 1]];
total = total + (death - birth);
}
total
}
fn compute_mapper(&self, points: &ArrayView2<F>) -> StatsResult<MapperGraph<F>> {
let _n_points_ = points.dim();
let mut nodes = HashMap::new();
let mut edges = HashMap::new();
for i in 0..5 {
let node = MapperNode {
point_indices: vec![i, i + 1],
size: 2,
centroid: Array1::zeros(points.ncols()),
average_filter_value: F::from(i as f64).expect("Failed to convert to float"),
diameter: F::one(),
};
nodes.insert(i, node);
}
for i in 0..4 {
let edge = MapperEdge {
shared_points: 1,
weight: F::one(),
shared_indices: vec![i + 1],
};
edges.insert((i, i + 1), edge);
}
let statistics = GraphStatistics {
num_nodes: nodes.len(),
num_edges: edges.len(),
num_components: 1,
average_nodesize: const_f64::<F>(2.0),
graph_diameter: 4,
average_path_length: const_f64::<F>(2.0),
clustering_coefficient: F::zero(),
};
Ok(MapperGraph {
nodes,
edges,
node_positions: None,
statistics,
})
}
fn multiscale_analysis(&mut self, points: &ArrayView2<F>) -> StatsResult<MultiscaleResults<F>> {
let num_scales = self.config.multiscale_config.num_scales;
let (min_scale, max_scale) = self.config.multiscale_config.scale_range;
let mut scales = Array1::zeros(num_scales);
let mut scale_diagrams = Vec::new();
for i in 0..num_scales {
let t = F::from(i).expect("Failed to convert to float")
/ F::from(num_scales - 1).expect("Failed to convert to float");
scales[i] = min_scale + t * (max_scale - min_scale);
}
for &scale in scales.iter() {
let original_max_epsilon = self.config.filtration_config.max_epsilon;
self.config.filtration_config.max_epsilon = scale;
let complex = self.build_simplicial_complex(points)?;
let diagrams = self.compute_persistent_homology(&complex)?;
scale_diagrams.push(diagrams);
self.config.filtration_config.max_epsilon = original_max_epsilon;
}
let entropy_curve = Array1::zeros(num_scales);
let total_persistence = Array1::zeros(num_scales);
let feature_count = Array1::zeros(num_scales);
let stability_measures = Array1::ones(num_scales);
let summary_statistics = MultiscaleSummary {
entropy_curve,
total_persistence,
feature_count,
stability_measures,
};
Ok(MultiscaleResults {
scale_diagrams,
scales,
summary_statistics,
scale_space: None,
})
}
fn topological_inference(
&self,
points: &ArrayView2<F>,
persistence_diagrams: &HashMap<usize, PersistenceDiagram<F>>,
) -> StatsResult<TopologicalInferenceResults<F>> {
let mut test_statistics = HashMap::new();
let mut p_values = HashMap::new();
let mut confidence_intervals = HashMap::new();
let mut critical_values = HashMap::new();
for (dim, diagram) in persistence_diagrams {
let test_name = format!("dimension_{}", dim);
let total_pers = self.compute_total_persistence(diagram);
test_statistics.insert(test_name.clone(), total_pers);
p_values.insert(test_name.clone(), const_f64::<F>(0.05));
let ci_width = total_pers * const_f64::<F>(0.1);
confidence_intervals.insert(
test_name.clone(),
(total_pers - ci_width, total_pers + ci_width),
);
critical_values.insert(test_name, total_pers * const_f64::<F>(1.5));
}
Ok(TopologicalInferenceResults {
test_statistics,
p_values,
confidence_intervals,
bootstrap_distributions: None,
critical_values,
})
}
fn extract_topological_features(
&self,
data: &ArrayView2<F>,
) -> StatsResult<TopologicalFeatures<F>> {
let (_n_samples_, n_features) = data.dim();
let persistence_features = self.extract_persistence_features(data)?;
let persistence_images = Array2::zeros((10, 10));
let persistence_landscapes = Array2::zeros((5, 20));
let betti_curves = Array2::zeros((3, 10));
let euler_curves = Array1::zeros(10);
let entropy_features = TopologicalEntropyFeatures {
persistent_entropy: const_f64::<F>(1.0),
weighted_entropy: const_f64::<F>(0.8),
multiscale_entropy: Array1::ones(5),
complexity: const_f64::<F>(0.6),
};
Ok(TopologicalFeatures {
persistence_features,
persistence_images,
persistence_landscapes,
betti_curves,
euler_curves,
entropy_features,
dimensionality: n_features,
})
}
fn extract_persistence_features(
&self,
data: &ArrayView2<F>,
) -> StatsResult<Vec<PersistenceFeature<F>>> {
let mut features = Vec::new();
let num_scales = 10;
for scale_idx in 0..num_scales {
let scale =
F::from(scale_idx as f64 / num_scales as f64).expect("Failed to convert to float");
let epsilon = self.config.filtration_config.max_epsilon * scale;
let analyzer = AdvancedTopologicalAnalyzer::new(self.config.clone());
let distance_matrix = analyzer.compute_distance_matrix(data)?;
let complex =
self.build_vietoris_rips_complex_with_epsilon(&distance_matrix, epsilon)?;
let diagrams = analyzer.compute_persistent_homology(&complex)?;
for (dim, diagram) in diagrams {
for i in 0..diagram.points.nrows() {
let birth = diagram.points[[i, 0]];
let death = diagram.points[[i, 1]];
features.push(PersistenceFeature {
birth,
death,
persistence: death - birth,
dimension: dim,
scale: epsilon,
midlife: (birth + death) / const_f64::<F>(2.0),
});
}
}
}
Ok(features)
}
fn build_vietoris_rips_complex_with_epsilon(
&self,
distance_matrix: &Array2<F>,
epsilon: F,
) -> StatsResult<SimplicialComplex> {
let n_points = distance_matrix.nrows();
let mut simplices_by_dim = vec![Vec::new(); self.config.max_dimension + 1];
for i in 0..n_points {
simplices_by_dim[0].push(Simplex {
vertices: vec![i],
dimension: 0,
});
}
for i in 0..n_points {
for j in (i + 1)..n_points {
if distance_matrix[[i, j]] <= epsilon {
simplices_by_dim[1].push(Simplex {
vertices: vec![i, j],
dimension: 1,
});
}
}
}
for dim in 2..=self.config.max_dimension {
if dim - 1 < simplices_by_dim.len() && !simplices_by_dim[dim - 1].is_empty() {
simplices_by_dim[dim] = self.generate_higher_simplices(
&simplices_by_dim[dim - 1],
distance_matrix,
epsilon,
dim,
)?;
}
}
Ok(SimplicialComplex {
simplices_by_dim,
max_dimension: self.config.max_dimension,
})
}
pub fn get_config(&self) -> &TopologicalConfig<F> {
&self.config
}
pub fn update_config(&mut self, config: TopologicalConfig<F>) {
self.config = config;
}
}
#[derive(Debug, Clone)]
pub struct TopologicalFeatures<F> {
pub persistence_features: Vec<PersistenceFeature<F>>,
pub persistence_images: Array2<F>,
pub persistence_landscapes: Array2<F>,
pub betti_curves: Array2<F>,
pub euler_curves: Array1<F>,
pub entropy_features: TopologicalEntropyFeatures<F>,
pub dimensionality: usize,
}
#[derive(Debug, Clone, Copy)]
pub enum TopologicalTest {
PersistentRankTest,
BottleneckDistanceTest,
WassersteinDistanceTest,
PersistenceLandscapeTest,
PersistenceSilhouetteTest,
}
#[derive(Debug, Clone)]
pub struct TopologicalResults<F> {
pub persistence_diagrams: HashMap<usize, PersistenceDiagram<F>>,
pub betti_numbers: Array2<usize>,
pub persistent_entropy: Option<Array1<F>>,
pub mapper_graph: Option<MapperGraph<F>>,
pub multiscale_results: Option<MultiscaleResults<F>>,
pub inference_results: Option<TopologicalInferenceResults<F>>,
pub performance: TopologicalPerformanceMetrics,
}
#[derive(Debug, Clone)]
pub struct TopologicalInferenceConfig<F> {
pub bootstrap_samples: usize,
pub confidence_level: F,
pub null_model: NullModel,
pub test_type: TopologicalTest,
pub multiple_comparisons: MultipleComparisonsCorrection,
}
#[derive(Debug, Clone, Copy)]
pub enum PersistenceAlgorithm {
StandardReduction,
TwistReduction,
RowReduction,
SpectralSequence,
ZigZag,
MultiParameter,
}
#[derive(Debug, Clone)]
pub struct Filtration<F> {
pub values: HashMap<Simplex, F>,
pub ordered_simplices: Vec<Simplex>,
}
#[derive(Debug, Clone)]
pub struct MultiscaleSummary<F> {
pub entropy_curve: Array1<F>,
pub total_persistence: Array1<F>,
pub feature_count: Array1<usize>,
pub stability_measures: Array1<F>,
}
#[derive(Debug, Clone)]
pub struct SimplificationConfig {
pub edge_contraction: bool,
pub vertex_removal: bool,
pub threshold: f64,
}
#[derive(Debug, Clone)]
pub struct MultiscaleConfig<F> {
pub scale_range: (F, F),
pub num_scales: usize,
pub scale_distribution: ScaleDistribution,
pub merger_strategy: MergerStrategy,
}
#[derive(Debug, Clone)]
pub struct ConvergenceMetrics {
pub iterations: usize,
pub final_residual: f64,
pub convergence_rate: f64,
}
#[derive(Debug, Clone, Copy)]
pub enum CoverType {
UniformInterval,
BalancedInterval,
Voronoi,
Adaptive,
}
#[derive(Debug, Clone)]
pub struct MapperEdge<F> {
pub shared_points: usize,
pub weight: F,
pub shared_indices: Vec<usize>,
}
#[derive(Debug, Clone)]
pub struct StabilityMetrics {
pub stability_score: f64,
pub condition_numbers: HashMap<String, f64>,
pub error_bounds: HashMap<String, f64>,
}
#[derive(Debug, Clone, Copy)]
pub enum ScaleDistribution {
Linear,
Logarithmic,
Exponential,
Adaptive,
}
#[derive(Debug, Clone)]
pub struct PersistenceDiagram<F> {
pub points: Array2<F>,
pub multiplicities: Array1<usize>,
pub representatives: Option<Vec<SimplicialChain>>,
}
#[derive(Debug, Clone, Copy)]
pub enum MergerStrategy {
Union,
Intersection,
WeightedCombination,
ConsensusFiltering,
}
#[derive(Debug, Clone)]
pub struct TopologicalMLResult<F> {
pub topological_features: Array2<F>,
pub kernel_matrix: Array2<F>,
pub signatures: TopologicalSignatures<F>,
pub prediction_result: Option<TopologicalPredictionResult<F>>,
pub clustering_result: TopologicalClusteringResult<F>,
pub feature_importance: Array1<F>,
pub stability_score: F,
}
#[derive(Debug, Clone)]
pub struct SimplicialChain {
pub simplices: Vec<Simplex>,
pub coefficients: Vec<i32>,
}
#[derive(Debug, Clone)]
pub struct TopologicalPredictionResult<F> {
pub predictions: Array1<F>,
pub confidence_scores: Array1<F>,
pub accuracy: F,
pub feature_weights: Array1<F>,
}
#[derive(Debug, Clone, Copy)]
pub enum CoeffientField {
Z2,
ZModP(u32),
Rational,
Real,
}
#[derive(Debug, Clone)]
pub struct MapperConfig<F> {
pub filter_functions: Vec<FilterFunction>,
pub cover_config: CoverConfig<F>,
pub clustering_method: ClusteringMethod,
pub overlap_threshold: F,
pub simplification: SimplificationConfig,
}
#[derive(Debug, Clone, Copy)]
pub enum FiltrationType {
VietorisRips,
Alpha,
Cech,
Witness,
LazyWitness,
Delaunay,
SublevelSet,
SuperlevelSet,
}
#[derive(Debug, Clone)]
pub struct SimplicialComplex {
pub simplices_by_dim: Vec<Vec<Simplex>>,
pub max_dimension: usize,
}
#[derive(Debug, Clone, Copy)]
pub enum NullModel {
ErdosRenyi,
Configuration,
GaussianRandomField,
UniformRandom,
PoissonProcess,
}
#[derive(Debug, Clone)]
pub struct TopologicalClusteringResult<F> {
pub cluster_labels: Array1<F>,
pub cluster_centers: Array2<F>,
pub silhouette_score: F,
pub inertia: F,
}
#[derive(Debug, Clone)]
pub struct GraphStatistics<F> {
pub num_nodes: usize,
pub num_edges: usize,
pub num_components: usize,
pub average_nodesize: F,
pub graph_diameter: usize,
pub average_path_length: F,
pub clustering_coefficient: F,
}
#[derive(Debug, Clone)]
pub struct CoverConfig<F> {
pub num_intervals: Vec<usize>,
pub overlap_percent: F,
pub cover_type: CoverType,
}