Skip to main content

somatize_core/
graph.rs

1//! What is connected to what.
2//!
3//! A core `Graph` is **topology only**: identities and edges. What a node does
4//! is none of its business, because creating a graph does not need to know. That
5//! map (id → implementation) lives with whoever has implementations to store.
6//! It is the reason the core depends on nothing.
7
8use std::collections::HashSet;
9use std::fmt;
10
11/// A node's name inside a graph. Its own type so no other kind of id gets
12/// through.
13#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
14#[cfg_attr(
15    feature = "serde",
16    derive(serde::Serialize, serde::Deserialize),
17    serde(transparent)
18)]
19pub struct NodeId(String);
20
21impl NodeId {
22    /// The id as text.
23    pub fn as_str(&self) -> &str {
24        &self.0
25    }
26}
27
28impl From<&str> for NodeId {
29    fn from(s: &str) -> Self {
30        Self(s.to_string())
31    }
32}
33
34impl From<String> for NodeId {
35    fn from(s: String) -> Self {
36        Self(s)
37    }
38}
39
40impl fmt::Display for NodeId {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        f.write_str(&self.0)
43    }
44}
45
46/// A directed connection between two nodes.
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct Edge {
49    /// Where it leaves from.
50    pub source: NodeId,
51    /// Where it arrives.
52    pub target: NodeId,
53}
54
55/// A directed acyclic graph of named nodes.
56///
57/// The invariant — unique ids, edges between nodes that exist, no cycles — is
58/// upheld by the constructors, so `topological_sort` cannot fail. Adjacency is
59/// computed on the fly: O(n) where it could be O(1), deliberately.
60#[derive(Debug, Default, Clone, PartialEq, Eq)]
61pub struct Graph {
62    nodes: Vec<NodeId>,
63    edges: Vec<Edge>,
64}
65
66impl Graph {
67    /// An empty graph.
68    pub fn new() -> Self {
69        Self::default()
70    }
71
72    /// Adds a node, unless the id is taken.
73    pub fn add_node(&mut self, id: impl Into<NodeId>) -> Result<&NodeId, GraphError> {
74        let id = id.into();
75        if self.contains(&id) {
76            return Err(GraphError::DuplicateNode(id));
77        }
78        self.nodes.push(id);
79        Ok(self.nodes.last().expect("just inserted it"))
80    }
81
82    /// Connects two nodes that exist, unless the edge is already there or would
83    /// close a cycle.
84    pub fn add_edge(
85        &mut self,
86        source: impl Into<NodeId>,
87        target: impl Into<NodeId>,
88    ) -> Result<&Edge, GraphError> {
89        let (source, target) = (source.into(), target.into());
90        for end in [&source, &target] {
91            if !self.contains(end) {
92                return Err(GraphError::UnknownNode(end.clone()));
93            }
94        }
95        if self
96            .edges
97            .iter()
98            .any(|e| e.source == source && e.target == target)
99        {
100            return Err(GraphError::DuplicateEdge {
101                from: source,
102                to: target,
103            });
104        }
105        if source == target || self.reaches(&target, &source) {
106            return Err(GraphError::WouldCycle {
107                from: source,
108                to: target,
109            });
110        }
111        self.edges.push(Edge { source, target });
112        Ok(self.edges.last().expect("just inserted it"))
113    }
114
115    /// A free id starting from the one you want, suffixing `_2`, `_3`, … if needed.
116    pub fn free_id(&self, wanted: &str) -> NodeId {
117        let mut candidate = NodeId::from(wanted);
118        let mut n = 1;
119        while self.contains(&candidate) {
120            n += 1;
121            candidate = NodeId::from(format!("{wanted}_{n}"));
122        }
123        candidate
124    }
125
126    /// The nodes, in insertion order.
127    pub fn nodes(&self) -> &[NodeId] {
128        &self.nodes
129    }
130
131    /// The edges, in insertion order.
132    pub fn edges(&self) -> &[Edge] {
133        &self.edges
134    }
135
136    /// How many nodes there are.
137    pub fn len(&self) -> usize {
138        self.nodes.len()
139    }
140
141    /// `true` while the graph has no nodes.
142    pub fn is_empty(&self) -> bool {
143        self.nodes.is_empty()
144    }
145
146    /// Whether the id names a node of this graph.
147    pub fn contains(&self, id: &NodeId) -> bool {
148        self.nodes.contains(id)
149    }
150
151    /// The nodes feeding into `id`, in their edges' insertion order.
152    pub fn predecessors(&self, id: &NodeId) -> Vec<&NodeId> {
153        self.edges
154            .iter()
155            .filter(|e| &e.target == id)
156            .map(|e| &e.source)
157            .collect()
158    }
159
160    /// The nodes `id` feeds into, in their edges' insertion order.
161    pub fn successors(&self, id: &NodeId) -> Vec<&NodeId> {
162        self.edges
163            .iter()
164            .filter(|e| &e.source == id)
165            .map(|e| &e.target)
166            .collect()
167    }
168
169    /// The nodes without predecessors: where execution enters.
170    pub fn roots(&self) -> Vec<&NodeId> {
171        self.nodes
172            .iter()
173            .filter(|id| !self.edges.iter().any(|e| e.target == **id))
174            .collect()
175    }
176
177    /// The nodes without successors: where it leaves.
178    pub fn leaves(&self) -> Vec<&NodeId> {
179        self.nodes
180            .iter()
181            .filter(|id| !self.edges.iter().any(|e| e.source == **id))
182            .collect()
183    }
184
185    /// The nodes in an order where each comes after its predecessors. Ties
186    /// break by insertion order, so it is deterministic.
187    pub fn topological_sort(&self) -> Vec<&NodeId> {
188        let mut pending: Vec<usize> = self
189            .nodes
190            .iter()
191            .map(|id| self.predecessors(id).len())
192            .collect();
193        let mut order = Vec::with_capacity(self.nodes.len());
194        let mut placed = HashSet::new();
195
196        while order.len() < self.nodes.len() {
197            let next = pending
198                .iter()
199                .enumerate()
200                .find(|(i, n)| **n == 0 && !placed.contains(i))
201                .map(|(i, _)| i)
202                .expect("a non-empty DAG always has a node with nothing pending");
203
204            placed.insert(next);
205            let id = &self.nodes[next];
206            for succ in self.successors(id) {
207                let i = self
208                    .nodes
209                    .iter()
210                    .position(|n| n == succ)
211                    .expect("an edge only points at nodes of the graph");
212                pending[i] -= 1;
213            }
214            order.push(id);
215        }
216        order
217    }
218
219    /// Whether `from` reaches `to` by following edges. This is the cycle check.
220    fn reaches(&self, from: &NodeId, to: &NodeId) -> bool {
221        let mut frontier = vec![from];
222        let mut seen = HashSet::new();
223        while let Some(current) = frontier.pop() {
224            if current == to {
225                return true;
226            }
227            if seen.insert(current) {
228                frontier.extend(self.successors(current));
229            }
230        }
231        false
232    }
233}
234
235/// An attempt to build a graph that cannot exist.
236///
237/// The four ways to break the invariant, returned at insertion time: there is
238/// no `validate()` afterwards, because there is no instant at which the graph
239/// is malformed.
240#[derive(Debug, Clone, PartialEq, Eq)]
241pub enum GraphError {
242    /// There is already a node with that id.
243    DuplicateNode(NodeId),
244    /// The id names no node of this graph.
245    UnknownNode(NodeId),
246    /// That edge is already there.
247    DuplicateEdge {
248        /// Source.
249        from: NodeId,
250        /// Target.
251        to: NodeId,
252    },
253    /// Adding that edge would close a cycle.
254    WouldCycle {
255        /// Source.
256        from: NodeId,
257        /// Target, which already reaches the source.
258        to: NodeId,
259    },
260}
261
262impl fmt::Display for GraphError {
263    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264        match self {
265            Self::DuplicateNode(id) => write!(f, "there is already a node called `{id}`"),
266            Self::UnknownNode(id) => write!(f, "`{id}` names no node of this graph"),
267            Self::DuplicateEdge { from, to } => {
268                write!(f, "the edge `{from}` → `{to}` already exists")
269            }
270            Self::WouldCycle { from, to } => write!(
271                f,
272                "the edge `{from}` → `{to}` would close a cycle: `{to}` already reaches `{from}`"
273            ),
274        }
275    }
276}
277
278impl std::error::Error for GraphError {}