use crate::graph::{VertexId, Weight};
use std::collections::{HashMap, HashSet, VecDeque};
#[derive(Debug, Clone)]
pub struct FragmentationConfig {
pub phi: f64,
pub max_fragment_size: usize,
pub min_fragment_size: usize,
pub boundary_sparsity: f64,
}
impl Default for FragmentationConfig {
fn default() -> Self {
Self {
phi: 0.1,
max_fragment_size: 1000,
min_fragment_size: 10,
boundary_sparsity: 0.5,
}
}
}
#[derive(Debug, Clone)]
pub struct Fragment {
pub id: u64,
pub vertices: HashSet<VertexId>,
pub boundary: Vec<(VertexId, VertexId)>,
pub internal_edge_count: usize,
pub volume: usize,
pub parent: Option<u64>,
pub children: Vec<u64>,
pub is_expander: bool,
}
impl Fragment {
pub fn new(id: u64, vertices: HashSet<VertexId>) -> Self {
Self {
id,
vertices,
boundary: Vec::new(),
internal_edge_count: 0,
volume: 0,
parent: None,
children: Vec::new(),
is_expander: false,
}
}
pub fn size(&self) -> usize {
self.vertices.len()
}
pub fn contains(&self, v: VertexId) -> bool {
self.vertices.contains(&v)
}
pub fn boundary_sparsity(&self) -> f64 {
if self.volume == 0 {
return 0.0;
}
self.boundary.len() as f64 / self.volume as f64
}
}
#[derive(Debug, Clone)]
pub struct TrimResult {
pub trimmed_vertices: HashSet<VertexId>,
pub cut_edges: Vec<(VertexId, VertexId)>,
pub cut_value: f64,
pub success: bool,
}
#[derive(Debug)]
pub struct Fragmentation {
config: FragmentationConfig,
fragments: HashMap<u64, Fragment>,
vertex_fragment: HashMap<VertexId, u64>,
next_id: u64,
adjacency: HashMap<VertexId, HashMap<VertexId, Weight>>,
roots: Vec<u64>,
}
impl Fragmentation {
pub fn new(config: FragmentationConfig) -> Self {
Self {
config,
fragments: HashMap::new(),
vertex_fragment: HashMap::new(),
next_id: 1,
adjacency: HashMap::new(),
roots: Vec::new(),
}
}
pub fn with_defaults() -> Self {
Self::new(FragmentationConfig::default())
}
pub fn insert_edge(&mut self, u: VertexId, v: VertexId, weight: Weight) {
self.adjacency.entry(u).or_default().insert(v, weight);
self.adjacency.entry(v).or_default().insert(u, weight);
}
pub fn delete_edge(&mut self, u: VertexId, v: VertexId) {
if let Some(neighbors) = self.adjacency.get_mut(&u) {
neighbors.remove(&v);
}
if let Some(neighbors) = self.adjacency.get_mut(&v) {
neighbors.remove(&u);
}
}
pub fn vertices(&self) -> Vec<VertexId> {
self.adjacency.keys().copied().collect()
}
pub fn neighbors(&self, v: VertexId) -> Vec<(VertexId, Weight)> {
self.adjacency
.get(&v)
.map(|n| n.iter().map(|(&v, &w)| (v, w)).collect())
.unwrap_or_default()
}
pub fn degree(&self, v: VertexId) -> usize {
self.adjacency.get(&v).map_or(0, |n| n.len())
}
pub fn fragment(&mut self) -> Vec<u64> {
let vertices: HashSet<_> = self.vertices().into_iter().collect();
if vertices.is_empty() {
return Vec::new();
}
let root_id = self.create_fragment(vertices);
self.roots.push(root_id);
let mut to_process = vec![root_id];
while let Some(fragment_id) = to_process.pop() {
if let Some(children) = self.try_fragment_recursive(fragment_id) {
to_process.extend(children);
}
}
self.roots.clone()
}
fn try_fragment_recursive(&mut self, fragment_id: u64) -> Option<Vec<u64>> {
let fragment = self.fragments.get(&fragment_id)?;
if fragment.size() <= self.config.min_fragment_size {
return None;
}
if fragment.size() <= self.config.max_fragment_size && fragment.is_expander {
return None;
}
let vertices: Vec<_> = fragment.vertices.iter().copied().collect();
let trim_result = self.trim(&vertices);
if !trim_result.success || trim_result.trimmed_vertices.is_empty() {
if let Some(f) = self.fragments.get_mut(&fragment_id) {
f.is_expander = true;
}
return None;
}
let remaining: HashSet<_> = fragment
.vertices
.difference(&trim_result.trimmed_vertices)
.copied()
.collect();
if remaining.is_empty() || trim_result.trimmed_vertices.len() == fragment.vertices.len() {
return None;
}
let child1_id = self.create_fragment(trim_result.trimmed_vertices);
let child2_id = self.create_fragment(remaining);
if let Some(f) = self.fragments.get_mut(&fragment_id) {
f.children = vec![child1_id, child2_id];
}
if let Some(f) = self.fragments.get_mut(&child1_id) {
f.parent = Some(fragment_id);
}
if let Some(f) = self.fragments.get_mut(&child2_id) {
f.parent = Some(fragment_id);
}
Some(vec![child1_id, child2_id])
}
fn create_fragment(&mut self, vertices: HashSet<VertexId>) -> u64 {
let id = self.next_id;
self.next_id += 1;
let mut internal_edge_count = 0;
let mut boundary = Vec::new();
let mut volume = 0;
for &v in &vertices {
let neighbors = self.neighbors(v);
volume += neighbors.len();
for (neighbor, _weight) in neighbors {
if vertices.contains(&neighbor) {
internal_edge_count += 1;
} else {
boundary.push((v, neighbor));
}
}
}
internal_edge_count /= 2;
let fragment = Fragment {
id,
vertices: vertices.clone(),
boundary,
internal_edge_count,
volume,
parent: None,
children: Vec::new(),
is_expander: false,
};
for &v in &vertices {
self.vertex_fragment.insert(v, id);
}
self.fragments.insert(id, fragment);
id
}
pub fn trim(&self, vertices: &[VertexId]) -> TrimResult {
if vertices.is_empty() {
return TrimResult {
trimmed_vertices: HashSet::new(),
cut_edges: Vec::new(),
cut_value: 0.0,
success: false,
};
}
let vertex_set: HashSet<_> = vertices.iter().copied().collect();
let mut sorted_vertices: Vec<_> = vertices.to_vec();
sorted_vertices.sort_by_key(|&v| self.degree(v));
let mut trimmed = HashSet::new();
let mut trimmed_volume = 0usize;
let mut boundary_count = 0usize;
for &v in &sorted_vertices {
let neighbors = self.neighbors(v);
let degree = neighbors.len();
let mut internal_neighbors = 0usize;
let mut external_neighbors = 0usize;
for (neighbor, _) in &neighbors {
if trimmed.contains(neighbor) {
internal_neighbors += 1;
} else if vertex_set.contains(neighbor) {
external_neighbors += 1;
}
}
let new_boundary = boundary_count - internal_neighbors + external_neighbors;
let new_volume = trimmed_volume + degree;
let sparsity = if new_volume > 0 {
new_boundary as f64 / new_volume as f64
} else {
0.0
};
if sparsity <= self.config.boundary_sparsity {
trimmed.insert(v);
trimmed_volume = new_volume;
boundary_count = new_boundary;
}
if trimmed.len() >= vertex_set.len() / 2 {
break;
}
}
if trimmed.is_empty() || trimmed.len() >= vertex_set.len() {
return TrimResult {
trimmed_vertices: HashSet::new(),
cut_edges: Vec::new(),
cut_value: 0.0,
success: false,
};
}
let mut cut_edges = Vec::new();
let mut cut_value = 0.0;
for &v in &trimmed {
for (neighbor, weight) in self.neighbors(v) {
if !trimmed.contains(&neighbor) && vertex_set.contains(&neighbor) {
cut_edges.push((v, neighbor));
cut_value += weight;
}
}
}
TrimResult {
trimmed_vertices: trimmed,
cut_edges,
cut_value,
success: true,
}
}
pub fn is_expander(&self, fragment_id: u64) -> bool {
let fragment = match self.fragments.get(&fragment_id) {
Some(f) => f,
None => return false,
};
if fragment.vertices.len() <= 2 {
return true; }
let vertices: Vec<_> = fragment.vertices.iter().copied().collect();
let trim = self.trim(&vertices);
if !trim.success {
return true; }
let cut_volume = trim
.trimmed_vertices
.iter()
.map(|&v| self.degree(v))
.sum::<usize>();
let remaining_volume = fragment.volume - cut_volume;
let min_volume = cut_volume.min(remaining_volume);
if min_volume == 0 {
return true;
}
let expansion_ratio = trim.cut_edges.len() as f64 / min_volume as f64;
expansion_ratio >= self.config.phi
}
pub fn get_fragment(&self, id: u64) -> Option<&Fragment> {
self.fragments.get(&id)
}
pub fn get_vertex_fragment(&self, v: VertexId) -> Option<&Fragment> {
self.vertex_fragment
.get(&v)
.and_then(|&id| self.fragments.get(&id))
}
pub fn leaf_fragments(&self) -> Vec<&Fragment> {
self.fragments
.values()
.filter(|f| f.children.is_empty())
.collect()
}
pub fn num_fragments(&self) -> usize {
self.fragments.len()
}
pub fn max_depth(&self) -> usize {
fn depth_of(fragments: &HashMap<u64, Fragment>, id: u64) -> usize {
match fragments.get(&id) {
Some(f) if f.children.is_empty() => 0,
Some(f) => {
1 + f
.children
.iter()
.map(|&c| depth_of(fragments, c))
.max()
.unwrap_or(0)
}
None => 0,
}
}
self.roots
.iter()
.map(|&r| depth_of(&self.fragments, r))
.max()
.unwrap_or(0)
}
}
impl Default for Fragmentation {
fn default() -> Self {
Self::with_defaults()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_path_graph(frag: &mut Fragmentation, n: usize) {
for i in 0..n - 1 {
frag.insert_edge(i as u64, (i + 1) as u64, 1.0);
}
}
fn build_clique(frag: &mut Fragmentation, vertices: &[u64]) {
for i in 0..vertices.len() {
for j in i + 1..vertices.len() {
frag.insert_edge(vertices[i], vertices[j], 1.0);
}
}
}
#[test]
fn test_fragmentation_empty() {
let mut frag = Fragmentation::with_defaults();
let roots = frag.fragment();
assert!(roots.is_empty());
}
#[test]
fn test_fragmentation_single_vertex() {
let mut frag = Fragmentation::with_defaults();
frag.insert_edge(1, 1, 0.0); frag.adjacency.entry(1).or_default();
let mut frag2 = Fragmentation::with_defaults();
frag2.insert_edge(1, 2, 1.0);
let roots = frag2.fragment();
assert_eq!(roots.len(), 1);
}
#[test]
fn test_fragmentation_path() {
let mut frag = Fragmentation::new(FragmentationConfig {
min_fragment_size: 2,
max_fragment_size: 5,
..Default::default()
});
build_path_graph(&mut frag, 10);
let roots = frag.fragment();
assert!(!roots.is_empty());
assert!(frag.num_fragments() >= 1);
}
#[test]
fn test_fragmentation_clique() {
let mut frag = Fragmentation::with_defaults();
let vertices: Vec<u64> = (1..=6).collect();
build_clique(&mut frag, &vertices);
let roots = frag.fragment();
assert!(!roots.is_empty());
let leaves = frag.leaf_fragments();
let leaf = leaves.first().unwrap();
assert!(leaf.size() <= 6);
}
#[test]
fn test_trim_basic() {
let mut frag = Fragmentation::with_defaults();
build_path_graph(&mut frag, 10);
let vertices: Vec<u64> = (0..10).collect();
let result = frag.trim(&vertices);
assert!(result.trimmed_vertices.len() <= vertices.len());
}
#[test]
fn test_fragment_properties() {
let mut frag = Fragmentation::with_defaults();
build_clique(&mut frag, &[1, 2, 3, 4]);
build_clique(&mut frag, &[5, 6, 7, 8]);
frag.insert_edge(4, 5, 1.0);
let roots = frag.fragment();
assert!(!roots.is_empty());
let f1 = frag.get_vertex_fragment(1);
assert!(f1.is_some());
}
#[test]
fn test_is_expander() {
let mut frag = Fragmentation::new(FragmentationConfig {
phi: 0.1,
min_fragment_size: 2,
..Default::default()
});
build_clique(&mut frag, &[1, 2, 3, 4, 5]);
let roots = frag.fragment();
assert!(!roots.is_empty());
let is_exp = frag.is_expander(roots[0]);
assert!(is_exp || frag.leaf_fragments().len() > 1);
}
#[test]
fn test_hierarchy_depth() {
let mut frag = Fragmentation::new(FragmentationConfig {
min_fragment_size: 3,
max_fragment_size: 10,
..Default::default()
});
build_path_graph(&mut frag, 20);
frag.fragment();
let depth = frag.max_depth();
assert!(depth >= 0);
}
}