//! Comprehensive tests for GraphSAGE layer functionality use rtx_geom::{ Edge, GeomError, Graph, Node, layers::{GNNLayer, GraphSAGELayer}, }; use rtx_tensor::{Device, Tensor}; #[cfg(test)] mod graphsage_tests { use super::*; fn create_test_device() -> Device { Device::cpu() } fn create_test_graph() -> Graph { let mut graph = Graph::new(); // Add nodes with features for i in 0..5 { let features = Tensor::from_data( vec![i as f32, (i * 2) as f32, (i * 3) as f32], [3], &create_test_device(), ) .unwrap(); graph.add_node(Node::new(features)); } // Get the NodeIds that were returned let node_ids: Vec<_> = graph.nodes(); // Add edges using actual NodeIds // 0 -> 1, 0 -> 2 graph .add_edge( node_ids[0], node_ids[1], Edge::new(Tensor::from_data(vec![1.0], [1], &create_test_device()).unwrap()), ) .unwrap(); graph .add_edge( node_ids[0], node_ids[2], Edge::new(Tensor::from_data(vec![0.5], [1], &create_test_device()).unwrap()), ) .unwrap(); // 1 -> 3 graph .add_edge( node_ids[1], node_ids[3], Edge::new(Tensor::from_data(vec![2.0], [1], &create_test_device()).unwrap()), ) .unwrap(); // 2 -> 3, 2 -> 4 graph .add_edge( node_ids[2], node_ids[3], Edge::new(Tensor::from_data(vec![1.5], [1], &create_test_device()).unwrap()), ) .unwrap(); graph .add_edge( node_ids[2], node_ids[4], Edge::new(Tensor::from_data(vec![0.8], [1], &create_test_device()).unwrap()), ) .unwrap(); // 3 -> 4 graph .add_edge( node_ids[3], node_ids[4], Edge::new(Tensor::from_data(vec![1.2], [1], &create_test_device()).unwrap()), ) .unwrap(); graph } #[test] fn test_graphsage_layer_creation() { let mut layer = GraphSAGELayer::new(3, 8, 2).unwrap(); // Test basic layer properties assert_eq!(layer.parameters().len(), 3); // weight_self, weight_neighbor, bias } #[test] #[ignore = "Pre-existing device mismatch (CPU vs Metal) error"] fn test_mean_aggregation() { let mut layer = GraphSAGELayer::new(3, 4, 10).unwrap(); let graph = create_test_graph(); // Test forward pass let output = layer.forward(&graph); assert!(output.is_ok()); let output = output.unwrap(); assert_eq!(output.len(), 5); // 5 nodes in the graph assert_eq!(output[0].shape().dims(), &[4]); // output dimension is 4 } #[test] #[ignore = "Pre-existing device mismatch (CPU vs Metal) error"] fn test_neighbor_sampling() { let mut layer = GraphSAGELayer::new(3, 4, 1).unwrap(); // Sample only 1 neighbor let graph = create_test_graph(); let output = layer.forward(&graph); assert!(output.is_ok()); let output = output.unwrap(); assert_eq!(output.len(), 5); } #[test] #[ignore = "Pre-existing device mismatch (CPU vs Metal) error"] fn test_batch_processing() { let mut layer = GraphSAGELayer::new(3, 4, 10).unwrap(); let graph = create_test_graph(); // Process entire graph let output = layer.forward(&graph); assert!(output.is_ok()); let output = output.unwrap(); assert_eq!(output.len(), 5); // All 5 nodes processed } #[test] #[ignore = "Pre-existing device mismatch (CPU vs Metal) error"] fn test_isolated_node() { let mut graph = Graph::new(); // Add isolated node let features = Tensor::from_data(vec![1.0, 2.0, 3.0], [3], &create_test_device()).unwrap(); graph.add_node(Node::new(features)); let mut layer = GraphSAGELayer::new(3, 4, 10).unwrap(); let output = layer.forward(&graph); // Should handle isolated nodes gracefully assert!(output.is_ok()); let output = output.unwrap(); assert_eq!(output.len(), 1); assert_eq!(output[0].shape().dims(), &[4]); } #[test] #[ignore = "Pre-existing device mismatch (CPU vs Metal) error"] fn test_subgraph_extraction() { let graph = create_test_graph(); let mut layer = GraphSAGELayer::new(3, 4, 2).unwrap(); let node_ids = graph.nodes(); let neighbors = graph.neighbors(node_ids[0]); // Verify graph structure assert!(neighbors.len() > 0); // Process entire graph let output = layer.forward(&graph); assert!(output.is_ok()); } #[test] #[ignore = "Pre-existing device mismatch (CPU vs Metal) error"] fn test_edge_weight_preservation() { let graph = create_test_graph(); let mut layer = GraphSAGELayer::new(3, 4, 10).unwrap(); // Forward should preserve edge weights in aggregation let output = layer.forward(&graph); assert!(output.is_ok()); let output = output.unwrap(); assert_eq!(output.len(), 5); } #[test] #[ignore = "Pre-existing device mismatch (CPU vs Metal) error"] fn test_gradients() { let mut layer = GraphSAGELayer::new(3, 4, 10).unwrap(); let graph = create_test_graph(); // Enable gradients layer.enable_gradients(true); // Forward pass let output = layer.forward(&graph).unwrap(); // Backward pass let result = layer.backward(&output); assert!(result.is_ok()); assert!(layer.has_computed_gradients()); } #[test] fn test_empty_graph() { let graph = Graph::new(); let mut layer = GraphSAGELayer::new(3, 4, 10).unwrap(); let result = layer.forward(&graph); assert!(result.is_err()); } #[test] fn test_layer_parameters() { let layer = GraphSAGELayer::new(3, 4, 10).unwrap(); let params = layer.parameters(); assert_eq!(params.len(), 3); // weight_self, weight_neighbor, bias // Check parameter shapes assert_eq!(params[0].shape().dims(), &[3, 4]); // weight_self assert_eq!(params[1].shape().dims(), &[3, 4]); // weight_neighbor assert_eq!(params[2].shape().dims(), &[4]); // bias } }