2026-05-07 04:29:59 +01:00
use std ::collections ::HashMap ;
use std ::fs ;
use std ::path ::Path ;
use epiphany_graph_rs ::{
Graph , Layout3dConfig , Layout3dSolver , RepulsionAccuracyCandidate , RepulsionMode ,
2026-05-07 04:45:45 +01:00
evaluate_layout_quality , solver_structural_accuracy_sweep ,
2026-05-07 04:29:59 +01:00
} ;
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! (
2026-05-07 04:45:45 +01:00
" 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 "
2026-05-07 04:29:59 +01:00
) ;
for fixture in fixtures ( ) {
2026-05-07 04:45:45 +01:00
let base_graph = fixture . graph . clone ( ) ;
let mut solver = Layout3dSolver ::new ( base_graph . clone ( ) , Layout3dConfig ::default ( ) ) ;
2026-05-07 04:29:59 +01:00
solver . tick ( 8 ) ;
let reports = solver_structural_accuracy_sweep ( & solver , & candidates ) ;
for report in reports {
2026-05-07 04:45:45 +01:00
let quality = candidate_quality ( & base_graph , & report . candidate ) ;
2026-05-07 04:29:59 +01:00
println! (
2026-05-07 04:45:45 +01:00
" {},{},{},{},{},{},{:.2},{},{:.2},{:.2},{},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6} " ,
2026-05-07 04:29:59 +01:00
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 ,
2026-05-07 04:45:45 +01:00
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
2026-05-07 04:29:59 +01:00
) ;
}
}
}
struct Fixture {
name : & 'static str ,
graph : Graph ,
edge_count : usize ,
}
2026-05-07 04:45:45 +01:00
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 )
}
2026-05-07 04:29:59 +01:00
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 " ,
1500 usize ,
) ,
(
" snap_p2p_gnutella08 " ,
" data/tuning/p2p-Gnutella08.txt " ,
3000 usize ,
) ,
] {
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 = 32 usize ;
for idx in 0 .. count {
let rank = idx / width ;
let next_rank = rank + 1 ;
for offset in [ 0 usize , 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 = 8 usize ;
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 [ 1 usize , 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 " ,
}
}