1#![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};
23
24pub fn general_randic_index(graph: &Graph, alpha: f64) -> IgraphResult<f64> {
43 let n = graph.vcount();
44 if n == 0 {
45 return Ok(0.0);
46 }
47
48 let mut r = 0.0_f64;
49
50 for (u, v) in graph.edges() {
51 if u == v {
52 continue;
53 }
54 let du = graph.degree(u)? as f64;
55 let dv = graph.degree(v)? as f64;
56 let prod = du * dv;
57 if prod <= 0.0 {
58 continue;
59 }
60 r += prod.powf(alpha);
61 }
62
63 Ok(r)
64}
65
66pub fn general_sum_connectivity_index(graph: &Graph, alpha: f64) -> IgraphResult<f64> {
85 let n = graph.vcount();
86 if n == 0 {
87 return Ok(0.0);
88 }
89
90 let mut chi = 0.0_f64;
91
92 for (u, v) in graph.edges() {
93 if u == v {
94 continue;
95 }
96 let du = graph.degree(u)? as f64;
97 let dv = graph.degree(v)? as f64;
98 let sum_d = du + dv;
99 if sum_d <= 0.0 {
100 continue;
101 }
102 chi += sum_d.powf(alpha);
103 }
104
105 Ok(chi)
106}
107
108pub fn reciprocal_randic_index(graph: &Graph) -> IgraphResult<f64> {
124 let n = graph.vcount();
125 if n == 0 {
126 return Ok(0.0);
127 }
128
129 let mut rr = 0.0_f64;
130
131 for (u, v) in graph.edges() {
132 if u == v {
133 continue;
134 }
135 let du = graph.degree(u)? as f64;
136 let dv = graph.degree(v)? as f64;
137 let prod = du * dv;
138 if prod <= 0.0 {
139 continue;
140 }
141 rr += prod.sqrt();
142 }
143
144 Ok(rr)
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 fn single_edge() -> Graph {
152 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
153 }
154
155 fn path3() -> Graph {
156 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
157 }
158
159 fn path4() -> Graph {
160 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
161 }
162
163 fn k3() -> Graph {
164 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
165 }
166
167 fn k4() -> Graph {
168 Graph::from_edges(
169 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
170 false,
171 Some(4),
172 )
173 .unwrap()
174 }
175
176 fn cycle4() -> Graph {
177 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
178 }
179
180 fn cycle5() -> Graph {
181 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
182 }
183
184 fn star5() -> Graph {
185 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
186 }
187
188 fn paw() -> Graph {
189 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
190 }
191
192 #[test]
195 fn gr_empty() {
196 let g = Graph::with_vertices(0);
197 assert!((general_randic_index(&g, -0.5).unwrap()).abs() < 1e-10);
198 }
199
200 #[test]
201 fn gr_single_vertex() {
202 let g = Graph::with_vertices(1);
203 assert!((general_randic_index(&g, 1.0).unwrap()).abs() < 1e-10);
204 }
205
206 #[test]
207 fn gr_single_edge() {
208 assert!((general_randic_index(&single_edge(), 1.0).unwrap() - 1.0).abs() < 1e-10);
210 }
211
212 #[test]
213 fn gr_alpha_neg_half_is_randic() {
214 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
216 let gr = general_randic_index(g, -0.5).unwrap();
217 let mut expected = 0.0_f64;
218 for (u, v) in g.edges() {
219 if u == v {
220 continue;
221 }
222 let du = g.degree(u).unwrap() as f64;
223 let dv = g.degree(v).unwrap() as f64;
224 expected += 1.0 / (du * dv).sqrt();
225 }
226 assert!((gr - expected).abs() < 1e-8);
227 }
228 }
229
230 #[test]
231 fn gr_alpha_1_is_second_zagreb() {
232 for g in &[single_edge(), path3(), k3(), k4(), star5()] {
234 let gr = general_randic_index(g, 1.0).unwrap();
235 let mut m2 = 0.0_f64;
236 for (u, v) in g.edges() {
237 if u == v {
238 continue;
239 }
240 let du = g.degree(u).unwrap() as f64;
241 let dv = g.degree(v).unwrap() as f64;
242 m2 += du * dv;
243 }
244 assert!((gr - m2).abs() < 1e-8);
245 }
246 }
247
248 #[test]
249 fn gr_alpha_0_equals_m() {
250 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5()] {
252 let gr = general_randic_index(g, 0.0).unwrap();
253 assert!((gr - g.ecount() as f64).abs() < 1e-10);
254 }
255 }
256
257 #[test]
258 fn gr_k3() {
259 assert!((general_randic_index(&k3(), 1.0).unwrap() - 12.0).abs() < 1e-10);
261 }
262
263 #[test]
264 fn gr_k4() {
265 assert!((general_randic_index(&k4(), 1.0).unwrap() - 54.0).abs() < 1e-10);
267 }
268
269 #[test]
270 fn gr_star5_alpha1() {
271 assert!((general_randic_index(&star5(), 1.0).unwrap() - 16.0).abs() < 1e-10);
273 }
274
275 #[test]
276 fn gr_path3_alpha2() {
277 assert!((general_randic_index(&path3(), 2.0).unwrap() - 8.0).abs() < 1e-10);
279 }
280
281 #[test]
282 fn gr_regular_formula() {
283 for g in &[k3(), k4(), cycle4(), cycle5()] {
285 let m = g.ecount() as f64;
286 let r = g.degree(0).unwrap() as f64;
287 for &alpha in &[-0.5_f64, 0.0, 0.5, 1.0, 2.0] {
288 let expected = m * r.powf(2.0 * alpha);
289 let actual = general_randic_index(g, alpha).unwrap();
290 assert!(
291 (actual - expected).abs() < 1e-6,
292 "alpha={alpha}, expected={expected}, got={actual}"
293 );
294 }
295 }
296 }
297
298 #[test]
301 fn gs_empty() {
302 let g = Graph::with_vertices(0);
303 assert!((general_sum_connectivity_index(&g, -0.5).unwrap()).abs() < 1e-10);
304 }
305
306 #[test]
307 fn gs_alpha_neg_half_is_sci() {
308 for g in &[single_edge(), path3(), k3(), star5()] {
310 let gs = general_sum_connectivity_index(g, -0.5).unwrap();
311 let mut expected = 0.0_f64;
312 for (u, v) in g.edges() {
313 if u == v {
314 continue;
315 }
316 let du = g.degree(u).unwrap() as f64;
317 let dv = g.degree(v).unwrap() as f64;
318 expected += 1.0 / (du + dv).sqrt();
319 }
320 assert!((gs - expected).abs() < 1e-8);
321 }
322 }
323
324 #[test]
325 fn gs_alpha_1_is_first_zagreb() {
326 for g in &[single_edge(), path3(), k3(), k4(), star5()] {
328 let gs = general_sum_connectivity_index(g, 1.0).unwrap();
329 let mut m1 = 0.0_f64;
330 for (u, v) in g.edges() {
331 if u == v {
332 continue;
333 }
334 let du = g.degree(u).unwrap() as f64;
335 let dv = g.degree(v).unwrap() as f64;
336 m1 += du + dv;
337 }
338 assert!((gs - m1).abs() < 1e-8);
339 }
340 }
341
342 #[test]
343 fn gs_alpha_0_equals_m() {
344 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5()] {
345 let gs = general_sum_connectivity_index(g, 0.0).unwrap();
346 assert!((gs - g.ecount() as f64).abs() < 1e-10);
347 }
348 }
349
350 #[test]
351 fn gs_k3_alpha1() {
352 assert!((general_sum_connectivity_index(&k3(), 1.0).unwrap() - 12.0).abs() < 1e-10);
354 }
355
356 #[test]
357 fn gs_k4_alpha1() {
358 assert!((general_sum_connectivity_index(&k4(), 1.0).unwrap() - 36.0).abs() < 1e-10);
360 }
361
362 #[test]
363 fn gs_star5_alpha1() {
364 assert!((general_sum_connectivity_index(&star5(), 1.0).unwrap() - 20.0).abs() < 1e-10);
366 }
367
368 #[test]
369 fn gs_regular_formula() {
370 for g in &[k3(), k4(), cycle4(), cycle5()] {
372 let m = g.ecount() as f64;
373 let r = g.degree(0).unwrap() as f64;
374 for &alpha in &[-0.5_f64, 0.0, 0.5, 1.0, 2.0] {
375 let expected = m * (2.0 * r).powf(alpha);
376 let actual = general_sum_connectivity_index(g, alpha).unwrap();
377 assert!((actual - expected).abs() < 1e-6);
378 }
379 }
380 }
381
382 #[test]
385 fn rr_empty() {
386 let g = Graph::with_vertices(0);
387 assert!((reciprocal_randic_index(&g).unwrap()).abs() < 1e-10);
388 }
389
390 #[test]
391 fn rr_single_edge() {
392 assert!((reciprocal_randic_index(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
394 }
395
396 #[test]
397 fn rr_path3() {
398 let expected = 2.0 * 2.0_f64.sqrt();
400 assert!((reciprocal_randic_index(&path3()).unwrap() - expected).abs() < 1e-10);
401 }
402
403 #[test]
404 fn rr_k3() {
405 assert!((reciprocal_randic_index(&k3()).unwrap() - 6.0).abs() < 1e-10);
407 }
408
409 #[test]
410 fn rr_k4() {
411 assert!((reciprocal_randic_index(&k4()).unwrap() - 18.0).abs() < 1e-10);
413 }
414
415 #[test]
416 fn rr_star5() {
417 assert!((reciprocal_randic_index(&star5()).unwrap() - 8.0).abs() < 1e-10);
419 }
420
421 #[test]
422 fn rr_cycle4() {
423 assert!((reciprocal_randic_index(&cycle4()).unwrap() - 8.0).abs() < 1e-10);
425 }
426
427 #[test]
428 fn rr_equals_gr_half() {
429 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
431 let rr = reciprocal_randic_index(g).unwrap();
432 let gr = general_randic_index(g, 0.5).unwrap();
433 assert!((rr - gr).abs() < 1e-8);
434 }
435 }
436
437 #[test]
438 fn rr_regular_formula() {
439 for g in &[k3(), k4(), cycle4(), cycle5()] {
441 let m = g.ecount() as f64;
442 let r = g.degree(0).unwrap() as f64;
443 assert!((reciprocal_randic_index(g).unwrap() - m * r).abs() < 1e-8);
444 }
445 }
446
447 #[test]
450 fn all_positive_for_connected() {
451 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5()] {
452 assert!(general_randic_index(g, 1.0).unwrap() > 0.0);
453 assert!(general_sum_connectivity_index(g, 1.0).unwrap() > 0.0);
454 assert!(reciprocal_randic_index(g).unwrap() > 0.0);
455 }
456 }
457
458 #[test]
459 fn gr_geq_rr_for_alpha_geq_half() {
460 for g in &[single_edge(), path3(), k3(), k4(), star5()] {
462 let r1 = general_randic_index(g, 1.0).unwrap();
463 let rr = reciprocal_randic_index(g).unwrap();
464 assert!(r1 >= rr - 1e-8);
465 }
466 }
467
468 #[test]
469 fn paw_alpha1() {
470 assert!((general_randic_index(&paw(), 1.0).unwrap() - 19.0).abs() < 1e-10);
473 }
474
475 #[test]
476 fn path4_alpha1() {
477 assert!((general_randic_index(&path4(), 1.0).unwrap() - 8.0).abs() < 1e-10);
480 }
481}