rust_igraph/algorithms/properties/
degree_neighbor_stats.rs1#![allow(
11 clippy::cast_possible_truncation,
12 clippy::cast_precision_loss,
13 clippy::many_single_char_names,
14 clippy::needless_range_loop,
15 clippy::too_many_lines
16)]
17
18use crate::core::{Graph, IgraphResult};
19
20pub fn degree_neighbor_max_sum(graph: &Graph) -> IgraphResult<u64> {
37 let n = graph.vcount() as usize;
38 let mut result = 0_u64;
39
40 for v in 0..n {
41 let v_id = v as u32;
42 let neighbors = graph.neighbors(v_id)?;
43 let mut max_d = None;
44 for &u in &neighbors {
45 let d = graph.degree(u)?;
46 max_d = Some(match max_d {
47 None => d,
48 Some(prev) if d > prev => d,
49 Some(prev) => prev,
50 });
51 }
52 if let Some(m) = max_d {
53 result += m as u64;
54 }
55 }
56
57 Ok(result)
58}
59
60pub fn degree_neighbor_min_sum(graph: &Graph) -> IgraphResult<u64> {
77 let n = graph.vcount() as usize;
78 let mut result = 0_u64;
79
80 for v in 0..n {
81 let v_id = v as u32;
82 let neighbors = graph.neighbors(v_id)?;
83 let mut min_d = None;
84 for &u in &neighbors {
85 let d = graph.degree(u)?;
86 min_d = Some(match min_d {
87 None => d,
88 Some(prev) if d < prev => d,
89 Some(prev) => prev,
90 });
91 }
92 if let Some(m) = min_d {
93 result += m as u64;
94 }
95 }
96
97 Ok(result)
98}
99
100pub fn degree_neighbor_range_sum(graph: &Graph) -> IgraphResult<u64> {
118 let n = graph.vcount() as usize;
119 let mut result = 0_u64;
120
121 for v in 0..n {
122 let v_id = v as u32;
123 let neighbors = graph.neighbors(v_id)?;
124 let mut max_d: Option<usize> = None;
125 let mut min_d: Option<usize> = None;
126 for &u in &neighbors {
127 let d = graph.degree(u)?;
128 max_d = Some(match max_d {
129 None => d,
130 Some(prev) if d > prev => d,
131 Some(prev) => prev,
132 });
133 min_d = Some(match min_d {
134 None => d,
135 Some(prev) if d < prev => d,
136 Some(prev) => prev,
137 });
138 }
139 if let (Some(mx), Some(mn)) = (max_d, min_d) {
140 result += (mx - mn) as u64;
141 }
142 }
143
144 Ok(result)
145}
146
147pub fn degree_neighbor_variance_sum(graph: &Graph) -> IgraphResult<f64> {
165 let n = graph.vcount() as usize;
166 let mut result = 0.0_f64;
167
168 for v in 0..n {
169 let v_id = v as u32;
170 let neighbors = graph.neighbors(v_id)?;
171 if neighbors.is_empty() {
172 continue;
173 }
174 let k = neighbors.len() as f64;
175 let mut sum = 0.0_f64;
176 let mut sum_sq = 0.0_f64;
177 for &u in &neighbors {
178 let d = graph.degree(u)? as f64;
179 sum += d;
180 sum_sq += d * d;
181 }
182 let mean = sum / k;
183 let var = sum_sq / k - mean * mean;
184 result += var;
185 }
186
187 Ok(result)
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 fn single_edge() -> Graph {
195 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
196 }
197
198 fn path3() -> Graph {
199 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
200 }
201
202 fn k3() -> Graph {
203 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
204 }
205
206 fn k4() -> Graph {
207 Graph::from_edges(
208 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
209 false,
210 Some(4),
211 )
212 .unwrap()
213 }
214
215 fn cycle4() -> Graph {
216 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
217 }
218
219 fn star5() -> Graph {
220 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
221 }
222
223 fn paw() -> Graph {
224 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
225 }
226
227 #[test]
230 fn nmax_empty() {
231 let g = Graph::with_vertices(0);
232 assert_eq!(degree_neighbor_max_sum(&g).unwrap(), 0);
233 }
234
235 #[test]
236 fn nmax_isolated() {
237 let g = Graph::with_vertices(5);
238 assert_eq!(degree_neighbor_max_sum(&g).unwrap(), 0);
239 }
240
241 #[test]
242 fn nmax_single_edge() {
243 assert_eq!(degree_neighbor_max_sum(&single_edge()).unwrap(), 2);
245 }
246
247 #[test]
248 fn nmax_k3() {
249 assert_eq!(degree_neighbor_max_sum(&k3()).unwrap(), 6);
251 }
252
253 #[test]
254 fn nmax_k4() {
255 assert_eq!(degree_neighbor_max_sum(&k4()).unwrap(), 12);
257 }
258
259 #[test]
260 fn nmax_star5() {
261 assert_eq!(degree_neighbor_max_sum(&star5()).unwrap(), 17);
265 }
266
267 #[test]
268 fn nmax_path3() {
269 assert_eq!(degree_neighbor_max_sum(&path3()).unwrap(), 5);
274 }
275
276 #[test]
277 fn nmax_paw() {
278 assert_eq!(degree_neighbor_max_sum(&paw()).unwrap(), 11);
284 }
285
286 #[test]
289 fn nmin_empty() {
290 let g = Graph::with_vertices(0);
291 assert_eq!(degree_neighbor_min_sum(&g).unwrap(), 0);
292 }
293
294 #[test]
295 fn nmin_isolated() {
296 let g = Graph::with_vertices(5);
297 assert_eq!(degree_neighbor_min_sum(&g).unwrap(), 0);
298 }
299
300 #[test]
301 fn nmin_single_edge() {
302 assert_eq!(degree_neighbor_min_sum(&single_edge()).unwrap(), 2);
303 }
304
305 #[test]
306 fn nmin_k3() {
307 assert_eq!(degree_neighbor_min_sum(&k3()).unwrap(), 6);
309 }
310
311 #[test]
312 fn nmin_star5() {
313 assert_eq!(degree_neighbor_min_sum(&star5()).unwrap(), 17);
315 }
316
317 #[test]
318 fn nmin_path3() {
319 assert_eq!(degree_neighbor_min_sum(&path3()).unwrap(), 5);
321 }
322
323 #[test]
324 fn nmin_paw() {
325 assert_eq!(degree_neighbor_min_sum(&paw()).unwrap(), 8);
328 }
329
330 #[test]
333 fn nrange_empty() {
334 let g = Graph::with_vertices(0);
335 assert_eq!(degree_neighbor_range_sum(&g).unwrap(), 0);
336 }
337
338 #[test]
339 fn nrange_isolated() {
340 let g = Graph::with_vertices(5);
341 assert_eq!(degree_neighbor_range_sum(&g).unwrap(), 0);
342 }
343
344 #[test]
345 fn nrange_regular() {
346 assert_eq!(degree_neighbor_range_sum(&k3()).unwrap(), 0);
348 assert_eq!(degree_neighbor_range_sum(&k4()).unwrap(), 0);
349 assert_eq!(degree_neighbor_range_sum(&cycle4()).unwrap(), 0);
350 }
351
352 #[test]
353 fn nrange_single_edge() {
354 assert_eq!(degree_neighbor_range_sum(&single_edge()).unwrap(), 0);
356 }
357
358 #[test]
359 fn nrange_star5() {
360 assert_eq!(degree_neighbor_range_sum(&star5()).unwrap(), 0);
363 }
364
365 #[test]
366 fn nrange_path3() {
367 assert_eq!(degree_neighbor_range_sum(&path3()).unwrap(), 0);
369 }
370
371 #[test]
372 fn nrange_paw() {
373 assert_eq!(degree_neighbor_range_sum(&paw()).unwrap(), 3);
377 }
378
379 #[test]
382 fn nvar_empty() {
383 let g = Graph::with_vertices(0);
384 assert!(degree_neighbor_variance_sum(&g).unwrap().abs() < 1e-10);
385 }
386
387 #[test]
388 fn nvar_isolated() {
389 let g = Graph::with_vertices(5);
390 assert!(degree_neighbor_variance_sum(&g).unwrap().abs() < 1e-10);
391 }
392
393 #[test]
394 fn nvar_regular() {
395 assert!(degree_neighbor_variance_sum(&k3()).unwrap().abs() < 1e-10);
397 assert!(degree_neighbor_variance_sum(&k4()).unwrap().abs() < 1e-10);
398 assert!(degree_neighbor_variance_sum(&cycle4()).unwrap().abs() < 1e-10);
399 }
400
401 #[test]
402 fn nvar_single_edge() {
403 assert!(degree_neighbor_variance_sum(&single_edge()).unwrap().abs() < 1e-10);
405 }
406
407 #[test]
408 fn nvar_star5() {
409 assert!(degree_neighbor_variance_sum(&star5()).unwrap().abs() < 1e-10);
411 }
412
413 #[test]
414 fn nvar_path3() {
415 assert!(degree_neighbor_variance_sum(&path3()).unwrap().abs() < 1e-10);
417 }
418
419 #[test]
420 fn nvar_paw() {
421 let expected = 0.25 + 0.25 + 2.0 / 9.0;
427 assert!((degree_neighbor_variance_sum(&paw()).unwrap() - expected).abs() < 1e-10);
428 }
429
430 #[test]
433 fn max_ge_min() {
434 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
435 assert!(degree_neighbor_max_sum(g).unwrap() >= degree_neighbor_min_sum(g).unwrap());
436 }
437 }
438
439 #[test]
440 fn range_equals_max_minus_min() {
441 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
442 let mx = degree_neighbor_max_sum(g).unwrap();
443 let mn = degree_neighbor_min_sum(g).unwrap();
444 let rng = degree_neighbor_range_sum(g).unwrap();
445 assert!(rng <= mx - mn);
446 }
447 }
448
449 #[test]
450 fn variance_nonnegative() {
451 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
452 assert!(degree_neighbor_variance_sum(g).unwrap() >= -1e-10);
453 }
454 }
455}