use super::graph_prep::{lay_out_by_piece, PreparedGraph};
use super::Mat;
use rand::{rngs::SmallRng, RngExt, SeedableRng};
use rayon::prelude::*;
const K_S: f32 = 0.1;
const K_S_MAX: f32 = 10.0;
#[must_use]
pub fn forceatlas2_layout_2d_on_graph(
n: usize,
edges: &[(usize, usize, f32)],
iterations: usize,
seed: u64,
) -> Mat {
let graph = PreparedGraph::new(n, edges);
lay_out_by_piece(
&graph,
|piece, _| layout_piece(piece, iterations, seed),
None,
)
}
fn layout_piece(graph: &PreparedGraph, iterations: usize, seed: u64) -> Mat {
let m = graph.n();
let mut rng = SmallRng::seed_from_u64(seed);
let mut pos: Vec<[f32; 2]> = (0..m)
.map(|_| [rng.random_range(-1.0..1.0), rng.random_range(-1.0..1.0)])
.collect();
let mut old = vec![[0.0_f32; 2]; m];
for _ in 0..iterations {
let mut force: Vec<[f32; 2]> = (0..m)
.into_par_iter()
.map(|i| {
let [xi, yi] = pos[i];
let mut f = [0.0_f32; 2];
for p in &pos {
let (dx, dy) = (xi - p[0], yi - p[1]);
let d2 = dx * dx + dy * dy;
if d2 > 0.0 {
let r = 1.0 / d2;
f[0] += r * dx;
f[1] += r * dy;
}
}
f
})
.collect();
for &(i, j, w) in graph.edges() {
let (dx, dy) = (pos[i][0] - pos[j][0], pos[i][1] - pos[j][1]);
force[i][0] -= w * dx;
force[i][1] -= w * dy;
force[j][0] += w * dx;
force[j][1] += w * dy;
}
let (mut total_swing, mut total_traction) = (0.0_f32, 0.0_f32);
for (f, o) in force.iter().zip(&old) {
total_swing += ((f[0] - o[0]).powi(2) + (f[1] - o[1]).powi(2)).sqrt();
total_traction += ((f[0] + o[0]).powi(2) + (f[1] + o[1]).powi(2)).sqrt() / 2.0;
}
if total_swing == 0.0 {
break;
}
let speed = total_traction / total_swing;
for (p, f) in pos.iter_mut().zip(&force) {
let norm = (f[0] * f[0] + f[1] * f[1]).sqrt();
let s_i = (K_S * speed).min(K_S_MAX / norm);
p[0] += s_i * f[0];
p[1] += s_i * f[1];
}
old = force;
}
Mat::from_fn(m, 2, |i, c| pos[i][c])
}
#[cfg(test)]
mod tests;