rust_igraph/algorithms/properties/
index_entropy.rs1#![allow(
15 clippy::cast_possible_truncation,
16 clippy::cast_precision_loss,
17 clippy::many_single_char_names,
18 clippy::needless_range_loop,
19 clippy::too_many_lines
20)]
21
22use crate::core::{Graph, IgraphResult};
23
24fn shannon_entropy(weights: &[f64]) -> f64 {
25 let total: f64 = weights.iter().sum();
26 if total <= 0.0 {
27 return 0.0;
28 }
29 let mut h = 0.0_f64;
30 for &w in weights {
31 if w > 0.0 {
32 let p = w / total;
33 h -= p * p.ln();
34 }
35 }
36 h
37}
38
39pub fn first_zagreb_entropy(graph: &Graph) -> IgraphResult<f64> {
55 let mut weights = Vec::new();
56
57 for (u, v) in graph.edges() {
58 if u == v {
59 continue;
60 }
61 let du = graph.degree(u)? as f64;
62 let dv = graph.degree(v)? as f64;
63 weights.push(du + dv);
64 }
65
66 Ok(shannon_entropy(&weights))
67}
68
69pub fn second_zagreb_entropy(graph: &Graph) -> IgraphResult<f64> {
85 let mut weights = Vec::new();
86
87 for (u, v) in graph.edges() {
88 if u == v {
89 continue;
90 }
91 let du = graph.degree(u)? as f64;
92 let dv = graph.degree(v)? as f64;
93 let product = du * dv;
94 if product > 0.0 {
95 weights.push(product);
96 }
97 }
98
99 Ok(shannon_entropy(&weights))
100}
101
102pub fn randic_entropy(graph: &Graph) -> IgraphResult<f64> {
118 let mut weights = Vec::new();
119
120 for (u, v) in graph.edges() {
121 if u == v {
122 continue;
123 }
124 let du = graph.degree(u)? as f64;
125 let dv = graph.degree(v)? as f64;
126 let product = du * dv;
127 if product > 0.0 {
128 weights.push(1.0 / product.sqrt());
129 }
130 }
131
132 Ok(shannon_entropy(&weights))
133}
134
135pub fn abc_entropy(graph: &Graph) -> IgraphResult<f64> {
152 let mut weights = Vec::new();
153
154 for (u, v) in graph.edges() {
155 if u == v {
156 continue;
157 }
158 let du = graph.degree(u)? as f64;
159 let dv = graph.degree(v)? as f64;
160 let product = du * dv;
161 let numer = du + dv - 2.0;
162 if product > 0.0 && numer > 0.0 {
163 weights.push((numer / product).sqrt());
164 }
165 }
166
167 Ok(shannon_entropy(&weights))
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 fn single_edge() -> Graph {
175 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
176 }
177
178 fn path3() -> Graph {
179 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
180 }
181
182 fn k3() -> Graph {
183 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
184 }
185
186 fn k4() -> Graph {
187 Graph::from_edges(
188 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
189 false,
190 Some(4),
191 )
192 .unwrap()
193 }
194
195 fn cycle4() -> Graph {
196 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
197 }
198
199 fn cycle5() -> Graph {
200 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
201 }
202
203 fn star5() -> Graph {
204 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
205 }
206
207 fn paw() -> Graph {
208 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
209 }
210
211 #[test]
214 fn fze_empty() {
215 let g = Graph::with_vertices(0);
216 assert!(first_zagreb_entropy(&g).unwrap().abs() < 1e-10);
217 }
218
219 #[test]
220 fn fze_isolated() {
221 let g = Graph::with_vertices(5);
222 assert!(first_zagreb_entropy(&g).unwrap().abs() < 1e-10);
223 }
224
225 #[test]
226 fn fze_single_edge() {
227 assert!(first_zagreb_entropy(&single_edge()).unwrap().abs() < 1e-10);
229 }
230
231 #[test]
232 fn fze_k3() {
233 assert!((first_zagreb_entropy(&k3()).unwrap() - 3.0_f64.ln()).abs() < 1e-10);
235 }
236
237 #[test]
238 fn fze_k4() {
239 assert!((first_zagreb_entropy(&k4()).unwrap() - 6.0_f64.ln()).abs() < 1e-10);
241 }
242
243 #[test]
244 fn fze_cycle4() {
245 assert!((first_zagreb_entropy(&cycle4()).unwrap() - 4.0_f64.ln()).abs() < 1e-10);
247 }
248
249 #[test]
250 fn fze_cycle5() {
251 assert!((first_zagreb_entropy(&cycle5()).unwrap() - 5.0_f64.ln()).abs() < 1e-10);
253 }
254
255 #[test]
256 fn fze_path3() {
257 assert!((first_zagreb_entropy(&path3()).unwrap() - 2.0_f64.ln()).abs() < 1e-10);
259 }
260
261 #[test]
262 fn fze_star5() {
263 assert!((first_zagreb_entropy(&star5()).unwrap() - 4.0_f64.ln()).abs() < 1e-10);
265 }
266
267 #[test]
268 fn fze_paw() {
269 let total = 18.0_f64;
272 let expected =
273 -2.0 * (4.0 / total) * (4.0 / total).ln() - 2.0 * (5.0 / total) * (5.0 / total).ln();
274 assert!((first_zagreb_entropy(&paw()).unwrap() - expected).abs() < 1e-10);
275 }
276
277 #[test]
280 fn sze_empty() {
281 let g = Graph::with_vertices(0);
282 assert!(second_zagreb_entropy(&g).unwrap().abs() < 1e-10);
283 }
284
285 #[test]
286 fn sze_single_edge() {
287 assert!(second_zagreb_entropy(&single_edge()).unwrap().abs() < 1e-10);
288 }
289
290 #[test]
291 fn sze_k3() {
292 assert!((second_zagreb_entropy(&k3()).unwrap() - 3.0_f64.ln()).abs() < 1e-10);
293 }
294
295 #[test]
296 fn sze_k4() {
297 assert!((second_zagreb_entropy(&k4()).unwrap() - 6.0_f64.ln()).abs() < 1e-10);
298 }
299
300 #[test]
301 fn sze_cycle4() {
302 assert!((second_zagreb_entropy(&cycle4()).unwrap() - 4.0_f64.ln()).abs() < 1e-10);
303 }
304
305 #[test]
306 fn sze_path3() {
307 assert!((second_zagreb_entropy(&path3()).unwrap() - 2.0_f64.ln()).abs() < 1e-10);
309 }
310
311 #[test]
312 fn sze_star5() {
313 assert!((second_zagreb_entropy(&star5()).unwrap() - 4.0_f64.ln()).abs() < 1e-10);
315 }
316
317 #[test]
318 fn sze_paw() {
319 let total = 19.0_f64;
321 let expected = -(4.0 / total) * (4.0 / total).ln()
322 - 2.0 * (6.0 / total) * (6.0 / total).ln()
323 - (3.0 / total) * (3.0 / total).ln();
324 assert!((second_zagreb_entropy(&paw()).unwrap() - expected).abs() < 1e-10);
325 }
326
327 #[test]
330 fn re_empty() {
331 let g = Graph::with_vertices(0);
332 assert!(randic_entropy(&g).unwrap().abs() < 1e-10);
333 }
334
335 #[test]
336 fn re_single_edge() {
337 assert!(randic_entropy(&single_edge()).unwrap().abs() < 1e-10);
338 }
339
340 #[test]
341 fn re_k3() {
342 assert!((randic_entropy(&k3()).unwrap() - 3.0_f64.ln()).abs() < 1e-10);
343 }
344
345 #[test]
346 fn re_k4() {
347 assert!((randic_entropy(&k4()).unwrap() - 6.0_f64.ln()).abs() < 1e-10);
348 }
349
350 #[test]
351 fn re_cycle4() {
352 assert!((randic_entropy(&cycle4()).unwrap() - 4.0_f64.ln()).abs() < 1e-10);
353 }
354
355 #[test]
356 fn re_path3() {
357 assert!((randic_entropy(&path3()).unwrap() - 2.0_f64.ln()).abs() < 1e-10);
359 }
360
361 #[test]
362 fn re_star5() {
363 assert!((randic_entropy(&star5()).unwrap() - 4.0_f64.ln()).abs() < 1e-10);
365 }
366
367 #[test]
368 fn re_paw() {
369 let w0 = 0.5;
371 let w1 = 1.0 / 6.0_f64.sqrt();
372 let w3 = 1.0 / 3.0_f64.sqrt();
373 let total = w0 + 2.0 * w1 + w3;
374 let expected = -(w0 / total) * (w0 / total).ln()
375 - 2.0 * (w1 / total) * (w1 / total).ln()
376 - (w3 / total) * (w3 / total).ln();
377 assert!((randic_entropy(&paw()).unwrap() - expected).abs() < 1e-10);
378 }
379
380 #[test]
383 fn abce_empty() {
384 let g = Graph::with_vertices(0);
385 assert!(abc_entropy(&g).unwrap().abs() < 1e-10);
386 }
387
388 #[test]
389 fn abce_single_edge() {
390 assert!(abc_entropy(&single_edge()).unwrap().abs() < 1e-10);
392 }
393
394 #[test]
395 fn abce_k3() {
396 assert!((abc_entropy(&k3()).unwrap() - 3.0_f64.ln()).abs() < 1e-10);
397 }
398
399 #[test]
400 fn abce_k4() {
401 assert!((abc_entropy(&k4()).unwrap() - 6.0_f64.ln()).abs() < 1e-10);
402 }
403
404 #[test]
405 fn abce_cycle4() {
406 assert!((abc_entropy(&cycle4()).unwrap() - 4.0_f64.ln()).abs() < 1e-10);
407 }
408
409 #[test]
410 fn abce_path3() {
411 assert!((abc_entropy(&path3()).unwrap() - 2.0_f64.ln()).abs() < 1e-10);
413 }
414
415 #[test]
416 fn abce_star5() {
417 assert!((abc_entropy(&star5()).unwrap() - 4.0_f64.ln()).abs() < 1e-10);
419 }
420
421 #[test]
422 fn abce_paw() {
423 let w0 = 1.0 / 2.0_f64.sqrt();
425 let w3 = (2.0_f64 / 3.0).sqrt();
426 let total = 3.0 * w0 + w3;
427 let expected = -3.0 * (w0 / total) * (w0 / total).ln() - (w3 / total) * (w3 / total).ln();
428 assert!((abc_entropy(&paw()).unwrap() - expected).abs() < 1e-10);
429 }
430
431 #[test]
434 fn regular_all_equal_ln_m() {
435 for g in &[k3(), k4(), cycle4(), cycle5()] {
437 let ln_m = (g.ecount() as f64).ln();
438 assert!((first_zagreb_entropy(g).unwrap() - ln_m).abs() < 1e-10);
439 assert!((second_zagreb_entropy(g).unwrap() - ln_m).abs() < 1e-10);
440 assert!((randic_entropy(g).unwrap() - ln_m).abs() < 1e-10);
441 assert!((abc_entropy(g).unwrap() - ln_m).abs() < 1e-10);
442 }
443 }
444
445 #[test]
446 fn entropy_nonnegative() {
447 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
448 assert!(first_zagreb_entropy(g).unwrap() >= -1e-10);
449 assert!(second_zagreb_entropy(g).unwrap() >= -1e-10);
450 assert!(randic_entropy(g).unwrap() >= -1e-10);
451 assert!(abc_entropy(g).unwrap() >= -1e-10);
452 }
453 }
454
455 #[test]
456 fn entropy_le_ln_m() {
457 for g in &[path3(), k3(), k4(), cycle4(), star5(), paw()] {
459 let ln_m = (g.ecount() as f64).ln();
460 assert!(first_zagreb_entropy(g).unwrap() <= ln_m + 1e-10);
461 assert!(second_zagreb_entropy(g).unwrap() <= ln_m + 1e-10);
462 assert!(randic_entropy(g).unwrap() <= ln_m + 1e-10);
463 }
464 }
465}