1type Result<T, E = torsh_core::error::TorshError> = std::result::Result<T, E>;
5
6use crate::parameter::Parameter;
7use crate::{GraphData, GraphLayer};
8use torsh_tensor::{
9 creation::{randn, zeros},
10 Tensor,
11};
12
13#[derive(Debug)]
15pub struct SAGEConv {
16 in_features: usize,
17 out_features: usize,
18 weight_neighbor: Parameter,
19 weight_self: Parameter,
20 bias: Option<Parameter>,
21}
22
23impl SAGEConv {
24 pub fn new(in_features: usize, out_features: usize, bias: bool) -> Result<Self> {
26 let weight_neighbor = Parameter::new(randn(&[in_features, out_features])?);
27 let weight_self = Parameter::new(randn(&[in_features, out_features])?);
28 let bias = if bias {
29 Some(Parameter::new(zeros(&[out_features])?))
30 } else {
31 None
32 };
33
34 Ok(Self {
35 in_features,
36 out_features,
37 weight_neighbor,
38 weight_self,
39 bias,
40 })
41 }
42
43 pub fn in_features(&self) -> usize {
45 self.in_features
46 }
47
48 pub fn out_features(&self) -> usize {
50 self.out_features
51 }
52
53 pub fn forward(&self, graph: &GraphData) -> Result<GraphData> {
55 let num_nodes = graph.num_nodes;
56 let edge_data = crate::utils::tensor_to_vec2::<f32>(&graph.edge_index)?;
57
58 let mut adjacency_list: Vec<Vec<usize>> = vec![Vec::new(); num_nodes];
60 for j in 0..edge_data[0].len() {
61 let src = edge_data[0][j] as usize;
62 let dst = edge_data[1][j] as usize;
63 adjacency_list[dst].push(src);
64 }
65
66 let mut neighbor_features = zeros(&[num_nodes, self.in_features])?;
68
69 for node in 0..num_nodes {
70 if !adjacency_list[node].is_empty() {
71 let mut aggregated = zeros(&[self.in_features])?;
72
73 for &neighbor in &adjacency_list[node] {
74 let neighbor_slice = graph.x.slice(0, neighbor, neighbor + 1)?.to_tensor()?;
75 let neighbor_feat = neighbor_slice.squeeze(0)?;
76 aggregated = aggregated.add(&neighbor_feat)?;
77 }
78
79 aggregated = aggregated.div_scalar(adjacency_list[node].len() as f32)?;
81 let aggregated_data = aggregated.to_vec()?;
83 for (i, &value) in aggregated_data.iter().enumerate() {
84 neighbor_features.set_item(&[node, i], value)?;
85 }
86 }
87 }
88
89 let neighbor_transformed = neighbor_features.matmul(&self.weight_neighbor.clone_data())?;
91 let self_transformed = graph.x.matmul(&self.weight_self.clone_data())?;
92
93 let mut output_features = neighbor_transformed.add(&self_transformed)?;
95
96 if let Some(ref bias) = self.bias {
98 output_features = output_features.add(&bias.clone_data())?;
99 }
100
101 let norm_val = output_features.norm()?;
104 let epsilon = 1e-8_f32;
105 let norm_scalar = norm_val.item()?.max(epsilon);
106 output_features = output_features.div_scalar(norm_scalar)?;
107
108 Ok(GraphData {
110 x: output_features,
111 edge_index: graph.edge_index.clone(),
112 edge_attr: graph.edge_attr.clone(),
113 batch: graph.batch.clone(),
114 num_nodes: graph.num_nodes,
115 num_edges: graph.num_edges,
116 })
117 }
118}
119
120impl GraphLayer for SAGEConv {
121 fn forward(&self, graph: &GraphData) -> Result<GraphData> {
122 self.forward(graph)
123 }
124
125 fn parameters(&self) -> Vec<Tensor> {
126 let mut params = vec![
127 self.weight_neighbor.clone_data(),
128 self.weight_self.clone_data(),
129 ];
130 if let Some(ref bias) = self.bias {
131 params.push(bias.clone_data());
132 }
133 params
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use torsh_core::device::DeviceType;
141 use torsh_tensor::creation::from_vec;
142
143 #[test]
144 fn test_sage_creation() {
145 let sage = SAGEConv::new(10, 20, true);
146 let params = sage.expect("operation should succeed").parameters();
147 assert_eq!(params.len(), 3); }
149
150 #[test]
151 fn test_sage_forward() {
152 let sage = SAGEConv::new(4, 8, false);
153
154 let x = from_vec(
156 vec![
157 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, ],
161 &[3, 4],
162 DeviceType::Cpu,
163 )
164 .expect("operation should succeed");
165 let edge_index = from_vec(vec![0.0, 1.0, 2.0, 1.0, 2.0, 0.0], &[2, 3], DeviceType::Cpu)
166 .expect("from vec should succeed");
167 let graph = GraphData::new(x, edge_index);
168
169 let output = sage
170 .expect("operation should succeed")
171 .forward(&graph)
172 .expect("operation should succeed");
173 assert_eq!(output.x.shape().dims(), &[3, 8]);
174 assert_eq!(output.num_nodes, 3);
175
176 let output_values = output
178 .x
179 .to_vec()
180 .expect("tensor to_vec conversion should succeed");
181 for &val in &output_values {
182 assert!(val.is_finite(), "Output should be finite");
183 }
184 }
185}