From dfac3b1b44d96e8bfc5835599d0b9424e378ce1a Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 17 Jan 2023 11:24:03 +0100 Subject: [PATCH] WIP: Implemented property support for two-boolean --- src/db/db/dbAsIfFlatRegion.cc | 28 ++++- src/db/db/dbDeepRegion.cc | 5 +- src/db/db/dbLocalOperation.cc | 142 ++++++++++++---------- src/db/db/dbLocalOperation.h | 38 ++++-- src/db/unit_tests/dbHierProcessorTests.cc | 4 +- testdata/algo/flat_region_au40.gds | Bin 11322 -> 11498 bytes 6 files changed, 137 insertions(+), 80 deletions(-) diff --git a/src/db/db/dbAsIfFlatRegion.cc b/src/db/db/dbAsIfFlatRegion.cc index 858582d9a..ea4fac536 100644 --- a/src/db/db/dbAsIfFlatRegion.cc +++ b/src/db/db/dbAsIfFlatRegion.cc @@ -1584,9 +1584,7 @@ AsIfFlatRegion::andnot_with (const Region &other, PropertyConstraint property_co // Nothing to do return std::make_pair (new EmptyRegion (), clone ()); - } else { - - // @@@ TODO: implement property constraint + } else if (property_constraint == db::IgnoreProperties) { // Generic case db::EdgeProcessor ep (report_progress (), progress_desc ()); @@ -1629,6 +1627,30 @@ AsIfFlatRegion::andnot_with (const Region &other, PropertyConstraint property_co return std::make_pair (new_region1.release (), new_region2.release ()); + } else { + + db::generic_shape_iterator polygons (db::make_wp_iter (begin ())); + + std::unique_ptr output1 (new FlatRegion ()); + std::unique_ptr output2 (new FlatRegion ()); + std::vector results; + results.push_back (&output1->raw_polygons ()); + results.push_back (&output2->raw_polygons ()); + + db::two_bool_and_not_local_operation_with_properties op (output1->properties_repository (), output2->properties_repository (), properties_repository (), &other.properties_repository (), property_constraint); + + db::local_processor proc; + proc.set_base_verbosity (base_verbosity ()); + proc.set_description (progress_desc ()); + proc.set_report_progress (report_progress ()); + + std::vector > others; + others.push_back (db::make_wp_iter (other.begin ())); + + proc.run_flat (polygons, others, std::vector (), &op, results); + + return std::make_pair (output1.release (), output2.release ()); + } } diff --git a/src/db/db/dbDeepRegion.cc b/src/db/db/dbDeepRegion.cc index 976c29b84..0cec4e28d 100644 --- a/src/db/db/dbDeepRegion.cc +++ b/src/db/db/dbDeepRegion.cc @@ -881,7 +881,10 @@ DeepRegion::and_and_not_with (const DeepRegion *other, PropertyConstraint proper } else { - db::TwoBoolAndNotLocalOperationWithProperties op (&deep_layer ().layout (), &other->deep_layer ().layout (), property_constraint); + db::PropertiesRepository *pr_out1 = &dl_out1.layout ().properties_repository (); + db::PropertiesRepository *pr_out2 = &dl_out2.layout ().properties_repository (); + const db::PropertiesRepository *pr = &deep_layer ().layout ().properties_repository (); + db::TwoBoolAndNotLocalOperationWithProperties op (pr_out1, pr_out2, pr, pr, property_constraint); db::local_processor proc (const_cast (&deep_layer ().layout ()), const_cast (&deep_layer ().initial_cell ()), &other->deep_layer ().layout (), &other->deep_layer ().initial_cell (), deep_layer ().breakout_cells (), other->deep_layer ().breakout_cells ()); proc.set_base_verbosity (base_verbosity ()); diff --git a/src/db/db/dbLocalOperation.cc b/src/db/db/dbLocalOperation.cc index 6fb2e517c..3797d2b35 100644 --- a/src/db/db/dbLocalOperation.cc +++ b/src/db/db/dbLocalOperation.cc @@ -116,44 +116,48 @@ template class DB_PUBLIC local_operation +bool_and_or_not_local_operation::bool_and_or_not_local_operation (bool is_and) : m_is_and (is_and) { // .. nothing yet .. } +template OnEmptyIntruderHint -BoolAndOrNotLocalOperation::on_empty_intruder_hint () const +bool_and_or_not_local_operation::on_empty_intruder_hint () const { return m_is_and ? Drop : Copy; } +template std::string -BoolAndOrNotLocalOperation::description () const +bool_and_or_not_local_operation::description () const { return m_is_and ? tl::to_string (tr ("AND operation")) : tl::to_string (tr ("NOT operation")); } +template void -BoolAndOrNotLocalOperation::do_compute_local (db::Layout *layout, const shape_interactions &interactions, std::vector > &results, size_t max_vertex_count, double area_ratio) const +bool_and_or_not_local_operation::do_compute_local (db::Layout *layout, const shape_interactions &interactions, std::vector > &results, size_t max_vertex_count, double area_ratio) const { tl_assert (results.size () == 1); - std::unordered_set &result = results.front (); + std::unordered_set &result = results.front (); db::EdgeProcessor ep; size_t p1 = 0, p2 = 1; - std::set others; - for (shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) { - for (shape_interactions::iterator2 j = i->second.begin (); j != i->second.end (); ++j) { + std::set others; + for (auto i = interactions.begin (); i != interactions.end (); ++i) { + for (auto j = i->second.begin (); j != i->second.end (); ++j) { others.insert (interactions.intruder_shape (*j).second); } } - for (shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) { + for (auto i = interactions.begin (); i != interactions.end (); ++i) { - const db::PolygonRef &subject = interactions.subject_shape (i->first); + const TR &subject = interactions.subject_shape (i->first); if (others.find (subject) != others.end ()) { if (m_is_and) { result.insert (subject); @@ -164,7 +168,7 @@ BoolAndOrNotLocalOperation::do_compute_local (db::Layout *layout, const shape_in result.insert (subject); } } else { - for (db::PolygonRef::polygon_edge_iterator e = subject.begin_edge (); ! e.at_end(); ++e) { + for (auto e = subject.begin_edge (); ! e.at_end(); ++e) { ep.insert (*e, p1); } p1 += 2; @@ -174,15 +178,15 @@ BoolAndOrNotLocalOperation::do_compute_local (db::Layout *layout, const shape_in if (! others.empty () && p1 > 0) { - for (std::set::const_iterator o = others.begin (); o != others.end (); ++o) { - for (db::PolygonRef::polygon_edge_iterator e = o->begin_edge (); ! e.at_end(); ++e) { + for (auto o = others.begin (); o != others.end (); ++o) { + for (auto e = o->begin_edge (); ! e.at_end(); ++e) { ep.insert (*e, p2); } p2 += 2; } db::BooleanOp op (m_is_and ? db::BooleanOp::And : db::BooleanOp::ANotB); - db::PolygonRefGenerator pr (layout, result); + db::polygon_ref_generator pr (layout, result); db::PolygonSplitter splitter (pr, area_ratio, max_vertex_count); db::PolygonGenerator pg (splitter, true, true); ep.set_base_verbosity (50); @@ -191,6 +195,9 @@ BoolAndOrNotLocalOperation::do_compute_local (db::Layout *layout, const shape_in } } +template class DB_PUBLIC bool_and_or_not_local_operation; +template class DB_PUBLIC bool_and_or_not_local_operation; + // --------------------------------------------------------------------------------------------- // BoolAndOrNotLocalOperationWithProperties implementation @@ -314,47 +321,49 @@ bool_and_or_not_local_operation_with_properties::do_compute_local (d } } -template class bool_and_or_not_local_operation_with_properties; -template class bool_and_or_not_local_operation_with_properties; +template class DB_PUBLIC bool_and_or_not_local_operation_with_properties; +template class DB_PUBLIC bool_and_or_not_local_operation_with_properties; // --------------------------------------------------------------------------------------------- // TwoBoolAndNotLocalOperation implementation -TwoBoolAndNotLocalOperation::TwoBoolAndNotLocalOperation () - : db::local_operation () +template +two_bool_and_not_local_operation::two_bool_and_not_local_operation () + : db::local_operation () { // .. nothing yet .. } +template void -TwoBoolAndNotLocalOperation::do_compute_local (db::Layout *layout, const shape_interactions &interactions, std::vector > &results, size_t max_vertex_count, double area_ratio) const +two_bool_and_not_local_operation::do_compute_local (db::Layout *layout, const shape_interactions &interactions, std::vector > &results, size_t max_vertex_count, double area_ratio) const { tl_assert (results.size () == 2); db::EdgeProcessor ep; - std::unordered_set &result0 = results [0]; - std::unordered_set &result1 = results [1]; + std::unordered_set &result0 = results [0]; + std::unordered_set &result1 = results [1]; size_t p1 = 0, p2 = 1; - std::set others; - for (db::shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) { - for (db::shape_interactions::iterator2 j = i->second.begin (); j != i->second.end (); ++j) { + std::set others; + for (auto i = interactions.begin (); i != interactions.end (); ++i) { + for (auto j = i->second.begin (); j != i->second.end (); ++j) { others.insert (interactions.intruder_shape (*j).second); } } - for (db::shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) { + for (auto i = interactions.begin (); i != interactions.end (); ++i) { - const db::PolygonRef &subject = interactions.subject_shape (i->first); + const TS &subject = interactions.subject_shape (i->first); if (others.find (subject) != others.end ()) { result0.insert (subject); } else if (i->second.empty ()) { // shortcut (not: keep, and: drop) result1.insert (subject); } else { - for (db::PolygonRef::polygon_edge_iterator e = subject.begin_edge (); ! e.at_end(); ++e) { + for (auto e = subject.begin_edge (); ! e.at_end(); ++e) { ep.insert (*e, p1); } p1 += 2; @@ -364,20 +373,20 @@ TwoBoolAndNotLocalOperation::do_compute_local (db::Layout *layout, const shape_i if (! others.empty () && p1 > 0) { - for (std::set::const_iterator o = others.begin (); o != others.end (); ++o) { - for (db::PolygonRef::polygon_edge_iterator e = o->begin_edge (); ! e.at_end(); ++e) { + for (auto o = others.begin (); o != others.end (); ++o) { + for (auto e = o->begin_edge (); ! e.at_end(); ++e) { ep.insert (*e, p2); } p2 += 2; } db::BooleanOp op0 (db::BooleanOp::And); - db::PolygonRefGenerator pr0 (layout, result0); + db::polygon_ref_generator pr0 (layout, result0); db::PolygonSplitter splitter0 (pr0, area_ratio, max_vertex_count); db::PolygonGenerator pg0 (splitter0, true, true); db::BooleanOp op1 (db::BooleanOp::ANotB); - db::PolygonRefGenerator pr1 (layout, result1); + db::polygon_ref_generator pr1 (layout, result1); db::PolygonSplitter splitter1 (pr1, area_ratio, max_vertex_count); db::PolygonGenerator pg1 (splitter1, true, true); @@ -392,52 +401,59 @@ TwoBoolAndNotLocalOperation::do_compute_local (db::Layout *layout, const shape_i } -std::string TwoBoolAndNotLocalOperation::description () const +template +std::string two_bool_and_not_local_operation::description () const { return tl::to_string (tr ("ANDNOT operation")); } +template class DB_PUBLIC two_bool_and_not_local_operation; +template class DB_PUBLIC two_bool_and_not_local_operation; + // --------------------------------------------------------------------------------------------- // TwoBoolAndNotLocalOperationWithProperties implementation -TwoBoolAndNotLocalOperationWithProperties::TwoBoolAndNotLocalOperationWithProperties (const db::Layout *subject_layout, const db::Layout *intruder_layout, db::PropertyConstraint property_constraint) - : db::local_operation (), - m_property_constraint (property_constraint), mp_subject_layout (subject_layout), mp_intruder_layout (intruder_layout) +template +two_bool_and_not_local_operation_with_properties::two_bool_and_not_local_operation_with_properties (db::PropertiesRepository *target1_pr, db::PropertiesRepository *target2_pr, const db::PropertiesRepository *subject_pr, const db::PropertiesRepository *intruder_pr, db::PropertyConstraint property_constraint) + : db::local_operation, db::object_with_properties, db::object_with_properties > (), + m_property_constraint (property_constraint), mp_target1_pr (target1_pr), mp_target2_pr (target2_pr), mp_subject_pr (subject_pr), mp_intruder_pr (intruder_pr) { // .. nothing yet .. } +template void -TwoBoolAndNotLocalOperationWithProperties::do_compute_local (db::Layout *layout, const shape_interactions &interactions, std::vector > &results, size_t max_vertex_count, double area_ratio) const +two_bool_and_not_local_operation_with_properties::do_compute_local (db::Layout *layout, const shape_interactions, db::object_with_properties > &interactions, std::vector > > &results, size_t max_vertex_count, double area_ratio) const { - db::PropertyMapper pms (*layout, *mp_subject_layout); - db::PropertyMapper pmi (*layout, *mp_intruder_layout); + db::PropertyMapper pms (*mp_target1_pr, *mp_subject_pr); + db::PropertyMapper pmi (*mp_target1_pr, *mp_intruder_pr); + db::PropertyMapper pm12 (*mp_target2_pr, *mp_target1_pr); tl_assert (results.size () == 2); - std::unordered_set &result0 = results [0]; - std::unordered_set &result1 = results [1]; + std::unordered_set > &result0 = results [0]; + std::unordered_set > &result1 = results [1]; db::EdgeProcessor ep; - std::map, std::set > > by_prop_id; + std::map, std::set > > by_prop_id; - for (shape_interactions::iterator i = interactions.begin (); i != interactions.end (); ++i) { + for (auto i = interactions.begin (); i != interactions.end (); ++i) { - const db::PolygonRefWithProperties &subject = interactions.subject_shape (i->first); + const db::object_with_properties &subject = interactions.subject_shape (i->first); if (i->second.empty ()) { - result1.insert (db::PolygonRefWithProperties (subject, pms (subject.properties_id ()))); + result1.insert (db::object_with_properties (subject, pms (subject.properties_id ()))); } else { db::properties_id_type prop_id_s = pms (subject.properties_id ()); - std::pair, std::set > &shapes_by_prop = by_prop_id [prop_id_s]; + auto &shapes_by_prop = by_prop_id [prop_id_s]; shapes_by_prop.first.push_front (subject); - for (shape_interactions::iterator2 j = i->second.begin (); j != i->second.end (); ++j) { - const db::PolygonRefWithProperties &intruder = interactions.intruder_shape (*j).second; + for (auto j = i->second.begin (); j != i->second.end (); ++j) { + const db::object_with_properties &intruder = interactions.intruder_shape (*j).second; db::properties_id_type prop_id_i = (m_property_constraint != db::NoPropertyConstraint ? pmi (intruder.properties_id ()) : prop_id_s); if ((prop_id_i != prop_id_s) == (m_property_constraint == db::DifferentPropertiesConstraint)) { shapes_by_prop.second.insert (intruder); @@ -453,19 +469,19 @@ TwoBoolAndNotLocalOperationWithProperties::do_compute_local (db::Layout *layout, ep.clear (); size_t p1 = 0, p2 = 1; - const std::set &others = p2s->second.second; + const std::set &others = p2s->second.second; db::properties_id_type prop_id = p2s->first; for (auto s = p2s->second.first.begin (); s != p2s->second.first.end (); ++s) { - const db::PolygonRef &subject = *s; + const TR &subject = *s; if (others.find (subject) != others.end ()) { - result0.insert (db::PolygonRefWithProperties (subject, prop_id)); + result0.insert (db::object_with_properties (subject, prop_id)); } else if (others.empty ()) { // shortcut (not: keep, and: drop) - result1.insert (db::PolygonRefWithProperties (subject, prop_id)); + result1.insert (db::object_with_properties (subject, pm12 (prop_id))); } else { - for (db::PolygonRef::polygon_edge_iterator e = subject.begin_edge (); ! e.at_end(); ++e) { + for (auto e = subject.begin_edge (); ! e.at_end(); ++e) { ep.insert (*e, p1); } p1 += 2; @@ -475,23 +491,23 @@ TwoBoolAndNotLocalOperationWithProperties::do_compute_local (db::Layout *layout, if (! others.empty () && p1 > 0) { - for (std::set::const_iterator o = others.begin (); o != others.end (); ++o) { - for (db::PolygonRef::polygon_edge_iterator e = o->begin_edge (); ! e.at_end(); ++e) { + for (auto o = others.begin (); o != others.end (); ++o) { + for (auto e = o->begin_edge (); ! e.at_end(); ++e) { ep.insert (*e, p2); } p2 += 2; } - std::unordered_set result0_wo_props; - std::unordered_set result1_wo_props; + std::unordered_set result0_wo_props; + std::unordered_set result1_wo_props; db::BooleanOp op0 (db::BooleanOp::And); - db::PolygonRefGenerator pr0 (layout, result0_wo_props); + db::polygon_ref_generator pr0 (layout, result0_wo_props); db::PolygonSplitter splitter0 (pr0, area_ratio, max_vertex_count); db::PolygonGenerator pg0 (splitter0, true, true); db::BooleanOp op1 (db::BooleanOp::ANotB); - db::PolygonRefGenerator pr1 (layout, result1_wo_props); + db::polygon_ref_generator pr1 (layout, result1_wo_props); db::PolygonSplitter splitter1 (pr1, area_ratio, max_vertex_count); db::PolygonGenerator pg1 (splitter1, true, true); @@ -503,10 +519,10 @@ TwoBoolAndNotLocalOperationWithProperties::do_compute_local (db::Layout *layout, ep.process (procs); for (auto r = result0_wo_props.begin (); r != result0_wo_props.end (); ++r) { - result0.insert (db::PolygonRefWithProperties (*r, prop_id)); + result0.insert (db::object_with_properties (*r, prop_id)); } for (auto r = result1_wo_props.begin (); r != result1_wo_props.end (); ++r) { - result1.insert (db::PolygonRefWithProperties (*r, prop_id)); + result1.insert (db::object_with_properties (*r, pm12 (prop_id))); } } @@ -514,11 +530,15 @@ TwoBoolAndNotLocalOperationWithProperties::do_compute_local (db::Layout *layout, } } -std::string TwoBoolAndNotLocalOperationWithProperties::description () const +template +std::string two_bool_and_not_local_operation_with_properties::description () const { return tl::to_string (tr ("ANDNOT operation")); } +template class DB_PUBLIC two_bool_and_not_local_operation_with_properties; +template class DB_PUBLIC two_bool_and_not_local_operation_with_properties; + // --------------------------------------------------------------------------------------------- SelfOverlapMergeLocalOperation::SelfOverlapMergeLocalOperation (unsigned int wrap_count) diff --git a/src/db/db/dbLocalOperation.h b/src/db/db/dbLocalOperation.h index 0a11660bd..0323d06e5 100644 --- a/src/db/db/dbLocalOperation.h +++ b/src/db/db/dbLocalOperation.h @@ -133,13 +133,15 @@ protected: /** * @brief Implements a boolean AND or NOT operation */ -class DB_PUBLIC BoolAndOrNotLocalOperation - : public local_operation + +template +class DB_PUBLIC bool_and_or_not_local_operation + : public local_operation { public: - BoolAndOrNotLocalOperation (bool is_and); + bool_and_or_not_local_operation (bool is_and); - virtual void do_compute_local (db::Layout *layout, const shape_interactions &interactions, std::vector > &result, size_t max_vertex_count, double area_ratio) const; + virtual void do_compute_local (db::Layout *layout, const shape_interactions &interactions, std::vector > &result, size_t max_vertex_count, double area_ratio) const; virtual OnEmptyIntruderHint on_empty_intruder_hint () const; virtual std::string description () const; @@ -147,6 +149,8 @@ private: bool m_is_and; }; +typedef bool_and_or_not_local_operation BoolAndOrNotLocalOperation; + /** * @brief Implements a boolean AND or NOT operation with property handling */ @@ -176,36 +180,44 @@ typedef bool_and_or_not_local_operation_with_properties + +template +class DB_PUBLIC two_bool_and_not_local_operation + : public local_operation { public: - TwoBoolAndNotLocalOperation (); + two_bool_and_not_local_operation (); - virtual void do_compute_local (db::Layout *layout, const shape_interactions &interactions, std::vector > &result, size_t max_vertex_count, double area_ratio) const; + virtual void do_compute_local (db::Layout *layout, const shape_interactions &interactions, std::vector > &result, size_t max_vertex_count, double area_ratio) const; virtual std::string description () const; }; +typedef two_bool_and_not_local_operation TwoBoolAndNotLocalOperation; + /** * @brief Implements a boolean AND plus NOT operation * * This processor delivers two outputs: the first one having the AND result, the second * one having the NOT result. */ -class DB_PUBLIC TwoBoolAndNotLocalOperationWithProperties - : public local_operation +template +class DB_PUBLIC two_bool_and_not_local_operation_with_properties + : public local_operation, db::object_with_properties, db::object_with_properties > { public: - TwoBoolAndNotLocalOperationWithProperties (const db::Layout *subject_layout, const db::Layout *intruder_layout, db::PropertyConstraint property_constraint); + two_bool_and_not_local_operation_with_properties (db::PropertiesRepository *target1_pr, db::PropertiesRepository *target2_pr, const db::PropertiesRepository *subject_pr, const db::PropertiesRepository *intruder_pr, db::PropertyConstraint property_constraint); - virtual void do_compute_local (db::Layout *layout, const shape_interactions &interactions, std::vector > &result, size_t max_vertex_count, double area_ratio) const; + virtual void do_compute_local (db::Layout *layout, const shape_interactions, db::object_with_properties > &interactions, std::vector > > &result, size_t max_vertex_count, double area_ratio) const; virtual std::string description () const; private: db::PropertyConstraint m_property_constraint; - const db::Layout *mp_subject_layout, *mp_intruder_layout; + db::PropertiesRepository *mp_target1_pr, *mp_target2_pr; + const db::PropertiesRepository *mp_subject_pr, *mp_intruder_pr; }; +typedef two_bool_and_not_local_operation_with_properties TwoBoolAndNotLocalOperationWithProperties; + /** * @brief Implements a merge operation with an overlap count * With a given wrap_count, the result will only contains shapes where diff --git a/src/db/unit_tests/dbHierProcessorTests.cc b/src/db/unit_tests/dbHierProcessorTests.cc index e22e96876..d98e48c76 100644 --- a/src/db/unit_tests/dbHierProcessorTests.cc +++ b/src/db/unit_tests/dbHierProcessorTests.cc @@ -55,7 +55,7 @@ class BoolAndOrNotWithSizedLocalOperation { public: BoolAndOrNotWithSizedLocalOperation (bool is_and, db::Coord dist) - : BoolAndOrNotLocalOperation (is_and), m_dist (dist) + : db::BoolAndOrNotLocalOperation (is_and), m_dist (dist) { // .. nothing yet .. } @@ -72,7 +72,7 @@ public: } } - BoolAndOrNotLocalOperation::do_compute_local (layout, sized_interactions, results, max_vertex_count, area_ratio); + db::BoolAndOrNotLocalOperation::do_compute_local (layout, sized_interactions, results, max_vertex_count, area_ratio); } db::Coord dist () const diff --git a/testdata/algo/flat_region_au40.gds b/testdata/algo/flat_region_au40.gds index 9fc94ae5e90be620bde7558869ce1d12203d7a1e..a99bba994d570dd01f49dbe1bd168e549b9e814d 100644 GIT binary patch delta 732 zcmah_J!lhQ7=Ck?TrSPe<-Gh{64U%NX>6mU)u4hR2aQl@f({v?S8xfKkUqR9SRw$oI}vT!BtSGTL%LTG}wYe-Lm*4ht)xFc)#a;-|zi-p7&kl`<)~S z#BmimYvJsNWFeWH6rjbw{mo=r`A7QA-3uJ<@uf8fk>KWtBf-NF^AqD&lz1Y3NGUHv zSDVQ9mMFwNej^_zQjS?ooP;dLHVfy4{6(_LMArXL6f~mw1-48hg)YA)3Vx+iT-?SD zK7sfAz!+J*7JRUYcwsFgCyF?!J&f+&XmD6LD+kcm{}g+E|n77U+v^V|~j}Q=c_v z>g!HxSV>H{brL4GNZjOh#Bj{58*Ac8yaqivz<#2Q&Wsydfm_&16(@di#(KRRBljQT*U~E3K^0?D2QN&LI(@_-QD8Q;qia`-uJ=py`QTeY8nv; z&)3Kqj`trDjz}a)WMcf^KiBfHQ}Eln=XuV;j<6)8j|fS;!e?OtBTmF0VVxyKkH3b< zbR4~@5sq_FRMVI3`_r==*8CzyzCHHPf})17?_bA5VH=X@L(6}arG*!P+h_!qkqKPE zSTwQ6sv(;8B(TZhV=#pa)Wn;Ri8iZVP=si1N%9nsE!mXb3N7 z+8D%dVLw(yQ4S#%U&4@OCQ0!nVM9sWLp^WcUDCuRV>^io?xe;zW_@GU#@}QenoH=H(mCfE#IGegrVw5WwbUbbByMbQo}j`)8TyUSAm