From 9b7879b2a9261a748cba63a6ebaf895024b0df96 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 6 Apr 2021 21:05:02 +0200 Subject: [PATCH] Faster hierarchical edges. --- src/db/db/dbDeepRegion.cc | 161 ++++++++++++++++++++----- src/db/db/dbHierProcessor.cc | 95 +++++++++++++-- src/db/db/dbHierProcessor.h | 11 ++ src/db/unit_tests/dbDeepRegionTests.cc | 44 ++++++- testdata/algo/deep_edges_au10.gds | Bin 7012 -> 7304 bytes testdata/algo/deep_edges_au3.gds | Bin 7142 -> 6170 bytes testdata/algo/deep_edges_au4.gds | Bin 7898 -> 7034 bytes testdata/algo/deep_region_au13.gds | Bin 2488 -> 2596 bytes testdata/algo/deep_region_au13b.gds | Bin 0 -> 66628 bytes testdata/algo/deep_region_au14b.gds | Bin 2514 -> 2514 bytes testdata/algo/deep_region_au25b.gds | Bin 13346 -> 13346 bytes testdata/algo/deep_region_edges.gds | Bin 0 -> 748 bytes testdata/drc/drcSimpleTests_au16.gds | Bin 22856 -> 21512 bytes 13 files changed, 273 insertions(+), 38 deletions(-) create mode 100644 testdata/algo/deep_region_au13b.gds create mode 100644 testdata/algo/deep_region_edges.gds diff --git a/src/db/db/dbDeepRegion.cc b/src/db/db/dbDeepRegion.cc index a4100ef19..c869ac5af 100644 --- a/src/db/db/dbDeepRegion.cc +++ b/src/db/db/dbDeepRegion.cc @@ -1070,56 +1070,163 @@ DeepRegion::snapped (db::Coord gx, db::Coord gy) return res.release (); } +namespace +{ + +class PolygonToEdgeLocalOperation + : public local_operation +{ +public: + PolygonToEdgeLocalOperation () + : local_operation () + { + // .. nothing yet .. + } + + virtual db::Coord dist () const { return 1; } + virtual bool requests_single_subjects () const { return true; } + virtual std::string description () const { return std::string ("polygon to edges"); } + + virtual void do_compute_local (db::Layout * /*layout*/, const shape_interactions &interactions, std::vector > &results, size_t /*max_vertex_count*/, double /*area_ratio*/) const + { + db::EdgeProcessor ep; + + for (shape_interactions::subject_iterator s = interactions.begin_subjects (); s != interactions.end_subjects (); ++s) { + ep.insert (s->second); + } + + if (interactions.num_intruders () == 0) { + + db::EdgeToEdgeSetGenerator eg (results.front ()); + db::MergeOp op (0); + ep.process (eg, op); + + } else { + + // With intruders: to compute our local contribution we take the edges without and with intruders + // and deliver what is in both sets + + db::MergeOp op (0); + + std::vector edges1; + db::EdgeContainer ec1 (edges1); + ep.process (ec1, op); + + ep.clear (); + + for (shape_interactions::subject_iterator s = interactions.begin_subjects (); s != interactions.end_subjects (); ++s) { + ep.insert (s->second); + } + for (shape_interactions::intruder_iterator i = interactions.begin_intruders (); i != interactions.end_intruders (); ++i) { + ep.insert (i->second.second); + } + + std::vector edges2; + db::EdgeContainer ec2 (edges2); + ep.process (ec2, op); + + // Runs the boolean AND between the result with and without intruders + + db::box_scanner scanner; + scanner.reserve (edges1.size () + edges2.size ()); + + for (std::vector::const_iterator i = edges1.begin (); i != edges1.end (); ++i) { + scanner.insert (i.operator-> (), 0); + } + for (std::vector::const_iterator i = edges2.begin (); i != edges2.end (); ++i) { + scanner.insert (i.operator-> (), 1); + } + + EdgeBooleanClusterCollector > cluster_collector (&results.front (), EdgeAnd); + scanner.process (cluster_collector, 1, db::box_convert ()); + + } + + } +}; + +} + EdgesDelegate * DeepRegion::edges (const EdgeFilterBase *filter) const { - const db::DeepLayer &polygons = merged_deep_layer (); + if (! filter && merged_semantics ()) { - std::unique_ptr vars; + // Hierarchical edge detector - no pre-merge required - if (filter && filter->vars ()) { + const db::DeepLayer &polygons = deep_layer (); - vars.reset (new db::VariantsCollectorBase (filter->vars ())); + db::PolygonToEdgeLocalOperation op; - vars->collect (polygons.layout (), polygons.initial_cell ()); + db::local_processor proc (const_cast (&polygons.layout ()), + const_cast (&polygons.initial_cell ()), + polygons.breakout_cells ()); - // NOTE: m_merged_polygons is mutable, so why is the const_cast needed? - const_cast (polygons).separate_variants (*vars); + proc.set_description (progress_desc ()); + proc.set_report_progress (report_progress ()); + proc.set_base_verbosity (base_verbosity ()); + proc.set_threads (polygons.store ()->threads ()); - } + // a boolean core makes somewhat better hierarchy + proc.set_boolean_core (true); - db::Layout &layout = const_cast (polygons.layout ()); + std::unique_ptr res (new db::DeepEdges (polygons.derived ())); - std::unique_ptr res (new db::DeepEdges (polygons.derived ())); - for (db::Layout::iterator c = layout.begin (); c != layout.end (); ++c) { + proc.run (&op, polygons.layer (), foreign_idlayer (), res->deep_layer ().layer ()); + + return res.release (); + + } else { + + const db::DeepLayer &polygons = merged_deep_layer (); + + std::unique_ptr vars; + + if (filter && filter->vars ()) { + + vars.reset (new db::VariantsCollectorBase (filter->vars ())); + + vars->collect (polygons.layout (), polygons.initial_cell ()); + + // NOTE: m_merged_polygons is mutable, so why is the const_cast needed? + const_cast (polygons).separate_variants (*vars); - db::ICplxTrans tr; - if (vars.get ()) { - const std::map &v = vars->variants (c->cell_index ()); - tl_assert (v.size () == size_t (1)); - tr = v.begin ()->first; } - const db::Shapes &s = c->shapes (polygons.layer ()); - db::Shapes &st = c->shapes (res->deep_layer ().layer ()); + db::Layout &layout = const_cast (polygons.layout ()); - for (db::Shapes::shape_iterator si = s.begin (db::ShapeIterator::All); ! si.at_end (); ++si) { + std::unique_ptr res (new db::DeepEdges (polygons.derived ())); + for (db::Layout::iterator c = layout.begin (); c != layout.end (); ++c) { - db::Polygon poly; - si->polygon (poly); + db::ICplxTrans tr; + if (vars.get ()) { + const std::map &v = vars->variants (c->cell_index ()); + tl_assert (v.size () == size_t (1)); + tr = v.begin ()->first; + } - for (db::Polygon::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end (); ++e) { - if (! filter || filter->selected ((*e).transformed (tr))) { - st.insert (*e); + const db::Shapes &s = c->shapes (polygons.layer ()); + db::Shapes &st = c->shapes (res->deep_layer ().layer ()); + + for (db::Shapes::shape_iterator si = s.begin (db::ShapeIterator::All); ! si.at_end (); ++si) { + + db::Polygon poly; + si->polygon (poly); + + for (db::Polygon::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end (); ++e) { + if (! filter || filter->selected ((*e).transformed (tr))) { + st.insert (*e); + } } + } } - } + res->set_is_merged (merged_semantics () || is_merged ()); + return res.release (); - res->set_is_merged (merged_semantics () || is_merged ()); - return res.release (); + } } RegionDelegate * diff --git a/src/db/db/dbHierProcessor.cc b/src/db/db/dbHierProcessor.cc index df90ff1ba..8d39b1a00 100644 --- a/src/db/db/dbHierProcessor.cc +++ b/src/db/db/dbHierProcessor.cc @@ -409,6 +409,16 @@ local_processor_cell_contexts::create (const context_key_type &intru return &m_contexts[intruders]; } +template +static void +subtract_set (std::unordered_set &res, const std::unordered_set &other) +{ + // for everything else, we don't use a boolean core but just set intersection + for (typename std::unordered_set::const_iterator o = other.begin (); o != other.end (); ++o) { + res.erase (*o); + } +} + template static void subtract (std::unordered_set &res, const std::unordered_set &other, db::Layout *layout, const db::local_processor *proc) @@ -417,6 +427,11 @@ subtract (std::unordered_set &res, const std::unordered_setboolean_core ()) { + subtract_set (res, other); + return; + } + size_t max_vertex_count = proc->max_vertex_count (); double area_ratio = proc->area_ratio (); @@ -449,14 +464,60 @@ subtract (std::unordered_set &res, const std::unordered_set +static void +subtract (std::unordered_set &res, const std::unordered_set &other, db::Layout *layout, const db::local_processor *proc) +{ + if (other.empty ()) { + return; + } + + if (! proc->boolean_core ()) { + subtract_set (res, other); + return; + } + + db::box_scanner scanner; + scanner.reserve (res.size () + other.size ()); + + for (std::unordered_set::const_iterator i = res.begin (); i != res.end (); ++i) { + scanner.insert (i.operator-> (), 0); + } + for (std::unordered_set::const_iterator i = other.begin (); i != other.end (); ++i) { + scanner.insert (i.operator-> (), 1); + } + + std::unordered_set result; + EdgeBooleanClusterCollector > cluster_collector (&result, EdgeNot); + scanner.process (cluster_collector, 1, db::box_convert ()); + + res.swap (result); +} + template static void subtract (std::unordered_set &res, const std::unordered_set &other, db::Layout * /*layout*/, const db::local_processor * /*proc*/) { - // for edges, we don't use a boolean core but just set intersection - for (typename std::unordered_set::const_iterator o = other.begin (); o != other.end (); ++o) { - res.erase (*o); - } + subtract_set (res, other); +} + +// determines the default boolean core flag per result type + +namespace +{ + +template +struct default_boolean_core +{ + bool operator() () const { return false; } +}; + +template <> +struct default_boolean_core +{ + bool operator() () const { return true; } +}; + } namespace { @@ -1221,7 +1282,7 @@ local_processor::local_processor (db::Layout *layout, db::Cell *top, : mp_subject_layout (layout), mp_intruder_layout (layout), mp_subject_top (top), mp_intruder_top (top), mp_subject_breakout_cells (breakout_cells), mp_intruder_breakout_cells (breakout_cells), - m_report_progress (true), m_nthreads (0), m_max_vertex_count (0), m_area_ratio (0.0), m_base_verbosity (30), m_progress (0), mp_progress (0) + m_report_progress (true), m_nthreads (0), m_max_vertex_count (0), m_area_ratio (0.0), m_boolean_core (default_boolean_core () ()), m_base_verbosity (30), m_progress (0), mp_progress (0) { // .. nothing yet .. } @@ -1231,7 +1292,7 @@ local_processor::local_processor (db::Layout *subject_layout, db::Ce : mp_subject_layout (subject_layout), mp_intruder_layout (intruder_layout), mp_subject_top (subject_top), mp_intruder_top (intruder_top), mp_subject_breakout_cells (subject_breakout_cells), mp_intruder_breakout_cells (intruder_breakout_cells), - m_report_progress (true), m_nthreads (0), m_max_vertex_count (0), m_area_ratio (0.0), m_base_verbosity (30), m_progress (0), mp_progress (0) + m_report_progress (true), m_nthreads (0), m_max_vertex_count (0), m_area_ratio (0.0), m_boolean_core (default_boolean_core () ()), m_base_verbosity (30), m_progress (0), mp_progress (0) { // .. nothing yet .. } @@ -1535,6 +1596,11 @@ void local_processor::compute_contexts (local_processor_contexts ()); } + // this cache should reduce the effort of checking array vs. array + typedef std::pair > effective_instance_cache_key_type; + typedef std::map > effective_instance_cache_type; + effective_instance_cache_type effective_instance_cache; + for (typename std::unordered_map::const_iterator i = interactions.begin (); i != interactions.end (); ++i) { db::Cell &subject_child_cell = mp_subject_layout->cell (i->first->object ().cell_index ()); @@ -1568,17 +1634,28 @@ void local_processor::compute_contexts (local_processor_contexts inst_bcii (*mp_intruder_layout, ail); for (std::unordered_set::const_iterator j = i->second.first.begin (); j != i->second.first.end (); ++j) { + for (db::CellInstArray::iterator k = (*j)->begin_touching (safe_box_enlarged (nbox, -1, -1), inst_bcii); ! k.at_end (); ++k) { + db::ICplxTrans tk = (*j)->complex_trans (*k); // NOTE: no self-interactions if (i->first != *j || tn != tk) { + // optimize the intruder instance so it will be as low as possible - std::pair ei = effective_instance (contexts.subject_layer (), i->first->object ().cell_index (), ail, (*j)->object ().cell_index (), tni * tk, dist); - if (ei.first) { - intruders_below.first.insert (ei.second); + effective_instance_cache_key_type key (ail, std::make_pair ((*j)->object ().cell_index (), tni * tk)); + effective_instance_cache_type::iterator cached = effective_instance_cache.find (key); + if (cached == effective_instance_cache.end ()) { + std::pair ei = effective_instance (contexts.subject_layer (), i->first->object ().cell_index (), ail, (*j)->object ().cell_index (), tni * tk, dist); + cached = effective_instance_cache.insert (std::make_pair (key, ei)).first; } + if (cached->second.first) { + intruders_below.first.insert (cached->second.second); + } + } + } + } } diff --git a/src/db/db/dbHierProcessor.h b/src/db/db/dbHierProcessor.h index c65da727f..712ebf53e 100644 --- a/src/db/db/dbHierProcessor.h +++ b/src/db/db/dbHierProcessor.h @@ -479,6 +479,16 @@ public: return m_area_ratio; } + void set_boolean_core (double boolean_core) + { + m_boolean_core = boolean_core; + } + + double boolean_core () const + { + return m_boolean_core; + } + private: template friend class local_processor_cell_contexts; template friend class local_processor_context_computation_task; @@ -494,6 +504,7 @@ private: unsigned int m_nthreads; size_t m_max_vertex_count; double m_area_ratio; + bool m_boolean_core; int m_base_verbosity; mutable std::unique_ptr > > mp_cc_job; mutable size_t m_progress; diff --git a/src/db/unit_tests/dbDeepRegionTests.cc b/src/db/unit_tests/dbDeepRegionTests.cc index 46b57bf22..1cfc12999 100644 --- a/src/db/unit_tests/dbDeepRegionTests.cc +++ b/src/db/unit_tests/dbDeepRegionTests.cc @@ -800,7 +800,7 @@ TEST(13_Edges) db::Region r3 (db::RecursiveShapeIterator (ly, top_cell, l3), dss); db::Edges r3edges = r3.edges (); - EXPECT_EQ (r3edges.is_merged (), true); + EXPECT_EQ (r3edges.is_merged (), false); db::EdgeLengthFilter f (0, 500, true); db::Edges r3edges_filtered = r3.edges (f); @@ -816,6 +816,46 @@ TEST(13_Edges) db::compare_layouts (_this, target, tl::testsrc () + "/testdata/algo/deep_region_au13.gds"); } +TEST(13b_Edges) +{ + db::Layout ly; + { + std::string fn (tl::testsrc ()); + fn += "/testdata/algo/deep_region_edges.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly); + } + + db::cell_index_type top_cell_index = *ly.begin_top_down (); + db::Cell &top_cell = ly.cell (top_cell_index); + + db::DeepShapeStore dss; + + unsigned int l1 = ly.get_layer (db::LayerProperties (1, 0)); + unsigned int l2 = ly.get_layer (db::LayerProperties (2, 0)); + + db::Region r1 (db::RecursiveShapeIterator (ly, top_cell, l1), dss); + db::Edges r1edges = r1.edges (); + EXPECT_EQ (r1edges.is_merged (), false); + + db::Region r2 (db::RecursiveShapeIterator (ly, top_cell, l2), dss); + db::Edges r2edges = r2.edges (); + EXPECT_EQ (r2edges.is_merged (), false); + + + db::Layout target; + unsigned int target_top_cell_index = target.add_cell (ly.cell_name (top_cell_index)); + + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (1, 0)), r1); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (2, 0)), r2); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (11, 0)), r1edges); + target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (12, 0)), r2edges); + + CHECKPOINT(); + db::compare_layouts (_this, target, tl::testsrc () + "/testdata/algo/deep_region_au13b.gds"); +} + TEST(14_Interacting) { db::Layout ly; @@ -936,7 +976,7 @@ TEST(14_Interacting) target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (32, 0)), r6r.selected_interacting (r1er)); target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (33, 0)), r6r.selected_not_interacting (r1er)); - EXPECT_EQ (r6.selected_interacting (r1e).is_merged (), true); + EXPECT_EQ (r6.selected_interacting (r1e).is_merged (), false); EXPECT_EQ (r6.selected_interacting (r1er).is_merged (), false); EXPECT_EQ (r6r.selected_interacting (r1e).is_merged (), false); EXPECT_EQ (r6r.selected_interacting (r1er).is_merged (), false); diff --git a/testdata/algo/deep_edges_au10.gds b/testdata/algo/deep_edges_au10.gds index 13ded556bf1632921542df723bc3894c87a3fd81..c809ae30cd56f7a127a32a12005980ce48a228bb 100644 GIT binary patch delta 701 zcmZ`$O=}ZT6rDG1-b~WWN1DmJ8K)saEJ7D1lo}_}Hsc3~*pLb?Bor)+xD=7DtqT_l z7CWA05QGSJC1N)s(oIoZxabe4Rj9hFcIn2_J2MsBWb@8BZ|=GGygT0;51U9Y(>)Y}hhI+=w zz9R5t*u`BAOY6o|C{L%_0Oo1o4*w3+Hh-3@#{Hbi6a&gR$2--vi zpG^~bR!O$f&3kBT9c*QF{80?z=^NTw8Q&=9Z_T6mKJxk`c5->FX$Ab%J6JVyc+HE% z%a}L(ID53`QVFLR^`i*4lCg{Cdo0+On1vViGMx{^4TOaz7Q{&mjRvfQn@)?)6Hbe? zhXZFEn*|RW&N#D^>dn!#`a+D<$&NcAECx&@UXggiV(P6Dk{6*>z?56VF5wbkSh~Mn lwGD=+;wq|Q5xb&;yUsLDIRSn-r_pzUe$^fPv(FxzV24#s$*uqZ delta 1081 zcmaJ=&r4Kc6#d?NGw;p3Uo+#3GroDgoE8m3gyOGBMZ&C26qw~gBy0Wvv!GQ3;j$tq zJ#9y`)6L`I1oICypc(HHmDx(`2TnYleOBpRbT_Dzle zo05YM()=ASBIS0FpX-P|l5pN~V8tA)X(tI?yfR(*Y7-W;HjKsHpf(ftaFIB(b`2w_ z#Jcd+&QZRMXYoP2vs|3fIri|zbO@a(OC_0H7 zB0(ZgjV@J63!yDy#j5AZSlpE9R5A9+N&ypkJq*$>k{80PZl`*Jl-Sr#9l{OC zmBj{j84fPdm(JR{>Z?M;>#3ngN1jWil<)u8!zsF&=sc@Oj=wa^u*uDNipQ)ja<8N2 z*1YAR$7-7*cx{*I$Ks(&Vcd%e@_kcs@!W86E-^q#Dy|!xV~15Vn>oBs+?`4_<3Vya z9;R9`k+ShERm$)e`u^r;p5xbuvvwAJX${M=g-l&LJSB!irj`50Gt{51^*b0}575n?S*O^Zst7_QFu8GjoS8pNY+ua>A-%3i>- zV&(sJA$%LbmYdVGS2L5UI%5y>B6Wfusf(#l?obp8t# zsV%JRW0A&EI7lU!tTS(RZr{GUnSHB;Pxkq_nfbo?@pi#Mmc54MPS*PcIn2Nk^uUGx zhJNOrtvv!*?9Mz`d4Komw=bi6t6y*IetI*6WqPPmrmb_9Egz3pBRXZ1k{p?cOQl2wUNO_c^D?O_4AAD7q~=SZlbYD;pR=!xt-Z)qs?l}?F`v8SPj7gB6ZWRyyY4Owd$rBb5Ccun#! zjH^K5b!fWd-v@l(_%kKdww?8wxWr|%9%Q+D0O&Ncx40;=>;`Gx5J z$nTW1qt^S64lyo=v*Qrk_;>W`x2*D2|`pJyum#dG3bHH%%c{foN7EouU|-O0^X#@6wd7vZhoe$rh`Clg{UEH1SmB$wThe zR59rkWzyU{$2T|MCN9Z-tcs+#sv01hy}dMwGYUQPm7D5eTz743NnGtRil{P+6V)IU z+I9f85%i(ZmU{DI%HO;v>r16LCp!C_S<++6`ckQSRi@r3sW(+UDx!>=S2YdLZvB3( zQk+qw232Kr$_5opf>3#t;-0J2mq8h|tWsufsFX|vshC=&s1)0hc^Y*9i^A!tREkB7 zr$gIQCR^pXwKy+j!m1v!hEyqB(`g!t;?ft>D9&1?9~vIgJw)P)dap|1Dc5g1NgA3{ z&#-0O91MzX49cuj-!{0j$!#*~8!Bb?4TCcJhDw=z!=Q}5p;BhwP$|iE$>Lh}s+8F` zEK2Vi7Nz$MgDR)Ow7#KItcKJV_QkML8Yrc27*ze8BF&cBHyUh7Tv6XpDYI`Fl)Ph2 zxh)%%(Kigr=o`lO2D;(V&0)%Wib|RHD3zk~^6x_amJ;Zi8~9avV{QHMb9^Di?-%_A{?g&UOyG>7KR(WQZ}39j qTin(6E{*iPYY+9k+x4#`H9zkk=-iW=`rgL2zPI_K+Cv5N9R2~4^+xUh literal 7142 zcmb7JO=}cE5UuQPCY#NNF&`=tL=PSWK~X_GC`JMrlqfNXNPdJTZ$cm^J>{;4-24k3 za`s>@-s2(QIS|N6Fd6H0P3_Edwt8v>ZQ1TORrTui>z-Z8sMUH)izBVEKQuvev_O+Y z)&EnsmAzPbLNwnVeYEuH{_`K-jvg$3zqRw_-8wCf+PGept*P#qiCUj^)XyS`ZbC?VBJOamhozhW1Pvhw~>+>Gh!DHtuE>e;#g8`wQyV;EGKgCAAT+?%1+i44bfy(n2N zbxqFuO@jH;LuKBN67{j01@);)n!4Oy9W>`hMM|xDp;=d{YwQo! zw-Z&Y4t05%m%4a$*n@r)cC4cO83I+=ugfUVxbXeif@3HH@N))xp=Ib8_DH z4QPI3A3KHr7N>KeLlcF5h1H?U(K%_NWH(Ok$vD1HQ&a*4XAAcV_6=_=VVqY74*hk3 z`N=&&50fF$_=0#8qpR95>k`d#jKE=pTCn=n&nze^(tHC-cZxR_`XtPY!`13CV`+C~ zjQZ7pQpfgZh|>Xz!Wj8RB3AWGiMni}`brwKD-$UAA=eYS?9EWRtiIvpP+~yyh#9$! znPGYV5LSF$<_zn%ovP-|SbY|PdcACDcSTWNl*EG1gAIR|^)^?WW3IZ8VR;9L+$O2T4Mn4;cY6snI%vjJw$$WI`nJBs2fWxBO z?k;m}d{zpV?ww-aHxh4bIEipNV2pge0mVBcpduGFQNkfk*a1GDM9HZqH;5V=4JgCs z6DTll_4q&3d9W!xV#z;{K6x3_@d;(S8^q?#shvI04&zrH5K5wF!1>5Hn zD6pYAj2zoUC4JsR2^)sbCr}BWH&IESH&J~hsV`RDCWW$n-bAT4sdM`jrK?pVJ`|WY zecsGa`la3*onIzOy-7RZ%`oxin}`L!Fnr!bq08ob1@#`{+y7P!L+TR3+$)+MW7{nbOE z>dZ&yvKdS2(o@fi67H14;ZSP^Iw9U@K;-6~B~RflJGiir}J zk?$NhUj17U6D4)2qP%Q~HozgAz>7kxkeQB7^;YjT-nvAG>W7irm~2Qr^377oe8X=J z{0r0C%IedXY^40VLU&evfe61C@Le5$LY?tk@0C3_zhlo`IkM+&JhtcVZrgJY5A3;T Tx9z#LO?z(RS2@QFb~^MQ_(Qq2 diff --git a/testdata/algo/deep_edges_au4.gds b/testdata/algo/deep_edges_au4.gds index f91ff852647012efd3784ad0cb6f753bdebaa90d..c9a752daa4da0797d9a0e9781c31ddc20f4947f4 100644 GIT binary patch literal 7034 zcmbW6F>4e-6vyA~UG~m1iDx_%B#0Ikf}lh}EEFRF4N8=Fh)6z!m0ci^N-KLU()krE zQd?Nr$0ChoAdpHhS>M~YbGN&9|No5%n30|P-J5yy=C`vONhL|%$YLXD|CW}d(w8Nf zE&pFSN&0;Cu}Hr;^KkjYy=ULQ9^PO1c5CPJ+kq_3V6K~{gZ1ULS0b&BNII8DBI$f0 zA~$=@EIWRmWhd9I6>02=Bp*W?u3Iau^CP}l=CkcA`@5fI*|tdYs($NcXv1}Doj>F8 zy9J+Kvr%-;HTAd7t7om!HVWAsMV&@jY#*zQLFs&~1$FjRu^>fb&GJ|wJGBu)%5&P- z8|Jn=FGvk@m*=pSc}V%Zd>zL!KXrlY zAQfVGt8}?GAVqTsDW4apjZ0`=Whys9d4!O%5tjYo^J)tP`K39%tMkyd^+oxWQ5&vnn`db}ewO{&P;n2uZSQ(k zLmRHA1LvpN+DC7DE14UOdK%?9GZ&s5#8cJG)w~PuLbgs8!`G`u!8-$;7o_OCAVuc| zDLOBBXRuiMPuI)ZP$k8BLCWV9YQ2_2NO^^ZiquLiI|EX_PV+sFIp2$=tdh)Xw%W1x zNnhpC9{P_Ff}H~A<*~wkkIjZsy1OlhW{KGK8h$#b;@7LFiQFu)v3$MEAMV50^|DW! z0j#ar2IjEtuDDoU>sm>9HsUMFJQ?QI-i?h_vvE{Kja+Uk5MLX`r^B5jGM1TIcZgDB zI|ZJ+Jcqy=kfPpz6!iwAypn)72t~XBDX%2p4M>Gr4^>>LC`frM;0;D$Z!pR&Gszo- zBHnfKO3g zz2=Px=R-)*a}=c9XnOJvXPj~lA!T(4&xi0SY8YeEISNwLhmfK^gcS85p@&aLq=gAG79^UP{fCjqCT8L0Ur{I_z+UmhmaaQXWB`x=0iy7Dq|lKDm)Vx zhASI@P{fCXB0gkqMD(d0JQG2R?qx!ey$mTgH8>vdV^5J0Fa^vfD|m)4k)3r6!Gxr+ArP>M4Z%f16by-QP=X?i3Hk_E?g9c;Nu}N{ z+~pTssCHpB?wvy5Iv`LLi1adZ=Z)U()t>Y2T42V!yXVcFnRCy~-IYpuy~lE6p||u- zdU8e9r7yQ8|1ZN{_0#6}BIEwzw;O+a{qWUaC-)z`y#3$=_->3^ng z{XJ|vH|zK%p*tN`)#lctuVgS18C{KCuh=fnpXi%)2eymZtD}<{tJNn9dm{PR{!oQ0e0%z?9!oUHEcYub{xO#Tp2aBu0A)$3_sLN>fhKhYP(-Ka=U!3 z6lJmNr&A6To%Y)0%QL-leVtTW{vK+ta}=x%-4LGx1MQL&Xf)E(3s@9VYYOKQC;WZ9GUmr#dJ`Z`GymT z8(FV}aXF)`6E(~#_T_U+(_4yAo(<~`micT-Av?@wrb1t)0M^aTW*wIJYM7K5m7!MO zOXmrS=sZM4?n1ezl#QUK=f+Z$>AT$wX6QWhMRlH_h|WV)oGZ84%tlZ|=OK#fJVbfD zY>qO{X9@sO7pHNS&J$EKF(mfR;S1|L@nt6k=sZMOomxg^Dhg4W4XpDJS+?Sy*Lz$1@$ka)JDCa!5FMrJIr8=1Em1UlwtX@`SvzdxQl*bb{scg0(DAJb+ ziu7fO@_6EWWz#Z2k-iL3&R%f;A}G?AAx?hrBNfbzZ7S_Hu?pxjbGrkPfzgL7vJHvy9apfn`6B8 U;~l)Vy@%I!-?VF*!NEZO2ULgv%K!iX diff --git a/testdata/algo/deep_region_au13.gds b/testdata/algo/deep_region_au13.gds index ade409f90a01666a0a934d521d35d059aff5f1c7..14c9e6e4e2be727bcded2a913ac9ea3229b0d611 100644 GIT binary patch delta 389 zcmaJ-yGjF55S_h`*(C%~5($z7#VUyCvdD@}h+-o~u&|IV#4oV45CTFfn;1EeMg$ST z#zxbmv=EE+2dqLGOL4*0c)gp-CRTH1<~)Y!X`fUiBH=DwmUc{m22}`hP?Bqt=CwCg3 zdKPcmpETy@gUE~1I$ByD+Dai-0gkzdD_KS&1DwhbEfu0K7$vuW9bL333ZYHwix9qM i99T_;S1{l*&c#f65N>n{L4=vr&audh`nXJlUJh=pD-2*nZ$!9nOCf{OF a0J2#opJ0*P{Fm(+qd1Nb+5C$`ixB|GA5EkH diff --git a/testdata/algo/deep_region_au13b.gds b/testdata/algo/deep_region_au13b.gds new file mode 100644 index 0000000000000000000000000000000000000000..98ccf1b008569d1c4cc451c074fc0b48765603b8 GIT binary patch literal 66628 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

t36V&U}@I-s=B_|eB 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 H=yc>iOoO6S diff --git a/testdata/algo/deep_region_edges.gds b/testdata/algo/deep_region_edges.gds new file mode 100644 index 0000000000000000000000000000000000000000..c0a9af034c2bbfeea78cd6c2df77590c78914081 GIT binary patch literal 748 zcmb_ZJFdb&44f>BSwTP$pvw`EkkX*nAV8!*e2Wx_nxoLrQPJ><*Kq~vS8x-0j)0x; z*$5$}U{}s~z45Ha20^JMd`A^7&>%G7z_WcAMU&A0Xyo$U{yqVmLllh-XZQ^qhO6@{w7iFR? zZl^H>QAnfpgAh@Ih{VzgYT`m%RNaV3osAGek*>5B4K*Ncx-g#iUK2{-O$!QL9PWMR zoO|B+&N<)te#P*{6~iR6Fr%@=90p_kuguKq*a3Eou?O$0!Dz{6;*9M#)y0P2d@A?d z$D2{4MRniy~n2 ziD>Pm2z?xjiX0*7jX}u9(GsfQoHL1AhXkAre}+;oM~Un$B#xy zeR0gw_oAO96hoe3l={i9KZ;LWPvB0djI|?4{2Y|9-Wx{2ZNr%;$86&T^}ZanW4zG~ zn}_r<(%Ubn_vIMr?WFIvt(x1zhFZH|JTpQB{x;BsubpAs>N<)S;(M{=m2meUN4c{? znQ-_!lQ`8C!G?>_`6QIdcDFNzbM)=>azflk8HxDRv7(++ju0++MEu!to-!hR#}LXL zVaiCv)RX6HEfpmpW%g;QwBeKz`#c zIzm&^qjgzK+HvQw*5zuF^nR_&Vv6)VT9+4(g$+)<1wQF7?nOB6 z4JL?N95Le9b>i4*s~uU%LA>(dhe5|fxP@6Mqg%pXq*rmtE8?w86m!`me&=c2Obw{S zSV{*VW)gTm7$LH72>CdwIgVm}8r3urDb0xpq$k;M9aRVYb~F!r;{XoFlrghmIZ7?0 zp}!p?y`A(!efI}nX)TfAnZI;%dpR+xYg1JDig`{@h!x5X@CjJT1qc>FBb|_oqnOp;%_)!-s47H)n&FsBYr9}Rqw&0$%;V0PrA>yI(x6uhy3kkGi?0}|FE%WUb2Q0$d?@#$X>1y@ t%&|tYFh=JI4w>w0q+g(W)AMvMk&f;QbT&=+@x`9~ke}VK& z@L^6EGG&{aAB-|bH$>`iGUoasK1d&Ah`1ptr7L5Kj44>LAdca~@OSR%NdlT~;M2f| z%XiQB`_AwD?mg%C-Fx(c>B}poz0ArKWs)_Tlufspg*7pSH8N-EJ*!l#nbaU-wH20l z;;kokUHx#aPyaM@{=L0>Sfi!vyiaU}*D+i3e9fYEi{Q zscQ<)mv80SA5{3l0ZYFb@jeZo_t~*l6~?}*BRJICkA1cv_V?DpTcyEk3wkzwSqmT8 zrWDMoI?h)2!=)a<&FTQne;hZ|i)|avHD0tH#jmjjylJV%pRvdAd=21eci_%|ju$mM zzOVNgW4-<$90B4r;uFNXiKnsdK8W8G2V7wdlU)hacMV{1)utpoJ_pY2?8LF8hGQdxIH7CECTkFghT#exLY~%H z-Le@6B2x%@H{+M!Ww;`7EQN>KJdCM#8M6@9qzRKnl)QQuKPqm&A!#W}tusPupyXQ& zsg*ME*&KP+QmN!ivdV?3sR%BWD0z}D`Wo6^Wz0rVh0uPLaIT1wx6MeUouX1%@s&^- zDET>~(u(&*QjR=nsgx%bahbHR%o(AyNn9_>HuG+Wd%@h$ZQ95i_N0beBX}cPhpy&s zIw&fpy;m?AP!Vd08u)wj0H&G`;<$gDHdn)(uOD3=9iG2T0q0`z$n~-|GK^n zo{s6V#`i(T&qkAEQ#zjM4jK2$QPD8jG;uw|QL`*}|KBXsOobMdO(B~^qU0a3W|{5j zHhIdOQTmXkvUyBf>aOfKq^vuCKR)Z#aK5SrZ};`AyMGPRu}0kKoy7gVzysVLM`Ee6 zr}B2}72_ldD;KA7ejtOB^=9nQgnbJ;7pHSxqnMjw2_Hs%DsuXh_?m23bfq27j)alj z8HasDgI&*JWu%=tRYmnM>GVNdAJ(Am)cmGP!iP9=A2KZ!k-LtFL-ToBxU)%Fg|cp2 z93?+TcI9a?ylSN+%-xM-k|(R=*$T}A}?>(N??bt7} ba^%^HQe2+!<23E2ldh$Spp|}F&1UvD>|gCs