This repository has been archived on 2026-10-29. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
epiphany-graph-rs/examples/organ_battleground.rs
2026-05-07 04:45:45 +01:00

229 lines
7.4 KiB
Rust

use std::collections::HashMap;
use std::fs;
use std::path::Path;
use epiphany_graph_rs::{
Graph, Layout3dConfig, Layout3dSolver, RepulsionAccuracyCandidate, RepulsionMode,
evaluate_layout_quality, solver_structural_accuracy_sweep,
};
fn main() {
let candidates = [
RepulsionAccuracyCandidate::exact(),
RepulsionAccuracyCandidate::barnes_hut(1.0),
RepulsionAccuracyCandidate::barnes_hut(1.2),
RepulsionAccuracyCandidate::spatial_grid(240.0, 1),
RepulsionAccuracyCandidate::structured(RepulsionMode::Exact, 0.20, 0.50, 256),
RepulsionAccuracyCandidate::structured(RepulsionMode::SpatialGrid, 0.25, 0.75, 512),
];
println!(
"dataset,nodes,edges,communities,cores,mode,theta,body_mode,body_scale,body_far,elapsed_us,mean_abs,max_abs,mean_rel,rms_rel,max_rel,fold_compactness,group_separation,rank_error,edge_mean,edge_var,bridge_mean,bridge_var"
);
for fixture in fixtures() {
let base_graph = fixture.graph.clone();
let mut solver = Layout3dSolver::new(base_graph.clone(), Layout3dConfig::default());
solver.tick(8);
let reports = solver_structural_accuracy_sweep(&solver, &candidates);
for report in reports {
let quality = candidate_quality(&base_graph, &report.candidate);
println!(
"{},{},{},{},{},{},{:.2},{},{:.2},{:.2},{},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6}",
fixture.name,
solver.positions().len(),
fixture.edge_count,
solver.analysis().communities.len(),
solver.analysis().core_shells.len(),
mode_name(report.candidate.repulsion_mode),
report.candidate.barnes_hut_theta,
mode_name(report.candidate.body_repulsion_mode),
report.candidate.body_repulsion_scale,
report.candidate.body_far_repulsion_scale,
report.elapsed.as_micros(),
report.mean_absolute_error,
report.max_absolute_error,
report.mean_relative_error,
report.rms_relative_error,
report.max_relative_error,
quality.mean_fold_compactness,
quality.mean_group_separation,
quality.mean_rank_error,
quality.edge_length_mean,
quality.edge_length_variance,
quality.bridge_length_mean,
quality.bridge_length_variance
);
}
}
}
struct Fixture {
name: &'static str,
graph: Graph,
edge_count: usize,
}
fn candidate_quality(
graph: &Graph,
candidate: &RepulsionAccuracyCandidate,
) -> epiphany_graph_rs::LayoutQualityReport {
let config = Layout3dConfig {
repulsion_mode: candidate.repulsion_mode,
barnes_hut_theta: candidate.barnes_hut_theta,
grid_cell_size: candidate.grid_cell_size,
grid_radius: candidate.grid_radius,
barnes_hut_near_radius: candidate.barnes_hut_near_radius,
near_repulsion_scale: candidate.near_repulsion_scale,
far_repulsion_scale: candidate.far_repulsion_scale,
body_repulsion_mode: candidate.body_repulsion_mode,
body_repulsion_scale: candidate.body_repulsion_scale,
body_exact_limit: candidate.body_exact_limit,
body_far_repulsion_scale: candidate.body_far_repulsion_scale,
..Layout3dConfig::default()
};
let mut solver = Layout3dSolver::new(graph.clone(), config);
solver.tick(32);
let layout = solver.snapshot();
evaluate_layout_quality(graph, &layout)
}
fn fixtures() -> Vec<Fixture> {
let mut fixtures = vec![
Fixture {
name: "synthetic_layered_512",
graph: synthetic_layered(512),
edge_count: 0,
},
Fixture {
name: "synthetic_clustered_1024",
graph: synthetic_clustered(1024),
edge_count: 0,
},
];
for (name, path, limit) in [
(
"snap_email_eu_core",
"data/tuning/email-Eu-core.txt",
1500usize,
),
(
"snap_p2p_gnutella08",
"data/tuning/p2p-Gnutella08.txt",
3000usize,
),
] {
if let Some((graph, edge_count)) = load_edge_list(path, limit) {
fixtures.push(Fixture {
name,
graph,
edge_count,
});
}
}
fixtures
}
fn load_edge_list(path: &str, node_limit: usize) -> Option<(Graph, usize)> {
let path = Path::new(path);
let contents = fs::read_to_string(path).ok()?;
let mut ids = HashMap::<usize, usize>::new();
let mut edges = Vec::<(usize, usize)>::new();
for line in contents.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let mut parts = line.split_whitespace();
let Some(source) = parts.next().and_then(|part| part.parse::<usize>().ok()) else {
continue;
};
let Some(target) = parts.next().and_then(|part| part.parse::<usize>().ok()) else {
continue;
};
if !ids.contains_key(&source) {
if ids.len() >= node_limit {
continue;
}
ids.insert(source, ids.len());
}
if !ids.contains_key(&target) {
if ids.len() >= node_limit {
continue;
}
ids.insert(target, ids.len());
}
edges.push((ids[&source], ids[&target]));
}
let mut graph = Graph::with_capacity(ids.len(), edges.len());
let nodes = (0..ids.len())
.map(|_| graph.add_node(1.0))
.collect::<Vec<_>>();
for (source, target) in &edges {
graph.add_edge(nodes[*source], nodes[*target]);
}
Some((graph, edges.len()))
}
fn synthetic_layered(count: usize) -> Graph {
let mut graph = Graph::with_capacity(count, count * 3);
let nodes = (0..count).map(|_| graph.add_node(1.0)).collect::<Vec<_>>();
let width = 32usize;
for idx in 0..count {
let rank = idx / width;
let next_rank = rank + 1;
for offset in [0usize, 3, 11] {
let target = next_rank * width + ((idx + offset) % width);
if target < count {
graph.add_edge(nodes[idx], nodes[target]);
}
}
}
graph
}
fn synthetic_clustered(count: usize) -> Graph {
let mut graph = Graph::with_capacity(count, count * 4);
let nodes = (0..count).map(|_| graph.add_node(1.0)).collect::<Vec<_>>();
let clusters = 8usize;
let cluster_size = count / clusters;
for cluster in 0..clusters {
let start = cluster * cluster_size;
let end = if cluster == clusters - 1 {
count
} else {
start + cluster_size
};
for idx in start..end {
for step in [1usize, 2, 5] {
let target = start + ((idx - start + step) % (end - start));
graph.add_edge(nodes[idx], nodes[target]);
}
}
if cluster + 1 < clusters {
graph.add_edge(nodes[start], nodes[(cluster + 1) * cluster_size]);
}
}
graph
}
fn mode_name(mode: RepulsionMode) -> &'static str {
match mode {
RepulsionMode::Exact => "exact",
RepulsionMode::BarnesHut => "barnes_hut",
RepulsionMode::SpatialGrid => "spatial_grid",
}
}