use std::{
collections::{BTreeMap, BTreeSet},
hash::Hash,
};
use crate::{FingerprintValue, ValueFingerprint};
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum EdgeClass<C> {
Data,
Control,
Custom(C),
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum GraphDirection {
Forward,
Reverse,
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Boundary {
Internal,
Input,
Output,
InputOutput,
}
impl Boundary {
fn is_input(self) -> bool {
matches!(self, Self::Input | Self::InputOutput)
}
fn is_output(self) -> bool {
matches!(self, Self::Output | Self::InputOutput)
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct NodeSpec<N, L> {
pub id: N,
pub location: L,
pub boundary: Boundary,
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct EdgeSpec<E, N, C> {
pub id: E,
pub source: N,
pub target: N,
pub class: EdgeClass<C>,
pub direction: GraphDirection,
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Node<N, L> {
id: N,
location: L,
boundary: Boundary,
}
impl<N, L> Node<N, L> {
pub fn id(&self) -> &N {
&self.id
}
pub fn location(&self) -> &L {
&self.location
}
pub fn boundary(&self) -> Boundary {
self.boundary
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Edge<E, N, C> {
id: E,
source: N,
target: N,
class: EdgeClass<C>,
direction: GraphDirection,
}
impl<E, N, C> Edge<E, N, C> {
pub fn id(&self) -> &E {
&self.id
}
pub fn source(&self) -> &N {
&self.source
}
pub fn target(&self) -> &N {
&self.target
}
pub fn class(&self) -> &EdgeClass<C> {
&self.class
}
pub fn direction(&self) -> GraphDirection {
self.direction
}
pub(super) fn predecessor_and_successor(&self) -> (&N, &N) {
match self.direction {
GraphDirection::Forward => (&self.source, &self.target),
GraphDirection::Reverse => (&self.target, &self.source),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GraphBuildError<N, E> {
DuplicateNode(N),
DuplicateEdge(E),
MissingNode {
edge: E,
node: N,
},
InputHasPredecessor(N),
OutputHasSuccessor(N),
Empty,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DataflowGraph<N, E, L, C> {
nodes: BTreeMap<N, Node<N, L>>,
edges: BTreeMap<E, Edge<E, N, C>>,
predecessors: BTreeMap<N, Box<[E]>>,
successors: BTreeMap<N, Box<[E]>>,
fingerprint: ValueFingerprint,
}
impl<N, E, L, C> DataflowGraph<N, E, L, C>
where
N: Clone + Hash + Ord,
E: Clone + Hash + Ord,
L: Hash + Ord,
C: Hash + Ord,
{
pub fn build(
nodes: impl IntoIterator<Item = NodeSpec<N, L>>,
edges: impl IntoIterator<Item = EdgeSpec<E, N, C>>,
) -> Result<Self, GraphBuildError<N, E>> {
let mut frozen_nodes = BTreeMap::new();
for node in nodes {
let id = node.id.clone();
let node = Node {
id: node.id,
location: node.location,
boundary: node.boundary,
};
if frozen_nodes.insert(id.clone(), node).is_some() {
return Err(GraphBuildError::DuplicateNode(id));
}
}
if frozen_nodes.is_empty() {
return Err(GraphBuildError::Empty);
}
let mut frozen_edges = BTreeMap::new();
for edge in edges {
let id = edge.id.clone();
for endpoint in [&edge.source, &edge.target] {
if !frozen_nodes.contains_key(endpoint) {
return Err(GraphBuildError::MissingNode {
edge: id,
node: endpoint.clone(),
});
}
}
let edge = Edge {
id: edge.id,
source: edge.source,
target: edge.target,
class: edge.class,
direction: edge.direction,
};
if frozen_edges.insert(id.clone(), edge).is_some() {
return Err(GraphBuildError::DuplicateEdge(id));
}
}
let mut predecessors = frozen_nodes
.keys()
.cloned()
.map(|id| (id, BTreeSet::new()))
.collect::<BTreeMap<_, _>>();
let mut successors = predecessors.clone();
for (edge_id, edge) in &frozen_edges {
let (predecessor, successor) = edge.predecessor_and_successor();
successors
.get_mut(predecessor)
.expect("validated edge source exists")
.insert(edge_id.clone());
predecessors
.get_mut(successor)
.expect("validated edge target exists")
.insert(edge_id.clone());
}
for (id, node) in &frozen_nodes {
if node.boundary.is_input() && !predecessors[id].is_empty() {
return Err(GraphBuildError::InputHasPredecessor(id.clone()));
}
if node.boundary.is_output() && !successors[id].is_empty() {
return Err(GraphBuildError::OutputHasSuccessor(id.clone()));
}
}
let fingerprint = (&frozen_nodes, &frozen_edges).incremental_fingerprint();
Ok(Self {
nodes: frozen_nodes,
edges: frozen_edges,
predecessors: freeze_index(predecessors),
successors: freeze_index(successors),
fingerprint,
})
}
pub fn node(&self, id: &N) -> Option<&Node<N, L>> {
self.nodes.get(id)
}
pub fn edge(&self, id: &E) -> Option<&Edge<E, N, C>> {
self.edges.get(id)
}
pub fn nodes(&self) -> impl ExactSizeIterator<Item = &Node<N, L>> {
self.nodes.values()
}
pub fn edges(&self) -> impl ExactSizeIterator<Item = &Edge<E, N, C>> {
self.edges.values()
}
pub fn predecessors(&self, node: &N) -> Option<&[E]> {
self.predecessors.get(node).map(Box::as_ref)
}
pub fn successors(&self, node: &N) -> Option<&[E]> {
self.successors.get(node).map(Box::as_ref)
}
pub fn fingerprint(&self) -> ValueFingerprint {
self.fingerprint
}
}
fn freeze_index<K: Ord, V: Ord>(index: BTreeMap<K, BTreeSet<V>>) -> BTreeMap<K, Box<[V]>> {
index
.into_iter()
.map(|(key, values)| (key, values.into_iter().collect()))
.collect()
}
pub trait LocatedGraphAdapter {
type NodeId: Clone + Hash + Ord;
type EdgeId: Clone + Hash + Ord;
type Location: Hash + Ord;
type Class: Hash + Ord;
fn nodes(&self) -> Vec<NodeSpec<Self::NodeId, Self::Location>>;
fn edges(&self) -> Vec<EdgeSpec<Self::EdgeId, Self::NodeId, Self::Class>>;
fn build_graph(&self) -> AdapterBuildResult<Self> {
DataflowGraph::build(self.nodes(), self.edges())
}
}
pub type AdaptedGraph<A> = DataflowGraph<
<A as LocatedGraphAdapter>::NodeId,
<A as LocatedGraphAdapter>::EdgeId,
<A as LocatedGraphAdapter>::Location,
<A as LocatedGraphAdapter>::Class,
>;
pub type AdapterBuildResult<A> = Result<
AdaptedGraph<A>,
GraphBuildError<<A as LocatedGraphAdapter>::NodeId, <A as LocatedGraphAdapter>::EdgeId>,
>;
#[cfg(test)]
mod tests {
use super::*;
fn node(id: u8, boundary: Boundary) -> NodeSpec<u8, (u8, u8)> {
NodeSpec {
id,
location: (id, id + 1),
boundary,
}
}
fn edge(id: u8, source: u8, target: u8) -> EdgeSpec<u8, u8, &'static str> {
EdgeSpec {
id,
source,
target,
class: EdgeClass::Data,
direction: GraphDirection::Forward,
}
}
#[test]
fn insertion_order_does_not_change_structure_or_fingerprint() {
let left = DataflowGraph::build(
[
node(1, Boundary::Input),
node(2, Boundary::Internal),
node(3, Boundary::Output),
],
[edge(10, 1, 2), edge(20, 2, 3)],
)
.unwrap();
let right = DataflowGraph::build(
[
node(3, Boundary::Output),
node(1, Boundary::Input),
node(2, Boundary::Internal),
],
[edge(20, 2, 3), edge(10, 1, 2)],
)
.unwrap();
assert_eq!(left, right);
assert_eq!(left.fingerprint(), right.fingerprint());
assert_eq!(left.successors(&1), Some([10].as_slice()));
assert_eq!(left.predecessors(&3), Some([20].as_slice()));
}
#[test]
fn rejects_duplicate_missing_and_invalid_boundary_declarations() {
assert_eq!(
DataflowGraph::<_, u8, _, &str>::build(
[node(1, Boundary::Internal), node(1, Boundary::Internal)],
[],
),
Err(GraphBuildError::DuplicateNode(1))
);
assert!(matches!(
DataflowGraph::build([node(1, Boundary::Internal)], [edge(7, 1, 2)]),
Err(GraphBuildError::MissingNode { edge: 7, node: 2 })
));
assert_eq!(
DataflowGraph::build(
[node(1, Boundary::Internal), node(2, Boundary::Input)],
[edge(7, 1, 2)],
),
Err(GraphBuildError::InputHasPredecessor(2))
);
assert_eq!(
DataflowGraph::build(
[node(1, Boundary::Output), node(2, Boundary::Internal)],
[edge(7, 1, 2)],
),
Err(GraphBuildError::OutputHasSuccessor(1))
);
}
#[test]
fn graph_public_surface_remains_representation_neutral() {
let source = include_str!("graph.rs");
let public_surface = source
.lines()
.filter(|line| line.trim_start().starts_with("pub "))
.collect::<String>();
for forbidden in ["Machine", "Jvm", "JVM", "LocatedCode"] {
assert!(
!public_surface.contains(forbidden),
"public graph surface names {forbidden}"
);
}
}
}