use numrs2::new_modules::nn::graph::*;
use scirs2_core::ndarray::Array2;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== GCN Node Classification Example ===\n");
let num_nodes = 7;
let edges = vec![
(0, 1),
(1, 0), (0, 2),
(2, 0),
(1, 2),
(2, 1),
(3, 4),
(4, 3),
(2, 3),
(3, 2), (5, 6),
(6, 5),
(1, 5),
(5, 1), ];
println!("Citation Network:");
println!(" Nodes: {} papers", num_nodes);
println!(" Edges: {} citations", edges.len());
println!(" Research areas: ML (0,1,2), CV (3,4), NLP (5,6)\n");
let adj = AdjacencyMatrix::<f64>::from_edges(num_nodes, &edges)?;
println!("Adjacency matrix created");
let degrees = adj.degree_matrix()?;
println!("Node degrees (citation counts):");
for (i, °) in degrees.iter().enumerate() {
println!(" Paper {}: {} citations", i, deg);
}
println!();
let node_features = Array2::from_shape_fn((num_nodes, 10), |(i, j)| {
let area = if i <= 2 {
0
} else if i <= 4 {
1
} else {
2
};
let base = (area * 3 + j) as f64 / 10.0;
let noise = ((i * 7 + j * 13) % 20) as f64 / 100.0;
base + noise
});
println!("Node features shape: {:?}", node_features.shape());
println!(
"Feature range: [{:.3}, {:.3}]",
node_features.iter().cloned().fold(f64::INFINITY, f64::min),
node_features
.iter()
.cloned()
.fold(f64::NEG_INFINITY, f64::max)
);
println!();
let input_dim = 10;
let hidden_dim = 16;
let num_classes = 3;
println!("Building GCN architecture:");
println!(" Layer 1: {} -> {} (GCN + ReLU)", input_dim, hidden_dim);
println!(" Layer 2: {} -> {} (GCN)", hidden_dim, num_classes);
println!();
let gcn_layer1 = GcnLayer::new(input_dim, hidden_dim)?;
let gcn_layer2 = GcnLayer::new(hidden_dim, num_classes)?;
println!("Forward pass...");
let hidden = gcn_layer1.forward(&adj, &node_features.view())?;
println!(" After layer 1: shape {:?}", hidden.shape());
let mut hidden_relu = hidden.clone();
for i in 0..hidden_relu.nrows() {
for j in 0..hidden_relu.ncols() {
if hidden_relu[[i, j]] < 0.0 {
hidden_relu[[i, j]] = 0.0;
}
}
}
println!(
" After ReLU: {} activations zeroed",
hidden.iter().filter(|&&x| x < 0.0).count()
);
let logits = gcn_layer2.forward(&adj, &hidden_relu.view())?;
println!(" After layer 2: shape {:?}", logits.shape());
let mut predictions = Array2::zeros((num_nodes, num_classes));
for i in 0..num_nodes {
let mut max_logit = logits[[i, 0]];
for j in 1..num_classes {
if logits[[i, j]] > max_logit {
max_logit = logits[[i, j]];
}
}
let mut sum_exp = 0.0;
for j in 0..num_classes {
sum_exp += (logits[[i, j]] - max_logit).exp();
}
for j in 0..num_classes {
predictions[[i, j]] = (logits[[i, j]] - max_logit).exp() / sum_exp;
}
}
println!("\nNode Classification Results:");
println!("{:-<60}", "");
println!(
"{:<8} {:^15} {:^15} {:^15}",
"Paper", "ML Prob", "CV Prob", "NLP Prob"
);
println!("{:-<60}", "");
for i in 0..num_nodes {
let area_label = if i <= 2 {
"ML*"
} else if i <= 4 {
"CV*"
} else {
"NLP*"
};
println!(
"{:<6} {} {:^15.4} {:^15.4} {:^15.4}",
i,
area_label,
predictions[[i, 0]],
predictions[[i, 1]],
predictions[[i, 2]]
);
}
println!("{:-<60}", "");
println!("* indicates ground truth label");
println!("\nGraph Structure Analysis:");
println!(" Symmetric normalization preserves total probability mass");
println!(" Node features are aggregated from citation neighborhood");
println!(" Papers with similar citations get similar representations");
let mut correct = 0;
for i in 0..num_nodes {
let true_class = if i <= 2 {
0
} else if i <= 4 {
1
} else {
2
};
let mut pred_class = 0;
let mut max_prob = predictions[[i, 0]];
for j in 1..num_classes {
if predictions[[i, j]] > max_prob {
max_prob = predictions[[i, j]];
pred_class = j;
}
}
if pred_class == true_class {
correct += 1;
}
}
println!("\nPerformance (random initialization):");
println!(
" Accuracy: {}/{} = {:.1}%",
correct,
num_nodes,
100.0 * correct as f64 / num_nodes as f64
);
println!(" Note: Train the network to improve accuracy!");
println!("\n=== Example completed successfully! ===");
Ok(())
}