rust_igraph/algorithms/properties/
edge_degree_mean.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 edge_degree_harmonic_sum(graph: &Graph) -> IgraphResult<f64> {
40 let mut result = 0.0_f64;
41
42 for (u, v) in graph.edges() {
43 if u == v {
44 continue;
45 }
46 let du = graph.degree(u)? as f64;
47 let dv = graph.degree(v)? as f64;
48 let sum = du + dv;
49 if sum == 0.0 {
50 continue;
51 }
52 result += 2.0 * du * dv / sum;
53 }
54
55 Ok(result)
56}
57
58pub fn edge_degree_geometric_sum(graph: &Graph) -> IgraphResult<f64> {
77 let mut result = 0.0_f64;
78
79 for (u, v) in graph.edges() {
80 if u == v {
81 continue;
82 }
83 let du = graph.degree(u)? as f64;
84 let dv = graph.degree(v)? as f64;
85 result += (du * dv).sqrt();
86 }
87
88 Ok(result)
89}
90
91pub fn edge_degree_ratio_sum(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)?;
116 let dv = graph.degree(v)?;
117 if du == 0 || dv == 0 {
118 continue;
119 }
120 let min_d = du.min(dv) as f64;
121 let max_d = du.max(dv) as f64;
122 result += min_d / max_d;
123 }
124
125 Ok(result)
126}
127
128pub fn edge_degree_rms(graph: &Graph) -> IgraphResult<f64> {
146 let mut sum_sq = 0.0_f64;
147 let mut edge_count = 0_u64;
148
149 for (u, v) in graph.edges() {
150 if u == v {
151 continue;
152 }
153 let du = graph.degree(u)? as f64;
154 let dv = graph.degree(v)? as f64;
155 sum_sq += du * du + dv * dv;
156 edge_count += 1;
157 }
158
159 if edge_count == 0 {
160 return Ok(0.0);
161 }
162
163 Ok((sum_sq / (2.0 * edge_count as f64)).sqrt())
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 fn single_edge() -> Graph {
171 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
172 }
173
174 fn path3() -> Graph {
175 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
176 }
177
178 fn k3() -> Graph {
179 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
180 }
181
182 fn k4() -> Graph {
183 Graph::from_edges(
184 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
185 false,
186 Some(4),
187 )
188 .unwrap()
189 }
190
191 fn cycle4() -> Graph {
192 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
193 }
194
195 fn star5() -> Graph {
196 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
197 }
198
199 fn paw() -> Graph {
200 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
201 }
202
203 #[test]
206 fn harm_empty() {
207 let g = Graph::with_vertices(0);
208 assert!(edge_degree_harmonic_sum(&g).unwrap().abs() < 1e-10);
209 }
210
211 #[test]
212 fn harm_isolated() {
213 let g = Graph::with_vertices(5);
214 assert!(edge_degree_harmonic_sum(&g).unwrap().abs() < 1e-10);
215 }
216
217 #[test]
218 fn harm_regular() {
219 assert!((edge_degree_harmonic_sum(&k3()).unwrap() - 6.0).abs() < 1e-10);
221 assert!((edge_degree_harmonic_sum(&k4()).unwrap() - 18.0).abs() < 1e-10);
222 assert!((edge_degree_harmonic_sum(&cycle4()).unwrap() - 8.0).abs() < 1e-10);
223 }
224
225 #[test]
226 fn harm_single_edge() {
227 assert!((edge_degree_harmonic_sum(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
229 }
230
231 #[test]
232 fn harm_star5() {
233 assert!((edge_degree_harmonic_sum(&star5()).unwrap() - 6.4).abs() < 1e-10);
235 }
236
237 #[test]
238 fn harm_path3() {
239 let expected = 8.0 / 3.0;
241 assert!((edge_degree_harmonic_sum(&path3()).unwrap() - expected).abs() < 1e-10);
242 }
243
244 #[test]
245 fn harm_paw() {
246 let expected = 2.0 + 2.4 + 2.4 + 1.5;
248 assert!((edge_degree_harmonic_sum(&paw()).unwrap() - expected).abs() < 1e-10);
249 }
250
251 #[test]
254 fn geom_empty() {
255 let g = Graph::with_vertices(0);
256 assert!(edge_degree_geometric_sum(&g).unwrap().abs() < 1e-10);
257 }
258
259 #[test]
260 fn geom_isolated() {
261 let g = Graph::with_vertices(5);
262 assert!(edge_degree_geometric_sum(&g).unwrap().abs() < 1e-10);
263 }
264
265 #[test]
266 fn geom_regular() {
267 assert!((edge_degree_geometric_sum(&k3()).unwrap() - 6.0).abs() < 1e-10);
269 assert!((edge_degree_geometric_sum(&k4()).unwrap() - 18.0).abs() < 1e-10);
270 assert!((edge_degree_geometric_sum(&cycle4()).unwrap() - 8.0).abs() < 1e-10);
271 }
272
273 #[test]
274 fn geom_single_edge() {
275 assert!((edge_degree_geometric_sum(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
277 }
278
279 #[test]
280 fn geom_star5() {
281 assert!((edge_degree_geometric_sum(&star5()).unwrap() - 8.0).abs() < 1e-10);
283 }
284
285 #[test]
286 fn geom_path3() {
287 let expected = 2.0 * 2.0_f64.sqrt();
289 assert!((edge_degree_geometric_sum(&path3()).unwrap() - expected).abs() < 1e-10);
290 }
291
292 #[test]
293 fn geom_paw() {
294 let expected = 2.0 + 2.0 * 6.0_f64.sqrt() + 3.0_f64.sqrt();
296 assert!((edge_degree_geometric_sum(&paw()).unwrap() - expected).abs() < 1e-10);
297 }
298
299 #[test]
302 fn ratio_empty() {
303 let g = Graph::with_vertices(0);
304 assert!(edge_degree_ratio_sum(&g).unwrap().abs() < 1e-10);
305 }
306
307 #[test]
308 fn ratio_isolated() {
309 let g = Graph::with_vertices(5);
310 assert!(edge_degree_ratio_sum(&g).unwrap().abs() < 1e-10);
311 }
312
313 #[test]
314 fn ratio_regular() {
315 assert!((edge_degree_ratio_sum(&k3()).unwrap() - 3.0).abs() < 1e-10);
317 assert!((edge_degree_ratio_sum(&k4()).unwrap() - 6.0).abs() < 1e-10);
318 assert!((edge_degree_ratio_sum(&cycle4()).unwrap() - 4.0).abs() < 1e-10);
319 }
320
321 #[test]
322 fn ratio_single_edge() {
323 assert!((edge_degree_ratio_sum(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
325 }
326
327 #[test]
328 fn ratio_star5() {
329 assert!((edge_degree_ratio_sum(&star5()).unwrap() - 1.0).abs() < 1e-10);
331 }
332
333 #[test]
334 fn ratio_path3() {
335 assert!((edge_degree_ratio_sum(&path3()).unwrap() - 1.0).abs() < 1e-10);
337 }
338
339 #[test]
340 fn ratio_paw() {
341 let expected = 1.0 + 2.0 / 3.0 + 2.0 / 3.0 + 1.0 / 3.0;
343 assert!((edge_degree_ratio_sum(&paw()).unwrap() - expected).abs() < 1e-10);
344 }
345
346 #[test]
349 fn rms_empty() {
350 let g = Graph::with_vertices(0);
351 assert!(edge_degree_rms(&g).unwrap().abs() < 1e-10);
352 }
353
354 #[test]
355 fn rms_isolated() {
356 let g = Graph::with_vertices(5);
357 assert!(edge_degree_rms(&g).unwrap().abs() < 1e-10);
358 }
359
360 #[test]
361 fn rms_regular() {
362 assert!((edge_degree_rms(&k3()).unwrap() - 2.0).abs() < 1e-10);
364 assert!((edge_degree_rms(&k4()).unwrap() - 3.0).abs() < 1e-10);
365 assert!((edge_degree_rms(&cycle4()).unwrap() - 2.0).abs() < 1e-10);
366 }
367
368 #[test]
369 fn rms_single_edge() {
370 assert!((edge_degree_rms(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
372 }
373
374 #[test]
375 fn rms_star5() {
376 let expected = (68.0 / 8.0_f64).sqrt();
378 assert!((edge_degree_rms(&star5()).unwrap() - expected).abs() < 1e-10);
379 }
380
381 #[test]
382 fn rms_path3() {
383 let expected = (10.0 / 4.0_f64).sqrt();
385 assert!((edge_degree_rms(&path3()).unwrap() - expected).abs() < 1e-10);
386 }
387
388 #[test]
389 fn rms_paw() {
390 let expected = (44.0 / 8.0_f64).sqrt();
393 assert!((edge_degree_rms(&paw()).unwrap() - expected).abs() < 1e-10);
394 }
395
396 #[test]
399 fn harmonic_le_geometric_le_arithmetic() {
400 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
403 let h = edge_degree_harmonic_sum(g).unwrap();
404 let geo = edge_degree_geometric_sum(g).unwrap();
405 assert!(h <= geo + 1e-10);
406 }
407 }
408
409 #[test]
410 fn ratio_in_0_m() {
411 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
412 let val = edge_degree_ratio_sum(g).unwrap();
413 let m = g.edges().filter(|&(u, v)| u != v).count() as f64;
414 assert!(val >= -1e-10);
415 assert!(val <= m + 1e-10);
416 }
417 }
418
419 #[test]
420 fn ratio_equals_m_for_regular() {
421 for g in &[k3(), k4(), cycle4()] {
423 let val = edge_degree_ratio_sum(g).unwrap();
424 let m = g.edges().filter(|&(u, v)| u != v).count() as f64;
425 assert!((val - m).abs() < 1e-10);
426 }
427 }
428
429 #[test]
430 fn rms_ge_mean() {
431 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
433 let rms = edge_degree_rms(g).unwrap();
434 let edges: Vec<_> = g.edges().filter(|&(u, v)| u != v).collect();
435 if edges.is_empty() {
436 continue;
437 }
438 let mean: f64 = edges
439 .iter()
440 .map(|&(u, v)| {
441 let du = g.degree(u).unwrap() as f64;
442 let dv = g.degree(v).unwrap() as f64;
443 f64::midpoint(du, dv)
444 })
445 .sum::<f64>()
446 / edges.len() as f64;
447 assert!(rms >= mean - 1e-10);
448 }
449 }
450}