rust_igraph/algorithms/properties/
sum_connectivity.rs1#![allow(
14 clippy::cast_possible_truncation,
15 clippy::cast_precision_loss,
16 clippy::many_single_char_names,
17 clippy::needless_range_loop,
18 clippy::too_many_lines
19)]
20
21use crate::core::{Graph, IgraphResult};
22
23pub fn sum_connectivity_index(graph: &Graph) -> IgraphResult<f64> {
42 let n = graph.vcount();
43 if n == 0 {
44 return Ok(0.0);
45 }
46
47 let mut sci = 0.0_f64;
48
49 for (u, v) in graph.edges() {
50 if u == v {
51 continue;
52 }
53 let du = graph.degree(u)? as f64;
54 let dv = graph.degree(v)? as f64;
55 let sum_d = du + dv;
56 if sum_d <= 0.0 {
57 continue;
58 }
59 sci += 1.0 / sum_d.sqrt();
60 }
61
62 Ok(sci)
63}
64
65pub fn inverse_sum_indeg_index(graph: &Graph) -> IgraphResult<f64> {
85 let n = graph.vcount();
86 if n == 0 {
87 return Ok(0.0);
88 }
89
90 let mut isi = 0.0_f64;
91
92 for (u, v) in graph.edges() {
93 if u == v {
94 continue;
95 }
96 let du = graph.degree(u)? as f64;
97 let dv = graph.degree(v)? as f64;
98 let sum_d = du + dv;
99 if sum_d <= 0.0 {
100 continue;
101 }
102 isi += du * dv / sum_d;
103 }
104
105 Ok(isi)
106}
107
108pub fn symmetric_division_deg_index(graph: &Graph) -> IgraphResult<f64> {
127 let n = graph.vcount();
128 if n == 0 {
129 return Ok(0.0);
130 }
131
132 let mut sdd = 0.0_f64;
133
134 for (u, v) in graph.edges() {
135 if u == v {
136 continue;
137 }
138 let du = graph.degree(u)? as f64;
139 let dv = graph.degree(v)? as f64;
140 let prod = du * dv;
141 if prod <= 0.0 {
142 continue;
143 }
144 sdd += (du * du + dv * dv) / prod;
145 }
146
147 Ok(sdd)
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 fn single_edge() -> Graph {
155 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
156 }
157
158 fn path3() -> Graph {
159 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
160 }
161
162 fn path4() -> Graph {
163 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
164 }
165
166 fn k3() -> Graph {
167 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
168 }
169
170 fn k4() -> Graph {
171 Graph::from_edges(
172 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
173 false,
174 Some(4),
175 )
176 .unwrap()
177 }
178
179 fn cycle4() -> Graph {
180 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
181 }
182
183 fn cycle5() -> Graph {
184 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
185 }
186
187 fn star5() -> Graph {
188 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
189 }
190
191 #[test]
194 fn sci_empty() {
195 let g = Graph::with_vertices(0);
196 assert!((sum_connectivity_index(&g).unwrap() - 0.0).abs() < 1e-10);
197 }
198
199 #[test]
200 fn sci_single_vertex() {
201 let g = Graph::with_vertices(1);
202 assert!((sum_connectivity_index(&g).unwrap() - 0.0).abs() < 1e-10);
203 }
204
205 #[test]
206 fn sci_no_edges() {
207 let g = Graph::with_vertices(3);
208 assert!((sum_connectivity_index(&g).unwrap() - 0.0).abs() < 1e-10);
209 }
210
211 #[test]
212 fn sci_single_edge() {
213 let expected = 1.0 / 2.0_f64.sqrt();
215 assert!((sum_connectivity_index(&single_edge()).unwrap() - expected).abs() < 1e-10);
216 }
217
218 #[test]
219 fn sci_path3() {
220 let expected = 2.0 / 3.0_f64.sqrt();
222 assert!((sum_connectivity_index(&path3()).unwrap() - expected).abs() < 1e-10);
223 }
224
225 #[test]
226 fn sci_path4() {
227 let expected = 2.0 / 3.0_f64.sqrt() + 0.5;
229 assert!((sum_connectivity_index(&path4()).unwrap() - expected).abs() < 1e-10);
230 }
231
232 #[test]
233 fn sci_k3() {
234 assert!((sum_connectivity_index(&k3()).unwrap() - 1.5).abs() < 1e-10);
236 }
237
238 #[test]
239 fn sci_k4() {
240 let expected = 6.0 / 6.0_f64.sqrt();
242 assert!((sum_connectivity_index(&k4()).unwrap() - expected).abs() < 1e-10);
243 }
244
245 #[test]
246 fn sci_cycle4() {
247 assert!((sum_connectivity_index(&cycle4()).unwrap() - 2.0).abs() < 1e-10);
249 }
250
251 #[test]
252 fn sci_cycle5() {
253 assert!((sum_connectivity_index(&cycle5()).unwrap() - 2.5).abs() < 1e-10);
255 }
256
257 #[test]
258 fn sci_star5() {
259 let expected = 4.0 / 5.0_f64.sqrt();
261 assert!((sum_connectivity_index(&star5()).unwrap() - expected).abs() < 1e-10);
262 }
263
264 #[test]
265 fn sci_regular_formula() {
266 for g in &[k3(), k4(), cycle4(), cycle5()] {
268 let m = g.ecount() as f64;
269 let r = g.degree(0).unwrap() as f64;
270 let expected = m / (2.0 * r).sqrt();
271 assert!((sum_connectivity_index(g).unwrap() - expected).abs() < 1e-8);
272 }
273 }
274
275 #[test]
276 fn sci_positive() {
277 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5()] {
278 assert!(sum_connectivity_index(g).unwrap() > 0.0);
279 }
280 }
281
282 #[test]
285 fn isi_empty() {
286 let g = Graph::with_vertices(0);
287 assert!((inverse_sum_indeg_index(&g).unwrap() - 0.0).abs() < 1e-10);
288 }
289
290 #[test]
291 fn isi_single_vertex() {
292 let g = Graph::with_vertices(1);
293 assert!((inverse_sum_indeg_index(&g).unwrap() - 0.0).abs() < 1e-10);
294 }
295
296 #[test]
297 fn isi_no_edges() {
298 let g = Graph::with_vertices(3);
299 assert!((inverse_sum_indeg_index(&g).unwrap() - 0.0).abs() < 1e-10);
300 }
301
302 #[test]
303 fn isi_single_edge() {
304 assert!((inverse_sum_indeg_index(&single_edge()).unwrap() - 0.5).abs() < 1e-10);
306 }
307
308 #[test]
309 fn isi_path3() {
310 let expected = 4.0 / 3.0;
312 assert!((inverse_sum_indeg_index(&path3()).unwrap() - expected).abs() < 1e-10);
313 }
314
315 #[test]
316 fn isi_path4() {
317 let expected = 7.0 / 3.0;
320 assert!((inverse_sum_indeg_index(&path4()).unwrap() - expected).abs() < 1e-10);
321 }
322
323 #[test]
324 fn isi_k3() {
325 assert!((inverse_sum_indeg_index(&k3()).unwrap() - 3.0).abs() < 1e-10);
327 }
328
329 #[test]
330 fn isi_k4() {
331 assert!((inverse_sum_indeg_index(&k4()).unwrap() - 9.0).abs() < 1e-10);
333 }
334
335 #[test]
336 fn isi_cycle4() {
337 assert!((inverse_sum_indeg_index(&cycle4()).unwrap() - 4.0).abs() < 1e-10);
339 }
340
341 #[test]
342 fn isi_cycle5() {
343 assert!((inverse_sum_indeg_index(&cycle5()).unwrap() - 5.0).abs() < 1e-10);
345 }
346
347 #[test]
348 fn isi_star5() {
349 let expected = 16.0 / 5.0;
351 assert!((inverse_sum_indeg_index(&star5()).unwrap() - expected).abs() < 1e-10);
352 }
353
354 #[test]
355 fn isi_regular_formula() {
356 for g in &[k3(), k4(), cycle4(), cycle5()] {
358 let m = g.ecount() as f64;
359 let r = g.degree(0).unwrap() as f64;
360 let expected = m * r / 2.0;
361 assert!((inverse_sum_indeg_index(g).unwrap() - expected).abs() < 1e-8);
362 }
363 }
364
365 #[test]
366 fn isi_positive() {
367 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5()] {
368 assert!(inverse_sum_indeg_index(g).unwrap() > 0.0);
369 }
370 }
371
372 #[test]
373 fn isi_diamond() {
374 let g =
380 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)], false, Some(4)).unwrap();
381 let expected = 63.0 / 10.0;
382 assert!((inverse_sum_indeg_index(&g).unwrap() - expected).abs() < 1e-10);
383 }
384
385 #[test]
388 fn sdd_empty() {
389 let g = Graph::with_vertices(0);
390 assert!((symmetric_division_deg_index(&g).unwrap() - 0.0).abs() < 1e-10);
391 }
392
393 #[test]
394 fn sdd_single_vertex() {
395 let g = Graph::with_vertices(1);
396 assert!((symmetric_division_deg_index(&g).unwrap() - 0.0).abs() < 1e-10);
397 }
398
399 #[test]
400 fn sdd_no_edges() {
401 let g = Graph::with_vertices(3);
402 assert!((symmetric_division_deg_index(&g).unwrap() - 0.0).abs() < 1e-10);
403 }
404
405 #[test]
406 fn sdd_single_edge() {
407 assert!((symmetric_division_deg_index(&single_edge()).unwrap() - 2.0).abs() < 1e-10);
409 }
410
411 #[test]
412 fn sdd_path3() {
413 assert!((symmetric_division_deg_index(&path3()).unwrap() - 5.0).abs() < 1e-10);
417 }
418
419 #[test]
420 fn sdd_path4() {
421 assert!((symmetric_division_deg_index(&path4()).unwrap() - 7.0).abs() < 1e-10);
424 }
425
426 #[test]
427 fn sdd_k3() {
428 assert!((symmetric_division_deg_index(&k3()).unwrap() - 6.0).abs() < 1e-10);
430 }
431
432 #[test]
433 fn sdd_k4() {
434 assert!((symmetric_division_deg_index(&k4()).unwrap() - 12.0).abs() < 1e-10);
436 }
437
438 #[test]
439 fn sdd_cycle4() {
440 assert!((symmetric_division_deg_index(&cycle4()).unwrap() - 8.0).abs() < 1e-10);
442 }
443
444 #[test]
445 fn sdd_cycle5() {
446 assert!((symmetric_division_deg_index(&cycle5()).unwrap() - 10.0).abs() < 1e-10);
448 }
449
450 #[test]
451 fn sdd_star5() {
452 assert!((symmetric_division_deg_index(&star5()).unwrap() - 17.0).abs() < 1e-10);
454 }
455
456 #[test]
457 fn sdd_regular_equals_2m() {
458 for g in &[k3(), k4(), cycle4(), cycle5()] {
460 let expected = 2.0 * g.ecount() as f64;
461 assert!((symmetric_division_deg_index(g).unwrap() - expected).abs() < 1e-10);
462 }
463 }
464
465 #[test]
466 fn sdd_geq_2m() {
467 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5()] {
469 let sdd = symmetric_division_deg_index(g).unwrap();
470 assert!(sdd >= 2.0 * g.ecount() as f64 - 1e-10);
471 }
472 }
473
474 #[test]
475 fn sdd_diamond() {
476 let g =
485 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)], false, Some(4)).unwrap();
486 let expected = 32.0 / 3.0;
487 assert!((symmetric_division_deg_index(&g).unwrap() - expected).abs() < 1e-10);
488 }
489
490 #[test]
493 fn all_positive_for_connected() {
494 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5()] {
495 assert!(sum_connectivity_index(g).unwrap() > 0.0);
496 assert!(inverse_sum_indeg_index(g).unwrap() > 0.0);
497 assert!(symmetric_division_deg_index(g).unwrap() > 0.0);
498 }
499 }
500
501 #[test]
502 fn with_isolated_vertex() {
503 let g = Graph::from_edges(&[(0, 1)], false, Some(3)).unwrap();
504 let sci = sum_connectivity_index(&g).unwrap();
506 assert!((sci - 1.0 / 2.0_f64.sqrt()).abs() < 1e-10);
507
508 let isi = inverse_sum_indeg_index(&g).unwrap();
509 assert!((isi - 0.5).abs() < 1e-10);
510
511 let sdd = symmetric_division_deg_index(&g).unwrap();
512 assert!((sdd - 2.0).abs() < 1e-10);
513 }
514
515 #[test]
516 fn isi_leq_second_zagreb_over_2() {
517 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5()] {
520 let isi = inverse_sum_indeg_index(g).unwrap();
521 let mut m2 = 0.0_f64;
522 for (u, v) in g.edges() {
523 if u == v {
524 continue;
525 }
526 let du = g.degree(u).unwrap() as f64;
527 let dv = g.degree(v).unwrap() as f64;
528 m2 += du * dv;
529 }
530 assert!(isi <= m2 / 2.0 + 1e-10);
531 }
532 }
533}