Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
354
vendor/ruvector/crates/prime-radiant/src/mincut/isolation.rs
vendored
Normal file
354
vendor/ruvector/crates/prime-radiant/src/mincut/isolation.rs
vendored
Normal file
@@ -0,0 +1,354 @@
|
||||
//! Isolation Structures
|
||||
//!
|
||||
//! Data structures representing isolated regions and results.
|
||||
|
||||
use super::{EdgeId, VertexId, Weight};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
|
||||
/// Result of an isolation operation
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct IsolationResult {
|
||||
/// Vertices in the isolated region
|
||||
pub isolated_vertices: HashSet<VertexId>,
|
||||
/// Edges in the cut boundary
|
||||
pub cut_edges: Vec<EdgeId>,
|
||||
/// Total weight of the cut (boundary)
|
||||
pub cut_value: f64,
|
||||
/// Number of high-energy edges that triggered isolation
|
||||
pub num_high_energy_edges: usize,
|
||||
/// Threshold used for high-energy classification
|
||||
pub threshold: Weight,
|
||||
/// Whether the cut was verified by witness tree
|
||||
pub is_verified: bool,
|
||||
}
|
||||
|
||||
impl IsolationResult {
|
||||
/// Create a result indicating no isolation needed
|
||||
pub fn no_isolation() -> Self {
|
||||
Self {
|
||||
isolated_vertices: HashSet::new(),
|
||||
cut_edges: vec![],
|
||||
cut_value: 0.0,
|
||||
num_high_energy_edges: 0,
|
||||
threshold: 0.0,
|
||||
is_verified: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if any vertices were isolated
|
||||
pub fn has_isolation(&self) -> bool {
|
||||
!self.isolated_vertices.is_empty()
|
||||
}
|
||||
|
||||
/// Get number of isolated vertices
|
||||
pub fn num_isolated(&self) -> usize {
|
||||
self.isolated_vertices.len()
|
||||
}
|
||||
|
||||
/// Get number of cut edges
|
||||
pub fn num_cut_edges(&self) -> usize {
|
||||
self.cut_edges.len()
|
||||
}
|
||||
|
||||
/// Calculate isolation efficiency (cut value per isolated vertex)
|
||||
pub fn efficiency(&self) -> f64 {
|
||||
if self.isolated_vertices.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
self.cut_value / self.isolated_vertices.len() as f64
|
||||
}
|
||||
|
||||
/// Check if a vertex is in the isolated set
|
||||
pub fn is_isolated(&self, vertex: VertexId) -> bool {
|
||||
self.isolated_vertices.contains(&vertex)
|
||||
}
|
||||
|
||||
/// Get boundary vertices (endpoints of cut edges in isolated set)
|
||||
pub fn boundary_vertices(&self) -> HashSet<VertexId> {
|
||||
let mut boundary = HashSet::new();
|
||||
for (u, v) in &self.cut_edges {
|
||||
if self.isolated_vertices.contains(u) {
|
||||
boundary.insert(*u);
|
||||
}
|
||||
if self.isolated_vertices.contains(v) {
|
||||
boundary.insert(*v);
|
||||
}
|
||||
}
|
||||
boundary
|
||||
}
|
||||
}
|
||||
|
||||
/// A connected region of high-energy edges
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct IsolationRegion {
|
||||
/// Vertices in this region
|
||||
pub vertices: HashSet<VertexId>,
|
||||
/// Internal edges (both endpoints in region)
|
||||
pub internal_edges: Vec<EdgeId>,
|
||||
/// Boundary edges (one endpoint outside region)
|
||||
pub boundary_edges: Vec<EdgeId>,
|
||||
/// Total energy of internal edges
|
||||
pub total_energy: Weight,
|
||||
/// Total weight of boundary edges
|
||||
pub boundary_weight: Weight,
|
||||
/// Unique region identifier
|
||||
pub region_id: usize,
|
||||
}
|
||||
|
||||
impl IsolationRegion {
|
||||
/// Get number of vertices in region
|
||||
pub fn num_vertices(&self) -> usize {
|
||||
self.vertices.len()
|
||||
}
|
||||
|
||||
/// Get number of internal edges
|
||||
pub fn num_internal_edges(&self) -> usize {
|
||||
self.internal_edges.len()
|
||||
}
|
||||
|
||||
/// Get number of boundary edges
|
||||
pub fn num_boundary_edges(&self) -> usize {
|
||||
self.boundary_edges.len()
|
||||
}
|
||||
|
||||
/// Calculate region density (edges per vertex)
|
||||
pub fn density(&self) -> f64 {
|
||||
if self.vertices.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
self.internal_edges.len() as f64 / self.vertices.len() as f64
|
||||
}
|
||||
|
||||
/// Calculate boundary ratio (boundary / internal edges)
|
||||
pub fn boundary_ratio(&self) -> f64 {
|
||||
if self.internal_edges.is_empty() {
|
||||
return f64::INFINITY;
|
||||
}
|
||||
self.boundary_edges.len() as f64 / self.internal_edges.len() as f64
|
||||
}
|
||||
|
||||
/// Calculate average energy per edge
|
||||
pub fn avg_energy(&self) -> Weight {
|
||||
if self.internal_edges.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
self.total_energy / self.internal_edges.len() as f64
|
||||
}
|
||||
|
||||
/// Check if vertex is in this region
|
||||
pub fn contains(&self, vertex: VertexId) -> bool {
|
||||
self.vertices.contains(&vertex)
|
||||
}
|
||||
|
||||
/// Check if edge is internal to this region
|
||||
pub fn is_internal_edge(&self, edge: &EdgeId) -> bool {
|
||||
self.internal_edges.contains(edge)
|
||||
}
|
||||
|
||||
/// Check if edge is on the boundary
|
||||
pub fn is_boundary_edge(&self, edge: &EdgeId) -> bool {
|
||||
self.boundary_edges.contains(edge)
|
||||
}
|
||||
|
||||
/// Get vertices on the boundary (adjacent to outside)
|
||||
pub fn boundary_vertices(&self) -> HashSet<VertexId> {
|
||||
let mut boundary = HashSet::new();
|
||||
for (u, v) in &self.boundary_edges {
|
||||
if self.vertices.contains(u) {
|
||||
boundary.insert(*u);
|
||||
}
|
||||
if self.vertices.contains(v) {
|
||||
boundary.insert(*v);
|
||||
}
|
||||
}
|
||||
boundary
|
||||
}
|
||||
|
||||
/// Get interior vertices (not on boundary)
|
||||
pub fn interior_vertices(&self) -> HashSet<VertexId> {
|
||||
let boundary = self.boundary_vertices();
|
||||
self.vertices
|
||||
.iter()
|
||||
.filter(|v| !boundary.contains(v))
|
||||
.copied()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Comparison result between two isolation results
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IsolationComparison {
|
||||
/// Vertices isolated in both results
|
||||
pub common_isolated: HashSet<VertexId>,
|
||||
/// Vertices only isolated in first result
|
||||
pub only_first: HashSet<VertexId>,
|
||||
/// Vertices only isolated in second result
|
||||
pub only_second: HashSet<VertexId>,
|
||||
/// Jaccard similarity of isolated sets
|
||||
pub jaccard_similarity: f64,
|
||||
}
|
||||
|
||||
impl IsolationComparison {
|
||||
/// Compare two isolation results
|
||||
pub fn compare(first: &IsolationResult, second: &IsolationResult) -> Self {
|
||||
let common: HashSet<_> = first
|
||||
.isolated_vertices
|
||||
.intersection(&second.isolated_vertices)
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
let only_first: HashSet<_> = first
|
||||
.isolated_vertices
|
||||
.difference(&second.isolated_vertices)
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
let only_second: HashSet<_> = second
|
||||
.isolated_vertices
|
||||
.difference(&first.isolated_vertices)
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
let union_size =
|
||||
first.isolated_vertices.len() + second.isolated_vertices.len() - common.len();
|
||||
let jaccard = if union_size > 0 {
|
||||
common.len() as f64 / union_size as f64
|
||||
} else {
|
||||
1.0 // Both empty = identical
|
||||
};
|
||||
|
||||
Self {
|
||||
common_isolated: common,
|
||||
only_first,
|
||||
only_second,
|
||||
jaccard_similarity: jaccard,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if results are identical
|
||||
pub fn is_identical(&self) -> bool {
|
||||
self.only_first.is_empty() && self.only_second.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_no_isolation() {
|
||||
let result = IsolationResult::no_isolation();
|
||||
assert!(!result.has_isolation());
|
||||
assert_eq!(result.num_isolated(), 0);
|
||||
assert!(result.is_verified);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_isolation_result() {
|
||||
let mut isolated = HashSet::new();
|
||||
isolated.insert(1);
|
||||
isolated.insert(2);
|
||||
isolated.insert(3);
|
||||
|
||||
let result = IsolationResult {
|
||||
isolated_vertices: isolated,
|
||||
cut_edges: vec![(3, 4), (3, 5)],
|
||||
cut_value: 2.5,
|
||||
num_high_energy_edges: 2,
|
||||
threshold: 1.0,
|
||||
is_verified: true,
|
||||
};
|
||||
|
||||
assert!(result.has_isolation());
|
||||
assert_eq!(result.num_isolated(), 3);
|
||||
assert_eq!(result.num_cut_edges(), 2);
|
||||
assert!(result.is_isolated(1));
|
||||
assert!(!result.is_isolated(4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boundary_vertices() {
|
||||
let mut isolated = HashSet::new();
|
||||
isolated.insert(1);
|
||||
isolated.insert(2);
|
||||
isolated.insert(3);
|
||||
|
||||
let result = IsolationResult {
|
||||
isolated_vertices: isolated,
|
||||
cut_edges: vec![(3, 4), (2, 5)],
|
||||
cut_value: 2.0,
|
||||
num_high_energy_edges: 1,
|
||||
threshold: 1.0,
|
||||
is_verified: true,
|
||||
};
|
||||
|
||||
let boundary = result.boundary_vertices();
|
||||
assert!(boundary.contains(&3));
|
||||
assert!(boundary.contains(&2));
|
||||
assert!(!boundary.contains(&1)); // Not on boundary
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_region() {
|
||||
let mut vertices = HashSet::new();
|
||||
vertices.insert(1);
|
||||
vertices.insert(2);
|
||||
vertices.insert(3);
|
||||
|
||||
let region = IsolationRegion {
|
||||
vertices,
|
||||
internal_edges: vec![(1, 2), (2, 3)],
|
||||
boundary_edges: vec![(3, 4)],
|
||||
total_energy: 5.0,
|
||||
boundary_weight: 1.0,
|
||||
region_id: 0,
|
||||
};
|
||||
|
||||
assert_eq!(region.num_vertices(), 3);
|
||||
assert_eq!(region.num_internal_edges(), 2);
|
||||
assert_eq!(region.num_boundary_edges(), 1);
|
||||
assert!((region.avg_energy() - 2.5).abs() < 0.01);
|
||||
assert!(region.contains(1));
|
||||
assert!(!region.contains(4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comparison() {
|
||||
let mut isolated1 = HashSet::new();
|
||||
isolated1.insert(1);
|
||||
isolated1.insert(2);
|
||||
isolated1.insert(3);
|
||||
|
||||
let result1 = IsolationResult {
|
||||
isolated_vertices: isolated1,
|
||||
cut_edges: vec![],
|
||||
cut_value: 0.0,
|
||||
num_high_energy_edges: 0,
|
||||
threshold: 1.0,
|
||||
is_verified: true,
|
||||
};
|
||||
|
||||
let mut isolated2 = HashSet::new();
|
||||
isolated2.insert(2);
|
||||
isolated2.insert(3);
|
||||
isolated2.insert(4);
|
||||
|
||||
let result2 = IsolationResult {
|
||||
isolated_vertices: isolated2,
|
||||
cut_edges: vec![],
|
||||
cut_value: 0.0,
|
||||
num_high_energy_edges: 0,
|
||||
threshold: 1.0,
|
||||
is_verified: true,
|
||||
};
|
||||
|
||||
let comparison = IsolationComparison::compare(&result1, &result2);
|
||||
|
||||
assert_eq!(comparison.common_isolated.len(), 2); // {2, 3}
|
||||
assert_eq!(comparison.only_first.len(), 1); // {1}
|
||||
assert_eq!(comparison.only_second.len(), 1); // {4}
|
||||
assert!(!comparison.is_identical());
|
||||
assert!(comparison.jaccard_similarity > 0.0 && comparison.jaccard_similarity < 1.0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user