From 7f71cc3a5696ba7be74fcf92e3c41fcec767d0f3 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 18 Feb 2019 22:24:34 +0100 Subject: [PATCH] Some bug fixes, added tests for hier DRC (at least for what is there yet) --- src/db/db/dbAsIfFlatRegion.cc | 16 ++++ src/db/db/dbDeepRegion.cc | 12 ++- src/drc/unit_tests/drcSimpleTests.cc | 42 +++++++++- src/drc/unit_tests/drcSuiteTests.cc | 19 ++++- testdata/drc/drcSimpleTests_4.drc | 49 +++++++++++ testdata/drc/drcSimpleTests_au4.oas | Bin 0 -> 1110 bytes testdata/drc/drcSuiteTests.drc | 120 ++++++++++++++++++--------- testdata/drc/drcSuiteTests_au5.oas | Bin 0 -> 11337 bytes testdata/drc/drcSuiteTests_au6.oas | Bin 0 -> 18358 bytes testdata/drc/drctest.gds | Bin 2858 -> 3000 bytes 10 files changed, 212 insertions(+), 46 deletions(-) create mode 100644 testdata/drc/drcSimpleTests_4.drc create mode 100644 testdata/drc/drcSimpleTests_au4.oas create mode 100644 testdata/drc/drcSuiteTests_au5.oas create mode 100644 testdata/drc/drcSuiteTests_au6.oas diff --git a/src/db/db/dbAsIfFlatRegion.cc b/src/db/db/dbAsIfFlatRegion.cc index 03782957f..0bfc03df1 100644 --- a/src/db/db/dbAsIfFlatRegion.cc +++ b/src/db/db/dbAsIfFlatRegion.cc @@ -440,6 +440,14 @@ template void AsIfFlatRegion::produce_markers_for_grid_check (con EdgePairs AsIfFlatRegion::grid_check (db::Coord gx, db::Coord gy) const { + if (gx < 0 || gy < 0) { + throw tl::Exception (tl::to_string (tr ("Grid check requires a positive grid value"))); + } + + if (gx == 0 && gy == 0) { + return EdgePairs (); + } + std::auto_ptr res (new db::FlatEdgePairs ()); for (RegionIterator p (begin_merged ()); ! p.at_end (); ++p) { @@ -567,6 +575,14 @@ AsIfFlatRegion::snapped_polygon (const db::Polygon &poly, db::Coord gx, db::Coor RegionDelegate * AsIfFlatRegion::snapped (db::Coord gx, db::Coord gy) { + if (gx < 0 || gy < 0) { + throw tl::Exception (tl::to_string (tr ("Grid check requires a positive grid value"))); + } + + if (gx == 0 && gy == 0) { + return this; + } + std::auto_ptr new_region (new FlatRegion (merged_semantics ())); gx = std::max (db::Coord (1), gx); diff --git a/src/db/db/dbDeepRegion.cc b/src/db/db/dbDeepRegion.cc index e4b6c82da..dbef515b5 100644 --- a/src/db/db/dbDeepRegion.cc +++ b/src/db/db/dbDeepRegion.cc @@ -716,7 +716,7 @@ DeepRegion::to_string (size_t nmax) const EdgePairs DeepRegion::grid_check (db::Coord gx, db::Coord gy) const { - if (gx <= 0 || gy <= 0) { + if (gx < 0 || gy < 0) { throw tl::Exception (tl::to_string (tr ("Grid check requires a positive grid value"))); } @@ -725,6 +725,10 @@ DeepRegion::grid_check (db::Coord gx, db::Coord gy) const return db::AsIfFlatRegion::grid_check (gx, gy); } + if (gx == 0) { + return EdgePairs (); + } + ensure_merged_polygons_valid (); db::Layout &layout = m_merged_polygons.layout (); @@ -796,7 +800,7 @@ DeepRegion::angle_check (double min, double max, bool inverse) const RegionDelegate * DeepRegion::snapped (db::Coord gx, db::Coord gy) { - if (gx <= 0 || gy <= 0) { + if (gx < 0 || gy < 0) { throw tl::Exception (tl::to_string (tr ("Snapping requires a positive grid value"))); } @@ -805,6 +809,10 @@ DeepRegion::snapped (db::Coord gx, db::Coord gy) return db::AsIfFlatRegion::snapped (gx, gy); } + if (! gx) { + return this; + } + ensure_merged_polygons_valid (); db::cell_variants_collector vars (gx); diff --git a/src/drc/unit_tests/drcSimpleTests.cc b/src/drc/unit_tests/drcSimpleTests.cc index b3d225ce8..197d7b4df 100644 --- a/src/drc/unit_tests/drcSimpleTests.cc +++ b/src/drc/unit_tests/drcSimpleTests.cc @@ -102,7 +102,7 @@ TEST(2) db::compare_layouts (_this, layout, au, db::NoNormalization); } -TEST(3) +TEST(3_Flat) { std::string rs = tl::testsrc (); rs += "/testdata/drc/drcSimpleTests_3.drc"; @@ -141,3 +141,43 @@ TEST(3) db::compare_layouts (_this, layout, au, db::NoNormalization); } + +TEST(4_Hierarchical) +{ + std::string rs = tl::testsrc (); + rs += "/testdata/drc/drcSimpleTests_4.drc"; + + std::string input = tl::testsrc (); + input += "/testdata/drc/drctest.gds"; + + std::string au = tl::testsrc (); + au += "/testdata/drc/drcSimpleTests_au4.oas"; + + std::string output = this->tmp_file ("tmp.gds"); + + { + // Set some variables + lym::Macro config; + config.set_text (tl::sprintf ( + "$drc_test_source = '%s'\n" + "$drc_test_target = '%s'\n" + , input, output) + ); + config.set_interpreter (lym::Macro::Ruby); + EXPECT_EQ (config.run (), 0); + } + + lym::Macro drc; + drc.load_from (rs); + EXPECT_EQ (drc.run (), 0); + + db::Layout layout; + + { + tl::InputStream stream (output); + db::Reader reader (stream); + reader.read (layout); + } + + db::compare_layouts (_this, layout, au, db::NoNormalization); +} diff --git a/src/drc/unit_tests/drcSuiteTests.cc b/src/drc/unit_tests/drcSuiteTests.cc index 94e043626..b134a853d 100644 --- a/src/drc/unit_tests/drcSuiteTests.cc +++ b/src/drc/unit_tests/drcSuiteTests.cc @@ -72,25 +72,36 @@ void runtest (tl::TestBase *_this, int mode) db::compare_layouts (_this, layout, au, db::WriteOAS); } -TEST(1) +TEST(1_Flat) { runtest (_this, 1); } -TEST(2) +TEST(2_BigFlat) { test_is_long_runner (); runtest (_this, 2); } -TEST(3) +TEST(3_Tiled) { test_is_long_runner (); runtest (_this, 3); } -TEST(4) +TEST(4_BigTiled) { test_is_long_runner (); runtest (_this, 4); } + +TEST(5_Hier) +{ + runtest (_this, 5); +} + +TEST(6_BigHier) +{ + test_is_long_runner (); + runtest (_this, 6); +} diff --git a/testdata/drc/drcSimpleTests_4.drc b/testdata/drc/drcSimpleTests_4.drc new file mode 100644 index 000000000..579156b06 --- /dev/null +++ b/testdata/drc/drcSimpleTests_4.drc @@ -0,0 +1,49 @@ + +# Foreign cell test + +source($drc_test_source, "TOPTOP_SMALL") +target($drc_test_target) + +cell("TOPTOP_SMALL") + +l1_flat = input(1) +l1_flat.is_deep? && raise("l1_flat should not be deep") + +is_deep? && raise("is_deep? is true") + +deep + +is_deep? || raise("is_deep? is false") + +l1 = input(1) +l1.is_deep? || raise("l1 should be deep") +l2 = input(2) +l2.is_deep? || raise("l2 should be deep") + +flat + +is_deep? && raise("is_deep? is true") + +l2_flat = input(2) +l2_flat.is_deep? && raise("l2_flat should not be deep") + +l1.output(1, 0) +l2.output(2, 0) + +l1_flat.output(11, 0) +l2_flat.output(12, 0) + +l1.and(l2).output(1000, 0) +l1_flat.and(l2).output(1001, 0) +l1.and(l2_flat).output(1002, 0) +l1_flat.and(l2_flat).output(1003, 0) + +l1.space(0.2).output(1010, 0) +l1_flat.space(0.2).output(1011, 0) + +l1.separation(l2, 0.3).output(1020, 0) +l1_flat.separation(l2, 0.3).output(1021, 0) +l1.separation(l2_flat, 0.3).output(1022, 0) +l1_flat.separation(l2_flat, 0.3).output(1023, 0) + + diff --git a/testdata/drc/drcSimpleTests_au4.oas b/testdata/drc/drcSimpleTests_au4.oas new file mode 100644 index 0000000000000000000000000000000000000000..aa37d1e7d0d7f0e1416d743cbccea8e70ca92b79 GIT binary patch literal 1110 zcmd^-K}#D!0EK6q$!xMol*kOBVGqS3dQqb7!JtS(jW!sgbQ82z$R(#jOTo(uDQE?W zrIrLDc<`_h5rd_yrz+%-v|v-3LvoAYA@oq9g+hBuK(I3rici#JM(z+z3Eha z6aJn_uj*IrGDLm7ZcvlJ)uAw#8kz9UL{$G+&^HqC2SdIw-=oO1eVfiahWI0yZ0;i4S$c8weo8r|6nw2W$`mj6c4J_b-kgZNa5mhi@U}gP z^24mJ1(KYt+*X&X?RlF{_B+@BH0z{-EBF{Eka$XM8QKVQ9d9@c%c*2o3h>lJwUit! z&nAPkm_UuN6-Nzdz$dNZO)FW`F+QH8Gdju8{4mvOl1{z^e)UqVXwI5e?{jUTCcSGZ znuQnhoJ$xjNpFy@EK6(Xlk)j4b7Qp3mFj#Ft)V??hmPN1Y20|`&`PuQuoXldK{j|U zfy<3muh_WaKjUUrj9>S^8qaOPm6zpiBgBTT04RBLoq%fF_6lC@(x1|pV3C_Xa02GB zK~incaSU~N-;%dA?ihn?=wBSWqX2OIFOH45gd&7%B=rGd$zhh-HXIE(&5?2>IEm7P zC*m*-OO2y6Ec+ex=Tys@isnP-ZV%OTQj^NvwcA3|$)Pj;n8rA2af_%HP00mVZKo#* z1OS9U(4?C@@DQMzX`UVeD6Hs7)>IGc#0oj43+SeEI@ujPa5k~gn5ZM(8t(!$CL9Vb zo|Sq|c_Ga-F;PtORCM7?Dw=PQf7~Q9&js)C+kA!;;7;_b>nP08^A=Oh7T3|v==%=9 t?uo`y&8pYY+hGm~qdk&CTS|uH(Ff>UU4e;{QAV_yZ1ElHmXV literal 0 HcmV?d00001 diff --git a/testdata/drc/drcSuiteTests.drc b/testdata/drc/drcSuiteTests.drc index 8490b026e..88765993e 100644 --- a/testdata/drc/drcSuiteTests.drc +++ b/testdata/drc/drcSuiteTests.drc @@ -4,12 +4,16 @@ def message(m) $stdout.flush end -def run_testsuite(dm, ic, tiled = false) +def expect_eq(a, b) + a == b || raise("unexpected value #{a.to_s}, should be #{b.to_s}") +end + +def run_testsuite(dm, ic, tiled = false, hier = false) has_float_range = ((0..0.5).max != 0) lb = 100 - + a = input(RBA::LayerInfo::new(1, 0)) b = input(2) c = input(3) @@ -21,16 +25,16 @@ def run_testsuite(dm, ic, tiled = false) layers.each { |l| h[l] = true } h[RBA::LayerInfo::new(1, 0)] || raise("missing layer 1/0 in layers list") - c.is_merged? == false || raise("unexpected value") + expect_eq(c.is_merged?, false) message "--- general #{lb}" l1 = a&b l1.output(lb, dm) - l1.is_empty? && raise("must not be empty") + expect_eq(l1.is_empty?, false) a.and(b).xor(l1).is_empty? || raise("xor not empty") - tiled || (a.and(b).is_merged? == true || raise("unexpected value")) + tiled || hier || expect_eq(a.and(b).is_merged?, true) a.xor(b).output(RBA::LayerInfo::new(lb + 1, dm)) a.xor(b).xor(a ^ b).is_empty? || raise("xor not empty") @@ -43,28 +47,36 @@ def run_testsuite(dm, ic, tiled = false) a.join(b).xor(a + b).is_empty? || raise("xor not empty") if !tiled - a.join(b).data.size == 16 * ic || raise("unexpected shape count") + expect_eq(a.join(b).data.size, 16 * ic) end - c.raw.is_clean? == false || raise("unexpected value") - # c.raw switched the semantics - c.is_clean? == false || raise("unexpected value") - (c.area / ic).to_s == "10.0" || raise("unexpected value") - (c.edges.length / ic).to_s == "18.0" || raise("unexpected value") - (c.perimeter / ic).to_s == "18.0" || raise("unexpected value") - c.clean - c.is_clean? == true || raise("unexpected value") - (c.area / ic).to_s == "9.0" || raise("unexpected value") - (c.edges.length / ic).to_s == "14.0" || raise("unexpected value") - (c.perimeter / ic).to_s == "14.0" || raise("unexpected value") - (c.dup.area / ic).to_s == "9.0" || raise("unexpected value") - (c.raw.clean.area / ic).to_s == "9.0" || raise("unexpected value") - (c.area / ic).to_s == "9.0" || raise("unexpected value") - (c.raw.area / ic).to_s == "10.0" || raise("unexpected value") - c.clean + # NOTE: there is no clean/raw semantics in deep mode + if ! hier + expect_eq(c.raw.is_clean?, false) + # c.raw switched the semantics + expect_eq(c.is_clean?, false) + expect_eq((c.area / ic).to_s, "10.0") + expect_eq((c.edges.length / ic).to_s, "18.0") + expect_eq((c.perimeter / ic).to_s, "18.0") + c.clean + expect_eq(c.is_clean?, true) + end + + expect_eq((c.area / ic).to_s, "9.0") + expect_eq((c.edges.length / ic).to_s, "14.0") + expect_eq((c.perimeter / ic).to_s, "14.0") + expect_eq((c.dup.area / ic).to_s, "9.0") + + # NOTE: there is no clean/raw semantics in deep mode + if ! hier + expect_eq((c.raw.clean.area / ic).to_s, "9.0") + expect_eq((c.area / ic).to_s, "9.0") + expect_eq((c.raw.area / ic).to_s, "10.0") + c.clean + end if ic == 1 - c.bbox.to_s == "(-4,-5;0,-2)" || raise("unexpected value") + expect_eq(c.bbox.to_s, "(-4,-5;0,-2)") end lb += 10 #110 @@ -80,15 +92,15 @@ def run_testsuite(dm, ic, tiled = false) a.edges.end_segments(0, 0.5).extended_out(0.01).output(lb + 7, dm) a.edges.end_segments(0.5, 0.5).extended_out(0.01).output(lb + 8, dm) - a.polygons? == true || raise("unexpected value") - a.edges? == false || raise("unexpected value") - a.edge_pairs? == false || raise("unexpected value") - a.edges.edge_pairs? == false || raise("unexpected value") - a.edges.polygons? == false || raise("unexpected value") - a.edges.edges? == true || raise("unexpected value") - a.space(0.5).edge_pairs? == true || raise("unexpected value") - a.space(0.5).polygons? == false || raise("unexpected value") - a.space(0.5).edges? == false || raise("unexpected value") + expect_eq(a.polygons?, true) + expect_eq(a.edges?, false) + expect_eq(a.edge_pairs?, false) + expect_eq(a.edges.edge_pairs?, false) + expect_eq(a.edges.polygons?, false) + expect_eq(a.edges.edges?, true) + expect_eq(a.space(0.5).edge_pairs?, true) + expect_eq(a.space(0.5).polygons?, false) + expect_eq(a.space(0.5).edges?, false) lb += 10 #120 message "--- extended #{lb}" @@ -132,7 +144,7 @@ def run_testsuite(dm, ic, tiled = false) b.interacting(a).in(b).output(lb + 1, dm) (b|a).not_in(b).output(lb + 2, dm) x.in(b).output(lb + 3, dm) - b.sized(0.1).in(b).is_empty? == true || raise("unexpected value") + expect_eq(b.sized(0.1).in(b).is_empty?, true) lb += 10 #170 message "--- inside, outside, overlapping, interacting #{lb}" @@ -209,14 +221,18 @@ def run_testsuite(dm, ic, tiled = false) lb += 10 #180 message "--- merge #{lb}" - c.raw + if !hier + c.raw + end if !tiled - c.merged.is_merged? == true || raise("unexpected value") + expect_eq(c.merged.is_merged?, true) end c.merged.output(lb, dm) c.merged(2).output(lb + 1, dm) c.merged(3).output(lb + 2, dm) - c.clean + if !hier + c.clean + end cdup = c.dup cdup.merge.xor(c.merged).output(lb + 3, dm) cdup.xor(c.merged).output(lb + 4, dm) @@ -252,9 +268,11 @@ def run_testsuite(dm, ic, tiled = false) a.non_rectangles.output(lb + 1, dm) a.rectilinear.output(lb + 2, dm) a.non_rectilinear.output(lb + 3, dm) - c.raw - c.non_rectangles.output(lb + 4, dm) - c.clean + if !hier + c.raw + c.non_rectangles.output(lb + 4, dm) + c.clean + end c.rectangles.output(lb + 5, dm) lb += 10 #210 @@ -561,12 +579,18 @@ if $drc_test_mode == 1 source($drc_test_source, "TOP") target($drc_test_target, "TOP") + + flat + run_testsuite(0, 1) elsif $drc_test_mode == 2 target($drc_test_target, "TOPTOP") source($drc_test_source, "TOPTOP") + + flat + run_testsuite(0, 900) elsif $drc_test_mode == 3 @@ -592,5 +616,23 @@ elsif $drc_test_mode == 4 run_testsuite(0, 900, true) +elsif $drc_test_mode == 5 + + source($drc_test_source, "TOP") + target($drc_test_target, "TOP") + + deep + + run_testsuite(0, 1, false, true) + +elsif $drc_test_mode == 6 + + target($drc_test_target, "TOPTOP") + source($drc_test_source, "TOPTOP") + + deep + + run_testsuite(0, 900, false, true) + end diff --git a/testdata/drc/drcSuiteTests_au5.oas b/testdata/drc/drcSuiteTests_au5.oas new file mode 100644 index 0000000000000000000000000000000000000000..c49c2c6164431654c5f4e39ba9a9a6e4a31a9c5a GIT binary patch literal 11337 zcmd^FeQ;FO72o^b%YJ1;AiWn@NC`=sRtqx8f+bHZ169Nn1g3!ju~TN+wYC3PY_+qi z2~C1xLixzbN1>zRE)W%pm^Dz1Void^1S3rlaCZh&u%n6S*a!--f<5QneQ)3HTQ&iT zG9xp|kay2L-{*JF{U|J(aa-wibElV;mSs7KTs)!3(qpq2|K+k#WlL_GzGz8V>C8DZ zZeCJ4Xa05DZ?!AAS-E0(!JJeCxy)I%Wd7WFOKzGmd$vo4|H=3(%R(RU zT6RjE_IA>%GWAYk4_YHiM2RYOGX8XBZ>RkWSP8Kg%xu ztsS%bCeWjGa`)o?1(}Msfi#dihTv}+*_#1;=h8(%>voFd>z&^2#eq3Y{f${hk{X~| z{%?t0=q7}R9koMH-$WZy>6_AggSNh)b#De&DWiHqHN0w$hsoB(=;D5^=?JJzsJ7px#1t=(Q>cTdw6Q5OuYwKoV72ja)*J1*BMbb?K)7tgBX-=L6aJl-K5^S3Qc0_AB6sV zBG(^CEY8o!4k9dQD&iKj6K=u-N2It7^*`V?Y#xR-{888-6yP=(7@-wFjaGCZBgu)H z^-4lBVFgrzdatnJlkYtKU9=(~b6V85KSQ@7XysNQhsVe&(x_0T-Y2Z+W1&2Z0w=LJ z&*)a{xt3eO84{=8XayLEk*IQ`6@L;|)Q>!z&m)n*2(3VO0JTB0sA9BY^N(nS@1WK5 z5>#*|@nrn0jM4>uEPVLupi&09-ml2}l^V|hqf$*SUefGFttLFcVt!NR6BNM zy@untng+L^%w?weVqDDqvm$?CukoC>i|9oZFDjKuyj!GkPUfy8r$)T|J=7E&PrTLe zk=|2H1WctY`*@W%I0R~@MzEzlwoVXAea)_;nN=RZFb&iEtB7-3Ry|0hNN8ws%r^cD!- zogJ7%eO?SW;B6eid^;rsdPLQ~5=3ev{E)G@5VVs}Ui+x-?0!h@vmiI3)DCHO)eNbV zgL%ES<2n7#uIv_9gX63-1k}O2J{Gk0JNtn$s^G$rgjxw8&(vVD)EmeJ(J=|T!Ffwf zQ8GcYj)dCsF-qo3SIGs%BzS?2#5G3)TrGY&Qw?6@P|QY$h()ZVTZnm~GObCNcC2^@ z#4B-SqVsUwW8jViePBIJy*@4KwDVQfN#TsIeQAC2 zcj1%d$;^{7+8{H@8@T>!>c@Sp*HcV3%aOtbSJaRxJBcNA04`bQc?IMcSW$csU68{F?H`Je8~N}*r=)pfo1Cp&s4VX#w_+Y6f) z?sGxAge>#lG|=N{8d~)b&?b&2NE(a0E4^=KM-}?J!vr_u&;dT$PH>lT)|tbKj~ImU zZFUknn7)y$%hhyy%RtW5h)H^aC2BPNc>Yl%H2@K{q2xwKkFB*Z414J9G*m(#dj~MSWvYdiblL+lo^HHrVwdPSNTNnZ&5+imVaMiS z(0{?-ae0g!hu->xiNydOP~W@g8Slf4gTCg%wuNEG$uhL8)z$E;wXP1*y*N4-|9;XQ zQR;4}w?~!QX&n<$&(*GW(ol*t50@YX{$1(WYV9B|&+V9vU)FMp5PWGW6r3T&tx6y9Q^9 zq!Y;!PsmhkEG+*cU=hzTbgeNbMV1=#R--UvsrqpO4=qnGmJf~o2mB6rG?!NO5nA;r z{G5WH)9~{-{G5S0{sJWJFN7ep7vjE{k^d4#z9dvV2b+K18oJLDG4d6RJSZ{p5d5r= zsQY0_jMqqFyjEhxYb5X&l2Q%MaGtDV?j~M33+kmhxvA6=bKImj+NFjyIv)@6Iq_$1MH8B<> z%mHoVo~N74X8ElX;XbNwE?V4(NH9oUqX4t8^Sa# z2PnS}wnLQ_$8*qNG%u+}IR?k;awP94&AD-H7!=fs62LlLvyqEEuK}Hy`YcQ~rmjd# za~Tbkym~t8ML}bw)p*4v`~xJMm>jb1&Uk-vxZoUb*{j{}&clA8%H20pKPX}9a|4m` zLu#fz4|J_lLvTUVHZ@!T+Z_l`wHx=WF_z*mn>0yuQ9B72h}O0p_P>2Infe0EuJ(qk zXPqm&;)on)S5};2NeWT&BCPLw2T+QsFAYRNnt3;~6?|}FOvt(?qi@2V$*A;+4DJ`W zWvZXV|4#DLi7_?qHdj;Wt9)y;(k*6hzXi?~hITK8(*ZQa;7jE3|CiFrrNrHfQj*=j z6@t-P7_x?}eG@`F#;qCH6Bb3Ixx0r*a~m$G$FOx6SBfKIVQE*BcvMdYsjk*<~9ssQaA?T z+N6LPLxkcD0aRbR!V+l3ekpd_7Q4G%@8nmVMGx9GxS9Go$iKhFBW{;(6xVWaVvs=O z?v(iL;axp8=Nm=z7kK5#uZ;PvH*>!shHve$#YZjl_5)In4X<9c2^L!Mue7>!Ns5H8 zCr}t*qJ(cL;8Hev2>N;c8p3~Aid|%hJE?^0H0?fwsYj&bV?Nv`q(13}?+I5-RJ%$t z+_-2@9$4{PCeDNA8^4Lt*rhz0)^4!i+{z^DBCJUD(8Q+;_D2)_7{ABzzpamG*M0tX z^iQ#u7qJmIPr4ix(>nUNiSwWxt$EOJQZ!%Np%-07t7g{BEpPnx)2OwJ|LCbTTtoh4I0x*WbE`7gmf7sTZpjkaYLE#h;l(4LVtMLGj|$-DXH&EgzWr zxk=WgL?}Hx96O`C)Bh#pZ0yNeQr4c+w3hKdjxg z+oT0Ii5MpoFo8g!1(^Vm1OX>T5Hvy(6cUJ-@FOM_Bm@fyh$euNpu&F7z3;twGjD(( zb?fKT@<~qayZ7Al>z;G&x$nLYnbYq4!IWF3etX)KX~~KpjUPMC)s^V7{|jhCr#(qKOZ`o@YpvMiX%M<~O@B=Agxqa%o7^rJOZ3~ETB-Iws7(~ywZ4|r zKBXhQ&KGi5jjo%ljT77rL&M&7;4VpPlS?FRqAb@x9?Z(SXZs2v-@kbrrpb#j)SVdi zhTPq{n$zT|NhrFbZnB!wl^Ak2-rGA?Jy0yQJktMQlAIkD!op8d2&!#037|VweK_Q< zR0+LQ$!>Y1 zuSzbFoRXS~Y5H_o`gND*$yWv+8==m~R22z6Zl14;w zl-UVY-WP9HbIwT0TqLh@Im#I@sk}wge*qGG zo+tDDRUnbAw8;#20fcW9Y{J)(O4ahqLb-bmsif)u#pP!cnfyR;DIW`K1g^p6aZG~- zfvNDDRxzSO`PY~ZOVUV(|K>Vy0hkUpLZk(-NsC4zBq}6Xx5y>qTEPAEC%6_LU9XwXACTFS!=ITGIOTMno%-Q44dL3btBxEK0!!%WWFqfhY zPkZEPkoDKHv{Mdd?Ur}=PmEFnB~sBHE=~W9oUl`F`$p*g=iP7oATpBVdNn0o(`U#D zYmyr#=l$!^^i@A;^gn&QI%AJ}k-)gPvJ$?p=`&?%O>%yEfp>lK4jK=b5`3R{KIxdx z$NAj>Q~kvpL0OetE~zux zWNr=D`s8#qcdno`d9bv|9T;d3T5eSD4tqb>)!cb}0wssA7bED)F>x&-!tk5^d5xgy ziv+>V6BnYAM2K1wvd6DWDvGlMXaa$|!~Hlkr@OKLw{ zuMd3NXUofRu3XSMaJe=OIG^f`2!rnfm4A`L&y;{iKuk-`6U8Y=WsolCpaK&b0^?u6h z8gz>_aoi-WKHTJCyN0n>M6@yYg^0%5H>Fr^*boM)okh>c_XAOI*b9P-wvL#Eb!k~~ zmoDC38WBHYxY!lb(#5LLU#cSCT8ov6y8vHU=GYZ>*y#U#wZ66PuRzIR>ji;VGx66` z&G_nu)3;s};>Jzu8wu#PcX)R&sRZmo81G+!!1{beZF zoZ6Ba8W9>nuy-t~ipC8>S0bvCUr=d4M+8l86k-l7s(+_I)sN=i#ScpQ9zmAs_X)c* zw-@n|iKyx_^X}Q5Ni`2nR#HP|?L*sqs7+|zsJ%9P8odOrz7M$3<58SO679U~y{YZ8 z`XBoRIt*enTiPH{!QH2%YvWsO%-At6h8(6Z5*7qZ+1|0CYkHf5dz2=6`=!T69k5e# zXj+YfWr4rSTZVd~{qLN1I(Rj+_4gdIUCeUCx!T*6Sf5#ivt*@OHc?%62syZJvP+%V zxxNYjt~C4|XY zE5?e0BUKkfA(`t8nMtc{zBW1Uo;t)Ovjlp8k-g2d6)dxjEO8Iln`YD(3EZ@_p~+I6 zWvF(;P^{-3B2hN6@m4jSEb1@25vMgcKPI`dZyu~Z!HvdDrrw4(B}u4A)76{?V3Azv zq-O=p%M%1w-_(dHVPIMwK> zT_ISDOb+Zr#{xzto+`#FpD;{ox`TEj&E!tUoh9Os@yNKeQ6GRr#K*|AcAw;4YWG_c z!{(*>$tW78pI#{)wY>*+M?RXW=JyI}{we&P#_t*YeuCe#h~snMv@;Wn(2z-oP)+z$ z(1gzfRsUQt-o^RP3w-1PMrMhc@HBo4Mb-a|$VxkZP~_u9qBee>2zw!1=gX}*-HD`H zw}J!d%_ne{-Y}diU+pcHuz*sDgw&!x8#RNDNSB=>6pMsKd_r#QQ{C9+sH4sY!dcsV zB~sZ4_?lh<&!jOmrzW96s8qpTpKq{hPTjsQdg9ELBltv0BhfN0h$EvN1BAzy0>`ea zCM*;+eYuDox7r7MaKLgcdL1smIH>pLXz33dpoxn^6W*&>lFqg`Us*G2w zR?c0_Lg&G9u~^zX#bp@own(;%pVrg|=tVM1eA@Wup+!_OV#{^Ol!r1XlHv$GCcR;t zl0T(Ho}G-mhA6*{b}XKJW35lc{Zw!(?K4EF#e&_O61 z)_oP^i*X0;qX_x+5YD;t$3%=X$jFhRs}BU7pL$H2+C!J@Wv@Q4k+KchtWO*v$L!}) zGhY<5FI7PBas#o(EbWL-O%Fr6I0Z#?_(g?hq#(RR$pPWF;W(5x^Kd#TOzOqND9gc- zxXfS`rPDWVs)B%KP$F4hs@Mon&1=g}n*I_d+g+DCrZbE#=e(9XTS1{3p;i5*bJz$D zofFF3FDD$GP&N8ZG7m-Qk;(_gt9D!U-l^i9C4pInB!lI@eLM&r1H z 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 a9S}+UPM$oEt5X_}mnJ)KIWx1cFaQ8K*Dj_2 delta 200 zcmdlXzDg{KfsKKQDS|&S1cxfXrqPU}E!g4UhNnbP93|iez9AVP>^+ z>@@d2w)}&o%MSeov!g;7WLR1ZLt