1use crate::base::Graph;
14use std::collections::{HashMap, HashSet};
15
16#[derive(Debug, Clone, PartialEq)]
25pub struct TemporalEdge {
26 pub source: usize,
28 pub target: usize,
30 pub timestamp: f64,
32 pub weight: f64,
34}
35
36impl TemporalEdge {
37 pub fn new(source: usize, target: usize, timestamp: f64) -> Self {
39 TemporalEdge {
40 source,
41 target,
42 timestamp,
43 weight: 1.0,
44 }
45 }
46
47 pub fn with_weight(source: usize, target: usize, timestamp: f64, weight: f64) -> Self {
49 TemporalEdge {
50 source,
51 target,
52 timestamp,
53 weight,
54 }
55 }
56}
57
58#[derive(Debug, Clone)]
82pub struct TemporalGraph {
83 pub nodes: usize,
85 pub edges: Vec<TemporalEdge>,
87 sorted: bool,
89}
90
91impl TemporalGraph {
92 pub fn new(n_nodes: usize) -> Self {
94 TemporalGraph {
95 nodes: n_nodes,
96 edges: Vec::new(),
97 sorted: true,
98 }
99 }
100
101 pub fn add_edge(&mut self, edge: TemporalEdge) {
103 if let Some(last) = self.edges.last() {
104 if edge.timestamp < last.timestamp {
105 self.sorted = false;
106 }
107 }
108 self.edges.push(edge);
109 }
110
111 pub(crate) fn ensure_sorted(&mut self) {
113 if !self.sorted {
114 self.edges.sort_by(|a, b| {
115 a.timestamp
116 .partial_cmp(&b.timestamp)
117 .unwrap_or(std::cmp::Ordering::Equal)
118 });
119 self.sorted = true;
120 }
121 }
122
123 pub fn time_ordered_edges(&mut self) -> &[TemporalEdge] {
125 self.ensure_sorted();
126 &self.edges
127 }
128
129 pub fn edges_slice(&self) -> &[TemporalEdge] {
131 &self.edges
132 }
133
134 pub fn edges_in_window(&mut self, start: f64, end: f64) -> &[TemporalEdge] {
136 self.ensure_sorted();
137 let lo = self.edges.partition_point(|e| e.timestamp < start);
138 let hi = self.edges.partition_point(|e| e.timestamp < end);
139 &self.edges[lo..hi]
140 }
141
142 pub fn snapshot(&mut self, start: f64, end: f64) -> Graph<usize, f64> {
152 let window: Vec<TemporalEdge> = self.edges_in_window(start, end).to_vec();
153
154 let mut edge_weights: HashMap<(usize, usize), f64> = HashMap::new();
155 let mut active: HashSet<usize> = HashSet::new();
156
157 for e in &window {
158 active.insert(e.source);
159 active.insert(e.target);
160 let key = (e.source.min(e.target), e.source.max(e.target));
161 *edge_weights.entry(key).or_insert(0.0) += e.weight;
162 }
163
164 let mut graph: Graph<usize, f64> = Graph::new();
165 for &n in &active {
166 graph.add_node(n);
167 }
168 for ((u, v), w) in edge_weights {
169 let _ = graph.add_edge(u, v, w);
171 }
172 graph
173 }
174
175 pub fn aggregate_graph(&mut self) -> Graph<usize, f64> {
182 if self.edges.is_empty() {
183 let mut g: Graph<usize, f64> = Graph::new();
184 for i in 0..self.nodes {
185 g.add_node(i);
186 }
187 return g;
188 }
189
190 self.ensure_sorted();
191 let t_start = self.edges.first().map(|e| e.timestamp).unwrap_or(0.0);
192 let t_end = self.edges.last().map(|e| e.timestamp + 1.0).unwrap_or(1.0);
193 self.snapshot(t_start, t_end)
194 }
195
196 pub fn temporal_neighbors(
204 &mut self,
205 node: usize,
206 t_start: f64,
207 t_end: f64,
208 ) -> Vec<(usize, f64)> {
209 let window: Vec<TemporalEdge> = self.edges_in_window(t_start, t_end).to_vec();
210
211 let mut first_contact: HashMap<usize, f64> = HashMap::new();
212 for e in &window {
213 if e.source == node {
214 first_contact
215 .entry(e.target)
216 .and_modify(|t| *t = t.min(e.timestamp))
217 .or_insert(e.timestamp);
218 } else if e.target == node {
219 first_contact
220 .entry(e.source)
221 .and_modify(|t| *t = t.min(e.timestamp))
222 .or_insert(e.timestamp);
223 }
224 }
225
226 first_contact.into_iter().collect()
227 }
228
229 pub fn foremost_path(
238 &mut self,
239 source: usize,
240 target: usize,
241 t_start: f64,
242 t_end: f64,
243 ) -> Option<Vec<usize>> {
244 if source >= self.nodes || target >= self.nodes {
245 return None;
246 }
247 if source == target {
248 return Some(vec![source]);
249 }
250
251 self.ensure_sorted();
252
253 let mut arrival = vec![f64::INFINITY; self.nodes];
254 arrival[source] = t_start;
255 let mut pred: Vec<Option<usize>> = vec![None; self.nodes];
256
257 use std::cmp::Reverse;
259 use std::collections::BinaryHeap;
260
261 #[derive(PartialEq)]
262 struct State(ordered_float::OrderedFloat<f64>, usize);
263 impl Eq for State {}
264 impl PartialOrd for State {
265 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
266 Some(self.cmp(other))
267 }
268 }
269 impl Ord for State {
270 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
271 Reverse(self.0)
273 .cmp(&Reverse(other.0))
274 .then(self.1.cmp(&other.1))
275 }
276 }
277
278 let mut heap = BinaryHeap::new();
279 heap.push(State(ordered_float::OrderedFloat(t_start), source));
280
281 while let Some(State(arr_of, node)) = heap.pop() {
282 let arr = arr_of.0;
283 if arr > arrival[node] {
284 continue; }
286 if node == target {
287 let mut path = Vec::new();
288 let mut cur = target;
289 loop {
290 path.push(cur);
291 match pred[cur] {
292 None => break,
293 Some(p) => cur = p,
294 }
295 }
296 path.reverse();
297 return Some(path);
298 }
299
300 let lo = self.edges.partition_point(|e| e.timestamp < arr);
302 for e in &self.edges[lo..] {
303 if e.timestamp >= t_end {
304 break;
305 }
306 let nbr = if e.source == node {
307 e.target
308 } else if e.target == node {
309 e.source
310 } else {
311 continue;
312 };
313 if e.timestamp < arrival[nbr] {
314 arrival[nbr] = e.timestamp;
315 pred[nbr] = Some(node);
316 heap.push(State(ordered_float::OrderedFloat(e.timestamp), nbr));
317 }
318 }
319 }
320
321 None
322 }
323
324 pub fn n_edges(&self) -> usize {
326 self.edges.len()
327 }
328}
329
330#[cfg(test)]
335mod tests {
336 use super::*;
337
338 fn make_chain() -> TemporalGraph {
339 let mut tg = TemporalGraph::new(4);
340 tg.add_edge(TemporalEdge::new(0, 1, 1.0));
341 tg.add_edge(TemporalEdge::new(1, 2, 2.0));
342 tg.add_edge(TemporalEdge::new(2, 3, 3.0));
343 tg
344 }
345
346 #[test]
347 fn test_snapshot_basic() {
348 let mut tg = make_chain();
349 let snap = tg.snapshot(0.5, 2.5);
350 assert_eq!(snap.edge_count(), 2);
352 }
353
354 #[test]
355 fn test_time_ordered_edges_sorted() {
356 let mut tg = TemporalGraph::new(3);
357 tg.add_edge(TemporalEdge::new(0, 1, 5.0));
358 tg.add_edge(TemporalEdge::new(1, 2, 2.0));
359 tg.add_edge(TemporalEdge::new(0, 2, 8.0));
360
361 let edges = tg.time_ordered_edges();
362 let ts: Vec<f64> = edges.iter().map(|e| e.timestamp).collect();
363 assert!(ts.windows(2).all(|w| w[0] <= w[1]));
364 }
365
366 #[test]
367 fn test_foremost_path() {
368 let mut tg = make_chain();
369 let path = tg.foremost_path(0, 3, 0.0, 10.0);
370 assert!(path.is_some());
371 let p = path.expect("path should exist");
372 assert_eq!(p.first(), Some(&0));
373 assert_eq!(p.last(), Some(&3));
374 }
375
376 #[test]
377 fn test_foremost_path_no_time_travel() {
378 let mut tg = make_chain();
379 let path = tg.foremost_path(0, 3, 0.0, 2.5);
381 assert!(path.is_none());
382 }
383
384 #[test]
385 fn test_aggregate_graph() {
386 let mut tg = make_chain();
387 let agg = tg.aggregate_graph();
388 assert_eq!(agg.node_count(), 4);
389 assert_eq!(agg.edge_count(), 3);
390 }
391
392 #[test]
393 fn test_temporal_neighbors() {
394 let mut tg = make_chain();
395 let nbrs = tg.temporal_neighbors(1, 0.0, 10.0);
396 let nbr_ids: Vec<usize> = nbrs.iter().map(|(n, _)| *n).collect();
397 assert!(nbr_ids.contains(&0));
398 assert!(nbr_ids.contains(&2));
399 }
400
401 #[test]
402 fn test_edge_with_weight() {
403 let e = TemporalEdge::with_weight(0, 1, 5.0, 2.5);
404 assert_eq!(e.weight, 2.5);
405 }
406}