use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use crate::{
graph::{Graph, GraphMut, edge::Edge, node::Node},
triskel::{
layout::{LayoutGraph, layer_of, loop_reserve},
order::{Ordering, Slot},
router::assign_ports,
segment::SegmentInfo,
},
};
enum Entity {
Vertex(usize),
Segment,
}
struct Cells {
rank: Vec<usize>,
pos: Vec<usize>,
width: Vec<f64>,
is_dummy: Vec<bool>,
entity: Vec<Entity>,
layers: Vec<Vec<usize>>,
parents: Vec<Vec<usize>>,
children: Vec<Vec<usize>>,
parent_off: Vec<Vec<(f64, f64)>>,
child_off: Vec<Vec<(f64, f64)>>,
segment_chains: Vec<Vec<usize>>,
}
pub(crate) fn assign_x(
graph: &mut LayoutGraph,
seg: &SegmentInfo,
ordering: &Ordering,
node_gap: f64,
) {
let cells = build_cells(graph, seg, ordering);
if cells.rank.is_empty() {
return;
}
let marked = mark_type1(&cells);
let mut sum = vec![0.0f64; cells.rank.len()];
for &down in &[true, false] {
for &left in &[true, false] {
let xs = directional_pass(&cells, &marked, node_gap, down, left);
for (i, x) in xs.into_iter().enumerate() {
sum[i] += x;
}
}
}
let mut xs: Vec<f64> = sum.into_iter().map(|x| x / 4.0).collect();
assert!(enforce_coordinate_constraints(
&cells,
&[],
node_gap,
&mut xs
));
let mut gadget_equalities = Vec::new();
for equality in gadget_endpoint_equalities(graph, &cells) {
let mut candidate_equalities = gadget_equalities.clone();
candidate_equalities.push(equality);
let mut candidate_xs = xs.clone();
if enforce_coordinate_constraints(
&cells,
&candidate_equalities,
node_gap,
&mut candidate_xs,
) {
gadget_equalities = candidate_equalities;
xs = candidate_xs;
}
}
for (cid, entity) in cells.entity.iter().enumerate() {
if let Entity::Vertex(id) = entity {
graph.get_node_mut(*id).unwrap().x = xs[cid];
}
}
}
fn build_cells(graph: &LayoutGraph, seg: &SegmentInfo, ordering: &Ordering) -> Cells {
let slots = &ordering.slots;
let mut rank = Vec::new();
let mut pos = Vec::new();
let mut width = Vec::new();
let mut is_dummy = Vec::new();
let mut entity = Vec::new();
let mut layers: Vec<Vec<usize>> = vec![Vec::new(); slots.len()];
let mut vertex_cell: HashMap<usize, usize> = HashMap::default();
let mut seg_cell: HashMap<(usize, usize), usize> = HashMap::default();
for (r, row) in slots.iter().enumerate() {
for (i, slot) in row.iter().enumerate() {
let cid = rank.len();
rank.push(r);
pos.push(i);
match *slot {
Slot::Vertex(id) => {
let node = graph.get_node(id).unwrap();
width.push(node.width + 2.0 * loop_reserve(node.self_loops));
is_dummy.push(node.is_dummy);
entity.push(Entity::Vertex(id));
vertex_cell.insert(id, cid);
}
Slot::Segment(qid) => {
width.push(0.0);
is_dummy.push(true);
entity.push(Entity::Segment);
seg_cell.insert((r, qid), cid);
}
}
layers[r].push(cid);
}
}
let n = rank.len();
let mut parents = vec![Vec::new(); n];
let mut children = vec![Vec::new(); n];
let mut parent_off: Vec<Vec<(f64, f64)>> = vec![Vec::new(); n];
let mut child_off: Vec<Vec<(f64, f64)>> = vec![Vec::new(); n];
let mut link = |p: usize, c: usize, off: (f64, f64)| {
children[p].push(c);
child_off[p].push(off);
parents[c].push(p);
parent_off[c].push(off);
};
let ports = assign_ports(graph);
let off_of = |eid: usize| {
(
ports.start.get(&eid).copied().unwrap_or(0.0),
ports.end.get(&eid).copied().unwrap_or(0.0),
)
};
let mut edge_ids: Vec<usize> = graph.edges().map(|e| e.id()).collect();
edge_ids.sort_unstable();
for eid in edge_ids {
let edge = graph.get_edge(eid).unwrap();
let from = edge.from_id();
let to = edge.to_id();
if seg.pvertices.contains(&from) && seg.qvertices.contains(&to) {
continue; }
if layer_of(graph, to) == layer_of(graph, from) + 1
&& let (Some(&p), Some(&c)) = (vertex_cell.get(&from), vertex_cell.get(&to))
{
link(p, c, off_of(eid));
}
}
let mut segment_chains = Vec::new();
let mut qids: Vec<usize> = seg.qvertices.iter().copied().collect();
qids.sort_unstable();
for q in qids {
let p = graph
.get_node(q)
.unwrap()
.parents()
.next()
.unwrap()
.node_id();
let prank = layer_of(graph, p);
let qrank = layer_of(graph, q);
let mut chain = vec![vertex_cell[&p]];
for r in (prank + 1)..qrank {
chain.push(seg_cell[&(r, q)]);
}
chain.push(vertex_cell[&q]);
for pair in chain.windows(2) {
link(pair[0], pair[1], (0.0, 0.0));
}
segment_chains.push(chain);
}
Cells {
rank,
pos,
width,
is_dummy,
entity,
layers,
parents,
children,
parent_off,
child_off,
segment_chains,
}
}
fn enforce_coordinate_constraints(
cells: &Cells,
extra_equalities: &[(usize, usize)],
node_gap: f64,
xs: &mut [f64],
) -> bool {
fn find(parent: &mut [usize], x: usize) -> usize {
if parent[x] != x {
parent[x] = find(parent, parent[x]);
}
parent[x]
}
let n = xs.len();
let mut parent: Vec<usize> = (0..n).collect();
let mut unite = |left: usize, right: usize| {
let a = find(&mut parent, left);
let b = find(&mut parent, right);
if a != b {
parent[b] = a;
}
};
for chain in &cells.segment_chains {
for pair in chain.windows(2) {
unite(pair[0], pair[1]);
}
}
for &(left, right) in extra_equalities {
unite(left, right);
}
let groups: Vec<usize> = (0..n).map(|i| find(&mut parent, i)).collect();
let mut total = vec![0.0; n];
let mut count = vec![0usize; n];
for (i, &g) in groups.iter().enumerate() {
total[g] += xs[i];
count[g] += 1;
}
let mut value = vec![0.0; n];
for i in 0..n {
if count[i] != 0 {
value[i] = total[i] / count[i] as f64;
}
}
for _ in 0..n {
let mut changed = false;
for layer in &cells.layers {
for pair in layer.windows(2) {
let (left, right) = (pair[0], pair[1]);
let (a, b) = (groups[left], groups[right]);
if a == b {
continue;
}
let sep = (cells.width[left] + cells.width[right]) / 2.0 + node_gap;
let required = value[a] + sep;
if value[b] < required {
value[b] = required;
changed = true;
}
}
}
if !changed {
break;
}
}
let feasible = cells.layers.iter().all(|layer| {
layer.windows(2).all(|pair| {
let sep = (cells.width[pair[0]] + cells.width[pair[1]]) / 2.0 + node_gap;
value[groups[pair[1]]] + 1e-9 >= value[groups[pair[0]]] + sep
})
});
if !feasible {
return false;
}
for (i, &g) in groups.iter().enumerate() {
xs[i] = value[g];
}
true
}
fn gadget_endpoint_equalities(graph: &LayoutGraph, cells: &Cells) -> Vec<(usize, usize)> {
let vertex_cells: HashMap<usize, usize> = cells
.entity
.iter()
.enumerate()
.filter_map(|(cell, entity)| match entity {
Entity::Vertex(id) => Some((*id, cell)),
Entity::Segment => None,
})
.collect();
let is_dummy = |id: usize| graph.get_node(id).unwrap().is_dummy;
let mut ids: Vec<_> = graph
.nodes()
.filter(|node| node.is_dummy)
.map(|node| node.id())
.collect();
ids.sort_unstable();
let mut result = Vec::new();
for id in ids {
let node = graph.get_node(id).unwrap();
let child = node
.children()
.find(|edge| {
graph.get_edge(edge.edge_id()).unwrap().reversed && is_dummy(edge.node_id())
})
.map(|edge| edge.node_id());
let has_real_child = node.children().any(|edge| {
graph.get_edge(edge.edge_id()).unwrap().reversed && !is_dummy(edge.node_id())
});
let parent = node
.parents()
.find(|edge| {
graph.get_edge(edge.edge_id()).unwrap().reversed && is_dummy(edge.node_id())
})
.map(|edge| edge.node_id());
let has_real_parent = node.parents().any(|edge| {
graph.get_edge(edge.edge_id()).unwrap().reversed && !is_dummy(edge.node_id())
});
let column = if has_real_child {
child
} else if has_real_parent {
parent
} else {
None
};
if let Some(column) = column
&& let (Some(&endpoint), Some(&column)) =
(vertex_cells.get(&id), vertex_cells.get(&column))
{
result.push((endpoint, column));
}
}
result
}
fn directional_pass(
cells: &Cells,
marked: &HashSet<(usize, usize)>,
node_gap: f64,
down: bool,
left: bool,
) -> Vec<f64> {
if left {
core(cells, &cells.pos, marked, node_gap, down, false)
} else {
let n = cells.rank.len();
let mut pos = vec![0usize; n];
for layer in &cells.layers {
let len = layer.len();
for (i, &c) in layer.iter().enumerate() {
pos[c] = len - 1 - i;
}
}
let mut xs = core(cells, &pos, marked, node_gap, down, true);
for x in &mut xs {
*x = -*x;
}
xs
}
}
fn core(
cells: &Cells,
pos: &[usize],
marked: &HashSet<(usize, usize)>,
node_gap: f64,
down: bool,
mirror: bool,
) -> Vec<f64> {
let n = cells.rank.len();
let mut root: Vec<usize> = (0..n).collect();
let mut align: Vec<usize> = (0..n).collect();
let mut drift = vec![0.0f64; n];
let mut layers: Vec<Vec<usize>> = vec![Vec::new(); cells.layers.len()];
for c in 0..n {
layers[cells.rank[c]].push(c);
}
for layer in &mut layers {
layer.sort_by_key(|&c| pos[c]);
}
vertical_align(
cells, &layers, pos, marked, down, mirror, &mut root, &mut align, &mut drift,
);
horizontal_compact(cells, &layers, pos, node_gap, &root, &align, &drift)
}
#[allow(clippy::too_many_arguments)]
fn vertical_align(
cells: &Cells,
layers: &[Vec<usize>],
pos: &[usize],
marked: &HashSet<(usize, usize)>,
down: bool,
mirror: bool,
root: &mut [usize],
align: &mut [usize],
drift: &mut [f64],
) {
let rank_order: Vec<usize> = if down {
(0..layers.len()).collect()
} else {
(0..layers.len()).rev().collect()
};
let sign = if mirror { -1.0 } else { 1.0 };
for r in rank_order {
let mut prev_bound: i64 = -1;
for &v in &layers[r] {
let (cells_nb, offs) = if down {
(&cells.parents[v], &cells.parent_off[v])
} else {
(&cells.children[v], &cells.child_off[v])
};
if cells_nb.is_empty() {
continue;
}
let mut neighbors: Vec<(usize, f64, f64)> = cells_nb
.iter()
.zip(offs)
.map(|(&c, &(s, e))| (c, s, e))
.collect();
neighbors.sort_by_key(|&(c, _, _)| pos[c]);
let d = neighbors.len();
for &m in &[(d - 1) / 2, d / 2] {
if align[v] != v {
break;
}
let (um, start, end) = neighbors[m];
let key = if down { (um, v) } else { (v, um) };
if !marked.contains(&key) && prev_bound < pos[um] as i64 {
align[um] = v;
root[v] = root[um];
align[v] = root[v];
prev_bound = pos[um] as i64;
let contrib = if down { start - end } else { end - start };
drift[v] = drift[um] + sign * contrib;
}
}
}
}
}
fn horizontal_compact(
cells: &Cells,
layers: &[Vec<usize>],
pos: &[usize],
node_gap: f64,
root: &[usize],
align: &[usize],
drift: &[f64],
) -> Vec<f64> {
let n = cells.rank.len();
let mut sink: Vec<usize> = (0..n).collect();
let mut shift = vec![f64::INFINITY; n];
let mut x = vec![f64::NAN; n];
for layer in layers {
for &v in layer {
if root[v] == v {
place_block(
cells, layers, pos, node_gap, root, align, drift, &mut sink, &mut shift,
&mut x, v,
);
}
}
}
let mut result = vec![0.0f64; n];
for v in 0..n {
let r = root[v];
let mut xv = x[r] + drift[v];
let s = shift[sink[r]];
if s.is_finite() {
xv += s;
}
result[v] = xv;
}
result
}
#[allow(clippy::too_many_arguments)]
fn place_block(
cells: &Cells,
layers: &[Vec<usize>],
pos: &[usize],
node_gap: f64,
root: &[usize],
align: &[usize],
drift: &[f64],
sink: &mut [usize],
shift: &mut [f64],
x: &mut [f64],
v: usize,
) {
if !x[v].is_nan() {
return;
}
x[v] = 0.0;
let mut w = v;
loop {
let r = cells.rank[w];
let p = pos[w];
if p > 0 {
let pred = layers[r][p - 1];
let u = root[pred];
place_block(
cells, layers, pos, node_gap, root, align, drift, sink, shift, x, u,
);
if sink[v] == v {
sink[v] = sink[u];
}
let sep = (cells.width[pred] + cells.width[w]) / 2.0 + node_gap;
if sink[v] != sink[u] {
let candidate = x[v] - x[u] - sep + drift[w] - drift[pred];
shift[sink[u]] = shift[sink[u]].min(candidate);
} else {
let candidate = x[u] + sep + drift[pred] - drift[w];
if candidate > x[v] {
x[v] = candidate;
}
}
}
w = align[w];
if w == v {
break;
}
}
}
fn mark_type1(cells: &Cells) -> HashSet<(usize, usize)> {
let mut marked = HashSet::default();
for r in 0..cells.layers.len().saturating_sub(1) {
let upper = &cells.layers[r];
let lower = &cells.layers[r + 1];
let mut k0: i64 = 0;
let mut l = 0usize;
for (l1, &v) in lower.iter().enumerate() {
let inner_upper = inner_upper(cells, v, r);
let is_last = l1 == lower.len() - 1;
if is_last || inner_upper.is_some() {
let k1 = inner_upper.unwrap_or_else(|| upper.len().saturating_sub(1) as i64);
while l <= l1 {
let w = lower[l];
for &p in &cells.parents[w] {
let k = cells.pos[p] as i64;
if k < k0 || k > k1 {
marked.insert((p, w));
}
}
l += 1;
}
k0 = k1;
}
}
}
marked
}
fn inner_upper(cells: &Cells, v: usize, r: usize) -> Option<i64> {
if !cells.is_dummy[v] {
return None;
}
for &p in &cells.parents[v] {
if cells.is_dummy[p] && cells.rank[p] == r {
return Some(cells.pos[p] as i64);
}
}
None
}
#[cfg(test)]
pub(crate) fn mandatory_constraints_feasible(
graph: &LayoutGraph,
seg: &SegmentInfo,
ordering: &Ordering,
) -> bool {
let cells = build_cells(graph, seg, ordering);
let mut xs = vec![0.0; cells.rank.len()];
enforce_coordinate_constraints(&cells, &[], 1.0, &mut xs)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fixed_ordering_output_admits_mandatory_segment_constraints() {
let mut graph = LayoutGraph::default();
let nodes: Vec<_> = (0..6)
.map(|i| {
graph.make_node(crate::triskel::layout::NodeLayoutData {
width: 20.0 + (i * 37) as f64,
height: 20.0,
rank: i as i64,
..Default::default()
})
})
.collect();
for pair in nodes.windows(2) {
graph.make_edge(
pair[0],
pair[1],
crate::triskel::layout::EdgeLayoutData::default(),
);
}
graph.make_edge(
nodes[0],
nodes[5],
crate::triskel::layout::EdgeLayoutData::default(),
);
graph.make_edge(
nodes[1],
nodes[4],
crate::triskel::layout::EdgeLayoutData::default(),
);
let seg = crate::triskel::segment::build_segments(&mut graph);
let ordering = crate::triskel::order::order(&mut graph, &seg, nodes[0], 4);
assert!(mandatory_constraints_feasible(&graph, &seg, &ordering));
}
#[test]
fn segment_constraint_keeps_complete_chain_together_and_clear_of_wide_slot() {
let cells = Cells {
rank: vec![0, 1, 2, 1],
pos: vec![0, 1, 0, 0],
width: vec![0.0, 0.0, 0.0, 120.0],
is_dummy: vec![true; 4],
entity: vec![
Entity::Segment,
Entity::Segment,
Entity::Segment,
Entity::Vertex(0),
],
layers: vec![vec![0], vec![3, 1], vec![2]],
parents: vec![Vec::new(); 4],
children: vec![Vec::new(); 4],
parent_off: vec![Vec::new(); 4],
child_off: vec![Vec::new(); 4],
segment_chains: vec![vec![0, 1, 2]],
};
let mut xs = vec![-85.0, 85.0, 85.0, 0.0];
assert!(enforce_coordinate_constraints(&cells, &[], 10.0, &mut xs));
assert!(xs.iter().all(|x| x.is_finite()));
assert_eq!(xs[0], xs[1], "p and lane must share one x");
assert_eq!(xs[1], xs[2], "lane and q must share one x");
assert!(
xs[1] - xs[3] >= 70.0,
"segment lane must remain right of the wide rank-1 vertex: {xs:?}"
);
}
}