228 lines
6.6 KiB
Rust
228 lines
6.6 KiB
Rust
//! 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
|
|
}
|
|
}
|