1use std::collections::{HashMap, VecDeque};
16
17use scirs2_core::ndarray::Array2;
18use scirs2_core::random::{Rng, RngExt, SeedableRng, StdRng};
19
20use crate::error::{GraphError, Result};
21
22fn extract_edges(adj: &Array2<f64>) -> Vec<(usize, usize, f64)> {
31 let n = adj.nrows();
32 let mut edges = Vec::new();
33 for i in 0..n {
34 for j in (i + 1)..n {
35 let w = adj[[i, j]];
36 if w > 0.0 {
37 edges.push((i, j, w.clamp(0.0, 1.0)));
39 }
40 }
41 }
42 edges
43}
44
45fn can_reach(n: usize, edges: &[(usize, usize, f64)], active: &[bool], s: usize, t: usize) -> bool {
49 if s == t {
50 return true;
51 }
52 let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
54 for (idx, &(u, v, _)) in edges.iter().enumerate() {
55 if active[idx] {
56 adj[u].push(v);
57 adj[v].push(u);
58 }
59 }
60 let mut visited = vec![false; n];
61 visited[s] = true;
62 let mut queue = VecDeque::new();
63 queue.push_back(s);
64 while let Some(node) = queue.pop_front() {
65 for &nb in &adj[node] {
66 if nb == t {
67 return true;
68 }
69 if !visited[nb] {
70 visited[nb] = true;
71 queue.push_back(nb);
72 }
73 }
74 }
75 false
76}
77
78fn is_fully_connected(n: usize, edges: &[(usize, usize, f64)], active: &[bool]) -> bool {
80 if n <= 1 {
81 return true;
82 }
83 let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
84 for (idx, &(u, v, _)) in edges.iter().enumerate() {
85 if active[idx] {
86 adj[u].push(v);
87 adj[v].push(u);
88 }
89 }
90 let mut visited = vec![false; n];
91 visited[0] = true;
92 let mut queue = VecDeque::new();
93 queue.push_back(0usize);
94 let mut count = 1usize;
95 while let Some(node) = queue.pop_front() {
96 for &nb in &adj[node] {
97 if !visited[nb] {
98 visited[nb] = true;
99 count += 1;
100 queue.push_back(nb);
101 }
102 }
103 }
104 count == n
105}
106
107#[derive(Debug, Clone)]
122pub struct NetworkReliability {
123 pub source: usize,
125 pub terminal: usize,
127}
128
129impl NetworkReliability {
130 pub fn new(source: usize, terminal: usize) -> Self {
132 Self { source, terminal }
133 }
134
135 pub fn monte_carlo(
145 &self,
146 adj: &Array2<f64>,
147 num_trials: usize,
148 seed: Option<u64>,
149 ) -> Result<f64> {
150 let n = adj.nrows();
151 if self.source >= n {
152 return Err(GraphError::InvalidParameter {
153 param: "source".into(),
154 value: self.source.to_string(),
155 expected: format!("< {n}"),
156 context: "NetworkReliability::monte_carlo".into(),
157 });
158 }
159 if self.terminal >= n {
160 return Err(GraphError::InvalidParameter {
161 param: "terminal".into(),
162 value: self.terminal.to_string(),
163 expected: format!("< {n}"),
164 context: "NetworkReliability::monte_carlo".into(),
165 });
166 }
167 if num_trials == 0 {
168 return Ok(0.0);
169 }
170
171 let edges = extract_edges(adj);
172 let m = edges.len();
173 let mut rng: StdRng = match seed {
174 Some(s) => StdRng::seed_from_u64(s),
175 None => StdRng::from_rng(&mut scirs2_core::random::rng()),
176 };
177
178 let mut successes = 0u64;
179 let mut active = vec![false; m];
180
181 for _ in 0..num_trials {
182 for (idx, &(_, _, p)) in edges.iter().enumerate() {
183 active[idx] = rng.random::<f64>() < p;
184 }
185 if can_reach(n, &edges, &active, self.source, self.terminal) {
186 successes += 1;
187 }
188 }
189
190 Ok(successes as f64 / num_trials as f64)
191 }
192
193 pub fn monte_carlo_with_ci(
198 &self,
199 adj: &Array2<f64>,
200 num_trials: usize,
201 seed: Option<u64>,
202 ) -> Result<(f64, f64)> {
203 let p_hat = self.monte_carlo(adj, num_trials, seed)?;
204 let half_width = if num_trials > 0 {
206 1.96 * (p_hat * (1.0 - p_hat) / num_trials as f64).sqrt()
207 } else {
208 1.0
209 };
210 Ok((p_hat, half_width))
211 }
212
213 pub fn exact(&self, adj: &Array2<f64>) -> Result<f64> {
218 let n = adj.nrows();
219 if self.source >= n || self.terminal >= n {
220 return Err(GraphError::InvalidParameter {
221 param: "source/terminal".into(),
222 value: format!("{}/{}", self.source, self.terminal),
223 expected: format!("< {n}"),
224 context: "NetworkReliability::exact".into(),
225 });
226 }
227 let edges = extract_edges(adj);
228 let m = edges.len();
229 if m > 25 {
230 return Err(GraphError::InvalidParameter {
231 param: "num_edges".into(),
232 value: m.to_string(),
233 expected: "<= 25 for exact computation".into(),
234 context: "NetworkReliability::exact".into(),
235 });
236 }
237
238 let mut total = 0.0_f64;
239 for mask in 0u32..(1u32 << m) {
240 let active: Vec<bool> = (0..m).map(|i| (mask >> i) & 1 == 1).collect();
241 let prob: f64 = edges
243 .iter()
244 .enumerate()
245 .map(|(i, &(_, _, p))| if active[i] { p } else { 1.0 - p })
246 .product();
247 if can_reach(n, &edges, &active, self.source, self.terminal) {
248 total += prob;
249 }
250 }
251 Ok(total)
252 }
253}
254
255#[derive(Debug, Clone, Default)]
263pub struct AllTerminalReliability;
264
265impl AllTerminalReliability {
266 pub fn new() -> Self {
268 Self
269 }
270
271 pub fn monte_carlo(
273 &self,
274 adj: &Array2<f64>,
275 num_trials: usize,
276 seed: Option<u64>,
277 ) -> Result<f64> {
278 let n = adj.nrows();
279 if n == 0 {
280 return Err(GraphError::InvalidGraph("empty adjacency".into()));
281 }
282 if num_trials == 0 {
283 return Ok(0.0);
284 }
285
286 let edges = extract_edges(adj);
287 let m = edges.len();
288 let mut rng: StdRng = match seed {
289 Some(s) => StdRng::seed_from_u64(s),
290 None => StdRng::from_rng(&mut scirs2_core::random::rng()),
291 };
292
293 let mut successes = 0u64;
294 let mut active = vec![false; m];
295
296 for _ in 0..num_trials {
297 for (idx, &(_, _, p)) in edges.iter().enumerate() {
298 active[idx] = rng.random::<f64>() < p;
299 }
300 if is_fully_connected(n, &edges, &active) {
301 successes += 1;
302 }
303 }
304
305 Ok(successes as f64 / num_trials as f64)
306 }
307
308 pub fn exact(&self, adj: &Array2<f64>) -> Result<f64> {
310 let n = adj.nrows();
311 let edges = extract_edges(adj);
312 let m = edges.len();
313 if m > 20 {
314 return Err(GraphError::InvalidParameter {
315 param: "num_edges".into(),
316 value: m.to_string(),
317 expected: "<= 20 for exact computation".into(),
318 context: "AllTerminalReliability::exact".into(),
319 });
320 }
321 let mut total = 0.0_f64;
322 for mask in 0u32..(1u32 << m) {
323 let active: Vec<bool> = (0..m).map(|i| (mask >> i) & 1 == 1).collect();
324 let prob: f64 = edges
325 .iter()
326 .enumerate()
327 .map(|(i, &(_, _, p))| if active[i] { p } else { 1.0 - p })
328 .product();
329 if is_fully_connected(n, &edges, &active) {
330 total += prob;
331 }
332 }
333 Ok(total)
334 }
335}
336
337#[derive(Debug, Clone)]
352pub struct ReliabilityPolynomial {
353 pub coeffs: Vec<u64>,
355 pub num_edges: usize,
357 pub num_nodes: usize,
359}
360
361impl ReliabilityPolynomial {
362 pub fn compute(adj: &Array2<f64>) -> Result<Self> {
367 let n = adj.nrows();
368 let edges: Vec<(usize, usize)> = (0..n)
370 .flat_map(|i| {
371 (i + 1..n).filter_map(move |j| {
372 if adj[[i, j]] > 0.0 {
373 Some((i, j))
374 } else {
375 None
376 }
377 })
378 })
379 .collect();
380 let m = edges.len();
381 if m > 20 {
382 return Err(GraphError::InvalidParameter {
383 param: "num_edges".into(),
384 value: m.to_string(),
385 expected: "<= 20 for polynomial computation".into(),
386 context: "ReliabilityPolynomial::compute".into(),
387 });
388 }
389
390 let mut coeffs = vec![0u64; m + 1];
391 for mask in 0u32..(1u32 << m) {
393 let k = mask.count_ones() as usize;
394 let active: Vec<bool> = (0..m).map(|i| (mask >> i) & 1 == 1).collect();
395 let active_edges: Vec<(usize, usize, f64)> =
397 edges.iter().map(|&(u, v)| (u, v, 1.0)).collect();
398 let active_flags: Vec<bool> = (0..m).map(|i| active[i]).collect();
399 if is_fully_connected(n, &active_edges, &active_flags) {
400 coeffs[k] += 1;
401 }
402 }
403
404 Ok(Self {
405 coeffs,
406 num_edges: m,
407 num_nodes: n,
408 })
409 }
410
411 pub fn evaluate(&self, p: f64) -> f64 {
415 let m = self.num_edges;
416 let q = 1.0 - p;
417 self.coeffs
418 .iter()
419 .enumerate()
420 .map(|(k, &c)| {
421 if c == 0 {
422 0.0
423 } else {
424 c as f64 * p.powi(k as i32) * q.powi((m - k) as i32)
425 }
426 })
427 .sum()
428 }
429
430 pub fn min_connected_edges(&self) -> usize {
432 self.coeffs
433 .iter()
434 .position(|&c| c > 0)
435 .unwrap_or(self.num_edges)
436 }
437
438 pub fn total_connected_subgraphs(&self) -> u64 {
440 self.coeffs.iter().sum()
441 }
442}
443
444#[derive(Debug, Clone)]
450enum BddNode {
451 Terminal(bool),
453 Internal { var: usize, low: usize, high: usize },
455}
456
457#[derive(Debug)]
470pub struct BDD {
471 nodes: Vec<BddNode>,
472 unique: HashMap<(usize, usize, usize), usize>,
474 num_edges: usize,
476 num_nodes: usize,
478 edges: Vec<(usize, usize)>,
480 root: usize,
482}
483
484impl BDD {
485 pub fn build_all_terminal(adj: &Array2<f64>) -> Result<Self> {
489 let n = adj.nrows();
490 let edges: Vec<(usize, usize)> = (0..n)
491 .flat_map(|i| {
492 (i + 1..n).filter_map(move |j| {
493 if adj[[i, j]] > 0.0 {
494 Some((i, j))
495 } else {
496 None
497 }
498 })
499 })
500 .collect();
501 let m = edges.len();
502 if m > 20 {
503 return Err(GraphError::InvalidParameter {
504 param: "num_edges".into(),
505 value: m.to_string(),
506 expected: "<= 20 for BDD".into(),
507 context: "BDD::build_all_terminal".into(),
508 });
509 }
510
511 let mut bdd = BDD {
512 nodes: Vec::new(),
513 unique: HashMap::new(),
514 num_edges: m,
515 num_nodes: n,
516 edges: edges.clone(),
517 root: 0,
518 };
519
520 bdd.nodes.push(BddNode::Terminal(false));
522 bdd.nodes.push(BddNode::Terminal(true));
523
524 let active_mask = (1u32 << m) - 1; let root = bdd.build_node(0, active_mask, n, &edges);
526 bdd.root = root;
527 Ok(bdd)
528 }
529
530 fn build_node(
535 &mut self,
536 var: usize,
537 forced_on: u32,
538 n_nodes: usize,
539 edges: &[(usize, usize)],
540 ) -> usize {
541 let m = edges.len();
542 if var == m {
543 let active: Vec<bool> = (0..m).map(|i| (forced_on >> i) & 1 == 1).collect();
545 let edge_data: Vec<(usize, usize, f64)> =
546 edges.iter().map(|&(u, v)| (u, v, 1.0)).collect();
547 let connected = is_fully_connected(n_nodes, &edge_data, &active);
548 return if connected { 1 } else { 0 };
549 }
550
551 let key = (var, forced_on as usize, 0);
555 if let Some(&idx) = self.unique.get(&key) {
556 return idx;
557 }
558
559 let low_forced = forced_on & !(1u32 << var);
562 let low = self.build_node(var + 1, low_forced, n_nodes, edges);
563
564 let high_forced = forced_on | (1u32 << var);
566 let high = self.build_node(var + 1, high_forced, n_nodes, edges);
567
568 if low == high {
570 self.unique.insert(key, low);
571 return low;
572 }
573
574 let idx = self.nodes.len();
575 self.nodes.push(BddNode::Internal { var, low, high });
576 self.unique.insert(key, idx);
577 idx
578 }
579
580 pub fn reliability(&self, probs: &[f64]) -> Result<f64> {
586 if probs.len() != self.num_edges {
587 return Err(GraphError::InvalidParameter {
588 param: "probs.len()".into(),
589 value: probs.len().to_string(),
590 expected: self.num_edges.to_string(),
591 context: "BDD::reliability".into(),
592 });
593 }
594 Ok(self.eval_node(self.root, probs))
595 }
596
597 fn eval_node(&self, node_idx: usize, probs: &[f64]) -> f64 {
598 match &self.nodes[node_idx] {
599 BddNode::Terminal(t) => {
600 if *t {
601 1.0
602 } else {
603 0.0
604 }
605 }
606 BddNode::Internal { var, low, high } => {
607 let p = probs[*var];
608 let q = 1.0 - p;
609 q * self.eval_node(*low, probs) + p * self.eval_node(*high, probs)
610 }
611 }
612 }
613
614 pub fn size(&self) -> usize {
616 self.nodes.len()
617 }
618
619 pub fn num_edges(&self) -> usize {
621 self.num_edges
622 }
623
624 pub fn num_network_nodes(&self) -> usize {
626 self.num_nodes
627 }
628}
629
630#[derive(Debug, Clone)]
636pub struct FailureNode {
637 pub failed_edges: Vec<usize>,
639 pub is_cut: bool,
641 pub probability: f64,
643 pub children: Vec<FailureNode>,
645}
646
647#[derive(Debug)]
654pub struct ComponentFailureTree {
655 pub root: FailureNode,
657 pub minimal_cuts: Vec<Vec<usize>>,
659 pub num_edges: usize,
661 pub num_nodes: usize,
663}
664
665impl ComponentFailureTree {
666 pub fn build(adj: &Array2<f64>, max_depth: usize) -> Result<Self> {
674 let n = adj.nrows();
675 let edges: Vec<(usize, usize, f64)> = extract_edges(adj);
676 let m = edges.len();
677
678 let mut minimal_cuts = Vec::new();
679 let root_active = vec![true; m];
680 let is_cut = !is_fully_connected(n, &edges, &root_active);
681
682 let root = FailureNode {
683 failed_edges: Vec::new(),
684 is_cut,
685 probability: 1.0,
686 children: Vec::new(),
687 };
688
689 let mut tree = ComponentFailureTree {
690 root,
691 minimal_cuts: Vec::new(),
692 num_edges: m,
693 num_nodes: n,
694 };
695
696 let failed: Vec<usize> = Vec::new();
698 let edge_probs: Vec<f64> = edges.iter().map(|&(_, _, p)| p).collect();
699 failure_tree_expand_node(
700 &mut tree.root.children,
701 &failed,
702 0,
703 max_depth,
704 n,
705 &edges,
706 &edge_probs,
707 m,
708 &mut minimal_cuts,
709 );
710 tree.minimal_cuts = minimal_cuts;
711
712 Ok(tree)
713 }
714
715 pub fn minimal_cuts(&self) -> &[Vec<usize>] {
717 &self.minimal_cuts
718 }
719
720 pub fn unreliability_upper_bound(&self) -> f64 {
725 Self::sum_cut_probs(&self.root)
726 }
727
728 fn sum_cut_probs(node: &FailureNode) -> f64 {
729 let self_contribution = if node.is_cut { node.probability } else { 0.0 };
730 let child_sum: f64 = node.children.iter().map(Self::sum_cut_probs).sum();
731 self_contribution + child_sum
732 }
733}
734
735#[allow(clippy::too_many_arguments)]
736fn failure_tree_expand_node(
737 children: &mut Vec<FailureNode>,
738 parent_failed: &[usize],
739 start_edge: usize,
740 remaining_depth: usize,
741 n: usize,
742 edges: &[(usize, usize, f64)],
743 edge_probs: &[f64],
744 m: usize,
745 minimal_cuts: &mut Vec<Vec<usize>>,
746) {
747 if remaining_depth == 0 {
748 return;
749 }
750 for e in start_edge..m {
751 let mut failed = parent_failed.to_vec();
752 failed.push(e);
753
754 let prob: f64 = (0..m)
756 .map(|i| {
757 if failed.contains(&i) {
758 1.0 - edge_probs[i]
759 } else {
760 edge_probs[i]
761 }
762 })
763 .product();
764
765 let active: Vec<bool> = (0..m).map(|i| !failed.contains(&i)).collect();
766 let is_cut = !is_fully_connected(n, edges, &active);
767
768 if is_cut {
770 let parent_active: Vec<bool> = (0..m).map(|i| !parent_failed.contains(&i)).collect();
772 let parent_is_cut = !is_fully_connected(n, edges, &parent_active);
773 if !parent_is_cut {
774 minimal_cuts.push(failed.clone());
775 }
776 }
777
778 let mut node = FailureNode {
779 failed_edges: failed.clone(),
780 is_cut,
781 probability: prob,
782 children: Vec::new(),
783 };
784
785 if !is_cut && remaining_depth > 1 {
786 failure_tree_expand_node(
787 &mut node.children,
788 &failed,
789 e + 1,
790 remaining_depth - 1,
791 n,
792 edges,
793 edge_probs,
794 m,
795 minimal_cuts,
796 );
797 }
798
799 children.push(node);
800 }
801}
802
803#[cfg(test)]
808mod tests {
809 use super::*;
810
811 fn triangle_adj(p: f64) -> Array2<f64> {
812 let mut adj = Array2::<f64>::zeros((3, 3));
813 adj[[0, 1]] = p;
814 adj[[1, 0]] = p;
815 adj[[1, 2]] = p;
816 adj[[2, 1]] = p;
817 adj[[0, 2]] = p;
818 adj[[2, 0]] = p;
819 adj
820 }
821
822 fn path_adj_p(n: usize, p: f64) -> Array2<f64> {
823 let mut adj = Array2::<f64>::zeros((n, n));
824 for i in 0..(n - 1) {
825 adj[[i, i + 1]] = p;
826 adj[[i + 1, i]] = p;
827 }
828 adj
829 }
830
831 #[test]
832 fn test_two_terminal_exact_vs_mc() {
833 let p = 0.8;
835 let adj = path_adj_p(3, p);
836 let rel = NetworkReliability::new(0, 2);
837 let exact = rel.exact(&adj).unwrap();
838 assert!((exact - p * p).abs() < 1e-9, "Exact: {exact} vs {}", p * p);
840 let mc = rel.monte_carlo(&adj, 50000, Some(99)).unwrap();
841 assert!((mc - exact).abs() < 0.02, "MC: {mc} vs exact: {exact}");
842 }
843
844 #[test]
845 fn test_all_terminal_triangle_exact() {
846 let p = 0.9;
847 let adj = triangle_adj(p);
848 let rel = AllTerminalReliability::new();
849 let exact = rel.exact(&adj).unwrap();
850 let expected = 3.0 * p * p * (1.0 - p) + p * p * p;
853 assert!(
854 (exact - expected).abs() < 1e-9,
855 "Exact: {exact} vs {expected}"
856 );
857 let mc = rel.monte_carlo(&adj, 50000, Some(7)).unwrap();
858 assert!((mc - exact).abs() < 0.02);
859 }
860
861 #[test]
862 fn test_reliability_polynomial() {
863 let adj = triangle_adj(1.0); let poly = ReliabilityPolynomial::compute(&adj).unwrap();
865 assert_eq!(poly.num_edges, 3);
866 let r1 = poly.evaluate(1.0);
868 assert!((r1 - 1.0).abs() < 1e-9, "R(1)={r1}");
869 let r0 = poly.evaluate(0.0);
870 assert!((r0 - 0.0).abs() < 1e-9, "R(0)={r0}");
871 }
872
873 #[test]
874 fn test_bdd_vs_exact() {
875 let p = 0.75;
876 let adj = triangle_adj(p);
877 let bdd = BDD::build_all_terminal(&adj).unwrap();
878 let probs = vec![p; 3];
879 let bdd_result = bdd.reliability(&probs).unwrap();
880 let exact = AllTerminalReliability::new().exact(&adj).unwrap();
881 assert!(
882 (bdd_result - exact).abs() < 1e-9,
883 "BDD: {bdd_result} vs exact: {exact}"
884 );
885 }
886
887 #[test]
888 fn test_component_failure_tree() {
889 let adj = triangle_adj(0.9);
890 let tree = ComponentFailureTree::build(&adj, 2).unwrap();
891 assert!(!tree.minimal_cuts().is_empty());
893 }
894
895 #[test]
896 fn test_reliability_ci() {
897 let adj = path_adj_p(3, 0.9);
898 let rel = NetworkReliability::new(0, 2);
899 let (p_hat, hw) = rel.monte_carlo_with_ci(&adj, 10000, Some(1)).unwrap();
900 assert!((0.0..=1.0).contains(&p_hat));
901 assert!((0.0..=0.1).contains(&hw));
902 }
903}