#![allow(clippy::needless_range_loop)]
use alloc::{vec, vec::Vec};
use num_traits::Float;
use super::{
super::randomized_graphs::XorShift64,
fme::FastMultipoleEmbedder,
multilevel::{LevelGraph, build_hierarchy},
};
#[derive(Debug, Clone, PartialEq)]
pub struct MixerConfig<F> {
pub iterations: usize,
pub theta: F,
pub extra_scaling_steps: usize,
pub node_size: F,
pub rep_factor: F,
pub edge_factor: F,
pub jitter: F,
pub seed: u64,
}
impl<F: Float> Default for MixerConfig<F> {
fn default() -> Self {
Self {
iterations: 1000,
theta: F::from(0.5).unwrap(),
extra_scaling_steps: 2,
node_size: F::from(1.0 / 65.0).unwrap(),
rep_factor: F::from(2.0).unwrap(),
edge_factor: F::from(0.5).unwrap(),
jitter: F::one(),
seed: 0x1234_5678_9abc_def0,
}
}
}
pub(super) fn multilevel_layout<F>(
n: usize,
edges: &[(usize, usize)],
config: &MixerConfig<F>,
) -> Vec<F>
where
F: Float
+ num_traits::float::FloatCore
+ Send
+ Sync
+ core::ops::AddAssign
+ core::ops::SubAssign,
{
let s = config.node_size * F::from(2.0).unwrap().sqrt() / F::from(2.0).unwrap();
let mut rng = XorShift64::from(XorShift64::normalize_seed(config.seed));
let (levels, prolongs) = build_hierarchy(n, edges, &mut rng);
let mut fme = FastMultipoleEmbedder::<F>::new();
let coarsest = levels.len() - 1;
let mut positions = vec![F::zero(); levels[coarsest].n * 2];
for level in (1..=coarsest).rev() {
run_level(&mut fme, &mut positions, &levels[level], s, config);
move_to_zero::<F>(&mut positions, levels[level].n);
positions = prolongs[level - 1].apply::<F>(&positions, &mut rng, config.jitter);
}
run_level(&mut fme, &mut positions, &levels[0], s, config);
positions
}
fn run_level<F>(
fme: &mut FastMultipoleEmbedder<F>,
positions: &mut [F],
level: &LevelGraph,
node_size: F,
config: &MixerConfig<F>,
) where
F: Float
+ num_traits::float::FloatCore
+ Send
+ Sync
+ core::ops::AddAssign
+ core::ops::SubAssign,
{
let sizes = vec![node_size; level.n];
scaling_layout::<F>(
fme,
positions,
&level.edges,
&sizes,
config.iterations,
config.theta,
config.extra_scaling_steps,
config.rep_factor,
config.edge_factor,
);
}
#[allow(clippy::too_many_arguments)]
fn scaling_layout<F>(
fme: &mut FastMultipoleEmbedder<F>,
positions: &mut [F],
edges: &[(usize, usize)],
node_sizes: &[F],
iterations: usize,
theta: F,
extra_scaling_steps: usize,
rep_factor: F,
edge_factor: F,
) where
F: Float
+ num_traits::float::FloatCore
+ Send
+ Sync
+ core::ops::AddAssign
+ core::ops::SubAssign,
{
let n = node_sizes.len();
let mut avg_start = F::zero();
let mut have_start = false;
for _ in 0..=extra_scaling_steps {
let avg = avg_edge_length::<F>(positions, edges, n);
if avg <= F::zero() {
move_to_zero::<F>(positions, n);
} else {
if !have_start {
avg_start = avg;
have_start = true;
}
let scaling = avg_start / avg;
move_to_zero::<F>(positions, n);
for value in positions.iter_mut() {
*value = *value * scaling;
}
}
fme.run_with_factors(
positions,
edges,
node_sizes,
iterations,
theta,
rep_factor,
edge_factor,
);
}
}
fn avg_edge_length<F: Float>(positions: &[F], edges: &[(usize, usize)], n: usize) -> F {
if n == 0 {
return F::zero();
}
let mut total = F::zero();
for &(a, b) in edges {
let mut dsq = F::zero();
for d in 0..2 {
let delta = positions[a * 2 + d] - positions[b * 2 + d];
dsq = dsq + delta * delta;
}
total = total + dsq.sqrt();
}
total / F::from(n).unwrap()
}
fn move_to_zero<F: Float>(positions: &mut [F], n: usize) {
if n == 0 {
return;
}
let mut center = [F::zero(); 2];
for i in 0..n {
for d in 0..2 {
center[d] = center[d] + positions[i * 2 + d];
}
}
let inv = F::one() / F::from(n).unwrap();
for d in 0..2 {
center[d] = center[d] * inv;
}
for i in 0..n {
for d in 0..2 {
positions[i * 2 + d] = positions[i * 2 + d] - center[d];
}
}
}