1use std::collections::VecDeque;
12
13use crate::core::{Graph, IgraphResult, VertexId};
14
15fn all_neighbors(graph: &Graph, v: VertexId, directed: bool) -> IgraphResult<Vec<VertexId>> {
16 if directed && graph.is_directed() {
17 graph.neighbors(v)
18 } else if graph.is_directed() {
19 let mut out = graph.out_neighbors_vec(v)?;
20 out.extend(graph.in_neighbors_vec(v)?);
21 Ok(out)
22 } else {
23 graph.neighbors(v)
24 }
25}
26
27pub fn betweenness_subset(
58 graph: &Graph,
59 sources: &[VertexId],
60 targets: &[VertexId],
61 directed: bool,
62) -> IgraphResult<Vec<f64>> {
63 let n = graph.vcount();
64 let n_us = n as usize;
65 let mut betweenness = vec![0.0_f64; n_us];
66
67 if n == 0 || sources.is_empty() || targets.is_empty() {
68 return Ok(betweenness);
69 }
70
71 let mut is_target = vec![false; n_us];
72 for &t in targets {
73 if (t as usize) < n_us {
74 is_target[t as usize] = true;
75 }
76 }
77
78 let mut sigma = vec![0.0_f64; n_us];
79 let mut dist = vec![-1_i64; n_us];
80 let mut pred: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
81 let mut stack: Vec<VertexId> = Vec::with_capacity(n_us);
82 let mut delta = vec![0.0_f64; n_us];
83
84 for &s in sources {
85 if s >= n {
86 continue;
87 }
88
89 sigma.fill(0.0);
90 dist.fill(-1);
91 for slot in &mut pred {
92 slot.clear();
93 }
94 stack.clear();
95 delta.fill(0.0);
96
97 sigma[s as usize] = 1.0;
98 dist[s as usize] = 0;
99 let mut queue: VecDeque<VertexId> = VecDeque::new();
100 queue.push_back(s);
101
102 while let Some(v) = queue.pop_front() {
103 stack.push(v);
104 let v_dist = dist[v as usize];
105
106 for w in all_neighbors(graph, v, directed)? {
107 if dist[w as usize] < 0 {
108 queue.push_back(w);
109 dist[w as usize] = v_dist + 1;
110 }
111 if dist[w as usize] == v_dist + 1 {
112 sigma[w as usize] += sigma[v as usize];
113 pred[w as usize].push(v);
114 }
115 }
116 }
117
118 while let Some(w) = stack.pop() {
119 let coeff = if is_target[w as usize] {
120 (1.0 + delta[w as usize]) / sigma[w as usize]
121 } else {
122 delta[w as usize] / sigma[w as usize]
123 };
124
125 for &v in &pred[w as usize] {
126 delta[v as usize] += sigma[v as usize] * coeff;
127 }
128
129 if w != s {
130 betweenness[w as usize] += delta[w as usize];
131 }
132 }
133 }
134
135 if !directed || !graph.is_directed() {
136 for slot in &mut betweenness {
137 *slot /= 2.0;
138 }
139 }
140
141 Ok(betweenness)
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 fn close(actual: &[f64], expected: &[f64], tol: f64) {
149 assert_eq!(actual.len(), expected.len(), "length mismatch");
150 for (i, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
151 assert!((a - e).abs() < tol, "vertex {i}: actual={a} expected={e}");
152 }
153 }
154
155 #[test]
156 fn empty_graph() {
157 let g = Graph::new(0, false).unwrap();
158 let b = betweenness_subset(&g, &[0], &[0], true).unwrap();
159 assert!(b.is_empty());
160 }
161
162 #[test]
163 fn no_sources() {
164 let mut g = Graph::new(3, false).unwrap();
165 g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
166 let b = betweenness_subset(&g, &[], &[0, 1, 2], true).unwrap();
167 close(&b, &[0.0, 0.0, 0.0], 1e-12);
168 }
169
170 #[test]
171 fn no_targets() {
172 let mut g = Graph::new(3, false).unwrap();
173 g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
174 let b = betweenness_subset(&g, &[0, 1, 2], &[], true).unwrap();
175 close(&b, &[0.0, 0.0, 0.0], 1e-12);
176 }
177
178 #[test]
179 fn all_sources_all_targets_undirected() {
180 let mut g = Graph::new(4, false).unwrap();
183 g.add_edges(vec![(0, 1), (1, 2), (2, 3)]).unwrap();
184 let b = betweenness_subset(&g, &[0, 1, 2, 3], &[0, 1, 2, 3], true).unwrap();
185 close(&b, &[0.0, 2.0, 2.0, 0.0], 1e-12);
187 }
188
189 #[test]
190 fn path_subset_sources() {
191 let mut g = Graph::new(5, false).unwrap();
196 g.add_edges(vec![(0, 1), (1, 2), (2, 3), (3, 4)]).unwrap();
197 let b = betweenness_subset(&g, &[0], &[0, 1, 2, 3, 4], true).unwrap();
198 close(&b, &[0.0, 1.5, 1.0, 0.5, 0.0], 1e-12);
199 }
200
201 #[test]
202 fn path_subset_targets() {
203 let mut g = Graph::new(5, false).unwrap();
208 g.add_edges(vec![(0, 1), (1, 2), (2, 3), (3, 4)]).unwrap();
209 let b = betweenness_subset(&g, &[0, 1, 2, 3, 4], &[4], true).unwrap();
210 close(&b, &[0.0, 0.5, 1.0, 1.5, 0.0], 1e-12);
211 }
212
213 #[test]
214 fn path_both_subsets() {
215 let mut g = Graph::new(5, false).unwrap();
223 g.add_edges(vec![(0, 1), (1, 2), (2, 3), (3, 4)]).unwrap();
224 let b = betweenness_subset(&g, &[0, 1], &[3, 4], true).unwrap();
225 close(&b, &[0.0, 1.0, 2.0, 1.0, 0.0], 1e-12);
226 }
227
228 #[test]
229 fn directed_path() {
230 let mut g = Graph::new(4, true).unwrap();
235 g.add_edges(vec![(0, 1), (1, 2), (2, 3)]).unwrap();
236 let b = betweenness_subset(&g, &[0], &[2, 3], true).unwrap();
237 close(&b, &[0.0, 2.0, 1.0, 0.0], 1e-12);
238 }
239
240 #[test]
241 fn directed_as_undirected() {
242 let mut g = Graph::new(4, true).unwrap();
245 g.add_edges(vec![(0, 1), (1, 2), (2, 3)]).unwrap();
246 let b = betweenness_subset(&g, &[0], &[2, 3], false).unwrap();
247 close(&b, &[0.0, 1.0, 0.5, 0.0], 1e-12);
248 }
249
250 #[test]
251 fn triangle() {
252 let mut g = Graph::new(3, false).unwrap();
254 g.add_edges(vec![(0, 1), (1, 2), (2, 0)]).unwrap();
255 let b = betweenness_subset(&g, &[0, 1, 2], &[0, 1, 2], true).unwrap();
256 close(&b, &[0.0, 0.0, 0.0], 1e-12);
257 }
258
259 #[test]
260 fn star_subset() {
261 let mut g = Graph::new(5, false).unwrap();
265 g.add_edges(vec![(0, 1), (0, 2), (0, 3), (0, 4)]).unwrap();
266 let b = betweenness_subset(&g, &[1, 2], &[3, 4], true).unwrap();
267 close(&b, &[2.0, 0.0, 0.0, 0.0, 0.0], 1e-12);
268 }
269
270 #[test]
271 fn out_of_range_vertices_ignored() {
272 let mut g = Graph::new(3, false).unwrap();
273 g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
274 let b = betweenness_subset(&g, &[0, 99], &[2], true).unwrap();
276 close(&b, &[0.0, 0.5, 0.0], 1e-12);
278 }
279}