use graphops::ellipsoidal::{
ellipsoid_distance, ellipsoid_overlap, ellipsoidal_embedding, EllipsoidalConfig,
};
use graphops::Graph;
struct VecGraph {
adj: Vec<Vec<usize>>,
}
impl Graph for VecGraph {
fn node_count(&self) -> usize {
self.adj.len()
}
fn neighbors(&self, node: usize) -> Vec<usize> {
self.adj[node].clone()
}
}
fn main() {
let adj = vec![
vec![1, 2, 3, 4], vec![0, 2, 3], vec![0, 1, 3], vec![0, 1, 2, 7], vec![0, 5], vec![4, 6, 7, 8], vec![5, 7, 8], vec![3, 5, 6, 8], vec![5, 6, 7], ];
let g = VecGraph { adj };
let config = EllipsoidalConfig {
dim: 3,
..Default::default()
};
let embeddings = ellipsoidal_embedding(&g, &config);
println!("Ellipsoidal link prediction");
println!(" {} nodes, dim={}", g.node_count(), config.dim);
println!();
for (i, e) in embeddings.iter().enumerate() {
println!(
" node {i}: center=[{:.3}, {:.3}, {:.3}]",
e.center[0], e.center[1], e.center[2],
);
}
println!();
let n = Graph::node_count(&g);
let mut predictions: Vec<(usize, usize, f64)> = Vec::new();
for i in 0..n {
for j in (i + 1)..n {
if g.adj[i].contains(&j) {
continue;
}
let overlap = ellipsoid_overlap(&embeddings[i], &embeddings[j]).unwrap_or(0.0);
predictions.push((i, j, overlap));
}
}
predictions.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap());
println!("Top predicted links (by ellipsoid overlap):");
for &(i, j, score) in predictions.iter().take(5) {
let dist = ellipsoid_distance(&embeddings[i], &embeddings[j]).unwrap_or(f64::NAN);
println!(" ({i}, {j}): overlap={score:.4}, distance={dist:.4}");
}
println!();
let cluster_a = [0, 1, 2, 3];
let cluster_b = [5, 6, 7, 8];
let intra_a: f64 = pairwise_mean_distance(&embeddings, &cluster_a);
let intra_b: f64 = pairwise_mean_distance(&embeddings, &cluster_b);
let inter: f64 = cross_mean_distance(&embeddings, &cluster_a, &cluster_b);
println!("Cluster analysis:");
println!(" Cluster A intra-distance: {intra_a:.4}");
println!(" Cluster B intra-distance: {intra_b:.4}");
println!(" Inter-cluster distance: {inter:.4}");
if inter > intra_a && inter > intra_b {
println!(" Inter > intra (clusters well-separated in embedding)");
}
}
fn pairwise_mean_distance(embeddings: &[graphops::ellipsoidal::Ellipsoid], nodes: &[usize]) -> f64 {
let mut sum = 0.0;
let mut count = 0;
for (i, &a) in nodes.iter().enumerate() {
for &b in &nodes[i + 1..] {
sum += ellipsoid_distance(&embeddings[a], &embeddings[b]).unwrap_or(0.0);
count += 1;
}
}
if count == 0 {
0.0
} else {
sum / count as f64
}
}
fn cross_mean_distance(
embeddings: &[graphops::ellipsoidal::Ellipsoid],
a: &[usize],
b: &[usize],
) -> f64 {
let mut sum = 0.0;
let mut count = 0;
for &i in a {
for &j in b {
sum += ellipsoid_distance(&embeddings[i], &embeddings[j]).unwrap_or(0.0);
count += 1;
}
}
if count == 0 {
0.0
} else {
sum / count as f64
}
}