refactor(autorouter/ratsnest): Move unionfind to new file, don't use `.graph()` there

This commit is contained in:
Mikolaj Wielgus 2025-09-14 01:40:39 +02:00
parent e0cfc521ef
commit 71fdec2f06
6 changed files with 75 additions and 49 deletions

View File

@ -21,6 +21,7 @@ allowed_scopes = [
"autorouter/autoroute", "autorouter/autoroute",
"autorouter/autorouter", "autorouter/autorouter",
"autorouter/compare_detours", "autorouter/compare_detours",
"autorouter/conncomps",
"autorouter/execution", "autorouter/execution",
"autorouter/history", "autorouter/history",
"autorouter/invoker", "autorouter/invoker",

View File

@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2025 Topola contributors
//
// SPDX-License-Identifier: MIT
use derive_getters::Getters;
use petgraph::unionfind::UnionFind;
use specctra_core::rules::AccessRules;
use crate::{
drawing::{graph::PrimitiveIndex, primitive::GetJoints},
graph::GetIndex,
layout::Layout,
};
#[derive(Clone, Getters)]
pub struct Conncomps {
unionfind: UnionFind<usize>,
}
impl Conncomps {
pub fn new(layout: &Layout<impl AccessRules>) -> Self {
let mut unionfind = UnionFind::new(layout.drawing().geometry().dot_index_bound());
for primitive in layout.drawing().primitive_nodes() {
match primitive {
PrimitiveIndex::FixedSeg(seg) => {
let joints = layout.drawing().primitive(seg).joints();
unionfind.union(joints.0.index(), joints.1.index());
}
PrimitiveIndex::LoneLooseSeg(seg) => {
let joints = layout.drawing().primitive(seg).joints();
unionfind.union(joints.0.index(), joints.1.index());
}
PrimitiveIndex::SeqLooseSeg(seg) => {
let joints = layout.drawing().primitive(seg).joints();
unionfind.union(joints.0.index(), joints.1.index());
}
PrimitiveIndex::FixedBend(bend) => {
let joints = layout.drawing().primitive(bend).joints();
unionfind.union(joints.0.index(), joints.1.index());
}
PrimitiveIndex::LooseBend(bend) => {
let joints = layout.drawing().primitive(bend).joints();
unionfind.union(joints.0.index(), joints.1.index());
}
_ => (),
}
}
Self { unionfind }
}
}

View File

@ -5,6 +5,7 @@
pub mod autoroute; pub mod autoroute;
mod autorouter; mod autorouter;
pub mod compare_detours; pub mod compare_detours;
pub mod conncomps;
pub mod execution; pub mod execution;
pub mod history; pub mod history;
pub mod invoker; pub mod invoker;

View File

@ -6,15 +6,11 @@ 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, prelude::StableUnGraph, visit::NodeIndexable};
data::Element,
prelude::StableUnGraph,
unionfind::UnionFind,
visit::{EdgeRef, IntoEdgeReferences, NodeIndexable},
};
use spade::{HasPosition, InsertionError, Point2}; use spade::{HasPosition, InsertionError, Point2};
use crate::{ use crate::{
autorouter::conncomps::Conncomps,
drawing::{ drawing::{
band::BandTermsegIndex, band::BandTermsegIndex,
dot::FixedDotIndex, dot::FixedDotIndex,
@ -74,11 +70,7 @@ pub struct Ratsnest {
impl Ratsnest { impl Ratsnest {
pub fn new(layout: &Layout<impl AccessRules>) -> Result<Self, InsertionError> { pub fn new(layout: &Layout<impl AccessRules>) -> Result<Self, InsertionError> {
let mut unionfind = UnionFind::new(layout.drawing().geometry().graph().node_bound()); let conncomps = Conncomps::new(layout);
for edge in layout.drawing().geometry().graph().edge_references() {
unionfind.union(edge.source().index(), edge.target().index());
}
let mut this = Self { let mut this = Self {
graph: StableUnGraph::default(), graph: StableUnGraph::default(),
@ -145,7 +137,7 @@ impl Ratsnest {
if let Some((source, target)) = g.edge_endpoints(i) { if let Some((source, target)) = g.edge_endpoints(i) {
let source_index = g.node_weight(source).unwrap().node_index().index(); let source_index = g.node_weight(source).unwrap().node_index().index();
let target_index = g.node_weight(target).unwrap().node_index().index(); let target_index = g.node_weight(target).unwrap().node_index().index();
!unionfind.equiv(source_index, target_index) !conncomps.unionfind().equiv(source_index, target_index)
} else { } else {
true true
} }

View File

@ -7,7 +7,7 @@ use enum_dispatch::enum_dispatch;
use geo::Point; use geo::Point;
use petgraph::{ use petgraph::{
stable_graph::StableDiGraph, stable_graph::StableDiGraph,
visit::{EdgeRef, Walker}, visit::{EdgeRef, NodeIndexable, Walker},
Direction::{Incoming, Outgoing}, Direction::{Incoming, Outgoing},
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -518,6 +518,18 @@ impl<
pub fn bend_joints(&self, bend: BI) -> (DI, DI) { pub fn bend_joints(&self, bend: BI) -> (DI, DI) {
self.joints(bend.into()) self.joints(bend.into())
} }
pub fn dot_index_bound(&self) -> usize {
self.graph.node_bound()
}
pub fn seg_index_bound(&self) -> usize {
self.graph.node_bound()
}
pub fn bend_index_bound(&self) -> usize {
self.graph.node_bound()
}
} }
impl<PW: Copy + Retag<Index = PI>, DW, SW, BW, CW, Cel, PI, DI, SI, BI> impl<PW: Copy + Retag<Index = PI>, DW, SW, BW, CW, Cel, PI, DI, SI, BI>

View File

@ -4,9 +4,9 @@
use std::{fs::File, io::BufReader}; use std::{fs::File, io::BufReader};
use petgraph::{unionfind::UnionFind, visit::NodeIndexable};
use topola::{ use topola::{
autorouter::{ autorouter::{
conncomps::Conncomps,
history::{History, HistoryError}, history::{History, HistoryError},
invoker::{Invoker, InvokerError}, invoker::{Invoker, InvokerError},
Autorouter, Autorouter,
@ -160,7 +160,7 @@ pub fn assert_single_layer_groundless_autoroute(
autorouter: &mut Autorouter<impl AccessMesadata>, autorouter: &mut Autorouter<impl AccessMesadata>,
layername: &str, layername: &str,
) { ) {
let unionfind = unionfind(autorouter); let conncomps = Conncomps::new(autorouter.board().layout());
for ratline in autorouter.ratsnest().graph().edge_indices() { for ratline in autorouter.ratsnest().graph().edge_indices() {
let (origin_dot, destination_dot) = ratline.ref_(autorouter).endpoint_dots(); let (origin_dot, destination_dot) = ratline.ref_(autorouter).endpoint_dots();
@ -217,8 +217,8 @@ pub fn assert_single_layer_groundless_autoroute(
if let Some(netname) = autorouter.board().layout().rules().net_netname(net) { if let Some(netname) = autorouter.board().layout().rules().net_netname(net) {
// We don't route ground. // We don't route ground.
let org = unionfind.find(origin_dot.index()); let org = conncomps.unionfind().find(origin_dot.index());
let desc = unionfind.find(destination_dot.index()); let desc = conncomps.unionfind().find(destination_dot.index());
if netname != "GND" { if netname != "GND" {
assert_eq!(org, desc); assert_eq!(org, desc);
@ -256,35 +256,3 @@ pub fn assert_band_length(
rel_err rel_err
); );
} }
fn unionfind(autorouter: &mut Autorouter<impl AccessMesadata>) -> UnionFind<usize> {
for ratline in autorouter.ratsnest().graph().edge_indices() {
// Accessing endpoints may create new dots because apex construction is lazy, so we access
// tem all before starting unionfind, as it requires a constant index bound.
let _ = ratline.ref_(autorouter).endpoint_dots();
}
let mut unionfind = UnionFind::new(
autorouter
.board()
.layout()
.drawing()
.geometry()
.graph()
.node_bound(),
);
for primitive in autorouter.board().layout().drawing().primitive_nodes() {
for joined in autorouter
.board()
.layout()
.drawing()
.geometry()
.joineds(primitive)
{
unionfind.union(primitive.index(), joined.index());
}
}
unionfind
}