From 0c501e0c26b4959b949c712bb6b10cc411cad6b3 Mon Sep 17 00:00:00 2001 From: ameligrana Date: Wed, 18 Mar 2026 09:57:10 +0100 Subject: [PATCH 1/4] Improve the interface by unifying StreamSampler and SequentialSampler --- .gitignore | 3 + Project.toml | 2 +- benchmark/Project.toml | 27 ++++---- benchmark/benchmark_comparison_stream.jl | 26 ++++---- benchmark/benchmark_ondisk.jl | 38 ++++++++--- benchmark/comparison_ondisk_algs.pdf | Bin 0 -> 9684 bytes benchmark/comparison_stream_algs.pdf | Bin 0 -> 16334 bytes docs/src/api.md | 1 - docs/src/basics.md | 19 +++--- docs/src/benchmark.md | 2 +- docs/src/example.md | 11 ++-- src/SamplingInterface.jl | 76 +++++++++++----------- src/SamplingReduction.jl | 2 +- src/SortedSamplingMulti.jl | 10 +-- src/StreamSampling.jl | 12 ++-- test/runtests.jl | 2 +- test/sequential_inds_sampling_tests.jl | 64 ++++++++++++++++++ test/sequential_sampling_tests.jl | 79 +++++++++++++---------- test/stream_sampling_tests.jl | 77 ---------------------- 19 files changed, 239 insertions(+), 212 deletions(-) create mode 100644 benchmark/comparison_ondisk_algs.pdf create mode 100644 benchmark/comparison_stream_algs.pdf create mode 100644 test/sequential_inds_sampling_tests.jl delete mode 100644 test/stream_sampling_tests.jl diff --git a/.gitignore b/.gitignore index 29126e47..4beb294d 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ docs/site/ # committed for packages, but should be committed for applications that require a static # environment. Manifest.toml + +benchmark/Manifest.toml +benchmark/random_data.arrow diff --git a/Project.toml b/Project.toml index 96840e22..e517c7c2 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "StreamSampling" uuid = "ff63dad9-3335-55d8-95ec-f8139d39e468" -version = "0.7.6" +version = "0.8.0" [deps] Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" diff --git a/benchmark/Project.toml b/benchmark/Project.toml index 922d5aad..464bc637 100644 --- a/benchmark/Project.toml +++ b/benchmark/Project.toml @@ -1,20 +1,23 @@ - [deps] -StreamSampling = "ff63dad9-3335-55d8-95ec-f8139d39e468" -CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" -ChunkSplitter = "ae650224-84b6-46f8-82ea-d812ca08434e" -PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" +Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45" BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" -StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" -Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +ChunkSplitters = "ae650224-84b6-46f8-82ea-d812ca08434e" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" +PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" +StreamSampling = "ff63dad9-3335-55d8-95ec-f8139d39e468" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +ThreadPinning = "811555cd-349b-4f26-b7bc-1f208b848042" [compat] -StreamSampling = "0.4" +Arrow = "2" +BenchmarkTools = "1" +ChunkSplitters = "3" Distributions = "0.25" +PyCall = "1" Random = "1" StatsBase = "0.34" -CairoMakie = "0.12" -PyCall = "1.96" -BenchmarkTools = "1.6" -ChunkSplitter = "3" +StreamSampling = "0.7" +CairoMakie = "0.15" +ThreadPinning = "1" diff --git a/benchmark/benchmark_comparison_stream.jl b/benchmark/benchmark_comparison_stream.jl index 6a92d681..698358d5 100644 --- a/benchmark/benchmark_comparison_stream.jl +++ b/benchmark/benchmark_comparison_stream.jl @@ -29,9 +29,9 @@ end function strsamplesum(rng, stream, wf, n, alg, W=nothing) W == nothing && (W = sum(wf(x) for x in stream)) st = if alg in (AlgD(), AlgORDSWR()) - StreamSampler{Int}(rng, stream, n, W, alg) + SequentialSampler{Int}(rng, stream, n, W, alg) else - StreamSampler{Int}(rng, stream, w, n, W, alg) + SequentialSampler{Int}(rng, stream, w, n, W, alg) end return sum(st) end @@ -96,11 +96,11 @@ end using CairoMakie -f = Figure(fontsize = 9,); +f = Figure(fontsize = 9, size = (600, 600)); axs = [Axis(f[i, j], yscale = log10, xscale = log10, xgridstyle = :dot, - ygridstyle = :dot) for i in 1:4 for j in 1:2]; + ygridstyle = :dot, titlesize=13, xlabelsize=10, ylabelsize=10) for i in 1:4 for j in 1:2]; -labels = ("population", "reservoir", "stream", "stream - one pass" ) +labels = ("population", "reservoir", "sequential", "sequential - one pass" ) markers = (:circle, :rect, :utriangle, :xcross) a, b = 0, 0 @@ -126,8 +126,14 @@ for j in 1:8 j in (1, 2, 5, 6) && hidexdecorations!(axs[j], grid = false) end -for i in 1:8 - axs[i].yticks = LogTicks(WilkinsonTicks(4, k_min=4, k_max=6)) +for i in (1, 2, 5, 6) + axs[i].yticks = ([1e1, 1e2, 1e3, 1e4, 1e5], ["10¹", "10²", "10³", "10⁴", "10⁵"]) + ylims!(axs[i], 1e1, 1e5) +end + +for i in (3, 4, 7, 8) + axs[i].yticks = ([1e-1, 1e0, 1e1, 1e2, 1e3, 1e4], ["10⁻¹", "10⁰", "10¹", "10²", "10³", "10⁴"]) + ylims!(axs[i], 1e-1, 1e4) end linkyaxes!((axs[i] for i in [1,2,5,6])...) @@ -140,12 +146,8 @@ for i in [3,4] axs[i].xticklabelsvisible = false end - f[5, 1] = Legend(f, axs[1], framevisible = false, orientation = :horizontal, - halign = :center, padding=(248,0,0,0)) - -Label(f[0, :], "Performance of Sampling Algorithms on Iterators", fontsize = 13, - font=:bold) + halign = :center, padding=(248,0,0,0), labelsize=10) f diff --git a/benchmark/benchmark_ondisk.jl b/benchmark/benchmark_ondisk.jl index cfde8408..bb002acb 100644 --- a/benchmark/benchmark_ondisk.jl +++ b/benchmark/benchmark_ondisk.jl @@ -10,6 +10,11 @@ const totaltpl = 10^11÷32 #100GB! const chunktpl = totaltpl ÷ 100 const numchunks = ceil(Int, totaltpl / chunktpl) +function drop_page_cache() + run(`sync`) + run(`sudo sh -c "echo 3 > /proc/sys/vm/drop_caches"`) +end + function generate_file(filename) for i in 1:numchunks starttpl, endtpl = (i-1)*chunktpl+1, min(i*chunktpl, totaltpl) @@ -83,7 +88,7 @@ wf(d) = d[end] function sample_file_st(data, rng, n, alg) W = sum(x[end] for x in data) s = Vector{dtype}(undef, n) - @inbounds for (i, d) in enumerate(StreamSampler{dtype}(rng, data, wf, n, W, alg)) + @inbounds for (i, d) in enumerate(SequentialSampler{dtype}(rng, data, wf, n, W, alg)) s[i] = d end return s @@ -93,8 +98,7 @@ function psample_file_st(data, rngs, n, alg) weights = Vector{Float64}(undef, Threads.nthreads()) Threads.@threads for (i,c) in enumerate(chunks(1:length(data), n=Threads.nthreads())) W = sum((@inbounds data[j][end]) for j in c) - st_data = ((@inbounds data[j]) for j in c) - samples[i] = collect(StreamSampler{dtype}(rngs[i], @view(data[c]), wf, n, W, alg)) + samples[i] = collect(SequentialSampler{dtype}(rngs[i], @view(data[c]), wf, n, W, alg)) weights[i] = W end return combine(rngs, samples, weights) @@ -117,22 +121,35 @@ precompile(sample_file_st, typeof.((data, rng, n, AlgORDWSWR()))) precompile(psample_file_st, typeof.((data, rngs, n, AlgORDWSWR()))) times = [] +drop_page_cache() for n in (totaltpl ÷ 100000, totaltpl ÷ 10000, totaltpl ÷ 1000, totaltpl ÷ 100) + if n != totaltpl ÷ 100 t1 = @elapsed sample_file_pop(data, rng, n); + drop_page_cache() + t2 = @elapsed psample_file_pop(data, rngs, n); + drop_page_cache() else t1 = nothing t2 = nothing end + t3 = @elapsed sample_file_st(data, rng, n, AlgORDWSWR()); + drop_page_cache() + t4 = @elapsed psample_file_st(data, rngs, n, AlgORDWSWR()); + drop_page_cache() t5 = @elapsed sample_file_rs(data, rng, n, AlgWRSWRSKIP()); + drop_page_cache() + t6 = @elapsed psample_file_rs(data, rngs, n, AlgWRSWRSKIP()); + drop_page_cache() push!(times, [t1, t2, t3, t4, t5, t6]) + println(times) end times = hcat(times...) @@ -142,17 +159,18 @@ x = 1:4 xtick_positions = [1,2,3,4] xtick_labels = ["0.001%","0.01%","0.1%","1%"] -algonames = ["chunks", "chunks (4 threads)", "stream", "stream (4 threads)", +algonames = ["chunks", "chunks (4 threads)", "sequential", "sequential (4 threads)", "reservoir", "reservoir (4 threads)",] markers = [:circle, :rect, :utriangle, :hexagon, :diamond, :xcross] fig = Figure(); ax = Axis(fig[1, 1]; xlabel = "sample size", ylabel = "time (s)", - title = "Sampling Performance on Persistent Data", - xticks = (xtick_positions, xtick_labels), + title = "Sampling Performance from On-Disk Data", titlesize = 16, + xticks = (xtick_positions, xtick_labels), + yticks = 0:50:300, xgridstyle = :dot, ygridstyle = :dot, - xticklabelsize = 10, yticklabelsize = 10, - xlabelsize = 12, ylabelsize = 12 + xticklabelsize = 11, yticklabelsize = 11, + xlabelsize = 14, ylabelsize = 14 ) for i in 1:size(times, 1) @@ -164,9 +182,9 @@ for i in 1:size(times, 1) linewidth = 2,) end -ylims!(low=0, high = 250) +ylims!(low=0, high = 300) fig[2, 1] = Legend(fig, ax, framevisible = false, orientation = :horizontal, halign = :center, nbanks=2, fontsize=10) fig -save("comparison_ondisk_algs.svg", fig) +save("comparison_ondisk_algs.pdf", fig) diff --git a/benchmark/comparison_ondisk_algs.pdf b/benchmark/comparison_ondisk_algs.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bd37321e5c171efbd6c940a5cb94f939b543079e GIT binary patch literal 9684 zcmb`N1zc2Hx4;1@=@Kb%Xpk5N7)rWJQc5KT7+|EEp}VD9LIG){RB8n2Mrr8=r4=Nk z9)ohfd*8k9ec$i>9>Y0%@3q(7XYaMnoc&+>43oNy94ClN0Eg-A*6R}-J^(ks&g3bM zhzJ0v0=2bpwgiALMVdGO001Zlvv!6$UZ&O%XQ&L+)XofwBPNF9_$2Fv`Cm!EKQ zG`0)13hz{fI5&;q)A8e875c|w5H0K;q4Z}r1oCaA)4p?G@LkkOPD!Xc_N=}!=2%je zDCO_*sP;qVG6s9r5tq40iJnOT>?ynkl_yxT=BS0akpNbl$C8DCy(p!+?-)h~p+t`a z9Lj*C_^QEB)b;r)ro;y&xPsQk4(C3Y7w)`q$$OmzN!fVtTLJuHeavR=emAE3m-f%j z-ZrknZMO?b)}}gr@3`*mwfefw&72A|Wk~>*yQxKDZYyb_jMMe{&vDruES1_8kcw*mJ5+rv!jasuj%r-cjnfJ{9If*?S5f zcTnhQj8KAehTK>(#!OxtPoP*wd>4*qqb^OO{7lQz!j@nXp`Y<7+dUz|^C|q*`04Wa zNQ>1NA{^%jV>u_nY#k2mEyW4vTS#5%p3Hb4TJ|PmEc@te)p*x+Qi_!`21UZpo8wnVp_XR~V7EqUrDbC;vvTKNkIm?!gb zJ2TXanFZ*STwd1=PqK+odM-qqOXQ5usG7`LyBr=Zj0x52P!<<@@Wp#9o;K* za>2dWlX0~$9{+*EyF4Ny;|I|cPm^xkA!I)>U{Hzrx@*#WpNjwd%(cUl(Y4&9$E8xR zavXF@`LcC6kEgC*y>=(LGBh$ijr=Q5*7$sB^EYSK@=-e@Y6SP-fKah@ z-+}0T!w;pqWc|%(!i{6~*6n$=G}wa$&F@t1MMTyp;RLC`c8_z5CZ^G{G!29Vtxc>q zmAPYsvRC<{4u>n-&NMZ1_D7Rtq{~Ry+k(l;1w0?gJv6{RDf@J$sU1K`cZ>0$Adha< z41;h{GRKf%vqtJr8FG_^NoP=l=m#&BKs-j3Y_r^FyWr2ap+&KJZHkCq?5%QFIalHh zISUnYP_D2k4tFQvedg_?hR7`S7j{(NUbl5Mm@l}5-L!9bL0L|MhxnZ6U8;-mj612f z5i_$OPMY;bhtFi+@L{@1@iaDhv5vxS^YY?O^jG6uA6;+y-LRBh{%+bE|xS;Qno{@U#EsNcm{c#;12VGt*J>1pD2yryPBL~2E8wT;#^!G zh+3%Bt7uL*gnc}dXKNO#=bBn@MjrkiS3|0^ecz{yiFzZ8q8X+l_ zc*9}b^flgpOoyB@<;CV!MVI%)%eT2XKQI*o$XI|+9>&&a`cajurPAQ%p!dJR{Q!$G za25nBYUFD$9DMrX5jENOkuCzL?p3|IC-UZ=-#vL_0>5YZAcZ4EHge1jcIi~|J6Hte zPxu*R9v+Ef3fYHgfti9J@wI|IOK-!M@LW^xF6?w+9*EZ}mqzAv9^4E)F;jMC-wN!| z3ZTx(sNkS6D}cOZ$1Q1Dk(LJ)!W{Vnsc~c43iJE@aCU9M(j+_NQL>td_%d?LmD5q}(u2fbR zF!;S#?QI30&B5amd*Pd3gUiAYi*S)F?1t`?{U?Z=Z;AGtHg{(Q!r59(H|<5J2-ln+ zFyQ4h=8v+im8PNB7Vul-iq|Q1v<8ivj#^%=JzV&c-yHVIS%}a$^>_vyc&X%PsN_@s--o! zkt4;X?mC4v*d-&y%I-R)Z!k)_hA2fkXCM2oKt}OJ8t}@_bZ_Xj7;$~4MW5h!wTHTRC1@RF@I@UOc2SBPmZ2tQ%ilRdC3u9-EBEK4q>QsHb( z0haC7&3_1YIr25QFI1aobh}uCh8L?vxkhQgP+u>+cU}IbIlt%4ZhuDLODZ=ue6j&) zc@w+$f#8`bCH~QIW%y6W(L{;Q609!T@Wu;0dc}lL|HTw5wWP)MY#=;APa#b7#;_Dj zVrErr=H#hYN3`p%tW}GG`-5ZKxLPk-0Bi59u*2Z;IfZx2>QNH}lX{HJ>n4++KI+I( zbB(D!^G}23S;bOtfxNw-z?Wy15q`{r)Anf%y-uN_}$6=kA(wukI`|9*({W{V742dN>Q_2KQ{3JYWH&0c$qF;nT1p30CGyz0>5b;bav3&x_D$f~D!6BH^lhf;+; zEVUV3@2PXovN^0fY-HklebD*-WV`xsVlHbyY&`D`h>bbnYf6!j)}qexT2z1cM*|Tq ztJ&#{#Ec2$HH#JYeEx`>)Gw8VGh}ZYo=>(KY)xP;POUl2h$h4L0o{t?$LFPiUKb^~ zUhMnd#m2&;o=cyb2?ok55gnZYRJ2w zUVtRKt#TL$eoW8Ktl`kr&`h#qQdHFGU^DIdh|VPaweW~7Gx1%IOuN=M&3e|%r13-w zCd0J6C|#0U)h4+G*ZJ3^FIH{kSiJXyro@K)$GA*hD zf9Nh^*YL(htD$bISP$^H-)EO*=~y19R({Kna0;u&sOa_N4B`Xcd^?&S>N=w(S-nHfjJ*g${(U_61B3)5F(g|(A4>E*pnWwQQu`LE{bL1LTIRu;c z?nP)c+gohz)THC!_V@0ry+u^OAfM_HP=XA7!jBYMg8at$d?o#4a zN2H9EGEV$3ira*pA5K&dd%UV|>Oxp<6}O;Oq@@}lR*iku8)kp|w*3?f_ z+Cr%L8mKs0w0B~Pruur{Z8UWt2%-$ki>MCOsaJ9ln0p!$hK)-$!zl6d_ORMh6s$+6e7E1AQ7A6& zW~**BNwKX5PoWZl`W~5K?g%zQuv0O*iHUrovWD$uFivm>`2A`u8bz`Rt{%batyjpLMzouI*5clr;L~QNVZAD5}n7!M(6yzBephbY+_3)AN9_x z&P585nG}!UFB*`-(cCGL_(qyZX}`VB(`S+;Cr;(ECL+I=`&KFn!R}$zP_t_@Li#eC zo=!ml+CN}GtJiEZpVwFR)hjRf&~*aNST8JtCX?z=Kbt#qBOpM=?rEWFSfOFpjVDVU zYBF%wK$XR`VfU6@>(dG2wB|Yt-eE;(5bcv!ld8b*=ftJjcCn_y&)H|0J)C#qwmPA? zFJRa~O_=ZXIr&y%`Gl zzT4Ik<1h3Q?b(`p(VjpSn)!mPwCG}Dz~$t#47oD$jsT*U7w9z_&vJH#5jZc(vTCzx zwGpfJ-a1B^K78aH=%n(}I=aq<5p{&F7;|?%JM@At%*- tGoJ=o4ZpjX&`HeDzO zLlieavmUb%?`7Brl6(wt5746@>b-GTv-@nsyd_UrFA$a&qi95SbL{rb(c0WQ0^rv;KnSyF| z){-17uEKYpi4&MkfW@>VF8hPC(N)GuN2z11vnpyN#l^s8L3dZq)u}8mA;1mh!aQw8yv58e-str9)SNy7Rdu;9ItMS5RV#|ociBv z>A|p~+J=dc^0f@q$s%Wr3gzCw|9ia+PM?0qn5Gxo|6KdgN4f&V09K;4@4F|AZDguz2fGbtN zl`h+_KCK}ZPS-?z*}s&Oojc$$fD_Cu1mNTm+B$7_1(92jrh)>|Eh#WMs%*g{1yb?%5>=mFe3rpv#ZmvXH&QKd2fZ(t8$b!Ok z0XglTXt_4@Mx-c_mOQ%bd@(3V9 zpsZ+w1&VDl|;L-e%#JvZ!%oVJxg`h9 zaSsmXUK|@e9;;%6Cf!YF6r@N)}ozD*8+w0iZsQ*oJtw zP9`wk-_cr|JQ;-}s)Z@D`P)`0QSeZq>MCNq>Ed$I$&g&)`GaY>8}x>6mg5dAXbU z6v}iTrl7G(K)!vt*YnlGIhxnb89Lr9Tn)~)>4*_Txl?R)LRY0Ers78k59*T+>F>it zfk_WuybHJVTA(@-8u5*mrkOLd{?d>Ygu0=~9QaPPFmQL3=v>#ASS>J;6`PzuNWHzn z>f~;xE@q&(KihCz8wfUaO1+olv;$`(^QkjZ3))|3l*_C{O{~Dvk&6^}w_#i31_&SGN<$4)=r zGD~?B4nZ@OSk`7_vj?;{(X=Ik^a|A{73Oe_jOsaZ!x*)2!>OChX6f@B^KAMr&=43& zV8L$hi}hveA^k}M`v`v75(^t2@@fB+A#NS{pWW!k7cj~d^AeA`BMN#j7P|mnfN$uH zYW`Qd#XsLuHx=Yi5Og*heSIVPV$u>jSJq3)hK66_XBXG}U}`;GwIo)N?cFwgYfOi| z1sPYq&6(oqgAN#+(2OGG0Lp**ZSy5)J}#9iMk4yd`)}fYcO`8YxEzcsALPAlZ3cVP z%&ZTd)}%Mc2+ZXlkt{vBudub{k|>d)H$!{O8$mlTyRE(=CXTSrj;5cY8kS=#c9X#WXfKBK_X*tGf#Os{t5ccl=xO-=79dv%VM$d97 zPgxJZsT3P)FbUC*bj?)_Tzw02&@z3?3j%ygZ3K1)n@5oUlLO$Jm*89u#2r?iwYx7e zB;b@jBZRxA-}vdCAU^DSDJ#x>EAPqAIDj11gKzb$61&{5c57ms2#>&)?1F}AJpA!_ z?scRjnGo6Syjwh3y&p{9BIg34e3Wf2>C#9^c+*9*qet&QTV6NR zttn^X%22PQvUzr^3uTIY^xIUNA*t?itN%jR(vJ5%OL0S;aRokM~H(`RbvT?K1XXPcVf|W z!(YH+*=JuahJDD%bp?96Z?Ob%q#c&MI<2+!tUA;7vPG3F*prmwscvW5$28pm7Osof zy>NZ4@nx$iqfsBzqVc3~{d=6!UPeH1iL?n$o6cvFyL)Y93IaLQs|mZ|l)-VwtX_;` zi=&&I!M!5|iRtktR$t5Y%Vo+_Gb>D!h1j#IiQXF<^k$^5ao`N%ZnOR9+&2hx#~OPT zf(BfBhha!Mx8{AZO2tqnX5cX?Waqi>egSNu`J9wxw7-;n{&z23S^l?|ctL-lH4o^w z^ZtsB|07!KYsl$nYyTasrR=QDu7K`}Q2*~-4FUl;dAS9UTrG47#5~~t7p~?3UybcQ z;_6?U^#9t6zrMTwYM@{J{cl|T?=AH|L-pl`I}+sn8&qHU>i-PY*XVhjBH{Ep|DU7+ zSGfCkO@T|e<>yCQjr))E?^;L+5`q7GMvj30(&3i?eFgu>dSniX|48}uaTy3)N|3e4 z7JtWPk}T{U$*}@Kc3N!>wntHO%$}_6H%8a zOlEy12*YL$6(mw(3(Bh*3XHwosm{UB{R)1fq`Ikzj(Y+sNrMXM(W5873;@FE8JC^n z5}CdqTB&u#6w{@kbQw)eF5E!FWATbEi3Z{q+?-t8UF zn(nA7Q%Ih+@A zw9|kluxATxM+Z)WYB>-l-GCJjf|+B08>{(QbNWkp{SM{>ftWbeFEoYPnH#w&jjBf& zvjluD+Ui?h8%CXlH|g=x7{udv14owu79X7`M$XA+g4s_Wmunh&r>!;`)PC@;1N_9G zaIpP{H*i;0{{7UsbnW%haQU_V@4>2`Ce-}O@Yit;_pk8xpO%TMAP43F1@Qd(p6CJ| zg8)1Lo?jujrk$NLfahN?r4((=?Jh&K>zm|{pFf!z8y{U{hxz@&e@Ui&c=Fi?D1TZ~>hhwoPP=GgqECqwJ9CnOoKE;z#UtYzQQ)&Omm}JADsa{Tn zkO_Y4GO8W4B%h2qJjnE?5RClsrFg$_?DYG=4(pF>Sdw*#xA*%L-|LVEz5+8H?>|VI zuXJogghygx_^#c?2;7=RhK^l#g)Az3pIndZh=0WL;$TH=rHwP|vOJj?ZpM7wSr--?@_+0*)84{O#+Xjf0Yhir&G)qyp!+pKq%mjVq9Ti@`1i9NVUT+I~vK<`oqZ7KiE?v;?mA z!LYNE765++8GLIm)pZvK6N6j0c%&%0uL0GWMVLmR<{|bgu+7NIXO$rSCXL3X6>I{t z?q!{?t07i;@nm{sdJFdQ%n#-p-}j--ZWN|rLspN)zm$?bZ;tKZm@XxiYK*;d87)oB z(~x@Vcvs9Ou%BJAfs&`s3 zsSx%XCQ1fZg2Jh7)F3J=R&rj1c^}>vbkC+VVVOFq(BZZB7cd1MHZ^owqd;x$e1Jk= zK431b=wU9?TB>1+UWWSQP>Go4@-(vr`rCA)`>VaHez5A#2V zh7WKA>+_Z+^%ZhpaRD(ZG~K?mgP-_U&TBxr4qv`|&jjDTkD!h)jrP7r9l@PcZbw@E zJ8^=!&agq36;rFOTLJ0|;|cWj7Ocb}lkY8XCio9uaQSoxtn7ruXd~XBK7He8f0?EM zYI}p{&{U*05?CLhr8n_x10W;L^tMFJN$~CI>(RvFTn<$?({}Tsl)z2`Ewx%2Cc^g2 z$LX}Dl!tpvmNfoEA7_|8av)d)JyRQqfyG0VbXhoGj=?J`oU4vGSQ-|pYKO1{ULROn zjM8%Tw5BY-n8@F`(B*=;c#J>Yf`k&n`bMMPc0_a;#Ys6(d!*~D)%&5-yv*K}G+Qu+ zvcjr^JV$Ei7Tr-(QR;}8T7sLN>IfaT);5$`qI;ZvN2vXvA5w7Js+f=RgAcVVnW9XQ zNdox`p;IIRQTi#Hv+qAyDv zn|<+Lm&8~ciZq=HwYjrUTpxqv1-1ZD;dIxdjj%H9gDE~9udJyok-qi3)5<#SVHYO> z2H+uBNf6*b(vO~|qay9^#WMYz?YM3B!rU8wf-cla-j}GFh1o;2M)XMN@fXm6+<0b3 zsBOA0eN>(jNxP>SehD3sT)4i!K!a*T)<&lXH#weueO_6I@6WZLYpf?1*V)%Xf+Hp~ zXfk__9(dp0XECUD_`+k$?0c6VTz~GCqF?hjgv5942eUo6BO~VG=E% z^(s)FS1vy_Po%JF+27A4C&E_!M2eZM3AI{XcFtx{aT?s07hIn7@g2}ZW0>snP4a`H zxlMNNrNByrKqjGQvLTGwhRcj`FXMYhvPZW;0KNh0&}Gj$$n0mZXcC9B%(A3Xf< zC2ds%UHqd7+8%d`^r^kydo3OIIhw6unVwc!kJtp|G`F0l9YexTlimFBSE?6HkVR~P z!6W(&2(tKX7q4HOR8>tiy?g=sU3pRFq7oUwSStTV53-gDY15hZX!L84)O!0|hD2>* zX`Pl05ysat$(Kw0H3N57zGvl;oA~otE>iDV&!L_<>4-ntf9RvogN9DA>#ccRjEHWn zVoT_@h2s+PxXE$O?DEDzQevL-)Xz|*@@8jq0Zu7@8dE6>j0&1qe8C_8;AuRe@d)s$ zmoR3UbTa@#*dwpcHXixVvnMqK$$633sN3j4zPa|ERM*xU+Bg9fy0C|DgCZcP`$Mf{ z7?y5?ooGE#-vX`BZ*xABBt-4Zj_6cS!2X-{2bG!;EEKhD49kE9^OxHKA2 zg=heFUe=(f$G#q0Tvj0HvlKYl(-C9#{t@vqMKZ!Nb6RN>J%O2!a2FKV3 z#Jgb$A0&O;=&fYFc52r7;eHCt@07Fw++vzyTTk}x=LO*Xh$d-Sr6T^?_>hPK|0VNL zt`kDmk0=67h$Hqhcs4m*rB-2!H-gkojDlS~+5Myzq6yy- z?jhej3#u|5IG-P0FfLB{*EfZZ@d2TI!)qB@7Jiea{qXINd;APmvCFqy7aj~RBg{pL zwj&VvF3Na?FAd71IouXYNOyIFshTI^k`AoWgvWP_y&v{BU2>`;ybzfkM>j-hY|mta zrnnb8$?_rJ*{l*p^eYiLFcOQY4FwUMR*yy>Tv{(FZ_s{Zdz(rCR8K5fcZVavRA^g6wlX$I!3UVv(Pc9mZfvKO*wir6a(oVIR@W zdlGKY4Q`5)!{aADs*VMDDc5z@9_SHoFpmcY0U^^stw7KSx7?OU_heAFZ$Yc-B+xG0 zU|r;xDHjE=$aEzKjy!s=o7Q8F3q>=;ZC_M}yCQW44#$7@`DfsGoJ^}!P5Q1xOT@}k zU|W3xT15U+tDfFTUj9--NsJlEJw(XIz%-^7o74v~FQ2|5>|f5x(WvMT)Oq~0e$|he zMaYmnL}F2`ii zeFT1nw@~8%?4CKN#3xF~F6sryEqfz}N_IjF!VB~S+YJO>V-n_EL#?=Ts^zzQ)&!Z* zUHjgUOiqlWXC6d|EYjGah=e<1ny2_VIK0ZlJz^I@tEUbcS4Kg7>@5xnpx=^@xa-`= z{1MZaq4fMFgqsK1`Yf(mh7nKgd1m7-tX80=MBCgTj~$fZSD=&RDAq39+eky#9)#V; z;_pz>Sw9#h_(ic&-gIy5_zJ#`3B@DDiTP&9kON(R-}Ij8_G_~>DL48v2bq^=lm z)MhY~Q#_iVScp@Bv-z8D#Es~9IL{`-NT-F>eTkhfy#2Q!wJ39tLmvCe2Ly*&T`#z! zude0a<_xiSrgHa~uImMvvWD5r4H+=4@0qe1hS^8EGk6~5^$@(!sL$$N7;RIvv=}>| zGA!O;vR*#a$#`2VcE%t~JmOcT(Q`ynl_T5K&bHO{(2nwje=lw%P7Bzi7%KlEoK%W~%Px=nlA-0u__Qa>wh zy_~hm+)WfDw`J)X3ky5>;5p_EYC|@eSlsgu+)n75HI@YU?Qu3AyxF04{3bROlYOdt zvcw_jN7hMJ!xK(9?}VNq2_!T>_6!e6lL9W27#p4d6AZnM(zphOo~rl}Z0haf4@W0j zbx*C56vyZodCK+TpRSX?)6L^&5Yvr-u5^y!n@&>3k#n-VH$p3`|Ad@ujuE5KEg)W( z53N;fkS?Y$xudqgd$e@-Pb`vJv+0wIe77`N=&Kl`r4_$bIY zb+fke+gzCbqZsbCt+&yAVQci(S-#o>6GM+ek_?=62M}~a@Lo9U4_tYm%QP;`I#8S2 zlR^q_Zx?5WmV*nh4m?RBd9FP+bWBvy_jh&;y8Q+C3aj;8bW^8eFrVQQ!_-OI6)b<(y^-QG8RzO?5a!Qtd z&=^XsnBhSl?{-Fyqv*WbXSX8yvFVI(fjTsdr(BpWrBQUcdNWmRU~%Y^H+>WS7i(^uz;;9(Ak`tkCW}}%$ga+Z%HYes zVcun3C7@m+f!Aru=N&YBJ)Kr0`(Xhxx#CqY#o`oPI_G+8f&v61J@13h6Mq5g4E9K+ zEJ4#*?ZrsOSgLjp`+O*GyTlguBFW%*YE-HzPB2cMU190+=QgfaDCy|ckoud(NQ^EQ@X=w|{$zqRU z6wuMc>*B<^5#GX&=K4QR2awJHa7J`VT(_)|*c>ptNQNw9ll^n2_W zoN`Zje&-@1iCt?lj`}|3cb*{O?QB`KjQjX|8xrDS30$_P=jB&|&6w&PTIjffqL!S( zgZzxoL;j23UZHOinedA(qp{0VP6i}e@ov8qa#oS4` znm#o&eVbJ^aAr=PmE+K7@WDq5&QJZFtV`3*UaV&9kd){PRMCl|g;2I_1p1*%~WG3Uoy*_wc4_7oGWXW}eci0a$=OSPui z^pPgi9K^>>97RAj-P-DMu{n~QpOZ08Tl7xV`q&dqk_wy}g&BChCM!$xHFh&ZpPDa5 z%i(+=OrW>h;Nq-stIJ@2OP{7emxxXEb*grsT;yS@af*(hn>JsaZnTnUg7-dgM!c@= z+|;QPb%aBK(20wltYTy046!UzcSmiX$s!8be9x34^>!3IvzlIz(mWQqBLhiGk5;url0IPrDws@)oK|eNYxo{ zlAbH;r`>vg=1_f_Uwc&c4jHF+Gr7(dZ*n_h_LiHAEUrAQlo;Scj>HhBp~x(KT2Brd ze_N{8W90VUakaeOaW^GwN#hbyuKDiZobY_Su4CePV%z|CMzu9_{@28k)I)J=rdDqZ zhtqAKV2R2pD=qpBvQ%R6HM}W$;udD4b3Vz@2K!v|JahVm?>S5}Rk0&{0WT;cHah#d z=RKm%`-K%`^V@PHyO`=`gTJi^kV*7l;?TXv=rOl<@i*QcBC#7YYccX9ln~P;xu|45 zxW-3U_1V&0H@n79!Ia+8E0}d=F`%Uj;p@ESr0Zw?Hl#AVS_A1qUIPmdfC8Q|DwZM`;l0an*LjRdP`~)_^rG*h9V{mdX%>Wbbx57nRYPo?!wM*SYhod)T_1ptj`0lFFCt*^< zcKCVXkH@IuYNw#Wzx7_jSS~h zdSx2u)7+%!#IYIGb)O!6SBY2XsLN33SkT87wc&ZX&ZrxAcwLr+*n9m22 zwRe7y#hZI~$kfpvxk}N|{$Nnooj!<_D*LWpDo)uURwI64TRmKVl|T12!2*+tIeW-t z6NfTL1)Q+0P%JuFCr3A#sThLE!gslj^JF;Z>l8j~ki5&WvlR;d1~hgZsk=xHLYtVZ z+TFknxr;z|DTT^uMY*Q##a`_5omTZc@sdU{F!P?q5dQQR^r5;mU0PjSXFi(Ahqg)9 z$~}9rBXwl#1N{}xD71?8brg!*n2A#e4x|Oe=Ms_2gC-o?Kg#=eioRjqELG=2?yp5@nVi3ffGg3epLQNBZzm9C0)+-6pO>lb(nZ!rT^) zt93e?N2l{yJjJmmkLw+)1OI_S$g_|XKIqOD|1;6ug@&rEQw+1uIxJB^ufMm} zo(aNB@Hm<40*{-EJU__?>Al|=goy;5ih7{I=ES7{i@rJmrND7}DVyP8B_O;#Bv6&q z8l=`4Y%dW}jfJgVL@)aIv1zSZBqk@i-Iwun2_|F*8SlV}Ok024p{Le_Sn({ehW2*$ z5nOA$@}tq;XUu@%=l(CbSIQ2zW9)o>9Pb>&P_+Br9AED7+4Ucd&R*MHytrK(9;n3o z>2f=NaFN~NdvhpNt%zbOB+B;|XT#=n?Wg|j*AwsColv`_(^bRU)h(o%K^x*|a)Ptr zm!zHz^}<(Z#k1%0=D333yfa--Ep!5dj_ut^FCN)>d(mY4nkN1W=I%5*QH$lC)WN-l z@xpkY60Gv%1*?_Fv|}5d<*UubOK7Vm9yM<^*s6Sc?H}h6Pp@@FgNH{?r-(~1S$_C3n0PO%p82eH zzQ)gM;=i$}NhaC$vPqN9uQ_Ry)7FLPuT0ikEUfA%E-X+V<7<}TZ<*ZHJf5itqG-8A@OJm6Muys&{7W&1o!x2ecLMv}5hk&7 z@PN7Q3fv{x{*dDD#dmQxXGv9OnD7qZ=f5++{;@)UumpfrOauS|uo^>Pzc4xecOK|> z9*_kDzR$it`BmcAFRO%|t@B^}nKA>JfdH=Gg8$tmOqoHzE7<>k zH5vr`qtyUbHFtX`fK}1RQq|e!z7PLeQ2>E{*JV`zfbMf7EF7Jj0qp-e|DTr`*i?bb z5gQR-A3i|fhSUG|c;37JS3p6Wf3)n+c>b}p0lHi6`0v*??)tC#(#ZL~8+V-niT!Q8 z!&}2sEolI2X;_S4y^x2i;VhVtQbO3BupW^zCl431i7N-7qZS|m=Pvb7+0%$$8N4t- zOe4(xrW)B%4pZDe_Ptm+CAJY2B9?p{q1c3mCx7vETw8{VOUHqR-Odoj64w&H%SIzx zZEbe++;*5k@pU1{=H+t{h0hUn8RZLGQx&=stCJJ1*0Q6NynLcz=-CgT$jU{Z=_jp| zwp}`Rd?HdTv+ab?I4a(*dQZu>UOqpf`=0iZ11WRI+6?~YbI+qM?@(^{!G7-|m){1v z#gAc=34FB~l5+Cpbd6rRDY~lF=6O6$qS9!`c$(Qp%_cM-8v9y`CY*8@EAJ5qTM`^D zM+05wE}^XM6^;*MMS2RMiGH47vMSRb2VWCG6YsVvS~S0$M}JI^nXvCoVn)K1PMBWS z5Z!>)kkk<9!nm8ZThi9`vzxop;p|8Ogp2ACy-FN*iKOkUm^438Mn%P%_={iW=de+lhkR$6Qj+dF6GIY7Q~+ zs)<9`4C!wVE2u8pR-E-a7Z%%p_Qh8-ED_5J!p#IDw@<5-B0eBekEnism+IyLSFvz9VAA__sVr1i zS>TmFOVHe#w)VXE(QdJ7B$=s)Emf2*{&xLg61j&)3He$yK;P5t**>a++M%X5`fnaK zNn&FlkPG`iX*i_q@xovIQdw?h;Blx&*yHOTRwVL#UT`DQDx@#1CMP67FlRQmkXVO4 zV~7t|)4yf;LV4herOM6uY%%!wsHnL$oiq(Jexo(JtAg1JeboNf zuk>oK^RD2M(^97v&nLgL&{%)VEq$*l7wT2~6|sCAS-o2Vc>1<)AxS87{wM^nfxIgp z*}Q4B5wa(p%8F`FuxzK=;djIf7U#5-D67yVd~(9(+MC^6V^*_`%`^x$y}D}IfNYP z*w+|G$t|h2*>+8|a%o(Kh;u5jZ7&II=CqTLcA)(jgGQlO?naxcf=?cjKhTZ1G`QM( z6oSo3udYzP3tW8 z@UT51&(+@tK9qsso068NZbdivYs$@c4YVE~=(&cFDdV>R{f`n!L#5iOE&S8vJ|oIs zz4D75$lF%WhVV^VK^mf(ODWPN^()xF)dOnu`|K6@dt>HLwuO5DxgKkH5KM+W(F(F# z*DOuyijJF2oEH#*qMq2N9y$JY5CKvRw5wIzXW=Lh_an|yJ`%B`p7MQn#+-vEs+uWS zYIk*2ru>K&YtXjIBrhE?(oF=!(QxU+z!l`1_WH_Q!7QZLP0DC-!6(VEB8GMH2JwgV zw;`pRuGr*e&AMGt|4ob$_ncsXV5!rOwFq9c!s9`)KNiO1ydu_Y~r)g8q6oy`l}74mq^NmrfKNML(Z@)#*BC zeAYckniqaORpY{_U}?r~dA#$jLg;IC8B%?AYNq-%oIH7v{vpEmOTN>mX3z|k(olCw zN-YBl#O+WlWa=M3+~$0O+6k6^@4uzA|_y@aJY*B5r6b!-57R(JU;Ua>%NJ`K&klT zr#2XHyd+e7%hCMm`!l0Ab|U%2oGy>r9+Ay1zi?#Yy1A_kvA=DZt_;%qoaEtBNEq!f z+^NrN?KTm#J*&lER=0iil;e>wMW^~Bc$tfRotG%nSP4cN1f#R)UWpqf&-v!|Hw@3# zv}wP`i7tC4mKVRsi$=97y z(UQEIq5WAT^wAwNQ0TGSn$v4W%8SGO;4zZUjCH>XW6#Iy0-rxusu|*Q>MdVkq{xTw z{{uw$z0U#miU;va~_CJ6)|3{Dj!#;m(fY6WsU>gWT?LTmjAbltH7|o_e z2GQah3+cI}vF8J+40MYi`j28uJ}!DMC(tW=xtGjHGFGPq8zFkoqb$?;E`}e3dI8XO z8Wcyr6T)5vX|B&_=ZUv_Qb(kkl=E`+f-J?0Yb2An(fOH`%O-HN!HN5~!?~zEjPawY4M<;OFNvR-!5Lk#R zJz_^Kag#UVo7%e(!q`u`aju4=!f7HPMx(cEN@RP@mBm-(paRe?$NHC(jBEaR-_AkV|>%X=ZdVRReGDXND3cK{iYpaBC_NH;9u}e4^;5f00wc&mU^cVcO$2dv6 z)xL0H#rkZ;@}%%RU!hGj-2Mg2Se)MF1xi{^?eRYh=iLzf3-PgW0{v5{Zgv2O zje`vU0y7zzXDJnUTe$-741rJ2DX?t6n6#9j(& zVP@`p@8;g5>I}8f0C4}Rf0vN@ow(~ZZ1)ZncFj8&tnwGc{?9S`y3i7Fo|tBSueBxpUdtt;L<%{X7m8rxmST=HJxTS9$CB~zFL@A#o@ zrG>PGvn*(k`*{I(%T`+OE6D|5_8_jD=UeS6rmKyo)bl1C;?7sIAJWOHeG8&7w;y*i zmJ)=lQSM-O@jbq%eBt&*eo_A$TURCWIkR~RU;3a9!*_u$0;UY_Nh+ssX!Y`BfD=A* zYNL7Gnd~PWukNy^fqA5C`KxFqg(y8PC#2eACAe+fz>?*@vA3^=zI>Yg#L@ct=`7CR z*}8;0ZwIdTSoV6#^yR3gEK^|bfR^zq2OWxp%s}2ApMcS;c8wA)BAP%B(qRo#BRu59m`ciy;onIjd69K=7=VoS$d+>R6|Hs0XYHt+pZw{ zgerecNIZjdJ~`wf_aGQ|A1kEHnn4!t4K|0eeh9JNtNH3D6;;n1pS@n0V0ktCO~&Dq zp3Cce0%(~vwXT_NO~t2YpBk(^8}cdeCg4l$)h6Ty_*;Tq##N^pHKvP7^@8|wPg_r0 zyB9>y1RG04Z};o1xQZVFQ|~otiptHkzhSz}nVaZZeW`gjS}(5&4F7V&JV0 z^)!2BN~?!WA5EpJ14S|QmD|*77SK#CjsqKw*$j>`8A6ut0IELofaOQj@Y`lV+NW`p zP1)1&`#XCW_yl;-^hzSq)T!btVN}##&kX5t<>KY%)6*Ld%x!1Vixiix=bVXR_A-C= zjz^E>OY-qW$1@b*TP9qRa!lga>nf@7v*M3&u1V{N&7y2Ro-Udpy6m2pFMcD&KC42K zCa;n}|9t4+lFa6#g}V54uVnaf33gc@&54XtHqWRvNQiU3WKPerA^qis3SKw5fc`cZdno^T^O9u{4}mN$LdJm_c6r^ce-n5?fK=Oef-usCvPO z{B>IT6v7&6eSQXl^3;u)rJ70EABr04#kR)Zd`cAaKiV=Sp?f9&I#Q`l=a&+bw zTE7vx{Xotm^hrA*&5I&IbUGg})e>79mf>|SWmH_x2Y&65XM`@Q3o4X2#rW zy|a;%ksVJClW<=2+cWsF&csZ<01C#e*nymB549iqLcMsAvlCa0`z1v(_?1f(`|U+> z71dUIFpy6}k-U`r)*?HA(T^RbLNAQnVvgPt_}cdJReZ-;3wuLh(YrE+bY)M?`!r{5 zPp~EM$c5r2?g)QPzMq0{57at7quJ4AzVm7}&@Jldm^j9V2Z#0Yh-&R||4HN~VJKmT z*-i8j(;ATk<66!W#UHy&{eRS72{WRXvtJ0|eZ02n`7%ernT>2nvDOIU zANr0)J?0j0y>>YPKV6$+o*~wmj3=CowjN`63sMuR|IEO=CtmOOgL-91&3>(_mj1Ah!!jK zEk)?d#!sib6t=aH3gC={eVOyOnFlj&_9(_@3^4|yT3@?Zi5Q6(30L;hNl4B{Bl{(8 zr~jMtbhSx-W*Bx}%07k>k{Wbn?zS(rrM{aPHF|K3NOMl1kK9kJz?#LJeaL>MS+p8YzPeuCws`~s~}oFrQ{nDJ`5oUAQO9GjBvw4 zKqWb8CxDfOnD8S(R8Qvv1bAx?KNL8UE4E|8A9{mHyiRR{;Q&7Wb`{Yj#2V7UM08W| z6^%WF2n=rmWC_b@bS~xUVU_raTEONNX@FWu??yykGOdc>O09>*O@#jL1s|cyc*8Zs zxS%=iy%bhGZpic#S@r}L<+INwm-Vg&x2MY@@dJ0sh7 z#`FCL25|>b{>31`FmV2xLV)j(&wqh4;6LT-|Fk1sKeo%V7l0XZAlOIR7}`f8zfFq> z^&HCmGEs+N=qwj30X0H*zvgJ!?MA2f8oTKb>QVgRd0-9yQ``WJuuH2LiJ&81l+d9j zdK%5VY&Ge)!jy#zjv*Vxf!(d(-{X28tKV^D2mKM&J1G1+tp5vUCLyaMCnCZ49BK*Cc&Q4pb^3!m z{N`f6vH*8^|1$*x0bygx%+3jhkqsUg9|E(1|04$j|Ds%faxmBpa)(;~!ogr}?oI!h zgTXHRYdPgl&DjJ=A$R+*spvAXMuqN54a@s4iMOPj!WriHWnA6cp|?d`3hHh1jvo?61% zeOKeZP`7(_qjI043D5^Z>Jn!E*5S!Fpv%O*8=+b ze_^M8WfT8hVD9X{IPZUo$ZrY>VEx@mW~uuY{~B2n2#h7eLVV9F?|Jesa{edXxt|XH z%AUc$g`~d-M&MtZ=srgO@E0?8_Wx;BDN4;sDN&tJ5apfY`?w&zQ6*hOC7F++k|HH0 z>qRI9VL1RaKnYSnxK0hDrn0}zj&h`_&;83YfbYs` zyq3~_V8JUxvBETgkNHg_-_gRIqRXuxw}q{_)IBS++Sm9|KSp|H%ah`0%Xr0r#}3ES zoKhzGZ49HLPUpf9^zfTbdg(jiR19A1PIaiB#>Twnbfa!rl63d=OCj>SYjGD_{ju1`K4P%Z8IobJ|Je`I&yqDSbp<*uoX{q~g{ep2of(g@y`J0GSgA$eM ze5R!+VMR-|;jcQCBt!Ty;o5p9EF6JCMC8N8;Q@5ZedlCc6fS2X2BV)Rb6$@-lkQFI znB|TP(rcOFHRNNtV^|l_`}7~|I&eOm@((e3N4!|+Q8WHFU<`RZKP6=_n~2Q8aYS~5 zDu}9_QvKs(a6&~B+u%6}uh>faj7o{o(Ln6jFnvYuM6zO!z->2lL+rJ%=aDgT({28! zA=eyHiVd`3FE}S5RyEKzsYLd&pHJ+jXgR(t5;F$A(CVT20GNpUKV%*#ZqZk#yx-N^>UK9-d7!2k&vM zRay^Nf8;jN24xwE8XrS>s`{Pcm0Z*|b`8~ydTRs3zB{krMtyu|ctjmDyI7hY`^m21`gfntu7N;0qvCi3w zs8H4j{DwK;TGq~(SLv*3V>fJc-od%! zUWUpl`nlx2g_~7RQfHcOjk}hM-pCKS#eII0i!tj_7AlX2^vK@H^s%o@@^Kv#)Z0w@ z%uE^O5pU8)0rL%VYp@5pBHlgy$FFz>HWy@cvXQHJ=Krf|;cPW1gG0wX1 zuO;F3=`!s#V>wFXcxT-lT?wS2~sO0!S{YsNAci1MTemuJQmPV zIe=5BLa@BXcR^)J_s;o4CI?)NYsVn@BXi@VftSr5;lNdsa(WphiI92bwy*r$aeek3 z6>ytl-^AI`Oem_G5IVZ{rJ1P(5g!aLwu;*A!oRDO{h;Fea{q~09h~EMLypqGhc?vx zmnm2^xQng~GxBrip!3b|3#6i{`;Twq^>E+X#%?{~%QZi44VKu^$Wgzw6d#RK9zN)L zRpb*n!yglB&LBq`^JcbuwxG6HCX4MPHAO)EI3q*Z(*o$t`k z_!NPc$5Q7~s{zdbK_EpKUaidNVoP3wh0ulj`?3p%M2m2X5Ox9Mqjs}KZkfVOf;{u2 zp??^W-{LTAM8G^ef1tO2ek3d!`{FDH$-*tFO2t}0UbL8v!V)^Gt0KmVn<{jwx6aD%}-`|S=-SJNG z|82iJ!c^+}HGn&<`d69@R_fnW>-!nyuj2*VAoXAQh~mF(TY|yV-@Anrey{D^Z(9Ph z{fhiQfW5!24ywluTlV~#h(0BL#8C`NU8F(fx z1q|hDaV11&l4?}?h|;<&(htzx6wwxSY(_(rrsl1Kdq87XT7L-l49MNJ8N?uPj_y{q zq(~MuzDGYUOdkv;b{pYxKwp_|?%Izx$;-s*QI{sMniLs zU)62cNGIseCVGXR(4lqse5Snv3&9&b+xp?b9D~D0A>7yFL$5Abay=y-Q7*-N**!>e zn7mhZJ{jks@ZxafH3nY9H$LtNV54SH7cV95Na~$DTBd}a-vnvINaO|YuDBOTY;SBO zHyqlv%5WbQzPom=^S`#Ixq8g>(01@_!N}0iWcdm9>L>uj0+0>Hk|G>^f;|Hf7VV~Zu7`l#U=5g&1>_#0Nzos} zAr