Experimental feature: Region#peel to reduce hierarchical load

This commit is contained in:
Matthias Koefferlein 2026-04-12 01:03:37 +02:00
parent b7d18af0e1
commit adb81262a4
12 changed files with 310 additions and 62 deletions

View File

@ -138,6 +138,11 @@ public:
virtual RegionDelegate *add (const Region &other) const;
virtual RegionDelegate *peel (double /*complexity_factor*/) const
{
return const_cast<AsIfFlatRegion *> (this);
}
virtual RegionDelegate *selected_outside (const Region &other) const
{
return selected_interacting_generic (other, 1, false, Positive, size_t (1), std::numeric_limits<size_t>::max ()).first;

View File

@ -880,6 +880,140 @@ DeepRegion::nets (LayoutToNetlist *l2n, NetPropertyMode prop_mode, const tl::Var
return new db::DeepRegion (result);
}
namespace {
/**
* @brief Implements a boolean AND or NOT operation with property handling
*/
class DB_PUBLIC PushHierLocalOperationWithProperties
: public local_operation<db::object_with_properties<db::PolygonRef>, db::object_with_properties<db::PolygonRef>, db::object_with_properties<db::PolygonRef> >
{
public:
PushHierLocalOperationWithProperties (double complexity_factor)
: local_operation<db::object_with_properties<db::PolygonRef>, db::object_with_properties<db::PolygonRef>, db::object_with_properties<db::PolygonRef> > (),
m_complexity_factor (complexity_factor)
{
// .. nothing yet ..
}
OnEmptyIntruderHint on_empty_intruder_hint () const { return Copy; }
std::string description () const
{
return tl::to_string (tr ("'peel' operation"));
}
virtual void do_compute_local (db::Layout *layout, db::Cell * /*subject_cell*/, const shape_interactions<db::object_with_properties<db::PolygonRef>, db::object_with_properties<db::PolygonRef> > &interactions, std::vector<std::unordered_set<db::object_with_properties<db::PolygonRef> > > &results, const db::LocalProcessorBase *proc) const
{
tl_assert (results.size () == 1);
auto &result = results.front ();
db::EdgeProcessor ep;
for (auto i = interactions.begin (); i != interactions.end (); ++i) {
const auto &subject = interactions.subject_shape (i->first);
db::properties_id_type prop_id = subject.properties_id ();
if (i->second.empty ()) {
result.insert (subject);
} else {
ep.clear ();
const auto &subject = interactions.subject_shape (i->first);
for (auto e = subject.begin_edge (); ! e.at_end(); ++e) {
ep.insert (*e, 0);
}
size_t p2 = 1;
for (auto ii = i->second.begin (); ii != i->second.end (); ++ii) {
const auto &intruder = interactions.intruder_shape (*ii);
for (auto e = intruder.second.begin_edge (); ! e.at_end(); ++e) {
ep.insert (*e, p2);
}
p2 += 2;
}
std::unordered_set<db::object_with_properties<db::PolygonRef> > result1;
db::BooleanOp op (db::BooleanOp::ANotB);
db::polygon_ref_generator_with_properties<db::object_with_properties<db::PolygonRef> > pr (layout, result1, prop_id);
db::PolygonSplitter splitter (pr, proc->area_ratio (), proc->max_vertex_count ());
db::PolygonGenerator pg (splitter, true, true);
ep.set_base_verbosity (50);
ep.process (pg, op);
if (result1.empty ()) {
// shortcut: nothing to do
} else if (m_complexity_factor < 0.0) {
// no complexity limit
result.insert (result1.begin (), result1.end ());
} else if (m_complexity_factor == 0.0) {
// only remove shape if it is really entirely covered in this case
result.insert (subject);
} else {
size_t vertices_before = subject.vertices ();
size_t vertices_after = 0;
for (auto r = result1.begin (); r != result1.end (); ++r) {
vertices_after += r->vertices ();
}
if (floor (0.5 + m_complexity_factor * vertices_before) >= vertices_after) {
result.insert (result1.begin (), result1.end ());
} else {
result.insert (subject);
}
}
}
}
}
private:
double m_complexity_factor;
};
}
RegionDelegate *
DeepRegion::peel (double complexity_factor) const
{
if (empty ()) {
// we can return "this", as this method is only intended for in-place execution inside Region
return const_cast<DeepRegion *> (this);
}
DeepLayer dl_out (deep_layer ().derived ());
PushHierLocalOperationWithProperties op (complexity_factor);
db::local_processor<db::PolygonRefWithProperties, db::PolygonRefWithProperties, db::PolygonRefWithProperties> proc (const_cast<db::Layout *> (&deep_layer ().layout ()), const_cast<db::Cell *> (&deep_layer ().initial_cell ()), deep_layer ().breakout_cells ());
configure_proc (proc);
proc.set_threads (deep_layer ().store ()->threads ());
proc.set_area_ratio (deep_layer ().store ()->max_area_ratio ());
proc.set_max_vertex_count (deep_layer ().store ()->max_vertex_count ());
// with this setting, only top-down interactions are considered
proc.set_top_down (true);
proc.run (&op, deep_layer ().layer (), deep_layer ().layer (), dl_out.layer ());
return new DeepRegion (dl_out);
}
RegionDelegate *
DeepRegion::and_with (const Region &other, PropertyConstraint property_constraint) const
{

View File

@ -140,6 +140,8 @@ public:
virtual RegionDelegate *sized_inside (const Region &inside, bool outside, coord_type d, int steps, unsigned int mode) const;
virtual RegionDelegate *sized_inside (const Region &inside, bool outside, coord_type dx, coord_type dy, int steps, unsigned int mode) const;
virtual RegionDelegate *peel (double complexity_factor) const;
virtual void insert_into (Layout *layout, db::cell_index_type into_cell, unsigned int into_layer) const;
virtual RegionDelegate *nets (LayoutToNetlist *l2n, NetPropertyMode prop_mode, const tl::Variant &net_prop_name, const std::vector<const Net *> *nets) const;

View File

@ -111,6 +111,8 @@ public:
virtual RegionDelegate *add_in_place (const Region &other);
virtual RegionDelegate *add (const Region &other) const;
virtual RegionDelegate *peel (double /*complexity_factor*/) const { return new EmptyRegion (); }
virtual RegionDelegate *selected_outside (const Region &) const { return new EmptyRegion (); }
virtual RegionDelegate *selected_not_outside (const Region &) const { return new EmptyRegion (); }
virtual std::pair<RegionDelegate *, RegionDelegate *> selected_outside_pair (const Region &) const { return std::make_pair (new EmptyRegion (), new EmptyRegion ()); }

View File

@ -744,7 +744,7 @@ local_processor_result_computation_task<TS, TI, TR>::perform ()
// LocalProcessorBase implementation
LocalProcessorBase::LocalProcessorBase ()
: m_report_progress (true), m_nthreads (0), m_max_vertex_count (0), m_area_ratio (0.0), m_boolean_core (false),
: m_report_progress (true), m_nthreads (0), m_max_vertex_count (0), m_area_ratio (0.0), m_top_down (false), m_boolean_core (false),
m_base_verbosity (30), mp_vars (0), mp_current_cell (0)
{
// .. nothing yet ..
@ -1034,84 +1034,90 @@ void local_processor<TS, TI, TR>::compute_contexts (local_processor_contexts<TS,
}
}
// TODO: can we shortcut this if interactions is empty?
for (std::vector<unsigned int>::const_iterator il = contexts.intruder_layers ().begin (); il != contexts.intruder_layers ().end (); ++il) {
// in top-down mode we are not interested in cell-to-cell interactions, nor shape-to-instance interactions
// except local ones (shape-to-child cells), hence we skip this part
if (! top_down ()) {
db::box_convert <db::CellInstArray, true> inst_bci (*mp_intruder_layout, contexts.actual_intruder_layer (*il));
// TODO: can we shortcut this if interactions is empty?
for (std::vector<unsigned int>::const_iterator il = contexts.intruder_layers ().begin (); il != contexts.intruder_layers ().end (); ++il) {
db::box_scanner2<db::CellInstArray, int, db::CellInstArray, int> scanner;
interaction_registration_inst2inst<TS, TI, TR> rec (mp_subject_layout, contexts.subject_layer (), mp_intruder_layout, contexts.actual_intruder_layer (*il), contexts.is_foreign (*il), dist, &interactions);
db::box_convert <db::CellInstArray, true> inst_bci (*mp_intruder_layout, contexts.actual_intruder_layer (*il));
unsigned int id = 0;
db::box_scanner2<db::CellInstArray, int, db::CellInstArray, int> scanner;
interaction_registration_inst2inst<TS, TI, TR> rec (mp_subject_layout, contexts.subject_layer (), mp_intruder_layout, contexts.actual_intruder_layer (*il), contexts.is_foreign (*il), dist, &interactions);
if (subject_cell == intruder_cell) {
unsigned int id = 0;
// Use the same id's for same instances - this way we can easily detect same instances
// and don't make them self-interacting
if (subject_cell == intruder_cell) {
for (db::Cell::const_iterator i = subject_cell->begin (); !i.at_end (); ++i) {
unsigned int iid = ++id;
if (! inst_bcs (i->cell_inst ()).empty () && ! subject_cell_is_breakout (i->cell_index ())) {
scanner.insert1 (&i->cell_inst (), iid);
}
if (! inst_bci (i->cell_inst ()).empty () && ! intruder_cell_is_breakout (i->cell_index ())) {
scanner.insert2 (&i->cell_inst (), iid);
}
}
// Use the same id's for same instances - this way we can easily detect same instances
// and don't make them self-interacting
} else {
for (db::Cell::const_iterator i = subject_cell->begin (); !i.at_end (); ++i) {
if (! inst_bcs (i->cell_inst ()).empty () && ! subject_cell_is_breakout (i->cell_index ())) {
scanner.insert1 (&i->cell_inst (), ++id);
}
}
if (intruder_cell) {
for (db::Cell::const_iterator i = intruder_cell->begin (); !i.at_end (); ++i) {
for (db::Cell::const_iterator i = subject_cell->begin (); !i.at_end (); ++i) {
unsigned int iid = ++id;
if (! inst_bcs (i->cell_inst ()).empty () && ! subject_cell_is_breakout (i->cell_index ())) {
scanner.insert1 (&i->cell_inst (), iid);
}
if (! inst_bci (i->cell_inst ()).empty () && ! intruder_cell_is_breakout (i->cell_index ())) {
scanner.insert2 (&i->cell_inst (), ++id);
scanner.insert2 (&i->cell_inst (), iid);
}
}
} else {
for (db::Cell::const_iterator i = subject_cell->begin (); !i.at_end (); ++i) {
if (! inst_bcs (i->cell_inst ()).empty () && ! subject_cell_is_breakout (i->cell_index ())) {
scanner.insert1 (&i->cell_inst (), ++id);
}
}
if (intruder_cell) {
for (db::Cell::const_iterator i = intruder_cell->begin (); !i.at_end (); ++i) {
if (! inst_bci (i->cell_inst ()).empty () && ! intruder_cell_is_breakout (i->cell_index ())) {
scanner.insert2 (&i->cell_inst (), ++id);
}
}
}
}
for (std::set<db::CellInstArray>::const_iterator i = intruders.first.begin (); i != intruders.first.end (); ++i) {
if (! inst_bci (*i).empty ()) {
scanner.insert2 (i.operator-> (), ++id);
}
}
scanner.process (rec, dist, inst_bcs, inst_bci);
}
for (std::set<db::CellInstArray>::const_iterator i = intruders.first.begin (); i != intruders.first.end (); ++i) {
if (! inst_bci (*i).empty ()) {
scanner.insert2 (i.operator-> (), ++id);
if (! intruders.second.empty () || ! intruder_shapes.empty ()) {
db::box_scanner2<db::CellInstArray, int, TI, int> scanner;
db::addressable_object_from_shape<TI> heap;
interaction_registration_inst2shape<TS, TI, TR> rec (mp_subject_layout, contexts.subject_layer (), dist, &interactions);
for (db::Cell::const_iterator i = subject_cell->begin (); !i.at_end (); ++i) {
if (! inst_bcs (i->cell_inst ()).empty () && ! subject_cell_is_breakout (i->cell_index ())) {
scanner.insert1 (&i->cell_inst (), 0);
}
}
}
scanner.process (rec, dist, inst_bcs, inst_bci);
}
if (! intruders.second.empty () || ! intruder_shapes.empty ()) {
db::box_scanner2<db::CellInstArray, int, TI, int> scanner;
db::addressable_object_from_shape<TI> heap;
interaction_registration_inst2shape<TS, TI, TR> rec (mp_subject_layout, contexts.subject_layer (), dist, &interactions);
for (db::Cell::const_iterator i = subject_cell->begin (); !i.at_end (); ++i) {
if (! inst_bcs (i->cell_inst ()).empty () && ! subject_cell_is_breakout (i->cell_index ())) {
scanner.insert1 (&i->cell_inst (), 0);
for (typename std::map<unsigned int, std::set<TI> >::const_iterator il = intruders.second.begin (); il != intruders.second.end (); ++il) {
for (typename std::set<TI>::const_iterator i = il->second.begin (); i != il->second.end (); ++i) {
scanner.insert2 (i.operator-> (), il->first);
}
}
}
for (typename std::map<unsigned int, std::set<TI> >::const_iterator il = intruders.second.begin (); il != intruders.second.end (); ++il) {
for (typename std::set<TI>::const_iterator i = il->second.begin (); i != il->second.end (); ++i) {
scanner.insert2 (i.operator-> (), il->first);
for (std::map<unsigned int, const db::Shapes *>::const_iterator im = intruder_shapes.begin (); im != intruder_shapes.end (); ++im) {
for (db::Shapes::shape_iterator i = im->second->begin (shape_flags<TI> ()); !i.at_end (); ++i) {
scanner.insert2 (heap (*i), im->first);
}
}
}
for (std::map<unsigned int, const db::Shapes *>::const_iterator im = intruder_shapes.begin (); im != intruder_shapes.end (); ++im) {
for (db::Shapes::shape_iterator i = im->second->begin (shape_flags<TI> ()); !i.at_end (); ++i) {
scanner.insert2 (heap (*i), im->first);
}
}
scanner.process (rec, dist, inst_bcs, db::box_convert<TI> ());
scanner.process (rec, dist, inst_bcs, db::box_convert<TI> ());
}
}
@ -1417,14 +1423,14 @@ local_processor<TS, TI, TR>::compute_local_cell (const db::local_processor_conte
}
}
// local shapes vs. child cell
db::box_convert<db::CellInstArray, true> inst_bci (*mp_intruder_layout, ail);
typename std::map<unsigned int, std::set<TI> >::const_iterator ipl = intruders.second.find (*il);
static std::set<TI> empty_intruders;
if (! subject_shapes->empty () && (intruder_shapes || ipl != intruders.second.end ())) {
// local shapes vs. local shapes
if (! top_down () && ! subject_shapes->empty () && (intruder_shapes || ipl != intruders.second.end ())) {
if (subject_cell == intruder_cell && contexts.subject_layer () == ail && !foreign) {
@ -1439,6 +1445,8 @@ local_processor<TS, TI, TR>::compute_local_cell (const db::local_processor_conte
}
// local shapes vs. child cells
if (! subject_shapes->empty () && ! ((! intruder_cell || intruder_cell->begin ().at_end ()) && intruders.first.empty ())) {
db::box_scanner2<TS, int, db::CellInstArray, int> scanner;
@ -1452,7 +1460,7 @@ local_processor<TS, TI, TR>::compute_local_cell (const db::local_processor_conte
unsigned int inst_id = 0;
if (subject_cell == intruder_cell && contexts.subject_layer () == ail && !foreign) {
if (! top_down () && subject_cell == intruder_cell && contexts.subject_layer () == ail && !foreign) {
// Same cell, same layer -> no shape to child instance interactions because this will be taken care of
// by the instances themselves (and their intruders). This also means, we prefer to deal with

View File

@ -465,6 +465,16 @@ public:
return m_nthreads;
}
void set_top_down (bool f)
{
m_top_down = f;
}
bool top_down () const
{
return m_top_down;
}
void set_max_vertex_count (size_t max_vertex_count)
{
m_max_vertex_count = max_vertex_count;
@ -525,6 +535,7 @@ private:
unsigned int m_nthreads;
size_t m_max_vertex_count;
double m_area_ratio;
bool m_top_down;
bool m_boolean_core;
int m_base_verbosity;
const db::VariantsCollectorBase *mp_vars;

View File

@ -1331,6 +1331,25 @@ public:
return std::make_pair (Region (res.first), Region (res.second));
}
/**
* @brief For deep regions, remove parts of shapes which are covered by child cell shapes (push shapes into hierarchy)
*
* This will reduce the hierarchical load. This means that shapes that do not add information
* will be removed, so their interactions with child cells does not need to be considered.
* These shapes are - maybe partially - "peeled" from upper hierarchy layers.
*
* The complexity factor indicates by how much the complexity of the resulting polygons
* (counted in terms of vertexes) can increase before a shape is left as it was.
* A negative complexity factor indicates, that no such limit exists. A zero complexity factor
* means that only shapes are removed if they are covered entirely by shapes from below the
* hierarchy.
*/
Region &peel (double complexity_factor = 0.0)
{
set_delegate (mp_delegate->peel (complexity_factor));
return *this;
}
/**
* @brief Selects all polygons of this region which are completely outside polygons from the other region
*

View File

@ -248,6 +248,8 @@ public:
virtual RegionDelegate *add (const Region &other) const = 0;
virtual std::pair<RegionDelegate *, RegionDelegate *> andnot_with (const Region &other, PropertyConstraint prop_constraint) const = 0;
virtual RegionDelegate *peel (double complexity_factor) const = 0;
virtual RegionDelegate *selected_outside (const Region &other) const = 0;
virtual RegionDelegate *selected_not_outside (const Region &other) const = 0;
virtual std::pair<RegionDelegate *, RegionDelegate *> selected_outside_pair (const Region &other) const = 0;

View File

@ -2732,6 +2732,20 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"\n"
"This method has been introduced in version 0.29.3."
) +
method ("peel", &db::Region::peel, gsi::arg ("complexity_factor", -1.0, "unlimited"),
"@brief Removes shapes parts which are overlapping with child cell shapes, reducing hierarchical load.\n"
"\n"
"This method will reduce the hierarchical load. This means that shapes that do not add information\n"
"will be removed, so their interactions with child cells does not need to be considered.\n"
"These shapes are - maybe partially - \"peeled\" from upper hierarchy layers.\n"
"\n"
"The complexity factor determines if the subtraction is rejected when the complexity - measured as polygon "
"vertex count - increases by more than the given factor. This allows trading off hierarchical complexity vs. "
"polygon complexity. A negative factor means no rejection. A factor of zero means that only shapes are removed "
"which are entirely covered by shapes from below the hierarchy.\n"
"\n"
"This method has been introduced in version 0.30.8."
) +
method_ext ("andnot", &andnot, gsi::arg ("other"), gsi::arg ("property_constraint", db::IgnoreProperties, "IgnoreProperties"),
"@brief Returns the boolean AND and NOT between self and the other region\n"
"\n"

View File

@ -3290,3 +3290,54 @@ TEST(processed_delivers_polygon_refs)
EXPECT_EQ (rx.to_string (), "(0,0;0,2000;2000,2000;2000,0);(0,0;0,2000;2000,2000;2000,0/200,200;1800,200;1800,1800;200,1800){n=>42}");
}
TEST(deep_region_peel)
{
db::Layout ly;
{
std::string fn (tl::testdata ());
fn += "/algo/deep_region_peel.gds";
tl::InputStream stream (fn);
db::Reader reader (stream);
reader.read (ly);
}
db::cell_index_type top_cell_index = *ly.begin_top_down ();
db::Cell &top_cell = ly.cell (top_cell_index);
db::DeepShapeStore dss;
unsigned int l1 = ly.get_layer (db::LayerProperties (1, 0));
unsigned int l2 = ly.get_layer (db::LayerProperties (2, 0));
db::RecursiveShapeIterator si1 (ly, top_cell, l1);
si1.apply_property_translator (db::PropertiesTranslator::make_pass_all ());
db::RecursiveShapeIterator si2 (ly, top_cell, l2);
si2.apply_property_translator (db::PropertiesTranslator::make_pass_all ());
db::Region r1 (si1, dss);
db::Region r2 (si2, dss);
unsigned int l1001 = ly.get_layer (db::LayerProperties (1001, 0));
unsigned int l1002 = ly.get_layer (db::LayerProperties (1002, 0));
unsigned int l1011 = ly.get_layer (db::LayerProperties (1011, 0));
unsigned int l1012 = ly.get_layer (db::LayerProperties (1012, 0));
unsigned int l1021 = ly.get_layer (db::LayerProperties (1021, 0));
unsigned int l1022 = ly.get_layer (db::LayerProperties (1022, 0));
unsigned int l1031 = ly.get_layer (db::LayerProperties (1031, 0));
unsigned int l1032 = ly.get_layer (db::LayerProperties (1032, 0));
db::Region (r1).peel (-1.0).insert_into (&ly, top_cell_index, l1001);
db::Region (r2).peel (-1.0).insert_into (&ly, top_cell_index, l1002);
db::Region (r1).peel (0.0).insert_into (&ly, top_cell_index, l1011);
db::Region (r2).peel (0.0).insert_into (&ly, top_cell_index, l1012);
db::Region (r1).peel (2.0).insert_into (&ly, top_cell_index, l1021);
db::Region (r2).peel (2.0).insert_into (&ly, top_cell_index, l1022);
db::Region (r1).peel (4.0).insert_into (&ly, top_cell_index, l1031);
db::Region (r2).peel (4.0).insert_into (&ly, top_cell_index, l1032);
CHECKPOINT();
db::compare_layouts (_this, ly, tl::testdata () + "/algo/deep_region_peel_au.gds");
}

BIN
testdata/algo/deep_region_peel.gds vendored Normal file

Binary file not shown.

BIN
testdata/algo/deep_region_peel_au.gds vendored Normal file

Binary file not shown.