1use ruvector_mincut::cluster::hierarchy::{HierarchyConfig, ThreeLevelHierarchy};
38use ruvector_mincut::localkcut::deterministic::DeterministicLocalKCut;
39use ruvector_mincut::{DynamicGraph, DynamicMinCut, MinCutBuilder, MinCutConfig, MinCutWrapper};
40use serde::{Deserialize, Serialize};
41use std::sync::Arc;
42use wasm_bindgen::prelude::*;
43
44#[wasm_bindgen]
46pub struct WasmMinCut {
47 inner: DynamicMinCut,
48}
49
50#[derive(Serialize, Deserialize)]
51struct EdgeInput {
52 u: u64,
53 v: u64,
54 weight: f64,
55}
56
57#[derive(Serialize, Deserialize)]
58struct Partition {
59 s: Vec<u64>,
60 t: Vec<u64>,
61}
62
63#[derive(Serialize, Deserialize)]
64struct Edge {
65 u: u64,
66 v: u64,
67 weight: f64,
68}
69
70#[derive(Serialize, Deserialize)]
71struct Stats {
72 num_vertices: usize,
73 num_edges: usize,
74 min_cut_value: f64,
75 is_connected: bool,
76 num_operations: usize,
77}
78
79#[wasm_bindgen]
80impl WasmMinCut {
81 #[wasm_bindgen(constructor)]
83 pub fn new() -> Result<WasmMinCut, JsError> {
84 console_error_panic_hook::set_once();
85
86 Ok(WasmMinCut {
87 inner: DynamicMinCut::new(MinCutConfig::default()),
88 })
89 }
90
91 #[wasm_bindgen(js_name = "fromEdges")]
102 pub fn from_edges(edges: JsValue) -> Result<WasmMinCut, JsError> {
103 console_error_panic_hook::set_once();
104
105 let edges_vec: Vec<Vec<f64>> = serde_wasm_bindgen::from_value(edges)
107 .map_err(|e| JsError::new(&format!("Failed to parse edges: {}", e)))?;
108
109 let mut edge_tuples = Vec::with_capacity(edges_vec.len());
111
112 for edge in edges_vec {
113 if edge.len() != 3 {
114 return Err(JsError::new("Each edge must be [u, v, weight]"));
115 }
116
117 let u = edge[0] as u64;
118 let v = edge[1] as u64;
119 let weight = edge[2];
120
121 edge_tuples.push((u, v, weight));
122 }
123
124 let inner = MinCutBuilder::new()
125 .with_edges(edge_tuples)
126 .build()
127 .map_err(|e| JsError::new(&format!("Failed to build mincut: {}", e)))?;
128
129 Ok(WasmMinCut { inner })
130 }
131
132 #[wasm_bindgen(js_name = "insertEdge")]
142 pub fn insert_edge(&mut self, u: u64, v: u64, weight: f64) -> Result<f64, JsError> {
143 self.inner
144 .insert_edge(u, v, weight)
145 .map_err(|e| JsError::new(&format!("Failed to insert edge: {}", e)))
146 }
147
148 #[wasm_bindgen(js_name = "deleteEdge")]
157 pub fn delete_edge(&mut self, u: u64, v: u64) -> Result<f64, JsError> {
158 self.inner
159 .delete_edge(u, v)
160 .map_err(|e| JsError::new(&format!("Failed to delete edge: {}", e)))
161 }
162
163 #[wasm_bindgen(js_name = "minCutValue")]
168 pub fn min_cut_value(&self) -> f64 {
169 self.inner.min_cut_value()
170 }
171
172 #[wasm_bindgen]
184 pub fn partition(&self) -> JsValue {
185 let (s_set, t_set) = self.inner.partition();
186
187 let partition = Partition {
188 s: s_set.into_iter().collect(),
189 t: t_set.into_iter().collect(),
190 };
191
192 serde_wasm_bindgen::to_value(&partition).unwrap_or(JsValue::NULL)
193 }
194
195 #[wasm_bindgen(js_name = "cutEdges")]
206 pub fn cut_edges(&self) -> JsValue {
207 let edges = self.inner.cut_edges();
208
209 let edge_list: Vec<Edge> = edges
210 .into_iter()
211 .map(|e| Edge {
212 u: e.source,
213 v: e.target,
214 weight: e.weight,
215 })
216 .collect();
217
218 serde_wasm_bindgen::to_value(&edge_list).unwrap_or(JsValue::NULL)
219 }
220
221 #[wasm_bindgen(js_name = "numVertices")]
223 pub fn num_vertices(&self) -> usize {
224 self.inner.num_vertices()
225 }
226
227 #[wasm_bindgen(js_name = "numEdges")]
229 pub fn num_edges(&self) -> usize {
230 self.inner.num_edges()
231 }
232
233 #[wasm_bindgen(js_name = "isConnected")]
238 pub fn is_connected(&self) -> bool {
239 self.inner.is_connected()
240 }
241
242 #[wasm_bindgen]
259 pub fn stats(&self) -> JsValue {
260 let algo_stats = self.inner.stats();
261 let stats = Stats {
262 num_vertices: self.inner.num_vertices(),
263 num_edges: self.inner.num_edges(),
264 min_cut_value: self.inner.min_cut_value(),
265 is_connected: self.inner.is_connected(),
266 num_operations: (algo_stats.insertions + algo_stats.deletions + algo_stats.queries)
267 as usize,
268 };
269
270 serde_wasm_bindgen::to_value(&stats).unwrap_or(JsValue::NULL)
271 }
272
273 #[wasm_bindgen(js_name = "updateEdge")]
283 pub fn update_edge(&mut self, u: u64, v: u64, new_weight: f64) -> Result<f64, JsError> {
284 let _ = self.inner.delete_edge(u, v);
286
287 self.inner
289 .insert_edge(u, v, new_weight)
290 .map_err(|e| JsError::new(&format!("Failed to update edge: {}", e)))
291 }
292
293 #[wasm_bindgen(js_name = "batchInsert")]
307 pub fn batch_insert(&mut self, edges: JsValue) -> Result<f64, JsError> {
308 let edges_vec: Vec<Vec<f64>> = serde_wasm_bindgen::from_value(edges)
309 .map_err(|e| JsError::new(&format!("Failed to parse edges: {}", e)))?;
310
311 for edge in edges_vec {
312 if edge.len() != 3 {
313 return Err(JsError::new("Each edge must be [u, v, weight]"));
314 }
315
316 let u = edge[0] as u64;
317 let v = edge[1] as u64;
318 let weight = edge[2];
319
320 self.inner.insert_edge(u, v, weight).map_err(|e| {
321 JsError::new(&format!("Failed to insert edge [{}, {}]: {}", u, v, e))
322 })?;
323 }
324
325 Ok(self.inner.min_cut_value())
326 }
327
328 #[wasm_bindgen(js_name = "batchDelete")]
342 pub fn batch_delete(&mut self, edges: JsValue) -> Result<f64, JsError> {
343 let edges_vec: Vec<Vec<f64>> = serde_wasm_bindgen::from_value(edges)
344 .map_err(|e| JsError::new(&format!("Failed to parse edges: {}", e)))?;
345
346 for edge in edges_vec {
347 if edge.len() < 2 {
348 return Err(JsError::new("Each edge must be [u, v] or [u, v, weight]"));
349 }
350
351 let u = edge[0] as u64;
352 let v = edge[1] as u64;
353
354 self.inner.delete_edge(u, v).map_err(|e| {
355 JsError::new(&format!("Failed to delete edge [{}, {}]: {}", u, v, e))
356 })?;
357 }
358
359 Ok(self.inner.min_cut_value())
360 }
361
362 #[wasm_bindgen]
364 pub fn clear(&mut self) {
365 self.inner = DynamicMinCut::new(MinCutConfig::default());
366 }
367}
368
369#[wasm_bindgen(start)]
373pub fn init() {
374 console_error_panic_hook::set_once();
375}
376
377#[wasm_bindgen(js_name = "getVersion")]
379pub fn get_version() -> String {
380 env!("CARGO_PKG_VERSION").to_string()
381}
382
383#[derive(Serialize, Deserialize)]
389struct HierarchyStatsJs {
390 num_expanders: usize,
391 num_preclusters: usize,
392 num_clusters: usize,
393 num_vertices: usize,
394 num_edges: usize,
395 global_min_cut: f64,
396 avg_expander_size: f64,
397}
398
399#[wasm_bindgen]
406pub struct WasmThreeLevelHierarchy {
407 inner: ThreeLevelHierarchy,
408}
409
410#[wasm_bindgen]
411impl WasmThreeLevelHierarchy {
412 #[wasm_bindgen(constructor)]
414 pub fn new() -> WasmThreeLevelHierarchy {
415 console_error_panic_hook::set_once();
416 WasmThreeLevelHierarchy {
417 inner: ThreeLevelHierarchy::with_defaults(),
418 }
419 }
420
421 #[wasm_bindgen(js_name = "withPhi")]
423 pub fn with_phi(phi: f64) -> WasmThreeLevelHierarchy {
424 console_error_panic_hook::set_once();
425 WasmThreeLevelHierarchy {
426 inner: ThreeLevelHierarchy::new(HierarchyConfig {
427 phi,
428 ..Default::default()
429 }),
430 }
431 }
432
433 #[wasm_bindgen(js_name = "insertEdge")]
435 pub fn insert_edge(&mut self, u: u64, v: u64, weight: f64) {
436 self.inner.insert_edge(u, v, weight);
437 }
438
439 #[wasm_bindgen(js_name = "deleteEdge")]
441 pub fn delete_edge(&mut self, u: u64, v: u64) {
442 self.inner.delete_edge(u, v);
443 }
444
445 #[wasm_bindgen]
449 pub fn build(&mut self) {
450 self.inner.build();
451 }
452
453 #[wasm_bindgen]
455 pub fn stats(&self) -> JsValue {
456 let s = self.inner.stats();
457 let stats = HierarchyStatsJs {
458 num_expanders: s.num_expanders,
459 num_preclusters: s.num_preclusters,
460 num_clusters: s.num_clusters,
461 num_vertices: s.num_vertices,
462 num_edges: s.num_edges,
463 global_min_cut: s.global_min_cut,
464 avg_expander_size: s.avg_expander_size,
465 };
466 serde_wasm_bindgen::to_value(&stats).unwrap_or(JsValue::NULL)
467 }
468
469 #[wasm_bindgen(js_name = "globalMinCut")]
471 pub fn global_min_cut(&self) -> f64 {
472 self.inner.global_min_cut
473 }
474
475 #[wasm_bindgen]
477 pub fn vertices(&self) -> JsValue {
478 let verts: Vec<u64> = self.inner.vertices();
479 serde_wasm_bindgen::to_value(&verts).unwrap_or(JsValue::NULL)
480 }
481}
482
483#[derive(Serialize, Deserialize)]
489struct LocalCutJs {
490 cut_value: f64,
491 vertices: Vec<u64>,
492}
493
494#[wasm_bindgen]
501pub struct WasmLocalKCut {
502 inner: DeterministicLocalKCut,
503 num_vertices: usize,
504 num_edges: usize,
505}
506
507#[wasm_bindgen]
508impl WasmLocalKCut {
509 #[wasm_bindgen(constructor)]
516 pub fn new(lambda_max: u64, volume_bound: usize, beta: usize) -> WasmLocalKCut {
517 console_error_panic_hook::set_once();
518 WasmLocalKCut {
519 inner: DeterministicLocalKCut::new(lambda_max, volume_bound, beta),
520 num_vertices: 0,
521 num_edges: 0,
522 }
523 }
524
525 #[wasm_bindgen(js_name = "insertEdge")]
527 pub fn insert_edge(&mut self, u: u64, v: u64, weight: f64) {
528 self.inner.insert_edge(u, v, weight);
529 self.num_edges += 1;
530 self.num_vertices = self.num_vertices.max((u.max(v) + 1) as usize);
532 }
533
534 #[wasm_bindgen(js_name = "deleteEdge")]
536 pub fn delete_edge(&mut self, u: u64, v: u64) {
537 self.inner.delete_edge(u, v);
538 self.num_edges = self.num_edges.saturating_sub(1);
539 }
540
541 #[wasm_bindgen]
545 pub fn query(&self, source: u64) -> JsValue {
546 let results = self.inner.query(source);
547 let cuts: Vec<LocalCutJs> = results
548 .into_iter()
549 .map(|c| LocalCutJs {
550 cut_value: c.cut_value,
551 vertices: c.vertices.into_iter().collect(),
552 })
553 .collect();
554 serde_wasm_bindgen::to_value(&cuts).unwrap_or(JsValue::NULL)
555 }
556
557 #[wasm_bindgen(js_name = "numVertices")]
559 pub fn num_vertices(&self) -> usize {
560 self.num_vertices
561 }
562
563 #[wasm_bindgen(js_name = "numEdges")]
565 pub fn num_edges(&self) -> usize {
566 self.num_edges
567 }
568}
569
570#[derive(Serialize, Deserialize)]
576struct CurvePoint {
577 k: usize,
578 min_cut: u64,
579}
580
581#[derive(Serialize, Deserialize)]
583struct ElbowResult {
584 k: usize,
585 drop: u64,
586}
587
588#[wasm_bindgen]
596pub struct WasmMinCutWrapper {
597 inner: MinCutWrapper,
598}
599
600#[wasm_bindgen]
601impl WasmMinCutWrapper {
602 #[wasm_bindgen(constructor)]
604 pub fn new() -> WasmMinCutWrapper {
605 console_error_panic_hook::set_once();
606 let graph = Arc::new(DynamicGraph::new());
607 WasmMinCutWrapper {
608 inner: MinCutWrapper::new(graph),
609 }
610 }
611
612 #[wasm_bindgen(js_name = "insertEdge")]
614 pub fn insert_edge(&mut self, u: u64, v: u64) {
615 let time = self.inner.current_time() + 1;
616 self.inner.insert_edge(time, u, v);
617 }
618
619 #[wasm_bindgen(js_name = "deleteEdge")]
621 pub fn delete_edge(&mut self, u: u64, v: u64) {
622 let time = self.inner.current_time() + 1;
623 self.inner.delete_edge(time, u, v);
624 }
625
626 #[wasm_bindgen]
628 pub fn query(&mut self) -> f64 {
629 self.inner.min_cut_value() as f64
630 }
631
632 #[wasm_bindgen(js_name = "numInstances")]
634 pub fn num_instances(&self) -> usize {
635 self.inner.num_instances()
636 }
637
638 #[wasm_bindgen(js_name = "currentTime")]
640 pub fn current_time(&self) -> u64 {
641 self.inner.current_time()
642 }
643
644 #[wasm_bindgen(js_name = "queryWithCertification")]
648 pub fn query_with_certification(&mut self, source: u64) -> JsValue {
649 let (cut_value, certified) = self.inner.query_with_local_kcut(source);
650 let result = serde_json::json!({
651 "cut_value": cut_value,
652 "certified": certified,
653 });
654 serde_wasm_bindgen::to_value(&result).unwrap_or(JsValue::NULL)
655 }
656
657 #[wasm_bindgen(js_name = "localCuts")]
659 pub fn local_cuts(&self, source: u64, lambda_max: u64) -> JsValue {
660 let cuts = self.inner.local_cuts(source, lambda_max);
661 let result: Vec<LocalCutJs> = cuts
662 .into_iter()
663 .map(|(value, verts)| LocalCutJs {
664 cut_value: value,
665 vertices: verts,
666 })
667 .collect();
668 serde_wasm_bindgen::to_value(&result).unwrap_or(JsValue::NULL)
669 }
670
671 #[wasm_bindgen(js_name = "connectivityCurve")]
680 pub fn connectivity_curve(&self, ranked_edges: JsValue, k_max: usize) -> JsValue {
681 let edges: Vec<Vec<f64>> = serde_wasm_bindgen::from_value(ranked_edges).unwrap_or_default();
682
683 let ranked: Vec<(u64, u64, f64)> = edges
684 .into_iter()
685 .filter_map(|e| {
686 if e.len() >= 3 {
687 Some((e[0] as u64, e[1] as u64, e[2]))
688 } else {
689 None
690 }
691 })
692 .collect();
693
694 let curve = self.inner.connectivity_curve(&ranked, k_max);
695 let result: Vec<CurvePoint> = curve
696 .into_iter()
697 .map(|(k, min_cut)| CurvePoint { k, min_cut })
698 .collect();
699
700 serde_wasm_bindgen::to_value(&result).unwrap_or(JsValue::NULL)
701 }
702
703 #[wasm_bindgen(js_name = "findElbow")]
707 pub fn find_elbow(curve: JsValue) -> JsValue {
708 let points: Vec<CurvePoint> = serde_wasm_bindgen::from_value(curve).unwrap_or_default();
709
710 let curve_data: Vec<(usize, u64)> = points.into_iter().map(|p| (p.k, p.min_cut)).collect();
711
712 match MinCutWrapper::find_elbow(&curve_data) {
713 Some((k, drop)) => {
714 let result = ElbowResult { k, drop };
715 serde_wasm_bindgen::to_value(&result).unwrap_or(JsValue::NULL)
716 }
717 None => JsValue::NULL,
718 }
719 }
720
721 #[wasm_bindgen(js_name = "detectorQuality")]
730 pub fn detector_quality(&self, ranked_edges: JsValue, true_cut_size: usize) -> f64 {
731 let edges: Vec<Vec<f64>> = serde_wasm_bindgen::from_value(ranked_edges).unwrap_or_default();
732
733 let ranked: Vec<(u64, u64, f64)> = edges
734 .into_iter()
735 .filter_map(|e| {
736 if e.len() >= 3 {
737 Some((e[0] as u64, e[1] as u64, e[2]))
738 } else {
739 None
740 }
741 })
742 .collect();
743
744 self.inner.detector_quality(&ranked, true_cut_size)
745 }
746}
747
748#[cfg(test)]
749mod tests {
750 use super::*;
751 use wasm_bindgen_test::*;
752
753 #[wasm_bindgen_test]
754 fn test_new() {
755 let mincut = WasmMinCut::new().unwrap();
756 assert_eq!(mincut.num_vertices(), 0);
757 assert_eq!(mincut.num_edges(), 0);
758 }
759
760 #[wasm_bindgen_test]
761 fn test_insert_edge() {
762 let mut mincut = WasmMinCut::new().unwrap();
763 let result = mincut.insert_edge(0, 1, 1.0);
764 assert!(result.is_ok());
765 assert_eq!(mincut.num_edges(), 1);
766 }
767
768 #[wasm_bindgen_test]
769 fn test_min_cut_value() {
770 let mut mincut = WasmMinCut::new().unwrap();
771 mincut.insert_edge(0, 1, 1.0).unwrap();
772 mincut.insert_edge(1, 2, 2.0).unwrap();
773 mincut.insert_edge(0, 2, 1.5).unwrap();
774
775 let cut_value = mincut.min_cut_value();
776 assert!(cut_value > 0.0);
777 }
778}