#![allow(dead_code)]
use crate::parameter::Parameter;
use crate::{GraphData, GraphLayer};
use torsh_tensor::{
creation::{randn, zeros},
Tensor,
};
#[derive(Debug)]
pub struct GINConv {
in_features: usize,
out_features: usize,
eps: f64,
train_eps: bool,
eps_param: Option<Parameter>,
mlp: Vec<Parameter>, bias: Option<Parameter>,
}
impl GINConv {
pub fn new(
in_features: usize,
out_features: usize,
eps: f64,
train_eps: bool,
bias: bool,
) -> Self {
let eps_param = if train_eps {
Some(Parameter::new(
torsh_tensor::creation::tensor_scalar(eps as f32)
.expect("failed to create epsilon scalar"),
))
} else {
None
};
let hidden_dim = (in_features + out_features) / 2;
let mlp = vec![
Parameter::new(
randn(&[in_features, hidden_dim]).expect("failed to create MLP layer 1 weights"),
),
Parameter::new(
randn(&[hidden_dim, out_features]).expect("failed to create MLP layer 2 weights"),
),
];
let bias = if bias {
Some(Parameter::new(
zeros(&[out_features]).expect("failed to create bias tensor"),
))
} else {
None
};
Self {
in_features,
out_features,
eps,
train_eps,
eps_param,
mlp,
bias,
}
}
pub fn forward(&self, graph: &GraphData) -> GraphData {
let num_nodes = graph.num_nodes;
let edge_flat = graph
.edge_index
.to_vec()
.expect("conversion should succeed");
let num_edges = edge_flat.len() / 2;
let edge_data = vec![
edge_flat[0..num_edges].to_vec(),
edge_flat[num_edges..].to_vec(),
];
let mut adjacency_list: Vec<Vec<usize>> = vec![Vec::new(); num_nodes];
for j in 0..edge_data[0].len() {
let src = edge_data[0][j] as usize;
let dst = edge_data[1][j] as usize;
if src < num_nodes && dst < num_nodes {
adjacency_list[dst].push(src);
}
}
let neighbor_features = zeros(&[num_nodes, self.in_features])
.expect("failed to create neighbor features tensor");
for node in 0..num_nodes {
let mut aggregated =
zeros(&[self.in_features]).expect("failed to create aggregated features tensor");
for &neighbor in &adjacency_list[node] {
let neighbor_feat = graph
.x
.slice_tensor(0, neighbor, neighbor + 1)
.expect("failed to slice neighbor features")
.squeeze_tensor(0)
.expect("failed to squeeze neighbor features");
aggregated = aggregated
.add(&neighbor_feat)
.expect("operation should succeed");
}
let mut node_slice = neighbor_features
.slice_tensor(0, node, node + 1)
.expect("failed to slice neighbor features");
let _ = node_slice.copy_(
&aggregated
.unsqueeze_tensor(0)
.expect("failed to unsqueeze aggregated features"),
);
}
let epsilon = if let Some(ref eps_param) = self.eps_param {
eps_param
.clone_data()
.to_vec()
.expect("conversion should succeed")[0] as f64
} else {
self.eps
};
let self_weighted = graph
.x
.mul_scalar((1.0 + epsilon) as f32)
.expect("failed to scale self features");
let combined_features = self_weighted
.add(&neighbor_features)
.expect("operation should succeed");
let mut output = combined_features
.matmul(&self.mlp[0].clone_data())
.expect("operation should succeed");
let zero_tensor =
zeros(output.shape().dims()).expect("failed to create zero tensor for ReLU");
output = output
.maximum(&zero_tensor)
.expect("failed to apply ReLU activation");
output = output
.matmul(&self.mlp[1].clone_data())
.expect("operation should succeed");
if let Some(ref bias) = self.bias {
output = output
.add(&bias.clone_data())
.expect("operation should succeed");
}
GraphData {
x: output,
edge_index: graph.edge_index.clone(),
edge_attr: graph.edge_attr.clone(),
batch: graph.batch.clone(),
num_nodes: graph.num_nodes,
num_edges: graph.num_edges,
}
}
}
impl GraphLayer for GINConv {
fn forward(&self, graph: &GraphData) -> GraphData {
self.forward(graph)
}
fn parameters(&self) -> Vec<Tensor> {
let mut params = vec![self.mlp[0].clone_data(), self.mlp[1].clone_data()];
if let Some(ref eps_param) = self.eps_param {
params.push(eps_param.clone_data());
}
if let Some(ref bias) = self.bias {
params.push(bias.clone_data());
}
params
}
}
#[cfg(test)]
mod tests {
use super::*;
use torsh_core::device::DeviceType;
use torsh_tensor::creation::from_vec;
#[test]
fn test_gin_creation() {
let gin = GINConv::new(8, 16, 0.5, true, true);
let params = gin.parameters();
assert!(params.len() >= 2); assert!(params.len() <= 4); }
#[test]
fn test_gin_forward() {
let gin = GINConv::new(4, 6, 0.0, false, false);
let x = from_vec(
vec![
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, ],
&[3, 4],
DeviceType::Cpu,
)
.expect("operation should succeed");
let edge_index = from_vec(vec![0.0, 1.0, 2.0, 1.0, 2.0, 0.0], &[2, 3], DeviceType::Cpu)
.expect("from vec should succeed");
let graph = GraphData::new(x, edge_index);
let output = gin.forward(&graph);
assert_eq!(output.x.shape().dims(), &[3, 6]);
assert_eq!(output.num_nodes, 3);
}
#[test]
fn test_gin_trainable_eps() {
let gin_fixed = GINConv::new(4, 8, 1.0, false, false);
let gin_trainable = GINConv::new(4, 8, 1.0, true, false);
let fixed_params = gin_fixed.parameters();
let trainable_params = gin_trainable.parameters();
assert_eq!(trainable_params.len(), fixed_params.len() + 1);
}
}