From 0eaa955472b466813cadf01f4409b91c889c05bc Mon Sep 17 00:00:00 2001 From: tjkessler Date: Tue, 1 Aug 2023 22:13:33 -0400 Subject: [PATCH 1/9] setup.py -> pyproject.toml --- pyproject.toml | 24 ++++++++++++++++++++++++ setup.py | 16 ---------------- 2 files changed, 24 insertions(+), 16 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..67d9d6d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "ecabc" +version = "3.0.1" +authors = [ + { name="Sanskriti Sharma", email="Sanskriti_Sharma@student.uml.edu" }, + { name="Hernan Gelaf-Romer", email="Hernan_Gelafromer@student.uml.edu" } + { name="Travis Kessler", email="travis.j.kessler@gmail.com" }, +] +description = "Artificial bee colony for function parameter optimization" +readme = "README.md" +requires-python = ">=3.11" +classifiers = [ + "Programming Language :: Python :: 3.11", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] + +[project.urls] +"Homepage" = "https://github.com/ecrl/ecabc" +"Bug Tracker" = "https://github.com/ecrl/ecabc/issues" \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 480533d..0000000 --- a/setup.py +++ /dev/null @@ -1,16 +0,0 @@ -from setuptools import setup - -setup( - name='ecabc', - version='3.0.0', - description='Artificial bee colony for function parameter optimization', - url='https://github.com/ECRL/ecabc', - author='Sanskriti Sharma, Hernan Gelaf-Romer, Travis Kessler', - author_email='Sanskriti_Sharma@student.uml.edu, ' - 'Hernan_Gelafromer@student.uml.edu, ' - 'Travis_Kessler@student.uml.edu', - license='MIT', - packages=['ecabc'], - install_requires=[], - zip_safe=False -) From 35f6ec490e7d97f67297aec16c30fab213c3dd60 Mon Sep 17 00:00:00 2001 From: tjkessler Date: Tue, 1 Aug 2023 22:14:29 -0400 Subject: [PATCH 2/9] Remove Azure pipelines config --- azure-pipelines.yml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index da688ea..0000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,21 +0,0 @@ -trigger: -- master - -pool: - vmImage: 'ubuntu-latest' - -steps: -- task: UsePythonVersion@0 - inputs: - versionSpec: '3.7' - architecture: 'x64' - -- script: | - python -m pip install --upgrade pip setuptools wheel - python setup.py install - displayName: 'Install dependencies' - -- script: | - cd tests - python test_all.py - displayName: 'Unit Tests' From 8cbd54b54a3deafaf29ae31c5c9fe3668fee70fe Mon Sep 17 00:00:00 2001 From: tjkessler Date: Tue, 1 Aug 2023 22:19:29 -0400 Subject: [PATCH 3/9] Remove .editorconfig --- .editorconfig | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index b0d1ce9..0000000 --- a/.editorconfig +++ /dev/null @@ -1,8 +0,0 @@ -root = true - -[*.py] -charset = utf-8 -indent_style = space -indent_size = 4 -insert_final_newline = true -end_of_line = lf From 7767ac40c2eaeeb1ee82599743fa5c5564f4c897 Mon Sep 17 00:00:00 2001 From: tjkessler Date: Tue, 1 Aug 2023 22:20:38 -0400 Subject: [PATCH 4/9] Remove Azure pipelines badge --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 774853f..d719b77 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ [![PyPI version](https://badge.fury.io/py/ecabc.svg)](https://badge.fury.io/py/ecabc) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ECRL/ecabc/blob/master/LICENSE) [![DOI](http://joss.theoj.org/papers/10.21105/joss.01420/status.svg)](https://doi.org/10.21105/joss.01420) -[![Build Status](https://dev.azure.com/uml-ecrl/package-management/_apis/build/status/ECRL.ecabc?branchName=master)](https://dev.azure.com/uml-ecrl/package-management/_build/latest?definitionId=5&branchName=master) **ECabc** is an open source Python package used to tune parameters for user-supplied functions based on the [Artificial Bee Colony by D. Karaboğa](http://scholarpedia.org/article/Artificial_bee_colony_algorithm). ECabc optimizes user supplied functions, or **fitness function**s, using a set of variables that exist within a search space. The bee colony consists of three types of bees: employers, onlookers and scouts. An **employer bee** exploits a solution comprised of a permutation of the variables in the search space, and evaluates the viability of the solution. An **onlooker bee** chooses an employer bee with an optimal solution and searches for new solutions near them. The **scout bee**, a variant of the employer bee, will search for a new solution if it has stayed too long at its current solution. From af17d850f9c29f285bef31076b90dc4ce5f27823 Mon Sep 17 00:00:00 2001 From: tjkessler Date: Tue, 1 Aug 2023 22:22:57 -0400 Subject: [PATCH 5/9] Remove redundancy --- images/state.png | Bin 62779 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 images/state.png diff --git a/images/state.png b/images/state.png deleted file mode 100644 index 7cb0690363985a6690f030420f01910f297c2d2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62779 zcmeFYWl&sA+wY48hakb-U4v_ay95ocK>`B|?(XjH4#C~s-CcvbJM7_po>z9&-k;v{ z;nb-^EvjbLYQ4JW>c0NJUcm}-;z;ng@L*tINRkpFN?>5%m0)0y2C$z%cfhF9sKCGw zsmz3h6(og)i4|-CCT72l!N4Sf5x?=UwT!GHy$P511DxtGk-fg!K(fx#mXo47K!W2c=Sxz@sxH*u;U7#`K{c} zMK6|6Q)ZRSy~Qfy|KP*S&I)gU6a;{8v~|Tf+mr6u4m=UGCikv1@d5{v+O78OCYcXh z6iNDm!;*I|iSAcP98f2|1La)C)u$ClHy(_06^VI4-c{9;#E>u$vlJ)6w(}b3X0>&# z-Xm~~@MseZpJ8gzPE3Mk@Fj>yiq)_u@V5@e6(m`pW$!dL&aa1YmMBW(V8ul}lckug zR%ZEjuP@wwWLFP1`XNn=Uy(J(Wns1vGmMmandKRosMVvfRD03;Q7#9DMV!G|gztDo zHKv5`)FGMZlnXe$8%1M?c=;NbP#8Hx+qpjPiVn~;Zl&OyTu#9wHzEarc@x7jdt=lq zM?tcBW#u>2W4-+d^8@4f>}%Hv=^&i4g%f;*N^)ZOjpPBHlrSo66d@||N=RVV6K9In zg*S?lXe@DJ5Nhr7FJJ2@=y&Pe8b7irw9X$zs4p1lkWD6Clz7jQ1Ih&RsJ_A9dYKJi zr*Ic~+^w1TAnrUWxwp<52be1WcYapi@wVW$FtgLG0nJ$wFOWdWB(PY z1}`1$3$RtZr@(fbPvtNt7gfwLf@cVZEkdXFTKQx+o~64{Ox2J$6c(|sgQ|k83RQ|W zIG3cQv-foh7g7Yps+)s@k^43|$d=V~b`dr1M^L5R?w^r9I_(R7Orl zmVEIQ;ajFGwgSnMk(-51C)r8WC6f3h1TOdjDB<3GI^Qx*4Bm?4ST;MrVwMA&xiU;=80^ph9v|g%! z+7zJ^ryW;2u+|UL0+%I4fRl66K zMf^7^RWHHiH=k%&@=ucAm7?rKf+V7s2EXTF&-L=`F2p?NQxP=JCwuYNDeJGu=^+KVO{OHxu-Xyj;&fB)rbsThZDH%*SEdq81wSzx zhk4@|IwbL828rqqspC@i?-@|2bE^gubRKP`wtLqZwk?XM1U=v0HFSOhk z+YvW{&burwY95eUA9<+8O}4)okX3~*N}frM zAcariTkTbyVe4fZaTRr?s-dwYvLx)BQ!V7&e&w<&Jd72`8z-p5Uc@mgbc9h7*PPaz zx-8Kq?k=4>MQ`d+YxI-QlKW4HL%?0q-8cVogNkmYt&-6n#R|oWP32co@nz=aM-@D> zbqaM_w?Rkjb2{wRDgHmRa5XK#S>^@v!{mz=GgH8L|XO)x)O_&s$pGd+o08lR<-rIKy` zy)@mMg&vz;4AT&Lhk1u%C$`?ZzJo*FwfGMF^mzH)UhMGU@WM{Y4*6(x`Y~^07HNKL zRy$v_^jQLBp=K(zh*KC4y3XCUu9%YBDrO@VAI8IX)Gkm0$R)}Zl=cjK`||1mDeS8N z8Qt~Cm+ezq0HUy94`L5{02PvJ*nyaU7(_0+SYenBb6wKgDE*$xo`y+gRl7}6)vQyC zP3WapaK2=#Wc1LtSn&dkAC5C$XV?p53*1e_OwdP7QdLsdIH^)wQm@Ark~vd58Y*ig zYSn)$T3oIw4a690m}*!)PKKE5TEttZnAy*yn4Vgto3$-9&wZ-1soGjmUeu})wbZtR zu1~dG5knVq4a1Bm$9EryFy99x0@N%Q^d~AKE19Y*>O9%|f6H7YsTigVxY=9V4B9Rp zUUVw<*t8l(8@BD>QQ=WV{P3TV{gna?y5`&G*jKV$7Ou9A)&!BXQ? zt2zc>Amn6gz-W-Qa$IF!%Uw%s(!C&df^r(YHobT_I6O+;cV4_^V&biksc6%g7|@8= z318tZpIPZmJ#AZSSqnUj-L4rcTw7R7Jk>mW?wy#8JZ=~{PwuGm40soKR|7`|-}W*D zKZoRp+=t@$r2j4Z8^t$o7m>lBGYzSQv?Db^a>S=|t! z2czdwsmh#0*n}k$%-~B$rW3Ys-f3$It%fwhT&iA9KNLXC`O(0sz@4M}qK%-fP=zOd z{WZNy-bVgSNt>01o5Weo_WkeXkOh^atWB~K-|n6ANHO#eCb^l^1oqQjySc-YLvbZo znqV5&9|ASGi}5FK%Bn{NvjTF&zX!V)_;i^pN->>N{#X){fSGDan~v$8O6vLV;Q9xG(%qs2@ySQ%4o6x zKKE{IYom#=sEDQu8lu4o}OWyM`kT<+)|CA;bJIr|?mzD8&QO+h z9XroD?|j32^8-6c4zsa33ST@xJFTq(vNLgHe0IU+L+n>|NhBqG*sqP9+_Rvx}}guXev?ZVF(2QKmOJJ8Ul{ z0JruT(@?lQ3zj+_XQ9s#kg)if2s{3I6yH@J@21c(szqqqH@TGc)#dM{tkgBDM$SLg z+gMUnJnIO$lO6Owrk1590z}u%TL?U#&(diem(Oj_1v(P#&u?E}o*_~81j+g6o%8OB zUaH=9hCMajkM&wCo`WN@BIx7|GWibv+O@mUIzKg8D02R3c}bqCDcP5sz;pTT@>5sF zW9nAv#x|?rLH<^dv#r|2{)VZeqx?qp=-7q9ga5%~Npr2C<(=@^@rS3~`;Oz%`KR?n zx5?Ma{oLn)J^A3w45ZNqnWxsb(A&hPiY;9tWD^wRkVS*~7izDrfP#GR3lmPTT5=}` zQ)n+%4I9o_gq%kCVblkCbsNvvP&%-dSFoT=6d@?S+~3|~_y)9Y!x5V{9$@wkgl58| zl0XNghn#Lagsq;9gLz|+}d?G`9-fU}m+0D^&GQhfY_ODd6HfC38xGi7x>=63j=J8iP;&y zGBT42z!MV_^Vu4i@Fte{0phy8O>wP@e_h`I!FGegg1Tqw9=dU_xM$BHxsqz)#ZQT4hz92QuBQ zD9A;r$>Fo}B;!7je?f}t75qB+QWEtE@O^kCERIrGG@3*>J1TpyOe84IZ#21ZwdVb} zVO;z940y)9pWzzdI?;6BAdu;LFJR#`zr0bm;Boi!Si?fI-uAmV1i8@1MI+W3Yp`o< zBk2Vt0lI|HvmkcD|9L_l6$4e&sZ_nJZi+?>4!UTW*CGF%Nzx0Ro}x6|pYr2A8Ym0@ z|DOe9f&aP1ymx|#0dR{J3&F2F->VWBu+ERzN%1&>PgW^xFS3Fx_bs=Qc;Wt1fRCWj zD2e15!9~OG_Wt-&vRG#xV&7+Mpu$bXB;DT{>*r-mmq>0*!`$aJ9izC@o57`Vq9SEB z6%7XWH^@t+twg=yE2`O2+AzKN-zzmE+Dff5dDl16Di~xm*O$CeEon^j55XrXTuUXI6*;v)6usIIRZ(eZe_Iyb3{8-EmXF`n^hRci_i z_@}>>HP;acW?&#fDC;#TOx*R48QgoQoS+(}t(I}z_L(cnmwjRfC5Qrp6A|moXwo`i z0rn9Jj7aL$S&^vT7xvQX&R_0GzEKK6Cu~05dhmTe`Q=i=IRwc=RXX!$+xbB(nzfLe z>sJVsH-wPhvDIZeujolTx*L{eZIL?DJ;ub^AB4`nYqa7{G$)kB^p;@9Nh%(g(#PHH z!zrmerDUi*i79dgA-I<+mDV<~5PqwSPn(C6a%G&)7z)_X`_-U)iVK0rp$(^y2J-1! z7Ba814=-)uXwm4#F<@|wu#3Wlq4&YxmIusN75I7tKGV46(0!7>MhB;@bXq7-(m8Bs zKuu=TO@2#2o5+eVm@CFT*P@Puk|2fxckRNxbimF^$oq}eR9&7IP9R5t>7!EO`op`g z=~ElS@}rB++_3cWU^zZ(7@KYgnRgIu5lP>TYDI9_I(0J@M?YsAk>XD=DIAE^V6q4m ze{LrJ-$gDu$~rbs(u(9!KOoG@H5$InVNOv_aU5O|fm*~hM^lCr-t{Q}$*c^OsuDvO zVhT+`N~S?Hp3oBAGLF#`b@Q_U{t^0dYik{z=?`ZVB#v#NI8q5>4=DT|WMhMKjjyrC zz1x8t6BJc2n1QsNq{d~}KE-^0kaoa6l0`sGg6f(Qv)2sI{T(u_331}xH%PQ1?2N~` zpfv4qG?Cqd?aayzNA3p}#>42Q6?!K_Fko4T2El#rUk!sv20XEZ4J6>;I{!i@LrLW= zf~s(>-yBZ+STIIOOE)~m|508iZp*MMQFeN4&>K{5Ew1S-i}g!ILd zL9^WOh>1o#DPRheV2c>OQ7$3f`iU<1Igth`Q&BHhHfE;i81hKM$JL$l=L_ zE7in(gH`Z`_C||d2$1DTQK_;MnIa3nIcwR;?|H2ljMd(V?g@+(OQAPy)u{1^BJ^*a{YXA*H=RoI=e3N6}w_~Vy#7=jU;*-9qv#^1Qsa?k5bz~?05(#EXJBQP(&H+ng?liaCyDXT6QVP;0i zeb!h@FS$)27^>%DM6%f)Nlzzj-AmYH(;=djg%iE7eoBueuGV5fXQ|^oig6%b|!2tG&Ahj?kVrWZ?IJUo;{CFlR(@ zI43r`@pNjm2rW*lOoDHXsx^J~nQpd<#TwTqSqn}VoBGXd#Q@A^s%4yUpb7_8zBp{vQY}`$dqBx^GU6`$;zG-77r*eijRJ4>kOn!~zpYL9; zdA8{I8t|_9npr7`yUZZXGeBl|g7*R)Ly^1`S^m?P*UzXcc z&U98hoHLisrs5y5j(UWH$FY%_2+1&9saHw4O(>lv2T^6eqs|9W`D^mn4uLLq%K!|^ zOv#u)u>DTu5ebKV!v3bMe#YsB^CEiD_4-pHGnXG~-q|8qbXr`$uFbtKTLjn@GR|2R zL96v`VzA31cL&kyMCx?KhUvbe1C`a|^PTZ1g-I+d;G$nPwO)d;$i5-dI|vRk@Ck`$ z7)y7QLkE>W|77~&=f*w<1bQcJ?XLcVi8gnLHfVZZe)W1kg5Mz_ zw|X1=(5SY&_L>umaWB2rynnbc2$`l|^w+^;9QpTI8xtK~ujO2GA-%>`DKE|h0vBjq z!)OIp2FR$=zl*%_^GI`8;!~A0)WU2m=)Wo6TDNMDP$cz@q>}4^?FpC1Su^ z>wWsM>z02czJKw$;O-mDthfvte!2d=;E%u8(QP8P&3Tq9$M&#CJ7evcsiq#{)UDhb zC>V1!u|0t%gFzE6JX`mI7a^)95#nibCZ#;As@Vl+RPrve*Uttg(sGO=39khcxlOtr z5BmN$XRpz6y6tW>+7*qud#@K6MHz{);)UaoMm+(4#sT+mi&;8~C?)qy>AV|{CWn< z{^wx=fi$z>cZ2iibQqp(VVmtOBp7b)G?(c^W;Nv|Tj!1rkT-s8%a%`FR+AHK^NV@Qj3`Fp-M@txGfO20;zd-^Jrc4Z!cuh>gh_fVFTdfVNL`qz5|td}F) z%Oh*0tTs8^ISY1{xm0O+x^ixPhx%{N=PZvmHD){~C8g$O(3>KoT*bQYq=9`t{?Q$O zaue?_gvTKJinA+lZIAz`nZM7KavtauwCqprG3o+F(iQ0ZOw=rx5R;+j;fB|Xrt;6o zpYZuP*}fi+lS%hd^JnGFzq9KJm2Ys}uwpU(EcWdFj@p*2SMG8f z($!kGK~tEP=ki+v>sR)#unZH@MN-2X6FtkI^)<(k4SB&o@z4mW{S1QYeYkhw6{Q3> zPuZ&#tyd6_kNfuDTu*N+nj|zCn$dG3*6gumF(d@qOdz zJZHEu_#S!f47Y(Lg1_z0DM#7djs@N!%a&oA5x>Uu;fW4=%BAA3#wSZ8iH=|mVH-o4 z%8!zIVfv9c>0PHpRnkZXI_-jA!YMw7m%D!Vcy|YWufBBAcRB!CdfF*oyBAz(RdS$#C5#uv6GwBcN17AkBMElyArwIZK`5^Fn>_3; zv(?M}(|4AMyO~Jgx~9hG;a{U#+iy5?k4AO6L}k;PS>zq*>l-Z0)(Bq_yte|wBT7|D zKP$tHCRHgjU<(vrVyM|sp|~RZ?{LgMF z)5l=)h(Rrk%npEMc@NBH=feKhsb4)Ll~Sj1C*(^*@3sXVKjXs2B!&2Kah9^c2=ix~ku(;=V74-ihH>Yg`YBgkF;;&E z0c%sY+-0xp9s>)p-vDSW5{(4I$kP2noJI%a1Es2ehM({;Q>>{smg_tZf!8QNxi%Lh zcEKLP`*XnqKQ)yAZmV5?FlxhGKH)X2?3ZvoL_p8*m$SBeO4D7d?opfVUheTU%^AwX zU&%dRFP+G}B|y=G=yw`*e72^TZbRak0wy%XbZFO_0SYWeHy^_tZz6B?=x+lN-w?8dTWA8d2fv2m(FZZ#^$atFHuBHrt%04@sT0 zV_Y|_jHTCJZ^7$m^vk67m0{2F1MM#N9yiUCcs;F|noS0aNXTNa{V~Xy;axde*G;(8 zw=K)9n}~C_M@#E0*PO=QebiwpS-+!E_GWclE&Xad;H6MT$TwG4)ax2^Tgrak7Ml3g z{gh2{U?;8N|6b(8CCeBAn5vF>b)ZYzNU4o%I%r zM209`k}g+)bWj8l2r>=!$Bg^mBAEw^+?QUxr)~iBC4I{jC;2ePy^Qkhe@5&sDeldT z4O~ztFIym6xGV<3bQ^OeBhL$=fqHg($@%u5=c$m~3PZJ==g6Ko9gaG6vwFty8Gg>9 z+&IK}P-mEjQi>mD?<33r%glrcX1OY4F`c{kR_{?W77EiHN2dA>PNHOttu2LV>807Q%?B&*{!1Ku9f33bkY|T)(B)t+( z^%uq~kTwHF-J+WA6z&KX#?Kk16i~9i0=Aj8utu|plZc@7$uzmx9DRUnT>ztQ6jeQf z<&CfzdPa3OKCjCQCRG~^#~h>(CiSph3sd{bYJdiGI8V@ z2CX-iYdTN(!m)yk1lu=fo55Jf)L-lN4H6#Y1>F!V+`R)|rZCSaNk|opChI$jVV$2E8&@lKXb{4v>4Ankp3M zlX&F{#!@)&lW;r>5y7Q3po{O>n$yrE06FV=`=eMB5APv!ogFND%BniOfjQn=5H1=D z2}?BSHLl!c7a>?ow2R9_e82UO!`Hz*g!PT7=yVoB-Kx;}${|AtP#V(C!~{)7>6>zm z9r@4ij?LYgmN8F&LHa6XsU=!XT8kMFE}u%(-is@UP+Y$jrm#;;@n>`%_X>DcUiFmY zDOfQtFE-kwj-&HFlQ9j#?sj79qI>mNmp<9#lA2-wp z>aRqg>nGs}g|ed38F{8+$5j-^aLNu5!qeoDzLgUSc1$g`Y05}RBh|QcJqY~oVQ7_w zKUFSip(*L&Nxs3;GN1G+n z`Nf6@vh9_Qrl~YGQX;}bgrLobcF|+Ywsw-mA-x&cV<#Zi$ssJ9XE?Gs@7{QP<5^{L z=9n;sCp-z_D8*l9);326iBWO4zZR};n@>dB!{u`^dW@jsgpwju!6{-~6D>b-uG zvqMz@GxcQB8QI}FAHW%7oIBMkCM|ZgR;|9vBb2*vP@#EgPRN@3wWy0 zb-ZO>1`aN{ep>EgI;A(`ROoy7$F0fzk6bXweqt|GAdgts`d;nM!px zd1wW5s$$MP=xMSXW9FZqRg(8c{rOWi8SzU-HACjT7bMaa=hJs5D>POv|Ktfu>m zpGDd3sgN0DU=igEC4=>Plm{0BsrEu$n5r|&xS%xZSSX23QLs;V<7rqfIz0keSGJc! zfI?+GxH82SAm=6W6_U347!#I!q$<9W3^5>tu@j1UBR`?a)I!)F9H=v&>JHBw%UD(*gN965=>%4(gK;$Om);WfpYFr; zjX&))BfC zXdYr%<5j=4(Y#co!jERcct(3sMtRseyUVP!M2mow(U>@}h8U^o5dEqT9_0nq4VsY% z6M@;FxeB@@r9hKHk}jZ+^dIsB`!V_Xhyp;8JP0I^(|`t^{0}x}=szrrm=E;z!VO#M zAJi3<2U0U*Y7Nxs|Djpl7oe}**ZR~S8KZ<^KEmPumsI)~-TC&q?cN?axb8lsn=Vyq zOs(-DP&*5QItyrITi_$#wA@fU@qp}Gd0#7%-cmWN3qy_+W$=XqQ9J`O(C-pYut4q` z#JuqRq0$N@jQ9e=k2a>~HIknJ(eXam>PBc1&LE#iqwC-5m4;S{t3@%Nj}?s1^;v}p zv5hIuAF=c;o_j3g5>(o@2&hguVj9XiSoO8cY3OkqrqQ)$m$$>&X48ZOs4E`MI*Gb5 zi(4|U$I`S>|Hs?*^5T7}P(jde+h&3yv@^C}5t1Ur+d9~;x{}Hk^zd*GpiS(h9Q7m= z>8XF!y-F4UUB~S|$ILcTsJ@N)uHCkRH3=Q=uNSBUWIY!1H}WWLrm(h(7mf4Ql36Of zrQ!TH;|q8RVh)JAlcw#qN4e-6QHs-rz8IbMX3V5|oA|s;*J7Q`+v>%-}Xz*$>WE^f2>q;mYhPhzVEJ7iE}6s4*=e^ z=NJwvlSfH_a#F-F8W&=KJ2H*ZG%j67wK2iK$x9GxfVx8=EnA5%`K|Nl@ppo)dRw#c zWiAY8Ca#0#`^Yno9$ZWiSlHJaEp@Tvn}};Q?FA#!bP#LF2{BiX#C8ay7MM5x8X*9m)edoSZE~Rz&lVYH4ldP^XF1c(_qu2CN z(!Dw__cybl|8NlbwFe(#Cem4=qf(}s5>)$J&NzY0_K}Vi!TcUdCgl{N z#cKrP|IJb5DcjVMiToE*L=E~MBE@V6Pg8}N%N;n76^)=yK-(HZHZ{-^od%_PjA>x+ zDOGl53?hzy0id}LSFZbS@4Y?-uAz}`^<|=mGbE0iqKo?n9~=f4gr;NY{ThwIefnmL zdL8g%F}z$|YX;NeV~U2S_rCX9Yf5#3anAu(rsy(e5dIONjhEDmz>tN2j_V}S>|3$O zTin?*1Is`@?RBY<2lrRBW=w^rN40l9sB~HpAikT$Y44wv51-grp+@Caq=bv|3iaZ* zYj(=S&T}v8N9tocfsF2^YBsp|DhT?uggRQ%?#6q#;ze^`#~%V{E)kEYRnto@$NjHB z=Kub=5~~aq5D0EAIqVQkcPzEMxTS1BqMqN(`9(d=ViL6MR_v?%Y0V2i87ArBy3Xf9 z3kveWRgwYRrVJj1=eQaex(Pp`<{Rh5k#zpa+g4Xex}NR{DA^a?{UTn8Aqv4wk+qj% zkEd*jYKdkU%lQ7$q95tq%aho@^%kT0acv-^S;#*#famwxX(}InfWX5mEO2C!{%nD& z@e$1y@AIC(uZuyDiXp__52)6Nym7&8zoul$(L;%r`4W2x)$vQfLgOyybdrW=mYI=- zXndfyCBQ&+p<609%yA7D6tz-x5~_#2RPo0(*Z>(QP7B}gdBUQpVq=BA!*&+!bPv>- zPR!b@I?$3t0d=We^;lR3)&r~;^?k2(Sh|gD(Ihay*@+5PiedoEzjLZ{R#(YDDS3ANwJ={LeNQ{7hUYwDO<>7Ev!yGMem`oR89@ zXaOH?IYKUp`IW-hEevhrdYekIJIdfQ961X3nyN}_U!c)#pmA)WDZM1d2X^xcAO|h2 z_EK(PF&|qbd)Ooni)HJhxHJkRa!^SUv!=voUM{gIChBBsxcDLbCGz|{CX!pZxhrRj;Etwx@*^!46FWWS6(s$B}>z!w~fi(C2V6-7HRk;%At zTTybc$}kd<@I@tJT426PG-7)I*ZKKwjn(#VgHrQN@zsXasjas6>-B^{ddI?X{N7ka zLbb7c=xq$V@{`m2#?hST@XcZ99KV9Za($Z){1+ab8a}&&Ah)}vukMkpFM=DmrFntB zKOzN5T|G=@4A3&AU{rlA=roCyYnpvxV1nn-1-*ZuBi9%-l2{Q-DhfZJXxw16MiI&P z6wq%wM6s{r?ceeZ4Zq%KsABrE%HyZvn#VJH1cbD_yu9@|+~U^lJ~%{$6)q{~A>+1cV>WKwG(jMr@fX+oyT1<#ky^2B!O7dcg?7Yt z-;ck993R|JD|aw#`-5|?i{wTnW}Jm{ec2}DvX&Sacu6+KHT3cWfk26$KT|w@I0(K( z627XvJ?sFM+FU#Ec@xTA?c-_FNea_!mAw2W1;bBrL~HI~Wgnv)D=#Fj#g{&|yY}g92taW4TbtLwavh#M?o0X4TN#EPcXuv9 zr;Ey$XF#*bQMo)iQ;RkFgu4j;whouuLlH2&5es6Ur|VNsbpO^mI8_+zCXdftO>pQ= z3cKgf)2*lUP)wikLYo0 z+;waS5WKZUAaFDSNM_Qh#xtD@!rf0c29M^n6FPx~uVQmg$I8u=jI}%G6j0@J9y7bvzmGg-(Ga{Y~xjZ9&?2_&1VtKoc^3&6ME|t zoOz16+~IETTMqB#7Z8ljR(Dx}d(%;#*yBFDuQzD66a1V&YwTj_XyR<8iIt%)V#OjWh(Gz{`cSw}Ed@4~~tv`n>B3Oqv;aLQ%4i{|6g2$n_mWF?lZ`)!KX7b-`}7)I#=~w}kgiOc2;Wd5 z$k;JiKLLQtw11S%aD2dS*V~0Ed?0rPhog4I#HEn`Hjwv)1LZEk@nPM`{aVu1uJ-Mf zFcBnwImC2+ef{&+|BwTHu4Uz2e=1QFviNQ?qeVw{G<`hq4$5 z;SjmsaI`VPZzVzgtUuvQdurOx><~X|YY1lBS1y!8!nqeUVS3Ds+}W4`Ikcc3pUO_z z;1Mzo;)Rzwzc6Y^tXVuMC!)NB_h_kd<>rEZ0Hs)gMD})CJ0ONhLKu0j=xe923}Tq` zTSRWmimPqRv4OZ^@#2JL<@+X6Ev?7p$7xs%+AVh_O+A}TP?Y?U(7s;h<+RufDbs3) z8R`|YRsbK3@Jw!XQ(@R%-}s=Zmc$KyGI9^2axdUQ-16lk6bEHDM*Z^J}!D~ zHQTq#g3I7nHl5a6jLN(BN_W|Zn;!$Q+j^A*1iN0Mr=hCro8#KkX6g8F=>o2#5(Pw& zWTBY^F~V1-5dc-{GRvh43ad zmutY_Gp-^{V|+AQ>PdCJ<{2Rk)gp!dOV&2x0kVE{b)@eCd%FV9znhz}RF9#D#c&3w zb*OY32Mf!xi7#1EM^!3xSdtp4^TlfVoU;YEDjz8_H zdM4P48aqR2+=9oY-*y1ge{DPHdfX;KXSy!0N5+UQM~P7McT7gr&EcSGnh zUMPVF>i5J@$Rmf9d*c0g)bnPp8n}~gI@NRf;F-|;Ga^lpFvu2D3MXA>+E4*(JRj$Q zaBagZ-Hmg~!);?jK^qp^Iq}~7u4Zy|^6ntL?%s1~^s7t{4wb?IH>CMd>_Gm@)nNq9 zH!a|p|8ghfGrj?9+fmaW*_)(@E<%~$VIqEfY~oG*{o5x;T?8zFZ@MtU=);S|i~a#e z!B7GUEPp=*HYNGOL_(#bf5*Q8^W`qX>q((&Wx+glNX1Z|Q-3`25 z#foEgG5HVj87$G!h}|DBg}hoI%WKzWB;)zmhtLNd&Bb>R7$VpWQZf-Bsgiv!HeC<8 zH0vOQClEZZ_g-^C-8=(R(%`#IJdtk-yh%425Yt>`Q*&`d&Pid5-7f>(lj_w0xl#sUQCnh2VSZI0NMY zH|UMP@cS{xZl&u&qCgXzl2jI;iM1BHowHY{%VrEUu{wu`p|{QAqtj z*alyl-lVFUTCCx1!VKU5+r9~>8(-r>`Rj<&F`3L03)8;CMHC<`Q4yD$bySJVewYhpS8rExzLqy0- z$cfsjYfy(|!x6I9kZWN$()cqk&GzFnOr)YA7`6_#*n(Tn^v+0xXiEi?2Mj6NT9YgA z_}zZhk9V4x5WtBh(2i+{-(aupg4Js*3zFby|J1SH955Q|O+^4F@*!;DnoAg(EEwYN zc-wYWBF|&^Kf6AkRxMw2Yil8*8yKoI^ei*oN8zjoO_VB1igyVEr}K!?V2I0_&djfU z5b^H9k;OIiND0TAj2r&V4A()Y__o!d@DH}*1qsV4-+T>B7cUG3;A_^_n1-F^O1%W} zW#+?K2LS4@xad|d3_aQ~v`?UpSI&!|exigBNtBJbf@p9=f zu=OTB!T`9?R3x=g`INxsh^)6RqgO$K=OfV;6FRCgX`sSu$xEPmG~CYSkOpO>CWS{>`n%KqP%+jCF&ZAZ@6suF7>M0SXBn2cKa49fOU z3xca;>RCv~a|*~z3MSz0`;3rwfKm16?~>aYYzW=1x4(%T1n89sSsqhALPMGEBrbWl zq5%1iQ*?!Dx0V!jjz1IrxI;AjfxBDzeVt27B)=Z?Hk(pN@7&ao;Dc{Gl|(UK65m4> z0~o%%6n~#%mBE=4o>)4A*QA;&Rb$h1KKnYgSAog6VqP@D#8eYM3pyn8_?UY#IHLs_ zq1{q{w-Q66&fkfm$zBXouKTkAA6On2)dPO`w$4W){V1aa6v6FfbH*#6M^&b zM)L6h%i?Wg51D|Pr7D0T{~ti4hayuqTckA-Z!`*>1uDyu=!5aL!>i44Dl$f?b*9NvM#%y#MX ztZYIn^9*Vr7whs1OPIgCMi)M!N@Z@4s{l$H#ZDy;%=Vb7EJKpz^Yo*S^n;4>CU;vrur#FNxxu;*}Ub%9_$d!VG~s{8b~1*5)W@yT81 z>dz11iLpk#+l3ra_YUpvGFtdm*scS26)#CC+!Ipq!^5xl8z&Lv6G%F<4J%tZBCko> zimUouftQ27GVd;q-IK2Z@Y+Rwu0~Qadh9PIe~f(uH1oIW&y`u8vH8r!$`Ln|M>{dV zz0hATo_*#DY!o5L$K|@7Ygyq;m9bp&5lxQS0}#ri-ui{m_uYpISTO$H+Nhh%RDO0_ zTD6acjL@H87q`|=9Ja<`7NCSbaZx{lXl3M9Bac1NWWZ-4kBeHn1M67AZ6DG=h+ZoS zZ(*;q7n=xn2W$twoB=*`OhlN}6&yB`gw?6)mW2D`qlwyh!aJm1KD?tm+g)3ia#n@* zM`I{iQ=b%0NCD$Ws{eW)v#{ZT?>wy+x{VgS82(6k`zj0{oX zf<ro@^Kjm8pu7E` zdAI!!TCZsjJM^}kL1*Rv4=_{XzA;pZJoe)y3<&TKJ~{4BpDgP0aXcC9`rK__=5_R&Ohe(+-Hd=S5h)iAbguXWQS2Tj-) zOvVRdwes@>&GOlex8eBFBuPvN(^6~G7Q143FaApPqssh=n^i&qX`|yab#fL4kg}Qs zLVG1Ev{yr>Ey3e4SLo6p-~FlG1-nY)2SeBMSsXSw3(Y^tk~qwZIYzd6L}tbb9+mZ2 zZ0iXRvnUqbsfzl9l8LTD73u$>nXz8xsi3p|$pvHmKlqGykw8ifc9W*PMd;j526NT% z6$_)V>7+D}zJ&@Uvm&A?nwmdC1d1fK6(Us~>%c9ha{iy@nSTUUrn1u$_D-3-uJvuU zr9jdqJ2?A!{z?v-*MHzlwhgX@!Uj>^s>1Ql#i}~fVh&%5l_|%$74Sh_uZN1elKPjn zYQSzqoxzf6lPvRCg|8Ru(EeX&=BHZ<+Y>H9hsG!T$KdRo89{&0 z`vDX=S8-_9_3oVu=s&LbUwMM;0l6Z7MUp{Sh!KK$(R#0S@*20mK|TA*7Ij$nAD}7! zKLDEe{|#uS#cZb0WI(}#f!Fb4I4m-U#?=+KR&(TaiGdfn+6VvdulFSDde$w^VwH;W zTS25Dyd;ahk$GwFRD?L|G6M@DIk%4$QeGKg-ESe7v&y1sKGh(SK z7tk$Rju$mo{#=$V>B@ajn@8%~6Ssg^amRkX zK(ImD`ig?(VLt%}Vzdu14`Rf(xgG>OaM}UnyR=|adRX}< z^liaKhZQZeC*g}IM2gVU(C#gZ<+&Id2{@XB62z($?f=JN<^KiCZZ=+YAyRm|N$4G` zUe=!xXXu}Y#?j%eL0c`;gQ)xmPl^7s(x=m^Z1lg_d&{soy0u#~5ZqmY1$R$^yGzjE z?gV#tx8Uw1KyY`5g*yb-0KwfIy4IV$zw`CkeNK1(Ki9>NkX5XjC3DuSXN-Hy^B6-C zZicxy@{TP7XI?LLQ6lI8qk-=uk;O`Zf*U<#lycam#_;G*#g6OeSm$+3D)|=MGfmN0 zbOiw7CcJH;DD~bHazBX~W3fRe-^N-hgPWOrewY7;Co{VZ0|u34t}5!7p}02YVIDEr**L;M=o1*>yT$qhB)8ytitb=7kr zYpLaDBSu~}W#X=`WO2;YuntQ``nXF2B>UL==sZIfg5|LgZMP20bBe|ziyWO3w_+w_ z2Wp=re*YGVJLZ{PN2xv!Fdhd?L!SLsorm>I?UH2`G2zZz&OR?saqNH^#kJM9a33W2 zfWI37sIWD-J2X`q3BAkB84EHg`}B<^YS^CZK5^eN#3FE#>N!HO+1h&+>or6DXcrOvw;&YHw90ZIYq1D^(3sWyb6@cCf17%Q$@~)!Nmc+LwSt$2@DB)8bwK?6T=e;?={o zD`gD66kEOO_pyG&FUptdcqp=+w^O-l<~n&Hi0s-Ad@*}XsgD;$8=~A`M+yTbAN|f( z)|)U^Ltkz>)i`K6K5TXW5*uBs_x?v72CA1CUQ&}HfHI9n;yd8)ry`_L+3{GI{Ks4U z`e>JKM7<>p&JKlvOg1Vp2OcT|k=;?>R5!p;@BSV|GTJimVw$f?hAFWwO^;F{FTM>6 z5Om$4tKGt>!P2s$UU=o(o!qG4nanb0Y5O=zf$(qc8ZB19cx@6HhJL~0f3(xXd7HF; z>i4IKABl4K6GFV+oY@YRNgI{d1! z+S>mS6@lEp3Q9U_2XU>L-s%HP$yhABKk%GR@H_dMhigjcvg(!}c;813PmwKH9N)ov z*WPTUq5RL8L3jrU;(#(Ap!(?U+pfB=)FE62n`~_|iNLBQ8l`{+ZyqV?yz#WACRFy! z+p#0?8h@3Za)?@9Sgx5m5G-|a6eDx8x&1o5_#d3??=eI@*I)<;oooyiE$QvAqzD!} z2a$_P`C*bn7-ZPP;Xl(KQqBKM*dFTq!Wo2TzY0KztR;71LI1F_s-`tJbpKYi-{|!> z*nB1X-Z7g@l9c9d&*o{r11AR&Aq$-xnPgqsSAEkZWEhi^m}WU-XsF}ep46%4@YKDM@2p+g1c>bJrqQ;NJ)$_KP8qGpSVx8g7s|X*Zt0(ddKU&fy zv(;74q^4C=Ef+JPjt903+ zwpg@|(lcynvB2dD?3!EM^mdH^CT4fMReMj{*_+L+&eG)Hfkr%ldm@$!!#^{L2hpGP z|0Q;ZOXFYy)7T)=vjNrfn|HJ~oa^)7_=uLmuT67U;NyA15EyD6@*pFll*bK;xWPky z7@u>9-0uF*{@qjSM0Wfrz*-S&1PK_?HoXmF<;=hnB?FQ$EPmDjhYM~9Y<1(0fXs>i zbI^$P|3UeV`)MW%oPkuh1;n|qM$nsj3X|5sBxg$Nqq9V1Ljdhu1+%eia_9rX6EgIb z7Tc88n~q;}(G!eBF~U$G+E~6Hhg$@iN49+Km-tXX0d}!)s>1EGT~*~BxEBCA`F#av zO#uzYAUedy`Lg9)i%9bX^9-kZ@Ek7l0BbI!U;iyAe}Z@qf?;qKp;_n|J-B1h4^p#Txd)0e~`4w$bsJf&I0(K zs9InWeKEf*VsccKH}Bp@6Eg%h=m6B}=cACoPckV2!23a;dd$Ky8VZk<-5R}ks;nGj zI9cuv$caGZ;&aC5FRFu0gBUQ>zv zWrBkO|5*n)*G$_CWbsiQOO2;mAsLnQ+ ze2vfX`&wFfOywJ{(PIaE+%pA_Ss026_|3c~B6CFHMIwYKVtN0XEJ@QX- z;lBgCB@QLmsl`VxIJF-Er-QG+q@-Br{f{wjYtE0-V@fcjcG_qH9%91!AS$Af5_RtQ z1@7@U^4DQydDNL*V)>g>lu%TXB*$?kdYzU!jFB3$aq2j94x1IG3FlXZHr>iA_kAH+ zy^b$rwIj-l-jC7oKjN8$<6>G`Q?H8!tfLYV~8)EU^~K< zST1AC9CV&6@BWW{MstqG^gHd8Fj(?%D)j@S1VhCNe(=lKF;qCI|#(65^54?jB#=Kwa>P9bieNLJ)$PY?$ zTZ~(FH+5nEF+ATQ(XcdvUo~Zx+P;e~POlSBN`Z~<=8k6EKujQnqbK!;gfm)_W!YZKl&y2Of_rz3#RJcxpms>TI_FLT)EffQ6|&0!r)=IDCpFDm)|m8>A;- z!B{N zQX*L_bD1zzs7&bdn5aFYSG zDY+*YblVj--yJCNg)8RQ*Kux{;~_KRTv9eszNedI@xHRu@lvn*X^p#J$pg17ZpF?* z8xGqwQs3wL&w9S$7}STBsA?!N14`TcM=K&Uz6H`I+jYN$QPWOWJq0hj`68a;SLR`3$*0EQK7Bad0UD_>pQ0n;KMlRj2gLBqraZY%PxUIgZX4wLQ;t=7 zSZDa`EsY;+ywY+6d>HgzTI(!U$w$1XD_m!GnW0P~LVY@Ye&io2^NWx)PJ&ByEqu$L z$bu1FQnZ(Jclq3>E!nIe$oz#OmS-z;bW+|jfAU9RmH^}g_~E~v+6&uC_$gB*5Ak>sq~m6q*Xn@eebimq&z#KS^)~#5s6o%8RNUuj!Uch`3bjhp z1t&l$o?B~lEz#q4bsuYf>oMZvYoiL~HWX4f7uzs_k{2{hwKFC4e+n|Vt@B0! zV01LI`NN;}mFAK9W{)C=Xu>Jw8IW{?OAtDmDNM zupo7IhxrT*(zFAl!}$JrSC<~ycZrvWw6>ftd&guI_Ro(oI5Qk8e77q_hge@-jx!m` zHFZj#S+$?$w2ZFYV+RMh^P6SV=A& zdBNx6+;NG-8b8H)*&3E$a5h9StTfA)x#K!k(9)u1i8&Ba9Wa?SLKO`>;AUje*NOSUsb_%)ul zJ`(Rky2PV0D}0UcyEV+RDoARWuZT;Y?b=I13wLvW`(GHAXC7qj+l_uRsGyt7L&B@D znmeLvKI@_5N?!+|QB@$S=HCUzvwGPbyP^9(K3&>oS#@SSHzdGh<86LvXD5n=ty5H2 z@Oe&z3K5skcFcK@fO^e!Q*1Or9#J($i(UH44ryEvi;O`7+HQ6(e*@T6g$1RGmGaIfWUrO zGS6O?hoM#gvb@Vlq*$cdp@%#L$sd`N7GVCORCRLpW&kvK42-mW_u==|UGN@__Z^t5 zTTqU&C&Y2^w#zpjyZ&_sAl0p`?WWC?^?DMl-uijncuK-{%8!{v&$tZg#Yns;dl&VV zo`E-kkay<;^>Qt~XQ*@m)F*(5lc~dThYn`o6kmor*pK zPFQe~Mu!KIV}-h>saKuh z5(Q;9xkGgEs_E-=#}?LVBEp5PA?nhQImf#GBk>#g*C^g7_`*$H_@(K5T?!O} zd-%Ju^9GrOZ1f6i1+{Bj2WIdIx+AnxD_d(wI1h?@FtT4~zNjGwA|MVTlJ|;D-x)r% zh1c$PEdRinTIn2LZJXr#6$WFyc8|c5Av}61injX2+VPK;Lin$;sCWow6WOC{B|LIf zj#pg)T(^kw`kW`B`nNgnwgXP7>4S0chixRBZ1Fhic=_MgXRa22;y?1^p(ha9Hl=@*i@LAt40kkrYkRUa z%w+k!dT6&wappY?vHGXf7g+AAkdwCBJXNc$BuLLZHP7-lV`rpTnnkdTAsr3}`ZhU| zxJ?Q~BBdLIA)m*p(!)bxJwD8vPQwz9O&a9-HsKPl=*6w2R0&$S?_RQfoo?%55Pk_U zR0`p_FkPQ0Fq-?!dtmAQjv#?4K zKA@uShJt!)o`^U14nH)nTY>4z!g1s5JOUEPP@NsRz|`Y|Yhx>`d0L`a#)q#;YGcqu z*CQu)Ti%z-XAH_zcj+vhQ0Om!ztc5{mHmf-8~SA|o@%e=r>(Oyajp$H?qbR0!@vzh z+2yyMJA3H(Cb-hHX}l`~3VeC`d^QP8)VH`%^idZ9)9!#bz`3%{cqqYnf+3NMU-!3u z^!oMCoCncDn>+oVQYit>8ikV#8y{(cfK-PKJX?O7MP7M6ptMq~W@gW9UldAM_9u!B z{v(!3z9eb$QO$ve`cb5P?RNEmCZ~u%VXi_nD8%=NuOZ$rAFuO%!R%EgTF7jn7oVOS z{eH%B52#8m>z#PeI(qwCh9wlIJ!Cvk7~nxgM2dd6MLpO+5VM}k!xvJ=v%1r-uGPSc zwnHj6%yX(rfh_aoaXCuWtkRkabBHz>&Md+8;Q=0o3TO0cFL}uFD~_N2&mnz{23b=d zw^IZNPxaf4s98qg6E8XIsZdcaa|E6_XILLthD=Ab4dt@PalRVeVZn$aztd&_JAHgn z`DSD%&JDuc?#_xmbQu)V`ZHpbX|nSv{^@}AX{$sRG@hMSolg!Gnk%hK6Q}|qNLhdG zHb^l?4e+p31MC!t-4aloMv}`N%kT_LcCtG`B1hxN{OtA&I6gpZ2RATbS^sTj2O$DA z%F%7GN4pT{zEEXb2hTIQ5U4?V(5v|LHm7fxjA;_T0Ld50byG769dY_MDbZe4*u#=| z%*Zu|6FU36>qc;z)w-vzFix$@XfXd&ai?6M`^Td;jAGCGq>bqCPN~2wRb8)r%x6rP zPgPjo`kQy3SWs~|C13&*Y&)-MS*lo35C{<$msg(+aU!MhcD2<<*7d34ykyhd050Q; z{?uFs{scTn8k+bGEQwrV41YQqiO-$MLZ?t*$25+PfoLBgRB02CMIe5v;D65`o8Eb| z1h>EXNX|%L`?P>Um)Lsv`i@8Fv#rPQK6E!GkJ5^r5ia!#(Y)K5gAAYJ0io8~)Fjsc zyoYsG_L;_}0pFLy*Ta4FlMR2QXjHzZ7P+@uv3A?nn1masXZ``=F>k!8E`v4#DPc_| zA|H8HHYKel&sYTxrFol}OSjmsB~US`F75CzKFA=zpuR}I+;(n^2NF^Jk$3Wt>5tAT z-{OB+AVNRx&m%AORbaDTzD@??%k%wtjouUO%v`N;O`o?vr%T2JBj)^8Fa8c|oyg08 zc21^zdjU%nhiT!iM^-7e|^;&EpX9?5hfqvT@Iuk9op|2fsN2{x-46iM1U<{ zT#HgH(Rm)?a^!$)x?VJ+Fln6~{a1h153c-Z3Wb3-|OvMSanq4}NgJPLjyX6IP!vh^`n(u(gEt z1kh*`TJngClP;$5wQqZUH;0c+noW3?NzkDDGb%_CtoJwN?Rnw&d`ZNQIs#Ra)&vl!^1t6^=cf5=ro}B&pj>jE_DdyB)&U-!*zC?=5T? z?#Fj)%xO5Ec>P>E22t3hpqH6>E$i8|9>2pZi_w1`i7*(iAUJcPXC7rVJa=JX@(F7*y@i9FyDM-g;xt`fzB{Mtv9Z zb#r08KsrSckKRTbdQonqU4p4T7N zx@}0X=}k|BQBn5t4Dj&ukXa(o?u{xi48q=SyDWYEX&$`$=?b2~tjy~u%_6eFl({%* z5hxrwj5qEeFvyen{LDGh;*V``H6rWXB}fA}=o&gcK<`tdWdTaI6k^z^%}%5HT{DkS z?9vAUTO?!X?QB;;4x49PeK^W?3)f_JyJ7k@!1MZ-SHRFvxSrfS-gZ_3FBXtd~L=Dk%TJ$3Ou_!`FpTuW|$k zS2SJ)kxA#dr;hT?By07l*B{8Qj@)fVE=F^C47{#nZ|UmUB&FQ{L4`E0wKE6Z=N<3F zXojEA0mQ`?)f{%peaF`e9o5=%8nGP@w#GPNT_lRzqmR9|^YBO}J#Ya`E|8|Ki3^n5 z0`M;48DaqYZw>ijGpy#NjBYnfMF&~vXoVV2SzgR63X6pU)5a`BR3~GIbVKathB zIQ3TF6yJfJ2oynood_F1>L3LP!k7y1jA7ku_=l_3oGHs}oFBw1VC4b?elob!hVX`n zf0tbIh|5pp!_!C6Jy&?EL*-%uMgM`NaUtrY>OR*2jL;Tks>4Ml&^E%7E;>EcvJ_7X zsWobt@b>qPZ|69}e!>BzY#R7nMxZ#H_w9T6^fn2wYx@!zAj7in*Z+$Z0Vw%a^Otix zhRrtR0U&hz+QCP<>VMa+3)-Ci@6?uKmPd$pu4{AhECJspo>>Q$1mq%mR~6|J z@zQ&j%z10JR#7B*DWC=C6%9SqV3M38=oiZ}q`TE{)wt_+FM}RuEOk|=`P8W@)WWA)14KpUz1Og$1SN=&Cdfc&RuWsjO z-)BOSA2aKhN;#sea;Mfs4NfESJXQ++X-mky`2eO&O`3PJI5>>q3jjJmRZsIidR_fmHIZLegdQ0Y?= z{o(&2sNh$l5E>1vb_{Z|;{1zz2HTQr5De^r@;&fF2Ikc8sr&!()sk$$ zeITVu%7F5B<9}AyuB|Z^xq$Ug|K$G`^clpUjHY)(CEcE18K?5vGbOmmqhu%%ANF5S6 zJv3|8AVus2xJ1x_;10n>ssFJ_x=e-{gUjxHi7uXpo+Ki^>NCA#I^PyJvLPJUXQrT% ztOa^ii$DQl=9X@1>+Lnb+Br`#Fv9-B{4b9{sc^21G9R4lXM!m{2XoFpVG^%tPwNN)bO7A&_65O3l`A;#yYde zbV7vgKPhSiwh5E#gJdidBT}FtPk;_`fBCx-Qtk~X$b*R?FmTQOrDOz=6I+)bo`M=U zv_ECI$#kgjP5&IRff$P=#$ZU7{bCS%DiqM?0bJYgw6tqhfSJ=7xV^URb^cRo{u{{* zhb(&8NbNwGNKq&2s+ z0#Rj61KP7DJohSCdzK*i9JdwSDL$NJ%d|bw_IWtdCz2nB$)OyeGSnNaAE^s@Wexw# zHjljdx?Ir+=r1#FyTkTCv?(}NH6K725XSc%#PtCQ*lt_J^$woRC_7uBp=iSU@}=ti*aVxQ zX+4v{SShxz7rfmxZ&#v;JfVi|fzava3h4cHAI~zc^FAwTZ(+HA(%neY6wGbVfFP;5 zL{#Fd0tQVQI(Xlw^TteXIhoG7)Aq-FZSU(~3pm!#b7l83x}M%aK3Zb=hqilUK=tj5 z#cat@3Kg}$^fa%-ioH9#d3-WXrx)wY!{86wJ(z(RA!nGL*TX5t;F7T=fuJ=^5lq9cs9I9YgbFHuiX8(h1OiEtK zLIaQfsk!*C`GqPwf+!(=Czh`Q)M9{cI6_S#Kusx~d32{zp{<+#p;;RS`9T`2pB=9Q znAdg+Ht0eYo-24{3$qc!C1=;Z+JVv2@o-x+1l{-x5D9&;HBH(g5qxQP63z2&@H5Bk zFNERp{HPipAmAB8tQH}qz&8~=JL>(j%&^`fKvj*vD3ArgUNzQaHeC1uA`tdrPCn8axkhc}M12DaM8#)5993fEl$Y3OSxG^fz z#{*ssY07}d^~Cw<=7f(X)mKOZk|^YzFF^NZI!Lo(?-nI8>uF}?VBs+y*(O4Z=!x?W zhdR6)pDj^QK-GF>Lf+y1*R2LULbamS4It*ktTCVNIWdga6 zN(B)Q@pf>bkG)&fP;6?IFUmKk0{Po{*>XILPj)U8UaN~Zbbr|23*+1S`WsAr4=ykr zs|{SM6pOgSQgdO({=Iqb_S8hgX}5rnBJR_~)C(HPo?_3#+5(R63$GTiuo%p(^BM0e zkSGMWkcnRQIpivs+wTM?L;b`3LbrU0+jZ!x4ErMXV1L5%0JMDDdB3E4$AJFUwyW3f z@%Y80NaI0hu%2KnNi6hR|Hid<36xm2qX3G#$@@O%=ph;`>)UiRXGf9DQ%61>^}JR=MQz*pSMi} zQ`MLgnY_>MOXXmqf%yaXG-i|>aJY7 znGXFUT*CGSD46{${uQSgMBpO4adC{~JQsAy{niMfEeE}; z4kx-cwNd}*wAbh}yZxp%U^U>N?!R4UPT2gpl6%Tb!-hxLyvT(<3xCmG=K`xEf#;FQ zwN>7W%-6!cdvO8(=q<6Y&`ySsft&Vyiz`zLfpo9W(YS(lv6~ zI0-1zX;rLfc~F#a-`EI)ZL(+KZhlCAr2(PlOd%yIbHa*gcM1*U4kPaCx^F8t?`=L_ z5%_R&QH@?vrGGe17jq{a|1MVgCqUD6M#$tQM4YlwG8CMc{`dRi$;g0%A8mM@_=~=~LT6#Ou8W4V znK|9hKJ`wYNF+i|IcgBd_Aoj8;yRJz%6?IvgbNjbv*SRZ=YzNa6DFou0&ycX*!7fspPdg6vt0RSdte0^XGL)B z^RRCp`|U3@U&rMpGi-s7(Ke0-f838%uFfjI1Htw5B-s19!XN$sk_i0+QTt( z%Vsm_aJ(1)X2G+(GKd6;RV7Sgc3BwV^XRsEvdXo2ex>Zn9T0psDeYM=Pfhf3%|Rr@ zJb(#4VP~xpX?|dmOjOOF*+JsKyAJN*cEml`=W;AY$N;lCuo}Awa1Y3(e=0wWiqvP= z$_v%x+42_0V#V1e#ud?I{S;84$}6j<0tpj(QtY~RvSZhsN{nQ3W)}nsT;&zK_UC=s z5JHJ#UG*rCNr~0vTb60}*xid|K^%|~D{4LM*Z~UTGQ%x(g~akuwqZn>`*fn(c7vey z+qL)YdL8noMs1D&1?jQ;Z2OyMwN4-(9!1BD-R#P>tx1etwL^{qmSd7Kv_g2L_6{V| zP2YYxZ+31N7j_>+vrHaMLp4C6Cp4f{ti(mDQIqo)EJPpO!m`r zFcVZ+8p>c!#?Z1RtIINOqKhS~US>C4aQWE-*D=#DP2t;}@HY+pFh5pV&bpoko7IZO zF#$N)`A}PwgE@ywpS|umAs8YmLQRVPV3&-I?B?(?^%!8V$V&-bEVgm3JvStjl<1^QR=dmmpgN6F!nCI_T=S2 zmgNt$hd!F%9c$4)A z{91zN)9LVXvHW<&c{5cbJkoQ_f{u@Jp5iftPr&G9?gWFlUC4oSH#OTq$1LBBqqnklnk;X3>XiN?IFPzf&O@vgqdJeAI zwhx&6-irE5#B^l8Z8fRmL}|78$?C0Gsp)~Sf?TK7HC#3=Q{=#Z-lB7GqMK{@f{S;8|Guo`tv$911U%E$)0*niHc9#kF&5^*-~Knoh? zjW`v~Qd!YU7AWHAsp{WgnF*;5L-=JkeAn7mcE-oWv9L9fbSiE^mN+ZKoO>!Z0`%TF^rP}b8$R%w~sREZ-Zc||DvRH62$u~|a2@v~uc z&c15a2ls43olmWYILx-Vx^9{^Oe1dIa_=wb8z(Tchlg4>BQA*!xks^qDI{uO6uRu% z0Y<*r^NT{w zXWp4V?;cT|PD*!K>Md5;4e1AfhsOC6qKM-*c0yc^&(7Q9QZPk(jj0w}mC@gWN&R?3 zkQT`?eYc}!xsRCxe-z)!l2#+3R`iyn6MCxUe5;m0=(#l$J`yXowb1 zL0{o{IE2gemRmp3SEYn-E3;3yVy7hx7c-h?H~3e z`URpjE0MjE&d%nwV8P$eeSZkf4IS83k3vC&ymzS1x2x+Ck3yVbG*q4(9Sx;7Ft4e1 zC@F9q^BnsX$RhgqVQl@w%&EVTLa&uCBza2ZjQe!v5URZTUFfmoaB{8w z5|LUUZWy&Kv0g_(-L=hBD?9`_go?Pnp#UY&sVOGolE9ZBh5ng}=`xaYV*LBFS(eQjX*GgNpS4dsyZ=BBC}dN`SlpN89?~6xaxXn^ zN2_M1b)rdD-s%od^`1VJa7VU8av!Pv{1v*3sPhHV%DwTg;RLcH%jlvSQh$ zRdXg#XXVmI7$k7sdt76&%~Gtm6_l$C*u)Su>$|-%4)?4VdAs)ud%28w^^sW5>dm%T zve`}#8)xq3>UUxGlDyb6%^=_Zw#OGZareesHQAw@f7%Svu)}6l zl)dO%5HAvuv3PEghdRe^GyvgPXZqQhe+zMxBv3u#pbzUp?f79rIET<{L0tVVH#f?r z{hAIFz;w72BXLj=4*J=mrnV1GHb3}J9%Koe9_M~Ycf9K}HYh9HBqjlE_S?~rb6|Qa zJd0?IzF7Df8AflNUwmX70)mYc66#M`QtW(KAs&~-0v*O8*@;*B1ts4SQsEN>;(`V; z**6a61(^4ASs1+?i5X567 zPB;3yP)`z78+ArV6jU}i;e5Nj3;zw+{;l7zFtB-B_9sn0tbdGP_QShLZanIqgt|6t z>j?Jg@S3AwJq2Q`x}$P%Aixk<&f83{M*a>z2erAi{DK3))w;1DslVS;4?!56N{Vy} zkoO@}9Tse)xIm6h&5dRTR?eFAhBHENvJT+uM+1cygi{lDP%+$X5m@PeOL}J2L%Wv` z0r3U5ZBz&05;-zwSZ<7ryRKi>ba13F-FIoUgEDU~EGN{-{0k?-W{Mfb_zAWtCH)_{ z6iRA3Oyh_S5ucWdox0D+w~7-3g{3X$if1Wvp=6UQv8DX{^6EE{6-r-37 zU|rMz!tr6YnViL#&`NlM5k7;S-p5E!x|yEo-eBAa2wUUI*Kif#&5m0Ivm_BBu&j<% z#W^xjk+ynJgIkvTr!BHSjRl6>{{y)n_U;tf{t+K_yn$S9gB&|4XIMW$`z{OV?kp?`JvO$LF| zr6I#v1syc4SDdTjUj@_rY_T-UZ0D>{8F>D{54t0837`P%a;DrSnVfB#lX z|DHzR@WHb8#lB$S#|}krEIEmk!6XHmA4E6D<+_~kXcQX`q`D%B-nO_UXJ)k=gMD5_ zPu|IN0Y5}T*M)KjW5)jE&14!`M#DBWRR|@Z3ktqbO=fAHsC3JVK4^(JBrI!1XBq<0 z3j0AO#{^8ThJCKxEr41}O^iv!181u*Ldg=k=8?J~)_;N2ho zR0FI(!^#PW@Jet+EyagL5i2u``P!jzI5ZFl>iqUv^_JnYnx8FHnIGM00O`RP zjl~($8QVFTi4RVp3qv7#Zp(sn7K5^v__BWC)F;jl4<8!%!tZdoa^G#f15HdX3d5Jr z!_4)g%!Ml(9*DU1k|$FB?eA|mF`?nf3^B!y0DLPad@j>>xt_e}?-kPpsKCwxhS^H3 zEr$;5GNKtL^8^M!7`;hTv4Sg5GfgQKk#vwVpJx6z$g!t(cgjDN}KR zx==uDeM=~JSWD5Nv97-xKPB*_#?Z0qLrEd~5z5V2EeIn1a3uM`s2^Z#;kxwSI-|6| zH+RotiYQ5N+Lr=OeUyU=HkIWJESHg?1sd061#>G_LU+1-{2Ms^HknygqpG}twl75a zZUwi;-Kd&W&0cR$Q88OR2+T-|4MbE%pAeTuzOgkn&Dws-xL)2W;c@Itr;Ce8C^`U zdsaO%HXNUn#@LW7tg<^oY9mhuq-qNlY_4Os z>XJlUS~bnjEes_V6atMYZzHtuB?P^Dm0V8H7u$4>KKkxe=4=@yK6vT^C|m!2J@Rb4 z&3;`E4D@KC&6OU4x)Z+?mu2OHlyNU!^?+}oz(9OrB#RP;q!A?WCwCMPnkxK6xH|=Y z?yc^$`fHq&W4OJe*^l4UBU2>xgEhO2j$y9jMg_!Nj+Oo;*vA$_O}*#G|a1~`e-4}T^3Gkgey)Cq*3p}(U0nYbTtnlQcp%IbGfz@}!5ge~yawZ=Jx`;RBUZvxoVOZC0%UyFbr2heug%)P%RfG#s&Q+|3h zs=qRS7Xi@r|2$0PH&8G45|Dh^*o)_`Pq@kk&V+PK+&~}eAq8Hg2v6E9NHO46>^m1K zQDOzxIX30Q1cT@8o%XO?36UfLLx2DqudeF;Tq0(n=}=9z@LseD9cVcA>emOo9_|m+0DOoB?#qH~ z=`&>dd}}JyoI9$it=F?Zv@A^0iz$Jj8tA~akR~o|=wXf6{c|ZC9l%wC76M+F&=TE| z^)RYX8gh9XdsfOTsaqb|7&Y-Ns`_r2tzIc=dBHsE!}Tk6;DeK-#dfllAjW|LSMUCG@vEkPk{dy zS`Dua1HM<#4-)4sgU4riNcZ}lg$L;g2eS&LoHx)6*CVl9l5Zdmb5#tO0GC=|>*x`f=l~>9z%kA^B0qYjnfOUGO5v)rQ$aAzRv$41? zXO8ehn!J>VX?wzz@pKkp1_X6pb$lumNLXo=xx9!YbrOlaQTQGl^_|3zj_I%vuJohl zKuk}*8<%905Tp-q6)$C^Scp2_>j&fS)_Cw8MAor_7oX?`ja;Ja3lF9BPy$eL9LXtG;|Ylj^{DTwnL^ZW-hX${KIyo60?~#S+8WCgK&3})b^@#PdDIk`nED!IeUXugjN}ep zltbM0Eejtfunsp7drMp#s4xt&MS!?eeke(yBjLKgBLmeAg1d+4Vf$H^^Tmwqp zfsi}pP~hQbdnBd(g`+yhL-EMqN2VLFjB>91et3$_~ zAl1vY!mi!as_94$31w!HfdMuC2hiWK8Y{aVLA@tyzcc`qenqtAK^?7vsG_JSKnhpbcd_)X*K-TU!Vv3R0{|(DsHhV-iBc^=u*Z8nsH1y&%E`RW^!OT$d^u}cHV^aRa6IVI zWyY=~&NlmtBSc1KM%|yP$z)q3Kiu4@Bw{FH@gUs{L{~9YU=?wlvt(swqWZvN7CZx5 zl>5Go$m|6j+mk`GD`hO>4>Eu^FRC`xfmP8F%@SSBmjRDEP)UE6R&D{7R*rjL6%_)t zNtr7uJ1>S!3`{x_ncDxEa%6jCtKDXEQ(0GavsOYWm|#)F`aiDm_XoN#7TTCRXD zA|t3_7+>=n6gvDRdoBe2%`lDRdw(beYDEr331^+p`YTZ-cEB_KVn~k})!8l>nQwwV z=lq|k&?bCpOZcpvg4zCbAkgn~w!`X?H>!GI2HG#+6S}3|Ye3DorB?i)^HDZSaHj3o zvI7qA9?XC{*cs@y^M2vqG*kdC{vQk$XROGths?qhw+>P5Z-ACxyw&i=fJQ;Qbg&lr z7Vs+B)d@B8@S~-IEEx8%!WVNS(H-2kg=qswye1G)N{4>|kDAMEUo*gRB@RiUa0ev|yahF#DfeRa%KVOX zdu!^^)P*qX4d=RazSs$W*J(5$cmH^*1LT=2-ruhC0m0(v4!!7o7-;O^LC_A4u&lNS zB$gStA}I#EgZ61zSaR&e0$ugIKNoboRIddtqGW1cQ_*_=nN_-PBFI0Q`gUzQq=dYk z-j6!6H&qKD$$+d7&2v_!u`Y9%;W;dV+R4=Fdg`1*7kf-9QaDINl%3-9Jd>}q%oJ_}};;A45F)-UzdWM21aRqCR}TmdvwXP*?N zXN`6RW5H4Qn#>A~e7`=enpPK+PzJ>h6?k)%>kQ5ZE?`)C1b)5#aE#4$oZw>X zB9F~c+;@MWI?WN~g}KZ-<>}>7Xv1kde@*;+lWUY=#CM-riNdiom}G=}!!@k9wh)E- z2{NiG7tRieL3esbEy#6H6mJ01u@gepsCP*HQo5#@BQdX6ne8fheK4^9oW$Wo9{7SHciQ4Gkjo>G_?2A7i6RO(+(|4^KgJ?7nDw-BQ?u*k0(67}LFvzY#PJGk02jk-aTxiuj7d zQWw5sR}N$Z7da7s2W8p?X#QX9z2#d~UD)<3NOws$Qqm&b-67o}A|TQ&CEX?64bmY< zcS|=&35)LT*n|6ij(5L%{{hdZ`#AjKVyrdCm@DR(*Y7;fD{PdXn+oQBuc8eI;4VV7 zWjxEkIqVxk-*EaXEDW6YYK9wiE5G_1oxHd2Azs4Ex%pHKgDN}AWleRUL#tu>BeoL1 zMx-FGy(Z6ZWDnMi(7n_<=9>M*`CiXZW=GbgI-54~3Xacd7#vn1e<6x5>r^v^*J)LD zw}umWzKwVCz3k(&aDS*}#_Sn%E!L^6Zl1XF+Br4y_S}wD`XqC8Xm#FoU7BKbaXr5- z(_;F2>kI@8T5Go6zi)JMnbD?gLYG?Xt)H{9${cYbJwYLxQDttsU)0-bz1fs%IJ~u1 z44XJGZr?-y{q(SMk)adrdcCy#s7=6o?m*J>aK)7fn)KS{5b<3ySU7j(j(=J%>+`i8<9I=AAYU#prdG?F4zWO&Y;DdpmCn2boL{KQijs&?Er*og1nlp)DTs%CuGtPh5;S z-qc2sLa{Vg6*7kPd-YMMcR$RO`7@Q=sMk0Y>c?|4(2#zfWfomhC#Df_q0ecamF6$H2Wc>iYns(i?ebCWoeWCsHlgX6aPL{l+z{7|BahW7-68e7hr6=r?jxB4sg{wLqEhRSG z4vZA!&(hCT({#}Dh^v0hgWllY_5faQp~iv^yQX zm@EOf7;saoe|}lzp$dr#Ktw+4II0RhNecBRFyCprcR7{UuQiX6H`eAgVqcuxoFy{6 zj7k^rV)bc~nLPZ^jr;-T#9mj+dahEUKxq^=RzBk@c1uIY`FYypEAqSxwoVQcZ8Cn%xG| z@Lq&Ftcn~uv!?7!+|YO;l;PFnweWVkEZTL%?srTF3mAx#(^&`M!310VC~+9jzQs7i z#CL92FL6%0Z~PQ@-)fg;qU;Z0-Sk=Qzg99c;mXptt1Ex=i%g*z(&{Z1>0%GAf}~H9+wreD(tZu@E)}9LP3_5pV5-oqH$wQW?KLE2 zJauAW{lWUL-`n4DEx7&-a>!HI=nJel3zu;+_pb-f6vTEdPVi?Rg-6X4f}-Fa>Ul8U9vxz1&$1vYuhE z`#aHMNKg)U)U^3Et05K^nd=&K2ccZlW2}49sxvAD2(9P+LD zhwf5%)Ej;0XzURrV3*@{`n>A+kW^`YgF?V7ag;;!rp6s95cy!iF!wBy$8h_zIN`FZ zNvY$`cY#ZjwN4ft_`_-0A)XqzaATO$?{r+60(xxpb2a=5%}MxQ8qwM23B2SjaU7~x zwdVvP%{q-4ZJr(Gz8B2t@*MAx=zCAi@ zO8ty1;fQI1Ms5%%+VgJQBTd|nD*s>o3wj{xCp|ct&6W%gO(B>rYFKnL#!buRA8c&z zCVpeDcDnNf&Y)@3d~-h=wll`7A`$ZX6&u()tCvSMhVhm=7=AfVY-z1Wh=(Dv{WoKc z`o{9n$@KwREIirQVFDIk3STx-ky+Jxcy=1YE`Jj5)lv+x$qgs6nFQo`3B4ceIpL6t zz04A_{@!3%aF_`6BhL^iB5pv@NgIdiFoF+E|FL)1<7Adh1>3LNcZfhjV6_it_lZklG3 z9FHHu9o$>{DIIX^1M4Oe-78Zx!cbv#_wsFuH6j5Bc3tAPwU6oPKeY~4bHa`iSlaYT z$;Md80*zrcGPuu5e<|)k+ccTCQ2*(u%-idlKM|#~G(7h{1$P;Ii3IGMaxLFs(a2G? zaxo7RQi*mA?=3y7G-=c4`a*FuxxQkg{%LqfF*yvvN1je+$_n;60Q^2J6`d2v#dKw=)acOCK~a zXp1!n9VBk|yYofJ=9@rE+z1n~bDbrEz22}D$cyg0_!7t_3Wf4twmt1Q<`zzK z|HVV2^t<4EA8Aekvf&ppF@F+d;LhdzEc_>1z5ZScitb*vEH)e}x2I{&02i({%4HroIpjailTnh#ZTvBfQJ6 z@eO}nxmw#X=>6PZZL1FuLE4aSkJ(=f=P#)&U_@R zfoyTf^YGgy{~_Mbh$8Gm+@A|iV-!(?3B%aF;jv@9Vp?&zcV0m~G&(cAKA9HrYg#J* zenuL&y;UQ>!KTYKsNIowz?WN4ymim`81Yb{m6(})gkyY4+o*2@^X=6TTf0G#$|ZMz zoB8@3B`H-qKHLN}1lBQKy;MtMgd5VW=a9NMZd{569QA3yQI9gwcl+0=kHDHp-;xzh z7>{5ra9lE{4@Rsatid#u^PwmLuTsThx;Q?k`USgVh!3m*2DyQOMy}K>;bu*?>>Bb5 z{HHKe4(~<2>rce~*fZ%Qym}=)P#Yl@7)G!n(s^QwS_LS$iCPD_vhgOwCM34Pv($|* z*?McJIYPeCH|oTxvbl-j{PscKv?io~3sA z&Xf%oQhD@qP)) z)g-;>$ZNcZ9>fmv+8vpiX59-V-VpS)?{q$FvCC|SAA2)~oUm@f+^H9L>FVLQ@x?O7 zEP-oYgX+8giAcIS_&EhFyDO2|MoxcPeyVHShb@7G`*eV+G?L%9=aLhiB%$kYH%8XC zo`La3ZuepCyTQdxh@Iqs(d%d%=ud(=tyHoQgy)hXcf`+>OYPimJ=)zGwXr?L*^T7C z9+|dP3#p)lFjyt|J{e?^O{^Eb+ioo0W!z=n{ikO+zLwzc(Z)x@DsMgd%O5<5Z0r3} zDu8F#RCNPvP7^{0qpZ2A?6L#U3_$GkT?xaE=I-0_F$z@`sfn3#?2G_Ghr_6mOgp{m zK$}^@H_38&#%>$A!j4=YCVP1UL0SG36Wb4%Q(vckE6PnM{<$wG&w1b*k_nxlG@{Xm z#tYXp${l0Kxxvl}_YAU-pwzjXNYrCec~j%|SSO&tR$-9Vy2P2ON~c6-_&4lV)KRl9 zD2f1}p+FZ{=alD+Oyt9ZQY-ZD@vqGcg0YO3NfkX_S79*S{dC|oVoAJk!U8pIwb^NL zi6K{{bWm!Bk}y&i)F|C@-%Wx>N5OIT8qsk^49<8Dz*e`+3nxq`EHoz^r+8-1=PI~b zg0_{e9H0V@UwqIWct{h-%b&~TG+DhZj{0htS%AGymo2%a|Fh#It(Iw|rQuO-nNjIZCT zQ(}|R3QhqKap8PM>;Bi(TTB@4-%@Bfoj)jgn97AZ?GSvJE`DSG=j4MrJ&Q_tO^Tzo z+O#x7A6cC=`6R;Juib_@s%p2UIm^c}L!rJtx{IskDcbkpla@rq)Jqc<0e}_JFKxHs zRBn5Gx1TY%i0ud2+EGvP-jUSwc*c3JqkCb_ubPi#aB7P^c$tUfpw67nQ~)iOO<@U6 z7Pum+!exnRN2>Mh`-2Mn?B1dKf2e)AlzFlG)u6Y1=nVAozWm3=0g(TXjsM5S|6}9- zvGM=d_O7UV8&H_{&;7)3C2_E}rlHI7g+?5s@q}T!tKq43qm=M;dlb2cqHk zVdkL2(zTs~Yl+S))bl->*U_>vQp~&q zs2#B>!;ors(#${2<&os=2k}dnx%-q6@qEkwn|6nUavnhJcK17Y(=G`VAt)-qX|P!4 z{V9l1lXn>(3(I;i;Xo(P+UBYB{-n>aVeNi3C=+I@IL$Tp{u)xzFmiX%j%UaiH)}w| zl62~#(tDQvX$HRj!+6D;YQ8Y%orA1xc->YHX*7a`H z^$#)FPzdq>XLRf#sknv?sEAv7lQ1cy&cqXPC`PL)35Zy?JrLeF?3qpccyE=ykm>!X z0Pttu{bsXpCmu`2!w1_THq(3zOgw1=Y&%Q`J{rpMny9WtWq+V(Q=p2(VeXtRndK{L zJ6a~nGanaA_xx!$^`3eo0K{L*#BT10ZL2f`Ps@D{tEj z5PUIzWK>FIDWhDzdtIqc?IU@G*m^g1RD5n!wj8 z-Q@w^`YRUsaeQiDCD+gVDLa5k(R;wx_ca4VguzBjo9R(nW3!?qGzTK$Wut}o&q>|R znSdu+ZOm?e(G{}s_U{F}AE~eV<$f$}=2y)OPiJZ7r&`NQX0afCW{@2}zyn%V%QsYQ zKzt8}UkQ2rfF0#2;_MB-dAoi8tOfZ=WZ#-`H?0|#e;%Vb=rpTOY0}5fpsnrZLRhL_ zCzIiED&Us0MShi^2bx9$B%wM{xe6=NSM~Gb!01Hr1-7C~>B%?8sOxJPkX!n5m|_cW z?R2+LWqu7V^4x_Do;=kA)SIpS9bI0I{bNfH+Ice5#kJ4pj>9uB3q9JKU7=5R>tZvSA zlH*Atj40yz!#f1j4$l<~?z5rT^qQL1hQdSAZZWEwRf~3m~+p7^=$6?H^ zeA3N|d}X279_#%C6E*Wxw+F=VCx1)acUnZ&U}L((NUfKfO;u`j7@kqYSD_5=)rlzA zvEOIA8DHPjxl%CpJ^?Sa2!Wu7j?1-&&`C0CgrE;kPi;WF*eeO)1Voh`?m zLG~jJ2N!ZLFVF0Qh`(Enc`87H9KfHLwT^QYsTzI6NPZ76-5#D{oMwv&xsXj*IET%u zYwG`vqu^h5Uty3Lu_pv3?Jo2XG% zKGwH2q)ln)-J)jGb~aLYD-uc)-e7fSS#AC58BjIPwEevF^X6KakOM7o;Xllo&+Mz* zxc6;MK$316y}_Ao%pE+Y`RS6t zz3cYtXJ2y##$U5cqP3wjWYnm{IFg0JZQIOnuU;O3&*$=eL!?*T-=OBhlpz~0cO(T* zny&Azh$&Ke_W1f-;g$l)ywe0YpqLudpX8^F+b{jD$Q&jDXR7T$rMbMr5G(6j{;|Nf zlfn>e14mNcek4+R*Soe>85f?DJfe-`4;QdyKP@J4VFj*@yjJewS9z~6L>ylwFu3F` zONAACw-Jl@uEO6#RuRn_k%nJegnKWR^#1+Y=8-AbX1~F!-RxSs9C<;;h#1A4lLY!h zz>?Ic$}#m53Lbj43cnZLHygUvu<+o89EIadG(l!qhj2*B{S%>@OnLATd7zOCzHK7bE`F?!3Y7G@n#o z5RWxRwldd#ZboA&{_5&6rEMR{?5 z=7Eo_Jj;%n&-AvCvAtFyHE^Q&#*}x+H-O<>AVD4&a6xkwc))H|?lr#Et~S=@n(l6S zu4lNX1P-G%4iW0zeA9FB*bF#@W?h{h6Gld$_=<53BF-znS0ONxQ(*( zf$fp(Yh^9aBpt4*^1q8n74mMFADXpcME7=}s6iX$DNLDn@)}=bMS~+{6}>*rExJ|y zzbNcd@mZU8fl_wKRAoDN;?5)cYI}j)R_Zyk4$I!G4QJu$y@W@0FFXE~`{quQqZR@^ zOwYk_=j!1E`_`_WeZum)K$5v4jRWo*r(rsgl7_DrkZ{fUCYMakG=is{FP@B8SGZh) z<*2^$iOdE-ZQ!9(XK(1WV_-EDBQL1v+aLlRDtuUoQI$swrM1mrZrl1OHfS+fAas$p zQSpf-!JuW?=@g_u%$}~vkri{@@=e`vjGPp!JA^Y2iSQ=@mi^J$bbqemoOAhjRG_ZW zVX?&ro}TpblR?Uzo`|*Ke3r)Cp`_M&cD!+;!qwqY1rvD;r=$0?$1;(Fgz>e4mqw{p ziSFA?>KCgi?>xILNQ_tNliAxQQ!&yG7FwcKl5yig`tB20GM9Kc?+y*M_yd2AiOX?L zWe0@KF!j<4-EJdm*P90zP1IwqD?iPF?z7dnE;Yid=nzNSdUTzoDd8|$M*fIs+SsU8 z)~A?u5l~{nX?->12aS$q;uQfuRn;e+DH zIM%=*0R+j>BHIafWcAM`1@2+0{L`|;>j)qg}-PGDO2Ame)(cG#1lR5QC2>htZuU+NaX(*@Dv zXFB?MUg7#&!pEZNF5rdFIOW?9crtNmVfCDpwf?N54Bx}1mheV|KpMDfc278uuX+4h zJ`B@O>CJ0%f==S6)gYZcHzU^lWLCR!Y`lqxcmpGZHD@sY$kc_`Ku$aE{-P`hd`T>! zQn_pzIGv(s;QG|~dFcu{KC9VXvRQD}=wg&r%k4T&X#}yVa33|R)_C*(0MJq5jRqsc zpNmK?@iAx8?;-Tn;BJ@XbtU!)<>_5dNllyhV`>R$IfezRJ)b-Z=TcD8K8jF*X-I=s z5+kwVP@&S3#?3+n&Ax`6RAJq-VOvq?pJWHKFen#oT(f5DyB?n0lO*Rq=8!&#=^I1upkqBNF@DOcy(S zUASMUG=9%C^9J3z?GuqI0`8lzdS$MU+c;aw54T&p@YUM!F#m#0h$8@- z+czfvU#tnM?Xm`=#Xr_if#zpdtEH1a+QvG?cEW|{b1d{Q1pgIJm9|nxl7B>CGiXL* z8=nA0(Wne*%YXwU47?jf_3r13S(^jtWTgMQzcCFY7yV}OS z$9%;Tv<=@?Sty#hUO3JuZm1f5FZTw;zHD&gL)?`U0;LA1`{DZA{i( zrC8AZjd;?zyG*)!nsuk`BZVGvE5WuSTh7Hd-0)ix$+x*MJb0Udn!!56&Ut_DRrD}~$OKL=hc91Pp z1q4xBW4tL>7rG<%!1V1QODg*97jfHzR8$Jewzex+m)vPcR0)lXR{XmHMouQ(1 zl(LTshixRWFg5 zYiD!}-`ko3Drjf&7A^$uUPQj~bBMOn%f7|6=^|C+^t$G5GWWRxi;+zrjblZ3oCv&$Nhf;n5s?D954c-{j!*n?Y*iSY>sFriwF1 zTMFKu5vPSZ#j-9}tvT+;-t6vhg<|M2Y1OScdFRNLBZsmS-;dWlh|E{R*-pCS??gjG z>5i!v>+FI&RO3p4h94{Xm5m8{BSt!?eztV}M?z9HtSVKp+JJTdDY3G~--x&NEAVtK zX?|qE3*%B`MX)S7^XrL9lxGq3`vh{Y$JCf=)eC>&+%R5I&V;lY@Sjw{ZblH6VYJx& z-eB09+Qm|arWu@WZ&QLnd3kmn*4Oc)%W5u0lNh6<8HV8<8UplVGcSH>y)Lmq7lC)x zCSn=2)?CjnIdcdWzDEHJ+EUA>efpVm*OMqbggWC$J{0c1XWXlL{2Lk4f~ylaXKYj2 z8_NIqb(;Vr6>9qATry}-$vc9N9}Xj(#ftDOIIuO&^O==L0Z=TBxGaoijN*VLv6tYb zUyLvs6gwHy7DC;3iGPd|u}&ntsUBcY!A+n$t%zF^xa&K2TX(1$#ZCxE1*P!9Fg$S3 zQcu}^t*su>=pNY#*9q?*UXI%i z-e1B&kHqM57@Cq|(016$5Q9eMVgzMi(%G2^14Pni!=mN1R10-4taOPHUZ+JIQ;u9$ zQUaO{Q+X|aFRM80N9>PO#~mp!fxmBu33F)jiq*?ad%p76K1}W_%Cj0uNKC~ppm0VH|bOIBe9$cbEmWnc?9D2k=tr4-DmY1N4=y(=*@#?gD)iRogoIh`ytl{ zCId>JSD#^{YKf-cuO4ycYvo{`qqzT%v-h{zfAsP{dikHT_kYjXI|nV7Uj0E9Iry8v zxk=2hZci`Sujgj79x~rPowqXL(iF2I2$w=vv@liQdv_!X9pDT!9x+$_p~H6P#iDxw z{Vw+kF8Lpmd_J3x@@n;GV%izc2&c${S(?xXNaOyZYX8JF^Rea(FsS|pZd>4{&!_N_ z?li3}!i&=d8lCKDSc+A}@1R7}3{~)tXWl}*y~H-)Q?e+otpD8r$aFZ9O!)VSP(H2! z4Da@Es+B1hmEJwu-E;o&!!raRzLu>Q#Z>MW4Y3t^4=yRo($AuaoC@FtWdz-%L@1D{ zJ0I>r@yv}%`XAT6O0cj9q9z>{k~SUYVIR0+ZAK9P69ga&1sy=Gmu*h>Fso z(?3ta1{4cWLjF~Zxcsjck$3rNQqoOb|^o> zqY+0x++E3y=SVp9y6n$U5OG=KEd0vXYp_@O^<_R-_^-`UqZ4p{vK_SP)PDZT_u(6k zkOutM~>mL>{bY(R)x`c)-krO zGX&i8K$p1F`?FELzMY?nK8*u$#ko{*d@h_lp%3*}n-scrZFS#GJ z=wvP8DDoY=0~WFdhaXZ=q!T5YEG=#BSJoC|Sw2I_>{3UW?uQL3g^DCQvlWI~Pa_>_ zD|5ASXlkX*#Ub<(oMo~Xi?2Bzv}GdFV`9HizF6f|zC%KE^ickI^VD5zm-ey6tM22l zy9_Xc_tQy28^JjnAtV?f{W3zC@p6&~#j*Q7Q*UQ7Uuo>esQXLa1y!{FZ|mJ5-x6=v zj5EHpS2?l07hT9kq2VV?GVBre?g0n~B{pe@&} z^a$@BBZ*gUIKcp{{#cdGa!!SLJd^5}reee1`*xM`{`*WLB+ZV5_r-|XiOl*il5oWTuEhw+c)S=*%69ClAo|*Zv(}>JXY4K!d=o|hQzgILcl}^D z%A6o+m^pR)kZ_^MLY>(U+@JSmEAZ|Ec!x;DxTqF^*Y%CI_RbmIPqWd%5$j~CpWZh@ zls@P57s1-=A>!Csw$v%+_7FJ<3O}EqDtTfdFArv6;IN7x6}*!t6FO=>BYqjBN2+?z zZn)5$At+bKTOtvwCgm*02!L2ae7@RP2Zju`{;;I6u=0H2JK5G>l5wSmxGE%5rsnsE$g zY;A7U+)`NKunNWbDosYCto~_sOD)wtYr4PLLc6(`UD?R=t}Yw)2qiCzRUZ@mB)>IW zK~%1dwi`?pX`Ar9^EOy6kvU#6g3zJ@IBxyIiFs6Ps3PfA3#7TR-CdgLYah-C*_c=x z)+XX)zZz2~$jQ9Ibt5yx>x?&dxZas;i@)Q$cs~eNHV}W7z_ce9)weLPQ<#~wmu83t zEX$epa;XNNeGi}r_~(+sIge$k0mO3;-`|&g73S3>~os0 z%5V;~89MU5*}z*e4fgMNiy26yHOG~%mvvL7(_VTu8dJKHS)e-eJpnIeoEFt;S0W9p z`KMVXZn?{Hh#rMhcoOsV-ovTqM1zBzAzDA^CR!)#zY5ad`{)-KP_j~h9c3J%8IywreQ}G}RyM$}k>U5<2I35=_66AyqKz|pO;ZJEjNk?!Wh zZpkwpPM#cLfhkv9iu~FJNNDw!9Soks`uz^~K2-`E0DBFJGbeNkKXh;%_1|n|7T38P z{3&aC6$U)bXhQt%0U>~T%hm2SJWvXAKk-tMJvr!n6x`Q2XX8|o`8iu2+fIOIdRL|| z7wVB=Vzoo>i*nVCs|C0GVgQ$!T!QP*84Jp^BZcf)tCyS;6bW|1vpto*;&SKpb1lyM zsYUM7=%a{Eoi)ZfNU8Tc?LrqSitl%^$Zi+c2u~()TgMc6tbEx9{Ql}D)blyM4~Aaz zKbjuV1kN#dR{fE7AtCt=0qpS6m%vY6QZHAmv2MV#lV<|E+S!mJfv!4R6~8KSDZ6)f zMHUUbINSy?1~rZ3wzk=iBvJ5OptD*r3Z7R&ZsePyN%u~~S8z!p@GoA_(#eR6sO9U_ z?w@eht-OSX=8fXk0DS%yG~M&=KWej4FHs4$z5tQ&BP6d6t$y{dN#b!B{q4g1urk?m zB6ec0?7E!wD#uK%=m@h@T{)8@nPbm=TH-n}Fwm|{dd>95FZ1_G$?|62g3#z@b$6So zWE=I6RJk(b!zummiBkW8%eSTE4&YxfIl2N)8`$ZhqF@NwL zGM`l+_Kppf8!=KV-^ccZali){`x&D00B6Z=3YYZs=d$(AKTovSG#|ZVf2T8ld0JUw z8ORQro?InQ2_~CmL(~5egjZ4_2wN>m1}sy+CKz)EoV0__l9R5I#_eW<`O2Tx7y6DT zOQ(}cM2pL&pipfuKER;fYCWT0gQw@3NbD`@&~X${do*Do#eL~> zk)ue&u|y6r5t9WpGoc6dK9!2(@|Ym+GWprXPv)&iIX$mJoo~Y>ZZUKWt*jf~oI933 z$j1L3C|Gf*sAmNf%KpGfpXA9b-KqM}pxcf+oP$ZPMs(S$q(q0E<<1j@iG|yQILAfw zQQ+xbndc@?b|PDB#B~8%0De$asReIMbt)~F?82}ddsp~mO~MZ?7Z!Io;5Nw6kzuB( zS#^xVy7|POjRop#lW9`45o8y1BoU?&#Ok(r{hWL#R;qubAci^1MbEUIt5@u68Nk&h z-v0F?;bbo4!)OZATg%9~gt8vaF#;}!O_$;Yr^0!KZ5G{+6x`E^aT}T(u*Rz{AG|Lc=0Gml-Nqx9 zxkoNBcToXMBmbMr`qAp1z2=|!{dwa!6&U_iht;sS3{^PC=&RM!wC}!{!l7#7XL7KW zQD|)uu!X%^>W^Vhv+f7pVsca>gTLZF%mct)90M$Xv_en!sVJ@{;d*ZQgfK#LNbIEW zt&_u;av$OH3P4R3E3?t}iMaw+oY zD9=b){d38dix=)(^e^CSJm?=;9+$kH&AmsR!E2!mBMzAp3#=;Ui`)yomXG%v4LXG8 zY)iIcms02oM!}sRhIvO{3wI3)y({L~7#6kU>4GiQERSF6xO}myJ7k^F#+t**jDbKp z%HB83f+9JjwJL`G3vCd|VIW*gP;3dHk3s51pqD9Vp|H`d`{be-5D4+|MKt>0lyB0* zyN6Q6+@@B#snFfH=REK=FAOJ{Pd#)flUcikG~bmPgHP88(SIlL$MLG@eR35`P4~Y0 z3eH|_2<)Yg%;avT%KMncmYfq`5tvfURinS}OSO)*Vi9%kIw$kJpT<9NZP#g(sXB$d z+0OdIX7X|K+NRBFyo`H4(_QfrKbFVL3vvL*u}by8ta}+I7?7|6++$C+RQ~u&`Kuai z9SyNc<#7yO*(DS9#IexEh6E9n3G!({Jd9gsTuKJ{2Z>FE*ixv^DrGvj{TgSk(6ar| zwPZ6TOt0BB3;2*{Sz}#t_0fHufFz~%mEov?-vvBUgorqB!%CnKzLB(!qL#%`T?wgP z4C=IQBD+*CRzul?*Z27}#8eu@jh@2)p|tYLyo#|2l$=Qc*@#$;!|FZlF1|nV71kmB zqHcYkZN+NF+2Cv@cD!aUf}KJAg-8W0{1#y*lEm5a-Ta`;>a|4e=-B2Co}RLp``yPO zMmDBbF5KZPAY532tWzsh8?o!4Z_s&Xf+se6YiL;e@;3?YQuu%^z^~)uqT32CAGSdx z<3rERTB%mmm+c+sg+kj`_@52>slW}bl=9Ac8M!}7iN53hoV-bw9(B?;n-$XKH21MA z#M4e>vd?X-bM-(Qb>Z8p6#$I}5YxkjO3T4xo$HAbG`n3rrxl?jF|9)oVqL_cNGTlj z)nQX+(KO>D$|+p-{@HGyNMG}zxK2lxe_w6H-h$0X=Iq|}U9J_vk`|-1q2NYDRg5;A za^I{W%Wg4{uZCXx4l!1q!moCv3w>!ny0hD)lsecjDe`EsLAQeWBoqEtHi*yA_P*uJ znM*3zx}hQ4q4}g%-wzko0ECD%G`^2a{Kb9tb40l+bD`v z%db{n<4>Av3+}R8R}jXK#%-uo5O&5z6JSat@WqW##edddb$7HBOPXs45j=$7^4~X% z7!UPyzFv1fa+gSLOojC(L5JsJov-_P(H}^f{n&K?(KS#=nHm=jz@LlwwiZm!mhV6GEE5(9F3p7hjb%>1#?uBk=gC8%7SJyI*K91xP+27Z1 z4O^ERmj3n`Xtg#gJ*8A27u>Jhzv2u}C1|@Q;W)K$J0I6g{}Jvg!q3{!@ILp?ZQ4TK zN_dlTi~+=UNs4SBq>s4Ih8sjQ^LsBZ_~L4C&z9>`wLpis`__E^``PRii*RJgX-CoK zhtQRvuAVjFq9QExeVXi>I2DTg@gXW~jwlr7HioaD$8kv~Q5dfydrtnE7pv6oH#5lo zF@A_)LH5Hb>gk8Q(yMDck+^ezYEx@CJ|@4-UyoCbRNcI;YTGl$V%u!*kSF_eYCwE_ zf4g_|RrG8m{i#@Hx^JTaqdL(w_r^GkfsiEQj2(Q!Gv)edE#zvVZ(i#XWTGHx_O3wF zqyH08sAWr#75$1B#CnEw#>+Q`H<3RFJA8Le(F+qzNP(4El^wP>DW(GXq|Z5$ZWvk7 z8&LhUg&_T%9)X5x^_f;krMO|L^xtc5;xIo)=*4q`vQBr^&~*~s$H#hDI=x3a_yWos zFmeFgX={R`DaNSQBL(qQ=(vIh1NsCHD`k|+yoU!j;8^mu~@9~ok6SVJ8aC0AvCA9P8xe*+b&mql)?%I`S!T4(3Ngugo}{~0tTa8uqWewKd(pc zD?!Gk8W^YgfZN*+M~gM>8#4f_Fj&53g9?49dQZhDCz@SJ6D%LUf^Cwc)&*B6Yis>T>*_6cq;aDmYvtb+(wg_|sSe!lk zDkfI+%ltQ%ND|Jaw5T8$G!v`;)S@xz-=3Wy=J$V2DljMl```6*^|6Aw%kke}Qt@Z0 zMraVN%s)w__h9Je?04fvP{jrRa=_vxdb(o&jUM#^L(eP+SoQz??|X3YoV8V%f0Ibt zz|fo8k4en`_Wt|(rpM0(RztbKoqzLJ|NqNu zr|?q12T$~(taLdtsIt-x#i6GJy40pW#N4*Rmq#lVv3fP;<5_u67qf=d7Lzpe8l_2S zpkGOT*4ZJ6p4r9jG*C;E&ez#w5`#z_l?Hp$Iy`I8G?e&tJ1)KrEJ(b3;a_4pm|%as z8Z?N?wMdRpqCDH+VCMPwo1wySM9d$kiDf_yBz)?}dodvT&FR_pMP`nMMaK1)q{#F* z4H3Qv3M5CKt3`*6PGpuiNF<+PqthM@xNjZV$b<);KycdLKA;&rp1n5GPS69Q>_02_ zH)p12o5Lc;&Y-S}O{+N7m%?FLplbn|N_=N1uNTd5T|@M2{y6a6ep+2~$0`_4e7~fk z)qX9R!!qHW&GO>OLaV1cy# zrIvxDs3$MbSuPBP@SU^^A0^}B)rvO@r~*$d07>QRXoYXky?j0}{21-^>ajEmHKUkR zlG5ZDwxY>1noEsN8{ODSIN@#=d$UTJN0Wt7H5QYS)z#G{gpF8*3aJI7BIg=qI{9@9 zb?fh?NT`EIoRhF!N;JyyR0|dDmz-u1b$p+L3VC4cqa?S|AfTNt(DY%T*Ch2u5Ye)m zjZ86Bg}!1yYD8Dq+Zo|Invx`BonvLH25NuxyB_WoJJwCsk(zc^1GpeIc1Klkp5H&$ zNd-R5)Y%w9(%ZmGGlOksgOP14Ob+G`)xyZbM=`Fo#Ri93%}LK-wsJUB!flgEwRh$3 zb0t->MPYsBlG}>aOHw8k`B|q}t$)YSH`WHtEM^Chz5zwpA5_7;=-%M#Cq4%a(HDj$ zMT}$8)zGOir+5j65*En~-+)Xdq;d2}Rg{^6W#h{yBR(c3%o=WU7L)@)k_-|Xy? z=hafq>Mag}FuB|}&ER`#;Vd}l=b4S?O5Gjl+?I^UbuC?7e4xAiqck{@&Ib%78`->1 zC6N~W(G<%a7LKc+)e>Yj6x4(m$t-^Cbv);>wZSp^ui(MCtn)!$KtCkCKr1B^@{q4c zZwH*g6sW=_xD32*Hgd-OvWXpr*q*;~H>#-nbb3d%pdqBkI|z`D?xjx|2a#;1=PYNv z52MHu8=Y?05dgEyN_$a(F_^6U5~}DN>HWC*-$1Vgq>Uat!*gx+&W6Wg$T%Zmwz+tA zK^ABhpz!0ndWl9z;IgILaR{FC2`z|4}W;YCF z?7Ng?i?TCWl4KkElC7ULnNhavOU2m99uwZj?>*;y&N=V<_xbDmJD=xyzW04U_kCU0 z{r%48c`AByr+k0XXU&&YUAOQpu9ToBT)B4e`ObhUE@*djAANXE)_v+${amuyrq_1` zEDvaR4RmD|^YlAJpzs;mnUB9ELKV)0p)aO7Or)YP_q7?wMN|IEh-0F5%L8Z2_BSu& zKK#hF)498|*06ps9GQN>WPJI)IGfn|d^ZymLV3Xi{+9@pj@Fa(QSwzp>3)A&VnogI z0g?_1*AIadQx(Z$Rb34r7z{>EXAtKzwTMU+v!&@R(#YbkAk%>9)4p*gcVLj7WCRN_ zygf2P4k~%N>rvOmc_q?2?X=dFqVCt=9n~eu=CGY6$0(Ik zj0-DVCXmSiYA8IOf|5;KkRkz@9`o@Tx2EU#t%Qp=8$>21m&p%dWs(J}k_l5~bUPtF6bYQ`ZQ@ z=9O>;vfOPeok+@VMkjyBjk5fZC+GErDJ71uHl4AowILd{4i99={Q=bs*r}t-&DBfU zvQYR}2{7{Y#?LhYerva;ZehG1P|M^Gm}7xNohZstMkn;iO2L7V>&URR-qzG0uap~m zif}*h>ug%T?JGvndJe;(%Cgqc;nh(cfsA%TyW81%%pb#cy1Sz83^9IPzgHc2S~xuj z*C78YP|CKC2EwTRn&Rv=zx?eHWoY5+r($Gu?z^bLquU^&R<9+!JDCf-**aH**|?J! zovr|9l{O@NIcYtmPuuP$MRYx(p-p7^7?#A7liR|yfCeLrS`JmL-76X4Y3`M`u6phY zQIaRDE~nox1%%0Qtj!&GVEcPx-z~gshGZD57kM{t}B+A2Q`L$;NRHeuw`Xs(Y zHGZgZ#AS)AinHTH9Da203dXLPT20VX(hh1Fot-JDb*yXAuIatC!I?3QC9Azu~5 z9C19P!B;A(S@6yGw(S@Eo_xw_Q@(FjxjHtkA~zzi+oZ$meyC-4m|-*AD6qRf#M`hp zOBQ#0jU-jLN2P7X23~|QhAOO|+32cw zrpbai(yh^GC08x*CjAG|>z0BJ{rnbFDauq0+gHnd4o6^5okAQASYOsJ!!$f~xPb$w zK+2wC)Xhi<_3C@lSABgg%WLwMnmE?+dV99hk*v);;!|8nWjSn(`DZTsRWQK=it5#zxWCHO(T$|JT@;)oc_ue#Z zesRijt+-e*K+KfQ>1rC)Y$YOm_B55bcbvXzTs(nL)IoU7ffhPq z3uf>6<0^({5q|jt0xjeVr7DVJDKSu0phB&%i)>6c7N+@)3Z`hxIw%H|fd2foX=&@S%u#XCfFiTwq(_zVSZCBTo3}@;+Jq1m^6Fiv zw;@DD7Fq}g>qD4z`qi?3W{T`PDIF7C`kHWbF{Q_PZ8l9+?++y{s>A z#wn?uilO&r1tIBf$HejldqMv6V5QxR)M#B-)9Q*>$>xtKpfbSb<(RX>1{(0^#6rw5<`toBEnxUgRt&cm z)y-a9e5|e=&g2#!nn#}+Bq3|O)5}W`xwg)?cbnjoOL-5sx_Oh34S`*=!heL@s@KN9 z4Xqw0-;>#8lhQbttxBev&H1MH>HJ=a_Lfl1wMWtKr2>Mq`xwc8k`a0}3FUkdOh$|g zQ3|cnULQE)mQRQ%>(Xbh97QfxTuO!?_552(&iU1`{^ee+5(C(K+r`v_5~9n)`#++l z7e`04`A5zT$H@TL5(lQ)qs%qH^wi-w&cSVcoc+kx;fN)L!36E!zmL&p`xl;%ax&)H z(g>2O4Hxt=zsS+j8I2g$u{!{O+F3q^bzpN)u~2hkKScU?nL+t=!p?N#cJ=q!-4bRa z^horMOnzJm=QcTmCc#k1&&;F72(1RFPG1*5SMQVbbIiZUe)XnFdMrjfVt9t6kUC<~ zu)Ae-1b7Ei1145ELG#ej!s&JAqfS@2adOFlgw`oHkKPFRANyp ztD&))oOH`Z;pjf*DppH|uD|lFZ42hv0aIs!+QG`1>rjOa4?Fr8d?Q}*lEmiJ4F;Yp~$ zG+hG@lnrdj;VpWzQuJM)OYEZH2%BrH^KDk(8sJ@H$diFYCf{lPNv{VaaVP7Jj^=bD z%V-ix+vU{Q<8KBy0J@E_Q^wV401`Yk05tORa!S4o4O(QCZ!~R#!{&%gYwfUrR zW)+sL{Awu1vS`5EI1@9a>ccZ){w?B!`yzo$v@pT8O zAa+Fw7Z_rKA5|8ePCtz#w>8E}XK3NEfS}KT4QJh&xn>V7?r~n|ajNC=Fn=UXUA+)& z=`7+GD^0Zb$D&9Wq>BR3So-R~8};y?gG^;yCib~Y02=})qe z4zvSw&JQzXAfzW>?4(M*AWe`gH{6Qx2d-LcW~Qd$+lI4JB*~PkJ)^0$|2t^bgy)v8 zEVk|!qp)kE%}1xHXu7{LUS=AvC{HTXkwTwJ*J7Rfmz}xFXRRww6t4J!_cncK`*`jd zw-ES(*i6g=NAl(ew`67$UA)qJqAH2w3rJ{2&zL?X$St5GS-;O*_JW?}=JEJ^K4CJT z{pd)qe3OueKR8&w5N`=%tD-J>8{s>i`7imOu0YD&YJxWWxL$5R80-cT>r)zn%Dy#} zj^n1yda>SHv;n3N6dMQXUy5y;Nt8uoH+MxO1i`FQ4v~<+@9tn>mo5Y}B_#b_+a05` z|E9EMAcC=MSkfZzxKAV|fYqQ};!oeDFI3U`O48FjI)$R< z(n4bCfHFRzI*N!TPbSg3%{-k%o0TG$4rGv!ab~?|ZmMwpZx@E@;M|-CJDeyyXO&%P zrcfcGv;cvAHI|S1`>`Me3LRB?R9RA`CWXs<4(oDX5`vETNHLGZJMBp({W9j98?aK_ zrIA9#>ePVwdxIIZ{ElWF5$1oRndFa_b#W#`g=|hpnsds&D3(l;znmVT*I_2kAV1Uc zY_at_E9{c-a9V0_qo;O@(N7+wdT$XOp4lqM^vO zt!MT<~Lo;&N;^S$UByGhl+4bv@8;v>VKwglv^&?L zxxK&Q(m{BIu|eLz=TW}{HGU81e=n8D<@dbV$8W9=;A}m>0cimnqte^C*b9iBlvyjm z(o2E9cvV4kRZbj}CD+nOQf7+7xs5TLb~T_&h8RfL*eOw}A{>WLUZ_Hsv=wvT%ro~O z3-Rc+Rl|g)2c^mkwfiU$&y|Gz0PoLHL9D1xD^>3{qd%yd4*Tuv`}vrLt8d>lcLb6m zd?f3*wMFJcQ&i@ghXYN)12hHnY)Ze@$5IQy-RP9a$(?dLlDV(|`)w@LHh~x;&PMwzH zk7#eAq;Fv?NCT+lU^fAp9q)Y!=AeM|6kE5~d};NS4T%oxrdRq}pDClGe~_ZcBs0HV(%!|oZT<8IZ~A%X{=nBh!Y~B0nIds{ zvumUtc2-N$dBBPZ;F+q{l6vf61(@d}Y1qaBw1bUe(-1lNNOOhz-qXVLV5y=;0a8ig zcv)4Y=vKdf<-;&L$q+t3sS8X1lVALLgyp0?CnE+ z$PJ}^v(Ejc{)^fB=~t3hZnIMtwSAQ`#v#1dFmRa~v_7J+8|M0pSX5g+5F*d_@l0`+ z?5`thH_x9XHth8(?WN~yFA*kuab!y`4GZnPdU}gO;j9NrT|Uc=;y#DjNLvY7=m8gS zIV9l6VY_8k`A@R^rc1}59q1$8=8E67Xr0h*6}jXoFH%d4c1YTjKwi`zP@42+iB3$S zriL^r1D4maUPN7l@=gZN9jVx0ms@7zb8MP-S;bh81ep|cxU43zPc$Iq8`#F(j5}`A z-_4wi{T@B{>&BSS#b9YK>rudxZ{79R=Ak5~X7I@VvsJ{q{kK-JfmUfgkdUAjc|k@y zuUcxnbkct1w{^zI9?G6w5$}@_(DEdts(^k;DQZ!-t(M1K3X6#7z+f! z7RWkFuz_)uw3k6!nS5ST28jOupZ0$hlCWb~XcEVq&|ivL-~t^A4HaGG(%aUL{{id( By43&x From 8bcaac768707070a303c4946701e489e30c2dbb1 Mon Sep 17 00:00:00 2001 From: tjkessler Date: Tue, 1 Aug 2023 23:05:01 -0400 Subject: [PATCH 6/9] Unit test overhaul --- tests/test_all.py | 238 +++++++++++++++++++++++++++++++++- tests/unit_tests/bee.py | 48 ------- tests/unit_tests/colony.py | 130 ------------------- tests/unit_tests/parameter.py | 63 --------- tests/unit_tests/utils.py | 59 --------- 5 files changed, 231 insertions(+), 307 deletions(-) delete mode 100644 tests/unit_tests/bee.py delete mode 100644 tests/unit_tests/colony.py delete mode 100644 tests/unit_tests/parameter.py delete mode 100644 tests/unit_tests/utils.py diff --git a/tests/test_all.py b/tests/test_all.py index 0b89926..c9f2d61 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -1,11 +1,235 @@ -import unittest +import pytest -from unit_tests.bee import TestBee -from unit_tests.colony import TestColony -from unit_tests.parameter import TestParameter -from unit_tests.utils import TestUtils +import ecabc.utils as abc_utils +from ecabc import ABC, Bee, Parameter -if __name__ == '__main__': +def _objective_function(params): + return sum(params) - unittest.main() + +def _objective_function_kwargs(params, my_kwarg): + return sum(params) + my_kwarg + + +# utils.py + + +def test_apply_mutation(): + params = [Parameter(0, 10, True) for _ in range(3)] + curr_params = [5, 5, 5] + mut_params = abc_utils.apply_mutation(curr_params, params) + assert curr_params != mut_params + + +def test_call_obj_fn(): + param_vals = [2, 2, 2] + ret_params, result = abc_utils.call_obj_fn( + param_vals, _objective_function, {} + ) + assert result == 6 + assert ret_params == param_vals + fn_args = {'my_kwarg': 2} + ret_params, result = abc_utils.call_obj_fn( + param_vals, _objective_function_kwargs, fn_args + ) + assert result == 8 + assert param_vals == ret_params + + +def test_choose_bee(): + + bee_1 = Bee([0], 100000000000000000000, 0) + bee_2 = Bee([0], 0, 0) + chosen_bee = abc_utils.choose_bee([bee_1, bee_2]) + assert chosen_bee == bee_2 + + +def test_determine_best_bee(): + + bee_1 = Bee([10], 10, 0) + bee_2 = Bee([0], 0, 0) + bee_2_fitness = bee_2._fitness_score + bee_2_ret_val = bee_2._obj_fn_val + bee_2_params = bee_2._params + _fit, _ret, _param = abc_utils.determine_best_bee([bee_1, bee_2]) + assert _fit == bee_2_fitness + assert _ret == bee_2_ret_val + assert _param == bee_2_params + + +# parameter.py + + +def test_param_init(): + param = Parameter(0, 10, False) + assert param._restrict is False + param = Parameter(0, 10) + assert param._restrict is True + assert param._dtype == int + assert param._min_val == 0 + assert param._max_val == 10 + + +def test_rand_val(): + for _ in range(100): + param = Parameter(0, 10) + rand_val = param.rand_val + assert rand_val >= 0 + assert rand_val <= 10 + assert type(rand_val) == int + param = Parameter(0.0, 10.0) + rand_val = param.rand_val + assert type(rand_val) == float + + +def test_mutate(): + param = Parameter(0, 10) + rand_val = param.rand_val + for _ in range(1000): + mutation = param.mutate(rand_val) + if mutation < 0 or mutation > 10: + raise ValueError('Mutation outside min/max bounds') + param = Parameter(0, 3, False) + rand_val = param.rand_val + outside_bounds = False + mutation = param.mutate(rand_val) + while True: + if mutation < 0 or mutation > 3: + outside_bounds = True + break + mutation = param.mutate(mutation) + assert outside_bounds is True + + +# abc.py + + +def test_colony_init(): + c = ABC(10, _objective_function) + assert c._num_employers == 10 + assert c._obj_fn == _objective_function + kwargs = {'my_kwarg': 2} + c = ABC(10, _objective_function_kwargs, kwargs) + assert c._obj_fn_args == kwargs + c = ABC(10, _objective_function, num_processes=8) + assert c._num_processes == 8 + with pytest.raises(ReferenceError): + c = ABC(10, None) + + +def test_no_bees(): + c = ABC(10, _objective_function) + assert c.best_fitness == 0 + assert c.best_ret_val is None + assert c.best_params == {} + assert c.average_fitness is None + assert c.average_ret_val is None + with pytest.raises(RuntimeError): + c.search() + + +def test_add_parameter(): + c = ABC(10, _objective_function) + c.add_param(0, 1) + c.add_param(2, 3) + c.add_param(4, 5) + assert len(c._params) == 3 + assert c._params[0]._min_val == 0 + assert c._params[0]._max_val == 1 + assert c._params[1]._min_val == 2 + assert c._params[1]._max_val == 3 + assert c._params[2]._min_val == 4 + assert c._params[2]._max_val == 5 + assert c._params[0]._dtype == int + c.add_param(0.0, 1.0) + assert c._params[3]._dtype == float + assert c._params[0]._restrict is True + c.add_param(0, 1, False) + assert c._params[4]._restrict is False + + +def test_initialize(): + c = ABC(10, _objective_function) + c.add_param(0, 10) + c.add_param(0, 10) + c.initialize() + assert len(c._bees) == 20 + + +def test_search_and_stats(): + c = ABC(10, _objective_function) + c.add_param(0, 0) + c.add_param(0, 0) + c.initialize() + for _ in range(50): + c.search() + assert c.best_fitness == 1 + assert c.best_ret_val == 0 + assert c.best_params == {'P0': 0, 'P1': 0} + + +def test_kwargs(): + c = ABC(10, _objective_function_kwargs, {'my_kwarg': 2}) + c.add_param(0, 0) + c.add_param(0, 0) + c.initialize() + for _ in range(50): + c.search() + assert c.best_ret_val == 2 + + +def test_multiprocessing(): + c = ABC(20, _objective_function, num_processes=4) + assert c._num_processes == 4 + c.add_param(0, 10) + c.add_param(0, 10) + c.initialize() + c.search() + + +def test_custom_param_name(): + c = ABC(20, _objective_function) + c.add_param(0, 10, name='int1') + c.add_param(0, 10, name='int2') + c.initialize() + for _ in range(50): + c.search() + assert c.best_params == {'int1': 0, 'int2': 0} + + +# bee.py + + +def test_bee_init(): + bee = Bee([0, 0, 0], 0, 1, True) + assert bee._is_employer is True + bee = Bee([0, 0, 0], 0, 1) + assert bee._is_employer is False + assert bee._params == [0, 0, 0] + assert bee._obj_fn_val == 0 + assert bee._fitness_score == 1 + assert bee._stay_limit == 1 + + +def test_abandon(): + bee = Bee([0, 0, 0], 0, 2) + result = bee.abandon + assert result is False + result = bee.abandon + assert result is True + + +def test_calc_fitness(): + result = Bee.calc_fitness(0) + assert result == 1 + result = Bee.calc_fitness(-1) + assert result == 2 + + +def test_is_better_food(): + bee = Bee([0, 0, 1], 1, 1) + result = bee.is_better_food(0) + assert result is True + result = bee.is_better_food(2) + assert result is False diff --git a/tests/unit_tests/bee.py b/tests/unit_tests/bee.py deleted file mode 100644 index c1ef7cd..0000000 --- a/tests/unit_tests/bee.py +++ /dev/null @@ -1,48 +0,0 @@ -import unittest - -from ecabc import Bee - - -class TestBee(unittest.TestCase): - - def test_bee_init(self): - - bee = Bee([0, 0, 0], 0, 1, True) - self.assertTrue(bee._is_employer) - - bee = Bee([0, 0, 0], 0, 1) - self.assertFalse(bee._is_employer) - - bee = Bee([0, 0, 0], 0, 1) - self.assertEqual(bee._params, [0, 0, 0]) - self.assertEqual(bee._obj_fn_val, 0) - self.assertEqual(bee._fitness_score, 1) - self.assertEqual(bee._stay_limit, 1) - - def test_abandon(self): - - bee = Bee([0, 0, 0], 0, 2) - result = bee.abandon - self.assertFalse(result) - result = bee.abandon - self.assertTrue(result) - - def test_calc_fitness(self): - - result = Bee.calc_fitness(0) - self.assertEqual(result, 1) - result = Bee.calc_fitness(-1) - self.assertEqual(result, 2) - - def test_is_better_food(self): - - bee = Bee([0, 0, 1], 1, 1) - result = bee.is_better_food(0) - self.assertTrue(result) - result = bee.is_better_food(2) - self.assertFalse(result) - - -if __name__ == '__main__': - - unittest.main() diff --git a/tests/unit_tests/colony.py b/tests/unit_tests/colony.py deleted file mode 100644 index 3e83290..0000000 --- a/tests/unit_tests/colony.py +++ /dev/null @@ -1,130 +0,0 @@ -import unittest - -from ecabc import ABC - - -def objective_function(params): - return sum(params) - - -def objective_function_kwargs(params, my_kwarg): - return sum(params) + my_kwarg - - -class TestColony(unittest.TestCase): - - def test_colony_init(self): - - c = ABC(10, objective_function) - self.assertEqual(c._num_employers, 10) - self.assertEqual(c._obj_fn, objective_function) - - kwargs = {'my_kwarg', 2} - c = ABC(10, objective_function, kwargs) - self.assertEqual(c._obj_fn_args, kwargs) - - c = ABC(10, objective_function, num_processes=8) - self.assertEqual(c._num_processes, 8) - - try: - c = ABC(10, None) - except Exception as E: - self.assertEqual(type(E), ReferenceError) - - def test_no_bees(self): - - c = ABC(10, objective_function) - self.assertEqual(c.best_fitness, 0) - self.assertEqual(c.best_ret_val, None) - self.assertEqual(c.best_params, {}) - self.assertEqual(c.average_fitness, None) - self.assertEqual(c.average_ret_val, None) - - try: - c.search() - except Exception as E: - self.assertEqual(type(E), RuntimeError) - - def test_add_parameter(self): - - c = ABC(10, objective_function) - c.add_param(0, 1) - c.add_param(2, 3) - c.add_param(4, 5) - self.assertEqual(len(c._params), 3) - self.assertEqual(c._params[0]._min_val, 0) - self.assertEqual(c._params[0]._max_val, 1) - self.assertEqual(c._params[1]._min_val, 2) - self.assertEqual(c._params[1]._max_val, 3) - self.assertEqual(c._params[2]._min_val, 4) - self.assertEqual(c._params[2]._max_val, 5) - self.assertEqual(c._params[0]._dtype, int) - c.add_param(0.0, 1.0) - self.assertEqual(c._params[3]._dtype, float) - self.assertTrue(c._params[0]._restrict) - c.add_param(0, 1, False) - self.assertFalse(c._params[4]._restrict) - - def test_initialize(self): - - c = ABC(10, objective_function) - c.add_param(0, 10) - c.add_param(0, 10) - c.initialize() - self.assertEqual(len(c._bees), 20) - - def test_get_stats(self): - - c = ABC(10, objective_function) - c.add_param(0, 0) - c.add_param(0, 0) - c.initialize() - self.assertEqual(c.best_fitness, 1) - self.assertEqual(c.best_ret_val, 0) - self.assertEqual(c.best_params, {'P0': 0, 'P1': 0}) - self.assertEqual(c.average_fitness, 1) - self.assertEqual(c.average_ret_val, 0) - - def test_kwargs(self): - - c = ABC(10, objective_function_kwargs, {'my_kwarg': 2}) - c.add_param(0, 0) - c.add_param(0, 0) - c.initialize() - self.assertEqual(c.best_ret_val, 2) - - def test_search(self): - - c = ABC(20, objective_function) - c.add_param(0, 10) - c.add_param(0, 10) - c.initialize() - for _ in range(50): - c.search() - self.assertEqual(c.best_fitness, 1) - self.assertEqual(c.best_ret_val, 0) - self.assertEqual(c.best_params, {'P0': 0, 'P1': 0}) - - def test_multiprocessing(self): - - c = ABC(20, objective_function, num_processes=4) - self.assertEqual(c._num_processes, 4) - c.add_param(0, 10) - c.add_param(0, 10) - c.initialize() - c.search() - - def test_custom_param_name(self): - - c = ABC(20, objective_function) - c.add_param(0, 10, name='int1') - c.add_param(0, 10, name='int2') - c.initialize() - for _ in range(50): - c.search() - self.assertEqual(c.best_params, {'int1': 0, 'int2': 0}) - - -if __name__ == '__main__': - - unittest.main() diff --git a/tests/unit_tests/parameter.py b/tests/unit_tests/parameter.py deleted file mode 100644 index 437bfbb..0000000 --- a/tests/unit_tests/parameter.py +++ /dev/null @@ -1,63 +0,0 @@ -import unittest - -from ecabc import Parameter - - -class TestParameter(unittest.TestCase): - - def test_param_init(self): - - param = Parameter(0, 10, False) - self.assertFalse(param._restrict) - - param = Parameter(0, 10) - self.assertTrue(param._restrict) - - param = Parameter(0, 10) - self.assertEqual(param._dtype, int) - self.assertEqual(param._min_val, 0) - self.assertEqual(param._max_val, 10) - - param = Parameter(0.0, 10.0) - self.assertEqual(param._dtype, float) - self.assertEqual(param._min_val, 0.0) - self.assertEqual(param._max_val, 10.0) - - def test_rand_val(self): - - param = Parameter(0, 10) - rand_val = param.rand_val - self.assertGreaterEqual(rand_val, 0) - self.assertLessEqual(rand_val, 10) - self.assertEqual(type(rand_val), int) - - param = Parameter(0.0, 10.0) - rand_val = param.rand_val - self.assertEqual(type(rand_val), float) - - def test_mutate(self): - - param = Parameter(0, 10) - rand_val = param.rand_val - for _ in range(1000): - mutation = param.mutate(rand_val) - if mutation < 0 or mutation > 10: - raise ValueError('Mutation outside min/max bounds') - - param = Parameter(0, 3, False) - rand_val = param.rand_val - outside_bounds = False - mutation = param.mutate(rand_val) - while True: - if mutation < 0 or mutation > 3: - outside_bounds = True - break - mutation = param.mutate(mutation) - self.assertTrue( - outside_bounds, 'Mutation did not occur outside min/max bounds' - ) - - -if __name__ == '__main__': - - unittest.main() diff --git a/tests/unit_tests/utils.py b/tests/unit_tests/utils.py deleted file mode 100644 index afa04af..0000000 --- a/tests/unit_tests/utils.py +++ /dev/null @@ -1,59 +0,0 @@ -import unittest - -import ecabc.utils as abc_utils -from ecabc import Bee, Parameter - - -class TestUtils(unittest.TestCase): - - def test_apply_mutation(self): - - params = [Parameter(0, 10, True) for _ in range(3)] - curr_params = [5, 5, 5] - mut_params = abc_utils.apply_mutation(curr_params, params) - self.assertNotEqual(curr_params, mut_params) - - def test_call_obj_fn(self): - - def obj_fn(params): - return sum(params) - - def obj_fn_kw(params, my_kwarg): - return sum(params) + my_kwarg - - param_vals = [2, 2, 2] - - ret_params, result = abc_utils.call_obj_fn(param_vals, obj_fn, {}) - self.assertEqual(6, result) - self.assertEqual(param_vals, ret_params) - - fn_args = {'my_kwarg': 2} - ret_params, result = abc_utils.call_obj_fn( - param_vals, obj_fn_kw, fn_args - ) - self.assertEqual(8, result) - self.assertEqual(param_vals, ret_params) - - def test_choose_bee(self): - - bee_1 = Bee([0], 100000000000000000000, 0) - bee_2 = Bee([0], 0, 0) - chosen_bee = abc_utils.choose_bee([bee_1, bee_2]) - self.assertEqual(chosen_bee, bee_2) - - def test_determine_best_bee(self): - - bee_1 = Bee([10], 10, 0) - bee_2 = Bee([0], 0, 0) - bee_2_fitness = bee_2._fitness_score - bee_2_ret_val = bee_2._obj_fn_val - bee_2_params = bee_2._params - _fit, _ret, _param = abc_utils.determine_best_bee([bee_1, bee_2]) - self.assertEqual(_fit, bee_2_fitness) - self.assertEqual(_ret, bee_2_ret_val) - self.assertEqual(_param, bee_2_params) - - -if __name__ == '__main__': - - unittest.main() From 23cad6016917d913961dd947f791e26c01b51c37 Mon Sep 17 00:00:00 2001 From: tjkessler Date: Tue, 1 Aug 2023 23:05:34 -0400 Subject: [PATCH 7/9] Add 'paper*' to exclude list --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 67d9d6d..9ac716f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,12 +2,15 @@ requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" +[tool.setuptools.packages.find] +exclude = ["paper*"] + [project] name = "ecabc" version = "3.0.1" authors = [ { name="Sanskriti Sharma", email="Sanskriti_Sharma@student.uml.edu" }, - { name="Hernan Gelaf-Romer", email="Hernan_Gelafromer@student.uml.edu" } + { name="Hernan Gelaf-Romer", email="Hernan_Gelafromer@student.uml.edu" }, { name="Travis Kessler", email="travis.j.kessler@gmail.com" }, ] description = "Artificial bee colony for function parameter optimization" From 18fee60ae326a625a5f0e152eee7f5c825970923 Mon Sep 17 00:00:00 2001 From: tjkessler Date: Tue, 1 Aug 2023 23:07:02 -0400 Subject: [PATCH 8/9] Update install instructions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d719b77..cd8447f 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Note: if multiple Python releases are installed on your system (e.g. 2.7 and 3.7 ### Method 2: From source - Download the ECabc repository, navigate to the download location on the command line/terminal, and execute: ``` -python setup.py install +pip install . ``` There are currently no additional dependencies for ECabc. From 123301ea92480b09fa85ffcbe1ce058015f9d982 Mon Sep 17 00:00:00 2001 From: tjkessler Date: Tue, 1 Aug 2023 23:15:07 -0400 Subject: [PATCH 9/9] Add PyPI publish workflow, unit testing workflow --- .github/workflows/publish_to_pypi.yml | 30 +++++++++++++++++++++++++++ .github/workflows/run_tests.yml | 26 +++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 .github/workflows/publish_to_pypi.yml create mode 100644 .github/workflows/run_tests.yml diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml new file mode 100644 index 0000000..1ddf084 --- /dev/null +++ b/.github/workflows/publish_to_pypi.yml @@ -0,0 +1,30 @@ +name: Upload new PaDELPy version to PyPI + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Python 3.11 + uses: actions/setup-python@v3 + with: + python-version: '3.11' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml new file mode 100644 index 0000000..b2b19a8 --- /dev/null +++ b/.github/workflows/run_tests.yml @@ -0,0 +1,26 @@ +name: Run ECabc tests + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Python 3.11 + uses: actions/setup-python@v3 + with: + python-version: '3.11' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + pip install pytest pytest-md + - name: Install package + run: python -m pip install . + - name: Run tests + uses: pavelzw/pytest-action@v2 + with: + emoji: false + report-title: 'ECabc test report' \ No newline at end of file