use super::graph::{Axis, Channel};
use super::rect::Rect;
use super::runs::{Chain, EPS, Pin};
use crate::ast::Side;
use std::collections::BTreeMap;
pub(super) type ChanKey = (usize, u8, usize);
#[derive(Clone, Copy, Debug, PartialEq)]
struct SpanRec {
lo: f64,
hi: f64,
fan: Option<usize>,
pin: Option<f64>,
unit: Option<u32>,
margin: bool,
}
pub struct Occupancy {
map: BTreeMap<ChanKey, Vec<SpanRec>>,
units: BTreeMap<(String, u8), u32>,
clearance: f64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Closure {
Open,
Short(usize),
Hard,
}
impl Occupancy {
pub fn new(clearance: f64) -> Occupancy {
Occupancy {
map: BTreeMap::new(),
units: BTreeMap::new(),
clearance,
}
}
fn unit_id(&mut self, path: &str, side: Side) -> u32 {
let next = self.units.len() as u32;
*self
.units
.entry((path.to_owned(), side.index()))
.or_insert(next)
}
#[cfg(test)]
#[allow(clippy::too_many_arguments)]
pub fn closes(
&self,
channel: &Channel,
world: usize,
axis: Axis,
chan: usize,
lo: f64,
hi: f64,
k: usize,
exempt: Option<usize>,
relaxed: bool,
deny: &[Rect],
) -> bool {
self.closure(
channel, world, axis, chan, lo, hi, k, exempt, relaxed, false, deny,
) != Closure::Open
}
#[allow(clippy::too_many_arguments)]
pub fn closure(
&self,
channel: &Channel,
world: usize,
axis: Axis,
chan: usize,
lo: f64,
hi: f64,
k: usize,
exempt: Option<usize>,
relaxed: bool,
margin: bool,
deny: &[Rect],
) -> Closure {
let c = self.clearance;
let empty = Vec::new();
let spans = self.map.get(&(world, axis.index(), chan)).unwrap_or(&empty);
let mut hull = (lo, hi);
let mut joined = vec![false; spans.len()];
loop {
let mut grew = false;
for (i, s) in spans.iter().enumerate() {
if joined[i] || (s.fan.is_some() && s.fan == exempt) {
continue;
}
if s.lo < hull.1 + c && s.hi > hull.0 - c {
joined[i] = true;
hull = (hull.0.min(s.lo), hull.1.max(s.hi));
grew = true;
}
}
if !grew {
break;
}
}
type Member = (f64, f64, Option<f64>, Option<u32>, bool);
let mut members: Vec<Member> = spans
.iter()
.zip(&joined)
.filter(|&(_, &j)| j)
.map(|(s, _)| (s.lo, s.hi, s.pin, s.unit, s.margin))
.collect();
members.sort_by(|a, b| a.0.total_cmp(&b.0).then(a.1.total_cmp(&b.1)));
members.extend(std::iter::repeat_n((lo, hi, None, None, margin), k));
let packed: Vec<(f64, f64, Option<f64>)> = members
.iter()
.map(|&(lo, hi, pin, ..)| (lo, hi, pin))
.collect();
let (at, pins) = pack(&packed, c);
let (u0, u1) = channel.usable(hull.0, hull.1, c);
let (mut b0, mut b1) = if relaxed { channel.walls() } else { (u0, u1) };
if margin {
if channel.outer[0] {
b0 = f64::NEG_INFINITY;
}
if channel.outer[1] {
b1 = f64::INFINITY;
}
}
let anchored = margin || members.iter().any(|m| m.4);
let mid = cluster_midline(channel, u0, u1, pins.len(), c, anchored);
let ords = cluster_ordinates(&pins, mid, c);
let beyond = |o: f64, wall: usize| match wall {
0 => o < b0 - EPS,
_ => o > b1 + EPS,
};
let inner_out = ords
.iter()
.filter(|&&o| {
(beyond(o, 0) && !channel.outer[0]) || (beyond(o, 1) && !channel.outer[1])
})
.count();
if inner_out > 0 {
return Closure::Short(inner_out);
}
if ords.iter().any(|&o| beyond(o, 0) || beyond(o, 1)) {
return Closure::Hard;
}
let denied = at[members.len() - k..].iter().any(|&rail| {
deny.iter().any(|d| {
let ((d0, d1), (t0, t1)) = match axis {
Axis::H => ((d.y0, d.y1), (d.x0, d.x1)),
Axis::V => ((d.x0, d.x1), (d.y0, d.y1)),
};
lo < t1 && hi > t0 && ords[rail] > d0 && ords[rail] < d1
})
});
if denied {
return Closure::Hard;
}
let pinched = members
.iter()
.enumerate()
.any(|(i, &(alo, ahi, _, ua, _))| {
members[i + 1..].iter().zip(&at[i + 1..]).any(|(b, &rb)| {
at[i] != rb
&& !(ua.is_some() && ua == b.3)
&& alo < b.1 + c
&& ahi > b.0 - c
&& (ords[at[i]] - ords[rb]).abs() < c - EPS
})
});
if pinched {
Closure::Hard
} else {
Closure::Open
}
}
#[allow(clippy::too_many_arguments)]
pub fn commit(
&mut self,
world: usize,
axis: Axis,
chan: usize,
lo: f64,
hi: f64,
fan: Option<usize>,
pin: Option<f64>,
unit: Option<u32>,
margin: bool,
) {
if hi < lo {
return;
}
let rec = SpanRec {
lo,
hi,
fan,
pin,
unit,
margin,
};
let spans = self.map.entry((world, axis.index(), chan)).or_default();
if fan.is_some() && spans.contains(&rec) {
return;
}
spans.push(rec);
}
pub fn commit_chain(&mut self, chain: &Chain) {
let fan = chain.ends[0].fan.or(chain.ends[1].fan);
for r in &chain.runs {
let (lo, hi) = r.span();
let (pin, unit) = match r.pin {
Pin::Free => (None, None),
Pin::Fixed(v) => (Some(v), None),
Pin::Port(e) => {
let end = &chain.ends[e];
let unit = self.unit_id(&end.path, end.side);
(Some(r.ord), Some(unit))
}
};
self.commit(
chain.world,
r.axis,
r.chan,
lo,
hi,
fan,
pin,
unit,
chain.margin,
);
}
}
}
pub(super) fn pack(
members: &[(f64, f64, Option<f64>)],
clearance: f64,
) -> (Vec<usize>, Vec<Option<f64>>) {
struct Rail {
pin: Option<f64>,
spans: Vec<(f64, f64)>,
}
let mut rails: Vec<Rail> = Vec::new();
let mut at: Vec<usize> = Vec::with_capacity(members.len());
for (i, &(lo, hi, pin)) in members.iter().enumerate() {
let same_pin = pin.and_then(|p| rails.iter().position(|r| r.pin == Some(p)));
let slot = same_pin.or_else(|| {
let floor = members[..i]
.iter()
.zip(&at)
.filter(|&(&(l, h, _), _)| l < hi && h > lo)
.map(|(_, &r)| r + 1)
.max()
.unwrap_or(0);
(floor..rails.len()).find(|&r| {
let pin_free = rails[r].pin.is_none() || pin.is_none();
pin_free
&& rails[r]
.spans
.iter()
.all(|&(a, b)| lo >= b + clearance || hi <= a - clearance)
})
});
let r = slot.unwrap_or_else(|| {
rails.push(Rail {
pin: None,
spans: Vec::new(),
});
rails.len() - 1
});
if pin.is_some() {
rails[r].pin = pin;
}
rails[r].spans.push((lo, hi));
at.push(r);
}
(at, rails.iter().map(|r| r.pin).collect())
}
pub struct Ports {
used: BTreeMap<(String, u8), usize>,
clearance: f64,
}
pub(super) fn side_len(rect: Rect, side: Side) -> f64 {
match side {
Side::Left | Side::Right => rect.h(),
Side::Top | Side::Bottom => rect.w(),
}
}
pub fn side_capacity(rect: Rect, side: Side, clearance: f64) -> usize {
let free = side_len(rect, side) - 2.0 * clearance;
if free < 0.0 {
1
} else {
(free / clearance).floor() as usize + 1
}
}
impl Ports {
pub fn new(clearance: f64) -> Ports {
Ports {
used: BTreeMap::new(),
clearance,
}
}
pub fn capacity(&self, rect: Rect, side: Side) -> usize {
side_capacity(rect, side, self.clearance)
}
pub fn free(&self, path: &str, side: Side, rect: Rect) -> usize {
let used = self
.used
.get(&(path.to_owned(), side.index()))
.copied()
.unwrap_or(0);
self.capacity(rect, side).saturating_sub(used)
}
pub fn commit(&mut self, path: &str, side: Side, n: usize) {
*self
.used
.entry((path.to_owned(), side.index()))
.or_insert(0) += n;
}
}
pub(super) fn cluster_midline(
channel: &Channel,
u0: f64,
u1: f64,
n: usize,
clearance: f64,
anchored: bool,
) -> f64 {
let half = (n as f64 - 1.0) / 2.0 * clearance;
if !anchored || 2.0 * half <= u1 - u0 + EPS {
return (u0 + u1) / 2.0;
}
match channel.outer {
[false, true] => u0 + half,
[true, false] => u1 - half,
_ => (u0 + u1) / 2.0,
}
}
pub(super) fn cluster_ordinates(pins: &[Option<f64>], midline: f64, clearance: f64) -> Vec<f64> {
let n = pins.len();
let mut ords = vec![0.0; n];
let pin_pos: Vec<usize> = (0..n).filter(|&i| pins[i].is_some()).collect();
if pin_pos.is_empty() {
for (i, o) in ords.iter_mut().enumerate() {
*o = midline + (i as f64 - (n as f64 - 1.0) / 2.0) * clearance;
}
return ords;
}
for &i in &pin_pos {
ords[i] = pins[i].unwrap();
}
let first = pin_pos[0];
let last = *pin_pos.last().unwrap();
for i in (0..first).rev() {
ords[i] = ords[i + 1] - clearance;
}
for i in last + 1..n {
ords[i] = ords[i - 1] + clearance;
}
for w in pin_pos.windows(2) {
let (a, b) = (w[0], w[1]);
let gap = (ords[b] - ords[a]) / (b - a) as f64;
for i in a + 1..b {
ords[i] = ords[a] + gap * (i - a) as f64;
}
}
ords
}
#[cfg(test)]
mod tests {
use super::*;
fn vchan(w0: f64, w1: f64) -> Channel {
Channel {
rect: Rect::new(w0, 0.0, w1, 1000.0),
axis: Axis::V,
soft: [Vec::new(), Vec::new()],
outer: [false, false],
}
}
#[test]
fn runs_clear_along_the_channel_share_a_lane() {
let mut occ = Occupancy::new(8.0);
occ.commit(0, Axis::V, 0, 0.0, 10.0, None, None, None, false);
occ.commit(0, Axis::V, 0, 50.0, 60.0, None, None, None, false);
let two_lanes = vchan(0.0, 8.0);
assert!(!occ.closes(&two_lanes, 0, Axis::V, 0, 8.0, 52.0, 1, None, false, &[]));
occ.commit(0, Axis::V, 0, 8.0, 52.0, None, None, None, false);
assert!(occ.closes(&two_lanes, 0, Axis::V, 0, 12.0, 48.0, 1, None, false, &[]));
}
#[test]
fn mutually_close_runs_need_distinct_lanes() {
let mut occ = Occupancy::new(8.0);
occ.commit(0, Axis::V, 0, 0.0, 10.0, None, None, None, false);
occ.commit(0, Axis::V, 0, 5.0, 15.0, None, None, None, false);
assert!(occ.closes(
&vchan(0.0, 8.0),
0,
Axis::V,
0,
8.0,
12.0,
1,
None,
false,
&[]
));
assert!(!occ.closes(
&vchan(0.0, 16.0),
0,
Axis::V,
0,
8.0,
12.0,
1,
None,
false,
&[]
));
}
#[test]
fn degenerate_jog_spans_still_count() {
let mut occ = Occupancy::new(8.0);
occ.commit(0, Axis::V, 0, 0.0, 0.0, None, None, None, false);
assert!(occ.closes(
&vchan(0.0, 4.0),
0,
Axis::V,
0,
5.0,
5.0,
1,
None,
false,
&[]
));
assert!(!occ.closes(
&vchan(0.0, 8.0),
0,
Axis::V,
0,
5.0,
5.0,
1,
None,
false,
&[]
));
}
#[test]
fn close_pins_close_the_channel() {
let mut occ = Occupancy::new(8.0);
occ.commit(0, Axis::V, 0, 0.0, 10.0, None, Some(2.0), None, false);
assert!(!occ.closes(
&vchan(0.0, 16.0),
0,
Axis::V,
0,
5.0,
12.0,
1,
None,
false,
&[]
));
occ.commit(0, Axis::V, 0, 5.0, 12.0, None, Some(6.0), None, false);
assert!(occ.closes(
&vchan(0.0, 16.0),
0,
Axis::V,
0,
3.0,
9.0,
1,
None,
false,
&[]
));
}
#[test]
fn fan_trunk_counts_once_and_never_blocks_itself() {
let mut occ = Occupancy::new(8.0);
occ.commit(0, Axis::H, 2, 0.0, 50.0, Some(7), Some(5.0), None, false);
occ.commit(0, Axis::H, 2, 0.0, 50.0, Some(7), Some(5.0), None, false);
occ.commit(0, Axis::H, 2, 30.0, 80.0, Some(7), Some(5.0), None, false);
let hchan = |w0: f64, w1: f64| Channel {
rect: Rect::new(0.0, w0, 1000.0, w1),
axis: Axis::H,
soft: [Vec::new(), Vec::new()],
outer: [false, false],
};
assert!(!occ.closes(
&hchan(0.0, 8.0),
0,
Axis::H,
2,
0.0,
80.0,
1,
Some(7),
false,
&[]
));
assert!(!occ.closes(
&hchan(0.0, 16.0),
0,
Axis::H,
2,
0.0,
80.0,
1,
None,
false,
&[]
));
assert!(occ.closes(
&hchan(0.0, 16.0),
0,
Axis::H,
2,
0.0,
80.0,
2,
None,
false,
&[]
));
}
#[test]
fn pack_shares_lanes_and_keeps_overlapping_members_ordered() {
let (at, pins) = pack(
&[(0.0, 10.0, None), (8.0, 52.0, None), (60.0, 70.0, None)],
8.0,
);
assert_eq!(at, vec![0, 1, 0]);
assert_eq!(pins, vec![None, None]);
}
#[test]
fn pack_anchors_same_pin_members_to_one_rail() {
let (at, pins) = pack(
&[
(0.0, 50.0, Some(5.0)),
(30.0, 80.0, Some(5.0)),
(20.0, 40.0, None),
],
8.0,
);
assert_eq!(at, vec![0, 0, 1]);
assert_eq!(pins, vec![Some(5.0), None]);
}
#[test]
fn port_capacity_floors_at_one() {
let ports = Ports::new(8.0);
let r = Rect::new(0.0, 0.0, 60.0, 10.0);
assert_eq!(ports.capacity(r, Side::Top), 6);
assert_eq!(ports.capacity(r, Side::Right), 1);
}
#[test]
fn port_slots_run_out() {
let mut ports = Ports::new(8.0);
let r = Rect::new(0.0, 0.0, 30.0, 30.0);
assert_eq!(ports.capacity(r, Side::Left), 2);
assert_eq!(ports.free("a", Side::Left, r), 2);
ports.commit("a", Side::Left, 2);
assert_eq!(ports.free("a", Side::Left, r), 0);
assert_eq!(ports.free("a", Side::Right, r), 2);
}
#[test]
fn pinless_cluster_centres_at_clearance_pitch() {
let ords = cluster_ordinates(&[None, None, None], 50.0, 8.0);
assert_eq!(ords, vec![42.0, 50.0, 58.0]);
}
#[test]
fn pins_anchor_and_frees_step_outward() {
let ords = cluster_ordinates(&[None, Some(40.0), None, None], 99.0, 8.0);
assert_eq!(ords, vec![32.0, 40.0, 48.0, 56.0]);
}
#[test]
fn frees_between_two_pins_spread_evenly() {
let ords = cluster_ordinates(&[Some(10.0), None, None, Some(40.0)], 0.0, 8.0);
assert_eq!(ords, vec![10.0, 20.0, 30.0, 40.0]);
}
#[test]
fn pins_at_extremes_hold_their_ordinates() {
let ords = cluster_ordinates(&[Some(0.0), None, Some(16.0)], 100.0, 8.0);
assert_eq!(ords, vec![0.0, 8.0, 16.0]);
}
}