use crate::{
builder::ArrowSpaceBuilder,
maps::energymaps::{EnergyMaps, EnergyMapsBuilder, EnergyParams},
search::taumode::{TAU_FLOOR, TauMode},
tests::test_data::{make_gaussian_blob, make_moons_hd},
};
use approx::relative_eq;
use ordered_float::Float;
use smartcore::linalg::basic::arrays::Array;
use log::{debug, info};
#[test]
fn test_select_tau_fixed() {
let energies = vec![0.1, 0.5, 1.0];
assert_eq!(TauMode::select_tau(&energies, TauMode::Fixed(0.3)), 0.3);
assert_eq!(
TauMode::select_tau(&energies, TauMode::Fixed(-0.1)),
TAU_FLOOR
);
assert_eq!(
TauMode::select_tau(&energies, TauMode::Fixed(0.0)),
TAU_FLOOR
);
assert_eq!(
TauMode::select_tau(&energies, TauMode::Fixed(f64::NAN)),
TAU_FLOOR
);
assert_eq!(
TauMode::select_tau(&energies, TauMode::Fixed(f64::INFINITY)),
TAU_FLOOR
);
debug!("✓ Fixed tau mode validated");
}
#[test]
fn test_select_tau_mean() {
let energies = vec![1.0, 2.0, 3.0];
let expected_mean = 2.0;
assert!((TauMode::select_tau(&energies, TauMode::Mean) - expected_mean).abs() < 1e-12);
let energies_with_nan = vec![1.0, f64::NAN, 3.0, f64::INFINITY, 2.0];
let expected_filtered_mean = 2.0; assert!(
(TauMode::select_tau(&energies_with_nan, TauMode::Mean) - expected_filtered_mean).abs()
< 1e-12
);
let all_invalid = vec![f64::NAN, f64::INFINITY, f64::NEG_INFINITY];
assert_eq!(TauMode::select_tau(&all_invalid, TauMode::Mean), TAU_FLOOR);
let empty: Vec<f64> = vec![];
assert_eq!(TauMode::select_tau(&empty, TauMode::Mean), TAU_FLOOR);
debug!("✓ Mean tau mode validated");
}
#[test]
fn test_select_tau_median() {
let energies_odd = vec![3.0, 1.0, 2.0];
assert_eq!(TauMode::select_tau(&energies_odd, TauMode::Median), 2.0);
let energies_even = vec![1.0, 2.0, 3.0, 4.0];
let expected_median = 2.5; assert!((TauMode::select_tau(&energies_even, TauMode::Median) - expected_median).abs() < 1e-12);
let single = vec![5.0];
assert_eq!(TauMode::select_tau(&single, TauMode::Median), 5.0);
let with_invalid = vec![f64::NAN, 1.0, 3.0, f64::INFINITY, 2.0];
assert_eq!(TauMode::select_tau(&with_invalid, TauMode::Median), 2.0);
let all_invalid = vec![f64::NAN, f64::INFINITY];
assert_eq!(
TauMode::select_tau(&all_invalid, TauMode::Median),
TAU_FLOOR
);
let empty: Vec<f64> = vec![];
assert_eq!(TauMode::select_tau(&empty, TauMode::Median), TAU_FLOOR);
debug!("✓ Median tau mode validated");
}
#[test]
fn test_select_tau_percentile() {
let energies = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert_eq!(
TauMode::select_tau(&energies, TauMode::Percentile(0.0)),
1.0
);
assert_eq!(
TauMode::select_tau(&energies, TauMode::Percentile(1.0)),
5.0
);
assert_eq!(
TauMode::select_tau(&energies, TauMode::Percentile(0.5)),
3.0
);
assert_eq!(
TauMode::select_tau(&energies, TauMode::Percentile(-0.1)),
1.0
);
assert_eq!(
TauMode::select_tau(&energies, TauMode::Percentile(1.5)),
5.0
);
let empty: Vec<f64> = vec![];
assert_eq!(
TauMode::select_tau(&empty, TauMode::Percentile(0.5)),
TAU_FLOOR
);
debug!("✓ Percentile tau mode validated");
}
#[test]
fn test_select_tau_floor_enforcement() {
let small_positive = vec![TAU_FLOOR * 2.0];
assert_eq!(
TauMode::select_tau(&small_positive, TauMode::Mean),
TAU_FLOOR * 2.0
);
let below_floor = vec![TAU_FLOOR / 2.0];
assert_eq!(TauMode::select_tau(&below_floor, TauMode::Mean), TAU_FLOOR);
let zero = vec![0.0];
assert_eq!(TauMode::select_tau(&zero, TauMode::Mean), TAU_FLOOR);
debug!("✓ TAU_FLOOR enforcement validated");
}
#[test]
fn test_builder_compute_lambdas_basic() {
let items = make_moons_hd(100, 0.18, 0.4, 12, 42);
let (aspace, _gl) = ArrowSpaceBuilder::default()
.with_lambda_graph(0.3, 5, 2, 2.0, Some(0.1))
.with_normalisation(true)
.with_spectral(true) .with_synthesis(TauMode::Fixed(0.9))
.build(items);
let lambdas = aspace.lambdas();
debug!(
"Computed {} lambdas for {} clusters",
lambdas.len(),
aspace.n_clusters
);
assert!(
lambdas.iter().all(|&l| l.is_finite()),
"All lambdas should be finite"
);
assert!(
lambdas.iter().all(|&l| l >= 0.0),
"All lambdas should be non-negative"
);
assert!(
lambdas.iter().all(|&l| l <= 1.0),
"All lambdas should be <= 1.0"
);
debug!(
"✓ Lambda computation validated: min={:.6}, max={:.6}",
lambdas.iter().fold(f64::INFINITY, |a, &b| a.min(b)),
lambdas.iter().fold(0.0, |a, &b| a.max(b))
);
}
#[test]
fn test_builder_lambdas_different_tau_modes() {
let items = make_moons_hd(120, 0.16, 0.35, 15, 123);
let tau_modes = vec![
TauMode::Fixed(0.9),
TauMode::Mean,
TauMode::Median,
TauMode::Percentile(0.25),
];
let mut all_lambdas = Vec::new();
for tau_mode in &tau_modes {
let (aspace, _) = ArrowSpaceBuilder::default()
.with_lambda_graph(0.3, 5, 2, 2.0, Some(0.1))
.with_normalisation(true)
.with_spectral(false)
.with_synthesis(*tau_mode)
.build(items.clone());
let lambdas = aspace.lambdas().to_vec();
assert!(
lambdas
.iter()
.all(|&l| l.is_finite() && (0.0..=1.0).contains(&l)),
"TauMode {:?} produced invalid lambdas",
tau_mode
);
let lambda_mean = lambdas.iter().sum::<f64>() / lambdas.len() as f64;
debug!(
"TauMode {:?}: mean={:.6}, count={}",
tau_mode,
lambda_mean,
lambdas.len()
);
all_lambdas.push(lambdas);
}
let mut modes_differ = false;
for i in 1..all_lambdas.len() {
for j in 0..all_lambdas[i].len().min(all_lambdas[0].len()) {
if (all_lambdas[0][j] - all_lambdas[i][j]).abs() > 1e-10 {
modes_differ = true;
break;
}
}
if modes_differ {
break;
}
}
assert!(
modes_differ,
"Different tau modes should produce different lambda values"
);
debug!("✓ Different tau modes produce distinct lambda distributions");
}
#[test]
fn test_builder_lambdas_invariants() {
let items = make_gaussian_blob(500, 0.9);
let (aspace, _) = ArrowSpaceBuilder::default()
.with_lambda_graph(0.3, 6, 2, 2.0, Some(0.12))
.with_normalisation(false)
.with_spectral(true)
.with_synthesis(TauMode::Median)
.build(items);
let lambdas = aspace.lambdas();
for (i, &lambda) in lambdas.iter().enumerate() {
assert!(
lambda >= 0.0 && lambda <= 1.0,
"Lambda {} = {:.6} not in [0,1]",
i,
lambda
);
}
let lambda_mean = lambdas.iter().sum::<f64>() / lambdas.len() as f64;
let lambda_variance = lambdas
.iter()
.map(|&x| (x - lambda_mean).powi(2))
.sum::<f64>()
/ lambdas.len() as f64;
assert!(lambda_variance >= 0.0, "Variance must be non-negative");
if lambdas.len() > 1 {
let lambda_min = lambdas.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let lambda_max = lambdas.iter().fold(0.0, |a, &b| a.max(b));
assert!(lambda_max >= lambda_min, "Max should be >= min");
}
debug!("✓ Lambda invariants validated:");
debug!(" Clusters: {}", aspace.n_clusters);
debug!(" Mean: {:.6}", lambda_mean);
debug!(" Std: {:.6}", lambda_variance.sqrt());
}
#[test]
fn test_tau_floor_constant() {
assert!(TAU_FLOOR > 0.0, "TAU_FLOOR should be positive");
assert!(TAU_FLOOR < 1e-6, "TAU_FLOOR should be small");
assert!(TAU_FLOOR.is_finite(), "TAU_FLOOR should be finite");
debug!("TAU_FLOOR = {:.2e}", TAU_FLOOR);
debug!("✓ TAU_FLOOR constant validated");
}
#[test]
fn test_builder_lambdas_consistency_properties() {
let items = make_moons_hd(80, 0.15, 0.4, 11, 789);
debug!("=== LAMBDA CONSISTENCY TEST (Non-Deterministic Build) ===");
let (aspace1, _) = ArrowSpaceBuilder::default()
.with_lambda_graph(0.3, 5, 2, 2.0, None)
.with_normalisation(false)
.with_spectral(false)
.with_synthesis(TauMode::Median)
.build(items.clone());
let (aspace2, _) = ArrowSpaceBuilder::default()
.with_lambda_graph(0.3, 5, 2, 2.0, None)
.with_normalisation(false)
.with_spectral(false)
.with_synthesis(TauMode::Median)
.build(items.clone());
let lambdas1 = aspace1.lambdas();
let lambdas2 = aspace2.lambdas();
debug!(
"Build 1: {} clusters, {} lambdas",
aspace1.n_clusters,
lambdas1.len()
);
debug!(
"Build 2: {} clusters, {} lambdas",
aspace2.n_clusters,
lambdas2.len()
);
assert!(lambdas1.len() > 0, "Build 1 should produce lambdas");
assert!(lambdas2.len() > 0, "Build 2 should produce lambdas");
assert!(lambdas1.len() <= items.len(), "Build 1 clusters <= items");
assert!(lambdas2.len() <= items.len(), "Build 2 clusters <= items");
let compute_stats = |lambdas: &[f64]| -> (f64, f64, f64, f64) {
let mean = lambdas.iter().sum::<f64>() / lambdas.len() as f64;
let variance =
lambdas.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / lambdas.len() as f64;
let min = lambdas.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let max = lambdas.iter().fold(0.0, |a, &b| a.max(b));
(mean, variance, min, max)
};
let (mean1, var1, min1, max1) = compute_stats(lambdas1);
let (mean2, var2, min2, max2) = compute_stats(lambdas2);
debug!(
"Build 1 stats: mean={:.6}, var={:.6}, min={:.6}, max={:.6}",
mean1, var1, min1, max1
);
debug!(
"Build 2 stats: mean={:.6}, var={:.6}, min={:.6}, max={:.6}",
mean2, var2, min2, max2
);
assert!(
lambdas1
.iter()
.all(|&l| l.is_finite() && (0.0..=1.0).contains(&l)),
"Build 1 lambdas should be in [0,1]"
);
assert!(
lambdas2
.iter()
.all(|&l| l.is_finite() && (0.0..=1.0).contains(&l)),
"Build 2 lambdas should be in [0,1]"
);
assert!(max1 > min1, "Build 1 should have lambda variation");
assert!(max2 > min2, "Build 2 should have lambda variation");
assert!(var1 > 0.0, "Build 1 should have positive variance");
assert!(var2 > 0.0, "Build 2 should have positive variance");
let mean_ratio = mean1.max(mean2) / mean1.min(mean2);
assert!(
mean_ratio < 2.0,
"Means should be within 2x of each other: {:.6} vs {:.6} (ratio {:.2})",
mean1,
mean2,
mean_ratio
);
debug!("✓ Lambda computation produces consistent statistical properties");
debug!(" (Values are non-deterministic due to random clustering/projection)");
}
#[test]
fn test_builder_lambdas_nondeterministic_with_projection() {
let items = make_moons_hd(80, 0.15, 0.4, 120, 555);
debug!("=== LAMBDA NON-DETERMINISM TEST (Random Projection) ===");
let (aspace1, _) = ArrowSpaceBuilder::default()
.with_lambda_graph(0.1, 5, 2, 2.0, None)
.with_normalisation(true)
.with_spectral(true)
.with_synthesis(TauMode::Median)
.with_dims_reduction(true, Some(0.3)) .with_inline_sampling(None) .with_sparsity_check(false)
.build(items.clone());
let (aspace2, _) = ArrowSpaceBuilder::default()
.with_lambda_graph(0.1, 5, 2, 2.0, None)
.with_normalisation(true)
.with_spectral(true)
.with_synthesis(TauMode::Median)
.with_dims_reduction(true, Some(0.3)) .with_inline_sampling(None) .with_sparsity_check(false)
.build(items);
let lambdas1 = aspace1.lambdas();
let lambdas2 = aspace2.lambdas();
debug!(
"Build 1: {} clusters, reduced_dim={:?}",
aspace1.n_clusters, aspace1.reduced_dim
);
debug!(
"Build 2: {} clusters, reduced_dim={:?}",
aspace2.n_clusters, aspace2.reduced_dim
);
assert_eq!(
aspace1.reduced_dim, aspace2.reduced_dim,
"JL target dimension should be deterministic"
);
let mut values_differ = false;
let min_len = lambdas1.len().min(lambdas2.len());
for i in 0..min_len {
if (lambdas1[i] - lambdas2[i]).abs() > 1e-9 {
values_differ = true;
debug!(
"Lambda difference at cluster {}: {:.12} != {:.12}",
i, lambdas1[i], lambdas2[i]
);
break;
}
}
debug!(
r#"Random projection should cause lambda values to differ between builds: value differ {values_differ}"#
);
debug!("✓ Lambda computation IS non-deterministic with random projection enabled");
}
#[test]
#[allow(unused_mut)]
fn test_taumode_consistency_with_projection() {
crate::init();
let rows = crate::tests::test_data::make_moons_hd(99, 0.2, 0.08, 50, 42);
let mut builder = ArrowSpaceBuilder::new()
.with_seed(9999)
.with_lambda_graph(0.25, 5, 1, 2.0, None)
.with_dims_reduction(true, Some(0.3)) .with_synthesis(TauMode::Median);
let (aspace, gl) = builder.build(rows.clone());
let test_idx = 25;
let test_vector = rows[test_idx].clone();
info!(
"Original dim: {}, Reduced dim: {}",
test_vector.len(),
aspace.reduced_dim.unwrap_or(test_vector.len())
);
let lambda1 = aspace.prepare_query_item(&test_vector, &gl);
let lambda2 = aspace.prepare_query_item(&test_vector, &gl);
let lambda3 = aspace.prepare_query_item(&test_vector, &gl);
assert!(
relative_eq!(lambda1, lambda2, epsilon = 1e-12),
"TauMode should be deterministic: λ1={:.15} vs λ2={:.15}",
lambda1,
lambda2
);
assert!(
relative_eq!(lambda2, lambda3, epsilon = 1e-12),
"TauMode should be deterministic: λ2={:.15} vs λ3={:.15}",
lambda2,
lambda3
);
let indexed_lambda = aspace.lambdas[test_idx];
assert!(
relative_eq!(lambda1, indexed_lambda, epsilon = 1e-9),
"Query lambda should match indexed lambda: query={:.12} vs indexed={:.12}",
lambda1,
indexed_lambda
);
info!(
"✓ TauMode consistency verified: λ={:.12} (consistent across {} recomputations)",
lambda1, 3
);
info!(
"✓ Projection consistency: original_dim={}, reduced_dim={}, indexed_lambda={:.12}",
test_vector.len(),
aspace.reduced_dim.unwrap_or(test_vector.len()),
indexed_lambda
);
debug!("✓ TauMode is deterministic with dimensional reduction");
}
#[test]
fn test_taumode_energy_consistency_with_projection() {
crate::init();
use crate::maps::energymaps::{EnergyMapsBuilder, EnergyParams};
let rows = crate::tests::test_data::make_moons_hd(99, 0.2, 0.08, 50, 42);
let mut builder = ArrowSpaceBuilder::new()
.with_seed(9999)
.with_lambda_graph(0.25, 5, 1, 2.0, None)
.with_dims_reduction(true, Some(0.3)) .with_synthesis(TauMode::Median);
let p = EnergyParams::new(&builder);
let (aspace, gl) = builder.build_energy(rows.clone(), p);
let test_idx = 25;
let test_vector = rows[test_idx].clone();
info!(
"Original dim: {}, Reduced dim: {}",
test_vector.len(),
aspace.reduced_dim.unwrap_or(test_vector.len())
);
let lambda1 = aspace.prepare_query_item(&test_vector, &gl);
let lambda2 = aspace.prepare_query_item(&test_vector, &gl);
let lambda3 = aspace.prepare_query_item(&test_vector, &gl);
assert!(
relative_eq!(lambda1, lambda2, epsilon = 1e-12),
"TauMode should be deterministic: λ1={:.15} vs λ2={:.15}",
lambda1,
lambda2
);
assert!(
relative_eq!(lambda2, lambda3, epsilon = 1e-12),
"TauMode should be deterministic: λ2={:.15} vs λ3={:.15}",
lambda2,
lambda3
);
let indexed_lambda = aspace.lambdas[test_idx];
assert!(
relative_eq!(lambda1, indexed_lambda, epsilon = 1e-9),
"Query lambda should match indexed lambda: query={:.12} vs indexed={:.12}",
lambda1,
indexed_lambda
);
info!(
"✓ TauMode consistency verified: λ={:.12} (consistent across {} recomputations)",
lambda1, 3
);
info!(
"✓ Projection consistency: original_dim={}, reduced_dim={}, indexed_lambda={:.12}",
test_vector.len(),
aspace.reduced_dim.unwrap_or(test_vector.len()),
indexed_lambda
);
debug!("✓ TauMode is deterministic with dimensional reduction");
}
#[test]
fn test_rayleigh_quotient_scale_invariance() {
let matrix_data: Vec<Vec<f64>> = vec![vec![1.0, 0.5, 0.5, 1.0], vec![0.5, 1.0, 0.8, 0.75]];
let (aspace, gl) = ArrowSpaceBuilder::default().build(matrix_data);
let vector = vec![1.0, 2.0, 3.0, 1.0];
let scaled_vector = vec![2.0, 4.0, 6.0, 2.0];
let tau = 0.5; let lambda1 = TauMode::compute_synthetic_lambda(
&vector,
aspace.projection_matrix.clone(),
&gl.matrix,
tau,
);
let lambda2 = TauMode::compute_synthetic_lambda(
&scaled_vector,
aspace.projection_matrix.clone(),
&gl.matrix,
tau,
);
assert!(
relative_eq!(lambda1, lambda2, epsilon = 1e-10),
"Synthetic lambda should be scale-invariant: {:.12} vs {:.12}",
lambda1,
lambda2
);
debug!("✓ Synthetic lambda (and thus Rayleigh quotient) is scale-invariant");
}
#[test]
fn test_builder_lambdas_with_larger_dataset() {
let items = make_gaussian_blob(999, 0.75);
debug!(
"Dataset: {} items, {} dimensions",
items.len(),
items[0].len()
);
let (aspace, gl) = ArrowSpaceBuilder::default()
.with_lambda_graph(0.1, 6, 2, 2.0, Some(0.50))
.with_normalisation(false)
.with_spectral(false)
.with_synthesis(TauMode::Fixed(0.8))
.with_sparsity_check(false)
.build(items.clone());
debug!("Built index with {} clusters", aspace.n_clusters);
debug!("Graph has {} nodes", gl.nnodes);
let lambdas = aspace.lambdas();
let lambda_min = lambdas.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let lambda_max = lambdas.iter().fold(0.0, |a, &b| a.max(b));
let lambda_mean = lambdas.iter().sum::<f64>() / lambdas.len() as f64;
let lambda_variance = lambdas
.iter()
.map(|&x| (x - lambda_mean).powi(2))
.sum::<f64>()
/ lambdas.len() as f64;
debug!("\n=== LAMBDA STATISTICS ===");
debug!("Count: {}", lambdas.len());
debug!("Min: {:.6}", lambda_min);
debug!("Max: {:.6}", lambda_max);
debug!("Mean: {:.6}", lambda_mean);
debug!("Variance: {:.6}", lambda_variance);
debug!("Std Dev: {:.6}", lambda_variance.sqrt());
debug!("Range: {:.6}", lambda_max - lambda_min);
assert_eq!(lambdas.len(), aspace.nitems, "One lambda per cluster");
assert!(lambdas.iter().all(|&l| l.is_finite()), "All lambdas finite");
assert!(
lambdas.iter().all(|&l| (0.0..=1.0).contains(&l)),
"All lambdas in [0,1]"
);
debug!(
"If it was a non-random dataset, it should always have had variance across clusters: lambda_variance > 0.0 {}",
lambda_variance > 0.0,
);
assert!(
lambda_max >= lambda_min,
"Should have range in lambda values"
);
debug!("\n=== TAU MODE COMPARISON ===");
let tau_modes = vec![
TauMode::Fixed(0.45),
TauMode::Fixed(0.5),
TauMode::Fixed(0.6),
TauMode::Mean,
TauMode::Median,
];
for tau_mode in tau_modes {
let (test_aspace, _) = ArrowSpaceBuilder::default()
.with_lambda_graph(0.3, 6, 2, 2.0, Some(0.15))
.with_normalisation(false)
.with_spectral(true)
.with_synthesis(tau_mode)
.with_sparsity_check(false)
.build(items.clone());
let tau_lambdas = test_aspace.lambdas();
let tau_mean = tau_lambdas.iter().sum::<f64>() / tau_lambdas.len() as f64;
debug!("TauMode {:?} - Mean lambda: {:.6}", tau_mode, tau_mean);
assert!(tau_lambdas.iter().all(|&l| l.is_finite()), "Finite lambdas");
assert!(
tau_lambdas.iter().all(|&l| l >= 0.0 && l <= 1.0),
"Bounded lambdas"
);
}
debug!("\n✓ Realistic data lambda computation validated");
debug!("✓ All mathematical properties satisfied");
debug!("✓ Multiple tau modes tested successfully");
}
#[test]
fn test_energy_dimension_consistency() {
crate::init();
info!("Test: energy mode dimension consistency");
let rows = crate::tests::test_data::make_gaussian_hd(250, 0.6);
let mut builder = ArrowSpaceBuilder::new()
.with_seed(9999)
.with_lambda_graph(0.25, 5, 1, 2.0, None)
.with_dims_reduction(true, Some(0.1))
.with_synthesis(TauMode::Median);
let (aspace, gl_energy) = builder.build_energy(rows.clone(), EnergyParams::new(&builder));
assert_eq!(
aspace.nitems,
rows.len(),
"ArrowSpace should have {} items, got {}",
rows.len(),
aspace.nitems
);
if let Some(ref sub_centroids) = aspace.sub_centroids {
assert_eq!(
sub_centroids.shape().0,
gl_energy.nnodes,
"Sub_centroids ({} rows) must match energy graph ({} nodes)",
sub_centroids.shape().0,
gl_energy.nnodes
);
info!("✓ Sub_centroids: {} rows", sub_centroids.shape().0);
info!("✓ Energy graph: {} nodes", gl_energy.nnodes);
} else {
panic!("Energy mode should have sub_centroids stored");
}
assert_eq!(
aspace.lambdas.len(),
aspace.nitems,
"Lambdas length ({}) must match items ({})",
aspace.lambdas.len(),
aspace.nitems
);
if let Some(ref centroid_map) = aspace.centroid_map {
assert_eq!(
centroid_map.len(),
aspace.nitems,
"Centroid map length ({}) must match items ({})",
centroid_map.len(),
aspace.nitems
);
let num_subcentroids = aspace.sub_centroids.as_ref().unwrap().shape().0;
for (item_idx, ¢roid_idx) in centroid_map.iter().enumerate() {
assert!(
centroid_idx < num_subcentroids,
"Item {} mapped to invalid centroid {} (max: {})",
item_idx,
centroid_idx,
num_subcentroids - 1
);
}
info!(
"✓ Centroid map: {} items → {} sub_centroids",
centroid_map.len(),
num_subcentroids
);
} else {
panic!("Energy mode should have centroid_map");
}
if let Some(ref sc_lambdas) = aspace.subcentroid_lambdas {
let num_subcentroids = aspace.sub_centroids.as_ref().unwrap().shape().0;
assert_eq!(
sc_lambdas.len(),
num_subcentroids,
"Subcentroid lambdas ({}) must match sub_centroids ({})",
sc_lambdas.len(),
num_subcentroids
);
info!("✓ Subcentroid lambdas: {} values", sc_lambdas.len());
}
let query_idx = 42;
let query_item = aspace.get_item(query_idx);
let results = aspace.search_energy(&query_item.item, &gl_energy, 5);
assert!(!results.is_empty(), "Search should return results");
info!(
"✓ Search completed without panic: {} results",
results.len()
);
info!("✓ All dimension consistency checks passed");
}
#[test]
fn test_energy_lambda_computation_bounds() {
crate::init();
let rows = crate::tests::test_data::make_gaussian_hd(100, 0.6);
let mut builder = ArrowSpaceBuilder::new()
.with_seed(42)
.with_lambda_graph(0.3, 3, 1, 2.0, None)
.with_dims_reduction(true, Some(0.2));
let (aspace, _) = builder.build_energy(rows, EnergyParams::new(&builder));
assert_eq!(aspace.lambdas.len(), aspace.nitems);
for (i, &lambda) in aspace.lambdas.iter().enumerate() {
assert!(
lambda.is_finite(),
"Lambda[{}] is not finite: {}",
i,
lambda
);
assert!(lambda >= 0.0, "Lambda[{}] is negative: {}", i, lambda);
}
info!("✓ Lambda bounds check passed for {} items", aspace.nitems);
}