1use std::collections::{BTreeMap, BTreeSet};
2
3use blockworx_geom::Pos2;
4use pathfinding::directed::dijkstra::dijkstra;
5use petgraph::{
6 graph::{NodeIndex, UnGraph},
7 visit::EdgeRef,
8};
9
10use crate::router::{
11 block::{Block, ROUTE_GUTTER},
12 channel::{Channel, ChannelOrientation, h_channel, v_channel},
13 coord::{CoordX, CoordY, INFINITY_X, INFINITY_Y, NEG_INFINITY_X, NEG_INFINITY_Y},
14 cost::{COST_ZERO, Cost},
15 event::{Event, EventSense},
16 point::{Point, point},
17 segment::{HSegment, Segment, VSegment, hseg, vseg},
18 turtle::{Mark, Turtle},
19};
20pub mod block;
21pub mod channel;
22pub mod coord;
23pub mod cost;
24pub mod event;
25pub mod point;
26pub mod segment;
27pub mod turtle;
28
29#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
30pub enum Direction {
31 North,
32 South,
33 East,
34 West,
35}
36
37impl Direction {
38 fn opposite(self) -> Self {
39 match self {
40 Direction::North => Direction::South,
41 Direction::South => Direction::North,
42 Direction::East => Direction::West,
43 Direction::West => Direction::East,
44 }
45 }
46}
47
48pub fn direction_between(from: Point, to: Point) -> Option<Direction> {
52 if to.x > from.x {
53 Some(Direction::East)
54 } else if to.x < from.x {
55 Some(Direction::West)
56 } else if to.y > from.y {
57 Some(Direction::South)
58 } else if to.y < from.y {
59 Some(Direction::North)
60 } else {
61 None
62 }
63}
64
65const TURN_COST: Cost = Cost::new(25.0);
66const MOVE_COST: Cost = Cost::new(1.0);
67pub const WIRE_COST: Cost = Cost::new(10.0);
68
69fn cross_cost(
70 from: Option<Direction>,
71 to: Direction,
72 cost_to_cross_east_west: Cost,
73 cost_to_cross_north_south: Cost,
74) -> Cost {
75 if let Some(from_dir) = from {
76 match (from_dir, to) {
77 (Direction::North, Direction::South) | (Direction::South, Direction::North) => {
78 cost_to_cross_east_west
79 }
80 (Direction::East, Direction::West) | (Direction::West, Direction::East) => {
81 cost_to_cross_north_south
82 }
83 _ => COST_ZERO,
84 }
85 } else {
86 COST_ZERO
87 }
88}
89
90fn turn_cost(from: Option<Direction>, to: Direction) -> Cost {
91 if let Some(from_dir) = from {
92 if from_dir == to {
93 COST_ZERO
94 } else if to == from_dir.opposite() {
95 TURN_COST * 100.0
96 } else {
97 TURN_COST
98 }
99 } else {
100 COST_ZERO
101 }
102}
103
104#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Hash)]
105struct SearchState {
106 node: NodeIndex,
107 dir: Option<Direction>,
108}
109
110#[derive(Debug, Clone, Default)]
111pub struct RouterNGBuilder {
112 blocks: Vec<Block>,
114 channels: Vec<Channel>,
116 seed_points: Vec<Point>,
120}
121
122impl RouterNGBuilder {
123 pub fn add_h_channel(&mut self, seed: impl Into<Point>, cost: impl Into<Cost>) {
124 self.channels.push(h_channel(seed, cost));
125 }
126 pub fn add_seed_point(&mut self, p: impl Into<Point>) {
130 self.seed_points.push(p.into());
131 }
132 pub fn add_v_channel(&mut self, seed: impl Into<Point>, cost: impl Into<Cost>) {
133 self.channels.push(v_channel(seed, cost));
134 }
135 fn add_routing_moat(
136 &mut self,
137 top_left: Point,
138 bottom_right: Point,
139 distance: i32,
140 cost: Cost,
141 ) {
142 let min_x = top_left.x.min(bottom_right.x);
143 let max_x = top_left.x.max(bottom_right.x);
144 let min_y = top_left.y.min(bottom_right.y);
145 let max_y = top_left.y.max(bottom_right.y);
146 self.add_v_channel(point(min_x - distance - 2, min_y), cost);
147 self.add_v_channel(point(min_x - distance - 2, max_y), cost);
148 self.add_v_channel(point(max_x + distance + 2, min_y), cost);
149 self.add_v_channel(point(max_x + distance + 2, max_y), cost);
150 self.add_h_channel(point(min_x, min_y - distance - 2), cost);
151 self.add_h_channel(point(max_x, min_y - distance - 2), cost);
152 self.add_h_channel(point(min_x, max_y + distance + 2), cost);
153 self.add_h_channel(point(max_x, max_y + distance + 2), cost);
154 }
155 pub fn add_block(&mut self, top_left: impl Into<Point>, bottom_right: impl Into<Point>) {
156 let top_left: Point = top_left.into();
157 let bottom_right: Point = bottom_right.into();
158 let min_x = top_left.x.min(bottom_right.x);
159 let max_x = top_left.x.max(bottom_right.x);
160 let min_y = top_left.y.min(bottom_right.y);
161 let max_y = top_left.y.max(bottom_right.y);
162 let block = Block {
163 top_left: point(min_x, min_y),
164 bottom_right: point(max_x, max_y),
165 };
166 self.blocks.push(block);
167 for moat_lane in 0..5 {
169 let cost = if moat_lane == 0 {
170 Cost::new(0.2)
171 } else {
172 Cost::new(0.1)
173 };
174 self.add_routing_moat(top_left, bottom_right, moat_lane, cost);
175 }
176 }
177 fn seed_channels_into_router(&self) -> RouterNG {
180 let block_index = BlockAxisIndex::build(&self.blocks);
181 let mut router = RouterNG {
182 blocks: self.blocks.clone(),
183 block_index,
184 h_segments: BTreeMap::new(),
185 v_segments: BTreeMap::new(),
186 nodes: BTreeSet::new(),
187 graph: UnGraph::default(),
188 node_to_index: BTreeMap::new(),
189 dirty: true,
190 };
191 for channel in &self.channels {
192 match channel.orientation {
193 ChannelOrientation::Horizontal => {
194 router.seed_horiz_channel(channel.seed, channel.cost);
195 }
196 ChannelOrientation::Vertical => {
197 router.seed_vert_channel(channel.seed, channel.cost);
198 }
199 }
200 }
201 router
202 }
203 #[cfg(test)]
206 pub fn build(self) -> RouterNG {
207 let mut router = self.seed_channels_into_router();
208 router.update();
209 router
210 }
211 pub fn build_closed(self) -> ClosedRouter {
215 let mut router = self.seed_channels_into_router();
216 for &p in &self.seed_points {
217 router.seed_channels(p, COST_ZERO);
218 }
219 router.update();
220 ClosedRouter { inner: router }
221 }
222}
223
224#[derive(Debug, Clone)]
230pub struct ClosedRouter {
231 inner: RouterNG,
232}
233
234impl ClosedRouter {
235 pub fn route_leg(
240 &mut self,
241 start: Point,
242 end: Point,
243 incoming: Option<Direction>,
244 ) -> (Vec<Point>, Option<Direction>) {
245 self.inner.path_find_with_fallback(start, end, incoming)
246 }
247
248 pub fn bump_leg(&mut self, path: &[Point], cost: Cost) {
251 for w in path.windows(2) {
252 self.add_wire_cost(w[0], w[1], cost);
253 }
254 }
255
256 pub fn add_wire_cost(&mut self, a: Point, b: Point, cost: Cost) {
262 let Some(dir) = direction_between(a, b) else {
263 return;
264 };
265 let Some(&target) = self.inner.node_to_index.get(&b) else {
266 return;
267 };
268 let Some(&start) = self.inner.node_to_index.get(&a) else {
269 return;
270 };
271 let mut cur = start;
272 let mut cur_pt = a;
273 for _ in 0..self.inner.node_to_index.len() {
276 if cur == target {
277 break;
278 }
279 let step = self.inner.graph.edges(cur).find_map(|e| {
280 let np = self.inner.point(e.target());
281 (direction_between(cur_pt, np) == Some(dir)).then_some((e.id(), e.target(), np))
282 });
283 let Some((edge_id, next, next_pt)) = step else {
284 break;
285 };
286 if let Some(w) = self.inner.graph.edge_weight_mut(edge_id) {
287 *w += cost;
288 }
289 cur = next;
290 cur_pt = next_pt;
291 }
292 }
293
294 pub fn is_wire_blocked(&self, a: Point, b: Point) -> bool {
296 self.inner
297 .blocks
298 .iter()
299 .any(|block| block.intersects_edge(a, b))
300 }
301
302 pub fn wire_hugs_block(&self, a: Point, b: Point) -> bool {
306 self.inner
307 .blocks
308 .iter()
309 .any(|blk| blk.hugs_wire(a, b, ROUTE_GUTTER))
310 }
311
312 pub fn is_accessible(&self, test: impl Into<Point>) -> bool {
313 self.inner.is_accessible(test)
314 }
315
316 pub fn debug_marks(&self) -> Vec<Mark> {
317 self.inner.debug_marks()
318 }
319}
320
321#[derive(Debug, Clone, Default)]
329struct BlockAxisIndex {
330 by_y: BTreeMap<CoordY, Vec<usize>>,
331 by_x: BTreeMap<CoordX, Vec<usize>>,
332}
333
334impl BlockAxisIndex {
335 fn build(blocks: &[Block]) -> Self {
336 let mut by_y: BTreeMap<CoordY, Vec<usize>> = BTreeMap::new();
337 let mut by_x: BTreeMap<CoordX, Vec<usize>> = BTreeMap::new();
338 for (i, block) in blocks.iter().enumerate() {
339 let ey = block.expand_y(1);
340 for y in ey.top_left.y.raw()..=ey.bottom_right.y.raw() {
341 by_y.entry(CoordY::from(y)).or_default().push(i);
342 }
343 let ex = block.expand_x(1);
344 for x in ex.top_left.x.raw()..=ex.bottom_right.x.raw() {
345 by_x.entry(CoordX::from(x)).or_default().push(i);
346 }
347 }
348 Self { by_y, by_x }
349 }
350
351 fn spanning_y<'a>(&'a self, y: CoordY, blocks: &'a [Block]) -> impl Iterator<Item = &'a Block> {
353 self.by_y.get(&y).into_iter().flatten().map(|&i| &blocks[i])
354 }
355
356 fn spanning_x<'a>(&'a self, x: CoordX, blocks: &'a [Block]) -> impl Iterator<Item = &'a Block> {
358 self.by_x.get(&x).into_iter().flatten().map(|&i| &blocks[i])
359 }
360}
361
362#[derive(Debug, Clone)]
363pub struct RouterNG {
364 blocks: Vec<Block>,
366 block_index: BlockAxisIndex,
368 h_segments: BTreeMap<CoordY, Vec<HSegment>>,
370 v_segments: BTreeMap<CoordX, Vec<VSegment>>,
372 nodes: BTreeSet<Point>,
374 graph: UnGraph<Point, Cost>,
376 node_to_index: BTreeMap<Point, petgraph::graph::NodeIndex>,
378 dirty: bool,
380}
381
382impl RouterNG {
383 pub fn debug_marks(&self) -> Vec<Mark> {
384 assert!(
385 !self.dirty,
386 "Cannot generate debug marks when the graph is dirty"
387 );
388 let mut turtle = Turtle::default();
389 for node in self.graph.node_indices() {
390 let pos = self.point(node);
391 turtle.move_to(pos.into());
392 turtle.circle(blockworx_geom::WorldPx::new(3.0));
393 for edge in self.graph.edges(node) {
396 let target = edge.target();
397 let edge_weight = edge.weight();
398 let target_pos = self.point(target);
399
400 let start_pos: Pos2 = pos.into();
402 let end_pos: Pos2 = target_pos.into();
403 let dx = end_pos.x - start_pos.x;
404 let dy = end_pos.y - start_pos.y;
405 let distance = (dx * dx + dy * dy).sqrt();
406
407 if distance < 8.0 {
409 continue;
410 }
411
412 let gap = 4.0;
414 let gap_ratio = gap / distance;
415
416 let line_start =
418 Pos2::new(start_pos.x + dx * gap_ratio, start_pos.y + dy * gap_ratio);
419
420 let line_end = Pos2::new(end_pos.x - dx * gap_ratio, end_pos.y - dy * gap_ratio);
422
423 turtle.move_to(line_start);
424 turtle.line_to(line_end);
425 let mid_point = line_start + (line_end - line_start) / 2.0;
426 let weight: f64 = (*edge_weight).into();
427 turtle.label(mid_point, weight as f32);
428 }
429 }
430 turtle.compile()
431 }
432 pub fn is_accessible(&self, test: impl Into<Point>) -> bool {
433 let test: Point = test.into();
434 !self.blocks.iter().any(|block| block.contains(test))
435 }
436 fn seed_horiz_channel(&mut self, center: impl Into<Point>, cost: impl Into<Cost>) {
437 let center: Point = center.into();
438 let cost: Cost = cost.into();
439 let mut left_endpoint = NEG_INFINITY_X;
440 let mut right_endpoint = INFINITY_X;
441 for block in self.block_index.spanning_y(center.y, &self.blocks) {
444 let block = block.expand_x(1).expand_y(1);
445 if block.spans_x(center.x) {
446 return;
448 }
449 if block.is_left_of(center.x) {
450 left_endpoint = left_endpoint.max(block.bottom_right.x);
451 }
452 if block.is_right_of(center.x) {
453 right_endpoint = right_endpoint.min(block.top_left.x);
454 }
455 }
456 if left_endpoint < right_endpoint {
457 self.add_horiz_segment(center.y, left_endpoint, right_endpoint, cost);
458 }
459 }
460 fn seed_vert_channel(&mut self, center: impl Into<Point>, cost: impl Into<Cost>) {
461 let center: Point = center.into();
462 let cost: Cost = cost.into();
463 let mut top_endpoint = NEG_INFINITY_Y;
464 let mut bottom_endpoint = INFINITY_Y;
465 for block in self.block_index.spanning_x(center.x, &self.blocks) {
468 if block.spans_y(center.y) {
469 return;
471 }
472 if block.is_above(center.y) {
473 top_endpoint = top_endpoint.max(block.bottom_right.y);
474 }
475 if block.is_below(center.y) {
476 bottom_endpoint = bottom_endpoint.min(block.top_left.y);
477 }
478 }
479 if top_endpoint < bottom_endpoint {
480 self.add_vert_segment(center.x, top_endpoint, bottom_endpoint, cost);
481 }
482 }
483 pub fn seed_channels(&mut self, center: impl Into<Point>, cost: impl Into<Cost>) {
484 let center: Point = center.into();
485 let cost: Cost = cost.into();
486 self.seed_horiz_channel(center, cost);
487 self.seed_vert_channel(center, cost);
488 }
489 pub fn add_horiz_segment(
490 &mut self,
491 vert: impl Into<CoordY>,
492 left: impl Into<CoordX>,
493 right: impl Into<CoordX>,
494 cost: impl Into<Cost>,
495 ) {
496 let vert: CoordY = vert.into();
497 let left: CoordX = left.into();
498 let right: CoordX = right.into();
499 let cost: Cost = cost.into();
500 if right > left {
501 self.h_segments
502 .entry(vert)
503 .or_default()
504 .push(hseg(left, right, cost));
505 self.dirty = true;
506 }
507 }
508 pub fn add_vert_segment(
509 &mut self,
510 horiz: impl Into<CoordX>,
511 top: impl Into<CoordY>,
512 bottom: impl Into<CoordY>,
513 cost: impl Into<Cost>,
514 ) {
515 let horiz: CoordX = horiz.into();
516 let top: CoordY = top.into();
517 let bottom: CoordY = bottom.into();
518 let cost: Cost = cost.into();
519 if bottom > top {
520 self.v_segments
521 .entry(horiz)
522 .or_default()
523 .push(vseg(top, bottom, cost));
524 self.dirty = true;
525 }
526 }
527 pub fn update(&mut self) {
528 if !self.dirty {
529 return;
530 }
531 let _span = tracing::info_span!("router_rebuild").entered();
534 let h_segments = std::mem::take(&mut self.h_segments);
535 for (vert, segments) in h_segments {
536 normalize_collinear_segments(segments, |left, right, cost| {
537 self.add_horiz_segment(vert, left, right, cost);
538 });
539 }
540 let v_segments = std::mem::take(&mut self.v_segments);
541 for (horiz, segments) in v_segments {
542 normalize_collinear_segments(segments, |top, bottom, cost| {
543 self.add_vert_segment(horiz, top, bottom, cost);
544 });
545 }
546 self.nodes = collect_intersections(self.iter_hsegs(), self.iter_vsegs());
547 let mut h_segments = std::mem::take(&mut self.h_segments);
549 self.nodes.iter().for_each(|&node| {
550 h_segments
551 .entry(node.y)
552 .or_default()
553 .push(hseg(node.x, node.x, COST_ZERO));
554 });
555 for (vert, segments) in h_segments {
556 normalize_collinear_segments(segments, |left, right, cost| {
557 self.add_horiz_segment(vert, left, right, cost);
558 });
559 }
560 let mut v_segments = std::mem::take(&mut self.v_segments);
561 self.nodes.iter().for_each(|&node| {
562 v_segments
563 .entry(node.x)
564 .or_default()
565 .push(vseg(node.y, node.y, COST_ZERO));
566 });
567 for (horiz, segments) in v_segments {
568 normalize_collinear_segments(segments, |top, bottom, cost| {
569 self.add_vert_segment(horiz, top, bottom, cost);
570 });
571 }
572 let nodes = self
578 .iter_hsegs()
579 .flat_map(|(y, h_seg)| [point(h_seg.start, y), point(h_seg.end, y)])
580 .chain(
581 self.iter_vsegs()
582 .flat_map(|(x, v_seg)| [point(x, v_seg.start), point(x, v_seg.end)]),
583 )
584 .collect();
585 self.nodes = nodes;
586 self.rebuild_graph();
587 self.dirty = false;
588 }
589 fn iter_hsegs(&self) -> impl Iterator<Item = (CoordY, HSegment)> + '_ {
590 self.h_segments
591 .iter()
592 .flat_map(|(&y, h_segs)| h_segs.iter().map(move |h_seg| (y, *h_seg)))
593 }
594 fn iter_vsegs(&self) -> impl Iterator<Item = (CoordX, VSegment)> + '_ {
595 self.v_segments
596 .iter()
597 .flat_map(|(&x, v_segs)| v_segs.iter().map(move |v_seg| (x, *v_seg)))
598 }
599 fn rebuild_graph(&mut self) {
600 let mut node_to_index: BTreeMap<Point, petgraph::graph::NodeIndex> = BTreeMap::new();
601 let mut graph = UnGraph::default();
602 for &node in &self.nodes {
603 let index = graph.add_node(node);
604 node_to_index.insert(node, index);
605 }
606 for hseg in self.iter_hsegs() {
607 let y = hseg.0;
608 let h_seg = hseg.1;
609 let start_node = point(h_seg.start, y);
610 let end_node = point(h_seg.end, y);
611 graph.add_edge(
612 node_to_index[&start_node],
613 node_to_index[&end_node],
614 h_seg.cost,
615 );
616 }
617 for vseg in self.iter_vsegs() {
618 let x = vseg.0;
619 let v_seg = vseg.1;
620 let start_node = point(x, v_seg.start);
621 let end_node = point(x, v_seg.end);
622 graph.add_edge(
623 node_to_index[&start_node],
624 node_to_index[&end_node],
625 v_seg.cost,
626 );
627 }
628 self.graph = graph;
629 self.node_to_index = node_to_index;
630 }
631 fn point(&self, node: NodeIndex) -> Point {
634 self.graph.node_weight(node).copied().unwrap_or(Point::ZERO)
635 }
636
637 fn successors(&self, state: SearchState) -> Vec<(SearchState, Cost)> {
638 let prev_dir = state.dir;
639 let prev_point = self.point(state.node);
640 let mut north_cost: Option<Cost> = None;
641 let mut south_cost: Option<Cost> = None;
642 let mut east_cost: Option<Cost> = None;
643 let mut west_cost: Option<Cost> = None;
644 for edge in self.graph.edges(state.node) {
646 let neighbor = edge.target();
647 let cost = *edge.weight();
648 let neighbor_point = self.point(neighbor);
649 if neighbor_point.x > prev_point.x {
650 east_cost = Some(cost);
651 } else if neighbor_point.x < prev_point.x {
652 west_cost = Some(cost);
653 } else if neighbor_point.y > prev_point.y {
654 south_cost = Some(cost);
655 } else {
656 north_cost = Some(cost);
657 }
658 }
659 let east_west_crossing_cost = match (east_cost, west_cost) {
663 (Some(east), Some(west)) => east.max(west),
664 _ => COST_ZERO,
665 };
666 let north_south_crossing_cost = match (north_cost, south_cost) {
667 (Some(north), Some(south)) => north.max(south),
668 _ => COST_ZERO,
669 };
670 self.graph
672 .edges(state.node)
673 .map(|edge| {
674 let neighbor = edge.target();
675 let cost = *edge.weight();
676 let neighbor_point = self.point(neighbor);
677 let dir = if neighbor_point.x > prev_point.x {
678 Direction::East
679 } else if neighbor_point.x < prev_point.x {
680 Direction::West
681 } else if neighbor_point.y > prev_point.y {
682 Direction::South
683 } else {
684 Direction::North
685 };
686 let step_length = neighbor_point.manhattan_distance(prev_point) as f64;
687 let step_cost = turn_cost(prev_dir, dir)
688 + MOVE_COST * step_length
689 + cost * step_length
690 + cross_cost(
691 prev_dir,
692 dir,
693 east_west_crossing_cost,
694 north_south_crossing_cost,
695 );
696 (
697 SearchState {
698 node: neighbor,
699 dir: Some(dir),
700 },
701 step_cost,
702 )
703 })
704 .collect()
705 }
706 fn path_find(
713 &mut self,
714 start: impl Into<Point>,
715 end: impl Into<Point>,
716 incoming: Option<Direction>,
717 ) -> Option<(Vec<Point>, Option<Direction>)> {
718 self.update();
719 let start: Point = start.into();
720 let end: Point = end.into();
721 let &start_node = self.node_to_index.get(&start)?;
722 let end_node = self.node_to_index.get(&end)?;
723 let start = SearchState {
724 node: start_node,
725 dir: incoming,
726 };
727 let result = dijkstra(
728 &start,
729 |state| self.successors(*state),
730 |state| state.node == *end_node,
731 );
732 result.map(|(path, _cost)| {
733 let outgoing = path.last().and_then(|state| state.dir);
734 let points = path
735 .into_iter()
736 .map(|state| self.point(state.node))
737 .collect();
738 (points, outgoing)
739 })
740 }
741 pub fn path_find_with_fallback(
742 &mut self,
743 start: impl Into<Point>,
744 end: impl Into<Point>,
745 incoming: Option<Direction>,
746 ) -> (Vec<Point>, Option<Direction>) {
747 let start: Point = start.into();
748 let end: Point = end.into();
749 if let Some(result) = self.path_find(start, end, incoming) {
750 return result;
751 }
752 let path = vec![start, point(end.x, start.y), end];
754 let outgoing = path
755 .windows(2)
756 .rev()
757 .find_map(|w| direction_between(w[0], w[1]))
758 .or(incoming);
759 (path, outgoing)
760 }
761}
762
763#[cfg(test)]
774pub(crate) mod ci_stats {
775 use std::sync::atomic::{AtomicU64, Ordering};
776 pub static CALLS: AtomicU64 = AtomicU64::new(0);
777 pub static EVENTS: AtomicU64 = AtomicU64::new(0);
778 pub static SCANS: AtomicU64 = AtomicU64::new(0);
779 pub static INNER_ITERS: AtomicU64 = AtomicU64::new(0);
780 pub static INTERSECTIONS: AtomicU64 = AtomicU64::new(0);
781 pub fn reset() {
782 for c in [&CALLS, &EVENTS, &SCANS, &INNER_ITERS, &INTERSECTIONS] {
783 c.store(0, Ordering::Relaxed);
784 }
785 }
786 pub fn dump(label: &str) {
787 let g = |c: &AtomicU64| c.load(Ordering::Relaxed);
788 eprintln!(
789 "[{label}] collect_intersections calls={} events={} scans(V)={} \
790 inner_iters={} intersections={} (waste = inner_iters/intersections = {:.1}x)",
791 g(&CALLS),
792 g(&EVENTS),
793 g(&SCANS),
794 g(&INNER_ITERS),
795 g(&INTERSECTIONS),
796 g(&INNER_ITERS) as f64 / (g(&INTERSECTIONS).max(1)) as f64,
797 );
798 }
799}
800
801fn collect_intersections(
802 h_segments: impl IntoIterator<Item = (CoordY, HSegment)>,
803 v_segments: impl IntoIterator<Item = (CoordX, VSegment)>,
804) -> BTreeSet<Point> {
805 let mut events: Vec<Event<CoordX, (CoordY, CoordY)>> = h_segments
806 .into_iter()
807 .flat_map(|(y, h_seg)| {
808 [
809 Event::enter(h_seg.start, (y, y)),
810 Event::exit(h_seg.end, (y, y)),
811 ]
812 })
813 .chain(
814 v_segments
815 .into_iter()
816 .map(|(x, v_seg)| Event::scan(x, (v_seg.start, v_seg.end))),
817 )
818 .collect::<Vec<_>>();
819 events.sort();
820 #[cfg(test)]
821 {
822 use std::sync::atomic::Ordering::Relaxed;
823 ci_stats::CALLS.fetch_add(1, Relaxed);
824 ci_stats::EVENTS.fetch_add(events.len() as u64, Relaxed);
825 }
826 let mut intersections = BTreeSet::new();
827 let mut active_h_segments: BTreeMap<CoordY, usize> = BTreeMap::new();
830 for event in events {
831 match event.sense() {
832 EventSense::Enter => {
833 let y = event.cost().0;
834 *active_h_segments.entry(y).or_insert(0) += 1;
835 }
836 EventSense::Exit => {
837 let y = event.cost().0;
838 if let Some(count) = active_h_segments.get_mut(&y) {
839 *count = count.saturating_sub(1);
840 if *count == 0 {
841 active_h_segments.remove(&y);
842 }
843 }
844 }
845 EventSense::Scan => {
846 let (start, end) = event.cost();
847 let in_span = active_h_segments.range(start..=end);
852 #[cfg(test)]
853 {
854 use std::sync::atomic::Ordering::Relaxed;
855 ci_stats::SCANS.fetch_add(1, Relaxed);
856 ci_stats::INNER_ITERS.fetch_add(in_span.clone().count() as u64, Relaxed);
857 }
858 for (&y, _) in in_span {
859 intersections.insert(point(event.t(), y));
860 #[cfg(test)]
861 ci_stats::INTERSECTIONS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
862 }
863 }
864 }
865 }
866 intersections
867}
868
869fn normalize_collinear_segments<T: Ord + Copy>(
870 segments: impl IntoIterator<Item = Segment<T>>,
871 mut maker: impl FnMut(T, T, Cost),
872) {
873 let mut events = segments
874 .into_iter()
875 .flat_map(|seg| {
876 [
877 Event::enter(seg.start, seg.cost),
878 Event::exit(seg.end, seg.cost),
879 ]
880 })
881 .collect::<Vec<_>>();
882 events.sort();
884 scan_disjoint_segments(events, |start, end, cost| {
885 maker(start, end, cost);
886 });
887}
888
889fn scan_disjoint_segments<T: Ord + Copy>(
890 events: impl IntoIterator<Item = Event<T, Cost>>,
891 mut maker: impl FnMut(T, T, Cost),
892) {
893 let mut events_iter = events.into_iter();
894
895 let Some(first_event) = events_iter.next() else {
897 return;
898 };
899
900 let mut last_t = first_event.t();
901 let mut line_count = first_event.count();
902 let mut current_cost = if first_event.is_enter() {
903 first_event.cost()
904 } else {
905 COST_ZERO - first_event.cost()
906 };
907
908 for event in events_iter {
910 let t = event.t();
911 if line_count != 0 {
913 maker(last_t, t, current_cost);
914 }
915 last_t = t;
916 line_count += event.count();
917 current_cost = if event.is_enter() {
918 current_cost + event.cost()
919 } else {
920 current_cost - event.cost()
921 };
922 }
923}
924
925fn interval_overlap<T: Ord + Copy>(a_start: T, a_end: T, b_start: T, b_end: T) -> bool {
926 a_start < b_end && b_start < a_end
927}
928
929#[cfg(test)]
930mod tests {
931
932 use super::*;
933
934 #[test]
935 fn block_axis_index_selects_the_same_blocks_as_a_scan() {
936 use crate::router::block::Block;
937 use crate::router::point::point;
938 use std::collections::BTreeSet;
939 let blocks = vec![
941 Block {
942 top_left: point(0, 0),
943 bottom_right: point(8, 8),
944 },
945 Block {
946 top_left: point(20, 4),
947 bottom_right: point(30, 12),
948 },
949 Block {
950 top_left: point(-10, -3),
951 bottom_right: point(-2, 40),
952 },
953 ];
954 let index = BlockAxisIndex::build(&blocks);
955 for c in -14..=44 {
956 let y = CoordY::from(c);
957 let from_index: BTreeSet<i32> = index
958 .spanning_y(y, &blocks)
959 .map(|b| b.top_left.x.raw())
960 .collect();
961 let brute: BTreeSet<i32> = blocks
962 .iter()
963 .filter(|b| b.expand_y(1).spans_y(y))
964 .map(|b| b.top_left.x.raw())
965 .collect();
966 assert_eq!(from_index, brute, "spanning_y mismatch at y={c}");
967
968 let x = CoordX::from(c);
969 let from_index_x: BTreeSet<i32> = index
970 .spanning_x(x, &blocks)
971 .map(|b| b.top_left.y.raw())
972 .collect();
973 let brute_x: BTreeSet<i32> = blocks
974 .iter()
975 .filter(|b| b.expand_x(1).spans_x(x))
976 .map(|b| b.top_left.y.raw())
977 .collect();
978 assert_eq!(from_index_x, brute_x, "spanning_x mismatch at x={c}");
979 }
980 }
981
982 macro_rules! hseg {
983 (y=$y:expr, [$(($start:expr => $end:expr, $cost:expr)),* $(,)?]) => {
984 BTreeMap::from([(
985 CoordY::from($y),
986 vec![
987 $(HSegment {
988 start: CoordX::from($start),
989 end: CoordX::from($end),
990 cost: $cost.into(),
991 }),*
992 ]
993 )])
994 };
995 }
996
997 macro_rules! vseg {
998 (x=$x:expr, [$(($start:expr => $end:expr, $cost:expr)),* $(,)?]) => {
999 BTreeMap::from([(
1000 CoordX::from($x),
1001 vec![
1002 $(VSegment {
1003 start: CoordY::from($start),
1004 end: CoordY::from($end),
1005 cost: $cost.into(),
1006 }),*
1007 ]
1008 )])
1009 };
1010 }
1011
1012 fn collect_intersections_brute_force(
1014 h_segments: impl IntoIterator<Item = (CoordY, HSegment)>,
1015 v_segments: impl IntoIterator<Item = (CoordX, VSegment)>,
1016 ) -> Vec<Point> {
1017 let mut points = vec![];
1018 let v_segments = v_segments.into_iter().collect::<Vec<_>>();
1019 for (y, hseg) in h_segments {
1020 for (x, vseg) in &v_segments {
1021 if hseg.start <= *x && hseg.end >= *x && vseg.start <= y && vseg.end >= y {
1022 points.push(point(*x, y));
1023 }
1024 }
1025 }
1026 points
1027 }
1028
1029 #[test]
1030 fn test_vseed() {
1031 let mut router = RouterNGBuilder::default().build();
1032 router.seed_vert_channel(point(0, 0), 1.0);
1033 router.update();
1034 assert_eq!(
1035 router.v_segments,
1036 BTreeMap::from([(CoordX::from(0), vec![vseg(NEG_INFINITY_Y, INFINITY_Y, 1.0)])])
1037 );
1038 }
1039
1040 #[test]
1041 fn test_normalize() {
1042 let mut router = RouterNGBuilder::default().build();
1043 router.add_horiz_segment(0, 0, 10, 1.0);
1044 router.add_horiz_segment(0, 5, 15, 2.0);
1045 router.update();
1046 assert_eq!(
1047 router.h_segments,
1048 hseg!(y=0, [(0=>5, 1.0), (5=>10, 3.0), (10=>15, 2.0)])
1049 );
1050 }
1051
1052 #[test]
1053 fn test_normalize_complete_overlap() {
1054 let mut router = RouterNGBuilder::default().build();
1056 router.add_horiz_segment(0, 0, 20, 1.0);
1057 router.add_horiz_segment(0, 5, 15, 2.0);
1058 router.update();
1059 assert_eq!(
1060 router.h_segments,
1061 hseg!(y=0, [(0=>5, 1.0), (5=>15, 3.0), (15=>20, 1.0)])
1062 );
1063 }
1064
1065 #[test]
1066 fn test_normalize_no_overlap() {
1067 let mut router = RouterNGBuilder::default().build();
1069 router.add_horiz_segment(0, 0, 10, 1.0);
1070 router.add_horiz_segment(0, 20, 30, 2.0);
1071 router.update();
1072 assert_eq!(router.h_segments, hseg!(y=0, [(0=>10, 1.0), (20=>30, 2.0)]));
1073 }
1074
1075 #[test]
1076 fn test_normalize_adjacent_segments() {
1077 let mut router = RouterNGBuilder::default().build();
1079 router.add_horiz_segment(0, 0, 10, 1.0);
1080 router.add_horiz_segment(0, 10, 20, 2.0);
1081 router.update();
1082 assert_eq!(router.h_segments, hseg!(y=0, [(0=>10, 1.0), (10=>20, 2.0)]));
1083 }
1084
1085 #[test]
1086 fn test_normalize_triple_overlap() {
1087 let mut router = RouterNGBuilder::default().build();
1089 router.add_horiz_segment(0, 0, 15, 1.0);
1090 router.add_horiz_segment(0, 5, 20, 2.0);
1091 router.add_horiz_segment(0, 10, 25, 3.0);
1092 router.update();
1093 assert_eq!(
1094 router.h_segments,
1095 hseg!(y=0, [(0=>5, 1.0), (5=>10, 3.0), (10=>15, 6.0), (15=>20, 5.0), (20=>25, 3.0)])
1096 );
1097 }
1098
1099 #[test]
1100 fn test_normalize_multiple_rows() {
1101 let mut router = RouterNGBuilder::default().build();
1103 router.add_horiz_segment(0, 0, 10, 1.0);
1104 router.add_horiz_segment(0, 5, 15, 2.0);
1105 router.add_horiz_segment(5, 0, 10, 3.0);
1106 router.add_horiz_segment(5, 5, 15, 4.0);
1107 router.update();
1108
1109 let mut expected = BTreeMap::new();
1110 expected.extend(hseg!(y=0, [(0=>5, 1.0), (5=>10, 3.0), (10=>15, 2.0)]));
1111 expected.extend(hseg!(y=5, [(0=>5, 3.0), (5=>10, 7.0), (10=>15, 4.0)]));
1112 assert_eq!(router.h_segments, expected);
1113 }
1114
1115 #[test]
1116 fn test_normalize_negative_coords() {
1117 let mut router = RouterNGBuilder::default().build();
1119 router.add_horiz_segment(-5, -20, -10, 1.0);
1120 router.add_horiz_segment(-5, -15, -5, 2.0);
1121 router.update();
1122 assert_eq!(
1123 router.h_segments,
1124 hseg!(y=-5, [(-20 => -15, 1.0), (-15 => -10, 3.0), (-10 => -5, 2.0)])
1125 );
1126 }
1127
1128 #[test]
1129 fn test_normalize_vertical_segments() {
1130 let mut router = RouterNGBuilder::default().build();
1132 router.add_vert_segment(0, 0, 10, 1.0);
1133 router.add_vert_segment(0, 5, 15, 2.0);
1134 router.update();
1135 assert_eq!(
1136 router.v_segments,
1137 vseg!(x=0, [(0=>5, 1.0), (5=>10, 3.0), (10=>15, 2.0)])
1138 );
1139 }
1140
1141 #[test]
1142 fn test_normalize_identical_segments() {
1143 let mut router = RouterNGBuilder::default().build();
1145 router.add_horiz_segment(0, 0, 10, 1.0);
1146 router.add_horiz_segment(0, 0, 10, 1.0);
1147 router.update();
1148 assert_eq!(router.h_segments, hseg!(y=0, [(0=>10, 2.0)]));
1149 }
1150
1151 #[test]
1152 fn test_normalize_reverse_order() {
1153 let mut router = RouterNGBuilder::default().build();
1155 router.add_horiz_segment(0, 20, 30, 1.0);
1156 router.add_horiz_segment(0, 10, 25, 2.0);
1157 router.add_horiz_segment(0, 0, 15, 3.0);
1158 router.update();
1159 assert_eq!(
1160 router.h_segments,
1161 hseg!(y=0, [(0=>10, 3.0), (10=>15, 5.0), (15=>20, 2.0), (20=>25, 3.0), (25=>30, 1.0)])
1162 );
1163 }
1164
1165 #[test]
1168 fn test_collect_intersections_no_intersections() {
1169 let h_segs = vec![
1171 (CoordY::from(0), hseg(0, 10, 1.0)),
1172 (CoordY::from(5), hseg(15, 25, 1.0)),
1173 ];
1174 let v_segs = vec![(CoordX::from(20), vseg(10, 15, 1.0))];
1176 let intersections = collect_intersections(h_segs, v_segs);
1177 assert_eq!(intersections, BTreeSet::new());
1178 }
1179
1180 #[test]
1181 fn test_collect_intersections_at_boundaries() {
1182 let h_segs = vec![(CoordY::from(5), hseg(0, 10, 1.0))];
1184 let v_segs_start = vec![(CoordX::from(0), vseg(0, 10, 1.0))];
1186 let intersections = collect_intersections(h_segs.clone(), v_segs_start);
1187 assert_eq!(intersections, BTreeSet::from([point(0, 5)]));
1188
1189 let v_segs_end = vec![(CoordX::from(10), vseg(0, 10, 1.0))];
1191 let intersections = collect_intersections(h_segs.clone(), v_segs_end);
1192 assert_eq!(intersections, BTreeSet::from([point(10, 5)]));
1193
1194 let v_segs_y_start = vec![(CoordX::from(5), vseg(5, 15, 1.0))];
1196 let intersections = collect_intersections(h_segs.clone(), v_segs_y_start);
1197 assert_eq!(intersections, BTreeSet::from([point(5, 5)]));
1198
1199 let v_segs_y_end = vec![(CoordX::from(5), vseg(0, 5, 1.0))];
1201 let intersections = collect_intersections(h_segs, v_segs_y_end);
1202 assert_eq!(intersections, BTreeSet::from([point(5, 5)]));
1203 }
1204
1205 #[test]
1206 fn test_collect_intersections_corners() {
1207 let h_segs = vec![(CoordY::from(5), hseg(10, 20, 1.0))];
1209 let v_segs = vec![(CoordX::from(10), vseg(5, 15, 1.0))];
1210 let intersections = collect_intersections(h_segs, v_segs);
1211 assert_eq!(intersections, BTreeSet::from([point(10, 5)]));
1212 }
1213
1214 #[test]
1215 fn test_collect_intersections_corner_all_endpoints() {
1216 let h_seg_y = CoordY::from(10);
1218 let h_segs = vec![(h_seg_y, hseg(5, 15, 1.0))];
1219
1220 let v_segs = vec![(CoordX::from(5), vseg(10, 20, 1.0))];
1222 let intersections = collect_intersections(h_segs.clone(), v_segs);
1223 assert_eq!(intersections, BTreeSet::from([point(5, 10)]));
1224
1225 let v_segs = vec![(CoordX::from(5), vseg(0, 10, 1.0))];
1227 let intersections = collect_intersections(h_segs.clone(), v_segs);
1228 assert_eq!(intersections, BTreeSet::from([point(5, 10)]));
1229
1230 let v_segs = vec![(CoordX::from(15), vseg(10, 20, 1.0))];
1232 let intersections = collect_intersections(h_segs.clone(), v_segs);
1233 assert_eq!(intersections, BTreeSet::from([point(15, 10)]));
1234
1235 let v_segs = vec![(CoordX::from(15), vseg(0, 10, 1.0))];
1237 let intersections = collect_intersections(h_segs, v_segs);
1238 assert_eq!(intersections, BTreeSet::from([point(15, 10)]));
1239 }
1240
1241 #[test]
1242 fn test_collect_intersections_multiple() {
1243 let h_segs = vec![
1245 (CoordY::from(5), hseg(0, 20, 1.0)),
1246 (CoordY::from(10), hseg(0, 20, 1.0)),
1247 (CoordY::from(15), hseg(0, 20, 1.0)),
1248 ];
1249 let v_segs = vec![(CoordX::from(10), vseg(0, 20, 1.0))];
1250 let intersections = collect_intersections(h_segs, v_segs);
1251 assert_eq!(
1252 intersections,
1253 BTreeSet::from([point(10, 5), point(10, 10), point(10, 15)])
1254 );
1255 }
1256
1257 #[test]
1258 fn test_collect_intersections_multiple_verticals() {
1259 let h_segs = vec![(CoordY::from(10), hseg(0, 30, 1.0))];
1261 let v_segs = vec![
1262 (CoordX::from(5), vseg(5, 15, 1.0)),
1263 (CoordX::from(15), vseg(5, 15, 1.0)),
1264 (CoordX::from(25), vseg(5, 15, 1.0)),
1265 ];
1266 let intersections = collect_intersections(h_segs, v_segs);
1267 assert_eq!(
1268 intersections,
1269 BTreeSet::from([point(5, 10), point(15, 10), point(25, 10)])
1270 );
1271 }
1272
1273 #[test]
1274 fn test_collect_intersections_vertical_outside_horizontal_y_range() {
1275 let h_segs = vec![(CoordY::from(10), hseg(0, 20, 1.0))];
1277 let v_segs = vec![(CoordX::from(10), vseg(15, 25, 1.0))];
1278 let intersections = collect_intersections(h_segs, v_segs);
1279 assert_eq!(intersections, BTreeSet::new());
1280 }
1281
1282 #[test]
1283 fn test_collect_intersections_empty_inputs() {
1284 let intersections =
1286 collect_intersections(vec![], vec![(CoordX::from(5), vseg(0, 10, 1.0))]);
1287 assert_eq!(intersections, BTreeSet::new());
1288
1289 let intersections =
1291 collect_intersections(vec![(CoordY::from(5), hseg(0, 10, 1.0))], vec![]);
1292 assert_eq!(intersections, BTreeSet::new());
1293
1294 let intersections: BTreeSet<Point> = collect_intersections(
1296 Vec::<(CoordY, HSegment)>::new(),
1297 Vec::<(CoordX, VSegment)>::new(),
1298 );
1299 assert_eq!(intersections, BTreeSet::new());
1300 }
1301
1302 #[test]
1303 fn test_random_segments_line_sweep_matches_brute_force() {
1304 use rand::rngs::StdRng;
1305 use rand::{Rng, SeedableRng};
1306
1307 const NUM_H_SEGMENTS: usize = 1000;
1308 const NUM_V_SEGMENTS: usize = 1000;
1309 const FIELD_SIZE: i32 = 200;
1310
1311 let mut rng = StdRng::seed_from_u64(42);
1313
1314 let mut router = RouterNGBuilder::default().build();
1315
1316 for _ in 0..NUM_H_SEGMENTS {
1318 let y = rng.random_range(0..FIELD_SIZE);
1319 let x1 = rng.random_range(0..FIELD_SIZE);
1320 let x2 = rng.random_range(0..FIELD_SIZE);
1321 let (start, end) = if x1 <= x2 { (x1, x2) } else { (x2, x1) };
1322 if start < end {
1324 router.add_horiz_segment(y, start, end, rng.random_range(0.1..10.0));
1325 }
1326 }
1327
1328 for _ in 0..NUM_V_SEGMENTS {
1330 let x = rng.random_range(0..FIELD_SIZE);
1331 let y1 = rng.random_range(0..FIELD_SIZE);
1332 let y2 = rng.random_range(0..FIELD_SIZE);
1333 let (start, end) = if y1 <= y2 { (y1, y2) } else { (y2, y1) };
1334 if start < end {
1336 router.add_vert_segment(x, start, end, rng.random_range(0.1..10.0));
1337 }
1338 }
1339
1340 let normalize_start = std::time::Instant::now();
1342 router.update();
1343 let normalize_time = normalize_start.elapsed();
1344
1345 println!("Normalization took: {normalize_time:?}");
1346 println!(
1347 "Normalized to {} horizontal segments and {} vertical segments",
1348 router
1349 .h_segments
1350 .values()
1351 .map(std::vec::Vec::len)
1352 .sum::<usize>(),
1353 router
1354 .v_segments
1355 .values()
1356 .map(std::vec::Vec::len)
1357 .sum::<usize>()
1358 );
1359
1360 let line_sweep_start = std::time::Instant::now();
1362 let line_sweep_intersections =
1363 collect_intersections(router.iter_hsegs(), router.iter_vsegs());
1364 let line_sweep_time = line_sweep_start.elapsed();
1365
1366 println!("Line-sweep algorithm took: {line_sweep_time:?}");
1367 println!("Found {} intersections", line_sweep_intersections.len());
1368
1369 let brute_force_start = std::time::Instant::now();
1371 let brute_force_intersections: BTreeSet<Point> =
1372 collect_intersections_brute_force(router.iter_hsegs(), router.iter_vsegs())
1373 .into_iter()
1374 .collect();
1375 let brute_force_time = brute_force_start.elapsed();
1376
1377 println!("Brute-force algorithm took: {brute_force_time:?}");
1378
1379 let speedup = brute_force_time.as_secs_f64() / line_sweep_time.as_secs_f64();
1380 println!("Line-sweep is {speedup:.2}x faster than brute-force");
1381
1382 assert_eq!(
1384 line_sweep_intersections.len(),
1385 brute_force_intersections.len(),
1386 "Number of intersections differs: line-sweep found {}, brute-force found {}",
1387 line_sweep_intersections.len(),
1388 brute_force_intersections.len()
1389 );
1390
1391 assert_eq!(
1392 line_sweep_intersections, brute_force_intersections,
1393 "Intersection sets differ between line-sweep and brute-force algorithms"
1394 );
1395 }
1396
1397 #[test]
1398 fn incoming_direction_prevents_double_back_out_of_waypoint() {
1399 let mut router = RouterNGBuilder::default().build();
1404 router.seed_channels(point(0, 0), 0.0);
1405 router.seed_channels(point(10, -10), 0.0);
1406
1407 let (path, outgoing) =
1411 router.path_find_with_fallback(point(0, 0), point(10, -10), Some(Direction::South));
1412
1413 assert!(path.len() >= 2, "expected a real path, got {path:?}");
1414 assert!(
1415 path[1].x > path[0].x,
1416 "router doubled back instead of turning east first: {path:?}"
1417 );
1418 assert_eq!(outgoing, Some(Direction::North));
1421 }
1422}