1use crate::base::{EdgeWeight, Hypergraph, IndexType, Node};
7use crate::error::{GraphError, Result};
8use std::collections::{HashMap, HashSet, VecDeque};
9
10#[derive(Debug, Clone)]
12pub struct HypergraphCut<N: Node> {
13 pub partition_a: HashSet<N>,
15 pub partition_b: HashSet<N>,
17 pub cut_hyperedges: Vec<usize>,
19 pub cut_weight: f64,
21}
22
23#[derive(Debug, Clone)]
25pub struct MinimalTransversal<N: Node> {
26 pub nodes: HashSet<N>,
28 pub hit_hyperedges: Vec<usize>,
30 pub size: usize,
32}
33
34#[allow(dead_code)]
46pub fn minimal_transversals<N, E, Ix>(
47 hypergraph: &Hypergraph<N, E, Ix>,
48 max_size: Option<usize>,
49) -> Vec<MinimalTransversal<N>>
50where
51 N: Node + Clone + Ord + std::fmt::Debug,
52 E: EdgeWeight,
53 Ix: IndexType,
54{
55 let mut transversals = Vec::new();
56 let nodes: Vec<N> = hypergraph.nodes().cloned().collect();
57 let hyperedges = hypergraph.hyperedges();
58
59 if nodes.is_empty() || hyperedges.is_empty() {
60 return transversals;
61 }
62
63 let max_size = max_size.unwrap_or(nodes.len());
64
65 for size in 1..=max_size.min(nodes.len()) {
67 let combinations = generate_combinations(&nodes, size);
68
69 for candidate in combinations {
70 let candidate_set: HashSet<N> = candidate.into_iter().collect();
71
72 let mut hit_hyperedges = Vec::new();
74 let mut hits_all = true;
75
76 for hyperedge in hyperedges {
77 let hyperedge_nodes: std::collections::HashSet<N> =
78 hyperedge.nodes.iter().cloned().collect();
79 if candidate_set
80 .intersection(&hyperedge_nodes)
81 .next()
82 .is_some()
83 {
84 hit_hyperedges.push(hyperedge.id);
85 } else {
86 hits_all = false;
87 break;
88 }
89 }
90
91 if hits_all {
92 let mut is_minimal = true;
94 for existing in &transversals {
95 if existing.nodes.is_subset(&candidate_set) && existing.nodes != candidate_set {
96 is_minimal = false;
97 break;
98 }
99 }
100
101 if is_minimal {
102 transversals
104 .retain(|t| !candidate_set.is_subset(&t.nodes) || t.nodes == candidate_set);
105
106 transversals.push(MinimalTransversal {
107 size: candidate_set.len(),
108 nodes: candidate_set,
109 hit_hyperedges,
110 });
111 }
112 }
113 }
114 }
115
116 transversals
117}
118
119#[allow(dead_code)]
121fn generate_combinations<T: Clone>(items: &[T], k: usize) -> Vec<Vec<T>> {
122 if k == 0 {
123 return vec![vec![]];
124 }
125 if k > items.len() {
126 return vec![];
127 }
128 if k == items.len() {
129 return vec![items.to_vec()];
130 }
131
132 let mut result = Vec::new();
133
134 let first = items[0].clone();
136 let rest = &items[1..];
137 for mut combo in generate_combinations(rest, k - 1) {
138 combo.insert(0, first.clone());
139 result.push(combo);
140 }
141
142 result.extend(generate_combinations(rest, k));
144
145 result
146}
147
148#[allow(dead_code)]
160pub fn minimum_vertex_cut<N, E, Ix>(
161 hypergraph: &Hypergraph<N, E, Ix>,
162 source: &N,
163 target: &N,
164) -> Result<HypergraphCut<N>>
165where
166 N: Node + Clone + Ord + std::fmt::Debug,
167 E: EdgeWeight
168 + Clone
169 + Default
170 + scirs2_core::numeric::Zero
171 + std::ops::Add<Output = E>
172 + Into<f64>,
173 Ix: IndexType,
174{
175 if !hypergraph.has_node(source) || !hypergraph.has_node(target) {
176 return Err(GraphError::node_not_found("node"));
177 }
178
179 if source == target {
180 return Err(GraphError::InvalidGraph(
181 "Source and target cannot be the same".to_string(),
182 ));
183 }
184
185 let _graph = hypergraph.to_graph();
188
189 let all_nodes: HashSet<N> = hypergraph.nodes().cloned().collect();
192
193 let mut partition_a = HashSet::new();
195 let mut partition_b = HashSet::new();
196
197 partition_a.insert(source.clone());
198 partition_b.insert(target.clone());
199
200 for node in &all_nodes {
202 if node != source && node != target {
203 if partition_a.len() <= partition_b.len() {
204 partition_a.insert(node.clone());
205 } else {
206 partition_b.insert(node.clone());
207 }
208 }
209 }
210
211 let mut cut_hyperedges = Vec::new();
213 let mut cut_weight = 0.0;
214
215 for hyperedge in hypergraph.hyperedges() {
216 let hyperedge_nodes: std::collections::HashSet<N> =
217 hyperedge.nodes.iter().cloned().collect();
218 let has_a = hyperedge_nodes.intersection(&partition_a).next().is_some();
219 let has_b = hyperedge_nodes.intersection(&partition_b).next().is_some();
220
221 if has_a && has_b {
222 cut_hyperedges.push(hyperedge.id);
223 cut_weight += hyperedge.weight.clone().into();
224 }
225 }
226
227 Ok(HypergraphCut {
228 partition_a,
229 partition_b,
230 cut_hyperedges,
231 cut_weight,
232 })
233}
234
235#[allow(dead_code)]
248pub fn hyperedge_connectivity<N, E, Ix>(
249 hypergraph: &Hypergraph<N, E, Ix>,
250 source: &N,
251 target: &N,
252) -> Result<usize>
253where
254 N: Node + Clone + Ord + std::fmt::Debug,
255 E: EdgeWeight + Clone,
256 Ix: IndexType,
257{
258 if !hypergraph.has_node(source) || !hypergraph.has_node(target) {
259 return Err(GraphError::node_not_found("node"));
260 }
261
262 if source == target {
263 return Ok(0);
264 }
265
266 if !hypergraph.are_connected(source, target) {
268 return Ok(0);
269 }
270
271 let hyperedges = hypergraph.hyperedges();
273 let connecting_hyperedges = hypergraph.connecting_hyperedges(source, target);
274
275 if !connecting_hyperedges.is_empty() {
277 return Ok(1);
278 }
279
280 for hyperedge in hyperedges {
283 let mut temphypergraph: Hypergraph<N, E, Ix> = Hypergraph::new();
284
285 for node in hypergraph.nodes() {
287 temphypergraph.add_node(node.clone());
288 }
289
290 for other_hyperedge in hyperedges {
292 if other_hyperedge.id != hyperedge.id {
293 temphypergraph.add_hyperedge(
294 other_hyperedge.nodes.clone(),
295 other_hyperedge.weight.clone(),
296 )?;
297 }
298 }
299
300 if !temphypergraph.are_connected(source, target) {
302 return Ok(1);
303 }
304 }
305
306 Ok(2)
309}
310
311#[allow(dead_code)]
322pub fn hypergraph_diameter<N, E, Ix>(hypergraph: &Hypergraph<N, E, Ix>) -> Option<usize>
323where
324 N: Node + Clone + Ord + std::fmt::Debug,
325 E: EdgeWeight,
326 Ix: IndexType,
327{
328 let nodes: Vec<N> = hypergraph.nodes().cloned().collect();
329 if nodes.len() < 2 {
330 return Some(0);
331 }
332
333 let mut max_distance = 0;
334
335 for i in 0..nodes.len() {
336 for j in (i + 1)..nodes.len() {
337 if let Some(distance) = hypergraph_distance(hypergraph, &nodes[i], &nodes[j]) {
338 max_distance = max_distance.max(distance);
339 } else {
340 return None;
342 }
343 }
344 }
345
346 Some(max_distance)
347}
348
349#[allow(dead_code)]
361pub fn hypergraph_distance<N, E, Ix>(
362 hypergraph: &Hypergraph<N, E, Ix>,
363 source: &N,
364 target: &N,
365) -> Option<usize>
366where
367 N: Node + Clone + Ord + std::fmt::Debug,
368 E: EdgeWeight,
369 Ix: IndexType,
370{
371 if source == target {
372 return Some(0);
373 }
374
375 if !hypergraph.has_node(source) || !hypergraph.has_node(target) {
376 return None;
377 }
378
379 let mut visited = HashSet::new();
381 let mut queue = VecDeque::new();
382 let mut distances = HashMap::new();
383
384 queue.push_back(source.clone());
385 visited.insert(source.clone());
386 distances.insert(source.clone(), 0);
387
388 while let Some(current) = queue.pop_front() {
389 let current_distance = distances[¤t];
390
391 for hyperedge in hypergraph.hyperedges_containing_node(¤t) {
393 for neighbor in &hyperedge.nodes {
395 if neighbor != ¤t && !visited.contains(neighbor) {
396 visited.insert(neighbor.clone());
397 distances.insert(neighbor.clone(), current_distance + 1);
398 queue.push_back(neighbor.clone());
399
400 if neighbor == target {
401 return Some(current_distance + 1);
402 }
403 }
404 }
405 }
406 }
407
408 None
409}
410
411#[allow(dead_code)]
424pub fn hypergraph_connected_components<N, E, Ix>(
425 hypergraph: &Hypergraph<N, E, Ix>,
426) -> Vec<HashSet<N>>
427where
428 N: Node + Clone + Ord + std::fmt::Debug,
429 E: EdgeWeight,
430 Ix: IndexType,
431{
432 let mut components = Vec::new();
433 let mut visited: HashSet<N> = HashSet::new();
434
435 for node in hypergraph.nodes() {
436 if !visited.contains(node) {
437 let mut component = HashSet::new();
438 let mut stack = vec![node.clone()];
439
440 while let Some(current) = stack.pop() {
441 if !visited.contains(¤t) {
442 visited.insert(current.clone());
443 component.insert(current.clone());
444
445 let neighbors = hypergraph.neighbors(¤t);
447 for neighbor in neighbors {
448 if !visited.contains(&neighbor) {
449 stack.push(neighbor);
450 }
451 }
452 }
453 }
454
455 if !component.is_empty() {
456 components.push(component);
457 }
458 }
459 }
460
461 components
462}
463
464#[allow(dead_code)]
474pub fn ishypergraph_connected<N, E, Ix>(hypergraph: &Hypergraph<N, E, Ix>) -> bool
475where
476 N: Node + Clone + Ord + std::fmt::Debug,
477 E: EdgeWeight,
478 Ix: IndexType,
479{
480 let components = hypergraph_connected_components(hypergraph);
481 components.len() <= 1
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487 use crate::base::Hypergraph;
488
489 #[test]
490 fn test_minimal_transversals_simple() {
491 let mut hypergraph: Hypergraph<i32, f64> = Hypergraph::new();
492
493 hypergraph.add_hyperedge_from_vec(vec![1, 2], 1.0).unwrap();
495 hypergraph.add_hyperedge_from_vec(vec![2, 3], 1.0).unwrap();
496 hypergraph.add_hyperedge_from_vec(vec![1, 3], 1.0).unwrap();
497
498 let transversals = minimal_transversals(&hypergraph, Some(2));
499
500 assert!(!transversals.is_empty());
502
503 for transversal in &transversals {
505 assert_eq!(transversal.hit_hyperedges.len(), 3);
506 }
507 }
508
509 #[test]
510 fn testhypergraph_distance() {
511 let mut hypergraph: Hypergraph<&str, f64> = Hypergraph::new();
512
513 hypergraph
515 .add_hyperedge_from_vec(vec!["A", "B"], 1.0)
516 .unwrap();
517 hypergraph
518 .add_hyperedge_from_vec(vec!["B", "C"], 1.0)
519 .unwrap();
520 hypergraph
521 .add_hyperedge_from_vec(vec!["C", "D"], 1.0)
522 .unwrap();
523
524 assert_eq!(hypergraph_distance(&hypergraph, &"A", &"A"), Some(0));
525 assert_eq!(hypergraph_distance(&hypergraph, &"A", &"B"), Some(1));
526 assert_eq!(hypergraph_distance(&hypergraph, &"A", &"C"), Some(2));
527 assert_eq!(hypergraph_distance(&hypergraph, &"A", &"D"), Some(3));
528
529 hypergraph.add_node("E");
531 assert_eq!(hypergraph_distance(&hypergraph, &"A", &"E"), None);
532 }
533
534 #[test]
535 fn testhypergraph_diameter() {
536 let mut hypergraph: Hypergraph<i32, f64> = Hypergraph::new();
537
538 hypergraph.add_hyperedge_from_vec(vec![1, 2], 1.0).unwrap();
540 hypergraph.add_hyperedge_from_vec(vec![2, 3], 1.0).unwrap();
541 hypergraph.add_hyperedge_from_vec(vec![3, 4], 1.0).unwrap();
542
543 assert_eq!(hypergraph_diameter(&hypergraph), Some(3));
544
545 hypergraph.add_hyperedge_from_vec(vec![1, 4], 1.0).unwrap();
547 assert_eq!(hypergraph_diameter(&hypergraph), Some(2));
548 }
549
550 #[test]
551 fn testhypergraph_connected_components() {
552 let mut hypergraph: Hypergraph<&str, f64> = Hypergraph::new();
553
554 hypergraph
556 .add_hyperedge_from_vec(vec!["A", "B"], 1.0)
557 .unwrap();
558 hypergraph
559 .add_hyperedge_from_vec(vec!["B", "C"], 1.0)
560 .unwrap();
561
562 hypergraph
563 .add_hyperedge_from_vec(vec!["D", "E"], 1.0)
564 .unwrap();
565
566 let components = hypergraph_connected_components(&hypergraph);
567 assert_eq!(components.len(), 2);
568
569 let sizes: Vec<usize> = components.iter().map(|c| c.len()).collect();
571 assert!(sizes.contains(&3)); assert!(sizes.contains(&2)); }
574
575 #[test]
576 fn test_ishypergraph_connected() {
577 let mut hypergraph: Hypergraph<i32, f64> = Hypergraph::new();
578
579 hypergraph
581 .add_hyperedge_from_vec(vec![1, 2, 3], 1.0)
582 .unwrap();
583 assert!(ishypergraph_connected(&hypergraph));
584
585 hypergraph.add_node(4);
587 assert!(!ishypergraph_connected(&hypergraph));
588
589 hypergraph.add_hyperedge_from_vec(vec![3, 4], 1.0).unwrap();
591 assert!(ishypergraph_connected(&hypergraph));
592 }
593
594 #[test]
595 fn test_minimum_vertex_cut() {
596 let mut hypergraph: Hypergraph<&str, f64> = Hypergraph::new();
597
598 hypergraph
600 .add_hyperedge_from_vec(vec!["A", "B"], 1.0)
601 .unwrap();
602 hypergraph
603 .add_hyperedge_from_vec(vec!["B", "C"], 1.0)
604 .unwrap();
605 hypergraph
606 .add_hyperedge_from_vec(vec!["C", "D"], 1.0)
607 .unwrap();
608
609 let cut = minimum_vertex_cut(&hypergraph, &"A", &"D").unwrap();
610
611 assert!(!cut.partition_a.is_empty());
612 assert!(!cut.partition_b.is_empty());
613 assert!(!cut.cut_hyperedges.is_empty());
614 assert!(cut.cut_weight > 0.0);
615 }
616
617 #[test]
618 fn test_hyperedge_connectivity() {
619 let mut hypergraph: Hypergraph<i32, f64> = Hypergraph::new();
620
621 hypergraph.add_hyperedge_from_vec(vec![1, 2], 1.0).unwrap();
623 assert_eq!(hyperedge_connectivity(&hypergraph, &1, &2).unwrap(), 1);
624
625 hypergraph
627 .add_hyperedge_from_vec(vec![1, 3, 4], 1.2)
628 .unwrap();
629
630 assert!(
632 hypergraph.are_connected(&1, &4),
633 "Nodes 1 and 4 should be directly connected in the same hyperedge"
634 );
635
636 let connectivity = hyperedge_connectivity(&hypergraph, &1, &4).unwrap();
637 assert!(
638 connectivity >= 1,
639 "Expected connectivity >= 1, got {connectivity}"
640 );
641
642 hypergraph.add_node(5); assert_eq!(hyperedge_connectivity(&hypergraph, &1, &5).unwrap(), 0);
645
646 assert_eq!(hyperedge_connectivity(&hypergraph, &1, &1).unwrap(), 0);
648 }
649
650 #[test]
651 fn test_generate_combinations() {
652 let items = vec![1, 2, 3, 4];
653
654 let combinations = generate_combinations(&items, 2);
655 assert_eq!(combinations.len(), 6); let combinations = generate_combinations(&items, 0);
658 assert_eq!(combinations.len(), 1);
659 assert!(combinations[0].is_empty());
660
661 let combinations = generate_combinations(&items, 5);
662 assert!(combinations.is_empty());
663 }
664
665 #[test]
666 fn test_emptyhypergraph() {
667 let hypergraph: Hypergraph<i32, f64> = Hypergraph::new();
668
669 let transversals = minimal_transversals(&hypergraph, None);
670 assert!(transversals.is_empty());
671
672 assert_eq!(hypergraph_diameter(&hypergraph), Some(0));
673
674 let components = hypergraph_connected_components(&hypergraph);
675 assert!(components.is_empty());
676
677 assert!(ishypergraph_connected(&hypergraph));
678 }
679}