1use crate::core::{Graph, IgraphError, IgraphResult};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum StrengthMode {
22 Out,
24 In,
26 All,
28}
29
30pub fn strength(graph: &Graph, weights: &[f64]) -> IgraphResult<Vec<f64>> {
64 strength_with_mode(graph, weights, StrengthMode::All, true)
65}
66
67#[allow(clippy::cast_possible_truncation)]
114pub fn strength_with_mode(
115 graph: &Graph,
116 weights: &[f64],
117 mode: StrengthMode,
118 loops: bool,
119) -> IgraphResult<Vec<f64>> {
120 if weights.len() != graph.ecount() {
121 return Err(IgraphError::InvalidArgument(format!(
122 "weight vector length {} != edge count {}",
123 weights.len(),
124 graph.ecount()
125 )));
126 }
127
128 let n = graph.vcount() as usize;
129 let mut res = vec![0.0_f64; n];
130
131 let effective_mode = if graph.is_directed() {
132 mode
133 } else {
134 StrengthMode::All
135 };
136
137 let add_out = effective_mode == StrengthMode::Out || effective_mode == StrengthMode::All;
138 let add_in = effective_mode == StrengthMode::In || effective_mode == StrengthMode::All;
139
140 for (eid, &w) in weights.iter().enumerate() {
141 let eid_u32 = eid as u32;
142 let from = graph.edge_source(eid_u32)? as usize;
143 let to = graph.edge_target(eid_u32)? as usize;
144 let is_loop = from == to;
145
146 if !loops && is_loop {
147 continue;
148 }
149 if add_out {
150 res[from] += w;
151 }
152 if add_in {
153 res[to] += w;
154 }
155 }
156
157 Ok(res)
158}
159
160#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
213pub fn diversity(graph: &Graph, weights: &[f64]) -> IgraphResult<Vec<f64>> {
214 if graph.is_directed() {
215 return Err(IgraphError::InvalidArgument(
216 "diversity measure works with undirected graphs only".into(),
217 ));
218 }
219
220 if weights.len() != graph.ecount() {
221 return Err(IgraphError::InvalidArgument(format!(
222 "weight vector length {} != edge count {}",
223 weights.len(),
224 graph.ecount()
225 )));
226 }
227
228 if graph.ecount() > 0 {
229 for (idx, &w) in weights.iter().enumerate() {
230 if w.is_nan() {
231 return Err(IgraphError::InvalidArgument(format!(
232 "weight vector must not contain NaN values (index {idx})"
233 )));
234 }
235 if w < 0.0 {
236 return Err(IgraphError::InvalidArgument(format!(
237 "weight vector must be non-negative (index {idx}: {w})"
238 )));
239 }
240 }
241 }
242
243 if crate::algorithms::properties::multiplicity::has_multiple(graph)? {
244 return Err(IgraphError::InvalidArgument(
245 "diversity measure works only if the graph has no multiple edges".into(),
246 ));
247 }
248
249 let n = graph.vcount() as usize;
250 let mut res = Vec::with_capacity(n);
251
252 for v in 0..graph.vcount() {
253 let edges = graph.incident(v)?;
254 let k = edges.len();
255
256 let d = if k == 0 {
257 f64::NAN
258 } else if k == 1 {
259 let w = weights[edges[0] as usize];
260 if w > 0.0 { 0.0 } else { f64::NAN }
261 } else {
262 let mut s = 0.0_f64;
263 let mut ent = 0.0_f64;
264 for &eid in &edges {
265 let w = weights[eid as usize];
266 if w == 0.0 {
267 continue;
268 }
269 s += w;
270 ent += w * w.ln();
271 }
272 if s == 0.0 {
273 f64::NAN
274 } else {
275 (s.ln() - ent / s) / (k as f64).ln()
276 }
277 };
278
279 res.push(d);
280 }
281
282 Ok(res)
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288 use crate::core::Graph;
289
290 #[test]
293 fn strength_empty_graph() {
294 let g = Graph::with_vertices(0);
295 let s = strength(&g, &[]).unwrap();
296 assert!(s.is_empty());
297 }
298
299 #[test]
300 fn strength_no_edges() {
301 let g = Graph::with_vertices(5);
302 let s = strength(&g, &[]).unwrap();
303 assert_eq!(s.len(), 5);
304 for v in &s {
305 assert!((*v - 0.0).abs() < 1e-15);
306 }
307 }
308
309 #[test]
310 fn strength_triangle_undirected() {
311 let mut g = Graph::with_vertices(3);
312 g.add_edge(0, 1).unwrap();
313 g.add_edge(0, 2).unwrap();
314 g.add_edge(1, 2).unwrap();
315 let s = strength(&g, &[1.0, 2.0, 3.0]).unwrap();
316 assert!((s[0] - 3.0).abs() < 1e-12);
317 assert!((s[1] - 4.0).abs() < 1e-12);
318 assert!((s[2] - 5.0).abs() < 1e-12);
319 }
320
321 #[test]
322 fn strength_self_loop_undirected() {
323 let mut g = Graph::with_vertices(2);
324 g.add_edge(0, 0).unwrap(); g.add_edge(0, 1).unwrap();
326 let s = strength(&g, &[3.0, 5.0]).unwrap();
327 assert!((s[0] - (3.0 + 3.0 + 5.0)).abs() < 1e-12);
329 assert!((s[1] - 5.0).abs() < 1e-12);
330 }
331
332 #[test]
333 fn strength_self_loop_no_loops() {
334 let mut g = Graph::with_vertices(2);
335 g.add_edge(0, 0).unwrap();
336 g.add_edge(0, 1).unwrap();
337 let s = strength_with_mode(&g, &[3.0, 5.0], StrengthMode::All, false).unwrap();
338 assert!((s[0] - 5.0).abs() < 1e-12);
339 assert!((s[1] - 5.0).abs() < 1e-12);
340 }
341
342 #[test]
343 fn strength_directed_out() {
344 let mut g = Graph::new(4, true).unwrap();
345 g.add_edge(0, 1).unwrap();
346 g.add_edge(0, 2).unwrap();
347 g.add_edge(1, 3).unwrap();
348 g.add_edge(2, 3).unwrap();
349 let s = strength_with_mode(&g, &[1.0, 2.0, 3.0, 4.0], StrengthMode::Out, true).unwrap();
350 assert!((s[0] - 3.0).abs() < 1e-12); assert!((s[1] - 3.0).abs() < 1e-12); assert!((s[2] - 4.0).abs() < 1e-12); assert!((s[3] - 0.0).abs() < 1e-12);
354 }
355
356 #[test]
357 fn strength_directed_in() {
358 let mut g = Graph::new(4, true).unwrap();
359 g.add_edge(0, 1).unwrap();
360 g.add_edge(0, 2).unwrap();
361 g.add_edge(1, 3).unwrap();
362 g.add_edge(2, 3).unwrap();
363 let s = strength_with_mode(&g, &[1.0, 2.0, 3.0, 4.0], StrengthMode::In, true).unwrap();
364 assert!((s[0] - 0.0).abs() < 1e-12);
365 assert!((s[1] - 1.0).abs() < 1e-12);
366 assert!((s[2] - 2.0).abs() < 1e-12);
367 assert!((s[3] - 7.0).abs() < 1e-12); }
369
370 #[test]
371 fn strength_directed_all() {
372 let mut g = Graph::new(4, true).unwrap();
373 g.add_edge(0, 1).unwrap();
374 g.add_edge(0, 2).unwrap();
375 g.add_edge(1, 3).unwrap();
376 g.add_edge(2, 3).unwrap();
377 let s = strength_with_mode(&g, &[1.0, 2.0, 3.0, 4.0], StrengthMode::All, true).unwrap();
378 assert!((s[0] - 3.0).abs() < 1e-12); assert!((s[1] - 4.0).abs() < 1e-12); assert!((s[2] - 6.0).abs() < 1e-12); assert!((s[3] - 7.0).abs() < 1e-12); }
383
384 #[test]
385 fn strength_directed_self_loop_all() {
386 let mut g = Graph::new(2, true).unwrap();
387 g.add_edge(0, 0).unwrap();
388 g.add_edge(0, 1).unwrap();
389 let s = strength_with_mode(&g, &[10.0, 5.0], StrengthMode::All, true).unwrap();
390 assert!((s[0] - 25.0).abs() < 1e-12); assert!((s[1] - 5.0).abs() < 1e-12);
392 }
393
394 #[test]
395 fn strength_directed_self_loop_no_loops() {
396 let mut g = Graph::new(2, true).unwrap();
397 g.add_edge(0, 0).unwrap();
398 g.add_edge(0, 1).unwrap();
399 let s = strength_with_mode(&g, &[10.0, 5.0], StrengthMode::All, false).unwrap();
400 assert!((s[0] - 5.0).abs() < 1e-12);
401 assert!((s[1] - 5.0).abs() < 1e-12);
402 }
403
404 #[test]
405 fn strength_wrong_weight_length() {
406 let g = Graph::with_vertices(3);
407 let result = strength(&g, &[1.0]);
408 assert!(result.is_err());
409 }
410
411 #[test]
412 fn strength_matches_python_test() {
413 let mut g = Graph::new(3, true).unwrap();
433 g.add_edge(0, 1).unwrap();
434 g.add_edge(1, 2).unwrap();
435 g.add_edge(2, 0).unwrap();
436 let w = [2.0, 3.0, 5.0];
437 let out = strength_with_mode(&g, &w, StrengthMode::Out, true).unwrap();
438 assert!((out[0] - 2.0).abs() < 1e-12);
439 assert!((out[1] - 3.0).abs() < 1e-12);
440 assert!((out[2] - 5.0).abs() < 1e-12);
441 let ins = strength_with_mode(&g, &w, StrengthMode::In, true).unwrap();
442 assert!((ins[0] - 5.0).abs() < 1e-12);
443 assert!((ins[1] - 2.0).abs() < 1e-12);
444 assert!((ins[2] - 3.0).abs() < 1e-12);
445 }
446
447 #[test]
450 fn diversity_null_graph() {
451 let g = Graph::with_vertices(0);
452 let d = diversity(&g, &[]).unwrap();
453 assert!(d.is_empty());
454 }
455
456 #[test]
457 fn diversity_empty_graph() {
458 let g = Graph::with_vertices(5);
459 let d = diversity(&g, &[]).unwrap();
460 assert_eq!(d.len(), 5);
461 for v in &d {
462 assert!(v.is_nan());
463 }
464 }
465
466 #[test]
467 fn diversity_c_reference_4v5e() {
468 let mut g = Graph::with_vertices(4);
473 for (u, v) in [(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)] {
474 g.add_edge(u, v).unwrap();
475 }
476 let d = diversity(&g, &[3.0, 2.0, 8.0, 1.0, 1.0]).unwrap();
477 assert!((d[0] - 0.970_951).abs() < 1e-5);
478 assert!((d[1] - 0.75).abs() < 1e-5);
479 assert!((d[2] - 0.69137).abs() < 1e-4);
480 assert!((d[3] - 1.0).abs() < 1e-12);
481 }
482
483 #[test]
484 fn diversity_equal_weights_max_diversity() {
485 let mut g = Graph::with_vertices(3);
487 for (u, v) in [(0, 1), (0, 2), (1, 2)] {
488 g.add_edge(u, v).unwrap();
489 }
490 let d = diversity(&g, &[1.0, 1.0, 1.0]).unwrap();
491 for val in &d {
492 assert!((*val - 1.0).abs() < 1e-12);
493 }
494 }
495
496 #[test]
497 fn diversity_degree_one_vertices() {
498 let mut g = Graph::with_vertices(4);
500 for v in 1..4 {
501 g.add_edge(0, v).unwrap();
502 }
503 let d = diversity(&g, &[1.0, 2.0, 3.0]).unwrap();
504 assert!(d[0] > 0.0 && d[0] <= 1.0);
506 assert!((d[1] - 0.0).abs() < 1e-12);
508 assert!((d[2] - 0.0).abs() < 1e-12);
509 assert!((d[3] - 0.0).abs() < 1e-12);
510 }
511
512 #[test]
513 fn diversity_rejects_directed() {
514 let g = Graph::new(3, true).unwrap();
515 let result = diversity(&g, &[]);
516 assert!(result.is_err());
517 }
518
519 #[test]
520 fn diversity_rejects_multi_edges() {
521 let mut g = Graph::with_vertices(3);
522 g.add_edge(0, 1).unwrap();
523 g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap();
525 let result = diversity(&g, &[1.0, 2.0, 3.0]);
526 assert!(result.is_err());
527 }
528
529 #[test]
530 fn diversity_rejects_negative_weights() {
531 let mut g = Graph::with_vertices(3);
532 g.add_edge(0, 1).unwrap();
533 g.add_edge(1, 2).unwrap();
534 let result = diversity(&g, &[1.0, -1.0]);
535 assert!(result.is_err());
536 }
537
538 #[test]
539 fn diversity_rejects_nan_weights() {
540 let mut g = Graph::with_vertices(3);
541 g.add_edge(0, 1).unwrap();
542 g.add_edge(1, 2).unwrap();
543 let result = diversity(&g, &[1.0, f64::NAN]);
544 assert!(result.is_err());
545 }
546
547 #[test]
548 fn diversity_rejects_wrong_weight_length() {
549 let mut g = Graph::with_vertices(3);
550 g.add_edge(0, 1).unwrap();
551 let result = diversity(&g, &[1.0, 2.0]);
552 assert!(result.is_err());
553 }
554
555 #[test]
556 fn diversity_zero_weight_edge() {
557 let mut g = Graph::with_vertices(3);
559 g.add_edge(0, 1).unwrap();
560 g.add_edge(0, 2).unwrap();
561 g.add_edge(1, 2).unwrap();
562 let d = diversity(&g, &[0.0, 0.0, 0.0]).unwrap();
563 for val in &d {
564 assert!(val.is_nan());
565 }
566 }
567
568 #[test]
569 fn diversity_self_loop_contributes() {
570 let mut g = Graph::with_vertices(2);
573 g.add_edge(0, 0).unwrap();
574 g.add_edge(0, 1).unwrap();
575 let d = diversity(&g, &[2.0, 3.0]).unwrap();
578 let s = 7.0_f64;
586 let ent = 4.0 * 2.0_f64.ln() + 3.0 * 3.0_f64.ln();
587 let expected = (s.ln() - ent / s) / 3.0_f64.ln();
588 assert!((d[0] - expected).abs() < 1e-12);
589 assert!((d[1] - 0.0).abs() < 1e-12);
590 }
591}
592
593#[cfg(all(test, feature = "proptest-harness"))]
594mod proptests {
595 use super::*;
596 use crate::core::Graph;
597 use proptest::prelude::*;
598
599 proptest! {
600 #[test]
601 fn strength_all_equals_sum_of_weights(
602 n in 2u32..20,
603 seed in 0u64..10000
604 ) {
605 let m = ((n as u64 * (n as u64 - 1)) / 4).max(1) as usize;
606 let mut g = Graph::with_vertices(n);
607 let mut rng = seed;
608 let mut edges = Vec::new();
609 for _ in 0..m {
610 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
611 let u = (rng % n as u64) as u32;
612 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
613 let v = (rng % n as u64) as u32;
614 edges.push(u);
615 edges.push(v);
616 }
617 let _ = g.add_edges(edges.chunks_exact(2).map(|c| (c[0], c[1])));
618 let weights: Vec<f64> = (0..g.ecount())
619 .map(|i| (i as f64 + 1.0) * 0.5)
620 .collect();
621 let s = strength(&g, &weights).unwrap();
622 let total_strength: f64 = s.iter().sum();
625 let total_weight: f64 = weights.iter().sum();
626 prop_assert!((total_strength - 2.0 * total_weight).abs() < 1e-6);
627 }
628
629 #[test]
630 fn strength_out_plus_in_equals_all_directed(
631 n in 2u32..15,
632 seed in 0u64..10000
633 ) {
634 let m = ((n as u64 * (n as u64 - 1)) / 4).max(1) as usize;
635 let mut g = Graph::new(n, true).unwrap();
636 let mut rng = seed;
637 let mut edges = Vec::new();
638 for _ in 0..m {
639 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
640 let u = (rng % n as u64) as u32;
641 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
642 let v = (rng % n as u64) as u32;
643 edges.push(u);
644 edges.push(v);
645 }
646 let _ = g.add_edges(edges.chunks_exact(2).map(|c| (c[0], c[1])));
647 let weights: Vec<f64> = (0..g.ecount())
648 .map(|i| (i as f64 + 1.0) * 0.3)
649 .collect();
650 let s_out = strength_with_mode(&g, &weights, StrengthMode::Out, true).unwrap();
651 let s_in = strength_with_mode(&g, &weights, StrengthMode::In, true).unwrap();
652 let s_all = strength_with_mode(&g, &weights, StrengthMode::All, true).unwrap();
653 for v in 0..n as usize {
654 prop_assert!((s_out[v] + s_in[v] - s_all[v]).abs() < 1e-9,
655 "vertex {}: out={} + in={} != all={}", v, s_out[v], s_in[v], s_all[v]);
656 }
657 }
658
659 #[test]
660 fn diversity_bounded_0_1(
661 n in 3u32..15,
662 seed in 0u64..10000
663 ) {
664 let m = ((n as u64 * (n as u64 - 1)) / 4).max(1) as usize;
665 let mut g = Graph::with_vertices(n);
666 let mut rng = seed;
667 let mut edge_set = std::collections::HashSet::new();
669 for _ in 0..m {
670 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
671 let u = (rng % n as u64) as u32;
672 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
673 let v = (rng % n as u64) as u32;
674 if u != v {
675 let key = if u < v { (u, v) } else { (v, u) };
676 edge_set.insert(key);
677 }
678 }
679 for &(u, v) in &edge_set {
680 let _ = g.add_edge(u, v);
681 }
682 if g.ecount() == 0 {
683 return Ok(());
684 }
685 let weights: Vec<f64> = (0..g.ecount())
686 .map(|i| i as f64 + 1.0)
687 .collect();
688 let d = diversity(&g, &weights).unwrap();
689 for (v, &val) in d.iter().enumerate() {
690 if val.is_nan() {
691 continue;
693 }
694 prop_assert!(val >= -1e-12,
695 "diversity[{}] = {} < 0", v, val);
696 prop_assert!(val <= 1.0 + 1e-12,
697 "diversity[{}] = {} > 1", v, val);
698 }
699 }
700
701 #[test]
702 fn diversity_equal_weights_is_one_for_degree_ge_2(
703 n in 3u32..15,
704 seed in 0u64..10000
705 ) {
706 let m = ((n as u64 * (n as u64 - 1)) / 4).max(1) as usize;
707 let mut g = Graph::with_vertices(n);
708 let mut rng = seed;
709 let mut edge_set = std::collections::HashSet::new();
710 for _ in 0..m {
711 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
712 let u = (rng % n as u64) as u32;
713 rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
714 let v = (rng % n as u64) as u32;
715 if u != v {
716 let key = if u < v { (u, v) } else { (v, u) };
717 edge_set.insert(key);
718 }
719 }
720 for &(u, v) in &edge_set {
721 let _ = g.add_edge(u, v);
722 }
723 if g.ecount() == 0 {
724 return Ok(());
725 }
726 let weights = vec![1.0; g.ecount()];
728 let d = diversity(&g, &weights).unwrap();
729 for v in 0..n {
730 let deg = g.degree(v).unwrap();
731 let val = d[v as usize];
732 if deg >= 2 {
733 prop_assert!((val - 1.0).abs() < 1e-10,
734 "diversity[{}] = {} (degree {}), expected 1.0", v, val, deg);
735 } else if deg == 1 {
736 prop_assert!((val - 0.0).abs() < 1e-12,
737 "diversity[{}] = {} (degree 1), expected 0.0", v, val);
738 } else {
739 prop_assert!(val.is_nan(),
740 "diversity[{}] = {} (degree 0), expected NaN", v, val);
741 }
742 }
743 }
744 }
745}