1use std::{
4 collections::{BTreeMap, BTreeSet},
5 hash::Hash,
6};
7
8use crate::{FingerprintValue, ValueFingerprint};
9
10#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
12pub enum EdgeClass<C> {
13 Data,
15 Control,
17 Custom(C),
19}
20
21#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
23pub enum GraphDirection {
24 Forward,
26 Reverse,
28}
29
30#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
32pub enum Boundary {
33 Internal,
35 Input,
37 Output,
39 InputOutput,
41}
42
43impl Boundary {
44 fn is_input(self) -> bool {
45 matches!(self, Self::Input | Self::InputOutput)
46 }
47
48 fn is_output(self) -> bool {
49 matches!(self, Self::Output | Self::InputOutput)
50 }
51}
52
53#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
55pub struct NodeSpec<N, L> {
56 pub id: N,
58 pub location: L,
60 pub boundary: Boundary,
62}
63
64#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
66pub struct EdgeSpec<E, N, C> {
67 pub id: E,
69 pub source: N,
71 pub target: N,
73 pub class: EdgeClass<C>,
75 pub direction: GraphDirection,
77}
78
79#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
81pub struct Node<N, L> {
82 id: N,
83 location: L,
84 boundary: Boundary,
85}
86
87impl<N, L> Node<N, L> {
88 pub fn id(&self) -> &N {
90 &self.id
91 }
92
93 pub fn location(&self) -> &L {
95 &self.location
96 }
97
98 pub fn boundary(&self) -> Boundary {
100 self.boundary
101 }
102}
103
104#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
106pub struct Edge<E, N, C> {
107 id: E,
108 source: N,
109 target: N,
110 class: EdgeClass<C>,
111 direction: GraphDirection,
112}
113
114impl<E, N, C> Edge<E, N, C> {
115 pub fn id(&self) -> &E {
117 &self.id
118 }
119
120 pub fn source(&self) -> &N {
122 &self.source
123 }
124
125 pub fn target(&self) -> &N {
127 &self.target
128 }
129
130 pub fn class(&self) -> &EdgeClass<C> {
132 &self.class
133 }
134
135 pub fn direction(&self) -> GraphDirection {
137 self.direction
138 }
139
140 pub(super) fn predecessor_and_successor(&self) -> (&N, &N) {
141 match self.direction {
142 GraphDirection::Forward => (&self.source, &self.target),
143 GraphDirection::Reverse => (&self.target, &self.source),
144 }
145 }
146}
147
148#[derive(Clone, Debug, Eq, PartialEq)]
150pub enum GraphBuildError<N, E> {
151 DuplicateNode(N),
153 DuplicateEdge(E),
155 MissingNode {
157 edge: E,
159 node: N,
161 },
162 InputHasPredecessor(N),
164 OutputHasSuccessor(N),
166 Empty,
168}
169
170#[derive(Clone, Debug, Eq, PartialEq)]
177pub struct DataflowGraph<N, E, L, C> {
178 nodes: BTreeMap<N, Node<N, L>>,
179 edges: BTreeMap<E, Edge<E, N, C>>,
180 predecessors: BTreeMap<N, Box<[E]>>,
181 successors: BTreeMap<N, Box<[E]>>,
182 fingerprint: ValueFingerprint,
183}
184
185impl<N, E, L, C> DataflowGraph<N, E, L, C>
186where
187 N: Clone + Hash + Ord,
188 E: Clone + Hash + Ord,
189 L: Hash + Ord,
190 C: Hash + Ord,
191{
192 pub fn build(
194 nodes: impl IntoIterator<Item = NodeSpec<N, L>>,
195 edges: impl IntoIterator<Item = EdgeSpec<E, N, C>>,
196 ) -> Result<Self, GraphBuildError<N, E>> {
197 let mut frozen_nodes = BTreeMap::new();
198 for node in nodes {
199 let id = node.id.clone();
200 let node = Node {
201 id: node.id,
202 location: node.location,
203 boundary: node.boundary,
204 };
205 if frozen_nodes.insert(id.clone(), node).is_some() {
206 return Err(GraphBuildError::DuplicateNode(id));
207 }
208 }
209 if frozen_nodes.is_empty() {
210 return Err(GraphBuildError::Empty);
211 }
212
213 let mut frozen_edges = BTreeMap::new();
214 for edge in edges {
215 let id = edge.id.clone();
216 for endpoint in [&edge.source, &edge.target] {
217 if !frozen_nodes.contains_key(endpoint) {
218 return Err(GraphBuildError::MissingNode {
219 edge: id,
220 node: endpoint.clone(),
221 });
222 }
223 }
224 let edge = Edge {
225 id: edge.id,
226 source: edge.source,
227 target: edge.target,
228 class: edge.class,
229 direction: edge.direction,
230 };
231 if frozen_edges.insert(id.clone(), edge).is_some() {
232 return Err(GraphBuildError::DuplicateEdge(id));
233 }
234 }
235
236 let mut predecessors = frozen_nodes
237 .keys()
238 .cloned()
239 .map(|id| (id, BTreeSet::new()))
240 .collect::<BTreeMap<_, _>>();
241 let mut successors = predecessors.clone();
242 for (edge_id, edge) in &frozen_edges {
243 let (predecessor, successor) = edge.predecessor_and_successor();
244 successors
245 .get_mut(predecessor)
246 .expect("validated edge source exists")
247 .insert(edge_id.clone());
248 predecessors
249 .get_mut(successor)
250 .expect("validated edge target exists")
251 .insert(edge_id.clone());
252 }
253 for (id, node) in &frozen_nodes {
254 if node.boundary.is_input() && !predecessors[id].is_empty() {
255 return Err(GraphBuildError::InputHasPredecessor(id.clone()));
256 }
257 if node.boundary.is_output() && !successors[id].is_empty() {
258 return Err(GraphBuildError::OutputHasSuccessor(id.clone()));
259 }
260 }
261
262 let fingerprint = (&frozen_nodes, &frozen_edges).incremental_fingerprint();
263 Ok(Self {
264 nodes: frozen_nodes,
265 edges: frozen_edges,
266 predecessors: freeze_index(predecessors),
267 successors: freeze_index(successors),
268 fingerprint,
269 })
270 }
271
272 pub fn node(&self, id: &N) -> Option<&Node<N, L>> {
274 self.nodes.get(id)
275 }
276
277 pub fn edge(&self, id: &E) -> Option<&Edge<E, N, C>> {
279 self.edges.get(id)
280 }
281
282 pub fn nodes(&self) -> impl ExactSizeIterator<Item = &Node<N, L>> {
284 self.nodes.values()
285 }
286
287 pub fn edges(&self) -> impl ExactSizeIterator<Item = &Edge<E, N, C>> {
289 self.edges.values()
290 }
291
292 pub fn predecessors(&self, node: &N) -> Option<&[E]> {
294 self.predecessors.get(node).map(Box::as_ref)
295 }
296
297 pub fn successors(&self, node: &N) -> Option<&[E]> {
299 self.successors.get(node).map(Box::as_ref)
300 }
301
302 pub fn fingerprint(&self) -> ValueFingerprint {
304 self.fingerprint
305 }
306}
307
308fn freeze_index<K: Ord, V: Ord>(index: BTreeMap<K, BTreeSet<V>>) -> BTreeMap<K, Box<[V]>> {
309 index
310 .into_iter()
311 .map(|(key, values)| (key, values.into_iter().collect()))
312 .collect()
313}
314
315pub trait LocatedGraphAdapter {
321 type NodeId: Clone + Hash + Ord;
323 type EdgeId: Clone + Hash + Ord;
325 type Location: Hash + Ord;
327 type Class: Hash + Ord;
329
330 fn nodes(&self) -> Vec<NodeSpec<Self::NodeId, Self::Location>>;
332
333 fn edges(&self) -> Vec<EdgeSpec<Self::EdgeId, Self::NodeId, Self::Class>>;
335
336 fn build_graph(&self) -> AdapterBuildResult<Self> {
338 DataflowGraph::build(self.nodes(), self.edges())
339 }
340}
341
342pub type AdaptedGraph<A> = DataflowGraph<
344 <A as LocatedGraphAdapter>::NodeId,
345 <A as LocatedGraphAdapter>::EdgeId,
346 <A as LocatedGraphAdapter>::Location,
347 <A as LocatedGraphAdapter>::Class,
348>;
349
350pub type AdapterBuildResult<A> = Result<
352 AdaptedGraph<A>,
353 GraphBuildError<<A as LocatedGraphAdapter>::NodeId, <A as LocatedGraphAdapter>::EdgeId>,
354>;
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359
360 fn node(id: u8, boundary: Boundary) -> NodeSpec<u8, (u8, u8)> {
361 NodeSpec {
362 id,
363 location: (id, id + 1),
364 boundary,
365 }
366 }
367
368 fn edge(id: u8, source: u8, target: u8) -> EdgeSpec<u8, u8, &'static str> {
369 EdgeSpec {
370 id,
371 source,
372 target,
373 class: EdgeClass::Data,
374 direction: GraphDirection::Forward,
375 }
376 }
377
378 #[test]
379 fn insertion_order_does_not_change_structure_or_fingerprint() {
380 let left = DataflowGraph::build(
381 [
382 node(1, Boundary::Input),
383 node(2, Boundary::Internal),
384 node(3, Boundary::Output),
385 ],
386 [edge(10, 1, 2), edge(20, 2, 3)],
387 )
388 .unwrap();
389 let right = DataflowGraph::build(
390 [
391 node(3, Boundary::Output),
392 node(1, Boundary::Input),
393 node(2, Boundary::Internal),
394 ],
395 [edge(20, 2, 3), edge(10, 1, 2)],
396 )
397 .unwrap();
398
399 assert_eq!(left, right);
400 assert_eq!(left.fingerprint(), right.fingerprint());
401 assert_eq!(left.successors(&1), Some([10].as_slice()));
402 assert_eq!(left.predecessors(&3), Some([20].as_slice()));
403 }
404
405 #[test]
406 fn rejects_duplicate_missing_and_invalid_boundary_declarations() {
407 assert_eq!(
408 DataflowGraph::<_, u8, _, &str>::build(
409 [node(1, Boundary::Internal), node(1, Boundary::Internal)],
410 [],
411 ),
412 Err(GraphBuildError::DuplicateNode(1))
413 );
414 assert!(matches!(
415 DataflowGraph::build([node(1, Boundary::Internal)], [edge(7, 1, 2)]),
416 Err(GraphBuildError::MissingNode { edge: 7, node: 2 })
417 ));
418 assert_eq!(
419 DataflowGraph::build(
420 [node(1, Boundary::Internal), node(2, Boundary::Input)],
421 [edge(7, 1, 2)],
422 ),
423 Err(GraphBuildError::InputHasPredecessor(2))
424 );
425 assert_eq!(
426 DataflowGraph::build(
427 [node(1, Boundary::Output), node(2, Boundary::Internal)],
428 [edge(7, 1, 2)],
429 ),
430 Err(GraphBuildError::OutputHasSuccessor(1))
431 );
432 }
433
434 #[test]
435 fn graph_public_surface_remains_representation_neutral() {
436 let source = include_str!("graph.rs");
437 let public_surface = source
438 .lines()
439 .filter(|line| line.trim_start().starts_with("pub "))
440 .collect::<String>();
441 for forbidden in ["Machine", "Jvm", "JVM", "LocatedCode"] {
442 assert!(
443 !public_surface.contains(forbidden),
444 "public graph surface names {forbidden}"
445 );
446 }
447 }
448}