use std::collections::BTreeMap;
use super::cost::min_pitch;
use super::graph::{Axis, Corridor};
use super::ladder::ladder;
use super::order;
use super::rect::Rect;
use super::{Chain, Run, World};
use crate::ast::Side;
pub(super) struct Item {
members: Vec<(usize, usize)>,
span: (f64, f64),
clamp: (f64, f64),
pref: f64,
window: Option<(f64, f64)>,
link: usize,
world: usize,
chan: usize,
landings: Vec<(Side, Rect)>,
}
type Pref = (f64, Option<(f64, f64)>);
pub(crate) fn place(worlds: &[World], chains: &mut [Option<Chain>], clearance: f64) {
settle_axes(worlds, chains, clearance);
refresh_spans(chains);
settle_axes(worlds, chains, clearance);
}
fn law_range(window: Option<(f64, f64)>, corr: &Corridor, clearance: f64) -> (f64, f64) {
let u = corr.usable(clearance);
match window {
Some(w) => {
let tight = (w.0.max(u.0), w.1.min(u.1));
if tight.0 <= tight.1 { tight } else { w }
}
None => {
if u.0 <= u.1 {
u
} else {
corr.walls
}
}
}
}
fn corner_clamp(worlds: &[World], chain: &Chain, ri: usize) -> (f64, f64) {
let graph = &worlds[chain.world].graph;
let travel = |r: &Run| {
match r.axis {
Axis::H => &graph.h[r.chan],
Axis::V => &graph.v[r.chan],
}
.travel()
};
let mut clamp = (f64::NEG_INFINITY, f64::INFINITY);
if ri > 0 {
let t = travel(&chain.runs[ri - 1]);
clamp = (clamp.0.max(t.0), clamp.1.min(t.1));
}
if ri + 1 < chain.runs.len() {
let t = travel(&chain.runs[ri + 1]);
clamp = (clamp.0.max(t.0), clamp.1.min(t.1));
}
clamp
}
fn refresh_spans(chains: &mut [Option<Chain>]) {
for chain in chains.iter_mut().flatten() {
let n = chain.runs.len();
if n < 2 {
continue;
}
let ords: Vec<f64> = chain
.runs
.iter()
.map(|r| r.ord.expect("first round placed every run"))
.collect();
for (i, run) in chain.runs.iter_mut().enumerate() {
let lo = if i == 0 {
chain.ends[0].side_coord()
} else {
ords[i - 1]
};
let hi = if i == n - 1 {
chain.ends[1].side_coord()
} else {
ords[i + 1]
};
run.span = (lo.min(hi), lo.max(hi));
}
}
}
fn settle_axes(worlds: &[World], chains: &mut [Option<Chain>], clearance: f64) {
let prefs: Vec<Vec<Pref>> = chains
.iter()
.map(|c| c.as_ref().map_or(Vec::new(), |ch| chain_prefs(ch, worlds)))
.collect();
let ests: Vec<Vec<f64>> = prefs
.iter()
.map(|v| v.iter().map(|p| p.0).collect())
.collect();
let mut by_axis: BTreeMap<u8, Vec<Item>> = BTreeMap::new();
for (ci, chain) in chains.iter().enumerate() {
let Some(chain) = chain else { continue };
let last = chain.runs.len() - 1;
for (ri, run) in chain.runs.iter().enumerate() {
let mut landings = Vec::new();
if ri == 0 {
landings.push((chain.ends[0].side, chain.ends[0].rect));
}
if ri == last {
landings.push((chain.ends[1].side, chain.ends[1].rect));
}
let span = (run.span.0.min(run.span.1), run.span.0.max(run.span.1));
by_axis.entry(run.axis.index()).or_default().push(Item {
members: vec![(ci, ri)],
span,
clamp: corner_clamp(worlds, chain, ri),
pref: prefs[ci][ri].0,
window: prefs[ci][ri].1,
link: chain.link,
world: chain.world,
chan: run.chan,
landings,
});
}
}
for (axis, mut items) in by_axis {
let axis = [Axis::H, Axis::V][axis as usize];
merge_fans(&mut items, chains);
items.sort_by(|a, b| {
a.span
.0
.total_cmp(&b.span.0)
.then(a.link.cmp(&b.link))
.then(a.world.cmp(&b.world))
.then(a.chan.cmp(&b.chan))
});
let corridors: Vec<Corridor> = items
.iter()
.map(|i| {
worlds[i.world]
.graph
.corridor(axis, i.chan, i.span.0, i.span.1)
})
.collect();
let n = items.len();
let mut parent: Vec<usize> = (0..n).collect();
fn root(parent: &mut [usize], mut i: usize) -> usize {
while parent[i] != i {
parent[i] = parent[parent[i]];
i = parent[i];
}
i
}
for i in 0..n {
for j in i + 1..n {
let near = near(items[i].span, items[j].span, clearance);
let shared = (items[i].world == items[j].world
&& (items[i].chan == items[j].chan
|| corridors[i].chans.contains(&items[j].chan)
|| corridors[j].chans.contains(&items[i].chan)))
|| items[i]
.landings
.iter()
.any(|l| items[j].landings.contains(l));
if near && shared {
let (a, b) = (root(&mut parent, i), root(&mut parent, j));
parent[a.max(b)] = a.min(b);
}
}
}
let mut clusters: BTreeMap<usize, Vec<(Item, Corridor)>> = BTreeMap::new();
for (i, (item, corr)) in items.into_iter().zip(corridors).enumerate() {
clusters
.entry(root(&mut parent, i))
.or_default()
.push((item, corr));
}
for (_, cluster) in clusters {
settle(cluster, clearance, chains, &ests);
}
}
}
fn chain_prefs(chain: &Chain, worlds: &[World]) -> Vec<Pref> {
let last = chain.runs.len() - 1;
chain
.runs
.iter()
.enumerate()
.map(|(ri, run)| {
let (a, b) = (&chain.ends[0], &chain.ends[1]);
if ri == 0 && ri == last {
let shared = (a.window.0.max(b.window.0), a.window.1.min(b.window.1));
debug_assert!(
shared.0 <= shared.1,
"a straight run needs overlapping windows (the search jogs otherwise)"
);
let mid = (a.centre() + b.centre()) / 2.0;
(mid.max(shared.0).min(shared.1), Some(shared))
} else if ri == 0 {
(a.centre(), Some(a.window))
} else if ri == last {
(b.centre(), Some(b.window))
} else {
let (lo, hi) = (run.span.0.min(run.span.1), run.span.0.max(run.span.1));
let corridor = worlds[chain.world]
.graph
.corridor(run.axis, run.chan, lo, hi);
(corridor.anchor(), None)
}
})
.collect()
}
fn merge_fans(items: &mut Vec<Item>, chains: &[Option<Chain>]) {
let mut merged: Vec<Item> = Vec::new();
for item in items.drain(..) {
let (ci, ri) = item.members[0];
let chain = chains[ci].as_ref().expect("placed chain");
let fan = fan_of(chain, ri);
let twin = fan.and_then(|f| {
merged.iter_mut().find(|m| {
let (mc, mr) = m.members[0];
fan_of(chains[mc].as_ref().expect("placed chain"), mr) == Some(f)
})
});
match twin {
Some(m) => {
m.span = (m.span.0.min(item.span.0), m.span.1.max(item.span.1));
m.clamp = (m.clamp.0.max(item.clamp.0), m.clamp.1.min(item.clamp.1));
m.window = match (m.window, item.window) {
(Some(a), Some(b)) => Some((a.0.max(b.0), a.1.min(b.1))),
(w, None) | (None, w) => w,
};
m.link = m.link.min(item.link);
m.members.extend(item.members);
for l in item.landings {
if !m.landings.contains(&l) {
m.landings.push(l);
}
}
}
None => merged.push(item),
}
}
*items = std::mem::take(&mut merged);
}
fn fan_of(chain: &Chain, ri: usize) -> Option<usize> {
let last = chain.runs.len() - 1;
match (ri == 0, ri == last) {
(true, true) => chain.ends[0].fan.or(chain.ends[1].fan),
(true, false) => chain.ends[0].fan,
(false, true) => chain.ends[1].fan,
_ => None,
}
}
fn settle(
mut cluster: Vec<(Item, Corridor)>,
clearance: f64,
chains: &mut [Option<Chain>],
ests: &[Vec<f64>],
) {
let ctx = order::Ctx {
chains: &*chains,
ests,
};
cluster.sort_by(|a, b| {
a.0.pref
.total_cmp(&b.0.pref)
.then_with(|| order::cmp_runs(&ctx, a.0.members[0], b.0.members[0]))
});
let n = cluster.len();
let prefs: Vec<f64> = cluster.iter().map(|(i, _)| i.pref).collect();
let bounds: Vec<(f64, f64)> = cluster
.iter()
.map(|(i, corr)| {
let r = law_range(i.window, corr, clearance);
let tight = (r.0.max(i.clamp.0), r.1.min(i.clamp.1));
if tight.0 <= tight.1 { tight } else { r }
})
.collect();
let mut seps: Vec<f64> = cluster
.windows(2)
.map(|w| {
if contend(&w[0].0, &w[1].0, clearance) {
clearance
} else {
0.0
}
})
.collect();
let chain_ok = (0..n).all(|i| {
(i + 2..n).all(|j| {
!contend(&cluster[i].0, &cluster[j].0, clearance) || seps[i..j].iter().all(|s| *s > 0.0)
})
});
let mut feasible = chain_ok;
if chain_ok {
for _ in 0..n.max(1) * 2 {
let mut binding = 0;
let mut x = f64::NEG_INFINITY;
let mut violated = None;
for k in 0..n {
let pushed = if k == 0 { bounds[k].0 } else { x + seps[k - 1] };
if pushed <= bounds[k].0 {
binding = k;
}
x = pushed.max(bounds[k].0);
if x > bounds[k].1 + 1e-9 {
violated = Some((binding, k));
break;
}
}
let Some((i, j)) = violated else {
feasible = true;
break;
};
feasible = false;
let avail = (bounds[j].1 - bounds[i].0).max(0.0);
let gaps = seps[i..j].iter().filter(|s| **s > 0.0).count().max(1);
let target = (avail / gaps as f64).max(min_pitch(clearance));
let mut lowered = false;
for s in &mut seps[i..j] {
if *s > target {
*s = target;
lowered = true;
}
}
if !lowered {
break;
}
}
}
let ords = if feasible {
ladder(&prefs, &bounds, &seps)
} else {
super::pairwise::pairwise(&cluster, &prefs, &bounds, clearance)
};
for ((item, _), ord) in cluster.iter().zip(&ords) {
for &(ci, ri) in &item.members {
chains[ci].as_mut().expect("placed chain").runs[ri].ord = Some(*ord);
}
}
}
pub(super) fn contend(a: &Item, b: &Item, clearance: f64) -> bool {
let same_wire = a
.members
.iter()
.any(|(c0, _)| b.members.iter().any(|(c1, _)| c0 == c1));
let overlap = a.span.0.max(b.span.0) < a.span.1.min(b.span.1);
overlap || (near(a.span, b.span, clearance) && !same_wire)
}
fn near(a: (f64, f64), b: (f64, f64), clearance: f64) -> bool {
b.0 <= a.1 + clearance + 1e-6 && a.0 <= b.1 + clearance + 1e-6
}
#[cfg(test)]
mod tests {
use super::super::graph::ChannelGraph;
use super::super::rect::Rect;
use super::super::{EndInfo, Run};
use super::*;
use crate::ast::Side;
const C: f64 = 8.0;
fn world(bounds: Rect, keepouts: &[Rect]) -> World {
World {
path: String::new(),
graph: ChannelGraph::build(bounds, keepouts, false),
}
}
fn end(side: Side, rect: Rect) -> EndInfo {
let window = match side {
Side::Left | Side::Right => (rect.y0 + C, rect.y1 - C),
Side::Top | Side::Bottom => (rect.x0 + C, rect.x1 - C),
};
EndInfo {
side,
rect,
window,
fan: None,
}
}
fn facing() -> (World, Rect, Rect) {
let a = Rect::new(20.0, 20.0, 40.0, 80.0);
let b = Rect::new(160.0, 20.0, 180.0, 80.0);
let w = world(
Rect::new(0.0, 0.0, 200.0, 100.0),
&[a.inflate(C), b.inflate(C)],
);
(w, a, b)
}
fn h_chan(w: &World, x: f64, y: f64) -> usize {
w.graph
.h
.iter()
.position(|c| x >= c.rect.x0 && x <= c.rect.x1 && y >= c.rect.y0 && y <= c.rect.y1)
.expect("h channel at point")
}
fn straight(link: usize, a: Rect, b: Rect, chan: usize) -> Chain {
Chain {
link,
world: 0,
runs: vec![Run {
axis: Axis::H,
chan,
span: (a.x1, b.x0),
ord: None,
}],
ends: [end(Side::Right, a), end(Side::Left, b)],
}
}
#[test]
fn a_lone_straight_takes_the_shared_centre() {
let (w, a, b) = facing();
let chan = h_chan(&w, 100.0, 50.0);
let mut chains = vec![Some(straight(0, a, b, chan))];
place(&[w], &mut chains, C);
assert_eq!(chains[0].as_ref().unwrap().runs[0].ord, Some(50.0));
}
#[test]
fn a_bundle_ladders_centred_on_the_shared_centre() {
let (w, a, b) = facing();
let chan = h_chan(&w, 100.0, 50.0);
let mut chains: Vec<Option<Chain>> =
(0..4).map(|i| Some(straight(i, a, b, chan))).collect();
place(&[w], &mut chains, C);
let ords: Vec<f64> = chains
.iter()
.map(|c| c.as_ref().unwrap().runs[0].ord.unwrap())
.collect();
assert_eq!(ords, vec![38.0, 46.0, 54.0, 62.0]);
}
#[test]
fn an_interior_run_rests_on_the_channel_midline() {
let (w, a, b) = facing();
let hchan = h_chan(&w, 100.0, 50.0);
let vchan = w
.graph
.v
.iter()
.position(|c| c.rect == Rect::new(48.0, 0.0, 152.0, 100.0))
.expect("middle V channel");
let mut chains = vec![Some(Chain {
link: 0,
world: 0,
runs: vec![
Run {
axis: Axis::H,
chan: hchan,
span: (40.0, 100.0),
ord: None,
},
Run {
axis: Axis::V,
chan: vchan,
span: (48.0, 52.0),
ord: None,
},
Run {
axis: Axis::H,
chan: hchan,
span: (100.0, 160.0),
ord: None,
},
],
ends: [end(Side::Right, a), end(Side::Left, b)],
})];
place(&[w], &mut chains, C);
let runs = &chains[0].as_ref().unwrap().runs;
assert_eq!(runs[0].ord, Some(50.0));
assert_eq!(runs[1].ord, Some(100.0));
assert_eq!(runs[2].ord, Some(50.0));
}
#[test]
fn turning_wires_nest_in_arrival_order() {
let a1 = Rect::new(20.0, 10.0, 40.0, 26.0);
let a2 = Rect::new(20.0, 34.0, 40.0, 50.0);
let b = Rect::new(80.0, 160.0, 120.0, 180.0);
let w = world(
Rect::new(0.0, 0.0, 200.0, 200.0),
&[a1.inflate(C), a2.inflate(C), b.inflate(C)],
);
let vchan = w
.graph
.v
.iter()
.position(|c| {
c.rect.x0 <= 88.0 && c.rect.x1 >= 112.0 && c.rect.y0 <= 60.0 && c.rect.y1 >= 140.0
})
.expect("V channel above b");
let l_chain = |link: usize, src: Rect, hchan: usize| Chain {
link,
world: 0,
runs: vec![
Run {
axis: Axis::H,
chan: hchan,
span: (src.x1, 100.0),
ord: None,
},
Run {
axis: Axis::V,
chan: vchan,
span: ((src.y0 + src.y1) / 2.0, 160.0),
ord: None,
},
],
ends: [end(Side::Right, src), end(Side::Top, b)],
};
let h1 = h_chan(&w, 60.0, 18.0);
let h2 = h_chan(&w, 60.0, 42.0);
let mut chains = vec![Some(l_chain(0, a1, h1)), Some(l_chain(1, a2, h2))];
place(&[w], &mut chains, C);
let x1 = chains[0].as_ref().unwrap().runs[1].ord.unwrap();
let x2 = chains[1].as_ref().unwrap().runs[1].ord.unwrap();
assert!(
x1 > x2,
"upper wire turns outside the lower: x1={x1} x2={x2}"
);
}
#[test]
fn fan_siblings_share_one_port_ordinate() {
let (w, a, b) = facing();
let chan = h_chan(&w, 100.0, 50.0);
let mut c1 = straight(0, a, b, chan);
let mut c2 = straight(1, a, b, chan);
c1.ends[0].fan = Some(0);
c2.ends[0].fan = Some(0);
let mut chains = vec![Some(c1), Some(c2)];
place(&[w], &mut chains, C);
let o1 = chains[0].as_ref().unwrap().runs[0].ord.unwrap();
let o2 = chains[1].as_ref().unwrap().runs[0].ord.unwrap();
assert_eq!(o1, o2, "one fan, one port");
}
#[test]
fn disjoint_clusters_both_take_the_midline() {
let w = world(Rect::new(0.0, 0.0, 400.0, 100.0), &[]);
let interior = |link: usize, span: (f64, f64)| Chain {
link,
world: 0,
runs: vec![
Run {
axis: Axis::V,
chan: 0,
span: (10.0, 20.0),
ord: None,
},
Run {
axis: Axis::H,
chan: 0,
span,
ord: None,
},
Run {
axis: Axis::V,
chan: 0,
span: (80.0, 90.0),
ord: None,
},
],
ends: [
end(Side::Bottom, Rect::new(span.0 - 20.0, 0.0, span.0, 10.0)),
end(Side::Bottom, Rect::new(span.1, 0.0, span.1 + 20.0, 10.0)),
],
};
let mut chains = vec![
Some(interior(0, (40.0, 120.0))),
Some(interior(1, (240.0, 320.0))),
];
place(&[w], &mut chains, C);
let m1 = chains[0].as_ref().unwrap().runs[1].ord.unwrap();
let m2 = chains[1].as_ref().unwrap().runs[1].ord.unwrap();
assert_eq!(m1, 50.0, "the empty world's H anchor is its midline");
assert_eq!(m1, m2, "disjoint spans share the midline in peace");
}
#[test]
fn place_is_deterministic() {
let (w, a, b) = facing();
let chan = h_chan(&w, 100.0, 50.0);
let run = |chains: &mut Vec<Option<Chain>>| {
place(
&[world(
Rect::new(0.0, 0.0, 200.0, 100.0),
&[a.inflate(C), b.inflate(C)],
)],
chains,
C,
);
chains
.iter()
.map(|c| c.as_ref().unwrap().runs[0].ord.unwrap())
.collect::<Vec<f64>>()
};
let mut first = (0..4)
.map(|i| Some(straight(i, a, b, chan)))
.collect::<Vec<_>>();
let baseline = run(&mut first);
for _ in 0..50 {
let mut again = (0..4)
.map(|i| Some(straight(i, a, b, chan)))
.collect::<Vec<_>>();
assert_eq!(run(&mut again), baseline);
}
}
}