1use std::collections::{BTreeMap, BTreeSet, VecDeque};
8
9use axioval_ir::{Evidence, ObjectId};
10use thiserror::Error;
11
12#[derive(Clone, Debug, Error, PartialEq, Eq)]
14pub enum TopologyError {
15 #[error("duplicate topology node `{0}`")]
17 DuplicateNode(Box<ObjectId>),
18 #[error("connection endpoint `{0}` is outside the declared topology universe")]
20 UnknownEndpoint(Box<ObjectId>),
21 #[error("topology query references unknown node `{0}`")]
23 UnknownNode(Box<ObjectId>),
24 #[error("connectivity evidence is not exact")]
26 InexactConnection,
27 #[error("self connections are invalid for `{0}`")]
29 SelfConnection(Box<ObjectId>),
30 #[error("invalid clear width `{0}`")]
32 InvalidWidth(String),
33 #[error("duplicate connection between `{left}` and `{right}`")]
35 DuplicateConnection {
36 left: Box<ObjectId>,
37 right: Box<ObjectId>,
38 },
39 #[error("connectivity evidence locator must not be blank")]
41 BlankEvidenceLocator,
42 #[error("topology coverage evidence is not exact")]
44 InexactTopologyCoverage,
45}
46
47#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct CompleteTopologyEvidence(Evidence);
50
51impl CompleteTopologyEvidence {
52 pub fn try_new(evidence: Evidence) -> Result<Self, TopologyError> {
54 if !evidence.exact {
55 return Err(TopologyError::InexactTopologyCoverage);
56 }
57 validate_evidence_locator(&evidence)?;
58 Ok(Self(evidence))
59 }
60
61 pub fn evidence(&self) -> &Evidence {
63 &self.0
64 }
65}
66
67#[derive(Clone, Debug, PartialEq)]
69pub struct VerifiedConnection {
70 left: ObjectId,
71 right: ObjectId,
72 clear_width_metres: f64,
73 evidence: Evidence,
74}
75
76impl VerifiedConnection {
77 pub fn try_new(
79 left: ObjectId,
80 right: ObjectId,
81 clear_width_metres: f64,
82 evidence: Evidence,
83 ) -> Result<Self, TopologyError> {
84 if left == right {
85 return Err(TopologyError::SelfConnection(Box::new(left)));
86 }
87 validate_width(clear_width_metres)?;
88 if !evidence.exact {
89 return Err(TopologyError::InexactConnection);
90 }
91 validate_evidence_locator(&evidence)?;
92 let (left, right) = ordered_pair(left, right);
93 Ok(Self {
94 left,
95 right,
96 clear_width_metres,
97 evidence,
98 })
99 }
100
101 pub fn left(&self) -> &ObjectId {
103 &self.left
104 }
105
106 pub fn right(&self) -> &ObjectId {
108 &self.right
109 }
110
111 pub fn clear_width_metres(&self) -> f64 {
113 self.clear_width_metres
114 }
115
116 pub fn evidence(&self) -> &Evidence {
118 &self.evidence
119 }
120}
121
122#[derive(Clone, Debug, PartialEq, Eq)]
124pub enum RouteOutcome {
125 Route(Vec<ObjectId>),
127 Unreachable,
129}
130
131#[derive(Clone, Debug, PartialEq)]
133pub struct ConnectivityGraph {
134 nodes: BTreeSet<ObjectId>,
135 adjacency: BTreeMap<ObjectId, BTreeMap<ObjectId, VerifiedConnection>>,
136 coverage: CompleteTopologyEvidence,
137}
138
139impl ConnectivityGraph {
140 pub fn try_new(
142 nodes: impl IntoIterator<Item = ObjectId>,
143 connections: impl IntoIterator<Item = VerifiedConnection>,
144 coverage: CompleteTopologyEvidence,
145 ) -> Result<Self, TopologyError> {
146 let mut node_set = BTreeSet::new();
147 for node in nodes {
148 if !node_set.insert(node.clone()) {
149 return Err(TopologyError::DuplicateNode(Box::new(node)));
150 }
151 }
152 let mut adjacency = node_set
153 .iter()
154 .cloned()
155 .map(|node| (node, BTreeMap::new()))
156 .collect::<BTreeMap<_, _>>();
157 for connection in connections {
158 add_connection(&node_set, &mut adjacency, connection)?;
159 }
160 Ok(Self {
161 nodes: node_set,
162 adjacency,
163 coverage,
164 })
165 }
166
167 pub fn coverage(&self) -> &CompleteTopologyEvidence {
169 &self.coverage
170 }
171
172 pub fn reachable_from(
174 &self,
175 origin: &ObjectId,
176 minimum_clear_width_metres: f64,
177 ) -> Result<Vec<ObjectId>, TopologyError> {
178 self.require_node(origin)?;
179 validate_width(minimum_clear_width_metres)?;
180 let mut seen = BTreeSet::from([origin.clone()]);
181 let mut queue = VecDeque::from([origin.clone()]);
182 while let Some(current) = queue.pop_front() {
183 for (neighbor, edge) in &self.adjacency[¤t] {
184 if edge.clear_width_metres >= minimum_clear_width_metres
185 && seen.insert(neighbor.clone())
186 {
187 queue.push_back(neighbor.clone());
188 }
189 }
190 }
191 Ok(seen.into_iter().collect())
192 }
193
194 pub fn route(
196 &self,
197 origin: &ObjectId,
198 destination: &ObjectId,
199 minimum_clear_width_metres: f64,
200 ) -> Result<RouteOutcome, TopologyError> {
201 self.require_node(origin)?;
202 self.require_node(destination)?;
203 validate_width(minimum_clear_width_metres)?;
204 if origin == destination {
205 return Ok(RouteOutcome::Route(vec![origin.clone()]));
206 }
207 let parents = self.search(origin, destination, minimum_clear_width_metres);
208 if !parents.contains_key(destination) {
209 return Ok(RouteOutcome::Unreachable);
210 }
211 Ok(RouteOutcome::Route(reconstruct_route(
212 origin,
213 destination,
214 &parents,
215 )))
216 }
217
218 fn require_node(&self, node: &ObjectId) -> Result<(), TopologyError> {
219 if self.nodes.contains(node) {
220 Ok(())
221 } else {
222 Err(TopologyError::UnknownNode(Box::new(node.clone())))
223 }
224 }
225
226 fn search(
227 &self,
228 origin: &ObjectId,
229 destination: &ObjectId,
230 minimum_clear_width_metres: f64,
231 ) -> BTreeMap<ObjectId, ObjectId> {
232 let mut parents = BTreeMap::new();
233 let mut seen = BTreeSet::from([origin.clone()]);
234 let mut queue = VecDeque::from([origin.clone()]);
235 while let Some(current) = queue.pop_front() {
236 for (neighbor, edge) in &self.adjacency[¤t] {
237 if edge.clear_width_metres < minimum_clear_width_metres
238 || !seen.insert(neighbor.clone())
239 {
240 continue;
241 }
242 parents.insert(neighbor.clone(), current.clone());
243 if neighbor == destination {
244 return parents;
245 }
246 queue.push_back(neighbor.clone());
247 }
248 }
249 parents
250 }
251}
252
253fn add_connection(
254 nodes: &BTreeSet<ObjectId>,
255 adjacency: &mut BTreeMap<ObjectId, BTreeMap<ObjectId, VerifiedConnection>>,
256 connection: VerifiedConnection,
257) -> Result<(), TopologyError> {
258 for endpoint in [&connection.left, &connection.right] {
259 if !nodes.contains(endpoint) {
260 return Err(TopologyError::UnknownEndpoint(Box::new(endpoint.clone())));
261 }
262 }
263 if adjacency[&connection.left].contains_key(&connection.right) {
264 return Err(TopologyError::DuplicateConnection {
265 left: Box::new(connection.left),
266 right: Box::new(connection.right),
267 });
268 }
269 adjacency
270 .get_mut(&connection.left)
271 .expect("validated node")
272 .insert(connection.right.clone(), connection.clone());
273 adjacency
274 .get_mut(&connection.right)
275 .expect("validated node")
276 .insert(connection.left.clone(), connection);
277 Ok(())
278}
279
280fn reconstruct_route(
281 origin: &ObjectId,
282 destination: &ObjectId,
283 parents: &BTreeMap<ObjectId, ObjectId>,
284) -> Vec<ObjectId> {
285 let mut route = vec![destination.clone()];
286 let mut current = destination;
287 while current != origin {
288 current = &parents[current];
289 route.push(current.clone());
290 }
291 route.reverse();
292 route
293}
294
295fn ordered_pair(left: ObjectId, right: ObjectId) -> (ObjectId, ObjectId) {
296 if left < right {
297 (left, right)
298 } else {
299 (right, left)
300 }
301}
302
303fn validate_width(width: f64) -> Result<(), TopologyError> {
304 if width.is_finite() && width >= 0.0 {
305 Ok(())
306 } else {
307 Err(TopologyError::InvalidWidth(width.to_string()))
308 }
309}
310
311fn validate_evidence_locator(evidence: &Evidence) -> Result<(), TopologyError> {
312 if evidence.locator.trim().is_empty() {
313 Err(TopologyError::BlankEvidenceLocator)
314 } else {
315 Ok(())
316 }
317}