From afc9fc9c7aa789d9a7416fda9981af906811afaf Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 26 Dec 2020 19:43:51 +0100 Subject: [PATCH] WIP: Bugfixed deep processor (multi-input mode and input layer index), added tests --- src/db/db/dbCompoundOperation.cc | 21 ++- src/db/db/dbCompoundOperation.h | 6 + src/db/db/dbDeepRegion.cc | 139 ++++++------------ src/db/db/dbDeepRegion.h | 3 +- src/db/db/dbHierProcessor.cc | 58 ++++---- src/db/unit_tests/dbCompoundOperationTests.cc | 34 ++++- testdata/drc/compound_au12d.gds | Bin 0 -> 2834 bytes 7 files changed, 133 insertions(+), 128 deletions(-) create mode 100644 testdata/drc/compound_au12d.gds diff --git a/src/db/db/dbCompoundOperation.cc b/src/db/db/dbCompoundOperation.cc index fe9bb49a0..363a931b6 100644 --- a/src/db/db/dbCompoundOperation.cc +++ b/src/db/db/dbCompoundOperation.cc @@ -418,7 +418,7 @@ std::string CompoundRegionLogicalBoolOperationNode::generated_description () con } else if (m_op == Or) { r += "or"; } - return r + CompoundRegionMultiInputOperationNode::description (); + return r + CompoundRegionMultiInputOperationNode::generated_description (); } template @@ -482,7 +482,7 @@ CompoundRegionGeometricalBoolOperationNode::generated_description () const } else if (m_op == Not) { r = "not"; } - r += CompoundRegionMultiInputOperationNode::description (); + r += CompoundRegionMultiInputOperationNode::generated_description (); return r; } @@ -499,6 +499,21 @@ CompoundRegionGeometricalBoolOperationNode::result_type () const } } +db::Coord +CompoundRegionGeometricalBoolOperationNode::dist () const +{ + db::Coord d = CompoundRegionMultiInputOperationNode::dist (); + + ResultType res_a = child (0)->result_type (); + ResultType res_b = child (1)->result_type (); + + if (res_a == Region && res_b == Region) { + return d; // overlapping is sufficient + } else { + return d + 1; // we need "touching" if edges are involved + } +} + template static void run_bool (CompoundRegionGeometricalBoolOperationNode::GeometricalOp, db::Layout *, const std::unordered_set &, const std::unordered_set &, const std::unordered_set &) @@ -868,7 +883,7 @@ std::string CompoundRegionLogicalCaseSelectOperationNode::generated_description // TODO: could be nicer ... std::string r; r = "if-then"; - return r + CompoundRegionMultiInputOperationNode::description (); + return r + CompoundRegionMultiInputOperationNode::generated_description (); } CompoundRegionLogicalCaseSelectOperationNode::ResultType diff --git a/src/db/db/dbCompoundOperation.h b/src/db/db/dbCompoundOperation.h index f5f4109eb..6adac3894 100644 --- a/src/db/db/dbCompoundOperation.h +++ b/src/db/db/dbCompoundOperation.h @@ -61,6 +61,11 @@ public: CompoundRegionOperationNode (); virtual ~CompoundRegionOperationNode (); + const std::string &raw_description () const + { + return m_description; + } + std::string description () const; void set_description (const std::string &d); @@ -425,6 +430,7 @@ public: // specifies the result type virtual ResultType result_type () const; + virtual db::Coord dist () const; // the different computation slots virtual void do_compute_local (db::Layout *layout, const shape_interactions &interactions, std::vector > &results, size_t max_vertex_count, double area_ratio) const; diff --git a/src/db/db/dbDeepRegion.cc b/src/db/db/dbDeepRegion.cc index a64f74135..ee2c77187 100644 --- a/src/db/db/dbDeepRegion.cc +++ b/src/db/db/dbDeepRegion.cc @@ -1343,22 +1343,37 @@ DeepRegion::in (const Region &other, bool invert) const return db::AsIfFlatRegion::in (other, invert); } -EdgePairsDelegate * -DeepRegion::cop_to_edge_pairs (db::CompoundRegionOperationNode &node) +template +static +Output *region_cop_impl (DeepRegion *region, db::CompoundRegionOperationNode &node) { - std::vector other_layers; + // Fall back to flat mode if one of the inputs is flat std::vector inputs = node.inputs (); + for (std::vector::const_iterator i = inputs.begin (); i != inputs.end (); ++i) { + if (*i && ! dynamic_cast ((*i)->delegate ())) { + return 0; + } + } + + db::local_processor proc (const_cast (®ion->deep_layer ().layout ()), + const_cast (®ion->deep_layer ().initial_cell ()), + region->deep_layer ().breakout_cells ()); + + proc.set_base_verbosity (region->base_verbosity ()); + proc.set_threads (region->deep_layer ().store ()->threads ()); + + bool needs_merged = node.wants_merged (); + const db::DeepLayer &polygons (needs_merged ? region->merged_deep_layer () : region->deep_layer ()); + + std::vector other_layers; for (std::vector::const_iterator i = inputs.begin (); i != inputs.end (); ++i) { if (! *i) { - // @@@ in case of *i == null - what to do? - other_layers.push_back (deep_layer ().layer ()); + other_layers.push_back (polygons.layer ()); } else { const db::DeepRegion *other_deep = dynamic_cast ((*i)->delegate ()); - if (! other_deep) { - return db::AsIfFlatRegion::cop_to_edge_pairs (node); - } - if (&other_deep->deep_layer ().layout () != &deep_layer ().layout () || &other_deep->deep_layer ().initial_cell () != &deep_layer ().initial_cell ()) { + tl_assert (other_deep != 0); + if (&other_deep->deep_layer ().layout () != ®ion->deep_layer ().layout () || &other_deep->deep_layer ().initial_cell () != ®ion->deep_layer ().initial_cell ()) { throw tl::Exception (tl::to_string (tr ("Complex DeepRegion operations need to use the same layout and top cell for all inputs"))); } other_layers.push_back (other_deep->deep_layer ().layer ()); @@ -1366,104 +1381,44 @@ DeepRegion::cop_to_edge_pairs (db::CompoundRegionOperationNode &node) } - // @@@ really always "merged"? - const db::DeepLayer &polygons = merged_deep_layer (); - - db::local_processor proc (const_cast (&deep_layer ().layout ()), - const_cast (&deep_layer ().initial_cell ()), - deep_layer ().breakout_cells ()); - - proc.set_base_verbosity (base_verbosity ()); - proc.set_threads (polygons.store ()->threads ()); - - compound_local_operation op (&node); - - std::auto_ptr res (new db::DeepEdgePairs (polygons.derived ())); + std::auto_ptr res (new Output (polygons.derived ())); + compound_local_operation op (&node); proc.run (&op, polygons.layer (), other_layers, res->deep_layer ().layer ()); return res.release (); } +EdgePairsDelegate * +DeepRegion::cop_to_edge_pairs (db::CompoundRegionOperationNode &node) +{ + DeepEdgePairs *output = region_cop_impl (this, node); + if (! output) { + return AsIfFlatRegion::cop_to_edge_pairs (node); + } else { + return output; + } +} + RegionDelegate * DeepRegion::cop_to_region (db::CompoundRegionOperationNode &node) { - std::vector other_layers; - std::vector inputs = node.inputs (); - for (std::vector::const_iterator i = inputs.begin (); i != inputs.end (); ++i) { - - if (! *i) { - // @@@ in case of *i == null - what to do? - other_layers.push_back (deep_layer ().layer ()); - } else { - const db::DeepRegion *other_deep = dynamic_cast ((*i)->delegate ()); - if (! other_deep) { - return db::AsIfFlatRegion::cop_to_region (node); - } - if (&other_deep->deep_layer ().layout () != &deep_layer ().layout () || &other_deep->deep_layer ().initial_cell () != &deep_layer ().initial_cell ()) { - throw tl::Exception (tl::to_string (tr ("Complex DeepRegion operations need to use the same layout and top cell for all inputs"))); - } - other_layers.push_back (other_deep->deep_layer ().layer ()); - } - + DeepRegion *output = region_cop_impl (this, node); + if (! output) { + return AsIfFlatRegion::cop_to_region (node); + } else { + return output; } - - // @@@ really always "merged"? - const db::DeepLayer &polygons = merged_deep_layer (); - - db::local_processor proc (const_cast (&deep_layer ().layout ()), - const_cast (&deep_layer ().initial_cell ()), - deep_layer ().breakout_cells ()); - - proc.set_base_verbosity (base_verbosity ()); - proc.set_threads (polygons.store ()->threads ()); - - compound_local_operation op (&node); - - std::auto_ptr res (new db::DeepRegion (polygons.derived ())); - proc.run (&op, polygons.layer (), other_layers, res->deep_layer ().layer ()); - - return res.release (); } EdgesDelegate * DeepRegion::cop_to_edges (db::CompoundRegionOperationNode &node) { - std::vector other_layers; - std::vector inputs = node.inputs (); - for (std::vector::const_iterator i = inputs.begin (); i != inputs.end (); ++i) { - - if (! *i) { - // @@@ in case of *i == null - what to do? - other_layers.push_back (deep_layer ().layer ()); - } else { - const db::DeepRegion *other_deep = dynamic_cast ((*i)->delegate ()); - if (! other_deep) { - return db::AsIfFlatRegion::cop_to_edges (node); - } - if (&other_deep->deep_layer ().layout () != &deep_layer ().layout () || &other_deep->deep_layer ().initial_cell () != &deep_layer ().initial_cell ()) { - throw tl::Exception (tl::to_string (tr ("Complex DeepRegion operations need to use the same layout and top cell for all inputs"))); - } - other_layers.push_back (other_deep->deep_layer ().layer ()); - } - + DeepEdges *output = region_cop_impl (this, node); + if (! output) { + return AsIfFlatRegion::cop_to_edges (node); + } else { + return output; } - - // @@@ really always "merged"? - const db::DeepLayer &polygons = merged_deep_layer (); - - db::local_processor proc (const_cast (&deep_layer ().layout ()), - const_cast (&deep_layer ().initial_cell ()), - deep_layer ().breakout_cells ()); - - proc.set_base_verbosity (base_verbosity ()); - proc.set_threads (polygons.store ()->threads ()); - - compound_local_operation op (&node); - - std::auto_ptr res (new db::DeepEdges (polygons.derived ())); - proc.run (&op, polygons.layer (), other_layers, res->deep_layer ().layer ()); - - return res.release (); } EdgePairsDelegate * diff --git a/src/db/db/dbDeepRegion.h b/src/db/db/dbDeepRegion.h index 0b0c0f62e..7dc15d51f 100644 --- a/src/db/db/dbDeepRegion.h +++ b/src/db/db/dbDeepRegion.h @@ -132,6 +132,8 @@ public: void set_is_merged (bool f); + const DeepLayer &merged_deep_layer () const; + protected: virtual void merged_semantics_changed (); virtual void min_coherence_changed (); @@ -157,7 +159,6 @@ private: void init (); void ensure_merged_polygons_valid () const; - const DeepLayer &merged_deep_layer () const; DeepLayer and_or_not_with(const DeepRegion *other, bool and_op) const; std::pair and_and_not_with (const DeepRegion *other) const; DeepRegion *apply_filter (const PolygonFilterBase &filter) const; diff --git a/src/db/db/dbHierProcessor.cc b/src/db/db/dbHierProcessor.cc index b600d5734..8a819278c 100644 --- a/src/db/db/dbHierProcessor.cc +++ b/src/db/db/dbHierProcessor.cc @@ -718,8 +718,8 @@ struct interaction_registration_shape2shape : db::box_scanner_receiver2 { public: - interaction_registration_shape2shape (db::Layout *layout, shape_interactions *result, unsigned int intruder_layer) - : mp_result (result), mp_layout (layout), m_intruder_layer (intruder_layer) + interaction_registration_shape2shape (db::Layout *layout, shape_interactions *result, unsigned int intruder_layer_index) + : mp_result (result), mp_layout (layout), m_intruder_layer_index (intruder_layer_index) { // nothing yet .. } @@ -735,9 +735,9 @@ public: // In order to guarantee the refs come from the subject layout, we'd need to // rewrite them db::shape_reference_translator rt (mp_layout); - mp_result->add_intruder_shape (id2, m_intruder_layer, rt (*ref2)); + mp_result->add_intruder_shape (id2, m_intruder_layer_index, rt (*ref2)); } else { - mp_result->add_intruder_shape (id2, m_intruder_layer, *ref2); + mp_result->add_intruder_shape (id2, m_intruder_layer_index, *ref2); } } @@ -747,7 +747,7 @@ public: private: shape_interactions *mp_result; db::Layout *mp_layout; - unsigned int m_intruder_layer; + unsigned int m_intruder_layer_index; }; template @@ -755,8 +755,8 @@ struct interaction_registration_shape1 : db::box_scanner_receiver2 { public: - interaction_registration_shape1 (shape_interactions *result, unsigned int intruder_layer) - : mp_result (result), m_intruder_layer (intruder_layer) + interaction_registration_shape1 (shape_interactions *result, unsigned int intruder_layer_index) + : mp_result (result), m_intruder_layer_index (intruder_layer_index) { // nothing yet .. } @@ -767,14 +767,14 @@ public: mp_result->add_subject_shape (id1, *ref1); } if (!mp_result->has_intruder_shape_id (id2)) { - mp_result->add_intruder_shape (id2, m_intruder_layer, *ref2); + mp_result->add_intruder_shape (id2, m_intruder_layer_index, *ref2); } mp_result->add_interaction (id1, id2); } private: shape_interactions *mp_result; - unsigned int m_intruder_layer; + unsigned int m_intruder_layer_index; }; template @@ -782,8 +782,8 @@ struct interaction_registration_shape1 : db::box_scanner_receiver { public: - interaction_registration_shape1 (shape_interactions *result, unsigned int intruder_layer) - : mp_result (result), m_intruder_layer (intruder_layer) + interaction_registration_shape1 (shape_interactions *result, unsigned int intruder_layer_index) + : mp_result (result), m_intruder_layer_index (intruder_layer_index) { // nothing yet .. } @@ -794,14 +794,14 @@ public: mp_result->add_subject_shape (id1, *ref1); } if (!mp_result->has_intruder_shape_id (id2)) { - mp_result->add_intruder_shape (id2, m_intruder_layer, *ref2); + mp_result->add_intruder_shape (id2, m_intruder_layer_index, *ref2); } mp_result->add_interaction (id1, id2); } private: shape_interactions *mp_result; - unsigned int m_intruder_layer; + unsigned int m_intruder_layer_index; }; template @@ -809,8 +809,8 @@ struct interaction_registration_shape2inst : db::box_scanner_receiver2 { public: - interaction_registration_shape2inst (db::Layout *subject_layout, const db::Layout *intruder_layout, unsigned int intruder_layer, db::Coord dist, shape_interactions *result) - : mp_subject_layout (subject_layout), mp_intruder_layout (intruder_layout), m_intruder_layer (intruder_layer), m_dist (dist), mp_result (result) + interaction_registration_shape2inst (db::Layout *subject_layout, const db::Layout *intruder_layout, unsigned int intruder_layer, unsigned int intruder_layer_index, db::Coord dist, shape_interactions *result) + : mp_subject_layout (subject_layout), mp_intruder_layout (intruder_layout), m_intruder_layer (intruder_layer), m_intruder_layer_index (intruder_layer_index), m_dist (dist), mp_result (result) { // nothing yet .. } @@ -836,7 +836,7 @@ public: private: db::Layout *mp_subject_layout; const db::Layout *mp_intruder_layout; - unsigned int m_intruder_layer; + unsigned int m_intruder_layer, m_intruder_layer_index; db::Coord m_dist; shape_interactions *mp_result; std::unordered_map m_inst_shape_ids; @@ -862,7 +862,7 @@ private: if (k == m_inst_shape_ids.end ()) { k = m_inst_shape_ids.insert (std::make_pair (ref2, mp_result->next_id ())).first; - mp_result->add_intruder_shape (k->second, m_intruder_layer, ref2); + mp_result->add_intruder_shape (k->second, m_intruder_layer_index, ref2); } @@ -1686,10 +1686,10 @@ template struct scan_shape2shape_same_layer { void - operator () (const db::Shapes *subject_shapes, unsigned int subject_id0, const std::set &intruders, unsigned int intruder_layer, shape_interactions &interactions, db::Coord dist) const + operator () (const db::Shapes *subject_shapes, unsigned int subject_id0, const std::set &intruders, unsigned int intruder_layer_index, shape_interactions &interactions, db::Coord dist) const { db::box_scanner2 scanner; - interaction_registration_shape1 rec (&interactions, intruder_layer); + interaction_registration_shape1 rec (&interactions, intruder_layer_index); unsigned int id = subject_id0; for (db::Shapes::shape_iterator i = subject_shapes->begin (shape_flags ()); !i.at_end (); ++i) { @@ -1734,10 +1734,10 @@ template struct scan_shape2shape_different_layers { void - operator () (db::Layout *layout, const db::Shapes *subject_shapes, const db::Shapes *intruder_shapes, unsigned int subject_id0, const std::set *intruders, unsigned int intruder_layer, shape_interactions &interactions, db::Coord dist) const + operator () (db::Layout *layout, const db::Shapes *subject_shapes, const db::Shapes *intruder_shapes, unsigned int subject_id0, const std::set *intruders, unsigned int intruder_layer_index, shape_interactions &interactions, db::Coord dist) const { db::box_scanner2 scanner; - interaction_registration_shape2shape rec (layout, &interactions, intruder_layer); + interaction_registration_shape2shape rec (layout, &interactions, intruder_layer_index); unsigned int id = subject_id0; for (db::Shapes::shape_iterator i = subject_shapes->begin (shape_flags ()); !i.at_end (); ++i) { @@ -1790,7 +1790,8 @@ local_processor::compute_local_cell (const db::local_processor_conte } - for (std::vector::const_iterator il = contexts.intruder_layers ().begin (); il != contexts.intruder_layers ().end (); ++il) { + unsigned int il_index = 0; + for (std::vector::const_iterator il = contexts.intruder_layers ().begin (); il != contexts.intruder_layers ().end (); ++il, ++il_index) { const db::Shapes *intruder_shapes = 0; if (intruder_cell) { @@ -1811,12 +1812,12 @@ local_processor::compute_local_cell (const db::local_processor_conte if (subject_cell == intruder_cell && contexts.subject_layer () == *il) { - scan_shape2shape_same_layer () (subject_shapes, subject_id0, ipl == intruders.second.end () ? empty_intruders : ipl->second, *il, interactions, op->dist ()); + scan_shape2shape_same_layer () (subject_shapes, subject_id0, ipl == intruders.second.end () ? empty_intruders : ipl->second, il_index, interactions, op->dist ()); } else { db::Layout *target_layout = (mp_subject_layout == mp_intruder_layout ? 0 : mp_subject_layout); - scan_shape2shape_different_layers () (target_layout, subject_shapes, intruder_shapes, subject_id0, &(ipl == intruders.second.end () ? empty_intruders : ipl->second), *il, interactions, op->dist ()); + scan_shape2shape_different_layers () (target_layout, subject_shapes, intruder_shapes, subject_id0, &(ipl == intruders.second.end () ? empty_intruders : ipl->second), il_index, interactions, op->dist ()); } @@ -1825,7 +1826,7 @@ local_processor::compute_local_cell (const db::local_processor_conte if (! subject_shapes->empty () && ! ((! intruder_cell || intruder_cell->begin ().at_end ()) && intruders.first.empty ())) { db::box_scanner2 scanner; - interaction_registration_shape2inst rec (mp_subject_layout, mp_intruder_layout, *il, op->dist (), &interactions); + interaction_registration_shape2inst rec (mp_subject_layout, mp_intruder_layout, *il, il_index, op->dist (), &interactions); unsigned int id = subject_id0; for (db::Shapes::shape_iterator i = subject_shapes->begin (shape_flags ()); !i.at_end (); ++i) { @@ -2014,11 +2015,12 @@ local_processor::run_flat (const generic_shape_iterator &subject } } - for (typename std::vector >::const_iterator il = intruders.begin (); il != intruders.end (); ++il) { + unsigned int il_index = 0; + for (typename std::vector >::const_iterator il = intruders.begin (); il != intruders.end (); ++il, ++il_index) { if (*il == subjects) { - scan_shape2shape_same_layer_flat () ((unsigned int) (il - intruders.begin ()), interactions, op->dist ()); + scan_shape2shape_same_layer_flat () (il_index, interactions, op->dist ()); } else { - scan_shape2shape_different_layers_flat () (*il, common_box, (unsigned int) (il - intruders.begin ()), interactions, op->dist ()); + scan_shape2shape_different_layers_flat () (*il, common_box, il_index, interactions, op->dist ()); } } diff --git a/src/db/unit_tests/dbCompoundOperationTests.cc b/src/db/unit_tests/dbCompoundOperationTests.cc index 361ca2309..28de97672 100644 --- a/src/db/unit_tests/dbCompoundOperationTests.cc +++ b/src/db/unit_tests/dbCompoundOperationTests.cc @@ -501,7 +501,7 @@ TEST(11_EdgeFilterOperation) db::compare_layouts (_this, ly, tl::testsrc () + "/testdata/drc/compound_au11.gds"); } -TEST(12_EdgeBooleanOperations) +void run_test12 (tl::TestBase *_this, bool deep) { db::Layout ly; { @@ -512,14 +512,26 @@ TEST(12_EdgeBooleanOperations) reader.read (ly); } + db::DeepShapeStore dss; + db::RegionCheckOptions check_options; check_options.metrics = db::Projection; unsigned int l1 = ly.get_layer (db::LayerProperties (1, 0)); - db::Region r (db::RecursiveShapeIterator (ly, ly.cell (*ly.begin_top_down ()), l1)); + db::Region r; + if (deep) { + r = db::Region (db::RecursiveShapeIterator (ly, ly.cell (*ly.begin_top_down ()), l1), dss); + } else { + r = db::Region (db::RecursiveShapeIterator (ly, ly.cell (*ly.begin_top_down ()), l1)); + } unsigned int l2 = ly.get_layer (db::LayerProperties (2, 0)); - db::Region r2 (db::RecursiveShapeIterator (ly, ly.cell (*ly.begin_top_down ()), l2)); + db::Region r2; + if (deep) { + r2 = db::Region (db::RecursiveShapeIterator (ly, ly.cell (*ly.begin_top_down ()), l2), dss); + } else { + r2 = db::Region (db::RecursiveShapeIterator (ly, ly.cell (*ly.begin_top_down ()), l2)); + } db::CompoundRegionOperationPrimaryNode *primary = new db::CompoundRegionOperationPrimaryNode (); db::CompoundRegionToEdgeProcessingOperationNode *primary_edges = new db::CompoundRegionToEdgeProcessingOperationNode (new db::PolygonToEdgeProcessor (), primary, true); @@ -563,5 +575,19 @@ TEST(12_EdgeBooleanOperations) res.insert_into (&ly, *ly.begin_top_down (), l1005); CHECKPOINT(); - db::compare_layouts (_this, ly, tl::testsrc () + "/testdata/drc/compound_au12.gds"); + if (deep) { + db::compare_layouts (_this, ly, tl::testsrc () + "/testdata/drc/compound_au12d.gds"); + } else { + db::compare_layouts (_this, ly, tl::testsrc () + "/testdata/drc/compound_au12.gds"); + } +} + +TEST(12_EdgeBooleanOperations) +{ + run_test12 (_this, false); +} + +TEST(12d_EdgeBooleanOperations) +{ + run_test12 (_this, true); } diff --git a/testdata/drc/compound_au12d.gds b/testdata/drc/compound_au12d.gds new file mode 100644 index 0000000000000000000000000000000000000000..8bc661cd6f4eafd9ae14f73dd695c8bcb0d59776 GIT binary patch literal 2834 zcmb7GyH3ME5S-YK9Y}!iNQ2TKQBWYDLr4fIhynx>2&F(&d`L;em6 zd8c)~dH8tuv9;G-zr8tcqgiHfaeUG`Z6m4x@l=2k;^_duYCV*{*45|$VGl6TI*Mk2 zD8VS6_VJ1Q-y~3()0{PnL%rkwnGpR;6r2h1v#c^uXSv+B>!A=I7eMl)W3?;9*FIok zqa(!k2GydY`Csy+xpMDQT7*57i_}1f&o(e~JB%_%_1xr|;saYt(oVi#X*hpHc)&#S zJjb8Gm!E}rxd$jxZBq}!Xr9Zc^q5e+l#IHU@e*~kGNU`XK&n$V#GYp5i6Ty_m2xn( zrd+Qsu6!pl88WDBe|%A-jOlW<>x;_H#yBW5L2@f>4Vk)rYkKY|%F7c)(@tC(Jq`-c zyW*Rno^0xJ+HJ242Qwi{@}CV7Ku;9ijhCU>LH{|_if5f;)btKEMD4fa^i(6QN9~t? zT{&;@AzP{qENs?m4tvaN>l51WfYy59=;fL=o z6zgmZig7ju#W)*-Vw{aZF&@RBh|A%J42p5K5h#bVIZ;DrEAS`=#cU;ua&Dy)rTyL1 mB|XHRUMI@lO7%_RiDEp8;mUpYcyl>@-pN($QSv)8itq>TU(MG5 literal 0 HcmV?d00001