use super::graph_prep::{lay_out_by_piece, PreparedGraph};
use super::{classical_mds_2d, Mat};
use rand::{rngs::SmallRng, seq::SliceRandom, RngExt, SeedableRng};
use rayon::prelude::*;
use std::cmp::Reverse;
use std::collections::BinaryHeap;
const ITERATIONS: usize = 30;
const EPSILON: f32 = 0.1;
const NUDGE: f32 = 1e-3;
#[must_use]
pub fn stress_layout_2d_on_graph(n: usize, edges: &[(usize, usize, f32)], seed: u64) -> Mat {
let graph = PreparedGraph::new(n, edges);
lay_out_by_piece(&graph, |piece, _| piece_layout(piece, seed), None)
}
fn piece_layout(graph: &PreparedGraph, seed: u64) -> Mat {
let m = graph.n();
let d = shortest_paths(graph);
let pairs: Vec<(u32, u32, f32)> = (0..m)
.flat_map(|i| (0..i).map(move |j| (i, j)))
.map(|(i, j)| (i as u32, j as u32, d[i * m + j]))
.collect();
let (d_min, d_max) = pairs
.iter()
.fold((f32::INFINITY, 0.0_f32), |(lo, hi), &(_, _, v)| {
(lo.min(v), hi.max(v))
});
let start = classical_mds_2d(&Mat::from_fn(m, m, |i, j| d[i * m + j] * d[i * m + j]));
drop(d);
let mut x: Vec<[f32; 2]> = (0..m).map(|i| [start[(i, 0)], start[(i, 1)]]).collect();
let mut rng = SmallRng::seed_from_u64(seed);
nudge_apart(&mut x, NUDGE * d_min, &mut rng);
let eta_max = d_max * d_max;
let eta_min = EPSILON * d_min * d_min;
let lambda = (eta_max / eta_min).ln() / (ITERATIONS - 1) as f32;
let mut order = pairs;
for t in 0..ITERATIONS {
let eta = eta_max * (-lambda * t as f32).exp();
order.shuffle(&mut rng);
for &(i, j, dij) in &order {
let (i, j) = (i as usize, j as usize);
let mu = (eta / (dij * dij)).min(1.0);
let (dx, dy) = (x[i][0] - x[j][0], x[i][1] - x[j][1]);
let mag = dx.hypot(dy);
if mag == 0.0 {
continue;
}
let s = mu * 0.5 * (mag - dij) / mag;
x[i][0] -= s * dx;
x[i][1] -= s * dy;
x[j][0] += s * dx;
x[j][1] += s * dy;
}
}
Mat::from_fn(m, 2, |i, c| x[i][c])
}
fn nudge_apart(x: &mut [[f32; 2]], step: f32, rng: &mut SmallRng) {
for i in 1..x.len() {
let close = (0..i).any(|j| (x[i][0] - x[j][0]).hypot(x[i][1] - x[j][1]) < step);
if close {
let theta = rng.random_range(0.0..std::f32::consts::TAU);
x[i][0] += step * theta.cos();
x[i][1] += step * theta.sin();
}
}
}
fn shortest_paths(graph: &PreparedGraph) -> Vec<f32> {
let m = graph.n();
let mut table = vec![f32::INFINITY; m * m];
table.par_chunks_mut(m).enumerate().for_each(|(s, dist)| {
let mut heap = BinaryHeap::new();
dist[s] = 0.0;
heap.push(Reverse((0.0_f32.to_bits(), s)));
while let Some(Reverse((du, u))) = heap.pop() {
let du = f32::from_bits(du);
for &(v, w) in graph.neighbours(u) {
let dv = du + 1.0 / w;
if dv < dist[v] {
dist[v] = dv;
heap.push(Reverse((dv.to_bits(), v)));
}
}
}
});
table
}
#[cfg(test)]
mod tests;