1#![allow(dead_code)]
4type Result<T, E = torsh_core::error::TorshError> = std::result::Result<T, E>;
7
8use crate::parameter::Parameter;
9use crate::{GraphData, GraphLayer};
10use torsh_tensor::{
11 creation::{randn, zeros},
12 Tensor,
13};
14
15#[derive(Debug)]
17pub struct GATConv {
18 in_features: usize,
19 out_features: usize,
20 heads: usize,
21 weight: Parameter,
22 attention: Parameter,
23 bias: Option<Parameter>,
24 dropout: f32,
25}
26
27impl GATConv {
28 pub fn new(
30 in_features: usize,
31 out_features: usize,
32 heads: usize,
33 dropout: f32,
34 bias: bool,
35 ) -> Result<Self> {
36 let weight = Parameter::new(randn(&[in_features, heads * out_features])?);
37 let attention = Parameter::new(randn(&[heads, 2 * out_features])?);
38 let bias = if bias {
39 Some(Parameter::new(zeros(&[heads * out_features])?))
40 } else {
41 None
42 };
43
44 Ok(Self {
45 in_features,
46 out_features,
47 heads,
48 weight,
49 attention,
50 bias,
51 dropout,
52 })
53 }
54
55 pub fn forward(&self, graph: &GraphData) -> Result<GraphData> {
57 let num_nodes = graph.num_nodes;
58
59 let x_transformed = graph.x.matmul(&self.weight.clone_data())?;
61
62 let x_reshaped = x_transformed.view(&[
64 num_nodes as i32,
65 self.heads as i32,
66 self.out_features as i32,
67 ])?;
68
69 let edge_flat = graph.edge_index.to_vec()?;
71 let num_edges = graph.num_edges;
72
73 let src_nodes: Vec<usize> = (0..num_edges).map(|i| edge_flat[i] as usize).collect();
75 let dst_nodes: Vec<usize> = (0..num_edges)
76 .map(|i| edge_flat[i + num_edges] as usize)
77 .collect();
78
79 let mut output = zeros(&[num_nodes, self.heads * self.out_features])?;
81
82 for head in 0..self.heads {
84 let attention_head = self
86 .attention
87 .clone_data()
88 .slice_tensor(0, head, head + 1)?
89 .squeeze_tensor(0)?;
90
91 let mut attention_scores = Vec::with_capacity(num_edges);
93
94 for edge_idx in 0..num_edges {
95 let src = src_nodes[edge_idx];
96 let dst = dst_nodes[edge_idx];
97
98 let src_feat = x_reshaped
100 .slice_tensor(0, src, src + 1)?
101 .slice_tensor(1, head, head + 1)?
102 .squeeze_tensor(0)?
103 .squeeze_tensor(0)?;
104
105 let dst_feat = x_reshaped
106 .slice_tensor(0, dst, dst + 1)?
107 .slice_tensor(1, head, head + 1)?
108 .squeeze_tensor(0)?
109 .squeeze_tensor(0)?;
110
111 let concat_feat = Tensor::cat(&[&src_feat, &dst_feat], 0)?;
113
114 let attention_coeff = attention_head.mul(&concat_feat)?.sum()?;
117
118 let coeff_val = attention_coeff.to_vec()?[0] as f64;
120 let activated_val = if coeff_val > 0.0 {
121 coeff_val
122 } else {
123 0.2 * coeff_val };
125
126 attention_scores.push((src, dst, activated_val));
127 }
128
129 let mut normalized_scores = vec![0.0; num_edges];
131 for node in 0..num_nodes {
132 let mut node_edge_indices = Vec::new();
134 let mut node_scores = Vec::new();
135
136 for (edge_idx, (_, dst, score)) in attention_scores.iter().enumerate() {
137 if *dst == node {
138 node_edge_indices.push(edge_idx);
139 node_scores.push(*score);
140 }
141 }
142
143 if !node_scores.is_empty() {
144 let max_score = node_scores.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
146 let exp_scores: Vec<f64> =
147 node_scores.iter().map(|s| (*s - max_score).exp()).collect();
148 let sum_exp: f64 = exp_scores.iter().sum();
149
150 for (i, &edge_idx) in node_edge_indices.iter().enumerate() {
151 normalized_scores[edge_idx] = exp_scores[i] / sum_exp;
152 }
153 }
154 }
155
156 let head_output = zeros(&[num_nodes, self.out_features])?;
158
159 for node in 0..num_nodes {
160 let mut node_output = zeros(&[self.out_features])?;
161
162 for (edge_idx, (src, dst, _)) in attention_scores.iter().enumerate() {
163 if *dst == node {
164 let weight = normalized_scores[edge_idx];
165 if weight > 0.0 {
166 let src_feat = x_reshaped
167 .slice_tensor(0, *src, *src + 1)?
168 .slice_tensor(1, head, head + 1)?
169 .squeeze_tensor(0)?
170 .squeeze_tensor(0)?;
171
172 let weighted_feat = src_feat.mul_scalar(weight as f32)?;
173 node_output = node_output.add(&weighted_feat)?;
174 }
175 }
176 }
177
178 let mut node_slice = head_output.slice_tensor(0, node, node + 1)?;
180 let _ = node_slice.copy_(&node_output.unsqueeze_tensor(0)?);
181 }
182
183 let start_feat = head * self.out_features;
185 let end_feat = (head + 1) * self.out_features;
186 let mut output_slice = output.slice_tensor(1, start_feat, end_feat)?;
187 let _ = output_slice.copy_(&head_output);
188 }
189
190 if let Some(ref bias) = self.bias {
192 output = output.add(&bias.clone_data())?;
193 }
194
195 if self.dropout > 0.0 {
197 }
200
201 Ok(GraphData {
202 x: output,
203 edge_index: graph.edge_index.clone(),
204 edge_attr: graph.edge_attr.clone(),
205 batch: graph.batch.clone(),
206 num_nodes: graph.num_nodes,
207 num_edges: graph.num_edges,
208 })
209 }
210}
211
212impl GraphLayer for GATConv {
213 fn forward(&self, graph: &GraphData) -> Result<GraphData> {
214 self.forward(graph)
215 }
216
217 fn parameters(&self) -> Vec<Tensor> {
218 let mut params = vec![self.weight.clone_data(), self.attention.clone_data()];
219 if let Some(ref bias) = self.bias {
220 params.push(bias.clone_data());
221 }
222 params
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use torsh_core::device::DeviceType;
230 use torsh_tensor::creation::from_vec;
231
232 #[test]
233 fn test_gat_creation() {
234 let gat = GATConv::new(16, 8, 4, 0.1, true).expect("operation should succeed");
235 let params = gat.parameters();
236 assert_eq!(params.len(), 3); assert_eq!(gat.heads, 4);
238 }
239
240 #[test]
241 fn test_gat_forward() {
242 let gat = GATConv::new(3, 4, 2, 0.0, false);
243
244 let x = from_vec(
246 vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
247 &[3, 3],
248 DeviceType::Cpu,
249 )
250 .expect("operation should succeed");
251 let edge_index = from_vec(vec![0.0, 1.0, 2.0, 1.0, 2.0, 0.0], &[2, 3], DeviceType::Cpu)
252 .expect("from vec should succeed");
253 let graph = GraphData::new(x, edge_index);
254
255 let output = gat
256 .expect("operation should succeed")
257 .forward(&graph)
258 .expect("operation should succeed");
259 assert_eq!(output.x.shape().dims(), &[3, 8]); assert_eq!(output.num_nodes, 3);
261 }
262}