feat(autorouter/autoroute): Implement basic brute-force permutator

Permutates if and until a solution is found. Does not do optimization
so far.

Thanks to this, it is now possible to route the DE-9 to DE-9 test
without having to find the correct sequence of autorouting jobs.
This commit is contained in:
Mikolaj Wielgus 2025-07-18 23:28:22 +02:00
parent f09aa053b6
commit d181c7df1b
5 changed files with 137 additions and 54 deletions

View File

@ -53,6 +53,7 @@ contracts-try = "0.7"
derive-getters.workspace = true
enum_dispatch = "0.3"
geo.workspace = true
itertools = "0.14"
log.workspace = true
petgraph.workspace = true
ron = "0.10"

View File

@ -5,6 +5,7 @@
//! Manages autorouting of ratlines in a layout, tracking status and processed
//! routing steps.
use itertools::{Itertools, Permutations};
use std::ops::ControlFlow;
use crate::{
@ -33,7 +34,7 @@ pub enum AutorouteContinueStatus {
Running,
/// A specific segment has been successfully routed.
Routed(BandTermsegIndex),
/// A specific segment was already routed and has been skipped.
/// A specific segment had been already routed and has been skipped.
Skipped(BandTermsegIndex),
}
@ -103,6 +104,8 @@ impl<M: AccessMesadata> Step<Autorouter<M>, Option<BoardEdit>, AutorouteContinue
&mut self,
autorouter: &mut Autorouter<M>,
) -> Result<ControlFlow<Option<BoardEdit>, AutorouteContinueStatus>, AutorouterError> {
// TODO: Use a proper state machine here for better readability?
if self.curr_ratline_index >= self.ratlines.len() {
let recorder = self.dissolve_route_stepper_into_layout_edit();
return Ok(ControlFlow::Break(Some(BoardEdit::new_from_edits(
@ -214,3 +217,91 @@ impl GetDebugOverlayData for AutorouteExecutionStepper {
self.route.as_ref().map_or(&[], |route| route.obstacles())
}
}
pub struct AutorouteExecutionPermutator {
stepper: AutorouteExecutionStepper,
permutations_iter: Permutations<std::vec::IntoIter<RatlineIndex>>,
options: AutorouterOptions,
}
impl AutorouteExecutionPermutator {
pub fn new(
autorouter: &mut Autorouter<impl AccessMesadata>,
ratlines: Vec<RatlineIndex>,
options: AutorouterOptions,
) -> Result<Self, AutorouterError> {
let ratlines_len = ratlines.len();
Ok(Self {
stepper: AutorouteExecutionStepper::new(autorouter, ratlines.clone(), options)?,
permutations_iter: ratlines.into_iter().permutations(ratlines_len),
options,
})
}
}
impl<M: AccessMesadata> Step<Autorouter<M>, Option<BoardEdit>, AutorouteContinueStatus>
for AutorouteExecutionPermutator
{
type Error = AutorouterError;
fn step(
&mut self,
autorouter: &mut Autorouter<M>,
) -> Result<ControlFlow<Option<BoardEdit>, AutorouteContinueStatus>, AutorouterError> {
match self.stepper.step(autorouter) {
Ok(ok) => Ok(ok),
Err(..) => {
self.stepper.abort(autorouter);
let Some(new_permutation) = self.permutations_iter.next() else {
return Ok(ControlFlow::Break(None));
};
self.stepper =
AutorouteExecutionStepper::new(autorouter, new_permutation, self.options)?;
self.stepper.step(autorouter)
}
}
}
}
impl<M: AccessMesadata> Abort<Autorouter<M>> for AutorouteExecutionPermutator {
fn abort(&mut self, autorouter: &mut Autorouter<M>) {
self.permutations_iter.all(|_| true);
self.stepper.abort(autorouter);
}
}
impl EstimateProgress for AutorouteExecutionPermutator {
type Value = f64;
fn estimate_progress_value(&self) -> f64 {
// TODO.
self.stepper.estimate_progress_value()
}
fn estimate_progress_maximum(&self) -> f64 {
// TODO.
self.stepper.estimate_progress_maximum()
}
}
impl GetDebugOverlayData for AutorouteExecutionPermutator {
fn maybe_thetastar(&self) -> Option<&ThetastarStepper<Navmesh, f64>> {
self.stepper.maybe_thetastar()
}
fn maybe_navcord(&self) -> Option<&Navcord> {
self.stepper.maybe_navcord()
}
fn ghosts(&self) -> &[PrimitiveShape] {
self.stepper.ghosts()
}
fn obstacles(&self) -> &[PrimitiveIndex] {
self.stepper.obstacles()
}
}

View File

@ -7,20 +7,22 @@ use geo::Point;
use petgraph::graph::NodeIndex;
use serde::{Deserialize, Serialize};
use spade::InsertionError;
use std::collections::BTreeSet;
use std::{cmp::Ordering, collections::BTreeSet};
use thiserror::Error;
use crate::{
board::{AccessMesadata, Board},
drawing::{band::BandTermsegIndex, Infringement},
geometry::shape::MeasureLength,
graph::MakeRef,
layout::{via::ViaWeight, LayoutEdit},
router::{navmesh::NavmeshError, ng, thetastar::ThetastarError, RouterOptions},
stepper::Step,
triangulation::GetTrianvertexNodeIndex,
};
use super::{
autoroute::AutorouteExecutionStepper,
autoroute::{AutorouteExecutionPermutator, AutorouteExecutionStepper},
compare_detours::CompareDetoursExecutionStepper,
measure_length::MeasureLengthExecutionStepper,
place_via::PlaceViaExecutionStepper,
@ -105,8 +107,38 @@ impl<M: AccessMesadata> Autorouter<M> {
&mut self,
selection: &PinSelection,
options: AutorouterOptions,
) -> Result<AutorouteExecutionStepper, AutorouterError> {
self.autoroute_ratlines(self.selected_ratlines(selection), options)
) -> Result<AutorouteExecutionPermutator, AutorouterError> {
let mut ratlines = self.selected_ratlines(selection);
match options.presort_by {
PresortBy::RatlineIntersectionCountAndLength => ratlines.sort_unstable_by(|a, b| {
let a_intersector_count = a.ref_(self).find_intersecting_ratlines().count();
let b_intersector_count = b.ref_(self).find_intersecting_ratlines().count();
let primary_ordering = a_intersector_count.cmp(&b_intersector_count);
if primary_ordering != Ordering::Equal {
primary_ordering
} else {
let a_length = a.ref_(self).length();
let b_length = b.ref_(self).length();
let secondary_ordering = a_length.total_cmp(&b_length);
secondary_ordering
}
}),
PresortBy::PairwiseDetours => ratlines.sort_unstable_by(|a, b| {
let mut compare_detours = self.compare_detours_ratlines(*a, *b, options).unwrap();
if let Ok((al, bl)) = compare_detours.finish(self) {
PartialOrd::partial_cmp(&al, &bl).unwrap()
} else {
Ordering::Equal
}
}),
}
AutorouteExecutionPermutator::new(self, ratlines, options)
}
pub(super) fn autoroute_ratlines(

View File

@ -15,7 +15,7 @@ use crate::{
};
use super::{
autoroute::AutorouteExecutionStepper,
autoroute::AutorouteExecutionPermutator,
compare_detours::CompareDetoursExecutionStepper,
invoker::{GetDebugOverlayData, Invoker, InvokerError},
measure_length::MeasureLengthExecutionStepper,
@ -45,7 +45,7 @@ pub enum Command {
#[enum_dispatch(GetDebugOverlayData)]
pub enum ExecutionStepper<M> {
Autoroute(AutorouteExecutionStepper),
Autoroute(AutorouteExecutionPermutator),
TopoAutoroute(ng::AutorouteExecutionStepper<M>),
PlaceVia(PlaceViaExecutionStepper),
RemoveBands(RemoveBandsExecutionStepper),

View File

@ -4,7 +4,7 @@
//! Manages the execution of routing commands within the autorouting system.
use std::{cmp::Ordering, ops::ControlFlow};
use std::ops::ControlFlow;
use contracts_try::debug_requires;
use derive_getters::{Dissolve, Getters};
@ -15,8 +15,8 @@ use thiserror::Error;
use crate::{
board::AccessMesadata,
drawing::graph::PrimitiveIndex,
geometry::{primitive::PrimitiveShape, shape::MeasureLength},
graph::{GenericIndex, MakeRef},
geometry::primitive::PrimitiveShape,
graph::GenericIndex,
layout::poly::PolyWeight,
router::{
navcord::Navcord,
@ -28,14 +28,14 @@ use crate::{
};
use super::{
autoroute::AutorouteExecutionStepper,
autoroute::AutorouteExecutionPermutator,
compare_detours::CompareDetoursExecutionStepper,
execution::{Command, ExecutionStepper},
history::{History, HistoryError},
measure_length::MeasureLengthExecutionStepper,
place_via::PlaceViaExecutionStepper,
remove_bands::RemoveBandsExecutionStepper,
Autorouter, AutorouterError, PresortBy,
Autorouter, AutorouterError,
};
/// Trait for getting the information to display on the debug overlay,
@ -167,48 +167,7 @@ impl<M: AccessMesadata + Clone> Invoker<M> {
fn dispatch_command(&mut self, command: &Command) -> Result<ExecutionStepper<M>, InvokerError> {
Ok(match command {
Command::Autoroute(selection, options) => {
let mut ratlines = self.autorouter.selected_ratlines(selection);
match options.presort_by {
PresortBy::RatlineIntersectionCountAndLength => {
ratlines.sort_unstable_by(|a, b| {
let a_intersector_count = a
.ref_(self.autorouter())
.find_intersecting_ratlines()
.count();
let b_intersector_count = b
.ref_(self.autorouter())
.find_intersecting_ratlines()
.count();
let primary_ordering = a_intersector_count.cmp(&b_intersector_count);
if primary_ordering != Ordering::Equal {
primary_ordering
} else {
let a_length = a.ref_(self.autorouter()).length();
let b_length = b.ref_(self.autorouter()).length();
let secondary_ordering = a_length.total_cmp(&b_length);
secondary_ordering
}
})
}
PresortBy::PairwiseDetours => ratlines.sort_unstable_by(|a, b| {
let mut compare_detours = self
.autorouter
.compare_detours_ratlines(*a, *b, *options)
.unwrap();
if let Ok((al, bl)) = compare_detours.finish(&mut self.autorouter) {
PartialOrd::partial_cmp(&al, &bl).unwrap()
} else {
Ordering::Equal
}
}),
}
ExecutionStepper::Autoroute(self.autorouter.autoroute_ratlines(ratlines, *options)?)
ExecutionStepper::Autoroute(self.autorouter.autoroute(selection, *options)?)
}
Command::TopoAutoroute {
selection,