From 6f1c12e9249f1a65201830c034076399adbe318c Mon Sep 17 00:00:00 2001 From: Jinyu Wang Date: Fri, 19 May 2023 02:09:59 +0000 Subject: [PATCH 1/6] init lwd for mis --- examples/mis/lwd/README.md | 132 ++++++++++++++++++ examples/mis/lwd/figures/fig1_mdp.png | Bin 0 -> 61094 bytes examples/mis/lwd/figures/fig2_transition.png | Bin 0 -> 51109 bytes .../mis/lwd/figures/fig3_diversity_reward.png | Bin 0 -> 38969 bytes 4 files changed, 132 insertions(+) create mode 100644 examples/mis/lwd/README.md create mode 100644 examples/mis/lwd/figures/fig1_mdp.png create mode 100644 examples/mis/lwd/figures/fig2_transition.png create mode 100644 examples/mis/lwd/figures/fig3_diversity_reward.png diff --git a/examples/mis/lwd/README.md b/examples/mis/lwd/README.md new file mode 100644 index 000000000..a8f10491b --- /dev/null +++ b/examples/mis/lwd/README.md @@ -0,0 +1,132 @@ +# LWD: Learning What to Defer for Maximum Independent Sets + +It is the application of the [LwD](http://proceedings.mlr.press/v119/ahn20a.html) method (published in ICML 2020). +Some code are referred from the original GitHub [repository](https://github.com/sungsoo-ahn/learning_what_to_defer) +held by the authors. + +## Deferred Markov Decision Process + +
+ + ![MDP Illustration](./figures/fig1_mdp.png) + +
+ +### State + +Each state of the MDP is represented as a *vertex-state* vector: + +
+ +$s = [s_i: i \in V] \in \{0, 1, *\}^V$ + +
+ +where *0, 1, \** indicates vertex *i* is *excluded*, *included*, +and *the determination is deferred and expected to be made in later iterations* respectively. +The MDP is *initialized* with the deferred vertex-states, i.e., $s_i = *, \forall i \in V$, +while *terminated* when (a) there is no deferred vertex-state left or (b) time limit is reached. + +### Action + +Actions correspond to new assignments for the next state of vertices, defined only on the deferred vertices here: + +
+ +$a_* = [a_i: i \in V_*] \in \{0, 1, *\}^{V_*}$ + +
+ +where $V_* = \{i: i \in V, x_i = *\}$. + +### Transition + +The transition $P_{a_*}(s, s')$ consists of two deterministic phases: + +- *update phase*: takes the action $a_*$ to get an intermediate vertex-state $\hat{s}$, +i.e., $\hat{s_i} = a_i$ if $i \in V_*$ and $\hat{s_i} = s_i$ otherwise. +- *clean-up phase*: modifies $\hat{s}$ to yield a valid vertex-state $s'$. + + - Whenever there exists a pair of included vertices adjacent to each other, + they are both mapped back to the deferred vertex-state. + - Excludes any deferred vertex neighboring with an included vertex. + +Here is an illustration of the transition fucntion: + +
+ +![Transition](./figures/fig2_transition.png) + +
+ +### Reward + +A *cardinality reward* is defined here: + +
+ +$R(s, s') = \sum_{i \in V_* \setminus V_*'}{s_i'}$ + +
+ +where $V_*$ and $V_*'$ are the set of vertices with deferred vertex-state with respect to $s$ and $s'$ respectively. +By doing so, the overall reward of the MDP corresonds to the cardinality of the independent set returned. + +## Diversification Reward + +Couple two copies of MDPs defined on an indentical graph $G$ into a new MDP. +Then the new MDP is associated with a pair of distinct vertex-state vectors $(s, \bar{s})$, +and let the resulting solutions be $(x, \bar{x})$. +We directly reward the deviation between the coupled solutions in terms of $l_1$-norm, i.e., $||x-\bar{x}||_1$. +To be specific, the deviation is decomposed into rewards in each iteration of the MDP defined by: + +
+ +$R_{div}(s, s', \bar{s}, \bar{s}') = \sum_{i \in \hat{V}}|s_i'-\bar{s}_i'|$, where $\hat{V}=(V_* \setminus V_*')\cup(\bar{V}_* \setminus \bar{V}_*')$ + +
+ +Here is an example of the diversity reward: + +
+ +![Diversity Reward](./figures/fig3_diversity_reward.png) + +
+ +*The Entropy Regularization plays a similar role to the diversity reward introduced above. +But note that, the entropy regularition only attempts to generate diverse trajectories of the same MDP, +which does not necessarily lead to diverse solutions at last, +since there existing many trajectories resulting in the same solution.* + +## Design of the Neural Network + +The policy network $\pi(a|s)$ and the value network $V(s)$ is designed to follow the +[GraphSAGE](https://proceedings.neurips.cc/paper/2017/hash/5dd9db5e033da9c6fb5ba83c7a7ebea9-Abstract.html) architecture, +which is a general inductive framework that leverages node feature information +to efficiently generate node embeddings by sampling and aggregating features from a node's local neighborhood. +Each network consists of multiple layers $h^{(n)}$ with $n = 1, ..., N$ +where the $n$-layer with weights $W_1^{(n)}$ and $W_2^{(n)}$ performs the following transformation on input $H$: + +
+ +$h^{(n)} = ReLU(HW_1^{(n)}+D^{-\frac{1}{2}}BD^{-\frac{1}{2}}HW_2^{(N)})$. + +
+ +Here $B$ and $D$ corresponds to adjacency and degree matrix of the graph $G$, respectively. At the final layer, +the policy and value networks apply softmax function and graph readout function with sum pooling instead of ReLU +to generate actions and value estimates, respectively. + +## Input of the Neural Network + +- The subgraph that is induced on the deferred vertices $V_*$ as the input of the networks +since the determined part of the graph no longer affects the future rewards of the MDP. +- Input features: + + - Vertex degrees; + - The current iteration-index of the MDP, normalized by the maximum number of iterations. + +## Training Algorithm + +The Proximal Policy Optimization (PPO) is used in this solution. diff --git a/examples/mis/lwd/figures/fig1_mdp.png b/examples/mis/lwd/figures/fig1_mdp.png new file mode 100644 index 0000000000000000000000000000000000000000..2e6a3e5c24d5f2bd1633b64d49881f9ae9748363 GIT binary patch literal 61094 zcma&OWk6NY);3B?cWk;vL|Q_+OHdFHkXBJTr9nESBm|^WNAEU z(7nhMC%)aG|O+nDFJ_oB;CkFD#S=1hrh`LM=2ge$jq%k{SE zQGLPu&O5UJa3oN3hG6Fbo15COW9niT;~7z z3jUGE`MIO?|Gq6StN~Nt~-gw-s?j--J^w?Pu1hwign7SYwhOY^`9w1lJ5I*LH~C> zlZ(tN{@g~56rwKH!@0^qQEHNxTC<^inYUhii@0knsgl59zcf*1T>*LhPzCIOg6iJVxO^f0GTLy6nb}r0Wi}~gz z7OCFY+ti{ioAqzjj2hjtY!z>dy*)b^ki5S5onm)!abX&yn)=|$?EU|)g@l5b$AS4; zf7+Y1ep+#lgU`<@1+6FT|NVaCyhDrR@31*pE+0B$f? z&2Jk*LNFTdy!;lCua=>m<>Gd_zxvgAL$B7BmXPp0m*H0wbnGgt3EPoRkIIaiG2 zF)@L6=e<7Onb+Y}l|wRFn69x&;dsXP@&#dCeSKFXb?xh)$>+yAI(zL{@IR#4FD@@5 z?>-3oGn9k4tAW-pCs9)fO@hqGS$#KiILVh4K<{vEaS+_cc(I+jPtq^R#%l!-fo z-t)csj@H4@5zHtX{GPAAV8g@#7S7}FFVXczS&O#xV_$B*H@{Woc%Bw%U0)t&U_1L~ z_*^|Zij;VJR=_~Q%0y9W+%gAG*L*0Ou|PsXg3T#{OFVu)Fw>~zVqaycVA2jxsbh1h zQZrXcddcY;jcOPtW_Uv99un89%dRyJTDjx|Y_BGq8>GAEgL-Uz`}A z6f7=vg?9`Uw?OHJlXLnY&Ot3h;%WIKFNQ|1Q^%E_7~YrP z5rr*iSGhs_(@-+ z65b2cW$EN${GL_Lrrh<8tbF~iA7*rP2+mTr^tA%(}=7o(ZdsEufy zC>mV>RaR*t&gNnq>O5k6xF@h&Y_Z`6gkHz9uAKxiIQ}Y$T+bgEF8U9oi}%IfCF%X$ zm$+jX@iv$|={c#0x0vnQ8tW+(6co$Rg0^s2+Ie<`I)9zHYAc;+I&pTbg7?gd_+sbm zPm6FVcoyKf!d4={6Uou~{9sD+lWN;-&y!uow{bqF;uga!2coV-%W!W+W^*X;zq4C5 z@HIAZDX(f>w)}jq>NOjV!oNk*XNbavF}~bfeVV$N8FEjjRBIsVWUzG(cG>)|U(}!G za+Tu_HzwvA-AU+zy&K-1AN8k+LZ!YjHa6a_u^3@BIGGwR(T}^M7R7=YV;o8!_<+0f zC8H6hgIXxF%mIQ)LL0TqabukGFe2<2pA?<*vFkV9lhd_~trWW6DX35<#&@K5{i_P7 zpTi>Gd(j=m87Nev7fLOoJ9pv9*@?wysr-R#8&!1&lb!$(=$yq`a0yjWm?`A9v2f6b3Zxd z5B?|*E|5m|8?<;|?!F477PRh35#n}_X(lYcxQLZK&QXkRL-$+lOT2k%Pqj$sF$tw< zJVdzDo9>^oc%AGnc1okS+@=l0OkjVCjT}l|qLD5(;eCC17=V!wjK^7`zpSPgMN>}7 zY5A;XlT4xOmngpQ)qWrMUu6<{{DKjDT9HAsJvxU8+WuxDx9ypF=yXJQ$NQY?3pF;s zoSH}6G;55pX!?G^Gc0|K{^{d8a{e5Mf`Sh_rZ;)>)pRlE!bLk@xU}xKj@2Txi}4^$_D{vOr&=ovg{i*v3Rg zy4Wd;{5u~Ee1nH7Z4&EauuHTDlH7Lcye~zXP?e3U%wsB}?zYXM2xt3fx+ ztUo2tLvb9^kfy6)MexV!Hd-&oCkW&aFCKk4N^e<{agxiX60jsCdTp3Zb+kRZvA!P0 z>-@@`Jh1}iO3$~+@|RE5*`%>yt4=7QCH_+Ozod^Hp`G$2E?8scfoFB{%dX|g`a%f3 zx4N`;o$C%f^RV|x-O=>L42r&MZB-hdR4r&D2O1o?#}&v@K-GpKuCpd{1O3 zn@qp)jGKWXPRZKC%FsX~?6~>M z^`yAW2*GCJe$eB%pL^TRh@xh7Ri!V2vMiQmDjudFmFvNuA*hxegyB~wX%59mDl##m zc!nsN2Rq{>DcIPz{{-mbbC+EdYDxy5ihG$CKIs_FM6NK)BFR@VJ6%PLHm#=E@e9}c zTpw0h;OuSg&8yzOM)pHdEn1$bf5XnEJJaMTq+es*+}!+}b(iEZZ^!}=8y+67A_J&&w^yd%tLV5PXYyd$>_t-AwF zieE#;7 zTUVFoFwr{StO{xE!y+w)+%xzB7(t=DWGysB!0Mab13Yx8q`#wul~xm6nC5#+UG*Dh z9@4vQ^$cjqGbFc*MC|4|)&@9t=d$G;nmKh!b=h@(my(-J@!=fuJOA~A^8EIWY$ICo zYLFfe=aX8-;}JA8G=pZZVl{e|-q?g<9Zm$d9=moGF5PE&U(46MGU2-T^jE(ogz#R! zgepJFVV$EdHEP1%>9%f=NDDzS`+RW341C?~nR0$x>q2AWH@J1V zI+q7=3TPO(Ur6sMTvr%0TNi4rzxp1(niUQ0NkbWm@y~pV{OG_FkcCs}_* z_#gOOi5u~8yF@R`aV!{*I{1O`3RWAcv6ivX(!EZ3 zQDn^DcF0qBQYCWMa%5bXX=~$ZZ0GNJod0UQ;4*5|HQdN~8<=T8_s8wvPYmvSjSaN| z)01pD$8UezgK*^fl6Z3(?=4~@K5OJ*w`|p$K<7JYdb9TUBlm^l`fy&LoCufO-_g78 zlG$ReD_%`!&q@dIH-v`}NZ!*Q-@iQ8OTDc^D(rc*>1?vV$0!DqmZqkSLinBUKUZky zb4}G8CVsp9!+D(h5nZSMIGqns8# z19l*F052Do8h+dQRSSqbgGs(MhM9YNzLwv{vMsv7RgKyI#Py!GxkK}%_CK2bjEP24 z7PC#=K4{H%bL%m#QJEB>Ko;B7)+dpSY3WhLeSd37bD<6w-cwNxj(kG!d*=fonS-@@ zA3GVz4jpv=%rqI%FsI)?C`mDGv%b1eQh2<@y6ZIf=`i5B;a;F|n?E}9$&8HFqW}HZ zKNiMe-Nvfln7jVxSQZ#KfPbm}t2=+8aV zNS?is7FP3HT^3@vygWZ%-u(Uj15V*Px&Q2PQqS-4c^BWtfA{6{R*vLz%D*!osrG%X zoHFis@?~|~FfvuZ=B&bTm1@`8?Td?Od@JhmBj|wh#ff?9j5bbdE6OT$YW}D_ z-qQB6jga3w6Ay2D&ZalrFoo)t)855tHBp<-RXN%%y&*tzV`vw9pt{Enc!?^0_UZ8A%79*(HK+mbF`-Dq<1 zzMol*B2BT(XT6`pdEdp}MR}91k z1TItXx<6IFr8!TV^U*CRB-0<5zedJ!mhx752-?N(Hh$l*V?y^E1t z<(vwkJ#WUR1y0ej=)!+5#&nDef4fqiVHxxWxtW>`1zhVZy7WIT=h@>@uXlWiOFl#6w{zPbb=Yn-Ei*UQQnHFLW^ zUEH%S^??JE#$=hXBr%H@5F3rCwVx?Eb@tEM762Ti2-!)z`c5i5;roL9Sp`dN^YxYI zI{7l{SgB!yQVf%rMy;*kQ~j!@69jYyQ3^_Js3E|hdS&7NYwf)Lmi)F2M4&5l9N#Dk$%#PGp&NirjQK%u0r&Fez#c49za!p1%KQN$%!?!nA4qQXerj8~T z4^KV4qN^hiH|rS(+ciJxXoIWWho&&sXc(VvZf=1`o#eu~#6Z?n?F$%m!m)VUOV?#D zib3Gm#Pt5F4$tNW=i8&P#ozakJ^^YOHj*WaL3U8Sfa=)xsxKkNZn2#toP4YXH$$?_ zhWvC}bZeZuMVIf@F#dj>tJ=%gj%mk!s8lZlYF9x`0X+aXogp2SRB~^r`OtOD&{So@ z{_5E1u0Q&;M`r*A=gDirwgHI|l*Khtq`2#Y!aV!MR-z@pGTk?shErGK-M&{ko z`Ho)Z%wn9VE9JA_JSIScOm*tMOlOJtG$(L`$)ZsU)r^*w;GxF|_`UB>EZE}Z_$>iaj@v=J*Q#=<3$mFh2&pFR*B_+YF%Aj#hP*y1=8;bW#Cf!=URwn!^WW`!JkM@Aa@KZ{MlADd z^Rq@+L8y!YA%}j=kBJGP*FTb)JPs=)N{_yOxCb011QS~BTM!u75189wo!AujX?b7F zl;|fou5uVQSk8otd>h}M&ijd3Yn5L@n-K<}Zv|Jv!YjtTYO z@1HqcK@=8dqXDO$jB>~ved3K6P~{*MDq6t65Z6+iJkzHQNAh`G8WP&S z{ExZNrHfgZ*t5wIXt344zh^`p5)&7H0~#4fcmVbnB#4)Gc3OaXlI<+tEFf${c2KtY<}3lKH1IaW2ZX!#(*SeZ=9882hR`@b5+wfiijJb3CbBa zp+hcd5A-oKEMgYbq`||dW6;l6VtDMn$HhIA4e9q=V~j*bo<@Yyw2;V=P$Ze(Nx=+YxB4@(@MUV?^GkKe7OPe1&MyqzF}Ma%jy zfZ@A?v^~rSP(9jFV9G}{gc;s7ZWjA{t1tJ3m(z)_0ilb$n!T&qRAv4+rFQ9P=)(5w zG`_KQi9y%(`Ofs$rVQ_4h0W$`BT=WVTr^=oZ84U49nmDXU( zl5IY~fodslaGf5Gq7TL0wm2vD2ufMmTI;Fd4;{2&%mgOg z!^N5EOT&38(BvMbi@5`B!yz5S7d$&Qws}%0hDm;mY%ET-w(5UI|KBjv2&Om($hSz@; z-jDYb*eaG$9i<0z6bZZzl;c=mT50B0Y))n=ps6H`_RN>Rq#{Zcd76?QfZ0yUZQ!O2 zbJk;%cRPq~pJORo-t&`cA+O{`c>}&@>U7i34Kb&f1WE^Tsk%^b_H5C5xqqCv)XzQ25u00vgW&da4-`>aDvj8)5 zcS5G(;-v(6)V>Pauy=}!y7OE;#)?6-wZ`EBswD6)=tIvTmEb@$dS7|~yac7o^t+SC z+G86^?fl2n$ac|m7CC(DBgF##BcCibj!KQ*UV{8J5)=`A9z;$+FYzN)xRS8k=WM+; zU#)tmI~Y#@)#t2A&{)hao!o5Z0W8;vJ6IWCUA7)-zzFA5H+frTG`)s2UN%7!JKG=-f^4aq-5IG} z(%dSpl)&+9l7^UMF$xg1r>E!n>FJ|%v8gP&01OVJ#w6C9#k1p`>x9|Ui_?Efp`ZBW zRT#OZo_Smu;grjUZg!eaS6OILXoh~#1=s{a$`>kNo}ew~91fXO^?{HN9>UhfMz|<1 zbpP7xt8NgN52Ao_3qlgY;dwLz zjAL_H{v4BocI5}W}9#(664eNpG}pyD|Q5TJysBVtJ0Z~#;)Izxq4 z!VZU)80ZJ$NSF_TLwB&?33ICV_Bi}uAb4zo3v`)W+{1}+1~m1#+MT+SJsS~v0#<;vBIy1dAMgCC-GN;;kSdI7 z@e9NfyktoqZ#}$<&>E~noo0~h0RbzKkYx>ET`_fLh;WNBbeb}LUzR;PIq~}UQz%QE z`F^$^UC3^6cn{W07idLUM;3tjW0>SE$3J7eR7n#N_=K_bBvT3*O`*%%%0&76a077% z-yit}W{gUxrHs~~E2Sq%JeUx$X@rTL#p7@ku)OTKLJu$JudftC4)!0$F?UacM@1*` zd22kkWH9^XH*;ne1lJ6JZ$z4~v_IOu`F3sF@w|_k#!$|kR7sz!xuyZeG#qksG^a_P z@Rk-q8n>&99oX2;cshq^3i>9*hF?=m;AcfSoeTJ4R)V2r7-^nDKk4yXzb zyh4l^k_pw{-mc^PLjSTmD)OYSNfzyL7K$1dLqJg%l$Cnmh!5?IpE#Iesvr5*BX;o?DiXMjuWYo_XB2nS&=17bR>At|(TtwN2gr@IPFvqz}^TM*350A|Ig&V!{qGHxTjm*2wD zMClt{w$5^u*OtG{PJUUu{v*$3FuMKRZAl+B#2V;&`O9wBN5=EM6|WStsud&ZlKEC! z{;k>3^O#5l<9@9Fz$A~YLr$w`+Ovfy*atOG1zlcG%;$>couJ3TA3#O_G(e(S-HM`@ zyo&l}_`VlrEXc4bk{XXmIrW@&7uo=vpJ3`{qFtsPXLkem{hLEci*_U8e?8uqAfS$T z*cQ?mxB3YA42E^OsO$EY88tKJitFx3zS=d>pfu9fIhbtj%m){q zN!fX3mwVmxrW4@$^_;J=-zi9-xWfjhk|A& zxYrquh_9sAmIGRyJ>qYm=&%cwN zgnb$M9(K&h-g0rcYxH!rmFCCiq62J9vX0WeD(luiZCBbow})lU4~1^=Y{4pxZ>SfWLP$O5(T;>Ub)wPq8?eND=(@y9?6q4t@&F z7i_o^a45p;813pt=|y)ah2`~Q2aE4eQ3~UKmju1u%@`Q+p!9^=Y9uL7y-A&vV_PJ( z(BqHUX*uzNHhMjgUlGF39oq(%O6nHE5!@1&2bw}cwdxLSP|fft65pMx8|;GVt(?H| zk#0gUv{5FWR7M}YJ-ij4_NyG=(a6!Y&}2XW%fJ0E0BP_QT`>Q5NqFiQTCxIQNGOVd z!_Bbcjdt_-tqcAabV9Z?GO=)PN|zV}0@i%^3p~AxW?mjqsClb9WE2wBajb5R#A00aygq%HF?! z{|*mf%=$ciPckSEEko(MY%uNO1lk{cy6#~g{;VPd7DqA<%c(W-J$ z=mq{jzrOf4u+kd`W5a%-)ejjNSv?P=Iob6htwJXB9w`uw!U!BbLy5y?3BjY*t}=hp zPE-h5k>dt{{lLDSTPqp?=XmC`$@+VRUggBB(#906LBREzN??^7r{{oN6K$ z?b2d)G*uhqH9~Y7TsT+dwBpg~f@MSS3)c?s*AvEqfFY+LVh&r6$&^bM^cbTXvG<}G zgx~Sc&sHgEz-=zkE}^Pz&6W>mpj6KTobrL&IQ>1yCB$p6Xiy#oOlEp|XQ}8c8a_$@ zK6EAIPvY-t?mmX8(P8JI2eAuAn=HMf6>J2D_UELlXIX|W9D^sGoPkGe{5slW>eNz( ze-@lUQ@7kzVhPv}>_LC-M`)g2vFH!d9YC`)aCukqI!p9xAyZ)H*!G2y*g@KW*_oCV zxT`W3YIN>RQTO}$h5RTC=RuC?0q1#Xk4Tj~#bA(;1wnKFb;MVEk`+_leV3~XcQR6U zXbLyCo5cQ<-H14u34;!9_2mx*sv zm}1Wpx|^`$N+`*ls6BE}vbaEZFV-%Jy`$D_+Dj~h%jE`u_3UutA1SFj&>k3YTwva( z;so!Iv#Do-qYv9rEuJl=4INelVi~JosyqS2_L%`~7`Bla_APV{5Zv#k81PhuiWm^e z{dJavZ692_=D%>TKH}(PzrWJU&gKYl9DE`!1HuS8aVo~W_zT_q4qwx7hN`-tqUF5X|S*F9^bGul!I)Yz4R`8Q)a08gP3Ie zWY`St;W#hOS!jR6-d3R?-}8}5g7w1uh(jmr$O7_e;~ifW5K8)zs;k!gK~)t$ThGtX zE;E`X<9c8>Cq}6O^0v3OZ&NR3DT5prrt;Vh{TF7RBcJ)!uMz0F2N=3kYI<+lfZ1)% z)W^}smMf3kw6TBZ@Pr&aAB7t10`>uWEjCTJ4AJ=ZTyU2A&F>$mw{Qy}NJ9{-1B%M& z9@xZ$Q>o1O)Y()BG{;$j3ex)VONdla!lk49VNhP=R813kiY_k*uif0v!w3pq6xvt; zt8pc{n|X87&f0)B@2~cC7o+@Hm@xL??4Wg&RvOkZeoF@J!TJI)^D2$Qu7N#JD!yE3 z1$->Hm@zou$?CVX3`CtyU0WFU2Nm{UK#l_y1ApbXcHX;KGK-K2A?Ey-)bGe(7s{S* zm0Ehi7Tht08ANh{*|8|uwB(dY2aEo%4X^G}XrqPuziGvR38Xn!v367c@f|v_-(}1hcuVc2 z%~K)p#Q2_ZgWGR9V~ZFUb{*it!L?AcXxiQCT9t+ZE;vb##ctkp;oup(z=&e#I>UIMII0lqn|($|LCuk?T(Va!!hZvtrXFoYBl zA7BKaj}@(S93nCGbTKtFye|T;0#)Gg?2~&;eP1{0p4^q(Xz7#bla_`Xtqb2Q_xjXBz+_ZAb}Nu>9|Be&K9EOx;2eX%NWyg zeU*aoge%?>=rkxgZ6+Nx@ZBn2RrmMyzA4%CrwRk~Fe-n6&I^s|A@9m_abO+w0*AP_ zSP}7|mIk}v@j8j0F`#_%XTnnD`h&aJh0f@|uYz1{ITkBnW*T_&q-p9GpvoBme$|WJ z)j;a_>i+MwrPQ{*de#mx3|)dLpq2gd;WWrZdkQ5zhlt)qNJqk0rd?Q>u@ufm@K@3sTxEtOY^)H<`Js_{wKKF8{R1<;IgS( zlx+W%X7e9lH7yI6CcyG9&Uac!NIrrLqETv4PsML8l?=cif{7?JPwu@?g54t*zvR!h z2XsdPNNJU#v$w98+pY;{i;jyOv8q9-j9vG*Q8WhiR@5X;N2S#IA9&2&D>Xt3ti%A~DQOrYS~{_YkZwssjExxmBJ+duBV;~6;O-&zu##9 zl=*e!@$S$-?lMkILis&|U$w4>o7*6v+xf{UyOEp>EVU6$MT%cod~1 zb?E!`i~rlY%e+UJ_7s?@K_Qqrbqu}^e;;-#hCY_LV7$qd{h`n4KuTP6aBFf%{-GEU z`VDVw!vr03e8eIW&wlxgxRLxc1aSwm;r8%6`@@2S?PB<5r|EFd$f*(ddmY zb4z#fgLCA=kNyov7H5XyZ65m~qdJLir*oxAUYRzyXp-F4<8#{`g?IGyG?SipbOr3Y z_-Y*#nG6wU%lHRhf6ZTynD%~CO>Z=ZUV=q@5P>K|qPzPIb;R-d>g}OaATlYMQMTT! zfbK7rP?L^eJoh~S1qvcep|$D->WtX*;vPR>4jpd@aaatyA8uTWLGG_T$O@Y%Q6K2g z`22!9wHSyxKsxHMF=c_aG@tz~27gs1;v1OSRTi2Cq!lXQ3ZkWoeJ~&#D`IIC(Dk|E zcPzO-S8bK(Eyrsrt{!R}=vGj6p6D|WIH~Xob1`T=AH73?=d)jR5scN6=wwKbDe0@^0iH&eZIRrl&uu^ttvX<4#3d2NlH|=+F~O_WQJ;Bb4fvFM-&k z)ffuISr(^bF5`g%N_D7^o=tP|E z|0MM5?6DqGIq=xosbM@J;!&yNTXF1-)nBQ0DIuTB2?3wKuiC2Y^^eKLZKFUvPL15q z8D4S+Bt$&twn)Sdk)|_@k{qgHt-=>gD%=_-(9@ws$*bi*p7lE39trf?56g40>4R93 zxF^5gN6}+GC%oS7dCzl0{L$!(U~TAWFyG81J3=#%Fd|9r;0u4AjQm8;(X-dK;EqMpT9*Aee+zmNx~=y)9vc`mNUsU>Yz0k;jS$lv7+@*V@jn z^lDPf`ptuYn+8a`2!rVsjQTwl^TBL+96fDE>G%h7gxN1S{zfjeMqX`J4mdICuEwct zFUZh~neQFfw!X~rdV8k3zuJLQYtU$Z*XSal#pm`5mp_AxHrl}@#w~Q@p@kiHF1BhX zDDUf$b1N{K%YBhT?t`3Tr5UGuxL)(=Z)F%*g_GE1?12*|rggsi#lfz~=E6qOj%^5T zBYxYNBNAeqrPDvx&~= z3?z|Id3Ayix>2%mzao%`jduH<@}BAvI_U&vKZ2NvCh-Clu28EUC=(v_oMJVd2-;dDhd%4p*E~6_zLsmfQiF1>Qfo>HgOPKKVNzD_B%%&Iw7UBkD?%h5BMR860)$@ zm}!ZCirtPdz>Ma%_(UVTe>Nws(wC3`CQY!YW2Z(%?yDiD$&^=HqLm1fu#N_GA{txISKQ*lCs(T=4g~*u#(`q$Pfqkl3?47D!>L zc%FJ$67)d(vo5do)e3;|7oDLoqOP`xi7+}*7u>&C%ohvy9T|a)Jwc&Q#TmX0850}? zID-}+`N~#>_SSxmgfdKD^wX;3C?p(hRl9$5T-DQj%#m=J8?jO6xccrf+S_bJXwpUF zYi2)(ZxKY%R!Z?no(5tMW!p^d&J2{@naKtnY{#kcuV~PUKZv1$t0!k?{U7fu&W7%` zmiNcr4ksK+78H-PoUK-2_SyeDjEu^!fhjNPIj&Kf%4CK^uFLmYtV>j8zM}?PaT@_? ziiPnKsu0~KJ)Da(E0vFY2{;Ij>x-9n4X61WvdJH~mfp6~?)&{yh&K}2O{951Hx(;@La7axIxF6_kf1o+17-RJcT zuSM1#BrrD`QuTsanrA+vyX+#1Xam4wu?~&bmY_1t#EZt zKJTbfzW#V0QY6d67II%)Y$9DHd~B!6J7PrytQS6rH3zcCMk|UaafRLkV}mR#lwq>s z70&H4qsxDyu142aqGD>)4d|#CdX8)33TTV|&@nuYMo|!4Rk@F>gzXocfoczy>~j>U zBMToKAFIW(^nP`&yK18c{z)1=^qF_V8d+Dp$W- z@$b2HeY5X|BQYQLtmaY+Ui8|D*hNykM|$LJ+Hd`lS2i4p=tqn!^+*t=%wB_Yr9qR2 z0AxIFvbzB)r;z8@Fif&{-a>(491F)Ip@2A4|(!9V0uh1ebLP4=#8})5uYfX4MlKMHgQ>}gu`i*hx7jqJ`1|-_UV-I z$3_2q;PPW1_Km|!8#!dAo>%Wk6IT1G+Yh$RGZJ-bD0pAUl}49Qzc(1{?lueNkBGrA zJc!=2o^M`WQ7(z}K2x@eTI}GgcYJ0$BZbSc+GiEug0>XW@!NsyecY!<0=FpHH96s| z4KG@blpiZgwP~1O@dFcI2;%n5Hc%lro_-%L%Sb@f5)Qp0+wLSLsjH!(qBV)0NL` zf9Vk(qG%%$Bz7U7Wj^05E)XpJWTF(AHar0*Ae@*8-f@hqK2^C;DzNq7*O#V+YdS2V zVo;sf$r6!^WqI@%gR*YUJpq`;2#Dyx&_MZ*`60!10_Yv2x~~o0KR#?+)H&$&KuF>f zKeL~B1zkCJxr{*VG?4)#!ID{-bX?ueE~j_e58MHedRPI@##u zu41&_JF<~+;$6>CHkCGiJ)9-s(Sj^ILjOo=PK#k(%ZYKP&_j0-u58e-ARd^z%GN-N)@@j^P^c} zpQ+1IW;Gz(oId0;YV-~Dk*k-LGVS|t4}q+v>_qFxD4i`QtA5LM+q?+XPI^0*p0_<# zvPDp#pp0lViXH``w^mq3n@{kK{L!$q{x#t0J^{8j#G>cO^%9V$=>$Os1@Rnwu7AmJgEoEx#_5+!I_3gngjIvcvD9xGZNU?=M z#mo|4?LOgR;j;ZZI!%b;Ldc|B`AP7dZx91_{RUxPns`GE18&F8j$IgKr73t30G5i0 z--(6hzVTsTU}zn=Kh+sF%_KNU@zdex^Y~d;xW0(L zjwMlz%}C?-@e;Ft47LG~epm={QJl^)gK{_&J1O^%lccq=@ELoO64G~{Pjo;G{r$!T11_w#RiUu5&+Z4t9C zjK>+yHO{~8%_fqrUn!Qyv3a~(*1|8!IFKp-5~50Y_cKw4uyPchQSqOQPE1T3rUkIh z(SZ`#y{)kZF0~xtO9BDVo2VDscqhL|Q;!EV%g+3&tsN~XuKt~;l7NQQkFywFTmq3N zu!y7uhx6&*IklbkmYOR6ylkEIm1Rf`!MAW6F8WX-jl39W)`|}yWn~C743XXB-Vkd8 zR+?xazsLxUDC`%+g^>vO)q6eLX#a4}sv+E{xypLB2Ffr)s zog;M~hecmq@W6XbeD~Ne#qHSFU2{!_qx@zY8~RE^zCbo_i4QWUzqhsw>F~&?S}XHV z!WQBl1JhYD|MVzj{L%F;+m_yE-EazCOV&q4Z}q7(D1Hc8!q6>;<^!qYfn%JT9{$F}1z(CbEYSNphsWEfX&Ts$rQghx1RWHoLswplyR z-<>IoVUrovDtTR%t7=hbsQ-^k;d^ypRvSRHKMQ+HA3#Yv> z5d}-7jXvk@B=4gKiQGDGe2l&Jf*y8to}*7wU~q#z6oez$9n--prL(~QUBF+!%F+tU zyeyKsTE6urCp1!Dndg0}6NF6`MRKPfkIjc|Z>x5Gc&X3AGf|q6n%~@a=j;xj<d5!6p;s)sfUzIIV40m(y&s{^(S~)PkzN79Fme1u@V# z-#4*7O0G?2W(AS1DhI6%5^H~$bK`1v=8cz=Ap|}WQ~$=wYLp@9-|uIQO)9sQ-ZSv^ z6Db;9fL_e6?T@zlIL*WPyru?UHT~Zg$Gn=p6zz^T=3@`_v2QI{^3>3KyFtK^($y6X z-CpVA;N5e1 zi(rCNX{e6|Ys*|(AIR}2;^Ra{x5jf64oU28lugfa*l2FIM05|SP6zkkAIBd;kQ1^wfS?0o4@g((fmW+w4NWvN;0M6`ER&Jj$ z-r5ZW0f6ZpXbC#fa6~8^=O&I1!5tYnZY}fGvEs5UoR54KIPagMilG^v98UOzGDxaq zrn{5CNWn?RSqf&l_n-ofeCoA$zcoiNG+@5H)>>Kc2X5UN!>&_`^1AvFwGAb?`~w%& z;?rl9Rmt?C+SI8XL+!iE+dijx5=+){3E%y-F6gl+MJQDmCF%^>6HYYdyW?2G%;r>GFT2IW&gFol%BUXz3%*3nTE$(3u zCLM0TS&S0>>i#Iq4|iXJZtD)%eeZ7vk2LPCBshYhsTA{Tcyw%$V7U=rb4of{G2v#0 zS(r2KV>`@89Zg+}BVso}##@qC1toIAu8(t=$BK8z+bc;+^T_^e=@LylgIFzSU=N0WAh+Sy_y;B+ zoSVkLnFVl_dU;F0A;+iU#}r_s2lEqCj->Vw+8xN#hQM*4_hTW$(ICd^7-q$X-)g`q zfumn>6}*B+VEq$-!wsSuaJSouN^R(Oy&xr}z8XnwY(A}}xn$OFzM`7&wjFKDcRoc( zAVk7gVk?ONmj`?1mnT|Y+TD~p@dd^$5(9#E-rz+=%9>30oXX?S3v2um!N&30m7=DS zqgF557`I@?&QauEiBbBB!K1O<8Ajr;)JeqjJdvy0+=G9h)b24fmJi zg_#Nt(;)rQama}A_f#FlVzlM;CtBQc%Q36{vrE^=>uRffo7XZA7AaS5BfdmYyFLqz zn46meb7d$)aIbB*<@%C@2sTu5E6`SyP*w4R{MU=%+2$xjs?*p@Ffk}YoQE+HFs~$>#Q~$C6}*u+ zKKAGcg$RlmxJNku5m&Lo2F*%TyAq6-3&?MC?lGey(7@+m<|d<<+?%SLaO|CE4@xel z7uQ=h<*9*8cj4mIr2rEqHTc4x5_R?g+RuJ zBCwnho97F)kWs!`StN0r*vdq&_qAaT&a3a{Dx*($A^Ock`%?Wccmhy;aUnkeoK8?6 z0yp6u8Y-J#8 zm4UC;GYIY#G>5=~#cg|>vEYx5a9}}9=Hmnl;p^U=o_6%1qywqHTfgiQvl>7aTz>xk zKF!&$S^>vfDjoUx!rGs|bVi;tdAiu#^5WOxRn43RX_$IX2 zs378bJigz)az{0kV7CY?1=BlLzdA9H>EfH;O*EMHe!{?~v7R{*P9t)3IpLZ8=N^KO z0jDG)E(T}Y1+}bEzhLD&J!17k^BwpJ4{@~V!gCE@pD)QSbcG|pj>yUj^d-85$tx9_C$mZXqPkf# zSL|z7M+YkPI{}@6Mp}qZgnr0BJVopQZ>ER(1EuwrK`21TtU|(3VEpOAZxqA{7jMqC zz~i$>H)^^i_4ixU7zdsfDnrnP^dr7u%!$%8UWBY68|7Ng`>9E*`c`w!; zdPwI=pHULF!3_vlPwqmh@ed}G3KuX|d2i6tlQH4veaOf~(Filun?Pdt^BqzagaI&~ zMwVAxVEHp^1~bd>0!~)ToXIB-8^?J_BjKf+fgSNHukSnb3bl{fwChZFR!@gYQpVw6 z#IxAjp0ScXqTjLnK6w_0$P)DMr+>}Q-+?$&chuXbjdg+pp~P(JQg9%|C`HKXP51e6 z%b#Dh+#*cI&0aBQVsp_*2lK71QWRo87s1ow`65QkZZo}aYiohU+z1E4Al|1_K2HU3 z+Ipfit@bKwZnB&tvsyF1lwQ(%3~Ft;a;WUSS^v#X(J(`mVU@t#Wru860)sRPRnM%r zLM+RTlg<7lyEH^GGe4jH5u6P90r{hl<#jv&>7b?FnaVmEYW;9eCm5nRPf*agP^kqh z4~~{qS=-Tf57i`2<@R}gPOmQr1wzOtmauyFa3k{r;UIjSm8SJt1kQw1zUsS05EJ+R zc>C^us{i+Y+p&+8%{gYt4p|w;CR-Vqr6OeSy-%_;5;9U`6B*fL6(J#em61Y8vie@< z{rP;ppFiMxyM153ybhh`({nr?*W$lEg3bW?MiTs-va={;i!iBX``)O?cEQYf)4$T>J+H zCr7IMio8HiYJfolMVV%$`Bef5AAr?{rQ&JSV zO84DWcq!we+zB2W0FMZo^U9to-t_3+^*H#C*IjLu77RCF062*JESG{VZF|^1MNTmf zpyp$=AQSq_(cFYm_QAB4&Q8~^PF7jV>t^o!{CXMy1OX2DgZ(`+B<6U4CRVAHx=RUE z+Q7sZq^5d$-snrNEa|`fj|Qr-CrjVwMO9;0>N$RfFVoB}6Ywu^JXH({1jZoL@4t3} z%##h2z@|myvx>wF9NwAleVWA5t35gpc7Q7T`fd3rMKbVLG#d zF)GC>!RU6jCyd<-z!iN^3gP65n))#RO%loqxf{9Km-z0HKyvQW zN8wd__mC@acV+&|M3xS~(}pT=JS*))$u16vb&y3?#^3!fPb#%*!3zJOq&!83$OZNy zz89wvkp;f<;>x)^_4*=)=*w80r|JE}-Nexfm<7&EDiGF)Tk&uDLGl6+Q$EB_YO|#T zpw;=n2~Q}#Hi3T(Ea}Z!L|u3DL;4=}V`+IJa|4ytfoVXufOCO@UeaEY%p7j8GWs8ETUfe- z4N?<1H1Z|04*%P1iw>M%S_6LzT9NkAzi&>$=}c02;z~-U_BUTC{$2YGn2zWL)bQ$Ithy)*yj&*!_(qOs7wfPSD56`*FP9YI@;DdHiI1l9W!$&m)i^1 zP4imJ@zxj3C^U_`cQ?_}?dcaQAF!(zM#2hii+1CCXZ0`oQ}?s@iq+mN~J8i9Zen9F}d zq?cA#A1Q^MRd<|O&c7m@+kg@T%zwgQXluSsse+6Zm_HdgPIGf}CA7CUHu}K;7*4+TKL@a{fY;a(3@4a^Zje90 zGzaQ%Ip!Ii4$!uN5(K&Tz%A-)rT^o5)|+!E^S>@=(PUt_y03QX7`P z@3SunPLMg)t$)aWHvp-YX`#LtbnBtPJvabu)i`k8g7)dqV;s@|UwomTS5{;xgj?F- zi@2fr>E`FQphox_uBEKrVE)=|u?my7w4NV!-AFhSxY92T1m|Pt+0M`9sV+; z%GXRE-iCP(Cxkcj3{D7St80b&HIPuj2`wO}ffGXSy0rA%J5afpmeK>E(*(|b=fl%V zI6LqmP2lXnsV_Gakn!IwfV1P6$Z&QjzHYw<=sKB8$p(IeZrBB$4#+T|@d1AhKvq7U z|9GW57Ka}pO%vex2O}ODv~cI{o8x>eEIv5p5nMGm^t2?LfQ%lpdEAd7$FCFEi{w}( z90njFIygKvF4R|U@4MSyZT;p2CNKPQ;K^Za!pTm@zcNDsWe%fT5)wY8@cZjx6SS4j z;KPAbJ8{@bFB!ZEn80G~MczO^gdd>5%oNA3E5qd3fsjhD>}?&Wkm2_bosHlwf$dvr zC~yc&SX0x7F;C0kYcRBNUjxk?d<{l8?qNVV4-dl}1Of0c3i{!hRorOQhi3+g-3mAc z&`UT*BJ3;}jxo)GFP-f6(7;0gW)bccKqiKJ1WTRx@Crb{5l`+z5UmNvBL(3A?DK>N z1vjr;E2YPgGbJRfQEEQk5ItE~ZWu6`z=IITNZovmHoh>8O`ue&Eiv9;Kdw&w?5R?x z{Zyg(B)#&Ioy2X2bW_M%;mTZQU+Zbant`L78H`aPjK)_zSgBrO^6)r$czVi%Lqe{V z@-)49!*;@G^JRIt@V*QbH_2VLv;=S|4C71HrEKs4umbVJi`FpARba`erI(PnVgjE* zK`v${AwiD&J&vvLL_#7^2R=jTNLd)c4$pGn>6JGLWE;4VU(rFINCnq#2>w-Y-l4VoG#2KxM4u~agRX}EXm-H0wWzaupz&K6FJVlu7n3E2@+fQ1EA7}H}o6| z0yr^v6TXC}3acaxY~>GTa17+iaDojU_&X2+li~Ru=EKoA$wZ7?guUWe31|-C+jiz^ zhnnD71`mo^gPJuREc4*F*!f5^2)?%Pz;cyw4=;)11{|j1YxEh01Kfh$u2}-;gi6SL zh(tw2qdIQhffIm`b$+SZ|2|w15a}t;;xEBaMS%`2Rz5N8`F}>If`b)5oF8N;lDP|D zLk0Jxl_ip=!tTKjcYk<%{O&PcK1^H{C~jfm605=pECA;Y+-L&O3J!&Ui@QHufQPRE z$d7vr5ZB?4vP!#@!eapC!1Y3XoZKF6-U*Toc*vsdhZuOSu*|@96hPY#PH0?+A%+tI z*&cU7SnXj*cUt3S0l*lz&poE_8PGzQV&N7;kHJ(?CttA*9_1}}d=CCe^&#2lqH?7`4#V3|}&@mhO2EZ?Cfm;wD8f zyo-q6r-Y70sXk5m7YykhCq|iJ&`X&mWaL9zw@8_BDR1&W7;}sP^c!jQ| zVA=h*xQo6@uoQti5-$9|_NidO`u{Um_#2kr3851%P+?Ts?o+V0<27tn> zbTkE|c3t3z2wF5Zr~m-?xL!B{-k~f*sv_x$bY-J3V9WyohB zE%(@Omm>w)IuNQ^}SCx|G%wRFUY2DLJ0?$ z^)Lt;p@xd3;R*$3NFUKiP_99m>rV(`Dr=wzgA3{y_>cj(#wF)H!1y*0fJj;%4M}Ki zLdpUdN(UrVEz3lZ2fh9AdO{qGK_PoAW$RjjtJz-c2m{bv%?uCgZMggSeaMslgR2-^ zHN%CWg~{+eft!N2q%O}^%W2CTwq2-2={<|I5}fY#fg{tb?% z120^V3;*4E%`tEa!j)oP3B`iw-Q=a_dFUbnzw_;lcbWryfCB^bue`+-(+>~7?(PTu zFQZsc{(#>KYsbq*yI?%i;m6{)f`fyxEYOUB%FJjG8hN8faQiQ{+z4Lk=15mAP>aWC zp`w|R3$P#peQ|R-!it23^J-l$FNLzNRVet6j(Bs(B{rfg0Yg~nPfiX;C+5lf`Cb@K^a9Vc}F&%%@EAxx+GprRf~pPqXN)>FBlZFyeglAe+Rxq<96V#u@C^fp=2 zt+75B7seH5p)1|m!=+kAoY0^f%oJvmBAf3w>UD+Eik*`m4F&5dB$cMEmunrzxLGM~ zCdwZ{HV$*lZMp`Hvrn#uw_2ukJ^YBCg+R0yiLN5sGsX5`(z&y6OR{<$&Iwmr3`~jv zl#SVsf=m(04bDI)BNP}1$)Q&2S1lO30F&X5zmyuqI_Vd@5YyBJB5DT!A}^2>@%H9p z6zEsVkUdK=ApJjveEC`{0;lyhS{jDx^T}CkShAJe$3W?V58E>h6sk$%-nUD+GD3h5 z2yJPIBjUXg+=QigfvnBj^KYiIhiXVXu-YduI(}A=Q=MxSWhYPyg-9V&{k1ch$Tj*X zmaM^XYz0zp(=mWP#1czeVcN}Qr@YUTXL=hS+0$a%Rzm*5|jiHr5qmMSGKUF6tZD6+gystN2C9YA@Wv5-H(K3%xpv zZ)2$prRKolkbTmcc=IRD!NcVrbp1>Xk)g-mFF&D~LG@Chg;c`+kr^d==O+H@T63&` z!N@|`$FL1eaY$gh#%C|aLEN)DBP1j&WDwNKpG@zO9hoDU?|VZgu8N&fAztmjgDzSh z%6P|^!N@2EzPnqyFxBfhWuMXeY)_}Ud&iL15hM~TJ^L^3XDw38n510L%R4Wp0tQ#Q zV2_TBP1*Ic#Z~}0-dC&HkO_uM5{Ny9cXa}2~e2vy>DVwS{9&v06!`K0cGh*IQ=0 zc8#{fX17oiTv#>u_K5#Yr73S%P|R+OgN(}?fH`hbB4u-oK%p&XXm^ZHIMLS85eAC} z62n+bI7{;jl%3%%q4Z7kEk{+SFOEV*dIhk1u%ei@mQtOF+M8V%h`<@Jp(q!fPjj)-}pw{rP(7(Pt|9AXzS2X(KCD;Ebu?8wj@t z_plv(ij~wMUBXu4=GD(--%!e27hFMW_^E->Up~aWr3oooImG{OCdINh7eDx4sa3N? zH%QjheJVf5Du87d)aV;F>76L~fjix_Zz~GU?~Z?ZbnYF<31cKt!f=JyT%<<@JITN# z1}#D^{`3%6qiEkK1U}2$(aJ+ne|EG=5c58V>{{lqaU690Ozhb4dZw3q2KL;Hc|rkM za~PWJ3|hr0mF>=Q*sY+hied?zA6M_r<=4GMpo= znEP`fNL1bVw>==a_Bhva6rCCJU9tOUm(o?7r1zjpbIP}Q(NTzjeJ3h#u}{;|AY6_}~|Zcj&qhWmq; z>c3|*V|3cz@fvhexrtvexgnhjBi3G-%BQ!%I`wlL&&6PhGdb!B9^1b>By(s*s0N}< z=Z_u|8b^2PP@hSDfAc#z@!AYt$u|6oLtT2Qpsm3Tr$68cNXKPp=@!qOi5K*lmZ~dz zdk#Np@Bu`lnV~a5axZgBA~(!Z;dUBtJwor+E6*Mh$fwkUz{MM7)apO#_xU5ZAoo>* zkNw~TCA=dQ#F@>;@8p#CBfP=EYSJOrD((QH5me4qIDOp4NV-j4&7_a&7S8d>cjR>; zuM-wEO8)XLQ!iigLeemlWw-B*cyN5D)hqU8QukrhM0+@q^Zb%%z~UAK;nDt~OyP&Z z(XuSLlzeAk(SUjl5iT7q-Fbv*hU)z0>Kd5$j)BH_rX(^l{Fhk)-c^cM@#HVUbkqY3 zuxV=qwT62Oy%0zZ>qb*y&3cK-lPiX_DRdG~dER{#JYb#M)X2V~%`VE%uKfn9?{C)i z&M8MJtPU@bHcgKYUJ)9RJDz7J45|zLGQ`+6o=cDiMW~8I5Zj*ehb@Byva*vn%*1(t z=o0DCqC_2%D0r!bS+4|iOgo2NCrXXdA02>mWnSB(#9`DKXgFY^LaHNV;uU%5Mom7X zgrQr1mu0|5+kpOdj+k2%9Vxro`x2#={9_)yST8f2I!x_P+;jt|6@Ob*NrS6%$>@hz z8X+8wKLqYAugRqXQ4#Mcvv8w-k3Wp@#ju6ulzi>x`%0VfBL}4bRQuf)Qc@+Ymj_LAJJ1BdVri*eH1f zYwqD`KUY|>!DDx}3SBISp^lD@y1N@^@L3bLpeX$fG`I4WH(cRg-X>h=Cn&r*QF*$R z1WEZux!(QF-(jevT?K(&0(8+{J`!^DYuY#L0Z5taMoPMl7e^^xjk8FLVjY(&aBx_m zGnXS5no7p7Kop7RsxiP?4W>!CjZi$mfo8>(oz9*Vg%1UcU72nc9K5No(tb*wN?I-bk_TUst5%>yn1fYye(qF*| zw|W~{uV<%gnx(j_UJl8(hWK`E{Caqt2SrfIm4KgZM+v&Q8)M>PSZ4tAA!#(-QZerc zy|Pyr0zo;w4B{`s6~wb}43G$3wolQREg&iX`k5e`@dASWSjW%Rhv~xSd(GR(QXc73dB6Vs3fa!d z*|+;j%2CBwm2+lOyaYk}SV`#&uO6p)*igPDWD@xkavO^LW#ILkXxt>ca>Z@qp(R}b zcBR)tCjT`c!Zsfb{FRAz90_Nb)uLHcpEdKP=rf9TgV5WE0wOmEf9?;Y{gQ98IP{V( zh$}eTp87t1X_L&~A6|lS2Zi`QJ?lZPA%#mLMG$Kb|NaH1_rB0}k|3JrHMZzp9sti? z2~-!umJ~YV{Er$mV6pekZ+~Rnb@=hCAGC&^89zvK949)eqUw0*%u%Y;oNx!;so(%P zK<1jd-F;76o3jv9Nz=+)wSNS)NR5j>AfJ!-KX`etj;TMz{98PaZoP7)QZt4Ml1+lA z_zxGKU1}&=Sny=L@Xxpg>fU#bB%|9{6ZlK6U*C{{+Jvlo+Z~Vj-_wbG4&g~f%8rHx zO(=Fu_ll%b;z+2(nh46&oWU`@BcSfi+x->mPjp3K(sQsngoACouQ-+5r7%JnJcV*q z`SWH;)7=B-6I#N&C;fCcWA3Z`bzZcZLS3q@h=`zO+6L#7IPe@0ASR411=n84$Dv@> zpG?6VN2ZF7Yq8YKwKAbk9==I~rVgypJ%P6+**dL!WM8sKvcKhWtNtE%f4ZjdYJV@# z%r?%Z8OkVLt3`c%mq8(i#gp{(diFsso!&e&V7|eeNA1V^>^)wXQWukV{=(H?4Ke7m&U2McKO>RIz8vIx>_t)#c!+kC95^;blM@r6NWi#h_s<`2)$H_t>3`k4 z*^_UM_EqHlwKlN=;wx8-)JSXYlF`d4=#Z^EmbhM#*Mem*QrnfT5>;1k0eHV;MV%Qu zChmdq4`wz5gaH-*yNFl{0L87WNzwiyrHh^k;PL}f!A|1uOpV$irN-U~Bfi9|AJ!sg zhM3D3Ue`nI?lcZHuQXWG8yI|m3U>N(BGFb(Sr7S5zuO2624AfL)oPxB49#`hN$0cd zoKn&C?YM6OvD3OUwqd6k;~!|=#>ZLDz0=h{aGh(St&DeGHmn_b`&H`RgjlQZhSN*q z8GHH^LYA9R(1qmH&o#$xVBUWkB)z?A>k{uP_e3>~RZ1lTdR|da-UE{iUW4jzaIK$rs5 z#c<>!J*H9Lj{sD5Igq-i(?rwt_w5`BPK^5~=6MtTG{-WAzhe4OgC#v>d zFZAB%B_ar#P{beg$GZP0zbW&lC4;KPGR3R6>76uP@?e%%FCbVi8mEWAo|` z)oBStY6uoxBeX%EDlAN%AM+c|QLkga9L+&X6_UYVFpP-)hubSd4&j;91o}Eb>x*^@ zkzcV!nv=vmYtHK*WBvC}G?QVm#{a1G>gIOsL(g@Yz?0%%$>K}rZ)pY)S<`$L1pBH? z#)wVu&sBXJUlRh}CcRT_e^*+bPkC;C{Tj>2we|ISo28Vq9QNjLs@!ki`5tNpf}duu zT`b}?&uLY>@>;d4(qbJ1LzZVaRoKIGZ#%s{1HCLq7Uc{b6{?y3ixjCgSn{Jf?}+!0 z*ZV%>Eg1A?Ni%1pfimo>)40&MrgvF~lgZx*^uOL=s&5ZFGR}tv51ca?O9D|SS?JVv z=pF)rRqG;s{@079rAU|Rm^bp;!)o{A8N@Y6oqinU2an%iD#SnfJB~mbbP3p%cjY@I zjiesuVhuhc8ILdBcfs4~)m*6z@OxUs35dzfLkvIV(jX0ooMcE~;G=sVbWJlf&H4H2<}BxZT>+o-D)_HVBa zA=w7ncyJnd6IlQkaa-w-Pd!y4{?5@wR{vSPC~-(ezCrf~$6&_AtOhqKVi*%tUrzv~ zFjY}G#6eNtf;214ih&_Pm8mQ@a*#A&q7{8;%%zw@{4Dwv>g9B}ojGO#|3xi$RZYl# zBBD5f;iWoZYk2wOj_h@!3~+He1WFk@oeJ4j0N}IGmq{mKbIJz2HqbMIHYz2w5ag&h zG)&+;dDU3qqkV%5Zf@)(vg3G4;ai_8e!j*h2?F0IbJXT2DG3RXrAV`s=Y0u!$qot( z4Daa&15Gn>xyy(h*d)(0wkciC?CWZnQs2O=|xAGy9YX&MScvUS3f=7@}Wa z1|oz83FAN%SY}K{2CyxUVj?C|lIu=;Mox~n9D?v!p*>}r&oT>2t@4Q!mtwG1&t7`Z zU-`K3AN2XXt|U@X_-@v)lhxCe`D-a;nkfhBRSRzdf8SdU{_D=hNnzCAqbkx?aGZ6p z-&&cEPQMru7$At$2`_P%YT&iP8xh$1BBGjL3EhgEt<^Aujq+0k=4yC$4dYH5lxXrV zA8@#9?*q+T4x=^X06KZ#ia0bpFK&PF(c|KHpQ<3szX=%HNH+hlbckY8aaLAtMhqS}VHqhUn=kc8SYEyWN&+0aI$*G+lmPRP_!7lri2)ZguZR)c{8FmS(NB2= zfT)ed7l=d{MluyUqE@AhuxFTJfh?G3GS3jDc8;XP&;0)#Fa8^dY1iXkTXBDmV1}Jn z{5-hEmUj=8E@K#pWhB0YX4Co%s8>cGHeBPZ&e_^yRq*>FH_Mn7~6Q1O9xU&3bA+HP7+w zm|jiHOr+WWq4LwZ#ldAtoI%Xq-pU%^_i3mpGLK*iwfWnjif$$#Fe7W@pMXR3EB%|i ztf57(=C4xcDOA?<6O*2V$2p<30zAum$4nqmaUeS-!^`Sg(ts54h{D}_>(DJ1Vr3}_ygMUYH8z{+XQy!D!l z151?QwX<*qw76iiIlxnyx4q|$g`UP9cFbl{xBZAbRT8|*kNiC?L`83{;*4Ymc@Ex8 zMX5jvxfHq8Ew#OveBZAoXg*rXr3oeD_rt2{eRkGXj!Svx`+eH3fFr?B)t**9CL#po z566psepipZLtYJdg{al&kGe}hfz%s8#YE&!ObpgA`p9Ir{lp_y+N+QH0y{=-fa7B> zN_rTc)bJ5@$puaR_1)e2saY=-F~B#K-P~m5n)LLkf&wgdRK%N?g5H9x#?@Ei34+8s z4S)f3!Ds=b&P_5Yzjl{Z17zqq-iJnXT-k8Sk#^+{1>zi#TPrcD-N5oe5h(x`%WXE3 zE(4o#UD~tvKe(aI9{C6QFkyVOU>{_*J12f`CCXi;K=`E0wj*EhJt1*8;`;7SFxrW1 zarNxN5(SAMfVED+VO5p$w;v4@S?{xD+D(W+CK z9WMK}&RWJMcU9!@3=}gRK)qK2u<56e&LL^#4K>Z({Gu%7N+FQOfMaPdsAQU~D(sSh zM3Qyh2*dwPNS~KrjS@e`=cV%$p4ZAtu;;mM9Y=)a-^&XLU0`vj_6^CK1eS}+NmbGa zAq5F_Py^WmnzzJVu-io*uSNoW%gwjH5C{a2%CcL)Bo9jzXNB}3cRMz{>-3?}@d%%L za^X&sdlz8uH?6*~cHKR4@EE-Lwt_<+KofSEwMfW9gui|dQT_ABolP((Fo{)4XZ|Ct z$Z8}&jxH~f5%)i7lHMh$WJsq4O`--YzBS($Yf&SYTfO3{Eto%lbP98;MUi*r0UTH8>>QtuBIe%7=iq4?I~)?HZedjxE{i@IY<1Z_cKS~9^HUoM zHAQj#P$f1WLks*j2qz>@Korz_8zU18qGo}bHAvXDfgn(1tJo#8n8`g-N*)0o!Q zHQn0=shDvUh1m$gh7U<=LmAFM`s`}tacu*Cq3Dzr+yH<0`oU4Q_$?BRS72%mZ3cDA ziNw(*p5;j3^PLByRNuGERXuIb4kEr;)nE^#a+aDUy${XOfTjFBS^ODpRL}vVTtw;0W;X2JfR^KkmRBZyPMaYld`NTS z1|d~a4k?r5^|WS!zhCHj8Ywx8gQ1Xw{^c9RSbUroOhNou@W!FRFQagm|C)yLYb z2wf{xTONQ8viNil_BYM8oJlV>HB)iOF{sajbyg zlfYDb`Q2da(<7e9)8E4Dk54j>W>mfxWEZJJ%g`JI)&Uw<0eA(nrb`%io78#26<#LQ zH%(sb58GkSn+40K$v7d6ke?WV#>LgPV$fx3{*%-N7(8l`fF>m#c#i3?Q6)5sCfs59 zg+lbl3hQG8!8GH+TWk3R+b$sDP)jX&L+q635) zwgoJSFW~`j7VG z=T&(ZF&D;cvp?I)_lhsLDoAPA9PXo8UD89rXYmVX!1i^Zd%Lv%rt4Dqb*;iYRWRf3 zW#iC*dj=5%#AN~gc4JvB%_>AVOMAF>m(B6YPn&iu1_l9!IGK5&jORP1JMYi5j2-~N zKsQvQl1|Bl|GUCnB_$>1$Ku@PCBClYgqNb6SV)Hr8HPv#x_@k~KZpk&)LrkCubDZY z&eJpxJleIxZFXoVzOC#FOQ`J5yBoiLq{sc?)?_A&F-oln_r-K;XHKCh!otErKmGut zHe+%2LdbiM4ix3ILQf?{lL6JXIkQg`q^yW6sR^^^0tyE~wB)v^RE%}Fh=t*2+eQHAIMHLiOKFwIg5$R|k9 z4}^QEL)2b1Wo3C(oq_u#VMwrDT*s#EWoRj1B1)ZF1V@rkibSk$gBh*iwYA)vP?K_I z_c}k3xJ^zF6`!&$AkzzcV(I;ctr}y!1wOy6m1cp%m$p9dV$|)+bN20wCdi5b0C4XJh2Fq7QL0@aukYr@$Kzq>yf>DZ2PZ@bFm_=qS2yNLSy(y9N+Zt;P7e(1qbE({KW!%=tsQBHe@~c^pG5LkvnwDvQb7hjSn`X z_?sY#W*PN8cYI{?k><%@s>~k10=H$x`Sfy5OOnMjOtXg1qtf|d_b;H3)GP~i14c7ZjSxaOKjik=x&M7+Mpe{DZjg zbZJ#>)x$DRP&NAusHm3XN6~F3qe!v+msgn7=Ix&EF1H{5kl&mrBCfa+8bxuFu;!Yl z^5e{!v$b&ef*eG5iq=nn%Vl0L>6`AL>%!;gm;1?m_{GuJY|`V|gnR7T6UurvIl05i z-hurmQqH5qz5QEI?M#{1vQfG}c0_IFts1Vl5MTO5&?FxKLPlXn35NS(nG1L}OqKlW zh^?t=u9MnEN%3xyzsf8|#Q;k*dv0+`BxvdeubsT+w$E2Bm+!bP&*4^7$VSJuWm!v2-rzi+;G8`*U=+a>cHj%Sr^^1`b6 zqsn^)WAtoR80?j(<5OtvaFq}WNS;=(;*8}*S?>z3-sov?{#3^j-E&oWKQVcNQP|w@ zz@$kFaCJ2Ofio$rm3ksFoRdHMb^s)yT>w`RMhD_RBI7vMy6nwo(xvokz5FL7`Jvnw z=1ji{b&7T@9U_X=t#3peO(-p6dv@1zq&W#p$YoVRUY0Hs(MihP@{efOSgcnpcLq0b z;v&rs-dxFbkX>b5ze{n z+!f>!O9E;b&0FBv!xk8CV|Ct4gy9d5b@}hQ<7rgE>nn5f{aagXd<#EbeQ@x_+1oKX zx0NfefW)ZY400~ldi3vPe^^Rqm#v-#>-l&1Xf9p0QCu!%U@VSeK$7%+dZD-clG)SK zuSpO=tZs}+s4-d0iFWTfE!5;=2HCR!`XLv;4kVYWWKx>}U;+0|jZQ28Q9?_j~^JHNO3u&9vN;Cv_Obg`g3 zpiF%A>~$(IsTA~XeWU6&!2Di|4XrkxFGR~E zoT*LD&$-LHD!-!pvHq207^A=DC+~2@07TAAjsLu*9Ktz;^Hdr9r$nrG4+=08zdzX6 zubPfuM=_;gqFPI4mrc7NO(DHIs#Ov+`iDYH#8YiIQ=bT*tgN^)JjK1d zAg*63WJNltNX@vK`v9av7~;VR-V`s++Rw?N5u!SBU9DK8zlk)a+tx?z{Ne37Y20R` zj!ic>T6mHp31dl0eAieVY^IqDo#vWq#p`d>IcNu%`C2xA#VZs?SYWM?C3@5Zuv?YR z7RnSaYlB)fW44-Ud+Zr0+QEU>xrZ1Gg2U}sjjK*jw?1MahW0%gBo{ zd$KeoDrT|p7XnPBoEVG<$QNJdyQ*amRYKUIQVFgQz4L&fX0*>Sh02b`n=ZZ<1STsf zh5~h20evHA47GUoW%nz=qJ3+h2Wcv5ZHKcf~pr_YG2SM&19a z8{|KlEcR>P*-9p-g?Jau(4I@bBn->xqBa+8G~(=^K9S}C5KckVytaWFQH5G#07_~d zfi=!+&IKQwJc8yU9XER#^_NF)rK*PGbTCtLYjd@2*J7K9LhuGzEN98c!q}RZ-J4Eb z@r-yZCVXVZyXg3IMx7npj=-G8xDV^0Z#$ORW#*LtQX4HIwk37c*1fwx(c(a0VBl%Nt0olUeE~92n5zR8y||{I)7+s~hzLO-n)E3G z2vkRQlFa0`N&7s^%y~vXA3e6Q0b+7*iEVjtCFo(K<@$oKu$EUOvYGto zyQeA;F^!NZc;8}}9=le^HNK}Q7p<+{)Y1o7cH7e=YOyDnscL{TaqYW;li<W?SLW< zdH(}p!8U?hSl?GrE6(cfs$n95>Ty)dK4UtS$+zB(;P`x3NJ@oZol+aA>VV}xP?~}P7 zKbl7Jw}RxGVonXUZ$w0TID-%cB*{5Ky*o!t&qv2f?>wz5F&zKrEfX9WJ|^cm(ND5L zbM#<`$txeJ)?W>a$R&$arO_fr&-^F#{98zQ|`ev2EbimfeD2r(U~^3m$2Sf!lQ zNRGc~zv4a>;Bn!kIJwH1hv-01v&CD64z>*N*E@fW&2A~cZzx4ro!mc==W-)hPH{WI z2CEuhz})>R|9!mb2UbhSlNbGHyMVT~H(QqiCFv{Fp_#apF7^(9wnp9c5xx1jp-l>d z0QNs=6F3WP`pcsLxp+4IyCYt$>{L4plP_1!C*H4Dy?%!uq0o#IN?CtPiMw$BbDg?1 z=`w`)eU8#HohfqfM%X$K5%FA8&}Gd*#)7D0lb15Z3Td~5k$TKP3B|c8&X~X83`B%K zwdY%gk!ejHIHUb;j?h+yK;=LES9|*E1Pm7Rv31XNZ>a$imJ9lI%gNA;RI5-@`<2Tq zg$AC1LF@0+9~>fO;^@_Qv5JG~7d`^ARuCl{<2X}H_9XgA$*a-xCx%8o?i9&PRH(Qj ztxGq?i~s)47=nnYq0i3w8r9#5^xQTL<;r&f7Lk-lsq*!a{efV6uAnGt{5 zS#749jJz&xK8IE=LJxWqHFE>^ZRI;@?c@7yx)bi>gKuJ`C9cnV&Ixvmfe+r8xh`D0 zzZ_un}zfB96k*Y&lyD#j)KO|0uuw+Tm%Di z7)lj$#aSE)hdQlWWFI^m+L)-Pj;C0~#5Hj3S%VgMv{9~CplkK;gU8E}5Ux4TS}ezTS_)HRta-XgzhefQ}`(EYyZ;yFgCf;3D7%jSL2c!yMRBL|fH| zW5N+bjDu@m#F%>V-aLqBRti;SohGKBqnOFsl8YJQ%ki&9cuZ;?Sl6i3)H_j|TJX=Bu zV}AVXB?AZkXG5b(ypZ+GVdjzvl>>gFPv;~RcPJe{tzXx%I{UsvBJs?aOaL|k1K1Q2 z4a&C>*crP6&oF%WcuN6JU$xi)!~n_QKK3 zAH=Tm#<1Xqb)Bf)BP5-RSgh=0`Uxx2>Z`Zx!|%eDVLl2TD@5;O_am-6$}HeDc3XV_ zP$E&`3^=2;<{}kbon1vEmGNkI(4 zW9G<+VISB08RMJ6|26Yx3Pn1>dM2`X!iGuPknV3?llWuld*Ok~s>T8bMOdwY5SjsL zPyKTPctnxhF!|nnHa>^IFS};@Yf=;2;oa}qj@(J-%H)lJW?Oq(?%6F+wTI_i}U<=zIRukc4Dw6#v{*K?si!4(kQG*ow7UaK@|E zL&f;Zn1SY=mMlj+x986@4_Yg)lSY}@C zb){>(Q@e^yC~I|2KEB2s+0{s3Qbd)~>L*9ml^SxmyW1r1LKo5s)zRC+Re< zb+AOv1im#gXocI`%#F*Do~=ZNysCMrSw=Nj5?PVyKf~301Vafu!*8I0$L#7zxIlf& zO*B<9ySlnsyziEB62;7~TQcO)icpUW&R>pbcb5@C5UWH@C+|tROs5dcQkD_c3!k0u zGlxoxJeE(xcKXjFyFTLY!)#ZO)+ylZ0W{pvM$3M>mH3R>tgsz-D(H6p+)~EV}FoX=_xzkrR%BNC?+BDREx#$ zEtJbFuGk-Z_6mOklm4dOO>S(+&lpsEb}tOCj-j#~T7q^}F6* zTMDvY`X05%S4L_0HC^b(ljb(GeS15+QTjD{hVTaUmFK*Iud8;^<_jdAZf z&4zN|Uf9I<%X`qNdQ3E4UbLnBQpf8nC8AlJkUzq)OY!@c-S6Om1C+!u^f>;Dr#;)o zmV}B#m4&mIs6!?lh9Zw*%4rWQ4^>>Ia-euGwb^sFf;UEXPw-x301;E`8deqBq>67b zUG<|1DA+aPe5Rco%9^Y%Ikkv!<7ZPp7|bA2#i1?1*%)2b_02?KUoaj6?h&o-t;y}u`4Gi8s<)PDMb2Lyhv%yP`P`!lM%CsjFr84c;5Ie--{$&xx!bnH%*gkr#N;JkrnOBsnhN%vK)haWez|U zXGoR?HJNmRPG7R4@?KZphH$zS@#Z0PpT+2aJw$yQYpFg08xpX6d(-Ly{1HT>Z3f>I z#T_swytDa$UDR6!LBq?Ys!-w8^`1vy?dL}WrI!ULyArZ~N)tN9;v6q?icJ|Sn_=ts z3W9xKT?iORF)?D(Vn*iF76ly@ng}g5YUXDirlUE^bpATn2)B`)69F~+=+Vz3&V*W0 zGar2hOhVU40s`Z57rI~3%(mo1t*7#Xitws2f~2Jny(m!wrE5#&*(B=*^~9@7L&VTM z$Uv)-@F*%ZQN}817bx%hn36NlRUs4JcK?bXvsjP3N}_rhpMM?N_R3T5H1XmISiEE` z@w5_cZsid#+}U-Yu;_NyL?o8@(^?Lzf7G=%?RkUugzh6>Gs+w`Gs-Hha4(FSZU(hw z0d6B}?PT}cD`|fH*E-)8Est{6tS^0uuSzWK{Q4ky9WT#EX67DY1rICE)@9;eN9ZZ$ zgoN;RC%RB!E%FA2tn&EHrQP-vfrUKQ0?ty$|*dg?n3h9)?B=+>QP86O3+B1p;1)$(w?F2AvN7S7ShL7 zxjxt8QkwD1*2q!Jj#@gm+e%Sto#N-2`=(~$R6IQFbopo>yZA9;-nP-m22M|^7Qi|XKmLH@sshAVt*MH zX#73nX)e-HWB|l{eI#*FyHWG%p2ti3vhL9ScA_@j7LIALT+f9cpzb>Fh#7UJz9M}u z;|;KQ6hlvBGPJ3{pb-Cv7T6RWb9>h0$;IUEXiX@?d&Awywy~;_S?J4JcCYgKxB=($BG64w@MiCWYVIOm27{_ID8+ROV!hb|IrPEl^TaX@!xiJ&=a%6^Qu3rc) zkU7%m03w>sYa6)pdL?SUV&n~KYgF%(90-eT(EO6HI4Xh86>U&9k0!M_e{xFX!@ece z2%XsNoB9e03ee=!pxVvp?xBs^v)ru|#~}I3tgkC}*#9~(si$)U`SKGaDgJZI{RYvQ z=1HU>V-IZ#X>)GphY!hCj-ti|_w)YF;a2M~deT`9-1b+$a`;~_GCNXHDvWEr2l}ZI zAt=6gEk;v)q@m+zx!fJ|Ac2_gVbhVy0|iH~AK_FW^&Wb-n~v-?K!0FT^S4T+8YJGs z4$SzNmt19{@cXc!whO?MNaeY*YuC>sIi2W-41SJM(F(&0>bg@O1S~AXrI`<*UgZz` z{ttWS{ZIA($9>zekG;pSS4gt5j*(S%wlc%VmX*EtjG`iYCKOrOgs6-}A$vu#WxJlf zx9bnM{($R8Zqjki`~7-9$K!E-#Q*z@>ydbM?{1La47}0gI^=(v9QQ9GK#w7KSol{8OZv*o(ZeYxbhURkf7^HEu!7YG5S4Oau?PALv7_r0ew%=x z7>?W5_S=L=C)Z{X=IC$Xrzl!ceMcy0>Rt_!;yodfmZ9wP)_K^p5MFtA=t6cb$P*o@ zVG(&18W~EuF!J1MJ7_`n5qeQ(y9Se)olI5vAj+jPF0ST(R&{0bj?1-NKxybrRH2Al zrOhFyifhoN9%=W;Yukdu(*u2F90Ah^FjQz8ba0>h^~IV)lJ6#1@m>}|5pA1H+9m;T zipaRwge&wMXhOJ{VM$T>`)9kpva}EaM%skT`+Ivg+74|Oa@a>b(4nN|#jNkJnTcyP zAOHJnF(YdLTrT+#A^R%6P(nfiAkC(yG&cTHUVtMiV-tG4PQf(MZMeC>LHq#hcSk?@ z8S`lR7l3zBWxE5IADEEn&3A@Wf5*$f7or>amz*WCL(V4ZiH7__%E!_FfzS5KNT+X1 ze?M*~Jhod~TNghjGz6XcP%nx4Y|Z?F#V>pt*c>hJ)Cy@mev6LrgPkl83qU0S=t4-s zeSNi1SAKZ{_5`?H22>&6>UzMTvr#{$Y!&QGRJ;OVCMr(!%|(G8?2G8f{RLp#2CRXX z9dY8nWe_Su9geubUKV)?%=L29o101*u-K%(`Co=W&l>2DcvI_69Lo{L+nOeoFYJX67PP*$?Pc0{s2C)lxA`=@#gkGDdMhI*1!YjaT#Y7wu3%0pp z44|8+y$~q+2A*9pt1d2OJ)u}40qxn%(c**zp+(@&OhJGbJm|e7#yyU<=TQ|hj_csz zO$YBjSPaI?_3f|3P8%F!|3JQ=`IMiFOsZ>a6AD^oYP>5gh(0*2YJ}F7VRwmBDDZgp zLGUpybuOe`L&;~8UXzA;=mGOfSdL1=4#hSMy$%C=xz=WTd_9i4k{Vzxa(oY0h%l%% zdi-IOa|o6+5W{qGWU==T08MeMH5Hj`p4tVVFxH=d&rR>%a*)Ely#Y(EuI+6=W_w)s z{DE>V>K@W#RH!P`wzjS*HzQ_gzivT$;3g%BGybB!g@Aed21i~aN=9rr-Ng^izd0;fj%f*lZY>aM6hF}M zJh}@qVF#!CD0qa!M+T4rjMWHXJtHv*=4Z2IR6edn%SQql2XQ3$TNgfI#h~oGK<=(iSQF2Bt0vYLEakRPKoM5`yQdG#(d!b{-lU- zL%kzPyN4k2{0)bP$UU?s`i?pckur^gaqwc^pm5HQP%pK+Mzbu6M;lEJR6Q%**Tj#2 zA?Eks$;Zhw+^sIR89V4|`ES=CgoV^(yH)vQiY}omf0_2<_^f~zeoYP{B=Ece>Moy*w%21UOTji&=YeVXAKA9-o{yoJ;5jWFv7ySwtX zozH{tq?|xkBUTf=`cV$Ka3dV1f`4@EPyx`M^nZfv%OwJ8UT z5?nFj(s4~a!B>EKMripEkjg|#Cy)&Xt83)v4l=d`ILvow_NB)P1Y%Y(_qoR{ShQw} zHEAb?;z)#rVxNDi&V8vy`9aTe8}fG54_ZNTxW;fZ&ail!GhBZoaW=3xL=8L28;E71 zU1}VRPspDn1JtAJ@eDcRV~C{Yk5>f@TEzKETCq>AP)1%7TX@+BoZ0#Ie2j{kANCMQ z)^7DE=?o3T`lawW<7L+IY}9bHT22saIL^0{yF`vx@_q_Mnlo^#87mIY9j5!ky7v2b zUsqYXQ6Ab|{@ZQamcRz$AJ+b@lsT+6}hc;@_Y((QNz@Q^E-*(703>{+jWV zMB=2~?#P>zc?W`4`Y#MUSB?}?V&!yCy#~>2-^g$sH*sckFqetMx^M)sNJw?$FsZh; zWH7enc1w9uger*+&sbto_7yXZ_KCDxaK`R?B*)TJxu08sViaNH?J|brzZAT;*Ho^c zJM_Fy;66lz~zcq2T?XWJLH(U2pC71`Uoeukve9zyHJBI@UT2NS9j>+`>jFQEYFC9I(PdKLKn`nfQxP|3=j4^=(O z&o{+Yll31SJKkCx_I9$w(rttY`UXeCMqz(`Xo(N26NCFW`g-X~OraK~EDPvj9)GeQ z>f*pOKq{ajc^gF+Q?od{j{qs%dzwd*a@`lQs%E2063f=j@e$klO#V|Xg*Ch4DF z0U9ZL4jVs9|C2we3?QmVx@td(QRLa5!rmhV9?qlm0$Lc(CyjEpD|ThH>X{)aUCqD?~3)PlJ#@zfqosz{LLL_N$Xs7tC{k^c3wK#VDQ7Sl$Ld z+^5adHGD`x^J=JM9}dfhap&L^aX~RAenHIguIkdvvCi-t=x+w6g@-gkbYMeWl(f!6yla+F$)2r1Tki&a4Mx?Z=Y6NqxUq|gy` zICdD$GXkU*9%GATB3Xo!effKsI_kRu@M&cB-2*|5U=T$^1XAps(qPixD``17RQWhf z^W-$&*PdeM(!BvEcdGPn`QiSITLaeE#>RHwZ5udYqc=cf~^zG zxZ@7GlVKWj-o#lV@KR~6C0AxT#D3p&~Iu{WuLChwr9{7ETTktqR;fw<=J zw-8kQ4q4y^6InhSgWX3#&Lzw{d@o`1>CU0XAGi$OBRh@EXi9xfJth5a8)WeF#vZ84 zye8IaCECS!FU7PJCUkzAVtRhQc8a!kj08P!aa9iXYlVAdhQ1`@_$v_*b=php>VHs%&yXh{&cw<{ z{Paz6FiV8tZE4C!z9#QVq}L_dfAF-7qhm#rN=U}SZFl{9h5F-X%Cip%8PoY9bLHGL zUymd&Mj@ycqJ?-~T=T`7!9s;qC4mlTpsGA+;C$-U6w& zA}mj zgf`5QaAQfC^D%ow?inLJT+Ng$GI9@^^OWJjrv|CXFlf&|@z`>9Ai1r!Rj?dk{`ffN z`@~n{dU{?_HlmuNFJ!ls2%*W0@vbv*3f!JU|KcDzv4;q}=3Q1CVSuLwa)06fs& z@Ci5HggSU>!8s}wv;40T(Xp(x``*gkpl0;xjD^EfNoA%lPD5iTB~e=Ynu(D)=t~r3 zhKpc#w%BJw)dQ;)fQBsLo8@4JOum}Df+~yg3dO)>?Vev!7?nlNNpbmFVETmvYV)Aj zR4Xi{XOM=Bm`UOF>4}DP><^po&bNtD^-N7-I}O9F&eGi6Yi8%wH0_`T2S+R%+?HyP zLYQ`QEug1m5LjS*;)$%J*RYj{q=g-=YnHiQa0V>&v=ew(Z1I$l%OGFO;2kNtw*dxe zuJxP;XGTFGnJJa5MY);oBLB`82IS3Y@g@4Oo*$68TuSLW=lz67wQBAQNMJzS9scg) z?#}Z!d$$BzM~Ie=vcL3HX^n}L)sV){HBxlhWE^g)H4k~9to6VD?-Xyb0ac#{6`v^8 z18@{Fpgw$t#_m&BwCnBw4xvYnmKqCT8jfi-sMu6Ko?g>0N$eCQx${yysm^(=M3$45 z_}bFHSj?|QMF;zRHRR~f4=RHUs`HgV5Y(0KgsSnoLgX8gvBrJ*(Uc}$c)!F)-Op~0zPTT{5poq}h7JHxYq&UCB=de#2-bMTMxwVvReO2v1)ci$nhhb6RwI$_I>;tm zs^@@=c$-E%*D6)>eq_hdPg@U7L_1K&VXc+-NYCPskpa$J6`J1A_#4@`d`Sxzq{~9$ zd7I_{(Yi#t2kibu=f@93LY=v^vgrn@sv_8aJA&($?F0UxC^ z+(~EH^O3>Ne59rI?O)@uXNs1)#ZnMcA!?@uFykceNYme*yH8zLrcf+HQCUUh)Gizq zO7svC}hZmdj7esQ&ynVVVmg^IS?B756<2lJ<&+k zLRF5Jlb_p&J;=#3iFP$4mvsnbf-U8nk)~|glK;xEU(%=ZM#0t^#YNipdC8jA{tAxM znvO;^lkI|3F%?{3=9H&Z34;Zv##$)VQ>7BUJc%MI5MCh6daUJ))qdk4rz=9# zjvC0#zaP9zB_y44v=HwhSRvu5rFV?}x~{3XxL&JQH$g3&MmeGyAu$^D)9U*NrvzNnnoCml1KVuOhG6 z|Kawe_!U$!>Lfawhl%NkU0c?m?iH>hdEzIEiuggYvdg`=j5-yHn3(=h%*`Tet5CUCip0xy$+y_=7kJr`|h-TWHu zj`^3KuN${qNs67W!n>C=tbblE%>b;@Fdai5f5LU6tl&|_f7F}oa-#q*caU24L@G#G zBvv|OEr`+M)H`tX9KorW0w@l1}hK(Y^#Rcn;TL4GQ z;0XWbXh)U;o;p48I^GvCqr27T370?$J1NJ#uNcVJ^H3tKwS6WPLGrLac##3?5)%F< zJRb!J_6$Lg17%F|&{U^=KbG!W`9CYLLOVIJp|dv&;u^h<;?8@WYG&{B6H_pC0Mkpo z#kvA(d5A;AK;7L!RhgBxN4DTE#z9%QIo>(tgtA4M6(`A7fv^fqo=F*IOyFx6eEb*# z&{jq*Ha{Os`Fce{G0lZyEkHI68d0`b8N2poBFuw>1C=Xzbq7aAl2B2BX+GGorzcNV zCdl_WGFqb=PfEB}wq7CQ*vhZOx=+`5;Pli{H%1E$)YHe8J|Hi1BYpn~>+X{nC>jti|GaJfd;7z+ak8f;iJB*95I(`IEn6W+q5oI9N2}Fm*SAQ>l@!(?ytfeuvqgcL*Z+` z{h*(scB%KB6$YOTW2ec71P44oU{wgC)Cw4LZk@ak+11c&`}&MmN9*yNh#}^$FJ>K| zmzt~%@&u-im2e}7MwhMj#HzaaYKOb#`eHReHLX{qhCWu8Z_S+zZdJm&o$yfh;_l^} z)p^W5)1pWAf)J9@^PP04+Zn(1Bg+S&i8&4}nXZVlUTggk#>oD3soLv`w|xb4>&h6& zi)>w8>#(*GR0qg}0uI)fQ>U|K9_z8}6bdul`EqHxF|&jt(O|8TFuA_~a9*2J)y5`r zv@1a;Q2|~V{=1HzAA_il&-ibO61i~9)ou|H`1YG@rG_>Y@T)%)bWmmux@G~O2d3SX zhRCJxnkqm~?ncl))@0{Jd&6gC%qnM;cP;gD`mHs6VFVs?M5}pZxiDWlR~&Bs1|{4m?YtRznf@UJ#QeUf*4E z7bEINDG|54*VzpK#a!eq^AG%eQeeM!*+U4)&Ff%-3&_hH2KlP$; zL%744v}R-#d#9?3%%m6j<=-G*Tm8L9A4(R1_p12I+_UNByhqqCA1k;B*g@L{SX_Xh zJilR|+&muU`thvL|IaG^ECt9a{VqtR#%gX3tEN&sQFPKbb<}^WaF|?q^W@(aS<)gO z3rgr*=r&Jb#A^VRMSW5+Qk`ue*(6jOWy=I&K} zdB@~or+(R1&E^sU^q!+)OHV-KGC?LzNM6TwTP=7LZx#QNuroTxUi(#GZrgD|Z1om^ zn98hTErTQ^cqN0%fp!9dmpnBYY9tav$-5qN5F;tUE1$+MQcL>bXF4yn$HKx*J|Ydl zk_%AA#pB~yA2^x_US%~|(Rt2Lwqi09yzZgz&d5|Ck*+}c6stP^!3g?Kz+bxS zOAMV%C%ltzsc)bA{Rd|Csv|Wq_H_nWmvi{lG1OI;f4IdG%fwvN^`0EvdR(%BLw>a` z!+92N4soip zxD9t?X6;cW&jsr5+XS0{7Qqslw|y}WnmmhJC)Nq&w1m_G`h`z$Lr8BGcIZ>CaKzr&i**mW zQDL`Ku3wG)_QPNqsu>WGF^I$lzVcWQT5^~S?c{OfOEN=ca_bi3W2l#kqP-=!Lv_SF zsWZ?0<^i5|HQdrB>G1!3V2jB&Eu^gX=XI?&(HM^~Y3=n$njxHk=vRt7c-5|57@u&_ zpa-3Og3z$sTazp^FyWz-GQuMuWt9^&vbEX7BVn+;ak+tnLg5KVHH;^fEu|8tBMVnbZYM6C2JA^~yzWU{vOIX`fSw0fJP!%K(eP=h2II4=%K5Bn1 z!td!%w6z19n37`y&twcGu4O1yy0-fVAKrVdWa;#q7Ra8006>=YfL;6Hw>$G#pKu?i z!6lT7SMX(UZ2s)@}3&b=exC;et&t(mMTOTy|+4!T_$A$4}< z`7s_qP>*_&oD!)9bVFiZ?_^^#Yu@-Yi|7ET?ss#ZkRyWfCtTgysqG20eAbm6;11<% z>d;@di%q4(BrM+Z_06QjX{xnNdx)s&o$FD>Qgk-E_J2nZdOps0mdws!+ z2QueGi7>B;qrbtOxt(wMc^y2j+co9ip9#Aq6Kjw5t1N(NwYJBIg&RTT<&qhn(5Z3|4_|L19d zjkieni{RQ^cn0eRwYcvv{eIy@qyS43<`HX(rPkPcqENM+*z<=abEtFriHX*cMoHAJ z#C?q!CaK{uWX8z}s@YECDMNk=qDyCZ0)ZM?x-Q+C!-UIZ0%k~|Z?|3$zXfKD%72NZ zhr^El0)dG0DT4heIgGf&0bRjZ7;)oZyk}Gir+Ag1>oe~0U8An0h6%?WI*A5Asj+h^ z9SjN?DkB5M%#qBaAUtsQ;NG+v!o|$!pkt1t8X)h4_NIKw?F%ku7X~xXSV&z*T5*+& zmDAYAZxC3mgrAi-10a{VQu}sDd5CaTg&g}4k@E2DES)QO>9M$J3SYvE6V~C)&~N~9 zrs2M*N@nj2pt%kq;q8M~B(%J$ziJo55&GaVutj_o3qJ*2^(RfH2cLe3#DiqIm#&~h zcz@jT{COS-n{8@g$@~ird)U`^5GU?%8t2Gy7lk?O3YeX7x1mFI-mhWB5~ZYT!U=mz z`dCXV607Au&>?ubf9&hyVPm^W8WIWk%Ugdj4CZb1#(Ta9S6P3DQDhxIyt zk2i7MZ_rxou6)@j&S@wLTD5`acxO#EtS=e$l^2vtP&%w@Ufx3l$R_~4%ZIU_XR;_b zVWG$+xxL6n0sCgn=c2_RJZ6Ur;5#@+DgwTSRkBm`tnxLoQCR=z+Qv0cjVF0~teBAU zln86zk0sh{7S#!hn>aN|g;M<79u~(#V##?pN(|qA*8?ipC|m%)@@u7bnjkg!o9{%WU0& zqQwDDLnWP9zJ8bM<}e_WLN~9#bb_+9UU$RSMqCUvS^&E=%;7n0qFI5#h5p+bAdxSVXCMf@Sg@!G87CA|?$-~5jc{p|MQC=luTScGrM#z0s9w?- z>`jlgVHEp^nOsb_q^ecEm=Cy{X!X?exy&y=BaUS3L=t_&V2X+_A4e}9*aH}@tw+bF zh3|9M#M?@dk^$pghaT>o02^rHow15g5{h*l8%H#MPz|026kg(pT3Y7q5X+!C||andAOs!)Q-OL|A6!N$D$+d8bY(?w(~PG;(awDmJ=yH^A-k74wQG27{gKjHp1@O0n@i{6uDHhgoeZh1gOc{78e&=kT`G$_(Vo^i)p6V-p_Hz{!iBret<}*g6>*mhzHl z!DV5DyhucSY<$Xnlr>1_GoPkeo zN{~Vs=jDf{r{7ZY?^lOB?fKugk&G#-(#EX2^KKbPXpOq6UQbEOkqRAE!8tVX#w=6y z@*P51P&Hdp>LBOiB_i#HtNCK;M8i@zB6M1C_l~@dwD<1Pm;HnA+9+NYItM1PtB)m& zRmRWV?);Q1gH`JH+7&&<#>v&i6CHJNQfe8RnP~*i7}TA2-oFFtnC5#iz5C-6<6b;N zZ6y;8bk8Uh=Lyq**hV)R1V5_Lbms2xwwW`Si-64GUGa#kXHhCG+MVxC8P31%|DkS( z@mR0Z2A&;xL;w>CxpQvrySCMv%t#f~7)Slyr* zNXIXJA^X^K?1W^$1)we;2Xg*{%{Q7yO(gJ}-1$A~Sr7*bFwkjz(g$hDjYIaV8ay$FxsEVT4pVR#UhxjT^(S%c2O1k| z1qtd*nK0cbVdCJ#i6F{}r{p2k?!;_lEXcMTzH-%l_JE_2QOuzRwpKWWO@Grrk4_-E zCf>h~I4RI>iM5U*g2W0d(2L^|Qn6)%z-plzXEqjb8(!`*e>s#?RrgyNnhD4&=jL}5 zE)}e0w1;&4PjWYAbHX`0)ct55i`|FhGjV?C!2hLmTcptv)>C$BvF52v0Q6-0&8C(k{l2!fm>dbRu%;0zQdekXKE;*2UTwJggv% zg~r5^2V`TrTBSqmd=9vdR3vO7FuEvX6=OH%4GE^=zW)2=&vdTz!icE-!izENlO~n@ zPRlcU=UOW)PDsG{R-_i@9rN7k_sBIp$u|v8Y(bay{7|L(57hXaXsJEp@BmFozKnxw=;`jtYGUnc>u_%OxgABf>I>o6h97>9S6;R(1yw z=u-IM5e#OMuIW>(Sx0gnDQQEaKxH96H>?mFgL}9!q0OT1zy?T>#>w@14j^lLy~a8x zmGThxBalyJ9#;T#wVG9^7S1V=ZmYj{Y(K~q9@&156%8jHKWabxiY*xbdSkKH8MA=xf{prES?3`?gQj3y{O!V6hP#Qa6F&k3-)5G@F))gU|0pQHiOj=8 z{OS9b8(^Pl<@2z!g@a$Dk#?PFlM^o8*@-Ubj*?{$eeQP#mB~8~!_L@PT6=0;D z(_F|7=e|&*CHq*q#5;`Jmj?R`s=r3D;lIe!8!3<{TDX<)x^{WkZKfseuP#&)Vt$r-3Xcv;lEA_BcdCIb2I}U|D%Gn*8iScBm}+6dVm3kQIc&tpNX>9}T_pDck%WZA zlxmwhl`@MDc+TW@BlPp|0X0OG%%7o3ByC}7$tEpdDSgP)IejL^ee1`&pM@qrb+J=p zS-yV#>i(bJC@Gl?M*@#-=7Sm!D+8X%OIe44sX)8Vz~f5ARBT%Fr*A|N&)4il^GEdc z^kA)I1n;i-6UgCub~rcybH@>;8q@8&4&0FmAeXvKC1Ko~>f9ZX^P)TAXd^!G^ms3V z=-d!=^Z4^VTnm(Mt6jRJCwQOP>i9fkzhZ2q2$6i+5jXo7EZ>zfT6}zxM?gT3`s%^k z5wyB&tZ;2$hgO)59RVkAc$xDod)DbvG)-b`Y-mBuQH0})W_$C858d@MZ9y;P1ty_0 zZY%X}V+;4Z_#$75`^LBIIGr_g2*{O`#}YsP>ntnd-S<0td$pK~QvRMIb`LvYf@J9u zbZY$~{H!FlH_H#>l9HC)-i5~x%*&>MwTHdG7X?!3$din=_eB0gRbp)wB3%*A%WO|9 zT8smmc>EKlv43K=?ldpwXiYDu$xDhm2-Y*@ppm!VWrlMTbvdGuPUT>zFl}&j_f`;j zddT8_f{#z9$mkK(mtJ61hCt<&fqjh$VOL7ZztX4E^yjCa0Iq$-M{n)o| zq%nWmU_3Lo^6?%93g4xV7{n#`WSF&AF{H67UKH75wjv$Eo%|{GzK<(6gJGA%-_~}k ziAgzL(GlJFxrM-**hA4cN7~!LD;4`e|NV}+ZG*RT zqj}`PF%B!YlpkqI$5tUZb4aARV*9+@t1|) zA_rK+1_fL}fjW$TgM)*2c~Y6^)g*Q_C~SnWxWhe_8OVkKQn6_n#+HLcw%p0p@zs^*}WnKj6z*Q z37ug>cOdilXu7lF@84Yqy>5A1gZT?@sK$cX{(R_^Fy66$s zqgG>I>%xeeCaK|fdOKS=uw^{p(DXG2<3qap5N;;EQ7i(_qXW}WF-wrof(VFw;=d0b znTUNFXGiC%WX>OiZnaqD_$^eEHRa>xKbzwpj(6a{&=`ji&Q2~nJA2;iIgBe%tw(R) z88W~Ggzgq%HCi8kG(F;B9eQon@E1^dN1D!<;dmR~M4g?LPmiphgTrSp^RsDG&b@=h zBG*5~6VN|J)hi)l3zrPzs->_R13_kj+5S4Ju<@^q*BWvRHLALD?4pr9(l&lP5r(uq zA=X&KrlIZZsE=X?MHdwz@1`wwh8TY-3<^?xArK&PCj6*Z;tHdDSc6HO%nVz8S;(qq z5``!*9QPxC?l5y?<7b2$yB9yJ}NzpMd#N*_vKuBP$K8P$xxbbH9 zw`L!}k4MIV&zQ2%AG72?f&eRK{~Q7r|Cxu!0;&r3yF{;47Nfp{8u zu!Cytyj}jX2&eKUHkO?IAsxk#dMPMmvofj@p^aR%3X{u-Jo;+6BWERj>!S6V16T{v&j5M!bR9dU_4S)r6>K!d z_p|6vA1D^?2}Ye%L`Fpgf$01DPc!u{Ai~e(WD178p@HSGV6iJEN&MDv7sr~#eIcRY z7n->F>gOkrL}&HeF|2CWgAAR$$FM zaW0$R!=RkESh4|bBq!=29z>qVr=j|6J{2i)LzE#waCXKzVZY0Ry)=wg!b@Z3(o^;e z3g&OXkQ}lnAJ<_jYcV`U9P^H{FJy8@7#{Mon4fdy zb7$`=_)P%fe61dM_>|084*>g4Rg%4h7v=_kAhZW42@jti%(j*=OB%n0g$RKdtq!u^ zu!arCE~9TFxU#qYA-EYghJ}s2F;*U-Jg51mzkDLBuC5NGCT} }Y?%V#i1@w^ASQ zZDvv%;1<}MPwkq?r?ANG{`(hWS7%tlWFRTXZE7AYK>rl`{rsOll?Ph)4f6cHBlhfU z3KD+ePYj*k55aJ5LD3k5xWIyYzOL#T%JcV!1H>m1EgbQjXv)EP^cHX(2sWpkPO_gdDG7*(>l}4fPp_x ze|W#yi|^ug?2r7Q)15zRl3Oh%t2Za-*10nxpK4P?iWjhihAO*s*Og|TNgC`2^lHK!Oi!rzK^sL;S(8y zBI&7Krf%)SyEtLfCRZ8U)O*WBRFUM}G2MwBfBYUlR#zXKqtwib&HZLDY(!PMk1pH| zjJKJIM}GhrPozLUcAzqxxY5Q1QQS$4i>3;%ioNXeym$+>hEpCw)w>w`jj(ZnJhx(| z-sNRRvrRX5;G*dVxAMzdZiu$1k5VOrBPMKrtAEs7Au9gDlc;-4iS%UUi`i>Eu8X8yUTeeHICuG#G?YGoV4qv^zDeJ$l)B4&E z%=qS;Ac#$`w*$pT)-Q(ODQ>qP&Pl}mtF{=%_?+({ZryPTE0TLuH;5arDmE$3ur|_R zSASp8gy~G{x|s)MecCPYW7WX2*RUExEK#wUc56dJoc5$9)Cu!0#xJ)IyMr*KNKisH zsZY_ktHFlj4!1sR--^fV6lf6PM&tCRXOu}gnCEvjqQGrsl^vd9sOZl2Q~T$edGR-< zC_XR7!0z;?-76X^mt(eSx5%qcQIv?=vVcsylfTe$l@ zWIP+E;msnjw4xKhi-`AzSesxZ#iw&N@ZO8f~9)h#; zC>W+2qrsv&(mq>V`aEKUmXxq5w$udSMv)9=>GJsRW$4p#DBz*w?e$IHycAPgZ7_RE2S&T|x7<+KtU22%5Cad1`3|gtiF^{Jj>Vq{hZG;<@{d95*=gd)m zlx%;muUClt#gN0x+eX>aFL{^ z%%#ZLn4$_JCS;@8V1S(b6ZO5AGx|a>vZ$Zh;c77_5j_fXNNo_lh&t=?USD1=KIx~O zmTI6Lzn8SV1)BREE@#ckUPk6)#c}83?ODOc33eN}MIV1FAU^09=?xutattO zpBRHx2Vjm#)58U5C%+C2X;)9|Sp}OR&Df7r*&3WCgc`9iH19#$4O4*#ks%wBv6foE zdq@wt+VJfF+oRHiMEm~Fvq$?BV3nsG2lo-auM9n+d5n*YKrK@nH zJa@UC|F6qgzqlY$8bumX*%1E^}8PK*sC=(=B%i<$i!M{$iv#3Ym{M0)R~;!P0vk-eeY-24ce&*V;C98` z`1X(xM*3m*!_KWxRAEl32LxK~)(*+Rv!dtY z$4;DBUyF=KU8&zs8po(#W8VR4g8I)2lHgToNlD|$XPEXnYUU@UOzGsi`U=J^2``%b z4=U+))5nh(17(MA*o1IP=3X-SSv^=hDp{9CdQjt+{9Nqo*qbq*5Ku724=_yB0 zwjKC(Dby_Gv5n(|G=hjtYI1Vac?qT|js>n225INVsW)a4*W6rinE@yCd@W|p0wd(}i<^VV zYAP(!zJC+X=BWD3;08o~R%>?*eNxcEdvhR}K|wZ8+#snmB0?Yt@)67Q${riXflF}N zc7eteXTlL$XxLxTQBV}zY&ZW32QM;1{J$|o3^8Ov@dyVg$fLJs!OpmJ#mE6yW|E5< zAO6fE(A*$|$q}8Mj@Pod@JsMeI`A|FL-G`8DEF1Vn;2JP7vVA!;ap$acb*gsp%pMv z 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 literal 0 HcmV?d00001 diff --git a/examples/mis/lwd/figures/fig2_transition.png b/examples/mis/lwd/figures/fig2_transition.png new file mode 100644 index 0000000000000000000000000000000000000000..6e1963536a5747b5dc2f4b051ea86fc8e3cc74bb GIT binary patch literal 51109 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 literal 0 HcmV?d00001 From 388deba41ecdb7829a66b0c83be4deb6140683e6 Mon Sep 17 00:00:00 2001 From: Jinyu Wang Date: Fri, 19 May 2023 03:31:53 +0000 Subject: [PATCH 2/6] Add MIS simulator --- examples/mis/lwd/simulator/__init__.py | 8 + examples/mis/lwd/simulator/common.py | 32 +++ .../mis/lwd/simulator/mis_business_engine.py | 259 ++++++++++++++++++ 3 files changed, 299 insertions(+) create mode 100644 examples/mis/lwd/simulator/__init__.py create mode 100644 examples/mis/lwd/simulator/common.py create mode 100644 examples/mis/lwd/simulator/mis_business_engine.py diff --git a/examples/mis/lwd/simulator/__init__.py b/examples/mis/lwd/simulator/__init__.py new file mode 100644 index 000000000..ac949bd50 --- /dev/null +++ b/examples/mis/lwd/simulator/__init__.py @@ -0,0 +1,8 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +from examples.mis.lwd.simulator.common import Action, MISDecisionPayload, MISEnvMetrics, VertexState +from examples.mis.lwd.simulator.mis_business_engine import MISBusinessEngine + + +__all__ = ["Action", "MISBusinessEngine", "MISDecisionPayload", "MISEnvMetrics", "VertexState"] diff --git a/examples/mis/lwd/simulator/common.py b/examples/mis/lwd/simulator/common.py new file mode 100644 index 000000000..175a4f63b --- /dev/null +++ b/examples/mis/lwd/simulator/common.py @@ -0,0 +1,32 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +from enum import Enum, IntEnum + +import dgl +import torch + +from maro.common import BaseAction, BaseDecisionEvent + + +class VertexState(IntEnum): + Excluded = 0 + Included = 1 + Deferred = 2 + + +class MISEnvMetrics(Enum): + IncludedNodeCount = "Included Node Count" + HammingDistanceAmongSamples = "Hamming Distance Among Two Samples" + IsDoneMasks = "Is Done Masks" + + +class Action(BaseAction): + def __init__(self, vertex_states: torch.Tensor) -> None: + self.vertex_states = vertex_states + + +class MISDecisionPayload(BaseDecisionEvent): + def __init__(self, graph: dgl.DGLGraph, vertex_states: torch.Tensor) -> None: + self.graph = graph + self.vertex_states = vertex_states diff --git a/examples/mis/lwd/simulator/mis_business_engine.py b/examples/mis/lwd/simulator/mis_business_engine.py new file mode 100644 index 000000000..7dd052853 --- /dev/null +++ b/examples/mis/lwd/simulator/mis_business_engine.py @@ -0,0 +1,259 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import math +import random +from typing import Dict, List, Optional + +import dgl +import dgl.function as fn +import torch + +from maro.backends.frame import FrameBase, SnapshotList +from maro.event_buffer import CascadeEvent, EventBuffer, MaroEvents +from maro.simulator.scenarios import AbsBusinessEngine + +from examples.mis.lwd.simulator.common import Action, MISDecisionPayload, MISEnvMetrics, VertexState + + +class MISBusinessEngine(AbsBusinessEngine): + def __init__( + self, + event_buffer: EventBuffer, + topology: Optional[str], + start_tick: int, + max_tick: int, + snapshot_resolution: int, + max_snapshots: Optional[int], + additional_options: dict = None, + ) -> None: + super(MISBusinessEngine, self).__init__( + scenario_name="MaximumIndependentSet", + event_buffer=event_buffer, + topology=topology, + start_tick=start_tick, + max_tick=max_tick, + snapshot_resolution=snapshot_resolution, + max_snapshots=max_snapshots, + additional_options=additional_options, + ) + self._config = self._parse_additional_options(additional_options) + + # NOTE: not used now + self._frame: FrameBase = FrameBase() + self._snapshots = self._frame.snapshots + + self._register_events() + + self._batch_graphs: dgl.DGLGraph = None + self._vertex_states: torch.Tensor = None + self._is_done_mask: torch.Tensor = None + + self._num_included_record: Dict[int, torch.Tensor] = None + self._hamming_distance_among_samples_record: Dict[int, torch.Tensor] = None + + self.reset() + + def _parse_additional_options(self, additional_options: dict) -> dict: + required_keys = [ + "graph_batch_size", + "num_samples", + "device", + "num_node_lower_bound", + "num_node_upper_bound", + "node_sample_probability", + ] + for key in required_keys: + assert key in additional_options, f"Parameter {key} is required in additional_options!" + + self._graph_batch_size = additional_options["graph_batch_size"] + self._num_samples = additional_options["num_samples"] + self._device = additional_options["device"] + self._num_node_lower_bound = additional_options["num_node_lower_bound"] + self._num_node_upper_bound = additional_options["num_node_upper_bound"] + self._node_sample_probability = additional_options["node_sample_probability"] + + return {key: additional_options[key] for key in required_keys} + + @property + def configs(self) -> dict: + return self._config + + @property + def frame(self) -> FrameBase: + return self._frame + + @property + def snapshots(self) -> SnapshotList: + return self._snapshots + + def _register_events(self) -> None: + self._event_buffer.register_event_handler(MaroEvents.TAKE_ACTION, self._on_action_received) + + def get_agent_idx_list(self) -> List[int]: + return [0] + + def set_seed(self, seed: int) -> None: + pass + + def _calculate_and_record_metrics(self, tick: int, undecided_before_mask: torch.Tensor) -> None: + # Calculate and record the number of included vertexes for cardinality reward. + included_mask = self._vertex_states == VertexState.Included + self._batch_graphs.ndata["h"] = included_mask.float() + node_count = dgl.sum_nodes(self._batch_graphs, "h") + self._batch_graphs.ndata.pop("h") + self._num_included_record[tick] = node_count + + # Calculate and record the diversity for diversity reward. + if self._num_samples == 2: + undecided_before_mask_left, undecided_before_mask_right = undecided_before_mask.split(1, dim=1) + + states_left, states_right = self._vertex_states.split(1, dim=1) + undecided_mask_left = states_left == VertexState.Deferred + undecided_mask_right = states_right == VertexState.Deferred + + hamming_distance = torch.abs(states_left.float() - states_right.float()) + hamming_distance[undecided_mask_left | undecided_mask_right] = 0.0 + hamming_distance[~undecided_before_mask_left & ~undecided_before_mask_right] = 0.0 + + self._batch_graphs.ndata["h"] = hamming_distance + distance = dgl.sum_nodes(self._batch_graphs, "h").expand_as(node_count) + self._batch_graphs.ndata.pop("h") + self._hamming_distance_among_samples_record[tick] = distance + + return + + def _on_action_received(self, event: CascadeEvent) -> None: + actions = event.payload + assert isinstance(actions, List) + + undecided_before_mask = self._vertex_states == VertexState.Deferred + + # Update Phase + for action in actions: + assert isinstance(action, Action) + undecided_mask = self._vertex_states == VertexState.Deferred + self._vertex_states[undecided_mask] = action.vertex_states[undecided_mask] + + # Clean-Up Phase: Set clashed node pairs to Deferred + included_mask = self._vertex_states == VertexState.Included + self._batch_graphs.ndata["h"] = included_mask.float() + self._batch_graphs.update_all(fn.copy_u(u="h", out="m"), fn.sum(msg="m", out="h")) + neighbor_included_mask = self._batch_graphs.ndata.pop("h").bool() + + # Clashed: if the node and its neighbor are both set to included + clashed_mask = included_mask & neighbor_included_mask + self._vertex_states[clashed_mask] = VertexState.Deferred + neighbor_included_mask[clashed_mask] = False + + # Clean-Up Phase: exclude the deferred vertex neighboring to an included one. + undecided_mask = self._vertex_states == VertexState.Deferred + self._vertex_states[undecided_mask & neighbor_included_mask] = VertexState.Excluded + + # Timeout handling + if event.tick + 1 == self._max_tick: + undecided_mask = self._vertex_states == VertexState.Deferred + self._vertex_states[undecided_mask] = VertexState.Excluded + + self._calculate_and_record_metrics(event.tick, undecided_before_mask) + self._update_is_done_mask() + + return + + def step(self, tick: int) -> None: + decision_payload = MISDecisionPayload( + graph=self._batch_graphs, + vertex_states=self._vertex_states.clone(), + ) + decision_event = self._event_buffer.gen_decision_event(tick, decision_payload) + self._event_buffer.insert_event(decision_event) + + def _generate_er_graph(self) -> dgl.DGLGraph: + num_nodes = random.randint(self._num_node_lower_bound, self._num_node_upper_bound) + + w = -1 + lp = math.log(1.0 - self._node_sample_probability) + + # Nodes in graph are from 0, num_nodes - 1 (start with v as the first node index). + v = 1 + u_list, v_list = [], [] + while v < num_nodes: + lr = math.log(1.0 - random.random()) + w = w + 1 + int(lr / lp) + while w >= v and v < num_nodes: + w = w - v + v = v + 1 + if v < num_nodes: + u_list.extend([v, w]) + v_list.extend([w, v]) + + graph = dgl.graph((u_list, v_list), num_nodes=num_nodes) + + return graph + + def reset(self, keep_seed: bool = False) -> None: + self._batch_graphs = dgl.batch([self._generate_er_graph() for _ in range(self._graph_batch_size)]) + self._batch_graphs.set_n_initializer(dgl.init.zero_initializer) + self._batch_graphs = self._batch_graphs.to(self._device) + + tensor_size = (self._batch_graphs.num_nodes(), self._num_samples) + self._vertex_states = torch.full(size=tensor_size, fill_value=VertexState.Deferred, device=self._device) + self._update_is_done_mask() + + self._num_included_record = {} + self._hamming_distance_among_samples_record = {} + return + + def _update_is_done_mask(self) -> None: + undecided_mask = self._vertex_states == VertexState.Deferred + self._batch_graphs.ndata["h"] = undecided_mask.float() + num_undecided = dgl.sum_nodes(self._batch_graphs, "h") + self._batch_graphs.ndata.pop("h") + self._is_done_mask = (num_undecided == 0) + return + + def post_step(self, tick: int) -> bool: + if tick + 1 == self._max_tick: + return True + + return torch.all(self._is_done_mask).item() + + def get_metrics(self) -> dict: + return { + MISEnvMetrics.IncludedNodeCount: self._num_included_record, + MISEnvMetrics.HammingDistanceAmongSamples: self._hamming_distance_among_samples_record, + MISEnvMetrics.IsDoneMasks: self._is_done_mask.cpu().detach().numpy(), + } + + +if __name__ == "__main__": + from maro.simulator import Env + device = torch.device("cuda:0") + + env = Env( + business_engine_cls=MISBusinessEngine, + options={ + "graph_batch_size": 4, + "num_samples": 2, + "device": device, + "num_node_lower_bound": 15, + "num_node_upper_bound": 20, + "node_sample_probability": 0.15, + }, + ) + + env.reset() + metrics, decision_event, done = env.step(None) + while not done: + assert isinstance(decision_event, MISDecisionPayload) + vertex_state = decision_event.vertex_states + undecided_mask = vertex_state == VertexState.Deferred + random_mask = torch.rand(vertex_state.size(), device=device) < 0.8 + vertex_state[undecided_mask & random_mask] = VertexState.Included + action = Action(vertex_state) + metrics, decision_event, done = env.step(action) + + for key in [MISEnvMetrics.IncludedNodeCount]: + print(f"[{env.tick - 1:02d}] {key:28s} {metrics[key][env.tick - 1].reshape(-1)}") + for key in [MISEnvMetrics.IsDoneMasks]: + print(f"[{env.tick - 1:02d}] {key:28s} {metrics[key].reshape(-1)}") From 56c003db6702e11d95afa856a976e7b2f3bdb1d0 Mon Sep 17 00:00:00 2001 From: Jinyu Wang Date: Fri, 19 May 2023 03:45:06 +0000 Subject: [PATCH 3/6] init ppo --- examples/mis/lwd/ppo/__init__.py | 65 ++++++++ examples/mis/lwd/ppo/graph_net.py | 124 +++++++++++++++ examples/mis/lwd/ppo/model.py | 209 ++++++++++++++++++++++++++ examples/mis/lwd/ppo/ppo.py | 186 +++++++++++++++++++++++ examples/mis/lwd/ppo/replay_memory.py | 150 ++++++++++++++++++ 5 files changed, 734 insertions(+) create mode 100644 examples/mis/lwd/ppo/__init__.py create mode 100644 examples/mis/lwd/ppo/graph_net.py create mode 100644 examples/mis/lwd/ppo/model.py create mode 100644 examples/mis/lwd/ppo/ppo.py create mode 100644 examples/mis/lwd/ppo/replay_memory.py diff --git a/examples/mis/lwd/ppo/__init__.py b/examples/mis/lwd/ppo/__init__.py new file mode 100644 index 000000000..607979975 --- /dev/null +++ b/examples/mis/lwd/ppo/__init__.py @@ -0,0 +1,65 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +from maro.rl.training.algorithms.ppo import PPOParams +from maro.rl.utils.common import get_env + +from examples.mis.lwd.ppo.model import GraphBasedPolicyNet, GraphBasedVNet +from examples.mis.lwd.ppo.ppo import GraphBasedPPOPolicy, GraphBasedPPOTrainer + + +def get_ppo_policy( + name: str, + state_dim: int, + action_num: int, + hidden_dim: int, + num_layers: int, + init_lr: float, +) -> GraphBasedPPOPolicy: + return GraphBasedPPOPolicy( + name=name, + policy_net=GraphBasedPolicyNet( + state_dim=state_dim, + action_num=action_num, + hidden_dim=hidden_dim, + num_layers=num_layers, + init_lr=init_lr, + ), + ) + + +def get_ppo_trainer( + name: str, + state_dim: int, + hidden_dim: int, + num_layers: int, + init_lr: float, + clip_ratio: float, + max_tick: int, + batch_size: int, + reward_discount: float, + graph_batch_size: int, + graph_num_samples: int, + num_train_epochs: int, + norm_base: float, +) -> GraphBasedPPOTrainer: + return GraphBasedPPOTrainer( + name=name, + params=PPOParams( + get_v_critic_net_func=lambda: GraphBasedVNet(state_dim, hidden_dim, num_layers, init_lr, norm_base), + grad_iters=1, + lam=None, # GAE not used here. + clip_ratio=clip_ratio, + ), + replay_memory_capacity=max_tick, + batch_size=batch_size, + reward_discount=reward_discount, + graph_batch_size=graph_batch_size, + graph_num_samples=graph_num_samples, + input_feature_size=state_dim, + num_train_epochs=num_train_epochs, + log_dir=get_env("LOG_PATH", required=False, default=None), + ) + + +__all__ = ["get_ppo_policy", "get_ppo_trainer"] diff --git a/examples/mis/lwd/ppo/graph_net.py b/examples/mis/lwd/ppo/graph_net.py new file mode 100644 index 000000000..e4c34d1a4 --- /dev/null +++ b/examples/mis/lwd/ppo/graph_net.py @@ -0,0 +1,124 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import dgl +import dgl.function as fn +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class GraphConvLayer(nn.Module): + def __init__( + self, in_feats: int, out_feats: int, norm: bool=True, jump: bool=True, bias: bool=True, activation=None, + ) -> None: + """The GraphConvLayer that can forward based on the given graph and graph node features. + + Args: + in_feats (int): The dimension of input feature. + out_feats (int): The dimension of the output tensor. + norm (bool): Add feature normalization operation or not. Defaults to True. + jump (bool): Add skip connections of the input feature to the aggregation or not. Defaults to True. + bias (bool): Add a learnable bias layer or not. Defaults to True. + activation (torch.nn.functional): The output activation function to use. Defaults to None. + """ + super(GraphConvLayer, self).__init__() + self._in_feats = in_feats + self._out_feats = out_feats + self._norm = norm + self._jump = jump + self._activation = activation + + if jump: + self.weight = nn.Parameter(torch.Tensor(2 * in_feats, out_feats)) + else: + self.weight = nn.Parameter(torch.Tensor(in_feats, out_feats)) + + if bias: + self.bias = nn.Parameter(torch.Tensor(out_feats)) + else: + self.register_parameter("bias", None) + + self.reset_parameters() + + def reset_parameters(self) -> None: + torch.nn.init.xavier_uniform_(self.weight) + if self.bias is not None: + torch.nn.init.zeros_(self.bias) + + def forward(self, feat: torch.Tensor, graph: dgl.DGLGraph, mask=None) -> torch.Tensor: + if self._jump: + _feat = feat + + if self._norm: + if mask is None: + norm = torch.pow(graph.in_degrees().float(), -0.5) + norm.masked_fill_(graph.in_degrees() == 0, 1.0) + shp = norm.shape + (1,) * (feat.dim() - 1) + norm = torch.reshape(norm, shp).to(feat.device) + feat = feat * norm.unsqueeze(1) + else: + graph.ndata["h"] = mask.float() + graph.update_all(fn.copy_u(u="h", out="m"), fn.sum(msg="m", out="h")) + masked_deg = graph.ndata.pop("h") + norm = torch.pow(masked_deg, -0.5) + norm.masked_fill_(masked_deg == 0, 1.0) + feat = feat * norm.unsqueeze(-1) + + if mask is not None: + feat = mask.float().unsqueeze(-1) * feat + + graph.ndata["h"] = feat + graph.update_all(fn.copy_u(u="h", out="m"), fn.sum(msg="m", out="h")) + rst = graph.ndata.pop("h") + + if self._norm: + rst = rst * norm.unsqueeze(-1) + + if self._jump: + rst = torch.cat([rst, _feat], dim=-1) + + rst = torch.matmul(rst, self.weight) + + if self.bias is not None: + rst = rst + self.bias + + if self._activation is not None: + rst = self._activation(rst) + + return rst + + +class GraphConvNet(nn.Module): + def __init__( + self, + input_dim: int, + output_dim: int, + hidden_dim: int, + num_layers: int, + activation=F.relu, + out_activation=None, + ) -> None: + """The GraphConvNet constructed with multiple GraphConvLayers. + + Args: + input_dim (int): The dimension of the input feature. + output_dim (int): The dimension of the output tensor. + hidden_dim (int): The dimension of the hidden layers. + num_layers (int): How many layers in this GraphConvNet in total, including the input and output layer. >= 2. + activation (torch.nn.functional): The activation function used in input layer and hidden layers. Defaults to + torch.nn.functional.relu. + out_activation (torch.nn.functional): The output activation function to use. Defaults to None. + """ + super(GraphConvNet, self).__init__() + + self.layers = nn.ModuleList( + [GraphConvLayer(input_dim, hidden_dim, activation=activation)] + + [GraphConvLayer(hidden_dim, hidden_dim, activation=activation) for _ in range(num_layers - 2)] + + [GraphConvLayer(hidden_dim, output_dim, activation=out_activation)] + ) + + def forward(self, h: torch.Tensor, graph: dgl.DGLGraph, mask=None) -> torch.Tensor: + for layer in self.layers: + h = layer(h, graph, mask=mask) + return h diff --git a/examples/mis/lwd/ppo/model.py b/examples/mis/lwd/ppo/model.py new file mode 100644 index 000000000..f6fab8b99 --- /dev/null +++ b/examples/mis/lwd/ppo/model.py @@ -0,0 +1,209 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +from typing import Tuple + +import dgl +import torch +from torch.distributions import Categorical +from torch.optim import Adam + +from maro.rl.model import VNet, DiscretePolicyNet + +from examples.mis.lwd.ppo.graph_net import GraphConvNet +from examples.mis.lwd.simulator import VertexState + + +VertexStateIndex = 0 + + +def get_masks_idxs_subgraph_h(obs: torch.Tensor, graph: dgl.DGLGraph) -> Tuple[ + torch.Tensor, torch.Tensor, torch.Tensor, dgl.DGLGraph, torch.Tensor, +]: + """Extract masks and input feature information for the deferred vertexes. + + Args: + obs (torch.Tensor): The observation tensor with shape (num_nodes, num_samples, feature_size). + graph (dgl.DGLGraph): The input graph. + + Returns: + Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + - undecided_node_mask, with shape (num_nodes, num_samples) + - subgraph_mask, with shape (num_nodes) + - subgraph_node_mask, with shape (num_nodes, num_samples) + Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + - flatten_node_idxs, with shape (num_nodes * num_samples) + - flatten_subgraph_idxs, with shape (num_nodes) + - flatten_subgraph_node_idxs, with shape (num_nodes * num_samples) + dgl.DGLGraph: + torch.Tensor: input tensor with shape (num_nodes, num_samples, 2) + """ + # Mask tensor with shape (num_nodes, num_samples) + undecided_node_mask = obs.select(2, VertexStateIndex).long() == VertexState.Deferred + # Flatten index tensor with shape (num_nodes * num_samples) + flatten_node_idxs = undecided_node_mask.reshape(-1).nonzero().squeeze(1) + + # Mask tensor with shape (num_nodes) + subgraph_mask = undecided_node_mask.any(dim=1) + # Flatten index tensor with shape (num_nodes) + flatten_subgraph_idxs = subgraph_mask.nonzero().squeeze(1) + + # Mask tensor with shape (num_nodes, num_samples) + subgraph_node_mask = undecided_node_mask.index_select(0, flatten_subgraph_idxs) + # Flatten index tensor with shape (num_nodes * num_samples) + flatten_subgraph_node_idxs = subgraph_node_mask.view(-1).nonzero().squeeze(1) + + # Extract a subgraph with only node in flatten_subgraph_idxs, batch_size -> 1 + subgraph = graph.subgraph(flatten_subgraph_idxs) + + # The observation of the deferred vertexes. + h = obs.index_select(0, flatten_subgraph_idxs) + + num_nodes, num_samples = obs.size(0), obs.size(1) + return subgraph_node_mask, flatten_node_idxs, flatten_subgraph_node_idxs, subgraph, h, num_nodes, num_samples + + +class GraphBasedPolicyNet(DiscretePolicyNet): + def __init__( + self, + state_dim: int, + action_num: int, + hidden_dim: int, + num_layers: int, + init_lr: float, + ) -> None: + """A discrete policy net implemented with a graph as input. + + Args: + state_dim (int): The dimension of the input state for this policy net. + action_num (int): The number of pre-defined discrete actions, i.e., the size of the discrete action space. + hidden_dim (int): The dimension of the hidden layers used in the GraphConvNet of the actor. + num_layers (int): The number of layers of the GraphConvNet of the actor. + init_lr (float): The initial learning rate of the optimizer. + """ + super(GraphBasedPolicyNet, self).__init__(state_dim, action_num) + + self._actor = GraphConvNet( + input_dim=state_dim, + output_dim=action_num, + hidden_dim=hidden_dim, + num_layers=num_layers, + ) + with torch.no_grad(): + self._actor.layers[-1].bias[2].add_(3.0) + + self._optim = Adam(self._actor.parameters(), lr=init_lr) + + def _get_action_probs_impl(self, states: torch.Tensor, **kwargs) -> torch.Tensor: + raise NotImplementedError + + def _get_actions_impl(self, states: torch.Tensor, exploring: bool, **kwargs) -> torch.Tensor: + action, _ = self._get_actions_with_logps_impl(states, exploring, **kwargs) + return action + + def _get_actions_with_probs_impl( + self, states: torch.Tensor, exploring: bool, **kwargs + ) -> Tuple[torch.Tensor, torch.Tensor]: + raise NotImplementedError + + def _get_actions_with_logps_impl( + self, states: torch.Tensor, exploring: bool, **kwargs + ) -> Tuple[torch.Tensor, torch.Tensor]: + assert "graph" in kwargs, f"graph is required to given in kwargs" + graph = kwargs["graph"] + subg_mask, node_idxs, subg_node_idxs, subg, h, num_nodes, num_samples = get_masks_idxs_subgraph_h(states, graph) + + # Compute logits to get action, logits: shape (num_nodes * num_samples, 3) + logits = self._actor(h, subg, mask=subg_mask).view(-1, self.action_num).index_select(0, subg_node_idxs) + + action = torch.zeros(num_nodes * num_samples, dtype=torch.long, device=self._device) + action_log_probs = torch.zeros(num_nodes * num_samples, device=self._device) + + # NOTE: here we do not distinguish exploration mode and exploitation mode. + # The main reason here for doing so is that the LwD modeling is learnt to better exploration, + # the final result is chosen from the sampled trajectories. + m = Categorical(logits=logits) + action[node_idxs] = m.sample() + action_log_probs[node_idxs] = m.log_prob(action.index_select(0, node_idxs)) + + action = action.view(-1, num_samples) + action_log_probs = action_log_probs.view(-1, num_samples) + + return action, action_log_probs + + def _get_states_actions_probs_impl(self, states: torch.Tensor, actions: torch.Tensor, **kwargs) -> torch.Tensor: + raise NotImplementedError + + def _get_states_actions_logps_impl(self, states: torch.Tensor, actions: torch.Tensor, **kwargs) -> torch.Tensor: + assert "graph" in kwargs, f"graph is required to given in kwargs" + graph = kwargs["graph"] + subg_mask, node_idxs, subg_node_idxs, subg, h, num_nodes, num_samples = get_masks_idxs_subgraph_h(states, graph) + + # compute logits to get action + logits = self._actor(h, subg, mask=subg_mask).view(-1, self.action_num).index_select(0, subg_node_idxs) + + try: + m = Categorical(logits=logits) + except Exception: + print(f"[GraphBasedPolicyNet] flatten_subgraph_node_idxs with shape {subg_node_idxs.shape}") + print(f"[GraphBasedPolicyNet] logits with shape {logits.shape}") + return None + + # compute log probability of actions per node + actions = actions.reshape(-1) + action_log_probs = torch.zeros(num_nodes * num_samples, device=self._device) + action_log_probs[node_idxs] = m.log_prob(actions.index_select(0, node_idxs)) + action_log_probs = action_log_probs.view(-1, num_samples) + + return action_log_probs + + def get_actions(self, states: torch.Tensor, exploring: bool, **kwargs) -> torch.Tensor: + actions = self._get_actions_impl(states, exploring, **kwargs) + return actions + + def get_states_actions_logps(self, states: torch.Tensor, actions: torch.Tensor, **kwargs) -> torch.Tensor: + logps = self._get_states_actions_logps_impl(states, actions, **kwargs) + return logps + + +class GraphBasedVNet(VNet): + def __init__(self, state_dim: int, hidden_dim: int, num_layers: int, init_lr: float, norm_base: float) -> None: + """A value net implemented with a graph as input. + + Args: + state_dim (int): The dimension of the input state for this value network. + hidden_dim (int): The dimension of the hidden layers used in the GraphConvNet of the critic. + num_layers (int): The number of layers of the GraphConvNet of the critic. + init_lr (float): The initial learning rate of the optimizer. + norm_base (float): The normalization base for the predicted value. The critic network will predict the value + of each node, and the returned v value is defined as `Sum(predicted_node_values) / normalization_base`. + """ + super(GraphBasedVNet, self).__init__(state_dim) + self._critic = GraphConvNet( + input_dim=state_dim, + output_dim=1, + hidden_dim=hidden_dim, + num_layers=num_layers, + ) + self._optim = Adam(self._critic.parameters(), lr=init_lr) + self._normalization_base = norm_base + + def _get_v_values(self, states: torch.Tensor, **kwargs) -> torch.Tensor: + assert "graph" in kwargs, f"graph is required to given in kwargs" + graph = kwargs["graph"] + subg_mask, node_idxs, subg_node_idxs, subg, h, num_nodes, num_samples = get_masks_idxs_subgraph_h(states, graph) + + values = self._critic(h, subg, mask=subg_mask).view(-1).index_select(0, subg_node_idxs) + # Init node value prediction, shape (num_nodes * num_samples) + node_value_preds = torch.zeros(num_nodes * num_samples, device=self._device) + node_value_preds[node_idxs] = values + + graph.ndata["h"] = node_value_preds.view(-1, num_samples) + value_pred = dgl.sum_nodes(graph, "h") / self._normalization_base + graph.ndata.pop("h") + + return value_pred + + def v_values(self, states: torch.Tensor, **kwargs) -> torch.Tensor: + v = self._get_v_values(states, **kwargs) + return v diff --git a/examples/mis/lwd/ppo/ppo.py b/examples/mis/lwd/ppo/ppo.py new file mode 100644 index 000000000..673fcf79a --- /dev/null +++ b/examples/mis/lwd/ppo/ppo.py @@ -0,0 +1,186 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import math +from typing import List, Tuple, cast + +import dgl +import torch + +from maro.rl.model import DiscretePolicyNet +from maro.rl.policy import DiscretePolicyGradient, RLPolicy +from maro.rl.training.algorithms.base import ACBasedOps, ACBasedParams, ACBasedTrainer +from maro.rl.utils import ndarray_to_tensor +from maro.utils import LogFormat, Logger + +from examples.mis.lwd.ppo.replay_memory import BatchGraphReplayMemory, GraphBasedExpElement, GraphBasedTransitionBatch + + +class GraphBasedPPOTrainOps(ACBasedOps): + def __init__( + self, + name: str, + policy: RLPolicy, + params: ACBasedParams, + reward_discount: float, + parallelism: int = 1, + ) -> None: + super().__init__(name, policy, params, reward_discount, parallelism) + + self._clip_lower_bound = math.log(1.0 - self._clip_ratio) + self._clip_upper_bound = math.log(1.0 + self._clip_ratio) + + def _get_critic_loss(self, batch: GraphBasedTransitionBatch) -> torch.Tensor: + states = ndarray_to_tensor(batch.states, device=self._device).permute(1, 0, 2) + returns = ndarray_to_tensor(batch.returns, device=self._device) + + self._v_critic_net.train() + kwargs = {"graph": batch.graph} + value_preds = self._v_critic_net.v_values(states, **kwargs).permute(1, 0) + critic_loss = 0.5 * (value_preds - returns).pow(2).mean() + return critic_loss + + def _get_actor_loss(self, batch: GraphBasedTransitionBatch) -> Tuple[torch.Tensor, bool]: + graph = batch.graph + kwargs = {"graph": batch.graph} + + states = ndarray_to_tensor(batch.states, device=self._device).permute(1, 0, 2) + actions = ndarray_to_tensor(batch.actions, device=self._device).permute(1, 0) + advantages = ndarray_to_tensor(batch.advantages, device=self._device) + logps_old = ndarray_to_tensor(batch.old_logps, device=self._device) + if self._is_discrete_action: + actions = actions.long() + + self._policy.train() + logps = self._policy.get_states_actions_logps(states, actions, **kwargs).permute(1, 0) + diff = logps - logps_old + clamped_diff = torch.clamp(diff, self._clip_lower_bound, self._clip_upper_bound) + stacked_diff = torch.stack([diff, clamped_diff], dim=2) + + graph.ndata["h"] = stacked_diff.permute(1, 0, 2) + h = dgl.sum_nodes(graph, "h").permute(1, 0, 2) + graph.ndata.pop("h") + ratio = torch.exp(h.select(2, 0)) + clamped_ratio = torch.exp(h.select(2, 1)) + + actor_loss = -torch.min(ratio * advantages, clamped_ratio * advantages).mean() + return actor_loss, False # TODO: add early-stop logic if needed + + +class GraphBasedPPOTrainer(ACBasedTrainer): + def __init__( + self, + name: str, + params: ACBasedParams, + replay_memory_capacity: int = 32, + batch_size: int = 16, + data_parallelism: int = 1, + reward_discount: float = 0.9, + graph_batch_size: int = 4, + graph_num_samples: int = 2, + input_feature_size: int = 2, + num_train_epochs: int = 4, + log_dir: str = None, + ) -> None: + super().__init__(name, params, replay_memory_capacity, batch_size, data_parallelism, reward_discount) + + self._graph_batch_size = graph_batch_size + self._graph_num_samples = graph_num_samples + self._input_feature_size = input_feature_size + self._num_train_epochs = num_train_epochs + + self._trainer_logger = None + if log_dir is not None: + self._trainer_logger = Logger( + tag=self.name, + format_=LogFormat.none, + dump_folder=log_dir, + dump_mode="w", + extension_name="csv", + auto_timestamp=False, + stdout_level="INFO", + ) + self._trainer_logger.debug( + "Steps,Mean Reward,Mean Return,Mean Advantage,0-Action,1-Action,2-Action,Critic Loss,Actor Loss" + ) + + def build(self) -> None: + self._ops = cast(GraphBasedPPOTrainOps, self.get_ops()) + self._replay_memory = BatchGraphReplayMemory( + max_t=self._replay_memory_capacity, + graph_batch_size=self._graph_batch_size, + num_samples=self._graph_num_samples, + feature_size=self._input_feature_size, + ) + + def record_multiple(self, env_idx: int, exp_elements: List[GraphBasedExpElement]) -> None: + self._replay_memory.reset() + + self._ops._v_critic_net.eval() + self._ops._policy.eval() + + for exp in exp_elements: + state = ndarray_to_tensor(exp.state, self._ops._device) + action = ndarray_to_tensor(exp.action, self._ops._device) + value_pred = self._ops._v_critic_net.v_values(state, graph=exp.graph).cpu().detach().numpy() + logps = self._ops._policy.get_states_actions_logps(state, action, graph=exp.graph).cpu().detach().numpy() + self._replay_memory.add_transition(exp, value_pred, logps) + + self._ops._v_critic_net.train() + self._ops._policy.train() + + def get_local_ops(self) -> GraphBasedPPOTrainOps: + return GraphBasedPPOTrainOps( + name=self._policy.name, + policy=self._policy, + parallelism=self._data_parallelism, + reward_discount=self._reward_discount, + params=self._params, + ) + + def train_step(self) -> None: + assert isinstance(self._ops, GraphBasedPPOTrainOps) + + data_loader = self._replay_memory.build_update_sampler( + batch_size=self._batch_size, + num_train_epochs=self._num_train_epochs, + gamma=self._reward_discount, + ) + + statistics = self._replay_memory.get_statistics() + + avg_critic_loss, avg_actor_loss = 0, 0 + for batch in data_loader: + for _ in range(self._params.grad_iters): + critic_loss = self._ops.update_critic(batch) + actor_loss, _ = self._ops.update_actor(batch) + avg_critic_loss += critic_loss + avg_actor_loss += actor_loss + avg_critic_loss /= self._params.grad_iters + avg_actor_loss /= self._params.grad_iters + + if self._trainer_logger is not None: + self._trainer_logger.debug( + f"{statistics['step_t']}," + f"{statistics['reward']}," + f"{statistics['return']}," + f"{statistics['advantage']}," + f"{statistics['action_0']}," + f"{statistics['action_1']}," + f"{statistics['action_2']}," + f"{avg_critic_loss}," + f"{avg_actor_loss}" + ) + + +class GraphBasedPPOPolicy(DiscretePolicyGradient): + def __init__(self, name: str, policy_net: DiscretePolicyNet, trainable: bool = True, warmup: int = 0) -> None: + super(GraphBasedPPOPolicy, self).__init__(name, policy_net, trainable, warmup) + + def get_actions_tensor(self, states: torch.Tensor, **kwargs) -> torch.Tensor: + actions = self._get_actions_impl(states, **kwargs) + return actions + + def get_states_actions_logps(self, states: torch.Tensor, actions: torch.Tensor, **kwargs) -> torch.Tensor: + logps = self._get_states_actions_logps_impl(states, actions, **kwargs) + return logps diff --git a/examples/mis/lwd/ppo/replay_memory.py b/examples/mis/lwd/ppo/replay_memory.py new file mode 100644 index 000000000..fc6f2a185 --- /dev/null +++ b/examples/mis/lwd/ppo/replay_memory.py @@ -0,0 +1,150 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Dict + +import dgl +import numpy as np +from torch.utils.data.sampler import BatchSampler, SubsetRandomSampler + + +@dataclass +class GraphBasedExpElement: + state: np.ndarray + action: np.ndarray + reward: np.ndarray + is_done: np.ndarray + graph: dgl.DGLGraph + + def split_contents_by_trainer(self, agent2trainer: Dict[Any, str]) -> Dict[str, GraphBasedExpElement]: + """Split the ExpElement's contents by trainer. + + Args: + agent2trainer (Dict[Any, str]): Mapping of agent name and trainer name. + + Returns: + Contents (Dict[str, ExpElement]): A dict that contains the ExpElements of all trainers. The key of this + dict is the trainer name. + """ + return {trainer_name: self for trainer_name in agent2trainer.values()} + + +@dataclass +class GraphBasedTransitionBatch: + states: np.ndarray + actions: np.ndarray + returns: np.ndarray + graph: dgl.DGLGraph + advantages: np.ndarray + old_logps: np.ndarray + + +class BatchGraphReplayMemory: + def __init__( + self, + max_t: int, + graph_batch_size: int, + num_samples: int, + feature_size: int, + ) -> None: + self.max_t = max_t + self.graph_batch_size: int = graph_batch_size + self.num_nodes: int = None + self.num_samples: int = num_samples + self.feature_size: int = feature_size + self._t = 0 + + self.graph: dgl.DGLGraph = None + self.states: np.ndarray = None # shape [max_t + 1, num_nodes, num_samples, feature_size] + self.actions: np.ndarray = None # shape [max_t, num_nodes, num_samples] + self.action_logps: np.ndarray = None + + array_size = (self.max_t + 1, graph_batch_size, num_samples) + self.rewards: np.ndarray = np.zeros(array_size, dtype=np.float32) + self.is_done: np.ndarray = np.ones(array_size, dtype=np.int8) + self.returns: np.ndarray = np.zeros(array_size, dtype=np.float32) + self.advantages: np.ndarray = np.zeros(array_size, dtype=np.float32) + self.value_preds: np.ndarray = np.zeros(array_size, dtype=np.float32) + + def _init_storage(self, graph: dgl.DGLGraph) -> None: + self.graph = graph + self.num_nodes = graph.num_nodes() + + array_size = (self.max_t + 1, self.num_nodes, self.num_samples) + self.states = np.zeros((*array_size, self.feature_size), dtype=np.float32) + self.actions = np.zeros(array_size, dtype=np.float32) + self.action_logps = np.zeros(array_size, dtype=np.float32) + + def add_transition(self, exp_element: GraphBasedExpElement, value_pred: np.ndarray, logps: np.ndarray) -> None: + if self._t == 0: + assert exp_element.graph is not None + self._init_storage(exp_element.graph) + + self.states[self._t] = exp_element.state + self.actions[self._t] = exp_element.action + self.rewards[self._t] = exp_element.reward + self.value_preds[self._t] = value_pred + self.action_logps[self._t] = logps + self.is_done[self._t + 1] = exp_element.is_done + + self._t += 1 + + def build_update_sampler(self, batch_size: int, num_train_epochs: int, gamma: float) -> GraphBasedTransitionBatch: + for t in reversed(range(self.max_t)): + self.returns[t] = self.rewards[t] + gamma * (1 - self.is_done[t + 1]) * self.returns[t + 1] + + advantages = self.returns[:-1] - self.value_preds[:-1] + self.advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-5) + + flat_states = np.transpose(self.states[: self._t], (0, 2, 1, 3)).reshape(-1, self.num_nodes, self.feature_size) + flat_actions = np.transpose(self.actions[: self._t], (0, 2, 1)).reshape(-1, self.num_nodes) + flat_returns = np.transpose(self.returns[: self._t], (0, 2, 1)).reshape(-1, self.graph_batch_size) + flat_advantages = np.transpose(self.advantages[: self._t], (0, 2, 1)).reshape(-1, self.graph_batch_size) + flat_action_logps = np.transpose(self.action_logps[: self._t], (0, 2, 1)).reshape(-1, self.num_nodes) + + flat_dim = flat_states.shape[0] + + sampler = BatchSampler( + sampler=SubsetRandomSampler(range(flat_dim)), + batch_size=min(flat_dim, batch_size), + drop_last=False, + ) + + sampler_t = 0 + while sampler_t < num_train_epochs: + for idx in sampler: + yield GraphBasedTransitionBatch( + states=flat_states[idx], + actions=flat_actions[idx], + returns=flat_returns[idx], + graph=self.graph, + advantages=flat_advantages[idx], + old_logps=flat_action_logps[idx], + ) + + sampler_t += 1 + if sampler_t == num_train_epochs: + break + + def reset(self) -> None: + self.is_done[0] = 0 + self._t = 0 + + def get_statistics(self) -> Dict[str, float]: + action_0_count = np.count_nonzero(self.actions[: self._t] == 0) + action_1_count = np.count_nonzero(self.actions[: self._t] == 1) + action_2_count = np.count_nonzero(self.actions[: self._t] == 2) + action_count = action_0_count + action_1_count + action_2_count + assert action_count == self.actions[: self._t].size + return { + "step_t": self._t, + "action_0": action_0_count / action_count, + "action_1": action_1_count / action_count, + "action_2": action_2_count / action_count, + "reward": self.rewards[: self._t].mean(), + "return": self.returns[: self._t].mean(), + "advantage": self.advantages[: self._t].mean(), + } From aa247dc9dd1c9d58f4cc65eb4bcc8cdcd76c6b3e Mon Sep 17 00:00:00 2001 From: Jinyu Wang Date: Fri, 19 May 2023 07:17:33 +0000 Subject: [PATCH 4/6] init env_sampler --- examples/mis/lwd/env_sampler/baseline.py | 46 +++ .../mis/lwd/env_sampler/mis_env_sampler.py | 343 ++++++++++++++++++ .../mis/lwd/simulator/mis_business_engine.py | 25 +- 3 files changed, 409 insertions(+), 5 deletions(-) create mode 100644 examples/mis/lwd/env_sampler/baseline.py create mode 100644 examples/mis/lwd/env_sampler/mis_env_sampler.py diff --git a/examples/mis/lwd/env_sampler/baseline.py b/examples/mis/lwd/env_sampler/baseline.py new file mode 100644 index 000000000..3282d1a88 --- /dev/null +++ b/examples/mis/lwd/env_sampler/baseline.py @@ -0,0 +1,46 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import random +from typing import Dict, List + + +def _choose_by_weight(graph: Dict[int, List[int]], node2weight: Dict[int, float]) -> List[int]: + """Choose node in the order of descending weight if not blocked.. + + Args: + graph (Dict[int, List[int]]): The adjacent matrix of the target graph. The key is the node id of each node. The + value is a list of each node's neighbor nodes. + node2weight (Dict[int, float]): The node to weight dictionary with node id as key and node weight as value. + + Returns: + List[int]: A list of chosen node id. + """ + node_weight_list = [(node, weight) for node, weight in node2weight.items()] + # Shuffle the candidates to get random result in the case there are nodes sharing the same weight. + random.shuffle(node_weight_list) + # Sort node candidates with descending weight. + sorted_nodes = sorted(node_weight_list, key=lambda x: x[1], reverse=True) + + chosen_node_id_set: set = set() + blocked_node_id_set: set = set() + # Choose node in the order of descending weight if it is not blocked yet by the chosen nodes. + for node, _ in sorted_nodes: + if node in blocked_node_id_set: + continue + chosen_node_id_set.add(node) + for neighbor_node in graph[node]: + blocked_node_id_set.add(neighbor_node) + + chosen_node_ids = [node for node in chosen_node_id_set] + return chosen_node_ids + +def uniform_mis_solver(graph: Dict[int, List[int]]) -> List[int]: + node2weight: Dict[int, float] = {node: 1 for node in graph.keys()} + chosen_node_list = _choose_by_weight(graph, node2weight) + return chosen_node_list + +def greedy_mis_solver(graph: Dict[int, List[int]]) -> List[int]: + node2weight: Dict[int, float] = {node: 1 / (1 + len(neighbor_list)) for node, neighbor_list in graph.items()} + chosen_node_list = _choose_by_weight(graph, node2weight) + return chosen_node_list diff --git a/examples/mis/lwd/env_sampler/mis_env_sampler.py b/examples/mis/lwd/env_sampler/mis_env_sampler.py new file mode 100644 index 000000000..f1d2e4024 --- /dev/null +++ b/examples/mis/lwd/env_sampler/mis_env_sampler.py @@ -0,0 +1,343 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import os +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import torch + +from maro.rl.policy.abs_policy import AbsPolicy +from maro.rl.rollout import SimpleAgentWrapper, AbsEnvSampler, CacheElement +from maro.rl.utils import ndarray_to_tensor +from maro.rl.workflows.callback import Callback +from maro.simulator.core import Env + +from examples.mis.lwd.env_sampler.baseline import greedy_mis_solver, uniform_mis_solver +from examples.mis.lwd.ppo.ppo import GraphBasedPPOPolicy +from examples.mis.lwd.ppo.replay_memory import GraphBasedExpElement +from examples.mis.lwd.simulator import Action, MISDecisionPayload, MISEnvMetrics, MISBusinessEngine + + +class MISAgentWrapper(SimpleAgentWrapper): + def __init__(self, policy_dict: Dict[str, AbsPolicy], agent2policy: Dict[Any, str]) -> None: + super().__init__(policy_dict, agent2policy) + + def _choose_actions_impl(self, state_by_agent: Dict[Any, torch.Tensor]) -> Dict[Any, np.ndarray]: + assert len(state_by_agent) == 1 + for agent_name, state in state_by_agent.items(): + break + + policy_name = self._agent2policy[agent_name] + policy = self._policy_dict[policy_name] + assert isinstance(policy, GraphBasedPPOPolicy) + + assert isinstance(state, Tuple) + assert len(state) == 2 + action = policy.get_actions(state[0], graph=state[1]) + return {agent_name: action} + + +class MISEnvSampler(AbsEnvSampler): + def __init__( + self, + learn_env: Env, + test_env: Env, + policies: List[AbsPolicy], + agent2policy: Dict[Any, str], + trainable_policies: List[str] = None, + reward_eval_delay: int = None, + max_episode_length: int = None, + diversity_reward_coef: float = 0.1, + reward_normalization_base: float = None + ) -> None: + super(MISEnvSampler, self).__init__( + learn_env=learn_env, + test_env=test_env, + policies=policies, + agent2policy=agent2policy, + trainable_policies=trainable_policies, + agent_wrapper_cls=MISAgentWrapper, + reward_eval_delay=reward_eval_delay, + max_episode_length=max_episode_length, + ) + be = learn_env.business_engine + assert isinstance(be, MISBusinessEngine) + self._device = be._device + self._diversity_reward_coef = diversity_reward_coef + self._reward_normalization_base = reward_normalization_base + + self._sample_metrics: List[tuple] = [] + self._eval_metrics: List[tuple] = [] + + def _get_global_and_agent_state( + self, + event: Any, + tick: int = None, + ) -> Tuple[Optional[Any], Dict[Any, Union[np.ndarray, list]]]: + return self._get_global_and_agent_state_impl(event, tick) + + def _get_global_and_agent_state_impl( + self, + event: MISDecisionPayload, + tick: int = None, + ) -> Tuple[Union[None, np.ndarray, list], Dict[Any, Union[np.ndarray, list]]]: + vertex_states = event.vertex_states.unsqueeze(2).float().cpu() + normalized_tick = torch.full(vertex_states.size(), tick / self.env._business_engine._max_tick) + state = torch.cat([vertex_states, normalized_tick], dim=2).cpu().detach().numpy() + return None, {0: (state, event.graph)} + + def _translate_to_env_action(self, action_dict: dict, event: Any) -> dict: + return {k: Action(vertex_states=ndarray_to_tensor(v, self._device)) for k, v in action_dict.items()} + + def _get_reward(self, env_action_dict: dict, event: Any, tick: int) -> Dict[Any, float]: + be = self.env.business_engine + assert isinstance(be, MISBusinessEngine) + + cardinality_record_dict = self.env.metrics[MISEnvMetrics.IncludedNodeCount] + hamming_distance_record_dict = self.env.metrics[MISEnvMetrics.HammingDistanceAmongSamples] + + assert (tick - 1) in cardinality_record_dict + cardinality_reward = cardinality_record_dict[tick - 1] - cardinality_record_dict.get(tick - 2, 0) + + assert (tick - 1) in hamming_distance_record_dict + diversity_reward = hamming_distance_record_dict[tick - 1] + + reward = cardinality_reward + self._diversity_reward_coef * diversity_reward + reward /= self._reward_normalization_base + + return {0: reward.cpu().detach().numpy()} + + def _eval_baseline(self) -> Dict[str, float]: + assert isinstance(self.env.business_engine, MISBusinessEngine) + graph_list: List[Dict[int, List[int]]] = self.env.business_engine._batch_adjs + num_samples: int = self.env.business_engine._num_samples + + def best_among_samples( + solver: Callable[[Dict[int, List[int]]], List[int]], + num_samples: int, + graph: Dict[int, List[int]], + ) -> int: + res = 0 + for _ in range(num_samples): + res = max(res, solver(graph)) + return res + + graph_size_list = [len(graph) for graph in graph_list] + uniform_size_list = [best_among_samples(uniform_mis_solver, num_samples, graph) for graph in graph_list] + greedy_size_list = [best_among_samples(greedy_mis_solver, num_samples, graph) for graph in graph_list] + + return { + "graph_size": np.mean(graph_size_list), + "uniform_size": np.mean(uniform_size_list), + "greedy_size": np.mean(greedy_size_list), + } + + def sample(self, policy_state: Optional[Dict[str, Dict[str, Any]]] = None, num_steps: Optional[int] = None) -> dict: + if policy_state is not None: # Update policy state if necessary + self.set_policy_state(policy_state) + self._switch_env(self._learn_env) # Init the env + self._agent_wrapper.explore() # Collect experience + + # One complete episode in one sample call here. + self._reset() + experiences: List[GraphBasedExpElement] = [] + + while not self._end_of_episode: + state = self._agent_state_dict[0][0] + graph = self._agent_state_dict[0][1] + # Get agent actions and translate them to env actions + action_dict = self._agent_wrapper.choose_actions(self._agent_state_dict) + env_action_dict = self._translate_to_env_action(action_dict, self._event) + # Update env and get new states (global & agent) + self._step(list(env_action_dict.values())) + assert self._reward_eval_delay is None + reward_dict = self._get_reward(env_action_dict, self._event, self.env.tick) + + experiences.append( + GraphBasedExpElement( + state=state, + action=action_dict[0], + reward=reward_dict[0], + is_done=self.env.metrics[MISEnvMetrics.IsDoneMasks], + graph=graph, + ) + ) + + self._total_number_interactions += 1 + self._current_episode_length += 1 + self._post_step(None) + + return { + "experiences": [experiences], + "info": [], + } + + def eval(self, policy_state: Dict[str, Dict[str, Any]] = None, num_episodes: int = 1) -> dict: + self._switch_env(self._test_env) + info_list = [] + + for _ in range(num_episodes): + self._reset() + + baseline_info = self._eval_baseline() + info_list.append(baseline_info) + + if policy_state is not None: + self.set_policy_state(policy_state) + + self._agent_wrapper.exploit() + while not self._end_of_episode: + action_dict = self._agent_wrapper.choose_actions(self._agent_state_dict) + env_action_dict = self._translate_to_env_action(action_dict, self._event) + # Update env and get new states (global & agent) + self._step(list(env_action_dict.values())) + + self._post_eval_step(None) + + return {"info": info_list} + + def _post_step(self, cache_element: CacheElement) -> None: + if not (self._end_of_episode or self.truncated): + return + + node_count_record = self.env.metrics[MISEnvMetrics.IncludedNodeCount] + num_steps = max(node_count_record.keys()) + 1 + # Report the mean among samples as the average MIS size in rollout. + num_nodes = torch.mean(node_count_record[num_steps - 1]).item() + + self._sample_metrics.append((num_steps, num_nodes)) + + def _post_eval_step(self, cache_element: CacheElement) -> None: + if not (self._end_of_episode or self.truncated): + return + + node_count_record = self.env.metrics[MISEnvMetrics.IncludedNodeCount] + num_steps = max(node_count_record.keys()) + 1 + # Report the maximum among samples as the MIS size in evaluation. + num_nodes = torch.mean(torch.max(node_count_record[num_steps - 1], dim=1).values).item() + + self._eval_metrics.append((num_steps, num_nodes)) + + def post_collect(self, info_list: list, ep: int) -> None: + assert len(self._sample_metrics) == 1, f"One Episode for One Rollout/Collection in Current Workflow Design." + num_steps, mis_size = self._sample_metrics[0] + + cur = { + "n_steps": num_steps, + "avg_mis_size": mis_size, + "n_interactions": self._total_number_interactions, + } + self.metrics.update(cur) + + # clear validation metrics + self.metrics = {k: v for k, v in self.metrics.items() if not k.startswith("val/")} + self._sample_metrics.clear() + + def post_evaluate(self, info_list: list, ep: int) -> None: + num_eval = len(self._eval_metrics) + assert num_eval > 0, f"Num evaluation rounds much be positive!" + + cur = { + "val/num_eval": num_eval, + "val/avg_n_steps": np.mean([n for n, _ in self._eval_metrics]), + "val/avg_mis_size": np.mean([s for _, s in self._eval_metrics]), + "val/std_mis_size": np.std([s for _, s in self._eval_metrics]), + "val/avg_graph_size": np.mean([info["graph_size"] for info in info_list]), + "val/uniform_size": np.mean([info["uniform_size"] for info in info_list]), + "val/uniform_std": np.std([info["uniform_size"] for info in info_list]), + "val/greedy_size": np.mean([info["greedy_size"] for info in info_list]), + "val/greedy_std": np.std([info["greedy_size"] for info in info_list]), + } + self.metrics.update(cur) + self._eval_metrics.clear() + + +class MISPlottingCallback(Callback): + def __init__(self, log_dir: str) -> None: + super().__init__() + self._log_dir = log_dir + + def _plot_trainer(self) -> None: + for trainer_name in self.training_manager._trainer_dict.keys(): + df = pd.read_csv(os.path.join(self._log_dir, f"{trainer_name}.csv")) + columns = [ + "Steps", + "Mean Reward", + "Mean Return", + "Mean Advantage", + "0-Action", + "1-Action", + "2-Action", + "Critic Loss", + "Actor Loss", + ] + + _, axes = plt.subplots(len(columns), sharex=False, figsize=(20, 21)) + + for col, ax in zip(columns, axes): + data = df[col].dropna().to_numpy()[:-1] + ax.plot(data, label=col) + ax.legend() + + plt.tight_layout() + plt.savefig(os.path.join(self._log_dir, f"{trainer_name}.png")) + plt.close() + plt.cla() + plt.clf() + + @staticmethod + def _plot_mean_std( + ax: plt.Axes, x: np.ndarray, y_mean: np.ndarray, y_std: np.ndarray, color: str, label: str, + ) -> None: + ax.plot(x, y_mean, label=label, color=color) + ax.fill_between(x, y_mean - y_std, y_mean + y_std, color=color, alpha=0.2) + + def _plot_metrics(self) -> None: + df_rollout = pd.read_csv(os.path.join(self._log_dir, "metrics_full.csv")) + df_eval = pd.read_csv(os.path.join(self._log_dir, "metrics_valid.csv")) + + n_steps = df_rollout["n_steps"].to_numpy() + eval_n_steps = df_eval["val/avg_n_steps"].to_numpy() + + eval_ep = df_eval["ep"].to_numpy() + graph_size = df_eval["val/avg_graph_size"].to_numpy() + + mis_size = df_rollout["avg_mis_size"].to_numpy() + eval_mis_size, eval_size_std = df_eval["val/avg_mis_size"].to_numpy(), df_eval["val/std_mis_size"].to_numpy() + uniform_size, uniform_std = df_eval["val/uniform_size"].to_numpy(), df_eval["val/uniform_std"].to_numpy() + greedy_size, greedy_std = df_eval["val/greedy_size"].to_numpy(), df_eval["val/greedy_std"].to_numpy() + + fig, (ax_t, ax_gsize, ax_mis) = plt.subplots(3, sharex=False, figsize=(20, 15)) + + color_map = { + "rollout": "cornflowerblue", + "eval": "orange", + "uniform": "green", + "greedy": "firebrick", + } + + ax_t.plot(n_steps, label="n_steps", color=color_map["rollout"]) + ax_t.plot(eval_ep, eval_n_steps, label="val/n_steps", color=color_map["eval"]) + + ax_gsize.plot(eval_ep, graph_size, label="val/avg_graph_size", color=color_map["eval"]) + + ax_mis.plot(mis_size, label="avg_mis_size", color=color_map["rollout"]) + self._plot_mean_std(ax_mis, eval_ep, eval_mis_size, eval_size_std, color_map["eval"], "val/mis_size") + self._plot_mean_std(ax_mis, eval_ep, uniform_size, uniform_std, color_map["uniform"], "val/uniform_size") + self._plot_mean_std(ax_mis, eval_ep, greedy_size, greedy_std, color_map["greedy"], "val/greedy_size") + + for ax in fig.get_axes(): + ax.legend() + + plt.tight_layout() + plt.savefig(os.path.join(self._log_dir, "metrics.png")) + plt.close() + plt.cla() + plt.clf() + + def on_validation_end(self, ep: int) -> None: + self._plot_trainer() + self._plot_metrics() diff --git a/examples/mis/lwd/simulator/mis_business_engine.py b/examples/mis/lwd/simulator/mis_business_engine.py index 7dd052853..0a20d1b0b 100644 --- a/examples/mis/lwd/simulator/mis_business_engine.py +++ b/examples/mis/lwd/simulator/mis_business_engine.py @@ -3,7 +3,7 @@ import math import random -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Tuple import dgl import dgl.function as fn @@ -45,6 +45,7 @@ def __init__( self._register_events() + self._batch_adjs: List[Dict[int, List[int]]] = [] self._batch_graphs: dgl.DGLGraph = None self._vertex_states: torch.Tensor = None self._is_done_mask: torch.Tensor = None @@ -168,8 +169,9 @@ def step(self, tick: int) -> None: decision_event = self._event_buffer.gen_decision_event(tick, decision_payload) self._event_buffer.insert_event(decision_event) - def _generate_er_graph(self) -> dgl.DGLGraph: + def _generate_er_graph(self) -> Tuple[dgl.DGLGraph, Dict[int, List[int]]]: num_nodes = random.randint(self._num_node_lower_bound, self._num_node_upper_bound) + adj = {node: [] for node in range(num_nodes)} w = -1 lp = math.log(1.0 - self._node_sample_probability) @@ -186,15 +188,28 @@ def _generate_er_graph(self) -> dgl.DGLGraph: if v < num_nodes: u_list.extend([v, w]) v_list.extend([w, v]) + adj[v].append(w) + adj[w].append(v) graph = dgl.graph((u_list, v_list), num_nodes=num_nodes) - return graph + return graph, adj - def reset(self, keep_seed: bool = False) -> None: - self._batch_graphs = dgl.batch([self._generate_er_graph() for _ in range(self._graph_batch_size)]) + def _reset_batch_graph(self) -> None: + graph_list = [] + self._batch_adjs = [] + for _ in range(self._graph_batch_size): + graph, adj = self._generate_er_graph() + graph_list.append(graph) + self._batch_adjs.append(adj) + + self._batch_graphs = dgl.batch(graph_list) self._batch_graphs.set_n_initializer(dgl.init.zero_initializer) self._batch_graphs = self._batch_graphs.to(self._device) + return + + def reset(self, keep_seed: bool = False) -> None: + self._reset_batch_graph() tensor_size = (self._batch_graphs.num_nodes(), self._num_samples) self._vertex_states = torch.full(size=tensor_size, fill_value=VertexState.Deferred, device=self._device) From 5f03367ea06fc237121c4e14ce2df145918848c2 Mon Sep 17 00:00:00 2001 From: Jinyu Wang Date: Fri, 19 May 2023 07:52:03 +0000 Subject: [PATCH 5/6] add rl_component_bundle and configs --- examples/mis/lwd/README.md | 36 +++++-- examples/mis/lwd/__init__.py | 99 +++++++++++++++++++ examples/mis/lwd/config.py | 39 ++++++++ examples/mis/lwd/config.yml | 37 +++++++ .../mis/lwd/env_sampler/mis_env_sampler.py | 2 +- 5 files changed, 203 insertions(+), 10 deletions(-) create mode 100644 examples/mis/lwd/__init__.py create mode 100644 examples/mis/lwd/config.py create mode 100644 examples/mis/lwd/config.yml diff --git a/examples/mis/lwd/README.md b/examples/mis/lwd/README.md index a8f10491b..1d6070137 100644 --- a/examples/mis/lwd/README.md +++ b/examples/mis/lwd/README.md @@ -4,7 +4,9 @@ It is the application of the [LwD](http://proceedings.mlr.press/v119/ahn20a.html Some code are referred from the original GitHub [repository](https://github.com/sungsoo-ahn/learning_what_to_defer) held by the authors. -## Deferred Markov Decision Process +## Algorithm Introduction + +### Deferred Markov Decision Process

@@ -12,7 +14,7 @@ held by the authors.
-### State +#### State Each state of the MDP is represented as a *vertex-state* vector: @@ -27,7 +29,7 @@ and *the determination is deferred and expected to be made in later iterations* The MDP is *initialized* with the deferred vertex-states, i.e., $s_i = *, \forall i \in V$, while *terminated* when (a) there is no deferred vertex-state left or (b) time limit is reached. -### Action +#### Action Actions correspond to new assignments for the next state of vertices, defined only on the deferred vertices here: @@ -39,7 +41,7 @@ $a_* = [a_i: i \in V_*] \in \{0, 1, *\}^{V_*}$ where $V_* = \{i: i \in V, x_i = *\}$. -### Transition +#### Transition The transition $P_{a_*}(s, s')$ consists of two deterministic phases: @@ -59,7 +61,7 @@ Here is an illustration of the transition fucntion: -### Reward +#### Reward A *cardinality reward* is defined here: @@ -72,7 +74,7 @@ $R(s, s') = \sum_{i \in V_* \setminus V_*'}{s_i'}$ where $V_*$ and $V_*'$ are the set of vertices with deferred vertex-state with respect to $s$ and $s'$ respectively. By doing so, the overall reward of the MDP corresonds to the cardinality of the independent set returned. -## Diversification Reward +### Diversification Reward Couple two copies of MDPs defined on an indentical graph $G$ into a new MDP. Then the new MDP is associated with a pair of distinct vertex-state vectors $(s, \bar{s})$, @@ -99,7 +101,7 @@ But note that, the entropy regularition only attempts to generate diverse trajec which does not necessarily lead to diverse solutions at last, since there existing many trajectories resulting in the same solution.* -## Design of the Neural Network +### Design of the Neural Network The policy network $\pi(a|s)$ and the value network $V(s)$ is designed to follow the [GraphSAGE](https://proceedings.neurips.cc/paper/2017/hash/5dd9db5e033da9c6fb5ba83c7a7ebea9-Abstract.html) architecture, @@ -118,7 +120,7 @@ Here $B$ and $D$ corresponds to adjacency and degree matrix of the graph $G$, re the policy and value networks apply softmax function and graph readout function with sum pooling instead of ReLU to generate actions and value estimates, respectively. -## Input of the Neural Network +### Input of the Neural Network - The subgraph that is induced on the deferred vertices $V_*$ as the input of the networks since the determined part of the graph no longer affects the future rewards of the MDP. @@ -127,6 +129,22 @@ since the determined part of the graph no longer affects the future rewards of t - Vertex degrees; - The current iteration-index of the MDP, normalized by the maximum number of iterations. -## Training Algorithm +### Training Algorithm The Proximal Policy Optimization (PPO) is used in this solution. + +## Quick Start + +Please make sure the environment is correctly set up, refer to +[MARO](https://github.com/microsoft/maro#install-maro-from-source) for more installation guidance. +To try the example code, you can simply run: + +```sh +python examples/rl/run.py examples/mis/lwd/config.yml +``` + +The default log path is set to *examples/mis/lwd/log/test*, the recorded metrics and training curves can be found here. + +To adjust the configurations of the training workflow, go to file: *examples/mis/lwd/config.yml*, +To adjust the problem formulation, network setting and some other detailed configurations, +go to file *examples/mis/lwd/config.py*. diff --git a/examples/mis/lwd/__init__.py b/examples/mis/lwd/__init__.py new file mode 100644 index 000000000..a1517b73c --- /dev/null +++ b/examples/mis/lwd/__init__.py @@ -0,0 +1,99 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import torch + +from maro.rl.rl_component.rl_component_bundle import RLComponentBundle +from maro.rl.utils.common import get_env +from maro.simulator import Env + +from examples.mis.lwd.config import Config +from examples.mis.lwd.env_sampler.mis_env_sampler import MISEnvSampler, MISPlottingCallback +from examples.mis.lwd.simulator.mis_business_engine import MISBusinessEngine +from examples.mis.lwd.ppo import get_ppo_policy, get_ppo_trainer + + +config = Config() + +# Environments +learn_env = Env( + business_engine_cls=MISBusinessEngine, + durations=config.max_tick, + options={ + "graph_batch_size": config.train_graph_batch_size, + "num_samples": config.train_num_samples, + "device": torch.device(config.device), + "num_node_lower_bound": config.num_node_lower_bound, + "num_node_upper_bound": config.num_node_upper_bound, + "node_sample_probability": config.node_sample_probability, + }, +) + +test_env = Env( + business_engine_cls=MISBusinessEngine, + durations=config.max_tick, + options={ + "graph_batch_size": config.eval_graph_batch_size, + "num_samples": config.eval_num_samples, + "device": torch.device(config.device), + "num_node_lower_bound": config.num_node_lower_bound, + "num_node_upper_bound": config.num_node_upper_bound, + "node_sample_probability": config.node_sample_probability, + }, +) + +# Agent, policy, and trainers +agent2policy = {agent: f"ppo_{agent}.policy" for agent in learn_env.agent_idx_list} + +policies = [ + get_ppo_policy( + name=f"ppo_{agent}.policy", + state_dim=config.input_dim, + action_num=config.output_dim, + hidden_dim=config.hidden_dim, + num_layers=config.num_layers, + init_lr=config.init_lr, + ) + for agent in learn_env.agent_idx_list +] + +trainers = [ + get_ppo_trainer( + name=f"ppo_{agent}", + state_dim=config.input_dim, + hidden_dim=config.hidden_dim, + num_layers=config.num_layers, + init_lr=config.init_lr, + clip_ratio=config.clip_ratio, + max_tick=config.max_tick, + batch_size=config.batch_size, + reward_discount=config.reward_discount, + graph_batch_size=config.train_graph_batch_size, + graph_num_samples=config.train_num_samples, + num_train_epochs=config.num_train_epochs, + norm_base=config.reward_normalization_base, + ) + for agent in learn_env.agent_idx_list +] + +device_mapping = {f"ppo_{agent}.policy": config.device for agent in learn_env.agent_idx_list} + +# Build RLComponentBundle +rl_component_bundle = RLComponentBundle( + env_sampler=MISEnvSampler( + learn_env=learn_env, + test_env=test_env, + policies=policies, + agent2policy=agent2policy, + diversity_reward_coef=config.diversity_reward_coef, + reward_normalization_base=config.reward_normalization_base, + ), + agent2policy=agent2policy, + policies=policies, + trainers=trainers, + device_mapping=device_mapping, + customized_callbacks=[MISPlottingCallback(log_dir=get_env("LOG_PATH", required=False, default="./"))], +) + + +__all__ = ["rl_component_bundle"] diff --git a/examples/mis/lwd/config.py b/examples/mis/lwd/config.py new file mode 100644 index 000000000..30a873aa3 --- /dev/null +++ b/examples/mis/lwd/config.py @@ -0,0 +1,39 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + + +class Config(object): + device: str = "cuda:0" + + # Configuration for graph batch size + train_graph_batch_size = 32 + eval_graph_batch_size = 32 + + # Configuration for num_samples + train_num_samples = 2 + eval_num_samples = 10 + + # Configuration for the MISEnv + max_tick = 32 # Once the max_tick reached, the timeout processing will set all deferred nodes to excluded + num_node_lower_bound: int = 15 + num_node_upper_bound: int = 20 + node_sample_probability: float = 0.15 + + # Configuration for the reward definition + diversity_reward_coef = 0.1 # reward = cardinality reward + coef * diversity Reward + reward_normalization_base = 20 + + # Configuration for the GraphBasedActorCritic + input_dim = 2 + output_dim = 3 + hidden_dim = 128 + num_layers = 5 + + # Configuration for PPO update + init_lr = 1e-4 + clip_ratio = 0.2 + reward_discount = 1.0 + + # Configuration for main loop + batch_size = 16 + num_train_epochs = 4 diff --git a/examples/mis/lwd/config.yml b/examples/mis/lwd/config.yml new file mode 100644 index 000000000..a42b19134 --- /dev/null +++ b/examples/mis/lwd/config.yml @@ -0,0 +1,37 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +# Please refer to `maro/rl/workflows/config/template.yml` for the complete template and detailed explanations. + +job: mis_lwd +scenario_path: "examples/mis/lwd" +# The log dir where you want to save the training loggings and model checkpoints. +log_path: "examples/mis/lwd/log/test" +main: + # Number of episodes to run. Each episode is one cycle of roll-out and training. + num_episodes: 1000 + # This can be an integer or a list of integers. An integer indicates the interval at which policies are evaluated. + # A list indicates the episodes at the end of which policies are to be evaluated. Note that episode indexes are + # 1-based. + eval_schedule: [1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 600, 700, 800, 900, 1000] + # Number of Episodes to run in evaluation. + num_eval_episodes: 5 + min_n_sample: 1 + logging: + stdout: INFO + file: DEBUG +rollout: + logging: + stdout: INFO + file: DEBUG +training: + mode: simple + load_path: null + load_episode: null + checkpointing: + path: null + # Interval at which trained policies / models are persisted to disk. + interval: 200 + logging: + stdout: INFO + file: DEBUG diff --git a/examples/mis/lwd/env_sampler/mis_env_sampler.py b/examples/mis/lwd/env_sampler/mis_env_sampler.py index f1d2e4024..c0aaabe60 100644 --- a/examples/mis/lwd/env_sampler/mis_env_sampler.py +++ b/examples/mis/lwd/env_sampler/mis_env_sampler.py @@ -122,7 +122,7 @@ def best_among_samples( ) -> int: res = 0 for _ in range(num_samples): - res = max(res, solver(graph)) + res = max(res, len(solver(graph))) return res graph_size_list = [len(graph) for graph in graph_list] From ce921acaee343f4f7db53b0121898467c76a6b18 Mon Sep 17 00:00:00 2001 From: Jinyu Wang Date: Fri, 19 May 2023 08:04:58 +0000 Subject: [PATCH 6/6] update default graph size configs --- examples/mis/lwd/config.py | 4 ++-- examples/mis/lwd/config.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/mis/lwd/config.py b/examples/mis/lwd/config.py index 30a873aa3..eb59ae8ef 100644 --- a/examples/mis/lwd/config.py +++ b/examples/mis/lwd/config.py @@ -15,8 +15,8 @@ class Config(object): # Configuration for the MISEnv max_tick = 32 # Once the max_tick reached, the timeout processing will set all deferred nodes to excluded - num_node_lower_bound: int = 15 - num_node_upper_bound: int = 20 + num_node_lower_bound: int = 40 + num_node_upper_bound: int = 50 node_sample_probability: float = 0.15 # Configuration for the reward definition diff --git a/examples/mis/lwd/config.yml b/examples/mis/lwd/config.yml index a42b19134..b057b2c8b 100644 --- a/examples/mis/lwd/config.yml +++ b/examples/mis/lwd/config.yml @@ -6,7 +6,7 @@ job: mis_lwd scenario_path: "examples/mis/lwd" # The log dir where you want to save the training loggings and model checkpoints. -log_path: "examples/mis/lwd/log/test" +log_path: "examples/mis/lwd/log/test_40_50" main: # Number of episodes to run. Each episode is one cycle of roll-out and training. num_episodes: 1000