1use crate::base::{EdgeWeight, Graph, IndexType, Node};
7use crate::error::{GraphError, Result};
8use std::collections::{BTreeMap, HashMap, HashSet};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct TimeInstant {
13 pub time: u64,
15}
16
17impl TimeInstant {
18 pub fn new(time: u64) -> Self {
20 TimeInstant { time }
21 }
22
23 pub fn value(&self) -> u64 {
25 self.time
26 }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct TimeInterval {
32 pub start: TimeInstant,
34 pub end: TimeInstant,
36}
37
38impl TimeInterval {
39 pub fn new(start: u64, end: u64) -> Result<Self> {
41 if start >= end {
42 return Err(GraphError::InvalidGraph(
43 "Start time must be before end time".to_string(),
44 ));
45 }
46 Ok(TimeInterval {
47 start: TimeInstant::new(start),
48 end: TimeInstant::new(end),
49 })
50 }
51
52 pub fn contains(&self, time: TimeInstant) -> bool {
54 time >= self.start && time < self.end
55 }
56
57 pub fn overlaps(&self, other: &TimeInterval) -> bool {
59 self.start < other.end && other.start < self.end
60 }
61
62 pub fn duration(&self) -> u64 {
64 self.end.time - self.start.time
65 }
66
67 pub fn intersection(&self, other: &TimeInterval) -> Option<TimeInterval> {
69 if !self.overlaps(other) {
70 return None;
71 }
72
73 let start = self.start.max(other.start);
74 let end = self.end.min(other.end);
75
76 if start < end {
77 Some(TimeInterval { start, end })
78 } else {
79 None
80 }
81 }
82}
83
84#[derive(Debug, Clone)]
86pub struct TemporalEdge<N: Node, E: EdgeWeight> {
87 pub source: N,
89 pub target: N,
91 pub weight: E,
93 pub interval: TimeInterval,
95}
96
97#[derive(Debug, Clone)]
99pub struct TemporalGraph<N: Node, E: EdgeWeight, Ix: IndexType = u32> {
100 nodes: HashSet<N>,
102 edges: BTreeMap<TimeInstant, Vec<TemporalEdge<N, E>>>,
104 node_intervals: HashMap<N, TimeInterval>,
106 edge_counter: usize,
108 _phantom: std::marker::PhantomData<Ix>,
110}
111
112impl<N: Node + std::fmt::Debug, E: EdgeWeight, Ix: IndexType> Default for TemporalGraph<N, E, Ix> {
113 fn default() -> Self {
114 Self::new()
115 }
116}
117
118impl<N: Node + std::fmt::Debug, E: EdgeWeight, Ix: IndexType> TemporalGraph<N, E, Ix> {
119 pub fn new() -> Self {
121 TemporalGraph {
122 nodes: HashSet::new(),
123 edges: BTreeMap::new(),
124 node_intervals: HashMap::new(),
125 edge_counter: 0,
126 _phantom: std::marker::PhantomData,
127 }
128 }
129
130 pub fn add_node(&mut self, node: N, interval: TimeInterval) {
132 self.nodes.insert(node.clone());
133 self.node_intervals.insert(node, interval);
134 }
135
136 pub fn add_edge(
138 &mut self,
139 source: N,
140 target: N,
141 weight: E,
142 interval: TimeInterval,
143 ) -> Result<usize> {
144 if let Some(source_interval) = self.node_intervals.get(&source) {
146 if source_interval.intersection(&interval).is_none() {
147 return Err(GraphError::InvalidGraph(
148 "Source node not active during edge interval".to_string(),
149 ));
150 }
151 } else {
152 self.add_node(source.clone(), interval);
154 }
155
156 if let Some(target_interval) = self.node_intervals.get(&target) {
157 if target_interval.intersection(&interval).is_none() {
158 return Err(GraphError::InvalidGraph(
159 "Target node not active during edge interval".to_string(),
160 ));
161 }
162 } else {
163 self.add_node(target.clone(), interval);
165 }
166
167 let edge = TemporalEdge {
168 source,
169 target,
170 weight,
171 interval,
172 };
173
174 self.edges.entry(interval.start).or_default().push(edge);
176
177 let edge_id = self.edge_counter;
178 self.edge_counter += 1;
179 Ok(edge_id)
180 }
181
182 pub fn snapshot_at(&self, time: TimeInstant) -> Graph<N, E, Ix>
184 where
185 N: Clone,
186 E: Clone,
187 {
188 let mut snapshot = Graph::new();
189
190 for (node, interval) in &self.node_intervals {
192 if interval.contains(time) {
193 snapshot.add_node(node.clone());
194 }
195 }
196
197 for edges in self.edges.values() {
199 for edge in edges {
200 if edge.interval.contains(time) {
201 snapshot
202 .add_edge(
203 edge.source.clone(),
204 edge.target.clone(),
205 edge.weight.clone(),
206 )
207 .unwrap();
208 }
209 }
210 }
211
212 snapshot
213 }
214
215 pub fn edges_in_interval(&self, interval: TimeInterval) -> Vec<&TemporalEdge<N, E>> {
217 let mut result = Vec::new();
218
219 for edges in self.edges.values() {
220 for edge in edges {
221 if edge.interval.overlaps(&interval) {
222 result.push(edge);
223 }
224 }
225 }
226
227 result
228 }
229
230 pub fn change_times(&self) -> Vec<TimeInstant> {
232 let mut times = HashSet::new();
233
234 for interval in self.node_intervals.values() {
236 times.insert(interval.start);
237 times.insert(interval.end);
238 }
239
240 for edges in self.edges.values() {
242 for edge in edges {
243 times.insert(edge.interval.start);
244 times.insert(edge.interval.end);
245 }
246 }
247
248 let mut times: Vec<_> = times.into_iter().collect();
249 times.sort();
250 times
251 }
252
253 pub fn active_interval(&self) -> Option<TimeInterval> {
255 if self.nodes.is_empty() {
256 return None;
257 }
258
259 let change_times = self.change_times();
260 if change_times.len() < 2 {
261 return None;
262 }
263
264 let start = change_times[0];
265 let end = change_times[change_times.len() - 1];
266
267 TimeInterval::new(start.time, end.time).ok()
268 }
269
270 pub fn node_count_at(&self, time: TimeInstant) -> usize {
272 self.node_intervals
273 .values()
274 .filter(|interval| interval.contains(time))
275 .count()
276 }
277
278 pub fn edge_count_at(&self, time: TimeInstant) -> usize {
280 let mut count = 0;
281 for edges in self.edges.values() {
282 for edge in edges {
283 if edge.interval.contains(time) {
284 count += 1;
285 }
286 }
287 }
288 count
289 }
290
291 pub fn nodes(&self) -> impl Iterator<Item = &N> {
293 self.nodes.iter()
294 }
295
296 pub fn temporal_edges(&self) -> Vec<&TemporalEdge<N, E>> {
298 let mut result = Vec::new();
299 for edges in self.edges.values() {
300 result.extend(edges.iter());
301 }
302 result
303 }
304
305 pub fn are_connected_at(&self, node1: &N, node2: &N, time: TimeInstant) -> bool {
307 for edges in self.edges.values() {
308 for edge in edges {
309 if edge.interval.contains(time)
310 && ((edge.source == *node1 && edge.target == *node2)
311 || (edge.source == *node2 && edge.target == *node1))
312 {
313 return true;
314 }
315 }
316 }
317 false
318 }
319
320 pub fn temporal_paths(
322 &self,
323 source: &N,
324 target: &N,
325 start_time: TimeInstant,
326 max_duration: u64,
327 ) -> Vec<TemporalPath<N, E>>
328 where
329 N: Clone + Ord,
330 E: Clone,
331 {
332 let mut paths = Vec::new();
333 let end_time = TimeInstant::new(start_time.time + max_duration);
334
335 let mut queue = std::collections::VecDeque::new();
337 queue.push_back(TemporalPath {
338 nodes: vec![source.clone()],
339 edges: Vec::new(),
340 total_weight: None,
341 start_time,
342 end_time: start_time,
343 });
344
345 while let Some(current_path) = queue.pop_front() {
346 let current_node = current_path.nodes.last().unwrap();
347
348 if current_node == target {
349 paths.push(current_path);
350 continue;
351 }
352
353 if current_path.end_time >= end_time {
354 continue;
355 }
356
357 for edges in self.edges.values() {
359 for edge in edges {
360 if edge.source == *current_node
361 && edge.interval.start >= current_path.end_time
362 && edge.interval.start <= end_time
363 && !current_path.nodes.contains(&edge.target)
364 {
365 let mut new_path = current_path.clone();
366 new_path.nodes.push(edge.target.clone());
367 new_path.edges.push(edge.clone());
368 new_path.end_time = edge.interval.end;
369
370 queue.push_back(new_path);
371 }
372 }
373 }
374 }
375
376 paths
377 }
378}
379
380#[derive(Debug, Clone)]
382pub struct TemporalPath<N: Node, E: EdgeWeight> {
383 pub nodes: Vec<N>,
385 pub edges: Vec<TemporalEdge<N, E>>,
387 pub total_weight: Option<E>,
389 pub start_time: TimeInstant,
391 pub end_time: TimeInstant,
393}
394
395impl<N: Node, E: EdgeWeight> TemporalPath<N, E> {
396 pub fn duration(&self) -> u64 {
398 self.end_time.time - self.start_time.time
399 }
400
401 pub fn hop_count(&self) -> usize {
403 self.edges.len()
404 }
405}
406
407#[allow(dead_code)]
411pub fn temporal_reachability<N, E, Ix>(
412 temporal_graph: &TemporalGraph<N, E, Ix>,
413 source: &N,
414 start_time: TimeInstant,
415 max_duration: u64,
416) -> HashSet<N>
417where
418 N: Node + Clone + Ord + std::fmt::Debug,
419 E: EdgeWeight + Clone,
420 Ix: IndexType,
421{
422 let mut reachable = HashSet::new();
423 let mut visited = HashSet::new();
424 let mut queue = std::collections::VecDeque::new();
425
426 queue.push_back((source.clone(), start_time));
427 visited.insert((source.clone(), start_time));
428 reachable.insert(source.clone());
429
430 let end_time = TimeInstant::new(start_time.time + max_duration);
431
432 while let Some((current_node, current_time)) = queue.pop_front() {
433 if current_time >= end_time {
434 continue;
435 }
436
437 for edges in temporal_graph.edges.values() {
439 for edge in edges {
440 if edge.source == current_node
441 && edge.interval.start >= current_time
442 && edge.interval.start <= end_time
443 {
444 let next_time = edge.interval.end;
445 let next_node = edge.target.clone();
446
447 if !visited.contains(&(next_node.clone(), next_time)) {
448 visited.insert((next_node.clone(), next_time));
449 reachable.insert(next_node.clone());
450 queue.push_back((next_node, next_time));
451 }
452 }
453 }
454 }
455 }
456
457 reachable
458}
459
460#[allow(dead_code)]
462pub fn temporal_betweenness_centrality<N, E, Ix>(
463 temporal_graph: &TemporalGraph<N, E, Ix>,
464 time_window: TimeInterval,
465) -> HashMap<N, f64>
466where
467 N: Node + Clone + Ord + std::fmt::Debug,
468 E: EdgeWeight + Clone,
469 Ix: IndexType,
470{
471 let mut centrality = HashMap::new();
472
473 for node in temporal_graph.nodes() {
475 centrality.insert(node.clone(), 0.0);
476 }
477
478 let nodes: Vec<N> = temporal_graph.nodes().cloned().collect();
479
480 for i in 0..nodes.len() {
482 for j in (i + 1)..nodes.len() {
483 let source = &nodes[i];
484 let target = &nodes[j];
485
486 let paths = temporal_graph.temporal_paths(
487 source,
488 target,
489 time_window.start,
490 time_window.duration(),
491 );
492
493 if !paths.is_empty() {
494 for path in &paths {
496 for k in 1..(path.nodes.len() - 1) {
497 let intermediate = &path.nodes[k];
498 *centrality.get_mut(intermediate).unwrap() += 1.0 / paths.len() as f64;
499 }
500 }
501 }
502 }
503 }
504
505 centrality
506}
507
508#[cfg(test)]
509mod tests {
510 use super::*;
511
512 #[test]
513 fn test_time_instant() {
514 let t1 = TimeInstant::new(100);
515 let t2 = TimeInstant::new(200);
516
517 assert!(t1 < t2);
518 assert_eq!(t1.value(), 100);
519 assert_eq!(t2.value(), 200);
520 }
521
522 #[test]
523 fn test_time_interval() {
524 let interval = TimeInterval::new(100, 200).unwrap();
525
526 assert_eq!(interval.duration(), 100);
527 assert!(interval.contains(TimeInstant::new(150)));
528 assert!(!interval.contains(TimeInstant::new(50)));
529 assert!(!interval.contains(TimeInstant::new(200))); assert!(TimeInterval::new(200, 100).is_err());
533 }
534
535 #[test]
536 fn test_interval_overlap() {
537 let interval1 = TimeInterval::new(100, 200).unwrap();
538 let interval2 = TimeInterval::new(150, 250).unwrap();
539 let interval3 = TimeInterval::new(300, 400).unwrap();
540
541 assert!(interval1.overlaps(&interval2));
542 assert!(!interval1.overlaps(&interval3));
543
544 let intersection = interval1.intersection(&interval2).unwrap();
545 assert_eq!(intersection.start.time, 150);
546 assert_eq!(intersection.end.time, 200);
547
548 assert!(interval1.intersection(&interval3).is_none());
549 }
550
551 #[test]
552 fn test_temporal_graph_creation() {
553 let mut tgraph: TemporalGraph<&str, f64> = TemporalGraph::new();
554
555 let interval1 = TimeInterval::new(0, 100).unwrap();
556 let interval2 = TimeInterval::new(50, 150).unwrap();
557
558 tgraph.add_node("A", interval1);
559 tgraph.add_node("B", interval2);
560
561 let edge_interval = TimeInterval::new(60, 90).unwrap();
562 tgraph.add_edge("A", "B", 1.0, edge_interval).unwrap();
563
564 let snapshot_at_70 = tgraph.snapshot_at(TimeInstant::new(70));
566 assert_eq!(snapshot_at_70.node_count(), 2);
567 assert_eq!(snapshot_at_70.edge_count(), 1);
568
569 let snapshot_at_120 = tgraph.snapshot_at(TimeInstant::new(120));
570 assert_eq!(snapshot_at_120.node_count(), 1); assert_eq!(snapshot_at_120.edge_count(), 0);
572 }
573
574 #[test]
575 fn test_temporal_connectivity() {
576 let mut tgraph: TemporalGraph<i32, f64> = TemporalGraph::new();
577
578 let node_interval = TimeInterval::new(0, 200).unwrap();
579 tgraph.add_node(1, node_interval);
580 tgraph.add_node(2, node_interval);
581
582 let edge_interval = TimeInterval::new(50, 150).unwrap();
583 tgraph.add_edge(1, 2, 1.0, edge_interval).unwrap();
584
585 assert!(!tgraph.are_connected_at(&1, &2, TimeInstant::new(30)));
587 assert!(tgraph.are_connected_at(&1, &2, TimeInstant::new(100)));
588 assert!(!tgraph.are_connected_at(&1, &2, TimeInstant::new(170)));
589 }
590
591 #[test]
592 fn test_change_times() {
593 let mut tgraph: TemporalGraph<&str, f64> = TemporalGraph::new();
594
595 let interval1 = TimeInterval::new(0, 100).unwrap();
596 let interval2 = TimeInterval::new(50, 150).unwrap();
597
598 tgraph.add_node("A", interval1);
599 tgraph.add_edge("A", "B", 1.0, interval2).unwrap();
600
601 let change_times = tgraph.change_times();
602 let times: Vec<u64> = change_times.iter().map(|t| t.time).collect();
603
604 assert!(times.contains(&0));
605 assert!(times.contains(&50));
606 assert!(times.contains(&100));
607 assert!(times.contains(&150));
608 }
609
610 #[test]
611 fn test_temporal_reachability() {
612 let mut tgraph: TemporalGraph<i32, f64> = TemporalGraph::new();
613
614 let node_interval = TimeInterval::new(0, 300).unwrap();
615 for i in 1..=4 {
616 tgraph.add_node(i, node_interval);
617 }
618
619 tgraph
621 .add_edge(1, 2, 1.0, TimeInterval::new(10, 50).unwrap())
622 .unwrap();
623 tgraph
624 .add_edge(2, 3, 1.0, TimeInterval::new(60, 100).unwrap())
625 .unwrap();
626 tgraph
627 .add_edge(3, 4, 1.0, TimeInterval::new(110, 150).unwrap())
628 .unwrap();
629
630 let reachable = temporal_reachability(&tgraph, &1, TimeInstant::new(0), 200);
631
632 assert!(reachable.contains(&1));
633 assert!(reachable.contains(&2));
634 assert!(reachable.contains(&3));
635 assert!(reachable.contains(&4));
636 assert_eq!(reachable.len(), 4);
637
638 let reachable_limited = temporal_reachability(&tgraph, &1, TimeInstant::new(0), 80);
640 assert!(reachable_limited.contains(&1));
641 assert!(reachable_limited.contains(&2));
642 assert!(reachable_limited.contains(&3));
643 assert!(!reachable_limited.contains(&4)); }
645
646 #[test]
647 fn test_temporal_paths() {
648 let mut tgraph: TemporalGraph<&str, f64> = TemporalGraph::new();
649
650 let node_interval = TimeInterval::new(0, 200).unwrap();
651 for &node in &["A", "B", "C"] {
652 tgraph.add_node(node, node_interval);
653 }
654
655 tgraph
657 .add_edge("A", "C", 1.0, TimeInterval::new(10, 50).unwrap())
658 .unwrap();
659 tgraph
660 .add_edge("A", "B", 1.0, TimeInterval::new(20, 60).unwrap())
661 .unwrap();
662 tgraph
663 .add_edge("B", "C", 1.0, TimeInterval::new(70, 110).unwrap())
664 .unwrap();
665
666 let paths = tgraph.temporal_paths(&"A", &"C", TimeInstant::new(0), 150);
667
668 assert!(!paths.is_empty());
669
670 let has_direct = paths.iter().any(|p| p.nodes.len() == 2);
672 let has_indirect = paths.iter().any(|p| p.nodes.len() == 3);
673
674 assert!(has_direct || has_indirect);
675 }
676
677 #[test]
678 fn test_edge_count_at_time() {
679 let mut tgraph: TemporalGraph<i32, f64> = TemporalGraph::new();
680
681 let node_interval = TimeInterval::new(0, 200).unwrap();
682 tgraph.add_node(1, node_interval);
683 tgraph.add_node(2, node_interval);
684 tgraph.add_node(3, node_interval);
685
686 tgraph
687 .add_edge(1, 2, 1.0, TimeInterval::new(10, 50).unwrap())
688 .unwrap();
689 tgraph
690 .add_edge(2, 3, 1.0, TimeInterval::new(30, 70).unwrap())
691 .unwrap();
692
693 assert_eq!(tgraph.edge_count_at(TimeInstant::new(5)), 0);
694 assert_eq!(tgraph.edge_count_at(TimeInstant::new(40)), 2);
695 assert_eq!(tgraph.edge_count_at(TimeInstant::new(60)), 1);
696 assert_eq!(tgraph.edge_count_at(TimeInstant::new(80)), 0);
697 }
698}