rust_igraph/algorithms/properties/
topological_indices.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
24pub fn first_zagreb_index(graph: &Graph) -> IgraphResult<u64> {
36 let n = graph.vcount() as usize;
37 let deg = compute_degrees(graph, n)?;
38
39 let mut m1: u64 = 0;
40 for &d in ° {
41 m1 = m1.saturating_add((d as u64).saturating_mul(d as u64));
42 }
43
44 Ok(m1)
45}
46
47pub fn second_zagreb_index(graph: &Graph) -> IgraphResult<u64> {
59 let n = graph.vcount() as usize;
60 let deg = compute_degrees(graph, n)?;
61
62 let mut m2: u64 = 0;
63 for (u, v) in graph.edges() {
64 let du = deg[u as usize] as u64;
65 let dv = deg[v as usize] as u64;
66 m2 = m2.saturating_add(du.saturating_mul(dv));
67 }
68
69 Ok(m2)
70}
71
72pub fn randic_index(graph: &Graph) -> IgraphResult<f64> {
89 let n = graph.vcount() as usize;
90 let deg = compute_degrees(graph, n)?;
91
92 let mut r: f64 = 0.0;
93
94 for (u, v) in graph.edges() {
95 let ui = u as usize;
96 let vi = v as usize;
97 if ui == vi || deg[ui] == 0 || deg[vi] == 0 {
98 continue;
99 }
100 let product = (deg[ui] as f64) * (deg[vi] as f64);
101 r += 1.0 / product.sqrt();
102 }
103
104 Ok(r)
105}
106
107pub fn abc_index(graph: &Graph) -> IgraphResult<f64> {
121 let n = graph.vcount() as usize;
122 let deg = compute_degrees(graph, n)?;
123
124 let mut abc: f64 = 0.0;
125
126 for (u, v) in graph.edges() {
127 let ui = u as usize;
128 let vi = v as usize;
129 if ui == vi || deg[ui] == 0 || deg[vi] == 0 {
130 continue;
131 }
132 let du = deg[ui] as f64;
133 let dv = deg[vi] as f64;
134 let numerator = du + dv - 2.0;
135 if numerator >= 0.0 {
136 abc += (numerator / (du * dv)).sqrt();
137 }
138 }
139
140 Ok(abc)
141}
142
143pub fn harmonic_graph_index(graph: &Graph) -> IgraphResult<f64> {
158 let n = graph.vcount() as usize;
159 let deg = compute_degrees(graph, n)?;
160
161 let mut h: f64 = 0.0;
162
163 for (u, v) in graph.edges() {
164 let ui = u as usize;
165 let vi = v as usize;
166 if ui == vi {
167 continue;
168 }
169 let sum_deg = deg[ui] as f64 + deg[vi] as f64;
170 if sum_deg > 0.0 {
171 h += 2.0 / sum_deg;
172 }
173 }
174
175 Ok(h)
176}
177
178fn compute_degrees(graph: &Graph, n: usize) -> IgraphResult<Vec<usize>> {
179 let mut deg = vec![0_usize; n];
180 for v in 0..n as u32 {
181 deg[v as usize] = graph.degree(v)?;
182 }
183 Ok(deg)
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 fn path3() -> Graph {
191 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
192 }
193
194 fn path4() -> Graph {
195 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
196 }
197
198 fn k3() -> Graph {
199 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
200 }
201
202 fn k4() -> Graph {
203 Graph::from_edges(
204 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
205 false,
206 Some(4),
207 )
208 .unwrap()
209 }
210
211 fn cycle4() -> Graph {
212 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
213 }
214
215 fn star5() -> Graph {
216 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
217 }
218
219 fn single_edge() -> Graph {
220 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
221 }
222
223 #[test]
226 fn fzi_empty() {
227 let g = Graph::with_vertices(0);
228 assert_eq!(first_zagreb_index(&g).unwrap(), 0);
229 }
230
231 #[test]
232 fn fzi_no_edges() {
233 let g = Graph::with_vertices(3);
234 assert_eq!(first_zagreb_index(&g).unwrap(), 0);
235 }
236
237 #[test]
238 fn fzi_single_edge() {
239 assert_eq!(first_zagreb_index(&single_edge()).unwrap(), 2);
241 }
242
243 #[test]
244 fn fzi_path3() {
245 assert_eq!(first_zagreb_index(&path3()).unwrap(), 6);
247 }
248
249 #[test]
250 fn fzi_path4() {
251 assert_eq!(first_zagreb_index(&path4()).unwrap(), 10);
253 }
254
255 #[test]
256 fn fzi_k3() {
257 assert_eq!(first_zagreb_index(&k3()).unwrap(), 12);
259 }
260
261 #[test]
262 fn fzi_k4() {
263 assert_eq!(first_zagreb_index(&k4()).unwrap(), 36);
265 }
266
267 #[test]
268 fn fzi_cycle4() {
269 assert_eq!(first_zagreb_index(&cycle4()).unwrap(), 16);
271 }
272
273 #[test]
274 fn fzi_star5() {
275 assert_eq!(first_zagreb_index(&star5()).unwrap(), 20);
277 }
278
279 #[test]
282 fn szi_empty() {
283 let g = Graph::with_vertices(0);
284 assert_eq!(second_zagreb_index(&g).unwrap(), 0);
285 }
286
287 #[test]
288 fn szi_single_edge() {
289 assert_eq!(second_zagreb_index(&single_edge()).unwrap(), 1);
291 }
292
293 #[test]
294 fn szi_path3() {
295 assert_eq!(second_zagreb_index(&path3()).unwrap(), 4);
297 }
298
299 #[test]
300 fn szi_k3() {
301 assert_eq!(second_zagreb_index(&k3()).unwrap(), 12);
303 }
304
305 #[test]
306 fn szi_k4() {
307 assert_eq!(second_zagreb_index(&k4()).unwrap(), 54);
309 }
310
311 #[test]
312 fn szi_star5() {
313 assert_eq!(second_zagreb_index(&star5()).unwrap(), 16);
315 }
316
317 #[test]
320 fn ri_empty() {
321 let g = Graph::with_vertices(0);
322 assert!((randic_index(&g).unwrap() - 0.0).abs() < 1e-10);
323 }
324
325 #[test]
326 fn ri_single_edge() {
327 assert!((randic_index(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
329 }
330
331 #[test]
332 fn ri_path3() {
333 let r = randic_index(&path3()).unwrap();
335 assert!((r - std::f64::consts::SQRT_2).abs() < 1e-10);
336 }
337
338 #[test]
339 fn ri_k3() {
340 let r = randic_index(&k3()).unwrap();
342 assert!((r - 1.5).abs() < 1e-10);
343 }
344
345 #[test]
346 fn ri_k4() {
347 let r = randic_index(&k4()).unwrap();
349 assert!((r - 2.0).abs() < 1e-10);
350 }
351
352 #[test]
355 fn abc_single_edge() {
356 assert!((abc_index(&single_edge()).unwrap() - 0.0).abs() < 1e-10);
358 }
359
360 #[test]
361 fn abc_path3() {
362 let a = abc_index(&path3()).unwrap();
364 assert!((a - 2.0 * (0.5_f64).sqrt()).abs() < 1e-10);
365 }
366
367 #[test]
368 fn abc_k3() {
369 let a = abc_index(&k3()).unwrap();
371 assert!((a - 3.0 * (0.5_f64).sqrt()).abs() < 1e-10);
372 }
373
374 #[test]
377 fn hgi_empty() {
378 let g = Graph::with_vertices(0);
379 assert!((harmonic_graph_index(&g).unwrap() - 0.0).abs() < 1e-10);
380 }
381
382 #[test]
383 fn hgi_single_edge() {
384 assert!((harmonic_graph_index(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
386 }
387
388 #[test]
389 fn hgi_path3() {
390 let h = harmonic_graph_index(&path3()).unwrap();
392 assert!((h - 4.0 / 3.0).abs() < 1e-10);
393 }
394
395 #[test]
396 fn hgi_k3() {
397 let h = harmonic_graph_index(&k3()).unwrap();
399 assert!((h - 1.5).abs() < 1e-10);
400 }
401
402 #[test]
403 fn hgi_star5() {
404 let h = harmonic_graph_index(&star5()).unwrap();
406 assert!((h - 1.6).abs() < 1e-10);
407 }
408
409 #[test]
412 fn m1_equals_sum_over_edges() {
413 for g in &[path3(), path4(), k3(), k4(), cycle4(), star5()] {
415 let m1 = first_zagreb_index(g).unwrap();
416 let n = g.vcount() as usize;
417 let deg: Vec<usize> = (0..n as u32).map(|v| g.degree(v).unwrap()).collect();
418
419 let mut seen = std::collections::HashSet::new();
420 let mut alt: u64 = 0;
421 for (u, v) in g.edges() {
422 let key = (u.min(v), u.max(v));
423 if seen.insert(key) {
424 alt += deg[u as usize] as u64 + deg[v as usize] as u64;
425 }
426 }
427 assert_eq!(m1, alt, "M₁ edge sum formula mismatch");
428 }
429 }
430
431 #[test]
432 fn regular_graph_indices() {
433 let g = cycle4(); assert_eq!(first_zagreb_index(&g).unwrap(), 16); assert_eq!(second_zagreb_index(&g).unwrap(), 16); let r = randic_index(&g).unwrap();
439 assert!((r - 2.0).abs() < 1e-10); let h = harmonic_graph_index(&g).unwrap();
441 assert!((h - 2.0).abs() < 1e-10); }
443}