From 422c88207c191c8de4b65998b794948e1743309f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 21 Oct 2024 21:50:35 +0200 Subject: [PATCH 01/13] WIP --- src/db/db/db.pro | 3 + src/db/db/dbCompoundOperation.h | 30 +++-- src/db/db/dbEdgeNeighborhood.cc | 107 +++++++++++++++++ src/db/db/dbEdgeNeighborhood.h | 106 +++++++++++++++++ src/db/db/gsiDeclDbEdgeNeighborhood.cc | 159 +++++++++++++++++++++++++ 5 files changed, 395 insertions(+), 10 deletions(-) create mode 100644 src/db/db/dbEdgeNeighborhood.cc create mode 100644 src/db/db/dbEdgeNeighborhood.h create mode 100644 src/db/db/gsiDeclDbEdgeNeighborhood.cc diff --git a/src/db/db/db.pro b/src/db/db/db.pro index 299f029e37..81f25683d8 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -23,6 +23,7 @@ SOURCES = \ dbCommonReader.cc \ dbCompoundOperation.cc \ dbEdge.cc \ + dbEdgeNeighborhood.cc \ dbEdgePair.cc \ dbEdgePairFilters.cc \ dbEdgePairRelations.cc \ @@ -119,6 +120,7 @@ SOURCES = \ gsiDeclDbCommonStreamOptions.cc \ gsiDeclDbCompoundOperation.cc \ gsiDeclDbEdge.cc \ + gsiDeclDbEdgeNeighborhood.cc \ gsiDeclDbEdgePair.cc \ gsiDeclDbEdgePairs.cc \ gsiDeclDbEdgeProcessor.cc \ @@ -249,6 +251,7 @@ HEADERS = \ dbCommonReader.h \ dbCompoundOperation.h \ dbEdge.h \ + dbEdgeNeighborhood.h \ dbEdgePair.h \ dbEdgePairFilters.h \ dbEdgePairRelations.h \ diff --git a/src/db/db/dbCompoundOperation.h b/src/db/db/dbCompoundOperation.h index 9047b8e092..cdb125e51b 100644 --- a/src/db/db/dbCompoundOperation.h +++ b/src/db/db/dbCompoundOperation.h @@ -266,6 +266,7 @@ class DB_PUBLIC CompoundRegionOperationNode virtual db::Coord computed_dist () const = 0; virtual std::string generated_description () const; + virtual bool wants_caching () const { return true; } private: std::string m_description; @@ -276,23 +277,32 @@ class DB_PUBLIC CompoundRegionOperationNode { // TODO: confine caching to those nodes which need it. - std::pair > *> cp = cache->get (this); + if (wants_caching ()) { - if (! cp.first) { + std::pair > *> cp = cache->get (this); - std::vector > uncached_results; - uncached_results.resize (results.size ()); + if (! cp.first) { - do_compute_local (cache, layout, cell, interactions, uncached_results, proc); + std::vector > uncached_results; + uncached_results.resize (results.size ()); - cp.second->swap (uncached_results); + do_compute_local (cache, layout, cell, interactions, uncached_results, proc); - } + cp.second->swap (uncached_results); + + } + + tl_assert (results.size () == cp.second->size ()); + for (size_t r = 0; r < results.size (); ++r) { + results[r].insert ((*cp.second)[r].begin (), (*cp.second)[r].end ()); + } + + } else { + + do_compute_local (cache, layout, cell, interactions, results, proc); - tl_assert (results.size () == cp.second->size ()); - for (size_t r = 0; r < results.size (); ++r) { - results[r].insert ((*cp.second)[r].begin (), (*cp.second)[r].end ()); } + } }; diff --git a/src/db/db/dbEdgeNeighborhood.cc b/src/db/db/dbEdgeNeighborhood.cc new file mode 100644 index 0000000000..7aab8d821d --- /dev/null +++ b/src/db/db/dbEdgeNeighborhood.cc @@ -0,0 +1,107 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2024 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "dbEdgeNeighborhood.h" +#include "dbBoxScanner.h" + +namespace db +{ + +EdgeNeighborhoodCompoundOperationNode::EdgeNeighborhoodCompoundOperationNode (const std::vector &children, EdgeNeighborhoodVisitor *visitor, db::Coord bext, db::Coord eext, db::Coord din, db::Coord dout) + : CompoundRegionMultiInputOperationNode (children), m_bext (bext), m_eext (eext), m_din (din), m_dout (dout), mp_visitor (visitor) +{ + tl_assert (visitor != 0); + visitor->keep (); +} + +db::Coord +EdgeNeighborhoodCompoundOperationNode::computed_dist () const +{ + return std::max (std::max (m_bext, m_eext), std::max (m_din, m_dout)); +} + +std::string +EdgeNeighborhoodCompoundOperationNode::generated_description () const +{ + return tl::to_string (tr ("Neighborhood collector")); +} + +void +EdgeNeighborhoodCompoundOperationNode::do_collect_neighbors (db::box_scanner2 &scanner, const db::Layout *layout, const db::Cell *cell) const +{ + + +} + +void +EdgeNeighborhoodCompoundOperationNode::do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const +{ + tl_assert (interactions.num_subjects () == 1); + + db::box_scanner2 scanner; + + std::list edges; + std::list polygons; + + for (auto i = interactions.begin_intruders (); i != interactions.end_intruders (); ++i) { + db::PolygonRef pr (i->second.second); + polygons.push_back (pr.instantiate ()); + scanner.insert2 (&polygons.back (), i->second.first); + } + + const db::PolygonRef &pr = interactions.begin_subjects ()->second; + unsigned int ie = 0; + for (auto e = pr.begin_edge (); ! e.at_end (); ++e, ++ie) { + edges.push_back (*e); + scanner.insert1 (&edges.back (), ie); + } + + do_collect_neighbors (scanner, layout, cell); +} + +void +EdgeNeighborhoodCompoundOperationNode::do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const +{ + tl_assert (interactions.num_subjects () == 1); + + db::box_scanner2 scanner; + + std::list edges; + std::list polygons; + + for (auto i = interactions.begin_intruders (); i != interactions.end_intruders (); ++i) { + polygons.push_back (i->second.second); + scanner.insert2 (&polygons.back (), i->second.first); + } + + const db::Polygon &pr = interactions.begin_subjects ()->second; + unsigned int ie = 0; + for (auto e = pr.begin_edge (); ! e.at_end (); ++e, ++ie) { + edges.push_back (*e); + scanner.insert1 (&edges.back (), ie); + } + + do_collect_neighbors (scanner, layout, cell); +} + +} + diff --git a/src/db/db/dbEdgeNeighborhood.h b/src/db/db/dbEdgeNeighborhood.h new file mode 100644 index 0000000000..cef890085e --- /dev/null +++ b/src/db/db/dbEdgeNeighborhood.h @@ -0,0 +1,106 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2024 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + + +#ifndef HDR_dbEdgeNeighborhood +#define HDR_dbEdgeNeighborhood + +#include "dbCommon.h" +#include "dbCompoundOperation.h" +#include "dbBoxScanner.h" + +namespace db +{ + +/** + * @brief A visitor for the neighbors of an edge + */ +class DB_PUBLIC EdgeNeighborhoodVisitor + : public gsi::ObjectBase, public tl::Object +{ +public: + typedef std::pair position_interval_type; + typedef unsigned int input_key_type; + typedef std::list neighbor_shapes_type; + typedef std::map neighbors_per_interval_type; + typedef std::vector > neighbors_type; + + /** + * @brief Constructor + */ + EdgeNeighborhoodVisitor () { } + + /** + * @brief Destructor + */ + virtual ~EdgeNeighborhoodVisitor () { } + + /** + * @brief Event handler for each edge plus it's neighborhood + */ + void on_edge (const db::Layout *layout, const db::Cell *cell, const db::Edge &edge, const neighbors_type &neighbors); +}; + +/** + * @brief A local operation for implementation of the neighborhood visitor + */ +class DB_PUBLIC EdgeNeighborhoodCompoundOperationNode + : public CompoundRegionMultiInputOperationNode +{ +public: + EdgeNeighborhoodCompoundOperationNode (const std::vector &children, EdgeNeighborhoodVisitor *visitor, const db::Coord bext, db::Coord eext, db::Coord din, db::Coord dout); + + virtual ResultType result_type () const + { + return CompoundRegionOperationNode::Edges; + } + + virtual bool wants_caching () const + { + return false; + } + +protected: + virtual db::Coord computed_dist () const; + virtual std::string generated_description () const; + + virtual void do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const; + virtual void do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const; + + // not implemented + virtual void do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const { } + virtual void do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const { } + virtual void do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const { } + virtual void do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const { } + +private: + db::Coord m_bext, m_eext, m_din, m_dout; + tl::weak_ptr mp_visitor; + + void do_collect_neighbors (db::box_scanner2 &scanner, const db::Layout *layout, const db::Cell *cell) const; +}; + +} + +#endif + diff --git a/src/db/db/gsiDeclDbEdgeNeighborhood.cc b/src/db/db/gsiDeclDbEdgeNeighborhood.cc new file mode 100644 index 0000000000..947b0322a7 --- /dev/null +++ b/src/db/db/gsiDeclDbEdgeNeighborhood.cc @@ -0,0 +1,159 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2024 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#include "gsiDecl.h" +#include "gsiEnums.h" + +#include "tlThreads.h" +#include "dbEdgeNeighborhood.h" + +namespace gsi +{ + +// --------------------------------------------------------------------------------- +// EdgeFilter binding + +class EdgeNeighborhoodVisitorImpl + : public db::EdgeNeighborhoodVisitor +{ +public: + EdgeNeighborhoodVisitorImpl () { } + + void issue_on_edge (const db::Layout *, const db::Cell *, const db::Edge &, const tl::Variant &) + { + // just for signature + } + + void on_edge (const db::Layout *layout, const db::Cell *cell, const db::Edge &edge, const db::EdgeNeighborhoodVisitor::neighbors_type &neighbors) + { + if (f_on_edge.can_issue ()) { + + tl::Variant neighborhood = build_neighbors (neighbors); + + // NOTE: as scripts are potentially thread unsafe, we lock here + tl::MutexLocker locker (&m_lock); + return f_on_edge.issue (&EdgeNeighborhoodVisitorImpl::issue_on_edge, layout, cell, edge, neighborhood); + + } + } + + gsi::Callback f_on_edge; + + static tl::Variant build_neighbors (const db::EdgeNeighborhoodVisitor::neighbors_type &neighbors) + { + tl::Variant result; + result.set_list (); + + for (auto n = neighbors.begin (); n != neighbors.end (); ++n) { + + tl::Variant row; + row.set_list (); + + tl::Variant interval; + interval.set_list (); + interval.push (n->first.first); + interval.push (n->first.second); + row.push (interval); + + tl::Variant nmap; + nmap.set_array (); + + for (auto nn = n->second.begin (); nn != n->second.end (); ++nn) { + nmap.insert (tl::Variant (nn->first), tl::Variant (nn->second)); + } + + row.push (nmap); + + result.push (row); + + } + + return result; + } + +private: + // No copying + EdgeNeighborhoodVisitorImpl &operator= (const EdgeNeighborhoodVisitorImpl &); + EdgeNeighborhoodVisitorImpl (const EdgeNeighborhoodVisitorImpl &); + + tl::Mutex m_lock; +}; + +Class decl_EdgeNeighborhoodVisitorImpl ("db", "EdgeNeighborhoodVisitor", + gsi::callback ("on_edge", &EdgeNeighborhoodVisitorImpl::issue_on_edge, &EdgeNeighborhoodVisitorImpl::f_on_edge, gsi::arg ("layout"), gsi::arg ("cell"), gsi::arg ("edge"), gsi::arg ("neighborhood"), + "@brief Is called for each edge with the edge neighbors\n" + "This method is called for every edge on the input region. It delivers the edge and the edge neighborhood. " + "The edge neighborhood is classified in intervals along the edge. The intervals are given by a range of " + "positions along the edge - 0.0 being the beginning of the edge and positive values towards the end of the edge. " + "For 'bext' and 'eext' larger than zero (see " + "\\EdgeNeighborhoodCompoundOperationNode), the position can be negative or larger than the edge length.\n" + "\n" + "The structure of the neighbors is:\n" + "\n" + "@code\n" + "[\n" + " [ [ from, to ], { input_index => polygons }\n" + "]\n" + "@/code\n" + "\n" + "'from' and 'to' are the positions of the interval, 'input_index' is the index of the input the neighbors are on " + "(see 'children' argument of \\EdgeNeighborhoodCompoundOperationNode constructor), 'prop_id' is the properties ID of " + "the neighbors and 'polygons' is a list of polygons describing the neighborhood.\n" + "The polygons are projected on the edge - i.e. they are in a coordinate system where the edge is horizonal and " + "goes from (0,0) to (length,0).\n" + "\n" + "The polygons are boxes for manhattan input and trapezoids in the general case.\n" + ), + "@brief A visitor for the neighborhood of edges in the input\n" + "\n" + "Objects of this class are passed to \\EdgeNeighborhoodCompoundOperationNode constructor to handle " + "events on each edge of the primary input along with the neighborhood taken from the additional inputs.\n" + "\n" + "See \\on_edge for the description of the events delivered." + "\n" + "This class has been introduced in version 0.xx.\n" // @@@ +); + +// --------------------------------------------------------------------------------- +// EdgeProcessor binding + +static db::CompoundRegionOperationNode *new_edge_neighborhood (const std::vector &children, db::EdgeNeighborhoodVisitor *visitor, const db::Coord bext, db::Coord eext, db::Coord din, db::Coord dout) +{ + return new db::EdgeNeighborhoodCompoundOperationNode (children, visitor, bext, eext, din, dout); +} + +gsi::ClassExt decl_CompoundRegionOperationNode_ext ( + gsi::constructor ("new_edge_neighborhood", &new_edge_neighborhood, gsi::arg ("children"), gsi::arg ("visitor"), gsi::arg ("bext", 0), gsi::arg ("eext", 0), gsi::arg ("din", 0), gsi::arg ("dout", 0), + "@brief Creates a new edge neighborhood collector\n" + "\n" + "@param children The inputs to use. The first one in the primary input, the others are neighbors.\n" + "@param visitor The visitor object (see \\EdgeNeighborhoodVisitor) receiving the edge events.\n" + "@param bext The search window extension to use at the edge beginning.\n" + "@param eext The search window extension to use at the edge beginning.\n" + "@param din The search window extension to use at the edge beginning.\n" + "@param dout The search window extension to use at the edge beginning.\n" + ) +); + +} + From 46b44e62875649a2959a6977de63ba092ffa843b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 21 Oct 2024 23:35:34 +0200 Subject: [PATCH 02/13] WIP: builds, but not tested yet. --- src/db/db/dbEdgeNeighborhood.cc | 113 +++++++++++++++++++++++++++++++- src/db/db/dbEdgeNeighborhood.h | 2 +- 2 files changed, 112 insertions(+), 3 deletions(-) diff --git a/src/db/db/dbEdgeNeighborhood.cc b/src/db/db/dbEdgeNeighborhood.cc index 7aab8d821d..bfa0be7ad8 100644 --- a/src/db/db/dbEdgeNeighborhood.cc +++ b/src/db/db/dbEdgeNeighborhood.cc @@ -22,6 +22,7 @@ #include "dbEdgeNeighborhood.h" #include "dbBoxScanner.h" +#include "dbClip.h" namespace db { @@ -45,16 +46,121 @@ EdgeNeighborhoodCompoundOperationNode::generated_description () const return tl::to_string (tr ("Neighborhood collector")); } -void -EdgeNeighborhoodCompoundOperationNode::do_collect_neighbors (db::box_scanner2 &scanner, const db::Layout *layout, const db::Cell *cell) const +namespace { + +class EdgeCollectorReceiver + : public db::box_scanner_receiver2 { +public: + EdgeCollectorReceiver (db::EdgeNeighborhoodVisitor *visitor, const db::Layout *layout, const db::Cell *cell, + db::Coord bext, db::Coord eext, db::Coord din, db::Coord dout) + : mp_visitor (visitor), mp_layout (layout), mp_cell (cell), + m_bext (bext), m_eext (eext), m_din (din), m_dout (dout) + { + // .. nothing yet .. + } + + void add (const db::Edge *o1, const unsigned int & /*p1*/, const db::Polygon *o2, const unsigned int &p2) + { + m_edge_neighbors[o1][p2].push_back (o2); + } + + void finalize (bool) + { + for (auto en = m_edge_neighbors.begin (); en != m_edge_neighbors.end (); ++en) { + commit_edge (*en->first, en->second); + } + } +private: + std::map > > m_edge_neighbors; + db::EdgeNeighborhoodVisitor *mp_visitor; + const db::Layout *mp_layout; + const db::Cell *mp_cell; + db::Coord m_bext, m_eext, m_din, m_dout; + + void commit_edge (const db::Edge &edge, const std::map > &neighbors) const + { + if (edge.is_degenerate ()) { + return; + } + + db::DVector e = db::DVector (edge.d ()); + e = e * (1.0 / e.double_length ()); + + db::DVector ne (-e.y (), e.x ()); + + // transform on the edge + db::IMatrix2d trans (e.x (), ne.x (), e.y (), ne.y ()); + db::IMatrix2d itrans = trans.inverted (); + + db::Edge ref_edge = itrans * edge; + tl_assert (ref_edge.dy () == 0); + tl_assert (ref_edge.dx () > 0); + + std::set xpos; + db::Coord xmin = -m_bext - 1; + db::Coord xmax = m_eext + 1; + + for (auto n = neighbors.begin (); n != neighbors.end (); ++n) { + for (auto p = n->second.begin (); p != n->second.end (); ++p) { + db::Polygon poly = itrans * **p; + for (auto p = poly.begin_edge (); ! p.at_end (); ++p) { + db::Edge e = *p; + xpos.insert (std::max (xmin, std::min (xmax, e.p1 ().x ()))); + xpos.insert (std::max (xmin, std::min (xmax, e.p2 ().x ()))); + } + } + } + + EdgeNeighborhoodVisitor::neighbors_type binned_neighbors; + + for (auto i = xpos.begin (); i != xpos.end (); ) { + + db::Coord xfrom = *i; + ++i; + if (i == xpos.end ()) { + break; + } + db::Coord xto = *i; + + binned_neighbors.push_back (EdgeNeighborhoodVisitor::neighbors_type::value_type ()); + binned_neighbors.back ().first.first = xfrom; + binned_neighbors.back ().first.second = xto; + + db::Box clip_box (xfrom, -m_din - 1, xto, m_dout + 1); + + EdgeNeighborhoodVisitor::neighbors_per_interval_type &i2n = binned_neighbors.back ().second; + + // NOTE: this could be more efficient if we had a multi-layer capable trapezoid decomposition tool + for (auto n = neighbors.begin (); n != neighbors.end (); ++n) { + for (auto p = n->second.begin (); p != n->second.end (); ++p) { + db::Polygon poly = itrans * **p; + db::clip_poly (poly, clip_box, i2n[n->first], false); + } + } + + } + + mp_visitor->on_edge (mp_layout, mp_cell, edge, binned_neighbors); + } +}; + +} +void +EdgeNeighborhoodCompoundOperationNode::do_collect_neighbors (db::box_scanner2 &scanner, const db::Layout *layout, const db::Cell *cell) const +{ + EdgeCollectorReceiver rec (const_cast (mp_visitor.get ()), layout, cell, m_bext, m_eext, m_din, m_dout); + scanner.process (rec, computed_dist (), db::box_convert (), db::box_convert ()); } void EdgeNeighborhoodCompoundOperationNode::do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const { + if (! mp_visitor) { + return; + } tl_assert (interactions.num_subjects () == 1); db::box_scanner2 scanner; @@ -81,6 +187,9 @@ EdgeNeighborhoodCompoundOperationNode::do_compute_local (CompoundRegionOperation void EdgeNeighborhoodCompoundOperationNode::do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const { + if (! mp_visitor) { + return; + } tl_assert (interactions.num_subjects () == 1); db::box_scanner2 scanner; diff --git a/src/db/db/dbEdgeNeighborhood.h b/src/db/db/dbEdgeNeighborhood.h index cef890085e..88c8f6185c 100644 --- a/src/db/db/dbEdgeNeighborhood.h +++ b/src/db/db/dbEdgeNeighborhood.h @@ -41,7 +41,7 @@ class DB_PUBLIC EdgeNeighborhoodVisitor public: typedef std::pair position_interval_type; typedef unsigned int input_key_type; - typedef std::list neighbor_shapes_type; + typedef std::vector neighbor_shapes_type; typedef std::map neighbors_per_interval_type; typedef std::vector > neighbors_type; From f7dde684f0ebbe8684b826ae79463ada970b377c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 23 Oct 2024 18:38:26 +0200 Subject: [PATCH 03/13] WIP: some basic debugging --- src/db/db/dbEdgeNeighborhood.h | 2 +- src/db/db/gsiDeclDbEdgeNeighborhood.cc | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/db/db/dbEdgeNeighborhood.h b/src/db/db/dbEdgeNeighborhood.h index 88c8f6185c..6f2913e948 100644 --- a/src/db/db/dbEdgeNeighborhood.h +++ b/src/db/db/dbEdgeNeighborhood.h @@ -58,7 +58,7 @@ class DB_PUBLIC EdgeNeighborhoodVisitor /** * @brief Event handler for each edge plus it's neighborhood */ - void on_edge (const db::Layout *layout, const db::Cell *cell, const db::Edge &edge, const neighbors_type &neighbors); + virtual void on_edge (const db::Layout * /*layout*/, const db::Cell * /*cell*/, const db::Edge & /*edge*/, const neighbors_type & /*neighbors*/) { } }; /** diff --git a/src/db/db/gsiDeclDbEdgeNeighborhood.cc b/src/db/db/gsiDeclDbEdgeNeighborhood.cc index 947b0322a7..128721a00c 100644 --- a/src/db/db/gsiDeclDbEdgeNeighborhood.cc +++ b/src/db/db/gsiDeclDbEdgeNeighborhood.cc @@ -99,7 +99,11 @@ class EdgeNeighborhoodVisitorImpl tl::Mutex m_lock; }; -Class decl_EdgeNeighborhoodVisitorImpl ("db", "EdgeNeighborhoodVisitor", +Class decl_EdgeNeighborhoodVisitor ("db", "EdgeNeighborhoodVisitorBase", + "@hide" +); + +Class decl_EdgeNeighborhoodVisitorImpl (decl_EdgeNeighborhoodVisitor, "db", "EdgeNeighborhoodVisitor", gsi::callback ("on_edge", &EdgeNeighborhoodVisitorImpl::issue_on_edge, &EdgeNeighborhoodVisitorImpl::f_on_edge, gsi::arg ("layout"), gsi::arg ("cell"), gsi::arg ("edge"), gsi::arg ("neighborhood"), "@brief Is called for each edge with the edge neighbors\n" "This method is called for every edge on the input region. It delivers the edge and the edge neighborhood. " From 7af91fa2b5f93422899286ab4aad08434e7763fc Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 23 Oct 2024 22:53:16 +0200 Subject: [PATCH 04/13] Further debugging --- src/db/db/dbEdgeNeighborhood.cc | 34 +++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/db/db/dbEdgeNeighborhood.cc b/src/db/db/dbEdgeNeighborhood.cc index bfa0be7ad8..5ec532654d 100644 --- a/src/db/db/dbEdgeNeighborhood.cc +++ b/src/db/db/dbEdgeNeighborhood.cc @@ -93,18 +93,19 @@ class EdgeCollectorReceiver // transform on the edge db::IMatrix2d trans (e.x (), ne.x (), e.y (), ne.y ()); db::IMatrix2d itrans = trans.inverted (); + db::Disp move (db::Point () - edge.p1 ()); - db::Edge ref_edge = itrans * edge; + db::Edge ref_edge = itrans * (move * edge); tl_assert (ref_edge.dy () == 0); tl_assert (ref_edge.dx () > 0); std::set xpos; db::Coord xmin = -m_bext - 1; - db::Coord xmax = m_eext + 1; + db::Coord xmax = ref_edge.dx () + m_eext + 1; for (auto n = neighbors.begin (); n != neighbors.end (); ++n) { for (auto p = n->second.begin (); p != n->second.end (); ++p) { - db::Polygon poly = itrans * **p; + db::Polygon poly = itrans * (move * **p); for (auto p = poly.begin_edge (); ! p.at_end (); ++p) { db::Edge e = *p; xpos.insert (std::max (xmin, std::min (xmax, e.p1 ().x ()))); @@ -124,20 +125,33 @@ class EdgeCollectorReceiver } db::Coord xto = *i; - binned_neighbors.push_back (EdgeNeighborhoodVisitor::neighbors_type::value_type ()); - binned_neighbors.back ().first.first = xfrom; - binned_neighbors.back ().first.second = xto; + bool first_per_interval = true; db::Box clip_box (xfrom, -m_din - 1, xto, m_dout + 1); - EdgeNeighborhoodVisitor::neighbors_per_interval_type &i2n = binned_neighbors.back ().second; - // NOTE: this could be more efficient if we had a multi-layer capable trapezoid decomposition tool for (auto n = neighbors.begin (); n != neighbors.end (); ++n) { + + EdgeNeighborhoodVisitor::neighbor_shapes_type polygons; for (auto p = n->second.begin (); p != n->second.end (); ++p) { - db::Polygon poly = itrans * **p; - db::clip_poly (poly, clip_box, i2n[n->first], false); + db::Polygon poly = itrans * (move * **p); + db::clip_poly (poly, clip_box, polygons, false); + } + + if (!polygons.empty ()) { + + if (first_per_interval) { + first_per_interval = false; + binned_neighbors.push_back (EdgeNeighborhoodVisitor::neighbors_type::value_type ()); + binned_neighbors.back ().first.first = xfrom; + binned_neighbors.back ().first.second = xto; + } + + EdgeNeighborhoodVisitor::neighbors_per_interval_type &i2n = binned_neighbors.back ().second; + i2n[n->first].swap (polygons); + } + } } From 0d2379ad7514ce20b382f037d6605a2bb7763fb7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 24 Oct 2024 17:55:56 +0200 Subject: [PATCH 05/13] WIP: debugging, added polygon callback for EdgeNeighborhoodVisitor --- src/db/db/dbEdgeNeighborhood.cc | 4 +++ src/db/db/dbEdgeNeighborhood.h | 12 ++++++++ src/db/db/gsiDeclDbEdgeNeighborhood.cc | 41 ++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/src/db/db/dbEdgeNeighborhood.cc b/src/db/db/dbEdgeNeighborhood.cc index 5ec532654d..c171d04ae4 100644 --- a/src/db/db/dbEdgeNeighborhood.cc +++ b/src/db/db/dbEdgeNeighborhood.cc @@ -195,7 +195,9 @@ EdgeNeighborhoodCompoundOperationNode::do_compute_local (CompoundRegionOperation scanner.insert1 (&edges.back (), ie); } + const_cast (mp_visitor.get ())->begin_polygon (layout, cell, pr.instantiate ()); do_collect_neighbors (scanner, layout, cell); + const_cast (mp_visitor.get ())->end_polygon (); } void @@ -223,7 +225,9 @@ EdgeNeighborhoodCompoundOperationNode::do_compute_local (CompoundRegionOperation scanner.insert1 (&edges.back (), ie); } + const_cast (mp_visitor.get ())->begin_polygon (layout, cell, pr); do_collect_neighbors (scanner, layout, cell); + const_cast (mp_visitor.get ())->end_polygon (); } } diff --git a/src/db/db/dbEdgeNeighborhood.h b/src/db/db/dbEdgeNeighborhood.h index 6f2913e948..b01295d188 100644 --- a/src/db/db/dbEdgeNeighborhood.h +++ b/src/db/db/dbEdgeNeighborhood.h @@ -55,6 +55,18 @@ class DB_PUBLIC EdgeNeighborhoodVisitor */ virtual ~EdgeNeighborhoodVisitor () { } + /** + * @brief Event handler called when a new polygon is encountered + * Following this event, the edges with their neighborhood are reported. + * After the edges are reported, "end_polygon" is called. + */ + virtual void begin_polygon (const db::Layout * /*layout*/, const db::Cell * /*cell*/, const db::Polygon & /*polygon*/) { } + + /** + * @brief Event handler called after the polygon was processed + */ + virtual void end_polygon () { } + /** * @brief Event handler for each edge plus it's neighborhood */ diff --git a/src/db/db/gsiDeclDbEdgeNeighborhood.cc b/src/db/db/gsiDeclDbEdgeNeighborhood.cc index 128721a00c..1cbed771ea 100644 --- a/src/db/db/gsiDeclDbEdgeNeighborhood.cc +++ b/src/db/db/gsiDeclDbEdgeNeighborhood.cc @@ -59,6 +59,38 @@ class EdgeNeighborhoodVisitorImpl gsi::Callback f_on_edge; + void issue_begin_polygon (const db::Layout *, const db::Cell *, const db::Polygon &poly) + { + // just for signature + } + + void begin_polygon (const db::Layout *layout, const db::Cell *cell, const db::Polygon &poly) + { + if (f_begin_polygon.can_issue ()) { + // NOTE: as scripts are potentially thread unsafe, we lock here + tl::MutexLocker locker (&m_lock); + return f_begin_polygon.issue (&EdgeNeighborhoodVisitorImpl::begin_polygon, layout, cell, poly); + } + } + + gsi::Callback f_begin_polygon; + + void issue_end_polygon () + { + // just for signature + } + + void end_polygon () + { + if (f_end_polygon.can_issue ()) { + // NOTE: as scripts are potentially thread unsafe, we lock here + tl::MutexLocker locker (&m_lock); + return f_end_polygon.issue (&EdgeNeighborhoodVisitorImpl::end_polygon); + } + } + + gsi::Callback f_end_polygon; + static tl::Variant build_neighbors (const db::EdgeNeighborhoodVisitor::neighbors_type &neighbors) { tl::Variant result; @@ -127,6 +159,15 @@ Class decl_EdgeNeighborhoodVisitorImpl (decl_E "goes from (0,0) to (length,0).\n" "\n" "The polygons are boxes for manhattan input and trapezoids in the general case.\n" + ) + + gsi::callback ("begin_polygon", &EdgeNeighborhoodVisitorImpl::issue_begin_polygon, &EdgeNeighborhoodVisitorImpl::f_begin_polygon, gsi::arg ("layout"), gsi::arg ("cell"), gsi::arg ("polygon"), + "@brief Is called for each new polygon\n" + "This event announces a new primary polygon. After this event, the edges of the polygon are reported via \\on_edge, " + "followed by a call of \\end_polygon." + ) + + gsi::callback ("end_polygon", &EdgeNeighborhoodVisitorImpl::issue_end_polygon, &EdgeNeighborhoodVisitorImpl::f_end_polygon, + "@brief Is called after the polygon\n" + "See \\begin_polygon for a description of this protocol." ), "@brief A visitor for the neighborhood of edges in the input\n" "\n" From 9f2a2075c79ed9c86ad2c26c19e1d9d51e2fe3c6 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 29 Oct 2024 23:18:01 +0100 Subject: [PATCH 06/13] Implementing full support for compound operations in EdgeNeighborhood, including foreign() --- src/db/db/dbEdgeNeighborhood.cc | 56 ++++++++++++++------------------- src/db/db/dbEdgeNeighborhood.h | 3 ++ 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/src/db/db/dbEdgeNeighborhood.cc b/src/db/db/dbEdgeNeighborhood.cc index c171d04ae4..cc01131c56 100644 --- a/src/db/db/dbEdgeNeighborhood.cc +++ b/src/db/db/dbEdgeNeighborhood.cc @@ -170,38 +170,20 @@ EdgeNeighborhoodCompoundOperationNode::do_collect_neighbors (db::box_scanner2 &interactions, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const +EdgeNeighborhoodCompoundOperationNode::do_compute_local (CompoundRegionOperationCache *cache, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > &results, const db::LocalProcessorBase *proc) const { - if (! mp_visitor) { - return; - } - tl_assert (interactions.num_subjects () == 1); - - db::box_scanner2 scanner; - - std::list edges; - std::list polygons; - - for (auto i = interactions.begin_intruders (); i != interactions.end_intruders (); ++i) { - db::PolygonRef pr (i->second.second); - polygons.push_back (pr.instantiate ()); - scanner.insert2 (&polygons.back (), i->second.first); - } - - const db::PolygonRef &pr = interactions.begin_subjects ()->second; - unsigned int ie = 0; - for (auto e = pr.begin_edge (); ! e.at_end (); ++e, ++ie) { - edges.push_back (*e); - scanner.insert1 (&edges.back (), ie); - } + compute_local_impl (cache, layout, cell, interactions, results, proc); +} - const_cast (mp_visitor.get ())->begin_polygon (layout, cell, pr.instantiate ()); - do_collect_neighbors (scanner, layout, cell); - const_cast (mp_visitor.get ())->end_polygon (); +void +EdgeNeighborhoodCompoundOperationNode::do_compute_local (CompoundRegionOperationCache *cache, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > &results, const db::LocalProcessorBase *proc) const +{ + compute_local_impl (cache, layout, cell, interactions, results, proc); } +template void -EdgeNeighborhoodCompoundOperationNode::do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const +EdgeNeighborhoodCompoundOperationNode::compute_local_impl (CompoundRegionOperationCache *cache, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > & /*results*/, const db::LocalProcessorBase *proc) const { if (! mp_visitor) { return; @@ -213,19 +195,29 @@ EdgeNeighborhoodCompoundOperationNode::do_compute_local (CompoundRegionOperation std::list edges; std::list polygons; - for (auto i = interactions.begin_intruders (); i != interactions.end_intruders (); ++i) { - polygons.push_back (i->second.second); - scanner.insert2 (&polygons.back (), i->second.first); + for (unsigned int i = 0; i < children (); ++i) { + + std::vector > others; + others.push_back (std::unordered_set ()); + + shape_interactions computed_interactions; + child (i)->compute_local (cache, layout, cell, interactions_for_child (interactions, i, computed_interactions), others, proc); + + for (auto p = others.front ().begin (); p != others.front ().end (); ++p) { + polygons.push_back (p->instantiate ()); + scanner.insert2 (&polygons.back (), i); + } + } - const db::Polygon &pr = interactions.begin_subjects ()->second; + const T &pr = interactions.begin_subjects ()->second; unsigned int ie = 0; for (auto e = pr.begin_edge (); ! e.at_end (); ++e, ++ie) { edges.push_back (*e); scanner.insert1 (&edges.back (), ie); } - const_cast (mp_visitor.get ())->begin_polygon (layout, cell, pr); + const_cast (mp_visitor.get ())->begin_polygon (layout, cell, pr.instantiate ()); do_collect_neighbors (scanner, layout, cell); const_cast (mp_visitor.get ())->end_polygon (); } diff --git a/src/db/db/dbEdgeNeighborhood.h b/src/db/db/dbEdgeNeighborhood.h index b01295d188..6eec7ac47b 100644 --- a/src/db/db/dbEdgeNeighborhood.h +++ b/src/db/db/dbEdgeNeighborhood.h @@ -110,6 +110,9 @@ class DB_PUBLIC EdgeNeighborhoodCompoundOperationNode tl::weak_ptr mp_visitor; void do_collect_neighbors (db::box_scanner2 &scanner, const db::Layout *layout, const db::Cell *cell) const; + + template + void compute_local_impl (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const; }; } From 0cca9afc7e2a4c5ccecab26deb9a4e7f519858d7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 31 Oct 2024 21:34:17 +0100 Subject: [PATCH 07/13] Generalization: providing output functionality. --- src/db/db/dbEdgeNeighborhood.cc | 166 +++++++++++++++++++++---- src/db/db/dbEdgeNeighborhood.h | 85 +++++++++++-- src/db/db/gsiDeclDbEdgeNeighborhood.cc | 32 ++++- 3 files changed, 245 insertions(+), 38 deletions(-) diff --git a/src/db/db/dbEdgeNeighborhood.cc b/src/db/db/dbEdgeNeighborhood.cc index cc01131c56..88c6ca56ee 100644 --- a/src/db/db/dbEdgeNeighborhood.cc +++ b/src/db/db/dbEdgeNeighborhood.cc @@ -27,6 +27,86 @@ namespace db { +// -------------------------------------------------------------------------------------------------- + +EdgeNeighborhoodVisitor::EdgeNeighborhoodVisitor () + : m_result_type (db::CompoundRegionOperationNode::ResultType::Edges) +{ + disconnect_outputs (); +} + +void +EdgeNeighborhoodVisitor::connect_output (Layout * /*layout*/, std::unordered_set *polygons) const +{ + disconnect_outputs (); + mp_polygons = polygons; +} + +void +EdgeNeighborhoodVisitor::connect_output (db::Layout *layout, std::unordered_set *polygons) const +{ + disconnect_outputs (); + mp_layout = layout; + mp_polygon_refs = polygons; +} + +void +EdgeNeighborhoodVisitor::connect_output (db::Layout * /*layout*/, std::unordered_set *edges) const +{ + disconnect_outputs (); + mp_edges = edges; +} + +void +EdgeNeighborhoodVisitor::connect_output (Layout * /*layout*/, std::unordered_set *edge_pairs) const +{ + disconnect_outputs (); + mp_edge_pairs = edge_pairs; +} + +void +EdgeNeighborhoodVisitor::disconnect_outputs () const +{ + mp_layout = 0; + mp_polygons = 0; + mp_polygon_refs = 0; + mp_edges = 0; + mp_edge_pairs = 0; +} + +void +EdgeNeighborhoodVisitor::output_polygon (const db::Polygon &poly) +{ + if (mp_polygons) { + mp_polygons->insert (poly); + } else if (mp_polygon_refs) { + tl_assert (mp_layout != 0); + mp_polygon_refs->insert (db::PolygonRef (poly, mp_layout->shape_repository ())); + } else { + throw tl::Exception (tl::to_string (tr ("EdgeNeighborhoodVisitor is not configured for edge output (use 'result_type=Edges')"))); + } +} + +void +EdgeNeighborhoodVisitor::output_edge (const db::Edge &edge) +{ + if (mp_edges == 0) { + throw tl::Exception (tl::to_string (tr ("EdgeNeighborhoodVisitor is not configured for edge output (use 'result_type=Edges')"))); + } + mp_edges->insert (edge); +} + +void +EdgeNeighborhoodVisitor::output_edge_pair (const db::EdgePair &edge_pair) +{ + if (mp_edge_pairs == 0) { + throw tl::Exception (tl::to_string (tr ("EdgeNeighborhoodVisitor is not configured for edge pair output (use 'result_type=EdgePairs')"))); + } + mp_edge_pairs->insert (edge_pair); +} + +// -------------------------------------------------------------------------------------------------- + EdgeNeighborhoodCompoundOperationNode::EdgeNeighborhoodCompoundOperationNode (const std::vector &children, EdgeNeighborhoodVisitor *visitor, db::Coord bext, db::Coord eext, db::Coord din, db::Coord dout) : CompoundRegionMultiInputOperationNode (children), m_bext (bext), m_eext (eext), m_din (din), m_dout (dout), mp_visitor (visitor) { @@ -172,54 +252,90 @@ EdgeNeighborhoodCompoundOperationNode::do_collect_neighbors (db::box_scanner2 &interactions, std::vector > &results, const db::LocalProcessorBase *proc) const { - compute_local_impl (cache, layout, cell, interactions, results, proc); + compute_local_impl (cache, layout, cell, interactions, results, proc); } void EdgeNeighborhoodCompoundOperationNode::do_compute_local (CompoundRegionOperationCache *cache, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > &results, const db::LocalProcessorBase *proc) const { - compute_local_impl (cache, layout, cell, interactions, results, proc); + compute_local_impl (cache, layout, cell, interactions, results, proc); +} + +void +EdgeNeighborhoodCompoundOperationNode::do_compute_local (CompoundRegionOperationCache *cache, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > &results, const db::LocalProcessorBase *proc) const +{ + compute_local_impl (cache, layout, cell, interactions, results, proc); +} + +void +EdgeNeighborhoodCompoundOperationNode::do_compute_local (CompoundRegionOperationCache *cache, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > &results, const db::LocalProcessorBase *proc) const +{ + compute_local_impl (cache, layout, cell, interactions, results, proc); +} + +void +EdgeNeighborhoodCompoundOperationNode::do_compute_local (CompoundRegionOperationCache *cache, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > &results, const db::LocalProcessorBase *proc) const +{ + compute_local_impl (cache, layout, cell, interactions, results, proc); +} + +void +EdgeNeighborhoodCompoundOperationNode::do_compute_local (CompoundRegionOperationCache *cache, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > &results, const db::LocalProcessorBase *proc) const +{ + compute_local_impl (cache, layout, cell, interactions, results, proc); } -template +template void -EdgeNeighborhoodCompoundOperationNode::compute_local_impl (CompoundRegionOperationCache *cache, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > & /*results*/, const db::LocalProcessorBase *proc) const +EdgeNeighborhoodCompoundOperationNode::compute_local_impl (CompoundRegionOperationCache *cache, db::Layout *layout, db::Cell *cell, const shape_interactions &interactions, std::vector > &results, const db::LocalProcessorBase *proc) const { if (! mp_visitor) { return; } tl_assert (interactions.num_subjects () == 1); + tl_assert (! results.empty ()); + + try { - db::box_scanner2 scanner; + mp_visitor->connect_output (layout, &results.front ()); - std::list edges; - std::list polygons; + db::box_scanner2 scanner; - for (unsigned int i = 0; i < children (); ++i) { + std::list edges; + std::list polygons; - std::vector > others; - others.push_back (std::unordered_set ()); + for (unsigned int i = 0; i < children (); ++i) { - shape_interactions computed_interactions; - child (i)->compute_local (cache, layout, cell, interactions_for_child (interactions, i, computed_interactions), others, proc); + std::vector > others; + others.push_back (std::unordered_set ()); + + shape_interactions computed_interactions; + child (i)->compute_local (cache, layout, cell, interactions_for_child (interactions, i, computed_interactions), others, proc); + + for (auto p = others.front ().begin (); p != others.front ().end (); ++p) { + polygons.push_back (p->instantiate ()); + scanner.insert2 (&polygons.back (), i); + } - for (auto p = others.front ().begin (); p != others.front ().end (); ++p) { - polygons.push_back (p->instantiate ()); - scanner.insert2 (&polygons.back (), i); } - } + const T &pr = interactions.begin_subjects ()->second; + unsigned int ie = 0; + for (auto e = pr.begin_edge (); ! e.at_end (); ++e, ++ie) { + edges.push_back (*e); + scanner.insert1 (&edges.back (), ie); + } - const T &pr = interactions.begin_subjects ()->second; - unsigned int ie = 0; - for (auto e = pr.begin_edge (); ! e.at_end (); ++e, ++ie) { - edges.push_back (*e); - scanner.insert1 (&edges.back (), ie); - } + const_cast (mp_visitor.get ())->begin_polygon (layout, cell, pr.instantiate ()); + do_collect_neighbors (scanner, layout, cell); + const_cast (mp_visitor.get ())->end_polygon (); - const_cast (mp_visitor.get ())->begin_polygon (layout, cell, pr.instantiate ()); - do_collect_neighbors (scanner, layout, cell); - const_cast (mp_visitor.get ())->end_polygon (); + mp_visitor->disconnect_outputs (); + + } catch (...) { + mp_visitor->disconnect_outputs (); + throw; + } } } diff --git a/src/db/db/dbEdgeNeighborhood.h b/src/db/db/dbEdgeNeighborhood.h index 6eec7ac47b..3566b336da 100644 --- a/src/db/db/dbEdgeNeighborhood.h +++ b/src/db/db/dbEdgeNeighborhood.h @@ -48,13 +48,38 @@ class DB_PUBLIC EdgeNeighborhoodVisitor /** * @brief Constructor */ - EdgeNeighborhoodVisitor () { } + EdgeNeighborhoodVisitor (); /** * @brief Destructor */ virtual ~EdgeNeighborhoodVisitor () { } + /** + * @brief Configure the polygon output + */ + void connect_output (db::Layout * /*layout*/, std::unordered_set *polygons) const; + + /** + * @brief Configure the polygon ref output + */ + void connect_output (db::Layout *layout, std::unordered_set *polygons) const; + + /** + * @brief Configure the edge output + */ + void connect_output (db::Layout * /*layout*/, std::unordered_set *edges) const; + + /** + * @brief Configure the edge pair output + */ + void connect_output (db::Layout * /*layout*/, std::unordered_set *edge_pairs) const; + + /** + * @brief Disconnects output + */ + void disconnect_outputs () const; + /** * @brief Event handler called when a new polygon is encountered * Following this event, the edges with their neighborhood are reported. @@ -71,6 +96,48 @@ class DB_PUBLIC EdgeNeighborhoodVisitor * @brief Event handler for each edge plus it's neighborhood */ virtual void on_edge (const db::Layout * /*layout*/, const db::Cell * /*cell*/, const db::Edge & /*edge*/, const neighbors_type & /*neighbors*/) { } + + /** + * @brief Sets the result type + */ + void set_result_type (db::CompoundRegionOperationNode::ResultType result_type) + { + m_result_type = result_type; + } + + /** + * @brief Gets the result type + */ + db::CompoundRegionOperationNode::ResultType result_type () const + { + return m_result_type; + } + + /** + * @brief Delivers a polygon + * This function is only permitted if the result type is Region. + */ + void output_polygon (const db::Polygon &poly); + + /** + * @brief Delivers an edge + * This function is only permitted if the result type is Edges. + */ + void output_edge (const db::Edge &edge); + + /** + * @brief Delivers an edge pair object + * This function is only permitted if the result type is EdgePairs. + */ + void output_edge_pair (const db::EdgePair &edge_pair); + +private: + db::CompoundRegionOperationNode::ResultType m_result_type; + mutable std::unordered_set *mp_polygons; + mutable std::unordered_set *mp_polygon_refs; + mutable std::unordered_set *mp_edges; + mutable std::unordered_set *mp_edge_pairs; + mutable db::Layout *mp_layout; }; /** @@ -84,7 +151,7 @@ class DB_PUBLIC EdgeNeighborhoodCompoundOperationNode virtual ResultType result_type () const { - return CompoundRegionOperationNode::Edges; + return mp_visitor ? mp_visitor->result_type () : CompoundRegionOperationNode::Edges; } virtual bool wants_caching () const @@ -98,12 +165,10 @@ class DB_PUBLIC EdgeNeighborhoodCompoundOperationNode virtual void do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const; virtual void do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const; - - // not implemented - virtual void do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const { } - virtual void do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const { } - virtual void do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const { } - virtual void do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const { } + virtual void do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const; + virtual void do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const; + virtual void do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const; + virtual void do_compute_local (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const; private: db::Coord m_bext, m_eext, m_din, m_dout; @@ -111,8 +176,8 @@ class DB_PUBLIC EdgeNeighborhoodCompoundOperationNode void do_collect_neighbors (db::box_scanner2 &scanner, const db::Layout *layout, const db::Cell *cell) const; - template - void compute_local_impl (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const; + template + void compute_local_impl (CompoundRegionOperationCache * /*cache*/, db::Layout * /*layout*/, db::Cell * /*cell*/, const shape_interactions & /*interactions*/, std::vector > & /*results*/, const db::LocalProcessorBase * /*proc*/) const; }; } diff --git a/src/db/db/gsiDeclDbEdgeNeighborhood.cc b/src/db/db/gsiDeclDbEdgeNeighborhood.cc index 1cbed771ea..b3b770d7b4 100644 --- a/src/db/db/gsiDeclDbEdgeNeighborhood.cc +++ b/src/db/db/gsiDeclDbEdgeNeighborhood.cc @@ -59,7 +59,7 @@ class EdgeNeighborhoodVisitorImpl gsi::Callback f_on_edge; - void issue_begin_polygon (const db::Layout *, const db::Cell *, const db::Polygon &poly) + void issue_begin_polygon (const db::Layout *, const db::Cell *, const db::Polygon &) { // just for signature } @@ -148,7 +148,7 @@ Class decl_EdgeNeighborhoodVisitorImpl (decl_E "\n" "@code\n" "[\n" - " [ [ from, to ], { input_index => polygons }\n" + " [ [ from, to ], { input_index => polygons } ]\n" "]\n" "@/code\n" "\n" @@ -168,6 +168,30 @@ Class decl_EdgeNeighborhoodVisitorImpl (decl_E gsi::callback ("end_polygon", &EdgeNeighborhoodVisitorImpl::issue_end_polygon, &EdgeNeighborhoodVisitorImpl::f_end_polygon, "@brief Is called after the polygon\n" "See \\begin_polygon for a description of this protocol." + ) + + gsi::method ("output", &EdgeNeighborhoodVisitorImpl::output_polygon, gsi::arg ("polygon"), + "@brief Outputs a polygon\n" + "Use this method from one of the callbacks (\\on_edge, \\begin_polygon, \\end_polygon) to deliver a polygon. " + "Note that you have to configure the result type as 'Region' on construction of the visitor before being able to do so." + ) + + gsi::method ("output", &EdgeNeighborhoodVisitorImpl::output_edge, gsi::arg ("edge"), + "@brief Outputs an edge\n" + "Use this method from one of the callbacks (\\on_edge, \\begin_polygon, \\end_polygon) to deliver a polygon. " + "Note that you have to configure the result type as 'Edges' on construction of the visitor before being able to do so." + ) + + gsi::method ("output", &EdgeNeighborhoodVisitorImpl::output_edge_pair, gsi::arg ("edge_pair"), + "@brief Outputs an edge pair\n" + "Use this method from one of the callbacks (\\on_edge, \\begin_polygon, \\end_polygon) to deliver a polygon. " + "Note that you have to configure the result type as 'EdgePairs' on construction of the visitor before being able to do so." + ) + + gsi::method ("result_type=", &EdgeNeighborhoodVisitorImpl::set_result_type, gsi::arg ("result_type"), + "@brief Configures the result type\n" + "Use this method to indicate what type of result you want to deliver. You can use the corresponding 'output' method then to " + "deliver result shapes from one the callbacks (\\on_edge, \\begin_polygon, \\end_polygon). Set this attribute when you create " + "the visitor object. This attribute does not need to be set if no output is indended to be delivered." + ) + + gsi::method ("result_type", &EdgeNeighborhoodVisitorImpl::result_type, + "@brief Gets the result type\n" ), "@brief A visitor for the neighborhood of edges in the input\n" "\n" @@ -176,7 +200,7 @@ Class decl_EdgeNeighborhoodVisitorImpl (decl_E "\n" "See \\on_edge for the description of the events delivered." "\n" - "This class has been introduced in version 0.xx.\n" // @@@ + "This class has been introduced in version 0.29.9.\n" ); // --------------------------------------------------------------------------------- @@ -197,6 +221,8 @@ gsi::ClassExt decl_CompoundRegionOperationNode_ "@param eext The search window extension to use at the edge beginning.\n" "@param din The search window extension to use at the edge beginning.\n" "@param dout The search window extension to use at the edge beginning.\n" + "\n" + "This constructor has been introduced in version 0.29.9.\n" ) ); From 3073c1917f6c18c564a9b2e387be3daaab240b17 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 31 Oct 2024 22:55:46 +0100 Subject: [PATCH 08/13] Some enhancements: internal merging, tests --- src/db/db/dbEdgeNeighborhood.cc | 42 ++++- src/db/db/gsiDeclDbEdgeNeighborhood.cc | 6 +- src/db/unit_tests/dbEdgeNeighborhoodTests.cc | 171 +++++++++++++++++++ src/db/unit_tests/unit_tests.pro | 1 + testdata/algo/edge_neighborhood.gds | Bin 0 -> 1286 bytes testdata/algo/edge_neighborhood_au1.gds | Bin 0 -> 1622 bytes testdata/algo/edge_neighborhood_au2.gds | Bin 0 -> 4038 bytes testdata/algo/edge_neighborhood_au3.gds | Bin 0 -> 2566 bytes testdata/algo/edge_neighborhood_au4.gds | Bin 0 -> 1350 bytes testdata/algo/edge_neighborhood_au5.gds | Bin 0 -> 2438 bytes 10 files changed, 209 insertions(+), 11 deletions(-) create mode 100644 src/db/unit_tests/dbEdgeNeighborhoodTests.cc create mode 100644 testdata/algo/edge_neighborhood.gds create mode 100644 testdata/algo/edge_neighborhood_au1.gds create mode 100644 testdata/algo/edge_neighborhood_au2.gds create mode 100644 testdata/algo/edge_neighborhood_au3.gds create mode 100644 testdata/algo/edge_neighborhood_au4.gds create mode 100644 testdata/algo/edge_neighborhood_au5.gds diff --git a/src/db/db/dbEdgeNeighborhood.cc b/src/db/db/dbEdgeNeighborhood.cc index 88c6ca56ee..e4c9fbe20d 100644 --- a/src/db/db/dbEdgeNeighborhood.cc +++ b/src/db/db/dbEdgeNeighborhood.cc @@ -165,9 +165,9 @@ class EdgeCollectorReceiver return; } + // compute normal and unit vector along edge db::DVector e = db::DVector (edge.d ()); e = e * (1.0 / e.double_length ()); - db::DVector ne (-e.y (), e.x ()); // transform on the edge @@ -183,11 +183,37 @@ class EdgeCollectorReceiver db::Coord xmin = -m_bext - 1; db::Coord xmax = ref_edge.dx () + m_eext + 1; + db::SimplePolygon per_edge_clip_box (db::Box (xmin, -m_din - 1, xmax, m_dout + 1)); + + // compute the merged neighbors + std::map > merged_neighbors; + + db::EdgeProcessor ep; for (auto n = neighbors.begin (); n != neighbors.end (); ++n) { + + ep.clear (); + + size_t id = 0; + for (auto nn = n->second.begin (); nn != n->second.end (); ++nn) { + for (auto e = (*nn)->begin_edge (); ! e.at_end (); ++e) { + ep.insert (itrans * (move * *e), id); + } + id += 2; + } + + ep.insert (per_edge_clip_box, size_t (1)); + + db::BooleanOp and_op (db::BooleanOp::And); + db::PolygonContainer pc (merged_neighbors [n->first]); + db::PolygonGenerator pg (pc, false); + ep.process (pg, and_op); + + } + + for (auto n = merged_neighbors.begin (); n != merged_neighbors.end (); ++n) { for (auto p = n->second.begin (); p != n->second.end (); ++p) { - db::Polygon poly = itrans * (move * **p); - for (auto p = poly.begin_edge (); ! p.at_end (); ++p) { - db::Edge e = *p; + for (auto pe = p->begin_edge (); ! pe.at_end (); ++pe) { + db::Edge e = *pe; xpos.insert (std::max (xmin, std::min (xmax, e.p1 ().x ()))); xpos.insert (std::max (xmin, std::min (xmax, e.p2 ().x ()))); } @@ -210,12 +236,12 @@ class EdgeCollectorReceiver db::Box clip_box (xfrom, -m_din - 1, xto, m_dout + 1); // NOTE: this could be more efficient if we had a multi-layer capable trapezoid decomposition tool - for (auto n = neighbors.begin (); n != neighbors.end (); ++n) { + for (auto n = merged_neighbors.begin (); n != merged_neighbors.end (); ++n) { + + EdgeNeighborhoodVisitor::neighbor_shapes_type polygons; - EdgeNeighborhoodVisitor::neighbor_shapes_type polygons; for (auto p = n->second.begin (); p != n->second.end (); ++p) { - db::Polygon poly = itrans * (move * **p); - db::clip_poly (poly, clip_box, polygons, false); + db::clip_poly (*p, clip_box, polygons, false); } if (!polygons.empty ()) { diff --git a/src/db/db/gsiDeclDbEdgeNeighborhood.cc b/src/db/db/gsiDeclDbEdgeNeighborhood.cc index b3b770d7b4..ede33d277a 100644 --- a/src/db/db/gsiDeclDbEdgeNeighborhood.cc +++ b/src/db/db/gsiDeclDbEdgeNeighborhood.cc @@ -218,9 +218,9 @@ gsi::ClassExt decl_CompoundRegionOperationNode_ "@param children The inputs to use. The first one in the primary input, the others are neighbors.\n" "@param visitor The visitor object (see \\EdgeNeighborhoodVisitor) receiving the edge events.\n" "@param bext The search window extension to use at the edge beginning.\n" - "@param eext The search window extension to use at the edge beginning.\n" - "@param din The search window extension to use at the edge beginning.\n" - "@param dout The search window extension to use at the edge beginning.\n" + "@param eext The search window extension to use at the edge end.\n" + "@param din The search window extension to the 'outside' of the edge.\n" + "@param dout The search window extension to the 'inside' of the edge.\n" "\n" "This constructor has been introduced in version 0.29.9.\n" ) diff --git a/src/db/unit_tests/dbEdgeNeighborhoodTests.cc b/src/db/unit_tests/dbEdgeNeighborhoodTests.cc new file mode 100644 index 0000000000..27bebd33c6 --- /dev/null +++ b/src/db/unit_tests/dbEdgeNeighborhoodTests.cc @@ -0,0 +1,171 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2024 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#include "tlUnitTest.h" + +#include "dbRegion.h" +#include "dbEdgeNeighborhood.h" +#include "dbReader.h" +#include "dbTestSupport.h" + +#include "tlStream.h" + +#include + +namespace +{ + +class ENPrimaryCopyVisitor + : public db::EdgeNeighborhoodVisitor +{ +public: + ENPrimaryCopyVisitor () + { + set_result_type (db::CompoundRegionOperationNode::ResultType::Region); + } + + void begin_polygon (const db::Layout *, const db::Cell *, const db::Polygon &polygon) + { + output_polygon (polygon); + } +}; + +class ENPrimaryCopyIntruderVisitor + : public db::EdgeNeighborhoodVisitor +{ +public: + ENPrimaryCopyIntruderVisitor (unsigned int input) + { + set_result_type (db::CompoundRegionOperationNode::ResultType::Region); + m_input = input; + } + + void on_edge (const db::Layout * /*layout*/, const db::Cell * /*cell*/, const db::Edge &edge, const neighbors_type &neighbors) + { + // Compute transformation to original edge + db::DVector e = db::DVector (edge.d ()); + e = e * (1.0 / e.double_length ()); + db::DVector ne (-e.y (), e.x ()); + + db::IMatrix2d trans (e.x (), ne.x (), e.y (), ne.y ()); + db::Disp move (edge.p1 () - db::Point ()); + + for (auto n = neighbors.begin (); n != neighbors.end (); ++n) { + for (auto nn = n->second.begin (); nn != n->second.end (); ++nn) { + if (nn->first == m_input) { + for (auto p = nn->second.begin (); p != nn->second.end (); ++p) { + output_polygon (move * (trans * *p)); + } + } + } + } + } + +private: + unsigned int m_input; +}; + +} + +static void prep_layer (db::Layout &ly, int gds_layer, db::Region &r, db::DeepShapeStore &dss, bool deep) +{ + unsigned int li = ly.get_layer (db::LayerProperties (gds_layer, 0)); + if (deep) { + r = db::Region (db::RecursiveShapeIterator (ly, ly.cell (*ly.begin_top_down ()), li), dss); + } else { + r = db::Region (db::RecursiveShapeIterator (ly, ly.cell (*ly.begin_top_down ()), li)); + } +} + +static void run_test (tl::TestBase *_this, db::EdgeNeighborhoodVisitor &visitor, const std::string &au_name, bool deep = true, db::Coord bext = 0, db::Coord eext = 0, db::Coord din = 0, db::Coord dout = 0) +{ + db::Layout ly; + { + std::string fn (tl::testdata ()); + fn += "/algo/edge_neighborhood.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly); + } + + db::DeepShapeStore dss; + + db::Region r1, r2, r3; + prep_layer (ly, 1, r1, dss, deep); + prep_layer (ly, 2, r2, dss, deep); + prep_layer (ly, 3, r3, dss, deep); + + std::vector children; + children.push_back (new db::CompoundRegionOperationPrimaryNode ()); + children.push_back (new db::CompoundRegionOperationForeignNode ()); + children.push_back (new db::CompoundRegionOperationSecondaryNode (&r2)); + children.push_back (new db::CompoundRegionOperationSecondaryNode (&r3)); + + db::EdgeNeighborhoodCompoundOperationNode en_node (children, &visitor, bext, eext, din, dout); + + unsigned int l100 = ly.get_layer (db::LayerProperties (100, 0)); + + if (en_node.result_type () == db::CompoundRegionOperationNode::ResultType::Region) { + auto res = r1.cop_to_region (en_node); + res.insert_into (&ly, *ly.begin_top_down (), l100); + } else if (en_node.result_type () == db::CompoundRegionOperationNode::ResultType::Edges) { + auto res = r1.cop_to_edges (en_node); + res.insert_into (&ly, *ly.begin_top_down (), l100); + } else if (en_node.result_type () == db::CompoundRegionOperationNode::ResultType::EdgePairs) { + auto res = r1.cop_to_edge_pairs (en_node); + res.insert_into (&ly, *ly.begin_top_down (), l100); + } + + db::compare_layouts (_this, ly, tl::testdata () + au_name); +} + +TEST(1) +{ + ENPrimaryCopyVisitor visitor; + run_test (_this, visitor, "/algo/edge_neighborhood_au1.gds"); +} + + +TEST(2) +{ + ENPrimaryCopyIntruderVisitor visitor (0); + run_test (_this, visitor, "/algo/edge_neighborhood_au2.gds", true, 100, 100, 100, 2000); +} + +TEST(3) +{ + ENPrimaryCopyIntruderVisitor visitor (1); + run_test (_this, visitor, "/algo/edge_neighborhood_au3.gds", true, 100, 100, 100, 2000); +} + +TEST(4) +{ + ENPrimaryCopyIntruderVisitor visitor (2); + run_test (_this, visitor, "/algo/edge_neighborhood_au4.gds", true, 100, 100, 100, 2000); +} + +TEST(5) +{ + ENPrimaryCopyIntruderVisitor visitor (3); + run_test (_this, visitor, "/algo/edge_neighborhood_au5.gds", true, 100, 100, 100, 2000); +} diff --git a/src/db/unit_tests/unit_tests.pro b/src/db/unit_tests/unit_tests.pro index a5ef9690ec..67e1318940 100644 --- a/src/db/unit_tests/unit_tests.pro +++ b/src/db/unit_tests/unit_tests.pro @@ -8,6 +8,7 @@ include($$PWD/../../lib_ut.pri) SOURCES = \ dbCompoundOperationTests.cc \ + dbEdgeNeighborhoodTests.cc \ dbFillToolTests.cc \ dbLogTests.cc \ dbRecursiveInstanceIteratorTests.cc \ diff --git a/testdata/algo/edge_neighborhood.gds b/testdata/algo/edge_neighborhood.gds new file mode 100644 index 0000000000000000000000000000000000000000..75c723e1c4c3332cd86dd37ceb7de9e7642ffd42 GIT binary patch literal 1286 zcmaizJ5L)?5QUG|=B@=!NCe`cSaPH^QG^mi!=pevC89)%3ksBqk}_p}Leix~O5+ZG zf=e2sxIl7>NE8$AH^QOV>EsI{r3ynKgs%MzCDut>v{Zs*2tPU1I}%*Y&1P+q5jHf{&Sqan=hb@ z3b)Yv!K&dF-L{`IXC04i*MaE$j~8!Lk5Qo!u6eZbCVbFrRukwkNe(v@7{CIyKu;|O;mDOv58?+F^&;T z$Nwm0?&I1r@FYLq0;i7z7OEg~@U40zr)w0OV-U*u1T>x!){k@mcZp~e0A zN&fMU@>xf|$#V=XKKTBtNv)sA{2zboA?w%m^!?0{Ia34b)}A-Cc!-7k%U%7)slS`Z zp@cHK(A;Cza8KQm*O^+U_ny{((K`2fFAUtPl}v4N@B9Dk6*;Ym{5|L1mCjx-CH<1c ze);!<*@_S^zUsfB#eFq#zaoC9GHC3p4W9Ad>LRsg++%{zJGrh%@3pqngG7s``M`^Y z$mOacd-d`}dq%Xl$N2tyuYM}3chf8U-h4N-c)$c-J3U%g^qs+jL%`7DzPm~OuU8_C zBN5`|s(Q@O;*Fy;|7w!?(brmc>{*L@jT86lTcK|38^7vefUfuXX8d~KcRcL%>-RgW z4xRH}`_S*T?T*%ObvD@>wl{6qn-TG>&{@!zyPbF6Sr_ruXLp=+5#MHs;w@)QAO9}1 Jrs#Do;2H<-=`8>N literal 0 HcmV?d00001 diff --git a/testdata/algo/edge_neighborhood_au2.gds b/testdata/algo/edge_neighborhood_au2.gds new file mode 100644 index 0000000000000000000000000000000000000000..209b56eb017151973a5399418b6c28802bcb28a5 GIT binary patch literal 4038 zcma)9OG{Nz6kgt|$4gljJuDOzorLs;=rIU;K~azc1!?4$ad+k2h$*xy=vE16U((=yOe>0Xyz zG9-uPgd9r$mRcpdbL*i~{`hv|;`R5ZW?$c%l!2}mc3C!gcS5AICX(%` zNQY#5D7B)1)k4d?mP!9K_FikV zH-ygJ471-@Yf^zn@C=>NzW^>k6FeVYqn7{s;wrX_TUvA zd-%k?b=jAbG{qe>Kk^6*b?`3VxSf5y?k%?Cqp` zXASjaWcPa!B6&Ss*M9Lr2y1g80RP-HjS%M&+e+Vm6 zmOl~ovvuD-`qi3!ePem2vlX?pb#5cX^~3j}ZN`Cm!Y`0~saFg3 z>1;=DZ9T^1G<0VBHv5rzy93JclOLJ)^0zwycM|W0l_|R$faG=Fx%vP7V*Lh0+~_+Z z;mP9s{>bBpf6fc?gFig~_NJ&lX`t3&dD!xfNdAd$ zk8{UcfI8s01Ig>$ZxO%g1$m&}tb5u6oK-cu>yq==x6BKx`Pd7q8{Rhfjn&CVbp2!Q zK(9PU)X(N4`S$jhSNIO!L`3qXS)Ighy6vAf#JNHLMDlv~xbdTZV!tTH{c2AnU%IOj z|B3D%+8luX;(bpYDz87+Kk5d5K(~4==A_Qg#@tkd z|Hl75uK)ah1NHgRdcKJ|7i`^&-}*=ImpH%6=TF|hf12^dI5GP$w}}4}Kjtpq8JIh( zD>jFKOr_g7#Ba&}-f3Z(t QhxZ?mJmc@yIo2uv0AVXbX#fBK literal 0 HcmV?d00001 diff --git a/testdata/algo/edge_neighborhood_au3.gds b/testdata/algo/edge_neighborhood_au3.gds new file mode 100644 index 0000000000000000000000000000000000000000..563bc732230bae7922a9c8407f4b5040b46dc0fd GIT binary patch literal 2566 zcmai0ziU%b6h3)L-wU={MQJN3B;X<kW?#HRLsyJx-^4Bp^KycKtXYF6dcSV z`X|(_OT|)zW)LDcWN^sfkRi|vh2#0|c_+E4-sfAs;k{+F) zGR@HvRsG*ojN*s)Zxfx%Pu*I6b?w2IkNq1f+e@u?PuFO1D#I?0*B;l23Pqy$WJEcN zPenv@`E;IncY`Q7P%PJ&+Z7^1sdkF&^J8gnuC${ab{%Q z*{2$o`c3K`S-Z}O*6vPf?b*22dZuuuakdJNM4dmzXuRmfk9EQxvjLCIhIU0~L%U); zFk~;M?7ij*{ChTx)`R~2*?@QvzoPa{*F*j>i#5(s+*@hCbw%8^XV?hwaU8yR5qN?^eX2>tTEJJ=xU$r*py8!_5!lGxUf0K_}Wr z>zmb4`7Hf7|B|Qa5PtDK=Rf7M>TkLQ`g0QY(2uT%@*kf&`!>S4!yK`5=+0;QZI#Gq GE6^W_-Vn+F literal 0 HcmV?d00001 diff --git a/testdata/algo/edge_neighborhood_au4.gds b/testdata/algo/edge_neighborhood_au4.gds new file mode 100644 index 0000000000000000000000000000000000000000..451dd3bd1fd5e2dcab0490d4bbbf0da0d2132700 GIT binary patch literal 1350 zcmb7@ze`(D6vw}LNp3=GtD>l-ffTxwQcQ5sI*73fiiKv#5IQ+@aO~JWq1`$bx@6No z!O=z0L9m11;E=%~gF}W48HD5g-s4GLpf{y&`8>Y&oOjQU?}I~i@PvyW_t-!Ym#{{H&~?Vn`*Gv9|~|9T$3pEa^(&VY04SB<6zE!00TnExE-@8&Zo zVuD-f{AAT|FI>&fnX`_^p4WlN7Gu2^#>Q$VFPn`0@Q+xL^Sa2*HDfm)WBn-Umn7|% z+e20>Lf(DTccbaPoAka?`Ju}&W8ZD?jQ7@noIQ2VNIvi6xsu-N?x_b!)8qNTVq2uQ zt>mtj&U9y_>7MEPv%~&5QN5d9;j{Y7XnJ5IUpGD3Qu@u{=@DQw-ETLVf3YIcJ{BQ+ z+v+i+>Fwiq{`~^$qp!8^v}aBCGmhM^{|e{Ez40lp2MoN=f5xAO{6`P_`HlLWU5Cwi TuWgNb?Mr&r|Ff%t|4t4MVa0|% literal 0 HcmV?d00001 diff --git a/testdata/algo/edge_neighborhood_au5.gds b/testdata/algo/edge_neighborhood_au5.gds new file mode 100644 index 0000000000000000000000000000000000000000..fda0698d3b97e42a8101a9b4788d462703311068 GIT binary patch literal 2438 zcma);ziU%b6vt0q+Lxf!DoRsnp@NGPsU-;h7^KyT6$LYN2rlj5(9zMwU2ze*2o44u z^iSy6C1NN-1q&5AWN_#p^bLUqTDYF?J^3c@RqxZce8Rc+`_9jMUnG_&s>@^{D(%S$ znUXQNCNt^ZQjX$B5AKMZFAm?He|7!g=a0$Fh4rhAcTbjNayUm9$4ifEB12`7_*5hX ziBCr&a^+0Xx#p@!Y0Ee@=iIl8A|&mlEg3rdSG`E(nR7b{D01nt$aI&a-7}zfZo71(Xn`kB$$>JznxT^Z2BC)SOa zy=28^5Tre<7nx~0*I6{8SJf}pGmv&=+JAA7KPqazF)Mg(y(MY)4Ct-XqYa~WMozAa zkhE)egZi&GoNI452X+>1#z@-R8)5y?IO4-xTi)rJrCn?6THAWQ9{#Z}X_~%I+H?FPZfMyncv>$AS{o`)kKeW4*GeXk7e;C%| zZcCQ!9;J??Jz37tzwYUk(BXZg*ZcLb{`&`uAA}D1kci*b!+Nca-jt}7oiY9L`LhPn ze(bDe`k(fXKJNa|e0uyJ|DF5h2b`#DA72az|F#~AU%#bm*6-;x?YTbH7D>Bj`mCnn zSKse3ub}BF+a&FtK|TEAPGlU_miYx~KXzAU{6h!-d=GUX?Rn=HGmi7-&yUtU{ojE0 ZJU#k{esO-#Kg Date: Thu, 31 Oct 2024 23:43:27 +0100 Subject: [PATCH 09/13] RBA tests, reproducible order of edge events, all edges are reported - even those which do not have neighbors. --- src/db/db/dbEdgeNeighborhood.cc | 26 ++++-- src/rba/unit_tests/rbaTests.cc | 1 + testdata/algo/edge_neighborhood_au2.gds | Bin 4038 -> 4230 bytes testdata/algo/edge_neighborhood_au3.gds | Bin 2566 -> 2822 bytes testdata/algo/edge_neighborhood_au5.gds | Bin 2438 -> 2630 bytes testdata/ruby/dbEdgeNeighborhood.rb | 114 ++++++++++++++++++++++++ 6 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 testdata/ruby/dbEdgeNeighborhood.rb diff --git a/src/db/db/dbEdgeNeighborhood.cc b/src/db/db/dbEdgeNeighborhood.cc index e4c9fbe20d..f1318adb6a 100644 --- a/src/db/db/dbEdgeNeighborhood.cc +++ b/src/db/db/dbEdgeNeighborhood.cc @@ -117,7 +117,7 @@ EdgeNeighborhoodCompoundOperationNode::EdgeNeighborhoodCompoundOperationNode (co db::Coord EdgeNeighborhoodCompoundOperationNode::computed_dist () const { - return std::max (std::max (m_bext, m_eext), std::max (m_din, m_dout)); + return std::max (std::max (m_bext, m_eext), std::max (m_din, m_dout)) + 1; } std::string @@ -140,25 +140,41 @@ class EdgeCollectorReceiver // .. nothing yet .. } - void add (const db::Edge *o1, const unsigned int & /*p1*/, const db::Polygon *o2, const unsigned int &p2) + void add (const db::Edge *o1, const unsigned int &p1, const db::Polygon *o2, const unsigned int &p2) { - m_edge_neighbors[o1][p2].push_back (o2); + m_edge_neighbors[p1][p2].push_back (o2); + enter_edge (o1, p1); + } + + void finish1 (const db::Edge *o1, const unsigned int &p1) + { + m_edge_neighbors[p1]; + enter_edge (o1, p1); } void finalize (bool) { for (auto en = m_edge_neighbors.begin (); en != m_edge_neighbors.end (); ++en) { - commit_edge (*en->first, en->second); + commit_edge (m_edges[en->first], en->second); } } private: - std::map > > m_edge_neighbors; + std::map > > m_edge_neighbors; + std::vector m_edges; db::EdgeNeighborhoodVisitor *mp_visitor; const db::Layout *mp_layout; const db::Cell *mp_cell; db::Coord m_bext, m_eext, m_din, m_dout; + void enter_edge (const db::Edge *o1, const unsigned int &p1) + { + while (size_t (p1) >= m_edges.size ()) { + m_edges.push_back (db::Edge ()); + } + m_edges[p1] = *o1; + } + void commit_edge (const db::Edge &edge, const std::map > &neighbors) const { if (edge.is_degenerate ()) { diff --git a/src/rba/unit_tests/rbaTests.cc b/src/rba/unit_tests/rbaTests.cc index c34b71bda7..d12563a2f1 100644 --- a/src/rba/unit_tests/rbaTests.cc +++ b/src/rba/unit_tests/rbaTests.cc @@ -101,6 +101,7 @@ RUBYTEST (dbEdgePairsTest, "dbEdgePairsTest.rb") RUBYTEST (dbEdgePairTest, "dbEdgePairTest.rb") RUBYTEST (dbEdgesTest, "dbEdgesTest.rb") RUBYTEST (dbEdgeTest, "dbEdgeTest.rb") +RUBYTEST (dbEdgeNeighborhood, "dbEdgeNeighborhood.rb") RUBYTEST (dbGlyphs, "dbGlyphs.rb") RUBYTEST (dbHierNetworkProcessorTests, "dbHierNetworkProcessorTests.rb") RUBYTEST (dbInstanceTest, "dbInstanceTest.rb") diff --git a/testdata/algo/edge_neighborhood_au2.gds b/testdata/algo/edge_neighborhood_au2.gds index 209b56eb017151973a5399418b6c28802bcb28a5..a3be46111b7ffd8780c1bfe61d8ea5cac22f2b9f 100644 GIT binary patch delta 359 zcmX>m-=?U+SLBqt^FHElDi4p@TVSWKo z3X%dT1xmeQ*P1K>H2Dh9)JdF9lXo!kOiti5nH<6aw26J+_>}+|p?t{z delta 251 zcmZouJSMNiz{bGD6u}_F$i)7Ffr~+&L5x9{K@^!iQAu48LvUl%E|$q5tPPVb7zHLj zVKbO4!X_}8gOO+Q2UeEJCTt0lMOYmsPhqp0EX3n9IfQ$k#Q*;{J}@vazkty44*&n( z3}s-DcbNQ&Q)}`R4xmDAph6zI$w6EWlZCjPChq_m_JGS|@)RIngac?8d&A@_KzSif zr^#1<>Vr6(7K`vRPM*T)Fu4M#R)Lp=O}K-Bfx~oiAEWH#}B3!*KFBMzKk3B9lc} z9n>z$Lv({^d58c1FDf8tkm(Tl$-mfUO|D?%nf!p=WbzbthsirYY}PMoa7O?g1JVw1 b42%Xl4(!OuyV&9;OK`4WV`pGsVPOCO%w1>6 delta 119 zcmZn@YZFmoU}IonieQjoWMY58z{McXAjY82V1Ue?sH854A-FMW7t7=jc8AF#taX#O zG4f4*!0~0W2$##`8|*ccr*Jq-u3+SuoWNnSScHvnatPO&$qlRyZ0rmSEG!HFNu?Sp diff --git a/testdata/algo/edge_neighborhood_au5.gds b/testdata/algo/edge_neighborhood_au5.gds index fda0698d3b97e42a8101a9b4788d462703311068..7d1ce189c3d994c550885770f7a845cef5e1ba2c 100644 GIT binary patch delta 198 zcmZn@J|?2Xz{bGD6u}_F$i)7Ffr~+&L7YLAK^U1mQAu48LvUl%F4oB+><*Jf*aEn2 zeE9$Wi^u=}H$F_xV_TxZz`%atKM;Uu1~3rrfYC5<5Ixz7{g#?=2Sf<2OE8^J1*g!VE|GoJadk-T3R8L0} delta 89 zcmX>m(k86Lz{bGD6u}_F$i)7Ffr~+&L5#tKK@XWdQAu48LvUl%F4oB*Yyp#(a0pD^ V!ZvTR5S!EFDeMO}U*RZX1OTAq6G#96 diff --git a/testdata/ruby/dbEdgeNeighborhood.rb b/testdata/ruby/dbEdgeNeighborhood.rb new file mode 100644 index 0000000000..5624fc3a86 --- /dev/null +++ b/testdata/ruby/dbEdgeNeighborhood.rb @@ -0,0 +1,114 @@ +# encoding: UTF-8 + +# KLayout Layout Viewer +# Copyright (C) 2006-2024 Matthias Koefferlein +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +if !$:.member?(File::dirname($0)) + $:.push(File::dirname($0)) +end + +load("test_prologue.rb") + +class MyVisitor < RBA::EdgeNeighborhoodVisitor + + def initialize + @log = "" + end + + def log + @log + end + + def begin_polygon(layout, cell, polygon) + output(polygon) + @log += "Polygon: #{polygon}\n" + end + + def end_polygon + @log += "/Polygon\n" + end + + def on_edge(layout, cell, edge, neighborhood) + @log += "edge = #{edge}\n" + neighborhood.each do |n| + x1, x2 = n[0] + polygons = n[1] + polygons.each do |inp, poly| + poly_str = poly.collect { |p| p.to_s }.join("/") + @log += " #{x1},#{x2} -> #{inp}: #{poly_str}\n" + end + end + end + +end + +class DBEdgeNeighborhood_TestClass < TestBase + + def test_1 + + ly = RBA::Layout::new + + l1 = ly.layer(1, 0) + cell = ly.create_cell("TOP") + + cell.shapes(l1).insert(RBA::Box::new(0, 0, 1000, 1000)) + cell.shapes(l1).insert(RBA::Box::new(-1100, 0, -100, 1000)) + + prim = RBA::Region::new(cell.begin_shapes_rec(l1)) + + visitor = MyVisitor::new + + visitor.result_type = RBA::CompoundRegionOperationNode::ResultType::Region + assert_equal(visitor.result_type, RBA::CompoundRegionOperationNode::ResultType::Region) + + bext = 0 + eext = 0 + din = 10 + dout = 100 + + children = [ + RBA::CompoundRegionOperationNode::new_foreign + ] + + node = RBA::CompoundRegionOperationNode::new_edge_neighborhood(children, visitor, bext, eext, din, dout) + res = prim.complex_op(node) + + assert_equal(visitor.log, + "Polygon: (0,0;0,1000;1000,1000;1000,0)\n" + + "edge = (0,0;0,1000)\n" + + " 0.0,1000.0 -> 0: (0,100;0,101;1000,101;1000,100)\n" + + "edge = (0,1000;1000,1000)\n" + + "edge = (1000,1000;1000,0)\n" + + "edge = (1000,0;0,0)\n" + + "/Polygon\n" + + "Polygon: (-1100,0;-1100,1000;-100,1000;-100,0)\n" + + "edge = (-1100,0;-1100,1000)\n" + + "edge = (-1100,1000;-100,1000)\n" + + "edge = (-100,1000;-100,0)\n" + + " 0.0,1000.0 -> 0: (0,100;0,101;1000,101;1000,100)\n" + + "edge = (-100,0;-1100,0)\n" + + "/Polygon\n" + ) + + assert_equal(res.to_s, "(-1100,0;-1100,1000;-100,1000;-100,0);(0,0;0,1000;1000,1000;1000,0)") + + end + +end + +load("test_epilogue.rb") + From dd0949867fa015ec28939d031a126c17873165bb Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 1 Nov 2024 17:34:46 +0100 Subject: [PATCH 10/13] Some convenience features: transformations in EdgeNeighborhood, added Matrix transformation support for edge pairs. --- src/db/db/dbEdgeNeighborhood.cc | 37 ++++++++++---- src/db/db/dbEdgeNeighborhood.h | 11 ++++ src/db/db/gsiDeclDbEdgeNeighborhood.cc | 19 ++++++- src/db/db/gsiDeclDbMatrix.cc | 25 +++++++++ src/db/unit_tests/dbEdgeNeighborhoodTests.cc | 10 +--- testdata/ruby/dbEdgeNeighborhood.rb | 54 ++++++++++++++++++++ testdata/ruby/dbMatrix.rb | 4 ++ 7 files changed, 140 insertions(+), 20 deletions(-) diff --git a/src/db/db/dbEdgeNeighborhood.cc b/src/db/db/dbEdgeNeighborhood.cc index f1318adb6a..2e4f7631b9 100644 --- a/src/db/db/dbEdgeNeighborhood.cc +++ b/src/db/db/dbEdgeNeighborhood.cc @@ -27,6 +27,17 @@ namespace db { +static db::IMatrix3d to_original_trans (const db::Edge &edge) +{ + // compute normal and unit vector along edge + db::DVector e = db::DVector (edge.d ()); + e = e * (1.0 / e.double_length ()); + db::DVector ne (-e.y (), e.x ()); + + // transform on the edge + return db::IMatrix3d (e.x (), ne.x (), e.y (), ne.y (), edge.p1 ().x (), edge.p1 ().y (), 0.0, 0.0); +} + // -------------------------------------------------------------------------------------------------- EdgeNeighborhoodVisitor::EdgeNeighborhoodVisitor () @@ -105,6 +116,18 @@ EdgeNeighborhoodVisitor::output_edge_pair (const db::EdgePair &edge_pair) mp_edge_pairs->insert (edge_pair); } +db::IMatrix3d +EdgeNeighborhoodVisitor::to_original_trans (const db::Edge &edge) +{ + return db::to_original_trans (edge); +} + +db::IMatrix3d +EdgeNeighborhoodVisitor::to_edge_local_trans (const db::Edge &edge) +{ + return db::to_original_trans (edge).inverted (); +} + // -------------------------------------------------------------------------------------------------- EdgeNeighborhoodCompoundOperationNode::EdgeNeighborhoodCompoundOperationNode (const std::vector &children, EdgeNeighborhoodVisitor *visitor, db::Coord bext, db::Coord eext, db::Coord din, db::Coord dout) @@ -181,17 +204,9 @@ class EdgeCollectorReceiver return; } - // compute normal and unit vector along edge - db::DVector e = db::DVector (edge.d ()); - e = e * (1.0 / e.double_length ()); - db::DVector ne (-e.y (), e.x ()); - - // transform on the edge - db::IMatrix2d trans (e.x (), ne.x (), e.y (), ne.y ()); - db::IMatrix2d itrans = trans.inverted (); - db::Disp move (db::Point () - edge.p1 ()); + db::IMatrix3d from_original_trans = db::to_original_trans (edge).inverted (); - db::Edge ref_edge = itrans * (move * edge); + db::Edge ref_edge = from_original_trans * edge; tl_assert (ref_edge.dy () == 0); tl_assert (ref_edge.dx () > 0); @@ -212,7 +227,7 @@ class EdgeCollectorReceiver size_t id = 0; for (auto nn = n->second.begin (); nn != n->second.end (); ++nn) { for (auto e = (*nn)->begin_edge (); ! e.at_end (); ++e) { - ep.insert (itrans * (move * *e), id); + ep.insert (from_original_trans * *e, id); } id += 2; } diff --git a/src/db/db/dbEdgeNeighborhood.h b/src/db/db/dbEdgeNeighborhood.h index 3566b336da..f8fcd48ac1 100644 --- a/src/db/db/dbEdgeNeighborhood.h +++ b/src/db/db/dbEdgeNeighborhood.h @@ -28,6 +28,7 @@ #include "dbCommon.h" #include "dbCompoundOperation.h" #include "dbBoxScanner.h" +#include "dbMatrix.h" namespace db { @@ -97,6 +98,16 @@ class DB_PUBLIC EdgeNeighborhoodVisitor */ virtual void on_edge (const db::Layout * /*layout*/, const db::Cell * /*cell*/, const db::Edge & /*edge*/, const neighbors_type & /*neighbors*/) { } + /** + * @brief Gets a transformation to transform from edge-local space to original space + */ + static db::IMatrix3d to_original_trans (const db::Edge &edge); + + /** + * @brief Gets a transformation to transform from original space into edge-local space + */ + static db::IMatrix3d to_edge_local_trans (const db::Edge &edge); + /** * @brief Sets the result type */ diff --git a/src/db/db/gsiDeclDbEdgeNeighborhood.cc b/src/db/db/gsiDeclDbEdgeNeighborhood.cc index ede33d277a..7138abc3c9 100644 --- a/src/db/db/gsiDeclDbEdgeNeighborhood.cc +++ b/src/db/db/gsiDeclDbEdgeNeighborhood.cc @@ -172,17 +172,34 @@ Class decl_EdgeNeighborhoodVisitorImpl (decl_E gsi::method ("output", &EdgeNeighborhoodVisitorImpl::output_polygon, gsi::arg ("polygon"), "@brief Outputs a polygon\n" "Use this method from one of the callbacks (\\on_edge, \\begin_polygon, \\end_polygon) to deliver a polygon. " - "Note that you have to configure the result type as 'Region' on construction of the visitor before being able to do so." + "Note that you have to configure the result type as 'Region' on construction of the visitor before being able to do so.\n" + "\n" + "'output' expects an object in original space - i.e. of the input edge. \\to_original_trans gives you a suitable " + "transformation to bring objects from 'edge is horizontal' space into the original space." ) + gsi::method ("output", &EdgeNeighborhoodVisitorImpl::output_edge, gsi::arg ("edge"), "@brief Outputs an edge\n" "Use this method from one of the callbacks (\\on_edge, \\begin_polygon, \\end_polygon) to deliver a polygon. " "Note that you have to configure the result type as 'Edges' on construction of the visitor before being able to do so." + "\n" + "'output' expects an object in original space - i.e. of the input edge. \\to_original_trans gives you a suitable " + "transformation to bring objects from 'edge is horizontal' space into the original space." ) + gsi::method ("output", &EdgeNeighborhoodVisitorImpl::output_edge_pair, gsi::arg ("edge_pair"), "@brief Outputs an edge pair\n" "Use this method from one of the callbacks (\\on_edge, \\begin_polygon, \\end_polygon) to deliver a polygon. " "Note that you have to configure the result type as 'EdgePairs' on construction of the visitor before being able to do so." + "\n" + "'output' expects an object in original space - i.e. of the input edge. \\to_original_trans gives you a suitable " + "transformation to bring objects from 'edge is horizontal' space into the original space." + ) + + gsi::method ("to_original_trans", &EdgeNeighborhoodVisitorImpl::to_original_trans, gsi::arg ("edge"), + "@brief For a given edge, computes the transformation that brings objects from the normalized space (edge is horizontal) to the original space of the edge.\n" + "Use this method to compute the objects suitable for 'output', after you derived them in edge-local space.\n" + ) + + gsi::method ("to_edge_local_trans", &EdgeNeighborhoodVisitorImpl::to_edge_local_trans, gsi::arg ("edge"), + "@brief For a given edge, computes the transformation that brings objects from original space to the edge-local space where the edge is horizontal.\n" + "Technically, this transformation is the inverse of \\to_original_trans.\n" ) + gsi::method ("result_type=", &EdgeNeighborhoodVisitorImpl::set_result_type, gsi::arg ("result_type"), "@brief Configures the result type\n" diff --git a/src/db/db/gsiDeclDbMatrix.cc b/src/db/db/gsiDeclDbMatrix.cc index cd1be88e21..f539f83589 100644 --- a/src/db/db/gsiDeclDbMatrix.cc +++ b/src/db/db/gsiDeclDbMatrix.cc @@ -27,6 +27,7 @@ #include "dbBox.h" #include "dbPolygon.h" #include "dbEdge.h" +#include "dbEdgePair.h" namespace gsi { @@ -130,6 +131,12 @@ static db::edge trans_edge (const db::matrix_2d *m, const db::edge &e) return e.transformed (*m); } +template +static db::edge_pair trans_edge_pair (const db::matrix_2d *m, const db::edge_pair &e) +{ + return e.transformed (*m); +} + template static double coeff_m (const db::matrix_2d *m, int i, int j) { @@ -234,6 +241,12 @@ matrix2d_methods () "@param e The edge to transform.\n" "@return The transformed edge\n" ) + + gsi::method_ext ("*", &trans_edge_pair, gsi::arg ("ep"), + "@brief Transforms an edge pair with this matrix.\n" + "@param ep The edge pair to transform.\n" + "@return The transformed edge\n" + "This variant has been added in version 0.29.9." + ) + gsi::method_ext ("*", &trans_box, gsi::arg ("box"), "@brief Transforms a box with this matrix.\n" "@param box The box to transform.\n" @@ -445,6 +458,12 @@ static db::edge trans_edge3 (const db::matrix_3d *m, const db::edge &e) return e.transformed (*m); } +template +static db::edge_pair trans_edge_pair3 (const db::matrix_3d *m, const db::edge_pair &e) +{ + return e.transformed (*m); +} + template static double coeff_m3 (const db::matrix_3d *m, int i, int j) { @@ -617,6 +636,12 @@ matrix3d_methods () "@param e The edge to transform.\n" "@return The transformed edge\n" ) + + gsi::method_ext ("*", &trans_edge_pair3, gsi::arg ("ep"), + "@brief Transforms an edge pair with this matrix.\n" + "@param ep The edge pair to transform.\n" + "@return The transformed edge pair\n" + "This variant has been added in version 0.29.9." + ) + gsi::method_ext ("*", &trans_box3, gsi::arg ("box"), "@brief Transforms a box with this matrix.\n" "@param box The box to transform.\n" diff --git a/src/db/unit_tests/dbEdgeNeighborhoodTests.cc b/src/db/unit_tests/dbEdgeNeighborhoodTests.cc index 27bebd33c6..36a881c8d9 100644 --- a/src/db/unit_tests/dbEdgeNeighborhoodTests.cc +++ b/src/db/unit_tests/dbEdgeNeighborhoodTests.cc @@ -62,19 +62,13 @@ class ENPrimaryCopyIntruderVisitor void on_edge (const db::Layout * /*layout*/, const db::Cell * /*cell*/, const db::Edge &edge, const neighbors_type &neighbors) { - // Compute transformation to original edge - db::DVector e = db::DVector (edge.d ()); - e = e * (1.0 / e.double_length ()); - db::DVector ne (-e.y (), e.x ()); - - db::IMatrix2d trans (e.x (), ne.x (), e.y (), ne.y ()); - db::Disp move (edge.p1 () - db::Point ()); + db::IMatrix3d trans = to_original_trans (edge); for (auto n = neighbors.begin (); n != neighbors.end (); ++n) { for (auto nn = n->second.begin (); nn != n->second.end (); ++nn) { if (nn->first == m_input) { for (auto p = nn->second.begin (); p != nn->second.end (); ++p) { - output_polygon (move * (trans * *p)); + output_polygon (trans * *p); } } } diff --git a/testdata/ruby/dbEdgeNeighborhood.rb b/testdata/ruby/dbEdgeNeighborhood.rb index 5624fc3a86..3837b4f794 100644 --- a/testdata/ruby/dbEdgeNeighborhood.rb +++ b/testdata/ruby/dbEdgeNeighborhood.rb @@ -56,8 +56,31 @@ def on_edge(layout, cell, edge, neighborhood) end +class MyVisitor2 < RBA::EdgeNeighborhoodVisitor + + def initialize + self.result_type = RBA::CompoundRegionOperationNode::ResultType::EdgePairs + end + + def on_edge(layout, cell, edge, neighborhood) + neighborhood.each do |n| + polygons = n[1] + polygons.each do |inp, poly| + poly.each do |p| + bbox = p.bbox + t = self.to_original_trans(edge) + ep = RBA::EdgePair::new(edge, t * RBA::Edge::new(bbox.p1, RBA::Point::new(bbox.right, bbox.bottom))) + output(ep) + end + end + end + end + +end + class DBEdgeNeighborhood_TestClass < TestBase + # basic events def test_1 ly = RBA::Layout::new @@ -108,6 +131,37 @@ def test_1 end + # edge pair output, to_original_trans + def test_2 + + ly = RBA::Layout::new + + l1 = ly.layer(1, 0) + cell = ly.create_cell("TOP") + + cell.shapes(l1).insert(RBA::Box::new(0, 0, 1000, 1000)) + cell.shapes(l1).insert(RBA::Box::new(-1100, 0, -100, 1000)) + + prim = RBA::Region::new(cell.begin_shapes_rec(l1)) + + visitor = MyVisitor2::new + + bext = 0 + eext = 0 + din = 10 + dout = 100 + + children = [ + RBA::CompoundRegionOperationNode::new_foreign + ] + + node = RBA::CompoundRegionOperationNode::new_edge_neighborhood(children, visitor, bext, eext, din, dout) + res = prim.complex_op(node) + + assert_equal(res.to_s, "(-100,1000;-100,0)/(0,1000;0,0);(0,0;0,1000)/(-100,0;-100,1000)") + + end + end load("test_epilogue.rb") diff --git a/testdata/ruby/dbMatrix.rb b/testdata/ruby/dbMatrix.rb index e33fd35f25..5f22cf56ff 100644 --- a/testdata/ruby/dbMatrix.rb +++ b/testdata/ruby/dbMatrix.rb @@ -253,6 +253,7 @@ def test_4 assert_equal((m * RBA::Polygon::new(RBA::Box::new(-5, -10, 10, 20))).to_s, "(5,-25;-10,-18;5,43;20,35)") assert_equal((m * RBA::SimplePolygon::new(RBA::Box::new(-5, -10, 10, 20))).to_s, "(5,-25;-10,-18;5,43;20,35)") assert_equal((m * RBA::Edge::new(RBA::Point::new(-5, -10), RBA::Point::new(10, 20))).to_s, "(-10,-18;20,35)") + assert_equal((m * RBA::EdgePair::new(RBA::Edge::new(RBA::Point::new(0, -10), RBA::Point::new(15, 20)), RBA::Edge::new(RBA::Point::new(-5, -10), RBA::Point::new(10, 20)))).to_s, "(-5,-20;25,33)/(-10,-18;20,35)") assert_equal(RBA::Region::new(RBA::Box::new(-5, -10, 10, 20)).transformed(m).to_s, "(5,-25;-10,-18;5,43;20,35)") r = RBA::Region::new(RBA::Box::new(-5, -10, 10, 20)) r.transform(m) @@ -273,6 +274,7 @@ def test_4 assert_equal((m * RBA::DPolygon::new(RBA::DBox::new(-5, -10, 10, 20))).to_s, "(5,-25;-10,-17.5;5,42.5;20,35)") assert_equal((m * RBA::DSimplePolygon::new(RBA::DBox::new(-5, -10, 10, 20))).to_s, "(5,-25;-10,-17.5;5,42.5;20,35)") assert_equal((m * RBA::DEdge::new(RBA::DPoint::new(-5, -10), RBA::DPoint::new(10, 20))).to_s, "(-10,-17.5;20,35)") + assert_equal((m * RBA::DEdgePair::new(RBA::DEdge::new(RBA::DPoint::new(0, -10), RBA::DPoint::new(15, 20)), RBA::DEdge::new(RBA::DPoint::new(-5, -10), RBA::DPoint::new(10, 20)))).to_s, "(-5,-20;25,32.5)/(-10,-17.5;20,35)") m = RBA::IMatrix3d::new(1.0, 0.5, 1.0, -0.5, 2.0, 0.0, 0.0, 0.0, 1.0) assert_equal((m * RBA::Point::new(10, 20)).to_s, "21,35") @@ -281,6 +283,7 @@ def test_4 assert_equal((m * RBA::Polygon::new(RBA::Box::new(-5, -10, 10, 20))).to_s, "(6,-25;-9,-18;6,43;21,35)") assert_equal((m * RBA::SimplePolygon::new(RBA::Box::new(-5, -10, 10, 20))).to_s, "(6,-25;-9,-18;6,43;21,35)") assert_equal((m * RBA::Edge::new(RBA::Point::new(-5, -10), RBA::Point::new(10, 20))).to_s, "(-9,-18;21,35)") + assert_equal((m * RBA::EdgePair::new(RBA::Edge::new(RBA::Point::new(0, -10), RBA::Point::new(15, 20)), RBA::Edge::new(RBA::Point::new(-5, -10), RBA::Point::new(10, 20)))).to_s, "(-4,-20;26,33)/(-9,-18;21,35)") assert_equal(RBA::Region::new(RBA::Box::new(-5, -10, 10, 20)).transformed(m).to_s, "(6,-25;-9,-18;6,43;21,35)") r = RBA::Region::new(RBA::Box::new(-5, -10, 10, 20)) r.transform(m) @@ -301,6 +304,7 @@ def test_4 assert_equal((m * RBA::DPolygon::new(RBA::DBox::new(-5, -10, 10, 20))).to_s, "(6,-25;-9,-17.5;6,42.5;21,35)") assert_equal((m * RBA::DSimplePolygon::new(RBA::DBox::new(-5, -10, 10, 20))).to_s, "(6,-25;-9,-17.5;6,42.5;21,35)") assert_equal((m * RBA::DEdge::new(RBA::DPoint::new(-5, -10), RBA::DPoint::new(10, 20))).to_s, "(-9,-17.5;21,35)") + assert_equal((m * RBA::DEdgePair::new(RBA::DEdge::new(RBA::DPoint::new(0, -10), RBA::DPoint::new(15, 20)), RBA::DEdge::new(RBA::DPoint::new(-5, -10), RBA::DPoint::new(10, 20)))).to_s, "(-4,-20;26,32.5)/(-9,-17.5;21,35)") end From fecb8a8c8a0275b92149c0e9d54e377f93d9811c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 1 Nov 2024 22:22:57 +0100 Subject: [PATCH 11/13] RDB tests for Python too --- src/pya/unit_tests/pyaTests.cc | 1 + testdata/python/rdbTest.py | 961 +++++++++++++++++++++++++++++++++ 2 files changed, 962 insertions(+) create mode 100644 testdata/python/rdbTest.py diff --git a/src/pya/unit_tests/pyaTests.cc b/src/pya/unit_tests/pyaTests.cc index 7420d1f691..19992cf959 100644 --- a/src/pya/unit_tests/pyaTests.cc +++ b/src/pya/unit_tests/pyaTests.cc @@ -108,6 +108,7 @@ PYTHONTEST (dbTransTest, "dbTransTest.py") PYTHONTEST (dbLayoutToNetlist, "dbLayoutToNetlist.py") PYTHONTEST (dbLayoutVsSchematic, "dbLayoutVsSchematic.py") PYTHONTEST (dbNetlistCrossReference, "dbNetlistCrossReference.py") +PYTHONTEST (rdbTest, "rdbTest.py") PYTHONTEST (layLayers, "layLayers.py") PYTHONTEST (layObjects, "layObjects.py") PYTHONTEST (layPixelBuffer, "layPixelBuffer.py") diff --git a/testdata/python/rdbTest.py b/testdata/python/rdbTest.py new file mode 100644 index 0000000000..91a0f08673 --- /dev/null +++ b/testdata/python/rdbTest.py @@ -0,0 +1,961 @@ +# KLayout Layout Viewer +# Copyright (C) 2006-2024 Matthias Koefferlein +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import pya +import unittest +import os +import sys + +def to_s_test(self): + r = self.name() + r += "[" + refs = [ self.database().cell_by_id(i.parent_cell_id).name() + "->" + str(i.trans) for i in self.each_reference() ] + r += ",".join(refs) + r += "]" + return r + +def to_s_items(self): + r = self.name() + r += "[" + ai = [] + for i in self.each_item(): + vv = [ str(v) for v in i.each_value() ] + ai.append("/".join(vv)) + r += ",".join(ai) + r += "]" + return r + +pya.RdbCell.to_s_items = to_s_items +pya.RdbCell.to_s_test = to_s_test + +class RDB_TestClass(unittest.TestCase): + + # RdbReference + def test_1(self): + + ref = pya.RdbReference(pya.DCplxTrans(), 0) + self.assertEqual(ref.trans.__str__(), "r0 *1 0,0") + ref.trans = pya.DCplxTrans(5.0) + self.assertEqual(ref.trans.__str__(), "r0 *5 0,0") + + self.assertEqual(ref.parent_cell_id, 0) + ref.parent_cell_id = 177 + self.assertEqual(ref.parent_cell_id, 177) + + # RdbCell, RdbCategory + def test_2(self): + + db = pya.ReportDatabase("name") + + cell = db.create_cell("cell_name") + self.assertEqual(cell.database().__repr__(), db.__repr__()) + self.assertEqual(cell.name(), "cell_name") + self.assertEqual(cell.rdb_id(), 1) + + cell2 = db.create_cell("new_cell", "var1") + self.assertEqual(cell2.name(), "new_cell") + self.assertEqual(cell2.layout_name(), "") + self.assertEqual(cell2.qname(), "new_cell:var1") + + cell2 = db.create_cell("cell_name", "var1", "cell_name$1") + self.assertEqual(cell.name(), "cell_name") + self.assertEqual(cell.qname(), "cell_name:1") + self.assertEqual(db.cell_by_qname("cell_name:1").rdb_id(), cell.rdb_id()) + self.assertEqual(db.cell_by_id(cell.rdb_id()).rdb_id(), cell.rdb_id()) + self.assertEqual(cell2.name(), "cell_name") + self.assertEqual(cell2.layout_name(), "cell_name$1") + self.assertEqual(cell2.qname(), "cell_name:var1") + self.assertEqual(db.cell_by_qname("cell_name:var1").rdb_id(), cell2.rdb_id()) + self.assertEqual(db.cell_by_id(cell2.rdb_id()).rdb_id(), cell2.rdb_id()) + self.assertEqual(cell2.rdb_id(), 3) + self.assertEqual(cell.num_items(), 0) + self.assertEqual(cell2.num_items(), 0) + self.assertEqual(cell.num_items_visited(), 0) + self.assertEqual(cell2.num_items_visited(), 0) + + cc = db.variants("cell_name") + self.assertEqual(len(cc), 2) + self.assertEqual(cc[0], cell.rdb_id()) + self.assertEqual(cc[1], cell2.rdb_id()) + + cc = [ c for c in db.each_cell() ] + self.assertEqual(len(cc), 3) + self.assertEqual(cc[0].rdb_id(), cell.rdb_id()) + self.assertEqual(cc[2].rdb_id(), cell2.rdb_id()) + + cat = db.create_category("cat") + self.assertEqual(cat.database().__repr__(), db.__repr__()) + self.assertEqual(cat.name(), "cat") + self.assertEqual(cat.rdb_id(), 4) + self.assertEqual(cat.path(), "cat") + + cats = db.create_category(cat, "subcat") + self.assertEqual(cats.name(), "subcat") + self.assertEqual(cats.rdb_id(), 5) + self.assertEqual(cats.path(), "cat.subcat") + self.assertEqual(cats.parent().rdb_id(), cat.rdb_id()) + + cat.description = "cat_desc" + self.assertEqual(cat.description, "cat_desc") + + x = [ c for c in cat.each_sub_category() ] + self.assertEqual(len(x), 1) + self.assertEqual(x[0].rdb_id(), cats.rdb_id()) + + item = db.create_item(cell.rdb_id(), cat.rdb_id()) + self.assertEqual(db.num_items(), 1) + self.assertEqual(db.num_items_visited(), 0) + self.assertEqual(db.num_items(cell.rdb_id(), cat.rdb_id()), 1) + self.assertEqual(db.num_items_visited(cell.rdb_id(), cat.rdb_id()), 0) + self.assertEqual(db.num_items(cell.rdb_id(), cats.rdb_id()), 0) + self.assertEqual(db.num_items_visited(cell.rdb_id(), cats.rdb_id()), 0) + self.assertEqual(cell.num_items(), 1) + self.assertEqual(cell2.num_items(), 0) + self.assertEqual(cat.num_items(), 1) + self.assertEqual(cats.num_items(), 0) + self.assertEqual(cell.num_items_visited(), 0) + self.assertEqual(cell2.num_items_visited(), 0) + self.assertEqual(cat.num_items_visited(), 0) + self.assertEqual(cats.num_items_visited(), 0) + + try: + item = db.create_item(1000, cat.rdb_id()) + self.assertEqual(False, True) + except Exception as ex: + self.assertEqual(ex.__str__(), "Not a valid cell ID: 1000 in ReportDatabase.create_item") + + try: + item = db.create_item(cell.rdb_id(), 1001) + self.assertEqual(False, True) + except Exception as ex: + self.assertEqual(ex.__str__(), "Not a valid category ID: 1001 in ReportDatabase.create_item") + + item2 = db.create_item(cell2, cats) + self.assertEqual(db.num_items(), 2) + self.assertEqual(db.num_items_visited(), 0) + self.assertEqual(db.num_items(cell2.rdb_id(), cats.rdb_id()), 1) + self.assertEqual(db.num_items_visited(cell2.rdb_id(), cats.rdb_id()), 0) + self.assertEqual(db.num_items(cell2.rdb_id(), cat.rdb_id()), 1) + self.assertEqual(db.num_items_visited(cell2.rdb_id(), cat.rdb_id()), 0) + self.assertEqual(cell.num_items(), 1) + self.assertEqual(cell2.num_items(), 1) + self.assertEqual(cat.num_items(), 2) + self.assertEqual(cats.num_items(), 1) + self.assertEqual(cell.num_items_visited(), 0) + self.assertEqual(cell2.num_items_visited(), 0) + self.assertEqual(cat.num_items_visited(), 0) + self.assertEqual(cats.num_items_visited(), 0) + + db.set_item_visited(item, True) + self.assertEqual(item.is_visited(), True) + self.assertEqual(cell.num_items_visited(), 1) + self.assertEqual(cell2.num_items_visited(), 0) + self.assertEqual(cat.num_items_visited(), 1) + self.assertEqual(cats.num_items_visited(), 0) + self.assertEqual(db.num_items(), 2) + self.assertEqual(db.num_items_visited(), 1) + self.assertEqual(db.num_items(cell.rdb_id(), cat.rdb_id()), 1) + self.assertEqual(db.num_items_visited(cell.rdb_id(), cat.rdb_id()), 1) + self.assertEqual(db.num_items(cell2.rdb_id(), cats.rdb_id()), 1) + self.assertEqual(db.num_items_visited(cell2.rdb_id(), cats.rdb_id()), 0) + self.assertEqual(db.num_items(cell.rdb_id(), cats.rdb_id()), 0) + self.assertEqual(db.num_items_visited(cell.rdb_id(), cats.rdb_id()), 0) + + db.set_item_visited(item2, True) + self.assertEqual(cell.num_items_visited(), 1) + self.assertEqual(cell2.num_items_visited(), 1) + self.assertEqual(cat.num_items_visited(), 2) + self.assertEqual(cats.num_items_visited(), 1) + self.assertEqual(db.num_items(), 2) + self.assertEqual(db.num_items_visited(), 2) + self.assertEqual(db.num_items(cell.rdb_id(), cat.rdb_id()), 1) + self.assertEqual(db.num_items_visited(cell.rdb_id(), cat.rdb_id()), 1) + self.assertEqual(db.num_items(cell2.rdb_id(), cats.rdb_id()), 1) + self.assertEqual(db.num_items_visited(cell2.rdb_id(), cats.rdb_id()), 1) + self.assertEqual(db.num_items(cell.rdb_id(), cats.rdb_id()), 0) + self.assertEqual(db.num_items_visited(cell.rdb_id(), cats.rdb_id()), 0) + + db.set_item_visited(item, False) + self.assertEqual(item.is_visited(), False) + self.assertEqual(cell.num_items_visited(), 0) + self.assertEqual(cell2.num_items_visited(), 1) + self.assertEqual(cat.num_items_visited(), 1) + self.assertEqual(cats.num_items_visited(), 1) + self.assertEqual(db.num_items(), 2) + self.assertEqual(db.num_items_visited(), 1) + self.assertEqual(db.num_items(cell.rdb_id(), cat.rdb_id()), 1) + self.assertEqual(db.num_items_visited(cell.rdb_id(), cat.rdb_id()), 0) + self.assertEqual(db.num_items(cell2.rdb_id(), cats.rdb_id()), 1) + self.assertEqual(db.num_items_visited(cell2.rdb_id(), cats.rdb_id()), 1) + self.assertEqual(db.num_items(cell.rdb_id(), cats.rdb_id()), 0) + self.assertEqual(db.num_items_visited(cell.rdb_id(), cats.rdb_id()), 0) + + ii = [ i for i in db.each_item() ] + self.assertEqual(len(ii), 2) + self.assertEqual(ii[0].cell_id(), item.cell_id()) + self.assertEqual(ii[0].category_id(), item.category_id()) + self.assertEqual(ii[1].cell_id(), item2.cell_id()) + self.assertEqual(ii[1].category_id(), item2.category_id()) + + ii = [ i for i in db.each_item_per_cell(cell.rdb_id()) ] + self.assertEqual(len(ii), 1) + self.assertEqual(ii[0].cell_id(), item.cell_id()) + self.assertEqual(ii[0].category_id(), item.category_id()) + + ii = [ i for i in cell.each_item() ] + self.assertEqual(len(ii), 1) + self.assertEqual(ii[0].cell_id(), item.cell_id()) + self.assertEqual(ii[0].category_id(), item.category_id()) + + ii = [ i for i in db.each_item_per_category(cats.rdb_id()) ] + self.assertEqual(len(ii), 1) + self.assertEqual(ii[0].cell_id(), item2.cell_id()) + self.assertEqual(ii[0].category_id(), item2.category_id()) + + ii = [ i for i in cats.each_item() ] + self.assertEqual(len(ii), 1) + self.assertEqual(ii[0].cell_id(), item2.cell_id()) + self.assertEqual(ii[0].category_id(), item2.category_id()) + + ii = [ i for i in db.each_item_per_cell_and_category(cell.rdb_id(), cats.rdb_id()) ] + self.assertEqual(len(ii), 0) + + ii = [ i for i in db.each_item_per_cell_and_category(cell2.rdb_id(), cats.rdb_id()) ] + self.assertEqual(len(ii), 1) + self.assertEqual(ii[0].cell_id(), item2.cell_id()) + self.assertEqual(ii[0].category_id(), item2.category_id()) + + refs = [ r for r in cell.each_reference() ] + self.assertEqual(len(refs), 0) + + cell.add_reference(pya.RdbReference(pya.DCplxTrans(2.5), 178)) + refs = [ r for r in cell.each_reference() ] + self.assertEqual(len(refs), 1) + self.assertEqual(refs[0].parent_cell_id, 178) + self.assertEqual(refs[0].database().__repr__(), db.__repr__()) + + cell.clear_references() + refs = [ r for r in cell.each_reference() ] + self.assertEqual(len(refs), 0) + + # RdbItemValue + def test_3(self): + + v = pya.RdbItemValue(1.0) + self.assertEqual(v.tag_id, 0) + v.tag_id = 15 + self.assertEqual(v.tag_id, 15) + + vf = pya.RdbItemValue(1.0) + vs = pya.RdbItemValue("a string") + vb = pya.RdbItemValue(pya.DBox(0, 10, 20, 30)) + vl = pya.RdbItemValue(pya.DText("abc", pya.DTrans())) + vp = pya.RdbItemValue(pya.DPolygon(pya.DBox(100, 101, 102, 103))) + ve = pya.RdbItemValue(pya.DEdge(pya.DPoint(0, 10), pya.DPoint(20, 30))) + vee = pya.RdbItemValue(pya.DEdgePair(pya.DEdge(0, 10, 5, 15), pya.DEdge(20, 30, 25, 35))) + + self.assertEqual(vf.__str__(), "float: 1") + self.assertEqual(vs.__str__(), "text: 'a string'") + self.assertEqual(vb.__str__(), "box: (0,10;20,30)") + self.assertEqual(vp.__str__(), "polygon: (100,101;100,103;102,103;102,101)") + self.assertEqual(vl.__str__(), "label: ('abc',r0 0,0)"); + self.assertEqual(ve.__str__(), "edge: (0,10;20,30)") + self.assertEqual(vee.__str__(), "edge-pair: (0,10;5,15)/(20,30;25,35)") + self.assertEqual(pya.RdbItemValue.from_s(vf.__str__()).__str__(), vf.__str__()) + self.assertEqual(pya.RdbItemValue.from_s(vs.__str__()).__str__(), vs.__str__()) + self.assertEqual(pya.RdbItemValue.from_s(vb.__str__()).__str__(), vb.__str__()) + self.assertEqual(pya.RdbItemValue.from_s(vp.__str__()).__str__(), vp.__str__()) + self.assertEqual(pya.RdbItemValue.from_s(vl.__str__()).__str__(), vl.__str__()) + self.assertEqual(pya.RdbItemValue.from_s(ve.__str__()).__str__(), ve.__str__()) + self.assertEqual(pya.RdbItemValue.from_s(vee.__str__()).__str__(), vee.__str__()) + + self.assertEqual(vf.is_float(), True) + self.assertEqual(vf.is_string(), False) + self.assertEqual(vf.is_polygon(), False) + self.assertEqual(vf.is_text(), False) + self.assertEqual(vf.is_edge(), False) + self.assertEqual(vf.is_box(), False) + self.assertEqual(vf.is_edge_pair(), False) + self.assertEqual(vf.float(), 1) + self.assertEqual(vf.string(), "1") + + self.assertEqual(vs.is_float(), False) + self.assertEqual(vs.is_string(), True) + self.assertEqual(vs.is_polygon(), False) + self.assertEqual(vs.is_text(), False) + self.assertEqual(vs.is_edge(), False) + self.assertEqual(vs.is_box(), False) + self.assertEqual(vs.is_edge_pair(), False) + self.assertEqual(vs.string(), "a string") + + self.assertEqual(vl.is_float(), False) + self.assertEqual(vl.is_string(), False) + self.assertEqual(vl.is_polygon(), False) + self.assertEqual(vl.is_text(), True) + self.assertEqual(vl.is_edge(), False) + self.assertEqual(vl.is_box(), False) + self.assertEqual(vl.is_edge_pair(), False) + self.assertEqual(vl.text().__str__(), "('abc',r0 0,0)") + self.assertEqual(vl.string(), "label: " + vl.text().__str__()) + + self.assertEqual(vp.is_float(), False) + self.assertEqual(vp.is_string(), False) + self.assertEqual(vp.is_polygon(), True) + self.assertEqual(vp.is_text(), False) + self.assertEqual(vp.is_edge(), False) + self.assertEqual(vp.is_box(), False) + self.assertEqual(vp.is_edge_pair(), False) + self.assertEqual(vp.polygon().__str__(), "(100,101;100,103;102,103;102,101)") + self.assertEqual(vp.string(), "polygon: " + vp.polygon().__str__()) + + self.assertEqual(ve.is_float(), False) + self.assertEqual(ve.is_string(), False) + self.assertEqual(ve.is_polygon(), False) + self.assertEqual(ve.is_text(), False) + self.assertEqual(ve.is_edge(), True) + self.assertEqual(ve.is_box(), False) + self.assertEqual(ve.is_edge_pair(), False) + self.assertEqual(ve.edge().__str__(), "(0,10;20,30)") + self.assertEqual(ve.string(), "edge: " + ve.edge().__str__()) + + self.assertEqual(vb.is_float(), False) + self.assertEqual(vb.is_string(), False) + self.assertEqual(vb.is_polygon(), False) + self.assertEqual(vb.is_text(), False) + self.assertEqual(vb.is_edge(), False) + self.assertEqual(vb.is_box(), True) + self.assertEqual(vb.is_edge_pair(), False) + self.assertEqual(vb.box().__str__(), "(0,10;20,30)") + self.assertEqual(vb.string(), "box: " + vb.box().__str__()) + + self.assertEqual(vee.is_float(), False) + self.assertEqual(vee.is_string(), False) + self.assertEqual(vee.is_polygon(), False) + self.assertEqual(vee.is_text(), False) + self.assertEqual(vee.is_edge(), False) + self.assertEqual(vee.is_box(), False) + self.assertEqual(vee.is_edge_pair(), True) + self.assertEqual(vee.edge_pair().__str__(), "(0,10;5,15)/(20,30;25,35)") + self.assertEqual(vee.string(), "edge-pair: " + vee.edge_pair().__str__()) + + # RdbItem + def test_4(self): + + db = pya.ReportDatabase("name") + + cell = db.create_cell("cell_name") + cat = db.create_category("cat") + + item = db.create_item(cell.rdb_id(), cat.rdb_id()) + self.assertEqual(item.database().__repr__(), db.__repr__()) + self.assertEqual(item.cell_id(), cell.rdb_id()) + self.assertEqual(item.category_id(), cat.rdb_id()) + + self.assertEqual(db.user_tag_id("x1") != 0, True) + self.assertEqual(db.tag_id("x1") != 0, True) + self.assertEqual(db.tag_id("x1") == db.user_tag_id("x1"), False) + db.set_tag_description(db.user_tag_id("x1"), "D") + self.assertEqual(db.tag_description(db.user_tag_id("x1")), "D") + self.assertEqual(db.tag_name(db.user_tag_id("x1")), "x1") + self.assertEqual(db.tag_description(db.tag_id("x1")), "") + self.assertEqual(db.tag_name(db.tag_id("x1")), "x1") + db.set_tag_description(db.tag_id("x1"), "U") + self.assertEqual(db.tag_description(db.user_tag_id("x1")), "D") + self.assertEqual(db.tag_description(db.tag_id("x1")), "U") + + item.add_tag(db.tag_id("x1")) + self.assertEqual(item.has_tag(db.tag_id("x2")), False) + self.assertEqual(item.has_tag(db.tag_id("x1")), True) + self.assertEqual(item.tags_str, "x1") + item.add_tag(db.tag_id("x2")) + self.assertEqual(item.has_tag(db.tag_id("x2")), True) + self.assertEqual(item.has_tag(db.tag_id("x1")), True) + self.assertEqual(item.tags_str, "x1,x2") + item.remove_tag(db.tag_id("x1")) + self.assertEqual(item.has_tag(db.tag_id("x2")), True) + self.assertEqual(item.has_tag(db.tag_id("x1")), False) + self.assertEqual(item.tags_str, "x2") + + item.tags_str="x2,x1" + self.assertEqual(item.has_tag(db.tag_id("x2")), True) + self.assertEqual(item.has_tag(db.tag_id("x1")), True) + self.assertEqual(item.tags_str, "x1,x2") + + item.tags_str="" + self.assertEqual(item.has_tag(db.tag_id("x2")), False) + self.assertEqual(item.has_tag(db.tag_id("x1")), False) + self.assertEqual(item.tags_str, "") + + self.assertEqual(item.image_str, "") + self.assertEqual(item.has_image(), False) + + self.assertEqual(item.comment, "") + item.comment = "abc" + self.assertEqual(item.comment, "abc") + + # can actually by any string, but only base64-encoded PNG images make sense + istr="iVBORw0KGgoAAAANSUhEUgAAACoAAAA0CAIAAABzfT3nAAAAA3NCSVQICAjb4U/gAAAACXBIWXMAAA0SAAANOgHo3ZneAAAA3UlEQVRYhe2WwQ3DIAxFoco8XaGZIaeO43FyYgZYgYXcQ6SWuDGgBhWq/qccIvGCEd9SbAwAAPSGaW2lFR2rfWDpXrPpSe2SP10fvnn/PZHZH9IwbKFVZZ/Z6wMtZcjW02Bn2FVpZYdWdkr2nvh23S2FyDNJuVITpwmRjTGbNr0v20U5byNtJuuJt/fO2f93+UlbEJl5UjVPr3Y71EQ/PoPPlU+lDJtWlCt3GwCMG33BuJGAcWMEMG6c1jBudCyf/nzV8nbZPRohclFLHdGbZ8eNSjN1fmf0AACA1jwA4hKxu4C6P7EAAAAASUVORK5CYII=" + item.image_str=istr + self.assertEqual(item.image_str, istr) + self.assertEqual(item.has_image(), True) + + if "image_pixels" in item.__dict__: + px=item.image_pixels + self.assertEqual(px.width, 42) + self.assertEqual(px.height, 52) + item.image = px + px2=item.image_pixels + self.assertEqual(px == px2, True) + + if "image" in item.__dict__: + px=item.image + self.assertEqual(px.width, 42) + self.assertEqual(px.height, 52) + item.image = px + px2=item.image + self.assertEqual(px2.width, 42) + self.assertEqual(px2.height, 52) + + vs = pya.RdbItemValue("a string") + vs2 = pya.RdbItemValue("a string (ii)") + item.add_value(vs) + item.add_value(vs2) + + vv=[ v for v in item.each_value() ] + self.assertEqual(len(vv), 2) + self.assertEqual(vv[0].__str__(), "text: 'a string'") + self.assertEqual(vv[1].__str__(), "text: 'a string (ii)'") + + item.clear_values() + vv=[ v for v in item.each_value() ] + self.assertEqual(len(vv), 0) + + item.clear_values() + item.add_value(1.0) + item.add_value("hello") + item.add_value(pya.DPolygon(pya.DBox(1, 2, 3, 4))) + item.add_value(pya.DBox(11, 12, 13, 14)) + item.add_value(pya.DEdge(21, 22, 23, 24)) + item.add_value(pya.DEdgePair(pya.DEdge(31, 32, 33, 34), pya.DEdge(41, 42, 43, 44))) + shapes = pya.Shapes() + pts = [ pya.Point(0, 0), pya.Point(50, 150) ] + shapes.insert(pya.Path(pts, 100)) + for s in shapes: + item.add_value(s, pya.CplxTrans(0.001)) + vv=[ v for v in item.each_value() ] + self.assertEqual(len(vv), 7) + self.assertEqual(vv[0].__str__(), "float: 1") + self.assertEqual(vv[1].__str__(), "text: hello") + self.assertEqual(vv[2].__str__(), "polygon: (1,2;1,4;3,4;3,2)") + self.assertEqual(vv[3].__str__(), "box: (11,12;13,14)") + self.assertEqual(vv[4].__str__(), "edge: (21,22;23,24)") + self.assertEqual(vv[5].__str__(), "edge-pair: (31,32;33,34)/(41,42;43,44)") + self.assertEqual(vv[6].__str__(), "path: (0,0;0.05,0.15) w=0.1 bx=0 ex=0 r=false") + + # Multiple items + def test_5(self): + + db = pya.ReportDatabase("name") + + cell = db.create_cell("cell_name") + cat = db.create_category("cat") + + r = pya.Region(pya.Box(0, 0, 100, 200)) + db.create_items(cell.rdb_id(), cat.rdb_id(), pya.CplxTrans(0.001), r) + a = [] + for item in db.each_item_per_category(cat.rdb_id()): + for v in item.each_value(): + a.append(str(v)) + self.assertEqual(";".join(a), "polygon: (0,0;0,0.2;0.1,0.2;0.1,0)") + + db = pya.ReportDatabase("name") + + cell = db.create_cell("cell_name") + cat = db.create_category("cat") + + p = pya.Polygon(pya.Box(0, 0, 100, 200)) + db.create_items(cell.rdb_id(), cat.rdb_id(), pya.CplxTrans(0.001), [p]) + a = [] + for item in db.each_item_per_category(cat.rdb_id()): + for v in item.each_value(): + a.append(str(v)) + self.assertEqual(";".join(a), "polygon: (0,0;0,0.2;0.1,0.2;0.1,0)") + + db = pya.ReportDatabase("name") + + cell = db.create_cell("cell_name") + cat = db.create_category("cat") + + r = pya.Edges(pya.Edge(0, 0, 100, 200)) + db.create_items(cell.rdb_id(), cat.rdb_id(), pya.CplxTrans(0.001), r) + a = [] + for item in db.each_item_per_category(cat.rdb_id()): + for v in item.each_value(): + a.append(str(v)) + self.assertEqual(";".join(a), "edge: (0,0;0.1,0.2)") + + db = pya.ReportDatabase("name") + + cell = db.create_cell("cell_name") + cat = db.create_category("cat") + + r = [ pya.Edge(0, 0, 100, 200) ] + db.create_items(cell.rdb_id(), cat.rdb_id(), pya.CplxTrans(0.001), r) + a = [] + for item in db.each_item_per_category(cat.rdb_id()): + for v in item.each_value(): + a.append(str(v)) + self.assertEqual(";".join(a), "edge: (0,0;0.1,0.2)") + + db = pya.ReportDatabase("name") + + cell = db.create_cell("cell_name") + cat = db.create_category("cat") + + r = pya.EdgePairs() + r.insert(pya.EdgePair(pya.Edge(0, 0, 100, 200), pya.Edge(10, 10, 50, 150))) + db.create_items(cell.rdb_id(), cat.rdb_id(), pya.CplxTrans(0.001), r) + a = [] + for item in db.each_item_per_category(cat.rdb_id()): + for v in item.each_value(): + a.append(str(v)) + self.assertEqual(";".join(a), "edge-pair: (0,0;0.1,0.2)/(0.01,0.01;0.05,0.15)") + + db = pya.ReportDatabase("name") + + cell = db.create_cell("cell_name") + cat = db.create_category("cat") + + r = [ pya.EdgePair(pya.Edge(0, 0, 100, 200), pya.Edge(10, 10, 50, 150)) ] + db.create_items(cell.rdb_id(), cat.rdb_id(), pya.CplxTrans(0.001), r) + a = [] + for item in db.each_item_per_category(cat.rdb_id()): + for v in item.each_value(): + a.append(str(v)) + self.assertEqual(";".join(a), "edge-pair: (0,0;0.1,0.2)/(0.01,0.01;0.05,0.15)") + + # ReportDatabase + def test_6(self): + + db = pya.ReportDatabase("name") + db.reset_modified() + self.assertEqual(db.is_modified(), False) + self.assertEqual(db.name(), "name") + db.description = "desc" + db.generator = "gg" + db.top_cell_name = "top" + db.original_file = "of" + self.assertEqual(db.description, "desc") + self.assertEqual(db.generator, "gg") + self.assertEqual(db.top_cell_name, "top") + self.assertEqual(db.original_file, "of") + + self.assertEqual(db.is_modified(), True) + db.reset_modified() + self.assertEqual(db.is_modified(), False) + + tag_id = db.tag_id("x") + self.assertEqual(tag_id, 1) + db.set_tag_description(tag_id, "xdesc") + self.assertEqual(db.tag_description(tag_id), "xdesc") + + cell = db.create_cell("cell_name") + cc = [ c for c in db.each_cell() ] + self.assertEqual(len(cc), 1) + self.assertEqual(cc[0].rdb_id(), cell.rdb_id()) + + cat = db.create_category("cat") + cc = [ c for c in db.each_category() ] + self.assertEqual(len(cc), 1) + self.assertEqual(cc[0].rdb_id(), cat.rdb_id()) + + cats = db.create_category(cat, "subcat") + c = db.category_by_path("x") + self.assertEqual(c, None) + c = db.category_by_path("cat") + self.assertEqual(c.rdb_id(), cat.rdb_id()) + c = db.category_by_path("cat.subcat") + self.assertEqual(c.rdb_id(), cats.rdb_id()) + + self.assertEqual(db.category_by_id(cat.rdb_id()).rdb_id(), cat.rdb_id()) + self.assertEqual(db.category_by_id(cats.rdb_id()).rdb_id(), cats.rdb_id()) + + item = db.create_item(cell.rdb_id(), cat.rdb_id()) + v = pya.RdbItemValue("a") + v.tag_id = db.user_tag_id("x2") + item.add_value(v) + v = pya.RdbItemValue("b") + v.tag_id = db.tag_id("x1") + item.add_value(v) + item.add_tag(db.tag_id("x1")) + item.add_tag(db.user_tag_id("x2")) + istr="iVBORw0KGgoAAAANSUhEUgAAACoAAAA0CAIAAABzfT3nAAAAA3NCSVQICAjb4U/gAAAACXBIWXMAAA0SAAANOgHo3ZneAAAA3UlEQVRYhe2WwQ3DIAxFoco8XaGZIaeO43FyYgZYgYXcQ6SWuDGgBhWq/qccIvGCEd9SbAwAAPSGaW2lFR2rfWDpXrPpSe2SP10fvnn/PZHZH9IwbKFVZZ/Z6wMtZcjW02Bn2FVpZYdWdkr2nvh23S2FyDNJuVITpwmRjTGbNr0v20U5byNtJuuJt/fO2f93+UlbEJl5UjVPr3Y71EQ/PoPPlU+lDJtWlCt3GwCMG33BuJGAcWMEMG6c1jBudCyf/nzV8nbZPRohclFLHdGbZ8eNSjN1fmf0AACA1jwA4hKxu4C6P7EAAAAASUVORK5CYII=" + if "image_str" in item.__dict__: + item.image_str=istr + + ut_testtmp = os.getenv("TESTTMP", ".") + tmp = os.path.join(ut_testtmp, "tmp.lyrdb") + + self.assertEqual(db.filename(), "") + db.save(tmp) + self.assertEqual(db.filename(), tmp) + + # load and save + db = None + + db2 = pya.ReportDatabase("neu") + db2.load(tmp) + self.assertEqual(os.path.basename(db2.filename()), os.path.basename(tmp)) + self.assertEqual(db2.name(), os.path.basename(tmp)) + + self.assertEqual(db2.description, "desc") + self.assertEqual(db2.generator, "gg") + self.assertEqual(db2.top_cell_name, "top") + self.assertEqual(db2.original_file, "of") + + c = db2.category_by_path("cat.subcat") + self.assertEqual(c.path(), "cat.subcat") + + cc = [ c for c in db2.each_category() ] + self.assertEqual(len(cc), 1) + + ii = [ i for i in db2.each_item() ] + self.assertEqual(len(ii), 1) + self.assertEqual(ii[0].tags_str, "x1,#x2") + self.assertEqual(ii[0].has_tag(db2.user_tag_id("x2")), True) + self.assertEqual(ii[0].has_tag(db2.tag_id("x1")), True) + self.assertEqual(ii[0].has_tag(db2.tag_id("x")), False) + # Only the first 30 bytes count ... the remaining part is too different for different versions of Qt + if "image_str" in ii[0].__dict__: + self.assertEqual(ii[0].image_str[range(0, 30)], istr[range(0, 30)]) + self.assertEqual(db2.cell_by_id(ii[0].cell_id()).qname(), "cell_name") + self.assertEqual(db2.category_by_id(ii[0].category_id()).path(), "cat") + vs = "" + for v in ii[0].each_value(): + vs += v.string() + self.assertEqual(vs, "ab") + vs = "" + for v in ii[0].each_value(): + if v.tag_id == db2.tag_id("x1"): + vs += v.string() + self.assertEqual(vs, "b") + vs = "" + for v in ii[0].each_value(): + if v.tag_id == db2.user_tag_id("x1"): + vs += v.string() + self.assertEqual(vs, "") + vs = "" + for v in ii[0].each_value(): + if v.tag_id == db2.user_tag_id("x2"): + vs += v.string() + self.assertEqual(vs, "a") + + # LayoutView + def test_10(self): + + if not "Application" in pya.__dict__: + return + + mw = pya.Application.instance().main_window() + mw.create_layout(1) + view = mw.current_view() + + self.ot = 0 + def on_changed(): + self.ot += 1 + view.on_rdb_list_changed += on_changed + + rdb_index = view.create_rdb("NEW_RDB") + self.assertEqual(view.num_rdbs(), 1) + self.assertEqual(self.ot, 1) + self.assertEqual(view.rdb(rdb_index).name(), "NEW_RDB") + view.remove_rdb(rdb_index) + self.assertEqual(view.num_rdbs(), 0) + self.assertEqual(self.ot, 2) + + view.on_rdb_list_changed -= on_changed + + self.ot = 0 + rdb_index = view.create_rdb("NEW_RDB2") + self.assertEqual(view.rdb(rdb_index).name(), "NEW_RDB2") + self.assertEqual(self.ot, 0) + self.assertEqual(view.num_rdbs(), 1) + rdb_index = view.create_rdb("NEW_RDB3") + self.assertEqual(view.rdb(rdb_index).name(), "NEW_RDB3") + self.assertEqual(view.num_rdbs(), 2) + self.assertEqual(self.ot, 0) + + mw.close_current_view() + + # scan_... methods + def test_11(self): + + ly = pya.Layout() + c0 = ly.create_cell("c0") + c1 = ly.create_cell("c1") + c2 = ly.create_cell("c2") + c3 = ly.create_cell("c3") + c1.insert(pya.CellInstArray(c2.cell_index(), pya.Trans(10, 20))) + c2.insert(pya.CellInstArray(c3.cell_index(), pya.Trans(11, 21))) + l1 = ly.insert_layer(pya.LayerInfo(1, 0)) + prop_id = ly.properties_id([[ "a", 17 ]]) + c0.shapes(l1).insert(pya.Box(0, 1, 2, 3), prop_id) + prop_id = ly.properties_id([[ "a", 21 ]]) + c1.shapes(l1).insert(pya.Box(0, 1, 20, 30), prop_id) + c2.shapes(l1).insert(pya.Box(0, 1, 21, 31)) + c3.shapes(l1).insert(pya.Box(0, 1, 22, 32)) + + rdb = pya.ReportDatabase("neu") + cat = rdb.create_category("l1") + cat.scan_layer(ly, l1) + self.assertEqual(rdb.tag_name(1), "a") # from property + self.assertEqual(cat.num_items(), 4) + cn = [ c.to_s_test() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c0[];c1[];c2[];c3[]") + cn = [ c.to_s_items() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c0[polygon: (0,0.001;0,0.003;0.002,0.003;0.002,0.001)/float: 17];c1[polygon: (0,0.001;0,0.03;0.02,0.03;0.02,0.001)/float: 21];c2[polygon: (0,0.001;0,0.031;0.021,0.031;0.021,0.001)];c3[polygon: (0,0.001;0,0.032;0.022,0.032;0.022,0.001)]") + + rdb = pya.ReportDatabase("neu") + cat = rdb.create_category("l1") + cat.scan_layer(ly, l1, None, -1, False) + self.assertEqual(cat.num_items(), 4) + cn = [ c.to_s_test() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c0[];c1[];c2[];c3[]") + cn = [ c.to_s_items() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c0[polygon: (0,0.001;0,0.003;0.002,0.003;0.002,0.001)];c1[polygon: (0,0.001;0,0.03;0.02,0.03;0.02,0.001)];c2[polygon: (0,0.001;0,0.031;0.021,0.031;0.021,0.001)];c3[polygon: (0,0.001;0,0.032;0.022,0.032;0.022,0.001)]") + + rdb = pya.ReportDatabase("neu") + cat = rdb.create_category("l1") + cat.scan_layer(ly, l1, c1) + self.assertEqual(cat.num_items(), 3) + cn = [ c.to_s_test() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[];c2[c1->r0 *1 0.01,0.02];c3[c1->r0 *1 0.021,0.041]") + cn = [ c.to_s_items() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[polygon: (0,0.001;0,0.03;0.02,0.03;0.02,0.001)/float: 21];c2[polygon: (0,0.001;0,0.031;0.021,0.031;0.021,0.001)];c3[polygon: (0,0.001;0,0.032;0.022,0.032;0.022,0.001)]") + + rdb = pya.ReportDatabase("neu") + cat = rdb.create_category("l1") + cat.scan_layer(ly, l1, c1, 0) + self.assertEqual(cat.num_items(), 1) + cn = [ c.to_s_test() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[]") + cn = [ c.to_s_items() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[polygon: (0,0.001;0,0.03;0.02,0.03;0.02,0.001)/float: 21]") + + rdb = pya.ReportDatabase("neu") + cat = rdb.create_category("l1") + cat.scan_layer(ly, l1, c1, -1) + self.assertEqual(cat.num_items(), 3) + cn = [ c.to_s_test() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[];c2[c1->r0 *1 0.01,0.02];c3[c1->r0 *1 0.021,0.041]") + cn = [ c.to_s_items() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[polygon: (0,0.001;0,0.03;0.02,0.03;0.02,0.001)/float: 21];c2[polygon: (0,0.001;0,0.031;0.021,0.031;0.021,0.001)];c3[polygon: (0,0.001;0,0.032;0.022,0.032;0.022,0.001)]") + + rdb = pya.ReportDatabase("neu") + cat = rdb.create_category("l1") + cat.scan_layer(ly, l1, c1, 1) + self.assertEqual(cat.num_items(), 2) + cn = [ c.to_s_test() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[];c2[c1->r0 *1 0.01,0.02]") + cn = [ c.to_s_items() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[polygon: (0,0.001;0,0.03;0.02,0.03;0.02,0.001)/float: 21];c2[polygon: (0,0.001;0,0.031;0.021,0.031;0.021,0.001)]") + + rdb = pya.ReportDatabase("neu") + cat = rdb.create_category("l1") + cat.scan_shapes(c1.begin_shapes_rec(l1)) # hierarchical scan + self.assertEqual(cat.num_items(), 3) + cn = [ c.to_s_test() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[];c2[c1->r0 *1 0.01,0.02];c3[c1->r0 *1 0.021,0.041]") + cn = [ c.to_s_items() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[polygon: (0,0.001;0,0.03;0.02,0.03;0.02,0.001)/float: 21];c2[polygon: (0,0.001;0,0.031;0.021,0.031;0.021,0.001)];c3[polygon: (0,0.001;0,0.032;0.022,0.032;0.022,0.001)]") + + rdb = pya.ReportDatabase("neu") + cat = rdb.create_category("l1") + cat.scan_shapes(c1.begin_shapes_rec(l1), False, False) # hierarchical scan + self.assertEqual(cat.num_items(), 3) + cn = [ c.to_s_test() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[];c2[c1->r0 *1 0.01,0.02];c3[c1->r0 *1 0.021,0.041]") + cn = [ c.to_s_items() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[polygon: (0,0.001;0,0.03;0.02,0.03;0.02,0.001)];c2[polygon: (0,0.001;0,0.031;0.021,0.031;0.021,0.001)];c3[polygon: (0,0.001;0,0.032;0.022,0.032;0.022,0.001)]") + + rdb = pya.ReportDatabase("neu") + cat = rdb.create_category("l1") + cat.scan_shapes(c1.begin_shapes_rec(l1), True) # flat scan + self.assertEqual(cat.num_items(), 3) + cn = [ c.to_s_test() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[]") + cn = [ c.to_s_items() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[polygon: (0,0.001;0,0.03;0.02,0.03;0.02,0.001)/float: 21,polygon: (0.01,0.021;0.01,0.051;0.031,0.051;0.031,0.021),polygon: (0.021,0.042;0.021,0.073;0.043,0.073;0.043,0.042)]") + + rdb = pya.ReportDatabase("neu") + cat = rdb.create_category("l1") + r = pya.Region(c1.begin_shapes_rec(l1)) + cat.scan_collection(rdb.create_cell("TOP"), pya.CplxTrans(0.001), r) # hierarchical scan + self.assertEqual(cat.num_items(), 3) + cn = [ c.to_s_test() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "TOP[];c1[TOP->r0 *1 0,0];c2[c1->r0 *1 0.01,0.02];c3[c1->r0 *1 0.021,0.041]") + cn = [ c.to_s_items() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "TOP[];c1[polygon: (0,0.001;0,0.03;0.02,0.03;0.02,0.001)/float: 21];c2[polygon: (0,0.001;0,0.031;0.021,0.031;0.021,0.001)];c3[polygon: (0,0.001;0,0.032;0.022,0.032;0.022,0.001)]") + + rdb = pya.ReportDatabase("neu") + cat = rdb.create_category("l1") + r = pya.Region(c1.begin_shapes_rec(l1)) + cat.scan_collection(rdb.create_cell("TOP"), pya.CplxTrans(0.001), r, True) # flat scan + self.assertEqual(cat.num_items(), 3) + cn = [ c.to_s_test() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "TOP[]") + cn = [ c.to_s_items() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "TOP[polygon: (0,0.001;0,0.03;0.02,0.03;0.02,0.001)/float: 21,polygon: (0.01,0.021;0.01,0.051;0.031,0.051;0.031,0.021),polygon: (0.021,0.042;0.021,0.073;0.043,0.073;0.043,0.042)]") + + rdb = pya.ReportDatabase("neu") + cat = rdb.create_category("l1") + r = pya.Region(c1.begin_shapes_rec(l1)) + cat.scan_collection(rdb.create_cell("TOP"), pya.CplxTrans(0.001), r, True, False) # flat scan + self.assertEqual(cat.num_items(), 3) + cn = [ c.to_s_test() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "TOP[]") + cn = [ c.to_s_items() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "TOP[polygon: (0,0.001;0,0.03;0.02,0.03;0.02,0.001),polygon: (0.01,0.021;0.01,0.051;0.031,0.051;0.031,0.021),polygon: (0.021,0.042;0.021,0.073;0.043,0.073;0.043,0.042)]") + + rdb = pya.ReportDatabase("neu") + cat = rdb.create_category("l1") + r = pya.Region(c1.begin_shapes_rec(l1)).merged() + cat.scan_collection(rdb.create_cell("TOP"), pya.CplxTrans(0.001), r, True) # flat scan + self.assertEqual(cat.num_items(), 1) + cn = [ c.to_s_test() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "TOP[]") + cn = [ c.to_s_items() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "TOP[polygon: (0,0.001;0,0.03;0.01,0.03;0.01,0.051;0.021,0.051;0.021,0.073;0.043,0.073;0.043,0.042;0.031,0.042;0.031,0.021;0.02,0.021;0.02,0.001)]") + + # shape insertion from shape, shapes, recursive iterator + def test_12(self): + + ly = pya.Layout() + c0 = ly.create_cell("c0") + c1 = ly.create_cell("c1") + c2 = ly.create_cell("c2") + c3 = ly.create_cell("c3") + c1.insert(pya.CellInstArray(c2.cell_index(), pya.Trans(10, 20))) + c2.insert(pya.CellInstArray(c3.cell_index(), pya.Trans(11, 21))) + l1 = ly.insert_layer(pya.LayerInfo(1, 0)) + c0.shapes(l1).insert(pya.Box(0, 1, 2, 3)) + c1.shapes(l1).insert(pya.Text("Hello, world!", pya.Trans())) + c2.shapes(l1).insert(pya.Edge(0, 1, 21, 31)) + c3.shapes(l1).insert(pya.Polygon(pya.Box(0, 1, 22, 32))) + + rdb = pya.ReportDatabase("neu") + cat1 = rdb.create_category("l1") + cell1 = rdb.create_cell("c1") + for s in c0.shapes(l1).each(): + rdb.create_item(cell1.rdb_id(), cat1.rdb_id(), pya.CplxTrans(ly.dbu), s) + self.assertEqual(cat1.num_items(), 1) + cn = [ c.to_s_items() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[polygon: (0,0.001;0,0.003;0.002,0.003;0.002,0.001)]") + + rdb = pya.ReportDatabase("neu") + cat1 = rdb.create_category("l1") + cell1 = rdb.create_cell("c1") + rdb.create_items(cell1.rdb_id(), cat1.rdb_id(), pya.CplxTrans(ly.dbu), c0.shapes(l1)) + self.assertEqual(cat1.num_items(), 1) + cn = [ c.to_s_items() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[polygon: (0,0.001;0,0.003;0.002,0.003;0.002,0.001)]") + + rdb = pya.ReportDatabase("neu") + cat1 = rdb.create_category("l1") + cell1 = rdb.create_cell("c1") + rdb.create_items(cell1.rdb_id(), cat1.rdb_id(), c1.begin_shapes_rec(l1)) + self.assertEqual(cat1.num_items(), 3) + cn = [ c.to_s_items() for c in rdb.each_cell() ] + self.assertEqual(";".join(cn), "c1[label: ('Hello, world!',r0 0,0),edge: (0.01,0.021;0.031,0.051),polygon: (0.021,0.042;0.021,0.073;0.043,0.073;0.043,0.042)]") + + def test_13(self): + + # manipulations + rdb = pya.ReportDatabase("") + + _cell = rdb.create_cell("CELL") + _cat = rdb.create_category("cat") + _subcat = rdb.create_category(_cat, "subcat") + _subcat.description = "subcat_d" + _item1 = rdb.create_item(_cell.rdb_id(), _subcat.rdb_id()) + _item1.add_value(17.5) + _item1.add_value("string") + _item2 = rdb.create_item(_cell.rdb_id(), _subcat.rdb_id()) + _item2.add_value("b") + _subsubcat = rdb.create_category(_subcat, "subsubcat") + _cat2 = rdb.create_category("cat2") + + cell = rdb.cell_by_id(_cell.rdb_id()) + self.assertEqual(cell._is_const_object(), False) + self.assertEqual([ c for c in rdb.each_cell() ][0]._is_const_object(), False) + + cell = rdb.cell_by_qname("CELL") + self.assertEqual(cell._is_const_object(), False) + + cat = rdb.category_by_id(_cat.rdb_id()) + self.assertEqual(cat._is_const_object(), False) + + cat = rdb.category_by_path("cat") + self.assertEqual(cat._is_const_object(), False) + subcat = rdb.category_by_path("cat.subcat") + + self.assertEqual([ c for c in rdb.each_category() ][0]._is_const_object(), False) + self.assertEqual(",".join([ c.name() for c in rdb.each_category() ]), "cat,cat2") + self.assertEqual(subcat._is_const_object(), False) + self.assertEqual(subcat.database()._is_const_object(), False) + self.assertEqual(subcat.name(), "subcat") + self.assertEqual(subcat.parent().name(), "cat") + + self.assertEqual(subcat.description, "subcat_d") + subcat.description = "changed" + self.assertEqual(subcat.description, "changed") + + self.assertEqual(";".join([ "/".join([ str(v) for v in item.each_value() ]) for item in rdb.each_item_per_category(subcat.rdb_id())]), "float: 17.5/text: string;text: b") + + item1 = [ i for i in rdb.each_item_per_category(subcat.rdb_id())][0] + self.assertEqual(item1._is_const_object(), False) + item1.clear_values() + self.assertEqual(";".join([ "/".join([ str(v) for v in item.each_value() ]) for item in rdb.each_item_per_category(subcat.rdb_id())]), ";text: b") + item1.add_value("x") + self.assertEqual(";".join([ "/".join([ str(v) for v in item.each_value() ]) for item in rdb.each_item_per_category(subcat.rdb_id())]), "text: x;text: b") + item1.add_tag(17) + self.assertEqual(item1.has_tag(17), True) + self.assertEqual(item1.has_tag(16), False) + + item1 = [ i for i in rdb.each_item() ][0] + self.assertEqual(item1._is_const_object(), False) + self.assertEqual(item1.has_tag(17), True) + + item1 = [ i for i in rdb.each_item_per_cell(cell.rdb_id()) ][0] + self.assertEqual(item1._is_const_object(), False) + self.assertEqual(item1.has_tag(17), True) + + item1 = [ i for i in rdb.each_item_per_cell_and_category(cell.rdb_id(), subcat.rdb_id())][0] + self.assertEqual(item1._is_const_object(), False) + self.assertEqual(item1.has_tag(17), True) + + item1 = [ i for i in cell.each_item()][0] + self.assertEqual(item1._is_const_object(), False) + self.assertEqual(item1.has_tag(17), True) + +# run unit tests +if __name__ == '__main__': + suite = unittest.TestLoader().loadTestsFromTestCase(RDB_TestClass) + + if not unittest.TextTestRunner(verbosity = 1).run(suite).wasSuccessful(): + sys.exit(1) + From 0f438cdbd22d9bb029d4a27536bbbe5c04c0a50f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 13 Nov 2024 18:26:06 +0100 Subject: [PATCH 12/13] On same name, creating a RDB category would return the original one. --- src/rdb/rdb/rdb.cc | 12 ++++++++++++ src/rdb/rdb/rdb.h | 2 +- testdata/python/rdbTest.py | 20 ++++++++++++++++++++ testdata/ruby/rdbTest.rb | 17 +++++++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/rdb/rdb/rdb.cc b/src/rdb/rdb/rdb.cc index 64f8192f41..1f7ea37536 100644 --- a/src/rdb/rdb/rdb.cc +++ b/src/rdb/rdb/rdb.cc @@ -881,6 +881,13 @@ Categories::category_by_name (const char *path) return 0; } +Category * +Categories::category_by_raw_name (const std::string &name) +{ + auto c = m_categories_by_name.find (name); + return c == m_categories_by_name.end () ? 0 : c->second; +} + void Categories::import_category (Category *category) { @@ -1353,6 +1360,11 @@ Database::create_category (const std::string &name) Category * Database::create_category (Categories *container, const std::string &name) { + Category *existing = container->category_by_raw_name (name); + if (existing) { + return existing; + } + set_modified (); Category *cat = new Category (name); diff --git a/src/rdb/rdb/rdb.h b/src/rdb/rdb/rdb.h index 808af53ba7..c055ff4161 100644 --- a/src/rdb/rdb/rdb.h +++ b/src/rdb/rdb/rdb.h @@ -413,8 +413,8 @@ class RDB_PUBLIC Categories } void add_category (Category *cath); - void set_database (Database *database); + Category *category_by_raw_name (const std::string &name); }; /** diff --git a/testdata/python/rdbTest.py b/testdata/python/rdbTest.py index 91a0f08673..2916ffd106 100644 --- a/testdata/python/rdbTest.py +++ b/testdata/python/rdbTest.py @@ -952,6 +952,26 @@ def test_13(self): self.assertEqual(item1._is_const_object(), False) self.assertEqual(item1.has_tag(17), True) + def test_14(self): + + # same names do not generate a new category + rdb = pya.ReportDatabase("") + + _cell = rdb.create_cell("CELL") + + _cat = rdb.create_category("cat") + _cat_same = rdb.create_category("cat") + self.assertEqual(_cat.rdb_id(), _cat_same.rdb_id()) + + _subcat = rdb.create_category(_cat, "subcat") + _subcat_same = rdb.create_category(_cat_same, "subcat") + self.assertEqual(_subcat.rdb_id(), _subcat_same.rdb_id()) + + # testing whether decrementing the reference count would do harm + _cat = None + _cat_same = None + self.assertEqual(_subcat.rdb_id(), _subcat_same.rdb_id()) + # run unit tests if __name__ == '__main__': suite = unittest.TestLoader().loadTestsFromTestCase(RDB_TestClass) diff --git a/testdata/ruby/rdbTest.rb b/testdata/ruby/rdbTest.rb index eb99bd859a..15a4129bc4 100644 --- a/testdata/ruby/rdbTest.rb +++ b/testdata/ruby/rdbTest.rb @@ -1017,6 +1017,23 @@ def test_13 end + def test_14 + + # same names do not generate a new category + rdb = RBA::ReportDatabase::new("") + + _cell = rdb.create_cell("CELL") + + _cat = rdb.create_category("cat") + _cat_same = rdb.create_category("cat") + assert_equal(_cat.rdb_id, _cat_same.rdb_id) + + _subcat = rdb.create_category(_cat, "subcat") + _subcat_same = rdb.create_category(_cat_same, "subcat") + assert_equal(_subcat.rdb_id, _subcat_same.rdb_id) + + end + end load("test_epilogue.rb") From ce4dc0896923b1070eb0f27905549c43e2a5706b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 26 Nov 2024 23:15:28 +0100 Subject: [PATCH 13/13] Robustness of tests against different hash implementations, documentation update --- src/db/db/gsiDeclDbEdgeNeighborhood.cc | 5 +++- testdata/ruby/dbEdgeNeighborhood.rb | 33 +++++++++++++++----------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/db/db/gsiDeclDbEdgeNeighborhood.cc b/src/db/db/gsiDeclDbEdgeNeighborhood.cc index 7138abc3c9..15a9c04ab8 100644 --- a/src/db/db/gsiDeclDbEdgeNeighborhood.cc +++ b/src/db/db/gsiDeclDbEdgeNeighborhood.cc @@ -163,7 +163,10 @@ Class decl_EdgeNeighborhoodVisitorImpl (decl_E gsi::callback ("begin_polygon", &EdgeNeighborhoodVisitorImpl::issue_begin_polygon, &EdgeNeighborhoodVisitorImpl::f_begin_polygon, gsi::arg ("layout"), gsi::arg ("cell"), gsi::arg ("polygon"), "@brief Is called for each new polygon\n" "This event announces a new primary polygon. After this event, the edges of the polygon are reported via \\on_edge, " - "followed by a call of \\end_polygon." + "followed by a call of \\end_polygon.\n" + "\n" + "Note, that the polygon object is a temporary reference to a C++ object and it is only valid during the execution of this " + "callback. If you like to keep the polygon object, create a copy of it using the 'dup' method." ) + gsi::callback ("end_polygon", &EdgeNeighborhoodVisitorImpl::issue_end_polygon, &EdgeNeighborhoodVisitorImpl::f_end_polygon, "@brief Is called after the polygon\n" diff --git a/testdata/ruby/dbEdgeNeighborhood.rb b/testdata/ruby/dbEdgeNeighborhood.rb index 3837b4f794..edfeda7923 100644 --- a/testdata/ruby/dbEdgeNeighborhood.rb +++ b/testdata/ruby/dbEdgeNeighborhood.rb @@ -26,30 +26,35 @@ class MyVisitor < RBA::EdgeNeighborhoodVisitor def initialize - @log = "" + @log = {} + @current_log = nil end def log - @log + @log.keys.sort { |a,b| a < b ? -1 : (a == b ? 0 : 1) }.collect { |k| @log[k].join("") }.join("") end def begin_polygon(layout, cell, polygon) + polygon = polygon.dup output(polygon) - @log += "Polygon: #{polygon}\n" + @log[polygon] ||= [] + @current_log = @log[polygon] + @current_log << "Polygon: #{polygon}\n" end def end_polygon - @log += "/Polygon\n" + @current_log << "/Polygon\n" + @current_log = nil end def on_edge(layout, cell, edge, neighborhood) - @log += "edge = #{edge}\n" + @current_log << "edge = #{edge}\n" neighborhood.each do |n| x1, x2 = n[0] polygons = n[1] polygons.each do |inp, poly| poly_str = poly.collect { |p| p.to_s }.join("/") - @log += " #{x1},#{x2} -> #{inp}: #{poly_str}\n" + @current_log << " #{x1},#{x2} -> #{inp}: #{poly_str}\n" end end end @@ -68,7 +73,7 @@ def on_edge(layout, cell, edge, neighborhood) polygons.each do |inp, poly| poly.each do |p| bbox = p.bbox - t = self.to_original_trans(edge) + t = RBA::EdgeNeighborhoodVisitor.to_original_trans(edge) ep = RBA::EdgePair::new(edge, t * RBA::Edge::new(bbox.p1, RBA::Point::new(bbox.right, bbox.bottom))) output(ep) end @@ -111,19 +116,19 @@ def test_1 res = prim.complex_op(node) assert_equal(visitor.log, - "Polygon: (0,0;0,1000;1000,1000;1000,0)\n" + - "edge = (0,0;0,1000)\n" + - " 0.0,1000.0 -> 0: (0,100;0,101;1000,101;1000,100)\n" + - "edge = (0,1000;1000,1000)\n" + - "edge = (1000,1000;1000,0)\n" + - "edge = (1000,0;0,0)\n" + - "/Polygon\n" + "Polygon: (-1100,0;-1100,1000;-100,1000;-100,0)\n" + "edge = (-1100,0;-1100,1000)\n" + "edge = (-1100,1000;-100,1000)\n" + "edge = (-100,1000;-100,0)\n" + " 0.0,1000.0 -> 0: (0,100;0,101;1000,101;1000,100)\n" + "edge = (-100,0;-1100,0)\n" + + "/Polygon\n" + + "Polygon: (0,0;0,1000;1000,1000;1000,0)\n" + + "edge = (0,0;0,1000)\n" + + " 0.0,1000.0 -> 0: (0,100;0,101;1000,101;1000,100)\n" + + "edge = (0,1000;1000,1000)\n" + + "edge = (1000,1000;1000,0)\n" + + "edge = (1000,0;0,0)\n" + "/Polygon\n" )