scirs2_graph/dynamic/
snapshot.rs1use std::collections::HashMap;
4
5#[derive(Debug, Clone)]
7pub struct GraphSnapshot {
8 pub time: f64,
10 pub nodes: Vec<usize>,
12 pub edges: Vec<(usize, usize, f64)>,
14 pub node_attributes: HashMap<usize, HashMap<String, f64>>,
16}
17
18impl GraphSnapshot {
19 pub fn new(time: f64) -> Self {
21 Self {
22 time,
23 nodes: Vec::new(),
24 edges: Vec::new(),
25 node_attributes: HashMap::new(),
26 }
27 }
28
29 pub fn add_node(&mut self, node: usize) {
31 if !self.nodes.contains(&node) {
32 self.nodes.push(node);
33 }
34 }
35
36 pub fn add_edge(&mut self, src: usize, dst: usize, weight: f64) {
38 self.add_node(src);
39 self.add_node(dst);
40 self.edges.push((src, dst, weight));
41 }
42
43 pub fn degree(&self, node: usize) -> usize {
45 self.edges
46 .iter()
47 .filter(|(s, d, _)| *s == node || *d == node)
48 .count()
49 }
50
51 pub fn density(&self) -> f64 {
53 let n = self.nodes.len();
54 if n < 2 {
55 return 0.0;
56 }
57 self.edges.len() as f64 / (n * (n - 1)) as f64
58 }
59
60 pub fn n_nodes(&self) -> usize {
62 self.nodes.len()
63 }
64
65 pub fn n_edges(&self) -> usize {
67 self.edges.len()
68 }
69}
70
71#[derive(Debug, Clone)]
73pub struct SnapshotGraph {
74 pub snapshots: Vec<GraphSnapshot>,
76 pub time_window: Option<f64>,
78}
79
80impl SnapshotGraph {
81 pub fn new() -> Self {
83 Self {
84 snapshots: Vec::new(),
85 time_window: None,
86 }
87 }
88
89 pub fn with_time_window(mut self, window: f64) -> Self {
91 self.time_window = Some(window);
92 self
93 }
94
95 pub fn add_snapshot(&mut self, snapshot: GraphSnapshot) {
97 self.snapshots.push(snapshot);
98 self.snapshots.sort_by(|a, b| {
99 a.time
100 .partial_cmp(&b.time)
101 .unwrap_or(std::cmp::Ordering::Equal)
102 });
103 }
104
105 pub fn n_snapshots(&self) -> usize {
107 self.snapshots.len()
108 }
109
110 pub fn snapshot_at(&self, t: f64) -> Option<&GraphSnapshot> {
112 self.snapshots.iter().min_by(|a, b| {
113 (a.time - t)
114 .abs()
115 .partial_cmp(&(b.time - t).abs())
116 .unwrap_or(std::cmp::Ordering::Equal)
117 })
118 }
119
120 pub fn all_nodes(&self) -> Vec<usize> {
122 let mut nodes: Vec<usize> = self
123 .snapshots
124 .iter()
125 .flat_map(|s| s.nodes.iter().cloned())
126 .collect();
127 nodes.sort_unstable();
128 nodes.dedup();
129 nodes
130 }
131
132 pub fn temporal_degree(&self, node: usize) -> Vec<(f64, usize)> {
134 self.snapshots
135 .iter()
136 .map(|s| (s.time, s.degree(node)))
137 .collect()
138 }
139
140 pub fn burstiness(&self, src: usize, dst: usize) -> f64 {
144 let event_times: Vec<f64> = self
145 .snapshots
146 .iter()
147 .filter(|s| {
148 s.edges
149 .iter()
150 .any(|(s_, d_, _)| (*s_ == src && *d_ == dst) || (*s_ == dst && *d_ == src))
151 })
152 .map(|s| s.time)
153 .collect();
154
155 if event_times.len() < 2 {
156 return 0.0;
157 }
158
159 let inter_events: Vec<f64> = event_times.windows(2).map(|w| w[1] - w[0]).collect();
160 let n = inter_events.len() as f64;
161 let mu = inter_events.iter().sum::<f64>() / n;
162 let sigma = (inter_events.iter().map(|x| (x - mu).powi(2)).sum::<f64>() / n).sqrt();
163
164 if sigma + mu < 1e-10 {
165 0.0
166 } else {
167 (sigma - mu) / (sigma + mu)
168 }
169 }
170
171 pub fn temporal_correlation(&self, lag: usize) -> f64 {
173 if self.snapshots.len() <= lag {
174 return 0.0;
175 }
176
177 let n = self.snapshots.len() - lag;
178 let mut similarity_sum = 0.0;
179
180 for i in 0..n {
181 let s1 = &self.snapshots[i];
182 let s2 = &self.snapshots[i + lag];
183
184 let e1: std::collections::HashSet<(usize, usize)> = s1
185 .edges
186 .iter()
187 .map(|(a, b, _)| (*a.min(b), *a.max(b)))
188 .collect();
189 let e2: std::collections::HashSet<(usize, usize)> = s2
190 .edges
191 .iter()
192 .map(|(a, b, _)| (*a.min(b), *a.max(b)))
193 .collect();
194
195 let intersection = e1.intersection(&e2).count();
196 let union_size = e1.union(&e2).count();
197
198 similarity_sum += if union_size > 0 {
199 intersection as f64 / union_size as f64
200 } else {
201 0.0
202 };
203 }
204
205 similarity_sum / n as f64
206 }
207}
208
209impl Default for SnapshotGraph {
210 fn default() -> Self {
211 Self::new()
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn test_snapshot_density() {
221 let mut s = GraphSnapshot::new(1.0);
222 s.add_edge(0, 1, 1.0);
223 s.add_edge(0, 2, 1.0);
224 let d = s.density();
226 assert!((d - 1.0 / 3.0).abs() < 1e-9, "density={d}");
227 }
228
229 #[test]
230 fn test_temporal_correlation_identical() {
231 let mut sg = SnapshotGraph::new();
232 for t in 0..4 {
233 let mut snap = GraphSnapshot::new(t as f64);
234 snap.add_edge(0, 1, 1.0);
235 snap.add_edge(1, 2, 1.0);
236 sg.add_snapshot(snap);
237 }
238 let corr = sg.temporal_correlation(1);
240 assert!((corr - 1.0).abs() < 1e-9, "corr={corr}");
241 }
242
243 #[test]
244 fn test_burstiness_regular() {
245 let mut sg = SnapshotGraph::new();
247 for t in 0..5 {
248 let mut snap = GraphSnapshot::new(t as f64);
249 snap.add_edge(0, 1, 1.0);
250 sg.add_snapshot(snap);
251 }
252 let b = sg.burstiness(0, 1);
253 assert!(b < -0.5, "b={b}");
254 }
255
256 #[test]
257 fn test_all_nodes() {
258 let mut sg = SnapshotGraph::new();
259 let mut s1 = GraphSnapshot::new(0.0);
260 s1.add_edge(0, 1, 1.0);
261 let mut s2 = GraphSnapshot::new(1.0);
262 s2.add_edge(1, 2, 1.0);
263 sg.add_snapshot(s1);
264 sg.add_snapshot(s2);
265 let nodes = sg.all_nodes();
266 assert_eq!(nodes, vec![0, 1, 2]);
267 }
268}