forceatlas2 0.8.0

fast force-directed generic n-dimension graph layout
Documentation
use forceatlas2::*;
use rand::SeedableRng;
use std::io::BufRead;

const DEFAULT_N: usize = 2;
const DEFAULT_ITERATIONS: usize = 10000;

fn main() {
	let mut args = std::env::args().skip(1);
	let filename = args.next().expect(&format!(
		"Usage: csv_import <csv_file> [N (default={})] [iterations (default={})]",
		DEFAULT_N, DEFAULT_ITERATIONS
	));
	let dim = args
		.next()
		.map_or(DEFAULT_N, |arg| arg.parse().expect("Invalid argument N"));
	assert!(dim == 2 || dim == 3, "N must be 2 or 3");
	let iterations = args.next().map_or(DEFAULT_ITERATIONS, |arg| {
		arg.parse().expect("Invalid argument iterations")
	});
	let file = std::fs::File::open(filename).expect("Cannot open file");

	let mut nodes = Vec::new();
	let mut edges = Vec::<((usize, usize), f32)>::new();
	for (i, line) in std::io::BufReader::new(file).lines().enumerate() {
		let line = line.expect("Error reading CSV");
		let mut columns = line.split(&[' ', '\t', ',', ';'][..]);
		if let (Some(n1), Some(n2)) = (columns.next(), columns.skip_while(|&c| c.is_empty()).next())
		{
			if let (Ok(n1), Ok(n2)) = (n1.parse::<usize>(), n2.parse::<usize>()) {
				if n1 >= nodes.len() || n2 >= nodes.len() {
					nodes.extend((nodes.len()..=n1.max(n2)).map(|_| AbstractNode::default()));
				}
				if n1 != n2 {
					edges.push((if n1 < n2 { (n1, n2) } else { (n2, n1) }, 1.0));
					nodes[n1].mass += 1.0;
					nodes[n2].mass += 1.0;
				}
			} else {
				eprintln!("Ignored line {} has bad number format", i);
			}
		} else {
			eprintln!("Ignored line {} has <2 columns", i);
		}
	}

	eprintln!("Nodes: {}", nodes.len());

	// Deterministic RNG to be able to compare executions
	let mut rng = rand::prelude::SmallRng::seed_from_u64(42);

	match dim {
		2 => {
			let mut layout = Layout::<f32, 2>::from_abstract(
				Settings {
					theta: 0.5,
					ka: 0.01,
					kg: 0.001,
					kr: 0.002,
					lin_log: false,
					speed: 1.0,
					prevent_overlapping: None,
					strong_gravity: false,
				},
				nodes.into_iter().enumerate(),
				edges,
				&mut rng,
			);

			let start = std::time::Instant::now();

			for _i in 0..iterations {
				//eprint!("{}/{}\r", i, iterations);
				layout.iteration();
			}

			let duration = start.elapsed();

			for node in layout.nodes {
				println!("{}\t{}", node.pos[0], node.pos[1]);
			}

			println!("Duration: {} ms", duration.as_millis());
		}
		3 => {
			let mut layout = Layout::<f32, 3>::from_abstract(
				Settings {
					theta: 0.5,
					ka: 0.01,
					kg: 0.001,
					kr: 0.002,
					lin_log: false,
					speed: 1.0,
					prevent_overlapping: None,
					strong_gravity: false,
				},
				nodes.into_iter().enumerate(),
				edges,
				&mut rng,
			);

			let start = std::time::Instant::now();

			for _i in 0..iterations {
				//eprint!("{}/{}\r", i, iterations);
				layout.iteration();
			}

			let duration = start.elapsed();

			for node in layout.nodes {
				println!("{}\t{}\t{}", node.pos[0], node.pos[1], node.pos[2]);
			}

			println!("Duration: {} ms", duration.as_millis());
		}
		_ => unreachable!(),
	}
}