rust_igraph/algorithms/properties/
narumi_katayama.rs1#![allow(
11 clippy::cast_possible_truncation,
12 clippy::cast_precision_loss,
13 clippy::many_single_char_names,
14 clippy::needless_range_loop,
15 clippy::too_many_lines
16)]
17
18use crate::core::{Graph, IgraphResult};
19
20pub fn narumi_katayama_index(graph: &Graph) -> IgraphResult<f64> {
38 let mut nk = 1.0_f64;
39
40 for v in 0..graph.vcount() {
41 let d = graph.degree(v)?;
42 if d > 0 {
43 nk *= d as f64;
44 }
45 }
46
47 Ok(nk)
48}
49
50pub fn first_multiplicative_zagreb(graph: &Graph) -> IgraphResult<f64> {
67 let mut pi1 = 1.0_f64;
68
69 for v in 0..graph.vcount() {
70 let d = graph.degree(v)?;
71 if d > 0 {
72 let df = d as f64;
73 pi1 *= df * df;
74 }
75 }
76
77 Ok(pi1)
78}
79
80pub fn second_multiplicative_zagreb(graph: &Graph) -> IgraphResult<f64> {
97 let mut pi2 = 1.0_f64;
98
99 for (u, v) in graph.edges() {
100 if u == v {
101 continue;
102 }
103 let du = graph.degree(u)? as f64;
104 let dv = graph.degree(v)? as f64;
105 pi2 *= du * dv;
106 }
107
108 Ok(pi2)
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 fn single_edge() -> Graph {
116 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
117 }
118
119 fn path3() -> Graph {
120 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
121 }
122
123 fn path4() -> Graph {
124 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
125 }
126
127 fn path5() -> Graph {
128 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4)], false, Some(5)).unwrap()
129 }
130
131 fn k3() -> Graph {
132 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
133 }
134
135 fn k4() -> Graph {
136 Graph::from_edges(
137 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
138 false,
139 Some(4),
140 )
141 .unwrap()
142 }
143
144 fn cycle4() -> Graph {
145 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
146 }
147
148 fn cycle5() -> Graph {
149 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
150 }
151
152 fn star5() -> Graph {
153 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
154 }
155
156 fn paw() -> Graph {
157 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
158 }
159
160 #[test]
163 fn nk_empty() {
164 let g = Graph::with_vertices(0);
165 assert!((narumi_katayama_index(&g).unwrap() - 1.0).abs() < 1e-10);
166 }
167
168 #[test]
169 fn nk_single_vertex() {
170 let g = Graph::with_vertices(1);
171 assert!((narumi_katayama_index(&g).unwrap() - 1.0).abs() < 1e-10);
172 }
173
174 #[test]
175 fn nk_isolated_vertices() {
176 let g = Graph::with_vertices(5);
177 assert!((narumi_katayama_index(&g).unwrap() - 1.0).abs() < 1e-10);
178 }
179
180 #[test]
181 fn nk_single_edge() {
182 assert!((narumi_katayama_index(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
184 }
185
186 #[test]
187 fn nk_path3() {
188 assert!((narumi_katayama_index(&path3()).unwrap() - 2.0).abs() < 1e-10);
190 }
191
192 #[test]
193 fn nk_path4() {
194 assert!((narumi_katayama_index(&path4()).unwrap() - 4.0).abs() < 1e-10);
196 }
197
198 #[test]
199 fn nk_path5() {
200 assert!((narumi_katayama_index(&path5()).unwrap() - 8.0).abs() < 1e-10);
202 }
203
204 #[test]
205 fn nk_k3() {
206 assert!((narumi_katayama_index(&k3()).unwrap() - 8.0).abs() < 1e-10);
208 }
209
210 #[test]
211 fn nk_k4() {
212 assert!((narumi_katayama_index(&k4()).unwrap() - 81.0).abs() < 1e-10);
214 }
215
216 #[test]
217 fn nk_cycle4() {
218 assert!((narumi_katayama_index(&cycle4()).unwrap() - 16.0).abs() < 1e-10);
220 }
221
222 #[test]
223 fn nk_cycle5() {
224 assert!((narumi_katayama_index(&cycle5()).unwrap() - 32.0).abs() < 1e-10);
226 }
227
228 #[test]
229 fn nk_star5() {
230 assert!((narumi_katayama_index(&star5()).unwrap() - 4.0).abs() < 1e-10);
232 }
233
234 #[test]
235 fn nk_paw() {
236 assert!((narumi_katayama_index(&paw()).unwrap() - 12.0).abs() < 1e-10);
238 }
239
240 #[test]
241 fn nk_regular_is_r_pow_n() {
242 for g in &[k3(), k4(), cycle4(), cycle5()] {
244 let n = f64::from(g.vcount());
245 let r = g.degree(0).unwrap() as f64;
246 let expected = r.powf(n);
247 assert!((narumi_katayama_index(g).unwrap() - expected).abs() < 1e-6);
248 }
249 }
250
251 #[test]
252 fn nk_with_isolated() {
253 let g = Graph::from_edges(&[(0, 1)], false, Some(3)).unwrap();
255 assert!((narumi_katayama_index(&g).unwrap() - 1.0).abs() < 1e-10);
257 }
258
259 #[test]
262 fn pi1_empty() {
263 let g = Graph::with_vertices(0);
264 assert!((first_multiplicative_zagreb(&g).unwrap() - 1.0).abs() < 1e-10);
265 }
266
267 #[test]
268 fn pi1_single_vertex() {
269 let g = Graph::with_vertices(1);
270 assert!((first_multiplicative_zagreb(&g).unwrap() - 1.0).abs() < 1e-10);
271 }
272
273 #[test]
274 fn pi1_single_edge() {
275 assert!((first_multiplicative_zagreb(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
277 }
278
279 #[test]
280 fn pi1_path3() {
281 assert!((first_multiplicative_zagreb(&path3()).unwrap() - 4.0).abs() < 1e-10);
283 }
284
285 #[test]
286 fn pi1_path4() {
287 assert!((first_multiplicative_zagreb(&path4()).unwrap() - 16.0).abs() < 1e-10);
289 }
290
291 #[test]
292 fn pi1_k3() {
293 assert!((first_multiplicative_zagreb(&k3()).unwrap() - 64.0).abs() < 1e-10);
295 }
296
297 #[test]
298 fn pi1_k4() {
299 assert!((first_multiplicative_zagreb(&k4()).unwrap() - 6561.0).abs() < 1e-10);
301 }
302
303 #[test]
304 fn pi1_cycle4() {
305 assert!((first_multiplicative_zagreb(&cycle4()).unwrap() - 256.0).abs() < 1e-10);
307 }
308
309 #[test]
310 fn pi1_star5() {
311 assert!((first_multiplicative_zagreb(&star5()).unwrap() - 16.0).abs() < 1e-10);
313 }
314
315 #[test]
316 fn pi1_paw() {
317 assert!((first_multiplicative_zagreb(&paw()).unwrap() - 144.0).abs() < 1e-10);
319 }
320
321 #[test]
322 fn pi1_is_nk_squared() {
323 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
325 let nk = narumi_katayama_index(g).unwrap();
326 let pi1 = first_multiplicative_zagreb(g).unwrap();
327 assert!((pi1 - nk * nk).abs() < 1e-6);
328 }
329 }
330
331 #[test]
332 fn pi1_regular_formula() {
333 for g in &[k3(), k4(), cycle4(), cycle5()] {
335 let n = f64::from(g.vcount());
336 let r = g.degree(0).unwrap() as f64;
337 let expected = r.powf(2.0 * n);
338 assert!((first_multiplicative_zagreb(g).unwrap() - expected).abs() < 1e-4);
339 }
340 }
341
342 #[test]
345 fn pi2_empty() {
346 let g = Graph::with_vertices(0);
347 assert!((second_multiplicative_zagreb(&g).unwrap() - 1.0).abs() < 1e-10);
348 }
349
350 #[test]
351 fn pi2_single_vertex() {
352 let g = Graph::with_vertices(1);
353 assert!((second_multiplicative_zagreb(&g).unwrap() - 1.0).abs() < 1e-10);
354 }
355
356 #[test]
357 fn pi2_single_edge() {
358 assert!((second_multiplicative_zagreb(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
360 }
361
362 #[test]
363 fn pi2_path3() {
364 assert!((second_multiplicative_zagreb(&path3()).unwrap() - 4.0).abs() < 1e-10);
366 }
367
368 #[test]
369 fn pi2_path4() {
370 assert!((second_multiplicative_zagreb(&path4()).unwrap() - 16.0).abs() < 1e-10);
372 }
373
374 #[test]
375 fn pi2_k3() {
376 assert!((second_multiplicative_zagreb(&k3()).unwrap() - 64.0).abs() < 1e-10);
378 }
379
380 #[test]
381 fn pi2_k4() {
382 assert!((second_multiplicative_zagreb(&k4()).unwrap() - 531_441.0).abs() < 1e-6);
384 }
385
386 #[test]
387 fn pi2_cycle4() {
388 assert!((second_multiplicative_zagreb(&cycle4()).unwrap() - 256.0).abs() < 1e-10);
390 }
391
392 #[test]
393 fn pi2_cycle5() {
394 assert!((second_multiplicative_zagreb(&cycle5()).unwrap() - 1024.0).abs() < 1e-10);
396 }
397
398 #[test]
399 fn pi2_star5() {
400 assert!((second_multiplicative_zagreb(&star5()).unwrap() - 256.0).abs() < 1e-10);
402 }
403
404 #[test]
405 fn pi2_paw() {
406 assert!((second_multiplicative_zagreb(&paw()).unwrap() - 432.0).abs() < 1e-10);
409 }
410
411 #[test]
412 fn pi2_regular_formula() {
413 for g in &[k3(), k4(), cycle4(), cycle5()] {
415 let m = g.ecount() as f64;
416 let r = g.degree(0).unwrap() as f64;
417 let expected = r.powf(2.0 * m);
418 assert!((second_multiplicative_zagreb(g).unwrap() - expected).abs() < 1e-4);
419 }
420 }
421
422 #[test]
425 fn all_positive() {
426 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
427 assert!(narumi_katayama_index(g).unwrap() > 0.0);
428 assert!(first_multiplicative_zagreb(g).unwrap() > 0.0);
429 assert!(second_multiplicative_zagreb(g).unwrap() > 0.0);
430 }
431 }
432
433 #[test]
434 fn nk_leq_pi1() {
435 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
437 let nk = narumi_katayama_index(g).unwrap();
438 let pi1 = first_multiplicative_zagreb(g).unwrap();
439 assert!(nk <= pi1 + 1e-8);
440 }
441 }
442
443 #[test]
444 fn log_pi2_equals_sum_log_deg_products() {
445 for g in &[path3(), k3(), cycle4(), star5(), paw()] {
447 let pi2 = second_multiplicative_zagreb(g).unwrap();
448 let mut log_sum = 0.0_f64;
449 for (u, v) in g.edges() {
450 if u == v {
451 continue;
452 }
453 let du = g.degree(u).unwrap() as f64;
454 let dv = g.degree(v).unwrap() as f64;
455 log_sum += (du * dv).ln();
456 }
457 assert!((pi2.ln() - log_sum).abs() < 1e-8);
458 }
459 }
460}