rust_igraph/algorithms/properties/
sombor_index.rs1#![allow(
11 clippy::cast_possible_truncation,
12 clippy::cast_precision_loss,
13 clippy::many_single_char_names,
14 clippy::needless_range_loop,
15 clippy::too_many_lines
16)]
17
18use crate::core::{Graph, IgraphResult};
19
20pub fn sombor_index(graph: &Graph) -> IgraphResult<f64> {
36 let mut so = 0.0_f64;
37
38 for (u, v) in graph.edges() {
39 if u == v {
40 continue;
41 }
42 let du = graph.degree(u)? as f64;
43 let dv = graph.degree(v)? as f64;
44 so += (du * du + dv * dv).sqrt();
45 }
46
47 Ok(so)
48}
49
50pub fn reduced_sombor_index(graph: &Graph) -> IgraphResult<f64> {
68 let mut so_red = 0.0_f64;
69
70 for (u, v) in graph.edges() {
71 if u == v {
72 continue;
73 }
74 let du = graph.degree(u)?;
75 let dv = graph.degree(v)?;
76 let a = if du > 0 { (du - 1) as f64 } else { 0.0 };
77 let b = if dv > 0 { (dv - 1) as f64 } else { 0.0 };
78 so_red += (a * a + b * b).sqrt();
79 }
80
81 Ok(so_red)
82}
83
84pub fn average_sombor_index(graph: &Graph) -> IgraphResult<f64> {
100 let m = graph.ecount();
101 if m == 0 {
102 return Ok(0.0);
103 }
104 let so = sombor_index(graph)?;
105 Ok(so / m as f64)
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 fn single_edge() -> Graph {
113 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
114 }
115
116 fn path3() -> Graph {
117 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
118 }
119
120 fn path4() -> Graph {
121 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
122 }
123
124 fn k3() -> Graph {
125 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
126 }
127
128 fn k4() -> Graph {
129 Graph::from_edges(
130 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
131 false,
132 Some(4),
133 )
134 .unwrap()
135 }
136
137 fn cycle4() -> Graph {
138 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
139 }
140
141 fn cycle5() -> Graph {
142 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
143 }
144
145 fn star5() -> Graph {
146 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
147 }
148
149 fn paw() -> Graph {
150 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
151 }
152
153 #[test]
156 fn so_empty() {
157 let g = Graph::with_vertices(0);
158 assert!((sombor_index(&g).unwrap()).abs() < 1e-10);
159 }
160
161 #[test]
162 fn so_isolated() {
163 let g = Graph::with_vertices(5);
164 assert!((sombor_index(&g).unwrap()).abs() < 1e-10);
165 }
166
167 #[test]
168 fn so_single_edge() {
169 let expected = std::f64::consts::SQRT_2;
171 assert!((sombor_index(&single_edge()).unwrap() - expected).abs() < 1e-10);
172 }
173
174 #[test]
175 fn so_path3() {
176 let expected = 2.0 * 5.0_f64.sqrt();
179 assert!((sombor_index(&path3()).unwrap() - expected).abs() < 1e-10);
180 }
181
182 #[test]
183 fn so_path4() {
184 let expected = 2.0 * 5.0_f64.sqrt() + 2.0 * std::f64::consts::SQRT_2;
187 assert!((sombor_index(&path4()).unwrap() - expected).abs() < 1e-10);
188 }
189
190 #[test]
191 fn so_k3() {
192 let expected = 6.0 * std::f64::consts::SQRT_2;
194 assert!((sombor_index(&k3()).unwrap() - expected).abs() < 1e-10);
195 }
196
197 #[test]
198 fn so_k4() {
199 let expected = 18.0 * std::f64::consts::SQRT_2;
201 assert!((sombor_index(&k4()).unwrap() - expected).abs() < 1e-10);
202 }
203
204 #[test]
205 fn so_cycle4() {
206 let expected = 8.0 * std::f64::consts::SQRT_2;
208 assert!((sombor_index(&cycle4()).unwrap() - expected).abs() < 1e-10);
209 }
210
211 #[test]
212 fn so_cycle5() {
213 let expected = 10.0 * std::f64::consts::SQRT_2;
215 assert!((sombor_index(&cycle5()).unwrap() - expected).abs() < 1e-10);
216 }
217
218 #[test]
219 fn so_star5() {
220 let expected = 4.0 * 17.0_f64.sqrt();
222 assert!((sombor_index(&star5()).unwrap() - expected).abs() < 1e-10);
223 }
224
225 #[test]
226 fn so_paw() {
227 let expected = 2.0 * std::f64::consts::SQRT_2 + 2.0 * 13.0_f64.sqrt() + 10.0_f64.sqrt();
230 assert!((sombor_index(&paw()).unwrap() - expected).abs() < 1e-10);
231 }
232
233 #[test]
234 fn so_regular_formula() {
235 for g in &[k3(), k4(), cycle4(), cycle5()] {
237 let m = g.ecount() as f64;
238 let r = g.degree(0).unwrap() as f64;
239 let expected = m * r * std::f64::consts::SQRT_2;
240 assert!((sombor_index(g).unwrap() - expected).abs() < 1e-8);
241 }
242 }
243
244 #[test]
247 fn so_red_empty() {
248 let g = Graph::with_vertices(0);
249 assert!((reduced_sombor_index(&g).unwrap()).abs() < 1e-10);
250 }
251
252 #[test]
253 fn so_red_single_edge() {
254 assert!((reduced_sombor_index(&single_edge()).unwrap()).abs() < 1e-10);
256 }
257
258 #[test]
259 fn so_red_path3() {
260 assert!((reduced_sombor_index(&path3()).unwrap() - 2.0).abs() < 1e-10);
263 }
264
265 #[test]
266 fn so_red_path4() {
267 let expected = 2.0 + std::f64::consts::SQRT_2;
270 assert!((reduced_sombor_index(&path4()).unwrap() - expected).abs() < 1e-10);
271 }
272
273 #[test]
274 fn so_red_k3() {
275 let expected = 3.0 * std::f64::consts::SQRT_2;
277 assert!((reduced_sombor_index(&k3()).unwrap() - expected).abs() < 1e-10);
278 }
279
280 #[test]
281 fn so_red_k4() {
282 let expected = 12.0 * std::f64::consts::SQRT_2;
284 assert!((reduced_sombor_index(&k4()).unwrap() - expected).abs() < 1e-10);
285 }
286
287 #[test]
288 fn so_red_cycle4() {
289 let expected = 4.0 * std::f64::consts::SQRT_2;
291 assert!((reduced_sombor_index(&cycle4()).unwrap() - expected).abs() < 1e-10);
292 }
293
294 #[test]
295 fn so_red_cycle5() {
296 let expected = 5.0 * std::f64::consts::SQRT_2;
298 assert!((reduced_sombor_index(&cycle5()).unwrap() - expected).abs() < 1e-10);
299 }
300
301 #[test]
302 fn so_red_star5() {
303 assert!((reduced_sombor_index(&star5()).unwrap() - 12.0).abs() < 1e-10);
305 }
306
307 #[test]
308 fn so_red_paw() {
309 let expected = std::f64::consts::SQRT_2 + 2.0 * 5.0_f64.sqrt() + 2.0;
312 assert!((reduced_sombor_index(&paw()).unwrap() - expected).abs() < 1e-10);
313 }
314
315 #[test]
316 fn so_red_regular_formula() {
317 for g in &[k3(), k4(), cycle4(), cycle5()] {
319 let m = g.ecount() as f64;
320 let r = g.degree(0).unwrap() as f64;
321 let expected = m * (r - 1.0) * std::f64::consts::SQRT_2;
322 assert!((reduced_sombor_index(g).unwrap() - expected).abs() < 1e-8);
323 }
324 }
325
326 #[test]
327 fn so_red_leq_so() {
328 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
329 assert!(reduced_sombor_index(g).unwrap() <= sombor_index(g).unwrap() + 1e-10);
330 }
331 }
332
333 #[test]
336 fn so_avg_empty() {
337 let g = Graph::with_vertices(0);
338 assert!((average_sombor_index(&g).unwrap()).abs() < 1e-10);
339 }
340
341 #[test]
342 fn so_avg_isolated() {
343 let g = Graph::with_vertices(5);
344 assert!((average_sombor_index(&g).unwrap()).abs() < 1e-10);
345 }
346
347 #[test]
348 fn so_avg_single_edge() {
349 let expected = std::f64::consts::SQRT_2;
351 assert!((average_sombor_index(&single_edge()).unwrap() - expected).abs() < 1e-10);
352 }
353
354 #[test]
355 fn so_avg_k3() {
356 let expected = 2.0 * std::f64::consts::SQRT_2;
358 assert!((average_sombor_index(&k3()).unwrap() - expected).abs() < 1e-10);
359 }
360
361 #[test]
362 fn so_avg_k4() {
363 let expected = 3.0 * std::f64::consts::SQRT_2;
365 assert!((average_sombor_index(&k4()).unwrap() - expected).abs() < 1e-10);
366 }
367
368 #[test]
369 fn so_avg_regular_is_r_sqrt2() {
370 for g in &[k3(), k4(), cycle4(), cycle5()] {
372 let r = g.degree(0).unwrap() as f64;
373 let expected = r * std::f64::consts::SQRT_2;
374 assert!((average_sombor_index(g).unwrap() - expected).abs() < 1e-8);
375 }
376 }
377
378 #[test]
379 fn so_avg_consistency() {
380 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
381 let m = g.ecount();
382 if m > 0 {
383 let so = sombor_index(g).unwrap();
384 let so_avg = average_sombor_index(g).unwrap();
385 assert!((so_avg - so / m as f64).abs() < 1e-10);
386 }
387 }
388 }
389
390 #[test]
393 fn all_nonneg() {
394 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
395 assert!(sombor_index(g).unwrap() >= -1e-10);
396 assert!(reduced_sombor_index(g).unwrap() >= -1e-10);
397 assert!(average_sombor_index(g).unwrap() >= -1e-10);
398 }
399 }
400
401 #[test]
402 fn so_geq_degree_sum() {
403 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5()] {
406 let m = g.ecount() as f64;
407 assert!(sombor_index(g).unwrap() >= m * std::f64::consts::SQRT_2 - 1e-10);
408 }
409 }
410}