use crate::base::{EdgeWeight, Graph, IndexType, Node};
use crate::error::{GraphError, Result};
use std::collections::{BTreeMap, HashMap, HashSet};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TimeInstant {
pub time: u64,
}
impl TimeInstant {
pub fn new(time: u64) -> Self {
TimeInstant { time }
}
pub fn value(&self) -> u64 {
self.time
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TimeInterval {
pub start: TimeInstant,
pub end: TimeInstant,
}
impl TimeInterval {
pub fn new(start: u64, end: u64) -> Result<Self> {
if start >= end {
return Err(GraphError::InvalidGraph(
"Start time must be before end time".to_string(),
));
}
Ok(TimeInterval {
start: TimeInstant::new(start),
end: TimeInstant::new(end),
})
}
pub fn contains(&self, time: TimeInstant) -> bool {
time >= self.start && time < self.end
}
pub fn overlaps(&self, other: &TimeInterval) -> bool {
self.start < other.end && other.start < self.end
}
pub fn duration(&self) -> u64 {
self.end.time - self.start.time
}
pub fn intersection(&self, other: &TimeInterval) -> Option<TimeInterval> {
if !self.overlaps(other) {
return None;
}
let start = self.start.max(other.start);
let end = self.end.min(other.end);
if start < end {
Some(TimeInterval { start, end })
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct TemporalEdge<N: Node, E: EdgeWeight> {
pub source: N,
pub target: N,
pub weight: E,
pub interval: TimeInterval,
}
#[derive(Debug, Clone)]
pub struct TemporalGraph<N: Node, E: EdgeWeight, Ix: IndexType = u32> {
nodes: HashSet<N>,
edges: BTreeMap<TimeInstant, Vec<TemporalEdge<N, E>>>,
node_intervals: HashMap<N, TimeInterval>,
edge_counter: usize,
_phantom: std::marker::PhantomData<Ix>,
}
impl<N: Node + std::fmt::Debug, E: EdgeWeight, Ix: IndexType> Default for TemporalGraph<N, E, Ix> {
fn default() -> Self {
Self::new()
}
}
impl<N: Node + std::fmt::Debug, E: EdgeWeight, Ix: IndexType> TemporalGraph<N, E, Ix> {
pub fn new() -> Self {
TemporalGraph {
nodes: HashSet::new(),
edges: BTreeMap::new(),
node_intervals: HashMap::new(),
edge_counter: 0,
_phantom: std::marker::PhantomData,
}
}
pub fn add_node(&mut self, node: N, interval: TimeInterval) {
self.nodes.insert(node.clone());
self.node_intervals.insert(node, interval);
}
pub fn add_edge(
&mut self,
source: N,
target: N,
weight: E,
interval: TimeInterval,
) -> Result<usize> {
if let Some(source_interval) = self.node_intervals.get(&source) {
if source_interval.intersection(&interval).is_none() {
return Err(GraphError::InvalidGraph(
"Source node not active during edge interval".to_string(),
));
}
} else {
self.add_node(source.clone(), interval);
}
if let Some(target_interval) = self.node_intervals.get(&target) {
if target_interval.intersection(&interval).is_none() {
return Err(GraphError::InvalidGraph(
"Target node not active during edge interval".to_string(),
));
}
} else {
self.add_node(target.clone(), interval);
}
let edge = TemporalEdge {
source,
target,
weight,
interval,
};
self.edges.entry(interval.start).or_default().push(edge);
let edge_id = self.edge_counter;
self.edge_counter += 1;
Ok(edge_id)
}
pub fn snapshot_at(&self, time: TimeInstant) -> Graph<N, E, Ix>
where
N: Clone,
E: Clone,
{
let mut snapshot = Graph::new();
for (node, interval) in &self.node_intervals {
if interval.contains(time) {
snapshot.add_node(node.clone());
}
}
for edges in self.edges.values() {
for edge in edges {
if edge.interval.contains(time) {
snapshot
.add_edge(
edge.source.clone(),
edge.target.clone(),
edge.weight.clone(),
)
.expect("Test: operation failed");
}
}
}
snapshot
}
pub fn edges_in_interval(&self, interval: TimeInterval) -> Vec<&TemporalEdge<N, E>> {
let mut result = Vec::new();
for edges in self.edges.values() {
for edge in edges {
if edge.interval.overlaps(&interval) {
result.push(edge);
}
}
}
result
}
pub fn change_times(&self) -> Vec<TimeInstant> {
let mut times = HashSet::new();
for interval in self.node_intervals.values() {
times.insert(interval.start);
times.insert(interval.end);
}
for edges in self.edges.values() {
for edge in edges {
times.insert(edge.interval.start);
times.insert(edge.interval.end);
}
}
let mut times: Vec<_> = times.into_iter().collect();
times.sort();
times
}
pub fn active_interval(&self) -> Option<TimeInterval> {
if self.nodes.is_empty() {
return None;
}
let change_times = self.change_times();
if change_times.len() < 2 {
return None;
}
let start = change_times[0];
let end = change_times[change_times.len() - 1];
TimeInterval::new(start.time, end.time).ok()
}
pub fn node_count_at(&self, time: TimeInstant) -> usize {
self.node_intervals
.values()
.filter(|interval| interval.contains(time))
.count()
}
pub fn edge_count_at(&self, time: TimeInstant) -> usize {
let mut count = 0;
for edges in self.edges.values() {
for edge in edges {
if edge.interval.contains(time) {
count += 1;
}
}
}
count
}
pub fn nodes(&self) -> impl Iterator<Item = &N> {
self.nodes.iter()
}
pub fn temporal_edges(&self) -> Vec<&TemporalEdge<N, E>> {
let mut result = Vec::new();
for edges in self.edges.values() {
result.extend(edges.iter());
}
result
}
pub fn are_connected_at(&self, node1: &N, node2: &N, time: TimeInstant) -> bool {
for edges in self.edges.values() {
for edge in edges {
if edge.interval.contains(time)
&& ((edge.source == *node1 && edge.target == *node2)
|| (edge.source == *node2 && edge.target == *node1))
{
return true;
}
}
}
false
}
pub fn temporal_paths(
&self,
source: &N,
target: &N,
start_time: TimeInstant,
max_duration: u64,
) -> Vec<TemporalPath<N, E>>
where
N: Clone + Ord,
E: Clone,
{
let mut paths = Vec::new();
let end_time = TimeInstant::new(start_time.time + max_duration);
let mut queue = std::collections::VecDeque::new();
queue.push_back(TemporalPath {
nodes: vec![source.clone()],
edges: Vec::new(),
total_weight: None,
start_time,
end_time: start_time,
});
while let Some(current_path) = queue.pop_front() {
let current_node = current_path.nodes.last().expect("Operation failed");
if current_node == target {
paths.push(current_path);
continue;
}
if current_path.end_time >= end_time {
continue;
}
for edges in self.edges.values() {
for edge in edges {
if edge.source == *current_node
&& edge.interval.start >= current_path.end_time
&& edge.interval.start <= end_time
&& !current_path.nodes.contains(&edge.target)
{
let mut new_path = current_path.clone();
new_path.nodes.push(edge.target.clone());
new_path.edges.push(edge.clone());
new_path.end_time = edge.interval.end;
queue.push_back(new_path);
}
}
}
}
paths
}
}
#[derive(Debug, Clone)]
pub struct TemporalPath<N: Node, E: EdgeWeight> {
pub nodes: Vec<N>,
pub edges: Vec<TemporalEdge<N, E>>,
pub total_weight: Option<E>,
pub start_time: TimeInstant,
pub end_time: TimeInstant,
}
impl<N: Node, E: EdgeWeight> TemporalPath<N, E> {
pub fn duration(&self) -> u64 {
self.end_time.time - self.start_time.time
}
pub fn hop_count(&self) -> usize {
self.edges.len()
}
}
#[allow(dead_code)]
pub fn temporal_reachability<N, E, Ix>(
temporal_graph: &TemporalGraph<N, E, Ix>,
source: &N,
start_time: TimeInstant,
max_duration: u64,
) -> HashSet<N>
where
N: Node + Clone + Ord + std::fmt::Debug,
E: EdgeWeight + Clone,
Ix: IndexType,
{
let mut reachable = HashSet::new();
let mut visited = HashSet::new();
let mut queue = std::collections::VecDeque::new();
queue.push_back((source.clone(), start_time));
visited.insert((source.clone(), start_time));
reachable.insert(source.clone());
let end_time = TimeInstant::new(start_time.time + max_duration);
while let Some((current_node, current_time)) = queue.pop_front() {
if current_time >= end_time {
continue;
}
for edges in temporal_graph.edges.values() {
for edge in edges {
if edge.source == current_node
&& edge.interval.start >= current_time
&& edge.interval.start <= end_time
{
let next_time = edge.interval.end;
let next_node = edge.target.clone();
if !visited.contains(&(next_node.clone(), next_time)) {
visited.insert((next_node.clone(), next_time));
reachable.insert(next_node.clone());
queue.push_back((next_node, next_time));
}
}
}
}
}
reachable
}
#[allow(dead_code)]
pub fn temporal_betweenness_centrality<N, E, Ix>(
temporal_graph: &TemporalGraph<N, E, Ix>,
time_window: TimeInterval,
) -> HashMap<N, f64>
where
N: Node + Clone + Ord + std::fmt::Debug,
E: EdgeWeight + Clone,
Ix: IndexType,
{
let mut centrality = HashMap::new();
for node in temporal_graph.nodes() {
centrality.insert(node.clone(), 0.0);
}
let nodes: Vec<N> = temporal_graph.nodes().cloned().collect();
for i in 0..nodes.len() {
for j in (i + 1)..nodes.len() {
let source = &nodes[i];
let target = &nodes[j];
let paths = temporal_graph.temporal_paths(
source,
target,
time_window.start,
time_window.duration(),
);
if !paths.is_empty() {
for path in &paths {
for k in 1..(path.nodes.len() - 1) {
let intermediate = &path.nodes[k];
*centrality.get_mut(intermediate).expect("Operation failed") +=
1.0 / paths.len() as f64;
}
}
}
}
}
centrality
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_time_instant() {
let t1 = TimeInstant::new(100);
let t2 = TimeInstant::new(200);
assert!(t1 < t2);
assert_eq!(t1.value(), 100);
assert_eq!(t2.value(), 200);
}
#[test]
fn test_time_interval() {
let interval = TimeInterval::new(100, 200).expect("Operation failed");
assert_eq!(interval.duration(), 100);
assert!(interval.contains(TimeInstant::new(150)));
assert!(!interval.contains(TimeInstant::new(50)));
assert!(!interval.contains(TimeInstant::new(200)));
assert!(TimeInterval::new(200, 100).is_err());
}
#[test]
fn test_interval_overlap() {
let interval1 = TimeInterval::new(100, 200).expect("Operation failed");
let interval2 = TimeInterval::new(150, 250).expect("Operation failed");
let interval3 = TimeInterval::new(300, 400).expect("Operation failed");
assert!(interval1.overlaps(&interval2));
assert!(!interval1.overlaps(&interval3));
let intersection = interval1
.intersection(&interval2)
.expect("Operation failed");
assert_eq!(intersection.start.time, 150);
assert_eq!(intersection.end.time, 200);
assert!(interval1.intersection(&interval3).is_none());
}
#[test]
fn test_temporal_graph_creation() {
let mut tgraph: TemporalGraph<&str, f64> = TemporalGraph::new();
let interval1 = TimeInterval::new(0, 100).expect("Operation failed");
let interval2 = TimeInterval::new(50, 150).expect("Operation failed");
tgraph.add_node("A", interval1);
tgraph.add_node("B", interval2);
let edge_interval = TimeInterval::new(60, 90).expect("Operation failed");
tgraph
.add_edge("A", "B", 1.0, edge_interval)
.expect("Operation failed");
let snapshot_at_70 = tgraph.snapshot_at(TimeInstant::new(70));
assert_eq!(snapshot_at_70.node_count(), 2);
assert_eq!(snapshot_at_70.edge_count(), 1);
let snapshot_at_120 = tgraph.snapshot_at(TimeInstant::new(120));
assert_eq!(snapshot_at_120.node_count(), 1); assert_eq!(snapshot_at_120.edge_count(), 0);
}
#[test]
fn test_temporal_connectivity() {
let mut tgraph: TemporalGraph<i32, f64> = TemporalGraph::new();
let node_interval = TimeInterval::new(0, 200).expect("Operation failed");
tgraph.add_node(1, node_interval);
tgraph.add_node(2, node_interval);
let edge_interval = TimeInterval::new(50, 150).expect("Operation failed");
tgraph
.add_edge(1, 2, 1.0, edge_interval)
.expect("Operation failed");
assert!(!tgraph.are_connected_at(&1, &2, TimeInstant::new(30)));
assert!(tgraph.are_connected_at(&1, &2, TimeInstant::new(100)));
assert!(!tgraph.are_connected_at(&1, &2, TimeInstant::new(170)));
}
#[test]
fn test_change_times() {
let mut tgraph: TemporalGraph<&str, f64> = TemporalGraph::new();
let interval1 = TimeInterval::new(0, 100).expect("Operation failed");
let interval2 = TimeInterval::new(50, 150).expect("Operation failed");
tgraph.add_node("A", interval1);
tgraph
.add_edge("A", "B", 1.0, interval2)
.expect("Operation failed");
let change_times = tgraph.change_times();
let times: Vec<u64> = change_times.iter().map(|t| t.time).collect();
assert!(times.contains(&0));
assert!(times.contains(&50));
assert!(times.contains(&100));
assert!(times.contains(&150));
}
#[test]
fn test_temporal_reachability() {
let mut tgraph: TemporalGraph<i32, f64> = TemporalGraph::new();
let node_interval = TimeInterval::new(0, 300).expect("Operation failed");
for i in 1..=4 {
tgraph.add_node(i, node_interval);
}
tgraph
.add_edge(
1,
2,
1.0,
TimeInterval::new(10, 50).expect("Operation failed"),
)
.expect("Test: operation failed");
tgraph
.add_edge(
2,
3,
1.0,
TimeInterval::new(60, 100).expect("Operation failed"),
)
.expect("Test: operation failed");
tgraph
.add_edge(
3,
4,
1.0,
TimeInterval::new(110, 150).expect("Operation failed"),
)
.expect("Test: operation failed");
let reachable = temporal_reachability(&tgraph, &1, TimeInstant::new(0), 200);
assert!(reachable.contains(&1));
assert!(reachable.contains(&2));
assert!(reachable.contains(&3));
assert!(reachable.contains(&4));
assert_eq!(reachable.len(), 4);
let reachable_limited = temporal_reachability(&tgraph, &1, TimeInstant::new(0), 80);
assert!(reachable_limited.contains(&1));
assert!(reachable_limited.contains(&2));
assert!(reachable_limited.contains(&3));
assert!(!reachable_limited.contains(&4)); }
#[test]
fn test_temporal_paths() {
let mut tgraph: TemporalGraph<&str, f64> = TemporalGraph::new();
let node_interval = TimeInterval::new(0, 200).expect("Operation failed");
for &node in &["A", "B", "C"] {
tgraph.add_node(node, node_interval);
}
tgraph
.add_edge(
"A",
"C",
1.0,
TimeInterval::new(10, 50).expect("Operation failed"),
)
.expect("Test: operation failed");
tgraph
.add_edge(
"A",
"B",
1.0,
TimeInterval::new(20, 60).expect("Operation failed"),
)
.expect("Test: operation failed");
tgraph
.add_edge(
"B",
"C",
1.0,
TimeInterval::new(70, 110).expect("Operation failed"),
)
.expect("Test: operation failed");
let paths = tgraph.temporal_paths(&"A", &"C", TimeInstant::new(0), 150);
assert!(!paths.is_empty());
let has_direct = paths.iter().any(|p| p.nodes.len() == 2);
let has_indirect = paths.iter().any(|p| p.nodes.len() == 3);
assert!(has_direct || has_indirect);
}
#[test]
fn test_edge_count_at_time() {
let mut tgraph: TemporalGraph<i32, f64> = TemporalGraph::new();
let node_interval = TimeInterval::new(0, 200).expect("Operation failed");
tgraph.add_node(1, node_interval);
tgraph.add_node(2, node_interval);
tgraph.add_node(3, node_interval);
tgraph
.add_edge(
1,
2,
1.0,
TimeInterval::new(10, 50).expect("Operation failed"),
)
.expect("Test: operation failed");
tgraph
.add_edge(
2,
3,
1.0,
TimeInterval::new(30, 70).expect("Operation failed"),
)
.expect("Test: operation failed");
assert_eq!(tgraph.edge_count_at(TimeInstant::new(5)), 0);
assert_eq!(tgraph.edge_count_at(TimeInstant::new(40)), 2);
assert_eq!(tgraph.edge_count_at(TimeInstant::new(60)), 1);
assert_eq!(tgraph.edge_count_at(TimeInstant::new(80)), 0);
}
}