Files
wifi-densepose/vendor/ruvector/examples/prime-radiant/tests/spectral_tests.rs

296 lines
8.2 KiB
Rust

//! Integration tests for the Spectral Invariants module
use prime_radiant::spectral::{
Graph, SparseMatrix, SpectralAnalyzer, SpectralGap, Vector,
CheegerAnalyzer, CheegerBounds, cheeger_inequality,
SpectralClusterer, ClusterAssignment, ClusterConfig,
CollapsePredictor, CollapsePrediction, Warning, WarningLevel,
spectral_coherence_energy, SpectralEnergy, EnergyMinimizer,
LanczosAlgorithm, PowerIteration,
NodeId, EPS,
};
// ============================================================================
// Graph Construction Helpers
// ============================================================================
fn create_path_graph(n: usize) -> Graph {
let edges: Vec<(usize, usize, f64)> = (0..n - 1)
.map(|i| (i, i + 1, 1.0))
.collect();
Graph::from_edges(n, &edges)
}
fn create_cycle_graph(n: usize) -> Graph {
let mut edges: Vec<(usize, usize, f64)> = (0..n - 1)
.map(|i| (i, i + 1, 1.0))
.collect();
edges.push((n - 1, 0, 1.0));
Graph::from_edges(n, &edges)
}
fn create_complete_graph(n: usize) -> Graph {
let mut edges = Vec::new();
for i in 0..n {
for j in i + 1..n {
edges.push((i, j, 1.0));
}
}
Graph::from_edges(n, &edges)
}
fn create_barbell_graph(clique_size: usize) -> Graph {
let n = 2 * clique_size;
let mut g = Graph::new(n);
// First clique
for i in 0..clique_size {
for j in i + 1..clique_size {
g.add_edge(i, j, 1.0);
}
}
// Second clique
for i in clique_size..n {
for j in i + 1..n {
g.add_edge(i, j, 1.0);
}
}
// Bridge
g.add_edge(clique_size - 1, clique_size, 1.0);
g
}
fn create_star_graph(n: usize) -> Graph {
let edges: Vec<(usize, usize, f64)> = (1..n)
.map(|i| (0, i, 1.0))
.collect();
Graph::from_edges(n, &edges)
}
// ============================================================================
// Graph and SparseMatrix Tests
// ============================================================================
#[test]
fn test_graph_construction() {
let g = create_complete_graph(5);
assert_eq!(g.n, 5);
assert_eq!(g.num_edges(), 10);
assert!(g.is_connected());
assert_eq!(g.num_components(), 1);
}
#[test]
fn test_graph_degrees() {
let g = create_complete_graph(5);
let degrees = g.degrees();
for &d in &degrees {
assert!((d - 4.0).abs() < EPS);
}
}
#[test]
fn test_disconnected_graph() {
let g = Graph::from_edges(6, &[
(0, 1, 1.0), (1, 2, 1.0), (2, 0, 1.0),
(3, 4, 1.0), (4, 5, 1.0), (5, 3, 1.0),
]);
assert!(!g.is_connected());
assert_eq!(g.num_components(), 2);
}
#[test]
fn test_laplacian_properties() {
let g = create_complete_graph(4);
let l = g.laplacian();
for i in 0..4 {
let row_sum: f64 = (0..4).map(|j| l.get(i, j)).sum();
assert!(row_sum.abs() < EPS, "Row sum should be zero");
}
}
// ============================================================================
// Spectral Analyzer Tests
// ============================================================================
#[test]
fn test_spectral_analyzer_basic() {
let g = create_cycle_graph(6);
let mut analyzer = SpectralAnalyzer::new(g);
analyzer.compute_laplacian_spectrum();
assert!(!analyzer.eigenvalues.is_empty());
assert!(analyzer.eigenvalues[0].abs() < 0.01);
}
#[test]
fn test_algebraic_connectivity() {
let complete = create_complete_graph(10);
let path = create_path_graph(10);
let mut analyzer_complete = SpectralAnalyzer::new(complete);
let mut analyzer_path = SpectralAnalyzer::new(path);
analyzer_complete.compute_laplacian_spectrum();
analyzer_path.compute_laplacian_spectrum();
let ac_complete = analyzer_complete.algebraic_connectivity();
let ac_path = analyzer_path.algebraic_connectivity();
assert!(ac_complete > ac_path);
assert!(ac_complete > 0.0);
assert!(ac_path > 0.0);
}
#[test]
fn test_fiedler_vector() {
let g = create_barbell_graph(4);
let mut analyzer = SpectralAnalyzer::new(g);
analyzer.compute_laplacian_spectrum();
let fiedler = analyzer.fiedler_vector();
assert!(fiedler.is_some());
assert_eq!(fiedler.unwrap().len(), 8);
}
#[test]
fn test_bottleneck_detection() {
let g = create_barbell_graph(5);
let mut analyzer = SpectralAnalyzer::new(g);
analyzer.compute_laplacian_spectrum();
let bottlenecks = analyzer.detect_bottlenecks();
assert!(!bottlenecks.is_empty());
let has_bridge = bottlenecks.iter().any(|b| {
b.crossing_edges.contains(&(4, 5))
});
assert!(has_bridge, "Bridge edge should be in bottleneck");
}
// ============================================================================
// Cheeger Analyzer Tests
// ============================================================================
#[test]
fn test_cheeger_bounds() {
let g = create_complete_graph(10);
let mut analyzer = CheegerAnalyzer::new(&g);
let bounds = analyzer.compute_cheeger_bounds();
assert!(bounds.lower_bound >= 0.0);
assert!(bounds.lower_bound <= bounds.cheeger_constant);
assert!(bounds.cheeger_constant <= bounds.upper_bound);
}
#[test]
fn test_cheeger_well_connected() {
let g = create_complete_graph(10);
let mut analyzer = CheegerAnalyzer::new(&g);
let bounds = analyzer.compute_cheeger_bounds();
assert!(bounds.is_well_connected());
}
// ============================================================================
// Spectral Clustering Tests
// ============================================================================
#[test]
fn test_spectral_clustering_two_clusters() {
let g = create_barbell_graph(5);
let clusterer = SpectralClusterer::new(2);
let assignment = clusterer.cluster(&g);
assert_eq!(assignment.k, 2);
assert_eq!(assignment.labels.len(), 10);
assert!(assignment.quality.modularity > 0.0);
}
// ============================================================================
// Collapse Predictor Tests
// ============================================================================
#[test]
fn test_collapse_predictor_stable() {
let g = create_complete_graph(10);
let predictor = CollapsePredictor::new();
let prediction = predictor.predict_collapse(&g);
assert!(prediction.risk_score < 0.5);
}
#[test]
fn test_warning_levels() {
assert_eq!(WarningLevel::None.severity(), 0);
assert_eq!(WarningLevel::Critical.severity(), 4);
assert_eq!(WarningLevel::from_severity(2), WarningLevel::Medium);
}
// ============================================================================
// Spectral Energy Tests
// ============================================================================
#[test]
fn test_spectral_energy_basic() {
let g = create_complete_graph(10);
let energy = spectral_coherence_energy(&g);
assert!(energy.laplacian_energy > 0.0);
assert!(energy.coherence_energy > 0.0);
assert!(energy.stability_score >= 0.0 && energy.stability_score <= 1.0);
}
#[test]
fn test_spectral_energy_comparison() {
let complete = create_complete_graph(10);
let path = create_path_graph(10);
let energy_complete = spectral_coherence_energy(&complete);
let energy_path = spectral_coherence_energy(&path);
assert!(energy_complete.coherence_energy > energy_path.coherence_energy);
}
// ============================================================================
// Lanczos Algorithm Tests
// ============================================================================
#[test]
fn test_power_iteration() {
let g = create_complete_graph(5);
let l = g.laplacian();
let power = PowerIteration::default();
let (lambda, v) = power.largest_eigenvalue(&l);
let av = l.mul_vec(&v);
let error: f64 = av.iter()
.zip(v.iter())
.map(|(avi, vi)| (avi - lambda * vi).powi(2))
.sum::<f64>()
.sqrt();
assert!(error < 0.1, "Eigenvalue error: {}", error);
}
#[test]
fn test_lanczos_algorithm() {
let g = create_cycle_graph(8);
let l = g.laplacian();
let lanczos = LanczosAlgorithm::new(5);
let (eigenvalues, eigenvectors) = lanczos.compute_smallest(&l);
assert!(!eigenvalues.is_empty());
assert!(eigenvalues[0].abs() < 0.01);
}