rust_igraph/algorithms/properties/
local_structure_ratios.rs1#![allow(
12 clippy::cast_lossless,
13 clippy::cast_possible_truncation,
14 clippy::cast_precision_loss,
15 clippy::many_single_char_names,
16 clippy::needless_range_loop,
17 clippy::similar_names,
18 clippy::too_many_lines
19)]
20
21use crate::core::{Graph, IgraphResult};
22
23pub fn local_density_ratio(graph: &Graph) -> IgraphResult<f64> {
43 let n = graph.vcount() as usize;
44 if n < 3 {
45 return Ok(0.0);
46 }
47
48 let mut sum_density = 0.0_f64;
49 let mut count = 0_u64;
50
51 for v in 0..n {
52 let nbrs = graph.neighbors(v as u32)?;
53 let d = nbrs.len();
54 if d < 2 {
55 continue;
56 }
57
58 let max_edges = d * (d - 1) / 2;
59 let mut actual_edges = 0_u64;
60 for i in 0..d {
61 for j in (i + 1)..d {
62 if graph.has_edge(nbrs[i], nbrs[j]) {
63 actual_edges += 1;
64 }
65 }
66 }
67
68 sum_density += actual_edges as f64 / max_edges as f64;
69 count += 1;
70 }
71
72 if count == 0 {
73 return Ok(0.0);
74 }
75
76 Ok(sum_density / count as f64)
77}
78
79pub fn neighbor_connectivity_ratio(graph: &Graph) -> IgraphResult<f64> {
97 let n = graph.vcount() as usize;
98 if n == 0 {
99 return Ok(0.0);
100 }
101
102 let mut degrees = Vec::with_capacity(n);
103 for v in 0..n {
104 degrees.push(graph.degree(v as u32)?);
105 }
106
107 let mut sum_ratio = 0.0_f64;
108 let mut count = 0_u64;
109
110 for v in 0..n {
111 if degrees[v] == 0 {
112 continue;
113 }
114 let nbrs = graph.neighbors(v as u32)?;
115 let mut min_d = usize::MAX;
116 let mut max_d = 0_usize;
117 for &u in &nbrs {
118 let du = degrees[u as usize];
119 if du < min_d {
120 min_d = du;
121 }
122 if du > max_d {
123 max_d = du;
124 }
125 }
126 if max_d > 0 {
127 sum_ratio += min_d as f64 / max_d as f64;
128 count += 1;
129 }
130 }
131
132 if count == 0 {
133 return Ok(0.0);
134 }
135
136 Ok(sum_ratio / count as f64)
137}
138
139pub fn degree_neighbor_correlation(graph: &Graph) -> IgraphResult<f64> {
158 let n = graph.vcount() as usize;
159 if n < 2 {
160 return Ok(0.0);
161 }
162
163 let mut degrees = Vec::with_capacity(n);
164 for v in 0..n {
165 degrees.push(graph.degree(v as u32)?);
166 }
167
168 let mut x_vals = Vec::new(); let mut y_vals = Vec::new(); for v in 0..n {
172 let dv = degrees[v];
173 if dv == 0 {
174 continue;
175 }
176 let nbrs = graph.neighbors(v as u32)?;
177 let mean_nbr: f64 = nbrs
178 .iter()
179 .map(|&u| degrees[u as usize] as f64)
180 .sum::<f64>()
181 / dv as f64;
182 x_vals.push(dv as f64);
183 y_vals.push(mean_nbr);
184 }
185
186 if x_vals.len() < 2 {
187 return Ok(0.0);
188 }
189
190 let count = x_vals.len() as f64;
191 let mean_x: f64 = x_vals.iter().sum::<f64>() / count;
192 let mean_y: f64 = y_vals.iter().sum::<f64>() / count;
193
194 let mut cov = 0.0_f64;
195 let mut var_x = 0.0_f64;
196 let mut var_y = 0.0_f64;
197
198 for i in 0..x_vals.len() {
199 let dx = x_vals[i] - mean_x;
200 let dy = y_vals[i] - mean_y;
201 cov += dx * dy;
202 var_x += dx * dx;
203 var_y += dy * dy;
204 }
205
206 if var_x < 1e-30 || var_y < 1e-30 {
207 return Ok(0.0);
208 }
209
210 Ok(cov / (var_x.sqrt() * var_y.sqrt()))
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 fn empty() -> Graph {
218 Graph::with_vertices(0)
219 }
220
221 fn single() -> Graph {
222 Graph::with_vertices(1)
223 }
224
225 fn single_edge() -> Graph {
226 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
227 }
228
229 fn path3() -> Graph {
230 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
231 }
232
233 fn k3() -> Graph {
234 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
235 }
236
237 fn k4() -> Graph {
238 Graph::from_edges(
239 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
240 false,
241 Some(4),
242 )
243 .unwrap()
244 }
245
246 fn cycle4() -> Graph {
247 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
248 }
249
250 fn star5() -> Graph {
251 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
252 }
253
254 fn paw() -> Graph {
255 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
256 }
257
258 #[test]
261 fn ldr_empty() {
262 assert!(local_density_ratio(&empty()).unwrap().abs() < 1e-10);
263 }
264
265 #[test]
266 fn ldr_single() {
267 assert!(local_density_ratio(&single()).unwrap().abs() < 1e-10);
268 }
269
270 #[test]
271 fn ldr_single_edge() {
272 assert!(local_density_ratio(&single_edge()).unwrap().abs() < 1e-10);
274 }
275
276 #[test]
277 fn ldr_k3() {
278 assert!((local_density_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
280 }
281
282 #[test]
283 fn ldr_k4() {
284 assert!((local_density_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
286 }
287
288 #[test]
289 fn ldr_cycle4() {
290 assert!(local_density_ratio(&cycle4()).unwrap().abs() < 1e-10);
292 }
293
294 #[test]
295 fn ldr_star5() {
296 assert!(local_density_ratio(&star5()).unwrap().abs() < 1e-10);
300 }
301
302 #[test]
303 fn ldr_in_01() {
304 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
305 let r = local_density_ratio(g).unwrap();
306 assert!(r >= -1e-10);
307 assert!(r <= 1.0 + 1e-10);
308 }
309 }
310
311 #[test]
314 fn ncr_empty() {
315 assert!(neighbor_connectivity_ratio(&empty()).unwrap().abs() < 1e-10);
316 }
317
318 #[test]
319 fn ncr_single() {
320 assert!(neighbor_connectivity_ratio(&single()).unwrap().abs() < 1e-10);
321 }
322
323 #[test]
324 fn ncr_single_edge() {
325 assert!((neighbor_connectivity_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
327 }
328
329 #[test]
330 fn ncr_k3() {
331 assert!((neighbor_connectivity_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
332 }
333
334 #[test]
335 fn ncr_k4() {
336 assert!((neighbor_connectivity_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
337 }
338
339 #[test]
340 fn ncr_cycle4() {
341 assert!((neighbor_connectivity_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
343 }
344
345 #[test]
346 fn ncr_star5() {
347 assert!((neighbor_connectivity_ratio(&star5()).unwrap() - 1.0).abs() < 1e-10);
350 }
351
352 #[test]
353 fn ncr_paw() {
354 let r = neighbor_connectivity_ratio(&paw()).unwrap();
361 assert!((r - 17.0 / 24.0).abs() < 1e-10);
362 }
363
364 #[test]
365 fn ncr_in_01() {
366 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
367 let r = neighbor_connectivity_ratio(g).unwrap();
368 assert!(r >= -1e-10);
369 assert!(r <= 1.0 + 1e-10);
370 }
371 }
372
373 #[test]
376 fn dnc_empty() {
377 assert!(degree_neighbor_correlation(&empty()).unwrap().abs() < 1e-10);
378 }
379
380 #[test]
381 fn dnc_single() {
382 assert!(degree_neighbor_correlation(&single()).unwrap().abs() < 1e-10);
383 }
384
385 #[test]
386 fn dnc_k3() {
387 assert!(degree_neighbor_correlation(&k3()).unwrap().abs() < 1e-10);
389 }
390
391 #[test]
392 fn dnc_k4() {
393 assert!(degree_neighbor_correlation(&k4()).unwrap().abs() < 1e-10);
394 }
395
396 #[test]
397 fn dnc_cycle4() {
398 assert!(degree_neighbor_correlation(&cycle4()).unwrap().abs() < 1e-10);
399 }
400
401 #[test]
402 fn dnc_star5() {
403 assert!((degree_neighbor_correlation(&star5()).unwrap() + 1.0).abs() < 1e-10);
407 }
408
409 #[test]
410 fn dnc_in_range() {
411 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
412 let r = degree_neighbor_correlation(g).unwrap();
413 assert!(r >= -1.0 - 1e-10);
414 assert!(r <= 1.0 + 1e-10);
415 }
416 }
417
418 #[test]
421 fn regular_zero_correlation() {
422 assert!(degree_neighbor_correlation(&k3()).unwrap().abs() < 1e-10);
423 assert!(degree_neighbor_correlation(&k4()).unwrap().abs() < 1e-10);
424 assert!(degree_neighbor_correlation(&cycle4()).unwrap().abs() < 1e-10);
425 }
426
427 #[test]
428 fn complete_full_local_density() {
429 assert!((local_density_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
430 assert!((local_density_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
431 }
432}