rust_igraph/algorithms/properties/
degree_ratio_indices.rs1#![allow(
12 clippy::cast_possible_truncation,
13 clippy::cast_precision_loss,
14 clippy::many_single_char_names,
15 clippy::needless_range_loop,
16 clippy::too_many_lines
17)]
18
19use crate::core::{Graph, IgraphResult};
20
21pub fn symmetric_degree_ratio(graph: &Graph) -> IgraphResult<f64> {
39 let mut result = 0.0_f64;
40
41 for (u, v) in graph.edges() {
42 if u == v {
43 continue;
44 }
45 let du = graph.degree(u)? as f64;
46 let dv = graph.degree(v)? as f64;
47 if du <= 0.0 || dv <= 0.0 {
48 continue;
49 }
50 result += du / dv + dv / du;
51 }
52
53 Ok(result)
54}
55
56pub fn minmax_degree_ratio(graph: &Graph) -> IgraphResult<f64> {
74 let mut result = 0.0_f64;
75
76 for (u, v) in graph.edges() {
77 if u == v {
78 continue;
79 }
80 let du = graph.degree(u)? as f64;
81 let dv = graph.degree(v)? as f64;
82 if du <= 0.0 || dv <= 0.0 {
83 continue;
84 }
85 result += du.min(dv) / du.max(dv);
86 }
87
88 Ok(result)
89}
90
91pub fn degree_harmonic_mean_index(graph: &Graph) -> IgraphResult<f64> {
109 let mut result = 0.0_f64;
110
111 for (u, v) in graph.edges() {
112 if u == v {
113 continue;
114 }
115 let du = graph.degree(u)? as f64;
116 let dv = graph.degree(v)? as f64;
117 let s = du + dv;
118 if s <= 0.0 {
119 continue;
120 }
121 result += 2.0 * du * dv / s;
122 }
123
124 Ok(result)
125}
126
127pub fn degree_diff_connectivity(graph: &Graph) -> IgraphResult<f64> {
144 let mut result = 0.0_f64;
145
146 for (u, v) in graph.edges() {
147 if u == v {
148 continue;
149 }
150 let du = graph.degree(u)? as f64;
151 let dv = graph.degree(v)? as f64;
152 let diff = (du - dv).abs() + 1.0;
153 result += 1.0 / diff.sqrt();
154 }
155
156 Ok(result)
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 fn single_edge() -> Graph {
164 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
165 }
166
167 fn path3() -> Graph {
168 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
169 }
170
171 fn k3() -> Graph {
172 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
173 }
174
175 fn k4() -> Graph {
176 Graph::from_edges(
177 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
178 false,
179 Some(4),
180 )
181 .unwrap()
182 }
183
184 fn cycle4() -> Graph {
185 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
186 }
187
188 fn star5() -> Graph {
189 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
190 }
191
192 fn paw() -> Graph {
193 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
194 }
195
196 #[test]
199 fn sdr_empty() {
200 let g = Graph::with_vertices(0);
201 assert!(symmetric_degree_ratio(&g).unwrap().abs() < 1e-10);
202 }
203
204 #[test]
205 fn sdr_isolated() {
206 let g = Graph::with_vertices(5);
207 assert!(symmetric_degree_ratio(&g).unwrap().abs() < 1e-10);
208 }
209
210 #[test]
211 fn sdr_regular_is_2m() {
212 assert!((symmetric_degree_ratio(&k3()).unwrap() - 6.0).abs() < 1e-10);
214 assert!((symmetric_degree_ratio(&k4()).unwrap() - 12.0).abs() < 1e-10);
215 assert!((symmetric_degree_ratio(&cycle4()).unwrap() - 8.0).abs() < 1e-10);
216 }
217
218 #[test]
219 fn sdr_single_edge() {
220 assert!((symmetric_degree_ratio(&single_edge()).unwrap() - 2.0).abs() < 1e-10);
222 }
223
224 #[test]
225 fn sdr_star5() {
226 assert!((symmetric_degree_ratio(&star5()).unwrap() - 17.0).abs() < 1e-10);
228 }
229
230 #[test]
231 fn sdr_path3() {
232 assert!((symmetric_degree_ratio(&path3()).unwrap() - 5.0).abs() < 1e-10);
234 }
235
236 #[test]
237 fn sdr_paw() {
238 let expected = 2.0 + 2.0 * 13.0 / 6.0 + 10.0 / 3.0;
243 assert!((symmetric_degree_ratio(&paw()).unwrap() - expected).abs() < 1e-10);
244 }
245
246 #[test]
247 fn sdr_ge_2m() {
248 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
250 assert!(symmetric_degree_ratio(g).unwrap() >= 2.0 * g.ecount() as f64 - 1e-10);
251 }
252 }
253
254 #[test]
257 fn mmr_empty() {
258 let g = Graph::with_vertices(0);
259 assert!(minmax_degree_ratio(&g).unwrap().abs() < 1e-10);
260 }
261
262 #[test]
263 fn mmr_isolated() {
264 let g = Graph::with_vertices(5);
265 assert!(minmax_degree_ratio(&g).unwrap().abs() < 1e-10);
266 }
267
268 #[test]
269 fn mmr_regular_is_m() {
270 assert!((minmax_degree_ratio(&k3()).unwrap() - 3.0).abs() < 1e-10);
272 assert!((minmax_degree_ratio(&k4()).unwrap() - 6.0).abs() < 1e-10);
273 assert!((minmax_degree_ratio(&cycle4()).unwrap() - 4.0).abs() < 1e-10);
274 }
275
276 #[test]
277 fn mmr_single_edge() {
278 assert!((minmax_degree_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
279 }
280
281 #[test]
282 fn mmr_star5() {
283 assert!((minmax_degree_ratio(&star5()).unwrap() - 1.0).abs() < 1e-10);
285 }
286
287 #[test]
288 fn mmr_path3() {
289 assert!((minmax_degree_ratio(&path3()).unwrap() - 1.0).abs() < 1e-10);
291 }
292
293 #[test]
294 fn mmr_paw() {
295 let expected = 1.0 + 2.0 * 2.0 / 3.0 + 1.0 / 3.0;
300 assert!((minmax_degree_ratio(&paw()).unwrap() - expected).abs() < 1e-10);
301 }
302
303 #[test]
304 fn mmr_le_m() {
305 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
307 assert!(minmax_degree_ratio(g).unwrap() <= g.ecount() as f64 + 1e-10);
308 }
309 }
310
311 #[test]
314 fn dhm_empty() {
315 let g = Graph::with_vertices(0);
316 assert!(degree_harmonic_mean_index(&g).unwrap().abs() < 1e-10);
317 }
318
319 #[test]
320 fn dhm_isolated() {
321 let g = Graph::with_vertices(5);
322 assert!(degree_harmonic_mean_index(&g).unwrap().abs() < 1e-10);
323 }
324
325 #[test]
326 fn dhm_regular_is_mr() {
327 assert!((degree_harmonic_mean_index(&k3()).unwrap() - 6.0).abs() < 1e-10);
330 assert!((degree_harmonic_mean_index(&k4()).unwrap() - 18.0).abs() < 1e-10);
332 assert!((degree_harmonic_mean_index(&cycle4()).unwrap() - 8.0).abs() < 1e-10);
334 }
335
336 #[test]
337 fn dhm_single_edge() {
338 assert!((degree_harmonic_mean_index(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
340 }
341
342 #[test]
343 fn dhm_star5() {
344 assert!((degree_harmonic_mean_index(&star5()).unwrap() - 6.4).abs() < 1e-10);
346 }
347
348 #[test]
349 fn dhm_paw() {
350 let expected = 2.0 + 2.0 * 2.4 + 1.5;
355 assert!((degree_harmonic_mean_index(&paw()).unwrap() - expected).abs() < 1e-10);
356 }
357
358 #[test]
361 fn ddc_empty() {
362 let g = Graph::with_vertices(0);
363 assert!(degree_diff_connectivity(&g).unwrap().abs() < 1e-10);
364 }
365
366 #[test]
367 fn ddc_isolated() {
368 let g = Graph::with_vertices(5);
369 assert!(degree_diff_connectivity(&g).unwrap().abs() < 1e-10);
370 }
371
372 #[test]
373 fn ddc_regular_is_m() {
374 assert!((degree_diff_connectivity(&k3()).unwrap() - 3.0).abs() < 1e-10);
376 assert!((degree_diff_connectivity(&k4()).unwrap() - 6.0).abs() < 1e-10);
377 assert!((degree_diff_connectivity(&cycle4()).unwrap() - 4.0).abs() < 1e-10);
378 }
379
380 #[test]
381 fn ddc_single_edge() {
382 assert!((degree_diff_connectivity(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
383 }
384
385 #[test]
386 fn ddc_star5() {
387 assert!((degree_diff_connectivity(&star5()).unwrap() - 2.0).abs() < 1e-10);
389 }
390
391 #[test]
392 fn ddc_path3() {
393 let expected = 2.0 / 2.0_f64.sqrt();
395 assert!((degree_diff_connectivity(&path3()).unwrap() - expected).abs() < 1e-10);
396 }
397
398 #[test]
399 fn ddc_paw() {
400 let expected = 1.0 + 2.0 / 2.0_f64.sqrt() + 1.0 / 3.0_f64.sqrt();
405 assert!((degree_diff_connectivity(&paw()).unwrap() - expected).abs() < 1e-10);
406 }
407
408 #[test]
409 fn ddc_le_m() {
410 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
412 assert!(degree_diff_connectivity(g).unwrap() <= g.ecount() as f64 + 1e-10);
413 }
414 }
415
416 #[test]
419 fn dhm_positive() {
420 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
421 assert!(degree_harmonic_mean_index(g).unwrap() > 0.0);
422 }
423 }
424
425 #[test]
426 fn sdr_positive() {
427 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
428 assert!(symmetric_degree_ratio(g).unwrap() > 0.0);
429 }
430 }
431}