rust_igraph/algorithms/properties/
degree_vertex_class.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};
19
20pub fn degree_leaf_ratio(graph: &Graph) -> IgraphResult<f64> {
35 let n = graph.vcount() as usize;
36 if n == 0 {
37 return Ok(0.0);
38 }
39
40 let mut count = 0_usize;
41 for v in 0..n {
42 if graph.degree(v as u32)? == 1 {
43 count += 1;
44 }
45 }
46
47 Ok(count as f64 / n as f64)
48}
49
50pub fn degree_isolated_ratio(graph: &Graph) -> IgraphResult<f64> {
67 let n = graph.vcount() as usize;
68 if n == 0 {
69 return Ok(0.0);
70 }
71
72 let mut count = 0_usize;
73 for v in 0..n {
74 if graph.degree(v as u32)? == 0 {
75 count += 1;
76 }
77 }
78
79 Ok(count as f64 / n as f64)
80}
81
82pub fn degree_core_ratio(graph: &Graph) -> IgraphResult<f64> {
98 let n = graph.vcount() as usize;
99 if n == 0 {
100 return Ok(0.0);
101 }
102
103 let degrees = collect_degrees(graph)?;
104 let mean = degrees.iter().sum::<usize>() as f64 / n as f64;
105
106 let count = degrees
107 .iter()
108 .filter(|&&d| d as f64 >= mean - 1e-12)
109 .count();
110
111 Ok(count as f64 / n as f64)
112}
113
114pub fn degree_tail_ratio(graph: &Graph) -> IgraphResult<f64> {
130 let n = graph.vcount() as usize;
131 if n == 0 {
132 return Ok(0.0);
133 }
134
135 let degrees = collect_degrees(graph)?;
136 let mean = degrees.iter().sum::<usize>() as f64 / n as f64;
137 let threshold = 2.0 * mean;
138
139 let count = degrees
140 .iter()
141 .filter(|&&d| d as f64 >= threshold - 1e-12)
142 .count();
143
144 Ok(count as f64 / n as f64)
145}
146
147fn collect_degrees(graph: &Graph) -> IgraphResult<Vec<usize>> {
148 let n = graph.vcount() as usize;
149 let mut degrees = Vec::with_capacity(n);
150 for v in 0..n {
151 degrees.push(graph.degree(v as u32)?);
152 }
153 Ok(degrees)
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 fn single_edge() -> Graph {
161 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
162 }
163
164 fn path3() -> Graph {
165 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
166 }
167
168 fn k3() -> Graph {
169 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
170 }
171
172 fn k4() -> Graph {
173 Graph::from_edges(
174 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
175 false,
176 Some(4),
177 )
178 .unwrap()
179 }
180
181 fn cycle4() -> Graph {
182 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
183 }
184
185 fn star5() -> Graph {
186 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
187 }
188
189 fn paw() -> Graph {
190 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
191 }
192
193 #[test]
196 fn leaf_empty() {
197 let g = Graph::with_vertices(0);
198 assert!(degree_leaf_ratio(&g).unwrap().abs() < 1e-10);
199 }
200
201 #[test]
202 fn leaf_isolated() {
203 let g = Graph::with_vertices(5);
204 assert!(degree_leaf_ratio(&g).unwrap().abs() < 1e-10);
205 }
206
207 #[test]
208 fn leaf_single_edge() {
209 assert!((degree_leaf_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
211 }
212
213 #[test]
214 fn leaf_star5() {
215 assert!((degree_leaf_ratio(&star5()).unwrap() - 0.8).abs() < 1e-10);
217 }
218
219 #[test]
220 fn leaf_path3() {
221 let expected = 2.0 / 3.0;
223 assert!((degree_leaf_ratio(&path3()).unwrap() - expected).abs() < 1e-10);
224 }
225
226 #[test]
227 fn leaf_k3() {
228 assert!(degree_leaf_ratio(&k3()).unwrap().abs() < 1e-10);
230 }
231
232 #[test]
233 fn leaf_paw() {
234 assert!((degree_leaf_ratio(&paw()).unwrap() - 0.25).abs() < 1e-10);
236 }
237
238 #[test]
241 fn iso_empty() {
242 let g = Graph::with_vertices(0);
243 assert!(degree_isolated_ratio(&g).unwrap().abs() < 1e-10);
244 }
245
246 #[test]
247 fn iso_all_isolated() {
248 let g = Graph::with_vertices(5);
249 assert!((degree_isolated_ratio(&g).unwrap() - 1.0).abs() < 1e-10);
250 }
251
252 #[test]
253 fn iso_single_edge() {
254 assert!(degree_isolated_ratio(&single_edge()).unwrap().abs() < 1e-10);
255 }
256
257 #[test]
258 fn iso_k3() {
259 assert!(degree_isolated_ratio(&k3()).unwrap().abs() < 1e-10);
260 }
261
262 #[test]
263 fn iso_with_isolates() {
264 let mut g = Graph::with_vertices(5);
266 g.add_edge(0, 1).unwrap();
267 assert!((degree_isolated_ratio(&g).unwrap() - 0.6).abs() < 1e-10);
268 }
269
270 #[test]
271 fn iso_star5() {
272 assert!(degree_isolated_ratio(&star5()).unwrap().abs() < 1e-10);
273 }
274
275 #[test]
278 fn core_empty() {
279 let g = Graph::with_vertices(0);
280 assert!(degree_core_ratio(&g).unwrap().abs() < 1e-10);
281 }
282
283 #[test]
284 fn core_isolated() {
285 let g = Graph::with_vertices(5);
287 assert!((degree_core_ratio(&g).unwrap() - 1.0).abs() < 1e-10);
288 }
289
290 #[test]
291 fn core_regular() {
292 assert!((degree_core_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
294 assert!((degree_core_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
295 assert!((degree_core_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
296 }
297
298 #[test]
299 fn core_single_edge() {
300 assert!((degree_core_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
302 }
303
304 #[test]
305 fn core_star5() {
306 assert!((degree_core_ratio(&star5()).unwrap() - 0.2).abs() < 1e-10);
308 }
309
310 #[test]
311 fn core_path3() {
312 let expected = 1.0 / 3.0;
314 assert!((degree_core_ratio(&path3()).unwrap() - expected).abs() < 1e-10);
315 }
316
317 #[test]
318 fn core_paw() {
319 assert!((degree_core_ratio(&paw()).unwrap() - 0.75).abs() < 1e-10);
321 }
322
323 #[test]
326 fn tail_empty() {
327 let g = Graph::with_vertices(0);
328 assert!(degree_tail_ratio(&g).unwrap().abs() < 1e-10);
329 }
330
331 #[test]
332 fn tail_isolated() {
333 let g = Graph::with_vertices(5);
335 assert!((degree_tail_ratio(&g).unwrap() - 1.0).abs() < 1e-10);
336 }
337
338 #[test]
339 fn tail_regular() {
340 assert!(degree_tail_ratio(&k3()).unwrap().abs() < 1e-10);
342 assert!(degree_tail_ratio(&k4()).unwrap().abs() < 1e-10);
343 assert!(degree_tail_ratio(&cycle4()).unwrap().abs() < 1e-10);
344 }
345
346 #[test]
347 fn tail_single_edge() {
348 assert!(degree_tail_ratio(&single_edge()).unwrap().abs() < 1e-10);
350 }
351
352 #[test]
353 fn tail_star5() {
354 assert!((degree_tail_ratio(&star5()).unwrap() - 0.2).abs() < 1e-10);
356 }
357
358 #[test]
359 fn tail_path3() {
360 assert!(degree_tail_ratio(&path3()).unwrap().abs() < 1e-10);
362 }
363
364 #[test]
365 fn tail_paw() {
366 assert!(degree_tail_ratio(&paw()).unwrap().abs() < 1e-10);
368 }
369
370 #[test]
373 fn all_ratios_in_01() {
374 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
375 for f in &[
376 degree_leaf_ratio as fn(&Graph) -> IgraphResult<f64>,
377 degree_isolated_ratio,
378 degree_core_ratio,
379 degree_tail_ratio,
380 ] {
381 let val = f(g).unwrap();
382 assert!(val >= -1e-10, "ratio below 0: {val}");
383 assert!(val <= 1.0 + 1e-10, "ratio above 1: {val}");
384 }
385 }
386 }
387
388 #[test]
389 fn tail_le_core() {
390 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
392 let tail = degree_tail_ratio(g).unwrap();
393 let core = degree_core_ratio(g).unwrap();
394 assert!(tail <= core + 1e-10);
395 }
396 }
397
398 #[test]
399 fn leaf_plus_iso_le_one() {
400 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
402 let leaf = degree_leaf_ratio(g).unwrap();
403 let iso = degree_isolated_ratio(g).unwrap();
404 assert!(leaf + iso <= 1.0 + 1e-10);
405 }
406 }
407}