From 2f4b016a8b673a49fe476e9b7b8a55b08acccf95 Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Fri, 7 Nov 2025 02:25:58 +0100 Subject: [PATCH] fix(autorouter/planar_reconfigurer): Use heap-based best-first search for ratline permutations This fixes an infinite loop bug. --- src/autorouter/mod.rs | 1 + src/autorouter/permsearch.rs | 106 ++++++++++++++++++++++++++ src/autorouter/planar_reconfigurer.rs | 20 +++-- src/autorouter/scc.rs | 8 ++ 4 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 src/autorouter/permsearch.rs diff --git a/src/autorouter/mod.rs b/src/autorouter/mod.rs index c12113e..1954aac 100644 --- a/src/autorouter/mod.rs +++ b/src/autorouter/mod.rs @@ -14,6 +14,7 @@ pub mod multilayer_autoroute; pub mod multilayer_preconfigurer; pub mod multilayer_reconfigurator; pub mod multilayer_reconfigurer; +pub mod permsearch; pub mod place_via; pub mod planar_autoroute; pub mod planar_preconfigurer; diff --git a/src/autorouter/permsearch.rs b/src/autorouter/permsearch.rs new file mode 100644 index 0000000..481dc4d --- /dev/null +++ b/src/autorouter/permsearch.rs @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: 2025 Topola contributors +// +// SPDX-License-Identifier: MIT + +use std::{cmp::Ordering, collections::BinaryHeap, iter::Skip, iter::Take}; + +use itertools::{Itertools, Permutations}; + +#[derive(Clone, Debug)] +struct PermsearchNode { + curr_permutation: Vec, + permutations: Skip>>>, + length: usize, +} + +impl PartialEq for PermsearchNode { + fn eq(&self, other: &Self) -> bool { + self.curr_permutation == other.curr_permutation + } +} + +impl Eq for PermsearchNode {} + +impl Ord for PermsearchNode { + fn cmp(&self, other: &Self) -> Ordering { + self.length.cmp(&other.length) + } +} + +impl PartialOrd for PermsearchNode { + fn partial_cmp(&self, other: &Self) -> Option { + self.length.partial_cmp(&other.length) + } +} + +impl PermsearchNode { + fn permute(mut self) -> Option { + for (i, element) in self.permutations.next()?.iter().enumerate() { + self.curr_permutation[i] = element.clone(); + } + + Some(self) + } + + fn resize(self, length: usize) -> Option { + if length == self.length { + return None; + } + + // TODO: Get rid of `self.curr_permutation` clone somehow? + + Some(Self { + curr_permutation: self.curr_permutation.clone(), + permutations: self + .curr_permutation + .into_iter() + .take(length) + .permutations(length) + .skip(1), + length, + }) + } +} + +pub struct Permsearch { + curr_node: PermsearchNode, + frontier: BinaryHeap>, +} + +impl Permsearch { + pub fn new(original: Vec) -> Self { + let len = original.len(); + + // TODO: Get rid of `original` clone somehow? + + Self { + curr_node: PermsearchNode { + curr_permutation: original.clone(), + permutations: original.into_iter().take(len).permutations(0).skip(0), + length: 0, + }, + frontier: BinaryHeap::new(), + } + } + + pub fn step(&mut self, len: usize) -> Option<&[T]> { + // TODO: Get rid of `self.curr_node` clones somehow? + + if let Some(resized_curr_node) = self.curr_node.clone().resize(len) { + if let Some(permuted_resized_curr_node) = resized_curr_node.permute() { + self.frontier.push(permuted_resized_curr_node); + } + } + + if let Some(permuted_curr_node) = self.curr_node.clone().permute() { + self.frontier.push(permuted_curr_node); + } + + self.curr_node = self.frontier.pop()?; + Some(&self.curr_node.curr_permutation) + } + + pub fn curr_permutation(&self) -> &[T] { + &self.curr_node.curr_permutation + } +} diff --git a/src/autorouter/planar_reconfigurer.rs b/src/autorouter/planar_reconfigurer.rs index 7ff318e..b420553 100644 --- a/src/autorouter/planar_reconfigurer.rs +++ b/src/autorouter/planar_reconfigurer.rs @@ -9,6 +9,7 @@ use itertools::{Itertools, Permutations}; use specctra_core::mesadata::AccessMesadata; use crate::autorouter::{ + permsearch::Permsearch, planar_autoroute::{PlanarAutorouteConfiguration, PlanarAutorouteExecutionStepper}, planar_preconfigurer::SccIntersectionsAndLengthRatlinePlanarAutoroutePreconfigurer, scc::Scc, @@ -52,7 +53,7 @@ impl PlanarAutorouteReconfigurer { } pub struct SccPermutationsPlanarAutorouteReconfigurer { - sccs_permutations_iter: Skip>>, + sccs_permsearch: Permsearch, preconfiguration: PlanarAutorouteConfiguration, } @@ -66,10 +67,9 @@ impl SccPermutationsPlanarAutorouteReconfigurer { // TODO: Instead of instantiating presorter again here, get it from // an argument. let sccs = presorter.dissolve(); - let sccs_len = sccs.len(); Self { - sccs_permutations_iter: sccs.into_iter().permutations(sccs_len).skip(1), + sccs_permsearch: Permsearch::new(sccs), preconfiguration, } } @@ -79,9 +79,19 @@ impl MakeNextPlanarAutorouteConfiguration for SccPermutationsPlanarAutorouteReco fn next_configuration( &mut self, autorouter: &Autorouter, - _stepper: &PlanarAutorouteExecutionStepper, + stepper: &PlanarAutorouteExecutionStepper, ) -> Option { - let scc_permutation = self.sccs_permutations_iter.next()?; + let scc_index = self + .sccs_permsearch + .curr_permutation() + .iter() + .position(|scc| { + scc.scc_ref(autorouter) + .contains(self.preconfiguration.ratlines[*stepper.curr_ratline_index()]) + }) + .unwrap(); + + let scc_permutation = self.sccs_permsearch.step(scc_index + 1)?; let mut ratlines = vec![]; for scc in scc_permutation { diff --git a/src/autorouter/scc.rs b/src/autorouter/scc.rs index c7e176e..153f86a 100644 --- a/src/autorouter/scc.rs +++ b/src/autorouter/scc.rs @@ -26,6 +26,14 @@ pub struct Scc { length: f64, } +impl PartialEq for Scc { + fn eq(&self, other: &Self) -> bool { + self.node_indices == other.node_indices + } +} + +impl Eq for Scc {} + impl Scc { pub fn new( autorouter: &mut Autorouter,