Skip to main content

rust_igraph/algorithms/properties/
exponential_vertex_indices.rs

1//! Exponential vertex-degree indices (ALGO-TR-080).
2//!
3//! Vertex-additive indices using the exponential of degree-based
4//! functions, complementing the edge-based `exponential_indices.rs`:
5//!
6//! - **Exponential first Zagreb** `eM₁(G) = Σ_v e^{d(v)²}`
7//! - **Exponential forgotten** `eF(G) = Σ_v e^{d(v)³}`
8//! - **Exponential inverse degree** `eID(G) = Σ_v e^{1/d(v)}`
9//!   (d(v)>0 only)
10//! - **Exponential sum-connectivity** `eSC(G) = Σ_{(u,v)∈E}
11//!   e^{1/√(d(u)+d(v))}` — edge-based variant
12
13#![allow(
14    clippy::cast_possible_truncation,
15    clippy::cast_precision_loss,
16    clippy::many_single_char_names,
17    clippy::needless_range_loop,
18    clippy::too_many_lines
19)]
20
21use crate::core::{Graph, IgraphResult};
22
23/// Compute the exponential first Zagreb index.
24///
25/// `eM₁(G) = Σ_v e^{d(v)²}`
26///
27/// For each vertex, the contribution is `e` raised to the square of
28/// its degree. Isolated vertices contribute `e^0 = 1`.
29///
30/// # Examples
31///
32/// ```
33/// use rust_igraph::{Graph, exponential_first_zagreb};
34///
35/// // K_3: 3 vertices with d=2 → 3·e^4
36/// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, Some(3)).unwrap();
37/// assert!((exponential_first_zagreb(&g).unwrap() - 3.0 * 4.0_f64.exp()).abs() < 1e-10);
38/// ```
39pub fn exponential_first_zagreb(graph: &Graph) -> IgraphResult<f64> {
40    let n = graph.vcount() as usize;
41    let mut result = 0.0_f64;
42
43    for v in 0..n {
44        let d = graph.degree(v as u32)? as f64;
45        result += (d * d).exp();
46    }
47
48    Ok(result)
49}
50
51/// Compute the exponential forgotten index.
52///
53/// `eF(G) = Σ_v e^{d(v)³}`
54///
55/// For each vertex, the contribution is `e` raised to the cube of
56/// its degree. Isolated vertices contribute `e^0 = 1`.
57///
58/// # Examples
59///
60/// ```
61/// use rust_igraph::{Graph, exponential_forgotten};
62///
63/// // K_3: 3 vertices with d=2 → 3·e^8
64/// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, Some(3)).unwrap();
65/// assert!((exponential_forgotten(&g).unwrap() - 3.0 * 8.0_f64.exp()).abs() < 1e-6);
66/// ```
67pub fn exponential_forgotten(graph: &Graph) -> IgraphResult<f64> {
68    let n = graph.vcount() as usize;
69    let mut result = 0.0_f64;
70
71    for v in 0..n {
72        let d = graph.degree(v as u32)? as f64;
73        result += (d * d * d).exp();
74    }
75
76    Ok(result)
77}
78
79/// Compute the exponential inverse degree index.
80///
81/// `eID(G) = Σ_v e^{1/d(v)}` for `d(v) > 0`.
82///
83/// Vertices with degree 0 are skipped. Returns 0.0 for edgeless graphs.
84///
85/// # Examples
86///
87/// ```
88/// use rust_igraph::{Graph, exponential_inverse_degree};
89///
90/// // K_3: 3 vertices with d=2 → 3·e^{0.5}
91/// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, Some(3)).unwrap();
92/// assert!((exponential_inverse_degree(&g).unwrap() - 3.0 * 0.5_f64.exp()).abs() < 1e-10);
93/// ```
94pub fn exponential_inverse_degree(graph: &Graph) -> IgraphResult<f64> {
95    let n = graph.vcount() as usize;
96    let mut result = 0.0_f64;
97
98    for v in 0..n {
99        let d = graph.degree(v as u32)?;
100        if d == 0 {
101            continue;
102        }
103        result += (1.0 / d as f64).exp();
104    }
105
106    Ok(result)
107}
108
109/// Compute the exponential sum-connectivity index.
110///
111/// `eSC(G) = Σ_{(u,v)∈E} e^{1/√(d(u)+d(v))}`
112///
113/// Self-loops are skipped. Returns 0.0 for edgeless graphs.
114///
115/// # Examples
116///
117/// ```
118/// use rust_igraph::{Graph, exponential_sum_connectivity};
119///
120/// // K_3: 3 edges, each d=(2,2) → 3·e^{1/2}
121/// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, Some(3)).unwrap();
122/// assert!((exponential_sum_connectivity(&g).unwrap() - 3.0 * 0.5_f64.exp()).abs() < 1e-10);
123/// ```
124pub fn exponential_sum_connectivity(graph: &Graph) -> IgraphResult<f64> {
125    let mut result = 0.0_f64;
126
127    for (u, v) in graph.edges() {
128        if u == v {
129            continue;
130        }
131        let du = graph.degree(u)? as f64;
132        let dv = graph.degree(v)? as f64;
133        let s = du + dv;
134        if s > 0.0 {
135            result += (1.0 / s.sqrt()).exp();
136        }
137    }
138
139    Ok(result)
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    fn single_edge() -> Graph {
147        Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
148    }
149
150    fn path3() -> Graph {
151        Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
152    }
153
154    fn k3() -> Graph {
155        Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
156    }
157
158    fn k4() -> Graph {
159        Graph::from_edges(
160            &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
161            false,
162            Some(4),
163        )
164        .unwrap()
165    }
166
167    fn cycle4() -> Graph {
168        Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).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    // --- exponential_first_zagreb ---
180
181    #[test]
182    fn efz_empty() {
183        let g = Graph::with_vertices(0);
184        assert!(exponential_first_zagreb(&g).unwrap().abs() < 1e-10);
185    }
186
187    #[test]
188    fn efz_isolated() {
189        // d=0: e^0=1 per vertex → 5
190        let g = Graph::with_vertices(5);
191        assert!((exponential_first_zagreb(&g).unwrap() - 5.0).abs() < 1e-10);
192    }
193
194    #[test]
195    fn efz_k3() {
196        // 3·e^4
197        assert!((exponential_first_zagreb(&k3()).unwrap() - 3.0 * 4.0_f64.exp()).abs() < 1e-10);
198    }
199
200    #[test]
201    fn efz_k4() {
202        // 4·e^9
203        assert!((exponential_first_zagreb(&k4()).unwrap() - 4.0 * 9.0_f64.exp()).abs() < 1e-6);
204    }
205
206    #[test]
207    fn efz_single_edge() {
208        // 2·e^1
209        assert!(
210            (exponential_first_zagreb(&single_edge()).unwrap() - 2.0 * 1.0_f64.exp()).abs() < 1e-10
211        );
212    }
213
214    #[test]
215    fn efz_star5() {
216        // e^16 + 4·e^1
217        let expected = 16.0_f64.exp() + 4.0 * 1.0_f64.exp();
218        assert!((exponential_first_zagreb(&star5()).unwrap() - expected).abs() < 1e-6);
219    }
220
221    #[test]
222    fn efz_cycle4() {
223        // 4·e^4
224        assert!((exponential_first_zagreb(&cycle4()).unwrap() - 4.0 * 4.0_f64.exp()).abs() < 1e-10);
225    }
226
227    // --- exponential_forgotten ---
228
229    #[test]
230    fn ef_empty() {
231        let g = Graph::with_vertices(0);
232        assert!(exponential_forgotten(&g).unwrap().abs() < 1e-10);
233    }
234
235    #[test]
236    fn ef_isolated() {
237        let g = Graph::with_vertices(5);
238        assert!((exponential_forgotten(&g).unwrap() - 5.0).abs() < 1e-10);
239    }
240
241    #[test]
242    fn ef_k3() {
243        // 3·e^8
244        assert!((exponential_forgotten(&k3()).unwrap() - 3.0 * 8.0_f64.exp()).abs() < 1e-6);
245    }
246
247    #[test]
248    fn ef_single_edge() {
249        // 2·e^1
250        assert!(
251            (exponential_forgotten(&single_edge()).unwrap() - 2.0 * 1.0_f64.exp()).abs() < 1e-10
252        );
253    }
254
255    #[test]
256    fn ef_star5() {
257        // e^64 + 4·e^1
258        let expected = 64.0_f64.exp() + 4.0 * 1.0_f64.exp();
259        assert!((exponential_forgotten(&star5()).unwrap() - expected).abs() / expected < 1e-10);
260    }
261
262    // --- exponential_inverse_degree ---
263
264    #[test]
265    fn eid_empty() {
266        let g = Graph::with_vertices(0);
267        assert!(exponential_inverse_degree(&g).unwrap().abs() < 1e-10);
268    }
269
270    #[test]
271    fn eid_isolated() {
272        let g = Graph::with_vertices(5);
273        assert!(exponential_inverse_degree(&g).unwrap().abs() < 1e-10);
274    }
275
276    #[test]
277    fn eid_k3() {
278        // 3·e^{1/2}
279        assert!((exponential_inverse_degree(&k3()).unwrap() - 3.0 * 0.5_f64.exp()).abs() < 1e-10);
280    }
281
282    #[test]
283    fn eid_single_edge() {
284        // 2·e^1
285        assert!(
286            (exponential_inverse_degree(&single_edge()).unwrap() - 2.0 * 1.0_f64.exp()).abs()
287                < 1e-10
288        );
289    }
290
291    #[test]
292    fn eid_star5() {
293        // e^{1/4} + 4·e^1
294        let expected = 0.25_f64.exp() + 4.0 * 1.0_f64.exp();
295        assert!((exponential_inverse_degree(&star5()).unwrap() - expected).abs() < 1e-10);
296    }
297
298    #[test]
299    fn eid_paw() {
300        // d=(2,2,3,1): e^{1/2}+e^{1/2}+e^{1/3}+e^1
301        let expected = 2.0 * 0.5_f64.exp() + (1.0 / 3.0_f64).exp() + 1.0_f64.exp();
302        assert!((exponential_inverse_degree(&paw()).unwrap() - expected).abs() < 1e-10);
303    }
304
305    // --- exponential_sum_connectivity ---
306
307    #[test]
308    fn esc_empty() {
309        let g = Graph::with_vertices(0);
310        assert!(exponential_sum_connectivity(&g).unwrap().abs() < 1e-10);
311    }
312
313    #[test]
314    fn esc_isolated() {
315        let g = Graph::with_vertices(5);
316        assert!(exponential_sum_connectivity(&g).unwrap().abs() < 1e-10);
317    }
318
319    #[test]
320    fn esc_k3() {
321        // 3 edges, each (2,2): 3·e^{1/2}
322        assert!((exponential_sum_connectivity(&k3()).unwrap() - 3.0 * 0.5_f64.exp()).abs() < 1e-10);
323    }
324
325    #[test]
326    fn esc_single_edge() {
327        // 1 edge (1,1): e^{1/√2}
328        let expected = (1.0 / 2.0_f64.sqrt()).exp();
329        assert!((exponential_sum_connectivity(&single_edge()).unwrap() - expected).abs() < 1e-10);
330    }
331
332    #[test]
333    fn esc_k4() {
334        // 6 edges, each (3,3): 6·e^{1/√6}
335        let expected = 6.0 * (1.0 / 6.0_f64.sqrt()).exp();
336        assert!((exponential_sum_connectivity(&k4()).unwrap() - expected).abs() < 1e-10);
337    }
338
339    #[test]
340    fn esc_star5() {
341        // 4 edges (4,1): 4·e^{1/√5}
342        let expected = 4.0 * (1.0 / 5.0_f64.sqrt()).exp();
343        assert!((exponential_sum_connectivity(&star5()).unwrap() - expected).abs() < 1e-10);
344    }
345
346    #[test]
347    fn esc_cycle4() {
348        // 4 edges (2,2): 4·e^{1/2}
349        assert!(
350            (exponential_sum_connectivity(&cycle4()).unwrap() - 4.0 * 0.5_f64.exp()).abs() < 1e-10
351        );
352    }
353
354    #[test]
355    fn esc_paw() {
356        // (0,1)d=(2,2): e^{1/2}
357        // (0,2)d=(2,3): e^{1/√5}
358        // (1,2)d=(2,3): e^{1/√5}
359        // (2,3)d=(3,1): e^{1/2}
360        let expected = 2.0 * 0.5_f64.exp() + 2.0 * (1.0 / 5.0_f64.sqrt()).exp();
361        assert!((exponential_sum_connectivity(&paw()).unwrap() - expected).abs() < 1e-10);
362    }
363
364    // --- cross-consistency ---
365
366    #[test]
367    fn efz_ge_n() {
368        // e^{d²} ≥ 1, so eM₁ ≥ n
369        for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
370            assert!(exponential_first_zagreb(g).unwrap() >= f64::from(g.vcount()) - 1e-10);
371        }
372    }
373
374    #[test]
375    fn esc_positive() {
376        for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
377            assert!(exponential_sum_connectivity(g).unwrap() > 0.0);
378        }
379    }
380}