From da37fbe2ece838c41019f0f210fd70fb92873994 Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Fri, 19 Sep 2025 13:05:31 +0200 Subject: [PATCH] feat(board/mod): Make it possible to iterate over nodes under pin name --- committed.toml | 2 + src/bimapset.rs | 100 +++++++++++++++++++++++++++ src/board/mod.rs | 57 ++++++++------- src/drawing/drawing.rs | 6 +- src/geometry/recording_with_rtree.rs | 5 +- src/geometry/with_rtree.rs | 6 +- src/lib.rs | 1 + 7 files changed, 144 insertions(+), 33 deletions(-) create mode 100644 src/bimapset.rs diff --git a/committed.toml b/committed.toml index b4b0942..88cc23f 100644 --- a/committed.toml +++ b/committed.toml @@ -35,6 +35,8 @@ allowed_scopes = [ "autorouter/ratline", "autorouter/remove_bands", "autorouter/selection", + "board/mod", + "board/edit", "drawing/band", "drawing/bend", "drawing/cane", diff --git a/src/bimapset.rs b/src/bimapset.rs new file mode 100644 index 0000000..3d25c1c --- /dev/null +++ b/src/bimapset.rs @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2025 Topola contributors +// +// SPDX-License-Identifier: MIT + +use std::{ + borrow::Borrow, + collections::{BTreeMap, BTreeSet}, +}; + +/// A bidirectional map between keys and sets of values. +/// +/// - Each key can have multiple associated values (`BTreeSet`). +/// - Each value maps to exactly one key (i.e., it's unique across keys). +#[derive(Debug)] +pub struct BiBTreeMapSet { + key_to_values: BTreeMap>, // Forward mapping: key -> set of values. + value_to_key: BTreeMap, // Reverse mapping: value -> key. +} + +impl BiBTreeMapSet { + /// Creates a new, empty `BiBTreeMapSet`. + pub fn new() -> Self { + Self { + key_to_values: BTreeMap::new(), + value_to_key: BTreeMap::new(), + } + } + + /// Inserts a (key, value) pair. + /// + /// If the value was previously associated with a different key, + /// it is removed from the old key and reassigned to the new one. + pub fn insert(&mut self, key: K, value: V) { + // Insert the new value-to-key mapping, capturing the old key if it existed. + if let Some(old_key) = self.value_to_key.insert(value.clone(), key.clone()) { + // Remove value from the old key's set of values. + if let Some(set) = self.key_to_values.get_mut(&old_key) { + set.remove(&value); + // Clean up the old key if its set becomes empty. + if set.is_empty() { + self.key_to_values.remove(&old_key); + } + } + } + + // Insert value into the new key's set of values. + self.key_to_values + .entry(key) + .or_insert_with(BTreeSet::new) + .insert(value); + } + + /// Gets the set of values associated with the given key. + pub fn get_values(&self, key: &Q) -> Option<&BTreeSet> + where + K: Borrow, + { + self.key_to_values.get(key) + } + + /// Gets the key associated with the given value. + pub fn get_key(&self, value: &V) -> Option<&K> { + self.value_to_key.get(value) + } + + /// Removes a key from the forward map and all its associated values from + /// the reverse map. + pub fn remove_by_key(&mut self, key: &Q) -> Option> + where + K: Borrow, + { + if let Some(values) = self.key_to_values.remove(key) { + // Remove each value from the reverse map. + for v in &values { + self.value_to_key.remove(v); + } + Some(values) + } else { + None + } + } + + /// Removes a value from the reverse map and the key from the forward map if + /// it no longer has any values. + pub fn remove_by_value(&mut self, value: &V) -> Option { + if let Some(k) = self.value_to_key.remove(value) { + // Remove the value from the key's value set. + if let Some(set) = self.key_to_values.get_mut(&k) { + set.remove(value); + // Remove the key if it no longer has any values. + if set.is_empty() { + self.key_to_values.remove(&k); + } + } + Some(k) + } else { + None + } + } +} diff --git a/src/board/mod.rs b/src/board/mod.rs index e89540a..c3dc11a 100644 --- a/src/board/mod.rs +++ b/src/board/mod.rs @@ -13,9 +13,9 @@ pub use specctra_core::mesadata::AccessMesadata; use bimap::BiBTreeMap; use derive_getters::Getters; use geo::Point; -use std::collections::BTreeMap; use crate::{ + bimapset::BiBTreeMapSet, drawing::{ band::BandUid, dot::{FixedDotIndex, FixedDotWeight}, @@ -82,7 +82,7 @@ pub struct Board { bands_by_id: BiBTreeMap, // TODO: Simplify access logic to these members so that `#[getter(skip)]`s can be removed. #[getter(skip)] - node_to_pinname: BTreeMap, + pinname_nodes: BiBTreeMapSet, #[getter(skip)] band_bandname: BiBTreeMap, } @@ -93,7 +93,7 @@ impl Board { Self { layout, bands_by_id: BiBTreeMap::new(), - node_to_pinname: BTreeMap::new(), + pinname_nodes: BiBTreeMapSet::new(), band_bandname: BiBTreeMap::new(), } } @@ -119,8 +119,8 @@ impl Board { .add_fixed_dot_infringably(&mut recorder.layout_edit, weight); if let Some(pin) = maybe_pin { - self.node_to_pinname - .insert(GenericNode::Primitive(dot.into()), pin); + self.pinname_nodes + .insert(pin, GenericNode::Primitive(dot.into())); } dot @@ -142,8 +142,8 @@ impl Board { .add_fixed_seg_infringably(&mut recorder.layout_edit, from, to, weight); if let Some(pin) = maybe_pin { - self.node_to_pinname - .insert(GenericNode::Primitive(seg.into()), pin); + self.pinname_nodes + .insert(pin, GenericNode::Primitive(seg.into())); } seg @@ -166,34 +166,43 @@ impl Board { if let Some(pin) = maybe_pin { for i in nodes { - self.node_to_pinname - .insert(GenericNode::Primitive(*i), pin.clone()); + self.pinname_nodes + .insert(pin.clone(), GenericNode::Primitive(*i)); } - self.node_to_pinname - .insert(GenericNode::Primitive(apex.into()), pin.clone()); + self.pinname_nodes + .insert(pin.clone(), GenericNode::Primitive(apex.into())); - self.node_to_pinname - .insert(GenericNode::Compound(poly.into()), pin); + self.pinname_nodes + .insert(pin, GenericNode::Compound(poly.into())); } poly } - /// Returns the pin name associated with a given node. - pub fn node_pinname(&self, node: &NodeIndex) -> Option<&String> { - self.node_to_pinname.get(node) + /// Returns an iterator over the set of all nodes associated with a given + /// pin name. + pub fn pinname_nodes(&self, pinname: &str) -> impl Iterator + '_ { + self.pinname_nodes + .get_values(pinname) + .into_iter() + .flat_map(|set| set.iter().map(|node| *node)) } - /// Returns the apex belonging to a given pin, if any - /// - /// Warning: this is very slow. + /// Returns the pin name associated with a given node. + pub fn node_pinname(&self, node: &NodeIndex) -> Option<&String> { + self.pinname_nodes.get_key(node) + } + + /// Returns the apex belonging to a given pin, if there is any. pub fn pin_apex(&self, pin: &str, layer: usize) -> Option<(FixedDotIndex, Point)> { - self.node_to_pinname - .iter() - .filter(|(_, node_pin)| *node_pin == pin) - // this should only ever return one result - .find_map(|(node, _)| self.layout().apex_of_compoundless_node(*node, layer)) + self.pinname_nodes + .get_values(pin) + .map(|node| { + node.iter() + .find_map(|node| self.layout().apex_of_compoundless_node(*node, layer)) + }) + .flatten() } /// Returns the band name associated with a given band. diff --git a/src/drawing/drawing.rs b/src/drawing/drawing.rs index e905b4c..5c0a1f4 100644 --- a/src/drawing/drawing.rs +++ b/src/drawing/drawing.rs @@ -34,10 +34,10 @@ use crate::{ primitive::PrimitiveShape, recording_with_rtree::RecordingGeometryWithRtree, with_rtree::BboxedIndex, - AccessBendWeight, AccessDotWeight, AccessSegWeight, GenericNode, Geometry, GeometryLabel, - GetLayer, GetOffset, GetSetPos, GetWidth, + AccessBendWeight, AccessDotWeight, AccessSegWeight, GenericNode, Geometry, GetLayer, + GetOffset, GetSetPos, GetWidth, }, - graph::{GenericIndex, GetIndex, MakeRef}, + graph::{GenericIndex, MakeRef}, math::{NoBitangents, RotationSense}, }; diff --git a/src/geometry/recording_with_rtree.rs b/src/geometry/recording_with_rtree.rs index a53635e..b61b170 100644 --- a/src/geometry/recording_with_rtree.rs +++ b/src/geometry/recording_with_rtree.rs @@ -5,7 +5,6 @@ use std::collections::btree_map::Entry as BTreeMapEntry; use geo::Point; -use petgraph::stable_graph::StableDiGraph; use rstar::RTree; use crate::graph::{GenericIndex, GetIndex}; @@ -14,8 +13,8 @@ use super::{ compound::ManageCompounds, edit::{ApplyGeometryEdit, GeometryEdit}, with_rtree::{BboxedIndex, GeometryWithRtree}, - AccessBendWeight, AccessDotWeight, AccessSegWeight, GenericNode, Geometry, GeometryLabel, - GetLayer, GetWidth, Retag, + AccessBendWeight, AccessDotWeight, AccessSegWeight, GenericNode, Geometry, GetLayer, GetWidth, + Retag, }; #[derive(Debug)] diff --git a/src/geometry/with_rtree.rs b/src/geometry/with_rtree.rs index deadb9f..bcf060d 100644 --- a/src/geometry/with_rtree.rs +++ b/src/geometry/with_rtree.rs @@ -5,15 +5,15 @@ use contracts_try::debug_invariant; use derive_getters::Getters; use geo::Point; -use petgraph::{stable_graph::StableDiGraph, visit::Walker}; +use petgraph::visit::Walker; use rstar::{primitives::GeomWithData, Envelope, RTree, RTreeObject, AABB}; use crate::{ geometry::{ compound::ManageCompounds, primitive::{AccessPrimitiveShape, PrimitiveShape}, - AccessBendWeight, AccessDotWeight, AccessSegWeight, GenericNode, Geometry, GeometryLabel, - GetLayer, GetWidth, Retag, + AccessBendWeight, AccessDotWeight, AccessSegWeight, GenericNode, Geometry, GetLayer, + GetWidth, Retag, }, graph::{GenericIndex, GetIndex}, }; diff --git a/src/lib.rs b/src/lib.rs index 0e4789e..e6913a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,7 @@ pub mod graph; #[macro_use] pub mod drawing; pub mod autorouter; +pub mod bimapset; pub mod board; pub mod geometry; pub mod interactor;