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

703 lines
25 KiB
Rust

//! Comprehensive tests for Sheaf Cohomology Module
//!
//! This test suite verifies the mathematical properties of sheaf cohomology
//! including coboundary operators, cohomology groups, and obstruction detection.
use prime_radiant::cohomology::{
CohomologyEngine, CohomologyResult, SheafGraph, SheafNode, SheafEdge,
Obstruction, BeliefGraphBuilder, CohomologyError,
};
use proptest::prelude::*;
use approx::assert_relative_eq;
use std::collections::HashMap;
// =============================================================================
// COBOUNDARY OPERATOR TESTS
// =============================================================================
mod coboundary_tests {
use super::*;
/// Test the fundamental property: delta^2 = 0
/// The coboundary of a coboundary is always zero
#[test]
fn test_coboundary_squared_is_zero() {
// Create a triangle graph (simplest complex with non-trivial cohomology)
let mut graph = SheafGraph::new();
// Add 3 nodes forming a triangle
graph.add_node(SheafNode::new(0, "A", vec![1.0, 0.0, 0.0]));
graph.add_node(SheafNode::new(1, "B", vec![0.0, 1.0, 0.0]));
graph.add_node(SheafNode::new(2, "C", vec![0.0, 0.0, 1.0]));
// Add edges with identity restriction maps
graph.add_edge(SheafEdge::identity(0, 1, 3)).unwrap();
graph.add_edge(SheafEdge::identity(1, 2, 3)).unwrap();
graph.add_edge(SheafEdge::identity(2, 0, 3)).unwrap();
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
// The consistency energy should be computable
assert!(result.consistency_energy >= 0.0);
}
/// Test coboundary on exact sequences
#[test]
fn test_coboundary_on_consistent_sections() {
let mut graph = SheafGraph::new();
// Create nodes with identical sections (globally consistent)
let section = vec![1.0, 2.0, 3.0];
graph.add_node(SheafNode::new(0, "A", section.clone()));
graph.add_node(SheafNode::new(1, "B", section.clone()));
graph.add_node(SheafNode::new(2, "C", section.clone()));
graph.add_edge(SheafEdge::identity(0, 1, 3)).unwrap();
graph.add_edge(SheafEdge::identity(1, 2, 3)).unwrap();
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
// Globally consistent sections should have zero consistency energy
assert!(result.is_consistent);
assert!(result.consistency_energy < 1e-10);
}
/// Test coboundary with non-trivial restriction maps
#[test]
fn test_coboundary_with_projection_maps() {
let mut graph = SheafGraph::new();
// Higher-dimensional source, lower-dimensional target
graph.add_node(SheafNode::new(0, "High", vec![1.0, 2.0, 3.0, 4.0]));
graph.add_node(SheafNode::new(1, "Low", vec![1.0, 2.0]));
// Projection map: takes first 2 components
let projection = vec![
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
];
let edge = SheafEdge::with_map(0, 1, projection, 4, 2);
graph.add_edge(edge).unwrap();
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
// Should be consistent since projection matches
assert!(result.is_consistent);
}
/// Test coboundary linearity: delta(af + bg) = a*delta(f) + b*delta(g)
#[test]
fn test_coboundary_linearity() {
let mut graph1 = SheafGraph::new();
let mut graph2 = SheafGraph::new();
let mut graph_sum = SheafGraph::new();
// Graph 1
graph1.add_node(SheafNode::new(0, "A", vec![1.0, 0.0]));
graph1.add_node(SheafNode::new(1, "B", vec![0.0, 0.0]));
graph1.add_edge(SheafEdge::identity(0, 1, 2)).unwrap();
// Graph 2
graph2.add_node(SheafNode::new(0, "A", vec![0.0, 1.0]));
graph2.add_node(SheafNode::new(1, "B", vec![0.0, 0.0]));
graph2.add_edge(SheafEdge::identity(0, 1, 2)).unwrap();
// Sum graph
graph_sum.add_node(SheafNode::new(0, "A", vec![1.0, 1.0]));
graph_sum.add_node(SheafNode::new(1, "B", vec![0.0, 0.0]));
graph_sum.add_edge(SheafEdge::identity(0, 1, 2)).unwrap();
let engine = CohomologyEngine::new();
let e1 = engine.compute_cohomology(&graph1).unwrap().consistency_energy;
let e2 = engine.compute_cohomology(&graph2).unwrap().consistency_energy;
let e_sum = engine.compute_cohomology(&graph_sum).unwrap().consistency_energy;
// Energy is quadratic, so E(sum) <= E1 + E2 + 2*sqrt(E1*E2)
// But should satisfy triangle inequality for sqrt(energy)
let sqrt_sum = e_sum.sqrt();
let sqrt_bound = e1.sqrt() + e2.sqrt();
assert!(sqrt_sum <= sqrt_bound + 1e-10);
}
}
// =============================================================================
// COHOMOLOGY GROUP TESTS
// =============================================================================
mod cohomology_group_tests {
use super::*;
/// Test H^0 computation (global sections)
#[test]
fn test_h0_connected_graph() {
let mut graph = SheafGraph::new();
// Create a path graph: A -- B -- C
let section = vec![1.0, 2.0];
graph.add_node(SheafNode::new(0, "A", section.clone()));
graph.add_node(SheafNode::new(1, "B", section.clone()));
graph.add_node(SheafNode::new(2, "C", section.clone()));
graph.add_edge(SheafEdge::identity(0, 1, 2)).unwrap();
graph.add_edge(SheafEdge::identity(1, 2, 2)).unwrap();
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
// For consistent sections, H^0 dimension should be positive
assert!(result.h0_dim > 0);
}
/// Test H^0 on disconnected components
#[test]
fn test_h0_disconnected_graph() {
let mut graph = SheafGraph::new();
// Two disconnected nodes
graph.add_node(SheafNode::new(0, "A", vec![1.0, 0.0]));
graph.add_node(SheafNode::new(1, "B", vec![0.0, 1.0]));
// No edges - disconnected
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
// Disconnected components each contribute to H^0
// With no edges, no consistency constraints
assert!(result.is_consistent);
}
/// Test H^1 detection (obstruction group)
#[test]
fn test_h1_obstruction_detection() {
let mut graph = SheafGraph::new();
// Create inconsistent triangle
graph.add_node(SheafNode::new(0, "A", vec![1.0, 0.0]));
graph.add_node(SheafNode::new(1, "B", vec![0.0, 1.0]));
graph.add_node(SheafNode::new(2, "C", vec![1.0, 1.0]));
graph.add_edge(SheafEdge::identity(0, 1, 2)).unwrap();
graph.add_edge(SheafEdge::identity(1, 2, 2)).unwrap();
graph.add_edge(SheafEdge::identity(2, 0, 2)).unwrap();
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
// Should detect inconsistency
assert!(!result.is_consistent);
assert!(result.consistency_energy > 0.0);
}
/// Test Euler characteristic: chi = dim(H^0) - dim(H^1)
#[test]
fn test_euler_characteristic() {
let mut graph = SheafGraph::new();
// Simple path graph
let section = vec![1.0];
for i in 0..5 {
graph.add_node(SheafNode::new(i, &format!("N{}", i), section.clone()));
}
for i in 0..4 {
graph.add_edge(SheafEdge::identity(i, i + 1, 1)).unwrap();
}
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
// Euler characteristic should be computed correctly
let computed_chi = result.h0_dim as i64 - result.h1_dim as i64;
assert_eq!(computed_chi, result.euler_characteristic);
}
/// Test cohomology with scalar sections
#[test]
fn test_scalar_cohomology() {
let mut graph = SheafGraph::new();
// Simple graph with scalar (1D) sections
graph.add_node(SheafNode::new(0, "A", vec![1.0]));
graph.add_node(SheafNode::new(1, "B", vec![2.0]));
graph.add_edge(SheafEdge::identity(0, 1, 1)).unwrap();
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
// Inconsistent scalars
assert!(!result.is_consistent);
assert_relative_eq!(result.consistency_energy, 1.0, epsilon = 1e-10);
}
}
// =============================================================================
// OBSTRUCTION DETECTION TESTS
// =============================================================================
mod obstruction_detection_tests {
use super::*;
/// Test obstruction detection on known inconsistent graph
#[test]
fn test_detect_single_obstruction() {
let mut graph = SheafGraph::new();
graph.add_node(SheafNode::new(0, "Source", vec![1.0, 2.0, 3.0]));
graph.add_node(SheafNode::new(1, "Target", vec![4.0, 5.0, 6.0]));
graph.add_edge(SheafEdge::identity(0, 1, 3)).unwrap();
let engine = CohomologyEngine::new();
let obstructions = engine.detect_obstructions(&graph).unwrap();
assert_eq!(obstructions.len(), 1);
let obs = &obstructions[0];
assert_eq!(obs.source_node, 0);
assert_eq!(obs.target_node, 1);
// Expected obstruction vector: [1-4, 2-5, 3-6] = [-3, -3, -3]
assert_relative_eq!(obs.obstruction_vector[0], -3.0, epsilon = 1e-10);
assert_relative_eq!(obs.obstruction_vector[1], -3.0, epsilon = 1e-10);
assert_relative_eq!(obs.obstruction_vector[2], -3.0, epsilon = 1e-10);
// Magnitude should be sqrt(27) = 3*sqrt(3)
let expected_magnitude = (27.0_f64).sqrt();
assert_relative_eq!(obs.magnitude, expected_magnitude, epsilon = 1e-10);
}
/// Test obstruction detection on fully consistent graph
#[test]
fn test_no_obstructions_when_consistent() {
let mut graph = SheafGraph::new();
let section = vec![1.0, 2.0];
graph.add_node(SheafNode::new(0, "A", section.clone()));
graph.add_node(SheafNode::new(1, "B", section.clone()));
graph.add_node(SheafNode::new(2, "C", section.clone()));
graph.add_edge(SheafEdge::identity(0, 1, 2)).unwrap();
graph.add_edge(SheafEdge::identity(1, 2, 2)).unwrap();
let engine = CohomologyEngine::new();
let obstructions = engine.detect_obstructions(&graph).unwrap();
assert!(obstructions.is_empty());
}
/// Test obstruction ordering by magnitude
#[test]
fn test_obstructions_ordered_by_magnitude() {
let mut graph = SheafGraph::new();
graph.add_node(SheafNode::new(0, "A", vec![0.0]));
graph.add_node(SheafNode::new(1, "B", vec![1.0])); // Small diff
graph.add_node(SheafNode::new(2, "C", vec![10.0])); // Large diff
graph.add_edge(SheafEdge::identity(0, 1, 1)).unwrap();
graph.add_edge(SheafEdge::identity(0, 2, 1)).unwrap();
let engine = CohomologyEngine::new();
let obstructions = engine.detect_obstructions(&graph).unwrap();
assert_eq!(obstructions.len(), 2);
// Should be sorted by magnitude (descending)
assert!(obstructions[0].magnitude >= obstructions[1].magnitude);
}
/// Test obstruction detection with weighted nodes
#[test]
fn test_obstructions_with_weights() {
let mut graph = SheafGraph::new();
let node1 = SheafNode::new(0, "HighWeight", vec![1.0]).with_weight(10.0);
let node2 = SheafNode::new(1, "LowWeight", vec![2.0]).with_weight(0.1);
graph.add_node(node1);
graph.add_node(node2);
graph.add_edge(SheafEdge::identity(0, 1, 1)).unwrap();
let engine = CohomologyEngine::new();
let obstructions = engine.detect_obstructions(&graph).unwrap();
assert_eq!(obstructions.len(), 1);
assert_relative_eq!(obstructions[0].magnitude, 1.0, epsilon = 1e-10);
}
/// Test obstruction localization
#[test]
fn test_obstruction_localization() {
let mut graph = SheafGraph::new();
// Create a longer path with obstruction in middle
graph.add_node(SheafNode::new(0, "A", vec![1.0]));
graph.add_node(SheafNode::new(1, "B", vec![1.0]));
graph.add_node(SheafNode::new(2, "C", vec![5.0])); // Jump here
graph.add_node(SheafNode::new(3, "D", vec![5.0]));
graph.add_edge(SheafEdge::identity(0, 1, 1)).unwrap();
graph.add_edge(SheafEdge::identity(1, 2, 1)).unwrap();
graph.add_edge(SheafEdge::identity(2, 3, 1)).unwrap();
let engine = CohomologyEngine::new();
let obstructions = engine.detect_obstructions(&graph).unwrap();
// Only edge 1->2 should have obstruction
assert_eq!(obstructions.len(), 1);
assert_eq!(obstructions[0].source_node, 1);
assert_eq!(obstructions[0].target_node, 2);
}
}
// =============================================================================
// GLOBAL SECTIONS AND REPAIR TESTS
// =============================================================================
mod global_sections_tests {
use super::*;
/// Test computation of global sections
#[test]
fn test_compute_global_sections() {
let mut graph = SheafGraph::new();
let section = vec![1.0, 2.0, 3.0];
graph.add_node(SheafNode::new(0, "A", section.clone()));
graph.add_node(SheafNode::new(1, "B", section.clone()));
graph.add_node(SheafNode::new(2, "C", section.clone()));
graph.add_edge(SheafEdge::identity(0, 1, 3)).unwrap();
graph.add_edge(SheafEdge::identity(1, 2, 3)).unwrap();
let engine = CohomologyEngine::new();
let global_sections = engine.compute_global_sections(&graph).unwrap();
assert!(!global_sections.is_empty());
// Should approximate the common section
let gs = &global_sections[0];
assert_eq!(gs.len(), 3);
}
/// Test section repair
#[test]
fn test_repair_sections() {
let mut graph = SheafGraph::new();
// Slightly inconsistent sections
graph.add_node(SheafNode::new(0, "A", vec![1.0, 2.0]));
graph.add_node(SheafNode::new(1, "B", vec![1.1, 2.1]));
graph.add_edge(SheafEdge::identity(0, 1, 2)).unwrap();
let engine = CohomologyEngine::new();
let initial_energy = engine.compute_cohomology(&graph).unwrap().consistency_energy;
// Repair should reduce energy
let _adjustment = engine.repair_sections(&mut graph).unwrap();
let final_energy = engine.compute_cohomology(&graph).unwrap().consistency_energy;
assert!(final_energy <= initial_energy);
}
/// Test repair convergence
#[test]
fn test_repair_convergence() {
let mut graph = SheafGraph::new();
// Create a cycle with small inconsistency
graph.add_node(SheafNode::new(0, "A", vec![1.0]));
graph.add_node(SheafNode::new(1, "B", vec![1.1]));
graph.add_node(SheafNode::new(2, "C", vec![0.9]));
graph.add_edge(SheafEdge::identity(0, 1, 1)).unwrap();
graph.add_edge(SheafEdge::identity(1, 2, 1)).unwrap();
graph.add_edge(SheafEdge::identity(2, 0, 1)).unwrap();
let engine = CohomologyEngine::with_tolerance(1e-8);
// Multiple repair iterations should converge
for _ in 0..5 {
engine.repair_sections(&mut graph).unwrap();
}
let final_result = engine.compute_cohomology(&graph).unwrap();
// Should have reduced energy significantly
assert!(final_result.consistency_energy < 0.1);
}
}
// =============================================================================
// BELIEF GRAPH BUILDER TESTS
// =============================================================================
mod belief_graph_builder_tests {
use super::*;
/// Test building graph from beliefs
#[test]
fn test_build_from_beliefs() {
let builder = BeliefGraphBuilder::new(3);
let beliefs = vec![
("Belief1".to_string(), vec![1.0, 0.0, 0.0]),
("Belief2".to_string(), vec![0.0, 1.0, 0.0]),
("Belief3".to_string(), vec![0.0, 0.0, 1.0]),
];
let connections = vec![(0, 1), (1, 2)];
let graph = builder.build_from_beliefs(&beliefs, &connections).unwrap();
assert_eq!(graph.node_count(), 3);
assert_eq!(graph.edge_count(), 2);
}
/// Test builder with mixed dimensions
#[test]
fn test_builder_mixed_dimensions() {
let builder = BeliefGraphBuilder::new(4);
let beliefs = vec![
("Low".to_string(), vec![1.0, 2.0]),
("High".to_string(), vec![1.0, 2.0, 3.0, 4.0]),
];
let connections = vec![(0, 1)];
let graph = builder.build_from_beliefs(&beliefs, &connections).unwrap();
let engine = CohomologyEngine::new();
// Should handle dimension mismatch gracefully
let _result = engine.compute_cohomology(&graph).unwrap();
}
}
// =============================================================================
// EDGE CASES AND ERROR HANDLING
// =============================================================================
mod edge_cases_tests {
use super::*;
/// Test empty graph
#[test]
fn test_empty_graph_cohomology() {
let graph = SheafGraph::new();
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
assert_eq!(result.h0_dim, 0);
assert_eq!(result.h1_dim, 0);
assert!(result.is_consistent);
}
/// Test single node graph
#[test]
fn test_single_node_graph() {
let mut graph = SheafGraph::new();
graph.add_node(SheafNode::new(0, "Single", vec![1.0, 2.0, 3.0]));
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
assert!(result.is_consistent);
assert_eq!(result.consistency_energy, 0.0);
}
/// Test graph with zero-dimensional sections
#[test]
fn test_zero_dimensional_sections() {
let mut graph = SheafGraph::new();
graph.add_node(SheafNode::new(0, "Empty", vec![]));
graph.add_node(SheafNode::new(1, "Empty2", vec![]));
// This should still work, just with trivial cohomology
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
assert!(result.is_consistent);
}
/// Test invalid node reference in edge
#[test]
fn test_invalid_node_reference() {
let mut graph = SheafGraph::new();
graph.add_node(SheafNode::new(0, "Only", vec![1.0]));
// Edge to non-existent node
let result = graph.add_edge(SheafEdge::identity(0, 99, 1));
assert!(result.is_err());
}
/// Test large graph performance
#[test]
fn test_large_graph_performance() {
let mut graph = SheafGraph::new();
let n = 100;
// Create a path graph with n nodes
for i in 0..n {
graph.add_node(SheafNode::new(i, &format!("N{}", i), vec![i as f64]));
}
for i in 0..(n - 1) {
graph.add_edge(SheafEdge::identity(i, i + 1, 1)).unwrap();
}
let engine = CohomologyEngine::new();
let start = std::time::Instant::now();
let result = engine.compute_cohomology(&graph).unwrap();
let duration = start.elapsed();
// Should complete in reasonable time
assert!(duration.as_secs() < 5);
assert!(result.h0_dim > 0 || result.h1_dim > 0);
}
/// Test numerical stability with very small values
#[test]
fn test_numerical_stability_small_values() {
let mut graph = SheafGraph::new();
graph.add_node(SheafNode::new(0, "A", vec![1e-15, 1e-15]));
graph.add_node(SheafNode::new(1, "B", vec![1e-15, 1e-15]));
graph.add_edge(SheafEdge::identity(0, 1, 2)).unwrap();
let engine = CohomologyEngine::with_tolerance(1e-20);
let result = engine.compute_cohomology(&graph).unwrap();
// Should be consistent despite small values
assert!(result.is_consistent);
}
/// Test numerical stability with large values
#[test]
fn test_numerical_stability_large_values() {
let mut graph = SheafGraph::new();
graph.add_node(SheafNode::new(0, "A", vec![1e15, 1e15]));
graph.add_node(SheafNode::new(1, "B", vec![1e15, 1e15]));
graph.add_edge(SheafEdge::identity(0, 1, 2)).unwrap();
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
assert!(result.is_consistent);
}
}
// =============================================================================
// PROPERTY-BASED TESTS (using proptest)
// =============================================================================
mod property_tests {
use super::*;
proptest! {
/// Property: Consistent sections always have zero energy
#[test]
fn prop_consistent_sections_zero_energy(
values in proptest::collection::vec(-100.0..100.0f64, 1..10)
) {
let mut graph = SheafGraph::new();
let dim = values.len();
graph.add_node(SheafNode::new(0, "A", values.clone()));
graph.add_node(SheafNode::new(1, "B", values.clone()));
graph.add_edge(SheafEdge::identity(0, 1, dim)).unwrap();
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
prop_assert!(result.is_consistent);
prop_assert!(result.consistency_energy < 1e-10);
}
/// Property: Energy is always non-negative
#[test]
fn prop_energy_non_negative(
v1 in proptest::collection::vec(-100.0..100.0f64, 1..5),
v2 in proptest::collection::vec(-100.0..100.0f64, 1..5)
) {
if v1.len() != v2.len() {
return Ok(());
}
let mut graph = SheafGraph::new();
graph.add_node(SheafNode::new(0, "A", v1.clone()));
graph.add_node(SheafNode::new(1, "B", v2.clone()));
graph.add_edge(SheafEdge::identity(0, 1, v1.len())).unwrap();
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
prop_assert!(result.consistency_energy >= 0.0);
}
/// Property: Obstruction magnitudes match energy contribution
#[test]
fn prop_obstruction_magnitude_matches_energy(
diff in proptest::collection::vec(-10.0..10.0f64, 1..5)
) {
let mut graph = SheafGraph::new();
let base: Vec<f64> = vec![0.0; diff.len()];
let target: Vec<f64> = diff.clone();
graph.add_node(SheafNode::new(0, "A", base));
graph.add_node(SheafNode::new(1, "B", target));
graph.add_edge(SheafEdge::identity(0, 1, diff.len())).unwrap();
let engine = CohomologyEngine::new();
let obstructions = engine.detect_obstructions(&graph).unwrap();
if !obstructions.is_empty() {
let expected_magnitude: f64 = diff.iter().map(|x| x * x).sum::<f64>().sqrt();
prop_assert!((obstructions[0].magnitude - expected_magnitude).abs() < 1e-10);
}
}
/// Property: Adding consistent edge doesn't change consistency
#[test]
fn prop_consistent_edge_preserves_consistency(
section in proptest::collection::vec(-100.0..100.0f64, 1..5)
) {
let mut graph = SheafGraph::new();
graph.add_node(SheafNode::new(0, "A", section.clone()));
graph.add_node(SheafNode::new(1, "B", section.clone()));
graph.add_node(SheafNode::new(2, "C", section.clone()));
graph.add_edge(SheafEdge::identity(0, 1, section.len())).unwrap();
let engine = CohomologyEngine::new();
let before = engine.compute_cohomology(&graph).unwrap();
graph.add_edge(SheafEdge::identity(1, 2, section.len())).unwrap();
let after = engine.compute_cohomology(&graph).unwrap();
prop_assert_eq!(before.is_consistent, after.is_consistent);
}
}
}
// =============================================================================
// SHEAF NEURAL NETWORK TESTS (if included in cohomology module)
// =============================================================================
mod sheaf_neural_network_tests {
use super::*;
/// Test that Laplacian energy is non-negative
#[test]
fn test_laplacian_energy_non_negative() {
let mut graph = SheafGraph::new();
graph.add_node(SheafNode::new(0, "A", vec![1.0, -1.0]));
graph.add_node(SheafNode::new(1, "B", vec![-1.0, 1.0]));
graph.add_edge(SheafEdge::identity(0, 1, 2)).unwrap();
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
assert!(result.consistency_energy >= 0.0);
}
}