#![cfg(feature = "jtree")]
use std::collections::{HashMap, HashSet};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[derive(Clone, Debug, PartialEq)]
pub enum LazyLevel<T: Clone> {
Unmaterialized,
Materialized(T),
Dirty(T),
}
impl<T: Clone> LazyLevel<T> {
pub fn is_materialized(&self) -> bool {
matches!(self, LazyLevel::Materialized(_))
}
pub fn is_dirty(&self) -> bool {
matches!(self, LazyLevel::Dirty(_))
}
pub fn is_unmaterialized(&self) -> bool {
matches!(self, LazyLevel::Unmaterialized)
}
pub fn as_materialized(&self) -> Option<&T> {
match self {
LazyLevel::Materialized(data) => Some(data),
_ => None,
}
}
pub fn as_dirty(&self) -> Option<&T> {
match self {
LazyLevel::Dirty(data) => Some(data),
_ => None,
}
}
pub fn materialize(&mut self, data: T) {
*self = LazyLevel::Materialized(data);
}
pub fn mark_dirty(&mut self) {
if let LazyLevel::Materialized(data) = self {
*self = LazyLevel::Dirty(data.clone());
}
}
pub fn invalidate(&mut self) {
*self = LazyLevel::Unmaterialized;
}
}
#[derive(Clone, Debug)]
pub struct JTreeLevelData {
pub level: usize,
pub vertex_count: usize,
pub min_cut_value: f64,
pub computation_count: Arc<AtomicUsize>,
}
impl JTreeLevelData {
pub fn new(level: usize, vertices: usize) -> Self {
Self {
level,
vertex_count: vertices,
min_cut_value: vertices as f64 * 0.5,
computation_count: Arc::new(AtomicUsize::new(0)),
}
}
}
#[derive(Clone)]
pub struct BmsspJTreeLevel {
vertex_count: usize,
path_cache: HashMap<(u64, u64), f64>,
edges: HashMap<(u64, u64), f64>,
level: usize,
cache_hits: Arc<AtomicUsize>,
cache_misses: Arc<AtomicUsize>,
}
impl BmsspJTreeLevel {
pub fn new(vertex_count: usize, level: usize) -> Self {
Self {
vertex_count,
path_cache: HashMap::new(),
edges: HashMap::new(),
level,
cache_hits: Arc::new(AtomicUsize::new(0)),
cache_misses: Arc::new(AtomicUsize::new(0)),
}
}
pub fn add_edge(&mut self, source: u64, target: u64, weight: f64) {
let (u, v) = if source < target {
(source, target)
} else {
(target, source)
};
self.edges.insert((u, v), weight);
}
pub fn min_cut(&mut self, s: u64, t: u64) -> f64 {
let (u, v) = if s < t { (s, t) } else { (t, s) };
if let Some(&cached) = self.path_cache.get(&(u, v)) {
self.cache_hits.fetch_add(1, Ordering::Relaxed);
return cached;
}
self.cache_misses.fetch_add(1, Ordering::Relaxed);
let cut_value = self.compute_min_cut(u, v);
self.path_cache.insert((u, v), cut_value);
cut_value
}
pub fn multi_terminal_cut(&mut self, terminals: &[u64]) -> f64 {
if terminals.len() < 2 {
return f64::INFINITY;
}
let mut min_cut = f64::INFINITY;
for (i, &s) in terminals.iter().enumerate() {
for &t in terminals.iter().skip(i + 1) {
let cut = self.min_cut(s, t);
min_cut = min_cut.min(cut);
}
}
min_cut
}
pub fn invalidate_cache(&mut self, affected: &[u64]) {
let affected_set: HashSet<_> = affected.iter().copied().collect();
self.path_cache
.retain(|(u, v), _| !affected_set.contains(u) && !affected_set.contains(v));
}
pub fn clear_cache(&mut self) {
self.path_cache.clear();
}
pub fn cache_stats(&self) -> (usize, usize) {
(
self.cache_hits.load(Ordering::Relaxed),
self.cache_misses.load(Ordering::Relaxed),
)
}
fn compute_min_cut(&self, _s: u64, _t: u64) -> f64 {
self.edges
.values()
.copied()
.min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or(f64::INFINITY)
}
}
pub struct LazyJTreeHierarchy {
levels: Vec<LazyLevel<JTreeLevelData>>,
materialized: HashSet<usize>,
dirty: HashSet<usize>,
alpha: f64,
total_computations: AtomicUsize,
}
impl LazyJTreeHierarchy {
pub fn new(num_levels: usize, alpha: f64) -> Self {
let levels = (0..num_levels).map(|_| LazyLevel::Unmaterialized).collect();
Self {
levels,
materialized: HashSet::new(),
dirty: HashSet::new(),
alpha,
total_computations: AtomicUsize::new(0),
}
}
pub fn num_levels(&self) -> usize {
self.levels.len()
}
pub fn approximate_min_cut(&mut self) -> ApproximateCut {
if self.levels.is_empty() {
return ApproximateCut {
value: f64::INFINITY,
approximation_factor: f64::INFINITY,
level_used: 0,
};
}
let mut current_level = self.levels.len() - 1;
while current_level > 0 {
self.ensure_materialized(current_level);
if let Some(data) = self.levels[current_level].as_materialized() {
let approx_factor = self.alpha.powi((self.levels.len() - current_level) as i32);
if approx_factor < 2.0 {
return ApproximateCut {
value: data.min_cut_value,
approximation_factor: approx_factor,
level_used: current_level,
};
}
}
current_level -= 1;
}
self.ensure_materialized(0);
if let Some(data) = self.levels[0].as_materialized() {
ApproximateCut {
value: data.min_cut_value,
approximation_factor: 1.0,
level_used: 0,
}
} else {
ApproximateCut {
value: f64::INFINITY,
approximation_factor: f64::INFINITY,
level_used: 0,
}
}
}
pub fn approximate_min_cut_at_level(&mut self, level: usize) -> Option<f64> {
if level >= self.levels.len() {
return None;
}
self.ensure_materialized(level);
self.levels[level]
.as_materialized()
.map(|d| d.min_cut_value)
}
fn ensure_materialized(&mut self, level: usize) {
match &self.levels[level] {
LazyLevel::Unmaterialized => {
self.total_computations.fetch_add(1, Ordering::Relaxed);
let vertices = 100 / (level + 1); let data = JTreeLevelData::new(level, vertices);
self.levels[level] = LazyLevel::Materialized(data);
self.materialized.insert(level);
}
LazyLevel::Dirty(old_data) => {
self.total_computations.fetch_add(1, Ordering::Relaxed);
let mut new_data = old_data.clone();
new_data.computation_count.fetch_add(1, Ordering::Relaxed);
new_data.min_cut_value *= 0.95; self.levels[level] = LazyLevel::Materialized(new_data);
self.dirty.remove(&level);
}
LazyLevel::Materialized(_) => {
}
}
}
pub fn mark_dirty(&mut self, affected_levels: &[usize]) {
for &level in affected_levels {
if level < self.levels.len() && self.materialized.contains(&level) {
self.levels[level].mark_dirty();
self.dirty.insert(level);
}
}
}
pub fn is_materialized(&self, level: usize) -> bool {
level < self.levels.len() && self.materialized.contains(&level)
}
pub fn is_dirty(&self, level: usize) -> bool {
self.dirty.contains(&level)
}
pub fn total_computations(&self) -> usize {
self.total_computations.load(Ordering::Relaxed)
}
}
#[derive(Debug, Clone)]
pub struct ApproximateCut {
pub value: f64,
pub approximation_factor: f64,
pub level_used: usize,
}
pub struct TwoTierCoordinator {
jtree: LazyJTreeHierarchy,
exact_value: f64,
critical_threshold: f64,
max_approx_factor: f64,
cached_result: Option<CutResult>,
exact_queries: AtomicUsize,
approx_queries: AtomicUsize,
}
impl TwoTierCoordinator {
pub fn new(num_levels: usize, exact_value: f64, critical_threshold: f64) -> Self {
Self {
jtree: LazyJTreeHierarchy::new(num_levels, 1.5),
exact_value,
critical_threshold,
max_approx_factor: 2.0,
cached_result: None,
exact_queries: AtomicUsize::new(0),
approx_queries: AtomicUsize::new(0),
}
}
pub fn min_cut(&mut self, exact_required: bool) -> CutResult {
if let Some(cached) = &self.cached_result {
if !exact_required || cached.is_exact {
return cached.clone();
}
}
let approx = self.jtree.approximate_min_cut();
self.approx_queries.fetch_add(1, Ordering::Relaxed);
let should_escalate = exact_required
|| approx.value < self.critical_threshold
|| approx.approximation_factor > self.max_approx_factor;
let result = if should_escalate {
self.exact_queries.fetch_add(1, Ordering::Relaxed);
CutResult {
value: self.exact_value,
is_exact: true,
approximation_factor: 1.0,
tier_used: Tier::Exact,
}
} else {
CutResult {
value: approx.value,
is_exact: false,
approximation_factor: approx.approximation_factor,
tier_used: Tier::Approximate,
}
};
self.cached_result = Some(result.clone());
result
}
pub fn insert_edge(&mut self, _u: u64, _v: u64, _weight: f64) {
self.cached_result = None;
let all_levels: Vec<usize> = (0..self.jtree.num_levels()).collect();
self.jtree.mark_dirty(&all_levels);
}
pub fn delete_edge(&mut self, _u: u64, _v: u64) {
self.cached_result = None;
let all_levels: Vec<usize> = (0..self.jtree.num_levels()).collect();
self.jtree.mark_dirty(&all_levels);
}
pub fn query_stats(&self) -> (usize, usize) {
(
self.approx_queries.load(Ordering::Relaxed),
self.exact_queries.load(Ordering::Relaxed),
)
}
pub fn set_exact_value(&mut self, value: f64) {
self.exact_value = value;
}
}
#[derive(Debug, Clone)]
pub struct CutResult {
pub value: f64,
pub is_exact: bool,
pub approximation_factor: f64,
pub tier_used: Tier,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Tier {
Approximate,
Exact,
}
mod lazy_level_tests {
use super::*;
#[test]
fn test_unmaterialized_to_materialized_transition() {
let mut level: LazyLevel<JTreeLevelData> = LazyLevel::Unmaterialized;
assert!(level.is_unmaterialized());
assert!(!level.is_materialized());
assert!(!level.is_dirty());
assert!(level.as_materialized().is_none());
let data = JTreeLevelData::new(0, 100);
level.materialize(data);
assert!(!level.is_unmaterialized());
assert!(level.is_materialized());
assert!(!level.is_dirty());
assert!(level.as_materialized().is_some());
assert_eq!(level.as_materialized().unwrap().vertex_count, 100);
}
#[test]
fn test_materialized_to_dirty_transition() {
let mut level: LazyLevel<JTreeLevelData> = LazyLevel::Unmaterialized;
let data = JTreeLevelData::new(0, 100);
level.materialize(data);
assert!(level.is_materialized());
level.mark_dirty();
assert!(!level.is_unmaterialized());
assert!(!level.is_materialized());
assert!(level.is_dirty());
assert!(level.as_dirty().is_some());
assert_eq!(level.as_dirty().unwrap().vertex_count, 100);
}
#[test]
fn test_dirty_to_materialized_warm_start() {
let mut level: LazyLevel<JTreeLevelData> = LazyLevel::Unmaterialized;
let data = JTreeLevelData::new(0, 100);
level.materialize(data);
level.mark_dirty();
assert!(level.is_dirty());
let old_data = level.as_dirty().unwrap().clone();
let mut new_data = old_data;
new_data.min_cut_value *= 0.9; level.materialize(new_data);
assert!(level.is_materialized());
assert!(!level.is_dirty());
}
#[test]
fn test_cache_invalidation() {
let mut level: LazyLevel<JTreeLevelData> = LazyLevel::Unmaterialized;
let data = JTreeLevelData::new(0, 100);
level.materialize(data);
assert!(level.is_materialized());
level.invalidate();
assert!(level.is_unmaterialized());
assert!(!level.is_materialized());
assert!(!level.is_dirty());
}
#[test]
fn test_mark_dirty_on_unmaterialized_is_noop() {
let mut level: LazyLevel<JTreeLevelData> = LazyLevel::Unmaterialized;
level.mark_dirty();
assert!(level.is_unmaterialized());
assert!(!level.is_dirty());
}
#[test]
fn test_mark_dirty_on_dirty_is_noop() {
let mut level: LazyLevel<JTreeLevelData> = LazyLevel::Unmaterialized;
let data = JTreeLevelData::new(0, 100);
level.materialize(data);
level.mark_dirty();
let original_value = level.as_dirty().unwrap().min_cut_value;
level.mark_dirty();
assert!(level.is_dirty());
assert_eq!(level.as_dirty().unwrap().min_cut_value, original_value);
}
}
mod bmssp_jtree_level_tests {
use super::*;
fn create_test_level() -> BmsspJTreeLevel {
let mut level = BmsspJTreeLevel::new(10, 0);
level.add_edge(0, 1, 5.0);
level.add_edge(1, 2, 3.0);
level.add_edge(2, 3, 4.0);
level.add_edge(3, 4, 2.0);
level
}
#[test]
fn test_min_cut_returns_correct_approximation() {
let mut level = create_test_level();
let cut = level.min_cut(0, 4);
assert!(cut >= 0.0);
assert!(cut < f64::INFINITY);
assert_eq!(cut, 2.0);
}
#[test]
fn test_multi_terminal_cut_with_various_terminal_sets() {
let mut level = create_test_level();
let cut_2 = level.multi_terminal_cut(&[0, 4]);
assert!(cut_2 >= 0.0);
let cut_3 = level.multi_terminal_cut(&[0, 2, 4]);
assert!(cut_3 >= 0.0);
assert!(cut_3 <= cut_2 || (cut_3 - cut_2).abs() < f64::EPSILON);
let cut_1 = level.multi_terminal_cut(&[0]);
assert_eq!(cut_1, f64::INFINITY);
let cut_0 = level.multi_terminal_cut(&[]);
assert_eq!(cut_0, f64::INFINITY);
}
#[test]
fn test_cache_hits_and_misses() {
let mut level = create_test_level();
let _ = level.min_cut(0, 4);
let (hits, misses) = level.cache_stats();
assert_eq!(hits, 0);
assert_eq!(misses, 1);
let _ = level.min_cut(0, 4);
let (hits, misses) = level.cache_stats();
assert_eq!(hits, 1);
assert_eq!(misses, 1);
let _ = level.min_cut(4, 0);
let (hits, misses) = level.cache_stats();
assert_eq!(hits, 2);
assert_eq!(misses, 1);
let _ = level.min_cut(1, 3);
let (hits, misses) = level.cache_stats();
assert_eq!(hits, 2);
assert_eq!(misses, 2);
}
#[test]
fn test_cache_invalidation_for_affected_vertices() {
let mut level = create_test_level();
let _ = level.min_cut(0, 4);
let _ = level.min_cut(1, 3);
let (hits, _) = level.cache_stats();
assert_eq!(hits, 0);
let _ = level.min_cut(0, 4);
let (hits, _) = level.cache_stats();
assert_eq!(hits, 1);
level.invalidate_cache(&[2]);
let _ = level.min_cut(0, 4);
let (hits, _) = level.cache_stats();
assert_eq!(hits, 2);
let _ = level.min_cut(1, 3);
let (_, misses) = level.cache_stats();
assert!(misses >= 2);
}
#[test]
fn test_clear_cache() {
let mut level = create_test_level();
let _ = level.min_cut(0, 4);
let _ = level.min_cut(1, 3);
let _ = level.min_cut(0, 4);
let (hits, _) = level.cache_stats();
assert_eq!(hits, 1);
level.clear_cache();
let _ = level.min_cut(0, 4);
let _ = level.min_cut(1, 3);
let (_, misses) = level.cache_stats();
assert_eq!(misses, 4);
}
#[test]
fn test_symmetry_of_cut_values() {
let mut level = create_test_level();
let cut_forward = level.min_cut(0, 4);
level.clear_cache();
let cut_backward = level.min_cut(4, 0);
assert_eq!(cut_forward, cut_backward);
}
#[test]
fn test_self_cut_is_infinity_or_zero() {
let mut level = create_test_level();
let cut = level.min_cut(2, 2);
assert!(cut == f64::INFINITY || cut == 0.0 || cut == 2.0);
}
}
mod lazy_jtree_hierarchy_tests {
use super::*;
#[test]
fn test_level_demand_paging() {
let mut hierarchy = LazyJTreeHierarchy::new(5, 1.5);
for level in 0..5 {
assert!(!hierarchy.is_materialized(level));
}
assert_eq!(hierarchy.total_computations(), 0);
let _ = hierarchy.approximate_min_cut();
let materialized_count = (0..5).filter(|&l| hierarchy.is_materialized(l)).count();
assert!(materialized_count > 0);
assert!(hierarchy.total_computations() > 0);
}
#[test]
fn test_approximate_min_cut_at_various_levels() {
let mut hierarchy = LazyJTreeHierarchy::new(5, 1.5);
let cut_0 = hierarchy.approximate_min_cut_at_level(0);
assert!(cut_0.is_some());
assert!(hierarchy.is_materialized(0));
let cut_4 = hierarchy.approximate_min_cut_at_level(4);
assert!(cut_4.is_some());
assert!(hierarchy.is_materialized(4));
assert!(cut_0.unwrap() > 0.0);
assert!(cut_4.unwrap() > 0.0);
}
#[test]
fn test_out_of_bounds_level() {
let mut hierarchy = LazyJTreeHierarchy::new(5, 1.5);
let cut = hierarchy.approximate_min_cut_at_level(10);
assert!(cut.is_none());
}
#[test]
fn test_mark_dirty_propagation() {
let mut hierarchy = LazyJTreeHierarchy::new(5, 1.5);
let _ = hierarchy.approximate_min_cut_at_level(2);
let _ = hierarchy.approximate_min_cut_at_level(3);
assert!(hierarchy.is_materialized(2));
assert!(hierarchy.is_materialized(3));
assert!(!hierarchy.is_dirty(2));
assert!(!hierarchy.is_dirty(3));
hierarchy.mark_dirty(&[2, 3]);
assert!(hierarchy.is_dirty(2));
assert!(hierarchy.is_dirty(3));
assert!(!hierarchy.is_materialized(2) || hierarchy.is_dirty(2));
}
#[test]
fn test_warm_start_reduces_computation() {
let mut hierarchy = LazyJTreeHierarchy::new(5, 1.5);
let _ = hierarchy.approximate_min_cut_at_level(2);
let first_computations = hierarchy.total_computations();
hierarchy.mark_dirty(&[2]);
assert!(hierarchy.is_dirty(2));
let _ = hierarchy.approximate_min_cut_at_level(2);
let second_computations = hierarchy.total_computations();
assert_eq!(second_computations, first_computations + 1);
assert!(!hierarchy.is_dirty(2));
}
#[test]
fn test_hierarchy_consistency_after_updates() {
let mut hierarchy = LazyJTreeHierarchy::new(5, 1.5);
let cut_0 = hierarchy.approximate_min_cut_at_level(0).unwrap();
let cut_2 = hierarchy.approximate_min_cut_at_level(2).unwrap();
let cut_4 = hierarchy.approximate_min_cut_at_level(4).unwrap();
assert!(cut_0 > 0.0 && cut_0 < f64::INFINITY);
assert!(cut_2 > 0.0 && cut_2 < f64::INFINITY);
assert!(cut_4 > 0.0 && cut_4 < f64::INFINITY);
hierarchy.mark_dirty(&[0, 2, 4]);
let new_cut_0 = hierarchy.approximate_min_cut_at_level(0).unwrap();
let new_cut_2 = hierarchy.approximate_min_cut_at_level(2).unwrap();
assert!(new_cut_0 > 0.0);
assert!(new_cut_2 > 0.0);
}
#[test]
fn test_unmaterialized_levels_not_marked_dirty() {
let mut hierarchy = LazyJTreeHierarchy::new(5, 1.5);
let _ = hierarchy.approximate_min_cut_at_level(2);
hierarchy.mark_dirty(&[0, 1, 2, 3, 4]);
assert!(hierarchy.is_dirty(2));
assert!(!hierarchy.is_dirty(0)); assert!(!hierarchy.is_dirty(1)); assert!(!hierarchy.is_dirty(3)); }
}
mod two_tier_coordinator_tests {
use super::*;
#[test]
fn test_approximate_to_exact_escalation() {
let mut coordinator = TwoTierCoordinator::new(5, 10.0, 100.0);
let result = coordinator.min_cut(false);
assert!(result.is_exact || result.value < 100.0);
}
#[test]
fn test_exact_required_always_escalates() {
let mut coordinator = TwoTierCoordinator::new(5, 50.0, 10.0);
let result = coordinator.min_cut(true);
assert!(result.is_exact);
assert_eq!(result.tier_used, Tier::Exact);
assert_eq!(result.approximation_factor, 1.0);
assert_eq!(result.value, 50.0);
}
#[test]
fn test_cache_behavior() {
let mut coordinator = TwoTierCoordinator::new(5, 50.0, 10.0);
let result1 = coordinator.min_cut(false);
let (approx1, exact1) = coordinator.query_stats();
let result2 = coordinator.min_cut(false);
let (approx2, exact2) = coordinator.query_stats();
assert_eq!(result1.value, result2.value);
assert_eq!(approx1, approx2);
assert_eq!(exact1, exact2);
}
#[test]
fn test_cache_invalidation_on_edge_insert() {
let mut coordinator = TwoTierCoordinator::new(5, 50.0, 10.0);
let _ = coordinator.min_cut(false);
let (approx1, _) = coordinator.query_stats();
coordinator.insert_edge(1, 2, 5.0);
let _ = coordinator.min_cut(false);
let (approx2, _) = coordinator.query_stats();
assert_eq!(approx2, approx1 + 1);
}
#[test]
fn test_cache_invalidation_on_edge_delete() {
let mut coordinator = TwoTierCoordinator::new(5, 50.0, 10.0);
let _ = coordinator.min_cut(false);
let (approx1, _) = coordinator.query_stats();
coordinator.delete_edge(1, 2);
let _ = coordinator.min_cut(false);
let (approx2, _) = coordinator.query_stats();
assert_eq!(approx2, approx1 + 1);
}
#[test]
fn test_edge_update_propagation() {
let mut coordinator = TwoTierCoordinator::new(5, 50.0, 10.0);
let _ = coordinator.min_cut(false);
coordinator.insert_edge(1, 2, 5.0);
let before = coordinator.jtree.total_computations();
let _ = coordinator.min_cut(false);
let after = coordinator.jtree.total_computations();
assert!(after > before);
}
#[test]
fn test_approximate_only_when_safe() {
let mut coordinator = TwoTierCoordinator::new(5, 100.0, 5.0);
coordinator.set_exact_value(100.0);
let result = coordinator.min_cut(false);
assert!(result.value > 0.0);
assert!(result.value < f64::INFINITY);
}
#[test]
fn test_escalation_when_approx_factor_too_high() {
let mut coordinator = TwoTierCoordinator::new(5, 50.0, 1.0);
coordinator.max_approx_factor = 1.0;
let result = coordinator.min_cut(false);
assert!(result.is_exact || result.approximation_factor <= 1.0);
}
}
mod property_tests {
use super::*;
#[test]
fn property_approximate_cut_bound() {
let epsilon = 0.5;
let exact_value = 100.0;
let mut coordinator = TwoTierCoordinator::new(5, exact_value, 10.0);
for _ in 0..10 {
let approx = coordinator.jtree.approximate_min_cut();
let ratio = approx.value / exact_value;
assert!(ratio > 0.0, "Approximate cut should be positive");
assert!(
ratio < 100.0 || approx.value == f64::INFINITY,
"Approximation should be bounded"
);
coordinator.jtree.mark_dirty(&[0, 1, 2, 3, 4]);
}
}
#[test]
fn property_hierarchy_consistency_after_updates() {
let mut hierarchy = LazyJTreeHierarchy::new(5, 1.5);
for iteration in 0..20 {
let levels_to_materialize: Vec<usize> = (0..5).filter(|_| iteration % 2 == 0).collect();
for level in &levels_to_materialize {
let _ = hierarchy.approximate_min_cut_at_level(*level);
}
hierarchy.mark_dirty(&[iteration % 5]);
let cut = hierarchy.approximate_min_cut();
assert!(cut.value > 0.0 || cut.value == f64::INFINITY);
assert!(cut.approximation_factor >= 1.0);
assert!(cut.level_used < 5);
}
}
#[test]
fn property_cache_coherence() {
let mut level = BmsspJTreeLevel::new(10, 0);
level.add_edge(0, 1, 5.0);
level.add_edge(1, 2, 3.0);
level.add_edge(2, 3, 4.0);
for _ in 0..100 {
let cut1 = level.min_cut(0, 3);
let cut2 = level.min_cut(0, 3);
let cut3 = level.min_cut(3, 0);
assert_eq!(cut1, cut2, "Same query should return same result");
assert_eq!(cut1, cut3, "Cut should be symmetric");
}
}
#[test]
fn property_selective_invalidation() {
let mut level = BmsspJTreeLevel::new(10, 0);
level.add_edge(0, 1, 5.0);
level.add_edge(1, 2, 3.0);
level.add_edge(5, 6, 2.0);
level.add_edge(6, 7, 4.0);
let _ = level.min_cut(0, 2);
let _ = level.min_cut(5, 7);
let (hits_before, misses_before) = level.cache_stats();
level.invalidate_cache(&[1]);
let _ = level.min_cut(5, 7);
let (hits_after, _) = level.cache_stats();
assert!(
hits_after > hits_before,
"Unaffected region should still be cached"
);
}
}
mod edge_case_tests {
use super::*;
#[test]
fn test_empty_hierarchy() {
let mut hierarchy = LazyJTreeHierarchy::new(0, 1.5);
assert_eq!(hierarchy.num_levels(), 0);
let cut = hierarchy.approximate_min_cut();
assert_eq!(cut.value, f64::INFINITY);
}
#[test]
fn test_single_level_hierarchy() {
let mut hierarchy = LazyJTreeHierarchy::new(1, 1.5);
let cut = hierarchy.approximate_min_cut();
assert!(cut.value > 0.0);
assert_eq!(cut.level_used, 0);
}
#[test]
fn test_empty_bmssp_level() {
let mut level = BmsspJTreeLevel::new(0, 0);
let cut = level.min_cut(0, 1);
assert_eq!(cut, f64::INFINITY);
}
#[test]
fn test_disconnected_vertices() {
let mut level = BmsspJTreeLevel::new(10, 0);
level.add_edge(0, 1, 5.0);
let cut = level.min_cut(0, 3);
assert!(cut > 0.0 || cut == f64::INFINITY);
}
#[test]
fn test_very_large_weights() {
let mut level = BmsspJTreeLevel::new(5, 0);
level.add_edge(0, 1, 1e100);
level.add_edge(1, 2, 1e100);
let cut = level.min_cut(0, 2);
assert!(cut.is_finite());
assert!(cut > 0.0);
}
#[test]
fn test_very_small_weights() {
let mut level = BmsspJTreeLevel::new(5, 0);
level.add_edge(0, 1, 1e-100);
level.add_edge(1, 2, 1e-100);
let cut = level.min_cut(0, 2);
assert!(cut > 0.0);
}
#[test]
fn test_coordinator_with_zero_threshold() {
let mut coordinator = TwoTierCoordinator::new(5, 50.0, 0.0);
let result = coordinator.min_cut(false);
assert!(result.value > 0.0);
}
#[test]
fn test_coordinator_with_infinite_threshold() {
let mut coordinator = TwoTierCoordinator::new(5, 50.0, f64::INFINITY);
let result = coordinator.min_cut(false);
assert!(result.is_exact);
}
#[test]
fn test_rapid_cache_operations() {
let mut level = BmsspJTreeLevel::new(10, 0);
level.add_edge(0, 1, 1.0);
level.add_edge(1, 2, 2.0);
for _ in 0..1000 {
let _ = level.min_cut(0, 2);
level.invalidate_cache(&[1]);
let _ = level.min_cut(0, 2);
level.clear_cache();
}
let cut = level.min_cut(0, 2);
assert!(cut > 0.0);
}
}
mod stress_tests {
use super::*;
#[test]
fn stress_many_levels() {
let mut hierarchy = LazyJTreeHierarchy::new(100, 1.1);
for level in (0..100).step_by(10) {
let cut = hierarchy.approximate_min_cut_at_level(level);
assert!(cut.is_some());
}
let all_levels: Vec<usize> = (0..100).collect();
hierarchy.mark_dirty(&all_levels);
let cut = hierarchy.approximate_min_cut();
assert!(cut.value > 0.0);
}
#[test]
fn stress_many_queries() {
let mut level = BmsspJTreeLevel::new(100, 0);
for i in 0..99u64 {
level.add_edge(i, i + 1, (i + 1) as f64);
}
for i in 0u64..50 {
for j in (i + 1)..50 {
let _ = level.min_cut(i, j);
}
}
let (_, first_misses) = level.cache_stats();
for i in 0u64..50 {
for j in (i + 1)..50 {
let _ = level.min_cut(i, j);
}
}
let (hits, misses) = level.cache_stats();
assert!(misses > 0, "Should have cache misses from first pass");
assert!(hits > 0, "Should have cache hits from second pass");
assert!(
hits >= first_misses,
"Second pass should hit cache: hits={}, first_misses={}",
hits,
first_misses
);
}
#[test]
fn stress_coordinator_workload() {
let mut coordinator = TwoTierCoordinator::new(10, 100.0, 50.0);
for i in 0..1000 {
match i % 4 {
0 => {
let _ = coordinator.min_cut(false);
}
1 => {
let _ = coordinator.min_cut(true);
}
2 => {
coordinator.insert_edge(i as u64, (i + 1) as u64, 1.0);
}
3 => {
coordinator.delete_edge(i as u64, (i + 1) as u64);
}
_ => {}
}
}
let (approx, exact) = coordinator.query_stats();
assert!(approx > 0);
assert!(exact > 0);
}
}
mod thread_safety_tests {
use super::*;
use std::sync::Mutex;
#[test]
fn test_lazy_level_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
}
#[test]
fn test_concurrent_cache_stats() {
let level = BmsspJTreeLevel::new(10, 0);
let cache_hits = level.cache_hits.clone();
let cache_misses = level.cache_misses.clone();
cache_hits.fetch_add(1, Ordering::Relaxed);
cache_misses.fetch_add(1, Ordering::Relaxed);
assert_eq!(cache_hits.load(Ordering::Relaxed), 1);
assert_eq!(cache_misses.load(Ordering::Relaxed), 1);
}
}