rust_igraph/algorithms/properties/
inverse_degree.rs1#![allow(
14 clippy::cast_possible_truncation,
15 clippy::cast_possible_wrap,
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 inverse_degree_index(graph: &Graph) -> IgraphResult<f64> {
40 let mut id = 0.0_f64;
41
42 for v in 0..graph.vcount() {
43 let d = graph.degree(v)?;
44 if d > 0 {
45 id += 1.0 / d as f64;
46 }
47 }
48
49 Ok(id)
50}
51
52pub fn first_zagreb_coindex(graph: &Graph) -> IgraphResult<i64> {
69 let n = i64::from(graph.vcount());
70 let m = graph.ecount() as i64;
71
72 let mut m1: i64 = 0;
73 for v in 0..graph.vcount() {
74 let d = graph.degree(v)? as i64;
75 m1 = m1.saturating_add(d.saturating_mul(d));
76 }
77
78 Ok(2_i64
79 .saturating_mul(m)
80 .saturating_mul(n - 1)
81 .saturating_sub(m1))
82}
83
84pub fn second_zagreb_coindex(graph: &Graph) -> IgraphResult<i64> {
109 let mut sum_d: i64 = 0;
110 let mut sum_d2: i64 = 0;
111 for v in 0..graph.vcount() {
112 let d = graph.degree(v)? as i64;
113 sum_d = sum_d.saturating_add(d);
114 sum_d2 = sum_d2.saturating_add(d.saturating_mul(d));
115 }
116
117 let mut m2: i64 = 0;
118 for (u, v) in graph.edges() {
119 if u == v {
120 continue;
121 }
122 let du = graph.degree(u)? as i64;
123 let dv = graph.degree(v)? as i64;
124 m2 = m2.saturating_add(du.saturating_mul(dv));
125 }
126
127 let all_pairs_prod = (sum_d.saturating_mul(sum_d) - sum_d2) / 2;
129 Ok(all_pairs_prod - m2)
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 fn single_edge() -> Graph {
137 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
138 }
139
140 fn path3() -> Graph {
141 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
142 }
143
144 fn path4() -> Graph {
145 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
146 }
147
148 fn k3() -> Graph {
149 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
150 }
151
152 fn k4() -> Graph {
153 Graph::from_edges(
154 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
155 false,
156 Some(4),
157 )
158 .unwrap()
159 }
160
161 fn cycle4() -> Graph {
162 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
163 }
164
165 fn cycle5() -> Graph {
166 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
167 }
168
169 fn star5() -> Graph {
170 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
171 }
172
173 fn paw() -> Graph {
174 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
175 }
176
177 #[test]
180 fn id_empty() {
181 let g = Graph::with_vertices(0);
182 assert!((inverse_degree_index(&g).unwrap()).abs() < 1e-10);
183 }
184
185 #[test]
186 fn id_isolated() {
187 let g = Graph::with_vertices(5);
188 assert!((inverse_degree_index(&g).unwrap()).abs() < 1e-10);
189 }
190
191 #[test]
192 fn id_single_edge() {
193 assert!((inverse_degree_index(&single_edge()).unwrap() - 2.0).abs() < 1e-10);
194 }
195
196 #[test]
197 fn id_path3() {
198 assert!((inverse_degree_index(&path3()).unwrap() - 2.5).abs() < 1e-10);
200 }
201
202 #[test]
203 fn id_path4() {
204 assert!((inverse_degree_index(&path4()).unwrap() - 3.0).abs() < 1e-10);
206 }
207
208 #[test]
209 fn id_k3() {
210 assert!((inverse_degree_index(&k3()).unwrap() - 1.5).abs() < 1e-10);
211 }
212
213 #[test]
214 fn id_k4() {
215 assert!((inverse_degree_index(&k4()).unwrap() - 4.0 / 3.0).abs() < 1e-10);
216 }
217
218 #[test]
219 fn id_star5() {
220 assert!((inverse_degree_index(&star5()).unwrap() - 4.25).abs() < 1e-10);
221 }
222
223 #[test]
224 fn id_cycle4() {
225 assert!((inverse_degree_index(&cycle4()).unwrap() - 2.0).abs() < 1e-10);
227 }
228
229 #[test]
230 fn id_cycle5() {
231 assert!((inverse_degree_index(&cycle5()).unwrap() - 2.5).abs() < 1e-10);
233 }
234
235 #[test]
236 fn id_paw() {
237 assert!((inverse_degree_index(&paw()).unwrap() - 7.0 / 3.0).abs() < 1e-10);
239 }
240
241 #[test]
242 fn id_regular_is_n_over_r() {
243 for g in &[k3(), k4(), cycle4(), cycle5()] {
244 let n = f64::from(g.vcount());
245 let r = g.degree(0).unwrap() as f64;
246 assert!((inverse_degree_index(g).unwrap() - n / r).abs() < 1e-8);
247 }
248 }
249
250 #[test]
251 fn id_with_isolated() {
252 let g = Graph::from_edges(&[(0, 1)], false, Some(3)).unwrap();
253 assert!((inverse_degree_index(&g).unwrap() - 2.0).abs() < 1e-10);
254 }
255
256 #[test]
259 fn zco1_empty() {
260 let g = Graph::with_vertices(0);
261 assert_eq!(first_zagreb_coindex(&g).unwrap(), 0);
262 }
263
264 #[test]
265 fn zco1_single_edge() {
266 assert_eq!(first_zagreb_coindex(&single_edge()).unwrap(), 0);
267 }
268
269 #[test]
270 fn zco1_path3() {
271 assert_eq!(first_zagreb_coindex(&path3()).unwrap(), 2);
272 }
273
274 #[test]
275 fn zco1_path4() {
276 assert_eq!(first_zagreb_coindex(&path4()).unwrap(), 8);
278 }
279
280 #[test]
281 fn zco1_k3() {
282 assert_eq!(first_zagreb_coindex(&k3()).unwrap(), 0);
283 }
284
285 #[test]
286 fn zco1_k4() {
287 assert_eq!(first_zagreb_coindex(&k4()).unwrap(), 0);
288 }
289
290 #[test]
291 fn zco1_cycle4() {
292 assert_eq!(first_zagreb_coindex(&cycle4()).unwrap(), 8);
294 }
295
296 #[test]
297 fn zco1_cycle5() {
298 assert_eq!(first_zagreb_coindex(&cycle5()).unwrap(), 20);
300 }
301
302 #[test]
303 fn zco1_star5() {
304 assert_eq!(first_zagreb_coindex(&star5()).unwrap(), 12);
306 }
307
308 #[test]
309 fn zco1_paw() {
310 assert_eq!(first_zagreb_coindex(&paw()).unwrap(), 6);
312 }
313
314 #[test]
315 fn zco1_zero_for_complete() {
316 assert_eq!(first_zagreb_coindex(&k3()).unwrap(), 0);
317 assert_eq!(first_zagreb_coindex(&k4()).unwrap(), 0);
318 }
319
320 #[test]
323 fn zco2_empty() {
324 let g = Graph::with_vertices(0);
325 assert_eq!(second_zagreb_coindex(&g).unwrap(), 0);
326 }
327
328 #[test]
329 fn zco2_single_edge() {
330 assert_eq!(second_zagreb_coindex(&single_edge()).unwrap(), 0);
331 }
332
333 #[test]
334 fn zco2_path3() {
335 assert_eq!(second_zagreb_coindex(&path3()).unwrap(), 1);
337 }
338
339 #[test]
340 fn zco2_path4() {
341 assert_eq!(second_zagreb_coindex(&path4()).unwrap(), 5);
343 }
344
345 #[test]
346 fn zco2_k3() {
347 assert_eq!(second_zagreb_coindex(&k3()).unwrap(), 0);
348 }
349
350 #[test]
351 fn zco2_k4() {
352 assert_eq!(second_zagreb_coindex(&k4()).unwrap(), 0);
353 }
354
355 #[test]
356 fn zco2_cycle4() {
357 assert_eq!(second_zagreb_coindex(&cycle4()).unwrap(), 8);
359 }
360
361 #[test]
362 fn zco2_cycle5() {
363 assert_eq!(second_zagreb_coindex(&cycle5()).unwrap(), 20);
365 }
366
367 #[test]
368 fn zco2_star5() {
369 assert_eq!(second_zagreb_coindex(&star5()).unwrap(), 6);
371 }
372
373 #[test]
374 fn zco2_paw() {
375 assert_eq!(second_zagreb_coindex(&paw()).unwrap(), 4);
378 }
379
380 #[test]
381 fn zco2_zero_for_complete() {
382 assert_eq!(second_zagreb_coindex(&k3()).unwrap(), 0);
383 assert_eq!(second_zagreb_coindex(&k4()).unwrap(), 0);
384 }
385
386 #[test]
389 fn coindices_nonneg() {
390 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
391 assert!(first_zagreb_coindex(g).unwrap() >= 0);
392 assert!(second_zagreb_coindex(g).unwrap() >= 0);
393 }
394 }
395
396 #[test]
397 fn id_geq_1_for_connected() {
398 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5()] {
399 assert!(inverse_degree_index(g).unwrap() >= 1.0 - 1e-10);
400 }
401 }
402}