Compare commits

...

3 Commits

Author SHA1 Message Date
Mikolaj Wielgus a9040d0bd3 test: Add automatic test routine for tht_db25_to_tht_db25 test 2025-11-17 01:27:08 +01:00
Mikolaj Wielgus 50db79ebd3 feat(router/navmesh): Reduce number of navedges even further 2025-11-17 00:40:57 +01:00
Mikolaj Wielgus 1694227135 feat(router/navmesh): Reduce number of navedges
This provides a speedup, but may break squeezing through under bends in
some corner cases.
2025-11-16 23:38:00 +01:00
7 changed files with 607 additions and 105 deletions

View File

@ -2,10 +2,7 @@
//
// SPDX-License-Identifier: MIT
use std::iter::Skip;
use enum_dispatch::enum_dispatch;
use itertools::{Itertools, Permutations};
use specctra_core::mesadata::AccessMesadata;
use crate::autorouter::{

View File

@ -10,6 +10,7 @@ use crate::{
drawing::{
graph::{GetMaybeNet, MakePrimitiveRef},
primitive::MakePrimitiveShape,
seg::SeqLooseSegIndex,
Collision, Infringement,
},
geometry::{
@ -105,17 +106,24 @@ impl<CW: Clone, Cel: Copy, R: AccessRules> Drawing<CW, Cel, R> {
v.push(joints.0.into());
v.push(joints.1.into());
if let Some(seg0) = self.primitive(joints.0).seg() {
v.push(seg0.into());
}
if let Some(seg1) = self.primitive(joints.1).seg() {
v.push(seg1.into());
}
v.extend(
self.collect_bend_bow_segs(bend)
.map(Into::<PrimitiveIndex>::into),
);
v
}
pub fn collect_bend_bow_segs(
&self,
bend: LooseBendIndex,
) -> impl Iterator<Item = SeqLooseSegIndex> {
let joints = self.primitive(bend).joints();
self.primitive(joints.0)
.seg()
.into_iter()
.chain(self.primitive(joints.1).seg().into_iter())
}
pub fn cut(
&self,
line: Line,

View File

@ -249,6 +249,7 @@ impl Navmesh {
for prenavedge in prenavmesh.triangulation().edge_references() {
Self::add_prenavedge_to_repr_as_quadrinavedges(
layout,
&mut graph,
&prenavnode_to_navnodes,
&overlapping_prenavnodes_unions,
@ -266,6 +267,7 @@ impl Navmesh {
// quadrinavedges exist for every one of them.
for constraint in prenavmesh.constraints() {
Self::add_prenavedge_to_repr_as_quadrinavedges(
layout,
&mut graph,
&prenavnode_to_navnodes,
&overlapping_prenavnodes_unions,
@ -364,26 +366,27 @@ impl Navmesh {
PrenavmeshNodeIndex,
Vec<(NodeIndex<usize>, NodeIndex<usize>)>,
>,
trianvertex: PrenavmeshNodeIndex,
node: BinavnodeNodeIndex,
prenavnode: PrenavmeshNodeIndex,
binavnode: BinavnodeNodeIndex,
) {
let navnode1 = graph.add_node(NavnodeWeight {
binavnode: node,
binavnode,
maybe_sense: Some(RotationSense::Counterclockwise),
});
let navnode2 = graph.add_node(NavnodeWeight {
binavnode: node,
binavnode,
maybe_sense: Some(RotationSense::Clockwise),
});
prenavnode_to_navnodes
.get_mut(&trianvertex)
.get_mut(&prenavnode)
.unwrap()
.push((navnode1, navnode2));
}
fn add_prenavedge_to_repr_as_quadrinavedges(
layout: &Layout<impl AccessRules>,
graph: &mut UnGraph<NavnodeWeight, (), usize>,
prenavnode_to_navnodes: &BTreeMap<
PrenavmeshNodeIndex,
@ -403,6 +406,7 @@ impl Navmesh {
));
Self::add_prenavedge_as_quadrinavedges(
layout,
graph,
prenavnode_to_navnodes,
from_prenavnode_repr,
@ -411,6 +415,7 @@ impl Navmesh {
}
fn add_prenavedge_as_quadrinavedges(
layout: &Layout<impl AccessRules>,
graph: &mut UnGraph<NavnodeWeight, (), usize>,
prenavnode_to_navnodes: &BTreeMap<
PrenavmeshNodeIndex,
@ -419,16 +424,83 @@ impl Navmesh {
from_prenavnode: PrenavmeshNodeIndex,
to_prenavnode: PrenavmeshNodeIndex,
) {
for (from_navnode1, from_navnode2) in prenavnode_to_navnodes[&from_prenavnode].iter() {
for (to_navnode1, to_navnode2) in prenavnode_to_navnodes[&to_prenavnode].iter() {
let mut maybe_lowest_join: Option<(usize, usize)> = None;
for (from_index, (from_navnode1, from_navnode2)) in prenavnode_to_navnodes[&from_prenavnode]
.iter()
.rev()
.enumerate()
{
let from_binavnode = graph.node_weight(*from_navnode1).unwrap().binavnode;
// If binavnodes are found to be joined, only add a single binavedge
// between them.
if let Some((to_index, (to_navnode1, to_navnode2))) = prenavnode_to_navnodes
[&to_prenavnode]
.iter()
.rev()
.enumerate()
.find(|(_, (to_navnode1, _))| {
let to_binavnode = graph.node_weight(*to_navnode1).unwrap().binavnode;
Self::are_binavnodes_joined(layout, from_binavnode, to_binavnode)
})
{
// Add binavedge.
graph.update_edge(*from_navnode1, *to_navnode1, ());
graph.update_edge(*from_navnode2, *to_navnode2, ());
maybe_lowest_join = Some((from_index, to_index));
continue;
}
for (to_index, (to_navnode1, to_navnode2)) in prenavnode_to_navnodes[&to_prenavnode]
.iter()
.rev()
.enumerate()
{
// Don't create navedges that would cross through and thus
// infringe joins of navnodes outwards of them.
if maybe_lowest_join.is_some_and(|lowest_join| {
from_index >= lowest_join.0 || to_index >= lowest_join.1
}) {
continue;
}
// Add binavedge.
graph.update_edge(*from_navnode1, *to_navnode1, ());
graph.update_edge(*from_navnode2, *to_navnode2, ());
// Add two more navedges to upgrade the binavedge to form a
// quadrinavedge.
graph.update_edge(*from_navnode1, *to_navnode2, ());
graph.update_edge(*from_navnode2, *to_navnode1, ());
graph.update_edge(*from_navnode2, *to_navnode2, ());
}
}
}
fn are_binavnodes_joined(
layout: &Layout<impl AccessRules>,
from_binavnode: BinavnodeNodeIndex,
to_binavnode: BinavnodeNodeIndex,
) -> bool {
let BinavnodeNodeIndex::LooseBend(from_bend) = from_binavnode else {
return false;
};
let BinavnodeNodeIndex::LooseBend(to_bend) = to_binavnode else {
return false;
};
let from_bend_bow_segs: Vec<_> =
layout.drawing().collect_bend_bow_segs(from_bend).collect();
let to_bend_bow_segs: Vec<_> = layout.drawing().collect_bend_bow_segs(to_bend).collect();
from_bend_bow_segs
.iter()
.any(|seg| to_bend_bow_segs.contains(seg))
}
/// Returns the origin node.
pub fn origin(&self) -> FixedDotIndex {
self.origin

View File

@ -16,7 +16,7 @@ use crate::{
geometry::primitive::PrimitiveShape,
graph::GenericIndex,
layout::{poly::PolyWeight, Layout},
stepper::{Abort, EstimateProgress},
stepper::Abort,
};
use super::{

View File

@ -77,6 +77,13 @@ impl<
> 0)
}
pub fn exists_constraint(&self, from: I, to: I) -> bool {
self.cdt.exists_constraint(
self.trianvertex_to_handle[from].unwrap(),
self.trianvertex_to_handle[to].unwrap(),
)
}
pub fn intersects_constraint(&self, from: &VW, to: &VW) -> bool {
self.cdt
.intersects_constraint(from.position(), to.position())

View File

@ -22,6 +22,42 @@ mod common;
#[case::with_undo_redo_replay("with_undo_redo_replay")]
fn test_master(#[case] variant: &str) {}
#[apply(test_master)]
fn autoroute_0603_breakout(variant: &str) {
let mut autorouter = common::load_design("tests/unilayer/0603_breakout/0603_breakout.dsn");
common::assert_layer_0_navnode_count(&mut autorouter, "R1-2", "J1-2", 22);
let mut invoker = common::create_invoker_and_assert(autorouter);
common::replay_and_assert_and_report(
&mut invoker,
"tests/unilayer/0603_breakout/autoroute_all.cmd",
variant,
);
let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
//common::assert_number_of_conncomps(&mut autorouter, 2);
}
#[apply(test_master)]
fn autoroute_4x_3rd_order_smd_lc_filters(variant: &str) {
let mut autorouter = common::load_design(
"tests/unilayer/4x_3rd_order_smd_lc_filters/4x_3rd_order_smd_lc_filters.dsn",
);
common::assert_layer_0_navnode_count(&mut autorouter, "J1-1", "L1-1", 558);
let mut invoker = common::create_invoker_and_assert(autorouter);
common::replay_and_assert_and_report(
&mut invoker,
"tests/unilayer/4x_3rd_order_smd_lc_filters/autoroute_signals.cmd",
variant,
);
let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
//common::assert_number_of_conncomps(&mut autorouter, 16);
}
#[apply(test_master)]
fn autoroute_4x4_1206_led_matrix_breakout(variant: &str) {
let autorouter = common::load_design(
@ -54,6 +90,42 @@ fn autoroute_4x4_1206_led_matrix_breakout_in_predefined_order(variant: &str) {
common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
}
#[apply(test_master)]
fn autoroute_smd_non_rectangular_buck_converter(variant: &str) {
let path =
"tests/unilayer/smd_non_rectangular_buck_converter/smd_non_rectangular_buck_converter.dsn";
let autorouter = common::load_design(&path);
let mut invoker = common::create_invoker_and_assert(autorouter);
common::replay_and_assert_and_report(
&mut invoker,
"tests/unilayer/smd_non_rectangular_buck_converter/route_all.cmd",
variant,
);
let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
//common::assert_number_of_conncomps(&mut autorouter, 16);
}
#[apply(test_master)]
fn autoroute_tht_db25_to_tht_db25(variant: &str) {
let autorouter =
common::load_design("tests/unilayer/tht_db25_to_tht_db25/tht_db25_to_tht_db25.dsn");
let mut invoker = common::create_invoker_and_assert(autorouter);
common::undo_all_and_assert(&mut invoker);
common::replay_and_assert_and_report(
&mut invoker,
"tests/unilayer/tht_db25_to_tht_db25/autoroute_all.cmd",
variant,
);
let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
}
#[apply(test_master)]
fn autoroute_tht_de9_to_tht_de9(variant: &str) {
let autorouter =
@ -85,23 +157,6 @@ fn autoroute_tht_de9_to_tht_de9_in_predefined_order(variant: &str) {
common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
}
#[apply(test_master)]
fn autoroute_0603_breakout(variant: &str) {
let mut autorouter = common::load_design("tests/unilayer/0603_breakout/0603_breakout.dsn");
common::assert_layer_0_navnode_count(&mut autorouter, "R1-2", "J1-2", 22);
let mut invoker = common::create_invoker_and_assert(autorouter);
common::replay_and_assert_and_report(
&mut invoker,
"tests/unilayer/0603_breakout/autoroute_all.cmd",
variant,
);
let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
//common::assert_number_of_conncomps(&mut autorouter, 2);
}
#[apply(test_master)]
fn autoroute_tht_diode_bridge_rectifier(variant: &str) {
let mut autorouter = common::load_design(
@ -140,47 +195,23 @@ fn autoroute_tht_diode_bridge_rectifier(variant: &str) {
}
#[apply(test_master)]
fn autoroute_4x_3rd_order_smd_lc_filters(variant: &str) {
let mut autorouter = common::load_design(
"tests/unilayer/4x_3rd_order_smd_lc_filters/4x_3rd_order_smd_lc_filters.dsn",
);
common::assert_layer_0_navnode_count(&mut autorouter, "J1-1", "L1-1", 558);
fn autoroute_triangle_problem(variant: &str) {
let path = "tests/unilayer/triangle_problem/triangle_problem.dsn";
let autorouter = common::load_design(&path);
let mut invoker = common::create_invoker_and_assert(autorouter);
common::replay_and_assert_and_report(
&mut invoker,
"tests/unilayer/4x_3rd_order_smd_lc_filters/autoroute_signals.cmd",
"tests/unilayer/triangle_problem/route_all.cmd",
variant,
);
let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
//common::assert_number_of_conncomps(&mut autorouter, 16);
}
// FIXME: This test fails indeterministically.
// NOTE: Disabled until determinism is fixed.
//#[test]
/*#[allow(unused)]
#[case("")]
#[case("with_undo_redo_replay")]
fn test_tht_3pin_xlr_to_tht_3pin_xlr(#[case] variant: &str) {
let mut autorouter = common::load_design(
"tests/unilayer/tht_3pin_xlr_to_tht_3pin_xlr/tht_3pin_xlr_to_tht_3pin_xlr.dsn",
);
//common::assert_navnode_count(&mut autorouter, "R1-2", "J1-2", ?);
let mut invoker = common::create_invoker_and_assert(autorouter);
common::replay_and_assert_and_report(
&mut invoker,
"tests/unilayer/tht_3pin_xlr_to_tht_3pin_xlr/autoroute_all.cmd",
"undo_redo_replay",
);
let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_unilayer_groundless_ratlines_are_autorouted(&mut autorouter, "F.Cu");
}*/
#[apply(test_master)]
fn autoroute_vga_dac_breakout(variant: &str) {
let mut autorouter =
@ -197,41 +228,3 @@ fn autoroute_vga_dac_breakout(variant: &str) {
common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
}
#[apply(test_master)]
fn autoroute_smd_non_rectangular_buck_converter(variant: &str) {
let path =
"tests/unilayer/smd_non_rectangular_buck_converter/smd_non_rectangular_buck_converter.dsn";
let autorouter = common::load_design(&path);
let mut invoker = common::create_invoker_and_assert(autorouter);
common::replay_and_assert_and_report(
&mut invoker,
"tests/unilayer/smd_non_rectangular_buck_converter/route_all.cmd",
variant,
);
let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
//common::assert_number_of_conncomps(&mut autorouter, 16);
}
#[apply(test_master)]
fn autoroute_triangle_problem(variant: &str) {
let path = "tests/unilayer/triangle_problem/triangle_problem.dsn";
let autorouter = common::load_design(&path);
let mut invoker = common::create_invoker_and_assert(autorouter);
common::replay_and_assert_and_report(
&mut invoker,
"tests/unilayer/triangle_problem/route_all.cmd",
variant,
);
let (mut autorouter, ..) = invoker.dissolve();
common::assert_that_all_ratlines_besides_gnd_are_autorouted(&mut autorouter);
}

View File

@ -0,0 +1,425 @@
{
"done": [
{
"Autoroute": [
[
{
"pin": "J1-1",
"layer": "B.Cu"
},
{
"pin": "J1-1",
"layer": "F.Cu"
},
{
"pin": "J1-10",
"layer": "B.Cu"
},
{
"pin": "J1-10",
"layer": "F.Cu"
},
{
"pin": "J1-11",
"layer": "B.Cu"
},
{
"pin": "J1-11",
"layer": "F.Cu"
},
{
"pin": "J1-12",
"layer": "B.Cu"
},
{
"pin": "J1-12",
"layer": "F.Cu"
},
{
"pin": "J1-13",
"layer": "B.Cu"
},
{
"pin": "J1-13",
"layer": "F.Cu"
},
{
"pin": "J1-14",
"layer": "B.Cu"
},
{
"pin": "J1-14",
"layer": "F.Cu"
},
{
"pin": "J1-15",
"layer": "B.Cu"
},
{
"pin": "J1-15",
"layer": "F.Cu"
},
{
"pin": "J1-16",
"layer": "B.Cu"
},
{
"pin": "J1-16",
"layer": "F.Cu"
},
{
"pin": "J1-17",
"layer": "B.Cu"
},
{
"pin": "J1-17",
"layer": "F.Cu"
},
{
"pin": "J1-18",
"layer": "B.Cu"
},
{
"pin": "J1-18",
"layer": "F.Cu"
},
{
"pin": "J1-19",
"layer": "B.Cu"
},
{
"pin": "J1-19",
"layer": "F.Cu"
},
{
"pin": "J1-2",
"layer": "B.Cu"
},
{
"pin": "J1-2",
"layer": "F.Cu"
},
{
"pin": "J1-20",
"layer": "B.Cu"
},
{
"pin": "J1-20",
"layer": "F.Cu"
},
{
"pin": "J1-21",
"layer": "B.Cu"
},
{
"pin": "J1-21",
"layer": "F.Cu"
},
{
"pin": "J1-22",
"layer": "B.Cu"
},
{
"pin": "J1-22",
"layer": "F.Cu"
},
{
"pin": "J1-23",
"layer": "B.Cu"
},
{
"pin": "J1-23",
"layer": "F.Cu"
},
{
"pin": "J1-24",
"layer": "B.Cu"
},
{
"pin": "J1-24",
"layer": "F.Cu"
},
{
"pin": "J1-25",
"layer": "B.Cu"
},
{
"pin": "J1-25",
"layer": "F.Cu"
},
{
"pin": "J1-3",
"layer": "B.Cu"
},
{
"pin": "J1-3",
"layer": "F.Cu"
},
{
"pin": "J1-4",
"layer": "B.Cu"
},
{
"pin": "J1-4",
"layer": "F.Cu"
},
{
"pin": "J1-5",
"layer": "B.Cu"
},
{
"pin": "J1-5",
"layer": "F.Cu"
},
{
"pin": "J1-6",
"layer": "B.Cu"
},
{
"pin": "J1-6",
"layer": "F.Cu"
},
{
"pin": "J1-7",
"layer": "B.Cu"
},
{
"pin": "J1-7",
"layer": "F.Cu"
},
{
"pin": "J1-8",
"layer": "B.Cu"
},
{
"pin": "J1-8",
"layer": "F.Cu"
},
{
"pin": "J1-9",
"layer": "B.Cu"
},
{
"pin": "J1-9",
"layer": "F.Cu"
},
{
"pin": "J2-1",
"layer": "B.Cu"
},
{
"pin": "J2-1",
"layer": "F.Cu"
},
{
"pin": "J2-10",
"layer": "B.Cu"
},
{
"pin": "J2-10",
"layer": "F.Cu"
},
{
"pin": "J2-11",
"layer": "B.Cu"
},
{
"pin": "J2-11",
"layer": "F.Cu"
},
{
"pin": "J2-12",
"layer": "B.Cu"
},
{
"pin": "J2-12",
"layer": "F.Cu"
},
{
"pin": "J2-13",
"layer": "B.Cu"
},
{
"pin": "J2-13",
"layer": "F.Cu"
},
{
"pin": "J2-14",
"layer": "B.Cu"
},
{
"pin": "J2-14",
"layer": "F.Cu"
},
{
"pin": "J2-15",
"layer": "B.Cu"
},
{
"pin": "J2-15",
"layer": "F.Cu"
},
{
"pin": "J2-16",
"layer": "B.Cu"
},
{
"pin": "J2-16",
"layer": "F.Cu"
},
{
"pin": "J2-17",
"layer": "B.Cu"
},
{
"pin": "J2-17",
"layer": "F.Cu"
},
{
"pin": "J2-18",
"layer": "B.Cu"
},
{
"pin": "J2-18",
"layer": "F.Cu"
},
{
"pin": "J2-19",
"layer": "B.Cu"
},
{
"pin": "J2-19",
"layer": "F.Cu"
},
{
"pin": "J2-2",
"layer": "B.Cu"
},
{
"pin": "J2-2",
"layer": "F.Cu"
},
{
"pin": "J2-20",
"layer": "B.Cu"
},
{
"pin": "J2-20",
"layer": "F.Cu"
},
{
"pin": "J2-21",
"layer": "B.Cu"
},
{
"pin": "J2-21",
"layer": "F.Cu"
},
{
"pin": "J2-22",
"layer": "B.Cu"
},
{
"pin": "J2-22",
"layer": "F.Cu"
},
{
"pin": "J2-23",
"layer": "B.Cu"
},
{
"pin": "J2-23",
"layer": "F.Cu"
},
{
"pin": "J2-24",
"layer": "B.Cu"
},
{
"pin": "J2-24",
"layer": "F.Cu"
},
{
"pin": "J2-25",
"layer": "B.Cu"
},
{
"pin": "J2-25",
"layer": "F.Cu"
},
{
"pin": "J2-3",
"layer": "B.Cu"
},
{
"pin": "J2-3",
"layer": "F.Cu"
},
{
"pin": "J2-4",
"layer": "B.Cu"
},
{
"pin": "J2-4",
"layer": "F.Cu"
},
{
"pin": "J2-5",
"layer": "B.Cu"
},
{
"pin": "J2-5",
"layer": "F.Cu"
},
{
"pin": "J2-6",
"layer": "B.Cu"
},
{
"pin": "J2-6",
"layer": "F.Cu"
},
{
"pin": "J2-7",
"layer": "B.Cu"
},
{
"pin": "J2-7",
"layer": "F.Cu"
},
{
"pin": "J2-8",
"layer": "B.Cu"
},
{
"pin": "J2-8",
"layer": "F.Cu"
},
{
"pin": "J2-9",
"layer": "B.Cu"
},
{
"pin": "J2-9",
"layer": "F.Cu"
}
],
{
"principal_layer": 0,
"presort_by": "RatlineIntersectionCountAndLength",
"permutate": true,
"router": {
"routed_band_width": 100.0,
"wrap_around_bands": true,
"squeeze_through_under_bends": true
},
"timeout": {
"initial": 1.0,
"progress_bonus": 0.005
}
}
]
}
],
"undone": []
}