From b0f05b5327714b2992a8324b24f5e4f7de8ca067 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 29 Mar 2025 23:18:16 +0100 Subject: [PATCH 01/58] WIP --- src/db/db/db.pro | 2 + src/db/db/dbPolygonGraph.cc | 846 +++++++++++++++++++++++ src/db/db/dbPolygonGraph.h | 777 +++++++++++++++++++++ src/db/unit_tests/dbPolygonGraphTests.cc | 51 ++ src/db/unit_tests/unit_tests.pro | 1 + 5 files changed, 1677 insertions(+) create mode 100644 src/db/db/dbPolygonGraph.cc create mode 100644 src/db/db/dbPolygonGraph.h create mode 100644 src/db/unit_tests/dbPolygonGraphTests.cc diff --git a/src/db/db/db.pro b/src/db/db/db.pro index bbae9671a..732d42aa1 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -76,6 +76,7 @@ SOURCES = \ dbPCellVariant.cc \ dbPoint.cc \ dbPolygon.cc \ + dbPolygonGraph.cc \ dbPolygonNeighborhood.cc \ dbPolygonTools.cc \ dbPolygonGenerators.cc \ @@ -314,6 +315,7 @@ HEADERS = \ dbPCellVariant.h \ dbPoint.h \ dbPolygon.h \ + dbPolygonGraph.h \ dbPolygonNeighborhood.h \ dbPolygonTools.h \ dbPolygonGenerators.h \ diff --git a/src/db/db/dbPolygonGraph.cc b/src/db/db/dbPolygonGraph.cc new file mode 100644 index 000000000..c7ae4a349 --- /dev/null +++ b/src/db/db/dbPolygonGraph.cc @@ -0,0 +1,846 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "dbPolygonGraph.h" +#include "dbLayout.h" +#include "dbWriter.h" +#include "tlStream.h" +#include "tlLog.h" +#include "tlTimer.h" + +#include +#include +#include +#include + +namespace db +{ + +// ------------------------------------------------------------------------------------- +// GVertex implementation + +GVertex::GVertex () + : DPoint (), m_is_precious (false) +{ + // .. nothing yet .. +} + +GVertex::GVertex (const db::DPoint &p) + : DPoint (p), m_is_precious (false) +{ + // .. nothing yet .. +} + +GVertex::GVertex (const GVertex &v) + : DPoint (), m_is_precious (false) +{ + operator= (v); +} + +GVertex &GVertex::operator= (const GVertex &v) +{ + if (this != &v) { + // NOTE: edges are not copied! + db::DPoint::operator= (v); + m_is_precious = v.m_is_precious; + } + return *this; +} + +GVertex::GVertex (db::DCoord x, db::DCoord y) + : DPoint (x, y), m_is_precious (false) +{ + // .. nothing yet .. +} + +#if 0 // @@@ +bool +GVertex::is_outside () const +{ + for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) { + if ((*e)->is_outside ()) { + return true; + } + } + return false; +} +#endif + +std::vector +GVertex::polygons () const +{ + std::set seen; + std::vector res; + for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) { + for (auto t = (*e)->begin_polygons (); t != (*e)->end_polygons (); ++t) { + if (seen.insert (t.operator-> ()).second) { + res.push_back (t.operator-> ()); + } + } + } + return res; +} + +bool +GVertex::has_edge (const GPolygonEdge *edge) const +{ + for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) { + if (*e == edge) { + return true; + } + } + return false; +} + +size_t +GVertex::num_edges (int max_count) const +{ + if (max_count < 0) { + // NOTE: this can be slow for a std::list, so we have max_count to limit this effort + return mp_edges.size (); + } else { + size_t n = 0; + for (auto i = mp_edges.begin (); i != mp_edges.end () && --max_count >= 0; ++i) { + ++n; + } + return n; + } +} + +std::string +GVertex::to_string (bool with_id) const +{ + std::string res = tl::sprintf ("(%.12g, %.12g)", x (), y()); + if (with_id) { + res += tl::sprintf ("[%x]", (size_t)this); + } + return res; +} + +int +GVertex::in_circle (const DPoint &point, const DPoint ¢er, double radius) +{ + double dx = point.x () - center.x (); + double dy = point.y () - center.y (); + double d2 = dx * dx + dy * dy; + double r2 = radius * radius; + double delta = fabs (d2 + r2) * db::epsilon; + if (d2 < r2 - delta) { + return 1; + } else if (d2 < r2 + delta) { + return 0; + } else { + return -1; + } +} + +// ------------------------------------------------------------------------------------- +// GPolygonEdge implementation + +GPolygonEdge::GPolygonEdge () + : mp_v1 (0), mp_v2 (0), mp_left (), mp_right (), m_level (0), m_id (0), m_is_segment (false) +{ + // .. nothing yet .. +} + +GPolygonEdge::GPolygonEdge (GVertex *v1, GVertex *v2) + : mp_v1 (v1), mp_v2 (v2), mp_left (), mp_right (), m_level (0), m_id (0), m_is_segment (false) +{ + // .. nothing yet .. +} + +void +GPolygonEdge::set_left (GPolygon *t) +{ + mp_left = t; +} + +void +GPolygonEdge::set_right (GPolygon *t) +{ + mp_right = t; +} + +void +GPolygonEdge::link () +{ + mp_v1->mp_edges.push_back (this); + m_ec_v1 = --mp_v1->mp_edges.end (); + + mp_v2->mp_edges.push_back (this); + m_ec_v2 = --mp_v2->mp_edges.end (); +} + +void +GPolygonEdge::unlink () +{ + if (mp_v1) { + mp_v1->remove_edge (m_ec_v1); + } + if (mp_v2) { + mp_v2->remove_edge (m_ec_v2); + } + mp_v1 = mp_v2 = 0; +} + +GPolygon * +GPolygonEdge::other (const GPolygon *t) const +{ + if (t == mp_left) { + return mp_right; + } + if (t == mp_right) { + return mp_left; + } + tl_assert (false); + return 0; +} + +GVertex * +GPolygonEdge::other (const GVertex *t) const +{ + if (t == mp_v1) { + return mp_v2; + } + if (t == mp_v2) { + return mp_v1; + } + tl_assert (false); + return 0; +} + +bool +GPolygonEdge::has_vertex (const GVertex *v) const +{ + return mp_v1 == v || mp_v2 == v; +} + +GVertex * +GPolygonEdge::common_vertex (const GPolygonEdge *other) const +{ + if (has_vertex (other->v1 ())) { + return (other->v1 ()); + } + if (has_vertex (other->v2 ())) { + return (other->v2 ()); + } + return 0; +} + +std::string +GPolygonEdge::to_string (bool with_id) const +{ + std::string res = std::string ("(") + mp_v1->to_string (with_id) + ", " + mp_v2->to_string (with_id) + ")"; + if (with_id) { + res += tl::sprintf ("[%x]", (size_t)this); + } + return res; +} + +double +GPolygonEdge::distance (const db::DEdge &e, const db::DPoint &p) +{ + double l = db::sprod (p - e.p1 (), e.d ()) / e.d ().sq_length (); + db::DPoint pp; + if (l <= 0.0) { + pp = e.p1 (); + } else if (l >= 1.0) { + pp = e.p2 (); + } else { + pp = e.p1 () + e.d () * l; + } + return (p - pp).length (); +} + +bool +GPolygonEdge::crosses (const db::DEdge &e, const db::DEdge &other) +{ + return e.side_of (other.p1 ()) * e.side_of (other.p2 ()) < 0 && + other.side_of (e.p1 ()) * other.side_of (e.p2 ()) < 0; +} + +bool +GPolygonEdge::crosses_including (const db::DEdge &e, const db::DEdge &other) +{ + return e.side_of (other.p1 ()) * e.side_of (other.p2 ()) <= 0 && + other.side_of (e.p1 ()) * other.side_of (e.p2 ()) <= 0; +} + +db::DPoint +GPolygonEdge::intersection_point (const db::DEdge &e, const db::DEdge &other) +{ + return e.intersect_point (other).second; +} + +bool +GPolygonEdge::point_on (const db::DEdge &edge, const db::DPoint &point) +{ + if (edge.side_of (point) != 0) { + return false; + } else { + return db::sprod_sign (point - edge.p1 (), edge.d ()) * db::sprod_sign(point - edge.p2 (), edge.d ()) < 0; + } +} + +#if 0 // @@@ +bool +GPolygonEdge::is_for_outside_polygons () const +{ + return (left () && left ()->is_outside ()) || (right () && right ()->is_outside ()); +} +#endif + +bool +GPolygonEdge::has_polygon (const GPolygon *t) const +{ + return t != 0 && (left () == t || right () == t); +} + +// ------------------------------------------------------------------------------------- +// GPolygon implementation + +GPolygon::GPolygon () + : m_id (0) +{ + // .. nothing yet .. +} + +void +GPolygon::init () +{ + m_id = 0; + + if (mp_e.empty ()) { + return; + } + + std::vector e; + e.swap (mp_e); + + std::multimap v2e; + + for (auto i = e.begin (); i != e.end (); ++i) { + if (i != e.begin ()) { + v2e.insert (std::make_pair ((*i)->v1 (), *i)); + v2e.insert (std::make_pair ((*i)->v2 (), *i)); + } + } + + mp_e.reserve (e.size ()); + mp_e.push_back (e.front ()); + // NOTE: we assume the edges follow the clockwise orientation + mp_e.back ()->set_right (this); + + mp_v.reserve (e.size ()); + mp_v.push_back (mp_e.back ()->v1 ()); + + auto v = mp_e.back ()->v2 (); + + // join the edges in the order of the polygon + while (! v2e.empty ()) { + + mp_v.push_back (v); + + auto i = v2e.find (v); + tl_assert (i != v2e.end () && i->first == v && i->second != mp_e.back ()); + v2e.erase (i); + mp_e.push_back (i->second); + // NOTE: we assume the edges follow the clockwise orientation + mp_e.back ()->set_right (this); + + v = i->second->other (v); + i = v2e.find (v); + while (i != v2e.end () && i->first == v) { + if (i->second == mp_e.back ()) { + v2e.erase (i); + break; + } + ++i; + } + + } +} + +GPolygon::~GPolygon () +{ + unlink (); +} + +void +GPolygon::unlink () +{ + for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { + if ((*e)->left () == this) { + (*e)->set_left (0); + } + if ((*e)->right () == this) { + (*e)->set_right (0); + } + } +} + +std::string +GPolygon::to_string (bool with_id) const +{ + std::string res = "("; + for (int i = 0; i < int (size ()); ++i) { + if (i > 0) { + res += ", "; + } + if (vertex (i)) { + res += vertex (i)->to_string (with_id); + } else { + res += "(null)"; + } + } + res += ")"; + return res; +} + +double +GPolygon::area () const +{ + return fabs (db::vprod (mp_e[0]->d (), mp_e[1]->d ())) * 0.5; +} + +db::DBox +GPolygon::bbox () const +{ + db::DBox box; + for (auto i = mp_v.begin (); i != mp_v.end (); ++i) { + box += **i; + } + return box; +} + +GPolygonEdge * +GPolygon::find_edge_with (const GVertex *v1, const GVertex *v2) const +{ + for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { + if ((*e)->has_vertex (v1) && (*e)->has_vertex (v2)) { + return *e; + } + } + tl_assert (false); +} + +GPolygonEdge * +GPolygon::common_edge (const GPolygon *other) const +{ + for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { + if ((*e)->other (this) == other) { + return *e; + } + } + return 0; +} + +#if 0 // @@@ +int +GPolygon::contains (const db::DPoint &point) const +{ + auto c = *mp_v[2] - *mp_v[0]; + auto b = *mp_v[1] - *mp_v[0]; + + int vps = db::vprod_sign (c, b); + if (vps == 0) { + return db::vprod_sign (point - *mp_v[0], b) == 0 && db::vprod_sign (point - *mp_v[0], c) == 0 ? 0 : -1; + } + + int res = 1; + + const GVertex *vl = mp_v[2]; + for (int i = 0; i < 3; ++i) { + const GVertex *v = mp_v[i]; + int n = db::vprod_sign (point - *vl, *v - *vl) * vps; + if (n < 0) { + return -1; + } else if (n == 0) { + res = 0; + } + vl = v; + } + + return res; +} +#endif + +double +GPolygon::min_edge_length () const +{ + double lmin = mp_e[0]->d ().length (); + for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { + lmin = std::min (lmin, (*e)->d ().length ()); + } + return lmin; +} + +#if 0 // @@@ +double +GPolygon::b () const +{ + double lmin = min_edge_length (); + bool ok = false; + auto cr = circumcircle (&ok); + return ok ? lmin / cr.second : 0.0; +} +#endif + +bool +GPolygon::has_segment () const +{ + for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { + if ((*e)->is_segment ()) { + return true; + } + } + return false; +} + +unsigned int +GPolygon::num_segments () const +{ + unsigned int n = 0; + for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { + if ((*e)->is_segment ()) { + ++n; + } + } + return n; +} + +// ----------------------------------------------------------------------------------- + +static inline bool is_equal (const db::DPoint &a, const db::DPoint &b) +{ + return std::abs (a.x () - b.x ()) < std::max (1.0, (std::abs (a.x ()) + std::abs (b.x ()))) * db::epsilon && + std::abs (a.y () - b.y ()) < std::max (1.0, (std::abs (a.y ()) + std::abs (b.y ()))) * db::epsilon; +} + +PolygonGraph::PolygonGraph () + : m_id (0) + // @@@: m_is_constrained (false), m_level (0), m_id (0), m_flips (0), m_hops (0) +{ + // .. nothing yet .. +} + +PolygonGraph::~PolygonGraph () +{ + clear (); +} + +db::GVertex * +PolygonGraph::create_vertex (double x, double y) +{ + m_vertex_heap.push_back (db::GVertex (x, y)); + return &m_vertex_heap.back (); +} + +db::GVertex * +PolygonGraph::create_vertex (const db::DPoint &pt) +{ + m_vertex_heap.push_back (pt); + return &m_vertex_heap.back (); +} + +db::GPolygonEdge * +PolygonGraph::create_edge (db::GVertex *v1, db::GVertex *v2) +{ + db::GPolygonEdge *edge = 0; + + if (! m_returned_edges.empty ()) { + edge = m_returned_edges.back (); + m_returned_edges.pop_back (); + *edge = db::GPolygonEdge (v1, v2); + } else { + m_edges_heap.push_back (db::GPolygonEdge (v1, v2)); + edge = &m_edges_heap.back (); + } + + edge->link (); + edge->set_id (++m_id); + return edge; +} + +void +PolygonGraph::remove_polygon (db::GPolygon *poly) +{ + std::vector edges; + edges.reserve (poly->size ()); + for (int i = 0; i < int (poly->size ()); ++i) { + edges [i] = poly->edge (i); + } + + delete poly; + + // clean up edges we do no longer need + for (auto e = edges.begin (); e != edges.end (); ++e) { + if ((*e) && (*e)->left () == 0 && (*e)->right () == 0 && (*e)->v1 ()) { + (*e)->unlink (); + m_returned_edges.push_back (*e); + } + } +} + +void +PolygonGraph::insert_polygon (const db::DPolygon &polygon) +{ + if (polygon.begin_edge ().at_end ()) { + return; + } + + std::vector edges; + + for (unsigned int c = 0; c < polygon.holes () + 1; ++c) { + const db::DPolygon::contour_type &ctr = polygon.contour (c); + db::GVertex *v0 = 0, *vv, *v; + for (auto p = ctr.begin (); p != ctr.end (); ++p) { + v = create_vertex ((*p).x (), (*p).y ()); + if (! v0) { + v0 = v; + } else { + edges.push_back (create_edge (vv, v)); + } + vv = v; + } + if (v0 && v0 != v) { + edges.push_back (create_edge (v, v0)); + } + } + + create_polygon (edges.begin (), edges.end ()); +} + +std::string +PolygonGraph::to_string () +{ + std::string res; + for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) { + if (! res.empty ()) { + res += ", "; + } + res += t->to_string (); + } + return res; +} + +db::DBox +PolygonGraph::bbox () const +{ + db::DBox box; + for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) { + box += t->bbox (); + } + return box; +} + +#if 0 // @@@ +bool +PolygonGraph::check (bool check_delaunay) const +{ + bool res = true; + + if (check_delaunay) { + for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) { + auto cp = t->circumcircle (); + auto vi = find_inside_circle (cp.first, cp.second); + if (! vi.empty ()) { + res = false; + tl::error << "(check error) polygon does not meet Delaunay criterion: " << t->to_string (); + for (auto v = vi.begin (); v != vi.end (); ++v) { + tl::error << " vertex inside circumcircle: " << (*v)->to_string (true); + } + } + } + } + + for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) { + for (int i = 0; i < 3; ++i) { + if (! t->edge (i)->has_polygon (t.operator-> ())) { + tl::error << "(check error) edges " << t->edge (i)->to_string (true) + << " attached to polygon " << t->to_string (true) << " does not refer to this polygon"; + res = false; + } + } + } + + for (auto e = m_edges_heap.begin (); e != m_edges_heap.end (); ++e) { + + if (!e->left () && !e->right ()) { + continue; + } + + if (e->left () && e->right ()) { + if (e->left ()->is_outside () != e->right ()->is_outside () && ! e->is_segment ()) { + tl::error << "(check error) edge " << e->to_string (true) << " splits an outside and inside polygon, but is not a segment"; + res = false; + } + } + + for (auto t = e->begin_polygons (); t != e->end_polygons (); ++t) { + if (! t->has_edge (e.operator-> ())) { + tl::error << "(check error) edge " << e->to_string (true) << " not found in adjacent polygon " << t->to_string (true); + res = false; + } + if (! t->has_vertex (e->v1 ())) { + tl::error << "(check error) edges " << e->to_string (true) << " vertex 1 not found in adjacent polygon " << t->to_string (true); + res = false; + } + if (! t->has_vertex (e->v2 ())) { + tl::error << "(check error) edges " << e->to_string (true) << " vertex 2 not found in adjacent polygon " << t->to_string (true); + res = false; + } + db::GVertex *vopp = t->opposite (e.operator-> ()); + double sgn = (e->left () == t.operator-> ()) ? 1.0 : -1.0; + double vp = db::vprod (e->d(), *vopp - *e->v1 ()); // positive if on left side + if (vp * sgn <= 0.0) { + const char * side_str = sgn > 0.0 ? "left" : "right"; + tl::error << "(check error) external point " << vopp->to_string (true) << " not on " << side_str << " side of edge " << e->to_string (true); + res = false; + } + } + + if (! e->v1 ()->has_edge (e.operator-> ())) { + tl::error << "(check error) edge " << e->to_string (true) << " vertex 1 does not list this edge"; + res = false; + } + if (! e->v2 ()->has_edge (e.operator-> ())) { + tl::error << "(check error) edge " << e->to_string (true) << " vertex 2 does not list this edge"; + res = false; + } + + } + + for (auto v = m_vertex_heap.begin (); v != m_vertex_heap.end (); ++v) { + unsigned int num_outside_edges = 0; + for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { + if ((*e)->is_outside ()) { + ++num_outside_edges; + } + } + if (num_outside_edges > 0 && num_outside_edges != 2) { + tl::error << "(check error) vertex " << v->to_string (true) << " has " << num_outside_edges << " outside edges (can only be 2)"; + res = false; + for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { + if ((*e)->is_outside ()) { + tl::error << " Outside edge is " << (*e)->to_string (true); + } + } + } + } + + return res; +} +#endif + +db::Layout * +PolygonGraph::to_layout (bool decompose_by_id) const +{ + db::Layout *layout = new db::Layout (); + layout->dbu (0.001); + + auto dbu_trans = db::CplxTrans (layout->dbu ()).inverted (); + + db::Cell &top = layout->cell (layout->add_cell ("DUMP")); + unsigned int l1 = layout->insert_layer (db::LayerProperties (1, 0)); + // @@@ unsigned int l2 = layout->insert_layer (db::LayerProperties (2, 0)); + unsigned int l10 = layout->insert_layer (db::LayerProperties (10, 0)); + unsigned int l20 = layout->insert_layer (db::LayerProperties (20, 0)); + unsigned int l21 = layout->insert_layer (db::LayerProperties (21, 0)); + unsigned int l22 = layout->insert_layer (db::LayerProperties (22, 0)); + + std::vector pts; + for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) { + pts.clear (); + for (int i = 0; i < int (t->size ()); ++i) { + pts.push_back (*t->vertex (i)); + } + db::DPolygon poly; + poly.assign_hull (pts.begin (), pts.end ()); + top.shapes (/*@@@t->is_outside () ? l2 :*/ l1).insert (dbu_trans * poly); + if (decompose_by_id) { + if ((t->id () & 1) != 0) { + top.shapes (l20).insert (dbu_trans * poly); + } + if ((t->id () & 2) != 0) { + top.shapes (l21).insert (dbu_trans * poly); + } + if ((t->id () & 4) != 0) { + top.shapes (l22).insert (dbu_trans * poly); + } + } + } + + for (auto e = m_edges_heap.begin (); e != m_edges_heap.end (); ++e) { + if ((e->left () || e->right ()) && e->is_segment ()) { + top.shapes (l10).insert (dbu_trans * e->edge ()); + } + } + + return layout; +} + +void +PolygonGraph::dump (const std::string &path, bool decompose_by_id) const +{ + std::unique_ptr ly (to_layout (decompose_by_id)); + + tl::OutputStream stream (path); + + db::SaveLayoutOptions opt; + db::Writer writer (opt); + writer.write (*ly, stream); + + tl::info << "PolygonGraph written to " << path; +} + +void +PolygonGraph::clear () +{ + mp_polygons.clear (); + m_edges_heap.clear (); + m_vertex_heap.clear (); + m_returned_edges.clear (); + // @@@m_is_constrained = false; + // @@@m_level = 0; + m_id = 0; +} + +template +void +PolygonGraph::make_contours (const Poly &poly, const Trans &trans, std::vector > &edge_contours) +{ + edge_contours.push_back (std::vector ()); + for (auto pt = poly.begin_hull (); pt != poly.end_hull (); ++pt) { + edge_contours.back ().push_back (insert_point (trans * *pt)); + } + + for (unsigned int h = 0; h < poly.holes (); ++h) { + edge_contours.push_back (std::vector ()); + for (auto pt = poly.begin_hole (h); pt != poly.end_hole (h); ++pt) { + edge_contours.back ().push_back (insert_point (trans * *pt)); + } + } +} + +} diff --git a/src/db/db/dbPolygonGraph.h b/src/db/db/dbPolygonGraph.h new file mode 100644 index 000000000..cfbdee450 --- /dev/null +++ b/src/db/db/dbPolygonGraph.h @@ -0,0 +1,777 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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_dbPolygonGraph +#define HDR_dbPolygonGraph + +#include "dbCommon.h" +#include "dbTriangle.h" +#include "dbBox.h" +#include "dbRegion.h" + +#include "tlObjectCollection.h" +#include "tlStableVector.h" + +#include +#include +#include +#include + +namespace db +{ + +class Layout; + +class GPolygon; +class GPolygonEdge; + +/** + * @brief A class representing a vertex in a Delaunay triangulation graph + * + * The vertex carries information about the connected edges and + * an integer value that can be used in traversal algorithms + * ("level") + */ +class DB_PUBLIC GVertex + : public db::DPoint +{ +public: + typedef std::list edges_type; + typedef edges_type::const_iterator edges_iterator; + typedef edges_type::iterator edges_iterator_non_const; + + GVertex (); + GVertex (const DPoint &p); + GVertex (const GVertex &v); + GVertex (db::DCoord x, db::DCoord y); + + GVertex &operator= (const GVertex &v); + +#if 0 // @@@ + bool is_outside () const; +#endif + std::vector polygons () const; + + edges_iterator begin_edges () const { return mp_edges.begin (); } + edges_iterator end_edges () const { return mp_edges.end (); } + size_t num_edges (int max_count = -1) const; + + bool has_edge (const GPolygonEdge *edge) const; + + void set_is_precious (bool f) { m_is_precious = f; } + bool is_precious () const { return m_is_precious; } + + std::string to_string (bool with_id = false) const; + + /** + * @brief Returns 1 is the point is inside the circle, 0 if on the circle and -1 if outside + * TODO: Move to db::DPoint + */ + static int in_circle (const db::DPoint &point, const db::DPoint ¢er, double radius); + + /** + * @brief Returns 1 is this point is inside the circle, 0 if on the circle and -1 if outside + */ + int in_circle (const db::DPoint ¢er, double radius) const + { + return in_circle (*this, center, radius); + } + +private: + friend class GPolygonEdge; + + void remove_edge (const edges_iterator_non_const &ec) + { + mp_edges.erase (ec); + } + + edges_type mp_edges; + bool m_is_precious; +}; + +/** + * @brief A class representing an edge in the Delaunay triangulation graph + */ +class DB_PUBLIC GPolygonEdge +{ +public: + class GPolygonIterator + { + public: + typedef GPolygon value_type; + typedef GPolygon &reference; + typedef GPolygon *pointer; + + reference operator*() const + { + return *operator-> (); + } + + pointer operator->() const + { + return m_index ? mp_edge->right () : mp_edge->left (); + } + + bool operator== (const GPolygonIterator &other) const + { + return m_index == other.m_index; + } + + bool operator!= (const GPolygonIterator &other) const + { + return !operator== (other); + } + + GPolygonIterator &operator++ () + { + while (++m_index < 2 && operator-> () == 0) + ; + return *this; + } + + private: + friend class GPolygonEdge; + + GPolygonIterator (const GPolygonEdge *edge) + : mp_edge (edge), m_index (0) + { + if (! edge) { + m_index = 2; + } else { + --m_index; + operator++ (); + } + } + + const GPolygonEdge *mp_edge; + unsigned int m_index; + }; + + GPolygonEdge (); + GPolygonEdge (GVertex *v1, GVertex *v2); + + GVertex *v1 () const { return mp_v1; } + GVertex *v2 () const { return mp_v2; } + + void reverse () + { + std::swap (mp_v1, mp_v2); + std::swap (mp_left, mp_right); + } + + GPolygon *left () const { return mp_left; } + GPolygon *right () const { return mp_right; } + + GPolygonIterator begin_polygons () const + { + return GPolygonIterator (this); + } + + GPolygonIterator end_polygons () const + { + return GPolygonIterator (0); + } + + void set_level (size_t l) { m_level = l; } + size_t level () const { return m_level; } + + void set_id (size_t id) { m_id = id; } + size_t id () const { return m_id; } + + void set_is_segment (bool is_seg) { m_is_segment = is_seg; } + bool is_segment () const { return m_is_segment; } + + std::string to_string (bool with_id = false) const; + + /** + * @brief Converts to an db::DEdge + */ + db::DEdge edge () const + { + return db::DEdge (*mp_v1, *mp_v2); + } + + /** + * @brief Returns the distance of the given point to the edge + * + * The distance is the minimum distance of the point to one point from the edge. + * TODO: Move to db::DEdge + */ + static double distance (const db::DEdge &e, const db::DPoint &p); + + /** + * @brief Returns the distance of the given point to the edge + * + * The distance is the minimum distance of the point to one point from the edge. + */ + double distance (const db::DPoint &p) const + { + return distance (edge (), p); + } + + /** + * @brief Returns a value indicating whether this edge crosses the other one + * + * "crosses" is true, if both edges share at least one point which is not an endpoint + * of one of the edges. + * TODO: Move to db::DEdge + */ + static bool crosses (const db::DEdge &e, const db::DEdge &other); + + /** + * @brief Returns a value indicating whether this edge crosses the other one + * + * "crosses" is true, if both edges share at least one point which is not an endpoint + * of one of the edges. + */ + bool crosses (const db::DEdge &other) const + { + return crosses (edge (), other); + } + + /** + * @brief Returns a value indicating whether this edge crosses the other one + * + * "crosses" is true, if both edges share at least one point which is not an endpoint + * of one of the edges. + */ + bool crosses (const db::GPolygonEdge &other) const + { + return crosses (edge (), other.edge ()); + } + + /** + * @brief Returns a value indicating whether this edge crosses the other one + * "crosses" is true, if both edges share at least one point. + * TODO: Move to db::DEdge + */ + static bool crosses_including (const db::DEdge &e, const db::DEdge &other); + + /** + * @brief Returns a value indicating whether this edge crosses the other one + * "crosses" is true, if both edges share at least one point. + */ + bool crosses_including (const db::DEdge &other) const + { + return crosses_including (edge (), other); + } + + /** + * @brief Returns a value indicating whether this edge crosses the other one + * "crosses" is true, if both edges share at least one point. + */ + bool crosses_including (const db::GPolygonEdge &other) const + { + return crosses_including (edge (), other.edge ()); + } + + /** + * @brief Gets the intersection point + * TODO: Move to db::DEdge + */ + static db::DPoint intersection_point (const db::DEdge &e, const DEdge &other); + + /** + * @brief Gets the intersection point + */ + db::DPoint intersection_point (const db::DEdge &other) const + { + return intersection_point (edge (), other); + } + + /** + * @brief Gets the intersection point + */ + db::DPoint intersection_point (const GPolygonEdge &other) const + { + return intersection_point (edge (), other.edge ()); + } + + /** + * @brief Returns a value indicating whether the point is on the edge + * TODO: Move to db::DEdge + */ + static bool point_on (const db::DEdge &edge, const db::DPoint &point); + + /** + * @brief Returns a value indicating whether the point is on the edge + */ + bool point_on (const db::DPoint &point) const + { + return point_on (edge (), point); + } + + /** + * @brief Gets the side the point is on + * + * -1 is for "left", 0 is "on" and +1 is "right" + * TODO: correct to same definition as db::Edge (negative) + */ + static int side_of (const db::DEdge &e, const db::DPoint &point) + { + return -e.side_of (point); + } + + /** + * @brief Gets the side the point is on + * + * -1 is for "left", 0 is "on" and +1 is "right" + * TODO: correct to same definition as db::Edge (negative) + */ + int side_of (const db::DPoint &p) const + { + return -edge ().side_of (p); + } + + /** + * @brief Gets the distance vector + */ + db::DVector d () const + { + return *mp_v2 - *mp_v1; + } + + /** + * @brief Gets the other triangle for the given one + */ + GPolygon *other (const GPolygon *) const; + + /** + * @brief Gets the other vertex for the given one + */ + GVertex *other (const GVertex *) const; + + /** + * @brief Gets a value indicating whether the edge has the given vertex + */ + bool has_vertex (const GVertex *) const; + + /** + * @brief Gets the common vertex of the other edge and this edge or null if there is no common vertex + */ + GVertex *common_vertex (const GPolygonEdge *other) const; + +#if 0 // @@@ + /** + * @brief Returns a value indicating whether this edge can be flipped + */ + bool can_flip () const; + + /** + * @brief Returns a value indicating whether the edge separates two triangles that can be joined into one (via the given vertex) + */ + bool can_join_via (const GVertex *vertex) const; + + /** + * @brief Returns a value indicating whether this edge is an outside edge (no other triangles) + */ + bool is_outside () const; + + /** + * @brief Returns a value indicating whether this edge belongs to outside triangles + */ + bool is_for_outside_triangles () const; +#endif // @@@ + + /** + * @brief Returns a value indicating whether t is attached to this edge + */ + bool has_polygon (const GPolygon *t) const; + +protected: + void unlink (); + void link (); + +private: + friend class GPolygon; + friend class PolygonGraph; + + GVertex *mp_v1, *mp_v2; + GPolygon *mp_left, *mp_right; + GVertex::edges_iterator_non_const m_ec_v1, m_ec_v2; + size_t m_level; + size_t m_id; + bool m_is_segment; + + void set_left (GPolygon *t); + void set_right (GPolygon *t); +}; + +/** + * @brief A compare function that compares triangles by ID + * + * The ID acts as a more predicable unique ID for the object in sets and maps. + */ +struct GPolygonEdgeLessFunc +{ + bool operator () (GPolygonEdge *a, GPolygonEdge *b) const + { + return a->id () < b->id (); + } +}; + +/** + * @brief A class representing a triangle + */ +class DB_PUBLIC GPolygon + : public tl::list_node, public tl::Object +{ +public: + GPolygon (); + + template + GPolygon (Iter from, Iter to) + : mp_e (from, to) + { + init (); + } + + ~GPolygon (); + + void unlink (); + + void set_id (size_t id) { m_id = id; } + size_t id () const { return m_id; } + + // @@@bool is_outside () const { return m_is_outside; } + // @@@void set_outside (bool o) { m_is_outside = o; } + + std::string to_string (bool with_id = false) const; + + /** + * @brief Gets the number of vertexes + */ + size_t size () const + { + return mp_v.size (); + } + + /** + * @brief Gets the nth vertex (n wraps around and can be negative) + * The vertexes are oriented clockwise. + */ + inline GVertex *vertex (int n) const + { + size_t sz = size (); + tl_assert (sz > 0); + if (n >= 0 && size_t (n) < sz) { + return mp_v[n]; + } else { + return mp_v[(n + sz) % sz]; + } + } + + /** + * @brief Gets the nth edge (n wraps around and can be negative) + */ + inline GPolygonEdge *edge (int n) const + { + size_t sz = size (); + tl_assert (sz > 0); + if (n >= 0 && size_t (n) < sz) { + return mp_e[n]; + } else { + return mp_e[(n + sz) % sz]; + } + } + + /** + * @brief Gets the area + */ + double area () const; + + /** + * @brief Returns the bounding box of the triangle + */ + db::DBox bbox () const; + +#if 0 // @@@ + /** + * @brief Gets the center point and radius of the circumcircle + * If ok is non-null, it will receive a boolean value indicating whether the circumcircle is valid. + * An invalid circumcircle is an indicator for a degenerated triangle with area 0 (or close to). + */ + std::pair circumcircle (bool *ok = 0) const; + + /** + * @brief Gets the vertex opposite of the given edge + */ + GVertex *opposite (const GPolygonEdge *edge) const; + + /** + * @brief Gets the edge opposite of the given vertex + */ + GPolygonEdge *opposite (const GVertex *vertex) const; +#endif + + /** + * @brief Gets the edge with the given vertexes + */ + GPolygonEdge *find_edge_with (const GVertex *v1, const GVertex *v2) const; + + /** + * @brief Finds the common edge for both polygons + */ + GPolygonEdge *common_edge (const GPolygon *other) const; + +#if 0 // @@@ + /** + * @brief Returns a value indicating whether the point is inside (1), on the polygon (0) or outside (-1) + */ + int contains (const db::DPoint &point) const; +#endif + + /** + * @brief Gets a value indicating whether the triangle has the given vertex + */ + inline bool has_vertex (const db::GVertex *v) const + { + for (auto i = mp_v.begin (); i != mp_v.end (); ++i) { + if (*i == v) { + return true; + } + } + return false; + } + + /** + * @brief Gets a value indicating whether the triangle has the given edge + */ + inline bool has_edge (const db::GPolygonEdge *e) const + { + for (auto i = mp_e.begin (); i != mp_e.end (); ++i) { + if (*i == e) { + return true; + } + } + return false; + } + + /** + * @brief Returns the minimum edge length + */ + double min_edge_length () const; + +#if 0 // @@@ + /** + * @brief Returns the min edge length to circumcircle radius ratio + */ + double b () const; +#endif + + /** + * @brief Returns a value indicating whether the polygon borders to a segment + */ + bool has_segment () const; + + /** + * @brief Returns the number of segments the polygon borders to + */ + unsigned int num_segments () const; + +private: + // @@@ bool m_is_outside; + std::vector mp_e; + std::vector mp_v; + size_t m_id; + + void init (); + + // no copying + GPolygon &operator= (const GPolygon &); + GPolygon (const GPolygon &); +}; + +/** + * @brief A compare function that compares polygons by ID + * + * The ID acts as a more predicable unique ID for the object in sets and maps. + */ +struct GPolygonLessFunc +{ + bool operator () (GPolygon *a, GPolygon *b) const + { + return a->id () < b->id (); + } +}; + +class DB_PUBLIC PolygonGraph +{ +public: +#if 0 // @@@ + struct TriangulateParameters + { + TriangulateParameters () + : min_b (1.0), + min_length (0.0), + max_area (0.0), + max_area_border (0.0), + max_iterations (std::numeric_limits::max ()), + base_verbosity (30), + mark_triangles (false) + { } + + /** + * @brief Min. readius-to-shortest edge ratio + */ + double min_b; + + /** + * @brief Min. edge length + * + * This parameter does not provide a guarantee about a minimume edge length, but + * helps avoiding ever-reducing triangle splits in acute corners of the input polygon. + * Splitting of edges stops when the edge is less than the min length. + */ + double min_length; + + /** + * @brief Max area or zero for "no constraint" + */ + double max_area; + + /** + * @brief Max area for border triangles or zero for "use max_area" + */ + double max_area_border; + + /** + * @brief Max number of iterations + */ + size_t max_iterations; + + /** + * @brief The verbosity level above which triangulation reports details + */ + int base_verbosity; + + /** + * @brief If true, final triangles are marked using the "id" integer as a bit field + * + * This provides information about the result quality. + * + * Bit 0: skinny triangle + * Bit 1: bad-quality (skinny or area too large) + * Bit 2: non-Delaunay (in the strict sense) + */ + bool mark_triangles; + }; +#endif + + typedef tl::list polygons_type; + typedef polygons_type::const_iterator polygon_iterator; + + PolygonGraph (); + ~PolygonGraph (); + + /** + * @brief Inserts the given polygon + */ + void insert_polygon (const db::DPolygon &box); + + /** + * @brief Returns a string representation of the polygon graph. + */ + std::string to_string (); + + /** + * @brief Returns the bounding box of the polygon graph. + */ + db::DBox bbox () const; + + /** + * @brief Iterates the polygons in the graph (begin iterator) + */ + polygon_iterator begin () const { return mp_polygons.begin (); } + + /** + * @brief Iterates the polygons in the graph (end iterator) + */ + polygon_iterator end () const { return mp_polygons.end (); } + + /** + * @brief Returns the number of polygons in the graph + */ + size_t num_polygons () const { return mp_polygons.size (); } + + /** + * @brief Clears the polygon set + */ + void clear (); + +protected: +#if 0 // @@@ + /** + * @brief Checks the polygon graph for consistency + * This method is for testing purposes mainly. + */ + bool check (bool check_delaunay = true) const; +#endif + + /** + * @brief Dumps the polygon graph to a GDS file at the given path + * This method is for testing purposes mainly. + * + * "decompose_id" will map polygons to layer 20, 21 and 22. + * according to bit 0, 1 and 2 of the ID (useful with the 'mark_polygons' + * flat in TriangulateParameters). + */ + void dump (const std::string &path, bool decompose_by_id = false) const; + + /** + * @brief Creates a new layout object representing the polygon graph + * This method is for testing purposes mainly. + */ + db::Layout *to_layout (bool decompose_by_id = false) const; + +private: + tl::list mp_polygons; + tl::stable_vector m_edges_heap; + std::vector m_returned_edges; + tl::stable_vector m_vertex_heap; +// @@@ bool m_is_constrained; +// @@@ size_t m_level; + size_t m_id; +// @@@ size_t m_flips, m_hops; + + db::GVertex *create_vertex (double x, double y); + db::GVertex *create_vertex (const db::DPoint &pt); + db::GPolygonEdge *create_edge (db::GVertex *v1, db::GVertex *v2); + + template + db::GPolygon * + create_polygon (Iter from, Iter to) + { + db::GPolygon *res = new db::GPolygon (from ,to); + res->set_id (++m_id); + mp_polygons.push_back (res); + return res; + } + + void remove_polygon (db::GPolygon *tri); + template void make_contours (const Poly &poly, const Trans &trans, std::vector > &contours); +}; + +} + +#endif + diff --git a/src/db/unit_tests/dbPolygonGraphTests.cc b/src/db/unit_tests/dbPolygonGraphTests.cc new file mode 100644 index 000000000..9fdb91a2f --- /dev/null +++ b/src/db/unit_tests/dbPolygonGraphTests.cc @@ -0,0 +1,51 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "dbPolygonGraph.h" +#include "tlUnitTest.h" + +#include +#include + +class TestablePolygonGraph + : public db::PolygonGraph +{ +public: + using db::PolygonGraph::PolygonGraph; + // @@@ using db::PolygonGraph::check; + using db::PolygonGraph::dump; +}; + + +TEST(basic) +{ + db::DBox box (0, 0, 100.0, 200.0); + + TestablePolygonGraph pg; + pg.insert_polygon (db::DPolygon (box)); + + // @@@ + tl::info << pg.to_string (); + pg.dump ("debug.gds"); // @@@ +} + diff --git a/src/db/unit_tests/unit_tests.pro b/src/db/unit_tests/unit_tests.pro index ecb3c7ccb..047e46ea6 100644 --- a/src/db/unit_tests/unit_tests.pro +++ b/src/db/unit_tests/unit_tests.pro @@ -12,6 +12,7 @@ SOURCES = \ dbFillToolTests.cc \ dbLogTests.cc \ dbObjectWithPropertiesTests.cc \ + dbPolygonGraphTests.cc \ dbPolygonNeighborhoodTests.cc \ dbPropertiesFilterTests.cc \ dbQuadTreeTests.cc \ From 910f697d0b465d0f7cd94f9b946ec5ba0a21fda3 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 10 Apr 2025 19:15:12 +0200 Subject: [PATCH 02/58] WIP --- src/db/db/dbPolygonGraph.cc | 69 ++++++++++++++++++++---- src/db/db/dbPolygonGraph.h | 4 +- src/db/unit_tests/dbPolygonGraphTests.cc | 2 +- 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/db/db/dbPolygonGraph.cc b/src/db/db/dbPolygonGraph.cc index c7ae4a349..f745b0da2 100644 --- a/src/db/db/dbPolygonGraph.cc +++ b/src/db/db/dbPolygonGraph.cc @@ -348,8 +348,6 @@ GPolygon::init () mp_e.reserve (e.size ()); mp_e.push_back (e.front ()); - // NOTE: we assume the edges follow the clockwise orientation - mp_e.back ()->set_right (this); mp_v.reserve (e.size ()); mp_v.push_back (mp_e.back ()->v1 ()); @@ -365,8 +363,6 @@ GPolygon::init () tl_assert (i != v2e.end () && i->first == v && i->second != mp_e.back ()); v2e.erase (i); mp_e.push_back (i->second); - // NOTE: we assume the edges follow the clockwise orientation - mp_e.back ()->set_right (this); v = i->second->other (v); i = v2e.find (v); @@ -379,6 +375,33 @@ GPolygon::init () } } + + // establish clockwise order of the vertexes + + double area = 0.0; + const db::GVertex *vm1 = vertex (-1), *v0; + for (auto i = mp_v.begin (); i != mp_v.end (); ++i) { + v0 = *i; + area += db::vprod (*vm1 - db::DPoint (), *v0 - *vm1); + vm1 = v0; + } + + if (area > db::epsilon) { + std::reverse (mp_v.begin (), mp_v.end ()); + std::reverse (mp_e.begin (), mp_e.end ()); + } + + // link the polygon to the edges + + for (size_t i = 0; i < size (); ++i) { + db::GVertex *v = mp_v[i]; + db::GPolygonEdge *e = mp_e[i]; + if (e->v1 () == v) { + e->set_right (this); + } else { + e->set_left (this); + } + } } GPolygon::~GPolygon () @@ -602,9 +625,12 @@ PolygonGraph::remove_polygon (db::GPolygon *poly) } } +#if 0 // @@@ void -PolygonGraph::insert_polygon (const db::DPolygon &polygon) +PolygonGraph::convex_decompose (const db::DPolygon &polygon) { + clear (); + if (polygon.begin_edge ().at_end ()) { return; } @@ -612,23 +638,46 @@ PolygonGraph::insert_polygon (const db::DPolygon &polygon) std::vector edges; for (unsigned int c = 0; c < polygon.holes () + 1; ++c) { - const db::DPolygon::contour_type &ctr = polygon.contour (c); + + const db::DSimplePolygon::contour_type &ctr = polygon.contour (c); + db::GVertex *v0 = 0, *vv, *v; - for (auto p = ctr.begin (); p != ctr.end (); ++p) { - v = create_vertex ((*p).x (), (*p).y ()); + size_t n = ctr.size (); + for (size_t i = 0; i < n; ++i) { + + db::DPoint pm1 = ctr [i > 0 ? i - 1 : n - 1]; + db::DPoint pp1 = ctr [i + 1 < n ? i + 1 : 0]; + db::DPoint p = ctr [i]; + + bool is_convex = db::vprod_sign (p - pm1, pp1 - p); + // @@@ + + v = create_vertex (p.x (), p.y ()); if (! v0) { v0 = v; } else { edges.push_back (create_edge (vv, v)); } + vv = v; + } + if (v0 && v0 != v) { edges.push_back (create_edge (v, v0)); } - } - create_polygon (edges.begin (), edges.end ()); + } +} +#endif + +void +PolygonGraph::convex_decompose (const db::DPolygon &poly) +{ + + // @@@ + + } std::string diff --git a/src/db/db/dbPolygonGraph.h b/src/db/db/dbPolygonGraph.h index cfbdee450..d296b886f 100644 --- a/src/db/db/dbPolygonGraph.h +++ b/src/db/db/dbPolygonGraph.h @@ -684,9 +684,9 @@ public: ~PolygonGraph (); /** - * @brief Inserts the given polygon + * @brief Creates a convex decomposition for the given polygon */ - void insert_polygon (const db::DPolygon &box); + void convex_decompose (const DPolygon &poly); /** * @brief Returns a string representation of the polygon graph. diff --git a/src/db/unit_tests/dbPolygonGraphTests.cc b/src/db/unit_tests/dbPolygonGraphTests.cc index 9fdb91a2f..edf152942 100644 --- a/src/db/unit_tests/dbPolygonGraphTests.cc +++ b/src/db/unit_tests/dbPolygonGraphTests.cc @@ -42,7 +42,7 @@ TEST(basic) db::DBox box (0, 0, 100.0, 200.0); TestablePolygonGraph pg; - pg.insert_polygon (db::DPolygon (box)); + // @@@ pg.insert_polygon (db::DSimplePolygon (box)); // @@@ tl::info << pg.to_string (); From 6f9a2da04ad15084fb9341d85580cf02fba249e0 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 10 Apr 2025 23:33:50 +0200 Subject: [PATCH 03/58] WIP --- src/db/unit_tests/dbTrianglesTests.cc | 290 ++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) diff --git a/src/db/unit_tests/dbTrianglesTests.cc b/src/db/unit_tests/dbTrianglesTests.cc index df7c5e357..ba8875d3d 100644 --- a/src/db/unit_tests/dbTrianglesTests.cc +++ b/src/db/unit_tests/dbTrianglesTests.cc @@ -1076,3 +1076,293 @@ TEST(triangulate_with_vertexes) } } } + +// @@@@@@@@@@q + +// Hertel-Mehlhorn :) +TEST(JoinTriangles) +{ + db::Point contour[] = { + db::Point (0, 0), + db::Point (0, 100), + db::Point (1000, 100), + db::Point (1000, 500), + db::Point (1100, 500), + db::Point (1100, 100), + db::Point (2100, 100), + db::Point (2100, -1000), + db::Point (1050, -1000), + db::Point (1050, 0) + }; + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + double dbu = 0.001; + + db::Triangles::TriangulateParameters param; + param.min_b = 0.0; + + TestableTriangles tri; + db::CplxTrans trans = db::DCplxTrans (dbu) * db::CplxTrans (db::Trans (db::Point () - poly.box ().center ())); + trans = db::CplxTrans (dbu); // @@@ + tri.triangulate (poly, param, trans); + + // @@@ use edge "level" + // @@@ use edges from heap + std::unordered_set left; + + for (auto it = tri.begin (); it != tri.end (); ++it) { + for (unsigned int i = 0; i < 3; ++i) { + db::TriangleEdge *e = it->edge (i); + if (e->is_segment ()) { + left.insert (e); + } + } + } + + std::unordered_map > concave_corners; // @@@ + + while (! left.empty ()) { + + // First segment for a new loop + db::TriangleEdge *segment = *left.begin (); + + // walk along the segments in clockwise direction. Find concave + // vertexes and create new vertexes perpendicular to the incoming + // and outgoing edge. + + db::TriangleEdge *start_segment = segment; + + db::Vertex *vfrom = segment->v1 (); + db::Vertex *vto = segment->v2 (); + if (! segment->right ()) { + std::swap (vfrom, vto); + } + + do { + + left.erase (segment); + + double vp_max = 0.0; + int vp_max_sign = 0; + std::pair edges; + + db::TriangleEdge *prev_segment = segment; + segment = 0; + db::Vertex *vn = 0; + + // Look for the outgoing edge. We pick the one which bends "most", favoring + // convex corners. Multiple edges per vertex are possible is corner cases such as the + // "hourglass" configuration. + + for (auto e = vto->begin_edges (); e != vto->end_edges (); ++e) { + + db::TriangleEdge *en = *e; + if (en != prev_segment && en->is_segment ()) { + + tl_assert (left.find (en) != left.end () || en == start_segment); // @@@ + db::Vertex *v = en->other (vto); + db::DEdge e1 (*vfrom, *vto); + db::DEdge e2 (*vto, *v); + double vp = double (db::vprod (e1, e2)) / (e1.double_length () * e2.double_length ()); + + // vp > 0: concave, vp < 0: convex + + if (! segment || vp > vp_max) { + vp_max_sign = db::vprod_sign (e1, e2); + edges.first = e1; + edges.second = e2; + vp_max = vp; + segment = en; + vn = v; + } + + } + + } + + tl_assert (segment != 0); // @@@ + + if (vp_max_sign > 0) { + // concave corner + concave_corners.insert (std::make_pair (vto, edges)); + } + + vfrom = vto; + vto = vn; + + } while (segment != start_segment); + + } + + // @@@ sort convex vertexes + + std::vector > new_points; + + // Cut off pieces from convex corners by creating connections to points perpendicular + // to the incoming and outgoing edges + + for (auto cc = concave_corners.begin (); cc != concave_corners.end (); ++cc) { + + auto vtri = cc->first->triangles (); // @@@ slow? + + std::vector nvv, nvv_next; + + for (unsigned int ei = 0; ei < 2; ++ei) { + + db::DEdge ee = (ei == 0 ? cc->second.first : cc->second.second); + db::Vertex *v0 = cc->first; + + for (auto it = vtri.begin (); it != vtri.end (); ++it) { + + // Search for a segment in the direction perpendicular to the edge + nvv.clear (); + nvv.push_back (v0); + db::Triangle *t = *it; + + while (! nvv.empty ()) { + + nvv_next.clear (); + + for (auto iv = nvv.begin (); iv != nvv.end (); ++iv) { + + db::Vertex *v = *iv; + db::TriangleEdge *oe = t->opposite (v); + db::Triangle *tt = oe->other (t); + db::Vertex *v1 = oe->v1 (); + db::Vertex *v2 = oe->v2 (); + + if (db::vprod_sign (*v2 - *v, ee.d ()) >= 0 && db::vprod_sign (*v1 - *v, ee.d ()) >= 0 && + db::sprod_sign (*v2 - *v, ee.d ()) * db::sprod_sign (*v1 - *v, ee.d ()) < 0) { + + // this triangle covers the normal vector of e1 -> stop here or continue searching in that direction + if (oe->is_segment ()) { + auto cp = oe->edge ().cut_point (db::DEdge (*v0, *v0 + db::DVector (ee.dy (), -ee.dx ()))); + if (cp.first) { + new_points.push_back (std::make_pair (cp.second, v0)); + } + } else { + // continue searching in that direction + nvv_next.push_back (v1); + nvv_next.push_back (v2); + t = tt; + } + + break; + + } + + } + + nvv.swap (nvv_next); + + } + + } + + } + + } + + // Insert the new points and make connections + + std::unordered_set > clip_pairs; + + // @@@ TODO: what to do in case of equal new_points? + for (auto p = new_points.begin (); p != new_points.end (); ++p) { + auto v = tri.insert_point (p->first); + clip_pairs.insert (std::make_pair (v, p->second)); + } + + // Combine triangles, but don't cross clip edges + + db::Region result; + + std::unordered_set left_triangles; + for (auto it = tri.begin (); it != tri.end (); ++it) { + left_triangles.insert (it.operator-> ()); + } + + while (! left_triangles.empty ()) { + + std::unordered_map edges; + + const db::Triangle *tri = *left_triangles.begin (); + std::vector queue, next_queue; + queue.push_back (tri); + + while (! queue.empty ()) { + + next_queue.clear (); + + for (auto q = queue.begin (); q != queue.end (); ++q) { + + left_triangles.erase (*q); + + for (unsigned int i = 0; i < 3; ++i) { + + const db::TriangleEdge *e = (*q)->edge (i); + const db::Triangle *qq = e->other (*q); + + bool is_outer_edge = false; + if (! qq) { + is_outer_edge = true; + } else if (clip_pairs.find (std::make_pair (e->v1 (), e->v2 ())) != clip_pairs.end () || clip_pairs.find (std::make_pair (e->v2 (), e->v1 ())) != clip_pairs.end ()) { + is_outer_edge = true; + } else if (concave_corners.find (e->v1 ()) != concave_corners.end () && concave_corners.find (e->v2 ()) != concave_corners.end ()) { + is_outer_edge = true; + } + + if (is_outer_edge) { + if (e->right () == *q) { + edges.insert (std::make_pair (e->v1 (), e->v2 ())); + } else { + edges.insert (std::make_pair (e->v2 (), e->v1 ())); + } + } else if (left_triangles.find (qq) != left_triangles.end ()) { + next_queue.push_back (qq); + } + } + + } + + queue.swap (next_queue); + + } + + // stitch the loop points into a polygon + + tl_assert (! edges.empty ()); + + const db::Vertex *v = edges.begin ()->first; + const db::Vertex *v0 = v; + const db::Vertex *vv = edges.begin ()->second; + + std::vector polygon_points; + + do { + + polygon_points.push_back (*v); + + auto i = edges.find (vv); + tl_assert (i != edges.end ()); + + v = i->first; + vv = i->second; + + } while (v != v0); + + db::DPolygon poly; + poly.assign_hull (polygon_points.begin (), polygon_points.end ()); + result.insert (trans.inverted () * poly); + + } + + // @@@ + // tri.dump ("debug.gds"); + result.write ("debug.gds"); + +} + +// @@@@@@@@@@q From 7b069d17c3371905a15aa3d37131b10148fe38b8 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 12 Apr 2025 22:30:00 +0200 Subject: [PATCH 04/58] WIP --- src/db/unit_tests/dbTrianglesTests.cc | 348 ++++++++++++++++++-------- 1 file changed, 239 insertions(+), 109 deletions(-) diff --git a/src/db/unit_tests/dbTrianglesTests.cc b/src/db/unit_tests/dbTrianglesTests.cc index ba8875d3d..fe09c3817 100644 --- a/src/db/unit_tests/dbTrianglesTests.cc +++ b/src/db/unit_tests/dbTrianglesTests.cc @@ -1077,11 +1077,147 @@ TEST(triangulate_with_vertexes) } } -// @@@@@@@@@@q +// @@@@@@@@@@@ + +struct SortAngleAndEdgesByEdgeLength +{ + typedef std::list > angle_and_edges_list; + + bool operator() (const angle_and_edges_list::iterator &a, const angle_and_edges_list::iterator &b) const + { + double la = a->second->edge ().double_sq_length (); + double lb = b->second->edge ().double_sq_length (); + if (fabs (la - lb) > db::epsilon) { + return la < lb; + } else { + return a->second->edge ().less (b->second->edge ()); + } + } +}; + +struct ConcaveCorner +{ + ConcaveCorner () + : corner (0), incoming (0), outgoing (0) + { + // .. nothing yet .. + } + + ConcaveCorner (db::Vertex *_corner, db::TriangleEdge *_incoming, db::TriangleEdge *_outgoing) + : corner (_corner), incoming (_incoming), outgoing (_outgoing) + { + // .. nothing yet .. + } + + db::Vertex *corner; + db::TriangleEdge *incoming, *outgoing; +}; + +db::TriangleEdge *find_outgoing_segment (db::Vertex *vertex, db::TriangleEdge *incoming, bool &is_concave) +{ + db::Vertex *vfrom = incoming->other (vertex); + db::DEdge e1 (*vfrom, *vertex); + + double vp_max = 0.0; + int vp_max_sign = 0; + db::TriangleEdge *outgoing = 0; + + // Look for the outgoing edge. We pick the one which bends "most", favoring + // convex corners. Multiple edges per vertex are possible is corner cases such as the + // "hourglass" configuration. + + for (auto e = vertex->begin_edges (); e != vertex->end_edges (); ++e) { + + db::TriangleEdge *en = *e; + if (en != incoming && en->is_segment ()) { + + db::Vertex *v = en->other (vertex); + db::DEdge e2 (*vertex, *v); + double vp = double (db::vprod (e1, e2)) / (e1.double_length () * e2.double_length ()); + + // vp > 0: concave, vp < 0: convex + + if (! outgoing || vp > vp_max) { + vp_max_sign = db::vprod_sign (e1, e2); + vp_max = vp; + outgoing = en; + } + + } + + } + + is_concave = (vp_max_sign > 0); + + tl_assert (outgoing != 0); + return outgoing; +} + +void collect_concave_vertexes (db::Triangles &tris, std::vector &concave_vertexes) +{ + concave_vertexes.clear (); + + // @@@ use edge "level" + // @@@ use edges from heap + std::unordered_set left; + + for (auto it = tris.begin (); it != tris.end (); ++it) { + for (unsigned int i = 0; i < 3; ++i) { + db::TriangleEdge *e = it->edge (i); + if (e->is_segment ()) { + left.insert (e); + } + } + } + + while (! left.empty ()) { + + // First segment for a new loop + db::TriangleEdge *segment = *left.begin (); + + // walk along the segments in clockwise direction. Find concave + // vertexes and create new vertexes perpendicular to the incoming + // and outgoing edge. + + db::TriangleEdge *start_segment = segment; + db::Vertex *vto = segment->right () ? segment->v2 () : segment->v1 (); + + do { + + left.erase (segment); + + db::TriangleEdge *prev_segment = segment; + + bool is_concave = false; + segment = find_outgoing_segment (vto, prev_segment, is_concave); + + if (is_concave) { + concave_vertexes.push_back (ConcaveCorner (vto, prev_segment, segment)); + } + + vto = segment->other (vto); + + } while (segment != start_segment); + + } +} // Hertel-Mehlhorn :) TEST(JoinTriangles) { +#if 1 + db::Point contour[] = { + db::Point (0, 0), + db::Point (0, 100), + db::Point (1000, 100), + db::Point (1000, 500), + db::Point (1100, 500), + db::Point (1100, 100), + db::Point (2100, 100), + db::Point (2100, 0) + }; +#else + db::Point contour[] = { db::Point (0, 0), db::Point (0, 100), @@ -1094,10 +1230,13 @@ TEST(JoinTriangles) db::Point (1050, -1000), db::Point (1050, 0) }; +#endif db::Polygon poly; poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + // @@@ don't to anything if already convex + double dbu = 0.001; db::Triangles::TriangulateParameters param; @@ -1108,111 +1247,33 @@ TEST(JoinTriangles) trans = db::CplxTrans (dbu); // @@@ tri.triangulate (poly, param, trans); - // @@@ use edge "level" - // @@@ use edges from heap - std::unordered_set left; + std::vector concave_vertexes; + collect_concave_vertexes (tri, concave_vertexes); - for (auto it = tri.begin (); it != tri.end (); ++it) { - for (unsigned int i = 0; i < 3; ++i) { - db::TriangleEdge *e = it->edge (i); - if (e->is_segment ()) { - left.insert (e); - } - } - } - - std::unordered_map > concave_corners; // @@@ - - while (! left.empty ()) { - - // First segment for a new loop - db::TriangleEdge *segment = *left.begin (); - - // walk along the segments in clockwise direction. Find concave - // vertexes and create new vertexes perpendicular to the incoming - // and outgoing edge. - - db::TriangleEdge *start_segment = segment; - - db::Vertex *vfrom = segment->v1 (); - db::Vertex *vto = segment->v2 (); - if (! segment->right ()) { - std::swap (vfrom, vto); - } - - do { - - left.erase (segment); - - double vp_max = 0.0; - int vp_max_sign = 0; - std::pair edges; - - db::TriangleEdge *prev_segment = segment; - segment = 0; - db::Vertex *vn = 0; - - // Look for the outgoing edge. We pick the one which bends "most", favoring - // convex corners. Multiple edges per vertex are possible is corner cases such as the - // "hourglass" configuration. - - for (auto e = vto->begin_edges (); e != vto->end_edges (); ++e) { - - db::TriangleEdge *en = *e; - if (en != prev_segment && en->is_segment ()) { - - tl_assert (left.find (en) != left.end () || en == start_segment); // @@@ - db::Vertex *v = en->other (vto); - db::DEdge e1 (*vfrom, *vto); - db::DEdge e2 (*vto, *v); - double vp = double (db::vprod (e1, e2)) / (e1.double_length () * e2.double_length ()); - - // vp > 0: concave, vp < 0: convex - - if (! segment || vp > vp_max) { - vp_max_sign = db::vprod_sign (e1, e2); - edges.first = e1; - edges.second = e2; - vp_max = vp; - segment = en; - vn = v; - } - - } - - } - - tl_assert (segment != 0); // @@@ - - if (vp_max_sign > 0) { - // concave corner - concave_corners.insert (std::make_pair (vto, edges)); - } - - vfrom = vto; - vto = vn; - - } while (segment != start_segment); - - } + // @@@ return if no concave corners // @@@ sort convex vertexes - std::vector > new_points; + std::vector new_points; // Cut off pieces from convex corners by creating connections to points perpendicular // to the incoming and outgoing edges - for (auto cc = concave_corners.begin (); cc != concave_corners.end (); ++cc) { + for (auto cc = concave_vertexes.begin (); cc != concave_vertexes.end (); ++cc) { - auto vtri = cc->first->triangles (); // @@@ slow? + auto vtri = cc->corner->triangles (); // @@@ slow? std::vector nvv, nvv_next; for (unsigned int ei = 0; ei < 2; ++ei) { - db::DEdge ee = (ei == 0 ? cc->second.first : cc->second.second); - db::Vertex *v0 = cc->first; + db::DEdge ee; + db::Vertex *v0 = cc->corner; + if (ei == 0) { + ee = db::DEdge (*cc->incoming->other (v0), *v0); + } else { + ee = db::DEdge (*v0, *cc->outgoing->other (v0)); + } for (auto it = vtri.begin (); it != vtri.end (); ++it) { @@ -1240,7 +1301,7 @@ TEST(JoinTriangles) if (oe->is_segment ()) { auto cp = oe->edge ().cut_point (db::DEdge (*v0, *v0 + db::DVector (ee.dy (), -ee.dx ()))); if (cp.first) { - new_points.push_back (std::make_pair (cp.second, v0)); + new_points.push_back (cp.second); } } else { // continue searching in that direction @@ -1267,15 +1328,93 @@ TEST(JoinTriangles) // Insert the new points and make connections - std::unordered_set > clip_pairs; - // @@@ TODO: what to do in case of equal new_points? + // -> sort, remove duplicates for (auto p = new_points.begin (); p != new_points.end (); ++p) { - auto v = tri.insert_point (p->first); - clip_pairs.insert (std::make_pair (v, p->second)); + tri.insert_point (*p); } - // Combine triangles, but don't cross clip edges + if (! new_points.empty ()) { + collect_concave_vertexes (tri, concave_vertexes); + } + + // Collect essential edges + // Every concave vertex can have up to two essential edges. + // Other then suggested by Hertel-Mehlhorn we don't pick + // them one-by-one, but using them in length order, from the + + std::unordered_set essential_edges; + + typedef std::list > angles_and_edges_list; + angles_and_edges_list angles_and_edges; + std::vector sorted_edges; + + for (auto cc = concave_vertexes.begin (); cc != concave_vertexes.end (); ++cc) { + + std::cout << "@@@ cc=" << cc->corner->to_string () << ", in: " << cc->incoming->to_string () << ", out: " << cc->outgoing->to_string () << std::endl; // @@@ + + angles_and_edges.clear (); + db::Vertex *v0 = cc->corner; + + db::TriangleEdge *e = cc->incoming; + while (e) { + + db::Triangle *t = e->v2 () == v0 ? e->right () : e->left (); + tl_assert (t != 0); + + // @@@ make a method of triangle + db::TriangleEdge *en = 0; + for (unsigned int i = 0; i < 3; ++i) { + db::TriangleEdge *ee = t->edge (i); + if (ee != e && (ee->v1 () == v0 || ee->v2 () == v0)) { + en = ee; + break; + } + } + + db::DVector v1 = e->edge ().d () * (e->v1 () == v0 ? 1.0 : -1.0); + db::DVector v2 = en->edge ().d () * (en->v1 () == v0 ? 1.0 : -1.0); + + double angle = atan2 (db::vprod (v1, v2), db::sprod (v1, v2)); + + e = (en == cc->outgoing) ? 0 : en; + angles_and_edges.push_back (std::make_pair (angle, e)); + std::cout << "@@@ [a,e] =" << angle << "," << (e ? e->to_string () : std::string ("ENDL")) << std::endl; // @@@ + + } + + sorted_edges.clear (); + + for (auto i = angles_and_edges.begin (); i != angles_and_edges.end (); ++i) { + if (i->second) { + sorted_edges.push_back (i); + } + } + + std::sort (sorted_edges.begin (), sorted_edges.end (), SortAngleAndEdgesByEdgeLength ()); + + for (auto i = sorted_edges.end (); i != sorted_edges.begin (); ) { + --i; + angles_and_edges_list::iterator ii = *i; + angles_and_edges_list::iterator iin = ii; + ++iin; + std::cout << "@@@ checking [a,e] =" << ii->first << "," << iin->first << "," << ii->second->to_string () << std::endl; // @@@ + if (ii->first + iin->first < M_PI - db::epsilon) { + printf("@@@ %.12g, %.12g\n", ii->first + iin->first, M_PI); // @@@ + // not an essential edge -> remove + iin->first += ii->first; + angles_and_edges.erase (ii); + std::cout << "@@@ -> not essential" << std::endl; // @@@ + } + } + + for (auto i = angles_and_edges.begin (); i != angles_and_edges.end (); ++i) { + essential_edges.insert (i->second); + } + + } + + // Combine triangles, but don't cross essential edges db::Region result; @@ -1305,16 +1444,7 @@ TEST(JoinTriangles) const db::TriangleEdge *e = (*q)->edge (i); const db::Triangle *qq = e->other (*q); - bool is_outer_edge = false; - if (! qq) { - is_outer_edge = true; - } else if (clip_pairs.find (std::make_pair (e->v1 (), e->v2 ())) != clip_pairs.end () || clip_pairs.find (std::make_pair (e->v2 (), e->v1 ())) != clip_pairs.end ()) { - is_outer_edge = true; - } else if (concave_corners.find (e->v1 ()) != concave_corners.end () && concave_corners.find (e->v2 ()) != concave_corners.end ()) { - is_outer_edge = true; - } - - if (is_outer_edge) { + if (! qq || essential_edges.find (e) != essential_edges.end ()) { if (e->right () == *q) { edges.insert (std::make_pair (e->v1 (), e->v2 ())); } else { @@ -1360,7 +1490,7 @@ TEST(JoinTriangles) } // @@@ - // tri.dump ("debug.gds"); + tri.dump ("debugt.gds"); result.write ("debug.gds"); } From d7193e972cbf33d384610626e8bfe535c6898c7b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 12 Apr 2025 23:59:43 +0200 Subject: [PATCH 05/58] WIP --- src/db/db/dbTriangles.h | 19 +- src/db/unit_tests/dbTrianglesTests.cc | 296 +++++++++++++++----------- 2 files changed, 179 insertions(+), 136 deletions(-) diff --git a/src/db/db/dbTriangles.h b/src/db/db/dbTriangles.h index 31da3f48b..3bb34c87d 100644 --- a/src/db/db/dbTriangles.h +++ b/src/db/db/dbTriangles.h @@ -170,6 +170,17 @@ public: void triangulate (const db::DPolygon &poly, const TriangulateParameters ¶meters, const db::DCplxTrans &trans = db::DCplxTrans ()); void triangulate (const db::DPolygon &poly, const std::vector &vertexes, const TriangulateParameters ¶meters, const db::DCplxTrans &trans = db::DCplxTrans ()); + /** + * @brief Inserts a new vertex as the given point + * + * If "new_triangles" is not null, it will receive the list of new triangles created during + * the remove step. + * + * This method can be called after "triangulate" to add new points and adjust the triangulation. + * Inserting new points will maintain the (constrained) Delaunay condition. + */ + db::Vertex *insert_point (const db::DPoint &point, std::list > *new_triangles = 0); + /** * @brief Statistics: number of flips (fixing) */ @@ -214,14 +225,6 @@ protected: */ std::vector find_points_around (Vertex *vertex, double radius); - /** - * @brief Inserts a new vertex as the given point - * - * If "new_triangles" is not null, it will receive the list of new triangles created during - * the remove step. - */ - db::Vertex *insert_point (const db::DPoint &point, std::list > *new_triangles = 0); - /** * @brief Inserts a new vertex as the given point * diff --git a/src/db/unit_tests/dbTrianglesTests.cc b/src/db/unit_tests/dbTrianglesTests.cc index fe09c3817..138248694 100644 --- a/src/db/unit_tests/dbTrianglesTests.cc +++ b/src/db/unit_tests/dbTrianglesTests.cc @@ -1081,7 +1081,7 @@ TEST(triangulate_with_vertexes) struct SortAngleAndEdgesByEdgeLength { - typedef std::list > angle_and_edges_list; + typedef std::list > angle_and_edges_list; bool operator() (const angle_and_edges_list::iterator &a, const angle_and_edges_list::iterator &b) const { @@ -1095,6 +1095,26 @@ struct SortAngleAndEdgesByEdgeLength } }; +// TODO: move to some generic header +template +struct less_compare_func +{ + bool operator() (const T &a, const T &b) const + { + return a.less (b); + } +}; + +// TODO: move to some generic header +template +struct equal_compare_func +{ + bool operator() (const T &a, const T &b) const + { + return a.equal (b); + } +}; + struct ConcaveCorner { ConcaveCorner () @@ -1113,13 +1133,13 @@ struct ConcaveCorner db::TriangleEdge *incoming, *outgoing; }; -db::TriangleEdge *find_outgoing_segment (db::Vertex *vertex, db::TriangleEdge *incoming, bool &is_concave) +db::TriangleEdge *find_outgoing_segment (db::Vertex *vertex, db::TriangleEdge *incoming, int &vp_max_sign) { db::Vertex *vfrom = incoming->other (vertex); db::DEdge e1 (*vfrom, *vertex); double vp_max = 0.0; - int vp_max_sign = 0; + vp_max_sign = 0; db::TriangleEdge *outgoing = 0; // Look for the outgoing edge. We pick the one which bends "most", favoring @@ -1147,8 +1167,6 @@ db::TriangleEdge *find_outgoing_segment (db::Vertex *vertex, db::TriangleEdge *i } - is_concave = (vp_max_sign > 0); - tl_assert (outgoing != 0); return outgoing; } @@ -1188,10 +1206,10 @@ void collect_concave_vertexes (db::Triangles &tris, std::vector & db::TriangleEdge *prev_segment = segment; - bool is_concave = false; - segment = find_outgoing_segment (vto, prev_segment, is_concave); + int vp_sign = 0; + segment = find_outgoing_segment (vto, prev_segment, vp_sign); - if (is_concave) { + if (vp_sign > 0) { concave_vertexes.push_back (ConcaveCorner (vto, prev_segment, segment)); } @@ -1202,122 +1220,94 @@ void collect_concave_vertexes (db::Triangles &tris, std::vector & } } -// Hertel-Mehlhorn :) -TEST(JoinTriangles) +std::pair +search_crossing_with_next_segment (const db::Vertex *v0, const db::DVector &direction) { -#if 1 - db::Point contour[] = { - db::Point (0, 0), - db::Point (0, 100), - db::Point (1000, 100), - db::Point (1000, 500), - db::Point (1100, 500), - db::Point (1100, 100), - db::Point (2100, 100), - db::Point (2100, 0) - }; -#else + auto vtri = v0->triangles (); // TODO: slow? + std::vector nvv, nvv_next; - db::Point contour[] = { - db::Point (0, 0), - db::Point (0, 100), - db::Point (1000, 100), - db::Point (1000, 500), - db::Point (1100, 500), - db::Point (1100, 100), - db::Point (2100, 100), - db::Point (2100, -1000), - db::Point (1050, -1000), - db::Point (1050, 0) - }; -#endif + for (auto it = vtri.begin (); it != vtri.end (); ++it) { - db::Polygon poly; - poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + // Search for a segment in the direction perpendicular to the edge + nvv.clear (); + nvv.push_back (v0); + const db::Triangle *t = *it; - // @@@ don't to anything if already convex + while (! nvv.empty ()) { - double dbu = 0.001; + nvv_next.clear (); - db::Triangles::TriangulateParameters param; - param.min_b = 0.0; + for (auto iv = nvv.begin (); iv != nvv.end (); ++iv) { - TestableTriangles tri; - db::CplxTrans trans = db::DCplxTrans (dbu) * db::CplxTrans (db::Trans (db::Point () - poly.box ().center ())); - trans = db::CplxTrans (dbu); // @@@ - tri.triangulate (poly, param, trans); + const db::Vertex *v = *iv; + const db::TriangleEdge *oe = t->opposite (v); + const db::Triangle *tt = oe->other (t); + const db::Vertex *v1 = oe->v1 (); + const db::Vertex *v2 = oe->v2 (); + if (db::sprod_sign (*v2 - *v, direction) >= 0 && db::sprod_sign (*v1 - *v, direction) >= 0 && + db::vprod_sign (*v2 - *v, direction) * db::vprod_sign (*v1 - *v, direction) < 0) { + + // this triangle covers the normal vector of e1 -> stop here or continue searching in that direction + if (oe->is_segment ()) { + auto cp = oe->edge ().cut_point (db::DEdge (*v0, *v0 + direction)); + if (cp.first) { + return std::make_pair (true, cp.second); + } + } else { + // continue searching in that direction + nvv_next.push_back (v1); + nvv_next.push_back (v2); + t = tt; + } + + break; + + } + + } + + nvv.swap (nvv_next); + + } + + } + + return std::make_pair (false, db::DPoint ()); +} + +void +hertel_mehlhorn_decomposition (db::Triangles &tri, bool diagonals_only, bool no_collinear_edges, std::list &polygons) +{ std::vector concave_vertexes; collect_concave_vertexes (tri, concave_vertexes); // @@@ return if no concave corners - // @@@ sort convex vertexes + // @@@ sort concave vertexes std::vector new_points; - // Cut off pieces from convex corners by creating connections to points perpendicular - // to the incoming and outgoing edges + if (! diagonals_only) { - for (auto cc = concave_vertexes.begin (); cc != concave_vertexes.end (); ++cc) { + // Create internal segments cutting off pieces orthogonal to the edges + // connecting the concave vertex. - auto vtri = cc->corner->triangles (); // @@@ slow? + for (auto cc = concave_vertexes.begin (); cc != concave_vertexes.end (); ++cc) { - std::vector nvv, nvv_next; + for (unsigned int ei = 0; ei < 2; ++ei) { - for (unsigned int ei = 0; ei < 2; ++ei) { - - db::DEdge ee; - db::Vertex *v0 = cc->corner; - if (ei == 0) { - ee = db::DEdge (*cc->incoming->other (v0), *v0); - } else { - ee = db::DEdge (*v0, *cc->outgoing->other (v0)); - } - - for (auto it = vtri.begin (); it != vtri.end (); ++it) { - - // Search for a segment in the direction perpendicular to the edge - nvv.clear (); - nvv.push_back (v0); - db::Triangle *t = *it; - - while (! nvv.empty ()) { - - nvv_next.clear (); - - for (auto iv = nvv.begin (); iv != nvv.end (); ++iv) { - - db::Vertex *v = *iv; - db::TriangleEdge *oe = t->opposite (v); - db::Triangle *tt = oe->other (t); - db::Vertex *v1 = oe->v1 (); - db::Vertex *v2 = oe->v2 (); - - if (db::vprod_sign (*v2 - *v, ee.d ()) >= 0 && db::vprod_sign (*v1 - *v, ee.d ()) >= 0 && - db::sprod_sign (*v2 - *v, ee.d ()) * db::sprod_sign (*v1 - *v, ee.d ()) < 0) { - - // this triangle covers the normal vector of e1 -> stop here or continue searching in that direction - if (oe->is_segment ()) { - auto cp = oe->edge ().cut_point (db::DEdge (*v0, *v0 + db::DVector (ee.dy (), -ee.dx ()))); - if (cp.first) { - new_points.push_back (cp.second); - } - } else { - // continue searching in that direction - nvv_next.push_back (v1); - nvv_next.push_back (v2); - t = tt; - } - - break; - - } - - } - - nvv.swap (nvv_next); + db::DEdge ee; + const db::Vertex *v0 = cc->corner; + if (ei == 0) { + ee = db::DEdge (*cc->incoming->other (v0), *v0); + } else { + ee = db::DEdge (*v0, *cc->outgoing->other (v0)); + } + auto cp = search_crossing_with_next_segment (v0, db::DVector (ee.dy (), -ee.dx ())); + if (cp.first) { + new_points.push_back (cp.second); } } @@ -1326,16 +1316,21 @@ TEST(JoinTriangles) } - // Insert the new points and make connections - - // @@@ TODO: what to do in case of equal new_points? - // -> sort, remove duplicates - for (auto p = new_points.begin (); p != new_points.end (); ++p) { - tri.insert_point (*p); - } + // eliminate duplicates and put the new points in some order if (! new_points.empty ()) { + + std::sort (new_points.begin (), new_points.end (), less_compare_func ()); + new_points.erase (std::unique (new_points.begin (), new_points.end (), equal_compare_func ()), new_points.end ()); + + // Insert the new points and make connections + for (auto p = new_points.begin (); p != new_points.end (); ++p) { + tri.insert_point (*p); + } + + // As the insertion invalidates the edges, we need to collect the concave vertexes again collect_concave_vertexes (tri, concave_vertexes); + } // Collect essential edges @@ -1345,27 +1340,25 @@ TEST(JoinTriangles) std::unordered_set essential_edges; - typedef std::list > angles_and_edges_list; + typedef std::list > angles_and_edges_list; angles_and_edges_list angles_and_edges; std::vector sorted_edges; for (auto cc = concave_vertexes.begin (); cc != concave_vertexes.end (); ++cc) { - std::cout << "@@@ cc=" << cc->corner->to_string () << ", in: " << cc->incoming->to_string () << ", out: " << cc->outgoing->to_string () << std::endl; // @@@ - angles_and_edges.clear (); - db::Vertex *v0 = cc->corner; + const db::Vertex *v0 = cc->corner; - db::TriangleEdge *e = cc->incoming; + const db::TriangleEdge *e = cc->incoming; while (e) { - db::Triangle *t = e->v2 () == v0 ? e->right () : e->left (); + const db::Triangle *t = e->v2 () == v0 ? e->right () : e->left (); tl_assert (t != 0); // @@@ make a method of triangle - db::TriangleEdge *en = 0; + const db::TriangleEdge *en = 0; for (unsigned int i = 0; i < 3; ++i) { - db::TriangleEdge *ee = t->edge (i); + const db::TriangleEdge *ee = t->edge (i); if (ee != e && (ee->v1 () == v0 || ee->v2 () == v0)) { en = ee; break; @@ -1379,7 +1372,6 @@ TEST(JoinTriangles) e = (en == cc->outgoing) ? 0 : en; angles_and_edges.push_back (std::make_pair (angle, e)); - std::cout << "@@@ [a,e] =" << angle << "," << (e ? e->to_string () : std::string ("ENDL")) << std::endl; // @@@ } @@ -1398,13 +1390,10 @@ TEST(JoinTriangles) angles_and_edges_list::iterator ii = *i; angles_and_edges_list::iterator iin = ii; ++iin; - std::cout << "@@@ checking [a,e] =" << ii->first << "," << iin->first << "," << ii->second->to_string () << std::endl; // @@@ - if (ii->first + iin->first < M_PI - db::epsilon) { - printf("@@@ %.12g, %.12g\n", ii->first + iin->first, M_PI); // @@@ + if (ii->first + iin->first < (no_collinear_edges ? M_PI - db::epsilon : M_PI + db::epsilon)) { // not an essential edge -> remove iin->first += ii->first; angles_and_edges.erase (ii); - std::cout << "@@@ -> not essential" << std::endl; // @@@ } } @@ -1416,8 +1405,6 @@ TEST(JoinTriangles) // Combine triangles, but don't cross essential edges - db::Region result; - std::unordered_set left_triangles; for (auto it = tri.begin (); it != tri.end (); ++it) { left_triangles.insert (it.operator-> ()); @@ -1483,11 +1470,64 @@ TEST(JoinTriangles) } while (v != v0); - db::DPolygon poly; - poly.assign_hull (polygon_points.begin (), polygon_points.end ()); - result.insert (trans.inverted () * poly); + polygons.push_back (db::DPolygon ()); + polygons.back ().assign_hull (polygon_points.begin (), polygon_points.end ()); } +} + + +// Hertel-Mehlhorn :) +TEST(JoinTriangles) +{ +#if 0 + db::Point contour[] = { + db::Point (0, 0), + db::Point (0, 100), + db::Point (1000, 100), + db::Point (1000, 500), + db::Point (1100, 500), + db::Point (1100, 100), + db::Point (2100, 100), + db::Point (2100, 0) + }; +#else + + db::Point contour[] = { + db::Point (0, 0), + db::Point (0, 100), + db::Point (1000, 100), + db::Point (1000, 500), + db::Point (1100, 500), + db::Point (1100, 100), + db::Point (2100, 100), + db::Point (2100, -1000), + db::Point (150, -1000), + db::Point (150, 0) + }; +#endif + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + // @@@ don't to anything if already convex + + double dbu = 0.001; + + db::Triangles::TriangulateParameters param; + param.min_b = 0.0; + + TestableTriangles tri; + db::CplxTrans trans = db::CplxTrans (dbu); + tri.triangulate (poly, param, trans); + + std::list polygons; + hertel_mehlhorn_decomposition (tri, false, true, polygons); + + db::Region result; + for (auto p = polygons.begin (); p != polygons.end (); ++p) { + result.insert (trans.inverted () * *p); + } // @@@ tri.dump ("debugt.gds"); From 627e2444435fd93317ac53f79ad273ab38486ca2 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 13 Apr 2025 22:44:14 +0200 Subject: [PATCH 06/58] Refactoring: generalization of Triangles into db::plc::Graph in dbPLC.h --- src/db/db/db.pro | 6 +- src/db/db/{dbPolygonGraph.cc => dbPLC.cc} | 458 +++-- src/db/db/{dbPolygonGraph.h => dbPLC.h} | 447 +++-- src/db/db/dbPLCTriangulation.cc | 1649 +++++++++++++++++ src/db/db/dbPLCTriangulation.h | 306 +++ ...PolygonGraphTests.cc => dbPLCGraphTest.cc} | 18 +- src/db/unit_tests/dbPLCTriangulationTests.cc | 1117 +++++++++++ src/db/unit_tests/dbTrianglesTests.cc | 2 +- src/db/unit_tests/unit_tests.pro | 3 +- 9 files changed, 3602 insertions(+), 404 deletions(-) rename src/db/db/{dbPolygonGraph.cc => dbPLC.cc} (68%) rename src/db/db/{dbPolygonGraph.h => dbPLC.h} (64%) create mode 100644 src/db/db/dbPLCTriangulation.cc create mode 100644 src/db/db/dbPLCTriangulation.h rename src/db/unit_tests/{dbPolygonGraphTests.cc => dbPLCGraphTest.cc} (76%) create mode 100644 src/db/unit_tests/dbPLCTriangulationTests.cc diff --git a/src/db/db/db.pro b/src/db/db/db.pro index 732d42aa1..888a22dcc 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -70,13 +70,14 @@ SOURCES = \ dbNetlistSpiceReaderExpressionParser.cc \ dbObject.cc \ dbObjectWithProperties.cc \ + dbPLC.cc \ + dbPLCTriangulation.cc \ dbPath.cc \ dbPCellDeclaration.cc \ dbPCellHeader.cc \ dbPCellVariant.cc \ dbPoint.cc \ dbPolygon.cc \ - dbPolygonGraph.cc \ dbPolygonNeighborhood.cc \ dbPolygonTools.cc \ dbPolygonGenerators.cc \ @@ -309,13 +310,14 @@ HEADERS = \ dbObject.h \ dbObjectTag.h \ dbObjectWithProperties.h \ + dbPLC.h \ + dbPLCTriangulation.h \ dbPath.h \ dbPCellDeclaration.h \ dbPCellHeader.h \ dbPCellVariant.h \ dbPoint.h \ dbPolygon.h \ - dbPolygonGraph.h \ dbPolygonNeighborhood.h \ dbPolygonTools.h \ dbPolygonGenerators.h \ diff --git a/src/db/db/dbPolygonGraph.cc b/src/db/db/dbPLC.cc similarity index 68% rename from src/db/db/dbPolygonGraph.cc rename to src/db/db/dbPLC.cc index f745b0da2..559bf4572 100644 --- a/src/db/db/dbPolygonGraph.cc +++ b/src/db/db/dbPLC.cc @@ -21,7 +21,7 @@ */ -#include "dbPolygonGraph.h" +#include "dbPLC.h" #include "dbLayout.h" #include "dbWriter.h" #include "tlStream.h" @@ -36,28 +36,43 @@ namespace db { +namespace plc +{ + // ------------------------------------------------------------------------------------- -// GVertex implementation +// Vertex implementation -GVertex::GVertex () - : DPoint (), m_is_precious (false) +Vertex::Vertex (Graph *graph) + : DPoint (), mp_graph (graph), m_is_precious (false) { // .. nothing yet .. } -GVertex::GVertex (const db::DPoint &p) - : DPoint (p), m_is_precious (false) +Vertex::Vertex (Graph *graph, const db::DPoint &p) + : DPoint (p), mp_graph (graph), m_is_precious (false) { // .. nothing yet .. } -GVertex::GVertex (const GVertex &v) - : DPoint (), m_is_precious (false) +Vertex::Vertex (Graph *graph, const Vertex &v) + : DPoint (), mp_graph (graph), m_is_precious (false) { operator= (v); } -GVertex &GVertex::operator= (const GVertex &v) +Vertex::Vertex (Graph *graph, db::DCoord x, db::DCoord y) + : DPoint (x, y), mp_graph (graph), m_is_precious (false) +{ + // .. nothing yet .. +} + +Vertex::Vertex (const Vertex &v) + : DPoint (v), mp_graph (v.mp_graph), m_is_precious (v.m_is_precious) +{ + // NOTE: edges are not copied! +} + +Vertex &Vertex::operator= (const Vertex &v) { if (this != &v) { // NOTE: edges are not copied! @@ -67,15 +82,8 @@ GVertex &GVertex::operator= (const GVertex &v) return *this; } -GVertex::GVertex (db::DCoord x, db::DCoord y) - : DPoint (x, y), m_is_precious (false) -{ - // .. nothing yet .. -} - -#if 0 // @@@ bool -GVertex::is_outside () const +Vertex::is_outside () const { for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) { if ((*e)->is_outside ()) { @@ -84,13 +92,12 @@ GVertex::is_outside () const } return false; } -#endif -std::vector -GVertex::polygons () const +std::vector +Vertex::polygons () const { - std::set seen; - std::vector res; + std::set seen; + std::vector res; for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) { for (auto t = (*e)->begin_polygons (); t != (*e)->end_polygons (); ++t) { if (seen.insert (t.operator-> ()).second) { @@ -102,7 +109,7 @@ GVertex::polygons () const } bool -GVertex::has_edge (const GPolygonEdge *edge) const +Vertex::has_edge (const Edge *edge) const { for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) { if (*e == edge) { @@ -113,7 +120,7 @@ GVertex::has_edge (const GPolygonEdge *edge) const } size_t -GVertex::num_edges (int max_count) const +Vertex::num_edges (int max_count) const { if (max_count < 0) { // NOTE: this can be slow for a std::list, so we have max_count to limit this effort @@ -128,7 +135,7 @@ GVertex::num_edges (int max_count) const } std::string -GVertex::to_string (bool with_id) const +Vertex::to_string (bool with_id) const { std::string res = tl::sprintf ("(%.12g, %.12g)", x (), y()); if (with_id) { @@ -138,7 +145,7 @@ GVertex::to_string (bool with_id) const } int -GVertex::in_circle (const DPoint &point, const DPoint ¢er, double radius) +Vertex::in_circle (const DPoint &point, const DPoint ¢er, double radius) { double dx = point.x () - center.x (); double dy = point.y () - center.y (); @@ -155,34 +162,34 @@ GVertex::in_circle (const DPoint &point, const DPoint ¢er, double radius) } // ------------------------------------------------------------------------------------- -// GPolygonEdge implementation +// Edge implementation -GPolygonEdge::GPolygonEdge () - : mp_v1 (0), mp_v2 (0), mp_left (), mp_right (), m_level (0), m_id (0), m_is_segment (false) +Edge::Edge (Graph *graph) + : mp_graph (graph), mp_v1 (0), mp_v2 (0), mp_left (), mp_right (), m_level (0), m_id (0), m_is_segment (false) { // .. nothing yet .. } -GPolygonEdge::GPolygonEdge (GVertex *v1, GVertex *v2) - : mp_v1 (v1), mp_v2 (v2), mp_left (), mp_right (), m_level (0), m_id (0), m_is_segment (false) +Edge::Edge (Graph *graph, Vertex *v1, Vertex *v2) + : mp_graph (graph), mp_v1 (v1), mp_v2 (v2), mp_left (), mp_right (), m_level (0), m_id (0), m_is_segment (false) { // .. nothing yet .. } void -GPolygonEdge::set_left (GPolygon *t) +Edge::set_left (Polygon *t) { mp_left = t; } void -GPolygonEdge::set_right (GPolygon *t) +Edge::set_right (Polygon *t) { mp_right = t; } void -GPolygonEdge::link () +Edge::link () { mp_v1->mp_edges.push_back (this); m_ec_v1 = --mp_v1->mp_edges.end (); @@ -192,7 +199,7 @@ GPolygonEdge::link () } void -GPolygonEdge::unlink () +Edge::unlink () { if (mp_v1) { mp_v1->remove_edge (m_ec_v1); @@ -203,8 +210,8 @@ GPolygonEdge::unlink () mp_v1 = mp_v2 = 0; } -GPolygon * -GPolygonEdge::other (const GPolygon *t) const +Polygon * +Edge::other (const Polygon *t) const { if (t == mp_left) { return mp_right; @@ -216,8 +223,8 @@ GPolygonEdge::other (const GPolygon *t) const return 0; } -GVertex * -GPolygonEdge::other (const GVertex *t) const +Vertex * +Edge::other (const Vertex *t) const { if (t == mp_v1) { return mp_v2; @@ -230,13 +237,13 @@ GPolygonEdge::other (const GVertex *t) const } bool -GPolygonEdge::has_vertex (const GVertex *v) const +Edge::has_vertex (const Vertex *v) const { return mp_v1 == v || mp_v2 == v; } -GVertex * -GPolygonEdge::common_vertex (const GPolygonEdge *other) const +Vertex * +Edge::common_vertex (const Edge *other) const { if (has_vertex (other->v1 ())) { return (other->v1 ()); @@ -248,7 +255,7 @@ GPolygonEdge::common_vertex (const GPolygonEdge *other) const } std::string -GPolygonEdge::to_string (bool with_id) const +Edge::to_string (bool with_id) const { std::string res = std::string ("(") + mp_v1->to_string (with_id) + ", " + mp_v2->to_string (with_id) + ")"; if (with_id) { @@ -258,7 +265,7 @@ GPolygonEdge::to_string (bool with_id) const } double -GPolygonEdge::distance (const db::DEdge &e, const db::DPoint &p) +Edge::distance (const db::DEdge &e, const db::DPoint &p) { double l = db::sprod (p - e.p1 (), e.d ()) / e.d ().sq_length (); db::DPoint pp; @@ -273,27 +280,27 @@ GPolygonEdge::distance (const db::DEdge &e, const db::DPoint &p) } bool -GPolygonEdge::crosses (const db::DEdge &e, const db::DEdge &other) +Edge::crosses (const db::DEdge &e, const db::DEdge &other) { return e.side_of (other.p1 ()) * e.side_of (other.p2 ()) < 0 && other.side_of (e.p1 ()) * other.side_of (e.p2 ()) < 0; } bool -GPolygonEdge::crosses_including (const db::DEdge &e, const db::DEdge &other) +Edge::crosses_including (const db::DEdge &e, const db::DEdge &other) { return e.side_of (other.p1 ()) * e.side_of (other.p2 ()) <= 0 && other.side_of (e.p1 ()) * other.side_of (e.p2 ()) <= 0; } db::DPoint -GPolygonEdge::intersection_point (const db::DEdge &e, const db::DEdge &other) +Edge::intersection_point (const db::DEdge &e, const db::DEdge &other) { return e.intersect_point (other).second; } bool -GPolygonEdge::point_on (const db::DEdge &edge, const db::DPoint &point) +Edge::point_on (const db::DEdge &edge, const db::DPoint &point) { if (edge.side_of (point) != 0) { return false; @@ -302,42 +309,72 @@ GPolygonEdge::point_on (const db::DEdge &edge, const db::DPoint &point) } } -#if 0 // @@@ bool -GPolygonEdge::is_for_outside_polygons () const +Edge::can_flip () const +{ + if (! left () || ! right ()) { + return false; + } + + const Vertex *v1 = left ()->opposite (this); + const Vertex *v2 = right ()->opposite (this); + return crosses (db::DEdge (*v1, *v2)); +} + +bool +Edge::can_join_via (const Vertex *vertex) const +{ + if (! left () || ! right ()) { + return false; + } + + tl_assert (has_vertex (vertex)); + const Vertex *v1 = left ()->opposite (this); + const Vertex *v2 = right ()->opposite (this); + return db::DEdge (*v1, *v2).side_of (*vertex) == 0; +} + +bool +Edge::is_outside () const +{ + return left () == 0 || right () == 0; +} + +bool +Edge::is_for_outside_triangles () const { return (left () && left ()->is_outside ()) || (right () && right ()->is_outside ()); } -#endif bool -GPolygonEdge::has_polygon (const GPolygon *t) const +Edge::has_polygon (const Polygon *t) const { return t != 0 && (left () == t || right () == t); } // ------------------------------------------------------------------------------------- -// GPolygon implementation +// Polygon implementation -GPolygon::GPolygon () - : m_id (0) +Polygon::Polygon (Graph *graph) + : mp_graph (graph), m_is_outside (false), m_id (0) { // .. nothing yet .. } void -GPolygon::init () +Polygon::init () { m_id = 0; + m_is_outside = false; if (mp_e.empty ()) { return; } - std::vector e; + std::vector e; e.swap (mp_e); - std::multimap v2e; + std::multimap v2e; for (auto i = e.begin (); i != e.end (); ++i) { if (i != e.begin ()) { @@ -379,7 +416,7 @@ GPolygon::init () // establish clockwise order of the vertexes double area = 0.0; - const db::GVertex *vm1 = vertex (-1), *v0; + const Vertex *vm1 = vertex (-1), *v0; for (auto i = mp_v.begin (); i != mp_v.end (); ++i) { v0 = *i; area += db::vprod (*vm1 - db::DPoint (), *v0 - *vm1); @@ -394,8 +431,8 @@ GPolygon::init () // link the polygon to the edges for (size_t i = 0; i < size (); ++i) { - db::GVertex *v = mp_v[i]; - db::GPolygonEdge *e = mp_e[i]; + Vertex *v = mp_v[i]; + Edge *e = mp_e[i]; if (e->v1 () == v) { e->set_right (this); } else { @@ -404,13 +441,62 @@ GPolygon::init () } } -GPolygon::~GPolygon () +Polygon::Polygon (Graph *graph, Edge *e1, Edge *e2, Edge *e3) + : mp_graph (graph), m_is_outside (false), m_id (0) +{ + mp_e.resize (3, 0); + mp_v.resize (3, 0); + + mp_e[0] = e1; + mp_v[0] = e1->v1 (); + mp_v[1] = e1->v2 (); + + if (e2->has_vertex (mp_v[1])) { + mp_e[1] = e2; + mp_e[2] = e3; + } else { + mp_e[1] = e3; + mp_e[2] = e2; + } + mp_v[2] = mp_e[1]->other (mp_v[1]); + + // enforce clockwise orientation + int s = db::vprod_sign (*mp_v[2] - *mp_v[0], *mp_v[1] - *mp_v[0]); + if (s < 0) { + std::swap (mp_v[2], mp_v[1]); + } else if (s == 0) { + // Triangle is not orientable + tl_assert (false); + } + + // establish link to edges + for (int i = 0; i < 3; ++i) { + + Edge *e = mp_e[i]; + + unsigned int i1 = 0; + for ( ; e->v1 () != mp_v[i1] && i1 < 3; ++i1) + ; + unsigned int i2 = 0; + for ( ; e->v2 () != mp_v[i2] && i2 < 3; ++i2) + ; + + if ((i1 + 1) % 3 == i2) { + e->set_right (this); + } else { + e->set_left (this); + } + + } +} + +Polygon::~Polygon () { unlink (); } void -GPolygon::unlink () +Polygon::unlink () { for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { if ((*e)->left () == this) { @@ -423,7 +509,7 @@ GPolygon::unlink () } std::string -GPolygon::to_string (bool with_id) const +Polygon::to_string (bool with_id) const { std::string res = "("; for (int i = 0; i < int (size ()); ++i) { @@ -441,13 +527,13 @@ GPolygon::to_string (bool with_id) const } double -GPolygon::area () const +Polygon::area () const { return fabs (db::vprod (mp_e[0]->d (), mp_e[1]->d ())) * 0.5; } db::DBox -GPolygon::bbox () const +Polygon::bbox () const { db::DBox box; for (auto i = mp_v.begin (); i != mp_v.end (); ++i) { @@ -456,8 +542,77 @@ GPolygon::bbox () const return box; } -GPolygonEdge * -GPolygon::find_edge_with (const GVertex *v1, const GVertex *v2) const +std::pair +Polygon::circumcircle (bool *ok) const +{ + tl_assert (mp_v.size () == 3); + + // see https://en.wikipedia.org/wiki/Circumcircle + // we set A=(0,0), so the formulas simplify + + if (ok) { + *ok = true; + } + + db::DVector b = *mp_v[1] - *mp_v[0]; + db::DVector c = *mp_v[2] - *mp_v[0]; + + double b2 = b.sq_length (); + double c2 = c.sq_length (); + + double sx = 0.5 * (b2 * c.y () - c2 * b.y ()); + double sy = 0.5 * (b.x () * c2 - c.x() * b2); + + double a1 = b.x() * c.y(); + double a2 = c.x() * b.y(); + double a = a1 - a2; + double a_abs = std::abs (a); + + if (a_abs < (std::abs (a1) + std::abs (a2)) * db::epsilon) { + if (ok) { + *ok = false; + return std::make_pair (db::DPoint (), 0.0); + } else { + tl_assert (false); + } + } + + double radius = sqrt (sx * sx + sy * sy) / a_abs; + db::DPoint center = *mp_v[0] + db::DVector (sx / a, sy / a); + + return std::make_pair (center, radius); +} + +Vertex * +Polygon::opposite (const Edge *edge) const +{ + tl_assert (mp_v.size () == 3); + + for (int i = 0; i < 3; ++i) { + Vertex *v = mp_v[i]; + if (! edge->has_vertex (v)) { + return v; + } + } + tl_assert (false); +} + +Edge * +Polygon::opposite (const Vertex *vertex) const +{ + tl_assert (mp_v.size () == 3); + + for (int i = 0; i < 3; ++i) { + Edge *e = mp_e[i]; + if (! e->has_vertex (vertex)) { + return e; + } + } + tl_assert (false); +} + +Edge * +Polygon::find_edge_with (const Vertex *v1, const Vertex *v2) const { for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { if ((*e)->has_vertex (v1) && (*e)->has_vertex (v2)) { @@ -467,8 +622,8 @@ GPolygon::find_edge_with (const GVertex *v1, const GVertex *v2) const tl_assert (false); } -GPolygonEdge * -GPolygon::common_edge (const GPolygon *other) const +Edge * +Polygon::common_edge (const Polygon *other) const { for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { if ((*e)->other (this) == other) { @@ -478,10 +633,11 @@ GPolygon::common_edge (const GPolygon *other) const return 0; } -#if 0 // @@@ int -GPolygon::contains (const db::DPoint &point) const +Polygon::contains (const db::DPoint &point) const { + tl_assert (mp_v.size () == 3); + auto c = *mp_v[2] - *mp_v[0]; auto b = *mp_v[1] - *mp_v[0]; @@ -492,9 +648,9 @@ GPolygon::contains (const db::DPoint &point) const int res = 1; - const GVertex *vl = mp_v[2]; + const Vertex *vl = mp_v[2]; for (int i = 0; i < 3; ++i) { - const GVertex *v = mp_v[i]; + const Vertex *v = mp_v[i]; int n = db::vprod_sign (point - *vl, *v - *vl) * vps; if (n < 0) { return -1; @@ -506,10 +662,9 @@ GPolygon::contains (const db::DPoint &point) const return res; } -#endif double -GPolygon::min_edge_length () const +Polygon::min_edge_length () const { double lmin = mp_e[0]->d ().length (); for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { @@ -518,19 +673,17 @@ GPolygon::min_edge_length () const return lmin; } -#if 0 // @@@ double -GPolygon::b () const +Polygon::b () const { double lmin = min_edge_length (); bool ok = false; auto cr = circumcircle (&ok); return ok ? lmin / cr.second : 0.0; } -#endif bool -GPolygon::has_segment () const +Polygon::has_segment () const { for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { if ((*e)->is_segment ()) { @@ -541,7 +694,7 @@ GPolygon::has_segment () const } unsigned int -GPolygon::num_segments () const +Polygon::num_segments () const { unsigned int n = 0; for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { @@ -554,49 +707,42 @@ GPolygon::num_segments () const // ----------------------------------------------------------------------------------- -static inline bool is_equal (const db::DPoint &a, const db::DPoint &b) -{ - return std::abs (a.x () - b.x ()) < std::max (1.0, (std::abs (a.x ()) + std::abs (b.x ()))) * db::epsilon && - std::abs (a.y () - b.y ()) < std::max (1.0, (std::abs (a.y ()) + std::abs (b.y ()))) * db::epsilon; -} - -PolygonGraph::PolygonGraph () +Graph::Graph () : m_id (0) - // @@@: m_is_constrained (false), m_level (0), m_id (0), m_flips (0), m_hops (0) { // .. nothing yet .. } -PolygonGraph::~PolygonGraph () +Graph::~Graph () { clear (); } -db::GVertex * -PolygonGraph::create_vertex (double x, double y) +Vertex * +Graph::create_vertex (double x, double y) { - m_vertex_heap.push_back (db::GVertex (x, y)); + m_vertex_heap.push_back (Vertex (this, x, y)); return &m_vertex_heap.back (); } -db::GVertex * -PolygonGraph::create_vertex (const db::DPoint &pt) +Vertex * +Graph::create_vertex (const db::DPoint &pt) { - m_vertex_heap.push_back (pt); + m_vertex_heap.push_back (Vertex (this, pt)); return &m_vertex_heap.back (); } -db::GPolygonEdge * -PolygonGraph::create_edge (db::GVertex *v1, db::GVertex *v2) +Edge * +Graph::create_edge (Vertex *v1, Vertex *v2) { - db::GPolygonEdge *edge = 0; + Edge *edge = 0; if (! m_returned_edges.empty ()) { edge = m_returned_edges.back (); m_returned_edges.pop_back (); - *edge = db::GPolygonEdge (v1, v2); + *edge = Edge (this, v1, v2); } else { - m_edges_heap.push_back (db::GPolygonEdge (v1, v2)); + m_edges_heap.push_back (Edge (this, v1, v2)); edge = &m_edges_heap.back (); } @@ -605,11 +751,21 @@ PolygonGraph::create_edge (db::GVertex *v1, db::GVertex *v2) return edge; } -void -PolygonGraph::remove_polygon (db::GPolygon *poly) +Polygon * +Graph::create_triangle (Edge *e1, Edge *e2, Edge *e3) { - std::vector edges; - edges.reserve (poly->size ()); + Polygon *res = new Polygon (this, e1, e2, e3); + res->set_id (++m_id); + mp_polygons.push_back (res); + + return res; +} + +void +Graph::remove_polygon (Polygon *poly) +{ + std::vector edges; + edges.resize (poly->size (), 0); for (int i = 0; i < int (poly->size ()); ++i) { edges [i] = poly->edge (i); } @@ -625,63 +781,8 @@ PolygonGraph::remove_polygon (db::GPolygon *poly) } } -#if 0 // @@@ -void -PolygonGraph::convex_decompose (const db::DPolygon &polygon) -{ - clear (); - - if (polygon.begin_edge ().at_end ()) { - return; - } - - std::vector edges; - - for (unsigned int c = 0; c < polygon.holes () + 1; ++c) { - - const db::DSimplePolygon::contour_type &ctr = polygon.contour (c); - - db::GVertex *v0 = 0, *vv, *v; - size_t n = ctr.size (); - for (size_t i = 0; i < n; ++i) { - - db::DPoint pm1 = ctr [i > 0 ? i - 1 : n - 1]; - db::DPoint pp1 = ctr [i + 1 < n ? i + 1 : 0]; - db::DPoint p = ctr [i]; - - bool is_convex = db::vprod_sign (p - pm1, pp1 - p); - // @@@ - - v = create_vertex (p.x (), p.y ()); - if (! v0) { - v0 = v; - } else { - edges.push_back (create_edge (vv, v)); - } - - vv = v; - - } - - if (v0 && v0 != v) { - edges.push_back (create_edge (v, v0)); - } - - } -} -#endif - -void -PolygonGraph::convex_decompose (const db::DPolygon &poly) -{ - - // @@@ - - -} - std::string -PolygonGraph::to_string () +Graph::to_string () { std::string res; for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) { @@ -694,7 +795,7 @@ PolygonGraph::to_string () } db::DBox -PolygonGraph::bbox () const +Graph::bbox () const { db::DBox box; for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) { @@ -705,7 +806,7 @@ PolygonGraph::bbox () const #if 0 // @@@ bool -PolygonGraph::check (bool check_delaunay) const +Graph::check (bool check_delaunay) const { bool res = true; @@ -759,7 +860,7 @@ PolygonGraph::check (bool check_delaunay) const tl::error << "(check error) edges " << e->to_string (true) << " vertex 2 not found in adjacent polygon " << t->to_string (true); res = false; } - db::GVertex *vopp = t->opposite (e.operator-> ()); + Vertex *vopp = t->opposite (e.operator-> ()); double sgn = (e->left () == t.operator-> ()) ? 1.0 : -1.0; double vp = db::vprod (e->d(), *vopp - *e->v1 ()); // positive if on left side if (vp * sgn <= 0.0) { @@ -803,7 +904,7 @@ PolygonGraph::check (bool check_delaunay) const #endif db::Layout * -PolygonGraph::to_layout (bool decompose_by_id) const +Graph::to_layout (bool decompose_by_id) const { db::Layout *layout = new db::Layout (); layout->dbu (0.001); @@ -850,7 +951,7 @@ PolygonGraph::to_layout (bool decompose_by_id) const } void -PolygonGraph::dump (const std::string &path, bool decompose_by_id) const +Graph::dump (const std::string &path, bool decompose_by_id) const { std::unique_ptr ly (to_layout (decompose_by_id)); @@ -860,36 +961,19 @@ PolygonGraph::dump (const std::string &path, bool decompose_by_id) const db::Writer writer (opt); writer.write (*ly, stream); - tl::info << "PolygonGraph written to " << path; + tl::info << "Graph written to " << path; } void -PolygonGraph::clear () +Graph::clear () { mp_polygons.clear (); m_edges_heap.clear (); m_vertex_heap.clear (); m_returned_edges.clear (); - // @@@m_is_constrained = false; - // @@@m_level = 0; m_id = 0; } -template -void -PolygonGraph::make_contours (const Poly &poly, const Trans &trans, std::vector > &edge_contours) -{ - edge_contours.push_back (std::vector ()); - for (auto pt = poly.begin_hull (); pt != poly.end_hull (); ++pt) { - edge_contours.back ().push_back (insert_point (trans * *pt)); - } +} // namespace plc - for (unsigned int h = 0; h < poly.holes (); ++h) { - edge_contours.push_back (std::vector ()); - for (auto pt = poly.begin_hole (h); pt != poly.end_hole (h); ++pt) { - edge_contours.back ().push_back (insert_point (trans * *pt)); - } - } -} - -} +} // namespace db diff --git a/src/db/db/dbPolygonGraph.h b/src/db/db/dbPLC.h similarity index 64% rename from src/db/db/dbPolygonGraph.h rename to src/db/db/dbPLC.h index d296b886f..8d410d0b8 100644 --- a/src/db/db/dbPolygonGraph.h +++ b/src/db/db/dbPLC.h @@ -20,8 +20,8 @@ */ -#ifndef HDR_dbPolygonGraph -#define HDR_dbPolygonGraph +#ifndef HDR_dbPLC +#define HDR_dbPLC #include "dbCommon.h" #include "dbTriangle.h" @@ -41,8 +41,38 @@ namespace db class Layout; -class GPolygon; -class GPolygonEdge; +namespace plc +{ + +/** + * @brief A framework for piecewise linear curves + * + * This framework implements classes for dealing with piecewise linear + * curves. It is the basis for triangulation and polygon decomposition + * algorithms. + * + * The core class is the PLCGraph which is a collection of vertices, + * edges and edge loops (polygons). Vertices, edges and polygons form + * graphs. + * + * A "vertex" (db::plc::Vertex) is a point. A point connects two or + * more edges. + * A vertex has some attributes: + * * 'precious': if set, the vertex is not removed during triangulation + * for example. + * + * An "edge" (db::plc::Edge) is a line connecting two vertexes. The + * edge runs from vertex v1 to vertex v2. An edge separates two + * polygons (left and right, as seen in the run direction). + * + * A "segment" as an edge that is part of an original polygon outline. + * + * A "polygon" (db::plc::Polygon) is a loop of edges. + */ + +class Polygon; +class Edge; +class Graph; /** * @brief A class representing a vertex in a Delaunay triangulation graph @@ -51,40 +81,71 @@ class GPolygonEdge; * an integer value that can be used in traversal algorithms * ("level") */ -class DB_PUBLIC GVertex +class DB_PUBLIC Vertex : public db::DPoint { public: - typedef std::list edges_type; + typedef std::list edges_type; typedef edges_type::const_iterator edges_iterator; typedef edges_type::iterator edges_iterator_non_const; - GVertex (); - GVertex (const DPoint &p); - GVertex (const GVertex &v); - GVertex (db::DCoord x, db::DCoord y); + Vertex (const Vertex &v); + Vertex &operator= (const Vertex &v); - GVertex &operator= (const GVertex &v); - -#if 0 // @@@ + /** + * @brief Gets a value indicating whether any of the attached edges is "outside" + */ bool is_outside () const; -#endif - std::vector polygons () const; + /** + * @brief Gets a list of polygons that are attached to this vertex + */ + std::vector polygons() const; + + /** + * @brief Gets the graph object this vertex belongs to + */ + Graph *graph () const { return mp_graph; } + + /** + * @brief Iterates the edges on this vertex (begin) + */ edges_iterator begin_edges () const { return mp_edges.begin (); } + + /** + * @brief Iterates the edges on this vertex (end) + */ edges_iterator end_edges () const { return mp_edges.end (); } + + /** + * @brief Returns the number of edges attached to this vertex + */ size_t num_edges (int max_count = -1) const; - bool has_edge (const GPolygonEdge *edge) const; + /** + * @brief Returns a value indicating whether the given edge is attached to this vertex + */ + bool has_edge (const Edge *edge) const; + /** + * @brief Sets a valid indicating whether the vertex is precious + * + * "precious" vertexes are not removed during triangulation for example. + */ void set_is_precious (bool f) { m_is_precious = f; } + + /** + * @brief Gets a valid indicating whether the vertex is precious + */ bool is_precious () const { return m_is_precious; } + /** + * @brief Returns a string representation of the vertex + */ std::string to_string (bool with_id = false) const; /** * @brief Returns 1 is the point is inside the circle, 0 if on the circle and -1 if outside - * TODO: Move to db::DPoint */ static int in_circle (const db::DPoint &point, const db::DPoint ¢er, double radius); @@ -97,13 +158,20 @@ public: } private: - friend class GPolygonEdge; + friend class Edge; + friend class Graph; + + Vertex (Graph *graph); + Vertex (Graph *graph, const DPoint &p); + Vertex (Graph *graph, const Vertex &v); + Vertex (Graph *graph, db::DCoord x, db::DCoord y); void remove_edge (const edges_iterator_non_const &ec) { mp_edges.erase (ec); } + Graph *mp_graph; edges_type mp_edges; bool m_is_precious; }; @@ -111,15 +179,15 @@ private: /** * @brief A class representing an edge in the Delaunay triangulation graph */ -class DB_PUBLIC GPolygonEdge +class DB_PUBLIC Edge { public: - class GPolygonIterator + class PolygonIterator { public: - typedef GPolygon value_type; - typedef GPolygon &reference; - typedef GPolygon *pointer; + typedef Polygon value_type; + typedef Polygon &reference; + typedef Polygon *pointer; reference operator*() const { @@ -131,17 +199,17 @@ public: return m_index ? mp_edge->right () : mp_edge->left (); } - bool operator== (const GPolygonIterator &other) const + bool operator== (const PolygonIterator &other) const { return m_index == other.m_index; } - bool operator!= (const GPolygonIterator &other) const + bool operator!= (const PolygonIterator &other) const { return !operator== (other); } - GPolygonIterator &operator++ () + PolygonIterator &operator++ () { while (++m_index < 2 && operator-> () == 0) ; @@ -149,9 +217,9 @@ public: } private: - friend class GPolygonEdge; + friend class Edge; - GPolygonIterator (const GPolygonEdge *edge) + PolygonIterator (const Edge *edge) : mp_edge (edge), m_index (0) { if (! edge) { @@ -162,48 +230,72 @@ public: } } - const GPolygonEdge *mp_edge; + const Edge *mp_edge; unsigned int m_index; }; - GPolygonEdge (); - GPolygonEdge (GVertex *v1, GVertex *v2); + /** + * @brief Gets the first vertex ("from") + */ + Vertex *v1 () const { return mp_v1; } - GVertex *v1 () const { return mp_v1; } - GVertex *v2 () const { return mp_v2; } + /** + * @brief Gets the first vertex ("to") + */ + Vertex *v2 () const { return mp_v2; } + /** + * @brief Reverses the edge + */ void reverse () { std::swap (mp_v1, mp_v2); std::swap (mp_left, mp_right); } - GPolygon *left () const { return mp_left; } - GPolygon *right () const { return mp_right; } + /** + * @brief Gets the polygon on the left side (can be null) + */ + Polygon *left () const { return mp_left; } - GPolygonIterator begin_polygons () const + /** + * @brief Gets the polygon on the right side (can be null) + */ + Polygon *right () const { return mp_right; } + + /** + * @brief Iterates the polygons (one or two, begin iterator) + */ + PolygonIterator begin_polygons () const { - return GPolygonIterator (this); + return PolygonIterator (this); } - GPolygonIterator end_polygons () const + /** + * @brief Iterates the polygons (end iterator) + */ + PolygonIterator end_polygons () const { - return GPolygonIterator (0); + return PolygonIterator (0); } - void set_level (size_t l) { m_level = l; } - size_t level () const { return m_level; } - - void set_id (size_t id) { m_id = id; } - size_t id () const { return m_id; } - - void set_is_segment (bool is_seg) { m_is_segment = is_seg; } + /** + * @brief Gets a value indicating whether the edge is a segment + */ bool is_segment () const { return m_is_segment; } + /** + * @brief Gets the edge ID (a unique identifier) + */ + size_t id () const { return m_id; } + + /** + * @brief Gets a string representation of the edge + */ std::string to_string (bool with_id = false) const; /** - * @brief Converts to an db::DEdge + * @brief Converts to a db::DEdge */ db::DEdge edge () const { @@ -254,7 +346,7 @@ public: * "crosses" is true, if both edges share at least one point which is not an endpoint * of one of the edges. */ - bool crosses (const db::GPolygonEdge &other) const + bool crosses (const Edge &other) const { return crosses (edge (), other.edge ()); } @@ -279,7 +371,7 @@ public: * @brief Returns a value indicating whether this edge crosses the other one * "crosses" is true, if both edges share at least one point. */ - bool crosses_including (const db::GPolygonEdge &other) const + bool crosses_including (const Edge &other) const { return crosses_including (edge (), other.edge ()); } @@ -301,7 +393,7 @@ public: /** * @brief Gets the intersection point */ - db::DPoint intersection_point (const GPolygonEdge &other) const + db::DPoint intersection_point (const Edge &other) const { return intersection_point (edge (), other.edge ()); } @@ -353,24 +445,23 @@ public: /** * @brief Gets the other triangle for the given one */ - GPolygon *other (const GPolygon *) const; + Polygon *other (const Polygon *) const; /** * @brief Gets the other vertex for the given one */ - GVertex *other (const GVertex *) const; + Vertex *other (const Vertex *) const; /** * @brief Gets a value indicating whether the edge has the given vertex */ - bool has_vertex (const GVertex *) const; + bool has_vertex (const Vertex *) const; /** * @brief Gets the common vertex of the other edge and this edge or null if there is no common vertex */ - GVertex *common_vertex (const GPolygonEdge *other) const; + Vertex *common_vertex (const Edge *other) const; -#if 0 // @@@ /** * @brief Returns a value indicating whether this edge can be flipped */ @@ -379,81 +470,93 @@ public: /** * @brief Returns a value indicating whether the edge separates two triangles that can be joined into one (via the given vertex) */ - bool can_join_via (const GVertex *vertex) const; + bool can_join_via (const Vertex *vertex) const; + + /** + * @brief Returns a value indicating whether this edge belongs to outside triangles + */ + bool is_for_outside_triangles () const; /** * @brief Returns a value indicating whether this edge is an outside edge (no other triangles) */ bool is_outside () const; - /** - * @brief Returns a value indicating whether this edge belongs to outside triangles - */ - bool is_for_outside_triangles () const; -#endif // @@@ - /** * @brief Returns a value indicating whether t is attached to this edge */ - bool has_polygon (const GPolygon *t) const; + bool has_polygon (const Polygon *t) const; protected: void unlink (); void link (); private: - friend class GPolygon; - friend class PolygonGraph; + friend class Polygon; + friend class Graph; + friend class Triangulation; - GVertex *mp_v1, *mp_v2; - GPolygon *mp_left, *mp_right; - GVertex::edges_iterator_non_const m_ec_v1, m_ec_v2; + void set_level (size_t l) { m_level = l; } + size_t level () const { return m_level; } + + void set_id (size_t id) { m_id = id; } + + void set_is_segment (bool is_seg) { m_is_segment = is_seg; } + + Edge (Graph *graph); + Edge (Graph *graph, Vertex *v1, Vertex *v2); + + Graph *mp_graph; + Vertex *mp_v1, *mp_v2; + Polygon *mp_left, *mp_right; + Vertex::edges_iterator_non_const m_ec_v1, m_ec_v2; size_t m_level; size_t m_id; bool m_is_segment; - void set_left (GPolygon *t); - void set_right (GPolygon *t); + void set_left (Polygon *t); + void set_right (Polygon *t); }; /** - * @brief A compare function that compares triangles by ID + * @brief A compare function that compares edges by ID * * The ID acts as a more predicable unique ID for the object in sets and maps. */ -struct GPolygonEdgeLessFunc +struct EdgeLessFunc { - bool operator () (GPolygonEdge *a, GPolygonEdge *b) const + bool operator () (Edge *a, Edge *b) const { return a->id () < b->id (); } }; /** - * @brief A class representing a triangle + * @brief A class representing a polygon */ -class DB_PUBLIC GPolygon - : public tl::list_node, public tl::Object +class DB_PUBLIC Polygon + : public tl::list_node, public tl::Object { public: - GPolygon (); + Polygon (Graph *graph); + Polygon (Graph *graph, Edge *e1, Edge *e2, Edge *e3); template - GPolygon (Iter from, Iter to) - : mp_e (from, to) + Polygon (Graph *graph, Iter from, Iter to) + : mp_graph (graph), mp_e (from, to) { init (); } - ~GPolygon (); + ~Polygon (); void unlink (); void set_id (size_t id) { m_id = id; } size_t id () const { return m_id; } - // @@@bool is_outside () const { return m_is_outside; } - // @@@void set_outside (bool o) { m_is_outside = o; } + bool is_outside () const { return m_is_outside; } + void set_outside (bool o) { m_is_outside = o; } std::string to_string (bool with_id = false) const; @@ -469,7 +572,7 @@ public: * @brief Gets the nth vertex (n wraps around and can be negative) * The vertexes are oriented clockwise. */ - inline GVertex *vertex (int n) const + inline Vertex *vertex (int n) const { size_t sz = size (); tl_assert (sz > 0); @@ -483,7 +586,7 @@ public: /** * @brief Gets the nth edge (n wraps around and can be negative) */ - inline GPolygonEdge *edge (int n) const + inline Edge *edge (int n) const { size_t sz = size (); tl_assert (sz > 0); @@ -504,46 +607,50 @@ public: */ db::DBox bbox () const; -#if 0 // @@@ /** * @brief Gets the center point and radius of the circumcircle * If ok is non-null, it will receive a boolean value indicating whether the circumcircle is valid. * An invalid circumcircle is an indicator for a degenerated triangle with area 0 (or close to). + * + * This method only applies to triangles. */ std::pair circumcircle (bool *ok = 0) const; /** * @brief Gets the vertex opposite of the given edge + * + * This method only applies to triangles. */ - GVertex *opposite (const GPolygonEdge *edge) const; + Vertex *opposite (const Edge *edge) const; /** * @brief Gets the edge opposite of the given vertex + * + * This method only applies to triangles. */ - GPolygonEdge *opposite (const GVertex *vertex) const; -#endif + Edge *opposite (const Vertex *vertex) const; /** * @brief Gets the edge with the given vertexes */ - GPolygonEdge *find_edge_with (const GVertex *v1, const GVertex *v2) const; + Edge *find_edge_with (const Vertex *v1, const Vertex *v2) const; /** * @brief Finds the common edge for both polygons */ - GPolygonEdge *common_edge (const GPolygon *other) const; + Edge *common_edge (const Polygon *other) const; -#if 0 // @@@ /** * @brief Returns a value indicating whether the point is inside (1), on the polygon (0) or outside (-1) + * + * This method only applies to triangles currently. */ int contains (const db::DPoint &point) const; -#endif /** * @brief Gets a value indicating whether the triangle has the given vertex */ - inline bool has_vertex (const db::GVertex *v) const + inline bool has_vertex (const Vertex *v) const { for (auto i = mp_v.begin (); i != mp_v.end (); ++i) { if (*i == v) { @@ -556,7 +663,7 @@ public: /** * @brief Gets a value indicating whether the triangle has the given edge */ - inline bool has_edge (const db::GPolygonEdge *e) const + inline bool has_edge (const Edge *e) const { for (auto i = mp_e.begin (); i != mp_e.end (); ++i) { if (*i == e) { @@ -571,12 +678,12 @@ public: */ double min_edge_length () const; -#if 0 // @@@ /** * @brief Returns the min edge length to circumcircle radius ratio + * + * This method only applies to triangles currently. */ double b () const; -#endif /** * @brief Returns a value indicating whether the polygon borders to a segment @@ -589,16 +696,17 @@ public: unsigned int num_segments () const; private: - // @@@ bool m_is_outside; - std::vector mp_e; - std::vector mp_v; + Graph *mp_graph; + bool m_is_outside; + std::vector mp_e; + std::vector mp_v; size_t m_id; void init (); // no copying - GPolygon &operator= (const GPolygon &); - GPolygon (const GPolygon &); + Polygon &operator= (const Polygon &); + Polygon (const Polygon &); }; /** @@ -606,87 +714,29 @@ private: * * The ID acts as a more predicable unique ID for the object in sets and maps. */ -struct GPolygonLessFunc +struct PolygonLessFunc { - bool operator () (GPolygon *a, GPolygon *b) const + bool operator () (Polygon *a, Polygon *b) const { return a->id () < b->id (); } }; -class DB_PUBLIC PolygonGraph +/** + * @brief A class representing the polygon graph + * + * A polygon graph is the main container, holding vertexes, edges and polygons. + * The graph can be of "triangles" type, in which case it is guaranteed to only + * hold triangles (polygons with 3 vertexes). + */ +class DB_PUBLIC Graph { public: -#if 0 // @@@ - struct TriangulateParameters - { - TriangulateParameters () - : min_b (1.0), - min_length (0.0), - max_area (0.0), - max_area_border (0.0), - max_iterations (std::numeric_limits::max ()), - base_verbosity (30), - mark_triangles (false) - { } - - /** - * @brief Min. readius-to-shortest edge ratio - */ - double min_b; - - /** - * @brief Min. edge length - * - * This parameter does not provide a guarantee about a minimume edge length, but - * helps avoiding ever-reducing triangle splits in acute corners of the input polygon. - * Splitting of edges stops when the edge is less than the min length. - */ - double min_length; - - /** - * @brief Max area or zero for "no constraint" - */ - double max_area; - - /** - * @brief Max area for border triangles or zero for "use max_area" - */ - double max_area_border; - - /** - * @brief Max number of iterations - */ - size_t max_iterations; - - /** - * @brief The verbosity level above which triangulation reports details - */ - int base_verbosity; - - /** - * @brief If true, final triangles are marked using the "id" integer as a bit field - * - * This provides information about the result quality. - * - * Bit 0: skinny triangle - * Bit 1: bad-quality (skinny or area too large) - * Bit 2: non-Delaunay (in the strict sense) - */ - bool mark_triangles; - }; -#endif - - typedef tl::list polygons_type; + typedef tl::list polygons_type; typedef polygons_type::const_iterator polygon_iterator; - PolygonGraph (); - ~PolygonGraph (); - - /** - * @brief Creates a convex decomposition for the given polygon - */ - void convex_decompose (const DPolygon &poly); + Graph (); + ~Graph (); /** * @brief Returns a string representation of the polygon graph. @@ -718,15 +768,6 @@ public: */ void clear (); -protected: -#if 0 // @@@ - /** - * @brief Checks the polygon graph for consistency - * This method is for testing purposes mainly. - */ - bool check (bool check_delaunay = true) const; -#endif - /** * @brief Dumps the polygon graph to a GDS file at the given path * This method is for testing purposes mainly. @@ -743,35 +784,43 @@ protected: */ db::Layout *to_layout (bool decompose_by_id = false) const; -private: - tl::list mp_polygons; - tl::stable_vector m_edges_heap; - std::vector m_returned_edges; - tl::stable_vector m_vertex_heap; -// @@@ bool m_is_constrained; -// @@@ size_t m_level; - size_t m_id; -// @@@ size_t m_flips, m_hops; - - db::GVertex *create_vertex (double x, double y); - db::GVertex *create_vertex (const db::DPoint &pt); - db::GPolygonEdge *create_edge (db::GVertex *v1, db::GVertex *v2); +protected: + Vertex *create_vertex (double x, double y); + Vertex *create_vertex (const db::DPoint &pt); + Edge *create_edge (Vertex *v1, Vertex *v2); template - db::GPolygon * + Polygon * create_polygon (Iter from, Iter to) { - db::GPolygon *res = new db::GPolygon (from ,to); + Polygon *res = new Polygon (this, from ,to); res->set_id (++m_id); mp_polygons.push_back (res); return res; } - void remove_polygon (db::GPolygon *tri); - template void make_contours (const Poly &poly, const Trans &trans, std::vector > &contours); + Polygon *create_triangle (Edge *e1, Edge *e2, Edge *e3); + + void remove_polygon (Polygon *p); + +private: + friend class Triangulation; + friend class ConvexDecomposition; + + tl::list mp_polygons; + tl::stable_vector m_edges_heap; + std::vector m_returned_edges; + tl::stable_vector m_vertex_heap; + size_t m_id; + + tl::list &polygons () { return mp_polygons; } + tl::stable_vector &edges () { return m_edges_heap; } + tl::stable_vector &vertexes () { return m_vertex_heap; } }; -} +} // namespace plc + +} // namespace db #endif diff --git a/src/db/db/dbPLCTriangulation.cc b/src/db/db/dbPLCTriangulation.cc new file mode 100644 index 000000000..879cd9e7d --- /dev/null +++ b/src/db/db/dbPLCTriangulation.cc @@ -0,0 +1,1649 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "dbPLCTriangulation.h" +#include "dbLayout.h" +#include "dbWriter.h" +#include "tlStream.h" +#include "tlLog.h" +#include "tlTimer.h" + +#include +#include +#include +#include + +namespace db +{ + +namespace plc +{ + +static inline bool is_equal (const db::DPoint &a, const db::DPoint &b) +{ + return std::abs (a.x () - b.x ()) < std::max (1.0, (std::abs (a.x ()) + std::abs (b.x ()))) * db::epsilon && + std::abs (a.y () - b.y ()) < std::max (1.0, (std::abs (a.y ()) + std::abs (b.y ()))) * db::epsilon; +} + +Triangulation::Triangulation (Graph *graph) +{ + mp_graph = graph; + clear (); +} + +void +Triangulation::clear () +{ + mp_graph->clear (); + + m_is_constrained = false; + m_level = 0; + m_id = 0; + m_flips = m_hops = 0; +} + +void +Triangulation::init_box (const db::DBox &box) +{ + double xmin = box.left (), xmax = box.right (); + double ymin = box.bottom (), ymax = box.top (); + + Vertex *vbl = mp_graph->create_vertex (xmin, ymin); + Vertex *vtl = mp_graph->create_vertex (xmin, ymax); + Vertex *vbr = mp_graph->create_vertex (xmax, ymin); + Vertex *vtr = mp_graph->create_vertex (xmax, ymax); + + Edge *sl = mp_graph->create_edge (vbl, vtl); + Edge *sd = mp_graph->create_edge (vtl, vbr); + Edge *sb = mp_graph->create_edge (vbr, vbl); + + Edge *sr = mp_graph->create_edge (vbr, vtr); + Edge *st = mp_graph->create_edge (vtr, vtl); + + mp_graph->create_triangle (sl, sd, sb); + mp_graph->create_triangle (sd, sr, st); +} + +bool +Triangulation::check (bool check_delaunay) const +{ + bool res = true; + + for (auto t = mp_graph->polygons ().begin (); t != mp_graph->polygons ().end (); ++t) { + if (t->size () != 3) { + res = false; + tl::error << "(check error) not a triangle: " << t->to_string (); + } + } + + if (! res) { + return false; + } + + if (check_delaunay) { + for (auto t = mp_graph->polygons ().begin (); t != mp_graph->polygons ().end (); ++t) { + auto cp = t->circumcircle (); + auto vi = find_inside_circle (cp.first, cp.second); + if (! vi.empty ()) { + res = false; + tl::error << "(check error) triangle does not meet Delaunay criterion: " << t->to_string (); + for (auto v = vi.begin (); v != vi.end (); ++v) { + tl::error << " vertex inside circumcircle: " << (*v)->to_string (true); + } + } + } + } + + for (auto t = mp_graph->polygons ().begin (); t != mp_graph->polygons ().end (); ++t) { + for (int i = 0; i < 3; ++i) { + if (! t->edge (i)->has_polygon (t.operator-> ())) { + tl::error << "(check error) edges " << t->edge (i)->to_string (true) + << " attached to triangle " << t->to_string (true) << " does not refer to this triangle"; + res = false; + } + } + } + + for (auto e = mp_graph->edges ().begin (); e != mp_graph->edges ().end (); ++e) { + + if (!e->left () && !e->right ()) { + continue; + } + + if (e->left () && e->right ()) { + if (e->left ()->is_outside () != e->right ()->is_outside () && ! e->is_segment ()) { + tl::error << "(check error) edge " << e->to_string (true) << " splits an outside and inside triangle, but is not a segment"; + res = false; + } + } + + for (auto t = e->begin_polygons (); t != e->end_polygons (); ++t) { + if (! t->has_edge (e.operator-> ())) { + tl::error << "(check error) edge " << e->to_string (true) << " not found in adjacent triangle " << t->to_string (true); + res = false; + } + if (! t->has_vertex (e->v1 ())) { + tl::error << "(check error) edges " << e->to_string (true) << " vertex 1 not found in adjacent triangle " << t->to_string (true); + res = false; + } + if (! t->has_vertex (e->v2 ())) { + tl::error << "(check error) edges " << e->to_string (true) << " vertex 2 not found in adjacent triangle " << t->to_string (true); + res = false; + } + Vertex *vopp = t->opposite (e.operator-> ()); + double sgn = (e->left () == t.operator-> ()) ? 1.0 : -1.0; + double vp = db::vprod (e->d(), *vopp - *e->v1 ()); // positive if on left side + if (vp * sgn <= 0.0) { + const char * side_str = sgn > 0.0 ? "left" : "right"; + tl::error << "(check error) external point " << vopp->to_string (true) << " not on " << side_str << " side of edge " << e->to_string (true); + res = false; + } + } + + if (! e->v1 ()->has_edge (e.operator-> ())) { + tl::error << "(check error) edge " << e->to_string (true) << " vertex 1 does not list this edge"; + res = false; + } + if (! e->v2 ()->has_edge (e.operator-> ())) { + tl::error << "(check error) edge " << e->to_string (true) << " vertex 2 does not list this edge"; + res = false; + } + + } + + for (auto v = mp_graph->vertexes ().begin (); v != mp_graph->vertexes ().end (); ++v) { + unsigned int num_outside_edges = 0; + for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { + if ((*e)->is_outside ()) { + ++num_outside_edges; + } + } + if (num_outside_edges > 0 && num_outside_edges != 2) { + tl::error << "(check error) vertex " << v->to_string (true) << " has " << num_outside_edges << " outside edges (can only be 2)"; + res = false; + for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { + if ((*e)->is_outside ()) { + tl::error << " Outside edge is " << (*e)->to_string (true); + } + } + } + } + + return res; +} + +std::vector +Triangulation::find_points_around (Vertex *vertex, double radius) +{ + std::set seen; + seen.insert (vertex); + + std::vector res; + std::vector new_vertexes, next_vertexes; + new_vertexes.push_back (vertex); + + while (! new_vertexes.empty ()) { + next_vertexes.clear (); + for (auto v = new_vertexes.begin (); v != new_vertexes.end (); ++v) { + for (auto e = (*v)->begin_edges (); e != (*v)->end_edges (); ++e) { + Vertex *ov = (*e)->other (*v); + if (ov->in_circle (*vertex, radius) == 1 && seen.insert (ov).second) { + next_vertexes.push_back (ov); + res.push_back (ov); + } + } + } + new_vertexes.swap (next_vertexes); + } + + return res; +} + +Vertex * +Triangulation::insert_point (const db::DPoint &point, std::list > *new_triangles) +{ + return insert (mp_graph->create_vertex (point), new_triangles); +} + +Vertex * +Triangulation::insert_point (db::DCoord x, db::DCoord y, std::list > *new_triangles) +{ + return insert (mp_graph->create_vertex (x, y), new_triangles); +} + +Vertex * +Triangulation::insert (Vertex *vertex, std::list > *new_triangles) +{ + std::vector tris = find_triangle_for_point (*vertex); + + // the new vertex is outside the domain + if (tris.empty ()) { + tl_assert (! m_is_constrained); + insert_new_vertex (vertex, new_triangles); + return vertex; + } + + // check, if the new vertex is on an edge (may be edge between triangles or edge on outside) + std::vector on_edges; + std::vector on_vertex; + for (int i = 0; i < 3; ++i) { + Edge *e = tris.front ()->edge (i); + if (e->side_of (*vertex) == 0) { + if (is_equal (*vertex, *e->v1 ()) || is_equal (*vertex, *e->v2 ())) { + on_vertex.push_back (e); + } else { + on_edges.push_back (e); + } + } + } + + if (! on_vertex.empty ()) { + + tl_assert (on_vertex.size () == size_t (2)); + return on_vertex.front ()->common_vertex (on_vertex [1]); + + } else if (! on_edges.empty ()) { + + tl_assert (on_edges.size () == size_t (1)); + split_triangles_on_edge (vertex, on_edges.front (), new_triangles); + return vertex; + + } else if (tris.size () == size_t (1)) { + + // the new vertex is inside one triangle + split_triangle (tris.front (), vertex, new_triangles); + return vertex; + + } + + tl_assert (false); +} + +std::vector +Triangulation::find_triangle_for_point (const db::DPoint &point) +{ + Edge *edge = find_closest_edge (point); + + std::vector res; + if (edge) { + for (auto t = edge->begin_polygons (); t != edge->end_polygons (); ++t) { + if (t->contains (point) >= 0) { + res.push_back (t.operator-> ()); + } + } + } + + return res; +} + +Edge * +Triangulation::find_closest_edge (const db::DPoint &p, Vertex *vstart, bool inside_only) +{ + if (!vstart) { + + if (! mp_graph->polygons ().empty ()) { + + unsigned int ls = 0; + size_t n = mp_graph->vertexes ().size (); + size_t m = n; + + // A simple heuristics that takes a sqrt(N) sample from the + // vertexes to find a good starting point + + vstart = mp_graph->polygons ().begin ()->vertex (0); + double dmin = vstart->distance (p); + + while (ls * ls < m) { + m /= 2; + for (size_t i = m / 2; i < n; i += m) { + ++ls; + // NOTE: this assumes the heap is not too loaded with orphan vertexes + Vertex *v = (mp_graph->vertexes ().begin () + i).operator-> (); + if (v->begin_edges () != v->end_edges ()) { + double d = v->distance (p); + if (d < dmin) { + vstart = v; + dmin = d; + } + } + } + } + + } else { + + return 0; + + } + + } + + db::DEdge line (*vstart, p); + + double d = -1.0; + Edge *edge = 0; + Vertex *v = vstart; + + while (v) { + + Vertex *vnext = 0; + + for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { + + if (inside_only) { + // NOTE: in inside mode we stay on the line of sight as we don't + // want to walk around outside pockets. + if (! (*e)->is_segment () && (*e)->is_for_outside_triangles ()) { + continue; + } + if (! (*e)->crosses_including (line)) { + continue; + } + } + + double ds = (*e)->distance (p); + + if (d < 0.0) { + + d = ds; + edge = *e; + vnext = edge->other (v); + + } else if (fabs (ds - d) < std::max (1.0, fabs (ds) + fabs (d)) * db::epsilon) { + + // this differentiation selects the edge which bends further towards + // the target point if both edges share a common point and that + // is the one the determines the distance. + Vertex *cv = edge->common_vertex (*e); + if (cv) { + db::DVector edge_d = *edge->other (cv) - *cv; + db::DVector e_d = *(*e)->other(cv) - *cv; + db::DVector r = p - *cv; + double edge_sp = db::sprod (r, edge_d) / edge_d.length (); + double s_sp = db::sprod (r, e_d) / e_d.length (); + if (s_sp > edge_sp + db::epsilon) { + edge = *e; + vnext = edge->other (v); + } + } + + } else if (ds < d) { + + d = ds; + edge = *e; + vnext = edge->other (v); + + } + + } + + ++m_hops; + + v = vnext; + + } + + return edge; +} + +void +Triangulation::insert_new_vertex (Vertex *vertex, std::list > *new_triangles_out) +{ + if (mp_graph->polygons ().empty ()) { + + tl_assert (mp_graph->vertexes ().size () <= size_t (3)); // fails if vertexes were created but not inserted. + + if (mp_graph->vertexes ().size () == 3) { + + std::vector vv; + for (auto v = mp_graph->vertexes ().begin (); v != mp_graph->vertexes ().end (); ++v) { + vv.push_back (v.operator-> ()); + } + + // form the first triangle + Edge *s1 = mp_graph->create_edge (vv[0], vv[1]); + Edge *s2 = mp_graph->create_edge (vv[1], vv[2]); + Edge *s3 = mp_graph->create_edge (vv[2], vv[0]); + + if (db::vprod_sign (s1->d (), s2->d ()) == 0) { + // avoid degenerate Triangles to happen here + tl_assert (false); + } else { + Polygon *t = mp_graph->create_triangle (s1, s2, s3); + if (new_triangles_out) { + new_triangles_out->push_back (t); + } + } + + } + + return; + + } + + std::vector new_triangles; + + // Find closest edge + Edge *closest_edge = find_closest_edge (*vertex); + tl_assert (closest_edge != 0); + + Edge *s1 = mp_graph->create_edge (vertex, closest_edge->v1 ()); + Edge *s2 = mp_graph->create_edge (vertex, closest_edge->v2 ()); + + Polygon *t = mp_graph->create_triangle (s1, closest_edge, s2); + new_triangles.push_back (t); + + add_more_triangles (new_triangles, closest_edge, closest_edge->v1 (), vertex, s1); + add_more_triangles (new_triangles, closest_edge, closest_edge->v2 (), vertex, s2); + + if (new_triangles_out) { + new_triangles_out->insert (new_triangles_out->end (), new_triangles.begin (), new_triangles.end ()); + } + + fix_triangles (new_triangles, std::vector (), new_triangles_out); +} + +void +Triangulation::add_more_triangles (std::vector &new_triangles, + Edge *incoming_edge, + Vertex *from_vertex, Vertex *to_vertex, + Edge *conn_edge) +{ + while (true) { + + Edge *next_edge = 0; + + for (auto e = from_vertex->begin_edges (); e != from_vertex->end_edges (); ++e) { + if (! (*e)->has_vertex (to_vertex) && (*e)->is_outside ()) { + // TODO: remove and break + tl_assert (next_edge == 0); + next_edge = *e; + } + } + + tl_assert (next_edge != 0); + Vertex *next_vertex = next_edge->other (from_vertex); + + db::DVector d_from_to = *to_vertex - *from_vertex; + Vertex *incoming_vertex = incoming_edge->other (from_vertex); + if (db::vprod_sign(*from_vertex - *incoming_vertex, d_from_to) * db::vprod_sign(*from_vertex - *next_vertex, d_from_to) >= 0) { + return; + } + + Edge *next_conn_edge = mp_graph->create_edge (next_vertex, to_vertex); + Polygon *t = mp_graph->create_triangle (next_conn_edge, next_edge, conn_edge); + new_triangles.push_back (t); + + incoming_edge = next_edge; + conn_edge = next_conn_edge; + from_vertex = next_vertex; + + } +} + +void +Triangulation::split_triangle (Polygon *t, Vertex *vertex, std::list > *new_triangles_out) +{ + t->unlink (); + + std::map v2new_edges; + std::vector new_edges; + for (int i = 0; i < 3; ++i) { + Vertex *v = t->vertex (i); + Edge *e = mp_graph->create_edge (v, vertex); + v2new_edges[v] = e; + new_edges.push_back (e); + } + + std::vector new_triangles; + for (int i = 0; i < 3; ++i) { + Edge *e = t->edge (i); + Polygon *new_triangle = mp_graph->create_triangle (e, v2new_edges[e->v1 ()], v2new_edges[e->v2 ()]); + if (new_triangles_out) { + new_triangles_out->push_back (new_triangle); + } + new_triangle->set_outside (t->is_outside ()); + new_triangles.push_back (new_triangle); + } + + mp_graph->remove_polygon (t); + + fix_triangles (new_triangles, new_edges, new_triangles_out); +} + +void +Triangulation::split_triangles_on_edge (Vertex *vertex, Edge *split_edge, std::list > *new_triangles_out) +{ + Edge *s1 = mp_graph->create_edge (split_edge->v1 (), vertex); + Edge *s2 = mp_graph->create_edge (split_edge->v2 (), vertex); + s1->set_is_segment (split_edge->is_segment ()); + s2->set_is_segment (split_edge->is_segment ()); + + std::vector new_triangles; + + std::vector tris; + tris.reserve (2); + for (auto t = split_edge->begin_polygons (); t != split_edge->end_polygons (); ++t) { + tris.push_back (t.operator-> ()); + } + + for (auto t = tris.begin (); t != tris.end (); ++t) { + + (*t)->unlink (); + + Vertex *ext_vertex = (*t)->opposite (split_edge); + Edge *new_edge = mp_graph->create_edge (ext_vertex, vertex); + + for (int i = 0; i < 3; ++i) { + + Edge *e = (*t)->edge (i); + if (e->has_vertex (ext_vertex)) { + + Edge *partial = e->has_vertex (split_edge->v1 ()) ? s1 : s2; + Polygon *new_triangle = mp_graph->create_triangle (new_edge, partial, e); + + if (new_triangles_out) { + new_triangles_out->push_back (new_triangle); + } + new_triangle->set_outside ((*t)->is_outside ()); + new_triangles.push_back (new_triangle); + + } + + } + + } + + for (auto t = tris.begin (); t != tris.end (); ++t) { + mp_graph->remove_polygon (*t); + } + + std::vector fixed_edges; + fixed_edges.push_back (s1); + fixed_edges.push_back (s2); + fix_triangles (new_triangles, fixed_edges, new_triangles_out); +} + +std::vector +Triangulation::find_touching (const db::DBox &box) const +{ + // NOTE: this is a naive, slow implementation for test purposes + std::vector res; + for (auto v = mp_graph->vertexes ().begin (); v != mp_graph->vertexes ().end (); ++v) { + if (v->begin_edges () != v->end_edges ()) { + if (box.contains (*v)) { + res.push_back (const_cast (v.operator-> ())); + } + } + } + return res; +} + +std::vector +Triangulation::find_inside_circle (const db::DPoint ¢er, double radius) const +{ + // NOTE: this is a naive, slow implementation for test purposes + std::vector res; + for (auto v = mp_graph->vertexes ().begin (); v != mp_graph->vertexes ().end (); ++v) { + if (v->begin_edges () != v->end_edges ()) { + if (v->in_circle (center, radius) == 1) { + res.push_back (const_cast (v.operator-> ())); + } + } + } + return res; +} + +void +Triangulation::remove (Vertex *vertex, std::list > *new_triangles) +{ + if (vertex->begin_edges () == vertex->end_edges ()) { + // removing an orphan vertex -> ignore + } else if (vertex->is_outside ()) { + remove_outside_vertex (vertex, new_triangles); + } else { + remove_inside_vertex (vertex, new_triangles); + } +} + +void +Triangulation::remove_outside_vertex (Vertex *vertex, std::list > *new_triangles_out) +{ + auto to_remove = vertex->polygons (); + + std::vector outer_edges; + for (auto t = to_remove.begin (); t != to_remove.end (); ++t) { + outer_edges.push_back ((*t)->opposite (vertex)); + } + + for (auto t = to_remove.begin (); t != to_remove.end (); ++t) { + (*t)->unlink (); + } + + auto new_triangles = fill_concave_corners (outer_edges); + + for (auto t = to_remove.begin (); t != to_remove.end (); ++t) { + mp_graph->remove_polygon (*t); + } + + fix_triangles (new_triangles, std::vector (), new_triangles_out); +} + +void +Triangulation::remove_inside_vertex (Vertex *vertex, std::list > *new_triangles_out) +{ + std::set triangles_to_fix; + + bool make_new_triangle = true; + + while (vertex->num_edges (4) > 3) { + + Edge *to_flip = 0; + for (auto e = vertex->begin_edges (); e != vertex->end_edges () && to_flip == 0; ++e) { + if ((*e)->can_flip ()) { + to_flip = *e; + } + } + if (! to_flip) { + break; + } + + // NOTE: in the "can_join" case zero-area triangles are created which we will sort out later + triangles_to_fix.erase (to_flip->left ()); + triangles_to_fix.erase (to_flip->right ()); + + auto pp = flip (to_flip); + triangles_to_fix.insert (pp.first.first); + triangles_to_fix.insert (pp.first.second); + + } + + if (vertex->num_edges (4) > 3) { + + tl_assert (vertex->num_edges (5) == 4); + + // This case can happen if two edges attached to the vertex are collinear + // in this case choose the "join" strategy + Edge *jseg = 0; + for (auto e = vertex->begin_edges (); e != vertex->end_edges () && !jseg; ++e) { + if ((*e)->can_join_via (vertex)) { + jseg = *e; + } + } + tl_assert (jseg != 0); + + Vertex *v1 = jseg->left ()->opposite (jseg); + Edge *s1 = jseg->left ()->opposite (vertex); + Vertex *v2 = jseg->right ()->opposite (jseg); + Edge *s2 = jseg->right ()->opposite (vertex); + + Edge *jseg_opp = 0; + for (auto e = vertex->begin_edges (); e != vertex->end_edges () && !jseg_opp; ++e) { + if (!(*e)->has_polygon (jseg->left ()) && !(*e)->has_polygon (jseg->right ())) { + jseg_opp = *e; + } + } + + Edge *s1opp = jseg_opp->left ()->opposite (vertex); + Edge *s2opp = jseg_opp->right ()->opposite (vertex); + + Edge *new_edge = mp_graph->create_edge (v1, v2); + Polygon *t1 = mp_graph->create_triangle (s1, s2, new_edge); + Polygon *t2 = mp_graph->create_triangle (s1opp, s2opp, new_edge); + + triangles_to_fix.insert (t1); + triangles_to_fix.insert (t2); + + make_new_triangle = false; + + } + + auto to_remove = vertex->polygons (); + + std::vector outer_edges; + for (auto t = to_remove.begin (); t != to_remove.end (); ++t) { + outer_edges.push_back ((*t)->opposite (vertex)); + } + + if (make_new_triangle) { + + tl_assert (outer_edges.size () == size_t (3)); + + Polygon *nt = mp_graph->create_triangle (outer_edges[0], outer_edges[1], outer_edges[2]); + triangles_to_fix.insert (nt); + + } + + for (auto t = to_remove.begin (); t != to_remove.end (); ++t) { + triangles_to_fix.erase (*t); + mp_graph->remove_polygon (*t); + } + + if (new_triangles_out) { + for (auto t = triangles_to_fix.begin (); t != triangles_to_fix.end (); ++t) { + new_triangles_out->push_back (*t); + } + } + + std::vector to_fix_a (triangles_to_fix.begin (), triangles_to_fix.end ()); + fix_triangles (to_fix_a, std::vector (), new_triangles_out); +} + +void +Triangulation::fix_triangles (const std::vector &tris, const std::vector &fixed_edges, std::list > *new_triangles) +{ + m_level += 1; + for (auto e = fixed_edges.begin (); e != fixed_edges.end (); ++e) { + (*e)->set_level (m_level); + } + + std::set queue, todo; + + for (auto t = tris.begin (); t != tris.end (); ++t) { + for (int i = 0; i < 3; ++i) { + Edge *e = (*t)->edge (i); + if (e->level () < m_level && ! e->is_segment ()) { + queue.insert (e); + } + } + } + + while (! queue.empty ()) { + + todo.clear (); + todo.swap (queue); + + // NOTE: we cannot be sure that already treated edges will not become + // illegal by neighbor edges flipping .. + // for s in todo: + // s.level = self.level + + for (auto e = todo.begin (); e != todo.end (); ++e) { + + if (is_illegal_edge (*e)) { + + queue.erase (*e); + + auto pp = flip (*e); + auto t1 = pp.first.first; + auto t2 = pp.first.second; + auto s12 = pp.second; + + if (new_triangles) { + new_triangles->push_back (t1); + new_triangles->push_back (t2); + } + + ++m_flips; + tl_assert (! is_illegal_edge (s12)); // TODO: remove later! + + for (int i = 0; i < 3; ++i) { + Edge *s1 = t1->edge (i); + if (s1->level () < m_level && ! s1->is_segment ()) { + queue.insert (s1); + } + } + + for (int i = 0; i < 3; ++i) { + Edge *s2 = t2->edge (i); + if (s2->level () < m_level && ! s2->is_segment ()) { + queue.insert (s2); + } + } + + } + + } + + } +} + +bool +Triangulation::is_illegal_edge (Edge *edge) +{ + Polygon *left = edge->left (); + Polygon *right = edge->right (); + if (!left || !right) { + return false; + } + + bool ok = false; + + auto lr = left->circumcircle (&ok); + if (! ok || right->opposite (edge)->in_circle (lr.first, lr.second) > 0) { + return true; + } + + auto rr = right->circumcircle(&ok); + if (! ok || left->opposite (edge)->in_circle (rr.first, rr.second) > 0) { + return true; + } + + return false; +} + +std::pair, Edge *> +Triangulation::flip (Edge *edge) +{ + Polygon *t1 = edge->left (); + Polygon *t2 = edge->right (); + + bool outside = t1->is_outside (); + tl_assert (t1->is_outside () == outside); + + // prepare for the new triangle to replace this one + t1->unlink (); + t2->unlink (); + + Vertex *t1_vext = t1->opposite (edge); + Edge *t1_sext1 = t1->find_edge_with (t1_vext, edge->v1 ()); + Edge *t1_sext2 = t1->find_edge_with (t1_vext, edge->v2 ()); + + Vertex *t2_vext = t2->opposite (edge); + Edge *t2_sext1 = t2->find_edge_with (t2_vext, edge->v1 ()); + Edge *t2_sext2 = t2->find_edge_with (t2_vext, edge->v2 ()); + + Edge *s_new = mp_graph->create_edge (t1_vext, t2_vext); + + Polygon *t1_new = mp_graph->create_triangle (s_new, t1_sext1, t2_sext1); + t1_new->set_outside (outside); + Polygon *t2_new = mp_graph->create_triangle (s_new, t1_sext2, t2_sext2); + t2_new->set_outside (outside); + + mp_graph->remove_polygon (t1); + mp_graph->remove_polygon (t2); + + return std::make_pair (std::make_pair (t1_new, t2_new), s_new); +} + +std::vector +Triangulation::fill_concave_corners (const std::vector &edges) +{ + std::vector res; + std::vector points, terminals; + + std::map > vertex2edge; + for (auto e = edges.begin (); e != edges.end (); ++e) { + + auto i = vertex2edge.insert (std::make_pair ((*e)->v1 (), std::vector ())); + if (i.second) { + points.push_back ((*e)->v1 ()); + } + i.first->second.push_back (*e); + + i = vertex2edge.insert (std::make_pair ((*e)->v2 (), std::vector ())); + if (i.second) { + points.push_back ((*e)->v2 ()); + } + i.first->second.push_back (*e); + + } + + while (points.size () > size_t (2)) { + + terminals.clear (); + for (auto p = points.begin (); p != points.end (); ++p) { + if (vertex2edge [*p].size () == 1) { + terminals.push_back (*p); + } + } + tl_assert (terminals.size () == size_t (2)); + Vertex *v = terminals[0]; + + bool any_connected = false; + Vertex *vp = 0; + + std::set to_remove; + + while (vertex2edge [v].size () >= size_t (2) || ! vp) { + + Edge *seg = 0; + std::vector &ee = vertex2edge [v]; + for (auto e = ee.begin (); e != ee.end (); ++e) { + if (! (*e)->has_vertex (vp)) { + seg = (*e); + break; + } + } + + tl_assert (seg != 0); + Polygon *tri = seg->left () ? seg->left () : seg->right (); + Vertex *vn = seg->other (v); + + std::vector &een = vertex2edge [vn]; + if (een.size () < size_t (2)) { + break; + } + tl_assert (een.size () == size_t (2)); + + Edge *segn = 0; + for (auto e = een.begin (); e != een.end (); ++e) { + if (! (*e)->has_vertex (v)) { + segn = (*e); + break; + } + } + + tl_assert (segn != 0); + Vertex *vnn = segn->other (vn); + std::vector &eenn = vertex2edge [vnn]; + + // NOTE: tri can be None in case a lonely edge stays after removing + // attached triangles + if (! tri || seg->side_of (*vnn) * seg->side_of (*tri->opposite (seg)) < 0) { + + // is concave + Edge *new_edge = mp_graph->create_edge (v, vnn); + for (auto e = ee.begin (); e != ee.end (); ++e) { + if (*e == seg) { + ee.erase (e); + break; + } + } + ee.push_back (new_edge); + + for (auto e = eenn.begin (); e != eenn.end (); ++e) { + if (*e == segn) { + eenn.erase (e); + break; + } + } + eenn.push_back (new_edge); + + vertex2edge.erase (vn); + to_remove.insert (vn); + + Polygon *new_triangle = mp_graph->create_triangle (seg, segn, new_edge); + res.push_back (new_triangle); + any_connected = true; + + } else { + + vp = v; + v = vn; + + } + + } + + if (! any_connected) { + break; + } + + std::vector::iterator wp = points.begin (); + for (auto p = points.begin (); p != points.end (); ++p) { + if (to_remove.find (*p) == to_remove.end ()) { + *wp++ = *p; + } + } + points.erase (wp, points.end ()); + + } + + return res; +} + +std::vector +Triangulation::search_edges_crossing (Vertex *from, Vertex *to) +{ + Vertex *v = from; + Vertex *vv = to; + db::DEdge edge (*from, *to); + + Polygon *current_triangle = 0; + Edge *next_edge = 0; + + std::vector result; + + for (auto e = v->begin_edges (); e != v->end_edges () && ! next_edge; ++e) { + for (auto t = (*e)->begin_polygons (); t != (*e)->end_polygons (); ++t) { + Edge *os = t->opposite (v); + if (os->has_vertex (vv)) { + return result; + } + if (os->crosses (edge)) { + result.push_back (os); + current_triangle = t.operator-> (); + next_edge = os; + break; + } + } + } + + tl_assert (current_triangle != 0); + + while (true) { + + current_triangle = next_edge->other (current_triangle); + + // Note that we're convex, so there has to be a path across triangles + tl_assert (current_triangle != 0); + + Edge *cs = next_edge; + next_edge = 0; + for (int i = 0; i < 3; ++i) { + Edge *e = current_triangle->edge (i); + if (e != cs) { + if (e->has_vertex (vv)) { + return result; + } + if (e->crosses (edge)) { + result.push_back (e); + next_edge = e; + break; + } + } + } + + tl_assert (next_edge != 0); + + } +} + +Vertex * +Triangulation::find_vertex_for_point (const db::DPoint &point) +{ + Edge *edge = find_closest_edge (point); + if (!edge) { + return 0; + } + Vertex *v = 0; + if (is_equal (*edge->v1 (), point)) { + v = edge->v1 (); + } else if (is_equal (*edge->v2 (), point)) { + v = edge->v2 (); + } + return v; +} + +Edge * +Triangulation::find_edge_for_points (const db::DPoint &p1, const db::DPoint &p2) +{ + Vertex *v = find_vertex_for_point (p1); + if (!v) { + return 0; + } + for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { + if (is_equal (*(*e)->other (v), p2)) { + return *e; + } + } + return 0; +} + +std::vector +Triangulation::ensure_edge_inner (Vertex *from, Vertex *to) +{ + auto crossed_edges = search_edges_crossing (from, to); + std::vector result; + + if (crossed_edges.empty ()) { + + // no crossing edge - there should be a edge already + Edge *res = find_edge_for_points (*from, *to); + tl_assert (res != 0); + result.push_back (res); + + } else if (crossed_edges.size () == 1) { + + // can be solved by flipping + auto pp = flip (crossed_edges.front ()); + Edge *res = pp.second; + tl_assert (res->has_vertex (from) && res->has_vertex (to)); + result.push_back (res); + + } else { + + // split edge close to center + db::DPoint split_point; + double d = -1.0; + double l_half = 0.25 * (*to - *from).sq_length (); + for (auto e = crossed_edges.begin (); e != crossed_edges.end (); ++e) { + db::DPoint p = (*e)->intersection_point (db::DEdge (*from, *to)); + double dp = fabs ((p - *from).sq_length () - l_half); + if (d < 0.0 || dp < d) { + dp = d; + split_point = p; + } + } + + Vertex *split_vertex = insert_point (split_point); + + result = ensure_edge_inner (from, split_vertex); + + auto result2 = ensure_edge_inner (split_vertex, to); + result.insert (result.end (), result2.begin (), result2.end ()); + + } + + return result; +} + +std::vector +Triangulation::ensure_edge (Vertex *from, Vertex *to) +{ +#if 0 + // NOTE: this should not be required if the original segments are non-overlapping + // TODO: this is inefficient + for v in self.vertexes: + if edge.point_on(v): + return self.ensure_edge(Edge(edge.p1, v)) + self.ensure_edge(Edge(v, edge.p2)) +#endif + + auto edges = ensure_edge_inner (from, to); + for (auto e = edges.begin (); e != edges.end (); ++e) { + // mark the edges as fixed "forever" so we don't modify them when we ensure other edges + (*e)->set_level (std::numeric_limits::max ()); + } + return edges; +} + +void +Triangulation::join_edges (std::vector &edges) +{ + // edges are supposed to be ordered + for (size_t i = 1; i < edges.size (); ) { + + Edge *s1 = edges [i - 1]; + Edge *s2 = edges [i]; + tl_assert (s1->is_segment () == s2->is_segment ()); + Vertex *cp = s1->common_vertex (s2); + tl_assert (cp != 0); + + std::vector join_edges; + for (auto e = cp->begin_edges (); e != cp->end_edges (); ++e) { + if (*e != s1 && *e != s2) { + if ((*e)->can_join_via (cp)) { + join_edges.push_back (*e); + } else { + join_edges.clear (); + break; + } + } + } + + if (! join_edges.empty ()) { + + tl_assert (join_edges.size () <= 2); + + Edge *new_edge = mp_graph->create_edge (s1->other (cp), s2->other (cp)); + new_edge->set_is_segment (s1->is_segment ()); + + for (auto js = join_edges.begin (); js != join_edges.end (); ++js) { + + Polygon *t1 = (*js)->left (); + Polygon *t2 = (*js)->right (); + Edge *tedge1 = t1->opposite (cp); + Edge *tedge2 = t2->opposite (cp); + t1->unlink (); + t2->unlink (); + Polygon *tri = mp_graph->create_triangle (tedge1, tedge2, new_edge); + tri->set_outside (t1->is_outside ()); + mp_graph->remove_polygon (t1); + mp_graph->remove_polygon (t2); + } + + edges [i - 1] = new_edge; + edges.erase (edges.begin () + i); + + } else { + ++i; + } + + } +} + +void +Triangulation::constrain (const std::vector > &contours) +{ + tl_assert (! m_is_constrained); + + std::vector > > resolved_edges; + + for (auto c = contours.begin (); c != contours.end (); ++c) { + for (auto v = c->begin (); v != c->end (); ++v) { + auto vv = v; + ++vv; + if (vv == c->end ()) { + vv = c->begin (); + } + db::DEdge e (**v, **vv); + resolved_edges.push_back (std::make_pair (e, std::vector ())); + resolved_edges.back ().second = ensure_edge (*v, *vv); + } + } + + for (auto tri = mp_graph->polygons ().begin (); tri != mp_graph->polygons ().end (); ++tri) { + tri->set_outside (false); + for (int i = 0; i < 3; ++i) { + tri->edge (i)->set_is_segment (false); + } + } + + std::set new_tri; + + for (auto re = resolved_edges.begin (); re != resolved_edges.end (); ++re) { + auto edge = re->first; + auto edges = re->second; + for (auto e = edges.begin (); e != edges.end (); ++e) { + (*e)->set_is_segment (true); + Polygon *outer_tri = 0; + int d = db::sprod_sign (edge.d (), (*e)->d ()); + if (d > 0) { + outer_tri = (*e)->left (); + } + if (d < 0) { + outer_tri = (*e)->right (); + } + if (outer_tri) { + new_tri.insert (outer_tri); + outer_tri->set_outside (true); + } + } + } + + while (! new_tri.empty ()) { + + std::set next_tris; + + for (auto tri = new_tri.begin (); tri != new_tri.end (); ++tri) { + for (int i = 0; i < 3; ++i) { + auto e = (*tri)->edge (i); + if (! e->is_segment ()) { + auto ot = e->other (*tri); + if (ot && ! ot->is_outside ()) { + next_tris.insert (ot); + ot->set_outside (true); + } + } + } + } + + new_tri.swap (next_tris); + + } + + // join edges where possible + for (auto re = resolved_edges.begin (); re != resolved_edges.end (); ++re) { + auto edges = re->second; + join_edges (edges); + } + + m_is_constrained = true; +} + +void +Triangulation::remove_outside_triangles () +{ + tl_assert (m_is_constrained); + + // NOTE: don't remove while iterating + std::vector to_remove; + for (auto tri = mp_graph->begin (); tri != mp_graph->end (); ++tri) { + if (tri->is_outside ()) { + to_remove.push_back (const_cast (tri.operator-> ())); + } + } + + for (auto t = to_remove.begin (); t != to_remove.end (); ++t) { + mp_graph->remove_polygon (*t); + } +} + + +template +void +Triangulation::make_contours (const Poly &poly, const Trans &trans, std::vector > &edge_contours) +{ + edge_contours.push_back (std::vector ()); + for (auto pt = poly.begin_hull (); pt != poly.end_hull (); ++pt) { + edge_contours.back ().push_back (insert_point (trans * *pt)); + } + + for (unsigned int h = 0; h < poly.holes (); ++h) { + edge_contours.push_back (std::vector ()); + for (auto pt = poly.begin_hole (h); pt != poly.end_hole (h); ++pt) { + edge_contours.back ().push_back (insert_point (trans * *pt)); + } + } +} + +void +Triangulation::create_constrained_delaunay (const db::Region ®ion, const CplxTrans &trans) +{ + clear (); + + std::vector > edge_contours; + + for (auto p = region.begin_merged (); ! p.at_end (); ++p) { + make_contours (*p, trans, edge_contours); + } + + constrain (edge_contours); +} + +void +Triangulation::create_constrained_delaunay (const db::Polygon &p, const std::vector &vertexes, const CplxTrans &trans) +{ + clear (); + + for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { + insert_point (trans * *v)->set_is_precious (true); + } + + std::vector > edge_contours; + make_contours (p, trans, edge_contours); + + constrain (edge_contours); +} + +void +Triangulation::create_constrained_delaunay (const db::DPolygon &p, const std::vector &vertexes, const DCplxTrans &trans) +{ + clear (); + + for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { + insert_point (trans * *v)->set_is_precious (true); + } + + std::vector > edge_contours; + make_contours (p, trans, edge_contours); + + constrain (edge_contours); +} + +static bool is_skinny (const Polygon *tri, const TriangulationParameters ¶m) +{ + if (param.min_b < db::epsilon) { + return false; + } else { + double b = tri->b (); + double delta = (b + param.min_b) * db::epsilon; + return b < param.min_b - delta; + } +} + +static bool is_invalid (const Polygon *tri, const TriangulationParameters ¶m) +{ + if (is_skinny (tri, param)) { + return true; + } + + double amax = param.max_area; + if (param.max_area_border > db::epsilon) { + if (tri->has_segment ()) { + amax = param.max_area_border; + } + } + + if (amax > db::epsilon) { + double a = tri->area (); + double delta = (a + amax) * db::epsilon; + return tri->area () > amax + delta; + } else { + return false; + } +} + +void +Triangulation::triangulate (const db::Region ®ion, const TriangulationParameters ¶meters, double dbu) +{ + tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); + + create_constrained_delaunay (region, db::CplxTrans (dbu)); + refine (parameters); +} + +void +Triangulation::triangulate (const db::Region ®ion, const TriangulationParameters ¶meters, const db::CplxTrans &trans) +{ + tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); + + create_constrained_delaunay (region, trans); + refine (parameters); +} + +void +Triangulation::triangulate (const db::Polygon &poly, const TriangulationParameters ¶meters, double dbu) +{ + triangulate (poly, std::vector (), parameters, dbu); +} + +void +Triangulation::triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulationParameters ¶meters, double dbu) +{ + tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); + + create_constrained_delaunay (poly, vertexes, db::CplxTrans (dbu)); + refine (parameters); +} + +void +Triangulation::triangulate (const db::Polygon &poly, const TriangulationParameters ¶meters, const db::CplxTrans &trans) +{ + triangulate (poly, std::vector (), parameters, trans); +} + +void +Triangulation::triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulationParameters ¶meters, const db::CplxTrans &trans) +{ + tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); + + create_constrained_delaunay (poly, vertexes, trans); + refine (parameters); +} + +void +Triangulation::triangulate (const db::DPolygon &poly, const TriangulationParameters ¶meters, const DCplxTrans &trans) +{ + triangulate (poly, std::vector (), parameters, trans); +} + +void +Triangulation::triangulate (const db::DPolygon &poly, const std::vector &vertexes, const TriangulationParameters ¶meters, const DCplxTrans &trans) +{ + tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); + + create_constrained_delaunay (poly, vertexes, trans); + refine (parameters); +} + +void +Triangulation::refine (const TriangulationParameters ¶meters) +{ + if (parameters.min_b < db::epsilon && parameters.max_area < db::epsilon && parameters.max_area_border < db::epsilon) { + + // no refinement requested - we're done. + remove_outside_triangles (); + return; + + } + + unsigned int nloop = 0; + std::list > new_triangles; + for (auto t = mp_graph->polygons ().begin (); t != mp_graph->polygons ().end (); ++t) { + new_triangles.push_back (t.operator-> ()); + } + + // TODO: break if iteration gets stuck + while (nloop < parameters.max_iterations) { + + ++nloop; + if (tl::verbosity () >= parameters.base_verbosity + 10) { + tl::info << "Iteration " << nloop << " .."; + } + + std::list > to_consider; + for (auto t = new_triangles.begin (); t != new_triangles.end (); ++t) { + if (t->get () && ! (*t)->is_outside () && is_invalid (t->get (), parameters)) { + to_consider.push_back (*t); + } + } + + if (to_consider.empty ()) { + break; + } + + if (tl::verbosity () >= parameters.base_verbosity + 10) { + tl::info << to_consider.size() << " triangles to consider"; + } + + new_triangles.clear (); + + for (auto t = to_consider.begin (); t != to_consider.end (); ++t) { + + if (! t->get ()) { + // triangle got removed during loop + continue; + } + + auto cr = (*t)->circumcircle(); + auto center = cr.first; + + int s = (*t)->contains (center); + if (s >= 0) { + + if (s > 0) { + + double snap = 1e-3; + + // Snap the center to a segment center if "close" to it. + // This avoids generating very skinny triangles that can't be fixed as the + // segment cannot be flipped. This a part of the issue #1996 problem. + for (unsigned int i = 0; i < 3; ++i) { + if ((*t)->edge (i)->is_segment ()) { + auto e = (*t)->edge (i)->edge (); + auto c = e.p1 () + e.d () * 0.5; + if (c.distance (center) < e.length () * 0.5 * snap - db::epsilon) { + center = c; + break; + } + } + } + + } + + if (tl::verbosity () >= parameters.base_verbosity + 20) { + tl::info << "Inserting in-triangle center " << center.to_string () << " of " << (*t)->to_string (true); + } + insert_point (center, &new_triangles); + + } else { + + Vertex *vstart = 0; + for (unsigned int i = 0; i < 3; ++i) { + Edge *edge = (*t)->edge (i); + vstart = (*t)->opposite (edge); + if (edge->side_of (*vstart) * edge->side_of (center) < 0) { + break; + } + } + + Edge *edge = find_closest_edge (center, vstart, true /*inside only*/); + tl_assert (edge != 0); + + if (! edge->is_segment () || edge->side_of (*vstart) * edge->side_of (center) >= 0) { + + if (tl::verbosity () >= parameters.base_verbosity + 20) { + tl::info << "Inserting out-of-triangle center " << center << " of " << (*t)->to_string (true); + } + insert_point (center, &new_triangles); + + } else { + + double sr = edge->d ().length () * 0.5; + if (sr >= parameters.min_length) { + + db::DPoint pnew = *edge->v1 () + edge->d () * 0.5; + + if (tl::verbosity () >= parameters.base_verbosity + 20) { + tl::info << "Split edge " << edge->to_string (true) << " at " << pnew.to_string (); + } + Vertex *vnew = insert_point (pnew, &new_triangles); + auto vertexes_in_diametral_circle = find_points_around (vnew, sr); + + std::vector to_delete; + for (auto v = vertexes_in_diametral_circle.begin (); v != vertexes_in_diametral_circle.end (); ++v) { + bool has_segment = false; + for (auto e = (*v)->begin_edges (); e != (*v)->end_edges () && ! has_segment; ++e) { + has_segment = (*e)->is_segment (); + } + if (! has_segment && ! (*v)->is_precious ()) { + to_delete.push_back (*v); + } + } + + if (tl::verbosity () >= parameters.base_verbosity + 20) { + tl::info << " -> found " << to_delete.size () << " vertexes to remove"; + } + for (auto v = to_delete.begin (); v != to_delete.end (); ++v) { + remove (*v, &new_triangles); + } + + } + + } + + } + + } + + } + + if (tl::verbosity () >= parameters.base_verbosity + 20) { + tl::info << "Finishing .."; + } + + if (parameters.mark_triangles) { + + for (auto t = mp_graph->begin (); t != mp_graph->end (); ++t) { + + size_t id = 0; + + if (! t->is_outside ()) { + + if (is_skinny (t.operator-> (), parameters)) { + id |= 1; + } + if (is_invalid (t.operator-> (), parameters)) { + id |= 2; + } + auto cp = t->circumcircle (); + auto vi = find_inside_circle (cp.first, cp.second); + if (! vi.empty ()) { + id |= 4; + } + + } + + (const_cast (t.operator->()))->set_id (id); + + } + + } + + remove_outside_triangles (); +} + +} // namespace plc + +} // namespace db diff --git a/src/db/db/dbPLCTriangulation.h b/src/db/db/dbPLCTriangulation.h new file mode 100644 index 000000000..b3e941f89 --- /dev/null +++ b/src/db/db/dbPLCTriangulation.h @@ -0,0 +1,306 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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_dbPLCTriangulation +#define HDR_dbPLCTriangulation + +#include "dbCommon.h" +#include "dbPLC.h" + +#include +#include +#include +#include + +namespace db +{ + +namespace plc +{ + +struct DB_PUBLIC TriangulationParameters +{ + TriangulationParameters () + : min_b (1.0), + min_length (0.0), + max_area (0.0), + max_area_border (0.0), + max_iterations (std::numeric_limits::max ()), + base_verbosity (30), + mark_triangles (false) + { } + + /** + * @brief Min. readius-to-shortest edge ratio + */ + double min_b; + + /** + * @brief Min. edge length + * + * This parameter does not provide a guarantee about a minimume edge length, but + * helps avoiding ever-reducing triangle splits in acute corners of the input polygon. + * Splitting of edges stops when the edge is less than the min length. + */ + double min_length; + + /** + * @brief Max area or zero for "no constraint" + */ + double max_area; + + /** + * @brief Max area for border triangles or zero for "use max_area" + */ + double max_area_border; + + /** + * @brief Max number of iterations + */ + size_t max_iterations; + + /** + * @brief The verbosity level above which triangulation reports details + */ + int base_verbosity; + + /** + * @brief If true, final triangles are marked using the "id" integer as a bit field + * + * This provides information about the result quality. + * + * Bit 0: skinny triangle + * Bit 1: bad-quality (skinny or area too large) + * Bit 2: non-Delaunay (in the strict sense) + */ + bool mark_triangles; +}; + +/** + * @brief A Triangulation algorithm + * + * This class implements a constrained refined Delaunay triangulation using Chew's algorithm. + */ +class DB_PUBLIC Triangulation +{ +public: + /** + * @brief The constructor + * + * The graph will be one filled by the triangulation. + */ + Triangulation (Graph *graph); + + /** + * @brief Clears the triangulation + */ + void clear (); + + /** + * @brief Initializes the triangle collection with a box + * Two triangles will be created. + */ + void init_box (const db::DBox &box); + + /** + * @brief Creates a refined Delaunay triangulation for the given region + * + * The database unit should be chosen in a way that target area values are "in the order of 1". + * For inputs featuring acute angles (angles < ~25 degree), the parameters should defined a min + * edge length ("min_length"). + * "min_length" should be at least 1e-4. If a min edge length is given, the max area constaints + * may not be satisfied. + * + * Edges in the input should not be shorter than 1e-4. + */ + void triangulate (const db::Region ®ion, const TriangulationParameters ¶meters, double dbu = 1.0); + + // more versions + void triangulate (const db::Region ®ion, const TriangulationParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); + void triangulate (const db::Polygon &poly, const TriangulationParameters ¶meters, double dbu = 1.0); + void triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulationParameters ¶meters, double dbu = 1.0); + void triangulate (const db::Polygon &poly, const TriangulationParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); + void triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulationParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); + + /** + * @brief Triangulates a floating-point polygon + */ + void triangulate (const db::DPolygon &poly, const TriangulationParameters ¶meters, const db::DCplxTrans &trans = db::DCplxTrans ()); + void triangulate (const db::DPolygon &poly, const std::vector &vertexes, const TriangulationParameters ¶meters, const db::DCplxTrans &trans = db::DCplxTrans ()); + + /** + * @brief Inserts a new vertex as the given point + * + * If "new_triangles" is not null, it will receive the list of new triangles created during + * the remove step. + * + * This method can be called after "triangulate" to add new points and adjust the triangulation. + * Inserting new points will maintain the (constrained) Delaunay condition. + */ + Vertex *insert_point (const db::DPoint &point, std::list > *new_triangles = 0); + + /** + * @brief Statistics: number of flips (fixing) + */ + size_t flips () const + { + return m_flips; + } + + /** + * @brief Statistics: number of hops (searching) + */ + size_t hops () const + { + return m_hops; + } + +protected: + /** + * @brief Checks the polygon graph for consistency + * This method is for testing purposes mainly. + */ + bool check (bool check_delaunay = true) const; + + /** + * @brief Finds the points within (not "on") a circle of radius "radius" around the given vertex. + */ + std::vector find_points_around (Vertex *vertex, double radius); + + /** + * @brief Inserts a new vertex as the given point + * + * If "new_triangles" is not null, it will receive the list of new triangles created during + * the remove step. + */ + Vertex *insert_point (db::DCoord x, db::DCoord y, std::list > *new_triangles = 0); + + /** + * @brief Removes the given vertex + * + * If "new_triangles" is not null, it will receive the list of new triangles created during + * the remove step. + */ + void remove (Vertex *vertex, std::list > *new_triangles = 0); + + /** + * @brief Flips the given edge + */ + std::pair, Edge *> flip (Edge *edge); + + /** + * @brief Finds all edges that cross the given one for a convex triangulation + * + * Requirements: + * * self must be a convex triangulation + * * edge must not contain another vertex from the triangulation except p1 and p2 + */ + std::vector search_edges_crossing (Vertex *from, Vertex *to); + + /** + * @brief Finds the edge for two given points + */ + Edge *find_edge_for_points (const db::DPoint &p1, const db::DPoint &p2); + + /** + * @brief Finds the vertex for a point + */ + Vertex *find_vertex_for_point (const db::DPoint &pt); + + /** + * @brief Ensures all points between from an to are connected by edges and makes these segments + */ + std::vector ensure_edge (Vertex *from, Vertex *to); + + /** + * @brief Given a set of contours with edges, mark outer triangles + * + * The edges must be made from existing vertexes. Edge orientation is + * clockwise. + * + * This will also mark triangles as outside ones. + */ + void constrain (const std::vector > &contours); + + /** + * @brief Removes the outside triangles. + */ + void remove_outside_triangles (); + + /** + * @brief Creates a constrained Delaunay triangulation from the given Region + */ + void create_constrained_delaunay (const db::Region ®ion, const db::CplxTrans &trans = db::CplxTrans ()); + + /** + * @brief Creates a constrained Delaunay triangulation from the given Polygon + */ + void create_constrained_delaunay (const db::Polygon &poly, const std::vector &vertexes, const db::CplxTrans &trans = db::CplxTrans ()); + + /** + * @brief Creates a constrained Delaunay triangulation from the given DPolygon + */ + void create_constrained_delaunay (const db::DPolygon &poly, const std::vector &vertexes, const DCplxTrans &trans); + + /** + * @brief Returns a value indicating whether the edge is "illegal" (violates the Delaunay criterion) + */ + static bool is_illegal_edge (Edge *edge); + + // NOTE: these functions are SLOW and intended to test purposes only + std::vector find_touching (const db::DBox &box) const; + std::vector find_inside_circle (const db::DPoint ¢er, double radius) const; + +private: + Graph *mp_graph; + bool m_is_constrained; + size_t m_level; + size_t m_id; + size_t m_flips, m_hops; + + template void make_contours (const Poly &poly, const Trans &trans, std::vector > &contours); + + void remove_outside_vertex (Vertex *vertex, std::list > *new_triangles = 0); + void remove_inside_vertex (Vertex *vertex, std::list > *new_triangles_out = 0); + std::vector fill_concave_corners (const std::vector &edges); + void fix_triangles (const std::vector &tris, const std::vector &fixed_edges, std::list > *new_triangles); + std::vector find_triangle_for_point (const db::DPoint &point); + Edge *find_closest_edge (const db::DPoint &p, Vertex *vstart = 0, bool inside_only = false); + Vertex *insert (Vertex *vertex, std::list > *new_triangles = 0); + void split_triangle (Polygon *t, Vertex *vertex, std::list > *new_triangles_out); + void split_triangles_on_edge (Vertex *vertex, Edge *split_edge, std::list > *new_triangles_out); + void add_more_triangles (std::vector &new_triangles, + Edge *incoming_edge, + Vertex *from_vertex, Vertex *to_vertex, + Edge *conn_edge); + void insert_new_vertex(Vertex *vertex, std::list > *new_triangles_out); + std::vector ensure_edge_inner (Vertex *from, Vertex *to); + void join_edges (std::vector &edges); + void refine (const TriangulationParameters ¶m); +}; + +} // namespace plc + +} // namespace db + +#endif + diff --git a/src/db/unit_tests/dbPolygonGraphTests.cc b/src/db/unit_tests/dbPLCGraphTest.cc similarity index 76% rename from src/db/unit_tests/dbPolygonGraphTests.cc rename to src/db/unit_tests/dbPLCGraphTest.cc index edf152942..e02449401 100644 --- a/src/db/unit_tests/dbPolygonGraphTests.cc +++ b/src/db/unit_tests/dbPLCGraphTest.cc @@ -21,31 +21,21 @@ */ -#include "dbPolygonGraph.h" +#include "dbPLC.h" #include "tlUnitTest.h" #include #include -class TestablePolygonGraph - : public db::PolygonGraph -{ -public: - using db::PolygonGraph::PolygonGraph; - // @@@ using db::PolygonGraph::check; - using db::PolygonGraph::dump; -}; - - TEST(basic) { db::DBox box (0, 0, 100.0, 200.0); - TestablePolygonGraph pg; + db::plc::Graph plc; // @@@ pg.insert_polygon (db::DSimplePolygon (box)); // @@@ - tl::info << pg.to_string (); - pg.dump ("debug.gds"); // @@@ + tl::info << plc.to_string (); + plc.dump ("debug.gds"); // @@@ } diff --git a/src/db/unit_tests/dbPLCTriangulationTests.cc b/src/db/unit_tests/dbPLCTriangulationTests.cc new file mode 100644 index 000000000..0d58ffc04 --- /dev/null +++ b/src/db/unit_tests/dbPLCTriangulationTests.cc @@ -0,0 +1,1117 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "dbPLCTriangulation.h" +#include "dbWriter.h" +#include "dbRegionProcessors.h" +#include "tlUnitTest.h" +#include "tlStream.h" +#include "tlFileUtils.h" + +#include +#include +#include +#include + +class TestableTriangulation + : public db::plc::Triangulation +{ +public: + using db::plc::Triangulation::Triangulation; + using db::plc::Triangulation::check; + using db::plc::Triangulation::flip; + using db::plc::Triangulation::insert_point; + using db::plc::Triangulation::search_edges_crossing; + using db::plc::Triangulation::find_edge_for_points; + using db::plc::Triangulation::find_points_around; + using db::plc::Triangulation::find_inside_circle; + using db::plc::Triangulation::create_constrained_delaunay; + using db::plc::Triangulation::is_illegal_edge; + using db::plc::Triangulation::find_vertex_for_point; + using db::plc::Triangulation::remove; + using db::plc::Triangulation::ensure_edge; + using db::plc::Triangulation::constrain; + using db::plc::Triangulation::remove_outside_triangles; +}; + +class TestableGraph + : public db::plc::Graph +{ +public: + using db::plc::Graph::Graph; + using db::plc::Graph::create_vertex; + using db::plc::Graph::create_edge; + using db::plc::Graph::create_triangle; +}; + +TEST(basic) +{ + db::plc::Graph plc; + TestableTriangulation tris (&plc); + tris.init_box (db::DBox (1, 0, 5, 4)); + + EXPECT_EQ (plc.bbox ().to_string (), "(1,0;5,4)"); + EXPECT_EQ (plc.to_string (), "((1, 0), (1, 4), (5, 0)), ((1, 4), (5, 4), (5, 0))"); + + EXPECT_EQ (tris.check (), true); + + tris.clear (); + + EXPECT_EQ (plc.bbox ().to_string (), "()"); + EXPECT_EQ (plc.to_string (), ""); + + EXPECT_EQ (tris.check (), true); +} + +TEST(flip) +{ + db::plc::Graph plc; + TestableTriangulation tris (&plc); + tris.init_box (db::DBox (0, 0, 1, 1)); + EXPECT_EQ (plc.to_string (), "((0, 0), (0, 1), (1, 0)), ((0, 1), (1, 1), (1, 0))"); + + EXPECT_EQ (plc.num_polygons (), size_t (2)); + EXPECT_EQ (tris.check (), true); + + const db::plc::Polygon &t1 = *plc.begin (); + db::plc::Edge *diag_segment; + for (int i = 0; i < 3; ++i) { + diag_segment = t1.edge (i); + if (diag_segment->side_of (db::DPoint (0.5, 0.5)) == 0) { + break; + } + } + tris.flip (diag_segment); + EXPECT_EQ (plc.to_string (), "((1, 1), (0, 0), (0, 1)), ((1, 1), (1, 0), (0, 0))"); + EXPECT_EQ (tris.check (), true); +} + +TEST(insert) +{ + db::plc::Graph plc; + TestableTriangulation tris (&plc); + tris.init_box (db::DBox (0, 0, 1, 1)); + + tris.insert_point (0.2, 0.2); + EXPECT_EQ (plc.to_string (), "((0, 0), (0, 1), (0.2, 0.2)), ((1, 0), (0, 0), (0.2, 0.2)), ((1, 1), (0.2, 0.2), (0, 1)), ((1, 1), (1, 0), (0.2, 0.2))"); + EXPECT_EQ (tris.check (), true); +} + +TEST(split_segment) +{ + db::plc::Graph plc; + TestableTriangulation tris (&plc); + tris.init_box (db::DBox (0, 0, 1, 1)); + + tris.insert_point (0.5, 0.5); + EXPECT_EQ (plc.to_string (), "((1, 1), (1, 0), (0.5, 0.5)), ((1, 1), (0.5, 0.5), (0, 1)), ((0, 0), (0, 1), (0.5, 0.5)), ((0, 0), (0.5, 0.5), (1, 0))"); + EXPECT_EQ (tris.check(), true); +} + +TEST(insert_vertex_twice) +{ + db::plc::Graph plc; + TestableTriangulation tris (&plc); + tris.init_box (db::DBox (0, 0, 1, 1)); + + tris.insert_point (0.5, 0.5); + // inserted a vertex twice does not change anything + tris.insert_point (0.5, 0.5); + EXPECT_EQ (plc.to_string (), "((1, 1), (1, 0), (0.5, 0.5)), ((1, 1), (0.5, 0.5), (0, 1)), ((0, 0), (0, 1), (0.5, 0.5)), ((0, 0), (0.5, 0.5), (1, 0))"); + EXPECT_EQ (tris.check(), true); +} + +TEST(insert_vertex_convex) +{ + db::plc::Graph plc; + TestableTriangulation tris (&plc); + tris.insert_point (0.2, 0.2); + tris.insert_point (0.2, 0.8); + tris.insert_point (0.6, 0.5); + tris.insert_point (0.7, 0.5); + tris.insert_point (0.6, 0.4); + EXPECT_EQ (plc.to_string (), "((0.2, 0.2), (0.2, 0.8), (0.6, 0.5)), ((0.2, 0.8), (0.7, 0.5), (0.6, 0.5)), ((0.6, 0.4), (0.6, 0.5), (0.7, 0.5)), ((0.6, 0.4), (0.2, 0.2), (0.6, 0.5))"); + EXPECT_EQ (tris.check(), true); +} + +TEST(insert_vertex_convex2) +{ + db::plc::Graph plc; + TestableTriangulation tris (&plc); + tris.insert_point (0.25, 0.1); + tris.insert_point (0.1, 0.4); + tris.insert_point (0.4, 0.15); + tris.insert_point (1, 0.7); + EXPECT_EQ (plc.to_string (), "((0.25, 0.1), (0.1, 0.4), (0.4, 0.15)), ((1, 0.7), (0.4, 0.15), (0.1, 0.4))"); + EXPECT_EQ (tris.check(), true); +} + +TEST(insert_vertex_convex3) +{ + db::plc::Graph plc; + TestableTriangulation tris (&plc); + tris.insert_point (0.25, 0.5); + tris.insert_point (0.25, 0.55); + tris.insert_point (0.15, 0.8); + tris.insert_point (1, 0.4); + EXPECT_EQ (plc.to_string (), "((0.25, 0.5), (0.15, 0.8), (0.25, 0.55)), ((1, 0.4), (0.25, 0.5), (0.25, 0.55)), ((0.15, 0.8), (1, 0.4), (0.25, 0.55))"); + EXPECT_EQ (tris.check(), true); +} + +TEST(search_edges_crossing) +{ + db::plc::Graph plc; + TestableTriangulation tris (&plc); + db::plc::Vertex *v1 = tris.insert_point (0.2, 0.2); + db::plc::Vertex *v2 = tris.insert_point (0.2, 0.8); + db::plc::Vertex *v3 = tris.insert_point (0.6, 0.5); + /*db::plc::Vertex *v4 =*/ tris.insert_point (0.7, 0.5); + db::plc::Vertex *v5 = tris.insert_point (0.6, 0.4); + db::plc::Vertex *v6 = tris.insert_point (0.7, 0.2); + EXPECT_EQ (tris.check(), true); + + auto xedges = tris.search_edges_crossing (v2, v6); + + EXPECT_EQ (xedges.size (), size_t (2)); + auto s1 = tris.find_edge_for_points (*v1, *v3); + auto s2 = tris.find_edge_for_points (*v1, *v5); + EXPECT_EQ (std::find (xedges.begin (), xedges.end (), s1) != xedges.end (), true); + EXPECT_EQ (std::find (xedges.begin (), xedges.end (), s2) != xedges.end (), true); +} + +TEST(illegal_edge1) +{ + TestableGraph plc; + + db::plc::Vertex *v1 = plc.create_vertex (0, 0); + db::plc::Vertex *v2 = plc.create_vertex (1.6, 1.6); + db::plc::Vertex *v3 = plc.create_vertex (1, 2); + db::plc::Vertex *v4 = plc.create_vertex (2, 1); + + { + db::plc::Edge *e1 = plc.create_edge (v1, v3); + db::plc::Edge *e2 = plc.create_edge (v3, v4); + db::plc::Edge *e3 = plc.create_edge (v4, v1); + + plc.create_triangle (e1, e2, e3); + + db::plc::Edge *ee1 = plc.create_edge (v2, v3); + db::plc::Edge *ee2 = plc.create_edge (v4, v2); + + plc.create_triangle (ee1, e2, ee2); + + EXPECT_EQ (TestableTriangulation::is_illegal_edge (e2), true); + } + + { + // flipped + db::plc::Edge *e1 = plc.create_edge (v1, v2); + db::plc::Edge *e2 = plc.create_edge (v2, v3); + db::plc::Edge *e3 = plc.create_edge (v3, v1); + + plc.create_triangle (e1, e2, e3); + + db::plc::Edge *ee1 = plc.create_edge (v1, v4); + db::plc::Edge *ee2 = plc.create_edge (v4, v2); + + plc.create_triangle (ee1, ee2, e1); + + EXPECT_EQ (TestableTriangulation::is_illegal_edge (e2), false); + } +} + +TEST(illegal_edge2) +{ + TestableGraph plc; + + // numerical border case + db::plc::Vertex *v1 = plc.create_vertex (773.94756216690905, 114.45875269431208); + db::plc::Vertex *v2 = plc.create_vertex (773.29574734131643, 113.47402096138073); + db::plc::Vertex *v3 = plc.create_vertex (773.10652961562653, 114.25497975904504); + db::plc::Vertex *v4 = plc.create_vertex (774.08856345337881, 113.60495072750861); + + { + db::plc::Edge *e1 = plc.create_edge (v1, v2); + db::plc::Edge *e2 = plc.create_edge (v2, v4); + db::plc::Edge *e3 = plc.create_edge (v4, v1); + + plc.create_triangle (e1, e2, e3); + + db::plc::Edge *ee1 = plc.create_edge (v2, v3); + db::plc::Edge *ee2 = plc.create_edge (v3, v4); + + plc.create_triangle (ee1, ee2, e2); + + EXPECT_EQ (TestableTriangulation::is_illegal_edge (e2), false); + } + + { + // flipped + db::plc::Edge *e1 = plc.create_edge (v1, v2); + db::plc::Edge *e2 = plc.create_edge (v2, v3); + db::plc::Edge *e3 = plc.create_edge (v3, v1); + + plc.create_triangle (e1, e2, e3); + + db::plc::Edge *ee1 = plc.create_edge (v1, v4); + db::plc::Edge *ee2 = plc.create_edge (v4, v2); + + plc.create_triangle (ee1, ee2, e1); + + EXPECT_EQ (TestableTriangulation::is_illegal_edge (e1), false); + } +} + +// Returns a random float number between 0.0 and 1.0 +inline double flt_rand () +{ + return rand () * (1.0 / double (RAND_MAX)); +} + +namespace { + struct PointLessOp + { + bool operator() (const db::DPoint &a, const db::DPoint &b) const + { + return a.less (b); + } + }; +} + +TEST(insert_many) +{ + srand (0); + + db::plc::Graph plc; + TestableTriangulation tris (&plc); + double res = 65536.0; + + db::DBox bbox; + + unsigned int n = 200000; + for (unsigned int i = 0; i < n; ++i) { + double x = round (flt_rand () * res) * 0.0001; + double y = round (flt_rand () * res) * 0.0001; + tris.insert_point (x, y); + } + + // slow: EXPECT_EQ (tris.check (), true); + EXPECT_LT (double (tris.flips ()) / double (n), 3.1); + EXPECT_LT (double (tris.hops ()) / double (n), 23.0); +} + +TEST(heavy_insert) +{ + tl::info << "Running test_heavy_insert " << tl::noendl; + + for (unsigned int l = 0; l < 100; ++l) { + + srand (l); + tl::info << "." << tl::noendl; + + db::plc::Graph plc; + TestableTriangulation tris (&plc); + double res = 128.0; + + unsigned int n = rand () % 190 + 10; + + db::DBox bbox; + std::map vmap; + + for (unsigned int i = 0; i < n; ++i) { + double x = round (flt_rand () * res) * (1.0 / res); + double y = round (flt_rand () * res) * (1.0 / res); + db::plc::Vertex *v = tris.insert_point (x, y); + bbox += db::DPoint (x, y); + vmap.insert (std::make_pair (*v, false)); + } + + // not strictly true, but very likely with at least 10 vertexes: + EXPECT_GT (plc.num_polygons (), size_t (0)); + EXPECT_EQ (plc.bbox ().to_string (), bbox.to_string ()); + + bool ok = true; + for (auto t = plc.begin (); t != plc.end (); ++t) { + for (int i = 0; i < 3; ++i) { + auto f = vmap.find (*t->vertex (i)); + if (f == vmap.end ()) { + tl::error << "Could not identify triangle vertex " << t->vertex (i)->to_string () << " as inserted vertex"; + ok = false; + } else { + f->second = true; + } + } + } + for (auto m = vmap.begin (); m != vmap.end (); ++m) { + if (!m->second) { + tl::error << "Could not identify vertex " << m->first.to_string () << " with a triangle"; + ok = false; + } + } + EXPECT_EQ (ok, true); + + EXPECT_EQ (tris.check(), true); + + } + + tl::info << tl::endl << "done."; +} + +TEST(heavy_remove) +{ + tl::info << "Running test_heavy_remove " << tl::noendl; + + for (unsigned int l = 0; l < 100; ++l) { + + srand (l); + tl::info << "." << tl::noendl; + + db::plc::Graph plc; + TestableTriangulation tris (&plc); + double res = 128.0; + + unsigned int n = rand () % 190 + 10; + + for (unsigned int i = 0; i < n; ++i) { + double x = round (flt_rand () * res) * (1.0 / res); + double y = round (flt_rand () * res) * (1.0 / res); + tris.insert_point (x, y); + } + + EXPECT_EQ (tris.check(), true); + + std::set vset; + std::vector vertexes; + for (auto t = plc.begin (); t != plc.end (); ++t) { + for (int i = 0; i < 3; ++i) { + db::plc::Vertex *v = t->vertex (i); + if (vset.insert (v).second) { + vertexes.push_back (v); + } + } + } + + while (! vertexes.empty ()) { + + unsigned int n = rand () % (unsigned int) vertexes.size (); + db::plc::Vertex *v = vertexes [n]; + tris.remove (v); + vertexes.erase (vertexes.begin () + n); + + // just a few times as it wastes time otherwise + if (vertexes.size () % 10 == 0) { + EXPECT_EQ (tris.check (), true); + } + + } + + EXPECT_EQ (plc.num_polygons (), size_t (0)); + + } + + tl::info << tl::endl << "done."; +} + +TEST(ensure_edge) +{ + srand (0); + + db::plc::Graph plc; + TestableTriangulation tris (&plc); + double res = 128.0; + + db::DEdge ee[] = { + db::DEdge (0.25, 0.25, 0.25, 0.75), + db::DEdge (0.25, 0.75, 0.75, 0.75), + db::DEdge (0.75, 0.75, 0.75, 0.25), + db::DEdge (0.75, 0.25, 0.25, 0.25) + }; + + for (unsigned int i = 0; i < 200; ++i) { + double x = round (flt_rand () * res) * (1.0 / res); + double y = round (flt_rand () * res) * (1.0 / res); + bool ok = true; + for (unsigned int j = 0; j < sizeof (ee) / sizeof (ee[0]); ++j) { + if (ee[j].side_of (db::DPoint (x, y)) == 0) { + --i; + ok = false; + } + } + if (ok) { + tris.insert_point (x, y); + } + } + + for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) { + tris.insert_point (ee[i].p1 ()); + } + + EXPECT_EQ (tris.check (), true); + + for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) { + tris.ensure_edge (tris.find_vertex_for_point (ee[i].p1 ()), tris.find_vertex_for_point (ee[i].p2 ())); + } + + EXPECT_EQ (tris.check (false), true); + + double area_in = 0.0; + db::DBox clip_box; + for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) { + clip_box += ee[i].p1 (); + } + for (auto t = plc.begin (); t != plc.end (); ++t) { + if (clip_box.overlaps (t->bbox ())) { + EXPECT_EQ (t->bbox ().inside (clip_box), true); + area_in += t->area (); + } + } + + EXPECT_EQ (tl::to_string (area_in), "0.25"); +} + +static bool safe_inside (const db::DBox &b1, const db::DBox &b2) +{ + typedef db::coord_traits ct; + + return (ct::less (b2.left (), b1.left ()) || ct::equal (b2.left (), b1.left ())) && + (ct::less (b1.right (), b2.right ()) || ct::equal (b1.right (), b2.right ())) && + (ct::less (b2.bottom (), b1.bottom ()) || ct::equal (b2.bottom (), b1.bottom ())) && + (ct::less (b1.top (), b2.top ()) || ct::equal (b1.top (), b2.top ())); +} + +TEST(constrain) +{ + srand (0); + + db::plc::Graph plc; + TestableTriangulation tris (&plc); + double res = 128.0; + + db::DEdge ee[] = { + db::DEdge (0.25, 0.25, 0.25, 0.75), + db::DEdge (0.25, 0.75, 0.75, 0.75), + db::DEdge (0.75, 0.75, 0.75, 0.25), + db::DEdge (0.75, 0.25, 0.25, 0.25) + }; + + for (unsigned int i = 0; i < 200; ++i) { + double x = round (flt_rand () * res) * (1.0 / res); + double y = round (flt_rand () * res) * (1.0 / res); + bool ok = true; + for (unsigned int j = 0; j < sizeof (ee) / sizeof (ee[0]); ++j) { + if (ee[j].side_of (db::DPoint (x, y)) == 0) { + --i; + ok = false; + } + } + if (ok) { + tris.insert_point (x, y); + } + } + + std::vector contour; + for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) { + contour.push_back (tris.insert_point (ee[i].p1 ())); + } + std::vector > contours; + contours.push_back (contour); + + EXPECT_EQ (tris.check (), true); + + tris.constrain (contours); + EXPECT_EQ (tris.check (false), true); + + tris.remove_outside_triangles (); + + EXPECT_EQ (tris.check (), true); + + double area_in = 0.0; + db::DBox clip_box; + for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) { + clip_box += ee[i].p1 (); + } + for (auto t = plc.begin (); t != plc.end (); ++t) { + EXPECT_EQ (clip_box.overlaps (t->bbox ()), true); + EXPECT_EQ (safe_inside (t->bbox (), clip_box), true); + area_in += t->area (); + } + + EXPECT_EQ (tl::to_string (area_in), "0.25"); +} + +TEST(heavy_constrain) +{ + tl::info << "Running test_heavy_constrain " << tl::noendl; + + for (unsigned int l = 0; l < 100; ++l) { + + srand (l); + tl::info << "." << tl::noendl; + + db::plc::Graph plc; + TestableTriangulation tris (&plc); + double res = 128.0; + + db::DEdge ee[] = { + db::DEdge (0.25, 0.25, 0.25, 0.75), + db::DEdge (0.25, 0.75, 0.75, 0.75), + db::DEdge (0.75, 0.75, 0.75, 0.25), + db::DEdge (0.75, 0.25, 0.25, 0.25) + }; + + unsigned int n = rand () % 150 + 50; + + for (unsigned int i = 0; i < n; ++i) { + double x = round (flt_rand () * res) * (1.0 / res); + double y = round (flt_rand () * res) * (1.0 / res); + bool ok = true; + for (unsigned int j = 0; j < sizeof (ee) / sizeof (ee[0]); ++j) { + if (ee[j].side_of (db::DPoint (x, y)) == 0) { + --i; + ok = false; + } + } + if (ok) { + tris.insert_point (x, y); + } + } + + std::vector contour; + for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) { + contour.push_back (tris.insert_point (ee[i].p1 ())); + } + std::vector > contours; + contours.push_back (contour); + + EXPECT_EQ (tris.check (), true); + + tris.constrain (contours); + EXPECT_EQ (tris.check (false), true); + + tris.remove_outside_triangles (); + + EXPECT_EQ (tris.check (), true); + + double area_in = 0.0; + db::DBox clip_box; + for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) { + clip_box += ee[i].p1 (); + } + for (auto t = plc.begin (); t != plc.end (); ++t) { + EXPECT_EQ (clip_box.overlaps (t->bbox ()), true); + EXPECT_EQ (safe_inside (t->bbox (), clip_box), true); + area_in += t->area (); + } + + EXPECT_EQ (tl::to_string (area_in), "0.25"); + + } + + tl::info << tl::endl << "done."; +} + +TEST(heavy_find_point_around) +{ + tl::info << "Running Triangle_test_heavy_find_point_around " << tl::noendl; + + for (unsigned int l = 0; l < 100; ++l) { + + srand (l); + tl::info << "." << tl::noendl; + + db::plc::Graph plc; + TestableTriangulation tris (&plc); + double res = 128.0; + + unsigned int n = rand () % 190 + 10; + + std::vector vertexes; + + for (unsigned int i = 0; i < n; ++i) { + double x = round (flt_rand () * res) * (1.0 / res); + double y = round (flt_rand () * res) * (1.0 / res); + vertexes.push_back (tris.insert_point (x, y)); + } + + EXPECT_EQ (tris.check(), true); + + for (int i = 0; i < 100; ++i) { + + unsigned int nv = rand () % (unsigned int) vertexes.size (); + auto vertex = vertexes [nv]; + + double r = round (flt_rand () * res) * (1.0 / res); + auto p1 = tris.find_points_around (vertex, r); + auto p2 = tris.find_inside_circle (*vertex, r); + + std::set sp1 (p1.begin (), p1.end ()); + std::set sp2 (p2.begin (), p2.end ()); + sp2.erase (vertex); + + EXPECT_EQ (sp1 == sp2, true); + + } + + } + + tl::info << tl::endl << "done."; +} + +TEST(create_constrained_delaunay) +{ + db::Region r; + r.insert (db::Box (0, 0, 1000, 1000)); + + db::Region r2; + r2.insert (db::Box (200, 200, 800, 800)); + + r -= r2; + + db::plc::Graph plc; + TestableTriangulation tris (&plc); + tris.create_constrained_delaunay (r); + tris.remove_outside_triangles (); + + EXPECT_EQ (tris.check (), true); + + EXPECT_EQ (plc.to_string (), + "((1000, 0), (0, 0), (200, 200)), " + "((0, 1000), (200, 200), (0, 0)), " + "((1000, 0), (200, 200), (800, 200)), " + "((1000, 0), (800, 200), (1000, 1000)), " + "((800, 200), (800, 800), (1000, 1000)), " + "((0, 1000), (1000, 1000), (800, 800)), " + "((0, 1000), (800, 800), (200, 800)), " + "((0, 1000), (200, 800), (200, 200))"); +} + +TEST(triangulate_basic) +{ + db::Region r; + r.insert (db::Box (0, 0, 10000, 10000)); + + db::Region r2; + r2.insert (db::Box (2000, 2000, 8000, 8000)); + + r -= r2; + + db::plc::TriangulationParameters param; + param.min_b = 1.2; + param.max_area = 1.0; + + db::plc::Graph plc; + TestableTriangulation tri (&plc); + tri.triangulate (r, param, 0.001); + + EXPECT_EQ (tri.check (), true); + + for (auto t = plc.begin (); t != plc.end (); ++t) { + EXPECT_LE (t->area (), param.max_area); + EXPECT_GE (t->b (), param.min_b); + } + + EXPECT_GT (plc.num_polygons (), size_t (100)); + EXPECT_LT (plc.num_polygons (), size_t (150)); + + // for debugging: + // tri.dump ("debug.gds"); + + param.min_b = 1.0; + param.max_area = 0.1; + + tri.triangulate (r, param, 0.001); + + EXPECT_EQ (tri.check (), true); + + for (auto t = plc.begin (); t != plc.end (); ++t) { + EXPECT_LE (t->area (), param.max_area); + EXPECT_GE (t->b (), param.min_b); + } + + EXPECT_GT (plc.num_polygons (), size_t (900)); + EXPECT_LT (plc.num_polygons (), size_t (1000)); +} + +static void read_polygons (const std::string &path, db::Region ®ion, double dbu) +{ + tl::InputStream is (path); + tl::TextInputStream ti (is); + + unsigned int nvert = 0, nedges = 0; + + { + tl::Extractor ex (ti.get_line ().c_str ()); + ex.read (nvert); + ex.read (nedges); + } + + std::vector v; + auto dbu_trans = db::CplxTrans (dbu).inverted (); + for (unsigned int i = 0; i < nvert; ++i) { + double x = 0, y = 0; + tl::Extractor ex (ti.get_line ().c_str ()); + ex.read (x); + ex.read (y); + v.push_back (dbu_trans * db::DPoint (x, y)); + } + + unsigned int nstart = 0; + bool new_contour = true; + std::vector contour; + + for (unsigned int i = 0; i < nedges; ++i) { + + unsigned int n1 = 0, n2 = 0; + + tl::Extractor ex (ti.get_line ().c_str ()); + ex.read (n1); + ex.read (n2); + + if (new_contour) { + nstart = n1; + new_contour = false; + } + + contour.push_back (v[n1]); + + if (n2 == nstart) { + // finish contour + db::SimplePolygon sp; + sp.assign_hull (contour.begin (), contour.end ()); + region.insert (sp); + new_contour = true; + contour.clear (); + } else if (n2 <= n1) { + tl::error << "Invalid polygon wrap in line " << ti.line_number (); + tl_assert (false); + } + + } +} + +TEST(triangulate_geo) +{ + double dbu = 0.001; + + db::Region r; + read_polygons (tl::combine_path (tl::testsrc (), "testdata/algo/triangles1.txt"), r, dbu); + + // for debugging purposes dump the inputs + if (false) { + + db::Layout layout = db::Layout (); + layout.dbu (dbu); + db::Cell &top = layout.cell (layout.add_cell ("DUMP")); + unsigned int l1 = layout.insert_layer (db::LayerProperties (1, 0)); + r.insert_into (&layout, top.cell_index (), l1); + + { + tl::OutputStream stream ("input.gds"); + db::SaveLayoutOptions opt; + db::Writer writer (opt); + writer.write (layout, stream); + } + + } + + db::plc::TriangulationParameters param; + param.min_b = 1.0; + param.max_area = 0.1; + param.min_length = 0.001; + + db::plc::Graph plc; + TestableTriangulation tri (&plc); + tri.triangulate (r, param, dbu); + + EXPECT_EQ (tri.check (false), true); + + // for debugging: + // tri.dump ("debug.gds"); + + size_t n_skinny = 0; + for (auto t = plc.begin (); t != plc.end (); ++t) { + EXPECT_LE (t->area (), param.max_area); + if (t->b () < param.min_b) { + ++n_skinny; + } + } + + EXPECT_LT (n_skinny, size_t (20)); + EXPECT_GT (plc.num_polygons (), size_t (29000)); + EXPECT_LT (plc.num_polygons (), size_t (30000)); +} + +TEST(triangulate_analytic) +{ + double dbu = 0.0001; + + double star1 = 9.0, star2 = 5.0; + double r = 1.0; + int n = 100; + + auto dbu_trans = db::CplxTrans (dbu).inverted (); + + std::vector contour1, contour2; + for (int i = 0; i < n; ++i) { + double a = -M_PI * 2.0 * double (i) / double (n); // "-" for clockwise orientation + double rr, x, y; + rr = r * (1.0 + 0.4 * cos (star1 * a)); + x = rr * cos (a); + y = rr * sin (a); + contour1.push_back (dbu_trans * db::DPoint (x, y)); + rr = r * (0.1 + 0.03 * cos (star2 * a)); + x = rr * cos (a); + y = rr * sin (a); + contour2.push_back (dbu_trans * db::DPoint (x, y)); + } + + db::Region rg; + + db::SimplePolygon sp1; + sp1.assign_hull (contour1.begin (), contour1.end ()); + db::SimplePolygon sp2; + sp2.assign_hull (contour2.begin (), contour2.end ()); + + rg = db::Region (sp1) - db::Region (sp2); + + db::plc::TriangulationParameters param; + param.min_b = 1.0; + param.max_area = 0.01; + + db::plc::Graph plc; + TestableTriangulation tri (&plc); + tri.triangulate (rg, param, dbu); + + EXPECT_EQ (tri.check (false), true); + + // for debugging: + // tri.dump ("debug.gds"); + + for (auto t = plc.begin (); t != plc.end (); ++t) { + EXPECT_LE (t->area (), param.max_area); + EXPECT_GE (t->b (), param.min_b); + } + + EXPECT_GT (plc.num_polygons (), size_t (1250)); + EXPECT_LT (plc.num_polygons (), size_t (1300)); +} + +TEST(triangulate_problematic) +{ + db::DPoint contour[] = { + db::DPoint (129145.00000, -30060.80000), + db::DPoint (129145.00000, -28769.50000), + db::DPoint (129159.50000, -28754.90000), // this is a very short edge <-- from here. + db::DPoint (129159.60000, -28754.80000), // <-- to here. + db::DPoint (129159.50000, -28754.70000), + db::DPoint (129366.32200, -28547.90000), + db::DPoint (130958.54600, -26955.84600), + db::DPoint (131046.25000, -27043.55000), + db::DPoint (130152.15000, -27937.65000), + db::DPoint (130152.15000, -30060.80000) + }; + + db::DPolygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + db::plc::TriangulationParameters param; + param.min_b = 1.0; + param.max_area = 100000.0; + param.min_length = 0.002; + + db::plc::Graph plc; + TestableTriangulation tri (&plc); + tri.triangulate (poly, param); + + EXPECT_EQ (tri.check (false), true); + + // for debugging: + // tri.dump ("debug.gds"); + + for (auto t = plc.begin (); t != plc.end (); ++t) { + EXPECT_LE (t->area (), param.max_area); + EXPECT_GE (t->b (), param.min_b); + } + + EXPECT_GT (plc.num_polygons (), size_t (540)); + EXPECT_LT (plc.num_polygons (), size_t (560)); +} + +TEST(triangulate_thin) +{ + db::DPoint contour[] = { + db::DPoint (18790, 58090), + db::DPoint (18790, 58940), + db::DPoint (29290, 58940), + db::DPoint (29290, 58090) + }; + + db::DPoint hole[] = { + db::DPoint (18791, 58091), + db::DPoint (29289, 58091), + db::DPoint (29289, 58939), + db::DPoint (18791, 58939) + }; + + db::DPolygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + poly.insert_hole (hole + 0, hole + sizeof (hole) / sizeof (hole[0])); + + double dbu = 0.001; + + db::plc::TriangulationParameters param; + param.min_b = 0.5; + param.max_area = 0.0; + param.min_length = 2 * dbu; + + db::plc::Graph plc; + TestableTriangulation tri (&plc); + db::DCplxTrans trans = db::DCplxTrans (dbu) * db::DCplxTrans (db::DTrans (db::DPoint () - poly.box ().center ())); + tri.triangulate (trans * poly, param); + + EXPECT_EQ (tri.check (false), true); + + // for debugging: + // tri.dump ("debug.gds"); + + for (auto t = plc.begin (); t != plc.end (); ++t) { + EXPECT_GE (t->b (), param.min_b); + } + + EXPECT_GT (plc.num_polygons (), size_t (13000)); + EXPECT_LT (plc.num_polygons (), size_t (13200)); +} + +TEST(triangulate_issue1996) +{ + db::DPoint contour[] = { + db::DPoint (-8000, -8075), + db::DPoint (-8000, 8075), + db::DPoint (18000, 8075), + db::DPoint (18000, -8075) + }; + + db::DPolygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + double dbu = 0.001; + + db::plc::TriangulationParameters param; + param.min_b = 0.5; + param.max_area = 5000.0 * dbu * dbu; + + db::plc::Graph plc; + TestableTriangulation tri (&plc); + db::DCplxTrans trans = db::DCplxTrans (dbu) * db::DCplxTrans (db::DTrans (db::DPoint () - poly.box ().center ())); + tri.triangulate (trans * poly, param); + + EXPECT_EQ (tri.check (false), true); + + // for debugging: + // tri.dump ("debug.gds"); + + for (auto t = plc.begin (); t != plc.end (); ++t) { + EXPECT_LE (t->area (), param.max_area); + EXPECT_GE (t->b (), param.min_b); + } + + EXPECT_GT (plc.num_polygons (), size_t (128000)); + EXPECT_LT (plc.num_polygons (), size_t (132000)); +} + +TEST(triangulate_with_vertexes) +{ + db::Point contour[] = { + db::Point (0, 0), + db::Point (0, 100), + db::Point (1000, 100), + db::Point (1000, 0) + }; + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + double dbu = 0.001; + + db::plc::TriangulationParameters param; + param.min_b = 0.0; + param.max_area = 0.0; + + std::vector vertexes; + + db::plc::Graph plc; + TestableTriangulation tri (&plc); + db::CplxTrans trans = db::DCplxTrans (dbu) * db::CplxTrans (db::Trans (db::Point () - poly.box ().center ())); + tri.triangulate (poly, param, trans); + + EXPECT_EQ (plc.to_string (), "((-0.5, -0.05), (-0.5, 0.05), (0.5, 0.05)), ((0.5, -0.05), (-0.5, -0.05), (0.5, 0.05))"); + + vertexes.clear (); + + // outside vertexes are ignored, but lead to a different triangulation + vertexes.push_back (db::Point (50, 150)); + tri.triangulate (poly, vertexes, param, trans); + + EXPECT_EQ (plc.to_string (), "((-0.5, -0.05), (-0.133333333333, 0.05), (0.5, -0.05)), ((0.5, 0.05), (0.5, -0.05), (-0.133333333333, 0.05)), ((-0.133333333333, 0.05), (-0.5, -0.05), (-0.5, 0.05))"); + + for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { + auto *vp = tri.find_vertex_for_point (trans * *v); + EXPECT_EQ (vp, 0); + } + + vertexes.clear (); + vertexes.push_back (db::Point (50, 50)); + tri.triangulate (poly, vertexes, param, trans); + + EXPECT_EQ (plc.to_string (), "((-0.45, 0), (-0.5, -0.05), (-0.5, 0.05)), ((0.5, 0.05), (-0.45, 0), (-0.5, 0.05)), ((-0.45, 0), (0.5, -0.05), (-0.5, -0.05)), ((-0.45, 0), (0.5, 0.05), (0.5, -0.05))"); + + for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { + auto *vp = tri.find_vertex_for_point (trans * *v); + if (! vp) { + tl::warn << "Vertex not present in output: " << v->to_string (); + EXPECT_EQ (1, 0); + } + } + + // aggressive triangulation + param.min_b = 1.0; + param.max_area = 20 * 20 * dbu * dbu; + + tri.triangulate (poly, vertexes, param, trans); + + EXPECT_GT (plc.num_polygons (), size_t (380)); + EXPECT_LT (plc.num_polygons (), size_t (400)); + + for (auto t = plc.begin (); t != plc.end (); ++t) { + EXPECT_LE (t->area (), param.max_area); + EXPECT_GE (t->b (), param.min_b); + } + + for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { + auto *vp = tri.find_vertex_for_point (trans * *v); + if (! vp) { + tl::warn << "Vertex not present in output: " << v->to_string (); + EXPECT_EQ (1, 0); + } + } +} diff --git a/src/db/unit_tests/dbTrianglesTests.cc b/src/db/unit_tests/dbTrianglesTests.cc index 138248694..cd5779f2d 100644 --- a/src/db/unit_tests/dbTrianglesTests.cc +++ b/src/db/unit_tests/dbTrianglesTests.cc @@ -461,7 +461,7 @@ TEST(ensure_edge) EXPECT_EQ (tl::to_string (area_in), "0.25"); } -bool safe_inside (const db::DBox &b1, const db::DBox &b2) +static bool safe_inside (const db::DBox &b1, const db::DBox &b2) { typedef db::coord_traits ct; diff --git a/src/db/unit_tests/unit_tests.pro b/src/db/unit_tests/unit_tests.pro index 047e46ea6..ad9f9e382 100644 --- a/src/db/unit_tests/unit_tests.pro +++ b/src/db/unit_tests/unit_tests.pro @@ -12,7 +12,8 @@ SOURCES = \ dbFillToolTests.cc \ dbLogTests.cc \ dbObjectWithPropertiesTests.cc \ - dbPolygonGraphTests.cc \ + dbPLCGraphTest.cc \ + dbPLCTriangulationTests.cc \ dbPolygonNeighborhoodTests.cc \ dbPropertiesFilterTests.cc \ dbQuadTreeTests.cc \ From 802cf995217976b4033382fd177059f60b5a2518 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 13 Apr 2025 22:58:37 +0200 Subject: [PATCH 07/58] Porting dbTriangleTests --- src/db/db/dbPLC.h | 29 +- .../{dbPLCGraphTest.cc => dbPLCGraphTests.cc} | 0 src/db/unit_tests/dbPLCTests.cc | 582 ++++++++++++++++++ src/db/unit_tests/dbTriangleTests.cc | 130 ++-- src/db/unit_tests/unit_tests.pro | 3 +- 5 files changed, 665 insertions(+), 79 deletions(-) rename src/db/unit_tests/{dbPLCGraphTest.cc => dbPLCGraphTests.cc} (100%) create mode 100644 src/db/unit_tests/dbPLCTests.cc diff --git a/src/db/db/dbPLC.h b/src/db/db/dbPLC.h index 8d410d0b8..9d7b35e43 100644 --- a/src/db/db/dbPLC.h +++ b/src/db/db/dbPLC.h @@ -157,15 +157,16 @@ public: return in_circle (*this, center, radius); } -private: - friend class Edge; - friend class Graph; - +protected: Vertex (Graph *graph); Vertex (Graph *graph, const DPoint &p); Vertex (Graph *graph, const Vertex &v); Vertex (Graph *graph, db::DCoord x, db::DCoord y); +private: + friend class Edge; + friend class Graph; + void remove_edge (const edges_iterator_non_const &ec) { mp_edges.erase (ec); @@ -491,21 +492,20 @@ protected: void unlink (); void link (); -private: - friend class Polygon; - friend class Graph; - friend class Triangulation; - void set_level (size_t l) { m_level = l; } size_t level () const { return m_level; } void set_id (size_t id) { m_id = id; } - void set_is_segment (bool is_seg) { m_is_segment = is_seg; } Edge (Graph *graph); Edge (Graph *graph, Vertex *v1, Vertex *v2); +private: + friend class Polygon; + friend class Graph; + friend class Triangulation; + Graph *mp_graph; Vertex *mp_v1, *mp_v2; Polygon *mp_left, *mp_right; @@ -538,9 +538,6 @@ class DB_PUBLIC Polygon : public tl::list_node, public tl::Object { public: - Polygon (Graph *graph); - Polygon (Graph *graph, Edge *e1, Edge *e2, Edge *e3); - template Polygon (Graph *graph, Iter from, Iter to) : mp_graph (graph), mp_e (from, to) @@ -695,7 +692,13 @@ public: */ unsigned int num_segments () const; +protected: + Polygon (Graph *graph); + Polygon (Graph *graph, Edge *e1, Edge *e2, Edge *e3); + private: + friend class Graph; + Graph *mp_graph; bool m_is_outside; std::vector mp_e; diff --git a/src/db/unit_tests/dbPLCGraphTest.cc b/src/db/unit_tests/dbPLCGraphTests.cc similarity index 100% rename from src/db/unit_tests/dbPLCGraphTest.cc rename to src/db/unit_tests/dbPLCGraphTests.cc diff --git a/src/db/unit_tests/dbPLCTests.cc b/src/db/unit_tests/dbPLCTests.cc new file mode 100644 index 000000000..f93c5d373 --- /dev/null +++ b/src/db/unit_tests/dbPLCTests.cc @@ -0,0 +1,582 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "dbPLC.h" +#include "tlUnitTest.h" + +#include +#include + +class TestableEdge + : public db::plc::Edge +{ +public: + using db::plc::Edge::Edge; + using db::plc::Edge::link; + using db::plc::Edge::unlink; + using db::plc::Edge::set_is_segment; + using db::plc::Edge::set_level; + using db::plc::Edge::level; + + TestableEdge (db::plc::Vertex *v1, db::plc::Vertex *v2) + : db::plc::Edge (0, v1, v2) + { } +}; + +class TestableVertex + : public db::plc::Vertex +{ +public: + TestableVertex () + : db::plc::Vertex (0) + { } + + TestableVertex (double x, double y) + : db::plc::Vertex (0, x, y) + { } + + TestableVertex (const db::DPoint &pt) + : db::plc::Vertex (0, pt) + { } +}; + +class TestablePolygon + : public db::plc::Polygon +{ +public: + TestablePolygon () + : db::plc::Polygon (0) + { } + + TestablePolygon (db::plc::Edge *e1, db::plc::Edge *e2, db::plc::Edge *e3) + : db::plc::Polygon (0, e1, e2, e3) + { } +}; + +// Tests for Vertex class + +TEST(Vertex_basic) +{ + TestableVertex v; + + v.set_x (1.5); + v.set_y (0.5); + EXPECT_EQ (v.to_string (), "(1.5, 0.5)"); + EXPECT_EQ (v.x (), 1.5); + EXPECT_EQ (v.y (), 0.5); + + v = TestableVertex (db::DPoint (2, 3)); + EXPECT_EQ (v.to_string (), "(2, 3)"); +} + +static std::string edges_from_vertex (const TestableVertex &v) +{ + std::string res; + for (auto e = v.begin_edges (); e != v.end_edges (); ++e) { + if (! res.empty ()) { + res += ", "; + } + res += (*e)->to_string (); + } + return res; +} + +static std::string triangles_from_vertex (const TestableVertex &v) +{ + auto tri = v.polygons (); + std::string res; + for (auto t = tri.begin (); t != tri.end (); ++t) { + if (! res.empty ()) { + res += ", "; + } + res += (*t)->to_string (); + } + return res; +} + +TEST(Vertex_edge_registration) +{ + TestableVertex v1 (0, 0); + TestableVertex v2 (1, 2); + TestableVertex v3 (2, 1); + + std::unique_ptr e1 (new TestableEdge (&v1, &v2)); + e1->link (); + EXPECT_EQ (edges_from_vertex (v1), "((0, 0), (1, 2))"); + EXPECT_EQ (edges_from_vertex (v2), "((0, 0), (1, 2))"); + EXPECT_EQ (edges_from_vertex (v3), ""); + + std::unique_ptr e2 (new TestableEdge (&v2, &v3)); + e2->link (); + EXPECT_EQ (edges_from_vertex (v1), "((0, 0), (1, 2))"); + EXPECT_EQ (edges_from_vertex (v2), "((0, 0), (1, 2)), ((1, 2), (2, 1))"); + EXPECT_EQ (edges_from_vertex (v3), "((1, 2), (2, 1))"); + + e2->unlink (); + e2.reset (0); + EXPECT_EQ (edges_from_vertex (v1), "((0, 0), (1, 2))"); + EXPECT_EQ (edges_from_vertex (v2), "((0, 0), (1, 2))"); + EXPECT_EQ (edges_from_vertex (v3), ""); + + e1->unlink (); + e1.reset (0); + EXPECT_EQ (edges_from_vertex (v1), ""); + EXPECT_EQ (edges_from_vertex (v2), ""); + EXPECT_EQ (edges_from_vertex (v3), ""); +} + +TEST(Vertex_triangles) +{ + TestableVertex v1 (0, 0); + TestableVertex v2 (1, 2); + TestableVertex v3 (2, 1); + TestableVertex v4 (-1, 2); + EXPECT_EQ (triangles_from_vertex (v1), ""); + + std::unique_ptr e1 (new TestableEdge (&v1, &v2)); + e1->link (); + std::unique_ptr e2 (new TestableEdge (&v2, &v3)); + e2->link (); + std::unique_ptr e3 (new TestableEdge (&v3, &v1)); + e3->link (); + + std::unique_ptr tri (new TestablePolygon (e1.get (), e2.get (), e3.get ())); + EXPECT_EQ (triangles_from_vertex (v1), "((0, 0), (1, 2), (2, 1))"); + EXPECT_EQ (triangles_from_vertex (v2), "((0, 0), (1, 2), (2, 1))"); + EXPECT_EQ (triangles_from_vertex (v3), "((0, 0), (1, 2), (2, 1))"); + + std::unique_ptr e4 (new TestableEdge (&v1, &v4)); + e4->link (); + std::unique_ptr e5 (new TestableEdge (&v2, &v4)); + e5->link (); + std::unique_ptr tri2 (new TestablePolygon (e1.get (), e4.get (), e5.get ())); + EXPECT_EQ (triangles_from_vertex (v1), "((0, 0), (-1, 2), (1, 2)), ((0, 0), (1, 2), (2, 1))"); + EXPECT_EQ (triangles_from_vertex (v2), "((0, 0), (-1, 2), (1, 2)), ((0, 0), (1, 2), (2, 1))"); + EXPECT_EQ (triangles_from_vertex (v3), "((0, 0), (1, 2), (2, 1))"); + EXPECT_EQ (triangles_from_vertex (v4), "((0, 0), (-1, 2), (1, 2))"); + + tri->unlink (); + EXPECT_EQ (triangles_from_vertex (v1), "((0, 0), (-1, 2), (1, 2))"); + + tri2->unlink (); + EXPECT_EQ (triangles_from_vertex (v1), ""); +} + +// Tests for Triangle class + +TEST(Triangle_basic) +{ + TestableVertex v1; + TestableVertex v2 (1, 2); + TestableVertex v3 (2, 1); + + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v2, &v3); + TestableEdge s3 (&v3, &v1); + + EXPECT_EQ (s1.v1 () == &v1, true); + EXPECT_EQ (s2.v2 () == &v3, true); + + TestablePolygon tri (&s1, &s2, &s3); + EXPECT_EQ (tri.to_string (), "((0, 0), (1, 2), (2, 1))"); + EXPECT_EQ (tri.edge (-1) == &s3, true); + EXPECT_EQ (tri.edge (0) == &s1, true); + EXPECT_EQ (tri.edge (1) == &s2, true); + EXPECT_EQ (tri.edge (3) == &s1, true); + + // ordering + TestableEdge s11 (&v1, &v2); + TestableEdge s12 (&v3, &v2); + TestableEdge s13 (&v1, &v3); + + TestablePolygon tri2 (&s11, &s12, &s13); + EXPECT_EQ (tri2.to_string (), "((0, 0), (1, 2), (2, 1))"); + + // triangle registration + EXPECT_EQ (s11.right () == &tri2, true); + EXPECT_EQ (s11.left () == 0, true); + EXPECT_EQ (s12.left () == &tri2, true); + EXPECT_EQ (s12.right () == 0, true); + EXPECT_EQ (s13.left () == &tri2, true); + EXPECT_EQ (s13.right () == 0, true); + + EXPECT_EQ (s13.to_string (), "((0, 0), (2, 1))"); + s13.reverse (); + EXPECT_EQ (s13.to_string (), "((2, 1), (0, 0))"); + EXPECT_EQ (s13.right () == &tri2, true); + EXPECT_EQ (s13.left () == 0, true); + + // flags + EXPECT_EQ (tri.is_outside (), false); + tri.set_outside (true); + EXPECT_EQ (tri.is_outside (), true); +} + +TEST(Triangle_find_segment_with) +{ + TestableVertex v1; + TestableVertex v2 (1, 2); + TestableVertex v3 (2, 1); + + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v2, &v3); + TestableEdge s3 (&v3, &v1); + + TestablePolygon tri (&s1, &s2, &s3); + + EXPECT_EQ (tri.find_edge_with (&v1, &v2)->to_string (), "((0, 0), (1, 2))"); + EXPECT_EQ (tri.find_edge_with (&v2, &v1)->to_string (), "((0, 0), (1, 2))"); +} + +TEST(Triangle_ext_vertex) +{ + TestableVertex v1; + TestableVertex v2 (1, 2); + TestableVertex v3 (2, 1); + + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v2, &v3); + TestableEdge s3 (&v3, &v1); + + TestablePolygon tri (&s1, &s2, &s3); + + EXPECT_EQ (tri.opposite (&s1)->to_string (), "(2, 1)"); + EXPECT_EQ (tri.opposite (&s3)->to_string (), "(1, 2)"); +} + +TEST(Triangle_opposite_vertex) +{ + TestableVertex v1; + TestableVertex v2 (1, 2); + TestableVertex v3 (2, 1); + + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v2, &v3); + TestableEdge s3 (&v3, &v1); + + TestablePolygon tri (&s1, &s2, &s3); + + EXPECT_EQ (tri.opposite (&s1)->to_string (), "(2, 1)"); + EXPECT_EQ (tri.opposite (&s3)->to_string (), "(1, 2)"); +} + +TEST(Triangle_opposite_edge) +{ + TestableVertex v1; + TestableVertex v2 (1, 2); + TestableVertex v3 (2, 1); + + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v2, &v3); + TestableEdge s3 (&v3, &v1); + + TestablePolygon tri (&s1, &s2, &s3); + + EXPECT_EQ (tri.opposite (&v1)->to_string (), "((1, 2), (2, 1))"); + EXPECT_EQ (tri.opposite (&v3)->to_string (), "((0, 0), (1, 2))"); +} + +TEST(Triangle_contains) +{ + TestableVertex v1; + TestableVertex v2 (1, 2); + TestableVertex v3 (2, 1); + + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v2, &v3); + TestableEdge s3 (&v3, &v1); + + { + TestablePolygon tri (&s1, &s2, &s3); + EXPECT_EQ (tri.contains (db::DPoint (0, 0)), 0); + EXPECT_EQ (tri.contains (db::DPoint (-1, -2)), -1); + EXPECT_EQ (tri.contains (db::DPoint (0.5, 1)), 0); + EXPECT_EQ (tri.contains (db::DPoint (0.5, 2)), -1); + EXPECT_EQ (tri.contains (db::DPoint (2.5, 1)), -1); + EXPECT_EQ (tri.contains (db::DPoint (1, -1)), -1); + EXPECT_EQ (tri.contains (db::DPoint (1, 1)), 1); + } + + s1.reverse (); + s2.reverse (); + s3.reverse (); + + { + TestablePolygon tri2 (&s3, &s2, &s1); + EXPECT_EQ (tri2.contains(db::DPoint(0, 0)), 0); + EXPECT_EQ (tri2.contains(db::DPoint(0.5, 1)), 0); + EXPECT_EQ (tri2.contains(db::DPoint(0.5, 2)), -1); + EXPECT_EQ (tri2.contains(db::DPoint(2.5, 1)), -1); + EXPECT_EQ (tri2.contains(db::DPoint(1, -1)), -1); + EXPECT_EQ (tri2.contains(db::DPoint(1, 1)), 1); + } +} + +TEST(Triangle_contains_small) +{ + TestableVertex v1; + TestableVertex v2 (0.001, 0.002); + TestableVertex v3 (0.002, 0.001); + + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v2, &v3); + TestableEdge s3 (&v3, &v1); + + { + TestablePolygon tri (&s1, &s2, &s3); + EXPECT_EQ (tri.contains (db::DPoint (0, 0)), 0); + EXPECT_EQ (tri.contains (db::DPoint (-0.001, -0.002)), -1); + EXPECT_EQ (tri.contains (db::DPoint (0.0005, 0.001)), 0); + EXPECT_EQ (tri.contains (db::DPoint (0.0005, 0.002)), -1); + EXPECT_EQ (tri.contains (db::DPoint (0.0025, 0.001)), -1); + EXPECT_EQ (tri.contains (db::DPoint (0.001, -0.001)), -1); + EXPECT_EQ (tri.contains (db::DPoint (0.001, 0.001)), 1); + } + + s1.reverse (); + s2.reverse (); + s3.reverse (); + + { + TestablePolygon tri2 (&s3, &s2, &s1); + EXPECT_EQ (tri2.contains(db::DPoint(0, 0)), 0); + EXPECT_EQ (tri2.contains(db::DPoint(0.0005, 0.001)), 0); + EXPECT_EQ (tri2.contains(db::DPoint(0.0005, 0.002)), -1); + EXPECT_EQ (tri2.contains(db::DPoint(0.0025, 0.001)), -1); + EXPECT_EQ (tri2.contains(db::DPoint(0.001, -0.001)), -1); + EXPECT_EQ (tri2.contains(db::DPoint(0.001, 0.001)), 1); + } +} + +TEST(Triangle_circumcircle) +{ + TestableVertex v1; + TestableVertex v2 (1, 2); + TestableVertex v3 (2, 1); + + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v2, &v3); + TestableEdge s3 (&v3, &v1); + + TestablePolygon tri (&s1, &s2, &s3); + + auto cc = tri.circumcircle (); + auto center = cc.first; + auto radius = cc.second; + + EXPECT_EQ (tl::to_string (center), "0.833333333333,0.833333333333"); + EXPECT_EQ (tl::to_string (radius), "1.17851130198"); + + EXPECT_EQ (TestableVertex::in_circle (center, center, radius), 1); + EXPECT_EQ (TestableVertex::in_circle (db::DPoint (-1, -1), center, radius), -1); + EXPECT_EQ (v1.in_circle (center, radius), 0); + EXPECT_EQ (v2.in_circle (center, radius), 0); + EXPECT_EQ (v3.in_circle (center, radius), 0); +} + +// Tests for TriangleEdge class + +TEST(TriangleEdge_basic) +{ + TestableVertex v1; + TestableVertex v2 (1, 0.5); + + TestableEdge edge (&v1, &v2); + EXPECT_EQ (edge.to_string (), "((0, 0), (1, 0.5))"); + + EXPECT_EQ (edge.is_segment (), false); + edge.set_is_segment (true); + EXPECT_EQ (edge.is_segment (), true); + + EXPECT_EQ (edge.level (), size_t (0)); + edge.set_level (42); + EXPECT_EQ (edge.level (), size_t (42)); + + EXPECT_EQ (edge.other (&v1) == &v2, true); + EXPECT_EQ (edge.other (&v2) == &v1, true); +} + +TEST(TriangleEdge_triangles) +{ + TestableVertex v1 (0, 0); + TestableVertex v2 (1, 2); + TestableVertex v3 (2, 1); + TestableVertex v4 (-1, 2); + + std::unique_ptr e1 (new TestableEdge (&v1, &v2)); + std::unique_ptr e2 (new TestableEdge (&v2, &v3)); + std::unique_ptr e3 (new TestableEdge (&v3, &v1)); + + std::unique_ptr tri (new TestablePolygon (e1.get (), e2.get (), e3.get ())); + + std::unique_ptr e4 (new TestableEdge (&v1, &v4)); + std::unique_ptr e5 (new TestableEdge (&v2, &v4)); + std::unique_ptr tri2 (new TestablePolygon (e1.get (), e4.get (), e5.get ())); + + EXPECT_EQ (e1->is_outside (), false); + EXPECT_EQ (e2->is_outside (), true); + EXPECT_EQ (e4->is_outside (), true); + + EXPECT_EQ (e1->is_for_outside_triangles (), false); + tri->set_outside (true); + EXPECT_EQ (e1->is_for_outside_triangles (), true); + + EXPECT_EQ (e1->has_polygon (tri.get ()), true); + EXPECT_EQ (e1->has_polygon (tri2.get ()), true); + EXPECT_EQ (e4->has_polygon (tri.get ()), false); + EXPECT_EQ (e4->has_polygon (tri2.get ()), true); + + EXPECT_EQ (e1->other (tri.get ()) == tri2.get (), true); + EXPECT_EQ (e1->other (tri2.get ()) == tri.get (), true); + + EXPECT_EQ (e1->common_vertex (e2.get ()) == &v2, true); + EXPECT_EQ (e2->common_vertex (e4.get ()) == 0, true); + + tri->unlink (); + EXPECT_EQ (e1->has_polygon (tri.get ()), false); + EXPECT_EQ (e1->has_polygon (tri2.get ()), true); +} + +TEST(TriangleEdge_side_of) +{ + TestableVertex v1; + TestableVertex v2 (1, 0.5); + + TestableEdge edge (&v1, &v2); + EXPECT_EQ (edge.to_string (), "((0, 0), (1, 0.5))"); + + EXPECT_EQ (edge.side_of (TestableVertex (0, 0)), 0) + EXPECT_EQ (edge.side_of (TestableVertex (0.5, 0.25)), 0) + EXPECT_EQ (edge.side_of (TestableVertex (0, 1)), -1) + EXPECT_EQ (edge.side_of (TestableVertex (0, -1)), 1) + EXPECT_EQ (edge.side_of (TestableVertex (0.5, 0.5)), -1) + EXPECT_EQ (edge.side_of (TestableVertex (0.5, 0)), 1) + + TestableVertex v3 (1, 0); + TestableVertex v4 (0, 1); + TestableEdge edge2 (&v3, &v4); + + EXPECT_EQ (edge2.side_of (TestableVertex(0.2, 0.2)), -1); +} + +namespace { + class VertexHeap + { + public: + TestableVertex *make_vertex (double x, double y) + { + m_heap.push_back (TestableVertex (x, y)); + return &m_heap.back (); + } + private: + std::list m_heap; + }; +} + +TEST(TriangleEdge_crosses) +{ + VertexHeap heap; + + TestableEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5)); + EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, -0.5), heap.make_vertex(1, -0.5))), false); + EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0), heap.make_vertex(1, 0))), false); // only cuts + EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(1, 0.5))), false); + EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(2, 0.5))), false); + EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.25), heap.make_vertex(2, 0.25))), true); + EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(-0.1, 0.5))), false); + EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.6), heap.make_vertex(0, 0.6))), false); + EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 1), heap.make_vertex(1, 1))), false); + + EXPECT_EQ (s1.crosses_including (TestableEdge (heap.make_vertex (-1, -0.5), heap.make_vertex(1, -0.5))), false); + EXPECT_EQ (s1.crosses_including (TestableEdge (heap.make_vertex (-1, 0), heap.make_vertex(1, 0))), true); // only cuts + EXPECT_EQ (s1.crosses_including (TestableEdge (heap.make_vertex (-1, 0.25), heap.make_vertex(2, 0.25))), true); +} + +TEST(TriangleEdge_point_on) +{ + VertexHeap heap; + + TestableEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5)); + EXPECT_EQ (s1.point_on (db::DPoint (0, 0)), false); // endpoints are not "on" + EXPECT_EQ (s1.point_on (db::DPoint (0, -0.5)), false); + EXPECT_EQ (s1.point_on (db::DPoint (0.5, 0)), false); + EXPECT_EQ (s1.point_on (db::DPoint (0.5, 0.25)), true); + EXPECT_EQ (s1.point_on (db::DPoint (1, 0.5)), false); // endpoints are not "on" + EXPECT_EQ (s1.point_on (db::DPoint (1, 1)), false); + EXPECT_EQ (s1.point_on (db::DPoint (2, 1)), false); +} + +TEST(TriangleEdge_intersection_point) +{ + VertexHeap heap; + + TestableEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5)); + EXPECT_EQ (s1.intersection_point (TestableEdge (heap.make_vertex (-1, 0.25), heap.make_vertex (2, 0.25))).to_string (), "0.5,0.25"); +} + +TEST(TriangleEdge_can_flip) +{ + TestableVertex v1 (2, -1); + TestableVertex v2 (0, 0); + TestableVertex v3 (1, 0); + TestableVertex v4 (0.5, 1); + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v1, &v3); + TestableEdge s3 (&v2, &v3); + TestableEdge s4 (&v2, &v4); + TestableEdge s5 (&v3, &v4); + TestablePolygon t1 (&s1, &s2, &s3); + TestablePolygon t2 (&s3, &s4, &s5); + EXPECT_EQ (s3.left () == &t2, true); + EXPECT_EQ (s3.right () == &t1, true); + EXPECT_EQ (s3.can_flip(), false); + v1.set_x (0.5); + EXPECT_EQ (s3.can_flip(), true); + v1.set_x (-0.25); + EXPECT_EQ (s3.can_flip(), true); + v1.set_x (-0.5); + EXPECT_EQ (s3.can_flip(), false); + v1.set_x (-1.0); + EXPECT_EQ (s3.can_flip(), false); +} + +TEST(TriangleEdge_distance) +{ + TestableVertex v1 (0, 0); + TestableVertex v2 (1, 0); + + TestableEdge seg (&v1, &v2); + EXPECT_EQ (seg.distance (db::DPoint (0, 0)), 0); + EXPECT_EQ (seg.distance (db::DPoint (0, 1)), 1); + EXPECT_EQ (seg.distance (db::DPoint (1, 2)), 2); + EXPECT_EQ (seg.distance (db::DPoint (1, -1)), 1); + EXPECT_EQ (seg.distance (db::DPoint (2, 0)), 1); + EXPECT_EQ (seg.distance (db::DPoint (-2, 0)), 2); + seg.reverse (); + EXPECT_EQ (seg.distance (db::DPoint (0, 0)), 0); + EXPECT_EQ (seg.distance (db::DPoint (0, 1)), 1); + EXPECT_EQ (seg.distance (db::DPoint (1, 2)), 2); + EXPECT_EQ (seg.distance (db::DPoint (1, -1)), 1); + EXPECT_EQ (seg.distance (db::DPoint (2, 0)), 1); + EXPECT_EQ (seg.distance (db::DPoint (-2, 0)), 2); +} diff --git a/src/db/unit_tests/dbTriangleTests.cc b/src/db/unit_tests/dbTriangleTests.cc index 4d0eb9e78..b460f96ec 100644 --- a/src/db/unit_tests/dbTriangleTests.cc +++ b/src/db/unit_tests/dbTriangleTests.cc @@ -27,7 +27,7 @@ #include #include -class TestableTriangleEdge +class TestableEdge : public db::TriangleEdge { public: @@ -35,7 +35,7 @@ public: using db::TriangleEdge::link; using db::TriangleEdge::unlink; - TestableTriangleEdge (db::Vertex *v1, db::Vertex *v2) + TestableEdge (db::Vertex *v1, db::Vertex *v2) : db::TriangleEdge (v1, v2) { } }; @@ -87,13 +87,13 @@ TEST(Vertex_edge_registration) db::Vertex v2 (1, 2); db::Vertex v3 (2, 1); - std::unique_ptr e1 (new TestableTriangleEdge (&v1, &v2)); + std::unique_ptr e1 (new TestableEdge (&v1, &v2)); e1->link (); EXPECT_EQ (edges_from_vertex (v1), "((0, 0), (1, 2))"); EXPECT_EQ (edges_from_vertex (v2), "((0, 0), (1, 2))"); EXPECT_EQ (edges_from_vertex (v3), ""); - std::unique_ptr e2 (new TestableTriangleEdge (&v2, &v3)); + std::unique_ptr e2 (new TestableEdge (&v2, &v3)); e2->link (); EXPECT_EQ (edges_from_vertex (v1), "((0, 0), (1, 2))"); EXPECT_EQ (edges_from_vertex (v2), "((0, 0), (1, 2)), ((1, 2), (2, 1))"); @@ -120,11 +120,11 @@ TEST(Vertex_triangles) db::Vertex v4 (-1, 2); EXPECT_EQ (triangles_from_vertex (v1), ""); - std::unique_ptr e1 (new TestableTriangleEdge (&v1, &v2)); + std::unique_ptr e1 (new TestableEdge (&v1, &v2)); e1->link (); - std::unique_ptr e2 (new TestableTriangleEdge (&v2, &v3)); + std::unique_ptr e2 (new TestableEdge (&v2, &v3)); e2->link (); - std::unique_ptr e3 (new TestableTriangleEdge (&v3, &v1)); + std::unique_ptr e3 (new TestableEdge (&v3, &v1)); e3->link (); std::unique_ptr tri (new db::Triangle (e1.get (), e2.get (), e3.get ())); @@ -132,9 +132,9 @@ TEST(Vertex_triangles) EXPECT_EQ (triangles_from_vertex (v2), "((0, 0), (1, 2), (2, 1))"); EXPECT_EQ (triangles_from_vertex (v3), "((0, 0), (1, 2), (2, 1))"); - std::unique_ptr e4 (new TestableTriangleEdge (&v1, &v4)); + std::unique_ptr e4 (new TestableEdge (&v1, &v4)); e4->link (); - std::unique_ptr e5 (new TestableTriangleEdge (&v2, &v4)); + std::unique_ptr e5 (new TestableEdge (&v2, &v4)); e5->link (); std::unique_ptr tri2 (new db::Triangle (e1.get (), e4.get (), e5.get ())); EXPECT_EQ (triangles_from_vertex (v1), "((0, 0), (-1, 2), (1, 2)), ((0, 0), (1, 2), (2, 1))"); @@ -157,9 +157,9 @@ TEST(Triangle_basic) db::Vertex v2 (1, 2); db::Vertex v3 (2, 1); - TestableTriangleEdge s1 (&v1, &v2); - TestableTriangleEdge s2 (&v2, &v3); - TestableTriangleEdge s3 (&v3, &v1); + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v2, &v3); + TestableEdge s3 (&v3, &v1); EXPECT_EQ (s1.v1 () == &v1, true); EXPECT_EQ (s2.v2 () == &v3, true); @@ -172,9 +172,9 @@ TEST(Triangle_basic) EXPECT_EQ (tri.edge (3) == &s1, true); // ordering - TestableTriangleEdge s11 (&v1, &v2); - TestableTriangleEdge s12 (&v3, &v2); - TestableTriangleEdge s13 (&v1, &v3); + TestableEdge s11 (&v1, &v2); + TestableEdge s12 (&v3, &v2); + TestableEdge s13 (&v1, &v3); db::Triangle tri2 (&s11, &s12, &s13); EXPECT_EQ (tri2.to_string (), "((0, 0), (1, 2), (2, 1))"); @@ -205,9 +205,9 @@ TEST(Triangle_find_segment_with) db::Vertex v2 (1, 2); db::Vertex v3 (2, 1); - TestableTriangleEdge s1 (&v1, &v2); - TestableTriangleEdge s2 (&v2, &v3); - TestableTriangleEdge s3 (&v3, &v1); + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v2, &v3); + TestableEdge s3 (&v3, &v1); db::Triangle tri (&s1, &s2, &s3); @@ -221,9 +221,9 @@ TEST(Triangle_ext_vertex) db::Vertex v2 (1, 2); db::Vertex v3 (2, 1); - TestableTriangleEdge s1 (&v1, &v2); - TestableTriangleEdge s2 (&v2, &v3); - TestableTriangleEdge s3 (&v3, &v1); + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v2, &v3); + TestableEdge s3 (&v3, &v1); db::Triangle tri (&s1, &s2, &s3); @@ -237,9 +237,9 @@ TEST(Triangle_opposite_vertex) db::Vertex v2 (1, 2); db::Vertex v3 (2, 1); - TestableTriangleEdge s1 (&v1, &v2); - TestableTriangleEdge s2 (&v2, &v3); - TestableTriangleEdge s3 (&v3, &v1); + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v2, &v3); + TestableEdge s3 (&v3, &v1); db::Triangle tri (&s1, &s2, &s3); @@ -253,9 +253,9 @@ TEST(Triangle_opposite_edge) db::Vertex v2 (1, 2); db::Vertex v3 (2, 1); - TestableTriangleEdge s1 (&v1, &v2); - TestableTriangleEdge s2 (&v2, &v3); - TestableTriangleEdge s3 (&v3, &v1); + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v2, &v3); + TestableEdge s3 (&v3, &v1); db::Triangle tri (&s1, &s2, &s3); @@ -269,9 +269,9 @@ TEST(Triangle_contains) db::Vertex v2 (1, 2); db::Vertex v3 (2, 1); - TestableTriangleEdge s1 (&v1, &v2); - TestableTriangleEdge s2 (&v2, &v3); - TestableTriangleEdge s3 (&v3, &v1); + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v2, &v3); + TestableEdge s3 (&v3, &v1); { db::Triangle tri (&s1, &s2, &s3); @@ -305,9 +305,9 @@ TEST(Triangle_contains_small) db::Vertex v2 (0.001, 0.002); db::Vertex v3 (0.002, 0.001); - TestableTriangleEdge s1 (&v1, &v2); - TestableTriangleEdge s2 (&v2, &v3); - TestableTriangleEdge s3 (&v3, &v1); + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v2, &v3); + TestableEdge s3 (&v3, &v1); { db::Triangle tri (&s1, &s2, &s3); @@ -341,9 +341,9 @@ TEST(Triangle_circumcircle) db::Vertex v2 (1, 2); db::Vertex v3 (2, 1); - TestableTriangleEdge s1 (&v1, &v2); - TestableTriangleEdge s2 (&v2, &v3); - TestableTriangleEdge s3 (&v3, &v1); + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v2, &v3); + TestableEdge s3 (&v3, &v1); db::Triangle tri (&s1, &s2, &s3); @@ -368,7 +368,7 @@ TEST(TriangleEdge_basic) db::Vertex v1; db::Vertex v2 (1, 0.5); - TestableTriangleEdge edge (&v1, &v2); + TestableEdge edge (&v1, &v2); EXPECT_EQ (edge.to_string (), "((0, 0), (1, 0.5))"); EXPECT_EQ (edge.is_segment (), false); @@ -390,14 +390,14 @@ TEST(TriangleEdge_triangles) db::Vertex v3 (2, 1); db::Vertex v4 (-1, 2); - std::unique_ptr e1 (new TestableTriangleEdge (&v1, &v2)); - std::unique_ptr e2 (new TestableTriangleEdge (&v2, &v3)); - std::unique_ptr e3 (new TestableTriangleEdge (&v3, &v1)); + std::unique_ptr e1 (new TestableEdge (&v1, &v2)); + std::unique_ptr e2 (new TestableEdge (&v2, &v3)); + std::unique_ptr e3 (new TestableEdge (&v3, &v1)); std::unique_ptr tri (new db::Triangle (e1.get (), e2.get (), e3.get ())); - std::unique_ptr e4 (new TestableTriangleEdge (&v1, &v4)); - std::unique_ptr e5 (new TestableTriangleEdge (&v2, &v4)); + std::unique_ptr e4 (new TestableEdge (&v1, &v4)); + std::unique_ptr e5 (new TestableEdge (&v2, &v4)); std::unique_ptr tri2 (new db::Triangle (e1.get (), e4.get (), e5.get ())); EXPECT_EQ (e1->is_outside (), false); @@ -429,7 +429,7 @@ TEST(TriangleEdge_side_of) db::Vertex v1; db::Vertex v2 (1, 0.5); - TestableTriangleEdge edge (&v1, &v2); + TestableEdge edge (&v1, &v2); EXPECT_EQ (edge.to_string (), "((0, 0), (1, 0.5))"); EXPECT_EQ (edge.side_of (db::Vertex (0, 0)), 0) @@ -441,7 +441,7 @@ TEST(TriangleEdge_side_of) db::Vertex v3 (1, 0); db::Vertex v4 (0, 1); - TestableTriangleEdge edge2 (&v3, &v4); + TestableEdge edge2 (&v3, &v4); EXPECT_EQ (edge2.side_of (db::Vertex(0.2, 0.2)), -1); } @@ -464,26 +464,26 @@ TEST(TriangleEdge_crosses) { VertexHeap heap; - TestableTriangleEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5)); - EXPECT_EQ (s1.crosses (TestableTriangleEdge (heap.make_vertex (-1, -0.5), heap.make_vertex(1, -0.5))), false); - EXPECT_EQ (s1.crosses (TestableTriangleEdge (heap.make_vertex (-1, 0), heap.make_vertex(1, 0))), false); // only cuts - EXPECT_EQ (s1.crosses (TestableTriangleEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(1, 0.5))), false); - EXPECT_EQ (s1.crosses (TestableTriangleEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(2, 0.5))), false); - EXPECT_EQ (s1.crosses (TestableTriangleEdge (heap.make_vertex (-1, 0.25), heap.make_vertex(2, 0.25))), true); - EXPECT_EQ (s1.crosses (TestableTriangleEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(-0.1, 0.5))), false); - EXPECT_EQ (s1.crosses (TestableTriangleEdge (heap.make_vertex (-1, 0.6), heap.make_vertex(0, 0.6))), false); - EXPECT_EQ (s1.crosses (TestableTriangleEdge (heap.make_vertex (-1, 1), heap.make_vertex(1, 1))), false); + TestableEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5)); + EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, -0.5), heap.make_vertex(1, -0.5))), false); + EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0), heap.make_vertex(1, 0))), false); // only cuts + EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(1, 0.5))), false); + EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(2, 0.5))), false); + EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.25), heap.make_vertex(2, 0.25))), true); + EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(-0.1, 0.5))), false); + EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.6), heap.make_vertex(0, 0.6))), false); + EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 1), heap.make_vertex(1, 1))), false); - EXPECT_EQ (s1.crosses_including (TestableTriangleEdge (heap.make_vertex (-1, -0.5), heap.make_vertex(1, -0.5))), false); - EXPECT_EQ (s1.crosses_including (TestableTriangleEdge (heap.make_vertex (-1, 0), heap.make_vertex(1, 0))), true); // only cuts - EXPECT_EQ (s1.crosses_including (TestableTriangleEdge (heap.make_vertex (-1, 0.25), heap.make_vertex(2, 0.25))), true); + EXPECT_EQ (s1.crosses_including (TestableEdge (heap.make_vertex (-1, -0.5), heap.make_vertex(1, -0.5))), false); + EXPECT_EQ (s1.crosses_including (TestableEdge (heap.make_vertex (-1, 0), heap.make_vertex(1, 0))), true); // only cuts + EXPECT_EQ (s1.crosses_including (TestableEdge (heap.make_vertex (-1, 0.25), heap.make_vertex(2, 0.25))), true); } TEST(TriangleEdge_point_on) { VertexHeap heap; - TestableTriangleEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5)); + TestableEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5)); EXPECT_EQ (s1.point_on (db::DPoint (0, 0)), false); // endpoints are not "on" EXPECT_EQ (s1.point_on (db::DPoint (0, -0.5)), false); EXPECT_EQ (s1.point_on (db::DPoint (0.5, 0)), false); @@ -497,8 +497,8 @@ TEST(TriangleEdge_intersection_point) { VertexHeap heap; - TestableTriangleEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5)); - EXPECT_EQ (s1.intersection_point (TestableTriangleEdge (heap.make_vertex (-1, 0.25), heap.make_vertex (2, 0.25))).to_string (), "0.5,0.25"); + TestableEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5)); + EXPECT_EQ (s1.intersection_point (TestableEdge (heap.make_vertex (-1, 0.25), heap.make_vertex (2, 0.25))).to_string (), "0.5,0.25"); } TEST(TriangleEdge_can_flip) @@ -507,11 +507,11 @@ TEST(TriangleEdge_can_flip) db::Vertex v2 (0, 0); db::Vertex v3 (1, 0); db::Vertex v4 (0.5, 1); - TestableTriangleEdge s1 (&v1, &v2); - TestableTriangleEdge s2 (&v1, &v3); - TestableTriangleEdge s3 (&v2, &v3); - TestableTriangleEdge s4 (&v2, &v4); - TestableTriangleEdge s5 (&v3, &v4); + TestableEdge s1 (&v1, &v2); + TestableEdge s2 (&v1, &v3); + TestableEdge s3 (&v2, &v3); + TestableEdge s4 (&v2, &v4); + TestableEdge s5 (&v3, &v4); db::Triangle t1 (&s1, &s2, &s3); db::Triangle t2 (&s3, &s4, &s5); EXPECT_EQ (s3.left () == &t2, true); @@ -532,7 +532,7 @@ TEST(TriangleEdge_distance) db::Vertex v1 (0, 0); db::Vertex v2 (1, 0); - TestableTriangleEdge seg (&v1, &v2); + TestableEdge seg (&v1, &v2); EXPECT_EQ (seg.distance (db::DPoint (0, 0)), 0); EXPECT_EQ (seg.distance (db::DPoint (0, 1)), 1); EXPECT_EQ (seg.distance (db::DPoint (1, 2)), 2); diff --git a/src/db/unit_tests/unit_tests.pro b/src/db/unit_tests/unit_tests.pro index ad9f9e382..f16d29b44 100644 --- a/src/db/unit_tests/unit_tests.pro +++ b/src/db/unit_tests/unit_tests.pro @@ -12,7 +12,8 @@ SOURCES = \ dbFillToolTests.cc \ dbLogTests.cc \ dbObjectWithPropertiesTests.cc \ - dbPLCGraphTest.cc \ + dbPLCGraphTests.cc \ + dbPLCTests.cc \ dbPLCTriangulationTests.cc \ dbPolygonNeighborhoodTests.cc \ dbPropertiesFilterTests.cc \ From 76e039bd2a04a196c78c6574d3033e91e013bfe3 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 13 Apr 2025 23:06:49 +0200 Subject: [PATCH 08/58] Removing obsolete dbTriangle/dbTriangles --- src/db/db/db.pro | 4 - src/db/db/dbPLC.h | 1 - src/db/db/dbRegionProcessors.cc | 8 +- src/db/db/dbRegionProcessors.h | 4 +- src/db/db/dbTriangle.cc | 607 --------- src/db/db/dbTriangle.h | 579 -------- src/db/db/dbTriangles.cc | 1784 ------------------------- src/db/db/dbTriangles.h | 360 ----- src/db/db/gsiDeclDbPolygon.cc | 36 +- src/db/unit_tests/dbTriangleTests.cc | 549 -------- src/db/unit_tests/dbTrianglesTests.cc | 1538 --------------------- src/db/unit_tests/unit_tests.pro | 2 - 12 files changed, 26 insertions(+), 5446 deletions(-) delete mode 100644 src/db/db/dbTriangle.cc delete mode 100644 src/db/db/dbTriangle.h delete mode 100644 src/db/db/dbTriangles.cc delete mode 100644 src/db/db/dbTriangles.h delete mode 100644 src/db/unit_tests/dbTriangleTests.cc delete mode 100644 src/db/unit_tests/dbTrianglesTests.cc diff --git a/src/db/db/db.pro b/src/db/db/db.pro index 888a22dcc..93795a97d 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -107,8 +107,6 @@ SOURCES = \ dbTextWriter.cc \ dbTilingProcessor.cc \ dbTrans.cc \ - dbTriangle.cc \ - dbTriangles.cc \ dbUserObject.cc \ dbUtils.cc \ dbVector.cc \ @@ -347,8 +345,6 @@ HEADERS = \ dbTextWriter.h \ dbTilingProcessor.h \ dbTrans.h \ - dbTriangle.h \ - dbTriangles.h \ dbTypes.h \ dbUserObject.h \ dbUtils.h \ diff --git a/src/db/db/dbPLC.h b/src/db/db/dbPLC.h index 9d7b35e43..14ac8d01c 100644 --- a/src/db/db/dbPLC.h +++ b/src/db/db/dbPLC.h @@ -24,7 +24,6 @@ #define HDR_dbPLC #include "dbCommon.h" -#include "dbTriangle.h" #include "dbBox.h" #include "dbRegion.h" diff --git a/src/db/db/dbRegionProcessors.cc b/src/db/db/dbRegionProcessors.cc index 3c3449107..730f6ea7a 100644 --- a/src/db/db/dbRegionProcessors.cc +++ b/src/db/db/dbRegionProcessors.cc @@ -453,8 +453,8 @@ TriangulationProcessor::process (const db::Polygon &poly, std::vector - -namespace db -{ - -// ------------------------------------------------------------------------------------- -// Vertex implementation - -Vertex::Vertex () - : DPoint (), m_is_precious (false) -{ - // .. nothing yet .. -} - -Vertex::Vertex (const db::DPoint &p) - : DPoint (p), m_is_precious (false) -{ - // .. nothing yet .. -} - -Vertex::Vertex (const Vertex &v) - : DPoint (), m_is_precious (false) -{ - operator= (v); -} - -Vertex &Vertex::operator= (const Vertex &v) -{ - if (this != &v) { - // NOTE: edges are not copied! - db::DPoint::operator= (v); - m_is_precious = v.m_is_precious; - } - return *this; -} - -Vertex::Vertex (db::DCoord x, db::DCoord y) - : DPoint (x, y), m_is_precious (false) -{ - // .. nothing yet .. -} - -bool -Vertex::is_outside () const -{ - for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) { - if ((*e)->is_outside ()) { - return true; - } - } - return false; -} - -std::vector -Vertex::triangles () const -{ - std::set seen; - std::vector res; - for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) { - for (auto t = (*e)->begin_triangles (); t != (*e)->end_triangles (); ++t) { - if (seen.insert (t.operator-> ()).second) { - res.push_back (t.operator-> ()); - } - } - } - return res; -} - -bool -Vertex::has_edge (const TriangleEdge *edge) const -{ - for (auto e = mp_edges.begin (); e != mp_edges.end (); ++e) { - if (*e == edge) { - return true; - } - } - return false; -} - -size_t -Vertex::num_edges (int max_count) const -{ - if (max_count < 0) { - // NOTE: this can be slow for a std::list, so we have max_count to limit this effort - return mp_edges.size (); - } else { - size_t n = 0; - for (auto i = mp_edges.begin (); i != mp_edges.end () && --max_count >= 0; ++i) { - ++n; - } - return n; - } -} - -std::string -Vertex::to_string (bool with_id) const -{ - std::string res = tl::sprintf ("(%.12g, %.12g)", x (), y()); - if (with_id) { - res += tl::sprintf ("[%x]", (size_t)this); - } - return res; -} - -int -Vertex::in_circle (const DPoint &point, const DPoint ¢er, double radius) -{ - double dx = point.x () - center.x (); - double dy = point.y () - center.y (); - double d2 = dx * dx + dy * dy; - double r2 = radius * radius; - double delta = fabs (d2 + r2) * db::epsilon; - if (d2 < r2 - delta) { - return 1; - } else if (d2 < r2 + delta) { - return 0; - } else { - return -1; - } -} - -// ------------------------------------------------------------------------------------- -// TriangleEdge implementation - -TriangleEdge::TriangleEdge () - : mp_v1 (0), mp_v2 (0), mp_left (), mp_right (), m_level (0), m_id (0), m_is_segment (false) -{ - // .. nothing yet .. -} - -TriangleEdge::TriangleEdge (Vertex *v1, Vertex *v2) - : mp_v1 (v1), mp_v2 (v2), mp_left (), mp_right (), m_level (0), m_id (0), m_is_segment (false) -{ - // .. nothing yet .. -} - -void -TriangleEdge::set_left (Triangle *t) -{ - mp_left = t; -} - -void -TriangleEdge::set_right (Triangle *t) -{ - mp_right = t; -} - -void -TriangleEdge::link () -{ - mp_v1->mp_edges.push_back (this); - m_ec_v1 = --mp_v1->mp_edges.end (); - - mp_v2->mp_edges.push_back (this); - m_ec_v2 = --mp_v2->mp_edges.end (); -} - -void -TriangleEdge::unlink () -{ - if (mp_v1) { - mp_v1->remove_edge (m_ec_v1); - } - if (mp_v2) { - mp_v2->remove_edge (m_ec_v2); - } - mp_v1 = mp_v2 = 0; -} - -Triangle * -TriangleEdge::other (const Triangle *t) const -{ - if (t == mp_left) { - return mp_right; - } - if (t == mp_right) { - return mp_left; - } - tl_assert (false); - return 0; -} - -Vertex * -TriangleEdge::other (const Vertex *t) const -{ - if (t == mp_v1) { - return mp_v2; - } - if (t == mp_v2) { - return mp_v1; - } - tl_assert (false); - return 0; -} - -bool -TriangleEdge::has_vertex (const Vertex *v) const -{ - return mp_v1 == v || mp_v2 == v; -} - -Vertex * -TriangleEdge::common_vertex (const TriangleEdge *other) const -{ - if (has_vertex (other->v1 ())) { - return (other->v1 ()); - } - if (has_vertex (other->v2 ())) { - return (other->v2 ()); - } - return 0; -} - -std::string -TriangleEdge::to_string (bool with_id) const -{ - std::string res = std::string ("(") + mp_v1->to_string (with_id) + ", " + mp_v2->to_string (with_id) + ")"; - if (with_id) { - res += tl::sprintf ("[%x]", (size_t)this); - } - return res; -} - -double -TriangleEdge::distance (const db::DEdge &e, const db::DPoint &p) -{ - double l = db::sprod (p - e.p1 (), e.d ()) / e.d ().sq_length (); - db::DPoint pp; - if (l <= 0.0) { - pp = e.p1 (); - } else if (l >= 1.0) { - pp = e.p2 (); - } else { - pp = e.p1 () + e.d () * l; - } - return (p - pp).length (); -} - -bool -TriangleEdge::crosses (const db::DEdge &e, const db::DEdge &other) -{ - return e.side_of (other.p1 ()) * e.side_of (other.p2 ()) < 0 && - other.side_of (e.p1 ()) * other.side_of (e.p2 ()) < 0; -} - -bool -TriangleEdge::crosses_including (const db::DEdge &e, const db::DEdge &other) -{ - return e.side_of (other.p1 ()) * e.side_of (other.p2 ()) <= 0 && - other.side_of (e.p1 ()) * other.side_of (e.p2 ()) <= 0; -} - -db::DPoint -TriangleEdge::intersection_point (const db::DEdge &e, const db::DEdge &other) -{ - return e.intersect_point (other).second; -} - -bool -TriangleEdge::point_on (const db::DEdge &edge, const db::DPoint &point) -{ - if (edge.side_of (point) != 0) { - return false; - } else { - return db::sprod_sign (point - edge.p1 (), edge.d ()) * db::sprod_sign(point - edge.p2 (), edge.d ()) < 0; - } -} - -bool -TriangleEdge::can_flip () const -{ - if (! left () || ! right ()) { - return false; - } - - const db::Vertex *v1 = left ()->opposite (this); - const db::Vertex *v2 = right ()->opposite (this); - return crosses (db::DEdge (*v1, *v2)); -} - -bool -TriangleEdge::can_join_via (const Vertex *vertex) const -{ - if (! left () || ! right ()) { - return false; - } - - tl_assert (has_vertex (vertex)); - const db::Vertex *v1 = left ()->opposite (this); - const db::Vertex *v2 = right ()->opposite (this); - return db::DEdge (*v1, *v2).side_of (*vertex) == 0; -} - -bool -TriangleEdge::is_outside () const -{ - return left () == 0 || right () == 0; -} - -bool -TriangleEdge::is_for_outside_triangles () const -{ - return (left () && left ()->is_outside ()) || (right () && right ()->is_outside ()); -} - -bool -TriangleEdge::has_triangle (const Triangle *t) const -{ - return t != 0 && (left () == t || right () == t); -} - -// ------------------------------------------------------------------------------------- -// Triangle implementation - -Triangle::Triangle () - : m_is_outside (false), m_id (0) -{ - for (int i = 0; i < 3; ++i) { - mp_v[i] = 0; - mp_e[i] = 0; - } -} - -Triangle::Triangle (TriangleEdge *e1, TriangleEdge *e2, TriangleEdge *e3) - : m_is_outside (false), m_id (0) -{ - mp_e[0] = e1; - mp_v[0] = e1->v1 (); - mp_v[1] = e1->v2 (); - - if (e2->has_vertex (mp_v[1])) { - mp_e[1] = e2; - mp_e[2] = e3; - } else { - mp_e[1] = e3; - mp_e[2] = e2; - } - mp_v[2] = mp_e[1]->other (mp_v[1]); - - // enforce clockwise orientation - int s = db::vprod_sign (*mp_v[2] - *mp_v[0], *mp_v[1] - *mp_v[0]); - if (s < 0) { - std::swap (mp_v[2], mp_v[1]); - } else if (s == 0) { - // Triangle is not orientable - tl_assert (false); - } - - // establish link to edges - for (int i = 0; i < 3; ++i) { - - TriangleEdge *e = mp_e[i]; - - unsigned int i1 = 0; - for ( ; e->v1 () != mp_v[i1] && i1 < 3; ++i1) - ; - unsigned int i2 = 0; - for ( ; e->v2 () != mp_v[i2] && i2 < 3; ++i2) - ; - - if ((i1 + 1) % 3 == i2) { - e->set_right (this); - } else { - e->set_left (this); - } - - } -} - -Triangle::~Triangle () -{ - unlink (); -} - -void -Triangle::unlink () -{ - for (int i = 0; i != 3; ++i) { - db::TriangleEdge *e = mp_e[i]; - if (e->left () == this) { - e->set_left (0); - } - if (e->right () == this) { - e->set_right (0); - } - } -} - -std::string -Triangle::to_string (bool with_id) const -{ - std::string res = "("; - for (int i = 0; i < 3; ++i) { - if (i > 0) { - res += ", "; - } - if (vertex (i)) { - res += vertex (i)->to_string (with_id); - } else { - res += "(null)"; - } - } - res += ")"; - return res; -} - -double -Triangle::area () const -{ - return fabs (db::vprod (mp_e[0]->d (), mp_e[1]->d ())) * 0.5; -} - -db::DBox -Triangle::bbox () const -{ - db::DBox box; - for (int i = 0; i < 3; ++i) { - box += *mp_v[i]; - } - return box; -} - - -std::pair -Triangle::circumcircle (bool *ok) const -{ - // see https://en.wikipedia.org/wiki/Circumcircle - // we set A=(0,0), so the formulas simplify - - if (ok) { - *ok = true; - } - - db::DVector b = *mp_v[1] - *mp_v[0]; - db::DVector c = *mp_v[2] - *mp_v[0]; - - double b2 = b.sq_length (); - double c2 = c.sq_length (); - - double sx = 0.5 * (b2 * c.y () - c2 * b.y ()); - double sy = 0.5 * (b.x () * c2 - c.x() * b2); - - double a1 = b.x() * c.y(); - double a2 = c.x() * b.y(); - double a = a1 - a2; - double a_abs = std::abs (a); - - if (a_abs < (std::abs (a1) + std::abs (a2)) * db::epsilon) { - if (ok) { - *ok = false; - return std::make_pair (db::DPoint (), 0.0); - } else { - tl_assert (false); - } - } - - double radius = sqrt (sx * sx + sy * sy) / a_abs; - db::DPoint center = *mp_v[0] + db::DVector (sx / a, sy / a); - - return std::make_pair (center, radius); -} - -Vertex * -Triangle::opposite (const TriangleEdge *edge) const -{ - for (int i = 0; i < 3; ++i) { - Vertex *v = mp_v[i]; - if (! edge->has_vertex (v)) { - return v; - } - } - tl_assert (false); -} - -TriangleEdge * -Triangle::opposite (const Vertex *vertex) const -{ - for (int i = 0; i < 3; ++i) { - TriangleEdge *e = mp_e[i]; - if (! e->has_vertex (vertex)) { - return e; - } - } - tl_assert (false); -} - -TriangleEdge * -Triangle::find_edge_with (const Vertex *v1, const Vertex *v2) const -{ - for (int i = 0; i < 3; ++i) { - TriangleEdge *e = mp_e[i]; - if (e->has_vertex (v1) && e->has_vertex (v2)) { - return e; - } - } - tl_assert (false); -} - -TriangleEdge * -Triangle::common_edge (const Triangle *other) const -{ - for (int i = 0; i < 3; ++i) { - TriangleEdge *e = mp_e[i];; - if (e->other (this) == other) { - return e; - } - } - return 0; -} - -int -Triangle::contains (const db::DPoint &point) const -{ - auto c = *mp_v[2] - *mp_v[0]; - auto b = *mp_v[1] - *mp_v[0]; - - int vps = db::vprod_sign (c, b); - if (vps == 0) { - return db::vprod_sign (point - *mp_v[0], b) == 0 && db::vprod_sign (point - *mp_v[0], c) == 0 ? 0 : -1; - } - - int res = 1; - - const Vertex *vl = mp_v[2]; - for (int i = 0; i < 3; ++i) { - const Vertex *v = mp_v[i]; - int n = db::vprod_sign (point - *vl, *v - *vl) * vps; - if (n < 0) { - return -1; - } else if (n == 0) { - res = 0; - } - vl = v; - } - - return res; -} - -double -Triangle::min_edge_length () const -{ - double lmin = mp_e[0]->d ().length (); - for (int i = 1; i < 3; ++i) { - lmin = std::min (lmin, mp_e[i]->d ().length ()); - } - return lmin; -} - -double -Triangle::b () const -{ - double lmin = min_edge_length (); - bool ok = false; - auto cr = circumcircle (&ok); - return ok ? lmin / cr.second : 0.0; -} - -bool -Triangle::has_segment () const -{ - for (int i = 0; i < 3; ++i) { - if (mp_e[i]->is_segment ()) { - return true; - } - } - return false; -} - -unsigned int -Triangle::num_segments () const -{ - unsigned int n = 0; - for (int i = 0; i < 3; ++i) { - if (mp_e[i]->is_segment ()) { - ++n; - } - } - return n; -} - -} diff --git a/src/db/db/dbTriangle.h b/src/db/db/dbTriangle.h deleted file mode 100644 index 332d1c695..000000000 --- a/src/db/db/dbTriangle.h +++ /dev/null @@ -1,579 +0,0 @@ - -/* - - KLayout Layout Viewer - Copyright (C) 2006-2025 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_dbTriangle -#define HDR_dbTriangle - -#include "dbCommon.h" -#include "dbPoint.h" -#include "dbEdge.h" - -#include "tlObjectCollection.h" -#include "tlList.h" - -#include -#include -#include -#include - -namespace db -{ - -class Triangle; -class TriangleEdge; - -/** - * @brief A class representing a vertex in a Delaunay triangulation graph - * - * The vertex carries information about the connected edges and - * an integer value that can be used in traversal algorithms - * ("level") - */ -class DB_PUBLIC Vertex - : public db::DPoint -{ -public: - typedef std::list edges_type; - typedef edges_type::const_iterator edges_iterator; - typedef edges_type::iterator edges_iterator_non_const; - - Vertex (); - Vertex (const DPoint &p); - Vertex (const Vertex &v); - Vertex (db::DCoord x, db::DCoord y); - - Vertex &operator= (const Vertex &v); - - bool is_outside () const; - std::vector triangles () const; - - edges_iterator begin_edges () const { return mp_edges.begin (); } - edges_iterator end_edges () const { return mp_edges.end (); } - size_t num_edges (int max_count = -1) const; - - bool has_edge (const TriangleEdge *edge) const; - - void set_is_precious (bool f) { m_is_precious = f; } - bool is_precious () const { return m_is_precious; } - - std::string to_string (bool with_id = false) const; - - /** - * @brief Returns 1 is the point is inside the circle, 0 if on the circle and -1 if outside - * TODO: Move to db::DPoint - */ - static int in_circle (const db::DPoint &point, const db::DPoint ¢er, double radius); - - /** - * @brief Returns 1 is this point is inside the circle, 0 if on the circle and -1 if outside - */ - int in_circle (const db::DPoint ¢er, double radius) const - { - return in_circle (*this, center, radius); - } - -private: - friend class TriangleEdge; - - void remove_edge (const edges_iterator_non_const &ec) - { - mp_edges.erase (ec); - } - - edges_type mp_edges; - bool m_is_precious; -}; - -/** - * @brief A class representing an edge in the Delaunay triangulation graph - */ -class DB_PUBLIC TriangleEdge -{ -public: - class TriangleIterator - { - public: - typedef Triangle value_type; - typedef Triangle &reference; - typedef Triangle *pointer; - - reference operator*() const - { - return *operator-> (); - } - - pointer operator->() const - { - return m_index ? mp_edge->right () : mp_edge->left (); - } - - bool operator== (const TriangleIterator &other) const - { - return m_index == other.m_index; - } - - bool operator!= (const TriangleIterator &other) const - { - return !operator== (other); - } - - TriangleIterator &operator++ () - { - while (++m_index < 2 && operator-> () == 0) - ; - return *this; - } - - private: - friend class TriangleEdge; - - TriangleIterator (const TriangleEdge *edge) - : mp_edge (edge), m_index (0) - { - if (! edge) { - m_index = 2; - } else { - --m_index; - operator++ (); - } - } - - const TriangleEdge *mp_edge; - unsigned int m_index; - }; - - TriangleEdge (); - TriangleEdge (Vertex *v1, Vertex *v2); - - Vertex *v1 () const { return mp_v1; } - Vertex *v2 () const { return mp_v2; } - - void reverse () - { - std::swap (mp_v1, mp_v2); - std::swap (mp_left, mp_right); - } - - Triangle *left () const { return mp_left; } - Triangle *right () const { return mp_right; } - - TriangleIterator begin_triangles () const - { - return TriangleIterator (this); - } - - TriangleIterator end_triangles () const - { - return TriangleIterator (0); - } - - void set_level (size_t l) { m_level = l; } - size_t level () const { return m_level; } - - void set_id (size_t id) { m_id = id; } - size_t id () const { return m_id; } - - void set_is_segment (bool is_seg) { m_is_segment = is_seg; } - bool is_segment () const { return m_is_segment; } - - std::string to_string (bool with_id = false) const; - - /** - * @brief Converts to an db::DEdge - */ - db::DEdge edge () const - { - return db::DEdge (*mp_v1, *mp_v2); - } - - /** - * @brief Returns the distance of the given point to the edge - * - * The distance is the minimum distance of the point to one point from the edge. - * TODO: Move to db::DEdge - */ - static double distance (const db::DEdge &e, const db::DPoint &p); - - /** - * @brief Returns the distance of the given point to the edge - * - * The distance is the minimum distance of the point to one point from the edge. - */ - double distance (const db::DPoint &p) const - { - return distance (edge (), p); - } - - /** - * @brief Returns a value indicating whether this edge crosses the other one - * - * "crosses" is true, if both edges share at least one point which is not an endpoint - * of one of the edges. - * TODO: Move to db::DEdge - */ - static bool crosses (const db::DEdge &e, const db::DEdge &other); - - /** - * @brief Returns a value indicating whether this edge crosses the other one - * - * "crosses" is true, if both edges share at least one point which is not an endpoint - * of one of the edges. - */ - bool crosses (const db::DEdge &other) const - { - return crosses (edge (), other); - } - - /** - * @brief Returns a value indicating whether this edge crosses the other one - * - * "crosses" is true, if both edges share at least one point which is not an endpoint - * of one of the edges. - */ - bool crosses (const db::TriangleEdge &other) const - { - return crosses (edge (), other.edge ()); - } - - /** - * @brief Returns a value indicating whether this edge crosses the other one - * "crosses" is true, if both edges share at least one point. - * TODO: Move to db::DEdge - */ - static bool crosses_including (const db::DEdge &e, const db::DEdge &other); - - /** - * @brief Returns a value indicating whether this edge crosses the other one - * "crosses" is true, if both edges share at least one point. - */ - bool crosses_including (const db::DEdge &other) const - { - return crosses_including (edge (), other); - } - - /** - * @brief Returns a value indicating whether this edge crosses the other one - * "crosses" is true, if both edges share at least one point. - */ - bool crosses_including (const db::TriangleEdge &other) const - { - return crosses_including (edge (), other.edge ()); - } - - /** - * @brief Gets the intersection point - * TODO: Move to db::DEdge - */ - static db::DPoint intersection_point (const db::DEdge &e, const DEdge &other); - - /** - * @brief Gets the intersection point - */ - db::DPoint intersection_point (const db::DEdge &other) const - { - return intersection_point (edge (), other); - } - - /** - * @brief Gets the intersection point - */ - db::DPoint intersection_point (const TriangleEdge &other) const - { - return intersection_point (edge (), other.edge ()); - } - - /** - * @brief Returns a value indicating whether the point is on the edge - * TODO: Move to db::DEdge - */ - static bool point_on (const db::DEdge &edge, const db::DPoint &point); - - /** - * @brief Returns a value indicating whether the point is on the edge - */ - bool point_on (const db::DPoint &point) const - { - return point_on (edge (), point); - } - - /** - * @brief Gets the side the point is on - * - * -1 is for "left", 0 is "on" and +1 is "right" - * TODO: correct to same definition as db::Edge (negative) - */ - static int side_of (const db::DEdge &e, const db::DPoint &point) - { - return -e.side_of (point); - } - - /** - * @brief Gets the side the point is on - * - * -1 is for "left", 0 is "on" and +1 is "right" - * TODO: correct to same definition as db::Edge (negative) - */ - int side_of (const db::DPoint &p) const - { - return -edge ().side_of (p); - } - - /** - * @brief Gets the distance vector - */ - db::DVector d () const - { - return *mp_v2 - *mp_v1; - } - - /** - * @brief Gets the other triangle for the given one - */ - Triangle *other (const Triangle *) const; - - /** - * @brief Gets the other vertex for the given one - */ - Vertex *other (const Vertex *) const; - - /** - * @brief Gets a value indicating whether the edge has the given vertex - */ - bool has_vertex (const Vertex *) const; - - /** - * @brief Gets the common vertex of the other edge and this edge or null if there is no common vertex - */ - Vertex *common_vertex (const TriangleEdge *other) const; - - /** - * @brief Returns a value indicating whether this edge can be flipped - */ - bool can_flip () const; - - /** - * @brief Returns a value indicating whether the edge separates two triangles that can be joined into one (via the given vertex) - */ - bool can_join_via (const Vertex *vertex) const; - - /** - * @brief Returns a value indicating whether this edge is an outside edge (no other triangles) - */ - bool is_outside () const; - - /** - * @brief Returns a value indicating whether this edge belongs to outside triangles - */ - bool is_for_outside_triangles () const; - - /** - * @brief Returns a value indicating whether t is attached to this edge - */ - bool has_triangle (const Triangle *t) const; - -protected: - void unlink (); - void link (); - -private: - friend class Triangle; - friend class Triangles; - - Vertex *mp_v1, *mp_v2; - Triangle *mp_left, *mp_right; - Vertex::edges_iterator_non_const m_ec_v1, m_ec_v2; - size_t m_level; - size_t m_id; - bool m_is_segment; - - void set_left (Triangle *t); - void set_right (Triangle *t); -}; - -/** - * @brief A compare function that compares triangles by ID - * - * The ID acts as a more predicable unique ID for the object in sets and maps. - */ -struct TriangleEdgeLessFunc -{ - bool operator () (TriangleEdge *a, TriangleEdge *b) const - { - return a->id () < b->id (); - } -}; - -/** - * @brief A class representing a triangle - */ -class DB_PUBLIC Triangle - : public tl::list_node, public tl::Object -{ -public: - Triangle (); - Triangle (TriangleEdge *e1, TriangleEdge *e2, TriangleEdge *e3); - - ~Triangle (); - - void unlink (); - - void set_id (size_t id) { m_id = id; } - size_t id () const { return m_id; } - - bool is_outside () const { return m_is_outside; } - void set_outside (bool o) { m_is_outside = o; } - - std::string to_string (bool with_id = false) const; - - /** - * @brief Gets the nth vertex (n wraps around and can be negative) - * The vertexes are oriented clockwise. - */ - inline Vertex *vertex (int n) const - { - if (n >= 0 && n < 3) { - return mp_v[n]; - } else { - return mp_v[(n + 3) % 3]; - } - } - - /** - * @brief Gets the nth edge (n wraps around and can be negative) - */ - inline TriangleEdge *edge (int n) const - { - if (n >= 0 && n < 3) { - return mp_e[n]; - } else { - return mp_e[(n + 3) % 3]; - } - } - - /** - * @brief Gets the area - */ - double area () const; - - /** - * @brief Returns the bounding box of the triangle - */ - db::DBox bbox () const; - - /** - * @brief Gets the center point and radius of the circumcircle - * If ok is non-null, it will receive a boolean value indicating whether the circumcircle is valid. - * An invalid circumcircle is an indicator for a degenerated triangle with area 0 (or close to). - */ - std::pair circumcircle (bool *ok = 0) const; - - /** - * @brief Gets the vertex opposite of the given edge - */ - Vertex *opposite (const TriangleEdge *edge) const; - - /** - * @brief Gets the edge opposite of the given vertex - */ - TriangleEdge *opposite (const Vertex *vertex) const; - - /** - * @brief Gets the edge with the given vertexes - */ - TriangleEdge *find_edge_with (const Vertex *v1, const Vertex *v2) const; - - /** - * @brief Finds the common edge for both triangles - */ - TriangleEdge *common_edge (const Triangle *other) const; - - /** - * @brief Returns a value indicating whether the point is inside (1), on the triangle (0) or outside (-1) - */ - int contains (const db::DPoint &point) const; - - /** - * @brief Gets a value indicating whether the triangle has the given vertex - */ - inline bool has_vertex (const db::Vertex *v) const - { - return mp_v[0] == v || mp_v[1] == v || mp_v[2] == v; - } - - /** - * @brief Gets a value indicating whether the triangle has the given edge - */ - inline bool has_edge (const db::TriangleEdge *e) const - { - return mp_e[0] == e || mp_e[1] == e || mp_e[2] == e; - } - - /** - * @brief Returns the minimum edge length - */ - double min_edge_length () const; - - /** - * @brief Returns the min edge length to circumcircle radius ratio - */ - double b () const; - - /** - * @brief Returns a value indicating whether the triangle borders to a segment - */ - bool has_segment () const; - - /** - * @brief Returns the number of segments the triangle borders to - */ - unsigned int num_segments () const; - -private: - bool m_is_outside; - TriangleEdge *mp_e[3]; - db::Vertex *mp_v[3]; - size_t m_id; - - // no copying - Triangle &operator= (const Triangle &); - Triangle (const Triangle &); -}; - -/** - * @brief A compare function that compares triangles by ID - * - * The ID acts as a more predicable unique ID for the object in sets and maps. - */ -struct TriangleLessFunc -{ - bool operator () (Triangle *a, Triangle *b) const - { - return a->id () < b->id (); - } -}; - -} - -#endif - diff --git a/src/db/db/dbTriangles.cc b/src/db/db/dbTriangles.cc deleted file mode 100644 index e712abaa2..000000000 --- a/src/db/db/dbTriangles.cc +++ /dev/null @@ -1,1784 +0,0 @@ - -/* - - KLayout Layout Viewer - Copyright (C) 2006-2025 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 "dbTriangles.h" -#include "dbLayout.h" -#include "dbWriter.h" -#include "tlStream.h" -#include "tlLog.h" -#include "tlTimer.h" - -#include -#include -#include -#include - -namespace db -{ - -static inline bool is_equal (const db::DPoint &a, const db::DPoint &b) -{ - return std::abs (a.x () - b.x ()) < std::max (1.0, (std::abs (a.x ()) + std::abs (b.x ()))) * db::epsilon && - std::abs (a.y () - b.y ()) < std::max (1.0, (std::abs (a.y ()) + std::abs (b.y ()))) * db::epsilon; -} - -Triangles::Triangles () - : m_is_constrained (false), m_level (0), m_id (0), m_flips (0), m_hops (0) -{ - // .. nothing yet .. -} - -Triangles::~Triangles () -{ - clear (); -} - -db::Vertex * -Triangles::create_vertex (double x, double y) -{ - m_vertex_heap.push_back (db::Vertex (x, y)); - return &m_vertex_heap.back (); -} - -db::Vertex * -Triangles::create_vertex (const db::DPoint &pt) -{ - m_vertex_heap.push_back (pt); - return &m_vertex_heap.back (); -} - -db::TriangleEdge * -Triangles::create_edge (db::Vertex *v1, db::Vertex *v2) -{ - db::TriangleEdge *edge = 0; - - if (! m_returned_edges.empty ()) { - edge = m_returned_edges.back (); - m_returned_edges.pop_back (); - *edge = db::TriangleEdge (v1, v2); - } else { - m_edges_heap.push_back (db::TriangleEdge (v1, v2)); - edge = &m_edges_heap.back (); - } - - edge->link (); - edge->set_id (++m_id); - return edge; -} - -db::Triangle * -Triangles::create_triangle (TriangleEdge *e1, TriangleEdge *e2, TriangleEdge *e3) -{ - db::Triangle *res = new db::Triangle (e1, e2, e3); - res->set_id (++m_id); - mp_triangles.push_back (res); - - return res; -} - -void -Triangles::remove_triangle (db::Triangle *tri) -{ - db::TriangleEdge *edges [3]; - for (int i = 0; i < 3; ++i) { - edges [i] = tri->edge (i); - } - - delete tri; - - // clean up edges we do no longer need - for (int i = 0; i < 3; ++i) { - db::TriangleEdge *e = edges [i]; - if (e && e->left () == 0 && e->right () == 0 && e->v1 ()) { - e->unlink (); - m_returned_edges.push_back (e); - } - } -} - -void -Triangles::init_box (const db::DBox &box) -{ - double xmin = box.left (), xmax = box.right (); - double ymin = box.bottom (), ymax = box.top (); - - db::Vertex *vbl = create_vertex (xmin, ymin); - db::Vertex *vtl = create_vertex (xmin, ymax); - db::Vertex *vbr = create_vertex (xmax, ymin); - db::Vertex *vtr = create_vertex (xmax, ymax); - - db::TriangleEdge *sl = create_edge (vbl, vtl); - db::TriangleEdge *sd = create_edge (vtl, vbr); - db::TriangleEdge *sb = create_edge (vbr, vbl); - - db::TriangleEdge *sr = create_edge (vbr, vtr); - db::TriangleEdge *st = create_edge (vtr, vtl); - - create_triangle (sl, sd, sb); - create_triangle (sd, sr, st); -} - -std::string -Triangles::to_string () -{ - std::string res; - for (auto t = mp_triangles.begin (); t != mp_triangles.end (); ++t) { - if (! res.empty ()) { - res += ", "; - } - res += t->to_string (); - } - return res; -} - -db::DBox -Triangles::bbox () const -{ - db::DBox box; - for (auto t = mp_triangles.begin (); t != mp_triangles.end (); ++t) { - box += t->bbox (); - } - return box; -} - -bool -Triangles::check (bool check_delaunay) const -{ - bool res = true; - - if (check_delaunay) { - for (auto t = mp_triangles.begin (); t != mp_triangles.end (); ++t) { - auto cp = t->circumcircle (); - auto vi = find_inside_circle (cp.first, cp.second); - if (! vi.empty ()) { - res = false; - tl::error << "(check error) triangle does not meet Delaunay criterion: " << t->to_string (); - for (auto v = vi.begin (); v != vi.end (); ++v) { - tl::error << " vertex inside circumcircle: " << (*v)->to_string (true); - } - } - } - } - - for (auto t = mp_triangles.begin (); t != mp_triangles.end (); ++t) { - for (int i = 0; i < 3; ++i) { - if (! t->edge (i)->has_triangle (t.operator-> ())) { - tl::error << "(check error) edges " << t->edge (i)->to_string (true) - << " attached to triangle " << t->to_string (true) << " does not refer to this triangle"; - res = false; - } - } - } - - for (auto e = m_edges_heap.begin (); e != m_edges_heap.end (); ++e) { - - if (!e->left () && !e->right ()) { - continue; - } - - if (e->left () && e->right ()) { - if (e->left ()->is_outside () != e->right ()->is_outside () && ! e->is_segment ()) { - tl::error << "(check error) edge " << e->to_string (true) << " splits an outside and inside triangle, but is not a segment"; - res = false; - } - } - - for (auto t = e->begin_triangles (); t != e->end_triangles (); ++t) { - if (! t->has_edge (e.operator-> ())) { - tl::error << "(check error) edge " << e->to_string (true) << " not found in adjacent triangle " << t->to_string (true); - res = false; - } - if (! t->has_vertex (e->v1 ())) { - tl::error << "(check error) edges " << e->to_string (true) << " vertex 1 not found in adjacent triangle " << t->to_string (true); - res = false; - } - if (! t->has_vertex (e->v2 ())) { - tl::error << "(check error) edges " << e->to_string (true) << " vertex 2 not found in adjacent triangle " << t->to_string (true); - res = false; - } - db::Vertex *vopp = t->opposite (e.operator-> ()); - double sgn = (e->left () == t.operator-> ()) ? 1.0 : -1.0; - double vp = db::vprod (e->d(), *vopp - *e->v1 ()); // positive if on left side - if (vp * sgn <= 0.0) { - const char * side_str = sgn > 0.0 ? "left" : "right"; - tl::error << "(check error) external point " << vopp->to_string (true) << " not on " << side_str << " side of edge " << e->to_string (true); - res = false; - } - } - - if (! e->v1 ()->has_edge (e.operator-> ())) { - tl::error << "(check error) edge " << e->to_string (true) << " vertex 1 does not list this edge"; - res = false; - } - if (! e->v2 ()->has_edge (e.operator-> ())) { - tl::error << "(check error) edge " << e->to_string (true) << " vertex 2 does not list this edge"; - res = false; - } - - } - - for (auto v = m_vertex_heap.begin (); v != m_vertex_heap.end (); ++v) { - unsigned int num_outside_edges = 0; - for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { - if ((*e)->is_outside ()) { - ++num_outside_edges; - } - } - if (num_outside_edges > 0 && num_outside_edges != 2) { - tl::error << "(check error) vertex " << v->to_string (true) << " has " << num_outside_edges << " outside edges (can only be 2)"; - res = false; - for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { - if ((*e)->is_outside ()) { - tl::error << " Outside edge is " << (*e)->to_string (true); - } - } - } - } - - return res; -} - -db::Layout * -Triangles::to_layout (bool decompose_by_id) const -{ - db::Layout *layout = new db::Layout (); - layout->dbu (0.001); - - auto dbu_trans = db::CplxTrans (layout->dbu ()).inverted (); - - db::Cell &top = layout->cell (layout->add_cell ("DUMP")); - unsigned int l1 = layout->insert_layer (db::LayerProperties (1, 0)); - unsigned int l2 = layout->insert_layer (db::LayerProperties (2, 0)); - unsigned int l10 = layout->insert_layer (db::LayerProperties (10, 0)); - unsigned int l20 = layout->insert_layer (db::LayerProperties (20, 0)); - unsigned int l21 = layout->insert_layer (db::LayerProperties (21, 0)); - unsigned int l22 = layout->insert_layer (db::LayerProperties (22, 0)); - - for (auto t = mp_triangles.begin (); t != mp_triangles.end (); ++t) { - db::DPoint pts[3]; - for (int i = 0; i < 3; ++i) { - pts[i] = *t->vertex (i); - } - db::DPolygon poly; - poly.assign_hull (pts + 0, pts + 3); - top.shapes (t->is_outside () ? l2 : l1).insert (dbu_trans * poly); - if (decompose_by_id) { - if ((t->id () & 1) != 0) { - top.shapes (l20).insert (dbu_trans * poly); - } - if ((t->id () & 2) != 0) { - top.shapes (l21).insert (dbu_trans * poly); - } - if ((t->id () & 4) != 0) { - top.shapes (l22).insert (dbu_trans * poly); - } - } - } - - for (auto e = m_edges_heap.begin (); e != m_edges_heap.end (); ++e) { - if ((e->left () || e->right ()) && e->is_segment ()) { - top.shapes (l10).insert (dbu_trans * e->edge ()); - } - } - - return layout; -} - -void -Triangles::dump (const std::string &path, bool decompose_by_id) const -{ - std::unique_ptr ly (to_layout (decompose_by_id)); - - tl::OutputStream stream (path); - - db::SaveLayoutOptions opt; - db::Writer writer (opt); - writer.write (*ly, stream); - - tl::info << "Triangles written to " << path; -} - -std::vector -Triangles::find_points_around (db::Vertex *vertex, double radius) -{ - std::set seen; - seen.insert (vertex); - - std::vector res; - std::vector new_vertexes, next_vertexes; - new_vertexes.push_back (vertex); - - while (! new_vertexes.empty ()) { - next_vertexes.clear (); - for (auto v = new_vertexes.begin (); v != new_vertexes.end (); ++v) { - for (auto e = (*v)->begin_edges (); e != (*v)->end_edges (); ++e) { - db::Vertex *ov = (*e)->other (*v); - if (ov->in_circle (*vertex, radius) == 1 && seen.insert (ov).second) { - next_vertexes.push_back (ov); - res.push_back (ov); - } - } - } - new_vertexes.swap (next_vertexes); - } - - return res; -} - -db::Vertex * -Triangles::insert_point (const db::DPoint &point, std::list > *new_triangles) -{ - return insert (create_vertex (point), new_triangles); -} - -db::Vertex * -Triangles::insert_point (db::DCoord x, db::DCoord y, std::list > *new_triangles) -{ - return insert (create_vertex (x, y), new_triangles); -} - -db::Vertex * -Triangles::insert (db::Vertex *vertex, std::list > *new_triangles) -{ - std::vector tris = find_triangle_for_point (*vertex); - - // the new vertex is outside the domain - if (tris.empty ()) { - tl_assert (! m_is_constrained); - insert_new_vertex (vertex, new_triangles); - return vertex; - } - - // check, if the new vertex is on an edge (may be edge between triangles or edge on outside) - std::vector on_edges; - std::vector on_vertex; - for (int i = 0; i < 3; ++i) { - db::TriangleEdge *e = tris.front ()->edge (i); - if (e->side_of (*vertex) == 0) { - if (is_equal (*vertex, *e->v1 ()) || is_equal (*vertex, *e->v2 ())) { - on_vertex.push_back (e); - } else { - on_edges.push_back (e); - } - } - } - - if (! on_vertex.empty ()) { - - tl_assert (on_vertex.size () == size_t (2)); - return on_vertex.front ()->common_vertex (on_vertex [1]); - - } else if (! on_edges.empty ()) { - - tl_assert (on_edges.size () == size_t (1)); - split_triangles_on_edge (vertex, on_edges.front (), new_triangles); - return vertex; - - } else if (tris.size () == size_t (1)) { - - // the new vertex is inside one triangle - split_triangle (tris.front (), vertex, new_triangles); - return vertex; - - } - - tl_assert (false); -} - -std::vector -Triangles::find_triangle_for_point (const db::DPoint &point) -{ - db::TriangleEdge *edge = find_closest_edge (point); - - std::vector res; - if (edge) { - for (auto t = edge->begin_triangles (); t != edge->end_triangles (); ++t) { - if (t->contains (point) >= 0) { - res.push_back (t.operator-> ()); - } - } - } - - return res; -} - -db::TriangleEdge * -Triangles::find_closest_edge (const db::DPoint &p, db::Vertex *vstart, bool inside_only) -{ - if (!vstart) { - - if (! mp_triangles.empty ()) { - - unsigned int ls = 0; - size_t n = m_vertex_heap.size (); - size_t m = n; - - // A simple heuristics that takes a sqrt(N) sample from the - // vertexes to find a good starting point - - vstart = mp_triangles.begin ()->vertex (0); - double dmin = vstart->distance (p); - - while (ls * ls < m) { - m /= 2; - for (size_t i = m / 2; i < n; i += m) { - ++ls; - // NOTE: this assumes the heap is not too loaded with orphan vertexes - db::Vertex *v = (m_vertex_heap.begin () + i).operator-> (); - if (v->begin_edges () != v->end_edges ()) { - double d = v->distance (p); - if (d < dmin) { - vstart = v; - dmin = d; - } - } - } - } - - } else { - - return 0; - - } - - } - - db::DEdge line (*vstart, p); - - double d = -1.0; - db::TriangleEdge *edge = 0; - db::Vertex *v = vstart; - - while (v) { - - db::Vertex *vnext = 0; - - for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { - - if (inside_only) { - // NOTE: in inside mode we stay on the line of sight as we don't - // want to walk around outside pockets. - if (! (*e)->is_segment () && (*e)->is_for_outside_triangles ()) { - continue; - } - if (! (*e)->crosses_including (line)) { - continue; - } - } - - double ds = (*e)->distance (p); - - if (d < 0.0) { - - d = ds; - edge = *e; - vnext = edge->other (v); - - } else if (fabs (ds - d) < std::max (1.0, fabs (ds) + fabs (d)) * db::epsilon) { - - // this differentiation selects the edge which bends further towards - // the target point if both edges share a common point and that - // is the one the determines the distance. - db::Vertex *cv = edge->common_vertex (*e); - if (cv) { - db::DVector edge_d = *edge->other (cv) - *cv; - db::DVector e_d = *(*e)->other(cv) - *cv; - db::DVector r = p - *cv; - double edge_sp = db::sprod (r, edge_d) / edge_d.length (); - double s_sp = db::sprod (r, e_d) / e_d.length (); - if (s_sp > edge_sp + db::epsilon) { - edge = *e; - vnext = edge->other (v); - } - } - - } else if (ds < d) { - - d = ds; - edge = *e; - vnext = edge->other (v); - - } - - } - - ++m_hops; - - v = vnext; - - } - - return edge; -} - -void -Triangles::insert_new_vertex (db::Vertex *vertex, std::list > *new_triangles_out) -{ - if (mp_triangles.empty ()) { - - tl_assert (m_vertex_heap.size () <= size_t (3)); // fails if vertexes were created but not inserted. - - if (m_vertex_heap.size () == 3) { - - std::vector vv; - for (auto v = m_vertex_heap.begin (); v != m_vertex_heap.end (); ++v) { - vv.push_back (v.operator-> ()); - } - - // form the first triangle - db::TriangleEdge *s1 = create_edge (vv[0], vv[1]); - db::TriangleEdge *s2 = create_edge (vv[1], vv[2]); - db::TriangleEdge *s3 = create_edge (vv[2], vv[0]); - - if (db::vprod_sign (s1->d (), s2->d ()) == 0) { - // avoid degenerate Triangles to happen here - tl_assert (false); - } else { - db::Triangle *t = create_triangle (s1, s2, s3); - if (new_triangles_out) { - new_triangles_out->push_back (t); - } - } - - } - - return; - - } - - std::vector new_triangles; - - // Find closest edge - db::TriangleEdge *closest_edge = find_closest_edge (*vertex); - tl_assert (closest_edge != 0); - - db::TriangleEdge *s1 = create_edge (vertex, closest_edge->v1 ()); - db::TriangleEdge *s2 = create_edge (vertex, closest_edge->v2 ()); - - db::Triangle *t = create_triangle (s1, closest_edge, s2); - new_triangles.push_back (t); - - add_more_triangles (new_triangles, closest_edge, closest_edge->v1 (), vertex, s1); - add_more_triangles (new_triangles, closest_edge, closest_edge->v2 (), vertex, s2); - - if (new_triangles_out) { - new_triangles_out->insert (new_triangles_out->end (), new_triangles.begin (), new_triangles.end ()); - } - - fix_triangles (new_triangles, std::vector (), new_triangles_out); -} - -void -Triangles::add_more_triangles (std::vector &new_triangles, - db::TriangleEdge *incoming_edge, - db::Vertex *from_vertex, db::Vertex *to_vertex, - db::TriangleEdge *conn_edge) -{ - while (true) { - - db::TriangleEdge *next_edge = 0; - - for (auto e = from_vertex->begin_edges (); e != from_vertex->end_edges (); ++e) { - if (! (*e)->has_vertex (to_vertex) && (*e)->is_outside ()) { - // TODO: remove and break - tl_assert (next_edge == 0); - next_edge = *e; - } - } - - tl_assert (next_edge != 0); - db::Vertex *next_vertex = next_edge->other (from_vertex); - - db::DVector d_from_to = *to_vertex - *from_vertex; - db::Vertex *incoming_vertex = incoming_edge->other (from_vertex); - if (db::vprod_sign(*from_vertex - *incoming_vertex, d_from_to) * db::vprod_sign(*from_vertex - *next_vertex, d_from_to) >= 0) { - return; - } - - db::TriangleEdge *next_conn_edge = create_edge (next_vertex, to_vertex); - db::Triangle *t = create_triangle (next_conn_edge, next_edge, conn_edge); - new_triangles.push_back (t); - - incoming_edge = next_edge; - conn_edge = next_conn_edge; - from_vertex = next_vertex; - - } -} - -void -Triangles::split_triangle (db::Triangle *t, db::Vertex *vertex, std::list > *new_triangles_out) -{ - t->unlink (); - - std::map v2new_edges; - std::vector new_edges; - for (int i = 0; i < 3; ++i) { - db::Vertex *v = t->vertex (i); - db::TriangleEdge *e = create_edge (v, vertex); - v2new_edges[v] = e; - new_edges.push_back (e); - } - - std::vector new_triangles; - for (int i = 0; i < 3; ++i) { - db::TriangleEdge *e = t->edge (i); - db::Triangle *new_triangle = create_triangle (e, v2new_edges[e->v1 ()], v2new_edges[e->v2 ()]); - if (new_triangles_out) { - new_triangles_out->push_back (new_triangle); - } - new_triangle->set_outside (t->is_outside ()); - new_triangles.push_back (new_triangle); - } - - remove_triangle (t); - - fix_triangles (new_triangles, new_edges, new_triangles_out); -} - -void -Triangles::split_triangles_on_edge (db::Vertex *vertex, db::TriangleEdge *split_edge, std::list > *new_triangles_out) -{ - TriangleEdge *s1 = create_edge (split_edge->v1 (), vertex); - TriangleEdge *s2 = create_edge (split_edge->v2 (), vertex); - s1->set_is_segment (split_edge->is_segment ()); - s2->set_is_segment (split_edge->is_segment ()); - - std::vector new_triangles; - - std::vector tris; - tris.reserve (2); - for (auto t = split_edge->begin_triangles (); t != split_edge->end_triangles (); ++t) { - tris.push_back (t.operator-> ()); - } - - for (auto t = tris.begin (); t != tris.end (); ++t) { - - (*t)->unlink (); - - db::Vertex *ext_vertex = (*t)->opposite (split_edge); - TriangleEdge *new_edge = create_edge (ext_vertex, vertex); - - for (int i = 0; i < 3; ++i) { - - db::TriangleEdge *e = (*t)->edge (i); - if (e->has_vertex (ext_vertex)) { - - TriangleEdge *partial = e->has_vertex (split_edge->v1 ()) ? s1 : s2; - db::Triangle *new_triangle = create_triangle (new_edge, partial, e); - - if (new_triangles_out) { - new_triangles_out->push_back (new_triangle); - } - new_triangle->set_outside ((*t)->is_outside ()); - new_triangles.push_back (new_triangle); - - } - - } - - } - - for (auto t = tris.begin (); t != tris.end (); ++t) { - remove_triangle (*t); - } - - std::vector fixed_edges; - fixed_edges.push_back (s1); - fixed_edges.push_back (s2); - fix_triangles (new_triangles, fixed_edges, new_triangles_out); -} - -std::vector -Triangles::find_touching (const db::DBox &box) const -{ - // NOTE: this is a naive, slow implementation for test purposes - std::vector res; - for (auto v = m_vertex_heap.begin (); v != m_vertex_heap.end (); ++v) { - if (v->begin_edges () != v->end_edges ()) { - if (box.contains (*v)) { - res.push_back (const_cast (v.operator-> ())); - } - } - } - return res; -} - -std::vector -Triangles::find_inside_circle (const db::DPoint ¢er, double radius) const -{ - // NOTE: this is a naive, slow implementation for test purposes - std::vector res; - for (auto v = m_vertex_heap.begin (); v != m_vertex_heap.end (); ++v) { - if (v->begin_edges () != v->end_edges ()) { - if (v->in_circle (center, radius) == 1) { - res.push_back (const_cast (v.operator-> ())); - } - } - } - return res; -} - -void -Triangles::remove (db::Vertex *vertex, std::list > *new_triangles) -{ - if (vertex->begin_edges () == vertex->end_edges ()) { - // removing an orphan vertex -> ignore - } else if (vertex->is_outside ()) { - remove_outside_vertex (vertex, new_triangles); - } else { - remove_inside_vertex (vertex, new_triangles); - } -} - -void -Triangles::remove_outside_vertex (db::Vertex *vertex, std::list > *new_triangles_out) -{ - auto to_remove = vertex->triangles (); - - std::vector outer_edges; - for (auto t = to_remove.begin (); t != to_remove.end (); ++t) { - outer_edges.push_back ((*t)->opposite (vertex)); - } - - for (auto t = to_remove.begin (); t != to_remove.end (); ++t) { - (*t)->unlink (); - } - - auto new_triangles = fill_concave_corners (outer_edges); - - for (auto t = to_remove.begin (); t != to_remove.end (); ++t) { - remove_triangle (*t); - } - - fix_triangles (new_triangles, std::vector (), new_triangles_out); -} - -void -Triangles::remove_inside_vertex (db::Vertex *vertex, std::list > *new_triangles_out) -{ - std::set triangles_to_fix; - - bool make_new_triangle = true; - - while (vertex->num_edges (4) > 3) { - - db::TriangleEdge *to_flip = 0; - for (auto e = vertex->begin_edges (); e != vertex->end_edges () && to_flip == 0; ++e) { - if ((*e)->can_flip ()) { - to_flip = *e; - } - } - if (! to_flip) { - break; - } - - // NOTE: in the "can_join" case zero-area triangles are created which we will sort out later - triangles_to_fix.erase (to_flip->left ()); - triangles_to_fix.erase (to_flip->right ()); - - auto pp = flip (to_flip); - triangles_to_fix.insert (pp.first.first); - triangles_to_fix.insert (pp.first.second); - - } - - if (vertex->num_edges (4) > 3) { - - tl_assert (vertex->num_edges (5) == 4); - - // This case can happen if two edges attached to the vertex are collinear - // in this case choose the "join" strategy - db::TriangleEdge *jseg = 0; - for (auto e = vertex->begin_edges (); e != vertex->end_edges () && !jseg; ++e) { - if ((*e)->can_join_via (vertex)) { - jseg = *e; - } - } - tl_assert (jseg != 0); - - db::Vertex *v1 = jseg->left ()->opposite (jseg); - db::TriangleEdge *s1 = jseg->left ()->opposite (vertex); - db::Vertex *v2 = jseg->right ()->opposite (jseg); - db::TriangleEdge *s2 = jseg->right ()->opposite (vertex); - - db::TriangleEdge *jseg_opp = 0; - for (auto e = vertex->begin_edges (); e != vertex->end_edges () && !jseg_opp; ++e) { - if (!(*e)->has_triangle (jseg->left ()) && !(*e)->has_triangle (jseg->right ())) { - jseg_opp = *e; - } - } - - db::TriangleEdge *s1opp = jseg_opp->left ()->opposite (vertex); - db::TriangleEdge *s2opp = jseg_opp->right ()->opposite (vertex); - - db::TriangleEdge *new_edge = create_edge (v1, v2); - db::Triangle *t1 = create_triangle (s1, s2, new_edge); - db::Triangle *t2 = create_triangle (s1opp, s2opp, new_edge); - - triangles_to_fix.insert (t1); - triangles_to_fix.insert (t2); - - make_new_triangle = false; - - } - - auto to_remove = vertex->triangles (); - - std::vector outer_edges; - for (auto t = to_remove.begin (); t != to_remove.end (); ++t) { - outer_edges.push_back ((*t)->opposite (vertex)); - } - - if (make_new_triangle) { - - tl_assert (outer_edges.size () == size_t (3)); - - db::Triangle *nt = create_triangle (outer_edges[0], outer_edges[1], outer_edges[2]); - triangles_to_fix.insert (nt); - - } - - for (auto t = to_remove.begin (); t != to_remove.end (); ++t) { - triangles_to_fix.erase (*t); - remove_triangle (*t); - } - - if (new_triangles_out) { - for (auto t = triangles_to_fix.begin (); t != triangles_to_fix.end (); ++t) { - new_triangles_out->push_back (*t); - } - } - - std::vector to_fix_a (triangles_to_fix.begin (), triangles_to_fix.end ()); - fix_triangles (to_fix_a, std::vector (), new_triangles_out); -} - -void -Triangles::fix_triangles (const std::vector &tris, const std::vector &fixed_edges, std::list > *new_triangles) -{ - m_level += 1; - for (auto e = fixed_edges.begin (); e != fixed_edges.end (); ++e) { - (*e)->set_level (m_level); - } - - std::set queue, todo; - - for (auto t = tris.begin (); t != tris.end (); ++t) { - for (int i = 0; i < 3; ++i) { - db::TriangleEdge *e = (*t)->edge (i); - if (e->level () < m_level && ! e->is_segment ()) { - queue.insert (e); - } - } - } - - while (! queue.empty ()) { - - todo.clear (); - todo.swap (queue); - - // NOTE: we cannot be sure that already treated edges will not become - // illegal by neighbor edges flipping .. - // for s in todo: - // s.level = self.level - - for (auto e = todo.begin (); e != todo.end (); ++e) { - - if (is_illegal_edge (*e)) { - - queue.erase (*e); - - auto pp = flip (*e); - auto t1 = pp.first.first; - auto t2 = pp.first.second; - auto s12 = pp.second; - - if (new_triangles) { - new_triangles->push_back (t1); - new_triangles->push_back (t2); - } - - ++m_flips; - tl_assert (! is_illegal_edge (s12)); // TODO: remove later! - - for (int i = 0; i < 3; ++i) { - db::TriangleEdge *s1 = t1->edge (i); - if (s1->level () < m_level && ! s1->is_segment ()) { - queue.insert (s1); - } - } - - for (int i = 0; i < 3; ++i) { - db::TriangleEdge *s2 = t2->edge (i); - if (s2->level () < m_level && ! s2->is_segment ()) { - queue.insert (s2); - } - } - - } - - } - - } -} - -bool -Triangles::is_illegal_edge (db::TriangleEdge *edge) -{ - db::Triangle *left = edge->left (); - db::Triangle *right = edge->right (); - if (!left || !right) { - return false; - } - - bool ok = false; - - auto lr = left->circumcircle (&ok); - if (! ok || right->opposite (edge)->in_circle (lr.first, lr.second) > 0) { - return true; - } - - auto rr = right->circumcircle(&ok); - if (! ok || left->opposite (edge)->in_circle (rr.first, rr.second) > 0) { - return true; - } - - return false; -} - -std::pair, TriangleEdge *> -Triangles::flip (TriangleEdge *edge) -{ - db::Triangle *t1 = edge->left (); - db::Triangle *t2 = edge->right (); - - bool outside = t1->is_outside (); - tl_assert (t1->is_outside () == outside); - - // prepare for the new triangle to replace this one - t1->unlink (); - t2->unlink (); - - db::Vertex *t1_vext = t1->opposite (edge); - db::TriangleEdge *t1_sext1 = t1->find_edge_with (t1_vext, edge->v1 ()); - db::TriangleEdge *t1_sext2 = t1->find_edge_with (t1_vext, edge->v2 ()); - - db::Vertex *t2_vext = t2->opposite (edge); - db::TriangleEdge *t2_sext1 = t2->find_edge_with (t2_vext, edge->v1 ()); - db::TriangleEdge *t2_sext2 = t2->find_edge_with (t2_vext, edge->v2 ()); - - db::TriangleEdge *s_new = create_edge (t1_vext, t2_vext); - - db::Triangle *t1_new = create_triangle (s_new, t1_sext1, t2_sext1); - t1_new->set_outside (outside); - db::Triangle *t2_new = create_triangle (s_new, t1_sext2, t2_sext2); - t2_new->set_outside (outside); - - remove_triangle (t1); - remove_triangle (t2); - - return std::make_pair (std::make_pair (t1_new, t2_new), s_new); -} - -std::vector -Triangles::fill_concave_corners (const std::vector &edges) -{ - std::vector res; - std::vector points, terminals; - - std::map > vertex2edge; - for (auto e = edges.begin (); e != edges.end (); ++e) { - - auto i = vertex2edge.insert (std::make_pair ((*e)->v1 (), std::vector ())); - if (i.second) { - points.push_back ((*e)->v1 ()); - } - i.first->second.push_back (*e); - - i = vertex2edge.insert (std::make_pair ((*e)->v2 (), std::vector ())); - if (i.second) { - points.push_back ((*e)->v2 ()); - } - i.first->second.push_back (*e); - - } - - while (points.size () > size_t (2)) { - - terminals.clear (); - for (auto p = points.begin (); p != points.end (); ++p) { - if (vertex2edge [*p].size () == 1) { - terminals.push_back (*p); - } - } - tl_assert (terminals.size () == size_t (2)); - db::Vertex *v = terminals[0]; - - bool any_connected = false; - db::Vertex *vp = 0; - - std::set to_remove; - - while (vertex2edge [v].size () >= size_t (2) || ! vp) { - - db::TriangleEdge *seg = 0; - std::vector &ee = vertex2edge [v]; - for (auto e = ee.begin (); e != ee.end (); ++e) { - if (! (*e)->has_vertex (vp)) { - seg = (*e); - break; - } - } - - tl_assert (seg != 0); - db::Triangle *tri = seg->left () ? seg->left () : seg->right (); - db::Vertex *vn = seg->other (v); - - std::vector &een = vertex2edge [vn]; - if (een.size () < size_t (2)) { - break; - } - tl_assert (een.size () == size_t (2)); - - db::TriangleEdge *segn = 0; - for (auto e = een.begin (); e != een.end (); ++e) { - if (! (*e)->has_vertex (v)) { - segn = (*e); - break; - } - } - - tl_assert (segn != 0); - db::Vertex *vnn = segn->other (vn); - std::vector &eenn = vertex2edge [vnn]; - - // NOTE: tri can be None in case a lonely edge stays after removing - // attached triangles - if (! tri || seg->side_of (*vnn) * seg->side_of (*tri->opposite (seg)) < 0) { - - // is concave - db::TriangleEdge *new_edge = create_edge (v, vnn); - for (auto e = ee.begin (); e != ee.end (); ++e) { - if (*e == seg) { - ee.erase (e); - break; - } - } - ee.push_back (new_edge); - - for (auto e = eenn.begin (); e != eenn.end (); ++e) { - if (*e == segn) { - eenn.erase (e); - break; - } - } - eenn.push_back (new_edge); - - vertex2edge.erase (vn); - to_remove.insert (vn); - - db::Triangle *new_triangle = create_triangle (seg, segn, new_edge); - res.push_back (new_triangle); - any_connected = true; - - } else { - - vp = v; - v = vn; - - } - - } - - if (! any_connected) { - break; - } - - std::vector::iterator wp = points.begin (); - for (auto p = points.begin (); p != points.end (); ++p) { - if (to_remove.find (*p) == to_remove.end ()) { - *wp++ = *p; - } - } - points.erase (wp, points.end ()); - - } - - return res; -} - -std::vector -Triangles::search_edges_crossing (Vertex *from, Vertex *to) -{ - db::Vertex *v = from; - db::Vertex *vv = to; - db::DEdge edge (*from, *to); - - db::Triangle *current_triangle = 0; - db::TriangleEdge *next_edge = 0; - - std::vector result; - - for (auto e = v->begin_edges (); e != v->end_edges () && ! next_edge; ++e) { - for (auto t = (*e)->begin_triangles (); t != (*e)->end_triangles (); ++t) { - db::TriangleEdge *os = t->opposite (v); - if (os->has_vertex (vv)) { - return result; - } - if (os->crosses (edge)) { - result.push_back (os); - current_triangle = t.operator-> (); - next_edge = os; - break; - } - } - } - - tl_assert (current_triangle != 0); - - while (true) { - - current_triangle = next_edge->other (current_triangle); - - // Note that we're convex, so there has to be a path across triangles - tl_assert (current_triangle != 0); - - db::TriangleEdge *cs = next_edge; - next_edge = 0; - for (int i = 0; i < 3; ++i) { - db::TriangleEdge *e = current_triangle->edge (i); - if (e != cs) { - if (e->has_vertex (vv)) { - return result; - } - if (e->crosses (edge)) { - result.push_back (e); - next_edge = e; - break; - } - } - } - - tl_assert (next_edge != 0); - - } -} - -db::Vertex * -Triangles::find_vertex_for_point (const db::DPoint &point) -{ - db::TriangleEdge *edge = find_closest_edge (point); - if (!edge) { - return 0; - } - db::Vertex *v = 0; - if (is_equal (*edge->v1 (), point)) { - v = edge->v1 (); - } else if (is_equal (*edge->v2 (), point)) { - v = edge->v2 (); - } - return v; -} - -db::TriangleEdge * -Triangles::find_edge_for_points (const db::DPoint &p1, const db::DPoint &p2) -{ - db::Vertex *v = find_vertex_for_point (p1); - if (!v) { - return 0; - } - for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { - if (is_equal (*(*e)->other (v), p2)) { - return *e; - } - } - return 0; -} - -std::vector -Triangles::ensure_edge_inner (db::Vertex *from, db::Vertex *to) -{ - auto crossed_edges = search_edges_crossing (from, to); - std::vector result; - - if (crossed_edges.empty ()) { - - // no crossing edge - there should be a edge already - db::TriangleEdge *res = find_edge_for_points (*from, *to); - tl_assert (res != 0); - result.push_back (res); - - } else if (crossed_edges.size () == 1) { - - // can be solved by flipping - auto pp = flip (crossed_edges.front ()); - db::TriangleEdge *res = pp.second; - tl_assert (res->has_vertex (from) && res->has_vertex (to)); - result.push_back (res); - - } else { - - // split edge close to center - db::DPoint split_point; - double d = -1.0; - double l_half = 0.25 * (*to - *from).sq_length (); - for (auto e = crossed_edges.begin (); e != crossed_edges.end (); ++e) { - db::DPoint p = (*e)->intersection_point (db::DEdge (*from, *to)); - double dp = fabs ((p - *from).sq_length () - l_half); - if (d < 0.0 || dp < d) { - dp = d; - split_point = p; - } - } - - db::Vertex *split_vertex = insert_point (split_point); - - result = ensure_edge_inner (from, split_vertex); - - auto result2 = ensure_edge_inner (split_vertex, to); - result.insert (result.end (), result2.begin (), result2.end ()); - - } - - return result; -} - -std::vector -Triangles::ensure_edge (db::Vertex *from, db::Vertex *to) -{ -#if 0 - // NOTE: this should not be required if the original segments are non-overlapping - // TODO: this is inefficient - for v in self.vertexes: - if edge.point_on(v): - return self.ensure_edge(Edge(edge.p1, v)) + self.ensure_edge(Edge(v, edge.p2)) -#endif - - auto edges = ensure_edge_inner (from, to); - for (auto e = edges.begin (); e != edges.end (); ++e) { - // mark the edges as fixed "forever" so we don't modify them when we ensure other edges - (*e)->set_level (std::numeric_limits::max ()); - } - return edges; -} - -void -Triangles::join_edges (std::vector &edges) -{ - // edges are supposed to be ordered - for (size_t i = 1; i < edges.size (); ) { - - db::TriangleEdge *s1 = edges [i - 1]; - db::TriangleEdge *s2 = edges [i]; - tl_assert (s1->is_segment () == s2->is_segment ()); - db::Vertex *cp = s1->common_vertex (s2); - tl_assert (cp != 0); - - std::vector join_edges; - for (auto e = cp->begin_edges (); e != cp->end_edges (); ++e) { - if (*e != s1 && *e != s2) { - if ((*e)->can_join_via (cp)) { - join_edges.push_back (*e); - } else { - join_edges.clear (); - break; - } - } - } - - if (! join_edges.empty ()) { - - tl_assert (join_edges.size () <= 2); - - TriangleEdge *new_edge = create_edge (s1->other (cp), s2->other (cp)); - new_edge->set_is_segment (s1->is_segment ()); - - for (auto js = join_edges.begin (); js != join_edges.end (); ++js) { - - db::Triangle *t1 = (*js)->left (); - db::Triangle *t2 = (*js)->right (); - db::TriangleEdge *tedge1 = t1->opposite (cp); - db::TriangleEdge *tedge2 = t2->opposite (cp); - t1->unlink (); - t2->unlink (); - db::Triangle *tri = create_triangle (tedge1, tedge2, new_edge); - tri->set_outside (t1->is_outside ()); - remove_triangle (t1); - remove_triangle (t2); - } - - edges [i - 1] = new_edge; - edges.erase (edges.begin () + i); - - } else { - ++i; - } - - } -} - -void -Triangles::constrain (const std::vector > &contours) -{ - tl_assert (! m_is_constrained); - - std::vector > > resolved_edges; - - for (auto c = contours.begin (); c != contours.end (); ++c) { - for (auto v = c->begin (); v != c->end (); ++v) { - auto vv = v; - ++vv; - if (vv == c->end ()) { - vv = c->begin (); - } - db::DEdge e (**v, **vv); - resolved_edges.push_back (std::make_pair (e, std::vector ())); - resolved_edges.back ().second = ensure_edge (*v, *vv); - } - } - - for (auto tri = mp_triangles.begin (); tri != mp_triangles.end (); ++tri) { - tri->set_outside (false); - for (int i = 0; i < 3; ++i) { - tri->edge (i)->set_is_segment (false); - } - } - - std::set new_tri; - - for (auto re = resolved_edges.begin (); re != resolved_edges.end (); ++re) { - auto edge = re->first; - auto edges = re->second; - for (auto e = edges.begin (); e != edges.end (); ++e) { - (*e)->set_is_segment (true); - db::Triangle *outer_tri = 0; - int d = db::sprod_sign (edge.d (), (*e)->d ()); - if (d > 0) { - outer_tri = (*e)->left (); - } - if (d < 0) { - outer_tri = (*e)->right (); - } - if (outer_tri) { - new_tri.insert (outer_tri); - outer_tri->set_outside (true); - } - } - } - - while (! new_tri.empty ()) { - - std::set next_tris; - - for (auto tri = new_tri.begin (); tri != new_tri.end (); ++tri) { - for (int i = 0; i < 3; ++i) { - auto e = (*tri)->edge (i); - if (! e->is_segment ()) { - auto ot = e->other (*tri); - if (ot && ! ot->is_outside ()) { - next_tris.insert (ot); - ot->set_outside (true); - } - } - } - } - - new_tri.swap (next_tris); - - } - - // join edges where possible - for (auto re = resolved_edges.begin (); re != resolved_edges.end (); ++re) { - auto edges = re->second; - join_edges (edges); - } - - m_is_constrained = true; -} - -void -Triangles::remove_outside_triangles () -{ - tl_assert (m_is_constrained); - - // NOTE: don't remove while iterating - std::vector to_remove; - for (auto tri = begin (); tri != end (); ++tri) { - if (tri->is_outside ()) { - to_remove.push_back (const_cast (tri.operator-> ())); - } - } - - for (auto t = to_remove.begin (); t != to_remove.end (); ++t) { - remove_triangle (*t); - } -} - -void -Triangles::clear () -{ - mp_triangles.clear (); - m_edges_heap.clear (); - m_vertex_heap.clear (); - m_returned_edges.clear (); - m_is_constrained = false; - m_level = 0; - m_id = 0; -} - -template -void -Triangles::make_contours (const Poly &poly, const Trans &trans, std::vector > &edge_contours) -{ - edge_contours.push_back (std::vector ()); - for (auto pt = poly.begin_hull (); pt != poly.end_hull (); ++pt) { - edge_contours.back ().push_back (insert_point (trans * *pt)); - } - - for (unsigned int h = 0; h < poly.holes (); ++h) { - edge_contours.push_back (std::vector ()); - for (auto pt = poly.begin_hole (h); pt != poly.end_hole (h); ++pt) { - edge_contours.back ().push_back (insert_point (trans * *pt)); - } - } -} - -void -Triangles::create_constrained_delaunay (const db::Region ®ion, const CplxTrans &trans) -{ - clear (); - - std::vector > edge_contours; - - for (auto p = region.begin_merged (); ! p.at_end (); ++p) { - make_contours (*p, trans, edge_contours); - } - - constrain (edge_contours); -} - -void -Triangles::create_constrained_delaunay (const db::Polygon &p, const std::vector &vertexes, const CplxTrans &trans) -{ - clear (); - - for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { - insert_point (trans * *v)->set_is_precious (true); - } - - std::vector > edge_contours; - make_contours (p, trans, edge_contours); - - constrain (edge_contours); -} - -void -Triangles::create_constrained_delaunay (const db::DPolygon &p, const std::vector &vertexes, const DCplxTrans &trans) -{ - clear (); - - for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { - insert_point (trans * *v)->set_is_precious (true); - } - - std::vector > edge_contours; - make_contours (p, trans, edge_contours); - - constrain (edge_contours); -} - -static bool is_skinny (const db::Triangle *tri, const Triangles::TriangulateParameters ¶m) -{ - if (param.min_b < db::epsilon) { - return false; - } else { - double b = tri->b (); - double delta = (b + param.min_b) * db::epsilon; - return b < param.min_b - delta; - } -} - -static bool is_invalid (const db::Triangle *tri, const Triangles::TriangulateParameters ¶m) -{ - if (is_skinny (tri, param)) { - return true; - } - - double amax = param.max_area; - if (param.max_area_border > db::epsilon) { - if (tri->has_segment ()) { - amax = param.max_area_border; - } - } - - if (amax > db::epsilon) { - double a = tri->area (); - double delta = (a + amax) * db::epsilon; - return tri->area () > amax + delta; - } else { - return false; - } -} - -void -Triangles::triangulate (const db::Region ®ion, const TriangulateParameters ¶meters, double dbu) -{ - tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); - - create_constrained_delaunay (region, db::CplxTrans (dbu)); - refine (parameters); -} - -void -Triangles::triangulate (const db::Region ®ion, const TriangulateParameters ¶meters, const db::CplxTrans &trans) -{ - tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); - - create_constrained_delaunay (region, trans); - refine (parameters); -} - -void -Triangles::triangulate (const db::Polygon &poly, const TriangulateParameters ¶meters, double dbu) -{ - triangulate (poly, std::vector (), parameters, dbu); -} - -void -Triangles::triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulateParameters ¶meters, double dbu) -{ - tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); - - create_constrained_delaunay (poly, vertexes, db::CplxTrans (dbu)); - refine (parameters); -} - -void -Triangles::triangulate (const db::Polygon &poly, const TriangulateParameters ¶meters, const db::CplxTrans &trans) -{ - triangulate (poly, std::vector (), parameters, trans); -} - -void -Triangles::triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulateParameters ¶meters, const db::CplxTrans &trans) -{ - tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); - - create_constrained_delaunay (poly, vertexes, trans); - refine (parameters); -} - -void -Triangles::triangulate (const db::DPolygon &poly, const TriangulateParameters ¶meters, const DCplxTrans &trans) -{ - triangulate (poly, std::vector (), parameters, trans); -} - -void -Triangles::triangulate (const db::DPolygon &poly, const std::vector &vertexes, const TriangulateParameters ¶meters, const DCplxTrans &trans) -{ - tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); - - create_constrained_delaunay (poly, vertexes, trans); - refine (parameters); -} - -void -Triangles::refine (const TriangulateParameters ¶meters) -{ - if (parameters.min_b < db::epsilon && parameters.max_area < db::epsilon && parameters.max_area_border < db::epsilon) { - - // no refinement requested - we're done. - remove_outside_triangles (); - return; - - } - - unsigned int nloop = 0; - std::list > new_triangles; - for (auto t = mp_triangles.begin (); t != mp_triangles.end (); ++t) { - new_triangles.push_back (t.operator-> ()); - } - - // TODO: break if iteration gets stuck - while (nloop < parameters.max_iterations) { - - ++nloop; - if (tl::verbosity () >= parameters.base_verbosity + 10) { - tl::info << "Iteration " << nloop << " .."; - } - - std::list > to_consider; - for (auto t = new_triangles.begin (); t != new_triangles.end (); ++t) { - if (t->get () && ! (*t)->is_outside () && is_invalid (t->get (), parameters)) { - to_consider.push_back (*t); - } - } - - if (to_consider.empty ()) { - break; - } - - if (tl::verbosity () >= parameters.base_verbosity + 10) { - tl::info << to_consider.size() << " triangles to consider"; - } - - new_triangles.clear (); - - for (auto t = to_consider.begin (); t != to_consider.end (); ++t) { - - if (! t->get ()) { - // triangle got removed during loop - continue; - } - - auto cr = (*t)->circumcircle(); - auto center = cr.first; - - int s = (*t)->contains (center); - if (s >= 0) { - - if (s > 0) { - - double snap = 1e-3; - - // Snap the center to a segment center if "close" to it. - // This avoids generating very skinny triangles that can't be fixed as the - // segment cannot be flipped. This a part of the issue #1996 problem. - for (unsigned int i = 0; i < 3; ++i) { - if ((*t)->edge (i)->is_segment ()) { - auto e = (*t)->edge (i)->edge (); - auto c = e.p1 () + e.d () * 0.5; - if (c.distance (center) < e.length () * 0.5 * snap - db::epsilon) { - center = c; - break; - } - } - } - - } - - if (tl::verbosity () >= parameters.base_verbosity + 20) { - tl::info << "Inserting in-triangle center " << center.to_string () << " of " << (*t)->to_string (true); - } - insert_point (center, &new_triangles); - - } else { - - db::Vertex *vstart = 0; - for (unsigned int i = 0; i < 3; ++i) { - db::TriangleEdge *edge = (*t)->edge (i); - vstart = (*t)->opposite (edge); - if (edge->side_of (*vstart) * edge->side_of (center) < 0) { - break; - } - } - - db::TriangleEdge *edge = find_closest_edge (center, vstart, true /*inside only*/); - tl_assert (edge != 0); - - if (! edge->is_segment () || edge->side_of (*vstart) * edge->side_of (center) >= 0) { - - if (tl::verbosity () >= parameters.base_verbosity + 20) { - tl::info << "Inserting out-of-triangle center " << center << " of " << (*t)->to_string (true); - } - insert_point (center, &new_triangles); - - } else { - - double sr = edge->d ().length () * 0.5; - if (sr >= parameters.min_length) { - - db::DPoint pnew = *edge->v1 () + edge->d () * 0.5; - - if (tl::verbosity () >= parameters.base_verbosity + 20) { - tl::info << "Split edge " << edge->to_string (true) << " at " << pnew.to_string (); - } - db::Vertex *vnew = insert_point (pnew, &new_triangles); - auto vertexes_in_diametral_circle = find_points_around (vnew, sr); - - std::vector to_delete; - for (auto v = vertexes_in_diametral_circle.begin (); v != vertexes_in_diametral_circle.end (); ++v) { - bool has_segment = false; - for (auto e = (*v)->begin_edges (); e != (*v)->end_edges () && ! has_segment; ++e) { - has_segment = (*e)->is_segment (); - } - if (! has_segment && ! (*v)->is_precious ()) { - to_delete.push_back (*v); - } - } - - if (tl::verbosity () >= parameters.base_verbosity + 20) { - tl::info << " -> found " << to_delete.size () << " vertexes to remove"; - } - for (auto v = to_delete.begin (); v != to_delete.end (); ++v) { - remove (*v, &new_triangles); - } - - } - - } - - } - - } - - } - - if (tl::verbosity () >= parameters.base_verbosity + 20) { - tl::info << "Finishing .."; - } - - if (parameters.mark_triangles) { - - for (auto t = begin (); t != end (); ++t) { - - size_t id = 0; - - if (! t->is_outside ()) { - - if (is_skinny (t.operator-> (), parameters)) { - id |= 1; - } - if (is_invalid (t.operator-> (), parameters)) { - id |= 2; - } - auto cp = t->circumcircle (); - auto vi = find_inside_circle (cp.first, cp.second); - if (! vi.empty ()) { - id |= 4; - } - - } - - (const_cast (t.operator->()))->set_id (id); - - } - - } - - remove_outside_triangles (); -} - -} diff --git a/src/db/db/dbTriangles.h b/src/db/db/dbTriangles.h deleted file mode 100644 index 3bb34c87d..000000000 --- a/src/db/db/dbTriangles.h +++ /dev/null @@ -1,360 +0,0 @@ - -/* - - KLayout Layout Viewer - Copyright (C) 2006-2025 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_dbTriangles -#define HDR_dbTriangles - -#include "dbCommon.h" -#include "dbTriangle.h" -#include "dbBox.h" -#include "dbRegion.h" - -#include "tlObjectCollection.h" -#include "tlStableVector.h" - -#include -#include -#include -#include - -namespace db -{ - -class Layout; - -class DB_PUBLIC Triangles -{ -public: - struct TriangulateParameters - { - TriangulateParameters () - : min_b (1.0), - min_length (0.0), - max_area (0.0), - max_area_border (0.0), - max_iterations (std::numeric_limits::max ()), - base_verbosity (30), - mark_triangles (false) - { } - - /** - * @brief Min. readius-to-shortest edge ratio - */ - double min_b; - - /** - * @brief Min. edge length - * - * This parameter does not provide a guarantee about a minimume edge length, but - * helps avoiding ever-reducing triangle splits in acute corners of the input polygon. - * Splitting of edges stops when the edge is less than the min length. - */ - double min_length; - - /** - * @brief Max area or zero for "no constraint" - */ - double max_area; - - /** - * @brief Max area for border triangles or zero for "use max_area" - */ - double max_area_border; - - /** - * @brief Max number of iterations - */ - size_t max_iterations; - - /** - * @brief The verbosity level above which triangulation reports details - */ - int base_verbosity; - - /** - * @brief If true, final triangles are marked using the "id" integer as a bit field - * - * This provides information about the result quality. - * - * Bit 0: skinny triangle - * Bit 1: bad-quality (skinny or area too large) - * Bit 2: non-Delaunay (in the strict sense) - */ - bool mark_triangles; - }; - - typedef tl::list triangles_type; - typedef triangles_type::const_iterator triangle_iterator; - - Triangles (); - ~Triangles (); - - /** - * @brief Initializes the triangle collection with a box - * Two triangles will be created. - */ - void init_box (const db::DBox &box); - - /** - * @brief Returns a string representation of the triangle graph. - */ - std::string to_string (); - - /** - * @brief Returns the bounding box of the triangle graph. - */ - db::DBox bbox () const; - - /** - * @brief Iterates the triangles in the graph (begin iterator) - */ - triangle_iterator begin () const { return mp_triangles.begin (); } - - /** - * @brief Iterates the triangles in the graph (end iterator) - */ - triangle_iterator end () const { return mp_triangles.end (); } - - /** - * @brief Returns the number of triangles in the graph - */ - size_t num_triangles () const { return mp_triangles.size (); } - - /** - * @brief Clears the triangle set - */ - void clear (); - - /** - * @brief Creates a refined Delaunay triangulation for the given region - * - * The database unit should be chosen in a way that target area values are "in the order of 1". - * For inputs featuring acute angles (angles < ~25 degree), the parameters should defined a min - * edge length ("min_length"). - * "min_length" should be at least 1e-4. If a min edge length is given, the max area constaints - * may not be satisfied. - * - * Edges in the input should not be shorter than 1e-4. - */ - void triangulate (const db::Region ®ion, const TriangulateParameters ¶meters, double dbu = 1.0); - - // more versions - void triangulate (const db::Region ®ion, const TriangulateParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); - void triangulate (const db::Polygon &poly, const TriangulateParameters ¶meters, double dbu = 1.0); - void triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulateParameters ¶meters, double dbu = 1.0); - void triangulate (const db::Polygon &poly, const TriangulateParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); - void triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulateParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); - - /** - * @brief Triangulates a floating-point polygon - */ - void triangulate (const db::DPolygon &poly, const TriangulateParameters ¶meters, const db::DCplxTrans &trans = db::DCplxTrans ()); - void triangulate (const db::DPolygon &poly, const std::vector &vertexes, const TriangulateParameters ¶meters, const db::DCplxTrans &trans = db::DCplxTrans ()); - - /** - * @brief Inserts a new vertex as the given point - * - * If "new_triangles" is not null, it will receive the list of new triangles created during - * the remove step. - * - * This method can be called after "triangulate" to add new points and adjust the triangulation. - * Inserting new points will maintain the (constrained) Delaunay condition. - */ - db::Vertex *insert_point (const db::DPoint &point, std::list > *new_triangles = 0); - - /** - * @brief Statistics: number of flips (fixing) - */ - size_t flips () const - { - return m_flips; - } - - /** - * @brief Statistics: number of hops (searching) - */ - size_t hops () const - { - return m_hops; - } - -protected: - /** - * @brief Checks the triangle graph for consistency - * This method is for testing purposes mainly. - */ - bool check (bool check_delaunay = true) const; - - /** - * @brief Dumps the triangle graph to a GDS file at the given path - * This method is for testing purposes mainly. - * - * "decompose_id" will map triangles to layer 20, 21 and 22. - * according to bit 0, 1 and 2 of the ID (useful with the 'mark_triangles' - * flat in TriangulateParameters). - */ - void dump (const std::string &path, bool decompose_by_id = false) const; - - /** - * @brief Creates a new layout object representing the triangle graph - * This method is for testing purposes mainly. - */ - db::Layout *to_layout (bool decompose_by_id = false) const; - - /** - * @brief Finds the points within (not "on") a circle of radius "radius" around the given vertex. - */ - std::vector find_points_around (Vertex *vertex, double radius); - - /** - * @brief Inserts a new vertex as the given point - * - * If "new_triangles" is not null, it will receive the list of new triangles created during - * the remove step. - */ - db::Vertex *insert_point (db::DCoord x, db::DCoord y, std::list > *new_triangles = 0); - - /** - * @brief Removes the given vertex - * - * If "new_triangles" is not null, it will receive the list of new triangles created during - * the remove step. - */ - void remove (db::Vertex *vertex, std::list > *new_triangles = 0); - - /** - * @brief Flips the given edge - */ - std::pair, db::TriangleEdge *> flip (TriangleEdge *edge); - - /** - * @brief Finds all edges that cross the given one for a convex triangulation - * - * Requirements: - * * self must be a convex triangulation - * * edge must not contain another vertex from the triangulation except p1 and p2 - */ - std::vector search_edges_crossing (db::Vertex *from, db::Vertex *to); - - /** - * @brief Finds the edge for two given points - */ - db::TriangleEdge *find_edge_for_points (const db::DPoint &p1, const db::DPoint &p2); - - /** - * @brief Finds the vertex for a point - */ - db::Vertex *find_vertex_for_point (const db::DPoint &pt); - - /** - * @brief Ensures all points between from an to are connected by edges and makes these segments - */ - std::vector ensure_edge (db::Vertex *from, db::Vertex *to); - - /** - * @brief Given a set of contours with edges, mark outer triangles - * - * The edges must be made from existing vertexes. Edge orientation is - * clockwise. - * - * This will also mark triangles as outside ones. - */ - void constrain (const std::vector > &contours); - - /** - * @brief Removes the outside triangles. - */ - void remove_outside_triangles (); - - /** - * @brief Creates a constrained Delaunay triangulation from the given Region - */ - void create_constrained_delaunay (const db::Region ®ion, const db::CplxTrans &trans = db::CplxTrans ()); - - /** - * @brief Creates a constrained Delaunay triangulation from the given Polygon - */ - void create_constrained_delaunay (const db::Polygon &poly, const std::vector &vertexes, const db::CplxTrans &trans = db::CplxTrans ()); - - /** - * @brief Creates a constrained Delaunay triangulation from the given DPolygon - */ - void create_constrained_delaunay (const db::DPolygon &poly, const std::vector &vertexes, const DCplxTrans &trans); - - /** - * @brief Returns a value indicating whether the edge is "illegal" (violates the Delaunay criterion) - */ - static bool is_illegal_edge (db::TriangleEdge *edge); - - // NOTE: these functions are SLOW and intended to test purposes only - std::vector find_touching (const db::DBox &box) const; - std::vector find_inside_circle (const db::DPoint ¢er, double radius) const; - -private: - struct TriangleBoxConvert - { - typedef db::DBox box_type; - box_type operator() (db::Triangle *t) const - { - return t ? t->bbox () : box_type (); - } - }; - - tl::list mp_triangles; - tl::stable_vector m_edges_heap; - std::vector m_returned_edges; - tl::stable_vector m_vertex_heap; - bool m_is_constrained; - size_t m_level; - size_t m_id; - size_t m_flips, m_hops; - - db::Vertex *create_vertex (double x, double y); - db::Vertex *create_vertex (const db::DPoint &pt); - db::TriangleEdge *create_edge (db::Vertex *v1, db::Vertex *v2); - db::Triangle *create_triangle (db::TriangleEdge *e1, db::TriangleEdge *e2, db::TriangleEdge *e3); - void remove_triangle (db::Triangle *tri); - - void remove_outside_vertex (db::Vertex *vertex, std::list > *new_triangles = 0); - void remove_inside_vertex (db::Vertex *vertex, std::list > *new_triangles_out = 0); - std::vector fill_concave_corners (const std::vector &edges); - void fix_triangles (const std::vector &tris, const std::vector &fixed_edges, std::list > *new_triangles); - std::vector find_triangle_for_point (const db::DPoint &point); - db::TriangleEdge *find_closest_edge (const db::DPoint &p, db::Vertex *vstart = 0, bool inside_only = false); - db::Vertex *insert (db::Vertex *vertex, std::list > *new_triangles = 0); - void split_triangle (db::Triangle *t, db::Vertex *vertex, std::list > *new_triangles_out); - void split_triangles_on_edge (db::Vertex *vertex, db::TriangleEdge *split_edge, std::list > *new_triangles_out); - void add_more_triangles (std::vector &new_triangles, - db::TriangleEdge *incoming_edge, - db::Vertex *from_vertex, db::Vertex *to_vertex, - db::TriangleEdge *conn_edge); - void insert_new_vertex(db::Vertex *vertex, std::list > *new_triangles_out); - std::vector ensure_edge_inner (db::Vertex *from, db::Vertex *to); - void join_edges (std::vector &edges); - void refine (const TriangulateParameters ¶m); - template void make_contours (const Poly &poly, const Trans &trans, std::vector > &contours); -}; - -} - -#endif - diff --git a/src/db/db/gsiDeclDbPolygon.cc b/src/db/db/gsiDeclDbPolygon.cc index 8282198ab..1a4fa5335 100644 --- a/src/db/db/gsiDeclDbPolygon.cc +++ b/src/db/db/gsiDeclDbPolygon.cc @@ -28,13 +28,13 @@ #include "dbPolygonTools.h" #include "dbPolygonGenerators.h" #include "dbHash.h" -#include "dbTriangles.h" +#include "dbPLCTriangulation.h" namespace gsi { template -static db::Region region_from_triangles (const db::Triangles &tri, const T &trans) +static db::Region region_from_triangles (const db::plc::Graph &tri, const T &trans) { db::Region result; @@ -53,10 +53,10 @@ static db::Region region_from_triangles (const db::Triangles &tri, const T &tran } template -static std::vector

polygons_from_triangles (const db::Triangles &tri, const T &trans) +static std::vector

polygons_from_triangles (const db::plc::Graph &tri, const T &trans) { std::vector

result; - result.reserve (tri.num_triangles ()); + result.reserve (tri.num_polygons ()); typename P::point_type pts [3]; @@ -89,14 +89,15 @@ static db::polygon to_polygon (const db::polygon &p) template static db::Region triangulate_ipolygon (const P *p, double max_area = 0.0, double min_b = 0.0, double dbu = 0.001) { - db::Triangles tris; - db::Triangles::TriangulateParameters param; + db::plc::Graph tris; + db::plc::Triangulation triangulation (&tris); + db::plc::TriangulationParameters param; param.min_b = min_b; param.max_area = max_area * dbu * dbu; db::CplxTrans trans = db::CplxTrans (dbu) * db::ICplxTrans (db::Trans (db::Point () - p->box ().center ())); - tris.triangulate (to_polygon (*p), param, trans); + triangulation.triangulate (to_polygon (*p), param, trans); return region_from_triangles (tris, trans.inverted ()); } @@ -104,14 +105,15 @@ static db::Region triangulate_ipolygon (const P *p, double max_area = 0.0, doubl template static db::Region triangulate_ipolygon_v (const P *p, const std::vector &vertexes, double max_area = 0.0, double min_b = 0.0, double dbu = 0.001) { - db::Triangles tris; - db::Triangles::TriangulateParameters param; + db::plc::Graph tris; + db::plc::Triangulation triangulation (&tris); + db::plc::TriangulationParameters param; param.min_b = min_b; param.max_area = max_area * dbu * dbu; db::CplxTrans trans = db::CplxTrans (dbu) * db::ICplxTrans (db::Trans (db::Point () - p->box ().center ())); - tris.triangulate (to_polygon (*p), vertexes, param, trans); + triangulation.triangulate (to_polygon (*p), vertexes, param, trans); return region_from_triangles (tris, trans.inverted ()); } @@ -119,14 +121,15 @@ static db::Region triangulate_ipolygon_v (const P *p, const std::vector static std::vector

triangulate_dpolygon (const P *p, double max_area = 0.0, double min_b = 0.0) { - db::Triangles tris; - db::Triangles::TriangulateParameters param; + db::plc::Graph tris; + db::plc::Triangulation triangulation (&tris); + db::plc::TriangulationParameters param; param.min_b = min_b; param.max_area = max_area; db::DCplxTrans trans = db::DCplxTrans (db::DTrans (db::DPoint () - p->box ().center ())); - tris.triangulate (to_polygon (*p), param, trans); + triangulation.triangulate (to_polygon (*p), param, trans); return polygons_from_triangles (tris, trans.inverted ()); } @@ -134,14 +137,15 @@ static std::vector

triangulate_dpolygon (const P *p, double max_area = 0.0, d template static std::vector

triangulate_dpolygon_v (const P *p, const std::vector &vertexes, double max_area = 0.0, double min_b = 0.0) { - db::Triangles tris; - db::Triangles::TriangulateParameters param; + db::plc::Graph tris; + db::plc::Triangulation triangulation (&tris); + db::plc::TriangulationParameters param; param.min_b = min_b; param.max_area = max_area; db::DCplxTrans trans = db::DCplxTrans (db::DTrans (db::DPoint () - p->box ().center ())); - tris.triangulate (to_polygon (*p), vertexes, param, trans); + triangulation.triangulate (to_polygon (*p), vertexes, param, trans); return polygons_from_triangles (tris, trans.inverted ()); } diff --git a/src/db/unit_tests/dbTriangleTests.cc b/src/db/unit_tests/dbTriangleTests.cc deleted file mode 100644 index b460f96ec..000000000 --- a/src/db/unit_tests/dbTriangleTests.cc +++ /dev/null @@ -1,549 +0,0 @@ - -/* - - KLayout Layout Viewer - Copyright (C) 2006-2025 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 "dbTriangle.h" -#include "tlUnitTest.h" - -#include -#include - -class TestableEdge - : public db::TriangleEdge -{ -public: - using db::TriangleEdge::TriangleEdge; - using db::TriangleEdge::link; - using db::TriangleEdge::unlink; - - TestableEdge (db::Vertex *v1, db::Vertex *v2) - : db::TriangleEdge (v1, v2) - { } -}; - -// Tests for Vertex class - -TEST(Vertex_basic) -{ - db::Vertex v; - - v.set_x (1.5); - v.set_y (0.5); - EXPECT_EQ (v.to_string (), "(1.5, 0.5)"); - EXPECT_EQ (v.x (), 1.5); - EXPECT_EQ (v.y (), 0.5); - - v = db::Vertex (db::DPoint (2, 3)); - EXPECT_EQ (v.to_string (), "(2, 3)"); -} - -static std::string edges_from_vertex (const db::Vertex &v) -{ - std::string res; - for (auto e = v.begin_edges (); e != v.end_edges (); ++e) { - if (! res.empty ()) { - res += ", "; - } - res += (*e)->to_string (); - } - return res; -} - -static std::string triangles_from_vertex (const db::Vertex &v) -{ - auto tri = v.triangles (); - std::string res; - for (auto t = tri.begin (); t != tri.end (); ++t) { - if (! res.empty ()) { - res += ", "; - } - res += (*t)->to_string (); - } - return res; -} - -TEST(Vertex_edge_registration) -{ - db::Vertex v1 (0, 0); - db::Vertex v2 (1, 2); - db::Vertex v3 (2, 1); - - std::unique_ptr e1 (new TestableEdge (&v1, &v2)); - e1->link (); - EXPECT_EQ (edges_from_vertex (v1), "((0, 0), (1, 2))"); - EXPECT_EQ (edges_from_vertex (v2), "((0, 0), (1, 2))"); - EXPECT_EQ (edges_from_vertex (v3), ""); - - std::unique_ptr e2 (new TestableEdge (&v2, &v3)); - e2->link (); - EXPECT_EQ (edges_from_vertex (v1), "((0, 0), (1, 2))"); - EXPECT_EQ (edges_from_vertex (v2), "((0, 0), (1, 2)), ((1, 2), (2, 1))"); - EXPECT_EQ (edges_from_vertex (v3), "((1, 2), (2, 1))"); - - e2->unlink (); - e2.reset (0); - EXPECT_EQ (edges_from_vertex (v1), "((0, 0), (1, 2))"); - EXPECT_EQ (edges_from_vertex (v2), "((0, 0), (1, 2))"); - EXPECT_EQ (edges_from_vertex (v3), ""); - - e1->unlink (); - e1.reset (0); - EXPECT_EQ (edges_from_vertex (v1), ""); - EXPECT_EQ (edges_from_vertex (v2), ""); - EXPECT_EQ (edges_from_vertex (v3), ""); -} - -TEST(Vertex_triangles) -{ - db::Vertex v1 (0, 0); - db::Vertex v2 (1, 2); - db::Vertex v3 (2, 1); - db::Vertex v4 (-1, 2); - EXPECT_EQ (triangles_from_vertex (v1), ""); - - std::unique_ptr e1 (new TestableEdge (&v1, &v2)); - e1->link (); - std::unique_ptr e2 (new TestableEdge (&v2, &v3)); - e2->link (); - std::unique_ptr e3 (new TestableEdge (&v3, &v1)); - e3->link (); - - std::unique_ptr tri (new db::Triangle (e1.get (), e2.get (), e3.get ())); - EXPECT_EQ (triangles_from_vertex (v1), "((0, 0), (1, 2), (2, 1))"); - EXPECT_EQ (triangles_from_vertex (v2), "((0, 0), (1, 2), (2, 1))"); - EXPECT_EQ (triangles_from_vertex (v3), "((0, 0), (1, 2), (2, 1))"); - - std::unique_ptr e4 (new TestableEdge (&v1, &v4)); - e4->link (); - std::unique_ptr e5 (new TestableEdge (&v2, &v4)); - e5->link (); - std::unique_ptr tri2 (new db::Triangle (e1.get (), e4.get (), e5.get ())); - EXPECT_EQ (triangles_from_vertex (v1), "((0, 0), (-1, 2), (1, 2)), ((0, 0), (1, 2), (2, 1))"); - EXPECT_EQ (triangles_from_vertex (v2), "((0, 0), (-1, 2), (1, 2)), ((0, 0), (1, 2), (2, 1))"); - EXPECT_EQ (triangles_from_vertex (v3), "((0, 0), (1, 2), (2, 1))"); - EXPECT_EQ (triangles_from_vertex (v4), "((0, 0), (-1, 2), (1, 2))"); - - tri->unlink (); - EXPECT_EQ (triangles_from_vertex (v1), "((0, 0), (-1, 2), (1, 2))"); - - tri2->unlink (); - EXPECT_EQ (triangles_from_vertex (v1), ""); -} - -// Tests for Triangle class - -TEST(Triangle_basic) -{ - db::Vertex v1; - db::Vertex v2 (1, 2); - db::Vertex v3 (2, 1); - - TestableEdge s1 (&v1, &v2); - TestableEdge s2 (&v2, &v3); - TestableEdge s3 (&v3, &v1); - - EXPECT_EQ (s1.v1 () == &v1, true); - EXPECT_EQ (s2.v2 () == &v3, true); - - db::Triangle tri (&s1, &s2, &s3); - EXPECT_EQ (tri.to_string (), "((0, 0), (1, 2), (2, 1))"); - EXPECT_EQ (tri.edge (-1) == &s3, true); - EXPECT_EQ (tri.edge (0) == &s1, true); - EXPECT_EQ (tri.edge (1) == &s2, true); - EXPECT_EQ (tri.edge (3) == &s1, true); - - // ordering - TestableEdge s11 (&v1, &v2); - TestableEdge s12 (&v3, &v2); - TestableEdge s13 (&v1, &v3); - - db::Triangle tri2 (&s11, &s12, &s13); - EXPECT_EQ (tri2.to_string (), "((0, 0), (1, 2), (2, 1))"); - - // triangle registration - EXPECT_EQ (s11.right () == &tri2, true); - EXPECT_EQ (s11.left () == 0, true); - EXPECT_EQ (s12.left () == &tri2, true); - EXPECT_EQ (s12.right () == 0, true); - EXPECT_EQ (s13.left () == &tri2, true); - EXPECT_EQ (s13.right () == 0, true); - - EXPECT_EQ (s13.to_string (), "((0, 0), (2, 1))"); - s13.reverse (); - EXPECT_EQ (s13.to_string (), "((2, 1), (0, 0))"); - EXPECT_EQ (s13.right () == &tri2, true); - EXPECT_EQ (s13.left () == 0, true); - - // flags - EXPECT_EQ (tri.is_outside (), false); - tri.set_outside (true); - EXPECT_EQ (tri.is_outside (), true); -} - -TEST(Triangle_find_segment_with) -{ - db::Vertex v1; - db::Vertex v2 (1, 2); - db::Vertex v3 (2, 1); - - TestableEdge s1 (&v1, &v2); - TestableEdge s2 (&v2, &v3); - TestableEdge s3 (&v3, &v1); - - db::Triangle tri (&s1, &s2, &s3); - - EXPECT_EQ (tri.find_edge_with (&v1, &v2)->to_string (), "((0, 0), (1, 2))"); - EXPECT_EQ (tri.find_edge_with (&v2, &v1)->to_string (), "((0, 0), (1, 2))"); -} - -TEST(Triangle_ext_vertex) -{ - db::Vertex v1; - db::Vertex v2 (1, 2); - db::Vertex v3 (2, 1); - - TestableEdge s1 (&v1, &v2); - TestableEdge s2 (&v2, &v3); - TestableEdge s3 (&v3, &v1); - - db::Triangle tri (&s1, &s2, &s3); - - EXPECT_EQ (tri.opposite (&s1)->to_string (), "(2, 1)"); - EXPECT_EQ (tri.opposite (&s3)->to_string (), "(1, 2)"); -} - -TEST(Triangle_opposite_vertex) -{ - db::Vertex v1; - db::Vertex v2 (1, 2); - db::Vertex v3 (2, 1); - - TestableEdge s1 (&v1, &v2); - TestableEdge s2 (&v2, &v3); - TestableEdge s3 (&v3, &v1); - - db::Triangle tri (&s1, &s2, &s3); - - EXPECT_EQ (tri.opposite (&s1)->to_string (), "(2, 1)"); - EXPECT_EQ (tri.opposite (&s3)->to_string (), "(1, 2)"); -} - -TEST(Triangle_opposite_edge) -{ - db::Vertex v1; - db::Vertex v2 (1, 2); - db::Vertex v3 (2, 1); - - TestableEdge s1 (&v1, &v2); - TestableEdge s2 (&v2, &v3); - TestableEdge s3 (&v3, &v1); - - db::Triangle tri (&s1, &s2, &s3); - - EXPECT_EQ (tri.opposite (&v1)->to_string (), "((1, 2), (2, 1))"); - EXPECT_EQ (tri.opposite (&v3)->to_string (), "((0, 0), (1, 2))"); -} - -TEST(Triangle_contains) -{ - db::Vertex v1; - db::Vertex v2 (1, 2); - db::Vertex v3 (2, 1); - - TestableEdge s1 (&v1, &v2); - TestableEdge s2 (&v2, &v3); - TestableEdge s3 (&v3, &v1); - - { - db::Triangle tri (&s1, &s2, &s3); - EXPECT_EQ (tri.contains (db::DPoint (0, 0)), 0); - EXPECT_EQ (tri.contains (db::DPoint (-1, -2)), -1); - EXPECT_EQ (tri.contains (db::DPoint (0.5, 1)), 0); - EXPECT_EQ (tri.contains (db::DPoint (0.5, 2)), -1); - EXPECT_EQ (tri.contains (db::DPoint (2.5, 1)), -1); - EXPECT_EQ (tri.contains (db::DPoint (1, -1)), -1); - EXPECT_EQ (tri.contains (db::DPoint (1, 1)), 1); - } - - s1.reverse (); - s2.reverse (); - s3.reverse (); - - { - db::Triangle tri2 (&s3, &s2, &s1); - EXPECT_EQ (tri2.contains(db::DPoint(0, 0)), 0); - EXPECT_EQ (tri2.contains(db::DPoint(0.5, 1)), 0); - EXPECT_EQ (tri2.contains(db::DPoint(0.5, 2)), -1); - EXPECT_EQ (tri2.contains(db::DPoint(2.5, 1)), -1); - EXPECT_EQ (tri2.contains(db::DPoint(1, -1)), -1); - EXPECT_EQ (tri2.contains(db::DPoint(1, 1)), 1); - } -} - -TEST(Triangle_contains_small) -{ - db::Vertex v1; - db::Vertex v2 (0.001, 0.002); - db::Vertex v3 (0.002, 0.001); - - TestableEdge s1 (&v1, &v2); - TestableEdge s2 (&v2, &v3); - TestableEdge s3 (&v3, &v1); - - { - db::Triangle tri (&s1, &s2, &s3); - EXPECT_EQ (tri.contains (db::DPoint (0, 0)), 0); - EXPECT_EQ (tri.contains (db::DPoint (-0.001, -0.002)), -1); - EXPECT_EQ (tri.contains (db::DPoint (0.0005, 0.001)), 0); - EXPECT_EQ (tri.contains (db::DPoint (0.0005, 0.002)), -1); - EXPECT_EQ (tri.contains (db::DPoint (0.0025, 0.001)), -1); - EXPECT_EQ (tri.contains (db::DPoint (0.001, -0.001)), -1); - EXPECT_EQ (tri.contains (db::DPoint (0.001, 0.001)), 1); - } - - s1.reverse (); - s2.reverse (); - s3.reverse (); - - { - db::Triangle tri2 (&s3, &s2, &s1); - EXPECT_EQ (tri2.contains(db::DPoint(0, 0)), 0); - EXPECT_EQ (tri2.contains(db::DPoint(0.0005, 0.001)), 0); - EXPECT_EQ (tri2.contains(db::DPoint(0.0005, 0.002)), -1); - EXPECT_EQ (tri2.contains(db::DPoint(0.0025, 0.001)), -1); - EXPECT_EQ (tri2.contains(db::DPoint(0.001, -0.001)), -1); - EXPECT_EQ (tri2.contains(db::DPoint(0.001, 0.001)), 1); - } -} - -TEST(Triangle_circumcircle) -{ - db::Vertex v1; - db::Vertex v2 (1, 2); - db::Vertex v3 (2, 1); - - TestableEdge s1 (&v1, &v2); - TestableEdge s2 (&v2, &v3); - TestableEdge s3 (&v3, &v1); - - db::Triangle tri (&s1, &s2, &s3); - - auto cc = tri.circumcircle (); - auto center = cc.first; - auto radius = cc.second; - - EXPECT_EQ (tl::to_string (center), "0.833333333333,0.833333333333"); - EXPECT_EQ (tl::to_string (radius), "1.17851130198"); - - EXPECT_EQ (db::Vertex::in_circle (center, center, radius), 1); - EXPECT_EQ (db::Vertex::in_circle (db::DPoint (-1, -1), center, radius), -1); - EXPECT_EQ (v1.in_circle (center, radius), 0); - EXPECT_EQ (v2.in_circle (center, radius), 0); - EXPECT_EQ (v3.in_circle (center, radius), 0); -} - -// Tests for TriangleEdge class - -TEST(TriangleEdge_basic) -{ - db::Vertex v1; - db::Vertex v2 (1, 0.5); - - TestableEdge edge (&v1, &v2); - EXPECT_EQ (edge.to_string (), "((0, 0), (1, 0.5))"); - - EXPECT_EQ (edge.is_segment (), false); - edge.set_is_segment (true); - EXPECT_EQ (edge.is_segment (), true); - - EXPECT_EQ (edge.level (), size_t (0)); - edge.set_level (42); - EXPECT_EQ (edge.level (), size_t (42)); - - EXPECT_EQ (edge.other (&v1) == &v2, true); - EXPECT_EQ (edge.other (&v2) == &v1, true); -} - -TEST(TriangleEdge_triangles) -{ - db::Vertex v1 (0, 0); - db::Vertex v2 (1, 2); - db::Vertex v3 (2, 1); - db::Vertex v4 (-1, 2); - - std::unique_ptr e1 (new TestableEdge (&v1, &v2)); - std::unique_ptr e2 (new TestableEdge (&v2, &v3)); - std::unique_ptr e3 (new TestableEdge (&v3, &v1)); - - std::unique_ptr tri (new db::Triangle (e1.get (), e2.get (), e3.get ())); - - std::unique_ptr e4 (new TestableEdge (&v1, &v4)); - std::unique_ptr e5 (new TestableEdge (&v2, &v4)); - std::unique_ptr tri2 (new db::Triangle (e1.get (), e4.get (), e5.get ())); - - EXPECT_EQ (e1->is_outside (), false); - EXPECT_EQ (e2->is_outside (), true); - EXPECT_EQ (e4->is_outside (), true); - - EXPECT_EQ (e1->is_for_outside_triangles (), false); - tri->set_outside (true); - EXPECT_EQ (e1->is_for_outside_triangles (), true); - - EXPECT_EQ (e1->has_triangle (tri.get ()), true); - EXPECT_EQ (e1->has_triangle (tri2.get ()), true); - EXPECT_EQ (e4->has_triangle (tri.get ()), false); - EXPECT_EQ (e4->has_triangle (tri2.get ()), true); - - EXPECT_EQ (e1->other (tri.get ()) == tri2.get (), true); - EXPECT_EQ (e1->other (tri2.get ()) == tri.get (), true); - - EXPECT_EQ (e1->common_vertex (e2.get ()) == &v2, true); - EXPECT_EQ (e2->common_vertex (e4.get ()) == 0, true); - - tri->unlink (); - EXPECT_EQ (e1->has_triangle (tri.get ()), false); - EXPECT_EQ (e1->has_triangle (tri2.get ()), true); -} - -TEST(TriangleEdge_side_of) -{ - db::Vertex v1; - db::Vertex v2 (1, 0.5); - - TestableEdge edge (&v1, &v2); - EXPECT_EQ (edge.to_string (), "((0, 0), (1, 0.5))"); - - EXPECT_EQ (edge.side_of (db::Vertex (0, 0)), 0) - EXPECT_EQ (edge.side_of (db::Vertex (0.5, 0.25)), 0) - EXPECT_EQ (edge.side_of (db::Vertex (0, 1)), -1) - EXPECT_EQ (edge.side_of (db::Vertex (0, -1)), 1) - EXPECT_EQ (edge.side_of (db::Vertex (0.5, 0.5)), -1) - EXPECT_EQ (edge.side_of (db::Vertex (0.5, 0)), 1) - - db::Vertex v3 (1, 0); - db::Vertex v4 (0, 1); - TestableEdge edge2 (&v3, &v4); - - EXPECT_EQ (edge2.side_of (db::Vertex(0.2, 0.2)), -1); -} - -namespace { - class VertexHeap - { - public: - db::Vertex *make_vertex (double x, double y) - { - m_heap.push_back (db::Vertex (x, y)); - return &m_heap.back (); - } - private: - std::list m_heap; - }; -} - -TEST(TriangleEdge_crosses) -{ - VertexHeap heap; - - TestableEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5)); - EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, -0.5), heap.make_vertex(1, -0.5))), false); - EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0), heap.make_vertex(1, 0))), false); // only cuts - EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(1, 0.5))), false); - EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(2, 0.5))), false); - EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.25), heap.make_vertex(2, 0.25))), true); - EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.5), heap.make_vertex(-0.1, 0.5))), false); - EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 0.6), heap.make_vertex(0, 0.6))), false); - EXPECT_EQ (s1.crosses (TestableEdge (heap.make_vertex (-1, 1), heap.make_vertex(1, 1))), false); - - EXPECT_EQ (s1.crosses_including (TestableEdge (heap.make_vertex (-1, -0.5), heap.make_vertex(1, -0.5))), false); - EXPECT_EQ (s1.crosses_including (TestableEdge (heap.make_vertex (-1, 0), heap.make_vertex(1, 0))), true); // only cuts - EXPECT_EQ (s1.crosses_including (TestableEdge (heap.make_vertex (-1, 0.25), heap.make_vertex(2, 0.25))), true); -} - -TEST(TriangleEdge_point_on) -{ - VertexHeap heap; - - TestableEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5)); - EXPECT_EQ (s1.point_on (db::DPoint (0, 0)), false); // endpoints are not "on" - EXPECT_EQ (s1.point_on (db::DPoint (0, -0.5)), false); - EXPECT_EQ (s1.point_on (db::DPoint (0.5, 0)), false); - EXPECT_EQ (s1.point_on (db::DPoint (0.5, 0.25)), true); - EXPECT_EQ (s1.point_on (db::DPoint (1, 0.5)), false); // endpoints are not "on" - EXPECT_EQ (s1.point_on (db::DPoint (1, 1)), false); - EXPECT_EQ (s1.point_on (db::DPoint (2, 1)), false); -} - -TEST(TriangleEdge_intersection_point) -{ - VertexHeap heap; - - TestableEdge s1 (heap.make_vertex (0, 0), heap.make_vertex (1, 0.5)); - EXPECT_EQ (s1.intersection_point (TestableEdge (heap.make_vertex (-1, 0.25), heap.make_vertex (2, 0.25))).to_string (), "0.5,0.25"); -} - -TEST(TriangleEdge_can_flip) -{ - db::Vertex v1 (2, -1); - db::Vertex v2 (0, 0); - db::Vertex v3 (1, 0); - db::Vertex v4 (0.5, 1); - TestableEdge s1 (&v1, &v2); - TestableEdge s2 (&v1, &v3); - TestableEdge s3 (&v2, &v3); - TestableEdge s4 (&v2, &v4); - TestableEdge s5 (&v3, &v4); - db::Triangle t1 (&s1, &s2, &s3); - db::Triangle t2 (&s3, &s4, &s5); - EXPECT_EQ (s3.left () == &t2, true); - EXPECT_EQ (s3.right () == &t1, true); - EXPECT_EQ (s3.can_flip(), false); - v1.set_x (0.5); - EXPECT_EQ (s3.can_flip(), true); - v1.set_x (-0.25); - EXPECT_EQ (s3.can_flip(), true); - v1.set_x (-0.5); - EXPECT_EQ (s3.can_flip(), false); - v1.set_x (-1.0); - EXPECT_EQ (s3.can_flip(), false); -} - -TEST(TriangleEdge_distance) -{ - db::Vertex v1 (0, 0); - db::Vertex v2 (1, 0); - - TestableEdge seg (&v1, &v2); - EXPECT_EQ (seg.distance (db::DPoint (0, 0)), 0); - EXPECT_EQ (seg.distance (db::DPoint (0, 1)), 1); - EXPECT_EQ (seg.distance (db::DPoint (1, 2)), 2); - EXPECT_EQ (seg.distance (db::DPoint (1, -1)), 1); - EXPECT_EQ (seg.distance (db::DPoint (2, 0)), 1); - EXPECT_EQ (seg.distance (db::DPoint (-2, 0)), 2); - seg.reverse (); - EXPECT_EQ (seg.distance (db::DPoint (0, 0)), 0); - EXPECT_EQ (seg.distance (db::DPoint (0, 1)), 1); - EXPECT_EQ (seg.distance (db::DPoint (1, 2)), 2); - EXPECT_EQ (seg.distance (db::DPoint (1, -1)), 1); - EXPECT_EQ (seg.distance (db::DPoint (2, 0)), 1); - EXPECT_EQ (seg.distance (db::DPoint (-2, 0)), 2); -} diff --git a/src/db/unit_tests/dbTrianglesTests.cc b/src/db/unit_tests/dbTrianglesTests.cc deleted file mode 100644 index cd5779f2d..000000000 --- a/src/db/unit_tests/dbTrianglesTests.cc +++ /dev/null @@ -1,1538 +0,0 @@ - -/* - - KLayout Layout Viewer - Copyright (C) 2006-2025 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 "dbTriangles.h" -#include "dbWriter.h" -#include "dbRegionProcessors.h" -#include "tlUnitTest.h" -#include "tlStream.h" -#include "tlFileUtils.h" - -#include -#include -#include -#include - -class TestableTriangles - : public db::Triangles -{ -public: - using db::Triangles::Triangles; - using db::Triangles::check; - using db::Triangles::dump; - using db::Triangles::flip; - using db::Triangles::insert_point; - using db::Triangles::search_edges_crossing; - using db::Triangles::find_edge_for_points; - using db::Triangles::find_points_around; - using db::Triangles::find_inside_circle; - using db::Triangles::create_constrained_delaunay; - using db::Triangles::is_illegal_edge; - using db::Triangles::find_vertex_for_point; - using db::Triangles::remove; - using db::Triangles::ensure_edge; - using db::Triangles::constrain; - using db::Triangles::remove_outside_triangles; -}; - -TEST(basic) -{ - TestableTriangles tris; - tris.init_box (db::DBox (1, 0, 5, 4)); - - EXPECT_EQ (tris.bbox ().to_string (), "(1,0;5,4)"); - EXPECT_EQ (tris.to_string (), "((1, 0), (1, 4), (5, 0)), ((1, 4), (5, 4), (5, 0))"); - - EXPECT_EQ (tris.check (), true); - - tris.clear (); - - EXPECT_EQ (tris.bbox ().to_string (), "()"); - EXPECT_EQ (tris.to_string (), ""); - - EXPECT_EQ (tris.check (), true); -} - -TEST(flip) -{ - TestableTriangles tris; - tris.init_box (db::DBox (0, 0, 1, 1)); - EXPECT_EQ (tris.to_string (), "((0, 0), (0, 1), (1, 0)), ((0, 1), (1, 1), (1, 0))"); - - EXPECT_EQ (tris.num_triangles (), size_t (2)); - - const db::Triangle &t1 = *tris.begin (); - db::TriangleEdge *diag_segment; - for (int i = 0; i < 3; ++i) { - diag_segment = t1.edge (i); - if (diag_segment->side_of (db::DPoint (0.5, 0.5)) == 0) { - break; - } - } - tris.flip (diag_segment); - EXPECT_EQ (tris.to_string (), "((1, 1), (0, 0), (0, 1)), ((1, 1), (1, 0), (0, 0))"); - EXPECT_EQ (tris.check (), true); -} - -TEST(insert) -{ - TestableTriangles tris; - tris.init_box (db::DBox (0, 0, 1, 1)); - - tris.insert_point (0.2, 0.2); - EXPECT_EQ (tris.to_string (), "((0, 0), (0, 1), (0.2, 0.2)), ((1, 0), (0, 0), (0.2, 0.2)), ((1, 1), (0.2, 0.2), (0, 1)), ((1, 1), (1, 0), (0.2, 0.2))"); - EXPECT_EQ (tris.check (), true); -} - -TEST(split_segment) -{ - TestableTriangles tris; - tris.init_box (db::DBox (0, 0, 1, 1)); - - tris.insert_point (0.5, 0.5); - EXPECT_EQ (tris.to_string (), "((1, 1), (1, 0), (0.5, 0.5)), ((1, 1), (0.5, 0.5), (0, 1)), ((0, 0), (0, 1), (0.5, 0.5)), ((0, 0), (0.5, 0.5), (1, 0))"); - EXPECT_EQ (tris.check(), true); -} - -TEST(insert_vertex_twice) -{ - TestableTriangles tris; - tris.init_box (db::DBox (0, 0, 1, 1)); - - tris.insert_point (0.5, 0.5); - // inserted a vertex twice does not change anything - tris.insert_point (0.5, 0.5); - EXPECT_EQ (tris.to_string (), "((1, 1), (1, 0), (0.5, 0.5)), ((1, 1), (0.5, 0.5), (0, 1)), ((0, 0), (0, 1), (0.5, 0.5)), ((0, 0), (0.5, 0.5), (1, 0))"); - EXPECT_EQ (tris.check(), true); -} - -TEST(insert_vertex_convex) -{ - TestableTriangles tris; - tris.insert_point (0.2, 0.2); - tris.insert_point (0.2, 0.8); - tris.insert_point (0.6, 0.5); - tris.insert_point (0.7, 0.5); - tris.insert_point (0.6, 0.4); - EXPECT_EQ (tris.to_string (), "((0.2, 0.2), (0.2, 0.8), (0.6, 0.5)), ((0.2, 0.8), (0.7, 0.5), (0.6, 0.5)), ((0.6, 0.4), (0.6, 0.5), (0.7, 0.5)), ((0.6, 0.4), (0.2, 0.2), (0.6, 0.5))"); - EXPECT_EQ (tris.check(), true); -} - -TEST(insert_vertex_convex2) -{ - TestableTriangles tris; - tris.insert_point (0.25, 0.1); - tris.insert_point (0.1, 0.4); - tris.insert_point (0.4, 0.15); - tris.insert_point (1, 0.7); - EXPECT_EQ (tris.to_string (), "((0.25, 0.1), (0.1, 0.4), (0.4, 0.15)), ((1, 0.7), (0.4, 0.15), (0.1, 0.4))"); - EXPECT_EQ (tris.check(), true); -} - -TEST(insert_vertex_convex3) -{ - TestableTriangles tris; - tris.insert_point (0.25, 0.5); - tris.insert_point (0.25, 0.55); - tris.insert_point (0.15, 0.8); - tris.insert_point (1, 0.4); - EXPECT_EQ (tris.to_string (), "((0.25, 0.5), (0.15, 0.8), (0.25, 0.55)), ((1, 0.4), (0.25, 0.5), (0.25, 0.55)), ((0.15, 0.8), (1, 0.4), (0.25, 0.55))"); - EXPECT_EQ (tris.check(), true); -} - -TEST(search_edges_crossing) -{ - TestableTriangles tris; - db::Vertex *v1 = tris.insert_point (0.2, 0.2); - db::Vertex *v2 = tris.insert_point (0.2, 0.8); - db::Vertex *v3 = tris.insert_point (0.6, 0.5); - /*db::Vertex *v4 =*/ tris.insert_point (0.7, 0.5); - db::Vertex *v5 = tris.insert_point (0.6, 0.4); - db::Vertex *v6 = tris.insert_point (0.7, 0.2); - EXPECT_EQ (tris.check(), true); - - auto xedges = tris.search_edges_crossing (v2, v6); - - EXPECT_EQ (xedges.size (), size_t (2)); - auto s1 = tris.find_edge_for_points (*v1, *v3); - auto s2 = tris.find_edge_for_points (*v1, *v5); - EXPECT_EQ (std::find (xedges.begin (), xedges.end (), s1) != xedges.end (), true); - EXPECT_EQ (std::find (xedges.begin (), xedges.end (), s2) != xedges.end (), true); -} - -TEST(illegal_edge1) -{ - db::Vertex v1 (0, 0); - db::Vertex v2 (1.6, 1.6); - db::Vertex v3 (1, 2); - db::Vertex v4 (2, 1); - - { - db::TriangleEdge e1 (&v1, &v3); - db::TriangleEdge e2 (&v3, &v4); - db::TriangleEdge e3 (&v4, &v1); - - db::Triangle t1 (&e1, &e2, &e3); - - db::TriangleEdge ee1 (&v2, &v3); - db::TriangleEdge ee2 (&v4, &v2); - - db::Triangle t2 (&ee1, &e2, &ee2); - - EXPECT_EQ (TestableTriangles::is_illegal_edge (&e2), true); - } - - { - // flipped - db::TriangleEdge e1 (&v1, &v2); - db::TriangleEdge e2 (&v2, &v3); - db::TriangleEdge e3 (&v3, &v1); - - db::Triangle t1 (&e1, &e2, &e3); - - db::TriangleEdge ee1 (&v1, &v4); - db::TriangleEdge ee2 (&v4, &v2); - - db::Triangle t2 (&ee1, &ee2, &e1); - - EXPECT_EQ (TestableTriangles::is_illegal_edge (&e2), false); - } -} - -TEST(illegal_edge2) -{ - // numerical border case - db::Vertex v1 (773.94756216690905, 114.45875269431208); - db::Vertex v2 (773.29574734131643, 113.47402096138073); - db::Vertex v3 (773.10652961562653, 114.25497975904504); - db::Vertex v4 (774.08856345337881, 113.60495072750861); - - { - db::TriangleEdge e1 (&v1, &v2); - db::TriangleEdge e2 (&v2, &v4); - db::TriangleEdge e3 (&v4, &v1); - - db::Triangle t1 (&e1, &e2, &e3); - - db::TriangleEdge ee1 (&v2, &v3); - db::TriangleEdge ee2 (&v3, &v4); - - db::Triangle t2 (&ee1, &ee2, &e2); - - EXPECT_EQ (TestableTriangles::is_illegal_edge (&e2), false); - } - - { - // flipped - db::TriangleEdge e1 (&v1, &v2); - db::TriangleEdge e2 (&v2, &v3); - db::TriangleEdge e3 (&v3, &v1); - - db::Triangle t1 (&e1, &e2, &e3); - - db::TriangleEdge ee1 (&v1, &v4); - db::TriangleEdge ee2 (&v4, &v2); - - db::Triangle t2 (&ee1, &ee2, &e1); - - EXPECT_EQ (TestableTriangles::is_illegal_edge (&e1), false); - } -} - -// Returns a random float number between 0.0 and 1.0 -inline double flt_rand () -{ - return rand () * (1.0 / double (RAND_MAX)); -} - -namespace { - struct PointLessOp - { - bool operator() (const db::DPoint &a, const db::DPoint &b) const - { - return a.less (b); - } - }; -} - -TEST(insert_many) -{ - srand (0); - - TestableTriangles tris; - double res = 65536.0; - - db::DBox bbox; - - unsigned int n = 200000; - for (unsigned int i = 0; i < n; ++i) { - double x = round (flt_rand () * res) * 0.0001; - double y = round (flt_rand () * res) * 0.0001; - tris.insert_point (x, y); - } - - EXPECT_LT (double (tris.flips ()) / double (n), 3.1); - EXPECT_LT (double (tris.hops ()) / double (n), 23.0); -} - -TEST(heavy_insert) -{ - tl::info << "Running test_heavy_insert " << tl::noendl; - - for (unsigned int l = 0; l < 100; ++l) { - - srand (l); - tl::info << "." << tl::noendl; - - TestableTriangles tris; - double res = 128.0; - - unsigned int n = rand () % 190 + 10; - - db::DBox bbox; - std::map vmap; - - for (unsigned int i = 0; i < n; ++i) { - double x = round (flt_rand () * res) * (1.0 / res); - double y = round (flt_rand () * res) * (1.0 / res); - db::Vertex *v = tris.insert_point (x, y); - bbox += db::DPoint (x, y); - vmap.insert (std::make_pair (*v, false)); - } - - // not strictly true, but very likely with at least 10 vertexes: - EXPECT_GT (tris.num_triangles (), size_t (0)); - EXPECT_EQ (tris.bbox ().to_string (), bbox.to_string ()); - - bool ok = true; - for (auto t = tris.begin (); t != tris.end (); ++t) { - for (int i = 0; i < 3; ++i) { - auto f = vmap.find (*t->vertex (i)); - if (f == vmap.end ()) { - tl::error << "Could not identify triangle vertex " << t->vertex (i)->to_string () << " as inserted vertex"; - ok = false; - } else { - f->second = true; - } - } - } - for (auto m = vmap.begin (); m != vmap.end (); ++m) { - if (!m->second) { - tl::error << "Could not identify vertex " << m->first.to_string () << " with a triangle"; - ok = false; - } - } - EXPECT_EQ (ok, true); - - EXPECT_EQ (tris.check(), true); - - } - - tl::info << tl::endl << "done."; -} - -TEST(heavy_remove) -{ - tl::info << "Running test_heavy_remove " << tl::noendl; - - for (unsigned int l = 0; l < 100; ++l) { - - srand (l); - tl::info << "." << tl::noendl; - - TestableTriangles tris; - double res = 128.0; - - unsigned int n = rand () % 190 + 10; - - for (unsigned int i = 0; i < n; ++i) { - double x = round (flt_rand () * res) * (1.0 / res); - double y = round (flt_rand () * res) * (1.0 / res); - tris.insert_point (x, y); - } - - EXPECT_EQ (tris.check(), true); - - std::set vset; - std::vector vertexes; - for (auto t = tris.begin (); t != tris.end (); ++t) { - for (int i = 0; i < 3; ++i) { - db::Vertex *v = t->vertex (i); - if (vset.insert (v).second) { - vertexes.push_back (v); - } - } - } - - while (! vertexes.empty ()) { - - unsigned int n = rand () % (unsigned int) vertexes.size (); - db::Vertex *v = vertexes [n]; - tris.remove (v); - vertexes.erase (vertexes.begin () + n); - - // just a few times as it wastes time otherwise - if (vertexes.size () % 10 == 0) { - EXPECT_EQ (tris.check (), true); - } - - } - - EXPECT_EQ (tris.num_triangles (), size_t (0)); - - } - - tl::info << tl::endl << "done."; -} - -TEST(ensure_edge) -{ - srand (0); - - TestableTriangles tris; - double res = 128.0; - - db::DEdge ee[] = { - db::DEdge (0.25, 0.25, 0.25, 0.75), - db::DEdge (0.25, 0.75, 0.75, 0.75), - db::DEdge (0.75, 0.75, 0.75, 0.25), - db::DEdge (0.75, 0.25, 0.25, 0.25) - }; - - for (unsigned int i = 0; i < 200; ++i) { - double x = round (flt_rand () * res) * (1.0 / res); - double y = round (flt_rand () * res) * (1.0 / res); - bool ok = true; - for (unsigned int j = 0; j < sizeof (ee) / sizeof (ee[0]); ++j) { - if (ee[j].side_of (db::DPoint (x, y)) == 0) { - --i; - ok = false; - } - } - if (ok) { - tris.insert_point (x, y); - } - } - - for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) { - tris.insert_point (ee[i].p1 ()); - } - - EXPECT_EQ (tris.check (), true); - - for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) { - tris.ensure_edge (tris.find_vertex_for_point (ee[i].p1 ()), tris.find_vertex_for_point (ee[i].p2 ())); - } - - EXPECT_EQ (tris.check (false), true); - - double area_in = 0.0; - db::DBox clip_box; - for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) { - clip_box += ee[i].p1 (); - } - for (auto t = tris.begin (); t != tris.end (); ++t) { - if (clip_box.overlaps (t->bbox ())) { - EXPECT_EQ (t->bbox ().inside (clip_box), true); - area_in += t->area (); - } - } - - EXPECT_EQ (tl::to_string (area_in), "0.25"); -} - -static bool safe_inside (const db::DBox &b1, const db::DBox &b2) -{ - typedef db::coord_traits ct; - - return (ct::less (b2.left (), b1.left ()) || ct::equal (b2.left (), b1.left ())) && - (ct::less (b1.right (), b2.right ()) || ct::equal (b1.right (), b2.right ())) && - (ct::less (b2.bottom (), b1.bottom ()) || ct::equal (b2.bottom (), b1.bottom ())) && - (ct::less (b1.top (), b2.top ()) || ct::equal (b1.top (), b2.top ())); -} - -TEST(constrain) -{ - srand (0); - - TestableTriangles tris; - double res = 128.0; - - db::DEdge ee[] = { - db::DEdge (0.25, 0.25, 0.25, 0.75), - db::DEdge (0.25, 0.75, 0.75, 0.75), - db::DEdge (0.75, 0.75, 0.75, 0.25), - db::DEdge (0.75, 0.25, 0.25, 0.25) - }; - - for (unsigned int i = 0; i < 200; ++i) { - double x = round (flt_rand () * res) * (1.0 / res); - double y = round (flt_rand () * res) * (1.0 / res); - bool ok = true; - for (unsigned int j = 0; j < sizeof (ee) / sizeof (ee[0]); ++j) { - if (ee[j].side_of (db::DPoint (x, y)) == 0) { - --i; - ok = false; - } - } - if (ok) { - tris.insert_point (x, y); - } - } - - std::vector contour; - for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) { - contour.push_back (tris.insert_point (ee[i].p1 ())); - } - std::vector > contours; - contours.push_back (contour); - - EXPECT_EQ (tris.check (), true); - - tris.constrain (contours); - EXPECT_EQ (tris.check (false), true); - - tris.remove_outside_triangles (); - - EXPECT_EQ (tris.check (), true); - - double area_in = 0.0; - db::DBox clip_box; - for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) { - clip_box += ee[i].p1 (); - } - for (auto t = tris.begin (); t != tris.end (); ++t) { - EXPECT_EQ (clip_box.overlaps (t->bbox ()), true); - EXPECT_EQ (safe_inside (t->bbox (), clip_box), true); - area_in += t->area (); - } - - EXPECT_EQ (tl::to_string (area_in), "0.25"); -} - -TEST(heavy_constrain) -{ - tl::info << "Running test_heavy_constrain " << tl::noendl; - - for (unsigned int l = 0; l < 100; ++l) { - - srand (l); - tl::info << "." << tl::noendl; - - TestableTriangles tris; - double res = 128.0; - - db::DEdge ee[] = { - db::DEdge (0.25, 0.25, 0.25, 0.75), - db::DEdge (0.25, 0.75, 0.75, 0.75), - db::DEdge (0.75, 0.75, 0.75, 0.25), - db::DEdge (0.75, 0.25, 0.25, 0.25) - }; - - unsigned int n = rand () % 150 + 50; - - for (unsigned int i = 0; i < n; ++i) { - double x = round (flt_rand () * res) * (1.0 / res); - double y = round (flt_rand () * res) * (1.0 / res); - bool ok = true; - for (unsigned int j = 0; j < sizeof (ee) / sizeof (ee[0]); ++j) { - if (ee[j].side_of (db::DPoint (x, y)) == 0) { - --i; - ok = false; - } - } - if (ok) { - tris.insert_point (x, y); - } - } - - std::vector contour; - for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) { - contour.push_back (tris.insert_point (ee[i].p1 ())); - } - std::vector > contours; - contours.push_back (contour); - - EXPECT_EQ (tris.check (), true); - - tris.constrain (contours); - EXPECT_EQ (tris.check (false), true); - - tris.remove_outside_triangles (); - - EXPECT_EQ (tris.check (), true); - - double area_in = 0.0; - db::DBox clip_box; - for (unsigned int i = 0; i < sizeof (ee) / sizeof (ee[0]); ++i) { - clip_box += ee[i].p1 (); - } - for (auto t = tris.begin (); t != tris.end (); ++t) { - EXPECT_EQ (clip_box.overlaps (t->bbox ()), true); - EXPECT_EQ (safe_inside (t->bbox (), clip_box), true); - area_in += t->area (); - } - - EXPECT_EQ (tl::to_string (area_in), "0.25"); - - } - - tl::info << tl::endl << "done."; -} - -TEST(heavy_find_point_around) -{ - tl::info << "Running Triangle_test_heavy_find_point_around " << tl::noendl; - - for (unsigned int l = 0; l < 100; ++l) { - - srand (l); - tl::info << "." << tl::noendl; - - TestableTriangles tris; - double res = 128.0; - - unsigned int n = rand () % 190 + 10; - - std::vector vertexes; - - for (unsigned int i = 0; i < n; ++i) { - double x = round (flt_rand () * res) * (1.0 / res); - double y = round (flt_rand () * res) * (1.0 / res); - vertexes.push_back (tris.insert_point (x, y)); - } - - EXPECT_EQ (tris.check(), true); - - for (int i = 0; i < 100; ++i) { - - unsigned int nv = rand () % (unsigned int) vertexes.size (); - auto vertex = vertexes [nv]; - - double r = round (flt_rand () * res) * (1.0 / res); - auto p1 = tris.find_points_around (vertex, r); - auto p2 = tris.find_inside_circle (*vertex, r); - - std::set sp1 (p1.begin (), p1.end ()); - std::set sp2 (p2.begin (), p2.end ()); - sp2.erase (vertex); - - EXPECT_EQ (sp1 == sp2, true); - - } - - } - - tl::info << tl::endl << "done."; -} - -TEST(create_constrained_delaunay) -{ - db::Region r; - r.insert (db::Box (0, 0, 1000, 1000)); - - db::Region r2; - r2.insert (db::Box (200, 200, 800, 800)); - - r -= r2; - - TestableTriangles tri; - tri.create_constrained_delaunay (r); - tri.remove_outside_triangles (); - - EXPECT_EQ (tri.check (), true); - - EXPECT_EQ (tri.to_string (), - "((1000, 0), (0, 0), (200, 200)), " - "((0, 1000), (200, 200), (0, 0)), " - "((1000, 0), (200, 200), (800, 200)), " - "((1000, 0), (800, 200), (1000, 1000)), " - "((800, 200), (800, 800), (1000, 1000)), " - "((0, 1000), (1000, 1000), (800, 800)), " - "((0, 1000), (800, 800), (200, 800)), " - "((0, 1000), (200, 800), (200, 200))"); -} - -TEST(triangulate_basic) -{ - db::Region r; - r.insert (db::Box (0, 0, 10000, 10000)); - - db::Region r2; - r2.insert (db::Box (2000, 2000, 8000, 8000)); - - r -= r2; - - db::Triangles::TriangulateParameters param; - param.min_b = 1.2; - param.max_area = 1.0; - - TestableTriangles tri; - tri.triangulate (r, param, 0.001); - - EXPECT_EQ (tri.check (), true); - - for (auto t = tri.begin (); t != tri.end (); ++t) { - EXPECT_LE (t->area (), param.max_area); - EXPECT_GE (t->b (), param.min_b); - } - - EXPECT_GT (tri.num_triangles (), size_t (100)); - EXPECT_LT (tri.num_triangles (), size_t (150)); - - // for debugging: - // tri.dump ("debug.gds"); - - param.min_b = 1.0; - param.max_area = 0.1; - - tri.triangulate (r, param, 0.001); - - EXPECT_EQ (tri.check (), true); - - for (auto t = tri.begin (); t != tri.end (); ++t) { - EXPECT_LE (t->area (), param.max_area); - EXPECT_GE (t->b (), param.min_b); - } - - EXPECT_GT (tri.num_triangles (), size_t (900)); - EXPECT_LT (tri.num_triangles (), size_t (1000)); -} - -void read_polygons (const std::string &path, db::Region ®ion, double dbu) -{ - tl::InputStream is (path); - tl::TextInputStream ti (is); - - unsigned int nvert = 0, nedges = 0; - - { - tl::Extractor ex (ti.get_line ().c_str ()); - ex.read (nvert); - ex.read (nedges); - } - - std::vector v; - auto dbu_trans = db::CplxTrans (dbu).inverted (); - for (unsigned int i = 0; i < nvert; ++i) { - double x = 0, y = 0; - tl::Extractor ex (ti.get_line ().c_str ()); - ex.read (x); - ex.read (y); - v.push_back (dbu_trans * db::DPoint (x, y)); - } - - unsigned int nstart = 0; - bool new_contour = true; - std::vector contour; - - for (unsigned int i = 0; i < nedges; ++i) { - - unsigned int n1 = 0, n2 = 0; - - tl::Extractor ex (ti.get_line ().c_str ()); - ex.read (n1); - ex.read (n2); - - if (new_contour) { - nstart = n1; - new_contour = false; - } - - contour.push_back (v[n1]); - - if (n2 == nstart) { - // finish contour - db::SimplePolygon sp; - sp.assign_hull (contour.begin (), contour.end ()); - region.insert (sp); - new_contour = true; - contour.clear (); - } else if (n2 <= n1) { - tl::error << "Invalid polygon wrap in line " << ti.line_number (); - tl_assert (false); - } - - } -} - -TEST(triangulate_geo) -{ - double dbu = 0.001; - - db::Region r; - read_polygons (tl::combine_path (tl::testsrc (), "testdata/algo/triangles1.txt"), r, dbu); - - // for debugging purposes dump the inputs - if (false) { - - db::Layout layout = db::Layout (); - layout.dbu (dbu); - db::Cell &top = layout.cell (layout.add_cell ("DUMP")); - unsigned int l1 = layout.insert_layer (db::LayerProperties (1, 0)); - r.insert_into (&layout, top.cell_index (), l1); - - { - tl::OutputStream stream ("input.gds"); - db::SaveLayoutOptions opt; - db::Writer writer (opt); - writer.write (layout, stream); - } - - } - - db::Triangles::TriangulateParameters param; - param.min_b = 1.0; - param.max_area = 0.1; - param.min_length = 0.001; - - TestableTriangles tri; - tri.triangulate (r, param, dbu); - - EXPECT_EQ (tri.check (false), true); - - // for debugging: - // tri.dump ("debug.gds"); - - size_t n_skinny = 0; - for (auto t = tri.begin (); t != tri.end (); ++t) { - EXPECT_LE (t->area (), param.max_area); - if (t->b () < param.min_b) { - ++n_skinny; - } - } - - EXPECT_LT (n_skinny, size_t (20)); - EXPECT_GT (tri.num_triangles (), size_t (29000)); - EXPECT_LT (tri.num_triangles (), size_t (30000)); -} - -TEST(triangulate_analytic) -{ - double dbu = 0.0001; - - double star1 = 9.0, star2 = 5.0; - double r = 1.0; - int n = 100; - - auto dbu_trans = db::CplxTrans (dbu).inverted (); - - std::vector contour1, contour2; - for (int i = 0; i < n; ++i) { - double a = -M_PI * 2.0 * double (i) / double (n); // "-" for clockwise orientation - double rr, x, y; - rr = r * (1.0 + 0.4 * cos (star1 * a)); - x = rr * cos (a); - y = rr * sin (a); - contour1.push_back (dbu_trans * db::DPoint (x, y)); - rr = r * (0.1 + 0.03 * cos (star2 * a)); - x = rr * cos (a); - y = rr * sin (a); - contour2.push_back (dbu_trans * db::DPoint (x, y)); - } - - db::Region rg; - - db::SimplePolygon sp1; - sp1.assign_hull (contour1.begin (), contour1.end ()); - db::SimplePolygon sp2; - sp2.assign_hull (contour2.begin (), contour2.end ()); - - rg = db::Region (sp1) - db::Region (sp2); - - db::Triangles::TriangulateParameters param; - param.min_b = 1.0; - param.max_area = 0.01; - - TestableTriangles tri; - tri.triangulate (rg, param, dbu); - - EXPECT_EQ (tri.check (false), true); - - // for debugging: - // tri.dump ("debug.gds"); - - for (auto t = tri.begin (); t != tri.end (); ++t) { - EXPECT_LE (t->area (), param.max_area); - EXPECT_GE (t->b (), param.min_b); - } - - EXPECT_GT (tri.num_triangles (), size_t (1250)); - EXPECT_LT (tri.num_triangles (), size_t (1300)); -} - -TEST(triangulate_problematic) -{ - db::DPoint contour[] = { - db::DPoint (129145.00000, -30060.80000), - db::DPoint (129145.00000, -28769.50000), - db::DPoint (129159.50000, -28754.90000), // this is a very short edge <-- from here. - db::DPoint (129159.60000, -28754.80000), // <-- to here. - db::DPoint (129159.50000, -28754.70000), - db::DPoint (129366.32200, -28547.90000), - db::DPoint (130958.54600, -26955.84600), - db::DPoint (131046.25000, -27043.55000), - db::DPoint (130152.15000, -27937.65000), - db::DPoint (130152.15000, -30060.80000) - }; - - db::DPolygon poly; - poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); - - db::Triangles::TriangulateParameters param; - param.min_b = 1.0; - param.max_area = 100000.0; - param.min_length = 0.002; - - TestableTriangles tri; - tri.triangulate (poly, param); - - EXPECT_EQ (tri.check (false), true); - - // for debugging: - // tri.dump ("debug.gds"); - - for (auto t = tri.begin (); t != tri.end (); ++t) { - EXPECT_LE (t->area (), param.max_area); - EXPECT_GE (t->b (), param.min_b); - } - - EXPECT_GT (tri.num_triangles (), size_t (540)); - EXPECT_LT (tri.num_triangles (), size_t (560)); -} - -TEST(triangulate_thin) -{ - db::DPoint contour[] = { - db::DPoint (18790, 58090), - db::DPoint (18790, 58940), - db::DPoint (29290, 58940), - db::DPoint (29290, 58090) - }; - - db::DPoint hole[] = { - db::DPoint (18791, 58091), - db::DPoint (29289, 58091), - db::DPoint (29289, 58939), - db::DPoint (18791, 58939) - }; - - db::DPolygon poly; - poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); - poly.insert_hole (hole + 0, hole + sizeof (hole) / sizeof (hole[0])); - - double dbu = 0.001; - - db::Triangles::TriangulateParameters param; - param.min_b = 0.5; - param.max_area = 0.0; - param.min_length = 2 * dbu; - - TestableTriangles tri; - db::DCplxTrans trans = db::DCplxTrans (dbu) * db::DCplxTrans (db::DTrans (db::DPoint () - poly.box ().center ())); - tri.triangulate (trans * poly, param); - - EXPECT_EQ (tri.check (false), true); - - // for debugging: - // tri.dump ("debug.gds"); - - for (auto t = tri.begin (); t != tri.end (); ++t) { - EXPECT_GE (t->b (), param.min_b); - } - - EXPECT_GT (tri.num_triangles (), size_t (13000)); - EXPECT_LT (tri.num_triangles (), size_t (13200)); -} - -TEST(triangulate_issue1996) -{ - db::DPoint contour[] = { - db::DPoint (-8000, -8075), - db::DPoint (-8000, 8075), - db::DPoint (18000, 8075), - db::DPoint (18000, -8075) - }; - - db::DPolygon poly; - poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); - - double dbu = 0.001; - - db::Triangles::TriangulateParameters param; - param.min_b = 0.5; - param.max_area = 5000.0 * dbu * dbu; - - TestableTriangles tri; - db::DCplxTrans trans = db::DCplxTrans (dbu) * db::DCplxTrans (db::DTrans (db::DPoint () - poly.box ().center ())); - tri.triangulate (trans * poly, param); - - EXPECT_EQ (tri.check (false), true); - - // for debugging: - // tri.dump ("debug.gds"); - - for (auto t = tri.begin (); t != tri.end (); ++t) { - EXPECT_LE (t->area (), param.max_area); - EXPECT_GE (t->b (), param.min_b); - } - - EXPECT_GT (tri.num_triangles (), size_t (128000)); - EXPECT_LT (tri.num_triangles (), size_t (132000)); -} - -TEST(triangulate_with_vertexes) -{ - db::Point contour[] = { - db::Point (0, 0), - db::Point (0, 100), - db::Point (1000, 100), - db::Point (1000, 0) - }; - - db::Polygon poly; - poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); - - double dbu = 0.001; - - db::Triangles::TriangulateParameters param; - param.min_b = 0.0; - param.max_area = 0.0; - - std::vector vertexes; - - TestableTriangles tri; - db::CplxTrans trans = db::DCplxTrans (dbu) * db::CplxTrans (db::Trans (db::Point () - poly.box ().center ())); - tri.triangulate (poly, param, trans); - - EXPECT_EQ (tri.to_string (), "((-0.5, -0.05), (-0.5, 0.05), (0.5, 0.05)), ((0.5, -0.05), (-0.5, -0.05), (0.5, 0.05))"); - - vertexes.clear (); - - // outside vertexes are ignored, but lead to a different triangulation - vertexes.push_back (db::Point (50, 150)); - tri.triangulate (poly, vertexes, param, trans); - - EXPECT_EQ (tri.to_string (), "((-0.5, -0.05), (-0.133333333333, 0.05), (0.5, -0.05)), ((0.5, 0.05), (0.5, -0.05), (-0.133333333333, 0.05)), ((-0.133333333333, 0.05), (-0.5, -0.05), (-0.5, 0.05))"); - - for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { - auto *vp = tri.find_vertex_for_point (trans * *v); - EXPECT_EQ (vp, 0); - } - - vertexes.clear (); - vertexes.push_back (db::Point (50, 50)); - tri.triangulate (poly, vertexes, param, trans); - - EXPECT_EQ (tri.to_string (), "((-0.45, 0), (-0.5, -0.05), (-0.5, 0.05)), ((0.5, 0.05), (-0.45, 0), (-0.5, 0.05)), ((-0.45, 0), (0.5, -0.05), (-0.5, -0.05)), ((-0.45, 0), (0.5, 0.05), (0.5, -0.05))"); - - for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { - auto *vp = tri.find_vertex_for_point (trans * *v); - if (! vp) { - tl::warn << "Vertex not present in output: " << v->to_string (); - EXPECT_EQ (1, 0); - } - } - - // aggressive triangulation - param.min_b = 1.0; - param.max_area = 20 * 20 * dbu * dbu; - - tri.triangulate (poly, vertexes, param, trans); - - EXPECT_GT (tri.num_triangles (), size_t (380)); - EXPECT_LT (tri.num_triangles (), size_t (400)); - - for (auto t = tri.begin (); t != tri.end (); ++t) { - EXPECT_LE (t->area (), param.max_area); - EXPECT_GE (t->b (), param.min_b); - } - - for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { - auto *vp = tri.find_vertex_for_point (trans * *v); - if (! vp) { - tl::warn << "Vertex not present in output: " << v->to_string (); - EXPECT_EQ (1, 0); - } - } -} - -// @@@@@@@@@@@ - -struct SortAngleAndEdgesByEdgeLength -{ - typedef std::list > angle_and_edges_list; - - bool operator() (const angle_and_edges_list::iterator &a, const angle_and_edges_list::iterator &b) const - { - double la = a->second->edge ().double_sq_length (); - double lb = b->second->edge ().double_sq_length (); - if (fabs (la - lb) > db::epsilon) { - return la < lb; - } else { - return a->second->edge ().less (b->second->edge ()); - } - } -}; - -// TODO: move to some generic header -template -struct less_compare_func -{ - bool operator() (const T &a, const T &b) const - { - return a.less (b); - } -}; - -// TODO: move to some generic header -template -struct equal_compare_func -{ - bool operator() (const T &a, const T &b) const - { - return a.equal (b); - } -}; - -struct ConcaveCorner -{ - ConcaveCorner () - : corner (0), incoming (0), outgoing (0) - { - // .. nothing yet .. - } - - ConcaveCorner (db::Vertex *_corner, db::TriangleEdge *_incoming, db::TriangleEdge *_outgoing) - : corner (_corner), incoming (_incoming), outgoing (_outgoing) - { - // .. nothing yet .. - } - - db::Vertex *corner; - db::TriangleEdge *incoming, *outgoing; -}; - -db::TriangleEdge *find_outgoing_segment (db::Vertex *vertex, db::TriangleEdge *incoming, int &vp_max_sign) -{ - db::Vertex *vfrom = incoming->other (vertex); - db::DEdge e1 (*vfrom, *vertex); - - double vp_max = 0.0; - vp_max_sign = 0; - db::TriangleEdge *outgoing = 0; - - // Look for the outgoing edge. We pick the one which bends "most", favoring - // convex corners. Multiple edges per vertex are possible is corner cases such as the - // "hourglass" configuration. - - for (auto e = vertex->begin_edges (); e != vertex->end_edges (); ++e) { - - db::TriangleEdge *en = *e; - if (en != incoming && en->is_segment ()) { - - db::Vertex *v = en->other (vertex); - db::DEdge e2 (*vertex, *v); - double vp = double (db::vprod (e1, e2)) / (e1.double_length () * e2.double_length ()); - - // vp > 0: concave, vp < 0: convex - - if (! outgoing || vp > vp_max) { - vp_max_sign = db::vprod_sign (e1, e2); - vp_max = vp; - outgoing = en; - } - - } - - } - - tl_assert (outgoing != 0); - return outgoing; -} - -void collect_concave_vertexes (db::Triangles &tris, std::vector &concave_vertexes) -{ - concave_vertexes.clear (); - - // @@@ use edge "level" - // @@@ use edges from heap - std::unordered_set left; - - for (auto it = tris.begin (); it != tris.end (); ++it) { - for (unsigned int i = 0; i < 3; ++i) { - db::TriangleEdge *e = it->edge (i); - if (e->is_segment ()) { - left.insert (e); - } - } - } - - while (! left.empty ()) { - - // First segment for a new loop - db::TriangleEdge *segment = *left.begin (); - - // walk along the segments in clockwise direction. Find concave - // vertexes and create new vertexes perpendicular to the incoming - // and outgoing edge. - - db::TriangleEdge *start_segment = segment; - db::Vertex *vto = segment->right () ? segment->v2 () : segment->v1 (); - - do { - - left.erase (segment); - - db::TriangleEdge *prev_segment = segment; - - int vp_sign = 0; - segment = find_outgoing_segment (vto, prev_segment, vp_sign); - - if (vp_sign > 0) { - concave_vertexes.push_back (ConcaveCorner (vto, prev_segment, segment)); - } - - vto = segment->other (vto); - - } while (segment != start_segment); - - } -} - -std::pair -search_crossing_with_next_segment (const db::Vertex *v0, const db::DVector &direction) -{ - auto vtri = v0->triangles (); // TODO: slow? - std::vector nvv, nvv_next; - - for (auto it = vtri.begin (); it != vtri.end (); ++it) { - - // Search for a segment in the direction perpendicular to the edge - nvv.clear (); - nvv.push_back (v0); - const db::Triangle *t = *it; - - while (! nvv.empty ()) { - - nvv_next.clear (); - - for (auto iv = nvv.begin (); iv != nvv.end (); ++iv) { - - const db::Vertex *v = *iv; - const db::TriangleEdge *oe = t->opposite (v); - const db::Triangle *tt = oe->other (t); - const db::Vertex *v1 = oe->v1 (); - const db::Vertex *v2 = oe->v2 (); - - if (db::sprod_sign (*v2 - *v, direction) >= 0 && db::sprod_sign (*v1 - *v, direction) >= 0 && - db::vprod_sign (*v2 - *v, direction) * db::vprod_sign (*v1 - *v, direction) < 0) { - - // this triangle covers the normal vector of e1 -> stop here or continue searching in that direction - if (oe->is_segment ()) { - auto cp = oe->edge ().cut_point (db::DEdge (*v0, *v0 + direction)); - if (cp.first) { - return std::make_pair (true, cp.second); - } - } else { - // continue searching in that direction - nvv_next.push_back (v1); - nvv_next.push_back (v2); - t = tt; - } - - break; - - } - - } - - nvv.swap (nvv_next); - - } - - } - - return std::make_pair (false, db::DPoint ()); -} - -void -hertel_mehlhorn_decomposition (db::Triangles &tri, bool diagonals_only, bool no_collinear_edges, std::list &polygons) -{ - std::vector concave_vertexes; - collect_concave_vertexes (tri, concave_vertexes); - - // @@@ return if no concave corners - - // @@@ sort concave vertexes - - std::vector new_points; - - if (! diagonals_only) { - - // Create internal segments cutting off pieces orthogonal to the edges - // connecting the concave vertex. - - for (auto cc = concave_vertexes.begin (); cc != concave_vertexes.end (); ++cc) { - - for (unsigned int ei = 0; ei < 2; ++ei) { - - db::DEdge ee; - const db::Vertex *v0 = cc->corner; - if (ei == 0) { - ee = db::DEdge (*cc->incoming->other (v0), *v0); - } else { - ee = db::DEdge (*v0, *cc->outgoing->other (v0)); - } - - auto cp = search_crossing_with_next_segment (v0, db::DVector (ee.dy (), -ee.dx ())); - if (cp.first) { - new_points.push_back (cp.second); - } - - } - - } - - } - - // eliminate duplicates and put the new points in some order - - if (! new_points.empty ()) { - - std::sort (new_points.begin (), new_points.end (), less_compare_func ()); - new_points.erase (std::unique (new_points.begin (), new_points.end (), equal_compare_func ()), new_points.end ()); - - // Insert the new points and make connections - for (auto p = new_points.begin (); p != new_points.end (); ++p) { - tri.insert_point (*p); - } - - // As the insertion invalidates the edges, we need to collect the concave vertexes again - collect_concave_vertexes (tri, concave_vertexes); - - } - - // Collect essential edges - // Every concave vertex can have up to two essential edges. - // Other then suggested by Hertel-Mehlhorn we don't pick - // them one-by-one, but using them in length order, from the - - std::unordered_set essential_edges; - - typedef std::list > angles_and_edges_list; - angles_and_edges_list angles_and_edges; - std::vector sorted_edges; - - for (auto cc = concave_vertexes.begin (); cc != concave_vertexes.end (); ++cc) { - - angles_and_edges.clear (); - const db::Vertex *v0 = cc->corner; - - const db::TriangleEdge *e = cc->incoming; - while (e) { - - const db::Triangle *t = e->v2 () == v0 ? e->right () : e->left (); - tl_assert (t != 0); - - // @@@ make a method of triangle - const db::TriangleEdge *en = 0; - for (unsigned int i = 0; i < 3; ++i) { - const db::TriangleEdge *ee = t->edge (i); - if (ee != e && (ee->v1 () == v0 || ee->v2 () == v0)) { - en = ee; - break; - } - } - - db::DVector v1 = e->edge ().d () * (e->v1 () == v0 ? 1.0 : -1.0); - db::DVector v2 = en->edge ().d () * (en->v1 () == v0 ? 1.0 : -1.0); - - double angle = atan2 (db::vprod (v1, v2), db::sprod (v1, v2)); - - e = (en == cc->outgoing) ? 0 : en; - angles_and_edges.push_back (std::make_pair (angle, e)); - - } - - sorted_edges.clear (); - - for (auto i = angles_and_edges.begin (); i != angles_and_edges.end (); ++i) { - if (i->second) { - sorted_edges.push_back (i); - } - } - - std::sort (sorted_edges.begin (), sorted_edges.end (), SortAngleAndEdgesByEdgeLength ()); - - for (auto i = sorted_edges.end (); i != sorted_edges.begin (); ) { - --i; - angles_and_edges_list::iterator ii = *i; - angles_and_edges_list::iterator iin = ii; - ++iin; - if (ii->first + iin->first < (no_collinear_edges ? M_PI - db::epsilon : M_PI + db::epsilon)) { - // not an essential edge -> remove - iin->first += ii->first; - angles_and_edges.erase (ii); - } - } - - for (auto i = angles_and_edges.begin (); i != angles_and_edges.end (); ++i) { - essential_edges.insert (i->second); - } - - } - - // Combine triangles, but don't cross essential edges - - std::unordered_set left_triangles; - for (auto it = tri.begin (); it != tri.end (); ++it) { - left_triangles.insert (it.operator-> ()); - } - - while (! left_triangles.empty ()) { - - std::unordered_map edges; - - const db::Triangle *tri = *left_triangles.begin (); - std::vector queue, next_queue; - queue.push_back (tri); - - while (! queue.empty ()) { - - next_queue.clear (); - - for (auto q = queue.begin (); q != queue.end (); ++q) { - - left_triangles.erase (*q); - - for (unsigned int i = 0; i < 3; ++i) { - - const db::TriangleEdge *e = (*q)->edge (i); - const db::Triangle *qq = e->other (*q); - - if (! qq || essential_edges.find (e) != essential_edges.end ()) { - if (e->right () == *q) { - edges.insert (std::make_pair (e->v1 (), e->v2 ())); - } else { - edges.insert (std::make_pair (e->v2 (), e->v1 ())); - } - } else if (left_triangles.find (qq) != left_triangles.end ()) { - next_queue.push_back (qq); - } - } - - } - - queue.swap (next_queue); - - } - - // stitch the loop points into a polygon - - tl_assert (! edges.empty ()); - - const db::Vertex *v = edges.begin ()->first; - const db::Vertex *v0 = v; - const db::Vertex *vv = edges.begin ()->second; - - std::vector polygon_points; - - do { - - polygon_points.push_back (*v); - - auto i = edges.find (vv); - tl_assert (i != edges.end ()); - - v = i->first; - vv = i->second; - - } while (v != v0); - - polygons.push_back (db::DPolygon ()); - polygons.back ().assign_hull (polygon_points.begin (), polygon_points.end ()); - - } -} - - -// Hertel-Mehlhorn :) -TEST(JoinTriangles) -{ -#if 0 - db::Point contour[] = { - db::Point (0, 0), - db::Point (0, 100), - db::Point (1000, 100), - db::Point (1000, 500), - db::Point (1100, 500), - db::Point (1100, 100), - db::Point (2100, 100), - db::Point (2100, 0) - }; -#else - - db::Point contour[] = { - db::Point (0, 0), - db::Point (0, 100), - db::Point (1000, 100), - db::Point (1000, 500), - db::Point (1100, 500), - db::Point (1100, 100), - db::Point (2100, 100), - db::Point (2100, -1000), - db::Point (150, -1000), - db::Point (150, 0) - }; -#endif - - db::Polygon poly; - poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); - - // @@@ don't to anything if already convex - - double dbu = 0.001; - - db::Triangles::TriangulateParameters param; - param.min_b = 0.0; - - TestableTriangles tri; - db::CplxTrans trans = db::CplxTrans (dbu); - tri.triangulate (poly, param, trans); - - std::list polygons; - hertel_mehlhorn_decomposition (tri, false, true, polygons); - - db::Region result; - for (auto p = polygons.begin (); p != polygons.end (); ++p) { - result.insert (trans.inverted () * *p); - } - - // @@@ - tri.dump ("debugt.gds"); - result.write ("debug.gds"); - -} - -// @@@@@@@@@@q diff --git a/src/db/unit_tests/unit_tests.pro b/src/db/unit_tests/unit_tests.pro index f16d29b44..b5aadac53 100644 --- a/src/db/unit_tests/unit_tests.pro +++ b/src/db/unit_tests/unit_tests.pro @@ -20,8 +20,6 @@ SOURCES = \ dbQuadTreeTests.cc \ dbRecursiveInstanceIteratorTests.cc \ dbRegionCheckUtilsTests.cc \ - dbTriangleTests.cc \ - dbTrianglesTests.cc \ dbUtilsTests.cc \ dbWriterTools.cc \ dbLoadLayoutOptionsTests.cc \ From 60d1fb0685cce65d1f9c8d42eefc46aca7d7d443 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 16 Apr 2025 00:02:25 +0200 Subject: [PATCH 09/58] Porting HM decomposition to new PLC framework --- src/db/db/db.pro | 2 + src/db/db/dbPLC.cc | 103 +--- src/db/db/dbPLC.h | 1 + src/db/db/dbPLCConvexDecomposition.cc | 530 ++++++++++++++++++ src/db/db/dbPLCConvexDecomposition.h | 132 +++++ src/db/db/dbPLCTriangulation.cc | 3 - .../dbPLCConvexDecompositionTests.cc | 112 ++++ src/db/unit_tests/unit_tests.pro | 1 + testdata/algo/hm_decomposition_au1.gds | Bin 0 -> 1636 bytes testdata/algo/hm_decomposition_au2.gds | Bin 0 -> 1922 bytes testdata/algo/hm_decomposition_au3.gds | Bin 0 -> 1460 bytes testdata/algo/hm_decomposition_au4.gds | Bin 0 -> 1786 bytes 12 files changed, 780 insertions(+), 104 deletions(-) create mode 100644 src/db/db/dbPLCConvexDecomposition.cc create mode 100644 src/db/db/dbPLCConvexDecomposition.h create mode 100644 src/db/unit_tests/dbPLCConvexDecompositionTests.cc create mode 100644 testdata/algo/hm_decomposition_au1.gds create mode 100644 testdata/algo/hm_decomposition_au2.gds create mode 100644 testdata/algo/hm_decomposition_au3.gds create mode 100644 testdata/algo/hm_decomposition_au4.gds diff --git a/src/db/db/db.pro b/src/db/db/db.pro index 93795a97d..295976015 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -71,6 +71,7 @@ SOURCES = \ dbObject.cc \ dbObjectWithProperties.cc \ dbPLC.cc \ + dbPLCConvexDecomposition.cc \ dbPLCTriangulation.cc \ dbPath.cc \ dbPCellDeclaration.cc \ @@ -309,6 +310,7 @@ HEADERS = \ dbObjectTag.h \ dbObjectWithProperties.h \ dbPLC.h \ + dbPLCConvexDecomposition.h \ dbPLCTriangulation.h \ dbPath.h \ dbPCellDeclaration.h \ diff --git a/src/db/db/dbPLC.cc b/src/db/db/dbPLC.cc index 559bf4572..f9572550e 100644 --- a/src/db/db/dbPLC.cc +++ b/src/db/db/dbPLC.cc @@ -804,105 +804,6 @@ Graph::bbox () const return box; } -#if 0 // @@@ -bool -Graph::check (bool check_delaunay) const -{ - bool res = true; - - if (check_delaunay) { - for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) { - auto cp = t->circumcircle (); - auto vi = find_inside_circle (cp.first, cp.second); - if (! vi.empty ()) { - res = false; - tl::error << "(check error) polygon does not meet Delaunay criterion: " << t->to_string (); - for (auto v = vi.begin (); v != vi.end (); ++v) { - tl::error << " vertex inside circumcircle: " << (*v)->to_string (true); - } - } - } - } - - for (auto t = mp_polygons.begin (); t != mp_polygons.end (); ++t) { - for (int i = 0; i < 3; ++i) { - if (! t->edge (i)->has_polygon (t.operator-> ())) { - tl::error << "(check error) edges " << t->edge (i)->to_string (true) - << " attached to polygon " << t->to_string (true) << " does not refer to this polygon"; - res = false; - } - } - } - - for (auto e = m_edges_heap.begin (); e != m_edges_heap.end (); ++e) { - - if (!e->left () && !e->right ()) { - continue; - } - - if (e->left () && e->right ()) { - if (e->left ()->is_outside () != e->right ()->is_outside () && ! e->is_segment ()) { - tl::error << "(check error) edge " << e->to_string (true) << " splits an outside and inside polygon, but is not a segment"; - res = false; - } - } - - for (auto t = e->begin_polygons (); t != e->end_polygons (); ++t) { - if (! t->has_edge (e.operator-> ())) { - tl::error << "(check error) edge " << e->to_string (true) << " not found in adjacent polygon " << t->to_string (true); - res = false; - } - if (! t->has_vertex (e->v1 ())) { - tl::error << "(check error) edges " << e->to_string (true) << " vertex 1 not found in adjacent polygon " << t->to_string (true); - res = false; - } - if (! t->has_vertex (e->v2 ())) { - tl::error << "(check error) edges " << e->to_string (true) << " vertex 2 not found in adjacent polygon " << t->to_string (true); - res = false; - } - Vertex *vopp = t->opposite (e.operator-> ()); - double sgn = (e->left () == t.operator-> ()) ? 1.0 : -1.0; - double vp = db::vprod (e->d(), *vopp - *e->v1 ()); // positive if on left side - if (vp * sgn <= 0.0) { - const char * side_str = sgn > 0.0 ? "left" : "right"; - tl::error << "(check error) external point " << vopp->to_string (true) << " not on " << side_str << " side of edge " << e->to_string (true); - res = false; - } - } - - if (! e->v1 ()->has_edge (e.operator-> ())) { - tl::error << "(check error) edge " << e->to_string (true) << " vertex 1 does not list this edge"; - res = false; - } - if (! e->v2 ()->has_edge (e.operator-> ())) { - tl::error << "(check error) edge " << e->to_string (true) << " vertex 2 does not list this edge"; - res = false; - } - - } - - for (auto v = m_vertex_heap.begin (); v != m_vertex_heap.end (); ++v) { - unsigned int num_outside_edges = 0; - for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { - if ((*e)->is_outside ()) { - ++num_outside_edges; - } - } - if (num_outside_edges > 0 && num_outside_edges != 2) { - tl::error << "(check error) vertex " << v->to_string (true) << " has " << num_outside_edges << " outside edges (can only be 2)"; - res = false; - for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { - if ((*e)->is_outside ()) { - tl::error << " Outside edge is " << (*e)->to_string (true); - } - } - } - } - - return res; -} -#endif - db::Layout * Graph::to_layout (bool decompose_by_id) const { @@ -913,7 +814,7 @@ Graph::to_layout (bool decompose_by_id) const db::Cell &top = layout->cell (layout->add_cell ("DUMP")); unsigned int l1 = layout->insert_layer (db::LayerProperties (1, 0)); - // @@@ unsigned int l2 = layout->insert_layer (db::LayerProperties (2, 0)); + unsigned int l2 = layout->insert_layer (db::LayerProperties (2, 0)); unsigned int l10 = layout->insert_layer (db::LayerProperties (10, 0)); unsigned int l20 = layout->insert_layer (db::LayerProperties (20, 0)); unsigned int l21 = layout->insert_layer (db::LayerProperties (21, 0)); @@ -927,7 +828,7 @@ Graph::to_layout (bool decompose_by_id) const } db::DPolygon poly; poly.assign_hull (pts.begin (), pts.end ()); - top.shapes (/*@@@t->is_outside () ? l2 :*/ l1).insert (dbu_trans * poly); + top.shapes (t->is_outside () ? l2 : l1).insert (dbu_trans * poly); if (decompose_by_id) { if ((t->id () & 1) != 0) { top.shapes (l20).insert (dbu_trans * poly); diff --git a/src/db/db/dbPLC.h b/src/db/db/dbPLC.h index 14ac8d01c..d5d6f0a26 100644 --- a/src/db/db/dbPLC.h +++ b/src/db/db/dbPLC.h @@ -504,6 +504,7 @@ private: friend class Polygon; friend class Graph; friend class Triangulation; + friend class ConvexDecomposition; Graph *mp_graph; Vertex *mp_v1, *mp_v2; diff --git a/src/db/db/dbPLCConvexDecomposition.cc b/src/db/db/dbPLCConvexDecomposition.cc new file mode 100644 index 000000000..1905b27db --- /dev/null +++ b/src/db/db/dbPLCConvexDecomposition.cc @@ -0,0 +1,530 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "dbPLCConvexDecomposition.h" +#include "dbPLCTriangulation.h" +#include "tlLog.h" +#include "tlTimer.h" + +#include +#include +#include +#include + +namespace db +{ + +namespace plc +{ + +ConvexDecomposition::ConvexDecomposition (Graph *graph) +{ + mp_graph = graph; + clear (); +} + +void +ConvexDecomposition::clear () +{ + mp_graph->clear (); +} + +struct SortAngleAndEdgesByEdgeLength +{ + typedef std::list > angle_and_edges_list; + + bool operator() (const angle_and_edges_list::iterator &a, const angle_and_edges_list::iterator &b) const + { + double la = a->second->edge ().double_sq_length (); + double lb = b->second->edge ().double_sq_length (); + if (fabs (la - lb) > db::epsilon) { + return la < lb; + } else { + return a->second->edge ().less (b->second->edge ()); + } + } +}; + +// TODO: move to some generic header +template +struct less_compare_func +{ + bool operator() (const T &a, const T &b) const + { + return a.less (b); + } +}; + +// TODO: move to some generic header +template +struct equal_compare_func +{ + bool operator() (const T &a, const T &b) const + { + return a.equal (b); + } +}; + +struct ConcaveCorner +{ + ConcaveCorner () + : corner (0), incoming (0), outgoing (0) + { + // .. nothing yet .. + } + + ConcaveCorner (Vertex *_corner, Edge *_incoming, Edge *_outgoing) + : corner (_corner), incoming (_incoming), outgoing (_outgoing) + { + // .. nothing yet .. + } + + Vertex *corner; + Edge *incoming, *outgoing; +}; + +Edge *find_outgoing_segment (Vertex *vertex, Edge *incoming, int &vp_max_sign) +{ + Vertex *vfrom = incoming->other (vertex); + db::DEdge e1 (*vfrom, *vertex); + + double vp_max = 0.0; + vp_max_sign = 0; + Edge *outgoing = 0; + + // Look for the outgoing edge. We pick the one which bends "most", favoring + // convex corners. Multiple edges per vertex are possible is corner cases such as the + // "hourglass" configuration. + + for (auto e = vertex->begin_edges (); e != vertex->end_edges (); ++e) { + + Edge *en = *e; + if (en != incoming && en->is_segment ()) { + + Vertex *v = en->other (vertex); + db::DEdge e2 (*vertex, *v); + double vp = double (db::vprod (e1, e2)) / (e1.double_length () * e2.double_length ()); + + // vp > 0: concave, vp < 0: convex + + if (! outgoing || vp > vp_max) { + vp_max_sign = db::vprod_sign (e1, e2); + vp_max = vp; + outgoing = en; + } + + } + + } + + tl_assert (outgoing != 0); + return outgoing; +} + +void collect_concave_vertexes (Graph &tris, std::vector &concave_vertexes) +{ + concave_vertexes.clear (); + + // @@@ use edge "level" + // @@@ use edges from heap + std::unordered_set left; + + for (auto it = tris.begin (); it != tris.end (); ++it) { + for (unsigned int i = 0; i < 3; ++i) { + Edge *e = it->edge (i); + if (e->is_segment ()) { + left.insert (e); + } + } + } + + while (! left.empty ()) { + + // First segment for a new loop + Edge *segment = *left.begin (); + + // walk along the segments in clockwise direction. Find concave + // vertexes and create new vertexes perpendicular to the incoming + // and outgoing edge. + + Edge *start_segment = segment; + Vertex *vto = segment->right () ? segment->v2 () : segment->v1 (); + + do { + + left.erase (segment); + + Edge *prev_segment = segment; + + int vp_sign = 0; + segment = find_outgoing_segment (vto, prev_segment, vp_sign); + + if (vp_sign > 0) { + concave_vertexes.push_back (ConcaveCorner (vto, prev_segment, segment)); + } + + vto = segment->other (vto); + + } while (segment != start_segment); + + } +} + +std::pair +search_crossing_with_next_segment (const Vertex *v0, const db::DVector &direction) +{ + auto vtri = v0->polygons (); // TODO: slow? + std::vector nvv, nvv_next; + + for (auto it = vtri.begin (); it != vtri.end (); ++it) { + + // Search for a segment in the direction perpendicular to the edge + nvv.clear (); + nvv.push_back (v0); + const Polygon *t = *it; + + while (! nvv.empty ()) { + + nvv_next.clear (); + + for (auto iv = nvv.begin (); iv != nvv.end (); ++iv) { + + const Vertex *v = *iv; + const Edge *oe = t->opposite (v); + const Polygon *tt = oe->other (t); + const Vertex *v1 = oe->v1 (); + const Vertex *v2 = oe->v2 (); + + if (db::sprod_sign (*v2 - *v, direction) >= 0 && db::sprod_sign (*v1 - *v, direction) >= 0 && + db::vprod_sign (*v2 - *v, direction) * db::vprod_sign (*v1 - *v, direction) < 0) { + + // this triangle covers the normal vector of e1 -> stop here or continue searching in that direction + if (oe->is_segment ()) { + auto cp = oe->edge ().cut_point (db::DEdge (*v0, *v0 + direction)); + if (cp.first) { + return std::make_pair (true, cp.second); + } + } else { + // continue searching in that direction + nvv_next.push_back (v1); + nvv_next.push_back (v2); + t = tt; + } + + break; + + } + + } + + nvv.swap (nvv_next); + + } + + } + + return std::make_pair (false, db::DPoint ()); +} + +void +ConvexDecomposition::hertel_mehlhorn_decomposition (Triangulation &tris, const ConvexDecompositionParameters ¶m) +{ + bool with_segments = param.with_segments; + bool split_edges = param.split_edges; + + std::vector concave_vertexes; + collect_concave_vertexes (*mp_graph, concave_vertexes); + + // @@@ return if no concave corners + + // @@@ sort concave vertexes + + std::vector new_points; + + if (with_segments) { + + // Create internal segments cutting off pieces orthogonal to the edges + // connecting the concave vertex. + + for (auto cc = concave_vertexes.begin (); cc != concave_vertexes.end (); ++cc) { + + for (unsigned int ei = 0; ei < 2; ++ei) { + + db::DEdge ee; + const Vertex *v0 = cc->corner; + if (ei == 0) { + ee = db::DEdge (*cc->incoming->other (v0), *v0); + } else { + ee = db::DEdge (*v0, *cc->outgoing->other (v0)); + } + + auto cp = search_crossing_with_next_segment (v0, db::DVector (ee.dy (), -ee.dx ())); + if (cp.first) { + new_points.push_back (cp.second); + } + + } + + } + + } + + // eliminate duplicates and put the new points in some order + + if (! new_points.empty ()) { + + std::sort (new_points.begin (), new_points.end (), less_compare_func ()); + new_points.erase (std::unique (new_points.begin (), new_points.end (), equal_compare_func ()), new_points.end ()); + + // Insert the new points and make connections + for (auto p = new_points.begin (); p != new_points.end (); ++p) { + tris.insert_point (*p); + } + + // As the insertion invalidates the edges, we need to collect the concave vertexes again + collect_concave_vertexes (*mp_graph, concave_vertexes); + + } + + // Collect essential edges + // Every concave vertex can have up to two essential edges. + // Other then suggested by Hertel-Mehlhorn we don't pick + // them one-by-one, but using them in length order, from the + + std::unordered_set essential_edges; + + typedef std::list > angles_and_edges_list; + angles_and_edges_list angles_and_edges; + std::vector sorted_edges; + + for (auto cc = concave_vertexes.begin (); cc != concave_vertexes.end (); ++cc) { + + angles_and_edges.clear (); + const Vertex *v0 = cc->corner; + + const Edge *e = cc->incoming; + while (e) { + + const Polygon *t = e->v2 () == v0 ? e->right () : e->left (); + tl_assert (t != 0); + + // @@@ make a method of Polygon -> next_edge(Edge *e, Vertex *at) + const Edge *en = 0; + for (unsigned int i = 0; i < 3; ++i) { + const Edge *ee = t->edge (i); + if (ee != e && (ee->v1 () == v0 || ee->v2 () == v0)) { + en = ee; + break; + } + } + + db::DVector v1 = e->edge ().d () * (e->v1 () == v0 ? 1.0 : -1.0); + db::DVector v2 = en->edge ().d () * (en->v1 () == v0 ? 1.0 : -1.0); + + double angle = atan2 (db::vprod (v1, v2), db::sprod (v1, v2)); + + e = (en == cc->outgoing) ? 0 : en; + angles_and_edges.push_back (std::make_pair (angle, e)); + + } + + sorted_edges.clear (); + + for (auto i = angles_and_edges.begin (); i != angles_and_edges.end (); ++i) { + if (i->second) { + sorted_edges.push_back (i); + } + } + + std::sort (sorted_edges.begin (), sorted_edges.end (), SortAngleAndEdgesByEdgeLength ()); + + for (auto i = sorted_edges.end (); i != sorted_edges.begin (); ) { + --i; + angles_and_edges_list::iterator ii = *i; + angles_and_edges_list::iterator iin = ii; + ++iin; + if (ii->first + iin->first < (split_edges ? M_PI + db::epsilon : M_PI - db::epsilon)) { + // not an essential edge -> remove + iin->first += ii->first; + angles_and_edges.erase (ii); + } + } + + for (auto i = angles_and_edges.begin (); i != angles_and_edges.end (); ++i) { + essential_edges.insert (i->second); + } + + } + + // Combine triangles, but don't cross essential edges + + std::unordered_set left_triangles; + for (auto it = mp_graph->begin (); it != mp_graph->end (); ++it) { + left_triangles.insert (it.operator-> ()); + } + + std::list > polygons; + + while (! left_triangles.empty ()) { + + polygons.push_back (std::vector ()); + std::vector &edges = polygons.back (); + + const Polygon *tri = *left_triangles.begin (); + std::vector queue, next_queue; + queue.push_back (tri); + + while (! queue.empty ()) { + + next_queue.clear (); + + for (auto q = queue.begin (); q != queue.end (); ++q) { + + left_triangles.erase (*q); + + for (unsigned int i = 0; i < 3; ++i) { + + const Edge *e = (*q)->edge (i); + const Polygon *qq = e->other (*q); + + if (! qq || essential_edges.find (e) != essential_edges.end ()) { + edges.push_back (const_cast (e)); // @@@ const_cast + } else if (left_triangles.find (qq) != left_triangles.end ()) { + next_queue.push_back (qq); + } + } + + } + + queue.swap (next_queue); + + } + + } + + // remove the triangles + + while (mp_graph->begin () != mp_graph->end ()) { + delete mp_graph->begin ().operator-> (); + } + + // create the polygons + + for (auto p = polygons.begin (); p != polygons.end (); ++p) { + mp_graph->create_polygon (p->begin (), p->end ()); + } +} + +void +ConvexDecomposition::decompose (const db::Polygon &poly, const ConvexDecompositionParameters ¶meters, double dbu) +{ + decompose (poly, parameters, db::CplxTrans (dbu)); +} + +void +ConvexDecomposition::decompose (const db::Polygon &poly, const std::vector &vertexes, const ConvexDecompositionParameters ¶meters, double dbu) +{ + decompose (poly, vertexes, parameters, db::CplxTrans (dbu)); +} + +void +ConvexDecomposition::decompose (const db::Polygon &poly, const ConvexDecompositionParameters ¶meters, const db::CplxTrans &trans) +{ + // start with a triangulation + TriangulationParameters param; + param.max_area = 0.0; + param.min_b = 0.0; + + Triangulation tri (mp_graph); + tri.triangulate (poly, param, trans); + + hertel_mehlhorn_decomposition (tri, parameters); +} + +void +ConvexDecomposition::decompose (const db::Polygon &poly, const std::vector &vertexes, const ConvexDecompositionParameters ¶meters, const db::CplxTrans &trans) +{ + // start with a triangulation + TriangulationParameters param; + param.max_area = 0.0; + param.min_b = 0.0; + + Triangulation tri (mp_graph); + tri.triangulate (poly, vertexes, param, trans); + + // @@@ consider vertexes + hertel_mehlhorn_decomposition (tri, parameters); +} + +void +ConvexDecomposition::decompose (const db::DPolygon &poly, const ConvexDecompositionParameters ¶meters, const db::DCplxTrans &trans) +{ + // start with a triangulation + TriangulationParameters param; + param.max_area = 0.0; + param.min_b = 0.0; + + Triangulation tri (mp_graph); + tri.triangulate (poly, param, trans); + + hertel_mehlhorn_decomposition (tri, parameters); +} + +void +ConvexDecomposition::decompose (const db::DPolygon &poly, const std::vector &vertexes, const ConvexDecompositionParameters ¶meters, const db::DCplxTrans &trans) +{ + // start with a triangulation + TriangulationParameters param; + param.max_area = 0.0; + param.min_b = 0.0; + + Triangulation tri (mp_graph); + tri.triangulate (poly, vertexes, param, trans); + + // @@@ consider vertexes + hertel_mehlhorn_decomposition (tri, parameters); +} + +void +ConvexDecomposition::decompose (const db::Region ®ion, const ConvexDecompositionParameters ¶meters, double dbu) +{ + decompose (region, parameters, db::CplxTrans (dbu)); +} + +void +ConvexDecomposition::decompose (const db::Region ®ion, const ConvexDecompositionParameters ¶meters, const db::CplxTrans &trans) +{ + // start with a triangulation + TriangulationParameters param; + param.max_area = 0.0; + param.min_b = 0.0; + + Triangulation tri (mp_graph); + tri.triangulate (region, param, trans); + + hertel_mehlhorn_decomposition (tri, parameters); +} + +} // namespace plc + +} // namespace db diff --git a/src/db/db/dbPLCConvexDecomposition.h b/src/db/db/dbPLCConvexDecomposition.h new file mode 100644 index 000000000..a04fdd844 --- /dev/null +++ b/src/db/db/dbPLCConvexDecomposition.h @@ -0,0 +1,132 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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_dbPLCConvexDecomposition +#define HDR_dbPLCConvexDecomposition + +#include "dbCommon.h" +#include "dbPLC.h" + +#include +#include +#include +#include + +namespace db +{ + +namespace plc +{ + +class Triangulation; + +struct DB_PUBLIC ConvexDecompositionParameters +{ + ConvexDecompositionParameters () + : with_segments (false), + split_edges (false), + base_verbosity (30) + { } + + /** + * @brief Introduce new segments + * + * If true, new segments will be introduced. + * New segments are constructed perpendicular to the edges forming + * a concave corner. + */ + bool with_segments; + + /** + * @brief Split edges + * + * If true, edges in the resulting polygons may be split. + * This will produce edge sections that correlate with + * other polygon edges, but may be collinear with neighbor + * edges. + */ + double split_edges; + + /** + * @brief The verbosity level above which triangulation reports details + */ + int base_verbosity; +}; + +/** + * @brief A convex decomposition algorithm + * + * This class implements a variant of the Hertel-Mehlhorn decomposition. + */ +class DB_PUBLIC ConvexDecomposition +{ +public: + /** + * @brief The constructor + * + * The graph will be one filled by the decomposition. + */ + ConvexDecomposition (Graph *graph); + + /** + * @brief Clears the triangulation + */ + void clear (); + + /** + * @brief Creates a decomposition for the given region + * + * The database unit should be chosen in a way that target area values are "in the order of 1". + * For inputs featuring acute angles (angles < ~25 degree), the parameters should defined a min + * edge length ("min_length"). + * "min_length" should be at least 1e-4. If a min edge length is given, the max area constaints + * may not be satisfied. + * + * Edges in the input should not be shorter than 1e-4. + */ + void decompose (const db::Region ®ion, const ConvexDecompositionParameters ¶meters, double dbu = 1.0); + + // more versions + void decompose (const db::Region ®ion, const ConvexDecompositionParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); + void decompose (const db::Polygon &poly, const ConvexDecompositionParameters ¶meters, double dbu = 1.0); + void decompose (const db::Polygon &poly, const std::vector &vertexes, const ConvexDecompositionParameters ¶meters, double dbu = 1.0); + void decompose (const db::Polygon &poly, const ConvexDecompositionParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); + void decompose (const db::Polygon &poly, const std::vector &vertexes, const ConvexDecompositionParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); + + /** + * @brief Decomposes a floating-point polygon + */ + void decompose (const db::DPolygon &poly, const ConvexDecompositionParameters ¶meters, const db::DCplxTrans &trans = db::DCplxTrans ()); + void decompose (const db::DPolygon &poly, const std::vector &vertexes, const ConvexDecompositionParameters ¶meters, const db::DCplxTrans &trans = db::DCplxTrans ()); + +private: + Graph *mp_graph; + + void hertel_mehlhorn_decomposition (Triangulation &tris, const ConvexDecompositionParameters ¶m); +}; + +} // namespace plc + +} // namespace db + +#endif + diff --git a/src/db/db/dbPLCTriangulation.cc b/src/db/db/dbPLCTriangulation.cc index 879cd9e7d..9785616a6 100644 --- a/src/db/db/dbPLCTriangulation.cc +++ b/src/db/db/dbPLCTriangulation.cc @@ -22,9 +22,6 @@ #include "dbPLCTriangulation.h" -#include "dbLayout.h" -#include "dbWriter.h" -#include "tlStream.h" #include "tlLog.h" #include "tlTimer.h" diff --git a/src/db/unit_tests/dbPLCConvexDecompositionTests.cc b/src/db/unit_tests/dbPLCConvexDecompositionTests.cc new file mode 100644 index 000000000..d2c334b4b --- /dev/null +++ b/src/db/unit_tests/dbPLCConvexDecompositionTests.cc @@ -0,0 +1,112 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "dbPLCConvexDecomposition.h" +#include "dbWriter.h" +#include "dbRegionProcessors.h" +#include "dbTestSupport.h" +#include "tlUnitTest.h" +#include "tlStream.h" +#include "tlFileUtils.h" + +#include +#include +#include +#include + +class TestableConvexDecomposition + : public db::plc::ConvexDecomposition +{ +public: + using db::plc::ConvexDecomposition::ConvexDecomposition; +}; + +TEST(basic) +{ + db::plc::Graph plc; + TestableConvexDecomposition decomp (&plc); + + db::Point contour[] = { + db::Point (0, 0), + db::Point (0, 100), + db::Point (1000, 100), + db::Point (1000, 500), + db::Point (1100, 500), + db::Point (1100, 100), + db::Point (2100, 100), + db::Point (2100, 0) + }; + + db::Point contour2[] = { + db::Point (4000, 0), + db::Point (4000, 100), + db::Point (5000, 100), + db::Point (5000, 500), + db::Point (5100, 500), + db::Point (5100, 100), + db::Point (6100, 100), + db::Point (6100, -1000), + db::Point (4150, -1000), + db::Point (4150, 0) + }; + + db::Region region; + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + region.insert (poly); + + poly.clear (); + poly.assign_hull (contour2 + 0, contour2 + sizeof (contour2) / sizeof (contour2[0])); + region.insert (poly); + + double dbu = 0.001; + + db::plc::ConvexDecompositionParameters param; + decomp.decompose (region, param, dbu); + + std::unique_ptr ly (plc.to_layout ()); + db::compare_layouts (_this, *ly, tl::testdata () + "/algo/hm_decomposition_au1.gds"); + + param.with_segments = true; + param.split_edges = false; + decomp.decompose (region, param, dbu); + + ly.reset (plc.to_layout ()); + db::compare_layouts (_this, *ly, tl::testdata () + "/algo/hm_decomposition_au2.gds"); + + param.with_segments = false; + param.split_edges = true; + decomp.decompose (region, param, dbu); + + ly.reset (plc.to_layout ()); + db::compare_layouts (_this, *ly, tl::testdata () + "/algo/hm_decomposition_au3.gds"); + + param.with_segments = true; + param.split_edges = true; + decomp.decompose (region, param, dbu); + + ly.reset (plc.to_layout ()); + db::compare_layouts (_this, *ly, tl::testdata () + "/algo/hm_decomposition_au4.gds"); +} + diff --git a/src/db/unit_tests/unit_tests.pro b/src/db/unit_tests/unit_tests.pro index b5aadac53..ace029ba3 100644 --- a/src/db/unit_tests/unit_tests.pro +++ b/src/db/unit_tests/unit_tests.pro @@ -12,6 +12,7 @@ SOURCES = \ dbFillToolTests.cc \ dbLogTests.cc \ dbObjectWithPropertiesTests.cc \ + dbPLCConvexDecompositionTests.cc \ dbPLCGraphTests.cc \ dbPLCTests.cc \ dbPLCTriangulationTests.cc \ diff --git a/testdata/algo/hm_decomposition_au1.gds b/testdata/algo/hm_decomposition_au1.gds new file mode 100644 index 0000000000000000000000000000000000000000..dd04b81d925b3ab992d3ac34c8e92a3a3deff618 GIT binary patch literal 1636 zcmb7^u}T9$5QhKTyW5hgf$G_P6rYU_SrargOjhxb;k| z|IYp>FZGi8$$l|@v7YMg@^+k9mp}Vg#ou-MNu8t)qC;5sHLv?R>uz_P`b9}Jjh@@( z$C#jrrlgi?9H&I7P?bs9$OX`Ha;xpK!^A&sopCAvRkix=J zh_kyhms9S{T(-#UW%f6-JNwPTAPgVS55mp|Vq_?AhU4lSX=uh*mq0H{&M%%$uHRlh zPe-puv**V<^pk%$V@CHkw}?}~?1Ttlc0&M%-3Z8LG^63{1jwiKLAE<|e~UlfLp>jl z{W@ofO9!5JmZt1)ui3@6?3tkXTRgOybiWm2j;-4 zAJ{N&;(<#$K?A5fOw)8Zm-}f>`Ha^cif@`3gRPPmqUDu&}Tc z;>^7>*(j?!xkYA^*>C4$_8d6G@h$pM+<8Na0u@ehxO$B|c9ZinpqFH)qlcr5m#2^8 z@$>tj#@y+EG(j0JGF(SC_7{Gp+0L6^1==eGXy7%;jYB}^jYd_sVGav4f zPvY`H;C-!|fAYy|xSwnENkb-eWgXIQ;s>n194;1LyR}}`U(LgFtGV`lZF-L1gK}J1 zNB6z1tV8;q-w1o$ls&pV-ez8q8ah2z_2W#j4)yClx6jhet!}EF(me}pRLB%>LuezV zUXpiZq|`|=(@145>|Xq5q|~i4(@3djBs)e*w;{lb7qNl|1@Y{|)MxP0o3G##_ym0jg&sV3 z6zc5kOsZn+#D(xByZg(`&dv-R;`j#rDDJ!>MS%*(IQaUGJa*%=Q=pe*C&T;0^XJF6 zqtVme^x^gj{p=6UxzY8-CDI&l8!;ld%^1LLHv!Tyz0qL31M+?PP}O-_-lFF>K6&vp z;5B*=|54R>(nh{JSS&uaf$o5Ik=<&hs`IRkyth#g;@Kb7dZF2VIiXFo->dh#oG(@9 zNgM4K(_n;^{-ft$r6pNsel5g*UE=R{{4L}K-2#oCzm0E0tQsjjvzgKxlZY8T^m*dr zBy$rqd?(jO7eAeWydhf1}xRn7i$uDVV`-5NdAYL_Z)L2+*gy~0RoElXw@DSs~RJE0F5Deam< a8!64+b~&&2B{q$=w9iWxGu5GrN%0A3iHH^e literal 0 HcmV?d00001 From 4f1b03496b6f273d6a416f1d20c0c47a056d9957 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 16 Apr 2025 01:15:18 +0200 Subject: [PATCH 10/58] Handling of points in Convex decomposition --- src/db/db/dbPLC.cc | 13 ++++++++ src/db/db/dbPLC.h | 33 ++++++++++++++++++- src/db/db/dbPLCConvexDecomposition.cc | 27 +++++++++++---- .../dbPLCConvexDecompositionTests.cc | 33 +++++++++++++++++++ 4 files changed, 98 insertions(+), 8 deletions(-) diff --git a/src/db/db/dbPLC.cc b/src/db/db/dbPLC.cc index f9572550e..d72b57362 100644 --- a/src/db/db/dbPLC.cc +++ b/src/db/db/dbPLC.cc @@ -542,6 +542,19 @@ Polygon::bbox () const return box; } +db::DPolygon +Polygon::polygon () const +{ + std::vector pts; + for (int i = 0; i < int (size ()); ++i) { + pts.push_back (*vertex (i)); + } + + db::DPolygon poly; + poly.assign_hull (pts.begin (), pts.end ()); + return poly; +} + std::pair Polygon::circumcircle (bool *ok) const { diff --git a/src/db/db/dbPLC.h b/src/db/db/dbPLC.h index d5d6f0a26..ee7f1a4f0 100644 --- a/src/db/db/dbPLC.h +++ b/src/db/db/dbPLC.h @@ -562,7 +562,25 @@ public: */ size_t size () const { - return mp_v.size (); + return mp_e.size (); + } + + /** + * @brief Gets the internal vertexes + * + * Internal vertexes are special points inside the polygons. + */ + size_t internal_vertexes () const + { + return mp_v.size () - mp_e.size (); + } + + /** + * @brief Adds a vertex as an internal vertex + */ + void add_internal_vertex (Vertex *v) + { + mp_v.push_back (v); } /** @@ -580,6 +598,14 @@ public: } } + /** + * @brief Gets the nth internal vertex + */ + inline Vertex *internal_vertex (size_t n) const + { + return mp_v[mp_e.size () + n]; + } + /** * @brief Gets the nth edge (n wraps around and can be negative) */ @@ -604,6 +630,11 @@ public: */ db::DBox bbox () const; + /** + * @brief Returns a DPolygon object for this polygon + */ + db::DPolygon polygon () const; + /** * @brief Gets the center point and radius of the circumcircle * If ok is non-null, it will receive a boolean value indicating whether the circumcircle is valid. diff --git a/src/db/db/dbPLCConvexDecomposition.cc b/src/db/db/dbPLCConvexDecomposition.cc index 1905b27db..ad7c65735 100644 --- a/src/db/db/dbPLCConvexDecomposition.cc +++ b/src/db/db/dbPLCConvexDecomposition.cc @@ -383,12 +383,16 @@ ConvexDecomposition::hertel_mehlhorn_decomposition (Triangulation &tris, const C left_triangles.insert (it.operator-> ()); } - std::list > polygons; + std::list > polygons; + std::list > internal_vertexes; while (! left_triangles.empty ()) { - polygons.push_back (std::vector ()); - std::vector &edges = polygons.back (); + polygons.push_back (std::unordered_set ()); + std::unordered_set &edges = polygons.back (); + + internal_vertexes.push_back (std::unordered_set ()); + std::unordered_set &ivs = internal_vertexes.back (); const Polygon *tri = *left_triangles.begin (); std::vector queue, next_queue; @@ -407,8 +411,15 @@ ConvexDecomposition::hertel_mehlhorn_decomposition (Triangulation &tris, const C const Edge *e = (*q)->edge (i); const Polygon *qq = e->other (*q); + if (e->v1 ()->is_precious ()) { + ivs.insert (e->v1 ()); + } + if (e->v2 ()->is_precious ()) { + ivs.insert (e->v2 ()); + } + if (! qq || essential_edges.find (e) != essential_edges.end ()) { - edges.push_back (const_cast (e)); // @@@ const_cast + edges.insert (const_cast (e)); // @@@ const_cast } else if (left_triangles.find (qq) != left_triangles.end ()) { next_queue.push_back (qq); } @@ -430,8 +441,12 @@ ConvexDecomposition::hertel_mehlhorn_decomposition (Triangulation &tris, const C // create the polygons + auto iv = internal_vertexes.begin (); for (auto p = polygons.begin (); p != polygons.end (); ++p) { - mp_graph->create_polygon (p->begin (), p->end ()); + Polygon *poly = mp_graph->create_polygon (p->begin (), p->end ()); + for (auto i = iv->begin (); i != iv->end (); ++i) { + poly->add_internal_vertex (*i); // @@@ reserve? + } } } @@ -472,7 +487,6 @@ ConvexDecomposition::decompose (const db::Polygon &poly, const std::vector vertexes; + vertexes.push_back (db::Point (10, 50)); + vertexes.push_back (db::Point (200, 70)); + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + double dbu = 0.001; + + db::plc::ConvexDecompositionParameters param; + decomp.decompose (poly, vertexes, param, dbu); + + for (auto p = plc.begin (); p != plc.end (); ++p) { + std::cout << p->polygon ().to_string () << std::endl; // @@@ + for (size_t i = 0; i < p->internal_vertexes (); ++i) { + std::cout << " " << p->internal_vertex (i)->to_string () << std::endl; // @@@ + } + } + +} + From 94396da11718e7c53f6c7f726c7996bc75f0b54c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 16 Apr 2025 23:43:27 +0200 Subject: [PATCH 11/58] Fixed problem of on-edge internal vertex/precious vertex --- src/db/db/dbPLC.cc | 2 +- src/db/db/dbPLCTriangulation.cc | 45 ++++++++++++++----- .../dbPLCConvexDecompositionTests.cc | 21 ++++++--- 3 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/db/db/dbPLC.cc b/src/db/db/dbPLC.cc index d72b57362..2bcf56202 100644 --- a/src/db/db/dbPLC.cc +++ b/src/db/db/dbPLC.cc @@ -551,7 +551,7 @@ Polygon::polygon () const } db::DPolygon poly; - poly.assign_hull (pts.begin (), pts.end ()); + poly.assign_hull (pts.begin (), pts.end (), false); return poly; } diff --git a/src/db/db/dbPLCTriangulation.cc b/src/db/db/dbPLCTriangulation.cc index 9785616a6..02afd64da 100644 --- a/src/db/db/dbPLCTriangulation.cc +++ b/src/db/db/dbPLCTriangulation.cc @@ -1017,7 +1017,7 @@ Triangulation::search_edges_crossing (Vertex *from, Vertex *to) if (os->has_vertex (vv)) { return result; } - if (os->crosses (edge)) { + if (os->crosses_including (edge)) { result.push_back (os); current_triangle = t.operator-> (); next_edge = os; @@ -1087,12 +1087,19 @@ Triangulation::find_edge_for_points (const db::DPoint &p1, const db::DPoint &p2) return 0; } +static bool is_touching (const db::DEdge &a, const db::DEdge &b) +{ + return (a.side_of (b.p1 ()) == 0 || a.side_of (b.p2 ()) == 0); +} + std::vector Triangulation::ensure_edge_inner (Vertex *from, Vertex *to) { auto crossed_edges = search_edges_crossing (from, to); std::vector result; + db::DEdge dedge (*from , *to); + if (crossed_edges.empty ()) { // no crossing edge - there should be a edge already @@ -1100,7 +1107,7 @@ Triangulation::ensure_edge_inner (Vertex *from, Vertex *to) tl_assert (res != 0); result.push_back (res); - } else if (crossed_edges.size () == 1) { + } else if (crossed_edges.size () == 1 && ! is_touching (dedge, crossed_edges.front ()->edge ())) { // can be solved by flipping auto pp = flip (crossed_edges.front ()); @@ -1112,18 +1119,29 @@ Triangulation::ensure_edge_inner (Vertex *from, Vertex *to) // split edge close to center db::DPoint split_point; + Edge *split_edge = 0; double d = -1.0; double l_half = 0.25 * (*to - *from).sq_length (); for (auto e = crossed_edges.begin (); e != crossed_edges.end (); ++e) { - db::DPoint p = (*e)->intersection_point (db::DEdge (*from, *to)); + db::DPoint p = (*e)->intersection_point (dedge); double dp = fabs ((p - *from).sq_length () - l_half); if (d < 0.0 || dp < d) { dp = d; split_point = p; + split_edge = *e; } } - Vertex *split_vertex = insert_point (split_point); + Vertex *split_vertex; + if (dedge.side_of (split_point) == 0) { + if (dedge.side_of (*split_edge->v1 ()) == 0) { + split_vertex = split_edge->v1 (); + } else { + split_vertex = split_edge->v2 (); + } + } else { + split_vertex = insert_point (split_point); + } result = ensure_edge_inner (from, split_vertex); @@ -1167,15 +1185,20 @@ Triangulation::join_edges (std::vector &edges) tl_assert (cp != 0); std::vector join_edges; - for (auto e = cp->begin_edges (); e != cp->end_edges (); ++e) { - if (*e != s1 && *e != s2) { - if ((*e)->can_join_via (cp)) { - join_edges.push_back (*e); - } else { - join_edges.clear (); - break; + + if (! cp->is_precious ()) { + + for (auto e = cp->begin_edges (); e != cp->end_edges (); ++e) { + if (*e != s1 && *e != s2) { + if ((*e)->can_join_via (cp)) { + join_edges.push_back (*e); + } else { + join_edges.clear (); + break; + } } } + } if (! join_edges.empty ()) { diff --git a/src/db/unit_tests/dbPLCConvexDecompositionTests.cc b/src/db/unit_tests/dbPLCConvexDecompositionTests.cc index df2c10a89..fb6dcd245 100644 --- a/src/db/unit_tests/dbPLCConvexDecompositionTests.cc +++ b/src/db/unit_tests/dbPLCConvexDecompositionTests.cc @@ -123,7 +123,7 @@ TEST(internal_vertex) }; std::vector vertexes; - vertexes.push_back (db::Point (10, 50)); + vertexes.push_back (db::Point (0, 50)); // on edge vertexes.push_back (db::Point (200, 70)); db::Polygon poly; @@ -134,12 +134,21 @@ TEST(internal_vertex) db::plc::ConvexDecompositionParameters param; decomp.decompose (poly, vertexes, param, dbu); - for (auto p = plc.begin (); p != plc.end (); ++p) { - std::cout << p->polygon ().to_string () << std::endl; // @@@ - for (size_t i = 0; i < p->internal_vertexes (); ++i) { - std::cout << " " << p->internal_vertex (i)->to_string () << std::endl; // @@@ - } + EXPECT_EQ (plc.begin () == plc.end (), false); + if (plc.begin () == plc.end ()) { + return; } + auto p = plc.begin (); + EXPECT_EQ (p->polygon ().to_string (), "(0,0;0,0.05;0,0.1;1,0.1;1,0)"); + + std::vector ip; + for (size_t i = 0; i < p->internal_vertexes (); ++i) { + ip.push_back (p->internal_vertex (i)->to_string ()); + } + std::sort (ip.begin (), ip.end ()); + EXPECT_EQ (tl::join (ip, "/"), "(0, 0.05)/(0.2, 0.07)"); + + EXPECT_EQ (++p == plc.end (), true); } From a2ef7a28f88ffb57a7fdc53cb6696e6c8371d779 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 17 Apr 2025 23:20:37 +0200 Subject: [PATCH 12/58] WIP --- src/db/db/dbPLC.cc | 10 ++ src/db/db/dbPLC.h | 43 +++++++-- src/db/db/dbPLCConvexDecomposition.cc | 38 ++------ src/db/db/dbPLCConvexDecomposition.h | 20 ++++ src/db/db/dbPLCTriangulation.cc | 10 +- src/db/unit_tests/dbPLCGraphTests.cc | 96 ++++++++++++++++++-- src/db/unit_tests/dbPLCTests.cc | 2 + src/db/unit_tests/dbPLCTriangulationTests.cc | 7 +- 8 files changed, 174 insertions(+), 52 deletions(-) diff --git a/src/db/db/dbPLC.cc b/src/db/db/dbPLC.cc index 2bcf56202..e75673cf1 100644 --- a/src/db/db/dbPLC.cc +++ b/src/db/db/dbPLC.cc @@ -72,6 +72,11 @@ Vertex::Vertex (const Vertex &v) // NOTE: edges are not copied! } +Vertex::~Vertex () +{ + // .. nothing yet .. +} + Vertex &Vertex::operator= (const Vertex &v) { if (this != &v) { @@ -176,6 +181,11 @@ Edge::Edge (Graph *graph, Vertex *v1, Vertex *v2) // .. nothing yet .. } +Edge::~Edge () +{ + // .. nothing yet .. +} + void Edge::set_left (Polygon *t) { diff --git a/src/db/db/dbPLC.h b/src/db/db/dbPLC.h index ee7f1a4f0..c88c8a506 100644 --- a/src/db/db/dbPLC.h +++ b/src/db/db/dbPLC.h @@ -161,10 +161,12 @@ protected: Vertex (Graph *graph, const DPoint &p); Vertex (Graph *graph, const Vertex &v); Vertex (Graph *graph, db::DCoord x, db::DCoord y); + ~Vertex (); private: friend class Edge; friend class Graph; + friend class tl::stable_vector; void remove_edge (const edges_iterator_non_const &ec) { @@ -499,12 +501,14 @@ protected: Edge (Graph *graph); Edge (Graph *graph, Vertex *v1, Vertex *v2); + ~Edge (); private: friend class Polygon; friend class Graph; friend class Triangulation; friend class ConvexDecomposition; + friend class tl::stable_vector; Graph *mp_graph; Vertex *mp_v1, *mp_v2; @@ -538,23 +542,34 @@ class DB_PUBLIC Polygon : public tl::list_node, public tl::Object { public: - template - Polygon (Graph *graph, Iter from, Iter to) - : mp_graph (graph), mp_e (from, to) - { - init (); - } + /** + * @brief Destructor + * + * It is legal to delete a polygon object to remove it. + */ ~Polygon (); + /** + * @brief Detaches a polygon object from the edges + */ void unlink (); - void set_id (size_t id) { m_id = id; } + /** + * @brief Gets the polygon's unique ID + */ size_t id () const { return m_id; } + /** + * @brief Gets a value indicating whether the polygon is an outside polygon + * + * Outside polygons are polygons that fill concave parts in a triangulation for example. + */ bool is_outside () const { return m_is_outside; } - void set_outside (bool o) { m_is_outside = o; } + /** + * @brief Returns a string representation + */ std::string to_string (bool with_id = false) const; /** @@ -727,8 +742,19 @@ protected: Polygon (Graph *graph); Polygon (Graph *graph, Edge *e1, Edge *e2, Edge *e3); + template + Polygon (Graph *graph, Iter from, Iter to) + : mp_graph (graph), mp_e (from, to) + { + init (); + } + + void set_outside (bool o) { m_is_outside = o; } + void set_id (size_t id) { m_id = id; } + private: friend class Graph; + friend class Triangulation; Graph *mp_graph; bool m_is_outside; @@ -764,6 +790,7 @@ struct PolygonLessFunc * hold triangles (polygons with 3 vertexes). */ class DB_PUBLIC Graph + : public tl::Object { public: typedef tl::list polygons_type; diff --git a/src/db/db/dbPLCConvexDecomposition.cc b/src/db/db/dbPLCConvexDecomposition.cc index ad7c65735..a39b5295d 100644 --- a/src/db/db/dbPLCConvexDecomposition.cc +++ b/src/db/db/dbPLCConvexDecomposition.cc @@ -85,24 +85,6 @@ struct equal_compare_func } }; -struct ConcaveCorner -{ - ConcaveCorner () - : corner (0), incoming (0), outgoing (0) - { - // .. nothing yet .. - } - - ConcaveCorner (Vertex *_corner, Edge *_incoming, Edge *_outgoing) - : corner (_corner), incoming (_incoming), outgoing (_outgoing) - { - // .. nothing yet .. - } - - Vertex *corner; - Edge *incoming, *outgoing; -}; - Edge *find_outgoing_segment (Vertex *vertex, Edge *incoming, int &vp_max_sign) { Vertex *vfrom = incoming->other (vertex); @@ -141,20 +123,16 @@ Edge *find_outgoing_segment (Vertex *vertex, Edge *incoming, int &vp_max_sign) return outgoing; } -void collect_concave_vertexes (Graph &tris, std::vector &concave_vertexes) +void +ConvexDecomposition::collect_concave_vertexes (std::vector &concave_vertexes) { concave_vertexes.clear (); - // @@@ use edge "level" - // @@@ use edges from heap std::unordered_set left; - for (auto it = tris.begin (); it != tris.end (); ++it) { - for (unsigned int i = 0; i < 3; ++i) { - Edge *e = it->edge (i); - if (e->is_segment ()) { - left.insert (e); - } + for (auto e = mp_graph->edges ().begin (); e != mp_graph->edges ().end (); ++e) { + if (e->is_segment () && (e->left () != 0 || e->right () != 0)) { + left.insert (e.operator-> ()); } } @@ -191,7 +169,7 @@ void collect_concave_vertexes (Graph &tris, std::vector &concave_ } std::pair -search_crossing_with_next_segment (const Vertex *v0, const db::DVector &direction) +ConvexDecomposition::search_crossing_with_next_segment (const Vertex *v0, const db::DVector &direction) { auto vtri = v0->polygons (); // TODO: slow? std::vector nvv, nvv_next; @@ -253,7 +231,7 @@ ConvexDecomposition::hertel_mehlhorn_decomposition (Triangulation &tris, const C bool split_edges = param.split_edges; std::vector concave_vertexes; - collect_concave_vertexes (*mp_graph, concave_vertexes); + collect_concave_vertexes (concave_vertexes); // @@@ return if no concave corners @@ -302,7 +280,7 @@ ConvexDecomposition::hertel_mehlhorn_decomposition (Triangulation &tris, const C } // As the insertion invalidates the edges, we need to collect the concave vertexes again - collect_concave_vertexes (*mp_graph, concave_vertexes); + collect_concave_vertexes (concave_vertexes); } diff --git a/src/db/db/dbPLCConvexDecomposition.h b/src/db/db/dbPLCConvexDecomposition.h index a04fdd844..03a6949bb 100644 --- a/src/db/db/dbPLCConvexDecomposition.h +++ b/src/db/db/dbPLCConvexDecomposition.h @@ -121,7 +121,27 @@ public: private: Graph *mp_graph; + struct ConcaveCorner + { + ConcaveCorner () + : corner (0), incoming (0), outgoing (0) + { + // .. nothing yet .. + } + + ConcaveCorner (Vertex *_corner, Edge *_incoming, Edge *_outgoing) + : corner (_corner), incoming (_incoming), outgoing (_outgoing) + { + // .. nothing yet .. + } + + Vertex *corner; + Edge *incoming, *outgoing; + }; + void hertel_mehlhorn_decomposition (Triangulation &tris, const ConvexDecompositionParameters ¶m); + void collect_concave_vertexes (std::vector &concave_vertexes); + std::pair search_crossing_with_next_segment (const Vertex *v0, const db::DVector &direction); }; } // namespace plc diff --git a/src/db/db/dbPLCTriangulation.cc b/src/db/db/dbPLCTriangulation.cc index 02afd64da..30757e249 100644 --- a/src/db/db/dbPLCTriangulation.cc +++ b/src/db/db/dbPLCTriangulation.cc @@ -1133,12 +1133,10 @@ Triangulation::ensure_edge_inner (Vertex *from, Vertex *to) } Vertex *split_vertex; - if (dedge.side_of (split_point) == 0) { - if (dedge.side_of (*split_edge->v1 ()) == 0) { - split_vertex = split_edge->v1 (); - } else { - split_vertex = split_edge->v2 (); - } + if (dedge.side_of (*split_edge->v1 ()) == 0) { + split_vertex = split_edge->v1 (); + } else if (dedge.side_of (*split_edge->v2 ()) == 0) { + split_vertex = split_edge->v2 (); } else { split_vertex = insert_point (split_point); } diff --git a/src/db/unit_tests/dbPLCGraphTests.cc b/src/db/unit_tests/dbPLCGraphTests.cc index e02449401..3536b5ba4 100644 --- a/src/db/unit_tests/dbPLCGraphTests.cc +++ b/src/db/unit_tests/dbPLCGraphTests.cc @@ -27,15 +27,97 @@ #include #include -TEST(basic) +namespace { - db::DBox box (0, 0, 100.0, 200.0); - db::plc::Graph plc; - // @@@ pg.insert_polygon (db::DSimplePolygon (box)); +class TestableGraph + : public db::plc::Graph +{ +public: + using db::plc::Graph::Graph; + using db::plc::Graph::create_vertex; + using db::plc::Graph::create_edge; + using db::plc::Graph::create_triangle; + using db::plc::Graph::create_polygon; +}; - // @@@ - tl::info << plc.to_string (); - plc.dump ("debug.gds"); // @@@ } +TEST(basic) +{ + TestableGraph plc; + + db::plc::Vertex *v1 = plc.create_vertex (db::DPoint (1, 2)); + EXPECT_EQ (v1->to_string (), "(1, 2)"); + + v1 = plc.create_vertex (db::DPoint (2, 1)); + EXPECT_EQ (v1->to_string (), "(2, 1)"); + + EXPECT_EQ (v1->is_precious (), false); + v1->set_is_precious (true); + EXPECT_EQ (v1->is_precious (), true); +} + +TEST(edge) +{ + TestableGraph plc; + + db::plc::Vertex *v1 = plc.create_vertex (db::DPoint (1, 2)); + db::plc::Vertex *v2 = plc.create_vertex (db::DPoint (3, 4)); + + db::plc::Edge *e = plc.create_edge (v1, v2); + EXPECT_EQ (e->to_string (), "((1, 2), (3, 4))"); + + EXPECT_EQ (v1->num_edges (), size_t (1)); + EXPECT_EQ (v2->num_edges (), size_t (1)); + + EXPECT_EQ ((*v1->begin_edges ())->edge ().to_string (), "(1,2;3,4)"); + EXPECT_EQ ((*v2->begin_edges ())->edge ().to_string (), "(1,2;3,4)"); +} + +TEST(polygon) +{ + TestableGraph plc; + EXPECT_EQ (plc.num_polygons (), size_t (0)); + EXPECT_EQ (plc.bbox ().to_string (), "()"); + + db::plc::Vertex *v1 = plc.create_vertex (db::DPoint (1, 2)); + db::plc::Vertex *v2 = plc.create_vertex (db::DPoint (3, 4)); + db::plc::Vertex *v3 = plc.create_vertex (db::DPoint (3, 2)); + + db::plc::Edge *e1 = plc.create_edge (v1, v2); + db::plc::Edge *e2 = plc.create_edge (v1, v3); + db::plc::Edge *e3 = plc.create_edge (v2, v3); + + db::plc::Polygon *tri = plc.create_triangle (e1, e2, e3); + EXPECT_EQ (tri->to_string (), "((1, 2), (3, 4), (3, 2))"); + EXPECT_EQ (tri->polygon ().to_string (), "(1,2;3,4;3,2)"); + EXPECT_EQ (plc.bbox ().to_string (), "(1,2;3,4)"); + EXPECT_EQ (plc.num_polygons (), size_t (1)); + + EXPECT_EQ (v1->num_edges (), size_t (2)); + EXPECT_EQ (v2->num_edges (), size_t (2)); + EXPECT_EQ (v3->num_edges (), size_t (2)); + + EXPECT_EQ (tri->edge (0) == e1, true); + EXPECT_EQ (tri->edge (3) == e1, true); + EXPECT_EQ (tri->edge (1) == e3, true); + EXPECT_EQ (tri->edge (2) == e2, true); + EXPECT_EQ (tri->edge (-1) == e2, true); + + EXPECT_EQ (e1->left () == 0, true); + EXPECT_EQ (e1->right () == tri, true); + EXPECT_EQ (e2->left () == tri, true); + EXPECT_EQ (e2->right () == 0, true); + EXPECT_EQ (e3->left () == 0, true); + EXPECT_EQ (e3->right () == tri, true); + + delete tri; + + EXPECT_EQ (e1->left () == 0, true); + EXPECT_EQ (e1->right () == 0, true); + EXPECT_EQ (e2->left () == 0, true); + EXPECT_EQ (e2->right () == 0, true); + EXPECT_EQ (e3->left () == 0, true); + EXPECT_EQ (e3->right () == 0, true); +} diff --git a/src/db/unit_tests/dbPLCTests.cc b/src/db/unit_tests/dbPLCTests.cc index f93c5d373..3c7745484 100644 --- a/src/db/unit_tests/dbPLCTests.cc +++ b/src/db/unit_tests/dbPLCTests.cc @@ -71,6 +71,8 @@ public: TestablePolygon (db::plc::Edge *e1, db::plc::Edge *e2, db::plc::Edge *e3) : db::plc::Polygon (0, e1, e2, e3) { } + + using db::plc::Polygon::set_outside; }; // Tests for Vertex class diff --git a/src/db/unit_tests/dbPLCTriangulationTests.cc b/src/db/unit_tests/dbPLCTriangulationTests.cc index 0d58ffc04..ae8856bb4 100644 --- a/src/db/unit_tests/dbPLCTriangulationTests.cc +++ b/src/db/unit_tests/dbPLCTriangulationTests.cc @@ -33,6 +33,9 @@ #include #include +namespace +{ + class TestableTriangulation : public db::plc::Triangulation { @@ -64,6 +67,8 @@ public: using db::plc::Graph::create_triangle; }; +} + TEST(basic) { db::plc::Graph plc; @@ -343,7 +348,7 @@ TEST(heavy_insert) double y = round (flt_rand () * res) * (1.0 / res); db::plc::Vertex *v = tris.insert_point (x, y); bbox += db::DPoint (x, y); - vmap.insert (std::make_pair (*v, false)); + vmap.insert (std::pair (*v, false)); } // not strictly true, but very likely with at least 10 vertexes: From 3ed39e8a4af66518eec0222b576c302bf4cb2cd6 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 17 Apr 2025 23:40:58 +0200 Subject: [PATCH 13/58] Internal vertex ID added, cleanup --- src/db/db/dbPLC.cc | 18 ++++++++-- src/db/db/dbPLC.h | 34 ++++++++++++++++--- src/db/db/dbPLCConvexDecomposition.cc | 20 +++-------- src/db/db/dbPLCTriangulation.cc | 6 ++-- .../dbPLCConvexDecompositionTests.cc | 5 +-- src/db/unit_tests/dbPLCGraphTests.cc | 3 +- 6 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/db/db/dbPLC.cc b/src/db/db/dbPLC.cc index e75673cf1..c44a96760 100644 --- a/src/db/db/dbPLC.cc +++ b/src/db/db/dbPLC.cc @@ -55,19 +55,19 @@ Vertex::Vertex (Graph *graph, const db::DPoint &p) } Vertex::Vertex (Graph *graph, const Vertex &v) - : DPoint (), mp_graph (graph), m_is_precious (false) + : DPoint (), mp_graph (graph), m_is_precious (false), m_id (0) { operator= (v); } Vertex::Vertex (Graph *graph, db::DCoord x, db::DCoord y) - : DPoint (x, y), mp_graph (graph), m_is_precious (false) + : DPoint (x, y), mp_graph (graph), m_is_precious (false), m_id (0) { // .. nothing yet .. } Vertex::Vertex (const Vertex &v) - : DPoint (v), mp_graph (v.mp_graph), m_is_precious (v.m_is_precious) + : DPoint (v), mp_graph (v.mp_graph), m_is_precious (v.m_is_precious), m_id (v.m_id) { // NOTE: edges are not copied! } @@ -83,6 +83,7 @@ Vertex &Vertex::operator= (const Vertex &v) // NOTE: edges are not copied! db::DPoint::operator= (v); m_is_precious = v.m_is_precious; + m_id = v.m_id; } return *this; } @@ -686,6 +687,17 @@ Polygon::contains (const db::DPoint &point) const return res; } +Edge * +Polygon::next_edge (const Edge *edge, const Vertex *vertex) const +{ + for (auto e = mp_e.begin (); e != mp_e.end (); ++e) { + if (*e != edge && ((*e)->v1 () == vertex || (*e)->v2 () == vertex)) { + return *e; + } + } + return 0; +} + double Polygon::min_edge_length () const { diff --git a/src/db/db/dbPLC.h b/src/db/db/dbPLC.h index c88c8a506..ea5ed4e38 100644 --- a/src/db/db/dbPLC.h +++ b/src/db/db/dbPLC.h @@ -127,17 +127,29 @@ public: bool has_edge (const Edge *edge) const; /** - * @brief Sets a valid indicating whether the vertex is precious + * @brief Sets a value indicating whether the vertex is precious * * "precious" vertexes are not removed during triangulation for example. */ - void set_is_precious (bool f) { m_is_precious = f; } + void set_is_precious (bool f, unsigned int id) + { + m_is_precious = f; + m_id = id; + } /** - * @brief Gets a valid indicating whether the vertex is precious + * @brief Gets a value indicating whether the vertex is precious */ bool is_precious () const { return m_is_precious; } + /** + * @brief Gets the ID passed to "set_is_precious" + * + * This ID can be used to identify the vertex in the context it came from (e.g. + * index in point vector). + */ + unsigned int id () const { return m_id; } + /** * @brief Returns a string representation of the vertex */ @@ -175,7 +187,8 @@ private: Graph *mp_graph; edges_type mp_edges; - bool m_is_precious; + bool m_is_precious : 1; + unsigned int m_id : 31; }; /** @@ -598,6 +611,14 @@ public: mp_v.push_back (v); } + /** + * @brief Reserves for n internal vertexes + */ + void reserve_internal_vertexes (size_t n) + { + mp_v.reserve (mp_v.size () + n); + } + /** * @brief Gets the nth vertex (n wraps around and can be negative) * The vertexes are oriented clockwise. @@ -716,6 +737,11 @@ public: return false; } + /** + * @brief Coming from an edge e and the vertex v, gets the next edge + */ + Edge *next_edge (const Edge *e, const Vertex *v) const; + /** * @brief Returns the minimum edge length */ diff --git a/src/db/db/dbPLCConvexDecomposition.cc b/src/db/db/dbPLCConvexDecomposition.cc index a39b5295d..45eda37ed 100644 --- a/src/db/db/dbPLCConvexDecomposition.cc +++ b/src/db/db/dbPLCConvexDecomposition.cc @@ -233,10 +233,6 @@ ConvexDecomposition::hertel_mehlhorn_decomposition (Triangulation &tris, const C std::vector concave_vertexes; collect_concave_vertexes (concave_vertexes); - // @@@ return if no concave corners - - // @@@ sort concave vertexes - std::vector new_points; if (with_segments) { @@ -306,15 +302,8 @@ ConvexDecomposition::hertel_mehlhorn_decomposition (Triangulation &tris, const C const Polygon *t = e->v2 () == v0 ? e->right () : e->left (); tl_assert (t != 0); - // @@@ make a method of Polygon -> next_edge(Edge *e, Vertex *at) - const Edge *en = 0; - for (unsigned int i = 0; i < 3; ++i) { - const Edge *ee = t->edge (i); - if (ee != e && (ee->v1 () == v0 || ee->v2 () == v0)) { - en = ee; - break; - } - } + const Edge *en = t->next_edge (e, v0); + tl_assert (en != 0); db::DVector v1 = e->edge ().d () * (e->v1 () == v0 ? 1.0 : -1.0); db::DVector v2 = en->edge ().d () * (en->v1 () == v0 ? 1.0 : -1.0); @@ -397,7 +386,7 @@ ConvexDecomposition::hertel_mehlhorn_decomposition (Triangulation &tris, const C } if (! qq || essential_edges.find (e) != essential_edges.end ()) { - edges.insert (const_cast (e)); // @@@ const_cast + edges.insert (const_cast (e)); // TODO: ugly const_cast } else if (left_triangles.find (qq) != left_triangles.end ()) { next_queue.push_back (qq); } @@ -422,8 +411,9 @@ ConvexDecomposition::hertel_mehlhorn_decomposition (Triangulation &tris, const C auto iv = internal_vertexes.begin (); for (auto p = polygons.begin (); p != polygons.end (); ++p) { Polygon *poly = mp_graph->create_polygon (p->begin (), p->end ()); + poly->reserve_internal_vertexes (iv->size ()); for (auto i = iv->begin (); i != iv->end (); ++i) { - poly->add_internal_vertex (*i); // @@@ reserve? + poly->add_internal_vertex (*i); } } } diff --git a/src/db/db/dbPLCTriangulation.cc b/src/db/db/dbPLCTriangulation.cc index 30757e249..90f68d296 100644 --- a/src/db/db/dbPLCTriangulation.cc +++ b/src/db/db/dbPLCTriangulation.cc @@ -1364,8 +1364,9 @@ Triangulation::create_constrained_delaunay (const db::Polygon &p, const std::vec { clear (); + unsigned int id = 0; for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { - insert_point (trans * *v)->set_is_precious (true); + insert_point (trans * *v)->set_is_precious (true, id++); } std::vector > edge_contours; @@ -1379,8 +1380,9 @@ Triangulation::create_constrained_delaunay (const db::DPolygon &p, const std::ve { clear (); + unsigned int id = 0; for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { - insert_point (trans * *v)->set_is_precious (true); + insert_point (trans * *v)->set_is_precious (true, id++); } std::vector > edge_contours; diff --git a/src/db/unit_tests/dbPLCConvexDecompositionTests.cc b/src/db/unit_tests/dbPLCConvexDecompositionTests.cc index fb6dcd245..2cb1e1a70 100644 --- a/src/db/unit_tests/dbPLCConvexDecompositionTests.cc +++ b/src/db/unit_tests/dbPLCConvexDecompositionTests.cc @@ -125,6 +125,7 @@ TEST(internal_vertex) std::vector vertexes; vertexes.push_back (db::Point (0, 50)); // on edge vertexes.push_back (db::Point (200, 70)); + vertexes.push_back (db::Point (0, 0)); // on vertex db::Polygon poly; poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); @@ -144,10 +145,10 @@ TEST(internal_vertex) std::vector ip; for (size_t i = 0; i < p->internal_vertexes (); ++i) { - ip.push_back (p->internal_vertex (i)->to_string ()); + ip.push_back (p->internal_vertex (i)->to_string () + "#" + tl::to_string (p->internal_vertex (i)->id ())); } std::sort (ip.begin (), ip.end ()); - EXPECT_EQ (tl::join (ip, "/"), "(0, 0.05)/(0.2, 0.07)"); + EXPECT_EQ (tl::join (ip, "/"), "(0, 0)#2/(0, 0.05)#0/(0.2, 0.07)#1"); EXPECT_EQ (++p == plc.end (), true); } diff --git a/src/db/unit_tests/dbPLCGraphTests.cc b/src/db/unit_tests/dbPLCGraphTests.cc index 3536b5ba4..29b7d9597 100644 --- a/src/db/unit_tests/dbPLCGraphTests.cc +++ b/src/db/unit_tests/dbPLCGraphTests.cc @@ -54,8 +54,9 @@ TEST(basic) EXPECT_EQ (v1->to_string (), "(2, 1)"); EXPECT_EQ (v1->is_precious (), false); - v1->set_is_precious (true); + v1->set_is_precious (true, 17); EXPECT_EQ (v1->is_precious (), true); + EXPECT_EQ (v1->id (), 17u); } TEST(edge) From c6a4b6aba0de4d6ac612515cc9fb70307157c9ed Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 18 Apr 2025 00:15:43 +0200 Subject: [PATCH 14/58] Added a new utility function --- src/db/db/dbPLCTriangulation.cc | 35 ++++++++++++++++++-- src/db/db/dbPLCTriangulation.h | 31 ++++++++++------- src/db/unit_tests/dbPLCTriangulationTests.cc | 30 +++++++++++++++++ 3 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/db/db/dbPLCTriangulation.cc b/src/db/db/dbPLCTriangulation.cc index 90f68d296..d148656a6 100644 --- a/src/db/db/dbPLCTriangulation.cc +++ b/src/db/db/dbPLCTriangulation.cc @@ -294,7 +294,7 @@ Triangulation::find_triangle_for_point (const db::DPoint &point) } Edge * -Triangulation::find_closest_edge (const db::DPoint &p, Vertex *vstart, bool inside_only) +Triangulation::find_closest_edge (const db::DPoint &p, Vertex *vstart, bool inside_only) const { if (!vstart) { @@ -1057,7 +1057,7 @@ Triangulation::search_edges_crossing (Vertex *from, Vertex *to) } Vertex * -Triangulation::find_vertex_for_point (const db::DPoint &point) +Triangulation::find_vertex_for_point (const db::DPoint &point) const { Edge *edge = find_closest_edge (point); if (!edge) { @@ -1073,7 +1073,7 @@ Triangulation::find_vertex_for_point (const db::DPoint &point) } Edge * -Triangulation::find_edge_for_points (const db::DPoint &p1, const db::DPoint &p2) +Triangulation::find_edge_for_points (const db::DPoint &p1, const db::DPoint &p2) const { Vertex *v = find_vertex_for_point (p1); if (!v) { @@ -1087,6 +1087,35 @@ Triangulation::find_edge_for_points (const db::DPoint &p1, const db::DPoint &p2) return 0; } +std::vector +Triangulation::find_vertexes_along_line (const db::DPoint &p1, const db::DPoint &p2) const +{ + db::DEdge e12 (p1, p2); + + Vertex *v = find_vertex_for_point (p1); + if (!v) { + v = find_vertex_for_point (p2); + e12.swap_points (); + } + + std::vector result; + + while (v) { + Vertex *vn = 0; + for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { + Vertex *vv = (*e)->other (v); + if (db::vprod_sign (e12.d (), *vv - *v) == 0 && db::sprod_sign (e12.d (), *vv - *v) > 0 && db::sprod_sign (e12.d (), *vv - e12.p2 ()) < 0) { + result.push_back (vv); + vn = vv; + break; + } + } + v = vn; + } + + return result; +} + static bool is_touching (const db::DEdge &a, const db::DEdge &b) { return (a.side_of (b.p1 ()) == 0 || a.side_of (b.p2 ()) == 0); diff --git a/src/db/db/dbPLCTriangulation.h b/src/db/db/dbPLCTriangulation.h index b3e941f89..af17aa876 100644 --- a/src/db/db/dbPLCTriangulation.h +++ b/src/db/db/dbPLCTriangulation.h @@ -158,6 +158,23 @@ public: */ Vertex *insert_point (const db::DPoint &point, std::list > *new_triangles = 0); + /** + * @brief Finds the edge for two given points + */ + Edge *find_edge_for_points (const db::DPoint &p1, const db::DPoint &p2) const; + + /** + * @brief Finds the vertex for a point + */ + Vertex *find_vertex_for_point (const db::DPoint &pt) const; + + /** + * @brief Finds the vertexes along the line given from p1 and p2 + * + * At least one of the points p1 and p2 must be existing vertexes. + */ + std::vector find_vertexes_along_line (const db::DPoint &p1, const db::DPoint &p2) const; + /** * @brief Statistics: number of flips (fixing) */ @@ -216,16 +233,6 @@ protected: */ std::vector search_edges_crossing (Vertex *from, Vertex *to); - /** - * @brief Finds the edge for two given points - */ - Edge *find_edge_for_points (const db::DPoint &p1, const db::DPoint &p2); - - /** - * @brief Finds the vertex for a point - */ - Vertex *find_vertex_for_point (const db::DPoint &pt); - /** * @brief Ensures all points between from an to are connected by edges and makes these segments */ @@ -275,7 +282,7 @@ private: bool m_is_constrained; size_t m_level; size_t m_id; - size_t m_flips, m_hops; + mutable size_t m_flips, m_hops; template void make_contours (const Poly &poly, const Trans &trans, std::vector > &contours); @@ -284,7 +291,7 @@ private: std::vector fill_concave_corners (const std::vector &edges); void fix_triangles (const std::vector &tris, const std::vector &fixed_edges, std::list > *new_triangles); std::vector find_triangle_for_point (const db::DPoint &point); - Edge *find_closest_edge (const db::DPoint &p, Vertex *vstart = 0, bool inside_only = false); + Edge *find_closest_edge (const db::DPoint &p, Vertex *vstart = 0, bool inside_only = false) const; Vertex *insert (Vertex *vertex, std::list > *new_triangles = 0); void split_triangle (Polygon *t, Vertex *vertex, std::list > *new_triangles_out); void split_triangles_on_edge (Vertex *vertex, Edge *split_edge, std::list > *new_triangles_out); diff --git a/src/db/unit_tests/dbPLCTriangulationTests.cc b/src/db/unit_tests/dbPLCTriangulationTests.cc index ae8856bb4..9253eb0ce 100644 --- a/src/db/unit_tests/dbPLCTriangulationTests.cc +++ b/src/db/unit_tests/dbPLCTriangulationTests.cc @@ -146,6 +146,36 @@ TEST(insert_vertex_twice) EXPECT_EQ (tris.check(), true); } +TEST(collect_vertexes) +{ + db::plc::Graph plc; + TestableTriangulation tris (&plc); + tris.init_box (db::DBox (0, 0, 1, 1)); + tris.insert_point (0.2, 0.2); + tris.insert_point (0.5, 0.5); + + std::vector vertexes = tris.find_vertexes_along_line (db::DPoint (0, 0), db::DPoint (1.5, 1.5)); + EXPECT_EQ (vertexes.size (), size_t (3)); + if (vertexes.size () >= size_t (3)) { + EXPECT_EQ (vertexes [0]->to_string (), "(0.2, 0.2)"); + EXPECT_EQ (vertexes [1]->to_string (), "(0.5, 0.5)"); + EXPECT_EQ (vertexes [2]->to_string (), "(1, 1)"); + } + + vertexes = tris.find_vertexes_along_line (db::DPoint (0, 0), db::DPoint (1.0, 1.0)); + EXPECT_EQ (vertexes.size (), size_t (2)); + if (vertexes.size () >= size_t (2)) { + EXPECT_EQ (vertexes [0]->to_string (), "(0.2, 0.2)"); + EXPECT_EQ (vertexes [1]->to_string (), "(0.5, 0.5)"); + } + + vertexes = tris.find_vertexes_along_line (db::DPoint (1, 1), db::DPoint (0.25, 0.25)); + EXPECT_EQ (vertexes.size (), size_t (1)); + if (vertexes.size () >= size_t (1)) { + EXPECT_EQ (vertexes [0]->to_string (), "(0.5, 0.5)"); + } +} + TEST(insert_vertex_convex) { db::plc::Graph plc; From 16604e5a92bd37b586f6f8f8b1611d6008e1cad6 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 18 Apr 2025 13:12:45 +0200 Subject: [PATCH 15/58] New module: 'pex' --- setup.py | 45 +++++++++++++++- src/klayout.pri | 1 + src/klayout.pro | 2 + src/pex/pex.pro | 6 +++ src/pex/pex/gsiDeclRExtractor.cc | 44 ++++++++++++++++ src/pex/pex/pex.pro | 23 +++++++++ src/pex/pex/pexCommon.h | 51 +++++++++++++++++++ src/pex/pex/pexForceLink.cc | 32 ++++++++++++ src/pex/pex/pexForceLink.h | 40 +++++++++++++++ src/pex/pex/pexRExtractor.cc | 36 +++++++++++++ src/pex/pex/pexRExtractor.h | 49 ++++++++++++++++++ src/pex/unit_tests/pexRExtractorTests.cc | 31 +++++++++++ src/pex/unit_tests/unit_tests.pro | 16 ++++++ .../distutils_src/klayout/pex/__init__.py | 4 ++ src/pymod/distutils_src/klayout/pexcore.pyi | 0 src/pymod/pex/pex.pro | 14 +++++ src/pymod/pex/pexMain.cc | 31 +++++++++++ src/pymod/pex/pexMain.h | 24 +++++++++ src/pymod/pymod.pro | 1 + src/pymod/unit_tests/pymod_tests.cc | 1 + src/with_all_libs.pri | 6 +-- testdata/pymod/import_pex.py | 37 ++++++++++++++ 22 files changed, 489 insertions(+), 5 deletions(-) create mode 100644 src/pex/pex.pro create mode 100644 src/pex/pex/gsiDeclRExtractor.cc create mode 100644 src/pex/pex/pex.pro create mode 100644 src/pex/pex/pexCommon.h create mode 100644 src/pex/pex/pexForceLink.cc create mode 100644 src/pex/pex/pexForceLink.h create mode 100644 src/pex/pex/pexRExtractor.cc create mode 100644 src/pex/pex/pexRExtractor.h create mode 100644 src/pex/unit_tests/pexRExtractorTests.cc create mode 100644 src/pex/unit_tests/unit_tests.pro create mode 100644 src/pymod/distutils_src/klayout/pex/__init__.py create mode 100644 src/pymod/distutils_src/klayout/pexcore.pyi create mode 100644 src/pymod/pex/pex.pro create mode 100644 src/pymod/pex/pexMain.cc create mode 100644 src/pymod/pex/pexMain.h create mode 100755 testdata/pymod/import_pex.py diff --git a/setup.py b/setup.py index c816a0c5e..1ac1ff473 100644 --- a/setup.py +++ b/setup.py @@ -592,6 +592,25 @@ _db = Library( ) config.add_extension(_db) +# ------------------------------------------------------------------ +# _pex dependency library + +_pex_path = os.path.join("src", "pex", "pex") +_pex_sources = set(glob.glob(os.path.join(_pex_path, "*.cc"))) + +_pex = Library( + config.root + "._pex", + define_macros=config.macros() + [("MAKE_PEX_LIBRARY", 1)], + include_dirs=[_tl_path, _gsi_path, _db_path, _pex_path], + extra_objects=[config.path_of("_tl", _tl_path), config.path_of("_gsi", _gsi_path), config.path_of("_db", _db_path)], + language="c++", + libraries=config.libraries('_pex'), + extra_link_args=config.link_args("_pex"), + extra_compile_args=config.compile_args("_pex"), + sources=list(_pex_sources), +) +config.add_extension(_pex) + # ------------------------------------------------------------------ # _lib dependency library @@ -869,6 +888,28 @@ db = Extension( sources=list(db_sources), ) +# ------------------------------------------------------------------ +# pex extension library + +pex_path = os.path.join("src", "pymod", "pex") +pex_sources = set(glob.glob(os.path.join(pex_path, "*.cc"))) + +pex = Extension( + config.root + ".pexcore", + define_macros=config.macros(), + include_dirs=[_db_path, _tl_path, _gsi_path, _pya_path, _pex_path], + extra_objects=[ + config.path_of("_db", _db_path), + config.path_of("_pex", _pex_path), + config.path_of("_tl", _tl_path), + config.path_of("_gsi", _gsi_path), + config.path_of("_pya", _pya_path), + ], + extra_link_args=config.link_args("pexcore"), + extra_compile_args=config.compile_args("pexcore"), + sources=list(pex_sources), +) + # ------------------------------------------------------------------ # lib extension library @@ -1011,8 +1052,8 @@ if __name__ == "__main__": package_data={config.root: ["src/pymod/distutils_src/klayout/*.pyi"]}, data_files=[(config.root, ["src/pymod/distutils_src/klayout/py.typed"])], include_package_data=True, - ext_modules=[_tl, _gsi, _pya, _rba, _db, _lib, _rdb, _lym, _laybasic, _layview, _ant, _edt, _img] + ext_modules=[_tl, _gsi, _pya, _rba, _db, _pex, _lib, _rdb, _lym, _laybasic, _layview, _ant, _edt, _img] + db_plugins - + [tl, db, lib, rdb, lay, pya], + + [tl, db, pex, lib, rdb, lay, pya], cmdclass={'build_ext': klayout_build_ext} ) diff --git a/src/klayout.pri b/src/klayout.pri index e7f90e037..067942aec 100644 --- a/src/klayout.pri +++ b/src/klayout.pri @@ -1,6 +1,7 @@ TL_INC = $$PWD/tl/tl DB_INC = $$PWD/db/db +PEX_INC = $$PWD/pex/pex DRC_INC = $$PWD/drc/drc LVS_INC = $$PWD/lvs/lvs EDT_INC = $$PWD/edt/edt diff --git a/src/klayout.pro b/src/klayout.pro index 2bb0bf555..3e211798d 100644 --- a/src/klayout.pro +++ b/src/klayout.pro @@ -7,6 +7,7 @@ SUBDIRS = \ tl \ gsi \ db \ + pex \ rdb \ lib \ plugins \ @@ -62,6 +63,7 @@ equals(HAVE_PYTHON, "1") { gsi.depends += tl db.depends += gsi +pex.depends += db rdb.depends += db lib.depends += db diff --git a/src/pex/pex.pro b/src/pex/pex.pro new file mode 100644 index 000000000..9bcdb2590 --- /dev/null +++ b/src/pex/pex.pro @@ -0,0 +1,6 @@ + +TEMPLATE = subdirs +SUBDIRS = pex unit_tests + +unit_tests.depends += pex + diff --git a/src/pex/pex/gsiDeclRExtractor.cc b/src/pex/pex/gsiDeclRExtractor.cc new file mode 100644 index 000000000..983ec13f6 --- /dev/null +++ b/src/pex/pex/gsiDeclRExtractor.cc @@ -0,0 +1,44 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "pexRExtractor.h" + +namespace gsi +{ + +// @@@ +static pex::RExtractor *new_sqc_rextractor () +{ + return new pex::RExtractor (); +} + +Class decl_RExtractor ("pex", "RExtractor", + gsi::constructor ("square_counting", &new_sqc_rextractor, + "@brief Creates a square counting R extractor\n" + ), + "@brief A base class for the R extractor\n" +); + +} + diff --git a/src/pex/pex/pex.pro b/src/pex/pex/pex.pro new file mode 100644 index 000000000..80b1b5a8b --- /dev/null +++ b/src/pex/pex/pex.pro @@ -0,0 +1,23 @@ + +DESTDIR = $$OUT_PWD/../.. +TARGET = klayout_pex + +include($$PWD/../../lib.pri) + +DEFINES += MAKE_PEX_LIBRARY + +SOURCES = \ + pexForceLink.cc \ + pexRExtractor.cc \ + gsiDeclRExtractor.cc \ + +HEADERS = \ + pexForceLink.h \ + pexRExtractor.h \ + +RESOURCES = \ + +INCLUDEPATH += $$TL_INC $$GSI_INC $$DB_INC +DEPENDPATH += $$TL_INC $$GSI_INC $$DB_INC +LIBS += -L$$DESTDIR -lklayout_tl -lklayout_gsi -lklayout_db + diff --git a/src/pex/pex/pexCommon.h b/src/pex/pex/pexCommon.h new file mode 100644 index 000000000..f978461ea --- /dev/null +++ b/src/pex/pex/pexCommon.h @@ -0,0 +1,51 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 !defined(HDR_pexCommon_h) +# define HDR_pexCommon_h + +# if defined _WIN32 || defined __CYGWIN__ + +# ifdef MAKE_PEX_LIBRARY +# define PEX_PUBLIC __declspec(dllexport) +# else +# define PEX_PUBLIC __declspec(dllimport) +# endif +# define PEX_LOCAL +# define PEX_PUBLIC_TEMPLATE + +# else + +# if __GNUC__ >= 4 || defined(__clang__) +# define PEX_PUBLIC __attribute__ ((visibility ("default"))) +# define PEX_PUBLIC_TEMPLATE __attribute__ ((visibility ("default"))) +# define PEX_LOCAL __attribute__ ((visibility ("hidden"))) +# else +# define PEX_PUBLIC +# define PEX_PUBLIC_TEMPLATE +# define PEX_LOCAL +# endif + +# endif + +#endif diff --git a/src/pex/pex/pexForceLink.cc b/src/pex/pex/pexForceLink.cc new file mode 100644 index 000000000..55c333593 --- /dev/null +++ b/src/pex/pex/pexForceLink.cc @@ -0,0 +1,32 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "pexForceLink.h" + +namespace pex +{ + int _force_link_f () + { + return 0; + } +} + diff --git a/src/pex/pex/pexForceLink.h b/src/pex/pex/pexForceLink.h new file mode 100644 index 000000000..69452fb3b --- /dev/null +++ b/src/pex/pex/pexForceLink.h @@ -0,0 +1,40 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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_pexForceLink +#define HDR_pexForceLink + +#include "pexCommon.h" + +/** + * @file Include this function to force linking of the pex module + */ + +namespace pex +{ + PEX_PUBLIC int _force_link_f (); + static int _force_link_target = _force_link_f (); +} + +#endif + diff --git a/src/pex/pex/pexRExtractor.cc b/src/pex/pex/pexRExtractor.cc new file mode 100644 index 000000000..ca67f5c59 --- /dev/null +++ b/src/pex/pex/pexRExtractor.cc @@ -0,0 +1,36 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "pexRExtractor.h" + +namespace pex +{ + +RExtractor::RExtractor () +{ + // .. nothing yet .. +} + +} + + diff --git a/src/pex/pex/pexRExtractor.h b/src/pex/pex/pexRExtractor.h new file mode 100644 index 000000000..ddc775927 --- /dev/null +++ b/src/pex/pex/pexRExtractor.h @@ -0,0 +1,49 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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_pexRExtractor +#define HDR_pexRExtractor + +#include "pexCommon.h" +#include + +namespace pex +{ + +/** + * @brief A base class for an resistance extractor + * + * The R extractor takes a polyon, a technology definition + * and port definitions and extracts a resistor network. + * + * Ports are points or polygons that define the connection + * points to the network. + */ +struct PEX_PUBLIC RExtractor +{ + RExtractor (); +}; + +} + +#endif + diff --git a/src/pex/unit_tests/pexRExtractorTests.cc b/src/pex/unit_tests/pexRExtractorTests.cc new file mode 100644 index 000000000..c3ad86a08 --- /dev/null +++ b/src/pex/unit_tests/pexRExtractorTests.cc @@ -0,0 +1,31 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "pexRExtractor.h" +#include "tlUnitTest.h" + +TEST(1) +{ + // @@@ +} + diff --git a/src/pex/unit_tests/unit_tests.pro b/src/pex/unit_tests/unit_tests.pro new file mode 100644 index 000000000..c6ccc1b74 --- /dev/null +++ b/src/pex/unit_tests/unit_tests.pro @@ -0,0 +1,16 @@ + +DESTDIR_UT = $$OUT_PWD/../.. +DESTDIR = $$OUT_PWD/.. + +TARGET = pex_tests + +include($$PWD/../../lib_ut.pri) + +SOURCES = \ + pexRExtractorTests.cc \ + +INCLUDEPATH += $$TL_INC $$DB_INC $$GSI_INC $$PEX_INC +DEPENDPATH += $$TL_INC $$DB_INC $$GSI_INC $$PEX_INC + +LIBS += -L$$DESTDIR_UT -lklayout_db -lklayout_tl -lklayout_gsi -lklayout_pex + diff --git a/src/pymod/distutils_src/klayout/pex/__init__.py b/src/pymod/distutils_src/klayout/pex/__init__.py new file mode 100644 index 000000000..883a7f0b1 --- /dev/null +++ b/src/pymod/distutils_src/klayout/pex/__init__.py @@ -0,0 +1,4 @@ + +import sys +from ..pexcore import __all__ +from ..pexcore import * diff --git a/src/pymod/distutils_src/klayout/pexcore.pyi b/src/pymod/distutils_src/klayout/pexcore.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/src/pymod/pex/pex.pro b/src/pymod/pex/pex.pro new file mode 100644 index 000000000..d705cced3 --- /dev/null +++ b/src/pymod/pex/pex.pro @@ -0,0 +1,14 @@ + +TARGET = pexcore +REALMODULE = pex +PYI = pexcore.pyi + +include($$PWD/../pymod.pri) + +SOURCES = \ + pexMain.cc \ + +HEADERS += \ + +LIBS += -lklayout_pex + diff --git a/src/pymod/pex/pexMain.cc b/src/pymod/pex/pexMain.cc new file mode 100644 index 000000000..2f64943d2 --- /dev/null +++ b/src/pymod/pex/pexMain.cc @@ -0,0 +1,31 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "../pymodHelper.h" +#include "pexMain.h" + +static PyObject *pex_module_init (const char *pymod_name, const char *mod_name, const char *mod_description) +{ + return module_init (pymod_name, mod_name, mod_description); +} + +DEFINE_PYMOD_WITH_INIT(pexcore, "pex", "KLayout core module 'pex'", pex_module_init) diff --git a/src/pymod/pex/pexMain.h b/src/pymod/pex/pexMain.h new file mode 100644 index 000000000..0777186bf --- /dev/null +++ b/src/pymod/pex/pexMain.h @@ -0,0 +1,24 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 + +*/ + +// to force linking of the pex module +#include "../../pex/pex/pexForceLink.h" diff --git a/src/pymod/pymod.pro b/src/pymod/pymod.pro index 1fac8d258..b286f9a74 100644 --- a/src/pymod/pymod.pro +++ b/src/pymod/pymod.pro @@ -4,6 +4,7 @@ include($$PWD/../klayout.pri) TEMPLATE = subdirs SUBDIRS = \ db \ + pex \ tl \ rdb \ lib \ diff --git a/src/pymod/unit_tests/pymod_tests.cc b/src/pymod/unit_tests/pymod_tests.cc index 15cca459a..8d78544b7 100644 --- a/src/pymod/unit_tests/pymod_tests.cc +++ b/src/pymod/unit_tests/pymod_tests.cc @@ -84,6 +84,7 @@ PYMODTEST (bridge, "bridge.py") PYMODTEST (import_tl, "import_tl.py") PYMODTEST (import_db, "import_db.py") +PYMODTEST (import_pex, "import_pex.py") PYMODTEST (klayout_db_tests, "klayout_db_tests.py") PYMODTEST (import_rdb, "import_rdb.py") PYMODTEST (import_lay, "import_lay.py") diff --git a/src/with_all_libs.pri b/src/with_all_libs.pri index c1c4812b9..e3576dd0f 100644 --- a/src/with_all_libs.pri +++ b/src/with_all_libs.pri @@ -1,8 +1,8 @@ -INCLUDEPATH += $$RBA_INC $$PYA_INC $$TL_INC $$GSI_INC $$DB_INC $$RDB_INC $$LYM_INC $$LAYBASIC_INC $$LAYVIEW_INC $$ANT_INC $$IMG_INC $$EDT_INC $$LIB_INC $$VERSION_INC -DEPENDPATH += $$RBA_INC $$PYA_INC $$TL_INC $$GSI_INC $$DB_INC $$RDB_INC $$LYM_INC $$LAYBASIC_INC $$LAYVIEW_INC $$ANT_INC $$IMG_INC $$EDT_INC $$LIB_INC $$VERSION_INC +INCLUDEPATH += $$RBA_INC $$PYA_INC $$TL_INC $$GSI_INC $$DB_INC $$PEX_INC $$RDB_INC $$LYM_INC $$LAYBASIC_INC $$LAYVIEW_INC $$ANT_INC $$IMG_INC $$EDT_INC $$LIB_INC $$VERSION_INC +DEPENDPATH += $$RBA_INC $$PYA_INC $$TL_INC $$GSI_INC $$DB_INC $$PEX_INC $$RDB_INC $$LYM_INC $$LAYBASIC_INC $$LAYVIEW_INC $$ANT_INC $$IMG_INC $$EDT_INC $$LIB_INC $$VERSION_INC -LIBS += "$$PYTHONLIBFILE" "$$RUBYLIBFILE" -L$$DESTDIR -lklayout_tl -lklayout_gsi -lklayout_db -lklayout_rdb -lklayout_lym -lklayout_laybasic -lklayout_layview -lklayout_ant -lklayout_img -lklayout_edt -lklayout_lib +LIBS += "$$PYTHONLIBFILE" "$$RUBYLIBFILE" -L$$DESTDIR -lklayout_tl -lklayout_gsi -lklayout_db -lklayout_pex -lklayout_rdb -lklayout_lym -lklayout_laybasic -lklayout_layview -lklayout_ant -lklayout_img -lklayout_edt -lklayout_lib !equals(HAVE_QT, "0") { diff --git a/testdata/pymod/import_pex.py b/testdata/pymod/import_pex.py new file mode 100755 index 000000000..56f3c3bf4 --- /dev/null +++ b/testdata/pymod/import_pex.py @@ -0,0 +1,37 @@ +# KLayout Layout Viewer +# Copyright (C) 2006-2025 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 testprep +import klayout.pex as pex +import unittest +import sys +import os + +# Tests the basic abilities of the module + +class BasicTest(unittest.TestCase): + + def test_1(self): + self.assertEqual("RExtractor" in pex.__all__, True) + +# run unit tests +if __name__ == '__main__': + suite = unittest.TestSuite() + suite = unittest.TestLoader().loadTestsFromTestCase(BasicTest) + + if not unittest.TextTestRunner(verbosity = 1).run(suite).wasSuccessful(): + sys.exit(1) From e764716bdae81ab9e52bc8ca7f51f6082e8dbe80 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 18 Apr 2025 15:12:30 +0200 Subject: [PATCH 16/58] Preserving the hull of simple polygons during insert in a Region, hence skipping the compression --- src/db/db/dbMutableRegion.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/db/db/dbMutableRegion.cc b/src/db/db/dbMutableRegion.cc index fef42681a..aa1a6b3a2 100644 --- a/src/db/db/dbMutableRegion.cc +++ b/src/db/db/dbMutableRegion.cc @@ -83,7 +83,7 @@ MutableRegion::insert (const db::SimplePolygon &polygon) { if (polygon.vertices () > 0) { db::Polygon poly; - poly.assign_hull (polygon.begin_hull (), polygon.end_hull ()); + poly.assign_hull (polygon.hull ()); do_insert (poly, 0); } } @@ -93,7 +93,7 @@ MutableRegion::insert (const db::SimplePolygonWithProperties &polygon) { if (polygon.vertices () > 0) { db::Polygon poly; - poly.assign_hull (polygon.begin_hull (), polygon.end_hull ()); + poly.assign_hull (polygon.hull ()); do_insert (poly, polygon.properties_id ()); } } From 71620445ee079c6714044ce4dba3d38f44fd5696 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 18 Apr 2025 15:13:29 +0200 Subject: [PATCH 17/58] Polygon#triangulate now returns a 'raw' region, same for SimplePolygon#triangulate. Working on hm_decomposition (tests, bug fixes etc.) --- src/db/db/dbPLCConvexDecomposition.cc | 35 +--- src/db/db/dbPLCConvexDecomposition.h | 13 +- src/db/db/gsiDeclDbPolygon.cc | 249 ++++++++++++++++---------- testdata/ruby/dbPolygonTest.rb | 26 +++ testdata/ruby/dbSimplePolygonTest.rb | 25 +++ 5 files changed, 220 insertions(+), 128 deletions(-) diff --git a/src/db/db/dbPLCConvexDecomposition.cc b/src/db/db/dbPLCConvexDecomposition.cc index 45eda37ed..a6dcff7c4 100644 --- a/src/db/db/dbPLCConvexDecomposition.cc +++ b/src/db/db/dbPLCConvexDecomposition.cc @@ -433,13 +433,8 @@ ConvexDecomposition::decompose (const db::Polygon &poly, const std::vector &vertexes, const ConvexDecompositionParameters ¶meters, const db::CplxTrans &trans) { - // start with a triangulation - TriangulationParameters param; - param.max_area = 0.0; - param.min_b = 0.0; - Triangulation tri (mp_graph); - tri.triangulate (poly, vertexes, param, trans); + tri.triangulate (poly, vertexes, parameters.tri_param, trans); hertel_mehlhorn_decomposition (tri, parameters); } @@ -461,13 +451,8 @@ ConvexDecomposition::decompose (const db::Polygon &poly, const std::vector &vertexes, const ConvexDecompositionParameters ¶meters, const db::DCplxTrans &trans) { - // start with a triangulation - TriangulationParameters param; - param.max_area = 0.0; - param.min_b = 0.0; - Triangulation tri (mp_graph); - tri.triangulate (poly, vertexes, param, trans); + tri.triangulate (poly, vertexes, parameters.tri_param, trans); hertel_mehlhorn_decomposition (tri, parameters); } @@ -495,13 +475,8 @@ ConvexDecomposition::decompose (const db::Region ®ion, const ConvexDecomposit void ConvexDecomposition::decompose (const db::Region ®ion, const ConvexDecompositionParameters ¶meters, const db::CplxTrans &trans) { - // start with a triangulation - TriangulationParameters param; - param.max_area = 0.0; - param.min_b = 0.0; - Triangulation tri (mp_graph); - tri.triangulate (region, param, trans); + tri.triangulate (region, parameters.tri_param, trans); hertel_mehlhorn_decomposition (tri, parameters); } diff --git a/src/db/db/dbPLCConvexDecomposition.h b/src/db/db/dbPLCConvexDecomposition.h index 03a6949bb..1fa9ce805 100644 --- a/src/db/db/dbPLCConvexDecomposition.h +++ b/src/db/db/dbPLCConvexDecomposition.h @@ -25,6 +25,7 @@ #include "dbCommon.h" #include "dbPLC.h" +#include "dbPLCTriangulation.h" #include #include @@ -37,15 +38,21 @@ namespace db namespace plc { -class Triangulation; - struct DB_PUBLIC ConvexDecompositionParameters { ConvexDecompositionParameters () : with_segments (false), split_edges (false), base_verbosity (30) - { } + { + tri_param.max_area = 0.0; + tri_param.min_b = 0.0; + } + + /** + * @brief The parameters used for the triangulation + */ + TriangulationParameters tri_param; /** * @brief Introduce new segments diff --git a/src/db/db/gsiDeclDbPolygon.cc b/src/db/db/gsiDeclDbPolygon.cc index 1a4fa5335..a5cb6f994 100644 --- a/src/db/db/gsiDeclDbPolygon.cc +++ b/src/db/db/gsiDeclDbPolygon.cc @@ -29,44 +29,76 @@ #include "dbPolygonGenerators.h" #include "dbHash.h" #include "dbPLCTriangulation.h" +#include "dbPLCConvexDecomposition.h" namespace gsi { +const std::string hm_docstring = + "The Hertel-Mehlhorn decomposition starts with a Delaunay triangulation of the polygons and recombines the " + "triangles into convex polygons.\n" + "\n" + "The decomposition is controlled by two parameters: 'with_segments' and 'split_edges'.\n" + "\n" + "If 'with_segments' is true (the default), new segments are introduced perpendicular to the edges forming " + "a concave corner. If false, only diagonals (edges connecting original vertexes) are used.\n" + "\n" + "If 'split_edges' is true, the algorithm is allowed to create collinear edges in the output. In this case, " + "the resulting polygons may contain edges that are split into collinear partial edges. Such edges usually recombine " + "into longer edges when processing the polygon further. When such a recombination happens, the edges no " + "longer correspond to original edges or diagonals. When 'split_edges' is false (the default), the resulting " + "polygons will not contain collinear edges, but the decomposition will be constrained to fewer cut lines." + "\n" + "'max_area' and 'min_b' are the corresponding parameters used for the triangulation (see \\delaunay).\n" +; + +const std::string delaunay_docstring = + "Refinement is implemented by Chew's second algorithm. A maximum area can be given. Triangles " + "larger than this area will be split. In addition 'skinny' triangles will be resolved where " + "possible. 'skinny' is defined in terms of shortest edge to circumcircle radius ratio (b). " + "A minimum number for b can be given. A value of 1.0 corresponds to a minimum angle of 30 degree " + "and is usually a good choice. The algorithm is stable up to roughly 1.2 which corresponds to " + "a minimum angle of abouth 37 degree.\n" + "\n" + "The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t.\n" + "\n" + "Picking a value of 0.0 for max_area and min_b will " + "make the implementation skip the refinement step. In that case, the results are identical to " + "the standard constrained Delaunay triangulation.\n" +; + +const std::string dbu_docstring = + "The 'dbu' parameter a numerical scaling parameter. It should be choosen in a way that the polygon dimensions " + "are \"in the order of 1\" (very roughly) after multiplication with the dbu parameter. A value of 0.001 is suitable " + "for polygons with typical dimensions in the order to 1000 DBU. Usually the default value is good enough.\n" +; + template -static db::Region region_from_triangles (const db::plc::Graph &tri, const T &trans) +static db::Region region_from_graph (const db::plc::Graph &plc, const T &trans) { db::Region result; + result.set_merged_semantics (false); - db::Point pts [3]; - - for (auto t = tri.begin (); t != tri.end (); ++t) { - for (int i = 0; i < 3; ++i) { - pts [i] = trans * *t->vertex (i); - } - db::SimplePolygon poly; - poly.assign_hull (pts + 0, pts + 3); - result.insert (poly); + for (auto t = plc.begin (); t != plc.end (); ++t) { + db::DPolygon dp = t->polygon (); + db::SimplePolygon sp; + sp.assign_hull (dp.hull ().begin (), dp.hull ().end (), trans, false); + result.insert (sp); } return result; } template -static std::vector

polygons_from_triangles (const db::plc::Graph &tri, const T &trans) +static std::vector

polygons_from_graph (const db::plc::Graph &plc, const T &trans) { std::vector

result; - result.reserve (tri.num_polygons ()); + result.reserve (plc.num_polygons ()); - typename P::point_type pts [3]; - - for (auto t = tri.begin (); t != tri.end (); ++t) { - for (int i = 0; i < 3; ++i) { - pts [i] = trans * *t->vertex (i); - } - P poly; - poly.assign_hull (pts + 0, pts + 3); - result.push_back (poly); + for (auto t = plc.begin (); t != plc.end (); ++t) { + db::DPolygon dp = t->polygon (); + result.push_back (P ()); + result.back ().assign_hull (dp.hull ().begin (), dp.hull ().end (), trans, false); } return result; @@ -87,7 +119,43 @@ static db::polygon to_polygon (const db::polygon &p) } template -static db::Region triangulate_ipolygon (const P *p, double max_area = 0.0, double min_b = 0.0, double dbu = 0.001) +static db::Region hm_decompose_ipolygon (const P *p, bool with_segments, bool split_edges, double max_area, double min_b, double dbu) +{ + db::plc::Graph plc; + db::plc::ConvexDecomposition decomp (&plc); + db::plc::ConvexDecompositionParameters param; + param.with_segments = with_segments; + param.split_edges = split_edges; + param.tri_param.max_area = max_area; + param.tri_param.min_b = min_b; + + db::CplxTrans trans = db::CplxTrans (dbu) * db::ICplxTrans (db::Trans (db::Point () - p->box ().center ())); + + decomp.decompose (to_polygon (*p), param, trans); + + return region_from_graph (plc, trans.inverted ()); +} + +template +static std::vector

hm_decompose_dpolygon (const P *p, bool with_segments, bool split_edges, double max_area, double min_b) +{ + db::plc::Graph plc; + db::plc::ConvexDecomposition decomp (&plc); + db::plc::ConvexDecompositionParameters param; + param.with_segments = with_segments; + param.split_edges = split_edges; + param.tri_param.max_area = max_area; + param.tri_param.min_b = min_b; + + db::DCplxTrans trans = db::DCplxTrans (db::DTrans (db::DPoint () - p->box ().center ())); + + decomp.decompose (to_polygon (*p), param, trans); + + return polygons_from_graph (plc, trans.inverted ()); +} + +template +static db::Region triangulate_ipolygon (const P *p, double max_area, double min_b, double dbu) { db::plc::Graph tris; db::plc::Triangulation triangulation (&tris); @@ -99,11 +167,11 @@ static db::Region triangulate_ipolygon (const P *p, double max_area = 0.0, doubl triangulation.triangulate (to_polygon (*p), param, trans); - return region_from_triangles (tris, trans.inverted ()); + return region_from_graph (tris, trans.inverted ()); } template -static db::Region triangulate_ipolygon_v (const P *p, const std::vector &vertexes, double max_area = 0.0, double min_b = 0.0, double dbu = 0.001) +static db::Region triangulate_ipolygon_v (const P *p, const std::vector &vertexes, double max_area, double min_b, double dbu) { db::plc::Graph tris; db::plc::Triangulation triangulation (&tris); @@ -115,7 +183,7 @@ static db::Region triangulate_ipolygon_v (const P *p, const std::vector @@ -131,7 +199,7 @@ static std::vector

triangulate_dpolygon (const P *p, double max_area = 0.0, d triangulation.triangulate (to_polygon (*p), param, trans); - return polygons_from_triangles (tris, trans.inverted ()); + return polygons_from_graph (tris, trans.inverted ()); } template @@ -147,7 +215,7 @@ static std::vector

triangulate_dpolygon_v (const P *p, const std::vector (tris, trans.inverted ()); + return polygons_from_graph (tris, trans.inverted ()); } template @@ -884,36 +952,35 @@ Class decl_SimplePolygon ("db", "SimplePolygon", "\n" "This method has been introduced in version 0.18.\n" ) + + method_ext ("hm_decomposition", &hm_decompose_ipolygon, + gsi::arg ("with_segments", true), gsi::arg ("split_edges", false), + gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), + gsi::arg ("dbu", 0.001), + "@brief Performs a Hertel-Mehlhorn convex decomposition.\n" + "\n" + "@return A \\Region holding the polygons of the decomposition.\n" + "The resulting region is in 'no merged semantics' mode, " + "to avoid re-merging of the polygons during following operations.\n" + "\n" + hm_docstring + "\n" + dbu_docstring + "\n" + "This method has been introduced in version 0.30.1." + ) + method_ext ("delaunay", &triangulate_ipolygon, gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), gsi::arg ("dbu", 0.001), "@brief Performs a Delaunay triangulation of the polygon.\n" "\n" "@return A \\Region holding the triangles of the refined, constrained Delaunay triangulation.\n" - "\n" - "Refinement is implemented by Chew's second algorithm. A maximum area can be given. Triangles " - "larger than this area will be split. In addition 'skinny' triangles will be resolved where " - "possible. 'skinny' is defined in terms of shortest edge to circumcircle radius ratio (b). " - "A minimum number for b can be given. A value of 1.0 corresponds to a minimum angle of 30 degree " - "and is usually a good choice. The algorithm is stable up to roughly 1.2 which corresponds to " - "a minimum angle of abouth 37 degree.\n" - "\n" - "The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t.\n" - "\n" - "The area value is given in terms of DBU units. Picking a value of 0.0 for area and min b will " - "make the implementation skip the refinement step. In that case, the results are identical to " - "the standard constrained Delaunay triangulation.\n" - "\n" - "The 'dbu' parameter a numerical scaling parameter. It should be choosen in a way that the polygon dimensions " - "are \"in the order of 1\" (very roughly) after multiplication with the dbu parameter. A value of 0.001 is suitable " - "for polygons with typical dimensions in the order to 1000 DBU. Usually the default value is good enough.\n" - "\n" - "This method has been introduced in version 0.30." + "\n" + delaunay_docstring + "\n" + "The area value is given in terms of DBU units.\n" + "\n" + dbu_docstring + "\n" + "This method has been introduced in version 0.30. Since version 0.30.1, the resulting region is in 'no merged semantics' mode, " + "to avoid re-merging of the triangles during following operations." ) + method_ext ("delaunay", &triangulate_ipolygon_v, gsi::arg ("vertexes"), gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), gsi::arg ("dbu", 0.001), "@brief Performs a Delaunay triangulation of the polygon.\n" "\n" "This variant of the triangulation function accepts an array of additional vertexes for the triangulation.\n" "\n" - "This method has been introduced in version 0.30." + "This method has been introduced in version 0.30. Since version 0.30.1, the resulting region is in 'no merged semantics' mode, " + "to avoid re-merging of the triangles during following operations." ) + simple_polygon_defs::methods (), "@brief A simple polygon class\n" @@ -1010,24 +1077,20 @@ Class decl_DSimplePolygon ("db", "DSimplePolygon", "\n" "This method has been introduced in version 0.25.\n" ) + + method_ext ("hm_decomposition", &hm_decompose_dpolygon, + gsi::arg ("with_segments", true), gsi::arg ("split_edges", false), + gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), + "@brief Performs a Hertel-Mehlhorn convex decomposition.\n" + "\n" + "@return An array holding the polygons of the decomposition.\n" + "\n" + hm_docstring + "\n" + "This method has been introduced in version 0.30.1." + ) + method_ext ("delaunay", &triangulate_dpolygon, gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), "@brief Performs a Delaunay triangulation of the polygon.\n" "\n" "@return An array of triangular polygons of the refined, constrained Delaunay triangulation.\n" - "\n" - "Refinement is implemented by Chew's second algorithm. A maximum area can be given. Triangles " - "larger than this area will be split. In addition 'skinny' triangles will be resolved where " - "possible. 'skinny' is defined in terms of shortest edge to circumcircle radius ratio (b). " - "A minimum number for b can be given. A value of 1.0 corresponds to a minimum angle of 30 degree " - "and is usually a good choice. The algorithm is stable up to roughly 1.2 which corresponds to " - "a minimum angle of abouth 37 degree.\n" - "\n" - "The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t.\n" - "\n" - "Picking a value of 0.0 for max area and min b will " - "make the implementation skip the refinement step. In that case, the results are identical to " - "the standard constrained Delaunay triangulation.\n" - "\n" + "\n" + delaunay_docstring + "\n" "This method has been introduced in version 0.30." ) + method_ext ("delaunay", &triangulate_dpolygon_v, gsi::arg ("vertexes"), gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), @@ -2211,36 +2274,36 @@ Class decl_Polygon ("db", "Polygon", "\n" "This method was introduced in version 0.18.\n" ) + + method_ext ("hm_decomposition", &hm_decompose_ipolygon, + gsi::arg ("with_segments", true), gsi::arg ("split_edges", false), + gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), + gsi::arg ("dbu", 0.001), + "@brief Performs a Hertel-Mehlhorn convex decomposition.\n" + "\n" + "@return A \\Region holding the polygons of the decomposition.\n" + "\n" + "The resulting region is in 'no merged semantics' mode, " + "to avoid re-merging of the polygons during following operations.\n" + "\n" + hm_docstring + "\n" + dbu_docstring + "\n" + "This method has been introduced in version 0.30.1." + ) + method_ext ("delaunay", &triangulate_ipolygon, gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), gsi::arg ("dbu", 0.001), "@brief Performs a Delaunay triangulation of the polygon.\n" "\n" "@return A \\Region holding the triangles of the refined, constrained Delaunay triangulation.\n" - "\n" - "Refinement is implemented by Chew's second algorithm. A maximum area can be given. Triangles " - "larger than this area will be split. In addition 'skinny' triangles will be resolved where " - "possible. 'skinny' is defined in terms of shortest edge to circumcircle radius ratio (b). " - "A minimum number for b can be given. A value of 1.0 corresponds to a minimum angle of 30 degree " - "and is usually a good choice. The algorithm is stable up to roughly 1.2 which corresponds to " - "a minimum angle of abouth 37 degree.\n" - "\n" - "The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t.\n" - "\n" - "The area value is given in terms of DBU units. Picking a value of 0.0 for area and min b will " - "make the implementation skip the refinement step. In that case, the results are identical to " - "the standard constrained Delaunay triangulation.\n" - "\n" - "The 'dbu' parameter a numerical scaling parameter. It should be choosen in a way that the polygon dimensions " - "are \"in the order of 1\" (very roughly) after multiplication with the dbu parameter. A value of 0.001 is suitable " - "for polygons with typical dimensions in the order to 1000 DBU. Usually the default value is good enough.\n" - "\n" - "This method has been introduced in version 0.30." + "\n" + delaunay_docstring + "\n" + "The area value is given in terms of DBU units.\n" + "\n" + dbu_docstring + "\n" + "This method has been introduced in version 0.30. Since version 0.30.1, the resulting region is in 'no merged semantics' mode, " + "to avoid re-merging of the triangles during following operations." ) + method_ext ("delaunay", &triangulate_ipolygon_v, gsi::arg ("vertexes"), gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), gsi::arg ("dbu", 0.001), "@brief Performs a Delaunay triangulation of the polygon.\n" "\n" "This variant of the triangulation function accepts an array of additional vertexes for the triangulation.\n" "\n" - "This method has been introduced in version 0.30." + "This method has been introduced in version 0.30. Since version 0.30.1, the resulting region is in 'no merged semantics' mode, " + "to avoid re-merging of the triangles during following operations." ) + polygon_defs::methods (), "@brief A polygon class\n" @@ -2364,24 +2427,20 @@ Class decl_DPolygon ("db", "DPolygon", "\n" "This method has been introduced in version 0.25.\n" ) + + method_ext ("hm_decomposition", &hm_decompose_dpolygon, + gsi::arg ("with_segments", true), gsi::arg ("split_edges", false), + gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), + "@brief Performs a Hertel-Mehlhorn convex decomposition.\n" + "\n" + "@return An array holding the polygons of the decomposition.\n" + "\n" + hm_docstring + "\n" + "This method has been introduced in version 0.30.1." + ) + method_ext ("delaunay", &triangulate_dpolygon, gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), "@brief Performs a Delaunay triangulation of the polygon.\n" "\n" "@return An array of triangular polygons of the refined, constrained Delaunay triangulation.\n" - "\n" - "Refinement is implemented by Chew's second algorithm. A maximum area can be given. Triangles " - "larger than this area will be split. In addition 'skinny' triangles will be resolved where " - "possible. 'skinny' is defined in terms of shortest edge to circumcircle radius ratio (b). " - "A minimum number for b can be given. A value of 1.0 corresponds to a minimum angle of 30 degree " - "and is usually a good choice. The algorithm is stable up to roughly 1.2 which corresponds to " - "a minimum angle of abouth 37 degree.\n" - "\n" - "The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t.\n" - "\n" - "Picking a value of 0.0 for max area and min b will " - "make the implementation skip the refinement step. In that case, the results are identical to " - "the standard constrained Delaunay triangulation.\n" - "\n" + "\n" + delaunay_docstring + "\n" "This method has been introduced in version 0.30." ) + method_ext ("delaunay", &triangulate_dpolygon_v, gsi::arg ("vertexes"), gsi::arg ("max_area", 0.0), gsi::arg ("min_b", 0.0), diff --git a/testdata/ruby/dbPolygonTest.rb b/testdata/ruby/dbPolygonTest.rb index 3ac9c74e4..c9de10f8e 100644 --- a/testdata/ruby/dbPolygonTest.rb +++ b/testdata/ruby/dbPolygonTest.rb @@ -1006,6 +1006,32 @@ class DBPolygon_TestClass < TestBase end + def sorted_polygons(arr) + arr.each.collect { |p| p.downcast.to_s }.sort.join(";") + end + + def sorted_dpolygons(arr) + arr.each.collect { |p| p.to_s }.sort.join(";") + end + + def test_hm_decomposition + + p = RBA::Polygon::new([ [0, 0], [0, 100], [1000, 100], [1000, 1000], [1100, 1000], [1100, 100], [2200, 100], [2200, 0] ]) + assert_equal(sorted_polygons(p.hm_decomposition(false, false)), "(0,0;0,100;1000,100);(0,0;1000,100;1100,100);(0,0;1100,100;2200,100;2200,0);(1000,100;1000,1000;1100,1000;1100,100)") + assert_equal(sorted_polygons(p.hm_decomposition(true, false)), "(0,0;0,100;1000,100;1000,0);(1000,0;1000,100;1100,100;1100,0);(1000,100;1000,1000;1100,1000;1100,100);(1100,0;1100,100;2200,100;2200,0)") + assert_equal(sorted_polygons(p.hm_decomposition(false, true)), "(0,0;0,100;1000,100;1100,100;2200,100;2200,0);(1000,100;1000,1000;1100,1000;1100,100)") + assert_equal(sorted_polygons(p.hm_decomposition(true, true)), "(0,0;0,100;1000,100;1000,0);(1000,0;1000,100;1000,1000;1100,1000;1100,100;1100,0);(1100,0;1100,100;2200,100;2200,0)") + assert_equal(sorted_polygons(p.hm_decomposition(false, false, 0.0, 0.5)), "(0,0;0,100;500,100;1000,100;1100,0;825,0;550,0;275,0);(1000,100;1000,550;1000,775;1000,1000;1100,1000;1100,550;1100,325;1100,100);(1100,0;1000,100;1100,100);(1100,0;1100,100;1650,100;1925,100;2200,100;2200,0;1650,0;1375,0)") + + p = RBA::DPolygon::new([ [0, 0], [0, 100], [1000, 100], [1000, 1000], [1100, 1000], [1100, 100], [2200, 100], [2200, 0] ]) + assert_equal(sorted_dpolygons(p.hm_decomposition(false, false)), "(0,0;0,100;1000,100);(0,0;1000,100;1100,100);(0,0;1100,100;2200,100;2200,0);(1000,100;1000,1000;1100,1000;1100,100)") + assert_equal(sorted_dpolygons(p.hm_decomposition(true, false)), "(0,0;0,100;1000,100;1000,0);(1000,0;1000,100;1100,100;1100,0);(1000,100;1000,1000;1100,1000;1100,100);(1100,0;1100,100;2200,100;2200,0)") + assert_equal(sorted_dpolygons(p.hm_decomposition(false, true)), "(0,0;0,100;1000,100;1100,100;2200,100;2200,0);(1000,100;1000,1000;1100,1000;1100,100)") + assert_equal(sorted_dpolygons(p.hm_decomposition(true, true)), "(0,0;0,100;1000,100;1000,0);(1000,0;1000,100;1000,1000;1100,1000;1100,100;1100,0);(1100,0;1100,100;2200,100;2200,0)") + assert_equal(sorted_dpolygons(p.hm_decomposition(false, false, 0.0, 0.5)), "(0,0;0,100;500,100;1000,100;1100,0;825,0;550,0;275,0);(1000,100;1000,550;1000,775;1000,1000;1100,1000;1100,550;1100,325;1100,100);(1100,0;1000,100;1100,100);(1100,0;1100,100;1650,100;1925,100;2200,100;2200,0;1650,0;1375,0)") + + end + end load("test_epilogue.rb") diff --git a/testdata/ruby/dbSimplePolygonTest.rb b/testdata/ruby/dbSimplePolygonTest.rb index 268d675ca..ee30323fe 100644 --- a/testdata/ruby/dbSimplePolygonTest.rb +++ b/testdata/ruby/dbSimplePolygonTest.rb @@ -438,6 +438,31 @@ class DBSimplePolygon_TestClass < TestBase end + def sorted_polygons(arr) + arr.each.collect { |p| p.downcast.to_s }.sort.join(";") + end + + def sorted_dpolygons(arr) + arr.each.collect { |p| p.to_s }.sort.join(";") + end + + def test_hm_decomposition + + p = RBA::SimplePolygon::new([ [0, 0], [0, 100], [1000, 100], [1000, 1000], [1100, 1000], [1100, 100], [2200, 100], [2200, 0] ]) + assert_equal(sorted_polygons(p.hm_decomposition(false, false)), "(0,0;0,100;1000,100);(0,0;1000,100;1100,100);(0,0;1100,100;2200,100;2200,0);(1000,100;1000,1000;1100,1000;1100,100)") + assert_equal(sorted_polygons(p.hm_decomposition(true, false)), "(0,0;0,100;1000,100;1000,0);(1000,0;1000,100;1100,100;1100,0);(1000,100;1000,1000;1100,1000;1100,100);(1100,0;1100,100;2200,100;2200,0)") + assert_equal(sorted_polygons(p.hm_decomposition(false, true)), "(0,0;0,100;1000,100;1100,100;2200,100;2200,0);(1000,100;1000,1000;1100,1000;1100,100)") + assert_equal(sorted_polygons(p.hm_decomposition(true, true)), "(0,0;0,100;1000,100;1000,0);(1000,0;1000,100;1000,1000;1100,1000;1100,100;1100,0);(1100,0;1100,100;2200,100;2200,0)") + assert_equal(sorted_polygons(p.hm_decomposition(false, false, 0.0, 0.5)), "(0,0;0,100;500,100;1000,100;1100,0;825,0;550,0;275,0);(1000,100;1000,550;1000,775;1000,1000;1100,1000;1100,550;1100,325;1100,100);(1100,0;1000,100;1100,100);(1100,0;1100,100;1650,100;1925,100;2200,100;2200,0;1650,0;1375,0)") + + p = RBA::DSimplePolygon::new([ [0, 0], [0, 100], [1000, 100], [1000, 1000], [1100, 1000], [1100, 100], [2200, 100], [2200, 0] ]) + assert_equal(sorted_dpolygons(p.hm_decomposition(false, false)), "(0,0;0,100;1000,100);(0,0;1000,100;1100,100);(0,0;1100,100;2200,100;2200,0);(1000,100;1000,1000;1100,1000;1100,100)") + assert_equal(sorted_dpolygons(p.hm_decomposition(true, false)), "(0,0;0,100;1000,100;1000,0);(1000,0;1000,100;1100,100;1100,0);(1000,100;1000,1000;1100,1000;1100,100);(1100,0;1100,100;2200,100;2200,0)") + assert_equal(sorted_dpolygons(p.hm_decomposition(false, true)), "(0,0;0,100;1000,100;1100,100;2200,100;2200,0);(1000,100;1000,1000;1100,1000;1100,100)") + assert_equal(sorted_dpolygons(p.hm_decomposition(true, true)), "(0,0;0,100;1000,100;1000,0);(1000,0;1000,100;1000,1000;1100,1000;1100,100;1100,0);(1100,0;1100,100;2200,100;2200,0)") + assert_equal(sorted_dpolygons(p.hm_decomposition(false, false, 0.0, 0.5)), "(0,0;0,100;500,100;1000,100;1100,0;825,0;550,0;275,0);(1000,100;1000,550;1000,775;1000,1000;1100,1000;1100,550;1100,325;1100,100);(1100,0;1000,100;1100,100);(1100,0;1100,100;1650,100;1925,100;2200,100;2200,0;1650,0;1375,0)") + + end end load("test_epilogue.rb") From 8a122c8a7d5a4e7776f22689d8470f6687186e7b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 18 Apr 2025 23:29:29 +0200 Subject: [PATCH 18/58] WIP: bug fixes, one more test --- scripts/make_stubs.sh | 3 + src/db/db/dbPLCConvexDecomposition.cc | 12 +- src/db/db/dbPLCConvexDecomposition.h | 3 + src/db/db/dbPLCTriangulation.cc | 8 +- src/db/db/dbPLCTriangulation.h | 8 +- .../dbPLCConvexDecompositionTests.cc | 34 ++++ src/pex/pex/gsiDeclRExtractor.cc | 6 +- src/pex/pex/pexRExtractor.cc | 135 +++++++++++++ src/pex/pex/pexRExtractor.h | 136 ++++++++++++- src/pymod/distutils_src/klayout/dbcore.pyi | 190 +++++++++++++----- src/pymod/distutils_src/klayout/pexcore.pyi | 5 + src/pymod/distutils_src/klayout/tlcore.pyi | 4 +- testdata/algo/hm_decomposition_au5.gds | Bin 0 -> 1314 bytes 13 files changed, 479 insertions(+), 65 deletions(-) create mode 100644 testdata/algo/hm_decomposition_au5.gds diff --git a/scripts/make_stubs.sh b/scripts/make_stubs.sh index e9f5bad03..68e39f1c2 100755 --- a/scripts/make_stubs.sh +++ b/scripts/make_stubs.sh @@ -57,6 +57,9 @@ $python $inst/stubgen.py tl >$pyi_srcdir/tlcore.pyi echo "Generating stubs for db .." $python $inst/stubgen.py db tl,lay,rdb >$pyi_srcdir/dbcore.pyi +echo "Generating stubs for pex .." +$python $inst/stubgen.py pex tl,db >$pyi_srcdir/pexcore.pyi + echo "Generating stubs for rdb .." $python $inst/stubgen.py rdb tl,db >$pyi_srcdir/rdbcore.pyi diff --git a/src/db/db/dbPLCConvexDecomposition.cc b/src/db/db/dbPLCConvexDecomposition.cc index a6dcff7c4..2723eb796 100644 --- a/src/db/db/dbPLCConvexDecomposition.cc +++ b/src/db/db/dbPLCConvexDecomposition.cc @@ -146,7 +146,7 @@ ConvexDecomposition::collect_concave_vertexes (std::vector &conca // and outgoing edge. Edge *start_segment = segment; - Vertex *vto = segment->right () ? segment->v2 () : segment->v1 (); + Vertex *vto = (segment->right () && ! segment->right ()->is_outside ()) ? segment->v2 () : segment->v1 (); do { @@ -338,7 +338,9 @@ ConvexDecomposition::hertel_mehlhorn_decomposition (Triangulation &tris, const C } for (auto i = angles_and_edges.begin (); i != angles_and_edges.end (); ++i) { - essential_edges.insert (i->second); + if (i->second) { + essential_edges.insert (i->second); + } } } @@ -347,7 +349,9 @@ ConvexDecomposition::hertel_mehlhorn_decomposition (Triangulation &tris, const C std::unordered_set left_triangles; for (auto it = mp_graph->begin (); it != mp_graph->end (); ++it) { - left_triangles.insert (it.operator-> ()); + if (! it->is_outside ()) { + left_triangles.insert (it.operator-> ()); + } } std::list > polygons; @@ -385,7 +389,7 @@ ConvexDecomposition::hertel_mehlhorn_decomposition (Triangulation &tris, const C ivs.insert (e->v2 ()); } - if (! qq || essential_edges.find (e) != essential_edges.end ()) { + if (! qq || qq->is_outside () || essential_edges.find (e) != essential_edges.end ()) { edges.insert (const_cast (e)); // TODO: ugly const_cast } else if (left_triangles.find (qq) != left_triangles.end ()) { next_queue.push_back (qq); diff --git a/src/db/db/dbPLCConvexDecomposition.h b/src/db/db/dbPLCConvexDecomposition.h index 1fa9ce805..6b0effc43 100644 --- a/src/db/db/dbPLCConvexDecomposition.h +++ b/src/db/db/dbPLCConvexDecomposition.h @@ -47,6 +47,9 @@ struct DB_PUBLIC ConvexDecompositionParameters { tri_param.max_area = 0.0; tri_param.min_b = 0.0; + + // Needed for the algorithm - don't change this + tri_param.remove_outside_triangles = false; } /** diff --git a/src/db/db/dbPLCTriangulation.cc b/src/db/db/dbPLCTriangulation.cc index d148656a6..3c9371fe4 100644 --- a/src/db/db/dbPLCTriangulation.cc +++ b/src/db/db/dbPLCTriangulation.cc @@ -1522,7 +1522,9 @@ Triangulation::refine (const TriangulationParameters ¶meters) if (parameters.min_b < db::epsilon && parameters.max_area < db::epsilon && parameters.max_area_border < db::epsilon) { // no refinement requested - we're done. - remove_outside_triangles (); + if (parameters.remove_outside_triangles) { + remove_outside_triangles (); + } return; } @@ -1690,7 +1692,9 @@ Triangulation::refine (const TriangulationParameters ¶meters) } - remove_outside_triangles (); + if (parameters.remove_outside_triangles) { + remove_outside_triangles (); + } } } // namespace plc diff --git a/src/db/db/dbPLCTriangulation.h b/src/db/db/dbPLCTriangulation.h index af17aa876..ea0830b00 100644 --- a/src/db/db/dbPLCTriangulation.h +++ b/src/db/db/dbPLCTriangulation.h @@ -46,7 +46,8 @@ struct DB_PUBLIC TriangulationParameters max_area_border (0.0), max_iterations (std::numeric_limits::max ()), base_verbosity (30), - mark_triangles (false) + mark_triangles (false), + remove_outside_triangles (true) { } /** @@ -93,6 +94,11 @@ struct DB_PUBLIC TriangulationParameters * Bit 2: non-Delaunay (in the strict sense) */ bool mark_triangles; + + /** + * @brief If false, the outside triangles are not removed after triangulation + */ + bool remove_outside_triangles; }; /** diff --git a/src/db/unit_tests/dbPLCConvexDecompositionTests.cc b/src/db/unit_tests/dbPLCConvexDecompositionTests.cc index 2cb1e1a70..5ab556ab8 100644 --- a/src/db/unit_tests/dbPLCConvexDecompositionTests.cc +++ b/src/db/unit_tests/dbPLCConvexDecompositionTests.cc @@ -153,3 +153,37 @@ TEST(internal_vertex) EXPECT_EQ (++p == plc.end (), true); } +TEST(problematic_polygon) +{ + db::Point contour[] = { + db::Point (14590, 990), + db::Point (6100, 990), + db::Point (7360, 4450), + db::Point (2280, 4450), + db::Point (2280, 6120), + db::Point (7360, 6120), + db::Point (8760, 7490), + db::Point (13590, 17100), + db::Point (10280, 6120), + db::Point (26790, 13060), + db::Point (41270, 970) + }; + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + double dbu = 0.001; + + db::plc::ConvexDecompositionParameters param; + param.with_segments = true; + param.split_edges = false; + + db::plc::Graph plc; + TestableConvexDecomposition decomp (&plc); + + decomp.decompose (poly, param, dbu); + + std::unique_ptr ly (plc.to_layout ()); + db::compare_layouts (_this, *ly, tl::testdata () + "/algo/hm_decomposition_au5.gds"); +} + diff --git a/src/pex/pex/gsiDeclRExtractor.cc b/src/pex/pex/gsiDeclRExtractor.cc index 983ec13f6..a0bc54078 100644 --- a/src/pex/pex/gsiDeclRExtractor.cc +++ b/src/pex/pex/gsiDeclRExtractor.cc @@ -28,13 +28,13 @@ namespace gsi { // @@@ -static pex::RExtractor *new_sqc_rextractor () +static pex::RExtractor *new_sqc_rextractor (double dbu) { - return new pex::RExtractor (); + return new pex::SquareCountingRExtractor (dbu); } Class decl_RExtractor ("pex", "RExtractor", - gsi::constructor ("square_counting", &new_sqc_rextractor, + gsi::constructor ("square_counting", &new_sqc_rextractor, gsi::arg ("dbu"), "@brief Creates a square counting R extractor\n" ), "@brief A base class for the R extractor\n" diff --git a/src/pex/pex/pexRExtractor.cc b/src/pex/pex/pexRExtractor.cc index ca67f5c59..5d7a30eab 100644 --- a/src/pex/pex/pexRExtractor.cc +++ b/src/pex/pex/pexRExtractor.cc @@ -26,11 +26,146 @@ namespace pex { +RNetwork::RNetwork () +{ + // .. nothing yet .. +} + +RNetwork::~RNetwork () +{ + clear (); +} + +void +RNetwork::clear () +{ + m_elements.clear (); // must come before m_nodes + m_nodes.clear (); + m_elements_by_nodes.clear (); + m_nodes_by_type.clear (); +} + +std::map, RElement *> m_elements_by_nodes; +std::map, RNode *> m_nodes; + +RNode * +RNetwork::create_node (RNode::node_type type, unsigned int port_index) +{ + if (type != RNode::INTERNAL) { + + auto i = m_nodes_by_type.find (std::make_pair (type, port_index)); + if (i != m_nodes_by_type.end ()) { + + return i->second; + + } else { + + RNode *new_node = new RNode (type, db::DBox (), port_index); + m_nodes.push_back (new_node); + m_nodes_by_type.insert (std::make_pair (std::make_pair (type, port_index), new_node)); + + return new_node; + } + + } else { + + RNode *new_node = new RNode (type, db::DBox (), port_index); + m_nodes.push_back (new_node); + return new_node; + + } +} + +RElement * +RNetwork::create_element (double conductivity, RNode *a, RNode *b) +{ + auto i = m_elements_by_nodes.find (std::make_pair (a, b)); + if (i != m_elements_by_nodes.end ()) { + + i->second->conductivity += conductivity; + return i->second; + + } else { + + RElement *element = new RElement (conductivity, a, b); + a->elements.push_back (element); + element->m_ia = --a->elements.end (); + b->elements.push_back (element); + element->m_ia = --b->elements.end (); + + m_elements_by_nodes.insert (std::make_pair (std::make_pair (a, b), element)); + return element; + + } +} + +void +RNetwork::remove_node (RNode *node) +{ + tl_assert (node->type == RNode::INTERNAL); + while (! node->elements.empty ()) { + remove_element (const_cast (node->elements.front ())); + } + delete node; +} + +void +RNetwork::remove_element (RElement *element) +{ + RNode *a = const_cast (element->a); + RNode *b = const_cast (element->b); + + delete element; + + if (a && a->type == RNode::INTERNAL && a->elements.empty ()) { + delete a; + } + if (b && b->type == RNode::INTERNAL && b->elements.empty ()) { + delete b; + } +} + + RExtractor::RExtractor () { // .. nothing yet .. } +RExtractor::~RExtractor () +{ + // .. nothing yet .. +} + + +SquareCountingRExtractor::SquareCountingRExtractor (double dbu) +{ + m_dbu = dbu; + + m_decomp_param.split_edges = true; + m_decomp_param.with_segments = false; +} + +void +SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector &vertex_ports, const std::vector &polygon_ports, RNetwork &rnetwork) +{ + db::CplxTrans trans = db::CplxTrans (m_dbu) * db::ICplxTrans (db::Trans (db::Point () - polygon.box ().center ())); + auto inv_trans = trans.inverted (); + + db::plc::Graph plc; + + db::plc::ConvexDecomposition decomp (&plc); + decomp.decompose (polygon, vertex_ports, m_decomp_param, trans); + + std::vector > decomp_polygons; + for (auto p = plc.begin (); p != plc.end (); ++p) { + // @@@decomp_polygons.push_back (db::Polygon ()); + // @@@decomp_polygons.back ().first = inv_trans * p->polygon (); + } + + // @@@ use box_scanner to find interactions between polygon_ports and decomp_polygons + +} + } diff --git a/src/pex/pex/pexRExtractor.h b/src/pex/pex/pexRExtractor.h index ddc775927..dccc95616 100644 --- a/src/pex/pex/pexRExtractor.h +++ b/src/pex/pex/pexRExtractor.h @@ -24,11 +24,112 @@ #define HDR_pexRExtractor #include "pexCommon.h" + +#include "dbPolygon.h" +#include "dbPLC.h" +#include "dbPLCConvexDecomposition.h" // @@@ +#include "dbPLCTriangulation.h" // @@@ +#include "tlList.h" + #include +#include namespace pex { +class RElement; +class RNode; + +struct PEX_PUBLIC RNode + : public tl::list_node +{ +public: + enum node_type { + INTERNAL, + VERTEX_PORT, + POLYGON_PORT + }; + + node_type type; + db::DBox location; + unsigned int port_index; + mutable std::list elements; + +protected: + friend class RNetwork; + friend class tl::list_impl; + + RNode (node_type _type, const db::DBox &_location, unsigned int _port_index) + : type (_type), location (_location), port_index (_port_index) + { } + + ~RNode () { } + +private: + RNode (const RNode &other); + RNode &operator= (const RNode &other); +}; + +struct PEX_PUBLIC RElement + : public tl::list_node +{ + double conductivity; + const RNode *a, *b; + + double resistance () const + { + return 1.0 / conductivity; + } + +protected: + friend class RNetwork; + friend class tl::list_impl; + + RElement (double _conductivity, const RNode *_a, const RNode *_b) + : conductivity (_conductivity), a (_a), b (_b) + { } + + ~RElement () + { + if (a) { + a->elements.erase (m_ia); + } + if (b) { + b->elements.erase (m_ib); + } + a = b = 0; + } + + std::list::iterator m_ia, m_ib; + +private: + RElement (const RElement &other); + RElement &operator= (const RElement &other); +}; + +class PEX_PUBLIC RNetwork +{ +public: + RNetwork (); + ~RNetwork (); + + RNode *create_node (RNode::node_type type, unsigned int port_index); + RElement *create_element (double conductivity, RNode *a, RNode *b); + void remove_element (RElement *element); + void remove_node (RNode *node); + void clear (); + +private: + tl::list m_nodes; + tl::list m_elements; + std::map, RElement *> m_elements_by_nodes; + std::map, RNode *> m_nodes_by_type; + + RNetwork (const RNetwork &); + RNetwork &operator= (const RNetwork &); +}; + + /** * @brief A base class for an resistance extractor * @@ -38,9 +139,42 @@ namespace pex * Ports are points or polygons that define the connection * points to the network. */ -struct PEX_PUBLIC RExtractor +class PEX_PUBLIC RExtractor { +public: RExtractor (); + virtual ~RExtractor (); + + virtual void extract (const db::Polygon &polygon, const std::vector &vertex_ports, const std::vector &polygon_ports, RNetwork &rnetwork) = 0; +}; + +// @@@ +class PEX_PUBLIC SquareCountingRExtractor + : public RExtractor +{ +public: + SquareCountingRExtractor (double dbu); + + db::plc::ConvexDecompositionParameters &decomposition_parameters () + { + return m_decomp_param; + } + + void set_dbu (double dbu) + { + m_dbu = dbu; + } + + double dbu () const + { + return m_dbu; + } + + virtual void extract (const db::Polygon &polygon, const std::vector &vertex_ports, const std::vector &polygon_ports, RNetwork &rnetwork); + +private: + db::plc::ConvexDecompositionParameters m_decomp_param; + double m_dbu; }; } diff --git a/src/pymod/distutils_src/klayout/dbcore.pyi b/src/pymod/distutils_src/klayout/dbcore.pyi index a9d4ed50c..989021861 100644 --- a/src/pymod/distutils_src/klayout/dbcore.pyi +++ b/src/pymod/distutils_src/klayout/dbcore.pyi @@ -13813,7 +13813,7 @@ class DPolygon: The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t. - Picking a value of 0.0 for max area and min b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation. + Picking a value of 0.0 for max_area and min_b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation. This method has been introduced in version 0.30. """ @@ -13912,6 +13912,24 @@ class DPolygon: This method has been introduced in version 0.25. """ ... + def hm_decomposition(self, with_segments: Optional[bool] = ..., split_edges: Optional[bool] = ..., max_area: Optional[float] = ..., min_b: Optional[float] = ...) -> List[DPolygon]: + r""" + @brief Performs a Hertel-Mehlhorn convex decomposition. + + @return An array holding the polygons of the decomposition. + + The Hertel-Mehlhorn decomposition starts with a Delaunay triangulation of the polygons and recombines the triangles into convex polygons. + + The decomposition is controlled by two parameters: 'with_segments' and 'split_edges'. + + If 'with_segments' is true (the default), new segments are introduced perpendicular to the edges forming a concave corner. If false, only diagonals (edges connecting original vertexes) are used. + + If 'split_edges' is true, the algorithm is allowed to create collinear edges in the output. In this case, the resulting polygons may contain edges that are split into collinear partial edges. Such edges usually recombine into longer edges when processing the polygon further. When such a recombination happens, the edges no longer correspond to original edges or diagonals. When 'split_edges' is false (the default), the resulting polygons will not contain collinear edges, but the decomposition will be constrained to fewer cut lines. + 'max_area' and 'min_b' are the corresponding parameters used for the triangulation (see \delaunay). + + This method has been introduced in version 0.30.1. + """ + ... def holes(self) -> int: r""" @brief Returns the number of holes @@ -14926,7 +14944,7 @@ class DSimplePolygon: The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t. - Picking a value of 0.0 for max area and min b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation. + Picking a value of 0.0 for max_area and min_b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation. This method has been introduced in version 0.30. """ @@ -15003,6 +15021,24 @@ class DSimplePolygon: This method has been introduced in version 0.25. """ ... + def hm_decomposition(self, with_segments: Optional[bool] = ..., split_edges: Optional[bool] = ..., max_area: Optional[float] = ..., min_b: Optional[float] = ...) -> List[DSimplePolygon]: + r""" + @brief Performs a Hertel-Mehlhorn convex decomposition. + + @return An array holding the polygons of the decomposition. + + The Hertel-Mehlhorn decomposition starts with a Delaunay triangulation of the polygons and recombines the triangles into convex polygons. + + The decomposition is controlled by two parameters: 'with_segments' and 'split_edges'. + + If 'with_segments' is true (the default), new segments are introduced perpendicular to the edges forming a concave corner. If false, only diagonals (edges connecting original vertexes) are used. + + If 'split_edges' is true, the algorithm is allowed to create collinear edges in the output. In this case, the resulting polygons may contain edges that are split into collinear partial edges. Such edges usually recombine into longer edges when processing the polygon further. When such a recombination happens, the edges no longer correspond to original edges or diagonals. When 'split_edges' is false (the default), the resulting polygons will not contain collinear edges, but the decomposition will be constrained to fewer cut lines. + 'max_area' and 'min_b' are the corresponding parameters used for the triangulation (see \delaunay). + + This method has been introduced in version 0.30.1. + """ + ... def inside(self, p: DPoint) -> bool: r""" @brief Gets a value indicating whether the given point is inside the polygon @@ -34920,11 +34956,11 @@ class Instance: Starting with version 0.25 the displacement is of vector type. Setter: - @brief Sets the displacement vector for the 'b' axis in micrometer units + @brief Sets the displacement vector for the 'b' axis - Like \b= with an integer displacement, this method will set the displacement vector but it accepts a vector in micrometer units that is of \DVector type. The vector will be translated to database units internally. + If the instance was not an array instance before it is made one. - This method has been introduced in version 0.25. + This method has been introduced in version 0.23. Starting with version 0.25 the displacement is of vector type. """ cell: Cell r""" @@ -34956,10 +34992,10 @@ class Instance: Getter: @brief Gets the basic \CellInstArray object associated with this instance reference. Setter: - @brief Changes the \CellInstArray object to the given one. - This method replaces the instance by the given CellInstArray object. + @brief Returns the basic cell instance array object by giving a micrometer unit object. + This method replaces the instance by the given CellInstArray object and it internally transformed into database units. - This method has been introduced in version 0.22 + This method has been introduced in version 0.25 """ cplx_trans: ICplxTrans r""" @@ -34967,10 +35003,9 @@ class Instance: @brief Gets the complex transformation of the instance or the first instance in the array This method is always valid compared to \trans, since simple transformations can be expressed as complex transformations as well. Setter: - @brief Sets the complex transformation of the instance or the first instance in the array (in micrometer units) - This method sets the transformation the same way as \cplx_trans=, but the displacement of this transformation is given in micrometer units. It is internally translated into database units. + @brief Sets the complex transformation of the instance or the first instance in the array - This method has been introduced in version 0.25. + This method has been introduced in version 0.23. """ da: DVector r""" @@ -46700,17 +46735,17 @@ class NetTerminalRef: @overload def device(self) -> Device: r""" - @brief Gets the device reference (non-const version). + @brief Gets the device reference. Gets the device object that this connection is made to. - - This constness variant has been introduced in version 0.26.8 """ ... @overload def device(self) -> Device: r""" - @brief Gets the device reference. + @brief Gets the device reference (non-const version). Gets the device object that this connection is made to. + + This constness variant has been introduced in version 0.26.8 """ ... def device_class(self) -> DeviceClass: @@ -47722,17 +47757,26 @@ class Netlist: @overload def circuit_by_name(self, name: str) -> Circuit: r""" - @brief Gets the circuit object for a given name. + @brief Gets the circuit object for a given name (const version). If the name is not a valid circuit name, nil is returned. + + This constness variant has been introduced in version 0.26.8. """ ... @overload def circuit_by_name(self, name: str) -> Circuit: r""" - @brief Gets the circuit object for a given name (const version). + @brief Gets the circuit object for a given name. If the name is not a valid circuit name, nil is returned. + """ + ... + @overload + def circuits_by_name(self, name_pattern: str) -> List[Circuit]: + r""" + @brief Gets the circuit objects for a given name filter. + The name filter is a glob pattern. This method will return all \Circuit objects matching the glob pattern. - This constness variant has been introduced in version 0.26.8. + This method has been introduced in version 0.26.4. """ ... @overload @@ -47745,15 +47789,6 @@ class Netlist: This constness variant has been introduced in version 0.26.8. """ ... - @overload - def circuits_by_name(self, name_pattern: str) -> List[Circuit]: - r""" - @brief Gets the circuit objects for a given name filter. - The name filter is a glob pattern. This method will return all \Circuit objects matching the glob pattern. - - This method has been introduced in version 0.26.4. - """ - ... def combine_devices(self) -> None: r""" @brief Combines devices where possible @@ -47996,7 +48031,7 @@ class Netlist: @overload def top_circuit(self) -> Circuit: r""" - @brief Gets the top circuit. + @brief Gets the top circuit (const version). This method will return nil, if there is no top circuit. It will raise an error, if there is more than a single top circuit. This convenience method has been added in version 0.29.5. @@ -48005,7 +48040,7 @@ class Netlist: @overload def top_circuit(self) -> Circuit: r""" - @brief Gets the top circuit (const version). + @brief Gets the top circuit. This method will return nil, if there is no top circuit. It will raise an error, if there is more than a single top circuit. This convenience method has been added in version 0.29.5. @@ -53581,11 +53616,13 @@ class Polygon: The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t. - The area value is given in terms of DBU units. Picking a value of 0.0 for area and min b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation. + Picking a value of 0.0 for max_area and min_b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation. + + The area value is given in terms of DBU units. The 'dbu' parameter a numerical scaling parameter. It should be choosen in a way that the polygon dimensions are "in the order of 1" (very roughly) after multiplication with the dbu parameter. A value of 0.001 is suitable for polygons with typical dimensions in the order to 1000 DBU. Usually the default value is good enough. - This method has been introduced in version 0.30. + This method has been introduced in version 0.30. Since version 0.30.1, the resulting region is in 'no merged semantics' mode, to avoid re-merging of the triangles during following operations. """ ... @overload @@ -53595,7 +53632,7 @@ class Polygon: This variant of the triangulation function accepts an array of additional vertexes for the triangulation. - This method has been introduced in version 0.30. + This method has been introduced in version 0.30. Since version 0.30.1, the resulting region is in 'no merged semantics' mode, to avoid re-merging of the triangles during following operations. """ ... def destroy(self) -> None: @@ -53682,6 +53719,28 @@ class Polygon: This method has been introduced in version 0.25. """ ... + def hm_decomposition(self, with_segments: Optional[bool] = ..., split_edges: Optional[bool] = ..., max_area: Optional[float] = ..., min_b: Optional[float] = ..., dbu: Optional[float] = ...) -> Region: + r""" + @brief Performs a Hertel-Mehlhorn convex decomposition. + + @return A \Region holding the polygons of the decomposition. + + The resulting region is in 'no merged semantics' mode, to avoid re-merging of the polygons during following operations. + + The Hertel-Mehlhorn decomposition starts with a Delaunay triangulation of the polygons and recombines the triangles into convex polygons. + + The decomposition is controlled by two parameters: 'with_segments' and 'split_edges'. + + If 'with_segments' is true (the default), new segments are introduced perpendicular to the edges forming a concave corner. If false, only diagonals (edges connecting original vertexes) are used. + + If 'split_edges' is true, the algorithm is allowed to create collinear edges in the output. In this case, the resulting polygons may contain edges that are split into collinear partial edges. Such edges usually recombine into longer edges when processing the polygon further. When such a recombination happens, the edges no longer correspond to original edges or diagonals. When 'split_edges' is false (the default), the resulting polygons will not contain collinear edges, but the decomposition will be constrained to fewer cut lines. + 'max_area' and 'min_b' are the corresponding parameters used for the triangulation (see \delaunay). + + The 'dbu' parameter a numerical scaling parameter. It should be choosen in a way that the polygon dimensions are "in the order of 1" (very roughly) after multiplication with the dbu parameter. A value of 0.001 is suitable for polygons with typical dimensions in the order to 1000 DBU. Usually the default value is good enough. + + This method has been introduced in version 0.30.1. + """ + ... def holes(self) -> int: r""" @brief Returns the number of holes @@ -62995,11 +63054,12 @@ class Shape: This method has been introduced in version 0.23. Setter: - @brief Sets the lower left point of the box + @brief Sets the lower left corner of the box with the point being given in micrometer units Applies to boxes only. Changes the lower left point of the box and throws an exception if the shape is not a box. + Translation from micrometer units to database units is done internally. - This method has been introduced in version 0.23. + This method has been introduced in version 0.25. """ box_p2: Point r""" @@ -63377,10 +63437,11 @@ class Shape: Starting with version 0.23, this method returns nil, if the shape does not represent a geometrical primitive that can be converted to a simple polygon. Setter: - @brief Replaces the shape by the given simple polygon (in micrometer units) - This method replaces the shape by the given text, like \simple_polygon= with a \SimplePolygon argument does. This version translates the polygon from micrometer units to database units internally. + @brief Replaces the shape by the given simple polygon object + This method replaces the shape by the given simple polygon object. This method can only be called for editable layouts. It does not change the user properties of the shape. + Calling this method will invalidate any iterators. It should not be called inside a loop iterating over shapes. - This method has been introduced in version 0.25. + This method has been introduced in version 0.22. """ text: Any r""" @@ -66718,11 +66779,13 @@ class SimplePolygon: The minimum angle of the resulting triangles relates to the 'b' parameter as: @t min_angle = arcsin(B/2) @/t. - The area value is given in terms of DBU units. Picking a value of 0.0 for area and min b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation. + Picking a value of 0.0 for max_area and min_b will make the implementation skip the refinement step. In that case, the results are identical to the standard constrained Delaunay triangulation. + + The area value is given in terms of DBU units. The 'dbu' parameter a numerical scaling parameter. It should be choosen in a way that the polygon dimensions are "in the order of 1" (very roughly) after multiplication with the dbu parameter. A value of 0.001 is suitable for polygons with typical dimensions in the order to 1000 DBU. Usually the default value is good enough. - This method has been introduced in version 0.30. + This method has been introduced in version 0.30. Since version 0.30.1, the resulting region is in 'no merged semantics' mode, to avoid re-merging of the triangles during following operations. """ ... @overload @@ -66732,7 +66795,7 @@ class SimplePolygon: This variant of the triangulation function accepts an array of additional vertexes for the triangulation. - This method has been introduced in version 0.30. + This method has been introduced in version 0.30. Since version 0.30.1, the resulting region is in 'no merged semantics' mode, to avoid re-merging of the triangles during following operations. """ ... def destroy(self) -> None: @@ -66797,6 +66860,27 @@ class SimplePolygon: This method has been introduced in version 0.25. """ ... + def hm_decomposition(self, with_segments: Optional[bool] = ..., split_edges: Optional[bool] = ..., max_area: Optional[float] = ..., min_b: Optional[float] = ..., dbu: Optional[float] = ...) -> Region: + r""" + @brief Performs a Hertel-Mehlhorn convex decomposition. + + @return A \Region holding the polygons of the decomposition. + The resulting region is in 'no merged semantics' mode, to avoid re-merging of the polygons during following operations. + + The Hertel-Mehlhorn decomposition starts with a Delaunay triangulation of the polygons and recombines the triangles into convex polygons. + + The decomposition is controlled by two parameters: 'with_segments' and 'split_edges'. + + If 'with_segments' is true (the default), new segments are introduced perpendicular to the edges forming a concave corner. If false, only diagonals (edges connecting original vertexes) are used. + + If 'split_edges' is true, the algorithm is allowed to create collinear edges in the output. In this case, the resulting polygons may contain edges that are split into collinear partial edges. Such edges usually recombine into longer edges when processing the polygon further. When such a recombination happens, the edges no longer correspond to original edges or diagonals. When 'split_edges' is false (the default), the resulting polygons will not contain collinear edges, but the decomposition will be constrained to fewer cut lines. + 'max_area' and 'min_b' are the corresponding parameters used for the triangulation (see \delaunay). + + The 'dbu' parameter a numerical scaling parameter. It should be choosen in a way that the polygon dimensions are "in the order of 1" (very roughly) after multiplication with the dbu parameter. A value of 0.001 is suitable for polygons with typical dimensions in the order to 1000 DBU. Usually the default value is good enough. + + This method has been introduced in version 0.30.1. + """ + ... def inside(self, p: Point) -> bool: r""" @brief Gets a value indicating whether the given point is inside the polygon @@ -67552,17 +67636,23 @@ class SubCircuit(NetlistObject): @overload def circuit(self) -> Circuit: r""" - @brief Gets the circuit the subcircuit lives in. + @brief Gets the circuit the subcircuit lives in (non-const version). This is NOT the circuit which is referenced. For getting the circuit that the subcircuit references, use \circuit_ref. + + This constness variant has been introduced in version 0.26.8 """ ... @overload def circuit(self) -> Circuit: r""" - @brief Gets the circuit the subcircuit lives in (non-const version). + @brief Gets the circuit the subcircuit lives in. This is NOT the circuit which is referenced. For getting the circuit that the subcircuit references, use \circuit_ref. - - This constness variant has been introduced in version 0.26.8 + """ + ... + @overload + def circuit_ref(self) -> Circuit: + r""" + @brief Gets the circuit referenced by the subcircuit. """ ... @overload @@ -67575,12 +67665,6 @@ class SubCircuit(NetlistObject): """ ... @overload - def circuit_ref(self) -> Circuit: - r""" - @brief Gets the circuit referenced by the subcircuit. - """ - ... - @overload def connect_pin(self, pin: Pin, net: Net) -> None: r""" @brief Connects the given pin to the specified net. @@ -68247,8 +68331,7 @@ class Text: Setter: @brief Sets the horizontal alignment - This property specifies how the text is aligned relative to the anchor point. - This property has been introduced in version 0.22 and extended to enums in 0.28. + This is the version accepting integer values. It's provided for backward compatibility. """ size: int r""" @@ -68284,7 +68367,8 @@ class Text: Setter: @brief Sets the vertical alignment - This is the version accepting integer values. It's provided for backward compatibility. + This property specifies how the text is aligned relative to the anchor point. + This property has been introduced in version 0.22 and extended to enums in 0.28. """ x: int r""" diff --git a/src/pymod/distutils_src/klayout/pexcore.pyi b/src/pymod/distutils_src/klayout/pexcore.pyi index e69de29bb..a91d281cb 100644 --- a/src/pymod/distutils_src/klayout/pexcore.pyi +++ b/src/pymod/distutils_src/klayout/pexcore.pyi @@ -0,0 +1,5 @@ +from typing import Any, ClassVar, Dict, Sequence, List, Iterator, Optional +from typing import overload +from __future__ import annotations +import klayout.tl as tl +import klayout.db as db diff --git a/src/pymod/distutils_src/klayout/tlcore.pyi b/src/pymod/distutils_src/klayout/tlcore.pyi index 09483e06c..fd53c5008 100644 --- a/src/pymod/distutils_src/klayout/tlcore.pyi +++ b/src/pymod/distutils_src/klayout/tlcore.pyi @@ -2908,7 +2908,9 @@ class Timer: r""" @brief Gets the current memory usage of the process in Bytes - This method has been introduced in version 0.27. + The returned value is the resident memory size on Linux and MacOS and the working set size on Windows. + + This method has been introduced in version 0.27. The value has been changed to be resident size (instead of virtual size) on Linux in version 0.30. """ ... @classmethod diff --git a/testdata/algo/hm_decomposition_au5.gds b/testdata/algo/hm_decomposition_au5.gds new file mode 100644 index 0000000000000000000000000000000000000000..875e1be4911c7a43a7c1b0593150dda38aed88eb GIT binary patch literal 1314 zcmbW1zb^zq6vsceyEnJQuak>K&IwUmAt4dmB_eUTMvy2-M5$8xH?G%dX!sYDDiTqu z)pRPAh?(7aXT@@}wt2g=@AKZ9@4PoCJnw*N!7IHGFiONY1L=1{Pi=3upyC(TH#)0Z zw^y&t_VvQ~+2J15;vbw+?fsoyf)Lc8M*%hD0c#^ZT&#e94~-9PpP*ic=sEeWy+c#` z)bzjOOIQlJ{y~`^@#amu=rM^?y@B%4_jo40{r@W;>fDAi*3@C*h?RA1v-o&Fl2^Ft z6PWD4Yy+mWc=bD&c>Ai$`e((PGy4&L;g`pt_0V09xxPf#eygjRG3tiHn$}MxLuE(m zf4M|~w9>sR6y4K$xKZYGES+;-R#QhxPSkdWBPHi%`@oTsX;?gsyV;S-nvvcb%EaW$ lC39-~GiQZ%svRj&KA(nCq0v&`B#ybPx7<51Rnm7&z$cXNd-DJQ literal 0 HcmV?d00001 From dfe67178dc1c87327e88bff56a0a23dd630841c3 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 19 Apr 2025 16:14:15 +0200 Subject: [PATCH 19/58] WIP --- src/pex/pex/pexRExtractor.cc | 94 +++++++++++++++++++----- src/pex/pex/pexRExtractor.h | 48 ++++++++---- src/pex/unit_tests/pexRExtractorTests.cc | 49 +++++++++++- 3 files changed, 158 insertions(+), 33 deletions(-) diff --git a/src/pex/pex/pexRExtractor.cc b/src/pex/pex/pexRExtractor.cc index 5d7a30eab..614e6f009 100644 --- a/src/pex/pex/pexRExtractor.cc +++ b/src/pex/pex/pexRExtractor.cc @@ -26,6 +26,50 @@ namespace pex { +// ----------------------------------------------------------------------------- + +std::string +RNode::to_string () const +{ + std::string res; + switch (type) { + default: + res += "$" + tl::to_string (port_index); + break; + case VertexPort: + res += "V" + tl::to_string (port_index); + break; + case PolygonPort: + res += "P" + tl::to_string (port_index); + break; + } + return res; +} + +// ----------------------------------------------------------------------------- + +std::string +RElement::to_string () const +{ + std::string res = "R "; + if (a ()) { + res += a ()->to_string (); + } else { + res += "(nil)"; + } + res += " "; + if (b ()) { + res += b ()->to_string (); + } else { + res += "(nil)"; + } + res += " "; + res += tl::sprintf ("%.6g", resistance ()); + return res; +} + +// ----------------------------------------------------------------------------- + RNetwork::RNetwork () { // .. nothing yet .. @@ -36,10 +80,23 @@ RNetwork::~RNetwork () clear (); } +std::string +RNetwork::to_string () const +{ + std::string res; + for (auto e = m_elements.begin (); e != m_elements.end (); ++e) { + if (! res.empty ()) { + res += "\n"; + } + res += e->to_string (); + } + return res; +} + void RNetwork::clear () { - m_elements.clear (); // must come before m_nodes + m_elements.clear (); // must happen before m_nodes m_nodes.clear (); m_elements_by_nodes.clear (); m_nodes_by_type.clear (); @@ -51,7 +108,7 @@ std::map, RNode *> m_nodes; RNode * RNetwork::create_node (RNode::node_type type, unsigned int port_index) { - if (type != RNode::INTERNAL) { + if (type != RNode::Internal) { auto i = m_nodes_by_type.find (std::make_pair (type, port_index)); if (i != m_nodes_by_type.end ()) { @@ -60,7 +117,7 @@ RNetwork::create_node (RNode::node_type type, unsigned int port_index) } else { - RNode *new_node = new RNode (type, db::DBox (), port_index); + RNode *new_node = new RNode (this, type, db::DBox (), port_index); m_nodes.push_back (new_node); m_nodes_by_type.insert (std::make_pair (std::make_pair (type, port_index), new_node)); @@ -69,7 +126,7 @@ RNetwork::create_node (RNode::node_type type, unsigned int port_index) } else { - RNode *new_node = new RNode (type, db::DBox (), port_index); + RNode *new_node = new RNode (this, type, db::DBox (), port_index); m_nodes.push_back (new_node); return new_node; @@ -87,13 +144,15 @@ RNetwork::create_element (double conductivity, RNode *a, RNode *b) } else { - RElement *element = new RElement (conductivity, a, b); - a->elements.push_back (element); - element->m_ia = --a->elements.end (); - b->elements.push_back (element); - element->m_ia = --b->elements.end (); - + RElement *element = new RElement (this, conductivity, a, b); + m_elements.push_back (element); m_elements_by_nodes.insert (std::make_pair (std::make_pair (a, b), element)); + + a->m_elements.push_back (element); + element->m_ia = --a->m_elements.end (); + b->m_elements.push_back (element); + element->m_ib = --b->m_elements.end (); + return element; } @@ -102,9 +161,9 @@ RNetwork::create_element (double conductivity, RNode *a, RNode *b) void RNetwork::remove_node (RNode *node) { - tl_assert (node->type == RNode::INTERNAL); - while (! node->elements.empty ()) { - remove_element (const_cast (node->elements.front ())); + tl_assert (node->type == RNode::Internal); + while (! node->m_elements.empty ()) { + delete const_cast (node->m_elements.front ()); } delete node; } @@ -112,19 +171,20 @@ RNetwork::remove_node (RNode *node) void RNetwork::remove_element (RElement *element) { - RNode *a = const_cast (element->a); - RNode *b = const_cast (element->b); + RNode *a = const_cast (element->a ()); + RNode *b = const_cast (element->b ()); delete element; - if (a && a->type == RNode::INTERNAL && a->elements.empty ()) { + if (a && a->type == RNode::Internal && a->m_elements.empty ()) { delete a; } - if (b && b->type == RNode::INTERNAL && b->elements.empty ()) { + if (b && b->type == RNode::Internal && b->m_elements.empty ()) { delete b; } } +// ----------------------------------------------------------------------------- RExtractor::RExtractor () { diff --git a/src/pex/pex/pexRExtractor.h b/src/pex/pex/pexRExtractor.h index dccc95616..87a2f2565 100644 --- a/src/pex/pex/pexRExtractor.h +++ b/src/pex/pex/pexRExtractor.h @@ -39,28 +39,36 @@ namespace pex class RElement; class RNode; +class RNetwork; struct PEX_PUBLIC RNode : public tl::list_node { public: enum node_type { - INTERNAL, - VERTEX_PORT, - POLYGON_PORT + Internal, + VertexPort, + PolygonPort }; node_type type; db::DBox location; unsigned int port_index; - mutable std::list elements; + + const std::list &elements () const + { + return m_elements; + } + + std::string to_string () const; protected: friend class RNetwork; + friend class RElement; friend class tl::list_impl; - RNode (node_type _type, const db::DBox &_location, unsigned int _port_index) - : type (_type), location (_location), port_index (_port_index) + RNode (RNetwork *network, node_type _type, const db::DBox &_location, unsigned int _port_index) + : type (_type), location (_location), port_index (_port_index), mp_network (network) { } ~RNode () { } @@ -68,39 +76,48 @@ protected: private: RNode (const RNode &other); RNode &operator= (const RNode &other); + + RNetwork *mp_network; + mutable std::list m_elements; }; struct PEX_PUBLIC RElement : public tl::list_node { double conductivity; - const RNode *a, *b; + + const RNode *a () const { return mp_a; } + const RNode *b () const { return mp_b; } double resistance () const { return 1.0 / conductivity; } + std::string to_string () const; + protected: friend class RNetwork; friend class tl::list_impl; - RElement (double _conductivity, const RNode *_a, const RNode *_b) - : conductivity (_conductivity), a (_a), b (_b) + RElement (RNetwork *network, double _conductivity, const RNode *a, const RNode *b) + : conductivity (_conductivity), mp_network (network), mp_a (a), mp_b (b) { } ~RElement () { - if (a) { - a->elements.erase (m_ia); + if (mp_a) { + mp_a->m_elements.erase (m_ia); } - if (b) { - b->elements.erase (m_ib); + if (mp_b) { + mp_b->m_elements.erase (m_ib); } - a = b = 0; + mp_a = mp_b = 0; } std::list::iterator m_ia, m_ib; + RNetwork *mp_network; + const RNode *mp_a, *mp_b; private: RElement (const RElement &other); @@ -108,6 +125,7 @@ private: }; class PEX_PUBLIC RNetwork + : public tl::Object { public: RNetwork (); @@ -119,6 +137,8 @@ public: void remove_node (RNode *node); void clear (); + std::string to_string () const; + private: tl::list m_nodes; tl::list m_elements; diff --git a/src/pex/unit_tests/pexRExtractorTests.cc b/src/pex/unit_tests/pexRExtractorTests.cc index c3ad86a08..3915e8337 100644 --- a/src/pex/unit_tests/pexRExtractorTests.cc +++ b/src/pex/unit_tests/pexRExtractorTests.cc @@ -24,8 +24,53 @@ #include "pexRExtractor.h" #include "tlUnitTest.h" -TEST(1) +TEST(network_basic) { - // @@@ + pex::RNetwork rn; + EXPECT_EQ (rn.to_string (), ""); + + pex::RNode *n1 = rn.create_node (pex::RNode::Internal, 1); + pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2); + /* pex::RElement *e12 = */ rn.create_element (0.5, n1, n2); + + EXPECT_EQ (rn.to_string (), + "R $1 $2 2" + ); + + pex::RNode *n3 = rn.create_node (pex::RNode::Internal, 3); + /* pex::RElement *e13 = */ rn.create_element (0.25, n1, n3); + pex::RElement *e23 = rn.create_element (1.0, n2, n3); + + EXPECT_EQ (rn.to_string (), + "R $1 $2 2\n" + "R $1 $3 4\n" + "R $2 $3 1" + ); + + pex::RElement *e23b = rn.create_element (4.0, n2, n3); + EXPECT_EQ (e23 == e23b, true); + + EXPECT_EQ (rn.to_string (), + "R $1 $2 2\n" + "R $1 $3 4\n" + "R $2 $3 0.2" + ); + + rn.remove_element (e23); + + EXPECT_EQ (rn.to_string (), + "R $1 $2 2\n" + "R $1 $3 4" + ); + + rn.remove_node (n3); + + EXPECT_EQ (rn.to_string (), + "R $1 $2 2" + ); + + rn.clear (); + + EXPECT_EQ (rn.to_string (), ""); } From 6c90da80072bc835f649b142218cc903ee28f940 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 19 Apr 2025 16:30:58 +0200 Subject: [PATCH 20/58] WIP --- src/pex/pex/gsiDeclRExtractor.cc | 2 +- src/pex/pex/pex.pro | 2 + src/pex/pex/pexRExtractor.cc | 32 --------- src/pex/pex/pexRExtractor.h | 31 --------- src/pex/pex/pexSquareCountingRExtractor.cc | 60 +++++++++++++++++ src/pex/pex/pexSquareCountingRExtractor.h | 66 +++++++++++++++++++ .../pexSquareCountingRExtractorTests.cc | 60 +++++++++++++++++ src/pex/unit_tests/unit_tests.pro | 1 + 8 files changed, 190 insertions(+), 64 deletions(-) create mode 100644 src/pex/pex/pexSquareCountingRExtractor.cc create mode 100644 src/pex/pex/pexSquareCountingRExtractor.h create mode 100644 src/pex/unit_tests/pexSquareCountingRExtractorTests.cc diff --git a/src/pex/pex/gsiDeclRExtractor.cc b/src/pex/pex/gsiDeclRExtractor.cc index a0bc54078..f74e8e657 100644 --- a/src/pex/pex/gsiDeclRExtractor.cc +++ b/src/pex/pex/gsiDeclRExtractor.cc @@ -22,7 +22,7 @@ #include "gsiDecl.h" -#include "pexRExtractor.h" +#include "pexSquareCountingRExtractor.h" namespace gsi { diff --git a/src/pex/pex/pex.pro b/src/pex/pex/pex.pro index 80b1b5a8b..251fb9935 100644 --- a/src/pex/pex/pex.pro +++ b/src/pex/pex/pex.pro @@ -10,10 +10,12 @@ SOURCES = \ pexForceLink.cc \ pexRExtractor.cc \ gsiDeclRExtractor.cc \ + pexSquareCountingRExtractor.cc HEADERS = \ pexForceLink.h \ pexRExtractor.h \ + pexSquareCountingRExtractor.h RESOURCES = \ diff --git a/src/pex/pex/pexRExtractor.cc b/src/pex/pex/pexRExtractor.cc index 614e6f009..de4a3585b 100644 --- a/src/pex/pex/pexRExtractor.cc +++ b/src/pex/pex/pexRExtractor.cc @@ -196,36 +196,4 @@ RExtractor::~RExtractor () // .. nothing yet .. } - -SquareCountingRExtractor::SquareCountingRExtractor (double dbu) -{ - m_dbu = dbu; - - m_decomp_param.split_edges = true; - m_decomp_param.with_segments = false; } - -void -SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector &vertex_ports, const std::vector &polygon_ports, RNetwork &rnetwork) -{ - db::CplxTrans trans = db::CplxTrans (m_dbu) * db::ICplxTrans (db::Trans (db::Point () - polygon.box ().center ())); - auto inv_trans = trans.inverted (); - - db::plc::Graph plc; - - db::plc::ConvexDecomposition decomp (&plc); - decomp.decompose (polygon, vertex_ports, m_decomp_param, trans); - - std::vector > decomp_polygons; - for (auto p = plc.begin (); p != plc.end (); ++p) { - // @@@decomp_polygons.push_back (db::Polygon ()); - // @@@decomp_polygons.back ().first = inv_trans * p->polygon (); - } - - // @@@ use box_scanner to find interactions between polygon_ports and decomp_polygons - -} - -} - - diff --git a/src/pex/pex/pexRExtractor.h b/src/pex/pex/pexRExtractor.h index 87a2f2565..96c704cbe 100644 --- a/src/pex/pex/pexRExtractor.h +++ b/src/pex/pex/pexRExtractor.h @@ -27,8 +27,6 @@ #include "dbPolygon.h" #include "dbPLC.h" -#include "dbPLCConvexDecomposition.h" // @@@ -#include "dbPLCTriangulation.h" // @@@ #include "tlList.h" #include @@ -168,35 +166,6 @@ public: virtual void extract (const db::Polygon &polygon, const std::vector &vertex_ports, const std::vector &polygon_ports, RNetwork &rnetwork) = 0; }; -// @@@ -class PEX_PUBLIC SquareCountingRExtractor - : public RExtractor -{ -public: - SquareCountingRExtractor (double dbu); - - db::plc::ConvexDecompositionParameters &decomposition_parameters () - { - return m_decomp_param; - } - - void set_dbu (double dbu) - { - m_dbu = dbu; - } - - double dbu () const - { - return m_dbu; - } - - virtual void extract (const db::Polygon &polygon, const std::vector &vertex_ports, const std::vector &polygon_ports, RNetwork &rnetwork); - -private: - db::plc::ConvexDecompositionParameters m_decomp_param; - double m_dbu; -}; - } #endif diff --git a/src/pex/pex/pexSquareCountingRExtractor.cc b/src/pex/pex/pexSquareCountingRExtractor.cc new file mode 100644 index 000000000..76f00b80a --- /dev/null +++ b/src/pex/pex/pexSquareCountingRExtractor.cc @@ -0,0 +1,60 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "pexSquareCountingRExtractor.h" + +namespace pex +{ + +SquareCountingRExtractor::SquareCountingRExtractor (double dbu) +{ + m_dbu = dbu; + + m_decomp_param.split_edges = true; + m_decomp_param.with_segments = false; +} + +void +SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector &vertex_ports, const std::vector &polygon_ports, RNetwork &rnetwork) +{ + db::CplxTrans trans = db::CplxTrans (m_dbu) * db::ICplxTrans (db::Trans (db::Point () - polygon.box ().center ())); + auto inv_trans = trans.inverted (); + + db::plc::Graph plc; + + db::plc::ConvexDecomposition decomp (&plc); + decomp.decompose (polygon, vertex_ports, m_decomp_param, trans); + + std::vector > decomp_polygons; + for (auto p = plc.begin (); p != plc.end (); ++p) { + // @@@decomp_polygons.push_back (db::Polygon ()); + // @@@decomp_polygons.back ().first = inv_trans * p->polygon (); + } + + // @@@ use box_scanner to find interactions between polygon_ports and decomp_polygons + +} + +} + + diff --git a/src/pex/pex/pexSquareCountingRExtractor.h b/src/pex/pex/pexSquareCountingRExtractor.h new file mode 100644 index 000000000..99881099b --- /dev/null +++ b/src/pex/pex/pexSquareCountingRExtractor.h @@ -0,0 +1,66 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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_pexSquareCountingRExtractor +#define HDR_pexSquareCountingRExtractor + +#include "pexCommon.h" +#include "pexRExtractor.h" + +#include "dbPLCConvexDecomposition.h" + +namespace pex +{ + +// @@@ doc +class PEX_PUBLIC SquareCountingRExtractor + : public RExtractor +{ +public: + SquareCountingRExtractor (double dbu); + + db::plc::ConvexDecompositionParameters &decomposition_parameters () + { + return m_decomp_param; + } + + void set_dbu (double dbu) + { + m_dbu = dbu; + } + + double dbu () const + { + return m_dbu; + } + + virtual void extract (const db::Polygon &polygon, const std::vector &vertex_ports, const std::vector &polygon_ports, RNetwork &rnetwork); + +private: + db::plc::ConvexDecompositionParameters m_decomp_param; + double m_dbu; +}; + +} + +#endif + diff --git a/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc b/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc new file mode 100644 index 000000000..613d630b9 --- /dev/null +++ b/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc @@ -0,0 +1,60 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "pexSquareCountingRExtractor.h" +#include "tlUnitTest.h" + +TEST(basic) +{ + db::Point contour[] = { + db::Point (0, 0), + db::Point (0, 100), + db::Point (1000, 100), + db::Point (1000, 1000), + db::Point (1100, 1000), + db::Point (1100, 100), + db::Point (1700, 100), + db::Point (1700, 0) + }; + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + double dbu = 0.001; + + pex::RNetwork rn; + pex::SquareCountingRExtractor rex (dbu); + + std::vector vertex_ports; + vertex_ports.push_back (db::Point (0, 50)); + vertex_ports.push_back (db::Point (1650, 50)); + + std::vector polygon_ports; + polygon_ports.push_back (db::Polygon (db::Box (1000, 900, 1100, 1000))); + + rex.extract (poly, vertex_ports, polygon_ports, rn); + + EXPECT_EQ (rn.to_string (), + "" + ) +} diff --git a/src/pex/unit_tests/unit_tests.pro b/src/pex/unit_tests/unit_tests.pro index c6ccc1b74..6c380c340 100644 --- a/src/pex/unit_tests/unit_tests.pro +++ b/src/pex/unit_tests/unit_tests.pro @@ -8,6 +8,7 @@ include($$PWD/../../lib_ut.pri) SOURCES = \ pexRExtractorTests.cc \ + pexSquareCountingRExtractorTests.cc INCLUDEPATH += $$TL_INC $$DB_INC $$GSI_INC $$PEX_INC DEPENDPATH += $$TL_INC $$DB_INC $$GSI_INC $$PEX_INC From 9cd29c9de70c316ca5fd8ecf1fc9bcbeff82a920 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 19 Apr 2025 18:45:31 +0200 Subject: [PATCH 21/58] WIP --- src/pex/pex/pexRExtractor.cc | 7 +- src/pex/pex/pexRExtractor.h | 8 +- src/pex/pex/pexSquareCountingRExtractor.cc | 283 ++++++++++++++++++++- 3 files changed, 290 insertions(+), 8 deletions(-) diff --git a/src/pex/pex/pexRExtractor.cc b/src/pex/pex/pexRExtractor.cc index de4a3585b..4913c90ab 100644 --- a/src/pex/pex/pexRExtractor.cc +++ b/src/pex/pex/pexRExtractor.cc @@ -139,7 +139,12 @@ RNetwork::create_element (double conductivity, RNode *a, RNode *b) auto i = m_elements_by_nodes.find (std::make_pair (a, b)); if (i != m_elements_by_nodes.end ()) { - i->second->conductivity += conductivity; + if (conductivity == pex::RElement::short_value () || i->second->conductivity == pex::RElement::short_value ()) { + i->second->conductivity = pex::RElement::short_value (); + } else { + i->second->conductivity += conductivity; + } + return i->second; } else { diff --git a/src/pex/pex/pexRExtractor.h b/src/pex/pex/pexRExtractor.h index 96c704cbe..890b6ad67 100644 --- a/src/pex/pex/pexRExtractor.h +++ b/src/pex/pex/pexRExtractor.h @@ -31,6 +31,7 @@ #include #include +#include namespace pex { @@ -87,9 +88,14 @@ struct PEX_PUBLIC RElement const RNode *a () const { return mp_a; } const RNode *b () const { return mp_b; } + static double short_value () + { + return std::numeric_limits::infinity (); + } + double resistance () const { - return 1.0 / conductivity; + return conductivity == short_value () ? 0.0 : 1.0 / conductivity; } std::string to_string () const; diff --git a/src/pex/pex/pexSquareCountingRExtractor.cc b/src/pex/pex/pexSquareCountingRExtractor.cc index 76f00b80a..cdfc0d183 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.cc +++ b/src/pex/pex/pexSquareCountingRExtractor.cc @@ -22,6 +22,9 @@ #include "pexSquareCountingRExtractor.h" +#include "dbBoxScanner.h" +#include "dbPolygonTools.h" +#include "tlIntervalMap.h" namespace pex { @@ -34,9 +37,179 @@ SquareCountingRExtractor::SquareCountingRExtractor (double dbu) m_decomp_param.with_segments = false; } -void -SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector &vertex_ports, const std::vector &polygon_ports, RNetwork &rnetwork) +namespace { + +class PolygonPortInteractionReceiver + : public db::box_scanner_receiver2 +{ +public: + void add (const db::Polygon *obj1, const size_t &index1, const db::Polygon *obj2, const size_t &index2) + { + if (db::interact_pp (*obj1, *obj2)) { + m_interactions[index1].insert (index2); + } + } + + const std::set &interactions (size_t index) const + { + static std::set empty; + auto i = m_interactions.find (index); + if (i == m_interactions.end ()) { + return empty; + } else { + return i->second; + } + } + +private: + std::map > m_interactions; +}; + +struct PortDefinition +{ + PortDefinition () + : type (pex::RNode::Internal), port_index (0) + { } + + PortDefinition (pex::RNode::node_type _type, const db::Point &_location, unsigned int _port_index) + : type (_type), location (_location), port_index (_port_index) + { } + + bool operator< (const PortDefinition &other) const + { + if (type != other.type) { + return type < other.type; + } + if (port_index != other.port_index) { + return port_index < other.port_index; + } + return false; + } + + bool operator== (const PortDefinition &other) const + { + return type == other.type && port_index == other.port_index; + } + + pex::RNode::node_type type; + db::Point location; + unsigned int port_index; +}; + +struct JoinEdgeSets +{ + void operator() (std::set &a, const std::set &b) const + { + a.insert (b.begin (), b.end ()); + } +}; + +} + +static +double yatx (const db::Edge &e, int x) +{ + db::Point p1 = e.p1 (), p2 = e.p2 (); + if (p1.x () > p2.x ()) { + std::swap (p1, p2); + } + + return p1.y () + double (p2.y () - p1.y ()) * double (x - p1.x ()) / double (p2.x () - p1.x ()); +} + +static +double calculate_squares (db::Coord x1, db::Coord x2, const std::set &edges) +{ + tl_assert (edges.size () == 2); + + auto i = edges.begin (); + db::Edge e1 = *i++; + db::Edge e2 = *i; + + double w1 = fabs (yatx (e1, x1) - yatx (e2, x1)); + double w2 = fabs (yatx (e1, x2) - yatx (e2, x2)); + + // integrate the resistance along the axis x1->x2 with w=w1->w2 + + if (w1 < db::epsilon) { + return 1e9; // @@@ + } else if (fabs (w1 - w2) < db::epsilon) { + return (x2 - x1) / w1; + } else { + return (x2 - x1) / (w2 - w1) * log (w2 / w1); + } +} + +static +void rextract_square_counting (const db::Polygon &db_poly, const std::vector > &ports, pex::RNetwork &rnetwork, double /*dbu*/) +{ + // "trans" will orient the polygon to be flat rather than tall + db::Trans trans; + if (db_poly.box ().width () < db_poly.box ().height ()) { + trans = db::Trans (db::Trans::r90); + } + + // sort the edges into an interval map - as the polygons are convex, there + // can only be two edges in each interval. + + tl::interval_map > edges; + for (auto e = db_poly.begin_edge (); ! e.at_end (); ++e) { + db::Edge et = trans * *e; + if (et.x1 () != et.x2 ()) { + std::set es; + es.insert (et); + JoinEdgeSets jes; + edges.add (std::min (et.p1 ().x (), et.p2 ().x ()), std::max (et.p1 ().x (), et.p2 ().x ()), es, jes); + } + } + + // sort the port locations + + std::multimap port_locations; + for (auto p = ports.begin (); p != ports.end (); ++p) { + db::Coord c = (trans * p->first.location).x (); + port_locations.insert (std::make_pair (c, p->second)); + } + + // walk along the long axis of the polygon and compute the square count between the port locations + + for (auto pl = port_locations.begin (); pl != port_locations.end (); ++pl) { + + auto pl_next = pl; + ++pl_next; + if (pl_next == port_locations.end ()) { + break; + } + + db::Coord c = pl->first; + db::Coord cc = pl_next->first; + + double r = 0.0; + + auto em = edges.find (c); + while (em != edges.end () && em->first.first < cc) { + r += calculate_squares (em->first.first, std::min (cc, em->first.second), em->second); + ++em; + } + + // @@@ TODO: multiply with sheet rho! + // @@@ TODO: width dependency + if (r == 0) { + // @@@ TODO: join nodes later! + rnetwork.create_element (pex::RElement::short_value (), pl->second, pl_next->second); + } else { + rnetwork.create_element (r, pl->second, pl_next->second); + } + + } +} + +void +SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector &vertex_ports, const std::vector &polygon_ports, pex::RNetwork &rnetwork) +{ + rnetwork.clear (); + db::CplxTrans trans = db::CplxTrans (m_dbu) * db::ICplxTrans (db::Trans (db::Point () - polygon.box ().center ())); auto inv_trans = trans.inverted (); @@ -45,13 +218,111 @@ SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector db::plc::ConvexDecomposition decomp (&plc); decomp.decompose (polygon, vertex_ports, m_decomp_param, trans); - std::vector > decomp_polygons; + // Set up a scanner to detect interactions between polygon ports + // and decomposed polygons + + db::box_scanner2 scanner; + + std::vector > decomp_polygons; for (auto p = plc.begin (); p != plc.end (); ++p) { - // @@@decomp_polygons.push_back (db::Polygon ()); - // @@@decomp_polygons.back ().first = inv_trans * p->polygon (); + decomp_polygons.push_back (std::make_pair (db::Polygon (), p.operator-> ())); + decomp_polygons.back ().first = inv_trans * p->polygon (); } - // @@@ use box_scanner to find interactions between polygon_ports and decomp_polygons + for (auto i = decomp_polygons.begin (); i != decomp_polygons.end (); ++i) { + scanner.insert1 (&i->first, i - decomp_polygons.begin ()); + } + + for (auto i = polygon_ports.begin (); i != polygon_ports.end (); ++i) { + scanner.insert2 (i.operator-> (), i - polygon_ports.begin ()); + } + + PolygonPortInteractionReceiver interactions; + db::box_convert bc; + scanner.process (interactions, 1, bc, bc); + + // Generate the internal ports: those are defined by edges connecting two polygons + + std::vector internal_port_edges; + std::map internal_ports; + std::vector > internal_port_indexes; + + for (auto i = decomp_polygons.begin (); i != decomp_polygons.end (); ++i) { + + internal_port_indexes.push_back (std::vector ()); + auto p = i->second; + + for (size_t j = 0; j < p->size (); ++j) { + + const db::plc::Edge *e = p->edge (j); + if (e->left () && e->right ()) { + + auto ip = internal_ports.find (e); + if (ip == internal_ports.end ()) { + size_t n = internal_port_edges.size (); + internal_port_edges.push_back (e); + ip = internal_ports.insert (std::make_pair (e, n)).first; + } + internal_port_indexes.back ().push_back (ip->second); + + } + + } + + } + + // Now we can extract the resistors + + std::vector > ports; + std::map nodes_for_ports; + + for (auto p = decomp_polygons.begin (); p != decomp_polygons.end (); ++p) { + + ports.clear (); + + const db::Polygon &db_poly = p->first; + const db::plc::Polygon *plc_poly = p->second; + const std::set &pp_indexes = interactions.interactions (p - decomp_polygons.begin ()); + const std::vector &ip_indexes = internal_port_indexes [p - decomp_polygons.begin ()]; + + // set up the ports: + + // 1. internal ports + for (auto i = ip_indexes.begin (); i != ip_indexes.end (); ++i) { + db::Point loc = (inv_trans * internal_port_edges [*i]->edge ()).bbox ().center (); + ports.push_back (std::make_pair (PortDefinition (pex::RNode::Internal, loc, *i), (pex::RNode *) 0)); + } + + // 2. vertex ports + for (size_t i = 0; i < plc_poly->internal_vertexes (); ++i) { + db::Point loc = inv_trans * *plc_poly->internal_vertex (i); + ports.push_back (std::make_pair (PortDefinition (pex::RNode::VertexPort, loc, i), (pex::RNode *) 0)); + } + + // 3. polygon ports + // (NOTE: here we only take the center of the bounding box) + for (auto i = pp_indexes.begin (); i != pp_indexes.end (); ++i) { + db::Point loc = polygon_ports [*i].box ().center (); + ports.push_back (std::make_pair (PortDefinition (pex::RNode::PolygonPort, loc, *i), (pex::RNode *) 0)); + } + + // create nodes for the ports + // (we reuse nodes for existing ports in "nodes_for_ports", hence to establish the connection) + + for (auto p = ports.begin (); p != ports.end (); ++p) { + auto n4p = nodes_for_ports.find (p->first); + if (n4p == nodes_for_ports.end ()) { + pex::RNode *node = rnetwork.create_node (p->first.type, p->first.port_index); + db::DPoint loc = trans * p->first.location; + node->location = db::DBox (loc, loc); + n4p = nodes_for_ports.insert (std::make_pair (p->first, node)).first; + } + p->second = n4p->second; + } + + rextract_square_counting (db_poly, ports, rnetwork, dbu ()); + + } } From 1379d305020d2a938cc9e1b3b0c72ff37dafa744 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 19 Apr 2025 20:55:15 +0200 Subject: [PATCH 22/58] Debugging, Tests --- src/db/db/dbPLCConvexDecomposition.cc | 1 + src/pex/pex/pexSquareCountingRExtractor.cc | 55 +++--------- src/pex/pex/pexSquareCountingRExtractor.h | 37 ++++++++ .../pexSquareCountingRExtractorTests.cc | 90 ++++++++++++++++++- 4 files changed, 136 insertions(+), 47 deletions(-) diff --git a/src/db/db/dbPLCConvexDecomposition.cc b/src/db/db/dbPLCConvexDecomposition.cc index 2723eb796..426dfa06d 100644 --- a/src/db/db/dbPLCConvexDecomposition.cc +++ b/src/db/db/dbPLCConvexDecomposition.cc @@ -419,6 +419,7 @@ ConvexDecomposition::hertel_mehlhorn_decomposition (Triangulation &tris, const C for (auto i = iv->begin (); i != iv->end (); ++i) { poly->add_internal_vertex (*i); } + ++iv; } } diff --git a/src/pex/pex/pexSquareCountingRExtractor.cc b/src/pex/pex/pexSquareCountingRExtractor.cc index cdfc0d183..eaa865719 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.cc +++ b/src/pex/pex/pexSquareCountingRExtractor.cc @@ -29,14 +29,6 @@ namespace pex { -SquareCountingRExtractor::SquareCountingRExtractor (double dbu) -{ - m_dbu = dbu; - - m_decomp_param.split_edges = true; - m_decomp_param.with_segments = false; -} - namespace { @@ -66,37 +58,6 @@ private: std::map > m_interactions; }; -struct PortDefinition -{ - PortDefinition () - : type (pex::RNode::Internal), port_index (0) - { } - - PortDefinition (pex::RNode::node_type _type, const db::Point &_location, unsigned int _port_index) - : type (_type), location (_location), port_index (_port_index) - { } - - bool operator< (const PortDefinition &other) const - { - if (type != other.type) { - return type < other.type; - } - if (port_index != other.port_index) { - return port_index < other.port_index; - } - return false; - } - - bool operator== (const PortDefinition &other) const - { - return type == other.type && port_index == other.port_index; - } - - pex::RNode::node_type type; - db::Point location; - unsigned int port_index; -}; - struct JoinEdgeSets { void operator() (std::set &a, const std::set &b) const @@ -107,6 +68,14 @@ struct JoinEdgeSets } +SquareCountingRExtractor::SquareCountingRExtractor (double dbu) +{ + m_dbu = dbu; + + m_decomp_param.split_edges = true; + m_decomp_param.with_segments = false; +} + static double yatx (const db::Edge &e, int x) { @@ -141,8 +110,8 @@ double calculate_squares (db::Coord x1, db::Coord x2, const std::set & } } -static -void rextract_square_counting (const db::Polygon &db_poly, const std::vector > &ports, pex::RNetwork &rnetwork, double /*dbu*/) +void +SquareCountingRExtractor::do_extract (const db::Polygon &db_poly, const std::vector > &ports, pex::RNetwork &rnetwork) { // "trans" will orient the polygon to be flat rather than tall db::Trans trans; @@ -189,7 +158,7 @@ void rextract_square_counting (const db::Polygon &db_poly, const std::vectorfirst.first < cc) { - r += calculate_squares (em->first.first, std::min (cc, em->first.second), em->second); + r += calculate_squares (std::max (c, em->first.first), std::min (cc, em->first.second), em->second); ++em; } @@ -320,7 +289,7 @@ SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector p->second = n4p->second; } - rextract_square_counting (db_poly, ports, rnetwork, dbu ()); + do_extract (db_poly, ports, rnetwork); } diff --git a/src/pex/pex/pexSquareCountingRExtractor.h b/src/pex/pex/pexSquareCountingRExtractor.h index 99881099b..b498bb1b2 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.h +++ b/src/pex/pex/pexSquareCountingRExtractor.h @@ -55,6 +55,43 @@ public: virtual void extract (const db::Polygon &polygon, const std::vector &vertex_ports, const std::vector &polygon_ports, RNetwork &rnetwork); +protected: + /** + * @brief A helper structure defining a port + */ + struct PortDefinition + { + PortDefinition () + : type (pex::RNode::Internal), port_index (0) + { } + + PortDefinition (pex::RNode::node_type _type, const db::Point &_location, unsigned int _port_index) + : type (_type), location (_location), port_index (_port_index) + { } + + bool operator< (const PortDefinition &other) const + { + if (type != other.type) { + return type < other.type; + } + if (port_index != other.port_index) { + return port_index < other.port_index; + } + return false; + } + + bool operator== (const PortDefinition &other) const + { + return type == other.type && port_index == other.port_index; + } + + pex::RNode::node_type type; + db::Point location; + unsigned int port_index; + }; + + void do_extract (const db::Polygon &db_poly, const std::vector > &ports, pex::RNetwork &rnetwork); + private: db::plc::ConvexDecompositionParameters m_decomp_param; double m_dbu; diff --git a/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc b/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc index 613d630b9..1e4d7e326 100644 --- a/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc +++ b/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc @@ -24,7 +24,87 @@ #include "pexSquareCountingRExtractor.h" #include "tlUnitTest.h" +namespace +{ + +class TestableSquareCountingRExtractor + : public pex::SquareCountingRExtractor +{ +public: + TestableSquareCountingRExtractor () + : pex::SquareCountingRExtractor (0.001) + { } + + using pex::SquareCountingRExtractor::PortDefinition; + using pex::SquareCountingRExtractor::do_extract; +}; + +} + TEST(basic) +{ + db::Point contour[] = { + db::Point (0, 0), + db::Point (0, 100), + db::Point (1000, 1000), + db::Point (2100, 1000), + db::Point (2100, 0) + }; + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + TestableSquareCountingRExtractor rex; + pex::RNetwork rn; + + TestableSquareCountingRExtractor::PortDefinition pd1 (pex::RNode::Internal, db::Point (-50, 50), 0); + TestableSquareCountingRExtractor::PortDefinition pd2 (pex::RNode::Internal, db::Point (1000, 100), 1); + TestableSquareCountingRExtractor::PortDefinition pd3 (pex::RNode::Internal, db::Point (1000, 500), 2); + TestableSquareCountingRExtractor::PortDefinition pd4 (pex::RNode::Internal, db::Point (2000, 500), 3); + + std::vector pds; + pds.push_back (TestableSquareCountingRExtractor::PortDefinition (pex::RNode::Internal, db::Point (0, 50), 0)); + pds.push_back (TestableSquareCountingRExtractor::PortDefinition (pex::RNode::Internal, db::Point (1000, 100), 1)); + pds.push_back (TestableSquareCountingRExtractor::PortDefinition (pex::RNode::Internal, db::Point (1000, 500), 2)); + pds.push_back (TestableSquareCountingRExtractor::PortDefinition (pex::RNode::Internal, db::Point (2000, 500), 3)); + + std::vector > ports; + for (auto pd = pds.begin (); pd != pds.end (); ++pd) { + ports.push_back (std::make_pair (*pd, rn.create_node (pd->type, pd->port_index))); + } + + rex.do_extract (poly, ports, rn); + + EXPECT_EQ (rn.to_string (), + "R $0 $1 0.390865\n" // w ramp w=100 to 1000 over x=0 to 1000 (squares = (x2-x1)/(w2-w1)*log(w2/w1) by integration) + "R $1 $2 0\n" // transition from y=50 to y=500 parallel to current direction + "R $2 $3 1" // 1 square between x=1000 and 2000 (w=1000) + ); + + // After rotation + + rn.clear (); + + db::Trans r90 (db::Trans::r90); + + poly.transform (r90); + + ports.clear (); + for (auto pd = pds.begin (); pd != pds.end (); ++pd) { + ports.push_back (std::make_pair (*pd, rn.create_node (pd->type, pd->port_index))); + ports.back ().first.location.transform (r90); + } + + rex.do_extract (poly, ports, rn); + + // Same network, but opposite order. $1 and $2 are shorted, hence can be swapped. + EXPECT_EQ (rn.to_string (), + "R $3 $1 1\n" + "R $1 $2 0\n" + "R $2 $0 0.390865" + ); +} + +TEST(extraction) { db::Point contour[] = { db::Point (0, 0), @@ -46,15 +126,17 @@ TEST(basic) pex::SquareCountingRExtractor rex (dbu); std::vector vertex_ports; - vertex_ports.push_back (db::Point (0, 50)); - vertex_ports.push_back (db::Point (1650, 50)); + vertex_ports.push_back (db::Point (0, 50)); // V0 + vertex_ports.push_back (db::Point (1650, 50)); // V1 std::vector polygon_ports; - polygon_ports.push_back (db::Polygon (db::Box (1000, 900, 1100, 1000))); + polygon_ports.push_back (db::Polygon (db::Box (1000, 900, 1100, 1000))); // P0 rex.extract (poly, vertex_ports, polygon_ports, rn); EXPECT_EQ (rn.to_string (), - "" + "R V0 $0 0.0952381\n" + "R $0 V1 0.166667\n" + "R P0 $0 0.117647" ) } From fcd42bd0f11f0543f6899c7fdf71a838b81651ff Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 19 Apr 2025 23:48:58 +0200 Subject: [PATCH 23/58] R network simplify --- src/pex/pex/pexRExtractor.cc | 111 ++++++++++++++++++++- src/pex/pex/pexRExtractor.h | 13 +++ src/pex/pex/pexSquareCountingRExtractor.cc | 11 +- src/pex/pex/pexSquareCountingRExtractor.h | 6 +- src/pex/unit_tests/pexRExtractorTests.cc | 111 +++++++++++++++++++++ 5 files changed, 242 insertions(+), 10 deletions(-) diff --git a/src/pex/pex/pexRExtractor.cc b/src/pex/pex/pexRExtractor.cc index 4913c90ab..1df03457b 100644 --- a/src/pex/pex/pexRExtractor.cc +++ b/src/pex/pex/pexRExtractor.cc @@ -22,6 +22,7 @@ #include "pexRExtractor.h" +#include "tlEquivalenceClusters.h" namespace pex { @@ -102,9 +103,6 @@ RNetwork::clear () m_nodes_by_type.clear (); } -std::map, RElement *> m_elements_by_nodes; -std::map, RNode *> m_nodes; - RNode * RNetwork::create_node (RNode::node_type type, unsigned int port_index) { @@ -189,6 +187,113 @@ RNetwork::remove_element (RElement *element) } } +void +RNetwork::join_nodes (RNode *a, RNode *b) +{ + for (auto e = b->elements ().begin (); e != b->elements ().end (); ++e) { + RNode *on = const_cast ((*e)->other (b)); + if (on != a) { + create_element ((*e)->conductivity, on, a); + } + } + + remove_node (b); +} + +void +RNetwork::simplify () +{ + bool any_change = true; + + while (any_change) { + + any_change = false; + + // join shorted clusters - we take care to remove internal nodes only + + tl::equivalence_clusters clusters; + for (auto e = m_elements.begin (); e != m_elements.end (); ++e) { + if (e->conductivity == pex::RElement::short_value () && (e->a ()->type == pex::RNode::Internal || e->b ()->type == pex::RNode::Internal)) { + clusters.same (e->a (), e->b ()); + } + } + + for (size_t ic = 1; ic <= clusters.size (); ++ic) { + + RNode *remaining = 0; + RNode *first_node = 0; + for (auto c = clusters.begin_cluster (ic); c != clusters.end_cluster (ic); ++c) { + RNode *n = const_cast ((*c)->first); + if (! first_node) { + first_node = n; + } + if (n->type != pex::RNode::Internal) { + remaining = n; + break; + } + } + + if (! remaining) { + // Only internal nodes + remaining = first_node; + } + + for (auto c = clusters.begin_cluster (ic); c != clusters.end_cluster (ic); ++c) { + RNode *n = const_cast ((*c)->first); + if (n != remaining && n->type == pex::RNode::Internal) { + any_change = true; + join_nodes (remaining, n); + } + } + + } + + // combine serial resistors if connected through an internal node + + std::vector nodes_to_remove; + + for (auto n = m_nodes.begin (); n != m_nodes.end (); ++n) { + + size_t nres = n->elements ().size (); + + if (n->type == pex::RNode::Internal && nres <= 2) { + + any_change = true; + + if (nres == 2) { + + auto e = n->elements ().begin (); + + RNode *n1 = const_cast ((*e)->other (n.operator-> ())); + double r1 = (*e)->resistance (); + + ++e; + RNode *n2 = const_cast ((*e)->other (n.operator-> ())); + double r2 = (*e)->resistance (); + + double r = r1 + r2; + if (r == 0.0) { + create_element (pex::RElement::short_value (), n1, n2); + } else { + create_element (1.0 / r, n1, n2); + } + + } + + nodes_to_remove.push_back (n.operator-> ()); + + } + + } + + for (auto n = nodes_to_remove.begin (); n != nodes_to_remove.end (); ++n) { + remove_node (*n); + } + + } + +} + // ----------------------------------------------------------------------------- RExtractor::RExtractor () diff --git a/src/pex/pex/pexRExtractor.h b/src/pex/pex/pexRExtractor.h index 890b6ad67..f1c4f4659 100644 --- a/src/pex/pex/pexRExtractor.h +++ b/src/pex/pex/pexRExtractor.h @@ -88,6 +88,16 @@ struct PEX_PUBLIC RElement const RNode *a () const { return mp_a; } const RNode *b () const { return mp_b; } + const RNode *other (const RNode *n) const + { + if (mp_a == n) { + return mp_b; + } else if (mp_b == n) { + return mp_a; + } + tl_assert (false); + } + static double short_value () { return std::numeric_limits::infinity (); @@ -140,6 +150,7 @@ public: void remove_element (RElement *element); void remove_node (RNode *node); void clear (); + void simplify (); std::string to_string () const; @@ -151,6 +162,8 @@ private: RNetwork (const RNetwork &); RNetwork &operator= (const RNetwork &); + + void join_nodes (RNode *a, RNode *b); }; diff --git a/src/pex/pex/pexSquareCountingRExtractor.cc b/src/pex/pex/pexSquareCountingRExtractor.cc index eaa865719..5477abdb0 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.cc +++ b/src/pex/pex/pexSquareCountingRExtractor.cc @@ -133,11 +133,11 @@ SquareCountingRExtractor::do_extract (const db::Polygon &db_poly, const std::vec } } - // sort the port locations + // sort the port locations - note that we take the port box centers for the location! std::multimap port_locations; for (auto p = ports.begin (); p != ports.end (); ++p) { - db::Coord c = (trans * p->first.location).x (); + db::Coord c = (trans * p->first.location).center ().x (); port_locations.insert (std::make_pair (c, p->second)); } @@ -258,7 +258,7 @@ SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector // 1. internal ports for (auto i = ip_indexes.begin (); i != ip_indexes.end (); ++i) { - db::Point loc = (inv_trans * internal_port_edges [*i]->edge ()).bbox ().center (); + db::Box loc = (inv_trans * internal_port_edges [*i]->edge ()).bbox (); ports.push_back (std::make_pair (PortDefinition (pex::RNode::Internal, loc, *i), (pex::RNode *) 0)); } @@ -271,7 +271,7 @@ SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector // 3. polygon ports // (NOTE: here we only take the center of the bounding box) for (auto i = pp_indexes.begin (); i != pp_indexes.end (); ++i) { - db::Point loc = polygon_ports [*i].box ().center (); + db::Box loc = polygon_ports [*i].box (); ports.push_back (std::make_pair (PortDefinition (pex::RNode::PolygonPort, loc, *i), (pex::RNode *) 0)); } @@ -282,8 +282,7 @@ SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector auto n4p = nodes_for_ports.find (p->first); if (n4p == nodes_for_ports.end ()) { pex::RNode *node = rnetwork.create_node (p->first.type, p->first.port_index); - db::DPoint loc = trans * p->first.location; - node->location = db::DBox (loc, loc); + node->location = trans * p->first.location; n4p = nodes_for_ports.insert (std::make_pair (p->first, node)).first; } p->second = n4p->second; diff --git a/src/pex/pex/pexSquareCountingRExtractor.h b/src/pex/pex/pexSquareCountingRExtractor.h index b498bb1b2..abacc2dfa 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.h +++ b/src/pex/pex/pexSquareCountingRExtractor.h @@ -66,6 +66,10 @@ protected: { } PortDefinition (pex::RNode::node_type _type, const db::Point &_location, unsigned int _port_index) + : type (_type), location (_location, _location), port_index (_port_index) + { } + + PortDefinition (pex::RNode::node_type _type, const db::Box &_location, unsigned int _port_index) : type (_type), location (_location), port_index (_port_index) { } @@ -86,7 +90,7 @@ protected: } pex::RNode::node_type type; - db::Point location; + db::Box location; unsigned int port_index; }; diff --git a/src/pex/unit_tests/pexRExtractorTests.cc b/src/pex/unit_tests/pexRExtractorTests.cc index 3915e8337..d386ce621 100644 --- a/src/pex/unit_tests/pexRExtractorTests.cc +++ b/src/pex/unit_tests/pexRExtractorTests.cc @@ -74,3 +74,114 @@ TEST(network_basic) EXPECT_EQ (rn.to_string (), ""); } +TEST(network_simplify1) +{ + pex::RNetwork rn; + EXPECT_EQ (rn.to_string (), ""); + + pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1); + pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2); + pex::RNode *n3 = rn.create_node (pex::RNode::VertexPort, 3); + + rn.create_element (1, n1, n2); + rn.create_element (pex::RElement::short_value (), n2, n3); + rn.create_element (1, n1, n3); + + EXPECT_EQ (rn.to_string (), + "R V1 $2 1\n" + "R $2 V3 0\n" + "R V1 V3 1" + ); + + rn.simplify (); + + EXPECT_EQ (rn.to_string (), + "R V1 V3 0.5" + ); +} + +TEST(network_simplify2) +{ + pex::RNetwork rn; + EXPECT_EQ (rn.to_string (), ""); + + pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1); + pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2); + pex::RNode *n3 = rn.create_node (pex::RNode::Internal, 3); + pex::RNode *n4 = rn.create_node (pex::RNode::VertexPort, 4); + pex::RNode *n5 = rn.create_node (pex::RNode::VertexPort, 5); + + rn.create_element (1, n1, n2); + rn.create_element (pex::RElement::short_value (), n2, n3); + rn.create_element (1, n3, n4); + rn.create_element (1, n3, n5); + + EXPECT_EQ (rn.to_string (), + "R V1 $2 1\n" + "R $2 $3 0\n" + "R $3 V4 1\n" + "R $3 V5 1" + ); + + rn.simplify (); + + EXPECT_EQ (rn.to_string (), + "R V1 $2 1\n" + "R V4 $2 1\n" + "R V5 $2 1" + ); +} + +TEST(network_simplify3) +{ + pex::RNetwork rn; + EXPECT_EQ (rn.to_string (), ""); + + pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1); + pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2); + pex::RNode *n3 = rn.create_node (pex::RNode::Internal, 3); + pex::RNode *n4 = rn.create_node (pex::RNode::VertexPort, 4); + + rn.create_element (1, n1, n2); + rn.create_element (pex::RElement::short_value (), n2, n3); + rn.create_element (1, n3, n4); + + EXPECT_EQ (rn.to_string (), + "R V1 $2 1\n" + "R $2 $3 0\n" + "R $3 V4 1" + ); + + rn.simplify (); + + EXPECT_EQ (rn.to_string (), + "R V1 V4 2" + ); +} + +TEST(network_simplify4) +{ + pex::RNetwork rn; + EXPECT_EQ (rn.to_string (), ""); + + pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1); + pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2); + pex::RNode *n3 = rn.create_node (pex::RNode::Internal, 3); + pex::RNode *n4 = rn.create_node (pex::RNode::VertexPort, 4); + + rn.create_element (1, n1, n4); + rn.create_element (1, n2, n1); + rn.create_element (1, n4, n3); + + EXPECT_EQ (rn.to_string (), + "R V1 V4 1\n" + "R $2 V1 1\n" + "R V4 $3 1" + ); + + rn.simplify (); + + EXPECT_EQ (rn.to_string (), + "R V1 V4 1" + ); +} From e9c2320f5decd6961a37d1f5b650102f899f2fe0 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 19 Apr 2025 23:59:45 +0200 Subject: [PATCH 24/58] WIP --- src/pex/pex/pexRExtractor.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pex/pex/pexRExtractor.cc b/src/pex/pex/pexRExtractor.cc index 1df03457b..d7d529dff 100644 --- a/src/pex/pex/pexRExtractor.cc +++ b/src/pex/pex/pexRExtractor.cc @@ -197,6 +197,7 @@ RNetwork::join_nodes (RNode *a, RNode *b) } } + a->location += b->location; remove_node (b); } From 88a1ccbcfb9bd546b90ed91a1e8c9d5dff4bdf9f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 20 Apr 2025 16:20:35 +0200 Subject: [PATCH 25/58] TriangulationRExtractor, some debugging, tests --- src/db/db/dbPLCTriangulation.cc | 2 +- src/pex/pex/pex.pro | 6 +- src/pex/pex/pexRExtractor.cc | 9 +- src/pex/pex/pexRExtractor.h | 50 +++- src/pex/pex/pexTriangulationRExtractor.cc | 221 ++++++++++++++++++ src/pex/pex/pexTriangulationRExtractor.h | 70 ++++++ src/pex/unit_tests/pexRExtractorTests.cc | 9 + .../pexTriangulationRExtractorTests.cc | 69 ++++++ src/pex/unit_tests/unit_tests.pro | 3 +- 9 files changed, 431 insertions(+), 8 deletions(-) create mode 100644 src/pex/pex/pexTriangulationRExtractor.cc create mode 100644 src/pex/pex/pexTriangulationRExtractor.h create mode 100644 src/pex/unit_tests/pexTriangulationRExtractorTests.cc diff --git a/src/db/db/dbPLCTriangulation.cc b/src/db/db/dbPLCTriangulation.cc index 3c9371fe4..07688f0c3 100644 --- a/src/db/db/dbPLCTriangulation.cc +++ b/src/db/db/dbPLCTriangulation.cc @@ -1043,7 +1043,7 @@ Triangulation::search_edges_crossing (Vertex *from, Vertex *to) if (e->has_vertex (vv)) { return result; } - if (e->crosses (edge)) { + if (e->crosses_including (edge)) { result.push_back (e); next_edge = e; break; diff --git a/src/pex/pex/pex.pro b/src/pex/pex/pex.pro index 251fb9935..145a1866e 100644 --- a/src/pex/pex/pex.pro +++ b/src/pex/pex/pex.pro @@ -10,12 +10,14 @@ SOURCES = \ pexForceLink.cc \ pexRExtractor.cc \ gsiDeclRExtractor.cc \ - pexSquareCountingRExtractor.cc + pexSquareCountingRExtractor.cc \ + pexTriangulationRExtractor.cc HEADERS = \ pexForceLink.h \ pexRExtractor.h \ - pexSquareCountingRExtractor.h + pexSquareCountingRExtractor.h \ + pexTriangulationRExtractor.h RESOURCES = \ diff --git a/src/pex/pex/pexRExtractor.cc b/src/pex/pex/pexRExtractor.cc index d7d529dff..db38fc948 100644 --- a/src/pex/pex/pexRExtractor.cc +++ b/src/pex/pex/pexRExtractor.cc @@ -134,7 +134,12 @@ RNetwork::create_node (RNode::node_type type, unsigned int port_index) RElement * RNetwork::create_element (double conductivity, RNode *a, RNode *b) { - auto i = m_elements_by_nodes.find (std::make_pair (a, b)); + std::pair key (a, b); + if (size_t (b) < size_t (a)) { + std::swap (key.first, key.second); + } + + auto i = m_elements_by_nodes.find (key); if (i != m_elements_by_nodes.end ()) { if (conductivity == pex::RElement::short_value () || i->second->conductivity == pex::RElement::short_value ()) { @@ -149,7 +154,7 @@ RNetwork::create_element (double conductivity, RNode *a, RNode *b) RElement *element = new RElement (this, conductivity, a, b); m_elements.push_back (element); - m_elements_by_nodes.insert (std::make_pair (std::make_pair (a, b), element)); + m_elements_by_nodes.insert (std::make_pair (key, element)); a->m_elements.push_back (element); element->m_ia = --a->m_elements.end (); diff --git a/src/pex/pex/pexRExtractor.h b/src/pex/pex/pexRExtractor.h index f1c4f4659..11e05e00d 100644 --- a/src/pex/pex/pexRExtractor.h +++ b/src/pex/pex/pexRExtractor.h @@ -142,6 +142,11 @@ class PEX_PUBLIC RNetwork : public tl::Object { public: + typedef tl::list node_list; + typedef node_list::const_iterator node_iterator; + typedef tl::list element_list; + typedef element_list::const_iterator element_iterator; + RNetwork (); ~RNetwork (); @@ -154,9 +159,50 @@ public: std::string to_string () const; + node_iterator begin_nodes () const + { + return m_nodes.begin (); + } + + node_iterator end_nodes () const + { + return m_nodes.end (); + } + + size_t num_nodes () const + { + return m_nodes.size (); + } + + size_t num_internal_nodes () const + { + size_t count = 0; + for (auto n = m_nodes.begin (); n != m_nodes.end (); ++n) { + if (n->type == pex::RNode::Internal) { + ++count; + } + } + return count; + } + + element_iterator begin_elements () const + { + return m_elements.begin (); + } + + element_iterator end_elements () const + { + return m_elements.end (); + } + + size_t num_elements () const + { + return m_elements.size (); + } + private: - tl::list m_nodes; - tl::list m_elements; + node_list m_nodes; + element_list m_elements; std::map, RElement *> m_elements_by_nodes; std::map, RNode *> m_nodes_by_type; diff --git a/src/pex/pex/pexTriangulationRExtractor.cc b/src/pex/pex/pexTriangulationRExtractor.cc new file mode 100644 index 000000000..d464e8fdb --- /dev/null +++ b/src/pex/pex/pexTriangulationRExtractor.cc @@ -0,0 +1,221 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "pexTriangulationRExtractor.h" +#include "dbBoxScanner.h" +#include "dbPolygonTools.h" +#include "tlIntervalMap.h" + +namespace pex +{ + +TriangulationRExtractor::TriangulationRExtractor (double dbu) +{ + m_dbu = dbu; + + m_tri_param.min_b = 0.3; + m_tri_param.max_area = 0.0; +} + +void +TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector &vertex_ports, const std::vector &polygon_ports, pex::RNetwork &rnetwork) +{ + rnetwork.clear (); + + db::CplxTrans trans = db::CplxTrans (m_dbu) * db::ICplxTrans (db::Trans (db::Point () - polygon.box ().center ())); + auto inv_trans = trans.inverted (); + + // NOTE: currently we treat polygon ports and points where the location is the center of the bounding box + std::vector vp = vertex_ports; + vp.reserve (vertex_ports.size () + polygon_ports.size ()); + for (auto pp = polygon_ports.begin (); pp != polygon_ports.end (); ++pp) { + vp.push_back (pp->box ().center ()); + } + + + db::plc::Graph plc; + db::plc::Triangulation tri (&plc); + + tri.triangulate (polygon, vp, m_tri_param, trans); + + // create a network node for each triangle node + + std::unordered_map vertex2node; + + size_t internal_node_id = 0; + + for (auto p = plc.begin (); p != plc.end (); ++p) { + + for (size_t iv = 0; iv < p->size (); ++iv) { + + const db::plc::Vertex *vertex = p->vertex (iv); + if (vertex2node.find (vertex) != vertex2node.end ()) { + continue; + } + + pex::RNode::node_type type = pex::RNode::Internal; + size_t port_index = 0; + + if (vertex->is_precious ()) { + size_t idx = vertex->id (); + if (idx >= vertex_ports.size ()) { + type = pex::RNode::PolygonPort; + port_index = size_t (idx) - vertex_ports.size (); + } else { + type = pex::RNode::VertexPort; + port_index = size_t (idx); + } + } else { + port_index = internal_node_id++; + } + + pex::RNode *n = rnetwork.create_node (type, port_index); + db::DPoint loc = *vertex; + n->location = db::DBox (loc, loc); + + vertex2node.insert (std::make_pair (vertex, n)); + + } + + } + + // produce the conductances for each triangle + + for (auto p = plc.begin (); p != plc.end (); ++p) { + create_conductances (*p, vertex2node, rnetwork); + } + + // eliminate internal nodes + + eliminate_all (rnetwork); +} + +void +TriangulationRExtractor::create_conductances (const db::plc::Polygon &tri, const std::unordered_map &vertex2node, RNetwork &rnetwork) +{ + tl_assert (tri.size () == 3); + + for (int i = 0; i < 3; ++i) { + + const db::plc::Vertex *pm1 = tri.vertex (i); + const db::plc::Vertex *p0 = tri.vertex (i + 1); + const db::plc::Vertex *p1 = tri.vertex (i + 2); + + double a = fabs (db::vprod (*pm1 - *p0, *p1 - *p0) * 0.5); + + double lm1 = (*p0 - *pm1).sq_length (); + double l0 = (*p1 - *p0).sq_length (); + double l1 = (*pm1 - *p1).sq_length (); + + double s = (l0 + l1 - lm1) / (8.0 * a); + + auto i0 = vertex2node.find (p0); + auto im1 = vertex2node.find (pm1); + rnetwork.create_element (s, i0->second, im1->second); + + } +} + +void +TriangulationRExtractor::eliminate_all (RNetwork &rnetwork) +{ + if (tl::verbosity () >= m_tri_param.base_verbosity + 10) { + tl::info << "Staring elimination with " << rnetwork.num_internal_nodes () << " internal nodes and " << rnetwork.num_elements () << " resistors"; + } + + unsigned int niter = 0; + std::vector to_eliminate; + + size_t nmax = 3; + while (nmax > 0) { + + bool another_loop = true; + while (another_loop) { + + size_t nmax_next = 0; + to_eliminate.clear (); + + for (auto n = rnetwork.begin_nodes (); n != rnetwork.end_nodes (); ++n) { + if (n->type == pex::RNode::Internal) { + size_t nn = n->elements ().size (); + if (nn <= nmax) { + to_eliminate.push_back (const_cast (n.operator-> ())); + } else if (nmax_next == 0 or nn < nmax_next) { + nmax_next = nn; + } + } + } + + if (to_eliminate.empty ()) { + + another_loop = false; + nmax = nmax_next; + + if (tl::verbosity () >= m_tri_param.base_verbosity + 10) { + tl::info << "Nothing left to eliminate with nmax=" << nmax; + } + + } else { + + for (auto n = to_eliminate.begin (); n != to_eliminate.end (); ++n) { + eliminate_node (*n, rnetwork); + } + + niter += 1; + + if (tl::verbosity () >= m_tri_param.base_verbosity + 10) { + tl::info << "Nodes left after iteration " << niter << " with nmax=" << nmax << ": " << rnetwork.num_internal_nodes () << " with " << rnetwork.num_elements () << " edges."; + } + + } + + } + + } +} + +void +TriangulationRExtractor::eliminate_node (pex::RNode *node, RNetwork &rnetwork) +{ + double s_sum = 0.0; + for (auto e = node->elements ().begin (); e != node->elements ().end (); ++e) { + s_sum += (*e)->conductivity; + } + + if (fabs (s_sum) > 1e-10) { + for (auto e = node->elements ().begin (); e != node->elements ().end (); ++e) { + auto ee = e; + ++ee; + for ( ; ee != node->elements ().end (); ++ee) { + pex::RNode *n1 = const_cast ((*e)->other (node)); + pex::RNode *n2 = const_cast ((*ee)->other (node)); + double c = (*e)->conductivity * (*ee)->conductivity / s_sum; + rnetwork.create_element (c, n1, n2); + } + } + } + + rnetwork.remove_node (node); +} + +} diff --git a/src/pex/pex/pexTriangulationRExtractor.h b/src/pex/pex/pexTriangulationRExtractor.h new file mode 100644 index 000000000..536d90f45 --- /dev/null +++ b/src/pex/pex/pexTriangulationRExtractor.h @@ -0,0 +1,70 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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_pexTriangulationRExtractor +#define HDR_pexTriangulationRExtractor + +#include "pexCommon.h" +#include "pexRExtractor.h" + +#include "dbPLCTriangulation.h" + +namespace pex +{ + +// @@@ doc +class PEX_PUBLIC TriangulationRExtractor + : public RExtractor +{ +public: + TriangulationRExtractor (double dbu); + + db::plc::TriangulationParameters &triangulation_parameters () + { + return m_tri_param; + } + + void set_dbu (double dbu) + { + m_dbu = dbu; + } + + double dbu () const + { + return m_dbu; + } + + virtual void extract (const db::Polygon &polygon, const std::vector &vertex_ports, const std::vector &polygon_ports, RNetwork &rnetwork); + +private: + db::plc::TriangulationParameters m_tri_param; + double m_dbu; + + void create_conductances (const db::plc::Polygon &tri, const std::unordered_map &vertex2node, RNetwork &rnetwork); + void eliminate_node (pex::RNode *node, RNetwork &rnetwork); + void eliminate_all (RNetwork &rnetwork); +}; + +} + +#endif + diff --git a/src/pex/unit_tests/pexRExtractorTests.cc b/src/pex/unit_tests/pexRExtractorTests.cc index d386ce621..1c2de17b7 100644 --- a/src/pex/unit_tests/pexRExtractorTests.cc +++ b/src/pex/unit_tests/pexRExtractorTests.cc @@ -56,6 +56,15 @@ TEST(network_basic) "R $2 $3 0.2" ); + pex::RElement *e23c = rn.create_element (5.0, n3, n2); + EXPECT_EQ (e23 == e23c, true); + + EXPECT_EQ (rn.to_string (), + "R $1 $2 2\n" + "R $1 $3 4\n" + "R $2 $3 0.1" + ); + rn.remove_element (e23); EXPECT_EQ (rn.to_string (), diff --git a/src/pex/unit_tests/pexTriangulationRExtractorTests.cc b/src/pex/unit_tests/pexTriangulationRExtractorTests.cc new file mode 100644 index 000000000..46609d1ec --- /dev/null +++ b/src/pex/unit_tests/pexTriangulationRExtractorTests.cc @@ -0,0 +1,69 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "pexTriangulationRExtractor.h" +#include "tlUnitTest.h" + +namespace +{ + +class TestableTriangulationRExtractor + : public pex::TriangulationRExtractor +{ +public: + TestableTriangulationRExtractor () + : pex::TriangulationRExtractor (0.001) + { } +}; + +} + +TEST(extraction) +{ + db::Point contour[] = { + db::Point (0, 0), + db::Point (0, 100), + db::Point (1000, 100), + db::Point (1000, 0) + }; + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + double dbu = 0.001; + + pex::RNetwork rn; + pex::TriangulationRExtractor rex (dbu); + + std::vector vertex_ports; + vertex_ports.push_back (db::Point (0, 50)); // V0 + vertex_ports.push_back (db::Point (1000, 50)); // V1 + + std::vector polygon_ports; + + rex.extract (poly, vertex_ports, polygon_ports, rn); + + EXPECT_EQ (rn.to_string (), + "R V1 V0 10.0938" + ) +} diff --git a/src/pex/unit_tests/unit_tests.pro b/src/pex/unit_tests/unit_tests.pro index 6c380c340..ad99cd169 100644 --- a/src/pex/unit_tests/unit_tests.pro +++ b/src/pex/unit_tests/unit_tests.pro @@ -8,7 +8,8 @@ include($$PWD/../../lib_ut.pri) SOURCES = \ pexRExtractorTests.cc \ - pexSquareCountingRExtractorTests.cc + pexSquareCountingRExtractorTests.cc \ + pexTriangulationRExtractorTests.cc INCLUDEPATH += $$TL_INC $$DB_INC $$GSI_INC $$PEX_INC DEPENDPATH += $$TL_INC $$DB_INC $$GSI_INC $$PEX_INC From f71210c64a5f76212b32745d3e752b76133da165 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 20 Apr 2025 19:09:24 +0200 Subject: [PATCH 26/58] WIP, more testcases, debugging --- src/db/db/dbPLC.cc | 10 +- src/db/db/dbPLCTriangulation.cc | 78 +++++--- src/db/db/dbPLCTriangulation.h | 64 ++++-- src/db/unit_tests/dbPLCTriangulationTests.cc | 28 +-- src/pex/pex/pexTriangulationRExtractor.cc | 166 ++++++++++++---- .../pexTriangulationRExtractorTests.cc | 188 ++++++++++++++++++ 6 files changed, 442 insertions(+), 92 deletions(-) diff --git a/src/db/db/dbPLC.cc b/src/db/db/dbPLC.cc index c44a96760..7a8be1455 100644 --- a/src/db/db/dbPLC.cc +++ b/src/db/db/dbPLC.cc @@ -300,8 +300,14 @@ Edge::crosses (const db::DEdge &e, const db::DEdge &other) bool Edge::crosses_including (const db::DEdge &e, const db::DEdge &other) { - return e.side_of (other.p1 ()) * e.side_of (other.p2 ()) <= 0 && - other.side_of (e.p1 ()) * other.side_of (e.p2 ()) <= 0; + int sa = e.side_of (other.p1 ()); + int sb = e.side_of (other.p2 ()); + int s1 = sa * sb; + + int s2 = other.side_of (e.p1 ()) * other.side_of (e.p2 ()); + + // e can end on other and so can other end on e, but both may not be coincident + return s1 <= 0 && s2 <= 0 && ! (sa == 0 && sb == 0); } db::DPoint diff --git a/src/db/db/dbPLCTriangulation.cc b/src/db/db/dbPLCTriangulation.cc index 07688f0c3..f8f4fdcc9 100644 --- a/src/db/db/dbPLCTriangulation.cc +++ b/src/db/db/dbPLCTriangulation.cc @@ -1099,14 +1099,19 @@ Triangulation::find_vertexes_along_line (const db::DPoint &p1, const db::DPoint } std::vector result; + result.push_back (v); while (v) { Vertex *vn = 0; for (auto e = v->begin_edges (); e != v->end_edges (); ++e) { Vertex *vv = (*e)->other (v); - if (db::vprod_sign (e12.d (), *vv - *v) == 0 && db::sprod_sign (e12.d (), *vv - *v) > 0 && db::sprod_sign (e12.d (), *vv - e12.p2 ()) < 0) { + int cs = 0; + if (db::vprod_sign (e12.d (), *vv - *v) == 0 && db::sprod_sign (e12.d (), *vv - *v) > 0 && (cs = db::sprod_sign (e12.d (), *vv - e12.p2 ())) <= 0) { result.push_back (vv); - vn = vv; + if (cs < 0) { + // continue searching + vn = vv; + } break; } } @@ -1377,8 +1382,6 @@ Triangulation::make_contours (const Poly &poly, const Trans &trans, std::vector< void Triangulation::create_constrained_delaunay (const db::Region ®ion, const CplxTrans &trans) { - clear (); - std::vector > edge_contours; for (auto p = region.begin_merged (); ! p.at_end (); ++p) { @@ -1389,15 +1392,8 @@ Triangulation::create_constrained_delaunay (const db::Region ®ion, const Cplx } void -Triangulation::create_constrained_delaunay (const db::Polygon &p, const std::vector &vertexes, const CplxTrans &trans) +Triangulation::create_constrained_delaunay (const db::Polygon &p, const CplxTrans &trans) { - clear (); - - unsigned int id = 0; - for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { - insert_point (trans * *v)->set_is_precious (true, id++); - } - std::vector > edge_contours; make_contours (p, trans, edge_contours); @@ -1405,15 +1401,8 @@ Triangulation::create_constrained_delaunay (const db::Polygon &p, const std::vec } void -Triangulation::create_constrained_delaunay (const db::DPolygon &p, const std::vector &vertexes, const DCplxTrans &trans) +Triangulation::create_constrained_delaunay (const db::DPolygon &p, const DCplxTrans &trans) { - clear (); - - unsigned int id = 0; - for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { - insert_point (trans * *v)->set_is_precious (true, id++); - } - std::vector > edge_contours; make_contours (p, trans, edge_contours); @@ -1458,6 +1447,8 @@ Triangulation::triangulate (const db::Region ®ion, const TriangulationParamet { tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); + clear (); + create_constrained_delaunay (region, db::CplxTrans (dbu)); refine (parameters); } @@ -1467,6 +1458,24 @@ Triangulation::triangulate (const db::Region ®ion, const TriangulationParamet { tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); + clear (); + + create_constrained_delaunay (region, trans); + refine (parameters); +} + +void +Triangulation::triangulate (const db::Region ®ion, const std::vector &vertexes, const TriangulationParameters ¶meters, const db::CplxTrans &trans) +{ + tl::SelfTimer timer (tl::verbosity () > parameters.base_verbosity, "Triangles::triangulate"); + + clear (); + + unsigned int id = 0; + for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { + insert_point (trans * *v)->set_is_precious (true, id++); + } + create_constrained_delaunay (region, trans); refine (parameters); } @@ -1482,7 +1491,16 @@ Triangulation::triangulate (const db::Polygon &poly, const std::vector parameters.base_verbosity, "Triangles::triangulate"); - create_constrained_delaunay (poly, vertexes, db::CplxTrans (dbu)); + db::CplxTrans trans (dbu); + + clear (); + + unsigned int id = 0; + for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { + insert_point (trans * *v)->set_is_precious (true, id++); + } + + create_constrained_delaunay (poly, trans); refine (parameters); } @@ -1497,7 +1515,14 @@ Triangulation::triangulate (const db::Polygon &poly, const std::vector parameters.base_verbosity, "Triangles::triangulate"); - create_constrained_delaunay (poly, vertexes, trans); + clear (); + + unsigned int id = 0; + for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { + insert_point (trans * *v)->set_is_precious (true, id++); + } + + create_constrained_delaunay (poly, trans); refine (parameters); } @@ -1512,7 +1537,14 @@ Triangulation::triangulate (const db::DPolygon &poly, const std::vector parameters.base_verbosity, "Triangles::triangulate"); - create_constrained_delaunay (poly, vertexes, trans); + clear (); + + unsigned int id = 0; + for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { + insert_point (trans * *v)->set_is_precious (true, id++); + } + + create_constrained_delaunay (poly, trans); refine (parameters); } diff --git a/src/db/db/dbPLCTriangulation.h b/src/db/db/dbPLCTriangulation.h index ea0830b00..db97f9651 100644 --- a/src/db/db/dbPLCTriangulation.h +++ b/src/db/db/dbPLCTriangulation.h @@ -142,6 +142,7 @@ public: // more versions void triangulate (const db::Region ®ion, const TriangulationParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); + void triangulate (const db::Region ®ion, const std::vector &vertexes, const TriangulationParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); void triangulate (const db::Polygon &poly, const TriangulationParameters ¶meters, double dbu = 1.0); void triangulate (const db::Polygon &poly, const std::vector &vertexes, const TriangulationParameters ¶meters, double dbu = 1.0); void triangulate (const db::Polygon &poly, const TriangulationParameters ¶meters, const db::CplxTrans &trans = db::CplxTrans ()); @@ -181,6 +182,17 @@ public: */ std::vector find_vertexes_along_line (const db::DPoint &p1, const db::DPoint &p2) const; + /** + * @brief Removes the outside triangles. + * + * This method is useful in combination with the "remove_outside_triangles = false" triangulation + * parameter. In this mode, outside triangles are not removed after triangulation (the + * triangulated area is convex). This enables use of the "find" functions. + * + * This method can be used to finally remove the outside triangles if no longer needed. + */ + void remove_outside_triangles (); + /** * @brief Statistics: number of flips (fixing) */ @@ -197,6 +209,37 @@ public: return m_hops; } + /** + * @brief Creates a constrained Delaunay triangulation from the given Region + * + * This method is used internally by the "triangulation" method to create the basic triangulation, + * followed by a "refine" step. + */ + void create_constrained_delaunay (const db::Region ®ion, const db::CplxTrans &trans = db::CplxTrans ()); + + /** + * @brief Creates a constrained Delaunay triangulation from the given Polygon + * + * This method is used internally by the "triangulation" method to create the basic triangulation, + * followed by a "refine" step. + */ + void create_constrained_delaunay (const db::Polygon &poly, const db::CplxTrans &trans = db::CplxTrans ()); + + /** + * @brief Creates a constrained Delaunay triangulation from the given DPolygon + * + * This method is used internally by the "triangulation" method to create the basic triangulation, + * followed by a "refine" step. + */ + void create_constrained_delaunay (const db::DPolygon &poly, const DCplxTrans &trans = db::DCplxTrans ()); + + /** + * @brief Refines the triangulation using the given parameters + * + * This method is used internally by the "triangulation" method after creating the basic triangulation. + */ + void refine (const TriangulationParameters ¶m); + protected: /** * @brief Checks the polygon graph for consistency @@ -254,26 +297,6 @@ protected: */ void constrain (const std::vector > &contours); - /** - * @brief Removes the outside triangles. - */ - void remove_outside_triangles (); - - /** - * @brief Creates a constrained Delaunay triangulation from the given Region - */ - void create_constrained_delaunay (const db::Region ®ion, const db::CplxTrans &trans = db::CplxTrans ()); - - /** - * @brief Creates a constrained Delaunay triangulation from the given Polygon - */ - void create_constrained_delaunay (const db::Polygon &poly, const std::vector &vertexes, const db::CplxTrans &trans = db::CplxTrans ()); - - /** - * @brief Creates a constrained Delaunay triangulation from the given DPolygon - */ - void create_constrained_delaunay (const db::DPolygon &poly, const std::vector &vertexes, const DCplxTrans &trans); - /** * @brief Returns a value indicating whether the edge is "illegal" (violates the Delaunay criterion) */ @@ -308,7 +331,6 @@ private: void insert_new_vertex(Vertex *vertex, std::list > *new_triangles_out); std::vector ensure_edge_inner (Vertex *from, Vertex *to); void join_edges (std::vector &edges); - void refine (const TriangulationParameters ¶m); }; } // namespace plc diff --git a/src/db/unit_tests/dbPLCTriangulationTests.cc b/src/db/unit_tests/dbPLCTriangulationTests.cc index 9253eb0ce..f31a049b2 100644 --- a/src/db/unit_tests/dbPLCTriangulationTests.cc +++ b/src/db/unit_tests/dbPLCTriangulationTests.cc @@ -155,24 +155,28 @@ TEST(collect_vertexes) tris.insert_point (0.5, 0.5); std::vector vertexes = tris.find_vertexes_along_line (db::DPoint (0, 0), db::DPoint (1.5, 1.5)); - EXPECT_EQ (vertexes.size (), size_t (3)); - if (vertexes.size () >= size_t (3)) { - EXPECT_EQ (vertexes [0]->to_string (), "(0.2, 0.2)"); - EXPECT_EQ (vertexes [1]->to_string (), "(0.5, 0.5)"); - EXPECT_EQ (vertexes [2]->to_string (), "(1, 1)"); + EXPECT_EQ (vertexes.size (), size_t (4)); + if (vertexes.size () >= size_t (4)) { + EXPECT_EQ (vertexes [0]->to_string (), "(0, 0)"); + EXPECT_EQ (vertexes [1]->to_string (), "(0.2, 0.2)"); + EXPECT_EQ (vertexes [2]->to_string (), "(0.5, 0.5)"); + EXPECT_EQ (vertexes [3]->to_string (), "(1, 1)"); } vertexes = tris.find_vertexes_along_line (db::DPoint (0, 0), db::DPoint (1.0, 1.0)); - EXPECT_EQ (vertexes.size (), size_t (2)); - if (vertexes.size () >= size_t (2)) { - EXPECT_EQ (vertexes [0]->to_string (), "(0.2, 0.2)"); - EXPECT_EQ (vertexes [1]->to_string (), "(0.5, 0.5)"); + EXPECT_EQ (vertexes.size (), size_t (4)); + if (vertexes.size () >= size_t (4)) { + EXPECT_EQ (vertexes [0]->to_string (), "(0, 0)"); + EXPECT_EQ (vertexes [1]->to_string (), "(0.2, 0.2)"); + EXPECT_EQ (vertexes [2]->to_string (), "(0.5, 0.5)"); + EXPECT_EQ (vertexes [3]->to_string (), "(1, 1)"); } vertexes = tris.find_vertexes_along_line (db::DPoint (1, 1), db::DPoint (0.25, 0.25)); - EXPECT_EQ (vertexes.size (), size_t (1)); - if (vertexes.size () >= size_t (1)) { - EXPECT_EQ (vertexes [0]->to_string (), "(0.5, 0.5)"); + EXPECT_EQ (vertexes.size (), size_t (2)); + if (vertexes.size () >= size_t (2)) { + EXPECT_EQ (vertexes [0]->to_string (), "(1, 1)"); + EXPECT_EQ (vertexes [1]->to_string (), "(0.5, 0.5)"); } } diff --git a/src/pex/pex/pexTriangulationRExtractor.cc b/src/pex/pex/pexTriangulationRExtractor.cc index d464e8fdb..cce1e4150 100644 --- a/src/pex/pex/pexTriangulationRExtractor.cc +++ b/src/pex/pex/pexTriangulationRExtractor.cc @@ -43,24 +43,74 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< rnetwork.clear (); db::CplxTrans trans = db::CplxTrans (m_dbu) * db::ICplxTrans (db::Trans (db::Point () - polygon.box ().center ())); - auto inv_trans = trans.inverted (); - - // NOTE: currently we treat polygon ports and points where the location is the center of the bounding box - std::vector vp = vertex_ports; - vp.reserve (vertex_ports.size () + polygon_ports.size ()); - for (auto pp = polygon_ports.begin (); pp != polygon_ports.end (); ++pp) { - vp.push_back (pp->box ().center ()); - } - db::plc::Graph plc; db::plc::Triangulation tri (&plc); - tri.triangulate (polygon, vp, m_tri_param, trans); + std::unordered_map pp_vertexes; - // create a network node for each triangle node + if (polygon_ports.empty ()) { + + tri.triangulate (polygon, vertex_ports, m_tri_param, trans); + + } else { + + // Subtract the polygon ports from the original polygon and compute the intersection. + // Hence we have coincident edges that we can use to identify the nodes that are + // connected for the polygon ports + + db::Region org (polygon); + db::Region pp (polygon_ports.begin (), polygon_ports.end ()); + + db::Region residual_poly = org - pp; + + // We must not remove outside triangles yet, as we need them for "find_vertexes_along_line" + db::plc::TriangulationParameters param = m_tri_param; + param.remove_outside_triangles = false; + + tri.clear (); + + unsigned int id = 0; + for (auto v = vertex_ports.begin (); v != vertex_ports.end (); ++v) { + tri.insert_point (trans * *v)->set_is_precious (true, id++); + } + + for (auto p = polygon_ports.begin (); p != polygon_ports.end (); ++p) { + // create vertexes for the port polygon vertexes - this ensures we will find vertexes + // on the edges of the polygons - yet, they may be outside of the original polygon. + // In that case they will not be considered + for (auto e = p->begin_edge (); !e.at_end (); ++e) { + tri.insert_point (trans * (*e).p1 ())->set_is_precious (true, id); + } + } + + // perform the triangulation + + tri.create_constrained_delaunay (residual_poly, trans); + tri.refine (param); + + // identify the vertexes present for the polygon port -> store them inside pp_vertexes + + for (auto p = polygon_ports.begin (); p != polygon_ports.end (); ++p) { + for (auto e = p->begin_edge (); !e.at_end (); ++e) { + // NOTE: this currently only works if one of the end points is an actual + // vertex. + auto vport = tri.find_vertexes_along_line (trans * (*e).p1 (), trans * (*e).p2 ()); + for (auto v = vport.begin (); v != vport.end (); ++v) { + pp_vertexes.insert (std::make_pair (*v, p - polygon_ports.begin ())); + } + } + } + + tri.remove_outside_triangles (); + + } + + // Create a network node for each triangle node. std::unordered_map vertex2node; + std::unordered_set vports_present; + std::map pport_nodes; size_t internal_node_id = 0; @@ -73,28 +123,71 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< continue; } - pex::RNode::node_type type = pex::RNode::Internal; - size_t port_index = 0; + pex::RNode *n = 0; - if (vertex->is_precious ()) { - size_t idx = vertex->id (); - if (idx >= vertex_ports.size ()) { - type = pex::RNode::PolygonPort; - port_index = size_t (idx) - vertex_ports.size (); + auto ipp = pp_vertexes.find (vertex); + if (ipp != pp_vertexes.end ()) { + + size_t port_index = ipp->second; + auto pn = pport_nodes.find (port_index); + if (pn != pport_nodes.end ()) { + n = pn->second; } else { - type = pex::RNode::VertexPort; - port_index = size_t (idx); + n = rnetwork.create_node (pex::RNode::PolygonPort, port_index); + pport_nodes.insert (std::make_pair (port_index, n)); + n->location = trans * polygon_ports [port_index].box (); } + + } else if (vertex->is_precious ()) { + + size_t port_index = size_t (vertex->id ()); + if (port_index < vertex_ports.size ()) { + n = rnetwork.create_node (pex::RNode::VertexPort, port_index); + n->location = db::DBox (*vertex, *vertex); + vports_present.insert (port_index); + } + } else { - port_index = internal_node_id++; + + n = rnetwork.create_node (pex::RNode::Internal, internal_node_id++); + n->location = db::DBox (*vertex, *vertex); + } - pex::RNode *n = rnetwork.create_node (type, port_index); - db::DPoint loc = *vertex; - n->location = db::DBox (loc, loc); + if (n) { + vertex2node.insert (std::make_pair (vertex, n)); + } - vertex2node.insert (std::make_pair (vertex, n)); + } + } + + // check for vertex ports not assigned to a node + // -> this may be an indication for a vertex port inside a polygon port + + for (size_t iv = 0; iv < vertex_ports.size (); ++iv) { + + if (vports_present.find (iv) != vports_present.end ()) { + continue; + } + + db::Point vp = vertex_ports [iv]; + + for (auto p = polygon_ports.begin (); p != polygon_ports.end (); ++p) { + + if (p->box ().contains (vp) && db::inside_poly_test (*p) (vp) >= 0) { + + auto ip = pport_nodes.find (p - polygon_ports.begin ()); + if (ip != pport_nodes.end ()) { + + // create a new vertex port and short it to the polygon port + auto n = rnetwork.create_node (pex::RNode::VertexPort, iv); + n->location = db::DBox (trans * vp, trans * vp); + rnetwork.create_element (pex::RElement::short_value (), n, ip->second); + + } + + } } } @@ -121,17 +214,22 @@ TriangulationRExtractor::create_conductances (const db::plc::Polygon &tri, const const db::plc::Vertex *p0 = tri.vertex (i + 1); const db::plc::Vertex *p1 = tri.vertex (i + 2); - double a = fabs (db::vprod (*pm1 - *p0, *p1 - *p0) * 0.5); - - double lm1 = (*p0 - *pm1).sq_length (); - double l0 = (*p1 - *p0).sq_length (); - double l1 = (*pm1 - *p1).sq_length (); - - double s = (l0 + l1 - lm1) / (8.0 * a); - auto i0 = vertex2node.find (p0); auto im1 = vertex2node.find (pm1); - rnetwork.create_element (s, i0->second, im1->second); + + if (i0->second != im1->second) { + + double a = fabs (db::vprod (*pm1 - *p0, *p1 - *p0) * 0.5); + + double lm1 = (*p0 - *pm1).sq_length (); + double l0 = (*p1 - *p0).sq_length (); + double l1 = (*pm1 - *p1).sq_length (); + + double s = (l0 + l1 - lm1) / (8.0 * a); + + rnetwork.create_element (s, i0->second, im1->second); + + } } } diff --git a/src/pex/unit_tests/pexTriangulationRExtractorTests.cc b/src/pex/unit_tests/pexTriangulationRExtractorTests.cc index 46609d1ec..de2074322 100644 --- a/src/pex/unit_tests/pexTriangulationRExtractorTests.cc +++ b/src/pex/unit_tests/pexTriangulationRExtractorTests.cc @@ -67,3 +67,191 @@ TEST(extraction) "R V1 V0 10.0938" ) } + +TEST(extraction_with_polygon_ports) +{ + db::Point contour[] = { + db::Point (0, 0), + db::Point (0, 100), + db::Point (1000, 100), + db::Point (1000, 0) + }; + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + double dbu = 0.001; + + pex::RNetwork rn; + pex::TriangulationRExtractor rex (dbu); + + std::vector vertex_ports; + + std::vector polygon_ports; + polygon_ports.push_back (db::Polygon (db::Box (-100, 0, 0, 100))); + polygon_ports.push_back (db::Polygon (db::Box (1000, 0, 1100, 100))); + + rex.extract (poly, vertex_ports, polygon_ports, rn); + + EXPECT_EQ (rn.to_string (), + "R P1 P0 10" + ) +} + +TEST(extraction_with_polygon_ports_inside) +{ + db::Point contour[] = { + db::Point (-100, 0), + db::Point (-100, 100), + db::Point (1100, 100), + db::Point (1100, 0) + }; + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + double dbu = 0.001; + + pex::RNetwork rn; + pex::TriangulationRExtractor rex (dbu); + + std::vector vertex_ports; + + std::vector polygon_ports; + polygon_ports.push_back (db::Polygon (db::Box (-100, 0, 0, 100))); + polygon_ports.push_back (db::Polygon (db::Box (1000, 0, 1100, 100))); + + rex.extract (poly, vertex_ports, polygon_ports, rn); + + EXPECT_EQ (rn.to_string (), + "R P1 P0 10" + ) +} + +TEST(extraction_split_by_ports) +{ + db::Point contour[] = { + db::Point (-100, 0), + db::Point (-100, 100), + db::Point (1100, 100), + db::Point (1100, 0) + }; + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + double dbu = 0.001; + + pex::RNetwork rn; + pex::TriangulationRExtractor rex (dbu); + + std::vector vertex_ports; + + std::vector polygon_ports; + polygon_ports.push_back (db::Polygon (db::Box (-100, 0, 0, 100))); + polygon_ports.push_back (db::Polygon (db::Box (1100, 0, 1200, 100))); + polygon_ports.push_back (db::Polygon (db::Box (500, 0, 600, 100))); + + rex.extract (poly, vertex_ports, polygon_ports, rn); + + EXPECT_EQ (rn.to_string (), + "R P2 P0 5\n" + "R P1 P2 5" + ) +} + +TEST(extraction_split_by_butting_port) +{ + db::Point contour[] = { + db::Point (-100, 0), + db::Point (-100, 100), + db::Point (1100, 100), + db::Point (1100, 0) + }; + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + double dbu = 0.001; + + pex::RNetwork rn; + pex::TriangulationRExtractor rex (dbu); + + std::vector vertex_ports; + + std::vector polygon_ports; + polygon_ports.push_back (db::Polygon (db::Box (-100, 0, 0, 100))); + polygon_ports.push_back (db::Polygon (db::Box (1100, 0, 1200, 100))); + polygon_ports.push_back (db::Polygon (db::Box (500, 100, 600, 200))); + + rex.extract (poly, vertex_ports, polygon_ports, rn); + + EXPECT_EQ (rn.to_string (), + "R P2 P0 4.84211\n" + "R P1 P2 4.84211\n" + "R P0 P1 281.111" + ) +} + +TEST(extraction_with_outside_polygon_port) +{ + db::Point contour[] = { + db::Point (-100, 0), + db::Point (-100, 100), + db::Point (1100, 100), + db::Point (1100, 0) + }; + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + double dbu = 0.001; + + pex::RNetwork rn; + pex::TriangulationRExtractor rex (dbu); + + std::vector vertex_ports; + + std::vector polygon_ports; + polygon_ports.push_back (db::Polygon (db::Box (-100, 0, 0, 100))); + polygon_ports.push_back (db::Polygon (db::Box (1100, 0, 1200, 100))); + polygon_ports.push_back (db::Polygon (db::Box (500, 200, 600, 300))); + + rex.extract (poly, vertex_ports, polygon_ports, rn); + + EXPECT_EQ (rn.to_string (), + "R P1 P0 11" + ) +} + +TEST(extraction_with_polygon_ports_and_vertex_port_inside) +{ + db::Point contour[] = { + db::Point (-100, 0), + db::Point (-100, 100), + db::Point (1100, 100), + db::Point (1100, 0) + }; + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + double dbu = 0.001; + + pex::RNetwork rn; + pex::TriangulationRExtractor rex (dbu); + + std::vector vertex_ports; + vertex_ports.push_back (db::Point (-50, 50)); + + std::vector polygon_ports; + polygon_ports.push_back (db::Polygon (db::Box (-100, 0, 0, 100))); + polygon_ports.push_back (db::Polygon (db::Box (1000, 0, 1100, 100))); + + rex.extract (poly, vertex_ports, polygon_ports, rn); + + EXPECT_EQ (rn.to_string (), + "R V0 P0 0\n" // shorted because V0 is inside P0 + "R P1 P0 10" + ) +} From 7f0b2d532de85dda05fc4ba88eeb3c9d688d6f2b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 20 Apr 2025 20:07:47 +0200 Subject: [PATCH 27/58] Bugfixes --- src/db/db/dbPLC.cc | 1 + src/pex/pex/pexSquareCountingRExtractor.cc | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/db/db/dbPLC.cc b/src/db/db/dbPLC.cc index 7a8be1455..7fbb26b44 100644 --- a/src/db/db/dbPLC.cc +++ b/src/db/db/dbPLC.cc @@ -443,6 +443,7 @@ Polygon::init () if (area > db::epsilon) { std::reverse (mp_v.begin (), mp_v.end ()); std::reverse (mp_e.begin (), mp_e.end ()); + std::rotate (mp_e.begin (), ++mp_e.begin (), mp_e.end ()); } // link the polygon to the edges diff --git a/src/pex/pex/pexSquareCountingRExtractor.cc b/src/pex/pex/pexSquareCountingRExtractor.cc index 5477abdb0..de6400491 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.cc +++ b/src/pex/pex/pexSquareCountingRExtractor.cc @@ -264,8 +264,9 @@ SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector // 2. vertex ports for (size_t i = 0; i < plc_poly->internal_vertexes (); ++i) { - db::Point loc = inv_trans * *plc_poly->internal_vertex (i); - ports.push_back (std::make_pair (PortDefinition (pex::RNode::VertexPort, loc, i), (pex::RNode *) 0)); + auto v = plc_poly->internal_vertex (i); + db::Point loc = inv_trans * *v; + ports.push_back (std::make_pair (PortDefinition (pex::RNode::VertexPort, loc, v->id ()), (pex::RNode *) 0)); } // 3. polygon ports From 9bf03390de4a7aabbdf15ebc2e34e68ff0a954f8 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 20 Apr 2025 21:23:01 +0200 Subject: [PATCH 28/58] More robust output for tests, some debugging --- src/db/db/gsiDeclDbTrans.cc | 4 +- src/pex/pex/pexRExtractor.cc | 20 +++-- src/pex/pex/pexTriangulationRExtractor.cc | 6 +- src/pex/unit_tests/pexRExtractorTests.cc | 14 ++-- .../pexSquareCountingRExtractorTests.cc | 8 +- .../pexTriangulationRExtractorTests.cc | 74 +++++++++++++++++-- 6 files changed, 97 insertions(+), 29 deletions(-) diff --git a/src/db/db/gsiDeclDbTrans.cc b/src/db/db/gsiDeclDbTrans.cc index f9c579986..27938b0d4 100644 --- a/src/db/db/gsiDeclDbTrans.cc +++ b/src/db/db/gsiDeclDbTrans.cc @@ -521,7 +521,7 @@ struct trans_defs method ("disp", (const vector_type &(C::*) () const) &C::disp, "@brief Gets to the displacement vector\n" "\n" - "Staring with version 0.25 the displacement type is a vector." + "Starting with version 0.25 the displacement type is a vector." ) + method ("rot", &C::rot, "@brief Gets the angle/mirror code\n" @@ -553,7 +553,7 @@ struct trans_defs "@param u The new displacement\n" "\n" "This method was introduced in version 0.20.\n" - "Staring with version 0.25 the displacement type is a vector." + "Starting with version 0.25 the displacement type is a vector." ) + method_ext ("mirror=", &set_mirror, arg ("m"), "@brief Sets the mirror flag\n" diff --git a/src/pex/pex/pexRExtractor.cc b/src/pex/pex/pexRExtractor.cc index db38fc948..02b5dbf59 100644 --- a/src/pex/pex/pexRExtractor.cc +++ b/src/pex/pex/pexRExtractor.cc @@ -52,19 +52,25 @@ RNode::to_string () const std::string RElement::to_string () const { - std::string res = "R "; + std::string na; if (a ()) { - res += a ()->to_string (); + na = a ()->to_string (); } else { - res += "(nil)"; + na = "(nil)"; } - res += " "; + + std::string nb; if (b ()) { - res += b ()->to_string (); + nb = b ()->to_string (); } else { - res += "(nil)"; + nb = "(nil)"; } - res += " "; + + if (nb < na) { + std::swap (na, nb); + } + + std::string res = "R " + na + " " + nb + " "; res += tl::sprintf ("%.6g", resistance ()); return res; } diff --git a/src/pex/pex/pexTriangulationRExtractor.cc b/src/pex/pex/pexTriangulationRExtractor.cc index cce1e4150..5a7a87047 100644 --- a/src/pex/pex/pexTriangulationRExtractor.cc +++ b/src/pex/pex/pexTriangulationRExtractor.cc @@ -42,6 +42,8 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< { rnetwork.clear (); + tl::SelfTimer timer (tl::verbosity () >= m_tri_param.base_verbosity + 1, "Extracting resistor network from polygon (TriangulationRExtractor)"); + db::CplxTrans trans = db::CplxTrans (m_dbu) * db::ICplxTrans (db::Trans (db::Point () - polygon.box ().center ())); db::plc::Graph plc; @@ -55,6 +57,8 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< } else { + tl::SelfTimer timer_tri (tl::verbosity () >= m_tri_param.base_verbosity + 11, "Triangulation step"); + // Subtract the polygon ports from the original polygon and compute the intersection. // Hence we have coincident edges that we can use to identify the nodes that are // connected for the polygon ports @@ -238,7 +242,7 @@ void TriangulationRExtractor::eliminate_all (RNetwork &rnetwork) { if (tl::verbosity () >= m_tri_param.base_verbosity + 10) { - tl::info << "Staring elimination with " << rnetwork.num_internal_nodes () << " internal nodes and " << rnetwork.num_elements () << " resistors"; + tl::info << "Starting elimination with " << rnetwork.num_internal_nodes () << " internal nodes and " << rnetwork.num_elements () << " resistors"; } unsigned int niter = 0; diff --git a/src/pex/unit_tests/pexRExtractorTests.cc b/src/pex/unit_tests/pexRExtractorTests.cc index 1c2de17b7..b0ace9056 100644 --- a/src/pex/unit_tests/pexRExtractorTests.cc +++ b/src/pex/unit_tests/pexRExtractorTests.cc @@ -97,7 +97,7 @@ TEST(network_simplify1) rn.create_element (1, n1, n3); EXPECT_EQ (rn.to_string (), - "R V1 $2 1\n" + "R $2 V1 1\n" "R $2 V3 0\n" "R V1 V3 1" ); @@ -126,7 +126,7 @@ TEST(network_simplify2) rn.create_element (1, n3, n5); EXPECT_EQ (rn.to_string (), - "R V1 $2 1\n" + "R $2 V1 1\n" "R $2 $3 0\n" "R $3 V4 1\n" "R $3 V5 1" @@ -135,9 +135,9 @@ TEST(network_simplify2) rn.simplify (); EXPECT_EQ (rn.to_string (), - "R V1 $2 1\n" - "R V4 $2 1\n" - "R V5 $2 1" + "R $2 V1 1\n" + "R $2 V4 1\n" + "R $2 V5 1" ); } @@ -156,7 +156,7 @@ TEST(network_simplify3) rn.create_element (1, n3, n4); EXPECT_EQ (rn.to_string (), - "R V1 $2 1\n" + "R $2 V1 1\n" "R $2 $3 0\n" "R $3 V4 1" ); @@ -185,7 +185,7 @@ TEST(network_simplify4) EXPECT_EQ (rn.to_string (), "R V1 V4 1\n" "R $2 V1 1\n" - "R V4 $3 1" + "R $3 V4 1" ); rn.simplify (); diff --git a/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc b/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc index 1e4d7e326..77af14a1e 100644 --- a/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc +++ b/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc @@ -98,9 +98,9 @@ TEST(basic) // Same network, but opposite order. $1 and $2 are shorted, hence can be swapped. EXPECT_EQ (rn.to_string (), - "R $3 $1 1\n" + "R $1 $3 1\n" "R $1 $2 0\n" - "R $2 $0 0.390865" + "R $0 $2 0.390865" ); } @@ -135,8 +135,8 @@ TEST(extraction) rex.extract (poly, vertex_ports, polygon_ports, rn); EXPECT_EQ (rn.to_string (), - "R V0 $0 0.0952381\n" + "R $0 V0 0.0952381\n" "R $0 V1 0.166667\n" - "R P0 $0 0.117647" + "R $0 P0 0.117647" ) } diff --git a/src/pex/unit_tests/pexTriangulationRExtractorTests.cc b/src/pex/unit_tests/pexTriangulationRExtractorTests.cc index de2074322..b2bfff50c 100644 --- a/src/pex/unit_tests/pexTriangulationRExtractorTests.cc +++ b/src/pex/unit_tests/pexTriangulationRExtractorTests.cc @@ -64,7 +64,7 @@ TEST(extraction) rex.extract (poly, vertex_ports, polygon_ports, rn); EXPECT_EQ (rn.to_string (), - "R V1 V0 10.0938" + "R V0 V1 10.0938" ) } @@ -94,7 +94,7 @@ TEST(extraction_with_polygon_ports) rex.extract (poly, vertex_ports, polygon_ports, rn); EXPECT_EQ (rn.to_string (), - "R P1 P0 10" + "R P0 P1 10" ) } @@ -124,7 +124,7 @@ TEST(extraction_with_polygon_ports_inside) rex.extract (poly, vertex_ports, polygon_ports, rn); EXPECT_EQ (rn.to_string (), - "R P1 P0 10" + "R P0 P1 10" ) } @@ -155,7 +155,7 @@ TEST(extraction_split_by_ports) rex.extract (poly, vertex_ports, polygon_ports, rn); EXPECT_EQ (rn.to_string (), - "R P2 P0 5\n" + "R P0 P2 5\n" "R P1 P2 5" ) } @@ -187,7 +187,7 @@ TEST(extraction_split_by_butting_port) rex.extract (poly, vertex_ports, polygon_ports, rn); EXPECT_EQ (rn.to_string (), - "R P2 P0 4.84211\n" + "R P0 P2 4.84211\n" "R P1 P2 4.84211\n" "R P0 P1 281.111" ) @@ -220,7 +220,7 @@ TEST(extraction_with_outside_polygon_port) rex.extract (poly, vertex_ports, polygon_ports, rn); EXPECT_EQ (rn.to_string (), - "R P1 P0 11" + "R P0 P1 11" ) } @@ -251,7 +251,65 @@ TEST(extraction_with_polygon_ports_and_vertex_port_inside) rex.extract (poly, vertex_ports, polygon_ports, rn); EXPECT_EQ (rn.to_string (), - "R V0 P0 0\n" // shorted because V0 is inside P0 - "R P1 P0 10" + "R P0 V0 0\n" // shorted because V0 is inside P0 + "R P0 P1 10" ) } + +static db::Polygon ellipse (const db::Box &box, int npoints) +{ + npoints = std::max (3, std::min (10000000, npoints)); + + std::vector pts; + pts.reserve (npoints); + + double da = M_PI * 2.0 / npoints; + for (int i = 0; i < npoints; ++i) { + double x = box.center ().x () - box.width () * 0.5 * cos (da * i); + double y = box.center ().y () + box.height () * 0.5 * sin (da * i); + pts.push_back (db::Point (x, y)); + } + + db::Polygon c; + c.assign_hull (pts.begin (), pts.end (), false); + return c; +} + +TEST(extraction_analytic_disc) +{ + db::Coord r1 = 2000; + db::Coord r2 = 10000; + db::Coord r2pin = 10000 + 1000; + + db::Polygon outer = ellipse (db::Box (-r2pin, -r2pin, r2pin, r2pin), 64); + db::Polygon disc = ellipse (db::Box (-r2, -r2, r2, r2), 64); + db::Polygon inner = ellipse (db::Box (-r1, -r1, r1, r1), 64); + + db::Polygon outer_port = *(db::Region (outer) - db::Region (disc)).nth (0); + + double dbu = 0.001; + + pex::RNetwork rn; + pex::TriangulationRExtractor rex (dbu); + + std::vector vertex_ports; + + std::vector polygon_ports; + polygon_ports.push_back (inner); + polygon_ports.push_back (outer_port); + + rex.extract (disc, vertex_ports, polygon_ports, rn); + + EXPECT_EQ (rn.to_string (), + "R P0 P1 0.245379" // theoretical: 1/(2*PI)*log(r2/r1) = 0.25615 with r2=10000, r1=2000 + ) + + rex.triangulation_parameters ().max_area = 100000 * dbu * dbu; + + rex.extract (disc, vertex_ports, polygon_ports, rn); + + EXPECT_EQ (rn.to_string (), + "R P0 P1 0.255614" // theoretical: 1/(2*PI)*log(r2/r1) = 0.25615 with r2=10000, r1=2000 + ) +} + From 93e10d1d720e263ceff7cc45d972939b4535bd77 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 20 Apr 2025 21:52:44 +0200 Subject: [PATCH 29/58] Doc and small fixes --- src/pex/pex/pexRExtractor.cc | 14 +- src/pex/pex/pexRExtractor.h | 176 +++++++++++++++++++-- src/pex/pex/pexSquareCountingRExtractor.cc | 2 +- src/pex/pex/pexSquareCountingRExtractor.h | 32 +++- src/pex/pex/pexTriangulationRExtractor.cc | 4 +- src/pex/pex/pexTriangulationRExtractor.h | 33 +++- 6 files changed, 240 insertions(+), 21 deletions(-) diff --git a/src/pex/pex/pexRExtractor.cc b/src/pex/pex/pexRExtractor.cc index 02b5dbf59..11c36c771 100644 --- a/src/pex/pex/pexRExtractor.cc +++ b/src/pex/pex/pexRExtractor.cc @@ -138,7 +138,7 @@ RNetwork::create_node (RNode::node_type type, unsigned int port_index) } RElement * -RNetwork::create_element (double conductivity, RNode *a, RNode *b) +RNetwork::create_element (double conductance, RNode *a, RNode *b) { std::pair key (a, b); if (size_t (b) < size_t (a)) { @@ -148,17 +148,17 @@ RNetwork::create_element (double conductivity, RNode *a, RNode *b) auto i = m_elements_by_nodes.find (key); if (i != m_elements_by_nodes.end ()) { - if (conductivity == pex::RElement::short_value () || i->second->conductivity == pex::RElement::short_value ()) { - i->second->conductivity = pex::RElement::short_value (); + if (conductance == pex::RElement::short_value () || i->second->conductance == pex::RElement::short_value ()) { + i->second->conductance = pex::RElement::short_value (); } else { - i->second->conductivity += conductivity; + i->second->conductance += conductance; } return i->second; } else { - RElement *element = new RElement (this, conductivity, a, b); + RElement *element = new RElement (this, conductance, a, b); m_elements.push_back (element); m_elements_by_nodes.insert (std::make_pair (key, element)); @@ -204,7 +204,7 @@ RNetwork::join_nodes (RNode *a, RNode *b) for (auto e = b->elements ().begin (); e != b->elements ().end (); ++e) { RNode *on = const_cast ((*e)->other (b)); if (on != a) { - create_element ((*e)->conductivity, on, a); + create_element ((*e)->conductance, on, a); } } @@ -225,7 +225,7 @@ RNetwork::simplify () tl::equivalence_clusters clusters; for (auto e = m_elements.begin (); e != m_elements.end (); ++e) { - if (e->conductivity == pex::RElement::short_value () && (e->a ()->type == pex::RNode::Internal || e->b ()->type == pex::RNode::Internal)) { + if (e->conductance == pex::RElement::short_value () && (e->a ()->type == pex::RNode::Internal || e->b ()->type == pex::RNode::Internal)) { clusters.same (e->a (), e->b ()); } } diff --git a/src/pex/pex/pexRExtractor.h b/src/pex/pex/pexRExtractor.h index 11e05e00d..0e7a2578e 100644 --- a/src/pex/pex/pexRExtractor.h +++ b/src/pex/pex/pexRExtractor.h @@ -40,25 +40,57 @@ class RElement; class RNode; class RNetwork; +/** + * @brief Represents a node in the R graph + * + * A node connects to multiple elements (resistors). + * Every element has two nodes. The nodes and elements form + * a graph. + * + * RNode object cannot be created directly. Use "create_node" + * from RNetwork. + */ struct PEX_PUBLIC RNode : public tl::list_node { public: + /** + * @brief The type of the node + */ enum node_type { - Internal, - VertexPort, - PolygonPort + Internal, // an internal node, not related to a port + VertexPort, // a node related to a vertex port + PolygonPort // a node related to a polygon port }; + /** + * @brief The node type + */ node_type type; + + /** + * @brief The location + extension of the node + */ db::DBox location; + + /** + * @brief An index locating the node in the vertex or polygon port lists + * + * For internal nodes, the index is a unique numbers. + */ unsigned int port_index; + /** + * @brief Gets the R elements connected to this node + */ const std::list &elements () const { return m_elements; } + /** + * @brief Returns a string representation of the node + */ std::string to_string () const; protected: @@ -80,14 +112,35 @@ private: mutable std::list m_elements; }; +/** + * @brief Represents an R element in the graph (an edge) + * + * An element has two nodes that form the ends of the edge and + * a conductance value (given in Siemens). + * + * The value can be RElement::short_value() indicating + * "infinite" conductance (a short). + * + * RElement objects cannot be created directly. Use "create_element" + * from RNetwork. + */ struct PEX_PUBLIC RElement : public tl::list_node { - double conductivity; + /** + * @brief The conductance value + */ + double conductance; + /** + * @brief The nodes the resistor connects + */ const RNode *a () const { return mp_a; } const RNode *b () const { return mp_b; } + /** + * @brief Gets the other node for n + */ const RNode *other (const RNode *n) const { if (mp_a == n) { @@ -98,16 +151,27 @@ struct PEX_PUBLIC RElement tl_assert (false); } + /** + * @brief Represents the conductance value for a short + */ static double short_value () { return std::numeric_limits::infinity (); } + /** + * @brief Gets the resistance value + * + * The resistance value is the inverse of the conducance. + */ double resistance () const { - return conductivity == short_value () ? 0.0 : 1.0 / conductivity; + return conductance == short_value () ? 0.0 : 1.0 / conductance; } + /** + * @brief Returns a string representation of the element + */ std::string to_string () const; protected: @@ -115,7 +179,7 @@ protected: friend class tl::list_impl; RElement (RNetwork *network, double _conductivity, const RNode *a, const RNode *b) - : conductivity (_conductivity), mp_network (network), mp_a (a), mp_b (b) + : conductance (_conductivity), mp_network (network), mp_a (a), mp_b (b) { } ~RElement () @@ -138,6 +202,9 @@ private: RElement &operator= (const RElement &other); }; +/** + * @brief Represents a R network (a graph of RNode and RElement) + */ class PEX_PUBLIC RNetwork : public tl::Object { @@ -147,33 +214,95 @@ public: typedef tl::list element_list; typedef element_list::const_iterator element_iterator; + /** + * @brief Constructor + */ RNetwork (); + + /** + * @brief Destructor + */ ~RNetwork (); + /** + * @brief Creates a node with the given type and port index + * + * If the node type is Internal, a new node is created always. + * If the node type is VertexPort or PolygonPort, an existing + * node is returned if one way created with the same type + * or port index already. This avoids creating duplicates + * for the same port. + */ RNode *create_node (RNode::node_type type, unsigned int port_index); - RElement *create_element (double conductivity, RNode *a, RNode *b); + + /** + * @brief Creates a new element between the given nodes + * + * If an element already exists between the specified nodes, the + * given value is added to the existing element and the existing + * object is returned. + */ + RElement *create_element (double conductance, RNode *a, RNode *b); + + /** + * @brief Removes the given element + * + * Removing the element will also remove any orphan nodes + * at the ends if they are of type Internal. + */ void remove_element (RElement *element); + + /** + * @brief Removes the node and the attached elements. + * + * Only nodes of type Internal can be removed. + */ void remove_node (RNode *node); + + /** + * @brief Clears the network + */ void clear (); + + /** + * @brief Simplifies the network + * + * This will: + * - Join serial resistors if connected by an internal node + * - Remove shorts and join the nodes, if one of them is + * an internal node. The non-internal node will persist. + * - Remove "dangling" resistors if the dangling node is + * an internal one + */ void simplify (); - std::string to_string () const; - + /** + * @brief Iterate the nodes (begin) + */ node_iterator begin_nodes () const { return m_nodes.begin (); } + /** + * @brief Iterate the nodes (end) + */ node_iterator end_nodes () const { return m_nodes.end (); } + /** + * @brief Gets the number of nodes + */ size_t num_nodes () const { return m_nodes.size (); } + /** + * @brief Gets the number of internal nodes + */ size_t num_internal_nodes () const { size_t count = 0; @@ -185,21 +314,35 @@ public: return count; } + /** + * @brief Iterate the elements (begin) + */ element_iterator begin_elements () const { return m_elements.begin (); } + /** + * @brief Iterate the elements (end) + */ element_iterator end_elements () const { return m_elements.end (); } + /** + * @brief Gets the number of elements + */ size_t num_elements () const { return m_elements.size (); } + /** + * @brief Returns a string representation of the graph + */ + std::string to_string () const; + private: node_list m_nodes; element_list m_elements; @@ -225,9 +368,24 @@ private: class PEX_PUBLIC RExtractor { public: + /** + * @brief Constructor + */ RExtractor (); + + /** + * @brief Destructor + */ virtual ~RExtractor (); + /** + * @brief Extracts the resistance network from the given polygon and ports + * + * The ports define specific locations where to connect to the resistance network. + * The network will contain corresponding nodes with the VertexPort for vertex ports + * and PolygonPort for polygon port. The node index is the index in the respective + * lists. + */ virtual void extract (const db::Polygon &polygon, const std::vector &vertex_ports, const std::vector &polygon_ports, RNetwork &rnetwork) = 0; }; diff --git a/src/pex/pex/pexSquareCountingRExtractor.cc b/src/pex/pex/pexSquareCountingRExtractor.cc index de6400491..97a96c1d1 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.cc +++ b/src/pex/pex/pexSquareCountingRExtractor.cc @@ -165,7 +165,6 @@ SquareCountingRExtractor::do_extract (const db::Polygon &db_poly, const std::vec // @@@ TODO: multiply with sheet rho! // @@@ TODO: width dependency if (r == 0) { - // @@@ TODO: join nodes later! rnetwork.create_element (pex::RElement::short_value (), pl->second, pl_next->second); } else { rnetwork.create_element (r, pl->second, pl_next->second); @@ -293,6 +292,7 @@ SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector } + rnetwork.simplify (); } } diff --git a/src/pex/pex/pexSquareCountingRExtractor.h b/src/pex/pex/pexSquareCountingRExtractor.h index abacc2dfa..9ceb99d46 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.h +++ b/src/pex/pex/pexSquareCountingRExtractor.h @@ -31,28 +31,58 @@ namespace pex { -// @@@ doc +/** + * @brief The Square Counting R Extractor + * + * The idea of that extractor is to first decompose the polygon into + * convex parts. Each convex part is taken as "thin" and the current + * flow being parallel and homogeneous to the long axis. + * + * Internal ports are created between the partial polygons where + * they touch. + * + * The ports are considered point-like (polygon ports are replaced + * by points in their bounding box centers) and inject current + * at their specific position only. The resistance is accumulated + * between ports by integrating the squares (length along + * the long axis / width). + */ class PEX_PUBLIC SquareCountingRExtractor : public RExtractor { public: + /** + * @brief The constructor + */ SquareCountingRExtractor (double dbu); + /** + * @brief Gets the decomposition parameters + */ db::plc::ConvexDecompositionParameters &decomposition_parameters () { return m_decomp_param; } + /** + * @brief Sets the database unit + */ void set_dbu (double dbu) { m_dbu = dbu; } + /** + * @brief Gets the database unit + */ double dbu () const { return m_dbu; } + /** + * @brief Implementation of the extraction function + */ virtual void extract (const db::Polygon &polygon, const std::vector &vertex_ports, const std::vector &polygon_ports, RNetwork &rnetwork); protected: diff --git a/src/pex/pex/pexTriangulationRExtractor.cc b/src/pex/pex/pexTriangulationRExtractor.cc index 5a7a87047..fabc3789e 100644 --- a/src/pex/pex/pexTriangulationRExtractor.cc +++ b/src/pex/pex/pexTriangulationRExtractor.cc @@ -301,7 +301,7 @@ TriangulationRExtractor::eliminate_node (pex::RNode *node, RNetwork &rnetwork) { double s_sum = 0.0; for (auto e = node->elements ().begin (); e != node->elements ().end (); ++e) { - s_sum += (*e)->conductivity; + s_sum += (*e)->conductance; } if (fabs (s_sum) > 1e-10) { @@ -311,7 +311,7 @@ TriangulationRExtractor::eliminate_node (pex::RNode *node, RNetwork &rnetwork) for ( ; ee != node->elements ().end (); ++ee) { pex::RNode *n1 = const_cast ((*e)->other (node)); pex::RNode *n2 = const_cast ((*ee)->other (node)); - double c = (*e)->conductivity * (*ee)->conductivity / s_sum; + double c = (*e)->conductance * (*ee)->conductance / s_sum; rnetwork.create_element (c, n1, n2); } } diff --git a/src/pex/pex/pexTriangulationRExtractor.h b/src/pex/pex/pexTriangulationRExtractor.h index 536d90f45..4215d0180 100644 --- a/src/pex/pex/pexTriangulationRExtractor.h +++ b/src/pex/pex/pexTriangulationRExtractor.h @@ -31,28 +31,59 @@ namespace pex { -// @@@ doc +/** + * @brief An R extractor based on a triangulation of the resistor area + * + * This resistor extractor starts with a triangulation of the + * polygon area and substitutes each triangle by a 3-resistor network. + * + * After this, it will eliminate nodes where possible. + * + * This extractor delivers a resistor matrix (there is a resistor + * between every specified port). + * + * Polygon ports are considered to be perfectly conductive and cover + * their given area, shorting all nodes at their boundary. + * + * This extractor delivers higher quality results than the square + * counting extractor, but is slower in general. + */ class PEX_PUBLIC TriangulationRExtractor : public RExtractor { public: + /** + * @brief The constructor + */ TriangulationRExtractor (double dbu); + /** + * @brief Gets the triangulation parameters + */ db::plc::TriangulationParameters &triangulation_parameters () { return m_tri_param; } + /** + * @brief Sets the database unit + */ void set_dbu (double dbu) { m_dbu = dbu; } + /** + * @brief Gets the database unit + */ double dbu () const { return m_dbu; } + /** + * @brief Implementation of the extraction function + */ virtual void extract (const db::Polygon &polygon, const std::vector &vertex_ports, const std::vector &polygon_ports, RNetwork &rnetwork); private: From 0e99ebc056279b412c022b6662f960333d8003c3 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 20 Apr 2025 22:28:55 +0200 Subject: [PATCH 30/58] Another test case, triangulation extractor behaves strange. --- src/pex/pex/pexSquareCountingRExtractor.cc | 2 +- src/pex/pex/pexTriangulationRExtractor.cc | 2 + .../pexSquareCountingRExtractorTests.cc | 56 +++++++++++++++++-- .../pexTriangulationRExtractorTests.cc | 48 ++++++++++++++++ 4 files changed, 102 insertions(+), 6 deletions(-) diff --git a/src/pex/pex/pexSquareCountingRExtractor.cc b/src/pex/pex/pexSquareCountingRExtractor.cc index 97a96c1d1..0ad1bfa27 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.cc +++ b/src/pex/pex/pexSquareCountingRExtractor.cc @@ -167,7 +167,7 @@ SquareCountingRExtractor::do_extract (const db::Polygon &db_poly, const std::vec if (r == 0) { rnetwork.create_element (pex::RElement::short_value (), pl->second, pl_next->second); } else { - rnetwork.create_element (r, pl->second, pl_next->second); + rnetwork.create_element (1.0 / r, pl->second, pl_next->second); } } diff --git a/src/pex/pex/pexTriangulationRExtractor.cc b/src/pex/pex/pexTriangulationRExtractor.cc index fabc3789e..e52a40f88 100644 --- a/src/pex/pex/pexTriangulationRExtractor.cc +++ b/src/pex/pex/pexTriangulationRExtractor.cc @@ -55,6 +55,8 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< tri.triangulate (polygon, vertex_ports, m_tri_param, trans); + plc.dump ("debug.gds"); + } else { tl::SelfTimer timer_tri (tl::verbosity () >= m_tri_param.base_verbosity + 11, "Triangulation step"); diff --git a/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc b/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc index 77af14a1e..3da3d7b58 100644 --- a/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc +++ b/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc @@ -75,7 +75,7 @@ TEST(basic) rex.do_extract (poly, ports, rn); EXPECT_EQ (rn.to_string (), - "R $0 $1 0.390865\n" // w ramp w=100 to 1000 over x=0 to 1000 (squares = (x2-x1)/(w2-w1)*log(w2/w1) by integration) + "R $0 $1 2.55843\n" // w ramp w=100 to 1000 over x=0 to 1000 (squares = (x2-x1)/(w2-w1)*log(w2/w1) by integration) "R $1 $2 0\n" // transition from y=50 to y=500 parallel to current direction "R $2 $3 1" // 1 square between x=1000 and 2000 (w=1000) ); @@ -100,7 +100,7 @@ TEST(basic) EXPECT_EQ (rn.to_string (), "R $1 $3 1\n" "R $1 $2 0\n" - "R $0 $2 0.390865" + "R $0 $2 2.55843" ); } @@ -135,8 +135,54 @@ TEST(extraction) rex.extract (poly, vertex_ports, polygon_ports, rn); EXPECT_EQ (rn.to_string (), - "R $0 V0 0.0952381\n" - "R $0 V1 0.166667\n" - "R $0 P0 0.117647" + "R $0 V0 10.5\n" + "R $0 V1 6\n" + "R $0 P0 8.5" + ) +} + +TEST(extraction_meander) +{ + db::Point contour[] = { + db::Point (0, 0), + db::Point (0, 1000), + db::Point (1600, 1000), + db::Point (1600, 600), + db::Point (2000, 600), + db::Point (2000, 1000), + db::Point (3600, 1000), + db::Point (3600, 600), + db::Point (4000, 600), + db::Point (4000, 1000), + db::Point (4600, 1000), + db::Point (4600, 0), + db::Point (3000, 0), + db::Point (3000, 400), + db::Point (2600, 400), + db::Point (2600, 0), + db::Point (1000, 0), + db::Point (1000, 400), + db::Point (600, 400), + db::Point (600, 0) + }; + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + double dbu = 0.001; + + pex::RNetwork rn; + pex::SquareCountingRExtractor rex (dbu); + + std::vector vertex_ports; + vertex_ports.push_back (db::Point (300, 0)); // V0 + vertex_ports.push_back (db::Point (4300, 1000)); // V1 + + std::vector polygon_ports; + + rex.extract (poly, vertex_ports, polygon_ports, rn); + + EXPECT_EQ (rn.to_string (), + "R V0 V1 10.0544" // that is pretty much the length of the center line / width :) ) } diff --git a/src/pex/unit_tests/pexTriangulationRExtractorTests.cc b/src/pex/unit_tests/pexTriangulationRExtractorTests.cc index b2bfff50c..09f1fce8f 100644 --- a/src/pex/unit_tests/pexTriangulationRExtractorTests.cc +++ b/src/pex/unit_tests/pexTriangulationRExtractorTests.cc @@ -313,3 +313,51 @@ TEST(extraction_analytic_disc) ) } +TEST(extraction_meander) +{ + db::Point contour[] = { + db::Point (0, 0), + db::Point (0, 1000), + db::Point (1600, 1000), + db::Point (1600, 600), + db::Point (2000, 600), + db::Point (2000, 1000), + db::Point (3600, 1000), + db::Point (3600, 600), + db::Point (4000, 600), + db::Point (4000, 1000), + db::Point (4600, 1000), + db::Point (4600, 0), + db::Point (3000, 0), + db::Point (3000, 400), + db::Point (2600, 400), + db::Point (2600, 0), + db::Point (1000, 0), + db::Point (1000, 400), + db::Point (600, 400), + db::Point (600, 0) + }; + + db::Polygon poly; + poly.assign_hull (contour + 0, contour + sizeof (contour) / sizeof (contour[0])); + + double dbu = 0.001; + + pex::RNetwork rn; + pex::TriangulationRExtractor rex (dbu); + rex.triangulation_parameters ().max_area = 10000 * dbu * dbu; + rex.triangulation_parameters ().min_b = 0.3; + + std::vector vertex_ports; + vertex_ports.push_back (db::Point (300, 0)); // V0 + vertex_ports.push_back (db::Point (4300, 1000)); // V1 + + std::vector polygon_ports; + + rex.extract (poly, vertex_ports, polygon_ports, rn); + + EXPECT_EQ (rn.to_string (), + "R V0 V1 8.75751" // what is the "real" value? + ) +} + From 8ece7bcce1cd42f6e41455b0a74d9dd3ce5365f0 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 22 Apr 2025 22:37:23 +0200 Subject: [PATCH 31/58] Activate pex module, GSI bindings --- src/buddies/src/bd/bd.pro | 6 +- src/buddies/src/bd/strmrun.cc | 1 + src/klayout_main/klayout_main/klayout.cc | 3 +- src/pex/pex/gsiDeclRExtractor.cc | 451 ++++++++++++++++++++++- src/pex/pex/pexRExtractor.h | 16 + src/rba/unit_tests/rbaTests.cc | 1 + src/unit_tests/unit_test_main.cc | 1 + testdata/ruby/pexTests.rb | 127 +++++++ 8 files changed, 599 insertions(+), 7 deletions(-) create mode 100644 testdata/ruby/pexTests.rb diff --git a/src/buddies/src/bd/bd.pro b/src/buddies/src/bd/bd.pro index ef71f239e..2deeb870f 100644 --- a/src/buddies/src/bd/bd.pro +++ b/src/buddies/src/bd/bd.pro @@ -32,9 +32,9 @@ HEADERS = \ RESOURCES = \ -INCLUDEPATH += $$TL_INC $$GSI_INC $$VERSION_INC $$DB_INC $$LIB_INC $$RDB_INC $$LYM_INC -DEPENDPATH += $$TL_INC $$GSI_INC $$VERSION_INC $$DB_INC $$LIB_INC $$RDB_INC $$LYM_INC -LIBS += -L$$DESTDIR -lklayout_tl -lklayout_db -lklayout_gsi -lklayout_lib -lklayout_rdb -lklayout_lym +INCLUDEPATH += $$TL_INC $$GSI_INC $$VERSION_INC $$DB_INC $$LIB_INC $$RDB_INC $$PEX_INC $$LYM_INC +DEPENDPATH += $$TL_INC $$GSI_INC $$VERSION_INC $$DB_INC $$LIB_INC $$RDB_INC $$PEX_INC $$LYM_INC +LIBS += -L$$DESTDIR -lklayout_tl -lklayout_db -lklayout_gsi -lklayout_lib -lklayout_rdb -lklayout_pex -lklayout_lym INCLUDEPATH += $$RBA_INC DEPENDPATH += $$RBA_INC diff --git a/src/buddies/src/bd/strmrun.cc b/src/buddies/src/bd/strmrun.cc index fa15cf018..8f5faf784 100644 --- a/src/buddies/src/bd/strmrun.cc +++ b/src/buddies/src/bd/strmrun.cc @@ -34,6 +34,7 @@ #include "gsiExpression.h" #include "libForceLink.h" #include "rdbForceLink.h" +#include "pexForceLink.h" #include "lymMacro.h" #include "lymMacroCollection.h" diff --git a/src/klayout_main/klayout_main/klayout.cc b/src/klayout_main/klayout_main/klayout.cc index db01f3c52..5f0ebaac8 100644 --- a/src/klayout_main/klayout_main/klayout.cc +++ b/src/klayout_main/klayout_main/klayout.cc @@ -38,8 +38,9 @@ #include "version.h" -// required to force linking of the "ext" and "lib" module +// required to force linking of the "lib" and other modules #include "libForceLink.h" +#include "pexForceLink.h" #include "antForceLink.h" #include "imgForceLink.h" #include "docForceLink.h" diff --git a/src/pex/pex/gsiDeclRExtractor.cc b/src/pex/pex/gsiDeclRExtractor.cc index f74e8e657..626575092 100644 --- a/src/pex/pex/gsiDeclRExtractor.cc +++ b/src/pex/pex/gsiDeclRExtractor.cc @@ -22,22 +22,467 @@ #include "gsiDecl.h" +#include "pexRExtractor.h" #include "pexSquareCountingRExtractor.h" +#include "pexTriangulationRExtractor.h" +#include "gsiEnums.h" namespace gsi { -// @@@ +class RNode +{ +public: + ~RNode () { } + + pex::RNode::node_type type () const { return checked_pointer ()->type; } + db::DBox location () const { return checked_pointer ()->location; } + unsigned int port_index () const { return checked_pointer ()->port_index; } + std::string to_string () const { return checked_pointer ()->to_string (); } + + size_t obj_id () const + { + return size_t (mp_ptr); + } + + static RNode *make_node_object (const pex::RNode *node) + { + return new RNode (node); + } + + const pex::RNode *checked_pointer () const + { + if (! mp_graph.get ()) { + throw tl::Exception (tl::to_string (tr ("Network graph has been destroyed - RNode object no longer is valid"))); + } + return mp_ptr; + } + + pex::RNode *checked_pointer () + { + if (! mp_graph.get ()) { + throw tl::Exception (tl::to_string (tr ("Network graph has been destroyed - RNode object no longer is valid"))); + } + return const_cast (mp_ptr); + } + +private: + tl::weak_ptr mp_graph; + const pex::RNode *mp_ptr; + + RNode (const pex::RNode *node) + : mp_graph (node->graph ()), + mp_ptr (node) + { + // .. nothing yet .. + } +}; + +class RElement +{ +public: + ~RElement () { } + + double conductance () const { return checked_pointer ()->conductance; } + double resistance () const { return checked_pointer ()->resistance (); } + + RNode *a () const { return RNode::make_node_object (checked_pointer ()->a ()); } + RNode *b () const { return RNode::make_node_object (checked_pointer ()->b ()); } + + std::string to_string () const { return checked_pointer ()->to_string (); } + + size_t obj_id () const + { + return size_t (mp_ptr); + } + + static RElement *make_element_object (const pex::RElement *element) + { + return new RElement (element); + } + + const pex::RElement *checked_pointer () const + { + if (! mp_graph.get ()) { + throw tl::Exception (tl::to_string (tr ("Network graph has been destroyed - RElement object no longer is valid"))); + } + return mp_ptr; + } + + pex::RElement *checked_pointer () + { + if (! mp_graph.get ()) { + throw tl::Exception (tl::to_string (tr ("Network graph has been destroyed - RElement object no longer is valid"))); + } + return const_cast (mp_ptr); + } + +private: + tl::weak_ptr mp_graph; + const pex::RElement *mp_ptr; + + RElement (const pex::RElement *node) + : mp_graph (node->graph ()), + mp_ptr (node) + { + // .. nothing yet .. + } +}; + +class RElementIterator +{ +public: + typedef std::list::const_iterator basic_iter; + typedef std::forward_iterator_tag iterator_category; + typedef RElement *value_type; + typedef RElement *reference; + typedef void pointer; + typedef void difference_type; + + RElementIterator (basic_iter it) + : m_it (it) + { } + + bool operator== (const RElementIterator &it) const { return m_it == it.m_it; } + void operator++ () { ++m_it; } + + RElement *operator* () const + { + return RElement::make_element_object (*m_it); + } + +private: + basic_iter m_it; +}; + +static RElementIterator begin_node_elements (RNode *node) +{ + return RElementIterator (node->checked_pointer ()->elements ().begin ()); +} + +static RElementIterator end_network_elements (RNode *node) +{ + return RElementIterator (node->checked_pointer ()->elements ().end ()); +} + +gsi::Enum decl_NodeType ("pex", "RNodeType", + gsi::enum_const ("Internal", pex::RNode::Internal, + "@brief Specifies an internal node in a R network\n" + "Internal nodes are generated during the R extraction process. " + "The port index of such a node is an arbitrary index." + ) + + gsi::enum_const ("VertexPort", pex::RNode::VertexPort, + "@brief Specifies a vertex port node in a R network\n" + "Vertex port nodes are generated for vertex ports in \\RExtractor#extract, see 'vertex_ports' argument. " + "The port index of such a node refers to the position in that list." + ) + + gsi::enum_const ("PolygonPort", pex::RNode::PolygonPort, + "@brief Specifies a polygon port node in a R network\n" + "Polygon port nodes are generated for polygon ports in \\RExtractor#extract, see 'polygon_ports' argument. " + "The port index of such a node refers to the position in that list." + ), + "@brief This class represents the node type for RNode.\n" + "\n" + "This class has been introduced in version 0.30.1" +); + +Class decl_RNode ("pex", "RNode", + gsi::method ("object_id", &RNode::obj_id, + "@brief Returns an ID representing the actual object\n" + "For every call, a new instance of this object is created, while multiple " + "ones may represent the same internal object. The 'object_id' is a ID that " + "indicates the internal object. Same object_id means same node." + ) + + gsi::method ("to_s", &RNode::to_string, + "@brief Returns a string representation of this object\n" + ) + + gsi::iterator_ext ("each_element", gsi::return_new_object (), &begin_node_elements, &end_network_elements, + "@brief Iterates the \\RElement objects attached to the node\n" + ) + + gsi::method ("type", &RNode::type, + "@brief Gets the type attribute of the node\n" + ) + + gsi::method ("location", &RNode::location, + "@brief Gets the location attribute of the node\n" + "The location defined the original position of the node" + ) + + gsi::method ("port_index", &RNode::port_index, + "@brief Gets the port index of the node\n" + "The port index associates a node with a original port definition." + ), + "@brief Represents a node in a R network graph\n" + "See \\RNetwork for a description of this object\n" + "\n" + "This class has been introduced in version 0.30.1" +); + +// Inject the RNode::node_type declarations into RNode +gsi::ClassExt inject_NodeType_in_RNode (decl_NodeType.defs ()); + +Class decl_RElement ("pex", "RElement", + gsi::method ("object_id", &RElement::obj_id, + "@brief Returns an ID representing the actual object\n" + "For every call, a new instance of this object is created, while multiple " + "ones may represent the same internal object. The 'object_id' is a ID that " + "indicates the internal object. Same object_id means same element." + ) + + gsi::method ("to_s", &RElement::to_string, + "@brief Returns a string representation of this object\n" + ) + + gsi::method ("resistance", &RElement::resistance, + "@brief Gets the resistance value of the object\n" + ) + + gsi::factory ("a", &RElement::a, + "@brief Gets the first node the element connects\n" + ) + + gsi::factory ("b", &RElement::b, + "@brief Gets the second node the element connects\n" + ), + "@brief Represents an edge (also called element) in a R network graph\n" + "See \\RNetwork for a description of this object" + "\n" + "This class has been introduced in version 0.30.1" +); + +static RNode *create_node (pex::RNetwork *network, pex::RNode::node_type type, unsigned int port_index) +{ + return RNode::make_node_object (network->create_node (type, port_index)); +} + +static RElement *create_element (pex::RNetwork *network, double r, RNode *a, RNode *b) +{ + double s = fabs (r) < 1e-10 ? pex::RElement::short_value () : 1.0 / r; + return RElement::make_element_object (network->create_element (s, a->checked_pointer (), b->checked_pointer ())); +} + +static void remove_element (pex::RNetwork *network, RElement *element) +{ + network->remove_element (element->checked_pointer ()); +} + +static void remove_node (pex::RNetwork *network, RNode *node) +{ + network->remove_node (node->checked_pointer ()); +} + +class NetworkElementIterator +{ +public: + typedef pex::RNetwork::element_iterator basic_iter; + typedef std::forward_iterator_tag iterator_category; + typedef RElement *value_type; + typedef RElement *reference; + typedef void pointer; + typedef void difference_type; + + NetworkElementIterator (basic_iter it) + : m_it (it) + { } + + bool operator== (const NetworkElementIterator &it) const { return m_it == it.m_it; } + void operator++ () { ++m_it; } + + RElement *operator* () const + { + return RElement::make_element_object (m_it.operator-> ()); + } + +private: + basic_iter m_it; +}; + +static NetworkElementIterator begin_network_elements (pex::RNetwork *network) +{ + return NetworkElementIterator (network->begin_elements ()); +} + +static NetworkElementIterator end_network_elements (pex::RNetwork *network) +{ + return NetworkElementIterator (network->end_elements ()); +} + +class NetworkNodeIterator +{ +public: + typedef pex::RNetwork::node_iterator basic_iter; + typedef std::forward_iterator_tag iterator_category; + typedef RNode *value_type; + typedef RNode *reference; + typedef void pointer; + typedef void difference_type; + + NetworkNodeIterator (basic_iter it) + : m_it (it) + { } + + bool operator== (const NetworkNodeIterator &it) const { return m_it == it.m_it; } + void operator++ () { ++m_it; } + + RNode *operator* () const + { + return RNode::make_node_object (m_it.operator-> ()); + } + +private: + basic_iter m_it; +}; + +static NetworkNodeIterator begin_network_nodes (pex::RNetwork *network) +{ + return NetworkNodeIterator (network->begin_nodes ()); +} + +static NetworkNodeIterator end_network_nodes (pex::RNetwork *network) +{ + return NetworkNodeIterator (network->end_nodes ()); +} + +Class decl_RNetwork ("pex", "RNetwork", + gsi::factory_ext ("create_node", &create_node, gsi::arg ("type"), gsi::arg ("port_index"), + "@brief Creates a new node with the given type and index'.\n" + "@return A reference to the new nbode object." + ) + + gsi::factory_ext ("create_element", &create_element, gsi::arg ("resistance"), gsi::arg ("a"), gsi::arg ("b"), + "@brief Creates a new element between the nodes given by 'a' abd 'b'.\n" + "If a resistor already exists between the two nodes, both resistors are combined into one.\n" + "@return A reference to the new resistor object." + ) + + gsi::method_ext ("remove_element", &remove_element, gsi::arg ("element"), + "@brief Removes the given element\n" + "If removing the element renders an internal node orphan (i.e. without elements), this " + "node is removed too." + ) + + gsi::method_ext ("remove_node", &remove_node, gsi::arg ("node"), + "@brief Removes the given node\n" + "Only internal nodes can be removed. Removing a node will also remove the " + "elements attached to this node." + ) + + gsi::method ("clear", &pex::RNetwork::clear, + "@brief Clears the network\n" + ) + + gsi::method ("simplify", &pex::RNetwork::simplify, + "@brief Simplifies the network\n" + "\n" + "This will:\n" + "@ul\n" + "@li Join serial resistors if connected by an internal node @/li\n" + "@li Remove shorts and join the nodes, if one of them is\n" + " an internal node. The non-internal node will persist @/li\n" + "@li Remove \"dangling\" resistors if the dangling node is\n" + " an internal one @/li\n" + "@/ul\n" + ) + + gsi::iterator_ext ("each_element", gsi::return_new_object (), &begin_network_elements, &end_network_elements, + "@brief Iterates the \\RElement objects inside the network\n" + ) + + gsi::iterator_ext ("each_node", gsi::return_new_object (), &begin_network_nodes, &end_network_nodes, + "@brief Iterates the \\RNode objects inside the network\n" + ) + + gsi::method ("num_nodes", &pex::RNetwork::num_nodes, + "@brief Gets the total number of nodes in the network\n" + ) + + gsi::method ("num_internal_nodes", &pex::RNetwork::num_internal_nodes, + "@brief Gets the number of internal nodes in the network\n" + ) + + gsi::method ("num_elements", &pex::RNetwork::num_elements, + "@brief Gets the number of elements in the network\n" + ) + + gsi::method ("to_s", &pex::RNetwork::to_string, + "@brief Returns a string representation of the network\n" + ), + "@brief Represents a network of resistors\n" + "\n" + "The network is basically a graph with nodes and edges (the resistors). " + "The resistors are called 'elements' and are represented by \\RElement objects. " + "The nodes are represented by \\RNode objects. " + "The network is created by \\RExtractor#extract, which turns a polygon into a resistor network.\n" + "\n" + "This class has been introduced in version 0.30.1\n" +); + static pex::RExtractor *new_sqc_rextractor (double dbu) { return new pex::SquareCountingRExtractor (dbu); } +static pex::RExtractor *new_tesselation_rextractor (double dbu, double min_b, double max_area) +{ + auto res = new pex::TriangulationRExtractor (dbu); + res->triangulation_parameters ().min_b = min_b; + res->triangulation_parameters ().max_area = max_area; + return res; +} + +static pex::RNetwork *extract_ipolygon (pex::RExtractor *rex, const db::Polygon &poly, const std::vector &vertex_ports, const std::vector &polygon_ports) +{ + std::unique_ptr p_network (new pex::RNetwork ()); + rex->extract (poly, vertex_ports, polygon_ports, *p_network); + return p_network.release (); +} + Class decl_RExtractor ("pex", "RExtractor", - gsi::constructor ("square_counting", &new_sqc_rextractor, gsi::arg ("dbu"), + gsi::constructor ("square_counting_extractor", &new_sqc_rextractor, gsi::arg ("dbu"), "@brief Creates a square counting R extractor\n" + "The square counting extractor extracts resistances from a polygon with ports using the following approach:\n" + "\n" + "@ul\n" + "@li Split the original polygon into convex parts using a Hertel-Mehlhorn decomposition @/li\n" + "@li Create internal nodes at the locations where the parts touch @/li\n" + "@li For each part, extract the resistance along the horizonal or vertical axis, whichever is longer @/li" + "@/ul\n" + "\n" + "The square counting extractor assumes the parts are 'thin' - i.e. the long axis is much longer than the short " + "axis - and the parts are either oriented horizontally or vertically. The current flow is assumed to be linear and " + "homogenous along the long axis. Ports define probe points for the voltages along the long long axis. " + "Polygon ports are considered points located at the center of the polygon's bounding box.\n" + "\n" + "The results of the extraction is normalized to a sheet resistance of 1 Ohm/square - i.e. to obtain the actual resistor " + "values, multiply the element resistance values by the sheet resistance.\n" + "\n" + "@param dbu The database unit of the polygons the extractor will work on\n" + "@return A new \\RExtractor object that implements the square counting extractor\n" + ) + + gsi::constructor ("tesselation_extractor", &new_tesselation_rextractor, gsi::arg ("dbu"), gsi::arg ("min_b", 0.3), gsi::arg ("max_area", 0.0), + "@brief Creates a tesselation R extractor\n" + "The tesselation extractor starts with a triangulation of the original polygon. The triangulation is " + "turned into a resistor network and simplified.\n" + "\n" + "The tesselation extractor is well suited for homogeneous geometries, but does not properly consider " + "the boundary conditions at the borders of the region. It is good for extracting resistance networks of " + "substrate or large sheet layers.\n" + "\n" + "The square counting extractor assumes the parts are 'thin' - i.e. the long axis is much longer than the short " + "axis - and the parts are either oriented horizontally or vertically. The current flow is assumed to be linear and " + "homogenous along the long axis. Ports define probe points for the voltages along the long long axis. " + "Polygon ports are considered points located at the center of the polygon's bounding box.\n" + "\n" + "The tesselation extractor delivers a full matrix of resistors - there is a resistor between every pair of ports.\n" + "\n" + "The results of the extraction is normalized to a sheet resistance of 1 Ohm/square - i.e. to obtain the actual resistor " + "values, multiply the element resistance values by the sheet resistance.\n" + "\n" + "@param dbu The database unit of the polygons the extractor will work on\n" + "@param min_b Defines the min 'b' value of the refined Delaunay triangulation (see \\Polygon#delaunay)\n" + "@param max_area Defines maximum area value of the refined Delaunay triangulation (see \\Polygon#delaunay). The value is given in square micrometer units.\n" + "@return A new \\RExtractor object that implements the square counting extractor\n" + ) + + gsi::factory_ext ("extract", &extract_ipolygon, gsi::arg ("polygon"), gsi::arg ("vertex_ports", std::vector (), "[]"), gsi::arg ("polygon_ports", std::vector (), "[]"), + "@brief Runs the extraction on the given polygon\n" + "This method will create a new \\RNetwork object from the given polygon.\n" + "\n" + "'vertex_ports' is an array of points that define point-like ports. A port will create a \\RNode object in the " + "resistor graph. This node object carries the type \\VertexPort and the index of the vertex in this array.\n" + "\n" + "'polygon_ports' is an array of polygons that define distributed ports. The polygons should be inside the resistor polygon " + "and convex. A port will create a \\RNode object in the resistor graph. " + "For polygon ports, this node object carries the type \\PolygonPort and the index of the polygon in this array.\n" ), - "@brief A base class for the R extractor\n" + "@brief The basic R extractor class\n" + "\n" + "Use \\tesselation_extractor and \\square_counting_extractor to create an actual extractor object.\n" + "To use the extractor, call the \\extract method on a given polygon with ports that define the network attachment points.\n" + "\n" + "This class has been introduced in version 0.30.1\n" ); } diff --git a/src/pex/pex/pexRExtractor.h b/src/pex/pex/pexRExtractor.h index 0e7a2578e..b9da173bc 100644 --- a/src/pex/pex/pexRExtractor.h +++ b/src/pex/pex/pexRExtractor.h @@ -93,6 +93,14 @@ public: */ std::string to_string () const; + /** + * @brief Gets the network the node lives in + */ + RNetwork *graph () const + { + return mp_network; + } + protected: friend class RNetwork; friend class RElement; @@ -174,6 +182,14 @@ struct PEX_PUBLIC RElement */ std::string to_string () const; + /** + * @brief Gets the network the node lives in + */ + RNetwork *graph () const + { + return mp_network; + } + protected: friend class RNetwork; friend class tl::list_impl; diff --git a/src/rba/unit_tests/rbaTests.cc b/src/rba/unit_tests/rbaTests.cc index 7b23e3940..e2ff18d3a 100644 --- a/src/rba/unit_tests/rbaTests.cc +++ b/src/rba/unit_tests/rbaTests.cc @@ -142,6 +142,7 @@ RUBYTEST (dbTransTest, "dbTransTest.rb") RUBYTEST (dbVectorTest, "dbVectorTest.rb") RUBYTEST (dbUtilsTests, "dbUtilsTests.rb") RUBYTEST (dbTechnologies, "dbTechnologies.rb") +RUBYTEST (pexTests, "pexTests.rb") RUBYTEST (edtTest, "edtTest.rb") RUBYTEST (extNetTracer, "extNetTracer.rb") RUBYTEST (imgObject, "imgObject.rb") diff --git a/src/unit_tests/unit_test_main.cc b/src/unit_tests/unit_test_main.cc index 20c8696dc..6391b619c 100644 --- a/src/unit_tests/unit_test_main.cc +++ b/src/unit_tests/unit_test_main.cc @@ -71,6 +71,7 @@ // and the plugins/auxiliary modules (some in non-Qt case) #include "libForceLink.h" #include "rdbForceLink.h" +#include "pexForceLink.h" #include "antForceLink.h" #include "imgForceLink.h" #include "edtForceLink.h" diff --git a/testdata/ruby/pexTests.rb b/testdata/ruby/pexTests.rb new file mode 100644 index 000000000..c18de6e03 --- /dev/null +++ b/testdata/ruby/pexTests.rb @@ -0,0 +1,127 @@ +# encoding: UTF-8 + +# KLayout Layout Viewer +# Copyright (C) 2006-2025 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 PEX_TestClass < TestBase + + # PEX basics + def test_1_Basic + + rn = RBA::RNetwork::new + + a = rn.create_node(RBA::RNode::VertexPort, 1) + b = rn.create_node(RBA::RNode::Internal, 2) + c = rn.create_node(RBA::RNode::PolygonPort, 3) + + assert_equal(a.type, RBA::RNode::VertexPort) + assert_equal(a.port_index, 1) + assert_equal(a.object_id, a.object_id) + assert_not_equal(a.object_id, b.object_id) + assert_equal(a.to_s, "V1") + + assert_equal(b.to_s, "$2") + assert_equal(c.to_s, "P3") + + rab = rn.create_element(1.0, a, b) + assert_equal(rab.a.object_id, a.object_id) + assert_equal(rab.b.object_id, b.object_id) + assert_equal(rab.to_s, "R $2 V1 1") + + rn.create_element(1.0, a, b) + assert_equal(rab.to_s, "R $2 V1 0.5") + + rbc = rn.create_element(1.0, b, c) + + assert_equal(rn.to_s, "R $2 V1 0.5\n" + "R $2 P3 1") + + assert_equal(b.each_element.collect(&:to_s).sort.join(";"), "R $2 P3 1;R $2 V1 0.5") + assert_equal(rn.each_element.collect(&:to_s).sort.join(";"), "R $2 P3 1;R $2 V1 0.5") + assert_equal(rn.each_node.collect(&:to_s).sort.join(";"), "$2;P3;V1") + + rn.simplify + assert_equal(rn.to_s, "R P3 V1 1.5") + + rn.clear + assert_equal(rn.to_s, "") + + end + + def test_2_Destroy + + rn = RBA::RNetwork::new + + a = rn.create_node(RBA::RNode::VertexPort, 1) + b = rn.create_node(RBA::RNode::Internal, 2) + rab = rn.create_element(1.0, a, b) + + # this should invalid the pointers + rn._destroy + + begin + assert_equal(a.to_s, "") + rescue => ex + # graph has been destroyed already + end + + begin + assert_equal(rab.to_s, "") + rescue => ex + # graph has been destroyed already + end + + end + + def test_3_SQC + + poly = RBA::Polygon::new(RBA::Box::new(0, 0, 1100, 100)) + + vp = [ RBA::Point::new(50, 50) ] + pp = [ RBA::Polygon::new(RBA::Box::new(1000, 0, 1100, 100)) ] + + rex = RBA::RExtractor::square_counting_extractor(0.001) + rn = rex.extract(poly, vp, pp) + + assert_equal(rn.to_s, "R P0 V0 10") + + end + + def test_3_TX + + poly = RBA::Polygon::new(RBA::Box::new(0, 0, 1100, 100)) + + vp = [ RBA::Point::new(50, 50) ] + pp = [ RBA::Polygon::new(RBA::Box::new(1000, 0, 1100, 100)) ] + + rex = RBA::RExtractor::tesselation_extractor(0.001, 0.8) + rn = rex.extract(poly, vp, pp) + + assert_equal(rn.to_s, "R P0 V0 9.44") + + end + +end + +load("test_epilogue.rb") + From f0943dea53600e2e05b13587bb64df3cf9cadc02 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 24 Apr 2025 22:01:56 +0200 Subject: [PATCH 32/58] More debugging support --- src/pex/pex/gsiDeclRExtractor.cc | 15 ++++++++++----- src/pex/pex/pexSquareCountingRExtractor.cc | 8 +++++--- src/pex/pex/pexSquareCountingRExtractor.h | 17 +++++++++++++++++ src/pex/pex/pexTriangulationRExtractor.cc | 5 ++++- src/pex/pex/pexTriangulationRExtractor.h | 17 +++++++++++++++++ 5 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/pex/pex/gsiDeclRExtractor.cc b/src/pex/pex/gsiDeclRExtractor.cc index 626575092..740765e33 100644 --- a/src/pex/pex/gsiDeclRExtractor.cc +++ b/src/pex/pex/gsiDeclRExtractor.cc @@ -400,16 +400,19 @@ Class decl_RNetwork ("pex", "RNetwork", "This class has been introduced in version 0.30.1\n" ); -static pex::RExtractor *new_sqc_rextractor (double dbu) +static pex::RExtractor *new_sqc_rextractor (double dbu, bool skip_simplify) { - return new pex::SquareCountingRExtractor (dbu); + auto res = new pex::SquareCountingRExtractor (dbu); + res->set_skip_simplfy (skip_simplify); + return res; } -static pex::RExtractor *new_tesselation_rextractor (double dbu, double min_b, double max_area) +static pex::RExtractor *new_tesselation_rextractor (double dbu, double min_b, double max_area, bool skip_reduction) { auto res = new pex::TriangulationRExtractor (dbu); res->triangulation_parameters ().min_b = min_b; res->triangulation_parameters ().max_area = max_area; + res->set_skip_reduction (skip_reduction); return res; } @@ -421,7 +424,7 @@ static pex::RNetwork *extract_ipolygon (pex::RExtractor *rex, const db::Polygon } Class decl_RExtractor ("pex", "RExtractor", - gsi::constructor ("square_counting_extractor", &new_sqc_rextractor, gsi::arg ("dbu"), + gsi::constructor ("square_counting_extractor", &new_sqc_rextractor, gsi::arg ("dbu"), gsi::arg ("skip_simplify", false), "@brief Creates a square counting R extractor\n" "The square counting extractor extracts resistances from a polygon with ports using the following approach:\n" "\n" @@ -440,9 +443,10 @@ Class decl_RExtractor ("pex", "RExtractor", "values, multiply the element resistance values by the sheet resistance.\n" "\n" "@param dbu The database unit of the polygons the extractor will work on\n" + "@param skip_simplify If true, the final step to simplify the netlist will be skipped. This feature is for testing mainly.\n" "@return A new \\RExtractor object that implements the square counting extractor\n" ) + - gsi::constructor ("tesselation_extractor", &new_tesselation_rextractor, gsi::arg ("dbu"), gsi::arg ("min_b", 0.3), gsi::arg ("max_area", 0.0), + gsi::constructor ("tesselation_extractor", &new_tesselation_rextractor, gsi::arg ("dbu"), gsi::arg ("min_b", 0.3), gsi::arg ("max_area", 0.0), gsi::arg ("skip_reduction", false), "@brief Creates a tesselation R extractor\n" "The tesselation extractor starts with a triangulation of the original polygon. The triangulation is " "turned into a resistor network and simplified.\n" @@ -464,6 +468,7 @@ Class decl_RExtractor ("pex", "RExtractor", "@param dbu The database unit of the polygons the extractor will work on\n" "@param min_b Defines the min 'b' value of the refined Delaunay triangulation (see \\Polygon#delaunay)\n" "@param max_area Defines maximum area value of the refined Delaunay triangulation (see \\Polygon#delaunay). The value is given in square micrometer units.\n" + "@param skip_reduction If true, the reduction step for the netlist will be skipped. This feature is for testing mainly. The resulting R graph will contain all the original triangles and the internal nodes representing the vertexes.\n" "@return A new \\RExtractor object that implements the square counting extractor\n" ) + gsi::factory_ext ("extract", &extract_ipolygon, gsi::arg ("polygon"), gsi::arg ("vertex_ports", std::vector (), "[]"), gsi::arg ("polygon_ports", std::vector (), "[]"), diff --git a/src/pex/pex/pexSquareCountingRExtractor.cc b/src/pex/pex/pexSquareCountingRExtractor.cc index 0ad1bfa27..9170239d0 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.cc +++ b/src/pex/pex/pexSquareCountingRExtractor.cc @@ -71,6 +71,7 @@ struct JoinEdgeSets SquareCountingRExtractor::SquareCountingRExtractor (double dbu) { m_dbu = dbu; + m_skip_simplify = false; m_decomp_param.split_edges = true; m_decomp_param.with_segments = false; @@ -162,8 +163,7 @@ SquareCountingRExtractor::do_extract (const db::Polygon &db_poly, const std::vec ++em; } - // @@@ TODO: multiply with sheet rho! - // @@@ TODO: width dependency + // TODO: width dependency? if (r == 0) { rnetwork.create_element (pex::RElement::short_value (), pl->second, pl_next->second); } else { @@ -292,7 +292,9 @@ SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector } - rnetwork.simplify (); + if (! m_skip_simplify) { + rnetwork.simplify (); + } } } diff --git a/src/pex/pex/pexSquareCountingRExtractor.h b/src/pex/pex/pexSquareCountingRExtractor.h index 9ceb99d46..2d95b3e26 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.h +++ b/src/pex/pex/pexSquareCountingRExtractor.h @@ -64,6 +64,22 @@ public: return m_decomp_param; } + /** + * @brief Sets a value indicating whether to skip the simplify step + */ + void set_skip_simplfy (bool f) + { + m_skip_simplify = f; + } + + /** + * @brief Gets a value indicating whether to skip the simplify step + */ + bool skip_simplify () const + { + return m_skip_simplify; + } + /** * @brief Sets the database unit */ @@ -129,6 +145,7 @@ protected: private: db::plc::ConvexDecompositionParameters m_decomp_param; double m_dbu; + bool m_skip_simplify; }; } diff --git a/src/pex/pex/pexTriangulationRExtractor.cc b/src/pex/pex/pexTriangulationRExtractor.cc index e52a40f88..b39a6a37d 100644 --- a/src/pex/pex/pexTriangulationRExtractor.cc +++ b/src/pex/pex/pexTriangulationRExtractor.cc @@ -32,6 +32,7 @@ namespace pex TriangulationRExtractor::TriangulationRExtractor (double dbu) { m_dbu = dbu; + m_skip_reduction = false; m_tri_param.min_b = 0.3; m_tri_param.max_area = 0.0; @@ -206,7 +207,9 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< // eliminate internal nodes - eliminate_all (rnetwork); + if (! m_skip_reduction) { + eliminate_all (rnetwork); + } } void diff --git a/src/pex/pex/pexTriangulationRExtractor.h b/src/pex/pex/pexTriangulationRExtractor.h index 4215d0180..6749c7f84 100644 --- a/src/pex/pex/pexTriangulationRExtractor.h +++ b/src/pex/pex/pexTriangulationRExtractor.h @@ -65,6 +65,22 @@ public: return m_tri_param; } + /** + * @brief Sets a value indicating whether to skip the reduction step + */ + void set_skip_reduction (bool f) + { + m_skip_reduction = f; + } + + /** + * @brief Gets a value indicating whether to skip the reduction step + */ + bool skip_reduction () const + { + return m_skip_reduction; + } + /** * @brief Sets the database unit */ @@ -89,6 +105,7 @@ public: private: db::plc::TriangulationParameters m_tri_param; double m_dbu; + bool m_skip_reduction; void create_conductances (const db::plc::Polygon &tri, const std::unordered_map &vertex2node, RNetwork &rnetwork); void eliminate_node (pex::RNode *node, RNetwork &rnetwork); From bc10bb6b14ead270f487a360b9b6623068c91639 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 3 May 2025 22:36:39 +0200 Subject: [PATCH 33/58] WIP: network R extractor, needs testing --- src/pex/pex/pex.pro | 6 + src/pex/pex/pexRExtractor.cc | 282 ------------- src/pex/pex/pexRExtractor.h | 340 ---------------- src/pex/pex/pexRExtractorTech.cc | 29 ++ src/pex/pex/pexRExtractorTech.h | 167 ++++++++ src/pex/pex/pexRNetExtractor.cc | 465 ++++++++++++++++++++++ src/pex/pex/pexRNetExtractor.h | 97 +++++ src/pex/pex/pexRNetwork.cc | 309 ++++++++++++++ src/pex/pex/pexRNetwork.h | 377 ++++++++++++++++++ src/pex/pex/pexSquareCountingRExtractor.h | 1 + src/pex/pex/pexTriangulationRExtractor.h | 1 + src/pex/unit_tests/pexRExtractorTests.cc | 1 + 12 files changed, 1453 insertions(+), 622 deletions(-) create mode 100644 src/pex/pex/pexRExtractorTech.cc create mode 100644 src/pex/pex/pexRExtractorTech.h create mode 100644 src/pex/pex/pexRNetExtractor.cc create mode 100644 src/pex/pex/pexRNetExtractor.h create mode 100644 src/pex/pex/pexRNetwork.cc create mode 100644 src/pex/pex/pexRNetwork.h diff --git a/src/pex/pex/pex.pro b/src/pex/pex/pex.pro index 145a1866e..6d69e018c 100644 --- a/src/pex/pex/pex.pro +++ b/src/pex/pex/pex.pro @@ -10,12 +10,18 @@ SOURCES = \ pexForceLink.cc \ pexRExtractor.cc \ gsiDeclRExtractor.cc \ + pexRExtractorTech.cc \ + pexRNetExtractor.cc \ + pexRNetwork.cc \ pexSquareCountingRExtractor.cc \ pexTriangulationRExtractor.cc HEADERS = \ pexForceLink.h \ pexRExtractor.h \ + pexRExtractorTech.h \ + pexRNetExtractor.h \ + pexRNetwork.h \ pexSquareCountingRExtractor.h \ pexTriangulationRExtractor.h diff --git a/src/pex/pex/pexRExtractor.cc b/src/pex/pex/pexRExtractor.cc index 11c36c771..10afe62cc 100644 --- a/src/pex/pex/pexRExtractor.cc +++ b/src/pex/pex/pexRExtractor.cc @@ -22,292 +22,10 @@ #include "pexRExtractor.h" -#include "tlEquivalenceClusters.h" namespace pex { -// ----------------------------------------------------------------------------- - -std::string -RNode::to_string () const -{ - std::string res; - switch (type) { - default: - res += "$" + tl::to_string (port_index); - break; - case VertexPort: - res += "V" + tl::to_string (port_index); - break; - case PolygonPort: - res += "P" + tl::to_string (port_index); - break; - } - return res; -} - -// ----------------------------------------------------------------------------- - -std::string -RElement::to_string () const -{ - std::string na; - if (a ()) { - na = a ()->to_string (); - } else { - na = "(nil)"; - } - - std::string nb; - if (b ()) { - nb = b ()->to_string (); - } else { - nb = "(nil)"; - } - - if (nb < na) { - std::swap (na, nb); - } - - std::string res = "R " + na + " " + nb + " "; - res += tl::sprintf ("%.6g", resistance ()); - return res; -} - -// ----------------------------------------------------------------------------- - -RNetwork::RNetwork () -{ - // .. nothing yet .. -} - -RNetwork::~RNetwork () -{ - clear (); -} - -std::string -RNetwork::to_string () const -{ - std::string res; - for (auto e = m_elements.begin (); e != m_elements.end (); ++e) { - if (! res.empty ()) { - res += "\n"; - } - res += e->to_string (); - } - return res; -} - -void -RNetwork::clear () -{ - m_elements.clear (); // must happen before m_nodes - m_nodes.clear (); - m_elements_by_nodes.clear (); - m_nodes_by_type.clear (); -} - -RNode * -RNetwork::create_node (RNode::node_type type, unsigned int port_index) -{ - if (type != RNode::Internal) { - - auto i = m_nodes_by_type.find (std::make_pair (type, port_index)); - if (i != m_nodes_by_type.end ()) { - - return i->second; - - } else { - - RNode *new_node = new RNode (this, type, db::DBox (), port_index); - m_nodes.push_back (new_node); - m_nodes_by_type.insert (std::make_pair (std::make_pair (type, port_index), new_node)); - - return new_node; - } - - } else { - - RNode *new_node = new RNode (this, type, db::DBox (), port_index); - m_nodes.push_back (new_node); - return new_node; - - } -} - -RElement * -RNetwork::create_element (double conductance, RNode *a, RNode *b) -{ - std::pair key (a, b); - if (size_t (b) < size_t (a)) { - std::swap (key.first, key.second); - } - - auto i = m_elements_by_nodes.find (key); - if (i != m_elements_by_nodes.end ()) { - - if (conductance == pex::RElement::short_value () || i->second->conductance == pex::RElement::short_value ()) { - i->second->conductance = pex::RElement::short_value (); - } else { - i->second->conductance += conductance; - } - - return i->second; - - } else { - - RElement *element = new RElement (this, conductance, a, b); - m_elements.push_back (element); - m_elements_by_nodes.insert (std::make_pair (key, element)); - - a->m_elements.push_back (element); - element->m_ia = --a->m_elements.end (); - b->m_elements.push_back (element); - element->m_ib = --b->m_elements.end (); - - return element; - - } -} - -void -RNetwork::remove_node (RNode *node) -{ - tl_assert (node->type == RNode::Internal); - while (! node->m_elements.empty ()) { - delete const_cast (node->m_elements.front ()); - } - delete node; -} - -void -RNetwork::remove_element (RElement *element) -{ - RNode *a = const_cast (element->a ()); - RNode *b = const_cast (element->b ()); - - delete element; - - if (a && a->type == RNode::Internal && a->m_elements.empty ()) { - delete a; - } - if (b && b->type == RNode::Internal && b->m_elements.empty ()) { - delete b; - } -} - -void -RNetwork::join_nodes (RNode *a, RNode *b) -{ - for (auto e = b->elements ().begin (); e != b->elements ().end (); ++e) { - RNode *on = const_cast ((*e)->other (b)); - if (on != a) { - create_element ((*e)->conductance, on, a); - } - } - - a->location += b->location; - remove_node (b); -} - -void -RNetwork::simplify () -{ - bool any_change = true; - - while (any_change) { - - any_change = false; - - // join shorted clusters - we take care to remove internal nodes only - - tl::equivalence_clusters clusters; - for (auto e = m_elements.begin (); e != m_elements.end (); ++e) { - if (e->conductance == pex::RElement::short_value () && (e->a ()->type == pex::RNode::Internal || e->b ()->type == pex::RNode::Internal)) { - clusters.same (e->a (), e->b ()); - } - } - - for (size_t ic = 1; ic <= clusters.size (); ++ic) { - - RNode *remaining = 0; - RNode *first_node = 0; - for (auto c = clusters.begin_cluster (ic); c != clusters.end_cluster (ic); ++c) { - RNode *n = const_cast ((*c)->first); - if (! first_node) { - first_node = n; - } - if (n->type != pex::RNode::Internal) { - remaining = n; - break; - } - } - - if (! remaining) { - // Only internal nodes - remaining = first_node; - } - - for (auto c = clusters.begin_cluster (ic); c != clusters.end_cluster (ic); ++c) { - RNode *n = const_cast ((*c)->first); - if (n != remaining && n->type == pex::RNode::Internal) { - any_change = true; - join_nodes (remaining, n); - } - } - - } - - // combine serial resistors if connected through an internal node - - std::vector nodes_to_remove; - - for (auto n = m_nodes.begin (); n != m_nodes.end (); ++n) { - - size_t nres = n->elements ().size (); - - if (n->type == pex::RNode::Internal && nres <= 2) { - - any_change = true; - - if (nres == 2) { - - auto e = n->elements ().begin (); - - RNode *n1 = const_cast ((*e)->other (n.operator-> ())); - double r1 = (*e)->resistance (); - - ++e; - RNode *n2 = const_cast ((*e)->other (n.operator-> ())); - double r2 = (*e)->resistance (); - - double r = r1 + r2; - if (r == 0.0) { - create_element (pex::RElement::short_value (), n1, n2); - } else { - create_element (1.0 / r, n1, n2); - } - - } - - nodes_to_remove.push_back (n.operator-> ()); - - } - - } - - for (auto n = nodes_to_remove.begin (); n != nodes_to_remove.end (); ++n) { - remove_node (*n); - } - - } - -} - -// ----------------------------------------------------------------------------- - RExtractor::RExtractor () { // .. nothing yet .. diff --git a/src/pex/pex/pexRExtractor.h b/src/pex/pex/pexRExtractor.h index b9da173bc..e80c3f248 100644 --- a/src/pex/pex/pexRExtractor.h +++ b/src/pex/pex/pexRExtractor.h @@ -26,352 +26,12 @@ #include "pexCommon.h" #include "dbPolygon.h" -#include "dbPLC.h" -#include "tlList.h" - -#include -#include -#include namespace pex { -class RElement; -class RNode; class RNetwork; -/** - * @brief Represents a node in the R graph - * - * A node connects to multiple elements (resistors). - * Every element has two nodes. The nodes and elements form - * a graph. - * - * RNode object cannot be created directly. Use "create_node" - * from RNetwork. - */ -struct PEX_PUBLIC RNode - : public tl::list_node -{ -public: - /** - * @brief The type of the node - */ - enum node_type { - Internal, // an internal node, not related to a port - VertexPort, // a node related to a vertex port - PolygonPort // a node related to a polygon port - }; - - /** - * @brief The node type - */ - node_type type; - - /** - * @brief The location + extension of the node - */ - db::DBox location; - - /** - * @brief An index locating the node in the vertex or polygon port lists - * - * For internal nodes, the index is a unique numbers. - */ - unsigned int port_index; - - /** - * @brief Gets the R elements connected to this node - */ - const std::list &elements () const - { - return m_elements; - } - - /** - * @brief Returns a string representation of the node - */ - std::string to_string () const; - - /** - * @brief Gets the network the node lives in - */ - RNetwork *graph () const - { - return mp_network; - } - -protected: - friend class RNetwork; - friend class RElement; - friend class tl::list_impl; - - RNode (RNetwork *network, node_type _type, const db::DBox &_location, unsigned int _port_index) - : type (_type), location (_location), port_index (_port_index), mp_network (network) - { } - - ~RNode () { } - -private: - RNode (const RNode &other); - RNode &operator= (const RNode &other); - - RNetwork *mp_network; - mutable std::list m_elements; -}; - -/** - * @brief Represents an R element in the graph (an edge) - * - * An element has two nodes that form the ends of the edge and - * a conductance value (given in Siemens). - * - * The value can be RElement::short_value() indicating - * "infinite" conductance (a short). - * - * RElement objects cannot be created directly. Use "create_element" - * from RNetwork. - */ -struct PEX_PUBLIC RElement - : public tl::list_node -{ - /** - * @brief The conductance value - */ - double conductance; - - /** - * @brief The nodes the resistor connects - */ - const RNode *a () const { return mp_a; } - const RNode *b () const { return mp_b; } - - /** - * @brief Gets the other node for n - */ - const RNode *other (const RNode *n) const - { - if (mp_a == n) { - return mp_b; - } else if (mp_b == n) { - return mp_a; - } - tl_assert (false); - } - - /** - * @brief Represents the conductance value for a short - */ - static double short_value () - { - return std::numeric_limits::infinity (); - } - - /** - * @brief Gets the resistance value - * - * The resistance value is the inverse of the conducance. - */ - double resistance () const - { - return conductance == short_value () ? 0.0 : 1.0 / conductance; - } - - /** - * @brief Returns a string representation of the element - */ - std::string to_string () const; - - /** - * @brief Gets the network the node lives in - */ - RNetwork *graph () const - { - return mp_network; - } - -protected: - friend class RNetwork; - friend class tl::list_impl; - - RElement (RNetwork *network, double _conductivity, const RNode *a, const RNode *b) - : conductance (_conductivity), mp_network (network), mp_a (a), mp_b (b) - { } - - ~RElement () - { - if (mp_a) { - mp_a->m_elements.erase (m_ia); - } - if (mp_b) { - mp_b->m_elements.erase (m_ib); - } - mp_a = mp_b = 0; - } - - std::list::iterator m_ia, m_ib; - RNetwork *mp_network; - const RNode *mp_a, *mp_b; - -private: - RElement (const RElement &other); - RElement &operator= (const RElement &other); -}; - -/** - * @brief Represents a R network (a graph of RNode and RElement) - */ -class PEX_PUBLIC RNetwork - : public tl::Object -{ -public: - typedef tl::list node_list; - typedef node_list::const_iterator node_iterator; - typedef tl::list element_list; - typedef element_list::const_iterator element_iterator; - - /** - * @brief Constructor - */ - RNetwork (); - - /** - * @brief Destructor - */ - ~RNetwork (); - - /** - * @brief Creates a node with the given type and port index - * - * If the node type is Internal, a new node is created always. - * If the node type is VertexPort or PolygonPort, an existing - * node is returned if one way created with the same type - * or port index already. This avoids creating duplicates - * for the same port. - */ - RNode *create_node (RNode::node_type type, unsigned int port_index); - - /** - * @brief Creates a new element between the given nodes - * - * If an element already exists between the specified nodes, the - * given value is added to the existing element and the existing - * object is returned. - */ - RElement *create_element (double conductance, RNode *a, RNode *b); - - /** - * @brief Removes the given element - * - * Removing the element will also remove any orphan nodes - * at the ends if they are of type Internal. - */ - void remove_element (RElement *element); - - /** - * @brief Removes the node and the attached elements. - * - * Only nodes of type Internal can be removed. - */ - void remove_node (RNode *node); - - /** - * @brief Clears the network - */ - void clear (); - - /** - * @brief Simplifies the network - * - * This will: - * - Join serial resistors if connected by an internal node - * - Remove shorts and join the nodes, if one of them is - * an internal node. The non-internal node will persist. - * - Remove "dangling" resistors if the dangling node is - * an internal one - */ - void simplify (); - - /** - * @brief Iterate the nodes (begin) - */ - node_iterator begin_nodes () const - { - return m_nodes.begin (); - } - - /** - * @brief Iterate the nodes (end) - */ - node_iterator end_nodes () const - { - return m_nodes.end (); - } - - /** - * @brief Gets the number of nodes - */ - size_t num_nodes () const - { - return m_nodes.size (); - } - - /** - * @brief Gets the number of internal nodes - */ - size_t num_internal_nodes () const - { - size_t count = 0; - for (auto n = m_nodes.begin (); n != m_nodes.end (); ++n) { - if (n->type == pex::RNode::Internal) { - ++count; - } - } - return count; - } - - /** - * @brief Iterate the elements (begin) - */ - element_iterator begin_elements () const - { - return m_elements.begin (); - } - - /** - * @brief Iterate the elements (end) - */ - element_iterator end_elements () const - { - return m_elements.end (); - } - - /** - * @brief Gets the number of elements - */ - size_t num_elements () const - { - return m_elements.size (); - } - - /** - * @brief Returns a string representation of the graph - */ - std::string to_string () const; - -private: - node_list m_nodes; - element_list m_elements; - std::map, RElement *> m_elements_by_nodes; - std::map, RNode *> m_nodes_by_type; - - RNetwork (const RNetwork &); - RNetwork &operator= (const RNetwork &); - - void join_nodes (RNode *a, RNode *b); -}; - - /** * @brief A base class for an resistance extractor * diff --git a/src/pex/pex/pexRExtractorTech.cc b/src/pex/pex/pexRExtractorTech.cc new file mode 100644 index 000000000..65e1a6604 --- /dev/null +++ b/src/pex/pex/pexRExtractorTech.cc @@ -0,0 +1,29 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "pexRExtractorTech.h" + +namespace pex +{ + // .. nothing yet .. +} diff --git a/src/pex/pex/pexRExtractorTech.h b/src/pex/pex/pexRExtractorTech.h new file mode 100644 index 000000000..b2840d020 --- /dev/null +++ b/src/pex/pex/pexRExtractorTech.h @@ -0,0 +1,167 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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_pexRExtractorTech +#define HDR_pexRExtractorTech + +#include "pexCommon.h" + +#include + +namespace pex +{ + +/** + * @brief Specifies the extraction parameters for vias + * + * Note that the layers are generic IDs. These are usigned ints specifying + * a layer. + */ +class RExtractorTechVia +{ +public: + RExtractorTechVia () + : cut_layer (0), top_conductor (0), bottom_conductor (0), resistance (0.0), merge_distance (0.0) + { + // .. nothing yet .. + } + + /** + * @brief Specifies the cut layer + * This is the layer the via sits on + */ + unsigned int cut_layer; + + /** + * @brief Specifies the top conductor + * The value is the ID of the top conductor layer + */ + unsigned int top_conductor; + + /** + * @brief Specifies the bottom conductor + * The value is the ID of the bottom conductor layer + */ + unsigned int bottom_conductor; + + /** + * @brief Specifies the resistance in Ohm * sqaure micrometer + */ + double resistance; + + /** + * @brief Specifies the merge distance in micrometers + * The merge distance indicates a range under which vias are merged + * into bigger effective areas to reduce the complexity of via arrays. + */ + double merge_distance; +}; + +/** + * @brief Specifies the extraction parameters for a conductor layer + * + * Note that the layers are generic IDs. These are usigned ints specifying + * a layer. + */ +class RExtractorTechConductor +{ +public: + /** + * @brief A algorithm to use + */ + enum Algorithm + { + /** + * @brief The square counting algorithm + * This algorithm is suitable for "long and thin" wires. + */ + SquareCounting = 0, + + /** + * @brief The triangulation algorithm + * This algorithm is suitable to "large" sheets, specifically substrate. + */ + Triangulation = 1 + }; + + /** + * @brief The constructor + */ + RExtractorTechConductor () + : layer (0), resistance (0.0), algorithm (SquareCounting), triangulation_min_b (-1.0), triangulation_max_area (-1.0) + { + // .. nothing yet .. + } + + /** + * @brief Specifies the layer + * The value is the generic ID of the layer. + */ + unsigned int layer; + + /** + * @brief Specifies the sheet resistance + * The sheet resistance is given in units of Ohm / square + */ + double resistance; + + /** + * @brief The algorihm to use + */ + Algorithm algorithm; + + /** + * @brief The "min_b" parameter for the triangulation + * The "b" parameter is a ratio of shortest triangle edge to circle radius. + * If a negative value is given, the default value is taken. + */ + double triangulation_min_b; + + /** + * @brief The "max_area" parameter for the triangulation + * The "max_area" specifies the maximum area of the triangles produced in square micrometers. + * If a negative value is given, the default value is taken. + */ + double triangulation_max_area; +}; + +/** + * @brief Specifies the extraction parameters + */ +class RExtractorTech +{ +public: + /** + * @brief A list of via definitions + */ + std::list vias; + + /** + * @brief A list of conductor definitions + */ + std::list conductors; +}; + +} + +#endif + diff --git a/src/pex/pex/pexRNetExtractor.cc b/src/pex/pex/pexRNetExtractor.cc new file mode 100644 index 000000000..78f4f6a4b --- /dev/null +++ b/src/pex/pex/pexRNetExtractor.cc @@ -0,0 +1,465 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "pexCommon.h" + +#include "pexRNetExtractor.h" +#include "pexRNetwork.h" +#include "pexRExtractorTech.h" +#include "pexSquareCountingRExtractor.h" +#include "pexTriangulationRExtractor.h" + +#include "dbBoxScanner.h" +#include "dbPolygonTools.h" +#include "dbRegionProcessors.h" +#include "dbCompoundOperation.h" +#include "dbPolygonNeighborhood.h" + +namespace pex +{ + +RNetExtractor::RNetExtractor (double dbu) + : m_dbu (dbu) +{ + // .. nothing yet .. +} + +void +RNetExtractor::extract (const RExtractorTech &tech, + const std::map &geo, + const std::map > &vertex_ports, + const std::map > &polygon_ports, + RNetwork &rnetwork) +{ + rnetwork.clear (); + + std::map > via_ports; + create_via_ports (tech, geo, via_ports, rnetwork); + + for (auto g = geo.begin (); g != geo.end (); ++g) { + + // Find the conductor spec for the given layer + const RExtractorTechConductor *cond = 0; + for (auto c = tech.conductors.begin (); c != tech.conductors.end () && !cond; ++c) { + if (c->layer == g->first) { + cond = c.operator-> (); + } + } + if (! cond) { + continue; + } + + // Compute the offsets for the port indexes + // The port indexes are assigned incrementally, so the first index of a port + // for layer n is: + // size(ports for layer 0) + ... + size(ports for layer n-1) + // (size is 0 for empty port list) + unsigned int vp_offset = 0, pp_offset = 0; + for (auto p = vertex_ports.begin (); p != vertex_ports.end () && p->first < g->first; ++p) { + vp_offset += p->second.size (); + } + for (auto p = polygon_ports.begin (); p != polygon_ports.end () && p->first < g->first; ++p) { + vp_offset += p->second.size (); + } + + // fetch the port list for vertex ports + auto ivp = vertex_ports.find (g->first); + static std::vector empty_vertex_ports; + const std::vector &vp = ivp == vertex_ports.end () ? empty_vertex_ports : ivp->second; + + // fetch the port list for polygon ports + auto ipp = polygon_ports.find (g->first); + static std::vector empty_polygon_ports; + const std::vector &pp = ipp == polygon_ports.end () ? empty_polygon_ports : ipp->second; + + // fetch the port list for via ports + auto iviap = via_ports.find (g->first); + static std::vector empty_via_ports; + const std::vector &viap = iviap == via_ports.end () ? empty_via_ports : iviap->second; + + // extract the conductor polygon and integrate the results into the target network + extract_conductor (*cond, g->second, vp, vp_offset, pp, pp_offset, viap, rnetwork); + + } +} + +static double +via_conductance (const RExtractorTechVia &via_tech, + const db::Polygon &poly, + double dbu) +{ + if (via_tech.resistance < 1e-10) { + return RElement::short_value (); + } else { + return (1.0 / via_tech.resistance) * dbu * dbu * poly.area (); + } +} + +namespace +{ + +class ViaAggregationVisitor + : public db::PolygonNeighborhoodVisitor +{ +public: + ViaAggregationVisitor (const RExtractorTechVia *via_tech, std::vector > *conductances, double dbu) + : mp_via_tech (via_tech), mp_conductances (conductances), m_dbu (dbu) + { + // .. nothing yet .. + } + + virtual void neighbors (const db::Layout * /*layout*/, const db::Cell * /*cell*/, const db::PolygonWithProperties &polygon, const neighbors_type &neighbors) + { + auto i = neighbors.find ((unsigned int) 1); + if (i == neighbors.end ()) { + return; + } + + double c = 0; + for (auto vp = i->second.begin (); vp != i->second.end (); ++vp) { + double cc = via_conductance (*mp_via_tech, *vp, m_dbu); + if (cc == RElement::short_value ()) { + c = cc; + break; + } else { + c += cc; + } + } + + mp_conductances->push_back (std::make_pair (c, polygon.box ().center ())); + } + +private: + const RExtractorTechVia *mp_via_tech; + std::vector > *mp_conductances; + double m_dbu; +}; + +} + +void +RNetExtractor::create_via_ports (const RExtractorTech &tech, + const std::map &geo, + std::map > &vias, + RNetwork &rnetwork) +{ + std::vector > via_conductances; + + for (auto v = tech.vias.begin (); v != tech.vias.end (); ++v) { + + auto g = geo.find (v->cut_layer); + if (g == geo.end ()) { + continue; + } + + via_conductances.clear (); + + if (v->merge_distance > db::epsilon) { + + // with merge, follow this scheme: + // 1.) do a merge by over/undersize + // 2.) do a convex decomposition, so we get convex via shapes with the bbox center inside the polygon + // 3.) re-aggregate the original via polygons and collect the total conductance per merged shape + + db::Coord sz = db::coord_traits::rounded (0.5 * v->merge_distance / m_dbu); + + db::Region merged_vias = g->second.sized (sz).sized (-sz); + merged_vias.process (db::ConvexDecomposition (db::PO_any)); + + std::vector children; + children.push_back (new db::CompoundRegionOperationPrimaryNode ()); + children.push_back (new db::CompoundRegionOperationSecondaryNode (const_cast (&g->second))); + + ViaAggregationVisitor visitor (v.operator-> (), &via_conductances, m_dbu); + db::PolygonNeighborhoodCompoundOperationNode en_node (children, &visitor, 0); + merged_vias.cop_to_region (en_node); + + } else { + + for (auto p = g->second.begin_merged (); ! p.at_end (); ++p) { + via_conductances.push_back (std::make_pair (via_conductance (*v, *p, m_dbu), p->box ().center ())); + } + + } + + // create the via resistor elements + unsigned int port_index = 0; + for (auto vc = via_conductances.begin (); vc != via_conductances.end (); ++vc) { + + RNode *a = rnetwork.create_node (RNode::Internal, port_index++); + RNode *b = rnetwork.create_node (RNode::Internal, port_index++); + + rnetwork.create_element (vc->first, a, b); + + vias[v->bottom_conductor].push_back (ViaPort (vc->second, a)); + vias[v->top_conductor].push_back (ViaPort (vc->second, b)); + + } + + } +} + +static inline size_t make_id (size_t index, unsigned int type) +{ + return (index << 2) + type; +} + +static inline size_t index_from_id (size_t id) +{ + return id >> 2; +} + +static inline unsigned int type_from_id (size_t id) +{ + return id & 3; +} + +namespace +{ + +class ExtractingReceiver + : public db::box_scanner_receiver2 +{ +public: + ExtractingReceiver (const RExtractorTechConductor *cond, + const std::vector *vertex_ports, + unsigned int vertex_port_index_offset, + const std::vector *polygon_ports, + unsigned int polygon_port_index_offset, + const std::vector *via_ports, + double dbu, + RNetwork *rnetwork) + : mp_cond (cond), + mp_vertex_ports (vertex_ports), + mp_polygon_ports (polygon_ports), + mp_via_ports (via_ports), + m_next_internal_port_index (0), + m_vertex_port_index_offset (vertex_port_index_offset), + m_polygon_port_index_offset (polygon_port_index_offset), + m_dbu (dbu), + mp_rnetwork (rnetwork) + { + for (auto n = rnetwork->begin_nodes (); n != rnetwork->end_nodes (); ++n) { + if (n->type == RNode::Internal && n->port_index > m_next_internal_port_index) { + m_next_internal_port_index = n->port_index; + } + } + } + + void finish1 (const db::Polygon *poly, const size_t poly_id) + { + auto i = m_interacting_ports.find (poly_id); + if (i == m_interacting_ports.end ()) { + static std::set empty_ids; + extract (*poly, empty_ids); + } else { + extract (*poly, i->second); + m_interacting_ports.erase (i); + } + } + + void add (const db::Polygon *poly, const size_t poly_id, const db::Box *port, const size_t port_id) + { + if (db::interact (*poly, *port)) { + m_interacting_ports[poly_id].insert (port_id); + } + } + +private: + std::map > m_interacting_ports; + const RExtractorTechConductor *mp_cond; + const std::vector *mp_vertex_ports; + const std::vector *mp_polygon_ports; + const std::vector *mp_via_ports; + std::map m_id_to_node; + unsigned int m_next_internal_port_index; + unsigned int m_vertex_port_index_offset; + unsigned int m_polygon_port_index_offset; + double m_dbu; + RNetwork *mp_rnetwork; + + void extract (const db::Polygon &poly, const std::set &port_ids) + { + std::vector local_vertex_ports; + std::vector local_vertex_port_ids; + std::vector local_polygon_ports; + std::vector local_polygon_port_ids; + + for (auto i = port_ids.begin (); i != port_ids.end (); ++i) { + switch (type_from_id (*i)) { + case 0: // vertex port + local_vertex_port_ids.push_back (*i); + local_vertex_ports.push_back ((*mp_vertex_ports) [index_from_id (*i)]); + break; + case 1: // via port + local_vertex_port_ids.push_back (*i); + local_vertex_ports.push_back ((*mp_via_ports) [index_from_id (*i)].position); + break; + case 2: // polygon port + local_polygon_port_ids.push_back (*i); + local_polygon_ports.push_back ((*mp_polygon_ports) [index_from_id (*i)]); + break; + } + } + + pex::RNetwork local_network; + + switch (mp_cond->algorithm) { + case RExtractorTechConductor::SquareCounting: + default: + { + pex::SquareCountingRExtractor rex (m_dbu); + rex.extract (poly, local_vertex_ports, local_polygon_ports, local_network); + } + break; + case RExtractorTechConductor::Triangulation: + { + pex::TriangulationRExtractor rex (m_dbu); + rex.extract (poly, local_vertex_ports, local_polygon_ports, local_network); + } + break; + } + + integrate (local_network, local_vertex_port_ids, local_polygon_port_ids); + } + + void integrate (const RNetwork &local_network, + const std::vector &local_vertex_port_ids, + const std::vector &local_polygon_port_ids) + { + // create or find the new nodes in the target network + std::unordered_map n2n; + for (auto n = local_network.begin_nodes (); n != local_network.end_nodes (); ++n) { + + const RNode *local = n.operator-> (); + RNode *global = 0; + + if (local->type == RNode::Internal) { + + // for internal nodes always create a node in the target network + global = mp_rnetwork->create_node (local->type, ++m_next_internal_port_index); + + } else if (local->type == RNode::VertexPort) { + + // for vertex nodes reuse the via node or create a new target node, unless one + // was created already. + + size_t id = local_vertex_port_ids [local->port_index]; + + auto i2n = m_id_to_node.find (id); + if (i2n != m_id_to_node.end ()) { + global = i2n->second; + } else { + if (type_from_id (id) == 0) { // vertex port + global = mp_rnetwork->create_node (RNode::VertexPort, index_from_id (id) + m_vertex_port_index_offset); + global->location = local->location; + } else if (type_from_id (id) == 1) { // via port + global = (*mp_via_ports) [index_from_id (id)].node; + } + m_id_to_node.insert (std::make_pair (id, global)); + } + + } else if (local->type == RNode::PolygonPort) { + + // for polygon nodes create a new target node, unless one was created already. + + size_t id = local_polygon_port_ids [local->port_index]; + tl_assert (type_from_id (id) == 2); + + auto i2n = m_id_to_node.find (id); + if (i2n != m_id_to_node.end ()) { + global = i2n->second; + } else { + global = mp_rnetwork->create_node (RNode::VertexPort, index_from_id (id) + m_polygon_port_index_offset); + global->location = local->location; + m_id_to_node.insert (std::make_pair (id, global)); + } + + } + + tl_assert (global != 0); + n2n.insert (std::make_pair (local, global)); + + } + + // create the R elements in the target network + for (auto e = local_network.begin_elements (); e != local_network.begin_elements (); ++e) { + + const RElement *local = e.operator-> (); + + auto ia = n2n.find (local->a ()); + auto ib = n2n.find (local->b ()); + tl_assert (ia != n2n.end ()); + tl_assert (ia != n2n.end ()); + + mp_rnetwork->create_element (local->conductance, ia->second, ib->second); + + } + } +}; + +} + +void +RNetExtractor::extract_conductor (const RExtractorTechConductor &cond, + const db::Region ®ion, + const std::vector &vertex_ports, + unsigned int vertex_ports_index_offset, + const std::vector &polygon_ports, + unsigned int polygon_ports_index_offset, + const std::vector &via_ports, + RNetwork &rnetwork) +{ + db::box_scanner2 scanner; + + size_t poly_id = 0; + for (auto p = region.addressable_merged_polygons (); ! p.at_end (); ++p) { + scanner.insert1 (p.operator-> (), poly_id++); + } + + std::list box_heap; + + // type 0 objects (vertex ports) + for (auto i = vertex_ports.begin (); i != vertex_ports.end (); ++i) { + // @@@ could be without enlarge? + box_heap.push_back (db::Box (*i, *i).enlarged (db::Vector (1, 1))); + scanner.insert2 (&box_heap.back (), make_id (i - vertex_ports.begin (), 0)); + } + + // type 1 objects (via ports) + for (auto i = via_ports.begin (); i != via_ports.end (); ++i) { + // @@@ could be without enlarge? + box_heap.push_back (db::Box (i->position, i->position).enlarged (db::Vector (1, 1))); + scanner.insert2 (&box_heap.back (), make_id (i - via_ports.begin (), 1)); + } + + // type 2 objects (polygon ports) + for (auto i = polygon_ports.begin (); i != polygon_ports.end (); ++i) { + box_heap.push_back (i->box ()); + scanner.insert2 (&box_heap.back (), make_id (i - polygon_ports.begin (), 2)); + } + + ExtractingReceiver rec (&cond, &vertex_ports, vertex_ports_index_offset, &polygon_ports, polygon_ports_index_offset, &via_ports, m_dbu, &rnetwork); + scanner.process (rec, 0, db::box_convert (), db::box_convert ()); +} + +} diff --git a/src/pex/pex/pexRNetExtractor.h b/src/pex/pex/pexRNetExtractor.h new file mode 100644 index 000000000..a2df7d6aa --- /dev/null +++ b/src/pex/pex/pexRNetExtractor.h @@ -0,0 +1,97 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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_pexRNetExtractor +#define HDR_pexRNetExtractor + +#include "pexCommon.h" +#include "dbRegion.h" + +namespace pex +{ + +class RExtractorTech; +class RExtractorTechConductor; +class RNetwork; +class RNode; + +/** + * @brief Implementation of the R extractor for a multi-polygon/multi-layer net + */ +class RNetExtractor +{ +public: + /** + * @brief Constructor + * @param dbu The database unit to be used to convert coordinates into micrometers + */ + RNetExtractor (double dbu); + + /** + * @brief Extracts a R network from a given set of geometries and ports + * @param geo The geometries per layer + * @param vertex_ports The vertex ports - a list of layer and points to attache a port to on this layer + * @param polygon_ports The polygon ports - a list of layer and polygons to attach a port to on this layer + * @param rnetwork The network extracted (output) + * + * The network nodes will carry the information about the port, in case they + * have been generated from a port. + */ + void extract (const RExtractorTech &tech, + const std::map &geo, + const std::map > &vertex_ports, + const std::map > &polygon_ports, + RNetwork &rnetwork); + + /** + * @brief A structure describing a via port + * This structure is used internally + */ + struct ViaPort + { + ViaPort () : node (0) { } + ViaPort (const db::Point &p, RNode *n) : position (p), node (n) { } + db::Point position; + RNode *node; + }; + +private: + double m_dbu; + + void create_via_ports (const RExtractorTech &tech, + const std::map &geo, + std::map > &vias, + RNetwork &rnetwork); + + void extract_conductor (const RExtractorTechConductor &cond, + const db::Region ®ion, + const std::vector &vertex_ports, + unsigned int vertex_ports_index_offset, + const std::vector &polygon_ports, + unsigned int polygon_ports_index_offset, + const std::vector &via_ports, + RNetwork &rnetwork); +}; + +} + +#endif diff --git a/src/pex/pex/pexRNetwork.cc b/src/pex/pex/pexRNetwork.cc new file mode 100644 index 000000000..0a552e2e2 --- /dev/null +++ b/src/pex/pex/pexRNetwork.cc @@ -0,0 +1,309 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "pexRNetwork.h" +#include "tlEquivalenceClusters.h" + +namespace pex +{ + +// ----------------------------------------------------------------------------- + +std::string +RNode::to_string () const +{ + std::string res; + switch (type) { + default: + res += "$" + tl::to_string (port_index); + break; + case VertexPort: + res += "V" + tl::to_string (port_index); + break; + case PolygonPort: + res += "P" + tl::to_string (port_index); + break; + } + return res; +} + +// ----------------------------------------------------------------------------- + +std::string +RElement::to_string () const +{ + std::string na; + if (a ()) { + na = a ()->to_string (); + } else { + na = "(nil)"; + } + + std::string nb; + if (b ()) { + nb = b ()->to_string (); + } else { + nb = "(nil)"; + } + + if (nb < na) { + std::swap (na, nb); + } + + std::string res = "R " + na + " " + nb + " "; + res += tl::sprintf ("%.6g", resistance ()); + return res; +} + +// ----------------------------------------------------------------------------- + +RNetwork::RNetwork () +{ + // .. nothing yet .. +} + +RNetwork::~RNetwork () +{ + clear (); +} + +std::string +RNetwork::to_string () const +{ + std::string res; + for (auto e = m_elements.begin (); e != m_elements.end (); ++e) { + if (! res.empty ()) { + res += "\n"; + } + res += e->to_string (); + } + return res; +} + +void +RNetwork::clear () +{ + m_elements.clear (); // must happen before m_nodes + m_nodes.clear (); + m_elements_by_nodes.clear (); + m_nodes_by_type.clear (); +} + +RNode * +RNetwork::create_node (RNode::node_type type, unsigned int port_index) +{ + if (type != RNode::Internal) { + + auto i = m_nodes_by_type.find (std::make_pair (type, port_index)); + if (i != m_nodes_by_type.end ()) { + + return i->second; + + } else { + + RNode *new_node = new RNode (this, type, db::DBox (), port_index); + m_nodes.push_back (new_node); + m_nodes_by_type.insert (std::make_pair (std::make_pair (type, port_index), new_node)); + + return new_node; + } + + } else { + + RNode *new_node = new RNode (this, type, db::DBox (), port_index); + m_nodes.push_back (new_node); + return new_node; + + } +} + +RElement * +RNetwork::create_element (double conductance, RNode *a, RNode *b) +{ + std::pair key (a, b); + if (size_t (b) < size_t (a)) { + std::swap (key.first, key.second); + } + + auto i = m_elements_by_nodes.find (key); + if (i != m_elements_by_nodes.end ()) { + + if (conductance == pex::RElement::short_value () || i->second->conductance == pex::RElement::short_value ()) { + i->second->conductance = pex::RElement::short_value (); + } else { + i->second->conductance += conductance; + } + + return i->second; + + } else { + + RElement *element = new RElement (this, conductance, a, b); + m_elements.push_back (element); + m_elements_by_nodes.insert (std::make_pair (key, element)); + + a->m_elements.push_back (element); + element->m_ia = --a->m_elements.end (); + b->m_elements.push_back (element); + element->m_ib = --b->m_elements.end (); + + return element; + + } +} + +void +RNetwork::remove_node (RNode *node) +{ + tl_assert (node->type == RNode::Internal); + while (! node->m_elements.empty ()) { + delete const_cast (node->m_elements.front ()); + } + delete node; +} + +void +RNetwork::remove_element (RElement *element) +{ + RNode *a = const_cast (element->a ()); + RNode *b = const_cast (element->b ()); + + delete element; + + if (a && a->type == RNode::Internal && a->m_elements.empty ()) { + delete a; + } + if (b && b->type == RNode::Internal && b->m_elements.empty ()) { + delete b; + } +} + +void +RNetwork::join_nodes (RNode *a, RNode *b) +{ + for (auto e = b->elements ().begin (); e != b->elements ().end (); ++e) { + RNode *on = const_cast ((*e)->other (b)); + if (on != a) { + create_element ((*e)->conductance, on, a); + } + } + + a->location += b->location; + remove_node (b); +} + +void +RNetwork::simplify () +{ + bool any_change = true; + + while (any_change) { + + any_change = false; + + // join shorted clusters - we take care to remove internal nodes only + + tl::equivalence_clusters clusters; + for (auto e = m_elements.begin (); e != m_elements.end (); ++e) { + if (e->conductance == pex::RElement::short_value () && (e->a ()->type == pex::RNode::Internal || e->b ()->type == pex::RNode::Internal)) { + clusters.same (e->a (), e->b ()); + } + } + + for (size_t ic = 1; ic <= clusters.size (); ++ic) { + + RNode *remaining = 0; + RNode *first_node = 0; + for (auto c = clusters.begin_cluster (ic); c != clusters.end_cluster (ic); ++c) { + RNode *n = const_cast ((*c)->first); + if (! first_node) { + first_node = n; + } + if (n->type != pex::RNode::Internal) { + remaining = n; + break; + } + } + + if (! remaining) { + // Only internal nodes + remaining = first_node; + } + + for (auto c = clusters.begin_cluster (ic); c != clusters.end_cluster (ic); ++c) { + RNode *n = const_cast ((*c)->first); + if (n != remaining && n->type == pex::RNode::Internal) { + any_change = true; + join_nodes (remaining, n); + } + } + + } + + // combine serial resistors if connected through an internal node + + std::vector nodes_to_remove; + + for (auto n = m_nodes.begin (); n != m_nodes.end (); ++n) { + + size_t nres = n->elements ().size (); + + if (n->type == pex::RNode::Internal && nres <= 2) { + + any_change = true; + + if (nres == 2) { + + auto e = n->elements ().begin (); + + RNode *n1 = const_cast ((*e)->other (n.operator-> ())); + double r1 = (*e)->resistance (); + + ++e; + RNode *n2 = const_cast ((*e)->other (n.operator-> ())); + double r2 = (*e)->resistance (); + + double r = r1 + r2; + if (r == 0.0) { + create_element (pex::RElement::short_value (), n1, n2); + } else { + create_element (1.0 / r, n1, n2); + } + + } + + nodes_to_remove.push_back (n.operator-> ()); + + } + + } + + for (auto n = nodes_to_remove.begin (); n != nodes_to_remove.end (); ++n) { + remove_node (*n); + } + + } + +} + +} diff --git a/src/pex/pex/pexRNetwork.h b/src/pex/pex/pexRNetwork.h new file mode 100644 index 000000000..d0fe65a9a --- /dev/null +++ b/src/pex/pex/pexRNetwork.h @@ -0,0 +1,377 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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_pexRNetwork +#define HDR_pexRNetwork + +#include "pexCommon.h" + +#include "dbPolygon.h" +#include "dbPLC.h" +#include "tlList.h" + +#include +#include +#include + +namespace pex +{ + +class RElement; +class RNode; +class RNetwork; + +/** + * @brief Represents a node in the R graph + * + * A node connects to multiple elements (resistors). + * Every element has two nodes. The nodes and elements form + * a graph. + * + * RNode object cannot be created directly. Use "create_node" + * from RNetwork. + */ +struct PEX_PUBLIC RNode + : public tl::list_node +{ +public: + /** + * @brief The type of the node + */ + enum node_type { + Internal, // an internal node, not related to a port + VertexPort, // a node related to a vertex port + PolygonPort // a node related to a polygon port + }; + + /** + * @brief The node type + */ + node_type type; + + /** + * @brief The location + extension of the node + */ + db::DBox location; + + /** + * @brief An index locating the node in the vertex or polygon port lists + * + * For internal nodes, the index is a unique numbers. + */ + unsigned int port_index; + + /** + * @brief Gets the R elements connected to this node + */ + const std::list &elements () const + { + return m_elements; + } + + /** + * @brief Returns a string representation of the node + */ + std::string to_string () const; + + /** + * @brief Gets the network the node lives in + */ + RNetwork *graph () const + { + return mp_network; + } + +protected: + friend class RNetwork; + friend class RElement; + friend class tl::list_impl; + + RNode (RNetwork *network, node_type _type, const db::DBox &_location, unsigned int _port_index) + : type (_type), location (_location), port_index (_port_index), mp_network (network) + { } + + ~RNode () { } + +private: + RNode (const RNode &other); + RNode &operator= (const RNode &other); + + RNetwork *mp_network; + mutable std::list m_elements; +}; + +/** + * @brief Represents an R element in the graph (an edge) + * + * An element has two nodes that form the ends of the edge and + * a conductance value (given in Siemens). + * + * The value can be RElement::short_value() indicating + * "infinite" conductance (a short). + * + * RElement objects cannot be created directly. Use "create_element" + * from RNetwork. + */ +struct PEX_PUBLIC RElement + : public tl::list_node +{ + /** + * @brief The conductance value + */ + double conductance; + + /** + * @brief The nodes the resistor connects + */ + const RNode *a () const { return mp_a; } + const RNode *b () const { return mp_b; } + + /** + * @brief Gets the other node for n + */ + const RNode *other (const RNode *n) const + { + if (mp_a == n) { + return mp_b; + } else if (mp_b == n) { + return mp_a; + } + tl_assert (false); + } + + /** + * @brief Represents the conductance value for a short + */ + static double short_value () + { + return std::numeric_limits::infinity (); + } + + /** + * @brief Gets the resistance value + * + * The resistance value is the inverse of the conducance. + */ + double resistance () const + { + return conductance == short_value () ? 0.0 : 1.0 / conductance; + } + + /** + * @brief Returns a string representation of the element + */ + std::string to_string () const; + + /** + * @brief Gets the network the node lives in + */ + RNetwork *graph () const + { + return mp_network; + } + +protected: + friend class RNetwork; + friend class tl::list_impl; + + RElement (RNetwork *network, double _conductivity, const RNode *a, const RNode *b) + : conductance (_conductivity), mp_network (network), mp_a (a), mp_b (b) + { } + + ~RElement () + { + if (mp_a) { + mp_a->m_elements.erase (m_ia); + } + if (mp_b) { + mp_b->m_elements.erase (m_ib); + } + mp_a = mp_b = 0; + } + + std::list::iterator m_ia, m_ib; + RNetwork *mp_network; + const RNode *mp_a, *mp_b; + +private: + RElement (const RElement &other); + RElement &operator= (const RElement &other); +}; + +/** + * @brief Represents a R network (a graph of RNode and RElement) + */ +class PEX_PUBLIC RNetwork + : public tl::Object +{ +public: + typedef tl::list node_list; + typedef node_list::const_iterator node_iterator; + typedef tl::list element_list; + typedef element_list::const_iterator element_iterator; + + /** + * @brief Constructor + */ + RNetwork (); + + /** + * @brief Destructor + */ + ~RNetwork (); + + /** + * @brief Creates a node with the given type and port index + * + * If the node type is Internal, a new node is created always. + * If the node type is VertexPort or PolygonPort, an existing + * node is returned if one way created with the same type + * or port index already. This avoids creating duplicates + * for the same port. + */ + RNode *create_node (RNode::node_type type, unsigned int port_index); + + /** + * @brief Creates a new element between the given nodes + * + * If an element already exists between the specified nodes, the + * given value is added to the existing element and the existing + * object is returned. + */ + RElement *create_element (double conductance, RNode *a, RNode *b); + + /** + * @brief Removes the given element + * + * Removing the element will also remove any orphan nodes + * at the ends if they are of type Internal. + */ + void remove_element (RElement *element); + + /** + * @brief Removes the node and the attached elements. + * + * Only nodes of type Internal can be removed. + */ + void remove_node (RNode *node); + + /** + * @brief Clears the network + */ + void clear (); + + /** + * @brief Simplifies the network + * + * This will: + * - Join serial resistors if connected by an internal node + * - Remove shorts and join the nodes, if one of them is + * an internal node. The non-internal node will persist. + * - Remove "dangling" resistors if the dangling node is + * an internal one + */ + void simplify (); + + /** + * @brief Iterate the nodes (begin) + */ + node_iterator begin_nodes () const + { + return m_nodes.begin (); + } + + /** + * @brief Iterate the nodes (end) + */ + node_iterator end_nodes () const + { + return m_nodes.end (); + } + + /** + * @brief Gets the number of nodes + */ + size_t num_nodes () const + { + return m_nodes.size (); + } + + /** + * @brief Gets the number of internal nodes + */ + size_t num_internal_nodes () const + { + size_t count = 0; + for (auto n = m_nodes.begin (); n != m_nodes.end (); ++n) { + if (n->type == pex::RNode::Internal) { + ++count; + } + } + return count; + } + + /** + * @brief Iterate the elements (begin) + */ + element_iterator begin_elements () const + { + return m_elements.begin (); + } + + /** + * @brief Iterate the elements (end) + */ + element_iterator end_elements () const + { + return m_elements.end (); + } + + /** + * @brief Gets the number of elements + */ + size_t num_elements () const + { + return m_elements.size (); + } + + /** + * @brief Returns a string representation of the graph + */ + std::string to_string () const; + +private: + node_list m_nodes; + element_list m_elements; + std::map, RElement *> m_elements_by_nodes; + std::map, RNode *> m_nodes_by_type; + + RNetwork (const RNetwork &); + RNetwork &operator= (const RNetwork &); + + void join_nodes (RNode *a, RNode *b); +}; + +} + +#endif + diff --git a/src/pex/pex/pexSquareCountingRExtractor.h b/src/pex/pex/pexSquareCountingRExtractor.h index 2d95b3e26..e5228ad87 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.h +++ b/src/pex/pex/pexSquareCountingRExtractor.h @@ -24,6 +24,7 @@ #define HDR_pexSquareCountingRExtractor #include "pexCommon.h" +#include "pexRNetwork.h" #include "pexRExtractor.h" #include "dbPLCConvexDecomposition.h" diff --git a/src/pex/pex/pexTriangulationRExtractor.h b/src/pex/pex/pexTriangulationRExtractor.h index 6749c7f84..634cf81eb 100644 --- a/src/pex/pex/pexTriangulationRExtractor.h +++ b/src/pex/pex/pexTriangulationRExtractor.h @@ -24,6 +24,7 @@ #define HDR_pexTriangulationRExtractor #include "pexCommon.h" +#include "pexRNetwork.h" #include "pexRExtractor.h" #include "dbPLCTriangulation.h" diff --git a/src/pex/unit_tests/pexRExtractorTests.cc b/src/pex/unit_tests/pexRExtractorTests.cc index b0ace9056..6e04a6f6a 100644 --- a/src/pex/unit_tests/pexRExtractorTests.cc +++ b/src/pex/unit_tests/pexRExtractorTests.cc @@ -22,6 +22,7 @@ #include "pexRExtractor.h" +#include "pexRNetwork.h" #include "tlUnitTest.h" TEST(network_basic) From f83cd61843037a875cb240732541c81cc42fc6d8 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 4 May 2025 13:11:46 +0200 Subject: [PATCH 34/58] WIP: debugging, tests. --- src/db/db/dbPropertiesRepository.cc | 6 +- src/db/db/dbPropertiesRepository.h | 2 +- src/pex/pex/pexRNetExtractor.cc | 68 ++++++---- src/pex/pex/pexRNetExtractor.h | 17 ++- src/pex/pex/pexRNetwork.cc | 18 ++- src/pex/pex/pexRNetwork.h | 6 +- src/pex/pex/pexSquareCountingRExtractor.cc | 5 +- src/pex/unit_tests/pexRNetExtractorTests.cc | 139 ++++++++++++++++++++ src/pex/unit_tests/unit_tests.pro | 1 + testdata/pex/netex_viagen1.gds | Bin 0 -> 414 bytes testdata/pex/netex_viagen2.gds | Bin 0 -> 630 bytes 11 files changed, 220 insertions(+), 42 deletions(-) create mode 100644 src/pex/unit_tests/pexRNetExtractorTests.cc create mode 100644 testdata/pex/netex_viagen1.gds create mode 100644 testdata/pex/netex_viagen2.gds diff --git a/src/db/db/dbPropertiesRepository.cc b/src/db/db/dbPropertiesRepository.cc index 790b2a919..bba686627 100644 --- a/src/db/db/dbPropertiesRepository.cc +++ b/src/db/db/dbPropertiesRepository.cc @@ -373,7 +373,7 @@ PropertiesRepository::prop_name_id (const tl::Variant &name) } } -property_names_id_type +property_values_id_type PropertiesRepository::prop_value_id (const tl::Variant &value) { tl::MutexLocker locker (&m_lock); @@ -383,9 +383,9 @@ PropertiesRepository::prop_value_id (const tl::Variant &value) m_property_values_heap.push_back (value); const tl::Variant &new_value = m_property_values_heap.back (); m_propvalues.insert (&new_value); - return property_names_id_type (&new_value); + return property_values_id_type (&new_value); } else { - return property_names_id_type (*pi); + return property_values_id_type (*pi); } } diff --git a/src/db/db/dbPropertiesRepository.h b/src/db/db/dbPropertiesRepository.h index a5bbf7a9f..99388e326 100644 --- a/src/db/db/dbPropertiesRepository.h +++ b/src/db/db/dbPropertiesRepository.h @@ -358,7 +358,7 @@ public: * This method will assign a new ID to the given value if required and * return the ID associated with it. */ - property_names_id_type prop_value_id (const tl::Variant &name); + property_values_id_type prop_value_id (const tl::Variant &name); /** * @brief Get the ID for a name diff --git a/src/pex/pex/pexRNetExtractor.cc b/src/pex/pex/pexRNetExtractor.cc index 78f4f6a4b..592d48859 100644 --- a/src/pex/pex/pexRNetExtractor.cc +++ b/src/pex/pex/pexRNetExtractor.cc @@ -33,6 +33,7 @@ #include "dbRegionProcessors.h" #include "dbCompoundOperation.h" #include "dbPolygonNeighborhood.h" +#include "dbPropertiesRepository.h" namespace pex { @@ -121,10 +122,11 @@ class ViaAggregationVisitor : public db::PolygonNeighborhoodVisitor { public: - ViaAggregationVisitor (const RExtractorTechVia *via_tech, std::vector > *conductances, double dbu) - : mp_via_tech (via_tech), mp_conductances (conductances), m_dbu (dbu) + ViaAggregationVisitor (const RExtractorTechVia *via_tech, double dbu) + : mp_via_tech (via_tech), m_dbu (dbu) { - // .. nothing yet .. + // this is just for consistency - we actually do not produce output + set_result_type (db::CompoundRegionCheckOperationNode::Region); } virtual void neighbors (const db::Layout * /*layout*/, const db::Cell * /*cell*/, const db::PolygonWithProperties &polygon, const neighbors_type &neighbors) @@ -145,15 +147,44 @@ public: } } - mp_conductances->push_back (std::make_pair (c, polygon.box ().center ())); + db::PropertiesSet ps; + ps.insert (prop_name_id, tl::Variant (c)); + + output_polygon (db::PolygonWithProperties (polygon, db::properties_id (ps))); } + static db::property_names_id_type prop_name_id; + private: const RExtractorTechVia *mp_via_tech; std::vector > *mp_conductances; + db::property_names_id_type m_prop_name_id; double m_dbu; }; +db::property_names_id_type ViaAggregationVisitor::prop_name_id = db::property_names_id (tl::Variant ()); + +} + +void +RNetExtractor::create_via_port (const pex::RExtractorTechVia &tech, + double conductance, + const db::Polygon &poly, + unsigned int &port_index, + std::map > &vias, + RNetwork &rnetwork) +{ + RNode *a = rnetwork.create_node (RNode::Internal, port_index++); + RNode *b = rnetwork.create_node (RNode::Internal, port_index++); + + db::CplxTrans to_um (m_dbu); + db::Box box = poly.box (); + b->location = a->location = to_um * box; + + rnetwork.create_element (conductance, a, b); + + vias[tech.bottom_conductor].push_back (ViaPort (box.center (), a)); + vias[tech.top_conductor].push_back (ViaPort (box.center (), b)); } void @@ -162,7 +193,7 @@ RNetExtractor::create_via_ports (const RExtractorTech &tech, std::map > &vias, RNetwork &rnetwork) { - std::vector > via_conductances; + unsigned int port_index = 0; for (auto v = tech.vias.begin (); v != tech.vias.end (); ++v) { @@ -171,8 +202,6 @@ RNetExtractor::create_via_ports (const RExtractorTech &tech, continue; } - via_conductances.clear (); - if (v->merge_distance > db::epsilon) { // with merge, follow this scheme: @@ -189,32 +218,23 @@ RNetExtractor::create_via_ports (const RExtractorTech &tech, children.push_back (new db::CompoundRegionOperationPrimaryNode ()); children.push_back (new db::CompoundRegionOperationSecondaryNode (const_cast (&g->second))); - ViaAggregationVisitor visitor (v.operator-> (), &via_conductances, m_dbu); + ViaAggregationVisitor visitor (v.operator-> (), m_dbu); db::PolygonNeighborhoodCompoundOperationNode en_node (children, &visitor, 0); - merged_vias.cop_to_region (en_node); + auto aggregated = merged_vias.cop_to_region (en_node); + + for (auto p = aggregated.begin (); ! p.at_end (); ++p) { + double c = db::properties (p.prop_id ()).value (ViaAggregationVisitor::prop_name_id).to_double (); + create_via_port (*v, c, *p, port_index, vias, rnetwork); + } } else { for (auto p = g->second.begin_merged (); ! p.at_end (); ++p) { - via_conductances.push_back (std::make_pair (via_conductance (*v, *p, m_dbu), p->box ().center ())); + create_via_port (*v, via_conductance (*v, *p, m_dbu), *p, port_index, vias, rnetwork); } } - // create the via resistor elements - unsigned int port_index = 0; - for (auto vc = via_conductances.begin (); vc != via_conductances.end (); ++vc) { - - RNode *a = rnetwork.create_node (RNode::Internal, port_index++); - RNode *b = rnetwork.create_node (RNode::Internal, port_index++); - - rnetwork.create_element (vc->first, a, b); - - vias[v->bottom_conductor].push_back (ViaPort (vc->second, a)); - vias[v->top_conductor].push_back (ViaPort (vc->second, b)); - - } - } } diff --git a/src/pex/pex/pexRNetExtractor.h b/src/pex/pex/pexRNetExtractor.h index a2df7d6aa..20ea0ab77 100644 --- a/src/pex/pex/pexRNetExtractor.h +++ b/src/pex/pex/pexRNetExtractor.h @@ -30,6 +30,7 @@ namespace pex { class RExtractorTech; +class RExtractorTechVia; class RExtractorTechConductor; class RNetwork; class RNode; @@ -37,7 +38,7 @@ class RNode; /** * @brief Implementation of the R extractor for a multi-polygon/multi-layer net */ -class RNetExtractor +class PEX_PUBLIC RNetExtractor { public: /** @@ -74,9 +75,7 @@ public: RNode *node; }; -private: - double m_dbu; - +protected: void create_via_ports (const RExtractorTech &tech, const std::map &geo, std::map > &vias, @@ -90,6 +89,16 @@ private: unsigned int polygon_ports_index_offset, const std::vector &via_ports, RNetwork &rnetwork); + +private: + double m_dbu; + + void create_via_port (const RExtractorTechVia &tech, + double conductance, + const db::Polygon &poly, + unsigned int &port_index, + std::map > &vias, + RNetwork &rnetwork); }; } diff --git a/src/pex/pex/pexRNetwork.cc b/src/pex/pex/pexRNetwork.cc index 0a552e2e2..435590d81 100644 --- a/src/pex/pex/pexRNetwork.cc +++ b/src/pex/pex/pexRNetwork.cc @@ -30,9 +30,10 @@ namespace pex // ----------------------------------------------------------------------------- std::string -RNode::to_string () const +RNode::to_string (bool with_coords) const { std::string res; + switch (type) { default: res += "$" + tl::to_string (port_index); @@ -44,24 +45,29 @@ RNode::to_string () const res += "P" + tl::to_string (port_index); break; } + + if (with_coords) { + res += location.to_string (); + } + return res; } // ----------------------------------------------------------------------------- std::string -RElement::to_string () const +RElement::to_string (bool with_coords) const { std::string na; if (a ()) { - na = a ()->to_string (); + na = a ()->to_string (with_coords); } else { na = "(nil)"; } std::string nb; if (b ()) { - nb = b ()->to_string (); + nb = b ()->to_string (with_coords); } else { nb = "(nil)"; } @@ -88,14 +94,14 @@ RNetwork::~RNetwork () } std::string -RNetwork::to_string () const +RNetwork::to_string (bool with_coords) const { std::string res; for (auto e = m_elements.begin (); e != m_elements.end (); ++e) { if (! res.empty ()) { res += "\n"; } - res += e->to_string (); + res += e->to_string (with_coords); } return res; } diff --git a/src/pex/pex/pexRNetwork.h b/src/pex/pex/pexRNetwork.h index d0fe65a9a..45f4ec36a 100644 --- a/src/pex/pex/pexRNetwork.h +++ b/src/pex/pex/pexRNetwork.h @@ -91,7 +91,7 @@ public: /** * @brief Returns a string representation of the node */ - std::string to_string () const; + std::string to_string (bool with_coords = false) const; /** * @brief Gets the network the node lives in @@ -180,7 +180,7 @@ struct PEX_PUBLIC RElement /** * @brief Returns a string representation of the element */ - std::string to_string () const; + std::string to_string (bool with_coords = false) const; /** * @brief Gets the network the node lives in @@ -357,7 +357,7 @@ public: /** * @brief Returns a string representation of the graph */ - std::string to_string () const; + std::string to_string (bool with_coords = false) const; private: node_list m_nodes; diff --git a/src/pex/pex/pexSquareCountingRExtractor.cc b/src/pex/pex/pexSquareCountingRExtractor.cc index 9170239d0..5c69b1fb3 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.cc +++ b/src/pex/pex/pexSquareCountingRExtractor.cc @@ -29,6 +29,9 @@ namespace pex { +// Value used for number of squares for width 0 (should not happen) +const double infinite_squares = 1e10; + namespace { @@ -103,7 +106,7 @@ double calculate_squares (db::Coord x1, db::Coord x2, const std::set & // integrate the resistance along the axis x1->x2 with w=w1->w2 if (w1 < db::epsilon) { - return 1e9; // @@@ + return infinite_squares; } else if (fabs (w1 - w2) < db::epsilon) { return (x2 - x1) / w1; } else { diff --git a/src/pex/unit_tests/pexRNetExtractorTests.cc b/src/pex/unit_tests/pexRNetExtractorTests.cc new file mode 100644 index 000000000..241ded8df --- /dev/null +++ b/src/pex/unit_tests/pexRNetExtractorTests.cc @@ -0,0 +1,139 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "pexRNetExtractor.h" +#include "pexRExtractorTech.h" +#include "pexRNetwork.h" +#include "dbReader.h" +#include "dbLayout.h" +#include "tlUnitTest.h" + +class TestableRNetExtractor + : public pex::RNetExtractor +{ +public: + TestableRNetExtractor (double dbu) : pex::RNetExtractor (dbu) { } + + using pex::RNetExtractor::create_via_ports; +}; + +TEST(netex_viagen1) +{ + db::Layout ly; + + { + std::string fn = tl::testdata () + "/pex/netex_viagen1.gds"; + tl::InputStream is (fn); + db::Reader reader (is); + reader.read (ly); + } + + TestableRNetExtractor rex (ly.dbu ()); + + auto tc = ly.cell_by_name ("TOP"); + tl_assert (tc.first); + + unsigned int l1 = ly.get_layer (db::LayerProperties (1, 0)); + unsigned int l2 = ly.get_layer (db::LayerProperties (2, 0)); + unsigned int l3 = ly.get_layer (db::LayerProperties (3, 0)); + + std::map geo; + geo.insert (std::make_pair (l2, db::Region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l2)))); + + pex::RNetwork network; + + pex::RExtractorTech tech; + + pex::RExtractorTechVia via1; + via1.bottom_conductor = l1; + via1.cut_layer = l2; + via1.top_conductor = l3; + via1.resistance = 2.0; + tech.vias.push_back (via1); + + std::map > via_ports; + rex.create_via_ports (tech, geo, via_ports, network); + + EXPECT_EQ (via_ports [l1].size (), size_t (4)); + EXPECT_EQ (via_ports [l2].size (), size_t (0)); + EXPECT_EQ (via_ports [l3].size (), size_t (4)); + + EXPECT_EQ (network.to_string (true), + "R $0(1.7,0.1;1.9,0.3) $1(1.7,0.1;1.9,0.3) 50\n" + "R $2(0.4,0.5;0.6,0.7) $3(0.4,0.5;0.6,0.7) 50\n" + "R $4(0.8,0.5;1,0.7) $5(0.8,0.5;1,0.7) 50\n" + "R $6(2.9,0.5;3.1,0.7) $7(2.9,0.5;3.1,0.7) 50" + ); +} + +TEST(netex_viagen2) +{ + db::Layout ly; + + { + std::string fn = tl::testdata () + "/pex/netex_viagen2.gds"; + tl::InputStream is (fn); + db::Reader reader (is); + reader.read (ly); + } + + TestableRNetExtractor rex (ly.dbu ()); + + auto tc = ly.cell_by_name ("TOP"); + tl_assert (tc.first); + + unsigned int l1 = ly.get_layer (db::LayerProperties (1, 0)); + unsigned int l2 = ly.get_layer (db::LayerProperties (2, 0)); + unsigned int l3 = ly.get_layer (db::LayerProperties (3, 0)); + + std::map geo; + geo.insert (std::make_pair (l2, db::Region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l2)))); + + pex::RNetwork network; + + pex::RExtractorTech tech; + + pex::RExtractorTechVia via1; + via1.bottom_conductor = l1; + via1.cut_layer = l2; + via1.top_conductor = l3; + via1.resistance = 2.0; + via1.merge_distance = 0.2; + tech.vias.push_back (via1); + + std::map > via_ports; + rex.create_via_ports (tech, geo, via_ports, network); + + EXPECT_EQ (via_ports [l1].size (), size_t (6)); + EXPECT_EQ (via_ports [l2].size (), size_t (0)); + EXPECT_EQ (via_ports [l3].size (), size_t (6)); + + EXPECT_EQ (network.to_string (true), + "R $0(4.6,2.8;4.8,3) $1(4.6,2.8;4.8,3) 50\n" + "R $2(2.5,3.7;2.7,3.9) $3(2.5,3.7;2.7,3.9) 50\n" + "R $4(3,3.7;3.2,3.9) $5(3,3.7;3.2,3.9) 50\n" + "R $6(2.2,1.2;3.4,3.4) $7(2.2,1.2;3.4,3.4) 2.77778\n" + "R $8(0.4,0.4;2.2,4.2) $9(0.4,0.4;2.2,4.2) 1\n" + "R $10(0.6,4.9;1.2,5.1) $11(0.6,4.9;1.2,5.1) 25" + ); +} diff --git a/src/pex/unit_tests/unit_tests.pro b/src/pex/unit_tests/unit_tests.pro index ad99cd169..6ef8b5f93 100644 --- a/src/pex/unit_tests/unit_tests.pro +++ b/src/pex/unit_tests/unit_tests.pro @@ -8,6 +8,7 @@ include($$PWD/../../lib_ut.pri) SOURCES = \ pexRExtractorTests.cc \ + pexRNetExtractorTests.cc \ pexSquareCountingRExtractorTests.cc \ pexTriangulationRExtractorTests.cc diff --git a/testdata/pex/netex_viagen1.gds b/testdata/pex/netex_viagen1.gds new file mode 100644 index 0000000000000000000000000000000000000000..2d7195da23071671e1fc7991e954bc22ef37f67a GIT binary patch literal 414 zcmZQzV_;&6V31*CVt>iN%D}?F&0xqNgUn{&U}E#}bYfr-VP>^+>@@d2w)}&o%MSeo zv!g;7WLWX&V`B^RbYx&);b353<7HxCVqoKAVqjp<5n%rR|Nk5q28yI0W0*LIW?&Hn z>St#lWKW2H08kw_0|SQ;#7;ITMxdEIGOSKfKpF%%gqau^flx+(nSp_O2Ll7p*XT6Z zHX=-6f|$bG0x<JKopMim}1n3T+8W7E>!@$6vgP>vJKpN&}78V8o@})XB literal 0 HcmV?d00001 diff --git a/testdata/pex/netex_viagen2.gds b/testdata/pex/netex_viagen2.gds new file mode 100644 index 0000000000000000000000000000000000000000..d218db4937350f9e918ba427f1fb5c306b2bcaf4 GIT binary patch literal 630 zcmb7>ze)o^5Qo2=+uba=Gvr?-TZ=`&CLlIKf=w`>#UT@4ih$*qwPLC;-=-_Ee}&Fm~5k#nMv)*B8>&X}F`GeP`(Hicp44ktJJ$Ip-N2S-nP z4|j`_=zhub{%ke@8^CV~g6{}0>Sk3{-LFg|m+Q8QFBRL<{1$)JDNj!IUJjw3YWi)X z%p$t!LUp;qKrp&vH-r8K Date: Sun, 4 May 2025 14:36:34 +0200 Subject: [PATCH 35/58] WIP: debugging, tests. Triangulation should be safer now against linear chains of vertexes. --- src/db/db/dbPLCTriangulation.cc | 22 ++++- src/db/unit_tests/dbPLCTriangulationTests.cc | 22 ++++- src/pex/pex/pexRNetExtractor.cc | 16 +++- src/pex/pex/pexSquareCountingRExtractor.cc | 5 +- src/pex/unit_tests/pexRNetExtractorTests.cc | 81 +++++++++++++++++++ testdata/pex/netex_test1.gds | Bin 0 -> 1066 bytes 6 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 testdata/pex/netex_test1.gds diff --git a/src/db/db/dbPLCTriangulation.cc b/src/db/db/dbPLCTriangulation.cc index f8f4fdcc9..70f784f3a 100644 --- a/src/db/db/dbPLCTriangulation.cc +++ b/src/db/db/dbPLCTriangulation.cc @@ -1471,12 +1471,17 @@ Triangulation::triangulate (const db::Region ®ion, const std::vector > edge_contours; + for (auto p = region.begin_merged (); ! p.at_end (); ++p) { + make_contours (*p, trans, edge_contours); + } + unsigned int id = 0; for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { insert_point (trans * *v)->set_is_precious (true, id++); } - create_constrained_delaunay (region, trans); + constrain (edge_contours); refine (parameters); } @@ -1495,12 +1500,15 @@ Triangulation::triangulate (const db::Polygon &poly, const std::vector > edge_contours; + make_contours (poly, trans, edge_contours); + unsigned int id = 0; for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { insert_point (trans * *v)->set_is_precious (true, id++); } - create_constrained_delaunay (poly, trans); + constrain (edge_contours); refine (parameters); } @@ -1517,12 +1525,15 @@ Triangulation::triangulate (const db::Polygon &poly, const std::vector > edge_contours; + make_contours (poly, trans, edge_contours); + unsigned int id = 0; for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { insert_point (trans * *v)->set_is_precious (true, id++); } - create_constrained_delaunay (poly, trans); + constrain (edge_contours); refine (parameters); } @@ -1539,12 +1550,15 @@ Triangulation::triangulate (const db::DPolygon &poly, const std::vector > edge_contours; + make_contours (poly, trans, edge_contours); + unsigned int id = 0; for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { insert_point (trans * *v)->set_is_precious (true, id++); } - create_constrained_delaunay (poly, trans); + constrain (edge_contours); refine (parameters); } diff --git a/src/db/unit_tests/dbPLCTriangulationTests.cc b/src/db/unit_tests/dbPLCTriangulationTests.cc index f31a049b2..2e3845e54 100644 --- a/src/db/unit_tests/dbPLCTriangulationTests.cc +++ b/src/db/unit_tests/dbPLCTriangulationTests.cc @@ -1118,11 +1118,29 @@ TEST(triangulate_with_vertexes) EXPECT_EQ (vp, 0); } + // normal triangulation vertexes.clear (); vertexes.push_back (db::Point (50, 50)); tri.triangulate (poly, vertexes, param, trans); - EXPECT_EQ (plc.to_string (), "((-0.45, 0), (-0.5, -0.05), (-0.5, 0.05)), ((0.5, 0.05), (-0.45, 0), (-0.5, 0.05)), ((-0.45, 0), (0.5, -0.05), (-0.5, -0.05)), ((-0.45, 0), (0.5, 0.05), (0.5, -0.05))"); + EXPECT_EQ (plc.to_string (), "((-0.5, -0.05), (-0.5, 0.05), (-0.45, 0)), ((-0.5, 0.05), (0.5, 0.05), (-0.45, 0)), ((0.5, -0.05), (-0.45, 0), (0.5, 0.05)), ((0.5, -0.05), (-0.5, -0.05), (-0.45, 0))"); + + for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { + auto *vp = tri.find_vertex_for_point (trans * *v); + if (! vp) { + tl::warn << "Vertex not present in output: " << v->to_string (); + EXPECT_EQ (1, 0); + } + } + + // linear chain of vertexes must not break triangulation + vertexes.clear (); + vertexes.push_back (db::Point (50, 50)); + vertexes.push_back (db::Point (100, 50)); + vertexes.push_back (db::Point (150, 50)); + tri.triangulate (poly, vertexes, param, trans); + + EXPECT_EQ (plc.to_string (), "((-0.5, -0.05), (-0.5, 0.05), (-0.45, 0)), ((-0.4, 0), (-0.45, 0), (-0.5, 0.05)), ((-0.5, -0.05), (-0.45, 0), (-0.4, 0)), ((0.5, -0.05), (-0.35, 0), (0.5, 0.05)), ((-0.5, -0.05), (-0.35, 0), (0.5, -0.05)), ((-0.5, -0.05), (-0.4, 0), (-0.35, 0)), ((-0.35, 0), (-0.5, 0.05), (0.5, 0.05)), ((-0.35, 0), (-0.4, 0), (-0.5, 0.05))"); for (auto v = vertexes.begin (); v != vertexes.end (); ++v) { auto *vp = tri.find_vertex_for_point (trans * *v); @@ -1139,7 +1157,7 @@ TEST(triangulate_with_vertexes) tri.triangulate (poly, vertexes, param, trans); EXPECT_GT (plc.num_polygons (), size_t (380)); - EXPECT_LT (plc.num_polygons (), size_t (400)); + EXPECT_LT (plc.num_polygons (), size_t (420)); for (auto t = plc.begin (); t != plc.end (); ++t) { EXPECT_LE (t->area (), param.max_area); diff --git a/src/pex/pex/pexRNetExtractor.cc b/src/pex/pex/pexRNetExtractor.cc index 592d48859..5894ac356 100644 --- a/src/pex/pex/pexRNetExtractor.cc +++ b/src/pex/pex/pexRNetExtractor.cc @@ -79,7 +79,7 @@ RNetExtractor::extract (const RExtractorTech &tech, vp_offset += p->second.size (); } for (auto p = polygon_ports.begin (); p != polygon_ports.end () && p->first < g->first; ++p) { - vp_offset += p->second.size (); + pp_offset += p->second.size (); } // fetch the port list for vertex ports @@ -377,6 +377,7 @@ private: // for internal nodes always create a node in the target network global = mp_rnetwork->create_node (local->type, ++m_next_internal_port_index); + global->location = local->location; } else if (local->type == RNode::VertexPort) { @@ -409,7 +410,7 @@ private: if (i2n != m_id_to_node.end ()) { global = i2n->second; } else { - global = mp_rnetwork->create_node (RNode::VertexPort, index_from_id (id) + m_polygon_port_index_offset); + global = mp_rnetwork->create_node (RNode::PolygonPort, index_from_id (id) + m_polygon_port_index_offset); global->location = local->location; m_id_to_node.insert (std::make_pair (id, global)); } @@ -422,7 +423,7 @@ private: } // create the R elements in the target network - for (auto e = local_network.begin_elements (); e != local_network.begin_elements (); ++e) { + for (auto e = local_network.begin_elements (); e != local_network.end_elements (); ++e) { const RElement *local = e.operator-> (); @@ -431,7 +432,14 @@ private: tl_assert (ia != n2n.end ()); tl_assert (ia != n2n.end ()); - mp_rnetwork->create_element (local->conductance, ia->second, ib->second); + double c; + if (mp_cond->resistance < 1e-10) { + c = RElement::short_value (); + } else { + c = local->conductance / mp_cond->resistance; + } + + mp_rnetwork->create_element (c, ia->second, ib->second); } } diff --git a/src/pex/pex/pexSquareCountingRExtractor.cc b/src/pex/pex/pexSquareCountingRExtractor.cc index 5c69b1fb3..acf9689d1 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.cc +++ b/src/pex/pex/pexSquareCountingRExtractor.cc @@ -181,7 +181,8 @@ SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector { rnetwork.clear (); - db::CplxTrans trans = db::CplxTrans (m_dbu) * db::ICplxTrans (db::Trans (db::Point () - polygon.box ().center ())); + db::CplxTrans to_um (m_dbu); + db::CplxTrans trans = to_um * db::ICplxTrans (db::Trans (db::Point () - polygon.box ().center ())); auto inv_trans = trans.inverted (); db::plc::Graph plc; @@ -285,7 +286,7 @@ SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector auto n4p = nodes_for_ports.find (p->first); if (n4p == nodes_for_ports.end ()) { pex::RNode *node = rnetwork.create_node (p->first.type, p->first.port_index); - node->location = trans * p->first.location; + node->location = to_um * p->first.location; n4p = nodes_for_ports.insert (std::make_pair (p->first, node)).first; } p->second = n4p->second; diff --git a/src/pex/unit_tests/pexRNetExtractorTests.cc b/src/pex/unit_tests/pexRNetExtractorTests.cc index 241ded8df..5227a323b 100644 --- a/src/pex/unit_tests/pexRNetExtractorTests.cc +++ b/src/pex/unit_tests/pexRNetExtractorTests.cc @@ -137,3 +137,84 @@ TEST(netex_viagen2) "R $10(0.6,4.9;1.2,5.1) $11(0.6,4.9;1.2,5.1) 25" ); } + +TEST(netex_2layer) +{ + db::Layout ly; + + { + std::string fn = tl::testdata () + "/pex/netex_test1.gds"; + tl::InputStream is (fn); + db::Reader reader (is); + reader.read (ly); + } + + TestableRNetExtractor rex (ly.dbu ()); + + auto tc = ly.cell_by_name ("TOP"); + tl_assert (tc.first); + + unsigned int l1 = ly.get_layer (db::LayerProperties (1, 0)); + unsigned int l1p = ly.get_layer (db::LayerProperties (1, 1)); + unsigned int l1v = ly.get_layer (db::LayerProperties (1, 2)); + unsigned int l2 = ly.get_layer (db::LayerProperties (2, 0)); + unsigned int l3 = ly.get_layer (db::LayerProperties (3, 0)); + unsigned int l3p = ly.get_layer (db::LayerProperties (3, 1)); + unsigned int l3v = ly.get_layer (db::LayerProperties (3, 2)); + + std::map geo; + geo.insert (std::make_pair (l1, db::Region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l1)))); + geo.insert (std::make_pair (l2, db::Region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l2)))); + geo.insert (std::make_pair (l3, db::Region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l3)))); + + pex::RNetwork network; + + pex::RExtractorTech tech; + + pex::RExtractorTechVia via1; + via1.bottom_conductor = l1; + via1.cut_layer = l2; + via1.top_conductor = l3; + via1.resistance = 2.0; + via1.merge_distance = 0.2; + tech.vias.push_back (via1); + + pex::RExtractorTechConductor cond1; + cond1.layer = l1; + cond1.resistance = 0.5; + tech.conductors.push_back (cond1); + + pex::RExtractorTechConductor cond2; + cond2.layer = l3; + cond2.resistance = 0.25; + tech.conductors.push_back (cond2); + + std::map > vertex_ports; + std::map > polygon_ports; + + db::Region l1p_region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l1p)); + for (auto p = l1p_region.begin_merged (); ! p.at_end (); ++p) { + polygon_ports[l1].push_back (*p); + } + + db::Region l3p_region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l3p)); + for (auto p = l3p_region.begin_merged (); ! p.at_end (); ++p) { + polygon_ports[l3].push_back (*p); + } + + db::Region l1v_region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l1v)); + for (auto p = l1v_region.begin_merged (); ! p.at_end (); ++p) { + vertex_ports[l1].push_back (p->box ().center ()); + } + + db::Region l3v_region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l3v)); + for (auto p = l3v_region.begin_merged (); ! p.at_end (); ++p) { + vertex_ports[l3].push_back (p->box ().center ()); + } + + rex.extract (tech, geo, vertex_ports, polygon_ports, network); + + EXPECT_EQ (network.to_string (true), + "" + ); +} diff --git a/testdata/pex/netex_test1.gds b/testdata/pex/netex_test1.gds new file mode 100644 index 0000000000000000000000000000000000000000..3eef63f00905aea0289f9ba77a3478d07e320a94 GIT binary patch literal 1066 zcmb7@u}i~16vn??l50RK(dr^W0+F_ZAc&$aLW@I-1s#e==l%%}4*mmf-F0wtb?net z{8L)%P>P+;cX>H`SlVjf_$BZ1z3+QR0tq2*5IG_@fDaFO>>#%95eV6+SAk8}KdU?( zU%b7HPS3mh&yTGpB7cES%IkUs@B%>Q1q9@p0I(OjN~tHasM5BVscXVpCw&gfe43Y6 z^xg^(Y_O-?cBtoS z1&-n=O`T}#UE4nyu|65;$=RQ2>M7^yOQBNZrq(Gnxn+MtQzy>V>)f1^=hn0HGW8jM zK0il)zraD8_P?0J25{J;`7@e2(e`J(V0~eij*h;2P0<6k?x!?$qOIriv;4goUUJbVN9W!lC7 literal 0 HcmV?d00001 From aca3095efa99281f664714fd04a2b6ecc692963b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 4 May 2025 15:38:45 +0200 Subject: [PATCH 36/58] WIP: allowing multiple vertex ports on the same location --- src/db/db/dbPLC.cc | 62 ++++++++++++++++--- src/db/db/dbPLC.h | 13 ++-- src/db/db/dbPLCTriangulation.h | 42 +++++++++---- .../dbPLCConvexDecompositionTests.cc | 2 +- src/db/unit_tests/dbPLCGraphTests.cc | 7 ++- src/pex/pex/pexRNetExtractor.cc | 4 +- src/pex/pex/pexSquareCountingRExtractor.cc | 4 +- src/pex/pex/pexTriangulationRExtractor.cc | 30 ++++++--- src/pex/unit_tests/pexRNetExtractorTests.cc | 14 ++++- .../pexTriangulationRExtractorTests.cc | 6 +- 10 files changed, 137 insertions(+), 47 deletions(-) diff --git a/src/db/db/dbPLC.cc b/src/db/db/dbPLC.cc index 7fbb26b44..8f7c650a5 100644 --- a/src/db/db/dbPLC.cc +++ b/src/db/db/dbPLC.cc @@ -43,47 +43,58 @@ namespace plc // Vertex implementation Vertex::Vertex (Graph *graph) - : DPoint (), mp_graph (graph), m_is_precious (false) + : DPoint (), mp_graph (graph), mp_ids (0) { // .. nothing yet .. } Vertex::Vertex (Graph *graph, const db::DPoint &p) - : DPoint (p), mp_graph (graph), m_is_precious (false) + : DPoint (p), mp_graph (graph), mp_ids (0) { // .. nothing yet .. } Vertex::Vertex (Graph *graph, const Vertex &v) - : DPoint (), mp_graph (graph), m_is_precious (false), m_id (0) + : DPoint (), mp_graph (graph), mp_ids (0) { operator= (v); } Vertex::Vertex (Graph *graph, db::DCoord x, db::DCoord y) - : DPoint (x, y), mp_graph (graph), m_is_precious (false), m_id (0) + : DPoint (x, y), mp_graph (graph), mp_ids (0) { // .. nothing yet .. } Vertex::Vertex (const Vertex &v) - : DPoint (v), mp_graph (v.mp_graph), m_is_precious (v.m_is_precious), m_id (v.m_id) + : DPoint (), mp_graph (v.mp_graph), mp_ids (0) { - // NOTE: edges are not copied! + operator= (v); } Vertex::~Vertex () { - // .. nothing yet .. + if (mp_ids) { + delete mp_ids; + mp_ids = 0; + } } Vertex &Vertex::operator= (const Vertex &v) { if (this != &v) { + // NOTE: edges are not copied! db::DPoint::operator= (v); - m_is_precious = v.m_is_precious; - m_id = v.m_id; + + if (mp_ids) { + delete mp_ids; + mp_ids = 0; + } + if (v.mp_ids) { + mp_ids = new std::set (*v.mp_ids); + } + } return *this; } @@ -99,6 +110,39 @@ Vertex::is_outside () const return false; } +void +Vertex::set_is_precious (bool f, unsigned int id) +{ + if (f) { + if (! mp_ids) { + mp_ids = new std::set (); + } + mp_ids->insert (id); + } else { + if (mp_ids) { + delete mp_ids; + mp_ids = 0; + } + } +} + +bool +Vertex::is_precious () const +{ + return mp_ids != 0; +} + +const std::set & +Vertex::ids () const +{ + if (mp_ids != 0) { + return *mp_ids; + } else { + static std::set empty; + return empty; + } +} + std::vector Vertex::polygons () const { diff --git a/src/db/db/dbPLC.h b/src/db/db/dbPLC.h index ea5ed4e38..6a65e61d1 100644 --- a/src/db/db/dbPLC.h +++ b/src/db/db/dbPLC.h @@ -131,16 +131,12 @@ public: * * "precious" vertexes are not removed during triangulation for example. */ - void set_is_precious (bool f, unsigned int id) - { - m_is_precious = f; - m_id = id; - } + void set_is_precious (bool f, unsigned int id); /** * @brief Gets a value indicating whether the vertex is precious */ - bool is_precious () const { return m_is_precious; } + bool is_precious () const; /** * @brief Gets the ID passed to "set_is_precious" @@ -148,7 +144,7 @@ public: * This ID can be used to identify the vertex in the context it came from (e.g. * index in point vector). */ - unsigned int id () const { return m_id; } + const std::set &ids () const; /** * @brief Returns a string representation of the vertex @@ -187,8 +183,7 @@ private: Graph *mp_graph; edges_type mp_edges; - bool m_is_precious : 1; - unsigned int m_id : 31; + std::set *mp_ids; }; /** diff --git a/src/db/db/dbPLCTriangulation.h b/src/db/db/dbPLCTriangulation.h index db97f9651..3d3f30d4d 100644 --- a/src/db/db/dbPLCTriangulation.h +++ b/src/db/db/dbPLCTriangulation.h @@ -236,10 +236,38 @@ public: /** * @brief Refines the triangulation using the given parameters * - * This method is used internally by the "triangulation" method after creating the basic triangulation. + * This method is used internally by the "triangulate" method after creating the basic triangulation. + * + * This method is provided as a partial solution of a triangulation for special cases. */ void refine (const TriangulationParameters ¶m); + /** + * @brief Given a set of contours with edges, mark outer triangles + * + * The edges must be made from existing vertexes. Edge orientation is + * clockwise. + * + * This will also mark triangles as outside ones. + * This method is used internally by the "triangulate" method after creating the basic triangulation. + * + * This method is provided as a partial solution of a triangulation for special cases. + */ + void constrain (const std::vector > &contours); + + /** + * @brief Inserts a contours of a polygon + * + * This method fills the contours of the given polygon by doint an "insert_point" + * on all points and logging the outer edges ("segments") into the "contours" + * array. The latter can be passed to "constrain" to create a constrained + * triangulation. + * + * This method is used internally by the "triangulate" method to create the basic triangulation. + * This method is provided as a partial solution of a triangulation for special cases. + */ + template void make_contours (const Poly &poly, const Trans &trans, std::vector > &contours); + protected: /** * @brief Checks the polygon graph for consistency @@ -287,16 +315,6 @@ protected: */ std::vector ensure_edge (Vertex *from, Vertex *to); - /** - * @brief Given a set of contours with edges, mark outer triangles - * - * The edges must be made from existing vertexes. Edge orientation is - * clockwise. - * - * This will also mark triangles as outside ones. - */ - void constrain (const std::vector > &contours); - /** * @brief Returns a value indicating whether the edge is "illegal" (violates the Delaunay criterion) */ @@ -313,8 +331,6 @@ private: size_t m_id; mutable size_t m_flips, m_hops; - template void make_contours (const Poly &poly, const Trans &trans, std::vector > &contours); - void remove_outside_vertex (Vertex *vertex, std::list > *new_triangles = 0); void remove_inside_vertex (Vertex *vertex, std::list > *new_triangles_out = 0); std::vector fill_concave_corners (const std::vector &edges); diff --git a/src/db/unit_tests/dbPLCConvexDecompositionTests.cc b/src/db/unit_tests/dbPLCConvexDecompositionTests.cc index 5ab556ab8..6b57ef505 100644 --- a/src/db/unit_tests/dbPLCConvexDecompositionTests.cc +++ b/src/db/unit_tests/dbPLCConvexDecompositionTests.cc @@ -145,7 +145,7 @@ TEST(internal_vertex) std::vector ip; for (size_t i = 0; i < p->internal_vertexes (); ++i) { - ip.push_back (p->internal_vertex (i)->to_string () + "#" + tl::to_string (p->internal_vertex (i)->id ())); + ip.push_back (p->internal_vertex (i)->to_string () + "#" + tl::to_string (p->internal_vertex (i)->ids ())); } std::sort (ip.begin (), ip.end ()); EXPECT_EQ (tl::join (ip, "/"), "(0, 0)#2/(0, 0.05)#0/(0.2, 0.07)#1"); diff --git a/src/db/unit_tests/dbPLCGraphTests.cc b/src/db/unit_tests/dbPLCGraphTests.cc index 29b7d9597..9a907a1c6 100644 --- a/src/db/unit_tests/dbPLCGraphTests.cc +++ b/src/db/unit_tests/dbPLCGraphTests.cc @@ -56,7 +56,12 @@ TEST(basic) EXPECT_EQ (v1->is_precious (), false); v1->set_is_precious (true, 17); EXPECT_EQ (v1->is_precious (), true); - EXPECT_EQ (v1->id (), 17u); + EXPECT_EQ (v1->ids ().size (), 1u); + EXPECT_EQ (*v1->ids ().begin (), 17u); + v1->set_is_precious (true, 1); + EXPECT_EQ (v1->is_precious (), true); + EXPECT_EQ (v1->ids ().size (), 2u); + EXPECT_EQ (*v1->ids ().begin (), 2u); } TEST(edge) diff --git a/src/pex/pex/pexRNetExtractor.cc b/src/pex/pex/pexRNetExtractor.cc index 5894ac356..3ce59e3ba 100644 --- a/src/pex/pex/pexRNetExtractor.cc +++ b/src/pex/pex/pexRNetExtractor.cc @@ -468,14 +468,14 @@ RNetExtractor::extract_conductor (const RExtractorTechConductor &cond, // type 0 objects (vertex ports) for (auto i = vertex_ports.begin (); i != vertex_ports.end (); ++i) { - // @@@ could be without enlarge? + // TODO: could be without enlarge? box_heap.push_back (db::Box (*i, *i).enlarged (db::Vector (1, 1))); scanner.insert2 (&box_heap.back (), make_id (i - vertex_ports.begin (), 0)); } // type 1 objects (via ports) for (auto i = via_ports.begin (); i != via_ports.end (); ++i) { - // @@@ could be without enlarge? + // TODO: could be without enlarge? box_heap.push_back (db::Box (i->position, i->position).enlarged (db::Vector (1, 1))); scanner.insert2 (&box_heap.back (), make_id (i - via_ports.begin (), 1)); } diff --git a/src/pex/pex/pexSquareCountingRExtractor.cc b/src/pex/pex/pexSquareCountingRExtractor.cc index acf9689d1..1abf32d95 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.cc +++ b/src/pex/pex/pexSquareCountingRExtractor.cc @@ -269,7 +269,9 @@ SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector for (size_t i = 0; i < plc_poly->internal_vertexes (); ++i) { auto v = plc_poly->internal_vertex (i); db::Point loc = inv_trans * *v; - ports.push_back (std::make_pair (PortDefinition (pex::RNode::VertexPort, loc, v->id ()), (pex::RNode *) 0)); + for (auto pi = v->ids ().begin (); pi != v->ids ().end (); ++pi) { + ports.push_back (std::make_pair (PortDefinition (pex::RNode::VertexPort, loc, *pi), (pex::RNode *) 0)); + } } // 3. polygon ports diff --git a/src/pex/pex/pexTriangulationRExtractor.cc b/src/pex/pex/pexTriangulationRExtractor.cc index b39a6a37d..18f2e70da 100644 --- a/src/pex/pex/pexTriangulationRExtractor.cc +++ b/src/pex/pex/pexTriangulationRExtractor.cc @@ -77,6 +77,14 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< tri.clear (); + std::vector > edge_contours; + + // first step of the triangulation + + for (auto p = residual_poly.begin_merged (); ! p.at_end (); ++p) { + tri.make_contours (*p, trans, edge_contours); + } + unsigned int id = 0; for (auto v = vertex_ports.begin (); v != vertex_ports.end (); ++v) { tri.insert_point (trans * *v)->set_is_precious (true, id++); @@ -91,9 +99,9 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< } } - // perform the triangulation + // constrain and refine the triangulation - tri.create_constrained_delaunay (residual_poly, trans); + tri.constrain (edge_contours); tri.refine (param); // identify the vertexes present for the polygon port -> store them inside pp_vertexes @@ -147,11 +155,19 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< } else if (vertex->is_precious ()) { - size_t port_index = size_t (vertex->id ()); - if (port_index < vertex_ports.size ()) { - n = rnetwork.create_node (pex::RNode::VertexPort, port_index); - n->location = db::DBox (*vertex, *vertex); - vports_present.insert (port_index); + for (auto pi = vertex->ids ().begin (); pi != vertex->ids ().end (); ++pi) { + size_t port_index = size_t (*pi); + if (port_index < vertex_ports.size ()) { + RNode *nn = rnetwork.create_node (pex::RNode::VertexPort, port_index); + nn->location = db::DBox (*vertex, *vertex); + if (n) { + // in case of multiple vertexes on the same spot, short them + rnetwork.create_element (RElement::short_value (), n, nn); + } else { + n = nn; + } + vports_present.insert (port_index); + } } } else { diff --git a/src/pex/unit_tests/pexRNetExtractorTests.cc b/src/pex/unit_tests/pexRNetExtractorTests.cc index 5227a323b..fac83d200 100644 --- a/src/pex/unit_tests/pexRNetExtractorTests.cc +++ b/src/pex/unit_tests/pexRNetExtractorTests.cc @@ -215,6 +215,18 @@ TEST(netex_2layer) rex.extract (tech, geo, vertex_ports, polygon_ports, network); EXPECT_EQ (network.to_string (true), - "" + "R $0(0.3,-5.7;0.5,-5.5) $1(0.3,-5.7;0.5,-5.5) 50\n" + "R $2(9.3,-5.9;9.9,-5.3) $3(9.3,-5.9;9.9,-5.3) 12.5\n" + "R $4(9.3,0.1;9.9,0.3) $5(9.3,0.1;9.9,0.3) 25\n" + "R $6(0.1,0.1;0.7,0.7) $7(0.1,0.1;0.7,0.7) 12.5\n" + "R $0(0.3,-5.7;0.5,-5.5) $2(9.3,-5.9;9.9,-5.3) 5.75\n" + "R $2(9.3,-5.9;9.9,-5.3) P0(12.9,-5.9;13.5,-5.3) 2.25\n" + "R $6(0.1,0.1;0.7,0.7) V0(5.2,0.4;5.2,0.4) 3\n" + "R $4(9.3,0.1;9.9,0.3) V0(5.2,0.4;5.2,0.4) 2.75\n" + "R $5(9.3,0.1;9.9,0.3) $8(10,-3.5;10,-2.7) 1.03125\n" + "R $3(9.3,-5.9;9.9,-5.3) $8(10,-3.5;10,-2.7) 0.78125\n" + "R $8(10,-3.5;10,-2.7) P1(12.9,-3.4;13.5,-2.8) 1\n" + "R $7(0.1,0.1;0.7,0.7) V1(0.4,-5.6;0.4,-5.6) 1.875\n" + "R $1(0.3,-5.7;0.5,-5.5) V1(0.4,-5.6;0.4,-5.6) 0" ); } diff --git a/src/pex/unit_tests/pexTriangulationRExtractorTests.cc b/src/pex/unit_tests/pexTriangulationRExtractorTests.cc index 09f1fce8f..ab485e7cb 100644 --- a/src/pex/unit_tests/pexTriangulationRExtractorTests.cc +++ b/src/pex/unit_tests/pexTriangulationRExtractorTests.cc @@ -301,7 +301,7 @@ TEST(extraction_analytic_disc) rex.extract (disc, vertex_ports, polygon_ports, rn); EXPECT_EQ (rn.to_string (), - "R P0 P1 0.245379" // theoretical: 1/(2*PI)*log(r2/r1) = 0.25615 with r2=10000, r1=2000 + "R P0 P1 0.245558" // theoretical: 1/(2*PI)*log(r2/r1) = 0.25615 with r2=10000, r1=2000 ) rex.triangulation_parameters ().max_area = 100000 * dbu * dbu; @@ -309,7 +309,7 @@ TEST(extraction_analytic_disc) rex.extract (disc, vertex_ports, polygon_ports, rn); EXPECT_EQ (rn.to_string (), - "R P0 P1 0.255614" // theoretical: 1/(2*PI)*log(r2/r1) = 0.25615 with r2=10000, r1=2000 + "R P0 P1 0.255609" // theoretical: 1/(2*PI)*log(r2/r1) = 0.25615 with r2=10000, r1=2000 ) } @@ -357,7 +357,7 @@ TEST(extraction_meander) rex.extract (poly, vertex_ports, polygon_ports, rn); EXPECT_EQ (rn.to_string (), - "R V0 V1 8.75751" // what is the "real" value? + "R V0 V1 8.61417" // what is the "real" value? ) } From f7c7e8b0be20578b6daf8dc865d4e319cf31b761 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 4 May 2025 15:41:26 +0200 Subject: [PATCH 37/58] Fixed unit tests --- src/db/unit_tests/dbPLCConvexDecompositionTests.cc | 2 +- src/db/unit_tests/dbPLCGraphTests.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/db/unit_tests/dbPLCConvexDecompositionTests.cc b/src/db/unit_tests/dbPLCConvexDecompositionTests.cc index 6b57ef505..007e6d4d6 100644 --- a/src/db/unit_tests/dbPLCConvexDecompositionTests.cc +++ b/src/db/unit_tests/dbPLCConvexDecompositionTests.cc @@ -145,7 +145,7 @@ TEST(internal_vertex) std::vector ip; for (size_t i = 0; i < p->internal_vertexes (); ++i) { - ip.push_back (p->internal_vertex (i)->to_string () + "#" + tl::to_string (p->internal_vertex (i)->ids ())); + ip.push_back (p->internal_vertex (i)->to_string () + "#" + tl::join (p->internal_vertex (i)->ids ().begin (), p->internal_vertex (i)->ids ().end (), ",")); } std::sort (ip.begin (), ip.end ()); EXPECT_EQ (tl::join (ip, "/"), "(0, 0)#2/(0, 0.05)#0/(0.2, 0.07)#1"); diff --git a/src/db/unit_tests/dbPLCGraphTests.cc b/src/db/unit_tests/dbPLCGraphTests.cc index 9a907a1c6..1e91948f3 100644 --- a/src/db/unit_tests/dbPLCGraphTests.cc +++ b/src/db/unit_tests/dbPLCGraphTests.cc @@ -61,7 +61,7 @@ TEST(basic) v1->set_is_precious (true, 1); EXPECT_EQ (v1->is_precious (), true); EXPECT_EQ (v1->ids ().size (), 2u); - EXPECT_EQ (*v1->ids ().begin (), 2u); + EXPECT_EQ (*v1->ids ().begin (), 1u); } TEST(edge) From 2bc8ac235aeddfc639b66737b3385b0c51f926d1 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 4 May 2025 16:22:03 +0200 Subject: [PATCH 38/58] Fixed unit tests --- src/pex/pex/gsiDeclRExtractor.cc | 13 ++++++++----- testdata/ruby/dbPolygonTest.rb | 6 +++--- testdata/ruby/dbSimplePolygonTest.rb | 12 ++++++------ 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/pex/pex/gsiDeclRExtractor.cc b/src/pex/pex/gsiDeclRExtractor.cc index 740765e33..8aadb05d3 100644 --- a/src/pex/pex/gsiDeclRExtractor.cc +++ b/src/pex/pex/gsiDeclRExtractor.cc @@ -38,7 +38,7 @@ public: pex::RNode::node_type type () const { return checked_pointer ()->type; } db::DBox location () const { return checked_pointer ()->location; } unsigned int port_index () const { return checked_pointer ()->port_index; } - std::string to_string () const { return checked_pointer ()->to_string (); } + std::string to_string (bool with_coords = false) const { return checked_pointer ()->to_string (with_coords); } size_t obj_id () const { @@ -89,7 +89,7 @@ public: RNode *a () const { return RNode::make_node_object (checked_pointer ()->a ()); } RNode *b () const { return RNode::make_node_object (checked_pointer ()->b ()); } - std::string to_string () const { return checked_pointer ()->to_string (); } + std::string to_string (bool with_coords = false) const { return checked_pointer ()->to_string (with_coords); } size_t obj_id () const { @@ -193,8 +193,9 @@ Class decl_RNode ("pex", "RNode", "ones may represent the same internal object. The 'object_id' is a ID that " "indicates the internal object. Same object_id means same node." ) + - gsi::method ("to_s", &RNode::to_string, + gsi::method ("to_s", &RNode::to_string, gsi::arg ("with_coords", false), "@brief Returns a string representation of this object\n" + "Nodes are printed with coordinates with 'with_coords' is true." ) + gsi::iterator_ext ("each_element", gsi::return_new_object (), &begin_node_elements, &end_network_elements, "@brief Iterates the \\RElement objects attached to the node\n" @@ -226,8 +227,9 @@ Class decl_RElement ("pex", "RElement", "ones may represent the same internal object. The 'object_id' is a ID that " "indicates the internal object. Same object_id means same element." ) + - gsi::method ("to_s", &RElement::to_string, + gsi::method ("to_s", &RElement::to_string, gsi::arg ("with_coords", false), "@brief Returns a string representation of this object\n" + "Nodes are printed with coordinates with 'with_coords' is true." ) + gsi::method ("resistance", &RElement::resistance, "@brief Gets the resistance value of the object\n" @@ -387,8 +389,9 @@ Class decl_RNetwork ("pex", "RNetwork", gsi::method ("num_elements", &pex::RNetwork::num_elements, "@brief Gets the number of elements in the network\n" ) + - gsi::method ("to_s", &pex::RNetwork::to_string, + gsi::method ("to_s", &pex::RNetwork::to_string, gsi::arg ("with_coords", false), "@brief Returns a string representation of the network\n" + "Nodes are printed with coordinates with 'with_coords' is true." ), "@brief Represents a network of resistors\n" "\n" diff --git a/testdata/ruby/dbPolygonTest.rb b/testdata/ruby/dbPolygonTest.rb index c9de10f8e..fc039c9a7 100644 --- a/testdata/ruby/dbPolygonTest.rb +++ b/testdata/ruby/dbPolygonTest.rb @@ -994,15 +994,15 @@ class DBPolygon_TestClass < TestBase assert_equal(p.delaunay(0.0, 0.5).to_s, "(0,0;0,100;250,0);(250,0;500,100;500,0);(250,0;0,100;500,100);(750,0;1000,100;1000,0);(500,0;500,100;750,0);(750,0;500,100;1000,100)") assert_equal(p.delaunay(20000, 0.0).to_s, "(0,0;250,50;500,0);(500,0;250,50;500,100);(250,50;0,100;500,100);(0,0;0,100;250,50);(500,0;500,100;750,50);(500,0;750,50;1000,0);(1000,0;750,50;1000,100);(750,50;500,100;1000,100)") - assert_equal(p.delaunay([ RBA::Point::new(50, 50) ]).to_s, "(0,0;0,100;50,50);(50,50;0,100;1000,100);(0,0;50,50;1000,0);(1000,0;50,50;1000,100)") + assert_equal(p.delaunay([ RBA::Point::new(50, 50) ]).to_s, "(0,0;0,100;50,50);(50,50;0,100;1000,100);(1000,0;50,50;1000,100);(0,0;50,50;1000,0)") p = RBA::DPolygon::new(RBA::DBox::new(0, 0, 1000, 100)) - assert_equal(p.delaunay.collect(&:to_s).join(";"), "(0,0;0,100;1000,100);(0,0;1000,100;1000,0)") + assert_equal(p.delaunay.each.collect(&:to_s).join(";"), "(0,0;0,100;1000,100);(0,0;1000,100;1000,0)") assert_equal(p.delaunay(0.0, 0.5).collect(&:to_s).join(";"), "(0,0;0,100;250,0);(250,0;500,100;500,0);(250,0;0,100;500,100);(750,0;1000,100;1000,0);(500,0;500,100;750,0);(750,0;500,100;1000,100)") assert_equal(p.delaunay(20000, 0.0).collect(&:to_s).join(";"), "(0,0;250,50;500,0);(500,0;250,50;500,100);(250,50;0,100;500,100);(0,0;0,100;250,50);(500,0;500,100;750,50);(500,0;750,50;1000,0);(1000,0;750,50;1000,100);(750,50;500,100;1000,100)") - assert_equal(p.delaunay([ RBA::DPoint::new(50, 50) ]).collect(&:to_s).join(";"), "(0,0;0,100;50,50);(50,50;0,100;1000,100);(0,0;50,50;1000,0);(1000,0;50,50;1000,100)") + assert_equal(p.delaunay([ RBA::DPoint::new(50, 50) ]).collect(&:to_s).join(";"), "(0,0;0,100;50,50);(50,50;0,100;1000,100);(1000,0;50,50;1000,100);(0,0;50,50;1000,0)") end diff --git a/testdata/ruby/dbSimplePolygonTest.rb b/testdata/ruby/dbSimplePolygonTest.rb index ee30323fe..e598228ae 100644 --- a/testdata/ruby/dbSimplePolygonTest.rb +++ b/testdata/ruby/dbSimplePolygonTest.rb @@ -426,15 +426,15 @@ class DBSimplePolygon_TestClass < TestBase assert_equal(p.delaunay(0.0, 0.5).to_s, "(0,0;0,100;250,0);(250,0;500,100;500,0);(250,0;0,100;500,100);(750,0;1000,100;1000,0);(500,0;500,100;750,0);(750,0;500,100;1000,100)") assert_equal(p.delaunay(20000, 0.0).to_s, "(0,0;250,50;500,0);(500,0;250,50;500,100);(250,50;0,100;500,100);(0,0;0,100;250,50);(500,0;500,100;750,50);(500,0;750,50;1000,0);(1000,0;750,50;1000,100);(750,50;500,100;1000,100)") - assert_equal(p.delaunay([ RBA::Point::new(50, 50) ]).to_s, "(0,0;0,100;50,50);(50,50;0,100;1000,100);(0,0;50,50;1000,0);(1000,0;50,50;1000,100)") + assert_equal(p.delaunay([ RBA::Point::new(50, 50) ]).to_s, "(0,0;0,100;50,50);(50,50;0,100;1000,100);(1000,0;50,50;1000,100);(0,0;50,50;1000,0)") - p = RBA::DSimplePolygon::new(RBA::DBox::new(0, 0, 1000, 100)) - assert_equal(p.delaunay.collect(&:to_s).join(";"), "(0,0;0,100;1000,100);(0,0;1000,100;1000,0)") + assert_equal(p.delaunay(20000, 0.0).each.collect(&:to_s).join(";"), "(0,0;250,50;500,0) props={};(500,0;250,50;500,100) props={};(250,50;0,100;500,100) props={};(0,0;0,100;250,50) props={};(500,0;500,100;750,50) props={};(500,0;750,50;1000,0) props={};(1000,0;750,50;1000,100) props={};(750,50;500,100;1000,100) props={}") + assert_equal(p.delaunay.each.collect(&:to_s).join(";"), "(0,0;0,100;1000,100) props={};(0,0;1000,100;1000,0) props={}") - assert_equal(p.delaunay(0.0, 0.5).collect(&:to_s).join(";"), "(0,0;0,100;250,0);(250,0;500,100;500,0);(250,0;0,100;500,100);(750,0;1000,100;1000,0);(500,0;500,100;750,0);(750,0;500,100;1000,100)") - assert_equal(p.delaunay(20000, 0.0).collect(&:to_s).join(";"), "(0,0;250,50;500,0);(500,0;250,50;500,100);(250,50;0,100;500,100);(0,0;0,100;250,50);(500,0;500,100;750,50);(500,0;750,50;1000,0);(1000,0;750,50;1000,100);(750,50;500,100;1000,100)") + assert_equal(p.delaunay(0.0, 0.5).each.collect(&:to_s).join(";"), "(0,0;0,100;250,0) props={};(250,0;500,100;500,0) props={};(250,0;0,100;500,100) props={};(750,0;1000,100;1000,0) props={};(500,0;500,100;750,0) props={};(750,0;500,100;1000,100) props={}") + assert_equal(p.delaunay(20000, 0.0).each.collect(&:to_s).join(";"), "(0,0;250,50;500,0) props={};(500,0;250,50;500,100) props={};(250,50;0,100;500,100) props={};(0,0;0,100;250,50) props={};(500,0;500,100;750,50) props={};(500,0;750,50;1000,0) props={};(1000,0;750,50;1000,100) props={};(750,50;500,100;1000,100) props={}") - assert_equal(p.delaunay([ RBA::DPoint::new(50, 50) ]).collect(&:to_s).join(";"), "(0,0;0,100;50,50);(50,50;0,100;1000,100);(0,0;50,50;1000,0);(1000,0;50,50;1000,100)") + assert_equal(p.delaunay([ RBA::DPoint::new(50, 50) ]).each.collect(&:to_s).join(";"), "(0,0;0,100;50,50) props={};(50,50;0,100;1000,100) props={};(1000,0;50,50;1000,100) props={};(0,0;50,50;1000,0) props={}") end From fc25590dd724cac2822cee220d05116b8a646ca8 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 4 May 2025 17:15:47 +0200 Subject: [PATCH 39/58] Include a simplification step in the net extraction --- src/pex/pex/pexRExtractorTech.cc | 6 ++++++ src/pex/pex/pexRExtractorTech.h | 16 +++++++++++++--- src/pex/pex/pexRNetExtractor.cc | 4 ++++ src/pex/unit_tests/pexRNetExtractorTests.cc | 14 ++++++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/pex/pex/pexRExtractorTech.cc b/src/pex/pex/pexRExtractorTech.cc index 65e1a6604..38cc77554 100644 --- a/src/pex/pex/pexRExtractorTech.cc +++ b/src/pex/pex/pexRExtractorTech.cc @@ -24,6 +24,12 @@ #include "pexRExtractorTech.h" namespace pex +{ + +RExtractorTech::RExtractorTech () + : skip_simplify (false) { // .. nothing yet .. } + +} diff --git a/src/pex/pex/pexRExtractorTech.h b/src/pex/pex/pexRExtractorTech.h index b2840d020..c833e666a 100644 --- a/src/pex/pex/pexRExtractorTech.h +++ b/src/pex/pex/pexRExtractorTech.h @@ -36,7 +36,7 @@ namespace pex * Note that the layers are generic IDs. These are usigned ints specifying * a layer. */ -class RExtractorTechVia +class PEX_PUBLIC RExtractorTechVia { public: RExtractorTechVia () @@ -82,7 +82,7 @@ public: * Note that the layers are generic IDs. These are usigned ints specifying * a layer. */ -class RExtractorTechConductor +class PEX_PUBLIC RExtractorTechConductor { public: /** @@ -147,9 +147,14 @@ public: /** * @brief Specifies the extraction parameters */ -class RExtractorTech +class PEX_PUBLIC RExtractorTech { public: + /** + * @brief Constructor + */ + RExtractorTech (); + /** * @brief A list of via definitions */ @@ -159,6 +164,11 @@ public: * @brief A list of conductor definitions */ std::list conductors; + + /** + * @brief A flag indicating to skip the simplify step after extraction + */ + bool skip_simplify; }; } diff --git a/src/pex/pex/pexRNetExtractor.cc b/src/pex/pex/pexRNetExtractor.cc index 3ce59e3ba..4233a4571 100644 --- a/src/pex/pex/pexRNetExtractor.cc +++ b/src/pex/pex/pexRNetExtractor.cc @@ -101,6 +101,10 @@ RNetExtractor::extract (const RExtractorTech &tech, extract_conductor (*cond, g->second, vp, vp_offset, pp, pp_offset, viap, rnetwork); } + + if (! tech.skip_simplify) { + rnetwork.simplify (); + } } static double diff --git a/src/pex/unit_tests/pexRNetExtractorTests.cc b/src/pex/unit_tests/pexRNetExtractorTests.cc index fac83d200..00c8143ef 100644 --- a/src/pex/unit_tests/pexRNetExtractorTests.cc +++ b/src/pex/unit_tests/pexRNetExtractorTests.cc @@ -170,6 +170,7 @@ TEST(netex_2layer) pex::RNetwork network; pex::RExtractorTech tech; + tech.skip_simplify = true; pex::RExtractorTechVia via1; via1.bottom_conductor = l1; @@ -229,4 +230,17 @@ TEST(netex_2layer) "R $7(0.1,0.1;0.7,0.7) V1(0.4,-5.6;0.4,-5.6) 1.875\n" "R $1(0.3,-5.7;0.5,-5.5) V1(0.4,-5.6;0.4,-5.6) 0" ); + + tech.skip_simplify = false; + + rex.extract (tech, geo, vertex_ports, polygon_ports, network); + + EXPECT_EQ (network.to_string (true), + "R $2(9.3,-5.9;9.9,-5.3) P0(12.9,-5.9;13.5,-5.3) 2.25\n" + "R $8(10,-3.5;10,-2.7) P1(12.9,-3.4;13.5,-2.8) 1\n" + "R $2(9.3,-5.9;9.9,-5.3) V1(0.3,-5.7;0.5,-5.5) 55.75\n" + "R $2(9.3,-5.9;9.9,-5.3) $8(10,-3.5;10,-2.7) 13.2813\n" + "R $8(10,-3.5;10,-2.7) V0(5.2,0.4;5.2,0.4) 28.7812\n" + "R V0(5.2,0.4;5.2,0.4) V1(0.3,-5.7;0.5,-5.5) 17.375" + ); } From 77aa729b0668905d089dd74b725a456e9d492a02 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 4 May 2025 19:45:30 +0200 Subject: [PATCH 40/58] GSI binding of RNetExtractor, introducing layers for nodes --- src/pex/pex/gsiDeclRExtractor.cc | 375 +--------------- src/pex/pex/gsiDeclRNetExtractor.cc | 393 +++++++++++++++++ src/pex/pex/gsiDeclRNetwork.cc | 412 ++++++++++++++++++ src/pex/pex/pex.pro | 2 + src/pex/pex/pexRExtractorTech.h | 4 +- src/pex/pex/pexRNetExtractor.cc | 37 +- src/pex/pex/pexRNetExtractor.h | 2 - src/pex/pex/pexRNetwork.cc | 23 +- src/pex/pex/pexRNetwork.h | 15 +- src/pex/pex/pexSquareCountingRExtractor.cc | 2 +- src/pex/pex/pexTriangulationRExtractor.cc | 8 +- src/pex/unit_tests/pexRExtractorTests.cc | 97 ++++- src/pex/unit_tests/pexRNetExtractorTests.cc | 63 +-- .../pexSquareCountingRExtractorTests.cc | 4 +- 14 files changed, 962 insertions(+), 475 deletions(-) create mode 100644 src/pex/pex/gsiDeclRNetExtractor.cc create mode 100644 src/pex/pex/gsiDeclRNetwork.cc diff --git a/src/pex/pex/gsiDeclRExtractor.cc b/src/pex/pex/gsiDeclRExtractor.cc index 8aadb05d3..a92fa42ec 100644 --- a/src/pex/pex/gsiDeclRExtractor.cc +++ b/src/pex/pex/gsiDeclRExtractor.cc @@ -30,379 +30,6 @@ namespace gsi { -class RNode -{ -public: - ~RNode () { } - - pex::RNode::node_type type () const { return checked_pointer ()->type; } - db::DBox location () const { return checked_pointer ()->location; } - unsigned int port_index () const { return checked_pointer ()->port_index; } - std::string to_string (bool with_coords = false) const { return checked_pointer ()->to_string (with_coords); } - - size_t obj_id () const - { - return size_t (mp_ptr); - } - - static RNode *make_node_object (const pex::RNode *node) - { - return new RNode (node); - } - - const pex::RNode *checked_pointer () const - { - if (! mp_graph.get ()) { - throw tl::Exception (tl::to_string (tr ("Network graph has been destroyed - RNode object no longer is valid"))); - } - return mp_ptr; - } - - pex::RNode *checked_pointer () - { - if (! mp_graph.get ()) { - throw tl::Exception (tl::to_string (tr ("Network graph has been destroyed - RNode object no longer is valid"))); - } - return const_cast (mp_ptr); - } - -private: - tl::weak_ptr mp_graph; - const pex::RNode *mp_ptr; - - RNode (const pex::RNode *node) - : mp_graph (node->graph ()), - mp_ptr (node) - { - // .. nothing yet .. - } -}; - -class RElement -{ -public: - ~RElement () { } - - double conductance () const { return checked_pointer ()->conductance; } - double resistance () const { return checked_pointer ()->resistance (); } - - RNode *a () const { return RNode::make_node_object (checked_pointer ()->a ()); } - RNode *b () const { return RNode::make_node_object (checked_pointer ()->b ()); } - - std::string to_string (bool with_coords = false) const { return checked_pointer ()->to_string (with_coords); } - - size_t obj_id () const - { - return size_t (mp_ptr); - } - - static RElement *make_element_object (const pex::RElement *element) - { - return new RElement (element); - } - - const pex::RElement *checked_pointer () const - { - if (! mp_graph.get ()) { - throw tl::Exception (tl::to_string (tr ("Network graph has been destroyed - RElement object no longer is valid"))); - } - return mp_ptr; - } - - pex::RElement *checked_pointer () - { - if (! mp_graph.get ()) { - throw tl::Exception (tl::to_string (tr ("Network graph has been destroyed - RElement object no longer is valid"))); - } - return const_cast (mp_ptr); - } - -private: - tl::weak_ptr mp_graph; - const pex::RElement *mp_ptr; - - RElement (const pex::RElement *node) - : mp_graph (node->graph ()), - mp_ptr (node) - { - // .. nothing yet .. - } -}; - -class RElementIterator -{ -public: - typedef std::list::const_iterator basic_iter; - typedef std::forward_iterator_tag iterator_category; - typedef RElement *value_type; - typedef RElement *reference; - typedef void pointer; - typedef void difference_type; - - RElementIterator (basic_iter it) - : m_it (it) - { } - - bool operator== (const RElementIterator &it) const { return m_it == it.m_it; } - void operator++ () { ++m_it; } - - RElement *operator* () const - { - return RElement::make_element_object (*m_it); - } - -private: - basic_iter m_it; -}; - -static RElementIterator begin_node_elements (RNode *node) -{ - return RElementIterator (node->checked_pointer ()->elements ().begin ()); -} - -static RElementIterator end_network_elements (RNode *node) -{ - return RElementIterator (node->checked_pointer ()->elements ().end ()); -} - -gsi::Enum decl_NodeType ("pex", "RNodeType", - gsi::enum_const ("Internal", pex::RNode::Internal, - "@brief Specifies an internal node in a R network\n" - "Internal nodes are generated during the R extraction process. " - "The port index of such a node is an arbitrary index." - ) + - gsi::enum_const ("VertexPort", pex::RNode::VertexPort, - "@brief Specifies a vertex port node in a R network\n" - "Vertex port nodes are generated for vertex ports in \\RExtractor#extract, see 'vertex_ports' argument. " - "The port index of such a node refers to the position in that list." - ) + - gsi::enum_const ("PolygonPort", pex::RNode::PolygonPort, - "@brief Specifies a polygon port node in a R network\n" - "Polygon port nodes are generated for polygon ports in \\RExtractor#extract, see 'polygon_ports' argument. " - "The port index of such a node refers to the position in that list." - ), - "@brief This class represents the node type for RNode.\n" - "\n" - "This class has been introduced in version 0.30.1" -); - -Class decl_RNode ("pex", "RNode", - gsi::method ("object_id", &RNode::obj_id, - "@brief Returns an ID representing the actual object\n" - "For every call, a new instance of this object is created, while multiple " - "ones may represent the same internal object. The 'object_id' is a ID that " - "indicates the internal object. Same object_id means same node." - ) + - gsi::method ("to_s", &RNode::to_string, gsi::arg ("with_coords", false), - "@brief Returns a string representation of this object\n" - "Nodes are printed with coordinates with 'with_coords' is true." - ) + - gsi::iterator_ext ("each_element", gsi::return_new_object (), &begin_node_elements, &end_network_elements, - "@brief Iterates the \\RElement objects attached to the node\n" - ) + - gsi::method ("type", &RNode::type, - "@brief Gets the type attribute of the node\n" - ) + - gsi::method ("location", &RNode::location, - "@brief Gets the location attribute of the node\n" - "The location defined the original position of the node" - ) + - gsi::method ("port_index", &RNode::port_index, - "@brief Gets the port index of the node\n" - "The port index associates a node with a original port definition." - ), - "@brief Represents a node in a R network graph\n" - "See \\RNetwork for a description of this object\n" - "\n" - "This class has been introduced in version 0.30.1" -); - -// Inject the RNode::node_type declarations into RNode -gsi::ClassExt inject_NodeType_in_RNode (decl_NodeType.defs ()); - -Class decl_RElement ("pex", "RElement", - gsi::method ("object_id", &RElement::obj_id, - "@brief Returns an ID representing the actual object\n" - "For every call, a new instance of this object is created, while multiple " - "ones may represent the same internal object. The 'object_id' is a ID that " - "indicates the internal object. Same object_id means same element." - ) + - gsi::method ("to_s", &RElement::to_string, gsi::arg ("with_coords", false), - "@brief Returns a string representation of this object\n" - "Nodes are printed with coordinates with 'with_coords' is true." - ) + - gsi::method ("resistance", &RElement::resistance, - "@brief Gets the resistance value of the object\n" - ) + - gsi::factory ("a", &RElement::a, - "@brief Gets the first node the element connects\n" - ) + - gsi::factory ("b", &RElement::b, - "@brief Gets the second node the element connects\n" - ), - "@brief Represents an edge (also called element) in a R network graph\n" - "See \\RNetwork for a description of this object" - "\n" - "This class has been introduced in version 0.30.1" -); - -static RNode *create_node (pex::RNetwork *network, pex::RNode::node_type type, unsigned int port_index) -{ - return RNode::make_node_object (network->create_node (type, port_index)); -} - -static RElement *create_element (pex::RNetwork *network, double r, RNode *a, RNode *b) -{ - double s = fabs (r) < 1e-10 ? pex::RElement::short_value () : 1.0 / r; - return RElement::make_element_object (network->create_element (s, a->checked_pointer (), b->checked_pointer ())); -} - -static void remove_element (pex::RNetwork *network, RElement *element) -{ - network->remove_element (element->checked_pointer ()); -} - -static void remove_node (pex::RNetwork *network, RNode *node) -{ - network->remove_node (node->checked_pointer ()); -} - -class NetworkElementIterator -{ -public: - typedef pex::RNetwork::element_iterator basic_iter; - typedef std::forward_iterator_tag iterator_category; - typedef RElement *value_type; - typedef RElement *reference; - typedef void pointer; - typedef void difference_type; - - NetworkElementIterator (basic_iter it) - : m_it (it) - { } - - bool operator== (const NetworkElementIterator &it) const { return m_it == it.m_it; } - void operator++ () { ++m_it; } - - RElement *operator* () const - { - return RElement::make_element_object (m_it.operator-> ()); - } - -private: - basic_iter m_it; -}; - -static NetworkElementIterator begin_network_elements (pex::RNetwork *network) -{ - return NetworkElementIterator (network->begin_elements ()); -} - -static NetworkElementIterator end_network_elements (pex::RNetwork *network) -{ - return NetworkElementIterator (network->end_elements ()); -} - -class NetworkNodeIterator -{ -public: - typedef pex::RNetwork::node_iterator basic_iter; - typedef std::forward_iterator_tag iterator_category; - typedef RNode *value_type; - typedef RNode *reference; - typedef void pointer; - typedef void difference_type; - - NetworkNodeIterator (basic_iter it) - : m_it (it) - { } - - bool operator== (const NetworkNodeIterator &it) const { return m_it == it.m_it; } - void operator++ () { ++m_it; } - - RNode *operator* () const - { - return RNode::make_node_object (m_it.operator-> ()); - } - -private: - basic_iter m_it; -}; - -static NetworkNodeIterator begin_network_nodes (pex::RNetwork *network) -{ - return NetworkNodeIterator (network->begin_nodes ()); -} - -static NetworkNodeIterator end_network_nodes (pex::RNetwork *network) -{ - return NetworkNodeIterator (network->end_nodes ()); -} - -Class decl_RNetwork ("pex", "RNetwork", - gsi::factory_ext ("create_node", &create_node, gsi::arg ("type"), gsi::arg ("port_index"), - "@brief Creates a new node with the given type and index'.\n" - "@return A reference to the new nbode object." - ) + - gsi::factory_ext ("create_element", &create_element, gsi::arg ("resistance"), gsi::arg ("a"), gsi::arg ("b"), - "@brief Creates a new element between the nodes given by 'a' abd 'b'.\n" - "If a resistor already exists between the two nodes, both resistors are combined into one.\n" - "@return A reference to the new resistor object." - ) + - gsi::method_ext ("remove_element", &remove_element, gsi::arg ("element"), - "@brief Removes the given element\n" - "If removing the element renders an internal node orphan (i.e. without elements), this " - "node is removed too." - ) + - gsi::method_ext ("remove_node", &remove_node, gsi::arg ("node"), - "@brief Removes the given node\n" - "Only internal nodes can be removed. Removing a node will also remove the " - "elements attached to this node." - ) + - gsi::method ("clear", &pex::RNetwork::clear, - "@brief Clears the network\n" - ) + - gsi::method ("simplify", &pex::RNetwork::simplify, - "@brief Simplifies the network\n" - "\n" - "This will:\n" - "@ul\n" - "@li Join serial resistors if connected by an internal node @/li\n" - "@li Remove shorts and join the nodes, if one of them is\n" - " an internal node. The non-internal node will persist @/li\n" - "@li Remove \"dangling\" resistors if the dangling node is\n" - " an internal one @/li\n" - "@/ul\n" - ) + - gsi::iterator_ext ("each_element", gsi::return_new_object (), &begin_network_elements, &end_network_elements, - "@brief Iterates the \\RElement objects inside the network\n" - ) + - gsi::iterator_ext ("each_node", gsi::return_new_object (), &begin_network_nodes, &end_network_nodes, - "@brief Iterates the \\RNode objects inside the network\n" - ) + - gsi::method ("num_nodes", &pex::RNetwork::num_nodes, - "@brief Gets the total number of nodes in the network\n" - ) + - gsi::method ("num_internal_nodes", &pex::RNetwork::num_internal_nodes, - "@brief Gets the number of internal nodes in the network\n" - ) + - gsi::method ("num_elements", &pex::RNetwork::num_elements, - "@brief Gets the number of elements in the network\n" - ) + - gsi::method ("to_s", &pex::RNetwork::to_string, gsi::arg ("with_coords", false), - "@brief Returns a string representation of the network\n" - "Nodes are printed with coordinates with 'with_coords' is true." - ), - "@brief Represents a network of resistors\n" - "\n" - "The network is basically a graph with nodes and edges (the resistors). " - "The resistors are called 'elements' and are represented by \\RElement objects. " - "The nodes are represented by \\RNode objects. " - "The network is created by \\RExtractor#extract, which turns a polygon into a resistor network.\n" - "\n" - "This class has been introduced in version 0.30.1\n" -); - static pex::RExtractor *new_sqc_rextractor (double dbu, bool skip_simplify) { auto res = new pex::SquareCountingRExtractor (dbu); @@ -490,7 +117,7 @@ Class decl_RExtractor ("pex", "RExtractor", "Use \\tesselation_extractor and \\square_counting_extractor to create an actual extractor object.\n" "To use the extractor, call the \\extract method on a given polygon with ports that define the network attachment points.\n" "\n" - "This class has been introduced in version 0.30.1\n" + "This class has been introduced in version 0.30.2\n" ); } diff --git a/src/pex/pex/gsiDeclRNetExtractor.cc b/src/pex/pex/gsiDeclRNetExtractor.cc new file mode 100644 index 000000000..5a65f0367 --- /dev/null +++ b/src/pex/pex/gsiDeclRNetExtractor.cc @@ -0,0 +1,393 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "pexRExtractorTech.h" +#include "pexRNetExtractor.h" +#include "pexRNetwork.h" +#include "gsiEnums.h" + +namespace gsi +{ + +static unsigned int via_get_bottom_conductor (const pex::RExtractorTechVia *via) +{ + return via->bottom_conductor; +} + +static void via_set_bottom_conductor (pex::RExtractorTechVia *via, unsigned int l) +{ + via->bottom_conductor = l; +} + +static unsigned int via_get_cut_layer (const pex::RExtractorTechVia *via) +{ + return via->cut_layer; +} + +static void via_set_cut_layer (pex::RExtractorTechVia *via, unsigned int l) +{ + via->cut_layer = l; +} + +static unsigned int via_get_top_conductor (const pex::RExtractorTechVia *via) +{ + return via->top_conductor; +} + +static void via_set_top_conductor (pex::RExtractorTechVia *via, unsigned int l) +{ + via->top_conductor = l; +} + +static double via_get_resistance (const pex::RExtractorTechVia *via) +{ + return via->resistance; +} + +static void via_set_resistance (pex::RExtractorTechVia *via, double r) +{ + via->resistance = r; +} + +static double via_get_merge_distance (const pex::RExtractorTechVia *via) +{ + return via->merge_distance; +} + +static void via_set_merge_distance (pex::RExtractorTechVia *via, double dist) +{ + via->merge_distance = dist; +} + +Class decl_RExtractorTechVia ("pex", "RExtractorTechVia", + gsi::method_ext ("merge_distance", &via_get_merge_distance, + "@brief Gets the merge distance\n" + "If this value is not zero, it specifies the distance below (or equal) which " + "vias are merged into bigger blocks. This is an optimization to reduce the " + "complexity of the via extraction. The value is given in micrometers." + ) + + gsi::method_ext ("merge_distance=", &via_set_merge_distance, gsi::arg ("d"), + "@brief Sets the merge distance\n" + "See \\merge_distance for a description of this attribute." + ) + + gsi::method_ext ("resistance", &via_get_resistance, + "@brief Gets the area resistance value of the vias\n" + "This value specifies the via resistance in Ohm * square micrometers. " + "The actual resistance is obtained by dividing this value by the via area." + ) + + gsi::method_ext ("resistance=", &via_set_resistance, gsi::arg ("d"), + "@brief Sets the via area resistance value\n" + "See \\resistance for a description of this attribute." + ) + + gsi::method_ext ("bottom_conductor", &via_get_bottom_conductor, + "@brief Gets the bottom conductor layer index\n" + "The layer index is a generic identifier for the layer. It is the value used as key in the " + "geometry and port arguments of \\RNetExtractor#extract." + ) + + gsi::method_ext ("bottom_conductor=", &via_set_bottom_conductor, gsi::arg ("l"), + "@brief Sets the via bottom conductor layer index\n" + "See \\bottom_conductor for a description of this attribute." + ) + + gsi::method_ext ("cut_layer", &via_get_cut_layer, + "@brief Gets the cut layer index\n" + "The layer index is a generic identifier for the layer. It is the value used as key in the " + "geometry and port arguments of \\RNetExtractor#extract. " + "The cut layer is the layer where the via exists." + ) + + gsi::method_ext ("cut_layer=", &via_set_cut_layer, gsi::arg ("l"), + "@brief Sets the cut layer index\n" + "See \\cut_layer for a description of this attribute." + ) + + gsi::method_ext ("top_conductor", &via_get_top_conductor, + "@brief Gets the top conductor layer index\n" + "The layer index is a generic identifier for the layer. It is the value used as key in the " + "geometry and port arguments of \\RNetExtractor#extract." + ) + + gsi::method_ext ("top_conductor=", &via_set_top_conductor, gsi::arg ("l"), + "@brief Sets the via top conductor layer index\n" + "See \\top_conductor for a description of this attribute." + ), + "@brief Describes a via for the network extraction.\n" + "This class is used to describe a via type in the context of " + "the \\RExtractorTech#extract method.\n" + "\n" + "This class has been introduced in version 0.30.2." +); + +static pex::RExtractorTechConductor::Algorithm cond_get_algorithm (const pex::RExtractorTechConductor *cond) +{ + return cond->algorithm; +} + +static void cond_set_algorithm (pex::RExtractorTechConductor *cond, pex::RExtractorTechConductor::Algorithm a) +{ + cond->algorithm = a; +} + +static unsigned int cond_get_layer (const pex::RExtractorTechConductor *cond) +{ + return cond->layer; +} + +static void cond_set_layer (pex::RExtractorTechConductor *cond, unsigned int l) +{ + cond->layer = l; +} + +static double cond_get_resistance (const pex::RExtractorTechConductor *cond) +{ + return cond->resistance; +} + +static void cond_set_resistance (pex::RExtractorTechConductor *cond, double r) +{ + cond->resistance = r; +} + +static double cond_get_triangulation_min_b (const pex::RExtractorTechConductor *cond) +{ + return cond->triangulation_min_b; +} + +static void cond_set_triangulation_min_b (pex::RExtractorTechConductor *cond, double min_b) +{ + cond->triangulation_min_b = min_b; +} + +static double cond_get_triangulation_max_area (const pex::RExtractorTechConductor *cond) +{ + return cond->triangulation_max_area; +} + +static void cond_set_triangulation_max_area (pex::RExtractorTechConductor *cond, double max_area) +{ + cond->triangulation_max_area = max_area; +} + +Class decl_RExtractorTechConductor ("pex", "RExtractorTechConductor", + gsi::method_ext ("algorithm", &cond_get_algorithm, + "@brief Gets the algorithm to use\n" + "Specifies the algorithm to use. The default algorithm is 'SquareCounting'." + ) + + gsi::method_ext ("algorithm=", &cond_set_algorithm, gsi::arg ("d"), + "@brief Sets the algorithm to use\n" + "See \\algorithm for a description of this attribute." + ) + + gsi::method_ext ("resistance", &cond_get_resistance, + "@brief Gets the sheet resistance value of the conductor layer\n" + "This value specifies the cond resistance in Ohm per square. " + "The actual resistance is obtained by multiplying this value with the number of squares." + ) + + gsi::method_ext ("resistance=", &cond_set_resistance, gsi::arg ("r"), + "@brief Sets the sheet resistance value of the conductor layer\n" + "See \\resistance for a description of this attribute." + ) + + gsi::method_ext ("layer", &cond_get_layer, + "@brief Gets the layer index\n" + "The layer index is a generic identifier for the layer. It is the value used as key in the " + "geometry and port arguments of \\RNetExtractor#extract. " + "This attribute specifies the layer the conductor is on." + ) + + gsi::method_ext ("layer=", &cond_set_layer, gsi::arg ("l"), + "@brief Sets the layer index\n" + "See \\layer for a description of this attribute." + ) + + gsi::method_ext ("triangulation_min_b", &cond_get_triangulation_min_b, + "@brief Gets the triangulation 'min_b' parameter\n" + "This parameter is used for the 'Tesselation' algorithm and specifies the shortest edge to circle radius ratio of " + "the Delaunay triangulation. " + ) + + gsi::method_ext ("triangulation_min_b=", &cond_set_triangulation_min_b, gsi::arg ("min_b"), + "@brief Sets the triangulation 'min_b' parameter\n" + "See \\triangulation_min_b for a description of this attribute." + ) + + gsi::method_ext ("triangulation_max_area", &cond_get_triangulation_max_area, + "@brief Gets the triangulation 'max_area' parameter\n" + "This parameter is used for the 'Tesselation' algorithm and specifies the maximum area of " + "the triangles in square micrometers." + ) + + gsi::method_ext ("triangulation_max_area=", &cond_set_triangulation_max_area, gsi::arg ("max_area"), + "@brief Sets the triangulation 'max_area' parameter\n" + "See \\triangulation_max_area for a description of this attribute." + ), + "@brief Describes a conductor layer for the network extraction.\n" + "This class is used to describe a conductor layer in the context of " + "the \\RExtractorTech#extract method.\n" + "\n" + "This class has been introduced in version 0.30.2." +); + +gsi::Enum decl_RExtractorTechConductor_Algorithm ("pex", "Algorithm", + gsi::enum_const ("SquareCounting", pex::RExtractorTechConductor::SquareCounting, + "@brief Specifies the square counting algorithm for \\RExtractorTechConductor#algorithm.\n" + "See \\RExtractor#square_counting_extractor for more details." + ) + + gsi::enum_const ("Tesselation", pex::RExtractorTechConductor::Tesselation, + "@brief Specifies the square counting algorithm for \\RExtractorTechConductor#algorithm.\n" + "See \\RExtractor#tesselation_extractor for more details." + ), + "@brief This enum represents the extraction algorithm for \\RExtractorTechConductor.\n" + "\n" + "This enum has been introduced in version 0.30.2." +); + +gsi::ClassExt inject_RExtractorTechConductor_in_parent (decl_RExtractorTechConductor_Algorithm.defs ()); + +static bool tech_get_skip_simplify (const pex::RExtractorTech *tech) +{ + return tech->skip_simplify; +} + +static void tech_set_skip_simplify (pex::RExtractorTech *tech, bool f) +{ + tech->skip_simplify = f; +} + +static auto tech_begin_vias (const pex::RExtractorTech *tech) +{ + return tech->vias.begin (); +} + +static auto tech_end_vias (const pex::RExtractorTech *tech) +{ + return tech->vias.end (); +} + +static void tech_clear_vias (pex::RExtractorTech *tech) +{ + tech->vias.clear (); +} + +static void tech_add_via (pex::RExtractorTech *tech, const pex::RExtractorTechVia &via) +{ + tech->vias.push_back (via); +} + +static auto tech_begin_conductors (const pex::RExtractorTech *tech) +{ + return tech->conductors.begin (); +} + +static auto tech_end_conductors (const pex::RExtractorTech *tech) +{ + return tech->conductors.end (); +} + +static void tech_clear_conductors (pex::RExtractorTech *tech) +{ + tech->conductors.clear (); +} + +static void tech_add_conductor (pex::RExtractorTech *tech, const pex::RExtractorTechConductor &conductor) +{ + tech->conductors.push_back (conductor); +} + +Class decl_RExtractorTech ("pex", "RExtractorTech", + gsi::method_ext ("skip_simplify", &tech_get_skip_simplify, + "@brief Gets a value indicating whether to skip the simplify step\n" + "This values specifies to skip the simplify step of the network after the extraction has " + "been done. By default, the network is simplified - i.e. serial resistors are joined etc. " + "By setting this attribute to 'false', this step is skipped." + ) + + gsi::method_ext ("skip_simplify=", &tech_set_skip_simplify, gsi::arg ("f"), + "@brief Sets a value indicating whether to skip the simplify step\n" + "See \\skip_simplify for a description of this attribute." + ) + + gsi::method_ext ("each_via", &tech_begin_vias, &tech_end_vias, + "@brief Iterates the list of via definitions\n" + ) + + gsi::method_ext ("clear_vias", &tech_clear_vias, + "@brief Clears the list of via definitions\n" + ) + + gsi::method_ext ("add_via", &tech_add_via, gsi::arg ("via"), + "@brief Adds the given via definition to the list of vias\n" + ) + + gsi::method_ext ("each_conductor", &tech_begin_conductors, &tech_end_conductors, + "@brief Iterates the list of conductor definitions\n" + ) + + gsi::method_ext ("clear_conductors", &tech_clear_conductors, + "@brief Clears the list of conductor definitions\n" + ) + + gsi::method_ext ("add_conductor", &tech_add_conductor, gsi::arg ("conductor"), + "@brief Adds the given conductor definition to the list of conductors\n" + ), + "@brief Specifies the tech stack for the R extraction.\n" + "The tech stack is a collection of via and conductor definitions and some other attributes. " + "It is used for the \\RNetExtractor#extract method.\n" + "\n" + "This enum has been introduced in version 0.30.2." +); + +static pex::RNetExtractor *new_net_rextractor (double dbu) +{ + return new pex::RNetExtractor (dbu); +} + +static pex::RNetwork *rex_extract (pex::RNetExtractor *rex, + const pex::RExtractorTech *tech, + const std::map *geo, + const std::map > *vertex_ports, + const std::map > *polygon_ports) +{ + std::unique_ptr network (new pex::RNetwork ()); + std::map empty_geo; + std::map > empty_vertex_ports; + std::map > empty_polygon_ports; + rex->extract (*tech, geo ? *geo : empty_geo, vertex_ports ? *vertex_ports : empty_vertex_ports, polygon_ports ? *polygon_ports : empty_polygon_ports, *network); + return network.release (); +} + +Class decl_RNetExtractor ("pex", "RNetExtractor", + gsi::constructor ("new", &new_net_rextractor, gsi::arg ("dbu"), + "@brief Creates a network R extractor\n" + "\n" + "@param dbu The database unit of the polygons the extractor will work on\n" + "@param skip_simplify If true, the final step to simplify the netlist will be skipped. This feature is for testing mainly.\n" + "@return A new \\RNetExtractor object that implements the net extractor\n" + ) + + gsi::factory_ext ("extract", &rex_extract, gsi::arg ("tech_stack"), gsi::arg ("geo"), gsi::arg ("vertex_ports"), gsi::arg ("polygon_ports"), + "@brief Runs the extraction on the given multi-layer geometry\n" + "See the description of the class for more details." + ), + "@brief The network R extractor class\n" + "\n" + "This class provides the algorithms for extracting a R network from a multi-layer arrangement of conductors and vias.\n" + "The main feature is the \\extract method. It takes a multi-layer geometry, a tech stack and a number of port definitions\n" + "and returns a R network. The nodes in that network are annotated, so the corresponding port can be deduced from a node of\n" + "VertexPort or PolygonPort type.\n" + "\n" + "Layers are given by layer indexes - those are generic IDs. Every layer has to be given a unique ID, which must be used throughout " + "the different specifications (geometry, vias, conductors, ports).\n" + "\n" + "Two kind of ports are provided: point-like vertex ports and polygon ports. Polygons for polygon ports should be convex and sit inside " + "the geometry they mark. Ports become nodes in the network. Beside ports, the network can have internal nodes. Nodes are annotated with " + "a type (vertex, polygon, internal) and an index and layer. The layer is the layer ID, the index specifies the position of the " + "corresponding port in the 'vertex_ports' or 'polygon_ports' list of the \\extract call.\n" + "\n" + "This class has been introduced in version 0.30.2\n" +); + +} + diff --git a/src/pex/pex/gsiDeclRNetwork.cc b/src/pex/pex/gsiDeclRNetwork.cc new file mode 100644 index 000000000..fb1c2a4f7 --- /dev/null +++ b/src/pex/pex/gsiDeclRNetwork.cc @@ -0,0 +1,412 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 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 "pexRExtractor.h" +#include "pexSquareCountingRExtractor.h" +#include "pexTriangulationRExtractor.h" +#include "gsiEnums.h" + +namespace gsi +{ + +class RNode +{ +public: + ~RNode () { } + + pex::RNode::node_type type () const { return checked_pointer ()->type; } + db::DBox location () const { return checked_pointer ()->location; } + unsigned int port_index () const { return checked_pointer ()->port_index; } + unsigned int layer () const { return checked_pointer ()->layer; } + std::string to_string (bool with_coords = false) const { return checked_pointer ()->to_string (with_coords); } + + size_t obj_id () const + { + return size_t (mp_ptr); + } + + static RNode *make_node_object (const pex::RNode *node) + { + return new RNode (node); + } + + const pex::RNode *checked_pointer () const + { + if (! mp_graph.get ()) { + throw tl::Exception (tl::to_string (tr ("Network graph has been destroyed - RNode object no longer is valid"))); + } + return mp_ptr; + } + + pex::RNode *checked_pointer () + { + if (! mp_graph.get ()) { + throw tl::Exception (tl::to_string (tr ("Network graph has been destroyed - RNode object no longer is valid"))); + } + return const_cast (mp_ptr); + } + +private: + tl::weak_ptr mp_graph; + const pex::RNode *mp_ptr; + + RNode (const pex::RNode *node) + : mp_graph (node->graph ()), + mp_ptr (node) + { + // .. nothing yet .. + } +}; + +class RElement +{ +public: + ~RElement () { } + + double conductance () const { return checked_pointer ()->conductance; } + double resistance () const { return checked_pointer ()->resistance (); } + + RNode *a () const { return RNode::make_node_object (checked_pointer ()->a ()); } + RNode *b () const { return RNode::make_node_object (checked_pointer ()->b ()); } + + std::string to_string (bool with_coords = false) const { return checked_pointer ()->to_string (with_coords); } + + size_t obj_id () const + { + return size_t (mp_ptr); + } + + static RElement *make_element_object (const pex::RElement *element) + { + return new RElement (element); + } + + const pex::RElement *checked_pointer () const + { + if (! mp_graph.get ()) { + throw tl::Exception (tl::to_string (tr ("Network graph has been destroyed - RElement object no longer is valid"))); + } + return mp_ptr; + } + + pex::RElement *checked_pointer () + { + if (! mp_graph.get ()) { + throw tl::Exception (tl::to_string (tr ("Network graph has been destroyed - RElement object no longer is valid"))); + } + return const_cast (mp_ptr); + } + +private: + tl::weak_ptr mp_graph; + const pex::RElement *mp_ptr; + + RElement (const pex::RElement *node) + : mp_graph (node->graph ()), + mp_ptr (node) + { + // .. nothing yet .. + } +}; + +class RElementIterator +{ +public: + typedef std::list::const_iterator basic_iter; + typedef std::forward_iterator_tag iterator_category; + typedef RElement *value_type; + typedef RElement *reference; + typedef void pointer; + typedef void difference_type; + + RElementIterator (basic_iter it) + : m_it (it) + { } + + bool operator== (const RElementIterator &it) const { return m_it == it.m_it; } + void operator++ () { ++m_it; } + + RElement *operator* () const + { + return RElement::make_element_object (*m_it); + } + +private: + basic_iter m_it; +}; + +static RElementIterator begin_node_elements (RNode *node) +{ + return RElementIterator (node->checked_pointer ()->elements ().begin ()); +} + +static RElementIterator end_network_elements (RNode *node) +{ + return RElementIterator (node->checked_pointer ()->elements ().end ()); +} + +gsi::Enum decl_NodeType ("pex", "RNodeType", + gsi::enum_const ("Internal", pex::RNode::Internal, + "@brief Specifies an internal node in a R network\n" + "Internal nodes are generated during the R extraction process. " + "The port index of such a node is an arbitrary index." + ) + + gsi::enum_const ("VertexPort", pex::RNode::VertexPort, + "@brief Specifies a vertex port node in a R network\n" + "Vertex port nodes are generated for vertex ports in \\RExtractor#extract, see 'vertex_ports' argument. " + "The port index of such a node refers to the position in that list." + ) + + gsi::enum_const ("PolygonPort", pex::RNode::PolygonPort, + "@brief Specifies a polygon port node in a R network\n" + "Polygon port nodes are generated for polygon ports in \\RExtractor#extract, see 'polygon_ports' argument. " + "The port index of such a node refers to the position in that list." + ), + "@brief This class represents the node type for RNode.\n" + "\n" + "This class has been introduced in version 0.30.2" +); + +Class decl_RNode ("pex", "RNode", + gsi::method ("object_id", &RNode::obj_id, + "@brief Returns an ID representing the actual object\n" + "For every call, a new instance of this object is created, while multiple " + "ones may represent the same internal object. The 'object_id' is a ID that " + "indicates the internal object. Same object_id means same node." + ) + + gsi::method ("to_s", &RNode::to_string, gsi::arg ("with_coords", false), + "@brief Returns a string representation of this object\n" + "Nodes are printed with coordinates with 'with_coords' is true." + ) + + gsi::iterator_ext ("each_element", gsi::return_new_object (), &begin_node_elements, &end_network_elements, + "@brief Iterates the \\RElement objects attached to the node\n" + ) + + gsi::method ("type", &RNode::type, + "@brief Gets the type attribute of the node\n" + ) + + gsi::method ("location", &RNode::location, + "@brief Gets the location attribute of the node\n" + "The location defined the original position of the node" + ) + + gsi::method ("port_index", &RNode::port_index, + "@brief Gets the port index of the node\n" + "The port index associates a node with a original port definition." + ) + + gsi::method ("layer", &RNode::layer, + "@brief Gets the Layer ID of the node\n" + "The port index associates a node with a original port definition layer-wise." + ), + "@brief Represents a node in a R network graph\n" + "See \\RNetwork for a description of this object\n" + "\n" + "This class has been introduced in version 0.30.2" +); + +// Inject the RNode::node_type declarations into RNode +gsi::ClassExt inject_NodeType_in_RNode (decl_NodeType.defs ()); + +Class decl_RElement ("pex", "RElement", + gsi::method ("object_id", &RElement::obj_id, + "@brief Returns an ID representing the actual object\n" + "For every call, a new instance of this object is created, while multiple " + "ones may represent the same internal object. The 'object_id' is a ID that " + "indicates the internal object. Same object_id means same element." + ) + + gsi::method ("to_s", &RElement::to_string, gsi::arg ("with_coords", false), + "@brief Returns a string representation of this object\n" + "Nodes are printed with coordinates with 'with_coords' is true." + ) + + gsi::method ("resistance", &RElement::resistance, + "@brief Gets the resistance value of the object\n" + ) + + gsi::factory ("a", &RElement::a, + "@brief Gets the first node the element connects\n" + ) + + gsi::factory ("b", &RElement::b, + "@brief Gets the second node the element connects\n" + ), + "@brief Represents an edge (also called element) in a R network graph\n" + "See \\RNetwork for a description of this object" + "\n" + "This class has been introduced in version 0.30.2" +); + +static RNode *create_node (pex::RNetwork *network, pex::RNode::node_type type, unsigned int port_index, unsigned int layer) +{ + return RNode::make_node_object (network->create_node (type, port_index, layer)); +} + +static RElement *create_element (pex::RNetwork *network, double r, RNode *a, RNode *b) +{ + double s = fabs (r) < 1e-10 ? pex::RElement::short_value () : 1.0 / r; + return RElement::make_element_object (network->create_element (s, a->checked_pointer (), b->checked_pointer ())); +} + +static void remove_element (pex::RNetwork *network, RElement *element) +{ + network->remove_element (element->checked_pointer ()); +} + +static void remove_node (pex::RNetwork *network, RNode *node) +{ + network->remove_node (node->checked_pointer ()); +} + +class NetworkElementIterator +{ +public: + typedef pex::RNetwork::element_iterator basic_iter; + typedef std::forward_iterator_tag iterator_category; + typedef RElement *value_type; + typedef RElement *reference; + typedef void pointer; + typedef void difference_type; + + NetworkElementIterator (basic_iter it) + : m_it (it) + { } + + bool operator== (const NetworkElementIterator &it) const { return m_it == it.m_it; } + void operator++ () { ++m_it; } + + RElement *operator* () const + { + return RElement::make_element_object (m_it.operator-> ()); + } + +private: + basic_iter m_it; +}; + +static NetworkElementIterator begin_network_elements (pex::RNetwork *network) +{ + return NetworkElementIterator (network->begin_elements ()); +} + +static NetworkElementIterator end_network_elements (pex::RNetwork *network) +{ + return NetworkElementIterator (network->end_elements ()); +} + +class NetworkNodeIterator +{ +public: + typedef pex::RNetwork::node_iterator basic_iter; + typedef std::forward_iterator_tag iterator_category; + typedef RNode *value_type; + typedef RNode *reference; + typedef void pointer; + typedef void difference_type; + + NetworkNodeIterator (basic_iter it) + : m_it (it) + { } + + bool operator== (const NetworkNodeIterator &it) const { return m_it == it.m_it; } + void operator++ () { ++m_it; } + + RNode *operator* () const + { + return RNode::make_node_object (m_it.operator-> ()); + } + +private: + basic_iter m_it; +}; + +static NetworkNodeIterator begin_network_nodes (pex::RNetwork *network) +{ + return NetworkNodeIterator (network->begin_nodes ()); +} + +static NetworkNodeIterator end_network_nodes (pex::RNetwork *network) +{ + return NetworkNodeIterator (network->end_nodes ()); +} + +Class decl_RNetwork ("pex", "RNetwork", + gsi::factory_ext ("create_node", &create_node, gsi::arg ("type"), gsi::arg ("port_index"), gsi::arg ("layer", (unsigned int) 0), + "@brief Creates a new node with the given type and index'.\n" + "@return A reference to the new nbode object." + ) + + gsi::factory_ext ("create_element", &create_element, gsi::arg ("resistance"), gsi::arg ("a"), gsi::arg ("b"), + "@brief Creates a new element between the nodes given by 'a' abd 'b'.\n" + "If a resistor already exists between the two nodes, both resistors are combined into one.\n" + "@return A reference to the new resistor object." + ) + + gsi::method_ext ("remove_element", &remove_element, gsi::arg ("element"), + "@brief Removes the given element\n" + "If removing the element renders an internal node orphan (i.e. without elements), this " + "node is removed too." + ) + + gsi::method_ext ("remove_node", &remove_node, gsi::arg ("node"), + "@brief Removes the given node\n" + "Only internal nodes can be removed. Removing a node will also remove the " + "elements attached to this node." + ) + + gsi::method ("clear", &pex::RNetwork::clear, + "@brief Clears the network\n" + ) + + gsi::method ("simplify", &pex::RNetwork::simplify, + "@brief Simplifies the network\n" + "\n" + "This will:\n" + "@ul\n" + "@li Join serial resistors if connected by an internal node @/li\n" + "@li Remove shorts and join the nodes, if one of them is\n" + " an internal node. The non-internal node will persist @/li\n" + "@li Remove \"dangling\" resistors if the dangling node is\n" + " an internal one @/li\n" + "@/ul\n" + ) + + gsi::iterator_ext ("each_element", gsi::return_new_object (), &begin_network_elements, &end_network_elements, + "@brief Iterates the \\RElement objects inside the network\n" + ) + + gsi::iterator_ext ("each_node", gsi::return_new_object (), &begin_network_nodes, &end_network_nodes, + "@brief Iterates the \\RNode objects inside the network\n" + ) + + gsi::method ("num_nodes", &pex::RNetwork::num_nodes, + "@brief Gets the total number of nodes in the network\n" + ) + + gsi::method ("num_internal_nodes", &pex::RNetwork::num_internal_nodes, + "@brief Gets the number of internal nodes in the network\n" + ) + + gsi::method ("num_elements", &pex::RNetwork::num_elements, + "@brief Gets the number of elements in the network\n" + ) + + gsi::method ("to_s", &pex::RNetwork::to_string, gsi::arg ("with_coords", false), + "@brief Returns a string representation of the network\n" + "Nodes are printed with coordinates with 'with_coords' is true." + ), + "@brief Represents a network of resistors\n" + "\n" + "The network is basically a graph with nodes and edges (the resistors). " + "The resistors are called 'elements' and are represented by \\RElement objects. " + "The nodes are represented by \\RNode objects. " + "The network is created by \\RExtractor#extract, which turns a polygon into a resistor network.\n" + "\n" + "This class has been introduced in version 0.30.2\n" +); + +} + diff --git a/src/pex/pex/pex.pro b/src/pex/pex/pex.pro index 6d69e018c..ab52eaf29 100644 --- a/src/pex/pex/pex.pro +++ b/src/pex/pex/pex.pro @@ -7,6 +7,8 @@ include($$PWD/../../lib.pri) DEFINES += MAKE_PEX_LIBRARY SOURCES = \ + gsiDeclRNetExtractor.cc \ + gsiDeclRNetwork.cc \ pexForceLink.cc \ pexRExtractor.cc \ gsiDeclRExtractor.cc \ diff --git a/src/pex/pex/pexRExtractorTech.h b/src/pex/pex/pexRExtractorTech.h index c833e666a..6062910ae 100644 --- a/src/pex/pex/pexRExtractorTech.h +++ b/src/pex/pex/pexRExtractorTech.h @@ -97,10 +97,10 @@ public: SquareCounting = 0, /** - * @brief The triangulation algorithm + * @brief The tesselation algorithm * This algorithm is suitable to "large" sheets, specifically substrate. */ - Triangulation = 1 + Tesselation = 1 }; /** diff --git a/src/pex/pex/pexRNetExtractor.cc b/src/pex/pex/pexRNetExtractor.cc index 4233a4571..e93d6e745 100644 --- a/src/pex/pex/pexRNetExtractor.cc +++ b/src/pex/pex/pexRNetExtractor.cc @@ -69,19 +69,6 @@ RNetExtractor::extract (const RExtractorTech &tech, continue; } - // Compute the offsets for the port indexes - // The port indexes are assigned incrementally, so the first index of a port - // for layer n is: - // size(ports for layer 0) + ... + size(ports for layer n-1) - // (size is 0 for empty port list) - unsigned int vp_offset = 0, pp_offset = 0; - for (auto p = vertex_ports.begin (); p != vertex_ports.end () && p->first < g->first; ++p) { - vp_offset += p->second.size (); - } - for (auto p = polygon_ports.begin (); p != polygon_ports.end () && p->first < g->first; ++p) { - pp_offset += p->second.size (); - } - // fetch the port list for vertex ports auto ivp = vertex_ports.find (g->first); static std::vector empty_vertex_ports; @@ -98,7 +85,7 @@ RNetExtractor::extract (const RExtractorTech &tech, const std::vector &viap = iviap == via_ports.end () ? empty_via_ports : iviap->second; // extract the conductor polygon and integrate the results into the target network - extract_conductor (*cond, g->second, vp, vp_offset, pp, pp_offset, viap, rnetwork); + extract_conductor (*cond, g->second, vp, pp, viap, rnetwork); } @@ -178,8 +165,8 @@ RNetExtractor::create_via_port (const pex::RExtractorTechVia &tech, std::map > &vias, RNetwork &rnetwork) { - RNode *a = rnetwork.create_node (RNode::Internal, port_index++); - RNode *b = rnetwork.create_node (RNode::Internal, port_index++); + RNode *a = rnetwork.create_node (RNode::Internal, port_index++, tech.bottom_conductor); + RNode *b = rnetwork.create_node (RNode::Internal, port_index++, tech.top_conductor); db::CplxTrans to_um (m_dbu); db::Box box = poly.box (); @@ -266,9 +253,7 @@ class ExtractingReceiver public: ExtractingReceiver (const RExtractorTechConductor *cond, const std::vector *vertex_ports, - unsigned int vertex_port_index_offset, const std::vector *polygon_ports, - unsigned int polygon_port_index_offset, const std::vector *via_ports, double dbu, RNetwork *rnetwork) @@ -277,8 +262,6 @@ public: mp_polygon_ports (polygon_ports), mp_via_ports (via_ports), m_next_internal_port_index (0), - m_vertex_port_index_offset (vertex_port_index_offset), - m_polygon_port_index_offset (polygon_port_index_offset), m_dbu (dbu), mp_rnetwork (rnetwork) { @@ -316,8 +299,6 @@ private: const std::vector *mp_via_ports; std::map m_id_to_node; unsigned int m_next_internal_port_index; - unsigned int m_vertex_port_index_offset; - unsigned int m_polygon_port_index_offset; double m_dbu; RNetwork *mp_rnetwork; @@ -355,7 +336,7 @@ private: rex.extract (poly, local_vertex_ports, local_polygon_ports, local_network); } break; - case RExtractorTechConductor::Triangulation: + case RExtractorTechConductor::Tesselation: { pex::TriangulationRExtractor rex (m_dbu); rex.extract (poly, local_vertex_ports, local_polygon_ports, local_network); @@ -380,7 +361,7 @@ private: if (local->type == RNode::Internal) { // for internal nodes always create a node in the target network - global = mp_rnetwork->create_node (local->type, ++m_next_internal_port_index); + global = mp_rnetwork->create_node (local->type, ++m_next_internal_port_index, mp_cond->layer); global->location = local->location; } else if (local->type == RNode::VertexPort) { @@ -395,7 +376,7 @@ private: global = i2n->second; } else { if (type_from_id (id) == 0) { // vertex port - global = mp_rnetwork->create_node (RNode::VertexPort, index_from_id (id) + m_vertex_port_index_offset); + global = mp_rnetwork->create_node (RNode::VertexPort, index_from_id (id), mp_cond->layer); global->location = local->location; } else if (type_from_id (id) == 1) { // via port global = (*mp_via_ports) [index_from_id (id)].node; @@ -414,7 +395,7 @@ private: if (i2n != m_id_to_node.end ()) { global = i2n->second; } else { - global = mp_rnetwork->create_node (RNode::PolygonPort, index_from_id (id) + m_polygon_port_index_offset); + global = mp_rnetwork->create_node (RNode::PolygonPort, index_from_id (id), mp_cond->layer); global->location = local->location; m_id_to_node.insert (std::make_pair (id, global)); } @@ -455,9 +436,7 @@ void RNetExtractor::extract_conductor (const RExtractorTechConductor &cond, const db::Region ®ion, const std::vector &vertex_ports, - unsigned int vertex_ports_index_offset, const std::vector &polygon_ports, - unsigned int polygon_ports_index_offset, const std::vector &via_ports, RNetwork &rnetwork) { @@ -490,7 +469,7 @@ RNetExtractor::extract_conductor (const RExtractorTechConductor &cond, scanner.insert2 (&box_heap.back (), make_id (i - polygon_ports.begin (), 2)); } - ExtractingReceiver rec (&cond, &vertex_ports, vertex_ports_index_offset, &polygon_ports, polygon_ports_index_offset, &via_ports, m_dbu, &rnetwork); + ExtractingReceiver rec (&cond, &vertex_ports, &polygon_ports, &via_ports, m_dbu, &rnetwork); scanner.process (rec, 0, db::box_convert (), db::box_convert ()); } diff --git a/src/pex/pex/pexRNetExtractor.h b/src/pex/pex/pexRNetExtractor.h index 20ea0ab77..befd52384 100644 --- a/src/pex/pex/pexRNetExtractor.h +++ b/src/pex/pex/pexRNetExtractor.h @@ -84,9 +84,7 @@ protected: void extract_conductor (const RExtractorTechConductor &cond, const db::Region ®ion, const std::vector &vertex_ports, - unsigned int vertex_ports_index_offset, const std::vector &polygon_ports, - unsigned int polygon_ports_index_offset, const std::vector &via_ports, RNetwork &rnetwork); diff --git a/src/pex/pex/pexRNetwork.cc b/src/pex/pex/pexRNetwork.cc index 435590d81..f64c60360 100644 --- a/src/pex/pex/pexRNetwork.cc +++ b/src/pex/pex/pexRNetwork.cc @@ -33,19 +33,24 @@ std::string RNode::to_string (bool with_coords) const { std::string res; - switch (type) { default: - res += "$" + tl::to_string (port_index); + res += "$"; break; case VertexPort: - res += "V" + tl::to_string (port_index); + res += "V"; break; case PolygonPort: - res += "P" + tl::to_string (port_index); + res += "P"; break; } + res += tl::to_string (port_index); + if (layer > 0) { + res += "."; + res += tl::to_string (layer); + } + if (with_coords) { res += location.to_string (); } @@ -116,27 +121,27 @@ RNetwork::clear () } RNode * -RNetwork::create_node (RNode::node_type type, unsigned int port_index) +RNetwork::create_node (RNode::node_type type, unsigned int port_index, unsigned int layer) { if (type != RNode::Internal) { - auto i = m_nodes_by_type.find (std::make_pair (type, port_index)); + auto i = m_nodes_by_type.find (std::make_pair (type, std::make_pair (port_index, layer))); if (i != m_nodes_by_type.end ()) { return i->second; } else { - RNode *new_node = new RNode (this, type, db::DBox (), port_index); + RNode *new_node = new RNode (this, type, db::DBox (), port_index, layer); m_nodes.push_back (new_node); - m_nodes_by_type.insert (std::make_pair (std::make_pair (type, port_index), new_node)); + m_nodes_by_type.insert (std::make_pair (std::make_pair (type, std::make_pair (port_index, layer)), new_node)); return new_node; } } else { - RNode *new_node = new RNode (this, type, db::DBox (), port_index); + RNode *new_node = new RNode (this, type, db::DBox (), port_index, layer); m_nodes.push_back (new_node); return new_node; diff --git a/src/pex/pex/pexRNetwork.h b/src/pex/pex/pexRNetwork.h index 45f4ec36a..b9438b0d9 100644 --- a/src/pex/pex/pexRNetwork.h +++ b/src/pex/pex/pexRNetwork.h @@ -80,6 +80,13 @@ public: */ unsigned int port_index; + /** + * @brief An index locating the node in a layer + * + * For internal nodes, the layer is 0. + */ + unsigned int layer; + /** * @brief Gets the R elements connected to this node */ @@ -106,8 +113,8 @@ protected: friend class RElement; friend class tl::list_impl; - RNode (RNetwork *network, node_type _type, const db::DBox &_location, unsigned int _port_index) - : type (_type), location (_location), port_index (_port_index), mp_network (network) + RNode (RNetwork *network, node_type _type, const db::DBox &_location, unsigned int _port_index, unsigned int _layer) + : type (_type), location (_location), port_index (_port_index), layer (_layer), mp_network (network) { } ~RNode () { } @@ -249,7 +256,7 @@ public: * or port index already. This avoids creating duplicates * for the same port. */ - RNode *create_node (RNode::node_type type, unsigned int port_index); + RNode *create_node (RNode::node_type type, unsigned int port_index, unsigned int layer); /** * @brief Creates a new element between the given nodes @@ -363,7 +370,7 @@ private: node_list m_nodes; element_list m_elements; std::map, RElement *> m_elements_by_nodes; - std::map, RNode *> m_nodes_by_type; + std::map >, RNode *> m_nodes_by_type; RNetwork (const RNetwork &); RNetwork &operator= (const RNetwork &); diff --git a/src/pex/pex/pexSquareCountingRExtractor.cc b/src/pex/pex/pexSquareCountingRExtractor.cc index 1abf32d95..cd956a703 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.cc +++ b/src/pex/pex/pexSquareCountingRExtractor.cc @@ -287,7 +287,7 @@ SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector for (auto p = ports.begin (); p != ports.end (); ++p) { auto n4p = nodes_for_ports.find (p->first); if (n4p == nodes_for_ports.end ()) { - pex::RNode *node = rnetwork.create_node (p->first.type, p->first.port_index); + pex::RNode *node = rnetwork.create_node (p->first.type, p->first.port_index, 0); node->location = to_um * p->first.location; n4p = nodes_for_ports.insert (std::make_pair (p->first, node)).first; } diff --git a/src/pex/pex/pexTriangulationRExtractor.cc b/src/pex/pex/pexTriangulationRExtractor.cc index 18f2e70da..6a867ef43 100644 --- a/src/pex/pex/pexTriangulationRExtractor.cc +++ b/src/pex/pex/pexTriangulationRExtractor.cc @@ -148,7 +148,7 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< if (pn != pport_nodes.end ()) { n = pn->second; } else { - n = rnetwork.create_node (pex::RNode::PolygonPort, port_index); + n = rnetwork.create_node (pex::RNode::PolygonPort, port_index, 0); pport_nodes.insert (std::make_pair (port_index, n)); n->location = trans * polygon_ports [port_index].box (); } @@ -158,7 +158,7 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< for (auto pi = vertex->ids ().begin (); pi != vertex->ids ().end (); ++pi) { size_t port_index = size_t (*pi); if (port_index < vertex_ports.size ()) { - RNode *nn = rnetwork.create_node (pex::RNode::VertexPort, port_index); + RNode *nn = rnetwork.create_node (pex::RNode::VertexPort, port_index, 0); nn->location = db::DBox (*vertex, *vertex); if (n) { // in case of multiple vertexes on the same spot, short them @@ -172,7 +172,7 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< } else { - n = rnetwork.create_node (pex::RNode::Internal, internal_node_id++); + n = rnetwork.create_node (pex::RNode::Internal, internal_node_id++, 0); n->location = db::DBox (*vertex, *vertex); } @@ -204,7 +204,7 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< if (ip != pport_nodes.end ()) { // create a new vertex port and short it to the polygon port - auto n = rnetwork.create_node (pex::RNode::VertexPort, iv); + auto n = rnetwork.create_node (pex::RNode::VertexPort, iv, 0); n->location = db::DBox (trans * vp, trans * vp); rnetwork.create_element (pex::RElement::short_value (), n, ip->second); diff --git a/src/pex/unit_tests/pexRExtractorTests.cc b/src/pex/unit_tests/pexRExtractorTests.cc index 6e04a6f6a..35ab0169a 100644 --- a/src/pex/unit_tests/pexRExtractorTests.cc +++ b/src/pex/unit_tests/pexRExtractorTests.cc @@ -30,15 +30,74 @@ TEST(network_basic) pex::RNetwork rn; EXPECT_EQ (rn.to_string (), ""); - pex::RNode *n1 = rn.create_node (pex::RNode::Internal, 1); - pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2); + pex::RNode *n1 = rn.create_node (pex::RNode::Internal, 1, 0); + pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 1, 1); + EXPECT_EQ (n1 != n2, true); + pex::RNode *n2_dup = rn.create_node (pex::RNode::Internal, 1, 1); + EXPECT_EQ (n2 != n2_dup, true); + + /* pex::RElement *e12 = */ rn.create_element (0.5, n1, n2); + + EXPECT_EQ (rn.to_string (), + "R $1 $1.1 2" + ); +} + +TEST(network_basic_vertex_nodes) +{ + pex::RNetwork rn; + EXPECT_EQ (rn.to_string (), ""); + + pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1, 0); + pex::RNode *n2 = rn.create_node (pex::RNode::VertexPort, 1, 1); + EXPECT_EQ (n1 != n2, true); + pex::RNode *n2_dup = rn.create_node (pex::RNode::VertexPort, 1, 1); + EXPECT_EQ (n2 == n2_dup, true); + pex::RNode *n2_wrong_type = rn.create_node (pex::RNode::PolygonPort, 1, 1); + EXPECT_EQ (n2 != n2_wrong_type, true); + + /* pex::RElement *e12 = */ rn.create_element (0.5, n1, n2); + + EXPECT_EQ (rn.to_string (), + "R V1 V1.1 2" + ); +} + +TEST(network_basic_polygon_nodes) +{ + pex::RNetwork rn; + EXPECT_EQ (rn.to_string (), ""); + + pex::RNode *n1 = rn.create_node (pex::RNode::PolygonPort, 1, 0); + pex::RNode *n2 = rn.create_node (pex::RNode::PolygonPort, 1, 1); + EXPECT_EQ (n1 != n2, true); + pex::RNode *n2_dup = rn.create_node (pex::RNode::PolygonPort, 1, 1); + EXPECT_EQ (n2 == n2_dup, true); + pex::RNode *n2_wrong_type = rn.create_node (pex::RNode::VertexPort, 1, 1); + EXPECT_EQ (n2 != n2_wrong_type, true); + + /* pex::RElement *e12 = */ rn.create_element (0.5, n1, n2); + + EXPECT_EQ (rn.to_string (), + "R P1 P1.1 2" + ); +} + +TEST(network_basic_elements) +{ + pex::RNetwork rn; + EXPECT_EQ (rn.to_string (), ""); + + pex::RNode *n1 = rn.create_node (pex::RNode::Internal, 1, 0); + pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2, 0); + /* pex::RElement *e12 = */ rn.create_element (0.5, n1, n2); EXPECT_EQ (rn.to_string (), "R $1 $2 2" ); - pex::RNode *n3 = rn.create_node (pex::RNode::Internal, 3); + pex::RNode *n3 = rn.create_node (pex::RNode::Internal, 3, 0); /* pex::RElement *e13 = */ rn.create_element (0.25, n1, n3); pex::RElement *e23 = rn.create_element (1.0, n2, n3); @@ -89,9 +148,9 @@ TEST(network_simplify1) pex::RNetwork rn; EXPECT_EQ (rn.to_string (), ""); - pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1); - pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2); - pex::RNode *n3 = rn.create_node (pex::RNode::VertexPort, 3); + pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1, 0); + pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2, 0); + pex::RNode *n3 = rn.create_node (pex::RNode::VertexPort, 3, 0); rn.create_element (1, n1, n2); rn.create_element (pex::RElement::short_value (), n2, n3); @@ -115,11 +174,11 @@ TEST(network_simplify2) pex::RNetwork rn; EXPECT_EQ (rn.to_string (), ""); - pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1); - pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2); - pex::RNode *n3 = rn.create_node (pex::RNode::Internal, 3); - pex::RNode *n4 = rn.create_node (pex::RNode::VertexPort, 4); - pex::RNode *n5 = rn.create_node (pex::RNode::VertexPort, 5); + pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1, 0); + pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2, 0); + pex::RNode *n3 = rn.create_node (pex::RNode::Internal, 3, 0); + pex::RNode *n4 = rn.create_node (pex::RNode::VertexPort, 4, 0); + pex::RNode *n5 = rn.create_node (pex::RNode::VertexPort, 5, 0); rn.create_element (1, n1, n2); rn.create_element (pex::RElement::short_value (), n2, n3); @@ -147,10 +206,10 @@ TEST(network_simplify3) pex::RNetwork rn; EXPECT_EQ (rn.to_string (), ""); - pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1); - pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2); - pex::RNode *n3 = rn.create_node (pex::RNode::Internal, 3); - pex::RNode *n4 = rn.create_node (pex::RNode::VertexPort, 4); + pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1, 0); + pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2, 0); + pex::RNode *n3 = rn.create_node (pex::RNode::Internal, 3, 0); + pex::RNode *n4 = rn.create_node (pex::RNode::VertexPort, 4, 0); rn.create_element (1, n1, n2); rn.create_element (pex::RElement::short_value (), n2, n3); @@ -174,10 +233,10 @@ TEST(network_simplify4) pex::RNetwork rn; EXPECT_EQ (rn.to_string (), ""); - pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1); - pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2); - pex::RNode *n3 = rn.create_node (pex::RNode::Internal, 3); - pex::RNode *n4 = rn.create_node (pex::RNode::VertexPort, 4); + pex::RNode *n1 = rn.create_node (pex::RNode::VertexPort, 1, 0); + pex::RNode *n2 = rn.create_node (pex::RNode::Internal, 2, 0); + pex::RNode *n3 = rn.create_node (pex::RNode::Internal, 3, 0); + pex::RNode *n4 = rn.create_node (pex::RNode::VertexPort, 4, 0); rn.create_element (1, n1, n4); rn.create_element (1, n2, n1); diff --git a/src/pex/unit_tests/pexRNetExtractorTests.cc b/src/pex/unit_tests/pexRNetExtractorTests.cc index 00c8143ef..9b4d0bd49 100644 --- a/src/pex/unit_tests/pexRNetExtractorTests.cc +++ b/src/pex/unit_tests/pexRNetExtractorTests.cc @@ -79,10 +79,10 @@ TEST(netex_viagen1) EXPECT_EQ (via_ports [l3].size (), size_t (4)); EXPECT_EQ (network.to_string (true), - "R $0(1.7,0.1;1.9,0.3) $1(1.7,0.1;1.9,0.3) 50\n" - "R $2(0.4,0.5;0.6,0.7) $3(0.4,0.5;0.6,0.7) 50\n" - "R $4(0.8,0.5;1,0.7) $5(0.8,0.5;1,0.7) 50\n" - "R $6(2.9,0.5;3.1,0.7) $7(2.9,0.5;3.1,0.7) 50" + "R $0.1(1.7,0.1;1.9,0.3) $1.2(1.7,0.1;1.9,0.3) 50\n" + "R $2.1(0.4,0.5;0.6,0.7) $3.2(0.4,0.5;0.6,0.7) 50\n" + "R $4.1(0.8,0.5;1,0.7) $5.2(0.8,0.5;1,0.7) 50\n" + "R $6.1(2.9,0.5;3.1,0.7) $7.2(2.9,0.5;3.1,0.7) 50" ); } @@ -129,12 +129,12 @@ TEST(netex_viagen2) EXPECT_EQ (via_ports [l3].size (), size_t (6)); EXPECT_EQ (network.to_string (true), - "R $0(4.6,2.8;4.8,3) $1(4.6,2.8;4.8,3) 50\n" - "R $2(2.5,3.7;2.7,3.9) $3(2.5,3.7;2.7,3.9) 50\n" - "R $4(3,3.7;3.2,3.9) $5(3,3.7;3.2,3.9) 50\n" - "R $6(2.2,1.2;3.4,3.4) $7(2.2,1.2;3.4,3.4) 2.77778\n" - "R $8(0.4,0.4;2.2,4.2) $9(0.4,0.4;2.2,4.2) 1\n" - "R $10(0.6,4.9;1.2,5.1) $11(0.6,4.9;1.2,5.1) 25" + "R $0.1(4.6,2.8;4.8,3) $1.2(4.6,2.8;4.8,3) 50\n" + "R $2.1(2.5,3.7;2.7,3.9) $3.2(2.5,3.7;2.7,3.9) 50\n" + "R $4.1(3,3.7;3.2,3.9) $5.2(3,3.7;3.2,3.9) 50\n" + "R $6.1(2.2,1.2;3.4,3.4) $7.2(2.2,1.2;3.4,3.4) 2.77778\n" + "R $8.1(0.4,0.4;2.2,4.2) $9.2(0.4,0.4;2.2,4.2) 1\n" + "R $10.1(0.6,4.9;1.2,5.1) $11.2(0.6,4.9;1.2,5.1) 25" ); } @@ -162,6 +162,11 @@ TEST(netex_2layer) unsigned int l3p = ly.get_layer (db::LayerProperties (3, 1)); unsigned int l3v = ly.get_layer (db::LayerProperties (3, 2)); + // That is coincidence, but it needs to be that way for the strings to match + EXPECT_EQ (l1, 1u); + EXPECT_EQ (l2, 0u); + EXPECT_EQ (l3, 2u); + std::map geo; geo.insert (std::make_pair (l1, db::Region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l1)))); geo.insert (std::make_pair (l2, db::Region (db::RecursiveShapeIterator (ly, ly.cell (tc.second), l2)))); @@ -216,19 +221,19 @@ TEST(netex_2layer) rex.extract (tech, geo, vertex_ports, polygon_ports, network); EXPECT_EQ (network.to_string (true), - "R $0(0.3,-5.7;0.5,-5.5) $1(0.3,-5.7;0.5,-5.5) 50\n" - "R $2(9.3,-5.9;9.9,-5.3) $3(9.3,-5.9;9.9,-5.3) 12.5\n" - "R $4(9.3,0.1;9.9,0.3) $5(9.3,0.1;9.9,0.3) 25\n" - "R $6(0.1,0.1;0.7,0.7) $7(0.1,0.1;0.7,0.7) 12.5\n" - "R $0(0.3,-5.7;0.5,-5.5) $2(9.3,-5.9;9.9,-5.3) 5.75\n" - "R $2(9.3,-5.9;9.9,-5.3) P0(12.9,-5.9;13.5,-5.3) 2.25\n" - "R $6(0.1,0.1;0.7,0.7) V0(5.2,0.4;5.2,0.4) 3\n" - "R $4(9.3,0.1;9.9,0.3) V0(5.2,0.4;5.2,0.4) 2.75\n" - "R $5(9.3,0.1;9.9,0.3) $8(10,-3.5;10,-2.7) 1.03125\n" - "R $3(9.3,-5.9;9.9,-5.3) $8(10,-3.5;10,-2.7) 0.78125\n" - "R $8(10,-3.5;10,-2.7) P1(12.9,-3.4;13.5,-2.8) 1\n" - "R $7(0.1,0.1;0.7,0.7) V1(0.4,-5.6;0.4,-5.6) 1.875\n" - "R $1(0.3,-5.7;0.5,-5.5) V1(0.4,-5.6;0.4,-5.6) 0" + "R $0.1(0.3,-5.7;0.5,-5.5) $1.2(0.3,-5.7;0.5,-5.5) 50\n" + "R $2.1(9.3,-5.9;9.9,-5.3) $3.2(9.3,-5.9;9.9,-5.3) 12.5\n" + "R $4.1(9.3,0.1;9.9,0.3) $5.2(9.3,0.1;9.9,0.3) 25\n" + "R $6.1(0.1,0.1;0.7,0.7) $7.2(0.1,0.1;0.7,0.7) 12.5\n" + "R $0.1(0.3,-5.7;0.5,-5.5) $2.1(9.3,-5.9;9.9,-5.3) 5.75\n" + "R $2.1(9.3,-5.9;9.9,-5.3) P0.1(12.9,-5.9;13.5,-5.3) 2.25\n" + "R $6.1(0.1,0.1;0.7,0.7) V0.1(5.2,0.4;5.2,0.4) 3\n" + "R $4.1(9.3,0.1;9.9,0.3) V0.1(5.2,0.4;5.2,0.4) 2.75\n" + "R $5.2(9.3,0.1;9.9,0.3) $8.2(10,-3.5;10,-2.7) 1.03125\n" + "R $3.2(9.3,-5.9;9.9,-5.3) $8.2(10,-3.5;10,-2.7) 0.78125\n" + "R $8.2(10,-3.5;10,-2.7) P0.2(12.9,-3.4;13.5,-2.8) 1\n" + "R $7.2(0.1,0.1;0.7,0.7) V0.2(0.4,-5.6;0.4,-5.6) 1.875\n" + "R $1.2(0.3,-5.7;0.5,-5.5) V0.2(0.4,-5.6;0.4,-5.6) 0" ); tech.skip_simplify = false; @@ -236,11 +241,11 @@ TEST(netex_2layer) rex.extract (tech, geo, vertex_ports, polygon_ports, network); EXPECT_EQ (network.to_string (true), - "R $2(9.3,-5.9;9.9,-5.3) P0(12.9,-5.9;13.5,-5.3) 2.25\n" - "R $8(10,-3.5;10,-2.7) P1(12.9,-3.4;13.5,-2.8) 1\n" - "R $2(9.3,-5.9;9.9,-5.3) V1(0.3,-5.7;0.5,-5.5) 55.75\n" - "R $2(9.3,-5.9;9.9,-5.3) $8(10,-3.5;10,-2.7) 13.2813\n" - "R $8(10,-3.5;10,-2.7) V0(5.2,0.4;5.2,0.4) 28.7812\n" - "R V0(5.2,0.4;5.2,0.4) V1(0.3,-5.7;0.5,-5.5) 17.375" + "R $2.1(9.3,-5.9;9.9,-5.3) P0.1(12.9,-5.9;13.5,-5.3) 2.25\n" + "R $8.2(10,-3.5;10,-2.7) P0.2(12.9,-3.4;13.5,-2.8) 1\n" + "R $2.1(9.3,-5.9;9.9,-5.3) V0.2(0.3,-5.7;0.5,-5.5) 55.75\n" + "R $2.1(9.3,-5.9;9.9,-5.3) $8.2(10,-3.5;10,-2.7) 13.2813\n" + "R $8.2(10,-3.5;10,-2.7) V0.1(5.2,0.4;5.2,0.4) 28.7812\n" + "R V0.1(5.2,0.4;5.2,0.4) V0.2(0.3,-5.7;0.5,-5.5) 17.375" ); } diff --git a/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc b/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc index 3da3d7b58..e3b6e057e 100644 --- a/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc +++ b/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc @@ -69,7 +69,7 @@ TEST(basic) std::vector > ports; for (auto pd = pds.begin (); pd != pds.end (); ++pd) { - ports.push_back (std::make_pair (*pd, rn.create_node (pd->type, pd->port_index))); + ports.push_back (std::make_pair (*pd, rn.create_node (pd->type, pd->port_index, 0))); } rex.do_extract (poly, ports, rn); @@ -90,7 +90,7 @@ TEST(basic) ports.clear (); for (auto pd = pds.begin (); pd != pds.end (); ++pd) { - ports.push_back (std::make_pair (*pd, rn.create_node (pd->type, pd->port_index))); + ports.push_back (std::make_pair (*pd, rn.create_node (pd->type, pd->port_index, 0))); ports.back ().first.location.transform (r90); } From 91005d5cb6896eb3862643f18b5d81ef3b1356e2 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 4 May 2025 20:42:12 +0200 Subject: [PATCH 41/58] Tests for RBA+pex, bug fixes --- src/pex/pex/gsiDeclRNetExtractor.cc | 4 +- testdata/ruby/pexTests.rb | 133 +++++++++++++++++++++++++++- 2 files changed, 134 insertions(+), 3 deletions(-) diff --git a/src/pex/pex/gsiDeclRNetExtractor.cc b/src/pex/pex/gsiDeclRNetExtractor.cc index 5a65f0367..3fff2dad5 100644 --- a/src/pex/pex/gsiDeclRNetExtractor.cc +++ b/src/pex/pex/gsiDeclRNetExtractor.cc @@ -315,7 +315,7 @@ Class decl_RExtractorTech ("pex", "RExtractorTech", "@brief Sets a value indicating whether to skip the simplify step\n" "See \\skip_simplify for a description of this attribute." ) + - gsi::method_ext ("each_via", &tech_begin_vias, &tech_end_vias, + gsi::iterator_ext ("each_via", &tech_begin_vias, &tech_end_vias, "@brief Iterates the list of via definitions\n" ) + gsi::method_ext ("clear_vias", &tech_clear_vias, @@ -324,7 +324,7 @@ Class decl_RExtractorTech ("pex", "RExtractorTech", gsi::method_ext ("add_via", &tech_add_via, gsi::arg ("via"), "@brief Adds the given via definition to the list of vias\n" ) + - gsi::method_ext ("each_conductor", &tech_begin_conductors, &tech_end_conductors, + gsi::iterator_ext ("each_conductor", &tech_begin_conductors, &tech_end_conductors, "@brief Iterates the list of conductor definitions\n" ) + gsi::method_ext ("clear_conductors", &tech_clear_conductors, diff --git a/testdata/ruby/pexTests.rb b/testdata/ruby/pexTests.rb index c18de6e03..477f07ed3 100644 --- a/testdata/ruby/pexTests.rb +++ b/testdata/ruby/pexTests.rb @@ -34,6 +34,7 @@ class PEX_TestClass < TestBase a = rn.create_node(RBA::RNode::VertexPort, 1) b = rn.create_node(RBA::RNode::Internal, 2) c = rn.create_node(RBA::RNode::PolygonPort, 3) + d = rn.create_node(RBA::RNode::PolygonPort, 3, 17) assert_equal(a.type, RBA::RNode::VertexPort) assert_equal(a.port_index, 1) @@ -43,6 +44,7 @@ class PEX_TestClass < TestBase assert_equal(b.to_s, "$2") assert_equal(c.to_s, "P3") + assert_equal(d.to_s, "P3.17") rab = rn.create_element(1.0, a, b) assert_equal(rab.a.object_id, a.object_id) @@ -58,7 +60,7 @@ class PEX_TestClass < TestBase assert_equal(b.each_element.collect(&:to_s).sort.join(";"), "R $2 P3 1;R $2 V1 0.5") assert_equal(rn.each_element.collect(&:to_s).sort.join(";"), "R $2 P3 1;R $2 V1 0.5") - assert_equal(rn.each_node.collect(&:to_s).sort.join(";"), "$2;P3;V1") + assert_equal(rn.each_node.collect(&:to_s).sort.join(";"), "$2;P3;P3.17;V1") rn.simplify assert_equal(rn.to_s, "R P3 V1 1.5") @@ -121,6 +123,135 @@ class PEX_TestClass < TestBase end + def test_4_ExtractorTech + + l1 = 1 + l2 = 2 + l3 = 3 + + tech = RBA::RExtractorTech::new + + tech.skip_simplify = true + assert_equal(tech.skip_simplify, true) + tech.skip_simplify = false + assert_equal(tech.skip_simplify, false) + + via1 = RBA::RExtractorTechVia::new + via1.bottom_conductor = l1 + via1.cut_layer = l2 + via1.top_conductor = l3 + via1.resistance = 2.0 + via1.merge_distance = 0.2 + + assert_equal(via1.bottom_conductor, l1) + assert_equal(via1.cut_layer, l2) + assert_equal(via1.top_conductor, l3) + assert_equal(via1.resistance, 2.0) + assert_equal(via1.merge_distance.to_s, "0.2") + + tech.add_via(via1) + assert_equal(tech.each_via.collect { |v| v.cut_layer }, [ l2 ]) + + tech.clear_vias + assert_equal(tech.each_via.collect { |v| v.cut_layer }, []) + + tech.add_via(via1) + assert_equal(tech.each_via.collect { |v| v.cut_layer }, [ l2 ]) + + cond1 = RBA::RExtractorTechConductor::new + cond1.layer = l1 + cond1.resistance = 0.5 + + assert_equal(cond1.layer, l1) + assert_equal(cond1.resistance, 0.5) + + cond2 = RBA::RExtractorTechConductor::new + cond2.layer = l3 + cond2.resistance = 0.25 + + tech.add_conductor(cond2) + assert_equal(tech.each_conductor.collect { |c| c.layer }, [ l3 ]) + + tech.clear_conductors + assert_equal(tech.each_conductor.collect { |c| c.layer }, []) + + tech.add_conductor(cond1) + tech.add_conductor(cond2) + assert_equal(tech.each_conductor.collect { |c| c.layer }, [ l1, l3 ]) + + end + + # A complete, small example for a R network extraction + + def test_5_NetEx + + ly = RBA::Layout::new + ly.read(File.join($ut_testsrc, "testdata", "pex", "netex_test1.gds")) + + rex = RBA::RNetExtractor::new(ly.dbu) + + tc = ly.top_cell + + l1 = ly.layer(1, 0) + l1p = ly.layer(1, 1) + l1v = ly.layer(1, 2) + l2 = ly.layer(2, 0) + l3 = ly.layer(3, 0) + l3p = ly.layer(3, 1) + l3v = ly.layer(3, 2) + + # That is coincidence, but it needs to be that way for the strings to match + assert_equal(l1, 1) + assert_equal(l2, 0) + assert_equal(l3, 2) + + geo = {} + [ l1, l2, l3 ].each do |l| + geo[l] = RBA::Region::new(tc.begin_shapes_rec(l)) + end + + tech = RBA::RExtractorTech::new + + via1 = RBA::RExtractorTechVia::new + via1.bottom_conductor = l1 + via1.cut_layer = l2 + via1.top_conductor = l3 + via1.resistance = 2.0 + via1.merge_distance = 0.2 + + tech.add_via(via1) + + cond1 = RBA::RExtractorTechConductor::new + cond1.layer = l1 + cond1.resistance = 0.5 + + cond2 = RBA::RExtractorTechConductor::new + cond2.layer = l3 + cond2.resistance = 0.25 + + tech.add_conductor(cond1) + tech.add_conductor(cond2) + + polygon_ports = { } + polygon_ports[l1] = RBA::Region::new(tc.begin_shapes_rec(l1p)).each_merged.to_a + polygon_ports[l3] = RBA::Region::new(tc.begin_shapes_rec(l3p)).each_merged.to_a + + vertex_ports = { } + vertex_ports[l1] = RBA::Region::new(tc.begin_shapes_rec(l1v)).each_merged.collect { |p| p.bbox.center } + vertex_ports[l3] = RBA::Region::new(tc.begin_shapes_rec(l3v)).each_merged.collect { |p| p.bbox.center } + + network = rex.extract(tech, geo, vertex_ports, polygon_ports) + + assert_equal(network.to_s(true) + "\n", <<"END") +R $2.1(9.3,-5.9;9.9,-5.3) P0.1(12.9,-5.9;13.5,-5.3) 2.25 +R $8.2(10,-3.5;10,-2.7) P0.2(12.9,-3.4;13.5,-2.8) 1 +R $2.1(9.3,-5.9;9.9,-5.3) V0.2(0.3,-5.7;0.5,-5.5) 55.75 +R $2.1(9.3,-5.9;9.9,-5.3) $8.2(10,-3.5;10,-2.7) 13.2813 +R $8.2(10,-3.5;10,-2.7) V0.1(5.2,0.4;5.2,0.4) 28.7812 +R V0.1(5.2,0.4;5.2,0.4) V0.2(0.3,-5.7;0.5,-5.5) 17.375 +END + end + end load("test_epilogue.rb") From 1c4077449b9dd69d63c98173e4dd1c87245f097f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 16 May 2025 18:55:55 +0200 Subject: [PATCH 42/58] Fixed build issue --- src/pex/pex/gsiDeclRNetExtractor.cc | 8 ++++---- testdata/ruby/pexTests.rb | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pex/pex/gsiDeclRNetExtractor.cc b/src/pex/pex/gsiDeclRNetExtractor.cc index 3fff2dad5..7ba3c1b54 100644 --- a/src/pex/pex/gsiDeclRNetExtractor.cc +++ b/src/pex/pex/gsiDeclRNetExtractor.cc @@ -264,12 +264,12 @@ static void tech_set_skip_simplify (pex::RExtractorTech *tech, bool f) tech->skip_simplify = f; } -static auto tech_begin_vias (const pex::RExtractorTech *tech) +static std::list::const_iterator tech_begin_vias (const pex::RExtractorTech *tech) { return tech->vias.begin (); } -static auto tech_end_vias (const pex::RExtractorTech *tech) +static std::list::const_iterator tech_end_vias (const pex::RExtractorTech *tech) { return tech->vias.end (); } @@ -284,12 +284,12 @@ static void tech_add_via (pex::RExtractorTech *tech, const pex::RExtractorTechVi tech->vias.push_back (via); } -static auto tech_begin_conductors (const pex::RExtractorTech *tech) +static std::list::const_iterator tech_begin_conductors (const pex::RExtractorTech *tech) { return tech->conductors.begin (); } -static auto tech_end_conductors (const pex::RExtractorTech *tech) +static std::list::const_iterator tech_end_conductors (const pex::RExtractorTech *tech) { return tech->conductors.end (); } diff --git a/testdata/ruby/pexTests.rb b/testdata/ruby/pexTests.rb index 477f07ed3..e5e98a878 100644 --- a/testdata/ruby/pexTests.rb +++ b/testdata/ruby/pexTests.rb @@ -250,6 +250,7 @@ R $2.1(9.3,-5.9;9.9,-5.3) $8.2(10,-3.5;10,-2.7) 13.2813 R $8.2(10,-3.5;10,-2.7) V0.1(5.2,0.4;5.2,0.4) 28.7812 R V0.1(5.2,0.4;5.2,0.4) V0.2(0.3,-5.7;0.5,-5.5) 17.375 END + end end From 113c7013451952113bdbb63eb52e2ff68a37e4cf Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 16 May 2025 23:23:11 +0200 Subject: [PATCH 43/58] Trying explicit member template instantiation to solve linker issue --- src/db/db/dbPLCTriangulation.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/db/db/dbPLCTriangulation.cc b/src/db/db/dbPLCTriangulation.cc index 70f784f3a..c1453f3dc 100644 --- a/src/db/db/dbPLCTriangulation.cc +++ b/src/db/db/dbPLCTriangulation.cc @@ -1379,6 +1379,9 @@ Triangulation::make_contours (const Poly &poly, const Trans &trans, std::vector< } } +template DB_PUBLIC void Triangulation::make_contours (const db::Polygon &, const db::CplxTrans &, std::vector > &); +template DB_PUBLIC void Triangulation::make_contours (const db::DPolygon &, const db::DCplxTrans &, std::vector > &); + void Triangulation::create_constrained_delaunay (const db::Region ®ion, const CplxTrans &trans) { From ca53d8718b81091301c60b4c40c2a12728db5dd8 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 17 May 2025 17:26:59 +0200 Subject: [PATCH 44/58] Fixed some build problems --- src/pex/pex/pexRNetExtractor.cc | 10 +++++----- src/pex/pex/pexRNetwork.h | 5 +++-- src/pex/pex/pexSquareCountingRExtractor.cc | 6 +++--- src/pex/pex/pexTriangulationRExtractor.cc | 12 ++++++------ 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/pex/pex/pexRNetExtractor.cc b/src/pex/pex/pexRNetExtractor.cc index e93d6e745..977524dcc 100644 --- a/src/pex/pex/pexRNetExtractor.cc +++ b/src/pex/pex/pexRNetExtractor.cc @@ -229,19 +229,19 @@ RNetExtractor::create_via_ports (const RExtractorTech &tech, } } -static inline size_t make_id (size_t index, unsigned int type) +static inline size_t make_id (unsigned int index, unsigned int type) { - return (index << 2) + type; + return (size_t (index) << 2) + type; } -static inline size_t index_from_id (size_t id) +static inline unsigned int index_from_id (size_t id) { - return id >> 2; + return (unsigned int) (id >> 2); } static inline unsigned int type_from_id (size_t id) { - return id & 3; + return (unsigned int) (id & 3); } namespace diff --git a/src/pex/pex/pexRNetwork.h b/src/pex/pex/pexRNetwork.h index b9438b0d9..8de7dcb49 100644 --- a/src/pex/pex/pexRNetwork.h +++ b/src/pex/pex/pexRNetwork.h @@ -50,7 +50,7 @@ class RNetwork; * RNode object cannot be created directly. Use "create_node" * from RNetwork. */ -struct PEX_PUBLIC RNode +class PEX_PUBLIC RNode : public tl::list_node { public: @@ -139,9 +139,10 @@ private: * RElement objects cannot be created directly. Use "create_element" * from RNetwork. */ -struct PEX_PUBLIC RElement +class PEX_PUBLIC RElement : public tl::list_node { +public: /** * @brief The conductance value */ diff --git a/src/pex/pex/pexSquareCountingRExtractor.cc b/src/pex/pex/pexSquareCountingRExtractor.cc index cd956a703..08b324d13 100644 --- a/src/pex/pex/pexSquareCountingRExtractor.cc +++ b/src/pex/pex/pexSquareCountingRExtractor.cc @@ -226,7 +226,7 @@ SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector for (size_t j = 0; j < p->size (); ++j) { - const db::plc::Edge *e = p->edge (j); + const db::plc::Edge *e = p->edge (int (j)); if (e->left () && e->right ()) { auto ip = internal_ports.find (e); @@ -262,7 +262,7 @@ SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector // 1. internal ports for (auto i = ip_indexes.begin (); i != ip_indexes.end (); ++i) { db::Box loc = (inv_trans * internal_port_edges [*i]->edge ()).bbox (); - ports.push_back (std::make_pair (PortDefinition (pex::RNode::Internal, loc, *i), (pex::RNode *) 0)); + ports.push_back (std::make_pair (PortDefinition (pex::RNode::Internal, loc, (unsigned int) *i), (pex::RNode *) 0)); } // 2. vertex ports @@ -278,7 +278,7 @@ SquareCountingRExtractor::extract (const db::Polygon &polygon, const std::vector // (NOTE: here we only take the center of the bounding box) for (auto i = pp_indexes.begin (); i != pp_indexes.end (); ++i) { db::Box loc = polygon_ports [*i].box (); - ports.push_back (std::make_pair (PortDefinition (pex::RNode::PolygonPort, loc, *i), (pex::RNode *) 0)); + ports.push_back (std::make_pair (PortDefinition (pex::RNode::PolygonPort, loc, (unsigned int) *i), (pex::RNode *) 0)); } // create nodes for the ports diff --git a/src/pex/pex/pexTriangulationRExtractor.cc b/src/pex/pex/pexTriangulationRExtractor.cc index 6a867ef43..500a5c179 100644 --- a/src/pex/pex/pexTriangulationRExtractor.cc +++ b/src/pex/pex/pexTriangulationRExtractor.cc @@ -133,7 +133,7 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< for (size_t iv = 0; iv < p->size (); ++iv) { - const db::plc::Vertex *vertex = p->vertex (iv); + const db::plc::Vertex *vertex = p->vertex (int (iv)); if (vertex2node.find (vertex) != vertex2node.end ()) { continue; } @@ -148,7 +148,7 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< if (pn != pport_nodes.end ()) { n = pn->second; } else { - n = rnetwork.create_node (pex::RNode::PolygonPort, port_index, 0); + n = rnetwork.create_node (pex::RNode::PolygonPort, (unsigned int) port_index, 0); pport_nodes.insert (std::make_pair (port_index, n)); n->location = trans * polygon_ports [port_index].box (); } @@ -158,7 +158,7 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< for (auto pi = vertex->ids ().begin (); pi != vertex->ids ().end (); ++pi) { size_t port_index = size_t (*pi); if (port_index < vertex_ports.size ()) { - RNode *nn = rnetwork.create_node (pex::RNode::VertexPort, port_index, 0); + RNode *nn = rnetwork.create_node (pex::RNode::VertexPort, (unsigned int) port_index, 0); nn->location = db::DBox (*vertex, *vertex); if (n) { // in case of multiple vertexes on the same spot, short them @@ -172,7 +172,7 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< } else { - n = rnetwork.create_node (pex::RNode::Internal, internal_node_id++, 0); + n = rnetwork.create_node (pex::RNode::Internal, (unsigned int) internal_node_id++, 0); n->location = db::DBox (*vertex, *vertex); } @@ -204,7 +204,7 @@ TriangulationRExtractor::extract (const db::Polygon &polygon, const std::vector< if (ip != pport_nodes.end ()) { // create a new vertex port and short it to the polygon port - auto n = rnetwork.create_node (pex::RNode::VertexPort, iv, 0); + auto n = rnetwork.create_node (pex::RNode::VertexPort, (unsigned int) iv, 0); n->location = db::DBox (trans * vp, trans * vp); rnetwork.create_element (pex::RElement::short_value (), n, ip->second); @@ -283,7 +283,7 @@ TriangulationRExtractor::eliminate_all (RNetwork &rnetwork) size_t nn = n->elements ().size (); if (nn <= nmax) { to_eliminate.push_back (const_cast (n.operator-> ())); - } else if (nmax_next == 0 or nn < nmax_next) { + } else if (nmax_next == 0 || nn < nmax_next) { nmax_next = nn; } } From 5dd189d413223fa38f5d416c50eea3fe5cf9be44 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 17 May 2025 18:39:11 +0200 Subject: [PATCH 45/58] Including pex dependencies in build --- src/klayout.pro | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/klayout.pro b/src/klayout.pro index 3e211798d..47891e836 100644 --- a/src/klayout.pro +++ b/src/klayout.pro @@ -69,7 +69,7 @@ lib.depends += db lym.depends += gsi $$LANG_DEPENDS -laybasic.depends += rdb +laybasic.depends += rdb pex layview.depends += laybasic ant.depends += layview @@ -117,7 +117,7 @@ equals(HAVE_RUBY, "1") { } else { - plugins.depends += layview ant img edt + plugins.depends += layview ant img edt } From 91cb8826c701675773d95f169a92ec1d5193f751 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 17 May 2025 18:41:22 +0200 Subject: [PATCH 46/58] Including more dependencies in build --- src/klayout.pro | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/klayout.pro b/src/klayout.pro index 47891e836..a7dde2251 100644 --- a/src/klayout.pro +++ b/src/klayout.pro @@ -121,8 +121,8 @@ equals(HAVE_RUBY, "1") { } -buddies.depends += plugins lym $$LANG_DEPENDS -unit_tests.depends += plugins lym $$MAIN_DEPENDS $$LANG_DEPENDS +buddies.depends += plugins pex lym $$LANG_DEPENDS +unit_tests.depends += plugins pex lym $$MAIN_DEPENDS $$LANG_DEPENDS !equals(HAVE_QT, "0") { From 1fb0f318dc84d2b7a0055c6bf45fc61250e28988 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 17 May 2025 18:49:32 +0200 Subject: [PATCH 47/58] Added pex lib to bd tools. --- src/buddies/src/buddy_app.pri | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buddies/src/buddy_app.pri b/src/buddies/src/buddy_app.pri index da47c2fa2..58da418bf 100644 --- a/src/buddies/src/buddy_app.pri +++ b/src/buddies/src/buddy_app.pri @@ -19,7 +19,7 @@ SOURCES = $$PWD/bd/main.cc INCLUDEPATH += $$BD_INC $$TL_INC $$GSI_INC DEPENDPATH += $$BD_INC $$TL_INC $$GSI_INC -LIBS += -L$$DESTDIR -lklayout_bd -lklayout_db -lklayout_tl -lklayout_gsi -lklayout_lib -lklayout_rdb -lklayout_lym +LIBS += -L$$DESTDIR -lklayout_bd -lklayout_db -lklayout_pex -lklayout_tl -lklayout_gsi -lklayout_lib -lklayout_rdb -lklayout_lym INCLUDEPATH += $$RBA_INC DEPENDPATH += $$RBA_INC From 4306b24b4a6bca7a9f41978269db3a801c3d7098 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 18 May 2025 09:27:10 +0200 Subject: [PATCH 48/58] Normalizing test case for RNetExtractor to reduce jitter --- src/pex/unit_tests/pexRNetExtractorTests.cc | 91 +++++++++++++-------- 1 file changed, 59 insertions(+), 32 deletions(-) diff --git a/src/pex/unit_tests/pexRNetExtractorTests.cc b/src/pex/unit_tests/pexRNetExtractorTests.cc index 9b4d0bd49..2b6f9f083 100644 --- a/src/pex/unit_tests/pexRNetExtractorTests.cc +++ b/src/pex/unit_tests/pexRNetExtractorTests.cc @@ -37,6 +37,33 @@ public: using pex::RNetExtractor::create_via_ports; }; +static std::string network2s (const pex::RNetwork &network) +{ + std::vector r; + + for (auto e = network.begin_elements (); e != network.end_elements (); ++e) { + + const pex::RElement &element = *e; + + std::string na = (element.a ()->type != pex::RNode::Internal ? element.a ()->to_string () : "") + + element.a ()->location.to_string (); + std::string nb = (element.b ()->type != pex::RNode::Internal ? element.b ()->to_string () : "") + + element.b ()->location.to_string (); + + if (nb < na) { + std::swap (na, nb); + } + + std::string s = "R " + na + " " + nb + " " + tl::to_string (element.resistance ()); + r.push_back (s); + + } + + std::sort (r.begin (), r.end ()); + + return tl::join (r, "\n"); +} + TEST(netex_viagen1) { db::Layout ly; @@ -78,11 +105,11 @@ TEST(netex_viagen1) EXPECT_EQ (via_ports [l2].size (), size_t (0)); EXPECT_EQ (via_ports [l3].size (), size_t (4)); - EXPECT_EQ (network.to_string (true), - "R $0.1(1.7,0.1;1.9,0.3) $1.2(1.7,0.1;1.9,0.3) 50\n" - "R $2.1(0.4,0.5;0.6,0.7) $3.2(0.4,0.5;0.6,0.7) 50\n" - "R $4.1(0.8,0.5;1,0.7) $5.2(0.8,0.5;1,0.7) 50\n" - "R $6.1(2.9,0.5;3.1,0.7) $7.2(2.9,0.5;3.1,0.7) 50" + EXPECT_EQ (network2s (network), + "R (0.4,0.5;0.6,0.7) (0.4,0.5;0.6,0.7) 50\n" + "R (0.8,0.5;1,0.7) (0.8,0.5;1,0.7) 50\n" + "R (1.7,0.1;1.9,0.3) (1.7,0.1;1.9,0.3) 50\n" + "R (2.9,0.5;3.1,0.7) (2.9,0.5;3.1,0.7) 50" ); } @@ -128,13 +155,13 @@ TEST(netex_viagen2) EXPECT_EQ (via_ports [l2].size (), size_t (0)); EXPECT_EQ (via_ports [l3].size (), size_t (6)); - EXPECT_EQ (network.to_string (true), - "R $0.1(4.6,2.8;4.8,3) $1.2(4.6,2.8;4.8,3) 50\n" - "R $2.1(2.5,3.7;2.7,3.9) $3.2(2.5,3.7;2.7,3.9) 50\n" - "R $4.1(3,3.7;3.2,3.9) $5.2(3,3.7;3.2,3.9) 50\n" - "R $6.1(2.2,1.2;3.4,3.4) $7.2(2.2,1.2;3.4,3.4) 2.77778\n" - "R $8.1(0.4,0.4;2.2,4.2) $9.2(0.4,0.4;2.2,4.2) 1\n" - "R $10.1(0.6,4.9;1.2,5.1) $11.2(0.6,4.9;1.2,5.1) 25" + EXPECT_EQ (network2s (network), + "R (0.4,0.4;2.2,4.2) (0.4,0.4;2.2,4.2) 1\n" + "R (0.6,4.9;1.2,5.1) (0.6,4.9;1.2,5.1) 25\n" + "R (2.2,1.2;3.4,3.4) (2.2,1.2;3.4,3.4) 2.77777777778\n" + "R (2.5,3.7;2.7,3.9) (2.5,3.7;2.7,3.9) 50\n" + "R (3,3.7;3.2,3.9) (3,3.7;3.2,3.9) 50\n" + "R (4.6,2.8;4.8,3) (4.6,2.8;4.8,3) 50" ); } @@ -220,32 +247,32 @@ TEST(netex_2layer) rex.extract (tech, geo, vertex_ports, polygon_ports, network); - EXPECT_EQ (network.to_string (true), - "R $0.1(0.3,-5.7;0.5,-5.5) $1.2(0.3,-5.7;0.5,-5.5) 50\n" - "R $2.1(9.3,-5.9;9.9,-5.3) $3.2(9.3,-5.9;9.9,-5.3) 12.5\n" - "R $4.1(9.3,0.1;9.9,0.3) $5.2(9.3,0.1;9.9,0.3) 25\n" - "R $6.1(0.1,0.1;0.7,0.7) $7.2(0.1,0.1;0.7,0.7) 12.5\n" - "R $0.1(0.3,-5.7;0.5,-5.5) $2.1(9.3,-5.9;9.9,-5.3) 5.75\n" - "R $2.1(9.3,-5.9;9.9,-5.3) P0.1(12.9,-5.9;13.5,-5.3) 2.25\n" - "R $6.1(0.1,0.1;0.7,0.7) V0.1(5.2,0.4;5.2,0.4) 3\n" - "R $4.1(9.3,0.1;9.9,0.3) V0.1(5.2,0.4;5.2,0.4) 2.75\n" - "R $5.2(9.3,0.1;9.9,0.3) $8.2(10,-3.5;10,-2.7) 1.03125\n" - "R $3.2(9.3,-5.9;9.9,-5.3) $8.2(10,-3.5;10,-2.7) 0.78125\n" - "R $8.2(10,-3.5;10,-2.7) P0.2(12.9,-3.4;13.5,-2.8) 1\n" - "R $7.2(0.1,0.1;0.7,0.7) V0.2(0.4,-5.6;0.4,-5.6) 1.875\n" - "R $1.2(0.3,-5.7;0.5,-5.5) V0.2(0.4,-5.6;0.4,-5.6) 0" + EXPECT_EQ (network2s (network), + "R (0.1,0.1;0.7,0.7) (0.1,0.1;0.7,0.7) 12.5\n" + "R (0.1,0.1;0.7,0.7) V0.1(5.2,0.4;5.2,0.4) 3\n" + "R (0.1,0.1;0.7,0.7) V0.2(0.4,-5.6;0.4,-5.6) 1.875\n" + "R (0.3,-5.7;0.5,-5.5) (0.3,-5.7;0.5,-5.5) 50\n" + "R (0.3,-5.7;0.5,-5.5) (9.3,-5.9;9.9,-5.3) 5.75\n" + "R (0.3,-5.7;0.5,-5.5) V0.2(0.4,-5.6;0.4,-5.6) 0\n" + "R (10,-3.5;10,-2.7) (9.3,-5.9;9.9,-5.3) 0.78125\n" + "R (10,-3.5;10,-2.7) (9.3,0.1;9.9,0.3) 1.03125\n" + "R (10,-3.5;10,-2.7) P0.2(12.9,-3.4;13.5,-2.8) 1\n" + "R (9.3,-5.9;9.9,-5.3) (9.3,-5.9;9.9,-5.3) 12.5\n" + "R (9.3,-5.9;9.9,-5.3) P0.1(12.9,-5.9;13.5,-5.3) 2.25\n" + "R (9.3,0.1;9.9,0.3) (9.3,0.1;9.9,0.3) 25\n" + "R (9.3,0.1;9.9,0.3) V0.1(5.2,0.4;5.2,0.4) 2.75" ); tech.skip_simplify = false; rex.extract (tech, geo, vertex_ports, polygon_ports, network); - EXPECT_EQ (network.to_string (true), - "R $2.1(9.3,-5.9;9.9,-5.3) P0.1(12.9,-5.9;13.5,-5.3) 2.25\n" - "R $8.2(10,-3.5;10,-2.7) P0.2(12.9,-3.4;13.5,-2.8) 1\n" - "R $2.1(9.3,-5.9;9.9,-5.3) V0.2(0.3,-5.7;0.5,-5.5) 55.75\n" - "R $2.1(9.3,-5.9;9.9,-5.3) $8.2(10,-3.5;10,-2.7) 13.2813\n" - "R $8.2(10,-3.5;10,-2.7) V0.1(5.2,0.4;5.2,0.4) 28.7812\n" + EXPECT_EQ (network2s (network), + "R (10,-3.5;10,-2.7) (9.3,-5.9;9.9,-5.3) 13.28125\n" + "R (10,-3.5;10,-2.7) P0.2(12.9,-3.4;13.5,-2.8) 1\n" + "R (10,-3.5;10,-2.7) V0.1(5.2,0.4;5.2,0.4) 28.78125\n" + "R (9.3,-5.9;9.9,-5.3) P0.1(12.9,-5.9;13.5,-5.3) 2.25\n" + "R (9.3,-5.9;9.9,-5.3) V0.2(0.3,-5.7;0.5,-5.5) 55.75\n" "R V0.1(5.2,0.4;5.2,0.4) V0.2(0.3,-5.7;0.5,-5.5) 17.375" ); } From 72c716f38d6b614de4a35f7af2c1f43326ed1264 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 18 May 2025 17:04:21 +0200 Subject: [PATCH 49/58] More robust tests. --- testdata/ruby/pexTests.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/testdata/ruby/pexTests.rb b/testdata/ruby/pexTests.rb index e5e98a878..2e11f42de 100644 --- a/testdata/ruby/pexTests.rb +++ b/testdata/ruby/pexTests.rb @@ -242,12 +242,15 @@ class PEX_TestClass < TestBase network = rex.extract(tech, geo, vertex_ports, polygon_ports) - assert_equal(network.to_s(true) + "\n", <<"END") -R $2.1(9.3,-5.9;9.9,-5.3) P0.1(12.9,-5.9;13.5,-5.3) 2.25 -R $8.2(10,-3.5;10,-2.7) P0.2(12.9,-3.4;13.5,-2.8) 1 -R $2.1(9.3,-5.9;9.9,-5.3) V0.2(0.3,-5.7;0.5,-5.5) 55.75 -R $2.1(9.3,-5.9;9.9,-5.3) $8.2(10,-3.5;10,-2.7) 13.2813 -R $8.2(10,-3.5;10,-2.7) V0.1(5.2,0.4;5.2,0.4) 28.7812 + n = network.to_s(true) + n = n.gsub(/ \$\d+\./, " $x.") + n = n.split("\n").sort.join("\n") + "\n" + assert_equal(n, <<"END") +R $x.1(9.3,-5.9;9.9,-5.3) $x.2(10,-3.5;10,-2.7) 13.2813 +R $x.1(9.3,-5.9;9.9,-5.3) P0.1(12.9,-5.9;13.5,-5.3) 2.25 +R $x.1(9.3,-5.9;9.9,-5.3) V0.2(0.3,-5.7;0.5,-5.5) 55.75 +R $x.2(10,-3.5;10,-2.7) P0.2(12.9,-3.4;13.5,-2.8) 1 +R $x.2(10,-3.5;10,-2.7) V0.1(5.2,0.4;5.2,0.4) 28.7812 R V0.1(5.2,0.4;5.2,0.4) V0.2(0.3,-5.7;0.5,-5.5) 17.375 END From 9b03a1ba642ff57a207cf72681d983a12638dc2e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 18 May 2025 22:41:46 +0200 Subject: [PATCH 50/58] More robust tests --- .../pexSquareCountingRExtractorTests.cc | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc b/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc index e3b6e057e..5b566152d 100644 --- a/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc +++ b/src/pex/unit_tests/pexSquareCountingRExtractorTests.cc @@ -41,6 +41,33 @@ public: } +static std::string network2s (const pex::RNetwork &network) +{ + std::vector r; + + for (auto e = network.begin_elements (); e != network.end_elements (); ++e) { + + const pex::RElement &element = *e; + + std::string na = (element.a ()->type != pex::RNode::Internal ? element.a ()->to_string () : "") + + element.a ()->location.to_string (); + std::string nb = (element.b ()->type != pex::RNode::Internal ? element.b ()->to_string () : "") + + element.b ()->location.to_string (); + + if (nb < na) { + std::swap (na, nb); + } + + std::string s = "R " + na + " " + nb + " " + tl::to_string (element.resistance ()); + r.push_back (s); + + } + + std::sort (r.begin (), r.end ()); + + return tl::join (r, "\n"); +} + TEST(basic) { db::Point contour[] = { @@ -134,10 +161,10 @@ TEST(extraction) rex.extract (poly, vertex_ports, polygon_ports, rn); - EXPECT_EQ (rn.to_string (), - "R $0 V0 10.5\n" - "R $0 V1 6\n" - "R $0 P0 8.5" + EXPECT_EQ (network2s (rn), + "R (1,0.1;1.1,0.1) P0(1,0.9;1.1,1) 8.5\n" + "R (1,0.1;1.1,0.1) V0(0,0.05;0,0.05) 10.5\n" + "R (1,0.1;1.1,0.1) V1(1.65,0.05;1.65,0.05) 6" ) } @@ -182,7 +209,7 @@ TEST(extraction_meander) rex.extract (poly, vertex_ports, polygon_ports, rn); - EXPECT_EQ (rn.to_string (), - "R V0 V1 10.0544" // that is pretty much the length of the center line / width :) + EXPECT_EQ (network2s (rn), + "R V0(0.3,0;0.3,0) V1(4.3,1;4.3,1) 10.0543767445" // that is pretty much the length of the center line / width :) ) } From 6b8c79c488dff179208bd093a20165560ff4847b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 22 May 2025 18:50:25 +0200 Subject: [PATCH 51/58] Fixed a segfault (thanks, Martin\!) --- src/db/db/dbPLC.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/db/db/dbPLC.cc b/src/db/db/dbPLC.cc index 8f7c650a5..07b7b8fa5 100644 --- a/src/db/db/dbPLC.cc +++ b/src/db/db/dbPLC.cc @@ -459,10 +459,11 @@ Polygon::init () auto i = v2e.find (v); tl_assert (i != v2e.end () && i->first == v && i->second != mp_e.back ()); - v2e.erase (i); mp_e.push_back (i->second); - v = i->second->other (v); + + v2e.erase (i); + i = v2e.find (v); while (i != v2e.end () && i->first == v) { if (i->second == mp_e.back ()) { From 4dd4524da9dc3decdd810fca45ea2162f3aac579 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 22 May 2025 19:47:39 +0200 Subject: [PATCH 52/58] Fixed typo --- src/db/db/gsiDeclDbLayoutToNetlist.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/db/gsiDeclDbLayoutToNetlist.cc b/src/db/db/gsiDeclDbLayoutToNetlist.cc index c3a565c50..065677906 100644 --- a/src/db/db/gsiDeclDbLayoutToNetlist.cc +++ b/src/db/db/gsiDeclDbLayoutToNetlist.cc @@ -1033,7 +1033,7 @@ Class decl_dbLayoutToNetlist ("db", "LayoutToNetlist", "'lmap' can also be left nil, in which case, a layer mapping will be provided based on the layer info attributes of " "the layers (see \\layer_info).\n" "\n" - "'cmap' specifies the cell mapping. Use \\create_cell_mapping or \\const_create_cell_mapping to " + "'cmap' specifies the cell mapping. Use \\cell_mapping_into or \\const_cell_mapping_into to " "define the target cells in the target layout and to derive a cell mapping.\n" "\n" "The method has three net annotation modes:\n" From dec7ad9da111f230d33250e91f173906491fed67 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 May 2025 16:28:46 +0200 Subject: [PATCH 53/58] [consider merging] properly conveying string encoding for Ruby, so that UTF-8 encoding is maintained when returning such strings from C++ --- src/gsi/gsi_test/gsiTest.cc | 16 +++-- src/gsi/gsi_test/gsiTest.h | 11 ++++ src/pya/pya/pyaConvert.cc | 2 +- src/rba/rba/rbaConvert.cc | 16 ++++- src/rba/rba/rbaConvert.h | 6 +- src/rba/rba/rbaMarshal.cc | 2 +- src/tl/tl/tlInternational.cc | 7 ++- testdata/python/basic.py | 85 ++++++++++++++++--------- testdata/ruby/basic_testcore.rb | 108 +++++++++++++++++++------------- 9 files changed, 166 insertions(+), 87 deletions(-) diff --git a/src/gsi/gsi_test/gsiTest.cc b/src/gsi/gsi_test/gsiTest.cc index 29363acca..27ac72de1 100644 --- a/src/gsi/gsi_test/gsiTest.cc +++ b/src/gsi/gsi_test/gsiTest.cc @@ -218,7 +218,7 @@ A::ia_cref_to_qs (const std::vector &ia) { QString s; for (std::vector::const_iterator i = ia.begin (); i != ia.end (); ++i) { - s.push_back (char (*i)); + s.push_back (QChar (*i)); } return s; } @@ -229,7 +229,7 @@ A::ia_cref_to_qs_ref (const std::vector &ia) static QString s; s.clear (); for (std::vector::const_iterator i = ia.begin (); i != ia.end (); ++i) { - s.push_back (char (*i)); + s.push_back (QChar (*i)); } return s; } @@ -243,7 +243,7 @@ A::ql1s_cref_to_ia (const QLatin1String &ql1s) const char *cp = ql1s.data (); size_t n = ql1s.size (); for (size_t i = 0; i < n; ++i) { - ia.push_back (*cp++); + ia.push_back ((unsigned char) *cp++); } return ia; } @@ -1270,7 +1270,15 @@ static gsi::Class decl_a ("", "A", gsi::method ("to_s", &A::to_s) + gsi::iterator ("a6", &A::a6b, &A::a6e) + gsi::iterator ("a7", &A::a7b, &A::a7e) + - gsi::iterator ("a8", &A::a8b, &A::a8e) + gsi::iterator ("a8", &A::a8b, &A::a8e) + +#if defined(HAVE_QT) + gsi::method ("ft_qba", &A::ft_qba) + + gsi::method ("ft_qs", &A::ft_qs) + +#endif + gsi::method ("ft_str", &A::ft_str) + + gsi::method ("ft_cv", &A::ft_cv) + + gsi::method ("ft_cptr", &A::ft_cptr) + + gsi::method ("ft_var", &A::ft_var) ); static gsi::Class decl_a_nc (decl_a, "", "A_NC"); diff --git a/src/gsi/gsi_test/gsiTest.h b/src/gsi/gsi_test/gsiTest.h index c2b4bf58b..100d8dd2d 100644 --- a/src/gsi/gsi_test/gsiTest.h +++ b/src/gsi/gsi_test/gsiTest.h @@ -419,6 +419,17 @@ struct A static int sp_i_get (); static void sp_i_set (int v); + // feed-through values for full cycle tests + // (mainly for string encoding and binary strings) + static std::string ft_str (const std::string &v) { return v; } + static std::vector ft_cv (const std::vector &v) { return v; } + static const char *ft_cptr (const char *v) { return v; } + static tl::Variant ft_var (const tl::Variant &v) { return v; } +#if defined(HAVE_QT) + static QString ft_qs (const QString &v) { return v; } + static QByteArray ft_qba (const QByteArray &v) { return v; } +#endif + // members std::vector m_d; int n; diff --git a/src/pya/pya/pyaConvert.cc b/src/pya/pya/pyaConvert.cc index c7b7c6f64..44afc5917 100644 --- a/src/pya/pya/pyaConvert.cc +++ b/src/pya/pya/pyaConvert.cc @@ -501,7 +501,7 @@ PyObject *c2python_func::operator() (const tl::Variant &c) } else if (c.is_bool ()) { return c2python (c.to_bool ()); } else if (c.is_a_string ()) { - return c2python (c.to_string ()); + return c2python (c.to_stdstring ()); } else if (c.is_a_bytearray ()) { return c2python (c.to_bytearray ()); } else if (c.is_long ()) { diff --git a/src/rba/rba/rbaConvert.cc b/src/rba/rba/rbaConvert.cc index 61817a5a7..cd8aa5d0d 100644 --- a/src/rba/rba/rbaConvert.cc +++ b/src/rba/rba/rbaConvert.cc @@ -29,6 +29,8 @@ #include "gsiDecl.h" +#include + namespace rba { @@ -120,9 +122,17 @@ tl::Variant ruby2c (VALUE rval) } } else if (TYPE (rval) == T_STRING) { - return tl::Variant (ruby2c (rval)); + + // UTF-8 encoded strings are taken to be string, others are byte strings + // At least this ensures consistency for a full Ruby-C++ turnaround cycle. + if (rb_enc_from_index (rb_enc_get_index (rval)) == rb_utf8_encoding ()) { + return tl::Variant (ruby2c (rval)); + } else { + return tl::Variant (ruby2c > (rval)); + } + } else { - return tl::Variant (ruby2c (rba_safe_obj_as_string (rval))); + return tl::Variant (ruby2c (rba_safe_obj_as_string (rval))); } } @@ -261,7 +271,7 @@ VALUE c2ruby (const tl::Variant &c) } else if (c.is_bool ()) { return c2ruby (c.to_bool ()); } else if (c.is_a_string ()) { - return c2ruby (c.to_string ()); + return c2ruby (c.to_stdstring ()); } else if (c.is_a_bytearray ()) { return c2ruby > (c.to_bytearray ()); } else if (c.is_long () || c.is_char ()) { diff --git a/src/rba/rba/rbaConvert.h b/src/rba/rba/rbaConvert.h index 01533f843..6865dc7d2 100644 --- a/src/rba/rba/rbaConvert.h +++ b/src/rba/rba/rbaConvert.h @@ -461,7 +461,7 @@ inline VALUE c2ruby (const float &c) template <> inline VALUE c2ruby (const std::string &c) { - return rb_str_new (c.c_str (), long (c.size ())); + return rb_utf8_str_new (c.c_str (), long (c.size ())); } template <> @@ -488,7 +488,7 @@ inline VALUE c2ruby (const QString &qs) return Qnil; } else { std::string c (tl::to_string (qs)); - return rb_str_new (c.c_str (), long (c.size ())); + return rb_utf8_str_new (c.c_str (), long (c.size ())); } } #endif @@ -509,7 +509,7 @@ inline VALUE c2ruby (const char * const & s) static const char null_string[] = "(null)"; return rb_str_new (null_string, sizeof (null_string) - 1); } else { - return rb_str_new (s, long (strlen (s))); + return rb_utf8_str_new (s, long (strlen (s))); } } diff --git a/src/rba/rba/rbaMarshal.cc b/src/rba/rba/rbaMarshal.cc index b55f82111..29cf797c5 100644 --- a/src/rba/rba/rbaMarshal.cc +++ b/src/rba/rba/rbaMarshal.cc @@ -679,7 +679,7 @@ struct reader if (!a.get ()) { *ret = Qnil; } else { - *ret = rb_str_new (a->c_str (), long (a->size ())); + *ret = rb_utf8_str_new (a->c_str (), long (a->size ())); } } }; diff --git a/src/tl/tl/tlInternational.cc b/src/tl/tl/tlInternational.cc index 343a5ffa4..f9d4e60b8 100644 --- a/src/tl/tl/tlInternational.cc +++ b/src/tl/tl/tlInternational.cc @@ -45,12 +45,13 @@ QTextCodec *ms_system_codec = 0; QString to_qstring (const std::string &s) { - return QString::fromUtf8 (s.c_str ()); + return QString::fromUtf8 (s.c_str (), s.size ()); } std::string to_string (const QString &s) { - return std::string (s.toUtf8 ().constData ()); + auto utf8 = s.toUtf8 (); + return std::string (utf8.constData (), utf8.size ()); } #if !defined(_WIN32) @@ -70,7 +71,7 @@ std::string string_to_system (const std::string &s) initialize_codecs (); } - QString qs = QString::fromUtf8 (s.c_str ()); + QString qs = QString::fromUtf8 (s.c_str (), s.size ()); return std::string (ms_system_codec->fromUnicode (qs).constData ()); } #endif diff --git a/testdata/python/basic.py b/testdata/python/basic.py index a0b39a025..9f604f9d0 100644 --- a/testdata/python/basic.py +++ b/testdata/python/basic.py @@ -2994,8 +2994,12 @@ class BasicTest(unittest.TestCase): qba = pya.A.ia_cref_to_qba([ 16, 42, 0, 8 ]) if sys.version_info < (3, 0): self.assertEqual(repr(qba), "bytearray(b'\\x10*\\x00\\x08')") + self.assertEqual(repr(pya.A.ft_qba(qba)), "bytearray(b'\\x10*\\x00\\x08')") + self.assertEqual(repr(pya.A.ft_var(qba)), "bytearray(b'\\x10*\\x00\\x08')") else: self.assertEqual(repr(qba), "b'\\x10*\\x00\\x08'") + self.assertEqual(repr(pya.A.ft_qba(qba)), "b'\\x10*\\x00\\x08'") + self.assertEqual(repr(pya.A.ft_var(qba)), "b'\\x10*\\x00\\x08'") self.assertEqual(pya.A.qba_to_ia(qba), [ 16, 42, 0, 8 ]) self.assertEqual(pya.A.qba_cref_to_ia(qba), [ 16, 42, 0, 8 ]) @@ -3079,26 +3083,29 @@ class BasicTest(unittest.TestCase): if "ia_cref_to_qs" in pya.A.__dict__: - qs = pya.A.ia_cref_to_qs([ 16, 42, 0, 8 ]) - self.assertEqual(repr(qs), "'\\x10*\\x00\\x08'") + qs = pya.A.ia_cref_to_qs([ 16, 42, 0, 8, 0x03a9 ]) + self.assertEqual(repr(qs), "'\\x10*\\x00\\x08\u03a9'") + # full cycle must preserve encoding, also for var + self.assertEqual(repr(pya.A.ft_qs(qs)), "'\\x10*\\x00\\x08\u03a9'") + self.assertEqual(repr(pya.A.ft_var(qs)), "'\\x10*\\x00\\x08\u03a9'") - self.assertEqual(pya.A.qs_to_ia(qs), [ 16, 42, 0, 8 ]) - self.assertEqual(pya.A.qs_cref_to_ia(qs), [ 16, 42, 0, 8 ]) - self.assertEqual(pya.A.qs_cptr_to_ia(qs), [ 16, 42, 0, 8 ]) - self.assertEqual(pya.A.qs_ref_to_ia(qs), [ 16, 42, 0, 8 ]) - self.assertEqual(pya.A.qs_ptr_to_ia(qs), [ 16, 42, 0, 8 ]) + self.assertEqual(pya.A.qs_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ]) + self.assertEqual(pya.A.qs_cref_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ]) + self.assertEqual(pya.A.qs_cptr_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ]) + self.assertEqual(pya.A.qs_ref_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ]) + self.assertEqual(pya.A.qs_ptr_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ]) - qs = pya.A.ia_cref_to_qs_cref([ 17, 42, 0, 8 ]) - self.assertEqual(repr(qs), "'\\x11*\\x00\\x08'") + qs = pya.A.ia_cref_to_qs_cref([ 17, 42, 0, 8, 0x03a9 ]) + self.assertEqual(repr(qs), "'\\x11*\\x00\\x08\u03a9'") - qs = pya.A.ia_cref_to_qs_ref([ 18, 42, 0, 8 ]) - self.assertEqual(repr(qs), "'\\x12*\\x00\\x08'") + qs = pya.A.ia_cref_to_qs_ref([ 18, 42, 0, 8, 0x03a9 ]) + self.assertEqual(repr(qs), "'\\x12*\\x00\\x08\u03a9'") - qs = pya.A.ia_cref_to_qs_cptr([ 19, 42, 0, 8 ]) - self.assertEqual(repr(qs), "'\\x13*\\x00\\x08'") + qs = pya.A.ia_cref_to_qs_cptr([ 19, 42, 0, 8, 0x03a9 ]) + self.assertEqual(repr(qs), "'\\x13*\\x00\\x08\u03a9'") - qs = pya.A.ia_cref_to_qs_ptr([ 20, 42, 0, 8 ]) - self.assertEqual(repr(qs), "'\\x14*\\x00\\x08'") + qs = pya.A.ia_cref_to_qs_ptr([ 20, 42, 0, 8, 0x03a9 ]) + self.assertEqual(repr(qs), "'\\x14*\\x00\\x08\u03a9'") self.assertEqual(pya.A.qs_to_ia('\x00\x01\x02'), [ 0, 1, 2 ]) @@ -3137,29 +3144,42 @@ class BasicTest(unittest.TestCase): if "ia_cref_to_ql1s" in pya.A.__dict__: - ql1s = pya.A.ia_cref_to_ql1s([ 16, 42, 0, 8 ]) - self.assertEqual(repr(ql1s), "'\\x10*\\x00\\x08'") + ql1s = pya.A.ia_cref_to_ql1s([ 16, 42, 0, 8, 0x03a9 ]) + self.assertEqual(repr(ql1s), "'\\x10*\\x00\\x08\u00a9'") - self.assertEqual(pya.A.ql1s_to_ia(ql1s), [ 16, 42, 0, 8 ]) - self.assertEqual(pya.A.ql1s_cref_to_ia(ql1s), [ 16, 42, 0, 8 ]) - self.assertEqual(pya.A.ql1s_cptr_to_ia(ql1s), [ 16, 42, 0, 8 ]) - self.assertEqual(pya.A.ql1s_ref_to_ia(ql1s), [ 16, 42, 0, 8 ]) - self.assertEqual(pya.A.ql1s_ptr_to_ia(ql1s), [ 16, 42, 0, 8 ]) + self.assertEqual(pya.A.ql1s_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ]) + self.assertEqual(pya.A.ql1s_cref_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ]) + self.assertEqual(pya.A.ql1s_cptr_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ]) + self.assertEqual(pya.A.ql1s_ref_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ]) + self.assertEqual(pya.A.ql1s_ptr_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ]) - ql1s = pya.A.ia_cref_to_ql1s_cref([ 17, 42, 0, 8 ]) - self.assertEqual(repr(ql1s), "'\\x11*\\x00\\x08'") + ql1s = pya.A.ia_cref_to_ql1s_cref([ 17, 42, 0, 8, 0x03a9 ]) + self.assertEqual(repr(ql1s), "'\\x11*\\x00\\x08\u00a9'") - ql1s = pya.A.ia_cref_to_ql1s_ref([ 18, 42, 0, 8 ]) - self.assertEqual(repr(ql1s), "'\\x12*\\x00\\x08'") + ql1s = pya.A.ia_cref_to_ql1s_ref([ 18, 42, 0, 8, 0x03a9 ]) + self.assertEqual(repr(ql1s), "'\\x12*\\x00\\x08\u00a9'") - ql1s = pya.A.ia_cref_to_ql1s_cptr([ 19, 42, 0, 8 ]) - self.assertEqual(repr(ql1s), "'\\x13*\\x00\\x08'") + ql1s = pya.A.ia_cref_to_ql1s_cptr([ 19, 42, 0, 8, 0x03a9 ]) + self.assertEqual(repr(ql1s), "'\\x13*\\x00\\x08\u00a9'") - ql1s = pya.A.ia_cref_to_ql1s_ptr([ 20, 42, 0, 8 ]) - self.assertEqual(repr(ql1s), "'\\x14*\\x00\\x08'") + ql1s = pya.A.ia_cref_to_ql1s_ptr([ 20, 42, 0, 8, 0x03a9 ]) + self.assertEqual(repr(ql1s), "'\\x14*\\x00\\x08\u00a9'") self.assertEqual(pya.A.ql1s_to_ia('\x00\x01\x02'), [ 0, 1, 2 ]) + def test_utf8Strings(self): + + # UTF8 strings (non-Qt) + s = "\u0010*\u0000\b\u03a9" + self.assertEqual(repr(s), "'\\x10*\\x00\\x08\u03a9'") + + # full cycle must preserve encoding, also for var + self.assertEqual(repr(pya.A.ft_str(s)), "'\\x10*\\x00\\x08\u03a9'") + self.assertEqual(repr(pya.A.ft_var(s)), "'\\x10*\\x00\\x08\u03a9'") + + # NUL character terminates in const char * mode: + self.assertEqual(repr(pya.A.ft_cptr(s)), "'\\x10*'") + def test_binaryStrings(self): # binary strings (non-Qt) @@ -3167,8 +3187,13 @@ class BasicTest(unittest.TestCase): ba = pya.A.ia_cref_to_ba([ 17, 42, 0, 8 ]) if sys.version_info < (3, 0): self.assertEqual(repr(ba), "bytearray(b'\\x11*\\x00\\x08')") + self.assertEqual(repr(pya.A.ft_cv(ba)), "bytearray(b'\\x11*\\x00\\x08')") + self.assertEqual(repr(pya.A.ft_var(ba)), "bytearray(b'\\x11*\\x00\\x08')") else: self.assertEqual(repr(ba), "b'\\x11*\\x00\\x08'") + self.assertEqual(repr(pya.A.ft_cv(ba)), "b'\\x11*\\x00\\x08'") + self.assertEqual(repr(pya.A.ft_var(ba)), "b'\\x11*\\x00\\x08'") + self.assertEqual(pya.A.ba_to_ia(ba), [ 17, 42, 0, 8 ]) self.assertEqual(pya.A.ba_cref_to_ia(ba), [ 17, 42, 0, 8 ]) self.assertEqual(pya.A.ba_cptr_to_ia(ba), [ 17, 42, 0, 8 ]) diff --git a/testdata/ruby/basic_testcore.rb b/testdata/ruby/basic_testcore.rb index 72a8d1e02..ac734c3bc 100644 --- a/testdata/ruby/basic_testcore.rb +++ b/testdata/ruby/basic_testcore.rb @@ -2958,6 +2958,9 @@ class Basic_TestClass < TestBase qba = RBA::A::ia_cref_to_qba([ 16, 42, 0, 8 ]) assert_equal(qba.inspect, "\"\\x10*\\x00\\b\"") + # full cycle must preserve encoding, also for var + assert_equal(RBA::A::ft_qba(qba).inspect, "\"\\x10*\\x00\\b\"") + assert_equal(RBA::A::ft_var(qba).inspect, "\"\\x10*\\x00\\b\"") assert_equal(RBA::A::qba_to_ia(qba), [ 16, 42, 0, 8 ]) assert_equal(RBA::A::qba_cref_to_ia(qba), [ 16, 42, 0, 8 ]) @@ -3016,23 +3019,26 @@ class Basic_TestClass < TestBase if RBA::A.respond_to?(:ia_cref_to_qs) - qs = RBA::A::ia_cref_to_qs([ 16, 42, 0, 8 ]) - assert_equal(qs.inspect, "\"\\x10*\\x00\\b\"") + qs = RBA::A::ia_cref_to_qs([ 16, 42, 0, 8, 0x03a9 ]) + assert_equal(qs.inspect, "\"\\u0010*\\u0000\\b\u03a9\"") + # full cycle must preserve encoding, also for var + assert_equal(RBA::A::ft_qs(qs).inspect, "\"\\u0010*\\u0000\\b\u03a9\"") + assert_equal(RBA::A::ft_var(qs).inspect, "\"\\u0010*\\u0000\\b\u03a9\"") - assert_equal(RBA::A::qs_to_ia(qs), [ 16, 42, 0, 8 ]) - assert_equal(RBA::A::qs_cref_to_ia(qs), [ 16, 42, 0, 8 ]) - assert_equal(RBA::A::qs_cptr_to_ia(qs), [ 16, 42, 0, 8 ]) - assert_equal(RBA::A::qs_ref_to_ia(qs), [ 16, 42, 0, 8 ]) - assert_equal(RBA::A::qs_ptr_to_ia(qs), [ 16, 42, 0, 8 ]) + assert_equal(RBA::A::qs_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ]) + assert_equal(RBA::A::qs_cref_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ]) + assert_equal(RBA::A::qs_cptr_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ]) + assert_equal(RBA::A::qs_ref_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ]) + assert_equal(RBA::A::qs_ptr_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ]) - qs = RBA::A::ia_cref_to_qs_cref([ 17, 42, 0, 8 ]) - assert_equal(qs.inspect, "\"\\x11*\\x00\\b\"") - qs = RBA::A::ia_cref_to_qs_ref([ 18, 42, 0, 8 ]) - assert_equal(qs.inspect, "\"\\x12*\\x00\\b\"") - qs = RBA::A::ia_cref_to_qs_cptr([ 19, 42, 0, 8 ]) - assert_equal(qs.inspect, "\"\\x13*\\x00\\b\"") - qs = RBA::A::ia_cref_to_qs_ptr([ 20, 42, 0, 8 ]) - assert_equal(qs.inspect, "\"\\x14*\\x00\\b\"") + qs = RBA::A::ia_cref_to_qs_cref([ 17, 42, 0, 8, 0x03a9 ]) + assert_equal(qs.inspect, "\"\\u0011*\\u0000\\b\u03a9\"") + qs = RBA::A::ia_cref_to_qs_ref([ 18, 42, 0, 8, 0x03a9 ]) + assert_equal(qs.inspect, "\"\\u0012*\\u0000\\b\u03a9\"") + qs = RBA::A::ia_cref_to_qs_cptr([ 19, 42, 0, 8, 0x03a9 ]) + assert_equal(qs.inspect, "\"\\u0013*\\u0000\\b\u03a9\"") + qs = RBA::A::ia_cref_to_qs_ptr([ 20, 42, 0, 8, 0x03a9 ]) + assert_equal(qs.inspect, "\"\\u0014*\\u0000\\b\u03a9\"") assert_equal(RBA::A::qs_to_ia("\x00\x01\x02"), [ 0, 1, 2 ]) @@ -3046,23 +3052,23 @@ class Basic_TestClass < TestBase if RBA::A.respond_to?(:ia_cref_to_ql1s) - ql1s = RBA::A::ia_cref_to_ql1s([ 16, 42, 0, 8 ]) - assert_equal(ql1s.inspect, "\"\\x10*\\x00\\b\"") + ql1s = RBA::A::ia_cref_to_ql1s([ 16, 42, 0, 8, 0x03a9 ]) + assert_equal(ql1s.inspect, "\"\\u0010*\\u0000\\b\u00a9\"") - assert_equal(RBA::A::ql1s_to_ia(ql1s), [ 16, 42, 0, 8 ]) - assert_equal(RBA::A::ql1s_cref_to_ia(ql1s), [ 16, 42, 0, 8 ]) - assert_equal(RBA::A::ql1s_cptr_to_ia(ql1s), [ 16, 42, 0, 8 ]) - assert_equal(RBA::A::ql1s_ref_to_ia(ql1s), [ 16, 42, 0, 8 ]) - assert_equal(RBA::A::ql1s_ptr_to_ia(ql1s), [ 16, 42, 0, 8 ]) + assert_equal(RBA::A::ql1s_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ]) + assert_equal(RBA::A::ql1s_cref_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ]) + assert_equal(RBA::A::ql1s_cptr_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ]) + assert_equal(RBA::A::ql1s_ref_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ]) + assert_equal(RBA::A::ql1s_ptr_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ]) - ql1s = RBA::A::ia_cref_to_ql1s_cref([ 17, 42, 0, 8 ]) - assert_equal(ql1s.inspect, "\"\\x11*\\x00\\b\"") - ql1s = RBA::A::ia_cref_to_ql1s_ref([ 18, 42, 0, 8 ]) - assert_equal(ql1s.inspect, "\"\\x12*\\x00\\b\"") - ql1s = RBA::A::ia_cref_to_ql1s_cptr([ 19, 42, 0, 8 ]) - assert_equal(ql1s.inspect, "\"\\x13*\\x00\\b\"") - ql1s = RBA::A::ia_cref_to_ql1s_ptr([ 20, 42, 0, 8 ]) - assert_equal(ql1s.inspect, "\"\\x14*\\x00\\b\"") + ql1s = RBA::A::ia_cref_to_ql1s_cref([ 17, 42, 0, 8, 0xa9 ]) + assert_equal(ql1s.inspect, "\"\\u0011*\\u0000\\b\u00a9\"") + ql1s = RBA::A::ia_cref_to_ql1s_ref([ 18, 42, 0, 8, 0xa9 ]) + assert_equal(ql1s.inspect, "\"\\u0012*\\u0000\\b\u00a9\"") + ql1s = RBA::A::ia_cref_to_ql1s_cptr([ 19, 42, 0, 8, 0xa9 ]) + assert_equal(ql1s.inspect, "\"\\u0013*\\u0000\\b\u00a9\"") + ql1s = RBA::A::ia_cref_to_ql1s_ptr([ 20, 42, 0, 8, 0xa9 ]) + assert_equal(ql1s.inspect, "\"\\u0014*\\u0000\\b\u00a9\"") assert_equal(RBA::A::ql1s_to_ia("\x00\x01\x02"), [ 0, 1, 2 ]) @@ -3077,7 +3083,7 @@ class Basic_TestClass < TestBase if RBA::A.respond_to?(:ia_cref_to_qsv) qsv = RBA::A::ia_cref_to_qsv([ 16, 42, 0, 8 ]) - assert_equal(qsv.inspect, "\"\\x10*\\x00\\b\"") + assert_equal(qsv.inspect, "\"\\u0010*\\u0000\\b\"") assert_equal(RBA::A::qsv_to_ia(qsv), [ 16, 42, 0, 8 ]) assert_equal(RBA::A::qsv_cref_to_ia(qsv), [ 16, 42, 0, 8 ]) @@ -3086,13 +3092,13 @@ class Basic_TestClass < TestBase assert_equal(RBA::A::qsv_ptr_to_ia(qsv), [ 16, 42, 0, 8 ]) qsv = RBA::A::ia_cref_to_qsv_cref([ 17, 42, 0, 8 ]) - assert_equal(qsv.inspect, "\"\\x11*\\x00\\b\"") + assert_equal(qsv.inspect, "\"\\u0011*\\u0000\\b\"") qsv = RBA::A::ia_cref_to_qsv_ref([ 18, 42, 0, 8 ]) - assert_equal(qsv.inspect, "\"\\x12*\\x00\\b\"") + assert_equal(qsv.inspect, "\"\\u0012*\\u0000\\b\"") qsv = RBA::A::ia_cref_to_qsv_cptr([ 19, 42, 0, 8 ]) - assert_equal(qsv.inspect, "\"\\x13*\\x00\\b\"") + assert_equal(qsv.inspect, "\"\\u0013*\\u0000\\b\"") qsv = RBA::A::ia_cref_to_qsv_ptr([ 20, 42, 0, 8 ]) - assert_equal(qsv.inspect, "\"\\x14*\\x00\\b\"") + assert_equal(qsv.inspect, "\"\\u0014*\\u0000\\b\"") assert_equal(RBA::A::qsv_to_ia("\x00\x01\x02"), [ 0, 1, 2 ]) @@ -3100,18 +3106,36 @@ class Basic_TestClass < TestBase end + def test_utf8Strings + + # UTF8 strings (non-Qt) + s = "\u0010*\u0000\b\u03a9" + assert_equal(s.inspect, "\"\\u0010*\\u0000\\b\u03a9\"") + + # full cycle must preserve encoding, also for var + assert_equal(RBA::A::ft_str(s).inspect, "\"\\u0010*\\u0000\\b\u03a9\"") + assert_equal(RBA::A::ft_var(s).inspect, "\"\\u0010*\\u0000\\b\u03a9\"") + + # NUL character terminates in const char * mode: + assert_equal(RBA::A::ft_cptr(s).inspect, "\"\\u0010*\"") + + end + def test_binaryStrings # binary strings (non-Qt) - ba = RBA::A::ia_cref_to_ba([ 16, 42, 1, 8 ]) - assert_equal(ba.inspect, "\"\\x10*\\x01\\b\"") + ba = RBA::A::ia_cref_to_ba([ 16, 42, 0, 8 ]) + assert_equal(ba.inspect, "\"\\x10*\\x00\\b\"") + # full cycle must preserve encoding, also for var + assert_equal(RBA::A::ft_cv(ba).inspect, "\"\\x10*\\x00\\b\"") + assert_equal(RBA::A::ft_var(ba).inspect, "\"\\x10*\\x00\\b\"") - assert_equal(RBA::A::ba_to_ia(ba), [ 16, 42, 1, 8 ]) - assert_equal(RBA::A::ba_cref_to_ia(ba), [ 16, 42, 1, 8 ]) - assert_equal(RBA::A::ba_cptr_to_ia(ba), [ 16, 42, 1, 8 ]) - assert_equal(RBA::A::ba_ref_to_ia(ba), [ 16, 42, 1, 8 ]) - assert_equal(RBA::A::ba_ptr_to_ia(ba), [ 16, 42, 1, 8 ]) + assert_equal(RBA::A::ba_to_ia(ba), [ 16, 42, 0, 8 ]) + assert_equal(RBA::A::ba_cref_to_ia(ba), [ 16, 42, 0, 8 ]) + assert_equal(RBA::A::ba_cptr_to_ia(ba), [ 16, 42, 0, 8 ]) + assert_equal(RBA::A::ba_ref_to_ia(ba), [ 16, 42, 0, 8 ]) + assert_equal(RBA::A::ba_ptr_to_ia(ba), [ 16, 42, 0, 8 ]) ba = RBA::A::ia_cref_to_ba_cref([ 17, 42, 0, 8 ]) assert_equal(ba.inspect, "\"\\x11*\\x00\\b\"") From ad80019b1219e2598f66ff5b38821e6311b74dc1 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 May 2025 16:31:20 +0200 Subject: [PATCH 54/58] Adding to_s (aka str(...)) methods to RNetExtractor tech objects --- src/pex/pex/gsiDeclRNetExtractor.cc | 9 +++ src/pex/pex/pexRExtractorTech.cc | 62 +++++++++++++++++++++ src/pex/pex/pexRExtractorTech.h | 16 ++++++ src/pex/unit_tests/pexRNetExtractorTests.cc | 39 +++++++++++++ testdata/ruby/pexTests.rb | 6 ++ 5 files changed, 132 insertions(+) diff --git a/src/pex/pex/gsiDeclRNetExtractor.cc b/src/pex/pex/gsiDeclRNetExtractor.cc index 7ba3c1b54..1b9b02f03 100644 --- a/src/pex/pex/gsiDeclRNetExtractor.cc +++ b/src/pex/pex/gsiDeclRNetExtractor.cc @@ -81,6 +81,9 @@ static void via_set_merge_distance (pex::RExtractorTechVia *via, double dist) } Class decl_RExtractorTechVia ("pex", "RExtractorTechVia", + gsi::method ("to_s", &pex::RExtractorTechVia::to_string, + "@brief Returns a string describing this object" + ) + gsi::method_ext ("merge_distance", &via_get_merge_distance, "@brief Gets the merge distance\n" "If this value is not zero, it specifies the distance below (or equal) which " @@ -186,6 +189,9 @@ static void cond_set_triangulation_max_area (pex::RExtractorTechConductor *cond, } Class decl_RExtractorTechConductor ("pex", "RExtractorTechConductor", + gsi::method ("to_s", &pex::RExtractorTechConductor::to_string, + "@brief Returns a string describing this object" + ) + gsi::method_ext ("algorithm", &cond_get_algorithm, "@brief Gets the algorithm to use\n" "Specifies the algorithm to use. The default algorithm is 'SquareCounting'." @@ -305,6 +311,9 @@ static void tech_add_conductor (pex::RExtractorTech *tech, const pex::RExtractor } Class decl_RExtractorTech ("pex", "RExtractorTech", + gsi::method ("to_s", &pex::RExtractorTech::to_string, + "@brief Returns a string describing this object" + ) + gsi::method_ext ("skip_simplify", &tech_get_skip_simplify, "@brief Gets a value indicating whether to skip the simplify step\n" "This values specifies to skip the simplify step of the network after the extraction has " diff --git a/src/pex/pex/pexRExtractorTech.cc b/src/pex/pex/pexRExtractorTech.cc index 38cc77554..90195a05f 100644 --- a/src/pex/pex/pexRExtractorTech.cc +++ b/src/pex/pex/pexRExtractorTech.cc @@ -23,13 +23,75 @@ #include "pexRExtractorTech.h" +#include "tlString.h" + namespace pex { +// ------------------------------------------------------------------ + +std::string +RExtractorTechVia::to_string () const +{ + std::string res = "Via("; + res += tl::sprintf ("bottom=L%u, cut=L%u, top=L%u, R=%.12gµm²*Ohm", bottom_conductor, cut_layer, top_conductor, resistance); + if (merge_distance > 1e-10) { + res += tl::sprintf(", d_merge=%.12gµm", merge_distance); + } + res += ")"; + return res; +} + +// ------------------------------------------------------------------ + +std::string +RExtractorTechConductor::to_string () const +{ + std::string res = "Conductor("; + res += tl::sprintf ("layer=L%u, R=%.12gOhm/sq", layer, resistance); + + switch (algorithm) { + case SquareCounting: + res += ", algo=SquareCounting"; + break; + case Tesselation: + res += ", algo=Tesselation"; + break; + default: + break; + } + + if (triangulation_min_b > 1e-10) { + res += tl::sprintf(", tri_min_b=%.12gµm", triangulation_min_b); + } + + if (triangulation_max_area > 1e-10) { + res += tl::sprintf(", tri_max_area=%.12gµm", triangulation_max_area); + } + + res += ")"; + return res; +} + +// ------------------------------------------------------------------ + RExtractorTech::RExtractorTech () : skip_simplify (false) { // .. nothing yet .. } +std::string +RExtractorTech::to_string () const +{ + std::string res; + if (skip_simplify) { + res += "skip_simplify=true\n"; + } + res += tl::join (vias.begin (), vias.end (), "\n"); + res += "\n"; + res += tl::join (conductors.begin (), conductors.end (), "\n"); + return res; +} + } diff --git a/src/pex/pex/pexRExtractorTech.h b/src/pex/pex/pexRExtractorTech.h index 6062910ae..60bc6edda 100644 --- a/src/pex/pex/pexRExtractorTech.h +++ b/src/pex/pex/pexRExtractorTech.h @@ -26,6 +26,7 @@ #include "pexCommon.h" #include +#include namespace pex { @@ -45,6 +46,11 @@ public: // .. nothing yet .. } + /** + * @brief Returns a string describing this object + */ + std::string to_string () const; + /** * @brief Specifies the cut layer * This is the layer the via sits on @@ -112,6 +118,11 @@ public: // .. nothing yet .. } + /** + * @brief Returns a string describing this object + */ + std::string to_string () const; + /** * @brief Specifies the layer * The value is the generic ID of the layer. @@ -155,6 +166,11 @@ public: */ RExtractorTech (); + /** + * @brief Returns a string describing this object + */ + std::string to_string () const; + /** * @brief A list of via definitions */ diff --git a/src/pex/unit_tests/pexRNetExtractorTests.cc b/src/pex/unit_tests/pexRNetExtractorTests.cc index 2b6f9f083..bba24e0c0 100644 --- a/src/pex/unit_tests/pexRNetExtractorTests.cc +++ b/src/pex/unit_tests/pexRNetExtractorTests.cc @@ -64,6 +64,45 @@ static std::string network2s (const pex::RNetwork &network) return tl::join (r, "\n"); } +TEST(basic) +{ + unsigned int l1 = 1; + unsigned int l2 = 1; + unsigned int l3 = 1; + + pex::RExtractorTech tech; + + pex::RExtractorTechVia via1; + via1.bottom_conductor = l1; + via1.cut_layer = l2; + via1.top_conductor = l3; + via1.resistance = 2.0; + via1.merge_distance = 0.2; + tech.vias.push_back (via1); + + pex::RExtractorTechConductor cond1; + cond1.layer = l1; + cond1.resistance = 0.5; + tech.conductors.push_back (cond1); + + pex::RExtractorTechConductor cond2; + cond2.layer = l3; + cond2.resistance = 0.25; + cond2.algorithm = pex::RExtractorTechConductor::Tesselation; + cond2.triangulation_max_area = 1.5; + cond2.triangulation_min_b = 0.5; + tech.conductors.push_back (cond2); + + tech.skip_simplify = true; + + EXPECT_EQ (tech.to_string (), + "skip_simplify=true\n" + "Via(bottom=L1, cut=L1, top=L1, R=2µm²*Ohm, d_merge=0.2µm)\n" + "Conductor(layer=L1, R=0.5Ohm/sq, algo=SquareCounting)\n" + "Conductor(layer=L1, R=0.25Ohm/sq, algo=Tesselation, tri_min_b=0.5µm, tri_max_area=1.5µm)" + ); +} + TEST(netex_viagen1) { db::Layout ly; diff --git a/testdata/ruby/pexTests.rb b/testdata/ruby/pexTests.rb index 2e11f42de..838095a2b 100644 --- a/testdata/ruby/pexTests.rb +++ b/testdata/ruby/pexTests.rb @@ -142,6 +142,7 @@ class PEX_TestClass < TestBase via1.top_conductor = l3 via1.resistance = 2.0 via1.merge_distance = 0.2 + assert_equal(via1.to_s, "Via(bottom=L1, cut=L2, top=L3, R=2µm²*Ohm, d_merge=0.2µm)") assert_equal(via1.bottom_conductor, l1) assert_equal(via1.cut_layer, l2) @@ -161,6 +162,7 @@ class PEX_TestClass < TestBase cond1 = RBA::RExtractorTechConductor::new cond1.layer = l1 cond1.resistance = 0.5 + assert_equal(cond1.to_s, "Conductor(layer=L1, R=0.5Ohm/sq, algo=SquareCounting)") assert_equal(cond1.layer, l1) assert_equal(cond1.resistance, 0.5) @@ -171,6 +173,10 @@ class PEX_TestClass < TestBase tech.add_conductor(cond2) assert_equal(tech.each_conductor.collect { |c| c.layer }, [ l3 ]) + assert_equal(tech.to_s, + "Via(bottom=L1, cut=L2, top=L3, R=2µm²*Ohm, d_merge=0.2µm)\n" + + "Conductor(layer=L3, R=0.25Ohm/sq, algo=SquareCounting)" + ) tech.clear_conductors assert_equal(tech.each_conductor.collect { |c| c.layer }, []) From 4a20a308886fe99e3abddf569822f35490e4bb2d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 May 2025 18:55:52 +0200 Subject: [PATCH 55/58] Hope to fix dependency on Ruby version rgarding string encoding. --- src/rba/rba/rbaConvert.h | 2 +- src/rba/rba/rbaUtils.cc | 16 +++++++++++ src/rba/rba/rbaUtils.h | 4 +++ testdata/ruby/basic_testcore.rb | 47 ++++++++++++++++++++++----------- 4 files changed, 52 insertions(+), 17 deletions(-) diff --git a/src/rba/rba/rbaConvert.h b/src/rba/rba/rbaConvert.h index 6865dc7d2..1f1e0ee14 100644 --- a/src/rba/rba/rbaConvert.h +++ b/src/rba/rba/rbaConvert.h @@ -507,7 +507,7 @@ inline VALUE c2ruby (const char * const & s) { if (! s) { static const char null_string[] = "(null)"; - return rb_str_new (null_string, sizeof (null_string) - 1); + return rb_utf8_str_new (null_string, sizeof (null_string) - 1); } else { return rb_utf8_str_new (s, long (strlen (s))); } diff --git a/src/rba/rba/rbaUtils.cc b/src/rba/rba/rbaUtils.cc index bdb9e5c16..ab4272ba1 100644 --- a/src/rba/rba/rbaUtils.cc +++ b/src/rba/rba/rbaUtils.cc @@ -32,6 +32,10 @@ # include #endif +#if HAVE_RUBY_VERSION_CODE < 20200 +# include +#endif + static VALUE ruby_top_self = Qnil; VALUE rb_get_top_self () @@ -188,6 +192,18 @@ rba_check_error (int state) } } +#if HAVE_RUBY_VERSION_CODE < 20200 + +// Ruby <2.2 does not have this useful function +VALUE rb_utf8_str_new (const char *ptr, long len) +{ + VALUE str = rb_str_new (ptr, len); + rb_enc_associate_index (str, rb_utf8_encindex ()); + return str; +} + +#endif + /** * @brief needed because StringValue is a macro: */ diff --git a/src/rba/rba/rbaUtils.h b/src/rba/rba/rbaUtils.h index 99422bd6d..fca9b06e9 100644 --- a/src/rba/rba/rbaUtils.h +++ b/src/rba/rba/rbaUtils.h @@ -135,6 +135,10 @@ inline void rb_hash_clear(VALUE hash) #endif +#if HAVE_RUBY_VERSION_CODE < 20200 +VALUE rb_utf8_str_new (const char *str, long len); +#endif + typedef VALUE (*ruby_func)(ANYARGS); /** diff --git a/testdata/ruby/basic_testcore.rb b/testdata/ruby/basic_testcore.rb index ac734c3bc..7db26da8b 100644 --- a/testdata/ruby/basic_testcore.rb +++ b/testdata/ruby/basic_testcore.rb @@ -3020,10 +3020,13 @@ class Basic_TestClass < TestBase if RBA::A.respond_to?(:ia_cref_to_qs) qs = RBA::A::ia_cref_to_qs([ 16, 42, 0, 8, 0x03a9 ]) - assert_equal(qs.inspect, "\"\\u0010*\\u0000\\b\u03a9\"") + assert_equal(qs.encoding.name, "UTF-8") + assert_equal(qs, "\u0010*\u0000\b\u03a9") # full cycle must preserve encoding, also for var - assert_equal(RBA::A::ft_qs(qs).inspect, "\"\\u0010*\\u0000\\b\u03a9\"") - assert_equal(RBA::A::ft_var(qs).inspect, "\"\\u0010*\\u0000\\b\u03a9\"") + assert_equal(RBA::A::ft_qs(qs).encoding.name, "UTF-8") + assert_equal(RBA::A::ft_qs(qs), "\u0010*\u0000\b\u03a9") + assert_equal(RBA::A::ft_var(qs).encoding.name, "UTF-8") + assert_equal(RBA::A::ft_var(qs), "\u0010*\u0000\b\u03a9") assert_equal(RBA::A::qs_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ]) assert_equal(RBA::A::qs_cref_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ]) @@ -3032,13 +3035,17 @@ class Basic_TestClass < TestBase assert_equal(RBA::A::qs_ptr_to_ia(qs), [ 16, 42, 0, 8, 0x03a9 ]) qs = RBA::A::ia_cref_to_qs_cref([ 17, 42, 0, 8, 0x03a9 ]) - assert_equal(qs.inspect, "\"\\u0011*\\u0000\\b\u03a9\"") + assert_equal(qs.encoding.name, "UTF-8") + assert_equal(qs, "\u0011*\u0000\b\u03a9") qs = RBA::A::ia_cref_to_qs_ref([ 18, 42, 0, 8, 0x03a9 ]) - assert_equal(qs.inspect, "\"\\u0012*\\u0000\\b\u03a9\"") + assert_equal(qs.encoding.name, "UTF-8") + assert_equal(qs, "\u0012*\u0000\b\u03a9") qs = RBA::A::ia_cref_to_qs_cptr([ 19, 42, 0, 8, 0x03a9 ]) - assert_equal(qs.inspect, "\"\\u0013*\\u0000\\b\u03a9\"") + assert_equal(qs.encoding.name, "UTF-8") + assert_equal(qs, "\u0013*\u0000\b\u03a9") qs = RBA::A::ia_cref_to_qs_ptr([ 20, 42, 0, 8, 0x03a9 ]) - assert_equal(qs.inspect, "\"\\u0014*\\u0000\\b\u03a9\"") + assert_equal(qs.encoding.name, "UTF-8") + assert_equal(qs, "\u0014*\u0000\b\u03a9") assert_equal(RBA::A::qs_to_ia("\x00\x01\x02"), [ 0, 1, 2 ]) @@ -3053,7 +3060,8 @@ class Basic_TestClass < TestBase if RBA::A.respond_to?(:ia_cref_to_ql1s) ql1s = RBA::A::ia_cref_to_ql1s([ 16, 42, 0, 8, 0x03a9 ]) - assert_equal(ql1s.inspect, "\"\\u0010*\\u0000\\b\u00a9\"") + assert_equal(ql1s.encoding.name, "UTF-8") + assert_equal(ql1s, "\u0010*\u0000\b\u00a9") assert_equal(RBA::A::ql1s_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ]) assert_equal(RBA::A::ql1s_cref_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ]) @@ -3062,13 +3070,17 @@ class Basic_TestClass < TestBase assert_equal(RBA::A::ql1s_ptr_to_ia(ql1s), [ 16, 42, 0, 8, 0xa9 ]) ql1s = RBA::A::ia_cref_to_ql1s_cref([ 17, 42, 0, 8, 0xa9 ]) - assert_equal(ql1s.inspect, "\"\\u0011*\\u0000\\b\u00a9\"") + assert_equal(ql1s.encoding.name, "UTF-8") + assert_equal(ql1s, "\u0011*\u0000\b\u00a9") ql1s = RBA::A::ia_cref_to_ql1s_ref([ 18, 42, 0, 8, 0xa9 ]) - assert_equal(ql1s.inspect, "\"\\u0012*\\u0000\\b\u00a9\"") + assert_equal(ql1s.encoding.name, "UTF-8") + assert_equal(ql1s, "\u0012*\u0000\b\u00a9") ql1s = RBA::A::ia_cref_to_ql1s_cptr([ 19, 42, 0, 8, 0xa9 ]) - assert_equal(ql1s.inspect, "\"\\u0013*\\u0000\\b\u00a9\"") + assert_equal(ql1s.encoding.name, "UTF-8") + assert_equal(ql1s, "\u0013*\u0000\b\u00a9") ql1s = RBA::A::ia_cref_to_ql1s_ptr([ 20, 42, 0, 8, 0xa9 ]) - assert_equal(ql1s.inspect, "\"\\u0014*\\u0000\\b\u00a9\"") + assert_equal(ql1s.encoding.name, "UTF-8") + assert_equal(ql1s, "\u0014*\u0000\b\u00a9") assert_equal(RBA::A::ql1s_to_ia("\x00\x01\x02"), [ 0, 1, 2 ]) @@ -3110,14 +3122,17 @@ class Basic_TestClass < TestBase # UTF8 strings (non-Qt) s = "\u0010*\u0000\b\u03a9" - assert_equal(s.inspect, "\"\\u0010*\\u0000\\b\u03a9\"") + assert_equal(s.encoding.name, "UTF-8") # full cycle must preserve encoding, also for var - assert_equal(RBA::A::ft_str(s).inspect, "\"\\u0010*\\u0000\\b\u03a9\"") - assert_equal(RBA::A::ft_var(s).inspect, "\"\\u0010*\\u0000\\b\u03a9\"") + assert_equal(RBA::A::ft_str(s).encoding.name, "UTF-8") + assert_equal(RBA::A::ft_str(s), "\u0010*\u0000\b\u03a9") + assert_equal(RBA::A::ft_var(s).encoding.name, "UTF-8") + assert_equal(RBA::A::ft_var(s), "\u0010*\u0000\b\u03a9") # NUL character terminates in const char * mode: - assert_equal(RBA::A::ft_cptr(s).inspect, "\"\\u0010*\"") + assert_equal(RBA::A::ft_cptr(s).encoding.name, "UTF-8") + assert_equal(RBA::A::ft_cptr(s), "\u0010*") end From fb16c8c6f61ad40d6220e74fb3152d7610cb3cef Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 May 2025 19:01:04 +0200 Subject: [PATCH 56/58] Trying to fix a linker issue --- src/rba/rba/rbaUtils.cc | 16 ---------------- src/rba/rba/rbaUtils.h | 12 +++++++++++- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/rba/rba/rbaUtils.cc b/src/rba/rba/rbaUtils.cc index ab4272ba1..bdb9e5c16 100644 --- a/src/rba/rba/rbaUtils.cc +++ b/src/rba/rba/rbaUtils.cc @@ -32,10 +32,6 @@ # include #endif -#if HAVE_RUBY_VERSION_CODE < 20200 -# include -#endif - static VALUE ruby_top_self = Qnil; VALUE rb_get_top_self () @@ -192,18 +188,6 @@ rba_check_error (int state) } } -#if HAVE_RUBY_VERSION_CODE < 20200 - -// Ruby <2.2 does not have this useful function -VALUE rb_utf8_str_new (const char *ptr, long len) -{ - VALUE str = rb_str_new (ptr, len); - rb_enc_associate_index (str, rb_utf8_encindex ()); - return str; -} - -#endif - /** * @brief needed because StringValue is a macro: */ diff --git a/src/rba/rba/rbaUtils.h b/src/rba/rba/rbaUtils.h index fca9b06e9..e4d4f1684 100644 --- a/src/rba/rba/rbaUtils.h +++ b/src/rba/rba/rbaUtils.h @@ -136,7 +136,17 @@ inline void rb_hash_clear(VALUE hash) #endif #if HAVE_RUBY_VERSION_CODE < 20200 -VALUE rb_utf8_str_new (const char *str, long len); + +#include + +// Ruby <2.2 does not have this useful function +inline VALUE rb_utf8_str_new (const char *ptr, long len) +{ + VALUE str = rb_str_new (ptr, len); + rb_enc_associate_index (str, rb_utf8_encindex ()); + return str; +} + #endif typedef VALUE (*ruby_func)(ANYARGS); From 534b33be1c2552cca5b3bf332bf6f5dd28134725 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 May 2025 20:40:45 +0200 Subject: [PATCH 57/58] MSVC does not assume UTF-8 encoding by default, hence use explicit bytes --- src/pex/pex/pexRExtractorTech.cc | 10 +++++----- src/pex/unit_tests/pexRNetExtractorTests.cc | 8 ++++---- testdata/ruby/pexTests.rb | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/pex/pex/pexRExtractorTech.cc b/src/pex/pex/pexRExtractorTech.cc index 90195a05f..075f8417b 100644 --- a/src/pex/pex/pexRExtractorTech.cc +++ b/src/pex/pex/pexRExtractorTech.cc @@ -34,9 +34,9 @@ std::string RExtractorTechVia::to_string () const { std::string res = "Via("; - res += tl::sprintf ("bottom=L%u, cut=L%u, top=L%u, R=%.12gµm²*Ohm", bottom_conductor, cut_layer, top_conductor, resistance); + res += tl::sprintf ("bottom=L%u, cut=L%u, top=L%u, R=%.12g \xC2\xB5m\xC2\xB2*Ohm", bottom_conductor, cut_layer, top_conductor, resistance); if (merge_distance > 1e-10) { - res += tl::sprintf(", d_merge=%.12gµm", merge_distance); + res += tl::sprintf(", d_merge=%.12g \xC2\xB5m", merge_distance); } res += ")"; return res; @@ -48,7 +48,7 @@ std::string RExtractorTechConductor::to_string () const { std::string res = "Conductor("; - res += tl::sprintf ("layer=L%u, R=%.12gOhm/sq", layer, resistance); + res += tl::sprintf ("layer=L%u, R=%.12g Ohm/sq", layer, resistance); switch (algorithm) { case SquareCounting: @@ -62,11 +62,11 @@ RExtractorTechConductor::to_string () const } if (triangulation_min_b > 1e-10) { - res += tl::sprintf(", tri_min_b=%.12gµm", triangulation_min_b); + res += tl::sprintf(", tri_min_b=%.12g \xC2\xB5m", triangulation_min_b); } if (triangulation_max_area > 1e-10) { - res += tl::sprintf(", tri_max_area=%.12gµm", triangulation_max_area); + res += tl::sprintf(", tri_max_area=%.12g \xC2\xB5m\xC2\xB2", triangulation_max_area); } res += ")"; diff --git a/src/pex/unit_tests/pexRNetExtractorTests.cc b/src/pex/unit_tests/pexRNetExtractorTests.cc index bba24e0c0..372384819 100644 --- a/src/pex/unit_tests/pexRNetExtractorTests.cc +++ b/src/pex/unit_tests/pexRNetExtractorTests.cc @@ -96,10 +96,10 @@ TEST(basic) tech.skip_simplify = true; EXPECT_EQ (tech.to_string (), - "skip_simplify=true\n" - "Via(bottom=L1, cut=L1, top=L1, R=2µm²*Ohm, d_merge=0.2µm)\n" - "Conductor(layer=L1, R=0.5Ohm/sq, algo=SquareCounting)\n" - "Conductor(layer=L1, R=0.25Ohm/sq, algo=Tesselation, tri_min_b=0.5µm, tri_max_area=1.5µm)" + u8"skip_simplify=true\n" + u8"Via(bottom=L1, cut=L1, top=L1, R=2 \xC2\xB5m\xC2\xB2*Ohm, d_merge=0.2 \xC2\xB5m)\n" + u8"Conductor(layer=L1, R=0.5 Ohm/sq, algo=SquareCounting)\n" + u8"Conductor(layer=L1, R=0.25 Ohm/sq, algo=Tesselation, tri_min_b=0.5 \xC2\xB5m, tri_max_area=1.5 \xC2\xB5m\xC2\xB2)" ); } diff --git a/testdata/ruby/pexTests.rb b/testdata/ruby/pexTests.rb index 838095a2b..efb4e3706 100644 --- a/testdata/ruby/pexTests.rb +++ b/testdata/ruby/pexTests.rb @@ -142,7 +142,7 @@ class PEX_TestClass < TestBase via1.top_conductor = l3 via1.resistance = 2.0 via1.merge_distance = 0.2 - assert_equal(via1.to_s, "Via(bottom=L1, cut=L2, top=L3, R=2µm²*Ohm, d_merge=0.2µm)") + assert_equal(via1.to_s, "Via(bottom=L1, cut=L2, top=L3, R=2 µm²*Ohm, d_merge=0.2 µm)") assert_equal(via1.bottom_conductor, l1) assert_equal(via1.cut_layer, l2) @@ -162,7 +162,7 @@ class PEX_TestClass < TestBase cond1 = RBA::RExtractorTechConductor::new cond1.layer = l1 cond1.resistance = 0.5 - assert_equal(cond1.to_s, "Conductor(layer=L1, R=0.5Ohm/sq, algo=SquareCounting)") + assert_equal(cond1.to_s, "Conductor(layer=L1, R=0.5 Ohm/sq, algo=SquareCounting)") assert_equal(cond1.layer, l1) assert_equal(cond1.resistance, 0.5) @@ -174,8 +174,8 @@ class PEX_TestClass < TestBase tech.add_conductor(cond2) assert_equal(tech.each_conductor.collect { |c| c.layer }, [ l3 ]) assert_equal(tech.to_s, - "Via(bottom=L1, cut=L2, top=L3, R=2µm²*Ohm, d_merge=0.2µm)\n" + - "Conductor(layer=L3, R=0.25Ohm/sq, algo=SquareCounting)" + "Via(bottom=L1, cut=L2, top=L3, R=2 µm²*Ohm, d_merge=0.2 µm)\n" + + "Conductor(layer=L3, R=0.25 Ohm/sq, algo=SquareCounting)" ) tech.clear_conductors From 13bc72383142464ecf2e9623692ec60e860b0bae Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 25 May 2025 23:24:38 +0200 Subject: [PATCH 58/58] Fix for MSVC builds --- src/pex/unit_tests/pexRNetExtractorTests.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pex/unit_tests/pexRNetExtractorTests.cc b/src/pex/unit_tests/pexRNetExtractorTests.cc index 372384819..b8fd19f71 100644 --- a/src/pex/unit_tests/pexRNetExtractorTests.cc +++ b/src/pex/unit_tests/pexRNetExtractorTests.cc @@ -96,10 +96,10 @@ TEST(basic) tech.skip_simplify = true; EXPECT_EQ (tech.to_string (), - u8"skip_simplify=true\n" - u8"Via(bottom=L1, cut=L1, top=L1, R=2 \xC2\xB5m\xC2\xB2*Ohm, d_merge=0.2 \xC2\xB5m)\n" - u8"Conductor(layer=L1, R=0.5 Ohm/sq, algo=SquareCounting)\n" - u8"Conductor(layer=L1, R=0.25 Ohm/sq, algo=Tesselation, tri_min_b=0.5 \xC2\xB5m, tri_max_area=1.5 \xC2\xB5m\xC2\xB2)" + "skip_simplify=true\n" + "Via(bottom=L1, cut=L1, top=L1, R=2 \xC2\xB5m\xC2\xB2*Ohm, d_merge=0.2 \xC2\xB5m)\n" + "Conductor(layer=L1, R=0.5 Ohm/sq, algo=SquareCounting)\n" + "Conductor(layer=L1, R=0.25 Ohm/sq, algo=Tesselation, tri_min_b=0.5 \xC2\xB5m, tri_max_area=1.5 \xC2\xB5m\xC2\xB2)" ); }