rust_igraph/algorithms/properties/
nirmala_index.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 nirmala_index(graph: &Graph) -> IgraphResult<f64> {
36 let mut n_idx = 0.0_f64;
37
38 for (u, v) in graph.edges() {
39 if u == v {
40 continue;
41 }
42 let du = graph.degree(u)? as f64;
43 let dv = graph.degree(v)? as f64;
44 n_idx += (du + dv).sqrt();
45 }
46
47 Ok(n_idx)
48}
49
50pub fn first_inverse_nirmala(graph: &Graph) -> IgraphResult<f64> {
66 let mut in1 = 0.0_f64;
67
68 for (u, v) in graph.edges() {
69 if u == v {
70 continue;
71 }
72 let du = graph.degree(u)? as f64;
73 let dv = graph.degree(v)? as f64;
74 let s = du + dv;
75 if s > 0.0 {
76 in1 += 1.0 / s.sqrt();
77 }
78 }
79
80 Ok(in1)
81}
82
83pub fn second_inverse_nirmala(graph: &Graph) -> IgraphResult<f64> {
100 let mut in2 = 0.0_f64;
101
102 for (u, v) in graph.edges() {
103 if u == v {
104 continue;
105 }
106 let du = graph.degree(u)? as f64;
107 let dv = graph.degree(v)? as f64;
108 let p = du * dv;
109 if p > 0.0 {
110 in2 += 1.0 / p.sqrt();
111 }
112 }
113
114 Ok(in2)
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 fn single_edge() -> Graph {
122 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
123 }
124
125 fn path3() -> Graph {
126 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
127 }
128
129 fn path4() -> Graph {
130 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
131 }
132
133 fn k3() -> Graph {
134 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
135 }
136
137 fn k4() -> Graph {
138 Graph::from_edges(
139 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
140 false,
141 Some(4),
142 )
143 .unwrap()
144 }
145
146 fn cycle4() -> Graph {
147 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
148 }
149
150 fn cycle5() -> Graph {
151 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
152 }
153
154 fn star5() -> Graph {
155 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
156 }
157
158 fn paw() -> Graph {
159 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
160 }
161
162 fn diamond() -> Graph {
163 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)], false, Some(4)).unwrap()
164 }
165
166 #[test]
169 fn nirmala_empty() {
170 let g = Graph::with_vertices(0);
171 assert!((nirmala_index(&g).unwrap()).abs() < 1e-10);
172 }
173
174 #[test]
175 fn nirmala_isolated() {
176 let g = Graph::with_vertices(5);
177 assert!((nirmala_index(&g).unwrap()).abs() < 1e-10);
178 }
179
180 #[test]
181 fn nirmala_single_edge() {
182 let expected = 2.0_f64.sqrt();
184 assert!((nirmala_index(&single_edge()).unwrap() - expected).abs() < 1e-10);
185 }
186
187 #[test]
188 fn nirmala_path3() {
189 let expected = 2.0 * 3.0_f64.sqrt();
191 assert!((nirmala_index(&path3()).unwrap() - expected).abs() < 1e-10);
192 }
193
194 #[test]
195 fn nirmala_path4() {
196 let expected = 2.0 * 3.0_f64.sqrt() + 2.0;
198 assert!((nirmala_index(&path4()).unwrap() - expected).abs() < 1e-10);
199 }
200
201 #[test]
202 fn nirmala_k3() {
203 assert!((nirmala_index(&k3()).unwrap() - 6.0).abs() < 1e-10);
205 }
206
207 #[test]
208 fn nirmala_k4() {
209 let expected = 6.0 * 6.0_f64.sqrt();
211 assert!((nirmala_index(&k4()).unwrap() - expected).abs() < 1e-10);
212 }
213
214 #[test]
215 fn nirmala_cycle4() {
216 assert!((nirmala_index(&cycle4()).unwrap() - 8.0).abs() < 1e-10);
218 }
219
220 #[test]
221 fn nirmala_cycle5() {
222 assert!((nirmala_index(&cycle5()).unwrap() - 10.0).abs() < 1e-10);
224 }
225
226 #[test]
227 fn nirmala_star5() {
228 let expected = 4.0 * 5.0_f64.sqrt();
230 assert!((nirmala_index(&star5()).unwrap() - expected).abs() < 1e-10);
231 }
232
233 #[test]
234 fn nirmala_paw() {
235 let expected = 4.0 + 2.0 * 5.0_f64.sqrt();
237 assert!((nirmala_index(&paw()).unwrap() - expected).abs() < 1e-10);
238 }
239
240 #[test]
241 fn nirmala_diamond() {
242 let expected = 6.0_f64.sqrt() + 4.0 * 5.0_f64.sqrt();
244 assert!((nirmala_index(&diamond()).unwrap() - expected).abs() < 1e-10);
245 }
246
247 #[test]
248 fn nirmala_regular_formula() {
249 for g in &[k3(), k4(), cycle4(), cycle5()] {
251 let m = g.ecount() as f64;
252 let r = g.degree(0).unwrap() as f64;
253 let expected = m * (2.0 * r).sqrt();
254 assert!((nirmala_index(g).unwrap() - expected).abs() < 1e-8);
255 }
256 }
257
258 #[test]
261 fn in1_empty() {
262 let g = Graph::with_vertices(0);
263 assert!((first_inverse_nirmala(&g).unwrap()).abs() < 1e-10);
264 }
265
266 #[test]
267 fn in1_single_edge() {
268 let expected = 1.0 / 2.0_f64.sqrt();
270 assert!((first_inverse_nirmala(&single_edge()).unwrap() - expected).abs() < 1e-10);
271 }
272
273 #[test]
274 fn in1_path3() {
275 let expected = 2.0 / 3.0_f64.sqrt();
277 assert!((first_inverse_nirmala(&path3()).unwrap() - expected).abs() < 1e-10);
278 }
279
280 #[test]
281 fn in1_k3() {
282 assert!((first_inverse_nirmala(&k3()).unwrap() - 1.5).abs() < 1e-10);
284 }
285
286 #[test]
287 fn in1_k4() {
288 let expected = 6.0_f64.sqrt();
290 assert!((first_inverse_nirmala(&k4()).unwrap() - expected).abs() < 1e-10);
291 }
292
293 #[test]
294 fn in1_cycle4() {
295 assert!((first_inverse_nirmala(&cycle4()).unwrap() - 2.0).abs() < 1e-10);
297 }
298
299 #[test]
300 fn in1_cycle5() {
301 assert!((first_inverse_nirmala(&cycle5()).unwrap() - 2.5).abs() < 1e-10);
303 }
304
305 #[test]
306 fn in1_star5() {
307 let expected = 4.0 / 5.0_f64.sqrt();
309 assert!((first_inverse_nirmala(&star5()).unwrap() - expected).abs() < 1e-10);
310 }
311
312 #[test]
313 fn in1_paw() {
314 let expected = 1.0 + 2.0 / 5.0_f64.sqrt();
316 assert!((first_inverse_nirmala(&paw()).unwrap() - expected).abs() < 1e-10);
317 }
318
319 #[test]
320 fn in1_regular_formula() {
321 for g in &[k3(), k4(), cycle4(), cycle5()] {
323 let m = g.ecount() as f64;
324 let r = g.degree(0).unwrap() as f64;
325 let expected = m / (2.0 * r).sqrt();
326 assert!((first_inverse_nirmala(g).unwrap() - expected).abs() < 1e-8);
327 }
328 }
329
330 #[test]
333 fn in2_empty() {
334 let g = Graph::with_vertices(0);
335 assert!((second_inverse_nirmala(&g).unwrap()).abs() < 1e-10);
336 }
337
338 #[test]
339 fn in2_single_edge() {
340 assert!((second_inverse_nirmala(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
342 }
343
344 #[test]
345 fn in2_path3() {
346 let expected = 2.0_f64.sqrt();
348 assert!((second_inverse_nirmala(&path3()).unwrap() - expected).abs() < 1e-10);
349 }
350
351 #[test]
352 fn in2_k3() {
353 assert!((second_inverse_nirmala(&k3()).unwrap() - 1.5).abs() < 1e-10);
355 }
356
357 #[test]
358 fn in2_k4() {
359 assert!((second_inverse_nirmala(&k4()).unwrap() - 2.0).abs() < 1e-10);
361 }
362
363 #[test]
364 fn in2_cycle4() {
365 assert!((second_inverse_nirmala(&cycle4()).unwrap() - 2.0).abs() < 1e-10);
367 }
368
369 #[test]
370 fn in2_cycle5() {
371 assert!((second_inverse_nirmala(&cycle5()).unwrap() - 2.5).abs() < 1e-10);
373 }
374
375 #[test]
376 fn in2_star5() {
377 assert!((second_inverse_nirmala(&star5()).unwrap() - 2.0).abs() < 1e-10);
379 }
380
381 #[test]
382 fn in2_paw() {
383 let expected = 0.5 + 2.0 / 6.0_f64.sqrt() + 1.0 / 3.0_f64.sqrt();
385 assert!((second_inverse_nirmala(&paw()).unwrap() - expected).abs() < 1e-10);
386 }
387
388 #[test]
389 fn in2_equals_randic() {
390 for g in &[k3(), k4(), cycle4(), cycle5()] {
394 let m = g.ecount() as f64;
395 let r = g.degree(0).unwrap() as f64;
396 assert!((second_inverse_nirmala(g).unwrap() - m / r).abs() < 1e-8);
397 }
398 }
399
400 #[test]
401 fn in2_regular_formula() {
402 for g in &[k3(), k4(), cycle4(), cycle5()] {
404 let m = g.ecount() as f64;
405 let r = g.degree(0).unwrap() as f64;
406 let expected = m / r;
407 assert!((second_inverse_nirmala(g).unwrap() - expected).abs() < 1e-8);
408 }
409 }
410
411 #[test]
414 fn nirmala_times_in1_geq_m() {
415 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
420 let n_val = nirmala_index(g).unwrap();
421 let in1_val = first_inverse_nirmala(g).unwrap();
422 let m = g.ecount() as f64;
423 assert!(n_val * in1_val >= m * m - 1e-8);
424 }
425 }
426
427 #[test]
428 fn all_positive() {
429 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
430 assert!(nirmala_index(g).unwrap() > 0.0);
431 assert!(first_inverse_nirmala(g).unwrap() > 0.0);
432 assert!(second_inverse_nirmala(g).unwrap() > 0.0);
433 }
434 }
435}