weavatrix_graph/algo/
distance.rs1use super::undirected_neighbors::UndirectedNeighbors;
2use crate::{IndexUndirectedGraphView, Vec};
3use alloc::collections::VecDeque;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct DistanceAnalytics<Node> {
8 eccentricities: Vec<(Node, usize)>,
9 radius: usize,
10 diameter: usize,
11 center: Vec<Node>,
12 periphery: Vec<Node>,
13}
14
15impl<Node: Copy + Eq> DistanceAnalytics<Node> {
16 #[must_use]
18 pub fn eccentricities(&self) -> &[(Node, usize)] {
19 &self.eccentricities
20 }
21
22 #[must_use]
24 pub fn eccentricity(&self, node: Node) -> Option<usize> {
25 self.eccentricities
26 .iter()
27 .find_map(|&(candidate, value)| (candidate == node).then_some(value))
28 }
29
30 #[must_use]
32 pub const fn radius(&self) -> usize {
33 self.radius
34 }
35
36 #[must_use]
38 pub const fn diameter(&self) -> usize {
39 self.diameter
40 }
41
42 #[must_use]
44 pub fn center(&self) -> &[Node] {
45 &self.center
46 }
47
48 #[must_use]
50 pub fn periphery(&self) -> &[Node] {
51 &self.periphery
52 }
53
54 #[must_use]
56 pub fn into_eccentricities(self) -> Vec<(Node, usize)> {
57 self.eccentricities
58 }
59}
60
61#[must_use]
66pub fn distance_analytics<G>(graph: &G) -> Option<DistanceAnalytics<G::Node>>
67where
68 G: IndexUndirectedGraphView,
69{
70 let neighbors = UndirectedNeighbors::new(graph, |_| true);
71 neighbors.nodes().first()?;
72 let mut workspace = BfsWorkspace::<G::Node>::new(graph.node_bound());
73 let mut eccentricities = Vec::with_capacity(neighbors.nodes().len());
74 for &node in neighbors.nodes() {
75 let (value, reached) = workspace.run(&neighbors, node);
76 if reached != neighbors.nodes().len() {
77 return None;
78 }
79 eccentricities.push((node, value));
80 }
81 finish(eccentricities)
82}
83
84#[must_use]
89pub fn distance_analytics_filtered<G, F>(
90 graph: &G,
91 allows_edge: F,
92) -> Option<DistanceAnalytics<G::Node>>
93where
94 G: IndexUndirectedGraphView,
95 F: Fn(G::Edge) -> bool,
96{
97 let neighbors = UndirectedNeighbors::new(graph, allows_edge);
98 neighbors.nodes().first()?;
99 let mut workspace = BfsWorkspace::<G::Node>::new(graph.node_bound());
100 let mut eccentricities = Vec::with_capacity(neighbors.nodes().len());
101 for &node in neighbors.nodes() {
102 let (eccentricity, reached) = workspace.run(&neighbors, node);
103 if reached != neighbors.nodes().len() {
104 return None;
105 }
106 eccentricities.push((node, eccentricity));
107 }
108 finish(eccentricities)
109}
110
111fn finish<Node: Copy + Eq>(eccentricities: Vec<(Node, usize)>) -> Option<DistanceAnalytics<Node>> {
112 let radius = eccentricities.iter().map(|pair| pair.1).min()?;
113 let diameter = eccentricities.iter().map(|pair| pair.1).max()?;
114 let center = select_nodes(&eccentricities, radius);
115 let periphery = select_nodes(&eccentricities, diameter);
116 Some(DistanceAnalytics {
117 eccentricities,
118 radius,
119 diameter,
120 center,
121 periphery,
122 })
123}
124
125#[must_use]
127pub fn eccentricity<G>(graph: &G, node: G::Node) -> Option<usize>
128where
129 G: IndexUndirectedGraphView,
130{
131 if !graph.contains_node(node) || graph.node_count() == 0 {
132 return None;
133 }
134 let neighbors = UndirectedNeighbors::new(graph, |_| true);
135 let (value, reached) = BfsWorkspace::<G::Node>::new(graph.node_bound()).run(&neighbors, node);
136 (reached == neighbors.nodes().len()).then_some(value)
137}
138
139#[must_use]
141pub fn diameter<G>(graph: &G) -> Option<usize>
142where
143 G: IndexUndirectedGraphView,
144{
145 distance_analytics(graph).map(|result| result.diameter())
146}
147
148#[must_use]
150pub fn radius<G>(graph: &G) -> Option<usize>
151where
152 G: IndexUndirectedGraphView,
153{
154 distance_analytics(graph).map(|result| result.radius())
155}
156
157#[must_use]
159pub fn center<G>(graph: &G) -> Option<Vec<G::Node>>
160where
161 G: IndexUndirectedGraphView,
162{
163 distance_analytics(graph).map(|result| result.center)
164}
165
166#[must_use]
168pub fn periphery<G>(graph: &G) -> Option<Vec<G::Node>>
169where
170 G: IndexUndirectedGraphView,
171{
172 distance_analytics(graph).map(|result| result.periphery)
173}
174
175fn select_nodes<Node: Copy>(values: &[(Node, usize)], target: usize) -> Vec<Node> {
176 values
177 .iter()
178 .filter_map(|&(node, value)| (value == target).then_some(node))
179 .collect()
180}
181
182struct BfsWorkspace<Node> {
183 seen: Vec<usize>,
184 distances: Vec<usize>,
185 epoch: usize,
186 queue: VecDeque<Node>,
187}
188
189impl<Node: Copy> BfsWorkspace<Node> {
190 fn new(node_bound: usize) -> Self {
191 Self {
192 seen: vec![0; node_bound],
193 distances: vec![0; node_bound],
194 epoch: 0,
195 queue: VecDeque::new(),
196 }
197 }
198
199 fn run<G>(&mut self, graph: &UndirectedNeighbors<G>, source: G::Node) -> (usize, usize)
200 where
201 G: IndexUndirectedGraphView<Node = Node>,
202 {
203 self.next_epoch();
204 let source_slot = G::node_slot(source);
205 self.seen[source_slot] = self.epoch;
206 self.distances[source_slot] = 0;
207 self.queue.push_back(source);
208 let mut reached = 0;
209 let mut maximum = 0;
210 while let Some(node) = self.queue.pop_front() {
211 let slot = G::node_slot(node);
212 reached += 1;
213 maximum = maximum.max(self.distances[slot]);
214 for &neighbor in graph.neighbors(node) {
215 let neighbor_slot = G::node_slot(neighbor);
216 if self.seen[neighbor_slot] != self.epoch {
217 self.seen[neighbor_slot] = self.epoch;
218 self.distances[neighbor_slot] = self.distances[slot] + 1;
219 self.queue.push_back(neighbor);
220 }
221 }
222 }
223 (maximum, reached)
224 }
225
226 fn next_epoch(&mut self) {
227 self.queue.clear();
228 if self.epoch == usize::MAX {
229 self.seen.fill(0);
230 self.epoch = 1;
231 } else {
232 self.epoch += 1;
233 }
234 }
235}