rust_igraph/algorithms/properties/
information_ratios.rs1#![allow(
13 clippy::cast_lossless,
14 clippy::cast_possible_truncation,
15 clippy::cast_precision_loss,
16 clippy::many_single_char_names,
17 clippy::needless_range_loop,
18 clippy::similar_names,
19 clippy::too_many_lines
20)]
21
22use crate::core::{Graph, IgraphResult};
23
24pub fn degree_entropy_ratio(graph: &Graph) -> IgraphResult<f64> {
46 let n = graph.vcount() as usize;
47 if n < 2 {
48 return Ok(0.0);
49 }
50
51 let mut degrees = Vec::with_capacity(n);
52 for v in 0..n {
53 degrees.push(graph.degree(v as u32)?);
54 }
55
56 let max_deg = degrees.iter().copied().max().unwrap_or(0);
58 let mut freq = vec![0_u64; max_deg + 1];
59 for &d in °rees {
60 freq[d] += 1;
61 }
62
63 let n_f = n as f64;
64 let mut entropy = 0.0_f64;
65 for &f in &freq {
66 if f > 0 {
67 let p = f as f64 / n_f;
68 entropy -= p * p.ln();
69 }
70 }
71
72 let max_entropy = n_f.ln();
73 if max_entropy < 1e-30 {
74 return Ok(0.0);
75 }
76
77 Ok(entropy / max_entropy)
78}
79
80pub fn edge_distribution_entropy(graph: &Graph) -> IgraphResult<f64> {
98 let n = graph.vcount() as usize;
99 if n < 2 {
100 return Ok(0.0);
101 }
102
103 let m = graph.ecount();
104 if m == 0 {
105 return Ok(0.0);
106 }
107
108 let mut degrees = Vec::with_capacity(n);
109 for v in 0..n {
110 degrees.push(graph.degree(v as u32)?);
111 }
112
113 let mut edge_types: std::collections::HashMap<(usize, usize), u64> =
115 std::collections::HashMap::new();
116
117 for v in 0..n {
118 let nbrs = graph.neighbors(v as u32)?;
119 for &u in &nbrs {
120 let ui = u as usize;
121 if ui > v {
122 let d1 = degrees[v].min(degrees[ui]);
123 let d2 = degrees[v].max(degrees[ui]);
124 *edge_types.entry((d1, d2)).or_insert(0) += 1;
125 }
126 }
127 }
128
129 let m_f = m as f64;
130 let mut entropy = 0.0_f64;
131 for &count in edge_types.values() {
132 if count > 0 {
133 let p = count as f64 / m_f;
134 entropy -= p * p.ln();
135 }
136 }
137
138 let max_entropy = m_f.ln();
139 if max_entropy < 1e-30 {
140 return Ok(0.0);
141 }
142
143 Ok(entropy / max_entropy)
144}
145
146pub fn structural_information_content(graph: &Graph) -> IgraphResult<f64> {
166 let n = graph.vcount() as usize;
167 if n < 2 {
168 return Ok(0.0);
169 }
170
171 let mut degrees = Vec::with_capacity(n);
172 for v in 0..n {
173 degrees.push(graph.degree(v as u32)?);
174 }
175
176 let mut seen = std::collections::HashSet::new();
177 for &d in °rees {
178 seen.insert(d);
179 }
180
181 let k = seen.len();
182 if k <= 1 {
183 return Ok(0.0);
184 }
185
186 let log2_k = (k as f64).log2();
187 let log2_n = (n as f64).log2();
188
189 if log2_n < 1e-30 {
190 return Ok(0.0);
191 }
192
193 Ok(log2_k / log2_n)
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 fn empty() -> Graph {
201 Graph::with_vertices(0)
202 }
203
204 fn single() -> Graph {
205 Graph::with_vertices(1)
206 }
207
208 fn single_edge() -> Graph {
209 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
210 }
211
212 fn path3() -> Graph {
213 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
214 }
215
216 fn path4() -> Graph {
217 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
218 }
219
220 fn k3() -> Graph {
221 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
222 }
223
224 fn k4() -> Graph {
225 Graph::from_edges(
226 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
227 false,
228 Some(4),
229 )
230 .unwrap()
231 }
232
233 fn cycle4() -> Graph {
234 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
235 }
236
237 fn star5() -> Graph {
238 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
239 }
240
241 fn paw() -> Graph {
242 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
243 }
244
245 #[test]
248 fn der_empty() {
249 assert!(degree_entropy_ratio(&empty()).unwrap().abs() < 1e-10);
250 }
251
252 #[test]
253 fn der_single() {
254 assert!(degree_entropy_ratio(&single()).unwrap().abs() < 1e-10);
255 }
256
257 #[test]
258 fn der_k3() {
259 assert!(degree_entropy_ratio(&k3()).unwrap().abs() < 1e-10);
261 }
262
263 #[test]
264 fn der_k4() {
265 assert!(degree_entropy_ratio(&k4()).unwrap().abs() < 1e-10);
266 }
267
268 #[test]
269 fn der_cycle4() {
270 assert!(degree_entropy_ratio(&cycle4()).unwrap().abs() < 1e-10);
271 }
272
273 #[test]
274 fn der_star5() {
275 let r = degree_entropy_ratio(&star5()).unwrap();
279 let expected = (-(1.0_f64 / 5.0) * (1.0_f64 / 5.0).ln()
280 - (4.0_f64 / 5.0) * (4.0_f64 / 5.0).ln())
281 / 5.0_f64.ln();
282 assert!((r - expected).abs() < 1e-10);
283 }
284
285 #[test]
286 fn der_in_01() {
287 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
288 let r = degree_entropy_ratio(g).unwrap();
289 assert!(r >= -1e-10);
290 assert!(r <= 1.0 + 1e-10);
291 }
292 }
293
294 #[test]
297 fn ede_empty() {
298 assert!(edge_distribution_entropy(&empty()).unwrap().abs() < 1e-10);
299 }
300
301 #[test]
302 fn ede_single() {
303 assert!(edge_distribution_entropy(&single()).unwrap().abs() < 1e-10);
304 }
305
306 #[test]
307 fn ede_k3() {
308 assert!(edge_distribution_entropy(&k3()).unwrap().abs() < 1e-10);
310 }
311
312 #[test]
313 fn ede_k4() {
314 assert!(edge_distribution_entropy(&k4()).unwrap().abs() < 1e-10);
316 }
317
318 #[test]
319 fn ede_cycle4() {
320 assert!(edge_distribution_entropy(&cycle4()).unwrap().abs() < 1e-10);
322 }
323
324 #[test]
325 fn ede_star5() {
326 assert!(edge_distribution_entropy(&star5()).unwrap().abs() < 1e-10);
328 }
329
330 #[test]
331 fn ede_paw() {
332 let r = edge_distribution_entropy(&paw()).unwrap();
335 assert!(r > 0.0);
336 assert!(r <= 1.0 + 1e-10);
337 }
338
339 #[test]
340 fn ede_in_01() {
341 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
342 let r = edge_distribution_entropy(g).unwrap();
343 assert!(r >= -1e-10);
344 assert!(r <= 1.0 + 1e-10);
345 }
346 }
347
348 #[test]
351 fn sic_empty() {
352 assert!(structural_information_content(&empty()).unwrap().abs() < 1e-10);
353 }
354
355 #[test]
356 fn sic_single() {
357 assert!(structural_information_content(&single()).unwrap().abs() < 1e-10);
358 }
359
360 #[test]
361 fn sic_k3() {
362 assert!(structural_information_content(&k3()).unwrap().abs() < 1e-10);
364 }
365
366 #[test]
367 fn sic_k4() {
368 assert!(structural_information_content(&k4()).unwrap().abs() < 1e-10);
369 }
370
371 #[test]
372 fn sic_cycle4() {
373 assert!(structural_information_content(&cycle4()).unwrap().abs() < 1e-10);
374 }
375
376 #[test]
377 fn sic_star5() {
378 let r = structural_information_content(&star5()).unwrap();
380 let expected = 1.0 / 5.0_f64.log2();
381 assert!((r - expected).abs() < 1e-10);
382 }
383
384 #[test]
385 fn sic_path4() {
386 let r = structural_information_content(&path4()).unwrap();
388 assert!((r - 0.5).abs() < 1e-10);
389 }
390
391 #[test]
392 fn sic_paw() {
393 let r = structural_information_content(&paw()).unwrap();
395 let expected = 3.0_f64.log2() / 4.0_f64.log2();
396 assert!((r - expected).abs() < 1e-10);
397 }
398
399 #[test]
400 fn sic_in_01() {
401 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
402 let r = structural_information_content(g).unwrap();
403 assert!(r >= -1e-10);
404 assert!(r <= 1.0 + 1e-10);
405 }
406 }
407
408 #[test]
411 fn regular_zero_entropy() {
412 assert!(degree_entropy_ratio(&k3()).unwrap().abs() < 1e-10);
414 assert!(degree_entropy_ratio(&k4()).unwrap().abs() < 1e-10);
415 assert!(degree_entropy_ratio(&cycle4()).unwrap().abs() < 1e-10);
416 }
417
418 #[test]
419 fn regular_zero_sic() {
420 assert!(structural_information_content(&k3()).unwrap().abs() < 1e-10);
421 assert!(structural_information_content(&k4()).unwrap().abs() < 1e-10);
422 assert!(structural_information_content(&cycle4()).unwrap().abs() < 1e-10);
423 }
424
425 #[test]
426 fn regular_zero_edge_entropy() {
427 assert!(edge_distribution_entropy(&k3()).unwrap().abs() < 1e-10);
428 assert!(edge_distribution_entropy(&k4()).unwrap().abs() < 1e-10);
429 assert!(edge_distribution_entropy(&cycle4()).unwrap().abs() < 1e-10);
430 }
431}