Skip to main content

rust_igraph/algorithms/properties/
degree_distribution.rs

1//! Degree distribution histogram (ALGO-PR-052).
2//!
3//! For each degree value `d` from 0 to `max_degree`, counts how many
4//! vertices have exactly that degree. Supports directed graphs via
5//! in-degree / out-degree / total-degree selection.
6
7use crate::algorithms::properties::degree::DegreeMode;
8use crate::core::{Graph, IgraphResult};
9
10/// Compute the degree distribution histogram of a graph.
11///
12/// Returns a `Vec<u32>` of length `max_degree + 1` where entry `i`
13/// is the number of vertices with degree `i`.
14///
15/// - `mode`: which degree to use (`Out`, `In`, or `All`). For
16///   undirected graphs, all modes are equivalent.
17/// - `loops`: if `true`, self-loops add 2 to the degree of their
18///   endpoint (matching igraph convention for undirected). If `false`,
19///   self-loops are still counted once per endpoint in directed mode.
20///
21/// # Examples
22///
23/// ```
24/// use rust_igraph::{Graph, degree_distribution};
25/// use rust_igraph::DegreeMode;
26///
27/// // Triangle (K3): all vertices have degree 2.
28/// let mut g = Graph::with_vertices(3);
29/// g.add_edge(0, 1).unwrap();
30/// g.add_edge(1, 2).unwrap();
31/// g.add_edge(2, 0).unwrap();
32/// let hist = degree_distribution(&g, DegreeMode::All).unwrap();
33/// // hist = [0, 0, 3] — zero vertices with degree 0 or 1, three with degree 2.
34/// assert_eq!(hist, vec![0, 0, 3]);
35/// ```
36pub fn degree_distribution(graph: &Graph, mode: DegreeMode) -> IgraphResult<Vec<u32>> {
37    let n = graph.vcount();
38
39    if n == 0 {
40        return Ok(Vec::new());
41    }
42
43    let n_usize = n as usize;
44    let ecount = graph.ecount();
45    let directed = graph.is_directed();
46
47    let mut deg = vec![0u32; n_usize];
48
49    if ecount == 0 {
50        return Ok(vec![n; 1]);
51    }
52
53    let m_u32 = u32::try_from(ecount)
54        .map_err(|_| crate::core::IgraphError::Internal("ecount exceeds u32::MAX"))?;
55
56    for eid in 0..m_u32 {
57        let (from, to) = graph.edge(eid)?;
58        let f = from as usize;
59        let t = to as usize;
60
61        if directed {
62            match mode {
63                DegreeMode::Out => {
64                    deg[f] += 1;
65                }
66                DegreeMode::In => {
67                    deg[t] += 1;
68                }
69                DegreeMode::All => {
70                    deg[f] += 1;
71                    deg[t] += 1;
72                }
73            }
74        } else if from == to {
75            deg[f] += 2;
76        } else {
77            deg[f] += 1;
78            deg[t] += 1;
79        }
80    }
81
82    let max_deg = deg.iter().copied().max().unwrap_or(0) as usize;
83    let mut hist = vec![0u32; max_deg + 1];
84
85    for &d in &deg {
86        hist[d as usize] += 1;
87    }
88
89    Ok(hist)
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn empty_graph() {
98        let g = Graph::with_vertices(0);
99        let h = degree_distribution(&g, DegreeMode::All).unwrap();
100        assert!(h.is_empty());
101    }
102
103    #[test]
104    fn no_edges() {
105        let g = Graph::with_vertices(5);
106        let h = degree_distribution(&g, DegreeMode::All).unwrap();
107        assert_eq!(h, vec![5]);
108    }
109
110    #[test]
111    fn triangle() {
112        let mut g = Graph::with_vertices(3);
113        g.add_edge(0, 1).unwrap();
114        g.add_edge(1, 2).unwrap();
115        g.add_edge(2, 0).unwrap();
116        let h = degree_distribution(&g, DegreeMode::All).unwrap();
117        assert_eq!(h, vec![0, 0, 3]);
118    }
119
120    #[test]
121    fn star_5() {
122        let mut g = Graph::with_vertices(5);
123        g.add_edge(0, 1).unwrap();
124        g.add_edge(0, 2).unwrap();
125        g.add_edge(0, 3).unwrap();
126        g.add_edge(0, 4).unwrap();
127        let h = degree_distribution(&g, DegreeMode::All).unwrap();
128        assert_eq!(h, vec![0, 4, 0, 0, 1]);
129    }
130
131    #[test]
132    fn path_5() {
133        let mut g = Graph::with_vertices(5);
134        g.add_edge(0, 1).unwrap();
135        g.add_edge(1, 2).unwrap();
136        g.add_edge(2, 3).unwrap();
137        g.add_edge(3, 4).unwrap();
138        let h = degree_distribution(&g, DegreeMode::All).unwrap();
139        // deg = [1, 2, 2, 2, 1] → hist[0]=0, hist[1]=2, hist[2]=3
140        assert_eq!(h, vec![0, 2, 3]);
141    }
142
143    #[test]
144    fn directed_out_degree() {
145        // 0→1, 0→2, 1→2
146        let mut g = Graph::new(3, true).unwrap();
147        g.add_edge(0, 1).unwrap();
148        g.add_edge(0, 2).unwrap();
149        g.add_edge(1, 2).unwrap();
150        let h = degree_distribution(&g, DegreeMode::Out).unwrap();
151        // out-deg = [2, 1, 0] → hist[0]=1, hist[1]=1, hist[2]=1
152        assert_eq!(h, vec![1, 1, 1]);
153    }
154
155    #[test]
156    fn directed_in_degree() {
157        // 0→1, 0→2, 1→2
158        let mut g = Graph::new(3, true).unwrap();
159        g.add_edge(0, 1).unwrap();
160        g.add_edge(0, 2).unwrap();
161        g.add_edge(1, 2).unwrap();
162        let h = degree_distribution(&g, DegreeMode::In).unwrap();
163        // in-deg = [0, 1, 2] → hist[0]=1, hist[1]=1, hist[2]=1
164        assert_eq!(h, vec![1, 1, 1]);
165    }
166
167    #[test]
168    fn directed_all_degree() {
169        // 0→1, 0→2, 1→2
170        let mut g = Graph::new(3, true).unwrap();
171        g.add_edge(0, 1).unwrap();
172        g.add_edge(0, 2).unwrap();
173        g.add_edge(1, 2).unwrap();
174        let h = degree_distribution(&g, DegreeMode::All).unwrap();
175        // total-deg = [2, 2, 2] → hist[0]=0, hist[1]=0, hist[2]=3
176        assert_eq!(h, vec![0, 0, 3]);
177    }
178
179    #[test]
180    fn self_loop_undirected() {
181        // 0-0, 0-1. Self-loop adds 2 to degree of 0.
182        let mut g = Graph::with_vertices(2);
183        g.add_edge(0, 0).unwrap();
184        g.add_edge(0, 1).unwrap();
185        let h = degree_distribution(&g, DegreeMode::All).unwrap();
186        // deg(0) = 2 + 1 = 3, deg(1) = 1 → hist = [0, 1, 0, 1]
187        assert_eq!(h, vec![0, 1, 0, 1]);
188    }
189
190    #[test]
191    fn k4_regular() {
192        let mut g = Graph::with_vertices(4);
193        for u in 0..4u32 {
194            for v in (u + 1)..4 {
195                g.add_edge(u, v).unwrap();
196            }
197        }
198        let h = degree_distribution(&g, DegreeMode::All).unwrap();
199        // All degree 3 → hist = [0, 0, 0, 4]
200        assert_eq!(h, vec![0, 0, 0, 4]);
201    }
202
203    #[test]
204    fn isolated_plus_edges() {
205        // 5 vertices: 0-1, 2-3, 4 isolated
206        let mut g = Graph::with_vertices(5);
207        g.add_edge(0, 1).unwrap();
208        g.add_edge(2, 3).unwrap();
209        let h = degree_distribution(&g, DegreeMode::All).unwrap();
210        // deg = [1, 1, 1, 1, 0] → hist[0]=1, hist[1]=4
211        assert_eq!(h, vec![1, 4]);
212    }
213}