Skip to main content

rust_igraph/algorithms/properties/
gourava_index.rs

1//! Gourava indices (ALGO-TR-063).
2//!
3//! - **First Gourava index** `GO₁(G) = Σ_{(u,v)∈E} [d(u)+d(v) + d(u)·d(v)]`
4//!   Introduced by Kulli (2017). Combines sum and product of endpoint degrees.
5//! - **Second Gourava index** `GO₂(G) = Σ_{(u,v)∈E} (d(u)+d(v)) · (d(u)·d(v))`
6//!   Product of degree sum and degree product over edges.
7//! - **First hyper-Gourava index** `HGO₁(G) = Σ_{(u,v)∈E} [d(u)+d(v)+d(u)·d(v)]²`
8//!   Squared version of the first Gourava.
9
10#![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
20/// Compute the first Gourava index.
21///
22/// `GO₁(G) = Σ_{(u,v)∈E} [d(u) + d(v) + d(u)·d(v)]`
23///
24/// Self-loops are skipped.
25///
26/// # Examples
27///
28/// ```
29/// use rust_igraph::{Graph, first_gourava_index};
30///
31/// // K_3: each edge (2+2+4)=8, 3 edges → 24
32/// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, Some(3)).unwrap();
33/// assert_eq!(first_gourava_index(&g).unwrap(), 24);
34/// ```
35pub fn first_gourava_index(graph: &Graph) -> IgraphResult<u64> {
36    let mut go1 = 0_u64;
37
38    for (u, v) in graph.edges() {
39        if u == v {
40            continue;
41        }
42        let du = graph.degree(u)? as u64;
43        let dv = graph.degree(v)? as u64;
44        go1 = go1.saturating_add(du + dv + du.saturating_mul(dv));
45    }
46
47    Ok(go1)
48}
49
50/// Compute the second Gourava index.
51///
52/// `GO₂(G) = Σ_{(u,v)∈E} (d(u)+d(v)) · (d(u)·d(v))`
53///
54/// Self-loops are skipped.
55///
56/// # Examples
57///
58/// ```
59/// use rust_igraph::{Graph, second_gourava_index};
60///
61/// // K_3: each edge (2+2)·(2·2) = 4·4 = 16, 3 edges → 48
62/// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, Some(3)).unwrap();
63/// assert_eq!(second_gourava_index(&g).unwrap(), 48);
64/// ```
65pub fn second_gourava_index(graph: &Graph) -> IgraphResult<u64> {
66    let mut go2 = 0_u64;
67
68    for (u, v) in graph.edges() {
69        if u == v {
70            continue;
71        }
72        let du = graph.degree(u)? as u64;
73        let dv = graph.degree(v)? as u64;
74        let s = du + dv;
75        let p = du.saturating_mul(dv);
76        go2 = go2.saturating_add(s.saturating_mul(p));
77    }
78
79    Ok(go2)
80}
81
82/// Compute the first hyper-Gourava index.
83///
84/// `HGO₁(G) = Σ_{(u,v)∈E} [d(u)+d(v)+d(u)·d(v)]²`
85///
86/// Self-loops are skipped.
87///
88/// # Examples
89///
90/// ```
91/// use rust_igraph::{Graph, first_hyper_gourava_index};
92///
93/// // K_3: each edge [2+2+4]²=64, 3 edges → 192
94/// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, Some(3)).unwrap();
95/// assert_eq!(first_hyper_gourava_index(&g).unwrap(), 192);
96/// ```
97pub fn first_hyper_gourava_index(graph: &Graph) -> IgraphResult<u64> {
98    let mut hgo1 = 0_u64;
99
100    for (u, v) in graph.edges() {
101        if u == v {
102            continue;
103        }
104        let du = graph.degree(u)? as u64;
105        let dv = graph.degree(v)? as u64;
106        let val = du + dv + du.saturating_mul(dv);
107        hgo1 = hgo1.saturating_add(val.saturating_mul(val));
108    }
109
110    Ok(hgo1)
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    fn single_edge() -> Graph {
118        Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
119    }
120
121    fn path3() -> Graph {
122        Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
123    }
124
125    fn path4() -> Graph {
126        Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
127    }
128
129    fn k3() -> Graph {
130        Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
131    }
132
133    fn k4() -> Graph {
134        Graph::from_edges(
135            &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
136            false,
137            Some(4),
138        )
139        .unwrap()
140    }
141
142    fn cycle4() -> Graph {
143        Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
144    }
145
146    fn cycle5() -> Graph {
147        Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
148    }
149
150    fn star5() -> Graph {
151        Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
152    }
153
154    fn paw() -> Graph {
155        Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
156    }
157
158    fn diamond() -> Graph {
159        Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)], false, Some(4)).unwrap()
160    }
161
162    // --- first_gourava_index ---
163
164    #[test]
165    fn go1_empty() {
166        let g = Graph::with_vertices(0);
167        assert_eq!(first_gourava_index(&g).unwrap(), 0);
168    }
169
170    #[test]
171    fn go1_isolated() {
172        let g = Graph::with_vertices(5);
173        assert_eq!(first_gourava_index(&g).unwrap(), 0);
174    }
175
176    #[test]
177    fn go1_single_edge() {
178        // (1+1+1·1) = 3
179        assert_eq!(first_gourava_index(&single_edge()).unwrap(), 3);
180    }
181
182    #[test]
183    fn go1_path3() {
184        // (0,1): 1+2+2=5, (1,2): 2+1+2=5 → 10
185        assert_eq!(first_gourava_index(&path3()).unwrap(), 10);
186    }
187
188    #[test]
189    fn go1_path4() {
190        // (0,1):1+2+2=5, (1,2):2+2+4=8, (2,3):2+1+2=5 → 18
191        assert_eq!(first_gourava_index(&path4()).unwrap(), 18);
192    }
193
194    #[test]
195    fn go1_k3() {
196        // 3 × (2+2+4) = 3×8 = 24
197        assert_eq!(first_gourava_index(&k3()).unwrap(), 24);
198    }
199
200    #[test]
201    fn go1_k4() {
202        // 6 × (3+3+9) = 6×15 = 90
203        assert_eq!(first_gourava_index(&k4()).unwrap(), 90);
204    }
205
206    #[test]
207    fn go1_cycle4() {
208        // 4 × (2+2+4) = 4×8 = 32
209        assert_eq!(first_gourava_index(&cycle4()).unwrap(), 32);
210    }
211
212    #[test]
213    fn go1_cycle5() {
214        // 5 × (2+2+4) = 5×8 = 40
215        assert_eq!(first_gourava_index(&cycle5()).unwrap(), 40);
216    }
217
218    #[test]
219    fn go1_star5() {
220        // 4 × (4+1+4) = 4×9 = 36
221        assert_eq!(first_gourava_index(&star5()).unwrap(), 36);
222    }
223
224    #[test]
225    fn go1_paw() {
226        // degrees [2,2,3,1]
227        // (0,1):2+2+4=8, (0,2):2+3+6=11, (1,2):2+3+6=11, (2,3):3+1+3=7
228        // GO1 = 8+11+11+7 = 37
229        assert_eq!(first_gourava_index(&paw()).unwrap(), 37);
230    }
231
232    #[test]
233    fn go1_diamond() {
234        // degrees [3,3,2,2]
235        // (0,1):3+3+9=15, (0,2):3+2+6=11, (0,3):3+2+6=11, (1,2):3+2+6=11, (1,3):3+2+6=11
236        // GO1 = 15+11+11+11+11 = 59
237        assert_eq!(first_gourava_index(&diamond()).unwrap(), 59);
238    }
239
240    #[test]
241    fn go1_is_m1_plus_m2() {
242        // GO₁ = M₁ + M₂ where M₁ = first Zagreb, M₂ = second Zagreb
243        // M₁ = Σ_{edges} (d(u)+d(v)), M₂ = Σ_{edges} d(u)·d(v)
244        for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
245            let mut m1 = 0_u64;
246            let mut m2 = 0_u64;
247            for (u, v) in g.edges() {
248                if u == v {
249                    continue;
250                }
251                let du = g.degree(u).unwrap() as u64;
252                let dv = g.degree(v).unwrap() as u64;
253                m1 += du + dv;
254                m2 += du * dv;
255            }
256            assert_eq!(first_gourava_index(g).unwrap(), m1 + m2);
257        }
258    }
259
260    #[test]
261    fn go1_regular_formula() {
262        // r-regular: GO1 = m·(2r + r²) = m·r·(r+2)
263        for g in &[k3(), k4(), cycle4(), cycle5()] {
264            let m = g.ecount() as u64;
265            let r = g.degree(0).unwrap() as u64;
266            assert_eq!(first_gourava_index(g).unwrap(), m * r * (r + 2));
267        }
268    }
269
270    // --- second_gourava_index ---
271
272    #[test]
273    fn go2_empty() {
274        let g = Graph::with_vertices(0);
275        assert_eq!(second_gourava_index(&g).unwrap(), 0);
276    }
277
278    #[test]
279    fn go2_isolated() {
280        let g = Graph::with_vertices(5);
281        assert_eq!(second_gourava_index(&g).unwrap(), 0);
282    }
283
284    #[test]
285    fn go2_single_edge() {
286        // (1+1)·(1·1) = 2
287        assert_eq!(second_gourava_index(&single_edge()).unwrap(), 2);
288    }
289
290    #[test]
291    fn go2_path3() {
292        // (0,1):(1+2)·(1·2)=6, (1,2):(2+1)·(2·1)=6 → 12
293        assert_eq!(second_gourava_index(&path3()).unwrap(), 12);
294    }
295
296    #[test]
297    fn go2_path4() {
298        // (0,1):(1+2)·2=6, (1,2):(2+2)·4=16, (2,3):(2+1)·2=6 → 28
299        assert_eq!(second_gourava_index(&path4()).unwrap(), 28);
300    }
301
302    #[test]
303    fn go2_k3() {
304        // 3 × (2+2)·(2·2) = 3×16 = 48
305        assert_eq!(second_gourava_index(&k3()).unwrap(), 48);
306    }
307
308    #[test]
309    fn go2_k4() {
310        // 6 × (3+3)·(3·3) = 6×54 = 324
311        assert_eq!(second_gourava_index(&k4()).unwrap(), 324);
312    }
313
314    #[test]
315    fn go2_cycle4() {
316        // 4 × (2+2)·4 = 4×16 = 64
317        assert_eq!(second_gourava_index(&cycle4()).unwrap(), 64);
318    }
319
320    #[test]
321    fn go2_cycle5() {
322        // 5 × 16 = 80
323        assert_eq!(second_gourava_index(&cycle5()).unwrap(), 80);
324    }
325
326    #[test]
327    fn go2_star5() {
328        // 4 × (4+1)·(4·1) = 4×20 = 80
329        assert_eq!(second_gourava_index(&star5()).unwrap(), 80);
330    }
331
332    #[test]
333    fn go2_paw() {
334        // (0,1):(2+2)·4=16, (0,2):(2+3)·6=30, (1,2):(2+3)·6=30, (2,3):(3+1)·3=12
335        // GO2 = 16+30+30+12 = 88
336        assert_eq!(second_gourava_index(&paw()).unwrap(), 88);
337    }
338
339    #[test]
340    fn go2_diamond() {
341        // (0,1):(3+3)·9=54, (0,2):(3+2)·6=30, (0,3):(3+2)·6=30, (1,2):30, (1,3):30
342        // GO2 = 54+30+30+30+30 = 174
343        assert_eq!(second_gourava_index(&diamond()).unwrap(), 174);
344    }
345
346    #[test]
347    fn go2_regular_formula() {
348        // r-regular: GO2 = m·2r·r² = 2m·r³
349        for g in &[k3(), k4(), cycle4(), cycle5()] {
350            let m = g.ecount() as u64;
351            let r = g.degree(0).unwrap() as u64;
352            assert_eq!(second_gourava_index(g).unwrap(), 2 * m * r * r * r);
353        }
354    }
355
356    // --- first_hyper_gourava_index ---
357
358    #[test]
359    fn hgo1_empty() {
360        let g = Graph::with_vertices(0);
361        assert_eq!(first_hyper_gourava_index(&g).unwrap(), 0);
362    }
363
364    #[test]
365    fn hgo1_isolated() {
366        let g = Graph::with_vertices(5);
367        assert_eq!(first_hyper_gourava_index(&g).unwrap(), 0);
368    }
369
370    #[test]
371    fn hgo1_single_edge() {
372        // [1+1+1]²=9
373        assert_eq!(first_hyper_gourava_index(&single_edge()).unwrap(), 9);
374    }
375
376    #[test]
377    fn hgo1_path3() {
378        // (0,1):[1+2+2]²=25, (1,2):[2+1+2]²=25 → 50
379        assert_eq!(first_hyper_gourava_index(&path3()).unwrap(), 50);
380    }
381
382    #[test]
383    fn hgo1_path4() {
384        // (0,1):25, (1,2):[2+2+4]²=64, (2,3):25 → 114
385        assert_eq!(first_hyper_gourava_index(&path4()).unwrap(), 114);
386    }
387
388    #[test]
389    fn hgo1_k3() {
390        // 3 × [2+2+4]² = 3×64 = 192
391        assert_eq!(first_hyper_gourava_index(&k3()).unwrap(), 192);
392    }
393
394    #[test]
395    fn hgo1_k4() {
396        // 6 × [3+3+9]² = 6×225 = 1350
397        assert_eq!(first_hyper_gourava_index(&k4()).unwrap(), 1350);
398    }
399
400    #[test]
401    fn hgo1_cycle4() {
402        // 4 × 64 = 256
403        assert_eq!(first_hyper_gourava_index(&cycle4()).unwrap(), 256);
404    }
405
406    #[test]
407    fn hgo1_cycle5() {
408        // 5 × 64 = 320
409        assert_eq!(first_hyper_gourava_index(&cycle5()).unwrap(), 320);
410    }
411
412    #[test]
413    fn hgo1_star5() {
414        // 4 × [4+1+4]² = 4×81 = 324
415        assert_eq!(first_hyper_gourava_index(&star5()).unwrap(), 324);
416    }
417
418    #[test]
419    fn hgo1_paw() {
420        // (0,1):8²=64, (0,2):11²=121, (1,2):11²=121, (2,3):7²=49
421        // HGO1 = 64+121+121+49 = 355
422        assert_eq!(first_hyper_gourava_index(&paw()).unwrap(), 355);
423    }
424
425    #[test]
426    fn hgo1_diamond() {
427        // (0,1):15²=225, (0,2):11²=121, (0,3):11²=121, (1,2):121, (1,3):121
428        // HGO1 = 225+121+121+121+121 = 709
429        assert_eq!(first_hyper_gourava_index(&diamond()).unwrap(), 709);
430    }
431
432    #[test]
433    fn hgo1_regular_formula() {
434        // r-regular: HGO1 = m·(2r+r²)² = m·r²·(r+2)²
435        for g in &[k3(), k4(), cycle4(), cycle5()] {
436            let m = g.ecount() as u64;
437            let r = g.degree(0).unwrap() as u64;
438            let val = r * (r + 2);
439            assert_eq!(first_hyper_gourava_index(g).unwrap(), m * val * val);
440        }
441    }
442
443    // --- cross-consistency ---
444
445    #[test]
446    fn go2_geq_go1() {
447        // Not universally true, but check computability
448        for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
449            let _ = first_gourava_index(g).unwrap();
450            let _ = second_gourava_index(g).unwrap();
451        }
452    }
453
454    #[test]
455    fn hgo1_geq_go1_squared_over_m() {
456        // By Cauchy-Schwarz: HGO1 ≥ GO1²/m (for m>0)
457        for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
458            let m = g.ecount() as u64;
459            if m > 0 {
460                let go1 = first_gourava_index(g).unwrap();
461                let hgo1 = first_hyper_gourava_index(g).unwrap();
462                assert!(hgo1 * m >= go1 * go1);
463            }
464        }
465    }
466
467    #[test]
468    fn all_positive_for_graphs_with_edges() {
469        for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
470            assert!(first_gourava_index(g).unwrap() > 0);
471            assert!(second_gourava_index(g).unwrap() > 0);
472            assert!(first_hyper_gourava_index(g).unwrap() > 0);
473        }
474    }
475}