antecedent_graph/
temporal.rs1use antecedent_core::TemporalNodeKey;
6use antecedent_core::{Lag, VariableId};
7
8use crate::algo::bfs_reaches;
9use crate::error::GraphError;
10use crate::types::{DenseNodeId, MarkedEdge, NodeRef};
11use crate::workspace::GraphWorkspace;
12
13#[derive(Clone, Debug)]
15pub struct TemporalDag {
16 nodes: Vec<NodeRef>,
17 children: Vec<Vec<DenseNodeId>>,
18 parents: Vec<Vec<DenseNodeId>>,
19 insert_ws: GraphWorkspace,
20}
21
22impl TemporalDag {
23 #[must_use]
25 pub fn empty() -> Self {
26 Self {
27 nodes: Vec::new(),
28 children: Vec::new(),
29 parents: Vec::new(),
30 insert_ws: GraphWorkspace::default(),
31 }
32 }
33
34 #[must_use]
36 pub fn node_count(&self) -> usize {
37 self.nodes.len()
38 }
39
40 #[must_use]
42 pub fn is_empty(&self) -> bool {
43 self.nodes.is_empty()
44 }
45
46 #[must_use]
48 pub fn nodes(&self) -> &[NodeRef] {
49 &self.nodes
50 }
51
52 pub fn add_node(&mut self, node: NodeRef) -> Result<DenseNodeId, GraphError> {
58 match node {
59 NodeRef::Lagged { .. } => {}
60 _ => {
61 return Err(GraphError::InvalidEndpoints {
62 message: "TemporalDag accepts only Lagged nodes",
63 });
64 }
65 }
66 let id = u32::try_from(self.nodes.len()).map_err(|_| GraphError::TooManyNodes)?;
67 self.nodes.push(node);
68 self.children.push(Vec::new());
69 self.parents.push(Vec::new());
70 Ok(DenseNodeId::from_raw(id))
71 }
72
73 pub fn add_lagged(
79 &mut self,
80 variable: VariableId,
81 lag: Lag,
82 ) -> Result<DenseNodeId, GraphError> {
83 self.add_node(NodeRef::Lagged { variable, lag })
84 }
85
86 pub fn insert_directed(
103 &mut self,
104 from: DenseNodeId,
105 to: DenseNodeId,
106 ) -> Result<(), GraphError> {
107 self.validate_node(from)?;
108 self.validate_node(to)?;
109 if let (
110 NodeRef::Lagged { variable: v1, lag: l1 },
111 NodeRef::Lagged { variable: v2, lag: l2 },
112 ) = (self.nodes[from.as_usize()], self.nodes[to.as_usize()])
113 {
114 if v1 == v2 && l1 == l2 && l1.is_contemporaneous() {
115 return Err(GraphError::ContemporaneousSelfEdge { variable: v1 });
116 }
117 crate::types::reject_future_to_past(&self.nodes, from, to)?;
118 }
119 if self.children[from.as_usize()].contains(&to) {
120 return Err(GraphError::DuplicateEdge { from: from.raw(), to: to.raw() });
121 }
122 let mut ws = core::mem::take(&mut self.insert_ws);
123 let cycle = bfs_reaches(&self.children, to, from, &mut ws);
124 self.insert_ws = ws;
125 if cycle {
126 return Err(GraphError::Cycle { from: from.raw(), to: to.raw() });
127 }
128 self.children[from.as_usize()].push(to);
129 self.parents[to.as_usize()].push(from);
130 Ok(())
131 }
132
133 #[must_use]
135 pub fn children(&self, id: DenseNodeId) -> &[DenseNodeId] {
136 &self.children[id.as_usize()]
137 }
138
139 pub fn edges(&self) -> impl Iterator<Item = MarkedEdge> + '_ {
141 self.children.iter().enumerate().flat_map(|(i, kids)| {
142 let from = DenseNodeId::from_raw(u32::try_from(i).expect("node fit"));
143 kids.iter().map(move |&to| MarkedEdge::directed(from, to))
144 })
145 }
146
147 #[must_use]
149 pub fn reaches(&self, from: DenseNodeId, to: DenseNodeId) -> bool {
150 let mut ws = GraphWorkspace::default();
151 bfs_reaches(&self.children, from, to, &mut ws)
152 }
153
154 pub fn reaches_with(
156 &self,
157 from: DenseNodeId,
158 to: DenseNodeId,
159 ws: &mut GraphWorkspace,
160 ) -> bool {
161 bfs_reaches(&self.children, from, to, ws)
162 }
163
164 #[must_use]
166 pub fn temporal_key(&self, id: DenseNodeId) -> Option<TemporalNodeKey> {
167 match self.nodes.get(id.as_usize())? {
168 NodeRef::Lagged { variable, lag } => {
169 let offset = -i32::try_from(lag.raw()).ok()?;
170 Some(TemporalNodeKey { variable: *variable, offset })
171 }
172 _ => None,
173 }
174 }
175
176 fn validate_node(&self, id: DenseNodeId) -> Result<(), GraphError> {
177 if id.as_usize() >= self.node_count() {
178 Err(GraphError::UnknownNode { id: id.raw() })
179 } else {
180 Ok(())
181 }
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn rejects_contemporaneous_self_edge() {
191 let mut g = TemporalDag::empty();
192 let n = g.add_lagged(VariableId::from_raw(0), Lag::CONTEMPORANEOUS).unwrap();
193 assert!(matches!(g.insert_directed(n, n), Err(GraphError::ContemporaneousSelfEdge { .. })));
194 }
195
196 #[test]
197 fn allows_lagged_self_edge() {
198 let mut g = TemporalDag::empty();
199 let past = g.add_lagged(VariableId::from_raw(0), Lag::from_raw(1)).unwrap();
200 let now = g.add_lagged(VariableId::from_raw(0), Lag::CONTEMPORANEOUS).unwrap();
201 g.insert_directed(past, now).unwrap();
202 assert!(g.reaches(past, now));
203 }
204
205 #[test]
206 fn rejects_future_to_past_edge() {
207 let mut g = TemporalDag::empty();
208 let past = g.add_lagged(VariableId::from_raw(0), Lag::from_raw(1)).unwrap();
209 let now = g.add_lagged(VariableId::from_raw(1), Lag::CONTEMPORANEOUS).unwrap();
210 assert!(matches!(g.insert_directed(now, past), Err(GraphError::FutureToPast { .. })));
212 }
213}