rust_igraph/algorithms/properties/
degree_shape.rs1#![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};
19use std::collections::HashMap;
20
21pub fn degree_mode(graph: &Graph) -> IgraphResult<usize> {
37 let n = graph.vcount() as usize;
38 if n == 0 {
39 return Ok(0);
40 }
41
42 let counts = degree_counts(graph)?;
43
44 let mut best_deg = 0_usize;
45 let mut best_count = 0_usize;
46 for (°, &count) in &counts {
47 if count > best_count || (count == best_count && deg < best_deg) {
48 best_deg = deg;
49 best_count = count;
50 }
51 }
52
53 Ok(best_deg)
54}
55
56pub fn degree_concentration(graph: &Graph) -> IgraphResult<f64> {
72 let n = graph.vcount() as usize;
73 if n == 0 {
74 return Ok(0.0);
75 }
76
77 let counts = degree_counts(graph)?;
78 let max_count = counts.values().copied().max().unwrap_or(0);
79
80 Ok(max_count as f64 / n as f64)
81}
82
83pub fn degree_diversity(graph: &Graph) -> IgraphResult<usize> {
98 let n = graph.vcount() as usize;
99 if n == 0 {
100 return Ok(0);
101 }
102
103 let counts = degree_counts(graph)?;
104 Ok(counts.len())
105}
106
107pub fn hub_dominance(graph: &Graph) -> IgraphResult<f64> {
123 let n = graph.vcount() as usize;
124 if n == 0 {
125 return Ok(0.0);
126 }
127
128 let mut d_max = 0_usize;
129 let mut total = 0_usize;
130 for v in 0..n {
131 let d = graph.degree(v as u32)?;
132 if d > d_max {
133 d_max = d;
134 }
135 total += d;
136 }
137
138 if total == 0 {
139 return Ok(0.0);
140 }
141
142 Ok(d_max as f64 / total as f64)
143}
144
145fn degree_counts(graph: &Graph) -> IgraphResult<HashMap<usize, usize>> {
146 let n = graph.vcount() as usize;
147 let mut counts: HashMap<usize, usize> = HashMap::new();
148 for v in 0..n {
149 let d = graph.degree(v as u32)?;
150 *counts.entry(d).or_insert(0) += 1;
151 }
152 Ok(counts)
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 fn single_edge() -> Graph {
160 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
161 }
162
163 fn path3() -> Graph {
164 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
165 }
166
167 fn k3() -> Graph {
168 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
169 }
170
171 fn k4() -> Graph {
172 Graph::from_edges(
173 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
174 false,
175 Some(4),
176 )
177 .unwrap()
178 }
179
180 fn cycle4() -> Graph {
181 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).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 mode_empty() {
196 let g = Graph::with_vertices(0);
197 assert_eq!(degree_mode(&g).unwrap(), 0);
198 }
199
200 #[test]
201 fn mode_isolated() {
202 let g = Graph::with_vertices(5);
203 assert_eq!(degree_mode(&g).unwrap(), 0);
204 }
205
206 #[test]
207 fn mode_k3() {
208 assert_eq!(degree_mode(&k3()).unwrap(), 2);
209 }
210
211 #[test]
212 fn mode_k4() {
213 assert_eq!(degree_mode(&k4()).unwrap(), 3);
214 }
215
216 #[test]
217 fn mode_single_edge() {
218 assert_eq!(degree_mode(&single_edge()).unwrap(), 1);
219 }
220
221 #[test]
222 fn mode_star5() {
223 assert_eq!(degree_mode(&star5()).unwrap(), 1);
225 }
226
227 #[test]
228 fn mode_path3() {
229 assert_eq!(degree_mode(&path3()).unwrap(), 1);
231 }
232
233 #[test]
234 fn mode_paw() {
235 assert_eq!(degree_mode(&paw()).unwrap(), 2);
237 }
238
239 #[test]
242 fn conc_empty() {
243 let g = Graph::with_vertices(0);
244 assert!(degree_concentration(&g).unwrap().abs() < 1e-10);
245 }
246
247 #[test]
248 fn conc_regular_one() {
249 assert!((degree_concentration(&k3()).unwrap() - 1.0).abs() < 1e-10);
250 assert!((degree_concentration(&k4()).unwrap() - 1.0).abs() < 1e-10);
251 assert!((degree_concentration(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
252 }
253
254 #[test]
255 fn conc_single_edge() {
256 assert!((degree_concentration(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
257 }
258
259 #[test]
260 fn conc_star5() {
261 assert!((degree_concentration(&star5()).unwrap() - 0.8).abs() < 1e-10);
263 }
264
265 #[test]
266 fn conc_path3() {
267 let expected = 2.0 / 3.0;
269 assert!((degree_concentration(&path3()).unwrap() - expected).abs() < 1e-10);
270 }
271
272 #[test]
273 fn conc_paw() {
274 assert!((degree_concentration(&paw()).unwrap() - 0.5).abs() < 1e-10);
276 }
277
278 #[test]
279 fn conc_in_01() {
280 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
281 let val = degree_concentration(g).unwrap();
282 assert!(val >= -1e-10);
283 assert!(val <= 1.0 + 1e-10);
284 }
285 }
286
287 #[test]
290 fn div_empty() {
291 let g = Graph::with_vertices(0);
292 assert_eq!(degree_diversity(&g).unwrap(), 0);
293 }
294
295 #[test]
296 fn div_isolated() {
297 let g = Graph::with_vertices(5);
298 assert_eq!(degree_diversity(&g).unwrap(), 1);
299 }
300
301 #[test]
302 fn div_regular_one() {
303 assert_eq!(degree_diversity(&k3()).unwrap(), 1);
304 assert_eq!(degree_diversity(&k4()).unwrap(), 1);
305 assert_eq!(degree_diversity(&cycle4()).unwrap(), 1);
306 }
307
308 #[test]
309 fn div_single_edge() {
310 assert_eq!(degree_diversity(&single_edge()).unwrap(), 1);
311 }
312
313 #[test]
314 fn div_star5() {
315 assert_eq!(degree_diversity(&star5()).unwrap(), 2);
317 }
318
319 #[test]
320 fn div_path3() {
321 assert_eq!(degree_diversity(&path3()).unwrap(), 2);
323 }
324
325 #[test]
326 fn div_paw() {
327 assert_eq!(degree_diversity(&paw()).unwrap(), 3);
329 }
330
331 #[test]
334 fn hub_empty() {
335 let g = Graph::with_vertices(0);
336 assert!(hub_dominance(&g).unwrap().abs() < 1e-10);
337 }
338
339 #[test]
340 fn hub_isolated() {
341 let g = Graph::with_vertices(5);
342 assert!(hub_dominance(&g).unwrap().abs() < 1e-10);
343 }
344
345 #[test]
346 fn hub_regular() {
347 let val = hub_dominance(&k3()).unwrap();
349 assert!((val - 1.0 / 3.0).abs() < 1e-10);
350
351 let val = hub_dominance(&k4()).unwrap();
352 assert!((val - 1.0 / 4.0).abs() < 1e-10);
353 }
354
355 #[test]
356 fn hub_single_edge() {
357 assert!((hub_dominance(&single_edge()).unwrap() - 0.5).abs() < 1e-10);
359 }
360
361 #[test]
362 fn hub_star5() {
363 assert!((hub_dominance(&star5()).unwrap() - 0.5).abs() < 1e-10);
365 }
366
367 #[test]
368 fn hub_path3() {
369 assert!((hub_dominance(&path3()).unwrap() - 0.5).abs() < 1e-10);
371 }
372
373 #[test]
374 fn hub_paw() {
375 assert!((hub_dominance(&paw()).unwrap() - 0.375).abs() < 1e-10);
377 }
378
379 #[test]
380 fn hub_in_01() {
381 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
382 let val = hub_dominance(g).unwrap();
383 assert!(val >= -1e-10);
384 assert!(val <= 1.0 + 1e-10);
385 }
386 }
387
388 #[test]
391 fn diversity_le_n() {
392 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
393 assert!(degree_diversity(g).unwrap() <= g.vcount() as usize);
394 }
395 }
396
397 #[test]
398 fn conc_ge_1_over_n() {
399 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
400 let n = f64::from(g.vcount());
401 assert!(degree_concentration(g).unwrap() >= 1.0 / n - 1e-10);
402 }
403 }
404}