rust_igraph/algorithms/properties/
betweenness_cutoff.rs1use std::collections::VecDeque;
11
12use crate::core::{Graph, IgraphResult, VertexId};
13
14pub fn betweenness_cutoff(graph: &Graph, cutoff: u32) -> IgraphResult<Vec<f64>> {
43 let n = graph.vcount();
44 let n_us = n as usize;
45 let mut betweenness = vec![0.0_f64; n_us];
46
47 if n == 0 {
48 return Ok(betweenness);
49 }
50
51 let mut sigma = vec![0.0_f64; n_us];
52 let mut dist = vec![-1_i64; n_us];
53 let mut pred: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
54 let mut stack: Vec<VertexId> = Vec::with_capacity(n_us);
55 let mut delta = vec![0.0_f64; n_us];
56 let cutoff_i64 = i64::from(cutoff);
57
58 for s in 0..n {
59 sigma.fill(0.0);
60 dist.fill(-1);
61 for slot in &mut pred {
62 slot.clear();
63 }
64 stack.clear();
65 delta.fill(0.0);
66
67 sigma[s as usize] = 1.0;
68 dist[s as usize] = 0;
69 let mut queue: VecDeque<VertexId> = VecDeque::new();
70 queue.push_back(s);
71
72 while let Some(v) = queue.pop_front() {
73 stack.push(v);
74 let v_dist = dist[v as usize];
75
76 if v_dist >= cutoff_i64 {
77 continue;
78 }
79
80 for w in graph.neighbors(v)? {
81 if dist[w as usize] < 0 {
82 queue.push_back(w);
83 dist[w as usize] = v_dist + 1;
84 }
85 if dist[w as usize] == v_dist + 1 {
86 sigma[w as usize] += sigma[v as usize];
87 pred[w as usize].push(v);
88 }
89 }
90 }
91
92 while let Some(w) = stack.pop() {
93 for &v in &pred[w as usize] {
94 delta[v as usize] +=
95 (sigma[v as usize] / sigma[w as usize]) * (1.0 + delta[w as usize]);
96 }
97 if w != s {
98 betweenness[w as usize] += delta[w as usize];
99 }
100 }
101 }
102
103 if !graph.is_directed() {
104 for slot in &mut betweenness {
105 *slot /= 2.0;
106 }
107 }
108
109 Ok(betweenness)
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 fn close(actual: &[f64], expected: &[f64], tol: f64) {
117 assert_eq!(actual.len(), expected.len(), "length mismatch");
118 for (i, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
119 assert!((a - e).abs() < tol, "vertex {i}: actual={a} expected={e}");
120 }
121 }
122
123 #[test]
124 fn empty_graph() {
125 let g = Graph::new(0, false).unwrap();
126 let b = betweenness_cutoff(&g, 1).unwrap();
127 assert!(b.is_empty());
128 }
129
130 #[test]
131 fn single_vertex() {
132 let g = Graph::new(1, false).unwrap();
133 let b = betweenness_cutoff(&g, 1).unwrap();
134 assert_eq!(b, vec![0.0]);
135 }
136
137 #[test]
138 fn disconnected() {
139 let g = Graph::new(3, false).unwrap();
140 let b = betweenness_cutoff(&g, 5).unwrap();
141 assert_eq!(b, vec![0.0, 0.0, 0.0]);
142 }
143
144 #[test]
145 fn path_full_cutoff() {
146 let mut g = Graph::new(5, false).unwrap();
148 g.add_edges(vec![(0, 1), (1, 2), (2, 3), (3, 4)]).unwrap();
149 let b = betweenness_cutoff(&g, 100).unwrap();
150 close(&b, &[0.0, 3.0, 4.0, 3.0, 0.0], 1e-12);
151 }
152
153 #[test]
154 fn path_cutoff_1() {
155 let mut g = Graph::new(5, false).unwrap();
158 g.add_edges(vec![(0, 1), (1, 2), (2, 3), (3, 4)]).unwrap();
159 let b = betweenness_cutoff(&g, 1).unwrap();
160 close(&b, &[0.0, 0.0, 0.0, 0.0, 0.0], 1e-12);
161 }
162
163 #[test]
164 fn path_cutoff_2() {
165 let mut g = Graph::new(5, false).unwrap();
170 g.add_edges(vec![(0, 1), (1, 2), (2, 3), (3, 4)]).unwrap();
171 let b = betweenness_cutoff(&g, 2).unwrap();
172 close(&b, &[0.0, 1.0, 1.0, 1.0, 0.0], 1e-12);
173 }
174
175 #[test]
176 fn star_cutoff_2() {
177 let mut g = Graph::new(5, false).unwrap();
180 g.add_edges(vec![(0, 1), (0, 2), (0, 3), (0, 4)]).unwrap();
181 let b = betweenness_cutoff(&g, 2).unwrap();
182 close(&b, &[6.0, 0.0, 0.0, 0.0, 0.0], 1e-12);
184 }
185
186 #[test]
187 fn triangle() {
188 let mut g = Graph::new(3, false).unwrap();
190 g.add_edges(vec![(0, 1), (1, 2), (2, 0)]).unwrap();
191 let b = betweenness_cutoff(&g, 10).unwrap();
192 close(&b, &[0.0, 0.0, 0.0], 1e-12);
193 }
194
195 #[test]
196 fn directed_path() {
197 let mut g = Graph::new(4, true).unwrap();
199 g.add_edges(vec![(0, 1), (1, 2), (2, 3)]).unwrap();
200 let b = betweenness_cutoff(&g, 100).unwrap();
201 close(&b, &[0.0, 2.0, 2.0, 0.0], 1e-12);
203 }
204
205 #[test]
206 fn directed_cutoff_2() {
207 let mut g = Graph::new(4, true).unwrap();
211 g.add_edges(vec![(0, 1), (1, 2), (2, 3)]).unwrap();
212 let b = betweenness_cutoff(&g, 2).unwrap();
213 close(&b, &[0.0, 1.0, 1.0, 0.0], 1e-12);
214 }
215
216 #[test]
217 fn cutoff_zero() {
218 let mut g = Graph::new(3, false).unwrap();
219 g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
220 let b = betweenness_cutoff(&g, 0).unwrap();
221 close(&b, &[0.0, 0.0, 0.0], 1e-12);
223 }
224}