1#![allow(dead_code)]
16type Result<T, E = torsh_core::error::TorshError> = std::result::Result<T, E>;
19
20use crate::parameter::Parameter;
21use crate::{GraphData, GraphLayer};
22use std::collections::{BTreeMap, HashMap};
23use torsh_tensor::{
24 creation::{from_vec, randn, zeros},
25 Tensor,
26};
27
28#[derive(Debug, Clone)]
30pub struct TemporalEvent {
31 pub time: f64,
33 pub event_type: EventType,
35 pub source: Option<usize>,
37 pub target: Option<usize>,
39 pub node: Option<usize>,
41 pub features: Option<Tensor>,
43 pub weight: Option<f32>,
45}
46
47#[derive(Debug, Clone, PartialEq)]
49pub enum EventType {
50 NodeAddition,
51 NodeDeletion,
52 NodeFeatureUpdate,
53 EdgeAddition,
54 EdgeDeletion,
55 EdgeFeatureUpdate,
56 GraphSnapshot,
57}
58
59#[derive(Debug, Clone)]
61pub struct TemporalGraphData {
62 pub current_graph: GraphData,
64 pub events: BTreeMap<u64, Vec<TemporalEvent>>, pub node_features_history: HashMap<usize, BTreeMap<u64, Tensor>>,
68 pub edge_features_history: HashMap<(usize, usize), BTreeMap<u64, Tensor>>,
70 pub current_time: f64,
72 pub time_window: f64,
74 pub max_events: usize,
76}
77
78impl TemporalGraphData {
79 pub fn new(initial_graph: GraphData, time_window: f64, max_events: usize) -> Self {
81 Self {
82 current_graph: initial_graph,
83 events: BTreeMap::new(),
84 node_features_history: HashMap::new(),
85 edge_features_history: HashMap::new(),
86 current_time: 0.0,
87 time_window,
88 max_events,
89 }
90 }
91
92 pub fn add_event(&mut self, event: TemporalEvent) -> Result<()> {
98 let timestamp = (event.time * 1000.0) as u64; self.events
100 .entry(timestamp)
101 .or_insert_with(Vec::new)
102 .push(event.clone());
103
104 self.current_time = self.current_time.max(event.time);
106
107 self.apply_event(&event)?;
109
110 self.cleanup_old_events();
112
113 Ok(())
114 }
115
116 fn apply_event(&mut self, event: &TemporalEvent) -> Result<()> {
118 match event.event_type {
119 EventType::NodeFeatureUpdate => {
120 if let (Some(node), Some(ref features)) = (event.node, &event.features) {
121 self.update_node_features(node, features.clone())?;
123
124 let timestamp = (event.time * 1000.0) as u64;
126 self.node_features_history
127 .entry(node)
128 .or_insert_with(BTreeMap::new)
129 .insert(timestamp, features.clone());
130 }
131 }
132 EventType::EdgeFeatureUpdate => {
133 if let (Some(source), Some(target), Some(ref features)) =
134 (event.source, event.target, &event.features)
135 {
136 let timestamp = (event.time * 1000.0) as u64;
137 self.edge_features_history
138 .entry((source, target))
139 .or_insert_with(BTreeMap::new)
140 .insert(timestamp, features.clone());
141 }
142 }
143 _ => {
144 }
147 }
148
149 Ok(())
150 }
151
152 fn update_node_features(&mut self, node_id: usize, features: Tensor) -> Result<()> {
154 let current_features = self.current_graph.x.to_vec()?;
156 let feature_dim = self.current_graph.x.shape().dims()[1];
157 let new_features = features.to_vec()?;
158
159 let mut updated_features = current_features;
160 let start_idx = node_id * feature_dim;
161 let _end_idx = start_idx + feature_dim.min(new_features.len());
162
163 for (i, &value) in new_features.iter().take(feature_dim).enumerate() {
164 if start_idx + i < updated_features.len() {
165 updated_features[start_idx + i] = value;
166 }
167 }
168
169 self.current_graph.x = from_vec(
170 updated_features,
171 &[self.current_graph.num_nodes, feature_dim],
172 torsh_core::device::DeviceType::Cpu,
173 )?;
174
175 Ok(())
176 }
177
178 fn cleanup_old_events(&mut self) {
180 let cutoff_time = ((self.current_time - self.time_window) * 1000.0) as u64;
181
182 let old_keys: Vec<u64> = self
184 .events
185 .keys()
186 .filter(|&×tamp| timestamp < cutoff_time)
187 .cloned()
188 .collect();
189
190 for key in old_keys {
191 self.events.remove(&key);
192 }
193
194 while self.events.len() > self.max_events {
196 if let Some(first_key) = self.events.keys().next().cloned() {
197 self.events.remove(&first_key);
198 } else {
199 break;
200 }
201 }
202 }
203
204 pub fn get_events_in_range(&self, start_time: f64, end_time: f64) -> Vec<&TemporalEvent> {
206 let start_timestamp = (start_time * 1000.0) as u64;
207 let end_timestamp = (end_time * 1000.0) as u64;
208
209 self.events
210 .range(start_timestamp..=end_timestamp)
211 .flat_map(|(_, events)| events.iter())
212 .collect()
213 }
214
215 pub fn get_node_features_at_time(&self, node_id: usize, time: f64) -> Option<Tensor> {
217 let timestamp = (time * 1000.0) as u64;
218
219 if let Some(history) = self.node_features_history.get(&node_id) {
220 if let Some((_, features)) = history.range(..=timestamp).next_back() {
222 return Some(features.clone());
223 }
224 }
225
226 None
227 }
228
229 pub fn snapshot_at_time(&self, _time: f64) -> GraphData {
231 self.current_graph.clone()
234 }
235}
236
237#[derive(Debug)]
239pub struct TGCNConv {
240 in_features: usize,
241 out_features: usize,
242 temporal_dim: usize,
243 spatial_weight: Parameter,
244 temporal_weight: Parameter,
245 bias: Option<Parameter>,
246 memory_size: usize,
247 time_encoding_dim: usize,
248}
249
250impl TGCNConv {
251 pub fn new(
253 in_features: usize,
254 out_features: usize,
255 temporal_dim: usize,
256 memory_size: usize,
257 bias: bool,
258 ) -> Result<Self> {
259 let spatial_weight = Parameter::new(randn(&[in_features, out_features])?);
260 let temporal_weight = Parameter::new(randn(&[temporal_dim, out_features])?);
261 let bias = if bias {
262 Some(Parameter::new(zeros(&[out_features])?))
263 } else {
264 None
265 };
266
267 Ok(Self {
268 in_features,
269 out_features,
270 temporal_dim,
271 spatial_weight,
272 temporal_weight,
273 bias,
274 memory_size,
275 time_encoding_dim: temporal_dim,
276 })
277 }
278
279 pub fn forward(&self, temporal_graph: &TemporalGraphData) -> Result<TemporalGraphData> {
281 let spatial_features = temporal_graph
283 .current_graph
284 .x
285 .matmul(&self.spatial_weight.clone_data())?;
286
287 let temporal_features = self.encode_temporal_context(temporal_graph)?;
289
290 let combined_features = spatial_features.add(&temporal_features)?;
292
293 let output_features = if let Some(ref bias) = self.bias {
295 combined_features.add(&bias.clone_data())?
296 } else {
297 combined_features
298 };
299
300 let mut output_graph = temporal_graph.clone();
302 output_graph.current_graph.x = output_features;
303 Ok(output_graph)
304 }
305
306 fn encode_temporal_context(&self, temporal_graph: &TemporalGraphData) -> Result<Tensor> {
308 let num_nodes = temporal_graph.current_graph.num_nodes;
309 let current_time = temporal_graph.current_time;
310 let lookback_time = current_time - temporal_graph.time_window;
311
312 let recent_events = temporal_graph.get_events_in_range(lookback_time, current_time);
314
315 let _temporal_encoding = zeros::<f32>(&[num_nodes, self.out_features])?;
317
318 let mut node_event_counts = vec![0.0; num_nodes];
320
321 for event in recent_events {
322 if let Some(node_id) = event.node {
323 if node_id < num_nodes {
324 let recency_weight =
326 1.0 - (current_time - event.time) / temporal_graph.time_window;
327 node_event_counts[node_id] += recency_weight;
328 }
329 }
330 }
331
332 let temporal_data: Vec<f32> = node_event_counts
334 .iter()
335 .flat_map(|&count| {
336 (0..self.out_features).map(move |_| count as f32)
338 })
339 .collect();
340
341 Ok(from_vec(
342 temporal_data,
343 &[num_nodes, self.out_features],
344 torsh_core::device::DeviceType::Cpu,
345 )?)
346 }
347}
348
349impl GraphLayer for TGCNConv {
350 fn forward(&self, graph: &GraphData) -> Result<GraphData> {
351 let temporal_graph = TemporalGraphData::new(graph.clone(), 1.0, 1000);
353 let output_temporal = TGCNConv::forward(self, &temporal_graph)?;
354 Ok(output_temporal.current_graph)
355 }
356
357 fn parameters(&self) -> Vec<Tensor> {
358 let mut params = vec![
359 self.spatial_weight.clone_data(),
360 self.temporal_weight.clone_data(),
361 ];
362 if let Some(ref bias) = self.bias {
363 params.push(bias.clone_data());
364 }
365 params
366 }
367}
368
369#[derive(Debug)]
371pub struct TGATConv {
372 in_features: usize,
373 out_features: usize,
374 heads: usize,
375 time_encoding_dim: usize,
376 query_weight: Parameter,
377 key_weight: Parameter,
378 value_weight: Parameter,
379 time_weight: Parameter,
380 output_weight: Parameter,
381 bias: Option<Parameter>,
382 dropout: f32,
383}
384
385impl TGATConv {
386 pub fn new(
388 in_features: usize,
389 out_features: usize,
390 heads: usize,
391 time_encoding_dim: usize,
392 dropout: f32,
393 bias: bool,
394 ) -> Result<Self> {
395 let query_weight = Parameter::new(randn(&[in_features, out_features])?);
396 let key_weight = Parameter::new(randn(&[in_features, out_features])?);
397 let value_weight = Parameter::new(randn(&[in_features, out_features])?);
398 let time_weight = Parameter::new(randn(&[time_encoding_dim, out_features])?);
399 let output_weight = Parameter::new(randn(&[out_features, out_features])?);
400
401 let bias = if bias {
402 Some(Parameter::new(zeros(&[out_features])?))
403 } else {
404 None
405 };
406
407 Ok(Self {
408 in_features,
409 out_features,
410 heads,
411 time_encoding_dim,
412 query_weight,
413 key_weight,
414 value_weight,
415 time_weight,
416 output_weight,
417 bias,
418 dropout,
419 })
420 }
421
422 pub fn forward(&self, temporal_graph: &TemporalGraphData) -> Result<TemporalGraphData> {
424 let num_nodes = temporal_graph.current_graph.num_nodes;
425 let head_dim = self.out_features / self.heads;
426
427 let queries = temporal_graph
429 .current_graph
430 .x
431 .matmul(&self.query_weight.clone_data())?;
432 let keys = temporal_graph
433 .current_graph
434 .x
435 .matmul(&self.key_weight.clone_data())?;
436 let values = temporal_graph
437 .current_graph
438 .x
439 .matmul(&self.value_weight.clone_data())?;
440
441 let time_encoding = self.compute_time_encoding(temporal_graph);
443 let time_transformed = time_encoding?.matmul(&self.time_weight.clone_data())?;
444
445 let q = queries.view(&[num_nodes as i32, self.heads as i32, head_dim as i32])?;
447 let k = keys.view(&[num_nodes as i32, self.heads as i32, head_dim as i32])?;
448 let v = values.view(&[num_nodes as i32, self.heads as i32, head_dim as i32])?;
449
450 let attended_features =
452 self.temporal_attention(&q, &k, &v, &time_transformed, temporal_graph);
453
454 let concatenated =
456 attended_features?.view(&[num_nodes as i32, self.out_features as i32])?;
457 let mut output = concatenated.matmul(&self.output_weight.clone_data())?;
458
459 if let Some(ref bias) = self.bias {
461 output = output.add(&bias.clone_data())?;
462 }
463
464 let mut output_graph = temporal_graph.clone();
466 output_graph.current_graph.x = output;
467 Ok(output_graph)
468 }
469
470 fn compute_time_encoding(&self, temporal_graph: &TemporalGraphData) -> Result<Tensor> {
472 let num_nodes = temporal_graph.current_graph.num_nodes;
473 let current_time = temporal_graph.current_time;
474
475 let mut time_features = vec![current_time as f32; num_nodes * self.time_encoding_dim];
477
478 for (node_id, history) in &temporal_graph.node_features_history {
480 if *node_id < num_nodes {
481 if let Some((timestamp, _)) = history.iter().next_back() {
482 let last_event_time = (*timestamp as f64) / 1000.0;
483 let time_diff = (current_time - last_event_time) as f32;
484
485 for dim in 0..self.time_encoding_dim {
487 let freq = 2.0_f32.powf(dim as f32);
488 let encoded = (time_diff * freq).sin();
489 time_features[*node_id * self.time_encoding_dim + dim] = encoded;
490 }
491 }
492 }
493 }
494
495 Ok(from_vec(
496 time_features,
497 &[num_nodes, self.time_encoding_dim],
498 torsh_core::device::DeviceType::Cpu,
499 )?)
500 }
501
502 fn temporal_attention(
504 &self,
505 q: &Tensor,
506 k: &Tensor,
507 v: &Tensor,
508 _time_encoding: &Tensor,
509 temporal_graph: &TemporalGraphData,
510 ) -> Result<Tensor> {
511 let num_nodes = temporal_graph.current_graph.num_nodes;
512 let head_dim = self.out_features / self.heads;
513
514 let mut output = zeros(&[num_nodes, self.heads, head_dim])?;
516
517 for head in 0..self.heads {
519 let _q_head = q.slice_tensor(1, head, head + 1)?;
521 let _k_head = k.slice_tensor(1, head, head + 1)?;
522 let v_head = v.slice_tensor(1, head, head + 1)?;
523
524 for i in 0..num_nodes {
526 let mut attended_value = zeros(&[head_dim])?;
527 let mut attention_sum = 0.0;
528
529 for j in 0..num_nodes {
530 let score = 1.0 / (1.0 + (i as f32 - j as f32).abs()); let v_j = v_head
535 .slice_tensor(0, j, j + 1)?
536 .squeeze_tensor(0)?
537 .squeeze_tensor(0)?;
538
539 let weighted_value = v_j.mul_scalar(score)?;
540 attended_value = attended_value.add(&weighted_value)?;
541 attention_sum += score;
542 }
543
544 if attention_sum > 0.0 {
546 attended_value = attended_value.div_scalar(attention_sum)?;
547 }
548
549 let attended_data = attended_value.to_vec()?;
551 for (dim, &val) in attended_data.iter().enumerate() {
552 if dim < head_dim {
553 output.set_item(&[i, head, dim], val)?;
554 }
555 }
556 }
557 }
558
559 Ok(output)
560 }
561}
562
563impl GraphLayer for TGATConv {
564 fn forward(&self, graph: &GraphData) -> Result<GraphData> {
565 let temporal_graph = TemporalGraphData::new(graph.clone(), 1.0, 1000);
566 let output_temporal = TGATConv::forward(self, &temporal_graph)?;
567 Ok(output_temporal.current_graph)
568 }
569
570 fn parameters(&self) -> Vec<Tensor> {
571 let mut params = vec![
572 self.query_weight.clone_data(),
573 self.key_weight.clone_data(),
574 self.value_weight.clone_data(),
575 self.time_weight.clone_data(),
576 self.output_weight.clone_data(),
577 ];
578 if let Some(ref bias) = self.bias {
579 params.push(bias.clone_data());
580 }
581 params
582 }
583}
584
585#[derive(Debug)]
587pub struct TGNConv {
588 in_features: usize,
589 out_features: usize,
590 memory_dim: usize,
591 time_encoding_dim: usize,
592 message_function: Parameter,
593 memory_updater: Parameter,
594 node_embedding: Parameter,
595 bias: Option<Parameter>,
596 node_memories: HashMap<usize, Tensor>,
597 last_update_times: HashMap<usize, f64>,
598}
599
600impl TGNConv {
601 pub fn new(
603 in_features: usize,
604 out_features: usize,
605 memory_dim: usize,
606 time_encoding_dim: usize,
607 bias: bool,
608 ) -> Result<Self> {
609 let message_function =
610 Parameter::new(randn(&[in_features + time_encoding_dim, memory_dim])?);
611 let memory_updater = Parameter::new(randn(&[memory_dim * 2, memory_dim])?);
612 let node_embedding = Parameter::new(randn(&[memory_dim, out_features])?);
613
614 let bias = if bias {
615 Some(Parameter::new(zeros(&[out_features])?))
616 } else {
617 None
618 };
619
620 Ok(Self {
621 in_features,
622 out_features,
623 memory_dim,
624 time_encoding_dim,
625 message_function,
626 memory_updater,
627 node_embedding,
628 bias,
629 node_memories: HashMap::new(),
630 last_update_times: HashMap::new(),
631 })
632 }
633
634 pub fn forward(&mut self, temporal_graph: &TemporalGraphData) -> Result<TemporalGraphData> {
639 self.update_memories(temporal_graph)?;
641
642 let output_features = self.generate_embeddings(temporal_graph)?;
644
645 let mut output_graph = temporal_graph.clone();
647 output_graph.current_graph.x = output_features;
648 Ok(output_graph)
649 }
650
651 fn update_memories(&mut self, temporal_graph: &TemporalGraphData) -> Result<()> {
653 let current_time = temporal_graph.current_time;
654 let lookback_time = current_time - temporal_graph.time_window;
655
656 let recent_events = temporal_graph.get_events_in_range(lookback_time, current_time);
658
659 for event in recent_events {
660 if let Some(node_id) = event.node {
661 let message = self.compute_message(event, current_time)?;
663
664 self.update_node_memory(node_id, message, event.time)?;
666 }
667 }
668
669 Ok(())
670 }
671
672 fn compute_message(&self, event: &TemporalEvent, current_time: f64) -> Result<Tensor> {
674 let time_diff = (current_time - event.time) as f32;
676 let mut time_encoding = Vec::new();
677
678 for i in 0..self.time_encoding_dim {
679 let freq = 2.0_f32.powf(i as f32);
680 time_encoding.push((time_diff * freq).sin());
681 }
682
683 let mut message_input = if let Some(ref features) = event.features {
685 features.to_vec()?
686 } else {
687 vec![1.0; self.in_features] };
689
690 message_input.extend(time_encoding);
691
692 let input_tensor = from_vec(
693 message_input,
694 &[1, self.in_features + self.time_encoding_dim],
695 torsh_core::device::DeviceType::Cpu,
696 )?;
697
698 Ok(input_tensor.matmul(&self.message_function.clone_data())?)
700 }
701
702 fn update_node_memory(
704 &mut self,
705 node_id: usize,
706 message: Tensor,
707 event_time: f64,
708 ) -> Result<()> {
709 let current_memory = match self.node_memories.get(&node_id).cloned() {
711 Some(memory) => memory,
712 None => zeros(&[1, self.memory_dim])?,
713 };
714
715 let current_data = current_memory.to_vec()?;
717 let message_data = message.to_vec()?;
718 let mut combined_data = current_data;
719 combined_data.extend(message_data);
720
721 let combined_tensor = from_vec(
722 combined_data,
723 &[1, self.memory_dim * 2],
724 torsh_core::device::DeviceType::Cpu,
725 )?;
726
727 let new_memory = combined_tensor.matmul(&self.memory_updater.clone_data())?;
729
730 self.node_memories.insert(node_id, new_memory);
731 self.last_update_times.insert(node_id, event_time);
732
733 Ok(())
734 }
735
736 fn generate_embeddings(&self, temporal_graph: &TemporalGraphData) -> Result<Tensor> {
738 let num_nodes = temporal_graph.current_graph.num_nodes;
739 let mut embeddings = Vec::new();
740
741 for node_id in 0..num_nodes {
742 let memory = match self.node_memories.get(&node_id).cloned() {
743 Some(memory) => memory,
744 None => zeros(&[1, self.memory_dim])?,
745 };
746
747 let embedding = memory.matmul(&self.node_embedding.clone_data())?;
748 let embedding_data = embedding.to_vec()?;
749 embeddings.extend(embedding_data);
750 }
751
752 let mut output = from_vec(
753 embeddings,
754 &[num_nodes, self.out_features],
755 torsh_core::device::DeviceType::Cpu,
756 )?;
757
758 if let Some(ref bias) = self.bias {
760 output = output.add(&bias.clone_data())?;
761 }
762
763 Ok(output)
764 }
765}
766
767pub mod pooling {
769 use super::*;
770
771 #[derive(Debug, Clone, Copy)]
773 pub enum TemporalPoolingMethod {
774 MostRecent,
775 TimeWeightedMean,
776 ExponentialDecay,
777 AttentionBased,
778 }
779
780 pub fn temporal_pool(
782 temporal_graph: &TemporalGraphData,
783 method: TemporalPoolingMethod,
784 ) -> Result<Tensor> {
785 match method {
786 TemporalPoolingMethod::MostRecent => {
787 Ok(temporal_graph.current_graph.x.mean(Some(&[0]), false)?)
789 }
790 TemporalPoolingMethod::TimeWeightedMean => time_weighted_pool(temporal_graph),
791 TemporalPoolingMethod::ExponentialDecay => exponential_decay_pool(temporal_graph),
792 TemporalPoolingMethod::AttentionBased => attention_temporal_pool(temporal_graph),
793 }
794 }
795
796 fn time_weighted_pool(temporal_graph: &TemporalGraphData) -> Result<Tensor> {
798 let current_time = temporal_graph.current_time;
799 let lookback_time = current_time - temporal_graph.time_window;
800 let recent_events = temporal_graph.get_events_in_range(lookback_time, current_time);
801
802 if recent_events.is_empty() {
803 return Ok(temporal_graph.current_graph.x.mean(Some(&[0]), false)?);
804 }
805
806 let mut weighted_sum = zeros(&[temporal_graph.current_graph.x.shape().dims()[1]])?;
808 let mut total_weight = 0.0;
809
810 for event in recent_events {
811 if let Some(ref features) = event.features {
812 let weight = 1.0 - (current_time - event.time) / temporal_graph.time_window;
813 let weighted_features = features.mul_scalar(weight as f32)?;
814
815 let features_data = weighted_features.to_vec()?;
817 let current_data = weighted_sum.to_vec()?;
818 let mut new_data = Vec::new();
819
820 for (_i, (¤t, &new)) in
821 current_data.iter().zip(features_data.iter()).enumerate()
822 {
823 new_data.push(current + new);
824 }
825
826 weighted_sum = from_vec(
827 new_data,
828 &[weighted_sum.shape().dims()[0]],
829 torsh_core::device::DeviceType::Cpu,
830 )?;
831
832 total_weight += weight;
833 }
834 }
835
836 if total_weight > 0.0 {
837 Ok(weighted_sum.div_scalar(total_weight as f32)?)
838 } else {
839 Ok(temporal_graph.current_graph.x.mean(Some(&[0]), false)?)
840 }
841 }
842
843 fn exponential_decay_pool(temporal_graph: &TemporalGraphData) -> Result<Tensor> {
845 let decay_rate = 0.1; let current_time = temporal_graph.current_time;
847
848 let decay_factor = (-decay_rate * current_time).exp() as f32;
850 Ok(temporal_graph
851 .current_graph
852 .x
853 .mul_scalar(decay_factor)?
854 .mean(Some(&[0]), false)?)
855 }
856
857 fn attention_temporal_pool(temporal_graph: &TemporalGraphData) -> Result<Tensor> {
859 let features = &temporal_graph.current_graph.x;
861 let attention_scores = features.sum_dim(&[1], false)?;
862 let attention_weights = attention_scores.softmax(0)?;
863 let attention_expanded = attention_weights.unsqueeze(-1)?;
864
865 let weighted_features = features.mul(&attention_expanded)?;
866 Ok(weighted_features.sum_dim(&[0], false)?)
867 }
868}
869
870pub mod utils {
872 use super::*;
873
874 pub fn generate_random_events(
876 num_events: usize,
877 num_nodes: usize,
878 time_span: f64,
879 feature_dim: usize,
880 ) -> Result<Vec<TemporalEvent>> {
881 let mut rng = scirs2_core::random::thread_rng();
882 let mut events = Vec::new();
883
884 for _ in 0..num_events {
885 let time = rng.gen_range(0.0..time_span);
886 let event_type = if rng.gen_range(0.0..1.0) < 0.7 {
887 EventType::NodeFeatureUpdate
888 } else {
889 EventType::EdgeAddition
890 };
891
892 let node = if matches!(event_type, EventType::NodeFeatureUpdate) {
893 Some(rng.gen_range(0..num_nodes))
894 } else {
895 None
896 };
897
898 let (source, target) = if matches!(event_type, EventType::EdgeAddition) {
899 let s = rng.gen_range(0..num_nodes);
900 let t = rng.gen_range(0..num_nodes);
901 (Some(s), Some(t))
902 } else {
903 (None, None)
904 };
905
906 let features = if matches!(event_type, EventType::NodeFeatureUpdate) {
907 Some(randn(&[feature_dim])?)
908 } else {
909 None
910 };
911
912 events.push(TemporalEvent {
913 time,
914 event_type,
915 source,
916 target,
917 node,
918 features,
919 weight: Some(rng.gen_range(0.1..1.0)),
920 });
921 }
922
923 events.sort_by(|a, b| {
925 a.time
926 .partial_cmp(&b.time)
927 .unwrap_or(std::cmp::Ordering::Equal)
928 });
929 Ok(events)
930 }
931
932 pub fn create_temporal_graph_from_events(
934 initial_graph: GraphData,
935 events: Vec<TemporalEvent>,
936 time_window: f64,
937 ) -> Result<TemporalGraphData> {
938 let mut temporal_graph = TemporalGraphData::new(initial_graph, time_window, 10000);
939
940 for event in events {
941 temporal_graph.add_event(event)?;
942 }
943
944 Ok(temporal_graph)
945 }
946
947 pub fn temporal_metrics(temporal_graph: &TemporalGraphData) -> TemporalMetrics {
949 let total_events = temporal_graph.events.values().map(|v| v.len()).sum();
950 let unique_nodes_with_events = temporal_graph.node_features_history.len();
951 let time_span = if let (Some(first), Some(last)) = (
952 temporal_graph.events.keys().next(),
953 temporal_graph.events.keys().next_back(),
954 ) {
955 (*last as f64 - *first as f64) / 1000.0
956 } else {
957 0.0
958 };
959
960 let event_rate = if time_span > 0.0 {
961 total_events as f64 / time_span
962 } else {
963 0.0
964 };
965
966 TemporalMetrics {
967 total_events,
968 unique_active_nodes: unique_nodes_with_events,
969 time_span,
970 event_rate,
971 current_time: temporal_graph.current_time,
972 }
973 }
974
975 #[derive(Debug, Clone)]
977 pub struct TemporalMetrics {
978 pub total_events: usize,
979 pub unique_active_nodes: usize,
980 pub time_span: f64,
981 pub event_rate: f64,
982 pub current_time: f64,
983 }
984}
985
986#[cfg(test)]
987mod tests {
988 use super::*;
989 use torsh_core::device::DeviceType;
990
991 #[test]
992 fn test_temporal_graph_creation() {
993 let features = randn(&[4, 3]).unwrap();
994 let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 0.0];
995 let edge_index = from_vec(edges, &[2, 4], DeviceType::Cpu).unwrap();
996 let graph = GraphData::new(features, edge_index);
997
998 let temporal_graph = TemporalGraphData::new(graph, 10.0, 1000);
999
1000 assert_eq!(temporal_graph.current_graph.num_nodes, 4);
1001 assert_eq!(temporal_graph.time_window, 10.0);
1002 assert_eq!(temporal_graph.max_events, 1000);
1003 }
1004
1005 #[test]
1006 fn test_temporal_event_addition() {
1007 let features = randn(&[3, 2]).unwrap();
1008 let edges = vec![0.0, 1.0, 1.0, 2.0];
1009 let edge_index = from_vec(edges, &[2, 2], DeviceType::Cpu).unwrap();
1010 let graph = GraphData::new(features, edge_index);
1011
1012 let mut temporal_graph = TemporalGraphData::new(graph, 5.0, 100);
1013
1014 let event = TemporalEvent {
1015 time: 1.0,
1016 event_type: EventType::NodeFeatureUpdate,
1017 source: None,
1018 target: None,
1019 node: Some(0),
1020 features: Some(randn(&[2]).unwrap()),
1021 weight: None,
1022 };
1023
1024 temporal_graph.add_event(event).expect("add event");
1025
1026 assert_eq!(temporal_graph.current_time, 1.0);
1027 assert!(!temporal_graph.events.is_empty());
1028 }
1029
1030 #[test]
1031 fn test_tgcn_layer() {
1032 let features = randn(&[3, 4]).unwrap();
1033 let edges = vec![0.0, 1.0, 1.0, 2.0];
1034 let edge_index = from_vec(edges, &[2, 2], DeviceType::Cpu).unwrap();
1035 let graph = GraphData::new(features, edge_index);
1036
1037 let temporal_graph = TemporalGraphData::new(graph, 1.0, 100);
1038 let tgcn = TGCNConv::new(4, 8, 16, 64, true).expect("operation should succeed");
1039
1040 let output = tgcn
1041 .forward(&temporal_graph)
1042 .expect("operation should succeed");
1043 assert_eq!(output.current_graph.x.shape().dims(), &[3, 8]);
1044 }
1045
1046 #[test]
1047 fn test_tgat_layer() {
1048 let features = randn(&[4, 6]).unwrap();
1049 let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0];
1050 let edge_index = from_vec(edges, &[2, 3], DeviceType::Cpu).unwrap();
1051 let graph = GraphData::new(features, edge_index);
1052
1053 let temporal_graph = TemporalGraphData::new(graph, 2.0, 200);
1054 let tgat = TGATConv::new(6, 12, 3, 8, 0.1, true).expect("operation should succeed");
1055
1056 let output = tgat
1057 .forward(&temporal_graph)
1058 .expect("operation should succeed");
1059 assert_eq!(output.current_graph.x.shape().dims(), &[4, 12]);
1060 }
1061
1062 #[test]
1063 fn test_temporal_pooling() {
1064 let features = randn(&[5, 4]).unwrap();
1065 let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0];
1066 let edge_index = from_vec(edges, &[2, 4], DeviceType::Cpu).unwrap();
1067 let graph = GraphData::new(features, edge_index);
1068
1069 let temporal_graph = TemporalGraphData::new(graph, 3.0, 150);
1070
1071 let pooled =
1072 pooling::temporal_pool(&temporal_graph, pooling::TemporalPoolingMethod::MostRecent)
1073 .expect("operation should succeed");
1074 assert_eq!(pooled.shape().dims(), &[4]);
1075
1076 let weighted_pooled = pooling::temporal_pool(
1077 &temporal_graph,
1078 pooling::TemporalPoolingMethod::TimeWeightedMean,
1079 )
1080 .expect("operation should succeed");
1081 assert_eq!(weighted_pooled.shape().dims(), &[4]);
1082 }
1083
1084 #[test]
1085 fn test_temporal_utils() {
1086 let events =
1087 utils::generate_random_events(10, 5, 10.0, 3).expect("operation should succeed");
1088 assert_eq!(events.len(), 10);
1089
1090 for i in 1..events.len() {
1092 assert!(events[i].time >= events[i - 1].time);
1093 }
1094
1095 let features = randn(&[5, 3]).unwrap();
1096 let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0, 4.0, 0.0];
1097 let edge_index = from_vec(edges, &[2, 5], DeviceType::Cpu).unwrap();
1098 let graph = GraphData::new(features, edge_index);
1099
1100 let temporal_graph = utils::create_temporal_graph_from_events(graph, events, 5.0)
1101 .expect("operation should succeed");
1102 let metrics = utils::temporal_metrics(&temporal_graph);
1103
1104 assert!(metrics.total_events > 0);
1105 assert!(metrics.time_span >= 0.0);
1106 }
1107
1108 #[test]
1109 fn test_event_time_range_query() {
1110 let features = randn(&[3, 2]).unwrap();
1111 let edges = vec![0.0, 1.0, 1.0, 2.0];
1112 let edge_index = from_vec(edges, &[2, 2], DeviceType::Cpu).unwrap();
1113 let graph = GraphData::new(features, edge_index);
1114
1115 let mut temporal_graph = TemporalGraphData::new(graph, 10.0, 100);
1116
1117 for i in 0..5 {
1119 let event = TemporalEvent {
1120 time: i as f64,
1121 event_type: EventType::NodeFeatureUpdate,
1122 source: None,
1123 target: None,
1124 node: Some(i % 3),
1125 features: Some(randn(&[2]).unwrap()),
1126 weight: None,
1127 };
1128 temporal_graph.add_event(event).expect("add event");
1129 }
1130
1131 let events_in_range = temporal_graph.get_events_in_range(1.0, 3.0);
1132 assert_eq!(events_in_range.len(), 3); }
1134}