use super::ordering::RankStructure;
pub(crate) struct Coords {
pub cross: Vec<f64>,
pub flow_start: Vec<f64>,
pub band_ext: Vec<f64>,
}
const ALIGN_PASSES: [bool; 3] = [true, false, true];
pub(crate) fn assign(
rs: &RankStructure,
cross_ext: &[f64],
flow_ext: &[f64],
node_gap: f64,
rank_gap: f64,
) -> Coords {
let member_count = cross_ext.len();
let mut cross = vec![0.0f64; member_count];
for rank in &rs.ranks {
let mut cur = 0.0;
for &m in rank {
cross[m] = cur;
cur += cross_ext[m] + node_gap;
}
}
for &downward in ALIGN_PASSES.iter() {
let rank_count = rs.ranks.len();
let indices: Vec<usize> = if downward {
(1..rank_count).collect()
} else {
(0..rank_count.saturating_sub(1)).rev().collect()
};
for r in indices {
let neighbors = if downward { &rs.up } else { &rs.down };
let members = &rs.ranks[r];
let wants: Vec<f64> = members
.iter()
.map(|&m| {
median_center(&neighbors[m], &cross, cross_ext)
.map_or(cross[m], |c| c - cross_ext[m] / 2.0)
})
.collect();
let exts: Vec<f64> = members.iter().map(|&m| cross_ext[m]).collect();
let starts = pack_rank(&wants, &exts, node_gap);
for (&m, start) in members.iter().zip(starts) {
cross[m] = start;
}
}
}
let mut flow_start = Vec::with_capacity(rs.ranks.len());
let mut band_ext = Vec::with_capacity(rs.ranks.len());
let mut cur = 0.0f64;
for rank in &rs.ranks {
let ext = rank.iter().map(|&m| flow_ext[m]).fold(0.0f64, f64::max);
flow_start.push(cur);
band_ext.push(ext);
cur += ext + rank_gap;
}
Coords {
cross,
flow_start,
band_ext,
}
}
fn pack_rank(wants: &[f64], exts: &[f64], gap: f64) -> Vec<f64> {
let count = wants.len();
let mut offset = 0.0f64;
let mut blocks: Vec<(f64, f64)> = Vec::with_capacity(count);
for i in 0..count {
let w = wants[i] - offset;
offset += exts[i] + gap;
blocks.push((w, 1.0));
while blocks.len() >= 2 {
let cur = blocks[blocks.len() - 1];
let prev = blocks[blocks.len() - 2];
if prev.0 / prev.1 > cur.0 / cur.1 {
blocks.pop();
let last = blocks.last_mut().expect("two blocks checked");
last.0 += cur.0;
last.1 += cur.1;
} else {
break;
}
}
}
let mut t = Vec::with_capacity(count);
for &(sum, n) in &blocks {
let mean = sum / n;
for _ in 0..(n as usize) {
t.push(mean);
}
}
let mut offset = 0.0f64;
(0..count)
.map(|i| {
let start = t[i] + offset;
offset += exts[i] + gap;
start
})
.collect()
}
fn median_center(neighbors: &[usize], cross: &[f64], cross_ext: &[f64]) -> Option<f64> {
if neighbors.is_empty() {
return None;
}
let mut centers: Vec<f64> = neighbors
.iter()
.map(|&m| cross[m] + cross_ext[m] / 2.0)
.collect();
centers.sort_by(f64::total_cmp);
let mid = centers.len() / 2;
if centers.len() % 2 == 1 {
Some(centers[mid])
} else {
Some((centers[mid - 1] + centers[mid]) / 2.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn colliding_siblings_center_as_a_block() {
let starts = pack_rank(&[0.0, 0.0], &[8.0, 8.0], 3.0);
assert_eq!(starts, vec![-5.5, 5.5]);
let free = pack_rank(&[0.0, 40.0], &[8.0, 8.0], 3.0);
assert_eq!(free, vec![0.0, 40.0]);
}
#[test]
fn chain_aligns_centers() {
let rs = RankStructure {
ranks: vec![vec![0], vec![1]],
up: vec![vec![], vec![0]],
down: vec![vec![1], vec![]],
};
let coords = assign(&rs, &[10.0, 4.0], &[3.0, 3.0], 3.0, 2.0);
let parent_center = coords.cross[0] + 5.0;
let child_center = coords.cross[1] + 2.0;
assert!((parent_center - child_center).abs() < 1e-9);
assert_eq!(coords.flow_start, vec![0.0, 5.0]);
assert_eq!(coords.band_ext, vec![3.0, 3.0]);
}
}