uni_algo/algo/algorithms/
graph_metrics.rs1use crate::algo::GraphProjection;
17use crate::algo::algorithms::Algorithm;
18use rayon::prelude::*;
19use std::cmp::Reverse;
20use std::collections::BinaryHeap;
21use uni_common::core::id::Vid;
22
23pub struct GraphMetrics;
24
25#[derive(Debug, Clone, Default)]
26pub struct GraphMetricsConfig {}
27
28pub struct GraphMetricsResult {
29 pub diameter: f64,
30 pub radius: f64,
31 pub eccentricities: Vec<(Vid, f64)>,
32 pub center: Vec<Vid>,
33 pub periphery: Vec<Vid>,
34}
35
36impl Algorithm for GraphMetrics {
37 type Config = GraphMetricsConfig;
38 type Result = GraphMetricsResult;
39
40 fn name() -> &'static str {
41 "graph_metrics"
42 }
43
44 fn run(graph: &GraphProjection, _config: Self::Config) -> Self::Result {
45 let n = graph.vertex_count();
46 if n == 0 {
47 return GraphMetricsResult {
48 diameter: 0.0,
49 radius: 0.0,
50 eccentricities: Vec::new(),
51 center: Vec::new(),
52 periphery: Vec::new(),
53 };
54 }
55
56 let eccentricities: Vec<f64> = (0..n)
58 .into_par_iter()
59 .map(|i| compute_eccentricity(graph, i as u32))
60 .collect();
61
62 let valid_eccs: Vec<f64> = eccentricities
73 .iter()
74 .copied()
75 .filter(|&e| e < f64::INFINITY)
76 .collect();
77
78 if valid_eccs.is_empty() {
79 return GraphMetricsResult {
80 diameter: 0.0,
81 radius: 0.0,
82 eccentricities: Vec::new(),
83 center: Vec::new(),
84 periphery: Vec::new(),
85 };
86 }
87
88 let diameter = valid_eccs.iter().fold(0.0f64, |a, &b| a.max(b));
89 let radius = valid_eccs.iter().fold(f64::INFINITY, |a, &b| a.min(b));
90
91 let mut center = Vec::new();
92 let mut periphery = Vec::new();
93 let mut ecc_mapped = Vec::new();
94
95 for (i, &ecc) in eccentricities.iter().enumerate() {
96 if ecc < f64::INFINITY {
97 ecc_mapped.push((graph.to_vid(i as u32), ecc));
98 if (ecc - radius).abs() < 1e-9 {
99 center.push(graph.to_vid(i as u32));
100 }
101 if (ecc - diameter).abs() < 1e-9 {
102 periphery.push(graph.to_vid(i as u32));
103 }
104 }
105 }
106
107 GraphMetricsResult {
108 diameter,
109 radius,
110 eccentricities: ecc_mapped,
111 center,
112 periphery,
113 }
114 }
115}
116
117fn compute_eccentricity(graph: &GraphProjection, start: u32) -> f64 {
118 let n = graph.vertex_count();
120 let mut dist = vec![f64::INFINITY; n];
121 let mut heap = BinaryHeap::new();
122
123 dist[start as usize] = 0.0;
124 heap.push(Reverse((0.0f64.to_bits(), start)));
125
126 let mut max_dist = 0.0;
127
128 while let Some(Reverse((d_bits, u))) = heap.pop() {
129 let d = f64::from_bits(d_bits);
130 if d > dist[u as usize] {
131 continue;
132 }
133 if d > max_dist {
134 max_dist = d;
135 }
136
137 for (i, &v) in graph.out_neighbors(u).iter().enumerate() {
138 let weight = if graph.has_weights() {
139 graph.out_weight(u, i)
140 } else {
141 1.0
142 };
143 let new_dist = d + weight;
144 if new_dist < dist[v as usize] {
145 dist[v as usize] = new_dist;
146 heap.push(Reverse((new_dist.to_bits(), v)));
147 }
148 }
149 }
150
151 max_dist
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use crate::algo::test_utils::build_test_graph;
163
164 #[test]
165 fn test_metrics_line() {
166 let vids = (0..4).map(Vid::from).collect();
174 let edges = vec![
176 (Vid::from(0), Vid::from(1)),
177 (Vid::from(1), Vid::from(0)),
178 (Vid::from(1), Vid::from(2)),
179 (Vid::from(2), Vid::from(1)),
180 (Vid::from(2), Vid::from(3)),
181 (Vid::from(3), Vid::from(2)),
182 ];
183 let graph = build_test_graph(vids, edges);
184
185 let result = GraphMetrics::run(&graph, GraphMetricsConfig::default());
186
187 assert_eq!(result.diameter, 3.0);
188 assert_eq!(result.radius, 2.0);
189
190 assert!(result.center.contains(&Vid::from(1)));
191 assert!(result.center.contains(&Vid::from(2)));
192 assert_eq!(result.center.len(), 2);
193
194 assert!(result.periphery.contains(&Vid::from(0)));
195 assert!(result.periphery.contains(&Vid::from(3)));
196 assert_eq!(result.periphery.len(), 2);
197 }
198}