From 619e44e54a729bdb7df2cad2516ebd8756db9713 Mon Sep 17 00:00:00 2001 From: krfricke Date: Thu, 30 Jul 2020 22:09:03 +0200 Subject: [PATCH] [tune] Added WandbLogger (#9725) Co-authored-by: Richard Liaw Co-authored-by: Kai Fricke --- doc/source/images/wandb_logo.png | Bin 0 -> 16106 bytes doc/source/images/wandb_logo_full.png | Bin 0 -> 66031 bytes doc/source/tune/_tutorials/overview.rst | 6 + doc/source/tune/_tutorials/tune-wandb.rst | 35 ++ doc/source/tune/examples/wandb_example.rst | 7 + python/ray/.anyscale.yaml | 1 + python/ray/session-default.yaml | 77 ++++ python/ray/tune/.anyscale.yaml | 1 + python/ray/tune/BUILD | 17 + python/ray/tune/_test.py | 43 +++ python/ray/tune/_test2.py | 207 +++++++++++ python/ray/tune/_test3.py | 42 +++ python/ray/tune/examples/cluster-p3.yaml | 71 ++++ python/ray/tune/examples/wandb_example.py | 103 ++++++ python/ray/tune/function_runner.py | 10 +- python/ray/tune/integration/wandb.py | 345 ++++++++++++++++++ python/ray/tune/progress_reporter.py | 8 +- python/ray/tune/session-default.yaml | 73 ++++ python/ray/tune/test.th | Bin 0 -> 137 bytes .../ray/tune/tests/test_integration_wandb.py | 298 +++++++++++++++ python/ray/tune/tests/test_serial.py | 28 ++ python/ray/tune/trialv2.py | 1 + python/ray/tune/utils/util.py | 8 +- python/requirements_tune.txt | 1 + 24 files changed, 1376 insertions(+), 6 deletions(-) create mode 100644 doc/source/images/wandb_logo.png create mode 100644 doc/source/images/wandb_logo_full.png create mode 100644 doc/source/tune/_tutorials/tune-wandb.rst create mode 100644 doc/source/tune/examples/wandb_example.rst create mode 100644 python/ray/.anyscale.yaml create mode 100644 python/ray/session-default.yaml create mode 100644 python/ray/tune/.anyscale.yaml create mode 100644 python/ray/tune/_test.py create mode 100644 python/ray/tune/_test2.py create mode 100644 python/ray/tune/_test3.py create mode 100644 python/ray/tune/examples/cluster-p3.yaml create mode 100644 python/ray/tune/examples/wandb_example.py create mode 100644 python/ray/tune/integration/wandb.py create mode 100644 python/ray/tune/session-default.yaml create mode 100644 python/ray/tune/test.th create mode 100644 python/ray/tune/tests/test_integration_wandb.py create mode 100644 python/ray/tune/tests/test_serial.py create mode 100644 python/ray/tune/trialv2.py diff --git a/doc/source/images/wandb_logo.png b/doc/source/images/wandb_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fd26b3f37f6fadd7f8ea2c07736c3bffa699d5c4 GIT binary patch literal 16106 zcma*OWn5IxA2v)0g0v!?5`xsytTanWswkaHN=YoGl!PE9;F5xrgdiYY!YZ;fvNV!P z2rSY_!!zse|J$mpYH?DNpW!NzZRy6c&Cv-__GzLod4Z?`7M(Fq*L?}gYQ&ZF3FiF-?;T0c21c&Gp zB)N(|91ALI{*DdUm3yu8?ldFVVcqA^Y_pLj>1W3k-`WFq@(n5-{33$8c-KFKxqXrEcs|@Fk{ia_#}C|Os!4=0wN@6!f67RvXC6&RDwPO z6IA+vUvKT{JMXkJ#c#_C%~dYXT32B(eS&5@j~n*m1nnC8WQOja9BkvX?g$-!EMZBY z&wkNsLmniyLRvUT-cW-uzAr`unM@Gslmb`3gfXcEUN5IPI$ahb3pP?`=+3 zUw$1U9hXF>-yb3|^-JunnOI_3v=puNB*4dh-2tM zM_!J~{==m{#p}v0G6@j0YpQ?>zrS?;Z^FCdPfL^+PRZ+^=jE{HV8@ke8CYUgF9}4- zCel$BO9I83zw^7+Wr`r^+-ohqw~U%tWU3N66k=ALf2DbR=(3jd;4s}aqd6xQa2$uX z>iSb6{j~FZ3c^3dLv%Ge&LAacM3d;wZB)am+htsThO}{C*pon|3Ea%9mQ4*QE0<+D zVqWwXzc*+`c~Zi4!bg%rh_~I;rTK5m<~LNk&p^BuW|<7Da%EqTiq zIsDtaOB5}^D|Bo{qVNaWMZ9TWwH#m9Eb<_QvQ!)TgZguVgd`On=KB-KQ6ZKAosKTB zxu_{D3&7jZ?^}q6`u&|Wy-_*9Ki>SwDBsIT_emE|VB5{`>;HPCq}b!j^QyqIWI?k2 z&S|^cm)X%aHr>@H5x&>#{&gJh3hCFE<|GibU6$}hrb@*Ff*%%+Q?IDBs4ql&Uc^XI z3mu;ls9EbIBU}n~JWk_Gzdry|6$U#_&bn9BGAKqK}N0D zj0qtYoAe@o9c$6WgUm49eBOlHiT#n}O94Z&3h!@AkV!AQGp-%hWF?9TBBc6}LL8(n z?av+IOPbSk`m!*?-`Ck$q+tRj{l2VU7*g;BDiN%loHdI#qy31hpP~m*zMiUu-xMkc z8Q@|zRulNN)v5<3srO?ilzyDlKoNcq|N63`z%>7EQa*f-)Ah=}1<7yu77#E)JS^kEjk4DMVKgFvZj_iDHtv!`8h5q&Q^0&w$|s6- z%|hVkPZ{9JLqs;cIV7jcXJf*~(I>%i zke=R0Ojw3ITI^}ew9;er^PtSz;X-BRU{MQ=E2_y1CLpv0v{(kVL)!6aP(E^+8FSJcHAxpcc)(8JAH@<*0U)3Glo>0o zq&BMAN>gWuS5h54nt|%6&Cyld0$Tyw_nM%wZRU+JDel>b*;J!XnQg-K1^6^5HsVg& zYmL8+jK(Wa^lkp9p0ts)&K${GO;kq~v~}xx%jUA1Kh(`~Pz(=My=Lm@427R&!wn59 z(l}ZbV;%@;-M~l14sz~NYS%janf;y_qv*%_wXxXdVhkk_^*3@VygO@qD{_U)L(;PL zCh{eX1)1Q@_OslMMqY|2VR#UMe;xMrEz!_k`eXrKEjogs z>Ppz;hvqgVeik~LQf_uDO_Mr!2yykHmFeb8(!EU`*gVNby1`U2^*KafK8fQwG%u6) zR_)RygMTHKrK9StXVCGwU^R?;PO@%>kvd>645BrmH*~(=Fi=%jnfY=~+Kqtl<2w=s zvbai&s7u(^Fm$_aJyWZQbaiG5?!5JxK7A9~ce9+tL5n&yMbI>5L|BM;OL@Pm?gRTs zJ9Rcsa8uIF7ZPTB2MsAG}*-S?&m5P~~9`32nb-o;psqQCn&8PgDl zbC3HO%QC$d!29xyY=#VaQa&X4Vc{=i>2phT3X%FjrYV(S1NJM) zV$567#EQuPi;14qu{?lIVSgUrI(yi8YhRJ<`IU6TM9%w7abe>%>j%p3H>Gb%_OnOy zN{2B@GTGu`?vyMkI6ATpsA*qNg;6yQexjq-rq0=fMwGr==Na1zO(_|B{~UP*HFY3^ zoqS#6TyHAKLUuw5v!Ys}z1>86B#WGh6e~Ez5UppBV{Es5VU>t%czQxAN9&h=2U2h& z0p^Im#gUPfEDakbWZluKb_w`U|0ur(6m#sN=PJCaO*W51M5Y7Q3 zb|3C=$X#unL8RK8kW@7g{S=r`);V7Tr!S-SMAI;VUbr8? z-k+XxN>YWu5gX4-Q6Ni8bnty)K3oEDnm=mx6apa{K8#Jhv}@+045y#6jBA&yi0uAU z4>fRK{qBW(q2*8isPRnT>s+z4lwYa4_2XqPQxsdUWz^~e^uCDRh__nl6ApCTo4LD- zbhYFP1hq|rVk+33{8_grl|9vVQg6i2$kZ=2-IjZJ45~{(Gr+k0suHJ8F)`ik68BNe zn?KF%Rz7OQf_|d1!_qfs<)b#Fs6w@(*?vMJAO-dm-|W(QcPeguEAq<6UPZ^H zN>Zn2LH&HMJ*~pBkfwi}V9EOCZGaxl2JN~jk4Z)y1ENi{`rQ*xDpVQ7nZ}iLq)L}8 zG&Bv0v3OtzaMh!S79^T+^pb;zwwbSR-=8&7zE1C+9zhVn)jOD?dp^`|9HmC6pIGW# z|3qwCE#P*lmzd*}`pzDr+mHKM{bZ`nxs^ACX)SF?;yOmM;ug14-T&XaZqkG%$?X5R zS!*9MK|@<$d)Fybry6vo3s4Q${Jk>ZZ!+LuS*8%ce=IOf;Kwlvfv#FkrYnfYTM}qg*)4rO zf@tI4QkmdhqiBcY>t`bGy)pMDIRCpC+eQhiP>oZ$f~;(zw9#P!%TyF&6)c+K$eFL0 zrqYvhDVpH`#(3A;751nC9*24YQ~U$=!_$NilEJ5Z#JzoGLRNi2Jb5eND{fb5!7Q|1 z2&k-DQL!vT`yS6ku(R>MXr{E)G5005WqNV-UdVJQ>K1q(zj*yP(f_n!!xSk=JtAs1 z{=)J876nSt+;J)*B&n>`q-gf?EjSF^olMcrw;$ZD;Mk=k$??jzC%pU?YblZ)o7w_8 zP2OOOcGd(2K-vqbi1$o3VfS`ZFZ0KMbagvNjv~&UxJ`{P6V%ph@Q&|xP?vjpmO6M)i7P^Wn<_kqZm`56U&TYRvsc}xjXrdj zrUj5Vt$0Ie5K5-(INK-?JM=Pi= z390$;3s7+e`W?jB!Rzn!WsC6b&V}{vMEi>EIy_LWjq4rr$Bg~8YhncP>s@>jf`?(B zX+Y9af%@xu(dHwvExo{L%_z`EE%fUpRb#7;=^=4`Y|&P~bzc&S#agNg{$iOOw(Q%w zDg{p1ly9JTM_%mVZ0be*M1Hl^L3qDAN(%LDJ*^!7G1tlMZR(_kXb5(z))CtObX8uw z3j^Keg;{kCd3tOS^(kKICtUfZ)?Ru@lF=+doqhM2;GfSLc*S@x@r4i!zI6HRoV^x$ z2CW0`4ovWMt&Y~jUVfAodA7xj)gnsFQBrr>g`df{DwO8ONKykg zm(YExpelZ?T6y1yyf%mbROBoRdmEkZirM@MoN8=nfrTSu*h}j3s-xxUl-oC3!a!gk zq+{+)ng5P2OS_<)c7ElX?~u)B3D?m?$rak?>5VE(lRv5b?y+!N` zu1ye#X5$M!p6EFKWg|&Vu?JFq5~joIu~b1~O1-`0UUa(MBl^kK>rQCxDQtAB>iaVP z@8fMx%MO+H=NLuQmYFhZ0`PPQD-7LU_4%SDJgx>5q;sWH-aIXu&h1(MlmS?0%en?C2jhlx(Q@f0svX{tproZaT z1VVm;KCB%K5AT|iHyg-KAQIlB54a}=2WwkZP4$U^MrsQssWiS)q`i)%q)<<1cN}ER zz~cU4u(ke#$%DcdtK>U~^KdA+U0T58$Ni!Ae;B3@FDNd5%~QbWsP3LJkWw9$ z;L>4Zy)K8Wgl`&Up9vAT-r=;(YWf2g>*=rb5$ETPx+=sQoj>}Ivyod0-y zUGd-ep5(ekM}kF*u5Lk$?6wMF=|v)AovJyBC^{~bb?+CH2sILEF->?1rA$Hm?Qqhm zpWJelJ6)3(Z`gBUoutYlVKu_V9qBaGv)>UBaiB4Mg(l#y1mbzh0XUxs)~^gTh_R2R zd`*KGyrRHR$=BSq2h*ocIyWa@uGsk}NxuG8G6}v)O>RA*H7p85=~Z}35YatjkMAKq zwa`WDu>sKG6)~pmKm>v=1XD`JY}$~t*+u{O-%VoWVchDS4hV*nD}QZ7_*3L^J8{>q zLqj1B42B!&X>pIKj2EG5ss?KakU5JB`u1z9SCUMIiIC=(S!jr&$zwsMpMDG~CBqfp zpzXqOLtyd0H|t?Ia=bxS|L+d8H`{g<_6CKrPZNmI(rrG$EootC&rlC0WSPEN$9_&k z+l7^!SMYM~rUw3X;I$1kU%rn!NFAcS=eJO~SOyB~(wDvOEQ+1*`gpFA;PP?TY#+h3 zE@5~leE?P9(a#49?<#8SW>|3;gZh&21iskjJ0FC|A9-&u!&l3U>1wkwM123dm-Vdch z1M|Gj1xuJ?^8ixIsEN{AT_eq>cIT{df-~@{Kur?eDFJ+F{rBzJg}u>hm!D5x9hj1D zYWT3_H{^L`>E?EqTUk@sNo=M*|88a7f%I3a#48Vr(}DFZj8&+nCt)^KNSb;{jQRLD znhEj5Hp&>`)D)q3$ePgNx+Q=NHp6!1r3xV0t*c~QTP{k2h&MWn>vXD@$6rHMe+4zX zZ~voZ9hYJ-wNFC&wF5k6M*(=bxAR5tH1Oxf+kzvw4Bb(&ZGyo8o?q*9dI(-@Z&bq%SqP+=&i`W38MGnk>oQlSrMF#p^G>bQj1FGW(Jl)Bcq8 zt81n}e&>QV%I|55z4x7bdY7Jd&9S2H;?!Ow%F4V;Q?@g9@Y#-)`zYAZb@fq)n{F|^ zeD;HKW^qCvRW7+;?W<5Uu^!)k`yw=AN&=wJh5+f*_z+{sA1wQP$I9cx!ItL`V*Ti& zxcw;mdxPp8V*d9w7qKV}i{FzGBa;0g$ zXmIT1CT>3*Xs8Du6xYnC<68z=BN<&G4`q$CFU%~uM6)nr{?Mv9MBM28#dr5M|FDsh!kjW3oOOD<8BXuS}iT-y)EWe;#S6eMXzyP*5K86}Z zi+T&B6DBMV4d;ZiW7(#M>D>-u%y;^vIe+xOYn>qMx@WY*4$|>>TzF?V=$8$@Av}~k zzYdl+UB~L|M$#c=?mXhAqhXtHun`g9Q_Pb?@G%OOa z?321Pk0-HPyi2yNywk@lU+?h%9a|kj+AR!!4{mHKX(e%58sl@fJ5TShKlxR6D*zLn zR+svj&GP%JS$$Y7O)*!6#y%@;L|P-IZPfBpfUOQ)omcHhQCWMiV4V9Mk@~Il?9JTY zhQ9JESoM=(V=4>hbj9mP4?H)|^}=uP5*Crj2Mt&b2P}vQ+?NFInE%#{p#O97^=2OL zEP!ZIx?7n1ksv}`B^gZcE>##uLw}{Uhp!4QB*`G&UO%TecWaYOBzI}dUg|UBou4z7_;UHvtm9}@Yu+n`X=&nNdW)pqZw1meS+c?k@iOpLjL0j25|y` zXDtb@a|QK@oS?K3W!oK#q2ghZ6?<{7xdN{=_4@4e9ioL&yT@~eo1ESg{k*%AKS7|K zTw+2Zjen|1Z7Day@51}22&Cu?BGtsn5UvuUpKAUXLs;e0Y{&G}yj}I3zGBx#Avczo zO$wpooV#IqFhx?iY`Z^12f4e%q+f4wUuE}WMaTWnHYd^4S^BdIeZk1ub126de0&2& zMR>Dmu!ixHV37E4k_tZt(e|X?tMostZ_EDn(O?~u<0Nbge~{0rt!GfCT0d5q27^K* zMEGb^4+7Kjt+?}RUapn0PZ3rS^?wmVbqu*H+E& zwuaY-iHek+dfM41yFgcR9LkNASZZEbQ*wtzwc>m)a?!j5RSgttwB=yoSo`bGY!z!4 zyolMm%6-`_20fpz``^jWR;nQGD7RUqXw6Lhg4H+ya56E)-fYPYu)o~^4?PFRyk~@m z^?E??3?!QE`w+=$j3h(R3nxY-Hmqztzh2^x@r2wC{pUN->+E>7OIxA6hOJJH*35=KXtJYwWne}H)x=22 zXoXmM`Frz|_5}ff!x9l_6HCQirr*yXSv<-zcrzB~grRfd-YoFbBImZ@l zimoVQ+X@Ur8hVAO+L!jP>J9LQ3^H{WX;Lfb20D6V_&67SjAZv>MK#_($9rydpuaM@ z7gZ0@3LHX&65-$rJAi9EDsbI>Uo!LgKt!B3s5~rPCz<1UGWuw#?OVOJl*xaflLtVJ zP`V)C5H|(zh;QjxKO$n%u)6VTDI^rYMmEEb%wOE=K_oCVzc&a(;h>s6fDTDvu9<5v zNMiPYyX2*G)X!&5A$laIa)}R|dQIHW`O!}wv&j*=41jg%T<26-CI@y%XJ5yKjTZ~% z(OH#u`%4sQTLET8`Sj9ml{O z2o-ezpW(!Fao#=9M-({O!mxRY($*8uPC!c+ z72yUc;t8QLtWcLI*&CWQ^y2FVkPB9>0)kjP*`G_#1j`vAdEJq~k)RAKx+Jim6F5y( z1wfx{@YqFpvyo{()f@@8NO-x;gDIYpek7cKvljdAUSmC^6zq-!D}Xrgwg~iyi@F3G zt~L@h&g5)~4xqt=o|uvs{Ze~L;BAmWxk0=nwYMCOf|Ej&2R74x*^) zxM{g7S4G}Pk7+8I$%#65d@BhMmzKyFm$_G~EukHusSL|GrJK)!N{MV_^P#9$i4 z)jECIB-aCen+XIw!NX&7!D#{TDl3(bHK+lvoTf!hIhuRnFjg04IR9;n@O0FsF)O;2 z3i#Yg1VbMKTnezD#p_?rm!RYgK>( z?#DsmwX`q%VU?=Dd76as?c{gx%zImXBrY)_1PF-_mxpkb+eb7*Bv?!CtMB{(vM~a1 z@I;b}t1Ju9KH?lzpZcTa&br&1WBDZ@>%JZ!ol>!a|CIV^{9rb#V{a7%eMXOCW9o36 zwrgzfJ3wT7esW)Khd5sw%FBh>3e8Tg$bKy??l~ZhOR*)WF&=~-UaW9&OBEi!paXLL z`#tZ%%9&4tthc^ldA0V*DfTp45&%D^AVln;x_qm`+f|K3U|ze~DgO|t0$N!yE-B?E zs^1NIbdij+-$7gu*TehS>IcKP?H{#Q(XQC80CHn(?r1Ms^lhGM&Fp>4#wt0STRF zBsO#sQ}~K!7ze1Ngw|RS&fG}sJ}t-P@8l1TS@=yb&m#th07Srd|8|D>>(nnJ-Q&(P zQFE4M{O*A6FTB=Lgs0Io2)Q>6sox6Hx^ZuvcD#l5!{Q4K9;3v_L9jtwM#mwetFp~d zS;-i#PxE*}e#E1Wh*?vpiDXJ~J~|!xu#<3Z4RD>TKP)Ci+>?%2Ym=X8Z!m5CxyQc> zoG@UPJHJXSAS>YCR@6Qv&T74VF`$(j^(_Bo<39>kFqi=wz7|A{k$^C(o#7|hvY{kMC(~Q(Kq#siI`j|SGO117J;RGvOVnD zaD0DE9%Q>U+kr^ZB+MT~d zKd5JSuSuUB&Km8z^jIaVo|nuh^Z2#&uUbAHSwMgOlQWwTHZFx@t9PqqL=q>I40+Lk zK|g8_8sHR#KTCeyC!<8i>Zvb_4y6&g*4en4Fst5q>x;+tx-6IHatk%!kD>GVJHw+= zj;}*@keCq-t9Z!W6>BS>#MTfN7kjLoE=>3_Mo7$I2C#3!u(@ZXvWysu^`}h;$A!p%ND{V4X!m?p?x zvVHogUDcNDtYcEo-nsOIAhAlkQD0Kg&=uu!xMeHxFE#znz@TRGq3O0^#8q)G5S`R5 zy#tG-nBSE$DEUfm0k)tlL~J#@{H!RiDI@6#^=jRCJ^#C2_Feu_1681?|OFIZMK+zfCWl z-@3D4<0TH>zjEqP1gtlS4kFv}hGl%Og(-QPp~c(pc9FfRfFH68^$fc6>qhiJ_#FT# zvt3;r)uh0*fDoah;95&P-+oBnkcmd>@1+Kg6SaF^C<4Ft|1p0F>ELcgAtGSWH{Zjx zLf+D9J>Sj9Qi_*ooJHW(AONi$X};{fFi=s$t-gQt$H`Vb^F{`Sp^FST{VO$kUI`Y$ z-V*gpf47@07G(csMazQ|fY$C=;VJt##oTDrmpx)Z0x`o(vk^?aQ(97!?h>ZC3u}{O zk4CTxT?ux_GQ9_o8ROW>4%vjs6DK|{kBXb&!V}8}K zWp$wl3rTSrFugB3n>wY(N3k~&>d`Wo#5F%=Ta}2Vc7W4HBo^SZ(5N*(APCd0w$7)3 z5!s)o`w^}wh{jq9MGor&5fn3EtapX+(anR&T#MA7VaTW{#pWc#_3n30We8#HKD?V_ zyT$x0u(9^0$Pb~?fHU3z9H{#}@l-ZnLniJMfqxm%&+|H#!j)5M&9>hP#AZc=6QhCU zx(u-8zN4Rzak&SBu?h*2i9geomV!nByb&<`$&NyK?e>BW$GF#8fe)eEx$?&>lF^^! zcB)J(iAu|-s5>r-=`*CyjRA=CL?JasZ7UlRllrAAtOo#(-|n5D+wM`ewoD+XYwZ68 zq4TQCLs0TVZh?2HDy8l&r(*~{iJiW7`zm{O!!~j`qXL_#znzr#YCZ5FY<@Z3j0aI(lSEqw)6nbUdxOdPf>(b8)lD5JBL^eSQJ|7>y z<_X%b_i%AsL?fFAzlyE2>sTW2#^ru7^nH^&1u0Wznk4n29*?7VDSB z>P9RYc3RX9qzfZ&!F7OdcC*lMVmNo32C+qE3hu(Cx|W0?QBz{RrvJ4>(VGxzy^@44 zM*sU+_bF?hO$kU!^uVWryqR)Hi5M^l13gU9o*l8S(w_^Xe>`)3TSaU;6Wt2L1QO(! zv5H&iDEzq2#TMns)yB{REjS>@x(0&)j)>+&txw7Lg zzDYf6$G;2g)cue&j^~kB>Y{)t#%NEs94sEAZy6S^s0nO_d(6c+BBK96QxCXp2CPa0 z;FM{M*P!aeA8m6F{J>7H08U+o$9v@ff1+tHNjBdIhfe`>HZTAlS|8^o#Q1hAa9X{r zNc#uM|3g*cZZEzJySfgIc6JAVF5 zj8Q~4f*QU-{;lxE)mOa%72bBJwDN%TTAad!X{_e;bf|1aO=0pX>r$Wh#x7Fp8jt{# za=!Sb#*R;H?x>X)ybun0&IBt~iUXSqc3CH6AFP`FN@Mq7{hCfu*GO5#`6wc1(1<3W z66Ql7kYbP#LBAZ`s$ebDE#MV?XN|c<c zd;3zsDrKd*a;W_B#Ym6@qVG=}XRh|j4Gcv=sW_qRn5N{dV{M_`%#&f!c0K`4(H_Lj zuJK%2%{)71jf~M!mw(ioxclsMXu*A1hRGu04!IY-Zqb(U>~l8akE^vi&UQ-7|3F_p z%-_1v{u~KZov!x=n4^KHSmuVz6%0Idr;g2=FtNakM<_fEVEW4lmI-cH$C7D~G)4Wi{Fc|F1kKS1QV z+a44?1QLx}?R|0>y{~Sb{q|`IOQzi{69Z8&%DV5j{bgjC#Y>1;F(CgsKJ*w7c!1l` z?uG4tU}hg??UcD3Ihvn;hnSKJa~K715DpMV6^Xpl>stsnC7)Y?#!dYbd6iF|6X8e* zZ3+u@kJD)|UU_s7asKikKmx1?#A&-1qgLrko-4iB4w$&#pZX~6?y=ZY%J`ZaiCic4 zJAGEZtl!d;q7SZ>*jzXn3cr^c@x*?7X};{A^$|(Iv@-vq*BNp%AE@ptX=H;rq-Z+g&WoRy4A_Ir*aeQOgDj=JH z>@XBPgXhF-cv0Ayi}4thB3cu;K;|KzeJ385Nf4*~jN)5$Q&e*BQCx?{K?)@Eak^)- zu(D9N%XL0Uf#^2mr_{!v{RW=sQ#vL5+3E<32 zK0tBI=o)Z$+dbFv4z*lFuZXg6nu@1dHd>-+vmOXOW&%W${FwQt?-mZSIaGBv6pz>F z_GRNI`N^nC&p8snuM5nQ%!K#;@y7`LO6!PN&m{h*D^)IOC7xvVXB?u`ad5KjHjOF@ z6jAeG6u8DPF(JgNeUcqHhq{Ra0{pml14Fa1Z}NXM(I04w3$t*_`;ZN2RH+ay34Ep~5p&O746mtI;P2-U2@0^8&Q zx+nZ4s__hR`Dvak+b+&AE0tp z25#x+x8ooXXNbSzw(fA9{s6NRwq5=AV1<_IGfYNQ@GA+QLD$v7w;|yGU0^tr=w}aG zJp?3LT*J`Z>8$CE3qL{(s8~?iXRTu>v{9RL#>t##hJpU({xSb+?`->P3MHK!JID{0 z-Z;U#peSSwpDAe;0{Q*XnL4PaHVH zLVA993p#>`vw6}L8~oZ%nd-xx)UO-d*Q6;hDdrlY=+Sq=8$Z~NG66!%6__4%{4Gb9 z5lUFPWSOWt%gTym#6jwSvqO)X0ByzV&+|dZN^Z&U(XyLwQ(&KXk?`~MaNK&0CW0W3 z^(Y$4Qc%nOo+D{Y!cqpCo((Q>pqNZ|5Obd;wcFWPykfZ@2J1CsXM6sLwlgzz6|hxt z5X74UEe(N}e30-_!dbo;Io-{%z=2QBIfU+UTG6Rz*`b6MzoSd({9R_a*eoR^!$b$% z$y?;>V{w2@bGmAC>K(?9s=sb-8+;;8zFK#PNna72MubLuYxBeARU6T2W#+8j_%{w@ z05rD(Oz^VIr~QZtOR#$ zKgQcraP#N?RbU!f6=>1|>2GhWEo+0E>y^1eiE*k_hy&bm)VNCeUXY4fR# z-GxKzlQ=b;DF=wk2<4Wc8n%XP342~tm=Ip%`_d^eJ8y%4ZbZblT5{o7(2g{|qJvz0 z@b4M#`?7Ly-YN9P^vYb3Ls350>VAalUAEsFv^R7LEllzKhLzi&1nK{$2ul5OHxz#Y zVtZXTH>^ZZk4yXELleOlfESD_l?P-iEz->VceJw*eBx5rokst~wpOmEJ6Y3$S_6PJ z;!1B}?wD&tuNjRo+J9=T;yamXjLgg|`5c0lNMjCdZ={4gNjm(@-D^x3;~^ge-|_^| z)uwEFff$>DcBxaRqfo9Z9-F;i3=kTo0*?MtK39Ijfy_BJL(b|W`8XT)U=)Cw` zSs8VVH3%?!5SbO#d$-WwBofQuuvFV3dQAihB=|#Lwhx;gFW?)x1D%xmuKcwpH-`%W zS>0jKG=)f@X@tGl4%ebP)hFa9*5C|O1-*QGV$DbT_4hv?1%O7SkZI@WU~0`LcYiNO zU*I9xr@pJ~0SBVJAOlk1pC0=Sx}ek|NB6@*2$N=y$CV4pZ$EA|ltx<$1Ru+Thx<5^ zJpXDn1E~Eb^VB=euF0@B8ov-d{Z5*0gOewT*H^-t8=6@`sB`ljVy}GC!UynjrVDM=% zWnrEbHHH>(>^b+#kYZ97KHPzv)*}xZV9Qc2Y-{fi`)2^$XZCPN4sY&7@5g`W5DBCX zR9|_7O5=T(O~^+%@?6!gTo%)2mZS#UCjMKaGJIwfM^i5!FgIC%tC53`U*4RYN2G>a z&+bV-Xjn~7h^Z(@|4|m)I73BeG}7`8KjAgUS>Q*KANvPWo~hXX7A z39bnRYJf~P`d-*?potl9VghP}#W*WzW|_3WIr{s@Lg7NETGVLE+nzE~Q%Z$!EyeYq zS~h&K6)j@3HXOGfr&ZQY+zN840a|(7-%G%GQUR%_i1JZ@K$A21?6tDxSF==kUT<#P z)9AgpcKNu&^P=zOyVfz$5K@7yN~82QvD85+N6Vmo#a8NZ5ZBQm`K7a$5!dLmf*Y?I zfZBK+_*}Ll3DugqAGG`erbKOOZO=bmz0W17A$FE}asT%qoA$FUc_qi}-NLCR+6Vym ze{}`A68{DK>Ka~*Rp`WIfB6bPtm}*Iw}}iBh$xBBi3`g#L*c#b-H$4gpMQVnlH%oG zf8wm$ah6jOfhvS9Y4IWNnuzZp#2n{xv9}%Irn}z>6&8UYB1rAUb}Voz4gabYr=N3G z5+eDd+X#>`{umi94{D61VUCKAY(CYeU>!)$d(B@=<7>f)>~bsoCZ^w71zn*gWAP0- zy{kbZmS2^y1#ue>&6&zOV|{BL1?i?zVEIk>-?+n;4WB%_emgz~AiXYZ{8B?Fkm?CJ zJYMPm5Pkz$_pRoiB|Mibxh4*r@`?1UPqY!mX~Of~e!Z>JmC^ENFL6@%@sh`6NFY)$)FV2f9bEjOx+Ea}zN`^sY&G`4UCF|J zHx4F+PE5V}%v+{@9)2fs7~!8pC8#Bus6CNV*8gTuEfEBuQ8wRS-tGCjTt@Gz!4K+d zKtTWvHYE{6Q9ARJ8L02qdl$b3F6fYcg##0QZ^EWu^pRH^(AU$r;RN-_$>KrWt4$lh z$9bZ+z)ouSq4>Cjh&F;A7u$Bn7BvH1!at?H*2a&rqvf*D&tY2x$insU?iy6a(NC9u zye@+{SyDqWDB}9jhrolY-@;`10T~0RkSLjBZem++Z=85suD&ey*)*U(YZc{Hzpb4DpZ|nGQI^+x}%RMNPUgV$PY0!%7 z!qr=>!hwqyXDV&|YZ{dMoo!v*ta=m?`Yl@OUm3>7FYxO1V4!ZfzPw?=ouUQEYN{YvF)E>*|1%+k^xyxFZk1ym(K!t9lWs6y++*17ja^;++|O0phAIx6_&(=7I0) z%d*T~(yB%Fo~=tZPU|gT*P{&OuV+NQ>O;D3@$NPQJVYG;JraU|DWwS#da7_>A?|{Q z+*@^fCuGstKJIA_U+)#R$tcR@39&M4d%5~WlI;mw(jm>7fN<%K3|ht!+)O7#T-gf)!BsraTR*e5J+rGW-piUi6OHB_} Jp)HXci(1Vgnw=_t%fW(lJ(jX}yB@P{fASpGZq)0i4(uhh7(jX;B$AFYW zgM{RFJ0*k>s|d~IVinF0x#ZrregBUz{ByG@1v&e9>c8ohNMA6p3jIsRz$&K+jfq-PJKRFc$jDIF+Gt25t! z8Q4yoPA0#(w)Gh{*kn|(`cp44qIkgHmkar9K45;3F!g@pNp$Xnmsi8=`MYEgUHNF>hyi3UHsI9BOnNtzB z6cSvIUU=8OGc4q>GeAF{c5N{IDBfCG+6=~eoc<}b=mqV2x^y2uXV4Cm$p1bdk9M0; zL)7!Ax`>|469iFCRK=UW=i=1~ZC|XtHUy%xuyW6azPu%xyalwOKbjYDY@iebTQfh& z{(6|o&P+B2DH=PI7AB!=*z`b(S<{@PUlgI_IQ~C3Aq*AW1R`3e>vz54U zrn}(>1})mrT=m}hyZ5mM$PPM7Z>}bdE-XH{`~pub9l#38I54ojkO2)@w^IdfL+@OUo99 zTyIg0R$FcI(HV$WJ})gIV?lbix1P*nZ$oqKc=HCo8C|AJM_hM%FUOHx9AND&l+&J- zgwwm|0!IAE^kOnrjfvDmoe zt=FrND9Ei8=2sT!yy8*1;8Mizm|QTc)i3` zI-@tbLxvq;;5{?EkOvb5m!QVQE23GG3&6N003 zN2P$xW{cn34sB~!Gf=gorkrbd1#`zC81ic-Lp69adct?V8UzsR2K$#ARc?lf=tm_P zDi+FZzX|A2ghRSAx04U9f1@nVf{49p*q2AxBB{%aP?qEykPB_daP_iC$GPr4s` z5bPXsOuQyA&^6cUOMlX0EHVvL7FaK;l4Hrsl8z|S@E7~RPlkP_jR9SsLgU@?o=xM{ z&@7>sBAMy;n1$b?H`tAOc@4mihlzhK`pcA(v0RV#GqZeI$?vB(UzZ--fy#*p+?wD3 zQe>HiE?5^QFIieGu0-EeUJ!Wc+9bs(T~!@}1CQcIu~`JP7?kumo_%#?gje`q`wY~w zym-&%B`$g`r@E+~83uwUx(q|b%UL2jTy_uWY)X;#6}#86PS^s((7V^ z_}2T^4ft#b7~trpsw36Q5;?$w5AA&zoqLcq?(Cp`-9z!_z{kkdynFR-K2pZuA~ed! zHY&$ugZfjh4TjNwpALSwl3GX~penuoGdZ;)d2S#eeyw)g^S@&$`rXN(TpqF?>!kvg z$W!I)D)UPWV!KwJsCIn`KgZ1cg(8=wmhXhiv|aR#7XzEN@oRy^4EUK&k6-QT7?2Cs zhW_i#ykUo{&ztLmlMJy~e0taC@B>smbQzSc5A0lP$VlQ{5lFE1@>jU9=#}TOgh`4M zlHzDTKn^-1M}_}0ZVFf=W3^H>K&~+3%9YqkjjXT;i-_Zt|5%{IxZJAgZNgUfqdeCK z2J9~$=Ey#H@~E&GwX1>-gW`1~e$`qrS|}U@qT1f8K`~zzx?RVvQ0Yvm$xDI7xJfiv z@SUSY^1m_ZyYnYmS^!u{SuS)LA0Y^L*O6@MWl4^ku1L1pH9DZ+{m9R|bIm73(7vA& zwgkJf%q!K#yCKkd&mX1HP#%5b%nLURZHlNy^kv?Ersyq}r9DZvf}AlY))CPxb}7An z1%M8Q;Gh#9J%aSs`u?XN)X)Dv*zr!{V_H_-k)CTF!SAJd$5YF}ilcLClG1^K_C-XP zRD_?9CwZkH%K&q`DM-|n_pR7NinjL=4KE!xPlSYO`J8QvKZam3GCyCC*ldsLaoX@# z9WMN5uUK_^S4oaunxfzPf0$HFHuF|oGY@dUF!NY7Iu96*X$3N}K?jo|6`%AJ$I$nPRFTQG@;4Cub^_JR;c~={{r8m<2Iv z@O7e4i=5$txKl=*3}VU8d^q58=3o&ZId6*}RU8f39XqdISlCn`Y!vG_M6JSoBLeJ{ zpPw65HrS(SUHX}@NA#U0zfCJO?N0tXa)ttKyb$d6f(r=Nw#Ot7rcYmAG>2gUjr& zLF}f@xpl7@XwBgaK)^~OZW$&u*xNu{`ZeL<`cAiMQn~4LNTTLEtuf3lEs~=OOvei7 z9zwfvw)PgX^R{{Q%ztf5jF4&%lLLWvB-b970%I3_M6B95zv6oTBIH(mFw;f*?0ae(mH z+JKI4PwHi<_(hY?E6wkC^O5;P0riq!nD0#xP-nw$ZTn_`EYPzk z%=3CT?8^c5ZI*w>?Du`5SF#OAN!g>vS#`SD`_r`@_-NOcznjOu+xW(WA5@Spx*Dwd zSqSIQ;YSYWFerD#D4z#nGl6vqEnjSshiXJ`69j8UO;tBvOIw7r&cbqQ{G!ddpOP%G zB-vWeqn@bf1-jn}K|J4@wi4*a3|;^ceF&OY2XdcPSXP0AgdbpR9kFq%4EF}tCQ(RK zIeq%fz38~;g_)~pw*_3=aZn6vPj5I~$HJ#C!{tpRk~buYCVa4UTjFR1a4s^8L|SO5 zt*YwzS&UqEBg1{|&qvMSE$X=rIU#+iq`Hu(xd)?O^*qUb=PV+$zk-GPr=SrHNipZm zmUtFWlDM^7P0YXf&y}-nuWxc>8;2moo#*PXQJw=65~f!t0fTfoBTXY3NLfCxM;f*7 z1lruHThacDZr?P3)V!40=^T~fWN%M=aj|ezoKR3}%RaQGkVKv@51dlyguk)3zlvx` zr!~I07%b}A!rJZtjI3iC%d5?_IQLO8+XNNKMg_jlijpQIv zA_l9EO{2Ry*7B9F8w>5It}SvglQ{4!3%1`?l@Xaa`hV=D0R}0dJ<(U|)+X^bP`l0m z5u=uTkg_aWNg-GSJ?wkeb;b^yGl2KX4z(GW)lQ4an6FQZ{9?jrXyleJeJ?@!m)&)o zMHphGgN=+t48Xu_H9$qbzM#Lel}WE2xkt+Z1@pQeFao&-D1G0UM@ATePlR477=r8X zB0k40D;gPaQEg?_4g4{=J|?@-^Q*U_eTx#3vdIC4@h=q7_XXVR;xd*Fkcw<_nbrT; zN(k7hUAICC5J=9*|KEW}YGDtt==8g~TQzJX|IJsSvmJY{dAP5TypkQ3Kz}AWN|*cK zdFplR3|W8~ELlQiU=isZo{#>!szku_huhtu2AhR(4{J8FE;Ig30*bcm{2!!Iku@#I z+aBv+=u@Z{|JZd2UOx>&^$UP7O$Df45=JBKw7%l21}OZ8trTt91EWv_WI~t3NcQBY zN*h@k`xY>TwzgaI7V+Ab5A1DF*JFTNqo&1r$kI}-)1EqgNgv3$zk50ZFf58OeDjL|s6(Bk2bpG#H?!dlKBM9THTlt0Z(0B5um#FcX zS_h7x*PX`>Y_!65)%lQiPpX~NQB(2#K$rePrTOo7Gw7}(Iy;Q;>XSfSULMU71a0Z& zI#BrPxypAfzP>(OQFW#4+XRY}bTX_coEDwj7kq8Q1s=cp|FdVmtEPj0Bm?Es)uo>; zdLinUQhS`K0?g{IMDGLJe+9Qlff!iqCMBEL?QBfz3iaH7B}^`tv%iokc3Uubr2Gd`48k6X0}qGKhMAiCn^7FIvCJvf)RQ#e znW&OHeBmE{L`i8$MFY?;!vHla!W<&{&i1&7LKHKL6)KW@o+L`JS>h-OqrN-!N7tb@ zvM_q#UiJ7F9QY(^;K{j|Kb|L-FdtjH^^L{5N)|Uy(&sE5Jh{G7SmxKh@ZHbKzD`9) zFHlN0SUXGf87;491xm-x2zl$xDS`SU-c|Rb0C1elh|E(_VR}^(@Xf_J6d6j38T%a^ z95x+h9=#x~bmsh4f>DRooa#n}f;~Q4(_pEoOgcFY_UUTFnH*{v<)Ag`=_UC)^7N^N z`SrjslqUfv+n`oa$M>>?epH^UL*}S@2*qNDXkfEy@D3LXIq`%6;%`E;#VkBi3X(F< zFUl&_Q)rB)K*LbAJ{!W&7LG{o z$%TSTf>S~34C$9e1LRkWr7LM9^pmM{#mHux;047vZeH=PywOM32Qy>@_%EQMP@?-$ zYGJSZKjhX}Q?$)Tg%zk=2?g~NYcesk12~5oxyxSVoRPX!n^~bG-tK1k(l4(M^S$rA zEN#>(qh`&a2P(|}y(v6yjX_F;-sgN9;M<1$UY<5gO8efZeSN1_@O9_0Irw+xpf;iKq19u5;9J7Di z+@JHrs1zdGu9pn%`1IazueYJPN@twH^sgSizR$WNRZ;$j`GVBn>43ck9hWq*fUViSm zLyhSJd}P?)nA^93Y5M*-){G)1QQ2@0>@LoM|E<|&a6;{?#f%W{zM}}XQkm5y86?3` z?$ZY+r>fGID}<9*6s06M&41=(X_oyBvCjM(l_IZSSY$Xl*jAw5yKC!W3ityccLDjV z|A`lpsM8YrWqfl=^n%Pi54!!-{!Nq~05w-(>WJ0EKYTrQCRM41;lne7!JL9N7ZUg@d*=4W2(!h3w+otV1Y|=+>7rVR}*qXW`u7C<|ArlP=n2t%`xFjJJ44MQci$t%(1(aFPwc6RKqME=pd$h=B@jSm^+<gbn8j=i%P zqUb}N#Evh@wL1Zt`?e1CF>D{+dS(k{a21~vZtue4LTDd->oY}_dRypTCDrd}Zd4y! z0w+rMY5aQFAdWTLmlD7z!^|#0i1>-T2FW%*k5-Qxb9wM_Jk)h$ z>T1%_ekn%~71EuY{rbBuDpE?~J)o}Y zH~-I}S}?4%WI&&Fop85uNa3t+RFNI3&qAFvyU?sgH)e&`$Z(t}Ue0gSp@XWjAHlQk zPEQ1RM73aD)VyxG%1|WTH|%DD#zyV+OZbLpv_h?OFc58pX0FkjIm1la~xz7Ggpo+Y}!Npb?h&t1Fq;;}$OjQ#B?uCBpU1 z`e`7r_4jT7d$?fCf=CORTbyd^r$S46;)tOIrgWPF1%(|ly!!m90+B|Qjkn1~2ZoPyYs6i-wPpt*Oz5h4+)&{@1fr#T|%11o5 z(sG6QSNdauOaFeQWCM)l!kZp5FAgICqC(w;(8l3=`hDkJ7=*GRf7HFEN27rT`zTra zRuJzpAZ3?p|07jOqr_3Z+#e(fb|`Ib>?~m)0UOD3rK;=Z(aSunjR!%g8val~Yxcf^JB@*ofY$ z-kq?$a-hpB7d#C2bO65@VPg&0$H8dC`0^ox7_pz`8$P3(a9@Cua)SYxTbH4Lb&g8V zT8RIOaHqcW4&&mOl1WN?U&1wIo%PZHe#x0FAbDS0u_oV^%U7rKo#~r!D%73Io3yu1cRy5h zofGb9Ain>>_4L7z+l8o0#R9t>87_Nws$onvcMbG`FyJmr9>E!)(I7Y%KlLl%c zVh?ESJNB#B@bU=x{$5zY5^jS+vo4<4s7~EUgW3MoaUzO#x89Ll#>nEU)6p1*q8ZwI%>F6S_DaY9wkY-ED(GzspD2w!rVXp*L z*|!j!nW>UGU!1(Tk~$eZ9KT9!SKs}L4jhyxh8%O}w1}+o)os&~Y@mutr!Ff(^|{Kh z2Dx|%HuWDdk{IViaI;h|EhZ4FCsr;;g0j_Bf^)^WtYx~K_6c@`>grr%?tOJt{p%m;9Vl0v4%4Gp^)`-x4ignCIjfEsz2$;>2lu`+&%PNm zL2*ptN$n{gFUe?vKkM7 zY!3@(0CM6(fb$rc7Q-8y4a%sy<^fOsw-?|fAK*!+0E_#L>*M#>Lp89ZBtg$#m_8ba zH=+#l2;GBJezH<9idE!wJmh%UNLnc~`MYoyZrK&myj#UJWNwe*zhh@=f+*U}!a8Hy z@x7cyoMjt|yJSlo1K zYfm_$h$nz*Qn+JNU~*nl{J`VZWIrDS6v}#f>djfnFv-4f4p)Xt-Fs}8H_|ib0K^R=y0&gb&N<+f%iJ`F7 z<MT+k4H9+`yhRM5TCJ9T``%MOqV|$*#BZdWiP7v>FrxZ1dacaP!NbYNXRZ@Q;9HowPz_93>@N@EFo zyc&}y$E4o20fiHHxVL&J#mea}zA(T@@S9LPHc zJ}fN?I2xt+jZ}Wrp3<|y@t9RqM%Xyl)a6t3yO_gtb|?dIk^$NJSHgIKJ6TaOKx+@^ zX9YK})Q#WqoSm@KEWy^r^TF}KJDl980{_S+`@`%hF^P9LE$;v1%=cr;%D_OHeHWSA z=32oo*~4+05$lY}d%CA~D~>px3W*o6iQnVP#^2BXSYw?%+o<});J>u50QNhX7=(a& z{VdLB?P<(CrZ>DqO!bU;dMyDo-_}3Nmd?i#5b)DQ(p{C^v>(m(UA8L+u*dAY4QH-8 z>vfA3)!P-99Hj>($-y7;v`>%^zpH9j#~hA(EVpf+M7=n3%-V?n#wSE$Wm+)y{{$C=`u37YzkwBP^`ZT=)j_N{RT zno*e1K5&G%wpZmFxZPGPa|2`FTc%>c87u1Z-iGs`q< z`*~^hr18P$U#p=b`P+(BG+?HcqeybCa-p9hrQZ%P%TQX1)O7v4$+sWk-Y=uDP*kv1r_nO*r)H+ zqn}w5u^+b|=yqe<85&an?ikLVJfwnXq^DJM$hT4cH4J-3jLm{Eu6zZwORdE1YJhqDH@G*b&n%<)*8w~hU*qVjY&@B5}VS$;7rziwS!Z@rGM#4`GZYcca02>B6p1WzzH}Z z_kE-v$jE^j2~57P1Ou_oSm!upTb5>`i>>vqG46u9?l@=K5?teSrfh>tQ1~|D!R>{rm?u}L}-%oM<~}kp%9%|Pl89F z;H>$b4E-x|B;ES~^_x0xp8X3_Mc;Qiz8p6rBfC3+SD;W2orWT{?@Fn6*{(Xc;Ek#) z;xSS5v2fK>y)$1`2}z!%4Z1byx~hZPwXeV%|g#_1#b&x5f*4PJG>M+)1mp)s$xXW z2{HKPMqD_CWHm)w^%LJJ%mXU{Z!W$=5&0O9;PXQ8&py9UD?IK-Xsm_T8sDEA4X8$r zIphT$$e@*_J{&PtAHj;;^_GhIDGk6}7%ZT|+dVmAmg!g;kEy0t^cmT!k2%!c6sTVR zba~Gw$IEIAiwTThDG8`9MEW%#MBG7n&OtVCgBIVGUvH`UXN$3%^>XWNJ>cH&RIPjH z&7&AomK4OOw)00>XFPb4ZyY^o1RWnrT z#<`wxCo&^=HbLk*z(mtBuc_A6o(j-Y(By8h{>MAjLl5iNTDMuUcS1p)Jd;Og6&a#q z0U@#0IR_zZpiEb^uELG@%j>8CCjHA^#j7O)XrFn3ywL`npPs=TfZhS41~w=rVBwX5 zHPol8!fd$w{4NZ)&R%XO<@upB`NX@|O=acyXAaq8?&F_x9mOZN$9oz?2buMuK@b3a z@9YTMWbSH{T95^WmBanWcR0#~n-npI%?m}1zp)BB#Cm0KY3U2gtB0sPnIjHjFRhz$ z!};TQ{*Hbe(L2x+u3JuMao>kxcYeey%C zd$4D-|B*nsZN>oJu7x%ImPj97YoYPezzC&LE$RX{eWEN1I9AEh_h0Z6@8fSn8I<_V zaT^<6H|1&jU_s4~wDuIL{R&fhw%|X0$~;00nrp6)Ocve!@@QjM9yqY|_*mNn+JLJi zIE-;VrZDIRS!ZP;>c0X}xGNDQmLq!y_K_<&I5aC`|IU`-3t5YMJ|xRqQsoEwXnvDk zV_#1ox^AG0v<*j;V2Wi5e=l#34sV$T@{1)SezWu;f&IAdAsa81yQcALYrGk4pZ(GN$x8s-iaz4mxT2BT-ODD7PdXfsICp3im}S0x`SFY8rx$%XIA2{R zfp6|D*@m|9#_$+}LeHw@3y+9*f#tZ7os5XGR=`?>n*R_YZgkd)_({r$**kdXp!54T zPndc48x^AJ>e*yIA4f@*bEH`&zi2MY084xh-w4>4w3ncy@j9oe9{<<}&!?%io>ZxR zj^4Wxs1SMGoj1Ox&vRBlNgkGvu{dMf_V1ayp2-x7oRJ-?Cm-r6t2rJ}ONp80!EUEvsi^n+Uwb z`=@jwBq^fXD{nXCo1Bs!w8~s{481d>z{mH9Q$FwO>FjW|RjrhrfKp?DX(|V;K~bP_ zn#zY%40s!3D{?yc@*^v9WltDWS}OgadB8$@U#Zz}Lq>WM-g~5tWEF3TG5MJ#tOxUU z)y4iMu4dzbbU%7;_tZqW>^UoOunIL*+#UlOw;EbJO;0*=s8JlFAu> zef;L)4J3|{&ig;{qc=pQ{-=v2ku6)a7Gx|RPFr%bOS;ypM!@Z{Qemhi{h~CQ2z}Rh z;4>dz;~R>Wf(`sjZTYTUR;LhA@cg9)t6>3-{5>0nO#JfxNBqcdIkFR<^RY^? zFExEmRKGM)`Uyo(Y?(<1>)UxLT|$#1ch=&O?_3aK>rd8(CNG{+jwq?I)VfH zqD2HouR9;Pp)=`=rC>YOI}a!sgYB&WjK1i1>XWWkS`39xZFja~2cA+*svNP>KOB-b zqJX#X+2r5e`HFprEbD1bh7n381GEp;D4W2C&jrz#%C_q-weqyMTe{+2K%{y_3@zCmRo_2TD)>inqq_i;Xj-FCk0*bxmRU~u&@QVF{Xz} zw*lD#0y@63f$JYUuO3tm{g@$^(&f%m+Xit!1T$5PrYR*Wu!sjK2t2MR>>-*6sAcO( z*uV3&2XE6rY#IwEC1|VIA0N0l;X!_qdjE4t5h>Hp`sJ34phBP5drIonhOrQ-4;*P} zDd7EmDfBenrWn{SSn*0UMXH1fKq8ghgUFBbBTUQI6!gJE6mgo6X%@1B&~#dAibbfs zLEfG*j!yAdqX6)lxD{-)TzsRT@kf@m219}E0OTMtg;iQtuS@NTrg7>>VXi*finym# za^5)sr$lS-laV|RaJlK+xW@xIIWHQ`C3D)F_jco@S`dTZ+>I#uTv z@HR?~+jmIO5=cA4c6`-9c|c5_vc1v4u&_DYk!U=c+(D;}rNiUdQkJ=~YYIq2E4P4# zX=FqR_F%U&|5Y%5{gSHibsboza!WmH&X^G?h5Zd?^A_Hm@sL$0R%6LuIeYZES{hh+ z{(CmzZXoij;Wstq%EzXTRl`R*S#|lKFI*EL;=&d{SDA-;<;PC$Q98QlR;8KaAhy4X zGr3FLm#}rBslGjQA%o5?%}E>|uW`q2O3>U@Nd-bzT8cBhRGSW{Kr#%OKh175uyq~i z|8dhpIJJ+_WQnYS{nOeR}ozC`+w_@J*oA z>r?vFOWK2avIeyM!Uq?@UzYu}bCh;vkwp!c+}9liZ?z<6}Hay>~bZtv_R80`Qk_Y2xO9Pq=DdVZ3d-8Dz>nK6B@X0(Li;KFR(MY^rME- zU$9k}DwAw$<`$0H9xKt(nx#uePL;jgvw>7rzHaIj0C90m_}<=`!97j?K?wLK0NpDa z4oLZFqcim5Ck6lrmnEEgTe!j!T#Hj5`VVE}{2=9n)OWo=RS-?BYvXMKSM^2EfQ=Gez1x=EW#mv$Vf6=(>YE1)jx_ zLMp%A{D5@$2!~0% zA4wUJ)_+B1y0%_Yz*2~M2w0+m`u0EGc~T`yyFxmrOv1z?t%X<4GB%eZ9Z}CGexk$C z9;%-E(<_G#=tV3hE9fE$Qz!8#@)o$a!UeQ%0Q*u1Mp8F>r-PwYda~NM)Zh7Qll49E zu*zxeM|)i*e)d&>kbp0SPNE<8-g`Y7?6MSCyCp95A?^FioUvc2(3jtTa#zbv@Of6; zZAjW6`00~PB>M#p&~1b$)I3j?pweY!72eUIf`pSRkOO5%lR-I=iZ1x{Mq6mnz%DMf zaFCRh@7fkJe60%Twq@HHM0%%w;~0%<26|zM41g3m#R{aUeQ1h@<{&)EkKxdPvxRMP z{;>d*65#HKUcK4C_KJq&zp|glSuHkjkLX)(c+?k;!B?R^`Ru&MjM_xmC%W*Xrqp6|Yk+4$v5> z3Y`Hix-sNLpY|UtS4J-ibiz)D0kzgDM#S&g?h%M$F&K0ow#S3Ct0&G}}%ygxYs0M_j7;}5<~VD6qW2siRXd^qbY zIEy^BYFkQV;IkqbJUCu_N<>^i`>5z>8Fh}VVIM0R3v$Sm@Y4mFZAY2lssPmhiitzo zUiV_W6#?+TMe@W|LKi=q&s_r?B(rSE_KH!4b7kORn3PeL)OHBb)T(6Xu9!MlMhXV9 zaVj%x9GoAxH_x8Gp4{$Kb*WJSq=a|iB1RwzL`IO3O;56piz6MVNR4ZjG!@$JdTGd zwPX4#YUm@d`O`bpnIk`z4hTE5G>gD+-7MFbep8>X=&2lO9UV8{dLl@ILT!TMc~ zTW5g&4t5xUo8?;xd9ol`Sb|bSL-Zz7h$--Rk$dA8{lv%E8I-jTKc<7N5z;8mJZyU4 z*ua`{e*SPY9|GXr=$?EvEpP=i%d=e=x4P13HJs4noLgMGYVsKQM7F-+Jj7r~UYGZO z{1Z{qAEE4F(oY7F12??Y_V2YxrJa*BzODc2#qmR+L%!Vf-9^{X0uUQUv>Rn$@*5Lj zpk-H(=u~jbS}LB7c5_w&HX`jgjTmtW!hiyN@xpBX4tiY5 zk;f{UrY6KCG*dz{Q#!U2IaZFrd()2>pqi-8p!AahKk~!tG1*kwkW6XKxbz!~3&4|E zeaP^)wIMr~HNNwYjHy;}x>}rmF;LydO?&g&Hr#UK&RDyZVtnUqZLQJF_Q$qbwy~-y znI*zi?DZy9omQ=pFDxr>$Q(xE;U<3$3<+)gqL2=oY&x3pchd1!81!a}UKsc$6?in` z?KtOet$WT+y4d@s9}c5v+}7^;`B*zygT@!HUh~+B*YwFt;m*uACKR4c!^0ZXbOsg` z+_j$)m9nSsaRMb>)_rUW3hQ4&W%^{_dW3Z~2l#LF(b@rDNxkE>W0Ugu+eMn|{agt;_TPR77rxIw=KF17^J1W$%xYJo@2Mf@p>ja zOa0^ikCA+0IocE#-$0C$=+K~nbpZ#r>6z4y-8hc3%I!XG*IR0PH}$AxAOAISs9P(y zZq>=|rOXErh?00XSQlGb%4Hp2ZId@V>o(9*|YmOScEh7akL9&z`?ZZ1}eUw z%ixWZ^V{1r@$b)lL|s;B3cz;y9?~Hi&mLmQ_^ok~BJ7X8Q1o!Vw&d}}tqgn0JFOx8Fp^nZ@dK9=va2QMVPS4f(V_B-Z)Y4Tq$g4 zB18DfSnlK$bSvlZ$_Qi0^c&&!VP_PVz_ia}X|Epwfbmwq?=)b+&Dg~s%8=HAivq9> zBBnkFGyAs09owrG@{2U*0xl_?Y}!^<*k7tm3OMzauxCYSv(MqEu6j0t&u+^NWqZa) zM2{{=6g@WkdL)O-=PgBk70OJ?5rXcj>W)szab8r=3!g;2#|C}6NgySf3neejcUiW3 zuShH3dMg}s(-%SHHQBEg*qM1#ij)yITD9NeCg35tT%d_m2Y=3cR*h9pHto(_Hs*8I zr&0>HJ&V^RA{eB(ZbMid;L# zJ|(uqqFIAx8-vVGLZsqZk<5%7A3@2ws_&lqud&sN!Q&~K$ALV(aB>FXT(woq_ld1z_}pqahrjv^dtqw*e=c zKp3dohd-d|j;-mD;4QXT2+ml%3+Cds+j#{4P-f8vlPO1eWL|@#arzKKyMN{^`pVEz$bJ5cI`y4}ZW&!Nt8d$ILe7&#+Z8uYA0Qa_wt{ta!Z?hRT+YZXLmi)je*_Hv!kabz;Pl^Lpl;_A!I#);Erf zzthl*-cOWcuD+(lg9JmwX@H5Tcg)crcfIK|z#^QDqL|lkn7)pj&<~ zBg>*phJOBf9L3!?lQ2{CAjHR{Q!gZ;qnvqa59zPYogCUFq6TyH0w~3HC7TiHG`Sj^ zdV2qY-BQ+(sGjVr7qg5B^}#dp%Bp>{$Ha_{-9U8MxlLz4GsBB5+Fzf8d2e9Ja0G2P z34rx&-r*F&3)DM@Vek-?OMHdSy=}+K5VD(+ryPhE-U8JohpIpGYm!g{9rE}dy<>P2 zOp?@o#K%~BBBJgGSmO6J7D$ysD)6yfSzk?kVN;09>)xjA_8z^@febwok?xj(nBAL2B(9WjYLkwFTSX#lTS5vwzPEY zZND%@iSCJ7lXpHY_Tl#%Y6(&I*{!3qUKedXVVx(PP1=rInWykhF4y$FeVIcV1ZoTq zQfrKwNo-OL2(QzU{=u4k5h(5ME609{ZlV9Uu4Ii?irUPLmAlNe<#D-}uiN|_Jij-p zOYQdZTfs}55CSbSnVgw*{FnE)*v9U!@PoxKCYfGo{`zt3aatYZ{Uf8>clV~f)iUuC zqrdpm{IeT7JUiIw4kGW$l0L1MIK3@tfheskNK0HyhAr-%s)1xIlPx6k4>#|1#q(<+=t+oShb~GhLB?(3^3Pu-7-QShr z+uGBpJ)p>cA&qaK?u+FVxaP#Q^oTbxP8ZDSH)z;=GCO4Wxk0ys&Fs_rq#lHvnmzw1{0Z!d)V;w=9*(o@;(H7T4w%c&^`^V0!tU|73D^m$oaWg<43c?N zHDy_QzwcAu=wu$R74E)o^l7qHmG!6xTKzsFpkfA&8zBpp zC6!CbYfgEds)AFXj9y7JT1hyRDa>OOQw4xJ4x_qfp?s{HVg+_!|n!Z5^xj6zo{8n0w%&8`3V%sOKVW@M`qW*It{|5)LT=TeIxuP-1I}|%) zsUW|WF8o_TPfF6%8GhFg{}VRvR2epJZsj(z)ysYrY~^j075>!-2AxN+d(WXq zxQF=S1TysZu(h-E3K-kRSd}IzKlyAVJTW9qi`XPZJ1$A@QXXn;YEG>_!~{KaX$|L0 zS^O*W(^xx0br!-CYx#|B#5+}Xa< zJxj-LOWqH_)xTiw&r_>Dvrs#>{GQD`{)TPohKv-J*oDU-}e5bWz0hPYs*dYy|I^U^Z$x zpr~nFe4Ck0qDs=gTbcgt3TTf#Nf@i+8v1ee25quH5F;#PurRRNlk&cuxHM6C? zWl~I8Vba|Xrhz(%$@5h$ppVSU{y`>*%UI#dA@kx+t>IgwY8q)wFIq^#mm6?;exE&E z3%g^6Hz_7Z(u0z!oV>J3w0FqdTa#k{a&HQz8BuNRmwI#jOP|=T{X$!_7d?WbqcHYC0 zGOn9DUL^Vm$#+&tH_5fWJ}0m=XOO%}NM5+?L1@Xu&kUx>a=T5i*wJy{9JmkGTGA0kS_i` z16=@ZC+$e&T=zimrhbfOQpa**YPAKEpK$Bx6tVHX-sj`T=)24EK~p${H`Q%r(0Wn8 z=wa$8w6FTn*0;mP=eUxVM0}*O>3p|d*t1He_u_;_CM9a$6L_Y|DI7{hLMB7$I*pnna1U=YyN6M%_UC2@f zFiQ!MBc(n0dTtUG4L?EKqVp#l%ZrTFR=N|oa_KBJ52W{IJ%yPtnZXxIbU0A7(eH2{^_X{qN$dNhD5-ATV_f!{7(BB7L(^A=MfE>#(<>mLbhm^w(%mer zNVxPD77*!>js*myTU?NmQY2*QUQ{}k?pSH1b7`LA@A^M4_6^s??m2VjGjq>9_snsy zrXsJqSA;6F3WZ0qVy9AsYr^Qj(c}8cn$X}@GRV#3G1(7`nIKl>)G8d1wF<%rf-VsG zG1DIQ7-zxkS&u3UCiVWrJW+0~bLGOZ1o)0{Yz-Fzi)5Es#Sg*wq&SmUWbC~kOB^S~ z{(x?<{Yj+4|6Te}Vs7}**a|FTOQHlqaUpxil{O|j2>kby!dv+ga;JY+&a8J7QAgD zFl|x%1@#+N=o38GEIXQGcFq9DJ)KO2{muGMO6p#cVBdkUuu7@YmIb5-5eeKQwzoH z)5BM3^PtNSKVvIH^RQz~u8WCLVg^SG`76o8S9PugHw?UNp%1CmUQrIF1mOudGxg2kJoUV=WL2C2OWrF+ zqCWYfN~8~zLVI}&pNl=ejMJM}-55WZroYdZ$jfr@n|@zU7k7B;ch(;b6*!-W+v0*1 zTB4Yeynlu>gvWR)hUL?kv&~F|VejBE$2ZWU6~VCE-<%KGhpuTV`fL8N8^ynLCA!Br z#avQAN}nfA87K@dz(C`X%+k)ppEnuXh+LJE$q zHcc6NZTXW_b4kB%eGNhx)YU>J+9e;kk-lUm+Y~QzB##vU%U3zxF{Dn^;Hs%AxJ8~a zgXantU}Ov{wcOozOS<1kQgin)9O*A1jl5~np0BsVKdS6RXJ`kL6PEL(6(UPsJ^C;8 z2LDyT?48KP;Eyq;fDbEEFM?P&7|_d}lX)UNb&mXLnVJ)ZXl5WfIX52}@?q8XG2{f{ zbgGvBx2sxHPF6vpTl&#{a!{Nc?sp}7sr~+W96<1@H3gt+inXXj8*iU_JiR34!k;De zr|)l>lRkVk{+Ziw{!#==S___5-JHCSF{*LG9n`~n0f9`9{0}_ zm%3XOvR{;jBnavA-ff}fGRVtEziW+rS=`4DUUFVXmWHv-Nr|1Q=P_7@SSe~Ct~%Dg z$lD$N()QmuN>#oIJxPU ziZ%0TTm|p@Lo?39x;_cjk#2S5oABP%msDwQPqb_zk#>Zx44m6HoN-LVk~m*qi!jaB zOiEH+zJts8hg`e`ulqOrdf_V8Fl|cr+%Cbht5=-wb3)EXN*%<!Oe^%xD zdXp-O=UA52xxU1Ges*Zixih{Ee6y4H`hS17M{!@2S{jzgs}IP{VqY$`B--MyR}Bes zf-y$xS2ymQKhc%QuJyGb?&@qDC2O1xRiHs0jooM?Tc2nh!wwfcw))(}$iRxq$E5QY zwf09Veh#vp1@S(qFwS#%%gHe+ku-me)gPC)^i;NI`J;2l6y4lW z%$ji9)$NSYNa3E3qL)Pe%X2Rk%`~tWxcyi5kO=bo%H;9xrT$R5Kcs!DI^>uZM({vo zvYgRO)H;j!c~reghDvA#Jy>TzwNc$aqjBNvL2B|V#X-E+H4Y#s>{7`@yb5}b0Y3pzgIlaZ<`_N}_!4D+?t z5g3Y|8$-K7aQ>5^qvG}VAm)6p;OyL?5X{i$^+X{nntg6+m#*;bQi}6TJhA|3cVusg zCPbU|dHasN1qrHc-p3=BC-9r_q#YI>Ew<#QmMgh(b@C2?de&bV$=~KRHC#z>CceGu z9^3fGB$ECwU`<4VVd1`TSOuu!iNJ3^XZU?6xyP#?9oqGZC0U00Mf5gP?+)5mHk1r^ zA8@|!bDVDeK#gmS1F#5w22eS+UOW$W(Jn9A@4|<#p4I?CRfdq3%Af!FC#?fz@a4pr z{8{6p{$OwGwHmq8%rcz14g+04Oq#b>2PK#)&y zSt*>vJ%I_ms$sMe;UA zSc1=PXn;!K3o0vi+ugS{W!HthLZy6_5wxo#6Y)IGu;Jo4+VI9PH3u}b^^Ji~md!h( zJvAU*?BX%`h3DJtaJ)JZ(G_YhyR{!ZrYDBG#(mF>vdfbmex}WtB-Oy_Q5i88z@}!+ zkEMmoyf#~V@JEU>a;K~B;;Z^l}CNyv6I2Gsr0!c71_d zS1l{)t*M*XL?TC3oqBiFj>v#}p|vA_g(Vje>ajA93SP((-yuC7DseufJ@nHum>$;*mn zV~CSf%PZ8NC~fn~I}nj&r|YTD=P(PTfUJCsle6xO2o?nDov%jIbsZwivqZOe0>gW; zE>bHLm--_)dC1&8@wu3pl=@0|-`n5D=(QeGb4>ZSlc~#0fz(UWmH9S7pycR}dOfk_ z(EHYolp|@ML%vnX%uLt#XV=(Ax_rQNB0ZcwlA)rw`~SKSF}OKg{wwVn_pCEUEw7$k z+gPuMZz^;vh&{Qj(ARW3KY1gL)>wDk`RR=`?)x!oZr|qq_|RpI7@gH8h{#PLTxvf& zDQG7-hPBK0N7e?@z1O{AiP1GId9my_M5Z2*82uD;LV9QpWow&!7+Dg?azT^kMrS)6uQ&63*ch3ahg6`t?lPq3R}g zGW%S%68Tb!^mHQ6w7eGyLZc8qcc97{G$(+x;~bo9p!BakI(uDvzl;x$U!l>O{qFDt zS3uROcK8gxO0)dSg)s0io3II$1gC7#mpwCy-~u1N_1Z(d^%FG57a#sKS_j5UGtA$_ z3W$5m~yGJi>mPXfJ z#S(nJI0V4m{3tHa$RJwW_?n+9f3`NSOt_L&$GKem?tt%K6*4-?FgKN@mZw8ahM%XV z=;1+k$g&>p_!6C*K_k^9N}Pg^A?)KEOojw(s$@wJ?6c;UFqfw|Z<6#$E5w`uT$=NLm_pwDu_uXhj_-by@{M zWTAbG>}qI(;o&5xBYN_cHs%)RUlvD=v1jAn45=#a;FvxSMLG)=4_!z%L3H56j952# z#EfF4jOs*xPuNuM=dY>Lcokg*$AnU)LXg&} z=wyLZX*QNc!)NAJaBH-%o zoIs@091PCRDK<;qyh0ux_8n@|5He+Q>>I4yt@95W66Xtps@4iVN}&p7;gRm_&IXYw z$c{lRW~tc^jwuD>B4CMy^i*jYJy$E5;9XMjo^L?bddO+6VfO3SnAX_rJTZXdXzdvN zNp1}jyi-q4N-{{m>3=dhWs8|#;r9Q1Q6&gQXE^*>O-^B0uejwu$92=cSY}Gq^$w^d zM997=uw@}Zc80I8QM^|0(e#bIXMAPcTrHc5GNI>ko3Jn}6L3$hJo-@nEN#Q-lPya% zE>J-6o{xQq1q&B$`swq~;H(hKMTc+eQRm@(S{dp569;vaQgM0+@g+HK&7$^WwSSh# zfch<@(+3Afi_a*iMqCQ2`F6)31%BIs?IUZ{VI~aCqhX3J>z7s%{x<)ka{CmJH;OUi;TC< zvV6bqW*?JkSSl!>I>58lPXK;JV+~k?Y#62GYNuFO zkpw+)Qo=}8b22e}|9j{y;Cnk@t94~)@oqmL2!~gid7cOD`XemKqVsP5nDdF)qxLn~ zn%oW$$IRiVVoY7f2evK_Cr{9ClvbXVfX?w7IBZ0IU~gMU_k%NS8t?pu=l-9w2f94w zpTmS$IA+e4v}sYHV2`SAp(t)kRQ4@l7|C>K^N54}53_gqsNw1mrsql_qhVF8;|pUM zn-6|ir>Aus`?H+VoQ}-!GAG;F6R>*U&F3ekD7!1DfzKUhwp<^Ri+wC*U43vte~7)G zbmCpDS>XPX^SjQ8-(QH24!Xfnob%$-M)neZ6^HaUq7;NYoh-b$E(mu@FZZ%$XW1PY))MUwra;z2 zK_#48F-Ag{l1T}0!{V!&TTX?O(Oy>QOv_;ZB`u+w)Wm-|RYNKfe=%OO4$OnD@ajPF z{)nJ9FGZ%51fK==IZgAw1fRA%7+KgcaZddm_PQn|d(7;EcM1Eq_c7oG#x)K4a_c4^ zsWf{%9eMsEn${|vc!A2&88*o?!KE_D3o+GN={zY z^&NC*r)=_0lyhf=h5nv-pbSj#!#Vt0suGlh0_N0r((Z$ha<`QN7VLOIE8>Q&cX0vWKZ8nv2n(H7z5!ay_W?C zmSE&M$+o@lEv{()X@cQ(z=uY3*W1zDSN;!pU9(D0&Z!X$flt3{pld!3xFqA1y}{4g z>VFI_XiHYAJ%8<{zevA??$SwNI#nlUdopKnmnY5|Uu7!mGEpznySL%=d$4Yg63P}F zvJo1{)^%czbs%wlz`&Z5D&aYa(gUMUxs&FfmG&V-xifu(I2bhQ_*EsO%YJuA3yq7h zJpU??swZx?%qnjmoW+VQgJDH2n}zBEYKa(}PVdD{^FGw7K<{3QQ4Bs=gBW@L`d2Ec z3k471KiYXmtcVUzra&zLN4&ferd7Q9sphI4^e%%PQ3P^Piw?SDK1o+S!kl!B~Lc8 zlklvKZAH?$l%8FqZ3^gloj&4g)dCFO{6_bDU&Kcff{y+^&e<~UJ=0ZJ8#w?WC97Op zrn)?tg09hLas|wbu&|YI*5wH~U|pw;L`6uZBp8wvHNw$c>zJXK8Tqqxu4^_~c>N&v z2*~P9aQk~G=QYs42*u`C$}0@Q0k9XcK1J7c2V1XU+q0bQbrh*ST6??o|{Uf1LCb)A%xQ~ zMh*?ycWHso{j)mMTW~?jo0VIuZZFFLcejoA{fKyY;Vc(&%1K|YkDt_i%RUo~K0pQl z`UUR&NLZo>m+`)aqW|}2h;N&28=)2-l@PIw+CZQ=GH#JN=!#^)x%ZY%ftjd>`dJ9f zHk?|o7mAbE8<{#hP7^<|2KzG4Xs}{JHF@fC9RuK}1XA?yUt_``!r9psVylVrH=o z2o=U2R;6^%vSY>sXnxYZs%v}YZxmL)f&Ls5g-%2h&v0zePBcAmN^r8a`scuYXbIY~ zl5en1-Ue%2zC9Gce)70ei_018&{teTm5Y?f;5Tl>t*9bM9ij#;>z%AdMPx>>Oa1>k z8!Y0Hb|gaurD*~1sG-Wd>8wJBEeGRob8g0+yRly!{3$oUyzSm$`MxtK#%n1-ZUV@+ zIYa$Uql9y6tK;2J4rP3#99mq{)&7#MoCvZqYxMQ@o*i=!k4|A_8zMNW%cfX$5|`|*pQ;8vgRAp@{hw5q?5J?b{eLY0(AeSw zJ^<=C^8l~eH70PzaEq3el{lbbvnD{-5SYli|HB^@6h{nWGs_8`Dc`P>cd$2 zEnBu0VhbnHNA0M|*u)TBwTN=dx;YrXlD=A1O$UhzLi?g4`u10E7ru{l%T_g*;o~(2 zv7#W#dKwYC8{TT?NB7PNVfTQXfB)Lddf%BhO;77tPFD2YJ4L^Fp*Fwj#Pch zvq@g)q<^_>T)av8ye{)duy=0zwrWvbg0I$BCBjC0q~Ce_Lq_%FJeZw0tM^lsOZ+g? zyH-20nh*8>bzNPx@Feuj$zz}o{@4F%I~OpeY|mS}asYh%J{o$#My;M8!c}>fuuKv> z@eLcIYf$?V?CVnX%w#XugK?uR^fzGCP}O6U*)$iqnKw_M;{8`>5Y0|Ga$);ZPP!bk zW_kqy*>aVqsN4>2aNNix+~BGcW9Cf|m=CX&XVwJIcSTx*sAXRRCNcJq&z*Sw;eixQ z=Zx^M%VQ^x|CzEYa_jf`V!O^cwUmX`jEh25lkZPUbZd_@(f8QtF1}hI!-!j>x4gYe z%hry5(=G}%GzKrJU*Uq-7P=yx{LjmO2_1RoiD?J6oIeQc4QuaR8yzRacsGWf2jaQi zJdo@SEjkF7MJNV_H@t07>;Yypfb2Yn{-Pnkrt z7dN8!X3%uLwo6_eu{J+{B-K)opZS}v*KRuHbSpK=&KVWhgz}g;4W!~_%YM7S9xhac zLOJ%W<`_UonA^10p#JB?rz5C8fPa;^fo9Ci9i z_+pW!<{i}7OSg&zJrk6!f;@RDUs*YQoC8_O-7kaiGb z5+1@a6A1GaM^thbI@X}#*LrA^Q`FQGD0k5C<9D50PeLk-JQRZefP#~9%eb=hf8sX@ z{Gt01+N3DN;4hCO*|WAeTWd0hw~m=TtM!@K(L*%<*IH83G{?AjEKb%O2F_V->IBJ6 z?L2OJ%;vqG$$V|Gf-CvYG9o_8)w=(k%-DC3@`Tre8;Jj^`cNB$WC$`>3A>q^umzY9 z&A?ehb{UOoV!8#vZi4j2v-#a5ZrO1Gkf#~gJgz7d#f@C=|AJ<8IkZAvat$668TeVBj_8?g6 z!ue%$wHSeS#$SUAX-(9nOaakbP05d5WMMI%SM?Y3lCy3|SK!M{@Ppf!gK*O?9EY?r zoeVnRl{ZXrE%^oBd4i__kDgqS?k58^V3E!dYbML}HSlo$=^=l3)X+{Y?^wEPzU(OG zT^BN>PX@?oMHBi~Vo>T$lP{sLRn~~gf#V%Va_?_dN%Y&r{4!LkR|8uGju9>e>;QKQ zOOlmBusXfU&>gaPHou5>Ix-BuBUM$mKBYcoKKExVEEe%Ul!tDGQTwP;vD1BsAE9{S%g(%8w{x^RCC>K0Crd z_|7t?v{^%rvP4e(@{;UJs42luoy=t81ty@% zibDQnPF5XM9FtZg>08y$E8+G3j@?8H+3iouJ@O7M1wt*>KTB;K<$W#*=N!~so_0n6 z$IIE^sp8#Cd-8noloHgt@Zg{u)bDnVG6abJ`g4_t*Hvl};^#FeoAx-M#r+sHoN3_&B{e&EFea|ftplpMm(~ta; z8qR!l1%nydPY%EIu6gXmG%2$52FT+yRuvwv-B)5>a5rKb-hy{9pCda}j~uvtMrMRU znAK#byKj${exE&9Eawc+p4bTh9_znj_+LfT2+5nl8Nv?IFApQN8ib`-w#%gi_Yff@ zeJJ_FH2n|DG`8Q9eDF$(PHL^ND)tVZx8=lLd!GxEf5}p}3_pW?8*7h-RzfwraYud;eh{LTvvuc z089NpnJJMymyG23uytdgZ1(sq4BG3Jd)yS9A*gRBycNxxRONT}zjR{Ae>2 z6#ZRyF0;6$%yU7~GLzhSB}=)LW`)rS*jqc25;Mj=DDX}~Fm7AMiQ$o1;h#<_JhwMr z+p!?yUkaoe&&$a zyW)6CuNbpF3EIw0_5?CZrrrAX$+w$0J$&0P~wMx%cspr0q|Q?$>hf zB^D#D?XKZ~r}N<`6d~5a`mVqHQ|-Rw2~#@Q>;T?wF?W>5rRAr}R3RtGQW8Jm9|_Uo zP0+(ln2DMSm)^cfSYefy9ja3Ub1}?pxyAL`je5ucmQlO_EVHk&^=U}CYAL`|L1xS8 z@f?|pvrZqnODI(-q0JxX)>~q1VeH~9kmM^LbV(neHhMI3%2=N&0^w zXggOj!%jRsq@M!-QOheSEDBn)zZNpd#aFl0l5NEn3Hkqk-+V?;3e{S#Kk9g@kHb4QMaE+vh0lxY7Y4AM`cabn;hOqxo9#}v;I_G`Qowk>7)*+VSQWz=-EV6pc+4@O@ zt((Zz7l(k=9@$86j@1$C@~DZHkUSVafH{ortC<)5cjC0Uk+LnAJ8MZ73ue)eTHHQj zGHpsaLtgHz_96Ubf3_p#!iepMn=$P@2WbKa=0me6*Vm|{mP5YNX?Q5nkm(y)Jwemg z-u#BP^2PscyF5i)X8QV6e8>Ga06@7=)VE%BzJHjT>JsMU!P>9ADfeDC&N&BR zR4jMgNqNnq>Bw0i5_>(CyFMX`BL#>RX;Q~6)IG!xWfv91OxR}LpD;E2Nk9KJPQGzo zKUvUdE}l4N!6$+bk(&Nk45(TK#oa8DGh+G|pnnDW#9w)zi0??L>;4kA>k;o<0EhuyaF)JZZFi)2<=TWPS13eeSGVq7Nt6zn(@=RTVLb@C3XT6Pf{-%h-iUEO@ z6khvrQ*BDXJ$n=N1%;cWsXDkjaYfdp*235Fb?()T_J^XXe+P?V+3*HWVRyGM*-#IB z(P5sOWV^Ai9@cnwH?Wsw$2iLIJLtggpVO$gQ;ar)(FTv(k~1281pbJ#lklI|?DX(8 zNEJy%7WU{?I5MGz@+tZpiw9OE-2zb{UF%OvG z!U5I{PkpWleSWwrOq^=|tV@)vL9qXACnbwv09~3`pvl)Iz07%mv?s_^q)^R6Ixi@RTiX2c>vGiudd(bScC?2xde0q;XJ z0kTlrHSC=}?~2&O>_@9am-i34H%-kjGMUDWwL?HLZ)4J%janMO0e2h9@~v}>Wbh|-z0a2&L2QVm-bj1hQu>)N2}p$7Oc?{Xq41O5THHLV(W9h#Ax=@ZyzY5?3pdbp5Re)g3=?~WDCeRs^>`UW&{KF^lt!)ghw`wNt4*+qQG0+rH zET<>HUix}48(DtU{c8+IM(~O|l8pN3+m?){t$=x zU|#Inh5c~+%Aj;yK?WIe6Mop9Hm1X)p`@i#-=~_j0~>OM4Kg zj|{eMy0e@oXd3m*DH+eUhS2a@-ozSSD@dzdHQY zj*}_tEk_b-66>23MQj-%wyMgzk34rQo~I9l;coHF$r{5$t11~HrLfihwbpUxJ6~YW zdjm2WSuQ5V^~^bR*nB#*sUZ;iP}K7jPZ%y3&k`pQu*}7+H@_GadWaSr*lTnoNz2k} z8QxIR2Idbggu4}daRVPoF!WV%l3ld`st-A7s&=LK3*iQt^ZK>;U@fW{3f~1OHx$jF z+=cNDR&M6}^uQ=hwgYU?gWq2`c8ymb&58_0>-o@BOm{_qhT{VH8ro$K4FGc)rv(UC z##{9yCLxny*4UOf$t4y=F!Pj(j(9+_Nm8C-GvEa|ED&)EhCx?C+m29lMjvVhGDbwn zs(3UIICq9M$vY`S^$-bAJG$#md+*I?aP&ORt6Ue5!Axo=x{KwrBq~rb^jla-w;|{2 zppf3ZXp5E6774Y!mQ7>rz3T#*PIBNH^W-`>+BXkCjLN{e=eR*Sov_u{L7Y@~D9G&D z1i(Jkiy7U@(wobwI{pHH5FGf+hhJkN!eKZY_P9IP32bq_SSxl~TipOjB^%E70?^Ed?jti;dc2fF7j((AadsINy zqKIO5U~)MY-RIubNE^0v`4DjUZTh8)>>#9g{@+PIzD89;rTq_`(oofH6U&N+ z%yP6dCVuQmNnF`sPraV){CM+gj8N@}5HM2^Y(>M)nZ<9iL8QlsGtZ=!*n_~cEw_lP z^-=dV?)6L4TSGa4D<=1c?K0U?U*E?^aLJdlleLFrUeBd^Ncuv6S*%}x>h1OcjT|ku zEO+q18j^;zclD=aYQGH$3kNn*{j5+W=ka%{v@sE#x@WNt;wJ?k+V-PwDIx2lAGiC* zIhzP~)NKGCG&OcvfTnN$IPz5@A%B;p&cePXS=#T3w1&Bn&YKb4kV+9Z-%HW*PD#%z zFO*2(%}II2mEDa$)k1C-6-)9Zi9jY8zPJU8W9SY!5Alv=B}uN^y@v;=!=`e;klzzj z^LLMBPW(HZ2*@0GPYTY@wxMWSL{9A4agV7LW5V*)Z@C-Odu@u!oz-b1iMDH_`pNv^ zTPVi@aa_pjK-})j%%_#n<`lr#9+Pk+&mH$Z4#j!NXz$qgx z;Cdi{7?pg~jd?6h%;@42^7(=&dbm8XEO}Y`E18{k%|k_dn{l7_fw3YP{xAj~go*rLcGT0E2e?O%>-0HdCUp2Gv$;Kvyd{TAtk~pkblem8e zy&1+Gl;f$B6iHTVOiSiPHBv2fP~8UXi487XMQ`3{<9+%%8(JDb$eh5Pd_X^AVhvWE zqAXr7APX{_It;cjm+yKR)i%17`VqU zlPhi4S}Zgp9|P7%$Ii&VWhZcZK^rep>YAHUu#+Axo2qAx!Ce9I)9HvxB#*b?Bi)ev zz!zwA;GEI$HdI>D4U}|mXKg7YyV%1B=aYLEFb&G`FDdj)ys2W36T)u^+V3EeO0+sD1N!w^wVre@ zV_U~o9yb#l7yLFKS3;XVXw|9v*MYdrSg?i{cSYvoOjwfsigf2`Xs4VDv4U#hMCynb z0X|}#wwJp{Ul##7>0mH%W^ao(Y~>2$_aD7DZ-`M4{IOE%VrP*J%NX?W3~QsjU?$gH zm}Z}Q26n0o1ciKH#}Rx2xm>PTNIaf_=cz=Ex-J6W|h@!cXW6;J^7v~SW4NT?Q&kYZF8du1^woGU>vrI?~pCuBYmAlVtS>1OS z6gEu-ai@W@tn>>3S&u<7lX}dRCTv*j+9`~6jPNu&Ovl1}Pd25O_9ZL8ZOa2j>s0gH z7Moqbdbw66!SdLjvD9BdO_cEUiDe70;gIkD!VcC-j#x|jjpH%aT((R34@=*qX0kE(itT3aRLhF~dDKQ-h zo@_Y!stCN6k6HO-ySNswRy#Y1wO*rjG&!%s#vP|Bw8@17(obH{+C8Pho&syFVNw&` zQS@1{rnytg%{CaoLc#&D%#q;~t|<-!>s-i*#O`>mJ2&cE z8Jh;%Ye+$J$K%iBpOP;#jl&ndlTh$IZv_MHpz(6*kXHEBw`9>hEy@)tgQarw?QmMV zWA0F|_+|cG8T{ENr?2Qq3RQ18sFhyhG!3e88Qd+j0}0T+buM02mKIdT`7iiSG`|Yf6P?%GX~R)w{kG{Q z#NpCZ9f-RB-rCEso>WU>9pH5S7znM>UC!ULe4mOM{{Ti59!TCDl%)o3qy7yAHqlFw z^A?5R-R;+en3vD`PDg-3@CdOPBcZVk+L?D#1+D$d(~_stk}cS5PnoV-&fOpA_DHR302s&!HFs&#GT2$fIVSzp?X{O=mpvJWVln+?NdUsJ$v?$C zuOGW&?A}q1gMw54>cUv=1D;HP+P&s$TrnI^>rQE>JP+b+R8()i<$nddC#J%IMQm{H z60G4ok%!-yZ785BN&G%jPEY;Yt`$ zXylZMdfv~Oqgh9uh39v+8xL>riD+L|)uyie*0y3w;g^4m&}eyzIP&ELQeovu4j5~y z8Wx6w!4gUz(Z;$Bw`QGLJs~vxAzT@}bidJE3rsrczB6a__WCA%*DUTq>w#~0EQJyq zq;?eWKcq_zVZxWgdw4ioR&z$ea@^1%f=eie`R`}XT?1y)x?Gx!IRP*U;UZzh?-p0h z^WvEs?|-t^bPnQHE8Rd*(gv}QJRQ=ZZJPCH+25|nr zLXf5N&tN(MSx17duS4wS&oj{*{n6qVjy4?K=M!mB?Tivf?d+9?q5++f6GY56Dqk(r z4NfQ*8u&k!mIUajLwuo~VbgS2d6jUMD0Ch_Z-@MXD$Pf@jJmviM?ZcxThT3pb z`STeTe=N8)@2SIo7&wULZS$i!<#Y$dgtDn!ioi!bA9v$-dKhDi4GgMzs(zm+bdt$G zd`WwDp+&L(&Bqh)7UEQ0T&F_I7XL@zo4r`{2sm29)B4Pat{*1~2?%}8$>b%LP?dcD zt;}}8%w;u5-3-ps^0YpO+N>**IAdP?vY1Zp)poyHtzidEhYU^`+mjXF!^K9CN(fW` zO-+5#`LY_d|BBD(w2F;l@lB1;4jUb9kpJ#P*;D99v4sg@wiu2|tf`C*$AtHWhhYP4 zVJcw6Qv=~(NfJ!?2G)1mTiEw$e6Ek{fW4OFBbO_p0dhulV?8mgv_B^Jb0G5#!$X(y z**kttaA%){8m3ih@=D{&W#(6~@0p4h{Xc+cz?#(Y2ch@m@rz(BxwqepfeMW_-STL{ zZiYQ+giJn(JeSsh#q~*If)K#;Uk{uI*!;jO8MNRq9s|{hGgTWV)UoJL^}5CK+hQp7 zYj>>es>Ke1oC~Y5;1b^xM!DI?{qIWv<{;ba^>VzuKu+Y*&tC86&JyYa?o0Z|dJ;WG zV!T&-38cUt~q`g^bI>&C-DUw-%B zMtmZ-Uy-6yz4WMNb8;y1;@yFsPX8(wU<-riZk<}=$T9oAcW*u#Qxl9gFYy~uD_2zJ8^ihYjN-|-4SVRDwhZeA!hiV8;1*{6Hg8NYy((aD~mJHty_K~RY8Q4Ke`%t2jq*e3n=W2H!0#eg` z0Iu{{A{UiHA~|*7gf}Gc9YH)lvI0081r93e;&|(-g{rfSt6}s zmfRfD$q@Lc^pS#cSg(PmP0)tIIR~VFH4shOYv-xl>T^VXV?+J_wE)i-1*j`^slMC& zz$k>TJfz5s8@*3ZELsem&y5Q`G%=B&6LX|k;TtDnes`lUmyr`*%E%V!rF+f~R09Br zbN(C7fRkLv;lXIQdFE>~SWqOQK1$d>AUyJ4&WdV;XT2`A{&QgdECws=aIvhEG{ZWi z02>gb{K=YP?Z&`5+2^bq397p8Yj2alho8^6hU;piDea!p1X55VvCu5Y1*A0Crta&f zO80FUx>uIX$nGjo7LTK6g^s>M-@*=^n5%P zGs=A&_#js8=9pOq&eOmX=R4rn? zYQDyN2XnTKkUD@1+nxB-HspL*;Ls3jO0^(ySp*Ld{zg2gv7!!^C>WJtg+Itw!YMgt z3xl9^Wh^3~)7~4|JrebnI5*3xio>^$=8zenHQxJxGd466MpXp=_s%jO8OGlLD+Y&w z9bzDU5NrOI*dh`7cyQHWfb)OeFtxUIS55g1kQbk9(qQkzFzJ_8#&UidHCjJQ6hXq7 zuLzqvj!>`=$r(C`P$&_^aWOY|@mXk8j#iPrPN$x)j-XybSt1kl|H9?_vZ(~FfVrX3 z>4L%4pFn=0{9y@5uW#fnNM_l72e|_Tdkm09rb(F>{|~=wG~Y%cH3ve9r(PG=H6@Eu zTFRgP&ejXA278k~t$va#R(oxJSil?Ou&HckvKa=xoEDp7ZpH-Q_y*`_H6W08+Z z7lQD4X|@Ip!WrbxL23P)U{uJPj~~(Urcx|XBBcV=tTQ>O+ol{&e{7)MXErEcaOhDW z7S~G?-1BbNp3OEXmJ88S*6csgP@j7i4;GI{CgV*O^E(V@lzw&tJkUy%CZZr^hhbuL z7@YrW1euGBM<$OMV6A56pu41G=Cx%1^MSo;rEU`8GWdM)WmNUcXezMLhp3SHW94Em z(0Z`i)jBnXCh3 z(a(*kXlD`zo_dCClS8x$+OniUPl4(vXr%b3bRp_2(bvbxG>*fBdD0%`++of|c%q&}R}bLMLwC-(wt_(5~3 zk3EH`gZ*)vzCZVI#W`7W2q_-dT0>jNJdiV-+^C?Izu7tYrZmAO#}g^dR+3t|$2*P4 zPot5qf4)I#A#1@Nr+zTw$RwU6myn;i4>HyNopK6<$cZmB zy=QKl>(vY6%Pb?~pLwJ`kLNu~lly(KibZ;|1Yp1-s27FrrRF(KS#{YK_X?A9spc)B zZZi4ZR*QUU06$3LwEi>zfNU5^=0e!Z=G)O}4eI*4XgtOxTo)!A#zK~=^zX3;)!BuU zZp^si1XyM{Mt=dI(L+n32)s&6{yq6aCGEX7tRcJ+yyzs`6LO%EG4;LEuBezR81 z4jd>_g)qlfwiDnzRp)=S)1DduAj9Y>N;yPf7`G!HDsWssR|E3kyCh+)lg_aHrh-$f zuezD9s3P&0NpqS)DC$V$@MJWSXY3UxZIHxqts-OdGJL}&q)0R}RWB@zz)Ce@TLlmW z{JiSXDz;3>E50!M+$kVESt09`QPb#-tsF?3UTHR7&*k2+a ze;cziip%>m8r;88nA@B1nKn}jXc!nsrI0G)|C6$*v3z-EB zn$by5SZ>DXV#Gxf&0$|<8Hn99?wO;tMDhvyX2dT4HZ?J8ft`+aVjny}R@H{r$e^Gn zGUwRW&YgO;1iY=2`D!OA!<{MF%J=>eU@+Qfc1RpXP2p#DKY3ggvlQM({lExdvj&6I z=c6-h4|xwNFMjoBIJcrp9Q_^vvk$cfuX^Ude>NY@(eRDQAum+Wq|nd}eH$mFXMOp& z_55#6)KI$M3zt8Ui6SMltfyFv5b;ni;=Kx!9$5Z(w_q*vsk)m|dehu*DKYyRpQGn# zSzSYB)5@?jz>reu-3eY2N4LiH+x9>E=ih0Xvva1ui-sW0wOs)L%m-SCRG{Li($i7= zSsFs0Fq#XB2JDhA>}cyIp&niUgoJIO8~yNyNC() z+~|Ncey(FvFpvY%e4O#MlI;{LA^Qaq-uG#gA1%~(5RXs#Hzz71bOaHO>{ns}#t-G8 zdtZ3~?5a49=y;KAL~zCV@8_wsth|Z6$b1FKU+2(ghIDn0J@~xnazXdF*GN5eX`4z< zE+AMl5XGRPI`!allcFf$H1W17W4GFooDH*Q9jrWhPRz$YN;0~QO7BxfX{^AS^35)n z{Hp=<=$c3ANm?pW^Iz;f5JXE3gzP5p+ctj}>X+15{QLT$!s{$SW>5uo@b;H>ZA3lL zkM!6B)~$^|MP4EEVmjUsrk%M7MKxfs^f_7JVhtJxS7#{zsH81&l{iiip~EPfcAVjc zj$UNP;o(`v)XT+S;_5J~T26(?E3opZ)AI;daco&fEa4BK> z{_Q(Z@w@A8q@A*6F=|@!nB(6PBDR^W>lCFneg>>hL^rhll>O5RvB)Xrr-?}{+fd3s zqlu0ZJ0z$f68T3(d{qp!*q(K`qQC1!hFJ6&-@1uEYPbxGw|&p?p5A7*q8q@<-;gJ2 zM^GCtV4hc8QPU;@GH#FcLN%1@(dz5}N7H+T!`(jb!}{tQM2X&_*F@CSB6^63-UZP+ zVRaECdhbz}L}#PdMX*E{R$pBbecgI{KKVYs{|hgc<8bWEHP@7L=9)8pLP;c21h=)- z>`i^*esUb0kXuDchQJ-3vBFQ{{i%L^HQwl0rp8RMc3WAvPVO^tsdSba9EV`|=fm0M zSZ!btVIjft6tau4YVPbb=AQ{RIU-)HiepmCZFdJyt%&aA{yM*i1f;{?7~4(n{rkE0 zLlMD>-S7?cym`gL*Lqc9F=R<_&DZt)D}kTu2WZ~>%9=}=E3SVmnqfp`lUOhDFi`Qu zun=nuemz@^IZSOac;|d@{G;rGrhJNrB+-@f3*YUlZ0ugu{{=T5#i{vSvS91l9K zjo2U9(Xmy6qOa(twlC;vTUY!aYRsn>G~PR`JWu&i(5IC?XdWhSb&qHzX^Tn$HkyRq zt5V9UTg>}1!)qTlVmwWJfgb;T4(Sr!lDZt&=Fgo<2+TEfL!Wc#+8XxQV-_D-)VLqk zjSJB6t@AoQo!7E@@czj|RcQg&>3KiG;SLd)K%SVx*Sa=L6Y%5R3y|cjt?R(>UGs?F z>?ctb$v-A_V8|G)gO-?Wab(dfW9L=cuzt(rhYvH^_)EXjkSF9o$FI`;YbBt;;%B%~y5M z-eYh`i~+Nrh8_A1z@LWR*`r6yBR~iy1XlMNkES*#fPwjh0p?|A68xF-xU~kSg#rFK zuZgBw_EX5XG`ZvHw=|+Q^jmZ+vaFGR2N#!V%wBWlU|&Poa`g!evu%pNcUZTgk3;IlkKaIJwu4fUwUJN+y>@!2@DwpG|j}E zioEh4bZVi)>95JRKjr?1n(I>Y@@F&-3JXSHE@Z5?i~J;y`8nU!+le9v;)c8YklVOb zLO~L);%9u>%$hgj1r3t)j`ogy%eE(-uryu%HWEp~aaRWc$Jij6mx>2H(HCT@j~e(q zY9c)k*=`RbC6b0kY7Ujil`5cRL&Bdzx^tQi7434lz-`B>OZxq_3Y7L!83Ih{HAS|n z5~~lbASojLs zdXg59vaY*ipuub+Z<+B&vDt@T=|Jlc?;W-T>-~Y5DOMa_0`@G9+vx+Pj2Sjm3_~#J z?Q3q>`rIJmv2AE5>+Ugs^4V?tJ^L3AOwOTB)Ly;zi~Q=V_AHUlL4rQ|zs63PKEw!D z)hzK;J-T5P`N9ezl4{*FHSJyH`EXQS_wMM+S{k-VnZ!?GyUVuiaEp^Pe(!LU;P`80 zIG>;SC(BNN2Nt5UR_uEmPB1VhxV^j0mYZSxuz9LjBeOXeR2bI|zF3%=`)W*&id3pY zsjNw@nrqtdm!=87!ZF2Vxg`Hlj8(p8LAC_`4?`axQ^|D1K(f(=`?67_MljR*@PWGK z81C4S$YBh*DkW2KOZI;Tyszi={;CHHbw)1Ynq}gCVH*3Y0qm)`yte^TdLCei>KdtTX!>cZ6({bew>AaM5B9pFThSgely8O}u65h4$J=);)!I144Qn2z-xH>E-C zFp4v3;Fx{hOpP#U){5^Mp3_m0(@faXKQ*V6G+tcgNMh#Xc@ry`P@SeL*ZEmb7TV*- znt`vDaw)c%voho^$ydBl_gyr(-~KmMxF_@WEx!jW8p;rBVjUow0{NDvo&)(ktK8x& z*eyL-naekmr?L6;H^=76w6A?zb2xvPYjOtiEBpDiu~~_BrD>9Ev@l>Y#uA*ZS6Wt~ zO>WTBH@4jspULEiDiPu{1KHleFGC5 z3;Jv*h05%Y+5-zj@7c^6OwluyEseY9=-4}azH39Itm8ly&Gsz?lNKShZb1I=VxK*8 zeLL`b|NGr_as)GxMfcMbI(k(DQ<`QDrkPazC$Y!hTfgnKGha+j&1EKcp5OmLsALR; zK`HlD5W>u73|zw&cc}r2ctNt! zc7#8b5E#AzU1T-KRgui6wLKAxFgg|JrD?ERIY(}w{wp)z<%0|epuLAqnIHqaZbFbh$OX5rF#cuV;HY=0ot{SJc! z^oR;pp-$7<(wrSD@M$-*vz965_NPv^&ts86z-HJA>>W`AEhHrS6n}3M_ZkLtG6<4SSyTGP3g~{!Oi2Wh^P1}R{dAV5vd5)Xm7VDJ=3^#i&Z6( zdY)2s!h=q5$AL#zD$`ggyqY$J5kquj&lx@*NzYjx5)7Lj`(i6x&*lD{)6<&-CT+Rm zi&KMdfh~T-sHthT=l*Lw^j7}RvM=UtQLW$@fFpxoAA<7n5%tKcVK;TqQ_sHJlyK1- z{9yb#!4~H%KJczVwdskVlTqND*0=kr5D--qxnVs}10I@n6h}Cnv1J3JB z0^~#Q+DobD-OQJ={X>OwDel=MEA>G=qkX>In?=ugsmGgKT>wXbF1l<{^ieJ`XFF+HN1#8JtRvncxOe~-6Q0y&}G{H!Y z9@Zt7T#m6V+`YzbS<)5I;FUk{kVIuD$VZ=)@BJZLO5kkDC$3tz8Y@TcSPK^tx4Pf< z_H^*&7^|I^1c&SX7L&q}3d)#`FcLbG;G1}VUSX#h7Mpw>tLxy|MFR(gN@4CkJ%(be zjMvJ4*j$`HQsUA+W%}r3ELmT{vgKQwGAiC*J^vwQL3Hg$g;p@A*yLCVg{sT7-OymJ zS>_AOS6bLyc-ZLc$-Q1Mkfk8EKmh|jq`Y}ztj+B6x~G^_T}M}=-wzifW!2}g`cBg( z*Fzev4isydZswdxE0$J1rRxXum!g!Vd~%&OJo0K(Ea$Cc8akmwE{ zmT^r@+r%W~%A;EE3I6Jfy+%1nnv00i`cz4ri}wCwRrdXZG+hn#N(2S+spg7lt0B%t zSWy@5YhYKAs$kYzPnXQ+tNxqlYG(#^COm^?$-(&-we;TE@d|0W7Pn!G@$a>m_2R3^ z&HCXQ5#s#%!ip(&&8p-Lc?2)vk_B*k-l^dhu!qo5Bif^iI0?5akz=`F>iM~Z9)0Hr z#yR<<8%uQa^ZYl>aaX-Rh9VRd2$#}%&XDbiut#xEYDpQ0A^&tcp&&1i&GkJu8b_Eb+e%*gRB`r3QSXeXbOvZE6U*HR~#GMXD zzioc@M_?7gX?^6ebQfs&epG4pR{*X(RsoRRSICALCi8u9)In&?m@K1pUPr3+kMUW# zZzA4VRDJSq~HL~T@EmQn?BDTRFj(7`m4~-a2B3Og?@g?h_g*8~MJ;nx9G%S}A1=V5#%{)>I?;LW z8?^vsJlT+5wT|5=h|g;fZkAMa<_ptIHW+OZ-`{4B6^F`E)THUE%f>b=JR)8C(5}QS zpdSfE$u>>w#O$&vVy9E|RZ4GvF^yf3WS2Hx2Z@hD!<0vSeGWGbje!*~tK z`LyYm3os5gSHUi|%*<@=qsc;zvbWQt$aN|nD1C)wh)D&F8*BrNk*7`knvxp5`_Xg;a@ROEaN(TTJMQvt(&!mlCZ zanHoMb-NrftHT59XID+teXV){KB{u#7Mv=%{e)Z`&cAZ8P^|hN^&P({r8uat3d*_< zj?#B%GGGb+y03L_>n?&91CP3m{kjR|^|xI4KnHkd!rS27N_ri`5K&`i3R;45{Pfg; zOLC=Z0aBuG)h*Pm;r#j$j_sQ!fc#~^-&80GUR@_}6dU^(`J^ARrafPnpKD;!d5LLk z60pbzbRnBGOxXX)l)?-9lIPjM+ciG2SVE1h{^zi^zuYuCbWxl<`5$xnKJJG-O`aVQ zmvDa$V@lrt+YD$#)si(`S6qc0y%e2$rX!Tb@%6qjvOX}KX7Fx!eXc7WPy1Nx92*fT z@*Q=;ZIwxb7a)i@_qra`izwy(Tkhl-5NC z>rF}dF=Li$ZfW-k5LZ;Rw#xCW)6SxxzRxA=HB1{}`pWy|wvN_vq}t#s?c75d($cWk z8H0ymdCbo0s#fP&8=>ScKV2?}X=?7SAE;&DN?NdKqPDhMC9}VRQ58}qj%@kHYFmrr z0%P^k9#6e=Ls<429=L%SWA%u;D1SRyClKIsKEkNPdViZLLp{~!BSxGcqjpUYqNn#v zSER#XUaQQusfMjjk?V6%%hW3gkNU3w#RnhH<4LMkiP&{*&NU_8MHk5RxApl0_USnl z1=Hx0Qsb2Zdjq8kGCAxj8^;8eu<4|;AI+>YJ84cLv@Jx?l{uVp>9hLrFw%KK^%U0~ zQ3AlT80#yX)qz!J4YgIIaqNPFO9iKbYb-TGk$Yt4tLpdcge(%$WrB+SP9jHSxePLV zWp(d79>ZUkVtqJ@0#Xy*8bp>!Pa*FiuCaPfmpq;BmqGDLzFdnRcS9v#OfPRcKfllE zs#hJli+}44xL~=KPx9Xxjt^g-Ou*@uAZF)2BGJQKxw%iE=%Y(4b=EvbX#SmAV9O)K zJg2)oBddN?&5IOPcO?_+1-)oa-n&H%#Mrt$$94rBI6AqT&8IBSIs|EF<=!4wlq-N& z;9_nXYYr7OUW6?hgr5i`e4(Lb)yE3Hcs5jlb(igsJED1%DNvr~^>eH9h19&sclf11 zCcqV=sOE>CJ42w#u1<+cDfs((INvX+!iIs3Nz#pN5oFn<(gm{g302zaVcZNB`(Q^j zzygiu$sR-I_tKBvVwc2zQB**#zaO*?{mxk)7%{Bgd`>oI}U=-x-8D7ZP=}B!AIb#b%p^iShLNk z;_h8Opvs15$+ss`p%qd&E~z7dNStL;^6kH5W(j)X{E?S zK}##oy#~T?ORQIcIrr?V5~3C;+6&_!sHIPfPPP>dtyiQI<2hxmA+_$C{#~5^WplJq zh1Ji=G)OWyO0|N$6m-jZh!J$Kg}EH$mA*k`+ZlN0@c+#Mu#hnxI=TV220al;9(C2!HAzgAj9u6r3I;_r%B2wX%lYu zd?6jd?EBsBehQvnl&T%Qi52-RJUno%k6WZP#XRAO}4;A z_F)UKLH)}i>;Dp>c_i7!Lv;PG>xAd?Zi@P&{^)R?nYcf5-JhLme2>|P21dMpt`h(j z=8o+$J94<6VYReF*&*&~1~F^Kzmm#QwMzu_M1>QoctC+ZVT^r+X8rmI%$>W>Xg0B_ zDDz)HncmNL*qOMwxcZ1j5ZOcL=mVe65}h+Y!A7<l_CP8n<;}WptR+Z{` za=%#MaVJ<>lgenA{y&u*I{_hSv%W8RC;tQLQ_g$Vy7)s@uaWm#Jpzv-vM|`XpL>1R z$&lgo(c#sm7f++H7gZR~U)}`CYw(7eb?h`Dxj_~mK(4MP!RS}={hfe%WKSD@4 z%6{SO=;#O6HSuY;#w?&Yh?pwu%**+k^=C~VJs{0_5_0WZqzxI}S@oGPy)%8Mtp5tR$w+q@ z8XS9UQygLcg#Jh(#2MlkcAaKt;(#e+3XqNEN<^o|xKT{K3jxE}*R+uKMmc8NGGUCn zZqS>5N)f}J&niC=@l%CG^wMbESb&NRPZ;CPIMRmsQLaqipQnOyzm7AG@`Dc8jK5}Q z1V!lS0*zc>+X zCXDQFi&(EwyXOU5U|X9;bKdb; zdl2f+0MJwg8ZH+7#8G{*Af-C>MOU?AZt3+U`SV60I%!X0{TYrhoA7f1trKbwp*nRD z*p;@XH;>&<_Pqjx6TsS>e<2u4f=C_e8zQ9zYH`c8?u{k^g8Th-t=vR2wCf_J!;Tem zx}dlOKD_Jbiyjze^78>YoX#LRNaf&TLrM_NvR;D5zd#k=;UEUpdA0*urMI$KZASQo z^kJ`}x^R)ai_z&44Umw9Wh}D;#{=Kw_r}f=SBKv@5%Nhv-pX zh$ZFK$YcXQ#*+y@1ittO-3mgX0*({^MEmFVgD0cjfT9AhyzM%}?yU}QefXycbjzzQ z@P;Dk2;z$&n{J;CgHgiu{;Y9G7 zXE!=+D3tP|=~E_c*rSg*#wc=tw!jxX9jVN)?*?ISdsrgxLBo(C!Uo}pXI)TO@vaikEP)oio|IL44*IVe z)L(6ut0>KcMxt)H&EGkrHlR*<#8U;AB>qL35U>tLF~up}>E)}@Toj2hqrjQ8AFT(w zhkU;*OS>)>7fvRYDRwS9_hb2Ex;GXMTmNDljTSGrf2Bj0(gNLipQY~^r~a1y$$}|! zs4U1EW=@zFr-%Jt5etSi*!BnNi)w(0bNSQ;ri(xE+@H%~t7rSq)4^dk0)s}evq5C= zH(g^fia8odyijMycAI;I4la-{R`^=te#OE)K98G=yb=nb&c`v*20XZ!6}<i^ z@_e2P&)V-p9hG7Wv9(AvYo6+ZvF+%ZLwuR84y609;ZF-O1r#3xa`@Ybhl!(cP*>zH zlxay>?jL&%U%U@HiWfArR$tT#V?IT7xIG-+er=Ei4${gkJ;XQda;%(b*X;_%>mYG? z=b7*&yg8{t%`^Qi#!6{-y}+2hn{*B=rT?A(_*KbvwVfw!CK%QI4=g7zzYup;TD7yf zWxu~!L|ah0L;|`@Dab)eHWgqnq4Rq@MBFe6;&u zm1fVCQo=rTH8f#oq`g05P#Bj0*G6zW^}S|TM3`Zc7CY)n<>us>8v7^Ru9l9&sq3P$ z*1Nd#eO3BeZT@IhqR#Bo*geVc zCjpaRa+wBy{DS;lg7U1PxR2yHb5hE1M1@%7^ws;f)<#=Ep0p?zA}9hK2`<+;sDLNzR`#oW-> zKmP2Mx~7Sv>SIwRI&#R1o51UD2Fl`}S3Jcmc|DRLE%6aj@VDYA3X%jyquv6emog$l zeh(@(9T(r;UrFW*YZmOcMZrfX=CnTD^S6Z*VRqibj`~wu+F2O49r6>CZgU$MM;aw^ zAm1<>f>t~!3cvqeLYb^?tCs!0K`!h2niLq&*0x6W(&Lr?F@4n4cqBsKzE7}#3*z*@ zwJF0HDvX-#Z)(66!kYbbd9^ciSo|70Yt^BKgK4w`u1MRJx*L|a8b1}~M(@!Y-kOPp z=Aice%Hg_gp;VK(@%KKE03LQOd)XQFE*_{VtYsbvxyu^@AhG&wn9D+(m*m&V_*&iM z%?a~*l|oeRz>1>D3qcH%8r3^2S|GskE?GSN@9LLW=1>GWrFzWgpP+vLM2WzEZv8^z zQj8pU9?)2Fd46qsnX8rCjl`YtLiM+PM8#2TJ*I@rs=7G}6YVpwP9Sr4Dk(OJtFY%IGIk0Zg197F@4eNP(Hrdsc%aYoDwqs?a*tjbg^uW1Mf~&`9a>NULvZz*;@|zN{pjG zyBnt)!}NlqdF>3USDxXp__jNW&D`89d)-5b%7;vu(FuBED3DmIM;`~$elKPyta{SY zQ2-fR`}Z}$^UfD5J!YM7pN%4n{Tnp4t%b2-Xp}~IhyRox)+o;wpiKY@N^qt>YCU8X z$o;hZV{{-vMVpDQlmvaugnc7Euq!0a^l6w;%j>&RXw9TUDU;ol%jyfz$$vh;mxL}( zLO^UYIWrhHoV#rqLfEs-c_1QIP-LF-c*2N=5hN#AG9j9x= zw%4JFh?itlweY(<#8Al!AXRVPttoDVBl-59>~scj`qMQ{8NJn}Ur@Qoo_AeJK#NdG z4u1v1^n~ zF(sGj$~#^q6_oCnh+85sUSG3ytQlXq*>ixfNZ20E`5qQ1JLn;sqVP@6TZ@P`fX1WAryHu{$`679g1Lo}Zn~&r;=HiP$qNS4@L5PnS>QBP7HM1>Lfj}Z!9Ss>ygg=)cHf}xCY87v+#6bg4G;b*>9e&;)nNKQaa=&2a|3d2} zIc$Y;uK=zd%bj$H>Mx2F42uZAW*RA-`nmm1-9qB68=HmxX(>V}%I5XF?e&^u?P2Gv z1I9|MO@(fJ2JtDYHW+AH^%zZkBk*UM8=kt@Pd#P=L)iU7*Ig8Mwn*+HlH0Y38DfLOo(Bk>VaN7^drGw{psUYlw z9~KxZtcJc2(^}mpSYNSKrE>V4w{ZeFhMISwS=}V?$#&VJgy>74inYVhx2W72 z^!?D+`rLM3)Y;|WyZzQ(5Y^8O`)c}9r)AxBd8Keh4`d)_6@+m9A^&YQ=PYBubvRMY z>*6k9`j51C-Boi3|MkxEr>7JMJQ-z|H#B|wGFR!{QCJ0Q<~bUB41B>+xmEQ zN-*vT@ZTk-USo^#o8|xV>DgXy*nh!{x+AK}UOnA-iC_F={H#pJK>W+g=>li5He=>j zDO#CW*RCs5t4f?i!%njy0u1(i@wU^$p}C4opwf} z<+UK=%#2-^l-=i2rJf@DT6>+D+fJB53Ap!ImzlQHB(hF#V0cp+>7LpLlb%l!Su-!TOpB^wICVxeW?D43hp3#x_Iy$p%kJy=j&y<7c?;? zbLR?1z&c}Xeo5dD7y#FN5Er4^y=;JK;g>BbptQHVOoj#cOIW>m6AsI&w&(G#tQY5w9Kf+e~B;jV?R8;jMLnbH?ji8c6RnKwAFo@q0PJB|IL%C%#0-#4mV#mCWP{ z3T6_Se!%v`B8r(NTQw^c}7by)>ngSPw)gJyiFDms6ZuA~)w`Uwbe}(a6ah<*o z`wkgHga14%V)94>n?NTz`8}__yB~#88>I4`+9!~cZ~s29BHyx@+WDrdMtMbC-r&~9 zsJYR!8L&h{E=4fc51iO9d|ucTs0GU(M}6${@gLtJJ#zaHE61h7XHcC>3IZY1!!6SX zZ9;-oX}V39Zst{jvB0`oKqhEDI+Nb&L*M0am}!_k<5>1%rex4Ac6^8{iKc|1|420J0|~7N+dm_>0O0S%gg+!O~pBqxtX;fG|<}9Db0sCwEtyA7MZ8ZoAh)_ zHW5p8*gGy|yj0?IYWLwtVf|__OFmD+wDcQw_M&3q{Dd;ufLS1;@ibl6C{NcBHiH={ zubwC<)#Y;!;sQt6Hfu%k0|>tSAvC1G%>A1y`I1J6#Pvo6)&Z!=ZT-ASfw}J7mp)l) zYwq@1`?}b#UPw%200-CR+YQet88-u8-Th^Hb3x_)A$Ki^G9^Fmkl5uNds5o>(^iSm zsg}|MpfDTsW+r3&>pvzSjoUJ~aU;V%{v4`VX!vIvgElSa2G?NU{5;OfOxf4yEe)|6 zQtn6UPyUU7!qP#pHoH=PU4b(jIK3NCL45grHxM(NY#+mvd(kO}*u8*?YX+DbF-T=) z_|4850$9u?ru7iZt2hC_GH73hur%$7i-6bPU(xidZ(e!ZM~=_-AL=f+${m8_ydXQ) zi0p)&m*1O!p0N7OwvV#*zPa`pAJM!;+L%(YRw(SRhHjffDgE?$K2)e{r{mVlHB=tz z>YM$aEhKGVAhWtc3@9YW1(~-yK2NEYX|dyJE07CZ!oeOg^MSLk=eIEV{QI9x#PJcK zN%4M$z^tfb-guO5XomNUozN@&?cp%8U{ErSdO^t*?w6N%DPH3FB{Xc&kwN|s&&NHk zv3F$hpNU2HUTx(xS-(8GfwR2)kiKfov+*`X^hY8h9+ zK>xL}$Wu@5ci=dLHrEAu-MV1GF|z9`{Ab7?iF)VY?a~;N^VZSq@p)$rSilcAA7|DF z;y$~V=#q8qgLmZqpF*5h-7^T{1P_sDlFsnV zumY1j4*r5sK;+?Y4XSWhUi{8CiKO!yF#f6r_#{7(F^t#OH5rF`@Zsf7BL3`mn$cmO z;8IEP8WOgnD4>ciC<9O|(8>u@n68HLdZlkY7Z?~F+I?%OUh3#2b5JRq!4bMsdbB!| zr>!=%mrr5gO+8=PWd1jHrP?vVB`s%yBJeAo{@Fh{0SB|J`*Xt?z(mq}hc0{t<1Q&g z`g&V51QhvnT4x#j=^PHN4;uJEcQJa;4}VM1gl!}_?P|<#KPeJ+KlkgGCO$vh*$HcB z9CwUh#`As?>>&^Go_mPJ&8}{`Xq?Z_f39OVM4BkW7#o{lJ3bMB2s-^$&P9gagTLcCyy=D|NpIAIMimwLG_R zNz{3fdgO10xm-$1m$PF5x49%=H)ZEgB{5NvRbz>2GnY<*guf zz5uQT8hOf+;M*jj0q>&t9=~?1vBnAGOXxZunQrQz%>H&WkwPZ;PhPFsCzxfgN|L(z zNY7yRfb?4ypx=+$?_xuVN*rd?wrN%BbwYPayfjLcxdGUtMEM zzAw80EBZIC-@ctD!E_!sKHWwOqp{j*eh=s)ngKMG!k?zR%`m2oZJc@>OICJpD#tm@ z2Izi!X)e;_(qh?jI0w2ijR%7cP!CG|7evHTyM#956um{EQiVF4F9L&Hg#!HUG>EmP8i#tA1P8RNJyh zXnaBBUIb9JU>uHM63N55eEEL>1ok^H6PKn-)B2M6hEksU3w*?P_pKJQ*-9)z7U(F% z35c1Irv=9JlzD9iwkQ5h*UrG=-sswIDl`78a06Cb)27;Q?Gn5x~r%mpVTxzKR(|8JrPu&N%u8`(}F5Onr~21D|o*@$Si-u6H?t9E=+ovXY=+`35gf3je_TfBuHL=jZ=9TCx zek}e+NJ;z>uJLN5*+mPGEIo*8t-`^@ozU*KlQuf`P|95%m7OtIi??8!-v?U$&&k*i zmh)X5f0e7xrXs^UmNO{2+_v@q%>rCZF_a$!e;A}Qdlc888%S7Qmil-LiQOy<0C--i zfI_~L>1rWW8%y1j$PS^*+fC}JpxsJpDLBZZqwliVh*}*%_xXPONDDGoJi6;dg9}XG)v>`2X4byW^3{+cXL3Vxs1J(fd4GShsY)w;9 z?*J~r4RHiFa0#qyD8+$%I1}ffV$|{Hy1!ZDBWS))+Vrbj;qYn5Ah48z+=F!f+o#Dq zuT>NhXZXG7U>2U4lK{Isa223m>ZivXO*enJXQ1Wzr2gH9bWq9lFU-Y$*cXBEwTSZ6 zV`p|EUFZq_2zPt}j@%PSnbSNa?D4*CqG$u?E>aJ4-pS_PM}vq!7PSg+qZ^eeS(?uQ z^n)6}wV*PLQ-vg$rAxj^Q`#I6+SaDv-_~x6|FO~qh22J|#hWK`_LdDmchS>!F z!&)Bp{PNagPW{Li_C_#nw}i~qxeGVsk5p6vmV0Xcxx1O2+LO&(#26p~fti&+@$Z+9 z!ET?vuw!+7Nw~cJ5FI|waHj;geoNO(2%Wwi_q!s|1E>H*cGRH2s^8O+G_B_qF4ymQ;8ORRnEK{P z+7_xTV#bfHWIv^%20oYIdb!K|up#BMY;g4qat(iz1p8#U%FX+ht&Zh#pXcp0`lUhs zmRU`8vryX!j?EVaq!k037JEgZ8(r1%{VTBK_ zO*^S=O&ErU_W%g;$Jmzhw#lViECGe_e>(QQJULd7oPi3+9Oxpz%*NJ(b1n^Qb)=yvtZ2BY z(=Rp!6^3*FsV#h0fw2ODMp6A#AuT`W2^}VIYI**T1gxa(MjTmGz8E$N8PtsnQNwKc zTRHUBheYcM{x|f?7aKD()xoIl_UsE!@KZ0u=KVjnTU+Jv_3*(HZo**uRp6lyKbn=g z$7L#`e`wCl91;=K^i1>*!|H>n*QYgt9ymur9>RZ2JBY%}lVB)yL>H&!%l48xez1%C zux)EhpPzyQG(iXfRok5~OctMuX8Rid9d2E?K51b1&$v#`x^5`1qba0WqnU%|!M`2- zUz#tuEo37sq6Tj$E>hP-^~E%kU=p-5JF%j8dSGC>D#s}`3wsT_H@U%`Do32H z?Z0inPEbyXV8P?-RGDsI3Rg`E45ga+d8m585H#JFttG?aSnE0kMkw>&y)|%?*2^c$ zxnX~n?v8UC9GyZ0tP%f~CpP3qr@^~5!LzsgTW~SFVrxojoc!ZD- z2I5a&t7PAu;9D6c`Tulr*EJ|u`8pM$~3*M=DYO#xGKLwpXG-# zdv%)Y+;0U~cNxVd_2)lu%s%HxN&64~$oKYstm^(=<6q|LYRWi{2b-zVvd6wxsUfMU z!ZwRt%6tiG!tZ(__Fk~=;nRC&0_=VD!6_&Og`1zDGDu2 zZd8N%AuSy{!^5}BD#&N=gg5icI?)~(Pbt(lbM|E(sQzc{mc4kC3L%6Wc#Ecx9zW1k z-i4-A3;Voi%Vfl2)VSe`h&PXCydzH@Kfoo8o6n<4YMkTzti2DoFY_edS=Z!ej8reV z!0$Rg1O~!Bl*QX0PbcI07$39PhHm%)YV1K=)&5Mr{`)m)hb(tDYYTaWI6wy9d5o|; zxWFl2b$j9{{Q0pOyx{Zc$1!C0V{~;^sw71V(aE*Rt6z!jDFVwr-+=kr^IMmTwa7X7 z@ND1IZ&j0=#HD)W<%%>}D7Z*dlYsaJ_8upA7F*bqPGVeJd0}b~SZ2tT#>SvRw%J;)&=Q(%gI&C)2Z59DgP1aF>ba^|W|aH(>i z{*9vHKt)a>-`)STz5nPS+F%m-FU1^JD-64hc@~Hj`XVLB#TZ;JH~A}SDFewh$DC#S z+wv2qa*jxz3m2FGfv+aRG)=OI=16})95tvv1gt4h7B8FTvbx+!m`BR?JHQ%Iq$!39 z77V}R8wi=gTucnD&mY00gxPaLPI4}9AE|wq)V%l%-Zb8S1aku1nanHCZ!TU?;oO1i z_9mq7JGEQXEXJsfortf=FE~vX*g>-7>?;7#qFQOAInWjK<=l1^ zc|*2ji&>3AuDqrJ^H$+P;=bS^Rg)_2jlF5SE;g;u;bLr7myIL+YT+MI;pfI7p(WzI zEzWGtW4Y|8A~rSOQ*D)Ew*{QdN9?XMJ+#&b*>;liFPQ~EcQ|>j)_b){upeCVv~Zv4 zA};~S|H9jVXL!1sK5!@P2Nn&RPY2Y)K}S^up~M6aHyqRX>LAEuEX(GR~wGvo=d>c6#+&00iI zzw6+IcxXlgW`~PC-H4?RUGxE4z}Zg{G;ij5aaA~ZU!acJg%6iuLA%dhkSmJ^?XOBk z5#QV}m@$>}%w1FpV^$n@+46a|vAPu&q%u;-v$TYqHD>drgs?1*m|k=a?2>fd5~<@k zy6~kmeDCUqf))6l-0`mO2CyIQP#0*4E6nCKb6hH7O*=>Qg$(9vi}l>fpf?4eKKx5m*2iZ4ETe zxTD!}1G(b+#|R9|+Zaw}K*BgeVvu*`zi}X%;u3|^E<_kB*|b3g z8G<|Y?{}ef*c07%&RAYr-miK83$&vAX*mthIM!+nZZt{vbRyiR@CfW%NhIwb`h zNJ)$iK|qiif=HtS8Qs#14h3{{H>h+-3*W2fc+UBKT)X&Vd)~XAyPtU7_zp{KS`-WD z&<6!~CX1LBfb%!h?67fKs?AeRQmnzP{xijDHL%rESX&n?T>h9ugKIc;B@ygb;s192 zw6;#jxj&&QGv2gF-zfQbj10Xs%>=t@*7W6aJA{+h{>zZsvp)M@hkhA8mgYPOk@2fl z+ei+?P(^T;fw@*$hgVhQJc$d$Uqw!uU z^IhSFfkd0S4h8mPHE&Di2`VLI?G7Y&UD8h+`OaaFOFZy0J2u&#>6BeXtEG;%@v(3m zMR5DanhmKWTxb%=2D``O`ihhLJy%nWfSQ~t!2{LwxNZO^MB{*W-|2r^5#$tDrzL%J z{A+bxr!UdD2ga0`MPZK`-{Me3~si_xr9o+>8v zV&==tVFDPGvcVN%T+RWxW?Wu3d3F+}*NI!|dh4tV>M?VEX;v*vPp;le=v?mFZX!yV z5~#N7beyD7tC**k3pHsqQJvvfbKzs~oUF ziVpvuQW)FSHn%N515Lgs$ERTjJmzc063JZPL+cgdXO4c+_a}D5bT&`CD%`V7i#&}E zNym0RmFJ8_8YHiVwO#c5berV2o2vRL)s?`PjeF6R;T z+6kx-2g@u&<7pa0%@{OJDUB^ofYklo>{axv{}rzz$AR3)0T!Un*5N5CX{0~d(fgRr zqBml;@v9lWx1s;3=KA>66ld2)oiTDWv2B6puFRt$X`yQg{@(~?2p|7ge6$KW1Dv_& zN5u-Aa%}1YZ*JGzHM&|d5b!|Y;aRTO)1nmNaMtgQ%a?zU4c8uflN?l zs}>|0B!y`bdOBbHVdaE|{piJ@QD%3wS5M`sLNjh}Eq>3S@aCl)H`_J?196YZ+B>x` zF-HRZL#dt#Ka#SnTm;eOZz(?I@%ado5~bWK|h9M{V7e@3*9mfHX9(i3B2Y|w{H-t=KYGiO_ZnP{9C4lodD;oqgTV~@CQ4(VdciWW!OjN*sm zv?pX~T-&;W5K<6N7jqL%)5Iy^zRZe|n~t5{|8!f};QF3VU`j|i_A${@mYgxF27`93 zTj6_aKJ!NyhGyIsa7paNQ_4M=v4|(k_oEj1lV!VOR4&LLKE{37Z4OpT?I$?I%L7&j zRO2t&`}m0yws*hb*?6e(rPM!&klBwZzAR86FL>i@V-i~g7nM=n1Q*$R$FBMVN10Zu zqjm9JnE+>0Qkrn|qc zxGue+3qK=X0Zm_pu-4eHR!Y?7C+@o+T`viG2?HP}(E+^q1u#=I?3$9Km6}B#F}0c@ zRE(wheIu|B?Qvf>aBxN)eU&auX8DaPk!xm7`S2}nQ`$@7+SboT>gi#6xFQo5&~0uu zp6Bwrz(y-^l`By7iE_0wlQB&t@lO5v>EQIYRpmWHV0FNLVWg zrb$mfjvTyXywsNf+_2&n)*9sBy`W9VNT)ZFqTcpE9bH5cpH}Tj_EP_Oy0GpqTWcgr zrL%1xz&!k_VlLvWj5MM>BS&2aZu$`NDPx=vy}o7TJW?SMtWg*1)U9okhS^8V z5O?Ja^)H6OW2{GGr9QD-{DF7HpGhbbkk>yTVT4StK9-|xKKLb#%Byf2;E*nQQFPuW z3kPlH13LMT)QwxeAEFfEp75xIvjl=Qdtk@wN@-`hX3Yz=a`HhUR1YCHHRjh=hmwD5(^TwN;Cn|;NEr^7I8}Ae59P(h-;&Bhs z*cn>)nFcK)o(yNe~(>L7Qi<_qIir=ZrG9aPv4KXjnu= z%_H^T_m@IYl<r>-B9*^{i!I0kjqk%?zEdD+UNf_N8UMTo|A zsbo&#o;iE@W%qZJE|PC{*pb4%Y zjg~iPM(VXfekKe>>B@F5Dn-h?JjB0{@UTC^Q%z z-klj&B`+@Pe5JYvCv-}1K!gHqFawc#ve(>MSMi;9IdE6>fO0>|?%iMtN5apTmiV~P zJ7pX3s}GiUF5H((`Bijo#~bWHt-e-{sO?!EW63w$U+5d^m>!%9BnQ`;K=8&^{rug$(Zs9vFu zd4g^F4vojF+RN5ibe|7Iy&VHtGI6@?I9F-p@4d@0nG5oVTp98}rS4rHhZ62`A@Rh% zV2`y0?p)Dzb{C!VU!H*9w$wtkYomEcnxd-MW=J-Zhf)oL`Ig;F^sYSU#+~92Oqbtd zj22|mv3z34sj#OqZ3x#&d((4jAV2Sxh|8nNOv*#Cp}giJL>*=ib|URq#!82^Dp~c< zL)*(uJ$IO1IvBft#r4!1O%F@g>>uh^#F3N$PFba~ey0AW&Z{5x{z@aTfutb;^=rlH z@I8%`a{YB+iJ)fe@rDo1I2By`ynf9{FYuEx&3iAdhddogoJ3jcKzZ?WQQXr0QO+%r zWcCq{8rBIM6z8mJfl?9VJPPXwKNr_Qqa<`zd~)Vo^`iXA&#%CT{KRq-JNDSH^xm@b zcd-rSC%hF@KHv$N>G!>%1y=)Ni{6+=adI80Oj5Cg>m8)3Hy@7ZdHsx>X4%4PT$4(+ z9>Foh!G(=pLmSPp!lEw6W;42BCL>48m+9X#$?U>4Y_BCfRI$u!@V$eP49(7FwZ^-9 zH!-EQqjtHTM_a`Cu1I)f-Rf-QSft7wy(?&9E_M)ok>O&9Fq9v34;+;0cbJ??_fs3w;<-(GTiX+RGUbL!i)+4H z&sL;643lVUes$o!li28{Zksa98pI0M->xY2sOOUBc^E49a$pj^DUOv|W=5?Gg!(Lr z&RoHsx67g2zg8<#P;&40mXEq$5lM$Uv3QV~PD8cGn}gWdnpUXtEN}5;XA8s~XG0{z zPMO%6xemxL5L>KBy<10#2GsO%BN%rW;uCsz0HT?NSrcTP;mSJ)f8#umML`)G_PXA@ z62o+2>M${HDzDnIm>??3jW6(y?Y1b>sMMx-{a~?F!jmL(G&y?r=Fmu?{ApRqshdTkcm*n5`hE}6sU8Qj{ zQ~BBDDL9P@2OqM9+>_rHvuc2{D9n^kYHkPJM4udyn(WI4xs#)+4cwm?Lcwus%ZT;q`-aME^Qug(3fT8SG2Nv14X|zN4|U5v;=mZ=+rjZa67E*#Aj&@7 z+e5sR{M^7J;{TfRqZAwMqFC5*-sj4%3)9-1=&)vjMw|33ccPtof=6FgGkT5)l+LPXjaTbmL@h-9K{F3hG-xE98r&BpV1DI5e8_}LnF8Zp~EZvjH<=f6BL8^6U)z!^k9kdrTB+^qV-^sKYIbvZQo|H7WZ5#=?p1Ai|Xry zb`-GXpQ}C@*Vw5_+lzfnw@aY65pye4-7^x|D_=n+bq7l9d6OQK!Iil*rr*N~ zMLi1|;4=(0cHnK!pBZq2#OcZxs5Hxgw_>RtUYs-8r>{k8;9KCyVpcNo z7O5G`*WwuG+i@9h*NhCEkW5{4)0XV(1Jy>RVy^HZr8!JzUFhTWMXZ#rvFy*F1aiB+ zr>|R|&M!?jjJ1U6-rQrogXFejkAVlNPa^gEMPjP-KYJ}5r{9A?jvSoMa7uYH!usD= zZ;eM|`ca7mEIOH+TEiE4xTWl3kPPDcy$tVdGVY{eUh5ULhqSO4gG6N8k3Yn3wOxp> znB%}*u;n}DsIjV%v;h`y37wCbrU|bS-=qSnWsV1Fz?nX*sZLhKoS(owBZOb_K7c0( z!RFK+NL(zzdvpVydy?onq6_1DheoxW1u05%J0FU5+oIf~f^x+^Kxgp1p*w4seUgH@ zOplE{{Q2-_4&u7Bu6XD-JCAjAjrboH2Ifh_uYKlM>sJ_y_xhymzS$ps64U;;c)cpY z!(2qVMZ4oBU&dn#?9q`C_Q=~{R?_qb=$q_Ph~(m9Dy5li;)t1p^=So8^s*gttA~ED{1e>UUb8YF zN_1Dsz=B7xzjQAJzcIaP=D;6&$TKDGc5Y=!^zG)nBaDCcJnzNQy(PCBJ5pGOu)Za3 zX}??|^cOEyCU+JsX}QP48kt%N2JN-as+>L|31Sj;@x6)Q>>IhLqmm}IL>s9hHm8vq zJUZ5ZnR)cYEm_sJPS%swaDXX^^KKiu~5@^|(Oj`TbA+|bv<-cklF z;1Z62{!0zNVtTnmk0ME5<`LCd!SWzgY4ht%fdOKhAE25O{hY-{AA&tLYwVY8`gI_I zu+*_y=j928{iqRTX~T)*)4pi6v4>B+Q-`nml6*5;RmXnEOvL!K$*O>>XZO`+E;6z; zN#BPzhitEqzb%ih&U_cSu!NF0HrTV_`!N`nschTmsD$K%7uCZ|x0odN{6RZlOD(K& z%ZtqTV%B2keGq){v?gIK7t`0PoJ_yfiL(1H@wvWm#RK^+xbSI2yfj7>`+R~Aerugl zXMf7uklUZGmb%t)`9wu0vFEf6ncVGxEg!)_*%adj&%nBk8iY^m`gvfbM@kqDOehNW zk_$D~^3YIQ*GFHi1+uSJ7|{swUg%jmx$lghP>o8uzdiNUP2Lsy?dUDsR5htb9CdW| zy~oA{dt^n+ebHppAAW@LQ%ihuPc@STGlI{B*7ZoZ=sJkM*c#*#3*5LhAk7a8`sjjt z3X74xcjMA+OAmX}nu%JP*{F+iqnZL{TH1#(5*N+RgxM6x)EQI$`KAm@|V+VV``W$xcGh`e>Cy4FJ z7Eb%p6V_&eAHpPx`%@nM?1pU2G>pn;aB`TE8Eray{XXf9gmtoE4VBix- zk4}W!iVhv3NCXDoRo9$(=WXNgtdX;MmNCI-A0fEK=$xT@jmZZeE_tyXqffDSKNiS{ z+=Fv}tIklC9w>-6pF;!lSdCIivmNvfOcz~P*-f5=(K4hu*e7xa6o4+ifx!0WRLk^n8-yF<}K0gR}Bfe*(<={*wOJUX-p4Odg-@^~%i-V)m;9e{z zM^CWTNC3f{C(34aE}=xEof3wvNwGdyU#v*q*L`X6bk*ud)baGz zOD8d#s?6{98HUulE83qN8~a2ZT1Tgv@uoN2aqc!u9{1fgNxXC0^zn%Z z_Du1*LHSHaN@~I4ooz^PB}00yz93IH2jJj^+#z3%I~gcuLbq~;LgaA|9kFGXN~E-3 zWDhRe8at09;P+ujPFS%OkIvA%g>a3N4f~GxJVOlW$lZ6;9YWkQbw0xIvpBnqIH!IG z>~eKsvN|yQ*GGERAPurK-zfjqyzbek@d#@~gVF6K2}YlcL=rBVv%-D!a2zhpm3Au> z`3`##cgK3*e7{&#KS>{GF?8aw?<<@CV(} z=^ovwSw!Wh`u45Sa+U zZL14WID7(1v`aqkrP!Ec%cHrrGK`=W0xqLb6IZ5wuMpy*EqzrL|+f^Vi7IcI$s zb?@gyA}7p&NC@YGOwuDg3qb{MSTyo}J6glA<2)|}lNb=4q6kU6^^*U$yf|Sx_a?0O zEnng}G`&tRAfUWM>OvBLyS5UW(-lRVIo*l#VbHGD5o#UjK~X%PZTdRmjx0hvNw?84 z3tp7+)4=*zxC{}J%L2K0gI;gGLf-tN7rYKk2K-5VFkm+1jN5&yM<7|$wfE4GC-dzX z2(Pp4ak=+P4-T#OVorZ%?Eq3UEn~EPqHA5Iy+u)+trO(-)%d^_NPzp;MtwE8!Yg4@ zRbqYWiszyt=eqf$qY|!+0QpWkit|v+J)WI9$B_1rR(9N^@j#3ZhDw8RzZ0{m!Kn4O z>`}?O6Vs3-WT+Qwcx5V69-f_UsXrTn5LPTS>;07($O_+g>^!8Ok|Y$4rfUWXM1}4b zkTKr_o_1wN@4B$Zgo0SQyINyrhy?Hk;EO2{5^e&2OAl#+wM&B-OZ;$03Hn!|hir=1 z@~7E2t4qe`5TY|mxEK))W?%9jePq8)Z?e-@ptmHomQ>2j!S?UjP5B^}^qr)t6{w_ueDb6$MJFAg@i8n5#8|FMO;LtP| zX(;%0;FSWB6V22Qx(Sjapq67P!rjITk0KrkZzep~pziX~rtXUW%Ro5MxlZ+ambn+@ z{efl-(6H9bCHIK#UypiI1n^fHm|uN8@Fo=+38)7{3fVcSqz}?_0OFkD zpI(EYL2FkgJYjzIH$2Zq`Nys=eg^pAytnJqxhZn~aElOWqRRL+fPyXX79!T(o5?XR z;C5H?V>{pxzCtFom{8UG6$8RM6TRKv2&s-q<_*ahm!9AE$A|q9>SWt(di6zBHB)nk z_0{f+=63*zuPio7o2evu5V8GH3o)dJ{&EOX7!rk?BsdlkZV5J_1tVx4yw@$ln;pZP zP9d9H{>ag@XikEn%anXJnR4#c?fc)tPN&>e<6jw?i6Sf%&kap{Mv!!JS0TO@uc6@p zzr7wfb2#Mu`Lnbn1w*hv(1AqRMGaYz;NP_Ae4HxVn}2ZBi}~cQt^TdUd+0m^2+*$p zhBMk+$DU(0rj2XZK^AeiO%1E$>@cJrdlpzzAb<3VrM5z~bU!V<(bTHKA7H9epJnLU zuU~h6HKND&Lt>enjW>~Uj^a~4lg&#nyyjQXFn}T$BZ+?z#`n_PvRD(SMec?=G&q(2 znay|lpv6TcF9^+YjE)XlvlIdk)=vvr+f*UV>dCN4*Npx-f?ubkhUBcS-3;CzKelV= zZOF!$WZ22V&lXh($qN4Q1H7o-_=OUi8(!9^L`E7HnW~Ww0!eyRbaHCfKlqjo4}N0$ zrDL4|J7ku_y&KR)2kkT5?HjeLV)$e0cgH01xAq7|lKwDtRmG6OEdKUghO!f# z@0>}erom3`-6>&+RdQAsa`>$rk;gT&{rYF?o^Q`W-V>Kd;T7@K=KVv1&Tnn>TP4?d zO7avEKYW)hGXdJhnQ^sL*4ki;63~lZe)<)`a8nbG81zd3{+QpDNc^!s3XRs|qqIOm z?=naVcg>Y9-ayB6nheTe8UY6240c6KYu0wf2UM6j@WD5II(PJ9zNfxMK1g?z-j+h zShNRUhvmK{)%R-_s%YwtGFC*GVpy#(Fs(=w%^tg@Chm8+FTtr94gsw{{%eH9A{%MY zyQYy=Ez?w-Vx6sZxK|%#d4Fu3S|w)#WX4V-n(zFjaK*D?leolwKl=~1C4EA-_tR{X zin&|sH>&Hr7kVFJX6xLqa+Z0e_Adbx!CXSWPs$jjGm#Yi5G||4`2MC6pfyPcKPBlG z#?~=y2ZWzoEx)iV;$@o$S<8*Ti2V{N7>)CttYGTJ;f=CUt&kD?(SMkH^@Lddt7~j`djedFIUmp~>WHj7)rb#JpvVt!8YD1BMh8 z(S?zP#gs9{n2wa`-pALxSvG4(QpELV;?eqL--_u(j-}N%Z-%ebaa=8$X=Vn9e_;e6 z6)U^IAuF08ipBTh%O4HJP!JdV^W3)y8KbL)JIF~3i_3RaV-CfLwcn}^W{;5-l0$`5 zLt9mW3Ajc-b%XC8TzZ7NUx2B5R{C!EP>~gkSRK%gXR|Aj0epe*HYL#pH8mYZG( z#vSZ4hhp$Wakk0d(&VguPApP&kK?|1`$NQXEGwFqz#Vd<2V1s1;|Ta#z&xp1fJdKW z4hTkF40S^)iHi_4Wq=d<^L&yy{x43OV|924cfU_l5kFPl0s%h?F25!#Y9iZ7{q>wZ z7(%PJZa#kxf8@_|XeT|wFL@t&O{rFoB%bCH!;0C%`9plS=n`o{`$K_I4AYbIr`vGX zpI+{qI}70T0FaTcrR?84BPSJ~(Try{%!>@*D28_5XjlWi1`i76&)%A6`VS+UdMcTveW`CZtapASO7})hT;j+e zpo6v?eTpdioYNQ_t-MCyS z*k%<-?bmkBwhy`Uj z??~FZUD@%pi~u{1s+I0ef*t>)al>A1U*)Zf*Zewmx~1A*=|sK{pKT>^@BVz;p_V=B zhv!PXLyT3YwYl)HByzD=l3EU~jNEdt2LWSl2h zOUx=g3sm~Pl7a98PBUp^(|r9At}2+~nwjC>Dut@4%<(Dv)jt)tNz3!9c;=~#Ac~kP zlSWX+XqZXca~HmEq4uWjbfWcwiufn}&~4rtR9t(3+w>Y-I*?&Qx@FasT4yzY)vpWv z6(!3u)cWmjrwNY^hENAz)1WIHVI)b%-7s{WQ4M*GgZM9Ay_26UisvFb4Zjira9U>{ z+s%yIV&n&bFGq%0K+S4yK;8(UTr^Cn-)ffpd^YPn@@xz2SU6Ig=1Z_+xkYDMo=mA# z?e+l3mJ6971I}2ZAiQZ(Ml5RAr1b)mi?O{rCs|ACeNCMFkAOw(T3nb(M<#t0&*cFD ze;#`Z`8?Wr@f~cSWdU3QG+rK}v`L!YUX=8+yhXYtWGVl7Si6h&Gv5`_Y)^(4Hr|M@ zOhqfPKs?a90_-#&LX{svobhHMRr?xh5ZpPibFN6CS1C9a;tapz^pD9ItWaDy=AV5; zMQ=@}(p~WDIfmBRJpQ=&oCg7*eiP;`1=lGtUcIvTtBY;-a`2c**OYkwtdyriH#le7 zSbT{($7O!~w{<4WP0Z5J+E8ZMZ_7(;xl2h;a8%O$zq@&~^WxT}fgX-=t&+)93Osy zSH1FK4x8`llpgDH$bXQ>p;;cNz<>SO)GvTAciQ*<($!KE;S6Jbd9p@|6fDY0$U6T_ zcA+ed*$L|nfXuKI-%+x(0@~Z*Aq^p>D$E~nj&LbJT31E8G%+_Za)aKCgc}B*>8N*w zcX&`L>VL!_3QKbW#PBmfmHNAR{H0Qhbyq5GdwOo+Q^=P*K+qq_I5s37k~ZRc%OEKKpr7odNrT`3&djl6C)Vyc+|AQkQv85y{K3RC;7dH9 zZ&ZAK|3k19-;Z~SgKx9e$ltC;RcE$GF1*7y-f*}j-m(5-A^KqSCQ*oyng#&W2}bKG z4!8YgAiw>Xrv+b*)c^K>ko8weP~E2!MVv^*0Q)1_S{A z7|SMBv6%{0OEaZ7cbTWg?0qu+qv8(tOdkpY1@<9ZI6a)<-N4mFvFXYq{x)7(vbET^ z8l54tl_3<%10tG1)CJXpYvehND)d!_VaL|i<5`plp71wel1xTgq=mtAAOsEqB=Ov# z|Aj7)dYpR^4B(n)060(O;MJ{uPaz3^qeN%KzzcH$cb`Bh#O*XR4BxWNKihE~*N9ss z2N0&6dFAM|P^4r+KS~y7J09a(%`V#h_RFx;GU{L<@YxzUmOGHZpQe`kU+GCJ*a}?g zer2c)qq`uDBpVoVVCD!noRS=G1ssB4|M{=*U0QbKgf#d84bPU!* z_CxA0{N=xV@b8I7B@hA#){@8K~#Z|d>b<-apNAp zu}Wn3q~!RbqRfjuadPlR+r~9jphy5HKvQbrT!I-vjWo63e0z;y-%*^`p!eVgFiTsP z!XMSFxNf-5dMAHWj;Jy1Faysg%-7cHE7Q617VBgb9YCof&g?5_Fu)K>aRfGXorP=z z(Cu3CNGT;-J)VG{+mJ($ImC>xXyE#vxWXCZKD8JF%y(i)5G0S5mZxDVe>jdrHk!GI z>F(f7^Xlw0WBx3V-O9i%SlnmRDcP{t47FeNl5{6YNMf)daF*g^&^EKbRE=NAoXCt^ z1}X86ADv7mlAr*0wE6&tr_AsejUHtBTOZ5KD7ldA(+GWJQU>k@-h!s9|E!;uBBojl z0a{`;zoS2=n1;k>2qS(Y<5zsIPpOZeGUEg)IM6g+spWV1)%^MW5v7?)JXr?T>w*@m zRT;Yu#4O%5PL-D6$kCq?4D>SHU3!2%T8({>ZI1a}bEDl!aU@>K$U*nY*Ki}A9Hihj z5~d_@%+nO%B!lk(t_l1B6t_b9CoKn-nRNO?K?^AnVmksS2~aZ&x>h+sQ}f)LU+c5v%^3LB1w!`M5B+ zJ0v1FLY4VfOZogq&yu;NH*yKcJAQM6q7nJF+NuqR;2g*oEHHDp+XVGd1v4NmP>`0( zCO_MQK_(Sy%0)S#u;304_zT@|1v8V+bXC%2DM<>M-uP#=+T9k_V(_TTs`1}oIXzR_ zQXx2s)isRX7L9e=40_+QfY(ZD7*s{Agn8f89S!1T^j}Y`Yk#a$Gsu)J%vLiv1~e`> zd0$&u9>^T+rYrnK9?*)Ax;iN%$p>VHq>Xa3(-ILS3vra~@ECf>m&`oj?r&-i8Pf=+ zx{9W8h^ej5{009I@S8jJoE)+MtH4X0c|kYVSyi;5Cc!N6C&%R!hU?rNh53ffox`5A zzv9^l%58+PxKZ%%xFf!@+~B`JbRZ7uLpnfjP}r5+RkP^F!Ik#);O7{BOZ5;XSt2ts zvw;Ukoxfs_U|EgH$AVvUwD$o8@oxhD{_mNbSL#jU3vs+d#tKBa#P{}%FDd;(a3igP z6sIXtWW#kR0A~jDd#M&Z4@hqkhxoR)6($w+{F2P1%#HtoLZd63U(*9+1HPH~-f>gy zNq?aw{h($HY(i-s9>h2vBIVgFp~Dh&W}KU z>|gLeGs5QyvtC{2PTe#WcfMxR-t+%YBsYM=A;+YF=oq%eNWC*7+!6%;%X|L=Mmn)S zl&_^^;RxjD-c1*g1Oz?FUuEw<1AxTi3VWa(Fh1$46ig$F}E0+6bIWikUdG8-j9KdNN1 zh8spWY#sv3vHwL(#JWt*DyGPoZkTIcch%c`^#%p?q8o=s%>MuzHWOzT1A%ZDVRseu zu9*2eaN<1>ptr1D7gpRDq+4>xc3Aq;?)eQIWncrKH;_D7JxggmRGYttc|aXut{Q3o zblY4Npdx`g_D@gnFFZDjHJ#rHcUPP1JjjBQ=*V5Z-eEHb=FJdDQewknfwsths7|!p z&z4p%`oVaQ6j)<3e!M^_WM8{}8?bw^=c9ibe1FkFQ0DW1iI@C<8Ltsv5`WzNne7&L z2TA0@`Gevab2I7x0qY=;1{v91d|P*>Q!2n3Ue{43&(;hYr%EBZYk6%44|owhgze^A oofbYI^9ic?{9lTYqC}T0Z*F>;zJ6P?2m=0K%9?je6fK|qAHx*y9{>OV literal 0 HcmV?d00001 diff --git a/doc/source/tune/_tutorials/overview.rst b/doc/source/tune/_tutorials/overview.rst index 30d72dd55..326e8c403 100644 --- a/doc/source/tune/_tutorials/overview.rst +++ b/doc/source/tune/_tutorials/overview.rst @@ -53,6 +53,11 @@ Take a look at any of the below tutorials to get started with Tune. :figure: /images/xgboost_logo.png :description: :doc:`A guide to tuning XGBoost parameters with Tune ` +.. customgalleryitem:: + :tooltip: Use Weights & Biases within Tune. + :figure: /images/wandb_logo.png + :description: :doc:`Track your experiment process with the Weights & Biases tools ` + .. raw:: html @@ -69,6 +74,7 @@ Take a look at any of the below tutorials to get started with Tune. tune-pytorch-cifar.rst tune-pytorch-lightning.rst tune-xgboost.rst + tune-wandb.rst Colab Exercises --------------- diff --git a/doc/source/tune/_tutorials/tune-wandb.rst b/doc/source/tune/_tutorials/tune-wandb.rst new file mode 100644 index 000000000..315aaaaf5 --- /dev/null +++ b/doc/source/tune/_tutorials/tune-wandb.rst @@ -0,0 +1,35 @@ +.. _tune-wandb: + +Using Weights & Biases with Tune +================================ + +`Weights & Biases `_ (Wandb) is a tool for experiment +tracking, model optimizaton, and dataset versioning. It is very popular +in the machine learning and data science community for its superb visualization +tools. + +.. image:: /images/wandb_logo_full.png + :height: 80px + :alt: Weights & Biases + :align: center + :target: https://www.wandb.com/ + +Ray Tune currently offers two lightweight integrations for Weights & Biases. +One is the :ref:`WandbLogger `, which automatically logs +metrics reported to Tune to the Wandb API. + +The other one is the :ref:`@wandb_mixin ` decorator, which can be +used with the function API. It automatically +initializes the Wandb API with Tune's training information. You can just use the +Wandb API like you would normally do, e.g. using ``wandb.log()`` to log your training +process. + +Please :doc:`see here for a full example `. + +.. _tune-wandb-logger: + +.. autoclass:: ray.tune.integration.wandb.WandbLogger + +.. _tune-wandb-mixin: + +.. autofunction:: ray.tune.integration.wandb.wandb_mixin diff --git a/doc/source/tune/examples/wandb_example.rst b/doc/source/tune/examples/wandb_example.rst new file mode 100644 index 000000000..b84970b4e --- /dev/null +++ b/doc/source/tune/examples/wandb_example.rst @@ -0,0 +1,7 @@ +:orphan: + +wandb_example +~~~~~~~~~~~~~~~ + + +.. literalinclude:: /../../python/ray/tune/examples/wandb_example.py \ No newline at end of file diff --git a/python/ray/.anyscale.yaml b/python/ray/.anyscale.yaml new file mode 100644 index 000000000..34ad7154c --- /dev/null +++ b/python/ray/.anyscale.yaml @@ -0,0 +1 @@ +project_id: 643 diff --git a/python/ray/session-default.yaml b/python/ray/session-default.yaml new file mode 100644 index 000000000..fe5aa4c74 --- /dev/null +++ b/python/ray/session-default.yaml @@ -0,0 +1,77 @@ +# An unique identifier for the head node and workers of this cluster. +cluster_name: sgd-pytorch + +# The maximum number of workers nodes to launch in addition to the head +# node. This takes precedence over min_workers. min_workers default to 0. +min_workers: 2 +initial_workers: 2 +max_workers: 2 + +target_utilization_fraction: 0.9 + +# If a node is idle for this many minutes, it will be removed. +idle_timeout_minutes: 20 +# docker: +# image: tensorflow/tensorflow:1.5.0-py3 +# container_name: ray_docker + +# Cloud-provider specific configuration. +provider: + type: aws + region: us-west-2 + +# How Ray will authenticate with newly launched nodes. +auth: + ssh_user: ubuntu + +head_node: + InstanceType: p3.8xlarge + ImageId: latest_dlami + InstanceMarketOptions: + MarketType: spot + # SpotOptions: + # MaxPrice: "9.0" + + +worker_nodes: + InstanceType: p3.8xlarge + ImageId: latest_dlami + # Run workers on spot by default. Comment this out to use on-demand. + InstanceMarketOptions: + MarketType: spot + # SpotOptions: + # MaxPrice: "9.0" + +setup_commands: + - ray || pip install -U https://s3-us-west-2.amazonaws.com/ray-wheels/latest/ray-0.9.0.dev0-cp36-cp36m-manylinux1_x86_64.whl + - pip install -U ipdb ray[rllib] torch torchvision + - cp -r ~/tune ~/anaconda3/lib/python3.6/site-packages/ray + - cp -r ~/torch_ ~/anaconda3/lib/python3.6/site-packages/ray/util/sgd + - cp -r ~/autoscaler ~/anaconda3/lib/python3.6/site-packages/ray/ + # Install apex. + # - rm -rf apex || true + # - git clone https://github.com/NVIDIA/apex && cd apex && pip install -v --no-cache-dir ./ || true + + +file_mounts: { + ~/tune: ./tune/, + ~/torch_: ./util/sgd/torch/, + ~/autoscaler: ./autoscaler/ +} + +# Custom commands that will be run on the head node after common setup. +head_setup_commands: [] + +# Custom commands that will be run on worker nodes after common setup. +worker_setup_commands: [] + +# # Command to start ray on the head node. You don't need to change this. +head_start_ray_commands: + - ray stop + - ray start --head --port=6379 --object-manager-port=8076 --autoscaling-config=~/ray_bootstrap_config.yaml --object-store-memory=1000000000 + +# Command to start ray on worker nodes. You don't need to change this. +worker_start_ray_commands: + - ray stop + - ray start --address=$RAY_HEAD_IP:6379 --object-manager-port=8076 --object-store-memory=1000000000 + diff --git a/python/ray/tune/.anyscale.yaml b/python/ray/tune/.anyscale.yaml new file mode 100644 index 000000000..7a93e4030 --- /dev/null +++ b/python/ray/tune/.anyscale.yaml @@ -0,0 +1 @@ +project_id: 578 diff --git a/python/ray/tune/BUILD b/python/ray/tune/BUILD index 354da4aec..7bd338764 100644 --- a/python/ray/tune/BUILD +++ b/python/ray/tune/BUILD @@ -93,6 +93,14 @@ py_test( tags = ["exclusive"], ) +py_test( + name = "test_integration_wandb", + size = "small", + srcs = ["tests/test_integration_wandb.py"], + deps = [":tune_lib"], + tags = ["exclusive"], +) + py_test( name = "test_logger", size = "small", @@ -523,6 +531,15 @@ py_test( args = ["--smoke-test"] ) +py_test( + name = "wandb_example", + size = "small", + srcs = ["examples/wandb_example.py"], + deps = [":tune_lib"], + tags = ["exclusive", "example"], + args = ["--mock-api"] +) + py_test( name = "xgboost_example", size = "small", diff --git a/python/ray/tune/_test.py b/python/ray/tune/_test.py new file mode 100644 index 000000000..34b22f621 --- /dev/null +++ b/python/ray/tune/_test.py @@ -0,0 +1,43 @@ +class Trial: + hypers: dict = {} # static + config: dict = {} # static + status: str = None + trace: List[Dict] = [] + checkpoints: List[str] = [] + +space = {} +trials = [] + +trial_checkpoints = {} + +while not Optimizer.is_finished(): + while Optimizer.has_next(space, trials, state): + trials += [Optimizer.next(space, trials, state)] + trial = Optimizer.choose(trials, state) + if Optimizer.should_stop(trial, trials, state): + Executor.stop(trial) + elif Optimizer.should_pause(trial, state): + Executor.pause(trial) + elif Optimizer.should_restore(trial, state): + restore(trial, trial.checkpoints[-1]) + elif Optimizer.should_save(trial, state): + checkpoint = save(trial) + elif Optimizer.should_continue(trial, state): + step(trial) + + +exp = Experiment(logdir, name, restore=True) +failed_trials = exp.get_failed_trials() +run(failed_trials) + +exp = Experiment(logdir, name, restore=True) +trials = exp.trials_finished() +trials.reset_status() +run(trials) + + +optimizer = Optimizer(sweep, metric, *parameters) +sweep.configure_server() +sweep.add_logger(Logging) +sweep.set_executor(executor) +sweep.run(func, verbose=verbose) \ No newline at end of file diff --git a/python/ray/tune/_test2.py b/python/ray/tune/_test2.py new file mode 100644 index 000000000..a55db1fd1 --- /dev/null +++ b/python/ray/tune/_test2.py @@ -0,0 +1,207 @@ +storage = TrialStorage(location) +trials = storage.get_trials() +failed_trials = trials.filter(status=Failed) +parameters = [t.hypers for t in failed_trials] + + +# Builder Pattern + +factory = TrialFactory() +factory.queue(grid) +run(func, factory) + +factory = TrialFactory() +factory.queue(distribution, num_samples=3, repeat=5) +run(func, factory) + +factory = TrialFactory(optimizer) +factory.queue(distribution, delay_feedback=3, num_samples=20, max_concurrent=3) +run(func, factory) + +optimizer.restore(storage) + +factory = TrialFactory(optimizer) +factory.queue(parameter_list) +factory.queue(distribution, num_samples=3) + +# single process + +trials = [] + +while factory.has_next(): + x = factory.next() + trial = build(func, x) + trials.put(trial) + storage.save(factory, trials) + + while not trial.done(): + result = get_next_result(trial) + log(result) + storage.update_checkpoint(trial) + factory.update(result) + storage.save(factory, trials) + + +# concurrent + +trials = [] + +class Actor: + def __init__(self): + pass + + def configure(): + pass + + def step(): + pass + + def save(): + pass + + def restore(): + pass + +factory, trials = storage.recover() +optimizer = factory.optimizer +result_streams = [] + +while factory.has_next() or not trials.not_done(): + while factory.has_next(): + x = factory.next() + trial = build(func, x) + trials.put(trial) + storage.save(factory, trials) + + while Cluster.has_space(trials.live()) and trials.has_pending(): + trial = trials.pop_pending() + handle = Actor.configure(trial) + result_streams.add(handle) + + trial, handle, payload = process_next(result_streams) + + if payload.type == "SAVE": + trial.update(payload.checkpoint) + storage.save(trials) + elif payload.type == "STEP": + trial.track(payload.result) + log(payload.result) + else: + pass + + if should_checkpoint(trial): + Executor.save(handle) + elif not is_finished(trial): + action = Scheduler(trial, trials) + Executor.execute_action(action, trial) + elif is_finished(trial): + factory.update(trial, result) + storage.save(factory, trials) + + + +# concurrent with checkpointing + + + +# concurrent with pbt + +while factory.has_next() or not trials.not_done(): + # ... + + trial, handle, payload = process_next(result_streams) + elif not is_finished(trial): + action = pbt(trial, trials) + factory.queue(new_hps, trial3.checkpoint) + + Executor.execute_action(action, trial) + + + +# Restore last experiment +exp = Experiment.restore(storage=X) +trials = exp.get_trial(filter=failed) + +run(func, manual_list) +run(func, space, searcher) +run(func, grid) +run(func, manual_list, checkpoints) +run(func, manual_list) + + + + +run(func, exp) + + +# Core concepts: +# Result: Dict[str, value] +# t_state: Any +# Trial: hps[Dict], static_config[Dict] +# TrialTrace: List[Result], t_state, Trial + +# Trainable: t_state, Trial -> t_state, Result + +# Optimizer: o_state, List[TrialTrace], Trainable -> ( +# o_state, List[TrialTrace]) + +# SearchAlg: state, Dict[hps, Result] -> state, hps + + +# Execution concepts + +# Checkpoint +# LiveTrial: TrialTrace, location, status, is_idle +# Status: PENDING, SAVING, RESTORING, TRAINING, SETUP, STOP, ERROR +# Trainer: Trainable, location, t_state, Trial -> t_state, Result +step(o_state, LiveTrial, List[LiveTrial]) -> LiveTrial, *args + Server(List[LiveTrial]) -> List[LiveTrial] + checkpointer(LiveTrial, manager_state) -> TrialTrace + Logger(TrialTrace) + Optimizer(o_trace, ...) + Syncer() + + + + +TrialExecutor(reuse_actors, queue_trials) +ServerConfig(server_port) +Optimizer(stop, search_alg, scheduler) +Experiment(resume, local_dir) +CheckpointManager( + sync_on_checkpoint, + keep_checkpoints_num, + global_checkpoint_period, + export_formats, + checkpoint_score_attr +) +### Tune commands +tune.set_log_config( + upload_dir, + sync_to_cloud, + trial_name_creator, + sync_to_driver, + progress_reporter, + loggers, + verbose +) +tune.set_server(ServerConfig) +tune.run( + experiment, + trainable_fn, + raise_on_failed_trial, # where can this go? + max_failures: int or "fail-fast", + trial_executor, + restore_from, # checkpoint path to restore from + resources_per_trial, + num_samples, + search_space, # I'm not a big fan of this because Search Algs have their own search_space too + Optimizer, + CheckpointManager) + + + + + + + diff --git a/python/ray/tune/_test3.py b/python/ray/tune/_test3.py new file mode 100644 index 000000000..934fc1381 --- /dev/null +++ b/python/ray/tune/_test3.py @@ -0,0 +1,42 @@ +checkpoint_manager = State(location) +checkpoint_manager.optimizer_state +checkpoint_manager.generator_state +checkpoint_manager.trial_state + +# How much have we learned +optimizer = Optimizer.from_checkpoint(checkpoint_manager) + +optimizer = Optimizer(space, checkpoint=checkpoint_manager) +for x, y in warm_start: + optimizer.report(x, y) + +samples = [optimizer.sample(random=True) for i in range(50)] + +spec = TrialSpec(func, local_dir, checkpoint) + +generator = TrialGenerator.from_checkpoint(checkpoint, optimizer) + +generator = TrialGenerator.from_trials(trials) + +generator = TrialGenerator.from_spec(spec, optimizer) +generator.configure(checkpoint_callback) +generator.queue(samples) +generator.queue(num_samples=50, repeat=3, max_concurrent=4) +generator.next() + +generator = TrialGenerator.from_multi_spec(spec) + +run(generator) +################################################### + +# Exploration process +trial_list = get_trials(checkpoint_manager) +failed_trials = [t.reset() for t in trial_list if t.status == "FAILED"] + +generator = TrialGenerator.from_trials(failed_trials) +tune.run(generator) + +builder = Builder() + +for params in samples: + yield builder.build(params) \ No newline at end of file diff --git a/python/ray/tune/examples/cluster-p3.yaml b/python/ray/tune/examples/cluster-p3.yaml new file mode 100644 index 000000000..85a02fde1 --- /dev/null +++ b/python/ray/tune/examples/cluster-p3.yaml @@ -0,0 +1,71 @@ +# An unique identifier for the head node and workers of this cluster. +cluster_name: sgd-pytorch + +# The maximum number of workers nodes to launch in addition to the head +# node. This takes precedence over min_workers. min_workers default to 0. +min_workers: 0 +initial_workers: 0 +max_workers: 0 + +target_utilization_fraction: 0.9 + +# If a node is idle for this many minutes, it will be removed. +idle_timeout_minutes: 20 +# docker: +# image: tensorflow/tensorflow:1.5.0-py3 +# container_name: ray_docker + +# Cloud-provider specific configuration. +provider: + type: aws + region: us-west-2 + +# How Ray will authenticate with newly launched nodes. +auth: + ssh_user: ubuntu + +head_node: + InstanceType: p3.8xlarge + ImageId: latest_dlami + InstanceMarketOptions: + MarketType: spot + # SpotOptions: + # MaxPrice: "9.0" + + +worker_nodes: + InstanceType: p3.8xlarge + ImageId: latest_dlami + # Run workers on spot by default. Comment this out to use on-demand. + InstanceMarketOptions: + MarketType: spot + # SpotOptions: + # MaxPrice: "9.0" + +setup_commands: + - ray || pip install -U https://s3-us-west-2.amazonaws.com/ray-wheels/latest/ray-0.9.0.dev0-cp36-cp36m-manylinux1_x86_64.whl + - pip install -U ipdb ray[rllib] torch torchvision + # Install apex. + # - rm -rf apex || true + # - git clone https://github.com/NVIDIA/apex && cd apex && pip install -v --no-cache-dir ./ || true + + +file_mounts: { +} + +# Custom commands that will be run on the head node after common setup. +head_setup_commands: [] + +# Custom commands that will be run on worker nodes after common setup. +worker_setup_commands: [] + +# # Command to start ray on the head node. You don't need to change this. +head_start_ray_commands: + - ray stop + - ray start --head --redis-port=6379 --object-manager-port=8076 --autoscaling-config=~/ray_bootstrap_config.yaml --object-store-memory=1000000000 + +# Command to start ray on worker nodes. You don't need to change this. +worker_start_ray_commands: + - ray stop + - ray start --address=$RAY_HEAD_IP:6379 --object-manager-port=8076 --object-store-memory=1000000000 + diff --git a/python/ray/tune/examples/wandb_example.py b/python/ray/tune/examples/wandb_example.py new file mode 100644 index 000000000..526a7b1c5 --- /dev/null +++ b/python/ray/tune/examples/wandb_example.py @@ -0,0 +1,103 @@ +import argparse +import tempfile +from unittest.mock import MagicMock + +import numpy as np +import wandb + +from ray import tune +from ray.tune import Trainable +from ray.tune.integration.wandb import WandbLogger, WandbTrainableMixin, \ + wandb_mixin +from ray.tune.logger import DEFAULT_LOGGERS + + +def train_function(config, checkpoint_dir=None): + for i in range(30): + loss = config["mean"] + config["sd"] * np.random.randn() + tune.report(loss=loss) + + +def tune_function(api_key_file): + """Example for using a WandbLogger with the function API""" + tune.run( + train_function, + config={ + "mean": tune.grid_search([1, 2, 3, 4, 5]), + "sd": tune.uniform(0.2, 0.8), + "wandb": { + "api_key_file": api_key_file, + "project": "Wandb_example" + } + }, + loggers=DEFAULT_LOGGERS + (WandbLogger, )) + + +@wandb_mixin +def decorated_train_function(config, checkpoint_dir=None): + for i in range(30): + loss = config["mean"] + config["sd"] * np.random.randn() + tune.report(loss=loss) + wandb.log(dict(loss=loss)) + + +def tune_decorated(api_key_file): + """Example for using the @wandb_mixin decorator with the function API""" + tune.run( + decorated_train_function, + config={ + "mean": tune.grid_search([1, 2, 3, 4, 5]), + "sd": tune.uniform(0.2, 0.8), + "wandb": { + "api_key_file": api_key_file, + "project": "Wandb_example" + } + }) + + +class WandbTrainable(WandbTrainableMixin, Trainable): + def step(self): + for i in range(30): + loss = self.config["mean"] + self.config["sd"] * np.random.randn() + wandb.log({"loss": loss}) + return {"loss": loss, "done": True} + + +def tune_trainable(api_key_file): + """Example for using a WandTrainableMixin with the class API""" + tune.run( + WandbTrainable, + config={ + "mean": tune.grid_search([1, 2, 3, 4, 5]), + "sd": tune.uniform(0.2, 0.8), + "wandb": { + "api_key_file": api_key_file, + "project": "Wandb_example" + } + }) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "--mock-api", action="store_true", help="Mock Wandb API access") + args, _ = parser.parse_known_args() + + api_key_file = "~/.wandb_api_key" + + if args.mock_api: + WandbLogger._logger_process_cls = MagicMock + decorated_train_function.__mixins__ = tuple() + WandbTrainable._wandb = MagicMock() + wandb = MagicMock() # noqa: F811 + temp_file = tempfile.NamedTemporaryFile() + temp_file.write(b"1234") + temp_file.flush() + api_key_file = temp_file.name + + tune_function(api_key_file) + tune_decorated(api_key_file) + tune_trainable(api_key_file) + + if args.mock_api: + temp_file.close() diff --git a/python/ray/tune/function_runner.py b/python/ray/tune/function_runner.py index 8d81a1132..7408f780d 100644 --- a/python/ray/tune/function_runner.py +++ b/python/ray/tune/function_runner.py @@ -369,7 +369,15 @@ def detect_checkpoint_function(train_func, abort=False): def wrap_function(train_func): - class ImplicitFunc(FunctionRunner): + if hasattr(train_func, "__mixins__"): + inherit_from = train_func.__mixins__ + (FunctionRunner, ) + else: + inherit_from = (FunctionRunner, ) + + class ImplicitFunc(*inherit_from): + _name = train_func.__name__ if hasattr(train_func, "__name__") \ + else "func" + def _trainable_func(self, config, reporter, checkpoint_dir): func_args = inspect.getfullargspec(train_func).args if len(func_args) > 1: # more arguments than just the config diff --git a/python/ray/tune/integration/wandb.py b/python/ray/tune/integration/wandb.py new file mode 100644 index 000000000..bfe3786e3 --- /dev/null +++ b/python/ray/tune/integration/wandb.py @@ -0,0 +1,345 @@ +import os + +from multiprocessing import Process, Queue +from numbers import Number + +from ray import logger +from ray.tune import Trainable +from ray.tune.function_runner import FunctionRunner +from ray.tune.logger import Logger + +try: + import wandb +except ImportError: + logger.error("pip install 'wandb' to use WandbLogger/WandbTrainableMixin.") + wandb = None + +WANDB_ENV_VAR = "WANDB_API_KEY" +_WANDB_QUEUE_END = (None, ) + + +def wandb_mixin(func): + """wandb_mixin + + Weights and biases (https://www.wandb.com/) is a tool for experiment + tracking, model optimization, and dataset versioning. This Ray Tune + Trainable mixin helps initializing the Wandb API for use with the + ``Trainable`` class or with `@wandb_mixin` for the function API. + + For basic usage, just prepend your training function with the + ``@wandb_mixin`` decorator: + + .. code-block:: python + + from ray.tune.integration.wandb import wandb_mixin + + @wandb_mixin + def train_fn(config): + wandb.log() + + + Wandb configuration is done by passing a ``wandb`` key to + the ``config`` parameter of ``tune.run()`` (see example below). + + The content of the ``wandb`` config entry is passed to ``wandb.init()`` + as keyword arguments. The exception are the following settings, which + are used to configure the ``WandbTrainableMixin`` itself: + + Args: + api_key_file (str): Path to file containing the Wandb API KEY. + api_key (str): Wandb API Key. Alternative to setting `api_key_file`. + + Wandb's ``group``, ``run_id`` and ``run_name`` are automatically selected + by Tune, but can be overwritten by filling out the respective configuration + values. + + Please see here for all other valid configuration settings: + https://docs.wandb.com/library/init + + Example: + + .. code-block:: python + + from ray import tune + from ray.tune.integration.wandb import wandb_mixin + + @wandb_mixin + def train_fn(config): + for i in range(10): + loss = self.config["a"] + self.config["b"] + wandb.log({"loss": loss}) + tune.report(loss=loss, done=True) + + tune.run( + train_fn, + config={ + # define search space here + "a": tune.choice([1, 2, 3]), + "b": tune.choice([4, 5, 6]), + # wandb configuration + "wandb": { + "project": "Optimization_Project", + "api_key_file": "/path/to/file" + } + }) + + """ + func.__mixins__ = (WandbTrainableMixin, ) + func.__wandb_group__ = func.__name__ + return func + + +def _set_api_key(wandb_config): + """Set WandB API key from `wandb_config`. Will pop the + `api_key_file` and `api_key` keys from `wandb_config` parameter""" + api_key_file = os.path.expanduser(wandb_config.pop("api_key_file", "")) + api_key = wandb_config.pop("api_key", None) + + if api_key_file: + if api_key: + raise ValueError("Both WandB `api_key_file` and `api_key` set.") + with open(api_key_file, "rt") as fp: + api_key = fp.readline().strip() + if api_key: + os.environ[WANDB_ENV_VAR] = api_key + elif not os.environ.get(WANDB_ENV_VAR): + raise ValueError( + "No WandB API key found. Either set the {} environment " + "variable or pass `api_key` or `api_key_file` in the config". + format(WANDB_ENV_VAR)) + + +class _WandbLoggingProcess(Process): + """ + We need a `multiprocessing.Process` to allow multiple concurrent + wandb logging instances locally. + """ + + def __init__(self, queue, exclude, to_config, *args, **kwargs): + super(_WandbLoggingProcess, self).__init__() + self.queue = queue + self._exclude = set(exclude) + self._to_config = set(to_config) + self.args = args + self.kwargs = kwargs + + def run(self): + wandb.init(*self.args, **self.kwargs) + while True: + result = self.queue.get() + if result == _WANDB_QUEUE_END: + break + log, config_update = self._handle_result(result) + wandb.config.update(config_update, allow_val_change=True) + wandb.log(log) + wandb.join() + + def _handle_result(self, result): + config_update = result.get("config", {}).copy() + log = {} + + for k, v in result.items(): + if k in self._to_config: + config_update[k] = v + elif k in self._exclude: + continue + elif not isinstance(v, Number): + continue + else: + log[k] = v + + config_update.pop("callbacks", None) # Remove callbacks + return log, config_update + + +class WandbLogger(Logger): + """WandbLogger + + Weights and biases (https://www.wandb.com/) is a tool for experiment + tracking, model optimization, and dataset versioning. This Ray Tune + ``Logger`` sends metrics to Wandb for automatic tracking and + visualization. + + Wandb configuration is done by passing a ``wandb`` key to + the ``config`` parameter of ``tune.run()`` (see example below). + + The content of the ``wandb`` config entry is passed to ``wandb.init()`` + as keyword arguments. The exception are the following settings, which + are used to configure the WandbLogger itself: + + Args: + api_key_file (str): Path to file containing the Wandb API KEY. + api_key (str): Wandb API Key. Alternative to setting ``api_key_file``. + excludes (list): List of metrics that should be excluded from + the log. + log_config (bool): Boolean indicating if the ``config`` parameter of + the ``results`` dict should be logged. This makes sense if + parameters will change during training, e.g. with + PopulationBasedTraining. Defaults to False. + + Wandb's ``group``, ``run_id`` and ``run_name`` are automatically selected + by Tune, but can be overwritten by filling out the respective configuration + values. + + Please see here for all other valid configuration settings: + https://docs.wandb.com/library/init + + Example: + + .. code-block:: python + + from ray.tune.logger import DEFAULT_LOGGERS + from ray.tune.integration.wandb import WandbLogger + tune.run( + train_fn, + config={ + # define search space here + "parameter_1": tune.choice([1, 2, 3]), + "parameter_2": tune.choice([4, 5, 6]), + # wandb configuration + "wandb": { + "project": "Optimization_Project", + "api_key_file": "/path/to/file", + "log_config": True + } + }, + loggers=DEFAULT_LOGGERS + (WandbLogger, )) + + """ + + # Do not log these result keys + _exclude_results = ["done", "should_checkpoint"] + + # Use these result keys to update `wandb.config` + _config_results = [ + "trial_id", "experiment_tag", "node_ip", "experiment_id", "hostname", + "pid", "date" + ] + + _logger_process_cls = _WandbLoggingProcess + + def _init(self): + config = self.config.copy() + + try: + wandb_config = config.pop("wandb").copy() + except KeyError: + raise ValueError( + "Wandb logger specified but no configuration has been passed. " + "Make sure to include a `wandb` key in your `config` dict " + "containing at least a `project` specification.") + + _set_api_key(wandb_config) + + exclude_results = self._exclude_results.copy() + + # Additional excludes + additional_excludes = wandb_config.pop("excludes", []) + exclude_results += additional_excludes + + # Log config keys on each result? + log_config = wandb_config.pop("log_config", False) + if not log_config: + exclude_results += ["config"] + + # Fill trial ID and name + trial_id = self.trial.trial_id + trial_name = str(self.trial) + + # Project name for Wandb + try: + wandb_project = wandb_config.pop("project") + except KeyError: + raise ValueError( + "You need to specify a `project` in your wandb `config` dict.") + + # Grouping + wandb_group = wandb_config.pop("group", self.trial.trainable_name) + + wandb_init_kwargs = dict( + id=trial_id, + name=trial_name, + resume=True, + reinit=True, + allow_val_change=True, + group=wandb_group, + project=wandb_project, + config=config) + wandb_init_kwargs.update(wandb_config) + + self._queue = Queue() + self._wandb = self._logger_process_cls( + queue=self._queue, + exclude=exclude_results, + to_config=self._config_results, + **wandb_init_kwargs) + self._wandb.start() + + def on_result(self, result): + self._queue.put(result) + + def close(self): + self._queue.put(_WANDB_QUEUE_END) + self._wandb.join(timeout=10) + + +class WandbTrainableMixin: + _wandb = wandb + + def __init__(self, config, *args, **kwargs): + if not isinstance(self, Trainable): + raise ValueError( + "The `WandbTrainableMixin` can only be used as a mixin " + "for `tune.Trainable` classes. Please make sure your " + "class inherits from both. For example: " + "`class YourTrainable(WandbTrainableMixin)`.") + + super().__init__(config, *args, **kwargs) + + config = config.copy() + + try: + wandb_config = config.pop("wandb").copy() + except KeyError: + raise ValueError( + "Wandb mixin specified but no configuration has been passed. " + "Make sure to include a `wandb` key in your `config` dict " + "containing at least a `project` specification.") + + _set_api_key(wandb_config) + + # Fill trial ID and name + trial_id = self.trial_id + trial_name = self.trial_name + + # Project name for Wandb + try: + wandb_project = wandb_config.pop("project") + except KeyError: + raise ValueError( + "You need to specify a `project` in your wandb `config` dict.") + + # Grouping + if isinstance(self, FunctionRunner): + default_group = self._name + else: + default_group = type(self).__name__ + wandb_group = wandb_config.pop("group", default_group) + + wandb_init_kwargs = dict( + id=trial_id, + name=trial_name, + resume=True, + reinit=True, + allow_val_change=True, + group=wandb_group, + project=wandb_project, + config=config) + wandb_init_kwargs.update(wandb_config) + + self.wandb = self._wandb.init(**wandb_init_kwargs) + + def stop(self): + self._wandb.join() + if hasattr(super(), "stop"): + super().stop() diff --git a/python/ray/tune/progress_reporter.py b/python/ray/tune/progress_reporter.py index 621503269..c1325f102 100644 --- a/python/ray/tune/progress_reporter.py +++ b/python/ray/tune/progress_reporter.py @@ -469,6 +469,10 @@ def _get_trial_info(trial, parameters, metrics): result = trial.last_result config = trial.config trial_info = [str(trial), trial.status, str(trial.location)] - trial_info += [unflattened_lookup(param, config) for param in parameters] - trial_info += [unflattened_lookup(metric, result) for metric in metrics] + trial_info += [ + unflattened_lookup(param, config, default=None) for param in parameters + ] + trial_info += [ + unflattened_lookup(metric, result, default=None) for metric in metrics + ] return trial_info diff --git a/python/ray/tune/session-default.yaml b/python/ray/tune/session-default.yaml new file mode 100644 index 000000000..da8a1159d --- /dev/null +++ b/python/ray/tune/session-default.yaml @@ -0,0 +1,73 @@ +# An unique identifier for the head node and workers of this cluster. +cluster_name: sgd-pytorch + +# The maximum number of workers nodes to launch in addition to the head +# node. This takes precedence over min_workers. min_workers default to 0. +min_workers: 0 +initial_workers: 0 +max_workers: 0 + +target_utilization_fraction: 0.9 + +# If a node is idle for this many minutes, it will be removed. +idle_timeout_minutes: 20 +# docker: +# image: tensorflow/tensorflow:1.5.0-py3 +# container_name: ray_docker + +# Cloud-provider specific configuration. +provider: + type: aws + region: us-west-2 + +# How Ray will authenticate with newly launched nodes. +auth: + ssh_user: ubuntu + +head_node: + InstanceType: g3.8xlarge + ImageId: latest_dlami + InstanceMarketOptions: + MarketType: spot + # SpotOptions: + # MaxPrice: "9.0" + + +worker_nodes: + InstanceType: g3.8xlarge + ImageId: latest_dlami + # Run workers on spot by default. Comment this out to use on-demand. + InstanceMarketOptions: + MarketType: spot + # SpotOptions: + # MaxPrice: "9.0" + +setup_commands: + - ray || pip install -U https://s3-us-west-2.amazonaws.com/ray-wheels/latest/ray-0.9.0.dev0-cp36-cp36m-manylinux1_x86_64.whl + - pip install -U ipdb ray[rllib] torch torchvision + # Install apex. + # - rm -rf apex || true + # - git clone https://github.com/NVIDIA/apex && cd apex && pip install -v --no-cache-dir ./ || true + + +file_mounts: { + ~/anaconda3/lib/python3.6/site-packages/ray/tune: ./tune/, + ~/anaconda3/lib/python3.6/site-packages/ray/util/sgd/torch: ./util/sgd/torch/ +} + +# Custom commands that will be run on the head node after common setup. +head_setup_commands: [] + +# Custom commands that will be run on worker nodes after common setup. +worker_setup_commands: [] + +# # Command to start ray on the head node. You don't need to change this. +head_start_ray_commands: + - ray stop + - ray start --head --redis-port=6379 --object-manager-port=8076 --autoscaling-config=~/ray_bootstrap_config.yaml --object-store-memory=1000000000 + +# Command to start ray on worker nodes. You don't need to change this. +worker_start_ray_commands: + - ray stop + - ray start --address=$RAY_HEAD_IP:6379 --object-manager-port=8076 --object-store-memory=1000000000 + diff --git a/python/ray/tune/test.th b/python/ray/tune/test.th new file mode 100644 index 0000000000000000000000000000000000000000..7dbfb6790dfdf91be66d27abac1aca4d8f581158 GIT binary patch literal 137 zcmZo*>f*}zGso?xLe`1^NxcRp-ED=1.5.0 torchvision>=0.6.0 tune-sklearn==0.0.5 +wandb xgboost zoopt>=0.4.0