Merge pull request #2266 from KLayout/bugfix/issue-2262

Bugfix/issue 2262
This commit is contained in:
Matthias Köfferlein 2026-02-07 09:18:03 +01:00 committed by GitHub
commit 3f9d8906f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 898 additions and 114 deletions

View File

@ -29,11 +29,13 @@
#include "dbSaveLayoutOptions.h"
#include "dbRegion.h"
#include "dbDeepShapeStore.h"
#include "dbCellGraphUtils.h"
#include "gsiExpression.h"
#include "tlCommandLineParser.h"
#include "tlThreads.h"
#include "tlThreadedWorkers.h"
#include "tlTimer.h"
#include "tlOptional.h"
namespace {
@ -271,17 +273,19 @@ HealingTileLayoutOutputReceiver::output (const db::Box &box)
struct ResultDescriptor
{
ResultDescriptor ()
: shape_count (0), layer_a (-1), layer_b (-1), layer_output (-1), layout (0), top_cell (0)
: shape_count (0), flat_shape_count (0), layer_a (-1), layer_b (-1), layer_output (-1), layout (0), top_cell (0)
{
// .. nothing yet ..
}
size_t shape_count;
size_t flat_shape_count;
int layer_a;
int layer_b;
int layer_output;
db::Layout *layout;
db::cell_index_type top_cell;
tl::optional<db::Region> results;
size_t count () const
{
@ -296,6 +300,20 @@ struct ResultDescriptor
}
}
size_t flat_count () const
{
if (layout && layer_output >= 0) {
size_t res = 0;
db::CellCounter counter (layout, top_cell);
for (db::Layout::const_iterator c = layout->begin (); c != layout->end (); ++c) {
res += c->shapes (layer_output).size () * counter.weight (c->cell_index ());
}
return res;
} else {
return flat_shape_count;
}
}
bool is_empty () const
{
if (layout && layer_output >= 0) {
@ -586,12 +604,8 @@ BD_PUBLIC int strmxor (int argc, char *argv[])
const char *line_format = " %-10s %-12s %s";
std::string headline;
if (deep) {
headline = tl::sprintf (line_format, tl::to_string (tr ("Layer")), tl::to_string (tr ("Output")), tl::to_string (tr ("Differences (hierarchical shape count)")));
} else {
headline = tl::sprintf (line_format, tl::to_string (tr ("Layer")), tl::to_string (tr ("Output")), tl::to_string (tr ("Differences (shape count)")));
}
std::string headline = tl::sprintf (line_format, tl::to_string (tr ("Layer")), tl::to_string (tr ("Output")),
deep ? tl::to_string (tr ("Differences (hierarchical/flat count)")) : tl::to_string (tr ("Differences (shape count)")));
const char *sep = " ----------------------------------------------------------------";
@ -619,7 +633,11 @@ BD_PUBLIC int strmxor (int argc, char *argv[])
if (r->second.layer_output >= 0 && r->second.layout) {
out = r->second.layout->get_properties (r->second.layer_output).to_string ();
}
value = tl::to_string (r->second.count ());
if (deep) {
value = tl::sprintf (tl::to_string (tr ("%-6lu / %-6lu")), r->second.count (), r->second.flat_count ());
} else {
value = tl::to_string (r->second.count ());
}
}
if (! value.empty ()) {
tl::info << tl::sprintf (line_format, r->first.second.to_string (), out, value);
@ -767,8 +785,15 @@ bool run_tiled_xor (const XORData &xor_data)
proc.execute ("Running XOR");
}
// no stored results currently
for (std::map<std::pair<int, db::LayerProperties>, ResultDescriptor>::const_iterator r = xor_data.results->begin (); r != xor_data.results->end (); ++r) {
tl_assert (! r->second.results.has_value ());
}
// Determines the output status
for (std::map<std::pair<int, db::LayerProperties>, ResultDescriptor>::const_iterator r = xor_data.results->begin (); r != xor_data.results->end () && result; ++r) {
// no stored results currently
tl_assert (! r->second.results.has_value ());
result = r->second.is_empty ();
}
@ -846,29 +871,40 @@ public:
tl::SelfTimer timer (tl::verbosity () >= 11, "XOR on layer " + m_layer_props.to_string ());
db::RecursiveShapeIterator ri_a, ri_b;
if (m_la >= 0) {
ri_a = db::RecursiveShapeIterator (*mp_xor_data->layout_a, mp_xor_data->layout_a->cell (mp_xor_data->cell_a), m_la);
} else {
ri_a = db::RecursiveShapeIterator (*mp_xor_data->layout_a, mp_xor_data->layout_a->cell (mp_xor_data->cell_a), std::vector<unsigned int> ());
}
ri_a.set_for_merged_input (true);
if (m_lb >= 0) {
ri_b = db::RecursiveShapeIterator (*mp_xor_data->layout_b, mp_xor_data->layout_b->cell (mp_xor_data->cell_b), m_lb);
} else {
ri_b = db::RecursiveShapeIterator (*mp_xor_data->layout_b, mp_xor_data->layout_b->cell (mp_xor_data->cell_b), std::vector<unsigned int> ());
}
ri_b.set_for_merged_input (true);
db::Region in_a (ri_a, worker->dss (), db::ICplxTrans (mp_xor_data->layout_a->dbu () / m_dbu));
db::Region in_b (ri_b, worker->dss (), db::ICplxTrans (mp_xor_data->layout_b->dbu () / m_dbu));
db::Region xor_res;
{
tl::SelfTimer timer (tl::verbosity () >= 21, "Basic XOR on layer " + m_layer_props.to_string ());
xor_res = in_a ^ in_b;
if (m_la < 0) {
tl_assert (m_lb >= 0);
db::RecursiveShapeIterator ri_b (*mp_xor_data->layout_b, mp_xor_data->layout_b->cell (mp_xor_data->cell_b), m_lb);
xor_res = db::Region (ri_b, worker->dss (), db::ICplxTrans (mp_xor_data->layout_b->dbu () / m_dbu));
} else if (m_lb < 0) {
db::RecursiveShapeIterator ri_a (*mp_xor_data->layout_a, mp_xor_data->layout_a->cell (mp_xor_data->cell_a), m_la);
xor_res = db::Region (ri_a, worker->dss (), db::ICplxTrans (mp_xor_data->layout_a->dbu () / m_dbu));
} else {
db::RecursiveShapeIterator ri_a (*mp_xor_data->layout_a, mp_xor_data->layout_a->cell (mp_xor_data->cell_a), m_la);
db::RecursiveShapeIterator ri_b (*mp_xor_data->layout_b, mp_xor_data->layout_b->cell (mp_xor_data->cell_b), m_lb);
db::Region in_a (ri_a, worker->dss (), db::ICplxTrans (mp_xor_data->layout_a->dbu () / m_dbu));
db::Region in_b (ri_b, worker->dss (), db::ICplxTrans (mp_xor_data->layout_b->dbu () / m_dbu));
bool a_empty = in_a.empty ();
bool b_empty = in_b.empty ();
if (a_empty && ! b_empty) {
xor_res = in_b;
} else if (! a_empty && b_empty) {
xor_res = in_a;
} else if (! a_empty && ! b_empty) {
tl::SelfTimer timer (tl::verbosity () >= 21, "Basic XOR on layer " + m_layer_props.to_string ());
xor_res = in_a ^ in_b;
}
}
int tol_index = 0;
@ -896,9 +932,12 @@ public:
if (mp_xor_data->output_layout) {
result.layer_output = result.layout->insert_layer (lp);
xor_res.insert_into (mp_xor_data->output_layout, mp_xor_data->output_cell, result.layer_output);
if (! xor_res.empty ()) {
result.results = xor_res;
}
} else {
result.shape_count = xor_res.hier_count ();
result.flat_shape_count = xor_res.count ();
}
}
@ -964,6 +1003,22 @@ bool run_deep_xor (const XORData &xor_data)
job.start ();
job.wait ();
// Deliver the outputs
// NOTE: this is done single-threaded and in a delayed fashion as it is not efficient during
// computation and shifting hierarchy of the working layout
if (xor_data.output_layout) {
tl::SelfTimer timer (tl::verbosity () >= 11, "Result delivery");
for (std::map<std::pair<int, db::LayerProperties>, ResultDescriptor>::const_iterator r = xor_data.results->begin (); r != xor_data.results->end (); ++r) {
if (r->second.results.has_value ()) {
r->second.results.value ().insert_into (xor_data.output_layout, xor_data.output_cell, r->second.layer_output);
}
}
}
// Determine the output status
bool result = (xor_data.layers_missing == 0);

View File

@ -146,11 +146,11 @@ TEST(1A_Deep)
"Layer 10/0 is not present in first layout, but in second\n"
"Result summary (layers without differences are not shown):\n"
"\n"
" Layer Output Differences (hierarchical shape count)\n"
" Layer Output Differences (hierarchical/flat count)\n"
" ----------------------------------------------------------------\n"
" 3/0 3/0 3\n"
" 6/0 6/0 314\n"
" 8/1 8/1 1\n"
" 3/0 3/0 3 / 30 \n"
" 6/0 6/0 314 / 314 \n"
" 8/1 8/1 1 / 1 \n"
" 10/0 - (no such layer in first layout)\n"
"\n"
);
@ -188,11 +188,11 @@ TEST(1A_DeepNoEmptyCells)
"Layer 10/0 is not present in first layout, but in second\n"
"Result summary (layers without differences are not shown):\n"
"\n"
" Layer Output Differences (hierarchical shape count)\n"
" Layer Output Differences (hierarchical/flat count)\n"
" ----------------------------------------------------------------\n"
" 3/0 3/0 3\n"
" 6/0 6/0 314\n"
" 8/1 8/1 1\n"
" 3/0 3/0 3 / 30 \n"
" 6/0 6/0 314 / 314 \n"
" 8/1 8/1 1 / 1 \n"
" 10/0 - (no such layer in first layout)\n"
"\n"
);
@ -248,11 +248,11 @@ TEST(1B_Deep)
"Layer 10/0 is not present in first layout, but in second\n"
"Result summary (layers without differences are not shown):\n"
"\n"
" Layer Output Differences (hierarchical shape count)\n"
" Layer Output Differences (hierarchical/flat count)\n"
" ----------------------------------------------------------------\n"
" 3/0 - 3\n"
" 6/0 - 314\n"
" 8/1 - 1\n"
" 3/0 - 3 / 30 \n"
" 6/0 - 314 / 314 \n"
" 8/1 - 1 / 1 \n"
" 10/0 - (no such layer in first layout)\n"
"\n"
);
@ -830,10 +830,10 @@ TEST(7_OptimizeDeep)
EXPECT_EQ (cap.captured_text (),
"Result summary (layers without differences are not shown):\n"
"\n"
" Layer Output Differences (hierarchical shape count)\n"
" Layer Output Differences (hierarchical/flat count)\n"
" ----------------------------------------------------------------\n"
" 2/0 2/0 1\n"
" 3/0 3/0 8\n"
" 2/0 2/0 1 / 12 \n"
" 3/0 3/0 8 / 8 \n"
"\n"
);
}

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

@ -981,9 +981,10 @@ public:
* Only after sorting the query iterators are available.
* Sorting complexity is approx O(N*log(N)).
*/
void sort (const BoxConv &conv)
template <class BC>
void sort (const BC &conv)
{
typename BoxConv::complexity complexity_tag;
typename BC::complexity complexity_tag;
sort (conv, complexity_tag);
}
@ -1192,7 +1193,8 @@ private:
box_tree_node *mp_root;
/// Sort implementation for simple bboxes - no caching
void sort (const BoxConv &conv, const db::simple_bbox_tag &/*complexity*/)
template <class BC>
void sort (const BC &conv, const db::simple_bbox_tag &/*complexity*/)
{
m_elements.clear ();
m_elements.reserve (m_objects.size ());
@ -1204,7 +1206,7 @@ private:
if (! m_objects.empty ()) {
box_tree_picker_type picker (conv);
box_tree_picker<Box, Obj, BC, obj_vector_type> picker (conv);
box_type bbox;
for (typename obj_vector_type::const_iterator o = m_objects.begin (); o != m_objects.end (); ++o) {
@ -1221,7 +1223,8 @@ private:
}
/// Sort implementation for complex bboxes - with caching
void sort (const box_conv_type &conv, const db::complex_bbox_tag &/*complexity*/)
template <class BC>
void sort (const BC &conv, const db::complex_bbox_tag &/*complexity*/)
{
m_elements.clear ();
m_elements.reserve (m_objects.size ());
@ -1233,7 +1236,7 @@ private:
if (! m_objects.empty ()) {
box_tree_cached_picker<object_type, box_type, box_conv_type, obj_vector_type> picker (conv, m_objects.begin (), m_objects.end ());
box_tree_cached_picker<object_type, box_type, BC, obj_vector_type> picker (conv, m_objects.begin (), m_objects.end ());
for (typename obj_vector_type::const_iterator o = m_objects.begin (); o != m_objects.end (); ++o) {
m_elements.push_back (o.index ());
@ -1997,9 +2000,10 @@ public:
* Only after sorting the query iterators are available.
* Sorting complexity is approx O(N*log(N)).
*/
void sort (const BoxConv &conv)
template <class BC>
void sort (const BC &conv)
{
typename BoxConv::complexity complexity_tag;
typename BC::complexity complexity_tag;
sort (conv, complexity_tag);
}
@ -2159,13 +2163,14 @@ private:
box_tree_node *mp_root;
/// Sort implementation for simple bboxes - no caching
void sort (const BoxConv &conv, const db::simple_bbox_tag &/*complexity*/)
template <class BC>
void sort (const BC &conv, const db::simple_bbox_tag &/*complexity*/)
{
if (m_objects.empty ()) {
return;
}
box_tree_picker_type picker (conv);
box_tree_picker<box_type, object_type, BC, obj_vector_type> picker (conv);
if (mp_root) {
delete mp_root;
@ -2184,13 +2189,14 @@ private:
}
/// Sort implementation for complex bboxes - with caching
void sort (const box_conv_type &conv, const db::complex_bbox_tag &/*complexity*/)
template <class BC>
void sort (const BC &conv, const db::complex_bbox_tag &/*complexity*/)
{
if (m_objects.empty ()) {
return;
}
box_tree_cached_picker<object_type, box_type, box_conv_type, obj_vector_type> picker (conv, m_objects.begin (), m_objects.end ());
box_tree_cached_picker<object_type, box_type, BC, obj_vector_type> picker (conv, m_objects.begin (), m_objects.end ());
if (mp_root) {
delete mp_root;

View File

@ -274,11 +274,67 @@ Cell::is_shape_bbox_dirty () const
return false;
}
namespace
{
/**
* @brief An alternative box converter for CellInst which does not call layout.update
*
* Using this converter in cell.update_bbox() prevents recursive calls to layout.update.
*/
class InternalCellInstBoxConverter
{
public:
typedef db::Cell::box_type box_type;
typedef db::simple_bbox_tag complexity;
InternalCellInstBoxConverter (const db::Layout *layout)
: mp_layout (layout)
{
// .. nothing yet ..
}
box_type operator() (const db::CellInst &cell_inst) const
{
return mp_layout->cell (cell_inst.cell_index ()).bbox_with_empty_no_update ();
}
private:
const db::Layout *mp_layout;
};
/**
* @brief An alternative box converter for CellInst and layer which does not call layout.update
*
* Using this converter in cell.update_bbox() prevents recursive calls to layout.update.
*/
class InternalPerLayerCellInstBoxConverter
{
public:
typedef db::Cell::box_type box_type;
typedef db::simple_bbox_tag complexity;
InternalPerLayerCellInstBoxConverter (const db::Layout *layout, unsigned int layer)
: mp_layout (layout), m_layer (layer)
{
// .. nothing yet ..
}
box_type operator() (const db::CellInst &cell_inst) const
{
return mp_layout->cell (cell_inst.cell_index ()).bbox_no_update (m_layer);
}
private:
const db::Layout *mp_layout;
unsigned int m_layer;
};
}
bool
Cell::update_bbox (unsigned int layers)
{
unsigned int l;
// determine the bounding box
box_type org_bbox = m_bbox;
m_bbox = box_type ();
@ -308,10 +364,10 @@ Cell::update_bbox (unsigned int layers)
++o;
}
for (l = 0; l < layers; ++l) {
for (unsigned int l = 0; l < layers; ++l) {
// the per-layer bounding boxes
db::box_convert <cell_inst_type> bc (*mp_layout, l);
InternalPerLayerCellInstBoxConverter bc (mp_layout, l);
box_type lbox = o1_inst->bbox_from_raw_bbox (raw_box, bc);
if (! lbox.empty ()) {
@ -326,7 +382,7 @@ Cell::update_bbox (unsigned int layers)
}
db::box_convert <cell_inst_type, false> bc_we (*mp_layout);
InternalCellInstBoxConverter bc_we (mp_layout);
m_bbox_with_empty += o1_inst->bbox_from_raw_bbox (raw_box, bc_we);
}
@ -486,6 +542,17 @@ Cell::bbox (unsigned int l) const
}
}
const Cell::box_type &
Cell::bbox_no_update (unsigned int l) const
{
box_map::const_iterator b = m_bboxes.find (l);
if (b != m_bboxes.end ()) {
return b->second;
} else {
return ms_empty_box;
}
}
Cell::const_iterator
Cell::begin () const
{
@ -725,7 +792,7 @@ Cell::count_hier_levels () const
{
unsigned int l = 0;
for (const_iterator c = begin (); !c.at_end (); ++c) {
for (const_iterator c = m_instances.begin (); !c.at_end (); ++c) {
l = std::max (l, (unsigned int) mp_layout->cell (c->cell_index ()).m_hier_levels + 1);
}

View File

@ -522,21 +522,6 @@ public:
*/
bool is_shape_bbox_dirty () const;
/**
* @brief Updates the bbox
*
* This will update the bbox from the shapes and instances.
* This requires the bboxes of the child cells to be computed
* before. Practically this will be done by computing the
* bboxes bottom-up in the hierarchy.
* In addition, the number of hierarchy levels below is also
* updated.
*
* @param layers The max. number of layers in the child cells
* @return true, if the bounding box has changed.
*/
bool update_bbox (unsigned int layers);
/**
* @brief Sorts the shapes lists
*
@ -1110,6 +1095,23 @@ public:
*/
void move_shapes (db::Cell &source_cell, const db::LayerMapping &layer_mapping);
/**
* @brief Gets the current bounding box (with empty) without calling Layout::update
*
* This method is intended for internal purposes only.
*/
const box_type &bbox_with_empty_no_update () const
{
return m_bbox_with_empty;
}
/**
* @brief Gets the current per-layer bounding box without calling Layout::update
*
* This method is intended for internal purposes only.
*/
const box_type &bbox_no_update (unsigned int l) const;
protected:
/**
* @brief Standard constructor: create an empty cell object
@ -1135,6 +1137,7 @@ protected:
virtual Cell *clone (db::Layout &layout) const;
private:
friend class db::Layout;
cell_index_type m_cell_index;
mutable db::Layout *mp_layout;
shapes_map m_shapes_map;
@ -1170,9 +1173,9 @@ private:
}
/**
* @brief Return a reference to the instances object
* @brief Return a reference to the instances object
*/
instances_type &instances ()
const instances_type &instances () const
{
return m_instances;
}
@ -1180,7 +1183,7 @@ private:
/**
* @brief Return a reference to the instances object
*/
const instances_type &instances () const
instances_type &instances ()
{
return m_instances;
}
@ -1219,6 +1222,21 @@ private:
* @param force Force sorting, even if not strictly needed
*/
void sort_inst_tree (bool force);
/**
* @brief Updates the bbox
*
* This will update the bbox from the shapes and instances.
* This requires the bboxes of the child cells to be computed
* before. Practically this will be done by computing the
* bboxes bottom-up in the hierarchy.
* In addition, the number of hierarchy levels below is also
* updated.
*
* @param layers The max. number of layers in the child cells
* @return true, if the bounding box has changed.
*/
bool update_bbox (unsigned int layers);
};
/**

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 DB_PUBLIC 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

@ -25,6 +25,8 @@
#include "dbCellGraphUtils.h"
#include "dbCellMapping.h"
#include "dbLayoutUtils.h"
#include "dbCellInstanceSetHasher.h"
#include "dbHash.h"
#include "tlLog.h"
#include "tlTimer.h"
@ -122,28 +124,30 @@ private:
// Some utility class: a compare function for a instance set of two cells in the context
// of two layouts and two initial cells.
class InstanceSetCompareFunction
#if 1
class InstanceSetCompareFunction
{
public:
typedef std::multiset<db::ICplxTrans, db::trans_less_func<db::ICplxTrans> > trans_set_t;
typedef std::unordered_multiset<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 +166,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);
@ -175,11 +179,18 @@ public:
return trans.empty ();
}
void make_endpoint (db::cell_index_type /*cell_a*/, db::cell_index_type /*cell_b*/)
{
// not supported by this version
}
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;
@ -256,6 +267,146 @@ private:
}
};
#else
/*
* An alternative implementation of the InstanceSetCompareFunction
*
* This allows setting "endpoints" which are basically known cell identities
* and allow shortcutting the instance set comparison. They act as additional
* top cells.
*
* This implementation has a potential for higher performance, but
* in general it requires somewhat more memory and is not fully optimized yet.
*/
namespace {
struct InstanceSetCompareFunctionHash
{
std::size_t operator () (const std::pair<unsigned int, db::ICplxTrans> &p) const
{
return tl::hcombine (p.first, tl::hfunc (p.second));
}
};
}
class InstanceSetCompareFunction
{
public:
typedef std::unordered_multiset<std::pair<unsigned int, db::ICplxTrans>, InstanceSetCompareFunctionHash> 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_selection_cone_a (selection_cone_a),
m_layout_b (layout_b), m_selection_cone_b (selection_cone_b),
m_cell_a (std::numeric_limits<db::cell_index_type>::max ()),
m_ep_index (0)
{
m_endpoints_a.insert (std::make_pair (initial_cell_a, m_ep_index));
m_endpoints_b.insert (std::make_pair (initial_cell_b, m_ep_index));
++m_ep_index;
}
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_trans.clear ();
collect_or_compare_trans_set (true, m_trans, m_endpoints_a, m_layout_a, *m_selection_cone_a, m_cell_a, db::ICplxTrans ());
}
trans_set_t trans (m_trans);
double mag = m_layout_b.dbu () / m_layout_a.dbu ();
if (! collect_or_compare_trans_set (false, trans, m_endpoints_b, m_layout_b, *m_selection_cone_b, cell_b, db::ICplxTrans (mag))) {
return false;
} else {
return trans.empty ();
}
}
void make_endpoint (db::cell_index_type cell_a, db::cell_index_type cell_b)
{
m_cell_a = std::numeric_limits<db::cell_index_type>::max ();
m_trans.clear ();
m_endpoints_a.insert (std::make_pair (cell_a, m_ep_index));
m_endpoints_b.insert (std::make_pair (cell_b, m_ep_index));
++m_ep_index;
}
private:
const db::Layout &m_layout_a;
const std::set<db::cell_index_type> *m_selection_cone_a;
const db::Layout &m_layout_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;
std::map<db::cell_index_type, unsigned int> m_endpoints_a, m_endpoints_b;
unsigned int m_ep_index;
bool collect_or_compare_trans_set (bool collect,
trans_set_t &ts,
const std::map<db::cell_index_type, unsigned int> &endpoints,
const db::Layout &layout,
const std::set<db::cell_index_type> &selection,
db::cell_index_type for_cell,
const db::ICplxTrans &child_trans)
{
auto ep = endpoints.find (for_cell);
if (ep != endpoints.end ()) {
auto key = std::make_pair (ep->second, child_trans);
if (collect) {
ts.insert (key);
return true;
} else {
auto i = ts.find (key);
if (i == ts.end () || *i != key) {
return false;
} else {
ts.erase (i);
return true;
}
}
}
const db::Cell &fc = layout.cell (for_cell);
std::set<db::cell_index_type> selected_parents;
for (auto p = fc.begin_parent_cells (); p != fc.end_parent_cells (); ++p) {
if (selection.find (*p) != selection.end ()) {
selected_parents.insert (*p);
}
}
for (auto p = fc.begin_parent_insts (); ! p.at_end (); ++p) {
db::cell_index_type parent_cell = p->parent_cell_index ();
if (selected_parents.find (parent_cell) != selected_parents.end ()) {
const db::CellInstArray &inst = *p->basic_child_inst ();
for (auto a = inst.begin (); ! a.at_end (); ++a) {
auto ta = inst.complex_trans (*a);
if (! collect_or_compare_trans_set (collect, ts, endpoints, layout, selection, parent_cell, ta * child_trans)) {
return false;
}
}
}
}
return true;
}
};
#endif
// -------------------------------------------------------------------------------------
// CellMapping implementation
@ -438,18 +589,18 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty
db::CellCounter cc_b (&layout_b, cell_index_b);
std::multimap<size_t, db::cell_index_type> cm_b;
for (db::CellCounter::selection_iterator c = cc_b.begin (); c != cc_b.end (); ++c) {
for (auto c = cc_b.begin (); c != cc_b.end (); ++c) {
cm_b.insert (std::make_pair (*c == cell_index_b ? 0 : cc_b.weight (*c), *c));
}
std::multimap<size_t, db::cell_index_type> cm_a;
for (db::CellCounter::selection_iterator c = cc_a.begin (); c != cc_a.end (); ++c) {
for (auto c = cc_a.begin (); c != cc_a.end (); ++c) {
cm_a.insert (std::make_pair (*c == cell_index_a ? 0 : cc_a.weight (*c), *c));
}
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 ()) {
@ -480,10 +631,11 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty
unsigned int g = 0;
std::map <unsigned int, std::vector <db::cell_index_type> > b_group;
std::map <db::cell_index_type, unsigned int> b_group_of_cell;
std::map <db::cell_index_type, std::vector<db::cell_index_type> > new_candidates;
while (a != cm_a.end () && a->first == w) {
candidates.insert (std::make_pair (a->second, std::vector <db::cell_index_type> ()));
new_candidates.insert (std::make_pair (a->second, std::vector <db::cell_index_type> ()));
std::set <unsigned int> groups_taken;
@ -494,16 +646,16 @@ 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 ())) {
candidates [a->second] = b_group [bg->second];
if (cmp.compare (a->second, bb->second)) {
new_candidates [a->second] = b_group [bg->second];
groups_taken.insert (bg->second);
}
}
} else {
if (cmp.compare (a->second, cc_a.selection (), bb->second, cc_b.selection ())) {
candidates [a->second].push_back (bb->second);
if (cmp.compare (a->second, bb->second)) {
new_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);
}
@ -527,6 +679,15 @@ CellMapping::create_from_geometry (const db::Layout &layout_a, db::cell_index_ty
++b;
}
for (auto c = new_candidates.begin (); c != new_candidates.end (); ++c) {
if (c->second.size () == 1) {
// a single candidate: establish as new endpoint for the comparer
cmp.make_endpoint (c->first, c->second.front ());
}
}
candidates.insert (new_candidates.begin (), new_candidates.end ());
}
}

View File

@ -390,10 +390,12 @@ HierarchyBuilder::new_inst (const RecursiveShapeIterator *iter, const db::CellIn
if (all) {
CellMapKey key (inst.object ().cell_index (), iter->is_child_inactive (inst.object ().cell_index ()), std::set<db::Box> ());
// NOTE: this will set m_cm_new_entry
db::cell_index_type new_cell = make_cell_variant (key, iter->layout ()->cell_name (inst.object ().cell_index ()));
// for new cells, create this instance
if (m_cell_stack.back ().first) {
if (m_cell_stack.back ().first || m_cm_new_entry) {
db::CellInstArray new_inst (inst, &mp_target->array_repository ());
new_inst.object () = db::CellInst (new_cell);
new_inst.transform (always_apply);
@ -432,10 +434,12 @@ HierarchyBuilder::new_inst_member (const RecursiveShapeIterator *iter, const db:
}
CellMapKey key (inst.object ().cell_index (), iter->is_child_inactive (inst_cell), clip_variant.second);
// NOTE: this will set m_cm_new_entry
db::cell_index_type new_cell = make_cell_variant (key, iter->layout ()->cell_name (inst_cell));
// for a new cell, create this instance
if (m_cell_stack.back ().first) {
if (m_cell_stack.back ().first || m_cm_new_entry) {
db::CellInstArray new_inst (db::CellInst (new_cell), always_apply * trans);
new_inst.transform_into (m_trans);
for (std::vector<db::Cell *>::const_iterator c = m_cell_stack.back ().second.begin (); c != m_cell_stack.back ().second.end (); ++c) {

View File

@ -1524,6 +1524,84 @@ Instances::sort_child_insts (bool force)
std::sort (m_insts_by_cell_index.begin (), m_insts_by_cell_index.end (), cell_inst_compare_f<basic_inst_type> ());
}
namespace {
/**
* @brief An alternative box converter for CellInst which does not call layout.update
*
* Using this converter in cell.update_bbox() prevents recursive calls to layout.update.
*/
class InternalCellInstBoxConverter
{
public:
typedef db::Cell::box_type box_type;
typedef db::simple_bbox_tag complexity;
InternalCellInstBoxConverter (const db::Layout *layout)
: mp_layout (layout)
{
// .. nothing yet ..
}
db::Cell::box_type operator() (const db::Cell::cell_inst_type &cell_inst) const
{
return mp_layout->cell (cell_inst.cell_index ()).bbox_with_empty_no_update ();
}
private:
const db::Layout *mp_layout;
};
/**
* @brief An alternative box converter for CellInstArray with properties which does not call layout.update
*
* Using this converter in cell.update_bbox() prevents recursive calls to layout.update.
*/
struct InternalCellInstArrayWithPropertiesBoxConverter
{
typedef db::Cell::cell_inst_array_type cell_inst_array;
typedef db::Cell::box_type box_type;
typedef db::complex_bbox_tag complexity;
InternalCellInstArrayWithPropertiesBoxConverter (const db::Layout *layout)
: m_bc (layout)
{ }
box_type operator() (const db::object_with_properties<cell_inst_array> &array) const
{
return array.bbox (m_bc);
}
private:
InternalCellInstBoxConverter m_bc;
};
/**
* @brief An alternative box converter for CellInstArray which does not call layout.update
*
* Using this converter in cell.update_bbox() prevents recursive calls to layout.update.
*/
struct InternalCellInstArrayBoxConverter
{
typedef db::Cell::cell_inst_array_type cell_inst_array;
typedef db::Cell::box_type box_type;
typedef db::complex_bbox_tag complexity;
InternalCellInstArrayBoxConverter (const db::Layout *layout)
: m_bc (layout)
{ }
box_type operator() (const cell_inst_array &array) const
{
return array.bbox (m_bc);
}
private:
InternalCellInstBoxConverter m_bc;
};
}
void
Instances::sort_inst_tree (const Layout *g, bool force)
{
@ -1534,18 +1612,18 @@ Instances::sort_inst_tree (const Layout *g, bool force)
if (m_generic.any) {
if (is_editable ()) {
m_generic.stable_tree->sort (cell_inst_array_box_converter (*g));
m_generic.stable_tree->sort (InternalCellInstArrayBoxConverter (g));
} else {
m_generic.unstable_tree->sort (cell_inst_array_box_converter (*g));
m_generic.unstable_tree->sort (InternalCellInstArrayBoxConverter (g));
// since we use unstable instance trees in non-editable mode, we need to resort the child instances in this case
sort_child_insts (true);
}
}
if (m_generic_wp.any) {
if (is_editable ()) {
m_generic_wp.stable_tree->sort (cell_inst_wp_array_box_converter (*g));
m_generic_wp.stable_tree->sort (InternalCellInstArrayWithPropertiesBoxConverter (g));
} else {
m_generic_wp.unstable_tree->sort (cell_inst_wp_array_box_converter (*g));
m_generic_wp.unstable_tree->sort (InternalCellInstArrayWithPropertiesBoxConverter (g));
// since we use unstable instance trees in non-editable mode, we need to resort the child instances in this case
sort_child_insts (true);
}

View File

@ -97,7 +97,7 @@ public:
/**
* @brief Add a layer mapping
*
* @param layer_b The index of the layer in layout_a (the source of the mapping)
* @param layer_b The index of the layer in layout_b (the source of the mapping)
* @param layer_a The index of the layer in layout_a (the target of the mapping)
*/
void map (unsigned int layer_b, unsigned int layer_a)

View File

@ -1532,6 +1532,8 @@ Layout::rename_cell (cell_index_type id, const char *name)
bool
Layout::topological_sort ()
{
// NOTE: using cell.instances methods instead of the corresponding cell methods avoids a recursive update call
m_top_cells = 0;
m_top_down_list.clear ();
@ -1559,7 +1561,7 @@ Layout::topological_sort ()
// child cells.
for (const_iterator c = begin (); c != end (); ++c) {
if (c->parent_cells () == num_parents [c->cell_index ()]) {
if (c->instances ().parent_cells () == num_parents [c->cell_index ()]) {
m_top_down_list.push_back (c->cell_index ());
num_parents [c->cell_index ()] = std::numeric_limits<cell_index_type>::max ();
}
@ -1568,7 +1570,7 @@ Layout::topological_sort ()
// For all these a cells, increment the reported parent instance
// count in all the child cells.
for (cell_index_vector::const_iterator ii = m_top_down_list.begin () + n_top_down_cells; ii != m_top_down_list.end (); ++ii) {
for (cell_type::child_cell_iterator cc = cell (*ii).begin_child_cells (); ! cc.at_end (); ++cc) {
for (cell_type::child_cell_iterator cc = cell (*ii).instances ().begin_child_cells (); ! cc.at_end (); ++cc) {
tl_assert (num_parents [*cc] != std::numeric_limits<cell_index_type>::max ());
num_parents [*cc] += 1;
}
@ -1583,7 +1585,7 @@ Layout::topological_sort ()
}
// Determine the number of top cells
for (top_down_iterator e = m_top_down_list.begin (); e != m_top_down_list.end () && cell (*e).is_top (); ++e) {
for (top_down_iterator e = m_top_down_list.begin (); e != m_top_down_list.end () && cell (*e).instances ().is_top (); ++e) {
++m_top_cells;
}
@ -1815,6 +1817,8 @@ Layout::force_update_no_lock () const
void
Layout::update () const
{
tl::MutexLocker locker (&lock ());
// NOTE: the assumption is that either one thread is writing or
// multiple threads are reading. Hence, we do not need to lock hier_dirty() or bboxes_dirty().
// We still do double checking as another thread might do the update as well.
@ -1822,8 +1826,6 @@ Layout::update () const
return;
}
tl::MutexLocker locker (&lock ());
if (! under_construction ()) {
force_update_no_lock ();
}
@ -1886,13 +1888,14 @@ Layout::do_update ()
unsigned int layers = 0;
pr->set (0);
pr->set_desc (tl::to_string (tr ("Updating bounding boxes")));
for (bottom_up_iterator c = begin_bottom_up (); c != end_bottom_up (); ++c) {
for (bottom_up_iterator c = m_top_down_list.rbegin (); c != m_top_down_list.rend (); ++c) {
++*pr;
cell_type &cp (cell (*c));
if (cp.is_shape_bbox_dirty () || dirty_parents.find (*c) != dirty_parents.end ()) {
if (cp.update_bbox (layers)) {
// the bounding box has changed - need to insert parents into "dirty parents" list
for (cell_type::parent_cell_iterator p = cp.begin_parent_cells (); p != cp.end_parent_cells (); ++p) {
// NOTE: using "instances" instead of the cell directly avoids a recursive update call
for (cell_type::parent_cell_iterator p = cp.instances ().begin_parent_cells (); p != cp.instances ().end_parent_cells (); ++p) {
dirty_parents.insert (*p);
}
}
@ -1907,7 +1910,7 @@ Layout::do_update ()
tl::SelfTimer timer (tl::verbosity () > layout_base_verbosity + 10, "Sorting shapes");
pr->set (0);
pr->set_desc (tl::to_string (tr ("Sorting shapes")));
for (bottom_up_iterator c = begin_bottom_up (); c != end_bottom_up (); ++c) {
for (bottom_up_iterator c = m_top_down_list.rbegin (); c != m_top_down_list.rend (); ++c) {
++*pr;
cell_type &cp (cell (*c));
cp.sort_shapes ();
@ -1922,7 +1925,7 @@ Layout::do_update ()
size_t layers = 0;
pr->set (0);
pr->set_desc (tl::to_string (tr ("Sorting instances")));
for (bottom_up_iterator c = begin_bottom_up (); c != end_bottom_up (); ++c) {
for (bottom_up_iterator c = m_top_down_list.rbegin (); c != m_top_down_list.rend (); ++c) {
++*pr;
cell_type &cp (cell (*c));
bool force_sort_inst_tree = dirty_parents.find (*c) != dirty_parents.end ();

View File

@ -0,0 +1,145 @@
/*
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)
{
db::Layout ly;
db::cell_index_type top = ly.add_cell ("TOP");
db::cell_index_type c1 = ly.add_cell ("C1");
db::cell_index_type c2 = ly.add_cell ("C2");
db::cell_index_type c3 = ly.add_cell ("C3");
db::cell_index_type c4a = ly.add_cell ("C4A");
db::cell_index_type c5a = ly.add_cell ("C5A");
db::cell_index_type c4b = ly.add_cell ("C4B");
db::cell_index_type c5b = ly.add_cell ("C5B");
ly.cell (top).insert (db::CellInstArray (db::CellInst (c1), db::Trans (1, true, db::Vector (0, 0))));
ly.cell (top).insert (db::CellInstArray (db::CellInst (c1), db::Trans (0, false, db::Vector (0, 10000))));
ly.cell (c1).insert (db::CellInstArray (db::CellInst (c2), db::Trans (1, true, db::Vector (100, 200)), db::Vector (0, 1000), db::Vector (1000, 0), 2l, 3l));
// C4 and C5 are single instances in C2, C5 with mag 2
ly.cell (c2).insert (db::CellInstArray (db::CellInst (c4a), db::ICplxTrans (1.0, 0.0, false, db::Vector (10, 20))));
ly.cell (c2).insert (db::CellInstArray (db::CellInst (c5a), db::ICplxTrans (2.0, 0.0, false, db::Vector (10, 20))));
// C3 has same instances as C2, but flat
ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (100, 10200))));
ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (100, 11200))));
ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (1100, 10200))));
ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (1100, 11200))));
ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (2100, 10200))));
ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (1, true, db::Vector (2100, 11200))));
ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (200, 100))));
ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (200, 1100))));
ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (200, 2100))));
ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (1200, 100))));
ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (1200, 1100))));
ly.cell (top).insert (db::CellInstArray (db::CellInst (c3), db::Trans (0, false, db::Vector (1200, 2100))));
// C4 and C5 are single instances in C3, C5 with a different complex angle (45 degree)
ly.cell (c3).insert (db::CellInstArray (db::CellInst (c4b), db::ICplxTrans (1.0, 0.0, false, db::Vector (10, 20))));
ly.cell (c3).insert (db::CellInstArray (db::CellInst (c5b), db::ICplxTrans (1.0, 45.0, false, db::Vector (10, 20))));
db::CellInstanceSetHasher hasher1 (&ly, top, 0);
EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (top)), "00004450");
EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (c1)), "00023711");
EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (c2)), "001260aa");
EXPECT_EQ (hasher1.instance_set_hash (c3), hasher1.instance_set_hash (c2));
EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (c4a)), "001270ba");
EXPECT_EQ (hasher1.instance_set_hash (c4a), hasher1.instance_set_hash (c4b));
EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (c5a)), "0010da3a"); // != hash of C4A because of mag 2
EXPECT_EQ (tl::sprintf ("%08lx", hasher1.instance_set_hash (c5b)), "0011d5c4"); // != hash of C5A because of 45 degree angle
std::set<db::cell_index_type> set1;
set1.insert (top);
set1.insert (c1);
set1.insert (c2);
set1.insert (c3);
set1.insert (c4a);
set1.insert (c5a);
set1.insert (c4b);
set1.insert (c5b);
db::CellInstanceSetHasher hasher2 (&ly, top, &set1);
EXPECT_EQ (hasher1.instance_set_hash (top), hasher2.instance_set_hash (top));
EXPECT_EQ (hasher1.instance_set_hash (c1), hasher2.instance_set_hash (c1));
EXPECT_EQ (hasher1.instance_set_hash (c2), hasher2.instance_set_hash (c2));
EXPECT_EQ (hasher1.instance_set_hash (c3), hasher2.instance_set_hash (c3));
EXPECT_EQ (hasher1.instance_set_hash (c4a), hasher2.instance_set_hash (c4a));
EXPECT_EQ (hasher1.instance_set_hash (c4b), hasher2.instance_set_hash (c4b));
EXPECT_EQ (hasher1.instance_set_hash (c5a), hasher2.instance_set_hash (c5a));
EXPECT_EQ (hasher1.instance_set_hash (c5b), hasher2.instance_set_hash (c5b));
std::set<db::cell_index_type> set2 = set1;
// Remove C1 from selected set
set2.erase (c1);
db::CellInstanceSetHasher hasher3 (&ly, top, &set2);
EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (top)), "00004450");
// NOTE: C1 hash is not valid as this cell is not selected
EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c2)), "00000000"); // no path to TOP
EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c3)), "001260aa");
EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c4a)), "00000000"); // no path to TOP
EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c4b)), "001270ba");
EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c5a)), "00000000"); // no path to TOP
EXPECT_EQ (tl::sprintf ("%08lx", hasher3.instance_set_hash (c5b)), "0011d5c4"); // != hash of C5A because of 45 degree angle
}

View File

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