pub mod coordinator;
pub mod hierarchy;
pub mod level;
pub mod sparsifier;
pub use coordinator::{
EscalationPolicy, EscalationTrigger, QueryResult, TierMetrics, TwoTierCoordinator,
};
pub use hierarchy::{
ApproximateCut, CutResult, JTreeConfig, JTreeHierarchy, JTreeStatistics, Tier,
};
pub use level::{
BmsspJTreeLevel, ContractedGraph, JTreeLevel, LevelConfig, LevelStatistics, PathCutResult,
};
pub use sparsifier::{
DynamicCutSparsifier, ForestPacking, RecourseTracker, SparsifierConfig, SparsifierStatistics,
VertexSplitResult,
};
use crate::error::{MinCutError, Result};
#[derive(Debug, Clone)]
pub enum JTreeError {
InvalidConfig(String),
LevelOutOfBounds {
level: usize,
max_level: usize,
},
WasmInitError(String),
VertexNotFound(u64),
FfiBoundaryError(String),
RecourseExceeded {
actual: usize,
limit: usize,
},
CutComputationFailed(String),
}
impl std::fmt::Display for JTreeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidConfig(msg) => write!(f, "Invalid j-tree configuration: {msg}"),
Self::LevelOutOfBounds { level, max_level } => {
write!(f, "Level {level} out of bounds (max: {max_level})")
}
Self::WasmInitError(msg) => write!(f, "WASM initialization failed: {msg}"),
Self::VertexNotFound(v) => write!(f, "Vertex {v} not found in j-tree hierarchy"),
Self::FfiBoundaryError(msg) => write!(f, "FFI boundary error: {msg}"),
Self::RecourseExceeded { actual, limit } => {
write!(f, "Sparsifier recourse {actual} exceeded limit {limit}")
}
Self::CutComputationFailed(msg) => write!(f, "Cut computation failed: {msg}"),
}
}
}
impl std::error::Error for JTreeError {}
impl From<JTreeError> for MinCutError {
fn from(err: JTreeError) -> Self {
MinCutError::InternalError(err.to_string())
}
}
#[inline]
pub fn compute_alpha(epsilon: f64) -> f64 {
debug_assert!(epsilon > 0.0 && epsilon <= 1.0, "epsilon must be in (0, 1]");
2.0_f64.powf(1.0 / epsilon)
}
#[inline]
pub fn compute_num_levels(vertex_count: usize, alpha: f64) -> usize {
if vertex_count <= 1 {
return 1;
}
let n = vertex_count as f64;
(n.ln() / alpha.ln()).ceil() as usize
}
pub fn validate_config(config: &JTreeConfig) -> Result<()> {
if config.epsilon <= 0.0 || config.epsilon > 1.0 {
return Err(JTreeError::InvalidConfig(format!(
"epsilon must be in (0, 1], got {}",
config.epsilon
))
.into());
}
if config.critical_threshold < 0.0 {
return Err(JTreeError::InvalidConfig(format!(
"critical_threshold must be non-negative, got {}",
config.critical_threshold
))
.into());
}
if config.max_approximation_factor < 1.0 {
return Err(JTreeError::InvalidConfig(format!(
"max_approximation_factor must be >= 1.0, got {}",
config.max_approximation_factor
))
.into());
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_alpha() {
let alpha = compute_alpha(1.0);
assert!((alpha - 2.0).abs() < 1e-10);
let alpha = compute_alpha(0.5);
assert!((alpha - 4.0).abs() < 1e-10);
let alpha = compute_alpha(0.1);
assert!((alpha - 1024.0).abs() < 1e-6);
}
#[test]
fn test_compute_num_levels() {
assert_eq!(compute_num_levels(1, 2.0), 1);
assert_eq!(compute_num_levels(16, 2.0), 4);
assert_eq!(compute_num_levels(1000, 2.0), 10);
assert_eq!(compute_num_levels(1000, 10.0), 3);
}
#[test]
fn test_validate_config_valid() {
let config = JTreeConfig {
epsilon: 0.5,
critical_threshold: 10.0,
max_approximation_factor: 2.0,
..Default::default()
};
assert!(validate_config(&config).is_ok());
}
#[test]
fn test_validate_config_invalid_epsilon() {
let config = JTreeConfig {
epsilon: 0.0,
..Default::default()
};
assert!(validate_config(&config).is_err());
let config = JTreeConfig {
epsilon: 1.5,
..Default::default()
};
assert!(validate_config(&config).is_err());
}
#[test]
fn test_jtree_error_display() {
let err = JTreeError::LevelOutOfBounds {
level: 5,
max_level: 3,
};
assert_eq!(err.to_string(), "Level 5 out of bounds (max: 3)");
let err = JTreeError::VertexNotFound(42);
assert_eq!(err.to_string(), "Vertex 42 not found in j-tree hierarchy");
}
#[test]
fn test_jtree_error_to_mincut_error() {
let jtree_err = JTreeError::WasmInitError("test error".to_string());
let mincut_err: MinCutError = jtree_err.into();
assert!(matches!(mincut_err, MinCutError::InternalError(_)));
}
}