From 94a425da0d222676b7355d6ca8172a4de86e9bd2 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 26 Jan 2026 23:55:55 +0100 Subject: [PATCH] WIP --- src/db/db/db.pro | 2 + src/db/db/dbCellInstanceSetHasher.cc | 154 ++++++++++++++++++ src/db/db/dbCellInstanceSetHasher.h | 90 ++++++++++ src/db/db/dbCellMapping.cc | 89 +++++++++- .../dbCellInstanceSetHasherTests.cc | 63 +++++++ src/db/unit_tests/unit_tests.pro | 1 + 6 files changed, 390 insertions(+), 9 deletions(-) create mode 100644 src/db/db/dbCellInstanceSetHasher.cc create mode 100644 src/db/db/dbCellInstanceSetHasher.h create mode 100644 src/db/unit_tests/dbCellInstanceSetHasherTests.cc diff --git a/src/db/db/db.pro b/src/db/db/db.pro index 44edac6b7..188abf0d8 100644 --- a/src/db/db/db.pro +++ b/src/db/db/db.pro @@ -15,6 +15,7 @@ SOURCES = \ dbCellGraphUtils.cc \ dbCellHullGenerator.cc \ dbCellInst.cc \ + dbCellInstanceSetHasher.cc \ dbCellMapping.cc \ dbClipboard.cc \ dbClipboardData.cc \ @@ -253,6 +254,7 @@ HEADERS = \ dbCell.h \ dbCellHullGenerator.h \ dbCellInst.h \ + dbCellInstanceSetHasher.h \ dbCellMapping.h \ dbClipboardData.h \ dbClipboard.h \ diff --git a/src/db/db/dbCellInstanceSetHasher.cc b/src/db/db/dbCellInstanceSetHasher.cc new file mode 100644 index 000000000..a68c7ad22 --- /dev/null +++ b/src/db/db/dbCellInstanceSetHasher.cc @@ -0,0 +1,154 @@ + +/* + + 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 "dbCellInstanceSetHasher.h" +#include "dbHash.h" + +namespace db +{ + +CellInstanceSetHasher::MatrixHash::MatrixHash (double s) + : db::IMatrix3d (s, 0, 0, 0, s, 0, 0, 0, s) +{ + // .. nothing yet .. +} + +CellInstanceSetHasher::MatrixHash::MatrixHash (const db::ICplxTrans &trans) + : db::IMatrix3d (trans) +{ + // .. nothing yet .. +} + +CellInstanceSetHasher::MatrixHash::MatrixHash (const db::CellInstArray &array) + : db::IMatrix3d (array.complex_trans ()) +{ + db::Vector a, b; + unsigned long na = 0, nb = 0; + + if (array.is_regular_array (a, b, na, nb)) { + + na = std::max ((unsigned long) 1, na); + nb = std::max ((unsigned long) 1, nb); + + // compute the sum of all individual matrices + *this *= double (na * nb); + + db::DVector dab = db::DVector (a) * double ((nb * (na - 1) * na) / 2) + db::DVector (b) * double ((na * (nb - 1) * nb) / 2); + m() [0][2] += dab.x (); + m() [1][2] += dab.y (); + + } else if (array.is_iterated_array ()) { + + db::DVector dab; + double n = 0.0; + + tl_assert (! array.begin ().at_end ()); + db::DVector d0 = db::DVector ((*array.begin ()).disp ()); + for (auto i = array.begin (); ! i.at_end (); ++i) { + n += 1.0; + dab += db::DVector ((*i).disp ()) - d0; + } + + *this *= n; + + m() [0][2] += dab.x (); + m() [1][2] += dab.y (); + + } +} + +static inline size_t d2h (double d) +{ + return d < 0 ? size_t (d - 0.5) : size_t (d + 0.5); +} + +size_t +CellInstanceSetHasher::MatrixHash::hash_value () const +{ + // The "close-to-unity" elements are scaled with this value, so + // after rounding to int for the hash value we are able to + // resolve a certain level of details. This applies to the + // rotation/shear/scale submatrix elements (m11, m12, m21, m22). + const double res = 1024.0; + + size_t h = d2h (m ()[0][0] * res); + h = tl::hcombine (d2h (m ()[0][1] * res), h); + h = tl::hcombine (d2h (m ()[0][2]), h); + h = tl::hcombine (d2h (m ()[1][0] * res), h); + h = tl::hcombine (d2h (m ()[1][1] * res), h); + h = tl::hcombine (d2h (m ()[1][2]), h); + // m31 and m32 are always zero, so we don't count them here + h = tl::hcombine (d2h (m ()[2][2]), h); + return h; +} + +CellInstanceSetHasher::CellInstanceSetHasher (const db::Layout *layout, db::cell_index_type top_cell, const std::set *selection) + : mp_layout (layout), m_top_cell (top_cell), mp_selection (selection) +{ + // .. nothing yet .. +} + +size_t +CellInstanceSetHasher::instance_set_hash (db::cell_index_type for_cell) +{ + return get_hash (for_cell).hash_value (); +} + +CellInstanceSetHasher::MatrixHash +CellInstanceSetHasher::get_hash (cell_index_type for_cell) +{ + auto c = m_cache.find (for_cell); + if (c != m_cache.end ()) { + return c->second; + } else { + MatrixHash hm = get_hash_uncached (for_cell); + m_cache [for_cell] = hm; + return hm; + } +} + +CellInstanceSetHasher::MatrixHash +CellInstanceSetHasher::get_hash_uncached (cell_index_type for_cell) +{ + if (for_cell == m_top_cell) { + + return MatrixHash (); + + } else { + + const db::Cell &fc = mp_layout->cell (for_cell); + + MatrixHash hm (0.0); + for (auto pi = fc.begin_parent_insts (); ! pi.at_end (); ++pi) { + auto pci = pi->parent_cell_index (); + if (! mp_selection || mp_selection->find (pci) != mp_selection->end ()) { + hm += get_hash (pci) * MatrixHash (pi->child_inst ().cell_inst ()); + } + } + + return hm; + + } +} + +} diff --git a/src/db/db/dbCellInstanceSetHasher.h b/src/db/db/dbCellInstanceSetHasher.h new file mode 100644 index 000000000..8cc1cacf6 --- /dev/null +++ b/src/db/db/dbCellInstanceSetHasher.h @@ -0,0 +1,90 @@ + +/* + + 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_dbCellInstanceSetHasher +#define HDR_dbCellInstanceSetHasher + +#include "dbCommon.h" +#include "dbMatrix.h" +#include "dbLayout.h" + +namespace db +{ + +/** + * @brief A hasher for a set of cell instances + * + * The hasher starts with a layout, a top cell and optionally a + * set of cells selected. Only selected cells will be considered + * in the cell tree (the "cone"). + * + * The hasher allows to compute a hash value for a given cell, + * representative for the flat set of instances of that cell in + * the top cell. + */ +class DB_PUBLIC CellInstanceSetHasher +{ +public: + class MatrixHash + : public IMatrix3d + { + public: + MatrixHash (double s = 1.0); + MatrixHash (const db::ICplxTrans &trans); + MatrixHash (const db::CellInstArray &array); + + size_t hash_value () const; + }; + + /** + * @brief Creates a new cell instance set hasher + * + * @param layout The layout the hasher refers to + * @param top_cell The top cell the hasher starts with + * @param selection A set of selected cells or a null pointer if all cells should be considered + * + * The hasher will not take ownership over the layout, nor + * the selected cell set. + */ + CellInstanceSetHasher (const db::Layout *layout, db::cell_index_type top_cell, const std::set *selection = 0); + + /** + * @brief Computes the hash value representative for the flat instance set of the given cell in the top cell and the selection + */ + size_t instance_set_hash (db::cell_index_type for_cell); + +private: + const db::Layout *mp_layout; + db::cell_index_type m_top_cell; + const std::set *mp_selection; + std::map m_cache; + + MatrixHash get_hash (db::cell_index_type for_cell); + MatrixHash get_hash_uncached (db::cell_index_type for_cell); +}; + +} // namespace db + +#endif + diff --git a/src/db/db/dbCellMapping.cc b/src/db/db/dbCellMapping.cc index 7bc3dc712..d79732b4f 100644 --- a/src/db/db/dbCellMapping.cc +++ b/src/db/db/dbCellMapping.cc @@ -122,28 +122,29 @@ private: // Some utility class: a compare function for a instance set of two cells in the context // of two layouts and two initial cells. +#if 0 class InstanceSetCompareFunction { public: typedef std::multiset > trans_set_t; - InstanceSetCompareFunction (const db::Layout &layout_a, db::cell_index_type initial_cell_a, const db::Layout &layout_b, db::cell_index_type initial_cell_b) - : m_layout_a (layout_a), m_initial_cell_a (initial_cell_a), - m_layout_b (layout_b), m_initial_cell_b (initial_cell_b), + InstanceSetCompareFunction (const db::Layout &layout_a, db::cell_index_type initial_cell_a, const std::set *selection_cone_a, const db::Layout &layout_b, db::cell_index_type initial_cell_b, const std::set *selection_cone_b) + : m_layout_a (layout_a), m_initial_cell_a (initial_cell_a), m_selection_cone_a (selection_cone_a), + m_layout_b (layout_b), m_initial_cell_b (initial_cell_b), m_selection_cone_b (selection_cone_b), m_cell_a (std::numeric_limits::max ()), m_repr_set (false) { // .. } - bool compare (db::cell_index_type cell_a, const std::set &selection_cone_a, db::cell_index_type cell_b, const std::set &selection_cone_b) + bool compare (db::cell_index_type cell_a, db::cell_index_type cell_b) { if (cell_a != m_cell_a) { m_cell_a = cell_a; m_callers_a.clear (); - m_layout_a.cell (cell_a).collect_caller_cells (m_callers_a, selection_cone_a, -1); + m_layout_a.cell (cell_a).collect_caller_cells (m_callers_a, *m_selection_cone_a, -1); m_callers_a.insert (cell_a); m_trans.clear (); @@ -162,7 +163,7 @@ public: } std::set callers_b; - m_layout_b.cell (cell_b).collect_caller_cells (callers_b, selection_cone_b, -1); + m_layout_b.cell (cell_b).collect_caller_cells (callers_b, *m_selection_cone_b, -1); callers_b.insert (cell_b); trans_set_t trans (m_trans); @@ -178,8 +179,10 @@ public: private: const db::Layout &m_layout_a; db::cell_index_type m_initial_cell_a; + const std::set *m_selection_cone_a; const db::Layout &m_layout_b; db::cell_index_type m_initial_cell_b; + const std::set *m_selection_cone_b; db::cell_index_type m_cell_a; std::set m_callers_a; trans_set_t m_trans; @@ -255,6 +258,74 @@ private: } } }; +#else +class InstanceSetCompareFunction +{ +public: + typedef std::multiset > trans_set_t; + + InstanceSetCompareFunction (const db::Layout &layout_a, db::cell_index_type initial_cell_a, const std::set *selection_cone_a, const db::Layout &layout_b, db::cell_index_type initial_cell_b, const std::set *selection_cone_b) + : m_layout_a (layout_a), m_initial_cell_a (initial_cell_a), m_selection_cone_a (selection_cone_a), + m_layout_b (layout_b), m_initial_cell_b (initial_cell_b), m_selection_cone_b (selection_cone_b) + { + // .. + } + + // @@@ TODO: const method + bool compare (db::cell_index_type cell_a, db::cell_index_type cell_b) + { + return get_trans_set (m_tsa, m_layout_a, m_initial_cell_a, *m_selection_cone_a, cell_a) == get_trans_set (m_tsb, m_layout_b, m_initial_cell_b, *m_selection_cone_b, cell_b); + } + +private: + const db::Layout &m_layout_a; + db::cell_index_type m_initial_cell_a; + const std::set *m_selection_cone_a; // TODO -> ptr @@@ + const db::Layout &m_layout_b; + db::cell_index_type m_initial_cell_b; + const std::set *m_selection_cone_b; // TODO -> ptr @@@ + std::map m_tsa, m_tsb; + + const trans_set_t &get_trans_set (std::map &ts_cache, const db::Layout &layout, db::cell_index_type initial_cell, const std::set &selection, db::cell_index_type for_cell) + { + auto tsi = ts_cache.find (for_cell); + if (tsi == ts_cache.end ()) { + tsi = ts_cache.insert (std::make_pair (for_cell, trans_set_t ())).first; + get_trans_set_uncached (tsi->second, ts_cache, layout, initial_cell, selection, for_cell); + } + return tsi->second; + } + + void get_trans_set_uncached (trans_set_t &ts, std::map &ts_cache, const db::Layout &layout, db::cell_index_type initial_cell, const std::set &selection, db::cell_index_type for_cell) + { + if (for_cell == initial_cell) { + ts.insert (db::ICplxTrans ()); + return; + } + + const db::Cell &c = layout.cell (for_cell); + for (auto p = c.begin_parent_insts (); ! p.at_end (); ++p) { + + const db::CellInstArray &inst = p->child_inst ().cell_inst (); + db::cell_index_type parent_cell = p->parent_cell_index (); + + if (selection.find (inst.object ().cell_index ()) != selection.end ()) { + + const trans_set_t &pts = get_trans_set (ts_cache, layout, initial_cell, selection, parent_cell); + for (auto a = inst.begin (); ! a.at_end (); ++a) { + auto ta = inst.complex_trans (*a); + for (auto pt = pts.begin (); pt != pts.end (); ++pt) { + ts.insert (*pt * ta); + } + + } + + } + + } + } +}; +#endif // ------------------------------------------------------------------------------------- // CellMapping implementation @@ -449,7 +520,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty std::map > candidates; // key = index(a), value = indices(b) - InstanceSetCompareFunction cmp (layout_a, cell_index_a, layout_b, cell_index_b); + InstanceSetCompareFunction cmp (layout_a, cell_index_a, &cc_a.selection (), layout_b, cell_index_b, &cc_b.selection ()); std::multimap::const_iterator a = cm_a.begin (), b = cm_b.begin (); while (a != cm_a.end () && b != cm_b.end ()) { @@ -494,7 +565,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty if (bg != b_group_of_cell.end ()) { if (groups_taken.find (bg->second) == groups_taken.end ()) { - if (cmp.compare (a->second, cc_a.selection (), bb->second, cc_b.selection ())) { + if (cmp.compare (a->second, bb->second)) { candidates [a->second] = b_group [bg->second]; groups_taken.insert (bg->second); } @@ -502,7 +573,7 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty } else { - if (cmp.compare (a->second, cc_a.selection (), bb->second, cc_b.selection ())) { + if (cmp.compare (a->second, bb->second)) { candidates [a->second].push_back (bb->second); b_group_of_cell.insert (std::make_pair (bb->second, g)); b_group.insert (std::make_pair (g, std::vector ())).first->second.push_back (bb->second); diff --git a/src/db/unit_tests/dbCellInstanceSetHasherTests.cc b/src/db/unit_tests/dbCellInstanceSetHasherTests.cc new file mode 100644 index 000000000..2e0799406 --- /dev/null +++ b/src/db/unit_tests/dbCellInstanceSetHasherTests.cc @@ -0,0 +1,63 @@ + +/* + + 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 "dbCellInstanceSetHasher.h" +#include "tlUnitTest.h" + +TEST(1) +{ + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash ().to_string (), "(1,0,0) (0,1,0) (0,0,1)"); + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash (0).to_string (), "(0,0,0) (0,0,0) (0,0,0)"); + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash (db::ICplxTrans (2.0, 90.0, false, db::Vector (1, 2))).to_string (), "(0,-2,1) (2,0,2) (0,0,1)"); + + db::ICplxTrans t0 (2.0, 90.0, false, db::Vector (1, 2)); + + db::CellInstArray array (db::CellInst (0), t0, db::Vector (0, 100), db::Vector (100, 0), 2, 3); + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash (array).to_string (), "(0,-12,606) (12,0,312) (0,0,6)"); + + // emulate the regular array with an iterated array + std::vector dd; + for (unsigned int i = 0; i < 2; ++i) { + for (unsigned int j = 0; j < 3; ++j) { + db::Vector d = db::Vector (0, 100 * i) + db::Vector (100 * j, 0); + dd.push_back (d); + } + } + db::CellInstArray iter_array (db::CellInst (0), t0, dd.begin (), dd.end ()); + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash (iter_array).to_string (), db::CellInstanceSetHasher::MatrixHash (array).to_string ()); + + // equivalence of sum of matrices and computed matrix for array + db::CellInstanceSetHasher::MatrixHash hm (0.0); + for (unsigned int i = 0; i < 2; ++i) { + for (unsigned int j = 0; j < 3; ++j) { + db::Vector d = db::Vector (0, 100 * i) + db::Vector (100 * j, 0); + hm += db::CellInstanceSetHasher::MatrixHash (db::ICplxTrans (d) * t0); + } + } + EXPECT_EQ (db::CellInstanceSetHasher::MatrixHash (hm).to_string (), db::CellInstanceSetHasher::MatrixHash (array).to_string ()); +} + +TEST(2) +{ + // @@@ +} diff --git a/src/db/unit_tests/unit_tests.pro b/src/db/unit_tests/unit_tests.pro index ace029ba3..a51884427 100644 --- a/src/db/unit_tests/unit_tests.pro +++ b/src/db/unit_tests/unit_tests.pro @@ -7,6 +7,7 @@ TARGET = db_tests include($$PWD/../../lib_ut.pri) SOURCES = \ + dbCellInstanceSetHasherTests.cc \ dbCompoundOperationTests.cc \ dbEdgeNeighborhoodTests.cc \ dbFillToolTests.cc \