use crate::core::{Graph, IgraphError, IgraphResult};
const GRID_SIZE: usize = 400;
const RADIUS: i32 = 10;
const HALF_VIEW: f32 = 800.0;
const VIEW_TO_GRID: f32 = 0.25;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DrlTemplate {
Default,
Coarsen,
Coarsest,
Refine,
Final,
}
#[derive(Debug, Clone, Copy)]
struct StageParams {
iterations: u32,
temperature: f32,
attraction: f32,
damping_mult: f32,
}
#[derive(Debug, Clone)]
pub struct DrlOptions {
pub edge_cut: f32,
pub init_iterations: u32,
pub init_temperature: f32,
pub init_attraction: f32,
pub init_damping_mult: f32,
liquid: StageParams,
expansion: StageParams,
cooldown: StageParams,
crunch: StageParams,
simmer: StageParams,
}
impl DrlOptions {
pub fn from_template(templ: DrlTemplate) -> Self {
let edge_cut: f32 = 32.0 / 40.0;
match templ {
DrlTemplate::Default => Self {
edge_cut,
init_iterations: 0,
init_temperature: 2000.0,
init_attraction: 10.0,
init_damping_mult: 1.0,
liquid: StageParams {
iterations: 200,
temperature: 2000.0,
attraction: 10.0,
damping_mult: 1.0,
},
expansion: StageParams {
iterations: 200,
temperature: 2000.0,
attraction: 2.0,
damping_mult: 1.0,
},
cooldown: StageParams {
iterations: 200,
temperature: 2000.0,
attraction: 1.0,
damping_mult: 0.1,
},
crunch: StageParams {
iterations: 50,
temperature: 250.0,
attraction: 1.0,
damping_mult: 0.25,
},
simmer: StageParams {
iterations: 100,
temperature: 250.0,
attraction: 0.5,
damping_mult: 0.0,
},
},
DrlTemplate::Coarsen => Self {
edge_cut,
init_iterations: 0,
init_temperature: 2000.0,
init_attraction: 10.0,
init_damping_mult: 1.0,
liquid: StageParams {
iterations: 200,
temperature: 2000.0,
attraction: 2.0,
damping_mult: 1.0,
},
expansion: StageParams {
iterations: 200,
temperature: 2000.0,
attraction: 10.0,
damping_mult: 1.0,
},
cooldown: StageParams {
iterations: 200,
temperature: 2000.0,
attraction: 1.0,
damping_mult: 0.1,
},
crunch: StageParams {
iterations: 50,
temperature: 250.0,
attraction: 1.0,
damping_mult: 0.25,
},
simmer: StageParams {
iterations: 100,
temperature: 250.0,
attraction: 0.5,
damping_mult: 0.0,
},
},
DrlTemplate::Coarsest => Self {
edge_cut,
init_iterations: 0,
init_temperature: 2000.0,
init_attraction: 10.0,
init_damping_mult: 1.0,
liquid: StageParams {
iterations: 200,
temperature: 2000.0,
attraction: 2.0,
damping_mult: 1.0,
},
expansion: StageParams {
iterations: 200,
temperature: 2000.0,
attraction: 10.0,
damping_mult: 1.0,
},
cooldown: StageParams {
iterations: 200,
temperature: 2000.0,
attraction: 1.0,
damping_mult: 0.1,
},
crunch: StageParams {
iterations: 200,
temperature: 250.0,
attraction: 1.0,
damping_mult: 0.25,
},
simmer: StageParams {
iterations: 100,
temperature: 250.0,
attraction: 0.5,
damping_mult: 0.0,
},
},
DrlTemplate::Refine => Self {
edge_cut,
init_iterations: 0,
init_temperature: 50.0,
init_attraction: 0.5,
init_damping_mult: 0.0,
liquid: StageParams {
iterations: 0,
temperature: 2000.0,
attraction: 2.0,
damping_mult: 1.0,
},
expansion: StageParams {
iterations: 50,
temperature: 500.0,
attraction: 0.1,
damping_mult: 0.25,
},
cooldown: StageParams {
iterations: 50,
temperature: 200.0,
attraction: 1.0,
damping_mult: 0.1,
},
crunch: StageParams {
iterations: 50,
temperature: 250.0,
attraction: 1.0,
damping_mult: 0.25,
},
simmer: StageParams {
iterations: 0,
temperature: 250.0,
attraction: 0.5,
damping_mult: 0.0,
},
},
DrlTemplate::Final => Self {
edge_cut,
init_iterations: 0,
init_temperature: 50.0,
init_attraction: 0.5,
init_damping_mult: 0.0,
liquid: StageParams {
iterations: 0,
temperature: 2000.0,
attraction: 2.0,
damping_mult: 1.0,
},
expansion: StageParams {
iterations: 50,
temperature: 50.0,
attraction: 0.1,
damping_mult: 0.25,
},
cooldown: StageParams {
iterations: 50,
temperature: 200.0,
attraction: 1.0,
damping_mult: 0.1,
},
crunch: StageParams {
iterations: 50,
temperature: 250.0,
attraction: 1.0,
damping_mult: 0.25,
},
simmer: StageParams {
iterations: 25,
temperature: 250.0,
attraction: 0.5,
damping_mult: 0.0,
},
},
}
}
}
impl Default for DrlOptions {
fn default() -> Self {
Self::from_template(DrlTemplate::Default)
}
}
pub fn layout_drl(
graph: &Graph,
seed: Option<&[[f64; 2]]>,
options: &DrlOptions,
weights: Option<&[f64]>,
) -> IgraphResult<Vec<[f64; 2]>> {
let n = graph.vcount() as usize;
if n == 0 {
return Ok(Vec::new());
}
if n == 1 {
return Ok(vec![[0.0, 0.0]]);
}
if let Some(s) = seed {
if s.len() != n {
return Err(IgraphError::InvalidArgument(format!(
"seed length ({}) must equal vertex count ({})",
s.len(),
n
)));
}
}
if let Some(w) = weights {
let ne = graph.ecount();
if w.len() != ne {
return Err(IgraphError::InvalidArgument(format!(
"weights length ({}) must equal edge count ({})",
w.len(),
ne
)));
}
if ne > 0 {
for &wt in w {
if wt <= 0.0 || !wt.is_finite() {
return Err(IgraphError::InvalidArgument(
"weights must be positive and finite for DrL layout".into(),
));
}
}
}
}
let mut neighbors: Vec<Vec<(usize, f32)>> = vec![Vec::new(); n];
for eid in 0..graph.ecount() as u32 {
if let Ok((src, tgt)) = graph.edge(eid) {
let w = weights.map_or(1.0_f32, |ws| ws[eid as usize] as f32);
neighbors[src as usize].push((tgt as usize, w));
if src != tgt {
neighbors[tgt as usize].push((src as usize, w));
}
}
}
let mut rng = SplitMix64::new(42);
let mut positions: Vec<[f32; 2]> = if let Some(s) = seed {
s.iter().map(|p| [p[0] as f32, p[1] as f32]).collect()
} else {
(0..n)
.map(|_| {
[
(rng.next_uniform() as f32 - 0.5) * 1000.0,
(rng.next_uniform() as f32 - 0.5) * 1000.0,
]
})
.collect()
};
let mut density = DensityGrid::new();
let cut_length_end = (40000.0 * (1.0 - options.edge_cut)).max(1.0);
let cut_length_start = 4.0 * cut_length_end;
let cut_rate = (cut_length_start - cut_length_end) / 400.0;
let mut cut_off_length = cut_length_start;
let mut min_edges: f32 = 20.0;
let mut temperature;
let mut attraction;
let mut damping_mult;
let mut fine_density = false;
let mut first_add = true;
let mut fine_first_add = true;
let stages: [StageParams; 5] = [
options.liquid,
options.expansion,
options.cooldown,
options.crunch,
options.simmer,
];
for (stage_idx, stage) in stages.iter().enumerate() {
temperature = stage.temperature;
attraction = stage.attraction;
damping_mult = stage.damping_mult;
if stage_idx == 4 {
fine_density = true;
min_edges = 99.0;
}
for iter in 0..stage.iterations {
match stage_idx {
1 => {
if attraction > 1.0 {
attraction -= 0.05;
}
if min_edges > 12.0 {
min_edges -= 0.05;
}
cut_off_length -= cut_rate;
if damping_mult > 0.1 {
damping_mult -= 0.005;
}
}
2 => {
if temperature > 50.0 {
temperature -= 10.0;
}
if cut_off_length > cut_length_end {
cut_off_length -= cut_rate * 2.0;
}
if min_edges > 1.0 {
min_edges -= 0.2;
}
}
4 if temperature > 50.0 => {
temperature -= 2.0;
}
_ => {}
}
update_all_nodes(
n,
&mut positions,
&neighbors,
&mut density,
temperature,
attraction,
damping_mult,
min_edges,
cut_off_length,
cut_length_end,
fine_density,
first_add,
fine_first_add,
stage_idx,
&mut rng,
);
first_add = false;
if fine_density && iter > 0 {
fine_first_add = false;
}
}
if stage_idx == 1 {
min_edges = 12.0;
} else if stage_idx == 2 {
cut_off_length = cut_length_end;
min_edges = 1.0;
}
}
Ok(positions
.iter()
.map(|p| [p[0] as f64, p[1] as f64])
.collect())
}
fn update_all_nodes(
n: usize,
positions: &mut [[f32; 2]],
neighbors: &[Vec<(usize, f32)>],
density: &mut DensityGrid,
temperature: f32,
attraction: f32,
damping_mult: f32,
min_edges: f32,
cut_off_length: f32,
cut_end: f32,
fine_density: bool,
first_add: bool,
fine_first_add: bool,
stage: usize,
rng: &mut SplitMix64,
) {
let jump_length = 0.010 * temperature;
let attraction_factor = attraction * attraction * attraction * attraction * 2e-2;
for node_ind in 0..n {
density.subtract(positions[node_ind], first_add, fine_first_add, fine_density);
let (cx, cy) = solve_analytic(
node_ind,
positions,
neighbors,
damping_mult,
min_edges,
cut_off_length,
cut_end,
);
let old_pos = positions[node_ind];
positions[node_ind] = [cx, cy];
let energy_centroid = compute_energy(
node_ind,
positions,
neighbors,
density,
attraction_factor,
fine_density,
stage,
);
let rx = cx + (0.5 - rng.next_uniform() as f32) * jump_length;
let ry = cy + (0.5 - rng.next_uniform() as f32) * jump_length;
positions[node_ind] = [rx, ry];
let energy_random = compute_energy(
node_ind,
positions,
neighbors,
density,
attraction_factor,
fine_density,
stage,
);
positions[node_ind] = old_pos;
if (!fine_density && !first_add) || (!fine_first_add) {
density.add(positions[node_ind], fine_density);
}
let new_pos = if energy_centroid < energy_random {
[cx, cy]
} else {
[rx, ry]
};
density.subtract(positions[node_ind], false, false, fine_density);
positions[node_ind] = new_pos;
density.add(positions[node_ind], fine_density);
}
}
fn solve_analytic(
node_ind: usize,
positions: &[[f32; 2]],
neighbors: &[Vec<(usize, f32)>],
damping_mult: f32,
min_edges: f32,
cut_off_length: f32,
cut_end: f32,
) -> (f32, f32) {
let mut total_weight: f32 = 0.0;
let mut x_sum: f32 = 0.0;
let mut y_sum: f32 = 0.0;
for &(neighbor, weight) in &neighbors[node_ind] {
total_weight += weight;
x_sum += weight * positions[neighbor][0];
y_sum += weight * positions[neighbor][1];
}
if total_weight <= 0.0 {
return (positions[node_ind][0], positions[node_ind][1]);
}
let x_cen = x_sum / total_weight;
let y_cen = y_sum / total_weight;
let damping = 1.0 - damping_mult;
let pos_x = damping * positions[node_ind][0] + (1.0 - damping) * x_cen;
let pos_y = damping * positions[node_ind][1] + (1.0 - damping) * y_cen;
if min_edges >= 99.0 || cut_end >= 39500.0 {
return (pos_x, pos_y);
}
let _ = cut_off_length;
(pos_x, pos_y)
}
fn compute_energy(
node_ind: usize,
positions: &[[f32; 2]],
neighbors: &[Vec<(usize, f32)>],
density: &DensityGrid,
attraction_factor: f32,
fine_density: bool,
stage: usize,
) -> f32 {
let mut energy: f32 = 0.0;
for &(neighbor, weight) in &neighbors[node_ind] {
let dx = positions[node_ind][0] - positions[neighbor][0];
let dy = positions[node_ind][1] - positions[neighbor][1];
let mut dist = dx * dx + dy * dy;
if stage < 2 {
dist *= dist;
}
if stage == 0 {
dist *= dist;
}
energy += weight * attraction_factor * dist;
}
energy += density.get_density(positions[node_ind][0], positions[node_ind][1], fine_density);
energy
}
pub fn layout_drl_3d(
graph: &Graph,
seed: Option<&[[f64; 3]]>,
options: &DrlOptions,
weights: Option<&[f64]>,
) -> IgraphResult<Vec<[f64; 3]>> {
let n = graph.vcount() as usize;
if n == 0 {
return Ok(Vec::new());
}
if n == 1 {
return Ok(vec![[0.0, 0.0, 0.0]]);
}
if let Some(s) = seed {
if s.len() != n {
return Err(IgraphError::InvalidArgument(format!(
"seed length ({}) must equal vertex count ({})",
s.len(),
n
)));
}
}
if let Some(w) = weights {
let ne = graph.ecount();
if w.len() != ne {
return Err(IgraphError::InvalidArgument(format!(
"weights length ({}) must equal edge count ({})",
w.len(),
ne
)));
}
if ne > 0 {
for &wt in w {
if wt <= 0.0 || !wt.is_finite() {
return Err(IgraphError::InvalidArgument(
"weights must be positive and finite for DrL layout".into(),
));
}
}
}
}
let mut neighbors: Vec<Vec<(usize, f32)>> = vec![Vec::new(); n];
for eid in 0..graph.ecount() as u32 {
if let Ok((src, tgt)) = graph.edge(eid) {
let w = weights.map_or(1.0_f32, |ws| ws[eid as usize] as f32);
neighbors[src as usize].push((tgt as usize, w));
if src != tgt {
neighbors[tgt as usize].push((src as usize, w));
}
}
}
let mut rng = SplitMix64::new(42);
let mut positions: Vec<[f32; 3]> = if let Some(s) = seed {
s.iter()
.map(|p| [p[0] as f32, p[1] as f32, p[2] as f32])
.collect()
} else {
(0..n)
.map(|_| {
[
(rng.next_uniform() as f32 - 0.5) * 1000.0,
(rng.next_uniform() as f32 - 0.5) * 1000.0,
(rng.next_uniform() as f32 - 0.5) * 1000.0,
]
})
.collect()
};
let mut density = DensityGrid3d::new();
let cut_length_end = (40000.0 * (1.0 - options.edge_cut)).max(1.0);
let cut_length_start = 4.0 * cut_length_end;
let cut_rate = (cut_length_start - cut_length_end) / 400.0;
let mut cut_off_length = cut_length_start;
let mut min_edges: f32 = 20.0;
let mut temperature;
let mut attraction;
let mut damping_mult;
let mut fine_density = false;
let mut first_add = true;
let mut fine_first_add = true;
let stages: [StageParams; 5] = [
options.liquid,
options.expansion,
options.cooldown,
options.crunch,
options.simmer,
];
for (stage_idx, stage) in stages.iter().enumerate() {
temperature = stage.temperature;
attraction = stage.attraction;
damping_mult = stage.damping_mult;
if stage_idx == 4 {
fine_density = true;
min_edges = 99.0;
}
for iter in 0..stage.iterations {
match stage_idx {
1 => {
if attraction > 1.0 {
attraction -= 0.05;
}
if min_edges > 12.0 {
min_edges -= 0.05;
}
cut_off_length -= cut_rate;
if damping_mult > 0.1 {
damping_mult -= 0.005;
}
}
2 => {
if temperature > 50.0 {
temperature -= 10.0;
}
if cut_off_length > cut_length_end {
cut_off_length -= cut_rate * 2.0;
}
if min_edges > 1.0 {
min_edges -= 0.2;
}
}
4 if temperature > 50.0 => {
temperature -= 2.0;
}
_ => {}
}
update_all_nodes_3d(
n,
&mut positions,
&neighbors,
&mut density,
temperature,
attraction,
damping_mult,
min_edges,
cut_off_length,
cut_length_end,
fine_density,
first_add,
fine_first_add,
stage_idx,
&mut rng,
);
first_add = false;
if fine_density && iter > 0 {
fine_first_add = false;
}
}
if stage_idx == 1 {
min_edges = 12.0;
} else if stage_idx == 2 {
cut_off_length = cut_length_end;
min_edges = 1.0;
}
}
Ok(positions
.iter()
.map(|p| [p[0] as f64, p[1] as f64, p[2] as f64])
.collect())
}
#[allow(clippy::too_many_arguments)]
fn update_all_nodes_3d(
n: usize,
positions: &mut [[f32; 3]],
neighbors: &[Vec<(usize, f32)>],
density: &mut DensityGrid3d,
temperature: f32,
attraction: f32,
damping_mult: f32,
min_edges: f32,
cut_off_length: f32,
cut_end: f32,
fine_density: bool,
first_add: bool,
fine_first_add: bool,
stage: usize,
rng: &mut SplitMix64,
) {
let jump_length = 0.010 * temperature;
let attraction_factor = attraction * attraction * attraction * attraction * 2e-2;
for node_ind in 0..n {
density.subtract(positions[node_ind], first_add, fine_first_add, fine_density);
let (cx, cy, cz) = solve_analytic_3d(
node_ind,
positions,
neighbors,
damping_mult,
min_edges,
cut_off_length,
cut_end,
);
let old_pos = positions[node_ind];
positions[node_ind] = [cx, cy, cz];
let energy_centroid = compute_energy_3d(
node_ind,
positions,
neighbors,
density,
attraction_factor,
fine_density,
stage,
);
let rx = cx + (0.5 - rng.next_uniform() as f32) * jump_length;
let ry = cy + (0.5 - rng.next_uniform() as f32) * jump_length;
let rz = cz + (0.5 - rng.next_uniform() as f32) * jump_length;
positions[node_ind] = [rx, ry, rz];
let energy_random = compute_energy_3d(
node_ind,
positions,
neighbors,
density,
attraction_factor,
fine_density,
stage,
);
positions[node_ind] = old_pos;
if (!fine_density && !first_add) || (!fine_first_add) {
density.add(positions[node_ind], fine_density);
}
let new_pos = if energy_centroid < energy_random {
[cx, cy, cz]
} else {
[rx, ry, rz]
};
density.subtract(positions[node_ind], false, false, fine_density);
positions[node_ind] = new_pos;
density.add(positions[node_ind], fine_density);
}
}
fn solve_analytic_3d(
node_ind: usize,
positions: &[[f32; 3]],
neighbors: &[Vec<(usize, f32)>],
damping_mult: f32,
min_edges: f32,
cut_off_length: f32,
cut_end: f32,
) -> (f32, f32, f32) {
let mut total_weight: f32 = 0.0;
let mut x_sum: f32 = 0.0;
let mut y_sum: f32 = 0.0;
let mut z_sum: f32 = 0.0;
for &(neighbor, weight) in &neighbors[node_ind] {
total_weight += weight;
x_sum += weight * positions[neighbor][0];
y_sum += weight * positions[neighbor][1];
z_sum += weight * positions[neighbor][2];
}
if total_weight <= 0.0 {
return (
positions[node_ind][0],
positions[node_ind][1],
positions[node_ind][2],
);
}
let x_cen = x_sum / total_weight;
let y_cen = y_sum / total_weight;
let z_cen = z_sum / total_weight;
let damping = 1.0 - damping_mult;
let pos_x = damping * positions[node_ind][0] + (1.0 - damping) * x_cen;
let pos_y = damping * positions[node_ind][1] + (1.0 - damping) * y_cen;
let pos_z = damping * positions[node_ind][2] + (1.0 - damping) * z_cen;
if min_edges >= 99.0 || cut_end >= 39500.0 {
return (pos_x, pos_y, pos_z);
}
let _ = cut_off_length;
(pos_x, pos_y, pos_z)
}
fn compute_energy_3d(
node_ind: usize,
positions: &[[f32; 3]],
neighbors: &[Vec<(usize, f32)>],
density: &DensityGrid3d,
attraction_factor: f32,
fine_density: bool,
stage: usize,
) -> f32 {
let mut energy: f32 = 0.0;
for &(neighbor, weight) in &neighbors[node_ind] {
let dx = positions[node_ind][0] - positions[neighbor][0];
let dy = positions[node_ind][1] - positions[neighbor][1];
let dz = positions[node_ind][2] - positions[neighbor][2];
let mut dist = dx * dx + dy * dy + dz * dz;
if stage < 2 {
dist *= dist;
}
if stage == 0 {
dist *= dist;
}
energy += weight * attraction_factor * dist;
}
energy += density.get_density(
positions[node_ind][0],
positions[node_ind][1],
positions[node_ind][2],
fine_density,
);
energy
}
struct DensityGrid {
density: Vec<f32>,
fall_off: Vec<f32>,
bins: Vec<Vec<[f32; 2]>>,
}
impl DensityGrid {
fn new() -> Self {
let mut fall_off = vec![0.0_f32; (RADIUS * 2 + 1) as usize * (RADIUS * 2 + 1) as usize];
let diam = (RADIUS * 2 + 1) as usize;
for i in 0..diam {
for j in 0..diam {
let fi = (RADIUS - (i as i32 - RADIUS).abs()) as f32 / RADIUS as f32;
let fj = (RADIUS - (j as i32 - RADIUS).abs()) as f32 / RADIUS as f32;
fall_off[i * diam + j] = fi * fj;
}
}
Self {
density: vec![0.0_f32; GRID_SIZE * GRID_SIZE],
fall_off,
bins: vec![Vec::new(); GRID_SIZE * GRID_SIZE],
}
}
fn pos_to_grid(x: f32, y: f32) -> (i32, i32) {
let gx = ((x + HALF_VIEW + 0.5) * VIEW_TO_GRID) as i32;
let gy = ((y + HALF_VIEW + 0.5) * VIEW_TO_GRID) as i32;
(gx, gy)
}
fn add(&mut self, pos: [f32; 2], fine_density: bool) {
let (gx, gy) = Self::pos_to_grid(pos[0], pos[1]);
if fine_density {
if gx >= 0 && gx < GRID_SIZE as i32 && gy >= 0 && gy < GRID_SIZE as i32 {
self.bins[gy as usize * GRID_SIZE + gx as usize].push(pos);
}
return;
}
let sx = gx - RADIUS;
let sy = gy - RADIUS;
if sx < 0 || sy < 0 || sx >= GRID_SIZE as i32 || sy >= GRID_SIZE as i32 {
return;
}
let diam = (RADIUS * 2 + 1) as usize;
for i in 0..diam {
for j in 0..diam {
let dy = sy as usize + i;
let dx = sx as usize + j;
if dy < GRID_SIZE && dx < GRID_SIZE {
self.density[dy * GRID_SIZE + dx] += self.fall_off[i * diam + j];
}
}
}
}
fn subtract(
&mut self,
pos: [f32; 2],
first_add: bool,
fine_first_add: bool,
fine_density: bool,
) {
if fine_density && !fine_first_add {
let (gx, gy) = Self::pos_to_grid(pos[0], pos[1]);
if gx >= 0 && gx < GRID_SIZE as i32 && gy >= 0 && gy < GRID_SIZE as i32 {
let idx = gy as usize * GRID_SIZE + gx as usize;
if !self.bins[idx].is_empty() {
self.bins[idx].remove(0);
}
}
} else if !first_add {
let (gx, gy) = Self::pos_to_grid(pos[0], pos[1]);
let sx = gx - RADIUS;
let sy = gy - RADIUS;
if sx < 0 || sy < 0 || sx >= GRID_SIZE as i32 || sy >= GRID_SIZE as i32 {
return;
}
let diam = (RADIUS * 2 + 1) as usize;
for i in 0..diam {
for j in 0..diam {
let dy = sy as usize + i;
let dx = sx as usize + j;
if dy < GRID_SIZE && dx < GRID_SIZE {
self.density[dy * GRID_SIZE + dx] -= self.fall_off[i * diam + j];
}
}
}
}
}
fn get_density(&self, x: f32, y: f32, fine_density: bool) -> f32 {
let (gx, gy) = Self::pos_to_grid(x, y);
let boundary = 10_i32;
if gx >= GRID_SIZE as i32 - boundary
|| gx < boundary
|| gy >= GRID_SIZE as i32 - boundary
|| gy < boundary
{
return 10000.0;
}
if fine_density {
let mut d: f32 = 0.0;
for i in (gy - 1)..=(gy + 1) {
for j in (gx - 1)..=(gx + 1) {
if i >= 0 && i < GRID_SIZE as i32 && j >= 0 && j < GRID_SIZE as i32 {
let idx = i as usize * GRID_SIZE + j as usize;
for p in &self.bins[idx] {
let dx = x - p[0];
let dy_val = y - p[1];
let dist_sq = dx * dx + dy_val * dy_val;
d += 1e-4 / (dist_sq + 1e-50);
}
}
}
}
d
} else {
let val = self.density[gy as usize * GRID_SIZE + gx as usize];
val * val
}
}
}
const GRID_SIZE_3D: usize = 100;
const RADIUS_3D: i32 = 10;
const HALF_VIEW_3D: f32 = 125.0;
const VIEW_TO_GRID_3D: f32 = 0.4;
struct DensityGrid3d {
density: Vec<f32>,
fall_off: Vec<f32>,
bins: Vec<Vec<[f32; 3]>>,
}
impl DensityGrid3d {
fn new() -> Self {
let diam = (RADIUS_3D * 2 + 1) as usize;
let mut fall_off = vec![0.0_f32; diam * diam * diam];
for i in 0..diam {
for j in 0..diam {
for k in 0..diam {
let fi = (RADIUS_3D - (i as i32 - RADIUS_3D).abs()) as f32 / RADIUS_3D as f32;
let fj = (RADIUS_3D - (j as i32 - RADIUS_3D).abs()) as f32 / RADIUS_3D as f32;
let fk = (RADIUS_3D - (k as i32 - RADIUS_3D).abs()) as f32 / RADIUS_3D as f32;
fall_off[(i * diam + j) * diam + k] = fi * fj * fk;
}
}
}
let total = GRID_SIZE_3D * GRID_SIZE_3D * GRID_SIZE_3D;
Self {
density: vec![0.0_f32; total],
fall_off,
bins: vec![Vec::new(); total],
}
}
fn pos_to_grid(x: f32, y: f32, z: f32) -> (i32, i32, i32) {
let gx = ((x + HALF_VIEW_3D + 0.5) * VIEW_TO_GRID_3D) as i32;
let gy = ((y + HALF_VIEW_3D + 0.5) * VIEW_TO_GRID_3D) as i32;
let gz = ((z + HALF_VIEW_3D + 0.5) * VIEW_TO_GRID_3D) as i32;
(gx, gy, gz)
}
fn add(&mut self, pos: [f32; 3], fine_density: bool) {
let (gx, gy, gz) = Self::pos_to_grid(pos[0], pos[1], pos[2]);
if fine_density {
if gx >= 0
&& gx < GRID_SIZE_3D as i32
&& gy >= 0
&& gy < GRID_SIZE_3D as i32
&& gz >= 0
&& gz < GRID_SIZE_3D as i32
{
let idx = (gz as usize * GRID_SIZE_3D + gy as usize) * GRID_SIZE_3D + gx as usize;
self.bins[idx].push(pos);
}
return;
}
let sx = gx - RADIUS_3D;
let sy = gy - RADIUS_3D;
let sz = gz - RADIUS_3D;
if sx < 0
|| sy < 0
|| sz < 0
|| sx >= GRID_SIZE_3D as i32
|| sy >= GRID_SIZE_3D as i32
|| sz >= GRID_SIZE_3D as i32
{
return;
}
let diam = (RADIUS_3D * 2 + 1) as usize;
for i in 0..diam {
for j in 0..diam {
for k in 0..diam {
let dz = sz as usize + i;
let dy = sy as usize + j;
let dx = sx as usize + k;
if dz < GRID_SIZE_3D && dy < GRID_SIZE_3D && dx < GRID_SIZE_3D {
let grid_idx = (dz * GRID_SIZE_3D + dy) * GRID_SIZE_3D + dx;
self.density[grid_idx] += self.fall_off[(i * diam + j) * diam + k];
}
}
}
}
}
fn subtract(
&mut self,
pos: [f32; 3],
first_add: bool,
fine_first_add: bool,
fine_density: bool,
) {
if fine_density && !fine_first_add {
let (gx, gy, gz) = Self::pos_to_grid(pos[0], pos[1], pos[2]);
if gx >= 0
&& gx < GRID_SIZE_3D as i32
&& gy >= 0
&& gy < GRID_SIZE_3D as i32
&& gz >= 0
&& gz < GRID_SIZE_3D as i32
{
let idx = (gz as usize * GRID_SIZE_3D + gy as usize) * GRID_SIZE_3D + gx as usize;
if !self.bins[idx].is_empty() {
self.bins[idx].remove(0);
}
}
} else if !first_add {
let (gx, gy, gz) = Self::pos_to_grid(pos[0], pos[1], pos[2]);
let sx = gx - RADIUS_3D;
let sy = gy - RADIUS_3D;
let sz = gz - RADIUS_3D;
if sx < 0
|| sy < 0
|| sz < 0
|| sx >= GRID_SIZE_3D as i32
|| sy >= GRID_SIZE_3D as i32
|| sz >= GRID_SIZE_3D as i32
{
return;
}
let diam = (RADIUS_3D * 2 + 1) as usize;
for i in 0..diam {
for j in 0..diam {
for k in 0..diam {
let dz = sz as usize + i;
let dy = sy as usize + j;
let dx = sx as usize + k;
if dz < GRID_SIZE_3D && dy < GRID_SIZE_3D && dx < GRID_SIZE_3D {
let grid_idx = (dz * GRID_SIZE_3D + dy) * GRID_SIZE_3D + dx;
self.density[grid_idx] -= self.fall_off[(i * diam + j) * diam + k];
}
}
}
}
}
}
fn get_density(&self, x: f32, y: f32, z: f32, fine_density: bool) -> f32 {
let (gx, gy, gz) = Self::pos_to_grid(x, y, z);
let boundary = 10_i32;
if gx >= GRID_SIZE_3D as i32 - boundary
|| gx < boundary
|| gy >= GRID_SIZE_3D as i32 - boundary
|| gy < boundary
|| gz >= GRID_SIZE_3D as i32 - boundary
|| gz < boundary
{
return 10000.0;
}
if fine_density {
let mut d: f32 = 0.0;
for iz in (gz - 1)..=(gz + 1) {
for iy in (gy - 1)..=(gy + 1) {
for ix in (gx - 1)..=(gx + 1) {
if iz >= 0
&& iz < GRID_SIZE_3D as i32
&& iy >= 0
&& iy < GRID_SIZE_3D as i32
&& ix >= 0
&& ix < GRID_SIZE_3D as i32
{
let idx = (iz as usize * GRID_SIZE_3D + iy as usize) * GRID_SIZE_3D
+ ix as usize;
for p in &self.bins[idx] {
let dx = x - p[0];
let dy_val = y - p[1];
let dz_val = z - p[2];
let dist_sq = dx * dx + dy_val * dy_val + dz_val * dz_val;
d += 1e-4 / (dist_sq + 1e-50);
}
}
}
}
}
d
} else {
let idx = (gz as usize * GRID_SIZE_3D + gy as usize) * GRID_SIZE_3D + gx as usize;
let val = self.density[idx];
val * val
}
}
}
struct SplitMix64 {
state: u64,
}
impl SplitMix64 {
fn new(seed: u64) -> Self {
Self { state: seed }
}
fn next_u64(&mut self) -> u64 {
self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn next_uniform(&mut self) -> f64 {
(self.next_u64() >> 11) as f64 / ((1u64 << 53) as f64)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_drl_empty() {
let g = Graph::with_vertices(0);
let opts = DrlOptions::default();
let pos = layout_drl(&g, None, &opts, None).unwrap();
assert!(pos.is_empty());
}
#[test]
fn test_drl_single() {
let g = Graph::with_vertices(1);
let opts = DrlOptions::default();
let pos = layout_drl(&g, None, &opts, None).unwrap();
assert_eq!(pos.len(), 1);
assert!(pos[0][0].abs() < 1e-10 && pos[0][1].abs() < 1e-10);
}
#[test]
fn test_drl_path() {
let mut g = Graph::with_vertices(5);
for i in 0..4 {
g.add_edge(i, i + 1).unwrap();
}
let opts = DrlOptions::from_template(DrlTemplate::Refine);
let pos = layout_drl(&g, None, &opts, None).unwrap();
assert_eq!(pos.len(), 5);
for p in &pos {
assert!(p[0].is_finite() && p[1].is_finite());
}
}
#[test]
fn test_drl_cycle() {
let mut g = Graph::with_vertices(6);
for i in 0..6 {
g.add_edge(i, (i + 1) % 6).unwrap();
}
let opts = DrlOptions::from_template(DrlTemplate::Final);
let pos = layout_drl(&g, None, &opts, None).unwrap();
assert_eq!(pos.len(), 6);
for p in &pos {
assert!(p[0].is_finite() && p[1].is_finite());
}
}
#[test]
fn test_drl_complete() {
let mut g = Graph::with_vertices(4);
for i in 0..4u32 {
for j in (i + 1)..4 {
g.add_edge(i, j).unwrap();
}
}
let opts = DrlOptions::from_template(DrlTemplate::Refine);
let pos = layout_drl(&g, None, &opts, None).unwrap();
assert_eq!(pos.len(), 4);
for p in &pos {
assert!(p[0].is_finite() && p[1].is_finite());
}
}
#[test]
fn test_drl_with_seed() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let seed = vec![[0.0, 0.0], [100.0, 0.0], [50.0, 86.6]];
let opts = DrlOptions::from_template(DrlTemplate::Refine);
let pos = layout_drl(&g, Some(&seed), &opts, None).unwrap();
assert_eq!(pos.len(), 3);
for p in &pos {
assert!(p[0].is_finite() && p[1].is_finite());
}
}
#[test]
fn test_drl_seed_wrong_length() {
let g = Graph::with_vertices(3);
let seed = vec![[0.0, 0.0]];
let opts = DrlOptions::default();
assert!(layout_drl(&g, Some(&seed), &opts, None).is_err());
}
#[test]
fn test_drl_weighted() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
let weights = vec![1.0, 2.0, 0.5];
let opts = DrlOptions::from_template(DrlTemplate::Refine);
let pos = layout_drl(&g, None, &opts, Some(&weights)).unwrap();
assert_eq!(pos.len(), 4);
for p in &pos {
assert!(p[0].is_finite() && p[1].is_finite());
}
}
#[test]
fn test_drl_bad_weights() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let opts = DrlOptions::default();
let bad_w = vec![1.0, -1.0];
assert!(layout_drl(&g, None, &opts, Some(&bad_w)).is_err());
}
#[test]
fn test_drl_deterministic() {
let mut g = Graph::with_vertices(5);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 4).unwrap();
g.add_edge(4, 0).unwrap();
let opts = DrlOptions::from_template(DrlTemplate::Refine);
let pos1 = layout_drl(&g, None, &opts, None).unwrap();
let pos2 = layout_drl(&g, None, &opts, None).unwrap();
for i in 0..5 {
assert!((pos1[i][0] - pos2[i][0]).abs() < 1e-6);
assert!((pos1[i][1] - pos2[i][1]).abs() < 1e-6);
}
}
#[test]
fn test_drl_all_templates() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 0).unwrap();
for templ in [
DrlTemplate::Default,
DrlTemplate::Coarsen,
DrlTemplate::Coarsest,
DrlTemplate::Refine,
DrlTemplate::Final,
] {
let opts = DrlOptions::from_template(templ);
let pos = layout_drl(&g, None, &opts, None).unwrap();
assert_eq!(pos.len(), 4);
for p in &pos {
assert!(
p[0].is_finite() && p[1].is_finite(),
"Non-finite position with template {templ:?}"
);
}
}
}
#[test]
fn test_drl_disconnected() {
let mut g = Graph::with_vertices(6);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(3, 4).unwrap();
g.add_edge(4, 5).unwrap();
let opts = DrlOptions::from_template(DrlTemplate::Refine);
let pos = layout_drl(&g, None, &opts, None).unwrap();
assert_eq!(pos.len(), 6);
for p in &pos {
assert!(p[0].is_finite() && p[1].is_finite());
}
}
#[test]
fn test_drl_3d_empty() {
let g = Graph::with_vertices(0);
let opts = DrlOptions::default();
let pos = layout_drl_3d(&g, None, &opts, None).unwrap();
assert!(pos.is_empty());
}
#[test]
fn test_drl_3d_single() {
let g = Graph::with_vertices(1);
let opts = DrlOptions::default();
let pos = layout_drl_3d(&g, None, &opts, None).unwrap();
assert_eq!(pos.len(), 1);
assert!(pos[0][0].abs() < 1e-10 && pos[0][1].abs() < 1e-10 && pos[0][2].abs() < 1e-10);
}
#[test]
fn test_drl_3d_path() {
let mut g = Graph::with_vertices(5);
for i in 0..4 {
g.add_edge(i, i + 1).unwrap();
}
let opts = DrlOptions::from_template(DrlTemplate::Refine);
let pos = layout_drl_3d(&g, None, &opts, None).unwrap();
assert_eq!(pos.len(), 5);
for p in &pos {
assert!(p[0].is_finite() && p[1].is_finite() && p[2].is_finite());
}
}
#[test]
fn test_drl_3d_cycle() {
let mut g = Graph::with_vertices(6);
for i in 0..6 {
g.add_edge(i, (i + 1) % 6).unwrap();
}
let opts = DrlOptions::from_template(DrlTemplate::Final);
let pos = layout_drl_3d(&g, None, &opts, None).unwrap();
assert_eq!(pos.len(), 6);
for p in &pos {
assert!(p[0].is_finite() && p[1].is_finite() && p[2].is_finite());
}
}
#[test]
fn test_drl_3d_with_seed() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let seed = vec![[0.0, 0.0, 0.0], [100.0, 0.0, 0.0], [50.0, 86.6, 10.0]];
let opts = DrlOptions::from_template(DrlTemplate::Refine);
let pos = layout_drl_3d(&g, Some(&seed), &opts, None).unwrap();
assert_eq!(pos.len(), 3);
for p in &pos {
assert!(p[0].is_finite() && p[1].is_finite() && p[2].is_finite());
}
}
#[test]
fn test_drl_3d_seed_wrong_length() {
let g = Graph::with_vertices(3);
let seed = vec![[0.0, 0.0, 0.0]];
let opts = DrlOptions::default();
assert!(layout_drl_3d(&g, Some(&seed), &opts, None).is_err());
}
#[test]
fn test_drl_3d_weighted() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
let weights = vec![1.0, 2.0, 0.5];
let opts = DrlOptions::from_template(DrlTemplate::Refine);
let pos = layout_drl_3d(&g, None, &opts, Some(&weights)).unwrap();
assert_eq!(pos.len(), 4);
for p in &pos {
assert!(p[0].is_finite() && p[1].is_finite() && p[2].is_finite());
}
}
#[test]
fn test_drl_3d_bad_weights() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let opts = DrlOptions::default();
let bad_w = vec![1.0, -1.0];
assert!(layout_drl_3d(&g, None, &opts, Some(&bad_w)).is_err());
}
#[test]
fn test_drl_3d_deterministic() {
let mut g = Graph::with_vertices(5);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 4).unwrap();
g.add_edge(4, 0).unwrap();
let opts = DrlOptions::from_template(DrlTemplate::Refine);
let pos1 = layout_drl_3d(&g, None, &opts, None).unwrap();
let pos2 = layout_drl_3d(&g, None, &opts, None).unwrap();
for i in 0..5 {
assert!((pos1[i][0] - pos2[i][0]).abs() < 1e-6);
assert!((pos1[i][1] - pos2[i][1]).abs() < 1e-6);
assert!((pos1[i][2] - pos2[i][2]).abs() < 1e-6);
}
}
#[test]
fn test_drl_3d_all_templates() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 0).unwrap();
for templ in [
DrlTemplate::Default,
DrlTemplate::Coarsen,
DrlTemplate::Coarsest,
DrlTemplate::Refine,
DrlTemplate::Final,
] {
let opts = DrlOptions::from_template(templ);
let pos = layout_drl_3d(&g, None, &opts, None).unwrap();
assert_eq!(pos.len(), 4);
for p in &pos {
assert!(
p[0].is_finite() && p[1].is_finite() && p[2].is_finite(),
"Non-finite position with template {templ:?}"
);
}
}
}
#[test]
fn test_drl_3d_disconnected() {
let mut g = Graph::with_vertices(6);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(3, 4).unwrap();
g.add_edge(4, 5).unwrap();
let opts = DrlOptions::from_template(DrlTemplate::Refine);
let pos = layout_drl_3d(&g, None, &opts, None).unwrap();
assert_eq!(pos.len(), 6);
for p in &pos {
assert!(p[0].is_finite() && p[1].is_finite() && p[2].is_finite());
}
}
}