1#![allow(
17 clippy::cast_possible_truncation,
18 clippy::cast_precision_loss,
19 clippy::many_single_char_names,
20 clippy::needless_range_loop,
21 clippy::too_many_lines
22)]
23
24use crate::core::{Graph, IgraphResult};
25
26fn neighbor_degree_sums(graph: &Graph) -> IgraphResult<Vec<u64>> {
27 let n = graph.vcount();
28 let mut s = vec![0_u64; n as usize];
29
30 for v in 0..n {
31 let nbs = graph.neighbors(v)?;
32 let mut sum = 0_u64;
33 for nb in nbs {
34 sum = sum.saturating_add(graph.degree(nb)? as u64);
35 }
36 s[v as usize] = sum;
37 }
38
39 Ok(s)
40}
41
42pub fn first_neighborhood_zagreb(graph: &Graph) -> IgraphResult<u64> {
57 let s = neighbor_degree_sums(graph)?;
58 let mut nm1 = 0_u64;
59
60 for &sv in &s {
61 nm1 = nm1.saturating_add(sv.saturating_mul(sv));
62 }
63
64 Ok(nm1)
65}
66
67pub fn second_neighborhood_zagreb(graph: &Graph) -> IgraphResult<u64> {
83 let s = neighbor_degree_sums(graph)?;
84 let mut nm2 = 0_u64;
85
86 for (u, v) in graph.edges() {
87 if u == v {
88 continue;
89 }
90 let su = s[u as usize];
91 let sv = s[v as usize];
92 nm2 = nm2.saturating_add(su.saturating_mul(sv));
93 }
94
95 Ok(nm2)
96}
97
98pub fn neighborhood_forgotten_index(graph: &Graph) -> IgraphResult<u64> {
114 let s = neighbor_degree_sums(graph)?;
115 let mut nf = 0_u64;
116
117 for &sv in &s {
118 nf = nf.saturating_add(sv.saturating_mul(sv).saturating_mul(sv));
119 }
120
121 Ok(nf)
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 fn single_edge() -> Graph {
129 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
130 }
131
132 fn path3() -> Graph {
133 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
134 }
135
136 fn path4() -> Graph {
137 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
138 }
139
140 fn k3() -> Graph {
141 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
142 }
143
144 fn k4() -> Graph {
145 Graph::from_edges(
146 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
147 false,
148 Some(4),
149 )
150 .unwrap()
151 }
152
153 fn cycle4() -> Graph {
154 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
155 }
156
157 fn cycle5() -> Graph {
158 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
159 }
160
161 fn star5() -> Graph {
162 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
163 }
164
165 fn paw() -> Graph {
166 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
167 }
168
169 fn diamond() -> Graph {
170 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)], false, Some(4)).unwrap()
171 }
172
173 fn compute_s(g: &Graph) -> Vec<u64> {
175 neighbor_degree_sums(g).unwrap()
176 }
177
178 #[test]
181 fn nds_single_edge() {
182 assert_eq!(compute_s(&single_edge()), vec![1, 1]);
184 }
185
186 #[test]
187 fn nds_path3() {
188 assert_eq!(compute_s(&path3()), vec![2, 2, 2]);
190 }
191
192 #[test]
193 fn nds_k3() {
194 assert_eq!(compute_s(&k3()), vec![4, 4, 4]);
196 }
197
198 #[test]
199 fn nds_k4() {
200 assert_eq!(compute_s(&k4()), vec![9, 9, 9, 9]);
202 }
203
204 #[test]
205 fn nds_star5() {
206 assert_eq!(compute_s(&star5()), vec![4, 4, 4, 4, 4]);
209 }
210
211 #[test]
212 fn nds_cycle4() {
213 assert_eq!(compute_s(&cycle4()), vec![4, 4, 4, 4]);
215 }
216
217 #[test]
218 fn nds_paw() {
219 assert_eq!(compute_s(&paw()), vec![5, 5, 5, 3]);
225 }
226
227 #[test]
230 fn nm1_empty() {
231 let g = Graph::with_vertices(0);
232 assert_eq!(first_neighborhood_zagreb(&g).unwrap(), 0);
233 }
234
235 #[test]
236 fn nm1_isolated() {
237 let g = Graph::with_vertices(5);
238 assert_eq!(first_neighborhood_zagreb(&g).unwrap(), 0);
239 }
240
241 #[test]
242 fn nm1_single_edge() {
243 assert_eq!(first_neighborhood_zagreb(&single_edge()).unwrap(), 2);
245 }
246
247 #[test]
248 fn nm1_path3() {
249 assert_eq!(first_neighborhood_zagreb(&path3()).unwrap(), 12);
251 }
252
253 #[test]
254 fn nm1_path4() {
255 assert_eq!(first_neighborhood_zagreb(&path4()).unwrap(), 26);
259 }
260
261 #[test]
262 fn nm1_k3() {
263 assert_eq!(first_neighborhood_zagreb(&k3()).unwrap(), 48);
265 }
266
267 #[test]
268 fn nm1_k4() {
269 assert_eq!(first_neighborhood_zagreb(&k4()).unwrap(), 324);
271 }
272
273 #[test]
274 fn nm1_cycle4() {
275 assert_eq!(first_neighborhood_zagreb(&cycle4()).unwrap(), 64);
277 }
278
279 #[test]
280 fn nm1_cycle5() {
281 assert_eq!(first_neighborhood_zagreb(&cycle5()).unwrap(), 80);
283 }
284
285 #[test]
286 fn nm1_star5() {
287 assert_eq!(first_neighborhood_zagreb(&star5()).unwrap(), 80);
289 }
290
291 #[test]
292 fn nm1_paw() {
293 assert_eq!(first_neighborhood_zagreb(&paw()).unwrap(), 84);
295 }
296
297 #[test]
298 fn nm1_diamond() {
299 assert_eq!(first_neighborhood_zagreb(&diamond()).unwrap(), 170);
306 }
307
308 #[test]
309 fn nm1_regular_formula() {
310 for g in &[k3(), k4(), cycle4(), cycle5()] {
312 let n = u64::from(g.vcount());
313 let r = g.degree(0).unwrap() as u64;
314 assert_eq!(first_neighborhood_zagreb(g).unwrap(), n * r * r * r * r);
315 }
316 }
317
318 #[test]
321 fn nm2_empty() {
322 let g = Graph::with_vertices(0);
323 assert_eq!(second_neighborhood_zagreb(&g).unwrap(), 0);
324 }
325
326 #[test]
327 fn nm2_isolated() {
328 let g = Graph::with_vertices(5);
329 assert_eq!(second_neighborhood_zagreb(&g).unwrap(), 0);
330 }
331
332 #[test]
333 fn nm2_single_edge() {
334 assert_eq!(second_neighborhood_zagreb(&single_edge()).unwrap(), 1);
336 }
337
338 #[test]
339 fn nm2_path3() {
340 assert_eq!(second_neighborhood_zagreb(&path3()).unwrap(), 8);
342 }
343
344 #[test]
345 fn nm2_path4() {
346 assert_eq!(second_neighborhood_zagreb(&path4()).unwrap(), 21);
348 }
349
350 #[test]
351 fn nm2_k3() {
352 assert_eq!(second_neighborhood_zagreb(&k3()).unwrap(), 48);
354 }
355
356 #[test]
357 fn nm2_k4() {
358 assert_eq!(second_neighborhood_zagreb(&k4()).unwrap(), 486);
360 }
361
362 #[test]
363 fn nm2_cycle4() {
364 assert_eq!(second_neighborhood_zagreb(&cycle4()).unwrap(), 64);
366 }
367
368 #[test]
369 fn nm2_cycle5() {
370 assert_eq!(second_neighborhood_zagreb(&cycle5()).unwrap(), 80);
372 }
373
374 #[test]
375 fn nm2_star5() {
376 assert_eq!(second_neighborhood_zagreb(&star5()).unwrap(), 64);
378 }
379
380 #[test]
381 fn nm2_paw() {
382 assert_eq!(second_neighborhood_zagreb(&paw()).unwrap(), 90);
386 }
387
388 #[test]
389 fn nm2_diamond() {
390 assert_eq!(second_neighborhood_zagreb(&diamond()).unwrap(), 217);
394 }
395
396 #[test]
397 fn nm2_regular_formula() {
398 for g in &[k3(), k4(), cycle4(), cycle5()] {
400 let m = g.ecount() as u64;
401 let r = g.degree(0).unwrap() as u64;
402 assert_eq!(second_neighborhood_zagreb(g).unwrap(), m * r * r * r * r);
403 }
404 }
405
406 #[test]
409 fn nf_empty() {
410 let g = Graph::with_vertices(0);
411 assert_eq!(neighborhood_forgotten_index(&g).unwrap(), 0);
412 }
413
414 #[test]
415 fn nf_isolated() {
416 let g = Graph::with_vertices(5);
417 assert_eq!(neighborhood_forgotten_index(&g).unwrap(), 0);
418 }
419
420 #[test]
421 fn nf_single_edge() {
422 assert_eq!(neighborhood_forgotten_index(&single_edge()).unwrap(), 2);
424 }
425
426 #[test]
427 fn nf_path3() {
428 assert_eq!(neighborhood_forgotten_index(&path3()).unwrap(), 24);
430 }
431
432 #[test]
433 fn nf_path4() {
434 assert_eq!(neighborhood_forgotten_index(&path4()).unwrap(), 70);
436 }
437
438 #[test]
439 fn nf_k3() {
440 assert_eq!(neighborhood_forgotten_index(&k3()).unwrap(), 192);
442 }
443
444 #[test]
445 fn nf_k4() {
446 assert_eq!(neighborhood_forgotten_index(&k4()).unwrap(), 2916);
448 }
449
450 #[test]
451 fn nf_cycle4() {
452 assert_eq!(neighborhood_forgotten_index(&cycle4()).unwrap(), 256);
454 }
455
456 #[test]
457 fn nf_cycle5() {
458 assert_eq!(neighborhood_forgotten_index(&cycle5()).unwrap(), 320);
460 }
461
462 #[test]
463 fn nf_star5() {
464 assert_eq!(neighborhood_forgotten_index(&star5()).unwrap(), 320);
466 }
467
468 #[test]
469 fn nf_paw() {
470 assert_eq!(neighborhood_forgotten_index(&paw()).unwrap(), 402);
472 }
473
474 #[test]
475 fn nf_diamond() {
476 assert_eq!(neighborhood_forgotten_index(&diamond()).unwrap(), 1118);
478 }
479
480 #[test]
481 fn nf_regular_formula() {
482 for g in &[k3(), k4(), cycle4(), cycle5()] {
484 let n = u64::from(g.vcount());
485 let r = g.degree(0).unwrap() as u64;
486 let r6 = r * r * r * r * r * r;
487 assert_eq!(neighborhood_forgotten_index(g).unwrap(), n * r6);
488 }
489 }
490
491 #[test]
494 fn nm1_nm2_coincide_for_regular() {
495 for g in &[k3(), k4(), cycle4(), cycle5()] {
499 let nm1 = first_neighborhood_zagreb(g).unwrap();
500 let nm2 = second_neighborhood_zagreb(g).unwrap();
501 let r = g.degree(0).unwrap() as u64;
502 assert_eq!(nm2 * 2, nm1 * r);
503 }
504 }
505
506 #[test]
507 fn all_positive_for_nonempty_edges() {
508 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
509 assert!(first_neighborhood_zagreb(g).unwrap() > 0);
510 assert!(second_neighborhood_zagreb(g).unwrap() > 0);
511 assert!(neighborhood_forgotten_index(g).unwrap() > 0);
512 }
513 }
514}