1#![allow(dead_code)]
8use crate::GraphData;
9use serde::{Deserialize, Serialize};
10use serde_json;
11use std::collections::HashMap;
12use std::fs::File;
13use std::io::{BufRead, BufReader, Result as IoResult};
14use std::path::Path;
15use torsh_core::device::DeviceType;
16use torsh_tensor::{creation::from_vec, Tensor};
17
18pub trait GraphDatasetLoader {
20 fn load_from_file<P: AsRef<Path>>(&self, path: P) -> IoResult<GraphData>;
22
23 fn load_from_directory<P: AsRef<Path>>(&self, path: P) -> IoResult<Vec<GraphData>>;
25
26 fn supported_extensions(&self) -> Vec<&'static str>;
28}
29
30pub struct EdgeListLoader {
32 pub num_features: usize,
34 pub directed: bool,
36 pub delimiter: char,
38}
39
40impl EdgeListLoader {
41 pub fn new(num_features: usize, directed: bool) -> Self {
42 Self {
43 num_features,
44 directed,
45 delimiter: ' ',
46 }
47 }
48
49 pub fn with_delimiter(mut self, delimiter: char) -> Self {
50 self.delimiter = delimiter;
51 self
52 }
53}
54
55impl GraphDatasetLoader for EdgeListLoader {
56 fn load_from_file<P: AsRef<Path>>(&self, path: P) -> IoResult<GraphData> {
57 let file = File::open(path)?;
58 let reader = BufReader::new(file);
59
60 let mut edges = Vec::new();
61 let mut max_node_id = 0usize;
62
63 for line in reader.lines() {
65 let line = line?;
66 let line = line.trim();
67
68 if line.is_empty() || line.starts_with('#') {
70 continue;
71 }
72
73 let parts: Vec<&str> = line.split(self.delimiter).collect();
74 if parts.len() >= 2 {
75 if let (Ok(src), Ok(dst)) = (parts[0].parse::<usize>(), parts[1].parse::<usize>()) {
76 edges.extend_from_slice(&[src, dst]);
77 max_node_id = max_node_id.max(src).max(dst);
78
79 if !self.directed && src != dst {
81 edges.extend_from_slice(&[dst, src]);
82 }
83 }
84 }
85 }
86
87 let num_nodes = max_node_id + 1;
88 let num_edges = edges.len() / 2;
89
90 let features = (0..num_nodes * self.num_features)
92 .map(|i| (i as f32 * 0.1) % 1.0)
93 .collect::<Vec<f32>>();
94 let x =
95 from_vec(features, &[num_nodes, self.num_features], DeviceType::Cpu).map_err(|e| {
96 std::io::Error::new(
97 std::io::ErrorKind::InvalidData,
98 format!("Tensor creation failed: {:?}", e),
99 )
100 })?;
101
102 let edge_index = from_vec(
104 edges.into_iter().map(|e| e as i64).collect(),
105 &[2, num_edges],
106 DeviceType::Cpu,
107 )
108 .map_err(|e| {
109 std::io::Error::new(
110 std::io::ErrorKind::InvalidData,
111 format!("Edge tensor creation failed: {:?}", e),
112 )
113 })?;
114
115 Ok(GraphData::new(
116 x,
117 edge_index.to_f32_simd().map_err(|e| {
118 std::io::Error::new(
119 std::io::ErrorKind::InvalidData,
120 format!("Edge index conversion failed: {:?}", e),
121 )
122 })?,
123 ))
124 }
125
126 fn load_from_directory<P: AsRef<Path>>(&self, path: P) -> IoResult<Vec<GraphData>> {
127 let mut graphs = Vec::new();
128 let dir = std::fs::read_dir(path)?;
129
130 for entry in dir {
131 let entry = entry?;
132 let path = entry.path();
133
134 if let Some(ext) = path.extension() {
135 if self
136 .supported_extensions()
137 .contains(&ext.to_str().unwrap_or(""))
138 {
139 match self.load_from_file(&path) {
140 Ok(graph) => graphs.push(graph),
141 Err(e) => eprintln!("Warning: Failed to load {}: {}", path.display(), e),
142 }
143 }
144 }
145 }
146
147 Ok(graphs)
148 }
149
150 fn supported_extensions(&self) -> Vec<&'static str> {
151 vec!["edges", "edgelist", "txt"]
152 }
153}
154
155pub struct GMLLoader;
157
158impl GMLLoader {
159 pub fn new() -> Self {
160 Self
161 }
162}
163
164impl GraphDatasetLoader for GMLLoader {
165 fn load_from_file<P: AsRef<Path>>(&self, path: P) -> IoResult<GraphData> {
166 let content = std::fs::read_to_string(path)?;
167 self.parse_gml(&content)
168 }
169
170 fn load_from_directory<P: AsRef<Path>>(&self, path: P) -> IoResult<Vec<GraphData>> {
171 let mut graphs = Vec::new();
172 let dir = std::fs::read_dir(path)?;
173
174 for entry in dir {
175 let entry = entry?;
176 let path = entry.path();
177
178 if let Some(ext) = path.extension() {
179 if self
180 .supported_extensions()
181 .contains(&ext.to_str().unwrap_or(""))
182 {
183 match self.load_from_file(&path) {
184 Ok(graph) => graphs.push(graph),
185 Err(e) => {
186 eprintln!("Warning: Failed to load GML {}: {}", path.display(), e)
187 }
188 }
189 }
190 }
191 }
192
193 Ok(graphs)
194 }
195
196 fn supported_extensions(&self) -> Vec<&'static str> {
197 vec!["gml"]
198 }
199}
200
201impl GMLLoader {
202 fn parse_gml(&self, content: &str) -> IoResult<GraphData> {
203 let mut nodes = HashMap::new();
204 let mut edges = Vec::new();
205 let mut in_node = false;
206 let mut in_edge = false;
207 let mut current_node_id: Option<usize> = None;
208 let mut current_edge_src: Option<usize> = None;
209 let mut current_edge_dst: Option<usize> = None;
210
211 for line in content.lines() {
212 let line = line.trim();
213
214 if line.starts_with("node") {
215 in_node = true;
216 in_edge = false;
217 } else if line.starts_with("edge") {
218 in_edge = true;
219 in_node = false;
220 } else if line == "]" {
221 if in_node {
223 if let Some(id) = current_node_id {
224 nodes.insert(id, vec![1.0; 4]); }
226 current_node_id = None;
227 in_node = false;
228 } else if in_edge {
229 if let (Some(src), Some(dst)) = (current_edge_src, current_edge_dst) {
230 edges.extend_from_slice(&[src, dst]);
231 }
232 current_edge_src = None;
233 current_edge_dst = None;
234 in_edge = false;
235 }
236 } else if in_node && line.starts_with("id") {
237 if let Some(id_str) = line.split_whitespace().nth(1) {
238 current_node_id = id_str.parse().ok();
239 }
240 } else if in_edge && line.starts_with("source") {
241 if let Some(src_str) = line.split_whitespace().nth(1) {
242 current_edge_src = src_str.parse().ok();
243 }
244 } else if in_edge && line.starts_with("target") {
245 if let Some(dst_str) = line.split_whitespace().nth(1) {
246 current_edge_dst = dst_str.parse().ok();
247 }
248 }
249 }
250
251 let num_nodes = nodes.len();
253 let num_edges = edges.len() / 2;
254
255 if num_nodes == 0 {
256 return Err(std::io::Error::new(
257 std::io::ErrorKind::InvalidData,
258 "No nodes found in GML file",
259 ));
260 }
261
262 let mut node_features = Vec::new();
264 let mut node_mapping = HashMap::new();
265 let mut new_id = 0;
266
267 let mut sorted_nodes: Vec<_> = nodes.keys().collect();
268 sorted_nodes.sort();
269
270 for &original_id in sorted_nodes {
271 node_mapping.insert(original_id, new_id);
272 node_features.extend_from_slice(&nodes[&original_id]);
273 new_id += 1;
274 }
275
276 let remapped_edges: Vec<i64> = edges
278 .iter()
279 .map(|&id| *node_mapping.get(&id).unwrap_or(&0) as i64)
280 .collect();
281
282 let x = from_vec(node_features, &[num_nodes, 4], DeviceType::Cpu).map_err(|e| {
283 std::io::Error::new(
284 std::io::ErrorKind::InvalidData,
285 format!("Node features tensor creation failed: {:?}", e),
286 )
287 })?;
288
289 let edge_index: Tensor<i64> = from_vec(remapped_edges, &[2, num_edges], DeviceType::Cpu)
290 .map_err(|e| {
291 std::io::Error::new(
292 std::io::ErrorKind::InvalidData,
293 format!("Edge index tensor creation failed: {:?}", e),
294 )
295 })?;
296
297 Ok(GraphData::new(
298 x,
299 edge_index.to_f32_simd().map_err(|e| {
300 std::io::Error::new(
301 std::io::ErrorKind::InvalidData,
302 format!("Edge index conversion failed: {:?}", e),
303 )
304 })?,
305 ))
306 }
307}
308
309#[derive(Debug, Serialize, Deserialize)]
311pub struct JsonGraphData {
312 pub nodes: Vec<JsonNode>,
313 pub edges: Vec<JsonEdge>,
314 pub directed: Option<bool>,
315 pub multigraph: Option<bool>,
316}
317
318#[derive(Debug, Serialize, Deserialize)]
319pub struct JsonNode {
320 pub id: usize,
321 pub features: Option<Vec<f32>>,
322 pub label: Option<String>,
323}
324
325#[derive(Debug, Serialize, Deserialize)]
326pub struct JsonEdge {
327 pub source: usize,
328 pub target: usize,
329 pub weight: Option<f32>,
330 pub label: Option<String>,
331}
332
333pub struct JSONLoader {
334 pub default_features: usize,
335}
336
337impl JSONLoader {
338 pub fn new(default_features: usize) -> Self {
339 Self { default_features }
340 }
341}
342
343impl GraphDatasetLoader for JSONLoader {
344 fn load_from_file<P: AsRef<Path>>(&self, path: P) -> IoResult<GraphData> {
345 let content = std::fs::read_to_string(path)?;
346 let data: JsonGraphData = serde_json::from_str(&content)
347 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
348
349 self.convert_json_to_graph(data)
350 }
351
352 fn load_from_directory<P: AsRef<Path>>(&self, path: P) -> IoResult<Vec<GraphData>> {
353 let mut graphs = Vec::new();
354 let dir = std::fs::read_dir(path)?;
355
356 for entry in dir {
357 let entry = entry?;
358 let path = entry.path();
359
360 if let Some(ext) = path.extension() {
361 if self
362 .supported_extensions()
363 .contains(&ext.to_str().unwrap_or(""))
364 {
365 match self.load_from_file(&path) {
366 Ok(graph) => graphs.push(graph),
367 Err(e) => {
368 eprintln!("Warning: Failed to load JSON {}: {}", path.display(), e)
369 }
370 }
371 }
372 }
373 }
374
375 Ok(graphs)
376 }
377
378 fn supported_extensions(&self) -> Vec<&'static str> {
379 vec!["json"]
380 }
381}
382
383impl JSONLoader {
384 fn convert_json_to_graph(&self, data: JsonGraphData) -> IoResult<GraphData> {
385 let num_nodes = data.nodes.len();
386 let _num_edges = data.edges.len();
387
388 if num_nodes == 0 {
389 return Err(std::io::Error::new(
390 std::io::ErrorKind::InvalidData,
391 "No nodes in JSON graph data",
392 ));
393 }
394
395 let mut node_mapping = HashMap::new();
397 for (i, node) in data.nodes.iter().enumerate() {
398 node_mapping.insert(node.id, i);
399 }
400
401 let mut node_features = Vec::new();
403 for node in &data.nodes {
404 if let Some(ref features) = node.features {
405 node_features.extend_from_slice(features);
406 } else {
407 node_features.extend((0..self.default_features).map(|i| i as f32 * 0.1));
409 }
410 }
411
412 let feature_dim = node_features.len() / num_nodes;
413
414 let mut edges = Vec::new();
416 for edge in &data.edges {
417 if let (Some(&src), Some(&dst)) = (
418 node_mapping.get(&edge.source),
419 node_mapping.get(&edge.target),
420 ) {
421 edges.extend_from_slice(&[src as i64, dst as i64]);
422
423 if !data.directed.unwrap_or(false) && src != dst {
425 edges.extend_from_slice(&[dst as i64, src as i64]);
426 }
427 }
428 }
429
430 let final_num_edges = edges.len() / 2;
431
432 let x =
433 from_vec(node_features, &[num_nodes, feature_dim], DeviceType::Cpu).map_err(|e| {
434 std::io::Error::new(
435 std::io::ErrorKind::InvalidData,
436 format!("Node features failed: {:?}", e),
437 )
438 })?;
439
440 let edge_index = from_vec(edges, &[2, final_num_edges], DeviceType::Cpu).map_err(|e| {
441 std::io::Error::new(
442 std::io::ErrorKind::InvalidData,
443 format!("Edge index failed: {:?}", e),
444 )
445 })?;
446
447 Ok(GraphData::new(
448 x,
449 edge_index.to_f32_simd().map_err(|e| {
450 std::io::Error::new(
451 std::io::ErrorKind::InvalidData,
452 format!("Edge index conversion failed: {:?}", e),
453 )
454 })?,
455 ))
456 }
457}
458
459pub struct GraphDatasetCollection;
461
462impl GraphDatasetCollection {
463 pub fn create_synthetic_dataset(
465 num_graphs: usize,
466 nodes_per_graph: usize,
467 edge_probability: f64,
468 num_features: usize,
469 ) -> Result<Vec<GraphData>, torsh_core::error::TorshError> {
470 use crate::scirs2_integration::generation;
471
472 (0..num_graphs)
473 .map(|_| {
474 let mut graph = generation::erdos_renyi(nodes_per_graph, edge_probability)?;
475
476 if graph.x.shape().dims()[1] != num_features {
478 let new_features = (0..nodes_per_graph * num_features)
479 .map(|i| (i as f32 * 0.01) % 1.0)
480 .collect();
481 let x = from_vec(
482 new_features,
483 &[nodes_per_graph, num_features],
484 DeviceType::Cpu,
485 )?;
486 graph.x = x;
487 }
488
489 Ok(graph)
490 })
491 .collect()
492 }
493
494 pub fn load_with_augmentation<L: GraphDatasetLoader>(
496 loader: L,
497 path: impl AsRef<Path>,
498 augmentation_factor: usize,
499 ) -> IoResult<Vec<GraphData>> {
500 let base_graphs = loader.load_from_directory(path)?;
501 let mut augmented_graphs = base_graphs.clone();
502
503 for _ in 1..augmentation_factor {
504 for graph in &base_graphs {
505 let augmented = Self::add_feature_noise(graph, 0.1)?;
507 augmented_graphs.push(augmented);
508 }
509 }
510
511 Ok(augmented_graphs)
512 }
513
514 pub fn add_feature_noise(graph: &GraphData, noise_level: f32) -> IoResult<GraphData> {
520 let mut rng = scirs2_core::random::thread_rng();
521 let features = graph.x.to_vec().map_err(|e| {
522 std::io::Error::new(
523 std::io::ErrorKind::InvalidData,
524 format!("node feature conversion failed: {e:?}"),
525 )
526 })?;
527 let noisy_features: Vec<f32> = features
528 .iter()
529 .map(|&x| x + (rng.random::<f32>() - 0.5) * 2.0 * noise_level)
530 .collect();
531
532 let noisy_x =
533 from_vec(noisy_features, graph.x.shape().dims(), DeviceType::Cpu).map_err(|e| {
534 std::io::Error::new(
535 std::io::ErrorKind::InvalidData,
536 format!("noisy feature tensor creation failed: {e:?}"),
537 )
538 })?;
539
540 Ok(GraphData {
541 x: noisy_x,
542 edge_index: graph.edge_index.clone(),
543 edge_attr: graph.edge_attr.clone(),
544 batch: graph.batch.clone(),
545 num_nodes: graph.num_nodes,
546 num_edges: graph.num_edges,
547 })
548 }
549
550 pub fn train_val_test_split(
552 graphs: Vec<GraphData>,
553 train_ratio: f64,
554 val_ratio: f64,
555 ) -> (Vec<GraphData>, Vec<GraphData>, Vec<GraphData>) {
556 let n = graphs.len();
557 let train_size = (n as f64 * train_ratio) as usize;
558 let val_size = (n as f64 * val_ratio) as usize;
559
560 let mut train_graphs = Vec::new();
561 let mut val_graphs = Vec::new();
562 let mut test_graphs = Vec::new();
563
564 for (i, graph) in graphs.into_iter().enumerate() {
565 if i < train_size {
566 train_graphs.push(graph);
567 } else if i < train_size + val_size {
568 val_graphs.push(graph);
569 } else {
570 test_graphs.push(graph);
571 }
572 }
573
574 (train_graphs, val_graphs, test_graphs)
575 }
576}
577
578pub struct GraphSampler {
580 batch_size: usize,
581 shuffle: bool,
582}
583
584impl GraphSampler {
585 pub fn new(batch_size: usize, shuffle: bool) -> Self {
586 Self {
587 batch_size,
588 shuffle,
589 }
590 }
591
592 pub fn sample_batches<'a>(&self, graphs: &'a [GraphData]) -> Vec<Vec<&'a GraphData>> {
594 let mut indices: Vec<usize> = (0..graphs.len()).collect();
595
596 if self.shuffle {
597 let mut rng = scirs2_core::random::thread_rng();
598 for i in (1..indices.len()).rev() {
599 let j = (rng.random::<f64>() * (i + 1) as f64) as usize;
600 indices.swap(i, j);
601 }
602 }
603
604 indices
605 .chunks(self.batch_size)
606 .map(|chunk| chunk.iter().map(|&i| &graphs[i]).collect())
607 .collect()
608 }
609}
610
611pub struct TemporalGraphLoader {
613 pub time_steps: usize,
614 pub node_features: usize,
615}
616
617impl TemporalGraphLoader {
618 pub fn new(time_steps: usize, node_features: usize) -> Self {
619 Self {
620 time_steps,
621 node_features,
622 }
623 }
624
625 pub fn load_temporal_sequence<P: AsRef<Path>>(&self, base_path: P) -> IoResult<Vec<GraphData>> {
627 let mut graphs = Vec::new();
628 let base_path = base_path.as_ref();
629
630 for t in 0..self.time_steps {
631 let file_path = base_path.join(format!("graph_t{}.edges", t));
632
633 if file_path.exists() {
634 let loader = EdgeListLoader::new(self.node_features, true);
635 match loader.load_from_file(&file_path) {
636 Ok(graph) => graphs.push(graph),
637 Err(e) => {
638 return Err(std::io::Error::new(
639 std::io::ErrorKind::InvalidData,
640 format!("failed to load timestep {t}: {e}"),
641 ));
642 }
643 }
644 }
645 }
646
647 Ok(graphs)
648 }
649}
650
651#[cfg(test)]
652mod tests {
653 use super::*;
654 use std::io::Write;
655 use tempfile::NamedTempFile;
656
657 #[test]
658 fn test_edge_list_loader() {
659 let mut temp_file = NamedTempFile::new().unwrap();
660 writeln!(temp_file, "0 1").unwrap();
661 writeln!(temp_file, "1 2").unwrap();
662 writeln!(temp_file, "2 0").unwrap();
663
664 let loader = EdgeListLoader::new(3, false);
665 let graph = loader.load_from_file(temp_file.path()).unwrap();
666
667 assert_eq!(graph.num_nodes, 3);
668 assert_eq!(graph.num_edges, 6); assert_eq!(graph.x.shape().dims(), &[3, 3]);
670 }
671
672 #[test]
673 fn test_json_loader() {
674 let json_data = r#"{
675 "nodes": [
676 {"id": 0, "features": [1.0, 2.0]},
677 {"id": 1, "features": [3.0, 4.0]}
678 ],
679 "edges": [
680 {"source": 0, "target": 1}
681 ],
682 "directed": true
683 }"#;
684
685 let mut temp_file = NamedTempFile::new().unwrap();
686 temp_file.write_all(json_data.as_bytes()).unwrap();
687
688 let loader = JSONLoader::new(2);
689 let graph = loader.load_from_file(temp_file.path()).unwrap();
690
691 assert_eq!(graph.num_nodes, 2);
692 assert_eq!(graph.num_edges, 1);
693 assert_eq!(graph.x.shape().dims(), &[2, 2]);
694 }
695
696 #[test]
697 fn test_graph_dataset_collection() {
698 let graphs = GraphDatasetCollection::create_synthetic_dataset(5, 10, 0.2, 4)
699 .expect("operation should succeed");
700
701 assert_eq!(graphs.len(), 5);
702 for graph in graphs {
703 assert_eq!(graph.num_nodes, 10);
704 assert_eq!(graph.x.shape().dims(), &[10, 4]);
705 }
706 }
707
708 #[test]
709 fn test_train_val_test_split() {
710 let graphs = GraphDatasetCollection::create_synthetic_dataset(100, 10, 0.1, 3)
711 .expect("operation should succeed");
712 let (train, val, test) = GraphDatasetCollection::train_val_test_split(graphs, 0.7, 0.2);
713
714 assert_eq!(train.len(), 70);
715 assert_eq!(val.len(), 20);
716 assert_eq!(test.len(), 10);
717 }
718
719 #[test]
720 fn test_graph_sampler() {
721 let graphs = GraphDatasetCollection::create_synthetic_dataset(10, 5, 0.3, 2)
722 .expect("operation should succeed");
723 let sampler = GraphSampler::new(3, false);
724 let batches = sampler.sample_batches(&graphs);
725
726 assert_eq!(batches.len(), 4); assert_eq!(batches[0].len(), 3);
728 assert_eq!(batches[3].len(), 1); }
730
731 #[test]
732 fn test_feature_noise_augmentation() {
733 let base_graph = GraphDatasetCollection::create_synthetic_dataset(1, 5, 0.4, 3)
734 .expect("operation should succeed")[0]
735 .clone();
736 let noisy_graph = GraphDatasetCollection::add_feature_noise(&base_graph, 0.1)
737 .expect("operation should succeed");
738
739 assert_eq!(noisy_graph.num_nodes, base_graph.num_nodes);
740 assert_eq!(noisy_graph.num_edges, base_graph.num_edges);
741 assert_eq!(noisy_graph.x.shape().dims(), base_graph.x.shape().dims());
742
743 let original_features = base_graph.x.to_vec().unwrap();
745 let noisy_features = noisy_graph.x.to_vec().unwrap();
746
747 let mut differences = 0;
748 for (orig, noisy) in original_features.iter().zip(noisy_features.iter()) {
749 if (orig - noisy).abs() > 1e-6 {
750 differences += 1;
751 }
752 }
753
754 assert!(differences > 0);
756 }
757}