use super::cost::min_pitch;
use super::graph::Corridor;
use super::place::{Item, owed};
pub(super) fn pairwise(
cluster: &[(Item, Corridor)],
prefs: &[f64],
bounds: &[(f64, f64)],
clearance: f64,
) -> Vec<f64> {
let n = cluster.len();
let mut gaps: Vec<(usize, usize, f64)> = Vec::new();
for i in 0..n {
for j in i + 1..n {
let owes = owed(&cluster[i].0, &cluster[j].0, clearance, clearance);
if owes > 0.0 {
gaps.push((i, j, owes));
}
}
}
for _ in 0..64 {
let mut reach = vec![f64::NEG_INFINITY; n];
let mut origin: Vec<usize> = (0..n).collect();
let mut via: Vec<Option<usize>> = vec![None; n];
let mut violated = None;
'feasible: for j in 0..n {
let mut x = bounds[j].0;
for (e, &(i, jj, g)) in gaps.iter().enumerate() {
if jj == j && reach[i] + g > x {
x = reach[i] + g;
origin[j] = origin[i];
via[j] = Some(e);
}
}
reach[j] = x;
if x > bounds[j].1 + 1e-9 {
violated = Some(j);
break 'feasible;
}
}
let Some(t) = violated else { break };
let mut path = Vec::new();
let mut at = t;
while let Some(e) = via[at] {
path.push(e);
at = gaps[e].0;
}
let avail = (bounds[t].1 - bounds[origin[t]].0).max(0.0);
let target = (avail / path.len().max(1) as f64).max(min_pitch(clearance));
let mut lowered = false;
for e in path {
if gaps[e].2 > target {
gaps[e].2 = target;
lowered = true;
}
}
if !lowered {
break;
}
}
let mut x = prefs.to_vec();
let mut box_corr = vec![0.0; n];
let mut gap_corr = vec![0.0; gaps.len()];
for _ in 0..10_000 {
let mut moved = 0.0_f64;
for i in 0..n {
let y = x[i] + box_corr[i];
let p = y.max(bounds[i].0).min(bounds[i].1);
box_corr[i] = y - p;
moved = moved.max((p - x[i]).abs());
x[i] = p;
}
for (e, &(i, j, g)) in gaps.iter().enumerate() {
let (yi, yj) = (x[i] + gap_corr[e] / 2.0, x[j] - gap_corr[e] / 2.0);
let short = g - (yj - yi);
let d = short.max(0.0);
gap_corr[e] = d;
let (pi, pj) = (yi - d / 2.0, yj + d / 2.0);
moved = moved.max((pi - x[i]).abs()).max((pj - x[j]).abs());
x[i] = pi;
x[j] = pj;
}
if moved < 1e-11 {
break;
}
}
for i in 0..n {
x[i] = x[i].max(bounds[i].0).min(bounds[i].1);
}
x
}