use scirs2_core::ndarray::{Array1, Array2, ArrayView2};
use sklears_core::error::Result as SklResult;
use std::collections::HashMap;
pub trait ManifoldLearning {
fn intrinsic_dimension(&self) -> Option<usize>;
fn embedding_dimension(&self) -> usize;
fn parameters(&self) -> HashMap<String, f64>;
fn algorithm_name(&self) -> &'static str;
fn supports_transform(&self) -> bool;
fn complexity(&self) -> ManifoldComplexity;
}
pub trait DistanceMetric {
fn pairwise_distances(
&self,
x: ArrayView2<f64>,
y: Option<ArrayView2<f64>>,
) -> SklResult<Array2<f64>>;
fn metric_name(&self) -> &'static str;
fn is_metric(&self) -> bool;
}
pub trait NeighborhoodBased {
fn n_neighbors(&self) -> usize;
fn set_n_neighbors(&mut self, n_neighbors: usize);
fn neighborhood_graph(&self) -> Option<Array2<f64>>;
}
pub trait RandomizedAlgorithm {
fn random_state(&self) -> Option<u64>;
fn set_random_state(&mut self, random_state: Option<u64>);
}
pub trait IterativeOptimization {
fn n_iter(&self) -> usize;
fn max_iter(&self) -> usize;
fn set_max_iter(&mut self, max_iter: usize);
fn tolerance(&self) -> f64;
fn set_tolerance(&mut self, tolerance: f64);
fn optimization_history(&self) -> Option<Vec<f64>>;
}
pub trait SpectralEmbedding {
fn eigenvalues(&self) -> Option<Array1<f64>>;
fn eigenvectors(&self) -> Option<Array2<f64>>;
fn n_components(&self) -> usize;
fn set_n_components(&mut self, n_components: usize);
}
pub trait KernelBased {
fn kernel_matrix(&self) -> Option<Array2<f64>>;
fn kernel_params(&self) -> HashMap<String, f64>;
fn kernel_transform(&self, x: ArrayView2<f64>) -> SklResult<Array2<f64>>;
}
pub trait ProbabilisticEmbedding {
fn high_dim_probabilities(&self) -> Option<Array2<f64>>;
fn low_dim_probabilities(&self) -> Option<Array2<f64>>;
fn perplexity(&self) -> f64;
fn set_perplexity(&mut self, perplexity: f64);
}
pub trait EmbeddingQuality {
fn trustworthiness(
&self,
x: ArrayView2<f64>,
x_embedded: ArrayView2<f64>,
k: usize,
) -> SklResult<f64>;
fn continuity(
&self,
x: ArrayView2<f64>,
x_embedded: ArrayView2<f64>,
k: usize,
) -> SklResult<f64>;
fn neighborhood_hit_rate(
&self,
x: ArrayView2<f64>,
x_embedded: ArrayView2<f64>,
k: usize,
) -> SklResult<f64>;
fn reconstruction_error(
&self,
x: ArrayView2<f64>,
x_embedded: ArrayView2<f64>,
) -> SklResult<f64>;
}
#[derive(Debug, Clone, PartialEq)]
pub enum ManifoldComplexity {
Linear,
Quadratic,
Cubic,
LogLinear,
Custom(String),
}
impl ManifoldComplexity {
pub fn description(&self) -> &str {
match self {
ManifoldComplexity::Linear => "O(n) - Linear time complexity",
ManifoldComplexity::Quadratic => "O(n²) - Quadratic time complexity",
ManifoldComplexity::Cubic => "O(n³) - Cubic time complexity",
ManifoldComplexity::LogLinear => "O(n log n) - Log-linear time complexity",
ManifoldComplexity::Custom(desc) => desc,
}
}
}
#[derive(Debug, Clone)]
pub struct ManifoldConfig {
pub n_components: usize,
pub n_neighbors: Option<usize>,
pub random_state: Option<u64>,
pub max_iter: Option<usize>,
pub tolerance: Option<f64>,
pub metric: Option<String>,
pub params: HashMap<String, f64>,
}
impl Default for ManifoldConfig {
fn default() -> Self {
Self {
n_components: 2,
n_neighbors: None,
random_state: None,
max_iter: None,
tolerance: None,
metric: None,
params: HashMap::new(),
}
}
}
impl ManifoldConfig {
pub fn new() -> Self {
Self::default()
}
pub fn n_components(mut self, n_components: usize) -> Self {
self.n_components = n_components;
self
}
pub fn n_neighbors(mut self, n_neighbors: usize) -> Self {
self.n_neighbors = Some(n_neighbors);
self
}
pub fn random_state(mut self, random_state: u64) -> Self {
self.random_state = Some(random_state);
self
}
pub fn max_iter(mut self, max_iter: usize) -> Self {
self.max_iter = Some(max_iter);
self
}
pub fn tolerance(mut self, tolerance: f64) -> Self {
self.tolerance = Some(tolerance);
self
}
pub fn metric(mut self, metric: impl Into<String>) -> Self {
self.metric = Some(metric.into());
self
}
pub fn param(mut self, key: impl Into<String>, value: f64) -> Self {
self.params.insert(key.into(), value);
self
}
}
pub struct ManifoldPresets;
impl ManifoldPresets {
pub fn fast_visualization() -> ManifoldConfig {
ManifoldConfig::new()
.n_components(2)
.max_iter(250)
.param("perplexity", 30.0)
.param("learning_rate", 200.0)
}
pub fn high_quality_visualization() -> ManifoldConfig {
ManifoldConfig::new()
.n_components(2)
.max_iter(1000)
.param("perplexity", 30.0)
.param("learning_rate", 200.0)
}
pub fn clustering_preprocessing() -> ManifoldConfig {
ManifoldConfig::new()
.n_components(50)
.metric("euclidean".to_string())
}
pub fn nonlinear_reduction() -> ManifoldConfig {
ManifoldConfig::new()
.n_neighbors(12)
.n_components(10)
.metric("euclidean".to_string())
}
pub fn local_structure() -> ManifoldConfig {
ManifoldConfig::new().n_neighbors(12).n_components(2)
}
pub fn global_structure() -> ManifoldConfig {
ManifoldConfig::new()
.n_components(2)
.metric("euclidean".to_string())
.max_iter(300)
}
}
pub trait ManifoldFactory {
type Algorithm: ManifoldLearning;
fn default() -> Self::Algorithm;
fn with_config(config: ManifoldConfig) -> Self::Algorithm;
fn with_preset(preset: fn() -> ManifoldConfig) -> Self::Algorithm {
Self::with_config(preset())
}
}
#[derive(Debug, Clone)]
pub struct ManifoldPipeline {
step_configs: Vec<(String, ManifoldConfig)>,
}
impl Default for ManifoldPipeline {
fn default() -> Self {
Self::new()
}
}
impl ManifoldPipeline {
pub fn new() -> Self {
Self {
step_configs: Vec::new(),
}
}
pub fn add_step(mut self, name: impl Into<String>, config: ManifoldConfig) -> Self {
self.step_configs.push((name.into(), config));
self
}
pub fn len(&self) -> usize {
self.step_configs.len()
}
pub fn is_empty(&self) -> bool {
self.step_configs.is_empty()
}
pub fn step_names(&self) -> Vec<&str> {
self.step_configs
.iter()
.map(|(name, _)| name.as_str())
.collect()
}
pub fn step_configs(&self) -> &[(String, ManifoldConfig)] {
&self.step_configs
}
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_manifold_config_builder() {
let config = ManifoldConfig::new()
.n_components(3)
.n_neighbors(10)
.random_state(42)
.max_iter(500)
.tolerance(1e-6)
.metric("euclidean")
.param("perplexity", 50.0);
assert_eq!(config.n_components, 3);
assert_eq!(config.n_neighbors, Some(10));
assert_eq!(config.random_state, Some(42));
assert_eq!(config.max_iter, Some(500));
assert_eq!(config.tolerance, Some(1e-6));
assert_eq!(config.metric, Some("euclidean".to_string()));
assert_eq!(config.params.get("perplexity"), Some(&50.0));
}
#[test]
fn test_manifold_presets() {
let fast_config = ManifoldPresets::fast_visualization();
assert_eq!(fast_config.n_components, 2);
assert_eq!(fast_config.max_iter, Some(250));
let quality_config = ManifoldPresets::high_quality_visualization();
assert_eq!(quality_config.n_components, 2);
assert_eq!(quality_config.max_iter, Some(1000));
let clustering_config = ManifoldPresets::clustering_preprocessing();
assert_eq!(clustering_config.n_components, 50);
let nonlinear_config = ManifoldPresets::nonlinear_reduction();
assert_eq!(nonlinear_config.n_neighbors, Some(12));
assert_eq!(nonlinear_config.n_components, 10);
let local_config = ManifoldPresets::local_structure();
assert_eq!(local_config.n_neighbors, Some(12));
assert_eq!(local_config.n_components, 2);
let global_config = ManifoldPresets::global_structure();
assert_eq!(global_config.n_components, 2);
assert_eq!(global_config.max_iter, Some(300));
}
#[test]
fn test_manifold_complexity() {
let linear = ManifoldComplexity::Linear;
assert_eq!(linear.description(), "O(n) - Linear time complexity");
let quadratic = ManifoldComplexity::Quadratic;
assert_eq!(quadratic.description(), "O(n²) - Quadratic time complexity");
let custom = ManifoldComplexity::Custom("O(n^1.5)".to_string());
assert_eq!(custom.description(), "O(n^1.5)");
}
#[test]
fn test_manifold_pipeline() {
let pipeline = ManifoldPipeline::new();
assert!(pipeline.is_empty());
assert_eq!(pipeline.len(), 0);
let config1 = ManifoldConfig::new().n_components(2);
let config2 = ManifoldConfig::new().n_components(10);
let pipeline = pipeline.add_step("tsne", config1).add_step("pca", config2);
assert!(!pipeline.is_empty());
assert_eq!(pipeline.len(), 2);
assert_eq!(pipeline.step_names(), vec!["tsne", "pca"]);
assert_eq!(pipeline.step_configs().len(), 2);
}
}