1use rustc_hash::FxHashMap;
15use std::collections::{BTreeMap, BTreeSet};
16
17use blockworx_geom::Pos2;
18use pathfinding::directed::dijkstra::dijkstra;
19use petgraph::{
20 graph::{NodeIndex, UnGraph},
21 visit::EdgeRef,
22};
23
24pub mod block;
25pub mod channel;
26#[cfg(any(test, feature = "test-support"))]
27pub mod ci_stats;
28pub mod coord;
29pub mod cost;
30pub mod event;
31pub mod point;
32pub mod segment;
33pub mod turtle;
34
35use crate::{
36 block::{Block, ROUTE_GUTTER},
37 channel::{Channel, ChannelOrientation, h_channel, v_channel},
38 coord::{CoordX, CoordY, INFINITY_X, INFINITY_Y, NEG_INFINITY_X, NEG_INFINITY_Y},
39 cost::{COST_ZERO, Cost},
40 event::{Event, EventSense},
41 point::{Point, point},
42 segment::{HSegment, Segment, VSegment, hseg, vseg},
43 turtle::{Mark, Turtle},
44};
45
46#[derive(Clone, Copy, PartialEq, Eq, Debug)]
49pub enum Resolution {
50 Routed,
51 Fallback,
52}
53
54impl Resolution {
55 #[must_use]
57 pub fn and(self, other: Self) -> Self {
58 match (self, other) {
59 (Self::Routed, Self::Routed) => Self::Routed,
60 _ => Self::Fallback,
61 }
62 }
63}
64
65#[derive(Clone, Debug)]
68pub struct Leg {
69 pub path: Vec<Point>,
70 pub outgoing: Option<Direction>,
71 pub resolution: Resolution,
72}
73
74#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
75pub enum Direction {
76 North,
77 South,
78 East,
79 West,
80}
81
82impl Direction {
83 fn opposite(self) -> Self {
84 match self {
85 Direction::North => Direction::South,
86 Direction::South => Direction::North,
87 Direction::East => Direction::West,
88 Direction::West => Direction::East,
89 }
90 }
91}
92
93pub fn direction_between(from: Point, to: Point) -> Option<Direction> {
97 if to.x > from.x {
98 Some(Direction::East)
99 } else if to.x < from.x {
100 Some(Direction::West)
101 } else if to.y > from.y {
102 Some(Direction::South)
103 } else if to.y < from.y {
104 Some(Direction::North)
105 } else {
106 None
107 }
108}
109
110const TURN_COST: Cost = Cost::new(25.0);
111const MOVE_COST: Cost = Cost::new(1.0);
112pub const WIRE_COST: Cost = Cost::new(10.0);
113
114fn cross_cost(
115 from: Option<Direction>,
116 to: Direction,
117 cost_to_cross_east_west: Cost,
118 cost_to_cross_north_south: Cost,
119) -> Cost {
120 if let Some(from_dir) = from {
121 match (from_dir, to) {
122 (Direction::North, Direction::South) | (Direction::South, Direction::North) => {
123 cost_to_cross_east_west
124 }
125 (Direction::East, Direction::West) | (Direction::West, Direction::East) => {
126 cost_to_cross_north_south
127 }
128 _ => COST_ZERO,
129 }
130 } else {
131 COST_ZERO
132 }
133}
134
135fn turn_cost(from: Option<Direction>, to: Direction) -> Cost {
136 if let Some(from_dir) = from {
137 if from_dir == to {
138 COST_ZERO
139 } else if to == from_dir.opposite() {
140 TURN_COST * 100.0
141 } else {
142 TURN_COST
143 }
144 } else {
145 COST_ZERO
146 }
147}
148
149#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Hash)]
150struct SearchState {
151 node: NodeIndex,
152 dir: Option<Direction>,
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub struct Bounds {
164 left: CoordX,
165 right: CoordX,
166 top: CoordY,
167 bottom: CoordY,
168}
169
170impl Default for Bounds {
171 fn default() -> Self {
172 Self::UNBOUNDED
173 }
174}
175
176impl Bounds {
177 pub const UNBOUNDED: Self = Self {
179 left: NEG_INFINITY_X,
180 right: INFINITY_X,
181 top: NEG_INFINITY_Y,
182 bottom: INFINITY_Y,
183 };
184
185 #[must_use]
187 pub fn between(a: impl Into<Point>, b: impl Into<Point>) -> Self {
188 let (a, b) = (a.into(), b.into());
189 Self {
190 left: a.x.min(b.x),
191 right: a.x.max(b.x),
192 top: a.y.min(b.y),
193 bottom: a.y.max(b.y),
194 }
195 }
196
197 #[must_use]
199 pub fn holds(self, p: impl Into<Point>) -> bool {
200 let p: Point = p.into();
201 (self.left..=self.right).contains(&p.x) && (self.top..=self.bottom).contains(&p.y)
202 }
203
204 #[must_use]
208 fn reaches(self, top_left: Point, bottom_right: Point) -> bool {
209 top_left.x <= self.right
210 && bottom_right.x >= self.left
211 && top_left.y <= self.bottom
212 && bottom_right.y >= self.top
213 }
214}
215
216#[derive(Debug, Clone, Default)]
217pub struct RouterNGBuilder {
218 bounds: Bounds,
220 blocks: Vec<Block>,
222 channels: Vec<Channel>,
224 seed_points: Vec<Point>,
227}
228
229impl RouterNGBuilder {
230 #[must_use]
232 pub fn within(mut self, bounds: Bounds) -> Self {
233 self.bounds = bounds;
234 self
235 }
236
237 pub fn add_h_channel(&mut self, seed: impl Into<Point>, cost: impl Into<Cost>) {
238 self.channels.push(h_channel(seed, cost));
239 }
240 pub fn add_seed_point(&mut self, p: impl Into<Point>) {
244 self.seed_points.push(p.into());
245 }
246 pub fn add_v_channel(&mut self, seed: impl Into<Point>, cost: impl Into<Cost>) {
247 self.channels.push(v_channel(seed, cost));
248 }
249 fn add_routing_moat(
250 &mut self,
251 top_left: Point,
252 bottom_right: Point,
253 distance: i32,
254 cost: Cost,
255 ) {
256 let min_x = top_left.x.min(bottom_right.x);
257 let max_x = top_left.x.max(bottom_right.x);
258 let min_y = top_left.y.min(bottom_right.y);
259 let max_y = top_left.y.max(bottom_right.y);
260 self.add_v_channel(point(min_x - distance - 2, min_y), cost);
261 self.add_v_channel(point(min_x - distance - 2, max_y), cost);
262 self.add_v_channel(point(max_x + distance + 2, min_y), cost);
263 self.add_v_channel(point(max_x + distance + 2, max_y), cost);
264 self.add_h_channel(point(min_x, min_y - distance - 2), cost);
265 self.add_h_channel(point(max_x, min_y - distance - 2), cost);
266 self.add_h_channel(point(min_x, max_y + distance + 2), cost);
267 self.add_h_channel(point(max_x, max_y + distance + 2), cost);
268 }
269 pub fn add_block(&mut self, top_left: impl Into<Point>, bottom_right: impl Into<Point>) {
270 let top_left: Point = top_left.into();
271 let bottom_right: Point = bottom_right.into();
272 let min_x = top_left.x.min(bottom_right.x);
273 let max_x = top_left.x.max(bottom_right.x);
274 let min_y = top_left.y.min(bottom_right.y);
275 let max_y = top_left.y.max(bottom_right.y);
276 let block = Block {
277 top_left: point(min_x, min_y),
278 bottom_right: point(max_x, max_y),
279 };
280 if !self.bounds.reaches(block.top_left, block.bottom_right) {
281 return;
282 }
283 self.blocks.push(block);
284 for moat_lane in 0..crate::block::MOAT_LANES {
286 let cost = if moat_lane == 0 {
287 Cost::new(0.2)
288 } else {
289 Cost::new(0.1)
290 };
291 self.add_routing_moat(top_left, bottom_right, moat_lane, cost);
292 }
293 }
294 fn seed_channels_into_router(&self) -> RouterNG {
297 let block_index = BlockAxisIndex::build(&self.blocks);
298 let mut router = RouterNG {
299 bounds: self.bounds,
300 blocks: self.blocks.clone(),
301 block_index,
302 h_segments: BTreeMap::new(),
303 v_segments: BTreeMap::new(),
304 nodes: BTreeSet::new(),
305 graph: UnGraph::default(),
306 node_to_index: FxHashMap::default(),
307 dirty: true,
308 requested: Requests {
309 channels: self.channels.clone(),
310 seed_points: Vec::new(),
311 },
312 };
313 for channel in &self.channels {
314 match channel.orientation {
315 ChannelOrientation::Horizontal => {
316 router.seed_horiz_channel(channel.seed, channel.cost);
317 }
318 ChannelOrientation::Vertical => {
319 router.seed_vert_channel(channel.seed, channel.cost);
320 }
321 }
322 }
323 router
324 }
325 #[cfg(test)]
328 pub fn build(self) -> RouterNG {
329 let mut router = self.seed_channels_into_router();
330 router.update();
331 router
332 }
333 pub fn build_closed(self) -> ClosedRouter {
337 let mut router = self.seed_channels_into_router();
338 for &p in &self.seed_points {
339 router.seed_channels(p, COST_ZERO);
340 }
341 router.requested.seed_points.clone_from(&self.seed_points);
342 router.update();
343 ClosedRouter { inner: router }
344 }
345}
346
347#[derive(Debug, Clone)]
353pub struct ClosedRouter {
354 inner: RouterNG,
355}
356
357impl ClosedRouter {
358 #[must_use]
373 pub fn extended(mut self, add: impl FnOnce(&mut Opening<'_>)) -> Self {
374 let mut opening = Opening {
375 router: &mut self.inner,
376 };
377 add(&mut opening);
378 self.inner.reseed();
379 self.inner.update();
380 self
381 }
382
383 pub fn route_leg(&mut self, start: Point, end: Point, incoming: Option<Direction>) -> Leg {
389 self.inner.path_find_with_fallback(start, end, incoming)
390 }
391
392 pub fn bump_leg(&mut self, path: &[Point], cost: Cost) {
394 for w in path.windows(2) {
395 self.add_wire_cost(w[0], w[1], cost);
396 }
397 }
398
399 pub fn add_wire_cost(&mut self, a: Point, b: Point, cost: Cost) {
405 let Some(dir) = direction_between(a, b) else {
406 return;
407 };
408 let Some(&target) = self.inner.node_to_index.get(&b) else {
409 return;
410 };
411 let Some(&start) = self.inner.node_to_index.get(&a) else {
412 return;
413 };
414 let mut cur = start;
415 let mut cur_pt = a;
416 for _ in 0..self.inner.node_to_index.len() {
419 if cur == target {
420 break;
421 }
422 let step = self.inner.graph.edges(cur).find_map(|e| {
423 let np = self.inner.point(e.target());
424 (direction_between(cur_pt, np) == Some(dir)).then_some((e.id(), e.target(), np))
425 });
426 let Some((edge_id, next, next_pt)) = step else {
427 break;
428 };
429 if let Some(w) = self.inner.graph.edge_weight_mut(edge_id) {
430 *w += cost;
431 }
432 cur = next;
433 cur_pt = next_pt;
434 }
435 }
436
437 pub fn is_wire_blocked(&self, a: Point, b: Point) -> bool {
439 self.inner
440 .blocks
441 .iter()
442 .any(|block| block.intersects_edge(a, b))
443 }
444
445 pub fn wire_hugs_block(&self, a: Point, b: Point) -> bool {
449 self.inner
450 .blocks
451 .iter()
452 .any(|blk| blk.hugs_wire(a, b, ROUTE_GUTTER))
453 }
454
455 pub fn is_accessible(&self, test: impl Into<Point>) -> bool {
456 self.inner.is_accessible(test)
457 }
458
459 pub fn debug_marks(&self) -> Vec<Mark> {
460 self.inner.debug_marks()
461 }
462
463 #[must_use]
469 pub fn fingerprint(&self) -> Fingerprint {
470 let mut edges: Vec<(Point, Point, Cost)> = self
471 .inner
472 .graph
473 .edge_indices()
474 .filter_map(|e| {
475 let (a, b) = self.inner.graph.edge_endpoints(e)?;
476 let (a, b) = (self.inner.point(a), self.inner.point(b));
477 let (lo, hi) = if (a.x, a.y) <= (b.x, b.y) {
478 (a, b)
479 } else {
480 (b, a)
481 };
482 Some((lo, hi, *self.inner.graph.edge_weight(e)?))
483 })
484 .collect();
485 edges.sort_unstable_by_key(|&(a, b, cost)| ((a.x, a.y), (b.x, b.y), cost));
486 Fingerprint {
487 nodes: self.inner.nodes.iter().copied().collect(),
488 edges,
489 }
490 }
491}
492
493#[derive(Debug, Clone, PartialEq, Eq)]
496pub struct Fingerprint {
497 nodes: Vec<Point>,
498 edges: Vec<(Point, Point, Cost)>,
499}
500
501impl Fingerprint {
502 #[must_use]
504 pub fn points(&self) -> &[Point] {
505 &self.nodes
506 }
507
508 #[must_use]
510 pub fn nodes(&self) -> usize {
511 self.nodes.len()
512 }
513
514 #[must_use]
516 pub fn difference(&self, other: &Self) -> String {
517 let missing_nodes = self
518 .nodes
519 .iter()
520 .filter(|n| !other.nodes.contains(n))
521 .count();
522 let extra_nodes = other
523 .nodes
524 .iter()
525 .filter(|n| !self.nodes.contains(n))
526 .count();
527 format!(
528 "nodes {} vs {} ({missing_nodes} missing, {extra_nodes} extra), edges {} vs {}",
529 self.nodes.len(),
530 other.nodes.len(),
531 self.edges.len(),
532 other.edges.len(),
533 )
534 }
535}
536
537pub struct Opening<'a> {
540 router: &'a mut RouterNG,
541}
542
543impl Opening<'_> {
544 pub fn add_seed_point(&mut self, p: impl Into<Point>) {
547 self.router.requested.seed_points.push(p.into());
548 }
549
550 pub fn add_h_channel(&mut self, seed: impl Into<Point>, cost: impl Into<Cost>) {
551 self.router
552 .requested
553 .channels
554 .push(channel::h_channel(seed, cost));
555 }
556
557 pub fn add_v_channel(&mut self, seed: impl Into<Point>, cost: impl Into<Cost>) {
558 self.router
559 .requested
560 .channels
561 .push(channel::v_channel(seed, cost));
562 }
563
564 pub fn add_block(&mut self, top_left: impl Into<Point>, bottom_right: impl Into<Point>) {
568 let mut builder = RouterNGBuilder::default();
569 builder.add_block(top_left, bottom_right);
570 self.router.blocks.extend(builder.blocks.iter().copied());
571 self.router.requested.channels.extend(builder.channels);
572 self.router.block_index = BlockAxisIndex::build(&self.router.blocks);
573 }
574}
575
576#[derive(Debug, Clone, Default)]
584struct BlockAxisIndex {
585 by_y: BTreeMap<CoordY, Vec<usize>>,
586 by_x: BTreeMap<CoordX, Vec<usize>>,
587}
588
589impl BlockAxisIndex {
590 fn build(blocks: &[Block]) -> Self {
591 let mut by_y: BTreeMap<CoordY, Vec<usize>> = BTreeMap::new();
592 let mut by_x: BTreeMap<CoordX, Vec<usize>> = BTreeMap::new();
593 for (i, block) in blocks.iter().enumerate() {
594 let ey = block.expand_y(1);
595 for y in ey.top_left.y.raw()..=ey.bottom_right.y.raw() {
596 by_y.entry(CoordY::from(y)).or_default().push(i);
597 }
598 let ex = block.expand_x(1);
599 for x in ex.top_left.x.raw()..=ex.bottom_right.x.raw() {
600 by_x.entry(CoordX::from(x)).or_default().push(i);
601 }
602 }
603 Self { by_y, by_x }
604 }
605
606 fn spanning_y<'a>(&'a self, y: CoordY, blocks: &'a [Block]) -> impl Iterator<Item = &'a Block> {
608 self.by_y.get(&y).into_iter().flatten().map(|&i| &blocks[i])
609 }
610
611 fn spanning_x<'a>(&'a self, x: CoordX, blocks: &'a [Block]) -> impl Iterator<Item = &'a Block> {
613 self.by_x.get(&x).into_iter().flatten().map(|&i| &blocks[i])
614 }
615}
616
617#[derive(Debug, Clone)]
618pub struct RouterNG {
619 bounds: Bounds,
621 blocks: Vec<Block>,
623 block_index: BlockAxisIndex,
625 h_segments: BTreeMap<CoordY, Vec<HSegment>>,
627 v_segments: BTreeMap<CoordX, Vec<VSegment>>,
629 nodes: BTreeSet<Point>,
631 graph: UnGraph<Point, Cost>,
633 node_to_index: FxHashMap<Point, petgraph::graph::NodeIndex>,
635 dirty: bool,
637 requested: Requests,
644}
645
646#[derive(Debug, Clone, Default)]
648struct Requests {
649 channels: Vec<Channel>,
650 seed_points: Vec<Point>,
651}
652
653impl RouterNG {
654 pub fn debug_marks(&self) -> Vec<Mark> {
660 assert!(
661 !self.dirty,
662 "Cannot generate debug marks when the graph is dirty"
663 );
664 let mut turtle = Turtle::default();
665 for node in self.graph.node_indices() {
666 let pos = self.point(node);
667 turtle.move_to(pos.into());
668 turtle.circle(blockworx_geom::WorldPx::new(3.0));
669 for edge in self.graph.edges(node) {
672 let target = edge.target();
673 let edge_weight = edge.weight();
674 let target_pos = self.point(target);
675
676 let start_pos: Pos2 = pos.into();
678 let end_pos: Pos2 = target_pos.into();
679 let dx = end_pos.x - start_pos.x;
680 let dy = end_pos.y - start_pos.y;
681 let distance = (dx * dx + dy * dy).sqrt();
682
683 if distance < 8.0 {
685 continue;
686 }
687
688 let gap = 4.0;
690 let gap_ratio = gap / distance;
691
692 let line_start =
694 Pos2::new(start_pos.x + dx * gap_ratio, start_pos.y + dy * gap_ratio);
695
696 let line_end = Pos2::new(end_pos.x - dx * gap_ratio, end_pos.y - dy * gap_ratio);
698
699 turtle.move_to(line_start);
700 turtle.line_to(line_end);
701 let mid_point = line_start + (line_end - line_start) / 2.0;
702 let weight: f64 = (*edge_weight).into();
703 turtle.label(mid_point, weight as f32);
704 }
705 }
706 turtle.compile()
707 }
708 pub fn is_accessible(&self, test: impl Into<Point>) -> bool {
709 let test: Point = test.into();
710 !self.blocks.iter().any(|block| block.contains(test))
711 }
712 fn seed_horiz_channel(&mut self, center: impl Into<Point>, cost: impl Into<Cost>) {
713 let center: Point = center.into();
714 let cost: Cost = cost.into();
715 if !self.bounds.holds(center) {
716 return;
717 }
718 let mut left_endpoint = self.bounds.left;
719 let mut right_endpoint = self.bounds.right;
720 for block in self.block_index.spanning_y(center.y, &self.blocks) {
723 let block = block.expand_x(1).expand_y(1);
724 if block.spans_x(center.x) {
725 return;
727 }
728 if block.is_left_of(center.x) {
729 left_endpoint = left_endpoint.max(block.bottom_right.x);
730 }
731 if block.is_right_of(center.x) {
732 right_endpoint = right_endpoint.min(block.top_left.x);
733 }
734 }
735 if left_endpoint < right_endpoint {
736 self.add_horiz_segment(center.y, left_endpoint, right_endpoint, cost);
737 }
738 }
739 fn seed_vert_channel(&mut self, center: impl Into<Point>, cost: impl Into<Cost>) {
740 let center: Point = center.into();
741 let cost: Cost = cost.into();
742 if !self.bounds.holds(center) {
743 return;
744 }
745 let mut top_endpoint = self.bounds.top;
746 let mut bottom_endpoint = self.bounds.bottom;
747 for block in self.block_index.spanning_x(center.x, &self.blocks) {
750 if block.spans_y(center.y) {
751 return;
753 }
754 if block.is_above(center.y) {
755 top_endpoint = top_endpoint.max(block.bottom_right.y);
756 }
757 if block.is_below(center.y) {
758 bottom_endpoint = bottom_endpoint.min(block.top_left.y);
759 }
760 }
761 if top_endpoint < bottom_endpoint {
762 self.add_vert_segment(center.x, top_endpoint, bottom_endpoint, cost);
763 }
764 }
765 pub fn seed_channels(&mut self, center: impl Into<Point>, cost: impl Into<Cost>) {
766 let center: Point = center.into();
767 let cost: Cost = cost.into();
768 self.seed_horiz_channel(center, cost);
769 self.seed_vert_channel(center, cost);
770 }
771 pub fn add_horiz_segment(
772 &mut self,
773 vert: impl Into<CoordY>,
774 left: impl Into<CoordX>,
775 right: impl Into<CoordX>,
776 cost: impl Into<Cost>,
777 ) {
778 let vert: CoordY = vert.into();
779 let left: CoordX = left.into();
780 let right: CoordX = right.into();
781 let cost: Cost = cost.into();
782 if right > left {
783 self.h_segments
784 .entry(vert)
785 .or_default()
786 .push(hseg(left, right, cost));
787 self.dirty = true;
788 }
789 }
790 pub fn add_vert_segment(
791 &mut self,
792 horiz: impl Into<CoordX>,
793 top: impl Into<CoordY>,
794 bottom: impl Into<CoordY>,
795 cost: impl Into<Cost>,
796 ) {
797 let horiz: CoordX = horiz.into();
798 let top: CoordY = top.into();
799 let bottom: CoordY = bottom.into();
800 let cost: Cost = cost.into();
801 if bottom > top {
802 self.v_segments
803 .entry(horiz)
804 .or_default()
805 .push(vseg(top, bottom, cost));
806 self.dirty = true;
807 }
808 }
809 fn reseed(&mut self) {
813 self.h_segments = BTreeMap::new();
814 self.v_segments = BTreeMap::new();
815 let requested = std::mem::take(&mut self.requested);
816 for channel in &requested.channels {
817 match channel.orientation {
818 ChannelOrientation::Horizontal => {
819 self.seed_horiz_channel(channel.seed, channel.cost);
820 }
821 ChannelOrientation::Vertical => {
822 self.seed_vert_channel(channel.seed, channel.cost);
823 }
824 }
825 }
826 for &p in &requested.seed_points {
827 self.seed_channels(p, COST_ZERO);
828 }
829 self.requested = requested;
830 self.dirty = true;
831 }
832
833 pub fn update(&mut self) {
834 if !self.dirty {
835 return;
836 }
837 let _span = tracing::info_span!("router_rebuild").entered();
840 {
841 let _s = tracing::info_span!(
842 "normalize",
843 h = self.h_segments.len(),
844 v = self.v_segments.len()
845 )
846 .entered();
847 let h_segments = std::mem::take(&mut self.h_segments);
848 for (vert, segments) in h_segments {
849 normalize_collinear_segments(segments, |left, right, cost| {
850 self.add_horiz_segment(vert, left, right, cost);
851 });
852 }
853 let v_segments = std::mem::take(&mut self.v_segments);
854 for (horiz, segments) in v_segments {
855 normalize_collinear_segments(segments, |top, bottom, cost| {
856 self.add_vert_segment(horiz, top, bottom, cost);
857 });
858 }
859 }
860 {
861 let _s = tracing::info_span!("intersections").entered();
862 self.nodes = collect_intersections(self.iter_hsegs(), self.iter_vsegs());
863 }
864 {
866 let _s = tracing::info_span!("resegment", nodes = self.nodes.len()).entered();
867 let mut h_segments = std::mem::take(&mut self.h_segments);
868 self.nodes.iter().for_each(|&node| {
869 h_segments
870 .entry(node.y)
871 .or_default()
872 .push(hseg(node.x, node.x, COST_ZERO));
873 });
874 for (vert, segments) in h_segments {
875 normalize_collinear_segments(segments, |left, right, cost| {
876 self.add_horiz_segment(vert, left, right, cost);
877 });
878 }
879 let mut v_segments = std::mem::take(&mut self.v_segments);
880 self.nodes.iter().for_each(|&node| {
881 v_segments
882 .entry(node.x)
883 .or_default()
884 .push(vseg(node.y, node.y, COST_ZERO));
885 });
886 for (horiz, segments) in v_segments {
887 normalize_collinear_segments(segments, |top, bottom, cost| {
888 self.add_vert_segment(horiz, top, bottom, cost);
889 });
890 }
891 }
892 {
898 let _s = tracing::info_span!("endpoints").entered();
899 self.nodes = self
900 .iter_hsegs()
901 .flat_map(|(y, h_seg)| [point(h_seg.start, y), point(h_seg.end, y)])
902 .chain(
903 self.iter_vsegs()
904 .flat_map(|(x, v_seg)| [point(x, v_seg.start), point(x, v_seg.end)]),
905 )
906 .collect();
907 }
908 self.rebuild_graph();
909 self.dirty = false;
910 }
911 fn iter_hsegs(&self) -> impl Iterator<Item = (CoordY, HSegment)> + '_ {
912 self.h_segments
913 .iter()
914 .flat_map(|(&y, h_segs)| h_segs.iter().map(move |h_seg| (y, *h_seg)))
915 }
916 fn iter_vsegs(&self) -> impl Iterator<Item = (CoordX, VSegment)> + '_ {
917 self.v_segments
918 .iter()
919 .flat_map(|(&x, v_segs)| v_segs.iter().map(move |v_seg| (x, *v_seg)))
920 }
921 fn rebuild_graph(&mut self) {
922 let _s = tracing::info_span!("rebuild_graph", nodes = self.nodes.len()).entered();
923 let mut node_to_index: FxHashMap<Point, petgraph::graph::NodeIndex> = FxHashMap::default();
924 let edges: usize = self.h_segments.values().map(Vec::len).sum::<usize>()
925 + self.v_segments.values().map(Vec::len).sum::<usize>();
926 let mut graph = UnGraph::default();
927 {
928 let _s = tracing::info_span!("add_nodes").entered();
929 for &node in &self.nodes {
930 let index = graph.add_node(node);
931 node_to_index.insert(node, index);
932 }
933 }
934 let _e = tracing::info_span!("add_edges", edges).entered();
935 for hseg in self.iter_hsegs() {
936 let y = hseg.0;
937 let h_seg = hseg.1;
938 let start_node = point(h_seg.start, y);
939 let end_node = point(h_seg.end, y);
940 graph.add_edge(
941 node_to_index[&start_node],
942 node_to_index[&end_node],
943 h_seg.cost,
944 );
945 }
946 for vseg in self.iter_vsegs() {
947 let x = vseg.0;
948 let v_seg = vseg.1;
949 let start_node = point(x, v_seg.start);
950 let end_node = point(x, v_seg.end);
951 graph.add_edge(
952 node_to_index[&start_node],
953 node_to_index[&end_node],
954 v_seg.cost,
955 );
956 }
957 self.graph = graph;
958 self.node_to_index = node_to_index;
959 }
960 fn point(&self, node: NodeIndex) -> Point {
963 self.graph.node_weight(node).copied().unwrap_or(Point::ZERO)
964 }
965
966 fn successors(&self, state: SearchState) -> Vec<(SearchState, Cost)> {
967 let prev_dir = state.dir;
968 let prev_point = self.point(state.node);
969 let mut north_cost: Option<Cost> = None;
970 let mut south_cost: Option<Cost> = None;
971 let mut east_cost: Option<Cost> = None;
972 let mut west_cost: Option<Cost> = None;
973 for edge in self.graph.edges(state.node) {
975 let neighbor = edge.target();
976 let cost = *edge.weight();
977 let neighbor_point = self.point(neighbor);
978 if neighbor_point.x > prev_point.x {
979 east_cost = Some(cost);
980 } else if neighbor_point.x < prev_point.x {
981 west_cost = Some(cost);
982 } else if neighbor_point.y > prev_point.y {
983 south_cost = Some(cost);
984 } else {
985 north_cost = Some(cost);
986 }
987 }
988 let east_west_crossing_cost = match (east_cost, west_cost) {
992 (Some(east), Some(west)) => east.max(west),
993 _ => COST_ZERO,
994 };
995 let north_south_crossing_cost = match (north_cost, south_cost) {
996 (Some(north), Some(south)) => north.max(south),
997 _ => COST_ZERO,
998 };
999 self.graph
1001 .edges(state.node)
1002 .map(|edge| {
1003 let neighbor = edge.target();
1004 let cost = *edge.weight();
1005 let neighbor_point = self.point(neighbor);
1006 let dir = if neighbor_point.x > prev_point.x {
1007 Direction::East
1008 } else if neighbor_point.x < prev_point.x {
1009 Direction::West
1010 } else if neighbor_point.y > prev_point.y {
1011 Direction::South
1012 } else {
1013 Direction::North
1014 };
1015 let step_length = neighbor_point.manhattan_distance(prev_point) as f64;
1016 let step_cost = turn_cost(prev_dir, dir)
1017 + MOVE_COST * step_length
1018 + cost * step_length
1019 + cross_cost(
1020 prev_dir,
1021 dir,
1022 east_west_crossing_cost,
1023 north_south_crossing_cost,
1024 );
1025 (
1026 SearchState {
1027 node: neighbor,
1028 dir: Some(dir),
1029 },
1030 step_cost,
1031 )
1032 })
1033 .collect()
1034 }
1035 fn path_find(
1042 &mut self,
1043 start: impl Into<Point>,
1044 end: impl Into<Point>,
1045 incoming: Option<Direction>,
1046 ) -> Option<(Vec<Point>, Option<Direction>)> {
1047 self.update();
1048 let start: Point = start.into();
1049 let end: Point = end.into();
1050 let &start_node = self.node_to_index.get(&start)?;
1051 let end_node = self.node_to_index.get(&end)?;
1052 let start = SearchState {
1053 node: start_node,
1054 dir: incoming,
1055 };
1056 let result = dijkstra(
1057 &start,
1058 |state| self.successors(*state),
1059 |state| state.node == *end_node,
1060 );
1061 result.map(|(path, _cost)| {
1062 let outgoing = path.last().and_then(|state| state.dir);
1063 let points = path
1064 .into_iter()
1065 .map(|state| self.point(state.node))
1066 .collect();
1067 (points, outgoing)
1068 })
1069 }
1070 pub fn path_find_with_fallback(
1073 &mut self,
1074 start: impl Into<Point>,
1075 end: impl Into<Point>,
1076 incoming: Option<Direction>,
1077 ) -> Leg {
1078 let start: Point = start.into();
1079 let end: Point = end.into();
1080 if let Some((path, outgoing)) = self.path_find(start, end, incoming) {
1081 return Leg {
1082 path,
1083 outgoing,
1084 resolution: Resolution::Routed,
1085 };
1086 }
1087 let path = vec![start, point(end.x, start.y), end];
1088 let outgoing = path
1089 .windows(2)
1090 .rev()
1091 .find_map(|w| direction_between(w[0], w[1]))
1092 .or(incoming);
1093 Leg {
1094 path,
1095 outgoing,
1096 resolution: Resolution::Fallback,
1097 }
1098 }
1099}
1100
1101fn collect_intersections(
1106 h_segments: impl IntoIterator<Item = (CoordY, HSegment)>,
1107 v_segments: impl IntoIterator<Item = (CoordX, VSegment)>,
1108) -> BTreeSet<Point> {
1109 let mut events: Vec<Event<CoordX, (CoordY, CoordY)>> = h_segments
1110 .into_iter()
1111 .flat_map(|(y, h_seg)| {
1112 [
1113 Event::enter(h_seg.start, (y, y)),
1114 Event::exit(h_seg.end, (y, y)),
1115 ]
1116 })
1117 .chain(
1118 v_segments
1119 .into_iter()
1120 .map(|(x, v_seg)| Event::scan(x, (v_seg.start, v_seg.end))),
1121 )
1122 .collect::<Vec<_>>();
1123 events.sort();
1124 #[cfg(any(test, feature = "test-support"))]
1125 {
1126 use std::sync::atomic::Ordering::Relaxed;
1127 ci_stats::CALLS.fetch_add(1, Relaxed);
1128 ci_stats::EVENTS.fetch_add(events.len() as u64, Relaxed);
1129 }
1130 let mut intersections = BTreeSet::new();
1131 let mut active_h_segments: BTreeMap<CoordY, usize> = BTreeMap::new();
1134 for event in events {
1135 match event.sense() {
1136 EventSense::Enter => {
1137 let y = event.cost().0;
1138 *active_h_segments.entry(y).or_insert(0) += 1;
1139 }
1140 EventSense::Exit => {
1141 let y = event.cost().0;
1142 if let Some(count) = active_h_segments.get_mut(&y) {
1143 *count = count.saturating_sub(1);
1144 if *count == 0 {
1145 active_h_segments.remove(&y);
1146 }
1147 }
1148 }
1149 EventSense::Scan => {
1150 let (start, end) = event.cost();
1151 let in_span = active_h_segments.range(start..=end);
1156 #[cfg(any(test, feature = "test-support"))]
1157 {
1158 use std::sync::atomic::Ordering::Relaxed;
1159 ci_stats::SCANS.fetch_add(1, Relaxed);
1160 ci_stats::INNER_ITERS.fetch_add(in_span.clone().count() as u64, Relaxed);
1161 }
1162 for (&y, _) in in_span {
1163 intersections.insert(point(event.t(), y));
1164 #[cfg(any(test, feature = "test-support"))]
1165 ci_stats::INTERSECTIONS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1166 }
1167 }
1168 }
1169 }
1170 intersections
1171}
1172
1173fn normalize_collinear_segments<T: Ord + Copy>(
1174 segments: impl IntoIterator<Item = Segment<T>>,
1175 mut maker: impl FnMut(T, T, Cost),
1176) {
1177 let mut events = segments
1178 .into_iter()
1179 .flat_map(|seg| {
1180 [
1181 Event::enter(seg.start, seg.cost),
1182 Event::exit(seg.end, seg.cost),
1183 ]
1184 })
1185 .collect::<Vec<_>>();
1186 events.sort();
1188 scan_disjoint_segments(events, |start, end, cost| {
1189 maker(start, end, cost);
1190 });
1191}
1192
1193fn scan_disjoint_segments<T: Ord + Copy>(
1194 events: impl IntoIterator<Item = Event<T, Cost>>,
1195 mut maker: impl FnMut(T, T, Cost),
1196) {
1197 let mut events_iter = events.into_iter();
1198
1199 let Some(first_event) = events_iter.next() else {
1201 return;
1202 };
1203
1204 let mut last_t = first_event.t();
1205 let mut line_count = first_event.count();
1206 let mut current_cost = if first_event.is_enter() {
1207 first_event.cost()
1208 } else {
1209 COST_ZERO - first_event.cost()
1210 };
1211
1212 for event in events_iter {
1214 let t = event.t();
1215 if line_count != 0 {
1217 maker(last_t, t, current_cost);
1218 }
1219 last_t = t;
1220 line_count += event.count();
1221 current_cost = if event.is_enter() {
1222 current_cost + event.cost()
1223 } else {
1224 current_cost - event.cost()
1225 };
1226 }
1227}
1228
1229fn interval_overlap<T: Ord + Copy>(a_start: T, a_end: T, b_start: T, b_end: T) -> bool {
1230 a_start < b_end && b_start < a_end
1231}
1232
1233#[cfg(test)]
1234mod tests {
1235
1236 use super::*;
1237
1238 #[test]
1239 fn block_axis_index_selects_the_same_blocks_as_a_scan() {
1240 use crate::block::Block;
1241 use crate::point::point;
1242 use std::collections::BTreeSet;
1243 let blocks = vec![
1245 Block {
1246 top_left: point(0, 0),
1247 bottom_right: point(8, 8),
1248 },
1249 Block {
1250 top_left: point(20, 4),
1251 bottom_right: point(30, 12),
1252 },
1253 Block {
1254 top_left: point(-10, -3),
1255 bottom_right: point(-2, 40),
1256 },
1257 ];
1258 let index = BlockAxisIndex::build(&blocks);
1259 for c in -14..=44 {
1260 let y = CoordY::from(c);
1261 let from_index: BTreeSet<i32> = index
1262 .spanning_y(y, &blocks)
1263 .map(|b| b.top_left.x.raw())
1264 .collect();
1265 let brute: BTreeSet<i32> = blocks
1266 .iter()
1267 .filter(|b| b.expand_y(1).spans_y(y))
1268 .map(|b| b.top_left.x.raw())
1269 .collect();
1270 assert_eq!(from_index, brute, "spanning_y mismatch at y={c}");
1271
1272 let x = CoordX::from(c);
1273 let from_index_x: BTreeSet<i32> = index
1274 .spanning_x(x, &blocks)
1275 .map(|b| b.top_left.y.raw())
1276 .collect();
1277 let brute_x: BTreeSet<i32> = blocks
1278 .iter()
1279 .filter(|b| b.expand_x(1).spans_x(x))
1280 .map(|b| b.top_left.y.raw())
1281 .collect();
1282 assert_eq!(from_index_x, brute_x, "spanning_x mismatch at x={c}");
1283 }
1284 }
1285
1286 macro_rules! hseg {
1287 (y=$y:expr, [$(($start:expr => $end:expr, $cost:expr)),* $(,)?]) => {
1288 BTreeMap::from([(
1289 CoordY::from($y),
1290 vec![
1291 $(HSegment {
1292 start: CoordX::from($start),
1293 end: CoordX::from($end),
1294 cost: $cost.into(),
1295 }),*
1296 ]
1297 )])
1298 };
1299 }
1300
1301 macro_rules! vseg {
1302 (x=$x:expr, [$(($start:expr => $end:expr, $cost:expr)),* $(,)?]) => {
1303 BTreeMap::from([(
1304 CoordX::from($x),
1305 vec![
1306 $(VSegment {
1307 start: CoordY::from($start),
1308 end: CoordY::from($end),
1309 cost: $cost.into(),
1310 }),*
1311 ]
1312 )])
1313 };
1314 }
1315
1316 fn collect_intersections_brute_force(
1318 h_segments: impl IntoIterator<Item = (CoordY, HSegment)>,
1319 v_segments: impl IntoIterator<Item = (CoordX, VSegment)>,
1320 ) -> Vec<Point> {
1321 let mut points = vec![];
1322 let v_segments = v_segments.into_iter().collect::<Vec<_>>();
1323 for (y, hseg) in h_segments {
1324 for (x, vseg) in &v_segments {
1325 if hseg.start <= *x && hseg.end >= *x && vseg.start <= y && vseg.end >= y {
1326 points.push(point(*x, y));
1327 }
1328 }
1329 }
1330 points
1331 }
1332
1333 #[test]
1334 fn test_vseed() {
1335 let mut router = RouterNGBuilder::default().build();
1336 router.seed_vert_channel(point(0, 0), 1.0);
1337 router.update();
1338 assert_eq!(
1339 router.v_segments,
1340 BTreeMap::from([(CoordX::from(0), vec![vseg(NEG_INFINITY_Y, INFINITY_Y, 1.0)])])
1341 );
1342 }
1343
1344 #[test]
1345 fn test_normalize() {
1346 let mut router = RouterNGBuilder::default().build();
1347 router.add_horiz_segment(0, 0, 10, 1.0);
1348 router.add_horiz_segment(0, 5, 15, 2.0);
1349 router.update();
1350 assert_eq!(
1351 router.h_segments,
1352 hseg!(y=0, [(0=>5, 1.0), (5=>10, 3.0), (10=>15, 2.0)])
1353 );
1354 }
1355
1356 #[test]
1357 fn test_normalize_complete_overlap() {
1358 let mut router = RouterNGBuilder::default().build();
1360 router.add_horiz_segment(0, 0, 20, 1.0);
1361 router.add_horiz_segment(0, 5, 15, 2.0);
1362 router.update();
1363 assert_eq!(
1364 router.h_segments,
1365 hseg!(y=0, [(0=>5, 1.0), (5=>15, 3.0), (15=>20, 1.0)])
1366 );
1367 }
1368
1369 #[test]
1370 fn test_normalize_no_overlap() {
1371 let mut router = RouterNGBuilder::default().build();
1373 router.add_horiz_segment(0, 0, 10, 1.0);
1374 router.add_horiz_segment(0, 20, 30, 2.0);
1375 router.update();
1376 assert_eq!(router.h_segments, hseg!(y=0, [(0=>10, 1.0), (20=>30, 2.0)]));
1377 }
1378
1379 #[test]
1380 fn test_normalize_adjacent_segments() {
1381 let mut router = RouterNGBuilder::default().build();
1383 router.add_horiz_segment(0, 0, 10, 1.0);
1384 router.add_horiz_segment(0, 10, 20, 2.0);
1385 router.update();
1386 assert_eq!(router.h_segments, hseg!(y=0, [(0=>10, 1.0), (10=>20, 2.0)]));
1387 }
1388
1389 #[test]
1390 fn test_normalize_triple_overlap() {
1391 let mut router = RouterNGBuilder::default().build();
1393 router.add_horiz_segment(0, 0, 15, 1.0);
1394 router.add_horiz_segment(0, 5, 20, 2.0);
1395 router.add_horiz_segment(0, 10, 25, 3.0);
1396 router.update();
1397 assert_eq!(
1398 router.h_segments,
1399 hseg!(y=0, [(0=>5, 1.0), (5=>10, 3.0), (10=>15, 6.0), (15=>20, 5.0), (20=>25, 3.0)])
1400 );
1401 }
1402
1403 #[test]
1404 fn test_normalize_multiple_rows() {
1405 let mut router = RouterNGBuilder::default().build();
1407 router.add_horiz_segment(0, 0, 10, 1.0);
1408 router.add_horiz_segment(0, 5, 15, 2.0);
1409 router.add_horiz_segment(5, 0, 10, 3.0);
1410 router.add_horiz_segment(5, 5, 15, 4.0);
1411 router.update();
1412
1413 let mut expected = BTreeMap::new();
1414 expected.extend(hseg!(y=0, [(0=>5, 1.0), (5=>10, 3.0), (10=>15, 2.0)]));
1415 expected.extend(hseg!(y=5, [(0=>5, 3.0), (5=>10, 7.0), (10=>15, 4.0)]));
1416 assert_eq!(router.h_segments, expected);
1417 }
1418
1419 #[test]
1420 fn test_normalize_negative_coords() {
1421 let mut router = RouterNGBuilder::default().build();
1423 router.add_horiz_segment(-5, -20, -10, 1.0);
1424 router.add_horiz_segment(-5, -15, -5, 2.0);
1425 router.update();
1426 assert_eq!(
1427 router.h_segments,
1428 hseg!(y=-5, [(-20 => -15, 1.0), (-15 => -10, 3.0), (-10 => -5, 2.0)])
1429 );
1430 }
1431
1432 #[test]
1433 fn test_normalize_vertical_segments() {
1434 let mut router = RouterNGBuilder::default().build();
1436 router.add_vert_segment(0, 0, 10, 1.0);
1437 router.add_vert_segment(0, 5, 15, 2.0);
1438 router.update();
1439 assert_eq!(
1440 router.v_segments,
1441 vseg!(x=0, [(0=>5, 1.0), (5=>10, 3.0), (10=>15, 2.0)])
1442 );
1443 }
1444
1445 #[test]
1446 fn test_normalize_identical_segments() {
1447 let mut router = RouterNGBuilder::default().build();
1449 router.add_horiz_segment(0, 0, 10, 1.0);
1450 router.add_horiz_segment(0, 0, 10, 1.0);
1451 router.update();
1452 assert_eq!(router.h_segments, hseg!(y=0, [(0=>10, 2.0)]));
1453 }
1454
1455 #[test]
1456 fn test_normalize_reverse_order() {
1457 let mut router = RouterNGBuilder::default().build();
1459 router.add_horiz_segment(0, 20, 30, 1.0);
1460 router.add_horiz_segment(0, 10, 25, 2.0);
1461 router.add_horiz_segment(0, 0, 15, 3.0);
1462 router.update();
1463 assert_eq!(
1464 router.h_segments,
1465 hseg!(y=0, [(0=>10, 3.0), (10=>15, 5.0), (15=>20, 2.0), (20=>25, 3.0), (25=>30, 1.0)])
1466 );
1467 }
1468
1469 #[test]
1472 fn test_collect_intersections_no_intersections() {
1473 let h_segs = vec![
1475 (CoordY::from(0), hseg(0, 10, 1.0)),
1476 (CoordY::from(5), hseg(15, 25, 1.0)),
1477 ];
1478 let v_segs = vec![(CoordX::from(20), vseg(10, 15, 1.0))];
1480 let intersections = collect_intersections(h_segs, v_segs);
1481 assert_eq!(intersections, BTreeSet::new());
1482 }
1483
1484 #[test]
1485 fn test_collect_intersections_at_boundaries() {
1486 let h_segs = vec![(CoordY::from(5), hseg(0, 10, 1.0))];
1488 let v_segs_start = vec![(CoordX::from(0), vseg(0, 10, 1.0))];
1490 let intersections = collect_intersections(h_segs.clone(), v_segs_start);
1491 assert_eq!(intersections, BTreeSet::from([point(0, 5)]));
1492
1493 let v_segs_end = vec![(CoordX::from(10), vseg(0, 10, 1.0))];
1495 let intersections = collect_intersections(h_segs.clone(), v_segs_end);
1496 assert_eq!(intersections, BTreeSet::from([point(10, 5)]));
1497
1498 let v_segs_y_start = vec![(CoordX::from(5), vseg(5, 15, 1.0))];
1500 let intersections = collect_intersections(h_segs.clone(), v_segs_y_start);
1501 assert_eq!(intersections, BTreeSet::from([point(5, 5)]));
1502
1503 let v_segs_y_end = vec![(CoordX::from(5), vseg(0, 5, 1.0))];
1505 let intersections = collect_intersections(h_segs, v_segs_y_end);
1506 assert_eq!(intersections, BTreeSet::from([point(5, 5)]));
1507 }
1508
1509 #[test]
1510 fn test_collect_intersections_corners() {
1511 let h_segs = vec![(CoordY::from(5), hseg(10, 20, 1.0))];
1513 let v_segs = vec![(CoordX::from(10), vseg(5, 15, 1.0))];
1514 let intersections = collect_intersections(h_segs, v_segs);
1515 assert_eq!(intersections, BTreeSet::from([point(10, 5)]));
1516 }
1517
1518 #[test]
1519 fn test_collect_intersections_corner_all_endpoints() {
1520 let h_seg_y = CoordY::from(10);
1522 let h_segs = vec![(h_seg_y, hseg(5, 15, 1.0))];
1523
1524 let v_segs = vec![(CoordX::from(5), vseg(10, 20, 1.0))];
1526 let intersections = collect_intersections(h_segs.clone(), v_segs);
1527 assert_eq!(intersections, BTreeSet::from([point(5, 10)]));
1528
1529 let v_segs = vec![(CoordX::from(5), vseg(0, 10, 1.0))];
1531 let intersections = collect_intersections(h_segs.clone(), v_segs);
1532 assert_eq!(intersections, BTreeSet::from([point(5, 10)]));
1533
1534 let v_segs = vec![(CoordX::from(15), vseg(10, 20, 1.0))];
1536 let intersections = collect_intersections(h_segs.clone(), v_segs);
1537 assert_eq!(intersections, BTreeSet::from([point(15, 10)]));
1538
1539 let v_segs = vec![(CoordX::from(15), vseg(0, 10, 1.0))];
1541 let intersections = collect_intersections(h_segs, v_segs);
1542 assert_eq!(intersections, BTreeSet::from([point(15, 10)]));
1543 }
1544
1545 #[test]
1546 fn test_collect_intersections_multiple() {
1547 let h_segs = vec![
1549 (CoordY::from(5), hseg(0, 20, 1.0)),
1550 (CoordY::from(10), hseg(0, 20, 1.0)),
1551 (CoordY::from(15), hseg(0, 20, 1.0)),
1552 ];
1553 let v_segs = vec![(CoordX::from(10), vseg(0, 20, 1.0))];
1554 let intersections = collect_intersections(h_segs, v_segs);
1555 assert_eq!(
1556 intersections,
1557 BTreeSet::from([point(10, 5), point(10, 10), point(10, 15)])
1558 );
1559 }
1560
1561 #[test]
1562 fn test_collect_intersections_multiple_verticals() {
1563 let h_segs = vec![(CoordY::from(10), hseg(0, 30, 1.0))];
1565 let v_segs = vec![
1566 (CoordX::from(5), vseg(5, 15, 1.0)),
1567 (CoordX::from(15), vseg(5, 15, 1.0)),
1568 (CoordX::from(25), vseg(5, 15, 1.0)),
1569 ];
1570 let intersections = collect_intersections(h_segs, v_segs);
1571 assert_eq!(
1572 intersections,
1573 BTreeSet::from([point(5, 10), point(15, 10), point(25, 10)])
1574 );
1575 }
1576
1577 #[test]
1578 fn test_collect_intersections_vertical_outside_horizontal_y_range() {
1579 let h_segs = vec![(CoordY::from(10), hseg(0, 20, 1.0))];
1581 let v_segs = vec![(CoordX::from(10), vseg(15, 25, 1.0))];
1582 let intersections = collect_intersections(h_segs, v_segs);
1583 assert_eq!(intersections, BTreeSet::new());
1584 }
1585
1586 #[test]
1587 fn test_collect_intersections_empty_inputs() {
1588 let intersections =
1590 collect_intersections(vec![], vec![(CoordX::from(5), vseg(0, 10, 1.0))]);
1591 assert_eq!(intersections, BTreeSet::new());
1592
1593 let intersections =
1595 collect_intersections(vec![(CoordY::from(5), hseg(0, 10, 1.0))], vec![]);
1596 assert_eq!(intersections, BTreeSet::new());
1597
1598 let intersections: BTreeSet<Point> = collect_intersections(
1600 Vec::<(CoordY, HSegment)>::new(),
1601 Vec::<(CoordX, VSegment)>::new(),
1602 );
1603 assert_eq!(intersections, BTreeSet::new());
1604 }
1605
1606 #[test]
1607 fn test_random_segments_line_sweep_matches_brute_force() {
1608 use rand::rngs::StdRng;
1609 use rand::{Rng, SeedableRng};
1610
1611 const NUM_H_SEGMENTS: usize = 1000;
1612 const NUM_V_SEGMENTS: usize = 1000;
1613 const FIELD_SIZE: i32 = 200;
1614
1615 let mut rng = StdRng::seed_from_u64(42);
1617
1618 let mut router = RouterNGBuilder::default().build();
1619
1620 for _ in 0..NUM_H_SEGMENTS {
1622 let y = rng.random_range(0..FIELD_SIZE);
1623 let x1 = rng.random_range(0..FIELD_SIZE);
1624 let x2 = rng.random_range(0..FIELD_SIZE);
1625 let (start, end) = if x1 <= x2 { (x1, x2) } else { (x2, x1) };
1626 if start < end {
1628 router.add_horiz_segment(y, start, end, rng.random_range(0.1..10.0));
1629 }
1630 }
1631
1632 for _ in 0..NUM_V_SEGMENTS {
1634 let x = rng.random_range(0..FIELD_SIZE);
1635 let y1 = rng.random_range(0..FIELD_SIZE);
1636 let y2 = rng.random_range(0..FIELD_SIZE);
1637 let (start, end) = if y1 <= y2 { (y1, y2) } else { (y2, y1) };
1638 if start < end {
1640 router.add_vert_segment(x, start, end, rng.random_range(0.1..10.0));
1641 }
1642 }
1643
1644 let normalize_start = std::time::Instant::now();
1646 router.update();
1647 let normalize_time = normalize_start.elapsed();
1648
1649 println!("Normalization took: {normalize_time:?}");
1650 println!(
1651 "Normalized to {} horizontal segments and {} vertical segments",
1652 router
1653 .h_segments
1654 .values()
1655 .map(std::vec::Vec::len)
1656 .sum::<usize>(),
1657 router
1658 .v_segments
1659 .values()
1660 .map(std::vec::Vec::len)
1661 .sum::<usize>()
1662 );
1663
1664 let line_sweep_start = std::time::Instant::now();
1666 let line_sweep_intersections =
1667 collect_intersections(router.iter_hsegs(), router.iter_vsegs());
1668 let line_sweep_time = line_sweep_start.elapsed();
1669
1670 println!("Line-sweep algorithm took: {line_sweep_time:?}");
1671 println!("Found {} intersections", line_sweep_intersections.len());
1672
1673 let brute_force_start = std::time::Instant::now();
1675 let brute_force_intersections: BTreeSet<Point> =
1676 collect_intersections_brute_force(router.iter_hsegs(), router.iter_vsegs())
1677 .into_iter()
1678 .collect();
1679 let brute_force_time = brute_force_start.elapsed();
1680
1681 println!("Brute-force algorithm took: {brute_force_time:?}");
1682
1683 let speedup = brute_force_time.as_secs_f64() / line_sweep_time.as_secs_f64();
1684 println!("Line-sweep is {speedup:.2}x faster than brute-force");
1685
1686 assert_eq!(
1688 line_sweep_intersections.len(),
1689 brute_force_intersections.len(),
1690 "Number of intersections differs: line-sweep found {}, brute-force found {}",
1691 line_sweep_intersections.len(),
1692 brute_force_intersections.len()
1693 );
1694
1695 assert_eq!(
1696 line_sweep_intersections, brute_force_intersections,
1697 "Intersection sets differ between line-sweep and brute-force algorithms"
1698 );
1699 }
1700
1701 #[test]
1702 fn incoming_direction_prevents_double_back_out_of_waypoint() {
1703 let mut router = RouterNGBuilder::default().build();
1708 router.seed_channels(point(0, 0), 0.0);
1709 router.seed_channels(point(10, -10), 0.0);
1710
1711 let Leg {
1715 path,
1716 outgoing,
1717 resolution,
1718 } = router.path_find_with_fallback(point(0, 0), point(10, -10), Some(Direction::South));
1719 assert_eq!(resolution, Resolution::Routed, "the lattice holds a path");
1720
1721 assert!(path.len() >= 2, "expected a real path, got {path:?}");
1722 assert!(
1723 path[1].x > path[0].x,
1724 "router doubled back instead of turning east first: {path:?}"
1725 );
1726 assert_eq!(outgoing, Some(Direction::North));
1729 }
1730}
1731
1732#[cfg(test)]
1733mod fallback {
1734 use super::*;
1735
1736 #[test]
1739 fn a_leg_with_no_path_is_marked_as_a_fallback() {
1740 let mut router = RouterNGBuilder::default().build();
1741 router.seed_channels(point(0, 0), 0.0);
1742 let unseeded = point(10, -10);
1743 assert!(
1744 !router.node_to_index.contains_key(&unseeded),
1745 "precondition: the target is no node of the lattice"
1746 );
1747 let leg = router.path_find_with_fallback(point(0, 0), unseeded, None);
1748 assert_eq!(leg.resolution, Resolution::Fallback);
1749 assert_eq!(leg.path, vec![point(0, 0), point(10, 0), unseeded]);
1750 }
1751}
1752
1753#[cfg(test)]
1754mod foreground_replay {
1755 use super::*;
1756
1757 fn blocks() -> [(Point, Point); 3] {
1758 [
1759 (point(0, 0), point(8, 8)),
1760 (point(24, 0), point(32, 8)),
1761 (point(12, 20), point(20, 28)),
1762 ]
1763 }
1764
1765 fn whole(raised: &[(Point, Point)], seeds: &[Point]) -> ClosedRouter {
1768 let mut builder = RouterNGBuilder::default();
1769 for (tl, br) in blocks() {
1770 builder.add_block(tl, br);
1771 }
1772 for &(tl, br) in raised {
1773 builder.add_block(tl, br);
1774 }
1775 for &p in seeds {
1776 builder.add_seed_point(p);
1777 }
1778 builder.build_closed()
1779 }
1780
1781 fn background_plus_foreground(raised: &[(Point, Point)], seeds: &[Point]) -> ClosedRouter {
1783 let mut builder = RouterNGBuilder::default();
1784 for (tl, br) in blocks() {
1785 builder.add_block(tl, br);
1786 }
1787 let background = builder.build_closed();
1788 background.clone().extended(|opening| {
1789 for &(tl, br) in raised {
1790 opening.add_block(tl, br);
1791 }
1792 for &p in seeds {
1793 opening.add_seed_point(p);
1794 }
1795 })
1796 }
1797
1798 #[test]
1802 fn a_replayed_foreground_is_the_same_lattice_as_a_whole_build() {
1803 let raised = [(point(40, 12), point(48, 20))];
1804 let seeds = [point(10, 4), point(36, 16), point(44, 30)];
1805
1806 let whole = whole(&raised, &seeds);
1807 let replayed = background_plus_foreground(&raised, &seeds);
1808
1809 let (a, b) = (whole.fingerprint(), replayed.fingerprint());
1810 assert_eq!(a, b, "{}", a.difference(&b));
1811 }
1812
1813 #[test]
1816 fn replaying_seed_points_alone_is_the_same_lattice() {
1817 let seeds = [point(10, 4), point(36, 16)];
1818 let whole = whole(&[], &seeds);
1819 let replayed = background_plus_foreground(&[], &seeds);
1820
1821 let (a, b) = (whole.fingerprint(), replayed.fingerprint());
1822 assert_eq!(a, b, "{}", a.difference(&b));
1823 }
1824
1825 #[test]
1829 fn a_replayed_foreground_routes_the_same_as_a_whole_build() {
1830 let raised = [(point(40, 12), point(48, 20))];
1831 let seeds = [point(10, 4), point(36, 16), point(44, 30)];
1832 let mut whole = whole(&raised, &seeds);
1833 let mut replayed = background_plus_foreground(&raised, &seeds);
1834
1835 for (from, to) in [
1836 (point(10, 4), point(44, 30)),
1837 (point(36, 16), point(10, 4)),
1838 (point(44, 30), point(36, 16)),
1839 ] {
1840 let a = whole.route_leg(from, to, None).path;
1841 let b = replayed.route_leg(from, to, None).path;
1842 assert!(!a.is_empty(), "{from:?}→{to:?} found no path to compare");
1843 assert_eq!(a, b, "{from:?}→{to:?} routed differently");
1844 }
1845 }
1846}
1847
1848#[cfg(test)]
1849mod bounded {
1850 use super::*;
1851
1852 #[test]
1856 fn a_bounded_lattice_holds_nothing_outside_its_bounds() {
1857 let bounds = Bounds::between(point(0, 0), point(40, 40));
1858 let mut builder = RouterNGBuilder::default().within(bounds);
1859 builder.add_block(point(8, 8), point(16, 16));
1860 builder.add_block(point(200, 200), point(208, 208));
1861 builder.add_seed_point(point(4, 4));
1862 builder.add_seed_point(point(300, 300));
1863 let router = builder.build_closed();
1864
1865 let nodes = router.fingerprint();
1866 assert!(!nodes.points().is_empty(), "the bounded lattice is empty");
1867 for &node in nodes.points() {
1868 assert!(
1869 bounds.holds(node),
1870 "{node:?} escaped the bounds it was built within"
1871 );
1872 }
1873 }
1874
1875 #[test]
1878 fn a_bounded_lattice_is_what_an_unbounded_one_holds_inside_those_bounds() {
1879 let bounds = Bounds::between(point(0, 0), point(40, 40));
1880 let seed = point(4, 4);
1881 let block = (point(8, 8), point(16, 16));
1882
1883 let mut bounded = RouterNGBuilder::default().within(bounds);
1884 bounded.add_block(block.0, block.1);
1885 bounded.add_seed_point(seed);
1886 let bounded = bounded.build_closed();
1887
1888 let mut whole = RouterNGBuilder::default();
1889 whole.add_block(block.0, block.1);
1890 whole.add_seed_point(seed);
1891 let whole = whole.build_closed();
1892
1893 let inside: BTreeSet<Point> = whole
1894 .fingerprint()
1895 .points()
1896 .iter()
1897 .copied()
1898 .filter(|&p| bounds.holds(p))
1899 .collect();
1900 let held: BTreeSet<Point> = bounded.fingerprint().points().iter().copied().collect();
1901 assert!(
1902 inside.difference(&held).next().is_none(),
1903 "the bounded lattice is missing nodes the unbounded one has inside the bounds: {:?}",
1904 inside.difference(&held).take(4).collect::<Vec<_>>()
1905 );
1906 }
1907}