rust_igraph/algorithms/properties/
degree_sum_variants.rs1#![allow(
13 clippy::cast_possible_truncation,
14 clippy::cast_precision_loss,
15 clippy::many_single_char_names,
16 clippy::needless_range_loop,
17 clippy::too_many_lines
18)]
19
20use crate::core::{Graph, IgraphResult};
21
22pub fn arithmetic_geometric_index(graph: &Graph) -> IgraphResult<f64> {
38 let mut ag = 0.0_f64;
39
40 for (u, v) in graph.edges() {
41 if u == v {
42 continue;
43 }
44 let du = graph.degree(u)? as f64;
45 let dv = graph.degree(v)? as f64;
46 let product = du * dv;
47 if product > 0.0 {
48 ag += (du + dv) / (2.0 * product.sqrt());
49 }
50 }
51
52 Ok(ag)
53}
54
55pub fn sigma_coindex(graph: &Graph) -> IgraphResult<u64> {
76 let n = graph.vcount() as usize;
77 if n < 2 {
78 return Ok(0);
79 }
80
81 let mut degrees = Vec::with_capacity(n);
82 for v in 0..n {
83 degrees.push(graph.degree(v as u32)? as u64);
84 }
85
86 let mut total = 0_u64;
87 for u in 0..n {
88 for v in (u + 1)..n {
89 let diff = degrees[u].abs_diff(degrees[v]);
90 total = total.saturating_add(diff.saturating_mul(diff));
91 }
92 }
93
94 let mut edge_sum = 0_u64;
95 for (u, v) in graph.edges() {
96 if u == v {
97 continue;
98 }
99 let du = degrees[u as usize];
100 let dv = degrees[v as usize];
101 let diff = du.abs_diff(dv);
102 edge_sum = edge_sum.saturating_add(diff.saturating_mul(diff));
103 }
104
105 Ok(total.saturating_sub(edge_sum))
106}
107
108pub fn albertson_coindex(graph: &Graph) -> IgraphResult<u64> {
126 let n = graph.vcount() as usize;
127 if n < 2 {
128 return Ok(0);
129 }
130
131 let mut degrees = Vec::with_capacity(n);
132 for v in 0..n {
133 degrees.push(graph.degree(v as u32)? as u64);
134 }
135
136 let mut total = 0_u64;
137 for u in 0..n {
138 for v in (u + 1)..n {
139 let diff = degrees[u].abs_diff(degrees[v]);
140 total = total.saturating_add(diff);
141 }
142 }
143
144 let mut edge_sum = 0_u64;
145 for (u, v) in graph.edges() {
146 if u == v {
147 continue;
148 }
149 let du = degrees[u as usize];
150 let dv = degrees[v as usize];
151 let diff = du.abs_diff(dv);
152 edge_sum = edge_sum.saturating_add(diff);
153 }
154
155 Ok(total.saturating_sub(edge_sum))
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 fn single_edge() -> Graph {
163 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
164 }
165
166 fn path3() -> Graph {
167 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
168 }
169
170 fn path4() -> Graph {
171 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
172 }
173
174 fn k3() -> Graph {
175 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
176 }
177
178 fn k4() -> Graph {
179 Graph::from_edges(
180 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
181 false,
182 Some(4),
183 )
184 .unwrap()
185 }
186
187 fn cycle4() -> Graph {
188 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
189 }
190
191 fn cycle5() -> Graph {
192 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).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 ag_empty() {
207 let g = Graph::with_vertices(0);
208 assert!(arithmetic_geometric_index(&g).unwrap().abs() < 1e-10);
209 }
210
211 #[test]
212 fn ag_isolated() {
213 let g = Graph::with_vertices(5);
214 assert!(arithmetic_geometric_index(&g).unwrap().abs() < 1e-10);
215 }
216
217 #[test]
218 fn ag_single_edge() {
219 assert!((arithmetic_geometric_index(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
221 }
222
223 #[test]
224 fn ag_path3() {
225 let expected = 3.0 / 2.0_f64.sqrt();
227 assert!((arithmetic_geometric_index(&path3()).unwrap() - expected).abs() < 1e-10);
228 }
229
230 #[test]
231 fn ag_k3() {
232 assert!((arithmetic_geometric_index(&k3()).unwrap() - 3.0).abs() < 1e-10);
234 }
235
236 #[test]
237 fn ag_k4() {
238 assert!((arithmetic_geometric_index(&k4()).unwrap() - 6.0).abs() < 1e-10);
240 }
241
242 #[test]
243 fn ag_cycle4() {
244 assert!((arithmetic_geometric_index(&cycle4()).unwrap() - 4.0).abs() < 1e-10);
246 }
247
248 #[test]
249 fn ag_cycle5() {
250 assert!((arithmetic_geometric_index(&cycle5()).unwrap() - 5.0).abs() < 1e-10);
252 }
253
254 #[test]
255 fn ag_star5() {
256 let expected = 4.0 * 5.0 / 4.0;
258 assert!((arithmetic_geometric_index(&star5()).unwrap() - expected).abs() < 1e-10);
259 }
260
261 #[test]
262 fn ag_paw() {
263 let expected = 1.0 + 5.0 / 6.0_f64.sqrt() + 4.0 / (2.0 * 3.0_f64.sqrt());
268 assert!((arithmetic_geometric_index(&paw()).unwrap() - expected).abs() < 1e-10);
269 }
270
271 #[test]
272 fn ag_regular_equals_edge_count() {
273 for g in &[k3(), k4(), cycle4(), cycle5()] {
275 let m = g.ecount() as f64;
276 assert!((arithmetic_geometric_index(g).unwrap() - m).abs() < 1e-8);
277 }
278 }
279
280 #[test]
281 fn ag_ge_edge_count_for_simple() {
282 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
284 let m = g.ecount() as f64;
285 assert!(arithmetic_geometric_index(g).unwrap() >= m - 1e-10);
286 }
287 }
288
289 #[test]
292 fn sco_empty() {
293 let g = Graph::with_vertices(0);
294 assert_eq!(sigma_coindex(&g).unwrap(), 0);
295 }
296
297 #[test]
298 fn sco_isolated() {
299 let g = Graph::with_vertices(5);
300 assert_eq!(sigma_coindex(&g).unwrap(), 0);
301 }
302
303 #[test]
304 fn sco_single_edge() {
305 assert_eq!(sigma_coindex(&single_edge()).unwrap(), 0);
306 }
307
308 #[test]
309 fn sco_k3() {
310 assert_eq!(sigma_coindex(&k3()).unwrap(), 0);
311 }
312
313 #[test]
314 fn sco_k4() {
315 assert_eq!(sigma_coindex(&k4()).unwrap(), 0);
316 }
317
318 #[test]
319 fn sco_path3() {
320 assert_eq!(sigma_coindex(&path3()).unwrap(), 0);
322 }
323
324 #[test]
325 fn sco_path4() {
326 assert_eq!(sigma_coindex(&path4()).unwrap(), 2);
328 }
329
330 #[test]
331 fn sco_cycle4() {
332 assert_eq!(sigma_coindex(&cycle4()).unwrap(), 0);
334 }
335
336 #[test]
337 fn sco_cycle5() {
338 assert_eq!(sigma_coindex(&cycle5()).unwrap(), 0);
340 }
341
342 #[test]
343 fn sco_star5() {
344 assert_eq!(sigma_coindex(&star5()).unwrap(), 0);
346 }
347
348 #[test]
349 fn sco_paw() {
350 assert_eq!(sigma_coindex(&paw()).unwrap(), 2);
352 }
353
354 #[test]
355 fn sco_regular_zero() {
356 for g in &[k3(), k4(), cycle4(), cycle5()] {
358 assert_eq!(sigma_coindex(g).unwrap(), 0);
359 }
360 }
361
362 #[test]
365 fn aco_empty() {
366 let g = Graph::with_vertices(0);
367 assert_eq!(albertson_coindex(&g).unwrap(), 0);
368 }
369
370 #[test]
371 fn aco_isolated() {
372 let g = Graph::with_vertices(5);
373 assert_eq!(albertson_coindex(&g).unwrap(), 0);
374 }
375
376 #[test]
377 fn aco_single_edge() {
378 assert_eq!(albertson_coindex(&single_edge()).unwrap(), 0);
379 }
380
381 #[test]
382 fn aco_k3() {
383 assert_eq!(albertson_coindex(&k3()).unwrap(), 0);
384 }
385
386 #[test]
387 fn aco_k4() {
388 assert_eq!(albertson_coindex(&k4()).unwrap(), 0);
389 }
390
391 #[test]
392 fn aco_path3() {
393 assert_eq!(albertson_coindex(&path3()).unwrap(), 0);
395 }
396
397 #[test]
398 fn aco_path4() {
399 assert_eq!(albertson_coindex(&path4()).unwrap(), 2);
401 }
402
403 #[test]
404 fn aco_cycle4() {
405 assert_eq!(albertson_coindex(&cycle4()).unwrap(), 0);
406 }
407
408 #[test]
409 fn aco_star5() {
410 assert_eq!(albertson_coindex(&star5()).unwrap(), 0);
412 }
413
414 #[test]
415 fn aco_paw() {
416 assert_eq!(albertson_coindex(&paw()).unwrap(), 2);
418 }
419
420 #[test]
421 fn aco_regular_zero() {
422 for g in &[k3(), k4(), cycle4(), cycle5()] {
423 assert_eq!(albertson_coindex(g).unwrap(), 0);
424 }
425 }
426
427 #[test]
430 fn sigma_ge_albertson_for_nontrivial() {
431 for g in &[path4(), paw()] {
433 let s = sigma_coindex(g).unwrap();
434 let a = albertson_coindex(g).unwrap();
435 assert!(s >= a);
436 }
437 }
438}