=!QjiByB- zG<*3br^!ReauH5J5-zTs*0x^#nfa?^t)AAJw$(u#xg$msb|s%N#nVe2vkuX{)<(Il0Z7t_V2H)gLfB*f&EM2cuI<6kmJD!Ydo>v1F_pS4JU3GfH2IjKmezEY~ zc*aC;iXU`A&+nN^!zaS9&a&&JKiwxIElx|b2<#?DJIET*9drBA8n*T%Hq<7E0K2M9 zsTXzNmliT QBses=1Y(5GYLI)2E zLiJPsKluombPtcx*7DoI(3!Bk^P7}xank`dOC7Hf6S1q_Pw^*${r!22)0$>2)8EGB z8J2mQrg>*APFN*diDU8V4=F|cRXw;CT@|f_GOsbTW6Ul1T<9Pmci%6N8!SO|77seK z6%G5N;~R#?`USOi4bQKUu37~h@TyNO@Mm(xk%NQpaKS%j085Z@p0kh1#*is%*D6 z5M&&NlcG;cP0^Y$PR)G`i?R=$GU2M6^YH5 +Pk2zOJK-AtZ1U=}jK!MoM^erf-!IPx0v>2v!6?gltMot_ zz_`tE(dX3Dn$I$*e7q`SFAD(^MqXwVup*)Yl9|*-KG{y)bd@Gu9^92vW$p{twp1ZT ziN-RTHfb!K3A(J#2X-wT%*N~0?d}iX+gpg>B&ugB!eb=2 KGdaCL29iO&alM7yFi?B=j-}3#OO4_>+w3apV(m$a z bz+wFYVa-be656<@O<1?Gi;2hnLB zoi>>3 ;O^}aV(_!=qJFE-lH1KjCr<1F9w(=cv26;I>SA~s S} zO46|U6RgFGo*LtFc5?WuYWRSSsZGUR2n$m&K^$Qbma5Y5(D?~2!WHUDZ^%W>OCM&s zkwtS_V$2$6IQ^p(>@V)MasRaH6%~I>tLy*rfG>TOJ)AVy4sG+`e^N{d ib^oeM&J)07gw?+~ostq796d+3g7$QCF}KO2EIuYsEM86iv_66sm^m z+-*B;0z&z4K4O+nw&|%tI^^CJp=JR+gW?eE>~2^`(t> OPWfuNOw)fTU{5i9_JNdW-B{TJIavbOk#^oBLH z>^4Qwd{%2Q7~qrTBV-fVfpn|RIl?-sZPsKXi5S8VITO}yRpBhOAn$iyxXnV;6de6+ z>wUyY?x)&!#cGE{Qf?y6JE?~=SM78oO^AAp%h8R)sZ}lZ!mAJz%7eG83pf&r7jo1` z>f4HmHMgj$Bg9buB&sE0iIBekPjs76Rq=Y=%^Oh}iU^6nLd%K>(r>JlKNIR%cR#xzK`A**k?#@Gbs zT?himW Bw5Tuu@C}s>U6$3pSJG z E-1Qw!L$Q;c%h}XETHC&D-|qB2T01**wbyE?Zu{@5 z+S+>ETHCcXwYOKib2PO+=sy}d-A=&)OY*gUW}u5%AH=ZyQHQb>dF$EJ83Sij5Nrwn z?uO~M)a@#UnY q4<@zbnM6{;?X%(BAMWYbI(KwX zlIV)x8kB3hekvj`%DQyW4UIq}%-?+F@n8(wW+1i5(QdiCDkvKH*pGK?)Ol-%33#3R zV&%zSZITZahqK p<7OD7AiwluldIq^st3G|ws8#uj9nvPaGphEJX{ zmzO^-F#E$FG(~n)llcz{sY-fsCxqX=t(nq4Gf8i&Y)b`GQqJ2So2jI57ETCRn3El{ zc;6VH3SdM XWwrGJseq z6N|R*6q!hA2$^qdv|M2A2SHuTr&E0q8o%JY;)wVs?ksKW%}3V!CueOZmBKB!v_&fw zz-!DVDp@gRgvk=o_J&J&cy8+P^S5ZuBc4}O{}Vsy;|6+LyiHYftnT=ZlA2FR^ps!& zL&Eanr?>cIh(Ro@^a$s!>g4^Zi>iRErUp}cq()!7KdwccN2IKh#Hl%i$`t{dYp %YW+`81+#C_5@Rkg*ed2k0W|Adrn>Fy zO{C;9iXBu15)%xu5N1PFW%XDEl0|bOb6k1FuPJF LjPlPw-T#U@rDO z{90SvT6(&hYr0)l3dac>HY3^J&4dR91DMUAM-Kx@;5P$)7$>Zl4nWZ;$3RDUGpk-V zjis)5u)U2T-Ya!-A-7Lj_&am{`d{zh_6+LaV-;?MD;j8-V)7#3d8HU1US!8VNNO4t zXd|3BCo#dI^77jOR_jb`>=U bKxNaTfu}&(E{o!Hv?$WWSET?WN>)SoZQ;4M@@S<8}oM~VS znIMB%jiw{TLa~LX%IYK*2r>f|EGGMVmL1Mg#IW>)gc;7da6YO$;8B1ie#YQ!d#0!i zXF0djC>&b`^EcLf12$G3nk1M`ro|%N4~aT0Ge*;gZ;WBmQRo&VteFlVq*ktA677ea zyeDcL2s5MHX^HpLom)6-xhDhSMl;r9{$~?DArW~~bL(;vw_TIaM{{T6QTDfzdK%g` zRV!RZN95g*%}b>MY&1c~M0t1N4AxaC1#~zeL|TTvkvLp*-8}d@_P7j*hBz&IAq$F2 zs$}F)CGk>8P&ImSrgevdEdo5RBc8XHc3ACcJS8Kak5JB`Qtc4zOD{<9^0)awNjXEx zU!O}P-x9JlC0+9HbusAcK7NG|!f2j68 Bq6V zd)k=ehEl&ZTVD%_;~0{33$0Z4Lt#W3>ws=ah9km>h!ScUg*_z0TWK`{jvtv;XvB$X zX0=Opjm&jEO6bVQjRV~B$Fa Cq^%0qj%L B z7Auy#R_|gtL{47oK5-Ww+C=C=sF0?WebOK`@}Gt^dQIkxt 8=J*oiyQPs EPS-wTzpk?L8CMb1> LTHg)a74Vy6})D1TZhWO!`g;K!8U`|-F zTmJ5{Y(kJP)@mNMR#Zha1@^`^h9-1Az=~QO(rUt@6hiih4MK6&m$ L zywEex4{Qv`*JX3RTJrM^2`L4@A3Qiu)kx`CD(Ui_Y}FCiuX06JDyG*1gGjGP2Y!7Q zY%4EgJ6f!4tbo&BHhC5i=HflF+Q=~ g)rc4bZymWnqsiYWiY2f3i7cnA^6yX zBu=fs_M8lo(Ma
J0$ z*we_wUC3^>MS;!vc3mXN$ye>){JKO34kwp`=*#c^dLGm3DRh$sj@;&ksDq;bZp7iA z=hbY)!E&5$qc;&5sFcD^Rc};nl2c?meF&v1DNckMT#|Jw&by|$w4iaXI!Qg4K40q~ zHmzh43+%r{AKh^p5s8+v!>EO6i7P|qskkN(e4Ia@J?YBGU6XHv1y+>9{GH+5NLTDZ zn)^^Kc9pnyEn)DTxFI@6CImB2S1`~8-N#FwyaSw~U@8=ib7-h2hkPzG(7zUAxubg^ zy_o{UOUnGy%=aWif@&=thTu;%KH07Gay_zl0t+f Lcg9gel+0{DO)uhF0NB&;0y7iC_ff$JrnBU5G-_HkE}+8RFn zND3O1NYfcKf!~W|_IUR_VVEFy+dH*gI4_=?jo|eXy4~dUfA+a!a2pi6Ev^p&ZNyx@ z$DK|R^`1E2i=d_-A$U0lxtTT!NpyVT5pv;)X7Vw`6y{t ;8(dD^f-kyS`ry+}hM-)Rtj69C`L52q!^tmc1 z0Vf<$r6}-}<2Ub4EAnj1`{TfH*4Ts~m4AgyCH;sU>uhCfTD)?AE&YzI2mCE^S#&D? zz_y7tc%6Y-oG~SS>@23^emP6Ezoz-FpR!N(p5v-Em8c`OU@igr^{7}`v(#;Z;&%RJ z_@ !=KDSNRPF>V@lAVSMJBi!Pxt3BjSfAmlY{tZxkrCx{+X-a z*)sXQqZqx*p3-`5lP+a!j@68x2%fZ(xO32ezp5NT*rJ$A(OX)mBP!e>$5_4cUP58g zNb|!5b@&LUg0XkQej{Ish5yW4a~x6P(vJ5%>R}S=plC3%TLmapA3vvK^MpJ3zFM7m zieox>JL=P)oCx<;#*z6K1_O8hG!L;6bh&307ax3zzGaluXQbg^FKikY-) }?Ped ^(iZd0&NIc~p7) zXyDwT{tO#rFyQw_BolfamLoBRpkD($hWAmXWcfqqnuKVtScB9r#Zku41OX>a`R~5* z%o7+2Y$GX?K67qCbu$@4k(FW7kz>aZGF8w58p4ZW-@*Hu6jJ!8s}l$B(zdBmllw-e zPNT45U CU!l;iEZMWAX6TKJpk29x;~$M`jgjjg%sAbmY>SPvw>RtSm4x=f(Z@$_ z5);Wx@9`pY&~ afS{hYVpd zw9j SnF%R{&%YSX0BbVUr)?e0ua9QeU?t46J-L9$5^XC;73*3nD_ z6=jc5{v^gJL=GcszK$&rb{Rp^!puwjOml?T)t;oQI7ZE%qcR0Z$QbWhAae+{IeS@Q z{X1_G+)ZB3t`w|wd$+wdAnDHOeq-Q&DmgiUz8U<4N7+|zvC9JV$c`f7y7J~cgfS2A z8WZiY@Nc}w$0Ecs^*H?L*6kA#R}9ZD0#(y37TtCHBqYn)um= DUyYm>ya2{W%ikzZuuT%hK}=!jszMFz>Ny^8RT%D6T0g*I0YYDE#qrr z+ER{|^$We{t~vCIBfh|X0DT&kK$W>&{PR3i2@WCRG2rZ>=Zl5IuDQQAiYL-PF!mR( zF}gGNK<{glzn76R0&`F)ui+rCZJg61L*Tx93p2&soZSr0ufgDvvPR(SX3~?)gxXjp za9^Wn>kDHwS9y}>(r5J_Wac8-kF_#*%*N?ejg>JT3&B80o}gL9P12|FgvE|Wn?jSy z!U(Fo{_3S<->`jFiZQDWGql)i(|Wpm?V >Hc7pu`{R&_O zOH5O;VWratmYNhBaLOoQy3(o?b!f=SE<~wfZl*oQd3Kul$ZI$nD{=n2RfO~CAYLps z@D Y2{l*B{nRWD-hHjW?BT3&Sq`GYH8adJevpTcwi5Iw2J14% zQT$O3(ka7(tZ-m U@|m;-Hk?3gi9^FgqPGdHW-j1sYtS{!Gw7x65_W9P zWXt*Sm-p!C96lurRr0GtNgYq*k-N!lRQq!)zZ4f0y0}$pdH<~iC1)Zc-j3Oja!^e{ z&7f=_cSQK%({2wB@z~rF6n}yn8|G}p5Qh1|z&Io5tb6CDjJdfTI9g^qq5IvJc-5m~ zU&q1s1C+9Ir?~=UJ@Tik@y0+eClM;{A)+aN$g-{m(Uc jPoW_%`tnt3MjQU$G}h6~>Yp;bh$DPIi@ b7nZtkMBglo!9 zog>hYiI$W3=w;;kxtvus9w-?HzCmj1Wg2}6t`kZZ<~n1^S7kjB`KDxtdG3_ubiR;2 zt7mm}ajFkpJneK#^$WIo<3uo9G0u(#u3Wf)QnpPS_~+D+x_Nn7G97E|OKaIj#;S;j zb#H^~y$$O1%cEHQwc6JzcSMJ8OT+T@e7L`^q{~XQJp&_U=7z1+4mX%{j|rsvVq3|* ziB0U@au>Xj(N$n=Le>R3?qamB2}a@$9p-aOv1iTP0x+G7@V%|{V%*##o}2d8YcbZ6 zqO+@b?Lb%NlinyzyB%XiYnPBL@RRiP&z-7~kZkRpSnW*?t>GDHjDx$atH(@#=cMG5 z%_3fCUvPS>y&giWgasyx=C2}3-==9sK`codDf0B&WrA0>v{A=e+%;hR@H(oTm788& zEWXb7I#_ Mp`O(;#@o&s=aW&$qNcncW59_tMeR=^36`T@Um?$ z)!Ea=AYaKvu-h+c{l!1t1Z3zIU~0ub;Sw{+dov7z^V~grqkqeW|4v^te2_amZ^5-@ zHdqW%23%TNFz1INE0+G5w{&EIh{~w0Wb>Zt1z~90_mPNh=5Yb*o4d35BSpch*^aXH zB}o2P_YSA@{K b}E8V}v>-gjC$OK%(Gg#Ayj)5dz0)rt6-%+PX#k!XG)s{>2# z;sG}*XWc*+yTc`p1T^;@e?$KAE-3f*NmJFsR!U1c?ftc1hc`>!?^T?WZ&l$dgyzq= z=}g|qPM~_EyB}d+ANC346JiHFk{T?N8{hwx6QXW?(I-Fv05Qn_nmvt8ObuMDot^#( z3h(%S+f4=p;hXmeAa4sG4OefNxj?Mevbg~;npQ!CUtmJj#%M^CaY@LnW4&gS+_IkK zbii)o>8*{(+MC~}&W-TuQ|L~I*`GEtCDy403{-(GbNxaq&9==jhs=jIkuqNU^@MWQ z0xBue36MQjA|pz{{mO?3&p} F>ZGO9IZniFs-io$+CC`)_r^7 zq)p)5$fptt_<68t)n+J#5V2gBVwsH)jSijs11mI-Y9KU=n!}%{#ux?%t?>E18PkV_ zvxg?P={V`>_H4RQkZB6iO=c^|3Cf5f8HH@q*cKsIpqidOdKUOuJ{Se;a&)l5x&;)V zd`Bh yKf6*f>qtP#u##TRo^lk zh=SA=X+zoU*Gg8VIjTmTp)jeznKJhO6nIK^i->?*+U&+dV)DybnsEmi;csK^bog!I zDA6=SNhoG7x$)%SW)*CYA6r`QI%HUMdB?`S5-;uHR9-)?fIEN^ZjQ+EhoKOu@}yqt zTD{DoAMI!iuh)NFr+UA}emmU*|8HLG@@dj0fc|sy{zLx%Ei+9V9qk t*4_WfPq{O6y7jCR)6LI5cI1AHIBsulQ^RSFhCPntWlsZ zftoDG0ttp52;FAHcoab p5X!uz!CVC5=903Wp+K>8b8lVR6F$mxbM=Cr# z1ZbO6pbjHQ16@n|`-EHM`#T6&gZ}=@7|grK-6@0ntNyRO10dcoZw8>BCOs6!4^%*_ z{&3kuM|QxtUg_5q9veO!1u^PcVBoi@ud8ZQO%T5(K=|4Bx hCMINJ+MwA27Kr89G;#vebReQg#d^)qq zc|h } `F1^9d))y&~LvI!>y7&;vF08GYogjF(3-g}W+q#+_u^Tlz zhp9p|2qXmr3q?jq>dzmDiY IyQP4DjwtlR5^}( zDnyia*ct|pf@&%wMT>-ju{N?OY|~$80r{HkR0kLr@hKXv5MXSM6s?xPP?7hXOU)2V zW@~fOmzk7TFHlh)jxa`4#(2A7p?0CjZN*e6Q@~>0gFbF$!eW`u`f@1Z14(D2WGTa0 z!kK~LB09%qikhhGjqffB)*Z`Cor2gCJlPh&59TeEEVZr$Q%Dc#`rr>zLT^+aO;y~& zAvF|e;p4Mr+N c z4Kye%f*K^&Q{jg3E8t~TKZ)6 iE7YreobhX!~o|5m%w1u@f?NrpZR6z@!9J%x+Hey?R)PSzYsL6`#x@BL;s z%JhN)G6ZSG(U^n@uuWu?b~{;^RHjeYw~4cGKCFIR(7LL4Qz?%1e0Th|{b^A&=M*B; zKPDPh4-HP3U$(em33R$1%2%#Q@lcITyFl0)x*W*>tWa5p>hDf<64wa^pq=}vepjO215@* z)-%a>Xr$ W_kpQs zRQSc;5i+6==f)@w+LGZ;8=LeN1>jeR^G;ts2G6@)XN zPjOw8Wb6tV5V{|zMIV8Y;wrjDCM?&RROVH~Q5v+Kg0wNhRO&P~i^jj-aj_R}ri0g* zA9ga?517>_zRTPjne82X9N;WQza`_78X^ghO@I}F*s;`g9#WVCx%``K42+~$tmO+g zjFR2Wb}+Z@kb*~`z!dU<8->I@RTs=6gm1Bvj}`kw9O3+BZcF3v$!-b2f1j-(2C*=h zlR(t?ry<_T8`}9n6WH)dAd(I+vvZ3v`^T|Cy@ &>$3Qb)|DEXabHQ_XP^;HK=zs|!g~H7TWW+Rb&7$?Cijr&*WPw6TZr!6#+1nSp#V zgi=0kiKiQR0zRxgJHMNQKb#vS)HW} wfX zjk!OpKl%7>W;kDyqKhh&540EXm|a4a{}8PpZd;azEcPUkrxGGI$C`MMJxPG^zPBXh zp38O}Rx%!-5REcq=vqdEsNjnNpUvz`5G(O$PbxT;ncK(P9HvaDwq41e4jc~WvY6YN z*Hac$fLJk52+BF}c9Xn( z8od2Q TsD^qn$AFuuFT?qQs~T1 ziw;5f4Mt6A&-Z-#k!j$ScksO}Tkx>@)#zcDLOvftsnnLa$1HVvFCjM|LJI $J;H5M$F?Z!r ClYVYv%5Mw0xRjHC %r_dYhJYkC0k&0+9){J1vLQCa~>fN zRS#N>tBeNuTa#cG!adjUOn{2OmQ+E3av u|_tvRPJ7_mq9yq<9zGF7h4@}U5 z@tfO~HYX1kk{cV+)ktj6i8M@X7onJdM;(D(+e(t~gmTb^Wb-<6wElWRO3I4a0vE9w z>PJa7G-$s&(4q+7W+d{=5TI8w34gbY)Dv~`aA(b#O+%0~Xu-psNmqIobv*-V%JTt* zU)tCRL~xZc5cyIpXvNDP1Cb3(gTTG1d-0B89bT?InPl&n&z8JKVEssoHM}Lo65QlP z=R8LhsExJ&d2;h2)*k6V4-r5uw!YU=SEH=CSzoGL*}SD3!iYJlEV*1SsGc;i1rQmr zXavn=B_k@rb66djKeJ{t?(A${u3+IR{%V$MxhiHR8cr6s`(srIZNHh&%|d^W02$t; z*4q?pGCMD6C3Zp}xp6u3xE*MwW5pth-PwqJf^9=_Ivj-G$S^VScA;zMz?tJKJwx`d zB`flXC|`*?w?~d{bP}E+z>nDRkPx0(JCe$cZQo3=c2(UcjV|Tx)5;OCS8k@{;gBcC z7mMD*X1XX`y{QE;_*5E8`pxHYb+;dGpNJ1vuSq~{@sKHpKTv)=%#Hca*YoR@L#I0$ zJ^9W4tC1sTtnXWfyrk^2lt=jd%ck4F{^^(=9_u7!V#=KcjJJaMloJbos8HHmzU{?Q zFIvkCj{*U{Z6Xecyf_!ZUzDO?rqaN}B|?@zcHxv2-gGQK^OEZ6Iqy$jco%G=T2o@3 ztBdO%Jo-t$+Z62Q&{G^6`sM}%_2NHnZ=WFbK2)9p@!xv#E{O<(*f0b{iw$&c@Aq#B z0t4X(1LgYj(3!v^MrW46wn23Y&cM5x5ZKM(ysHB0Rxjo;16>B%iXi_elK;u&p-x4J zkh#nZLSZ7|MjrSp$FK+$>Z4W^!ba?U* @ZRY+* zi<7WRyZ9J-{KvEFX6xtX74I#IK&gcZj7n2TpHrwH_CS+i2BP+{cyrSIH2QFdjp=F7 zlnsir(Nf2@O3ID&7`o)7#bVao=2cpCqF4?D&-h_vdoT0L7h%3(^cU{YjVM-&_lKI{ z&j<0lBdnb2t@x jle_Z&sEuTS0*X~p-Xw>Q8a; oXKh |+ntZ=`v8(JoK{)5OY+9_R(HtUl zG+EL !Vzz@RAfxn@+X5eI-%^ryZIQ;5&2iaLI+D`nTc1S?XA_ zRzx~*=Cym#hW~ys zM;aP-o8n0SymaPs@KOdS6N$rcXuwVC!;kfD pQTuuai#!3u}h~|Kw z42c2H(s>;Cc_EP6uP=g(3kymP`+=(cB4Hlr5vHMsn1#@v1d>E!yzh(5Ml(tWu&K|$ z3zPJf)eHE^P!aXvBWs%1y{@B*@MX%L1wxq@zb08u`Po-M&I6~L9v7ok`U?{W{cZ%A zZDC#TtD>IM?*C4!{CzJI-XQ3dfoNAxG?^@>OQSlu9JEEV+N<|fPsSd3aWQ=K=gm^2 zre=J2`DpU!kB1AST&@4~{yoUs&HepR5W^hS)MU?L0%h@CzDOUm@vyqol`sZ-S(XUT zU&LDpix%3ml=$j4GbW)+&P30eIr_a*8Zye=hXAzqcGu#@yRnBZHx>{y$zC~+fdZjl z=Mh2jM3d6YI!DSo@w0}=fG-FHD2=#TN#kozsunE*=U7Q$x2m$72^l((U+?Yg!9|Fh zbEmdR;`QXigkqGg4)M5ThorF)kRD=A+Ub6@-!z!Q<73uwuVqlEY2phL>)Yr=lj=dW zx)PK{`D&iQSii6dq3j9lO%`n2=iOIJVl;kFHkGMwp3G5!^!;X#zzC=L>o-puNGw80 zY>K|YgBei4LAOvHhXC|}SM_c;cCDcrq_2y5d_;C+sYbl2>k<6RZuq%d>+0w}Sj2>| z$ly^R@-!^E7$AE-Qsssrh>(nWo9vUZ_7lnDrbkcU+Bm@54Z !KJ`ssE z8ST@c(9eW*x4*YSLQNtZLM4p1@M~-!t@x1vE4m HfCt+k zf&=&RieMGHub2eJV <1HP>YE?l?VhmjycMSjo2#_ za=hZJd;U>Kd*pDv_wl~U5DJS1h_#gR2Wq@}l?|DrnPnu<%s9XaIlBsIo;Q{dG#?j7 z%yS?RR)wcy4*L_OfH}J}`RM4P(qU2@h|b^XWciwQP+eryoehv9=;{Y^6fL%_YVTAn z5gR_3^}9R`*^cVoutTo&_-QFVeH8GJxVm2`6*Z=D{Nj*>Aos`UH{ovUu-dtEEStUn zA<)LWoaMHSx93rUQl`!z6T%ml)rZ)97qmi2y=$m3OO3R?w@IX`U=J_YA^c;d6n~Ks zD7{}teW^(8#6il|9pe;Ubm)S1?g>Z^>A=mA^TckqnBmw3E8`qr|I^@9E&`xC^9O!F zU;HMK^sbX|zOyp%w f V*T}WccYxb~7$ykVR<@H>{KXA%0}2`J&C&qWxgId-UVVhqoGs4dd)v zR6jLG#H&8-<+lw`w1In1VmTxdOXH;>UmVQfBaC{kKR(xG$1?uc6p^%5vmcZeBF4g) zDwM}?JXy62v4VNYnhLd{=^WMa7#Qcq@SX5`4`xz4{lcPhLbbw$ii+(5cT%2NBx5@E zZ{&kU|MVO1Uj{Z2#Q&$SuK=oZOSZ-xg1bv_mxH^zdvJFM?he6&ySux)ySuw3xNCmy z%$t`xc{4w!c2TFOwW^ES^?kj&SFdeYgAao;s-#laGBcY)({Be-5}0DA*6T@(&r~}w zLq$1vJI&t}qqT7BzLeW97B{Zvx_Y4iU*|7GcMNH#WtIkkGkJ+$8VEizP;kvDapxHS z;2fLCnr~R*ww5Ejr8P|Qo=Q{SvNoM=q?Lrdfr_(*XJ1j=3#xopwBvY#ao-cR{A3Aa zdr^J}SS352h4&&CLTJK(X9@K^zRB)VVJ=uIdFJHz&1H0&Tr;uGDo;Ic{)WX~Tr_D` zM8r{{vAf}D2*20pFdyMrHtO$w%lVUGiC<&xv+_>VW?l8n*M4ug3Hz+P!3D3FBzW#Z zlXL98h_4kRkBb@&3;KEn^=og~Zjb ^6ayHp|zy MwXv^49IcthQ>nlNCih;)32F6+ZLvfSNE>kI zX!p!JGx}C7+!VAiS8qgkb}yc|fIvKd5q8FfQMduGZ4qQ1GJty_*+-g|1$6^W`Iz~w zHu=}{)Xlc1|3j#D|BVmW5gucQfUfQpc&m-e=~y5?a`^e7r3MqlI#*)h^Gl7n$82Kd zU@%6$tnq2CqWm6+!A(|WpdGd&Ius{ |RKVY0oCP+S%|P||y~?l;Ke|DO zj5~n;f~#U^fv5Yhh8*fyoDVkhiS7%x?I9!tWi#!0`Dwh<4{ev`^L+<1j~nRD(Nt30 z5WRlX`epqZXP!^>d<<5v(r&z8?D-I)gPO>x5l!lIka_H&=XdlhmMJ46(}JBEBgR>6 z#$>n5b2Z=G=LGHo=gyE`e#i)!eNpzdT#VPPDSTx~u9&vJ3edBYf+v>%r}g|~ |)Ux z9KZ916WKT|AsD787@0ZK(812Ip);$a$N`4FsJLz^?*dChx1&8ZCDYe4+m+eS$B*A< zkB!9xNV Ytqia;c5o;B%OGM!ffUT=G9LGEOS1`u&uIeE=ir+5^-AdxjM3PG zg6ig8gRenoZHj `^a=@$y9>C#)qu50g^wWO(>*d85lw1k+?-^MBa((mA*na#3&)Z_}` zfPm2dR(7y4HMeoq{a3YNLUqk%O%&1VNDVF-GXdzE!<%xm9q?dMYB32~Y%B`}jJ^o` zK*h)x0~bmy*4K*+4aKHW8|2#pCcSUz8O)~VFA#K24Qvi6T ~F zXQAqWPiR7E;(|~StikLRx|3CCB4% 3yW#!vQ=SOwjBbSktw|zV(~qN z4GUE5&@%)>J_S}#{1m;^SPE1OW2HFDZWKqH1@g9zvytR0Os!3k{P*!p|S;NI?&5 z{@$z^FbQQQ2Lz^c9REnq&n}n9*Yb7YNskcUinKB6(_e)K$aliO3Juj}Eo}cNG-ygS ztTRUHwUW`&p3vtziB4KJ%4EWV>vQ`VA@{bq#!9 )G^GIyz$FI1ImkuYLxjTp>F z-nT`}iG>KT#uf2tA=OF~)0(2rQ`#jk5>3*(Ssek _xsqZbO&ll$?%!e=vPrvt*_B-ad*Q zR!DDU``P19MNX~J@iU8Nd;k7*-)kARbsU(EZBj(a(d8CJhTLdDaXs&3O)~kV8V8K` zbKQ@fp_L)m^!|(UO4IoLwEd%%3A0z;Ok{cWFqqB5RZiEo>4F%G!HDe>bH9{gSV*}k zK3={MU@`}7>MWkhAGzv)iRlY!$4{&4paHj@yT!N}Y?hZ57RR`E7q~t3&&qK{-+z(F zlXzOUq>s*nGE3XBx)YH1t!x}Kb++NjaL+ZmXsS#dOBOwJo>TfDR{+ihR(s7-{i#f{ zsR8n+wL|Z!2IYB+Gs;+$Ng}Fsa{b@!-bV1%$s^L9LS!){JF>%UDj2E{*my*3PGe0# zR_;J7#OJWGrP96z&}l4t-2Ng_JpZx={ifMG;w3vruF^Ks~F7F-rm1muOS9 zCBRik+eu-ycVtmhdU;+oZZ(`%@>2Y-g@Mb^J`C#%?JXtmf!+5NLc%^sW4-Em1XGU+ z2U&-?pm#Zjd6=m;?rcUDeX`6B8EA143P>CyO10wbo&bnXm{U^>5Ifiw<&&BZrsJh^ zcZz(Vy2u2~`4Zb9%3RF(qmD&8LzFhJ@Hq1u@e#!+oIBZ#CVEGTtonZ5&khH({k0tE z0|WE(R#QV~I24(-ML#c4C-|cJQi}N)ohIV990w&)o$4S`gX{=z(`xKrl_vdcLZS(x z)}UF3v}1X#ebOzxKJCBmwPG1qOTJ!8nsd&8q#05@#`2G4+CRk$SlF0|iW`uX$T%&o zW^2Kruj#}|707ULw 9!9{^ik%V0Ew&}g>?h-Gcl}?s ;ke#sumNsI;?6m !YM_hIcaB1j-0;ty*pvcElP|KVn(YwT=n a{HxhNEUGHht!?MT z|0?q`9+h&6HOrcJr4t|I^DBi?Kudx^*$=O-{xAwMd~sf)1-{y{HF?V%q~)BUZ|LBv zn`-A_PD61`G4Y^e(}Kr1!Z>1Bskt!U1wWY#(m-~Nkwnx44kt!}Eo`~gLmsLoMFN+q zY;NDnG0jjXw<7_`q`()<4Al1DbVxR$>GnAYgC%HICYIUm<$?@hTRw>8x!hJt%}c~v z6N P`1*oBWlrO^Jwxb%xe^RGWw&JlLkT5`E<#8 zfCSn}PIHj2QVAnz8azm`j*<$Ggbt5+&7Lah9A2??r3l=C3k>s9v-%vwrxisy44|-6>nwb+-7<4HOhVE8boYLT6 zw%ObB5}3T{JYCtQBr2Yd;hQd)pKl-eSLgzh17@Q7T}+FJ(mU}pIvc)N@?+|0yY;Wx zc?CpkbFzIqUsj`=LVbE1e|&@kl-AL7MF#a6RYN8=xC1mguc``{;vN7$5~82J@^~C{ zrIqXE>2UFv1qb=Zh|xqhTpy=dC*Ty_jD)9^@Vzl)RK2AQP$M(F@O&lAy>jifF~pB* z7 tR%(q#6OuWKdkJb7o>szGdrzTQustj_9 z*g=w@5K9*&X(5FeBcR^t?t5l@-&c)GfU8X=vIkQSYQmkP5eIyGj7o583qj+2rsY_z zi#T*D=%;304v!B>tppoQiRi=-4=NV3msur9-tFbtJqWQ=CNqmt07ESJmbL4udp%@7 z2ivpSpJ*R!D-Y|1kPr;_7Gk0RoUByau}S<=d|74+H-@Q5t}%c%lg6 Eo7pEFSb@Folw-#tg7XF4UxS7%a;{*%g57I?Cf>>MEx+h+x#NQ@;xM zf+yZwbhEj!wugv%s&Nwb@G`<6vSolmkwPAnBgUg*q5@zZLtTaPBt!P;YNs6YdpA)a za5$|tPTI-wU|Gw_rMAG0EJ(5=S0}n8KW#Qb)MJ*aUmDcp3qQ7BB8J1pZym&@5<^Co zi08bo(wH6vy@k$&lUW(^@R**_A_HfQ6fKdc4g^q8 z>JKsz%RBLwa#U}-tF9FjBMCM344pcur^YyqTfJrhb)`Dw{u;#~42y @?}Dgpx%1 zf?zKKP0%(;)Z^!mOKXC>N_%HBr`SD;F=yg{Tyj0?;+)hbl4UdIgJQX_Nu{-uLmZ ;kRGhWc<=#zng9}gh0=Y7 z>;34gczSjPw*hWR?1(ssFDDkVHjA)ra$z`QQG4o$iSxd5KDdAyj>9%cKgOKm&*pRW zIBMSsgFr0NQ26u2_g*bNEqacl3|ZlI6Bx2z7`Cb@PYjuvgR!|Unmu~HA7CD#h2Mb; z6>hqW)|)6YD+E!}V}}vt?M1+3bt&^Ec!o4l@qbB?1{}HvL(KBPH#GiWAK+^0W*o|o zCXH3}{;^EU;QFij43sQDmKA^6?GsXZcQ{Y{64V0& zg$gWPTMVq=5(@uQ RT?`e}rzKEo~vj{US)3rIrzX z2mSYVtX7*$+u?_F@bF_I;q(7}$GRFDI(?`O{`s19(RVcb=SwzHRpGB*>PMlQor0bz zd(_;w19O-G;G|#}gtxrupL?t=%Bi7E7E4%?e_s81k0+tgm=2aqU=Z%|u;JoDAlKrH zQ*xhrK?SRpq?W{By(~vrtIb9^E|Tdt-(3N$n%I6go;#waxm4{y1fgms&!_iY1Y-u= zbC&}q0K7hzAHGpAnJRHk%~WDXZa!EFSZ#%jbdu;?`idf8s+KH1_)I_?wnD&v8-juv z ==ibARdc#OrH2 zaqpDfvFUtjel-MjnOm@ET^={JRIl7N`Dl!9jVHmnKl3xp4VXT9b-HGOC@#$FyymcA z?>)6dO{3~XvQxCh>%)nDbd57nWGDJ7jky9dJo#yHU4mM71}B0bXkm)o`e^IkpV&u* zqVw3J01->os?t8>*vUSDEHW(c2SM)iEHT(NSaRK 3!6LsusXJ;`|h{-aKL;i)b1*Yo C&WpZh4w<6I4ym9RLgKoAYctI<+aI1bKX}O}9J?K$J!Y71Lcg}|SOWT2G z1Yh8+4 3Bm^EonJbB=sz5+-`VXP; zVTvH=)kikY|B;RVT~PYfQQy(&AA(YKt*>ire>q98_i+b~PuK6xTdQ)NDm%>zugy=Y zuTIqCN5g!N31pS12Ex>u*zdaF0CJ5xI1_r(i|g)jImbB0(jK;`>UTEXw(?{P*64YU zH;x@84t7iVOo#@R=V#4Ej4^JP(M>b8lNx2#e3)7`#VsW|B|5boAnsP;2H})q4{kD~ zuUdop>nQ{Cy)Oa1Hn7eJjtB|Nn>rNvQDreyv6?4-mcN2S5DI=c1=L5}&6q#lw^oHh zGR2`8!^CvO6)_&Zsf=eB9^{S ~00HfVJ_QV(g)j&hz^*<+; zJhSgbIRVFEMFu2_{UaJJ6)TB-s@{jCq$|{wFgYXi$Hz `$N%>6Ox1G{KR=mH;j91!^9NI)UL~+Pqhm-ZqC+iQf zSMyTDW!9}5;@3m{0Y e83hW@`4r1l1ECUg8OReEo>$CkF;{9BY63^ho>knWyHy2w{NFa F)- &;@Z1^Y5V0m8x-OPJFmbh9FK>4xaZO{+d{rIR0Rc|SZ8^el<8Lmp;-)&Xvt z2ay@3fShsE4;L-l;j(#_&Dl*byqcG>yYnAaMKOJ$1yHYkVa+kEz-dE?W7D^dd8=@e zZDXpwu|BFscm?9hcPR@6=iDtioWU%%ex_@tt<+~B%(+ENhq68o7R64Kn=({4Y#f^~ zY0xkEZow&QH*uw#BcUkEma>^1J;azEJ#{=q{d_ZVhNw#(R4#=A(K(i*<$?B@X1Q-P zoZ;?Q9ubf#q$I%Sb! >=$@Cm=oM82$qqJB_tef&7$WhtNVG6sAO6F zRCp8qY*}QX6#C_4`g|uG ~OW(A~?GE_$qmBTX50M z#1uohnP8xi$@^)QrhsV= mXG(CD84q_Ng~B{#H2C7--kH9N(L8S24&e$DwskK? zqFPlqa=X}j`2PKur@{M@BII>dr6K$w84n;UAyV}<{0Llp8&Me(gj0(yT^4Yt$a#wV z(^<3^664X+>2P&hq*SL~K9Pg_J#^Nv)zg;NZvFepiZocIs)~}d6>@W3NjCz~&d0Tu zc)sPZ) lOiv;^dwjOh~V(x~ &(cWu=Q|%#Zc10LWWj z*}KZGnh&|l*E(%Lc?{j+U{M$#G0vy=C=K#*Y^p;$L1#t@$l!fMG8e@xF_esi-BG6G ztvY)jjS#j;hr~ynQm%b@1I0bu%H|9lbGq|O+;8VQIeage@O^l7m2A=6jgpj()Ae-0 zy~h$_2x)Fw&T;Rr=m9}#t}Olyu+iM5IEWjO5F0imac_L $`FIjQPSN9|DRD?46V-c34&UYj$3xbwz 6{r)4qB=Im={-pF;V(Kb6v2mZ#D7v2Tx)YnB=9%GD32qyX-pW(XGfX@`O?B _bN*rT&ngJEWZE>^9`z9K$&5V}aFg0aOFqq@%*LZKc6u zXABJ113%|2uwRhmW*rrhB7@o|z-XJd@F4sKY9ovgg7@xxfBnj+BM6zeY{F`#vBTkC z-gd_gwWa4w&7wf|pb^%R#+mEKH^k=Vedqi4lSI?%56IjPz495@zvl-o`sR-RsLUD1 zf3aL+Kn=clqeeK7oZv3FWt#S1avrxLjd7(mju5ViMq$)oid|>HdAZ<<&0xvt6tA8f zdP%=o?ezE9z>8Qu$6eRn(iYmh=F_Z^Y{Eh 8A`pn0O8QH8VKmWqPfgv7YC=XNp1_IzSdH3J9y#753SjLu; 4>Nd~YRw$8LtLeqZHSk|9Uw zY>LQ&$hz#74vdRA24oA?sYjKAB3E(4s6pm5+UCQJqSRL_>%#tsSiaRs`+Nkm>j^!; z6fE~tt8Q|s9It!0CFMGb5$>sq9xatjzwOQIK{0B;o7Xlq|2d1NZ^__326%S9UdQtc zP7~+xRdg6!omeMg 2oj$8UiUZjyuoAi^_-iJvj poeORwAFkM=$;S!FYt3(a{sX)KT7%y2+Boh8-iIE7} zH<1-1BDKd5f0#n@`mBf=I5y2OU5#X+2OF5k`E_G&6~U!FYZ!tHy+SkuX;bZLeAp$u znZ73xP9lA;$`*)#N Ksp?2UJ(8#M?Y;Oq3 zHKl#mO?0kL-eg1EE&Qr(PDn!N7pCGu4E-7H;`NMu`nJb0I{~@Pf{>B6^6K)~))}dp zw(SUl^|h7TQW%~0Hw l7oh_f9-1i@Dzd0=_;?x**(2 zdY+ihj5$>GLw+fpEK#Fxf1)?eV9R)Pes4tV8nKDLr|9&0xo
zR0JtMIic%jZSpS6ymMb`E?j0r4#9qsZS^DYCzV2K<*rID zn`(qCT5suQss+uJ{f@{^TY_`I!WvQ*?1^xYR;i>_v_YOE`ZfG;lgawoz^C5%OMyO* zLR_r{_#$^Kk3x0x=e&isH^T88B~@R^YWYuML__+vdj>&R9COf zNNpmdfLM^@*h}S3#ba`D6Pi~A(nv^7gNy@RxHCfNT*6o!xLPtC>&YlVnX5cb$IOCf zLS#jK0Uwi`Yb?BpeGT8u4&fQwfj`>qsL2=OTixXECoK~Vfh3q{kH^9r_(ttDAvGbe z0 IO<@<%jnjuRm3Yqh<{2Vd0ev1cAlD z;q`;MIOI-iXDD|V708ddBeOEWmV{9+bZHIvGU??VH3VFAD)Ef5JMX#ajybXELb|C2 zU#3}N=v}I#8HQR9i iF{*(YMDFkpqP z?nN12Yk& zdG-2aa{5`L-?qt^FUAOCN zrv;ty-4qiY^raW5v1)PF7~e%8OpN283M+~z1oR~*Vcx+>EnPkIeNnX$d9$LP0wbcA zw%q|m)Eero566-WAS9=NaGdOTLRxB#?=UTSILbd9ph8l)sA!1D7Q}x+v=LAf)Fliz zIOJhqMM&oo*0HU^v)gcRmR{EzO1Ykx?~@C*E2qvYG4$S+6XEYrR4yHui9(L7Nxop^ zOnr7nrATscUqZSUqR5RNuEroi>al19@sRp{bn5Ax*-QS)iO*%UjX^1A!{0x2Z=_TM zY1|023r&$nObS_W FX8jEY I3ef4O2&tOOB$rpQA%bqaA zVlNV+={=O`6sd|l@=W!8FD=;{5_A*|-nA=14st7f$T!xn(_-JUUwpPRsMQOffl=Tz zqln~5<)`h19BJKoEf0CJe-K*H6QI?!XCvOt6jpv1)*{KnchmpEKpa^1!$Yy}Mlqg( zfi~$KnFjlrS6%k=1!7HR&TnpHH1vy3L+y9$pA#1*A)wT;G3%{J0Ip%3qx^;Vj0W&q zXuQbTocn`sJ_nUK@N5~^Xh_?iFeQxc>VG;4up_!Ocdq(BOJ5g`KG7l^hspmqG|h$@l^b@Sw8L! z8GSWXB|>uUaR(yxEO@VKPamS^Oe-P$OMh6-c3X>jnI{uVQH7S9^ssZ7pl<-DT^clg zNB8wGnK+Jq9TI=kE)*90>sEglw{==eAYXuyh(*vaA!WU0!SYyxc{cg(Gk*1tA$ilP zeby|;K=HMl&SLxroNZNjG2!Xa65F2$S4TgyV^90c$*)inW~z_a*5V)m-y{tQg5J13 zIne|o)_-d4W8XQSF o!|k-0Pn96#;Ui$#;trVKoFfTNYJ zr2$cyB-7$)Yu6)rCsBy#=Zg@N0S;n)hyBSdqVvR#7ZRQs8I~*ux?}4rM)0GP&Efc# zFn)7Flo0PRk>X7loN;N?6s=(So)H_tSa3Pj>xMyBoS4yPvf^{vThN`AkVm_2R zCHBo?ss$B89J3#!1|ZdTzcAL_!px1f8Tx^kwWU9&stQc?b9YA-T@3}hop0{uep6Hv zs80yKlLnH7&`qJ159Jf9WQy8nsa=E~>*(H|GrQ L@v znjBCjc58NG`*5{R-%yn)wKLjam- F(H}v+?8`!zqDzhXzG`g<)JxxNeVbz zxZbH|(3+S|7ZWI5SQ9{5w=p=4NqP;h!9`tEoE0F*VOOf?i4 t+_s&d&oZbKJOum5>Bx%%u# zf&8OP_|jZ4ygHVv7&PQ ^-e}l6tvCD%Vy^OD)FR2VCn=94UH_MLm zbXJ08M=~K&LJ49fT^S;sQ!q}dFgJKOQ-}uh#{0uVwGpVSN{3$UVrguFzNu@g@*MW+ z49B*x>pIZVDpD<%(&_SN z=HeF*)zra5jEbWVPn1o*!%L22R1B|ahccX2Ze4=|G=85}zgs=5%i Gk&x57QapQ%0^GqC>8ESi*{CCYsZmP!~W#&S;v!H%pXPu26lJ58LrbX(Hocu z%NdLd0)VHVm&~Zfa}UTxfy2 -g<84v6fS zA>z|=koG#FxP@b<3Ug%UXI-7ZSlf|^o$K2JUm;!@Fv5{7K{Sa7>CfB6gE*za (HGJ)p52iH=g5#$~*7K?R}3}jevy;?|M zht_DMWw$Y`4a((&Hnj^Z3mlHA(c0mUV{GZ3_-5DiJTBl?qwK0OVMqK-ftEeWv#ocO zamGRBXzD`GA*%F=;7`?yza=Y-vr$0KEs{_`hw%O!!4p@BmBqNju{FZxR4-FtVW#E^ z&aijdd#t^h7()^B!)zdaGOyvJaa=A6I4*~J$Rr`8c;IQ>)#KKP1n)Q$U7Rh_=siZn zP!uPR?ndnw_`e?wlQKJt6+dbn%^x~Y{NG*EKaPenBFX~70?Gmj$~_Kge+_4&c}RHx zLH?YN;yj0&Gb&7Bmo1b|y8&S;2L2}5i}3bb8F4mP^XV4E?4e_$DLM$!hEXj1U9K`x zK2`uVa1+^bvU|Qz;B6OAF)3bhwN-S3Q9V?74s;or*6y460Q|A4BoVAQE>dRwXfq?- zrBne#d|>k+pQBXu_NT|*h6kC2b=%|N?D|GVD;BCi+BKG8WuD$htTUsC_U49ySu5SW zl%g2B4)pmxt!Wi(Dr7)%0 fvnL(T3>BI)khF&ylOIAXgiFc(x5T10Hf{s|mBL z0R&?&2YWZOFPcQas{V1oIGu+H78Z20r0CX`zKoawpX?XnT?0Gf635by5Sv=Inf4G` zsreW2^^=q(4Z&gKMWl9dZ&lF~7!HtZ_y`IJ; |8^LC1{ z6gK3Z-Y9RAW~mT+*q>0&@_ThQNw7v!5vGUnslEh~^7XI4wDLT%OQi`|7IPo{i%g;*Jl8<-qZ&rPQRVT0Edv zLdeWJ(yd<%8p*e1?wDF^bV+H3?nJ&WY2Tjp>fyo@&uX8W)^5P9ruV=pssqA0kF4vC z=iEx0exbN+>i+v~e3kNLQvcY6x*zMqRPbN-J^ry9W#yDVN)>oxGBCb`h!E2qMUM!G z#2fsY4HGDEtSS2hd0 C<- zIe_WT5X|DQbJdInPm+DTkq5gnWq _ zf>v^3M7mayf|5$+07gQ(Ns*$MWpQF`N@_u7ylj6L rEYN{UvoTE$oLSb%Jo2>EJ+BSlrD!0oEI*Um6ZD$apfDyBYQUI=XOD+=>XbMqf@ zdBoWHIDW*`__0Vn*8g1B`A1xiwsyK!ADTz2zk>P_*@Mu>03~$sN=l3+vr>x)Y~I$T zyLdMrPWGa~mdgSyEMB^!KhK4;PZiTZtGL|*8;OlzRd`q(7P1PNVQtu_dxIVsb9y-s zRw2$scVT;BM9+@qwui3Xz<&R|^pR7S-q8;K>xG<4m}hM^CIXj)^ur%dKm>x>&p$tO zBYqzP`2T&+DT)ZmDGEEyk0s`cBSLI@@xdw!Ip~gq5D9=Oqrw=)L81b;2oVp@MVp$e zHoD7Nt9!p9VIc{z)+^|~3R6^ljhGgDAq=Jg!Tp6nT|8}WyX)>0A-<2DAm@NXhLy1| zue`o!1Mn_pUa_C&_J3x4g&dFxfLs@|PP?^LhqRW4c_%NADx~m??IoEgwLT3iEl~|t zqz14B_nDl7i35f0_P}x{J*Cx=O*sI-90hfwC-6^{ &=HGe%KQX0zFM0b{~ zMbz+H2w;r;>Lp=g1R+UVm HCNd0<{toa7Xbmsl>V={!mT{HaY&%D9r(estSz%4&@G+@sn@byI*jA7dg-KYQr ymrC!Ox8*M|1tQdw(E z&oy|U-6fNYeqQ-Fu#dv#61hw>W5nLxUpWA(adOS A5Gt$d+VZ@65!1-YqH6sHT(WiVpe0>cvdI|t zuC|!Qq=_e~pBUKp5*=U6)zem}GljZHEQIFO@f?-53FlsX _TZ< zX)1g7B3mhbk3OZ)y##9sb|e9hpMta00p8qs25vRF14N}bdyYsjp&%U!`?AXPbIGUY zy=z<|rmCt5xiR_ednlh0$=Ifc4IzZ1gmZXJ6!Q(`aa^1;%f7(oSYjJ6Lxw(c*Gi@d ze_M5&)>^8z>mT#Ut>^_i!IB}&R)ZYol6FxxOJ5GD;ayef>C%hcwbx{)^p93-m(M1> z4!!VT^V`Z?iBXVJK07-{^)Rb^tCCQwz*HK9Cce3=VZa50{`L}lyM(CO)gNDRCeR!s zK>59n7<^*m2c%O4Wb(W=%xrIoC24l=k2hiQt%djW`6K5>86CbUq|7V&bce$quF$>} z+lkr%ZVr{sxTBBn-#C8=iuN9L%S}51cIqI ?kWB|$$z>6i_b0)hMsB|ez-fLH{I9LspYT6zgnq*Z8Gpn7 zqp8r}ss5AU%5SP8mj6oiuYst)ll`Yj`rl+)T))ZwIamL8?0*`;`i<2Q`HlVefvi6{ z{v0m&%`q(bo8$jGYVarJpIXY_lr)O}hw{HDEC1yDQ+4&5Q&shEod42b{R#iGDf=7l ztn(ZGzb)Fo!~atY>^EFY|9|0sBr5;Y5c`wr&m+@sDg)d9M)j`))Sp~`CcMA7F75vt z*FV$WKbihaRDU!1JN!4Mf26H{68)K6{wBis`rnBDOf~=H{4+89%~|zdIsYw9{FCs{ or0rjX^}t3S3Fv>$JO7!&$xDHMBzizVXdj=Yk3(;m>tEmgA9?o8Hvj+t literal 0 HcmV?d00001 diff --git a/spark/processing/3.0/py3/yum/emr-apps.repo b/spark/processing/3.0/py3/yum/emr-apps.repo new file mode 100644 index 0000000..6466eba --- /dev/null +++ b/spark/processing/3.0/py3/yum/emr-apps.repo @@ -0,0 +1,7 @@ +[emr-apps] +name = EMR Application Repository +gpgkey = https://s3-REGION.amazonaws.com/repo.REGION.emr.amazonaws.com/apps-repository/emr-6.1.0/72a9ec2e-9bf6-4d7d-9244-86a0ab1e50d6/repoPublicKey.txt +enabled = 1 +baseurl = https://s3-REGION.amazonaws.com/repo.REGION.emr.amazonaws.com/apps-repository/emr-6.1.0/72a9ec2e-9bf6-4d7d-9244-86a0ab1e50d6 +priority = 5 +gpgcheck = 0 diff --git a/src/smspark/bootstrapper.py b/src/smspark/bootstrapper.py index ff1ff65..0369662 100644 --- a/src/smspark/bootstrapper.py +++ b/src/smspark/bootstrapper.py @@ -19,12 +19,13 @@ import shutil import socket import subprocess -from typing import Any, Dict, List, Optional, Sequence, Tuple, Union +from typing import Any, Dict, List, Sequence, Tuple, Union import psutil import requests from smspark.config import Configuration from smspark.defaults import default_resource_config +from smspark.errors import AlgorithmError from smspark.waiter import Waiter @@ -36,11 +37,18 @@ class Bootstrapper: HADOOP_CONFIG_PATH = "/opt/hadoop-config/" HADOOP_PATH = "/usr/lib/hadoop" SPARK_PATH = "/usr/lib/spark" + HIVE_PATH = "/usr/lib/hive" PROCESSING_CONF_INPUT_PATH = "/opt/ml/processing/input/conf/configuration.json" PROCESSING_JOB_CONFIG_PATH = "/opt/ml/config/processingjobconfig.json" INSTANCE_TYPE_INFO_PATH = "/opt/aws-config/ec2-instance-type-info.json" EMR_CONFIGURE_APPS_URL = "https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-configure-apps.html" + JAR_DEST = SPARK_PATH + "/jars" + # jets3t-0.9.0.jar is used by hadoop 2.8.5(https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common) + # and 2.10.0(https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common/2.10.0). However, it's not + # needed in 3.2.1 (https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common/3.2.1) + # TODO: use a map with spark version as the key to maintain the optional jars + OPTIONAL_JARS = {"jets3t-0.9.0.jar": HADOOP_PATH + "/lib"} def __init__(self, resource_config: Dict[str, Any] = default_resource_config): logging.basicConfig(level=logging.INFO) @@ -63,28 +71,43 @@ def bootstrap_history_server(self) -> None: def copy_aws_jars(self) -> None: self.logger.info("copying aws jars") - jar_dest = Bootstrapper.SPARK_PATH + "/jars" for f in glob.glob("/usr/share/aws/aws-java-sdk/*.jar"): - shutil.copyfile(f, os.path.join(jar_dest, os.path.basename(f))) - hadoop_aws_jar = "hadoop-aws-2.8.5-amzn-6.jar" - jets3t_jar = "jets3t-0.9.0.jar" - shutil.copyfile( - os.path.join(Bootstrapper.HADOOP_PATH, hadoop_aws_jar), os.path.join(jar_dest, hadoop_aws_jar), - ) - # this jar required for using s3a client + shutil.copyfile(f, os.path.join(self.JAR_DEST, os.path.basename(f))) + hadoop_aws_jar = self._get_hadoop_jar() shutil.copyfile( - os.path.join(Bootstrapper.HADOOP_PATH + "/lib", jets3t_jar), os.path.join(jar_dest, jets3t_jar), + os.path.join(Bootstrapper.HADOOP_PATH, hadoop_aws_jar), os.path.join(self.JAR_DEST, hadoop_aws_jar) ) + + self._copy_optional_jars() # copy hmclient (glue data catalog hive metastore client) jars to classpath: # https://github.com/awslabs/aws-glue-data-catalog-client-for-apache-hive-metastore for f in glob.glob("/usr/share/aws/hmclient/lib/*.jar"): - shutil.copyfile(f, os.path.join(jar_dest, os.path.basename(f))) + shutil.copyfile(f, os.path.join(self.JAR_DEST, os.path.basename(f))) + + # TODO: use glob.glob + def _get_hadoop_jar(self) -> str: + for file_name in os.listdir(Bootstrapper.HADOOP_PATH): + if file_name.startswith("hadoop-aws") and file_name.endswith(".jar"): + self.logger.info(f"Found hadoop jar {file_name}") + return file_name + + raise AlgorithmError("Error finding hadoop jar", caused_by=FileNotFoundError()) + + def _copy_optional_jars(self) -> None: + for jar, jar_path in self.OPTIONAL_JARS.items(): + if os.path.isfile(os.path.join(jar_path, jar)): + self.logger.info(f"Copying optional jar {jar} from {jar_path} to {self.JAR_DEST}") + shutil.copyfile( + os.path.join(jar_path, jar), os.path.join(self.JAR_DEST, jar), + ) + else: + self.logger.info(f"Optional jar {jar} in {jar_path} does not exist") def copy_cluster_config(self) -> None: self.logger.info("copying cluster config") def copy_config(src: str, dst: str) -> None: - self.logger.info("copying {} to {}".format(src, dst)) + self.logger.info(f"copying {src} to {dst}") shutil.copyfile(src, dst) copy_config( @@ -395,7 +418,7 @@ def get_yarn_spark_resource_config( { "spark.driver.memory": f"{driver_mem_mb}m", "spark.driver.memoryOverhead": f"{driver_mem_ovr_mb}m", - "spark.driver.defaultJavaOptions": f"{driver_java_opts}m", + "spark.driver.defaultJavaOptions": f"{driver_java_opts}", "spark.executor.memory": f"{executor_mem_mb}m", "spark.executor.memoryOverhead": f"{executor_mem_ovr_mb}m", "spark.executor.cores": f"{executor_cores}", diff --git a/test/integration/local/test_multinode_container.py b/test/integration/local/test_multinode_container.py index 4c63857..d70143a 100644 --- a/test/integration/local/test_multinode_container.py +++ b/test/integration/local/test_multinode_container.py @@ -75,12 +75,12 @@ def test_pyspark_multinode(input_data: str, output_data: str, verbose_opt: str) def test_scala_spark_multinode(input_data: str, output_data: str, verbose_opt: str) -> None: input = "--input {}".format(input_data) output = "--output {}".format(output_data) - host_jars_dir = "./test/resources/code/scala/hello-scala-spark/lib_managed/jars/org.json4s/json4s-native_2.11" + host_jars_dir = "./test/resources/code/scala/hello-scala-spark/lib_managed/jars/org.json4s/json4s-native_2.12" container_jars_dir = "/opt/ml/processing/input/jars" jars_mount = f"{host_jars_dir}:{container_jars_dir}" jars_arg = f"--jars {container_jars_dir}" class_arg = "--class com.amazonaws.sagemaker.spark.test.HelloScalaSparkApp" - app_jar = "/opt/ml/processing/input/code/scala/hello-scala-spark/target/scala-2.11/hello-scala-spark_2.11-1.0.jar" + app_jar = "/opt/ml/processing/input/code/scala/hello-scala-spark/target/scala-2.12/hello-scala-spark_2.12-1.0.jar" docker_compose_cmd = ( f"JARS_MOUNT={jars_mount} " f"CMD='{jars_arg} {class_arg} {verbose_opt} {app_jar} {input} {output}' " diff --git a/test/integration/sagemaker/test_spark.py b/test/integration/sagemaker/test_spark.py index 5f1c772..be25bd9 100644 --- a/test/integration/sagemaker/test_spark.py +++ b/test/integration/sagemaker/test_spark.py @@ -210,6 +210,69 @@ def test_sagemaker_pyspark_sse_s3(role, image_uri, sagemaker_session, region, sa assert len(output_contents) != 0 +def test_sagemaker_pyspark_sse_kms_s3(role, image_uri, sagemaker_session, region, sagemaker_client, account_id): + spark = PySparkProcessor( + base_job_name="sm-spark-py", + image_uri=image_uri, + role=role, + instance_count=2, + instance_type="ml.c5.xlarge", + max_runtime_in_seconds=1200, + sagemaker_session=sagemaker_session, + ) + + # This test expected AWS managed s3 kms key to be present. The key will be in + # KMS > AWS managed keys > aws/s3 + kms_key_id = None + kms_client = sagemaker_session.boto_session.client("kms", region_name=region) + for alias in kms_client.list_aliases()["Aliases"]: + if "s3" in alias["AliasName"]: + kms_key_id = alias["TargetKeyId"] + + if not kms_key_id: + raise ValueError("AWS managed s3 kms key(alias: aws/s3) does not exist") + + bucket = sagemaker_session.default_bucket() + timestamp = datetime.now().isoformat() + input_data_key = f"spark/input/sales/{timestamp}/data.jsonl" + input_data_uri = f"s3://{bucket}/{input_data_key}" + output_data_uri_prefix = f"spark/output/sales/{timestamp}" + output_data_uri = f"s3://{bucket}/{output_data_uri_prefix}" + s3_client = sagemaker_session.boto_session.client("s3", region_name=region) + with open("test/resources/data/files/data.jsonl") as data: + body = data.read() + s3_client.put_object( + Body=body, Bucket=bucket, Key=input_data_key, ServerSideEncryption="aws:kms", SSEKMSKeyId=kms_key_id + ) + + spark.run( + submit_app="test/resources/code/python/hello_py_spark/hello_py_spark_app.py", + submit_py_files=["test/resources/code/python/hello_py_spark/hello_py_spark_udfs.py"], + arguments=["--input", input_data_uri, "--output", output_data_uri], + configuration={ + "Classification": "core-site", + "Properties": { + "fs.s3a.server-side-encryption-algorithm": "SSE-KMS", + "fs.s3a.server-side-encryption.key": f"arn:aws:kms:{region}:{account_id}:key/{kms_key_id}", + }, + }, + ) + processing_job = spark.latest_job + waiter = sagemaker_client.get_waiter("processing_job_completed_or_stopped") + waiter.wait( + ProcessingJobName=processing_job.job_name, + # poll every 15 seconds. timeout after 15 minutes. + WaiterConfig={"Delay": 15, "MaxAttempts": 60}, + ) + + s3_objects = s3_client.list_objects(Bucket=bucket, Prefix=output_data_uri_prefix)["Contents"] + assert len(s3_objects) != 0 + for s3_object in s3_objects: + object_metadata = s3_client.get_object(Bucket=bucket, Key=s3_object["Key"]) + assert object_metadata["ServerSideEncryption"] == "aws:kms" + assert object_metadata["SSEKMSKeyId"] == f"arn:aws:kms:{region}:{account_id}:key/{kms_key_id}" + + def test_sagemaker_scala_jar_multinode(role, image_uri, configuration, sagemaker_session, sagemaker_client): """Test SparkJarProcessor using Scala application jar with external runtime dependency jars staged by SDK""" spark = SparkJarProcessor( @@ -233,10 +296,10 @@ def test_sagemaker_scala_jar_multinode(role, image_uri, configuration, sagemaker scala_project_dir = "test/resources/code/scala/hello-scala-spark" spark.run( - submit_app="{}/target/scala-2.11/hello-scala-spark_2.11-1.0.jar".format(scala_project_dir), + submit_app="{}/target/scala-2.12/hello-scala-spark_2.12-1.0.jar".format(scala_project_dir), submit_class="com.amazonaws.sagemaker.spark.test.HelloScalaSparkApp", submit_jars=[ - "{}/lib_managed/jars/org.json4s/json4s-native_2.11/json4s-native_2.11-3.6.9.jar".format(scala_project_dir) + "{}/lib_managed/jars/org.json4s/json4s-native_2.12/json4s-native_2.12-3.6.9.jar".format(scala_project_dir) ], arguments=["--input", input_data_uri, "--output", output_data_uri], configuration=configuration, diff --git a/test/resources/code/scala/hello-scala-spark/hello-scala-spark.sbt b/test/resources/code/scala/hello-scala-spark/hello-scala-spark.sbt index 7b44959..1ba7d17 100644 --- a/test/resources/code/scala/hello-scala-spark/hello-scala-spark.sbt +++ b/test/resources/code/scala/hello-scala-spark/hello-scala-spark.sbt @@ -1,9 +1,9 @@ name := "hello-scala-spark" version := "1.0" -scalaVersion := "2.11.12" +scalaVersion := "2.12.12" useCoursier := false retrieveManaged := true -libraryDependencies += "org.apache.spark" %% "spark-sql" % "2.4.4" +libraryDependencies += "org.apache.spark" %% "spark-sql" % "3.0.0" libraryDependencies += "org.json4s" %% "json4s-native" % "3.6.9" mainClass in (Compile, packageBin) := Some("HelloScalaSparkApp") mainClass in (Compile, run) := Some("HelloScalaSparkApp") diff --git a/test/unit/test_bootstrapper.py b/test/unit/test_bootstrapper.py index c20430c..759fd57 100644 --- a/test/unit/test_bootstrapper.py +++ b/test/unit/test_bootstrapper.py @@ -97,9 +97,11 @@ def test_env_classification(default_bootstrapper): assert output == expected +@patch("os.listdir", return_value=["hadoop-aws-2.8.5-amzn-5.jar"]) +@patch("os.path.isfile", return_value=True) @patch("glob.glob", side_effect=[["/aws-sdk.jar"], ["/hmclient/lib/client.jar"]]) @patch("shutil.copyfile", side_effect=None) -def test_copy_aws_jars(patched_copyfile, patched_glob, default_bootstrapper) -> None: +def test_copy_aws_jars(patched_copyfile, patched_glob, patched_isfile, patched_listdir, default_bootstrapper) -> None: default_bootstrapper.copy_aws_jars() expected = [ @@ -422,7 +424,7 @@ def test_get_yarn_spark_resource_config(default_bootstrapper: Bootstrapper) -> N exp_spark_config_props = { "spark.driver.memory": f"{exp_driver_mem_mb}m", "spark.driver.memoryOverhead": f"{exp_driver_mem_ovr_mb}m", - "spark.driver.defaultJavaOptions": f"{exp_driver_java_opts}m", + "spark.driver.defaultJavaOptions": f"{exp_driver_java_opts}", "spark.executor.memory": f"{exp_executor_mem_mb}m", "spark.executor.memoryOverhead": f"{exp_executor_mem_ovr_mb}m", "spark.executor.cores": f"{exp_executor_cores}", From faf06eebf6b855a2aeff73418febf90dd4899bd2 Mon Sep 17 00:00:00 2001 From: guoqiao <64932716+guoqiaoli1992@users.noreply.github.com> Date: Sat, 21 Nov 2020 23:31:52 -0800 Subject: [PATCH 2/3] fix: use al2 base image from ecr (#38) * fix: use al2 from ecr * extend timeout in job --- scripts/build.sh | 4 ++++ spark/processing/3.0/py3/docker/Dockerfile.cpu | 2 +- src/smspark/job.py | 11 +++++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/scripts/build.sh b/scripts/build.sh index a88c3dd..9e915e1 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -22,6 +22,8 @@ source scripts/shared.sh parse_std_args "$@" +aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin 137112412989.dkr.ecr.us-west-2.amazonaws.com + echo "building image ${version} ... " docker build \ -f ${build_context}/docker/Dockerfile.${processor} \ @@ -29,3 +31,5 @@ docker build \ --build-arg REGION=${REGION} \ -t sagemaker-spark:latest \ ${build_context} + +docker logout https://137112412989.dkr.ecr.us-west-2.amazonaws.com diff --git a/spark/processing/3.0/py3/docker/Dockerfile.cpu b/spark/processing/3.0/py3/docker/Dockerfile.cpu index a61b4f4..516a2a3 100644 --- a/spark/processing/3.0/py3/docker/Dockerfile.cpu +++ b/spark/processing/3.0/py3/docker/Dockerfile.cpu @@ -1,4 +1,4 @@ -FROM amazonlinux:2 +FROM 137112412989.dkr.ecr.us-west-2.amazonaws.com/amazonlinux:2 ARG REGION ENV AWS_REGION ${REGION} RUN yum clean all diff --git a/src/smspark/job.py b/src/smspark/job.py index 3096b95..a0aef46 100644 --- a/src/smspark/job.py +++ b/src/smspark/job.py @@ -32,6 +32,9 @@ class ProcessingJobManager(object): """Manages the lifecycle of a Spark job.""" + _bootstrapping_timeout = 600.0 # all hosts should report as ready within this timeout. + _wait_for_primary_timeout = 600.0 # then, all workers ask the primary if it's up within this timeout. + def __init__( self, resource_config: Dict[str, Any] = None, # type: ignore @@ -136,7 +139,11 @@ def all_hosts_have_bootstrapped() -> bool: has_bootstrapped = [message.status == Status.WAITING for message in host_statuses.values()] return all(has_bootstrapped) - self.waiter.wait_for(predicate_fn=all_hosts_have_bootstrapped, timeout=180.0, period=5.0) + self.waiter.wait_for( + predicate_fn=all_hosts_have_bootstrapped, + timeout=ProcessingJobManager._bootstrapping_timeout, + period=5.0, + ) try: subprocess.run(spark_submit_cmd, check=True, shell=True) @@ -172,7 +179,7 @@ def primary_is_down() -> bool: return not primary_is_up() self.logger.info("waiting for the primary to come up") - self.waiter.wait_for(primary_is_up, timeout=60.0, period=1.0) + self.waiter.wait_for(primary_is_up, timeout=ProcessingJobManager._wait_for_primary_timeout, period=1.0) self.logger.info("waiting for the primary to go down") self.waiter.wait_for(primary_is_down, timeout=float("inf"), period=5.0) self.logger.info("primary is down, worker now exiting") From 3d07cc09cf5c5cb18649c32fb6b9c138d4f2acc2 Mon Sep 17 00:00:00 2001 From: guoqiao <64932716+guoqiaoli1992@users.noreply.github.com> Date: Mon, 30 Nov 2020 17:28:29 -0800 Subject: [PATCH 3/3] fix: update partition for us-gov-west-1 (#43) * use different partition for PDT * update partition in one more place --- test/integration/sagemaker/test_spark.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/integration/sagemaker/test_spark.py b/test/integration/sagemaker/test_spark.py index be25bd9..e0d5296 100644 --- a/test/integration/sagemaker/test_spark.py +++ b/test/integration/sagemaker/test_spark.py @@ -232,6 +232,12 @@ def test_sagemaker_pyspark_sse_kms_s3(role, image_uri, sagemaker_session, region if not kms_key_id: raise ValueError("AWS managed s3 kms key(alias: aws/s3) does not exist") + # TODO: PDT is the only case requires different partition at this time, + # in the future we need to change it to fixture + aws_partition = "aws" + if region == "us-gov-west-1": + aws_partition = "aws-us-gov" + bucket = sagemaker_session.default_bucket() timestamp = datetime.now().isoformat() input_data_key = f"spark/input/sales/{timestamp}/data.jsonl" @@ -253,7 +259,7 @@ def test_sagemaker_pyspark_sse_kms_s3(role, image_uri, sagemaker_session, region "Classification": "core-site", "Properties": { "fs.s3a.server-side-encryption-algorithm": "SSE-KMS", - "fs.s3a.server-side-encryption.key": f"arn:aws:kms:{region}:{account_id}:key/{kms_key_id}", + "fs.s3a.server-side-encryption.key": f"arn:{aws_partition}:kms:{region}:{account_id}:key/{kms_key_id}", }, }, ) @@ -270,7 +276,7 @@ def test_sagemaker_pyspark_sse_kms_s3(role, image_uri, sagemaker_session, region for s3_object in s3_objects: object_metadata = s3_client.get_object(Bucket=bucket, Key=s3_object["Key"]) assert object_metadata["ServerSideEncryption"] == "aws:kms" - assert object_metadata["SSEKMSKeyId"] == f"arn:aws:kms:{region}:{account_id}:key/{kms_key_id}" + assert object_metadata["SSEKMSKeyId"] == f"arn:{aws_partition}:kms:{region}:{account_id}:key/{kms_key_id}" def test_sagemaker_scala_jar_multinode(role, image_uri, configuration, sagemaker_session, sagemaker_client):