This commit is contained in:
Matthias Koefferlein 2026-01-26 23:55:55 +01:00
parent d326df2424
commit 94a425da0d
6 changed files with 390 additions and 9 deletions

View File

@ -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 \

View File

@ -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<db::cell_index_type> *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;
}
}
}

View File

@ -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<db::cell_index_type> *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<db::cell_index_type> *mp_selection;
std::map<db::cell_index_type, MatrixHash> m_cache;
MatrixHash get_hash (db::cell_index_type for_cell);
MatrixHash get_hash_uncached (db::cell_index_type for_cell);
};
} // namespace db
#endif

View File

@ -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<db::ICplxTrans, db::trans_less_func<db::ICplxTrans> > 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<db::cell_index_type> *selection_cone_a, const db::Layout &layout_b, db::cell_index_type initial_cell_b, const std::set<db::cell_index_type> *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<db::cell_index_type>::max ()),
m_repr_set (false)
{
// ..
}
bool compare (db::cell_index_type cell_a, const std::set<db::cell_index_type> &selection_cone_a, db::cell_index_type cell_b, const std::set<db::cell_index_type> &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<db::cell_index_type> 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<db::cell_index_type> *m_selection_cone_a;
const db::Layout &m_layout_b;
db::cell_index_type m_initial_cell_b;
const std::set<db::cell_index_type> *m_selection_cone_b;
db::cell_index_type m_cell_a;
std::set<db::cell_index_type> m_callers_a;
trans_set_t m_trans;
@ -255,6 +258,74 @@ private:
}
}
};
#else
class InstanceSetCompareFunction
{
public:
typedef std::multiset<db::ICplxTrans, db::trans_less_func<db::ICplxTrans> > trans_set_t;
InstanceSetCompareFunction (const db::Layout &layout_a, db::cell_index_type initial_cell_a, const std::set<db::cell_index_type> *selection_cone_a, const db::Layout &layout_b, db::cell_index_type initial_cell_b, const std::set<db::cell_index_type> *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<db::cell_index_type> *m_selection_cone_a; // TODO -> ptr @@@
const db::Layout &m_layout_b;
db::cell_index_type m_initial_cell_b;
const std::set<db::cell_index_type> *m_selection_cone_b; // TODO -> ptr @@@
std::map<db::cell_index_type, trans_set_t> m_tsa, m_tsb;
const trans_set_t &get_trans_set (std::map<db::cell_index_type, trans_set_t> &ts_cache, const db::Layout &layout, db::cell_index_type initial_cell, const std::set<db::cell_index_type> &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<db::cell_index_type, trans_set_t> &ts_cache, const db::Layout &layout, db::cell_index_type initial_cell, const std::set<db::cell_index_type> &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 <db::cell_index_type, std::vector<db::cell_index_type> > 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<size_t, db::cell_index_type>::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 <db::cell_index_type> ())).first->second.push_back (bb->second);

View File

@ -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<db::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)
{
// @@@
}

View File

@ -7,6 +7,7 @@ TARGET = db_tests
include($$PWD/../../lib_ut.pri)
SOURCES = \
dbCellInstanceSetHasherTests.cc \
dbCompoundOperationTests.cc \
dbEdgeNeighborhoodTests.cc \
dbFillToolTests.cc \