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 + Clone + Default + num_traits::Zero + std::ops::Add<Output = E> + Into<f64>,
168 Ix: IndexType,
169{
170 if !hypergraph.has_node(source) || !hypergraph.has_node(target) {
171 return Err(GraphError::node_not_found("node"));
172 }
173
174 if source == target {
175 return Err(GraphError::InvalidGraph(
176 "Source and target cannot be the same".to_string(),
177 ));
178 }
179
180 let _graph = hypergraph.to_graph();
183
184 let all_nodes: HashSet<N> = hypergraph.nodes().cloned().collect();
187
188 let mut partition_a = HashSet::new();
190 let mut partition_b = HashSet::new();
191
192 partition_a.insert(source.clone());
193 partition_b.insert(target.clone());
194
195 for node in &all_nodes {
197 if node != source && node != target {
198 if partition_a.len() <= partition_b.len() {
199 partition_a.insert(node.clone());
200 } else {
201 partition_b.insert(node.clone());
202 }
203 }
204 }
205
206 let mut cut_hyperedges = Vec::new();
208 let mut cut_weight = 0.0;
209
210 for hyperedge in hypergraph.hyperedges() {
211 let hyperedge_nodes: std::collections::HashSet<N> =
212 hyperedge.nodes.iter().cloned().collect();
213 let has_a = hyperedge_nodes.intersection(&partition_a).next().is_some();
214 let has_b = hyperedge_nodes.intersection(&partition_b).next().is_some();
215
216 if has_a && has_b {
217 cut_hyperedges.push(hyperedge.id);
218 cut_weight += hyperedge.weight.clone().into();
219 }
220 }
221
222 Ok(HypergraphCut {
223 partition_a,
224 partition_b,
225 cut_hyperedges,
226 cut_weight,
227 })
228}
229
230#[allow(dead_code)]
243pub fn hyperedge_connectivity<N, E, Ix>(
244 hypergraph: &Hypergraph<N, E, Ix>,
245 source: &N,
246 target: &N,
247) -> Result<usize>
248where
249 N: Node + Clone + Ord + std::fmt::Debug,
250 E: EdgeWeight + Clone,
251 Ix: IndexType,
252{
253 if !hypergraph.has_node(source) || !hypergraph.has_node(target) {
254 return Err(GraphError::node_not_found("node"));
255 }
256
257 if source == target {
258 return Ok(0);
259 }
260
261 if !hypergraph.are_connected(source, target) {
263 return Ok(0);
264 }
265
266 let hyperedges = hypergraph.hyperedges();
268 let connecting_hyperedges = hypergraph.connecting_hyperedges(source, target);
269
270 if !connecting_hyperedges.is_empty() {
272 return Ok(1);
273 }
274
275 for hyperedge in hyperedges {
278 let mut temphypergraph: Hypergraph<N, E, Ix> = Hypergraph::new();
279
280 for node in hypergraph.nodes() {
282 temphypergraph.add_node(node.clone());
283 }
284
285 for other_hyperedge in hyperedges {
287 if other_hyperedge.id != hyperedge.id {
288 temphypergraph.add_hyperedge(
289 other_hyperedge.nodes.clone(),
290 other_hyperedge.weight.clone(),
291 )?;
292 }
293 }
294
295 if !temphypergraph.are_connected(source, target) {
297 return Ok(1);
298 }
299 }
300
301 Ok(2)
304}
305
306#[allow(dead_code)]
317pub fn hypergraph_diameter<N, E, Ix>(hypergraph: &Hypergraph<N, E, Ix>) -> Option<usize>
318where
319 N: Node + Clone + Ord + std::fmt::Debug,
320 E: EdgeWeight,
321 Ix: IndexType,
322{
323 let nodes: Vec<N> = hypergraph.nodes().cloned().collect();
324 if nodes.len() < 2 {
325 return Some(0);
326 }
327
328 let mut max_distance = 0;
329
330 for i in 0..nodes.len() {
331 for j in (i + 1)..nodes.len() {
332 if let Some(distance) = hypergraph_distance(hypergraph, &nodes[i], &nodes[j]) {
333 max_distance = max_distance.max(distance);
334 } else {
335 return None;
337 }
338 }
339 }
340
341 Some(max_distance)
342}
343
344#[allow(dead_code)]
356pub fn hypergraph_distance<N, E, Ix>(
357 hypergraph: &Hypergraph<N, E, Ix>,
358 source: &N,
359 target: &N,
360) -> Option<usize>
361where
362 N: Node + Clone + Ord + std::fmt::Debug,
363 E: EdgeWeight,
364 Ix: IndexType,
365{
366 if source == target {
367 return Some(0);
368 }
369
370 if !hypergraph.has_node(source) || !hypergraph.has_node(target) {
371 return None;
372 }
373
374 let mut visited = HashSet::new();
376 let mut queue = VecDeque::new();
377 let mut distances = HashMap::new();
378
379 queue.push_back(source.clone());
380 visited.insert(source.clone());
381 distances.insert(source.clone(), 0);
382
383 while let Some(current) = queue.pop_front() {
384 let current_distance = distances[¤t];
385
386 for hyperedge in hypergraph.hyperedges_containing_node(¤t) {
388 for neighbor in &hyperedge.nodes {
390 if neighbor != ¤t && !visited.contains(neighbor) {
391 visited.insert(neighbor.clone());
392 distances.insert(neighbor.clone(), current_distance + 1);
393 queue.push_back(neighbor.clone());
394
395 if neighbor == target {
396 return Some(current_distance + 1);
397 }
398 }
399 }
400 }
401 }
402
403 None
404}
405
406#[allow(dead_code)]
419pub fn hypergraph_connected_components<N, E, Ix>(
420 hypergraph: &Hypergraph<N, E, Ix>,
421) -> Vec<HashSet<N>>
422where
423 N: Node + Clone + Ord + std::fmt::Debug,
424 E: EdgeWeight,
425 Ix: IndexType,
426{
427 let mut components = Vec::new();
428 let mut visited: HashSet<N> = HashSet::new();
429
430 for node in hypergraph.nodes() {
431 if !visited.contains(node) {
432 let mut component = HashSet::new();
433 let mut stack = vec![node.clone()];
434
435 while let Some(current) = stack.pop() {
436 if !visited.contains(¤t) {
437 visited.insert(current.clone());
438 component.insert(current.clone());
439
440 let neighbors = hypergraph.neighbors(¤t);
442 for neighbor in neighbors {
443 if !visited.contains(&neighbor) {
444 stack.push(neighbor);
445 }
446 }
447 }
448 }
449
450 if !component.is_empty() {
451 components.push(component);
452 }
453 }
454 }
455
456 components
457}
458
459#[allow(dead_code)]
469pub fn ishypergraph_connected<N, E, Ix>(hypergraph: &Hypergraph<N, E, Ix>) -> bool
470where
471 N: Node + Clone + Ord + std::fmt::Debug,
472 E: EdgeWeight,
473 Ix: IndexType,
474{
475 let components = hypergraph_connected_components(hypergraph);
476 components.len() <= 1
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482 use crate::base::Hypergraph;
483
484 #[test]
485 fn test_minimal_transversals_simple() {
486 let mut hypergraph: Hypergraph<i32, f64> = Hypergraph::new();
487
488 hypergraph.add_hyperedge_from_vec(vec![1, 2], 1.0).unwrap();
490 hypergraph.add_hyperedge_from_vec(vec![2, 3], 1.0).unwrap();
491 hypergraph.add_hyperedge_from_vec(vec![1, 3], 1.0).unwrap();
492
493 let transversals = minimal_transversals(&hypergraph, Some(2));
494
495 assert!(!transversals.is_empty());
497
498 for transversal in &transversals {
500 assert_eq!(transversal.hit_hyperedges.len(), 3);
501 }
502 }
503
504 #[test]
505 fn testhypergraph_distance() {
506 let mut hypergraph: Hypergraph<&str, f64> = Hypergraph::new();
507
508 hypergraph
510 .add_hyperedge_from_vec(vec!["A", "B"], 1.0)
511 .unwrap();
512 hypergraph
513 .add_hyperedge_from_vec(vec!["B", "C"], 1.0)
514 .unwrap();
515 hypergraph
516 .add_hyperedge_from_vec(vec!["C", "D"], 1.0)
517 .unwrap();
518
519 assert_eq!(hypergraph_distance(&hypergraph, &"A", &"A"), Some(0));
520 assert_eq!(hypergraph_distance(&hypergraph, &"A", &"B"), Some(1));
521 assert_eq!(hypergraph_distance(&hypergraph, &"A", &"C"), Some(2));
522 assert_eq!(hypergraph_distance(&hypergraph, &"A", &"D"), Some(3));
523
524 hypergraph.add_node("E");
526 assert_eq!(hypergraph_distance(&hypergraph, &"A", &"E"), None);
527 }
528
529 #[test]
530 fn testhypergraph_diameter() {
531 let mut hypergraph: Hypergraph<i32, f64> = Hypergraph::new();
532
533 hypergraph.add_hyperedge_from_vec(vec![1, 2], 1.0).unwrap();
535 hypergraph.add_hyperedge_from_vec(vec![2, 3], 1.0).unwrap();
536 hypergraph.add_hyperedge_from_vec(vec![3, 4], 1.0).unwrap();
537
538 assert_eq!(hypergraph_diameter(&hypergraph), Some(3));
539
540 hypergraph.add_hyperedge_from_vec(vec![1, 4], 1.0).unwrap();
542 assert_eq!(hypergraph_diameter(&hypergraph), Some(2));
543 }
544
545 #[test]
546 fn testhypergraph_connected_components() {
547 let mut hypergraph: Hypergraph<&str, f64> = Hypergraph::new();
548
549 hypergraph
551 .add_hyperedge_from_vec(vec!["A", "B"], 1.0)
552 .unwrap();
553 hypergraph
554 .add_hyperedge_from_vec(vec!["B", "C"], 1.0)
555 .unwrap();
556
557 hypergraph
558 .add_hyperedge_from_vec(vec!["D", "E"], 1.0)
559 .unwrap();
560
561 let components = hypergraph_connected_components(&hypergraph);
562 assert_eq!(components.len(), 2);
563
564 let sizes: Vec<usize> = components.iter().map(|c| c.len()).collect();
566 assert!(sizes.contains(&3)); assert!(sizes.contains(&2)); }
569
570 #[test]
571 fn test_ishypergraph_connected() {
572 let mut hypergraph: Hypergraph<i32, f64> = Hypergraph::new();
573
574 hypergraph
576 .add_hyperedge_from_vec(vec![1, 2, 3], 1.0)
577 .unwrap();
578 assert!(ishypergraph_connected(&hypergraph));
579
580 hypergraph.add_node(4);
582 assert!(!ishypergraph_connected(&hypergraph));
583
584 hypergraph.add_hyperedge_from_vec(vec![3, 4], 1.0).unwrap();
586 assert!(ishypergraph_connected(&hypergraph));
587 }
588
589 #[test]
590 fn test_minimum_vertex_cut() {
591 let mut hypergraph: Hypergraph<&str, f64> = Hypergraph::new();
592
593 hypergraph
595 .add_hyperedge_from_vec(vec!["A", "B"], 1.0)
596 .unwrap();
597 hypergraph
598 .add_hyperedge_from_vec(vec!["B", "C"], 1.0)
599 .unwrap();
600 hypergraph
601 .add_hyperedge_from_vec(vec!["C", "D"], 1.0)
602 .unwrap();
603
604 let cut = minimum_vertex_cut(&hypergraph, &"A", &"D").unwrap();
605
606 assert!(!cut.partition_a.is_empty());
607 assert!(!cut.partition_b.is_empty());
608 assert!(!cut.cut_hyperedges.is_empty());
609 assert!(cut.cut_weight > 0.0);
610 }
611
612 #[test]
613 fn test_hyperedge_connectivity() {
614 let mut hypergraph: Hypergraph<i32, f64> = Hypergraph::new();
615
616 hypergraph.add_hyperedge_from_vec(vec![1, 2], 1.0).unwrap();
618 assert_eq!(hyperedge_connectivity(&hypergraph, &1, &2).unwrap(), 1);
619
620 hypergraph
622 .add_hyperedge_from_vec(vec![1, 3, 4], 1.2)
623 .unwrap();
624
625 assert!(
627 hypergraph.are_connected(&1, &4),
628 "Nodes 1 and 4 should be directly connected in the same hyperedge"
629 );
630
631 let connectivity = hyperedge_connectivity(&hypergraph, &1, &4).unwrap();
632 assert!(
633 connectivity >= 1,
634 "Expected connectivity >= 1, got {connectivity}"
635 );
636
637 hypergraph.add_node(5); assert_eq!(hyperedge_connectivity(&hypergraph, &1, &5).unwrap(), 0);
640
641 assert_eq!(hyperedge_connectivity(&hypergraph, &1, &1).unwrap(), 0);
643 }
644
645 #[test]
646 fn test_generate_combinations() {
647 let items = vec![1, 2, 3, 4];
648
649 let combinations = generate_combinations(&items, 2);
650 assert_eq!(combinations.len(), 6); let combinations = generate_combinations(&items, 0);
653 assert_eq!(combinations.len(), 1);
654 assert!(combinations[0].is_empty());
655
656 let combinations = generate_combinations(&items, 5);
657 assert!(combinations.is_empty());
658 }
659
660 #[test]
661 fn test_emptyhypergraph() {
662 let hypergraph: Hypergraph<i32, f64> = Hypergraph::new();
663
664 let transversals = minimal_transversals(&hypergraph, None);
665 assert!(transversals.is_empty());
666
667 assert_eq!(hypergraph_diameter(&hypergraph), Some(0));
668
669 let components = hypergraph_connected_components(&hypergraph);
670 assert!(components.is_empty());
671
672 assert!(ishypergraph_connected(&hypergraph));
673 }
674}