rust_igraph/algorithms/properties/
local_scan_k.rs1use std::collections::VecDeque;
10
11use crate::algorithms::properties::degree::DegreeMode;
12use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
13
14pub fn local_scan_k(graph: &Graph, k: u32, weights: Option<&[f64]>) -> IgraphResult<Vec<f64>> {
55 let n = graph.vcount();
56 let ecount = graph.ecount();
57
58 if let Some(w) = weights {
59 if w.len() != ecount {
60 return Err(IgraphError::InvalidArgument(format!(
61 "local_scan_k: weights length ({}) does not match edge count ({ecount})",
62 w.len()
63 )));
64 }
65 }
66
67 let n_usize = n as usize;
68 let mut result = vec![0.0_f64; n_usize];
69
70 if n == 0 || ecount == 0 {
71 return Ok(result);
72 }
73
74 let m_u32 =
75 u32::try_from(ecount).map_err(|_| IgraphError::Internal("ecount exceeds u32::MAX"))?;
76
77 let mut adj: Vec<Vec<u32>> = vec![Vec::new(); n_usize];
79 for eid in 0..m_u32 {
80 let (from, to) = graph.edge(eid)?;
81 adj[from as usize].push(to);
82 if !graph.is_directed() && from != to {
83 adj[to as usize].push(from);
84 }
85 }
86
87 let mut marked = vec![0u32; n_usize];
90 let mut queue: VecDeque<(u32, u32)> = VecDeque::new();
91
92 for v in 0..n {
93 let tag = v + 1;
94 marked[v as usize] = tag;
95 queue.clear();
96 queue.push_back((v, 0));
97
98 while let Some((cur, dist)) = queue.pop_front() {
99 if dist < k {
100 for &nei in &adj[cur as usize] {
101 if marked[nei as usize] != tag {
102 marked[nei as usize] = tag;
103 queue.push_back((nei, dist + 1));
104 }
105 }
106 }
107 }
108
109 let mut count = 0.0_f64;
111 for eid in 0..m_u32 {
112 let (from, to) = graph.edge(eid)?;
113 if marked[from as usize] == tag && marked[to as usize] == tag {
114 let w = weights.map_or(1.0, |ws| ws[eid as usize]);
115 count += w;
116 }
117 }
118
119 result[v as usize] = count;
120 }
121
122 Ok(result)
123}
124
125fn incident_with_mode(graph: &Graph, v: VertexId, mode: DegreeMode) -> IgraphResult<Vec<u32>> {
127 if !graph.is_directed() {
128 return graph.incident(v);
129 }
130 match mode {
131 DegreeMode::Out => graph.incident(v),
132 DegreeMode::In => graph.incident_in(v),
133 DegreeMode::All => {
134 let mut out = graph.incident(v)?;
135 let in_edges = graph.incident_in(v)?;
136 out.extend(in_edges);
137 Ok(out)
138 }
139 }
140}
141
142pub fn local_scan_k_ecount(
161 graph: &Graph,
162 k: u32,
163 weights: Option<&[f64]>,
164 mode: DegreeMode,
165) -> IgraphResult<Vec<f64>> {
166 if let Some(w) = weights {
167 if w.len() != graph.ecount() {
168 return Err(IgraphError::InvalidArgument(format!(
169 "local_scan_k_ecount: weights length ({}) != edge count ({})",
170 w.len(),
171 graph.ecount()
172 )));
173 }
174 }
175
176 let n = graph.vcount();
177 let n_usize = n as usize;
178 let ecount = graph.ecount();
179 let mut result = vec![0.0_f64; n_usize];
180
181 if n == 0 || ecount == 0 {
182 return Ok(result);
183 }
184
185 let undirected_or_all = mode == DegreeMode::All || !graph.is_directed();
186 let mut marker: Vec<i64> = vec![-1; n_usize];
187 let mut queue: VecDeque<(VertexId, u32)> = VecDeque::new();
188
189 for node in 0..n {
190 let node_i = i64::from(node);
191 queue.clear();
192 queue.push_back((node, 0));
193 marker[node as usize] = node_i;
194
195 while let Some((act, dist)) = queue.pop_front() {
196 let next_dist = dist
197 .checked_add(1)
198 .ok_or(IgraphError::Internal("k-scan distance overflow"))?;
199 let edges = incident_with_mode(graph, act, mode)?;
200
201 for &e in &edges {
202 let nei = graph.edge_other(e, act)?;
203 let w = weights.map_or(1.0, |ws| ws[e as usize]);
204
205 if next_dist <= k || marker[nei as usize] == node_i {
206 result[node as usize] += w;
207 }
208
209 if next_dist <= k && marker[nei as usize] != node_i {
210 queue.push_back((nei, next_dist));
211 marker[nei as usize] = node_i;
212 }
213 }
214 }
215
216 if undirected_or_all {
217 result[node as usize] /= 2.0;
218 }
219 }
220
221 Ok(result)
222}
223
224pub fn local_scan_k_ecount_them(
245 us: &Graph,
246 them: &Graph,
247 k: u32,
248 weights_them: Option<&[f64]>,
249 mode: DegreeMode,
250) -> IgraphResult<Vec<f64>> {
251 if us.vcount() != them.vcount() {
252 return Err(IgraphError::InvalidArgument(
253 "local_scan_k_ecount_them: vertex count mismatch".to_string(),
254 ));
255 }
256 if us.is_directed() != them.is_directed() {
257 return Err(IgraphError::InvalidArgument(
258 "local_scan_k_ecount_them: directedness mismatch".to_string(),
259 ));
260 }
261 if let Some(w) = weights_them {
262 if w.len() != them.ecount() {
263 return Err(IgraphError::InvalidArgument(format!(
264 "local_scan_k_ecount_them: weight vector length {} != them edge count {}",
265 w.len(),
266 them.ecount()
267 )));
268 }
269 }
270
271 let n = us.vcount();
272 let n_usize = n as usize;
273 let mut result = vec![0.0_f64; n_usize];
274 let mut marker: Vec<i64> = vec![-1; n_usize];
275 let undirected_or_all = mode == DegreeMode::All || !us.is_directed();
276
277 let mut queue: VecDeque<(VertexId, u32)> = VecDeque::new();
278 let mut marked_vertices: Vec<VertexId> = Vec::new();
279
280 for node in 0..n {
281 let node_i = i64::from(node);
282 queue.clear();
283 marked_vertices.clear();
284
285 queue.push_back((node, 0));
286 marker[node as usize] = node_i;
287 marked_vertices.push(node);
288
289 while let Some((act, dist)) = queue.pop_front() {
290 let next_dist = dist
291 .checked_add(1)
292 .ok_or(IgraphError::Internal("k-scan distance overflow"))?;
293 let edges = incident_with_mode(us, act, mode)?;
294
295 for &e in &edges {
296 let nei = us.edge_other(e, act)?;
297 if next_dist <= k && marker[nei as usize] != node_i {
298 queue.push_back((nei, next_dist));
299 marker[nei as usize] = node_i;
300 marked_vertices.push(nei);
301 }
302 }
303 }
304
305 for &mv in &marked_vertices {
306 let them_edges = incident_with_mode(them, mv, mode)?;
307 for &e in &them_edges {
308 let nei = them.edge_other(e, mv)?;
309 if marker[nei as usize] == node_i {
310 let w = weights_them.map_or(1.0, |ws| ws[e as usize]);
311 result[node as usize] += w;
312 }
313 }
314 }
315
316 if undirected_or_all {
317 result[node as usize] /= 2.0;
318 }
319 }
320
321 Ok(result)
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327
328 fn close(a: f64, b: f64) -> bool {
329 (a - b).abs() < 1e-10
330 }
331
332 #[test]
333 fn empty_graph() {
334 let g = Graph::with_vertices(0);
335 let s = local_scan_k(&g, 1, None).unwrap();
336 assert!(s.is_empty());
337 }
338
339 #[test]
340 fn no_edges() {
341 let g = Graph::with_vertices(5);
342 let s = local_scan_k(&g, 2, None).unwrap();
343 assert!(s.iter().all(|&v| close(v, 0.0)));
344 }
345
346 #[test]
347 fn k_zero_no_self_loops() {
348 let mut g = Graph::with_vertices(3);
351 g.add_edge(0, 1).unwrap();
352 g.add_edge(1, 2).unwrap();
353 let s = local_scan_k(&g, 0, None).unwrap();
354 assert!(s.iter().all(|&v| close(v, 0.0)));
355 }
356
357 #[test]
358 fn k_zero_with_self_loop() {
359 let mut g = Graph::with_vertices(2);
361 g.add_edge(0, 0).unwrap();
362 g.add_edge(0, 1).unwrap();
363 let s = local_scan_k(&g, 0, None).unwrap();
364 assert!(close(s[0], 1.0)); assert!(close(s[1], 0.0)); }
367
368 #[test]
369 fn k_one_triangle() {
370 let mut g = Graph::with_vertices(3);
372 g.add_edge(0, 1).unwrap();
373 g.add_edge(1, 2).unwrap();
374 g.add_edge(2, 0).unwrap();
375 let s = local_scan_k(&g, 1, None).unwrap();
376 assert!(close(s[0], 3.0));
377 assert!(close(s[1], 3.0));
378 assert!(close(s[2], 3.0));
379 }
380
381 #[test]
382 fn k_one_path_5() {
383 let mut g = Graph::with_vertices(5);
385 for i in 0..4u32 {
386 g.add_edge(i, i + 1).unwrap();
387 }
388 let s = local_scan_k(&g, 1, None).unwrap();
389 assert!(close(s[0], 1.0));
390 assert!(close(s[1], 2.0));
391 assert!(close(s[2], 2.0));
392 assert!(close(s[3], 2.0));
393 assert!(close(s[4], 1.0));
394 }
395
396 #[test]
397 fn k_two_path_5() {
398 let mut g = Graph::with_vertices(5);
404 for i in 0..4u32 {
405 g.add_edge(i, i + 1).unwrap();
406 }
407 let s = local_scan_k(&g, 2, None).unwrap();
408 assert!(close(s[0], 2.0));
409 assert!(close(s[1], 3.0));
410 assert!(close(s[2], 4.0));
411 assert!(close(s[3], 3.0));
412 assert!(close(s[4], 2.0));
413 }
414
415 #[test]
416 fn k_large_returns_total_edges() {
417 let mut g = Graph::with_vertices(5);
419 for i in 0..4u32 {
420 g.add_edge(i, i + 1).unwrap();
421 }
422 let s = local_scan_k(&g, 100, None).unwrap();
423 for &val in &s {
424 assert!(close(val, 4.0));
425 }
426 }
427
428 #[test]
429 fn two_components() {
430 let mut g = Graph::with_vertices(5);
434 g.add_edge(0, 1).unwrap();
435 g.add_edge(1, 2).unwrap();
436 g.add_edge(2, 0).unwrap();
437 g.add_edge(3, 4).unwrap();
438 let s = local_scan_k(&g, 1, None).unwrap();
439 assert!(close(s[0], 3.0));
440 assert!(close(s[3], 1.0));
441 assert!(close(s[4], 1.0));
442 }
443
444 #[test]
445 fn weighted() {
446 let mut g = Graph::with_vertices(3);
448 g.add_edge(0, 1).unwrap();
449 g.add_edge(1, 2).unwrap();
450 g.add_edge(2, 0).unwrap();
451 let w = vec![2.0, 3.0, 5.0];
452 let s = local_scan_k(&g, 1, Some(&w)).unwrap();
453 assert!(close(s[0], 10.0));
455 assert!(close(s[1], 10.0));
456 assert!(close(s[2], 10.0));
457 }
458
459 #[test]
460 fn star_k2() {
461 let mut g = Graph::with_vertices(4);
464 g.add_edge(0, 1).unwrap();
465 g.add_edge(0, 2).unwrap();
466 g.add_edge(0, 3).unwrap();
467 let s = local_scan_k(&g, 2, None).unwrap();
468 assert!(close(s[0], 3.0));
470 assert!(close(s[1], 3.0));
471 assert!(close(s[2], 3.0));
472 assert!(close(s[3], 3.0));
473 }
474
475 #[test]
476 fn weights_mismatch_errors() {
477 let mut g = Graph::with_vertices(2);
478 g.add_edge(0, 1).unwrap();
479 assert!(local_scan_k(&g, 1, Some(&[1.0, 2.0])).is_err());
480 }
481}