#![allow(dead_code)]
use crate::parameter::Parameter;
use crate::{GraphData, GraphLayer};
use torsh_tensor::{
creation::{from_vec, randn, zeros},
Tensor,
};
#[derive(Debug)]
pub struct GraphFNO {
in_features: usize,
out_features: usize,
hidden_features: usize,
num_modes: usize,
num_layers: usize,
fourier_weights: Vec<Parameter>,
conv_weights: Vec<Parameter>,
input_projection: Parameter,
output_projection: Parameter,
bias: Option<Parameter>,
}
impl GraphFNO {
pub fn new(
in_features: usize,
out_features: usize,
hidden_features: usize,
num_modes: usize,
num_layers: usize,
bias: bool,
) -> Self {
let mut fourier_weights = Vec::new();
let mut conv_weights = Vec::new();
for _ in 0..num_layers {
fourier_weights.push(Parameter::new(
randn(&[hidden_features, hidden_features, num_modes])
.expect("failed to create fourier_weights tensor"),
));
conv_weights.push(Parameter::new(
randn(&[hidden_features, hidden_features])
.expect("failed to create conv_weights tensor"),
));
}
let input_projection = Parameter::new(
randn(&[in_features, hidden_features])
.expect("failed to create input_projection tensor"),
);
let output_projection = Parameter::new(
randn(&[hidden_features, out_features])
.expect("failed to create output_projection tensor"),
);
let bias = if bias {
Some(Parameter::new(
zeros::<f32>(&[out_features]).expect("failed to create bias tensor"),
))
} else {
None
};
Self {
in_features,
out_features,
hidden_features,
num_modes,
num_layers,
fourier_weights,
conv_weights,
input_projection,
output_projection,
bias,
}
}
pub fn forward(&self, graph: &GraphData) -> GraphData {
let _num_nodes = graph.num_nodes;
let mut x = graph
.x
.matmul(&self.input_projection.clone_data())
.expect("operation should succeed");
for layer in 0..self.num_layers {
x = self.fourier_layer(&x, layer, graph);
}
let mut output = x
.matmul(&self.output_projection.clone_data())
.expect("operation should succeed");
if let Some(ref bias) = self.bias {
output = output
.add(&bias.clone_data())
.expect("operation should succeed");
}
let mut output_graph = graph.clone();
output_graph.x = output;
output_graph
}
fn fourier_layer(&self, x: &Tensor, layer: usize, graph: &GraphData) -> Tensor {
let fourier_x = self.graph_fourier_transform(x, graph);
let fourier_weights = &self.fourier_weights[layer];
let spectral_conv = self.spectral_convolution(&fourier_x, fourier_weights);
let spatial_features = self.inverse_graph_fourier_transform(&spectral_conv, graph);
let conv_weights = &self.conv_weights[layer];
let conv_output = spatial_features
.matmul(&conv_weights.clone_data())
.expect("operation should succeed");
let residual = x.add(&conv_output).expect("operation should succeed");
self.relu(&residual)
}
fn graph_fourier_transform(&self, x: &Tensor, graph: &GraphData) -> Tensor {
let num_nodes = graph.num_nodes;
let mut transform_data = Vec::new();
for i in 0..num_nodes {
for j in 0..self.num_modes {
let freq = (j as f32 + 1.0) * std::f32::consts::PI / num_nodes as f32;
let basis = (freq * i as f32).cos();
transform_data.push(basis);
}
}
let transform_matrix = from_vec(
transform_data,
&[num_nodes, self.num_modes],
torsh_core::device::DeviceType::Cpu,
)
.expect("GFT transform matrix creation should succeed");
transform_matrix
.t()
.expect("operation should succeed")
.matmul(x)
.expect("operation should succeed")
}
fn inverse_graph_fourier_transform(&self, fourier_x: &Tensor, graph: &GraphData) -> Tensor {
let num_nodes = graph.num_nodes;
let mut inv_transform_data = Vec::new();
for i in 0..num_nodes {
for j in 0..self.num_modes {
let freq = (j as f32 + 1.0) * std::f32::consts::PI / num_nodes as f32;
let basis = (freq * i as f32).cos();
inv_transform_data.push(basis);
}
}
let inv_transform_matrix = from_vec(
inv_transform_data,
&[num_nodes, self.num_modes],
torsh_core::device::DeviceType::Cpu,
)
.expect("inverse GFT transform matrix creation should succeed");
inv_transform_matrix
.matmul(fourier_x)
.expect("operation should succeed")
}
fn spectral_convolution(&self, fourier_x: &Tensor, weights: &Parameter) -> Tensor {
let weight_data = weights.clone_data();
let weight_2d = weight_data
.slice_tensor(2, 0, 1)
.expect("spectral weight slice should succeed")
.squeeze_tensor(2)
.expect("spectral weight squeeze should succeed");
fourier_x
.matmul(&weight_2d)
.expect("operation should succeed")
}
fn relu(&self, x: &Tensor) -> Tensor {
let data = x.to_vec().expect("conversion should succeed");
let activated_data: Vec<f32> = data.iter().map(|&val| val.max(0.0)).collect();
from_vec(
activated_data,
x.shape().dims(),
torsh_core::device::DeviceType::Cpu,
)
.expect("GraphFNO relu tensor creation should succeed")
}
}
impl GraphLayer for GraphFNO {
fn forward(&self, graph: &GraphData) -> GraphData {
self.forward(graph)
}
fn parameters(&self) -> Vec<Tensor> {
let mut params = vec![
self.input_projection.clone_data(),
self.output_projection.clone_data(),
];
for weight in &self.fourier_weights {
params.push(weight.clone_data());
}
for weight in &self.conv_weights {
params.push(weight.clone_data());
}
if let Some(ref bias) = self.bias {
params.push(bias.clone_data());
}
params
}
}
#[derive(Debug)]
pub struct GraphDeepONet {
trunk_net_features: usize,
branch_net_features: usize,
hidden_features: usize,
output_features: usize,
num_sensors: usize,
branch_layers: Vec<Parameter>,
trunk_layers: Vec<Parameter>,
bias: Option<Parameter>,
}
impl GraphDeepONet {
pub fn new(
trunk_net_features: usize,
branch_net_features: usize,
hidden_features: usize,
output_features: usize,
num_sensors: usize,
num_layers: usize,
bias: bool,
) -> Self {
let mut branch_layers = Vec::new();
let mut trunk_layers = Vec::new();
for i in 0..num_layers {
let in_dim = if i == 0 { num_sensors } else { hidden_features };
let out_dim = if i == num_layers - 1 {
output_features
} else {
hidden_features
};
branch_layers.push(Parameter::new(
randn(&[in_dim, out_dim]).expect("failed to create branch layer tensor"),
));
}
for i in 0..num_layers {
let in_dim = if i == 0 {
trunk_net_features
} else {
hidden_features
};
let out_dim = if i == num_layers - 1 {
output_features
} else {
hidden_features
};
trunk_layers.push(Parameter::new(
randn(&[in_dim, out_dim]).expect("failed to create trunk layer tensor"),
));
}
let bias = if bias {
Some(Parameter::new(
zeros::<f32>(&[output_features]).expect("failed to create DeepONet bias tensor"),
))
} else {
None
};
Self {
trunk_net_features,
branch_net_features,
hidden_features,
output_features,
num_sensors,
branch_layers,
trunk_layers,
bias,
}
}
pub fn forward(
&self,
graph: &GraphData,
sensor_data: &Tensor,
locations: &Tensor,
) -> GraphData {
let branch_output = self.forward_branch_net(sensor_data);
let trunk_output = self.forward_trunk_net(locations);
let combined = self.combine_outputs(&branch_output, &trunk_output);
let mut output = combined;
if let Some(ref bias) = self.bias {
output = output
.add(&bias.clone_data())
.expect("operation should succeed");
}
let mut output_graph = graph.clone();
output_graph.x = output;
output_graph
}
fn forward_branch_net(&self, sensor_data: &Tensor) -> Tensor {
let mut x = sensor_data.clone();
for (i, layer) in self.branch_layers.iter().enumerate() {
x = x
.matmul(&layer.clone_data())
.expect("operation should succeed");
if i < self.branch_layers.len() - 1 {
x = self.tanh(&x);
}
}
x
}
fn forward_trunk_net(&self, locations: &Tensor) -> Tensor {
let mut x = locations.clone();
for (i, layer) in self.trunk_layers.iter().enumerate() {
x = x
.matmul(&layer.clone_data())
.expect("operation should succeed");
if i < self.trunk_layers.len() - 1 {
x = self.tanh(&x);
}
}
x
}
fn combine_outputs(&self, branch_output: &Tensor, trunk_output: &Tensor) -> Tensor {
branch_output
.mul(trunk_output)
.expect("operation should succeed")
}
fn tanh(&self, x: &Tensor) -> Tensor {
let data = x.to_vec().expect("conversion should succeed");
let activated_data: Vec<f32> = data.iter().map(|&val| val.tanh()).collect();
from_vec(
activated_data,
x.shape().dims(),
torsh_core::device::DeviceType::Cpu,
)
.expect("DeepONet tanh tensor creation should succeed")
}
}
impl GraphLayer for GraphDeepONet {
fn forward(&self, graph: &GraphData) -> GraphData {
let sensor_data = graph
.x
.slice_tensor(1, 0, self.num_sensors.min(graph.x.shape().dims()[1]))
.expect("sensor data slice should succeed");
let locations = graph.x.clone();
self.forward(graph, &sensor_data, &locations)
}
fn parameters(&self) -> Vec<Tensor> {
let mut params = Vec::new();
for layer in &self.branch_layers {
params.push(layer.clone_data());
}
for layer in &self.trunk_layers {
params.push(layer.clone_data());
}
if let Some(ref bias) = self.bias {
params.push(bias.clone_data());
}
params
}
}
#[derive(Debug)]
pub struct PhysicsInformedGNN {
in_features: usize,
out_features: usize,
hidden_features: usize,
layers: Vec<Parameter>,
diffusion_coefficient: f32,
reaction_rate: f32,
bias: Option<Parameter>,
}
impl PhysicsInformedGNN {
pub fn new(
in_features: usize,
out_features: usize,
hidden_features: usize,
num_layers: usize,
diffusion_coefficient: f32,
reaction_rate: f32,
bias: bool,
) -> Self {
let mut layers = Vec::new();
for i in 0..num_layers {
let in_dim = if i == 0 { in_features } else { hidden_features };
let out_dim = if i == num_layers - 1 {
out_features
} else {
hidden_features
};
layers.push(Parameter::new(
randn(&[in_dim, out_dim]).expect("failed to create PIGNN layer tensor"),
));
}
let bias = if bias {
Some(Parameter::new(
zeros::<f32>(&[out_features]).expect("failed to create PIGNN bias tensor"),
))
} else {
None
};
Self {
in_features,
out_features,
hidden_features,
layers,
diffusion_coefficient,
reaction_rate,
bias,
}
}
pub fn forward(&self, graph: &GraphData) -> GraphData {
let mut x = graph.x.clone();
for (i, layer) in self.layers.iter().enumerate() {
x = x
.matmul(&layer.clone_data())
.expect("operation should succeed");
if i < self.layers.len() - 1 {
x = self.swish(&x);
}
}
let physics_constrained = self.apply_physics_constraints(&x, graph);
let mut output = physics_constrained;
if let Some(ref bias) = self.bias {
output = output
.add(&bias.clone_data())
.expect("operation should succeed");
}
let mut output_graph = graph.clone();
output_graph.x = output;
output_graph
}
fn apply_physics_constraints(&self, prediction: &Tensor, graph: &GraphData) -> Tensor {
let laplacian = self.compute_graph_laplacian(graph);
let diffusion_term = laplacian
.matmul(prediction)
.expect("operation should succeed")
.mul_scalar(self.diffusion_coefficient)
.expect("operation should succeed");
let reaction_term = prediction
.mul_scalar(self.reaction_rate)
.expect("operation should succeed");
prediction
.add(&diffusion_term)
.expect("operation should succeed")
.add(&reaction_term)
.expect("operation should succeed")
}
fn compute_graph_laplacian(&self, graph: &GraphData) -> Tensor {
let num_nodes = graph.num_nodes;
let _num_edges = graph.num_edges;
let mut adj_data = vec![0.0f32; num_nodes * num_nodes];
let edge_data = graph
.edge_index
.to_vec()
.expect("conversion should succeed");
for i in (0..edge_data.len()).step_by(2) {
if i + 1 < edge_data.len() {
let src = edge_data[i] as usize;
let dst = edge_data[i + 1] as usize;
if src < num_nodes && dst < num_nodes {
adj_data[src * num_nodes + dst] = 1.0;
adj_data[dst * num_nodes + src] = 1.0; }
}
}
let mut degree_data = vec![0.0f32; num_nodes * num_nodes];
for i in 0..num_nodes {
let mut degree = 0.0;
for j in 0..num_nodes {
degree += adj_data[i * num_nodes + j];
}
degree_data[i * num_nodes + i] = degree;
}
let mut laplacian_data = Vec::new();
for i in 0..num_nodes * num_nodes {
laplacian_data.push(degree_data[i] - adj_data[i]);
}
from_vec(
laplacian_data,
&[num_nodes, num_nodes],
torsh_core::device::DeviceType::Cpu,
)
.expect("graph Laplacian tensor creation should succeed")
}
fn swish(&self, x: &Tensor) -> Tensor {
let data = x.to_vec().expect("conversion should succeed");
let activated_data: Vec<f32> = data
.iter()
.map(|&val| val * (1.0 / (1.0 + (-val).exp())))
.collect();
from_vec(
activated_data,
x.shape().dims(),
torsh_core::device::DeviceType::Cpu,
)
.expect("PIGNN swish tensor creation should succeed")
}
}
impl GraphLayer for PhysicsInformedGNN {
fn forward(&self, graph: &GraphData) -> GraphData {
self.forward(graph)
}
fn parameters(&self) -> Vec<Tensor> {
let mut params = Vec::new();
for layer in &self.layers {
params.push(layer.clone_data());
}
if let Some(ref bias) = self.bias {
params.push(bias.clone_data());
}
params
}
}
#[derive(Debug)]
pub struct MultiScaleGNO {
in_features: usize,
out_features: usize,
num_scales: usize,
hidden_features: usize,
scale_operators: Vec<Parameter>,
fusion_weights: Parameter,
output_projection: Parameter,
bias: Option<Parameter>,
}
impl MultiScaleGNO {
pub fn new(
in_features: usize,
out_features: usize,
num_scales: usize,
hidden_features: usize,
bias: bool,
) -> Self {
let mut scale_operators = Vec::new();
for _ in 0..num_scales {
scale_operators.push(Parameter::new(
randn(&[in_features, hidden_features])
.expect("failed to create scale operator tensor"),
));
}
let fusion_weights = Parameter::new(
randn(&[num_scales * hidden_features, hidden_features])
.expect("failed to create fusion_weights tensor"),
);
let output_projection = Parameter::new(
randn(&[hidden_features, out_features])
.expect("failed to create MultiScaleGNO output_projection tensor"),
);
let bias = if bias {
Some(Parameter::new(
zeros::<f32>(&[out_features]).expect("failed to create MultiScaleGNO bias tensor"),
))
} else {
None
};
Self {
in_features,
out_features,
num_scales,
hidden_features,
scale_operators,
fusion_weights,
output_projection,
bias,
}
}
pub fn forward(&self, graph: &GraphData) -> GraphData {
let mut scale_features = Vec::new();
for scale in 0..self.num_scales {
let scale_graph = self.coarsen_graph(graph, scale);
let features = self.process_scale(&scale_graph, scale);
let upsampled = self.upsample_features(&features, graph.num_nodes);
scale_features.push(upsampled);
}
let fused_features = self.fuse_scales(&scale_features);
let mut output = fused_features
.matmul(&self.output_projection.clone_data())
.expect("operation should succeed");
if let Some(ref bias) = self.bias {
output = output
.add(&bias.clone_data())
.expect("operation should succeed");
}
let mut output_graph = graph.clone();
output_graph.x = output;
output_graph
}
fn coarsen_graph(&self, graph: &GraphData, scale: usize) -> GraphData {
let coarsening_factor = 2_usize.pow(scale as u32);
let coarse_nodes = (graph.num_nodes + coarsening_factor - 1) / coarsening_factor;
let mut coarse_features = Vec::new();
for coarse_id in 0..coarse_nodes {
let start_node = coarse_id * coarsening_factor;
let end_node = ((coarse_id + 1) * coarsening_factor).min(graph.num_nodes);
let mut sum_features = vec![0.0f32; graph.x.shape().dims()[1]];
let mut count = 0;
for node_id in start_node..end_node {
let features = graph
.x
.slice_tensor(0, node_id, node_id + 1)
.expect("node feature slice should succeed");
let feature_data = features.to_vec().expect("conversion should succeed");
for (i, &val) in feature_data.iter().enumerate() {
if i < sum_features.len() {
sum_features[i] += val;
}
}
count += 1;
}
if count > 0 {
for val in &mut sum_features {
*val /= count as f32;
}
}
coarse_features.extend(sum_features);
}
let coarse_x = from_vec(
coarse_features,
&[coarse_nodes, graph.x.shape().dims()[1]],
torsh_core::device::DeviceType::Cpu,
)
.expect("coarse node features tensor creation should succeed");
let mut coarse_edges = Vec::new();
for i in 0..coarse_nodes.saturating_sub(1) {
coarse_edges.push(i as f32);
coarse_edges.push((i + 1) as f32);
}
let coarse_edge_index = from_vec(
coarse_edges,
&[2, coarse_nodes.saturating_sub(1)],
torsh_core::device::DeviceType::Cpu,
)
.expect("coarse edge index tensor creation should succeed");
GraphData::new(coarse_x, coarse_edge_index)
}
fn process_scale(&self, graph: &GraphData, scale: usize) -> Tensor {
let operator = &self.scale_operators[scale];
graph
.x
.matmul(&operator.clone_data())
.expect("operation should succeed")
}
fn upsample_features(&self, features: &Tensor, target_nodes: usize) -> Tensor {
let current_nodes = features.shape().dims()[0];
let feature_dim = features.shape().dims()[1];
if current_nodes >= target_nodes {
return features
.slice_tensor(0, 0, target_nodes)
.expect("feature truncation should succeed");
}
let feature_data = features.to_vec().expect("conversion should succeed");
let mut upsampled_data = Vec::new();
for target_id in 0..target_nodes {
let source_id = (target_id * current_nodes) / target_nodes;
let start_idx = source_id * feature_dim;
let end_idx = start_idx + feature_dim;
if end_idx <= feature_data.len() {
upsampled_data.extend(&feature_data[start_idx..end_idx]);
} else {
upsampled_data.extend(vec![0.0f32; feature_dim]);
}
}
from_vec(
upsampled_data,
&[target_nodes, feature_dim],
torsh_core::device::DeviceType::Cpu,
)
.expect("upsampled features tensor creation should succeed")
}
fn fuse_scales(&self, scale_features: &[Tensor]) -> Tensor {
let mut concatenated_data = Vec::new();
let num_nodes = scale_features[0].shape().dims()[0];
for node_id in 0..num_nodes {
for scale_feature in scale_features {
let node_features = scale_feature
.slice_tensor(0, node_id, node_id + 1)
.expect("scale feature slice should succeed");
let feature_data = node_features.to_vec().expect("conversion should succeed");
concatenated_data.extend(feature_data);
}
}
let concatenated = from_vec(
concatenated_data,
&[num_nodes, self.num_scales * self.hidden_features],
torsh_core::device::DeviceType::Cpu,
)
.expect("concatenated scale features tensor creation should succeed");
concatenated
.matmul(&self.fusion_weights.clone_data())
.expect("operation should succeed")
}
}
impl GraphLayer for MultiScaleGNO {
fn forward(&self, graph: &GraphData) -> GraphData {
self.forward(graph)
}
fn parameters(&self) -> Vec<Tensor> {
let mut params = vec![
self.fusion_weights.clone_data(),
self.output_projection.clone_data(),
];
for operator in &self.scale_operators {
params.push(operator.clone_data());
}
if let Some(ref bias) = self.bias {
params.push(bias.clone_data());
}
params
}
}
pub mod utils {
use super::*;
pub fn compute_spectral_features(graph: &GraphData, num_eigenvalues: usize) -> Tensor {
let num_nodes = graph.num_nodes;
let mut spectral_data = Vec::new();
for i in 0..num_nodes {
for j in 0..num_eigenvalues {
let eigenvalue = (j as f32 + 1.0) / num_eigenvalues as f32;
let eigenvector_val = (std::f32::consts::PI * (i as f32 + 1.0) * (j as f32 + 1.0)
/ num_nodes as f32)
.sin();
spectral_data.push(eigenvalue * eigenvector_val);
}
}
from_vec(
spectral_data,
&[num_nodes, num_eigenvalues],
torsh_core::device::DeviceType::Cpu,
)
.expect("spectral features tensor creation should succeed")
}
pub fn generate_operator_data(
num_graphs: usize,
num_nodes: usize,
feature_dim: usize,
) -> Vec<(GraphData, GraphData)> {
let mut rng = scirs2_core::random::thread_rng();
let mut data_pairs = Vec::new();
for _ in 0..num_graphs {
let input_features = randn(&[num_nodes, feature_dim])
.expect("input features tensor creation should succeed");
let mut edge_data = Vec::new();
for _ in 0..(num_nodes * 2) {
let src = rng.gen_range(0..num_nodes) as f32;
let dst = rng.gen_range(0..num_nodes) as f32;
edge_data.push(src);
edge_data.push(dst);
}
let edge_index = from_vec(
edge_data,
&[2, num_nodes * 2],
torsh_core::device::DeviceType::Cpu,
)
.expect("edge index tensor creation should succeed");
let input_graph = GraphData::new(input_features, edge_index);
let output_features = input_graph
.x
.mul_scalar(2.0)
.expect("output transformation should succeed");
let output_graph = GraphData::new(output_features, input_graph.edge_index.clone());
data_pairs.push((input_graph, output_graph));
}
data_pairs
}
pub fn compute_operator_error(predicted: &GraphData, target: &GraphData) -> f32 {
let pred_data = predicted.x.to_vec().expect("conversion should succeed");
let target_data = target.x.to_vec().expect("conversion should succeed");
let mut mse = 0.0;
let mut count = 0;
for (pred, target) in pred_data.iter().zip(target_data.iter()) {
mse += (pred - target).powi(2);
count += 1;
}
if count > 0 {
mse / count as f32
} else {
0.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use torsh_core::device::DeviceType;
#[test]
fn test_graph_fno_creation() {
let fno = GraphFNO::new(4, 8, 16, 10, 3, true);
assert_eq!(fno.in_features, 4);
assert_eq!(fno.out_features, 8);
assert_eq!(fno.hidden_features, 16);
assert_eq!(fno.num_modes, 10);
assert_eq!(fno.num_layers, 3);
}
#[test]
fn test_graph_fno_forward() {
let features = randn(&[5, 4]).unwrap();
let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0];
let edge_index = from_vec(edges, &[2, 4], DeviceType::Cpu).unwrap();
let graph = GraphData::new(features, edge_index);
let fno = GraphFNO::new(4, 8, 16, 10, 3, true);
let output = fno.forward(&graph);
assert_eq!(output.x.shape().dims(), &[5, 8]);
}
#[test]
fn test_graph_deeponet_creation() {
let deeponet = GraphDeepONet::new(3, 4, 16, 8, 10, 3, true);
assert_eq!(deeponet.trunk_net_features, 3);
assert_eq!(deeponet.branch_net_features, 4);
assert_eq!(deeponet.output_features, 8);
assert_eq!(deeponet.num_sensors, 10);
}
#[test]
fn test_physics_informed_gnn() {
let features = randn(&[4, 3]).unwrap();
let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0];
let edge_index = from_vec(edges, &[2, 3], DeviceType::Cpu).unwrap();
let graph = GraphData::new(features, edge_index);
let pignn = PhysicsInformedGNN::new(3, 6, 12, 2, 0.1, 0.05, true);
let output = pignn.forward(&graph);
assert_eq!(output.x.shape().dims(), &[4, 6]);
}
#[test]
fn test_multi_scale_gno() {
let features = randn(&[8, 4]).unwrap();
let edges = vec![
0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 6.0, 6.0, 7.0,
];
let edge_index = from_vec(edges, &[2, 7], DeviceType::Cpu).unwrap();
let graph = GraphData::new(features, edge_index);
let ms_gno = MultiScaleGNO::new(4, 6, 3, 8, true);
let output = ms_gno.forward(&graph);
assert_eq!(output.x.shape().dims(), &[8, 6]);
}
#[test]
fn test_spectral_features() {
let features = randn(&[6, 3]).unwrap();
let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0, 4.0, 5.0];
let edge_index = from_vec(edges, &[2, 5], DeviceType::Cpu).unwrap();
let graph = GraphData::new(features, edge_index);
let spectral_features = utils::compute_spectral_features(&graph, 4);
assert_eq!(spectral_features.shape().dims(), &[6, 4]);
}
#[test]
fn test_operator_data_generation() {
let data_pairs = utils::generate_operator_data(3, 5, 4);
assert_eq!(data_pairs.len(), 3);
for (input, output) in &data_pairs {
assert_eq!(input.num_nodes, 5);
assert_eq!(output.num_nodes, 5);
assert_eq!(input.x.shape().dims()[1], 4);
assert_eq!(output.x.shape().dims()[1], 4);
}
}
#[test]
fn test_operator_error_computation() {
let features1 = from_vec(vec![1.0, 2.0, 3.0, 4.0], &[2, 2], DeviceType::Cpu).unwrap();
let features2 = from_vec(vec![1.1, 2.1, 3.1, 4.1], &[2, 2], DeviceType::Cpu).unwrap();
let edges = vec![0.0, 1.0];
let edge_index = from_vec(edges, &[2, 1], DeviceType::Cpu).unwrap();
let graph1 = GraphData::new(features1, edge_index.clone());
let graph2 = GraphData::new(features2, edge_index);
let error = utils::compute_operator_error(&graph1, &graph2);
assert!(error > 0.0);
assert!(error < 1.0); }
}