Skip to main content

rust_igraph/algorithms/properties/
ve_degree_indices.rs

1//! Vertex-edge degree indices (ALGO-TR-068).
2//!
3//! These indices use the **ve-degree** of a vertex v with respect to
4//! the edges incident to it: `d_{ve}(v) = Σ_{e∋v} d_{ev}(e)` where
5//! `d_{ev}(e)` for edge e=(u,w) equals `d(u)+d(w)-2`.
6//!
7//! Equivalently, `d_{ve}(v) = Σ_{u∈N(v)} [d(u)+d(v)-2]`
8//!             `= d(v)·[d(v)-2] + Σ_{u∈N(v)} d(u)`
9//!             `= d(v)² - 2·d(v) + S(v)`
10//! where `S(v) = Σ_{u∈N(v)} d(u)` is the neighbor degree sum.
11//!
12//! - **First ve-degree Zagreb alpha** `M₁^{αve}(G) = Σ_v d_{ve}(v)²`
13//! - **First ve-degree Zagreb beta**  `M₁^{βve}(G) = Σ_{(u,v)∈E} [d_{ve}(u)+d_{ve}(v)]`
14//! - **Second ve-degree Zagreb**      `M₂^{ve}(G) = Σ_{(u,v)∈E} d_{ve}(u)·d_{ve}(v)`
15
16#![allow(
17    clippy::cast_possible_truncation,
18    clippy::cast_precision_loss,
19    clippy::many_single_char_names,
20    clippy::needless_range_loop,
21    clippy::too_many_lines
22)]
23
24use crate::core::{Graph, IgraphResult};
25
26fn ve_degrees(graph: &Graph) -> IgraphResult<Vec<u64>> {
27    let n = graph.vcount() as usize;
28    let mut dve = vec![0_u64; n];
29
30    for v in 0..n {
31        let dv = graph.degree(v as u32)? as u64;
32        let nbs = graph.neighbors(v as u32)?;
33        let mut s_v = 0_u64;
34        for nb in nbs {
35            let du = graph.degree(nb)? as u64;
36            s_v = s_v.saturating_add(du);
37        }
38        dve[v] = dv
39            .saturating_mul(dv)
40            .saturating_add(s_v)
41            .saturating_sub(2 * dv);
42    }
43
44    Ok(dve)
45}
46
47/// Compute the first ve-degree Zagreb alpha index.
48///
49/// `M₁^{αve}(G) = Σ_v d_{ve}(v)²`
50///
51/// # Examples
52///
53/// ```
54/// use rust_igraph::{Graph, first_ve_degree_zagreb_alpha};
55///
56/// // K_3: d=[2,2,2], S(v)=4, d_ve(v)=4+4-4=4 for all
57/// // M₁^αve = 3×16 = 48
58/// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, Some(3)).unwrap();
59/// assert_eq!(first_ve_degree_zagreb_alpha(&g).unwrap(), 48);
60/// ```
61pub fn first_ve_degree_zagreb_alpha(graph: &Graph) -> IgraphResult<u64> {
62    let dve = ve_degrees(graph)?;
63    let mut m1a = 0_u64;
64
65    for &d in &dve {
66        m1a = m1a.saturating_add(d.saturating_mul(d));
67    }
68
69    Ok(m1a)
70}
71
72/// Compute the first ve-degree Zagreb beta index.
73///
74/// `M₁^{βve}(G) = Σ_{(u,v)∈E} [d_{ve}(u) + d_{ve}(v)]`
75///
76/// Self-loops are skipped.
77///
78/// # Examples
79///
80/// ```
81/// use rust_igraph::{Graph, first_ve_degree_zagreb_beta};
82///
83/// // K_3: d_ve=[4,4,4], 3 edges × (4+4) = 24
84/// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, Some(3)).unwrap();
85/// assert_eq!(first_ve_degree_zagreb_beta(&g).unwrap(), 24);
86/// ```
87pub fn first_ve_degree_zagreb_beta(graph: &Graph) -> IgraphResult<u64> {
88    let dve = ve_degrees(graph)?;
89    let mut m1b = 0_u64;
90
91    for (u, v) in graph.edges() {
92        if u == v {
93            continue;
94        }
95        let du = dve[u as usize];
96        let dv = dve[v as usize];
97        m1b = m1b.saturating_add(du.saturating_add(dv));
98    }
99
100    Ok(m1b)
101}
102
103/// Compute the second ve-degree Zagreb index.
104///
105/// `M₂^{ve}(G) = Σ_{(u,v)∈E} d_{ve}(u) · d_{ve}(v)`
106///
107/// Self-loops are skipped.
108///
109/// # Examples
110///
111/// ```
112/// use rust_igraph::{Graph, second_ve_degree_zagreb};
113///
114/// // K_3: d_ve=[4,4,4], 3 edges × 16 = 48
115/// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, Some(3)).unwrap();
116/// assert_eq!(second_ve_degree_zagreb(&g).unwrap(), 48);
117/// ```
118pub fn second_ve_degree_zagreb(graph: &Graph) -> IgraphResult<u64> {
119    let dve = ve_degrees(graph)?;
120    let mut m2 = 0_u64;
121
122    for (u, v) in graph.edges() {
123        if u == v {
124            continue;
125        }
126        let du = dve[u as usize];
127        let dv = dve[v as usize];
128        m2 = m2.saturating_add(du.saturating_mul(dv));
129    }
130
131    Ok(m2)
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    fn single_edge() -> Graph {
139        Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
140    }
141
142    fn path3() -> Graph {
143        Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
144    }
145
146    fn path4() -> Graph {
147        Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
148    }
149
150    fn k3() -> Graph {
151        Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
152    }
153
154    fn k4() -> Graph {
155        Graph::from_edges(
156            &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
157            false,
158            Some(4),
159        )
160        .unwrap()
161    }
162
163    fn cycle4() -> Graph {
164        Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
165    }
166
167    fn cycle5() -> Graph {
168        Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
169    }
170
171    fn star5() -> Graph {
172        Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
173    }
174
175    fn paw() -> Graph {
176        Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
177    }
178
179    fn dve(g: &Graph) -> Vec<u64> {
180        ve_degrees(g).unwrap()
181    }
182
183    // --- ve_degrees ---
184
185    #[test]
186    fn dve_empty() {
187        let g = Graph::with_vertices(0);
188        assert_eq!(dve(&g), Vec::<u64>::new());
189    }
190
191    #[test]
192    fn dve_isolated() {
193        let g = Graph::with_vertices(3);
194        assert_eq!(dve(&g), vec![0, 0, 0]);
195    }
196
197    #[test]
198    fn dve_single_edge() {
199        // d=[1,1], S(0)=1, S(1)=1
200        // d_ve(0)=1+1-2=0, d_ve(1)=0
201        assert_eq!(dve(&single_edge()), vec![0, 0]);
202    }
203
204    #[test]
205    fn dve_path3() {
206        // d=[1,2,1], S(0)=2, S(1)=1+1=2, S(2)=2
207        // d_ve(0)=1+2-2=1, d_ve(1)=4+2-4=2, d_ve(2)=1+2-2=1
208        assert_eq!(dve(&path3()), vec![1, 2, 1]);
209    }
210
211    #[test]
212    fn dve_path4() {
213        // d=[1,2,2,1], S(0)=2, S(1)=1+2=3, S(2)=2+1=3, S(3)=2
214        // d_ve(0)=1+2-2=1, d_ve(1)=4+3-4=3, d_ve(2)=4+3-4=3, d_ve(3)=1+2-2=1
215        assert_eq!(dve(&path4()), vec![1, 3, 3, 1]);
216    }
217
218    #[test]
219    fn dve_k3() {
220        // d=[2,2,2], S(v)=4, d_ve(v)=4+4-4=4 for all
221        assert_eq!(dve(&k3()), vec![4, 4, 4]);
222    }
223
224    #[test]
225    fn dve_k4() {
226        // d=[3,3,3,3], S(v)=9, d_ve(v)=9+9-6=12 for all
227        assert_eq!(dve(&k4()), vec![12, 12, 12, 12]);
228    }
229
230    #[test]
231    fn dve_cycle4() {
232        // d=[2,2,2,2], S(v)=4, d_ve(v)=4+4-4=4 for all
233        assert_eq!(dve(&cycle4()), vec![4, 4, 4, 4]);
234    }
235
236    #[test]
237    fn dve_cycle5() {
238        // d=[2,2,2,2,2], S(v)=4, d_ve(v)=4+4-4=4 for all
239        assert_eq!(dve(&cycle5()), vec![4, 4, 4, 4, 4]);
240    }
241
242    #[test]
243    fn dve_star5() {
244        // d=[4,1,1,1,1], S(0)=4, S(leaf)=4
245        // d_ve(0)=16+4-8=12, d_ve(leaf)=1+4-2=3
246        assert_eq!(dve(&star5()), vec![12, 3, 3, 3, 3]);
247    }
248
249    #[test]
250    fn dve_paw() {
251        // d=[2,2,3,1], S(0)=2+3=5, S(1)=2+3=5, S(2)=2+2+1=5, S(3)=3
252        // d_ve(0)=4+5-4=5, d_ve(1)=4+5-4=5, d_ve(2)=9+5-6=8, d_ve(3)=1+3-2=2
253        assert_eq!(dve(&paw()), vec![5, 5, 8, 2]);
254    }
255
256    // --- first_ve_degree_zagreb_alpha ---
257
258    #[test]
259    fn m1a_empty() {
260        let g = Graph::with_vertices(0);
261        assert_eq!(first_ve_degree_zagreb_alpha(&g).unwrap(), 0);
262    }
263
264    #[test]
265    fn m1a_isolated() {
266        let g = Graph::with_vertices(5);
267        assert_eq!(first_ve_degree_zagreb_alpha(&g).unwrap(), 0);
268    }
269
270    #[test]
271    fn m1a_single_edge() {
272        assert_eq!(first_ve_degree_zagreb_alpha(&single_edge()).unwrap(), 0);
273    }
274
275    #[test]
276    fn m1a_path3() {
277        // d_ve=[1,2,1], M₁α = 1+4+1 = 6
278        assert_eq!(first_ve_degree_zagreb_alpha(&path3()).unwrap(), 6);
279    }
280
281    #[test]
282    fn m1a_path4() {
283        // d_ve=[1,3,3,1], M₁α = 1+9+9+1 = 20
284        assert_eq!(first_ve_degree_zagreb_alpha(&path4()).unwrap(), 20);
285    }
286
287    #[test]
288    fn m1a_k3() {
289        // d_ve=[4,4,4], M₁α = 48
290        assert_eq!(first_ve_degree_zagreb_alpha(&k3()).unwrap(), 48);
291    }
292
293    #[test]
294    fn m1a_k4() {
295        // d_ve=[12,12,12,12], M₁α = 4×144 = 576
296        assert_eq!(first_ve_degree_zagreb_alpha(&k4()).unwrap(), 576);
297    }
298
299    #[test]
300    fn m1a_cycle4() {
301        // d_ve=[4,4,4,4], M₁α = 4×16 = 64
302        assert_eq!(first_ve_degree_zagreb_alpha(&cycle4()).unwrap(), 64);
303    }
304
305    #[test]
306    fn m1a_star5() {
307        // d_ve=[12,3,3,3,3], M₁α = 144+9+9+9+9 = 180
308        assert_eq!(first_ve_degree_zagreb_alpha(&star5()).unwrap(), 180);
309    }
310
311    #[test]
312    fn m1a_paw() {
313        // d_ve=[5,5,8,2], M₁α = 25+25+64+4 = 118
314        assert_eq!(first_ve_degree_zagreb_alpha(&paw()).unwrap(), 118);
315    }
316
317    // --- first_ve_degree_zagreb_beta ---
318
319    #[test]
320    fn m1b_empty() {
321        let g = Graph::with_vertices(0);
322        assert_eq!(first_ve_degree_zagreb_beta(&g).unwrap(), 0);
323    }
324
325    #[test]
326    fn m1b_single_edge() {
327        assert_eq!(first_ve_degree_zagreb_beta(&single_edge()).unwrap(), 0);
328    }
329
330    #[test]
331    fn m1b_path3() {
332        // d_ve=[1,2,1], edges: (0,1):3, (1,2):3 → 6
333        assert_eq!(first_ve_degree_zagreb_beta(&path3()).unwrap(), 6);
334    }
335
336    #[test]
337    fn m1b_path4() {
338        // d_ve=[1,3,3,1], edges: (0,1):4, (1,2):6, (2,3):4 → 14
339        assert_eq!(first_ve_degree_zagreb_beta(&path4()).unwrap(), 14);
340    }
341
342    #[test]
343    fn m1b_k3() {
344        // d_ve=[4,4,4], 3 edges × 8 = 24
345        assert_eq!(first_ve_degree_zagreb_beta(&k3()).unwrap(), 24);
346    }
347
348    #[test]
349    fn m1b_k4() {
350        // d_ve=[12,12,12,12], 6 edges × 24 = 144
351        assert_eq!(first_ve_degree_zagreb_beta(&k4()).unwrap(), 144);
352    }
353
354    #[test]
355    fn m1b_cycle4() {
356        // d_ve=[4,4,4,4], 4 edges × 8 = 32
357        assert_eq!(first_ve_degree_zagreb_beta(&cycle4()).unwrap(), 32);
358    }
359
360    #[test]
361    fn m1b_star5() {
362        // d_ve=[12,3,3,3,3], edges (0,leaf): 12+3=15, 4 edges → 60
363        assert_eq!(first_ve_degree_zagreb_beta(&star5()).unwrap(), 60);
364    }
365
366    #[test]
367    fn m1b_paw() {
368        // d_ve=[5,5,8,2]
369        // (0,1):10, (0,2):13, (1,2):13, (2,3):10 → 46
370        assert_eq!(first_ve_degree_zagreb_beta(&paw()).unwrap(), 46);
371    }
372
373    // --- second_ve_degree_zagreb ---
374
375    #[test]
376    fn m2_empty() {
377        let g = Graph::with_vertices(0);
378        assert_eq!(second_ve_degree_zagreb(&g).unwrap(), 0);
379    }
380
381    #[test]
382    fn m2_single_edge() {
383        assert_eq!(second_ve_degree_zagreb(&single_edge()).unwrap(), 0);
384    }
385
386    #[test]
387    fn m2_path3() {
388        // d_ve=[1,2,1], edges: (0,1):2, (1,2):2 → 4
389        assert_eq!(second_ve_degree_zagreb(&path3()).unwrap(), 4);
390    }
391
392    #[test]
393    fn m2_path4() {
394        // d_ve=[1,3,3,1], edges: (0,1):3, (1,2):9, (2,3):3 → 15
395        assert_eq!(second_ve_degree_zagreb(&path4()).unwrap(), 15);
396    }
397
398    #[test]
399    fn m2_k3() {
400        // d_ve=[4,4,4], 3 edges × 16 = 48
401        assert_eq!(second_ve_degree_zagreb(&k3()).unwrap(), 48);
402    }
403
404    #[test]
405    fn m2_k4() {
406        // d_ve=[12,12,12,12], 6 × 144 = 864
407        assert_eq!(second_ve_degree_zagreb(&k4()).unwrap(), 864);
408    }
409
410    #[test]
411    fn m2_cycle4() {
412        // d_ve=[4,4,4,4], 4 × 16 = 64
413        assert_eq!(second_ve_degree_zagreb(&cycle4()).unwrap(), 64);
414    }
415
416    #[test]
417    fn m2_star5() {
418        // d_ve=[12,3,3,3,3], edges: 4 × (12×3) = 144
419        assert_eq!(second_ve_degree_zagreb(&star5()).unwrap(), 144);
420    }
421
422    #[test]
423    fn m2_paw() {
424        // d_ve=[5,5,8,2]
425        // (0,1):25, (0,2):40, (1,2):40, (2,3):16 → 121
426        assert_eq!(second_ve_degree_zagreb(&paw()).unwrap(), 121);
427    }
428
429    // --- cross-consistency ---
430
431    #[test]
432    fn m1b_regular_formula() {
433        // r-regular: d_ve = r²-2r+r² = 2r²-2r = 2r(r-1)
434        // M₁β = m × 2 × d_ve = m × 4r(r-1)
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 dve_val = 2 * r * (r - 1);
439            let expected = m * 2 * dve_val;
440            assert_eq!(first_ve_degree_zagreb_beta(g).unwrap(), expected);
441        }
442    }
443
444    #[test]
445    fn m2_regular_formula() {
446        // r-regular: M₂ve = m × d_ve²
447        for g in &[k3(), k4(), cycle4(), cycle5()] {
448            let m = g.ecount() as u64;
449            let r = g.degree(0).unwrap() as u64;
450            let dve_val = 2 * r * (r - 1);
451            let expected = m * dve_val * dve_val;
452            assert_eq!(second_ve_degree_zagreb(g).unwrap(), expected);
453        }
454    }
455
456    #[test]
457    fn all_positive_for_nontrivial() {
458        for g in &[path3(), k3(), k4(), cycle4(), star5(), paw()] {
459            assert!(first_ve_degree_zagreb_alpha(g).unwrap() > 0);
460            assert!(first_ve_degree_zagreb_beta(g).unwrap() > 0);
461            assert!(second_ve_degree_zagreb(g).unwrap() > 0);
462        }
463    }
464
465    #[test]
466    fn single_edge_all_zero() {
467        // K_2: both vertices have d_ve=0 → all indices are 0
468        let g = single_edge();
469        assert_eq!(first_ve_degree_zagreb_alpha(&g).unwrap(), 0);
470        assert_eq!(first_ve_degree_zagreb_beta(&g).unwrap(), 0);
471        assert_eq!(second_ve_degree_zagreb(&g).unwrap(), 0);
472    }
473}