rust_igraph/algorithms/properties/
zagreb_connection.rs1#![allow(
15 clippy::cast_possible_truncation,
16 clippy::cast_precision_loss,
17 clippy::many_single_char_names,
18 clippy::needless_range_loop,
19 clippy::too_many_lines
20)]
21
22use crate::core::{Graph, IgraphResult};
23use std::collections::HashSet;
24
25fn connection_numbers(graph: &Graph) -> IgraphResult<Vec<f64>> {
26 let n = graph.vcount() as usize;
27 let mut tau = vec![0.0_f64; n];
28
29 for v in 0..graph.vcount() {
30 let mut dist1 = HashSet::new();
31 for nb in graph.neighbors(v)? {
32 dist1.insert(nb);
33 }
34 let mut dist2 = HashSet::new();
35 for &nb1 in &dist1 {
36 for nb2 in graph.neighbors(nb1)? {
37 if nb2 != v && !dist1.contains(&nb2) {
38 dist2.insert(nb2);
39 }
40 }
41 }
42 tau[v as usize] = dist2.len() as f64;
43 }
44
45 Ok(tau)
46}
47
48pub fn first_zagreb_connection(graph: &Graph) -> IgraphResult<f64> {
65 let tau = connection_numbers(graph)?;
66 Ok(tau.iter().map(|t| t * t).sum())
67}
68
69pub fn second_zagreb_connection(graph: &Graph) -> IgraphResult<f64> {
84 let tau = connection_numbers(graph)?;
85 let mut zc2 = 0.0_f64;
86 for (u, v) in graph.edges() {
87 if u == v {
88 continue;
89 }
90 zc2 += tau[u as usize] * tau[v as usize];
91 }
92 Ok(zc2)
93}
94
95pub fn modified_first_zagreb_connection(graph: &Graph) -> IgraphResult<f64> {
110 let tau = connection_numbers(graph)?;
111 let mut zcs = 0.0_f64;
112 for (u, v) in graph.edges() {
113 if u == v {
114 continue;
115 }
116 zcs += tau[u as usize] + tau[v as usize];
117 }
118 Ok(zcs)
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 fn single_edge() -> Graph {
126 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
127 }
128
129 fn path3() -> Graph {
130 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
131 }
132
133 fn path4() -> Graph {
134 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
135 }
136
137 fn path5() -> Graph {
138 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4)], false, Some(5)).unwrap()
139 }
140
141 fn k3() -> Graph {
142 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
143 }
144
145 fn k4() -> Graph {
146 Graph::from_edges(
147 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
148 false,
149 Some(4),
150 )
151 .unwrap()
152 }
153
154 fn cycle4() -> Graph {
155 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
156 }
157
158 fn cycle5() -> Graph {
159 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
160 }
161
162 fn cycle6() -> Graph {
163 Graph::from_edges(
164 &[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 0)],
165 false,
166 Some(6),
167 )
168 .unwrap()
169 }
170
171 fn star5() -> Graph {
172 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
173 }
174
175 fn paw() -> Graph {
176 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
177 }
178
179 #[test]
182 fn tau_empty() {
183 let g = Graph::with_vertices(0);
184 let tau = connection_numbers(&g).unwrap();
185 assert!(tau.is_empty());
186 }
187
188 #[test]
189 fn tau_single_vertex() {
190 let g = Graph::with_vertices(1);
191 let tau = connection_numbers(&g).unwrap();
192 assert!((tau[0]).abs() < 1e-10);
193 }
194
195 #[test]
196 fn tau_single_edge() {
197 let tau = connection_numbers(&single_edge()).unwrap();
199 assert!((tau[0]).abs() < 1e-10);
200 assert!((tau[1]).abs() < 1e-10);
201 }
202
203 #[test]
204 fn tau_path3() {
205 let tau = connection_numbers(&path3()).unwrap();
207 assert!((tau[0] - 1.0).abs() < 1e-10);
208 assert!((tau[1]).abs() < 1e-10);
209 assert!((tau[2] - 1.0).abs() < 1e-10);
210 }
211
212 #[test]
213 fn tau_path4() {
214 let tau = connection_numbers(&path4()).unwrap();
216 assert!((tau[0] - 1.0).abs() < 1e-10);
217 assert!((tau[1] - 1.0).abs() < 1e-10);
218 assert!((tau[2] - 1.0).abs() < 1e-10);
219 assert!((tau[3] - 1.0).abs() < 1e-10);
220 }
221
222 #[test]
223 fn tau_path5() {
224 let tau = connection_numbers(&path5()).unwrap();
226 assert!((tau[0] - 1.0).abs() < 1e-10);
227 assert!((tau[1] - 1.0).abs() < 1e-10);
228 assert!((tau[2] - 2.0).abs() < 1e-10);
229 assert!((tau[3] - 1.0).abs() < 1e-10);
230 assert!((tau[4] - 1.0).abs() < 1e-10);
231 }
232
233 #[test]
234 fn tau_k3() {
235 let tau = connection_numbers(&k3()).unwrap();
237 for &t in &tau {
238 assert!((t).abs() < 1e-10);
239 }
240 }
241
242 #[test]
243 fn tau_k4() {
244 let tau = connection_numbers(&k4()).unwrap();
245 for &t in &tau {
246 assert!((t).abs() < 1e-10);
247 }
248 }
249
250 #[test]
251 fn tau_cycle4() {
252 let tau = connection_numbers(&cycle4()).unwrap();
254 for &t in &tau {
255 assert!((t - 1.0).abs() < 1e-10);
256 }
257 }
258
259 #[test]
260 fn tau_cycle5() {
261 let tau = connection_numbers(&cycle5()).unwrap();
263 for &t in &tau {
264 assert!((t - 2.0).abs() < 1e-10);
265 }
266 }
267
268 #[test]
269 fn tau_cycle6() {
270 let tau = connection_numbers(&cycle6()).unwrap();
272 for &t in &tau {
273 assert!((t - 2.0).abs() < 1e-10);
274 }
275 }
276
277 #[test]
278 fn tau_star5() {
279 let tau = connection_numbers(&star5()).unwrap();
282 assert!((tau[0]).abs() < 1e-10);
283 for i in 1..5 {
284 assert!((tau[i] - 3.0).abs() < 1e-10);
285 }
286 }
287
288 #[test]
289 fn tau_paw() {
290 let tau = connection_numbers(&paw()).unwrap();
296 assert!((tau[0] - 1.0).abs() < 1e-10);
297 assert!((tau[1] - 1.0).abs() < 1e-10);
298 assert!((tau[2]).abs() < 1e-10);
299 assert!((tau[3] - 2.0).abs() < 1e-10);
300 }
301
302 #[test]
305 fn zc1_empty() {
306 let g = Graph::with_vertices(0);
307 assert!((first_zagreb_connection(&g).unwrap()).abs() < 1e-10);
308 }
309
310 #[test]
311 fn zc1_single_edge() {
312 assert!((first_zagreb_connection(&single_edge()).unwrap()).abs() < 1e-10);
313 }
314
315 #[test]
316 fn zc1_path3() {
317 assert!((first_zagreb_connection(&path3()).unwrap() - 2.0).abs() < 1e-10);
319 }
320
321 #[test]
322 fn zc1_path4() {
323 assert!((first_zagreb_connection(&path4()).unwrap() - 4.0).abs() < 1e-10);
325 }
326
327 #[test]
328 fn zc1_k3() {
329 assert!((first_zagreb_connection(&k3()).unwrap()).abs() < 1e-10);
331 }
332
333 #[test]
334 fn zc1_k4() {
335 assert!((first_zagreb_connection(&k4()).unwrap()).abs() < 1e-10);
336 }
337
338 #[test]
339 fn zc1_cycle4() {
340 assert!((first_zagreb_connection(&cycle4()).unwrap() - 4.0).abs() < 1e-10);
342 }
343
344 #[test]
345 fn zc1_cycle5() {
346 assert!((first_zagreb_connection(&cycle5()).unwrap() - 20.0).abs() < 1e-10);
348 }
349
350 #[test]
351 fn zc1_star5() {
352 assert!((first_zagreb_connection(&star5()).unwrap() - 36.0).abs() < 1e-10);
354 }
355
356 #[test]
357 fn zc1_paw() {
358 assert!((first_zagreb_connection(&paw()).unwrap() - 6.0).abs() < 1e-10);
360 }
361
362 #[test]
365 fn zc2_empty() {
366 let g = Graph::with_vertices(0);
367 assert!((second_zagreb_connection(&g).unwrap()).abs() < 1e-10);
368 }
369
370 #[test]
371 fn zc2_single_edge() {
372 assert!((second_zagreb_connection(&single_edge()).unwrap()).abs() < 1e-10);
373 }
374
375 #[test]
376 fn zc2_path3() {
377 assert!((second_zagreb_connection(&path3()).unwrap()).abs() < 1e-10);
379 }
380
381 #[test]
382 fn zc2_path4() {
383 assert!((second_zagreb_connection(&path4()).unwrap() - 3.0).abs() < 1e-10);
385 }
386
387 #[test]
388 fn zc2_k3() {
389 assert!((second_zagreb_connection(&k3()).unwrap()).abs() < 1e-10);
390 }
391
392 #[test]
393 fn zc2_cycle4() {
394 assert!((second_zagreb_connection(&cycle4()).unwrap() - 4.0).abs() < 1e-10);
396 }
397
398 #[test]
399 fn zc2_cycle5() {
400 assert!((second_zagreb_connection(&cycle5()).unwrap() - 20.0).abs() < 1e-10);
402 }
403
404 #[test]
405 fn zc2_star5() {
406 assert!((second_zagreb_connection(&star5()).unwrap()).abs() < 1e-10);
408 }
409
410 #[test]
411 fn zc2_paw() {
412 assert!((second_zagreb_connection(&paw()).unwrap() - 1.0).abs() < 1e-10);
416 }
417
418 #[test]
421 fn zcs_empty() {
422 let g = Graph::with_vertices(0);
423 assert!((modified_first_zagreb_connection(&g).unwrap()).abs() < 1e-10);
424 }
425
426 #[test]
427 fn zcs_single_edge() {
428 assert!((modified_first_zagreb_connection(&single_edge()).unwrap()).abs() < 1e-10);
429 }
430
431 #[test]
432 fn zcs_path3() {
433 assert!((modified_first_zagreb_connection(&path3()).unwrap() - 2.0).abs() < 1e-10);
435 }
436
437 #[test]
438 fn zcs_path4() {
439 assert!((modified_first_zagreb_connection(&path4()).unwrap() - 6.0).abs() < 1e-10);
441 }
442
443 #[test]
444 fn zcs_k3() {
445 assert!((modified_first_zagreb_connection(&k3()).unwrap()).abs() < 1e-10);
446 }
447
448 #[test]
449 fn zcs_cycle4() {
450 assert!((modified_first_zagreb_connection(&cycle4()).unwrap() - 8.0).abs() < 1e-10);
452 }
453
454 #[test]
455 fn zcs_cycle5() {
456 assert!((modified_first_zagreb_connection(&cycle5()).unwrap() - 20.0).abs() < 1e-10);
458 }
459
460 #[test]
461 fn zcs_star5() {
462 assert!((modified_first_zagreb_connection(&star5()).unwrap() - 12.0).abs() < 1e-10);
464 }
465
466 #[test]
467 fn zcs_paw() {
468 assert!((modified_first_zagreb_connection(&paw()).unwrap() - 6.0).abs() < 1e-10);
471 }
472
473 #[test]
476 fn all_zero_for_complete() {
477 for g in &[k3(), k4()] {
478 assert!((first_zagreb_connection(g).unwrap()).abs() < 1e-10);
479 assert!((second_zagreb_connection(g).unwrap()).abs() < 1e-10);
480 assert!((modified_first_zagreb_connection(g).unwrap()).abs() < 1e-10);
481 }
482 }
483
484 #[test]
485 fn all_nonneg() {
486 for g in &[
487 single_edge(),
488 path3(),
489 path4(),
490 k3(),
491 cycle4(),
492 cycle5(),
493 star5(),
494 paw(),
495 ] {
496 assert!(first_zagreb_connection(g).unwrap() >= -1e-10);
497 assert!(second_zagreb_connection(g).unwrap() >= -1e-10);
498 assert!(modified_first_zagreb_connection(g).unwrap() >= -1e-10);
499 }
500 }
501
502 #[test]
503 fn zcs_equals_2sum_tau_deg() {
504 for g in &[path3(), cycle4(), star5(), paw()] {
506 let tau = connection_numbers(g).unwrap();
507 let mut vertex_sum = 0.0_f64;
508 for v in 0..g.vcount() {
509 vertex_sum += tau[v as usize] * g.degree(v).unwrap() as f64;
510 }
511 let zcs = modified_first_zagreb_connection(g).unwrap();
512 assert!((zcs - vertex_sum).abs() < 1e-8);
513 }
514 }
515}