use crate::*;
use approx::assert_abs_diff_eq;
use scirs2_core::ndarray::{array, s, Array2};
use scirs2_core::random::seq::SliceRandom;
use scirs2_core::random::thread_rng;
use std::f64::consts::PI;
fn create_test_data() -> (Array2<f64>, Array2<f64>) {
let n = 20;
let mut x_high = Array2::zeros((n, 4));
let mut x_low = Array2::zeros((n, 2));
for i in 0..n {
let angle = 2.0 * PI * i as f64 / n as f64;
let radius = 1.0;
x_high[(i, 0)] = radius * angle.cos();
x_high[(i, 1)] = radius * angle.sin();
x_high[(i, 2)] = 0.1 * (i as f64 / n as f64); x_high[(i, 3)] = 0.05 * ((i * 2) as f64 / n as f64);
x_low[(i, 0)] = radius * angle.cos();
x_low[(i, 1)] = radius * angle.sin();
}
(x_high, x_low)
}
#[test]
fn test_trustworthiness_perfect_embedding() {
let (x_high, x_low) = create_test_data();
let trust = trustworthiness(&x_high.view(), &x_low.view(), 3);
assert!(
trust > 0.7,
"Trustworthiness should be high for perfect circular embedding: {}",
trust
);
}
#[test]
fn test_continuity_perfect_embedding() {
let (x_high, x_low) = create_test_data();
let cont = continuity(&x_high.view(), &x_low.view(), 3);
assert!(
cont > 0.7,
"Continuity should be high for perfect circular embedding: {}",
cont
);
}
#[test]
fn test_neighborhood_hit_rate_perfect_embedding() {
let (x_high, x_low) = create_test_data();
let hit_rate = neighborhood_hit_rate(&x_high.view(), &x_low.view(), 3);
assert!(
hit_rate > 0.6,
"Neighborhood hit rate should be high for perfect circular embedding: {}",
hit_rate
);
}
#[test]
fn test_normalized_stress_perfect_embedding() {
let (x_high, x_low) = create_test_data();
let stress = normalized_stress(&x_high.view(), &x_low.view());
assert!(
stress < 0.5,
"Normalized stress should be low for good embedding: {}",
stress
);
}
#[test]
fn test_quality_report_generation() {
let (x_high, x_low) = create_test_data();
let report = quality_report(&x_high.view(), &x_low.view(), Some(3));
assert_eq!(report.k_neighbors, 3);
assert!(report.trustworthiness >= 0.0 && report.trustworthiness <= 1.0);
assert!(report.continuity >= 0.0 && report.continuity <= 1.0);
assert!(report.neighborhood_hit_rate >= 0.0 && report.neighborhood_hit_rate <= 1.0);
assert!(
report.local_continuity_meta_criterion >= 0.0
&& report.local_continuity_meta_criterion <= 1.0
);
assert!(report.normalized_stress >= 0.0);
assert!(report.mean_relative_rank_error >= 0.0);
let display_str = format!("{}", report);
assert!(display_str.contains("Manifold Embedding Quality Report"));
assert!(display_str.contains("Trustworthiness"));
assert!(display_str.contains("Continuity"));
}
#[test]
fn test_lcmc_harmonic_mean() {
let (x_high, x_low) = create_test_data();
let trust = trustworthiness(&x_high.view(), &x_low.view(), 3);
let cont = continuity(&x_high.view(), &x_low.view(), 3);
let lcmc = local_continuity_meta_criterion(&x_high.view(), &x_low.view(), 3);
let expected_lcmc = if trust + cont > 0.0 {
2.0 * trust * cont / (trust + cont)
} else {
0.0
};
assert_abs_diff_eq!(lcmc, expected_lcmc, epsilon = 1e-10);
}
#[test]
fn test_quality_metrics_bad_embedding() {
let (x_high, _) = create_test_data();
let mut rng = thread_rng();
let mut x_bad = x_high.clone();
let mut indices: Vec<usize> = (0..x_high.nrows()).collect();
indices.shuffle(&mut rng);
for (new_idx, &old_idx) in indices.iter().enumerate() {
x_bad.row_mut(new_idx).assign(&x_high.row(old_idx));
}
let x_bad_2d = x_bad.slice(s![.., 0..2]).to_owned();
let trust = trustworthiness(&x_high.view(), &x_bad_2d.view(), 3);
let cont = continuity(&x_high.view(), &x_bad_2d.view(), 3);
let hit_rate = neighborhood_hit_rate(&x_high.view(), &x_bad_2d.view(), 3);
assert!(
trust < 0.9,
"Bad embedding should have lower trustworthiness: {}",
trust
);
assert!(
cont < 0.9,
"Bad embedding should have lower continuity: {}",
cont
);
assert!(
hit_rate < 0.9,
"Bad embedding should have lower hit rate: {}",
hit_rate
);
}
#[test]
fn test_edge_cases() {
let x_small = array![[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]];
let x_small_2d = array![[1.0], [3.0], [5.0]];
let trust = trustworthiness(&x_small.view(), &x_small_2d.view(), 5);
assert_eq!(trust, 1.0, "Should return perfect score when k >= n");
let cont = continuity(&x_small.view(), &x_small_2d.view(), 5);
assert_eq!(cont, 1.0, "Should return perfect score when k >= n");
let hit_rate = neighborhood_hit_rate(&x_small.view(), &x_small_2d.view(), 5);
assert_eq!(hit_rate, 1.0, "Should return perfect score when k >= n");
}
#[test]
fn test_tsne_neighborhood_preservation() {
let x = array![
[1.0, 1.0, 0.0],
[1.1, 1.1, 0.1], [1.0, 1.0, 0.05], [5.0, 5.0, 0.0],
[5.1, 5.1, 0.1], [5.0, 5.0, 0.05], ];
let tsne = TSNE::new()
.n_components(2)
.perplexity(2.0)
.n_iter(500) .learning_rate(100.0) .random_state(Some(42));
let fitted = tsne.fit(&x.view(), &()).expect("operation should succeed");
let embedding = fitted
.transform(&x.view())
.expect("operation should succeed");
let report = quality_report(&x.view(), &embedding.view(), Some(2));
assert!(
report.trustworthiness > 0.15,
"t-SNE should preserve some local structure. Trustworthiness: {}",
report.trustworthiness
);
assert!(
report.continuity > 0.15,
"t-SNE should preserve some local structure. Continuity: {}",
report.continuity
);
}
#[test]
fn test_isomap_neighborhood_preservation() {
let x = array![
[0.0, 0.0],
[1.0, 0.0],
[2.0, 0.0],
[3.0, 0.0],
[0.0, 1.0],
[1.0, 1.0],
[2.0, 1.0],
[3.0, 1.0],
];
let isomap = Isomap::new().n_components(2).n_neighbors(3);
let fitted = isomap
.fit(&x.view(), &())
.expect("operation should succeed");
let embedding = fitted
.transform(&x.view())
.expect("operation should succeed");
let report = quality_report(&x.view(), &embedding.view(), Some(3));
assert!(
report.trustworthiness > 0.6,
"Isomap should preserve local structure well. Trustworthiness: {}",
report.trustworthiness
);
assert!(
report.neighborhood_hit_rate > 0.4,
"Isomap should preserve neighbors. Hit rate: {}",
report.neighborhood_hit_rate
);
}
#[test]
fn test_lle_neighborhood_preservation() {
let mut x = Array2::zeros((10, 2));
for i in 0..10 {
let t = i as f64 / 9.0;
x[(i, 0)] = t;
x[(i, 1)] = t * t; }
let lle = LocallyLinearEmbedding::new().n_components(1).n_neighbors(4);
let fitted = lle.fit(&x.view(), &()).expect("operation should succeed");
let embedding = fitted
.transform(&x.view())
.expect("operation should succeed");
let report = quality_report(&x.view(), &embedding.view(), Some(3));
assert!(
report.trustworthiness > 0.4,
"LLE should preserve local structure. Trustworthiness: {}",
report.trustworthiness
);
assert!(
report.continuity > 0.4,
"LLE should preserve local structure. Continuity: {}",
report.continuity
);
}
#[test]
fn test_umap_neighborhood_preservation() {
let x = array![
[1.0, 1.0],
[1.1, 1.0],
[1.0, 1.1],
[1.1, 1.1],
[5.0, 5.0],
[5.1, 5.0],
[5.0, 5.1],
[5.1, 5.1],
[1.0, 5.0],
[1.1, 5.0],
[1.0, 5.1],
[1.1, 5.1],
];
let umap = UMAP::new()
.n_components(2)
.n_neighbors(3)
.min_dist(0.1)
.n_epochs(Some(200)) .learning_rate(0.5) .random_state(Some(42));
let fitted = umap.fit(&x.view(), &()).expect("operation should succeed");
let embedding = fitted
.transform(&x.view())
.expect("operation should succeed");
let report = quality_report(&x.view(), &embedding.view(), Some(3));
assert!(
report.trustworthiness > -0.5,
"UMAP should preserve local structure better than completely random. Trustworthiness: {}",
report.trustworthiness
);
assert!(
report.neighborhood_hit_rate > 0.2,
"UMAP should preserve neighbors. Hit rate: {}",
report.neighborhood_hit_rate
);
assert!(
report.normalized_stress < 25.0, "UMAP should have reasonable global structure. Stress: {}",
report.normalized_stress
);
}
#[test]
fn test_mds_distance_preservation() {
let x = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
let mds = MDS::new().n_components(2);
let fitted = mds.fit(&x.view(), &()).expect("operation should succeed");
let embedding = fitted
.transform(&x.view())
.expect("operation should succeed");
let report = quality_report(&x.view(), &embedding.view(), Some(2));
assert!(
report.normalized_stress < 0.1,
"MDS should preserve distances well. Stress: {}",
report.normalized_stress
);
assert!(
report.mean_relative_rank_error < 0.3,
"MDS should preserve distance ranking. MRRE: {}",
report.mean_relative_rank_error
);
}
type AlgorithmFnVec = Vec<(&'static str, Box<dyn Fn() -> Array2<f64>>)>;
#[test]
fn test_all_algorithms_basic_preservation() {
let x = array![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[1.0, 1.0, 0.0],
[1.0, 0.0, 1.0]
];
let x_clone = x.clone();
let algorithms: AlgorithmFnVec = vec![(
"PCA",
Box::new(move || {
x_clone.slice(s![.., 0..2]).to_owned()
}),
)];
for (name, embedding_fn) in algorithms {
let embedding = embedding_fn();
let report = quality_report(&x.view(), &embedding.view(), Some(2));
println!("Algorithm: {}", name);
println!("{}", report);
assert!(
report.trustworthiness.is_finite(),
"{}: Trustworthiness should be finite",
name
);
assert!(
report.continuity.is_finite(),
"{}: Continuity should be finite",
name
);
assert!(
report.normalized_stress.is_finite(),
"{}: Stress should be finite",
name
);
assert!(
report.mean_relative_rank_error.is_finite(),
"{}: MRRE should be finite",
name
);
assert!(
report.trustworthiness >= 0.0 && report.trustworthiness <= 1.0,
"{}: Trustworthiness out of range [0,1]: {}",
name,
report.trustworthiness
);
assert!(
report.continuity >= 0.0 && report.continuity <= 1.0,
"{}: Continuity out of range [0,1]: {}",
name,
report.continuity
);
assert!(
report.neighborhood_hit_rate >= 0.0 && report.neighborhood_hit_rate <= 1.0,
"{}: Hit rate out of range [0,1]: {}",
name,
report.neighborhood_hit_rate
);
}
}