rust_igraph/algorithms/properties/
forgotten_zagreb.rs1#![allow(
16 clippy::cast_possible_truncation,
17 clippy::cast_precision_loss,
18 clippy::many_single_char_names,
19 clippy::needless_range_loop,
20 clippy::too_many_lines
21)]
22
23use crate::core::{Graph, IgraphResult};
24
25pub fn forgotten_index(graph: &Graph) -> IgraphResult<f64> {
42 let n = graph.vcount();
43 if n == 0 {
44 return Ok(0.0);
45 }
46
47 let mut f = 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 f += du * du + dv * dv;
56 }
57
58 Ok(f)
59}
60
61pub fn reduced_second_zagreb(graph: &Graph) -> IgraphResult<f64> {
78 let n = graph.vcount();
79 if n == 0 {
80 return Ok(0.0);
81 }
82
83 let mut rm2 = 0.0_f64;
84
85 for (u, v) in graph.edges() {
86 if u == v {
87 continue;
88 }
89 let du = graph.degree(u)? as f64;
90 let dv = graph.degree(v)? as f64;
91 rm2 += (du - 1.0) * (dv - 1.0);
92 }
93
94 Ok(rm2)
95}
96
97pub fn modified_first_zagreb(graph: &Graph) -> IgraphResult<f64> {
113 let n = graph.vcount();
114 if n == 0 {
115 return Ok(0.0);
116 }
117
118 let mut mm1 = 0.0_f64;
119
120 for v in 0..n {
121 let d = graph.degree(v)? as f64;
122 if d > 0.0 {
123 mm1 += 1.0 / (d * d);
124 }
125 }
126
127 Ok(mm1)
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 fn single_edge() -> Graph {
135 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
136 }
137
138 fn path3() -> Graph {
139 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
140 }
141
142 fn path4() -> Graph {
143 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
144 }
145
146 fn k3() -> Graph {
147 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
148 }
149
150 fn k4() -> Graph {
151 Graph::from_edges(
152 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
153 false,
154 Some(4),
155 )
156 .unwrap()
157 }
158
159 fn cycle4() -> Graph {
160 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
161 }
162
163 fn cycle5() -> Graph {
164 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
165 }
166
167 fn star5() -> Graph {
168 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
169 }
170
171 fn paw() -> Graph {
172 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
173 }
174
175 fn diamond() -> Graph {
176 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)], false, Some(4)).unwrap()
177 }
178
179 #[test]
182 fn fi_empty() {
183 let g = Graph::with_vertices(0);
184 assert!((forgotten_index(&g).unwrap()).abs() < 1e-10);
185 }
186
187 #[test]
188 fn fi_single_vertex() {
189 let g = Graph::with_vertices(1);
190 assert!((forgotten_index(&g).unwrap()).abs() < 1e-10);
191 }
192
193 #[test]
194 fn fi_no_edges() {
195 let g = Graph::with_vertices(3);
196 assert!((forgotten_index(&g).unwrap()).abs() < 1e-10);
197 }
198
199 #[test]
200 fn fi_single_edge() {
201 assert!((forgotten_index(&single_edge()).unwrap() - 2.0).abs() < 1e-10);
203 }
204
205 #[test]
206 fn fi_path3() {
207 assert!((forgotten_index(&path3()).unwrap() - 10.0).abs() < 1e-10);
210 }
211
212 #[test]
213 fn fi_path4() {
214 assert!((forgotten_index(&path4()).unwrap() - 18.0).abs() < 1e-10);
217 }
218
219 #[test]
220 fn fi_k3() {
221 assert!((forgotten_index(&k3()).unwrap() - 24.0).abs() < 1e-10);
223 }
224
225 #[test]
226 fn fi_k4() {
227 assert!((forgotten_index(&k4()).unwrap() - 108.0).abs() < 1e-10);
229 }
230
231 #[test]
232 fn fi_cycle4() {
233 assert!((forgotten_index(&cycle4()).unwrap() - 32.0).abs() < 1e-10);
235 }
236
237 #[test]
238 fn fi_cycle5() {
239 assert!((forgotten_index(&cycle5()).unwrap() - 40.0).abs() < 1e-10);
241 }
242
243 #[test]
244 fn fi_star5() {
245 assert!((forgotten_index(&star5()).unwrap() - 68.0).abs() < 1e-10);
247 }
248
249 #[test]
250 fn fi_paw() {
251 assert!((forgotten_index(&paw()).unwrap() - 44.0).abs() < 1e-10);
255 }
256
257 #[test]
258 fn fi_diamond() {
259 assert!((forgotten_index(&diamond()).unwrap() - 70.0).abs() < 1e-10);
263 }
264
265 #[test]
266 fn fi_equals_sum_cubes() {
267 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
269 let f_edge = forgotten_index(g).unwrap();
270 let mut f_vertex = 0.0_f64;
271 for v in 0..g.vcount() {
272 let d = g.degree(v).unwrap() as f64;
273 f_vertex += d * d * d;
274 }
275 assert!((f_edge - f_vertex).abs() < 1e-8);
276 }
277 }
278
279 #[test]
280 fn fi_regular_formula() {
281 for g in &[k3(), k4(), cycle4(), cycle5()] {
283 let m = g.ecount() as f64;
284 let r = g.degree(0).unwrap() as f64;
285 let expected = 2.0 * m * r * r;
286 assert!((forgotten_index(g).unwrap() - expected).abs() < 1e-8);
287 }
288 }
289
290 #[test]
293 fn rm2_empty() {
294 let g = Graph::with_vertices(0);
295 assert!((reduced_second_zagreb(&g).unwrap()).abs() < 1e-10);
296 }
297
298 #[test]
299 fn rm2_single_vertex() {
300 let g = Graph::with_vertices(1);
301 assert!((reduced_second_zagreb(&g).unwrap()).abs() < 1e-10);
302 }
303
304 #[test]
305 fn rm2_no_edges() {
306 let g = Graph::with_vertices(3);
307 assert!((reduced_second_zagreb(&g).unwrap()).abs() < 1e-10);
308 }
309
310 #[test]
311 fn rm2_single_edge() {
312 assert!((reduced_second_zagreb(&single_edge()).unwrap()).abs() < 1e-10);
314 }
315
316 #[test]
317 fn rm2_path3() {
318 assert!((reduced_second_zagreb(&path3()).unwrap()).abs() < 1e-10);
320 }
321
322 #[test]
323 fn rm2_path4() {
324 assert!((reduced_second_zagreb(&path4()).unwrap() - 1.0).abs() < 1e-10);
326 }
327
328 #[test]
329 fn rm2_k3() {
330 assert!((reduced_second_zagreb(&k3()).unwrap() - 3.0).abs() < 1e-10);
332 }
333
334 #[test]
335 fn rm2_k4() {
336 assert!((reduced_second_zagreb(&k4()).unwrap() - 24.0).abs() < 1e-10);
338 }
339
340 #[test]
341 fn rm2_cycle4() {
342 assert!((reduced_second_zagreb(&cycle4()).unwrap() - 4.0).abs() < 1e-10);
344 }
345
346 #[test]
347 fn rm2_cycle5() {
348 assert!((reduced_second_zagreb(&cycle5()).unwrap() - 5.0).abs() < 1e-10);
350 }
351
352 #[test]
353 fn rm2_star5() {
354 assert!((reduced_second_zagreb(&star5()).unwrap()).abs() < 1e-10);
356 }
357
358 #[test]
359 fn rm2_paw() {
360 assert!((reduced_second_zagreb(&paw()).unwrap() - 5.0).abs() < 1e-10);
364 }
365
366 #[test]
367 fn rm2_diamond() {
368 assert!((reduced_second_zagreb(&diamond()).unwrap() - 12.0).abs() < 1e-10);
372 }
373
374 #[test]
375 fn rm2_nonneg_for_connected() {
376 for g in &[
377 single_edge(),
378 path3(),
379 k3(),
380 k4(),
381 star5(),
382 paw(),
383 diamond(),
384 ] {
385 assert!(reduced_second_zagreb(g).unwrap() >= -1e-10);
386 }
387 }
388
389 #[test]
390 fn rm2_regular_formula() {
391 for g in &[k3(), k4(), cycle4(), cycle5()] {
393 let m = g.ecount() as f64;
394 let r = g.degree(0).unwrap() as f64;
395 let expected = m * (r - 1.0) * (r - 1.0);
396 assert!((reduced_second_zagreb(g).unwrap() - expected).abs() < 1e-8);
397 }
398 }
399
400 #[test]
403 fn mm1_empty() {
404 let g = Graph::with_vertices(0);
405 assert!((modified_first_zagreb(&g).unwrap()).abs() < 1e-10);
406 }
407
408 #[test]
409 fn mm1_single_vertex() {
410 let g = Graph::with_vertices(1);
411 assert!((modified_first_zagreb(&g).unwrap()).abs() < 1e-10);
412 }
413
414 #[test]
415 fn mm1_no_edges() {
416 let g = Graph::with_vertices(3);
417 assert!((modified_first_zagreb(&g).unwrap()).abs() < 1e-10);
418 }
419
420 #[test]
421 fn mm1_single_edge() {
422 assert!((modified_first_zagreb(&single_edge()).unwrap() - 2.0).abs() < 1e-10);
424 }
425
426 #[test]
427 fn mm1_path3() {
428 assert!((modified_first_zagreb(&path3()).unwrap() - 2.25).abs() < 1e-10);
430 }
431
432 #[test]
433 fn mm1_k3() {
434 assert!((modified_first_zagreb(&k3()).unwrap() - 0.75).abs() < 1e-10);
436 }
437
438 #[test]
439 fn mm1_k4() {
440 assert!((modified_first_zagreb(&k4()).unwrap() - 4.0 / 9.0).abs() < 1e-10);
442 }
443
444 #[test]
445 fn mm1_cycle4() {
446 assert!((modified_first_zagreb(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
448 }
449
450 #[test]
451 fn mm1_cycle5() {
452 assert!((modified_first_zagreb(&cycle5()).unwrap() - 1.25).abs() < 1e-10);
454 }
455
456 #[test]
457 fn mm1_star5() {
458 let expected = 1.0 / 16.0 + 4.0;
460 assert!((modified_first_zagreb(&star5()).unwrap() - expected).abs() < 1e-10);
461 }
462
463 #[test]
464 fn mm1_paw() {
465 let expected = 0.25 + 0.25 + 1.0 / 9.0 + 1.0;
467 assert!((modified_first_zagreb(&paw()).unwrap() - expected).abs() < 1e-10);
468 }
469
470 #[test]
471 fn mm1_with_isolated() {
472 let g = Graph::from_edges(&[(0, 1)], false, Some(3)).unwrap();
475 assert!((modified_first_zagreb(&g).unwrap() - 2.0).abs() < 1e-10);
476 }
477
478 #[test]
479 fn mm1_regular_formula() {
480 for g in &[k3(), k4(), cycle4(), cycle5()] {
482 let n = f64::from(g.vcount());
483 let r = g.degree(0).unwrap() as f64;
484 let expected = n / (r * r);
485 assert!((modified_first_zagreb(g).unwrap() - expected).abs() < 1e-8);
486 }
487 }
488
489 #[test]
492 fn all_positive_for_connected() {
493 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5()] {
494 assert!(forgotten_index(g).unwrap() > 0.0);
495 assert!(modified_first_zagreb(g).unwrap() > 0.0);
496 }
497 }
498
499 #[test]
500 fn fi_geq_2m() {
501 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5()] {
503 let f = forgotten_index(g).unwrap();
504 assert!(f >= 2.0 * g.ecount() as f64 - 1e-10);
505 }
506 }
507
508 #[test]
509 fn fi_relationship_to_m1_sigma() {
510 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
518 let f = forgotten_index(g).unwrap();
519 let mut sigma = 0.0_f64;
520 for (u, v) in g.edges() {
521 if u == v {
522 continue;
523 }
524 let du = g.degree(u).unwrap() as f64;
525 let dv = g.degree(v).unwrap() as f64;
526 sigma += (du - dv) * (du - dv);
527 }
528 assert!(f >= sigma - 1e-8);
529 }
530 }
531}