feat(autorouter/permutator): Preorder and permutate over whole ratline conn. comps

This is not enough to autoroute 4x4_1206_led_matrix_breakout, but is as
usual, is a step forward.
This commit is contained in:
Mikolaj Wielgus 2025-09-06 01:24:00 +02:00
parent 19c6ede09a
commit 4326925bbf
4 changed files with 180 additions and 54 deletions

View File

@ -91,7 +91,7 @@ impl AutorouteExecutionStepper {
autorouter: &mut Autorouter<impl AccessMesadata>, autorouter: &mut Autorouter<impl AccessMesadata>,
index: usize, index: usize,
) -> Result<(), AutorouterError> { ) -> Result<(), AutorouterError> {
if index > self.board_data_edits.len() { if index >= self.board_data_edits.len() {
return Err(AutorouterError::NothingToUndoForPermutation); return Err(AutorouterError::NothingToUndoForPermutation);
} }
@ -232,11 +232,13 @@ impl<M: AccessMesadata> Permutate<Autorouter<M>> for AutorouteExecutionStepper {
autorouter: &mut Autorouter<M>, autorouter: &mut Autorouter<M>,
permutation: Vec<RatlineIndex>, permutation: Vec<RatlineIndex>,
) -> Result<(), AutorouterError> { ) -> Result<(), AutorouterError> {
let new_index = permutation let Some(new_index) = permutation
.iter() .iter()
.zip(self.ratlines.iter()) .zip(self.ratlines.iter())
.position(|(permuted, original)| *permuted != *original) .position(|(permuted, original)| *permuted != *original)
.unwrap(); else {
return Err(AutorouterError::NothingToUndoForPermutation);
};
self.ratlines = permutation; self.ratlines = permutation;
self.backtrace_to_index(autorouter, new_index)?; self.backtrace_to_index(autorouter, new_index)?;

View File

@ -7,18 +7,16 @@ use geo::Point;
use petgraph::graph::NodeIndex; use petgraph::graph::NodeIndex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use spade::InsertionError; use spade::InsertionError;
use std::{cmp::Ordering, collections::BTreeSet}; use std::collections::BTreeSet;
use thiserror::Error; use thiserror::Error;
use crate::{ use crate::{
autorouter::permutator::AutorouteExecutionPermutator, autorouter::permutator::AutorouteExecutionPermutator,
board::{AccessMesadata, Board}, board::{AccessMesadata, Board},
drawing::{band::BandTermsegIndex, Infringement}, drawing::{band::BandTermsegIndex, Infringement},
geometry::shape::MeasureLength,
graph::MakeRef, graph::MakeRef,
layout::{via::ViaWeight, LayoutEdit}, layout::{via::ViaWeight, LayoutEdit},
router::{navmesh::NavmeshError, ng, thetastar::ThetastarError, RouterOptions}, router::{navmesh::NavmeshError, ng, thetastar::ThetastarError, RouterOptions},
stepper::Step,
triangulation::GetTrianvertexNodeIndex, triangulation::GetTrianvertexNodeIndex,
}; };

View File

@ -2,9 +2,10 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use std::{cmp::Ordering, iter::Skip, ops::ControlFlow}; use std::{cmp::Ordering, ops::ControlFlow};
use itertools::{Itertools, Permutations}; use itertools::{Itertools, Permutations};
use petgraph::{algo::tarjan_scc, graph::NodeIndex};
use specctra_core::mesadata::AccessMesadata; use specctra_core::mesadata::AccessMesadata;
use crate::{ use crate::{
@ -12,7 +13,7 @@ use crate::{
autoroute::{AutorouteContinueStatus, AutorouteExecutionStepper}, autoroute::{AutorouteContinueStatus, AutorouteExecutionStepper},
invoker::GetDebugOverlayData, invoker::GetDebugOverlayData,
ratline::RatlineIndex, ratline::RatlineIndex,
Autorouter, AutorouterError, AutorouterOptions, PresortBy, Autorouter, AutorouterError, AutorouterOptions,
}, },
board::edit::BoardEdit, board::edit::BoardEdit,
drawing::graph::PrimitiveIndex, drawing::graph::PrimitiveIndex,
@ -22,54 +23,183 @@ use crate::{
stepper::{Abort, EstimateProgress, Permutate, Step}, stepper::{Abort, EstimateProgress, Permutate, Step},
}; };
pub struct AutorouteExecutionPermutator { struct RatlineSccPermuter {
stepper: AutorouteExecutionStepper, sccs_permutations_iter: Permutations<std::vec::IntoIter<Vec<NodeIndex<usize>>>>,
permutations_iter: Skip<Permutations<std::vec::IntoIter<RatlineIndex>>>, ratlines: Vec<RatlineIndex>,
options: AutorouterOptions,
} }
impl AutorouteExecutionPermutator { impl RatlineSccPermuter {
pub fn new( pub fn new(
autorouter: &mut Autorouter<impl AccessMesadata>, autorouter: &mut Autorouter<impl AccessMesadata>,
mut ratlines: Vec<RatlineIndex>, ratlines: Vec<RatlineIndex>,
options: AutorouterOptions, _options: &AutorouterOptions,
) -> Result<Self, AutorouterError> { ) -> Self {
let ratlines_len = ratlines.len(); // FIXME: Unnecessary copy.
let mut filtered_ratsnest = autorouter.ratsnest().graph().clone();
filtered_ratsnest.retain_edges(|_g, i| ratlines.contains(&i));
match options.presort_by { let mut sccs = tarjan_scc(&filtered_ratsnest);
PresortBy::RatlineIntersectionCountAndLength => ratlines.sort_unstable_by(|a, b| { let sccs_len = sccs.len();
let a_intersector_count = a.ref_(autorouter).interior_obstacle_ratlines().count();
let b_intersector_count = b.ref_(autorouter).interior_obstacle_ratlines().count(); sccs.sort_unstable_by(|a, b| {
// FIXME: These calculations should probably be stored somewhere
// instead of being done every time.
let mut a_intersector_count = 0;
let mut b_intersector_count = 0;
let mut a_length = 0.0;
let mut b_length = 0.0;
// FIXME: It's inefficient to iterate over the ratlines on every
// sort comparison. But this is the simplest solution I arrived
// at after realizing that `.tarjan_scc(...)` does not sort nodes
// inside components.
for ratline in ratlines.iter() {
if a.contains(&filtered_ratsnest.edge_endpoints(*ratline).unwrap().0)
&& a.contains(&filtered_ratsnest.edge_endpoints(*ratline).unwrap().1)
{
a_length += ratline.ref_(autorouter).length();
a_intersector_count += ratline
.ref_(autorouter)
.interior_obstacle_ratlines()
.count();
}
}
for ratline in ratlines.iter() {
if b.contains(&filtered_ratsnest.edge_endpoints(*ratline).unwrap().0)
&& b.contains(&filtered_ratsnest.edge_endpoints(*ratline).unwrap().1)
{
b_length += ratline.ref_(autorouter).length();
b_intersector_count += ratline
.ref_(autorouter)
.interior_obstacle_ratlines()
.count();
}
}
let primary_ordering = a_intersector_count.cmp(&b_intersector_count); let primary_ordering = a_intersector_count.cmp(&b_intersector_count);
if primary_ordering != Ordering::Equal { if primary_ordering != Ordering::Equal {
primary_ordering primary_ordering
} else { } else {
let a_length = a.ref_(autorouter).length();
let b_length = b.ref_(autorouter).length();
let secondary_ordering = a_length.total_cmp(&b_length); let secondary_ordering = a_length.total_cmp(&b_length);
secondary_ordering secondary_ordering
} }
}),
PresortBy::PairwiseDetours => ratlines.sort_unstable_by(|a, b| {
let mut compare_detours = autorouter
.compare_detours_ratlines(*a, *b, options)
.unwrap();
if let Ok((al, bl)) = compare_detours.finish(autorouter) { // Below is how I tried to do this before I realized that
PartialOrd::partial_cmp(&al, &bl).unwrap() // `.tarjan_scc(...)` does not sort nodes inside components.
/*let a_intersector_count: usize = a
.windows(2)
.map(|window| {
let ratline = filtered_ratsnest.find_edge(window[0], window[1]).unwrap();
ratline
.ref_(autorouter)
.interior_obstacle_ratlines()
.count()
})
.sum();
let b_intersector_count: usize = b
.windows(2)
.map(|window| {
let ratline = filtered_ratsnest.find_edge(window[0], window[1]).unwrap();
ratline
.ref_(autorouter)
.interior_obstacle_ratlines()
.count()
})
.sum();
let primary_ordering = a_intersector_count.cmp(&b_intersector_count);
if primary_ordering != Ordering::Equal {
primary_ordering
} else { } else {
Ordering::Equal let a_length: f64 = a
.windows(2)
.map(|window| {
let ratline = filtered_ratsnest.find_edge(window[0], window[1]).unwrap();
ratline.ref_(autorouter).length()
})
.sum();
let b_length: f64 = b
.windows(2)
.map(|window| {
let ratline = filtered_ratsnest.find_edge(window[0], window[1]).unwrap();
ratline.ref_(autorouter).length()
})
.sum();
let secondary_ordering = a_length.total_cmp(&b_length);
secondary_ordering
}*/
});
Self {
sccs_permutations_iter: sccs.into_iter().permutations(sccs_len),
ratlines,
} }
}),
} }
pub fn next_permutation(
&mut self,
autorouter: &mut Autorouter<impl AccessMesadata>,
) -> Option<Vec<RatlineIndex>> {
let scc_permutation = self.sccs_permutations_iter.next()?;
let mut ratlines = vec![];
for scc in scc_permutation {
for ratline in self.ratlines.iter() {
if scc.contains(
&autorouter
.ratsnest()
.graph()
.edge_endpoints(*ratline)
.unwrap()
.0,
) && scc.contains(
&autorouter
.ratsnest()
.graph()
.edge_endpoints(*ratline)
.unwrap()
.1,
) {
ratlines.push(*ratline);
}
}
}
Some(ratlines)
}
}
pub struct AutorouteExecutionPermutator {
stepper: AutorouteExecutionStepper,
permuter: RatlineSccPermuter,
options: AutorouterOptions,
}
impl AutorouteExecutionPermutator {
pub fn new(
autorouter: &mut Autorouter<impl AccessMesadata>,
ratlines: Vec<RatlineIndex>,
options: AutorouterOptions,
) -> Result<Self, AutorouterError> {
let mut permuter = RatlineSccPermuter::new(autorouter, ratlines, &options);
let initially_sorted_ratlines = permuter.next_permutation(autorouter).unwrap();
Ok(Self { Ok(Self {
stepper: AutorouteExecutionStepper::new(autorouter, ratlines.clone(), options)?, stepper: AutorouteExecutionStepper::new(
autorouter,
initially_sorted_ratlines,
options,
)?,
// Note: I assume here that the first permutation is the same as the original order. // Note: I assume here that the first permutation is the same as the original order.
permutations_iter: ratlines.into_iter().permutations(ratlines_len).skip(1), permuter,
options, options,
}) })
} }
@ -92,7 +222,7 @@ impl<M: AccessMesadata> Step<Autorouter<M>, Option<BoardEdit>, AutorouteContinue
} }
loop { loop {
let Some(permutation) = self.permutations_iter.next() else { let Some(permutation) = self.permuter.next_permutation(autorouter) else {
return Ok(ControlFlow::Break(None)); return Ok(ControlFlow::Break(None));
}; };
@ -111,7 +241,7 @@ impl<M: AccessMesadata> Step<Autorouter<M>, Option<BoardEdit>, AutorouteContinue
impl<M: AccessMesadata> Abort<Autorouter<M>> for AutorouteExecutionPermutator { impl<M: AccessMesadata> Abort<Autorouter<M>> for AutorouteExecutionPermutator {
fn abort(&mut self, autorouter: &mut Autorouter<M>) { fn abort(&mut self, autorouter: &mut Autorouter<M>) {
self.permutations_iter.all(|_| true); //self.permutations_iter.all(|_| true); // Why did I add this code here???
self.stepper.abort(autorouter); self.stepper.abort(autorouter);
} }
} }

View File

@ -2,18 +2,14 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
//! Defines data structures and methods for managing a graph
//! used in layout triangulation and routing tasks. It includes vertex and edge
//! structures for representing graph nodes and edges with associated metadata,
//! as well as functions for constructing and manipulating these graphs.
use std::collections::BTreeMap; use std::collections::BTreeMap;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use geo::Point; use geo::Point;
use petgraph::{ use petgraph::{
data::Element, data::Element,
graph::{NodeIndex, UnGraph}, graph::NodeIndex,
prelude::StableUnGraph,
unionfind::UnionFind, unionfind::UnionFind,
visit::{EdgeRef, IntoEdgeReferences, NodeIndexable}, visit::{EdgeRef, IntoEdgeReferences, NodeIndexable},
}; };
@ -74,7 +70,7 @@ impl HasPosition for RatvertexWeight {
} }
pub struct Ratsnest { pub struct Ratsnest {
graph: UnGraph<RatvertexWeight, RatlineWeight, usize>, graph: StableUnGraph<RatvertexWeight, RatlineWeight, usize>,
} }
impl Ratsnest { impl Ratsnest {
@ -86,7 +82,7 @@ impl Ratsnest {
} }
let mut this = Self { let mut this = Self {
graph: UnGraph::default(), graph: StableUnGraph::default(),
}; };
let mut triangulations = BTreeMap::new(); let mut triangulations = BTreeMap::new();
@ -166,7 +162,7 @@ impl Ratsnest {
self.graph.edge_weight_mut(ratline).unwrap().band_termseg = Some(termseg); self.graph.edge_weight_mut(ratline).unwrap().band_termseg = Some(termseg);
} }
pub fn graph(&self) -> &UnGraph<RatvertexWeight, RatlineWeight, usize> { pub fn graph(&self) -> &StableUnGraph<RatvertexWeight, RatlineWeight, usize> {
&self.graph &self.graph
} }
} }