rust_igraph/algorithms/properties/
sombor_variants.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 elliptic_sombor_index(graph: &Graph) -> IgraphResult<f64> {
38 let mut eso = 0.0_f64;
39
40 for (u, v) in graph.edges() {
41 if u == v {
42 continue;
43 }
44 let du = graph.degree(u)? as f64;
45 let dv = graph.degree(v)? as f64;
46 eso += (du + dv) * (du * du + dv * dv).sqrt();
47 }
48
49 Ok(eso)
50}
51
52pub fn modified_sombor_index(graph: &Graph) -> IgraphResult<f64> {
70 let mut mso = 0.0_f64;
71
72 for (u, v) in graph.edges() {
73 if u == v {
74 continue;
75 }
76 let du = graph.degree(u)? as f64;
77 let dv = graph.degree(v)? as f64;
78 let s = du * du + dv * dv;
79 if s > 0.0 {
80 mso += 1.0 / s.sqrt();
81 }
82 }
83
84 Ok(mso)
85}
86
87pub fn sombor_coindex(graph: &Graph) -> IgraphResult<f64> {
107 let n = graph.vcount();
108 let mut degrees = Vec::with_capacity(n as usize);
109 for v in 0..n {
110 degrees.push(graph.degree(v)? as f64);
111 }
112
113 let mut total_sum = 0.0_f64;
114 for u in 0..n {
115 for v in (u + 1)..n {
116 let du = degrees[u as usize];
117 let dv = degrees[v as usize];
118 total_sum += (du * du + dv * dv).sqrt();
119 }
120 }
121
122 let mut edge_sum = 0.0_f64;
123 for (u, v) in graph.edges() {
124 if u == v {
125 continue;
126 }
127 let du = degrees[u as usize];
128 let dv = degrees[v as usize];
129 edge_sum += (du * du + dv * dv).sqrt();
130 }
131
132 Ok(total_sum - edge_sum)
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 fn single_edge() -> Graph {
140 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
141 }
142
143 fn path3() -> Graph {
144 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
145 }
146
147 fn path4() -> Graph {
148 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
149 }
150
151 fn k3() -> Graph {
152 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
153 }
154
155 fn k4() -> Graph {
156 Graph::from_edges(
157 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
158 false,
159 Some(4),
160 )
161 .unwrap()
162 }
163
164 fn cycle4() -> Graph {
165 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
166 }
167
168 fn cycle5() -> Graph {
169 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
170 }
171
172 fn star5() -> Graph {
173 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
174 }
175
176 fn paw() -> Graph {
177 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
178 }
179
180 #[test]
183 fn eso_empty() {
184 let g = Graph::with_vertices(0);
185 assert!(elliptic_sombor_index(&g).unwrap().abs() < 1e-10);
186 }
187
188 #[test]
189 fn eso_isolated() {
190 let g = Graph::with_vertices(5);
191 assert!(elliptic_sombor_index(&g).unwrap().abs() < 1e-10);
192 }
193
194 #[test]
195 fn eso_single_edge() {
196 let expected = 2.0 * 2.0_f64.sqrt();
198 assert!((elliptic_sombor_index(&single_edge()).unwrap() - expected).abs() < 1e-10);
199 }
200
201 #[test]
202 fn eso_path3() {
203 let expected = 6.0 * 5.0_f64.sqrt();
206 assert!((elliptic_sombor_index(&path3()).unwrap() - expected).abs() < 1e-10);
207 }
208
209 #[test]
210 fn eso_k3() {
211 let expected = 12.0 * 8.0_f64.sqrt();
213 assert!((elliptic_sombor_index(&k3()).unwrap() - expected).abs() < 1e-10);
214 }
215
216 #[test]
217 fn eso_k4() {
218 let expected = 36.0 * 18.0_f64.sqrt();
220 assert!((elliptic_sombor_index(&k4()).unwrap() - expected).abs() < 1e-10);
221 }
222
223 #[test]
224 fn eso_cycle4() {
225 let expected = 16.0 * 8.0_f64.sqrt();
227 assert!((elliptic_sombor_index(&cycle4()).unwrap() - expected).abs() < 1e-10);
228 }
229
230 #[test]
231 fn eso_cycle5() {
232 let expected = 20.0 * 8.0_f64.sqrt();
234 assert!((elliptic_sombor_index(&cycle5()).unwrap() - expected).abs() < 1e-10);
235 }
236
237 #[test]
238 fn eso_star5() {
239 let expected = 20.0 * 17.0_f64.sqrt();
241 assert!((elliptic_sombor_index(&star5()).unwrap() - expected).abs() < 1e-10);
242 }
243
244 #[test]
245 fn eso_paw() {
246 let expected = 4.0 * 8.0_f64.sqrt() + 10.0 * 13.0_f64.sqrt() + 4.0 * 10.0_f64.sqrt();
251 assert!((elliptic_sombor_index(&paw()).unwrap() - expected).abs() < 1e-10);
252 }
253
254 #[test]
255 fn eso_regular_formula() {
256 for g in &[k3(), k4(), cycle4(), cycle5()] {
258 let m = g.ecount() as f64;
259 let r = g.degree(0).unwrap() as f64;
260 let expected = 2.0 * m * r * r * 2.0_f64.sqrt();
261 assert!((elliptic_sombor_index(g).unwrap() - expected).abs() < 1e-8);
262 }
263 }
264
265 #[test]
268 fn mso_empty() {
269 let g = Graph::with_vertices(0);
270 assert!(modified_sombor_index(&g).unwrap().abs() < 1e-10);
271 }
272
273 #[test]
274 fn mso_isolated() {
275 let g = Graph::with_vertices(5);
276 assert!(modified_sombor_index(&g).unwrap().abs() < 1e-10);
277 }
278
279 #[test]
280 fn mso_single_edge() {
281 let expected = 1.0 / 2.0_f64.sqrt();
283 assert!((modified_sombor_index(&single_edge()).unwrap() - expected).abs() < 1e-10);
284 }
285
286 #[test]
287 fn mso_path3() {
288 let expected = 2.0 / 5.0_f64.sqrt();
290 assert!((modified_sombor_index(&path3()).unwrap() - expected).abs() < 1e-10);
291 }
292
293 #[test]
294 fn mso_k3() {
295 let expected = 3.0 / 8.0_f64.sqrt();
297 assert!((modified_sombor_index(&k3()).unwrap() - expected).abs() < 1e-10);
298 }
299
300 #[test]
301 fn mso_k4() {
302 let expected = 6.0 / 18.0_f64.sqrt();
304 assert!((modified_sombor_index(&k4()).unwrap() - expected).abs() < 1e-10);
305 }
306
307 #[test]
308 fn mso_cycle4() {
309 let expected = 4.0 / 8.0_f64.sqrt();
311 assert!((modified_sombor_index(&cycle4()).unwrap() - expected).abs() < 1e-10);
312 }
313
314 #[test]
315 fn mso_star5() {
316 let expected = 4.0 / 17.0_f64.sqrt();
318 assert!((modified_sombor_index(&star5()).unwrap() - expected).abs() < 1e-10);
319 }
320
321 #[test]
322 fn mso_paw() {
323 let expected = 1.0 / 8.0_f64.sqrt() + 2.0 / 13.0_f64.sqrt() + 1.0 / 10.0_f64.sqrt();
325 assert!((modified_sombor_index(&paw()).unwrap() - expected).abs() < 1e-10);
326 }
327
328 #[test]
329 fn mso_regular_formula() {
330 for g in &[k3(), k4(), cycle4(), cycle5()] {
332 let m = g.ecount() as f64;
333 let r = g.degree(0).unwrap() as f64;
334 let expected = m / (r * 2.0_f64.sqrt());
335 assert!((modified_sombor_index(g).unwrap() - expected).abs() < 1e-8);
336 }
337 }
338
339 #[test]
340 fn mso_reciprocal_of_sombor() {
341 for g in &[k3(), k4(), cycle4(), cycle5()] {
345 let m = g.ecount() as f64;
346 let so = crate::sombor_index(g).unwrap();
347 let mso = modified_sombor_index(g).unwrap();
348 assert!((so * mso - m * m).abs() < 1e-6);
349 }
350 }
351
352 #[test]
355 fn sco_empty() {
356 let g = Graph::with_vertices(0);
357 assert!(sombor_coindex(&g).unwrap().abs() < 1e-10);
358 }
359
360 #[test]
361 fn sco_isolated() {
362 let g = Graph::with_vertices(5);
364 assert!(sombor_coindex(&g).unwrap().abs() < 1e-10);
365 }
366
367 #[test]
368 fn sco_single_edge() {
369 assert!(sombor_coindex(&single_edge()).unwrap().abs() < 1e-10);
371 }
372
373 #[test]
374 fn sco_k3() {
375 assert!(sombor_coindex(&k3()).unwrap().abs() < 1e-10);
377 }
378
379 #[test]
380 fn sco_k4() {
381 assert!(sombor_coindex(&k4()).unwrap().abs() < 1e-10);
382 }
383
384 #[test]
385 fn sco_path3() {
386 let expected = 2.0_f64.sqrt();
388 assert!((sombor_coindex(&path3()).unwrap() - expected).abs() < 1e-10);
389 }
390
391 #[test]
392 fn sco_path4() {
393 let expected = 2.0 * 5.0_f64.sqrt() + 2.0_f64.sqrt();
396 assert!((sombor_coindex(&path4()).unwrap() - expected).abs() < 1e-10);
397 }
398
399 #[test]
400 fn sco_cycle4() {
401 let expected = 2.0 * 8.0_f64.sqrt();
404 assert!((sombor_coindex(&cycle4()).unwrap() - expected).abs() < 1e-10);
405 }
406
407 #[test]
408 fn sco_star5() {
409 let expected = 6.0 * 2.0_f64.sqrt();
412 assert!((sombor_coindex(&star5()).unwrap() - expected).abs() < 1e-10);
413 }
414
415 #[test]
416 fn sco_paw() {
417 let expected = 2.0 * 5.0_f64.sqrt();
421 assert!((sombor_coindex(&paw()).unwrap() - expected).abs() < 1e-10);
422 }
423
424 #[test]
425 fn sco_complement_relation() {
426 for g in &[path3(), k3(), cycle4(), star5(), paw()] {
428 let so = crate::sombor_index(g).unwrap();
429 let sco = sombor_coindex(g).unwrap();
430 let n = g.vcount();
431 let mut total = 0.0_f64;
432 for u in 0..n {
433 for v in (u + 1)..n {
434 let du = g.degree(u).unwrap() as f64;
435 let dv = g.degree(v).unwrap() as f64;
436 total += (du * du + dv * dv).sqrt();
437 }
438 }
439 assert!((so + sco - total).abs() < 1e-8);
440 }
441 }
442
443 #[test]
446 fn all_positive_for_nontrivial() {
447 for g in &[path3(), k3(), k4(), cycle4(), star5(), paw()] {
448 assert!(elliptic_sombor_index(g).unwrap() > 0.0);
449 assert!(modified_sombor_index(g).unwrap() > 0.0);
450 }
451 }
452
453 #[test]
454 fn eso_ge_sombor() {
455 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
459 let eso = elliptic_sombor_index(g).unwrap();
460 let so = crate::sombor_index(g).unwrap();
461 assert!(eso >= so - 1e-10);
462 }
463 }
464}