rust_igraph/algorithms/properties/
edge_betweenness_subset.rs1use std::collections::VecDeque;
12
13use crate::core::graph::EdgeId;
14use crate::core::{Graph, IgraphResult, VertexId};
15
16pub fn edge_betweenness_subset(
51 graph: &Graph,
52 sources: &[VertexId],
53 targets: &[VertexId],
54 directed: bool,
55) -> IgraphResult<Vec<f64>> {
56 let n = graph.vcount();
57 let n_us = n as usize;
58 let m = graph.ecount();
59 let mut betweenness_e = vec![0.0_f64; m];
60
61 if n == 0 || m == 0 || sources.is_empty() || targets.is_empty() {
62 return Ok(betweenness_e);
63 }
64
65 let mut is_target = vec![false; n_us];
66 for &t in targets {
67 if (t as usize) < n_us {
68 is_target[t as usize] = true;
69 }
70 }
71
72 let mut sigma = vec![0.0_f64; n_us];
73 let mut dist = vec![-1_i64; n_us];
74 let mut pred: Vec<Vec<(VertexId, EdgeId)>> = vec![Vec::new(); n_us];
75 let mut stack: Vec<VertexId> = Vec::with_capacity(n_us);
76 let mut delta_v = vec![0.0_f64; n_us];
77 let use_directed = directed && graph.is_directed();
78
79 for &s in sources {
80 if s >= n {
81 continue;
82 }
83
84 sigma.fill(0.0);
85 dist.fill(-1);
86 for slot in &mut pred {
87 slot.clear();
88 }
89 stack.clear();
90 delta_v.fill(0.0);
91
92 sigma[s as usize] = 1.0;
93 dist[s as usize] = 0;
94 let mut queue: VecDeque<VertexId> = VecDeque::new();
95 queue.push_back(s);
96
97 while let Some(v) = queue.pop_front() {
98 stack.push(v);
99 let v_dist = dist[v as usize];
100
101 let edges = if use_directed {
102 graph.incident(v)?
103 } else if graph.is_directed() {
104 let mut e = graph.incident(v)?;
105 e.extend(graph.incident_in(v)?);
106 e
107 } else {
108 graph.incident(v)?
109 };
110
111 for e in edges {
112 let w = graph.edge_other(e, v)?;
113 if dist[w as usize] < 0 {
114 queue.push_back(w);
115 dist[w as usize] = v_dist + 1;
116 }
117 if dist[w as usize] == v_dist + 1 {
118 sigma[w as usize] += sigma[v as usize];
119 pred[w as usize].push((v, e));
120 }
121 }
122 }
123
124 while let Some(w) = stack.pop() {
125 let coeff = if is_target[w as usize] {
126 (1.0 + delta_v[w as usize]) / sigma[w as usize]
127 } else {
128 delta_v[w as usize] / sigma[w as usize]
129 };
130
131 for &(v, e) in &pred[w as usize] {
132 let c = sigma[v as usize] * coeff;
133 delta_v[v as usize] += c;
134 betweenness_e[e as usize] += c;
135 }
136 }
137 }
138
139 if !directed || !graph.is_directed() {
140 for slot in &mut betweenness_e {
141 *slot /= 2.0;
142 }
143 }
144
145 Ok(betweenness_e)
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 fn close(actual: &[f64], expected: &[f64], tol: f64) {
153 assert_eq!(actual.len(), expected.len(), "length mismatch");
154 for (i, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
155 assert!((a - e).abs() < tol, "edge {i}: actual={a} expected={e}");
156 }
157 }
158
159 #[test]
160 fn empty_graph() {
161 let g = Graph::new(0, false).unwrap();
162 let eb = edge_betweenness_subset(&g, &[0], &[0], true).unwrap();
163 assert!(eb.is_empty());
164 }
165
166 #[test]
167 fn no_edges() {
168 let g = Graph::new(3, false).unwrap();
169 let eb = edge_betweenness_subset(&g, &[0], &[1, 2], true).unwrap();
170 assert!(eb.is_empty());
171 }
172
173 #[test]
174 fn no_sources() {
175 let mut g = Graph::new(3, false).unwrap();
176 g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
177 let eb = edge_betweenness_subset(&g, &[], &[0, 1, 2], true).unwrap();
178 close(&eb, &[0.0, 0.0], 1e-12);
179 }
180
181 #[test]
182 fn no_targets() {
183 let mut g = Graph::new(3, false).unwrap();
184 g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
185 let eb = edge_betweenness_subset(&g, &[0, 1, 2], &[], true).unwrap();
186 close(&eb, &[0.0, 0.0], 1e-12);
187 }
188
189 #[test]
190 fn all_sources_all_targets_path() {
191 let mut g = Graph::new(4, false).unwrap();
193 g.add_edges(vec![(0, 1), (1, 2), (2, 3)]).unwrap();
194 let eb = edge_betweenness_subset(&g, &[0, 1, 2, 3], &[0, 1, 2, 3], true).unwrap();
195 close(&eb, &[3.0, 4.0, 3.0], 1e-12);
196 }
197
198 #[test]
199 fn path_subset_sources_targets() {
200 let mut g = Graph::new(4, false).unwrap();
205 g.add_edges(vec![(0, 1), (1, 2), (2, 3)]).unwrap();
206 let eb = edge_betweenness_subset(&g, &[0], &[2, 3], true).unwrap();
207 close(&eb, &[1.0, 1.0, 0.5], 1e-12);
208 }
209
210 #[test]
211 fn directed_path() {
212 let mut g = Graph::new(4, true).unwrap();
217 g.add_edges(vec![(0, 1), (1, 2), (2, 3)]).unwrap();
218 let eb = edge_betweenness_subset(&g, &[0], &[2, 3], true).unwrap();
219 close(&eb, &[2.0, 2.0, 1.0], 1e-12);
220 }
221
222 #[test]
223 fn triangle() {
224 let mut g = Graph::new(3, false).unwrap();
227 g.add_edges(vec![(0, 1), (1, 2), (2, 0)]).unwrap();
228 let eb = edge_betweenness_subset(&g, &[0, 1, 2], &[0, 1, 2], true).unwrap();
229 close(&eb, &[1.0, 1.0, 1.0], 1e-12);
230 }
231
232 #[test]
233 fn star_subset() {
234 let mut g = Graph::new(5, false).unwrap();
241 g.add_edges(vec![(0, 1), (0, 2), (0, 3), (0, 4)]).unwrap();
242 let eb = edge_betweenness_subset(&g, &[1, 2], &[3, 4], true).unwrap();
243 close(&eb, &[1.0, 1.0, 1.0, 1.0], 1e-12);
244 }
245
246 #[test]
247 fn out_of_range_ignored() {
248 let mut g = Graph::new(3, false).unwrap();
249 g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
250 let eb = edge_betweenness_subset(&g, &[0, 99], &[2], true).unwrap();
251 close(&eb, &[0.5, 0.5], 1e-12);
254 }
255}