use ruvector_mincut::cluster::hierarchy::{HierarchyConfig, ThreeLevelHierarchy};
use ruvector_mincut::localkcut::deterministic::DeterministicLocalKCut;
use ruvector_mincut::{DynamicGraph, DynamicMinCut, MinCutBuilder, MinCutConfig, MinCutWrapper};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct WasmMinCut {
inner: DynamicMinCut,
}
#[derive(Serialize, Deserialize)]
struct EdgeInput {
u: u64,
v: u64,
weight: f64,
}
#[derive(Serialize, Deserialize)]
struct Partition {
s: Vec<u64>,
t: Vec<u64>,
}
#[derive(Serialize, Deserialize)]
struct Edge {
u: u64,
v: u64,
weight: f64,
}
#[derive(Serialize, Deserialize)]
struct Stats {
num_vertices: usize,
num_edges: usize,
min_cut_value: f64,
is_connected: bool,
num_operations: usize,
}
#[wasm_bindgen]
impl WasmMinCut {
#[wasm_bindgen(constructor)]
pub fn new() -> Result<WasmMinCut, JsError> {
console_error_panic_hook::set_once();
Ok(WasmMinCut {
inner: DynamicMinCut::new(MinCutConfig::default()),
})
}
#[wasm_bindgen(js_name = "fromEdges")]
pub fn from_edges(edges: JsValue) -> Result<WasmMinCut, JsError> {
console_error_panic_hook::set_once();
let edges_vec: Vec<Vec<f64>> = serde_wasm_bindgen::from_value(edges)
.map_err(|e| JsError::new(&format!("Failed to parse edges: {}", e)))?;
let mut edge_tuples = Vec::with_capacity(edges_vec.len());
for edge in edges_vec {
if edge.len() != 3 {
return Err(JsError::new("Each edge must be [u, v, weight]"));
}
let u = edge[0] as u64;
let v = edge[1] as u64;
let weight = edge[2];
edge_tuples.push((u, v, weight));
}
let inner = MinCutBuilder::new()
.with_edges(edge_tuples)
.build()
.map_err(|e| JsError::new(&format!("Failed to build mincut: {}", e)))?;
Ok(WasmMinCut { inner })
}
#[wasm_bindgen(js_name = "insertEdge")]
pub fn insert_edge(&mut self, u: u64, v: u64, weight: f64) -> Result<f64, JsError> {
self.inner
.insert_edge(u, v, weight)
.map_err(|e| JsError::new(&format!("Failed to insert edge: {}", e)))
}
#[wasm_bindgen(js_name = "deleteEdge")]
pub fn delete_edge(&mut self, u: u64, v: u64) -> Result<f64, JsError> {
self.inner
.delete_edge(u, v)
.map_err(|e| JsError::new(&format!("Failed to delete edge: {}", e)))
}
#[wasm_bindgen(js_name = "minCutValue")]
pub fn min_cut_value(&self) -> f64 {
self.inner.min_cut_value()
}
#[wasm_bindgen]
pub fn partition(&self) -> JsValue {
let (s_set, t_set) = self.inner.partition();
let partition = Partition {
s: s_set.into_iter().collect(),
t: t_set.into_iter().collect(),
};
serde_wasm_bindgen::to_value(&partition).unwrap_or(JsValue::NULL)
}
#[wasm_bindgen(js_name = "cutEdges")]
pub fn cut_edges(&self) -> JsValue {
let edges = self.inner.cut_edges();
let edge_list: Vec<Edge> = edges
.into_iter()
.map(|e| Edge {
u: e.source,
v: e.target,
weight: e.weight,
})
.collect();
serde_wasm_bindgen::to_value(&edge_list).unwrap_or(JsValue::NULL)
}
#[wasm_bindgen(js_name = "numVertices")]
pub fn num_vertices(&self) -> usize {
self.inner.num_vertices()
}
#[wasm_bindgen(js_name = "numEdges")]
pub fn num_edges(&self) -> usize {
self.inner.num_edges()
}
#[wasm_bindgen(js_name = "isConnected")]
pub fn is_connected(&self) -> bool {
self.inner.is_connected()
}
#[wasm_bindgen]
pub fn stats(&self) -> JsValue {
let algo_stats = self.inner.stats();
let stats = Stats {
num_vertices: self.inner.num_vertices(),
num_edges: self.inner.num_edges(),
min_cut_value: self.inner.min_cut_value(),
is_connected: self.inner.is_connected(),
num_operations: (algo_stats.insertions + algo_stats.deletions + algo_stats.queries)
as usize,
};
serde_wasm_bindgen::to_value(&stats).unwrap_or(JsValue::NULL)
}
#[wasm_bindgen(js_name = "updateEdge")]
pub fn update_edge(&mut self, u: u64, v: u64, new_weight: f64) -> Result<f64, JsError> {
let _ = self.inner.delete_edge(u, v);
self.inner
.insert_edge(u, v, new_weight)
.map_err(|e| JsError::new(&format!("Failed to update edge: {}", e)))
}
#[wasm_bindgen(js_name = "batchInsert")]
pub fn batch_insert(&mut self, edges: JsValue) -> Result<f64, JsError> {
let edges_vec: Vec<Vec<f64>> = serde_wasm_bindgen::from_value(edges)
.map_err(|e| JsError::new(&format!("Failed to parse edges: {}", e)))?;
for edge in edges_vec {
if edge.len() != 3 {
return Err(JsError::new("Each edge must be [u, v, weight]"));
}
let u = edge[0] as u64;
let v = edge[1] as u64;
let weight = edge[2];
self.inner.insert_edge(u, v, weight).map_err(|e| {
JsError::new(&format!("Failed to insert edge [{}, {}]: {}", u, v, e))
})?;
}
Ok(self.inner.min_cut_value())
}
#[wasm_bindgen(js_name = "batchDelete")]
pub fn batch_delete(&mut self, edges: JsValue) -> Result<f64, JsError> {
let edges_vec: Vec<Vec<f64>> = serde_wasm_bindgen::from_value(edges)
.map_err(|e| JsError::new(&format!("Failed to parse edges: {}", e)))?;
for edge in edges_vec {
if edge.len() < 2 {
return Err(JsError::new("Each edge must be [u, v] or [u, v, weight]"));
}
let u = edge[0] as u64;
let v = edge[1] as u64;
self.inner.delete_edge(u, v).map_err(|e| {
JsError::new(&format!("Failed to delete edge [{}, {}]: {}", u, v, e))
})?;
}
Ok(self.inner.min_cut_value())
}
#[wasm_bindgen]
pub fn clear(&mut self) {
self.inner = DynamicMinCut::new(MinCutConfig::default());
}
}
#[wasm_bindgen(start)]
pub fn init() {
console_error_panic_hook::set_once();
}
#[wasm_bindgen(js_name = "getVersion")]
pub fn get_version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
#[derive(Serialize, Deserialize)]
struct HierarchyStatsJs {
num_expanders: usize,
num_preclusters: usize,
num_clusters: usize,
num_vertices: usize,
num_edges: usize,
global_min_cut: f64,
avg_expander_size: f64,
}
#[wasm_bindgen]
pub struct WasmThreeLevelHierarchy {
inner: ThreeLevelHierarchy,
}
#[wasm_bindgen]
impl WasmThreeLevelHierarchy {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmThreeLevelHierarchy {
console_error_panic_hook::set_once();
WasmThreeLevelHierarchy {
inner: ThreeLevelHierarchy::with_defaults(),
}
}
#[wasm_bindgen(js_name = "withPhi")]
pub fn with_phi(phi: f64) -> WasmThreeLevelHierarchy {
console_error_panic_hook::set_once();
WasmThreeLevelHierarchy {
inner: ThreeLevelHierarchy::new(HierarchyConfig {
phi,
..Default::default()
}),
}
}
#[wasm_bindgen(js_name = "insertEdge")]
pub fn insert_edge(&mut self, u: u64, v: u64, weight: f64) {
self.inner.insert_edge(u, v, weight);
}
#[wasm_bindgen(js_name = "deleteEdge")]
pub fn delete_edge(&mut self, u: u64, v: u64) {
self.inner.delete_edge(u, v);
}
#[wasm_bindgen]
pub fn build(&mut self) {
self.inner.build();
}
#[wasm_bindgen]
pub fn stats(&self) -> JsValue {
let s = self.inner.stats();
let stats = HierarchyStatsJs {
num_expanders: s.num_expanders,
num_preclusters: s.num_preclusters,
num_clusters: s.num_clusters,
num_vertices: s.num_vertices,
num_edges: s.num_edges,
global_min_cut: s.global_min_cut,
avg_expander_size: s.avg_expander_size,
};
serde_wasm_bindgen::to_value(&stats).unwrap_or(JsValue::NULL)
}
#[wasm_bindgen(js_name = "globalMinCut")]
pub fn global_min_cut(&self) -> f64 {
self.inner.global_min_cut
}
#[wasm_bindgen]
pub fn vertices(&self) -> JsValue {
let verts: Vec<u64> = self.inner.vertices();
serde_wasm_bindgen::to_value(&verts).unwrap_or(JsValue::NULL)
}
}
#[derive(Serialize, Deserialize)]
struct LocalCutJs {
cut_value: f64,
vertices: Vec<u64>,
}
#[wasm_bindgen]
pub struct WasmLocalKCut {
inner: DeterministicLocalKCut,
num_vertices: usize,
num_edges: usize,
}
#[wasm_bindgen]
impl WasmLocalKCut {
#[wasm_bindgen(constructor)]
pub fn new(lambda_max: u64, volume_bound: usize, beta: usize) -> WasmLocalKCut {
console_error_panic_hook::set_once();
WasmLocalKCut {
inner: DeterministicLocalKCut::new(lambda_max, volume_bound, beta),
num_vertices: 0,
num_edges: 0,
}
}
#[wasm_bindgen(js_name = "insertEdge")]
pub fn insert_edge(&mut self, u: u64, v: u64, weight: f64) {
self.inner.insert_edge(u, v, weight);
self.num_edges += 1;
self.num_vertices = self.num_vertices.max((u.max(v) + 1) as usize);
}
#[wasm_bindgen(js_name = "deleteEdge")]
pub fn delete_edge(&mut self, u: u64, v: u64) {
self.inner.delete_edge(u, v);
self.num_edges = self.num_edges.saturating_sub(1);
}
#[wasm_bindgen]
pub fn query(&self, source: u64) -> JsValue {
let results = self.inner.query(source);
let cuts: Vec<LocalCutJs> = results
.into_iter()
.map(|c| LocalCutJs {
cut_value: c.cut_value,
vertices: c.vertices.into_iter().collect(),
})
.collect();
serde_wasm_bindgen::to_value(&cuts).unwrap_or(JsValue::NULL)
}
#[wasm_bindgen(js_name = "numVertices")]
pub fn num_vertices(&self) -> usize {
self.num_vertices
}
#[wasm_bindgen(js_name = "numEdges")]
pub fn num_edges(&self) -> usize {
self.num_edges
}
}
#[derive(Serialize, Deserialize)]
struct CurvePoint {
k: usize,
min_cut: u64,
}
#[derive(Serialize, Deserialize)]
struct ElbowResult {
k: usize,
drop: u64,
}
#[wasm_bindgen]
pub struct WasmMinCutWrapper {
inner: MinCutWrapper,
}
#[wasm_bindgen]
impl WasmMinCutWrapper {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmMinCutWrapper {
console_error_panic_hook::set_once();
let graph = Arc::new(DynamicGraph::new());
WasmMinCutWrapper {
inner: MinCutWrapper::new(graph),
}
}
#[wasm_bindgen(js_name = "insertEdge")]
pub fn insert_edge(&mut self, u: u64, v: u64) {
let time = self.inner.current_time() + 1;
self.inner.insert_edge(time, u, v);
}
#[wasm_bindgen(js_name = "deleteEdge")]
pub fn delete_edge(&mut self, u: u64, v: u64) {
let time = self.inner.current_time() + 1;
self.inner.delete_edge(time, u, v);
}
#[wasm_bindgen]
pub fn query(&mut self) -> f64 {
self.inner.min_cut_value() as f64
}
#[wasm_bindgen(js_name = "numInstances")]
pub fn num_instances(&self) -> usize {
self.inner.num_instances()
}
#[wasm_bindgen(js_name = "currentTime")]
pub fn current_time(&self) -> u64 {
self.inner.current_time()
}
#[wasm_bindgen(js_name = "queryWithCertification")]
pub fn query_with_certification(&mut self, source: u64) -> JsValue {
let (cut_value, certified) = self.inner.query_with_local_kcut(source);
let result = serde_json::json!({
"cut_value": cut_value,
"certified": certified,
});
serde_wasm_bindgen::to_value(&result).unwrap_or(JsValue::NULL)
}
#[wasm_bindgen(js_name = "localCuts")]
pub fn local_cuts(&self, source: u64, lambda_max: u64) -> JsValue {
let cuts = self.inner.local_cuts(source, lambda_max);
let result: Vec<LocalCutJs> = cuts
.into_iter()
.map(|(value, verts)| LocalCutJs {
cut_value: value,
vertices: verts,
})
.collect();
serde_wasm_bindgen::to_value(&result).unwrap_or(JsValue::NULL)
}
#[wasm_bindgen(js_name = "connectivityCurve")]
pub fn connectivity_curve(&self, ranked_edges: JsValue, k_max: usize) -> JsValue {
let edges: Vec<Vec<f64>> = serde_wasm_bindgen::from_value(ranked_edges).unwrap_or_default();
let ranked: Vec<(u64, u64, f64)> = edges
.into_iter()
.filter_map(|e| {
if e.len() >= 3 {
Some((e[0] as u64, e[1] as u64, e[2]))
} else {
None
}
})
.collect();
let curve = self.inner.connectivity_curve(&ranked, k_max);
let result: Vec<CurvePoint> = curve
.into_iter()
.map(|(k, min_cut)| CurvePoint { k, min_cut })
.collect();
serde_wasm_bindgen::to_value(&result).unwrap_or(JsValue::NULL)
}
#[wasm_bindgen(js_name = "findElbow")]
pub fn find_elbow(curve: JsValue) -> JsValue {
let points: Vec<CurvePoint> = serde_wasm_bindgen::from_value(curve).unwrap_or_default();
let curve_data: Vec<(usize, u64)> = points.into_iter().map(|p| (p.k, p.min_cut)).collect();
match MinCutWrapper::find_elbow(&curve_data) {
Some((k, drop)) => {
let result = ElbowResult { k, drop };
serde_wasm_bindgen::to_value(&result).unwrap_or(JsValue::NULL)
}
None => JsValue::NULL,
}
}
#[wasm_bindgen(js_name = "detectorQuality")]
pub fn detector_quality(&self, ranked_edges: JsValue, true_cut_size: usize) -> f64 {
let edges: Vec<Vec<f64>> = serde_wasm_bindgen::from_value(ranked_edges).unwrap_or_default();
let ranked: Vec<(u64, u64, f64)> = edges
.into_iter()
.filter_map(|e| {
if e.len() >= 3 {
Some((e[0] as u64, e[1] as u64, e[2]))
} else {
None
}
})
.collect();
self.inner.detector_quality(&ranked, true_cut_size)
}
}
#[cfg(test)]
mod tests {
use super::*;
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn test_new() {
let mincut = WasmMinCut::new().unwrap();
assert_eq!(mincut.num_vertices(), 0);
assert_eq!(mincut.num_edges(), 0);
}
#[wasm_bindgen_test]
fn test_insert_edge() {
let mut mincut = WasmMinCut::new().unwrap();
let result = mincut.insert_edge(0, 1, 1.0);
assert!(result.is_ok());
assert_eq!(mincut.num_edges(), 1);
}
#[wasm_bindgen_test]
fn test_min_cut_value() {
let mut mincut = WasmMinCut::new().unwrap();
mincut.insert_edge(0, 1, 1.0).unwrap();
mincut.insert_edge(1, 2, 2.0).unwrap();
mincut.insert_edge(0, 2, 1.5).unwrap();
let cut_value = mincut.min_cut_value();
assert!(cut_value > 0.0);
}
}