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 {
pub(super) members: Vec<(usize, usize)>,
pub(super) span: (f64, f64),
clamp: (f64, f64),
pref: f64,
pub(super) window: Option<(f64, f64)>,
link: usize,
world: usize,
pub(super) 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) -> (f64, f64) {
let u = corr.usable();
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 => u,
}
}
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
}
pub(super) 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 (ests, by_axis) = collect(worlds, chains);
for (axis, mut items) in by_axis {
let axis = [Axis::H, Axis::V][axis as usize];
merge_fans(&mut items, chains);
for cluster in clusters_of(axis, items, worlds, clearance) {
settle(cluster, clearance, chains, &ests);
}
}
}
pub(super) fn collect(
worlds: &[World],
chains: &[Option<Chain>],
) -> (Vec<Vec<f64>>, BTreeMap<u8, Vec<Item>>) {
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,
});
}
}
(ests, by_axis)
}
pub(super) fn clusters_of(
axis: Axis,
mut items: Vec<Item>,
worlds: &[World],
clearance: f64,
) -> Vec<Vec<(Item, Corridor)>> {
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 abuts = corridors[i].walls.1 == corridors[j].walls.0
|| corridors[j].walls.1 == corridors[i].walls.0;
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)
|| abuts))
|| 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));
}
clusters.into_values().collect()
}
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);
let clamp = corner_clamp(worlds, chain, ri);
(corridor.clipped(clamp.0, clamp.1).anchor(), None)
}
})
.collect()
}
pub(super) 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,
}
}
pub(super) fn bound((i, corr): &(Item, Corridor)) -> (f64, f64) {
let r = law_range(i.window, corr);
let tight = (r.0.max(i.clamp.0), r.1.min(i.clamp.1));
if tight.0 <= tight.1 { tight } else { r }
}
pub(super) fn arrange(
cluster: Vec<(Item, Corridor)>,
chains: &[Option<Chain>],
ests: &[Vec<f64>],
) -> (Vec<f64>, Vec<(Item, Corridor)>) {
let ctx = order::Ctx { chains, ests };
let reps: Vec<(usize, usize)> = cluster.iter().map(|(i, _)| i.members[0]).collect();
let item_prefs: Vec<f64> = cluster
.iter()
.map(|c| {
let (lo, hi) = bound(c);
c.0.pref.max(lo).min(hi)
})
.collect();
let pos = order::ranks(&ctx, &reps, &item_prefs);
let mut indexed: Vec<_> = pos
.into_iter()
.zip(item_prefs.into_iter().zip(cluster))
.collect();
indexed.sort_by_key(|(p, _)| *p);
indexed.into_iter().map(|(_, pc)| pc).unzip()
}
pub(super) fn overrun(bounds: &[(f64, f64)], seps: &[f64]) -> Option<(usize, usize)> {
let mut binding = 0;
let mut x = f64::NEG_INFINITY;
for k in 0..bounds.len() {
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 {
return Some((binding, k));
}
}
None
}
fn settle(
cluster: Vec<(Item, Corridor)>,
clearance: f64,
chains: &mut [Option<Chain>],
ests: &[Vec<f64>],
) {
let (prefs, cluster) = arrange(cluster, &*chains, ests);
let n = cluster.len();
let bounds: Vec<(f64, f64)> = cluster.iter().map(bound).collect();
let mut seps: Vec<f64> = cluster
.windows(2)
.map(|w| owed(&w[0].0, &w[1].0, clearance, clearance))
.collect();
let chain_ok = seps.iter().all(|s| *s > 0.0)
&& (0..n).all(|i| {
(i + 2..n).all(|j| {
owed(&cluster[i].0, &cluster[j].0, clearance, clearance)
<= seps[i..j].iter().sum::<f64>() + 1e-9
})
});
let mut feasible = chain_ok;
if chain_ok {
for _ in 0..n.max(1) * 2 {
let Some((i, j)) = overrun(&bounds, &seps) 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)
}
pub(super) fn owed(a: &Item, b: &Item, clearance: f64, pitch: f64) -> f64 {
if !contend(a, b, clearance) {
return 0.0;
}
let gap = (b.span.0 - a.span.1).max(a.span.0 - b.span.1).max(0.0);
(pitch * pitch - gap * gap).max(0.0).sqrt()
}
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);
}
}
}