pax_global_header00006660000000000000000000000064136306471100014513gustar00rootroot0000000000000052 comment=26bacdf64f3153fa74b8caa62e219b76d91a55c1 PyXRD-0.8.4/000077500000000000000000000000001363064711000124725ustar00rootroot00000000000000PyXRD-0.8.4/.gitattributes000066400000000000000000000001101363064711000153550ustar00rootroot00000000000000pyxrd/__version.py filter=versioning mvc/__version.py filter=versioning PyXRD-0.8.4/.gitignore000066400000000000000000000012311363064711000144570ustar00rootroot00000000000000*.py[cod] *.tmp* *.backup *.log TODO* batch_file_* setuptools-* .fuse_hidden* # Generated file PyXRD.iss # Directories data/CACHE # C extensions *.so # Packages *.egg *.egg-info .eggs .eggs/* dist build* eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject .settings/ .texlipse *.spec dependency installers/ #Rendered docs docs/_build docs/_static docs/_templates pyxrd/temp.aux pyxrd/temp.dvi pyxrd/temp.log pyxrd/temp.tex docs/server-client\ arch.svg win_installer/_build_root/ PyXRD-0.8.4/LICENSE000066400000000000000000000044361363064711000135060ustar00rootroot00000000000000 PyXRD A python implementation of the matrix algorithm developed for the X-ray diffraction analysis of disordered lamellar structures Copyright (c) 2013-2014, Mathijs Dumon This software is licensed under a BSD-2 Clause ("FreeBSD") License, except for the mvc module, which is a derived work from the pygtkmvc library and is accordingly licensed under a GNU LGPL 2 license. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ################################################################################ All rights reserved - BSD-2-Clause ("FreeBSD") License. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ################################################################################ PyXRD-0.8.4/MANIFEST.in000066400000000000000000000011561363064711000142330ustar00rootroot00000000000000exclude .gitignore exclude .gitattributes exclude bump_version_to.py exclude MANIFEST.in exclude run_tests.py exclude versioning.py recursive-exclude tests_mvc * recursive-exclude test * recursive-exclude build * recursive-exclude docs * recursive-exclude win_installer * recursive-include pyxrd *.glade recursive-include pyxrd/data *.atl recursive-include pyxrd/data *.csv recursive-include pyxrd/data *.png recursive-include pyxrd/data *.cmp recursive-include pyxrd/data *.gon recursive-include pyxrd/data *.wld recursive-include pyxrd/application *.png recursive-include pyxrd/application *.ico PyXRD-0.8.4/Manual.pdf000066400000000000000000063543411363064711000144220ustar00rootroot00000000000000%PDF-1.7 4 0 obj (Identity) endobj 5 0 obj (Adobe) endobj 8 0 obj << /Filter /FlateDecode /Length 206850 /Length1 579384 /Type /Stream >> stream x `TE7^utWa &(]EqC}wt\ ufYgtk}%ԭ,g}JNnխ[uΩSV'#LκMٻI^~Jݼd T?ez~@roOo8Fܗ}Ysz3}s3bpܶ)(> -cxO~~ڹtuZWcOZ%B%K}ci.[v;? BWtmX,Ϗ@y͊^>~Wvw-)g_KWB :;NFN}pU3~"$y5K~|d.Ww6rڃȳ{c׭>᧮gtWgPk6l>)ɿv}ڿ'-^+pޓr+d%:ѹ/|io^,mV+C ʉQ#o^z;oj{.چ4PR! ecH[DEP"K(QKX{ņ9P2A/6H`B=HEz j5NEfO\Fƻ'{cN5Pb<~u yeixnDVOnH='v2_b3+yIDW"/G!+GЂq]cjT`Q p+AvqdϡP͘h'׌c;Px$[QD:r6Ł*{V3n'u'?!aL'PaDWhS+-[3& JT}BsM0&s|Oa Ŕ݌mP!Wa*;j( l?$p[~f\'rY$jP*jjuSInDCFKP ;l>(] lV@@KB㶯{/0^8ĈC5L#ϤF?ϣiNYfC %Pn0Თ#3D7pOnYXŚj~/B9A04=`oGOOτ/NSxBq3ޒF;=|M?;aTxdtdE O9yAG{ۼsfΚ2yzS㴆S&'kk&UWUVcyP0-F2D,XzC'Å$Q =Hj)d͙ˏ˙99S<~OKu~O?^0w;<=upͅ"^/[Vyzp̕;;렾^9??C.pő,\0^I=lkYO:!B]==.ϩrOoWkВΨbY)=lqi6ړݓ篫9=>ҕI5_!rI8&F6h!%m?@gv%Jƣ=L's8{Fl.Qwf~\iٶē~ ==lsҕw($"ٕk}o":6nShH:]()cڃ:fJH<;:hI]Q۽"T:H;zLSA(˖;@?{ޞdA OJAߎ˝Lz. J=팝 ҂O|T KNc;fdr1@ Nm$XRtj;dϴI쑎K m֦ܤA9Q S(Lm㷓!<JH8 \Hc!HAv:lm'}#<<{A 팖iyv6LlڳbӄpM۞RrB./lo,԰hے䎵++Ie;s۫B[oo!ҡfU)$!U_KFHD)d';T t86m: ıl.xn6/Q67U< ѻءÐ7.KjTV6 zPd("$zbU` < F*ľ␁ :wXhuД@]tK )451@n@($fr9R(_SAqCpB?SG^ ) )G!@GR")!(O=Jn ߡǔN>ex›~225N~C~«_ʎRx˴_r ?)@VS ?syϦLS=M[0')ߝzac^{]@wt}+-@7t# @]t-5@WVh'@e1k4oW"7>?'񼔎F RZZ) gPXMt (FBUJCBr eJ)P(PD0&zZ@!AAGAKACAMAEAc9$$'O3@}@o ;7^-7@=8c@DC~rzKJKTlʜ(lp&MRB0BB- h ,ˤ`X1"ږs(̥RC[6B+YfRh0B3(4RFB=: > ^x7';+ )y 1o;o@ W@_}R3 O@G?t_z'@=,g t>3Hy¹NMi+)lYN2 K),Eb (,p ),AISh0BB:B(\ 9"BT6 ~ " Cwƾ k@ +_@Al}/lvmm7nm;o6֪[YV;9[l}c-mm1la7gs|3Vٸmަw7}5li٦t$wotd?t8T^հm՛g&&MrUmog\^zܺs=DH&[f}b}r=qM=kfY5u0۱v*~X֫XEjUm1k۴LBRCZF}z5;KXzH=ƏTBR-VQj*VРViq%[W*g)٫86$pCbbݥIE(~gr\h$eH74p|Ztb9e5 )%Rg.%BO%wIӜh=ڄN"].C?^ח+Nt% ]Aעt# ݌nAۀ;Ю=~n;w{A=>t? = i4NHRI.^B{QȌƳ~tG4 aBIƿ='|AϢ'觠/KҝgSHWeеU-z~FC73>y~'}_(}+̠o\(HADzD:w |&xDB !C O#׷f0f7>~cy)Ë3 <1\^J(p#=(nK ;=]CL?BY}R.C:| & {e?OW3'_/!Hb'W;$x wgA``(=r5*EX 6Me *ᴔ;;(ƹ'RtX `/؂mvӉ]؍7uq3LBIpY70ʛx3|Fq \+ %{ VϼV[cdD64%}c~<Q!8vUCDӆ> }xv7\QzXlIPjA3ѼǐfWW'͗<Qy H =^/_6J`|>^U_7^ >H`W+AH$bcJ¡Ң8!-*t1!RÐ8f86 0g{p4hvRR<i&bV$K6Oΰt:\$ǘ+kⳕrF$qIJRmTsoBo 'WNrT(%2a)$>q~Lr:V;~Wgstm6dՙ+ʵEZ`E l_RaK#DxmG.g% юDefBe ,^W!6UIiN,Ɗ bJU5fI (be%Uz9'6sF9M_]܉W ZBQTn\&7刼s pr[{ $LXO4-$VLIǭ Gf;/>X: >\cJ㵃ϟ6O%gg })D~?K9P yB܅(erxٚI擮FԒQP0cbyTРXWZ #6f8F! "*)X1o=yS%~Ǜ.kbU2u춼I4,o+y;b[u4G_uF/9Cv#pvӽ+wz7Z;Ѳàe:Fk(Dz66Z$CL-8fۅɾRR4 8x߳fֈF x{?.K~pK [S2NccW=b塇iZ3&2G,JhQXC+qK($f5*4[9;,4;Lv̒1%!SkmHU9a.sZ6x& ]E"Pt!T&jYl2^F_N[.0&O_'(95F2&H, %!Q|}zC,tVg}Il`JE ώh+%jh4fA9"z].ʱ(w.F.wC^Xn^5 }& Z[ʫBms *&OZnU ^A*5p|>?G֏[, ),iDdaqq/n+UJu=VGfĆ=1#zQh18BOL(ml>QF%!Wf TO3Xnd2ra!" VLi~Lrpͱ|s\Dturg"H8LΝn?(>;'A13r"1BCro=7becVU׽" bŵ٩,)cCd2^5F+ׯkEш5p c6,3T3E#!fY#Ŭ)f0`m!tkUU3OsJWI8ΝiYk" Z>YEAf؃50OOYGS`[躐 I]{<Ԗw աh41my[ܖʩ gcl5fݛL'e&u;ۜ$ SAR%,Q'0dvl2P8E,-t*HA2wNmU4>sI1orQ(?_UN1培|^'W0 -QdK뇹yS^c'mnj^^h dN$N_c/#֩vXNQ:&37  v1bkTHu?O*&E9sMABITKIrši@X@3An\_<<W̸S7W,TEk3J%+$7ꈝZco 'N)&e+Lߺ b\skByĤ\SY\osFl n ;V\VXFbXqhź,t‚92 qKDI+jhě'PX >F|⨥g&KF.gU"+*' hCpEm!Crn_Zh /O%ڬyMʿ=h:߸.Ʌ[>MXÁ+tR͒t?n~ºI<\˺PӪ:V>BL`c.{*f'VHq~uBkބց2 $:'Rh2%V F*"VKYKcWa\)X^]AIZ%w6x֨ IC_K Ї쐔yN$a$j٢ _?sCR(Iq#]+*tQZWAmdR% vf=bcN HZV quc(9x1t'**Kw LlũN [|ES#\srL"Vn["Ni1Ә")$f,+&9OE/ϙks#8A!6 P RfwG1lK x[%s]_\aPqim\S/()_~aIMԟks2WhdؔgJ} ZyA.2?%<O,\4#;RJ2|Rc戇GC6X=q7ɠFzg2JKˆ;LW6nE\S+%Ws>RvG+j (]y`GS-//]sd>py0#yVC ػPЙǚ,ѻu+E+]@3ƻᅏQTUVPs'Dg581XZD  *{Fs({3r;P!t$GnL8_]) 9YVԏv%-\7~^|S +'Džgw_[|GuĎ>m1ZWýZЫ kCה\4\;y4njxz(O^.B%hRʂ‡WpIlj>|r j ~Wr*aӤL%\r|/Rd*iD]mB{̿ v[$Kefo?QQVpv䰋@jf,3L=@=mq^;k;q4M$"M{L* ;X7 yg Iڇ>\\5J.%>'"'ݠƵXDȏex&xrrW&N0ys&;Z;l#sμûvL.<܎I Myyg/(wL8`)ƀ0"LkP4id;^ࢗm;T8G&Lq/iFwZoCnE?^Q[{^##\6+XEyX<92{;7d;ޒδIkVblgsx£NhdJJ@׉gBab&dПpxS㵽"A~wew 9b\*ә=f ðij:9).Jdr9p3fM֔Ag^7=9 ϼT"S':ħ,a0*a敹0`b߁k)h%OdDs'Bb<eC\&UHXˬTMK8AYzN)RNcW1uEB1_B?-ze7 ^nKBl2_uiWm[\wY LYf;["Ta0IO}xW,$geΕ KwZIđT~xmyR*= %<~+;l5RCO܊rWy#*\pgZ5,=l_ r4"үɎ*0\Q-53̮0.f]:7S$toz.0XfX:$Yp 2krBk&NCY6[kT_6-ɮ}®dW z cf.ㅋ.xv{}\֘^4cy^in ~yMKE/ںDs.>9^x"`r/X1%K!-Lʽ""HиL1j<:1TFoOݳ4aT9΋LYNVZOTT3o-jq"h%Y/ >[ef)@䇪dpD2W@b`Ȋh;gmqn#{ Oe~أb[T`<8==u_V2ؚixi)m3jsz /b9^x'Rҥ㡀qMsi%ޕ?)[y6:! ԌfDSsC*'cH83X}Xj|s(B.`Ɲ!k,fͭ KV/Wħu+s͑)MT56P#0乓Zޢ.gj[G۔\5sIqթS&w;|v;+{#EӖN5WejTf5H66!_pZp A^āפ4l}/(lvˋxjթ 7\TID"J)H0A*H!3}>3o63&yme~vT6_u "hRCH#J?+ N9w毜Y_ʣϲrٟc g>fstIWm;v֢([F#n^ӷm5{IsEjMQuCP%G $K_d8γO3?h%~rGQ^qiVz*I1ZAAqrfei"C .(Bm P0ZTh"B޶ޕ E()nh/!4j(,*bGaGa٧Q-(rOrkK?vVn>|H(+0ȵw'Z1'* #W9G݁=0zr|s9aw%NH)62<)S-b]8qaM8xB438#z,VVg)EJBovW0TH Zʊ *w6XTri{ B09{P?B dVa0ԍ%mJ;9 קW=sƢpr& h8jz61^I&o<@Ӵ3<Ù]iɓP8*F˚flWcŸ|PFNT&cK[};}Lj(8sT)xӢ-ίcɫ "M*Z8dg^tFA / Eɶ:a)Mw{##C$R&2**x_N:=і?mIIgm>I]:H#żaњSoki%1J#hTӪNo9P[kp*[8.}NS^b7DkQ.Hb:ae.Nʐ/-rD O7'͌ IzvRԒ}gBN+2~Q/_';jnάDk2 ,*^r3\;+`0T#KwI͝ZdMJJ OloԆ*s7Z.g_[5kM)PJ#M+l]#(/**ɾ N9P fp׍çܣ*帥 O}{R֒9/>{0sߪh߬%xI,OuzԳ^iZbXF*n,\VRT\R`qc[fMڷq3jTIm[+v7]x;{:6 {W,}1,/6<,FRD7(IipYc! 4 7$-fa^vj@muTxk9P(r8&s(a'kL>-> ,p(tPAWi6*sȋb&GWX_Rʱ"iik|*tCjKق)Z=Y[ofzK Sɦp?`5 M~{eQ.-j"HϪɼxNK%$ mF?3A$%M{Ѝ Z߿?3CpQ gvElDj;H }M-ĭZFRt 1XfX`K0|@,D:qdBLX&1x pUkS(l'/d_ձcxB'T%Wߤ N0G(ZBr.cC vkYyEΩr&¦2}M}IU 8*}:n|b6찀/'bt%.nR- Cu҂iQ.VPTes^/%F}R~'k t6P+e ѪF98Ukg:rO$e ڿ̡@}cϪo'b_>PJ,)gY|i=o 2jIjkS4z/=fbDd#7>g_Ҫi60r"[㞿Փa :b[of^[դy-g젝ЇČUTBx \v{'b5I4-*4JYO7)?(W{KjVyд!(f|U(mmwU-yAr2ɞv\f;~DӫA&Q%T@Ȗ[jw}Qb102A(ё8&ȡUu ¡Q5u[lnh4"Q[,2# Sdbޑq3KhU5mD gbʅ՚1>#swbլ,sɌAJ h)c#b Rf1 /}$'RT_1,Gx9\~l'0Yuf9a$=3jppld5$ћL52k ,xVM3bVʲWH5qYsB2pw:nLiDח֗vĎ1µgeiF7do]X*]~$iUyz+U`ůX˖PDtaʮ78r G9aDWI| ^h]`v=>U\,LS[!5p OFZ*U=NxbQf#%^U*-6vtMOr$,枴yET6*( $Ro`DZTUky9krsmv(gg97{8%Y8L03Z"a.3?jᙶY}pĈo)Ƽ?³s氼)FXt1\%z T)Ǫyc22$?jNU^7>If2Ior5ke3Q!f|ݲQmSWxV x">_VɁ Ø1#iG0ʫeHm7P̼t84. _?Ne^5j=jwx?,jF /Mo1/}O`7ɟˆFC+U,Co1|NUkkeukkEQztLw^Evdՠ~ʽ:ف4GI-ѝ<ɰ {:qDkٜ mB?2yWIEēƆ;|&Ĕ m=~}D8'gE~[gԍZxdјY=4qq[SU((Z(ύV4@`cnIͭ"XE$s/e);@%'M^0Y۾(YKJU᫘rf!R#m Iy"g2g(7YҝVɊw+ ke,^Q#'?P㆚ \/sknϷqF뱙6Ʀ:^̔eqb91bv#"DWw1ȏ|O"Gb2KP-,?_xUg0&C8I '˔2!f!cLmР?4DJ(fO*Y.XE3ySO6'Q4w9+a LzJ :-xuB|F2ufA1 C4T(4ygaSh<#3lvAfQ<<;>]f>1I\^9'܈DuiF#q޷lwEhU/֪KV-ɖ\$[ee<8B #Plc؄N1 #$䅄Jwfʅy}}gΝ;sgΜ33޹]xx5Kރѭ?#M WO~kſ5-.P=}wuUT;г`hks| 9@ Øc>n.S.$&ғb46 x]iO=eqR+%jDjhln) `\c5}͖=U};}vNo+Th6f-j Z"ꅶ|wu0BitԺ9سjlMDDcGJΫ|"LT T~-R9*:l 絋@u*؅/pG3d'}CNw\xm?:xwn>zte XP%Ԍًz馺ȸ-6G⶝elRo2Z}&% WG-<>mS~pV$_m4љz~6h"'4#rġ$u{%*~}bļaԧ岇F2f2IF_qOKKѫ~.ѯ=<:OpmY>_-rVv 7zӽjopʻKmC;[>9Ps6-ֹ@Iwd˚3H56tt62asGrjJhi{hqlOq7wcOz]SO_r'Gon-m=bЄ;@W90=s6Er1y;lsJS2A%?OX>\PilĆRO!"kP눍Un#ιbO/qtV&쳠]@vx-8bK ~4P ?`dK߯oK=t\IumdJ PR$(A*i~^II ?776[}M+W10KbnvXד:R#%SwX([ {j`sm\$6GZ\:v( .l\bS! [gԔYEe%]?̞l>vo>}ñþhw+)MGo:FJזkJ֕(?l{˿c???=nWX_=' 3{Q=8jc_|:7Y)}c»d3XT|J(FiD?̥Ъa O5{jbtO(~z^5Qzuix|r=M14unPĽOL P5zEl*hOvx TIERZխYڍ!s\U7tg ynW$׫b8f̛*ˡo)<}Y LH7~by OwU,ԟJUR#ǧbiDo̰HF2//F3 N57Յ`iBPQRdT٧U'BE Ue=Cޓe{zZcɵz^)vJZz4}N5;Lۮw*]n7vhA ֋z5l*Vn%_Z_Ө|uFm.Go7I[lW FMVcI=\3?y6hl$Ԩ&Cǭ$`Oo AFmp}@F #fG9 P a%rɢ EZ_ܽqO]0[4 ^[ͩWʟ \U?LG硟||nW/h^1^ƣ!4C S-VH^~G-Uڊ#7(7*[6ي%rQ/CG[E:{ ߄A)Ջ Z>p1(`޻Sb kn SD ;H;(eF3L?ΰ#Kumr[Ic \J$ `Cq)_MaH^E% d0rGe;Ơ:ՔToU R0LFm-Le<_ߕ0mCØY(e¨?Ъ 61ܸfZ2R"0e=U%6# l|Ƣ:(伩Ļp1mQF+U6[#T5Zu3#cBF, Ev CuS zci@CԎZTT]Vc/oc41_7n/5tW[""[jSޖ%J7[UުU5zt["ԣ^}mΆ]MVCIm{q#$C;PBvͣk&o&dv,R1qx6m]uUSj-Z˱D7o3ߞ-5^!uhV /u=p~/oPVg*idXf\Pvf7*x*2Xh?kݡZ͡؂X-@&4ZԜQ:s"nrUx}wo{r͏}D 9vg4X/F`uuKs;|Tkً]gBkJƯѣ57[70.BV2 dZY~}7^;E,;mX6uQ^ca1X*hUڝ݅*F4bֺYW3KVo%*A4:>h5Z#x꧊rj4 EdOO h9֍wE?Hބ՛;AH_d-0'!IBXٗIមBٓcf뗶/gVlU T!fHt$.~r 25T~O0u^^Gp;h0F7[^MLydTUVMzu榀Bn `_wO#BS$/@+Iq)-rz# ԟrWYHJ's,ƥOi~-x6+x֘vsOxiS=dz a>oU~榰ƙ| wŐS5u|rf2N5)Nڔ1aSw\r+wqSZfR)dEG `Lt/ʠ@w&T pfYgGYLBf &sс}Q"Sk9o%_R[o$nTj>ԍ8xikҡJ6Jgͳ;9VKAC^55Th 0T`:2>9س*JZ;sH5waR 6R" R '>3 ރz|F',3e0aNP1X U~Hpx҆e_Tbt{/QiK2'nE&.VJ.zTF^tli*dG]]2u&1uzJRț r)p%8EĥxP?? )b.L).R\J'# Eå@^E)kR9Z|}?#G{cb.^]˲'9 ̣7}Wo;~Z[xJ-qz.~>Q^1~;+7t W]#;ڞ7SamY4ݘϢ__h!orQe|J Nݣ+kBjU;Z3xqG4_GL10| ߢ57ܥ~h]V+YuF\'c~rkG5MՂ/.QF~*}|9&!_;M/ G[oL3"`hn;VF>/#?/-OG/m6^=%9ZܟSm)fFR6+śrDrLhuV@ˬy63L #9d5NC%e@S vhɶ yP y8&HQ$K*#c2Q|E6y-',ZzΪ@g.[|u.GĭOlq5Զt{a0"7gUVIfE]e!WP.Z[F \9Cd1*٤(H>uڬ*Ts};apz}_E~=N~=NEO_M>?zS"_VRRj/w$'VEnJe6G5ۼLUȯ=x𪆆8PG!AnZ/ڠ9v^;Je=Ȟ$K#ې ^;ꡤ:yi<6pFHU֢;"|R6k,3Q|>]wGB}g{{g6D|gg6wܺsd\Qm>K9heDK[]1 KW vRNvs-RNdFA).FqnNG87A#R=<1;ZN3.r!Oj5M)( xEL^ȦQȷs;rȋM/ %4&Zq9Z&zM xBe~Ug*l)P*} Oj,z|ʚqk>F)8VJY uy1<оa yWcNok[IƊYIIQE(]"4]T*uS*^u8IpAtX%mdvp@@+xҕ. -ok)Ո52Z\h/"MiᚁZu**j*LkQj: _ajMr^ǚT+ VW$zڊr"~JCMRž/1o}00sKXr ԤB8D%FϬ 5R<&Q%E{(J_&lc 1OӎDSWk}2'JN*[smQ% Y ϧ;|}\+Zmmw3VKNP zDo(Fԍf^.c|'tt`뮅 vR\Jjy \j)F.EE]AMubR !O0̥8 e_^ؼc&f^!$}Շ( ʈZXC[48@<Z;>;Qrg̶Q﨨CAyHxюƎ&-&]]]|yю)˦AǠ{aUJJVw]>5aU8j廄a(xq5bOU=^DWh>ȗz%"?~ocS~> -zQ,YVwF T7g-(*]"/YX\\HB'[} 蛊+&ț!v){E%g[!vc$RL1\"B=])C}:M%k{'cgIzz:QAT 0?S#\`":bzH%P&kG "ZnQdwқ@?ƛ0Cʝ"gtuah[ɥRÓJwҿS#żͷL׸b1G4M% W5:@ĎQ)kY/|q~F?GXab;&׉ńN~e:`Tj f} } [aZmuZoHjR7e5yn1m?l}ߧ;p}d AW1,JЌ.ЌxQ4`6E~MԶF5Q5۠#e?_go@'3Q]5LmYX.WiL.nWVmh+ v[BG "gŅV'V 82D3VMg҅\@b}] HdjPiU Yx=ʞR82R`)KO3Z7w1!69@@s @~;~?NZa }_xRcxwQDߠ o]#S)JSȌ??4o/GߏSG7y'<\yxg$>%t(5zh~$ 5lڻB=L*uA=(?qZnȱ2Jv2Z >iK dߤza`8FfO6,rB[%i6ݢO>hՀ ֋*Ewz$y=$oB^# utw(;̴VT~0̽z dYfRFkݶ Ye,Z2Ro-RAE[=[`Xcp#fybBaɤRk2ZC [#:Dݛ/&7ם2߉D WQ?ZF"dtL\tN׻imF>ɌQ։dY4N B"8}'_D>sTTDo?q0P#P ;! v8\ 01,q åt~i~pg&* v6P\ͭ謨b[,kvpǨ|7+|8p5@ C?sTl_/Hs=CCWD,h "E}VC,7ଯ)f^K쟰"Das|&&%!qA9z&p9ɇz\h)~/LT:1` F1=4 P(i!%6-2֤DMxec.*B"_r+o+rs+!RQKTd^BV$ 9a||M޺,GU䖧 f D8 E ;_GM9tt7J{uy O"VDrԭXҾF $-9e?NaDCh%bWeJZZ|ɝ_ZPۇbX)*vBrFK~u%TRk)-EYĉwK?c<-Gp_Xos\ξ)#5xMnN(9 D#:'oB\K~Yg(žG2YmF;iA?gm2-{m(S\ʵVfZ!dNI3erT% crBQP'N1%`pZ-*F唒*CA(ڝY,4|H{gtGg.Э}{+[wVX2oflv 1 !w/H@=X);}l?vGwU,{eKkOY|amRx[APj}{o&Fp|H|VI+|B˶>~=ݺh}te?w.د W}ՉfcQȆMpTC[VK6D̅Ue ԋ"^Ӿ0BQθ%gZnM0LTR \ ̧8әqZ}ȬRi7g" 9hGgp"} )Z4h ԺlFKsp8&'o%Ŗvc{ǜu%[c6J% :R@/-fދG`yЊH ' =h3(˚d+AwNĩoUO)NRFwR"}{JI~.9JT&ш P)KJ`nƣjyRNE:B2H6XC$t *ܵ/v?D^׫u[VTu*6h}6t?qwz'm Z k)p2r)ai\}Qmt&XiE܄Nz^#XtwD$l>N8>@eE("ot ?+W)ɷƲM17ir>1ZriQj0kB6q`^WH2*3I>8ɡp XU F6֘Zu՗^Ҿ kuTawQD^RYZ[[UCf/OԎr&Md]$w36PiDʉcTN,Y00^ V( }0+jh0k[VS5Dc!J}ŦȦk ! -x#,G2v !y~0C/}C!T\^z+ܹIM`MI#3x'B7TuΒɺNJM9C9ɸR2w,ҢRkk~̪k-"sͩrC?l) 4BVibe05 늷g(+H,{V~8A xï ڦ Ƈ[uf5:^ 9"D$ J~u^ *LW[:'7kJ/t6R^VPTJRMҵn]F5O= @|*rvliE*EvNC۝2C3gK-˗JO f*V"/=ET2ϓl)ŴZ6E辴 sv' v5;1_$"7w6E(SB 5 y}%l2qYuQHc m~n̅h=Z#;br_:ø"vǰε:/\uϮ=ή룥h,4E˫]vm.֕uE׹|ݤ EvSYJ2??d_ϷrBqzZ!kVnh)F!z$f^iw  SD !:,xNtU2Я)UwRTJAk -蝴 T&:5Kj;;/*_f~2uyVx Oe:aBA`Υ D.GA pR ! Fs|EYa?9?(oC9 zK K# /-ɬ.t ]e`hcL;b`pyREqXX ec}s^fYy⺓ n}?>!7;Bg߁_|9 iCD!|9ʂ#ͅ& ᝢޢ-EE?gџ;䊒?㰣tGTTʎCpUYUj30YjzG;5kΠ7n]p%OvR_ &\+~hPpw} 6FUi,o5BX'TTtWh%VJ2Aq[- i~}m!MPA&(e{SݗaO 8d~Lہ1WH**O8CjZIRo} 3@Uj@/=m9@gMX׫@ہM]6AA Cg HlMe%( p }A 6b}ZmJh;h(&QQJu& ^=zԍ( ڗ6zybjԳ8q8Gb?T-P?h;sz!&怫UWuz091u K= ?Ut6u1TȏheS@SG6~恪R@-iK}az(u@&c@OC оlz=kJ j+ ܄sN@@U'"R!'ιs^9J(0=E뀫@S@ JkcP³@Uz#鑧RjIP  )jΦ~MTԟJG8V![a: ^d]MS#@5_cZIx^Mfz \j/PTN?BC'&$t?v7= wг _AI &Om߁z~GqB(qѷvJ{; ׾ %@'@)P>p\g]IO',yr#~x|O P/Oň(] ~ǁTR?@E8O Qn)4 |Do>xOqq<{!..\\H+_#K2uI \\Fd\\JX yH \$F.N<78MM7pq!3}!~@>..$Lpq74pq12%\KJ ..# .... r MK5gY9qVl3gY9qVl3gY9qVl3+L:.[C(X' FYb|416! 6K`:) Ma8SGL@`nHlBGqC]@G!h0ĉݐc 2z^Ct@{\ĶcN? yµ: "vA\0ywAe;hRo.Er`z83(uKa9Fi)kY#oZYF5ڀ'$n ˵ _9$#Dt^6M/[ rЀ$0D+XYyr#|<|UeBbaGi[9bpyL'-xuKܫX{;첑aY2dXƳ<,0%yL[f ýd"C_/ץ.lϢ4uzI'?b;qgq0)sI.; H{ZQ *Yqv4 /4>"elsqMY~l[ s/s.ҢsXKpl/:6Q5 Z&|v G}-qJ1`E8jFHCTCo=ql@x#qQ;еMD? J9qٝM\>tEcoV bmOd9t&p9Wm4gp rg6\ߌ]>9N밌Pɨ0f.γX00͇䳁;a UA+fI~ 8GYt5Ӂ[YG|mIɠ%#nLY^J[.>|~) Xrk :ڲ8>M8WFܓѐf,idX [jl^ZͼOaKI_ϵrAR2A|djX7E ˙<0=;3=;4 3uLwbnfL|wL;0LLoO0#3{g *9e|,tM̌3CS##;!uӺ0:O1M2yRfnzav$ vwƙ,3?g:zH|j.^L|r8>:e&Tf4>72Ap\ah"1<@u 1P 345&Ƙ^fwb~[3Pobj;0Yp(`v*>;f晱l|<12d&@#C3GL.L'fȩ,䜋昙ih ->112əy&1#Ygp `ǘv\0[||<\3L394Y&eF!h|hYA@!e.O] 0օgd|hφ3 UA/ EJ_&١N7kF;gP4`* w,AK2-3s#sa`~١Càk(+XCff&<\8R,#EH#м 3%fuf6gG K~)㳓y(nx/FVIl:2jtatad>Tr\Dפ+6=l7TX_~z %7vPq$wh .˪M@-/9Egtzr 8|(0?`4`<fG }e<1G6J ,MXDd驌H7B. L|41^ VήAbsaCx!(ǓH; 0nXM%2c)E3;*PlhÇtd- 3`PNGI !~fkj~ L.*qZOaFEdEY q>==eFeͲ j! "{Cq,47;,=:J823 ֪^UCl$=>=>Q7Xf⸀i-1(hwJVg9ݩyeX1)ܩqzPYT<(S(}O61=k{꺛fm mMAu^rtund43u] ԿYʹuhkm]-L=\׵|{D(w *jkAu6u7a]}[G[ ۅlB똵uݽm ;꺙׮iخnn1MiUխ1 knnkieZt46Ab}pVWV::Lc]g]Kj ҍq6$5B0tvaPvf.ki 2um=H kx$Nb .jbKAfdA{xil뀲zٙ[+[/+g`[o-[+ε+ &HKgVʭ[*ɾ@)1B$CF%!LRܓ 9d$l4\Ӫ_Dy_jͯB~woeABوbᇆ !SY VWBs~`C=@l"~B gr9E̓2b/"a"H%LBȝ ?FN#;Cq1ast OMϒ)zӗSReo藩Bjg OJרY-:m&mо^s/` 'f5|0? O_}00MS4=KI 00U08`u|;`0޷3?ss05y0{F|;`Fow0?̯P8 H`Հ0Y|``"`0?gK-1&OQJ4St̵0wn<~/ | 0? _o10s.`.̀y=z0+gmnC; 0s)`ñy7``0+`> ̿/JF^G9CT0 v<+.|`~ 0`~0AFS_i5oAt9آ嘥_l\71 000Og[D;!`̝y0Oi`~TQBBMT9@T39Mq00 s%o˴~pBwЯ[` 0C? YY! nQ`~h$i`#7杀߁{)G9H* T `^4`0_  -4n0,rz-}}j|#`:`00? _D!w̴70%LޱD8~iwm f~pF$$DO?ܙz=!?O{2|$U"q"xϝxU$&D[[S?clp寞9sUTj3~W>?ދi5u8#!D~h Q.`*H'WM{Μكk Vgh:6輬TlԒ [tr "FF*QB6[g6sj̝YYb_uֽ"- x7lن{Jט6fѦFr0C-j9b ӭ-}s2]uZQcOu̜pLK^LDst3EMZA ,Hibpai? !M6p9Q'-釫~6jja7OkyaA\Xip\luf򱝊B /6faldnՔ/2-TPCu>^TٳjEUV1-fyYo"~f3F|.\e.FІӟX#G ǀ@.^lkdѐ9C ?g_ %F(/ Qe|7L6$>9>Ζ[8Og:3սY!4\m>DEF,)'f\Saf' qyjp?E/tclP?hXLLLF-)I!1mI v܂p7'%ɗ[G|1P237Xغ #gg#dd*XZ:'Fy(@Rh !kF~fa/¦R/pffuEs 6ڳPT[/< LNC^ GG~{-Ц£ܱEk6멧Y dY"/5/ؐc$7O!r)tLKb,&9)*uLxyC~0y^|mmOgg,#-LSSE®v0@n:4<NάbV7bsEY,Aj 3G$8螖¬㷘ޛwؗ yw :":TO ǣASK)S48Z17M}a7a5PUM;KW8秅jRu:*S@}WlpGrLJJg|OJ /Uz6;u ,˲RTQ}9hSFNe0< >Sl ׭]g.u*kMYT› 9_9!gC1gysKȩWfVL^R^ұciec EPg eegތ2e[Gdq.MX>l{(pFKC."H]=Fc ( =F3|nj WZvcoR}VVq/dC1]4a~4~ Rl}٩iwp +sD6l{-Ӝ%?7bBa}ndffca/pDžvG'/+u *1%]X@}zڮGrڰ/b("⇜.g "WH腄(]&ou0<]N 8mk\^CI}q_IN^x]\Z_U׻O/Jbn.jR6Ӏ>/5Flu=B'ܓw=Y;15S+v&VjW6ߪ,4m~kYкEò0Vgς::Ge ,Sa-Q㰚 S0ģm}p1߇S4Tk94xV>*2 zno<O,!O\0" F KZ<0ó ށ3,@WEz  a< /18 p@ipcPuq:K6x1:@C`0yx[.| q8,^|˃p%\COD 5X `'f v@qP 0 Bx{Ͱ3 Y J@IRdЃc#qx^ރ]|fc&`=Ӟ*jfKRH>7iٲ14mucйMfeE+@u 7@;Ca,en8?V ePíQsTp54xG6<ca r߇=%#s;p0>Kpt`4<O|X oz {gYԥF.!cOw9_y[TBV!L!ץSn- v L؃.]zg'B8p>JM; h'lCؙ0pX̞w'$Gp%Z[wӷ0 ㄧ *ѽgNCGDXb~U S % E8pX>=c 'N&F8g\Nb8ƄC )Ei儫 gDp^ <ٷKf}a0,aªݭk`#t6 3$ 3 !F8pD)}Q&Gp \}uFp_&F8z B$T-+3g>C8UR(](e2GYW+\/k Q|2×|;?ORi|uIݭVOkUhMX"_o/wLz=g#S] FgL1ߌ7+fcb5g͵^oYVk ::>ImwK\;>V+^{A%_bu9m~xLv1=bČ8fm̶PPШP^ uݵ67͌ǖ Uص{cYqIqU:eeǍ ne渽qGKZ%JV)V2d%K/9䂒+Kn.䑒V|R|/_9~o+!)JBZBzB̄ $L؜7HB~X%1-1=Cbfbv W&nNܛx$1U*TRiKu(Y*RK-(R{K)d%%%UIJKJOꐔ4>iz҂I&I/mN*]tZJg.= J,GJ痱$R&Lz8m%9Qzb^%J\LWQPLn:+_E_nG+։wFE铢ߡc۫C+c̒: jB ra TrmQNuWX- k ږK]o²>HY72DZYG~(}{HzQHI(U&Josk2'J_@zT4Y$l* )ui79e;{5Ǘ8LhAܯZ9NȃCt9,)'喰l5<3òE) Rt762KʍaٲS̕DX4Wʓa٪òukòMYryXr4EzYQ(}k~o?)r{QHg{5ʉ1w%=R.4_[ufF1VC.lpnYOa{tX"eW)We_}e/,Ct)e-dMRRq~7, HK( r*()RlP-)e) 6@XIRZq'9CCׇa.;IʂHېnBΣ@-HmpQ'W',I3,O snFʺz@b k&FSQH"t}8=gÃ=vlCiѲ~F#wL#`v6&[,؆RWNңJ顲QzHRza^6J &KXJoE9Qixr䞏eG#KkRy:_9_ o}ΤvRvxYʡR6?%b@"P@h ==7zNL3W\Y~s;w|Y ڇ勲Ogx E,oY ,2xEz+\$}@b-Ti˒D).=ҚmrȲ^8Jmr,,7doQ !$oMiJ *#Z%[>W\*ُٯoTW<Ŏ[ 2G9rʹ\+vRn ˷ǁF1Wrw冑徱~Ez;6m|SWJo#IaLIfDET](ޓEI}>P#ϲe>(JEvȈyaZ;]Tk碈6_;e~Knysrb_x^{eO̰'O'ʓaE'=eOpPKpHWj}rpHɠӺO/~|yXO*1J LU8xJ)i%%RIӕd%P(a2X 9Pe(QSZe2 +c12Y ʓʓA| yېkZPp7MgguѺZ7ݩji=J֟h*m6jƲFkc2V`9=mL``/x崭~//%KKc_j_cxȮdW}}9++x]ͮvu^ҮevT;'u:f$]x݀_d7vc1hwvW~=RݛW}eݼ=+ك}}nU=˫q<~~W5I$^ӞlO)~=՞Si4~=ݞSL^۞eul~=Ǟ\~=ϞZ{׳ y}UU~~_gnez ~~`W{ obۼwwxs]]~~ho[y+##wٻ{okjoc1~}>'6#W!:a_;*ZWQrJEDuպk=~m].g+vl״kW׊̾bw3>v=d#Q!{=~~~~~~ڞa?c?k?g?o`hdl/Kr{Jmѹۛ{nwڻ}[;{Ĭ'%?Wp5v;.2|No}y_ mkcF&:Vϯ%r2)k=ug֋cCH'il6l-Ķl?;Ďgȶ,c֏U*b_S+-vJCR(#xe$)Je2 e2WY,UV+eO9UN*m2O=-$d`tކw]y&(>O/+Zo$>bM|8?*DZYQMVj{5CfDzP\Fr@`8Ɂ$'98ʁnI &9(0C(aq$&900#$E9M$9 ɁI$'98 n800qP`)Ҿ'}S}OIIvMv͐v=#%zV5[yi\i ҮyҮ]/IH^v-v"회V$]ɮW]]IH^v-v-oMiJi*iji[Ү5ҮҮuҮҮ]]]H6Iޕv-"R]d{Ү-Ү][]H>vmv}$!XڵKڵ[ڵG^i'Ҿ}ҾO}yҾϤ]KHvv})Lvm#vR'vv}-:"F똴;iqiҮ]']?JNI~v,ʗvvz;O|w]g}ҾCdQuZۦ<Ӎ;w| !>?#e ~g[~!(Tb[o}^7ރć`7#|$?}ݛ[ H>ÞuLxy< K%PB= :<~5 +V`(L%zڹ|.cX9^{aNs{ٝT&LΕ LeQ][ OOa2<'魺i4L8' <1sX /%X/, 5KaX뛰V-ȁ58{]ط!6<fx>m!l`| ;a=>})g>/|k8߈/1p~#4 p ,6:ܤRZ+m-OܪWnS:(cqFJ ;1RR2cneS٥V({O0TS>S++/0R9|VVpCF9M[r\^9ȏ)'򳒯Q 0pq0.Y_adVF##w]{'|ԫ4u]Hݡ~Tw=^u~W?W_/CWakzTV=~WWO?'SOig5_=g z#Do7z F~Joo[mzv~w;]p˝{wzoW@}>Xߣ%[OG#Qh}>VH?'Id }>UJ?Og3gYl9}@Y_/_K2}BS_Wo9}N_;&]}E_ߪo?Է;.}G߫? ~HJ?ѿя)' ~3y}>OQO?C{ƽFq1n07Ffy9n07Gs9|o>bN05'̧ s9|֜m>g17/KBs|\bnek̵:skn253[mv#s|>87Bpaѵ3 tYޔ7xs>mww>Y< Hu?|'I9ʧ/h9H͗4+/QkuЕ&E34%kt-3AvHOkt]{co)%wSJ ԙ+ͥ;UqLOz 47Zi4Qn47ǀS;#tŽUO!0 #X8E` (; w 171swO3?E|Fcžv.I!>m nMbJCL<1 $ y9zoS:&aHw{"Fq[[®-岆fѷ˷)wz~5[__V++5*jl:O񧰍]]__oo6MM?ooöo>gw==N.х6=^1AkfG0rx`3NS1oStvszگ >i.+귳YZ|PZ$'Ue֌Mq/N I[%U}cl'-Q+%i2EZYbvV^T]U*i˵*ZUUjh5هl;`l}gl?`_Kv}*?O433 *S;0r$Aulҏ6[{":A_`T9AeXEX=Y&@6bl",~gmLW.:х^WiEW&+D"*F( ܂Q]X!c1xĘ`5ό>wg=ia?*s3oIEZ-bWmYKY{ݿ+"cxX/CWo8<GdEgUG Pw4HIG)LW%Z]G4,xVJ+бDn?J{?ܮO701V ,qq+8s%pv2 c4$e)7oA Tw)#D#`'kSxG`&KXƺ‘#wG3Dl8!v`lp1x2IJ0(1E:]_%ǓwV38JOla6(jW]Xԯ(kp^Le,Xc1c#LG]߻+XT('E~0E)?xsCLq {N+Pv~8LWBx fa#ۂdGq*(qJxCR(FJ:vJGKC2RLJJ (vitTf2IV+jeI٪P*CQr?ˀU&K+Hg$ogIvduH&Hvbא®%ٕ#ٍ'ٝ5$ٓ5"ɚn VRHuOr Rs9 U~j g ɳ8pI^["\X%rĊ b{V"j?:b؉Dj!vaW"ve)U ~Qx2]x1݀ؗ5FbMfMti9tiE\+?PR SE\W}g:bߏxmùZ6-ּDvdG2;B"]ث$;$HvcKHvSFHE$W˒\9KHsw)kL*ggQ)?KUVQ(IO@؃zXFY$ޖ؍,[Oig#8cgM09|ܜl>aN14OKer s\e6w{}f<`4#Qy- ˴,+hV ruUժf%[խVMub]eZ:VuU׺֪gշXY mڶ]۳O?٧Rv]K]k̢L(pTOܬMWMJеfW[oo~q W*P|e3wn1@s[8#-iV4ηq 78ߖ[hogޙFhTaڹ_H[ꩰ *M P9KQ9'˯ S6ޢx]b(SG_o#|[JSYnp8`|}ܕK8qWdjvQ W݇mLci%;|O=-l7a$Кm[⏘:b/n#T)g.͂@|O9sրdZ%5,[%N%s\::5uNCzsi4u9-mt!H)dWt,h}⾉eN JuET*ҕ:1t/!h-dp:;ݰdtztzuwC gs8νν 8~g$$8QP팅$g \Ls@%g.g-Zh@*;7;7Cs;\܁U*8d"6r-oɵn.ʽ pY mjw,;Lzy̹3FiB>|Ir(*K㫇)ƾ6([:끲/eo/Ph||QNMMC977l!(#+'|~7?OwTeQVWBYßꯋ2D#ʖ(3Qvgrʑ ('rE(WsPoBѿſ6N>{PGyҟt>5`v $ *U5'a-0XO//šblERb.%1@e\FeT$"1DL%b*ST&rb.'rbS*\A\ALUbSjT#1$LLubS 15ILMbjSZ\I̕\IL 1)Ĥs1Ws1ĤJLmbjS:!1Ws51WFL1i\C5\CL]bSkkGL=bS'1 i@u\Gu4$!1 뉹F4"17s17Ә4& 1MiBLSbӔf4#1ĤNL bZӂVĴ"1iMLkbn&fbn&=1iOmFmt 1ۉ;; & b:әt! 1]JLWbӍnt#Nb$NbzӃ$'1=EL/bzIL&1&71KL_bEL1YMMLbӟ $f 1D b3 !f1C{{{a #f1ÉNpb'~b'f$1#I(bF3Č&f41cCb3Č'f<1yGy L f1(13L$1b#1b&3I1OKļDK, f1 yy,$f!1 1E,"Ub^%UbļFkļFb׉y׉YJRbe,#f91ˉYNļAĬ f1+y7yĬ$f%1YE*bVļE[ļEL195_, AHZ itcHAO̾ČY\9WWYhy 7}?uu9bsyfx;dB?z7{$q0LY0X Ka8']N7pz(Ep&:܃ap1tĝ? A(xΠ!~E|{.֟ٴ%|pዄ _ iUń.!|p)\F \A s J':PEl P^,9[H(̉{bVjqN7GԴx,{_U;dSPe6yL)1*81~ȹ ctt9qi&O\VY,ܦڨ))~*A= ߿-k%#z~a-@NV?OYhDK3+'YBVT]q4ؙg?>9b砿A;v./X7\zb4__٣̑O\ArtV:&X-g _&6b!bCX:gq6 Კr*揽mWZF٪:M͔mjW\q/y//z7_oů8^1ň;w37][7,aI̐ػczoxbHsOؘq`oF"(Ỹ1P)%Qf*})zw1 ńBRUUJ|Y[3zM銚] qUwpG8HNǃFwS,f}ӹ!B%Cw &nNގֻ" JoO~w4bRzRRP tPgڷ# Jى 1.["sq7_IśL~oz ^khցP9ɠܟOr wE]|s 搐,a?oGp$s{LFL'NEǖ KUiQ/Z%5nj,%;AeLYx-( )hp4-br[Ʉ% K EJLV-K8 (YH9\~T1oĬy3fe̪1oĬY.f}11b6Ƽ)݄ LHI*!5vB" k ȓG?f@A1c`94^L`1hC1bsyB+&TKHN U*w15^Vy3ցçݡ~A!7B+BoVVV քֆօև66 m rWu5 ktm1nu]-^Vp/s+n [Mv5F ncm榻nK&muC{l>w;tpOSݧi utW[n]~湟AK{-pz1Ujx5Zޕ^wxW{i5^]ZWkzywy^oFzOyӼ o7{{[- fcK:1X7֋-{elJڄu 6'lM؞ ~*jc/ZAJFܐjrk;?j3kٳgϞ={6%I$I8- 3n;ǭq#6I%4I$IJ$I$B؃Jgwk.r>JArpf!Jp J1t(('fo f9N')T11$N3'ę,1,FĨg{OŽg>sqx@R<(~%߈G\U&IRwS%K&HJfK/HKeriR@&mvHJ;]ntR:%(d.{ +g>9Kr@Αry|\Q$WʹrMΓapy#R)bQM+e$zxxxx(4i@olWWZ~A%> ձžika ߃mq ;owu%.&t刍")K\P=ELD?Qb%:b׋7xXd7IXGdw IXlBʋMfZՑ+'Ӥqe#x/+IW\&DY~/ؚICOِO;t(_ۗ8׭,T@yGSI|f|+#4'JV10*W%cdRWXi2C%Yѩb?e+ *_ 1?)),Ma9گҒ}i"~}9iC{='|~rQgWj+wGJ;GC}Zc򱉩SxWQd؈K[m7H=Ag6v:v{1.W̫T{.)W/-.q{ IW W$"̡ ';\Z4I4Z~.)RIRu)+,!R]qH$Q !ٜhTlEҨنxɐ#o ^m}6r-HE#&#?́ zCA^g9ƜԄ{RZqkVtTaL1LoeZ+Y*U_'q 1͏}T_`C٥V>Q*)KrD9|||WN*g(-UeԲj SUYUTUTQjWՀT+ת*j5zzZOTn0vO=Ƨ^3c%Xw/W"[/2f-ټMOdT3ba<㕓*vzsH2cbu'.5hd*l̕K/- 5r=ڿ%:V.կO?iS\pN]Z*NN%] ӹt=Cr>8^^eU zu~^or۹;܍]z;&ݕ5r$fD{sNb'XIYzROa'Nz,RO2YIK=f'9,y\ ˷?ՙ?2Nʕz!k,GuG%3IgH㳟l3>9YE^2y9Y ?DzA^JX%yԞj/Z>QuZRGcGԱ8u:QNQSC4u:S}gr<›7u:ɔKl24zקc.͇Ъ>ףz)j̎]8n]U{ j_sI-:O)y:ڦME5 {?@%F]|JCY+FnK#k# S-,I3m,H=wVɣxg峤'm3˒^4!hU\JsPkGXț,um"wi ̞ā! oP6v)qKmܴeIYRMI,mޗNi#{e74m s"((턽Ek}^$eߜE2F TT$N&5!L5#/S}_fVᡗMFk0Ʈ YmWPj'+\9NʲzHH#a[e 4P^&Vlu2kAvb{izkhplaU=ڦ=Aۭ34Z6+ZRU]RoVkvj'S?(4uY+uimÔk%advu7+T3dolDHBHm[XTzKȭ$﷦r?u֒T>>'/`2 Igdal08+$?#e9M#%e5 *o)ʗԫB(=LR7J2 R eXPɧD y/_#W6)7IPIIe}gfhhG0 }q)l89F*(&.ejĐnjT[˃7_R+:baĘ+O+OFREˊJxڧR+ڔ ʛVhflZY-YFrCsj&k HChW0nQ먷&j3nz4~o/k<Mc/GeibFrOZPxOP2SL"cKvw ܻƔoGRV@Ymd'eZ6Uk~CO=#'Nh'J$B.5jFmy1^+Z-V{azV4ȅUeji(L۪V))^ݔ`)AHp=$ P C AoHP ][0%ߪȝW^L* DH,(4gO(I4& -9\OK5%>yv2ܧ%#y1byShra dD Do\Ia$HYq8L?k˕{K?^^~ ʓZ1[aN;M=c4|2G'ufcc(ߜxJ)|9ބǿ)'nzC~_Rs޻i?չrf%sߟj_2NL#{6Ӹ yl']xyڬ/*cvEK. v㻯Fyǿ줷wil5hHe.?A2PVSUuXd&1sC\Pwm[f/֚0{ޭHE3I?/,N~(ORK3}hQ9+%?݃s^n,xm RWg\)\"KPYT3 -4MH+}[ yyo]%A}ߺKJu\nLYOǛ;葟 $\WIp5ý ^3\g#W?%Wne4qìͼHg.TB ^-uhTz"H+$IqX\.OgaL 3]h~4@zgAz?@ .jEʓIJ*W|#/-VK;^tZ.q>8 kϮX\bD|A.Goָ\S"ȕ[δ+U>r3痙 M{^}~y%L:%~hW:[^Z/ sE s%̥Wk ^-nsioٳ AM#>Mk<:`<(c<`e!b y=ƐW[g4zȦcTo=?Cxi" o"ۓodS4\tTMU\) +Q'4:no%\v!S}UV,o\FGK4oWIkIF-q>c[BG'D| q&qkCQoQnfX*#e'l0_|O؈toz2^Z0knRSOI{1a]ۢWd6D4ʓ.٥xﰚ+&irvm0w 'hObXDeasocœ>yszoS9=PBNb9m3P!OO6sM-6vu8$=K.J&Ѿe4 Q)J9׼ @ItR:IRYr&A"W!r;s\"90"aGH~(suUټ31`ĭ WSm>>"#VmKHCOq'>ߚp Qaek5OW9C/J r; WZiLqQj'uJ#G'MVtXeqev.\T+&WqA~T>.`lc%Kbo.*U\]6V\sAnb[d/߄o;|>_D>g ~5wa;$YlqY%גocdnekhneY,,K-+-k,--[-;--,-,',5Vk X+ZYkZXYZZ[Y[;[Y #cS3\b2*zFn>!w6-氩6g *۪jٚ:rm=lQ񶩶mmmmmmmmmm픝؎Th^^^^ўK1^``j/O`emo_h_b_a_m_g˾پþ~~~~LR\U[&Pbej_IVe:RG~eܝEs}~2cɹOa 9 })@˼"˷mHGsG#"H{LLSByDM%+ָ}|xD>2C'gFS=$;q g]=3q7O | |݁Lf E3H R4 E3Sf)E3Sfh)Rh̭ ݆3Hҳ\< zrF[z p(0d{cvMF』)һӝfLkOh#=bo; |}Q8 {3f;S >d2t@ﻗ}p߽<࿀(vka33/⨧y4 ?M}|>myG=ͣ0Ow֗GGFtop$ƀS/ѐiX~p?b1_8X|888ba!-#MeeI(puWe,fYwEYF7 awo/0~G8f v73nD.h\ k̳̳pKCe_jB RjKdYw~r98p2W5rmN\7VN=vn܈ؚ=k.mW31L_ncLw2X6GwӰߍgb晞 ktǘTE]hZy;thQ7Z⨥yK-KoAW?Nyh 7ڋ>@V8HZIv2)M)!E>ȇ"RRc׋ݸ"RC|SdZ&}fwLwv75x|=t<O=ttz—dNOSꅫL=7={oh"Woh"zz MF〬)0{)0{)05zc~@{o/p00 d1ccnj{3v`h!`v_M_AL!owAMǙq<775xzmyG=ͣ0̧BK-e Q#7 8Xc)@ 4&!Aab!A#󀓁S,A4ԃ7 8jPe=,롦4 aC a3f= {OA7gqv AETTzPQTTzPzPzPaz//7:oIFkp2%-%dYM֑U7\*B۪5J䗮^>N^#ry\[iPS-cX@x0j,7x=w_~`/o-66Xl!G(/\+T ՅBMP[# B'Mz  }`C?xQa0U ӅLablaP$ E ‹r%eaMm]=}apD8&|'0WsGSOO»xȵuu ^'*BUzF&fV6vN^' >a0D) Gqa0ExL&<.-Ku3JE+W (j󵉅'V߈|sb[-Hķۓ$w%)|O'I}5!ydHR.đJ~N^F+_%5LI>|tM2.e223e!(E2-ՔI8㔏ɊOR>!/|)YYgS>'RHeʗduW)_WSNI&Zєdmʷ)g)?ɦTgNR3Ԭ dPVH!rtk}OjqNhϒ<+y.!k7'SXKZr3ŚfY zlm5vq,2[dsc1CH9[YVǝJTcn;Q ՜YoK}QQqFGMMZ[-kXXxF`MMZ[-(2F0MX=ޟ!Tmڷ.*F$/JMBL.q % n5{mvq{a;{x?_b Z? |+Z :^|?~?͏'YX_ʯ%GRj%X:bMZee2222̵,,,ll =Tb+ZdêZV5hlnekm`mbmamkdbacdnekh YVRLr4uq%њTrwK*;st&#уtr&::6@I=00rc!ciMK:&83sIGG5Gx[kf`XXx-"2 *:`557ko Xx+.6`=; w6666vv  |v>  ;ppp$!Q1Gc'''ӀӁ3Og0  \|,p9b%K/W_ \|*p 5qw<<UeHyNY<,Q^P*/*˔ %eJyEYQ>S)+ה/ʗAX:ǷwVye|9ҠLIq#bT|J-ASiIYRMI,mޗJۥNi[:-{dsr%6ݦ1OMo5Ѣ;tr]cL=Ӎ(,2V(gTr*ZTjS=UVMVS`JHA% Ce|b1>|OUJ_~f{\Mn5]WT}jWՀkNkՊT% j fk|fd6ω% Rq\)Uʚ{1Qb);n:H$aeeOe2VWLPU&*el}|`]& { Il]QfѬMke$NMRKO`AU4M ͥinRмTuz?ǺYyO٢첲_9O5$KpkCkCapm6R{H+FiFk9ZP+UЮ*jZvVM^ݠnjj7ihxo+wMʻj`ᵅRkhhma뵃1+VFW)+_v>7I֧*]~F?Cn7h6F;(0 HЛ-ZVvVO]ݡ5jwiZoZSm6NK=M&i)T؟iZwSKj}p`$IWKnCdZ^wI ''rAHMсd1S\}L i4n+k3'$q;ANl]1ojNzHm6Q6u u:D:TWG#Շh \[mT[a9P}XCӴ֖jGl/u(ujմVsէ"u:_@}6ϩays/+Kٟ{M]SP׫Q7oooa]ulmMNSg>suz@R=~R]#Q}`G4l^jk7hQ{Gۤm}}mMۮ>vji'scq8d064G1[;{;m$.,_/DxfN|Ms8嗿G4_Hs|Nwrbxn~wP n Ny|˹w:ggv||^Qdz Vx|vCb3 P8"ͩrU鸫6}[qAg:EGCh:6Mf|np빍tc#q4%$/qH&_7m%@~?G~1_ů7\hg_eId`*,tE TP@0fL` ++^x#&f`m`; MM̀--m;;v  {''?Ob"@-@+F*/8Ct!p:N8Ct!p:N8Ct!p:N8Ct!p:N8Ct!p:N8Ct!p:N8Ct!p:N8Ct!p:N8Ct!p:N8CW9}QN08tap: N08tap: N08tap: N08tap: N08tap: N08tap: N08tap: N08tap: N08tap: N08r: NG(8tQp: NG(8tQp: NG(8tQp: NG(8tQp: NG(8tQp: NG(8tQp: NG(8tQp: NG(8tQp: NG(8tQp: NGUN_1p:N8ct 1p:N8ct 1p:N8ct 1p:N8ct 1p:N8ct 1p:N8ct 1p:N8ct 1p:N8ct 1p:N8ct 1p:NꜾ{8'BhځId` t@ ( 4.` Lf@<Z`E`%``UuՁ7kko x.`#`c`߀M̀́w[[[[ y*D.+v!]g%Yn_8tap: N08tap: N08tap: N08tap: N08tap: N08tap: N08tap: N08tap: N08tap: N08t*/8#tp:NG8#tp:NG8#tp:NG8#tp:NG8#tp:NG8#tp:NG8#tp:NG8#tp:NG8#tp:NG8#%.̇/"2yHrKdXn(qrpYU'\}ݖ֍|0qci]7[̭Ofoo9T'.g+L䧈7xQxPJ^y"fÔ@h``L׋8g]Ͱ3Q3/B32¼?ϼ/d/,}eǽ#pn81OÞxl#qм>t4fy8n.RqMZȤ5I.'uQUcqKeK< "D*3H^-EX2̎z1Q{#fdo3fBo!FiM2@6MG#Ҝ!IH;^H,c)NxǙgPp}O2Oׯj~=(Nl8gY>$zVV6^5خz؎z6\z6l Գ)gS~App6)5 +j>wa= <.gSz6l Zfa3fڛSpG<8_h^lSL7z6%nXbֳ)f=z6<ad2b 89e VT0 |8vyip 0d13c:!cb:!coZ4[+=-PFYli3sUt|GZԤ\:[\X**i-)C^@/0̏.{_ƽ/{_ƽ/ޗ1o@h]`M2@nX'~s9لWבeB*4t) Qdlef;-/q޼t't'au ƀCa`w ]gYgͧidP kG>75X%HgP;lcˏmK!rJɪenM*#=Jc)p/7 >ŭ`aӍ. bbc@C&q@C3*f U1Tۭ*y\(?,ӞSƘG1d}C8 ͹gQ@jEbu9|`_w@;-(_> \HC<G_vsy?ؑK3fZm[kNG }gs"y<_j\sh!tl 4T+c61+P9"FEK}ťxQJbS**;{9ٚTQ|`O`8pJ|0 6eg>;ri(Rk|P * N.O vk,,gmmLL $.ۦ1)eMS_3RPEE}&v9P6Գt}Gc'Q͛`sHcI(}>1SEB}B_7]^~XN?en0<ߨ`T5jzFChot6~c$lsO6|cXi1-c8h1]+%de5&!c3cXgel6qE\vW6E.jjjjZZZZZ::::Ƨ% ir+͛HV-fZصеĵµڵkkkk;ש4fOKM4O?BZմi5Lk*}Zni I6:m|ifO[4meښiӶH۝/`ڑiݼ;-eu5uݍmݹ|8m[ڮi}vMvw[tnnnnn^^^^^>>>>Χ' r+ݛH^-fzқI^> }hza SItnOOM]D'ݟ^!jz7Mo>sz^҇L>>}rY/J_2}M[wNߗ~0Hޓsc3eAQqi?O ~{E5M}#sG?_/OOGs k[;{'gَl5۝fWή]+nv&-fw#O٣fOe̎ee/^4dW̮]3NvFͳdw.=40{L3#sd/^*{mM[wfޟ}(Xk 9w*jZ:zF&BX(0$":.V`s`[`W`/ihh  C1 H`n`A`q`Y`U`m`C`S`k`g`O`PXDl5'9Ǒs|99sji$ENۜN9]rz3vD;Nkg)nj}5Mm=C3K{ t:tJVg9u~]TWk5t]nH7Mf:T u:NS:Ϋ 뒺z]Uסu#1݄nZ7KunSnn^^׋^Wzީ}YߦCQ~R?/W~K# Zo^}X}O?я'Y}Jm~_MS[OPDLRF^l C!MAfPiCf4 CQøa0c3,V a˰c3\1\7:Cj0t Øa0m5 ڰi6 7  O / o F(0xeFQk4F1j16mNc8d5'39q;=uxb♄Dq8f0Ng)㲑6n}5Mm=C3K{㱉0 L7&IiҚ&ojL fSc7 FMIӌiδhZ5-ӎittt`cozd:4=725}0yf\dfYou&ڴi6ML7MMLMOLL/MoLMf,0߀efYk6f9j17mNsN<^`/Krʮ[n{-v{>`OKuE%eU -]c^co7{}>jOgsEow{+}#s"!wzvqGhwt9zaY9ǔcޱXw\p\t\r\v\uprurzϠg3 o'G)y$G{'{j=O {zy<=%Ϻs{bԫFIov{w;^ڻ&ܫVƽFoxgSyw{{{{{{{{{=}yB_OS>¾ku}}A߈o7Remv}k۾{D/5Z|._o7;;| K˾[Ǿ׾w#ߩR¯v'&Gc ֟/iۿ_????OKuE%eU -]cS k;4 i@P{@S5#D`:0Ht`3 nn^DPdPTD]`)xxxxxx8 `a8( *1hz`2Xl ;`p$8Ngrnq B!Q Bʐ6d9CP4Tj5u[wi CҐ"C7%CPk# FBcth6 -fh;]  = = =  ɰ, k3G5ps- £q.:.t: ypqXVacd>n w}Hx,<φS0 owk{'g7D#EmqFh&iE:#=Pd42D"""iDQG{ GHS5E##Dd:2IE#td3ٍGEnFnGEFDE^FDGDT͏dTUFQsGњhC9DCxt2:.FWlt+݋^^DDup46E[h_t0:NDTt9JG7~Zfv^aIYeM}8F(Fd1eL3ǜ1,5ĚcmXO?6&c3bl5ƶb;ؕAN~Q0<*6!vBbXl"6b1:ێcb7ccbcObb/cobcq".Eq2.+ڸ9xM!ow{h|<>q6߉ůįwW/!L$<"roƷqH QLʄ6aN8D4QhH4'Db(1OL&fsjMl%v{+뉃ĝģayUmC$K ɢ$)O5NdmK\KLNKJ&'_%&?$OyjIZUVD]eM}򸚨TWjYZ[mvV5 m՝=CգՓ3sՋիlVN^wW?>~^m^FR#Qk5`Mffl͹55K55DQ=ۣQ=3GǙۣ s{}=>E7HT=>Eg2G=ęcLDQi(|LT=ۣwW-en'PEϞ\ay:s{dnQT=2G3GQS(R(jEOI%s{}%s{fn&UԖ==3GQӗ̖gdnuu2 QW(>=3GdnQ|=+s{ۣ$+s{2GIv(gn[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[p5k=׀{\s p5k=׀{\s p5k=׀{\s {>׀M=iH@44ͤ%M iKFӴ4];M7MK4d(M3JҌ4d2$giYf-YfYf=i9ِf4H6HEA6D~)/i6_NdK-W ٚf+4*I$7\I%WX$ K$iKүcIH>IIuJүS(M$jK>cAG(O_/ɭmrDfFy*Ag,?#erHd_&C#O(I3:U ?eK*PqSHs1).KerBW~!?'OT%sĹb8O6 "qX,K2\\{?"ߒI#bX (D](̒e)Y,g?+UՐ՜ՖՙՓ՟555555fmede]ɺuu'~֣ìYf}:xB^'y*gyA^Wky]^oww7;ϛ-yxyxyWy7xxwK> Dy!}Bi_IErSA$eHB.DP~` )(3 A X 2PT@3hmt. @?C`5Xց`E  ~l_/N`W#c? %l9NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNqNIW ^+pzN8W ^+pzN8W ^+pzN8W ^+pzN8W ^+pzN8W ^+pzN8W ^+pzN8W ^+pzN8W ^+pzN8W ^N/8^ pzN/8^ pzN/8^ pzN/8^ pzN/8^ pzN/8^ pzN/8^ pzN/8^ pzN/8^ pzN/89'^kpz N58^kpz N58^kpz N58^kpz N58^kpz N58^kpz N58^kpz N58^kpz N58^kpz N58^kpzsO:Mi NSp4)8Mi NSp4)8Mi NSp4)8Mi NSp4)8Mi NSp4)8Mi NSp4)8Mi NSp4)8Mi NSp4)8Mi NSp4)8Mi NSiNp44 i8MiNp44 i8MiNp44 i8MiNp44 i8MiNp44 i8MiNp44 i8MiNp44 i8MiNp44 i8MiNp44 i8MiNpN;B4kgdwdr Zor2>z7/'?)(($O|vZgf̟egJ*3gљ3ϼ:̇3'dϴ "RBI' ql$[v%a,y"ϓN^ /UyK> OkyDxDtD:&NKg)鲔nJ}5Mm=C3K{1Q'HgSyt]zAzQzIzYzUzCzKzW@XTBZNz$=-JKRc[.M֗6vvNNΖJKk7Ko+}XY7KeL ˗ߪd2+/]*]/PzRҫ7Jo-}PiץJJOe|YPV,2(˼,)5ZenYlP6"MȦelYF6e۲]پ츌(嗥.de2mY/Ք5u[Dzײw#i,LZ(Se޲pYllllll,U\Fmm]+Yv^ò'e^){_v,'y<-ɕr,wF o;=~|+{Q]Q٩/ϓʋRBvW'&yC-Gc |V/i|[+ߗ_ߔߖߓ???ore\,Gkʛ;{ˇG'gWY\Z(Wpy||||||ŠbD1PL+f)ŲVl*}5Mm=C3K{qQ!ȯH&Z!PVh+ EbbbbbbbbتةثRqNG+^U@UtWU VTULTLWV*+͊݊k7+nWܫxXYˊ7+RW#WʔJViV:~eTYlP6+۔erH9WN*gsE媒Un)w{+}#W),,Tʉ:2\VMrW||||||<$*JRVV+heMeCese[egeOePhxdL\bj%[USWyzA*+W|[DS U"D%WTzUVUqU-QW_yf{+T>|Y}届P T\#U2RUUN_UըTͪ6UGկRU՜jQbU[՞@uGu_Huzzz:U $U*UZ VūjZګz:3K{qQ%ʯUU*e\WEjڪ:zFƫ&fVت+U׫TݯzTuXU۪U'jZ.P%jZ֫j:kՍuKݫPϪϩ%:QWԤZVjکuYݦTCQzR=S/WլzKS_Q_Wկo'F)i$Fk&kj5MKӫ kji45%ͺQqjFӠiִi:5=~͐fT3h4U h4W45;GCs+[͉j EZVUiZ֭ jZmEۮjڳs)yvI}}}}}}Gi;=~vT;h紋U-hW׵;GCs+[퉎 tE:NS:έ Z]E׮tús)yݼnI{{{{{{;ҝ<}X/%t39ݢnUt;=u݁P\JVAw}^UzުwVߨoѷ~J^?_ү////oo__!Ph(6H `4 ^Cؐ${+}#3 " 7 z6 qCbh7tz aY9ÔaްdX7\0\4\2\6\5025<0<6<5063N|cXlFhư1i76[ncq3^>N2Zˠe2fL[f-)˲lZ-}5Mm=C3KnZ,Ös)y˼eɲn`hdljaeky`ylyjyaymyg9Zu:fN[g)법nZ}5Mm=C3K{뱍 lDuz:o][/X/Z/Y/[ZoXoYZX[Z_X_[Y6-Vh+Im fm^[ؖۚl[6h&lӶY[ʶlmmۮmvvvvvl'{=]fWڵviۣDvjbԮFI{jw}>mvھi߶7O/o!p;D!s(ZtQGhst:zD~;bԡpFuIGpt;ǘc1uڱv:7O/oN)p;EN)s*ZtQglsv:{!s9q9:'ߙ,t;NS4:N3L:MVgt8ǜi3\vMs׹||||||pqw?r_uVwti;^vM{׽~~~~~~>'#Gzzj< fO yF=IόgγY-ώgsssy9<|xy^[Dy&<ӞYOʳ=mϮgsssss%oW%2ҫNx6ozǽwѻe[{{{}}}=|B_'}*g}Aλ{yozo{yzxy_zx{}O|OS>_}CQ߸o7-V}o˷]]}}|o}|'~_/%~_~?kK^__W~CQ?/W˿__o'^@($y@w j@{+ D]Q ʀ6`8@4Ph4@`(0Lfsj lv{+끃ayUmC$ $(5`m1lv{T|p>\^^ ^ ^^%ڠ9 `M!l v{hp<8 A6 wW/$ B< C֐; CPK= CgCBSRh=t!t1t)t9t5t#t+t7 84 5BPh(4MfBsj mvB{+롃НУayUmC$ ᢰ$,5pm1nw{T|x>^__ _ __  ???  O#H^ '3bx5̆;AN~Q0<*6!|EHQDGT}qGx6iG"pl\d*r>2YG.D.F.E.GFnDnEFDGF^D^GE"Q~4/Z-J:jڣ^.ىEDG"w"#"W(/*D<֨;ƣhK=DgSRt=z!z1z)z9z5z#z+z7 84":.z=cyXqLS1c±d>kuĺayUmC$Ƌ c$&b5cXm1kuzcعT|l>[]]]]]݈݊ݍ===Nx^0^quǽp<7[x_|0>OħD]$΋ $.5xm1ow{T||>______߈ߊߍ???OD^0Q& u˜'p"O4%ZD_b01KL$Tb9A'6ۉ>QP% k&Dc%ўJ&És|b)xxxxxx8J&ɼda8)M*1iOzd2YlJ&;ɾ`r$9HN'grNn&3 sf.="a#(se.=̥Gq#*\zDd.=O3(se.=3љ̥GDf.="q#d.=̥G$\zDeKH3QE#Rf.="U#2d.="c#2e.="s#d.="[#g.="G#re.="w#e.="# f.=P#\zD5K.sg.=̥G̥GԔ􈾔3ї3QK#J#j\zD_\zDmKkK=s}=sud.=\zDKK+se.=\zǙK?2I#>\zH̥GHxK$;s3IN--------------------------------------------------------------------,PDI1J 1G,Kl;qNw#xN",^0 (+%_5-|M;9 1iN)4~ ~Ώy|cp 8ϐ?u = {\?*~ZY}6C' EBP.T B- ZaE. ³s)ypI. ($,*!%+| |,|*|!|-|'<~(>|=DEߑ„<M,R@4BRAO| _#~@~/# qy)G`~ ৠ ,?3 A XJRPrTU4& Z@+ht. z@/` !0 V5`X9~ll6_[W6k`;uK؍u2a^k29 L H8@~ $L a H@&0 $L a H@&0 $L a H@&P @=H@&0 $L a H@&0 $L a H8@2F`'$8΀?σ?gsOy_.? x=)7 /MEW6kp x -  Gb5!2gwO %]Bw %$x]B/$xB +$xB +$xB gTR~g66s@!'`!)(?b HbPRe,`+Ah 4 @; t @?` ` g`-Xփ6_/+`+U ~vvImD(6FڈBQh# mD(6FڈBQh# mD(6FڈBQh# mD(6FڈBQh# mD(6FڈBQh# mD(6FڈBQh# mD(6FڈBQh# mD(6FڈBQh# mD(6FڈBQh# mD(6FڈBQh# mD7^>o~oA![0(OY18]p8 ~N?gO91X~"3,π$(%` (KAXrPVJT@3h  @700X րւu`=`E  ~l_/N`W`7,ch#mDh6F4ڈFh#mDh6F4ڈFh#mDh6F4ڈFh#mDh6F4ڈFh#mDh6F4ڈFh#mDh6F4ڈFh#mDh6F4ڈFh#mDh6F4ڈFh#mDh6F4ڈFh#mDh6F4ڈFh?,Mk[p;p{p8 8#8 xgp 68~<~NGy,p)8 Egsp\W5p? Ȃ&Kp W"+p5 ^6FۈFh#mDh6F4ڈFh#mDh6F4ڈFh#mDh6F4ڈFh#mWmDh͵F ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈAeǙ?6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6{o?#??gowsw {$}p 8  ΂?࿀)p\.?p\A ?,^n/_;oKo]=we>i#m6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bF ڈA1h#mĠ6bFx F ڈA1\qmߠX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢXQch#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh?,Mk[p;p{p8 8#8 xgp 68~<~NGy,p)8 Egsp\W5p@d?&Kp W"+p5 ^6bFۈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mĢX6bF,ڈEh#mWmĢX˵F hm6@m6Fh hm6@m6Fh hm6@m6Fh hm6@m6Fh hm6@m6Fh hm6@m6Fh hm6@m6Fh hm6@m6Fh hm6@m6Fh hm6@m6F/{T}wivZҒA:P9SY" " "G@EDt<8*2eh=!c+W晙~{>׵>d:}<%EFQQlػ.EFQQl%EFQQl%EFQQl%EFQQl%EFQQl%EFQQl%EFQQl%EFQQl%EFQQl%EFQQl%EFQQl%EFQQl%EFQQl%EFQQl%EFQQl%EFQQl%EFQQl%EFQQl%EFQQl%EFQQl%EFNAIEMCK/EG>~}^z)}"^ND>>>>>>>~?}}6}}.}}>}BE%Kӗ/?J_A_I8} ZuduA7ݦ; Oӟo?KL<}+EFQd((J6d((J6d((J6d((J6d((J6d((J6d((J6bFQQlMG;LKԼĸLO3v^bf<9y> x,/dA"bC by]N4/1V}?_Ύ쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓NvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvLpH(hXz .8{Ker$})iss ///?D_J}9Q JcUkOҟGɘtnmC_O@H4&[ӷ_HONv]vL'I)vɎ:Q';dG쨓uNvɎ:Q';dG쨓uNvɎ:Q';2bdG쨓]w6_F d#ld A62F d#ld A62F d#ld A62F d#ld A62F d#ld A62F d#ld A62F d#ld A62F d#ld A62F d#ld A62F d#ld A62F(_.d#ld A62F d#ld A62F d#ld A62F d#ld A62F d#ld A62F d#ld A62F d#ld A62F d#ld A62F d#ld A62F d#ld A62F dMO>~/>^F/OGLBJFNAI>>>>>>}!A"bCЗ?F_E_M}-I:zӓ7ݦ; Oӟo?KL<}+d#e#ld A62F d#ld A62F d#ld A62F d#ld cE62F~gldLI62F&$d#ldLI62F&$d#ldLI62F&$d#ldLI62F&$d#ldLI62F&$d#ldLI62F&$d#ldLI62F&$d#ldLI62F&$d#ldLI62F&$d#ldLI62F&$d#ld;鲑I62F&$d#ldLI62F&$d#ldLI62F&$d#ldLI62F&$d#ldLI62F&$d#ldLI62F&$d#ldLI62F&$d#ldLI62F&$d#ldLI62F&$d#ldLI62F&$d#ldLI6JpH(hXz .8{Ker$})iss ///?D_J}9Q JcUkOҟG:ݠ'omC_O@H4&[ӷ_HOI62]62F&$d#ldLI62F&$d#ldLI62F&$d#ldLI62F&$1Jd#ldw6_F"Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F(G[.Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F"YdMO>~/>^F/OGLBJFNAI>>>>>>}!A"bCЗ?F_E_M}-I:z IOMwOџ?CD} yV  6FF"Yd#ld,E6F"Yd#ld,E6F"Yd#ld,E6F"Yd#Ƈld,;F &d#ldlM6F6&d#ldlM6F6&d#ldlM6F6&d#ldlM6F6&d#ldlM6F6&d#ldlM6F6&d#ldlM6F6&d#ldlM6F6&d#ldlM6F6&d#ldlM6F6&%vge#ldlM6F6&d#ldlM6F6&d#ldlM6F6&d#ldlM6F6&d#ldlM6F6&d#ldlM6F6&d#ldlM6F6&d#ldlM6F6&d#ldlM6F6&d#ldlM6F6&d#lQ1]q'綾'ISS33gg/?H_D_L_B0}rП?I_GuA7=9}=}}#)g7ӟo?OJ"=F6v&d#ldlM6F6&d#ldlM6F6&d#ldlM6F6&d#lȐlM6g9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9dľl䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍#>>>>>>>^B>~7}<RDz>~}2} }*}}:}}&~,l\<|Kї/?B_N}}5q'QN7&ݢ䈬oo?E }Yfs-[/_'!9.9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rF!9d#l䐍C6rFw|*رb%ᇂO\}=>|[NEN\ɾ}x@>Vqkb Js_yG8cU2gSgl~ #ƒ\ixZxoZz͞w4$u ^Jx{xsՖ5}-#E/NgS3$ghΝ9.7 9{cȻU5K)˨Q6@.D@/Vr$JyBBJ7KzVBz5JDŽ"b(QA )~A9.KBe*>9 9rPΖ(uQr\\XB)f(_(_rs9-(Cy jGyrnBQ{ LE:xy@yw$ʣw<;2,g{g<:ۼP~Ï0Ih!I#2gJm(ۃlD?B҄yB 5"PP\*BP?< <(4(/ -Dl/֔ T̹N&܁9# {JHuNJ =8P"#QN=2 *`'3L"*ys{o {1/ U#!cWibŻsP,:s3dbHq/IgHs#"gr=41jCCüh?~׌ONzKݱb,` v``xa 6 bv1--ᭂ/ ^ ,x+W;UW_~]s/CCwGK%qq A6XDke2Tt fcpnp.|~{rpap!|QpC b > _\ _\<|"h0 7)VЂ;AlpCpSMM--/_ 5* H7o·wwww   V? ~?<?< GOX_ e,W}dA9W}ssQ˿[/gr+[KyfE9y/nrvrv ^1FW(_5}:`_("dF(k P$ H6u"(׍DP΋GAD|n\W'r^lirHK|~Qb[GZ|I ʗFڢ|YʗG.G} ;`a NQ..(_un(_$>!SУδ4ПYFy#$F572%lgy#/`r~&1Sd¿ O~F~ƞK4[`cKļ!Ljk3|#<^sDŽ) ս7PgK^B‹8OKE[&ES.fn9-D}ќ^.#!!w閆-;xŃk8Gtkhoh(t9ms8_vh".qk>6!'H&}}1rK.w?JH%ኇNch%ޕ>>:}+ YOo2Mg豣=ӏNWFf2x;oɤVmxg/.._]o] ܻ~׆w> ƻZZ]b^xk~%ރ6zqb~k e\WX!!bS #(1ZcEK[ޒzZIntQp鬞*I\-:kDq*Dwq!n=MQn$>]՛pݏ]AWg3ec cG`3vc`/),[_x>yv1P BMqk5?+ĭ/Y,J LBJ>>>>>>>>^B^sUe?~ku{ȷcB("#WՊ*JĻû:L]k6=mIڴMiI4hHo-}z;gM2ft(Q1'cyFu hhXx#PT3s3dv95s+~Ou 9mR][hL荬`VYּuٓiU{uYTgMVgwumSsZ )͙<ٚ="*:Wm6knQ9sܭs+rrus6۶n׺Eu-;fݭu׭[U:Fr#M"m#]#EaPTEԼܼ&ym +͛<ۚ="*$m~as[WWWSkRm WZoNz[mWQ^uZ[ФmAׂas [ TTTWoRm_ZN[o_Q /U'ӄ͜L榝_. (svWv}wԮO\>iO2Q_2uUHG[nG=k--^_ۛRFzߢOv͵ާ{b/kunN{γ+5]"JT1G,aV.v qPT#|Jnn5s57%p?xO&vn^no+tu#W n5^L3T<&։bxI!v#Q)IIҥl)_j$5ZKRwG~7Fҏ7ߟ;cd{'K7w??k|_2tW龯:gye2/֞Ggf˳:u,n>q7 ߻{w^|_ M78Dz\O1n~$~_5u;'9TvSy,tWg[ܷn9w_!&YbX&V ]l[+M'>"&~t/n⛘X/̥de%9'9Ŀ%r59d*'#+gtuʗ'< YK;9Ty+gU$#Yy/ L湪'ׇ\8G<>{yd^ ;\V2o|N&!r井k]]n>ۯ;čn; |G2o:'7swۿ ͗$yFn~ֺ/Tkʗ]oI:6k\OԮ_ZQ޶ꌕ)_'T]3i͓;|yxAP|h~G;'?}ٟkk{n+pEbYV!ѝkr$dqeD=R:2F]F}aJj׻H%kL7g7tgMsU]当{|KI2VwvKdQH ܖOn9]׎Aq}*^.Գt7O̺c%nյ+Sf'>*̟(u=;F_hS{vQxW26Iʯ<ۼ?ΟyfYf?͍/k;y4?8/do%oݽp&d~n {qVtoMi5M;]jcn\BO{F;s&t7w쟄њ)l^Q֢{=N-=MnOɿzC|'g"%!EQ|\k))3OLpE#ϱLܕyrOWn'77[%yGvs7>./)n>kHɔݼ]dMg͍6Wrjb't1O,+ZaME!ܨknQK%qs7V!gn͏',wTfhggggoy_Nxpfn^3|s$-~wxw>unZ%F1/)yyMD9֊h춣Ye)))M))~jSMMqlYI8\Px= Mn3)l>PXz{knՙ4uw֦|Yqwݼ,3ۮf=ϼ;=k1ע=koYյ냆֮>R~gӵc#׮l\>z`w6wKQ~OJzZ|LY5I_q흮ץv}jZ3jﯨ]=~= ɺ&^V2{VJ}aJ}YJ}u B}NAJKJ}TJ}IJ}kJrJ03>!X JHSy)v)Ɣc||ISsUY_Сv1_:7Է_'s~]ԋRSkQzzzzQmZTR^SmRR]R)߿OJ⡵ά֜Y/MJ/KKoJRߛRL]TNR RSR=R귧Ǥ'W7_IR8~8~v}?RoRoRRRONJQ)I)otBlBȭ^bbgI}uߒ~# \+_-F.9J~5sZB͠{?hFGZ 7""cbbRA(u:ˇ.ZsUBuZwKSJzko>Z[H+k~wm|D]]6P(Պb6X,׆jCa09F'Q(ZJGm6NIOj )T+ʴ2$m6Y,ǵTEhӵ銤f*6K(mi@[##O{T{Tk+JZ[kk5JV[u:%S5] jf*fk6j,ii%[ۤmRhJXۢmQrV%W{Q{QDWW<55%_{]{Kv+熤4 )!E y/SBiJPF(C0Te::EP*T7TW8SZ) mmW. Pچvv) V. Qڇ*W)B chhreGUC+BCC>U:*C5C+]BU*WoB(ׅ+BGBGCGCGY,rC*=Y~ƬtgV +ܔ *СrsVvV;+VnUdE"J߬|( @5AV_Y+.Ⱥ@4r[~N)_峦&V5R[7%u N -A |$GVpTHu{5]}:UgIWHGWRR/§S~AsLΖ" L_ᦏkDj>kjԠ4iV5cݼv:;'ה|Vt>g9[9zDJ}{Cq|{m=`oBi:tZtM7Ԭ i΅.\t|Bs`-"mh V [u8xԪܺR}ZD 巙K+헏i߲fuqm׮1 \iN;:7.}q]t]m]v@>뎮׍a K{Dn\'y5%,qMszkT^73-YO]{=]{Cz޷eknH%}};fu-K}컷ԢL^6*W ߼٭oܯ ~GqT\݋va!CY]׼璟9 'v{⒉'2q}erYmYeʆM.Sl]yeo)Ēi=i$>}3gN~qɇSL1z4i괆ZO}Zɴ*L/bKߙ^13X1㛙 f9j[gYg5g+>]8{썳Oi7dn_Z;S}gnRbG[[J%bN]sWJruuj"seΐMsωԯŽfIr7NyNyHneiIs܉nJ]%#O2m2u+?n·uț0vNM98~;$}9P}9P?=^/A_†ʉ)%w8r_8xx:\00q%I,Kܙ.u6 &ށ;$gjM܄t'tK k{NI^C5MMRSE) K4$,f]5#NsK"M-wKQ6%dy*' I<Ӆ4{yuj =5Oo.5UgN^C"%[w&h7>yH뒌57D+ޔxx֜- 5!KB^\%Z-bWE1Lt#B],]%j#=$v T_'ė&ID@j!>zH7ӛ>kit4H$KwHCD +'ǐHΑ 5ittJn-]"/R{J* Q*w7H.-ҵrO&&& I=ҍpyt%Y+BڮȊ"P|JKєlJX K(JT44VΗ>Q.PJW+-J""sbFi+}WQ:*WJNJ'Y,Ct*7*7Iǔ>J?) PH?)cD٫LV&TeP+ʙӲlQ!9Kg9[٭|(W*o j%._zPQ+Yy^5y]{:^My7͖= Wx?~*==$WzU!WޯϽzUa wޟ?Gc^S)5EVsQ(~\ES/U/U)7}6ԙ,ur:_}@.V+ՇԥeZU*wQ5W PQ7)ԭ+O,u]T(2##ezPR=<>Yq|>_CeQI9w򑯻w;}}|}/|E[* +Poor7WMMUo[[ ==>ݓ{Soﯾ6/)77 y[/ _os_=RpD]=)}<Lj; ],pT>:!p2:)KPdʙ:=ӓ3223Y\u~f$3.,PlX]$*sZ4uu2Te>jg>\ݐ2sTUәg>>ʹ-A9TUTz|rc|W)&y=6jY:)$OB/2Ćxx3^-;D_i0!'Fih;oƝ1B{ܫWý|au j>/xWkO[lOJ6eJ PIsBBVĵo JA5Eĥ"+x0'bZu/T Mʠroh*th.4-B8 ŏ(E$ %I7C[>Hq]E)Er(1D)'oyBOB{E}hT}@A>>yP|[o{TTmq~u,TD-Quبψv&L] ybh0t74*C3 ȷzzzR6 :Aǡ ? + .-,rNgk"vf.̶0nl6mf[/̶nu̗Jʭ7}1o )0ϾJUO`<wUӫbP{xո{7TG{-޽}_/t2o逷tpehx-xCxK/O ,EWWBiP%3tF]x_6W݃wexR2~ {S +k̹oYcŶ[[?b?`7#$7X)h.4-B;лn=hWh/>}E} }UB{g"OBB-aI@w*e@r{Z8^?y}U@BGߠO<_C@B#?18=/Pk6;)~w-y_h ?+Cqn (Oʠr'#|4A zZ=?סgP~0F>c[ȇ1a|AuC7зa#?x18ý;_5>u4%P#uR(bQˋQ[{"-º낙YYYYYYYYYYbL;v 3f)̴SE1c1c1c1c}>Up482$fMfMfMfMfMfMfMfMfMfMfMfMF#Y(V`+0r ZF#UT`4*S)D~ Q?FT#Ոh5"Z(V ՈbX(Vpž+|e;dg8{W({Ĺ_Eӆҍg|.Gķ,0000000000|XfbĚk X'fO<հf`=5{kݶHb~uz;`\CsaaaaaaaaaaaaaX`-;5wk qaqaoaoaka88V| c? '`?Oa?9s0ο0ο0ο0F_ĬGAi{U?]}x<&|ke/X8c!wN(>r<{&>ꞃxv7*Ti)ww"e%fz6\wzbtƛxS!&Ԝ&-W1X.ht74-,uxķsdB/66{Ht+j8`<+_g߂m|Y,';#:MRpJehY!ZVehY!ZVehf6@D 0 0 0 0 0l'Ɠ|RÓ'5!) jZBA%PR-tj]u:BWBWA5PZ+t nzB7A+Ujh zZE!2  rh#4 z =mTJHo@ބނ+Nh6x]h7܁=oſlvBwwqދWxp7ʅB(/~H] W&~X5߫dCڂM-+q?EՏW@@ sAA&89. o5 ֨_kW逼w{?W T4(ʀP&4(eAP(@P](AP=BhsFyPc| p t9N| }#(^c 4Mǽy[ߎwP_g'ҤxeV'^~5*d@^H|Jҡ ( ſlr\.|̰sйP#<1t>*^j]5Z@-VPk t) j]:@+NPb? ]uBAݠ PF't+t3E[~Ph 2Mf@3Ylh4W$~RzZ = -CBa\@O@k'uP!2! !i(m6BOAOC@gsu۠7?CoBoAv@;];#E`9]vإ9΁<ky:h,z=z zzzz : z3&<ްzs\.DP]N}b"RfGԵ*ڬ*vi&U^rGԭ|G\ B{UWE;U/g;лn=hZ>NyDmï:K<-ʇQc`l~pq{(ѷ_|H/ʇAP}k5 / 5K6y/u_Ay /B>AP2 A!( ʆ@aρrPʃzPvN?G;BPs-(y1ʭGA-t.3( Bۆp<7 KO{BӠ>k;r+c*l|}'4:Mi~܈aZ:,|^Gh͏TJ"P^| /|JrٟN> AI01ᕻŋ2%kV* tYK*o7@7MAU ~@sQ͇@@ Ebh zZ=-V@+ǠUjqh zZE!=^0^%Z_É_> L^ VCY q[ۡ!GAch4o1-зCb[ }o1-зCb[ }o1-зCb[ }o1-з,t#  }a //s/"A7/CN`g}߀o@7;N}'}ߙ^9͎vDv];ѮhNk'ڵ)bJѶ*@)w3f1#-TګfZfZFf1D*ѺJȔbdJ12RL)F#S)ȔbdJ12RL)F#S)ȔbdJ12RL)F#S)ȔbdJ12RL)FD*JDD*JD#S*Ab{B1=G1Z-I7%z{^..Xzc=D?hG4#bD(F4bD(F4bD(F4bD(F4bD(F4bD(F4bD(F4bDhG4#FD?(>̅cqSx*z<=ABoyѳϳf!zYYYY߃A{Ў=hcڱ؃vA;{VF"ҵ㢅|3fi?u#ύ7?\M1-V}h*4 ̀fBC{c 37f`oޘ1{c 3/f`_}1b`e`ϓ_ǰ`?e n\=eXv`=Cch{ m1=Cch{ m1=Cch{ m1=Cch{ m1='~˧D>#׿jF5F~{[X)i!VJ:z[y`bUcKKKKKKKKKKKKKKKKK]\clؼ+wу;cBODO ГfIz =Ylؽ{c.]ݻU9zU^WU9zU^WU9zU^WU9zU^WU9zU^WU9zU^WU9zU^WU9zU^WU9q?v^bMp)2ߝNu'>3Ng'ى|/Ƹ3xbky~#cǧ?ɳB 2|Vߓ4y.EH^Wʨ@9PC\4 F@#Qhh 4*AwC{ нڗ1B2ЦI؟*yJ˗Q<=н ,ӡLh/ω&/}KAKߎ {y /B>AP2 A!( ʆ@a(ʅB(ʇŏ!c1bx 1<hߛq] ]u:C@]kuP7zh(q'4 FBh,TC@{Rh"TC;33DSDkyyҬ7'?^F!Vc&}N#6$~'0Gp s'D?Cc~ я!1D?Cc~ я!1D?Cc~ я!1D?ogpwt#  ;AáHh4JqbCtcn э!1D7&1ë1S1g0]hW"8xb 3 wF*yVT<9-~3(fQxqx6"K⇰a 8{N0Foc1zc6m6666666666666666666666666Qƨ cT1Ua0FU*X!GBb 9r+(VQX!GBb 9r+(VQ1oc1~c6Lࣨ}өN%"wpuq7Qe4( (ʢ *aQQ " BB4 Ӊ$TB: {₎:[rι9z:tsq.:Eǹ8\tsq.:Eǹ8\tsq.:Eǹ8h0chp7^wh9hAss/A{1ӟ4=^.Beh 24 Va'9h-ZZZZZZZZZZZZZZZZrВ%-9hAKZrВ%-9hAKZZZZZ*FKh-bTR1Z*FKh-bTR1Z*FKh-bTũh)j5Aࠅ*Ph͛nҭBUH V!8ҍ#8ҍ#8ҍ#8ҍ#8ҍ#8ҍ#8ҍ#8ҍ#8ҍ#8ҍ#8ҍ#8ҍ#8ҩB:UH T!*StN8fjas4s<LsWˊeղjYqZV\-+Y"Zd]ku-EֵȺY"Zd]ku-EֵȺY"Zd]ku-EֵȺY0#v+* 0+Nd@O6l}Q> G(@}Q> G(@}Q> G(@H0cH0cH0cH0e5DY QVCe5DY QVCe5DY QVCe5DY QVC'"4TP*BCEh "4TP*BCEh "4TP*BCEhH C,C,ca~6p;9Ƙc9Ƙc9Ƙc9Ƙc9ƒ][9ƘcAf͚w÷Jé[#cĎ^x,x謁 +Gr**Rk$1Szgv e%ĺ+ + $_ˑ|9/GHɗ#r$_ˑ|9/GHɗ#r$_ˑ|9/GHɗ#r$_U`}X_WU`}X_Wf*L%D3hTJ4Sf*L%D3hTJ4Sf*L%D3hTJ uG*!FKĵ6桿 W/V4|ohYe t"d r-Fȵ#bt\krCy5!<䚇\krCy5!<䚇\krCy5!<䚇MU`ST6UMU`ST6U܋{1r/FȽ#b^K{ r/A%Ƚ ^K{ r/A%Ƚ ^K{ r/A%Ƚ %cWxh/ƒWȏ˕2CDիz|J+7Yht)UtYps׉VD0yB5m,b}$]&>֠uE7\yX4KbPKoB|#GrSʘ}\k^HF6|`?~,a`ec){;dʞ8z왴1Ohu,ޤ-ΗgNv6eF0㭠ze2߮52?*@s:Q~\xc%KKT}Hu3R R=T"(R݌T+fi#Ҍ!!RARt+v$ nFۑf$EQ$ F%H0#H0Hp;܎#H07# W $ 19HAR@R1$CR1$CR1$CR1$CR1$9!H*$ )ID}/ jlp?ZTm~1DDze; arTy^i'Ga'[GXeR|9׸HK<$>|_.g,6硧opiܱrW3ǝ۹}0w勶XTwcIUXRVTƊJ+*J("vcXCP5T` UXBP%*, UNO=_ĜFQ _DE_d?,YN;E]{ܑjX ?ERұ.%mE*VnMk;ޠ!>;kD5:rlB"@Xfb,h U2>8w+F|u:znF'F'\݋t抐^>j !!#8$1uWv5Үs}2eςiOqLښHnX*Gث>!|"\g!? xefygp܋Ʊfu`* 2BYb!֩u`:;Vx+^jmЇcȾqW!71w;aE̻ys-bEx{8:!Gh~z<?B.+XDsc ;Ha/`+mrϾv DAxkySR2堂~wJP%I90vƎzܤl?8 7u%Z:py˃y96XdV0V}@cʋ>Xk>uJ6Zs h/n4:?tcɗcCΜXtoO_ Xs-@k +,LΖozyƹq>ƅ_$cenOOf6vM\>#ˍ>*Ia``%X@q+πq`Y{ Ʌr6^lL[fUƳ` o˷-l~2WqeO)[>sٴl9L0µ/o&sXX}X;(ml.ء] Jdءؠ]vyW8{&{/llV`w.`M{/e&$-sq41hR*x(>hA{-Z֠ hK\?tc)aa0>00J4`,xF.LY `0Uo7,A;/΋9q2^#T$OX-Xa-D>L%+~ +Vv&v!vv7+]VNVfV*V?nc%e<͊yU4d`?#ww{|F2FX[s˞+aϕx>|>{{^cSE9 90C̐6hj#䠩hj#+?MeWWWWWW◲ڗڷڷڷڗڷUU*asȃݘtY+`fEeEMaEMaE}jj|:gDk9zD4U]CXn<ߍOʧ`S<|7VFS)Xs DNXs DVȪA^Xr |#ȧkk|d4db)Xn sW'jf:gUZ6â3l 3gf̬Y} D٧SFfg2O]QU0 FψQ>`4&3OEJ%qzD^zˡ7[Uћk39VEoWBUZ,蹊zނ,轊ޫ} oz-LfYgಗ`0f8w0OONK݌ޢ*ʣȧ΃55IXB$,B(+ lB$L$,⬀(+ ao 괥`ܑN25GlkbQ95Gndd{nUg*{A-LbF":Lb0s22 w;]n,M&Ւ$δ+Kn)meQc!C RQ\q_}RgrҶ~1x)B+ˋ.vˁٻ|ugI]FM=m0b>="qZ)e!%w]Iݨif>khueFoIʣXy=1h5DXDXD4z,$(ZRBY} aaBY5RFy-3`2jWٌ3re9#,GH9)g#lp6=#ltn6f}UIf#(Sg= V\=];Cn8VO-;q@wYďWR#/%H.2G N'@7p28 tp8g9 Bp\z???Kerp/iʵ JurE93ss){&eo6A 9N%']`o UA2̸̖̱:U i\Gl]ni`(|;(*Z_ <.t3& ~>/ R@}t>#Wz9c_a*9IT7&X\S 8t] Dp) \W_:wO.rb ܧD9@ x "yּk^5/<%CX0L ^`\z7ː區|P@y(q & 6i8 9`ٟɾ'^FpB,g!Y<!d `#@.r1NX 'r{ rxb9<XO,'{e| JA 3yq8C|MmzhW\z[lkb[uunέku7]g!0< BF2XO˭c:9XBr`-f-f-f-fr,d1'rb yk' ш>v  DOOg`3ll|66>&H|$v>;%.9NESh&]Feht]&huZVǡqhuoxTw_(gSKU_.9z9fd2"hlFvWQ~ա5RdvA#Q0 h/F#haqX8fN~9-M ewhg2#*G'NY3 c1̆?fؒ9`K# }XiSYiSYir{kj~A!X9LV2V2L4AwscsW~j]{{ryaugY7~֍ugx00_$0< `σ`&x?/`HK~DL~ C0x #Hxxπq`<&Y0Lthi(RD?H-׳ v_1Y~l͂Y6 fA,a l͂Y B!D"D"@ B!|aDQ"(@H J$% DDQ"(@̀%3` O2Np{Ϫel 6M{~aS?lM6æ~ԏ-wE"n~[-w`ގ0oGu:_u:Dz"l=QDeL5(1PAP)߄ٗK`%0} >VG#ab01}>LL&Ӈab01}>LL&Ӈab01}>LL&Ӈab01}>LL&Ӈab01}>LL&Ӈ+Ev+\!2@0Ç 8|6L$"|]o9~eQFDaEζ2D"qȅȅȅ /' /'βz ?9qyC^8yAQ!ZH2ѺAƬMrc 5ey;;8C'C8Vl' |!pF:W8DB#Cn[8C4i"D"N ނ뉚$jzi QDK- Zar0~\?L&ar0~\?L&D]!QW+D" uBD]!QW+D" uBD]!/1ΑYfھ~6p;ew'(Z-D==)8^e8I4"PsKj* j'/_z~Dv7p_ޯ#|FٌLsZceyx԰:ƶ}!jϡhj7v&Ï.玡1Btp_ƛ_'/ro=jTS;xeTfGFAZnj ԣڟI1JAYMx|&$l`HA2F `#F'` ƀ`,'?K"K?/0| A2F?5M\%\%\%ܡܡܡܡZgp2cE<OR.I[e'@y 2|@ x (aCl!zm=Ķb[Cl!zm=Ķb[Cl!,`X>+G`%X61 <l9ƻO7 ^i6at'4₳؏9T0si11|LcNMx,< ,n2vSׁ8z6˵\RJPvjP#s@cR@+%X`"x̑o[k2w">d ˵[Gtʟσ kIj̞ĺJG';pdֻgY~ֻxd,i`:x<f0 0Gl0yrLe(Rp0+*(a18F $F1)4 x0L` 99>QIIsR Y ?j6Ǧ#Q0 {Z.!vȟg!vȟg!vȟg!vȟg!vȟg!vȟg!vȟg!vȟg!vȟg!vȟg=0mYc1r9kOɊ,kdf xl'$$$)D" 39L9C39L9R&yP&yJ&9I&9D&9CC#Y'Ȉu5P@X>D" CĿC:ĿC:ĿC:ĿC:ĿC#^:Ĩ/tFCbqCoƒReeb̗)#Ĕ{,a0*@s|tN9NidӌVcd|(Z!όD3c8K*(."D:rOs )!/\ag(!jNO+/Q} .5EvkW5H'{󓽹,OgXD2vx(]x"qzMg C(b~UڍM d:OVj@,欐Fqк>q l)xhl7Hq̣|Zdt@ x (a"\>====<;;|f-EVrVчr =.%-gDl1W{heq\si}UK'l PZgKԃ:)lo6ͬTH:6mO8740piUP}%18#h1ԄvWǛҖ3H&JTTHv,C2Ua{\B>ܑǞ;aBѢ>^^hV\T3t6{(Af71"P"#"Mc$?OjGqFT]vF2ꍁ25=^0R Su }d#yhe/# x붣T>ZsǬqw{9w` F 2~/#JNZ3JNJswFyJGe3jFk7h6^.x`Fm2Xn%sQWVkmFX'nړ\f: VH$DukH3O"F xiG;:HN;zw1JmI]̊\Ih9jOz}c:X]_'NN`yN\/;Ckwl{wh5ӏܽd)F⛕S_Xu|OjzGq jڝc\9z{K,pc{/qcW׵a~{wږp# k.}-tס{O= ">?~ ߓee߹gGRA\r*YEY2}!.s#u*y6Q;L& 5s&1Bh~FX,X$]RL;SyTD5M=_Ud׋~m!NnbM b^zxBwQz\c}>. !A|hXF` 1ŸQL5w<==R66kbL34MiJKd["r+55bu7X7|VoQ.k([CEzz\쳞DͰ&[5zz^L-`Ͳ^Rϯ]})Mƾ-fZ?_+__;1ڝ㴻}.M77Z{oo6??X{68 ɕڳv#^wػe_sVkk׾rsV~m?k{uV)RZfʞZ= Uv)ѱD73Cq["%"K1cπr<0>Cq_f5WBq_K}iGvij2>=]_%tŌ>[} 1o+l}AcxDcXDZFFhm\o\/*kXqq77S2`ŀV0* o`"I]fօօϋx͂.er̫uu%.lb%)Ka֭Լͺl]F%%-ʠd U\fY[+FL},m\6hb4C1Z5]X(qR얜`I$a(ǥ)v35#kϱzz.0΂8vͲ޷Vpf벛}kך)^KR\Z k-)^K*r٭b3%4&z>?,|G"aqJ?!|I ]qJS 6 ڎ * *h wp+R`:VF)%/+h S4e&q9im-&pDZp9∠F#/拁kn`.;B}ٽܑd$׈_RPOnՉ˯~vnkٶenf71}RH=1]rWڅ2f{?9ۉ~>g=PF\i͇j}ēk%sDЭ|w竲ok_y\+k6% KZѫQ|>_K,gijXzH>+7&~hb66{L4x}W]H,!bw$ n>h ?={/4w#r>59uWO_H?vϪZ..Oq\d3'Pog2}_w}WdQ}d {)3O鉳X7@t ;dH|^ ݪH N9F9Pr\<ͷ}, rl<_/.Aݑ(7ZN'lm?{FdƷO:>/vY8\tud넜ʁKmO*}-7؆-%bkΏ'l.AnG0S-;U68?bKuOzy{qVt|CfֿSxwV|gK Mtm0J\1k8]Գh[m-WܳX@䧇8 f߼۞`΍cPz+G~u$A^} oGwoURϐ=q+gst=G!̔ Ur1P ˹kFޡnN=~jV;袎볬D徫>?Q|DY͡w}q8"7y8<6;۟{Ɛ{r?xǷuuYÑo~KY☣mH9N> FWbrڗeDnGrE/tR$f̓5 x˪C8^¿n;|F|g{Tlv/Xt:{TTJ@ʉ>$>:jQWB+g,IhM\בZإ$p j\~\羈JE}>5v2gOqf6 ROm!|8ߐDƇ+cz:A֗h ,QY9>;pߟ5_W6.AF/|/x'' __ӷY|O TNj/f(ޭQϒ=R>G^e*F((z k&_K< igxM3kcC~? xq%E9G }vvPI?pkiRl7~laR6 Gٞ߹5];9-Gn).zOQߴ}^f^gs>O#C#qG"pwmi8srQ=i@KEGqWIbA?OoHzE?{"w5ܧ"U4'òX}۳h^7QQ_德!79,9rzw5s|I}s AGwOԿW&!; _fz}N*֝r 3zWޗxy{Zʏ&r@W$Ց4X~(ț*ei3*y[&s{UYe|CxgX7bCOV9l$M9O7=veUW#~O̻JD'0 v&u܉8֝?+ g.Q!yIUi%Q#a*/8VTU}X!oo\ תv,Rثcgq,95x#t"IGAuҖ;^ژ.1,LC^?sc/rt/Q6V s~LH}>Ed[vw92\M0եw4q.DKq88_Mu}.Z3Oku˴h GQLkų1E=%kcibfj3Rm6K-h+b~]3EC!r &wE'RY_@|[ dM+"CGWK%L__&UkEl}9zأybaQk42F*h q5:48FliFO-88_kd\h\51~o^kj1Rw/ ()bdϻeZ?~_k{mvo#oco7MnnhzwxwhxEڣbo6[ݥ Vz+'}ړW{ƴml2ʹ͖f;mؗK}!T_>җ?kkuuu֟u?;w>]wt}_߹|}Yw>Tվk龿C/}/× _  &&&陾B+W7}Lm_J_U%$M_IK$K?AR0,iR3ä6ʤvIOWk}}l(B}CƟj6WϿ7([t$/3l[vи>q}O!.> ~;x~dۃ${=ΘiO'sI<{=x~~xӞggoٯoKw c^n|bkFdl/v=0,0x<30:QlTSQbS2uJ[RREPBVP)0QP40-(m)ZümqwNjO%qGO|߹<|Vݮ㮿nĭ/w'2 T<_,fbl6E -aiZ#x<%].U;9> lxdx Wžž7P~#zS >Jm讍FX5 V/ҵ q6=Y1ɊaOV }l7xc{mZ}gk(aٞSS6Q{ߓ5h/6zoqFb0l?*^x 9vfn8ch0qWLn)iF8he1-dzbt.[r7w0ziyyl7Χ<{vS߭X}wtDcQB'gg833]4<%#y^[,,-aw)>{{L]46 #z^+4%q'$/f, 6m qbP_%ܛj6ޖf[E#ٖۙف汴HNf'gv~Whf`@cd$ly2b½Zw;ut=77W|׫W(J_%-TC#HKr|\%iPBÖFăLjH4g'&S%5IZVIZs& DEW5k-oqG4qc&JF !DN9Qȉ:{5'ɭn$N&? ݍ?޾^ooEоɾIoo}}}0طq|};?R..w==Թ׾:s;C%&{A ʉ>j?v=TُcaH zm?MX{-${ԙjOc<3ԟiڜe=۞-18o8~~Y\`Ϸ_x:v&W__gq&W߲{΢d"EDlWNJ{u>WOO^C/)ɶ7&q$yl7a|aխVډ8ηEwVhn)e##ED9Z@V]3L L'&& H"Qq&n$*t7eK$*T$*8$NEA#X3Ad/b?WO*LU1e3S6W1e޻GU]}d2o6L$/ĈiLS1RiDD"HiRRD"EHƔF44EDJoĈ}>}γY]{{u@E QTD=P_8ꁊ*@EQTD=0!ꁊ&D=PQ z0QnAԃ zM6nYb0Xn[%b*a!d-T(%+)&ye ɖ}N('+[ eeR!r~kjد5d Wk,W,W!\o¥[~ G\=bq̈ fXº- eGDظVXҋҋB,زieӥre+6IG\!^zOzl9 QpҠWM!I@eKRؗ/k`_ikS.[#6yqز²,ey6 >ؔ>ؔdSFd9Ɋ46e5laSVy;,jXEElA $ońx+*⭘ońx+QoEE5)j+*uQW,2QWu%QWTD]QuEEXD]1!QWL+*uEuEEbAԕhD]Quń+cbBԕ ":&ꊊ+FD]1!ꊊ+D]Quń+*uEEQWL"J#QWue<܂+u%QWTD]Gԕ2aLQWuE%Xӄ tCPJVS2 אe,tHIwc.Yvϐbzq5=F< i2'd8Oo!HLӳ.}t~DOH!B駦3B !4~IBy[i !By&Ne!˴ô$_5J|rLO61!7(oh:dzr=]ӻ$ɾA=q|7!$C52zOȇcl-IXK6$.xYī"^DATI6 AE H)8)^ΝR)D-77U$ĩs`3#$[yyhm3煗CH7K_LY0]):K|FOb)8'F'GK\*5OyY|GNC"e=e@yFyF\Dv,mSd*.W~F\I[3]p5Yϑ{JrFHVC\Ov?ş+nTpCVgدk%"s6;LVIVJmɬݭ͐Z6WmP֞VHEJ[*מ6I Ԥmޖn׎hG5 Nh'GS)iSGْ>.IdI KK !)Rz> : EJҶ6io<+-ӭUHrt+;ek7d䲃p<\L?qtn &?7X! .RBiX_jgdLI)RzJ(5QDi*f u`mG (-&z)VSZKi=MP{'={h>u J)w%JWeo-0J1JEt(OO^Je:MJ 5~~?5S3ɔQj旝 y!a>PZFi%ڥvANH覴Rz)׷S4@$A9@˔ Md  "(QSrPʢVLnߝr1)PBi:f6Rǘ(uҪѿm6RLiַmkD~ i7}k$~}nl)?/#~Q|gr*u 7o#p_A#\x3/_*VbZɣӚCȹZeN6괎S3s7 ZW%\oDȥX[wZX[YZYO%^"de B[Bj+!/PPl6NNFؚF87a>„%VvQ = [zv%M8p/a d Ṅ $l Klln[Vgohk)̟ٶy"eV#hLͶcwڎmmlglmmWY۰ITF$F%Z9vQD_bUb}&B'%NMA9 +WM\O)q ĝ{>DxK|t!)BbJ #Ih|+%/śRRҐҘb4eӌ@KZTiVCiiδ47JWէ9MHOkJ4SfJ֞ m1p("m5ڴҶnOI'm?!:v4Xډigӆ.]ua" mT\#unp4:Ӭ{6KW:DZ88s t s\ 8.f`Dz\=ݑN/N:'O!>pv<;w<}Uu7oKA;}# OI?O+$?TNB4s:QzLv:9NsVbs!MIΩ~zsݹtW8Wu'Bݹps?!Q1 iY3会!dhKtwFL JȜ WF^7,d~CFcFsiMms3g,XB2•Dwets26{Fތ]{3#8I!ch*l8q!љq9Kr\QƹW*vUpǏkpkkkk(vvVwֹ66/b͵õ:õu_]trqu.:&r g*F2-Qvo539i/xd22yfNlʜdݓ95s9l\8tV g;C̵yL279fnN3s083ewɟy,D餎̳C2s\e KeKLVj++Ϝ,o<>IʲjҜYYiNYRYYmYsg-Z,keb}VWVՒ!Z57k%YN&9 YeKlsvDl{#;+۝]]]p2{|)ӳgf&&#{QvgUkeoެ۲wdޗ}0,IdzOe>ϳ}1~g(c0*ǚrrs&4Lʙ3#gVΜ9smڄ e[)9kslsXlٙpFƜ9{r9s"tٜKTUr5\SnXnL-7hWn^\onaMnuFncn3\'s[sɞ̝;֑0w ܕ]݄r{sݛ{ !,p)/w d` sk;Pgu"eyqy+J0$ vo%?*ߚo t{l**ίϟߤS3Ys 0՚4EjR_>S3KvBggj<<ʻ>&J<)[H/M0w嶹 uy2]F%͋nt7g-q^uOv4[mYBenqw۽urMwpv'݃?V  t.(+8 2滊 IXPQPW0`bAK3 f+(XT6@zqr[~G*Xvoۂu-\-OM v+8XpxSg Wp\p ic;cDy#'˓lx<>Ty 'xFn $T ,= <=K=+u { wuQx-Å}L{ O: /EAvEk^I"2W3z [>/ʍp^yuxn{+u^xDB;EӁ3c, ûIw7k˽\5uލ.ww8Alni̽yo|q)==8[ދ+M"Xd)*%9rYdM\Wdsɶ%u?]UrJ:JJz$7/8R{ɕaj$3DŽ,(B/GƭJU}{x|MI77+eo=[[[[[[M-=lOM/ρsk]e,ҦjIҰ316y-M-ui/}${-m6rJK'N+m l+[:?[t yÖ.+]Yڥ{ Ԉҭ)J{Kw K(=}'SݥO u lc+c4"s$,^ ϑȲ2wYqPYEY!6$MNO*Y6l^YGV3JUiβ5e]e6{ʶ9vvKigWv`|ّe8Η]L])rZn,GZɄrO|BEӹ{/T>|F9 /-0l*_Q|mM[/*ߙ.SPc'O-*T~B*LN'cE1~ XaSW(hhh\1bnK*UZ[p/၊ùrYZ1Pqb\Ņ*JC22^̪t)ZY\YQYW9rbeK)+gVή\<;+;*W\P\SP}zd` ՗H5sMDM\QU.\f|Ě)5kf̮W<[fyَU557lVfwb=>ƒ5Gjkל9Ssb+5õJRUkMuzj}U MjΪSyvAڥ+rԮ]KmC-k7n^vOyGejOԞvZk9sj/^uԑ^ՙc2\u:W]^RkJnrݴֺu-[FDXR6men[)u{8z;m ]Sq+s]_@I;I-4]\wMI qW)ۗvu\qv5Of [)%" maZX"pFFuyn9Хg} Jeu 9S'ĒIu j|5^[CQy/^@Gu=|] =CnIhgBh!tùh[)= 3K "܀u1 z٭܇QHDf!Z@{(~tJH栏S >4CP;|﨏_ =Gy݊fpaQ2LkP$A3JYwܒ*d S/0T8` J@{*RBsͨT2ʯ #1]c~IL;QN^ ъiL ȇ#UJ#s9h@Zhc5KGzQ{!Z^Bn4rѯ2Q4~HA& /XF24ehPR=\نl\ @['υ<e;Q1),SCY^܉:%g!8b(FI[<#m00W<6@k RAg=tG=r6 m-+`FQVPO2#\J@)'cXKc-=ԧ76֣B;<W1ɿ' ^w@R;0nA fv5r?fs~<2tb(0Jݘ_cђnFz1f@7F1X/`yMZ7FNsx'(gN@ Cڏ9\#qS)4DAmNA/bC/"pR}#G Q{ c6hNѾE(zIJ)mm};@?0yKբ~Qz uYcIeFI A7iY5=Dۡ m'2Q71hz+th#FFw4x(+ = YQ'(Ć6CgkSC+F%sш(TC6 KK,z0kGYͳ ԀVBO 6Ѐ[P{Ꚋz0 9qPº "!8FupCq>(4kf#9crFz9 sh=h8OϠ(u!wn!Fu*d>Jjh0觰NcS~g*Տ;W0W3+qAxYi*~QjXe`Z | }oAߟB[)}w-~4;vjAq81a=9\W}У;Fl"+Dޢ.s$mNE1;Dmwd#-)|ih&'F_ ӌD}_} 5h(BJcYVb1T5y 286,FW>TcT5_0eD4Rц ;hF2H_} 5}"z?U!D>(uO(ڠhNyZz1WržZ)w{(:O/xHhדDsg`!+GX3$r-2\3f!e<Өq=Z1z#[F\xw+|0OcF'WÔQsa1i`N0hCZ)`ҰNaOCCul?8 ^q@Ͻ # #{1G2D􃌒cT] GwG[#ImD{={ؕmiX+ r` v䶢؛;Ѷbk$5EbN]~G<bFc|uG#Idcd02٨1 'aFIqv2J3o4Y HFETO>BF1U`/qܶHzN!_(>RAm|2v!Ѵ? {.va>Ւ/O> sakhY̗r{b^Chs0JFs@ȍ=,Im Np$ЂZ3~ V) ˫ӕX-0_|A N@s-)Oޅ{6!9෻EKޅMe>FncZ5Q3}[Oqz[nRf [QZXo7ëF0 N j 6Vk@wQSf㹔2i|5SF~It(x !7QםbУg yT Vif%^n?;@GZ&Օ(  {^2JOc{(~| ܃?ƭ?i*l /(C~h@k<4tN8(÷p;67Fl"$ݨQ-`ս ͂O2 j}x[ר5ʏ aA \VYȟL%퐼%A~sA/cE3m0Ęs]H6!.=\dNS u'FN׳x=GK9 -`~ы@T(}F#zz3lw3J#n9+GoSKXِL-ܿ&Tu2Jqio3P{X^vQ+a?ErloV[]?!kAil(猜+2VKlKv^xÀU؜l:ߤ]\JmO5zYXGli*ﻛ-p/#ߠ=7l!E-=Ohg[w?}F?~;߄ii)Hu; ٌDNOmN~{bE OE~ׅZ*ޣr-FE΅{PoGɣGOA&_aw+}i߽"Zegl 0x+qkuhmh C1)/8gok-y 78 jq7W g6~z~V~l DWNN_ǖuy [Nėa3<Ӱ 1-:šUęV%`]P'N~ztw"٫b!l; ?!,p#.Jvr$m&W6^F J#܂@Sw%~>C_gm9 ]ѻ6Mǭ} x jf+0>2B8m#<,]J Z.X(nxuU!F 篭D&!D̚K9FQnoОJbe6aL>9Z<&cG bצNI=An@n'sTu5s ¶wt'K˨@땇9WH4dj| p>g H~O2ۙOcmmڧfiU+a~C8FGpԒȳ.L'Wv+Vrc`G猪 F)2PAm%z1k^~yLpE;a7ηђ~?K} |4 ΫEާM-S5IEUSn=Y  ]?a@_/'`cܫr$ӌ;27cFh?4"8O5A{2gnfK&hC;]{Hz*oziA_.WqM݉RM(c꣍~M\!}hMCKaf|7q%vٟ1.h>K+s7~=]{ȝRد=`-XӓNҾ)L18:ʾN9eY#~ ʹ6nĺF)qX5384"Dm E%n`1N\' B0NEp0Y*f  c?#CS6_/D&P[RE'z2Al)MR[\\q>d bUE} 'A5I Y'%%GbBƏy&J-i4d|fK;ER\Z5~܌gA<}AоOK3"[_6̣g3CLKG&3R%3Lj`T0*0Gp ==|sefme8>dT嶂nbؕ-+pKx$O*iBxN >#|Vዿ_?·oiǾNZ:>t)"-aHpݘn눼"j""##&y"Z#"悞JK"+)uEtGlֈވ]{GӁã( D=q.Bk!E"=JȊ_K#["DN9;r^d.wRUk"}-m-r}_I#cѳEG9"OE{ꟓ1:yqW"Ƕϒ(ZQF}]F_WQQQɼޣr<#9G QMQF͈5'Q};"ji KVGZ)jKQ{G:u g;9D騳QC|>G]-DkѦ0>L籈 yӢ[ۢFϏ^11^bY,z%c]t9;!u okto|޳cȯzG]1@у# y;s/G_b 1ɟC:1v.;x.'Z?ۍwlb=p?ܫzin{W#GIq=w!}tu\b*bx]ƌ31ca(3f9VŬu1c6l}#f7'k_#11cNa_Hgi̙|\8&Mkk3˸qQvaܒQ{m]~,ne\W\7kԮc`o{.5Yo/mW^nI#v_؂hۈM7֮Ilύmfql3^c/vW,n+LFV>;7wmw9Z)o;Hu'Ʒ]S#͌kLUkAFD~?>JsHؔxD_5jdZdDX}Gpll>8=#mJTv3bb/ೋwʈ<"dcsllpdl珐m4N?V69]BCBcB3"es|v'OX%QH#g3GS$ m38"lqݍڤv'#G 6ƀ;p>}g *i{7hp w2e:͒js A$oLiΕ򱐌i5cN-8̑usߡ=gr74r!wjz4Z]iGn;2 cўqh8FےI>4r3:k(N=ђhAp.s\ %V=1'*FLD-NNw]7A&/(6π ĥYQVFYyIL77@Y`-0 5A0*HGb60 [㠏=9(_@9 @A~Џ@_W AB2_G߃/|o-2w@` b z0w#ndZ'ree®??AzB IhY[/|o=ḋ|ρȄB&4A8Hg³ zp8IT8Uq/Czu(cj6:t4tbmX*ʪ(܄=ʂ?=4d 8X!2}U={ 6?CX'N Fp2 B3Nم0VV8O$<8m0km> :1&*D~uz}3z2o̴$Ӏ}gyhye }m z"=/J(LÌ| oB(R9A-)(>Z%y<׃Oo>v]xVy\ 軠gSĽ#ޑq8'ї'!w,+aLZPW /Dnk#ڈ\,;Kʑr@O@?yƗ~ʡ|P!vdWo~} uuh|m%ڶ4 ֋аd:?_ 4o9l9l k m5Xi}n͐ǎS4PVY 5ڃSEƩ"#9"~QSQc*8d FUGtFo?ǃ=S({?.tаgd3\ w+AߌVQa k@̦ٔ0FR>1-1-A'AJҔPKq)j ފemg =͠}'d`˰5נ_ͣ 6VԷZŪV~`okK 4q1/ E}z ,[ncy |î`W/C==z SN_WA㼒qX>;Qe?@I)Ƥeq8eD;QXX=A'NEU??c~eoj !{\=.. Iv1lƩ+U0 YT=kO8K8c?*~!`=je%.6W苬e';A Q3{$ݛ?l*6pI_jo&RkppW׺O%_m-|ۭG_Kj>]_N}Yeser@9||\fo_8C<#:P}hڢDLsD|bX&M$q8C%Ρ,KjjzqE.9HCQsLF%?KgBCv瘝eS6U:p: _T {_~3%\`|? V C@ۮy_@V&[O_`V{+74\aP_H#+<(oY=+9QohK]Ʈ=7f zv&U/aWI hXhLĎ3;fd"/t__J_~KD;!f͏ Ϲ&^篍w_& HG&i'# iq-J]̏ v!W@? Isȝ0mN%߷^$[oh\J'5j W3_{Z mga>= PóİߖnJ2_z ^s(7Ϡen|h\Tڰ?k3<7n}y ~ݻ x>R ^1*F,D|՘Lkǘc}2'^V'gvgۍ 3FU_'R jm܄jbk׌q}CQ1 }+xOsaqGxGxG:H҂LAaA1A6CYPj dP^TT` j- j4-5-hnAK-A+6m ZJ<t{+pP_@ɠ8ttE zt0?r5dp ĿLr5D"LqvÔerMTgϽ5M4fn44ԁ5N5NލV֘֙66Ιv.vcw33~Sb:ch3LMWhך`c/TpT589'h `srF`.S\JϱF'?w:lzKWי^fl2c6se \ezeyE5ט̍9BW67:l8lC;.<֡/py50F;~z\:'՘3.92yyesۼ!hCc {]4że,e`C_~I>ahVͻ7ԂuD}jINOC9pJHy $ιyYg q` 6 \ŧ:HCC*B }ϮE$8Ԓ!i 2=T̐4 Ctd Y4ICV΄ .dcmFgȎݔ/ :rwlȩ3! +MWB.\ (cyeZ-NK1TY-,MIA,S-3 {-9YKȶ@J-:{Hױ}g3"pi;.ӟvfZnFNU;sѵ1Щ|B@'4c/ m/-_)-s}EE|⓸n?Eɯ=S$ܩϺoY &P ȷN-0c]IQDb^>ZQ)Yl] OO9ɞKe^CهYZ~!-֥2X%uI3j`kнQ¸쓨jT׀3n=qN`#.-"|8ڢ6h.o4l7ٷ{IKRwhc tMC.sl~.<㊶D?gܝ_FAh}|-yij5 iJxO3+Y3+52,s?/ٚeؓO3Hln1겷'yBFVП^8{&2#wgoWB2 % e}2a % ~ORc[8 s_, oa[ ԘQuG=α?B(#T,IO0\qXtJJl%\J%<|l[|*W8IIzqN/XŜrMb:sg g6ԏ*59YׂqZ%_wj&b`.{8 jI hL2t*F G͹/>%ε{O+h#jJVcT3њ)d#=;/?U5W1 yimIWXw9ŬPa)LXLI[ I ֋ԕ>IK(8ÌaO)w (>tkњ7疮7n2pdV |D-1 Zf ̯%K}Z lvԀZࠟǽaU n TCc ԫ 5Y5%NAW=|%J@Y-1c >&4`&0XPjr`5F`s5l;a~P 8 t1k| N+|ZWca@Z-AL[azn00'\ aH`Lab Lf|wis:,׀6ݹmvr~l}$]hWNV-S|7['|p>|i O/фNX#uZh4Zv܎F;]<@/:XtC2:HźgTקĕu2:_V3Ӛ(DNڨr=IҒ1u.6+(1w_,m,Ǔ+b'q1ƚ\c$1 į*;ciue?Lҏama]朄:İ4Tk -sMqsp4Ϊd\;9G·A?rOt9:6XqL3}E {8-9+DLIؘ-d!d@g9~1c[Xj 1+bWOϟ?Gd_/;92-? a;ꗫ<\mdX98nqsK/9Zj,NruYA=]vv6{^Q+w-=tR'ٱg{xwr㏫8S>0cı׉wV{W{8VǛ̍qe1O~Y2ؿc0q8lsV|^{=e;˯3 z#~X)qq BXX}ANGڟ/{D;Ew $HswklkdXss{캔ZZ)# LZ:XZ-#v'V4t;'q]u} _ ťZoB6JmaZv|_ݷBosJ;;T[FېjQm1D}IԖފvڢSrڂL[ vm[brmwm-ԖM%KQ˷%ԖFR[GjH"uVѷ|6ڑ@Q6Vg-i[/nQ[,j[EJ2Q$jkDQ[ P[jKCmc ]R, m iso3i'-m %GJ?mm|+?mߧ@|-ힶy]tЖwZvva]UoOms|8Zi8m ϖpM[ntmږM[i5mmִSj6M[f2eڲL۔ik2mG-ȴ혶bRLۈi0mm6_˷v1zqŖҶ'ڒY&K[cvX핶yoiճbIҖSfʷvRҶQʷboOɷxrh4њɷcrmYB+JQ򭓴]-|[$Pi#H۵i;#maD"ߪHњCi!nCVȷ⼡-| mmvG>Ghmף-z-;厞yht| mI{mmpow-ntheke4ڎF[3jsmdW}mCi+m-_͋vv.ŷm=DZz%k P7?+L2c8LOe阮7UhzfX̸PAļ@yETld^bO̟*vʜT*X?x xY$xGp&88x.8,!8:18.]uS+UjLp}jl`([_(ef@KMw;]@o ʀh d`0cXuF`6`''ݟs8 Pʁ 7Ā^\>h4Zm@g À`<0LfNtbN=Ω4t8͝VN[t82g3q;)tg3Y,3Jglq;0qI-t="%.rM?o_+9[#.N+;u5Pw; c'˽AgoA OO7w<9C^j MLm2|P;O >&^ RyVVx錥]*lm;Q"גH])a.^Jۖ503;_InZn 2:DLe&a2>%\!-y%mFڒ)PBzWPKW80 #etZǐØo"?J)C÷d }/gj:8 mw} ߟeei6i[)Mߓ2o!_A_iyR,)alwǤI:1WJ˛&}L~6o1+^>=֝!},=E^11$}˷H9Oۓ(=4]GwϏWȐ'X˘η9yH?d'w%c7dEEنѱ0a/RK[ܿOW+R~pً|ysb !iNeyH`k}S6'ߊ4!QǞ첾yd-$%auۋ\F|Iey"=Cf(e.g #XS!_u::; v ~=9xCKn݃7{o 5;x[O`i~˂G >NpeO0˂>@GG^%\3o飍vvvFo?l^drn9yǡ;pVAuD ū6W~eCc TAXFYΗ6F]3ʼEKHg#12|I9eF4I$:b"?RG(ߥ*VݼwGhioJIȊeR6_ )מt?Bzc3%k9*e^/S&S0d,=5A9JvFXk5Vs:`N;u鄽Q'?eFv`v9$N ވS RIC+S)CIY7[[ۭ۬2w x!o7{=}{7Co7ٛ==M~f{{ EϽ/%K2W^{[֨^̦&oP{2KnoD˸Isu3uy?UOHNb^/IFu Lf*}}U~ڃ|zA,LKmh)CHc]'_F%HS2BBh^w㔌#.$_$Sr*IVճHΧy I_ ]t~N"Rʗ/Sj5R*0ܲZe~PfgC{WB+pP)ZLK2pLh]hY@T ZtC|M -+aґ.#.链>M:s(t! V.5!5BQ2ԓo¡>ѯvƝ_*>?kẎ?v:\:GaUupI\h-7ٙ?eD^Fܙ'7#}[J?Yk毕Tݪhs5~UjZSR\LCefVMټ2_>Wk'z\;9XyTO/s"s?9Nc\]Kt[\9Y͸0R/Y9,U__]~U+z.gE!?N[/Oa/܀_M}Z\5\iɌUyM}_Xd~W*ӴmtU41M|0mFjc|HZױ1ʤTExBM)uR.zw{w T{x6gi7ݛZz󼟩9`o V[byP߆ԃH/ۑGy|kY?|Iu9cgC]x :Χ /B.z/|ݣ <=HzϞYBI'9#,= u:sTyr, 14TMImUgMT}T?5P Qòﻘw}BXЌ># Py:N]"iᴶ[vNGu;pg3y™Slg}Y,uV8*W]uZgy$_mgc;;ztv;Αldf]unMV껚zn۬miOt۸vu{Rw;-ipGEzݬҝNvA~39VKwA9 SF؝*wF(J|* )>yrZFɉ!G 6}n+v٭Uh6?j#&MTw [1XmR=Fg膞3a%m96 W33^2'{z7Or<@^Ͻ}zWϐr=R }Jdy^q,gop6W.Y9\(xo]ULKĞ~w6[7ћTCpZK[`__cl?N bYMXBԬjֶ2ڕ,7i9i)- ߷[<1jvq|nRsΪ5uom ;T$I6@st6R˫-,R:{NZj9i稥枣{Zjn FmSQqcW_7>n?_7w:1q)8AqBĸljs{ z;鼠.~Ε/55{_ûk]{}oQ3O͏ShfY=P-bv]̞B9R6!l{^j-9k&O=UsRS.-TO(VA[xZZ'ܖj>*|u3SOM7ssVXap>rƥ|7f6PrC֟h%i?Co+`9ZCj\1z?ٕKZNu%>ԛ}AzBp[j^}Ti ȯ}>zd[̩wθ-''~ړ!;{ˆYo m1 .b97A͐Gs2ӆ9} y\9fW׾^_\i-y\ip-Jq sMp-J[•.W/p_%WUR`sNƍp{=x${*0 n^jV[ko߶;#q{ 8 i4Zkw:9]@o pF; dpӜgܲjʝ:`#6gl; tݠsF%n}6q:TMvs{}sCaHpcz;",ww]l.w{8 v@7/6RK3ݞyun-k4ucy-Nyu]^l?o0p>*o,s7)o*0ߖ7;o>B3oUZ`CyomvH*(몼>)(`%_|ޑOJɯ4}<=j_Wwrߜn}m˯EvZpqi3mN~m;&;VotfoWWUw:p7ܪH["jVm"n:}Tίu. ֧`! F s)_L )l,㈓+X66et&Ma-#≠w{lj[7P/]X ~Ho _2K/&d&)|5c?#_@KLoXO|Brm&Crߪb# Y o{85fS'0s7UL!ðr֓aϴΕ~c1%nP5 w0_j﫼ΐ,F;S3k)D9߄h^y-%L~[iJwL_d%qd!Fh@HDŷL#Bd}z0#~krP'Ro.eA8d^|՛BMa}b{ c-590a|O8{̗oFe ?7G_Qbyޡ6G{b˒˒23s {ڱJ~:oX{r4\eyzS*)sSd;vaYd&Pߕ.M6LmyQfgG>.CGO5S塟d5swwjF=yeim5 W={yP^i*wYuOyQQO4ʳ+nE g˜m%E;gK?Ҝ9eK1S]؇p]5F[!)'h7v2 𪫲CMe_*e~S*mEdF wG@tbϨV7[+sM}lO3,oS:]:)z\WկF#Y>bkh4g}ЖXgѣhKQYꀜ,65sy'$w*W>Ew-\ F#Z5k=[Es)cZSm('&93!5ɺ9[j%:;l5r_,gr;8Od5}~|.ʜ5x^C_U/X6s6Ÿ;9x>3Ls?cl037ZV9Br6Vvk6{ʑy5gYԖDLf"^&sw]^/gL-jCQg`OHY/ jny?zUY㼭ͷݳOTy_ꭑ\8G'By:_[d%Zdl8WH9Y#㼯n8lPFeu>INlkt's=%|M|EQ dt5zRc1(3Fqc1͘i1eF9W댍fcc7GiA3f͆flkv0;̞fch1#1xsbH)ts9\h.1+5zsn21/[l93rmJ-JXux/FVS/j-0Vl٭VՋegRn5e'Zå.(K'HYL#} d\5y}%e<ԧD{1jOs_2& _7sקF,s*yZod~{?߭xNьHy>5r9e0e[]oCS2XW9YMÊ:Vnֿ j]w=acm/pg"ѝfNM4FάG_y!D};^[z Cg ]+{MW6hTN!L>< u*#Jqjj8Eo|,Ԏ:HLsU ΐ<,Z/ULz'S33*4Ow맑o9J?//t^N˹i|C TۍgwεIl;ik˙m_;LD4{Nך4$7rS8tP967) J-ЊqcUg%^;ƾOcc*U֙\R͔gSkZtV~V;U*5W*ugYuy A,'vmU{zɧA5cĩ]H)g=ȸQJΈ/ʎS7,gde˩SZe_%'?n@JD5s,pfZ,;.WR3c} ϕEqG\0Of̯fa}U;<3`'T4ŬgLIL'W=w,ӑ_},m.mu9')g]6ȹœ/*) F~?лK׺Ѻekopnk~ZzQU?E{6Gm8d"wFQfUG۫xC63SۢnmIߪFߥFߣ?^M'^HzIZzKJmOm)yBkʳ,U/ L\_7Vk+HݚB SCRC$%ߣTD; \OOJ>_mq 7\e?M%qI8Vq,\[p5)=)3:3:7L,L[H~n21/sӖ<%lڕoizJzfzW{UPVf}4]\R*pq2۲:a\/Ɠ8Ltζ5eޕV%Hg.>Sڬ}MiSH;i'56y%2-+Uׄ̄d\pH?c?1Ua23. #}j'`~/G}2k#>˕m |F.ע_9!!?a~Ч\`x#N'(zB9LC涉o|XK17ޫppлÃ@ PGãADŽǀʌh`FS9AG+G~"?<:-2 tzd:lFvD:GQ ~!ZTEo$G瀾]їݩѿGX by0% moX@F~/#H&& L [ ccK@Ɩ{ T9\b>DȊX(jsn,&@fqYc#hxgoĿzcFeƻo;WEJ{J{"!!?> t.֍ ʋ/PU*-koǷƏUkT^}eHtH ..L|M=ޚ[oޞtfb%RU$T*^~qLe?[ _nNG+O"mo|t}r=$vxɷo:$nMnݖ*kU4U"'G$&B_@TDS]S]U^[hTw* RRhwy]ߝゥ{!0ZZ2*ԆorSjB.;S>{ٚ S NsjW%L#)X\cev\1)r5,3,+)ꍫ(aUqL\. mSD KXM4Sy{AJ2%2@NW%e:YkrÚ*;'ŝSF|s帹1WJo'¹2]m{Bz(;T)bcǞ=ĦŦ& ӱc/^\>ر1ƎC U+P#n%̸ގ;8Vx^<eqTQ!GrTQ!GbO0>*> )}?(U'W+R6<)X%FTuVeK -_(_D$DPNDGQD1+_hՉM v[N]$_ÞJ\켬uO\S's3bݐٝ9o$kF&ԜDDoN g'`$z%zA~k7} V/$Ro' y'OO H Wb!7@L .mⷠKwKw@'*Y׳0׳0׳0!C q=`{!C\ q0=`B +z^O SVcwpv;x.^Naa7RS$u](Ϸҿ8 !qNCvM fѤO%9 #W}q7撑G0`(FȻ|B8#}\ 3Fgc/X_'?9.O?G34u}acbg&61.^_.7жEknF+nEmG1 77Q7M{J:wc~op,;xNJ-Pu?הh藫iT h=հb[?GRTҐA3U4F l G"c-"_Tȗ" TmK"T^H+U6Kkԗ#G:DntQGF.IKPmSG]+C$yk}d/OA&M~ތHs}Ϩ>ז`eݮvDyMAkg ϮS5ʹ>iϋ;Y_ФeSe@R2#,UKz`AIg 34GoKKMƛ?U4OY C2m7 ́́r& iCxԐ*\tHC]¾WMFK_^ _^mmmV9:zo/>RALcaek"g uA䀝r$F/b46W_Isro ,ڌneggvJ3? sDɎ*e?z?tHQ=U2ѯ0 $ɴr)s>z Sxί T."{QU8U<ä K_ԅ*&=RR +͹y3̋0R (93sH.Hb©HM";[CyTGdR(0(r]CEUUh4~tw$^^PxD2Rvʭ| o+5YR|Wы_|mk\kI4$/D%{i!4;M[+(R,JJ%grIry.;5 6'd:N-Sg,Ǫ喫%|qL㐼n|R^mfi ;o ĭ9S%*y6OXUR(?}{pd_^2IgσlQXg0Nll|;}X)8bS.53+;?{P%PI:-sJxExR5 uYJ ]ᗕ.#+W^^W^^¯_SHA7o Zz3&b S_ׇ׫@[2eZZ`fyc b^r|T)바(߄5Y@NYCRW>K:vؑ]鯮J6dmjjXIT-UH|'aukX=0gseUzjw*ï,'jGXڔLj8`BwZ5HZXQz YzxyŧòQ~u tYN#37~AiT7ACgޗӸFk"0d2̻t@hg-0nUgseF9~aBnp@y֦/Xlہ]@7`/p8;VN¤L$hl:;归}dӢ[ >rʹ_]T]ɾ  pOJ+m'l`쳸j]> TOHΆ99?(4" X lo'*wKf =ّT I%S ͂S-6OuJuMHL.j* O (*R#vFG{L&xS11obce-sҿ%fsPɷk~?i\>ǫ,>++7:諮dDuw8AM?,+'-cHFzq3)gq1)O a ;wXx" Kw7 )=y)\FɫLJZޟ.aR[Nݑ2 m;2?$t1ø S%䧑J{8JJz&@1eM)'yVM1/PI_@> IGZsȳߥf}绩$p ]j/A .u :=HL_| %D\O:4hzcB?}QkȉdJ^@}3Q҃H?&(J&3浒RiD % ?~G0>0WQlR;XG#m~͘Y( :4+}0ȣPJSE^!}o 吃glˬ+SC$.y;P &OJmD)[v"#[B]2WG2T+sh=$ӮtդIgvrI!=|#һI3usMBjJ!'-@w= (U,+-Y,,g>7H~GNJnDmOtv{G9w*:+ӤɎ^"]A5Rޝ5ej2:ء~Ƙ+AehcP.|W»cۈw:}MH-l7c9K<&c we*d]NW,KՉ}3} kю5ZO2uXDb͢8%eLFL1%äw9he͌>fY#gf*Y%r]#-.s/v>M0 /qٶy |#)ș i{ +f&frr^Ca^:OJ:{c[^OZ;|4ƞ3e)_M8iu-G.RJiͤ`W3eYW{kʱ#g;̖Ϝ/_!cɌ5R1L/[)Yw[J@@  s~Xvb%Ro}+VH̫U,\Y|Re^P2Ay^x o?"0Q"y:}e o%&6lc$Ɣct`Ơz¾(svɝ}=dXe [\ҿƞF(+>H?uۓzԝR#S/aK ݪ0#ʌ33>fJpk̳69Sʮ1 s0]R FEpaX#?|ל'z\n:M[N#?S1ζ:;?{s:' @~ @} M@s܋*6! &S,`XXJ` -v { p8p H>E@#)׺б к;^p - *(|]Thhmц(/VtH#An0`|]lPXl\t<8w``}S+#;X w0( |4䣃O&A>טCÉ|E :w3 ؉8"peX{N p2!Op| DK ]@otn))H/<:<&3s .9_-2h օ7~sx[x'>D0'>b7jI~q؍4@"6z_5Lh;[iH}H[Й|iax_5q3绹Xl0`b` #">DGVk1M.`oSp8;r8"iqX@&H:@aaԃu-ZE@]~/DDVF?FzHwIˆHkeY-$F= o>Ѻ/(n|GE;]@//t݁^@_?GEFNFgDgGGK+k ѷ[;Gc*H,IWP/ 8,2&>)5#;V+c{l*?{1u3Bb$MTV&I$$ZYk%;Y;J$;YI+IJ$YIfys\3cfly=|9lO{^l/KګnfCQDTECuYhN}#߅ў>Ѽ GsNۅw;}`t&ứ/ޝu\yuzFYN@ɇkϏ.";):hKSEхs8}2]Mv3)uT.S"{R>=Ď$k k1n^\#f*=w 6BBw^ - WZ֞f *XGYKiNfm~Z݇/x}EsH|U\Dof6tGSPjk8}_t# .kƉp/8]Ѐ8Sk: -5!tV1kbG5tC FxB\ûob]qSд{ZnhųEިY<;Ϥ&JnQ?GقZPp`c{+Q뾊Xx˅ -Epwj}gz =ӋY_}ދ=<ƀ;Ʈ(zB$EcH^\4[$a'/ѰA<8ŃCiPң{|2@#CiלҴ`V(g'XΏ{5A+UWb)C3c軷͈{%`KYcvwӌkj%ZtLst^ &%ڴcu%kV͒o_"/w)LRNӼw? '_o,"ODm!Ʋ{J<[^~hjm o)+kլOuS+#_VRᚕgU,\y{yRjے:ljuxcͪ2c{}Oǀ+WR?ʮ'ȑr$XyP US٪jU;IuWFjWsBLRfCUqmtƺn;.zYz^W5zުw.6Be7VF[e7 cXj4qTfa62fg3c4 Ts9\d.7W-Nsy<7$2253/0$02060)0=PX( 88A; ,l N W7lLa[f``²-(vNҷP#V6v'`6a4f/ lB9 Ks#ΥAIJ?=L#VpkND R@w {R9SnSӃXXM؈(( v PhC% t '}8>a.3^ kO[pyT֙b|9ON0Ћ~ǃ ֗xI6x5,8{$}, 7ȼڅ~~mn$U^\|UHP3e.TB\977!%*D55^FrsdMd!n 5."ʓbZ##ndCsD^2=L"P"N!./~:c=$ iE%~NVik=WY,fqe*.M0EvߌS=414>MF k#lL'kiU+Bǩz !g=5Ƈ0S˸bvӾkZ5~G.8<@+?gZNdwu}ϊfe_kMw^qz@6l0} x~Zߛs # I\pc<Ə?j.م rߥ5NmgZ 1O5)g<>u.-:F#~7-ɔ>I\ }n<q=Hy;R>H2(>DMg]4N,^7sB9>~ ^+kwB.jKجA}RW7̍&a ^8$Jy_:?8?OOgCI&GV8g)C5G^u@G=Ӭ($/kK>ϸDw' Uw$eo~3.fwFKZY"|NsK(E'P !HgB$nOrrbn[ o-q>#`wقŋ4n"۸DKk]^ܮ),;KG' \+p'GX7nDrܮ(>]*xoqnD?r̾[g%xQnG"lװKenbo2D>eǸ-=D\>}2j: q[$>jӏ\_ n׺.;35A / CLD(B88bDݼ/m续ɽ+cBU爎/+xyTxב|\d^|~#fȆȻDG>|9lmvоʾmbnϵ__+ЫoAmmm_`}%:,zWtx}mf~?(O9r)In;nOGqŌ MndxM+߶!ηW3n){Tb{ tHM؁dGQ &F6lٹdbRKٴr}o7vo9m>:eӢ:䙢N~"-;Qy(]Cf|Ȯ hk 2Ig/Lhn# (s$ J[%ĵB.l"ly]Ҋeygːpf6Vs,qE{2 fHYxXںFkUAh[OjPslomx>5?=\N虌;p2^^'fpZi9Vrj;a'N0?(oN̉+Hˣ,h->[}Pqid9q)M,ˊZՊadnz[X}[mV?'V'ݩd8-N7:spntz:79MvA}̣ZJ佛])e4:)ʙ.Esޝ_vnB}7Ds~^6nd';F(v =;3agg%KzY@FQ>>Lv ||.>2/$גnY{=N0C~xd]F:KlIOIyz4qd'azB_Bv$ב[n]d=5=7#A>͌Fkokt0:j1|~xN~cL"/c1 T="ZO ЬJ*Ns8 ^'$Ǖp-u %pa-q)า 4oc>{=>d9Ce J5sôY|gH9B3{ , 0w6#bB%M56h[{[\..jbFWh*#v'F4)}%93Od4'U!F\TwbV֪L%G!L ĺO;weu*kD6l B0ڷ텡:V`T&lw=}O=$,!b"a24$|AUWiL+l#f8{y&fGvكoN b~\ӈf5zjlӚDU֝bl =^:k¬{YZ _쇉|v[NTGW3.ywF՞Z>k1 l9ɟN'ɟ$Z8/wŌz7DͬЪJ+JfGv!%j)Qߙ&@I4֤~5TbI:߭C,H9Z hAӝ`w2;kٙIfld!v6bL;&h|_HYQ9HP=7Ah 0r($L)'_bPJ_2X;ʍaH}8gя(G0B +mvGo8QYR4'Ә |g÷.@Ӡ.|K G~a5xz]Y<g@1Rdi!'V4'ع @h]ⶌlaYી h9Zʄ*u^'(xwW;isw::h1* i1Ɠ%J@.n\܈ջAr͊eQ؛0UUU V#lF&ifLbl%6pP˔$K8C}Hꛗȍ򀪩NjUj:F#4Xoetl#0㌉c:h\L'< >L3>hORf,h|i(/_'a. ,[ 6S.H#ݰKՓRNѓɷ_K*]P#ߞFG-arw`}t7htѭ&_/ccu+1k+at]Hi1H[VGFTG7h& R@m%_WU+ C (ަfYPt>5K(PjcD!dmzR՛ތWU(D1z.6Uu jL)4cUc-ԫ߁r\eW\Wʽl(z1r/L/FTɗ IsKsU.fi4I5N#l)i<)|x;cِg>uy:AS/~=q*[=hcHm$Ҥ9hBe_tw6c ߛ{ 1p/4[Sn[M'3ghˠ;Ux+̱` /^`c=X/[ ͼp?qY1A$Bx'7^i^ $qoݸ&DȃWoc99 |΍_62m|C%5{S8kIm yXg*E|1AoWM''2̮LW>^p$އX,Kfk( ͮnOqI3Y^~kJ@jJȎ$;8aF28;3ag g"L.d1OLYMr\#wȣ*4R"V[κCFbá1FY2cTf}bN0g+4SNs's<0JKb+ɇӚCIV s97$bn7|1b57=|,D̥f[Jbmg9l䛅IfPf9 g98sF3;fϘ|#1G%0fs`̾ ̘M>=c1C?c!nxc1A3c!asu7mɘg5m6f#6c!4#l7.]}a1QC51(.OҼ稱Lj= =C>YzqPo$gg;Puؠgyh4͠z\JpYHsJp]Ƙ\L =cLJ`gNkJ] N>׹G1:C3<ߤ^Z94\0gګ@[B>)|Mblv$1~j}s '=^gJbg\ZfkkI>FefKZQ L'.K*NV]Z51ϐO#M.t\&3h:]N׎3`&GiCH!~2WUd_z% ˎjHCϐFyT6W[dsY =C>mzM$&ƪYb)ǥ(qhG('X0JBbAЬ;9US=#$02J,1rii7u)zO$Mk,^WD?ДԒ)_ZT}WLD}y ˧׍zpy'Oh~qcv+~JJFŜ32>->ܷP~^7ωffVetm/P|֕TqgF:9hI(d6^}@$=7~k;;tdGi.b΂Nv0N- 4dGcg:F RBfhT%7l^VCbWml.PgF'HWg6d=Q` tΆRN#csu75|l a`t}Փ|&vl M׮j =/z2}Ō!JB* ~CEɃ)Fe tF-j*~j䳲Pm, izqtst!99W%Ɏ('6dɉ4DaW94tK0\0iT%Cct\3XwJb=4 mQ0 ꅗ (VoBe}LA%qd-oa}L|#c,C#=NJTi&=r={Wpoees7) KEۑΣލ.\ȋs6]|݋|W([bTFvy/E_yop)wO wN*}*2QJWE V!HjRW{Yvssg`Z{v'w;RI2S`Y%]L& z|s<,y(K!o ʥs3ө>pEb=w%7fǬ"#.jaճ[Vƺjk]fu~lu[[[b:unYwYyvv{}=-{/#:nAw>WXx's9SaV"[~BPhg\ZwTj>k,m֯it +ߚiHYF"[#-r,Mx[]>nhmc7Yv3<ej}c4{=~^i^efcK3e\>+3:ft͸.܌7eܜqkmL)3u eȬY;3=N{=^ϱOcDcY3}ŕ))N#z rRGd zǺGH䧐"?~ ޴G?G;/%ߧNk[^ku0RdY+?V.q'gTuj­Զ)}n[OٶL~iFy閴SJ64 9'΁$a2jDh0"ӢQ+fDhn(چh^ZivI(]pڔ֯ݧ*L?RSO)2io=`7ηNqLX0D+;EU^_|Z:i2ǚg=kͷX/X/c^u:bH;#C"C#wE"FFFy:2'3yg1ڽY`؟E#q$f09%v[2bl h00<[{;E{ a5'b9Lb3,㮌3gܛPƸ?Ϙ19c*l3ڙz3lyv湙dafvEf^1N?μ.^7gμ5̡2˼1J"I4^nDdcԼ&ReD,om}/ms9c"cXoKQӟEu~(+"ɀV&u㿒SіFhBkIʹ~1~ @:{/qɽ)'嗤MY?txl$^D\')昬KW+j !fOuR(" Csż&z닶uY>D/Lx1ϝQ~J,F@ѝ'-OgggXUR{yqf +ʤb2ɩ_Fp:#i|*ꔓϋS $Yi+h=?F) eP{4v̉[J^*v !zT~&"^{"#L;E*Hz;/F}sj/^N //c_̽.͑F?\Zq~F[nߌ~;2 23+uc/̧3\JG&s[x9ӺF/Wki|yyY,Eb\p,dvh̸5cjJW2+huouj;[F pe>M!ɕj9fg PDP} yu{lQiL1o2NUhbeo-'S2ʟdOzr?Anċr:e&,(SOr_$MHI/)%'OEy *P6-5Z{Z;ֈ[NהK)6k{ꙸW:Go {N7qϴ=Ꙋq?}@KJBO^Sfyл.r9V~J Km$rjSʗARqۤt$s4mٻ:e^Am܊ ]wZq j JVESӹV+}ZT甭tqi~sNqוl1C6.wNyW 1jY.Z;vv h^ASl#vD2o~b,b//o)]x,#QBe|G _eom++;G'>1V=\Sx_DImtQ9Q=_g^2(3x=vO1&mT֯"+~Sx^"Td<@&Мws*ɨj^)G BZʙ9l8e"y!/C4wT2U|[8 5-gN3/|ya9 sJG*#|m#~OuNȩ%"ŏX(~D qs9 Fi,z:9狛 >΅Nkqs#~u.KJ*1ȹ$t~,B[4&Vin Ja k,X؍b7n=vGl`ؐذX^lx؈QxybG< w3#KK8R1QxzR{UWEȭUa]=j@IQɖ_}UDC\hu!V:òwuK`sH` H`KH`+HN.n\HM!!AC@BC@A C'@53|;Rܖ+p~ )OM{N5`)xU;ZJ/d&ɩr.9f]$r|Yr=oVrշ">BT8.UNP1jBjڨvrRuV ߾@a* /% OfLǗW&|U@sU-V#}*$WGM*Op)(Cv)8sDU{D?ܧ6CrU'!St:a#|Zqv8ÕxV\\Ʉ{/\ǞG)oc_^:/~? )4nJྉiRr_~pܽg pʐ(J00 {19WGqL_L-b)nhր:l` ) g y kpf FV4~Wϼ|*GZ)kv! ^abىԢ(fd牅侄,fb 5b"]qbpN-kN$-_< ܌'x.x]B$/5{3/">p$BD:o/\q鋸0]q30`E \%48߹|J8b<-c|u| >f#k8)g=>q|8Ϝ{.ŢxOe o1LI˹o(usK%w}N~ޱkq(wDW|B8=!!-[$N J(}:@'xR*>C=d38Rd=%]ӄ.#|u2:v^")~׷B)yRNY%Tm5*l+l y0JlCb8_VZ X*p5Y6We8G*XS>9Ziu%>X|S m9^!mʘ$VUev;*V|V4'_iꉊC߉yihF]84Sut_<B9/?R&V<8Me%$U 9U Q51_RcWJS]ZS'@kI oE\thM뢔_䫠gXq.>痢PC׎)xtg';WN Es=m6qYn ^^mveUnG^vuu׹׻ -n_6߽ý)(z4m6?[hʣ0(sIy5ʯ)fY"H!ʻAgDÅqH#ZrȷV&̽Lm:DU"L4vuN"Z;SD/vZaD|wT\AdRinovo.6 'Q)wwT;)a0qGJߨoEq4λQɛ)&SVT~.amT5P S^^MRv}̷R0hrG\^5#Jƹ$zZWU9)UXq7O3^dP _RWD=~ćDD⯓~#Ho Jk|IOcqK8UIr N)דnےxa*vzu0(zaD:q7bU1oRܚ>#sԹh%S o%-^PlߓN8 cl?4wϯR{#O&5OK'ϊjT9h'|4uqWۯt)SN+Sz|ΆxVz8x/tex*T'(飒؝I~_zJSRʣLcOu~?, BVdd_GZ#Ɛ2s#ɹy|viZi7C+7T܂㫔z:u*JPYO^~O]a!7wW [|_.ow̟+(/~jMn|HT/QeNڬ՘xx^%N2`5,3F5f-.,wȖgjjQN? c)9E~-+DMS_kY=ՙ:}_>$;+0QiQ8HL܉DQsQӝNǨ$iT)K"ݝNu_aw;MurZT_ }}RDݙLʟI--1))ẳ##sD}wė/ψ>+l99].ϻϋ&Bwh E"q{],s_t_%w)qq|qee].+(?B,?lwJ\F5]-.r_w_m5qqtooZK.zq}Ww7unv7+?W[-*n% A' I.^k ]!B&Lz&IFwH\H I2ZyYt/݋$DCJ H ) q$,+!J w;$ed% Q۝N& 0.Â|D''H2f3H2XJBJʓ }}|u|44BM 9f!nw7OOIHJKK#ݯI>j֫w+pt"^&jo &ٰYìA !TxPx5wi{:?E4Pa~$<^D$Q?ph' .ZZԵK,/KY.%kz5732[|'y/5YMS{ߺTqr5ߤLGy(qoW]Hg:[랺<=RV8=L=[ wY襖.|9]lFM#ld,X4-`t2~ySKZv@b 7Jm1< cϓs".5VR9ci76[n3exb3G|z.> 2^Y. |pyAmq5\_ % >x} .ۑC yP >#ʨ%b ~*9 Mi["i {5qpأ~~\W?CU9p!8,aQ ]|1'}u `wa\-\fq@{"MEnx_J҈/E@pFq' ']R"yVdxeHHϿ'߀S嵳hiۨm9ۉvj}=JO2!e+;RxҖ]~j7M)m96ɖq}{kdV}F@6ƦW> M|Z"F6K % ov$C[^7/$ni8$bc~b:4.|"Y4QͲdβ!{~r&GQr 'i2_ȹr\,Bgr,}<*OJ te+W5RYQ*[2$QuQUO4S'7OiLЙCVWUFqȋOV5-M- Rt#jv/tQZɚ_7H~=_ |1׀H# OǁZx!Bࣁ/a$ :|O| OۇFe(A}x#мԇ |_ދj+f˙43Lȟ$f=}`yGR?<3QN1<|sR(퍘$nv#XxsHy!Յ%f3.u2\.7OV5YD,ӹ 8ᕗf7ÀXIa>@!PNP](٫C7sFƀaf/ݡlwk<٪h`h[X Wb(_VWꈲ3 f%/o(o.O7 &Y${El19$=K$_W| T=4$;u~sᙘߜLֱ 7uYs?_aup+}·TH_9ĜUOITM_Q-٫n^sĥ'Ҫ{ 4_L= qA.L2*ƓWcY[. F4뒜@#ЏA>Wf~JDG xu"oh^F\u^4ZFN/ )L}gg/I}gǬ7n yǢKK$[jqk=Vo4@{Zڛ`gIy4xj1*jzK+)?Cr)\&y`m ܱkx^0vcz3%ǧS/ݡ2w׫W1P:Mў$SXJ6̐C:wLA9TX#U]c;BYwYDH4rF:Fz"=#=E-{j"v=7QAAblPpbS#^{I).XێoE6Vl'ZD8^jLY=>~zJyD$ߡ/q!9Az=Fs' 1G,!83Dq1Fl 4')lf/ ,*m;a SA*TkZZgn}$j["/AM^"n_Ҋ"⯁ y'DW?9&jA;(zv=QǾžEԵe?`? "¶n\Dm\~~AehHUUQ~~[Zg 77.%Kr7F3:ǰGѭѭg~-iif#x]uoθ} dC)O7gl&ψ:f+™e^G𷙿fәO`s\u?$::Fur.&gnv6g ou6gvg;uE"=a;{,ssqv[ĺǺX,7+2c=b=DXXObDzO+~~alPlź3v'J]=ؽ}aHtD;dO{iA!I*S-_i?Ҿ~@v'=.͋hAтBė;ŝ-3cėsɉ/#%AE{[KskHx8ISoT"DR?F0k8kiOnmdaa¢-W M1y k?c㽔tiԵ?NohIqz-lr}"3_9_8_P:_9_O矢||#-)r;T21SԊc!ajj %jjjHNbXDԎEcQrɌep̉9Tbɥacg:&&"Zϣի!Be떸|/T'./Em (qxI|ƠP Uͪbx<D8pD5ʬb%(F5Jn&JQJPrkk*U(K*H0Ne E!?=!GGߛ=fgrjZ,9(A|+v!A6:K\7})|AI5dpĸݶ _e_ċdd(E-Բ`R ƸD? aĻetq8M,w%Tu];E.L܋h0eΘY*.!WJ(J4PBB THRJDL9U=~a:_|ο``1Ki:ӹK~q`x(*>($>\i'~4~V/B9՚ZtLŠ8Q?]*A]/ "I\=dN~it%0_, ~eKlz (Җ:aˌvM g z`4LhOfƉs'g4 [ۈ9#)P{N i±c5vs8C9s r !>@! >!<˄V#B90 {3`ϥIpqq]}\#A䖅 U> { o jc9~}ztP;!c "q&3zb0^?|Ͻ< _9n }ζ/ CD7 ܮ6i-Q@m`?c̷ qsy `Y>rʹ+@urƶA˩opQD8MykvމW1iZ5F|Q4GѪ .2gOůݟtG0?=yW~tr>;X*ȷhs:_Au0gU8Sڣ9^wN~p}yyW1p3`qЗ/ "(b.B{FxāL ; _0B(*+@ P$мz-F"1"hG4xi<)cn"LɛhvE)x| t#1LBhRGкU(w&rChv4@!4B2j*~Xb$lU_ƌ5@7$moth@|? N" @-4ta>G.:ۖO ?( J*/?fœVHEИs&-cGģt4=eNG2PċwD`*x"fDӆhH-; M7ih<36gI9yǤT[hk7pO \mM|m~LlwJ c뚈% ;|=-q=d!&9;߷W?;"%A4tR# =&Ʒ&a@ 1`1mc5Jl%R@g Bz:ZT yyr$KB~[]ˈ"TE [:xc 3@&r{wZBKq,& _mH,"%".rI7.%8 u]O|e-߿˯#Fw^w ^w W>" 畡t9#GYWC)]SJ:aQt-(^saEFVp2L%űՖߋ< l8%vt}ҥAuzCd:mO6q̌v(zٰgXDIS@Q?VW@*[ViWo;w*@B0@KtK\8Da5f}=nf UcsgVc;Ħ7\*j3eHD"C8;d!o  QԾ2԰`q1q(O'uKO4:D˯P+@KBy.ct֥0M dz։_J|%huLweyFȠ2Jaivc$w Vzj0$mkḑQ򜓪vKߵSztsˡWS[w1lvտ7:AҎu"Xu/|hMe8xkֆt>~B>yhPTi#iޯ6TK`~Y3:n"f21;;G]5:g5da/QHO&+@,):+9*.wVU$+Ȫ(,#˺(ܝd%Ew@}חf3K(b|[Ϋ|P- ,ǰȯBʲ @+>Q:?|g-GP!a 팥a@[UzӬAF'nVܿ>z/=# U$uQ>.)U Ҥ䍾%mb<Ҝʫ fs_gAZqzr op6{@p}]|jrS6Xj f h cJ!G%DO_km:MOFy'JYl^bɣw09rGڨ@}`.s[̩ Ҽ"2=N?USqR:3JdS?DOPdǖ49c=E<( .HZ - qVXa1֛_/@≮EAdK ]9sV,]XMqq!;(֤BI;G[C  :q=҅;g'#-}wU$gư`tJZi8)c-9Z6*Yg$b8x yOc`glP(#ϕ o] 묌ewXp=5={;fW t{oD JcL4I;,s1ٺ z׫s'T!l"FRgSDhtpA٢&w)IoޟUg!ΒYG%yoSheokػT2-"bۘWزNWrqqG}ꩰ`\y 5k~S>$sf$&/갮ǺԳT UU[QI5:sL©3zț&^Ʒ*o[%B%ٹ젳[yK*,O>a`iUFַ7Rtn;_$"IN-_f$qu9+n>?q8د݀ߢ4!%Ά`BOeh!cav`ہm|g;snxi[K߬3;SFƒ356)twY]Lpݜύ-G-djJ0?Hkpql.!}vv醶Z9ֳC3u/uqU :&{WggHvVf+T^Hc0 XQjO+>K:t7`]WJ<ۺRIbjaQ!-ia2Lㅔ,qeq"/a#cFͫ$|Eԁ(Gk-<+$鹄ACT w kKlEwBV7xx~ WqŒ{V3ވ\&itUtwW_ykl=k?:)|{_kadATWw?9*PB}F"V&VCٴZF{Z##>A2 GRLv?*Zpbe݌-Qfmgl;cyų818~& 8Z_ )N"'yOuhEhݴqC^fgFKx»ެN@&fn3gA&sg> OG:/ OIBa@nY.ȼ(N.~rT 1R' F8}p.t 3>pJNo>Gc02;2GnC(s.RV'h fvsQCT԰J/Đc*a nv5jc9c;j~ jrG_.ɾW mՠ!ŽSqԈF1}'s,GZ]jvkOorJ0pXsO2xpF۷R\UYVg-$ k2n::3k}+of=^]Ala@` 1I #u#ᵈbF,e_r̤\.$"&wdowy 1uN5d!&bwx; FC6LwS@pX7wegWGt*+8@쓜KW92"I/_ A< }qD&!E"U$&/ާqvW."+\-׊Jlh6tlr RvRƜ d%hi"i4ImStئ"A̟Mk>ʤG=+:yhvrmjjN2sS|xo`n4[ϝkǬlh07=t?bnY$SeߤѬ~vsc'.,/ĥy 5{ n4dݗ7֤Vΐ!)nk~D=4VК\̈4LR endstream endobj 11 0 obj << /BaseFont /CIDFont+F1 /DescendantFonts [ << /BaseFont /CIDFont+F1 /CIDSystemInfo << /Ordering 4 0 R /Registry 5 0 R /Supplement 0 >> /CIDToGIDMap /Identity /FontDescriptor << /Ascent 952 /CapHeight 631 /Descent -268 /Flags 6 /FontBBox 6 0 R /FontFile2 8 0 R /FontName /CIDFont+F1 /ItalicAngle 0 /StemV 7 0 R /Type /FontDescriptor >> /Subtype /CIDFontType2 /Type /Font /W 9 0 R >> ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 10 0 R /Type /Font >> endobj 12 0 obj (Identity) endobj 13 0 obj (Adobe) endobj 16 0 obj << /Filter /FlateDecode /Length 118101 /Length1 293832 /Type /Stream >> stream x|?>[nnz?]:Nҩ׳"7aƶd[`iI(8Y6 %B Z>ܩ$_>4ݝ3yoڞ2f6,؅FfrD5/FHnwg?fp̗c,L3Qϼ ->grgƆOmߔ@[8f:V8?s;XG_:ؖf_,l:Ʈ:=pb>%/-G5ܿ[B#7A\5: V 1\&0+YP{o96ERP:~z bUǜX2/Z2;NwTӂN;cӧO2yR[kKsSc ښʊҒDըjDeh 5=}>6oi灠inb/Y.{bZ;Z_sjg3۷_ҧE }ѳ޵B{ }1?6i>YPlA<:'"|8D&^#\>B oNrA (u8ll e{13/m[KAxO^hƞBY}Hdڸ {1w%kz<gvJY2PLDcq{w .?JؗDŨ ף<j޹xI۱sKu]K~m_-xWz 6&u61n9{:iӅ&WC%bW{:)&dRQ ؂ub%kI쓏(K Cu"9nHj\aDG*T0SڱIcZd 9䘝-(&=h(F \z4OJMmô;i93R2k/'w} z"`S̑et,ݶnF{fnDž3" h4j\_ ] vi{අR6v/e[ovHuѹq~M&ͪSO]:}otZ<)zbw}unBif\J8BhJ Ial $L !%a)) ;`umg1fϖeۻpBf`%Q}ڽͩo}_px p8A) Iۻ@:""=: j٧ ]3ܷmQy`.lO%(2%@&)Gȴx om]}]1]8kPNʔ]"oBW`P@N[xX!/CԢnPEf]*8HH/D6+yDYLP) }8Z]R仺H奻K2 >%(4 @juK8铸h3h\i$`k(_ !lf9LI([3Ywpy~l`"lԵ}l@X~|l(o.K.! fQo/~[:k oK` GKCMEI &7 Au';%&W Be  ")  '??> ?"C_3?o*jڶi&4DipgRݴe}dž=;3z{4$ڷ>i‰&iu-k:YӱeU_WQ Ӂ<_ 3 `fith hҎe{v,/ݳcQ|aGOcA|^=:N阻gNGWc6?%>cϬ3LhO S:&oh1j7u42nk]\Xes^|Yq}ϵ3ɗmavM.Z6=VMGRtoXUGkvhnܧa5 4 j4}?W35jF66i$X'J񢦔7թU T-** E* tJ"_Gb(EKȁ)y;2DQ;Ѭؤ~pƤ>}ԥ};5}Nwi3s/E]յ'3ex+DI}Ι̭:&mשt=$߰iC,۰7!7|nڈc6n@8&6lZ M1|=~_!wu>ߌPz׈kyh=D?EDԍ.B?їЉyDPQs d<8dAh ~B="dYpȠ~ذ@ENR^-3:<]K=} r|ߜ/}˨,FkB֣,t6ڊAmt|_DWjt څEuzt݄¯X$܊B?@?D?Bvt @[J'f 8rݏ?@BGУ1dIx=3Yzdgv~~T²i_KeWЯЫ7w &zd]g)=zbނwcr6zҽ)O=H:2"I{H> }2/씓RS)'܋bpT>T1UBUQzjh% rq-pM g]aXZ^wa}h?K1(uZȵWѼI&sS0'U`yR<7~2?B 3oJT~$q"} P oCޗ//K0~yRp}p C{4ƘĽ18W7\ϓwп1?g+|5} W8t%Di4ZhAiyڤ!eDG(b)$G BH) U4qr)DJ9^* JUCfbaMIa6ʎsμE.wHA,M9Ͽ *JPIr < Kz-UG!$H0` OUCjA**PK׹HejJ'T_MCh>Z!{~(y :xT:)|sY^=A+Q+>D#7g`Lwө <= սx!*WkzX_QHR:NF5鲲d.)~|.eE.ZJJRJ2o}4 Ч{٭)oW&#zљ*vNγeƥ XZyrUhbktH2ن/atSܙ)w{*1ԉQs9<GnVxVhM**|ċ'`WʌȇB聇&us_Pm)R ~Ň%Ք3Y(oDrhQYG$fW$Ђb ~Jt%$ܪ=((t@oU`AaWЌÌW3PiEc]"@y"ODQ0U!~:ma $cZ5Ŧ,~vΤd̑sŃ_2JҔLWmxC Du ȎOa? I3$%9(.Uu*_Ң03yUF=bVGL2/h Xt򝡰lZ~fpEPvmy\DmѼQ*7Fj:DBLH ' ]$dtq&\ɔgp23B*Թ53K'XSڧT&_ ;*sX^P]{bUW;P5iꤪ@˶Esڊ v6UX% *&Ϳr=z\ō`y"| snXUL@g}(KE?!%}(*>Xm%0}$R%wF@wsC2^QG%_': )KK1bf 5 .%&H3g#U-p(+ jFǔ sTeT\n6;G~Y@iUԫ\Vr\?]IfjO6/?yfL>dU"$az W%đ~'ee#^_qO%ڐ /C}?|xYQ]Wd)WzBxLaԕTF4Th/jjQ'9cnԂ`4ǙhG̋lX}2 0ʔ7DVNE+޾.Iơ T le95#$/b)xsi傶Α/Zшr3L\p wjѥ}u>5 F=fhŲXi{0k4Bdh=MuV1<QMJ8]jѢNS[&򥣛̀f=,zp]^Iǁy2l2Zi^빫6='{/SY^5oʋWt>*otU:&(Xx͖U#=s/t0Bn)&I *ʊʖRH9ى.6aUZa:Q["K̶Y [d>3Q/ũF 5<\Lof8zu^gjPcʏjj;.eQ~%k6b2v@HnLHS/(Lدf5[ .ƒePz(Εt:9-S"V:b>?u/uAќZJ{qLG4/G=iNjuّ s&M'O R u *3?df'P |Ұ$ec-JakHLɰd٫w>y]k*5z w*Nl*R>qbh.ZZ4ثvϿ߹.SJə+V-M)L[j%5ֿA(MӔca bP0\,VO?Sz-.FV BNac|mKѠ}aJ#(sWҕisM& _YN?6$r'tN $A7eWhW!E R >QܬRPD^4@!Yܢ0"*s:yt!i\yi`ZQZ&fb6g0J3c?r@37.UB ++s #3-SA)fhW朷4}4eƲ%&&Qޠѡw49Vu/:蟃 hѿMiA!]DwǼ $0({y0G̀3aLiء)D jx&-nhXiڲgIIrFͩk̷Θ?t†D'YUmF?xSXX:w^9\(O44Op~h5갾:g '>TR"ܨ0Xqj}РPLj;A*!iq()'%4#W¨(}d0T軿Rx[)R ]bY9:m1h@FQUEnm7plddڻ:rkPjP3c> ݳ:KXYs#[l3;U[hZS[{YZRtOa*Wլ:zI6j~Ǡh^H ` jT]TZZ֛ۋX/ (U֢`«=ZCq+%u`o"01$m2dgG+<#d? eGߗ[栒FsB+ٜ_+qj%yyE:AQt#WdJMÄTJHf1e)~S8ϕ_ʿ>j|4W5%#La"Qo4c%LCKa+[_HyQcߪdmeypXsv:<#hE7}4|051XAJ?F5@8#3D ۠Iho0fSQJݫT*,vnj]jOhDrtg3!ЛL3 ە% *F]qht 3KA2#pF65T]1-Lڠ)w б`2 TEU CQj)fbW|0+*#Rxf]ȻO͖+O=1.ce5s4sVUN[rXk-Ĺ"9<4ֈ;i5jIawx+C ΚXV5ַ2FHRVع'*͵=*)sWQ Z3v ]aʏdH"6TdHMۗ N2T^xPF-G--V(&'Z[X<&bW6O.V sTN*CK7/LNZyfouLɫN,η{{啕f))ǢϴW6+)ֺ2D ˻>eJ NW'>r TW]yN_xa2|b{n7yԴzՔɲ`<_g‡H I2ꤵ0}p2Џڗ!#1A`<+$CgT9.IdH4Y c(Kp22NRkyC}Ǣv^ lKa0+,0z,i4\`W0 6y}WE,TZg:1Ĵ^VLV8y>Q+cmn^ۖdg.*=boa~y7O5m1Ax`25Z^03ڎ!.\R#rS8Aq`Dp.:tj!ng.($q12e2#:dg0LuMikLhod/s]xʽ9N #,Ayp&V JP ˜sk4+c:{E\k/f{4őo׊ʤ~`U}JSҋ-zIpNCz&*F AXV:ZxEjΪ++ el**գcJ B+&Ec1&T 9.!hÀY- 1BC)?ʲ -C5YBLoֲ8$RQ $ؖpsUnv -}6E1֥*,j<=-{&8-q\;@P]mkJfo&v+".+kyyiz~yͩ,;k^bJ:*5gme[y;Ͻ`V8ki.-fY g,F *Q8ww_6uE *=.6>rf+{OͣT3]c&_|+`BEB8LcK`p|ƪP<#Yx[ @t#X *AӨbKN%iv,]-Ӫ+HR+Z]'qsϞV:MpjJ0uԳɘ>VDjAːaS9tt Y8:傮V0.?LYRCUy:L&"Jf唎ma3Eql#;.4 Y`Rh;iYlK[A2hiMVL:}y7"!ߺkZ+CTT_MkE$ f9aj`2|Q)xkȗHkEvhwN\hXS,M]!4~֢0r_;2ɫ*Ar80<6F]v/Ugx+XSĭ#%{=}gSspO-qԒ.L= ߿8-'3rBb%h״HAN MV kuͺoo5Y쨯IQn4 e29|NP|_,8OYqrbV}:,RR͑ԊjJ&rl@~shâa4/&mE[MvٓD\o>A%Q3笩Cr^cty\C[BU%6-X0O_=ʗU.p2AxeJ&ݢNES;!Ft/Zr)S7M:o`^ݧ77`^4E ^y Ew!??^a1S}s³}ʙmTk[9h󕾹S*!'/%F8KAS$Q-ۋBWEUm<ܷd oBGNi.J9Y />Zt\eNܵiA].,Deqӧ,Ud |~G#Vem@Ke08hCh vGѰƄiiLjGЮ) =V^sătM$̼dP4,?VqܕM kk!kmOSV~">1JDC҉Ї~C[tKmaP ȱG(rT= _҅k]+z} WS{)-7<_-͝o ^[Wg,DRrQ؝-)U%bUXT,/ PRb"Z=ѪomK+G(,l)#(yrXȲV7 Ť1};Ili;l9 sWUNY>>sNs{"}뮽lY6WeU&91s*TryT:AݓQ*i1N g~50bDF5@-HiSm],Vː Gܯť1vWx̨X^TvoJSjHKk]0q#>Pyc]mzXK 2?hфMG>Ubzw`kSd6M+u6/N,SE+;tw{IVL$Y// ;'UeqgxJ-WJ۫5CaemKJ Uo\;7>e旨v\VSV˳BS&457ԛ'{G݊Iݴq3f4l3ǀzy(HF<祜X_u΍gv<̝y\q&<fl\ѰܖNMb~eDBH'u'<$wɞmFV^S7lltǝOQ5of;e{3$l]2vphɨn.#Np#۟۲G)n)oIW3w˴*2rɡ@zӛeng.0χٽIU\?=Ytl=Acg߁yg9}*U0<(&Ms"{hx{'K"3o5 e~CL99jr)5w,/dӅOf5mv9dٷ5񮞛'.Ʀ)leKcorUQ=;o)d)t<8%`y,ik2:3}޷s ^*- 㴵skߨxIo/?GiCTІ[N4-lbڝR7~AaJ! ѬDȶ-h)68ZrXv #GiVn&YW9N$=laI#NJsNF{[c2Ϡjz6+RZE~BVjqS)C˓6rXTz˥tKk-CahёSOalʡqp^MN97;\O_klfk8G]26l0Pi4 2JP>ӻP%__?L߉ʃ٫5F )d+`d_T[6Y^B H?&9a}]jqaUWY*S3|8f!k2C? b@Ov v-ܠpF}N%LnԀdM(yNI<cGdgₓZRvZ) !SSW^X)hʤCG~3d v`SYR;U:ZL}Ze*1P-sNo4DDwZQwR!I?6s5}Kudd/FbHfefq%DVb%VZ.hªOf4TXi4*`n=SDVc9F͑nX촠yG?”\ UϏ?6@.eq9>^׺>Ffh6f(M\N&cLf?F?=V4M1Ɠl[Y<-gq/O2ѪŃrFj̾ʩy9khL5ڽD?F)Ab~_l}Jl2(]M;J#~cZN^eP* [\n B% k|emJj[(ErF!ΠV48sF]u e[K8% 2ԑR PNY, w'x*Vt,IhH|;}C*ǽk5\^ISL* 'Ji54TCCۗr⸟207jD#s:|vJ < ll 7w,Hi>§bT:C\i\0K8rʊt:;WD)]Z,)Nus oԐ0'0u 6|ޢ`^>Jʣe! $Nk3W7Ϋv>;0VұgZK\NQiTz(&ENQULv'߮2Au5UҊD22[K\YOVj8qK2R񈜒QڜbMdޕsH w҉+4O/P^.!RZǎGGB4rA) B,Zs2((]/: JY@1a2[Hԕn-Z#J8y6SER#YZy4cT )3RhlXOJOouSYYtdzG]PEv#yqCٟ6M! xfVsQ<$ϧ+sF/wDiN.hI;9~W,LyJTZlj a@oyNr*~Β[3'줟>a@aT]a7.xS3Y`mTwӣ<7ޙDK2+_wPJKؙ0qJJ"͂`۬~Riqd[Dp#j֕Pۿ (73(`P2l!U],ɩ&rNӷ`ڡy?N<쥠IfTq*?^_f6,dW}]d_:$X|O9;VNVMn9JR!FDEj1Gnf6Jɴؚ[ZJ:E@ Qyu;lNŨjOx5e`5͗\ڹ⋟ƪhAFCu+S׷ݜ(h% fZR2AU.AsR7YbB1'cev:77_c:{3k3{LP- lyAv: XqNj 6s:-xWKeq蜮5AAcRq+kT ۍQS>v4*lc oz4R(MY]@:|ɚ\kMcJC`D辙my[4Qt,7?$kpťh6_`1zyfm(xP)Sl1exG?Buÿ#RP1ʧ^IyA|VQ{Lb뻑o`wl#w 8XV:zϑ>Q G0 D}ɪ/zZS)dZ! %}y~q,wxϦUNbJ#*ѝF;4:7Go1[)^N)!?/݂TX|+֔ǓϞF>Zrɲ8:(.!8gFx^KʎSTsxrlU7EE&*2A3SC:ȲN)g/MrYeRyNKb!F^?{&Eh}(S)3 RZ}`6A\6OQ6f8OAB˥89jGF׶ʲ*:u2J!_ z::OьfN$r 5lEf&[nT3AMey" |~~,5޽Qie}j:oz-%եj;"SF.TK:C-0&?ta6NГWIEG3,Va1>rheϟ%&}mLt&MAД z0*sucFjZi WVaWk*URTZ_WҦw+JFϞ1ծ؁jhR/ByB7)ٌtrUlkGSwFiuw`h$B"]ƙڋWUzZqK\oaoN?ZSG}əx3\T꜓e*[ ψ '4FFݺOc\<{gi%Q:*D,Y*d1_K] A jLC&dGJT.Hv8UTY÷̼73oK%<@BBO ! !T)i/$RL(eu-Ȫ+vtXԵbGEEŎ yiv'-s{=iO|㣫by\5Bd:}C @cMn' 偫omXX[vԴ_}0Cҳsݚ?F430B>ÃhH*>aoz('80A4KKft&K`4i1D)@yM"Ii$2&xCCB##3Bį L6bn;W;sUi0MG\{yB+Ff+ȸAI2uSYK_e󚜲#t%KdHypw h fH푟9ELHnMg`x;b{+fD/A1CbvʒӭqGםPe]pv$  z|5,op%n=36(G|e!r2. sr!`Q¢b&.y_'wCCÕ~Bؒ NCS݃<=xу7fi1398ʿ:{/6κ;u6l ca &H]JސpoP>_ viM+WÄl\)<Ư.)N6d7<#-C2B>[%DXa4 bu^L{}M0<5Sܢ80F$FA=e!~m00#ɑkqe⹝Ai¥{P 3/ Ur$P)b$؍>?R7B}3$,xV Gy"!Udt4(MzLd2vy&bRO5䍬UcV74'6{K+TF!ʑr<)j,IO)l^Wܸ̏lڲ g:uI]F]伅 gb9iC̤e4nº˔83ygPG=<;S8#d޺Avb|)mXڰ6"I6, x8R4v=|oJzj:,=}&'*QHZۛii>  hO&?SRҙԦ@a8$(@9 $롴jēJ`W<80H} x=֫1J:yT^HE"x?sNv.>="\Dl1dqX| 4c'7(LfK5LUg\ֺ|cSWrl@F4Ƿ^K^#g H獚86 6c!QQ}z^Xϼ#gj:4<'(1vզA$'x79-O~@PR~L?uw$xl bi Y+ßYL`.UWAԠA Fo5.R}ȴC{aCB3#2{ɝn4"(=bx|ŝw&l)-t}ߟ ,J+=(Wvty/s?Q퐾0A'ְg'򷤀 di++RxɃaOȏzuidg` Jb6YBm!W4 l!75Pvck UN4*GaFD\_r&).S2"6zp1c揊 UPoЈС4GT\iSF(.ų!a Mퟚ+SN^%?:+(6"Kd }%$Ȃi괝de^ޓl;{J}klӲ1O̐P_ea`ǡwɮ^ S mJ 'iD"i9 ^a*1;Ik73'M̜'SG*@S﴿EouhgX#Gjt8|˰=x4#bdJMro{);c%]v=SW^.3$XNizYwm-~i)|pa1:gS?7x0BYqh?@'vpȉhnw oHK%xWJ|FGY09Yi"U,6^};[`2;FoLmЉ:e)i  qmx;qkh7Ec;Eo`M{MZ#-wIʢ%pZi~B S&{'`='|x (ؼ1#M}G~JB:i=?fם@Oj]KXҝ̏ ^)WƓdA0|HؐCbL~?NSOJ)=!!pey߿c;Rw=ϥZt{7k:d{?͏b=ESG# BTѨm=|҄HvѼ{ˊ:v*P*W5?~ʴ#>[v8);CPHN.Xs(W6{#zPؐ82ek gp\'I񰐬AxG2MӼϴo8Y,0L~c3Ӫ?a 2x L iH[[슈6DBW:̈F8YۇC7T#RKzzjڦC23G?"a8HBlPPC\<> /E]i D4 y:~:EekސݽNĦR%[r&8xfaCmaCOn%:r'6G&>8$^Lr!HwKt&Ǚ]II;FHjLNjHH{gg'֙\{\{ N;J^^r#Sؠ㦝V gEhLG坩O~coDJ)&N_Xo)6ހGI/= 3IH͐4{jȠ!,ĂCB5tW+it:L'=IɮP{Ȕ8J:Jz4lQRq㦷>:>NsK$O;Nt:?Ig\7b]fFnF->LiJ+ ;Nt:Nt:Nt:Nt:NHWеAnlwDjoB}?m d%eDݾ 7wniood%y'Yz?o۾F_& LB0ۿ΍07Ř0(GO&!Dp sz9n!mf& 8r#' 8r#' 8r#' 8r<p -҂--ز [a2l9?5xu`$NAG܂m?u0 6`>0Z;I3!p#оF^/Fb9$h-FKt pL0 a&|p(rQdl&Dp ~,8i]I.;Y Y&e NUh\:l 2D.ӡN& }(ΕP5|]D.3Gl˜DGk%].;Hp._e/N=.ktz.$n ֻ.޻.d JN\|]l->[eVUl->[eVUl->[eVUvvKRI F24Ԑ HI"6Ja@$l%жTñ& Ae+CZ|d9(|0F QQBy%ۂPZ3z v9A)6$"2zocTvPVqAO cΧ %^GDkr;86^G+wù*k^hƶ$S!_G>#SpמQۛP50:Ǜa5pfpa"RStQk k=ek˺FA?1J-R3ʡ +Ԉ\lsip!E8:҄r*@TA`eQ4YhBhQl}kXja?e"Vk&T ZZsM5W̪fաzmbɱΦy[=;gܕ"xER'*A8Z-bҮh_ɂ~K. 6tPcqݧ jgۣ7uH uDnt,O̤ WbFAXP%ЄY@ 6C;zE3ΰVvi:Vu :z =P{v>?c a&$`V̚! 6$CDފ3kО$[璡uh1jJh-[<j9Tl-hu4mB=o@-.R8,9mqCځ+Ra[za_]zζ&(>^f[pڟК]9# kϰ񈖪OX>yl*k}Zx;ˑOƳ65$9iGp>Xk{f\E7ړNs^耠ĢŊ-#NDO]v옔ZWM,{PoC*܂ŲOjlb#z[G-+^gKs T, Ϧ!]Q2eX$h{ԠTˠMph1K\vWo9tc޾ڼ,9ƊeyV MX9M]Kޖl\Ů垈47'WXQb[=, !M)#^=/EʐvWkŽp]}f M-[(Aڃt'tn{X{-fӮt-ҳhD{@ECpG}aY\|jeW[b0ٖx뺻.8WzxʮNwrb9-82Jg'_@.8زHAfŭhluסx޸M~V KV6GeǐhcMu8'[>ţEdf,Ɩh-#92z̰BIF?f(8 sM"^? 9#F=qZyO1ZfB] ZYLKAaY ̦BϷf8? :9ɞi6H,Ɯ3*Ěh t7g#l! EK.@`Ni ̲B*yDȧypT-|AkTY @y*]1Bk.]FλxE_ '"犰fIc"JPVh-b'٨+)ѡ!P{Qe&>!ۮs h8k%p|-#"<сX#ڼÛ2,;;.ɛ{kW77y}Me$Wѷ[+YoiTx+V63b4oF$z :ΛR$T4y]ǩoN)T6FSHM->Fmyx k*|uM&-UV*~[khi!J_sY)ibF(À FiVWz4W{Zʛ>oc=୩[ ;Z8XklJ4{|e-&oiMަ2kEY)-=|8@!f ˽\oMmCYE,x 3S:U_-Y[}+䚥$M&omYJoE Ԛ`_0 hiiz[q14՜ ݛ끠e2/%&kLPQ J J0apHH #)5>(;$hfzgO+-͞6;`ZN7w3Eނ r`™9iE `% %E^ wljn|fO((,(TP2M9 N..)80;}f>V0mR1`ɝ;|4h΂wF~va!ʞ /M,> /ě_T raf s-T@삩ޜyxVR& &MdL,VR D3r3C&pFM˵Fvt3r;璓]c'w:}}r>?}{N/8} zZ 3p} N7w& Qᄞnd7bIRaPCo?.Nx|Ox ㇄@DopD"/$?!y4h8#+r!'Wr3]F璧%d;]G^n|I&?-mi$z~aO)_ ko=k+? ? guo? O9? 7{fԤ(Gɀ: m]?U/7+ uT?1/>_t/_'2_qIQBϥ.z CgR/ՀV@wJf}(?7z h wŀ9_7 UFTxF[U'Uk[ CDzYYڐAU{E՘1U[pصkC&9Rovp9ڠ9TɈSSo7v+-]ڵ VjV$R֞bNbǎ] ݺcrH!5XeFE"Tz@4~1 Jwy};T:%tK3U馛T9GL̨f]VO`FU֦e}o[>P:dϩPp?]iwJfD9(RRlڵҞ,B1Bb t`aeC&_\CoP ;ZwWdc9`9B SI t:'!C2ߍSV0IK[Nq233W_{}┭sSŠ2֭pjN5u>$?gEWuw(}{Mw9$(?ں¥ib", /ڱc&M+&kW:D1K 6hI'chWhjfNKZI~KU8eAp Ĵtk鋭uMk+dlDŕ$$)ӧtwhoH ݷIEO5juա ntBuk**DDewQլkXsiCEDEV+0g;+NwPe-Qv tQ-*!+ K.jU#Fq򢖼ApΨUec _:Z HƃfHAJ{L?ßjuϮ{0pIڔ)$ bUD pBL`Q:UԄ QI(D!ER씣8UPkkjr -I #tHRANR%Q!K.tw 3 Ni8XCy ]DխnG20`_:*Rg#ElKy)ei9\Ng2( cH$ &T~B5s\ fSL#>U&,ЎnKN\O"l胲-,k;~UcF>⭀sȄЏ쭆~4hFⷨ 5GS,}4xOW"|xn5# ypX  0Y/>3@V^_H~G"$&wГ.qѳQ/k}H?2Řӧ⢩^RURK'!cЗLwI,גjr= i]K9/ |E½BE@!Tz#B0NjT#0 $A2 !\<#UuF7!AߍA!|s_@27 R>B G  J5M5uUj4 LAp j+i ,GG Z65 SBx=[ލp@wALsJ @0`s NCXpRÝU@/9#v"܄&g+­w |.#܃fPcU&P ;M(^=q(C~XD#?G/_QQ$0ۃ {ԧue8Kpr5xD/jd[<1h6mv'JT&=ؽ~iҽ>bmzuzf̏ztd=N;;r{XYp;lػ/ba߷e&{rC#ǖݨo|MV>f5ܖ vv~;YyVo/<;_ v߰S+i\OM`yK!QG.u#ݏOף~{}nt{}֥>ŕidYHjr!\Cn =M})ʚ홶WZ{]/N}+_aoIvnR[ V^f)+s[;lR,/ ݹ^ߝKUXbbS[WV#vwYy;8mU۳.sL;ʗxܦv^+_j.w#VNu; =C:Cr|RIWRB+˱CV^g s[6l5KWT. R{Գ_6G}Կggt^+Sף;WݣK]{lG}=@άW*z=RNC08b.k!SFth & a\7 OoE%XO`&F<.M.|OZF&K^1Œu +.c9RJr'ڼl"O@ <ʋƵ^a\p;pz17)A};0E4^;pᎇ\` x}<>>:np%bUr5\ `Xñ%["E>Xi['4rΣcגi='ҳh3^J/%W :~C!WW+ U9\AeA,l`,\>d#?l0Zl9[NZlVg|]DgWVv-lMt$x:O'mv^ (7($ʵ4Ui||M&:\>W>fk5t74S~q%]~h1˸4n6\wg߹w~1i\5۬1k\j.AfY˃zggPlafr<d>͇̇ QQo>i>OO!̿yyhn3?$EEllSWW0uuj2w4mmnkˇ s0?2?si~j~G{ͽ|c3WW|y<Ǜߚ,{{md'??<b>an663/;͗W7ͷw͏O/7wA&naU0`m{Pw;rGTpHhK§zq KJP2>5O'}@[ TV|PEcJG t2-K]NFz6=t#3>H+mKÙt;}=êe`3YgVʪY[ֲ :v%b Ycjh]nd{#i`OWGy4~;<%nDcx/%|>~W:~f _$Q|#3?ȟ+mIR+ RQiT,͓J*|pοa-?|'O8IV7;a? ^ ?0ҹΝ?| e̟p'`*O9_)O8I.7;)3tp5O9?|צ=m>mMG6]t}bMg6]{m>ҦkM~lƦ[lmq MAm~զMW-v>XiԢOc}$.M.MҜ]fѥ]ˢK3-4E҂l~D iAf.-ԢK -^]Zo.-ҢKceצ+ƦkϒkߦoM@8A6]mt%t%t ҂]Z-#]6])6]lRmltetʴi5ڦkMXq6]mm&tMʱk7ɦ/Ϧ/ߦ/ JGF!]YBj`c'OE/KR[r/Ke`~QGc?sL_}|?oN}ǿ?V-L_f7)|<5,2j?{,Si7ʷ{?ǟ[6;tT`|Ao7=hiCxxG6O*~R<;8`w d!)*9!$7$8IcqĐl?2A"mvqr[0__a.w{aOgGˎo^wѣq(2o㷉7/0n>~ :_.GȋN<7rD~K' EӼ\J.#_ߓ+Uj}^K6FGr="7ɟ_`Gz+LJn'w.r7v. yGȣS}vO)4 i%[`,^'[6ؿ@AvEyB^%ME&w{}M>$ |FK'_ |G'?O B~%aFA(;dl#yL6-aDeUJhb15l [bz8b7]-6{ރ}>b=1>c{ %5}u5;a߲A~b+;6r9D"2' W CR^kY\/? ?18_(U[|7P-^^^^ސޔvIoIoKHJIKH='ҧg^s Ki_JZ: }#}+}'}/ ($~~~I6]ÕBe2M)R+g( DRf+su}K>~W`wY׮' ')u>O#oyL_wӽxyY*"*D. I%`_sġ~1 JAan `_|;pgc{_ ?c 0#M1Ź@$NZ2ӳI&]E&9t#,ӗH9kd\Kֲ57b[v]~Gֳ+ؕjsg7ɍOLOt}fl.ޤ b둊ľc5ʦN0,B>L{IrLf[ȋ*u/ { ;z$9!!Hg2abVLlW.b[@41.%Ċg'OĄs {]ģ}H/Cd֦$+8}>$w g^Wr5xrH?y $8<pyd9< L ACS d4v4 >r8~ry|D,xB"D !,&CT@>^A?=|޲b^4G} NN]"miۜ tBNg;jW,C^V# Btn>Z'rgR(S:fq0g(N%o!om80nu(=dY'u0jz!^Co{#iE0菴 rhǒ@a1Y9fux"m-Nv?{mfϰE{f}[vqkd >Ǔx:ų$^ȋW%/yb~9o7H8Zʫ =Mv~? ex?*y+IR V4Y.͒KRT'5KgKk uҕFN~i]zQzMzPB8pMb>fb|!":R:2:r: :Jfc^Es00)"7}D.="GaWl^j1ߦFb]U)-r`~;,"'M8&$B h,ii: :`%#V :R O| Yt2F 64}*4#z{|TOSf.€R0 p] "Ta5}!550- Nl#Ps ݘ/`ދy)rz?胘W҇0MWEf65Д0$̝j2暚"r0/7"\ |3r oAފ W;T/rr'33܉QuV30N|:j\KIyW"gd)W0QcWgb^|Z!rwa>̷UoW_S\SXF z5T LQu:JpT-|U Gj*FڸT-z!V-zzUg!g UUD9G9,@9,D9,B9P(pmIL2E,l'S_Zp\pEJꍰ5L(^S2쎂}oƊ Z? Aâ]GMUtuP3Ց(u:FSǫYu檓<5_-P'SBu:M-Rg DRgsԹt&M9౪!*ɺZjv1lA9Zr?9V?ST ԅ"T-SlBT}jXVk%Rk{OOϴ]՝뺡t=7 МjNߞgzO7][;{!ަĠ3!^#`#H2RT#024Șna3cqh4FXn0Vgsc<ƅE%ƥef9,1syq,,7+@@#[0?sٹd8>՝FL6? ); َ--dQHO1I\"s5-RRqe4/BdJb I/7^02 #ӫ12#k12݀un4r &1 [0ơϘgBEHCfr 7`c|(R`>ۺB(r+2uckM¢LfS\ ICW;4拻8'1'񼎰eṠW}m4veu,8Ϻ۹W<Φjk?}Ό9O$$$$I\%IHr%$I&J2*y*&a3ge>t|k^kZk}Y;KWpWsU/\1 >2++$ , |h? |kT E XHQU%MMz 5EU@dh;kL LCxS5 xK5 Zf2ڡB/j4ǪQ;Z缷~P'{ij:5BdҢ<3fM1Ș+ɘ}2f9^Ɯ c*c&cئj8=0p'##OA < }:0 : ;;*сg@> x\`t\`d6GcDs9ݜe3Ws1 Rr\U\5]WZZڹ:zr vpq%&faW6^%m,m}f24y;di-έ2ﴕyvw˼s;d錇e%ΟaVEϺf} \>o0[mmо#\>}wsm7xPs9>gOSL s~=9=|\˧TskfcaY /1\ ~eb`xxeWUWMx WmW}FfxFWm.ήn첟a` PHh8X5 >5 ZZZ _Ե+ENv3\]9la1xU?:cʰtcqVeBUê e]i57Z-ͭ6mVx{ aYYCCh(k`M'[Ӭ$k5>ǚo-/X—[+/kT&k ʰYax*JEI mne-w2<^}#6x[w{wgxGwWwxww/w?x_CA!Q1 ݓSIY9y%ݛ[[;}lx;.']<|XOS|sSL L33>33 >33>g2|g' >33>3ϳسԳ³ڳ޳ ѳųݳ۳ד s<"1O4,FXʈ1cbcg1bcg'TJJ*jj"B$_dH։dH֋dH֋$E$)"IllFl&l&$U$",",-""-".".4$M$;DC$;DS$;ES$DK$D.tdHvdHw"N$"IHdHdHdHdHHHD%,AIHE-C"9$C"IHrD+\$$HB"IHDrX$ErX$")IHHP$")QQ1IX$aER$"X$")IHJDR""9.")IHJErB$'DrB$'ErR$'ErJ$DrJ$e")IHER.ri"A$?HΈHl"qA1:;JAM$H4"E!C$HL"q%H,X"D[$}Z9߉𪦪jZs ګz^-q1;8,;'pʩh 8ӎt>p< v+`:pr8W4 |yS^Tv ͒w=J=[ꇤ#\'G =*HhY"BO =%LhrR?#vPC ̂Dj9mow:聍N "rDroNf91KtmP;xy'жޜ& lO`N%ѱo~L30= ;j;Wu.8ҹĜӸ{b#[MjѶTΉy컌2CVROpṅ^"sh_?{mTWoYT )df^h=Ȋ?{/m~ZaݫT[|\ k)?y~^6Qo/]hVkwGkwv|ug|+1֒94hzy qh׿wrn? _'Kg{|zWW@>~sLag)uP]TPto$Gע:ȈciO{|}_v~t~  Fΰ 3dqYq:f˪889.gFճ}=?Uo֟yaF\so֟aF\soDϓa=OF\l~Y Lqg6d -DGd9 4w g/.?r;XݜoQǩDcv5*_]DѨ<ʎh[EYoH׳_|w;aG߹;߹Y蛿O?0~yJr]*zuqag\ϺQc}O=K[[[طs*_oV_oo/÷׷ϷߗBþ_W;;+1J~/'/_rk--7O_/7gS![1y.?8|>ί|:\_1B:Vxo }|Sm|fH^:QEu3Mi %ڧjmI۪K,U֫5zC؂h;ȂC }>RO'SL}>__/WkT}[ߧgcz~ 3jF-Jhi6n3:]F/1n2$cd2 oi02FQ`'3iƚY#yc+fWG7*sDW)ds97#]1g>*tDŽ!B:T0Å>)tЧQB"tgбB:Nx>/4Q B_ЉB_:I+B' }U *t$o }Kl#] 't..,t%B?LrLBW ]%t5BNz)B7(tTn]hBw %4]n G^%l'G =*а"BKZ*ГBO -Z.j;4jBuPSK% +;wQK`֪"At3ChΓh܅kӵ7'i>K[J}v1- } 2i u[gudwuKGݫ*gRSO/^Uz5:kuQ7PoH**F덩7gPKL}&>˸=k{e\9O"]5]wQ[Gr >]QLU׫ԧ6Q TO<^yvjb5ԛ|%1K[yy)*)C'oW}Ɂ̐F`F]d43[`oSwfN#070nFH,Dz[W1qQ#BؿX؜p^Ο{!!Сb+V}u@ǺF5չyþ}|=p7DwEá#|#OŇKzYs}WKؗp.}|@?}]&Y-.-𭀮qGÝ;%C}ym~ݷ÷K~Ƿ˄~zwzwz}wj<_4ߗ=;=#B{ C}R_)$̇; =#çݱwyи##&c]ί24O~qHrXZ͏  moz7@oDƫ+A<>T Q7TMjDm"Q (]R5ڶj$ya,z3rdJSk؏kuU˝Ok-ESU5y.]ګ]TK5Zucr *NK[7zVK;d{Bzơ)4VhCkUZ[h}M6z6B (=8Q!B'.tM]}Q}DN:K<.j?ϩǝC R<…KD3?veD?\Dt$ xR2r"B#j~.4GJUjZ6 uXi ZOm6{Fm7w'CKX/'Vx|&֞Ξޞ19%]1v1c37fi11%fl|l汷v;l=|z/]G__=“Q9'G_}}q(#>jQVGyh][uP]c:TRD5UPsXgc~Ifl'7Ȏ"C'C*#ɒfI[2kǨ,—FGte/kG_^-{EyOjFy(i(͌+R#Q$WFy(*ߪ(/^k*G(kϋ*F78&Y#<5ujtԛhl͕-ݢ|Py>m6o'9do?uTT/_Ev^o60·<iQo"|g4+w.PkFGO?)gߤF jfjZV*UP{U*P%fjqZVC5Қk۴Z7_ ?9~LRm^Kvh{l@+NsUz+6=]T6_jh~_GFxvԲvExN4sDx(jϋ̒ۂ"Ht)Gq4:FP8c^.Mq4?KFLRbT'O>O͍^h#LtV?moGdn<#HlF7Dy~+nE_w׊P{"njzl yƝw<@9soR82QQb`TYQ>SqUهWM//#ѨW8{UGn(rs7uS9ݐK+mc\??]]K_koYa=??L)=/>8xEADD=??I?쟬iiz??]'z^埥 ss1<<=z~ܿؿX///O'SOez~ڿڿZֿV?n+Coo4t&&S ӿſpg3 {F?˟eF%?Lj!??l/K%FeԸ҈ˌiiqq5n\0Q-`*@qyƕ;ww4:: w2m\'p(po^q}F0qMO,`A`l0h }A"냕`qCJqcjqSZ*X#Xø9X3Xhem 5n ^ho!0и-(``]iq{Y><ܸ#"w;;N..F``W``7K{qwG53Ӹ'+mkt 3 7z >j|,3xp@pnkJ[oY=/jsғ21 eE?lJEut=\3W*9Y,9[.nqKjT/P)ժ=LyR-ڢ_QRP_dKIq2?KΖ56~&甖";Ok~ZSz֚Vk^)O~*rğ z;~,t+- 7P4y \WJi^S4n\&_]=)^qur$ʜfJfu5}-m1Ŕ(nrCFp2RnoM(nJf6RXuwmQ6ښm{{e;9!r֝͝l۱j+;68S ):2NS;̈́/vιk,e^!׮˾.:m85JQw{y]tNYuEץ8X{F3*-x[8yԮKz߭gN}Yp?g#80~Cji4dIC=dѐCR4OT}Mh6>}h`h͡~#rajXaMueXaMpئa ;<~xm>w'?B'{=9OfB P皊33/DfsbG3YR1%%oyJZ /8Mf23V9Ü5W<;]ގhMΝ#3ޏsgl)Vίe.Y . pg]UFH9k}93t^9A~QRROӹ+3YήG?H'͏<Ϊ3o^vjW5?ZcYKF2ıYijx1sr~Uե~g1bmmנyDVev?8+Edp֙tNbS+׏-dixiߗ-唳/Y!"?Xη生ʼn㈧c%bgg߫uUo|pgI6ΎAw%Q؉rv"EvE: g/@ӣJguOpsdsN?+ݳ=y^KOGK;p&rԮK3D_ù=qڍEv3ceùqWө]nqgG{fF4m&2*F*#Iv4Z~+f=v9R݋PJiTkTwuCzXMRd@=.OR^S.5UCj4ZuڠZGڭݥݥ2Z/GWA`=M$-I͠j3)Y8}m=վzSv\^aծ[h?i vz;*~HwҚwhZszGkh78nk>RA]IEOUғ7\#!c=Eowi/zHՏǴE }^%붡%nRmTҖ^ë}n ҨlTVUjjrΨk\Xd\m\ʹFsqqݸh0iN]Cg44iGSQc1Z;n1ht 턱X2K2\;m2iH-#FaLf}yys~9ќ00>;"sy>l~3f,'R5_w\>}+ o]U$W+uX::uuL*rﻎ\]\K,2er-?VU}UͪojX_[f*f}uukjݭ>)$CY)ƕ_֛֛F#-kzǚg\m-YVZf-3>>5ZZ[Zb}el}mXiVkafeZYF+6X!+ϸ*Xnet_}1x_X; {ݽܽW}})S݃ݏI x-/'ܯuO7g5sgvwc}c;ٝl|^X<>`q0m(=Fgga{{3ixvVҘN\5]F,voRC`n' Fk_h ƫ:jx$%0 &T3vIu:╭54}д]d<>`l<.3U5m{9:fv] ߁ +:lqO2{ k7V{ Ycsv5`6x̡?L}9Կ{A&9iw.q)O4vGٙģaOyX<;Γe4db^;8Ocl;Eԧs8'Df&IdfDf&IdfDf&giDZ9VNiDZ9QT@Ĕ1DL)Szv4: ǚ'QId5DM&QId5DM&QId5DM&,œx/fLX> `HG` ,k-ah_u`=H_{| 6oDf"yon] Xʰ[w}4WkfZ9Y qk^e\j:.\ Я~=i&j\`!u`=HG1Gbǃ1Cr%"z ;.{v]A 'BD!~"O?^iYhYhYhYhYhYhYe<-iOxZ2񴌧e<-iٌhٌ-;G}@j͔ B7X,Bve#׿#*#G<*ANvV1"Ct!b:DL1"Ct!b:DL1"Ct!b:DL1Җ%\qK\%{:N'Ӊt:]KOh}8O# l`v:yN! D k]b}Rfuȗt%de'ș9"gBL.q7Mՠ)N'ɣ^(ȧ"BS| O!)D>ȧ"BS3F'룶|R}z6k |RB2L)$S Z30W|q} `X ր %Xփon*+Aź/@uP\t&|6X؀+9:! vnȠ}&S?`nKj:e p%Xw\971&Ĝ2M\nb> . \ كt= .TrPuJP06c06c4Ap5h '=H.|VtVtVM"A=׉Dza=KooYyj7sz@ޡawNvyOcg)f;'v lvv`Hw {A&lp\yఝ2ރ{7vF;KxqOU\ūioY3L0O,+JSk`*xL\υebX#%zǚ.<<^Gz oi- 4VIhVIhVIhVIhVIhVIhVIhVIhVIhVIhVIhVIhVIhVIhVIhVIhVI-ڽh-b ǨJ }W*N5*Y>g"FS`꼟4Opzy)$-z"Y"}JГXM޴OUZ> `HG` ,r |VJ kZc|>? |}D_j#Yן90LPD} k`*x$gyNt@gp]=t~<z?ޠ *3x0Jc4LσDx&$ ^ |d|R ,Og`jSI$b>]bc}(P؋JP>ƉUYEjJS)}|,{90L ^SYR)}2;)unxMÛx3))))))/Sr ^N)x9/Sr ^N)x9/Sr ^N)x9/\ r^.x/x(^>x(^>x(^>x(^>DAQAdDAQAdDAQAdDAQAdDAQAdDAQx0ߟ0ۋ"^Njx1/|b>^Njx1/|b>^KEx/"TR^*KEx/"TR^*K!K!K!K!KYx) /e,R^KYx) /e,R^KYx) /e,RjJRdU/ xg^u˱n9-ǺXbR[uKn)-źXbR[uKn)-źXbR[uKn)-źXbrSuʱN9):X+kL2141}zݲ-ǹY"g,xNEe=,"-H"Ҳ+_? ? ? ? ? ? ? ? ? ? ? ? ?#r\/%r\/%r\/%r\//!r/!r/!r/!r*9w`X րr͌&gWa|d~706c06c06c06c06c06c06c06c06c06c06c06c06cF0:1at cF0:1at cF0:1a`;BeG0'JÎ2<\p.ex w>k(gfT5U-,WgzWU}x a2Wʰ^+zeX a2Wʰ^+zeX a2Wʰ^+zeX FjaeP+VU7ƘsC)v`!%<LÌ;~1&d^ y9蕃^9蕃^9蕃^9蕃^9蕃^9蕃^9蕃^9蕃^9蕃^9蕃^9蕃^9蕃^9RR_JK)~)/&sdN 9a2'L'b*&b*&b*&b*&b*&bX쮏`ߓn//YkC z1{4/qv~:sSqwcw \&豜뉹}Fu0gna] HqVRY'@WQ. <qm4ׂ"&{[<;q̳q* :HzFΏK;N^YxȢq'Id8^/bg{/US ywyo7v i /niyvjE h܅53f&<5aCXs,۰6sc<,1w r9Xmr۰\ۆaCXrX`,2\&; \rXnVj9XV;r1,u KBǰ1,PBX( b\,6,T P&:acX(We"y ˜">{4gS}+#dB" f]_bD);r0W+*e77Gv| xrzl]B=mA/ea5eT>Zn1Z.յiBLZ^ZeZews:ru=h H}CdmAN.R^Z=D")!jJR)%bb")!Rb"=gKj1k@Hե׋`.uVSvv gŭ?D!}q]D?!XgrFbh8$ Iɧa[kǕfpx9{ W|+Lp+v p +bR}7gFtz#ĉo6lT2:Z.hhIp|h:Mw{+?yzKbR .[q_:{VUfq,!.GU|\13` 8NjZo,Rep@'e@/zxz{{;N{{OL{oاT;,7Y.AK4DՈFA|%"1b~;t^fcl쐍t!CK1/Fd6!C3{ ^_t0N$^%&'WN}=60O}.ҷO׿ _пA68reJ-Sϗ蛤w b{$^Y`0niC?Y5m.e/&'d70c?ޗr>s|2縊=hHd^jTyL=d\ 0oW0_^q=P @C@c̹0ǫx5&)ݙ-_Zr7eF+͠.4hCV|yr#ququZwbwP\Ş1q ;{ uxYx~p ?dϏyఽ4.^j,aPdi_VeDq2Q̾d)첸2x98y ~ܫ09oY˽ϋdw_/ِL6$ ^|Ȟf){d}_/L${oٕ2f{ k@c\ k@3\Zޖ- ?v$^6A'Й]Ի{@7p/>;?_od}@_ Ĺgw!#{ )gKnVy_{w 9a=(s~~xS=^]L32fLI;ff 0`1ٽ^DfgKZ}u ٺl@F6,L}I$$69ɘȘɘdGd}oՃqKbV˵:0se 3rfj.f?Խ |E_=ݙLg@.TDEEWYEtYU * r)`B$!d8"$A*~=5UUO=ϧz>gj<[N0k~ayF6N[MGhW%U(*:hm46EUuh4vEMoO@Y\y7]ۗr#Rт+g\&lWVUfTqe+Ԯڭv+] [IjQ 5D6QMԦPMfYI-jP:JZOI)64egk(Ҕ%8@i%VBi(MyڢRSzڢPJ_O)PzN̕#%/U.N:6T=HY\7sV"]\9G*9x;Tw %b%LRGRGxΫ3KQz/[4$MXZ|al>EEYHK"BR5ijRҒϪh*(w;-Eh]h-:R"PDh]j5ZMVӢh]h5-ZM i*YH=h]nn ϵE3yE[uhЮc~踭Wvy:)+(r+(Bm)+t+̠˕JWu9_uBﮢ~Aa*MWa%gђrZsc%XQ޳SLOp9X50.si\Q^/i\ŵpz9=1WibyEsCbj\M]5]LkSjjXM+Ŵr1\L+Ŵp1%UŴn1[LӺX-0&[=·'_~irHe^S ƋEk(t5V$1؏܆ۆIvxZ]-}?}?OW gmTuj:xZPh*}@H0n}۪>;TpjP o7T㽾* IP/_aZBqj@5\%hjZZP TuD_W-QϯVwj}_&A}l rk9ȵlۨsNS4;:zkDcquDo[~:zީW:걉ި7:V%俷A+NǺ{Jx @}W5^K9{>uapD<h@?RFш٢wqLnO7ӟg96M9}SN_C9PN9m/irھ\F)h>JZJZJZJ:Z%BDh8UoZ_W_WQUZQ-56jZ-Bm9`Jsjm-f֌КҞZj_KFhWQKnJji-jZHWUVNҿin)zյhŃ@POۃ!wQN˩c㷿CSrz7=~W(~ԩ\?/a]#2Kb,/p1#(c`;/_ɧap| O9 @>(@kp*hڂv=N8t]@W tg'gs@op. @_p!\ nI",KR |$`%X%^}9/ VwH?NN7񢹿9hZv:p=p/_`?±EssӖ};^.q:n;C97ɧI`jȷZ@_9RNv n X484\5\5\5I /Ʉ_ {(/6R.Wr8ā&)hAy \ Eb\.`NGsO% pF`{\=Ƃ'Si x</quΛސ3r DN}k@rv,hh6@:8mA;О`{پn7ar:3͙LGs@s/r;Kxvx Ɓ)-6 VUP`5XZPցRlcV UJP A\XXXX!K|h}n}`#OyyG o d\+00dlbl,p~ ǡ}5:83_@96_ {8\;cncn,q[-e6p @D @ ,Z.`DGcGGGGGŽz>=EχCCx<ccq RN!&'ңщ+++++␈WӫpzuA+ 4hM4hMcxLhhh1 L.foE̶0 f9d^.iMryk I۬`]̫EU+6N޹VHv-BD P0 p0zm؊؊`=lFPmGӇcgF4~"eV4¶ĶĶ %};$V=:VؓvOHlHlHlHlllǣ؎GD#a_6e+ 䃮z((([蔃N9$⧲.Z1;pHFDFDFDFDF͋50j^q/+~1z&2z^a wMK #)t2Aӥ.t[ZTor%|_ ɒseI5P`"X""<P }Oi> Ч4@}Oi` l`;DEZb,r+* (a`-(@)X6`#(`,҂w1~S\-6='X-Z3d!,YH. ve޿ _/ex2޿ _K",  ",  ", -ndr njm?C,i,i,i,i,iK’!,iK’!,iKse2|n> [-seX8n7{#7{#7{#7~K$I  @ ؏e)V}2}$}$}$}$}$=EcX0=EcX0=EcX0=EcX0=EcX0=EcX0=E OD(a^ !AY̳B@1xxxvx7[wwyr@<o[-"x܆؀1W -Bx"-Bx"-Bx"F#l4F#l4F#l4F#e%2^5e!s]Jq!QoF!QoF!QojZEjZEjZEj_-`~c~;5@p#p#ppa<ܓ02u:.52Xuo4oq_ò?0.]YcGA3`e2w OiAiA+N{)9ޅ}7C/ãǣ`e2Xz, ^K/ӎӎӎӎ` F|uxݰ :\p!^8lW8'x0Å2?\p!9q!sąNoҟ[ W#p\ =+GOştn |#I3G=^p]|>O#iXxuAWqzEl ǎ#p;; ;HBoGvoG`O9< sxAAA`aaaa#aO"w$,""°0," "°0," "°0," "°0," "=" /U`!XEB f`6°0l# $̜ya@,+Dx922a|l%[V`+q8Jl%[V`+q8Jl%[V`+q8Jl%[V`+ql%[ VBl%[ VBl%[ VBl%[ VBl%[ VBl%[ VBl%[ VBl%[`+!؊z Srb#EF$l ׊ubq1D\c}͛oɅr@sV%s*9!Nmu"RZiӥ2BiP#6R5v.]NiRZ_$%5Ҟ?e-7۬ŔDN-&Q#vZ\L-^j>(oXk3tk-/[8k܊*MRI9?I=~4Rǧ-Q)Dz/z!smxة-ך?DV 5o@U|Lig,x%᪶94x@\+vre-'?\ b@HN .!9I< 118xOEb\" 1L?X482f 4I `O 쉁=1'{bd: !0 x< ƀg8Yx Ɓel#_5;3%rр"g^s&-ZtC_ꬺWGYN`}}FF[GGwGlsܣ|QGG72CP0 p0>Y`,` `,||k$_ @}`=lFP$]};}"h:.~'q|'x==ݶW4^Ϟfo-`+_oQ ͙A-A@oAoAor3LStę-'&أ'_ݷ9FZwDf@ܭnO5` f;}(`5XZPցR| 6`#(` l`;Ȓgsyڢ%`)X`X VO@` (kA XJz|>A(f9+x|5b9+` t&V4b[4Ә?Nc84WK_-e~RWK5rY+s:uʪp¥pppjxO5\ڝ'2p 22~~;O_o߱~ zUz) 6 E{kPdXDKk1s%@X\@[\kkE:sN]v=s~'R4l^_;JOen(aQ^+JPM==9_WΒw}LVx9XX zmݲ${)*m;VMWzEI<ıeԯb^l%cm-'2z ( <C].1],w9ganp`$igF՞U{fPAgԞQ{fF 3N5"(z+"Hb [208k(r}g<_gI+ k)ߩ: _x1~D~c^} <Q5-gTn M##5*b=hH4Yq<T5򓢺!.jEC>upFVN\+GO87Pֹ>m]5|+_r^ hG*wIhq\fNw;·Uk`VMԷmMK;ၻSm\b)5Rs_u~;RQUΛԌƸG5un;>=T~jCr/Ƚ YGHkod/=)*t 60Ғ4Q¯ 1c W{ksVg:RP_a 9ԡ9됬lUN,Ԏ,Un8jfE?q~:*WgScK`DJn{HxuTVtBO룪5ݢ18k<_KYK H8cK{39E8GKTm[Kݧ~O/USS[ץiZјz9הf ^$S)wg2#_43HI\lʈ!*Z)rQgWQ+LXX=JɷZ ח =8A\&dzWes2BGaV4NׁLDHL\ iRUFׅ4Yf~ vq tzQm~:BCzzwjm8^s+B32Xf`> :43@1 ̶-,A`>pͤnZ MiEB _Q.-MKHL| RIFtg.tY˖" /랃óa!\2+1\|P/F 6Y]JBGrLh()e"- itnDAbec[-G8z 3jV8eǏU˙d^ W-|6_5%ǥ:ߝ|˝ := YǏ]F~[GB\9W4s^BU"D}BN@OG(j}\ {='^X褲4V QZpdI)WB:4W:\zN_yG7Tbh sCsŊ#碷r5Oߤ%S׾odg~:_-~ƿO:}>M-ɾO_C<ְn?xH-Si|b:Z D;vЎbX/N^+KKş{8PN1̼ټUcn07Fsi0wG]nzx̬3'#zxJWS<ޫ)duuxl]+ ꝓbz礘oo*w*ꝊbzXީ(V::׉UꝊHSQw*RNE^SQTw*γ8L5kNEqXSQQT_;:3QNECw*2#ޑnN3zEn8-v [ MƅMEF7.U4n 2ƪ7Uo3Ʃ7o7Sw ~?B?C?czLD(Ro3|FI1b\ml u06tMx|E1Ox|1ELZl4`DZ WuR>&z^xhsp_x"u\Gⲉ(h܀V#En2솙1΅Osaq.%e"Vva1D0L^09IĪO<#_4gSq WMM~,t9ȹP[Ec, JMzmm)6imSmjLۦxmMS&4lS@&WۦDjZ֧݉|Obq\+c -@-NKmqA ⯱!^Y$ZY͏۠>ZDiOz0cBBai`w#űk F䠝XT'N[(mq8b܊͹;#tD*h`}w'_yyX[^Y8zae}m},m}9%eb%r%nD:쑣QD6jAӜi;3R*diUUrJ+K89;9X턕 :E۠8m jmPSmimPs9YiKT[K`q,mqݠ$-N`A`p>D. ,2K Q#bVV W3zCfajkN8FhЍpwLl(#BԶ VۂX+++Ȼڻ4j7?21o 5c?,ynsNo'w1c7{_ֺSř2&&pٷINHQf˘rSW7e>r+ eɵ K&ʿ\s~xK|7\?W/:2"?돧DOe m'臆o?o3 :nRUC'gA'3k/P[%< uKsz_GmhM˾x}GeϽӥϭ#zUHuةtTF5}ݲPWB;l=v|^n|vrWvL~bY|f;ʾ~Tkv5Vq->B/>hNQ؉ei&[÷D.X8S.mG%7qǿ_'kuc{[F$~'<#ru}lEd+_*'zTtj\#w7|An|Z~`/HnVg黚qut1 Ih>jWZbXjnVۜ+a귡mwNͦv?4 }Z69g$ֿ˱ cҖXQoղJ~ ~_^÷?ǫXC>B/I-}O<~'7?w?D`\X{Jﰄ*vɱEȾw ,7P?^ms ~[:Wl\&#etJ /=P5\I}YMT=Oȭk!c wϓYOg-/v!~!k^Bmh(+~bx܎{F/k_oFnd.So(a;J|]:} VYo_?ȇ7g,>~|)W3ST_tfi:(FǗ_ݤ˛Y7Cz  8g^00)ߛ-r;ɪǯGGo#__ |HU(hNo}qleǿ/гCs7o73fʏ=C;WO7Q{0;?ϯEz{ ԯhU&Pǭk-Ne_{o!wO8> 0[e" ٥LjnxGrD'cso+c&/?1Fʃ~xsq(6)OЖ`뿷 ⿺T[l2 3/_MtB'#g4=~~Ĝo8) YjqRl [Ziȓ+@~΋|sU#wKCr{+GBF6'6]HcOY)DO>m`|mE{bhqM#} pbb1ҸڸZ2n2n!cxo 5xxۘ*&$V`vcv6;,l1<ϼ@w41 gŇbw]ܜm~ \sXc7bsL3W+z O5f,1KFs^Yr+j,6YMbj*+;l+WZD"Xݬb!j~k5P|i }w_=cx}/q}oM}f}"j#1 X?h'mmFG qKlk_~B۱Cev#;޸na0h'vcl݌;33%_+kq XU)cWc~Ş`߰SZ{=(gٳ{=^d/26Fd%F]jnl c]iW{Nc߮5Ga9юg|4w u:Nw39c&mzfmlfffM0s7s3L3usVn+ڽŽlnurܻݻ͎}}f'QQ}}}ntYn[h.r}%<#c|wuךz"w疹[K/=Ƽ2S怨.Q]̫Gu7:?|sPԅQD]u98QWF 4uMԵQC7^L4oL L1 c̗[F3ͩgF6ߋN۬:3/7kw0k5#8 Z 6EEV*C0\gu n~n+U*w]e`5 X-VK4͈-| K%^pE- ;]WMDXtAm<,]=j$ :!ؾӱ}wRΟtw{KQXM12B"ވJ`%1rDkv3_|m7 ,vvQcǰЧũ3_Ec4_|mO=ÞMx*V5Lt7E;#cho|< kkkkm=C[\mmjkkH0SE sH2&ko"w>X j++\ ckX"9E[4mӱ"jn5YV sgmu:g#siFgk쎥NӖ:M[ 4MW4M7MWM7m`vv mE#eEeE3l7Ƿ@V\W\G&&"_qQ$,ff"Ŏ#MvSrnf7#x;4x$rn̹iv:3Lkgcpn, ȡ֤݊?nGv{`w "hnw"338ݙܺ]Hʹn\^z=eMssȡ}O8%%J{ u} `Lo[{h٣(~~-Nq_PG1\c9ǒSS4G"YE"hmO'V7dOo|{=YShT4{:,/Kyj@*j`@>q ,r0B[,bx% _ JDX1b/l?QC4KL BD b!HXEpRpH -7 |S$''{ Fxzp: Cw~/?"5~} &͜ 0 x rqp1rIph\\.?& R p\\Gp J܀,9iʂySpln!5 ۂȳ2XQؒPlIÖvn9XKD|mY<(&xTd,.^i11^ jBLS/^d+%X D/40-{L/SV||[%|c[*{gU{k"+J@nyQ"8VL4VL!dTL4V ve4';Ks2S1{ճ.űcŕ|]Uoͮb5UfWM4jU~ܯ_?F~_~7Z?s~ܯ~$~m=O?{~o_oaѢ9CX-||NF?2qqq%a\o\/N3CÌDgc1H^c0 |^`{s#gęlc!Gy=4y^/x^aig,Yû@'szVN .bFmm^sRYA;/K!>K#>K' f 3,/K m/^+AB9^1jבZ1$CW +CB YYHzHwwaHwׇZI$Q0iWJЫdUE C9ez+һH^g$ Zm$O6Wҫy7z7rT9Iky3̂Լ(yG"+/ P[= YFe&ZLͅz[-%z풖zF֨q8"|4 a~QffHxf)̻R42tx+fV<D U~xv̐!"9EJ̸I⢘1OC~1:j'ʼR?F2IubfcOē)0s̘d#0͌ Fqg1^0/2e6ZX`C}F0F=k<}x7axL1x\coo <#Yk Exe_o1׷ט=hخ񦿙wo=[v[\Q C<8 Ҿоߛ}}cɾLoG)01f+y%t{=fm4WwK9v;{UaiWUp{:`T||>j>9>e>D;pu'im Üu69Wjg3 nyx)j4Yb%8˜O$խ,6ݩ:9\:O{Ⱥ,ꌨXYUQ!|FB},h @AA_p) Cva&  #4̄` `<=0,KJ5Uv=8\DՉAab” %IX4aešȲ ޓpNd$'#2)1#1/MbGd^>:G/J 9 q0[w&ރTgJ|8g%NHV #U'k7$nJL܅ܗXu&$%@$eiYl =lIB^t#6κ+龤I\KI%43iv|⤏I>OڂJF'PQ}Ɂ i9ɭ{k7+%ᨒ7'JzSoO<9&) ˉS'<'yAk'!Un 1;*@b{iXX7NOIJHKiT)]SzrQe)R#O%岤,3䨔8gR!'LB.}-LY<哔)R6Qjeʮ}'ȺSvnjLj\jԔԬdNZpԳKzUu-.}>:69-_5䛩S3Sg#.F~FKڒZZ?P4_Z -6iZBZ2'*!3R;oKӮ@և場!i7ݞ6L'GA>r|D䔴Hu{is-M[&m}ZYZrG䁴HELoHKo1kz>iyӯGޒ~'QȇG>>Κ>)wO/L_< 雴Lߕ/. 3.͈!LLF\C-2R22 2etqqy~F?Ueܘq[]i#ce|-MԌ3>gs"rJt{s 2"չ+3d,ˬܑ'@c2Khig3jL fg%eedixcV׬9YҖfɺ踼,krpYdݙuY eM@Nz n¬EHuOfmڔU+k_V]f2N)Y_Y;!kgg/Ud_}+g&e?@_BrVײ̞=3{vg8;\9rKv:{?h/'4'!'-':9=s"/͹9(goss93&穜rL̙rN΂9+krA=9sENB#7$f/knOEHs/;8z%{rG>r' '待|7}da"Oks7nʭݕ|r3ܯskeRܜ.S^?I~yʻ.Ƽʻ/G=Tᗐ~3oZ>7?_⼏u 飳)unޡ;?4?!? ek-;wF7+Ð#G*r|DI萿 iʄΊ___'@RL$@(?X` '*/HJ\V0 5Eܒu5wܓLUO5=I9/!qB鉳k$.N\2>qmbCĦ퉻&He$)Ssܛ2;&eAlЋLHY@0E)RR8e^)R ?r0wMMLߕ8>~HoMHJ?~K=}J%έpfdDO2fYFVF挲U3fH[1'c~Œ%32Vgh؜-cؗќ>>pهg0mͨ&%e^!E%L-:zGFVcts̰їfFf:38̢̜E+9\qE\Ciq LgYhLlQP\*sMzEex˘mcN{2? pK̓blRVOV_mɶg;edGLvtVkv\vJdKvVvAvYԶSgdɞ0{Iu٫e7foޚ#{Ȯ}هfn>5?;Blw-ǚWNȞȜרI9i999E99ryW͙3M9r^4gEΪ59s6lٖ3gO9-9rN9s.8̹2Dtę"jkS^-׏pPn.s6FEW&fVϝ-$Nϝ[ w8gXlD]z%jsFm9wdenȒkݛ{l))P:1㹭;rrώ =?/RnOn_g{ Ϟ J^D^t^\𼂼yUy`ތ9y-[qN^]u:xsּ⼜XQQ"[:NGu]3/.[9^wk 8ʏ̏Oȏ__?.Nt͟?-8^4ʚ4rUϘko"Εw? a%8cG(.sH+W $:9)(i UU'ʂ 'Gy%GL(2b5 F,.="QrtUA}>F_>KtCM v-8[pHxA밵#ly—{*B{00tѣ S VN,Vqlo9 ,*\RHja]1a  7n-Qp_as£' ; Oe\~{^ /,.ҊE"WQXQdQLQBQZQNQQQEѸI*[T[xiъUEk'hKdҢE{,j):VtTљsE]-Km~AQű'gWF'*S%x:b{+VTj*NV>UqfU {*꨸(NW**jPsP?r OJOvE]LK+շ@HSO8Y_Nk N7fphX6h#~=h^F -TFI_B8:'|z@_{i }+[Ajg$|7zߡ>2Lh$}hS?Ex2ɠS!s 4-2?%,F{5 ן?x7 xY9hxt&Ȭ_йkײ ¶Z)ZI˟W?YmI%<}B6DKKqIzN2O_5^d~'ԟ0S? =<76 ]JKG0L> ߡ倳2 *wCwԒ ?Cx4Rj#/@B '@knYw! йy&\ ˙=8iI0Sr٠g@xcIVj5ۨ\ckW졀CX9i1Xln(Ȍ}':ݙD/WIOgu0G=GuO-[ea<(h q ZAeEаA>56tFm$ wGArX'l *jjQQKNEۮ/`X+rmhQ-:#QGGP7cӌ]ٗe+8,)? j~v2X.EK[ cⓅؗg윅{Be["مzSЮhW ڕ $pW] 3e% /1&J'P*i̴{0A&ELic=z03F-5EebϯEٽ(ZQj J]~ӗ߈n(p5H3zobk=X'sg.b.lPN,۩~|R%օu_~m?ODG'폣}"k'Dvj B!xLe-5gј'N1jlB1:Dʭk2{p-J/{1"2 m_sX X?DMA?e>мc0ҡ'\Qt;iqWO rNz+KQ{зz2JgB*lXXQcMTvi&~>}[As3cia,ǹ)iQ=XGA5{;~h4|VM6 RCĖK_trR¾/8Xu= |KH-gLE1&)cKCKg\~V6p[d;joBGL-ce)1c ĪK* x>?ޘ<`DXvNu⏨Ghןd w 7 vʍ{w=ܝ6famYsAX3BMDc7GTcYO`x??Qx(O<_qX "/ Hӗ2m溈w hXXnG++{am6eЦ!뢇Wr=⮔$1p=A9= p9&k$0>YO1N4\j/Q[pw4_|׉,I4jo8 49p't?qj4wpR;'Xojk_^e<^gCx`OgQ| '0>YxwW3Bgk@G了4^S/lXhy>@J 8;߇c%#1}ZG"s1Y,`D+9D}MI ;!ho0h`]? bt-z ZPrGq[0{'a$\+>דspү3|ƸjbL޻>d< N8P} 43/]WC̹Ic %:^fp7l"ˮ W^k`~hx@a<8'Wu[xO[$F pYU<4ZYѸkTB}uذ&btjQ8-2C{WpGxٕLt  Fȿ%l~NwWH32 N<x'K#-ڂ+P{%`loџS<`Ghw |c%O>!g|E?cJ#E0"F6 ^AQK*ʎzi}8oH^Et&cy/]_-b|g9RRWj;^c#c*"[-H駼ҹ>1 =B4b`R^8fzp[<O UqN"]y;j<^c#mNa⳪)}&iecY7^y@>25E| oQD/J)ڒ?Sh01541[+8?iwNX@g8xܗ9<:|OMO> eN(^<O!za"/T<srjl7>.Ksk`GwyͪQXgFeL0pe;Ncԛ'Տ17$ >syV/rf=|1/R]uƉ~9g>E?BJ_\3Wܽ]l&83g2#7_gQ;Qw/aS˗v<3? =g$ȘY8[eq^ } 4 O\NVVȴ8!>wX}5=߁_Ӽo1G?!樻#=(%~?ueݴ\Wႆe|W) V2m#h$w}v=/5ACNmóЙ(h5H_+ZxgGKx ,&h:<-\^} O#,'h< N 8xhwگNI<0cL;ލE,~k *3ɪsՕ%xx:_<ɇ uË~K46c74:8A<$vW4W %]Nߙ)n?oNd&ΤL?<1CXvCOq3D_= a]opcɷ91p4N~;LJwX]ILoRC39<̐O#0Ի ~~_omN߅a#s<? x>]]5X/ ;=K^ys]xnDpעoZTb&MErx {2~|%lx٠B}E/{,}x(xhKp?<}K˹QkE{5XnwBkcw&C7>yu?S#G zc<_s7]cXOq &8)A#>|;Pc($' Ϻb7oJÎdm$w;_!N'Zt=CxIDMx^!үJƳoÆxg8'Q;J`pL=o)}ڌl anQW B= 9O8-6KgJ|^؈mOsdk_ Yk 41:-t$\>FWU?G{hzb1p'8s9қ@r86BA6 #8 $5pR3{yl2h>x!Xk4OމUs4~M fCĩ¨jdo"Q<=6q#g5zc p1E/'6sxsOm> zgN%Wg<_W8^9`w1=v8[dmŋ (ufnx?`|˜R? {xIl-dʵ=q ~ki6&L2@O2{ -jڹ[xx5i.)+aw!?JШç)djuY͘<aC4ަDw0VnssTAC54n<~fƫGkpbwіKhUiPy0!A0 %~~+ wG]FBLkG>od4oVU3 ShRppʖ`OwpjN} z[,iey|@>$.ߒߖߑ/Yj, XZ,,,<"lO+GSbQkWD+qJEl5VR&*S;c5GOeBe\V+Fe3ZNۧ4+ Z3B9t+w+zٴ^&ȴrRfjoFcI<*9X?es?ߋ0uE׫iBc!XBQك-W2jt-m*J1: S\퇆+ KB` c c vnVwmj-alQX>S+@I+,ed~U}WMhzw3V&3_nY A #&f{2VETmK.!IN$;Jc r:g#f;[urq`-m=Cy-cS3s,sqW5|mN-2%}'N!;Pw12ߕ}6nmwA>{ooǷ@Y8{ Y&0 h8h nXn_D~3~ -u~ F7[vwpͯpگe^?wG'sEq9,_m;?h?ҿkp7ů7 Ni8mNo@3 Cڙ̸)s8+Sn|osƹb粛XKlrn)<<4Gt8@uw^r85,vt|+DD||V@נ,`7*`bԀs, Xnd6pkp4Āo|:}P~d` WsLB{܃mb_ȟ|tYżt_ZW.A++ ?]9q*\\ծIiYZ"_\K]+m=µʵzF6N~AW뤫:::uu5P 4m~٧s_ NX pq2_)%aJǖ6csv <x}v:O1#[;3ix^ R,A 'Bhf-Cؗ"h_潘?h=F@Pv AqHq^9x'MJ}Bh? πy\2Ac&M=#hN?- Z{ss֠Ay jv:t"-34օ"Ҡ .; 7-.?/k :okE`_ނ]a1 i9En$jCpuiP|iW\kɘ6s[}7~O?Z"socYzXq/ "xq[ 쳂 >BAqGs'ȧ_ 21A)ľ.$<$Ć$1  ) 2pȔ!Cj؇ Ye81c2u! !خΌ `@ c :B ًrfyGBp~MĂ?s04nmb\f8m1miώ!1_91UȥP!juFFƅfBdžVN~oИE*ff%F @w}YM@ .У@ Wa akӡP(GП Om 2!3#s?} }J߅w1G7qW_E)z^ jg }{$@AUh6V|.z B tv@gt&Cg2k!_ _F?(,<v'QIt8]߃{@yb3t @1uQVkIX@wb?'Ĝ3D)])xMx_hM̓A`<.iKiZ1w ꝇz6*UOC{QJG)m:FcD)'J9! KQH >,`z_ Q[NJPŊX*JnZŸCuA>>vO 1=3#}G˩rchc瀟O tb5ǃWWPD5/zsw ae)9 fk5g@?+O /zʎ5tз||~ ? ~+j@] k1lX ¶^%_ї*C X*־~?lk^D]+}!Vy8+@Ye- o6@^EWQEOqݙ_(> W5ѫ3ѓGO~iO=Xi6ЧAF{PАWhȌxFE<`Uj߁~*:bYςn=M'zugZȴ|2 <`i/@cҰiŠs/tg .ϡgL}';!_Eo@b11~:fuo 6okW hq__c\4<D*"[ :5cX ? +Kl4w=q#N'@+T**g֡Q{>E, ob%X*J}u?1*qnՐo|3䱏|;aa hx]^WC?kg kP04=~zzB@h zTQ4HUe?AÇh‡ DӜ"Ns8Oi#/W䫊M#*H WX %O)Q*ULP5]m^5eLY ;.JP+W%fͺN#_P#׀%n /`2BM@|KmL S  {\CUm;{Mg[tc~'8{.U-rvV"N5p$I#x3K9oEI%ބR#w(޽:XF||g5|g{*!m].^fub .Tn!BkЏhZ{)X{\ZcOFk-^Y#kHW/MQ -soKkƕdɰYJ,55הo\-IXӵ8CzZKe]Bgvˮk C(rܼZ-j%8뒥y*׀MC/Oa^ўq+Y`^ec骢 <'zNAZs%%ĵܳnE==ڼy6ҵV9~o\=R[OxW?}Vw{^xW{ؖۖS'y6zczOzϢkgwwuwu"<#7zO^꽂ڹʺjXE=P5 [XzCtxo,⽍'xoOs,SExV#JлIvS^ghx>V;͡HI493XHVϱW<{_lKGrQNcq֓vdG fOfyαͱ'ٓg{*{|"DҫV7l>>>suw=Za!K=骱//9a_f_yJ}}ľ־|GVgootُНVt{~Wmu(ޫN[O'RmudN/-G|"-}|&ڢ=Sո3gK|>K|ԑ'8@Vi3gKn}>>=9}{OOinF ^6>>nau8.K##i5H f{6;rE 8iBӬ::j=CleE[-*E6t--t9Qqp?K)"i;r80GdZ(V.#7* U=zt>̲|Ce@*]kݜ%2mqۄ͠|9vk3=skR,PvCdɤY^]6":ϕzp._EOJAAjh@g5pFD$0^j虀*e *rnKPK9 SБϣl7ZQ_" Vv3M)Ȕ'a/&)qfڵ01W--D=p zr`/pp=c z\{wX^u*V ;q㲡< _sjivU1xZt]s=:+n vH]-K9QVPW43?n4[톝Gs,+mf]c6k+Q({FIErZsjXZ3G c#|i>X҅)<:y F-B ,:gκZ^<x244NKPvGOpw`=f13՝b}iF@>Zm7atڀk1l Whp&am_[ T;I,L؁6=X_:f "Ю<.pYt׭9rżUI>x]`E$r?eG;k;v:)pRL\)쳜K89.:98.ɜfL$ {s0kIq 'c?]L丠?^Z.SsR1ꇇCY^?gBW2n&\*C h'yWqlYW}ҿ@1T@㫘\WOD@k,z X'̄U_M*W:( Xxƾ1behjD7Y_v?wh: 8)E{5Gxv:\8Zĺrp^vhrѾ:'wܻ(h1_=@@ڿDi 9+|€ϥ 4@@WK zNjts>\8]$_.Y=4a)Ix?f&&6.kifY"̩yY"SRHH3#'xcVd>G3Qd8FaLF(͌E3%_ζC4s64م"Pd\B3o2"+PL@3my"Y~"9/"Ody|3sg!SO<3/y"Od;3N!Nى:U'D&$]h3{Y6"cNʙYr"?q"'NdÉ<8g澉st9n"kmЇf2̜5&D1D/2DN<23Ld|1)&r0䅙aY`"˂9_"Ky^\"{Km-%D~9Y"KaUȫU"JdQ)9%rDȓR"7JdE|( %DƓuYN"l bu!2DN"wH" IGXfF[4D%"oHd \!%d l d`|c G=1xcfց:fnr|3kcfوL.Y#rjMcш //cf GFdǘy1"̂h"KE`<"r[DVg,"Edbσb椈lџ@'"D䛘&"DXQ"rID#"gy"-"7 \53ADzO}!yArC"8CpN\$BpU§=A?m~m6[he+zm֠mҚ.mv@;юkZ֥uItEvݩzLW }]}^Gj֙W#]J}7>Y?]mtuun~Y݆fX 2h(31 cQmL2(5K)[Xc76;=t4Zcurbn^:c3.q/IGGGGGGGq̣b{>w[@[nn/dƥi =EeQcc۠3hEH2VGh1ڔNΓ"{!2Ϡ,kVзN It%A?HKvKkZvߡ>2LEy4֟"<dЩCp[ٜZ)Za' WГJx&\?? ;}`b #枧D]>NХ<AWg>O |@/OYK~t!;HfjɄ!<ws)a!o ЫAAw=\x!٠ VZA'U+G-? k?Ftp{Y@z4[i'KNWox1͜)wVݣKY̭v5τ+Kn俆 y|{`<xֻ汝#׾`}<~_m4AI 4 m WbEx<i^O!wxD`?^goy*#k Ygi[|*+ho'1Qzo? ^e^/?38m(Lds鱋cp俋~k5(: ؃{ϣt>{@};z) 𽆎'j̢{)j`a /o౸n7xЦƚ.{xg`aEDMX:$'| wg۟?i_kʾ {}KF쿶o?cb[6/_n?l~_| hE AQ~WWhWhW.1ʋMe<(5R,S,7 "a"aytl0bq}aL'࿇ x_=MAWm\ǘ~iςNͳ MP9Mg|/^o_z荨=f|/=io+]2Wrd)xULvuZv$ fo>ļb<0ym8/ }p[q?M\".]Gveڥ^ H^+i T].We)H/RS_ d*eE\,}@- Ȓ"$uCGuc9.Nj͎prhq8xq㏎?9N:ly} *bÆذ+vlg9y6,ػbWlر<ĮXQ~ɾQ2ML&d7HRtN ]K7-Hz,=JϤ FJ=sz^/EF[ %FCYٯv*JKP"} et"R6)`h{}os/]Oh:z!P@("aAn^Z)SHr M6JiK+qiµ7 ܈p ­w ܍0!GExy^Dx6A#BB^PK!)%-SF+Ji-]!yI^c1e^ɶ!0+ʺ;1a !#7ĸ&rNO>EҌrВ$0C9 UR 7r)e2_apB3e[Bkj5nDIWԾ6t@]v+bH{w/CrQg0%CG&3u0]`:OD |.*hKŊ~OvݔnJ 3d]aM4fs9q'h:g:,bO;V!\r:nAF"<£"<‹o#r">F  @ B大Ay.*A2:ε"ƗZyّd"c\,CjR\Lvkbq)USTn|2JeGY*4ҟRggLy. }[m6IyQӧmNS}xc:*L8E+&|r[eH#u\q]Z.Sƥ}pINygQsOMinhTz(wA r1*}cIN>qᨂ>i]qGʼpjzT*Bpfe4]F>1PϓtiQpQi˼-S*gIZ. bO_d隷/e\M_?y(Lg/E[`8mдP9Jyn_>u$Z"ZQZ"&Ziҽ$]K2 stYCJJw{^*k[@X9\hm!V`o yB0[IˈDsJiVv`k&iflwH;}p#!fFx[,BʇCpD8D/A 0ׁ\`%?崇r xoo6FKtV: H19`r^:)\.Es_bt0[| |o,jv@'HP O70OD)b)BD[D*GZp{!Hk5=ZkhȃZ'KJq=R\@["u6)\(uH f!-R)C"u0<SZkz!-֖@Z3x} @z)SZHkK쯤WNC[ %%]tET5yBBfX%W(K4Λә3qL@H-C|IKҎt!~$aoC4rBxNa3%%v,>ԇhg;#S8!" &PA#N4U\,-Ic%䚕P'pzip&i 6iʅ@= {d!)dz>BA:s*/rIWlA \ C3C|D~*?V~?Ÿ|,?OC!O($B9-CKi#tz&Pa0V,L ea!D ci!F, ;nh/:NXDt=E/XK}Ŗb;'@a,CPqH\!Cq/G3yxK'+1Q#~棱Ĥ&&EДc:fVnޚ~! hD1U3KY"ҬҬl2/%SԔ9ֈF:jb5w44όc׼J_She)ͩͧ-5%r)66׶vzkhjGLtWFwsvv:PZvmnn BGB&pMBԥv0swӣ5aSƠ[ B j wvE!WP462 ]$`}D?&@p$!0)suA! P 鎣/NAj0*ptW)B?t; 0]Dߎ{ a;LYX:n9BBFQhߎ).č!!K@5`]'R bNz/n"dZL/N%TTL寐MK$Zo/0U /RSq_RM}j5Jo|nC~zuC8q48Ʊ15qlq88Ʉc8'~iёx4Q!R4H&FJBʰ@285? PSApU{[Ꞇiz '{kϗ`|TiRƮ;i%QjK3%o6IŃL<^<*$-}U_.ifzrGz崉Ff3@s|=IO ?P$oVǿ`}>$yӞZo\>ӫJu /~)j5 ڐN_VՒpEN0qtWmf\`̩Ym]/^7^7͋}f6,`"u7Kry b>A(kI{E"'cO+`OÞ{ J걧͌=mi͎=mcD!+Qd!ѪOQ} `x3x@'Dq*Y`D\% "b;7 h56rji i `J[SLc05m44=mfff,`YYf 6s l51˚`jb7GwZ^kkjN`i"Zw'/UMG %ih`-Bkǃ ڹE`V-\YCLZj96&j^D0VD2k9TeVkuԹq<4޺2`Nt XZ:躁 ~]јPDT0100Tzխ򺭌]``H.E K Z\{{bA,6Zl@"'|{ԓ r伄ճiZ:S-zhJZ X FXLCOt[@τ"*fv2|x),ś `σ/,wIOoӭROnsO-bi*82Ɨ Ҳ Am=-%ZEhA0F f8L8}lOzE9nc^byyW<[:W'ɏkZJ v';-BrןNؿNB` ?P |cta'r&;т2l] ["b<Õ3_*D)UZaZVn|[8\c#g|SLV*GwWx':OT ^.HUp;')Jv;/O0.ٹ:bV Fw1m:<_3G39K[汍if\$O"e 6?Įb 3pj%_z A\(ac /_߸(%K~#I!w[~fY<)SP n)$͖f"RLQr [ 9{w/ĸjߍ+A' 8a{,Vk6lXo]8I.{=dJڻ R(>y(%yQ_Ck Ypo-@Ns"@~8{~#}+4+B[-0C_SC(4[?(U2ъRH;3ΊՇ*R,1N#OҨAWvz K2QmE ~l%zLN Y8:nN}~̘Ἱz\c9׆Pn7Mp en#Aw.s7#y ޖy'ޙ/»_|K ߎ >'34N+p~3G3y*?#F<+QBv!S|.+.xeF܅B qGVo qChz hY![IӈYӟ8ԋ#A;CS!; 4LQ#vCSP ;YǓ<+L%3sniB]2TMB_ g:*iB=1} D4tGC<.S"<iRPC T7gpgL1I"K40 /F Q:cgcD+ꌉD;0wO.4QnD u swLG;ţ 5qXjiaxFvIq";1H6E5` 7Qڏf +3L;G9B8BP#i4I,.-N.)\Y4V'B3IAHuCRG_S2a.&ع9(h%m _B=L35ٙڹ݇NIgf#>yGlFDLC\ԟFO<#pF sVFn)غ27֣1֠ MD@^کNV;Mu}T?AOHOA˵Oe\'qs c'0il<7cLD &锉xeb4{1Kۀ)O>u__)5gcA$4V[x|HRr0MH%WcOؓ(/^J i=b(T؛N 21 ]rOrMI l-ɒψpI2BǟTvS]: SKT7jZ-ji*LƓsFW㎖p\cq2_5vY3 áN4cZV_B)-"HHq ՕDW -䲉 -]X@;GK(},kOb؜ S`ElIF-HYc׋% q8@@@ω%c'4@y@PP +-ߦ;;$ "bl[oMM}[xH2'sDOxYTF殘כ4!n]JOUr,ڔXSd*[/do7Jwe[pTi _ HLiMR>=r'KN)ac !m}MiyѧU琞o\<ҹ@st/mqڿ#t12P~xxNV:iAGZbұInӤI5 Dz%h92靃22[FR tOR|U&t>ztᔲ5?_5..J'~y>qY9o5J\c9tt"]syWLqkb'{J J[d A@Uv%Ybr1(dMv#ewٝ䐋INC r $Kr)#{ʞ$\Z.Me2$\ t,^) W+\+ɕH!\Չ\CA޲715M%"rm6).ב\HI\}d)7 冤HnDȍƤDnBɾ/)/7zzmN*-P-喤3,[*rk5*u_Խp@/|u!G>A>h|AcwzGh&4> g3, rVkN'(dD^W+2򊝜]oKʼ7_o7??ʏ~_o#H ?]'`+ ,OK*|_N"  (=!B0WX$…ao*%煫-!^x"EQ%1,fpDѕ{&zeĊbuP-yˎPl&;0:b?Vjq$ftSũ,&f%*)#'hR=w32 hQLhdat&)hA{=hE{h8l'y.l&ep'a+t49gDZ}ю;D+D=wl)46!D dWp<,rPKbINMi1#jl{OqGQ p(;& QlO-wK"Vd1vL|ve+)p뇾71v IaNMOt<Ha)!\~)L@;`3!d!} eo])Zݔ !Rg;\ȍw)%0 U2?ncܝ=X?E'cćG|%Bܯgΰ]FOW;B#PkE#7] 8rs=Ɋs<9/*WK9Ηkɵp~d h4H.1n}X[iƴ!/5=iÍ9K!by`!l7yq %tBaWq5 pRYŬ^]Si9tC+,p"ΤgvO q7X((Ec1j`R4pt~Pp`Ү$ީQ uZNߗs--F9{ 1P~xAZ\lHjA Q{,ڟ}5ڗ}τ} ڋ}a2{&tоޢ.2jWoC}37Q@~ sD(t?hOD.1dmnDp{`H-ƺMg, {;Ve*S02a:1zZr]@9< 䂹\mxg01 EAq̣Ag dZvcf8L ;tB8[KyyXᨇC ̱ӂfs; G7\4LoM/>xˀbo}<'5p8Ndqل?Q,#y1_00A"2Ro[#_g;gZg:qog3ٮhg|go~g+Z8cFSkͧK_v8q8f%G=<|6Dз]nn:3uì{<κ½S_8ތ`3; ʾ& s!)"d__wԤY:: {Im qܟ4b`) oBȋ}ñ93r"1ǾخˉI8QV! ?NNP o ]lz(F#<þ($ս(Ⱦ2ts"JbUkVbY cP9@D]pztg C~ǯDFB ?NNb_־4mI/$4Bl pŌP> M_fI8ը#D'1wG&"J,UE@# d6980!ΌPY~*'QMkYEuw,3I%+4D;~F GuL$"I~*U5" د1LzHadO싹Wr;8[;8tկLڵ4q,ţS~\`ۺ/u ,=Bޛ1^1gE`l<'2IQcֲPw  1ys6Xj5mQC mV91K9rx'H_ҋ I/zr%&;.wg+bM+וW rhbaU# 3lG24WDSh"j& zbagմm.]:s QgkرC^~rS+LuշW*{ob6C=;mжgo'* 9dp+i(m(Vg ij!]0`vb> ל~UߩjjN+SڳzբUJU+Z[~C^V쩖QG~]w4qy)ie$nq_rwo:V*;i9l)saY>b!WtXIDkˇ΅~h5̷hAuGv¸E?.6/NWM/W '9 wwy+W WZeԻr-ZRH˧Y\Bg|qնks+6˰|W4ߚV~{ .pC͉s- {LrnӚ0sq@!4YxcB̄}ӗ<:}e+~mu y(G^9>ǫs _m +!1 KkmV#Zp.+޿kOc=ճX]k:>o1S5ZJt fZ h:Zo㻁.fп2 vʘ`A KfM^ww]sYk},O-|n+_9w 6bu ~2ޓ;.ب놫^(}qC+׳^ުIݷqAA %vz[.5=^^Qm{kk͊pw,.r8b} ;25؛ l/'kStۄ-t>yHbTmݓrG=zUz|s5|ɉ ~o^[wߚ٤  D801g&Ǎy5hObl9լA Mabpf>NvT sr74+etss7JWC@ 4=vk}Ifz"o-1zz- SLqלx3dy nӠ;#H+%'(p%sqj#2o7eލ{_]PsϮI6G~wZmzbJ!r*}͢_U$ʻB]?eH2'V#Gʘk#>2/w({]H_ͷp-= |=QE0qS\ǂ3TL\n7Dw{[ھr>PCCA-a0W]a{v%:-QGQE=t/[%<:O&:"KRy6\v~ڇE`W(])TQ'f"F+f"'30Jd`q"9 A~~54?nfi.jvWg  Mn)w@k^=&ͧ崓W5,_PD {7Y}sD3&HZ^њ?ke6ף9g,4R^ӽwL:؛(ܭEnӗ-}KY-˱vmjXcb➑~*+ru[Ϛ폮<)qʾ3JZ&j<2à=+{OQ!91'otsmN9S2-ɷMyNT\/kJN}W]v%:eqsuw,|bA͎5ڝ]n/yuX\W [Nf=nf/uQq-uu/ =\7_Ne[JD3۟O=2*n\نW<4s2A5\V'=c6):_'z~x?!,N@C XeC(N,CLyR5Ր%Y 3/In6 'nN]۷ T闀. Pa(^4ww7|-n1=]RhHw׬v^?0A^/9˗08n<-Sͩ* mip>O3~.K[1јc7':g/n9ޮpLxSVkOW^cJ.7 >yӥkU4!ymdߨ?x34zƆ;g }uŖF=n6y7U@NfK;ւ#k[qɀDnVD2TrmV[Qw{WFZ]kv⣳[ܫ7% Zގ_+>?Ñ:le=}>ymm(ZJ9b|`vAmv+"OTpu/ZPbk}5ߧy*;6l9j^e%p` T[9F͓dq۟y+r&=s!|}?sXAܻg)xnv|%Wx$Jc}Q|kF8L%WUSX!l/BF2 {8H{9՗\^cu *Zm~̛bs+6Cn!HNfbӀaE ALKLAn(Qu%@_3 }}K]WzD<, ukbQ>Ju?(X?;SUoޭ_c2 0j6z[sN\VnުȾ^3r?i㨉{Cg>'6Ts~Wᗕ]LUa_XS^]wˁkaW]]S?|{ps)W]_els~߇~pps^|gLOEov`W_֯ z"D}:(bNY8,B w:jGd\6+?t' Y=vͥGܵԴB>ѩA-z36+rm*>Lvf;Ӥ3孹ak!n?|WvWyM/ _aʉW[[Rx=3 }ڹ,Yk[c h؈M\&TT=FV߲΁3 fE~n3mW#·ryngwTzj>qtIg󒬦hӊ4"v _}S6-ûM^SݥNah:7 ~Xݰ5?[hXMֆݱke54us}$ðyy'q ða* 7T0fsjաWP=l߻kxGNwH:iMzW室=o}I/=#\r6$xq] gt!3+O@{:v-jwUcu>z<%ɿ$&diٟ<׽tg*mM9O-p}j'<;W/w}r]tqVBjeկyßENzZweO,/k ?yڙ.3Bږ~b1&:>wGo1wcV\s2nP{{V"eiL^K쫨. xU:rC&W_-wb ܰ]Kr'*p3N>}-tsFyikOi~W۶]EV><_ @ ٳg e[nWg6q90HذTLl)٘y,ެl 澙 y"n .NͺN*aWfQl| ذџ799h mօ -޽:eSY ж Ts5{7\Uof*˽ˬ}@#4ckmRbNӎܹܛ.Q WDXzk~]sԵ柾txEb7~+1-|)/\n҅؇=#g?Ћ+Yg?}J&e-ZydA_UφZY1i;hgH3Բ䝨}Zͨkg[wԅveUsu#JGuV@j[DZq[,^=ÿJpOmyC첼xr[xu~'i5B*/sȪi\*YQ4ZѮBߒKmWmت{^WϖIF}U9V*!혐- endstream endobj 19 0 obj << /BaseFont /CIDFont+F2 /DescendantFonts [ << /BaseFont /CIDFont+F2 /CIDSystemInfo << /Ordering 12 0 R /Registry 13 0 R /Supplement 0 >> /CIDToGIDMap /Identity /FontDescriptor << /Ascent 952 /CapHeight 631 /Descent -268 /Flags 6 /FontBBox 14 0 R /FontFile2 16 0 R /FontName /CIDFont+F2 /ItalicAngle 0 /StemV 15 0 R /Type /FontDescriptor >> /Subtype /CIDFontType2 /Type /Font /W 17 0 R >> ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 18 0 R /Type /Font >> endobj 20 0 obj (Identity) endobj 21 0 obj (Adobe) endobj 24 0 obj << /Filter /FlateDecode /Length 237204 /Length1 638088 /Type /Stream >> stream x`\7:s{jŪֲ$[$˖d˖b 1"0% !6CB !&$!Z$1Z3wHr?ۑ~{~3wfΙ3gJ#"ƙCy-JӺq>G(v = !3>[H3sjjֶO}, 9»vYusӡ~gn?%u!BG֯\M 1t=ףTBy؇нQݳܳ~ȏSUk6U cqZ}nі#./PJul]oOo>7uњ3MοLs/\۳Xo\n1څm7 :Qo-zrCȥD$_ߡHiRpGRj$8LJ9=wۿ_prQqWR=PnWΓ4:)) oqyʝ";oBWQBg~|>_q{k4E&J:Rڍ߃Tt ث$znn@?`Xh&yN|?f#C &h::J1 roC_O;Ӹb(7e[Qp-kq9Y(ӌ2C]sox9tqx?_'dHӂ{q}RuMret9 mDS;yln3܉jf|&>_/W~$~ ?_;C^[L A)N x&.MH_JqNf-S8^#(_h Jo6VohMy;q&2~B&7{x i'v{ uѦXn駭^roe]Ktv-lmi^09MglQ;=ZS=tjI^nNvf8=-9&AQ L9C p͚CH虔=䇤 lsF! 94gt<'6PUN>z.wׅ:CE>WBX @ sUw\5X] k3B39hX6Z?3Ha)umu@CLC3ĺ3b]ѥ'/1m_giLPPP]P$5C/|GǧH)tPq5u!/ rHv8E:nrIvFdWƋw껥3W9vdt!>ܽw=:C: y}(/~ O`ukXD*6d1{RCyuuI]P[~"T:} g@|ݞ>vO`(w^ ނ (e&-W+휇  x V#t%=Z[oIJH9;3fK<):c'[n#ݓ,}H9.#$}o5Pn WLAS'Gt!1P.0s!j$ҋNZo:B0 Iۈmj 55w-2B"  V1>SGgp]F|:ʞa,ٌK;G:BC+"Ϝa%vπ.Eagdlhtp}} ƾPk{Gs<[{QnZX Uqv8/iKZ;2"da{܌ڎ41#$D$BjjRy(N &4%KèwiFFamJ MIvܙRn%\1+#XHxaDUˢʨ*t$!aȫhglGaUXSs'$i;IIцM}A+%Fs!XO}dmX5AX_&K ftC̡=>P[;)Hoy*,C*A%92dA^k=!B2C;jBBC0S&o1h:b"Tpox oARoVtP{hJ?|!/B.",>]SrB%\Kl,]Ao^],e6i“T)Fr/{1*yInZIt=H Jb5Ri\ z02فIlYC`Dr'Aq)&rdUI8d EDE98;+N' 3I2-V>hd1,hg<>j<@`z.!)X93ɲM1T"/bI?:2ivW` #Mz㪄ya@E9C;;@PY ,]A2x*!,L= CQ{i Ӯ] zxb]Q;6O}r?$Ł[A^zM̓A0]zEݰT/vubF뀊hFU:H5.ED.B3+ZZڇ,|"(񸥳)\nFaTyHi]|#)aFAHk|aRaq৷r极$@eܟPG$U@+  | 䣨 6bB?wdt # dqvԈpʉgC^NFed#0mlelFbd #g2Mld F3a$錜jFV1Fc0rF1RF0H#,fd#m,dFY|F129412FFf12Fcd#Lg$H #ՌLcJF*)gRF2RH1#E2RH>#y2H6#F0H&#Ig$#AF1ʈF<q1d#VF,11bd#ZF4Q1dD##<##$eF>f0#1Fy2#2#o3#aMF`όȟ##`5F~ȫbwoy0#/2#3#2kF~3Aiߨxf+Gc;TM[_x&Aşx?,?PZܱE ^MP[*^Y~CK4E*^y*Y4WT*Pqor?'TEŏwRqnJ-TDōT@ \OcQq-kWZ~HUڕT\AT2RT\BTz@\q!m Χ⼸ θ 1>7n bimTl@ME*Τb3H@A:ZZs 1*N4*VrXIl梨b=TtSe]ΖR6VAߨv7j,*(q+yq+ @̍[s@̡Y_il3ibCܺD}z1\3֝ jөRQCEu ;FcUqSJ**&24ʩ(f(AL:AkTM i΂4,?n"s3\Z3yÛ n2.u opEY oq|,QGVn~;o#m] ʚ:* JĶp 6Ćw!1FWB}k2haiDZ K[ksH~j\>gC%bݻi[4hTc#YqH{t2e۞0 `3pQܼAseLG OǑ1o /C`aApm5.7ν#˲"/:fTqf1"h67B7E8 e>]  8 p6,̀M3kk1+~@,,t:vb"@`!h,4f3 z@`0UJ@P(LEB@ @ i  <7p;,30 =@45@P9@+P4|87KS';o];oxπk^ වxysg iA/<xqcG<< C~p/g{? .? p;6[7np=:kW~ p% ݀?\ p꛾01 01 01 01 0 6 `0 6 `0 6 `0 6 `0 6 `0 6 `0 6 `0 6 `0 01} s01} s01H27# M@7NrHp._R܆P 6 FW'h؍t7BOgkaH-[$Gƾ;0"OJb?2fW#r3Reu+/<:5,J b%H4N-E]@vVfNG1ckJxOZ6Mh3:~(ȵ3f~ mGH[Ĕpe?  =s:_dLҔ Ѕ"赋%8Dˠ/GW|#}\J 5ZtRoBa̐kB"#WEϠ~zPe/hje@zvho˸v@IO*qGIk@j9M\ m|E4vZT[&if1F؉įC ^V pn!B?Ș)w)h~&dFb aG{>4"۵S)#0BGO )F@8EcϠ_z=^@AOC%{~^:`EQ=Ga0 -C̍l莱#c[Ǝ^}Kec_;ZxYѾ/ 3G$['HVs# X9)P9.?tQ޿VWQ<(3pkBJySW y蛣/卾y\wo?{TW =Q[w EKBb%|w7ՐQU&)vǠgMR䥼K&_ЁMS(P0+O-**JáӊVEoe)ccQ9#TH6Xur4TzB˔`S>'k{JkyMe)!Ď^+֤7 :]S* Mvlf-eK!ulѹQ!6> H$<2>  #j Q7aF_k4<7-N\:ސZiZ߄65{[m6TSSc./29M@MEÅ|VHM*&9'hnᩥvCf%6|nx%M7`% :WF[/lf^Uij*J=v!+y^i66F5+LrD~B" <9xh /:xqj$'L- mQniIl9d&IɷǞBN5HR'/jE^ 1CӸ3>/(Pg#X3Xj׈3u-vE%dfD)'K.<UIbf%j`BՈ\P .1O- @ȤJqq. ȌLPјߑM#ӧ,L:ghꚗ>旾llbegnpfY]ҲysJR]oTWe)-=c ZY*!MAII ?"Z1bDbq 츥UxOA%(Q:tQu46p༽%<,%7V'Q1( ;! تO(rdaYS9-2-'SZ˷5xܲ:c,xٚbQeezjΝ M2|e3đ ` 2>-VД j8AGGoFݒFҸtK-igB*Ш6o𰌎ECluyFYFrc2iѡ7~݉a~χ6/^w߻鞣?ih?q g3U| F -Fv3q!*CjUԪ U#)RY?4=Q0~2_pX"r uhvhٌ$',VPZՠk 1Ĵ[K@I/0@Z(P2$8{'Rf=f%U챚=&e41bvCf}c_ A_Q_ ///// k΋R h^%{.hH+|AS$XH1$ܰ4'-L+Bh"6<ΠU iSZRF1F)d2x'B},%))'N2?~;Z N26t ψftgIKIK$cd 7+0 G+5ΊZ -" ,*M出s DV toLVYbyx,oV#=M-5aF씨gKZl{fM֟Wm+I27kfb銾>񢮟rn(ùyu^/>;TS ; :]}zM_CY4IO)!^^4 JՒIqiHiHiHiHieӐ5ԁ6Xxb49( בZ"k)-8fG OjZ=~êzt4'|g--CZ|qװ.Û.6HcTXnU9 q5tJn8聹`lk\/לqI۸mC=Dj8x$)I G‘p$)= _=Л"*2aʚYCT5'nR11~2m/3^}E.DXreJFiv={C-.Ȝ.cܑ]h{)uZRJsaVB`HiQᆹ;; TVkrE0 md1|Z[vT@N>yR逸櫥櫥櫥t2Zj8װFjy$| |)y6\QXP]H;q|FG[:sdRJnTs=n'S\Jds;FFor[ t!dIIII/!6#҂^yC\__g#Yr`YUtdehz,*X`gm8zʔBS5 ^OcwjNS:hLM+jľ}S&MMƇ xYD]dMm8 TIҘOTaJl5Dl񛋮9jF` qo}y^`=&>Ao*CZN5JRRqD5dgVY5&[`&I&iajtl"_H5†Q:hvH>R5 nhjB\̱|/FIc3:Kj.2%NᲣ¨ŊF_M+L+z,@:\YzDwɽ.IZ|@66Gp*e81KqiTsL|jJM&{نOȲZ0$ uDcD"]Fbb&v:fzaOXL fjzeq #RY8έ.wO|t&{glkŕ J}т3fuH+lYvUKQFϭk ݅=B?LٽfE03Zt^eAQ37(Y4&ŜbQ Axrc%9>A R3JH#ۓ74~2f +YB^}& }\5OO̾N<:[2S%Oan:0H|brѿBiJXy^X/T{n.....0g$;L3'+ uF\Ȥ~`?ymT1~̿4FHxC$*`Gv۱U M UԨ8~t s\cxءRdĤ^MAg|=Z0Q-yH⑛[׭'Ƥ'j/Q4uje|4ͿMFf5B7(p5;-('ƍ~kAS'.+TY~F)>SW^C+⯁# Ո{ ӽfmX~zȵ1(^zRH bK$o_#>~^mqmqI{&Y&,Ǭuv߭4ydg]TeA=g ap;ڰnQ#tMX=UF9q<7ؕ<68r JN%Gbeq8#8X?)xc8H*ʼnhxCzNW ZgF+4k9xeHVu*-1d)6i^c^΄xbeG @<9vœ;ǂ_:9{2\# qhxzи3)(Po+Br m|~ 6OQE rZSz'~iMK;e[Ҧ"s˃r~|zSZ%uE^\T0h`]􌺊rʌv|S"Ӟx“Sh:w#d@G%tepQ~4[lt;// Gpɰb5y4uX|m!r~451@w$V 1(pq't_t7v6ܹ7xv[%( M+ڰ9;oіieJ7Xf{Ɣ;0MJ_ZjV\WJRĶ'>H_BZhH:鈿.!_ KIG/H1 փYGcib5XڀjLuZ]ӑX"썑5E$vm'*?+yݜR4+'TkT:ö@VpwQ]T7-ʲJy< j^TmNI#̺c0w5d+'؜5R9ydzW1}ULESD8mLO]P7vq+vuds^5ΐ rʊc_O?=_6jg'pcV L/|Ih,ac񑘮OP>|3cG? m $"эW*41o?P*W(eP&^P˨HDat;MZ&͠n9;5@[2i9E+!9V<k0ɾAp4&:NܧCl^%MW*-e*Ӿ2}JbvC* so{lccEzcMM58_@,. }O|mb m8oF51@)/V1OcYhCoU.+2J0Y3g`Oqis{2>b=FS`EY>{Y+6cC[U_ftoivne݆SSIG+Dի0-9=ܗo,>oa oSېiG9ҟF^|^H{x~m'/\Mi[T @ΝNߪ(~MskD?G/q ZhCi -SŶ@ߒoӝ兛xJzܩ5)qFkt&֠3UF?<.2z!{_Nqn " gEvz:djL)@OFG|qLt9$٤|9$qk1<+%͡%v4" vxF)"WylOM(OD ~Ry*MƄJש%d}e!@)VFW~cʉcF)N? 5ms4P 춎0yh?֖D_#ۖCb, 99b5P/Ǯ>*gTiK0?M+5xǶrg7+ĥB(ӖbV9r̊38U8KZW +e61kP*xꯂ~r7Z:u[2)H%#ڧ !?C1ejf*CQLQǍB9q 艜~V֊\~3O1a|bsXaJ;MIeqܜpzMڠ4M7 n}|r[1|gKf[ۢL~n@I}, i,'-%JWrfB# ُq7H<83aܐ|>umU;ݺyh<0B&[sռ۵+zcUuW7/nMek=v`/&*2S26K[vC/-2`im_,阋!(&^ 3a'օKն<ol,pqm=I^]"WhϞ$%Jh_,HG[!T~SE0'Wi#D"- |` #b$98)N`<: ^c:w~0kܭ;s`Y{9T[#wxz_]8cimt5Ы?\XU^z<>j՜m_1b*&$-\):JGdQop4/cRSeh|OL(m)M/2O/^F{Kxr#[~S˫, 0FJ町 7NvFM ?7{3Tq%wi81w G꥖3^7L.*;Cmzoɷp:q;APYeQI.bbbqZjұ}|-g0ܱʁ[)1*d&*gN/9sJ(ЋU Uִh3{fΙ8cfOQc^ڳ*YҧLe h붥y#չuˈ$jQ No0Bϊ0)0aQhKl aSR~4y&xȅ}1 P *sYƉG1)30X(ݧ9 ܶW ]L~rdLXztljoI2y*>ѣgyƯp}aZASZ!q\0}!Džx *k E*Z^?mį}_I}-WWg<Uk GQuԥBg)!r|0!E ԀE}+oJЬwyhE%ף2 ˷jt$U:rA!'Smtqeȍ29U 8:c]amNXk[&<8i.<3)&,?|]-FWAc }f^je̐7hSh&n۩@sJWt73`pwxy,׻r yQ|=Zogn%Ӝi*)74U~2/SaT7O06p&@(%iqwp3l㗕 \d!ħm̢{yo&UOJ,0XLl0J+a'ɭntQIFr1E+m"!1*}p/g Ue\т 񸖎kD_Kq~ ڼZ^rkxn19.&GFrZ<գ ZDNQ 鳀R;Q4*6sOcTsO0_`P=w?k (}/D0˺FuK)it+P3Xl?HμI?E'r2MJJ&9E%'⢠+<^k)O_yU̍9՛Y^0|ZOcVU OK]WX0}4V+k5 笟P2גݶcAGNMVCkmэG@S4xFT*MJZ'qQSG!ɷH/F'"F^:B6Ԓ G`x8lBG8?9.Z.L FU1ZTF°K[ж|Ҋa;y֛&]t/_-꽲+А4{l7+3f\qmŋh}F.G.l0+*SLmFU2λ`hs}Sj 7.FNб_O zxth>rXzB}|WNi<=ֻ>EպYQ@>٢"7a\F/_\9(} 1> ,11R~RRT<8wd WUS{^u%ϸ#\WT9ΐQV@|QMDK>9#ˤs{m{7_J;[ _ 3pG"!|3qy>*5(|)~5NS(ѕ)l~?;:+]6~)ϙדR9UWXE񉿡Wơٜk&UE5٦bJR VvZLvU5*6KJ`Mliy}Wg:YeE߮x:B!qryi?s6ތGWH {(MԽe| Y7j͇-p'1-5-[hҷ|ZNfj"aY`U9sgV6Ia@Ngm:En#1-+qFT gP%T%eN]1͂)+>0,5NV0$sӎ0gZ-$m:%M4`Kx g'g}[WmbvcެT)ikj68|`h׳Ck@eyZgUGq:- &3>c8?07zD>z}ItZvd8ֈf7tegH#^諅pv,H2Nş0UI(=6RhD:W$Sp}oB=H/R(oMu稬! ?K‚j!^~$Nu:#uNSP8|n$ K>]8Z1Ұj$ޙY=k[ۧ?}ϣ[$o8ܱOD7|nfu6i6~]kXrdg3Ժ٧Dw#owi Htr,;d!Wg/~/gAU1u_@lYن7$:UQwօ|/b]Tz J4] uj%m^r+`nT7 {=5PY-:ZVZn;-=Y;^rjm3^5)(dU`Efs*άjzj#E_Cš|Wh)#hʺ Hix5-3i9rEb_kQ,I@Sz9%üJ 4( YJ(-I%rkH{Pa0k"?{smbJk$ gkh^[&ZXU)Tջid0qǎ8ɣ7?7!7?GY™4uя*~h`j]Fv+tUqN:J麡-AF%YA\Hd75 z(ž:ZժCEh.aEtD5$tI˞H W`qʷ$zI~ P/ү@TqTtr@ArߚH½|5B==@(=BJB,)\1}Pifo>Ԏ_䝫%2fH}p˃E6~TQ߼5EBNhBo4G!) Ě<&A,A"(bd Ԛ/=[)SFlm~;CTЁU˽̙b{[[pt_,ȫ韾qBG"?S[\ +yg/0CtR]aJe aAi  =o%X3[9ARi ]vORzU>;Ҍo=@t|Y,ZFPghoZA9@j:CzlsQ-:"lG8ЊD]eva'u3Ekj$(F(mQ+U;Hj UA=#|Zݶp]`P6uoNxC>X<2y!>T9vd閱d!6'ȴ.Ptʜ)^{- 妯Eګ"l~Pu Kݻ2F"/W MT9t@9@MJB+P/o Y PѬ*ר7/h8 '0{̸-S!5<`,vBVˌ]>_.cQw~kEP'#_|XH"Lma)k]NbE]Qy;%jߋ7t گyU~saCqd.^(dFftnUURJ\ځVF6E@{Qj: yy1,R%?Eg熠B3w m x27 V!Qt0MQ(0Ȍ.ip< h(jߌ4Eb(5h6+u|\b 5,I2 Za6/?P^W0镁{-SN3 n;;r.2Gj+匘Ȇ`dٟuJ&8|`¼Vكw}/J1 %Of> *P(6Ѕ߈i9%i|U.U-(¶FZE}j?̊Z~ޣהk`G}9}n(c5yh h!~(B_!S@_!0` ZXYl{MT/K({ob2l3. m6${ Ѐ?^ވf/Vdp)U;>洜sP\#+9-h`!AAmn.zyX0 Wǒ-CYkyK B㖡 ?)Hfx9hE0x\o/q?^X/Ԯf?Dټ"lo2c;VN%9_%y0@,rȲ U2s+@BCgx$'Hp'oYּatg~;5l{VtSӸ֑hރz}s.P_HYVl^oԪ᝝`S&.tukZṶku4l#[BɞW& _鍍W{[#4.lj"D4buuBO:s7㐚`:6}62wSn胜.y20b--M"ճF 6mՓ 茘 sJ10B[+2P$${% ,MK{4뷯ȘPr[{ѡ|>\v,Ѳג'Ԫ f-Ǜ4Ўg;GvF5n;y}-W))Ko"gvp/}px͕]a1~kqKPTw*;;C5XARBV*)`K ;R.1w1,TQ?'Fpp)e y-|9̘TQ 0 9f^~G#:bAH)bsbҹnSyJJcqENv5c+ui4'[ilӟG4*{ǂjP0J\;5\ZK]UG v ;-U@5=P9.@ q5y2eR3;0,<ΜACĈ [K)dL@23BLJ!1#ut$g+obe'(j% .y`e]_X>Ysݠgw d.^O#}1#`jFzEP$?2++Xj/s@UUm*eqyPV3X 8.1jD?K~|]Xy &q a\,D/ytvEa)PEb PqTw~̔F8m8~ 5 cUiMUʉLI鑈W앤G"z}9:Q3V{iZpǣm6nmt趭M{t=9}9,RIs%bQ4( O/5=ȏ-5}iM/vKM?)ם?a`#Пs4=s]u_=2զ~SnuF#w<{F ە\}Q$?C3eQ24sD0R#5HXUujkkj*2q'؇Z`? ٗ#5ʁI{k[Pzv^\*+p{LlE:͑| 6-&Rf3f\9bcg)dCryHħP~t q2ΕGїk*gG0y+CO|N^>G7Ĺ;. :] 1ST(,5[.ƥH&JRaJ4_xRzx{/̑ Jy_,s2ߘya`_B!Ѻa8qA!T@I#U AB EC``*Bjrj9MzץyVCh?mVV?ߢғRq<_KRD. 2 K.TsOE"^=# ¯ˤRF w6ݶnM`ՏxX_΍Z G'LZf$??3HnbՊ\K,6S|adƈ\޴uVc0Na[&upS돵$h{HZ`;`λ)35Q;ٸ;gIފ)xZ6Ė 29XRhК_m{ԣvT*\V5L$g0MPHᐧ(JM L מD_?WmHR{Ը^4,fH,½j<dq zɍdO4[gPS|yI_iKWN]R6W :~ rz݋`GqukaP" 0gV^D?>iBB V$M(!2Yb-9Q$ r|๳(Uh({-9(Z l} (n-Zz9{7 ȹn`xn[j&[6}=;xePKvMвB3^ #΂Pd2hмfl=;'G8 '7vYQ\[ZъR3P|2i ]vI&[^̜:dzIm~]UӺ4 sjleP/VmP!OiT'!H`tq"C=v9H%5dSNjmMy>8B[ڇ-J"hMؖk*MIB#& y<#:W>˟v Τ}d,~:>)ϋˉOYۣu)[V.y uV/6 pG7V-pg'%sYhd.&5& Mkup"4id7[yW$:uK߭_NP/+%]ꮚpd9wd)ǿ\VءhpLb:\fKό>Tes%U<Ϛ<ئh Z~ŎͽCsVMT3<f.5g vn0GӜ kOxHy܋z/|'P>XO l6 |} ,ꑹcDŸUgk'1Ө#5҆'4LJ*MO9# IΡܱMGKѰ0*EM>ŭg* b)NE${Q^k4į`SBpi}K?^/%\m1C>/p9A>h"4½ cԏ~H~+Ǖ7IE{PV3jhY(LMk:4)QK-X^mJ ]+#"\f $uVdN!]FGZN TA05 fMX4*Q=>% gqy[ #p%9@1pOm"*D,'$5$x>xTLQ@H{x́JX5ZH:iX) Oc/̹ J۰:lBȍ̩'j#Oa(齬чG55exLPI)AчEUh=X)wu J.0< 2ChO}+S:k t7h1f;bNN~t`zY,2)Dݐ_`X[f9},W~Y| ́W"AXvdyL qIFL{kyq/LPa]ⴄ#@8/2YʉD h_ErY:0/?&bftQ/@drR^\KVudEWt>Vr\އ>s =e/ugةUtMJ>itv׆ހ];48SSj[QwhMxk&d!mjT`%jGz]BvkS@ESC[Y yWMiMPrV/H| H2]>1@:15@gs#}옝.;뎋{YB&n̄6@!JKSޅZlm)Sң'\j+X6I+ڴkPrBJ`A~JWu߅xadU9Xr#|?)RݲaYwZHLdKF¦u]X=~&\/vp*|BSν2^h%UCDwTjVXrtjd@le NAp 2ؑ;R̷Պ*J}Q1$|c\NzcWyQ{[=Jo(q͍coy}wۇVj&6v=D'6v^Jw{{<;ݞ,U+9^͂p-`vOg,d*^x1Jr*C0hvplSY@T?hHMK/U4$SΐM]1"c0[r-]aNsw"I U9ci)!>065Z6o0a.ؑuDLlfD"<7߻ymtx`$v6kcMp"Wc`P&iTpH1af(RvL**V :$Z-qRYkS6 ް}N)$"(JT@jw ࣊pyץƮAN$uTF¥n~OhT7].0{:QA$t!GÊ` ƞӺƏ^I6XX_&مDh\۔[buo%jN>$tj B+3,! O̗_Fc 4v38+ІUgŹKMZnP@j}q)5 u>VK3q>==R0:X^o%F '45VNyR/bM׺&o[ңaHTH&uvEiwCخ74#Ԥ%k F5FUvB|=KKK2XP獜ڳ?Mq VQPJo|H7{Ï6cΨ~\4gO">r)iCBg}x Q ne2Zed>p 6nCP) ~W(׈hѤJʀ]p7dt2}"WfH4窄 ;,ed٘lEKlh[^/0_6x9+D[MEOa˔L%p|C|Hw&,,14{όѧb}Nz}>}J{Hܖ[^cX^ @*kPhR^ U&034&i`ȇ B;h4S FQzF# V=j_ы]dz!2%>ͤ`;:L]PgMTtDo&4De 5 a}CÅUׁ^߿#y@I!F3+}8TIEEzԳ q6٦c[Z\I骦iFSܤxue4(ҤZ>7U`S32i?E?4#Sj,~Ȇހ::PӮ)}B*$숣8 f%Dc1",K8[FqH%^ y6^A<@*8*5,Eo2ܲqMI(bʷj$3+S+*,~IU YF%8JnJy-y8/)QyU]֊A.LY^*8.#1éџm:FE0zpjJU'ΤV."*FC;hX) `Y9ҧ¶ 渟S("u!U IdY,8rͭ㍔r2xV[j%JoN)2ֱR;ߊd-qAb}=Fe,)•Sjj=C8cYT.LKkǖ hK08%?8?)YUQKڰ~"Z4lf.paC k'-%883ӏ[ͻV7JȌhejїiP賖͛{Cw~S7 u|z8;jim5 @rV=d@;&Hym;lmvn~즱/0Bx81<$ϫs3awCςlҤ^ZkNݩm5d]qCx u^?(0(fX2!ưXGN<:R0`Vޓ"`ʬ`*ֈ)E5 hk,56+0:jGןąQ QX V+w8sMXf%V\9 sk9pyW_:痎7.| FcO>F@WE12_`%b!JGO3wps4"v|CXWv݂Nvݩ$ſ5q [0TŁ5cddB:FvB$S00y"W3'Ֆi ,^Ptɲf5Eo{ c !ާ%'/{u0pνCe9 qxUB x5X;Tԥ'/Eo;aB W^=x@<܄M8y<ߌM|K*K |i Vn.o=)e CLRZl( -XQrJ0&Tesx{hER ڷz NЗEU Oؾ;LLtQm5{*,hR?כThYHRCbxz=@pYAA4J=`,׃SVL' $:0 K7Yu Cf(uQ+ DWI*ioa'r3z;u?=G`//Rq)q'6qJ=;/Et #%L(Jŋ!ց_uAo+=BaEc H}ԘW`reWܪ> t =U^_z%u~di) (bZGB'I4{wb]0:Nsk``IPȤh\F hqz w 'O^*{凥4(^O_M1 o#| ЇJA61x C'N̮FOC!CRR2F kΖRc.-NoܴiEN٩WW"l?W zͷOS/)y-C+~"'m߀ؑ*"}S(7$WjAT.bV 1j~KBġS [^[ g*M fn6!2q\D 8 `\q`~Z:^X Lu:z ϰzᎋuj`W1`K&0:+tK[Eq+#C#m5،j'!X=1GQ?]K*zy3JPxZF]Z,f;C;@ήB Ի _jyZ,P$粘켂$3`0W_RI _#|o`lh2B8}Q)0w^Έj*ĂRϣr3+o/QB_j%sj'NN9y{ u5Ǔh3NNa 3ue g% EsFPǡsL IZa3tSSxA80>΋V&_C7/_tw߂_t5oE;?Ok{ߌS:Q`L oVa&6 k6L( 57!d,2>l.,9#,NқLzIybuo-|4.нA~ #GKoq< GH8\ 8̓CvO6Uj/ಖI`1̈i؟hAnC//S{4N|u4o{xa9b,I.M"(K\uuh_sSnkqtosK*jbN5y}Ẃdcpu jհ?Sxȫ,}phpHs|,TlG,g5l@T}+`qJCi D}D #u!vȡs~TW7`&nt`rEe0)Յ~*Ͽƃݐ°=ۅ>Uֳ>-qh4ea(x.kܿ >pZDOe}܎+rr#&tMsf?ZC:${ :P %*;{t/I܀ՃlM>k⡕Ol5yIiջ)JePʩMuU][F\x$<@}`J8軪7,s4^e]05^W5o\SY;^Ѷmtt5GxFURim-՛"5un lnNړ֨Evm# GK@<ڟոVGBquW@Z[mMzr2ԭ0Ʈ-gٵTm$ь0!`^̩^j2KLX0ѳ+k+]c,=]?7-Wi(G 7l^a^K܆/_=k+\_v(SFyPr4%d,}shNfFܝ{l, +n|C@eέRo |SM  DUd>ۧ%hC@#.4Zc=dmK3_5q֫_?n++_O$AJE!BƯl:wSפVB9 ΩMb ڃj.E6adyEob7~OMD**hC OuoYkq,\S)EZOe-1%pc 'N" N)푃Qh;c8; $XAsNE]8'z.qZ\:GpsNoҌۏCQx.q{nj3/Ul)#Pbs ac$}x7s> %STe霖FanM衬s.1Yak.`#ݿOQIz/!}xvzww-BQ3rc+FEo|d#8$߸ySk^ip|z^|`ڑUâ%czzy޹9i krN͌#d26J0*"x`F Q+[ 6)5h6yL zLƃ 5Cw/N젤ݧw̨,X-ٜE/Lr#$$ t~z$?ỳ'X~S(鑔(G'Ȏ,T{*Ny!t JiqsCCuj&im͵=ؐX[^aܴjڌ۷¹ &^v_7eT~yDg=4? `N;*2:,43v;XsUɋ~?=:ێ[%OT1KI/kȐEsv ==׊ɋ"j-*{rѥQ}qN}Zo.O$oh_XӢai:'l׮aF h=77mJ~Jan[k= ]VnZ~߮vQknh_\ݐYgyLk]&:wיKr塁-NOQEݎaI9#g f&LQXbO q޷!▓j 5ɥ` Iҍ<(jkU|3xS:<L3f-M'P}JRA|({Q\k]gäN؝y6ծVZI,Ҡ"d\0d%X`q1Nk8cl̎Uy~LTWVWS!jkE6KжQ{[,y_ň2ՂՄjivMd}= 'ƍD%ɮ8p>]ūPSqXIDs1IۑΫR75S$rDoײcI٘3lӎ;TZ늍߮nK]9 n̪ձeu e?κ\JM F7.¶gi8XԩiiL?ˑ>'tڦ;|N] ֡ҧ U$*?掦u,(bﲛev>g镫[.Ѓ; N jJa,JXچV&9otXu]+G-*--UjYO2|󺦵m^_XG#8 iYW"[O&n;Hn4Ĉ}рg C1MS %2 J^k`fn??h x[,Aˆ9t6>6]G ։(Cq3B9O9hLe; y5j Usܭlll2ǹiB\6+!$ݦnx%]͟p긻uMCЏm߱ZiΨsKg˺燮?rX~x,!tӾ>ߺfh|{ojXnEK4InO22]% 16@JbL)bNJt^Lh*cB„7 O>Ȧp'_m#}[[Bxu|A„0^]-?R$ fl0aNԗO!<,;t|23T}(Z )bvRrȐvv^p**y}[[8Ei?_^,h- SpTKGT  9I6S!)] a*y3 c"B9ES$\f3Z (F jvƦ5Ve|_˪WU;!Y [w]!,nkj7o"@x'샕eX5C|`h('ohkY-VuIFbl -X/`hݳQ FѶ=4x/֪P1=_?PC/> w Ǯ[yǖZhv 8=SU@ѓ_U:=(2V#gZ:h-pAR\3MRoAH'82 /L(P B$dw*unS5m|7ezWN:;YcӠ':LzE Ns8q388I =ς U$USO<9<]+wTk:< {>^h,,˰VPm=G+ǚVD؄4=_{{UwbgvfA,ofٶymS4xfMv@Či{G}gϛ|p]^Z@{ H_ hzͦ<g@g@4jn `#GL)\gϾud h94ht_E,* w%'`u] I;$Hd{Fl||wUZ(NXZǪݱ*) Ai_'-kZks?2*X#WM1Oۺ&e^Cdy'Ɇ-,.(V.%ҍс87׊VDs")p=O>2ݍO7wln74+.}^˷][~$߃´}DܥWw-j}[O6h`QD^!E~m A/2DGy/E>EQ&υX_cu}kz4i[Tֵ v2J,WxtDjeg  B:8vdb| s`i5YPh a%trY,,ΰ6,X<Rk1V8lzo1 #m}^5)iunf=$I,1 3Ơ5,fYʽ*:Iu/y"ۅBl|=кٻgrѮmuGY%.-2ۋj+, >~!mpԎC5X6pZwofhRA3Y@`(5UpQ'8v#OwBD*!&96[σ3!BDEʴFHٷ9g;!GlR-cib[C{9HrKKK$^BÛ{7@fކ!̵5e!9y]j-V˥Sdvc zK"J-6ܹ!̯mpyu+Bz!'K6%M䒆 +\5ݺ/\;uƾm2kk߷ i(\ubMm\d.=9{2ubLwBTw cY >l0Xߊ)S.FX6^VFA,]PlB_X[KNmjjr\HUn0I7z &ݱkh{Q7\L7lv45]{ʌei5V4_ u{>^oPta >ZYKx 7Ujܔa^Y.pSA "%ɻw\wHe8[3#-#qx%P>ѱ+uǒ_iߧ7ƛ wXSn8o xɲ  ʾ[B3"CSXFν5*洿 jH-}LE?%gP$y˿O[y3P0f7 oר4qF&?GDT>Ξ6÷JñQs8 C<%'^Q!zW\P= r۲q+蘣*$8ub "O*9R;,Q8 v"Y)^! F5Jpv xD7@IG҈J<^_zJuM&^4@a|g+ʺyO\މQ /T,ޏ}T )y⒒gTTN`J%6guèF}V٠T'Aw&qټqcuhT a;L& &lC4:XJnК8-Y2w6_yǓ{^SRr쥊]9ڞ]ZZܥ ڌW,.-]ڥE5?Z|fB@XE1LРI u>.K^R0iUhIs3{ќ\< 1 48 i9]f O6D :z&:MI<ruYUrB~cOY/7#^*ʱdLTNQڋ%,:@؎&+y`D(]bJ<6J#R ~K]H4:<5/!VG-2mu-3nZ[FT v4@ ੋG; |^A}W~lD̛ĝ_mC=+ѲS-v5jpC Q,@AZ"iE5bM+ibPd@t .ZLҢ6׆^B%Fb|Wl/Dl oDxZ^P@Ŧ+uw+m UONhThs!Z]^S2_VWՆ铐hTB ė]*UEfj$!^ K`/6|7 F@"'Q+Rƌ1%(> Gh=?PLaYv} &+2дc,8bFS۪Rg__ !H,tߪY :G]` ϔnwrmCС=}W)֗#${kA0pFԧJKjn"&Ng` `nvIͮ#5D  ]v4) "Ȱ*wO#SDUuy2\Wp_IsfZ4sUyky*9EyOYgOoz,h՜琁2g9%M% M_F8ӎXϥ5k,Źǖ0oEP^.R~1:;]Ų`b-V%>QKY4=Ruw*ܠ u ~ &4#n.CJD"]Ͼd2B$UL%F%I&!(Td"Y7ž6VQ|xPLK!pDH#G @XPt@)sCՠz[5-Bj])4UQK@a5i<&˥5"$IZOhN ؅lM܀#ϸ,ޠ%圸tQL޲eHk^,Pxf߼fyi1Z$3<30u9n |_BFBdI_9r)"QH9*1G)`Je?93skRN N,[32)\'hB 0R 0~H:ᤜux( sx)g%̙st(Go.FI1F #!+ωs~E+{? .jlÁ. ~Ԓ訪ɎhMG]2[^;~얻7j{«-гUt6 .{˧D4Н]F^!.)SݺSܩnb~*y+ }[ShWR ACd7kd PDR8j(ry-?ŷ>͖_=1w̍<2:NtV2^ @If<4.ݻ#E-Fhd0PiPC̥䄜4Z6;5kq=ɡ|VH4z9cbVlкrC1oXjV7*w^_WZ4Kނi"y'r;;pL~kA8Zw{=k#r9Xn$`W HNu;[] WsX 0*d_WbօZi,{=S69Rb'Nꪆ@&`3m3V6oz֞pEV5ij:NkxJmkmh&'y8gicΜ8/ikNQ@% 4'#Ÿx ]ko-½/nԘ8ެ&s͵<45>zmGuGȁ[ [7).]H~XH꓌*5$ Sj'y6@IOIޛ*ƨ5 4PW׀jArt_f2)d.u׼sxˑ̝gy=X30c8 tN0W*c=Vi|kGO6cGy-;ݬG(Ԁëj+r<7nˍ:&2G(k;!ϕ<%wmGȣ |B`݂Jj@R4`fH9Q)l'|#\,?ز2)c5ӱcըokέ 1,D`9L ޢ rOklܓ ZI6ižrڵdlfugT{*s!XW[k:ذcvX#Gʘd^3 ydl$mwAa'NSI%b_y}щ+G Å/$XEǞetgvgC=S͞TJ[1v2|MAv]. HʗmZuޔ#6~1֗kXyӲf7gY4Zet,&X#S4VR:Nc65aRf vM"4o(c#y: SG>Z(>7~XB;Bk 4ÿimWAPyeR~,~1ݟp!,W"V UiHYFs!(ܱ> _4sB{!G,NEU@}̙{zu&2ZVK[=G*2 ( Qk{MZE vfL3 ) vH~ &҃ɺ!D@ %,!f@i΋`?i[ر|B  IaKu{DC)͉FgqlUh?:w u9իe ܑ zg:Scj&E@?E'j@<Cb_I >[^4+?+ɷ Z o4NjOhJpbwmǬm/0=ס34gcȲ?*Jf9xPr9Q^%gףx`NCx^gŻ~cUWE/ a\( qSH搝L̬19 BNgW'x:1'#6V%ΈTO/yt.|IA Q,]#rJhEMG4r@&oz(G!L6AM&Fb>տ-+ml3=\B1-=Gt`13?Ct XeeT+x@VT`M1OkW/(|m3.;x6Q ynUIDV3[,#["~HjU@u՛9#HY=rYf=kBHvMa (bWa-X7[Ŷ` ƤZXV]ve㕡mmR |M•7 t_YPVN ]~pٝ551^,o"qmkƚ5;P6xLgkL?hsgDMɤS5A骓&Z~Xsqw/=d: }NU]{"U]—Y +;d2]St5&zXDow;$U?H1kW\M"3N&ӸK*TTߠj?L'qxy ?J7.W2 ~?+h1#*&'0!0 nʳ%kVMXa ?p2Yx*Xg{74RK} '/Wߧž ]++EDKjZFfi>$H[T3'j 9SeL\ ,sewR4[)FÞujME>lw{ku:-uގb3O?6?#8ϖA%;IϿO߿<Hݧ|^~+߈߿*~߿. ?,7Dq|qL)lMl=, )OI!213ND`ߖW)$ACQ;S$- 9T7"&F+~>98\c%46~gҙ!k$wćybSTlq?\۴]/fM/άΨ_)Wj!^ αzn+O}4P^i2oa4L%3sM>bPRpT4i:^ K* 31R%Jq^iN}wsCjW&ڢHY}= >x֚hJiTh}s,1klz?qnUӊ'vN`!m :,j.Ϧ6%(CKLp,"}Toݓ"ؗ1tIl?B)&Hл%;ߋuj6Y*lp)J W#3KﻏTjӫVi^"TF3|Cdj-ZA R+rЫ7kHBA+*dNһwwub\4($* 44haժ=5u۬+;&&ZW48d[>69Atdƞ/<[Et$%JY*SYf\jgNq7l'K7>8xi80 ])nAdI?w@6*hƨUMX(^P-+ho(XEt%9+I , N$%x< j1nLbv D./d`a0S먙p:)$ϫ5JJem[abze>Sf2n~2Nd)rF+r M%nGVƖ4GX92оǧN{<} Qmsbw4F/b1!?+ Q|4)0|G γ ~JE, k^HjH[TEsy-~j(ߓV`r묬V O}Qq(lJ|5 B/3`6 ,n|;6{N% H,[uvϰHAH b%" #YȤdBLSr.y>^m M2B9%ek Z-/W9UIj),GGz]#X+IvnSܱ%Zye y'?ɒBXȷjdeJz Ql,W^ FiЋ2ęj/?Ĩ1i2f){W,f3qivF|3yZBVZD2X dy6Zn6}pRvP=3 MRf7ф#ʈRYYþYJ -r祭 ͏Ftb:q<)XAg*R*sCerY)xtjKrtE&f1fkx #'՜*IXS"8c'Bgr%rp`ك˼^2),!PB}waJFo֧H App9ՠ?,f"b`.r*=*NJ4J]0ȵf]T<^YQne|JJ7tΔ |`Y,3eq~/,$y瓏ݾXrm\ e3_[}"QY2Naai|r48(qJmʛembVXDm c$8:)Z-ܣdkw|=3 R^6ۼ3Xn˴\_Ɵw 4+X>,bx V ɰCH[f0`˹sH35ϸ$jZ!'|M \ d XK:0p,ixC%}(<4crN  Z3he=-3{לg0BO3J(Xxc0{J=>ӰwEqo?\xl5D _:]H$HT?iġ"9d+m yز}ST((-ޞXs~g RUw|(&RIzc=/*)'x4/`&,O1͗ *}&DSZnh]i'glh2WXl̍jc3paNښޕu{Ŗ>X;:@y  |Yle8z4r`**6 /Ҳhrhč ͞dFaFVP1kVrFC%|=oѽijejp0ޟ EWձmنVƨgxrGXҶR+WBcZbu2 {@rq.b)ָd? ߕ`ȽJR-IKCp4 ,^'F$qI_>ؖs{<좌ߵ) V!rΔhy ?¡* u&ZnhiU0I<y&3 gx1~?̂~/ ]Yкu]I)*R:2}qpL % zvޟ3nܙm%toX*EcU5td{SM=mU hX1ω-Ha!C̽ ej+> I `.<7V8hܱe#ƸjYjy &C DH6m+mNY3g!ه[e,g(ki rL|Y\2"f7UagI[pa"p]ٛoMA9+ ewk>R˹ILϥrH=0}h)oPYN?_ڵ_)'&˜('Er,ˉ?]J:\|.(ڷ|2_OL,Cd3ͥϗ8oZo{9Yolåd\08RNFWϳ1_W)ɹΥ *`禪sSss wriTϢrr\UNY>qNz9rsn5ׂƝ;\MRGS_ZJ9tMX:;_"s=tseu[~Ye^ ~djzmIӒm#K,}i/]ԂT\m0bif ~?V ;jZ^y^_UK/W׍7 Ub"7eb\_J왼eeVW\}bVht͂׭X?MCisJũmirVm;߅©Ka禝on=?ڛ}oon;}g~e/Z~.+\kW5^.QUW>gO $@G Qc4D!\kCB.d4$$ˈE4 "HZkNkSKZ֦[yPJ!*漿Ιdf@>>7s/g_^kϙ8S?aCpƑqK-ϵ|MoZ|SͦM7ݺmݛmkSצnNܴĭm[ R[nﶍV']Kmmn_wwl[lҶힶm?hI/ڞiݝC5;Xh\Y7xp{9;v.΃;v<ܮ]J+qw5޵ӻkkۉk7*7)g=' :[}Vgo}Ot\3x ^5x ^=x ^5x ^5x;;c_ξyѿ>ڟHߙ}8{kk<]3kkkkW5x{?"1$D$ L6 \JJjk#`v#S=Z7^h` _\KrQЇ4r\NIs9i.'夹4r\NIs9i.'夹4IЎ~-0 6ifAR4bqq.q1D@BuDO=0S fgC,X"l)KNbv6!`@;6i?D+p ֕y3Β1 I!\h5@dA;Ò%Y{tCdi1 $Kc-ӈYĹur#慮4u`!m8Oz |Krb52̿Ip fQ6oCoS%W@Fl4~M;1XxHXşM%.YXףpVtXBw1~f|e0bgPX!+ӈYĹ*X9Kj 6Q-Fcn|z |jB=`v?xD>˂^eӰw`& ^+Ca-@ h[fm)ӈYĹD!4(ة=~ĦaKk׉\+PoE<; vþsaq. Op :Z',&X16c̿Ű1ЭFL^SR*1AEK\G-Q6XaBZ\X,AvQ$M+`iIV<[*k@/vR .~1` k71Ö\  ,+Ə b,^i/!M ;$fB%'I،J0N+"C:9˖Әi4rs9\Nc.1Әi4rs9Ƭ8+f~ f"T@?]Rb |@e>8c ߛb#qBmk;۴ciNAK{<}|^.nkJ GUv>Y:EtU+̏(<=f"7z3Cnnp1I8F%j{*9H\FS C"ԏU?~UK+JFXѷA'QĺV$R~)QX6@ nĮV$ g*0@l$"FS "!2_V.*%r>ɏMY'7щَc, M%p݃&.jBM߂GhG4aftjN fjm\)p)Q͐APr2>IN4bqq.q.;S`^dD%yL%3smpv QإW NjRIT0$L% SIT0$L% SIT0$L% SIT0$L% SIT0$L% SIT0$L% SIT0$,/pp1 V  Y<X]"*A<͖/#?_F~|2eϗ/#?_F~ks%@?n(8=ўiOۨ `3cڛR cdk Hdk Hdk HiqiFiqiFiqiFg;^ ޲w vþ`Ǘ-߷~B} -߷~B} -wB} -߷~ڥꋠ[{ FLBXb!լ n<b;| i A` -v_}6]0F "%]m$?"m i$ tw$?%^EHYOB5PqBCKLAӈYĹDf|F``H|K] !v'CXD[0DjȆ=; tcN&CKLli ,\b!us l#a (dlVQdN$:IN{ aD<Àcgu6]lΌf1 ]#/:91&MJӈYĹB껎n#8ppVzVz<VZ .,E+=KvtS~1 lc)n`188X!ajvCniGD2$#*,wal8X}k `@lbvY[oMy@{otJrK8AFoĂ]_˱Lp t[} t38Ca1ıBO\my ?6x὚,a43x-c+`ѴF1YU,^MU좯}Q}ޠ}YL%3s!b<'ŹzW^w?zHENXGy>@1Gyȉ`hX)o!h#s|J?91L{sHA!9,`dȢ.`L!Ac^ϲ`/c +>&|U?fnc̅2ւ}1#bFUl/c} B_ a]2 -1`4@f(qT]5[;K}4 QyQ^&b4w)f]ExQ +>h5J Z)ث%hׁ~Nk`5-SIAD_}12B~ED_ȏ苹/TUe.KL?N?UTZUG҅X\ihJ5@k笥44R A?[iKku'c2_,0N ސ5hV}L?ij@1c=EaאO6 VEZJ_GG>qEy _TA׈-8e EOQ==CO&ETxYi!rWO"˔Qb\NH W/Z:h7syLhށh14N4$njQ3ߛoDnO'5)2ݟv…O9ax{A1k O5'\?4cݯ}:z.u= ?`~ څ޼F0j]KF^zo2~m_}#tT"g Մ[_8MnaZir5 Dav&VzRiT % '.!}k5WyD&VW}A;Oz5S!AQ9Ѣ<|J<# |eFEqizꮥ3"|DycNȘOB c>s>Kkit}E= |<;Di1N":h,B) RGtcP^J1n&PY<%%4GF[@-hP[4h'z@BE~EA}y西C6D]b{W-l1,u(a|nƞM 3)?_Ι9#1s$*]9f]y%$3k ,2 p *tO oBH.ƟhS@V[NbbTXnU\uWDe)-Zw@+}}9gSI *I-h.&ỌV92W>#$%՞O#( K҉kA̟42<_[U<3|u6͓SS)=EJ_ceEZ\~ _jJOAYS]CSS[)o1r"jړ_V[^W~jUz*bjS9NU3ݻ[^V1fD:L5+=BeJOCmE\7S-W^WVz*W赞J[/GsTTʼ5e5>̳bZ?FyZK]0Iw7 S\1DElzgEGR _M[[_Y-K,JrS&¼0 ^- DoÚjjHaR2*P i`PA%zI*"؈+fc EwO Z2 a?u2gY2=zk1bj= HDn 8+#mnB/T[O*@Hp*VB 5mX Al^4++EE7<7ibuuݪOY ZSIT!,7V6p /mL+#ں2z0X㖿Z+*vnYB}bz䅉OOSoyg/)^),hvn^gB'L,]?a-r/̟ə3wI%Ey xyf_WX0;u(X;{,t7lD Z<#&4@ v]^ь|s.]tgyb̙4SST<{‚"O¢ 0}.7{"̒w]޼bP[gA~NAM|3.-=+ؓ? 7 Y<}*,jFA&yrs˙Gc"jfH8?0_Q<{<8 ,*xI BfB1Ayy(B՞((/\7 Kn^NZ :G6No <0h>#3c `ok6 }$0f4b'RO(J)|M>k{[ mS>kxGo,(^eD'>fC٥,]ŒazM8`IJ6݇X?C@^e=UHVV- c~&&)5K׳ R$K7I.QrKHäۥ$it@+X}Ri5-+jd.nA-5): i7z7Y<y%wvv(3S~Ïw{']O)c?T~ZTY*_(*ZYQZlslm:Vf/lsldml #l3Lm ߳ԈIimv6aGall#؏K%[U"Mmra"ئY mmm6/6mߋ4VI)P/-&6EM9lmnma!IYE5$lsNV R7*[tc\m/KM|),Q›Plm66O6 ~b;v)#]%|7=J&P*m6a`ӰQUCkR&Uڔ\kL1]f"FFNmF6lf lim66ͷqWm}QϰM/+Y`l3,maհf>qw6/6o6ܲ$ $9Y#Om2`Ybئ imZ`w``g`cMl>l'qOm`WaaFflim:`66?mm6o66`^Aq beM.l3Y ۔6^ئyyy#l6lsdL?Lizkimnm6߆m~mیmaBئmMl}ئ yy9r%3˓.m`ŰJئim6a6'XlbͲm%YΖ\uM lsl f;ls7l= YelsNqR#5+u<[¯I*ئ|y%llsbě%7K%^\BSy:oVz| lK(ls lscxS]Sϴo񓦍fl(l4lۼۜm<πՂܵ7:,aH&jik;zRA|Zfj=ڂ(s2Be{[r3D/$YrfU<'Du:Zh&IU>wM>{TXB54N6K3-nҶ`Vj51א!,>ƍ))nOgNqy}[[hB} )V';xl̖5$+jl+d=*ڣuo ΅((P]zlyM[[i^x_W7i皕{X…A(_m2DJKjuM]Z%ңE냡twEaE6wWMluf15\p .Zeg L={$'xX(Zefu$yP><(Y\UaGLV# fg [L(=89P3Гh*>TE1VcH/fI@8rZHGU8-)hv&Ycl^pGp_phY\JڔB^j2sq*Po zXی;z$\MN=ݣʳZEjXGdӬj[^=|ę&vp3{Y/KK#*aZD J G%a`'l/؎Kxott9m|!HD',oN܍):L Od'jeOViI\Ma6ħ 4eS$"ld7(FhH(&FQD;p2J2)!HDAłCA RbQS^Ύ T@eT6pl"P؁HeKVE׮m b4(,kZF+8@Ba Xѝp FBa X0D(VNV&)Xe8+!1r6ī2q3ECˣce1p aȧ6%2flV!v/tP5k:dzE'LoPdޠc~ohؔ&["4ɨ;Bܲ;-[6mJYoL,N{l:Zi%ll_'W % eZ!Dj˂"6ǍK7n4+ŸbY}dN[nVLawqqKmG=Rj(J`;ZnX̒E?Yٹٗ5/{m,,lG4OE\;Hi3w1BVD1BevoX"X$Iء@+6lJdaVI5zt(L1Hf}8.M=z􁜢g@=zE<=&a# \I &!g*xڲ!\xfyxvxvdF+-=׎~ܸsƾ1B(5JsZSR[Y80j5FA)_2U%GDj T=z'ӃxXDcO+V0&9,W>Kg׳-lۈN8+vlۂwf좠Y/\-77׮G$ۭl%xAbZ>nVz?Fgϻã'-9C œϢp^b"^Hk9ߧŸ4;3qI__"9 EQaEyLecD޻Y7qeBz+ f3lgv>N4x[E8Q~$gbց)$+JHHio&&F+ vjlͺɬ<*z砜 ;E$'4@X$N _Dzw"Ȝ./x1J7bEynJpg6(W hRMWs[Q֤I$:-NÏG},42>3E뙙<"Sk8e 1p ϳLq~;kCr%'"apJ3ҍVdUD\PxwprxuX(}bAVtPٽ2hT}E ~e>$/ıS"f앹DNSFv*2{R8>)tRrG8)dg)pq)ΊglQ.|Xd.uyy N8/Ne)81 &qb`Y89,t{V~3mw^[O< ,vAW8lmf\89d[DLgB¸ ׏~v g(Z?Wǀ<^qg??8?pgBv3s''/O9*S(. ^, m#xJ`Cg'5sZ%\WWs]:紡"V,e51d; ">C>FaOQm@Eގ_>bt/ Շ. ́1 BNYv*̉C]O?uHnT%''dIkǺ! )-*YkViKMQ,F>O'.al.9i>|{ǧ,Z+PUUVbbVUաCN洕˂W0qķtZP rfdw jkk]]5Y87ϛUUYΨۚN8>׬+ߛA)]]pY⾭3vTViiioq]kC1X)8%c@V8Sz:c8 fwZ'www#>6dsVzWE_ϧWEy:ޛ+=)ėdk pn_#^͠y.͟AK9T\ɸl*^rw)dL,M,o M$眉s2n6qI6Adž GC$T̄J]3AK1Y[Xj1NL(/ Q-vSi)jh8xP$$~O>$n[NGJwzxP<NQ+%.e\|5S&h->q).nWŲXF1DDPV3ғD<\=El:YGKqr:.-8Rڪ'iXs<"4X/.a s]k)aw&B4Y^/0)(.끵E/{<]f%5[ح`s%&'XrQ2Y>[VoSrS}"3#3.gװl Z]{GqKXL\A'o%% >J9i3*jV_"$CN|xaYQW&Gzb q+I^>ă'!bWQ<8zQ:$ *D@L"z㉓kj"1\I,bb ^s!ۉ{*ă'!bUy85;3nFc㈗ӉSӈ3󈋈%*rw-1@l&ۉĽć?p&>KM|%bM;SSW+x;xÜ^ _NOgħSLpf|vsU3 +◀SK++7W׃kVpF`n|;ќxYCW?N 2085Y`F9gĿƟ?g&e N 1 W% S|pF<07KXL(mNIO/KOxKi!nY J闣Wxƻ T,]74_G}]'{!iuY9V9~?Ixb4A.Kz9(#rH>*'9n<R^σwGx=bW2*Q٬RP~t)/*o*gLfpxTL"S!WYd$KrƲnښm[m}v>ƞffg/wC'#11ő(vT8ǽO9;v:DgT3.9nj̸n7;jWK}+;.+ߵsr={;]u?~ސ!C* i𐧆<74ihLJ>3з[" ᆄƄ 'ه% tX0߰vlNW' 7|+ oᇇ# #ƍ2"wD-#GqpF>t8.qJbnbqbEb %=qÉ_O<=R0r)#sG2}Gyl#O')I I㒦$&'U$Zړ'L:t,ӣQ ƍ2*wTQQ-GupQF>t<.yJrnrqrEr %=yǒ_O>=Z0z)sG2}G}lOQ$wWVFOf^)S)(+((خT=G˩Wƴ/G'Ĕ?G_~o+vEQc2Ws2f+zFO]BwyT;W4{w3.7j#Hc wh*s/~GtyN[tynsLymLɘr_`zLycS0HیFG]=K4\sk ΖVxR?z3(;z3?&Y%(yqBzz]K2ңz:UO Fs=(_Hv׿EVz`V=-5v#= .*5wtq.YK ]jȿl{N)ω)ƔwG1ң*]rmIL% kS9MeL-»P^OpR=źsE*{K[kXU= i8#}VO.#}DO}}g} +3Ԙߥ;FcCކh7<*'Ϩ}cstHC=]cij5iZF#}IOaZ#ݪծ[aގӍtoן%j²X.+`xG`Ǝ^o 4ܨӌعŰ&V m^9mykZLyJLyjLyI8}Z۷q3ɺ ?>lMLbg-}߭g[и-_m踲-#mOŔGoŔ_(Ckwne9j w>;Ecʛc{0͌9Ltr0Z[OV{QkĜ3z]f=i>EΒ%Rv9dVĖ1c1Β{ 9y@Of|zz5v}0uZÈKcouoWv޿6 /w[@m{7f{7^h/|8/1g9=}HԧYhz 51'pasǍ؅?5"O<~ZOD˜rM슞0knuK1҃&SCTqz>eƳ/g_C0ҟ2-dPwt\;l2f?;R=}8FjƓ\Czz^O[#3{¯z'V>mgbտ~|ܿ~EOm7ƩFjh?SGzH[#5>nޥ}f S72kf g>Ixь`%HIcޔxzoJ#wHu]K,#cdtgdnzctQcbd3ZڢOɶ_<HFUHvTHH"9TR\{L 51ZfDF͊_LXϕTnjOO].Uol iETTm{tCQ4$ i=;2Q)i|LQ)?"uEJÓbc߮H՘򸨷 i-7`t99!}r <=*HcFS>]Nm̘S^]"=|rk`yZvt9G.x&ھ3ǔ.Ggcʥ1*)]0dD]Eat9T]U=D_n.]~wosj:3Ḍ1)?S~{zb19{0I5gC}:ri'Ҩ+v4w2sW!̤w)j:Wq䡲$3*8p85 ye0L,~6|7OJOM9d }dO }=#}=Þs^dg\Qf\vCQڥ@: K}(ԗg!a/v!}Rڥnøq-s.P@2\'dpΣTSHR=KvӬѬGy9&j&g$;؈Kr R4)Hˤ&FZZ:m.6i!fmߥ kͲ풝%py8[%b{Xv>2%KJ@_RLrrSR´ҴRbҕ&/M55LM Vj{V$9TNj/_d>\LPSx:UGjOR3L>JRx:MGj6NW\5{|5_%Bc*Sj%uW:>Au6 |Z]/U&nP7I&uOS[V>Yݪnmm2~CPv>EݥW|GïR{yzz/ZP;xOǯQyV=Wԇԇx0QOWPSO՟K}J}竝j'R9/_/P_Z5F V-/T_߫ ??|g|._TO)_VOgԳ%Kxˮ/ɮɼu2^u%_r]+]]JW+Wffr+ߕotq7WrxU\\xkk kknpW J]µ7*\|U׸]ռuFqoj]|U׹|._ |ȃ55fWotu巸ֻW\]fW[]VVյӵos庋owow뺗pu6>>~kk?zow=zt=zr=zz(::?} MUgs}}==!!g$ gz2ɔ$$gBy$3B$)ux I{>}>{=}---#-#VĭV}Gm'jLc|5Χi0tMT_ZgiqZjfyZX ZDZ*-_Ezm=_m6%&m_m6em _mն6m_m׶UNm'_v5^m/_uT;#vT;7hǵ|vB;7i'svoNk C22 y֐7_|! P0Q!! ! !9! ŇPPj/&=zؿ^7v]Rf^ZIP*J;LL|,xPW>llllllL vv |+/?^GG''?N ~\\\\.467wwO %[h%%\V5zx-{ '-Nj5?؁n1DbLjYzeY VUĘ#5gX AlǦ9l [6ml/;NW̹m`[nv3E8RZ)3FbRiT]/5HR4RKuR+)QHi4J$͐H+it@:.IW <+ύ9/sS##"$/+ڼ!o;7|e| @j>SM|ˏ,$KOx9U+KerMXn8ڪC+;t!*N1ݐwTwbzӓxbk;ż#^כx7wC<1MbB׽MC;;#.N1w1'ƼOSLW^H;EiH1C\>t0W^#\Fzv5kxW^\&zMqktWO\fztZEzM ^]zu5k"-q[o W^]ָzuZg^\6zmrk鵀jm鵞kח^[]rkW^\zu~Wo\t;w[W^\z}굙FzZ;Bz}uWS^?zugW^zu뼫W^z]vuխkQ"~>'EqWiW뒨5cCMSyޘ7˼=;Wx7qջ8^:¿G1~Og?'~/W[X¶JyxSތÞ/wޯ;{x/:}R|%_WcϷ37b?9̿}y_@#U@s-r9{+&o)?e3?Eq)!FÞא#W/X9(ɚ,+dN'!ħ`O?vz΂OD,s2L@380hx#FD $(^?q[ަ@|a§=x{/ Ї\M69B6n%xHi%ͻ{2Iz a0|CCzs 0F[,c1f7&dT3ă90|X a,%rX+a5> 6-%l`| a]^8#-cp=(33_W y \kTQ$UHUjOԐjJR#at 5J0hF3Bj1FkG4K?Ig/үo.HKetU(c"DH^[7#Kf|.F&bɷ0o;N{06o~'"6kyC)w{>y|@>(Go1|B^>) O?g/o9|A(_/W5%Vj iZJ}V->QW˪/jEZIVQj ZKQKj_k6W_V[-VjkVmW;DY}E]njwS}MVPo}շԷw~ju:P}OV?PCՏapu:RVǨcqxu:M~Pgu:O.PuL]PWuNT]~nP7KuMZݮPwuOݯ~P[zL=~PWO?OgzA^RW/Wԫ5/x:QNVS__sy_g+.W}]}|}=|=}z^uoo??Sz&/[_o?{?Z, x@(Ow'?N~ 89p6Kos KWWb!J&شb>!،cGĎ;:vLqc'N;9v 颳4+C-aJscyx6 @O C(pq6ߑ9A{/ ɅМ˭hЬh^aqvBͭgiߛ[)}}4O@3{@N146{B*%='[.R{i@QRԅ;/?8A0HD?C˿F ߅t}H? A_-7h0Bx O>"!$BH@NKFwwwM jSU.+>,y'X|Q=^?uz]n)==SzY"UFݘw(j`41M\V[[j=.F0f ؁p DTԁi d<z[O}i0& ȣNէZ0tg̿z]cLA;&z+ܠwW ϫ[I!P< &*#e kš@>֌5ecY(Ⱥ$FB]6} R;tnS!}ץ7-]( 4D^%Q>Vxx\l77[[mbEE&o1o1--ž>}me_z+{+jj+oMoM[ۀ}mmv`%Fl;K%0nNa찒;%-Iw^ z>6a:)sͬa]dA D'x =owg4rY<OySzkoOB_RR+)iJ:%AyTɨdR2+YJ6%CɩRr+yJ>5vllϾaAvfGط(;Ǝ =;~`d.?/_WU~`2L{7ChR)!FE<C5:)EDiM q6P|☆Qhf>-Y"b~l >1*g4stEw:[%fk3Eݙ">) H%2Te__[}|&&>MMM}pG'ТGMOE?z}#myB, WH+\P $YMmooooooo ~;GiOQGIORSsRsw I=#(!FQ-bu9Q<8) Z2HE%A) ZXQ*Zx*5qQG>O#c20Zl lwQ |}!5U@"J(#ghMhw tAO;z#EXa1zð[Xy a~˱^X?J?*?j?W`-Flx#dh ?FO_c4. ,1)ϲ۲I6-ZgfNW?4/h:enF& ʷb|"ͫ#5ȕ;qrRN⯗"a?7qMq7 6 Qǜ^8'4|ޅ l j=N #VI|&RjC) KŤRRYlեRCG]^R_?dtUʋWإW5oKa8i4GZ$INitD:!~.q1NtO>oYV1 ^$^"^&^=A>+B%$VxF8Ƭ$fyYm!P\1=ė{Wx_jįzCįyuqb*.ZY!,;Қ,'EePqooooooo}k|k}|>mJAJT^E%˾+k~3{A?_________???????????P@L@0x,P x`Pp@%OJ `PA3x!x1x)*:Z4#͸Ͳ(8x}szRݤnK;4wG3"jl>tL,0< = [@X@Z1"f /O?߃@~+|EW&?_|UW_E_#WO ލzϠ^us ԼW)|dM kƐuc*c<i^44LL|S{HK@V1vij-|tk8yt;;v,L [L~%,S&2nr;sNzN1*-<^;]U֊7n{/+jL'bj6yl~%La-E˩= jړZIVZ{N+=*izTjg\Vea>Ui1o|L+vGW* L]t44%F h Z#L9xZKi(m{=iW@^^K[{MRi}Z{Wkô E/꠶erV*AVVk5:CʣA^6;QQ 8OK<@R%J2tǿ9 N'#?`a }!Wr!3#9dA9d6IfdvHx9I| iRqό,wۂ˒ P'AmyS/U ĩ?Vn4ıƧ![WVL$MTG.eˀҥn/Quy-dH/a-ݾ-A|_ǟR<%=SL}_~rvJ]/]:^Q^)݈4o){ID^ ˞wn~J֣yS?Zn+zkB%ք<4nqҰ rl~X@ѵxAxt>hM+V*}QGfb1!rEQRb>h 8h0rOXˢwv 8p2l&43-?Y3*H h#}\+aAZX{ZE'>EZT{ SZ1ŴHk%ОFQ1RagI }V+&%1yl;Vێ-"O+QUAZEV i-6ئ$) [V?RbIy݉ o;~U#e[N#gsr)\U+7[ɉrw_"'3 yE)˧sUţ[IdTr*J)RU4VZ)Jw_BXn"ȾYɾȾ9Ⱦ9Ѿ!/;)HNwRSr?yhȇcI'SYg%!_#o ,ow#) ?V=|F>Wȯ*x&r]q4S+3*ٕsc A^X)B^R)`5RY:KC JSJ;3D~A鉵φ2P|2L|2A|2C|HY|Fـ|Yن|Sُ|rH9rR9r G<^Oy{pT==鑧ddGՓSy~OaO1E=%=eTF^SSymO)ƞvx=]wAg aȇzFy& ♁|gge5Wy{6#ى|gOxN{~E~sseURUPujǫiՌ3Ys EՒKr˪ȫj 6j"j'jom}`ԡ(#q2KUzMV[^QGiWϩp/x=b-j^+4 ެ3{sz#--GR޲ފ+xzk#m V.;{{{#폼ww(!qx'y#]|wwuM7xx#=|S޳xy/#11r/FC1c;1ibI91yc b= !OVD%QB2d"$!LHfB2,d!$+!Y JH6Bd'$;!9AHBr"$!MHnBrB'$?!!R qB'qB R"!)LHaB !O!E)BHB$IB$(!E )JS! $@HBҀ4$!!iDH#B҄&4%)!M iFH3BҜ4'!-iAHKBZҒք&5!miCHBҖ'=! HHGB:HH"!t"!LHgB: ! !*!ҍnt#;! NHBz҃yEH/Bzқބ&!}CțIț%/!} GH?B.!.! OHB2 $d ! yyA "d!>!2 &B> B2! #d!NpB2 d$!# I(BF2ф&d4!cCB2#d!OxB2 L d!DdB&2)L!d !1!2L%d!FtB2OOA Bf2$d!ElBf29!d!s K\B2y'd>! Y@B,$d!YDbB%,!d !K YJRBe,'d9! YA BV$d!YEjBV"84-BnA]B ա6Dpd_{b,fN̲8E|C!N磉Db!vUowFXb^!C hGh>6y0&s`,qekq,@k5hSX DN+8 OMg%DwmʠX_\_6 - ۧ⛧[1_ Q>asp#hc$GQz4Pz,Qz<'Lt яN' ]9ҳ!:<.+RzD]\ljZu+ ]%@E1"0=9b^!7̶4Y Lk&Nb lYҩ6ÕefOw eJ=X+-\=r[I'`N9YIX/ ਍z$I9kI>!p~bȱV*P-%W~[@?] wK|c  KqMdR c-pk}9EQǓңNZ7MHi) \ oS9LiIHbtTyIVҮ+?Wbi5S*Eszo-W<;`d%|DELq=iLu<2 l,Nk3?Huϻ:Đ1{ Tģ-C5RG5E[Q'o#.q݁~*Ƽ54zr/g Fzt,7Y{=侭r}Olg>9>3_3>sοDϦ}>'9v'[⫿Wrq%$mL[.f@/bRn<7_ktKM6HWhQmwm0 64QhඪZ"QnNw f͆G&+\?/ۿnWgVz`/_mܩPPXĨixM 5 u@B #BVGszED@+NBsTq% ze@ 91=^O^=*q¶DgJӌX_U6 ~j01n:IXL(S!Tz J%&S5"N ޤ+-k#H .]StKTա2mAypÚ^ݽAԎQ|-A/[97HDFvWzc&z-ޡ}ݼ|,wkg"/@$0S_UKQR DA;):rD9DZZel/?EL7Zkޟ~P=UngaQ[QKniS6OJSn pd^dP$t%ED &IߝRVVVVքֆօ> }0Cadc1Xk3>5&{q8md1~6ΚMTLY|,e>k63˘ϛerfyYѬdV6U͚flov0;zo}qxs9ќdN6Sї-B?\e6̝.sk3ߘ̃!yIO?֧' }>K}~VEUM?/Ke~Uf hi2ZmF;h$@=c1oL0&2cXi2Vk!q8j3'+U &3sn4 '"fQ)Y,a0͗fKa4_3{s9m1ǚsy|sc,bgiV-2-˲X1@dMdmd]dsdkd{D|ʢqj9r^TEKR-ZD܄j)rkT]6{R G5>R}$5NoszC}>LG}>N?Go1~B^?O?ghd46MfFsexo6cq\c1X`,4%cm17qѸdn\6S͇4fZ3|`>jf43,fV3a4 fll61]nfws9|l~`1?43YVWsyh^27/W̫5 ,fIdKDg[[u:a}o~X?[XZY笋6An˶bvvٺmږma;NcGvf;nvA7>F_c+;?M@`PpHQ}oz`뾯["]{M#Rc2sqq 7rwz-Gs*8@JFls]S{ %Ũ7jUYqX]ݲҸ$wrG?.fBBseLH.#֩NkթĐޟf>hayjAy.o 3˘`R$=X+¢uwx/vt~LyҤ짘w~q2'i>b2{%Asy4[IK1G1ni[mv;f{ٽ>v_-m=~~l`?Z7وVrD T#tFC|_GJ_.]Qi驴^o]QF3/mbX_د??.hct}:3yEAO BQ0(.%(x{ xWͫ21 k+sE ZzvAxShv9"_K(|zLtՍ81"/ƞ)Q(l|al1ی.]l1ӚL솺EWDom Wo`R|bEzL!xl}( F]Y1netHh3-muo[~JOوoHşUn^Xl -ڇ7ac9!/$2.0bF_;v}ҹ'r>у5z]1N8banw>~ +~g*`;v].oװugmWݮ\P.yid:6$VV02+6z71l`߂b^m4zS`3^Xg 4h[hk3A#t2~2~bG0 ^*g'`ZsaY6R _:+ug Fٟ2jan4H/ʃ:S> Л>??[ǖo]Wڝ9b`tx ٰ cy(&r‹wT¨f;TŸf7}K bԢ bVAc^ "}OzGe0+Ÿg ͸ZM֗*2|y5nId":,FVjV6l3 To/ݯKX:j{3^Zgm9~]qӑp$ %#5"5HH+ŝBnl 9FLⱾ2#t13 @QxRWZJgTcz1>bUD}3XݚlM_P%un^ѱ.f^1&yubYA3ZnB+HKN1XIOG z:a|:p9#"PM T뻺JxާvYvkG,J8& džḰa4C4tG3+_ W W  N7 7 7 7 ww '^^ܛ G_j0N@ar^Q|1 ̙i^@қFnVwjͱtk |k} ;#hIΎ5vLXG섰&; 3hYؕEfd KhJ2Z "ˮ}$( woaGTd=8zӱeeHmqLQƃBcd22jd1mnlO?ٿؿ}ɾON|6x)^!|fќE XXmƓvQ)].ohWќﶚ44z|H&:OOgZ7䙢mƖtN/ڏRO"Gֵv!ͧ34.#N__H4}èx--ײx0+B'iVFmů<ICyra=<"ByFLb獎h]X8+EX 9i [b)"ʛ$Jٕ +I$(IB >&$AKIoi| 4:ѷT+|In͍`yb%1\P͵ǚ[ 澆vPߒQy1y91(Gh$hqlK<#T_Cm|»0#[< wr=m}œvJћ#b%! nq8a\6&鰆tCU{\:oyG:vfRBdUdmcH iWa^!ox8EWY͢wBQHfH$s},󍻵v5b;[MyKQޓ<^Odr4|ƃ9i Ɛh oŒ3-f8q坺6cyO<Oƃqhc1c\C4orM>WQAq!g(@'oE?mKжˌ52 Sg1V}G|p "VM X56j6X6s%2~vrO3CR7~1"rFsS-in y_^r+Fû Y~H?Y,$l4Zbe~c1ch5Xj,qѸqbTf*\gV7kf& -0WW[0{eĞsLR[]sdd;v-Q[gzzH}@+l758[/.t:.Źp&]]ݽZ5-5 -t6NhhUZ}@MW fUZj 5z4Ӣ& lu({^㒻{w=ᙎNMN 6v pdy # p4f,=r0+JR,ȪLd흰KDQzc*I˂Ĕ+Ұ ,+ˍRvi^ViA#c$Ғ 2 *Ҫ6,ik)k뀴내 뉴'냴뇴DC† 2B:M)*v0r9kb3UMpЯg׭_Q,R kMJ&· #|'Sz/Pz7/Rz/Qz/>J_~J_J_"S4>DiF#>Ji(qJ^f;~G$SDӕr!zBPzXЩY+v]a# yr  (wF&m΢)BR6Bz#qCg3v8i'Lg3Ynt"WӋləcaLĉB\?"yi489tB;ƴsij -_@!d' Dwxs$a2/;Sʘ:,彪#wiK}گێ$Ҟ_wEu'Z;0Ye[iǬN;fM1O;fSӎ4c6-MUf.71ѵ f9?9?;89睋jEQ#ވ/O~6'% rğ62%ntO{j Q98/GOFo Da#mtgOw~?|9wީY#8 v%]ݮk׷vCnf7w'ߙ};O5;{gG]y~p{hqN8!'g:&\f44ZfZ3,NV1;iW--83lNj-e:E֭-?=\(\,z1q I)SßggggWW~sɹ"<"G< .K)~u.WR+hַ?; O)N~SNw}*ԟ܁͉ӭɭN0c1D} qBf*3'7]u<_ǰD c} W+ v?O`[a\c/\J`O"<-zc(q (}rx -E?23Eh&ŃqOuzIv&n Hا7>Tw5{+whFL&So6j.F7M#c8e5Λ9>,-2ۅ|לb3ϋyN,v<{ZFzn6TQ.ՕR+U%Ka8i4KZ$ImntT:!~.q{yc$>K*o^~'Y~_%+k)irv9\P.*KrMXn%'>ryE GQãQ N/C PQGCCKs`|cǤX?&1)֏!4ۆeamz |"n&-MZv6,8ZıSGi1v6(_|7h'C㉟zjEŽSy uCFZ2dCeͲIiY6Y lg:yɬ@jJzs< h8Cx'@2Gy8C dZ ;C@vГd̓8<ȓIl'1' 8tb%i90gN )A:3HtZ"O9’BOPS w. > >&A.....vqϡ3:,nO}4A Pt=n 0AK% Hc^Ż'wQ-gB̽P{! Hc^Ż'wQs/~gJyg,`AWb+YJF ݨ5tnЍ~ep\5tK5tK5"ֽu/^^)ֽR{XJAccc%ipt"(.yiK~^ZҲA<3-vil4cAvаAwAwAR;hXj Ka4,)zN;ʝv+wAHWv+R;miEMb;hXj K;a$"M?E~4;Ow?76U?scw?!&af/}~b3Yv}]~]~]ξ+ؕYaW_bװ_fײ;o1,s,Z۝'jR>ӌvR2;m)?]X9?;"2iT+ΘL1㧞adam^V!X/c՗us-Ou_yLy^`?O/+o29@ec|y}ʿG:"-!GlX75oY1w"9~GW[?suTspN 9A7?'0~N`xNi ߩ1d:~r")Ә71%W:JA'QTh?~{7^,mbJ (SJEOYIZ'z>Q[Fy~י0e'-9Dyfd]?ۺ{䞧nf"kiOfSCZ3dΧnQ7K(YG#*eI5s-rQj)GPC16 ϛyFsvlޟ3:h#->&|GZj"5|#PAwtklFr^IRjj -p?R9Xoa~jג9+i#{D{N2,mbvfV5=z쏬`#d7j: :`mLclhė1kAHQqi3g=ORS>U}F5jP]#-Cl}GģhbJPf&%rtBxr) y*#{IRJXeېT28|7:;1K!n/\@x)ȑy%#fg S2)_$ [6dc_ ]/]}/]N~I^ZL-HJvS_KKe}AGڇmoFs*rs^Q sTՓTwh4GxS؍l.p s4&B3u 7?bFxCj8 KS}NW8jHdqAqX-"S-4r 㱏Di+V#柝>C>f|g̙#Odn)lC9foiCjtJl~,éT[Ti8r>*"8:VjM5yܜqsǵk`qr$7-r&rB^)Wk:yA$D cyNy|[#䟓(_&_._!<*_ W_,Eɿ."*&.!)||||7KY6^G>8#R_K{7=pҲ~w)b0AR E]|LI3ǼAF7+i/ow?=~Iϥ;讌/+wR~y?x_ {EȺ+wg#%wg2~l_zWyu}+sW}/}///@rr= ~Oa:L 8z2=J[V!!*AϠӏЏPccT&E?M?MݛpÔ^I]'Ϧޖ3r#z^?9.gXOQ{2P3~~^RC'>dW`> Yƥi9)]D8='70UD7Qb,r D,Ҿ N\E XPT*<*WQ)jS#s-',`%@/X Zp 1B1ÝIJ`8'm?t- kNxZɲȖx mm]]]ݤ):΢4Ch3m#Q#D7ӓd/MtGK!zOm;=~Н.WgS\);OMK:S ,KY:%>ecʖ)RH9gΤ'-񙶑ԌTE&UjJ:Rݩ`j$ubjkYsS;RRS.N]pd he(x;쓣X?[:6ֳ! kПb=nitܝ'._dm` hKAX:rPa5O)ُd~~0%{6a*sWJξݺR?(^?)-uVwE*?.#OI]σb$OY?P؟SJg=f |A\[_1H2f\A"fB{B-^!v|4jXb{)wi0,fjah&Ie1iL:3`2 ab~0&Q1jFð =+cLL1ccJ;Sʔ1N\&`b^0#|Lf`Z)TAf3yKҹ\ɹ8)9svNJ?==FlݩdDIvU&|&2uB&RVsc~&S9n+og%S7ԧoϨR}_?_SoR]Y 7[RoSޮ~MCmN]wޣ"r@zLwא bSo]FEdrʵʘ2L(\g5xs|9'pΑ9?99'sN弑s:ʠ2L"]UjSZAgTڢU6fˊ=+Q/ba{WPЗqd{"g)*E}}OIYhɎ3RsN$'Ry^*!RyQ*%RyY*#Wm*H5T^-}%>ho#Q.妌&RƑ=&s}&$@9dY %{RJﬓmm]]ݤi:4om} _!2?} a;b/wzܣ=\>ɔ/IT~U*KˣJKR;|W*G-!rO*/r=ZjRIeT~|X*YRTΗ'_*J5׬oJwTJfk$s |E*HkRG*Hrts~W=4&EIC6 M&rd%Ribf"M 1Tw6\̏GN&&Wci|IS1k 5M4%;S~سsXY6+ܢr[mTkT1U\Pz3 )zS9O?PV=zER_P/WRPdOGўZ[wș#>I5@MZMՎӦiѦ#f6뮌9ȩjrZVyZ }WW2tQ@}TC1SJ'NZ&)N+&s'絋/hk?]v@[j6m֮-:eZ\VhJG[j>RRa֫9?[SSjiNGϏigg1wNxսb%XSDIYbNQ!]]% Sl'IDhrZڀNkCmXۨhڏh'h[N~V;vER˴_.׾ҮЮ~I۴ǵ')m̛,rl і6y?vK5ؾLh wwzvYBMXӨh4͚h&hZ45L|Wj5iz4j,Ҽ|'kдjhjD.~ͧ55 h>+ƚN34ıٚGdiGkiff?ٱY-*m_p@'?>v_tvV[7GS+hB֣O}@4Y_?$2D( ơi%]j9'R\)>ۼjy;U\wUة`6O zd}rm0$[%[K66˶=C F".Ei9" FBɕaz=Fϼ=eJz5-vz>@G}ץL3Lǃ`x/(A%ՠd@\F0,͠,` VU`fN[)TAp8|~ >?7߁7߃"9 4N<4y8iNp<4y8iNp<4y8iNp<4y8iNp<4y8iNp<4y8iNp<4y8iNp<4y8iNp<4y8iNp<4tC%Pp:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:I?iN pZ8-iN pZ8-iN pZ8-iN pZ8-iN pZ8-iN pZ8-iN pZ8-iN pZ8-iN pZ8-iN pZgwы%J|BDd*8L` A *P j@@s<bv,`9X* 0ց!l#G ` 8(8  >S4!p:-w-a s\-a s3ŭtÜ%Pp:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:w}FUL읝igGJEUR멍j;2?뗪ש[; [dv.sSX,}]%~L2˖cL SCd[d;ܻŻʯ*_&WOBp"w Ľ {p5BbOg0.}WpL4OBQ:~ yA~4/|Z̅Ә5[,DQ(z I Jx?%>lJE0A͡کNdSc=oX|^9)*39ѐ9bWB'c;Ap%I,Ii Ob'%<)-I,I,I,I,I1kLe1LMfRsKsq݋b6a((Tj—Dz￟-WlrDj5RPL|~!C1p}V`?[l8~Aׁ_TJJHfMʯIR~BV`?[lbR1fr6Wa|!\!1gུV/kݿa?q2]zS&[ʷھ#il3/πQ0> 8L1-s+J>F8,(bMW*jhӚ#|%g]s0'=6!FHU*|U#ߑ(/9ݽގxؖ'v!ナe|K:.-,[πӾi_}Ӿi_Ǵz>Bqt3˨j_ݍŹH(F<ɘMdmo/s#ʹ.JFb߻^{W%N 9Зڑ= `YL5G0$="=MaYv,]H,c3 IO #&8NʳyD]%[S8ŧTNU!t9P~C/ EswQ.ʘTƥR\jj(p!(P"(P&P&P&P&P6C2XzAէW}ZEΜEyP1"cc-8_yA]GM<_ߛ8nK~|V!BY g*Pg*Xl|?q~Ν,4}xUəJZb h7MdF)]; .>bygwV͵V#[dr1>ƫ֩zY5zEAHkx*mb9x*=!D9(?MZebFIiٌ^UjK}56PڨQvR/lTZp1>>JP?p\6qYb[󤹿(㴈] aYedYbb㴇>Җ2jcYiYnI=~Y'7ﴢ;[=Ls:Uiڣ>iӌhW1hRH˛B$2%.Z,f.t1"f1YNX0`v38s9\`.3ט,ŦY,9 X'almf'Sl.`{>dbWkuv3aأ 4{^a8K䜊jaK,Ϯavnew}A{=Şe/kMҸ,8glp~.5s n6p=\7-冸UZnmvr{!(w;͝.rW-Ku*3,:ΥT vs8w;].s׸:J)uΠ3l:ΣBf$T l]nnGק- V66vNN.ni}^W9Qo.WЇ3sTҧJ=7zީY?I?U?C?[ߦ_KCUu m=Cs+[tnzc?,d12L Ƥ7LV6 ߓ?P_̿=6&3M2yMS44444n4-4Meզiiiiiii @Q) w X)XZ0`V\tttttttt.H/ c^*  &L+Y0`aAoA`  6l)^`o N)8_pjs9ì0kzl5;n4GͭYsۼȼؼļj)efYeFl7^s6O0O6O341; ͽ~yyy9n^ohbnek>`>l>f>i>c>odja)L-(Tj Bk]+ F 'N/U8pQ% 5Bp­;BKU-  'N.V8pNa{age+ W n,RpW ,Aq)Y2222n,Z-eՖeeeeeee((HQ)E"w(X)XZ4hVܢEE-/ EE(]`ёE](\t覕YޢeE+Vŋm,RhWޢE,:StRբE#TkUaXVjuXV5hX'Z[ӭs.kuuuu a&Vn>Aq)Ye5b88XYŶbgjnnn^^ްg+5bSQ.#[*[QU]xq|bxxSŻ,>R|T ŗߴQ4[Miclf9m5&٦ffS-Ň,>S|R#T[Ma6js6-h&Zmmlsm.[mmmm a&Vn>Aq)Ye5$$DY”J%g_*i.T2dF쒶% JzJJJR-ԒED_b*8J%`IdbIkY%sK:JJK,.YR/YS" l*Zdwɾ%GJ*9[rrɵvʞfϲ+`7mvcCf$T l{}}g/W77۷Q-vjwv=h'[s.{}}}} a&Vn>Aq)Ye5R44\JͥRg_*m.T:tFҶ J{JJJ*][tCm;K/=TzDitbikYsK;JJK..]R/]S*n*Ztw҃GJ*=[zrқʑr OavNw͎Iَ6|G1Xrruslplvlstqwrupvs\t\q\w*ҩGccccwqa&Vn>Aq)Ye52,,\e2g_*k.T6lF첶e zʖ *[[lCme;/;TvDseˮ]/夝NSFiw2llSֲe,;RvTٲ eˮtR4g\; Nt:=N3lvNrNupv9;8{}RsssssssssV9]^./WsrKU-'O.V>jqtqwru^p^v^s,ʳʕL\n+w{ISg.o+_||iP7o.V|OCGO.?W~J[.ڕT.etY\vu\ad4LWӵw R-o(W+˥t1.첹. ]\S]3\]mWk5ZZZ:::::UAWW+T\RapUx+ +U̬S^YbbYʊ+P- sYW*+&ULQ1b~łC*VPb[Ί=+U8Qq\Ŋ++niw[V9mq.pܓ3sNBw=^^^펻׻7wOR-f$T lw{{p/uW׺׹77wOOϹ/oUҕJU%WiT+]@erBi3+TWvV.\Vruer}-+wUV}LKWoTR}>OL>s|/kMu|ݾEž%>޷'}3,>ooooo[}|+}}qzFv.^a1Iy%U HMjMFFS1Xk5_M&R3fzͬ55]55j,Y^׬jk6lQf_#5ǩp̈́5jf̩iYX[_3Xfex5[j[spͱ5gj\Zsfğ+o[D////5~??????YKQ-NB?___7wO/oGjSk3jZ}Zujډkgέ]TvIZvMP;\vkݵj=^{l˵jo@Z + 0CQ-kk7n^voõjO֞=_{jڑ@j #h)` 8/ DY@W;(8$<p`S`k`G`w`_`HxTlBrZfUVUc u:[S 5MJN.nFR2u:}Zsuuufͭ[TnI:nMP7\nkݎu;^wl݅un`Z0+ 2ACAO S3m`Rwjݍ`j0#j)h :/ FY`W;(8$<ppSpkpGpwp_`HxTlBrZf=UVUg z[S7OZ?~v}[=}KWկ_WjMzGWOo^?~n}G}W}wKkM[wWHSg/_V3DBY!e B- yBP(j -BЪкІжОСQ% EBCYPW;(8$<ćքphShkhGhwh_`HxTlBrZfՐ֐ՠl` [o547Lj0avC[ = }  KV5mXװasö {7j8ptù W7ܢZ:5,nXҰoX 4 7ljڰawþ G7j8prõa* +L6magCp[x~xA'/ W׆ׅ77wOOυ/o5ҍFU#hlP-a>&,Û[;»Gçg—7ƴƬFe#hh47FcqR7.hikh\8Ըqm 5lӸP5^lxVGU#=x#H82!2jiݸ`㍧6^hxfE"1D[DP92)252#2;YE"K#CUu ͑m=Cӑs+[MtSzI5,M&W)n4iZ̦9MMM zȅȵ&))I4M&gjjn4iF즶M z6 5jZ۴iCmM;4o:tDsM4]oL77˛U\lov5{ ͓5lټyyY4ޣޣ2]/{TfYGG\O=*ŧJxQ=*G%#{TG1Sbe2O4GexQi=yTT)f?v(eJ1S)f?.WV)bޣww^iWY#{TJ1xV){fޣE({TN=*=*[{)GTޣR̒rxQxQ9]&?!{T~L){Tٗs9]{b^lQ#{CN=={xx{LG.EȥqdLF0dLF0dLF0dLF0dLF0dLF0dLF0dLF0dLF0dLF0dLF0dLF0dLF0dLF0dLF0dLF0dLF0d߈`_d~ $5Hk ٯA_d~ $5Hk ٯA_d~ $5Hk ٯ?g M ͤ08qLaNΌ'df0̽2rB9 T0لLa"T1jB5!0,!p#1zB=K1FB#c"41ńŌƔ0vB;SJXʔ1NB'SNXTV0nB7SEXx 0SGXDŽCLaHD#G?L 0 '2%(3ps?d0)T©̃21>L'kbl!^yy-32#q=l:(YaYcsYkd ȼ+MbST6OƮZdJ#32)Y6I6U6C6[&/[ dKeCUu Ͳm=CӲs+[4MrZEsvE{'ГiLzNw ^+t^Oo]^}|e2Yqc ?ggsD7߁7߃"9 4N<4y8iNp<4y8iNp<4y8iNp<4y8iNp<4y8iNp<4y8iNp<4y8iNp<4y8iNp<4y8iNp<4tC(((((((((((((((((((((((((((((((((((((&Pp:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:p:I?iN pZ8-iN pZ8-iN pZ8-iN pZ8-iN pZ8-iN pZ8-iN pZ8-iN pZ8-iN pZ8-iN pZ NOT5"[E;)R.g;mZ{ O>/S9+un\>|_ң<Ӗ3ZvkfikSw$b~P-=Cڣsڋ+[]Q1cd,q1^& df3ô3BgeJr=g3ɕvf9f1'3ysASɵ\!ɕun;meعlv"n"W鸮.0;Ncgsv] ]ɮfzv#b1${=^bdK28r &rtn7ຸnn[-xn 'pT 8Y8;\ s4n&7k:\/ r˸j.έ6r[.n/w;Nrg%*wѥ2t FיtVCtA]D7QתuutuKtunN 6vvP-.kuzuA2Jj]\^QE]KWw@wXwLwRwFw^wIwUwC7Ogz^7zwM _71mYɶ,˶,˶MM34M4M&!8B!B!BKM! R,Q)8Xc9(eaתdkfpM>\};abuz|i3Tϯ^X=Tze՛V猪=U>Q}lתoVߩ_ȗ}yBW}a_[[[[[[S||{}|}|N.n }s|} }Ce5M>gNN..nդdkj k555pMfB͔5j,YTfyͪ5j6lY[spͱjNלXszͭ5jצf׺5k6lQ#5{j9ZsTٚ 5kܬSsQmJmV6][Q[[ Fkj'N^;vnEKj׮][vsڝJڽjtڋWjު[q]Z]v]n]~SWUku k=Z{T kެS{Q]J]V.]WQW[ E&M^7nn݂EuK׭[[nsݶuJݮutݹuWݪ[?͟]~7#.пXXwZͺ;uSY~?_w+?Ss Kk;;i9Eu-]zW_TT?~Fy֯_]~cz{}^}a>XOR?~V/_U~Cm;]{?V]s_UA@Z ;@Uh D]``q`i`E`u`]`c`K`{ GDž` Ss Kk;J`W`o@pXㆴ܆Wijh0aFy 6hXݰacÖ awþ G7l8pRՆ 6,hX԰ayê 67lk٠4jppñN7kpzí ӂ`~`S0 'ggK+[ۃ<<<<<<|(4f44Z kۂ;JpWpo@pXƴFWijh8qFy6h\ݸqcFqwƃG7ll2r_͛M@SGظ@c5nnJknmor5yMMMIMSf4n4شiiӊM66mioҚv7k:txɦ3M.5]mt^f99l.n6͓͡5l>>>>)L  WWׅ7a-;/|0|$|<|2|&|>|)|5|#|;|/Uhhiu:[[@ku|i3[o]:ԺueZhZ=[m=zl˭Zoi(Ɋ#yխZ7nioZwk=zx3[/^mz^ÈɈDg8"H(鉌LĽ̉̏, EEVFDG6EFvD99999yԖҖfok+lsUնmQabdwd_`HxdL|RjFv^aЖіhsy|mP[{[Omfi߶mmYʶ5m6mm&m{j;vTٶ mۮlvQ{J{V=^^lG'Oi>}nabKmWnn]hhiw;ۋ۽@{}|i3o_>Ծ}eڷhڍ=m?~loi##ޑQv;}:tL1cAǢ%;VuбY!tdtt8:_G#1crǴs:w,XֱcMM[;vtHFǞ:v8qBk7;txMfEѼhaFp4NNNΊ΍....nnn*]ѽĨ/ўPtYtetMt}tStktGT=CѣSѳ kћ;G)YμNwgEgmg39szι :u.\޹sm͝:wv*:v}/v_}n=i====OOU'"L6t>}D/w_}~{O^Oa'L3gVܞ=z,YճgCm=;{]={{9]s={\sA޴^W;wFyĞ=z.\s~ϣޔެ^{o^oa7N;wVz.]ջwCm;{]{{=]s{^{A㾴ܾ>W7oFy}}[ݷo0Q_J_V/WW E&M7on߂E}K[۷os߶}J߮}t߹}WqZvn~_owO?vKW_׿Kxֿ_AabEm??z /_޿mw+?/_ni@Հi 25000i`,X1z`Ɓ-} 89pfI#3qJ8F%NzىGNdaO8J8~8q8'=IcL16qp&NzGaQ8q'Nz%;q(M8'=oQ8q%NzI>q$Nz Gcє8q4'Nz-G8qhK8'=hљ8q8q8qLL8&%Nz'=%NzS'='Nz'=_$Nz'=_&Nz3'=_%Nz3'=_'Nz'=$Nz'=&Nzs'='=Kպ&t}.U []mk;tz ߧtnNoq>n촺t;[D'~)R.\Ir+nʃǩi٩TOjU?)5ڕ::)uj٩RS.M]:u]-SZ}SO=z&|ԫ7RoK}&e9Ҝii4_Z -֞֓6>mrڴis-LJ[2mMMi[vIiFڞiҎH;v6Bki7O{nOK/LwWצ SҧJ }QצoHߜ-}g+}oҿK?~.b?HdTe32"]2fȘ1/c0cqҌ3elؒ=#eؗq0Hg2g\ʸq#vƽBfFfN#әYe2C=3'gN˜9's~̡e+3dܔ5sGidܟy(hSg3/d^μy3NGY)YYY,wVEVmV0+˚5%kz֬Y e-Z*kmֆY۲vf)Yf:u,뻬Y.f]ɺu+nփǣFeoҭKp[{KA<(+AY NVd%8Y NVd%8Y NVd%8Y NVd%8Y NVd%8Y NVଦ>ZCk)+Jp'+Jp'+Jp'+Jp'kpݴ>O(:qN':qN':qN':qN':qN篓3 ]@9>Ob"/%eBWr]A_+t}o5mCwz@ߧtn-c~BOvA?gƩH%*ST:5Iw/n%C@ү>5O D hp\k;=}D>LP6.Q.Q.Q.Q.QϋBBBBBBBBBBBë0t숍Fl?F͢h6͡OR;}QMOR'-tu"ZLKR-^ZN+hz 4Him!BôFhm4J;i1OB'Љt)LFпS4 :Π3,:ΡOӹwtMƒ6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a.A]H t1}ї2]J_t9}ӕ IWӷ6]Kߡt=}nӍ~H7ӏ1J?t;ӝtYSJT UFujP_K_}k~CoAQ Œ6a6a6a6a6a6a6a6a6a6a6a6a6a6a#OlF1lш l,:f$ӧh.u4t,uZHQ-Ŵi)2崂Q?@6f-4L[ivAv'tH'џgt :N/ +:΢o4KGIűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQűQű?Cg }.EHKt }.eUFWJ]Eߤ[t }u]G7FD?Gt nmS~FwNϭH%*ST:5Iw/n%C@ү>5O M(IűQűQűQűQűQűQűQűQűQűQűQűQűQű$6c86hF6Dl$b#HF"6Dl$b#HF"6Dl$b#HF"6Dl$b#HF"6Dl$b#HF"6Dl$b#HF"6Dl$b#HF"6Dl$b#HF"6Dl$b#HF"6Dl$b#HF"6Qb6Dl$b#HF"6Dl$b#HF"6Dl$b#HF"6Dl$b#HF"6Dl$b#HF"6Dl$b#HF"6Dl$b#HF"6Dl$b#HF"6Dl$b#HF"6Dl$b#HF"6?Cg }.EHKt }.eUFWJ]Eߤ[t }u]G7FD?Gt nmS~FwNq: KT UFujP_K_}k~CoAĤDl$b#HF"6Dl$b#HF"6Dl$b#HF"6Dl$b#b#Hш l$a# IHF6$l$a# IHF6$l$a# IHF6$l$a# IHF6$l$a# IHF6$l$a# IHF6$l$a# IHF6$l$a# IHF6$l$a# IHF6$l$a# IHF6$l$a.%m$a# IHF6$l$a# IHF6$l$a# IHF6$l$a# IHF6$l$a# IHF6$l$a# IHF6$l$a# IHF6$l$a# IHF6$l$a# IHF6$l$a# IHF6$l$a.A]H t1}ї2]J_t9}ӕ IWӷ6]Kߡt=}nӍ~H7ӏ1J?t;ӝ4FTϳLRԠ&EtKt?҃4a# IIIHF6$l$a# IHF6$l$a# IHF6$l$a# IHF6F64bHF26dl$c#HF26dl$c#HF26dl$c#HF26dl$c#HF26dl$c#HF26dl$c#HF26dl$c#HF26dl$c#HF26dl$c#HF26dl$c#HF[NHF26dl$c#HF26dl$c#HF26dl$c#HF26dl$c#HF26dl$c#HF26dl$c#HF26dl$c#HF26dl$c#HF26dl$c#HF26dl$c#HF3 ]@9>Ob"/%eBWr]A_+t}o5mCwz@ߧtn-c~BOvA?;iƩH%:l+TթAM~Aw/~Eѯ~ =@iF266dl$c#HF26dl$c#HF26dl$c#HF26dldl$c#9a;^yyoY{ 1_kx-}2^}ct#n͉:-<٭*S~wpϭqĎ`G;*Q vT`G;*Q vT`G;*Q vT`G;*Q vT`G;*Q vT`G;*Q vT`G;*Q vT`G;*Q vT`G;*Q vT`G;*Q vT`ľ$`G;*Q vT`G;*Q vT`G;*Q vT`G;*Q vT`G;*Q vT`G;*Q vT`G;*Q vT`G;*Q vT`G;*Q vT`G;*Q vT`.A]H t1}ї2]J_t9}ӕ IWӷ6]Kߡt=}nӍ~H7ӏ1J?t;ӝ4FTsRԠ&EtKt?҃4aG;*I;&GcD-;*Q vT`G;*Q vT`G;*Q vÎ vT2Flg`#HF*6RTlb#HF*6RTlb#HF*6RTlb#HF*6RTlb#HF*6RTlb#HF*6RTlb#HF*6RTlb#HF*6RTlb#HF*6RTlb#HF*6RTlb#%k5i#HF*6RTlb#HF*6RTlb#HF*6RTlb#HF*6RTlb#HF*6RTlb#HF*6RTlb#HF*6RTlb#HF*6RTlb#HF*6RTlb#HF*6RTlb# 3t}B<]D_tDЗR ]F_kt}oUMEзZ]Gߥ{t}nMC~DЏV F?gt1"L:kT5.M{^G7 HMHF*6RTlb#HF*6RTlb#HF*6RTlb#HF*6R6RF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6JZF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6g,хy@}./ӥJ :]Iߠt5}oӵK>H?t3nӭ~J9Ic4NE*Q*TϿN j] ~I?н+~Mo-=H6Ұ4la# iHF6Ұ4la# iHF6Ұ4la# iHF6Ұ4la#%la# i#6џtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұtlأtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұtl4<Y:H t}./!]B_K+t}.uAW7j]Cߦk;t} }~@7fB?['tnshT2UJ5:< 5.M{^G7 HOHF:6ұtlc#HF:6ұtlc#HF:6ұtlc#HF:6ұ6ұF62 ld`#F62 ld`#F62 ld`#F62 ld`#F62 ld`#F62 ld`#F62 ld`#F62 ld`#F62 ld`#F62 ld`#F6JFF62 ld`#F62 ld`#F62 ld`#F62 ld`#F62 ld`#F62 ld`#F62 ld`#F62 ld`#F62 ld`#F62 ld`#F6g,хy@}./ӥJ :]Iߠt5}oӵK>H?t3nӭ~J9Ic4NE*Q*T\t_=t/_z~K҄ ld$md`#F62 ld`#F62 ld`#F62 ld`#؈gFƈFlg`#F&62Lldb#F&62Lldb#F&62Lldb#F&62Lldb#F&62Lldb#F&62Lldb#F&62Lldb#F&62Lldb#F&62Lldb#F&62Lldb#%e3i#F&62Lldb#F&62Lldb#F&62Lldb#F&62Lldb#F&62Lldb#F&62Lldb#F&62Lldb#F&62Lldb#F&62Lldb#F&62Lldb# 3t}B<]D_tDЗR ]F_kt}oUMEзZ]Gߥ{t}nMC~DЏV F?gt1"LRԠ3~Aw/~Eѯ~ =@iF&6262Lldb#F&62Lldb#F&62Lldb#F&62LlsLldb#/'ccͼMȲ'>2jژPޯLMI|/57[Hs{cs{>7G[,:_W ?f0zyo cfZ\7wtJy?ʯ{[:'Gx y'F{zO<9[{~?}1fߍ7cTZc21/'*x.m?R7 B X^c-V mUָ3k<5j^mϭNm߱jkgvC[G)6!5%%%2vzJ5LɶOX'Sƹ)?Ǝ>ik<7Yk<5J/K_cNM?juDzgfU196jᨅB4ۓ%>œ{'E^l+(؞f메2;-w5E/3regΝm[k<'=k!w5~ߑn2,}:~?/ijRz>z~!![11ǭ;''z~ggz~^_z~_٭x7בkn6;Xwex^c߷Qn+x:ޥ7ZiyC5~VG?o?o{c5~ޟ:~jߟw/TEwk hNw!i,d[=>as9BI?|:dN5̷cNk\,bk\t[Rg5X9˜UcV;묱>^lAg7:q3d[aklg5nvLk]nk}>k?v?qϴvLkyVZ|_stvwĻ;T7x3Tҝ-g;vtvtKR?O<3 ' 2 s G[="a^Z(l}/ gs¥;$^E% tOu ?+>V([? *Yq;U% q0N%OXUEB-^vuyRRz~sP=gҬ=%b.y+3n&i9R!`} h eB)S ީ?[L:-D.BCkʅ`3XBhLʴ~lT MO~Ŷ=NO3Edn9z^-z>MM4Kͧt]M(=GoGgӦ-Ԡ{k.}hzͥC}4H#NS_z)В>j8}@)}VC雬oNa5]ڝnXIc7}վCVӏZH?<H?e!`闭6_ڜ~3oi> X#h&Z& sy3)焅"a(<g/ۿ>"}odWlnG~a@0^ L& ?& ?v O_Y?>e 3Wu>Vb}=vk˵v&Y;Jw8=]>Xlm(y=Kԗ-;<)]0K0[u+5y6J6Ŀ%L~!L~)~%~m}l?QKG>Kst!}./*)] )3SSOٞ+Pɔk)RRRoXKH?~=c^ƺGQ+GmsԞQGFuylG'{ {eH{" MO=1yO,yb[c) ϙ '7hε{O=蓓\'{,?ߴ?|*IOm|jSsCG?ZcchSG=4zͣ8q'o o0oc޾yd)3a1cY?f]c99☛cg#g/_=Wo?=pold성3]>vcw=4؋co}̶~Y` iйܹ޲.!IEMÂB7H l~:Tpb͂مH™ n/Uxd›e+7aq㖏[?n];9⸛].+t-wwmwrrt]tt=,.*,E&,,Zk`;X{۸ ,Dgm ˌ8|+}XS?[??o/+ w{}>Cigx+i4_sC~z)ǣ߻m,?=H^O _O1NfrNV1=yI^qo,jlHX* NA# p^,\n'H^O _՝J^#39kg%燯{^:-yݒ&?֑NI^7&&͚ߵi5Z{Ր\X-6 I% DŽYpU)ܵv[a+yl>[&ئ&1 3o_'{ßM^ _/x/\|ݍEe^JKWZ,y=3|K^w9x~Roz%_Y^u{jWr;n^ɯ?>4|wW5xpz'NH^Wӝ@$~Zklgfswsl>uɿn|'DYußzm$ܻMX!6[" )pI& li[ejmMv[mmmmmmmmmmmM]]ݲݳ=JIKIKqxSjSRSRaQg2zEĽ }}3;s: I*T$eTڤBY+JB*TJ&)Zv]cWNkkgvr=iw=yݯkO~{m/Қ~(Q*2 jQeU\n6ڜhϏΡѶJh[5_jF[J)5m4r(3mzVjF늶uQX 3ֻhKmmUFuS1sMCFmF덶My5FHC3cͣ\hqEiaw1mhVZGWrJVQuӟ_Q)*6V8O{] cq{:7h|TnB}_hg|hChسc7t:4ZڍNí++֝WYMۍYlN]"DO)p 繿 }έ+M0WW\6v=W4GhhxC1VchvP1V]~T)ͣu=)ǣUy<"QyNOEvK?o'6s7lN{h;I}/=D+< }]y p|v52hyRI3[ʫx?if%Eیѕ1CѪL3b #zEۓF˯fFL,0rN7Z%uX}mQ稠cѤHȈ~czb#wGVĈ)%FL.9mKr&hq )ӟ,SeF JDr#^g'byc -I2ZWeϻ-.H/Z^4ꀋ;ò˱FWn*OшWjvmb3C1G0F;h5hvjD$[9F7ڨT0vFj1fa]ޫDH2څFhh4ZcV uZY37*r)zP]biFk^h{h]aqv3Fh2zS&F몹] -uCCZ+WVSjcUDjQoWW31I~nhx'G1jG G7{MA4|4jrգM&]>>6W=Goq7/ѻwi_hا7ѯvDޤv#&6:ٺV/Z}}f;9z1\q&7h!rmǞs8ntmM-n>um_}O!G{s]Vp-Gdž}N,XqDŽ;~#jQw*Ү'ughwo9u^qD%ޛaw~}[w|`^MŻ>gNXggs9ےξ~i\ oے?;?qt֑߫ˀ^e@C|?<Ѯ(8̜n='Lyf!#qKXmܨq3'dO8:Į{M7q?}➉''MOtRI{&O&?Ozzf3:Θ9'BCǙg9nҙfneq֫>yVpmvfN24eeJƜ*s4gҜ?^1d箏VJf#G^FƮ$W:"Or#ƅp ckRm&2h :뉯ZvY|1kk-NC{8Wͬ%.ǷX<7$j"Ok7LJ䉞W}#7\\o7}8~Nd9x)b-jplƟh|1am/[h;quו/7+}8ؾBDJWFnh]w)~:yh|u]hFTC}RwJ!fWs[!+I3o*})m.yjIptȷъ4M(2(ţJ342Fiߖgt"scm:D{B1Uev#N.ZN< 7E~kxL<'K,ċ'Fo+ǛbKCU"'~nj`j nMzS STRlN)UM?(g4E7ժ:MUx5ޔV2)[m^krשL9 ꍦնj[&Ah:vR;<Ꝧ|TvSjw[}SMA32u:tQNTU].SlzubWUU8=z\=4UTn0[UxsKJ803{UT-v/nѕtK5K ee堒i9d9-y>UGi_hwfuMPS]Zv@sK-G}[;Q5L+.[UQ?ZMuv;֮֮z>5Q=lcYY'Tu)PgM&~ IitL,bkg6[[_7kEf={֕/?X2fZmR-29so406?fkckk~>}}'_o4{YCfc:/:9[88>,t|HtuZ>ӭz]˟_GkqX}VUOjS)Z-epXN_ԗꫴjKV}U^Yoѷhh;_>]z֞s#Z~L/֞!m~N_t mSq*l٩i)NөuVs8:jo9;jo;9k˜--ڇYWUWoh:t}|ǹT[|}7i8?ӾTmRJmWUV)rN'4V:PXY[;[e=OO/O ?wep/]^0%E_QH oT7 seK!&'\smuչy8`ѐ8ߟpϺg%ppi#㭮8o'5O.SOvGWjT<0IQ^*V,Ȇp,dg9%tEP,iKkE{hu!30 ^2_h6@m} Clm-[D+k 10&Lhd] o‡fP %P 倆g`(< E8!:ŋ=_# zצ⵩x[ nm66ooի6P}TNno Ij\g'BxNp!Q,Vx~O?oAk 87v,DL;v<Ei߄6Ka?j> |YYsldFVld==8`#k̵zsA̰4yv B}lg ҖFg`(<vE̥n|8,׋xvu^6(㙹xf.g♹xf.g♹xf.Wi!<-Bx 1A<&yVޗ>),0zF5xM.^5xM.^5xM.^b b VŊX.rZ. b *X#CBP=>ThE(D\T b.*bnd+ZJ{{7L&f2c}_W^Cdy2<@L O&'ȓ doHlNlΚuf]Ykk6 fk6@>f=YkÚf11uh>#hchchrg3ܙ@L w&;ȝ rg3ܙ@L`-zX֢bXs.֜5!%o ZkCnK 5gxo w.߅?@A{y>=_/wY֥g +X'|<%>^uK^TĐr:d̅r:!ܻ{mW]Ƚ;1QJWWa,g9p>yk'uIyR#uȟAa7UM`a40髛 'o']h,nQE3#T5DBFSX+wԦl?BO?.WGEFր5`d YFր5`d YFր5xŝ9*r;N'w:ɝNtr;ق;oySN;uԹSN;uԹSN;uƝ5ZG4N娕C @?avS٩nv);VQ{6*.SKy Zpm-= mp;t;. wC7ýp?A  X *>O`5| k3X :`=l-lZJ1m`;e `7 ɓt{ [,3̿SI;`''2˼,Kuy6ԁPOJyR[#OkZ|-զ;3d|k4& O[B3hB eZk+[ӿsIkھMm*Xq`*PՀmƼmƼmV@C`6oc6oK õЂ1'm72 :]WS\79 !xM0f"?29uPwy<5̊cqɸ&TPDŽ:&1 uLcPDŽ2j2Tj@MZP@]YAch AsZuВ]v+h p#np3t[# :C+ Bz 7$CX  /+XZ lTmla](_>MHJBJB4D<3L3D<3L3D<3L3go`#· ~E'6H_`+ 7B7BTvKpXjAmuph die.9 D*1S1f1kY#Jk kȹTG{?7g$swBеN<)s9-{ٳh>>S]kTi`Ϣgسhg dn򄵊,VjPrk= !\#F@ [ n7YJvF]ᰩ` h`ā@UաԄxaՂPB= !0N1N%@ShZNٮgvmS)nH܁V ngA>ac|b4]]D n$j/]s߅}筅KQ ÆqB3Q+٤/=# WB= 5 \2ҍj+4+Rm(9J S>Q?\Ai'J2S_<T.?`_`> `/g_MXo,eޅ}XJVG1|W^zH@cCwfP:I(о&)ٻ<.n$~ȕ{?O3}8Q0<`n`n`n`n`n`n`n`n`n`n`n`n`n`n`nG/y<!<Vy D(wF~rؘekze(V43}-s_2}+sܷ2}}\o Sa"3k+ʸ2k+ʸ@2HflyX AexP&mѡ[;3?g3l6fٌ#qd3lƑ8G6k%Y~čJb@H& 'ƽ#g3⣗c4b c:^i0fL!+0b 6:b 6:b .:⢃ .:b8qk[xނX֮azX````````````````YA 3:"3T|9ָ5.` \{km\`ؙiVٶ1~.0Tf,Se*Le2Y2Tf,Se*Le2Y2Tf,Se*Le2Y2Tf,Se*Le2Y2Tq 3I6;N%Y4>;3 3L? 3Yiۉvbn'ɬRU JaV)*Y0f¬RU JaV)*Y0f¬RU JaV)*Y0f¬RU JaV)*Y0f¬RX#6foN=[zp0t\әW-T30t|ҙO:I2Ng2xCw?is&<+ZqEPGL92K+l<^ ©,wyq 4) g9C2 `Q"10^q0&Da<9FL<xK0o'10Cf(3ă92WnPqbyTy[\~t(/ fVA:8 TjPj@MZP@] hGC?Џ~G' ]p7t{; => P,<yH0 ^0K0`2L? 3s1e,R\<BX!a#8ArCd &H ad &!~CBP?!~CBP?!~CBP?!?PCsϡ9\,$rA\,$rA nuCBP7! nuCBP7! nuCBP7Ěw44wUP;i18 O/ʝhyd!w><߇|v;a4P,;XXXXXKFY( a4lҰQ6JFi( a4lҰQ6JFi( a4lҰQ6JFi( a4lҰQ6JFi(aVUexYވV_TJ' G(أ{`Q= G(أ{`Q= G(أ{`Q= PЇ>}(CA X ^Ve5xY ^Ve5xY ^Ve5xY ^Ve5xB^,B^,B^,B^,B^,B^,B^,B^,B^,B^,B^,숭|Gm4}hCc؇>4}hCc؇>4}hCc؇999999990&}' zdF'P3e6vVf#x4ξ|zM\bm˸ QBTwxw1]wmb+b+* VB(Lw!t^Gha" lr}7?=;TJ]蚏蚏蚏u7F]wntݍu7F]wntݍu7F]wntݍ"|*§"|*§============y 0&d"#5VBHT6ʯTLe\EY9ٽsjB7~@ϕ{j~.e;;;iv.ޖE "N)dqTBpNBU`z<&yFm7-2v$ӟ @_-s1@LЧNz 17U/[|Gӗ_+xjyVoweNze>8@ |>GH@<ꌗ~g- ~LwR;s,s!8߁w UsSUjT=nT=gP@ jf)Jd)JYT b(X9(x` @A7 Q01 ` ` Q0sPP^P/rE(@P*R*BR*ER*ER*ER*ER:`(RE(@JPT4Uɩ&5JQh RaxP~wPʨ;?TUnW5P(=K jcz7Un {_SNWuD}TRj%f1WoCObü yj1O<O:`G*3+^шq.<<\AWNrwQ;#ˮ:NoV殣r:s;;c=x*26[;N'x1<^q 8W+!<"GcxBO ' ,w#x4F?0a<ӣOA_Ky~.2sJX8{*uĒM7J&q$aEL{y"ɚʋxroƝ%(q%<(Qf2(Sr=OLœ2cxZkԁL6k)%vn7b=tWݑ]C2-cp KWD%J\ gd2)&rYQ{-EZI6e,|ֈSv9SuY##*m55 ^v[{=ֹ֞j}9QmX<&6fļp|BmA:hI p4+6 j7Ԏ\s+&gvrڅS>ɪvr,+c_B&D]u0$˽/d<<+d +<^2 /gq~6 |X joBXRXF9sާC9R>rH6| _dx,I([uPui("@)[p>sDNΉ9UiDDN8A.:PUWP㥛L&ӻux^]4k1@owDXL 'j;[;uXop-9?HՃHՃHՃ(0h>q3? k3H J%J|"w"A>+~+IV&VV.rn +6Uj**Ϫ5+wVL>V~VA)޿Mx^1x9#O@DL~m8]f=\[YEdM"F[HDDZ'N2=ĢM9N9.F~"f'WĂ_1o!{ӟ&>+7666XEd}YEY>?'p+:,Dlz6kllMfm~N6~~>]H>qO\m!9Ņjr5X׎kB|:_ǯ\v2&|s2>2G6(?SAnW*>AvQT@ |{"aI>;Qv'q 8I4O4O4ޏ܀es9ACdM'Y,AȹIDD~O3DL"`&0eO2zDL"(C!bDt"vvhuhCt!:rNDtS: Q)cDtQM4ڏuvYYaXh'000RX*ppKe` V!,Ɋdg3YLV|:=՞jaY9CX1U~U~U~U~=qq /[Xe} 嬨嬨my&,v]]Sk*ŋ]zlgEX!l1[dL<>/˗˱LFh>޻=˜So <lw:|0BfV̊U:qҙY:;]:;Ft2BFTh032tFƼ)&o4 Q3x[om-2x[o (ER@R(=?G8L<#2뽼7"H=@ D\ʌ=;LmՁ}D9ę^xow>Ÿga751O;JPj`-Y3G,չdD ;g~@RWA| 'fՇfPU>QÞG{sN\C0c=ozg="C|,WpqQ^xe̾ٗEf>Za̪02f^um&\8̎12fV&=@{ΜwxmTK~%?c1}Z*B*O׊ \_`/G`pSHSȸ))))))d C/xސAc*1*7m`;;NO|Ӽ >t{`/0WEr|RC.Pe[-Tٖ:PQ5EP,ZAG!F(xU@wm2bZﭭ=L ˭`غ VQ8`3k\eiΕ6EfT04RQ) U*TPjr>.&)r$~>?GQÚW`.̃W5Q4?AE`)Xm,ŢkZ,V_&iXuVUaid}X+CX Oa |ksX_ 5|!YJ;QUIV:=T~,g)!GP=>.Q=>E;NVL5CXLbW-j\I D{J0aLI0Txtʑċċ}y? @.=ix4bTKuO!ƌvTTHyQ]Sj@MhH5MpMLr*c*c*c*c4XmXmՖM4k5i gY% &Ggκn;κ a,k_o,eޅ}XJ@*>O` /43mh ov88888E*q`AQRFO{2htN4Ӊf*X*PCPPPPPPPPPPjjR xT^*/JK%R xT^000d/ E c`,$݉݉݉݉݉vhj'ډvhj'ډv{{{{{{{Hޱ>uGG_?O_?O_?OLj|P^8>(?b(RȾȾȾȾȾ>>>>>MM簾wSӻnjz75MM簾wSӻnjz75MM簾wSӻnjz75MM簾wSӻnjz75MM簾wSӻnjz75m}IE&r\d"E&r\d"E&r\d"E&r\d"E&r\d"E&r\d"^"fKlf/f%RKHe/jKM ;YL'd1];3fdH6{\LvՔGf6vm8{EK+gMMU_~{gO/d9U=>,dc d~>E cxX`^wwwSةp!!\z_SOOOO&S٩걯S MS!BC4 iI [TM˨Q5jZC4ji TKvnnnnnnnnnnnnnnnnnn.UErQu\T]..UErQu\T]..U֞1 T['xg?C(< 0!ËKBsQl3gƵkf|^6+Քq̳0P&R%ڟ$OpқW`Щt*>Oөt*>Oөt*>Oөt*>Oөt*>Oөt*>Oөt*>Oө+>RWK,]LEoSxd"5=#Gb,e7u[IQdoEmԓM@P E~ br.; #O_ӻe-ؕxeYn>uD;nɅ}o;E]+yo¦ N^'QUwe\ԩ_?\sW/꾋#<;&r{۱-F6}қL,QBUQ'&(;r尸E EUT,tt[f o~TU̒ߒ-<"3rZ5*3W O"ӣ, 6;8@'TgQ :\Rbsa |xB4QfdI&` `8P C P jC 4&M4k\˿M}/<`L0flH9 ̅y*,Mox ކw`),vk}k2G/sXŇ]Ǿ>|!|X"BҪXzbKR3[.5lo˜sfZkm~$I$y$IG$$I$IB<$IGB҆Xb6u~fv[=9{>9{>gdlRWT֍R ? 1ޖ y2 dd$d!\Kyd>e+W! !K AB^, dM*c6d5d d-:Ȼ  C6Bv䝐] !C@TB3o+W1k 7@!LK%}4t8(8ǣxס[YymWC@B0vcW 1d+l| BDQȷ!'!?@j! g}?$i 5P[A $Rͅc,SCFC< ygeZOK2Rv@n<t蟇̀̄,703cqq_ 6KmF?dd ddI$S2 d*Y4s!3 3! /@fCp9 s! e~Jk !B:@t\yX722 d4a#G! ACL<y2 d2d Tȳi !$cXob,13 \ǞeÍAH(ȏF%Jϕ+?W(\&C>k |5YkN:~N;wH﷎gX`)}"^ 6>Lp3̈́W>*?U䁏4~L)3̄1>>B |5jQ&Q<p+++++=_z=_z=U8jv+R>Pa !BF@FBFAbK᳗g/^ >y)RޥK/`i.$7`؇ƸA)-ʂ,FyBRYw'Ř. 0QF4Hϱ\Y*3\q3;jOSOV|(@Qo̰5?4Ԓ CP2%(A,b_7 FO5vw~J &gĦ >||:u#G[>o4/b"Z̨e-/lV/8!`ctq̸j}~VfAkAׄOXh{Q>Z/|/` aPӌ+y5Pӊ󳆚qWbWÐ!?bfA.:ϥ."x`g1Oy1a>oGtmv/PoݸzLA*;'ṠAHTCWx##F#KqḹpQsHn-jl@èQ# 5P:c>B3oq>._n;frO߂`8l,c3ػ:Uq\J"݋Yu?ֻ#w5z|;zQς5,"b}퐾{qoq܇K,93}c1-bh|o|g| '>PO=h-WJͯuY/(|z,]PKj~afzk֡zxq݂QAmGp n3L@+J=Z1B50v=jYC?O1*ԎA/Qtw,}̸zøT%Z;-]tT~: | -_^㫨!ų>u}HѼD'(q3UG&?KhϮ:\*Kezu=Vo>8_9ߔs`f[`|"pԱ3D mG^$sb?y.a=(r0C-zGO-;GC>zK}%xO KDr4-!̎hӎZ_Qȏ8wL,t.pheʏP[aakއ#9-x!-0B0Pc շ~\ȏD~wB|p|8 \h7ږPc7PS%p-#pvD(GC(2NqLV6~t:,g,qa:[tb *Lcgٓ踽5F-˝k=8?Ә JgƲAk8˜'ϚչV=Wb ]˚36X!/, VZ\qX+c>}`բ` dS7`ljjW~`EqFRpFpFP2qF1 Jl>P&~ 9Q.\(Yc*Ϛ:*> Z-dB/F=!mo4fI]dET>R?>'m޲=:aRheLh֑ų(7mjqHyLu: U˯6N>=fSz~~L>y"~{ k#}ދs>33]8pٻVc٭w60 )5ɧ7ͦ§őĹ9,\ލo }Pkopy8]}o2mgMho3[4s\?wz>`ySz&vYq>g#3Qjg9b _?5}Z8n&Y.ssp}qY~-߾e~g1~=这D4x/_~%$WBS3.Ywv1KN:r=,>Kg(n7+뚤``ob;ħۇa/mqcMғY/tp>Uϣgϒsru&ojB?l|ŷK}Izjb2΄J}wkKqŸ}47:}|_E1b/ۍ]M"=&"5=ſ̖`.eoa똛+v\]Î *7W^ĆlG/f{=lxjxL'xMϋ$ѐ{J%=-{F"tt3{^%f+yl٦ S۪c۔/lb(ۡ^^vlQS;Wj&v@ª>ZSSRo(:Z}ի'>>-(3tPg,uV_BsuFh~$OՓBZuY jʹBRC lBͮE wk1Z0H+J{۴ۅǴ0 aT[*~ gxe`{*c0Q=x9 agr1#ь)ьiFꘑXx7N2r Ys+ k)VU,T鈺n1⇻ 7=ӝE=ԛYSb[p5EPk:eQCsz/!}e:-ߏ;wڃ5BHu$ʏ½g{OʌWG'4ɨ4h^F//ZK֗<|ւ[C&qk5dXYxId Mӑ ;(T` 0` #6PbJb}4dCZ$;Jv0f?6{/i +y=Ja%#+i&+O61XkX3Ad ,,`$K2 ӐHL&uc6;}׀۾}RO' ;)naG X4ؾ:f!FV/D-Sp{+ &ԫ`dظkM"릐u US[7IAY%ɺuu utxz߮C-FtC!KPw:,CH{mId,Du"j=N[=zS)sۧ #'Փ`$՛A%%X\vP";Jd5H{mZ=W`*VYzz-ꅒkEV/Z %}uq8]4I^kefy-Zd~`,[QˣLvJ<'Yٗ@,,g>`Sqs*Y%k2#AdGaA%plF@hێ d;xmh}Y|X "'#Wv+ތj.szg>̟p罞lp'Fq':o:c/o5i~i?35)$=ʿa?z}u?0Ӛk6 .E厱?|͘*wD{U,sxFS`g}Y{/7|/H{MW{`8J܄_x_+۶@>Oɒb f7b-2 vNcD/o |ʷGk&]`9>u:OC_yeK܋xy~nhRw;v]j=_wϯ?GӛOV~b(' +)kgy'Oo`1m41x +Vg'7w}m="`6~_45.y2qh9If. ܏dc:ֺ}4n ceVc1׸ w}x\wYF?gtdoϾU#6UzƔw?zC[ARN7z|~"o?ťo0}##: Ş,??yfk?p7p/sZxX9JoS :7#M!VBZqM`6rnU7Ns}K?^U?QwKmF_vc7 k؛hc1YƝ//KߺmI^/]wn >{w{:m%? =be)bx'5sV/7mC8w~ոh>o޻_oݍǑz]f_sl_v奾wn^v5V}G7eNe֊6p\`ns*^/lS˯#.g6Tyk~\d}ΰ7+v9E{.=ONgn+v7Sem+/(?Uk ܊'ƿ?n5>acx_Drg'_R}'3u*{]ݽVY$ol =Q/WNO.m; 5ROg!q5{6Q_eeOi.k7f Ս8#=qnM!c}*qO2enm9BOJN6ZoKZQX%_v[ua~}o|J2'i=Ms?7'lMuq14~ǸX'[a|ޏQO(Ls}Gg'?Y":qх ml|w&Ĝ򞹨} ?5댻v=JaAUKr'"k4qw+X|O r5F_ 14FB4  /^caM:_X]HaĵlpЙM 7l0@Ȟ&D9T!`Y,F-fkWE=.fmböb)lxؚ}&^-^>Jq+Na|V%".eĕ[[mmV\N(>)*4?qhZiZ8^KsOhZ8QKħ -C|ZҲ)ZV >iZV.>ډk,vvv\v8Ov6D".ԆkWHq6Z#.jSdq6E".צi37WbmF{C['k;nsq_ۧjh?5)xF;jY4fY3fx[J.YQ(aIT)6Hs9[2 hsBr[[K6R)|:mU64r-QTdM*|-=l/Lzܯmb=iJuH[$J3O:ߥYևdU-QOO[ɲMO#i9Jϕb9Y]_!gokTWW.7V}|K%ww{J+~PY>$>l}T:Y4.EM#GA~B?< /aY(nVX]x,ƒa%ͰL=䑸K(F9d1`1Mx5ݼ-d266t0ýxU!lk+ gC:k%XFasS`s㠉Y $B$$! [܊lqluW"fe7e7LЏcYxa<|:)#Le4X d3jj"tl6BCcV$l5/&k.šgadEdӃɦ_A6=lztl,R\$.bb5-.ww_ |NI>mQہ,VǓeR+K¤0֚Hc%` X Rja=`|=@X("X*H%([mWo`xȣǘb2 |9y:k.?/fK7}d̄Ug/4LoTWf:`:Z2?&1+_PT͚1}Â2}S;1gM'L'XKVSS,ttΘ`lgMg1M?"}ttt-+"kHJbbV8aP4fŮ(~]YbUXWQ ]9+-P7TiaJ8 RlJZ+vԍVŁbw*NUQ>AI`-D%$%J%)i %e܊u3L}*)(Y"rJlarr|r=r#ڿYoUz۔h.V PʕAx2U(+p<<юPFXRFe ZxDy)cQQC/`,34R(SX&xsȝLga 2SɊY,9K\V)P\-,V(Re)꾮,cW*R+o wuRނ~%(kQ]e=rS60M|R3>B-h>V>Fɭ'(]َ|@NpƼ[RϕYGك(_ُRBQVCooQ{ʜVNlQ`lg:֊ cE_mƲ 9 WՖ,G Um,WPXXN+Rv&B5YM&EMcj2 tnT3w7gjZ B/R[/S@9g3L@p& 8 8gb3X LH3"ΙX+p2k ʀ9XgN, vv+' i XEAhaahcxP))M&xsf6]++k]͙4`Z™L ~}JIbu!mhw3cWZqTfXK7[u?0hd9f h"s9e[[bs%̭X9Rpmfz0G nCn4Fnv@p; n1 vhlˍbMjƂ91|&Z^,Ai?C&~a Y7vgL,wgzoofzOߢb^z/ooe[z_CeP..w3>P2`}0rՇ0;ӇA~ G1>EcP?O@3QA} }*LӟØ g3Q~> l}6r__dq}K̕ŃgIˬ@E"Y/FeX\_7}K_Ь@ o`F@\(Yߌ^@]߁6n}7s}T_"w#K>֪*ǹ2+a6#ֱa}Ԋ<%[\Y'Z'H'y:%Y>Zs> 4K|s>DΧ@i4sٕnC|Z$& cx3nK̸1 b3nI8q&{LG=&c=c=&c=&c=&@{L'(~Ӝ\K{:P(~OGl[l!LL=L:V@\Fs.^(a'* oF F>?(y.s \|+ _|SxU+wYM¯ ޚX`L".5ĿX80$SfaEjAG8z8YgSr~eJrΏ?AWP93."f\ fiΉˈj, fܚMČ \N<&j.3+VKR#P,1 (bEDQ,EE3Q1Iy,1 (bPE#E (baEDQLEDQ(X&Q(P0"f(bE$P1EkNQ(P&QLE,u(b&fj?Dì?,⇙(~X&L?,⇙(~X3Q0 a&v55aR?:֑⇅Q0ua3Q0fӜceThqZ|x-\?IKbyZ#UK>]K-9[dWhy@ô B} vv Zk]2,[LQ&&rLEF-o$Fᴆ}q_GvEЌQp?|pܘCN6I~Εij䐇S~~k<~\E>OVբeh8:s<CNy;9f+<"x8Xy8ù<+ jiEVpȷ 'LzI?& c.Ç#>u@GU^:<tҍ<fX[XZYXy,fŢkAZP,fy>69X_;Y_3Eۑ$>L0 a;`o&% ?a>B3R^ Ob?I"$h@^Js$IV?yq$gCIp>=,L:3ɀg,< L23ɂg4+7YR_ooC4}-T}ᙤOE>I'}1r۠IS߉'ҿ+>I&|}hm?<;y&4G)i~LG t<^:(^`2 tPHGj/X>m Vܮ*flLcvX,ʿGYwec7װ{O| o̚`oxT}{MbcdvHޕ!-A~hܣZ ݄;p0NH StYo ݥפׄ ˣ1x a0yayX0e`o ww` O'Sw ρYqa%ί+`Bp[KSZpTgp? (+mkASzʭJѦUQqeLVfʋ*vR*7(۔m=t{^er@9 _;&LM|XAǁՋAe-Ֆj8\,[\F}V}V2fIVRsqYjlj=ΙG#e)t%Rgsty9}XH $,1.,x@l$ ɂ@ m ! ]!=!} !hJl#HDx'ABf@@@@CVAA6ưW޷ݾ:{!Ulo[|8F[-c̫ CmyqzWH}"H-l7*"zAzSĀs-;ʱaQ ޱGL7{ s! }K}+|M8Brr rrRC4߻O 0;/( qC %?kfo iE{& f}1\ߦ2wo| ph>2En Ow[v /| pHp 5:|\)g_,|e wyne#5'i/dh !vP:ޖ ̲lmml>A1I@ac[`[b[n[\ghbnmkUzk"pF(:0("hp#ґZ9Em#:Dt+/p@a"FL1-bpnB ,齸"buM[#vD߈@?#]#S:tٓn{޺;ڻ{{ڇ؇Gه})5+k 74~}}~~5HGʑ@ƴ-LfE"GvtH?rPc"GNjĩs" $ryu"noHG DFk(({00Uj -o 58jXԨQ&GM57jaҨQ yzSV 'jİQǀ'Ga]EG(EiWt2,n l1ZZ]=/z`~zыE9z[GkOFGW:d`#r8m]=}C G88;&9:f8fOUu-mkб۱ʇq.* =&c:c19 `ۘ1c 7f@a1bb&4cgrܘ1KcVĬYO1[cvs8OĜsN vEjL@3Xl lu:8;G;9':8;g ;98:99W::787;9wV68k'grZb!XGl|l*aemc;;k;>vRsb.]*vp#INBB$w%뛔įf`RY6I:sHG3Ҡ|I4"i VXIFkI3$-%JZֲ:iۙIrT%IN$:7'Kf|ǹd=9(94ٞLNLNOI.JHn!snɽk&H2PfT ɓ%J0yi7%o 쓼#rPɇ%H>$.rUGlJ N 8JINK)Ii.otI;_ %g)CRN21e )V) %KY6eCm)R*SII9r6>UNR^Nkˣ,%555+|7ۤHdwvM3OjACSGI:)u*/i:ö$uN%^/uy+uU:E5uc%žݶ7uw^^ZQZ֦Oci}xdY)i䥶i%ii9iEiimSV3!sZ7vH7.m@`{eڰQic&MNei-L["m5/֧mJ Ëi{{x&x%4a],AaQ``t/GX־3 >ގ#UzӻxӄX><}40XqS8}zm7<}~"2b ^9}[.@9^e~O?KX 9. Fa/pdgF(\~$ s6 x&KF{/ft3\FP.% 2"="cLxI|EȘ1#cNƂK2gX1cK{323<^ zFm97s+ܺ;k ݡnNtsEq^:UVw k{{{{{V[d4JnܳۻgqC+zW׻sr&{Oý}chk.p|L_0ױ̨LX䆕"ӝY:K\"uIf.)+eki]2fA;3G'kn̉qS:9=svE2Wfܐ9s?oG2k2Of6Yi(9(khܶYc(+gMʚeIm~¬Y3gdꟵ$kwZ.kwڒ=kwYX,wϪm:8fRZֳvcͶg;RcNNI8]]l+3bng&oVgp:{BXGYsf/^:{};{;&e>0Dau9b:?{SNpNXNT+'9ja;'/$uN9]rzDt/g`ΐm9ã+sF˙3%gz9r9sV:jslٜ-gWNe΁#95>`Gɘ9gss\KL]n`nH-בuڹ67m 鮹=s;4wDrȝ wIUr7n@Eקݛ[[r=(`m9\XX3Oc<=/(/4ϞKK=(("gE:4^33{Lz^/ Jy}7|vysVG/G͛79oZX~f[*]p[ܜ)okގ=y{cy'N9aYGAw䘟G(J^[{1]>.{ 7_!pќ/Ix}QOɟ?;7M8?Q2Ǻk‡ߐ9c+c&$x#1R6W!\ c6, ˼XЦ=5lQ}\l),);S ˊۤV4\qNQ ,SܿxPc',-xj񌄅sĬP$c5Dxyf--^Uxc w-*.+.V (%z䒠{$$$mIǤ΅nM[+Uҷd@a%JƖL(\2dVܒ%KKV.Y_dkɎ=%K+9QrT,ղ7JJU(u敖.mWԯciK,R:tt҉SJ._tYJ~}KזnH8Vt[ʒJ֔^ҳer,0yiYHQ_ *+(++kS־SYײng|`Yx[YAe#Ɣ/T6lF#eKʖYlcİ-ev-*.FkeYR˝V((o[ޡS9Z+Vޫˇ*|BiU%b=/P4n[7o-Q|co)?ty]\%FUWWUDU*+y%+Ut,P%cNJ^wEC*Wbg)S);s' TL]1p_+qb+O}X[hG*6Tlv*{zCU*v.,Xq믨8Yq_x&wv3 i6VډteB)9JOMt[J{[ȠtGL%}"k`>r"J_xF5wQ:ҙ΢~3}8*6/<4N:{Gx T&8<^ %|޺K9R?%4a4WIs7Fx (]JO}4:v^ fDusn_>~JS-?sefRrse~6rMw8 $OgJ'*Lhd^Tr!::9C}pHoyD/8B4+)ҋ(=){`u-;wf33w1S'""**""**"*⩪"'****2[cL}MU^k5k>}NX*v>Wq>U6*v?D$\ypfp<yRDJv 'W_SӜSQդ b8'@O>*ݧ)z=$J;{(e:@2,,Hch&oUߛs(mjc8EqhG XhPBV - ݌n (fzSp<4ԥ9v | S@  WF+"E#15xBR.$(q xJЋ'@O>}W(vVl6&zn<xE^ȑЧr'TgW{FWXWg.+OH&($囤!>xOE{?x%O_S+TJ,0 L^F1eݨeU:Zۇ6-1Lt!yI!'ij枩)z3v`:WHy>D{}}gIyV:S+o*GЯB zy둫tGvT^obgwvGá:]]yVpaZ5&tKWXF/G~)iO M7 -2u:LoFT 鵔WWczkGWƅzTw%O-Zi[x;ۊ"ogF(gӴiA f߲uZXhyB^G^:(N[.޲s^GJ?L%@Ϯv_^#:_Jx&7ckWjh{+PJ+J[5n%CU!בA#){[+N8]Co:J}4ٿ}gSSaEhG"$,"uhoSHe H)[),d,vPl/MeDMXJ,s܄e27c VVNi*DFJGaFvN,):ck#h8^2껑,e%Kg迯{hRlNmRl? Q!Co%藠!sSbTDgt^Wl14: CҿZx~A8SD41eKQidVj]a8nWPԱ:q7u@PAJ)šUY}bJL)ii5ڏjj%eI8?aQמz=o(v|E :u[sb_cjޗU2GY.֟Q 'LଖMSQ0zEShJPgIc?z=Z^k{i;3,^XV % pճx.-/#.C# {Py~zۡo~ W8V-9b/מHqIhvs_U˸w?dh49 rv;gSĄيV[}TKs~}J/*v뿨w}s-yGw-ug5h@}%N+ k4e!M٪6t:,lU,QcrrᬆjrKaCʅ+;BRuTNJ=A|d$i$#A#b[9&(bN߯|U_6ŪbUvpv5ܳ9:D: 2upn sϚe59X92~1&7_8\Ƽ}#e-4r!pGT5\[k; Ē~ {o|p=rk iJVǾipV?%TrV;Q?U!l8Zp4Nqd?oUZz=kx)L]OcB~ ^EihwDa+,2Wz[ ^Kʨ>\l/okߌY#1so>mў1-4r!r7r[53k#\wכϚ;П0 &\O+)kh8w0Va5Z=r9WDn'.<\ I#W9ޝ0fφ1*>w{;nBic4_-9],y/s}& Zݾ&I$!9LO:O⊝Ou-sxWqw9h̊cD|0ԻW""XFږ٢̣~\4|YNrEє9;&'HOE }:u\K#:yPwBZxޱqZ݌=KQ ?>f]5voo=Gۧ@C w} i\fN=kKn76wH*HKy;w?*m >=FەPzsg;<8?:MHYB yv3u9s3%n M%Z=39=D7v{8iܾXyN=}Z,V:bFrٮ .7X2r7V9`d/#W4œ~ZqΙa٭'vӾK9xHeK$aչP ^W\W+-` 78z MHhEkެfIb{3ܗ][C^=Eʧжe K7ip]fHsEڡa]rF:FK3?b/I{\eOPlӖfG%W2:J>}JE@ʪXʌhb65:iJi&ُIYQg;k-.ޣSt!vr8~DWHt*M+w; i ^2fŸ*8fžu<q+}u)+8WF?']sgܫS2O̕MF;g07g'mf8"OO*-qXgk}mbaÎ̺qom1=x1>s\r}Uu1ˌRww>> 6 [Y9uǙ{}Efô'H_+42ݻ6)hb/'eb]D4jOWµwJ,\`ɛ+p֑jw-}nBG5PGzVdIl92_7zRVjMq]?MW鹟ҳҤiF噯sN7帙5qo$B!FZlޫYy} Yu1'Yjfi~عN=%M=sɻ}h2io_w6Ki\U7<߂O-[Э-՜՜՜ғ}+QgC9N7]qP? AWGލBWtJiO,m^?X| Vlvݮp +!\|s zY.YyB;jCU /*H%uQő)qtHYjqw(4t?h}4Ng,'׹f_ץ_cKedimh s;\#mp)v<κU6AX]b{,rzY?MzF9UyZMLN_8̓Jbl1x*pւ lz/E㘌e&S4eh<>ǒ1{b Y;zϿ@',sv;J_:8r8V<꼡ouenw@݆my2G|hPCCQI7XP፶Y) k5Zd-VXk fkcYGi1;a'Rnl[Rյ:B;euLfDY&E;@seF70幽.m,dd4}ۃ4yYYtyix/͒U+|)-/v5<[ebݏ[V+κѺEg Y[#oݧZ 'uOMVkX57['73_ idHQ;nڵ@ڣo&nhwٽ>v_}=Ⱦ==~c?do߶'SYJ9{`ho_fy2 q}O{IXyYeu}aO˱f?J s|Xto7Vv7h_ #DqD)}=۞g/+(iWNlj9 'yQu8ʣitrtq;p1X3錑1ޙLLgH\  Yu6:[Dg̽|OG:+syw{oP9hݓBKIJkb{:(+"&(F;g{*᜞t#WWp3fg1OT{4N>/jmYb8NmyrJ[Kzhzr"s=yz;S;=s(. uPy/!^3P<ZT>&9Vrib9bY?d YV0R`xI!oL9 wM39  %jU``;|4Glu;/PW'~^y^y: t 7H`hy&Mɛ7;o^ޢU@^&oCm;;w4p:\O -?y4n~緸tw߽[)* ? sI0>Rs+\lߕ7Ep8p2L~Ew)P/ Re@@f@˂6/TеGA~ \ F-0`:2f,)X^X_"Z]`.I+I\@/uMFzUmC3tdK>Aɡ!0:9.919`Frvr^rQ%,M=F`Cr3Qʪ'=W Ҫ 6W(l]خca*[>o +y)_8pE0ppqel+ce ĠµƑJڮ6hKJwƒO61X_,{T\~= RTY^aIY_R-SmuKOuJuMq2505$5<5JǀbY=595=SRsS SKީUj&*ؚڑڝڗ:::::[dErվTl6,#d8՗'sQTEUZ-PYǝqj2S±@uұ[QOխOQL;^ڎqY<[Ѡ+!ct^U vqY+c3cq$V}chtl̸BѸ 1ȌU 8BDu,ԱOâ)}ȣ1KhFl~-*ZZď5E6m+Yh!X[Gڟ(:]tNcQS_dA&.os MaҸE0^Է0dGdqyq5-;;w)1$u(Uܷx@`[ * D4Եz|BbZg+2s$_ J%e%J4)iVҲMIN%]Kz.W2dHQ%cR27 My9 BzdBdW.38&{,L/󝒹% 35և^̳n%KJyc2ĪP}.UЮ2s Teh5JVx׹νοdU92R]+i26oI+Y_z*Z#3*]`Et%KN_-ԟ*㘦>'Ҝb:K6UJQڶgincJ{QH*_:ߡ#2}PDqKT?W3Jgk+Wti5tɀWNZͥJwQ?J3K(=]zS;V;Q;;[n] I{{i<ݷS kmͼYyW^'NfM_{c-yo:͕omVy:Ψoớ5T;j;5)kƹemTFYN˺(]f}_sf8&w6lC廛76gG:7ۛØoq7|'U\髙%GdzӛFƲ#IBVlO _[GAHm8=9t;CkJcpII+={z} Sss Ms=sېrOi34駑~p;lC6/Q &} 3C(7z+v6BFF((NН ,r@ȯzKtzahc+at;HGM'wru14g?ُ#`h䟂s i ݟ}f i@wB =zЏ*mE"|ִikd ?h` ҿJWG?SC^ z 伄? iGhڥ/BOo }Wkm߄&wj_flFǛd0gBߐ7,5,5o2OӴnb7)7rŸI2\۔KS~ԢΣy-[@nEz,2VlRG)O',%RhPGg-Zu.^WǓ]ɏA?}D'O'tЛ.υ kc&N.B$~9@7Q&h4ǐ\,'HM4n@"nd!BΓyG&r|o ؓZn1usrnB9$tM<dž[!%q>JvQ.$Gr}jAv.mv^Oƻ_!?&fR3|M}/s6+HH3Jv_ $\9ߢoQy C#fd&&PǏ\bgbq_ eq߄~[>h|tEε位A .NM]΄%cxڭƭgcO{F~>$/-E"䍐ף,ϔEuuA)7sO П?݁~~W迒~<ǣW+?󔋯9r&#g24ԡ:CyÑ3 xXEOI뻴KDuMD}yE޻-!M ߃@[#(i"/GRga=X1V_._HO|hh1%4\ts59fĜeSOY0 ~ EЋHOt, ]<4/?%F.s9sNRq#<mG)c9'ةw ir:*tsz:}1ꌐtS"T)Gjk5Kw!蚗9?wgQoZ+bb vV!볾ґH;:Ok9p44֟Yqz,+hYl*a>)g@;H8^N-t~XM8Y J }5/][M#b+y۱roځ\)4$͜p}͒7Ϲ{+~wez@.]Sg-^4JT':*'~ӅӇ3'٫ ~uW\U+Qqc\p Nˮ]]ZCiF螜mz)GAǠ[r: 1Ѝ8* Jۯ(gGF78+t=Sc#)/gwRՎNg;I_5b.:ƚZV[.:z藫9ņ1B8ՏѱqrLcl)<{߽]EN5F=3Gh^Yտ> cjt,R%hFxJ|9m"*^&~|+>!}Uv7cE‘q)S+gd[#9YRqY󳋳dɮ(h?9"-,9UvXDYjDJWu7džfwkM;%][u;{ *>7OsvACGdG_j[dȞ-ǼE[4{IIkDef<;{[=f s' ߶&Y粶89DSNRZWZ+~<4nNx=hʜ9Y9-hu&T41jvucsxN^9sH9s3^eI$i93s,Y7mL|2{[Z96lss&_mo#gO9s,dΙq9Ycrܸ~e^r&I=ćkr˲rU6/-6m)q\nO54G޹_Ǐr;Vos+*j0o *K횱wF۫B*Ia1#{/i-G8,p, FNg*L|-dA\ 0c@A*Z_2>hh[ءY@T -$U@ƾ=5/df [U* $]PT*ǔL;U`xۨ*p3Q!oHUTg k16@53^#dqpXX?8?V*c@W|zȗQv9Fԩ`Iȗ>vO2(j,*XL?E!εGԏZxQߪޯ2%ӷ*B7mJȼ`ѻ@l]p0c:o[kJѹx^Uzx:TUc|ca6] stns$f%G[%)>,;ꃋLHz'OTүMK} %ؓ|I\(sd([GaiX0}ğB?2_,,7PbpS;OʜP怅:{?ձP~b^2+zuBc&}ԱPn:of&6J} b• ;j;n m el7PM2)TeS(P72)0Myao`!%iR2I5?u| %iR-C~sS2I7򵟤dnMy_(SMT?c'&qM֯=+ͲXihfw^p;̞23N1G w2;̞pev2{-o2;n, w]2̾pGفbvQ5f3žO2{̮p? źY{ waɲ?ۧ->k#;+d;9V <+)tRV)uj[Sw+i\a%GG*Z%kډk_I O J|ͺ11,quKbtAsq'&nMK$$^&'N~D: 4h.Ъo[ #_`P L"0C`ZΖsާwIKR*}4i#%6kͻW"_-s~'~;VġZUE36_ }l%ǔwf$3ҷ[~7"_(HS+G?DRPuJW|W!zBG)k=7hskIep?FI?:Χׇ(vMoPq]$)R1[YxmH[Y~)ᨦl <+c Ф;_DίǢ|,| iCI$\} 3I t \HѹʏR?s" ~ |Ez2C?=+wBE 7Yl<֕~{&DDyD͉[ItI|.5Dĭ/$z$虸-+q{Oˉ;w%&ML<Fbl⛉%!PB*F!隆tZ -3>[Fr]f +CCQD}rXǭSU|ʶ.x;v+xIknPm״D^M )pyq+wg#HhS᛼K"m 7p~4ʿbok|^ߟkZNŃ`r!ᚷ6oz2 8gMoѯMt1r;ac[4w{̋mc1G:bc#crm4CMZ9S׼У\fwkput  =`xp_0"?|#<  3|+|;N09L < s'`i`YL"x.X  `UKoOowwwu0"LYYǀǦO&|A?̥^~ yZ:zFoF Po>>Wۂ;(?Nு?~<<xQʚy'OWW_oPl}2F젟NjbEV]CN3|OX33,r HpFV8)h2zZhgE9;#R/.p/OS3~zehKޠr[eq#s?o|*)whzazaj"G39SǪ#g^NQo`5VT ӧs'{ҧҧJ gx8V܊ ~~ZCs~&IMKI+)V ZZк mxar<x1*WPxbW[ׁLN09UhX-'[?RV$9s6|ZKp,ߥJ~k;3Czpzuz[tWӯ̬DM8(7[Oq;rzڋtbYAsRۃۭ\~/\rWbA_o7pXPjDV8 rկ߂Y iR 6k- v5P[řLv;BgYw69[ngs9wN9geҦ_w:n}mrۺn7#} whhw;ѝpgERwnv;=~{=vy^+z ^k.y.^w y#1xo7=x D?92g[m[.ow@y'3^q?S~_o7M~I]~o3X?ٟ %njU}ƾ&3ߑES+D+]HH}N7Snu]""M##"m#*6tt{W C3k*{ehS`ڋz\GyٷmO-!ON?oo i!-y~ m c970;V߀gV;/+lcJ27o50{CoC:y/Box1]m_g%p-v_$g͎O'nKJ8o kew ߆=yyf'1;Jz$v=y/2g11ٓm?6}o^Nz>:I &lZC`ʲ aǹ ѾK`gpvO3_wt6glF ZE0^߃3s|j"OWӠ~pE$D꯾qx p8aˤ gD)fܼfz;Z{.v͒*{ H=zlz!#O ߬⍏Gy B{s7x.D{"q|;x 6S^jmbsc$|^ZKV`R0I-_wV9Z7MÞ6<쓝>Ofא+))Ka/RR֕/M2޹AI/j v:sen%\;m%T]5FPkom)r)C;hQIVgeb2KGZj{Rߓg-5qR%̪q{sW7s%pܸOp3L'N633Ztt 3)eS{]u*;h +GW7mr6[~{AZjsEg[]BK䚧{}/a4i. =ג5Z]%/hr{ѥTB j:.-[]/,G%㶙>ffUF2&\#yD(m_+PzCǟfƟ3$!Z\{#?k:J%0GQ=~5,'ʅei)π{SKg^ifI=>/϶zGiY.$O[&ͭ>ih-`(XRFyp-崢[+clft2Te2f32y)F"FRF2Fڏ0D#M|B jH녿S||7 do7˛+P%ro^`P[noAxǽSYo?:j7 ,@O>~?T`-qD) ,ji| Jow j?O{GsoEI)ԍ4h,T2#-"#]"#"}582,222&2*ȤȴL9ȲJ-]:999#P9Ѹ@@)uEE 4Y?.F}e45#՛EKB5\J+0Qtrt9:7P`gtyt:3)U`8R" ǣΪ\/XqN;kt߈jnu/_׏W.Ԡ%8'6'H@4#o5G`A>8Z/ԿNkY[Mg^FS\ckWNwmɇA<城% `:qMM5iAzNBiʴYҜfek~ Ǭ2ͤ1k ) 7kcԳֶI/Sih̊)e%naJ^!}\YuXb5Y^@L0lFܬ2++'uB8D%^qTgV̚>P¬"%L&5HA"=M TVXsᷕXqs̺,?jmzY"+sݱ<2ђgM+sV V;Dí̊'-",}9"|RDUu!#HKjmVj9ڽb_ܪ3o[/W*6}sV$/Hqugz_毮55v_W_;,}|;vB)@im2i.??2%x\km}-߱'\toij5z\s+d~8_\_m@W #/3OuyO<3b^zYpFL;/,9uߏ~[ȅ*1eL8 >}'|h1_ڇTkZ.rwewZ!1e]zh|:}_ok=e3ߵ滊Wغ*ͪ:\i+CK˗+bfƚ>ZYZ+5-7Ys/68ld'5գ=R{kN9y=ْczk~fniΑZ %v~`Z({+{~BuCޢU}ZS?q8밵YW'|;Xfm51Ok-5K_GVo\6=&:_13M˼uF˽>f(k?P`,Eh`Zhn˫>5<ms'$Su8ritt8ݝ^N_g3 sF:cΤ 3͙q8eJgQnq;s9A= ׳ǹq7pS[sMf+t۠w y:1w{܁Rƺ.dw_ʼY5oKhyIhdC7/z+XmOyT'p4*_cpDD.!!"4҄#"D8"ሄDD44rF$"HHDH\DD.h"H4sy0 ][_gw=]kw<=࿀'Ɣw*o4/~KY|<nEG?-mr h37hhiΗLu{5.ə@=[\>zSK}{_x rN(7o==CJ.dOhmx-l w]hkZ {) ث|{E vˊY,ﻈƔ9O-'s /6klנT|_@տZJiK XS'UIjt5u7'_/|Rh­:SM<.Ѳ#^SgJ&8yZM;'M<#y_kE̳ܚx*X9#9;/;3q_k>/  I+IpZ<!_._F6>jzp5}-}c#d_PlLGrq;1<3Ny'{yg.qMSRެ[46M;Tiu&B%B}.y_o_;O  ]Эy AEKiQ͚y93OAAaj_91=2Isq>|詢].GL2~M0UP\ -werS]o,m<%F+Ϥf 5&PM(Ο|Ɠ93B6qmzCys O}aƖ.'bUb,dYMo;Ni ۞}|KAL!cI{uF۸øbH%~إJNN}&90Y>GMJޟ_U&?|@=|<}5]뾢~~.z>%Zޛ/wPui|: ϗG`;JtVZMJOe ޭWZ=itCDfB]oJM11=) foށYV M{f΢|52ll\ܒn$mriLwv~WpZ^w& |ސِ& zow:kgm sV> ovg$7Q,ja#L R^f~%.C> #u.ypp/yw/7Ҽ̡ޔٔUYיžyF*Ҧه`FdGdH`ɹZUnD4{ɨ}H{? \w7GH/Gtg1=Ho.HBݗ*OD(;232ƨxG12?h<ذ0ƀ?Cq*ƦˈaDQcXk*|yƩ_,٧BocWۂKFw)>[ZWpZWT0vOOU0HU`$qwb'R%F$F?rT%&J W$D]bJ&^%~@*wgS <Ȫ@u2dd/Hན$qϒ}g~~%'?~G'˒wMWg ;f =`A\ OcK.K@E˓kT0zI| Mɗ&ɓ*/usHI!)!5 5Oߑ|pn{Rߛ} R"k zd' F?R0[ QgU^Vo}^9hm/w Vw+wjtwrwvwq\wZu{=?ᾡt4X4]e2AA*.KIs΋Og@>~ #u/_^MoMk;;;;W_A^ާҿO[*0]XA\_:esE/e¬<|~U[/bί8f@v2HmT\Pb,12n'Ƙ{T>{T>{>`ZϤ?RdhmKOh[  s|SvSCf^O߁Ywx-ӭ|B -ڤ:t2,D`T>u2fxt߻fRb8=?}tU񿰞TT\ ^~zfi}O /btg(aHWLމ>5IKNލ>5 ^ Os?ޤp}}=kEk2^?|ݡvqVq0|\g.zLک}t))`рtߝrYm ;d:u \bSc*Q>|G}.~1(H=(O>L|GЉ"7ԀSS)\%Iq:ܔl$%odO*Rj2R[N!o@*,up~S!H©0$T8r8p"U|S'ͩ^H}8aT1-жTToH>HݚX4/#T?ROXc%=u;$Y>\,U?wBSRR +u`f@~Oj('S 75T9R|D#SSRO~!z goNm+௦^-uW _M\uuuuuuuuu uuuuu|j9_ye]~vZQ3ӿHO=I~=:1>{9mJ!e>> UKMEC4 *M}#P%:ɗ97~(Y%/b,8^y |:z9χG?>=,/G__'9./9 4S ġđeM1w=;??v{ S;ql(R:ڗpo_܉wo/ݯk7p F>$=,=<]YhN.yYcv'~jR!_}_5T^}sjKW)=xUtV}%{UoQo\MV r5c;x6 :e91Q.y BrpR1N{B)׹J;78Rm*fSܢvnsk۝Z)sFӧҍ8)ԭ2>8ݭl4K*_՛U{Q{玨U[~msehxrnB;FYi)ZGYu5Gxj (N8k$dt! \tVޒ|gCd] ,.fAGO;&7zJytt(IGӱ邴<:}ettÕai __o3oe)MFө̸ҦC# #Mc2Kk= nM}ffRibf+;w1e|{Žfcslӎ̰LYf; f {f· p=bA܍Ư_J4xQN.28y%N>^[ .8QǂM}c}}J١;$RfTWů}5Ư;wwnW5<[Cn3gґJC\{+vK+R#=YPhGoI--@{]?ThWRmt?Oۤ,7f-mMihm┉6VV*m/h%/BhIH *誘FHL_&U w53T#cct*wȸŝ4]YsU;ly?+o.6OkM8Ds/L[!)̭ɽr?CqS_4VܷYqg_? O'$\=vlHi;FUgcJbtd-ysk2&ίoȊjHB=#M~\j3]o6sG"'[(,P3*LZiW.ݹxx}/œ kr֗KeҼWSw_]|y[`/J$ab ɗܝp!`8O=fSn;=g|mj/a{{*F=mvs"ݿZƜ}%Wceo_>m 'j.ߦl 5 J6ƶ7{.*tb?D鱵O[[[[b/rbوP/^BMMjslʏg?Uؖ{ޠtt7Jutw7o#C7oRtOOc/"Ve e+;Ο0l׈bc2)74ۜfWw{{b:}0V*}J}|Xo֌Aҍ-e瓟<%wCcl ?y|^߅ hT׳ZUUZk?h:'C 򋅫a\5<ﴌgGR2,u|jP>9p- ^CMD$&}zJ3AGxԼ>RQzm9 t(`27mvň0# ëU*0N?{X-`y[Ez]1<\SoMZEqKg?>G^o/b7K](*'Ȉ=cGڜMWw+ѯC埨W Ռ~DИ4^ ΁] Z\\Z\Z\$Tl  ՃN< b7ƳAvGw u uC^PihPhh<4 T  O< tY*5;v-kCKCuBk!nB;C{p tt'Cz8vߝ C{…"Nt. FFW@>!:΄OELP>H&nDE:'w@~M;,1xEbubh#hEb Į^~nI#;:N|*Id4OJ>pdd7POPoPd?ʄ@C@y=21q {:9?T\\{erMr=herSr+z Cɣɣ)%8e YQAWrj*Ou ےv-s:쓜1_OwPTPT)ACSGLMJUj`/p֧jTZ҆6;S{RRh},u2pK)B m :zXTま`0肊  ffXh.hhVV6l)^`o>m'/8cg\F )vsxݪzV:`uJCungϦmon7{ʵꨧmsA*w~O,XEϦinC.;s'S@@3A#IsjO=4x w;G뮄lpwn!(;.J+di'mBN JwLwIwLw^}%>hmzPL.cP,1c ɤ@>px6g,k7 ~ QZMc5FW<8H.w$?d0J#~xpc %=s?OO 2]L JfSr9CW{>/czA?jI7ͯT&wӔzM+y+chr1ȔVg.E/$J,')y^OQr#S7ǔT1~?$Cy!}Nē?J<)gFXXϱD3ȏ(  6LpGe'(o?_%|ԞV!n=M޾N$w(q˽ӯE>MVWm DJf2w"K-}.n7c" _Meit20yH#ˎ"Qq\yRcA^FTifc|8;E5\'%mȦ);)5:y[Na\b̦y?.a*>n="qG?A9jR1;1?c[דo!/"Eq:M}2K";7JZԞpi4H ,N[)1q-FYV:+Ξ;ɿ@efWb:%Uȟcă=Og[ d;9+ďV{2i&[˃l]Oa=Z qǒ# I顆?sRؗ?>e2&Cqz} ^b/}DO6'Sf^ NӥO_K&=I:=9E??ngO@c}l/['?u4/%?wn @1 -nD|p<>k1#uo] ᗂV1᧭gM";uk4ˢ=ѾJT(8:87:,:xtJ s0n3gv 0!~z.=وs6{oc܃vF^oA/Ԅ{EeFFVQ>.t2<x`{4$Ͽ6khQh'^/AS$\('[Z`oF\"G|tZ﹝@m@8> jL?׀pGkvGPPw[I) PԿk!Ͽ6 ~KNǃJ):8JΗDFDGOEBOHd\L{f, JZXXOڽ}ӏ|B|Xp*?64.?MM=ͷ'9~fRڣcՠE^[[ ZZ[D{o~vzO@bGc'’ѽB=wl>.glGΨ^9ArǺ9mhnt?O?>'!ጯŷ[RzwJA|% =4x3ޙi3˩Հj:g! 섽'< |}>mryrJg_$qNn?Smp/8wmNaD5MܣEξh'O֌=àcѾI؍guz݌x/q.;#dv?S[2WusokꙦPo:bABXd[!$+Yr&WdmMe*7eǴS }תyzm>Fj߭su-y;TjzR5ݪ0'~M Un[SS8gŪ;3ȃ#YғăG?Fa o͎-vg\bJrs/Ůpw++\qf?'<^5kpWxs{._mO{QJso{(᮹_$:y9Zd})ZF"V~IVuZ/ȯ;qgbֶZ|%%`[A5gW uҸ즨 { V Sd $O/x^DjK3iK+Sտ=|<ŕSﮱԒo7- PgASb~w-Μ*KwZAyo5 FwҺ3"=.?0g+gSuW?{mUޏi:9WK'd$M=IR9^Xg}vf$%o⎻`R~${?[ [Uᦄ{ h;[eL;:ƭ2#Meɑ}thWj}Bw {t51XM\WM;M븯mCh9$6W %Zj`Ō2P>+@l]!y6 $ȳKެ~7`͚$yY$o7B7(/K57Hެ͚$y7k\a2+$Vi)>-d7 Jv)(]'zNڹ.tsPHqZZr9\J N2dGڝ/2ElJ%? 񬄰6]c5韨deG$OZ}i/['?h :Us;oPU+ܻV[\w;cy61.dyu\xVέR'zH_[z~P?6L#lF;mhc1ŘaTFhl3v #c晎,2=;f9a5'SYf\j4י^y9Lw%;`%;!090. !v7?vG(bB􉹌y9zLίei偕]]@c={}r=r}:ֳܕwa9;\auq rt݅xXzZz+XψY.FZDW0&yĮ@h K)-=o=wa]m]xb80Ӗ=^Ż0^ʻ๶rk" bYl/Ø]dG n/ tsi/ d*\k/0O l퐞fHϲ]k/ȺҋRƼ#BvFje,E-#?#P2s|;\d,B <˸V}ipދ@hq~G['гQEz,3"  JY*}a'탌ۮg6{7n|+k3,eWz\+5^d\c"Gv?|giJk'^!9҂nKVJ0JJ&\Rԇp k'!)*&eOe_õ|)5\Vv?80p?E76$6BUZ7V$9kMmR{$kCRc/2ěx)ZF:)>:Bl\.UL$j`a/{Ώ}ц=m"k<'evIgKkt_-ej2ȿ/r_uz~_)=Nc6!W-HdʿHNzqWˈ]*=N;!qW˪="rP֕_z.vU]ZJk/<{2JzS 1ײcnW`H?bH_AU#5 iqG6iqo2o#F qj83|"wJ}*w &1MK=a&6~*;rCxtQ+c=,kj-N~/i|?Gxwp܈qgʹIՀjAKAu\ZZpm>ks޷ӧ=>I<)rpe?1ݘ-WՍY[1BkNPJk%YOMa)bA9i%ĥϓ!!8]ʴPg>)Y`F7zy_xa/} JO]%A@a tey+r k*ns\EzO}DMWT4V-Uujtjڣ!uTP1ZPs4Wk:j]Z/_+iCrm6VM*,JhRN[6hmNmO;֎i'F].'.V/&_IjxBfki }-_ah˵Hfe||Ȫ!;|I\õCe~)ZOJz:Sgf-%#/BQMGΘ/Ȍ)ϤÈflN w3\3P'M H5a{dE*ge`Vr}m`iԲ{Dnʂ8$! 7ć׌F͕oDZ j}f$~,4bMzXیW[ +Qr.ܜ/anzz$ /^KyKQƼn7KRAI'~RX)c()gz]/r1o11}RRy%["",ۘ$U#9Z Y+%% ].ee^ǯ0tw,ڻwOoInNMU@ 1ہzzPaNoo8}䤇]aw *l#~SMv*#5VKɛf"m4c'|jE%3H$mAENJ{Z1|򮋕}*_xXNܕb5ma9j8LFLP\iUzU[R/϶xUߵfhưf/LӕZ:ī5NCxHw uD!݄!fZI{<+`ydfSj9V$mcDtpPE23m0$,GBaJ.~^UsګT'Gh/_Q.yIEߋK 5}|Y ;w.'?7ka4lUкۣUֆF* dm* Y7\MJK`(=OY2 \03raam #x֙huLP;i]ZI\^Μ0JV]5ž Wt /J S{\Ur1PM$pKpJj(Gi hkkҳn\ _FMd|ksJ)jibCwkofEK7n./:J~4a#iCWxH ٿ/#~K4+j-wNct_-#Jқb~i}i=ȴ#iS tqm<}bx# VmߌKgOm hh%6S4"^[Uo*WX53۶y`}DS#^;2h;IkH?QN^U*].B[)kߌK G>RyKU%q^]A' &n-FA먅ֱO5jZ>];~H}H_ n 2UF8lEf_hf9ٜj mVՓ \sBs \3tW~xSMhN@$h+4N6/I/ LٗXl DpEy9g6u9بl6f'QQ bSl\'_k'g1,+2L1 Ͷk1 ͆+Jj6$]6cJږhy>} Gh-ԍ }'aND:;c8}qRhD\r=^NL/3 \:Jnb(,iz72)cc%Zkj)1FaAz?|Ș5h{JA ,m]Μ2pupdTB#dL3互x6a K4ډ=cRf0tcVtQ=渔h0Dt:|'[ W4jm u<0ӈAjiDGS<,S^Nf>dW]D4|/1y\9Ō5#}12;7>"t jUCލUύTQivCҬJWY;lfIJSY'A Onc-'{~ƀd9ҿNC$Eэ[$NM,qV9k&?f7D{\=ʘ ']yA=f]7JF=okCY#O9-J|Oȓ9Jp7ͭ|rqyJKW粲f6-sK6C6N\RICec,;]͗OZg4yԿcʛ_j:x"]E7j)!}*.2ϟo}T`ߌKW Ry-0=hE4OY--y}nnS,XW{q-6mQNb,Շ61%Fc1c cqȘ.hS*syҭVwk5ƚn-X;#XacI4piYG0'Ӵir:bZ ko4kUoMMCnFfKl.JZnuMuaB,ΡBT7592͚iծ2<`n׌LV5\kl32We5zU֌yp)1kJ,3pcN1G#^<ySNg .f+ff f$9 SЕ 4FDgr\j4nj.=zsl0{.ۡ9pa33ffd`1 3.~Ƭ<1w2'<a8g JJO1f3t>}S?,gRCܤo2bl^e9CrqTXAS.zT94яmo0g5#ڟ'C2Gߨ}%pNxFkF'E/;Np9h3KG۠m r=^-і@<1zgm6[)3-_7x 4Ө І2nj f69 \kA=xrkCpY6MnF0v7V+];5V }a̞3jg=Hɚo:sCAj.j7^py XM֦Qc+>#5@S3/eyߊD#fR$V9w ؼ03J Ϛ|a)'dnx_)ωI? FOѼ79QZ%eT<?/vӢmPGͰofdT_,: ƲN$-Sl%{B]z~SjոSЌ&JM)&KTwC,EX%O*槉7jOܸ 9V_ޙqI jB:sVqkP/e疧dU'-A5j)V*Rۦԕ^'R}4a4=2"+򎆱sv.c/WdeSV_keaLmc>D|8a#M02S!R}UD F)cF4Շ-F!v.H+ܰFޔ1H؊ ZDXC2A]tTFR 5tT}c>xh1Q aybNelF[j>xb׍izc&$cD΋]Skfa:^p|t/㌉ZhLDct*5h A_@T/ՠS>m_ۢO#: y5@A2zhF1Vj@]ft&]@ndzoiZ+7ځZ^֋4w<_NT7e {V w9zJhNLn1MtcTO7Y}F+:ؘզfujTkղfEJtg_9|Lla !%' ka6 ] {'kN6u\iUN3op-5uۼ3GfLPVl?IY^k'+6Z\jqu=}L?_WW%+6M9Z ƁrzkYo#G$]i$饲V/k5_;MeEBf}#&dEJo"oMF9lzQV21YhZ*E'5Yirtj̋Z')P;$%nvLJRIxQ\ d5,1eഇ"HUڹjPW97;buqnwkR:*vpWTQ}6)Tm$|ZyО\N/}}BIcW&O!)eԤڨ\m`օJҔÔFW|-ݽw7(i4 Hkں|0b}_Co?QuX1]-X.urlP f5﷦S]SY:4#rZM/e;9Q\j5-Ije:9M k6ʿ g՝~F`0OGg+w:;Ɲ>>%9B_vg*98GT4'0}{m 3ώY[*Cs=#x[4;l~Q0LyMg'_@\WrNԉ;)pQ$M=3K ( 0aœ0*`BESDt3GgrST9wFدfYW@QޛMmOOUWv;|Ǘe |%_ >TGW u{|IBBW!( ;]1R> iY_39w\# nnI, cA9csFiKRBMݞO'S_L>U9\TT*79UO%+kp\W!. PD(*|KA, & S_LaS%T)) TTijhjiikZjjk ZeBkhZ;m#UgΗGS)Q<}/Òw& b~Wk[ha0HL}aiZ2u1:gYL߈'+m`.waEJ_RvȋZQEO,%ˈJbe1H![%[S0)1sTU,OZujw!䣱V ܩ4 ? =_t!+U"k5đD~+ D'LI-&.e_\rBBOB@efm̠I/֔Eij9|~_PP[ᝐ2jBmu^5V5jQKKKW8`Ou@uF0GNW|1 ~ jw'Sp19҇@G|"ߊi:FgYlP*1)јe.\g`K2~9_o6awd>ο_*D WuSuWE"U}UQ~8BbRvv-kF+&U2%n BPF(+T@++` VPa3K# uB$'aCaITיBXT^]n?D!&Ļ1|e1 ƴ7ezS1W7lHS(P䵜6m"!t͔gQRˍ<TXVtjjjG4ڀ#F-~3Q;SlϣfA3-nԾ@n6(?j]ӈoMYnzQƠYyN:rmv>:64/%*2tWCaMLG[V4"N[' f2;*(yۊbs)ܶ©2-z!E<Đ?|AdsGxz6"OpbTw5Fk]8,ǘ^diLk9<1 IYRZ׏I~{Gk b_U$OL AM9Y@p=X)cv'P\ 5W&?lO&r#E͸:)**ii,޾yׁƸY{8CMs Dч.z;KT+Qtu-Iiڇ MOމ4]DsPd,, yd,OZmhŴ|SV Y}0K8fi[eסesHO/y@硬}kYT|T3j_\WKr7!L-XR:㦯oD>,l59oI%#Nnhl@ɐ,|z09Nr"Sy<XxDʫɿD>1-{$HN#EDp![q!DDĸ!!;{{zȻy-?3;7bT G}OOʢ*ށTE~6_/yg!̘C,lnf,ʼnO"}Kf2}=euvƍ˲=zxtɢRgYoCN~F)2 Oٴ>Oϴ\)<ӷWwZt5@Z%UOR6ZJԐ:Nt! DWэ4 H3ME_F,!$E1tˊH]+tUIWXĖ;- ֥I6dd':fZG[.DYE롋 Etyj+aEH"&*I9}[Kl?sT (a$$ߎO`o_IR~v:>f@o i>8 p]g܉L2ׂ}+_-909F=y/ګdڟ׋t'R\I n\ׂ#,%,Gd@Sr:ހH߿lfPNVħ#Ч ~ "/F7t KJ #7UHn?݆ Q.K%kGPD6Xos%⿟ 9:Gb;\S*cWS+a` ~=w_.r} ,>Fë($Qp "1`csL2=]` '[DA O5rb>p)Oz![i|?ahe*=wGW! t_/xJDO?%;BC7Qs} /00}bHH,i)H .G*"`eKcjLJ q?b+B2JIk 6i~_5,;c di低MvSRBg*q/m .&+5M6-C-6=!.I%N׈i/t} \;>FS_u^ omM3Eo/ >jP~CߠKIE5ûHEchJyѯ>k1Q5]=mm%L_{KƋl³N0k٦Ogc\6gclbpGb\y+\H8HC3_v[s2S^q6}CwW0tOfˇe͇gOF4ȆofO𷲭hm eq':n1g[Sc<ȆR02x Wc<ζDqmqdۊ<Ͷc8ϲmqRmqgۚLҿ~|m{fw-9?0ֱKlx*Ϋlx*O23ё<$o Ly[fC~2*1GG0R]hP`/W&2qikC3#st",JRA!yI>TBRyTQ*IեRmTW'՗HRCXj)KNRg)\!/S=!U'%+ @ Hr*H, Br VP$?SB*A 8 e8*Ol TwU=pD8 yD w= |'<|`24tIԘPԚ\ԁh|Rgx.-<Pfk)LaN&UP,6R(m[-$Z&ք~Aɧ~iIp mV{p]0\0هPmQwv8yڰHp+iqpŘQ?m؄_!#\WӣvgsIY!|@I Ё=7uj*rۆea-`8܅h,& 0it_ذ ~I~9ɯDr0H(!{Ű|0xw-vDr3.r,iAr:4⅌LxWEFk-1HԎ!~wDy-Fq:91Lj* 6ۑ[:6p='eP.T@sA6"JPf(ZhYl%UsO>Dmgnnߖt_5q9T\:WL hhN1Y %Ȉ;5{xLi?3tCC6L{ZJڼHu-zPS*ɥ)z)++br= UVLPL5։Y(f L1eaCqe5 ƚT'm݊ƚA*Q)E$B^_؟(e >4}v{1R:3?hf R\Ɍ݇HC vs|Q辊$ ? 4 i5 io ٙR|ɰuRH+aV}sSFiEbRuQ iwH1}f`TLSk BS&iQ>ickkAXE ? ( O!>}7 1c{R; 3RE Yױc[H tQ\A%am NGǶ`k11%Vn2>1r[] nYl-&?W?aA+.z,W>~leF`$ƍ`?& dS`fL+Z~H "rɌ[3҆ԙp3K u*&Rl\v w\*2a@r! ?Qb9a[Nir)d[CpKp!ZtB7ʭ4R@w+6H%tOG:}.#M\PqHϼGQ6Pp[QADž1DWOw+~VY>1/rv(mJa}FC)#a,9^x1nd;w^ eFY !m6He04f@j%@ZkF:D7K!fe݈2DP*N R{1}@t)]H cjI{mn;وCn: >h0h~y*+qnăxAMwT(sVR)ʷܫ ɘ;} nNYHY^Y ,jE`&Si´b:0]L&Œ`2JwOg1+&Eq&\an1?;eYVpX7փ{ ~l)/i<'tLQ^l0ämLmƶλo=#/ i2JC d()HQ>R& g(7)HQzR& MFIJA4*i2V d(g)HQR&̥ MFKA򗂔OR7\2J[ dəl@0 4at@hՂ6 X*BًM*:mU:x QNf?`ϓYb:X#%QM{QM*eA[ٚmmK$Fe))Hf6P-۷hsM< (RyTuiXD,PXDF3RА: 5ü6rKNSxeԥaz܇PFاt9M ^s{%#o[1ϣ1l+5[o;1K|_osKDNX* C 坎y6拳#ctgh8*=ʢbJ_eIe9er'9#_}e__!E{8%B˒c/{hEyi5Eǀ;%vq=LihVx:bXh׳8ȣPy&I =><Z0n7tqd65f3=8b`qC!^ Ry:ˋ>8Ǣϡ*Пk8H^ +7|Yע&*'|c=#==TC7g #1utlAо`'?=Xw8ʳ/8'3fX٧5R=}j&v( W=zr#)HqV3 EC>\8PO(ЇCTqؖÞD18!"1#lT-yNCq\N3I$lNi7c喐OG }p^X|+h$l͐4=d)ТIoW@#L`,#8]t[G$nQGsuHkE>RJewCm?E eL`{D|;~g[51i kC;n䇎(f|OYCڣrpd ¨_`d#A~?6 35T$G Iy]l[8=,?].p@7lb# zAl0%G~%E-cK=$-jV rT"R$lPpc䟆wFkt8;LK_e~Fc /m|=sd.=vf-ttD޼N"w',GUZjB&!,i*$ IgunnI]]u;^Ib1ZQ%~'uuqo7:BʓP"1?;C> !n6K,3@{]miP@)Dg.~ˣ q$Hw7d}^Lr}a<1'2w=Xރ}a(;߅'@(~O@\{g ;ѩ:BA1DTQ%N8ZZCXF&©A˞S#6~zEl+8NqZ I^ЉaQ ??A%"jaEX'CAl=$pOG\Gc>UH#V22JMbBZB[ݛ$uy~%i쀖Ք@hKMKm5^..D8j k 99B@Xikݫ 4AjU! nC!"'GS)x(&Y,'IY ^ xQa"e2QksKEx$>낉NH׈huMtMH]3]3-t- ɫkkGԺUUb!Lw]w[BtA;#dMSKFn4Jh9L /Cu*z8-0Ti[ 񆺆\Zʹwn=CAqһR/N ٻ㩷fO;+5( ާ gJlJ%ր.=^ }(b{4%E J$ & !>{;wO''R EGt9HzJ~ˮ0pC AEgj?E;؉O' ψ\|NſſR|Iׄߊo!LJ,41 ڑѱY,JgM: t:[bQHxRC˪t*bSiR'Dhw'3\˯+@uut8F$DXVA36Z<֩=HW;-[.C - 5 u HWuwĐ+3֯=), a9,oX~b>>:8h!bis ?Y~٢ >-J&'gh- 9d>kd4X/B#t`we':? %S:pRFA'''On7R)hÂJk@G`iw:bb *Q- YπLΗEMO7ƔVAEZ0R0rXyb\5Ћj?I񨭨u$f.|>'  cqqHe2eyY`[0qXV[XDBƞ|}QY鳔,>Z߆o6 }+ZZ%URK5 ]HUt#MbL,"zbqїK%I{_ Ųb9H:UĪXMNZbKmafCIvL*DFhh_;鼠x+SIY3fk9ݧp5{Gʝ̋~p6&p' $9K.F#B^M#c( g\aJ2eJLu.>p6Bô`1L7j7& :TM#:{7-je}>oi}R%Zm٭~o™ޖ>U)5n[X_HY5WX>nAUk\lR~%*-wKew?<>)`^ÌbG6l,Ð_mݝV)y ~G+,vXk-6k^޺3|eGp9͛;o;*ڠWc5m&uF{=80Ėb{ׅg[{\ݩ:G3{߼~IekPڮ3͐UV-_r5&ݾ8H3Gz^O|ׂV[3GdުΏ"wk(cۍg."#]}~Se/SxoZ~塃 ? ~o'r Gc!=#N}3_HTf  V ~gG7f0`2)GUB ;Mzu(Jf&=Y\[eN-ݕYUo׊~6ݎkU7H4-J/b6^ڹ >z &z[_3&]y^I5-۝X_۫/lT^.e_g )Yc:B&V'Dت[5*znfʣ˕ϫl2բš=K\ܳYWllKW^-fRw yB{Lǐ3Fy4]ewW(9dSN=zd@7Scy5hX}f jl7QzY]͟vvk5RO_맇ꣾ GgFV1<:3͋3FOuc-or+]c̓I;%'Cȅ)]+*,t[1rȣfЫIr~G_kYv4̀?z'Vz{(Rgl_&Ekv溶ӌ=F6i]3Mޖx; zw?6Ժ5eÚgu[>u܄;nwlG nK:9ZsjJk_pٰEZ{|#tm52XV71urfXRt(uĄP7*@-)b~%z_b!Ju]L%Jx(PʷKG?R%|kF8J_`sĊiWYj`x;zVT`l3Hׂ"JoGW1AIqbYXj\/5`Q_unN7NaqG7j=}ϽTfK;MݷVp3jꬱL;59Hr.V[NQ ʕ-0a?_-|˘@Cs޾q~7m,ܬz}n& p,:r>&mꆇm]KFwvUT&R 3F,|Uaׄn+m?;i;{n‡/,}>-sfΓVA7SXw'V7-XU\葧^'ΫSswʭ<Su!sQ+Yk0sy}%U'1ϪK.,%y{Dslɠ?yW⁡K{v5hR8۟wiϑ:^?`¸c=k\3Zqz]f{Q)1ݘ[xue[WG'w=Xky \ݩG%&l?6 섚\fA'` 7qh¶Gujgǘ_RB4< 0,&͂f=AytûtV_TX>QrKK*/oKm {z攢qЬ,/|z麿F>:dT*SlHaף1V/39zDQR^tuz._p{T#ϩvNWQ,z/]{UqS=^3AFv8&MGzR?͐1U6U&]KEqڋ^X'vMӘ.?Wܫ8d|ѕ}s_vd7دrd:M -wM﫡FMB>ҺQ+-5C}~LY76>FpXۡR3nR+zݦ񴖷, Y3V9WqK۷ +Og})C?W5Ȋo*YPS_]d!|MLQUN3ak匱pA/K'6Ǭ:N&n9/i[sX}7U˃*hc]]kvD|Q9&6 kxX})v.fߝ)O.Z@=Ս^;&t3m2szjS?Ua%>[ob}l`FuuM Oo>C[mpq;;ihmċ֥̿Fw_4xfcMgg8S')i9@{|OmxۯQ?۝o[ѸޭWvn_k‚*c]UI ?U~;^G ߏ|ӗ*#wWOos}f;Xww̭~ bJnO"*NEoje}|/> ?2>9FrfQCv(cZsK:\Yl[kqa# \?1 FH) 5-m L;~ú)Cx] (:G{9e7uν =/Phfڥ~Zg+X_߬y'ҏ[@wS 6U?l|8ٱDvìО!}d ',*B_ɔ/ͅ%IWґ I/U'!d %QOg#KV)#μdsBTsl2eV?&s{W=Xy=J-ѶI?\=):Wѩ7|̹Si.q? уܟ~\6)jV,ڽE<ᑭfN5މϫܩ]DZ{{]ǧ.?wX7szlS$o<=}M׵7yI=a~ KC׿(WYZ |kԁJ˘UX:TXmyەv^|mN#1R4 g~bSmC*lm5wTӖ ;vNW+qLҹ,l3IZX}W3SS9+)rh WpE  *4ok=՞[̸_bCF~wֽyCֽۘ?VԝpjNkVF?Ie߮84ݬ~j{>kS.ط+"UckXWӁ_ ;Hy8_;7/;[_S}D/ԩA'Eۼ 5FSB\?Vq5W6uQLݹS"8ܑGڿvԌY<1*o)Cr'nޑe=fͽvoίʗTwOʫRWt[nw.] Γ.4ԙ'й{C7faK]z4Pǫu cYO0O o#KXV ^yrm 63LՋ搴}ƐMO,|q⸴o)?_zʄȪGy8uٽa.s 0dXʎi9=#EJHE}~fJS?qy,:;,: +n&AeˆO,2?vEn55(׶V68zT:prdCByg;^ܿ+oƦif؇L=~vp鴓K.h|6mSo;j^`no[%.-x%U-.T:|r#V;ܪWalɺER-[AdV endstream endobj 27 0 obj << /BaseFont /CIDFont+F3 /DescendantFonts [ << /BaseFont /CIDFont+F3 /CIDSystemInfo << /Ordering 20 0 R /Registry 21 0 R /Supplement 0 >> /CIDToGIDMap /Identity /FontDescriptor << /Ascent 952 /CapHeight 631 /Descent -268 /Flags 6 /FontBBox 22 0 R /FontFile2 24 0 R /FontName /CIDFont+F3 /ItalicAngle 0 /StemV 23 0 R /Type /FontDescriptor >> /Subtype /CIDFontType2 /Type /Font /W 25 0 R >> ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 26 0 R /Type /Font >> endobj 28 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 891 /Length 50287 /Subtype /Image /Type /XObject /Width 1176 >> stream JFIF``C   !(!0*21/*.-4;K@48G9-.BYBGNPTUT3?]c\RbKSTQC''Q6.6QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ{" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (<_To_ToB5Oi}G!o71O ]?O ]#T4{O==_3_To_ToB5Oi}G!1O ]?O ]#T4{O==_3_To_ToB5Oi}G!1O ]?O ]#T4{O==_3_To_ToB5Oi}G!1O ]?O ]#T4{O==_3_To_ToB5Oi}G!1O ]?O ]#T4{O==_3_To_ToB5Oi}G!1O ]?O ]#T4{O==_3_To_ToB5Oi}G!1O ]?O ]#T4{O==_3_To_ToB5Oi}G!1O ]?O ]#T4{O==_3_To_ToB5Oi}G!1O ]?O ]#T4{O==_3_To_ToB5Oi}G!1O ]?O ]#T4{O==_3'A[/MGOo!zhwA#W#wh?%wk]<{O==_3_To_ToB5Oi}G!hwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAm{O??罧MևtίJ7J7!S{mhwAF=?h;(C ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ("i' ib55W?9((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((t~q3R]ڠ0,~nگR((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((:3OosL Vg }+FJ֥ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (1n7Dx3L VmXvmַ()QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE##jelfi0,ZޮSº:(@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEɎ!sɧWGҟMssFj0x0'l\G𮢹(!~PE((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((*vJ}S՛n)`(G\El0>Gk8`(`>(EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPYƗ'+BBӱhFj~k;?3]wii0-QE((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((+(ǫV`xon??ʀ9њ4f=i6s]۴}04@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE~J|^0/9fMJ_bkvmc{9tQE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ^2zGk#ŏ\D4FhG3L 3]w6i\nkcf k40:j(((((((((((((((((((((((((((((((((((((((((((((((((((((((((-Q-@KUT˓QEQEQEQEQEQEQEQEQEQEQEQE=W]{04 њ4ffZ}K5wtC*@((((((((((((((((((((((((((((((((((((((((((((((((((((((((4Q@ ri1KSB%QԀ(((((((((((+uݭ]ӫu7ߩܷfњ4f 3]'+t> |k%}P`wQEHQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Hޣ-E="!j!j96ȭkdGzV͛߅OER(((((((((('57OsM׭`׏Js<\њfhPm9(WʰsZ}!>PQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEc^]8}KSu0Z34m%[t!5f6(@QEQEQEQEQEQEQEQEQEQEWnpޑWe^6"o^> 5h0$_Ф٭՝5ꖍ*:J((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ][*kCYYBi3Ji( zKQከEKf/"oC@=QH(((((((((( >Ey WxxrW@I3QLԶ֫f_kzBwB&cnޱ*j ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (35Ɍfr!.Rl^]'@vPSEY1`OIH((((((((((sǯWQו7+H@?4f3L摏i'@ߣZ72_W++yo1ZQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEW]яT]KRB6Q#LG/aH?أ6g/<5l( A~|<4y2'M}'?>g>߱@6y2'ML_I} bϤP͞LG/aH?أ6YGUJHu} }"t% ]J|u@PpjWɐ9ȏ^%V=ш~UXh[v= v?eABo#^K|=tΑ-j2Z# sOk7>d2XPWxk6swl8~z}'V-uP:P( ( ( (<,kiݨZfhHRV iR_&?.?J+S'%7]C(EPEPEPHʮYC) <;:fЮ\}5|*nƾ4/PJc@&&_ɯZmYٷp@nJ A~|<4y2'M}'?>g>߱@6y2'M##'RQ_J}} bVZј[Awp(gow7/䜎mٿ7:x_>^''hz(((AKwp"Kjy"OxOXޟr%׵oe$)80E!J_d"#fsN5- yb>neO|<& A~aH?ؠ<&&_ɯg>߱M{KѝUQLctdeRI5x]Ym#;H X(Mh7um- ޔ8t]F>pWm@ WVUiPTfɗy?kҾA #W} b>leO|<& A~aH?ؠ<&&_ɯg>߱Gl(&_ɣɗy?k?ϤQ?ɗy?heO|O6}} b>k1? 2 [8Q0}K\'ܞU9imñoNk?νz ĺl?Y ?pC@N3[\}J])-OX™ӯ@?Q*uo kCYHnKkleO|<& A~aH?ؠ>+f%ao'[\Eum)2 K^Y[a?01=k(|׷/('ڀ9t-$\R 4́I '5i'8EW/Ǒqր: h1xF37+yj((<⏲[}(_Oƺk^@Q@Q@Q@Q@Q@Q@5$BAEp(uciZu>y5֙zQ43FyNxZȁobLr}shE4}NL!>C,WeN觅Tk^bD$w(('u6OO4~!kν'Wܼ"~tQEQEQEQEQEQEQEW˻euwፅҴ\^I@;kZbdo~n釓!s^ygm}nP0V*lﴀێZPsĶ!ɶP>x)ORVhQ{g|]m;]<EQEQEQEQEQEQEQEQEQEQEQE_Og?_2\׹b|b?Tq^( ($l碂kozZ)$u\Ǽ_5OOZύ(ύ+Wo-R wGRoƀ;Z_~b=! kLse?ր=Bn_4N{H~[D%96E|{V)RPL *uHNp;~8^ŝڬ7p3ƀ ( lFX`Sׇ}=Et "Mg&f+ S32f@:]lUpzwm.qQEQEk0y=5=öZ7-;U lgf˛~S⳥+zY304_P}W&Of{G^R(@QEQEQEQEQEQִ}cLru=.G&\ψΘom2g׸  OO싹3q'ﯧW_5\i]@&?½z$#/[jEW=O  h[Һ(' x_TOE|aMkNmڄ͸~h!jy#.4VF]+\;2waVQEE>=*\$Y,+ѽǽyΛ}ͳSqWҕ -i2€;ok0ܧ}~_.׉$>,^ ޹|@m6:ϬdamOJҹOx`zת\֘=x{"Cզ}f^ƀ>HD` Sξx0bI`f?y}? Z(mdU1^ m' O~?x~/[BeƼs [M+}'M{ hh4S="Ez`_cz5kkTKaᏱlljH/ 6&S;rCkܑU"QEQQO#K3€1]== ,)޿x,}xZI|[0 !^|n,)=~0?o#G޿v:@y}tvZh ( ( 5+++?될 σצWW}V2 ( Wq5Y>*cP&>zOW)~zC*(F5nn Imx" }uTP7VXIo2&"zּ9*󿊖Kk29z鄗N$zPEQEC]\hҾsaB= }'WͲ}f-$=Z%Ur|9Gs@Q@6OO4lh [Bٿ{Oܯտ-w]׸D?((WGK?&?WGK?&=jt~TSGKED <QT|?)iX/Qk]mYul|94im@s+ڨ,C#Ix#*ץx;)wKm'5w!g( 2_ZmoGkãQ@F++e!;ו|8E*hڌF?ttתEPL!xeP{}>2 &+QhÓ>"V"{Wi麔q uw|qVT X((((((((((*s?Ω"s~:FK?PEQE{UTx:V=p*{W됭& ȯ!Zl(#Iq>ӎƬUUeTR>06v/1/b29[/lKτ'Zp2BLf((xʽ¼+ڢ>gWY#l5E-L7{7z>&"z_ֺOzt,̞VQ2CElt {o6=ᆁlMcӽ (qOu+H&W [W&bK_րYS^w7a״ހ>z5Yp/@E2RxRX28 w(_#۳^'UGt+l퐴?}%,I4Oe=s>eޫ j{Pth4-"+(@ʌyԢ(+˾(sak񯈣)PV &{ۼ3}K@^Ц`+uk߬!EP+>Oꮠ7L𮎀 ( ( ( 4+++?될 σצWW}V2 ( Wq5Y>*cP&>zO?W)~zC*(($GՈ o>O:*c`n0G]72 eog@*>eoQEQEQEV<;o-)ua>Jܢ>i}oٹr{ƹ:( ^Kv?c|5՛N4P3~dvc@EPEP:B2׊ä\><;0%So*߇}ys=E%<\G=5tQEQEQEQEQEQEQEQEQEQE_Og?_2\׹b|b?Tq^( (#y?ʾj|_JǼ_5OO<)"\j\ Am-'7>dHǑίZV=߯1&|.kIyy Hdyc+ϵ+&a$npιwu8U Zjxv蟟aT_?"srI#cINEmV9cDz]f{M]'CPwn洨(((()T^]}XPJkbQh3NG6OoצW=G"!:2O.#̾Q@Q@Q@Cyu 3HRMM^Q?iOFs;Ҁ9?kxX$adۗ)O@4vZGo5 ; ((((_# 4(sCUL(|U"ơ\MkVOX?뉠bk[R>#/{j5>QՀkO[7?_Y_{;d/5Qp+gom%{Oygog@*>goQEQEQEQEM1iqJ(}-nn`wy75K5]]'=S"hَvяZKu+Iv!h(b"AE >EQE2yR$CE,P]շkF'dǯaX_ /G+ba°A^ϘA+~?TnyGJ袊((((#QQFI='جSXڋ;m:|ޢhsZIP>:#؋8ؗ֟_ j_iǛ[s8vP"ȹNQEQE_mBi"Rn&&8) enFǩ&4}kJAW+t5\iW VH{tt 8WkO(+[4?]ܙ|tWy@Q@Q@Q@Q@xoGriPzk+>^@Q@Ewm嬖ӮXz?Ώ@|9>#󮞊?Ξ9k2-|/Z64t>sZƑ.Q}bER3R@Mr L.+\̧"ץuuVQ`3^\G qy>;Uܖ%vn^[&rs6h_ĉ.PFxi#vPXaGy[9$ֿ%x[4WxkšwaBy|8=(!|(WYEQEQESdTCN>oտ-w][׹xD?[Bտ{Oܠ((> {?q t7k {?q t7h((((+_j+Oͻĺt wvW%v>wc]mQE DZCm΢*#6u|n3sq?!AzhQ@ '#Sǎ=G:Tg̏'*}#Z,zk- OWxSTm6`7O>/Rkԉ S#ʰQ\oS^6ؒcڍjrc׸xK𵲲a?jiU׭,’ Gƽ? uQ@ro <(ؚkW_^);VZf+Qw4Q\WV%V$j~&_\j.V=J((((+~(xbٿd辕x^4NA'{'L'$G5hlm˜u{Kllq.s24-$O:; (((('G? Tݰv @6if &  |n⼫?}N:#wW>%mVNW,PQHԂ2K@Q@Q@Q@xoGriPzk+>^@Q@UmF4>{) u 4W OI[Q OI[P}EpKE?~ (5e ×g?vUECkwoyd!M@J.=SV暧¨sI蒎\6[CsfJ)C4SFF 2 |xQ.Ds Zx݂ʼK<  6ds{W7ZTᗳtU Vִȯ(OpjQEQE??өo ]Vu^?Or7Vuo^?(((Ͼ/\g,/\g,J( ( ( (3puI5Y|Vl|߭{oqS| gD{ǀ`<ao뢪MҭmG(n ( O6u\w+,ᥑ@4zzmҨE}% ypu@<)kXCP.kj((((((((((_3/.?1W>1\ds{ Q@Q@ D:^I'-Y$e-f=p*w_5o5oG|4t/REw(r**{  @տ45!U|?+m|54,"oZj\A!c]gՠ^ƞ"'v*)8qwsrI,ר[9bSptO XHtˏ~MxjZgg,So|.{ƽR8v(ni-c|ekJ((((<?I1E7ONzg8B}ixx"kOހ3~8{V!޽Bq54^2A w}ipV\FAI0#ހ%(wJ>IkMJM?RJNTNƑmVN5\ j~{nOt+)jm Fv}OZ׈Fn"MMz"{맾\&;Z_sh}Zz<fxV21$ ((뚂iz=$ut,1'ԓ^sV@:W]$'b{txn܀$eԞkrEQEQEQEɥ^iX* ,?>(ڿvrrye?r4Xi+ z5n׻6>zkD^"G5}u6v(*@((p2h[l-¸xkz{]NLGt{-aG? g͝A۹1ut%YNA}7Er*Hfld(5$\g5\h7;:֖n6ep>mǻ?t+k+`bBWxg\_⼈DPQ@Q@Q@xoGriPzk+>^@Q@dEC֬ȱ@<'E/U!G\_}-kʀ<*OxNsYw6` ] d8l*цE|kr\L;"6[]mG+;m"GA #הX^R } @KQ\߂|J"҃H@ *I@Q@##eaq^'zڭoJڡipk:T3+"{%mV[YlAAu; Fk;VX[޽Lj?Am;leh((MS |߫Zrڷ:ȟ@Q@Q@}@*>eog@*>eoQEQEQEQEPkmLT*c >-aboy&n¹imP5+Wgյ9$z½sគtN` Ҋ((i,ls!^+zWxSEܪۣF YcŴe>+kϾFq~놝dh((((((((((ʾ1\ds\s?Ω"4QEQEGq|???ξy?ʾj|@N|1!Z.>Ɵ\Ot#ˊW|w3]c04ޣkzǒޠ{zkzn<3NkkzMB*h@QEQEQEQEAz]Y&dpTV5|E͊k'J`xu6j(}WPWE}:F̶d|QҼX5hO}W'`$H|He4W?}$jey}5P@ 2 |A0EikN:,vqxI:GnaU")C5|"˭M-3z*DPF-PHK4V̊eyQs@?T$ lҽ 6m*mJE+i/fI\(/KN-HP( tQEQEQEQQ8HRǰ7|+{w-,>OrI_2\E|Sij?Fq]HUlz-tQ@Q@q?yp0H]6[T A{_?ƯM}rŤA fcqZ[yel^Lm,닎ڭφ~ͲջL]N>@zHX920 Pgеho`?t׳/q_@ijz|7)WpφyjMGڵ~x6.kp~BOz*(EQExĿ fjZ&-n Gci9 *{׶vjz|W()W޾>Me8(ݙ{"9XH20ʑS>χO Ǩz=QEQEiWWV!@UL3;PzeQE5kZ|U"ơ\M|_KZǤ?/SbkkOT-Q@y (_8TռWv̻J4unHYWM}qMGPGpki2hŌl^ ߶oL۔Z;( (<⾁C|M㱮#h~ !cQɯxkItrR+kNIծ,f4NF}Gc@F$jr2:φz,|Z|+g@Q@6OO4lh[Bտ{Oܯ տ-w][׹xD?((WGK/&?WGK?&=Ҋ()~4V^=Lwݒk>) VG% e? 5jDkق8\}_G}HLv xfFwCxWQ]2%瑆@ o_-dXs>*T*v-`5(Gj ( (O0eۛؕ5qG%Dt&";wͭ #Fdͽoʀ=SBKml`F?^hQEQEQEQEQEQEQEQEQEQEQEyW/uOOg?_2\׹b=((?_򯚧_'ҷ/WS/ ȱ!O~GS<'"Ɵ\K*hn.qj{1úPKwU^ިO?S{z`j5߽hxkQ|Ahs8C*ӭ S0j,tTV mb@J((((o7n-b^^P=y)zSj=OKQּ"K+m^'* i:WN~ͩǩFac@-dk4ʐ}:~W|.~)ݍv ׳(,iԣLnvd`-iyo\knvTQU"*@ EP^CcVN|Bƽ^,f!cjRQԮ.izP_In.b\xd_x &zWQ@Q@Q@Q@yoyg'ʧ7 SdzL[[= :VԡRJ:oPۮ&/ T`Q@>% yl>3u;6[[)4M_JWMۭin! N50Yܾom=]{kTѵHo؇}jvƙ .H(Q@r>IlxwZm&s"z^!ѣRʞ^};bسLz}kg&Oe)M)4L7qO(_# 5(sCUL(|U"ơ\MkVOX?뉠bkkOUJ_}-iʀ%((+hjcaLoɎ5{'`mU=k+xewG"akRh͌>zPM)4L7[Gm:gֿw'v(΅SO͏Nƽ*j6Qj:|s 24> ֛C">\½dEu9Vw~˥S SK+`}H2.n߇5}a?}>EPEPEPEPEPEPEPEPEPEPEP|b?Tq^?tSE#%{(h(=p*}+q|???΀>rsTO3OdU? ȱ!Z.}Aֹ[t?Hn@f'&<u&c'J梔3zݓtG}1u[2*B*;7VͳtI%HY"p~k-L7cw=?E)EOOYRh((~*c6ėMG^cj gϢW<t6Oï]W-# sHF}",h S ( ( +In$Qf&'⏴g&afSҀ9?xX$gr>Q GS||Co-ֽm jTv%Q@R3*)f ($ khZLӑ"y R5 o.OWCc;rV1ݪÏ k_oO;c۰ῆ?t ڀ008PEPHJ=hеS<~pK''NtmKW/r@7c^iޓ5nƾ4'RJchAhᯉ,?n7v']QEA}ge-U׀xC@%y{Wi ln}G@a?Nm؟v6hG:x_#R+<C$1d>EP^/[F\{Exorw^Z?צPEPY>*cP&'_,jI~zC*?/־H_@EQEW|Wоc fH>YqZ:;9mPʥH4>C׭>^v=T&%G󦻦K2؅'kվ)==ӵvQEQEyŝ rCœǧc\wuxS=^SRӧ^{{f=Gc@HPAdޖ [7#{c]u?շө5o ]fu>?Or;Vvo^?((((((((@'޹sZ.܉g.Mt$ 'wm{=-{A&xTַC %<;鷚Y@JǰPiegy[y$ש f X?}Gnv}((((((((((((ʾ1\ds]s?θ-7Sҧi.^F] ?3'.?1WgO \~c(ğPǼ_5OO2u{aXNIоcOB#,iVy牢)둼]ߍx}?pwZ"~=\oL6i[oXGkkþ]},.+k9nER((m;v]\ώ#H-$9B1[VcTjÌqVjٶ=+*1ַ*@">71f;W+54^3N }"YheJk>Úb҈ zא8?Mcg^@Q@Q@U}B :[˗ JX@;*C,L>8+jc@3`w,M^&>{1;IkW6W6Isi)d:x~/honO[ ?3'.?1WgO \~c(ğPן|N?ح?$ސ~~5g_ \~c+縑Cf@t]*Y!\:>oi.5}Os\ÿ Ke\E+(((>mS.|j 2(2Kab5&yOo Ru W7뚦},h+?3'.?1gO \~c(ğQ ?1q⇅g8@\u|=r=WM^unOM$R 20GXŵWv@C)K^WTؗ|s1^@xoriPzk+3ZO?ޯgO \~c(ğQ ?1qȳ^- ?1q47ޕՍ,͈w?|FB$LιC!O7ps@~ᶿrHO(3V񎻫egd/kq<ҷEQkմm}B*#NZEds{IW4=|˟N4]?FX۬Cco (((((((((((((( ^x5 CvP Ct?}WoEqt?}Q CvP C'+U@8m!ϗ\Պ([p:;k_ۓ<ʼz|皨bcVji,fR~у"eRU9fr.;)WDETQEQEW+5=-]Us:ڛڴ 8Q[U)#&(H^c+exd!O.QX¼Z爮.;#tws lb{myM$aqȸ3wD(M*C #D=|Emr;G^'W̐bk^7>[ $v_aqnw=BU64-Sc@U((++hRȑU7}kV?Xߟc~j(c~V:8V:Xߟڊ?Xߟ6KخI#FۂuPҊ((((( mi]q\+U@O+T±?]±?K CvP C'+U@\_ thI#ut! eTb2{Ө{zf˶IoxtP Ct?}WoEqt?}Q CvP C/+UQ@Hc4ƨ:(S(((^xwAPIC訠#_V:8V:c~z(c~V:8V:WY.]vEE7)P?m{&wtXt?}WoEqt?}Q CvP Ct?}WoEq?t?}Q CvP>#{H >:)aErK͛!79]13Z |5@AM}W5z;8@X((((((((((((((((((((( ڌN^ tdu=zׁxߠIHQ&FBƜƣ&4+~] ۦrВU?u+ByW1S-z((( :A-[oՏjaL ;z}ܖ03dP<8?&~ڝ>uGnE =\|Dڏƿ\J}vך .5j~!mH@OC\ĂѾ)oohw: B2>8UI[LvQ#^h &<s@4(tllPUf(+0 ( (36*wvk3oH}UxF{Ӿȥm3^E?lԀh((jņj <6}oKwu H$׃xV{¿,Quƭ>#ع!᩵ Ys# mj>EGS]̽yb+[t kTvw>30*Mך<7u)rrJ\Eyo_fП7˜QREPEPEPEPEPEPEPEݽJD%xIЉM(Xp~+=M0Ѧ&zW]س7$j@:d gbO{st'V''sUisU`'WJT.?PK4#d>q]WMsH9U$mjEPRC!xQ~F>Ү~ץZ>dJߘu97rѓV@QEI(ȵҦ8$m_y>},n+V?J۵SǼ_#5Z>"d`t=V_6|Ug#Zn?@z,~߯yW#Z.?Qqi@~&pt7GM1im-yO_n]\c @ڧ ZJA3\eޣ?wq$s]ΟP}w:4_hI>0< ? .Wu5R9q+ml1H0jIFqsך`:汮zKuuךƺywsμ6s<{up (Q@Q@Q@Q@PH'@^ag-Ä5ׄxė"W%mБ}oj]2OH[a-+H(((ç^0Xm&r}Z*ޙe6=YN*QEQEQE:7xWF*r8"[=[/M ?ݎsy=($ &}h*Gu E9ƽM$Et` j)QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE2I5P.x^MU~U-M9*jE VZMH( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (<폛ZުXy-}+ AyQE@QEZ.ZRC@tG*`k_}vRg-L}Lh2K 3A ^'IucS58I1V܆wx\M:{B{\}ހ:f`Nz^M "5io ~ ER( m 'QRdc^U%{׶Z2Gy RY%@c=04h&^:fgy,r}#W^5{߁/? ZJ[tTH(((((WΙei&.9Q?뺴.5aʿ=|jꚌ׷-I['ک *IIE`QEQEh"T}Qj߄1s=@"s,{׸iz]fPqR}MKv~PY'O\ ]bFT@:1N+SƲ#uV/|6so1~}=+hp>oԴ*ۼ2:*}h塷8yO5~*mi@3ٓʣҩ;QE@QEQEx+讶WX.@}-iu\[J*jjo ÷A2ٱ'E֬u5:B*QE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( Zsy-Ys9v O?{ 4M@"R-D"R-D"jEZQHQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEea`A[kfن6Jl_F׋R6'@[>E`QE?u,w;t"Dμ_ٞ)#{@9+#ZҼ;ytz*SfS:F8@1f,$Fk@'OlѺ-ymUFOv~qvzC5|/6CjQE (:27F5ψmM{lFJ~⾇bmJ an#撊J(MCa4l=Oz*^tYϜ+7FFGJ+0 ( ( ( 3Mx#D"l]\w4|Huccnoqt${J(EPZ5V;8 GYoF ;xr/+? Zh|vVq'SWh((,QE*+ aiP[otd-Y W{WA _MƾQWҐGuբ >0h+[yhdhSpEz#$;-hz="lr$tauHQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@zǻ3_ #uZ`JZMH*j%R)VS@)^jtLrhN(((((((((((((((((((+B/n1d뻪:ݒj:5ݣ##>q4O ZQES0e8 m/uE=iw -k tRmXt[kĈfWj/+BZJQ@ &8P@yhx#)[ӊ@{VfU}أ *V`QEQE|XӾѡEz|e=kZ%Xi>r"da)Q@9XSA@CcP]Ow`р~Z]ir7(|ϧB+k6ER((JBHQ /믯*vD?ƻ^"-پiHkj( ( ( j8"?fi|.ח|1#4bR* ڠvER((((m|AMXo|voc^/ZeBJJJF>"`+쭤%,P}X[G_LETQEQEQ֊(|y7wqJ|k.i~eM=++EQL( I~M{+`NA_2qykv4=*@(*Ai_\$kz®׏Nl?9?ZWյ ]J{ۆ%laT袴((ȬA,:j>7)p |;N3ol~\ױTIQE QEQEQEQEQEQEg:5dַS[Pk̿oՠH#v5i)QEQEb{YD챕kk$U*R~)bWQq-QVEPEPEPEidXeԚh[mBDV?힟{bGF]@Vf(@QEQEQEgxN]WD $d.{󼱴Sy>XA;MCҡѴ,ahQEfEPEPEPEPEPEPEPEP\GmoӶu+_ vPͺw]5z0UksAS!>W2 y hMՠzT{䨥( m( ԋQ)H D"jEԋH "QHTjiHԋ@ZMJ HSR) TԊj%UF(U5"JjE VSQ-H%"!I,hX ۲[΂)&`c[F0 EiC p&()Hj(QN((((((((((((((((((((((((+Ś* >:VeI]'tеA4+(`QE]5;#Q;k|/[T|}=Ex Ogyqcr6Re4H]\gl,q 3x%` (()@,@$ hk6ҷGmo(XP_?4q<鋻TvVmQE((((((((()  {Z(7?VqeKoÚ)[f EO_Bs F)I#n"`|Ezm*_r+ε j3wh;H*VǥSR)SH VSQ)R)e5"*jE42DS@KR)jEiQ)R)Ԋi*ԂSRƬ4"M_/pYH{oYxr 4(`!j޳\5ԙ?ZDHj(Q:o 5ARQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEEum ݴxYOq^ MQֲ?{:ΑgX=aѺ}E44V|3yaUQE(|/ ^;HWW6f֮SO{N+Kt J@QH(((((ƚk>{ߍx4R4r)WSc_LוLVc۟ߪUE`QEQEQEQEQE|5r9Pz;t{sTNX囲rkt2Ha\G{ܚ8QEQEQEQEQEQEQEQEQEQEQEQEQE#Ic1ȊaiP#<56i"F.pڿmfsmNg5Y$LˊM}!uciz.}AwP}ܲ5vTԊk/̄vYk!#6Zjk?N*D8xOzj!CTR)Ԋ~(ߕY_v7ijEphzlǩ^+Z2_v0Ԁo-Z־ҭcJlʕaY,q+bz B=dh-l1$cF*Z.3gS u3I?:޵P WV(EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPmCO&Wx֝Ksmk(ё:a`m} xcFՎ)+>}ec}ZKk(Yʾ׸[vӄ?2z ѰӬu!F0Tp (Q@Q@Q@Q@Q@Q@2hT2Sׄ%Ю XHrg_L\uEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEAޖ|:H+疈zWjZ]9qk}0o-?GL(d9k9%s]z_WO}tQ] 5c&$Sֽ›uekE;> stream xXKo1 ϯyJhn%!q@ǁ?`nl :@q/{ ẍ)$'gǧaw1=CG8S,:'ty+4rT7R)k%^Jt>JdQQi ^1 Vd>(VxwS6,&c؜/jh۟~c7!PT{^mKxyů^N?ͯm[_ޮ%Wjx-Z>i1!:V t6eΧV|0juOߔ&lK)1|TnKۖǖiݑVZ[e2 _|lR۰o]ޮUǏy\YrIͰ{kmS7xylW$l?`rǝA\ "RK3PY;S'hF5> /XObject << /Image1 28 0 R >> >> endobj 3 0 obj << /Contents [ 29 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 30 0 R /Rotate 0 /Type /Page >> endobj 32 0 obj << /Filter /FlateDecode /Length 2725 >> stream x]n8S DJv6o=-0aK*M٤薻|Z%DO1y+QLB|*FN˜㿾w(/z<ogyf:czpr9K;>=wN=bNu r,IX'24wps~lC!}ޥFuYkOF%8<=P?Yx!;ΜY)%u,%é̗0rxѪW >iო_p5紑O.ܤک1]Ig:Y1.zdjp?/O/8}s޾vflԶ6{\ϻ}x'xDoϢn [ "C vi_dh^%Âmb"r"⍮ ?5'x?vySjO|/& ׫Z&Q-*\hqz[|5;~O<pYA (4eMݑSYe t DOA V='xߝ]}[MPwa ޤ\]IǧjG p /pO<ʍ>]Mp>F= CRq*+P5ܸՆz5Oĝ1Jˇn͠^;ff7놞6k5a^=Ia}.{4xPߖ _ݡj~9 NC5m!j&1ɇrO<OƗ\oNw B Xվ}iF%tbvШچZ36!s_3\o2lykbs]6 O{Kn;%1y8,YzɵP p]Nw\pc 7Mҟ+QhA{ ڳ d ; KDlmCTфlfIGفxXXDOvCۢV(oK,z^t\1m59}>ԋƎ{zklO7ߴ} B󜻇utƫ""hS 顈L}1Dڬ= 4QJw R:RPPFVY(mH]8wI_6@o|Z`/ZI>U=ޖ^bjimڔliBi:J\mBi M<!U.Kpq`r&Pݧ栚j9iPsF"qjj@UYwvFVsI}0~Ԣ\1E8evlἔݵNB[8N&?Ea_ً)' }j:zИ:jk˘LiYlP PAJO_ q uro 7;sWiӲWoȝZOe0r}\_e%p"Y̷Dv 35Eֳ廈"ޯ iW#UVyq gcĵ/wXD O;P^GdJDÁ-*>{tOzԌT7Z rKB['x≿_J1r˟CQ%MA\-L߅&_Y`ShK*WK v-Y.5٢ڎ&lPӻuj5+N<7[^eo|%<ޞk$m'K<\yk`•A6&}|"WO< re.pen\)3x'^+K7J9SR裟--mŒ`_Ga]۲MiÔ,WeʵKS))SO<:|u_Ѓ^k5aެaQU7Na Y 9JtC'[ =>y?K endstream endobj 33 0 obj << /Font << /F1 27 0 R >> >> endobj 31 0 obj << /Contents [ 32 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 33 0 R /Rotate 0 /Type /Page >> endobj 35 0 obj << /Filter /FlateDecode /Length 720 >> stream xZM W0ÇTYjRJU=UjO{CJѦ]"c03{ K>=a(JbՕ~v~y>̲(A_~tÄJ焋Fn$9+WzAg0E%~MKp*VT V`Ά'{1XrF.昩zRگz8V/0oe9z+8X5S_۟d{Wnoos3$J rJpKt]:_1H%B)7-6ƆI1V8&HCrTRޝO:* *[ʼ`uVз jz3 \PnR5ߖM|rFV|iDke_T;ri$Q}Z6}.4*ؿxz<͜ju-Y볂qzXKnoϺ>3O,|b|N؞;Cx`omվՆYމjod4K#|HH/QcTw#5#&rz&ݚq+pAU7J=(2xScqAI|pڃw߷̯5&i1?֫;BŸHcڣcp6tu6l?4|7|7~ҏ*0ϿK ,I>C>z endstream endobj 36 0 obj << /Font << /F1 27 0 R >> >> endobj 34 0 obj << /Contents [ 35 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 36 0 R /Rotate 0 /Type /Page >> endobj 38 0 obj (Identity) endobj 39 0 obj (Adobe) endobj 42 0 obj << /Filter /FlateDecode /Length 86070 /Length1 372428 /Type /Stream >> stream x} `Tw潷v7{e7{d ɒ@d9@䆘 ]UDjjZ&GEmꍊU[QjbyIP@ȟ>y3͛w}"ǃt gqh'@zIl?}POqfNL[.i,)Zӧe`T/&M+-wt1e9o(,?ۃFOh|ݢLl9^z8Y<̕y*hN?e٩K~Y d,O>u9՘ВNee -9[ւNu ϙg 0^_KV˭9QqzdS@czsĊFozK朽d_`zhsۥ9|OdnYtTk˿@>R7Zߟ֚|:`"|Ɏz`ϩ2ঞg@_;<9F_+CWJGY 2O8\ S2r Hnʱ S]'QF v(ImO5@L0*e^HN!#$J!EؙSV'3׵] -l_q>8~Bj`p+߃u_qA&7pb[o|w`x7[X.MǠN;tXlXZkn_ܕ 8y0Pd`;qO! B *TYG $SW*T8 ڢC'*7UPB *TPB *TPB *T:S/P&PB  m|+`w&0@m|~9%鏰HzJ_]p7 4M5g\ޣt2XzϞwc˕q):]z{@7+O`p4fKƋ2oµ]^ :?l51p<rɯЂ *T8RaѰ3h|Kd2`v,23A)@=]7h&fx`kٿld)K@Q 'YUQB *<:1Bf܅ʢdj RvWBVBc@z/x}uYXaPdtq+|Kt$A0 5 LH`Fj 2Xڐ~v!ui8:~ .B'Rү n g>F4|HO} ANC4 ABAAi>""/@i?CZQEƠ 9~H9-"C:D(Pv N@P *RjN C9HA%PSA C F0u0M t,@:F"i=BzNNH'0i0tO0i'D0)14d3a g40IΆB 4 ' =?`.4!3·R.f ԇZ0|a%p2Ɵs.tK}a>3T+8] RU陰YH߇4H|XG <.V V"1JÙH/t5z.s^"Kmp>+Gs%w'pҫBWEHA#.FS$6a5ҵ2Lp#\&hK+ W"[*HokށMEKH ?Kw7:9һ9n@z/܈>9-܌inEڎ5ېۑn;SFeoWH;H7]HpC0ܝz~ =H}H#}~1x 2< K'NOԋl@4lDg؄x6D,lF~z^@Wx~%x˰5xӿH_ǐ#}7H߄?!} H=osJ=H߅?#} aҿóH?S‡~Akj^@1;E˩gSx.NCݜH>7BҧKxWo8丹$@2erC>dߑKLo2=.Lw{wLw{w#ӷsGL0Utu;TO2PfK>CEn{׌ hl";kdLcw|C5sGSD#hAQ Y~>t FPB Gٞ±c<~tkS!Ng։`7i` e sSP¿F % 2[f ,*D >+Y G] ڞRzǠmZ7ZQ V)=pgԛ5ECj *TPqAYt pl@)|-2i)QzE/^JVy:-:{fXͨPq0g{+V8f(MtmA!یfT`RЁA *T8NJmX9TwH7X m)mFinی*ր6#ImR=0F0Ll?m!$JST8JCjUߟB *D(Y6Т[FQ&vY4  :Ћޠ3ܹ+f ,)D >+Y 9dbƌmR6S#0 ((4zWm3z֪ 6sX@PB GX f)]ϐdr4`#陞5(^ā`_g8w ^fT8xr͌cIڧmmF蘁f['Wj?:P[.PB GTی@62Yf ZQF:m3=?)gJV0یbEf4fM`֠le[,6 ffѨ1H+y¾矾&Pq+# *TPBőe{fp)|e`:MZbqY`5b7OފFIadĿt\̽WͨPqpɌیM:jj  j14LF0e=gBS)l}*Z *gPB GEj9R_NUZeلfbʊ$ ̨H Vd-s.{Lb>+Y ;coGVuRsOTl8!;l2L[bbbXt&&T6gّAQPA *TPȃ+5G ڔ+f0c7uA*QV U6˸e=w Quz~Rt11U&|{+wku:Z{vȲCA͙eg֖IZ?}B仟J.zT8*`PB *<(jj9R֦,=C`Vd7+.@jQ.gVlȽ@{f}d*+rm3b᳔fNfi2o0++Mil}fG8*Ĝ!B *TyP+5G ڔgt庌df15(l;{liwܲΕ?ӛ%A!p_~LUűPLG}K`ipۙn= x^cӰ?ml6 9>̎T8* T*8PB G5Xy}j9R3* ݞ| vd7;SVLdҡrڝ݅?U'eL3SUql"|z@r9lŦԦe6'dg:'=Wj?:HA *T8򠨡ʫEȖFe۲z|>o'Y,uܞ,+޿7K3]g%PqXQ4=[bc.2gC"am[hp?abս=g0_| M*TPȃrXy#im3eLY@ ;g>ۙߺbb_B,]ᾀJVⰢdV mf3@6dwg B803C^0m4?mFXވem3} 8jqU8TД*TPȃr%Xy#\;PzA6Fvf-? ~cހCO-}zob>+YÊyQfzTjd&7{LÐB Pbd~ 9}f1>G-`󡃦lPB G5Xy}#\1KZP^^|LYq 5 A Ӄ!(zwJ b_Bl]ᾀJVbBfzTQoZ,i3̓0X}PWĶMi@ `YszaOk pcj}:*Z+T*h/*TPȃr%Xy}#\a6^~(`-d9paÑ@½s E^/I>}V CVYNp`>2B=o(1K)AI?m !96 6m36DT8W>8TY *TPBőJ H=R * F\PBv ;?\`!/tЍTPB#8=V^_qؘ~pe&l֫04``2/T#3Q(υB9 c1*BzץWW/곒U8_[Ng/^/tmAQw2\VQa0lbI.*bEor7_}?> /0_  B *Ty(Xy}#\P :C݊uEK %S ;`yި:^x}oBaŴ_+_ae_.a%Pҝj*ˡƍFTتLrI ,p7bd޼Eb08jqAP0.PB *<(j G 2SnNU2<#JJC AByIgaze%Ee }/ž=?)gJVbVg:`:;mCywa0rXFAQc9cgeoWKX![˶k j@Qx>u(8T0*TPȃfG 2SnNU9-Gv+g-D KӫkkK[^ya7`W/쳒U8d=3=*OTu0 Ǝce`⨉cG1LrU BF7*Z8?kT*= *TPBőJ̱ctQ"bWGab[{h! jVY=1L>rİas^VH+Y*Tni6I naFUB  #a 0d` *ICѡeUrIx)t)a,2 K`!åۏԯtK=KI~?ɸzkÔ3="b};kZ&v?B_!7W qMั?ɔӦh8ă/|k}#)aK~W\qeKO_E zOh1}jTQ^6p@iIqXQah~^$7 rlrf96j1ND_JD[b42vl1ێ9="Z!wDg 39OVθ3ޕȡ)FGBdF dt)x6c8BuC K9sA[]h,h5P F 1pGp]7ΌJdGF%Ѭ !nμ)u}pSq57rr"#aQ -?Lh!;"kە23̛͋31!ibǰswx7p5=S}B[gam %n35hS1-mcWb#O Y 3agHiYJ### `d%`9vȮ MoHӜ,hzzo<;lSbML极]i<ijPԮ%Fq֤1T*h[MJYЏjixBʗ#r@d{Ih 2>b5LτXv)q8TN,Cadl9MCJaWtdHNiTCp⥱ma)[3),5ҵ{K9y΄..G݂! i)3Cum-鶭ז^Օ%MOȔ2FSBǿ3NǐИ2VMpx?wLb{q{t5Cb{ok{gJCVS8.!phTfgjksMDlˀD7K;AA6&63zr$$G6GmZ2әr/1&ldHqKikB>&k'$>4>RlՁ1[0P뇓Zk2N>4Kي9Y\kW֜eQO9g0cfs9F20I9`b|~BD!DC#9vLHbv0;DC0FF!֘7VШPDDə>?D w|dH < eG'ȉ?~`(YZ9h۬ȏD;pi7&7>95)l,C1N'ƸO6>R7s0 ph^a1>3D ofHzKmS\е99\(bυpb/)֕e;6Cw>vKΝUfFLjPJ X+8=W((,Nur)Ԃ2LSpd|&GoN4XZg̙a5]i}VGkkk a`f,>D1b9,9uXiH |ޖp8Nfdn[76İ%lmPufUbtnC ʵ]=LJ[Vd糌?GKb_S2x|ɢ ,% ɓ3OGqMOOخL)aLSf@~o'M) g%SOaO_=(g? 9bBԠ_ԡ v G= a;:*vr!ch0)D۝eBEp)!K=t"/#]+=ytX"e!tK݆n;KrG(((O*St)tӍGu$t]6t,Ew!G)qq]9q/Z\7(=N6D6B.ߞ_|l“taŗ!%qA]pB4阸`_-A"PLmHV6@SSC~Bw*)tzG ]C߁ vHk݆tϡn{V&E7mA):-}L7xo`77u<בZkzU{l3JӁ`~:vWY'kǗQQi䨇\BnG@d?OG`'}o}(}"$1Ëx!n2ti02^Vtנ]rR]>t/tqtxN\Gtdp>K76$aї/ta}=a`E7 ltWGhnǼ y쀏ܩ`<: 0Ht0 !-t[ƣkoMFW]!F\!F^!F#y0Htl 14CH:+'FB#,lV: Dz"/:n yN%wzi!'iiևH6E+ok:!OI %5He;ƕs{GA(}4-FLxsR|+BJfoj!eKq<;>Nz 1,1,lt[}.NsWsjEZltWSttiЉ1/L/~b ITV*m:['1oA?BOW3MIWw|}(8I~9TC_+ _~z/eB,lM/;;)??|%)Ks˃Ov0h'AoKg 4z&y?<+ '585:38?9_en O (}6`bJ+[0fCpЌdAvQ;I;X[ kOdEgt:F'tY3^fidiDFE)DQ PO맍$sPiNb@O$8BX}655QOh'NUM2Xj3lBlc~?ijZO}z!-igpi'gdSS) '~L1gdW_kj, 'MeMMCϧY僐.Iɗcy| ׾"nt{^ g;3'?q<ӮV'1g1Kϳl,~ͳ4tg)Mg+HWgcbX4w3cDkI\q[6[і.`>I"G'FFڇY,yhdt;̪>+>tкȜM돛\Qױ.:V)l2+IdDZcUcUc? 8OnlȦQ=5m[p?%/yxhso .]ց1֔0EF&XR, K0[e:sаo YN1 VOVb ؊}9WOMOԢۮbl ;ĐLXיڪD`)]Y\ (6 ZCI<@VŠ&!NQ"LOŠ+WY)#]X m`q+WCX=q&kXWja 0/F2˄Ҹ=heP  Z`ԦH[.[ x( ݇O.L}ҙOR3d!dl VUf8~kp1TI3MmR;`=.-"Gp!^Vrd&R 9> fP 鰌SWK ~ 'S{0RHK 0 mr~#(8nE:5 gaDVCFa)L%Rc.?4 A8f& q6z#t&u5bv~^|6dsQ[LV*jLY 'y!K%T&ťsS/B X'BOH`\ZlRJ&ZH[3@G?vurK q5__krS(n?3i ?&/(:+L[vI~T)DO֐kɍdy|HG4@X.Ni bR ͇ɿ$*K] S._mU !&3yI֑<]Nl!_Sp-VTz.ZFoy1Rp j&a)jp 6 sb ۹LZ+&viLo~h[IH^\HnHNCP"q ĄmMpr,|D.jLЁ j<bL3&Mh nF\;qaf6:׺+'{P.L'e ! DJ=N8/yp GEtH$Oхbu @ŻI,4 7i>*xN4[}Xo .i*Y#Gp),O]H_ɩ t;_(_ReʴM8!Lr3PB܄PNA qRYؠN;TBPNNԩpz:(Fy&u>އaY<v*19iLW4v'~åM|AmKP'w'xV(ON12<߷aJ7 1bïưx|:5R\p5B[kʟp%(onqs6QJ쒽FndFIo;@w`Њ;xuiF&ӣ)OLfODyẅ́=5Pa$ma[>\7!a7q psOc8CRgxn¨1੡%I'zN(hu5UOJ; [Dk(mkZO +cĚkxdABlj /n"UT?< rP5eۊmwJ~PB[@+*3vN;Iz͢C?nf9 Y p+Y5|N\-6i*I**Wksʄ}f>|9"1KNy|A,>d0ڻFg~2dv=6nNtvme:_lZy`>[ :j Z>{ly>SsGlQ.L\ ;܋9m3>I~Աzߓ|\zo4 ޫ$Q"PJ  T TiLBGA붻yy'12*d=poVC'OmmXr,`M&PV=Xضm 9'bm``m5!vsȏys3N^1NRN$_e![H} ڤ9$jN lQ nLg1&. A)v6$9k[n,IJ;r|Vb@6eq]Ev(jˌYHzC;{G;f9gyNӞfX<;9xuZ)ke׭]U!v L= N}ǘkZ3m x8R1X/ta2l_LN`J@g6c`}m0d’B.d.W0SoDʂy0y94'D|rM+qvАBgGgEW@555YBf".qkc16:e2ˎS-:dWy`j53N{3;V\/s?/8|3}d}'S%ArKO^'-g7L/^([pvi5ȲX͝Wq;tr;J?5$j zΝs/ޠSFlW6^Y?غY/םOAZ4&#a?'@ 6ypd׾\I}l_C'1ՁEy<6 5M )QkjUn~rʕ&?+d`&V)?1-.0[!¯z}Q~ɩ3>W) :+-lك׬l{7iË"|˙C>%dM5llHnXCo/B&ظn'ІŠ#9 _10\`B#$\V9 heé^KSW_}BL j|ߚ|,9):j__ҴyIw+{sc hHOq*7𵂇cͭș^ i.+Oot_{/ټW 0`itNԉ'C5FrA8]Y.Kw-H<: 0pac6״ Ӎ[3bo+Y}x+ޙ4\1kNk=nO?řsM2y访(։wL`v=#HNAd]fӲFlNƇ !35dE}f366z>Əi艊^ !G#ms~F9b)'۰ ${#g]P>'><B_n!GjhBQ9Q'ܾ:ҭȣҖ`;W>uq-ϧjVU̗4zJ%goV~/`q}7~ iU¨ yp;ow1f56$;&;ZDIdo4Ny]k9aKAo#*r9ۊW҃aeiyes-h~mvϺG@oӋbUKPb=$c!%KVBuztzj0jx&jo2f/2͆LٯݺU~6\Sb @ƞrvp*p*r*qc2+BhAڝZ*E jY XG蘈 PT"^aD2ଊi`'J^C ӆ9=h2 |R[SLsއ.u!P.t♦KMObSƙYB1((i>۲Ƭ3RIWmlDڸnypQX]['FS2@YDutԙZ8T8-SN[:dRHIn4 2.4(bmw7YdnrIIxvԨ?Aai>wF*{`ν䝳sfݦg4k0niQ~}- ek·65X35*Yk ^&%#۽ji kn^k7; z6w۷ \&uͣ2F#0\FwCr#%{:ѵ&_O%G~$3o9RA|J܃2 R{/0vZNOMp{}{=ݣ׉urZY;Cs͙7w-l2S֓+]:/P[L|ke~ mRhu_Ie伇om>~%˥-MKJs@pN3jԘJǏQESF#>V0+)ݱP=$zt{\Yi.Ε_K}x'/ŠH*uїb(Z᪎"]msfNC SEϋ^*獨!N.jl|բ'S$ D* ~QKMXpdKmX h{1MvaɎ:Ny?kΨ_$HSL܋ƂY'ο9YJmCׯmLIu14J3'-exjfD@.cn`)%AQ/x8r̬X 3{g w-휂 9[>m}NTIrgqȷM3/?ru2w[;Z>(C6kfYMz]&MGwS[{؅*ڡ \.<ȉ]`/ @<ܬ\kd$W9\p;bs(oY^k'ç.q_qwOO@m-MR/ۃե|E+zQܠ[}[g|UbIVrvtZBDL/JbM׍!n\⢄򁑖N-#vD6¥-‡m?s/L7Gg~Bqriˤ'ϹK2j+9n3+O `>y 9# cf=֦acGؗ *I3k+W+E1`P%S7`ФsBj|哗 gL pR2+L 0my 3GhP?Z_W:3ظ|唬sϵH^"RxyuיZ:iE8h!jPmFa>s9}ԗ2 I䒺LRXt!43ẍ́i);/^g1?'i5@5$?/4RWgcj\{tA1S\IL&-dhP9OMŁ1r;Ćmualo=,PmBRȖ Q·첰;3VR*ڙeH;V.O3A8Wa6Ore<3fc4v0rv71U}y'=7=H|*&_ltj%d,8\cZ^6D*+8 f,Kțͳ{M54xS/gRiwTWW[Ͻ[HF|ONbtݩX//ü\d⪋NT^~Z\܊݂s@L@tiҥX?`B-2N;!< Z*Iڶa.-?\Ho2=Dݦ{^6hM :@y \1/l bYHmIdN8l;%5`%y%z)6H}4RRD# E 8eBGtU* {`w ;#7}}ױa"%0r9HbYcuf,LtdHwqe}xg9.۴mN݃?O^{C݇dCybԨcY -&pfM -,$kAg^<_YD@qX-f3epۨ(#ԡ78!gj6LB$zAa6L+u۝2MCk [ tnMob(nl˓dALqCYG-ʈMܝ흰wgf L?] FHz>zպ"17qa]WF\aՄ. iH {خ@g }UO LKj,49dOO}(*\Z7닼BsQQy7h\QhyaQˀ67~}/c .H]{m>qs|nؤecu.OȃBAw_TQ-V'ߠk[;ӴK1[eri^,]K--YR6O-塌xbJ߽|Ya᷽X4,K4}M}cܝUua Y~ e~X8G#佬_t&m06pˬ y"y^Cvcs#vp^Fu㖂8Dh(: @TfzqxEkڪ@b@jz{5vlXn]ҭRYa*+4sj&+-J ]NI~5;3 h,\wiVu_b{)+XLơۙFb{}ہ ;/Wu|MV'|x`Ab1R)3㌿ `,~wv3Jq}oX5S.Aʭ<)_,;4Z `9 >Hyb^;]v99 _~=e;aǗ*[pќgwċ&fYyӋ5-,>>g|ٻ^=/P:IbZ8P;d͓IuTLi0// ^ qԸjwݬk67Z]'e/-6/:={kUk׼:>v}/g{0RkiNN^lQCG1dp-Fv+M&{Hdcbl5ʽF>f36]KNʍ/+WM3MaM rg9cl#m.2.(<6n%$."I-D–Qlbx^s7Wq7& lX.Vea% WS{Fvb_1 #R-q5!Co wYDhcՙZ9![ RF4R gx=|qzՙI[IW?"i[Gf\iۘԇʽZ| h4(k:05kT:O|Qz}Y}+e3ff-y:\OcO04O5mSe+Qkd8# {1\Z]N !۬{oZx3/ۈlZl6Nֹ l|`j0Vq; ,mf1C!{nM ieޓFU佹7i]ҖP HBb)*m)miˍ *^xⱮ "xߋ}Zwe]3tf(ʥ6i/yI^˗K2)~F T7SF>r{AQ\zlFzBoEܐ72Jl(Nް7~ᜍY{{-ݝ+޺~ ֝YDߍ#!/>orb@R  ggE02MQv]Ag8UIh'uSBR}g;) , Y<3dY ֹT49!pl1Sw NiD$Ir5h7]%N@}ÇW] \4^[N]JZ&l'cqlb\I 神D+1-K/^2(]t[GBNFBfFBNF< %@UΖ?8 LdLCޅ%!Ct*rE#4uUhResq*(C)Z%$e."Hz1hGYQB 9&+ݚU~!bӳ6M;#$-;뇿p6on}L+wXF~?Iowvئ/w/HC ^@@+D>X^xG51xg$/|ɘ}0*rF#xWSoYVOͯ S۠۬_7: :ttu>VֈDЩ?z%U aVEZB-f~qCQH(BHLr\㢞VAeCX?oMlţ'N,D=$Ujˆ}Ԣ|CwɎ;/_y01>`W+0,;DO2F/`0zgIբ*I5RUVZJ#DT"l2PEʠ'ilaF)I`BCA'[\I&xoavAd46?.ogTDp2qڬQzĚF*XZɒQXgD!Du 6jv .]btW8MD=uz)~XlEו_V`.J=Fky'T%Cɝ.ojLy$|jćQ̶J&l4fD\jsbz: HVrFx LPe1`3GY&2oXDG<!F$7,KE^ \xfJV+?5HnSI fEfPo`2e}( K;Xc-t W[uz^R2fQآŒV).ʥ3X-BX{/l 4D21$bjR XZ,Xtj,JU?*4F)jNGb6?YF%F16>RV$Zc{{1 Q(1)/[F nvVk|J*l*Cd4emuf \'#*& ~A"rcQ!Œ hH$dfbĹ@򞳡/ݸMUMUՉf&R؍Ѯ|Z;z|P? xڧ+w֬޶5H(N|ޗ _x^x,UBFit4Q e+2&DD7Qfj2ζώj76K ދx/ro̧Oc>=rx. GH ,3×n`5Kzu8 (A9|v$ǒޥ_eFV2#1=j@KA{Žv\ۧ*})kM$ؽ䧨K`E-NEQEh>E=}I@f*>*:s0x(倖Ni2ul;G0kW[}b֭ͪGםyzt҈-/y+PMYFBNVI'i#D̨. +tLtl ͋*)-:5..jN̪oSaG4 PcgE[w"> ±/ `dܬvs. G-G-mecYec\F/j[|ږp?f;pڃ崏ZI3X^ @e&M{W\՚D"vbrOыc5խ:>h0MHF@VEa[AtFuu_Zx[o=K޷xfY3f­7VK/=o>Cw.i+fߧFfbI b8V*6ݢZg:SUgB3@z]-ˡ8$#)xOpP3Ok}Ɇ%?eljwҏM( ֘٩ٚN\y_' aQ7Sjت.6G3l[|w/:al1Ivߙ/(}H$?8N8?X +x[]qbu|CJ庋>ςIggOxӮ"r>bvn~aqEۢkѷZ-;R,t;>qаYUW7J.-lIݞTrx]H^ U9P $zhS0KdԋrrhȌEv;2؂Q8qu;>p+(QP8[qhr ,av[x[Ñ<3gGFhgFT6`¹ϫTsRZHpF9=9" #!Ӝr[POtI&z;04(Agi8=eriʇ۫"Y]R g$D$]P>~Yjlzy+ѡܢg2V4 Ȏ (> r}`=*9:*FhcKtF`Q<6#,A|3bF[+ЬlЈԇ͚5k^ը1|~?zڅs1 6RsDQ4I hC])iOdCOښ+=w1! a5ul֫G99Y\H+aI(=)rҨ]CScnEp[5^=9#3ԉ: :V)}X"/ZVkbC!*%b"j jA-(dZ% & :(#\Ƞл숤$'{kB7ξB20X<;6(]P1\ջ!Ϝz%= *H:Goo?xPYgfHtj6&,KU\ F=d xGNS,ō7ZW=~\EgqD 0S40\/7hB353 3"7(-{>$Ra(daz>RZ-(:vb&$N/IzXi|痞6aS&Ď0+BpH=ә+=I20]Cw=&1B5 ;A[~JEOcH#jtG=HEfABF\x "x+^9H!h3 +¨=GI=ȑ&"Rzjl8\:ZD>/1tRnVH2vUPq&nhdᅃnÅgwwwr?w .<#$;&*~R(SM؅u F$R{PD\Tȅ*ЮfܣV* E)~0nEۓۉĔ#crڑK/*./WA>IbUe#w gxjirg y{KzP=lC0C4_B?D XPdRp*1<4˸ܾN޾C_b45TsM󢮷i|# \0DNP{53!pa8,>y%EB66` lD[ wk\N6d-2$EHtHLX29Llg *@wmw^PGSCTݗz@Q]P'ӗC<;JgZF ݚ]R5+]@x^ؾr۷v?h!M%wnw K|7<<tp'o)[ zntDD<?ɗ(!x)徝皠0ᤠ^˾:;$B02$TR7F }i{a{6uK_C!a#8r57wPM [~4'kho)@fg5f{+/;/JIl/85+7%Sk3uNт$MIȅ\y+,"C*Xe9EƦirsήrCHBVbf* *?B+󩤈GXV?,vr~Lj'Co0'Q/hwZp0$AuwAF i<\eۃzާIk;Ҩa)}1-88w-6ަ[*pܫ9gx7s.WC(t& #dCІa &#+w!6`u3P us^rq=SHɮ/*Wi(TRKIv!rٸǍ^_1㖹o\iNTG g3k'\pS ,*#1^~ic{Ř3ȻFaL]pvqR^Ug%10wZ:'aM=j8pu:^aZܢC"a_ϋc\c KF$Y,E'z#b,BTJ9oMMO@5 L1L?o! qGWD./d筽՜s7\oLXqWS^ , }.)ï-yh9l~/5{OȥѾ!l w-֑֭0(~Ub,dUDŽ f@1{`lj⬜ܰ\/97f7wեc3iflIR'o*T~抽TP[L2i(*Yk\l9zm>VsN4Zpx51h{trp:qv{h28!.nH:ŢMrQvD9#7'=lm]g葨O~kרl'a6 8'_ȵqM!Ņ>zwW]nLnj|sgdu~PTJ&~5.ڕ?Yl ?f]upW~Ww}?8\$04&䧎O%2<4T[ s6iITTX^NOJTc aj"i@K:mW an߄Gw-4ܡ_Ӆ\%EE~h=fļe ƍo5y͔)g^_P~岃ٵWם3FnxCC.&|r%l"IRSMfʂYDkR( vǎ&գߍU~)wW;Wh8s'C!&3 H@DQN[RbZZHb∼XmhPuVd$`8~ `pp$%'"Hv`|wͽkeEw4ͻ6Wu6(!1#"u,@aĴVWQӄ̊K*~`g9eM Vf:zOr6֝uS{aMr7: Uaǻ$^UZ5=/ѡҞޗq l' =Ķnk^SqkIn#.B߇{]g[̡z[ ׫$ڦRp֨JSBP5X%z\/ӓ=0 C@Qk@ ߡ)eC_#inDIdޣVS50kNJ8! B j .|\$~M2EjnܻŅ0$E:MvCzh:߆P&:˕2(UbQJRR aI w oLK7q<>ݠ)5 FSӻ.gA+np z/7)K 8!gĸ>=k\W=yKRRݏC&7LЂH'TgN?wJp͆11ڃDeS2#i/VVjGq%XLub%! #QՇ֏'O~=8]\svzNu=B?@o͹M] ~c51ɮ .b ~Ժ$e`t E>P/\~o]*&\$N"kzą/lvoo@Bp D DDFk}{Y.etm:Lx n!]xfU7/bHҁ>j I*m:!F@6,R/ WqZ24Twݤ|\gq%.HN4 hh})vdŕw |M>(8$IlDmdzP38Uj{_)T$!ϘiCj'ٳ/z}w w(N-MWB?NrM&q̪=aVmx ϰ2$MB5 d3j.dRINZ"bE%j$BjMݭ$:FZCr遊=!vMJ5*H, +ڍ8aoUֽj=ŀ'[b#\ `=0rQ}U\T/X%Ii!IaDT< hB-vS#9fpFR؟ޝ 6aƒ[Ylk BАSW!hleΉ׆!BD#acxkp1C V%"c,]>q 1X>QTCP@yɍ庩S7=7RusgJUM`henEƪ7o<(jx[awuϾ f@c\)K. #˭DB%BQFK2fb &kAh0Bْ,-/gnw>n]JiSѷ3.xQw5P3`Baz-`: 4\7൙xm*YIdd02ц lXdFE-֪%TIfߢ òcؾ:/GθҘ1$Fv:C F% [YHO<JbX I|:Cz/6D(H5tӣ6)}rj(-?n+rrUx^*Z:>B2&a6[DF %Kf$'.UL!q p;VnL3 ' m7qoV=\aZ8t[eKfpqȍb*`:F?EuYdY44 MʸAU2^E(i`6$1ed"B R )BJʐ3Nm""!CFsLiXEK$::#>.8mlw |U]9 VB7L ^yQVWh۹O_q!%SPuH!֮~θ眽yј1K֯zbq8|1.£*jjv[X:NbZLIM e/1a̜ W kFDf2"{=lTE]S2gCɹ.',II{_b|Gܽb|1ᵃYkiWB/ChkH2=l^TAU֨"XċEq'q#QQ&©ax헛+Dv%FSXݲ}êФc-m#`hW{ČtrUoGzĢ!ΝD6лQalԳ:WeMV](2HPBz]kljրU ڴIPNjۿ&32""#6oA -c-F:Lb{ɼ=3AW&2zЄ:4ba+ݸAװ7Ks/m1m?㞁FU٤BJREݻ!B<3ᬁjTEMWFoV]zD 02qP&y$eL>zp`Ҙx<NؑpI(X=Lʲ#6%&9(F,Io~/'9Tqg5=һ5UMXPe`6Z`#%AY y)kwjyx]E72#c &, _9u[o'cokcY *Q^R6e0ƃƀx7O1 g)Y|b\IV2x<i pR3<sH#YH`_& a/ E?sX hQ69aZRsdUcS5HeilqX@pXKBF~&{G ѢЛ8C%a4$lE^,g `y6,Zc*Ca P*F+8T` 뼑 X6AouKl7S8]fD떞PCG3>Q,Gw)eFYzi ne`3|j{/m]HF9( ږQjB OF 2bB`B|3K OP+XUB<ߍXHzBs!- VzIP2({#CPf3x{}6p%B(աJ耴2C˨Cy jXtԱ&f֎֧BFc <Z6*-ik/ 5BK o'YdGԉbkc7 hb27 ׃}ϻ͐ PR*-򶢖ՉR@={#|ֺc#JihࣕGF݇Aמ|(@_If?/.~(R1% Al3o`vO3{C9{1]o0JdO n_)Ά4zP ^u`To&ñF2Sݾ~q팺(iXYltZ+yx) <\`-X3k.?P(F_2|>8;,\y 鄎G@% Oul%lLyZ j+u8V/}v2қkk Ж(mfN6z11Q{ؖ*W@Uo*U2WK_iΞRWݱ s,Xb}Efd~tF%,UkTcԫK;*j]T-QJ>{PP1zMp%g%dɗm:흵mrQk\M.okjoʐvw65SkY!s|out6Cۺ3EnQ-wu r}sW`9Cm;:i$iKh\ ]ưY7<wzF 3^;Cy3h aKe~5[/mnk]TO [erj C ?T[)>;(h7f5a ݰig3}I[k{m} jTAsU.RoͤiZ;1 çmON; 45m:gL[[ pTgskm>rvBjSwwLj%;k3;eP< ^F]bGj#/<$b/EsK;aqC+>LQ0M)sC k^g-`>Cn섑 ST9Lq r\m)x[A+T#G}{ݢ#ʠon̤Z+Od V~7F['7Z{fSݴN -̐77R!c4 X(z":xh$ha484@cߪ*^ iV%M ~t,l4ہ4u{ x ~?:dX} Z5!`52^@!VZ"OW5D.*O.+.)SB8%C^VUZ1JEU3qrQLybYyq\2cJeԩrE\6yʤ++;iZqYxy +ɧ F"ZU!J&T-`јIeU33qeUqPh";gنl/S>m|0 b5\0Mg=J؍Ts\-0kjۉ6v;*A֟%utu)sfd~Cg627[V^u܍D7m̽1Eu bV3ey]˙73w+seN>ܧlڎfgH+'KW>ZpHdJO0Pb?JZ2L@+Sj@I(TBP:P`EIP4 th:0i_&/1"Ǒ8 Uw5x ށx?$@Gߏ ?!\y߄u? HA'>t/#N /?ضUᅛXX xbtl/=z@t(]ܗ?3Ϸa}}/srga޷q6?ôoW\T~=Gp_>_1+fp)~S?(Jގic 0p;iܟ e\ ܯfRG P9gFLjt9h$7^=F/7hZɽs5y\}NǗL߯kyZ^oź;R=gs߲Vǣ bk_h9 ZB7hڍGAo?пwsF<90op%/Nſ*WlT 5Gøo]}kJ1w-Fn#uG7,c9EGM+&>>6H>Y(3SRm>wp~yN\};9rqx.bA$*4tVf=_T^==ޮ?2 'A Yh) ιv>c-~˝q9rmtG9n3rs?>TOqg>I>1sSSOݫOW+3|~sgy|<s%Η8~^O'^Ճ?rzˊ:/T7(_67pܾ  w>w7vEo;t$~'<o!qqQ7Kӏ8>scoo,~s}s}s}O'Oy/6o{a?G¿ @ _&(|cPxsPxkPAۂ;;» KPA_  Aa}PX ApbP89(N g3Aᜠp^PxXPxxPxDP8^> _ CO :0 ._n ہ r6(\~< ?A0iB?RoFzKs\;)k3фQ~W`\㒎xܿhtO wB5:m `령c.b]1{_OJggoOg\ǸiU㢦IWmtc7Ml8sb< i_=q?2t(~?"~d?9|#ht׍Y (fH0Ggk~; 'dkD-bjsg?3I3PW1@I5yL? yrky {>9toϡ_|??d>]vsDD͏FLӧ{|ؓcTGJv'ԜLsq~6JMKp[S.w :% q:]vnw9m ݉:~5'oY0rgIz77R(MkM''[';#߳y7^AJyNgo/.7O~}X̱]_~Ƴ8QD+cʞ}83Cz!'B '9i<ʛ<[N;~:ρ_|O FM\J!eƝ W q_VV9+Ku t'n 1_x]k [</eSΧ{bG@Vhec)-}a ;V{ ހ7RzR:`2/)W^Jc/E\uC^.wbB^2C?_:]w>W("͠{v;;fX6?ۏp8(\WJG̉]GL@r(~' :dSXxKFwh-V3m[_}R#-[6Hl~\'49ձeYϛ}49ZԩfTtWN#Sޑ~n ?NF1e4/ecHmeu9T3Vc'pt<\Yo`l54+TIG]%l˵W>ߝx㭡_/}Hz'V?XhϠx9'٧7e~aj:#uO5YwYgO޶wd9ⷰ/~}>kmuX+P'; r1_S$-`sK`Dʛ'1?/۱Ҽ:/pj4bEb^fuoԷJ0E܎vd@J $FZ`ʯ@og* ^rj/JGf 3GM1(s j?h gY)r; <&ٸ_c>Sq9ٓm|F%`ߩϾfFY=*sR9?]ڧ2N?[)K?0fLs~on=LNŀ|CpCG~lfuc>&ˮ0s #?8߀pz̯7gD~yx>fƣgfnt/+&ߏj۫I?0k<\hvǚǛ͓̓ syy<<LN2^h&in"-N> =d:@KeP4ft5 I@[e 0*mG@k;w ۍ4X+߳~6 x%X#AVI\6HM VlE@t("'k8Ef(7 @9(8x#0~J{? %O'3J'JJ1p{kox3gLϙ3d2!b"RLiĈcDbDD)M)1FDJs))Ҕ妔RD4)R.Kir)b;gI>{ynuY{^{9sfF y"_ŵcBsB+Rܰi~8P#N9'IbG 94o|F;)OPA(&>Os)S%xNei^ hnZZ).P -4׆4>'@s1]MiciLQK4o4'Gh^~A9FNVQBsHAbSNxXL0UO#`QK-C!D__'n\W ^|w7TLysr 2MUʂ#rʄQ6Y8JNY2h [!ǐ[[IH//_"vv۔9S[wwFʠH]](N)MzJR Jߡ 1&)+P(+s>QQ&a< 3&p :ȶIb8.V7rՋܪb:|b~QBGR:^:Bdd(#EL#EFN@G[PsssU!B>4OC)qV͗0t7v2uȡfE,Eq=2Pd!'CNyA3ވ X?往*bP *Aʀ!%D0)qDe:ΕLכ1(J: %g/"{VrzJ.;Ρ558S+)+ge2qF(#sƼq2]Ȍ F6=Jv/%ȡCGԝ=$zO_D&}t)rȡK= ȞȞ/!{__CysRЗC_үկ%>gҗC=_AlЇ(cr]ׇSvQ2f!WOI_G#t%Ds]*r跐CW_ԿHΤz1ܦ#+z eWU_AVGV}YȪȪIg*rjr뫔[˩)þO'S<*|g?_k=bRͼkU>T. a3lpn)7 niE۴;EH+Ҵr~==(r?y=B _wo1%~կ_ML]<Xxr"ʟv-G(B=o;/|{nhc/W qeZljf?u{JEov3u_+}ݾne ' _P~x,X m1SisBz.!^aKs :EtG0R<قJ0=Iֹ΋  h$Z:#hfn?} v'!u6}3t>:{I"<@UO'r333SYY4zzllll{tph3Kмx3:_ pNN{."]j s'Th0zw :c^n(gL0Jɞv`w7C|.?{F 3Mfgbog`%2A?b` ]zc-\`ll0l)c+w_Oz|AqY=kv}ait޹sS}/8%̣GKL/M,%86@7=47N 3į#bLx c}ͣ ˡE Zo -lg3cBϡ&Pw-T h,yVvz{SH/A:6u{xB'ޯRD/|<2io_P+]}@ؾ{K,)VP#2J>&qd<`ֶ>ŞA%fϨSsR]HuTzm5$(}*+J5C[Q'0  cJXF{Vͯ#G2iܮ?<$1d '#uODm%غ՛1,G ]V=[>B<v;GV ?cdcMT1L LY}Qq+VУA|݌!!ޖjk1U5ՈB=V"Q ?bAi3c!#A74{zͣk&|-sӡ- ~p%tGg`K<~LEuzͻ!g]hq}8> mug*^^dL3n+狄[X'yC jOA')vx{U[jxFu)w!~p^=f>۞8y?jnl@D$c3%p^ p~ ,0c>c 0:yA3H1Yk"i' ?nC"2_Di~eP2j&ǐEus~M8'tuqV)|رՍcHx'?*jmo¶M]:κX(b2pN1sJ<y-$16@;x+G|:3M/\BQ:+D(Ayֹ#-`X)NN$:TL#I0輀`KRK?} V!h&iMfvRuP^!ۿ e$g3>mrD9FarB$rxڥ+gCDQ6)1$Q۩rJWi!j卵(˔zHJ*߭45OY4STTިl'j2CiPJKi PJRTբLU*3y2S&(J)QMLAe2#E"e-Qe*e<4yV>ak~<,*NZ-yR]yh\hw#Y-dz*Bke6L-h&hQS*eUΤјGeFFe-]o$V9^Nπ(Յh=%A,ӶNj N[E9^}Z=\y{2Uz cق{IC0pT.bIGaZ9N)߭Q[)r[(`4vJHGV1jǵwv;Yğݾ?AZ坕[j_[=dNãicɛ4}bSXO[s,os3ƚhZw^ڟ oĚw/~6q?_T.yvmN^9iݼ~ ۵mv'wk{{y զ?pot}#h%};\ 5﷾ô=?N5k~#,@m.r>J!j`yXb={g/H{1^5˭=}%qXׯ7&U̴Bb{6k{`?8\"c!70e`k:\-.ZcCCy^^R)~S(r28CatPF=V)Q&*e2Zf[U+ %J-+6)UvXnZy^oYBϱEo5I\G+߼v|JqqZ}O WJme 9ths99qxgsxr _^kM?)ñ^?I͵r+NC[B 4{`l8عx D U?-q/x53RgORhVX{$ 6ldFjZFۡ!'kojo)m[&s$9RtGlvd;뒞KZؓ\&}+QIINF%-GIuII:ܞn]!$w[w%nÝ,u}JQVzU{Ӽio\ww;SwE)3 . %H. jQ#u !0 FX2 eSpt{`UНX`[BPkgh`% Zz{mB`tf]MrJ;t<g%xrPxv6>֯ fyQc1ۘg,0K:cXc4MFFN*C}AGcIqθhtT߸ c`ɐ|0 ڂER[q$9$7KY9$92ci>"\K o& [ۃC !K!{BsBPFhPhHhXhDhth,qJ7&RuД U- - "I6VVz'j m m  N&|88::4BgC#J0?t6i Mֹ Lr4(8Ի,QC#;c O2N''W&OKIzCFy!I8 m7$/2$/ u˓W%W$1.&7'oHޔܖzz9y}$K>|$%_L  aWpr0kápsB£Eq'KÓÕe!<t1;FvxY>"8?^Z^L^C= ox36p›C'[5ƚvC cC8EL5|*-1|&|>|)Eh)`c:Z3ZS2B'R؅ I2"et%elJIcCD K1wRRLM2JROʓgdT,KRjCRRV3wpchPxzrqL4'%e љm4'RDrAʎmS&HL9r4DxcJ<^~{%ӡ=QA$QƱ=jhFpEtPXU :$:,:"x(:::sl$쏎6ڢe49FDFgDsCթh-gheױѕJWSݦh [[RElOiMJm 4ߒV)P({팞M^*8_rZ]"-ΟiΔ9tZ6BA56eJ+҆4VI+&/I+H9lIMJ+pAgcZ5*iN-H[DzҴipJkNې8Jc1?Ҝ+TdYHR;K[cin{1d0?tڹVEZQT̏NUVUZVT\) )b~Z7cacִ)",dž cE=4XGzZb(k-RLh%ɱJY9زX}lE18=6.195ݸkuĎN.`{I7#u4ɟ^Ӽ^><ӇD/mG6q+'F8oTO Si&FgWMO_[$6"!}%ŃC&N"7D/ϫm< 4Ux3o Img\SGEφ{- !l8Hߑ;g:?}GHDgjc/T[\H~6SK5GOwFM~95mќ25CwMӘly2B<;^D?3gYyBYi#3Ir]FAÍjCڊ)dd7P11 d*2eCx6gg# R:i_ݞzXMDЕA+ݢAUoeQ|0#@N4YcˍEs25d֬]YR YyLUF{㙳"Q_(Jf{xdMp S;5_\LgIu'SB?'Y 4;/-Lg-="4PfDkjnZiZ ~@g:ӟՒ/kKֶ˳ZvKYf:IIԯhZsW(әs7eYv$N]| ɩEdl%ӕ]ZOޔ267fG١YD1? {xHȏI].LѲIƱ,Ovy]zmbdϤ(]j# tlޟ83'6!u~y9 R7`E4Ԕ(9,|$}t?#ɢ8i=Ohˣd= 崫)nސ)5>-{grVtH}C"H٧υe_>E()#.)ء5sY®N8'3450r:gTNQθ 9=rPgN%iLȜY9srӮ5mqWxjޫw959ޫw9rsV4Y1gs֜9rs9s($L=9rs\nvOqnΧ9`Kr#T%f/ wʹ0wל..^Ar ;9#xfnkjܒ܉eSrf6ZO' w1;Dܹչ g]܆ܕsģ {ܖ-战'x^rݛ={8hܳ51]w ?]bD) ij ?j&eW<x^<0YgF+ݿ~'.Ѝ'v|. S OwMx;|5~.yƣl+k/Ly3dZ!Ԃ=l9Bg wPސaZ> ҏ!Y ^pӎZ Y\clj`lzYL; kHG@C:-ylMa(?%&b z(K/f&zt5d@w@{ cgQ2V#fȤ1bҰ6Vv?9?A ~N !JͱXSD!wУU3́ǐbcǜ&;r8?9'3ͱg (zF(Ӫ|t#"iW@OUg0[B}B/kV?6|)R+t:}Ot/Tُ)Ke9##]xO.A2%%ؐ;ltSƱ},% s~ <4Z yo- ;3xi6Dqx`7ڝ3x r2a~ɯ!~n_~%Qzq̘oC[h{;Pw|~'Z?3K1GAe3fvǁPI/|{rƴ4_gf{;^;LZ@oG&s8ogJ\;@̢ seF8JC,#a"^ὓ\JC)<εN=matVȜ1v'dņSுZ5#p"vEh}EY`(l 2|75(uwRfLB[2jfDzЃ|b_l?%o<Ehv"GZ#yQuෘY v><gj) d v$@+ 60YyD9D!sܙD~vMa; "Ғ#2h,dVo{@#C?4cԒ"^X o6 w#qڝXo/kd 70v\znfZag˻mm;Lz.Z/=%Bc|XX{lɝGV3ڏxcgIm&mM#G别(C^s?^;&;o9XZ[ck!W.cc^jݎVJ"%a  h{zƛ{f ,5wж:{ ND سm;heYe{x~7>\|ҭ#/W!1"UOU:~CFDHJDūi3Mb݌.vwp"Qc+7C{ V1й7je XvoM9 ~biIYZ0"X#*_C{Kѻ#X}7#t%d~ffZƟQ&6c? #Pk>zzfb3¹Ծ\M? ]hůZXCcӶ4ѻ m߸jJ9f=B6s@6u4w +7?y=Vu> !9ىR=NF~btn7coNlnp|ѺK1#Yo͘>fȿ~Ez2zb܃LwedVٝʢ21|m74U~%'U (V~{rr_?LSUf*O*_oUY,`"A~DbsvbCfbw4z&YZ qn=$ڕ11mi0s?nһw6f"(gc[wQskt|c_E#Wb,Sl.:&w[t1I{x#q(u v 9s\';bccx11ZT8nw:*ÎJGxҨ<*N₸,锺 (kSr#:t. "Tr\#x dl;OG1|hZ±:oӱ2EU\% ˙J/(C*+R)RT*ӕY,Q0F#SA~v1-0MD?s(OBN g|2. cxh{dkGz;QJDӰ9@|m< K|95l1o"cڽ3=x5?CRRglkgUBe1!3U Nh( Y0VطVc 4cBIYQ8/;띯opw͍m>wfvhox[vޖm۲{~[\!e,ށ=w`w`?w`mQ~֖ڮw`mX mp\W^osU.~o怗-;;[ >gԋӓ=uHWͺt,ۉaw>f[j^LF>7M%+ѻq.'fXGm&׋%qg\ڌ&vx7{bLqz\ݻǷq|nl`z=~m5gvqNy\Q?޷.tMF;r5?l?% 1vqK3?Wjǯ9~ףA&9.ч#XD]/Q:˿q?ק{)VvTOuΉH}?C }^/ԗz#R_rޤ[-qeŨMG߭_o9ͷxo3o>+cꟗ,M Mkv_^MkQbNww ~f8;;绘NX]u/šK??Rd0οؿ zĵ__ߘ7kGĵЌ.;}?GS3_ @$( L fs׾v }r^%@y}X$>{~|IGZ {y\'yGֿ\ypY>ٻ?׳V_W7&s|L;$PoJՁ@K`K``G`7C>x.yl8 syU#jdyo1;i1qM9xS1(d^QiLnUoCw?}GV#}T="-3it7ƴZVmcE~͐v z7+;W+b *4;ylKǿObZ¥}?6.gچZr}΂;%T^2 2} ,=Qj>?Glf!2 NA~- ,(]%ܶ459_N<גQ JmnEH~ '0)d1ڀ%aLUs:᠟}+F}B[ׁNȚ(CͶv\h[m? '@/%y^jO] z4hHg "6lПktݽݵ'L7]A#J3JYt&1Af詠]Qo0[oŏa@ Dlvx_Q)1;?PW<mz{u?m#9T5#h ~;AVS l_ ䷀@^BގOuFK rWba?FpnYz1M9?0ȬB-9d E&N✸(*]/ʰCe!Qtqr,ɲRN9OGlV m d]֚*v+lCQl,gqoLt{ 3x9p9=ҳϢv_ Oikh $݌m7!m *73aOFhxpIn~m~Ì)/n:2r(D" P9q.nI;Fv &=T'C|P,g E!(EM5bX& |BNlV]".CC'|p=+އ?K~ʹLGT Лk!_eY<8À ̇yE`F3*mm6\jBm ޠVkMk-o#j[Kp@;Nh;].;ӡ;B(%9 ᎑v1Q(NZaICPcévf-t<ڱ8KYuIdOieXXXCo fj!JMV,tl &iMdN:HcYCLI qB1ptq;9Nj{cfi9o68.y%"p4'r!;zg̙ӣm3Kl9YH%+k$_%Q}Tu99'8K{,LsNO/Qm6z쯦vq%iK6Z;ioҁtulrt9I': Ig.$]v).KwBvEte܆55ҹ VQˮ1b^纤IC\\ΘBƣsٮyE @R:"r5U=juq5{_K9򻶒rmr\;yyNkuuuLkq$w&8:ȱrq,?v].v9|О?(&YsvҎR20NLtǹ'n 2w)g[Jt,H·dzm4eK e$ofcw .#O8u/v/sѽֽ1bt8ag5-ok?`/pmZ{\>o"~kF5x EfaL" BaȜ2 "e;و4=@;Dv17Q#v߇gЯ-X2jX{8&}H>g;P;=<9I8Ѷuw>vy3r~>X[cF+-ډK]bcFuׄ10#RL-R5F%,km5`߼< ~%Z1 zͶSby,OOAfWOg>b!Gy[gjWoΑM& 9wɊ A l9{-xwws cׄ>:c,~:xfl+/nS$*x8_(tJ8P t2^b 0!raE(,#',$_gC瘐xGzn]W%p| luY]Y7f,>M]=@Xᄲ8[j.>9` dSm}j-{]]f9CW%?sYmz÷6X}#я='\6*azƐEy\ su0@}-%LP>Jr 1CO<$)m sң/09c{df!OP)~ɹǿ~ [~sr]a\bOYFPor}byELӓk Olycy`+vO]޹?t5=yE"8Cpy]&O<0|jK7%/#fϊ~S@yI5m3z !|u<}5ON@a@M0gK&ӕ =멲s KLǝ^k<}R}%jO<7Y O=z_q 6ގw%8@p( N.{N!'%Cp[s~7ի砞19<0|{_?-&s@$*kpIszzO7/D%Ȳ τ~gu7}9z/7 ^0V/Qogཷ7w.QV.{~xc}͌`3o`x2tH~}LF]p~ommSzB,  tPQ:h@8.u^ ?'kgpZ~Pg> :el3x 10JOށcfqh W΂|z 4o/d B !|Z0_XgPEsFi|/ot#@z aa}}Zy xfoW$\BP+1\I`=Au`0Uw[ג1a&qiUMx8Xv_| F"9Z%r,ST9GYQC͕r\bW|6&jIU[uS:V- Ƕvkl]lklW㎕d (:nو_GmߋS`]7}H Js@['ki\LPd 1HAv?QS޵+BS)D~FO#3*S(S_p.}v w KN3EfG"Wlc-|qϋt'YQt">c@EW1F|LG/JE*b,hR,2I&-n^%'}1^eP)e SdKT1Q4qLb̔-E̕^yFrO^+崳.ZL*lb Ńr\#*ZV`oʔ hm_;-ΰpo!fꛚ~M2w `s_lVH´ڔ4 {t$w*KR:ž3iuJ̷%&sBi)c[.]ٯR:ʩ޵T}8{g=wMC$H$I$Ic$IB$II$InJN$I3;wƐLgsY{ﵟg!G RKzeY\Ȳe ^+Ʋl)$E޲G#9ANd+%r\-3]ayLTԠ>zްbFk3*F Qhh41F+#H#H60cnL43Ҡ|ky"cXK_al_x'~r9G }4#7L 3#(Y,oV25:FYlH'lm&f'k4gG`.;t!פp~bW^ޮ03h&\,{=Sf3̝gρ1` gΟ)6p?R`F3\lг98 .&N\95SxRfgSC'9w'{)qv1>*` }?߆ɸ=y"W3 nZc`5-ܘO;UV k!s9j} g %b{|"+*X8_k/F`O1S[",iO -zVHPOX={~ƺZKQGNuU_"L(bOkO;O'OgOOWO? \|S+S.g}:Pc9=l\:YT^͓t^;Ѽr7- @?)eN@~ahK qԆm?-e%K#nl!dh!-R/9}kN!K'KEjӿ I(꥓F/&mQ K- $/8,:g t_1}VAgea.z,ŲsIP:dLҴڄ  p)d(Tȼ䩐y¹ɠ4C[bWrk*[O+xemgO2{:~+^Fh[ǮT6q0=,tϙy}e51n6t4 2c%M6c8DQLE(nl3c̲f3f-Xo6̖f$j&W?:CycN0'12l%t\mf[^y<9;6,\r%PK|ϭxj i!<XJ(f8\Pp=-LmUqbc)-Yj;\kҳG@؀d஋\@mA#J䓊ɹIr9lƭ6gV$SnS\/ʇΝJߏ{s1cHe=Oh۫Z{J(sZ`S@HWUm}3q&v49Al,#*0ˎh褬Ce|̙1YzMk`6, +3b~D J !@ȠK\[ =<;7`'c=u Ggy~3${z:m:L`y$O? _O2fuojCbQʄ<_tku~a5TWMz6tBӴ=ShJhRhښbMyW@o6OWҋRž W]\}9{^ !>b>J Ċa̗O]a;XVk;n:BZF ">i[]% P$?猦km"]w3=D͏$5愨XA 9wfv fbfQLPR~:B^1#祘+q>q3a(3'V_GK9o|b|_ q9>/P/L٥RtQNmۓlwjMrےw MŮq>go._ȗ߅/竉ɷ|/?c$_(p ["VE!jzh" &DH\ʝK.%S@,ȁ(ݓ&@T\01OD1ET #R2i*ѣȭu.W=@1Vw# +,RYo֏ښ:6FPF{]b z,'YXcJ,38B>:¾dzPSì,knY ;~D>cz(aY853<7>b) )t!?"6䤋 X~ v䤔,Y,xS!, ſA%[- 3Z8k|L_ʗQʼO#/Qzzs~iW9OsJ=~_GG+{Y5:9uwhщ"sYxS;eD}!vqjvV-J*TJ3U2 ?kekJpoThf/.bǰSh=fLVE5q![lHYVpk^JX`VgM(٭t曢gD;mxm T a`K ǺNɘӼ:,Bת{O'P Ny[Xb>!q/?G.əsp(l8]~OsvXJ~Oa=|嬐ؤsROz/7\Zpi9*f'(}SdU3Գf޴2!PW~/йϞg/arLu5'0Ѕ+4Dg 7G<5EΐEr\! r)ɃF䞜4f;iV0^^ `vz J 5*1Y%]1[ZkQlBbX줯ӏ'?AzcFƇHu(x);iCk0BP}jP.__ s+sW:;oO6ZHr)w.' K>roW=GY)0UBeUSSwnp^wk pcp/1ܽ;zNJˀ{. wTkR)+wpNZn Y;xj+܃‡ypg?}pu aɰj%K;@wTepkNߎ̭!e "5P?CmS<@uu UW{;]9_^ѪtI/[z|mY[oҬ1/֎Ud9iYKb :}Cloƺ[ʮYr,ktTϗ%BMukcs֚5_cEc&133 N[EצBR_16ͅ ĕ~Z=3'a%S[\!9p.ی=YGڷi ?طq:Z!}76= i oNhc rϠ-sEu^a􌝫׳Ypm}3!1KGjkz~ z/}wݗDxn#J/) :Ω~1)| OEӛJ)'1 K!z%#/1n 1z4FȧrRN%#y:ȧ|9.B9B]%(h}zްGh]oN:鼤sķxvJJ&(w;TwdP;,E// Us;Cr)ޥH :">bƹ^yw\?:mS&vOva;̎%OH1]ڦJ3є5z$5qXPS‘egpf0*J?Sf)EnN^]'v4GǑ7$TLn-ˁkۻ+<<<Jm%2a`D**, Ӄªll llݣkV"088Gd4`p.p!p p9p5a  <@X: <<%4 Fc倕ztl lT#NʚpqvYРIeQO*2cUFeTjBɭTFTA%T~ޕ\JܨsjKa,(LfW\>3M*ePX _gם1:\S^4wV,=ٵg+ϊgETzV<\ήwgĘbYYPP 9гQ6~vZx"QKy_p⢼!b8,d kʆ$ȡr!ʕr< OaFQi44Z>PcFXaŵBC= Uj ?.,Go]|m#zCҵ#sk̵C3\_|ߜ}^fϯOs/}^D1y9_[c*s ՄQJ[5CԺ)wAMO2~zSf$ͦm mtIa*q8uiP=q4zT=iGдJcVkС]i]:ӡj81U[j9t5S5[gNZ rzzyΛ9Os>18}~_Fsߟ\Pc8/Oҏ6Mel[̖lCZi$ơ>PǡN,6uRiChS#<.Դͺ8ђf4})Pǿ5$OjrܱK-X%m~}:tCjG[:: գуrbˏ9xC9yuCWkiC&Y\5u#֌b̑~M4vNJsUb}:5L>&9aLr4C9:l][t"#B<#scT2y9Os~,y>iJn:e.:-ȝN;|vםyk9_O[tO#M}Qc5ń)3-->,{YVV==źqa7_~Ǫ ?Ȫyy?gս_x`5|(v80UljkK- b*FH)Yq1EĪb[nq@'z B'OJCze1YBVz'\? \ppt3u /N; '\!\Gp1'|8;O '9<,'\:\n:\n*\nCmp-.Kr{Ta:\p.Wr:\n ;B...BR]\*>wjz:N(cPrBw-uBW ]'t8+넮pwu Nn@*: W%'\79sUwU WU'\՜pUwu3U -Nj:IZNnCvw:Nc gu-y%->[R]Bl5Jt6ͤmV~vf1Xx1S9]M_S=qcwWSmr\uFyKݷq~o[пm#ΠvIkg"!5ʻ'޽~|{w?{ _=| WzKPcZ jm)[5e-֭2~U [[)[2eo%S{sum MjTC`sl,ɏ0a"Q&)6%>z>{8_+Bݣ{Z}UWc]7ˣV-Ts2d0k<󩶍(r4,!Bs|"gU vRcQJj>9|8GyC-uMs.R;P5XdREH}h{g}OS#Ng|3Ъٙ]oBdoH:;RcibLel< KQ;YZ@9H>'ȡ|Iȱ*L9K+ߓS\)ʯ&Nߓ_>ig7s9n0G1f9Μ`N4'SiLs9לo.4?2?1?30WLsa1QmIe[~+*j]iE[1:VU*oU*Z8UͪajnaiXs "(oo9oyooEoeo5-Z:޻x66&z;~}>b',~m?___?¿ʿƿ.cDJD}"GL;o-AR:JeeYMR >/_/R B &ߐo囂_5)RYެh2ku̺HyR0df/y\)O/oUV3`URU۪cE &ZA `RQaO_M+o???ϿпؿԿܿҿڿ6"9kDOF<bˑ#;Fv|"G䓑|ra 6V8YU&+_u|[/ˏ'H@}oG@yyͼ޼Ald61-Vf3L64לlN5gRs\+7dn53wߛCy̲eYaO`6e.VuZ[ VsWu{o1oqo of۽wzڛMػ}}>g~?____CO+bD.#{E,kt=GN}6.-E2iώ)ѷӵ&YڶJ_ͤm;m?ɟmpڶ`~l~j~n~i_h~hۏ)[ j[iҶжж$Ҷj[qmeohbaeg~Q7+o_____)≈/E9[dȧ"vaƢYwajT;Lt[bj<@wdզ>B'R_Gl(9NQJ))MQ/5;!;Ϩ珪 9jVjx7ѱ@ $$""n mVoۭ>wX} _ń}O=ggƳxz;> |N7:.M*Ŋf& (U*{#̜{sܸWK.$r%F+ruFBpN8-kje뾬,BG[BJ ljNIT-qIlu?Ͻ ktE6%Q=vQ'{cXdHt'1\bq-#}Rg&,:ձYsr:պX+#{:!GQl a:bksv5."吒{VHL[ 'uH[c媤a-Hݓs֝vr߭[z܉K;W"76qinå =@hqD-Obڑ'*J1=5")FKW>?[~!^lb'KO!ϐݹgTۨoy0E{=~~k/#t{=mbHEJUkZ6EL> Q-lQY3O؍A|}|={77=~~۞nOgٯ \CM{=˞mϱ߷?ؓ7v=~~^`/٪q#PR=*UcZґ3])"Wrӑ9:$ uQ{)_Ov-+Ǯ¬Z=I] {*gvbvG:dwaK$e?ֱ2YjLwbﲅT=ݓkTs#?_w)fw}yݏJ~xiy|8o7̧|A1Kq ? ?G~ſw]|KͫxOy̢9ݬ{Ռ Z]MكzQ 0P (cTrcMΕݨTyze=XO*ydS?OS6M7d!_K+3҃={vj,iAùђG<1КkxY~-/ | iX>Ou>otIÔ~}| _ʗm_|_|_3I6-wݤk䫅R|s#sԹ>>ξ4})Zx^P}}]Ӿծef׶owu;z]v}n}оndg7﷛MfCvsa}]ѾѮddٕx]ծfWok؏\K<#AP}m_mc25vYZ;~;ŽEbv}]܎JڥX}].g_g ^WQAEQf+ Կ&1! ?oEpK}#}|}^&^M{7oo/7oo=y|} |>---}[ooMT[o~-4g}þ/|+|}Vww _a||_Vmmmmm}ַݷ÷~ߏ_||ݶǗ˶-}G|@湠ܘL;C.OD 7!I亰`+' 4^qf|)~+[;x9-쨤VkptЯ'&h<Yu07J`z'U5]̓o{̈́TmtPXrJصNN*P.|m02fo'_v_ȦRPK䍠%M"v8JxK.0gǓwv(:)K{ k ^X)\ zB#\@L_"e4f&+Z Lb7|&iSɭv"kSKło,TcGdAZ9鴷ŝGSgyJ 뺂L[zD/^i4?N'T!z4jYN;!ZX4X^{kҡ']SXmOMdSץ4IK`EU21岔@Z'+bS}Ec3(zΜ>0y.3vԽ[pKa32g}&bC4]i$$}uggo$@1CB͌XĖl-l;Ȏ,8>O3ŋd8!Kd'OHyBRz>#^%|REƪ8P"1Wխ]-aQG=[y#|Ӛq=mPJ O[+˄yI'lI"Lt t+=ԲW=]xvxvS==O>I%L I#LS{"xzzz"'IO ~tRf!B@X )DZ010GLiۏkEZo]Y[E\5SNSE'b$}8&%m%cd9YIVe}X6eLdr\,˵r.Ca3*ՍF}hm$)F/1n1&S cXn56ۍ}!㘑eZmF1f9Yݬm7fb2SFYQeE5D(7"=Dz4CeHҦ/iM?J3LqZDkuD &D͌D[P&F .FA: i_c aD#1DGID'SDQ,%:X`,&Xj Xmd]OqVc^q!qq1ISn&5ʼnF%2Dc)+`ƙՉV5ku6 6mFlM`&M2N5S%o&:j :m#nN4lN334 3TKj+&̭NͽCQGfѓLZ嵊DXѲVzV%5h Uh]՘h#Ղhs@du!n"fg b Fi[N&[ӈNfZsζY.Xˉ.VZ뉮6X[n[{[XljNJQ\jg+¥VhsEԪWYZYUɥV4ƻjjZDmjjE+h;Wޮ~ADJ':55$TL3\ zk!//v-X5ڂ\Vw Vv nֶ wZԊRwqwZ..q+Lwuw-w]u(5"ݜh3wKw݉ND)4~DIt{{qIDggpu/$M5{{{-Lܻts$z}}Q #1Cp|G|"pY Rp88Y2p>sp>g98Y|g8+/g%8kYpւg8Yzpփ8_8d | lg8Fp6 Mlg38 poo Vp-8߂-86p;pg;8pvg'8.pvg8{ ^p=8߃=8>p~p7P(U=Lډ$8 w3L_tU8-i Σ< Σ8y y 58886-8mi N8 $v'DpiN{pڃN8It8I'dp:t8 Ngp:.t+8] N 8)ऀ nt;8<ं N*8=NpI' hܯzN/p?pqGI?%m8JZp(iQ҆GI?|18Y '|',g 8K9 Qps c88Q_ծ]9!p~p~089 ί ί8G QgYYMVeYC֘5eYK֚%$~gשQVe3esQͨS) kʇ,=5`.~/H ,, Rօ(Ft6MXPkhYԻJ=iqQ=ylj3S?~!d(Mnf`:Ԟ&j02㌂ te"VHvQCVLGy<a:|Ht7P5qt,q!>TO`Њx -h-\#4ۊIśu ʢfNJտ9+B̤xo}>K[N;bę̑*QyW^H{_'Vb1A.7T&Ő'9- ȿeriIuƹ [\>{`^jY(7  e'&Cc\93[81޼ T}KfF3g6WᄦO+JGYe^8!*7e[/IͶ, rv;zrtf!,\0z?~BRBC[V3/W[%zJrN U <-yyYhu}śbX';֥(b).}.1CK-3ڶ:p)p]dpt"™79_pUeI8hzNlsc!$ 9/pn-pnL9@6~]/6_}mY6=1,obr3xZwSǘE#G⊲ /WBVekg\H|y%< ,F?k3XvKpKpeK/8첏P \IKgJ8p9H(P'o h2^iWk:{imo7ڛؙ,rӓ&vVXy7 U5u.FO _S,`Bɀ(apW5< FlQ̐o=ssu`2I!q /'K\r:<r癏HX֐u0)3bUMY?ɽ˹^oz^Y.<%,b /NGeziVkgYHC~$=cK{yPeaaFe0JFU>;^aE C pK#BR-こo'N |8 88p&p0.p6= ?.~ \ 9p9 5u[n~   Be>]?$> l|,$?ll LlLv&;^tv<{Ӏ I"5ÑHpf8R3|1`NI#(lVkŀFڔmj*-~8,6:FGs7.)wTMG;RT.cNm߹bli)wcO֠}Bi\kr&J7˖q4@bWX &kk`qYpc҃qU ᔘӿI,`ݨBh設qC<3mQxD~|7s[3} 4>(O Q t{xl ^ JMI)2+T'a1CZqbO-%rL\ +z}ۥv-)-nݻMHO+ˢs7ص=ɝw\\=+\F-Fi܀ E2o\^Tqŕg%%ҾGl5R7Ƹ︱je]wO|`^۵˄/7ab lŽjLxugwd~/hz*y5:^Jw(ڬlVoŎ_7w k?kGW~5鈟hy{/} c/hu[cg7{{)=8m5LѦ5˟y]k_G؊{p;zN7ձ-]zkӻ-^Zv6n+^u휿z&MO?ɟͮe[mU:tO;Yml{wT*߶ϓ^ 1{C4zqϒ SuO/6ֵڇK7TR-}41زX B_L_*zb;vH!_U1W2ʲjTG,9K+ 42tA\/'˵9:≣o:dLozjVr}?*z\YQsaeR8ϯ+}Rhwp+\t\57vYwytKqcz}GW\1Ӫҝiޡ^iSOj3䶏?,'zoDƦwo_նyb侘Ot^WΈlhMW;až{}8SM+_/ɿ8#ZAe'9\lFJ>:X4@iԟb.nUM^n*CUksˁK[eMk.GTM;ʾp]ߤiq,zC|7W5Z7V%UԸF7&R6>Zդv)718ʛo.Ai+{W\[Bu 1i1)wq5P )JHx߾ P iq^%xQγ dg9PPg?Է/m'?{Ox_xq٪O}#k_bUt縧,Jzۏ~]3kz5CK~\zqߊbݏ~CR?k\2+#l12+^*;uw\WboRjW~XiʳMM?fwŰ%ݳO91wl1k^z/⋡߻xZV~NcMyLn.8]WNhMrLj&[6T::;!soؾBU/ G" BOюl^-7.L*I [S+STEJu!^!Da7Ҏ[|ʻ w|PoäfL o0%W.+ .F`iakb*L"FdZ0_GDTaNpH#賂ιc#51Ц(E}ɨ /'(ĮO_uy;IJ9RŪ@PIi+ڨK?ci*/9[n ކ:鉒9F4"*5l1EZW%];[O׫hymmjOL96ƥbWCt ] O~k)p+M0eA,nȣ9Ѭ{?S ); #"@!$CCrxĂ#+;ȶAFbI79}Nwq.7Tdv>sB J#OhP5I3{|]?HP9mw{(MZΡ{TWgZγAm!UV[fY9c.kqs; 9A@ [dr)ۃ0 Rk',Xkgܜ lB!{myhcR}hc͑"k n]ؠ5rS-jOܹlߏN2P9GwMcb^gQYJPڄ {vo9WȴU/|*@*8Q(mڼ݆2/,'`ᢲw:8Rzhj'{D΁ezb ")Y^fiOK4bs2uiԏ/yb,vdR"6(r>Lsn{<",4mhJ5?\pfW`gL z*8ܞ 0jBy,EEsEۧ쐚!gbsI}3ITj)\ l9OoPK<VP4267s$ŇURqs,M1g`r*zh.ӕn]`Kދ]P.k2'j)6yP9|ɬb/4]괣p") \N(l?EB-={AL[[.I}G>Hn-2XB2 bTltcCRg}ΖoAd!G6qqup$F7ɢs)#R2PDbɇYKژ x)SPK!fKZ rۻe7􎙿9_SߜXR-F#m|س OqfhoZ}~fwF/Lu?ٯۨͬ^Ɖb1d6)Yr5fCwOFT*u{!RPE8}c)se*E/͈ Atx I3|J+x`~O9n꼮QLi'nH.ɸO!H ߏ#c!u,umLEGL|gbNPGPE%0,uԝyB-YO12y\maDA !ɕOG b^>3mp* K!n mR 7 dմ<uʑWCE4͠Қ09&u鉛/r,%cEސu!'t66zB߹ᕣf{Cf^-%!Y3\ڦ¯sGuʔI z~}FEU _e=y'{;9&c^,H`Xt5uĊ7r@< ' endstream endobj 45 0 obj << /BaseFont /CIDFont+F4 /DescendantFonts [ << /BaseFont /CIDFont+F4 /CIDSystemInfo << /Ordering 38 0 R /Registry 39 0 R /Supplement 0 >> /CIDToGIDMap /Identity /FontDescriptor << /Ascent 905 /CapHeight 716 /Descent -211 /Flags 6 /FontBBox 40 0 R /FontFile2 42 0 R /FontName /CIDFont+F4 /ItalicAngle 0 /StemV 41 0 R /Type /FontDescriptor >> /Subtype /CIDFontType2 /Type /Font /W 43 0 R >> ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 44 0 R /Type /Font >> endobj 46 0 obj << /Filter /FlateDecode /Length 2126 >> stream x[ˮ6 +Ճd`p\t7]h,:&&ӬĖi:$i3`h&c/78c|;{/z_~yGKI&/w|`m)Ml/s|v/뒯|{?iq=ձ}8]{[y~}>%U ЧFZRȒOɢ85],ڋ9Zp&Mymcbk}d0gL}¥ulS3fNֵ>wEzֈ͒y|ksH߇?̼ dH`bb›B TZ'.͎WEMGՎ_Lk<߸kzwڥ<ϼh!my~(" ??iV|w}!Xvj_c-s?mضf_NG%O=#`+V>{<|~Gq$0o ۡ/[s{Yjv~ε[~,o@?}k}M^Y˧|7C #ހFOBK.%6s3>SӡgN)?0ɚsKN 5mS(Z0dɧ:[zVSlLN7CF:˼ x $2ƙ9 ^Jv4%U0Ee\2.f3E@y)mO$I.̈q08Ai֔;eY/\tPk!HrIA<'Ad#HAy`@q5sTҢx36t |-W_4P{mUT  WkVN pe.I ݕ`3ar '{$1uEȺ lbpW8Ʒi!@|eZ9P@zJ5>gԩ~թ $`as4 NJ1Y=!X^ru2;=4Ah[uAok{CSBK,WAا.\cöVٛ!⠫@$4$|-z.W\ !tI,h@mq.1jEMX]3Q?(AXFv>uǪ836.,#;<娟w7agS ]w 8%C z$zZAg) kp[5@.E/T\PTz6o+ xw]4`ɵfF!EB&G Y:7r;aJp?6uSyc%NkcSv֘IdW@\5B[9J5vRvՍ, W9e66ZIahaXxdD"I#T(*"F‡TJ>t 9m;Ԭ>eGhbm>˙Hkb#Ǚ.8I5;BX12W?8qOyq| ڷ:9BX:*hY 7.&`ct{ ^wB_!D5Cr uvNW,ΩK*p&@}b>b,? y{=3W hԴ;}Y DPTxv/m Xsi#۴N.QH NGH:G +L3{8 sY=@2+5&0BtSB.!rՌeYXzmFB>u~u=!m7z2zlQ,fvЛ6qRLcgXA-]j  ;>JZrH: ®=ulǴu F"9s09 2wkRɻBKoLZ 5мS)r2ƽkݕ+!%m:n endstream endobj 47 0 obj << /Font << /F1 27 0 R /F2 45 0 R >> >> endobj 37 0 obj << /Contents [ 46 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 47 0 R /Rotate 0 /Type /Page >> endobj 49 0 obj << /Filter /FlateDecode /Length 2755 >> stream x]ˎ:+UE  $bvz7d1ŐN*EɶΪeI|N=ĨjPFsoSt6_6jkj7bsۗ?7zwo0tac}󧟬=dzŲGb=}ecހ1@;Dsg2 ! 9^2#($)*`z)BC:/9|>7kאyg]|fpḢ?7`ad?lHZY#m+{DF}UKG/^xy;nFIC,;;Q{ 皎j: .4S1 BU0Y͙ʜT9[~ @7 1\kws]֌w>^fdʏ;̸|#wu5WS7kPBb;0&­h'p.MccܩgS=vKÐ* @;ɻ>fSPfGr׹ L=l TFLgTt9YI3±daO&3͎blQ@jN?z> >*] ~qTyM!9˔&䣊Ag$8 M5gd!-~}h*? !N9{p-$6ap>Z3'Nڗ+OQrͥؓ ֊CuD R uN 0L;*rdts`f@%Mr#@vɚZ| *.{ *#9J>yւ"P$x5QZvH]mrY] 94 ֵY(cQYLF!)39nrrO>?a| w)IKM!m hS݃"wrip;},-.p4gSluaۍ'B}=}L^&D^qk+{-Hy/CO #- @:-vDv T3yXD -3E*؎~yr A*l̩ߌN դ"ZyQPU6d86|LK3ֱr-vno NywJ`#)1E/ 01^fR%z,;aİcG(ǭUHJDĐ9 ʹKL8YC:md&AJ=9s ]꤭S8fGqD4\gHLTeP [-e8 h'c1d鄽R`QYjd%n{tsgblcO.ʢ`kAbعvT%Q2*p\r]Yk)rثK{!0#TuY F rN[):q/x0ի% -d^*_~j"`qFPSAFns~!1q͌4H@$M.hc= j|#2 w}19>2a suF3UUK޸.1xo{ORYZrOGF 6ypF4[AabgB#ӢxלBY?,QϿ{Q?Ԧh`b-Yᨣ$@1 !#q-`va]/c[7y ) e7qd́ ٨KeR栅zyQcߢ@\]r8[YU0^)42aFNPD S5n`\]`FBfveq˿{q铎.+ wYzt9<=**XI7~0_2`ӍJ>ߦ[xqu4\͍nV3FClj!Cއ2VvjF=|脿L9׾hiQ765|2} zַ " wI+ߗ`)/q^5(lznP0a) ^^K$oy pmRJpa$H{2{pYr;mZ/TԀ 8v|;wdpxdyʘYXtVb3bZE]1Ro ; 'He5`e.V?T Prtz[86P-n:[WD{fU(*L>KW6tu9=lq>=6%i\Maؿ:/t-`З/ N-(g!;no֗/C{J "@8Hz9Ϸ^/Øtܘ.gI3g88Hב!msh#CgrU>"ltX:h`ҹ*MWoޚK< ȀnSi`|BMpXLCȾ1#GeWƀ189W[i ?@ endstream endobj 50 0 obj << /Font << /F1 27 0 R /F2 45 0 R >> >> endobj 48 0 obj << /Contents [ 49 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 50 0 R /Rotate 0 /Type /Page >> endobj 52 0 obj << /Filter /FlateDecode /Length 1771 >> stream x[=o$7 WD_4acp TW"kO#λH<7'29wvs]I~s)zvj?zߧ'w~~ɟ}\q?R>3,_Or),w)6Y|($M۳/rH??s| 3*燵}&krf!)oӗ1vZh՟ա1tnvЬ\F}>/\`CgiGj@+ ]i3m P0xq2v 2628X$Thv[ d3*-'!C:w۞`!ၸ ţC][MM"vb#,eDZi!V*#҂A^c%BB(5 "_|Ż+\U@,7,Wd/zs :a{"ZYmc @iK3m4AcQBzZ.JlǠIaH0PߩkDC])V4ZYbIM2w"P7@ *osM 5-h(_E)g|e o2m~TmV GpbWPh@6$8$> >> endobj 51 0 obj << /Contents [ 52 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 53 0 R /Rotate 0 /Type /Page >> endobj 55 0 obj (Identity) endobj 56 0 obj (Adobe) endobj 59 0 obj << /Filter /FlateDecode /Length 66630 /Length1 340500 /Type /Stream >> stream x] @TνwVfa_D" *.J@,S6+ze=F-+_U_3_;`wsws w@( VLO nig~Vٓf<ݝC@- IrfOP0h2Hk훚dHt1~ss&>+P w笜1/CA鹎&aQ9'}F]* LhIaA0?:EKdG0Kv e'~j7 /X^j-q6sY {dӃe%_ߺ~awQK /߄򒒺Bq}KJhnHN`BiꚒjqGib{`}a0}S0Q3Ij_}>?Q;k00y&L?+hߟa5YaAiX.(%uw1>j(Q'$BC]pṨc3&ʀG7&Ɇq DQT1ת$VOV+P a7M譇. 䞊dH9u8 0i "H@)pppt 8-0 rCn;YcHGNg!I,$1Q-A:t0pt~Y_m9ډ#^ B^ DBE"S.ɨJ3 f> XZƨ/ؔ_H?4_ `BHC!D]T(pF@HB "Hp D!@4  F:bAh< A:b&@DG:$HPC2$"Ð@? IHS (t4@:0R(gh~a R'"MoaC:t4H3! $F'Da*d H3:&! S΂H`ҳl4f"Bz41e> 蹐tQp=@\ \E|%pR8_2ZPHAP%PJVBs2@9X|iT gt),A *p!T#] ]EPbU>?Aҕ'!.Dz),Gz WEp% UHWJ*k`5k1z=\\7H•HobfJn5HoE!kØz H`NXt܄/pp ҍ=g篌ap;Mp=p҇n#}H{>EnaWޅF؄t+<6a3&xvx xNxRށ}  HFV!}v6H_&/2lWH_'O*oO#}v! }UބsH߄?-x/"@oxH߀ëH?=^яua/H?*'~ o"=o! F9_;k%C[_ûH{HH  >D-|;?+p>AGS??gH>W^ H6#(p)̧f>1>̧=3=11ӿe>ӿa>ӿa>ӿa>ӿa>?>?̧̧g>}?1w>}鿫O?|ӏ0~#̧>OO>t'>|%{ABǓ@|gptkXۉ A` 5g3&46qF?9-,p&/nI<~ZßptSOj` ڋѭN>-C͜zIx?GD'6-&*gL7_;q!h888z첅>9#ZzI$l3pt =kf9ۉ A :]?*M͜~?N9 F͜sWWq5_%9)'GWAu+,GF{?[pzIACw3ϫ$g3شX  u+̟ts KuMx^]%yƊ?! ]14888888lГ)#8φKu32< t3A^OkJrtStb]H~CO6h'dsppL͜3 7%yql?!]~YR9"ۉl8$^ &S>fPr심_;U@*H888888'?n'γ 888z"'g3'>L_J}#f8):|q:Hghg+S#lAԳޫуЉMKpttS!zn~ҳ888z`tm?7%y%q tÁ aГ`n~ҳᔽ$^jgWWE{Ix$G7E'~=8 󀃃$[cD;q '/pppV$ǀcb%yp=j='uGWAT ' ۉl8c$^׷_f$IyH*FVG = >ѭ18φs0.Uü$$+n KǓaLppppppt? ptk g0$gWW<%XqtStGW8J~= V֘Ng9![1m( #ڑnfP/Ɗ·IGW4m=ppppppt?CO/ptki'γCIpppVl!!?-R$/Ƀd/g.GB'~=8 M0 z[v<N0)! =9K`U?> 8 yM= ѭQN緛&yI/M|}n<8KOzpt/GXInv<4K3ס${}cC:zl(بw&9!UE85Wׯ}O"Bʺ6󈈽x1'yIxjj sǝ:vQ)#'' KL;d1Gʎ~}#}mVd4u(ˈ,]1.)&jx*GbDaQi\rK&{tb6)jJ'%˩'gDɮ=Qr{vקGˮC" rFhY"r+siٚtnybs|l1 냜+$z G#d"ъrGg¢i \btFa+켌>q.2(j &|cYȊq' Zs]Z |Z_, YihO̻>⚌rk\%vj$ љk2Јrd,M"?E"e*}%Q4`2EM*[&| G6;+!PPn2DS8&[$ m[?&I;&',@SSmt`cFihY'Tc[o kFWG+j"|OT!OO{ڽ 44xS_Bhg7 əfo,<p3`f{&?pZٖdbŊ\EnDJē1 fYӻ!q4C KJ.]a? 0]@,+מNjA0/a}XZN}à!Ib(K+Za8FА䐟W7|hn%=)^! ST!'E3$dMZfsh֑+YF Q!F@0^[, 3~,|@21KcLc-Lf)?NZ]i&q^u7` _3* bA6^*6 {m 5X58@\iP-.xލ<VA h0(i&aO) Ɔ7lf ,YhŖ4Iy-CB5 8\CԮA`%Ohjb +́2ܒ,FE[xe{55Tv {I!"p7rn»Boa;pn0 /9F w6Xicb41!}4?8)-ZxVxq;abK0;znN:ąDžm0 @4xT)+SxTx7ᑆp}1f ̏ u }ifGc/ېB3Y۰Svl k)hgsIx9E$مЁ-EO'kkWQlmnd\jR귌/\0xs52VXEx^XLx.CoRըQL5Q5F5+OQQL5 P5 oj0,B,bYY4P# 55D 'p5dNpi$F"j$F"HDDHDD4dԐQCF iȨ!2ӐQCF iQÎv԰3 ;jQÎvagS'؏Qc?jgQc?jGLc?jG²-޴Qe/ELe/EEWkz3f%\'݅PwbǓPÅ.p1 jPÅ.B jF؈Qc#؈Qc#jldǓjt~PvkKHZa5Up+a [Xx1lbEp) W@ A 1?ցH)ify>Uxލ'$=q0_52mxAaA߭L^~^VGѵB ϸp,w8x ; !!O! !7!i&a!{Čs3%f8L7l;hh";`3Ãxns♂gxF`qC0}N<L`53: Vy+h9 1hj8 .p6HwEsaXSt8љƶ 5׵~ZNks|We\Ї*_] ?oT-fdx:=}ʒha2HLf2MdM}AuLILgB Kct3ҷ2K  A-11,ULh*% f9$ױJhI%I2%45M wA&7dB,iV9 *׵KB]Ee4,,qGVF[=aQ[์ܼ-9K9eD7OK*OYydJ3ˣeOkr||d.FGgSw@|gp>п['8i| _vr.%E yWpQ$T3݌$ gu̅bzALYT+3#?8#P#^F7$8%4.hnnnn)0p'q4E|?#Mp({T0C5A'h-\.Em$΍oÕ`H7b>cM؃F)F=v(1'.Z[aCN`^?9P%,{`FϚ.@0'^PѺOq3QB$1 oիE.Zb;B#oQrZ.(W˚+SZjHj+_g0{ Yoh;@'5`6HŒz] $1`B #GSg8 㑷dXb__4_eqׯN~Z"y9*U>GtG|$NS+Y[H>fAy>Fݜ`oM"Yl-IrDZ?U MGDRsA8"2HLf$iaJ-履Q):E!ώwɃt̓BJnHe!#\vz/&}ưnL>ф9VG921RA'{pm<,'4M7n}Gb/qqIĄD : SkO a:J74?jh( bqVLs=QٿG5*gF%`~2NbB3nn"O3͇?hCݎ_C/~Δ.e%=qٳTUr^:w9E>,M6Fm3F9li˴ev_-7>6womMυ Ötuؕ}@זc]n[gMg@k GIA@!0hV-h΁`[[}nԻ{("G%F QA2.YXy:1+jt^?tx>̌GSG'̿C\;'n7P\0Z-)88(Po  QQ~8sGĉ6jPV?L56vMykQ~1K'bԕ5L?yt8s@ބZtC*_HCZg>ߜ?'xNh~mM?LVƈ-cM-Sn7靥'/s`5Ї~c?i 뛗 1LGRg:Ci-紖#gON -džGKͿmx/6\JŽ'(Wm877F\@ _7O+xiz#&ѕubV/MEѢXY$,s6y7ݔh6IUw otyxAXM6Z7~; 9UBj$p GG \!9ܼ-Q0?Lȑhmޏll&.N/?gYc?/NдQvf0W'A+Y:m-՟~rha$mw'8|x:J~Le2PLvl ! }d, &w?PrrB!0CI %Q'u86!ȏqbrR?!r_ m7dJaJr>/DG\7{IN)tqe3ŷ?GsVdЈyF捋 }CտU8ѧ߆wH5@C8CLrDb3bm#' {v-آCtM ~"]wlW'* \70{՜s| DyѲ%8$>mHȹR'dۗC~5G F*~jICcMukapsVp aD%b ,Ȫ1,דEz$BOfs$$L ~~_XcM#[Ll8hض̘CxCAĔ?"x 'li"77,)I"5Lna:r~Ĕha/7#~}R'$ć}2z%΄@@#0I a k Z!o$ $LM )ѧOYUsSʓw4okrv<0<`9qciK5S :/9)!RP?̷M:y( $9㗶99!: ?~{?W,M?~?~tG= 3;W?~rį@{ξACyf&Q^o'x PaK Po5:[mnrpޯH4^G/9#ׁ%׃%hy1Bho [ BP6}ç$bY <":7Gx #*x}Z?r"xUz1M%x6pPUjWU^ʫWy,xVUj`=6]ibU[Yhb/xOl5m}Nwh70Xi>jjm6Umm ؏5^9mqGFEh<3ӸT>Kd|_֧jNi>WSV7jU,[Z_2$A" D\( g@TYˡLDyJ 1WҠ1n!A-J0,KV<&cK`bWbrc1zG|0r(BjV)G>H)P9TcZ-rhEXK;2WʹDPQq2[0x2KxQͧJkJǫET*żn Tr2_E?`uʙ^%X_R,ZQY;k1گӃ-SW`:4"w+ Y(f%:/f+=6R' ֖Aսc0YӒ̑~K嬄Jy7,6jؕr6rlQأǎuzK5kRV:2˙MUy՝UEOTWUkz%+Z-r)Bw5kLUǮQl7u:Rj)!#cSb):㞏jqrڶ@eNElgeZKٜ`=ڞT0n5V]éڶLp6=kc5-QRs6{CyT{^JUU[UkvgƩДߝh^R뙖3\r li}K+U JGuBkXv& bs $Q<2-c1[%W 1Zh!p_K<oQ뱘6Y:6yLw!E{Ԕ5B[/ZF2gJYseL637_)̗M'geegȳ)3O)3'N>e$y͜i,e5%#f6##{d&L>%7?NΜ;晉YiٹS&Ξ-gΚŧc3R2fdb19gr鬨XlVL+O5==#'d`&LPFM6eF6#mRӚddZΙ4;eLڌff3%'#NN˞C = DY,ԛBM-{&윌gMǼrrޟ;~ɴRĊ0_zŖ;1Lux[|קTy<ϟǟgL^=\?sޜ?~6>ϟ| R$ekH_b:f2 g+Y0zǭW $4N+MFJ$t4M4aKZ5ǫ Q赽VE@x8?!5)bv,k +i'$&T ʐpp;)܉aBn[~BD?  f"?Itq%/ QyE:K :ȯV t7K NZҭȯ%%놃KA~n,t =L}џ|>|!_G~~A*_}@ Ѱw@0N0^ OFlqq1oM@4]ccc`d<>!yg}v#+ȿ|D+C_!?O>hy_D <IJ"/YG X~ځXa Zís?z۰\۳gA= 771 1}#}_!ALs;s؎#.%:޾c._1۱ڱw:?o=/m F:m5b?ha\#Xh,BZjFԸ / x)\f ˍW"E~->wbI@&$2A̶#}ZE3j0k3Ԋ6Q;k1Aq' k \.\XS֔, u D530ձG u& +_s!8ODC =ű{I aNh,Yɵqmv3' /'fN1$:r9YG#[n$!P ҅\X$|(3+ŧăR4YZ*])6I/KꌺfCp1xqGF)YsV}pdK̲޲ɲ }ۖ,Gzk5:Қic-.^i]odf}3l\c+-]i[odf{3_ooHL9K}]w o~{=u=>Ҟic//_i_odf3G)?xcfo3~ğ -UJGm%/洒(/;;OI߼%qrH>['e`Zg>dV0kG'hp1CpVQk\-«Y:@%j7kZ~۴pfj-\y5Zy)<ZH ӾkV-~ۘ]+hp+MV ]ZW m/up.h7uBoy%Rp[.\5cmL i}\ӽ([65Z䯅PgYPn>yxUwY+J] m!|մWŒjYT /B հbT%Z-—԰R|M VT]Wk>5 I Wj᫬a*eOj`\ kfN.| _3p5?InL SIVmFh#vjF~fzRj3en5\eZ˖j[Vh[H8XӻXB+gjj*] R՚Zݠiޣm#]L9Q:i^w^Z^Yzm^YZxޥO7j>FݸدOB:saޯWhi4+f;ZY oy6oѬ{i]i{svVuڌUz#K=.:X>xN [>| 0J$b!$ qd8I%d:v~w<:l}øى{Wq>WYp8 ^H뵚矵tvv wh-C5SĆj.m^ܝGi9=ȸW_wާji~hj-X FfC<~Xk-Qm~<1ms9PߢջAkgfmjU[j76mvD /·԰IPo6wOlRÝZ;'ۧfzg^n#F>j}koG[~n߱3qWj|fz<_q sPQ/{v\/jyqjER/j=RKKKK@:c\{˚7}E[^Y 2JsWRFmdFFFk#m#'Ǵm666rnyn\F.k#W+ȫȗ״׶׷7j#ol#F`ys6mdW|yUo6![6o_{o{>߽[~/[~~ 򇒷o?'g1+Axp?c7wpSAJSϣ{C8)F%{guOԲUϞ;9c|ɬ;SOIsߧ];oq(}q423(EY LQF99qo+]bod1;=ә6Ұ [o1wk՝_uQI`{߼ڰ]Y@O[{gԾAyM٭S3u@9<z u 8N88~}powȡ|bƙ~:h&V.NU)h'{ХUUP"ᑞjuc-;PA/;'vHx=;W zOlWU1^8/xW@]ݎdzxϐVL8o( xGvekΓy3֯ yzW?rXg1۠kZ?(wZADVϰ ֹ±4q>st5XQ'̞mHmݥuK;AGў5hV#uU_Qnbi?)Z<ĽaLWg&٤v5e{\V#o،iξPk^Lju宛{{b#9ҲSk]Ǟ?gHlOxl$ ؖV{;ksVpۼS5s,wL}KsRn{Zv3hj;N,e+]՞\k6IoӅuee/tkD=q0Zv'ܙw }~;1ޟ(`$m9F?U=s>yJw\߁fmwLl$hz:z3M] }V&u+zL7u~֧9uy}Vw6V9v:{1k㞅ޏ{FPsް;Nq O羽GZ;&X[^)^Gb9ÞTЋ}K)#F;ه= ;CSN?~гKiMսKoO0nOkkx"N8sbcSZa3^t=X{ gOgmtx?A8'H!aT^#Sw[hVG;P>z*oXA816oaG퀲=*oXWD7:]}v \Г#y%eCV ^.{i}_G~ƿwCc=.A϶z|dg~W^9uϩƎg4= y(3?w3Jq:N<@@!ah0fC>̅ynxF;l^K"X/qHKH!ߑ#?tA0 &,* !B!Bp) bB0TH #a0F+ g & B0Y*L?>fvv& 1"bHRcDD#RD#biDi)Hc.RJ)\C)"R҈H\)4FL>g6D~qy3gΜ<9y<]LC.Gwcqr<^_ C$ayVO'SGcz%}^?OןgO:i!62z_zaǯw E IppQD 7EPȸMx):E}B(/|)Ds)^^ČG"HcaPl7(!"I;If~:USd)5I#|sH%|tQ uW8E5"oHP^'( Pץ %%)"&yWz8I~_z?I9I """#GXX 2S".EECSD;E?j(v+)=?EW\EQ,*hNx"Ә*q[-%QEcxxb'Dӵ4,&خ+&'OGi׊4&q@T_,DF9`xrQCIص_@l@|Q^ʃ)H4F&&9~p>LF΃/?O2#/9-_@N*rbr⋟ʉ/wwP+2".ʌ/PfjK d%GWt}:.[(_z{GNRWĽ"*_ARC,՝cHlE^` C՛K0 Kj1ZjVOJ-z6I+iIV~a8J߯iN_?-3o_,/p`b-1EisB'Bh47 ȳhBéL XPAPE0ɂ50]KeӮ< ,#XAk輞`VO{`u {zgx$k>^փD蜝vo7mHO A# ḧ^2޼^ nnzyOz{4 ŴlD'HtPmVkcmVMtfFmrY[E{ [Mn6vhhGۯq)e8]\$^]t_;t{PyQVsz>`! \0JQu=u% iGȐF4 }gl}>__/%}M-t`K۠t}m ç0$({0 nm`7\;+*|U,6iǩ?6 yQmo6ٷ+|+zo" [-ء}〃h;;ɠg<_h* ^\ރc0z(ݱ.eXO{O`Oz?݂:177#!]3,`v*Al`o#9&bԿǿ;]1L4CW kq} C*V"-濘㕖뀮m(^$캟O!  fH;J 0h)D3L#@?3T`.]'XD"cف@Vht.-${/u`;Mg(m Ba py{!p"p:pN;h tw#5S[ aAZ08X, i#c` &j:\\\LeiqЕ)r]p1 n n >z>8+ UM3 mDhVmQt=h84TL`FpNhfpAh6tϐ+Do@h!AT:k#CЖ.-ګec-te8Ay3A];8^S뤰ϰ;gz+S~Q~>N}pB?E<@axpW + k;0R9'\.cu Ơ'c pexamg^3kp~.\Ϡs̀6.%Ox8j Jᡊp24p_yL4>}bcv`ܵ %뀧m/y'\֡-!3uvId~O=WZ.^"M;ifbfy 0ľ'Bv ?HKA( NvY:ŝD"ΏH 8,cQ"NMm\j-+6ǧcZ `#jvn]|ayNAiwlcI.EAN N~AAc,#gei?*'u}9u.Wcėn^NmTj){LڹmVڡzkLƶ@ m!|5r}m\4Ag_|W eV¾Cq8X/# 1 3Ie2wi.).;tW$IiAHøS%G {[eNV(8Ҥe\K3}]ױ65Dakaa BDGr}c)D`$?dkϑй n.$_ B>x#AG3E=Ɣ'nw8- b9}_~#yrxvCK? AD;_2l;8b dD D@_́6l)~.Ri67vrY\Łvc GVy Ϡvηl^ &CfUD!>%NXDiԩDkt}_!8/='i*m&N_"M=_@|~.B\%Ijt|$@?ylm.+Mj5Ƚl)䜱$|8>d|QcS=R}<+ jo!3s.5?a(F/ۂ(bz74|xrs#K:0գl?eh=+!h;Z:"nuH<昍x5>qC }y: /w92i>FK M&eO~\^ށ9 +8cF>ZF&l~;A?Lk1A[NV̰g;K%s֦ /=Ή5@ pY/yNDghSWDGY3UsL/6ȟB](rQ.o8%FIC>ix4P%QRF@#[C4'KG|uf8F_ (,8 tjWqyX4gi״RGYtGPI0` ktI0ۂIo-lOo`L,%h"/O^G(dMzcgGk:y^|T! 8|{q95#My-OtcuvNt孽r V[Zv-XƛOd^ک\bR(>k2gok bگ*{an/9yG"4Ө{G"?ibTRRh|XT/|}7n E^+NЬ-mۊl_5>v^jM^Zb&uzsJ ҵoy7ހx2i[R]m#te5yl'YT/2x51\OCLBZ%w r/qW8.^bLzPB'ȇےkX'l8+{{+뷷rhNUH-̈́vc\n j XEVe}_k[fmF@wײop)ˇG)/5pdkvd9VlmE<\P,O[ ',9M`fJ^hJ6(5|s38H.))L9 9&?$'p5Z= `wE.6q̲T{^_dAg;K ΙghZaYuC\(!tTr; Q{:wS{:*_>w;1N_ %eԯ,&X&o^]C3l"JےKO^V `!kfJfM{hU?ր+K5VUY1vKZ@$~JIx00X|ri~\G2H.5*zFؤ ڟgHWWhK8Y!*p>uNvֈZgEQ\\.yN\(zNyNJŃZ@_:}U#>(K>|YC^V+鸏zo^'o?H~G<$Oŗ)dxAju#]2UDEyC){%rR@ (6)lkB6[+mR4^ݥ%ݯơJ;\#ˑ-8rJ+]Ϻv\dM29kk|maV׻ 򝮏!Q㒟 z\{|oI^&?2Lm-K#kiWkW˭}j!' ']A.4T/A6A_ Z0’)%(' kdj1uҠ`̣*lˮ"Xk]6vaʘ lx pj ZPJ:/\曼 f uyYHY> N N N   'zQp)-u-NE]9< bX;%p('ƙ#(gxtpgqbӌ"<#<3)5#M#bkɞ}]Щ`e5r,r29).%EF-H{dTD΄QE1јQ͎7 T{d0GRX"ZD;L#04j#M::ԨRFˉ_>d숎 hmNNV&DFB F/$zItykIUWt\vatst[*iY]Ԣ0?\ /D/s[b:EHp "<.?lK,dcX2] A!sX,$zTlt(6.VI= 61Tz aE*b3bb3 ώ͍TآRDzZbcM`lc\Ll InZbMXlW,DlQ@٠,KF̾D%ǎNNsS뎇&N=v.֖3˓hGq e( F$#aD2x|Ed;!c3]%p2*cLJ3*2B}3&Q̘jdP?̟QKgNF},2d XQ96cqƲ_Jνk2g26el|#%4"cgݜc3e$5XI6Q!|FQL6`[p,WCcx03Ƴ"EGJB `9YT|`zvK|(E~K|x'Oˍ$P\؝q{FaV_iFAlfndQ| t!ӱYF{9kDb1EҬ򌂬ыY&dM%ڃbM;3hhy..kzͪ㑛5+e疬F1I&h,,ka֒P_c_ym<^e.eaXZ[C 6dm& 7dm*%m Y;dm10i>8@S:u*؆q] A*e HaZhufƊ,![0Vd;uAp/EZ-7jP0z<;֢GFd.'唡jdyqEѢQF=DOE٣iLNǣ)2{T)ӲgǶdψr.@1\Un˞=׈HgF+W|ESKM_<)81kIv-n HjZ,.{#yuEzcv3:{{(uY,)1Jh]TBg>}:\|mF#ݖݑ9;81GqD N" 3+LF"oj54OOOvnO_!u!}?P#ouxnyx.;|GՠLw7 gV]A|O.)/^\6NY׃s&73΃Uh֎  w ޳hE'%܈gWA}4z[Ѣvh.E-@|. }3ld&cey : o1{g}GI-YC?$ß5Q =`:x=lG2&X·y|d~#ax59 {!s|ÌI.x/j9X_:HVBC5,fU:ڇvw?:X >{!nO s(v|v[36ɇVmdA,ͯ_2EOՍ<ڲ ~taδ̳ʲf`oHo'!_nC~Ỷp#{aZy eQ|F{oq~7:McE㕰eJXx1ff?ɗ\1 3M>xp*C] @†fn2*6m,Ozm!<@T?s~G5DYvՠE@bO(@f#V52tԬn_'as-"#.8 6~ sUÑR|\ Yْm6H?g,/(sό z(4\}1mvҫ8 -zJ3[(<c?B_y9mƭBC{ H6 M_~xZE6w ?|{3zpʁ_C&oÌ0 e ^~p1cIsL!WB&ØC:bt v65=s zhrq k Y މmM'0oUd;pvB9ɳ}f1i6̪ qVkc '#ؐ1T56jm=/b1W3ME_Cځ2 s4p~.kX@<.VֿWٽDݺ4!VA, 72_5gspB~$1ഁ3Q[z/6||V8)o??]Z=[g N"qs$'яNm{kgf4K^E´\bFQ s b)G?F~'oG_K;{)O3[BB%/D1YTN;}o-Nd~SwK)llk~'mM'.hJ'se>0gEsmU^7OAT%hK囫?xы[&$ǿ\xQ1M 5ˢX<-#bw(q8Eh-]>+G?G?q*UbjG11P(rbG11TT:nu;*Ń*Gx.^eGq~V\)KNI'H[J!0i$%IDi4M!͔fKs"i)?EZ-6J[.it@:L0:bGi:ZsFtHeEv~9" *G3o\BsVy6^ jf;PQ H)R-e+g[>)WVRR5꟔\C䋮>KS}:Rek7eG)^{[]e^z)[vkp> өsnLSz˔u>D3O!umkꜲ%uNٶjzl`ek+^O#c]=θ[*uQ]jzS2W1=[E%п~gQ>uω^Y5ݗ;[~k:_6R~H#=Y>6x}-ܷ]9d:uOq,!OzP_/O՛U]md}Mߩ/Yv~H?ʵY?-]0}N /K}||C||#}$3WyQf"DΗ8SԿ)i6ߌ:R}3}}s}{+{Gꝗ,|-[[y)UGʖ_|ڃC}?3lHO}}G=t'9_ï0w ,yğ9O+ he?_5O[/f}*$9_esoѿeeF<߿W˿ҿƿ޿ɿտÿۿ????oX ;зG.L˳=rg|Rz@IУ5П>1XSsvJʴu JSg^7y\#ָz_RsA9aMw.u͕X]3ۑ%CS};0"Pc)PfH=>LԥK9˞@C10$<}_@s`U\[7k{٘Ém]k"_xvc(ipPWq˷%~X?x?=o8꼟W~ҿ>Iw;mw=V~ ifiZy27Bf5dau;!dm6h+ s\FN?92? 6h8 ahnOSV߃;i9j:= ?L?_cl5'J*@}(u2_6@@χ=!:A4dlAOB^x~T\J!2l3ʢhzЅhQ-NDq!rD#R3ML;\e ~644aUп= ߄<>om |  2 33vx!؉4# ^R] t=h(FeХSz{&!>m:orAr;yh+fļ2A}=8,13th?oh9`g4a Aob! OaObxF<+Z:nX%:>'B-)p?i1pR&"Vub"]b/%}G qD;D7?ADJΣ@*Kt5\*ʤ1RTE$iT#J8HsH#>>`ܻO0Os;0S4GT>q{ݵ֓+X8W͸S&g>E<}Og[Og3S)TUߐhCw>&=~x>8%l9o:njm2U].sw~[f1x.cZm2\_ixr^1߶wGnF6AFoٳ6g_ 7mf6/s+pKR7Qw[+`Qh+m'-KA݌@K'0s\;a?zČ\1]N9 fNzd2ſ\oDl2Md oz&wb ߄RܭACŊl%aF,o.@-iK}jϷZjiJ2<9A1Fъy kd_V<S |K4F$W0kVZ=^hQ#F-AܝW3oUc-t>a5poaʁ%~[RE⇅aIcu,qpZ}ppw<2Ըs%u1D16bf{!Ӯh҈G=7k$9u=AAy)n5EwXYx:zUͪǪ {]2=AWc(D[NzY KD-|^Z>TiƔm8Koawu6Ut}=m8ݽL2:pgUvNדYg1} [uN:zuWl^xI.tT\Kĥmvxx]2U|:vh]O:_'zw|ii}^5V[ktIgճbeiP[I`+Ot~VoXLwXzw{{Z׭N!8O= /_9yߴosΗ?^]_͙2焷zթ~&dzN}No<>F_jYtAc_>_O&/?]E&5iu 8z ϐ< g*𕰤wa mw.ݑ AA e 8Mi h-8?Ue;G e!SAK7?9,|^$/8& I|՞+=Wk==`iiqwgs-EčZL.@H` rfUk 6l߇]iF); gdXϧ'4/(ĭz-H 3&߂q1\(ICRT.1A,UwcT'͒F-g XNG30MjueJ6H BK-yo1>;ħ+:+~λ_9k!}5 N+毎oZ7K,oy~sn=QAX罔u =7~Bs:1Ps.F"FyNy>ޝޝ@jQqemz^$d[ ZPdԂr<ւo< ZS&UL-G+IWKD'~Q0" L.읈olrrwC3 \5ZQ|q-Oڄ, }E?da yу*ϓz>N\M=㧞OouyLWa>}­;~"WȊS)?/Ȣ\NSD5LG?Sxq!87Xt hac:O$K_" )9%EHqI(|O&%!nRXIQ)*2 Q.eJN)K)G%JbG#c~R?qttGFq/ė iT'-KMRVH+:UAiRLu*ZZ-&Jk5⋴F_+&Iuai^LuQ(HKG#1US駢ZzCzC|IzSzSL~&L<.\ezlsι{ <1&IҤ1I'I$I$1cC$yz$&y$I IJ$! !$I$ɟ߽;)Yug^Ykm?{G#WU/r'֏kYg47M~ [W rtߦRKG5JwqwvS1*MEܣcKCysE҈1zĘZ M LC 5گ`nqҢR[8΍Q-iTMz#ViƺHQc*kSX>9hQ˼IBDXwFXmP/Q~]CwE^tj:V((}ꏏuO݆j咎bCT8>^Ǩ|q|q|NQy#xDuZ1f=jG=>^(Y|uL-^(EhG_ 6SK\(KԠxQ\X}#va웱ož(H Q%T4)Ԧ f0+ZK:VXk*MCUh,_y9Pk.R"?,zh5GiV;^g%KYL؃,@{LkBkxזzl8R.Qh}yhS݅ ִЧ=GjP&GUȇ Fih}4 mwo'@Xhq]`*~?o29JBs%` .Ѷ%_9-uRVDeEsKa@:u qۈcH-.RR|*aLp-` ^?ݝsm˩>7& X]p]K q0G|kexjh~Zx몣dQ/! P3;}}|=|=}|}{c0i=I>W%s1>&={GԷ|#ګg>Kzrʯ'۬z~mԢ7.U)ҢCNŤlohPq~5{$2n}z rնGڶF7u  u3mî}ӭ~L[=R=6uNl}AR_ҸDŽ EU=@qAp_'VP,E@cD[hU)f/@r -8%dGW2hAeкPӧ52)/E 2wQL=]bLMk?=LI鼧Y ZTiI3M/%z$m)%~EPohW o( /U/U/e/e/uɏiʫ%jU%Hp1j׳jbRv^89sb{-ƹ0U*N?]v}::}}}ACdI6%s9(S x$F$ ōN6M=>rJ|Lp'=?Ֆ%NE.qj uSqusJ-ǟZڇҜcCapoGS{e'e^Tý_o!hX!*T/#bg~="f-4ZO/:?~[H=MS{s--t`JP^pb:Ґ ;s* N̩VkLGSi4vZC\=ivёd84 HgL{SgYNjgpv;;T8剱 =qFv}"U<=5骶 OӌsXWE%&T.Ez8=Yt -#<J!!{h hYJZ[ 4 .1 wQzFU@xsb9&g4T!ZLMKх h\k_y3.&u9@ƗF3m*isi_ZDIJoukfN #ZWMkLi= U̵Mߙ2?U63_kLPuޓظ{L{tǴ\pо{uMk 2BtZGN= W.TRU;+@׋zt"Gڋ@=2h_R"zZFU!?Zsgj5KhGg Ƈ1xBm,u;T{EiCv 2Z|k | e_ ~.G=>MzuN{4HiTqVPYW_ gQ}iմ05ZZ~y}.ܝ U;9 g&BPɥM MMz @kp_upto7] T س2!*eYDw(piG|V9(VU/TgZYY"RK{Kb82IW)}C'k Jpd=V͜ұ_ʔܰɚTEj춂W=ߨyY +H5]rFUd'mjX?_%Uyi4tۓ5X k@qfT/-j+t^ y"jN_41%LKQnA3)?(_ (ڪrM 4kӼ8/TˑŚB(OJr]5P/"/էMF.:;k"2m@@;O#LmVkTmZӲty|vŚf4Vh,qti?北Ҋ`g6PeX J.2[?K^ 2V ȳ泧]_]_ݝl)57k!VR+N-jD.?r>O`;ؿzJNSNaG쏧`5]s _im{&$UtBx$tBN!͎82J Sb:#,ԘzY\tR?CTJ5%43NtG!Pȿ@)GRgjJ9G~'K'T7\A<q_N"~;j(8Z`4 .!x47D\O.2¯|#̷]|/]4 !D$d*h*Zh>TzP!EEh"G C]]=!Zѡҥghcc&bC狷b$bWJ+gcc ;NLж-YrQ"kZcs cm[̱7O _c {_Y#5VڢzUXk[^lv v\[@j+g،JK$~ԶRV[^mCw6j Lke]nږ/UAۀJXkVmGk?oZM1rQ$qҹ'CD{$]{nsYg1#ZJcQL`4:f46Ru+sm(ZHhMGPbGau>4@D: ;#,;R=:Epp.nwRX䞆Yqp䴞zk b8='B|)Ot)]~ !,+ubj 94Es{Q-Y䎍!Sd-<`̮>xD({SIRY!xz===~O#NU@}ƻG&'ѧ~X.xN.BQCa_bێd?srʘEIAJ0ڛg9`s5֘xe 2I2eTʡ+%F⟁&gr iw{UccدI;6 >B«ѬE5)-)i ~E j EݞIc[o2]G[|λo1<,FIS~_\m ax騯WJ8Bi/;}_#yzxzE6 {0$DoJCyih\4N{)ށhOȏiIbtZQ_$T~DV'j}$Ũ.Z%ju/(7MC8M$9UNΐs|\"r$r'yԲ+*oUXխVm0JP"03/8?noN"n7Y ^>jYK{ rļY{[z}cYgvE:7La0rB{bo({|^iU`gP&0Ǫ W@^qjoqx;%&_O%H_?8^zѿAOfQ9&@KN#c)YRȝS%)JưJ4Fi ƧfXeΛ!y`gI0*;4q~ ;"p w |`F8/c7ؑp1se4\fd;$ ؔR QKSJuOM/gZӝ> ǯlz Tz~;⻙ڬ=DQlFz, ͺ7c?0m˘c y"־7aZ 8^Z{^x h Z P TעǕF>-,4@R*0bYn'n(HCF% xN8ӽ兵cx/ZzUG ^/7>WP'0z5%QXK1[{w9ԫ.QƜ®F"\> TQsjORy#Ká :C룶g?\ss ]BE굶-O펨-be)? 8 4;HX Z^#e#ZJ<QAZdj:N-jV;N(t65QTJ0UkX(j -WR a8STjtaهE%.&}~!Ǻ@N2 0 &^D1ѪԺ52cbV"lG$A%fJp͝z} +kxTjhp&sZ3ޕl°d5E"ߵ'wij3jSZR%rڦZ۲Bd^UOR]#bN/ [OFj_9p>#MT>MGqzncI`7;=55kNoJwze8QKYۢԲYbavePu4 󐲂 sޗHK?/u1] O@䬰K<\ |~fwΒ:ݟ݋ݥ';%='ꘓkjrmKa$捓X- Ҩ1K}p Ɇb]ήcwvCϡ^]Fm4?iVߺp׃U%$WN֞e^`*$1j:$Û1% إ4빒vbUdRYK&ؽ?{ a Y;]Ȯb76։elvf$cc fҒXV-oR{4rF)bYw֗ `j (ݘ2]ͮe7κ,GbY,KD}oDW֓cCl{=)o'0 %`;:ʑYѩS,9 p4WSв]νwLi>ft8px쎝'={p) 5pN=!m/`y26lM>z;8w#G@dۓ z"enRpn}fwX1q+fS*kذ/q`K6t8pd߾)qNp12S|"xa=ЛCyٽ`}Fijt,1cOጳs$bN\륪RyH]N2 uWԺ$NroגDv&H?P)xA_ T*p!O c6I2$ˁӽb!,#ѻMjM]  o9Xxn0XNva1PRO*™bEj@A{]l[N xuOo;A +D^1!ɦ!9C.+&Kb V jkZY ^9qN4tZ8,g3ڙL,NUgo/7ݷܷѷw0S=nLZL ~VùF-1u _'D\Ӄ܏qM˔k_u"pC"Ir3R 0ӄSMò~!mAR4@l4eiF8$bmkHm%$#GR.ɤ̄=)=tXKp 0u ryamDŽ5M8ɄtxE;>f:Ƅuxe4y_]txU}al^m"\[?po\E"ד") o07n޺-E-{ [:!DLQlĦѬ>[Ėl=v=[E txkCRlij-76#ol…:leJU/.io7r&4鵮k &<; 1̄S zp)ѽW6ZTLf N; m 5 MnڭvtڙRnӆ6m} W1csۡvXB=o3֊cNvtSLMu2s@v5yj8[ZxNm*[tA#B2"c {"U\7-rY@ +,z,(ܮWzNi=y=^a7AJUO=SQz4~AbےY66@ Va7#GXE?Jމ,w4KXؔV;vwnv}VV7(Ϯ $hf6|?b˹P "kT^7MyKޖ<C>Xy"hȭ|7OO.1Eom~T8E&jVh&Zv!@1T<&ư*2E1XcD1ŪBMb%CՈ 2IV)aiI,'+ʪ4JOqz#md!XT$9MΔ[r\*(~ H^8VSQ˱\hUV}jItg>TV<#,@89΢706ٞExatGE8˳a==KOF8۳'{'5[gz {gg&wLkr&_M|m5fvMv|}|4kw&_MG|`G}&_?!_M~6w&\fryQc:_^:_^*_^Kk|y/GT1:_X/_:_ޠʗ7N---=G[^{Η7Aמo: :Ty+\z+\&\V6&ܝorW&wL.2nuW KdKMj|]fbU J5䫶&_uLD|]eUjS{Mk`w_Cku*? Y#: . R_Il$FR9Ma3i`hT;nv:^'$&-yoי%֛zhΞ3o9{pz[}Npz[me8mn9{=ή's{u;&ޝ~~{wo?߻/^5/FFkiiŗ4hKY֫@~TzUTwjO4f~Kejqe ߁ؐ@mGe`=}'Uߥ ֓7w-)0W#Hg_bQqTwA8%~R)_2TpqLqd2FbxF8q"Iıbi_1ak_҈q;d,~,lSI>Q`ԪIxd)c;LuO S] 'JžApک.>cgzg9?QHb~#'~S\SMs֩$U;Ne'9 mde'^"kȰ*1>!ʧ34*B—r-_7]\!WOzB~)vi|Ϯv_>{=j#({=ks D{=ٞjOgsyBM=#{o7_;^{}>H:A')Tpu*NUSݩ$;5r sssstq΍;/?++/WW'k_of +;g/wv{4(V z1A7X&X>X=L  |98;jw?~\8{\f\V\N܀qw? ~PZ-B"Ke-y|X>B5O|j)s5\E5iQ v}nL+R]vmb /T۟k Rs*STNCjJ쫱Dp k͊+;{G'gWHez` <'xQe˃WN WWWe7{2K|/}F//)IK>)gE|])A]:fa_o/?ۗv+`w3^vcSi ;ϞKe}^b/Wث: ۛ//]{}>du81N@~c7sΑU^Nwҝn $UM+W_Kk[{?pw{݃ qr```jN^A+ׂo|?a\ϸqgϊ?0~pY ^s{xd82qOFǓ )+2yyX۶۾nqܶamkohk)p۷; sm=mۺRu MM͋pۗ/o`l0,<7xq`N .\\\#޸~qO=-g|~?sqb?r@T1jTR.;S~NzМ ` gL@ ?n$lS>'ߕjmCy=zN1:jF?f|ЮUjx.>C342Q?~%8BFp3wHZgc_!<{>gdzl<[Q= Ḟ> 8aPw+hW*e!Įd6 宂srqvA܂s3%+ VnQkoT=ס,ltUBgQ!w+U8ע72(]ff 6+LyO*:Ʊ_ď q\&]X}?F1Όi粲 yٗ۵kץmss+4wnc9;Ν]鱳Y A3*j~5৬Q.gY쎸`m➎{?<)xy2VW1X {oD%A7OCֺzkܢJ~݉P=סYMIÃ^|x%YΖGZ]6Wyj[zXfl![FAlI5痆Y^P*nP s& lXmJMVt.3c9}N|UC`;l/[֛U'κr~yrJ%Ђw֡>kM烨CncΜ5ʞ.QJ$(K|mK~"r/{]x4X_OF <x:0.0>x&0!l`b߁I& L f, , <x;05p,03j+  XXx7N Lx1I`S`Oe6 l l |.;7/K]o`V`m`e`U`v@~`]g_ |&+}ρ_G]˵]'[p7j0\ȪKjb5Y'.ot#5=I=,3F/[>$IUl5$'$[ֲuԫn#Y }G}A+[^<O>O >9|.7mG||#Ŀ[+'p3ڇߏGMYeɊg K\x-݊#8۴5(qf#H֫c󗉯g2 /a' `|9ClGd `/Wkb/~c\r{x s2/x^'$^Wxu^gsy_l _ [|_̗|_W|5_:7|e0y*o5K#"z ޤ?spWGK4j]vtPnBi3j[.0wcyC`wԽt6=An-nOr{qOwRFВPKsHA2}CÕlw{ KwUMeuONs'ÐOTrĢ0L䂏$ClX6%pNpI 2bINL"I1Yfi!ɋHb" n@@}}{ G?}x.x>- 3ocڑ6vT皔ppZMlRg}#J]CVdx3AUG&͎JY%?jww-O+J{;㬣` 6͕ƏυӬDigFNî[--?+>¸[r_t֦d>coQ^ffi6OfiVOfVs1N'8jE͈QeEY_a=vwQ oߝLP-kGnwlﻇ=;PM _G+ov8ׅ`_W]}vSOA}4W{zM/`>'/w/{}}} Q0O<)|+" G ~TSnW5~d8O@ Hŷ%`8`>jv\!|=wDl u!kQT4'EbX-֋b#ґ,/e5YS֑ d\eW)yr\$r,w=0Y5:VjmZVk5e&YӬE;d Y YqjjVȍP CBnnD =ĤByOa=ՄVSͭVV[ P:SneX-QNYC)BIchkc d 'YSNRG\k·%r Y+| P|N&k.k/{! RYL.~@ay]$*d k)v k6M)LmEaKݞvvAaWMT;|!p ُ٣)e'PkOR8ŞnQ8Ӟk/pM^l/WR^cpJf{{?Q ;28)CaSީHa'ɩFaUCm,iLa#'iNa3ӆN;'NW=,?9@gacpF9c):(LqS8͙̥p3y…bgK W;Mnt6;)tPsأ,E(+OGYhT(DOUQeXȓFaO3OK [xZ{QӕΞ, 3=9 pgg#=c<LLpgg&30yׁy,f0o0o,M`M`-`m`m`%{R`yY2`00ˁYr`>C`>f0+Y*`V f50105`>`&f-0Y:`>S`>f=0Y`6lf#00 Ml9090_0_lf 0[V`//6`0_0ہv`k`f0;7|;-C pxG*(IDwbv3RA-N5TP| Sz]L;`iL{`s0s0L`:L:0L'`:t 0] LW` nt& `2Lw`'0= L/`z 70d L`,`^`^`&/09 nܩfL`?>Nc;| 36pg,$m,$-XHXHZ;c!ic!iw.f0ywy,f10~`s 0 TsdHŷ;``~`~f/0{ ̏̏f0 QPyRYuX=ր5bMXS֜dY[֞L=؟cfשU5Vg3sQ6`<<=1fgzBDV(TVT ,F,Md`nLw:@<4oc~1 *4_ꩾ' :1 FҝQX]V8 'ҝIF+HXqCW̅<~a.Y􄹨g梞E/\.z\fr!˟Eߕ*}T.\Er!Eϓ&=L.z\74(:w=8c`G7i8"$¤B+4ۊIs9ʢt{ߚt!fR7ޢ-'^ZʇI:Gs\u%L*SEH9(KaPVgmd Zk3kɥdV,+]1 FsUV]BϞNABa.7* *JH hU qI(Ze0S[RRIUDhW*obʧF,qu)7ۘji.1>tߒ3BhsNnJ=zUkmtrTo?9/*^:Ѥ-zUIjߣLW?mR X k;~~Bـo#VH&Zj6f4?h{>Rƨxmc-;(nXcvBK+5/P*N}1C̦XOc[ϡ&J!CE&mM> EsXJ>_8k33,K%Z/i)gI>r>%TtsZigm\[/1_3}KO-Q",8iTxyhqYWB$g\BDU-}͝)?ծbլMA7GJ~Y7+k53XzS~SpY_K2|/H !\QKKᨳ'A@il0'VDFS #w]~q?qݵ:Sw=;Gnz.;Y4'~.pG+Wp 'k~ Lh&~<a#G)( 1~d(/*+XYyVI+`RGnUSå~8I1OEӀpxp2sS 48eg|p.ી_:7 ۀK\ >2~pj~pী7n p?p3_n p;׀;QB}9S$ u +`7 ˀ>Yl9_+ڌEmƢ6cQE.,4Ze+?SIn;LlWgu2Lov{ev3tT6wfrugJN!Kfs2t%=[`(41xΤ_q)C+q|5 %=bJ!yc_JQf)KnW nM-֔L)C*nna/CBCGO9?"1\o>VCZ 3&K({9hj_pLRajMt2SV~[)[6掎}3gvY+.UHOYO.{\+1Ĕ=5'>Y};t'O˲ [ur:Jjqݵ)jI*ZW^QUmnesK2J[V^{u.JP_%f^=+KvRۮO־_٤ΥMjԮua:Cf.w2W,`n39cPٓWvM'XK}1?|Kz!IkXN߻p֌fN[=#v֯5E# n_x(6e!Ϟ挵O1k{x}g=>|}U>K~Kϫ9+_e53_r˹7V{}x^_v cnt׾pύ7n샍'=贸k|K˕o*FC{v{oy~/ݲžVm yi~(Q)H+)r3wO Tzfo=!P C]Pg-d|zɜx5JElݜrSʍo|2rr]vY^5{f>/]a/ӹ_Q"*&EIix]ڶsYJӔu^߼.ŤRV{X$)EڣT\^})k}vρzw^KL^R->w>ntuwNFWuv`V.ܑsddsKY2lIw} 72$žbK}[|{ RlidŒ\*zJ5+G%2)U%ǒRSjՂ02nZ)YeJNg]i4U<1apC?b\ןG/wy'RUxKhUi?bU|n_>^iW } Wlժջg]KEO<}}ͱx}+~wvc?G?4ÈF?yg_oWlZuv󣇼sݚG<+3pd;~OV5ms]?G^,;秥\Ru d^|?ӛ~FCDiqs7Ꮉ[WOqv붤]7_~)-xdRH]u--Z֪.^R+NuSj_ѥ㥵J}i^Z:v{ESSݵS!ڡ3ν*x"0R؅X= \ŝyCr)R;lڨǽ_zV:ɼa:ŋιT$rz 54cX% S}ILEԛƭ =}nIT:*+LID@fX-V0/8jT{ґ'RcJ-Aekt]!Fd|dS,r)jV3H yw"ܒgku?4Tn]m++M8B<˷bpfҐRPֵo(4Ta ]Px9,|i45֭RM<܍Hv\OY[l{܍AMv吲ۏ/$ 0dV_<ٌ"`+yNOR5N7.Om3Sۢ;=eK+LŶZ/Rp TJ^r> PB@hĂS3&_#QsҪ<5 CDr HY T$,~ sZ)-MPe1{0IHпu|+I|Soxz̓$]G;>%_gOo]sdi@ ))#fȽR,{Fe7XDK>L{]Gq/O;Q+&K98z r{v. yRdo`xiaGW޳%9y7^GNadbqqG9 =?Z?tk@-.KtZQǻs֯Tgg]7mxCALz ],nUrJ=rhyslKz9[@hg/$ݑ|Pd}~r׾yI (iVnNq@pL }S&„#M1Z$;D;6sy؈s.RA7(`U~^}7i]+> hh@ux:Toä{DdIzldAp6 sK-~r) 12tARGNVqW㖣zNƟRh/&LNǸjxL)J6/"(ys8jܫ]'wRQx>ì"[J8zE$Jԛm J m-2go} S4`ͿtT,-hY_|:YzhX DZ]I?*Miێ:/=QZ |8q箏LNyEϠUXj**Jڲw2Yg92{UfDgW5$D4̚,I8Y,Cyp"]:AQ+*xWwO}s뺤{~[4ݱ6'/1zXagD1лX+N|Km}.{Zúi _tm.mijcZc/Kuщ5>GᏺrGRO\P /puc2#ƏX=T0h~h(H`!6eŨX܇େY+j5剌30L {eɆ&ӂuԖFnᐘ9m _xct"dsq|i~p^ss4 t zwg].q-:?8< +{JVa,T5Fe WE'L ju.)MAW҈Co2uVKk)o#sΙ_<Yp|߁BNdقT@J?Ay-~SBgl6)) Tll05C[RԒ{Qb HYKJa>z>)=Y'GL7 A IUeiwte.Xo; g,p aI>A^{. ;?!6{Xkw|<*ĜrSs"8 %KHUNkpF)5yaC(8Q^p{eخN@&`p-.0 gӶ6OfW3rHgJSy`^HW_i,*'kZũS" lÚxF_-ӄǞz&,n+PQQE(V!Ĝ#-~*v"EY Y5s2Ց%sPwp+"IA0/qm#tQafLX=#Q^_w!^%cӓ?i^ř!U54xy/50a"Qoukr^\SSCYKdkv|2\ rYk8~(ef=Z>x;][Wki}| z(S hSخ}(]4|q:57QXxl3k[ G5 endstream endobj 62 0 obj << /BaseFont /CIDFont+F5 /DescendantFonts [ << /BaseFont /CIDFont+F5 /CIDSystemInfo << /Ordering 55 0 R /Registry 56 0 R /Supplement 0 >> /CIDToGIDMap /Identity /FontDescriptor << /Ascent 905 /CapHeight 715 /Descent -211 /Flags 6 /FontBBox 57 0 R /FontFile2 59 0 R /FontName /CIDFont+F5 /ItalicAngle 0 /StemV 58 0 R /Type /FontDescriptor >> /Subtype /CIDFontType2 /Type /Font /W 60 0 R >> ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 61 0 R /Type /Font >> endobj 63 0 obj (Identity) endobj 64 0 obj (Adobe) endobj 67 0 obj << /Filter /FlateDecode /Length 21238 /Length1 240824 /Type /Stream >> stream x\W.eΌ" X@QWD*XAE;vc/--b!Ʈ1j4n%gK|3疹νw,P AN؆ 4Gbcp]נ~D|R%W`rmI6 7?u 967mYkp`F<8.6Ue30+WfAѫ2^by7?H^~>9=)hI>نZͦCGl)>T:wojx4_MlTe6@[ҧaLKAAAAAAAAAAwQ ]0}?G}!'4g            { ?ĿIW}|y  o%#     (ᩭl ,DAc}p DVRg@>NR@O zp%刖7`>rN 5 KZz@Px[CD"_#~D}/XV@D4@R#T>@J4 D@>TPՠF4DPe j@m50kPh j"zB$zЀh8D w h 1(hb FМh4 ژ-h1DBfВhshEބ' D㠍D[A;xI:$m%mkD;@ юz $H#IdJ ݬB  =vDS!zҠѮId!!iOK4Ed@Yf a"!DB~D/@%:@ҁ0 It0!0PC4Z0#  DGDYII Dg_ Ӊ30fN9D'= S`.ѩ0g9iz C>LXDt|afbs` ѹ~8I{8e= $== g8O(\ z .=GL\!J$\SpiI ѽ &z='yg.oD/$exH <"+<&zF[ψހDoJz ^ZwmI]"z.bޗ%#[w#ʆcʖʎS޺~X(%甊 Jc/)$RD񂵷36}Dy-AAo'PO\<      H JV|Gswg  b}('.AAAAAAcQEZ@AA |AAAAAAhT?guTwu?'5_9@AAAؿPO\<      H j{?>?'AAAA;C>q      %8jXٟI-         ':*?(?>   꿝TDxAAAAAAtZGw+?'؇AA%#      p?ZWx?>+  ?NDxAAAAAA\G`>w;s'5_9@AAA;C>q      %w+=|,}WAANPO\<      H Wp}'5pAAvRC>q      %gʽqAAvRC>q      %x r{#%*OjAApI]>      ROٟI wy#%*OAANZC>q      %W*'>sTz'؇AApI?      RBPezws˟Ԡ   ?NDxAAAAAA77 ؓdĿ@<*(M7Ӡ5Zc}hTv>E*]>m71mMN&%QJ_5 [f-`)ٗ/`9.Y&5j^NZa5kTVJpP1O%<=ʻʹuїxNurtШUJF!14~`#Zʄ'Z= e{M,hH\"6<L! 40^I&.̋hdxĻ{8py@X.ڋkHȿFIš9{ZyY/''+=LHx_%nC5Ry2-\ʋЉ,޾"ĒJ |Bi- yODzDHu7b)U:ڲE|kbO 9%Vt"2 (emJ2heDlUF[Wi\"1<~#171Eߎ[ŕ(-6ŕ0YLIg(۞7:%*v&$(+"5R6y""[bA%/!"ՐGŴD=95%Q&TG}g?}ʼn#,Ipمɋ a^h%TXwQ H"v'W\SXҕĮb&͌j"5u42^ ]닛~hIrHxsBpb0^n)+fˋd&/2xSqNR>S83'J1܋;Ij-0%xY}wc#Hn .H.}ݢxJb\__y$TA #=""= yyIE֜N2/#" |7sDO8$R5E[dLEۢxy" IŋEwDŽ4]t<##{q9"uS)./EdUp8ӔԤe<Ҙ̏XR8Sؒ"d^/)]CLЉXrZZ0k(l/ѵcrJc^gO J1xsp2H/-f8Ƨ!ې˭ g\vaDޗ,_aW( Y%< =,yxv#Y(&W5d=챏NpvPa@RiIgz"KF*S64ϥ8ҒVR{EsŽ8EZ\-[Cfk 0DRŋm1$֗EeTYLR2wc2‡OH%b!-0T%fK\|I/"\ ŦKkFǽsV DqHҢ}膥@zPTr qyGE֑. <1- LԘ6_;=cX/m0I^ZN @4EJ[KJ]6H2!'Q lK}$>RM|bI0Dկ _NRJTj_,5dSjPe/$} oޓ8mIoHzFS%= ~IIW=t%"iq $]#xII'XIGH:\ҡt$m.ijQnLvIvg =wAAe%vD3tI[Υ[κ]F$%%%ud/},n@xdkO 7Q>ܬ۩~k Z4 k5Q}׆֐}6jTbDE!!%wbPL::IBooёkYV2 ŘB'Zz~ iyI {*G'u3SW%{H'5]uQȑ dVWtʥNU9Uiu ]Na1:ujP5 BAMUX*j-lߓMF<Ո%T (ZVםXߖ# ߍ%C%Zldl7l cOJqdc(,)BEwEWSC)6i`HNH@}4e4P>}h"-G"ʶ8XK (jސ4SIդj^Cee%!u/i@V{-d+;yLh-ynۼEAj@QȫZm`VlAd ?Ȧ$l (S3yyyyyyyyy<<Μgkanjbldnn2;Mg?9 ȠcfF^U>ëM%jޖ/e$5Yo~CWK-ےM&~\Q$| `46B~Z~a2HpHR/ $ +&Hq~*Mq3c _}ЕmLIkt#ӑGwI ]HLd_m~u }`lA$C7hl*.fd4Z*G4E-)4{A?}tLp>*^H#Ih'yo!;y^ })szF>ɚtӘ$@L绑V06lMIR4- NDN@#x0deyU&n7䎯$&nꭥ*hiLVE5VY 91LESr;ɉxVUE obSyd`I jgg`(&A_Ʌ/_jRsVPRgHM:55LkM==:& T@9ݫyC=4ɗKVu @N ՌHdCCm"Zf"WidvڷuL{LGq;RSN|+!o|hg⛻Pt(j;ub($cEyCt&]NPct,8A:d` 9wpch@#gA52 8Jg 2KmlJբWlyL,:O ;u4T#ȚSs˴b0cTdEVcdn֦0v& oa4#d:2iFMvH&+DiBQMFGzmq&=^ƽ#΋ʻWwn# 5gS;_=8RT:GUIzL/Eu;9e*yGk&O3#3YeaXfmm[֤UȧS4m!5@*$*s!I@equ 6mwe>'cȫd }5Ȭ =deusv4NQ2(cdnj6T ݆ l87'iAJ 0Ҏi7%Ylc %-9Ĝcdeb:djMTsd$SI:4v{2!ϴ/eGMr#E?9ZZJU\;:aD>E)Aŵyxhh+wa^1֤JHz/. u@ l[G6۵b@+yJ.]N%K=c(WTt8\yzgݯGxJ2uut twuVB㕶#1vS25b3+jؔnTCR2e@i`TWkUM!~(:>I810 [a' 0` 0` 0@𓄵d 0` 0` 0` 0` 0` 0`7DiPJZZBKh`"COj j2FJ-'vQ27Hl[l;qMA94y/{˖gJl2)um >Цf!)U(x~-XOyq^[~h˻JXOI>FJ[lsoHyHt2]J)_.ٕ%[۷lVʷ7 ݡ'G?JM|,d}r}a -6}lbV—@$t=!lI^KeHDsY؂4i\z){oIKɤ5d~oϋyK7I_SY/+i߾),ԅ2-]׳}-(=֫[c@lIq[⶚LKzgTHץg-ͫRNoeq9bcxeQreޔ^:?JzY=b};I=]|m߬I.= )לּ)שּׂ*+' Ej %H ľW$E&E)HNJ6%>{-[(M6pnu5ꉊs]skMQN!U3^$QiJkla{SvQv.fK5Eb0UdY_MZu=z \Nc.U>CS4 &UUĊFHeT-%'+Ui)Sh閱Zx`o֣KvFĤ{03=Z]̞Y=;g{ffLN#9܍nb<ǥTNJ04k,ǫCW ؆uhLiY-SYx`EWQi)ئ5W\~ʡիz=T M쓖b̥ʿ\JoOR, I(S&hS);ea  /kvdY1Wrf7^_˜&uTv`Ϧ+<&X~!zL 5P~˥lSycK|vWmq=tg=:j!`J۔C'WJ3>V}6M۹ρ3܆/[-;x"mX_<3&'E~놱Eվ(ߥN2_ {}v`ŃG嶼x4Z'Gu32ȥHȍK]2N՗𛼟3>9_. !W`r=<9&2y煾]U 58110A~ĨəKrni7 #wrvV(^E"AOd^6%klld*=6ңJNзo %%gb}d,)|dQui]*ƴɊ:i܌4aP=ree~_G=IbZr`ձz_ \wɐW)zYg}\pc=63 =vYpV‫/v7޿ע){-;[mbzws|W̚:N^m^]nV:{^P;q0ʧ4]WN씿xownvVU~qc1WA sUL=wɵg*ƒUl'Y+'ClZTq3Ŭ1$00HB͡1ԯ$7}# +^` 5ۮz\a}$S>1΍ۆ9vNi/Lߊ&Wֻыk37|57iK_5IdSAe~|VQ=#uөqߺ5yiװuŲ6=݋؜(ě]~25yGRN U6O>]گ;K_3v?>LZ5zʄ~sܬ:39 9&=;t5Jzd`r%.7o̍m3ykZm>5|T-Bvcqn4UA\l4&tTJh*T TLn~V |XW|ק/ߛW.P=3E2 &WEl^"Io-[+`ğt)JZh#a634Mf62䷌]Q=qH x>j\&tecMmӻ|cAOέ;VJ;eΠ^mURj_5gk5ʾ=pZq^W ӫ=ZVͣ-U0]'yFc^U_5fx+rTcGTZ}mj䡰oy;e)[?k*v'$ZG}?ܙUi+^۵[;v}'pm4}mqE8vVctǞ5ݰ}F_>;x[O<{X.b7tuO-O3sȂ;j|w~_/wyu䵈_I'UhSf[iח256syEKoZە.p֥K bI=k)c.\xFࡍSRN8n8u1/!kN;""Tu LY.s6p`ĥV|QZMV}?3Uch ,cqڽd/o͘=InZC٩=3Ӳk180j1AaQ<b}NGMͿ̅-/};GU?"49HUFMNs7uv ۵[nbcg#nv$8#oo<q ֔W7jlaUUܵ㬿w4 #ˌ+Ȑ[{_\bF&#oAaWrKQf&MqVM48 /L^ɇgCUFKV5ΊhO16/)4460FU篿,~I(. !B޺!|Kbťŗäfjԯu&TvJTiNEg2t7k甽JZMߪIݹu|bu4[1 ])>3?~ql'vϜ^0t^߶PԮ7kE} Q:^{n}{?7mU_ly0[oL;KiזGO9zuW9@VgL2}% T(;U.-7 ~kKgW-jm/ }ذD;o.,4wA=kNu9u׿yuɠ|<;ҳs˰ ;}ܷQ1nCAZcIZcv鶯s2/WZﱯtk&]v޲?y]8WN,鷢0FoSGk{Zn#xmwGw?q\}s4ļjɚL=tFcKVښuZ/qpk{рN566p)v(koml .OYyupk :V*XfjȐs5m5Ž׮桱iVM̨ O:;mћg{u75+QC .ηܺ^n8㘋^폹G׎ٽuOޑm~^"oSy%v]-.\hY(?(]g^<-rwwm}z[MxVk^b|qao}aeZw5SkFz^m}{$Sf;.?v]o/ Ȟ~x?msj7 q2?yӇ3,#:zĘɽ{non}_RugZ,g:A8}/7dVANYe[ئ&~,I~d֒MIGcpǠ 6\[z67Ȇ4f6u'3aÍuJOGS\2L"-KKO쟜埚n4.6V)dpҗsO\(7b]Z2|\]eg8^H:ތG+'HW#_ms~m Am.>e"<:>샰zb[^vTƿ_k,9޵Ћ7v'<3_ʕso>KSǧ[|d7mf͸"">$WEЂaH6alj6f2K)ڹuM: <0ihٞ\1|}LQ\uԃs*^8d;^jk3otmE_EAo7II"zwj;gSn|݌pba䖄3EG9q; -NDrU]hEMw[GΣϟn1zśoݨ>m=Q5^Hsz'Oud~Ͷ.cu <_~`q mݫUӨ>' vlIO/&K``b̕)8lO|5?l b;&P.o@XMFY Y5Y}56C<г'7ʢ le~7q 9vf%3)#fY.y6,3ү)SֽÐ6 G {h|3;tF'7ڴFaV[0^.7,||yGO0l;(nh@7kYSΝZK{'o"044'ވ{d{d|_HjďUz߁v*/9D~-?.-8VZR.x_0jHg:zܾn֫E[,ߴx`Gf 5uQ]3"5NMo eշ>֭NPw䜭;=6u*uhxхzv[,*,o{ M@~ USj;bgǮ]ٵI!'*p_7&9 Ĵy /;sf;)ӻ+Pz7z1}:{^O7uqw iPN,vP@kwiK#4qYB. p9L#./OIyRʁ?Pݓ{Vkw luInlcs"]4ulGN u:A.Rר;C V͉r'Ct5:GGMI'G${q&GGydz=jzN{H .\V(i19!PN1NźN|MIZKD-x<ɾ6޺޴?٪LttO7cxm&[W:C|{ 3=HORz%.";[Inz?'3E}~H?cQ0JƉrdS0diL51()G H1L&c&`|jfa0GY2s!ڌA :Iߖb$OS\%{dߐ'^[.VIv[)e{#sc%4T[i3)Қ("B-YR QwIJQT/*uIl<-D$%źH=F*.Go'OHvdolrHiobH*b(yEL/*"P*$>%=$ )ɳD*mgʈ~޶ #'O]JDJYK9?K*퐤NjkK/$}MsRI3s%TndlEL ä4_)񔮑Fqi/H0+'''g@,c D D!hhC*$C{VV2w^0@z!5*Y}-T!>ۀtd *( U 7Nq S6 *))xOZ2)y_WPع)ةԴ+wH}vCGaeU6-kڳ,dUհ#jYgVr, lVϺeYWXw]~.fKerK+v]Ůfװ]v#[~nba7[ح6vۭvYZNkfhf:b4s55K]5Տ0NffKW!pu}oE/ US*?xe'uj;^OFU՚5JQvV}J3].,PwӌiiiFiFU1&궚Mome5455[Tuuٚ >ښ)4sT_hi>,,UOQ_UUWRRa4O4OUTUK4(uCu#umfPN9H^AVTl5_ *u*XUYmRhj˫4=Hu[N.yf[w}]54UC; dXpU?U7Q1q͏u lr-+2_0_``2OsÀO||6?e/o _/UTV:WTE]E}T4V4oE"*)Z+GV*+_Y^,TW *9UPM9T9)(@jj 4VSfjZնj[h.. 1j7Ē~8 ZCku)ī{{A;\Rh 4cc< d AV4:yۯ˯+__[W_[^[_[7^[7_[$^G\@TשSSm'S8@^3ՐF!*rz7A/{t?NN~ѝ՝ӝ]]]]{{{{{{]LvvGq4p2N)8Ζ9Sr*Ni8Αsⴜ+yp7W8.¸Z\mgrpݯks@. pչ.O'f}>UOghZ鸲3ʹ;qH.+Eq 8Nfj>ھ~ځA!ڡ0mvvvvvvv6O;N;^;A;Q;I;Y;E;K;[;G;W;O;_v6_P9]hW+e"P9Tv J*81K,/#'{ @yWy:=PN2r]>\>ǃ|+fT'O"!;;PCAAM S j+|5uA ҬT*B%DI"MCC ZQ(ƶUmAe2)m F̀86AA@nQRNv)i\ iI0@Ad 5?iN9\\Q;0ZH&8:D,0a [;>MW+<8ءf6+>z__7r\.k5q͹\ q-V\k.K䒸N\2gR\.Krݸ\:CN^߁zq\p}~\n7 pCn Fp#Qhn7r\JYn-Wȭ6p빍\׆k˵stn7[-pKern[ŭ)mr۸n'}r w;rǸ܏ '$3w;͝~r"w]~r׸ &wr߸!{=rsϸ %S<3 ކx{啼Wށwx-xy/y,ʗxΗ=xOދWJy>7|W|ka|-6_7uz|8_#|ߐoG&|Sߜo|ߒoŷ ߖoǷ;D>'f>wS4+ߍΧ=|ߋl7߇ ~0?\~8?ɏGc|?O'~*?g~.?/" ~1_/__+*~5Z_ǯ7"k~ oN~A?OIg?Ÿ E_MC?Ko@Z` `+ +(4(8 ZY PF .BYU(' ](/xPA(x W* B` !X"T Յ!T!„ZBm` p!D (H MB3Bb8Jh- BNh/t: BIHBY" iBW]Hz= )d BoW'A`a0T oh>3N.988g.b iE@"(R*(*ЫH) [UDHPQ#{}gmn2Ih4eKh=AOPIQQQj (5JJG jҨʠ*(5Pu@5PD T 5Pm@uPE T5P@ Q= jQ5E Ljv9jB 5Pk@mPE (j:z 5PG@$ǩnc.u=[Rl5S/ڥΤ݊f9S܊ǙHpuح{%v,$'|gCZ蚳1WJ/gSJVJYV3W]V餺jtdw%[>} sǏۥŅ%__?P?\?Tv+&='n+åUѫW=wwHE'|*&}R{1Q~ZT":nÓ*<379ng*[%u.򺒮k:nꖺn;tt?~YQzzWzޮw}>O)}^_җU],LI4~SܐI1&Ô1Zih榕ik3ONnzfh!f6d34sB,3+Zl5f9`c؜0ys\6WMnn,8';rRT')d:*J%%%wHR%iylX"-%)tS%%v.qVrNr^R Q gMɟ$+t#DKb$kxI$SR^RAO$%r.Ue=iUTUTԐ'QIY򴤫ɳ$=$=%$%Y>rE2@P]$/IJM$=@E12@ ȱ{L|@|`e+W%I^ dKHJIK&H&J&I=")%-#+'/Y Y(yKH^U̷J$!;.[k$k%Z6eea..6aKa>i)]]I31ލBx++aXX/6M>|V5O.ua [c-kױ"3c03uH uS#FV2K糟jZZ )l֢J+lK+nCa+7-m*l̹ TsNjzMe6Gδ%r3mi{L;"gʏi:$=9Åۅ7]펌hOdD{##ȈDFt02ѱȈލ(ǿȿH)?OiffrJfZN]Q20Ffv2'lcmNs=*#eEQǢ~Nn= z~k1)1b̎9},6;#vO앸quMˏ*_3k%tJ1l8-X1EH )LKmi˖Jʖ,yd&ݐ-K\^p+ù />g: ,YN2e=dE{ +AV*U!A Y#k@քdmYd}y?dC A6&B6l#[@|dkl0d[G y䣐AG>$S!;ydg.OCv@> 9do,>y~!x /B9rG `W!_!@9#G@92#@9rGN9r T9 ozLY!@y!@.|#A.\2#s!A.̃\+!WA\#ArGnrvrnr~ya#o{Qw A ޿eؽ`ؽRؽFo><>?@Ϗ<"%/!#/C~y[ȫ?@@ 3/+ #oB ߮LWڕƕ+ӣ =22222s=Z{3Ώ1O0O1pE'<$)!OCȳ C~y3Ν.s7ƹ!Wr?ȃ<0l.Cw{}yB~1')g' OBȭ C!OC< y#C~y"%+ȯ!/C~W d䏐 Րk B\iTOTLJ'NV}}u*wwT;JΨTEwY}U*HRU:ikvugҮ}?ɟ*\J*|rEݓ^6;;ުZx|xlReOtla;v%_\fjzjߧLmy@֭~JRRa#7Q endstream endobj 70 0 obj << /BaseFont /CIDFont+F6 /DescendantFonts [ << /BaseFont /CIDFont+F6 /CIDSystemInfo << /Ordering 63 0 R /Registry 64 0 R /Supplement 0 >> /CIDToGIDMap /Identity /FontDescriptor << /Ascent 1079 /CapHeight 700 /Descent -250 /Flags 6 /FontBBox 65 0 R /FontFile2 67 0 R /FontName /CIDFont+F6 /ItalicAngle 0 /StemV 66 0 R /Type /FontDescriptor >> /Subtype /CIDFontType2 /Type /Font /W 68 0 R >> ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 69 0 R /Type /Font >> endobj 71 0 obj << /Filter /FlateDecode /Length 3316 >> stream x]n+9Hv5v0-0om["OUj[w_dX!mN)w4'civ_hd&;uǺpNShNr D_ny-9;6M&<쟞R| )DL'2cw.L>c Lܶ1w2| :\S撽^GzT=|#wu~S7׌R^{_v8")߮n֩AT>Ց`w%G>0Z?CĻ};!ؼD K2_K"BlͬʜY,]FW.L}Nst̺ I2e[Uɻ1Bȿl]ՂM!wLUX7Z7!=| 4 h #=Lt+j?;e% B_^z[[hş)?k\Ï{m~};kSZc*RH97rȶz~O[En=,Z.J{iu:|zn3z =xq:G=xU㪭2S\IP4qRj[lk|~#rl~ި.g`OXE#6%H, G4B":Q)`% bʲ*6b8Ɯ`]IZo;ZC4Q &7tt[b^Vy(1ZvjaMzw O~if\+7FcH.qPLiRH1)Ӥ8A/MSu`ESo(ڴ`JrWs+6kQ^B%?u̓羇ESiSr' B2} 5{+@WRms-%{W3 .4.ݚ-#c7nӏ:íi{:qdN܈JO;*ap%&7{Ae͉8(~c`z% pF~)􈪠3U7W2B? m,ia(~\WLy%o5E/ıhKen xⰱ.&)|tyw!y߶xkw[X:Woց׹6} \+oū?DzhDZGr<|^ȫUTQavcpN1s؆RU_m;u<ץf$ِ"u C]t55um4ct7f:bGb >|*e5:P3d:86ϭ-uBS1 MY1,=dbPccܒ fǃ).bzx8%%!Jz׽VŹCz8]sC=Oz< 0Og>Q诿/o TtsmO׻ e=V?I$Osa%b҇ 5h6J XI5>ݨbI/%R#.נ1Rh]KEH殙Ǿ,5,Ehzq8Bپb9 3\/)Τ"P[Rcݹk7\g҆E4}δ3MQ|:1 R}E%Af@%W=u'piJx՚_؞[n_MY%@9@Ѻ&Qc"haTMD.au1XEMe;6ªACgTVmV C@h"C?L4}sQ}Z h[JD4[ bt T/ZvR0hvD%s4Selv' մ'V-2MoTؖ(Nr KI!`u$z,d,}gϬ?& 6 lH41Kgu/ƎK&n-&bc{ ;e4dbzK4a _CV>ܻi!BHC]\Cm{U0FAc7'nT: &z[Sr_T$u@a| Y9Ds1_&Cv5| չq$PgpAlk;T2VHVnMIghpbXIDll%J+N S6??TtVMV*ل [Tq9Q\*7TpDG(덙<3^ W(mjӊ*GF6.Ye;@ظi9Mb'rԐ\[vd؍l1N n뽓Jgh k "T҂4Umm"\6Wm}Azv+抲bZ77|^f1 ݹf_^L;޷kmu{%bn e9^#źX~cXt ' YgrZ^~:F xQS]l?(j-z JM!5GNIk8Irzvjh X% *u=yO?d;h`?pfJoHR`>U U)vi;NbA:ŭ2tJOv4%J" \”u4ŝpx}7wC&M= (twǗmQ/8-]Pzʻ1"WNt endstream endobj 72 0 obj << /Font << /F1 27 0 R /F2 45 0 R /F3 11 0 R /F4 62 0 R /F5 70 0 R >> >> endobj 54 0 obj << /Contents [ 71 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 72 0 R /Rotate 0 /Type /Page >> endobj 74 0 obj << /Filter /FlateDecode /Length 3090 >> stream x]$+&G0`P@Ou d7DLW/ȈdY IcUv$|qc) Ɣ?{3aq=ǟ<|{??'KMo0 4aKƿ χg_};_y9~qٵ}zsy^)=?ScOH][AݓuH-x'A3P~Sڭdhx993"g>Uo/*CW(av'C\&y=R!#Ɵ =cY nTĆ(6%^cSֵdk㩐:S}~x?=?gd Z3^@?Cߠ\9hC-歸bDxзbњ]}Jo\y?#gz?mfH/u|`>ES~t-ƶ| Oa;z\p¼DSGJQJ׹Y(Arugf߯+ X|?_'8HDd<~.hYAѲ[s-|  od2~O2.1${"|'sEi$s22Cd4ұ3\647 ZC$_xay8q٥\OM].y |VP\j!{5"p9 kbFl b_̘r ]%dO时. =PѶ@Ɛnv ɲ6-\eL)ԤL - Ϩ_#r6)޴?cUN`Tnhl~SLUpc -4[ Ii FyFGڂnHA7OQ?: K/ډv1Ru3-i';~hgUp(\~mlf/?8mU?%Ӄ 6 5Gζ@謠,0Q9Z֊0H~JOlxqV+;y2'[T7 {*{Pe,wΗq6'?sgӰk.^+yI/kY7(ۋln!{C+%S=j,k= IzcP*fl YmjGdN)5Jn)3ܷϺsK^yfrR+ZUou_yU! [6!N@l5ˠ+e>M]hᯰݍP'-&E%4];XxonƯN vQW ԁiC6rD`@z?ѻjؒ%Ii4mkU /frβރmf_8rl b K!:{ NJC,Uޘޅj=&MnXM|.wI35uj`f WpcJ L'Ė]7ikv(nAUoUv dmHO)A%Uãȗf"'w5rM7'ݶ& @a 0L O0aYs62l.;x>6앫 /͎V̒?Ռ9׋>F}՞ajE|(xZuZ~1Eq:,yO;e}ş+S x-kN2kjtf+`k#t3ڊ[$ Iׯ/ [T"jsd ŚZ+bp{`ϥUV@[܌H%Uf|a[ȍk4]>JʹJT/CnPW[Pܶ[" F-j2jǥd2X 佮 5, BEq^,K7,`s1,cUWnǶ\ya?.+hi-&:Xu?"c-7vIIn/_vJmjAs_DA?.li ]KuAl(eK]~/[A[۱.>=7l'7P=m&lAVS(WGCg_׎`]V׎Czi-kEBGjII?,vN|:>G(>ğǦ4iv ´k!zj\iB5[uź#;OΉ>$T1my풪!7ө3;=vEM3M{AZhC=?u)[;roوC~@6Fh7]-.^tCwOBHk_ A|,VRլd P~-ifJGL?p V7E fA}f L;C n'l,f2Vvtr2;z6ɩW+ڽvTPYe9|k(3ҦHT"e^{ʦ-iwf&(/󨶶_-DK5 Ѥm2nخz$pqk#d)1Z#fN({W ,<*tVg^򾽼eP0~4 ?G)~Gw#(_jLb4=ɖd!Y!U*JSE) )pQld; +l$'ez@oOŠ4ZcdxVxK ^vDZB+ 5`bp IYu{罃gCAORD zۦeBQ=dC(1v3l endstream endobj 75 0 obj << /Font << /F1 27 0 R /F2 70 0 R /F3 45 0 R /F4 11 0 R /F5 62 0 R >> >> endobj 73 0 obj << /Contents [ 74 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 75 0 R /Rotate 0 /Type /Page >> endobj 77 0 obj << /Filter /FlateDecode /Length 821 >> stream xXMo0 W#ERMnrvzXa%emvɲd|Sȿ&aE)9kh?>< G "Lq:)x~~wJ|Y}Et{E',;Lx_ 88ˀ?LOPۀxJyBB$ xecxtR`"c繘Gƒ?zLޟSὫƍ} o_S+ނ%|z;]*ʇ/džOz>LwO["ɅBr(ل9./ɯݩ{ĚVs檍5{\,KZR+|=jy ]|־뼚xܭjy :¾9+ІImHICvE2kˌy!-+ez^UґlR,I/S4JR).|Iݔ7qS?^RH嗂Ir֧;$|6ը7X>Som)K}?ʫwckw+ vlДXx$[LK&݀KwF'ۡ'PrU> >> endobj 76 0 obj << /Contents [ 77 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 78 0 R /Rotate 0 /Type /Page >> endobj 80 0 obj << /Filter /FlateDecode /Length 2419 >> stream x\n#9)]+QT:䰷r[i9tvRMKUb9+3dʢDe3EoLƴg3)1< Ohy2=cM&?i.w f .ÿi㷻///:wXr)ŷcWc8x${?c$?\]dD&~9>|~CyU,0Ys\1Q>7e޳T/l(¥ّq \"2 $iW`NFkNc152WHˉv>FǢ,WҦ=o[ZYV[#^ o*c. O|lZ>?-f}aGp-yWy.}|Av:BAz~Q!}cirСQl.iSjI[,|KV?soޢ/kZxn~=[_Z4O߈C~FD6t-ۄ؈E/Y 8--gm9ޥm ,~V@+OwA{G|hxYb=9t wEՇd0R1'AM*@ĭ 5.HCvFWtP7EU1ZNҦ'P{Kd/DoB`*S4C:Xtf 5^wD14$9_[u>5mLdEZ G)xL )z3aOXhI@xzAGjקQ: !o tSdO?{{cϻ/[9t.~{u7ΐow%E֡f JJ@rdw545AA-JPIHPY_*-4*w u`ɭXuaW z9)fɽHP[{< :6;,raxx[,0.ANzF@6C6 %DLԭRx" r-oBS6`p~zq^*Z)Ny@A]4oO+ٯpa~<CӦض8Ĉ&e`Gkak'u}7H5H=hP&䥃IKcd&~&3d*KO2%h(SQI+]Bvca/>\x2m՞2|w1A{i_ L/#MXPz!z:^`;ʨ7o#vQI{?&b!=5>.L* W?6dRQ&maǍD[݋IGuGѽ2pnS`J9mSt):-œZU*eX(F+z'i# ڷK!wt)$Xwۥk^ BSi붬AE-ub?(~y%1XUb았!fVSbSL ɿvֆrD匟KZIB+o?}D}W! }j% boPܯfopqW" >\An9:,:r/m}km_Y].?.~Oa ŗ󻮲?M^]fP o=,JuyѫxN>磴xeŎ>_m瓴$mouiKOKm JU|vVq-S1TV}*ϧW]S>Oen>ͧT*~oS ^X1a"Vq5)7 xP q^p' -q\h?XLnaDo׻R0e޼ћAWᡏy$;k̫9K<[1yCh] ;-Z,7u?ܗ] endstream endobj 81 0 obj << /Font << /F1 27 0 R /F2 45 0 R /F3 70 0 R >> >> endobj 79 0 obj << /Contents [ 80 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 81 0 R /Rotate 0 /Type /Page >> endobj 83 0 obj (Identity) endobj 84 0 obj (Adobe) endobj 87 0 obj << /Filter /FlateDecode /Length 165095 /Length1 353736 /Type /Stream >> stream x} |[ř7=ݲuYC%ٖmY(lGN$v'v& 79 1 BPmEIh(@)mAR $@f#vw=73oΙy# >m|8ۋ'5Ի d/7G^> mSg&-E&k1%`sr]{ԶAB&D:_y9ڽ貞5KkXtǢ+yx׺-Y =gs뫗^uI D?_Ћ0AL+s]۶k_W.|{;?O^ԓR" m5X9oloc}h_sbYnM2-kLiJ].õ'?ܽ@3@ck< _/?~8 >*a=x1SYK#mM|z~౦Û/! KʽbH KHpRNtrOaqH,g ߱Kfҙ/l?k A*׌ oU45:Oz\!`5jJ ?5P/50 ?߃6ae\/L4ɲ\J3j:ژ 350 2߁iaq# ]qa-, m?edXNX,I  @T؂X+~/TAp;4 ͐& L L s30oJ[O.#"@+Wi,Kk@(m S{`E7W ~=ȡqI  ?c>.pca3!_&:nUs;\Uy\5ZQ_- ~i")B=ıEGK.ؾ)hdžzw(Qy[DҞA?CsYra̍#w78C| KC8ZЗʭw? yP+D]‘Da2q]_!CFoE]T釰Q1\ 'חҾ:^?]#4\ #rft}"1sp9,!_=ܘsc$`)78N?iWj*'pGa /rיof~ HUaǠtaX@'?qm'wskChGϿfscn̍17ܘscn̍17Y F6!LCxdD 4 LmHBd=jq\h{׆Mm˽$[_%,y.X{+apnov Wl&H-0SVJd(;?#\;{I Ba'8`9ߋG~Up㨲5ψ#`P|x2|Q}L۔|cJRO'SBNA9 |}v#|a&3ד|)}ʧ>@?o|<#Oz:`tw:UpWfQeasd6P<ʿ/ G%F'O/}G! nֿ?20_[}UAGKX.>~?I>Iɗ38=ܰ˜scn̍173cn̍17ܘs_(؝17ܘscn̍17ܘscn̍17ܘscn̍17ܘscn̍17$'YK=ptTz6ǃߡq)ÿ7cp317x fJ| ;p#j;)-NyPEPP ` ̆Ka9c paQoSg v_a‘S;-tg9+?rq\oodc:h1L66C/\W/a}}Ө1Kc؀c z]|`?ۺptp/rp3oǿNۅԿO9;gx[ w2~=)Ϝo?~K߽y/^ F^hI蔯p)TԔI)mp샻W~<=In;.V~7Ô&HJ;om̳32[)8ylUs ۪ym8Bk{m5|V _Ax[="mkؚ!E[ i6IZGLb6%۝"8o~wDHIV 9a ʰ.jDHxR&aX#B Gۀ{3|^@:;]$\.7Gwߣ؂<IoE DJ+~)_w!\'z8 "'.qn7!="w1,A$b q\)#[1 ƿ BRazSTc>A>VZ3cȦ_w5W]ֻreK,.Z`~׼s:;fϘ6uɓZ'Nhin4ׅkkƇUWUVK_~^q9 [b6 zVV)rT"Ƚ4t4YcjWĥSN@ost0Q*&Kk8 Θwq)1ޭā'1=XKm(:[s=hLۆ2!m UPDzG,+yA hSHAaP#-vbp Z#iĈ!FqȣNV^rh{gE: F٠[cM8R6+1XV0EE5n>́<ѧm"ލW9cwĘ8!&a_ `pǴۧ=:b|8 qY{,m&aWQFD] vE04t8n1l|,r;6ѲNouPCotaoXzJ7N2ℷcygGژ/k TFW!l\a߾MuqE3equ8bVHыCƌS!ng3`Hg")Q`Fs#X4&s,)ri2w,ͧuV5febBCq[Ӯա#XG%5ˮ գPh:^e7Wb#6rZ c]va g.:zKhmw+C,13Jus,Cxm vD(Xh_FwGJ贍L>6x;BU %X}yoe7\Vq2IK{GKDѾ&Щb +Nނn9y)z˫ &_qgrQd6 k1sRs¶ÄE$ ;cS}BXLMԎ'ţdx?SRwtC+imGmflv8L͘;pkmi:>Yyca,W` y1,ML#'iŎ<0nĜpir1mX:7QZ9Z}`; a$,+j.CLҤ#}, pTMR09%#+¶XK%7cIy( GNh'>sx3tU|,QO`2dL4n븾sYw'eU0zU1U#cJUOkiz.2W=*T-v(]<|utR@sۏ 9v!yR:&bf ݘۼ 9O-E1PXdBL-(-`&VPYŢesgG;XNq5⪎I=bٯw0c,uǔiQ3RluHq\. eC61e1bJ["DJQEX\U ⿬S<{9QT8"T&* v0k ߌCEL;]Wf-0;ЃZWe2%gIgTmڏ r]Peچ  Wa8!6͎/Ot_phf_BZ7}FJ=zWKb9rkan`M hy(=IKJ+ r5ޯ|"' =6QKQ0C㈭zWE(psuUST*7SF !͋ AjM؉}QOb|DI!NNlsލLAO KzbaW m|pAgOydh,v3I" c܄y% ճK,NX~;5[ {1]c2\HElCL~T]hOϢYhi3R\tӧ@:!M BAGsp=Wr֪mzےEZ ɓl4O' zUllqɍ?V% &VfUwMVbsmBx $B&ڏϓsg_$#g3GdddȻ;[ȩdd2dddKȉdddddW2ҟlKFnNFnJF&#sd#iOFڒIHk221)OF2R$#dDHƒ,'g2}w6O1wdϘ/2?,a?'ea?;ۙ[߂~f}3{̟60- P0ajM"܏“?EH* v#<C82Irl5 Vj[ bAl5Ac(ҥXKbR,]KAM3\7[(Mf`8ʵ!*Ui`;1~%|>_acEb . abXktH(|kMuFL'Є7e_!]E(G :Od%B ig_"NJ_s]=> iV(>ezN˺ÐT!zu"2'²DhOzDXHK_ WS6sΊDQqsP)8%#A7hHG1#qH3G.FbdbKcKfBdLgF$YdgcH n_&wI:3r*$ u GJD8$"(4?l{T}Qq$zFr%33C6[21-=۬2$bac!Gm1FhꔄnاB6M!d#.|/3*ƆwլcD6Rz\8Jzć U{UM|-= OTmmkS,,"޾(E}9]cD w!ȝ{8e'ʼhԆ~rBȋ4(N&#$\Ҏrvn;p&f )qoIȭ. G$:5yy2_ŐB^<"))#ʿ{/ӫ~G`UoO2#aǏ:##P]7UľhwJv d;cķ_/#t}rFx3;; XRZ{6] Ű2ĚS=֤w 6#~nD|qTD6[-y}U}%}OU-FcQ_j"`y><\&ߗZ8]9.MVv=[ OUևxVV(UjL ZGM>QiZ5)A_ ~@ū4`Q ԏQ56b?8s/֞ {\s8FTG'*%pE\诡? Bܐ'r졬-d C&IC|B|[[^3 gǂc}zS8LȮNLqPc6. sv#Vg{ b};;}XuF~X ލqhL\KuH'ùX~cO;K2G֡'j #9ԏ+ݺူ)׏* W<׍ '\%KabX- ezn5jKwշƞç򶹱TW=Nt(!i<0G=)zsv-"q!|W E8i>@=» )?Cx"pEnE؅Џ f"AD@hGhCЊ0PP@!Hˣ~8z6z&z:awoGOE}#Ϣ'/F_>}.l'?>=} +3#=%]mD룩QF%m;"3mרLOЏ g<˝d'@SA=DݢPݤY^k4%xݸÉ;Ї->WN\moǔ90f|vCk-Z wj{ރX/;r9Þ^ lo 7nיA FaK 7@ ñ5!*}<b8 0qI9RjSzSvN%7znӃqYl]H9x侇z qs5R >Mp7 onGcH]uHo.Jrf!9,[p5r܍8XuK9mHN|a#"Ԕ6bk0>)ƲQ 'A'9H}@\${/p N$) <KXc%Dǂܾsn%^ C?w1܁Oo#D^ڌcw_J}T 8M|eDV|P(ͿFyncAܘ/sے7  ~ <$sɟ1J_K/]'yd#$'6~=ڲ_#x~/ğ?_;B\w^ܞJX&L s"3VMh^7%e6,I\B3޻ uRz?YIPP a_W@QiHUP*C87R-'J_O\G== jOhlp5|YLd`IgLÇTh4]eDQ*ǦXݡ[dhrBvQM_=+L&w~VZdݘUlԌo*txs3|0pK~0N8\(Y 'b}pDp8%*ݙZvJҳsRʙfWʁ>xݢ'iYN;S&;-ON_e/ߕ~ڧCUU1A}z:J jo>~8-{轸m[X:4mf;e5uȤRclt r+Hdqf#q|FIոEt^}.-<ŗaI١JE XLŁ)ӋT.״ p8z<.55)Jsj~Cm;y㞜,[>{œ-uwxO@m4ֺ)HٛsyߺrÓo8,p>JVCO _ W>jjSR% >Uqrkt:ݓ:BgyE~b@Q}0w0/f.5Es\¼ɕ!e+D3d )]x /jq5iW/G3,uAt ?]qs."˥K|Xh+ ~ġ?_j/ύGؗ̌i/_8iω4fƯG~6](,jzFLI&9&p^E6mej7Z-MvߺAΰxq.H 38*_m!"M\7vK;d&{4_GdU*|%*.8t˼ȟNЕs%&3âSQO)2P>eBݮyazx"ĥS/?w]P\A1$)291=~zbߎsМ@@]ԐR.4B z\&?Mj&8gp2OBTg&)yE9 2^z4e* {8rG0ְBbݪT/9IqC 8)3 oZ4drii\ ?eaRHTq"#tzQ7 y$ GKpr]_%ˤ7~8)t  .~]|7K=dL0|?Ԝid^RVu?m ps 8,% 9f6`$#7n3/2JyadchB<=[dvu[OR[8 b>>1AWV6Lz %KbB{s)." f ?+-0d2Cs*ǓvR>Q?8;9~s/^ hߩD dV23} !r8<>o7f3V9J2&AgaM4a5$U#cKAq\Nohtp7SlE<ϾnG~S_Tra\SY5zbG"aƸ[Zה)ЕU%%k=fZTRe|[FšTbS4MPU0go :7 XWt [:3 +,IBbǾ܏{v2kD-.X*"bɜ% 6*T!%-Zh/+Zl6w񔛚&N3 |zݳZ>|]:^2熙^E[[LJt,W$& ZwuDv>ɾ$3?Z,0Nʤ#,p067gg07Џ^ #B^jMkI SX[yn?̶gMӡKbGJ`qhL[n#+YAf ^ e:qW/T/4+*61W;4j|ϰ~gEKM!dZsNMH}6\a3 @;aq_ț<7A;v y-%!IIeBE#.3Ltg*dzn*[/W7d`q;h4<+- kj )N )"F#nr-fC܄pm(ObG ߎoYY.y&>OA$HVÛVa_j"V}V}1}}r-oΤ/ um |(!+ELFVWD2c'%W;kTU1q*M[JU*k1)‚ɝs(6 ņ 1Pgw ނOcQkڻZCZ<<| T,ZRR'ǧ r\\ mu )&Z+>)rUy}'sRuV%8㪗U6^ɥf~YXwZLaoۯ}k<hc2 =u@E }5͚8#wV˔-ݛ7/:)Ӽ?y8Vs⸕MݟRo,jo?q|iI q˖W[뗔P(F8' P;unɓvoX͈IJq=SP$2b?rS-*3JT#}IjPUj. CmvM|U WT t1Tye^Ԯ(AjR`$]BSעq+X։[Z7ɸTG⫯_{yUT7M&Ի.{㾯Zgs ݓʲRL*1<̳?vMl)Bq2‘8!I1b͆f;zWdFO>kk,^7l1eKypVUCVnU.\ Kc\ANYr\ZO['ֲs ͽآK{ӛW&ӖZRG9G&M@SopUpJ[Եf97sg;޽ހ܃|=Nma*=jE`%μl(PDrӧ9ye#*.WeQB/Г=]Fva;iI6B&}N}ipM!sUx 0Z(H|ԚڮGުTGeMTWVRGpеSrR~!]cO.H;qsGuqMJ2;[0| ,v8T&hi=cڭgoڋTg 0Ǭ^%=ݥ*7]uU(c1g=C񝽴[+4~_vWd,ژ`GǨF`Hh ^<)7Wnҫ+a~B5=ӷԭG^zayon_df-V|*{2\ٕ2PDnL#Z~])7Y{f83Ϲ3~4q,zMK&k%Q?7*/%8dMΌZ|{nnͤLGe5 ԊGXױAe6,ܢ3)ּQyj֥W͙Z,rzl)gqrMp#gzm^wIqUB*:A2ͮwLzr9^N4r^Q^L'à%Fm֯=J\Vo! K4eOؚk^9) љ娯hȤƬvNmhQ%c`$jjU,1hܚX3f:Egf()ޘ ;,b4Ӕ!)&=-z\ҙ'xnRĭN}:GJgY)ܣ{j=]5O-AI쭥}G;.'c9⦪->tU]dyxZKkf%BcɬQk0xsnz[LgT+k~X]hWOtKUWSRabIՂ4#Oy6o3qe=3,(˷R}525:7 VPjLy h O4<`d g㔉2!xJ))ePiQI[6KܢދS2bm݄p]e<+D[Mǖx(xma0~$ӟVMY^P6% E7hL0(vPib8I#1KIL!E\ԎJ`L6qS&Y Kdzī1#qs6@JoLZ:*Ļt"t 6Y*9$=f[6>#POZl%e"me͟u(2_C&Io؛"* BħӢak(a8HṲ̂LTañZ:(.pA+kXx@N`ErLĖH$ypȅJxD6yp '1|p/.]:1i[ `8@,|e:rrYib|hyԅf|O/8;A z4K> |5ŝJVQ}e96{ ?iܜ{pɭ.w+9~Vv[A12>7]LP[Zɂeu!\S&R"7xReZpk13 CnAsi\YMZSDͫ%Z\%4e .}w0b'ƋgmVr _Pc2\H^RPU̿ڿRi$~e&K_ok*FbДZGQ"VDŰbצOML'M{2R-zSDQ_I͹ejVVDYd3ċ$dO6՚_BN OZhx+X%w']ST;# -#Á[M6[ٳͨy,h`)4mmյ onkumEWNc=}UX[]w*ԘZW*L\ɲ2o* =- _N1{,pu^FY˧ aVkZ ڽfHJ8)?i i ELK,:=@+Aͪj/F)2Wڈ&ڽ#ɔ4L'k=ͯ+FѡK:0ƭƕE?T69 7Enx/ϭ]0 =25pJqȆlaEC[Qr֐EE$):MC*μ>+n(;s_< Iv;8ODSf.}m(/a<}1M["Wz3\8uTivm7.% h 6Ur:BZv6kmfcI^*RQ,ic>7^cT;u-g[m!Œ5LGt:E53+83K~GQPGaC2e8|$yxVg./^VoR+#߼}˸Lz)9^zsGOcV~}[`6 1b?wlp1Fl*F7t9q)KxPD^T[/FtUnAU~?nn^( 1W1&C|l4 mL|Ŕ4bf݊K2AT,;|s0$1G8tgiL;)uwnȶFWwd*oҠ-5z/IhfZL-daL+p7'SqVXȰdfLf҂ݞg qh#-. [#:o%_z2K\5xz%6̪hT8F_=%{}Jt(ӯ{JʳyvsN Yh`UFGW #!@ {ie4m튔>K(yOX&-Z%g.OTԮ4JJ~6,PELQi)015#?0Q}A6aoC״=j|+WŬwGq8]:8(1\NRl/~Kd+qsڛIu##0Bx̀CZu( &$e4,B]|]哅_q q6Q\>\TH\2Օnu9.^jdg-i%4qd;MA;.UIҲC0J2$zUJ&JoS\>/eb!5g0\r'.dejfk.2|ȧRwty}4\@?(jޛ彟3Tin1BbPyJy(R轚O"{F2|~#./y7qSZ" uĊw-+_<|Us=,5mrSӂކp ]WWI vV2]75Oo.hi//UmRd}p4\6)rzNϥJ(͆,)/4KiN͒J9%7pzSI}K=ZH_\P3!JG|Gk-t:j;76^?7Y1ݿ}SN~fR{ُw꼎d|v@#M͐5a96#*"tvJ w$.])Ӌ(K/.|6;o}96/3+KCSa:PqSۖP:;4b5a(7S52-G0 GtZlwGSWrB_J5ƴh, 3#ؒ H"n${̶y0*[d̞+qyn|gDͷV?oM+*_bMq6Έa$!bK`Ĉ?鈅tFf4:0>e:? jeDDƧ}JأTdOth3ѷGL|Zg}CqƎ/M5 l݉ >̞yPW_d,_]fUDoR\!, tFo9z\$Br@jVF:}3NI6f i =4Qx:)`ީ_CNyek bӥ/#j3rOhrk K.2 s᜚°,,U%pFjJRR{*qkI<& p"O+Dv SWu/m`}BK˜Wӥ.(4Ԅ5CM:Cz&- #>1 [t$_ŗ}ʗcv~P`=q25Fb4_慃VXT)\^IuWa]żEŚK3AN.qW7(ϯ&.v+#q2SBњ̨pnK*Cƿt'$!R]į(ier*aF@74&\hYG qEPSCO:=3+P$K1xA(N3x9#e3֠`>xF+חd"H5>0d!w6'E Oq^̣3޽x<$4k!|a> +@UR>1uIK3|*q&_T˓GeNm}?[~vm=qc[='7^o;+~/~O#v߉aZZ츆QCRInn6'/"O_4d*1yݖaH~"O!TVwh6wWƚkz{=Yi,q qvK#O QRJ$w {)O9t4]"KPw|bl(59t[*<[bf0yÍ뮬`fQPgw %-)3+[]sg~|0D)+ G0Y/}MejR4~l}*`)_mkMț+,75|\h.%m}g4Gi^\]WaskV[Lyz LYmv;pizz@r[`? n>6'@`%Od;{ {(yO>i*S+GD=an]yŵZ:z5Skp\߼vjiD2qg֌pu~ĉW8RoiuCqM}wYt\; .Щ2S@0O4> *tf^} %%|F]e!Ԧw9ը$_0]>XSlx9P\rkaS6Y9&MU3ՌZo_p? KMBĐPėx3L%"aWF}7i]wD/=cʘWdT W1xòƂGݛGQe֭굪}M/Idt5 ;DA@E\P`u\ۨ.JuFgDt׿c߽UIXytz:sg[?qy=Lq\ܱȸ3#5#qL.ceK2:Ʀ3O SS9Ө%#FOl֒Zx~0^+( c!R鿏 WKiT+ۇFR,-02aތE"5}ك@ɲ$irFru!!`=9Y{smDP^˧)d)rga81ހOttʈ9EQ{j]CC !\*E'@!X, ڐCZ(@3$pTd4DCZHG/38yeo ݝ</-'3ʮ3#9Ei̱[󋡤ZX 9*yB>eW\V[:ᬆT]`e؄IW{$3F(qX̅OU_02nM!vbwb߇e1Dj)=hL$8{H0=ysbt>ROk]!e$GUΫ 6Qg5TV:tJ$9zz+^4w)suLI{swL0_dZ1ܫ -KXpp`!*DQ!*j1ܫiw5Te2E [&_1*c T@@[@rK$.'TMˆMzFiSZtt*g`X2bʅ2څ;F o'* =_ -D3byـ{ E@ۀOF]ϞjmvȀ8(<$ɀ}}6?/gBZTeOd;dP9tʋP^ZLwe@1[nef([gJϬloz[nkYuzYq]b$ꨨE vACC]6 W)XYgw|n 7ePLY0F8[XRe,CQfʫcW$"fU" J MwhVسKgnԿt` Fa!ìXACR6۔/ԦTVqˑZ’T- sy۸º۟i\;e4ړۋ,ϒB1C?, (Mk[X:Vb6hiCk펍jP¤d?P!E"ys+rC3cӇnٶq#Ǵ7a0$1V< 4Z v55,حRaSP ([:?VU\=,A ci1;I}j@k؋-L)ԧel=g'd&L(=)>"񑔃]J`:ors՗&öWhhN̵Qì5ҭѺ[v]:;ZLUZ忝:0l/" 3ՕU6Éƌ]i,[a[I!_ Z^dIT>I!Utx|@v5- ڥsƱo'S2d۰n+_E7B=5p^t]RrݸntUvtU|\@e6խ5v#⤉mCn$@ XNqFr)ASTD#5"2|%}FV p.~b99G'{;eo^ ;r-b$eWԻ*Z%7?sc{ðWHK}Nyy-U(.w)9w]ME:\(QJ5X}v{ ݭ\8XFa±gU5C "=YS2L^{z芄IW .p^!&I[2)E0,# !u#P1s"x٦4L]l4JSE(nݭx㜹MɚIs> ClU*~q>FyY>BҚspvi-u=aE/ bBN`IOXX,zuLJYITC1Lz3$T I2`+UA6A=hPsd&g2&L(L޳.cCj咇o; C: //3Xŕr 6O耙*ō /+cnjT b:QiR,S|aUOs{q& 355x9޳rBX)g@[>A˼2:4)/V+oüW]J휥ZZ_YgZc)STZH'J(֭[4j *I0` jhˠޕ JCq44Gc@`ñ mb;bFRW>'J b)I '8N{ilF%/A)@n%0bcj6⎥OA,k6XErnײ 9'/^ۇ;jqOÌtN .ƺ $:JG#-&k^- 䁥ɤtPCh D/"JOjQ}'j-)'&Q]u2 2P.-`?~c-snC* (u`Q+ [)x1OS/$(RVD5YG,K%pspTg?6Ae- c`n\lw7җdx_̓ y&?J"?|tNq\XpyZO!IXjES냴V!}/a8 t.w=H5NQ5^ w~Ixa>Cփ_/rS(Q(,ח.Y)1+6ZRW:zQ#\l S3ICqȏơkc@Hrj:d4; /2s_EP=atAiWN嫇`vHWPMN \|h*z*ẐB=P1Nkܫ#IwQӐf2  \޲)NPA   VˁK<ݐX#h7+;:t9i|M#gp73gW X ?<.__QS;&"UXRPA#˅@& jDW#83c5IffƳ6Z>&p$/ Yn\֜8t:vsݜ#gA`k74qCi"/!e'o\8gi|Aơ0Z[4R;P|W3Qoi,P GgĕOe T(,UoTS)Co߭O<"РD؇ w1(9.shs zKp r@Y`ͻ8.oEFnw4xZ^Mr'6#;{%4!#J(-݄ݘLnL/puc#]2IW.]zOsKz޳2CmY ]-Ys;]<Ay[O-z@ =+^rQ/GܾZ]k6nU`ئX]>ˆpз̻D(Y=j5önrm gҤ#2r*Gw\8[r|qѤyN6W̳G3MKlZ6to]G%ߴM/~ݟ`Wӿ깞 % >f`lX^Z Z--%I1XlBav.vq-av&'adƣZ`&{/KK!OWy,|A*[)W"{X)p6oQT}} 9`~%ǒ=_*A*tFS ;U*-cxچ?`֎ -Bm03 4󅚍 =>U dv8IJ(ʨYh3d D1W]2eʨswn/<,uΧPk.S}w\N< .Yq^f[}/Hqobt3 !†kwy۰tmV0 *j ,"r=foRn yy9p&j >F}]-UBLD + QH"Mi !@\YY!mH(dr b@"e8{2k`8rsh7 X[9ӹosD܁hEE&@R')'5[|9 Z^?h#K:&ԣ򒖊sGo{Cv˩cϨ-8W&b##.5R& 6Sm0@qn\?2_8u |w[7vNW4̇xNڂ 3O]w]>>ZE5ZIq;ԝS-pv>BWr'((\֠Ó>`p"qFfs7q ̷6Ve u@7S 3~~翸Dx%+!*Ukş|˯|3i-^pR 8Gڂ=%ګ(֊Ylv;{J/\ȵ]8_R[F-PLzÜ(+=T$u$DK} VV*nNE'Rb)x$@Vw g {7/Kg4ϴhřN+5#~*t=;x\ٻ}d.f u1B!Rv6^„LYgg 8h@Zl }'ywbHI0a,hn@7FNQW>tBP ;w;G{\Fq_6`GeUwhC{7a,i'0s2VI,56 ٶ)Ve6=yNwHߝ\Im֝ԃ'O7dV XNA8. 0˘G>Uſz: }WsZE͗=)~s?|j C{D|>?JR^&%'^ r.'2!lB~UR~yCv˯_YdFJQU*|K|[[-7tZ;Ғi+Sf˜'@Ks8| D_5g IqLq,[lDf?a6vQȒɍԌFffg*ɞ<-P*( `0@6PPiLZ-ﴬl6AɤѰJ] YjMoymN泯=s5Ĺye1!% RmpU[tOp{TU0N|z)xAR|~8w(3^17 =a+\e5Б R\gҴd\`Pi*}TJK(kro=ʃ#C '! Iݱ:c=.n_&&AʵF<*C/#!N5욲B;ZKţbסwj5TSsH|;jbNm t}>J*.I~3c̏~3cΖ|b wyYUV=*+Й-6pN'E_^q UFi919Lrk-aon1wbYnTT^z6֞Epb 4t!95vU"FCژ*bX䈱*/hJL&^7jê Z-pcķ^*SUi/75k2Ĩ+OMN$ſ.ٶŝɑ+lu˧MWnxHg? N#6zTNlƼ 9;&n*)*T]YT4^]W9OORZOcVڵ̭[1|oxiy.н7훾zEj7]uÌ.i#j֝SUSXvbx1&9q 7c0ə`mJv3nQ02iǟivU8V/w*a \Ji2A#='HY鱧\ƞ\[TՖt@?Jt <SƜx;>9ב3KFD!D1xnw2Tg2sw썿k;|ܪqT45jTv~ݹ{/?Vpm.\1hlb;W_|,H݆KP #Nl<'&M>[E',zwܡwTi<9Xfݚsjj鬶sp1I-6!p`: fU Fbkdj%.{I&؉K |Fw=ĝ{\Y6+ms÷*hvַx, mIS7kٿ5d?~]+w c{ح*k)g7Ⱥw5X^I,SUOPW<fN 4zwv[MNMKFZ~ xd<)w%a_Ae]e|}{j}cwħϡxZ22t _scmz[BdDt TUMCI 3; =vr#<Vq|Pud?3ߙ y.F =D;1 ؇!?rZTG؄߽QzW(y'oޔOo%*IݜSn/0=o(ޚfAŗL*y>Puʫߥާ2s>cSOJWW>R|'}O!](4|)e*NuR Rv j]j\]Q|5;5ϯ@Jp#2@B!e\kx 2QCvy* 7sv]+L;vbCw[o_BW;>_ȳ{'I'"=~/05I[f bŢIP*~RWY|E Tmd&HDnŷRV}utb@}O4FClC`')M&˔ؕhB6zfcaY9eo}'MVX-ٯ}']T*~ZƏ!W0{r [ }cUc:DFO͚fzGlƪ xSf/l6# oWώA"WLd( Gnwi5ArPBСX)*BE7Ukhe(Ѩ7qM(&K~)mi>|u>ݑQ >PPwҍjBu۔\- /ۖ4mK/a2m9]kGuب+ ckjnֳeP3n줩Qf|Y]k B OsܖֺZak}'DC0sCaOUd(&nO_vrON%, 2gK|l\XՉmѐm`{ ixFe.,]A;GW ([|uJ̰0ҽ sSǂ jA&MmE1鉕-'.ls9(Ha; ߫REʹ~#rE4F_<20+C*PMzS8-N ѭ;Ods czFRQcH7: u.a1}ƌk,VY-vg'mmy;a]xk1T-1iMt8d/*O#H6@|C'rc߈ʘ?>l4ۙYցF8Wh bqebUTUJl1|U˫jb"} Pe3 HNmdnDBA8A`i0X--? 4e"YCP.jgW:m&_49}"6Uf-0hF%6kKkqنŸq>7/۶<N';1iAd[hW2a,?R(${pЪ_🁐͠m#trS"R`xL@it:e( 0_e2kRQ>#ɭ5ΥƝ/uIXjS& ] :t8y}V2hHV gP[ZjIS_dJ(gQF.0I GŦY絆M$=fISH1) >3K8͛ﭤT>齛{5{?29wG1=o0qM?w7sB+hLm^o iqW3 Qv'9(њ5[|A]6qTI,N:="nFy=gUg&hpyXړs}?}2l_5~*-mUxt*X"h^{{a]e-ރ[rәdR•kqۺ{ּlSmNnr|̡/(TɐIO^ڠ HDw8 h>bgI$%L'Y?D"lzrjV^ѡ!҃j<)z$ R豤RGI !ǧ !g=6O iRkQ[mjR &PbWK ϝ1Lg2#_?$g1IXLoJOE!D:T0O? v*Fq?̓v&>=y IT4"xDJLA,mNW> CC=>m8hwJeinwF0cd2DjIye<:BƌeړOa:CmAR]PY\,^Vag!wyv|*U<.z(?Wgb1,yUr=0Ad%` Th򰘇 Y)e?EnrMWH9E3933{Pq>5oD]~vV7l0}DDrP)#T=J4ʀyr T\L"LpI c^iWFU0aGZ*PJ>4\bZm EjKkm* ]MQ.}77I?zD4}uF[{L&2.qv}zlfD_) niVZ5E(x ZR[)5TdHs/x\’.BȄz>gԜG-dxEᩚ(2Led9N셊Hec4nS@P"`(=ꂺ剨2NJO0r͗b:]`  |;qeiCG\Bh#TL<2R3*s׉9 }mwh]E0"u *KV^!\w+$2WZ^9*E0n0Gp#\=n8nOZ,,kRp˨r,eꜣ6y?7gV8 `g&CZ- h LgOZ9o'&_9rrSZu?.|hԾ Ǡ[+Q;*[C4ukƜ˰ǽ`4 a˹Rrm u*LFvy}Ze'"Gs5uVG4ti/Le+T)BccjiبuYJ`;R/.zoi9=ωjGƠ )d}*,?lveTuD5sU/1DzT7Ò;_6x*dVQ;E43Jlؕ9 ;(zFI(aiQ[M8/P]̴-IvwS|qr0E#6~tSȗ5+jkm>TSpdޫcwšq]U X|=u]i被oSQ96H7_QImQL,xg^:qaXUoOk"{Uֆ7wg QQ[FzT99\N W4CX|X FP`/: 8WvV&%,6P4:L<^>S+J)^$*,ϰ3Ոl1BJOs}3O] CAP b'Lq>= 6bbx~hF_NW۪Ild TF9@R@XsFhkϗO]yg*E *R |HP}>ro,d>{LLn 9E%@!yGJzi[.sYHo=p;).wC~s5 J7+μg-ou55}9lo HcT*W@e9yIŏxȏTC@e21pA1Q<+Y?B{FGŗ`.۸QX-XF!ht9a=V+$ Nϴ2#q@#C;yb f]3;qKNJM3:vnƌ'PeS-OwHm9 t0-t-a/ya bs ŨP'Q_ًosyop2s.S$b)U-@2za)=\)Cr͊g2-BS>lucnfE{Ӻ-~y1e>[_^?~جa]7.WU/u\~XFgm.hK*/zS5f)J 5f!R< d#@ !20CQŻ?CJ'"% \J @H?~į{ #Ci'{7ߨD+UjTMOUu2/|UŻ뿝3PZ&X`Y^ mzNoX{YS1y+]a')E: Ȋ6Ne^y1q'6~V n]}ϪgN%fu<:P-N2PeiqV@yT,p$qAGmcgFz ɟcN'l;a)p@X$-*` ؘ?syp/ߴ`ZJaRFnPnFE~9/km-;moj'?_д h^E./l1xF;bӐR$<9=/i8Aqu;0$w @KXC/S0!̎><7<h'ޗ)r/):3e!qw~xLƾ}@FDzmlG&1)6!)Ĭ6j8Cx{$m3[.,=Vɘc\4}Kܕpxۜ91f̾GcZlX(^&x?g(@8ЀĜeHnD!S@iH[ hg{tL^`k*R[=‘ &굍j>,*1_z~8:6kdfؔ.u }Wr}ZV{?F|V@KNwmd ؖe%^K%:*_IC+Mkz{bnP;}7P;cXkWp}?5~]GQޕ \󃫛۷|끒QB|{ёGtIZ Uq4YJՓ25)u>6&ӛJwU{wQd׼/ᳪ)jT_z~VboT9eBN|&<KA28 ߻qIºNvȇcE5N*;9.[XߵᩘXѹ6a_;θnfUpu ;T|5*wxt6`sqMٰ3Ύao>DqxC/90ti@N!}%C7eb2{4 5lW9{#޴jJVw>q1W}! ,jkj._0_pg31g l<41A (Xl,(_zX/HAI@/ĐU P1L0bH<~)[)a6v0h'#%qqb\O!~fNJB c߀7q#QP]F(0jkWu7G = g@ :+1&wc-Nї =vT\},"{ϛᲫ++"Ǩg+_+ܶtX˦ ͛(MP@Z}+1Z36M}qe0@a- J0T &E6*⸈XD^a@Rj<_r°Ȉ¬ tr2zU\@픊勩 <3˗?Z:M-Mk vFE/xҙ'vqJ-yTm )NIl]П_-l4.jf~BNlH(3 { ٷV.W[Jr=Rv^^o=0eR/Pa=࣪OLL2dIONBz%!K.]bW JBŒPkNJ*zdNϙ{ pZk^%ttWpFaj]0x<,tkNЍʲe񉥨If% 2[Gk҃Lb?G)&~_.H[\af>ψhQŅ[/.|_tw57'&IcF| ^㚖.h." o7|PeYxb3nKAxC2qZ4lLHR0'64XZ4tyqJic7N2݈,uR!u(䑊YO_ծI e27ͩ`f1'Lgf,|P9jҔ̃nlyG&я}Q7CB;!A*Y,.+ҩ]TXimSBY_[+"v Yv3OhסO*VhLNR`/,'1DJGP7@;7YꪉNu b5 )w>г殏0 F9ho7Gz=PzgvYv+ |6]{{X>pglߠUjHIT=ޗ=1 !G_7X$g-iLݡz;^ʼ@;dҒYӿcl:4O ۔<:hUIrtNƍp ܄ 0+&MaLi5Ūz2fzz/#\A2?bHC?siJ\zşP Y7 Y2yMI6^ ̎*v9#o\ #86.V]ZԎU:!BQ0.ihU򢢤`ՓYYȧpn%t80 ؀٤ħjhvJBy失-]P3G7%'}n͖dYj hC|[lޛVĞ*SV@|KIRC(7@=4WţnAIJ[ݬdv;ghR-6nUW6{qIϘND <\\1 t#]*:{kUN9YRΏ1 _~2@00Ay$ZUc`jsqp0Q|jMnxNJ2{GuzXLWŷ.xf5&(.By_b1Oʀ_ ME BK,;%0Wޭ&B: ˂ *;瀠 NT*:=l;Z| *#_9e洌O81tЂz] 8no¤2G\ľ`nܺt3DK쳏)xY2I4 `ѡ,,]1vc.I-e B E{W _I@ln tmt0MP:4^!0.)/-Ÿ1:d3+h׃$Ez{甾 ``:lDZH=Vg\Iޡ`7E&ćI!)WBzP-Q4'#8GFv[ >)>=wG[BE*| u3Mw/5%Z3pMzomWTpi[:.{.Ey4gMp[̚4_RS7V$x(Σf'I=jDj#xT`NRpS' iG0ƃsB/x@yl0KEge(;u[&F.z}n_xr{$e!P"*҇xɚm9:tX)E6F<9XtxӜNoA]5}pX@x0~@TʎmCB֗903T읆Q9% =j}%k{ӲfV6D;2G jW6WC F !$wQbXVO)9H.TZ5dAZm1"[DL _ E<ZVh\`?(mŅ ^Dޟ 1IaB$HK>+x5]lc>m\o0;b+&z+A+]n/(k$I$5&xfWNm[ݽa%QI'G+6zwtxrwʆaZ~]RA=HNH!:mcq=6p̸,ZϠB P.f&O쐕͜3Ma[WUV׈&Ny3kRub>e[ _|AOnm a>LEB.N)/㚪gZ&Z;C̯h*6P!-ْ\eMu(F% E.5r|i>.Mg=:Q(x!+W)̆Qlbһa鐲&-@m/.|̿ +(ȑ)4)hW<c aR:+jnXOAnY/JvݰL"8ǀ@L9x4+\%H[J3{փR>2$ ZyKn:`Ck"%% scӫ [2g|TD!(25iHl1 Bg#†ܶ!=hĖ5-b5΂+o=jmSRk@AzGFhf YGZ[xR?uz M|w64x7T!?Ca&'9lCJN*Y{gZ Z֮qEZ[uponS%οI˲+S=(K7v.1|Ybw%=ΞWt`Dۺv3eJR  5PnN("#:xHy=)hm6 YJl͖ :YlMObѩH@+]zeP+d'H=tSY]XKmoL=Y5= bUtȫHy_&0; vdT/61."b,Tx+^/H]qZѲvY*S< +6֡jo)̾{95)ߧ./1R!MoZUޜDn¡C]P<9DQS]# 1?zpu, 2#Z֡d~McOרLp< 4Ie[+m}8HieRwΨf3*q6w@|G'u'p0+)D:ZJj^_p ~.H+BcB35B51b[fOǔo-}B.fmW+Хh_/4Ћ%zn ˜!"_F}rh{pe8 *9 S$-pnL>R [6-sA1 ¹.—2bxPskF .Bdxz/ gs89QÔ' Y5׶ϧRZ=>SEeAļpk(jn[nA"+ :$M,MXTƒN14$tѧH$XRA_y̼zw|LpDۼR I,2̶d'xNˎ}ga{e(m2*Y 4ɣɛ5 B."vs||?_.LGk#~-f; 3AP@rP:2@B]#'hG5(<41Dc¤'.e(joc%XmL/*M&% :[fWFzY BX2 .]q*m`Ԑc;bDCnّCԡ(;wU)VDR 烽U+4I4y )B9mX$hte`֐2&QO53OJ" xjG Bc,s$D"CYoO2bJBWW1 k0`o8HT)rTRа*GΘ3nmhmrkn7'+QEDؘ>kq\Nr`!?c |KRY/U`(.=OްY|OO{)Ĥ>xS}\gesُ@Ԯ 4Da~~6)mJ.Ĵ]#%=L2C!N+5Z  5v̨d[ZFqjhx̢l.>^Μns#TB3Z ЩÓWO`TN͸}ۻwNSL"ѦVg#܌\9J;o(cw;Ӡd&P@Q-6>la-mA8xAfd_~FE ԏ+&v2z=rAR~@*#J:xPXO.y7,g'{ŢGXfE.W^Śc+(fxʺ&@!KӷISu coHa#ϴТ0:!6 nh]3OyItۑ<%1`ƒ@.>{G} T *.kgR{{U{Мu}_Qm)E4~%-u_VGq^&){3(k}BW?Cϕ#gl{ǽ/E\# J rha6naCEiRB"i].KкE P\I,GNlktC 3ju$ D\"Dbl5:^(XpRLLF85GlZm ]Ba0_Vŋ@-Poy I.2?J&,Yws{f}L~eMp왭z'(iz҄.%6@JCmH< MGGt]%V%`p<.A(Cr5-\t5dZ%5MjJfGpfT% v# pyB)#q j@Q"%HԮM>Լ7dΝ6&ǖ561ʝiQl挷fYwcƏHI]l]>L"L&ahčl rM`sxl03z7+`9ɼJ^4q%G"^cjw#=iImr*4%a:-xE]b`O$m" _ r]{h5!_f.35}?:k6xCLU0596EeƙOQVOA1j~`VFvjI%>g> I.ܕ`D ]&+q!HS ֠0};}ejr;ޜS;aI53M&ghIs[YSZ9=jj`~R{jգB,𸺜N Tj^#0ܡU!22+!nd(KU״ M||yݷ~󗏮ZS*L#?C;MV9)Ψv/K+1nG1LPJ]*KwdujCPbMj)Cqtɱ˯"_ܒSϽLÆ҉T=AҠI%XW7+uw.Ut.E$4ƛKd~n i^ҩq_D+JPle˄BMpO{)1i{0^*-"ޚyCmc]˾_o!A,D5?Е,bw9BFtIHv?NV?I=YRJ gKgbwHUˎetym->3AlueBtZGI-&,PlD}OK}7heoq4(ORnz]_N~b~_).f^<_AK{0+速 .t |yŮ܆{玟TR5`F3 ]cr Z[mmq3{swL[G.P 4{p ~et2ob<χI 8կ#ŋ%Cs@ꀇ& nګYpoNڐeCc(wc5s\״h!o T4@}YSc2OԎJ/X}ńǟii>ǢRMnZ χ cj]DK+o{6cKf5nX#}gz)X)L0Lo0x.: K@$%: 3Sm(EsE_u}>5Hӧnޜz ztr[Vf6-'0ajL7dܰpmnPZ&zm/}rk q);˞#J[N Yj6 B?Hץ hrkWNJ*(5!*qeԲ ]چ,mo"wN #chd[vfk;|f {WÇ _&P4j@ʫQ+/c K!]ᆵm|Z^zO—b(h#{_7_{o|ꂰ1ρ.W5 d_k+\L][jr4 @@WMifybe \)H*ý_oj#g]$WIYm `T(G&t.Sz]/.ZӕjG1O2{=ye8})7 EHZu _~fY5;϶!Ė߀bE9riFQf$Ojt. P䇉_ݘXLjb$NCl#3м\Y AI۞iy( 曞´yNwլ(ځi=ۖ}N_67įSg[ܞkQ%byGEF:U,>A(Gȋ.tG>Q"]>2eY ?k#p/Xj'wIAz0\&svUcL=w".?b5bٶZS]sӝ?v!.4jG]ouu×n6zBK|lBI cDXi$MrtAmy+ % 鵔+Ru52=σ b(88j\X8kVMo8nvʝ[Mrs ׈JK.5f;'MqZe "[<\c]6{EikFU$_&Sl}rDEKq!٥ktvIaR)p9+NR׿\ o>ễ~0h)4Du|o-o tF{Z`J aW, Z,h "\:F^CZ /O=5j |u{OnRZ} w<3ϝ:s-PRX>:'ЛI+m-b/*v/Κg}d\\U,1xv:~!b {?'ݍf=BƟЪtnrh7ԒǙ@_OLڇ0}5bFSVֶ9yNoUin~9Y9"i`٤rH8jRR3S%U |%# N== SѮ2.`O>Cr(C%:($'tQ:W^gh“3n/&q۸hL53uc45ᎵLeuKѯ%wB7 FLI;IK|_x+]%boM k+Y+խC;}{/hg/|WWcgsC=(b =N} *I"^Bux.;?lUYҜ9HmPƈ:2aYӈcb[6n^6ښhꆧ*9 <{!y~%C셋=˖E}b6#rp`0:VI 1!aFPBgOAm쯪nƂ"VU~|ޔXTSWfgSšjV $d4u:c(rW?i)ke֤SF̻͞8m^Z2J?R}.!oˏ ky(S@Afؔ]92aN]WAPb,!D>*N{?0avm*3{Ε@ḱ!5I`s 揎DS`.H̴$J~EF]$&ySWQN >[K>ǂ;~T|⼼@>=_"'>-_qɫ8 ɱGe@g-)!%ëqb9+eBaPC:e3[ǙϞe~悏sr߻`W8l`%˄L\YI\o4& OYnq.T!9XEGzWJ嵻Oz얀#ZKcw H:ef@.N8e2ڡP})ח F%2`@y9PY!B,"U;WYE:Ur?]*3\'IFeiבENzA=ʯuh,1l)i;QZ*ts%]?TzġaW1yCΊlˉorQN 8ٛwi| hnL]lޠqqX 81PH!jB̨ƚdT|&FKbw@yq J+Zgs5Wvk9}]ޥA.HofLy(~Чv:&Ng2|vD `\Bj.&T([6a͵r SkoPfz x#5lΟ/0rn D@Vla=ϔR^ 6֔xm&y(?&g6Y'YKnks YڂשH{7;%@m4`À)zQx(|kyl}/wj v er klKUu'2"tUCƖ禦 nG/ٕg.oܵ>7Z3&ec/~tt~屮s;8 1YZ ?:$On@SPE,2! k39J^*#ϩLnij_6x9d 6-6H$ib[" yk7J'u!z&>$&ؑP"h2@7_|ԍ)fj< p 8j:y`GOYݵ Tum-}BrUʰWo Bj l7bV >Sը%YSics0.T@^ؙ}3'ҾsשN ;nzb0}[վw)Js=3֛dZx~Q&oaJY䉄BG4P =W(=x=7]\{ O m.GoWoYԞ9Ь^n;rML;U!}ޟWw|ԏѬ_t~ ?Fj% A͈`8FԞÏC6sD,J{ˀD#롡e(X$?;[(cK]wSM{見{?Y˛<.}15=GxV=YU( Qe%?e%kiaלOD2a|)BMJaRn,C%%cMJ|,@Ӛݯ=n~SxXN@e0̯ۄ/K4_}qs#ج6G E㱉yɧ.W% Kncd$} ES(=5 Mҁy.p;6_FIFg% )#EOc s<> www>;DW(W["v98+wc~}W-^fFO G{jn 5dll!U1k=e~/`rRdbxD(;ɇ%Q;1iIԒߞYV_YkQ`&g>m+sV5u(/ ]y{G?hK_=Ǔt~юovjvmaUcDz`Χ)bi(4uacq"\-([&ŃėInVd#eA8|D:=Yq[p\evTp'wRu >ZZeZ&}+\SHerӹRJʪLV,|nI^6>Y"*|Ea1s2hh>N bWhG|9?<n܇6IWͣp#H@ BҎs% W lx= 3 vo!t;F焛Gsˤ Q )%k-`|*yD((TzGAWO@6p'+(SSSb`:o"k^&0L>v3q_&I*ӗd31% `f虭L?CFZ1Q.fymm2 :A(s1t*u@!CYda %r#Z΍Rjo_#ku|kImWb{ii{mBq R~[o; SyIeЃ;ɷ>PAg΢Dn7 rlbf0G֦L$Y&NDgCr! @|*"ߎB$)zЬL_ak9{lށ?cDk~' 5)< _gzБ3ky\hKov@žO;4R;5 ŎcrZKXOnˠáL BӓRX|GԞ[ٽ+z7Mu( ;{\FZ$UO p=þ7*oEN8%'E ,;%rpڥK]>?|(%&iƾ)+Z(E;& P@}>$4?sߡ؎Lk *|r$3 N*hT~VLQw4?_CGbP(ҏ?{+#ӰX{34+e>諏˷/CmÛdžkbi[o*n#lstˆcirZsDnntkuU{kْ xMk&YȌ&k٩sNF"\c_A+#}Z#ѤPff%zwr& Y9rDyo7 fTWRO RxEKhF#陿R]Đ3 ?<v=S|ke˾-."+T__V5u:@fAGS+_u5_k%\O/{Vwגch5׭Yu?.׬a?] ao-Ț\{U3wi1ȟ>84cz& t[n17駛/e%wr-'G%祜roʮϥT鿥19)}ISޞ:lj~Rҷ5U/}){J:.VO?O9grj6]L;DO~!_OLwߓ| rk?$fӷHOE--şOg(RP3mo.5T?oy[99=yK!65ͻݟ֟t޾ߗ!=z4uIˮ/e.3]?"G=TvGoR5`t<Ow<OuiOWx2r`5t<Ot<Ot<Ot<OBZBjF7nit<OGR&AhZAP_NG;cJrR 8r~*PRPϮ4'_eBFںn1LVa1wЏw4R&Qɼl ì'_ /TU"UT({ʗM+[oVkTIL,Ѷȿ%s B̃Pz3 Q*ɳ|w;(; T^^^#w|[pҏ؜_rlNLSdOɝ*V%1f#"/-o-vֱHJZ оnv]2u<Dk :@T*'J ߩ]2/5_&o0;^2g藛_s8N9=o%ܬ/V=ԯ|xj_C?xh_Y1轎YP0*E'R;Cݥ?mM-~[~,K+2$-`1òoDC(k 0q}x`<_69f?s"&#.ۺo"&"o[b_ﷃDXDEL6ኈ!┈GvqG?vsΞgߎC1gx>}gmdz}dz};¿*%D`(bOʡ.CS2\aO.:]Y\iHdҾKkYG.Ϡu@U;ٿA^'YgdST,ʊKUQ3O{B>ǒ)syXtI~K7ށ ul_ SW#߳]$]=rV5퟿oQPk6Ki ?uT/dI$E3"^fl?ÿ־WBʇ}ygEjrlEPyhE4J.o+ 8_MO(tU^n^Ѭ SfN{gySZW00{fJ'5##דQ557#3%{faZ,.HIP˛晚)ɤ6 +gR/.R -E*Ά-KI^_ tXDzlߝQBB,s JB2R_jJϘFR?30ˈLф.liB)~O?p6z_m!}e_m%[W={A -E*"I6\'()N .1ODE7-ou(r&bEThRGܥ KDv&Z2T,2R['Vi+bEjhI_i_k;]گFu1Dw(4F~1Co)\=(gC"NxX*R}KkֈO }_ljnoa\ʺ;7.DM/7p3 nnip3Kniynn97XnNf\77Mܔ|5pS7Oͫpn>=0ԃ]z8T7Hnnip3 n*nnp>chchPܜ 7Ip77%pQٝ]~ps܌M.̅Epno߂vGeZ(NwhM\77M6̂JnspnއVn~tCֶ֣Z7ͭpS7f 7Kp6|7f?fc& nn n>p;.՝ Y73psPjV 7%ps7yps7Qynށ 7?)Vab 7M<$ups dM!̆2Y7p<7opns ar\ܝps Jnf?7f#ܴͷ­7f ܔM9,iif*Fwep\?nFp73n n 7p2 QF?cof"dͭp3nVp|7#Z,#,& nJny;7Ops p7Zy n fhwXn+pO 7D#*Bq~X(Z/5p(lOf'(VabG1.m1Evs]FuxbҳOTי|+IT`-8rX5k!BW7lo~ h6+K曐08 Gm6QYg=-dfh8@'cma!<}n%q3G=L+XrG-$u˴eTLe~Ylli]Lѐ,G,L}杒RG[Pp:䀂f8%fjlv3ٍ(܌΂([(8!-$uGNuCO[h qJǃk!qIayk;4')A(j%z+OjhրWRooLSiJ 7SSǍQvޑ8G>B01hJ$[^CwJO?:)+l +U_GjΪpaj_y$+!B#w'F]J:oLQ Q AZC3q`^)U5JAn)g UŠd Pkh=ugJ:=Z maZpQ^l/l3+(||*#c[`2R2:Sv`]W$t+>~QC6ZۨfxR>u=&ǶztfƧCiH fR>zpUyZ,X[iXĹů,y!Y} R]B¥ {Wf[TrTT[X]'NV;p1$SUcaW=WՖM'؝`0F-(o?Lj#ͽD^%_@uE b"REħNH"~!Hia]54DXW5z*BC9U:Y >ׅz]B(ڒM]vє>ZhP"URq i/*>LLIb&vǔ{*RLo%)82T IF9G$%miI,O,SwL!jHW(R?GVuȅeX~\|RaoR|^t 1[tqA08` bDA1yNEecOR|\ N) (_ bŰS1B4=\.MFQ4QA5BF_5|!omm?NtWNը`#}]R:!&?,\ LNX6GOkCۤ픨Q6-!j&9}m'd|Jz1# `6*h |. oGٍW$⣷`wpHpFtA<#i$㎤YPOb{ AݤփM;P]Hbp,C@RAnod7,2 !E$,D ȵJD2V%# m4 ġUPYIFF_$pʰZYLNp!)"-]% KbO\|b lR^kGBUNAbTYKP1 m4)9a@>GRf|zQAR")' IR<*Q_i P.E"HMc[QZ]UҢ jz2S`; [xxuޱ@2ێR":)L 1үOBkU|6P>OJ"@) 0ygT*i7~2?-ʐ=OwA=P7qD#'''E"ś(Sw^6-"LKINN-MMONz—h|7{7¢EO>?"Rj?1+Ǭ};nEp^OOpS˜Q %\0qdcBc[#xeJiޢn?)BI`+»1EmZDȰ)Ã33"TD4.JL@clY a% eIB&vFc)Tq9"ΓEn_g栠S67D6GB1Y/+9a4ɾљ|_ yaAڰC&i6]{L{^{J?ў~N{E%엵W_%iF7&Mf7-oiocS{]{ja}ւ9_j_bj۴6B Wؕz%BLa aaX<g-b?gy+دZvbm°j[f/W^-4I,a8] ٸWF'  g0X'}"7]]a=# &u8|YaR×_I_:n :6Lp.v3Lp>0؟amڿǞ77΂7]1&b-ax 0ug#aK˗حYv`Xmكeoa^l/&a!t{{}&,,طoÞc=>^=>kbn{}>v ^}b_`_]!ב't Pð/v vc% JF9Ƒ_縎s z78nJ&:&R21 FǍ79n¾q3dd[`';S)ةT4Gv#;Ñ=1:RȤ$K:׋u/5+.v5k䯻'7\oorO\o$pme*kq =1{=uq߈{)o {sf7ƽԎ(". EMEQ(o]7.C@%e6BE"h- '-ǎ-*4~ܕ19 LribF+U~Tz?,Skq*R6ڎӦOӝ*~*?GT>JchWyZS0\I3`V=`K~`.7p'KO"C GV/vd֥bOr%\^^\~uG Mbb߻eFrfFYM~/S/"f'fɞ]}gi~ɴ_JML~9}r)s[rD^i'wsi R UfԢJKKKfev۾c=qnGi_ҤI&Df^WUVWY2ͳ-f9 v,WѻbLŔ*+vU\TL3lLe̅YGh5;߾+ܵmwk]-r/h 8g'6{-.dZ\2,hOH;t,qɑ*[iUUWTMJʩQU^US깪ƪ/vT:Rm[}1;]S7^vc]n>T}omɷ׫{/ O?WLKRm&|JOdiR<9K}EWvK=NL> i/3IVIJVTR=/(S2t[e5VJ[{s^{+~7K5R,ރB+IH%J_k[, _gY5kZvuz?QIj_~M/U*/u%H^KNI}]5Ȗ{D^z_>ɧS͝;Lji|Q׮|Cԩ;k1TYj~s|ʯRR鵪**gqUꚾךWkզ޽gb&:SM0ӸԫjBgn-꺧W1tM~Ƨ~ \w{9ni?i?WRh=m|,+aQJg[J‰6*-1oH,LeQO$щ$+[[9C>Y2[ř1*TQh} +F/D"hMEwV1?1|`D"ݵ\nlI>}N[/U^Uv_۽6amrկap޿}#X;dڌ }hCz구.ԧ ~@yxҔ' {{ݏ_}{:쨴N 3P)[~vʡwX(i-Ol1c_)犿l/ kgazvGh=zO6Dwݥx=F.{'kCSST4mO]FkWE5p}vhk7WWjڍ8}v~>AKoҳ }>M+ԧZh3b}6KJ2ͫW+2}JKWW[]H[??-7k+'gU ZAU[o7juzAWW{L__{\PP{BXL{RRo՞oמӿ۵>UC߯5tSd膮kAZa3lV#Ԉ3zvciDknqq88]ao ڍFqvjk+kt~}cEi~WGq|tG>Q(owv8:y^i\Q;9UԱQ89:;:O:Go9֟ıU_KvǷO`}3pr;=NG/3g:vw;/2 ΋`%ys9Lqg3qN7Nw93Ι\\,eL彘bX> 7bmqYmn[wp;,?ݱ'O> `w_l He+=cY7XOpOrlNqZ=tTk4w4w;]߽ؽrWY/ttu^g!kzkq?q?ky5/ZǺ_rd:νu]VD7?qX'[۬)vk{{5#kSbg-;Z;)&ɱYi9Ӭcswjպ0Owc]c֥'9jr&>65yP6fncf)1/!FNZ>6Of6SҤLj~ڵiu)o}vPD]ʛ:FZvj y*Vʹmm3m7S+x0?2f5 NbixjyO=O5TOu{+͆|VR8 ;y1&bu z ` .ozp&IFpL4ޓ20d, 0<nb7`c nsW#|/W8L0YX&5:.{eo:0\"P f f60r-:~?- !"0^a`n?O'A=x rDa6;\f#<(3:r-eԗ6Gce˱W{J K}`-X) ςW_F:uof>/8~i~@:0߲  l6ga D-H9y%5^7w87MOֹOgCf+k\׮Siꇘ5\gq ns\K.B0 `$H$p).J0\c` 420d2, 4\"P f0̂60̅R0xy>ĪdSitffr@ NpX% T pX V`  AT rU9DU*Qccc(.@=k|oAq1>Ăi  '옉= 8DaZP XDbTj0W\vjրZw<ua(XO $x|`h vݠ_ppt~p|~?_7p܇CEP}h *ڂ-AT *z=DEQfT *z\inu@4@/8yx0\a D׻%{u@2$ P-ϙ T[9o\I=bX/Mc08+c s)/@xԁ{`3xloQ6wl΁b3Y 9KtpfDaY _+/sM` oqm?-3u`{Xzq N~OR$xJ,┼S40%ϑȓ9-Fb-FZvtf W.NO8=)A jv5;DDzKc},u!乞u:v=a6d~\f~;2f:߃lvfl6VlUSuqɚ?ZLl9}?NǻտX4%cZsr> @ǝ\.U,52/H`#洊O9V3Rx<mPIpvf&.8;wA3x>V pm`; $%;߃'p 1_7p934^Hgfμ͜y96sm&5ݚ965vngv|vkoK f-Zι'jv";c~<p*@/γurRqSv!ssH88/ p% ջXp#7>8|$LIY\ $9sNp-Z1rgN}-'YONs9Xxs5Xjp'pgq'gq \ Qɾs՝qOs/x^1FY;,ܤ̵;׵`<:Gw=}ma?OzʞOg9'Sm|om@;;+߯E\ ڱt/oAc# ?C |:EgPuhm2ZM$nkk:p?x<uaxgC秐fTvO(an- cL?of_/x^F:h0M` [[mћX@,KfX';D'F?f쟂?K ֩K!^48zIξf6"8QD&FjćdCvN6?ğ"O6'MIj&AĠ\ћ8$.ft:[VԧiE}ZV<jSZفVVUi "}\6[QVFw@Df =jAZСΎrvxMjAZ赃^;xS~Gm 6k4/vע픶 8&уߖIk&;dM0ꮹNyz\-ܤd~y S@_p*8 3@t}3^ѷsNo;.pl47wݠ_| :w&/|ħM|ħ:>uoS&r= I`!ew@5ߋMlB@( Db@,#3N`{aZo>/kXiXiXiXiX)R< .#% p% \ c8pt eQ` nc|s\P 1VvOO?`;;v'&VUi`UXVUi`UXVUi`UX~ss#BA$a6;@OGmN=/<?[?ͬ>ԙ d.=.Nh`'4J]NڱV.îh`W4zcJ}י\!|ʕp, RQ2p9`}\k `\ZڸX0L7 RJ+LSA& [A(t怹̥^p;o@9w̡T2>}E%j,l9Xw$6o'pos:)E*6wougـ+ၵx`-s&8N_e(p1/sf^0[5!Z77E-JYYlF̦C͌0pM$e=hXh;8iM8::75ˢ1'G2>LMIyfvtڜB9Ռ>v g@ڝIAfbYуiwy|Pυ">D~ ԏ>\{F1h3\nn6Wf Wj_uܑ|7 <7L}7R~ndy <5y MTe& L>>\ϧV)3K4gh,ќY{~ `bkǰ1V,&@@ b@3ì)1kcfM1mff^3̋KR n|hSN;hSa.N3;.,f<7fSMRwS昘1P|6f5Ϯ׼'>k?a3c̪!-3&E`L2kT8K7iaDzQO e*F-DzQ=ѨhTO4'FDzQ=ѨhT~/I_Y  }v}*>DJO%ѧBiSEQzSCQzTUCճQ:ԻGQzԻGQzԻGQzԻD+Q:&[P:D[PzGkPznAQJCQJtiAYAm6Fm+QԶmAm[PQvԬݯff5Yjv_֡fY=jV}fYjjVf7fهY;jWPԬ 55kFQvl1jVf-Yj֌fl1jVfgf-Yjքf:l1jVfgfͨY;j֎m@f5Yjvj֌fl1jVfgf-YjւfٳY jVfPŨYjVEC ( cMxǍv7n2Jt y>SQu:Ԭ 5+Y3j֎fPuY jV]ԣ(((J+Ҁ(E(ʍ(J=!Ҏ5E)CQnDQPVEY,FQP" EiGQQ (bE)CQ@QPVEYCQP"4((J;EYԠ(e(JRd(1?k>18N׋~q_er69@O FYa&F8gCqWpX&k )њA`"Ό56> )spurŧy>{7, \{7'Q}(;k_i\OOp&eu0mΡ\C(k< JE)5H^Bߣhs]JcU_0k븎@Dp#e7qd56I<Ll*LYF]Ae\oZ`131%`83f6۸Ρy{|ʸsE޻7Qg&\"fI-*HRDQKAТZE UE ( TW]UW@AKTD!TP BEdچRXO^Od9<$mg#x'gfSMyc^P <{}Qu|r`ߓ~ PWʿNiCؖ 2%dJ0.ζ ӲVqLhƴUi=ô`ZLUaZEzDU$0~VqLi=ô0ӲVqUaZVzi0-c|gl Gx( [l؅djц,c)aUdwpT}VU`pX5V Uaӧ~sq'pA6$'`_S?}?gp\{ n$n$l$5S?u>Ϋ Nx臋y_/EwǾ)sw%]ql ֵuw uc`$7u}`_<0p2 ,A5XVލ+ύ`xvvc`NYډeT*MyQWtPUUBtP XV)j6 MZ&M9ee-l@Р 4h !ОuhQ#֡9М)0_H7_ƨCc֡/З)hK#\ct1S2Fs-YhV1$F\c(͘tPyW0RU:Fi #@'Љ tbQhC֡S~_EW z|Nx^K@ts/:%X@㓼 DfP [@vMnM4&7&DhrMnM4&7&DhrMnM4&7&DhrMnM4&7&~hu{2(r!`08 `&Fyp8䃑

.L/1%&` \ b;\.Ćƃ+U`k:0L׃߃|nPiC]h[Eѽ:3*BM(t{Czj='GQpr'GQpr'GQpr'GCkžŸI7t D`#p>]<j?аJmޠ˾QoEٚ e[ p$+;ѳJ:!2["Z"l(Q+бrtl&ϱT)AYD48~\.WpxK 5Z$p="Fڪvc3f}cD/Ƣ(A*зr}+7o%z_+~EЯA"W_+~EЯA"W_+~EЯA"W_Rt` 6:UNUST:UNUSTE2;.x,+`%|VrX >Oa)>ѧ0FS} Oa)>ѧ0FS} Oa)>ѧ0F @G tI `d>RAp8H+8dnHzAO 28ǃ N٠8 'SS p]Εs\9:WΕs\7c^\\\\D=顢d(YGQ(*E壨|QT>GQ(*E壨|QT>GQ(*E壨|QT>GQ(*E壨|5 ajmo2Q_&轈OIWߋ |Vek5&8lp`8L }TT; `ǡ`!8,C 4:#Q;;.ٛΊh,AKd s;`$u|09G.M Wcؼ86/ d* ZD'Q^nr2 pU]O;8v:gL]}nGP Pԯ+e2qw6vuzEv YBE(d YBE(d YBE(d YBE(d YBE(d YBE(d/ 8 /[ o`G `!x< OW7 XJe:g^8Hιsu;,]d\:sD P\dEzq^\Ezq^\Ezq^\Ezq^\EtAT:JQ *DtAT:JQ *DtAT:JQ /yK/yK/yK/yK/yK/yK/yK/y(:  | .pAWp;8 G8dcq7y,p8dxOOy28  iv  QB(!C!J%do D&8G#g3qę8Lq&8G#g6SK٨9J@(6(j CgXx297$zDl![HT% 2[ED UDC>0;ESd;* ޲ȧYΜ ɡGڂwnݍrt+`9~p'}٦leۏ_f!/d~ŌVUhUyԍ^qqqqqqqqqqqqqqqr::<BKdisl2nn!4[6M? W=(T>]e2DeAOo^Yi,Yi,Ywݸ6('Pk8ZD'Q^nr2h\G"k1ꦁv% &&T}*>dJ6F.2]8 ĕx,'{cXx, Ƣc% pp,J\Wĕ(q%J\ /+QJ'a|r'a|r'a|r'a|2.Ld' ,Ԁml;w`'`7?z`lЁ A@{a |6>Ghm |6>Ghm |6>Ghm |6>Ghm |6>GĽ6>@ |老6>@ |6PJTD%+QJTD%+>NހEFQFʃLnJW7 ͂0> !hH6dR!EhH>' s >' A|NQ D5 >' s >' A|NQ l΂ōU|F} 7[mX>`- | փP"k w͠| VPZ߁`߃mj@+m!P#K4e,4 -!tX '`<(p}.ыʍfXΥ uR vY˂]`,e. vY˂]`,e. vY˂]`,e.@;hg l q3n`^ `,g> Yς}iiiiiiii`{-k^ Zׂ`{-k^ ZׂVM 67`m M*prn ȹrn@v]@OC r\.fں1Yp'O7͠L[m ivpf;L0  ^:oMx#׍a|ց/zL/yw$;>6( 7`eP Bdu<Źԍ^^K_e8}A_y+/}奯W^K_y+/}奯W^K_y+/}奯W^K_y *ʠ2 *ʠgs)-LG,.4x$W^8(5FegS7\՚W(H5j`s\O]٠ tzSǑa}0z!WSΧoGRS \.]IzRԡ'uIzRԡ'uIzRԡ'uIzRԡ'uIzRԡ'uIzRԡ'uIrYŜbVn1/X-f.x,+`%|VrX >];4b8^1Nq֏C,0 U,*swҧJ[A jv|89"gZLi3-rEδș9"gZLi3-rEδș9"gZLigg[ѹR>d?.(牔M+KaVpuJ~'씫I Eup:]wup:]wup:]wup:]wY3e"p1(c%R0022^`<\&A!\ $p==URիJ}VAn ̱"̧"{wGGG`>e=oOC&$M@&-ml`W*]e\&1'g%1GQ(?QW})WI+aJ2YݷFsɒQ@-vN ԁn~fЀ\h ڀp(hNWžg%Y {Vžg%Y)n2z le׶ڃZ픴$"Χ]瓮J瓮J瓮']8~zA#1{,gO2_-Ы/yUFc0}9WU`5۟OgMA5ʾZYWu(hgP̶^wȼK]a~hSVP=ֻ|ƾ/z1X`;u?Rg}žӅ;Dx}_Hc+v&}A8~֗ >1gPR^`.K|)o7`]>a9hפrQ@~vص߁`ޮMCmzYbX\cIzE :.g{׀ l;L}y].Ċo2߭`LTdV >A*q RaYj&sQE'asU`w֫o>ubXjk8{m³TQ8Jfu[Q c3oP3raG o1VGGc )^9ٯsV"p;N}Q-眵^x-"x]^9B:NS~ր`=h6uG`a8dJ|Li Iy"1#aI @ pg xaw9 L'o &x; ; pق\-F p 3orEtEP ErT$ ux7@*z(E{(BEP EP^+rTQo$ pKU`2n7Km9w[ݖsm9ux{]tT$wץlKTr[ŭqc\P.F2*X /@{u ˵ {F I !=/Cӗ:3UZ>Or{ )E犯1K"Uo?շDfj3z wL2mʏ횸FJ7S_-y/j.S~=6ٍjjO@huxo6ξ3(s)/cp7wcp7oc(((7r .r .rvR!Z?24Gq(_bs%P {1}YEP94ҋsȧ圹+^0arfT Ϙu8[gٲuz c{i[8.LLL[W^q6~r~r~r~r~rW r jOg~?7/f^KmE衎rPvha{^"N]ʣv!3y?_0WRnfm: C& M{z/Q7]z1 ó)b]v4qqq߇o+5|JSd=P`H&sRUe*SN/!\,/F=s\`Ca(eP崙_( ^5P(ҟo]=?OY^`ߋe_Bu/78b&K`2.x>/+|Vn#}SVOөLs}NXx=bgTlQg3<[ |Kλ]E{epmgXW*4eP޼o%]zWާ\ >P| fϟ: >9|߀-*$[$[D3Lt0ӻ}\{Uc&-k ځ0toG|Iv2|lwݾ#(3@7z+g/'?u'Sow*8 flIknMYm+2M?ɴěl;l;l;l;l;3uc}$MBBwNKxFvV}D'n@Gpn Yp!YÇHE+Bъ2"Ǿ19f j042Wqvu$'u0. an+ *+SVEx9x98>:ebVlàjʻ(\_#aT#r]9uާǰ} R>rHP[{ާB;NO:7 `ϯl_Zp(xxyw R2 ~,tkȲ帟,2n"d2uV C頫r҉QrD"qPR'q q噃qRu ~ SsH e,b*/GMՀmA# 9׃S ,ݥ&NDgKH{ߣWV42"㔲!N@f<^e:x>m%rcs8O_ UV襭"8ŎIxC>nf™8|fz3ukmb\ln|Wu+Un 'oW9m)Gup a%P\ Xkc<v3F*uv=Տl{{-ylFþ42sp,o/@ѭjNo^BG[w)Ԧ )cjCrE}WeqG-CP2Ԡ8 ex eDl[FĖ2vN@\=蹰9҂۱Z^TQn${'W"7ZY8kWu\q:ZMiuܳsq:Y5fuܲSq:XJ h9.RE8H u\kq:Q18FǨu3q:P]JjZU_֢n-X=ʙFwI}JF_*E96ʱQC}msms.ҽ?űUd(5le[uJ5'aUwDFsP( EvBGQ( E(tTx AӢhf[.Ɏ0 Z37:3BcVVYhb/j֊TpejEr(Z(r_kʲGf7",#"ˈ2"L1ùKUn9KB>PװDy{eyu^^|xSo/+Mer Ve2]erƺ \*gF?HצRvuw" ƼY fhM/grh6^4/MkpY Z>ÇrP.VDOǍ'Ɗ~I ƽ3בs)Gȉȉȉ^rfs+\\[x]ۢ+aF ݴЭ ]GFp)ܜ͙ܜ͙ܜɭغH7ލ\wRL$+op##v*o{ >gr3T3}wř vdukEhJڤuF?.$ 쯨 .w.WVFVj7SCpvvpqN^oՙ76^C Et]L%;|rMuY[3+^ 95zu(${ :d?˫ͼk8 LڻQD/*POsϰl)"t#e Y.})WPeH7~7鷄_>$"OLޓ@qwOS'G+d$\k#&ohY"K*Z-vwe~S+k2TWLhosWZmRz}ğ8l1O圯 .[3zh>hKdp1sپ"ɢcx﷢E|Ng>^}ВgD9{@ho)犜q"+ _ʵ$Q]ro7Ex`X!rFMQ>OsqL}9+=bM.m#l+]+zCVjnW9lEbm>/f}σ)ر+`h[ޑQYٛ/Z7?º-uL:y1.Q޿['mU =ieO'2=iOɡo B;HWiq*rGEF+ъv=ZҎ%htZ2̧5Ac/ؓ-mGKt-]z9ACxzsAB-WqvDcKs-3hAk T!W&Ca$^9/qŗ|;KDy>Q2RLLԂ$[@L+׀W?5>X*P>bGxbm-&U% }PZ$ rD6 ˵3Y  y`(.䘋A.F_\ پLO=VS`3l/hKZ Բ`;4+^"S#S#_r-/%mG,|v]N,OA$?z`.  @gJ,7ovI  ٕ#@ޠ8y-y`p-ޞNoOtz{:=#B׈|z$QjM#􈟞nn:.kmB#Awp=A/p_˴'}m&m@l[B78ov< N3opr8-{_1X@hE-f띀!f5u# cl_ve7r.ƃ`|s<#Q8xท 9K!=[v7O'K9]\.UX~'ɋ\BȤ-.7ne6}Ri_uI\[nhSنE!U90 e jvqqq^.glkT{/-tс+r%?WΕ\irCVh:mݧT[rDM;-N{'WjAK="ЂN-HhEӊA\48E{s\6-@g !=R ̏\5}E_srf9wyIMEO \t4EGs\44 S t,@7s,tSvvaE7s\43E3s\43,@/s\2E/s\R \t2E'sI9sEr=x4mEs\1m,F]Uc:^H/LsуnNMoΊ>6?PL51!;͌}g$bK#XͲLYJڤYONj.\E\'`oZe^'jقQ7[{) [W#~B2/3m"3bfڐ6 >O*Dِ['en'8H/Їd|Wjw>O|w>O|v>Olv>Olu>qO\?oz>O狫^l[ԿMXfVtHϱ*ZSkՄz7Z- D˂fJ(a>Z \lrv3 9(A] T׏Q]?gtrqҢF%QaTrF%j~F'atrF'atrP^Sargrqdt$adrTW!)}%0gq/o)37jpb6NXlaT]\YHeѢ,1j{7\I,z+Kꬍҏ=ǞΞtgu.]hh~U"Bd2\,=lji6gX }r%3, 3,JV sqď=[FgFbEOXd"͟ t"?ݟ cx-\u0E| &*zFݴ|7999a"9->MG\uyü~0oZ,D Tճzށ;(_eU6ƕqeyUWkO:1&+,=A?ܨkqisv]1 0ӖYr0 )pϝ=u 3*%娎1zq. Ʌ!݂DW˜\l9w5~!jY_Oe|a\;ibZ^Ckhm #Wi, Z b8YW.uвݴl7-M hY-+eh&g=~_DкZW\@ 'Nj`N}#/+`N+ }M_.:ʻix!-Ţ-;.k"-H':::8.k"-^ri+~O`[:Rq5ǔzVj+$3Qf/p"raKiKqНr!>#6|K?-HUkuE_Т/hQZAEbhѫ(Mt"EhǢ>s\HuD2/4DlZk鳵qjn>[k.#^neTM;Jbvٸ9[*_N (NP>}G@_/u1}!QEk {^@"Z qzh#bB?_Q{Ϲ׃YNQ;խ l`k?Uq;@ALMj;M\k9V8eS u&^_ZEFsY{vѿ]*:eh!kyΖez^\k6g,{t\{{ݿBm؅j{^YWT*XzIpkK1TZ,\UMgŚGVW{'\k9>:ޱVբjpڦ-BI bkF; vލ쥉\Paoߵ[C_|jُC1a7=zr,ʬ}\t ڵ.P V8 5+;b:%^LF[>&&|Oq8GnkV׬v9ߞ9;jݻN'yUBhꚝ]ѦA|~Yf\S/Kv8}Ua"M +~{Vse9xGܡʢ(_V^Jqk+J5\f#OLi[SoW#!U{ő iwKm+d)=,!Hj$(z5wcv95Jj#Uf45U91 ҨM{'K;Mwﱻw-{Ҽfkggҷ4w.z%m@|~B;y8~ۇT}4w?c߾LnWk61cg@EE]%ܟ۟>TZ5 6yq̹[\x%{S: T6_ʟlmVFTu aWWږ"1lzջe/Ig>>ܞj)qM 󛌑b_fϔ?[{yK䧋C1Uwum T{ڞS NWV'WzR<4G⹔Y=\ꦪg̐qR/ͲNR)ǩI\ڑ숊惘gVۯ4s'~?Veqt{_P}gU+^e)Yf &鼿Y<:\Ok_u>kK?Kɚ.mlZK/Z~B%g?pk2w<[?+0g9Naw>?KbցJׇW9ˢw&\Y[Mmg`orYs,^mm%ߠ[+Ig#^JH&|j?nqQ2G"+ o&3o >>{Ͽio?Zj\E~ 1~ݩIi`Oky^q(9Yb=YdH= SRSVU kTS|s՜B~WtOJڴN۟k}4n|wrX,Uvke ƽܚupOErUM|,&?CVJOK ䷖FuՄ9ؗu俩1L\ Bj3c0dJEZOJBsYrT5*DÚy<y٥*z#_a|ڷwUB@\mR֦1ήȿ˚{׃os6*3?!ulmvlۆbhz|NXPkvլlQEHd溸]2W#CHq8[w\1U< &,ZlooxEgtZk+jC5خu:]G ѺiHvvSkkEm6^4[+uv^oMfڝzgݣ{?nj3YlW?OIviNxLIɞBO\NOLܨznܬgxx<=w<'RjE"jkAZ&Nҵtuպ##^M笾Z9Av6 #\|m4mPiq㵫zv-WonặjS48]{gh3xaP==)zkOiOLDM{F-J6aZgh_kٶ4KmնZV#k۴mV~Ӿ~wiu"W^^i!bGCڏkۚ-Wtq],ފzkmne#ʬ#da6Ymd&8s8%遤GE`c}_fI74YqDߓ^dI/nI$-z'BHz?iL c'"i8>)5'}8)뤭BKI%Zۄ0Li9DtvGOgA<4Ice.I {H10F*Fq_gyߍ26^1^7Bbyxm#K˨yXN W+8X1CCWqFQ.. І5)W33~~n|.t!="?񈢭b[dX^I(ҥSG?M]M06f;lo9>Z2ǜU7py<>ߜϻ2⺸gGg~ gD\$zf)8ijp"M:i-2}w}'~|^}޾G|S9Waa'KMǾY᫠{ټO|>CH93LsSr)CRD)Cr81exp2"erN -9\B\rL)H)བྷ\"I4RjƦcƥ:g2^\r8MO;2HauO.K;*(1 GZqtn0ӎM+ ]Q<;*qҩwTrC;ܹXᾞq!yIBSI$i$I$ik$kmd%+IV$IIB&IVf>v~|̹u:5ww y645xaxa:[W~v'I]ggN÷΅o"^u>U>^SO_ѷ ԓC}Y~MBxMa_%w!܋?3} b!/P^ _<䋿U=B=!%٥3bG(|Vې]ƾokVNe\"Xxygy.EM~ ?ă(ܗEE:ZL%\q=~3!Q~+a8Ʋi g]:@]uKdE%p!B S9<@8 p b|,F>FFO"7 x:7o(޸x}7޸x7F7zo\ x*^XD5pL( g, DUif&-j=OR@Pbq1{Zql(fYLvLvEvj'-f-#抹#!,LvXLG_/PŋfXꉷ[dH6vv8 PA1+ckl&h;W jA]i1V]rVѪZZ-J/ Ŭ6Jkc dמg큻:ZLXK"lBVKYn/"+2ֳƱ kOXkſA"B\[(5*$$ v)a]~/>?}~c* ;;W(d$ ž&!&X( B,oY C̏Guk Dӄ $;.?>8>Dqa`|x|8%E-Hm4H)>.> QH̖$KuӀ wOş<%JM:G:G:;%:_Nmo;e;uj #@>|:$|:t IAf&\TSC{D] ئMC L\iSH8"JߌNS@;v."ӂXK<iGG'ړށN#B v vFyv'S!0Oc`y\ S>q=+O; č)F⟋.KܒRL 5q+=Ѡ`Js{vCCܡsW.)5Rj x0 ݏKK L555j* 4M7tBP A=Is(wOw'QG-gPEZLlb !_]|Ւ * e] ( PV@Y퀲 Zeʺ (+P֛t_&6'6S __jؑAw+ AIavbb??J|DVˁDYg/U)stW*YZ*]o>i¼W<5|OU;}:swӕ?Sģ-?2ܳUQvmGgƟ>RQ=G>w\{ +б|ow˼zE@%t}<*s]TÆҝ@S??Y͘QXFѤɕjLQ2秏 OD'ϏVէqꨥ/ɾ,UC+{ gΨ '%˹rffUownjwZTT>~}{Y.{U;G0on]u)1*Δ_;>|v͢]z{?xg.m*튭8"ǞƏ3>7+huz}w jBAL!kEo[>%?Fi? J' o|7E뙒֯="w*aD玹4%$_egcLoqd?gsu5[,PgRo+bI_r{V~8 >Tד^kf5/juk?9Nq'LxӜG?fE~=$K/CYwK[UT ;DiË}v~?Q}2o|x{>+̿/w|>v2l=LTy&ӠE=Qh E抶-. +,&zkX\\+eIqeE1KE [Ybr01UwY51Rb,%SX1ULec1VbxJ5Ox](ϱB\`b%k.^/KZzR!`!j#ob.^ﱎ}\XWqP|®QvZ|zrQф&صif4GsY_-X?-X-eb|vVS;hj[ؠv[rr?<kj|9녬5㬭)ξHKJ uRRټ(Rj):ߜZz6CRS[ԎN><+HKR_}s&[~?8s,_~#)_~'ٿ,x~:7|?o9r%?s4(/Ks/^{?1#vQyRuT -UZh.ZG-jR&Dj:ڋ(M3q1m.K7D;.@;vJ&j њ КC6m%Բ5Z6Ge;h edWܛQqqurxҌvoPzj(Z?\zl1zB#PSd.IOdPHoD77Sn/PI4B}] x] SoRsle]czιsv}Ou?CL`G(` YbF/R_PccRq KyR_*r`Q)tV7|㛾h԰۬AV!vtFVJ~Ϧx'WӬҹ4P9~m?ׯCuz}>m7PԜԡ1rȿj7T4//auiljr~;Ywba:.~Vwg1߃R^_IGRŇR%߿ֿoo OgшF)<B9 fY$KQ(>> GSX[Xԏryhrshl trdYrLdq9&rLd1&mq-. B[Bh@[|!M-.->F7up֑cY'4uUʙf4v`=D'q9IoOKЗ{=ͤ~@#h>zCa+e/ Ӭ(ic!& 'ȉHyZf+j4Ҫk_i Vku0La{p m0E}ɏM4i.z2I~79hps=[S4^XGhLM1~C!Ls'ng-oi+k[[z66ߐ0H~t8??ys)i~_/'g%+<W/]$6עM&Je(n}h=h}eբ^}˲KDٷ+oϾ]\}GPѳJ*]Ejݜ1}F_]߭G2Fg Fh1(r(2%`c1(5)tc1טO? chl1vRn:Sf5Nn:fʤe-f oVf;Cn6@s9Ec$\9?ќ!nΦkO4s1\/7ט|}f2wӯ2[ / @@@@@SHyy0Aot tk4J}C#u WFF[c3sOE%UT^tdC`s`{`17?pHD>pftc:3[eǷ8UqZN=}!~i4wZ;f_%NO*"̜Mj p9Ch3֙Lv939SBg nY,wV9k fʩw9G{<2wysp-7&7ϭ6pXvr"[v#Rw;(rsܹ|wԥVpW#/0d1PIu׹lYw=Gy \E,sjX:^>xͼV^;o7яx1xz7ɨM5x3JȯŌ4?;uZSc!!pVCDsoŸ텭P}y>1ؗaU4bP23hP2`pFL6#- )Cȧl3󗌙U3k p snnų`;${/}mXN@j`eڹ ׅ|!%AS:)'`qW4|1%ħgyxģ:8),f.,JaxݸT ց,B8f.oU֒(UGǺзuc5u=4\6u0el H YH4cY.1{E4KR6D,Yx /.C34v2Ұ kFA#3F6Fo1ڰcgĞ=-Sl66>0{8D L|v|6[]|.s|^),S4,#\_^cD5Z V}0{RJ}zo/nj+OKzԇH}OS\}V/]s<+Hb4G(-D[ItD/J`1Lb(t1K̗bXGls9;qR5]EhY2o-ՠtD=L˗9kbLLZZ;Xzk}bm6DƈBmN֧39b }D4A7X?.1Q8md=,ѳEڒT&0іJIbd@:}idQ*~ 4Us :\C̴6Ϭa%6jf/6vqlfF3b79J\kfDJ=#yEߨF[Č*n@x ½ETr|Xr;IZ/+E~^1Rq\jAG?㸸רkpf{؅xO!)wK[4{eX^S-qn{A),oz=M}G7.C1mUj3"|S_v(R(?*yƐ[;"vs ‹ʫP)k.7w,_J1rhfirApfVӟGL,[p |7SQgh Ig6_'+-$sws1W4PF}!lo{+ZD>Vpa!=W1G_|;gD擃vѥ?κ|,rYGTawrb|"MYћ@mT9dB~3oa}GU yG>>W'+*&*U1O^}| A>Ds?iOi_ķ}Bb>HPjOQ#+dҳFL>Lt)㴴 chʹVҫP>͵G_A~< m6.RLצj3\%ByǐOAZ&oAg$<^һ{%= 1]גy2+z Mb4JQd!|*9}D:ޏJi@x6fQtTGWЗP ͢2&JRQWIN_mBvSvKBOcu*T\F!*Z>X%%\.ɕ<n]g|!b_/'_I|->zrCMɷo'$&%InOHY*guΚ9몜Wu#ՍW7nC39oqwY.3!pX@~a y' =O%ⴞg~K8J9'(}1_X 3I3:!bX@y}ʌ)(o9F^O9N2V9"s=K[s9O,>2}TPޛ7N}'fRc֐*L( 45KQ)=t)[ևP9[cL <%=T^JCl1V(M=?p(pxYXhɺbOhQяX Ɔe \lu:=du3O!nJJRcM9ZhQd>jx:`ӭ YR#>(-Rc4]bבcT])/:JuTb ;ST:;ށ ,+Dvhcux{QfZ k֞mV }~^fXv{dKuajRh>Lwj{}̘k ]J;d.v;1w*@m*N#}iJmn\Ob @=s6s:H-ٳ(0X-UD@W"8Ls3G Ӕr;ˍ$gepG# g.٬tFp1wPkO<2©e ޮj)CHy-2Mԃ C%zw0(QU[*@̵BAB wWʹ;]Gc!n^,.s{{R*̴e.$@ UsbEB}T9jEWEU+lj}RW(Si:.G@*$D:>7<{!t:k}0yJݣTW6Gm+m!ر5kJ .B.#Ok\OY *0j[@5: e‘@NRY0~GiF:sfpЀ,PBju:*quԸt"X}ZV1PY)^4ϬK >ʹwh@َGUJWnp{k a{!^K uT `r-%tւ2Tm*tCJCBĀZVbU h(đLgRME-A\Vh^@4܅sj jP);WaAk5q4$nCeB2@+Tk*v +e 8 EYE>P< x Mլ0r*^8(nA`P + o -JZŜPc`FC<00]c>GґP!F#S* (,C#PbLa0aJ>rXp<< ~!k$ԆU nLUJ-Vb>RMj]V1pw6힩qGÚVLUZ‚}jH7qt_h1 NurHA_!Yt%r7+@9X:7\TO~cէ{dd|6]@KB kVҷ{cj.ۖ6SLQGN\!֙Gx5v5뱛 _/g .hn 4?Dz;4Pwa\fml"9y`{S+b { kTo`j֨>AJeuuvT! xPxX|&>qL|*ЂRdxdxEZ]>om67 p(ooč8j$BuW/Sx$/{I^_-y![BxN0>_??+d|\ʵ1\_Oȧȵ1>5JdGUi>=Q5qh$|~iZrM|%k%%—%H /_Lܓ_J&J+|m#U>uMk7%{Éc|_xRC`}H$اoV xEEHI$-BGD49=9SēV䟓ωrOO~<,K~Dh#D['.|h'(^QDG.(:uAq\SRE7VtKzCtOmJ%zOjkq|Kڝ-OI A rQ(WEqDkqS[|7`U o:ww#Ƚ^?ۯ"FZ~ɌRɌ$3&HfL<7[eb|ܑ*W)rq:z1S<OJL̖dɞb?)cb?S/s?? +RZ׋w b7x=f 5q$Mvg-5 Jv쫵\?Z?[hT\V;Hz y1gv)g);̩ P(~BڃT_ }$93o]œxR> l_X}Eg)(J*erKaRze saN>Nkb5]0w)nem9rT!3jQ`9D2j $_ ^,{#Y ?p^i^c {f.wY-(\4OI 9*I_oow'ǏlW4Z?әfVLYdP{%t"+G\+pJ*ȚN-6zIo SxrgQ`9I.*w9M)th#kT/-|`3Ty%5s1g7roe(ezw+bY荧2ЛDgLYx3ݏfwj! Q[Z)[Omݴ{Hj/R`ΙHA/МBڍln93j75?ؓratO܍ GVplpԸdgafG/$>\8\\\ni n6n 4  :S!#{JV3<;<ϚXE\+ Jh2,2tix}xSTx[xWx vORfEHH-wGEEVGGZG:Gǃ~gO_d@dPdhdD=X`LL3#3vHdu"TdadI`UdydUdmd0ٞ-QMZ@SWP䈹)rFW'^9,v }\#&AP{ -Y1TV_婘 ;FUj!c.Sl-_Oir>KjRFyXżbjNG(mGV2ItKN\7,xccfTi)sӔ.;`ڞ,S}h?ܿZ2+$ÂYz7.M@,A٪G1_ an^]i`W-MJ(b[djojT@hױ D3/5^YV;0)*Pwq^45(_TlbF9R(FPTnDb kPJfU"WuE bLiq(b1k!;ص/Q3 D(, z^K6fJ>|SZ)άQ]M3|ѭ~iqceҞasBnPZuܕfs;aЀgb`0FSջbR1f=jvQOGU@M-øtyv,5k^Ą RDag!75f6A>j]TXmGV]WUZ1PV?T; 23x3 5&+EEIu֬zcov!rtPz/p]+eq@TLs*[ڀx5_0|'ariثR KkĚ-z>+a)U?6i6LVcZP:ҙ2GcwSK_ VDX˫3msڙ6P[MJqV=*,V;L3'G{gW_/+4+Z3?zWZFȓn4֏C(:ݷ|s9etQJR1t2)ej>[ty}ɢS^{(q$yus*%3(Ϲlcdn~gӯ27 7~'~ח;P5ӏK<(9wSMF3;]y.= kK`1.0;zRHKxcz`1+-yX?NnV1jWgy#LQr)t5˰P_n-!@d4ʬ=撥,qvohv#>vQ eޓBk@gZ |Xr ncsY S,0n}ǘSU?Nt!kNYb:gשy؊3N}\JjbJGcn쓪vgά/f2y}̓V11oțo0I=;k5 Cx̓Gs/ ^S8 F#2J L $ `)=z==ſo"}|b"v؋3i"b>{A&\̤fsaKk*YH."f1짰3q#==ÃWI)c]u&OJOP4CA ,PN~;UH J%ԭc\{ 0:}t7E-Z*P, g* h1UnJ ^F w5 e1`s,}~wF|G5bTOF+3]MYdށ,)P:S@n#Xׁ]Mz]ScߡLKGط+OվFCm#ӛJtP)lZWL ZAz\M6e>B D;>xbW}8)^%<8nD.؉7iAGk4+pj/m Q?6¹h!wEWb;eʧZډL n1X3Nx?#<I_OwVP=q2>3, `p pCZP;,zڏ5{8`MŗO7H[xqϸOs<FL·!e!s`yer1WMu8Q{~Xo7~s[1L32jjϫRH5)HR{*=ٛ{^P3R=Te?v*Z=RkKJy't>?pR:OSvŪԌ\jƧ֟PjMT'x@ۣffT;̻8G3|C5}2 wwR4"bY}}a׀{)rx5˽`]2`]J<A.c0X`]..rt\ \FuceRȅ? L,xI]/>P>6e_cBcBɱˠy%AxXx1%7R(Kp|@2*| tB8:LJs9I?T*(BϸBUJWA|_#_ojܚH%mP,bkɽS-~by B20B$nIT)lR9R($&aDnbSRu%"zHMRjUo%"|(|N-b0'I>'@u(TI.EzÅ>" b p Aozÿe=$mw@E*w"%C"Kb_կ4ߞyЌlih^hAh.,2:VЦ6kjhWhoh5t0t8t,t2̬mwX {6{y8F_?\ZwRV^Qi,ܜ҄pporkgO]5XJAN"<4<"<:<6D𩈰縻"VwGB+C5[s;KAFHmosAlɡ4`HH[ْ#IQkMn^HHIdphod) 69212%2=2:YcXd) _dud]dw(-DDvD] pGѓruJ;+RfRuh$MGkܽn43ZNJw63Ͱ{FKт0c"y=mmmP]u7EDFXz.:<22:*:&::MmXWt;k]`͋. ˢ+kIAgmn=mNIt==EEOX̌yX̷{3cUcűzFP5LJb]c=c}bTӜY:Nil@lPl=:6£ccKc#^,6!6ĦfĞZ>+[Dct{3?'ΏD\d/_ĦFDS6~ w:D:%~-&-Ndb1Gɰ '5{Lsbe\bPF S$E-b*m5eNA[}no+TlKH 1 .oX%^( u;WuEh\DAk<> P vK&Թ佰j臠J@@ --VڋX WV<b"1V<}-9yDTZUoJ4r{b-.vhhBzj(+%H"2ǥz48r63v.B/y_ukt*y`(V&$bKh:;$ȴγͻE(57cb-ҶLJxYq3" vD~Y\xX 9E=K\f2~HA,=\u%h#KHt7p__pUǰm)S@ KEc06`/!Jv0EpcGА7av{+xӊűD{htRoYo«F+N+ 뭊j %[Cou)VMzf[Bou%Ve[]Us譮ު%VBozn[UVBoz[[NzCouV}`-|vW_oxPyCYVJz(aa{Y>ٻC۲uv1~d':nl_ֿRUOiL,l5kb~_nġc*Jʠc :1]:&%똌kE aT f1%w |GQXc+a;V_8X~V`/Z'5ݷ` ovw{,46a u^#_a{5DXC/ĕ@(U@h"}5kt !ү>=gǤ=cZZ?moe3 Ch:YF3Csַ=G8ΎPg_hs84ܓ([vͦMC#9l]dڈ">p=B>Nl[Jׄۍq{pޡ2_;0Thlgjo?1yLDYX:FqS+p\Y/RAdjja(uFY:;]lOŎ s^%Vg!f7'DmBmZQ645|>RUMԡBMz"TaGNJ>Oh3"2Ƒ&"T΃Fv"1+[w:1cmh{O}d@NuN5uF waKNu<tLd{NPe}N EgY58٢FQcŖ368CĒF upqPMt4DFƍjQpӚhNTDM c{:+čNhp:%ʝ_&{xg\FdO%42$ ]Gqu(wKVu ތ. I:.1'1!,0+]a{I¦ġXIhjHxoTҪCSH&[D3B *w>'D&7!39")tJbKFIHp%!y 9ޛ<<eHD--Hi+#MHӇ'E79ȄM'uk9 趧ᣛ#B|_}D+Un 샧6z;QRLpi]h$KAŊ#Y1 Gq<F Lמ|l/:wŒdN/8|lހ@oGWF%GUQ5DH.C]<}b6騪^ՙr3Lk.CK]2{l yϫT dc3:e)=*("9<νi;ݖRNU|zb ܛV)$3Q hވvxOw^e(&z)Z'PrOv!wF ֢96r m ZNZO.ݾcpe(F֥nϟ^ʳkKC 1RD:}bYzڇWuXډk`/(>r>{5{탱t _.яF)W_J(g,P]zE a9;UE*.i2h7#U3jGa~;-zwxQ]Z(vx#k+C+]̃RdV6px,^y՟W?xz =EGwC/(2S~)J^ Y?Œߡ%Ǟ^Bn7]WZPbtu(=n方WQ-is19"p]s>ok 'mtf~0O1f~ [Pg*QBWQ K;}9F,=Q敐Dݍ)?B9=Dphhuq4,7x\8GOFrɼj`oѡ#M#D|"̈wMkE/<[8/`ts-l <xQf5F1[K1zr<3vEoF +y]5 S41E"w%(G4=Pg8Zl?#jepO 2'`$O\WkP77mSǺ?zkfm0N*ncHae8ex=Wc}@xϩWtVXnۡ ){)_Xd#t3ZMyX0r:uru'ܽ7zgk8g-?9p L921bElWx݅kbDIb}:a_ܝi5={; AoB8)SisyTtijAM:a~F#,+tĵ3VEߎ6uO䡪8uZ96&$rj5޹Z "*X,5 oTJ U+Wu<@(F4jİƳaŗ!E<&ac($1^Ư<ThZևxgSO}&GLJ"\7BJw)>vEp %trʻ"PJkMjF|:E_AF"=}STy}nȨ3C%D"!BdrtP_'!cYѹ(C`/n~zD:FEY*ճ_LE&ǜX̵b7nXX{Q-[H;cj}@ ֱٱySVֺGkį dO؇9kCiwc J yxx+cxxpxwkw(/l3ݑ=(7N `kgJ%nI>DEl*t o]]_eȸֹ)43YL1.N̍e }IJJ"XOWnQA%mGpƇ">|.dHdܒdd#'+dWSݞK[NyS4OsNrͲL&9pο < 9#-L\\K=45->}REZQ3VȌd̤hXd3KhYYYِٜfK`CZ{ݪ̱@5\/^,V5ZQF5LZ[ti/&luoVRzaK|g{otE{ǽSG~*=ϱ_Nkx/+N~H[\|c#ABs}[p?=˟eQel_MrkIr|tpTL!2?qأ#N6ͤd &٦$fh,y229;&.![oJ폝Lf^پٚPPvdvLvKDّyمCɅ%uUٵ\Ev;%9-Vmكɜ\"͹NJL}+Z::zz ~AQK3 1}=iɓ᩹9ᅹE܊Tqdnu4ב1N)‚;S[Єae1l1xexذs`n7OCP`$. &lHB^_Z1H<籸HfXpBg6}](!՛ {R{(';,("}XU-Bpill¥Qcqi,ȂQc~ 5{^XQ>&X9]lϱfi#p"kb{Za }1bKX"V$aHn`n1]`X߀v^Xl Ir6Ȃ`vOpyQ![ꎕ}*CAyªn P `kGA捷#>+"^;VGòA~9\ Хwسc.XAA(%>IO #$==O+LoJjYF9Sq>+ƕȣC?D:lh1݇oW)bbA`Ȼ WS$Tҿx VHƌ{[NXKiG z[*$PZG@qb_:e§D0j122EN<)?]03Qj~T|ϖ(#;. | wنʁD plD鰞Uк|gy07FꗆnV`|Ct/AA^})~x(Bl&}e "(_J"½liVx.v@) ^z:_&r?kJ#NgE[B™ y'[E 7vEu+,vL^;ie)[9*\V|qPIRiQU̫jDbZP]>MޗûK}Z`=Xu5Zw&j "A6bD_D_u3}.W2}n-5V3,t;^N^ߤ;u}]?izEP?ҳz~J?O|^ O"3X?zWB/eVw>̭aevNo]s',eε+V*XMVjc:Xtg]V/5R\k5M5KeN&q T s5\iͳ[%_dVfOdN|G38Zʣگdx_7<*,u$k5ek}~=n˱wS apsXMv=㔛 D״\gvp;t +LWasRޕt y-P bꑕ5$99JxC^Jyj'KlO?>B5PszmW ~b./>V9;X`7cm褵jZ=D5@t Avg?=¹(s3#娆aj⎵B1 }wf;_:dh6ֹ9J9 F6pTeҚa/M#ʦsRiG$ZdЁ F(RFz,[,/U<$tj(Ǐ]>RYa|ǂHǐHP~}X K\47%zɭ BϤ]F:i(v!C"-DJH)I3+.J˼^3 k]Zyyxmzmr&Eһ}}}Uyݽޝ]W=^)z赢׋___/6}'wԵ5#?݇AO0OPī*1/AŔV}_݆U6n\_'G=k@2GޚAn殅;|,wmf5858Qʣگ[uQو;oglĝ4s|z.z"٫ZGn4GQ6Zm|b}FHmuRG;%im~&=>P>q9(oiG̥<=YFwZin8kySM⹙>@90_#9Jr:h!hH9'ΏQW7D hb gmu'>/GsIJ!A%MXQ%@[2Pp&<`>7a ҊMF3QOD9>'QE"vOL*po+(d[E߅%% *V" L{ O|f-J& YS0~`@mQ!pzEZ8="QkAEq(bKb9HR>FU|"dO^\ElX _ ;{A=J*h!33O K/Oxp>Цxvu!ҏʬIA:YYmqC~d>MiX?/WH93T+ 8vqOa' dx&ZMԤ8⛵AI|5ٓѩtVNZV3ڌeۿx|x6x6d;OJ?_ 2)xb8 $oӗަ2v>1|*c/16>V]3/8\UgGK>sTh)jcz7q jc[Ń4ԏXn \CozؐK[.!w4qRj1"u-Ԅo9Qʫ%ny9Q8[s4 4ʬX-CCC#Suq#5Գܓ})8:$"+>^)ÝŵyTsV-n$ݵɉn1Y;nl2Pܽ掳$q(i X¥VƓ)Bߪp:kUt9OK$VbBнg ٗJd7кn+\fN ho9Kl-D]/]ZQ N"{l`Ɖ:ط no2g63+ p1iEr:v9*vK܊Sr~{(T~/:uFn3 c7dVv̵Py,{ݱNwӝL)D&\ 38Bjacīlq&:^ q-:%V\jljn 0*>0n3F5a&ԄTƒ4f~=TN8nYxco3R-FgƩV /Qq74eԲgG?թpP9wWG w>g6q'Riⴈ4u~]$>n|tHW4!]Ejܽ.<*"Cݽ1tF#1"Sy<1~hdd)., UR\=^C#`+4ƚFޏqH)C}i\vI&Qi;nMGzƌ-dM"MinwZGFE:8N$I%}4sQ?fXh RjѱnDŽZ{NRxtEty`t5`Ѣw97i z(Z=B#5nQZ#XKb943;D6hR+Jb%WhV52=Qc-hĶ(AmPkƪAnؐhHzIqWqdSbB8@t~dlzlzh!-tw#8ր3>DSx[QbܭnGn3w8|EokNDWg3} >} ^ooZT;% zC3}9:A9>q:#eEF#y :-*> 3P|5QEbjOG=Z+͋2S|;*7;U} g">עXh}?.Vh2 D( tY)Q4L7DYhU Ÿ JDÎ<3)v@6óJTuí3IP }AXOs58Ah_c5kCT1\",WriQ^S\bܩ-Fm1î_";,87J*ӛIP#wAG_x9Ҕ腢a "D(1#pz / c[ 2Zn"]C/A_ *caolfZq2:Ab .U[@1,E- 'S%-28/DDtD /[B!DG bWJ:8xA>Y<&k@V&fqxWca>E Sg:qǽ%2:(i}1׶( L%H[I"\&켊>Bi=`[#@1,Q2+eݓ8?wكHȂ$VJ V<-Z^$^KP! 3݃Ȭ8-p 57bAͯx2֫!LS\{#k,cR-H"EC&! OB>%X\*@Y`Iae1I.qLJ"H~>Z*hY?E+^#2ti)dUtRq- Q3=D^*9W`Y$u"ƀD,X$!2C%VCX{>ESQ(ԴvǢO_&ʽ=>= Ɵv{AV^^BW-cݣ1k;!P;à܌CݍXԇH QG#T4HǙ Oj2uᓂ/ѣ1ұ G v4?S E*#/:>֋(7-6 #Vv;:2ѣ#b#HGNfGWF342--v-VVEck*Ɠmk'\܍'j1aƑfȸѵё-i__;_;_M׭.`hƊ)DMX#DG!:Z(IF:SuKpDkQueё.mϠDϊtKusўʼnewebMb}Hb@ס@&N[1t\'cndFWMel쐬Lvq7MJvO,79KOzvKѧҦ&g}!h>K|EWG$(L!?%?=?+?7_zş_P?c}$s77}-1CbL`r:5 H ! INwz<-MNs؉=mǂ,8! 2+~ i"FbI_8PMjA9r?QSUOUkCDr-Œ*x \@e;"](ʂ,STdbv/Tz1"2%= 0F`$LRB `T ʥX li_,H:XJ} Y 5A̳PCyPsfO$ Z!!E `B`l 8vKe'o"=+C܂%\&*i-x"_,Eh l WK1Eb(u7uy K!mLEF  r~;0$o&VIH #{tƌ+,_3 Ť-3QOU2QtOZXfK$Od@$R;Afrd|%#ȴm } "-;`e0͗1sr"ЖX%8%qcPkGdo'Z@B ;1\ E)x 5X"Ǻd`u t|X+D_ --eJ*B+ŪsQ*ш3Bpj %0nUlX`-p f݆"qmR$UxSyl` bB:o<ƪ\ Ay}!s9AzX UckuIƳx-` b VY% RD܅@FU0%:ץ BZM1*sĹm"[B#Z3RDԆፒŴ #\5%i(qTkĬ7$-x:_b4 2D=Ň\F)#%'@aeUڸ#mH+zmL ʑ]FߐUKd&R-ڇE=~{ 6%@3qKkChI>VKڂ1Dt(Y1Bړ z?#zJ+OEkcc#ƭ 6v6󶴰`V/E+mVXK ͫ_kd-AOq]we~iZ8h$X2?U9hsAia`ˮR4q2q3 >wv?p-84)@L$;.{r򈝆*G>50Ӎy35ՈՇ-؂ zw1Es$z.*fdFl8} #yA"b/s/U R{_-IM xP@@"Ĉ?51g$^ F 9qڂuOb(ٖݑAVZ?67Nol~ }'635vzDn!(x Zsw|s-\ Xz, <)_ ' ?"2_~MWYN!]MqF!8DDz/C5^@eC+sh.Hmm @jڪ/@jwF ` E\O/7<Ě4`h# G-\1_Y;˔:6>C;AmC|?hw %Sꮠ-A;"}>JP ;A8>:uHY(rcxxxc'-gZYg%srkmg~odb>[NXNZNYmy(p̣2gF [ۼ^/{/f_"rײgnʾݜeW٭ٷ۲ogg:3쮢5Ek/Pk.jhǞgK?;zܗw }QʛBc?櫭Ow zիϜ2 41eiivF>])N)^Hk 0j)71Fc|`$P+GSARlg!}܇?Kٔ+ >kיsm06ߺWJ5-aSꭃbqz57*6޺vn] VY;@OauWi4 t,tIui>|2P`ܲ%mgTnc7{45jXTj.޵UX%zz_]f:.|+-uw^y{oڻϻOo.*ImZ,6 ֦Yלێo~kyzkg6}g!\ VmPh*H!B:[Kwҗ-vⳐ~_H雑^U]~.#_f(1mZ AW;Hap|\k,SC?2FU;ՂV;{@c&@*RM/hGW&soPL|byC.Rk)FMT_Uq5A}7m>q͈㻞xNu3V}KM9S F.{Jg@t*WS_sW=GD)n*U|X^b5edw `檵P ݪ$~f l,GJ X%=>>nVZETZT1I-SAuP՗mSԣj٦v̨FRuϪhUPw;g"2uzLMjXs8I@gwpsr5A{@=Բ@#?0J6mZ:t >`-]tZ AUfPswkπD$SAA޻펠]@v5 !#@G:5tY@ҏC%AWt=IuZ=IEC8hsVmAۃvQcA'N:tΰq.] t3aZ^=!q!mJ@ z]5h;Ў]@6l#a:thqA {hȰtYsA.]r8ah zM[Aw OĬm:2Ty_zvc^[ˢkz/5 U@qF;o7|iRX5#X*TS__CXf/5kIVu1z7ژL|bc5qMCIӍusNwѽu'z^w`V̡8s9\f3#2V`UY8k5ZVgY:icuW!Qʞ\/?g-њIj9u7~ɲw7wyoTy 8ws~8|Bܳ'u~y WR|ZJv7<,˺5,0Q}]M0߂LFXNwY={SOLlyyczXgSs ccI3мئ;icK-]L; 9jIߤddgY߬oV[hEJߦoS[v~BSzp??a}T5"#'d6lˈ!;d62vԎS6g$eFdFEEQQFh̘IflfqIf\fQo\`d&f7.3n^#kyvP«0y*cn|0&xwxw4&zwyw}՘m|7&{xz) xo7ՕݬoK55ou7Է2ߧ{5~=Y?w[S_6Vg?}O-e>}Jdg}B=qwײ#㹡zT~x/ ڼ+ E"sHi5h62J900QxH3(k6ef ͱ557 ^566dfsr;GNNulAFUF=2F74}S7lm4EKV^.zi+E^-zYkE^/z|荢7%E,z|.u1(䬕*߰|zVAkZ.3q̤ij;5͏i' gzǻڍxؼH22^dld#ȣޜ4.0.nQKnQwa֑`_Fe'?~0Z߅_u3Cf^Ɯ˙x6'j\u }A%TjO\C"=eg .S]Lݢz+7C;GRWԍ!uRO)BX"U{YjjLPoaCV=>P<@I:=nh(U5ƵƵjqQ3qZmt1F7zݸ6}jϸWcgܯ50R{붺:o}Gٝg~9퇖獨~A`zk#7FZ32??Y'}#Q0 0`Mbظ,1/3WW~hbf>d3K2ی/dgv2=e|Ӟk"^xқ6xOK{er$uKKƫ/Wޯ</~A~']+E/u[~[_ihcfd6'g3YOO9=5[m-͖9ll+=71{~:{G!bT#"B^f$*wyuܼa0o曙˗Yk~1)ְ\k|~^'gɿߚߟ?`80Ț?_-\\ZRzPZ(~^(+4ZZZZ>k,\_ZSWg\0Z[)X CW C í G7 Mi~Nn95m^To~A+wiAEhIhE%hϳMh߳CehK9{*O|U'"yjL;gT-W*kje#34-5ܻĻOMʾ@\;yC5ć*Ee:f̙M̦fْar]`r]dr}uu%*+&5&u&7LL&M./ar5&נ5jryM\DM.ondM>Y+jy-|^&צkry\TM.5NM.[z5 KrT.om5o BL4&Wɕ 2BLL&W+frE\\Zy3:0:2||LB&Wg&W&WW&Wˇre\9ռ(W1;ՃU*cr3*\\L>LL~L[\\\\\vbfuCz\QVcV/k/o)7>qp׸\/F>O-O:9B0FG>~gX8&ICda1ԐÝ>sW7<xěyȜKBW- Ä 5a])D%!)#OƴԵ\M w k8N}g7 -u(yQ cur$sG^)\{ב˃O>~ \+Wu;zp\O+Wp}~-`u4 UsCaG΍~k;~ʍʩϸ/|=M$!pSi#tQ}?On{z>~qxA'o?밇'|tu|8qSEgK_88t\u|qㆣ)¢0UH5SM I3Tk-՞*zw{ }wtZtFP:+}$>KHOg%stER*}%]KIjuA6&9ANd,EҕBWjVs՚"Oir@Nr3,9p9rDn(ɝ|@.;]rM.%rT +rOE/@ ɷUm`y\-?k`8\iϥ/nאklY5kkk!dTL,l9']s]O湖q-w=ZkWUmέW {̯Enbe{9q3nM;f9YΪ/}Yy탿!d.M^"aH?BΒ[ǜ܉|Ld:ߗ"3|5Yo'A"+''ȯ/*|CFHoh{v .8=$/;> JO>9RRS.5k89umZ)璲,NR;'t)_eJ] .GpAHT%njHqwH %܃2JMߤwFYy%'y{E^#?_7p;W8-rW3 I{8gss/_;!!Y[p~||y**~gg( {{2PȿU(G+  ǹ,n67{=s "bin [=-VpV Os4a0CxLh/f &O݇^܃)`F(|aw$ 5}ϩ Z}BߍF|ỊӈGI"̡ $IJ$89bqnpz>"9ӜpfُIYN#Hm0}A:ѱtA`r|'{Ho{0 '?q2yBJ⏜G~}bBn[3rkGnȡ9t;DEґ򖃼C~:"?H:XMƵ' x+ $} {]wo}~ԣ&Sr{M8ϻn+̀mb! ("/O>ؠR"9s;>:tC~ Ur#a0GdafO~ RAa#aJ5j9ݓeGn*o=kINPȯK^m]SG;;ɍA8%ę(qJQM*nJCaҏ=-.=*͐'MǞKJ+_J+_I'Ğ_I]JO >E Jy[nAܭK޼k$B@A9O>Z# f;TgB U捥R"ӹ,ŝDSZ֩1ep:P;Enw,,ZNZn9ercEgK-W-_YYj[[[޶r}i?,g,Z[>\|jlr%ΐ 'vމ;Wa\_ÏQOk?/M|#G),_`D!9.BP!UÅQh^'LaZX'l6 [nxG=-. Wސh$0dr "CPefa0paaaaaaaaaEFaaZam4Ơ1l`,4ˌ}5ƑZcqqqqqqqqqqqqѸ͸ӸxxxxxxxxDL&hr&)dL]L% S? Siii444`cZ`ZbZaZmZg`lbnm:`zttttttt=OHL&H ބ@BVBnB~BQBiBeBa #Lp f&MX,aeš6&5aGބ %$X W O,E,/V@8@($U X%B0&8X8DZ8T8LCV Gm?oD!(Gw0֛ X+x;hq$OLjW ^O)hw\pXx/x$}'?$% ^.vc7o_c"ğNN*|X|p8V-$GŇg6S)N%>8[(cs3狳2/\$$bq.SKyKψ B\ Ki_KW\-.Y5 _W p?E̅b.`g=``W#` X.=΀/_Wnn$ h3"̉)\<2)W RżE'w[̀M|EX_C'V]w_/|]|  qAq7_DXٛ{%:aGD.xT<&1-wſ' xB<7Q~ xZ<=3 ϊtH;9Ӏ Tx3#K9ŏ/?"^Rx+3k"ks" & KwD ȉ_5@A ^4 b`p13lDbXV fI[S,# b (YD@btZ. mz,)^P g'`- aQC0̲-i9t%Βz(EY\{%Fq#߂#jN BoL"]y7&`/ Ÿ7w 5d >s'a+yy2&S2"0A2EMHFf$pL߰G.S҇|n`HjLT[d䵙l1[ilv]fcj4saAs9˜mss;s.>s-2X քGB> R† N>C҈ۓHmI%]JLJQ䧒aT 9:CV+(Og%srER|\SV+(7&ʫjPIMP$ZV}~-ju:HURoSCju:LZHXޮTRPTRk՟ջ1=jzZާޯL\>WT'ICdu:K9]9|H'a+N& &Fb02"F7xx$3~@0?4~H$xxgl2 o2DaJ3vtS:5br4`tht?3M4M$MHg)7?Iii)62&MLHHi¥Kgä,HR@*,-H/󖿐ްf&l[*M)w2[oVM!{m'm =Jff7lmsASyM3g_mVömۯm&l/^"66]]= 3erv%9mfvr.]=d&{\ٻ%r}}%%%?%''sf N78KʨQ5eyrHt$rvXye#z|~OO~YZ*g'c7>('y{oȩBǸB@_.NHHZ;w[ζpb$[E-aKK!,}`lZ,c-,S-3,-,--,k--,mCv>kﳰVlf%VU:nFy.ku:::Zogdfmα..nnnne=j=a=m=ghbnm6MymA[Vh+>66Vci&ئf@g[l[n[e[k[odkm]]] zDy.`~`Qz8$49%u -G'W7R ) S))))e)};  SjwH0e}&]o"BH?p4wP`4H2ؠUP{ }t{>S;z􍁶 7j z6Bk%$I^) eIRTL 8N$M9NBZ-6H-vit@zK:*NK礋Ule{{B/W~y.5' 8Bg99999Ylp\>>{[7\\+ʥs~nֺֻ6vNκ..t܅bw{{==]^^^^~ѽ{{$zz=EROSLL4xxxxVxV{y6x6{zvzx{{N{y.zx{yoꕼ^o{JoowwNX$4L\"2Jיޭ0j====⽮JbUA颔(J?eR WF)ze8&}dzmz] SgN8}y77oKߙ?Pg/_ `bt`0ve>`m.868!858#8;8/8"&>953x x8xx)x5x#ÐapfpFŒ⌲>2gd̨ͨ1!cjƌ2g,X6c}Ʀƌm;3g8qk\֤Yd-Z.kcVcֶYe:u*lօYײI)[vd١UòGdߙ=&ٓg̞({Y5/fo~9{kg>}&|7†prvp(. +;cLJ'g / oo >>>> _19R))ɩ3(:gxΨssLiș8gEΚ9rsٟs(HS9gs.\ι!SD8"?D"y.HE_dP:2<2*2:Rỉ,,llll9999ގoL_/L7pE=w F܏x-041#&`<i&bD̓w#Gx >8r!NAg1{0_Aĉ!NAj- "-W1Ni,eHŜbwYʫye 8'">8a YGE| q>ܫDJ1Qb^+%"ND|q "-'iRl)nHK@US^4= s&a$*i?q$-h@o@Dt yסg[#uw 0b2RRL]ESRdcOD ރD12e1Ƹ)C؏4nP-UMռU+F{hF^[bz'`؉{?JMC^!p+eI?ݏѸi2-B~Hê!b6 451j S8LAN~j; iiYCy8 đ" X 8r!n6=;fv0ڏ^L+̇qz>:CgBĥ`n,7f - w=$)i x[7l5K0> {0 11K1sjh ^'5wYFm@ 4^BfbLL:u 5KCMf֐B/s6լp1iAAK}[o(0SXf~"&[ѳ݊V rH5A}I<7,]7i~>o (75Gd[1nŸq .0Ÿlڦ4r#i VԖqp$m}Ium@5w"Bџ6u>-H˂ŀk jt]4b.;HS"-.EKÚׁtѳ]xǜȩY04y]b.n±V5iÃ4\m4âՄyT\Mh{hN#T#@lD܉q/ĭXx]"PzZY,,N pv;w#Rn} ;|r@??TKTlP+A&aIdA&aIdvB ;PN(a'J %rBCq9!!8oA܊}"H't"H'"H!"X6esjG;qՊtĜ1gG#R`-XKr[t N)@:H~ ~ ~ ~ ~ ~ ~ ~ ~Pu:o[C!= (wK*!B܃G?aAG 704P3 _aok,=,# !W kZ\V eWUbJ_+QJ,U*T%Ve+YY+J+JW17 q?" PE"}C_/y< ܏:&j/j| "Hy R"HV+!Xv Ѯ"!Ha6y Aކ !Hy6y _k~ үA5H Vjk=0{aT쩆S fO5̞j=0{aT"_-W"_-W"_-J["Zk-㵖Zxe2^k+)O. ⫘ 4e2A c1{ ]0CTRuHֱ R~똜uL:&gYcr19Ql3F9Ql3F>zGzփ t9GYQz-ۯo0k5C8=)C 1x3D,;]E Dn"ҙt&"'6MAjS6MAjS6MAjSPSXMam4FSXMam4Fb݋mt/ѽFb݋mt/ѽ@}) p; w;Y{ q;FĝRdY}AV_dY}AV|o>7뛏c}X_W+` X}VsXsXsXsXsXsXsXs87?{54ƟF\v#/_E\f#k1">ܩ~MCK/giM/O"GXmc?x_SZӮ"6}}4hƫfZSfa $VMDk=;E.*00k1|_Ϗ'SLu@%luPkH/oA/r/kAKm>;ougߙ-?p(dpoMV߶;P>D,HDliWȿqƽɽo[dT=BJ̍I;\-p)r[MnW!c_Lݜ| 7@a(sA2QBm11 |~11oa#73|V0c -?#L_߈tr .!L$W]UUVFB$v[y9wn+'L۞:J QT=4᪨RHnhY44WTiN苑X*[F0>:]u "19-wI\v1抠d0Fh&W}q?GP´*cvn,6 $ GD~0Vc= ˑƈz9;ڳr(gaILw1w=_<.e,ȏ] e1L1 .a+S ًӳZ{ ;[<5ϳ暈Z#F 6-,ln\+T2>h7l5F]ڛꙶؐ!kC+w[ @< #]'Z~MOESJoƛW\rC&9 r]%j6]g)5ǁ"mƁz}1OQۂ6ࠟp`WX8>mW*sfwT/*fWE.XoΩlh8^c&8kmAYW3Ne6}<k¬Q73yUMf?O;d291ڹ>o6r]w 5DE>n1qopoMMȽͽMwwb^^!A $p pH=,c6`hH& !ccW2 VGS?0"ӌC?$3L&ɬTBOeEs.C'$1,[,BKn+{a^vXf_Zv'It=;N $C,?,%Ig,+H  z_`ϚG&[_EM9NV9vyG䷖V8 Y?Bf(.g:BSpeWf`P 66zv}2mk9X-8m7iknKc~P>ŗ6?,m 5䦟DbҠǤx5 g p5S,<)7s‘Y=,c!1PzpK?B^^RR'va^w#H<8Vထ1 L«Z(9P*4LeX+Y$AI^B 5"ga GF5yꑡy ;dCf@pvp]pANh/vAqq^B !ͺ`vͺjI뺉5,"`.Ƈ7_w*Hݓݳ+[Gnn^DS)hz]y@WإFt/ v|^Ѝ Tq(aG}*?(0)9M[STXu000['Te_f4/LZ>}0COIy~S?بC>?oZ2@/ ƾ4KZNkH[᪴`g( @^9}!m>[ :Aa0>/!o:M?L BAwA0a LA)233ό ȗ16cZ܌3^:_P(=+/4  XZZ3r6B``p@fBȜ9'sYLhpжYdEʲ@,! ͂Y; M^Y&8ٹP{p8@ll 7l%FX .ia5bx{ƍ00 _1@9_zrs@#?Gȸl@.#`p7ځ-hr;g;vl<̵ ߹@;.l3w]˹?l3`Oa\nj=̑q\o=cC';v~aN6vhutvw#1ww~K;<<_y:~:_"v... 2'ti貨˪.kl6r|ĮN-+ǻV#Yv.躲ngY拮X""G"켈0`(q@eE`E@"6nw ƸVmX7 ~A7}tk62t[ n5$pSݮvQ vY(Ü[ W1[qUbxrbk7@.>W|;]׽ûOᆬ0Vu?\wOw%%Pg YRQRU2dr%%kJ`-J/@P_Kz{(1M=`=z,Ǫzl}fjT* (tLSsJ-\XJ/5Sڼ'x |0H眞uzxڦ'̽={=n)EʊhRVU]]6lyٺ e_(C>Fa!ϲS,<[^AYƳ\xY6\ާ)|lrX;aN/Px(?T*?_sR {AEQE*+1i+sRbcm8P{ W/r{1z1WJz^d{}z5q+[[ [בh*ZOKZDMB~@+aV\w?KUr#n\hp֣a lj=ڬSNLLwNVVHuFS~L1g%AMtU,J~?&5䷝q|# 7ӡql5c]5pVu[h9Awom\BKR UZ4N(Bڌ>Q=&HhކWQR&j{=DVzuu<5Џ~?ٞB ;&:*]h6'4LUM2q@م7MgG݌ϨڀUߛη*&~q\kkh?շɕޯunyG5)8*mö_8>JZq}{2''z}LZ߯w<"GSa4"JUkZFPKOÔPwf+M;[Z5QMHyye5һq<{5\ԬiN9Zi#g)LG 1M,m]%?86g=3<[:6FmJ|6|NM禝1f^e77KlՠݡgT=Fth3xx,b)n1FK HEi}xTPcXx ,ɥLx֋Cɴ4i<>JlaN3/jzct67I0GNhJkP?|*IL5lQl5i*N;r=2>#˥ky7ao>I-k-me"4lIQ]uɈiP9ZxכmR׆k'Hz7xVۢf:^-ѹRKXeUK= 0`)1<%z Ԗ;<&{·~lDk~q#f~ůlڿ#뷵sV=A `^gu+O0iebWcŪ"r&{KISgE'('u6|SwdD[tR-[CɻIr@L!f/lQ2 =TI5zRȒ4Oס8jB=b=ыaK_Oj+5}׹˴油A_nkjji'4GרG+$o#1 --}TK=mǔ+Ig{9#e5,f:-sj+G/YaԯLgf{kEE6JEsb_WӛPzb"\}WRT1Rm,ӽjCNIScnǩaeL>(*A;~xzuwLM񞿕]٣Afc^ROof_v:1>fњQ[8ܾm{ mx8ʖUjc/G]b\]֊@ӿտd=@=Xc7Dlt\fg> :,JO7Q3@%}9ּ^3~5ƅn8tTUZL_uޖ3NZ_!zUmkhMt@K~ߐZcT5{kk*jUS[i3Qf M ָM i@ 6%KXc<e}^k̾H^]B>ph;1Xc-xӴt5NG[TE=շ ax_)bvm k>;xeUZB.*cgZ~$ZEwW褜DGxTkng^ksn˫êiMk)zv8 VCo"4;MTGwkIz2v܂x..Zpy43"l@i`zQtnG?;pc{sXK#$ KH=ZTjC>oqk9+AxZ(ֻivۂZM2) 9}$-Z~SKm 5:-Ѭ`M.}ђ1QjV)G}G3$cڀ-|#+6vc|Ow)]fG?f3:o{; ,/;nXg* -T}{I]~{ 89c-m>zuG-|C}<6e夥7[zMJאֻm\;Cv&9rڠ)B摒:߃1mUN'Λ~WPsV3~Ǧ,^\6FEw`{O]tWpTU^Ɖh}z m/vַJT5?q-*AҪD{S7>kBZ[I(a+R|.>GTaA7_.kcgz٭8Y7njcwY)J9٫ɂk=5UX?.w7<"ް?[Eykq"4=G+QQR DPD jQii+ZEJ-P*V-P *X(!9-'}&{Ĝfysnrc^VuȺ`5]`.CFkhPhxhrhvhQPUj85<"<;"\gRZ҈g~?KʢRp 1Q"v8)w, H_n7^oe:|<L^ ŗ,ȗAuy=gIޮ+d>oe-f%<.n4F^,ZLY.Y%v~VԲb8+,ǽS\\r ZVLcF5#g`8F|/EżKx | _’2W $hXgʥ\v QJChKfjf݄&4]NKbKg=X0YCE8l;99GntS ܒpKbbiLp}5846q+3Gn5.wߡ٫=nb̮ csploPo%|w|kn;H5k*1dYG#bx Y,JU* 1S86JL͂KF!?g VXʫ}F=0ЍQ+WfZCTrcNZ*nd^-:ߕq~!"auMjȿ1nocMڿ@<uc F`>l`VcWmVl] vX0w0w{n=|/~qL?~b/8 qs S~C= W֬px1?gPC]KPOzRS ___@=WzuZ'A= P?P?J_(dOuPπ2z&O@=S< 'ypI i39w= Epl0cL/>x+*?>xO*vE.ɢƶ"{cGh5RE7gaKB!m"r{MCnok6͓գ3TV[ނyދ+xxORx?e{Y|~{7t>^+z:;DF2Xbu:,?ދxY:oym4z9I g;4y =]ODi9ksǷ˽$s|JR|>ӕnKT'| 5oC)4~;9 UȔg(mRrvLoܑ اrv<x?{={x~ߍ^^|+=^R@{<؜x5ֹ̓ T]LS>O9G m}`0|Z@e ?1aͧS~q\+A鐞F?Q_xꀟ6^zgDWo~>瀟@!]~(KtY@)5*ka S}:O#$t--gc; >`g+vbg% v?+v_>v&ؽؙ)!w? 7;pn|83Ƀʝͻq2U˭|͑]i)ۏ=y-Mnoŕh6owf;e=%XǹQ9&z%Pe;fm9sWD'tDgu +P[n;mL1LqE˝yeTq endstream endobj 90 0 obj << /BaseFont /CIDFont+F7 /DescendantFonts [ << /BaseFont /CIDFont+F7 /CIDSystemInfo << /Ordering 83 0 R /Registry 84 0 R /Supplement 0 >> /CIDToGIDMap /Identity /FontDescriptor << /Ascent 777 /CapHeight 666 /Descent -222 /Flags 6 /FontBBox 85 0 R /FontFile2 87 0 R /FontName /CIDFont+F7 /ItalicAngle 0 /StemV 86 0 R /Type /FontDescriptor >> /Subtype /CIDFontType2 /Type /Font /W 88 0 R >> ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 89 0 R /Type /Font >> endobj 91 0 obj (Identity) endobj 92 0 obj (Adobe) endobj 95 0 obj << /Filter /FlateDecode /Length 150127 /Length1 353944 /Type /Stream >> stream x`\ŵ?<{6vUV]UeےU,ncl1:āȲ- !# I(oCD@k3wv%,IxO#=s̙3gܕ2kW? 8Z]Q~o!:}/BGuEcůRD'jk~'T67~kj:*Vܕn:ԖǿgxjwM+{z!nm'' | ׆M?=zI2iuM(yfuhu)B!{qx׋?!C um64f#D!\ˆ/Q)K~Q"\s&O{}{o<U@.гu9?>P6mܺ-A~͘~ӖMdq9%=({b0ǻځ*bG~x*'g 1֎_"py߇ʾO}uEk j8`!pߍϧExmf-pz_6K~snPMsEם#s&dD_#пFߣEG{CO ZGZfo!BC\gK0z =O_~ļ~F_VάW34fl *#jx&A8gh;>2VztmLޛB|e\OH`oBWJ"oט_E~=j,z8xmXiv l a6̆0fl ُ<3Bɥ!Kh$5-߁\)r yqt]_{6̆[SBl]-b]-Zkqm]W55 a6̆0fl a6̆0fl 64fl a6̆0ff`1=ZWVwGQq)%rD2Q1*EQ-G BԌ:P'ډ.DtrG#Ǒ(p9Jn[}HD|>ᚌQȵ2ʵ %g)\ˢ\/r_ \p"Dv<#F&$KaH_O @(|[{ԄVT;Ssnd7ϝ)50;9Qe۶nټiuk _u˗-hokminZqAC}]mMuUe`yYiɼ‚Y)~_7e1h5j\&x 24ҫ5a0ekodLvCVM<Ͱ[$sSr`eP'()dU^(Tycbzf.<(᮶ VnwpCU\Ve29$Nn:LQbN.>L#A;{[:O*E^Òa^^댮rNnQ ZPyzw3=Ph|XNV ?xق։PÜOu}ޱDs$>''q'ciuB<\Fh\ m$n>Yay.v؁ݙ(ப?h޻ʝ} Ì{U ƞ!oU[{p h[gg}O74b CKpwӰ[A Í`M[X$ZlP9{pCUʋ}x~$A]æJPg9{] .og%f8mxG|X 6:F[w'mgpoA>%pC%^(qwRv#D)p*\0:|E+. _R%{NoXKu"9k5PjJr F\O"`(!b|0r!6bE{5;./P Zm-K;ގjI{_Hn.J=֭ux=qY7v}kh0b|X)1U^5 x=wWXslW33ٻjp08{ŐoYb+ڹ۾ ?[P +*{+Z+ږv 侢sIp쿘K\/sj A "Wˊu(u uwaLK S24L{ˠŰ_1,Vr_N%8ͧLt66C^00b:"c,ݣH{}ci9ĥLnjqώ=}]0.c ~X Q@Q# x$d5\%W0K'ysEc]:I.a]DHj[n#m0d!~_2{&b|rlX p>T^a`x}] *,ZQS-2̋]Q+ҍCë/'.kp7ЗIh%k! [-p-= U{f;=QE4!Fjps6R-!{J})i끹c"}\G6:`}h;4, ~pwSӏ;ekt07{Fq?d[?zk;EC`|W{w$"=v!.`D>LHf} 7#n bA 5kn# '6is!čZv,o׃xUv\=LwFG,_cFA8{DG<Ag?,eF]#2(#N98FH8lql&g)Iu#k qT 8*V O@/)!MG`HB5  ,#@NK,&A@+,IXH , @@Z5 T*G (7(!0@16Eg!,\s<\lY2 dfxK#R H&~>R K$n.N"[30L#=#%r&"J r2k30bm HpXB+E@E8- W8E3'X>!>"!#cFBH #w @wH"&ߏoGK k"O' !%9H *WL(_"S/1#&KԏFL/x~Ĵ 9x yvRmm!7D`# XGj#}#}#zG *3btH#K.X6IZ:wtXI@"*I%\yNB@NX XTH@'Вrj*JB W وf%tDs@' !JP2$&@@`OCOAgPSH? ~c*!~u}s}}AA+ \7?C?B0A|ކ PvF55_A|BIKgO@|+_ğ*׹~ ^T~ x\]sʵgk\(]?;O+s\OA|1fרb Vm xG0@|#A|X|ANߕ__u wC CNNUmS4ڮ(ulzhKҼe--ooJ6y0xfsƎM6vlX߱*pu}:z WuvwWc v,;c /.l8VzpQ"_XЂºCu5xIp'0\E Pd*A,۟3:eSVJm^lʨ-Y%5Fm~}3S3kIcr#nia{UsmuR]F}#u1`9J]5>E]-X0GZ ˆ+}m3زtXr0X0E]%6vx}56QQ`9}k^tt! UtVnݾ5,mZ5 VSjuDMʥGDU95jKI㏈1[hnQ]r\$ʚ<#&Vʭ/\좶ƿ[5.^BpVb T`6|YRWOH|6&= x9GOSzcߔ2Cb?xam-#-<83Rn%m1'tNMs;B>q?L!}HX(؂z =@Gj9IϢ:.= cѻF<)_@?+ z@?_A?%Xߡ7A^*~~ѯ91ϡE}Tj`=ģy>|Y SI(/P=>D#7u57 XW aZJQ74_ee個 [mP JEi=Z1T4K$L ??//;'& ) ʘ\'-J1q.˘BWEIˮ2(8͹BiS^홛r2 !*jem12dSq,nުbk8թ8l󛘜9[[eJ$O;y5kW5 7$^'6J0̼L+42b2Bsё#d΀}$ރ:ؙ [4zjOLR*M~+&Zd)p8dJĦ eb Uu,庢,ts\g[8ͣY4/?~\Kir=dwك P^ Ma0Y4ֶ@ (tτ{5Nf<P{ͼI OI4}݇9gw&$r^^cKqxR2:j^R]2BJL)0nbG*aw5sB3~@Q ڌRi08J ,26iRSYcNjj?մXFds0М2Qlzhpx嚌wˈݩ2+U`c 56k2gg[Smm/MaeI|շ}ytoрzCZ^z桋=0;b ‚3ev ! (]F-w-Q*HP>!0T֋y'@dG(1ѐE*R*NX@*J%zЃTxl.De,c]rY[F/GiH?YNJ{K?y/Og)5W\8ؘkv_Gv7\ԕv^nP$2_f$LBJMiSdi*K $ )dH2p-&#Ailqhm`b=Ѐı ";2;,F>pbAm# ym ^/.5)rf^aF6h%ȔxrZ6fv3ZlFZP<բWhjTZ9`7>UǭT"7 JX:r~.x(`y)*Q2 D%$PlR#9zVVX0͘^HɱݗD>oM%۷Q #2 ͉Wl~#,Hx+Q˲d ^ױ}+./^0 %Ij)u{c~U^?@J zk]4wFATJwN Ƥw9La.WD{^r,b1A23SdF;C&Ӹˠ3(u%g22{NKfRd&whH~0R 7A rn mȒ6~Kq^r-5UɌwj>&y?hcu}IHKIM~i1Vg}+N_FE˱JUJ['48R-%.A{]%;G["c 3B{g$ )L-uJ='E0tlDwiNs9q$*k|kz=zţmwl]wmm[-zfghǯi]5۳^tݷnmXn4αiDt,RΥ(.?(Y$_6/{{ ?ǟ\k~ ef{B'0)IҠs}j͹663*%{(%ݍe]`NS`!rAeϔfR(T9aOHqfw8'-mt0|%{Z[t8KG8XN+. EKNh@t2yɬbzL&'|'7h[p2Srͭ<L֍̔h5{t<˯Z%H]|L~W=JR`YZIe]\=뉎A+ybqi1,1cn@쟧C(. *m|*WShIPPx O+>OQS窶Q*H5mL* ;T[ѯh84J'B"*h%r fZ"0x#&F= ޶.s`.O.m/(]^J.oio-OMkڋm+U5Zښ˒˶ۗ&W'C9l@zE~vnт`]UbYJϝ\4/2?ߞ44X1uz<cn+DyUbpMa|D73G\Ʌ@Lͧ8VMТۏ=oQ Ue~h9@ixbTb L6Zf h-38/GTW\!OFHWӋ& CyM$DY&2؝[ްjN< =GߡUo\pqorxK(WFw~BߙuidFN@F%ddQ&e>BPNE-x ?&-i2%2{%D_V2~ ">9[~V!؏Ҝ}b΅(/Nq{b)8i#&\Is3J)x \vf^5U'8粃E?XٗHTI>V0R9E&IT 6%Yr2Rٲ؞Gx9G{& A% EtNP4&)tNdbt4%jGE!݊$J ٭KfM̘d qST#Q/0/Ói~/PDG/cʒHͧg9FKigRpj^Nl/S+Ɠ$Sլ_] K/LDO[IʧhVFu}(:nmn^H_4[+:~*$ɚu=+|tۼ~m^J=D GrJ/U :EVu=WjdS,/ `9U\3Roǃ9Q*'V/(K tt0t]Xi2&EMegoXrӭҜfOgl(䲓^M>V!k.P@1O{LtW?͉430:Ԧ{KVkv s8ct+Xua6Y͙')bsϜʴ̮YԚ;oٔ㋜9%M|*wcQwX}kF\1ia6C h.t44w :c6Ϙq&I.a<ðWK+/%Kr)suj\wV Ų`U ބ;wwOJ't9=7;֔HҀFX yXnxpfRp|s~$YE6A-:> iG! ݙt< }o^;~JnPq1>%Kp]Ϋ.06|$*Cުkx/iͶ]X-JGQ(} ‚|>-|g}搙}br?;h6H?‡>Ða2}]{*xwHs\%VM=hip/ cVZ# o Wbǿkhe _ t%ߏvwF[@.Gne0؄}87e :j* GhIqhz7g#^IRF?YC-n~^jAsŬWuF6e9ϼq" 0֑T> JRyҠln{i~Iv(U|oLKd1``}qъŸ^p1B3;*쎄ϰY/p!Dwb@鵃U.l.HHiX_;g0xLurj$ieMyɍn8er٢S:m{kk}RdIn'a|! o^𭗬ϳb`>B:d*c1'e,䓙IK$SٳNqqpfz=|?1yq)D89cӣ'+6H^D+9a9lܽ +oQ/c'yz]jL"f^y^/KHJ23&2Q(352er ?ds;Wtg%LkGxbہS*m>nFN22ڏEIh{Ñh2uT‘t@ŏObxm'&:;u< ]^Kt0rLoM8n-hjhyR0h B-N?m/>Dm]Z#49-ѕs%52v}P=Me7yGzR8>L#78MFN҇ic:a` ؒt[YEۗAV"Hr"'n%uz.s"s'""Iv})yK*Ey*++|at˟W*&Y3,|rp< bd&d2^Oߢ;s^37WHSm6alZodqhXG VjӼZ)e6%*ݎ$Zknk5-La_OCDۈ?aD?Osbg 1p4‘?8cVﹹ/V m/z$xyEe>-ʻykM֊+͕C't_{Gp.س[ʻ`DP˃'N'78\#hRޛ%nYKPu 񕊏BI¬xQvځ;q %pI sچͷ->WRWKKsw-.*n'jEu9 Հׂ{Aso#A9To1' z54ASYѽl9_q}w02G )BB Vo^žiV[*w< 3v*Z/Ü\'eI. \LW&kpգgAM|*NKg:3d!3,K *z[ ̬$%\_G3<'UU8HmlO S\T*എT0)G$#Q.wjcFoӣdDbJN|ZP (VJbR Lԓ7`$">\H.N8;ųGP#:'@2}@ԕO9$1tH'oO=%}U0&&sgj`u$xTDC;]Fcx@4"!V#8>- D!ŋ8 k $#j͂+~9@e&%'zmJ{'Oha͛x ҥ&څ廊h-<>a=tDr5ڊOPKdyy<9;;!8Gq0J=R)Y( H:/0l U {5Vx_\ĜCA8:IhK,dkPicnb8]oao{<8S t6)`{eՊ-הY ^uqUK*s\j+?PAgժ@i˷^{+ʗiTV]׾nˆtou4tc'H$sn3*ЯϔtP':.((AW!,L0>KnHi\j,4/3&XlXu%Jv\ȋw>9|ɧ!(2)OOQ1Wdk. Xjҽ::9%-do8u,̩jzņ’%>Q{֓^Kn8xպUNJ ީ]`+,yVզkJR\@uEv[`d7B,f^Bsѱ?!iq'8R3QY  JTC8yvu7ssde5XY}S!_'7Mq[sżJP9s{볝ʥ]WORV4-Ϲ.c-4̟E:HrmX_֝VӚo &M%.@WSHVDь$-~u˫ {7:g'fSAqjSOsRڠ'EUjllYvjœF. 4c3YÔƏe94_b{{"W^䉸 ДwOO u/k8G_v*4-ov? U  O4E % ./MLd؋492Ki(ij/!i*o\ZdM0=ah%FNP$\s7xioZ.-GCᖵI F"UR3pR-P NPJ5%Di ]Ԛ˴U^\WcYfGo9IU?9@[^C:7Dz_tv]gJF(7 -Dͮ5樂2A0zmvAƎ (TR;OO6y:+~*h\)j~] ۈ%C5AnKۻ{zPϥUWh32W0oؙUW5|7Uۯä՟\( /*jnKbj|8I.'˗}pH||)۹΅šߩPAM# xIG z|^KH^F"P;y6(k%^iP('Z_dkJ8RZ"c@-iR ^pe [ 41;2NW^iچ`v]?ȫZxTW{y݄;fe9ڼ,L*W+]N K,%oavSJ6-֣tT>Oy24͘$@T@Ug a܍doH\|6%|Fޔ`UƲ9Jf ʥQTOu,(Ouͩop_U*L+5+;J꺜ֵ7d.Y{YD%3xZnEQZz 'c0xFieЈg ҜZ"i ΅S^~3k 7a̢G?R-gBQҴ_Bic5Zd¡}dEO/A̋;w8yZX-JYA)(Jrt[Ϊ nn߷@;/nEi2{ZIT6¬W]> Ug42J@A&t|YԺޅz7/ ֛Ly,JS:K\Ip0)&v >!PW%^$"IOqq3|;!T&ZiŬa^o drdޘ7JӝrKr%+Uˇ3` e%.˒G`VֶW7.(]t{vNʒ+52tM *VK&*3Seː\U0.V*ܔ֘jxޅfljZo5XneVVZUAfr)@dvCPmTyU84? y/,4*Q~tz 8u kBM_(GƵW-$O&jJ@IŅvT*a\Ul P: 3 r 1sCU^G%,Jʹ^vuuuZt̰R.a>/]Ste3Ta=ߚ;rS؊Fwnw2N:}G2) EPܷym[kw oܒ\P$}inWWZ-CZ]d/t6P͂uF}|-|{]5 orE .R5D4IeQ&b8sMxUn? v[8t{ka Q ֑pד?q6hCM'Œ6_9`3&6`N . B;P0BO_xsmnBFc3 mH-Iw(_wnƬױʔpInJ/ jfz<$ Kdž3G*q8o("^# o!<64q"CggO*?ȭy#%GKK>GKХ0:ɔ SMLݒξkfr{_*Ӓ[L/_p.]*f+j֔sv/ۯk W4 '-|eT֔E0FT<*]'Oh *悻h**~|#8(Ks*/Aeryk88{@Wұ6^rW쁯7VGsXtJuG/H(Y;:ۜBfWkmSb Ua%!6u Toꖆq)>Hyt \3%;Npx &݈)f+MW ;IK<=f+WɆ~Iե|>={V-0/F~N1iL|$"4%!yз/a Y35=W d=C <[4cph 4i %Q 35-4 -I4 NgfH0qsWwbtI8;Q‹nwd2?!gJ:#qN-HvJ-U욇qJ 3nACZ%,iƉtb@gC<[áp(.%tWZNsC"s%L'!6)ڻ14¿gIݘQiǫo:.XC$MҊ -۵̣vu_bjNȧ8֞t@-30MzeL8-= Szt )ANn-jz4 o|x QE4CM[EU_'!g$ĝ_Oq㢯I6}] 9Շ趹Kf/eDcD S HL clpq:("*yh2J*JLB7wS4eWۅ3ۚ3Uwvks]X.Tvo8TWJ]K^~`]\3Yk PawR@sOZ_юZ~XkE#f4>7|*q*mU@IU;.,D!d*o"}sp҄`L |G*XN/ ٙ]Yaw _W?xk3[^S[$[.o6*Ͼԋz7k(. GyK84Kثg=wviUgOyhԃx?[-= WNK/~9q]䯂3+x[kX"9Pk,.K6W45͠g.P8C^~{8$rd{8$^1ZT h 9si-YB{T͚q>fQ,ՀYjK{5KxzR-9>$jcSULd0l'ذ~t5{uoU]UTU-uݒ,˲E5n/8BB Y aIB! d00$fP!΢ֻ[d}ERuUsܳm= ?a9oA7{=sᾦ=GC~HhxvO87W\1>WTBMst$&щ g_f  Y%3Ukr2_Bꗿڦx@{lO{3=; 64΅,Rj]`Ᵹu(R7=Wn+Y˼̢\6",0A.Mp4@kf.ېOK'gƢxFqrɾ̈kxe|C$/-` d&U2s̈K}^I-^4e/Q.wqfTvp Isr$񝥣(E"C.[62fU,~MD7<-fyISP$@;l5Pv ]/],`'ee f7^@0Zh¯,;ʒFez`e (-5#c(J2S26jX[zńj&X+Ac-[x;9T7{ Q GNmH H0I9wġ2Bw\Pd" +Hxä?d'4HBK-2izܷm!5kyc{v=DsnFLf-g8,/thA-3ѮĦ.`[ =yewcGu)Rz8{0[wO"mqA}ȳpD>M܋>_T;RyBȧdit`~~ɤrxaU&la`Y.5ω!ذ*ŏ@E@4RQZ㢪 geqD,Go[ʝ]4`@!y+'K1?Pe 9}lL~??TqivOH{9Aj|`φiU95޺1o-&{oסK}v3e[mw:gQ 5kz]p#mϘ5: ɑB147l>ܳy7L*O7i[x:9Ke쉛`icdRxhC=!rh(l,f_$Rg%"ݞ.ڹ+]$"OIIu0{<(k`*fnҬ\F#L*3ۻPqdGB9m/0n:Bפ>~,xM헇q"_rSs`E&T|[kp*n YvH/ X%Dij6M-'n$^Q,ZO`tiWDq[VZ[f MK#g`{YF8S8mĎLOl['Ζ_w_Ǧ<P˯b_Cb3g^׻*M-Uq BDU[f_[G3uJ\ZU,[~@z-DQEË6;|,^:JARC ]m3) &kӾlזIN:j聲ֈp޴TYKYӁ uy ͮfќS>xEA@&JBrj&R@ f ,r$ږTY&폜/vfH:\W(q9_4 Օy% {2EIہ~NB9 ,E`swŽgsIyOk 0'b$|kQ~h~$jj Ho8OjrSXJ/m̀ .:( ԓ`M[r 8G.4G1ƽiMW^i YTBM<޺NπU%r F.^dXHbEamڲK/A5r7(GZ T/tsrbeFDM ԭ޼?߼>gƵ= S g3Zݽt`颽_{{@=T# P[߱E*Hr#Ufٟ2-~B#jTU>ۅϢ"_;a_5(S'ͣGI=ܸCHx.fW3Eֵ,G5:TDhԷ ^ɡNc9'+9 ;9 Ĩ1bnM0jaqlDTʏE)*4DR?7\yߖm4Nm Mih>ԚvdO\5pwް5pTz4go?x!=` l?t`{6ǫ곬^YoTRcֻ*4: Ϗ/1XXD H=Aɷ#%|ZU *-Aĝ@k~64 BĺR n-v_U@';S.5vK;#h@ը`uWDŻ'N!X {Ő*sc61&d1 c4~M!%t]M aiW, X fܢt4bޚ]]8bbOؾWc0\R}Kv.1pǦoX5lLڕ]vUTenKv};t]*5!Ro8pe3hi f#S9{:q6 #{54o傶/YęE>$e"ZMk*r`׺7Fj%un i)jsxJPMR rLpXj[`IM*DMQ,d,8I?WpDš0jVvKhIBt犅bؒxhYRDSR[Z_,dZhWX?e>xgR)0x ;%7RgMDDHsñ7[۵vѮw,EB($>_h#GU۶];_;vln˻nloˇ?kGY>>+<+mu"_h_1[gb3۷JtUڬ9BRkľ*qH8u +cqh qhiFj#lnq8rs}vc}G[皶78{Ϗg#|Ԕlr`0r ;WٳuX4~8> "TY_]T!|hKrRIK *f)h)g Iv%mPNU5 taHvg9cd@"WKShoA;& _OO8&e!!g#R{NI``cr( -4#mY9y6R{y0G<[xFsJ2nV%6u3:X}Mom?4m:|TWilj^繹B.hX_J(R 8113JAiJ0;5崱K3D<;devZYZU$]P#8GҵfJ23Y<<Ƽy/Q`88 =X_b ۛJ_d*4d#JwOo5oiAHtzSnB[yW"҉b-ݡδb1V(B MNΝ8^oH2k8kS:Po]aVe9 P&8qk g1P !"=ȗEexc!43Mc&N 4ߒ/'fBh!ՌCDyd .߳Dm/|" j =T!Y)-j*b[Q#mN&*\P)f'Cp+,br9Pk??VNd&NmW Y(yG4%efVN[,B 'K@}}灌䴴:X-~Kt@|Ƃdc1MzZI9NsvoE1̙sg?MsH>mdЊ2G,G?X=ݏ=GHd-W)fYL)Zj.[ngKX-W)@cؓ IRNwEh"vˈ?!ʂ7hUQZUZC@lDpfYVHfV`W2+u:!h: hbN8LS"3 >Vҙ,bbL5 xBDzĊ•̊1YB g;FڽBSΐj hjcr9Mt9lTB ^Ox7ֽ( *p:!9.0Y]1}}3;)UOC|\J.<f'b10ɐSH%Nb(r+h#E)&ӇB_2*g<+x4cH鉈`D(YB'z$32M ӻ@^i>9b]f$ƋbI"s&}&(P>^ ʶJ[@J/p̭]1%yLI:Zz<.*0Ѧo pUmsYmYsN5ts]k/6}kq>5]D_֞?h(튀@hOڥg"0m Z09sS$42re\H ;ݶvTpN6{'#d㜴kPokV1뢺wCpKХ1XIfJz2wrR*g)H)hnm6ԥ%W/DWp)8jCt`%Of9k!uCˌ>D?]舥lLtE=;"v! )#ɓ:H~9A,Uºhes !&mBW:qƔ|A:z?G>Zaߎjd~?WŸ-PP>6 -K&݃JatF7p*ޮ\q%''&ON7gܮMD&_Qj% eod (Y`:"{p|h'%χH>ѝQ _lgb2Ô;yLr$5獝夝7D yyd/wG3)vRJA"y@bo/FH'M vɯ#ɆA~<`(آՄvRXGAa9A/*ڕr}j`rI*|{!%`-Ja HerM1#0Fw,2dPqQr6\3 O ?#eQ`b TbyYufã;M^=Lì M<7:T'$E<,ID Hrt-F5J(N?Źx4ApUNF <^xqEKT1TLyKAF)B8 2Vb Rn? v`? d ȕOc2 i{$*+n}'6<; m(*]ܱ{ob BpoZtJ@c{KIvGpaLXUё'BGNDc$o Y-^#IZ XEB>IT}^S2jr iZI5 *\aqjdR{= b'LCNjK+7Lirmb͍߱z7_Wye-V[@E d2;<;Q ZNW ͇s; @E}A0":q4%F'Nk>]aVnW+'!y*md傅.PSXkAtH >œɗx|r~X}}AЌ<*3]ڢda05>b0Lk0hY'Q`dօMbg}L's+=}!R > `@9EԷV:LpN2e:Z~*dʖL}ygRQę8担}9 hҏJ/\ccH @#(P=F۞+(.5@[xzOcO}m5O_xziv 9hM=Y_Se'Dq sʼnb a v‘EF>Upyd6 !mUt-谆]7d})++#ӔnYeRu]NN kT4A*H6CltlИt*,n5K~qxG VsP1m01s3lur^ ]W#WP %̯ٺ^-1㵐֌ՏMF%Жz 4D%NMyZ&Z\'J\9MyV pmO)Fj 5/ppw)C\_ vr1}e`ػ޺4Iq8ROZYRӰgB I2$ᔂ[$e5$xiuJ0I6/AqT2qdZ V4Z0资\'i[s鍑+GXPcS)`:+sEhxس;epњaTe|Olnza*7k(uܴ`AoSħ>\SˣfFh A[~zWDہe o苵O'=9+zW1eqѡlnS~ph4rs!R=ar9({yу;nѝc 4Z,֌UUbGY -`9zA4h==;]jo[ehJ"iF陞o<>ms[ ̭T <0g o ֩ }أ]1vQk_]-Xk^.k6 [8VP" i W֝FOQv炎f9WOPӨIibT8HX]f_lϕīERb M>Ni"{;5prحx9觷LV͢V* mG)@5SѭM ͐Jtlmb$?ٱwֵzcnY-ә:oόHl@:; yrYklof['YI#q$-ei"rEX‰#t0u|"tI![TjQ_O?|"/կ'ÙiӥE**rղX{PNҬFQXcQ*pѩdFOJYijO{i8j A(O$!C}Ԁk) ?_@NWr%sb-#H<׫}SN4j=bO' a9ϋkjwe2)HT15)*)S6󫉮$:*G>Hִmi4(!gT ۼ{꣛#L$|o*dK}*۩sSڮ?r@XѳrO7t qfq:қMZU^ Gp}q rP̎nZڟEPG 56v|jSRȿZt׊T{+E+FPĢq-G$Esn=n\ͱw!2o\.:)4ǟFmp:l[VKXL&-NrU)^HTVm g\0ync4(&* 33{U+J%(@iM&Cp ̿@҄ OJ+_#_Z Cp"=P,؞|]XF%^l|eX+7ua+EFjQnPl偑jmfb_r0ff:-Sm4&w4!{| y8nW;sܽŐkrC_sؠ"uPu`-㱩#?b w^r$bσY!jY֩Cv"H7`9D:y<(Z_KlVY0(FTxXh}جWz形b5EKl֤x;\/vǟb{]G<p??~쨥׌ucG?mp`(RiJi:`7/}K>ܥȵ:Bl=W?6.oX<ΦQ4e',9A.: 5{D'A]NyLZRT 8 Y),!޷9}Aq}]*ZE Hj}8Py sWuqHS2J9vLNJ7R}f5 `Y4> 8;/>o$.|1d $SP87y^#xK nBl xv咫-`RђZ<by]MA?\j."U=JABL`QH :9>qsЬK|`" Nr JJ[Uݘ~Cr={I,1_>·Lѣmtݨ6wl^À+?ú'>.6 tU =#`kط6Y#Ζ}At#5>w]ו|+hG $+'gqTJҁѢe=Gdz( :#}dLE}esЇ [? ]WV i7A[Y_YMƭ4hRMxE*Wzz^+֐dV|aѲU YىiU3S@3xz%D7BJpjgfMm]:Mn:rWKy3ߟ Bj6jg\&UR lArȾЂX2 4J0U@.=<%m*vGT{XQ1aNļ5;ͷME&o3pbfݭ[_|[rWʞ Xy!&r`#u_ؑ;&=m/;ӗpLlO#aF`6.`ž #=䞪7Y -lP uۗ![0F6}eެ'NYablJIN=@)H =l'P H) 6`4HZ0i&4 m,9N&]h ] @##aWg4qYNlq]Me`Sخw\ͤ4&No5j< ԆM3_RZǢk8[џ^Rk0 J k݇ .6k㒫Z0*\u~/)|%^@iQv1JvbT `{Sښȿ18xü՚ezU 1E1?0q!/,z7ċE-fB2QjUG@T .!RpA^CO@-X4nh^Wi^ݜm0,o,`*liJ hr km~k]ȠQNϱƵi>[\N`lNBc1Q%0L: g*%'H+7GxjRgv58Ͽ*04 K&+վ5p1nFV \>qI;)L`%Ւ_@d-ܮA9iUJ4aѴJx̍.+(_΍JwO^ n͏8!h|v(ɶ |LojKg߽^}lO%6qX`k 4>0Z6>gee.er9^GQcHڵhDGzvk!򙶗Ίmaʲ(_J]jX KFhJ 0~ ^d*Yt o~Uȳ/*?S0z1j,ß?Q@9{Y$Q3Y/bG-ATu_P`lbe>zQrk9"V,o;.}jp❙dS3(dzЃg2[+:rT&qdnף\9su m3Sa`_wd0)Q2MgA{ͻ͙qo'%Z M@Zo:5s:2*Rsxޯt7 XxwF͝ 2kr4[7&,V/ȝ0,j n1@($&@W*E 0-L{m]r2V?_2ZhhL7|dֆc}6*ޝ 1arhqw!7[Y|fS؍uO}3wߐ񴬝ޙsFOk3ܞkG!;ؗI*;C3%';Cx7cKv_ʹАI!m*{4 'qGK9)0{-R^ǟh<;SjDYyNcӨ|]Vm:BX7VM+$`dUы*&LZ1iѡ+>ګS`%J)lm|6{ezX^?ȣ]ݼmlH:!VP؝yٱHh˯ F8ʅsW6,N9O$ҌP@' LO9`M(q, `'ai@O KI >9Y,ka6g'޻e}9)1{`wZ)v JmodL[<hF?ٳm]'r0X&=Upse ;SQ@@&iT15q{$"rjp&moFJ% Ӽ{#=t̯2 -j۩(m/{GqoޫʪNNSbuZ;.t^$l9vM S/ K))(7P?3dc yf~2l_%$)UM+Z4bjP6V'3aU ْr deg[ϹM{-VTnquz.EkV_(WݕyQf]̪û;o_ۿ.5?EWзՎ2ݛwf8 E3Hto6x̅i`jFmۄU!A [Xx=sm 5*#U/L1(B,*u-wT+lVVIFPQSRrGD^w}$\XG@(Q>Gy+V}- t몑gnO{WꈂxzQ2I6=ܜh9Iy9ӷw~`9JLH+F}S[UΨ+(]S*)F*lo+Ҫ}B*+2ƕ#붬[Tn2DdЋ驖5Lkf9=o."G?N5:`@L\\Yˢwvnp/vZC~պىEOh/iWRʹ\2ۻ\yݺR=~j+f;u]m%hW{q.ߴe@Ry[3suW"+`͢ ѮF3~[;rt/G.WV-q5\\W > s~K. @.\| {3#}V" W\Pb.LNrRBFeZWc ;f"хgP(gRr)>|z%o%I'kD@RcfzBfY)중zL-h4wʮ%fMQk?)V5CT*3d_G91Q!=1E?T5~&OOs \ =PԇqLjc×y9oz= *nT]̨_:ɩʧMiC񐧫{rY!6m$n->N_T ux V ^1o#៚YnM%ږ3ʳbyO_xs{Z{ƴ9~jD{/~ڸt;49(p]Wp??(e,ڗ}Zr=%e^Q5$%Aͫco959JdH RbڐennT+2NhrhJgՇpo9D"3 z@6%m Eħ\IB˩qOI߀)r EƧmeRr>;Pʅ( O&ΣK1rN+-cj&2L- O=J##Y#T+a"<_q"'D*@L~"1# b_q'?' yf})\5Cjeݭ󬚌Ut+7olo)4ɪ۹d˖%5Ey[GVwyqxX!Gw>F^ǐ%[?9p-w1P#ܼ2|!#TOIuvVߴiJP6j %}T,`.Whh)Qb{U@ZRI0$iQJ^0HMɚP<&8c6cM]g.YﰐWTd!z}R~x<$5>}NAF?M. 2<)3pgw2IXj9uϳ jMMe+_{ $1͝ a%R{QFie<(RYΪN6et; $K) <\7a|D8CT_~vgA f&GX 9~gA%Ԍw'p7)2o K{RT%&{y3v&Y.)TBFȘ_!U rSќY]`b 33K] .IR$NM-HMʰ5 ݬѥmI*IoY,&hP&VZJ%mз~ɣz:X-{Ƹ<$]N6[C&eZry]tX)R*4{d*=q tT):Ԍ>\ urfϧQz8zW(SK:IjzD2rPaeZͺSK6ܧϥ[WR"Mɉk r` =RYqێ'zޝI) l1=b>wnSs]wza0 BjA9 xiNT #[`<PBmV;!o]-Xϲ33z3 >P{CTPed<4uL,kd_5J* LCuF3(dG#ogh\ qNy7%G=΁ΑpurY]YU-r su,FdKd9?$Z&f4nB8Lξ5{IwS.]V`3 2%ۙYb#?=Σ/kɛOZܐcVz Yn[NQrvCOo#$L6)vMj]Z%9,}l.y5֠jb+Sg (ˏ>GVJbN۞ !Y5?|2۬gBl:4kI-@\bGuQU}9}KBlv-U,\gP2EbaIa>NE^vi_B.֢]4j_gkҜ͍~YjKjy&@$C#D? ,դ<`ޯ }9 (JcJy5\G_T⴦x5.iSz f9yZ&D&(/*f)#T@GɛA/0ϑCaveWNy}Ow9&OtYkhE5ʅ-`O>J<̋7CwƵQg E}rwQaNu5dA38("m9UO>w3 gcwVo[n-(]\b;mlS"Ӟ wǦ<1tvkmp_jyWqټ5bWEKk f"aKtS ^y剗?7Ϛm7ʣ{I7)աx0}5h1cZNS贰r4 5I8HQ$I B?"sߐs*U BH GVJ1CHc!-jC@CvE{5(D iƷ-Ce9NW%*8&T?-  "P Sʦ Nuw6S+10IXP-E.ڭVjdܕIjjI,dςtZo"#?ԺopgUURhdCzH߫әRR &tl~.J5QJi%Uw%%=lGV'V'6;Sg611):[!npK!E;$&wIVjiIRU'"\&1ۙBS" e*6W`/h[,ʌ@敔|M7JjP*ǦM6ȕJŬΗshUJQ+ib$5,l[~P_AW'C$Nٞl[V+J^O2$Dx} $/,|e܃>WRSsg]t"N @cJiJ,3rY&9DM}dqS^K}>4ي4;ogFaV6kt+e}Y_NNJss5bLlrg˵W+M ;# N}Ц*Z>|ru-IRR,H¼R#vSfo( hvnvYeYKљd2"4ʜNw:R`L'o7ߖ0/K8m,KhH5V5''> j铝|JknוgP9ƂVojI%J`s[Hx|I{;BONzan*gu?5+uM/ӘjR$\db3Pez#C$k|А? DZ+ Эђ 3E3ɄV|3+|+/:.k~h[ )"7L _Jj{'k+ZMQQPM$"n\TUeA^ſ4]ؒuk!m'g+1+) $b>+lqi_.7uٕڂz.%/3g:[UviG7լ4fgl59h1#pfRH9%ϥL2&)mST[-f2ә0- `:V}3_STyL xT[S4F%ko+rzSS2M)Yzд;dg2]%ZVњZ{yK`;d;XlEӕnA<@x 'o%Q:p*dZ*PJLk*H5TULpgBr]j.h _t]$J&VKEN*: 3u8ɳ^ԺKiN2HbjTh#j+3 5 /Kh#ISVCKpOY&4@O<;§\ )RV>e/zISAo<6>eRn)vV,C76Dg-zKVPVv&u%ĤWDTN#yi?z0Tv\,dU,l x -3n-t2mh* &e9E"NL$6\&1Ӗgj$-.&8JYV>Ylj"9 AKƞ?B^|r`\t?<66;8BHiD3u;EJD."w6[H1z^.< Ix֧#ʤ$QjבGQiX &JO)m\GYgF =c%R3]q* zw7*TPKdVעE+3&h#ij =bW0ơٯ5er2i!agZ,\OaR?@86I)r%S4hd*!}# =C~܀a!Ldbِ=Ź8/TJZ#bL @SOKjPGoE Y9Ed~Szπ_wߥDR'o?>|<=hӌpC\4\K;r|} Z^<(#Bȑ\Ev '=,HBJNFK!Cx,q>>NPw"XZYZW[f*.)4*-iYJ|Os_[m6%IߐXZ^]OtV dĐ,X4՗UV=/ըV!4 %F{ _较 JRuAfJI:$}JV\o= "@28 [`~o!\sr5߉;UL;lqѓ߬ VP ,f2ZtZH+Ll Xs@Dw5THzUm  mJkt_fQXMgNQ]^XQ!l_G(6)7M iUk>ZҶ dv2I5:Fk'C^עwW䲏%m[>fQu$ Y&YʨeE C6kbmMITND|_l&Up\ zFsq&ڤ<P$dI6*X,֤]4XV"(Nkݰ{ɲAې"SY\"KrִhSsj)Z`یYB DM YK-&]Z*)kjtTaƚ%Z5.}mAT0"kK׹. ɈpxxV;W97)}֍;GFz7ndL[bzIIɪ%+z$뼽iw孋Qw۽/4 L-lN z?ptϏn^;q_`ɹjLB;ncd]B$h[i zx~=%6럇 hI2[RgI[|p]/ Iy\MhVCтiaRia*-_Re.љ,D$IbQcژX>&ֿ8؋}H2X$bt[lbG(H,H,˥ZF-8>˯E~\?ҋ }e˃!%_Zֶ*YdYdsiuGW{YlFE휒=e# hJ\e,産ϗzwR(DfOeC=Y8/$RqOdh'q3^~mFvn8޾"BSj$,P9 SI4gǵ4g9f!܃.8c,-~.s65%Vx)3MCB"6wR[uNh\ŧwF3?=/P9x㢩MUf"^s%?¼o|lrعz*}| oUf3<ɛnj5mHV@װlRHL CT_E$dNpSMLJ@lՙ7?4ɢ&jMh$c5#\ ߈s=|!8?p6BoODI$*5Vxă`'{huAK4~i9fKP0_brͬq.m~G젏o0iJEJ=6'k2ԝQAz>Ɩp(H(OAx)mܥ 2!b5$;6g7i;_QeL.Ap0x?{3x4pFq}ԾP ']! ,=v~!;Xڿ/0'z(En DUe9XbƆɞ"2}X8&,kd-9a ׫ftCAd6֎H\=ۤ(#A|v&3,MtHH 2Doa˲ ?cj[\oMmP=CG"yv3toc9L$hѤWx[l]O;5$"Z2x`qN- gr`)srQf =vb-;@˖yy2K%v4P؞1ij\tOC&=&!y& O`u[ ?` MWd{tN6rpk\^uU|#Ǥ/%(]7m>&uO1G22V&iO”mwdhaʉN>t.g<jfu\ B b@GАHjW7y<}_6|K,"Nj*mŵWLpz )^ q3ϰ*=^ŕkkuWLDuqV@-r&gM3!,C]{YeJln x#7L_߷]i,^\UUҲ(Bl7VMZG$Qέ|1>ZY?*!jA?mĹ\Bޒ"1Iev]fKٜ{$kŜSR&SS2ԛ}g`GJL~*9!U irE,UJX)%ߟRN?8+AlU]re\ֶmݲyÚ5KYvs8qSƶƆ8_2y5fϬ9;1Ew4->*Mi4V<[IB%~ˬ 1-H8d7=I`)ؔbK^Zc|Oc>17"VW<0D1܀X!#сģv8?@XYFjX!83M WpKE8klmsY*[S 8ϩ_Q#͖ڞԇl̿w*;_Mn}VuV9&L\n*شpņ@c3rG6ocU2Y]6Q'jЉ$,`f)TXČke83~W-DE((*..ĺ4s<]!xH!?Hjt\%cP 'gڍ>\$I{)JHP"縗(Ӽo *ʗwN/[M~ 6μV ; V?Ij R&0b0;5Է |/!Qy_~e~X6;dv\Jl:]Bhae2= /TM֏ld+E#{?3} NX֩7WI#yPv|DQL.9$JUS/@w=ȬR!%(<-J!zJd=& ~ ~>_Sx}_%J7zbnGQK򑞞;,ԝSZ0k8juZ."vg8BIT]kJQ' P~J6]|08۶NKq% ]z4z-#\V9-fY#thIFk!33Ul`.]1p?f Nzd£{N7njl)~fҲ}tj*i$ m$7^zTh5!G0hpuکoraHd۹Q3j)I%IIIJ"MRHXMoO8ߏ>C8q& (GXX`Yfzdn$SF(mw ]$yrC^|MLV C_W>lK6" {*XL~g%'<1ysIa-'x4yU߅=<̢k6o.*iX5g-Yރ?/0~j4-$ZC:Mۗ4PYg;Α=fU+1gTܔ఻2Z^@1.`eqk 9:YhX+Tk5li_ja_T'fyM4&DLهR% R53'MX(}$(Q,SLOfY#$1s"Gx fKUr.07:_"WmH7< T%?\Z= 3Ӳ7-مVJsg"N%VTuDmw?9C9"2fXttwv*?tJqJU'JZ Wn[@|8iH)rԵp6?"L߸Ȍ@G#\GXa℆-LQhF^;U+F_|@xI }ULjRV~i%sxؐk$_BTQnXNICqQ%1Y""=]l3|&u6\YL H|f8dNM <,n" 7r-KK)Je3]T&)Ѣ9\ X B(fTS$J=5iOR|D%;U .-P9.[(m\ t%At|g%\^ʹu:3;(6-(ʃaM+7wAn;l5B".sSo`rق5[} v.0gidT:2_tsvzޢ@-.]u! ~ 7P:+l(P<\,BB9 Cpy?B=PBͣfucvh3T44u7a<@Zwնuun^H-\%z}碞w]X%_ B{ܳTKk3?Zh8<8/x墉ᩕwvY5wV޽껪o9ĺuO@os#3+;4:2|q>1rlS(l G_k{lmy [Z6AL|?ԥn{M;6e.wC;Ǘ]+Wl;\I]䫞Zr, ]}+43Ж]@WA?+o~& VwD~ UbZi#-v mƶKp|7γzx"o@O@#?:x/-0-ë@bK vN@%XZ-ҲFnB ԃi.%6bڂi'ο ߍp)D] Х0Ye`9XoC-s@xz Q  F@"NW _ z%om0.2VwDv@NntuyGpXA#=6S~@@F#kW므.@fƠmtHQ@8<"/t3"823=Gc c$Ơ?AK#ȷ +4A [1g@:"m8M@+o=mM>?z @A"@=bZi#-ȇ@qLOF~5ПDԊL;1yn;)Ls1-LD>z ?Г=)nhm@8 Mz0ŴFL[0a}#@ܷS( -@Ñ?A8In݃[=un݃[=un݃[=un݃[=un݃[=un}9 .4$P? =B߾ T9Ԋ@;q|[Yq|_4;$$ &qq\8׿ ?dc6H i K.,i’ K.,i’ K.\.,i@294(X.$#|\(7KcR b hJ1mYUDA9pp\Ùs8sqg.983p\Ùs8sq̬ '|bmD#01 ` #BJbCD.RC ց0U!fɚ RP!Z wBl !L9@5}ULxqPe^hGl.~NCx Y3q`p~A1r #ep+ko-P6S!W?F8h>!tBƵAb b?QQ4/G1OCЗ(b@A/BPrPţ ᑄ Cn< ujDB>T\m(o-Yܧ07tWQneseR~>?gJ}&dz݉.ƈFۊb8UGPEA 5Ncׅ)ח[WZ\Qz0Я^]x3[]%#,\ɵ=D9pD_sz6. WtgtAƟ_´ãcLpxd8;ejX [?6tG\eSpm8 Lމ1^cG&¨j 0.t#뙦ޡᾍ`x4?YezZ62|geF}AuwKo8Ȍ ôCrf4dk~Rh_84^66Bm2P!;4 C@``3k vCCSu,8%P0<41ޱpp a1hoˌ}#GEٱT94> C` 7Pov¬pHob3(cub1(eaf2CL87o@X¡QhwA@ e4t1dmFCe\[Hxcp@Eۄ/pEKK ½F4i ]ȏaa(msf72o4w0Z2 &Fׅ{GOzׂ!W6~e r8ZLhtcH8A vÃ1nUT,2p42Z;Cxߘf(Ee h44ǎz?<eqj8Wo9mC}`F#A+Ȥob{ Pd|lA YdG2eG a}YZCvJ]FZCeB_-F n^C[BC#Popx] j޶d{X`]@m 2b9ZQ"7 Ø4S,8 wDP&L1g B)l@ !\cFVQ( 7@;*g? ԡP/30]Cc] ՘0Z?=VÜEq >39Fu ZJFE6=41 #0XaHyGQ"/%0B |4LHg*$4<Ҹ[cH CЙ l)ˆ`XTbr ŠWƉ8whx gCs4 k 70j~t ),ҷzgIMW=tvu,nc2k:,iiX@eLGSӾiin2K;껻.Қk[572\{͠PiOjFw6ee^05]=͵ZkE]|TԷշiLb`jZ[qS5]˺zֺzH_=Z5min2u5m5T҅[T .0ʮKLMWs7GpB\ kjAP3 ,zQw}/u5PW7*9Wyą o>"w1\xL0Ӛ_xT ΅\x\q&#!2 q$B Ons?F !zJ9j5/|k4(jQ~ѯ7^/jW`7V>" *`F,'A"s8~ IRiRM|DjH!iEov(I:r% H$ɋqr3y E>B&#/ȟߒGwcq ORZeQY*^JUaj>jwPK+z'5N葉oߠnߥOSPG^FAOK-?4CDg~A ]MĈn7`$Fh`0 =F?^ 0 0Ff( 0JZ0VFÀ:VA)'owH 9#`TUF̀IW"F[0RF6ـQ `6F> `t0z0z0z0:ML"I5 FJh0FF߆F}@JOCJcT=`0Fѵэm=ѓ_F}FK 'ShWz7~^~6~Qh"Fq#`UF-xgn,l=pu?`]e˟O1R Fmrh `F;{cѫ͔zF^0j^h0FF_Fh)z/moλ0:Հ` 0==  }!# `W6fjHy0B_bx0'zeFZVY?FS0&)0*Fjh00=^>4Io:d'] -F1JF>0`t0z0z0'C,(jD&b>o?|wmڤ%i]ҦMk۴$m$i]sY%!׶i$$IGZII!!iIڤ^t}~;?s3s|>|>眙\6j6M}kl(h!Mn6A:&+M 6Z6 l5u5Q!e 4CgId9/JK`U`FF'Cч9tCxc:'x*]N>_+{```c<[3|ǧj ?ſ֞2~L'?蜟У;3/{L}ê&ժթ- *p8B2*1q8l** 81;'g`TD-jԴYyy"@--!XcXvHII)U'+ "t(V+Xjhu rTvnbnxy$â3bDn\JSÈOIL(^K Ґ Sʰ3B3Dp|Q[PW^^g [[Ͽ_j芞6joV=XԺaӢs{wb2,P?o\W .i x)*\W)wJ25 ⠰=:O%{:a$NHaNNg&ՉucSr,\Oگ0B&Sݧrp{L aAh]ĠF,Rtp#-h]M*Of( 9~ Q#d@xQ|p† 9^ 0'*zC5@TmU>e5VBG5?v` vI4, KA2:pLa^$JTmX,KFY, ıcs:<}K @h`Ɛi="e}ZTWÝrVmb G%P3i5 p͈; h047o씑 'G%;҆yj\MК֫x w`avkgWk=- 3FOBk;h|KLV5H7-K֌sM3k4Y0>==%N  dց{oޜQxڟ^]eD5+9wwpI3(ϐ 98t%{-nu[;ý%Ti#J6Ooͷ$TGyb3%J,SG:ʣz"l0rpP9.d=q5cªwK3}zo=U0A#2D_kPL^~ m~xV8)Dǐp I#~4*xGQ $ .o$ʹd!YJ\’n%$>I=3(K.Qt"=@*{L&,2,"#C m`{~Q~2` D" 12ΡH |P:Ƶm<[ðmӍѤw7DO$escH}8.r7LF!7H'OKU W¨r B?DP5٣( SBGՈ!FGqJX114"6El -b{N]0{"C@8 11q"A=3g!PňW#C܄qL0h?Qē Bt#[D!A 11 1eȀb'È k$ >8qĕ͈w#G8NږGa}>H$N(K#i_w͏{YLɫ/m˒t'&ݗS|yē}җ=2җՕv#Jz㵝,2L$SLj!Qwե'\_]|]|Зony:ݯtۃTzV`kc'{r'{_}ӓ;ҷq/e4_Z2Os}k'{]._zǴ s:0gh d&Յ$'4'&'-d:&ӯ!~ *z%zWXָR:ZTJ7Jf{+zȈqٕYm8bdt^ZHR:P‘=7} sN-A9eOV%3)8CVa{_Bz~+aʟUe*\sxu~ WMAj> R.0/U?\,iP>f23ya{{+r1dCyK S8Cd Yrx:oAy0g3 p30/Ԑp>tBq CJ'T0HE:h@K8 ] ł _oooF5l4"oCֱكm6M! 6$9͛F o[SƟOS ՞>@@A>XLC!r}>6cX},?1&+ k]zZttf#g"M6vJv\Ȼ<$ʾܕe^ExU9Hr0&!G#^~)GH9J,kȰ e5eqr2גx-'ʉ#9_r#ʅ"|וu:+ ^Ox}|_"ߖor/wxCEnɭr+y#7;NDxS[=ro& dB{y/+y7A~<,yDWˣ(o! e!Fy;;B yn{Q`1,zҳ-~2]2Sy-ȱ~n|Q$_T.+jN=MnʏL/!J~-oIY$OӲ8DB*6ojPdFf(*j}GS>tЭU 4F`hZ1mNh"uwڏfґ4d:ΦbE7mtKң7HҝBZ O$fY"KaKc}Xl&+a, l*沅l)[ֳ-l$+<ׁ~;<5@/^x2o;.'OC('))>/KJȷ||906q~JcUjh1Z}\KhZ[K&l<&|+kP ֡\xz+P  owPlF2.U@\m(WG*U\؉re#o[7l\*1n_O|}W^_}^z렯!_/}z}uk_c^}:.~0Uz}U)_|~*팧zkϢ~yzYezzY xzYe{zYAO/KzzY!O/zDNZ (sXOB[p_| D| *;|37xS~-!8$%o^xC.P=fG18v<ur`0A=G5C35[5 7W(t] Rk GFH>σH:7My%a|!__/Ksz ͒g3}. | r_߅ܕxN^Em^`} [B'92<3͇x0@>|;%cd yfOid:A$Oa. E%s0#}f/y{ޗ2N_Y?b*S_#K0S}f+JJ=MCd=MKV/O!I>"d7!Ksr|AC0!_kRHr|KNH"?GRL@cf֞:[YGv#ng wz&1F_֏gl-c0v.1>Qŧ}}l?_;̾dGžbG;f;ξa'ط0);~d t3a$ͼ=!w4ލa$0Y_2[`>?;|'$ gUZM{_ۮ}>vji'S@L۫kk/!vDJ;}jǴ7 [V}~Nk?ja5o4ۙ7f{yѼdnv60w]ͻ4a4{ͻ>f_L7aɀ%=0^s9ig2GY3l5X4s͇s935'Sͩ4s9|||ڜi>c25gϙs͹ {}>h_Gv}>akS'X8Tqq9՝\'9ϩtz%ΥeN#r Js\pqN\rwRNm^~`˂qF&˃͂WW &^L & & ^L  l1.xS05xs}`ۂ;v +;'7/?,7/?y@#G!]51 =Fގ|;;G+|='sɕ5myce0ȷ[ƇBsH-Z5k0JhZG] Ԇj#6NM֦i39|mT[mԶh۵]j Ϡ +(;xw+QvQEBFPvWN[AAٓ&E[MQPP7Tu%C-ŹJH%<D g ǙǙR@P>ӏ#U^B ;ӆ8 +?ƀh)`z9`O 7MRkh `m 8̤mI{zJ@nUx#w%U|f6/.@ٕ.e}eweb=({%({zJByT˘%FZ (WCqJK) k:m ZyZYlsh9hs/硕_Thh΅h:h:u:ubuDuD3E;ŵ(dwPV(Qv)({J {PP(ŭ(Q=rTJ FqP>(/JKSZcH@;jjwxeQqh,*RV ʮjeWBYT\,*Ej}jM݊UUv+:v+:oSVtRVtz ÝX]zHz!KRaDL3o)C ɃBF{0FtbjPP0;QEPI^5@?r*ŀTQJ?ضh"E3qW* -5"Q$kEN׋Z7FN$R͢Et6INoi'X3aT4˻aY,=EXkzm=F06 uCtw.&!z^[}E?}~su:d۴-۶;h2Em Q=ni'uv+zC{mb?O?8aw4'ډq8uXi9N3'޹i:7;[έ`' uq9:ÝH>g3r8ag88\!gl/od'yj&(j= `^ 3`ڏF%Mh6\Ou7k{Wxv%iGBr񆱜4kȵ9DLFA)ZkZvuMtؗbo(>Ԃ1DSL‘iLőd>#)82}GSqdLt:Lg80&}98}ǡo;`6ht%uO:К@;VE;v_7CoF;6 .*[6䟀I$Un?B֤" 6.p%]qFbBz2uT,Ӡ7)WH(L[h.5z&ww;rǸa~!w;}ĝNrG)tYyuS3ԡZi^Y*eRw/kHWկ"pvKz@Up/e^v]J,5u($p$]CkݷIHs7fw &0{d8w 餾$ir#σm16q)l,{ꗏR]J`'KK,Xԛ(Y@J~)?%"YޠJ^/*32X2K,s\ \ee>/UZC1ev0=.Lԯ6VVY~k 6lr%ycߤ ac8RϷJVkz=kWFx\37"dE#LIg2ZeR#/5^2RƿǸrAWJ_^o淢MY}kB]wneWjD w88d#\qp; jp,_b??:|(!B] GqRDF-~KҦ9Mh[ڞv]iOڏfatͦt"Rsw܁A w0`'s3!CܡC{qs88 8Qhn`;p cǺـ99ǹ>>*;3uw"Dw$I> m0hAp~:kI'YgugΆ6M1hY{;׋ݤj G%m;PsBQ~k&-J5Ԛi-VZ;U ԆiY8m6UjKzmCۣЎj'bХGuz3JowԻ0=KOԧ3:ȢKѾѾ}/E6%14Ÿq'N ;-1$aܹN Ɲ0܈qnr3Ɲ.w&\=뀌b ~;fZ2o テSŃԆkY GiZ.H32,, }z %r~@]{mN;^`# kI'S:YkE.@V#(5=d]d@z}&mtu/A ԋw]gAG P[@tl}> L}><}b}J-}Yҷ; wz!ȣ "7! 10+4"F hd}#hHH6ڀL1@7:]Av1}@6ҍL0cȑF2o䁜hL1ff4怜m3\`,6\j4o76dl5vn2 @1A4'@7b܄UAfYd 3ڬ Yߌlj6o0A&)f;mf'.fwifo3d?3r9 2s s͉iLOy K͕ zMV[.;=~{̓QGfȓiDz>a WD&jhQzr@Ċ)"^ =uJA "M=E?r*F.Dl+& &i l\|H,D, Wubȍbr)- #8BqRy*@-`ԻdZ@;!2P>   b+ DS8BG?2Ȥ#d 3 Df 2 d2@& Ff02d"d 3 Ef(2 s2 3 a C^dE^d#3Ȍ@f2#HdF"s2!s2(dF#3d!L2cdȄ #3ȌE&lddrAd@d!3q<̃2y y yYBd" 2 2Y"d#ȼ̫ȼkȼk,Ef)2Kyבy7y7Y dV ȬDf2YjdV#7y7Yd ȬEf2Yȼl@f2وFd6" MlBf32ٌ̻ȼ̻lAf 2[ن6d!>2#>2ۑَvd>@d>@f2;ف̇|̇Df'2;]Bf2#12#Fd ϐ Ef/2ه>d#||d s//9Ad"sCB029d s 9Qd"52_#52"S1d!s G82 2 sȜ@[dE[dN"s|w|L2E!=2#=29)d~@d~@429̏̏#SL12g9Uc&) DQ"C0d2  G#!!!###c c c c"c"pQDIiJI %D4|O͘ՕueY@W)Y>m꾃w:c5CI鎟m7dqd `m59(k-PR*uW?׸9Ϳ?w?&%Z1G tT~%KVW[S!\%eZ\ky/?Yϕ=d="z.Cs_?y Z2!8?sD_"C]AB*\]m"]j,ջ즞6MPkl#md,4'a~ۯaxoU+ܕЦmN!@ٴ%_|o[!{on7߷#Nۖ߼~A=I`aUX;]}>Xcݲ/Ar"ʑr̒a-s8+ˉr/ȅU|]mA#ȭ})wr,{~y@yTN~/?3!Bf(CP(T%tNzyCjj. 5 5 ŇoMioPK3  罗uQ_V_Qw>O_QފZRoeűת/YGv;z~,Úig]*.K%czJA妟.v 3:W\n?bSq+U\RYi~?dmq+-+-wUZT\|^y7BgO·nO?Y~݊%ʷobWE`$^!Rr=f]5sJdlޫ(">BԯҜ$Lq/|k n ġ!B81x?tg=WIJ4Vٚ2y+CyKf Agѹt]LtDtw.)"YC-6Hb)^ztɆ,Ƴ<6=fyl![–|mfNgY!;Nsy]ހ7Omx*Ȼȇ< |2g9|>_ė|ߒox?㼈kfi~5՚{Xkߨh0W)Dm6/0"az 8 Yat!f FD8a"G8>Q "f#ޏ8sB0G'"NBC|q2iD,lG8E/!E_E| q)o @\ q5⛈k"C|qFME܂ } @q'G?F܍X^}?G<AC ~xkBcA<-IG<i(B)"Ch UIaz<&XQ+߃Ai=JaN'`}}g% 1qQ~i1a04uf,XAzUV ֫sa=5^Նv!az,Y}X] XX] X= O`i4Ø5&υLQML^XOI$X7;;Y8 ;5B@/*`qx5  &RHp΄gg`]ENqݹ`]=m/f;W!ԷXGT3X/:ֈub-TG@Ǡ͡Uף}`sz`^:Ya=k`>d?t 9Lr3"fECa#$̒Fcʱ0WӃɇ0.Ys/_/,_\,&0ZT.\.0`p\IB]]{6r;CGˏ?~*?L~O\¼ fj__~%ZB 1o7'I"YxJBM4`)`3<,Fh0ςIx f}P0 X-3@h0 `MP`t(N`PcC^ЕW᷿V>h1eHNkP&xwfdd^3ɄnCə3 ']’n%D>)4-Q;Nj)J1'6SKQ/Y` sImuGm8\Dk!r^:55iӍ$CI ?ʤ RT[Or{#R BtH#k" "6Dl1b[tO"f"CP!k 3gو'#@8q1r6@?K˭;吞``smqHh.~h!ա=  DLe$s4"c$"^?dcfz~<3xs6!G2 c] .D2ňTd70jyEDztnU?FQ7DV}Y1?fK_~HvF??32G#zaDvqDd 2m#ȸ 2uQ聿l7{+"R\VbK?9.'߻6^~|۩ˏ0/?w67Oǥ//?4??b\ݡG_G.?F'V/CSš.c/Je5 _/V꿆m 7x@tEuq''y/>44e?D1;wB\\==O/vwK z{{^n?sIz{{~s=^~[{{~{GƽҽRWWgkkZZZ{{~ν޽^?Nqu&&{{ۤpp{{i4){{ͽϽOF3p}}D:~_F .rwDRwt-%+2}}Jzjte#wV6vsWu2McX]^kfFllX7^.dcbc}P^PCe!CdGc]b}Lv}y yyɇ==`Iϓr)Sr' <{y^ { |-_p1Ѱ&k-)MFEo[]mܟFOiI|d~Tx[ƕ~&\V]5/h%۪-|O[MͲ~|lաEÿښܢUW/ o[r˸o[-ŷzTܟϯ-[=B{Oh*Uu}B[?o=w8ۘo1u o] m|[#[[o:mwvOlkoS6 u}wh lh.A=0恩x|ma3~oAph-?zhC‡_{xۗ 86fp8ϕG2d9!ý#\8r^cm;ƱN'Nh6a؄W&̚0B҄/'5!5v1v{Ncb$}RI &LJɤ/'m9zrɽ&ySN7S>2K^z奅u_*}NS`j4si ܴ-Gyh؟sd?P鏭>;9.z[d<B폭>CySz!tm|<əW#Gk%OWψ tcqFv}O}Vdr~Y+t U7 m=%|~x-w4߳_K$}[[ZajEzWk3ZV~MkPYaҢUJL5l:*QԳK+Zh-_TV]^2+5ok9C<.V1 z奺R9![+N %$s;m~/Ej@E!zmY:鰪aU\jK}] U[]wuoDPoaB5rv%/5\VPxJ.[on4/oэp_P>4w]ƄjqGKD M?+4G{ fE:áY"t)\3RsgpG3" qQá<FOԘ.(c ?/;% w@AzvGHD [/i.UVA4.{/إv>H蚇Ylb.G9xV,ƈyb/ծyb"MdDQ(⸦4vب5ךZKإ AAqXyHm8F 6^{Qj/isE6ODiEkIlNm1[L[&m@Z=i>zZ~NSKߣߣ]׮郴C`>Tݠ??ݤGjGhBX룏_tm}OmH{\DT{ZZI{F_o&;,-=G/z^ԾOgoyG]I$u).cK-P6RdLUm-V&ˎIvvˮGv=I'ȾyM;(wir|P;,ô#r|Z+ Z$i|U״H~GVk2Y&k/rv^ٺ! d+RlQz[[c[-)lsm_l?~'m}-ݶ_i˴뉶RE5jjD_u$P_UeNDSNF7E:o:owG)C iH=݈2}p T1|q~hctЋ[ VV]Ia`l,4dU>dl26G9m앏qqX02(2(cSF|8oKd~nn#ߵl'?m/?o_&?,mbL~ia!GGo23qrEJ>\mm#7ߵϓ;?4g^Y2۾̾\WW2/T ~L/ȳv刲ikocc%[s44ՎWZ:f:^]񁭵c#[ǧ:8;9~upHuvlrlusluluwtp;z:82msrv8rl}>1[_ GvQeQ+}mFg= zAуl~v衶яE?n{ zd(CѣGq~6'?:WF:rd\\cKpuv8Sl8797ٞunqn=Lw=scǴ}mm_myؘ,ޘQ}bL[ W`sd4snscOڨu f1jbb3Q{,3+ꠙc>(*,1KMˬ:f2OE7fmTy%]ѮQQ5]-\\.\ސ u.=FsWWkkq`kkI)W55θ55nut1nvs4nwer{]y.1wcc WuܠGa 5xxOϻoqf( mER!- SNR;FqW\<lRZFuڤ,9D __7ajpTئxv"-ҞQ>-A\=-P]\!OɻxVhXظFkr~1wxƥ|{ރړ5!߆ oKbGE {ː#_U j,d$dʑVw6-َ e3[oU\1X9[X#0ZegYb/TeaWnxfk-flx ކw`̅wa"-jEqQJh0V<͑v*Dž]9S\#߀7ն%js2EoA!Ȃlȁ\8 y#wTceQ5ˆ Ω0n`Et5nc2i0^oo #1 fEsM0N?/}bnA{q}-#\ Cьlͣa.ߝa+Wṫ9l6?uÆa~nC|z߸ xJnTBDeoqq%|I̗|ݘV|w1חn̵/ޟV֕u%o][W漵#o[o=xk')gxzo(l!̇Ļ7Wx+wH")V>JL7y-Xgn'p2 xuV?1(Bu@A1RYPA8*]U98up."ȝ4U΂ÕO`IƧT-Sl !r0@P' pj`-Ԇ&~Uk!0SO3\ƪ q<S gLMŵѽg11_%__Z׳|pJYq(25V @ʲ6c;6lb?(u!TCS 8Uc#kq|U(|U^~!8 =o|w~<ӏgL?3x~<VixZ-VExL 1A<&[ݔO(9 T|Z5~Ə?^kx5~Ə? b bE?Vc Vc5? b c?Z^kz-ZEhhѢ-Ѣ-Ѣ?>aGW?Cdh/PRB u!^$,kyDIb4zG=QO㨧q8i4z,RS%^3 5x-$^G#Vīx>GX^h1z!FNҗPgQR[S[Qw㨻q8nu7GݍQw㨻q8nq#}ġ8 o[^Q/㨕q8jdqNQ'%N|8|?3 |/GJ G=QK㨥q"/@_w7Xo#}${EyH<1."PVx7F%Oc<J*;s w]^,>'\ ç;«8Do?3\O 4W,Ssp/aLI0抖 R&U)gqm]y?ap 7r3tm^+K/cm墧}*A,%9|_W5-,|?VO VDq lmv N. ine8UIޖl`ʋ8#t]{ qMp3@?ݫT58h B;h{{3t L:|q9́l {1=\v옋sFvYE t'GD<9i4C!xHs<9Ҝ'[<ي'?ـ'dl xO6<ـ'd;ȓx#OH85#E'yșs#r gBh;o/Ǎ8,~<,uK3-<3-<3-<3-<3-<3-<3-<3-<3-<3-<3-<3-<3-<~oݤ᝹xg.ޙw❹xg!ޙwfxg6ޙUUZ: o[3lUm]`t7gx7[xe| VjX9θ-@Uoo;J{g]tWDC6ѐK4 DC!` DCu/ 1wMWUa&Bop-o{=? X T!QdEQd9q{Opo@:*9>{J""9,!x1<ց/p\3t Czp zAo}fpwp p? A<35 o, o,G>Ϩ'$X Ksk|K[i;~a`5@6 `;]`U%DJ RBD 2\їIA_:u FHm3l ҅iupF5e AshW @?Ƨ3q% `"͘b̆ȅ<?.iJ7-\ =tN{G|Ύ9;>gs"K p% @f2;Vh m µks6ks6G' ]+t:U Q]ɻ_ϱ~6;~sGҁ.'8J*u&x^&Ve1UIXD'J<<=A9@ 0=|{pᅤE-gl"J ~q'E' V~ڣ!x_L/v.V{C`?dȄCِpdxQC>("(8P̉M]F;>TB}*$:kz*u~I%,SΕ ZX c| #XfJH{F0^?rXGN+0_K?s?s?s a'QKXSkaM9)5尦֔ÚrX-%V6i~/CHqe$֯*t vKni- a4ZTKj)R-EH"RZTKj)R-EH"RZTKj)R-EH"RZTKj)R-EH"RZ*إ)EHAލȻY7"WH+y6"Fو<g#>0U]ԧ,Q2 l_kɓT-{^-a.\E­럪1;~a`53dXz)+lTؤγi*_JX1}:c$E җa_7 f&[謹 9|_m0 C0 G` Ga<qO0QTxF3,<c5ǰ&̂cކw`̅wa{9VJ VjX?ZHu FHMj;֞RboJ?8aߨ(BA,tJ-7OD~ X*K/9NC_WBv}'g;o@"R*JJej}>V[XяXя/dYXӇ5}TT*R*)JEJ"bTSr*VNʩX9+bTSr*VNʩX9+bTSr*VNʩX9+bT\+rVX+W` \˱r9V.X+cr\˱r9V.X+crj*U5JUMRUSTT^ |x/>^ |x/>^ |x/X cj+,岱\\˅z~%W"=ǂX0 c#X?قbVcE?VcE?VcE?V,ƊX+cbXb1V,ƊX+cbXb1V,ƊX+cbXb1V,JJJJJJJJJ~J~J~J~JYX) +ea,RVJYX) +ea,RVʊ W ~ 諺a V q`jPj@ Z[vkn-ڭEh7vh7vh7vh7vh7vh7vh7vh7vh7vh7vh7vh7vh7vjN-کE;hԢZS+ 5ƥKmZ1#uFARd+=w uxaw?ZO+JR<. ˑˑˑKKKKKKKKKKKKK9,,,,,,,,[~o9-Gr,,,,,,,,,,MY*웰U[ }qqqqqqqqqqqq1d c@21d c@21d c@ƀ% w9F X/` X/лM!Mru!:诉fTZvA5Dh uz;{!d,{gN<}ч!& }rf/d_ D^ 8ׇ‡\>!|C.rˇ\>!|C.rˇ\>!|C.rˇ]%]%]%]%DN 9"'@bB68UUxU^UWUUUxU^UWUUUxU^UWUUUxU^UWUUUxU^UWUUUxU^U9@3)} V5MߩOS+kY!MeII*3 }ꇊX0#?b_-t[㶩3vI݅{G̗~1 J2ѐY s"RA]pܨNɾj6SOת<3R8̸Umvs.S1q=sGwX*hV r,J].hJ\WBSК6Nsݠ]xqRevuA?lBh<6O3mz澈6}hZbH'I4xF{i4W*>4U}h΋hy)4w ͝Bsh;N9/@sh;BVNhMFSi4t FCh$:N! YhBCI4 BCh:NhmEg'oF3A3H~.329^^ƷvHKC}ó2Vh5Z8QvQdW5-LyIBCk9Vm )7+ ٫2s%69Vlf 0K@^ugƞ0D%ndVhڏ,AhÛ2ynr-ټ%l xrOZjSOn\d.Od,OfԈm C7<vF{=cx1<^s 5A&1xL-s OSjcX BN x Bױ_`;y^縈w!c *3Gw w${53cychnVD{Ja7Of5԰V]T]  h⺖g=*uZ9JUu>#~yQȇBRG}On@c n哬fd BmSc lTVF~+V S^An*#7Ke2Ry,2LyLk-PI| #X$V4O/ڋ{a3k;T+z&(^K: y#pT Cx[[CzO?J5Y? U쟂j,y/CE! #ms._JH t`Se³ߓ1j0>\6mPoG#fSٜk-1Lflqjur 9!ѐH4$ jm p|* 0*> 0 0Ցs :C ݠ;p#S]fEnؿ^p0ޯ^'ӼNyxxa0<C`(aUFF $FS0 i}s?sc97dj+]4sakvak \tCGG(9g<{ Y7L8. 8&k$/I֑9;Ԡ].& ]l1]l1>|N=;ѝNe(N9dzWTTl|e'r&AUɠd{x+AuT \TLA&~'~R.Q mShy7Zu{\|Ƌ >%{^Xdm/Yۋdt|)##琅XA }a:urJY3A kf53@2LAv@ud%~d2#r^%yjdtZ:YG6ͼdtY:YK!yn2R&)ƚJdl2O6Y&,IF$dQ2jVdl2G&VdL֑52d E"lAH'S)d /KH'SxX= I$Cd!2/,BF"2/L'$A7}v_GdU`\/ˉQ&3 "&]f!"H"* "@T$" j9D_W.% ,uh:Zr DO:toIxz!V]&Ԩ%.ɪ9*FR$+ a!Y:jVY]5KgUլRVTjYM5)e5&UTR l5(3 Xt` ff f`f` fa t@fAfa|0{ e̞5~T8ɓ_ Rd\r`J8;L?w-9onr֜+>~t.FQHN}BG}}yUϩ*rp5y kV59mG5Tsľ$wюweOQ6OSyMТ-#V h$Q &OVC?=8VOSh$=FOSq=ZӏvOSa] .CՕ kbmTR*WzXOkb gX0y`* @X .4\@^.`l4jH/Agc}g 7 .fۘ}yT@^* / CHT%f\R@.) L}c%'XI)bG~?[%9|#0Ro*VmF[a3qc,Tȵ52j>a\&c|X!Vrp>m%^r11+h|5_M`|WȾ ,FȝjOFΆ2"TeU\*+XaZE˹h9-\s \vsn.>VC>>Hh K>WK_I˦ׯ')RjvTZ*]-=K-%TZN+7`+/xZN>| 8p/ _AzRz2zJzJz2z z z o(o(o(z"e>}B^v/ _bAĂ| %K,ȗX/ eeh$Mԡ|4QG/CueeehMQ˨e24OͯQw˨eܲUTR,R,%s\\A|Z#;^b 8N~R*E2?gžc|_xWǻ}ǻxwa|_xU19Bj>T1OT i*e/e"eDT(SeDaފGW|Q i6-¦Eس{6?)ĖEئ`P C/@t_ }YSTLTLTLTL˴ғ^a3{5_8V1+fEh9tAW5Fܗi*_ùVfe={{4cT_i%PbG?/f~ǻYg%ëQ[g~gxz5Z5Q,Y5_)ڋOhxjxE]^r\U 98upA-:&ZGN@Wݡ=F 7p3V n;N {^ xJmU۬-vة6j`7ѶD9|Ԃꓨ&T4 AshW8nT1 0S4^\T41v!ػ3T'8Ǫ/as\' s{ `+R84M p% @&29ju3`<P/G?6'DCF&p%~x!xx F$<7 M {*K0txE-Ûex2\.̃|x| #XKxsB-IQ8e`q$c~Ωe]7b@F C1x7#2 `Js$<'wyCHjn*C|B7e`t+U*J| RA!ȁB&NC5@Y<<<>O_GQ(||xc1|xS, ó.̃_g=Xc,6MĢX4&bDAUg`XuVUghhQ$Ц65} _5~?<$c85WB` WSȑST̠AFNY"zbQ0X.ڦXK0tx^0^D!W!We xdA6@.<@x <}yfb*2&FD[&[&[pz2*(dq;SDrDrDrDrNj7E1 fs?2p%eee3 }Sv|ʞ_r~9Te+ZMԼM$5v~/c'ЅJe8ZCp-:: :C ݠ;,]lxlW bsت:Cp;~ȀMԉMԉMԉMԉMԉMyǠ C)%1E-v;a4{!6&@&/,ȁ\8 y#p9Q<(5Nq0M_eϮ#1d&$LfАoF&p%3E`Xt]E`Xt]E`Xdddb 0 0BBd2tttttt(t(t(t(t($dRLjIM2I&5ɤ&$\jn.57KͥRss\jn.57"뺨עZ^kQ{-jEעZ^k璩璩B,( 8PMVMVMVMVMFHFHFHFHFHFѽdt/KFѽdt/KFѽdt/KFѽdt/KFѽdt/KFѽdt/KFѽkSSSSSSSSSS©>/T>8_FUH*$PLqeaz000MAYT7R!I=oލ%7r:IhHhFRfY7j*,ΗjaR-LI0&¤ZT jaR-LI7jэZtݨE7jэZtݨE7jэZtݨE7j>V')|$7,%j(h(h(.)|B52F&ȤT#jdRLI52F&ȤjEfѫYjEfѫYjEfѫYjpj,Pn"n"n2KKGKGKGmQRFT::779sgJ4s 0Ce4ssssm؏v:s+ݠ;'p/-z.u(դKϥKϥKϥKϥKϥKϥNNNN6^0^eT9T9TݡT]/Uk|+FL {{^*J0S>L2{FL1~H.W-ze^9=^٢Wssϱ?ƪ̖}LTy~ڢΥ-ioRMjڲv|ڢ0-zlۢǶ&t.zma"Imo[Emo[ts9̥skvnb.\t"&E̥M1.¤Ex"t^/].K᥋Ex"t^/].K᥋ExӅ2T)|Ut&]p+`%, /݆n8ĹJO 'oDž: `J+Ip~Fe9Sa3lUR9wO [qѭV\t+.݊nE[qѭV\t+.݊nE[qѭV\t+.݊nE[qѭV\t+.݊nŤ[1VLnŤ[1VLnŤ[1VLnŤ[1VLnŤ[1VLnŤ[1VLnŤ[1VLnŤ[1V\t+&݊Ib|u%bf<)jO!( Оwkx\rFnTe/RYRKR\EkfߩlMe3xflSm*lufvyf~LQ寰bܤRoMnUYlVP+U * ** ܧ/t/SY* XZָ;E̼xi?tLōKصIͤ^M5MķA \շj~TܨרѲO)¤jvAmYRf uHjF1E:D|5R9e[K"],C2Qz0 RD,QE_xS`6o;0»0_^a-<Acp -\ =P>5 o, o,PǴG>Vn*Y C*U }$jm͕h_db<!^<#]̓!.ԭ"jjXj3ԝ6rآ/ٜʰ\p2 xdA6@.<8 ~ȇcPPPǡR1 TA| #Xf6;`'ݐ{`/>p2 xdCݗGU]޼%ɼ$BldI2RH")))"FDHBDdd7D!Db{ 9sy3AC>BF8Pqz]n{VNwhfX c%JW_m 6W`]!9sϩSe&|UW_e8ϸ صNH3*ᙇ K; ܿܿܿܿܿܿ{x6m֕ϕ෕ಕ|zI{!5)' 3ԃԃken{^C+3Wfl?n`\6oD؄Pg77!y]GE7ncq~!ª-'dy;!])e8*i=V ,F/_K{Y+P$k֝XwbӬnKDMP>cyq:}1v0AxpwrY>J;{vk`VSX Y2J~rQJ!ߝ(J__? m4.jE.FZj\t>%ZAE~8w_ b6a7KA|M Fo1Yf pYghqg2{4OmS[^jhxYaЇCˁ>zJ̼ v=e -B0 G GDODSG"X!M4tv18w_RϰdO)(-|`(79Q? ሏ@6oG| gaԡ&p!#RzZpCqaH4D"MҀUHɯ5ǚЎ@0SZF=؄6 >0sun==se=5,xƂy|ssa Ǩ̇qa( F]9aTvQ5#3#3r^H@/$[ I~=18S҅Ctx"]mF8|H ._ְ_ȅ،u w· ~'|| ߾}9|rF×e̢\+k%rDɵr=ڧ%?^k?"\hX, g?a|b|",ۭ[ƫ"Hc74@qԓ7r`p]6 ޖމ5F e<!ћm1sץӷȻ8j5VxCCܵ:j?E8b|^k2z,ZlUd3.]W'552/j?gG\ !yZ!0n)ƌN#xM=]{]Fk0Oۍц0msk)?`sиIZFx-p,wl77d\5v"eX/lczW#=o}8a_3 y?t=EAͽb} O'ݵ^Er3s`aKtS̞6[qzS\RHcM[?gQn3蚝yy|yWe)K@__b~36;ᶊ/O8Qi'~}G[+Mr=;l̸kA+1lf 8-R&yi.`u>uߔ{*V?֑Lsynv~-'_g! b`o_W1S3AпN|z͎cz7ȓ1w1+4l5ֺOZ߂z*`t}{o^#E# i ;g"-wN6)v'k/o{lT6_*8X|^<(dO'XxYϲ /l(~&Wūl Tw//RfJ}R?T^41l$ʻʻePN)lz=3ه3AA;1}>gG?f3ic&;1]ZgS+YV)x?f 3Vb 5L LV_ Iy wkZk#Ղ-T hZ+ͮ x-QxLK҄aOoWׄ'(m[RIa IIu>J ϙ{ 6u6u>((Tu>(((((|QQQh(|Q}GT}}Hs>@#F#b'yUb<}ξ`Q /̾dѬ#PH3H3F/>qiqqy5b k-~,cmq,xxmO5k!4^[h'qg"kB$0i8C1 ?1Cc:cL5SUkM0t" 3]252?f_bG菤J?F(P,P[n̤ރ c>MO'43UbIɃŋfY1g|1gHW >>ʢEhY$`Dߩ@1ЌU2Q}JZVF1|0&"ן?C_V _0t{Z:>@lZ 5K4՗EKO'yU} _.F9oo eZ[ȻR]~xG}=V݈vnR7O6Ѫ.veV#QJ>BijPϪ szYFcRmR/3zE_͆jH(M dMfh TMe5KËҼ5oí k5šaM&@X |}ܡ`")m Mafؔ ޛ?,Le09ts9s+= k+Zz< z" ғ$&,XOSEO3 gHg!M4yzdBi u'E-K,Wra;.,V+k߭wcm{{~/j?MoJuC-~GH3@%Bo7p'F!N&#D d%5:bX`+̗,dLl[fRo7ރl > پ0vBv0L/\0 "SaYvd,KIdTص@n$h2Yjo74ܢIj_#vRr[ֆlٲve L} 8Tap k*5/]{J} qk2Y4UN 'vM]mNJvM">>\36N%q//B-J8I]&ykkX_ב;]DN8UݢnCm w 2ndڐu"D֭-Y`nȺ-j rq׆l\[q<6N-Ȗhf w{p 6{h~idD>3,Kk=6OsY+#~dAZÂBv. Q ܩ;E+a)zA"@HihBJ6?d#ZF@SuF?u:LhvgE0u'2>]m|8GpNn/>A^1cRx2>=G04M~P70o;9y>=yz z8p=W/n`F_9 76ܺo_u߭I`|s=w{v뿱hŹ[\y?yތ־G9Uo{uKƮ_{:N'1=?rtğet8y/~@M_\oůb4.WMww~qmDbfAwߙzXѳ %c9Tf{Vs_AHih!hsS͠]c)ڱx_OyVq׿41C &׵!-971V |f?Az{q yn4|uG=-6}aw6è8q|EkLjcI{Io֮uԯր ׾!o'_]K|x3vxi?owz|o'RƟD0Iz2/C_.]=%^[ɞo0^7=5~a;q/ c=Z|RvMi no؍|-1wT{Q?b]d]ѧqc1Q [鱴=mr1 iHC3s_Ê^e޽\/L\9 8BҧzMu+}̽;C|̿s#++:n ozYe(ˇa\̱_4zOϑ\c ɵ E5[ޔ`M IVݞoϏojܿ^!Wk}046]m Ffq8>Rw?A'~s87Vg#G'?Ҋ o XYOwq^okyxrЙu;uzvW/Iʸ~KucotsW߾g/]/.udA 㹾Zw˟3?s߼m!U#>꛺S̰'*%J1OtDqYcr% ~q1*݋{-ZYw~~T;g+D}]ihmu=CZ~%|}kO]azgܳX*+?m}qhy+k]>m-36{nA20.1 >cumo)@EuvNnqs5߷{>!c;j iKOʛJ6F[0{=H2ȘoS+x e b)hcfm,U?ſ M]ƬhsL1W3kS\uI_]=ue?ksW\?K`ڱ1B) |;^,̹>ψw)Vc’_?ՄaSۓmF]]1_Zy7| A> RqxݘZgx ,s"Y~a |m߮ë+dެ'cTW2Z5TX ?kz_J?61Ҙ x )¨Wo!ߚGIH #* Y1^Nv/KOv?@VAڰBw?w w'XQQ0D O sRd/ Yl{IxUX넝l*7L1[tV%D{K, NR.GgSu"JZNCTT* wυn%]n^ VJ+%\XXa O*.%LU{gʯ«eSGy^y^ا+/+BeP!S+˅ epByGY-T*kOMf]S>T>}arD9.\RN*'iЬ\P. -UꢦSD5Muajcj'1\LtMSVZڊZ{"fjZٴh1G<-IKmX Ն H;DKqbgb6M&vԦkϊKK]"u\{K즭VjkbmAOۢm{j;b/mG|@ۧ{kbZ;&>}5>>'y9^:K^wC)>׃ozD5ksđc? >}l> b˧ƧU(xާlwJ͋h*)ü¼R32 kk%y}ȼüWlo/c>h>,u7WOJk͵R_iias3iIKIֽt_7z,=!ݢIOz4Nңz#Iߤo&[?;4MH??Uݷo/\G|!WW(-+(DXly6?X+$lbXdfaRB%$¢yvÇ(Üwnfn:[O/?ظPv_  G=+`Xk{h6aOYhQ1Xe+e;(BJJ9)'}tO!=$K]K6`2iK3=gZ7f馗L/6܎o F֜erkμ5ʁ,Wn#aܦ&P|13LhN˧*0MXk0 ??gX .1An@]MeJ"_amڀ `A ` Ky+"ER$&YVdCe)fJ,OcVVLS棴V҆IJ*mQr4XuVP S‘תD!Mf]AɱJ,&( D%%$)IH$#}Jkd*g)Y̤d+̬((?OC^@iJ>([⌰¡b );(PBҙʝHMTAWa+e0j2< C9+Oe{VƢƧq,OyZZ+T&@OʟډJ/$W(a2%LQ+GT#,, ,IbXk_~2Uc++,SG/T_Sza%F.@HJX1Rb==e4+Y2_ߧ~raD9jQQ(Q)Xr\9Xr%Q "R^R.!U`mUE_Y"r KRC0VF1,EUX_ U+s+Ŝ|ˀ˅yU»^|E28tۓJw'q>)1»'qpzrxH<N1?O;[Ċ'S|"~[Xn?m?/@Ozūb3dw"BO}GH$B~$Y@OGVG3g*1tb]z;($ޙH3Y!L2X-@ΕrY'9 sV"&J.Ʌ9+M$>zG!>ڝh)8 f 34:kG3T63qKtKoEϥw!VZNIϨw$nKLT!5T} ry3h8lC?1 bmsR qJ┥)ER!6lKBLџb[b A KsY?Rs*N,RR =BX]9:bukC.X][bom <<[s(s9sޖAͩ,Pbl"06򺂫F-_Y,c.мdwoW(kLezݠ`">M|._@فv*ne7U" v+J5J I /^>1Bbx%2OO.CiT@^61\UTEv‡V` g{`{VȜsj*8g{%WD I ^bxŪKuvjZ ۫Whz d.xO!O`\h^~x/^\{ʻ {%GfѮi׌ִkGD>]> b9>S}c> }ֲn<ιoGA5 0/@8yqC2.ʼ DיA (qcwA?yysg\#442bO̟Ϙ9O'|?||Y2bi͗͗bf4KE]bI7A2ytV ANo+H#u+QzJCu;K!?!Uc'!cO;$z:4|O=Sφw6&I!44^Wa38照v)L]"w0!DXSvA8P9yy^yB BB!!!P z!~P!L_f"̾Űyx_}naU;l}=l^Gja~];IG8Ig S1W1W­''<Ԫ}UU׷~poÄG7[,-` rYXK%Y-],-,}-_YFyxKee82߲ȲԲƲ񺼔m]jqi`"bCBCÀx`jh6 BKB;v /t`ᡣBdžN:ppnekC\5'CBχ66aZ/00,$,L K s:[XOһc64lDqa rlya ÖU [aGa aMp5  [cÓ3E^Zx{x/`_B>8|X1S+_57o ?x<4WwEhGm$" FdGDDt#wD!Á"FL<)bjČYsG,X \k#6G8Qq:E6F4GZ/a a0"LLF:e"{FOdAC#GD91rrș"ryIdǮ\5,"rG^ȭG=XYy!)ye7 jZVR"(ZMkwk/k_`0H ׏VXX[+s󭋬K+k۬V[OWuceDC\QaQԨ쨂NQ]=zG#5$jxԨQfGYQsD-Zz޺?j5Z}v=QjNFE'l.7Ĩ6 #lv`-kslm,>A4hfm"p2!LʹͶͳ--!.s*zV^a jm &[K):cu9(F3"`yt`^&7z@a#D5]=> zNEKWDH+z?:xE+v=lT{'`W{`o{?@ 5>>>>>>>>׾ؾ p-fv c?hr#h111!1\B$crΘ2`n@gL1bƌ3.f"pr4̘y1 KbP*1[cv9s46)7s4;kZcc>plrlf.c{;8,v$pLc+bN;.]C8 7n?{<ٞ1!"+q,N3s9. .8.,Jgr++v /n pHpਸq&M?nVܘ%q B^77nYʘqk6B'`\Mɸx-7>0>$>"r r7r'Ύ\qUo7pQ`-a=&`K);?!(`c"`yB N0&a<$  S$T&IHX"a{Om U%T'NJ(tBCE*% kM"KThK6151; 7$kbc'7q`D~ՂGx?$M8IT{Lg$M\`+Ne+'Mܜ=BăēnXǭ\fɃIN$iII$$E$ٓW$'䤲Iݒz&I4(ih҈I&&MNf43ieJҼIK iV%Oښ#ioᤣIIIZMޡ-̈IɎE]†&wO0oXU ÒG&I\<7'W&I(Ek7&oKůBk+{ruqŤ+),EI1RSRSS RJR:tM;_!Ӧ OK˹SRR&LdycʌY) 06R,KY \9e{ʞ)5)'SR·)͡]ST-753ԐԈStQSsSΡcSIV:(u(pDpGԉɩwL 0>uIj_>ukԽWxN=ZQSS/Ԗi444K0bӒ2iEi|^uI ,\=/ဴi V%9һYkz?C҇jGOJ ܬ5}.|֧/#\EHӷI? ^^~2.bFF`s1Y[dfdDd3B2ۛ33rnpB.-fI3d142cDq3&gL˘1;c^Œ%Un2glؑ7>=?pьZn 33.d4ed23c32-X7Ľ9Lfۙ|=Wf:c92z>ZczdY9%)szl9(>O{FLgH3`'yEdi,gfΞ%W^"{Gnݟ007xѸٵ M?{&aK)۽XNXsbC{/AXDXש.sݘ=n9}s JuX\S||qcqsX[X_Qb/I,I/S5YRV}J,SҿdPR}В%KƕLN.V2dvɼ%KJ2KVJ֗l([rhImI}Ʌ&`sԻ?^Tj (Ɩ&f:JJKD^(^ګoJG)_ZWV^:t3tNEKrEҍZJ*_Z]z|Sb<Զ<ŔJ'~EL)vҵ-?X,Yt^#f$PכJ}yBNRһKȦ~H.# =sF= H.'t>3CNMg\\.Pd^|#qyqM:Cmk n#SqD*cS:/4 U?;fzǔK=##dGc0G,4()4<*T$W\Ar \Vy^=-Ő~L8JTc8IsSGOyZrZO<^^8`%3rM.MuQk >N /$ Hu]zjwyJ>ǼiG HmE%YNxSS CcU0Db,]ByQ9\/9=MrIhHSEB\Ar $7Pie8[d4ʻTc+Ҙi fDlVPJ KɼP49ҋ_P')4/9 Sz:;YӘ%7]g(3ԆSɗH"yHSEB\Ar Tc~O4/@BvJ^&٭5 !gR:^/b{@*-J 19f)S.g]iQʵRZh/3Wbzr?bCJ3.syH^Mjk\V;S\^eڄqQE,% Yi/49jZjU*$!)?&*?J,RQ8PJ_;Lwnsq t[sБp,C5)j@닕[0^) fYkar (o=@yRliy,kkW55~:y6_;q"G8/^WVqm6Rt-oS:^gj?P )2nW~5:DV "+JOḥk'QAA>S~^$:ǶnkOx]QxzSӔ>4彏 }$&y5Rz+ɡ$R@?84rV@Mc/n8`pk)+ѭI;R$PtIH=@=ӑfDLG !^zj7`ٽx.+e'n W)pWGzJ4:.]Ow 3gS唲?\N⳦pQ8K)۹J͋ya$#a&yɛHy4o-y䗸$v6'sjCԆB)exT g !zLeP*[yT~ ~::D&?M2oC ]67r[4 I9)ӪkLgHmͼ4+l^{IȢu-R]#y)YœTK^$_Gg2yt^nȉUm~^#|£q-%}. iX^c9e 2DJQkeJ t|55cxF$4NcRѝ*a/-P95M4 /$#y0ɛH״+"α9| %/w4ل}>^(%OGjI/J'Pʷ ;T~F6oK84mM'J (GviKA414% U ir2iRI4-tMӜN =乺R˴4fw!!WQoS(mJ櫉5Q3k?Mv7&ҔRH)F)RH)CH(RD)"H"HR1""FD1)RJhDDdof/>sOΝ93sf̹w热8O]p}1_b,WfBގYl.J/ U7AjlBZ9@;1MM}IX.o7viovbiE;W} $,7勊eq}yl%yh㭳|a2ĊŻ ~ tgq}&pz&޼CN2aog; %Ә˩ߌ=}2UB~ :og_epr2|GNs tr8% m'fE|= s,P9po M=ľ58۶WV©ě/8CNW~<ފN=o~/}7KIpS|X?r佉(uerrKdF}B>ˡ*|WyUH*|0O3!dKan^ w?~~wV}s-修ao6Z;|r>9/Qw÷gA :ëhoƯ8b|;(RUj_9f}X/A?F} =nqI/g9wrrrސ@]טRf b Z!d1Yt&7/SNlX"{^<FK]F><_`U#' + Bp*.B>R.0r݋+#SS< (r2s!YV"u){~@o%Z9DʹiŻo@{WY%s czkfKu`D˸U{ֲ8‚ @+݃^Gl}ynlv]$(Kׇ8w~~vKzz@\p!-ҝTv߃> ׾ې_D"t30ww0KwFIOnWa1C(=MYL֡ ߜMo/|SA8gkm(L_MSoN~=d f 3A270f Nw$' o%Jס XX0 >?qZ ӧ+Wg-hwy?B"*>ëP&`dR<[m<!)_M]K>mc1Zm' UT!,d10Ø0- s ]!fȹ>d!FW%lN_>u! B)b i?TUQ2u|9rJ|y;3x⣌+_gZ'[0x=^/Qwc<>bXƎaTk_Nik]QuQ?]})yP>07(q ѐi^<{!g.y mP1n޾K= [1,UŻd| )/-<] 9kf9zh <mLEя<0I༛L`}ZKFxwR/ƾa^F?L#Lk q#ʏ"/Q C6KІy#A-lEvSws/cCav!Lsmͺ}Zgbgy)tSa=_#e )CFidŶ~`3@"KUyn5|ioI[LY[&03#.ޜ?rv2k8?bLm w1X@ft;O5fte|Me^cY(a5cFOaL։"a-=⟑^HBcAe,W!VaT`[qi_B>a~o\PEi<qaL g8ׁ3 o h< wa{ϹD>rMujXNC}ZXPĿ79߈Emu Ə |5 aV_̀SS^@.lh-ĸV!y>X? aB>F=1;{Q/yJ& 7xKV>%X'xZ/ƋKރx5k3m1K, ksf-8OG0QÔR㈚"YbX eDeb(*D._G qZ+2,cHmdH."9[i<|i\ķ7Y3c )0,ORor#<_̡0;Byay 1펟&V@*f~o4f23hGV38o !BWD5It]Dwm]q$bQ-VNBOux\ϱ97'Sh 6=SNW{30gHQ9c9rf~(ərVYll;sv79s 4p^:MpppppFuw ? OG7#H:&R\cK{Wod#Y 222&2>2L̈̎k #KHiʶ┭6.:6i"Mv$F5Y'ImT#+=OlK"{ɞPߏP6 R/ȱ<:2!79豀ccnAܡ#Tw{yǼ܉4/zS<^a<189;lء r3`Mr2$H@rlL+댑qRC,y,䱏ܹǹ R6 wAbܗ-G܍ssG;>-#Os=Ͷ(_$A.Rb9ldtb6[/Cͼp^<(e*>&8cN^^y} ]2 72o } x?1-3Z'v8 gugy}*C3NJIf9,pz:ț7;o^¼% p}O<Q Kg3簽|?cIF,_"sy7 ~-eoLBOL`K~]KB}.5S^\)+".IAߢ!s~5[ߙ;_AEpGs?ܞRvp IJcyZZ1XXgn>c}b1A henFF>.61OĦĦf؂boeتXYl}l#=I;UƶǪY6ۏ؞lp2|phDtQ- Z)(.XХ; *[0`0a#XX0`|3 f ,,XR J Vx7B?i8<@xy'rއBzznSaW-OL> CY8 }k6n{w}O] }p?vo.Y>}ȇj= 9F) ^~ 8 (¿/C@G.] z= <C? umk@_hڤY ~-r9\W'5dcVOGCϠJZub'b'BnDȍ0O* R)#qTv" =Zqe1F);92/C4fBcv!iHe?99C&تw h彟"e5!Jȹyxyxtt =B! ;v [&,3Qw w@f"alt-H.C2Lj`V}$ŒD1*y0*9N3տ p ̈́46xR'~ Q##ڏcq&_D]6҅8GKz9c/Fhțu \"Ls.蹠QFe4^MVAUhiWm\^lӆ> ?A\Ԉ5 ҲTZ&l }.pB*l;] e}$lCm@# _HmF[;9s ghS@5kL XЀ|*}o¢ʢ>q)4~m#L>hVՃ0Њ,"{2 1:-X{ Xia41~,Bl1m¦i&f"o&CyML 6Ҳ5rƃ}K@ FcE!#8pSt7o ^sضa50`q9̷̈#?cV3MsigŒ G 8 s^xUIS Ֆeb?0˦̵ه4Ӂ,eVfH\ xdzl+1/6]il0"\CB9cO/0b-8%-&㌋S˦iiϒk);7zd9$SqE/^2$7Eyig,3F"nd3dGEvKRz timЃ;'@W#d^Нi:owOJyO'*OUk0ِxTOcLvɯ~>L'QVۺ-uW P}k"q_SӽK\xW2F@Uji==N Ng4/싥"_K}ip:&uuu]|}}|`\|# O]7$5뛡پyt-$Xȵ[+M^kuZd\r&ӖU;4v$/᫩w#8+G oU#W[{:ﭯ~!j(§^ kZ?-w1RG n$g J[__t%Zꨣ3SI16;;']<7CPk_d2%hт)JH]F_iFn=)df2,(Bm;.couhWSn39cbo2f,X }y2J2Vq(XzkHRo>Aؓ?pьO 5xi EN6޾*@opF@@@wj5VRjs/]|@GOptQa^`00H!lߖB_Y4Ҍj@gnžYdu7kێ֒58kX9Yc|mS_"5)J[d;NgM͚5;k^<=|f-S[o[O9 k>FS\DߡD([r4$Fi9KfH|b-A(YBNv\v4r5X/!X1Yi ҙBn@,􃄥~ l}BB~pr. S4$Ax4q^`Lz>f>0e!L!sWGVn6\-nSU <^^H}m`U_%Kou7:ߚo9T!]6|.#:އٟv0<7f08 ΖևLn:r : C>\)p}OG6+Lm4mK/ajVbeTcT;2Mި +Wm5TGv+GTѴG*|B0'B0UcOP}JO\M##d"d"/F- P~"P^ "Fvca|!2F+B] JO*|!-~xe9B,jۑR!VH#cd~E˨RnpsqUpky_W:̲Ԭ`bZVΔܜ=Hj3 t vFГAAC F&G0` tYs ,&XFPB`=F J{t4~i&5^ A f{K6ԽzyRe L0`$xIS ,$XB`5ZrM[vi&&1qj:=sg8QB-A{N].ﬓӏ` =3s~tO*Š"e~-Nu&9E% 4z?FsNv ӵ"' %% C%v%A9s.";Bv.Esrv2'BuQҡͱ.IojiY/|i_ *d;tw~\:5Π{g dhm2jXLg" ẻ+!*p%p  \g,#+9d(J` .2ס;>l}Mx,GR<>mg@x'ߞO C'/^;|>a@ϫU{t.Ӕ(ϩx*>hsyGY[wg$2¿t Oou#{!EHƿt᚝a.C3eQ n{#̝Aq^s[Lޘ _ *?|X|^Z驠9[?9I/ӒD"oנtpxZ&\9RĽҹ i=FȹA_g̛ Rԕ4R2'Bv_+A'\Wz.Bb28 o^hGB9z5ៃ aZlћT¿Էv`o~n^@3Qu|pxpb7 . xgpaEw . 1,xopE낏7 n >|.Ȳ?E^ l<f%yu@-ϗ<Y?g3jwX|ǩtqE\pzOv56טךי;E}n]>얹kr w}>nqsϻ/U_mKw[}w_woGݷc;q_ =[&B"$CF Y!;䄼-MT'Չ/MuI%c0̫i w&^J$,{jY,s\bzbH==;=y290Q(7aL[T jDU<a2G5<g=p98_ILo~ O7? Vw N 8 _Gos@~.IOisH`/JHj2x40QQׯ"ujuH >1jE8ʼnE'֓'CDXII~ QuOo~x+~x7> 6IS Ƥ c*߸ijg)-D uy 70_DD8'ȑ2D⾞x]DEQ)kw,ىFe$VUJWY St1M6݉ݩN+?4lYLug/$^Mǽ~\^ĮĮ:ax~7.'_u.yG|:hdHT$*RqzX9>$^߹.?#^q*> a/Na-\MWzqEwѽw]ErRV=^),^U Tm_Km¿:ha?{>|}eL E?!2 Z!!urW}}P-arw7((ѻn?ŷ=}/4_C;;HA$Z$CR>薸%¤IJ)ݕ>@9Ar(khwzZ\P'\'(2mFS f>VggIqDȭt+IP*C%rDxI2v'yK ɩv)ſrwWWnNG%*/7Hk^h-6q' =)}4֭8J aѻ%\~!ڠ t퇮3t]IӵWQk}rAh q8q4q4]h{ 4*Ϻt:=еw~4zL77,G#$u큮е}}I3w]{ zOH՞|QIYH8&VޥwPYvRy9c/" EB]}1X #Ř~[7>/h3.ghiHMWk GWks֠19ٮb#tosI%׽Wd׵}6Ypj/+^x]M]>LQjM"CYwȧ* OU[?[/{B&!Mؔ # oYo39|cXa4 fci6#q)pnig63[vfi1Sj0GR'S,s\l.3KUfhVvc7Gi˰V [1jiVseXaHk5ޚdMfXy~kZN*V+VBn.k/=9`XvOsҊۖ];jVv[dw{ؽYhsQX{=fOgs"{^_}=gnי Iڛ4{[RN{>hO'p'd]ONia6Kޓ;is:8nNϔϢo M]ߓ}3$K֋ᜯOJV;I4v#/)ƾkn|]g kiøtj[ߊߚ·=b'>Gh$VBDᴒuuIIoZe|pJ>5t=QK}fa)4Utս_cUǪMZ;tRXǑA3AO@K8r: Y mߗqj'RS>: ԡO}b5uq6:n|Ǎ)ǍbCaOGӱd ɂO'OߺCoKwvwǸcݟ?wor'trg?QnU^Mm޶t(eH55%zt:?KnMqi4Zr{zuSIzkxNSѹɚl{6ŠO$ ֥)%袭GBrn?]kt ius9͡a1 |6Q)!K bq9xiO5g@pǟH0C6\{?ݑغYO/ %'6tJ% #>Z` NO5_ Y[fJPJwi9B/%%. JCt4( DHgR5uW&GrNR50Қi=i30fbu1҆0F1ba-H{"6M\ʿ YnFxYd\eM&[f5ǚo- jZgm LVkMu:n ۱vg7#hATkLЍv  Gأq)LgEb{A Q˶B]io'&j>lOXӎasN 2S$hCTq]NG{Ӆ p;F5Lr:qDpf;KaY%(w69[*g`/Qs x,JO|ٞez{ZDt{tyyzY͵gggX&P=f:'PR*==[ qHNn>,Y99Npsm Ch^ójZa>KZkNY|NVW -r6ڠ7`AT5)3A卷A_m/&n-*<éXgR_:NQVay%Bք _-K1;&f\?Լ5=|`b&y{ҫC%~I78B>&REsqވHWI +&NLq ˾?Qo"qy,By!8qg@[:œ!#yg6 .đ75O\\8F#bׂ,u8罈Us˲ZV'!`j$uQ3CcFf_oo)fжZ&R_K5o5(:Dzn[p~77 3CL5CAMYc`RC𻹱!s3$as0Ddmu10B 71EiA59Q&5 ޖ_ h8KS&Grȧ2UFY 5:zlG$a7J\͕R3^bNb8JXakHmƖ1fGk10T-D͵@1ZFo21Id!΄AԜ@F<`Ε = VB^wleF/PL7rc; N)(%hNI>4" \S/u&~'JO+;Y F5Mnz﷝yif΄hz7~ۍM3wM3jyƯԩ_zN>S{rߴTnaEOβLZ>IuXrN7IR3 vrmܥf ]]SʭEd=rNVyU-ay&G-a^T :/j8d=>/9drkH]spSMGI:_?~jXQmsm5MߕMQF^T\]uܐ~Wss z!1{ q i:gT+̬SHJ~Д%%[yiuuVuFSs]}P:,V<Ӹf_S?w-e[G -)}n3[JVKylRKK綬mF]ZN@5bzp/`bXr56ZQS(f<*3lG9=ETSes3>sh-ĆX)ӟ;{-6~jpڙVBKݕꉟrںm?3-~gj}b؃gg8 6}&j\.JO={THżcbDRgyĖ6 )|/7by^;gJ^oLy_Dkp3e }ƬϘ586PJ&9ӘfmN^i}LϪ;>%3 0>PuH1O8RJYi~XϢbβ`X {B&GP}r-7*y}_Sr#wFʩxTH3%}ylҒޖ<lP]qy_~#Wsx'6$w.Ofnlp WsNΜ㽂+{>$r'')r%r\&K*Y&BVZayT A#lČ"(6:]F/1l 3Fcc1ɘj0fׁ"cXb,7JZDaUc8`nj_2|qӒMQΣYh67[m/|뼛= 5"3!ps|c9s2睁i4s&Ŝcz)vIhځh$Pju xLv{j )&V_3v$v1qƔ7RW'ea%;Qݕ0N@:MSBd@ڡG)@08TJ3Cn0-Xn)1j,SALuv]I},dH b8ŽM%xp^DYJݥvϒ<:#v92oy;'I^Ac! }Tga%'AJda?0;عGeCj Nj unˢBB^$;;׽ý^fpYoT(uz&)4P4~W2$k=脻w](:{ 'iJѢp83eI)xM:ק+(o6&֮3l:W,uK,2}_!^q{dEu]Pfs[؍)}ZNҙ_mؐh5G%{-n!u;6]Z|řϣRC;CDYthO/c>`Hh#QⰖxm xq jw'#SjG^n,0ĩ_a3^ᒻ|XGXo[>A{뮟YC߼G(&P >/a>FWټ}s5n8Q'˄:c 2؛-nacKцNѝ=݆iba])՞\EI~)s>V~s?V;>V{}ycXbX#v!v!nJ" ^@}-(~0Xq.;e'k-:G=V5T.W$_:L3l1bjnٔoj/ vugbζ5v gbOJ5rOcZĪ |W Y K[Pc|YO[ON8}Ң_ DG׃(z"c(xer Cn ]^%i1Zà ٟ ˜Cm'X g8oR{SZDGBhQr =ECO [B[ieR)g/ _Dr"Eϗ)WD幚8BKyݡtOh/"D9HO'񏄎XW_JIWWS99aBUsc^N%<: <2[ 0 r `fal9`+b͉*VYUbk*֏ |)3@ZEtmHO]>Z啨Hk}b;O,̪D4u'^y##aM9񧏔L%? "LriT[ F582F|1M_d\Q& ^ժKkɺj#M&ߥ)rwP֯'dew}:ֿo/DgQnҡN:g~H:`^}ouܿҮuJP/GuIH~_oMCjt)sD#;]:YG]nuK_FS5|/ko?8dܼu_ZM\FuYխD5ؤ떿d[A&-)sҴo#KҤ?&(Έ ߬ʷ?W+UsғU\uDi?XXɩH+mEuU pW?K>=YXSmT[}!?BuH!1Ri^EX+" VC c->RÆ5Ħf56j[c7d=?;j{! GGN##?3Qg/Bx E]z\::!cyb8HjɓZ*!9->?o7owǐ^^@z=`${oyKHI.A@'w64=׃Ap 49\ q}UJ7 8 P||>2 b]=G` DhaclL DP-A]@Vؚ @DC5]pM՚0Fz=G^űׇ⡤px4B;N餿9ѷ9~7; QCl|ee=y"VUa1곦%+ٟve{0v&cȑdv<].eW_{=Şg%c9ENt8CΌ9Εs:r\כ ⢹x.KҹtB9Tn6-sk|n+WRǕpǹ3ERvUpOG:J=n+o|K-)w]!}m/?L+G4Y9mH ~:?W&Zagsi k PWͲS]Y@JKdi)_ƗZL}^N%1YᜅF~4^; Nc-]RV 1xӐRD*^<}Iv"v#C¿Xj?D#CTo 䩥RrŜZ@sjDr`["}ijb!rjD|> y7 6^ S{&KQq"s2h0=M$:ԎUEܦnBTb8[9`h`W?Jg]!?+?[o໶Oh* C ̢앵x]X`*dW%iGTk&z߾ H+,ApGY2wjɋT'hR J,:pj:{sӁce;VϢ]lX׳Y(]I7`NB ]Mż!AXQc#lgsPժ kxG6KQ3ǂRO4,f@]DJzzı\ڒ[hm,]}5 wJ8,ecH&v,Sa "*?83&B&_8̌E-fnL$l,x-9[|" Էq sԡ OԳ#;ׅR}W& fٓMspɴ滟`ΡOWY]I.HX e#'E}N e\z@zVL9^%P9Ȋk>,̏WKg3\M51 8g2_n |_'JF Z%͑g>'E),HzrV#`y%+ygҜY ֵJt袟 @P BQ<1^_榨rJS2禹i݄4~ƭX u)&F5"ԯ-#Gl@PQTW8`MVswC#UMי?P2?0jaɝ7'(i`0}.cZ>!>ӥhqz>vNyB]a4J!-2}jNjƟ4|_S%;W y"w<>i{qWNA$V!.`m\l\ tq;9|";եy-(bb$p!8&(&OLlgD`p-M J- TI\"^P+0Rg̥ 2}I]Uγ{x$2$G+nk^.{<ԯ$e!ͤIb@=Ο{޼]wa ;za8eɌt7v>Mmӓ%3LWNj{+Lo;7z\_ hN- )r<٬e*qݽL&},P;Kxʈ4t7/'dP c:(k rٯ ĩRf<4%F֫+5r3 &O݈PvE']pЂw{ :N|_7%ٖ"m_y]L7gU'}6 *:O{n7f.{#wVLRhJ~2QӉ>׵~?nރ~N{q^YMeoV~w;w PYC**Ađ8SKQŷ^'KMF,ABj6ͩ3i) 2ϺTuy?Z<ᙦA+c >zܧv,Mc߽|+qmW 7bMT-p]T F+{a :oI:S.yZ:AI>Omko؃NV}~XveC}KZe4$OO4p☹MlZ.ܬdӅ5Vm:sia#aO9nгQZzW49vW흴»rvmMjAKE}WVwaM.LsoR:O~5F\?ڟ.Zl4e̩E)/f95"z lhl2\W:wb-3piݢc fo5Y&"`u E2n4sڨk-ۘrON&;kr3O. lZK{4 w69ӭy xkosqkkqwM>MV~גM/؛:]f-Vrg~Ɵ뾰 ?*[ֹ̰'s 7y_'V^kg3]Y<33wHkSJ2vW3x~q^Ҹ,p4vkxlZnj]$G'%ιҽ֢[µY'%~05_a s_/.+oȧf sYՍd 6Uc-oMƄ #w;vP|ж+8!Q0XXU@!( C\\a"_1ޗ&bOWd_뚤c{||̆Cfo9T̽l]_;56J[fٳK!Vd ?sOnUf:IO`lMV٢0ōNL6y>zqӞ5c=-MV۱|ʂAo˵lc;Ġ^Dt[Ve;G7x{yeg[deV~8`'mZw CJmx{K]{T+/o+ iw‰ ' h CM]WVxt%Ԯ"e^)Ѷ9dGAB͎&4_]:p\X/* keƊ#,&> #dF. gb5)6&<.8vPgc3 GGּ5dFm%OfYui.23z庹i`˸Itl Ƅ5"wTnۣ˳aGiDkCI69IXB*ߨ9nLр-%;N{OWɨRաGl~1E=cw6Qoqf͛yK-Wq 2LgS&+6zp͹cmy^_vy3?X3I4%Tᩅ#'(innfwkr[rSy?)>̟+tz9KZz/YYkƽ^Z:I'wkT!rY>$\nm񙃌˄/] t snשϷ\{Q=7k4wQmJcjFoR}j6-뗐} @ endstream endobj 98 0 obj << /BaseFont /CIDFont+F8 /DescendantFonts [ << /BaseFont /CIDFont+F8 /CIDSystemInfo << /Ordering 91 0 R /Registry 92 0 R /Supplement 0 >> /CIDToGIDMap /Identity /FontDescriptor << /Ascent 952 /CapHeight 633 /Descent -268 /Flags 6 /FontBBox 93 0 R /FontFile2 95 0 R /FontName /CIDFont+F8 /ItalicAngle 0 /StemV 94 0 R /Type /FontDescriptor >> /Subtype /CIDFontType2 /Type /Font /W 96 0 R >> ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 97 0 R /Type /Font >> endobj 99 0 obj << /Filter /FlateDecode /Length 4681 >> stream xˎsQ& x-0@AN6{CXE%R$lڔE~bVĿ1Q~F%1/R?o?.iDǯC|/N N/҆`ߟLyH}gE^+z ռQaPmF"7Y~>ct Eɷ\ r=ֻ~m\Ϣ}l25vzz#^>i}Xǫ3P+=ԈUkD @:}8?clY|o>mC~'?5umL)ך6\<<GlՋ aӡv١ƂtCρܰ ,0m.;) ~JgIQ~”~ #b'T#Hz ]kDG"E} PSX9 %HuX‘k4IE4ja}qK.z.CΔy^N w71v ޵Yq29 5נ]Adk*4ІՇmBgF2]!KLXMdIv֑x/Yo9/ˉp!h''rb>gN|_en=#5P3*TSw_"T Q=I58>gܙqˆާKú:.jÄGT|`QC R}##{Q( l厇 xP" q(J мG Rl4?#YU%MHC 'L|[mwtXЄhظc[vأo[`0r`"Nv2(G9@ uD_#SqHZ&*q t'Ex* W؞=ۋ9\m|X8֫_9LTa<*FCn &gz>\мi} HΫ͔ypPl%L%rv@"(nUG,nDd;ot| "`J  ,wwHx4N7FqBbmK\ JVT(բPd[-#tAL[.!IgTr~Q 0!O6Ed~CW>Y~-S4X/ɑLgiFtZpHU]3L'c]o[iM|)B.eWvt*52nbR|UO,ar ,z)*@m) )j},LyOòdl\+tGWNCJe^陧ً=)[%V_ѣ Λ+ݽᶟ:XqF"ie\H7@6=q-!%T׺Q{NRr̴GhI?NK+Rܶ!f UۏRzգcV-!pܲhTu>i40ץ7NUeDe)q$eh|m:?: }ĺUj _g\z/ϊيZuKkZc>Rm(^h4D_i[gX8 ~ft˖go lL1-cTTSg$*SY: h0Js{I K'+i#*Uec^bD8W(?Sn2D4D[6Kfb 1^D A9Ɲr.ϡpK"2N;qt7'’n?P-$Ռ%ۛ p#:"cme,-_ę,c;,w֝"hf,|Yv 4nK s2Yf2={i6z0Uy.1[@LD;81s&Q! D;x{P%!;J81s*i+A1iٹ__tD sl_TtA ^_wHd*'%E6|GD);gkx-Ǧj훺v[յKߑ'? Φy콟yk6Fy]w;-M`ME!5>T,c7")~UF$k%}b0'v3tcΤum7Rx׆gVrvUk{g[Oi zo֢ D endstream endobj 100 0 obj << /Font << /F1 27 0 R /F2 45 0 R /F3 90 0 R /F4 98 0 R /F5 62 0 R >> >> endobj 82 0 obj << /Contents [ 99 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 100 0 R /Rotate 0 /Type /Page >> endobj 102 0 obj (Identity) endobj 103 0 obj (Adobe) endobj 106 0 obj << /Filter /FlateDecode /Length 11712 /Length1 40416 /Type /Stream >> stream x} `sg7$ $h) LjXr TMyƺ d,dtűP}y%矋Xϵ"Vz=b'p\:;7!Z[6bW4Ʈ~젙U6wTgۧ cCao_a,l.Q`o_-j- myב 7/>#r]ЋcooARbO`NzfT0B֒DO%˜1K:\&J6f5{ݟa]abyK<=d(_S> >r)2:(ĿM6*{o[־o[־o[־o[־o[ڄ*ة8t3pfc.]nbE/G54MõT]ebcY#/іoL˝4qqco3Q#23F 5l\?A?MuU+'DGEFCm!V.4R>^=1Yngӝ7őW7%әUs>TzOgv6nm\,i24]M†.ߖ c2> gw{AwE%udff F:>97 Z{ h2֘-Bvf|923w2̠&6\#|!Q&Cg:Vl].M+^,rO 7lkEfmO|1}|ažgF:zB.#=0籣%n%>$FrKֵYNGVmA{f]^[ YN\00]P竡gMb-y>G sv=&Z3,w.axpMÁf|q`0W>>@lhh+[jZNy>Ǩ"g&!'2C~@T>KO+FFAFu, 7O3@]HIy>W[X`.ed9|ٕaeM#ec/+GfmA2K]=БB:3rȲyE%"w%>W>f8ߙW/ޛ@ɑO2)oDS@Nč3/pzyZȇb4,0C@}!=l(2qq֠0|JO7rj4"Uψݍof6 jvC)4ؐ#I$Is;+'OMC(+0s5WL;$> fp}#8pݤyTC=V:w* 1®A1 ډSN.z)XچvWS^‚Iyk4(:my/;p &R(@z'jI@Dž8#AY:͐E\(\-m2!!melPW%R pVjաC.#s`F %JH&]dc05}9@>x/9\ʇI0Cn(a^qu)ߔP}LT>c_rPU.˒ P_`F;s DIV 8o^Iz%}uuɬ>Iub܉N,> K%؟guJڷ7+iޏ+\{ӯڛt??{4OXÇs?f?~P/IQviD\K!IL{Wej]:ο_!-+a/{{_z_ {WyjoZ×~ϣ~mQ/ { Ʒħ||}ÞמwچmajOoX}6nZ+ҿRWI9╏?=_K;'E-MZzKK-V3^fd1߰xbmV.dO~ Hz%y0ʊ!IY$.q!"׊6Yn4ir-GO傇!b(w ˉ~Wx5AY=zemᣲI<,Q։,&wJãrӢrqWOJu[ԽQzTTqQQ>G Dd|5KVOاu!~\Crn%u.)\ :ڗ61W5K25`Ć{>rü}x=V@aN{X*w}㓼-~[oaBjlbVi-[~泚r qDSy;0QO!qq'HBϱߓ} !b]9~Ʀ"q7}z)ߒ9䯿x/b߃Z4BJC ’\""f:DöӁ_w]LzLWt~EL-z7#X;wo:GdGY(_NWBXܥoQwtYŮ8oQu !Y5\'X|tDd|db٣G$ߟȫyQ"H#&Ӊ|{"=/#o"/HH!:|Aj^ȗRs'SwR&Sdb-/QX?48D~WM"תW"FDrhjVjll15aӇq=8=f!$Ix<=29tU>pqB_ jKUgij/yKlXUQ,wObǹ{G ؕlk@WeMvvX9h/:-IU<)d!qa}Xܰ>1u G$w^DwO9*c;;S5[x告e<[v^ƯT|KT|8K eGx|:Ps¦xκ͊XT /*^7XX8x+KcNoc,ɊeGo޶Q|dp65Gdm3ʦy9^j^uRWaoGZ#++;FTVWUVezi 8FU),(*vu)Vl9&+<*wWO.HQZj:RdQqjwQLwʒ8yU8rS':r ]Q0WRRVXLjʕR7cVuP mp e3+SsJ KsGQlzsL.vz=EWTTΆ)SZV1-u{Xg{ 03`: )R(D tͬMVWw{ZYy>JB =4|Q9AO34s* ݯ badD@IWR %etNNE9cQJY+Vh=7AK4<,VHތ3xL Y'g|WPE$ +UOƈi=0+%IzEo+6^k%ET$6H,5L1 97[heXfRRU9RχBu6*Bœ3u~VΙY3щX%] K!hA|UmgGadU0I1LG80oV`_%O?b՛!(si)gFim0 ++g/_lo0n{&S࿰}4i;w2 |g{&x3]n*vrΫ.gӫogiӪP\m+0h4:'NQN75z'"n/`DգFʂSܕ4Ѹg c1g"*]=c/WNv]V;n'O{j /j:>h~Ϩc#=qqq3ꌣ..2% JMx8Jt$>5}7Q;^1j Ҳ^5誇:+S$~Cz~Nz hDjqsu_7UF=QgzRT=意aܺ@r9RkVVŧr}UKeVlRGM5ve[NZ[ՐRhK;D;"mѭD$n~FHQD;JʺZ?[4c,:Vo'F4h$W%/m3-DA{ D=N$ωMzx% EP:IBډ JcF^A4h<Კۈ ѽDaG'zDODGqKqJ" 'ہ; xc+Ị'N&|I|)[q>Hj$tɨ1ơF`DZF\(f BzՉ6w S(KJ@WX[PxIQHzDO)D$YV¦ 鵇 0 V X-6Kn ["-і8KK%W[-zN.n;wmg{߶׶vvlm'({=Ƹ3*@ǝ{ ϲ5l(^gen>㸿8<jwCpI| /%W~?K#q4_kz7C~O/yMlZkiZvM֦!GPT҂&Q=-{hUC  y /G|_}E^_VC(?򋚮ٵh]@mҲ1VHUalwi?hiKǴڳmWm[{_ۯ֎ks D&բV E+&)@rQ-{V,bxN,A8)uMz;z҇z>YOK ݫk2}_L$UDUn)I74"xմF6jOm_u\Y CԬ#M4%H&AHn6Qlkϛi#MsT Rԗib*4n7Q3n6PS&? o>4ǶBriSyB#7L㤶hmeεy,p0i(ϛIM&}^???#gQ~\?Կt=ޅųL[kP֡"H\vF}EbwɥYkǂeA%*[E>kߚ$ z@Hvk&?1̀> n£C'kl[[b`rc =oT1rِno?ߨoF\556Qu%G2P6zlmތ~cdccbw߷JkC"h9W.?m-?a-ͽVoO-3]h764o4}n;LnJ_ [lll7xZ6l^X؆6ꢖG&K }O?DODҕxqOk>H*oNyq$7O\8Lep+=Z f[(=+9*OÛemп7i1i S"pQߓ)G+ Ҷ篦ߥgxf/gHd' gIBQ}NAj蠾VGAAjd*g^GduxzJ}z=ԏL o17hrSvLZE#)汷:OYb&^Nξ8gY/vImsy8D[,VK%mEom ֍%e q(߃3߳ߛcߛߛgWhDQ~mhLk5+e?f/SV3V2߾L=e1_UÙ,<~m>yT=\ާ4*cWZvV-EyB&$EO1IKrqP\cT}%2X$Z,- 5w,C!24dRȔ!{mV^m}:$tZS²f-{8a{#]—~>14<⑈"q*RL|(rm;#?bQݢzEeEP1#/(#QƢLf %e z:dyn*Jd(/^֒ lZZ"NSFA]QwCIFKJ6YXHяYWXvi+CE&xM"?{cF = 2SWX9^soqLmjjA{w@{ft,b,aj V` }m&Xn&Xn&Xlšp+[Rre\;Qj0W&tȻaS-vahÝF,88l&P?'#=X a1/,ClY__/u@/u]Bu_> 3/EW!^X/X.Ӈ8ދr4b:hIfR/u2^(݈;xϗ(gu:}Eq9ezGĶ (cB9(1(' iQ_@7S NO{pyQԡ+fb75:6 aX.gSTqpp!H1Z$Ɗ ԝ+&E"n?%TbwjbS#jy?WkuxSlob&vyb-=H|X3qT'Bgė+qN\_o9r0FeǽPPMĿ/[Z wN<\T5/HmInxbgbU^TMcn˂ͼLۄߎF#QkKI~ޤ94.ݣc̿cR9_;gՓ= zf Ul#ϣ6uQ3L.iW$SgY<Y3i [01? fZ{k6=Ok~ge/)ٿ癞.EZ`?jQȿ8(rr(7P8FyC(/R@/L6 Y;%Uq)I) 2ɗ"j<{t NP^3j3L=ښQҠm) >ш_lmƤ54!pQIRἢIm?/\=K[|/Žqsy<->Vov-455Odka"RubXOh]EΠf%\sg4B!``t,+Dq 6Lk9+2;E;׋a9a؟!..u p p{pp > }IQl,gGX NDNH&(`@w(}#Y;J\z36Ķ}Cs].:vƲku\6g[LXl5%ɢSaі+,%ޛbtR(W,{,aXBYb|WjEqnz ΌXc\Fv )DUV!J!R!@!݁rŪK]ޥ;'zpR~GTۨka&덮?Dg endstream endobj 109 0 obj << /BaseFont /CIDFont+F9 /DescendantFonts [ << /BaseFont /CIDFont+F9 /CIDSystemInfo << /Ordering 102 0 R /Registry 103 0 R /Supplement 0 >> /CIDToGIDMap /Identity /FontDescriptor << /Ascent 905 /CapHeight 687 /Descent -211 /Flags 6 /FontBBox 104 0 R /FontFile2 106 0 R /FontName /CIDFont+F9 /ItalicAngle 0 /StemV 105 0 R /Type /FontDescriptor >> /Subtype /CIDFontType2 /Type /Font /W 107 0 R >> ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 108 0 R /Type /Font >> endobj 110 0 obj (Identity) endobj 111 0 obj (Adobe) endobj 114 0 obj << /Filter /FlateDecode /Length 63186 /Length1 249376 /Type /Stream >> stream x`TU?~+{Ld;Z P@XQTtaH+A]]WWEw]W˪{Ls_ϻs=J@+Fuw< UHV>ڛi@ tGφ_`CnjqԢmW/*r 0b1i/0^^`5uE&,sSW,s &qƌE3?/s [u3,]qL35θKϷ$mִ H/OOk&cH5٪m|g;}Ȋ<czIN_^SV-2Q n!{O_6Dps(?<_`_<p-\,|iwђv] T`ueZdc*K~$]阩yWpx$Z7\44u%X&m9Wa.o%;@x$W`gQV!pX5Q:.aPM}Q|5<}IKȓ~*M*oiXX ~ zY>Qzގw.-gcB>SQV]{+p]«ql+ J9)_kbk᭟FX 2d/w?E qR˜h;qd\P7@Uн$ R(d \p'P*1@Y|P$ECD2dȐ!C; Դ#<"ԴTmBX  M ,ߐdk p2~<𴂉Ú?TP*jP#k@-tz#lG9Xl+l 8.p"!9\ !>wHd$"{C) 9<~"9#B!9q&F,HCΆ ƹ;ir>d#@rE.<#AA+ɸz @!r/ 2."3%zr} BAЏq(|+/r%*b@!WC?Aȟ`< JaP\x8 D#aPSx4 <"aO`< p:F a|,DEއy, X|%,G+72+#Ͱ y 4"oe 7e?v*WZk`]Op-G:zyMȻw߅ ېo&[a;mpƷՑp\v ߉! #._np#}#op Cf #&^G.Cp7aƏ>6'{2~ ~8G~C5܏$<i8yÑ3Ќ,D~ZV#/!pEx46Sp%/QWȫ;xUƯIO! b6<<| 2~Gg8gEd%NEN_% ^F^UOkȟ?ב?gKx+cE8kx x;rw"/wp.'w廕N2c:=ty:[ӿE8i9L2tz~,tŨϢNLu+^܏1__0393?e:S?e:vۙNog:vAtLtLtGLtGLtGLtG#:}sNt< :} KN_/NtF71YN'N?t O0~L`:D7htY2/:3Y:]DtkLtLtL/WNWNIb:駘N?t)O]pqAVNp f%bA5mht2dȐ!CJ^@;R'S tÅJPJ5ލCP$pt4Jt)ŊvN|u4\x}Qnl2dȐ!Cƿ ٧Dfѐ il;5)E[w(45oeE}g|9FChj O#C 2do x!A4΅4@.>7mOËlMR>nԣ1 C 2d7٧Ai i9:M`v|vweM@QB63c0YuԧawT4jx) ԧESc|-}2dȐ!^}ȣo QF QNsch4Rw7_ dfֱHAK>"H/C 2dې} eH! pΧti>M.-iӑBOŦ+g1FC45ZkDыQɍM 2d=O%3haGK QN{< 4ݰL%Fz' kCG.X1)3hj M= ۽&C 2d\,AĠΊ>b tXJ6.Xuv `f[wA.-,ٝDQz' kC?Jb&teR3>Io\^ٍ_n!C 2.{A~!53cg̡PO `v]m}Aul kx~ނ=%&JObmG)Vždphj |Azbln 2dȐq Oy s!x1N`v64,RB{[\}l kdGO>i> qX1) N{45ZkDzblR 2dȐq {1hgDs\ɘ&tC+Xv~MBZ/G?o|QHiAwB)V.sxpM"Is1{M7~M 2dȸX`٧Avق:fPb}~Mg[̶sgt@&,/e|.Ӱ7_>[{ w\45|ĶpdF 2da Yiٝ>Ost;>7RQbo(\htb\ &A"$GScF\s8n2dȐ!bd& ;}=JlIvtث8j_pN63su|QGL6N+L~/)-5"} bl2dȐ!" Q`bF5xh6]c]z5R;M@҅:ܵ_B^~spRGji)hj |K:8c!C 2.C=,.p豀{4bIntnHv>jl7t[t~قFϡ, JHfBQI" !=%-%/IC-2dȐ!CE$^G5]I+3h( 0SCm?ɦKO̶K/xNfDCq0NzI'QK9E.z5Yh їtdȐ!C^^ݎII;3h(@9M牽j@?vA.-h*rgٛ/Z+smLRK9Ez5Yh/on!C 2.B$Cv x=$4|PTJ6Rb]AhZ]Cf j i Fj*QEW&^PEhMQ9ۇZdȐ!Cн)GsM7U 5SDS!jӥAf̶+l΄hvwDÐ^}M?}ŽxQ+d12(+@wHHqC-2dȐ!CE\^ݎ!128ܣRN $.b]1y@Ѕtqw-0zJ_>ErN /(Ŋ@y,X_y45ZkDzbl62dȐ!bA^ݎ yy豀o4)ӏfAԦˇ@̶+GX΃n.-xw-0in%d{agvG)VB5T@wH_y}dȐ!C &J{A~w;! hP bN5@Ԧ+^1ۮ?z5 ]&ABZ/a!} fVsm (Ŋ 9hhj |׈mB 2dȐ!Do^ iЫz,= 1SCm?ɦ+>1ǵ>#dw7_ !(]C\]G 4Jtg>CdȄJ{, (HOKM'Ąx+۬hJ#Sj654 i\O)]Tu~fo+=dKIɐT2YP{OVmdZ _]60XDVͪ6oesՊYM Z8bPfAKXsV@dzjv+*] z |ji#FVV|u9dTlfE`;Lb@;^l9tU .mMO2RGaV4;W;ŝ[n7UhSoޑ]s}p-ZTJ1.O>颦+iJo?iNVF5ZБ{6) m49h2KQՕ}>ZBp)F׏^4Bu\9˱9c97>zYg49ʚ=]2?dZoeSCTCƜ{uEC|< q<Ŗ80ꚅTS.A\"6ݭBXnnvw4m>W pu'32gl~DI3gd:D%/q.$'aFK߅!+|U`Ti<$ҁSQO#of<04(oPrj8I $$d$@fe\I!;6LZl~M!$ɗ(3匜Kr\.fr[܃!q̽ƽŽ}76%i|5?/WZ =A3 BQGxXxIXw7+@aRQPRܠhS#3kW*(W=-;v7 Wɯ4qܯnb`.+yE۸r WdYv>ra4 ¯^x{U\n`$Lsg^SzMmI_ ̈́p;ghm]v`_ i\5$hp;$steܝd5!q{ ٯ \ 8!dWJ.E>0n-_#lR#{ ~gٙ 'a(ϜV !v_:< 4f JАIAn%Dmdȣl3 ڈr,/Py"R!6AE!@pOvp xRL]c:3t`(61RZJgC^xx%( $䉠+:s6J,_5C|*8dcx)M`pmV^C85=agM׎?3E⒀8JYw'^aHRK~I|qƒ $3<+bG gkN^k &{;nH'9>XMOa4:'QO,fT(`i]9_Nn}c-y:M)x/^0 WIB (Vd9y`>6cؚpRM5jDV{VM퇐dEm4VtHQFzWwHG񄲀Z${ _hY]%}K 8zq(gF@_ u:<{}X0"/E:/R1ŝPK-Iu|, wC|CKpk&wzo6"6m!8(^<B#Ys`7=ڎ>`}-nv:;*O^z@a)B)W%L5ECΚy.?1cV( >n;[\\3$P>kow?YO~˘ol-ށGYǾ=I uW_=;` *7n?LB"zFTs*+A@F"!/x^Q9 :l:^Z2ȩSN>luț-jAhtEQFh4Ѷөa!V]7Ũqeav٠@뇝y}ti<>^gb1Io|p5PqEuq\emLm@bWB|@y+ ?SS+w&+Z~79iQ_]hoU9W:9O[VH.l)65}K]hfӝ*:Eag@*1iybv8Vj825gQ-6*AUPz}z3tj Lukmܦ֌Ʊ3מO4g;MgE{UZJ́^Ԕ(6#PX>gMnrZZϢk۸KCc̻n}7TcWcL!w[U7\vz]CjO o=}ܼilcM,!d/Ky(+JYzsЫ3֘+d>U* mm~mCZs&A9k}^޹'5p$6njol*ߴ3d~D['s=Jru*ӨdmCo?u󇿪)?o;lcoRqӜVGg{D;xcL+o7w6I紾4'=-nrB8O]B6*`Q^R>M'#@A=몧ݣއJ|%>xWSOtɟ4t?Z6ošjš5ypWSQ686l6w/!52Xq>r2wcʊܦ1^c.UUf6LQ%{`%! L&Oи\iAQtiL?[i&*UтъLvV֜kuTY^. \+t&qvBI+5&HBB+gH<{Y[oڶr lfS^W _%՘kl+H3t#_ʾ2ة6 [[9s.^Dj J^>4@ l^[ن~˩6C<|UdqR/*wjd2ZʐY[o!(8*7R40]HL( qK4A5Q_@8TGe;Ƭ܇kpˆNapx]syD?w?my=anR̗(KŦʁ* K?2/VU/̶.t7ZֹM,oXu~TЛhQVV/؟ &mB=$ <-6=NƆ#`b*!r,QJJ d66pٷTj[EF2Br",vYVk@*)n=$>S֨2Tq ٬jP=F3c!v UZqq;EcElmOePTdޡ}X/ԮӾsX&NW!r yE۠Ǚ8gՃlTE{ մeeӗW?EM;&Eh ]j1 (zn/ Vu dV>1z;KA+W['upWH9w P-ɆŬaa%Lw]ScY1 @,n|g~澶|w 5n{z?{>I["Bx%* Pq8r~eg7bW-L:Ƅ$*T@(RIib!Ӝ\=iPj.\^(ʡ+wUf 9ټߕsEWfzNnv2\ғhRSY*Б|,;}oԊ{pW}6ĆfleU%O>!2 o.!ZNp3n: 'RrP]eXg|ê=VG:,FcAkRO5yz#!·) TGNc`UJ\\`#u=Y^ES HCb7(8pc4(1/M)l8M;;]΍lXVqXCT p3?>#2'ZAz1>^𚚲Kţ}o߻'ļkސ|8449yGt ^mWo"|L[V* tmkYϧAnvnR%@8c E|-8IBKqQkZD:i dXlF#gXL8,QgW-֎3y]8* ghcRv?Mg7b'q!!T lW0:qkXGvę.\pMZn.oQ*-)bkjq*g5\~\uj%Qe L#irҏ{<<'hݞ:z?&<{} &;PpQᡴTD>C3J$.731+2ԁqvj-6{{OH+ pH2)BiMǨU{- % mɐGo [\j߃0&iR;nO %hJԓV[<`RmR]_Gؐ@g:IS;byGO xt"ҕV%#,QGYQA wb>f'WN|nj+&_$SN=caْK+jqO ?o}k~ÚS'cm/0;<[g4=#k\ܛyj<$c2dƀJ cdj:4șPHr$78I6] v4:ICv6H _GM~d[A]谽٪]zY"uNݣD}y5^Sy[ԡMWNLX3cՎ?, 흿ܸ]9 ~"JgCkfNujAwU='MWqB/RUF( FA藌#uJE]aDqTnb, &` 9LL8B`R_d}m :TA,֚d$KuQ *NwP5&h*eisqPRݎ*|齹*>8K'ioY?p{xn:aB!d^3n Ggfr{)5G ]b%FJZ8 Vˡϊ-PeP+`T)<)t1ALն0^Pb ʣ\2(8m+rGIwm;dADq٦ϜSdhfH,ISt\%:j\04I4uּcL]uYE2r ֩*BZ*H礤 EIjXU׮..\05SK&$'3aœd!+//-y>Y7|tL +GT'YTP8m822KOЉQFhr@g#Z<: ^ͿUM49Qx ?UX|Ҭ$`AKJbwݔ{×8X>u{g\(nj$ Ko"Zg~*&ә0iy|]PtGP괸WfIxota;k%9@O M`(ê4;_J:,DtQc,!_8tǓEjJƽzm?4|7tC ԆyD$tԟdK@nӝf8;y@~ta=Rig,p:"Tv56{#!p[VͼK<0xrՐKMVW]nw/]o |ԉCMr[B]!B#QpX'3,$ M Xn3J]Op=мǣѠ9"C9Ą< hueAH^N0m"6AgL\۔5zS;@i*ŗcN*9K<z~{ڔ_M^S -I'~a 8%C6(҆p6©Fl#I- ;Bߍ iv3 = |~": ~M_T-立8S~ } Ƈ"Ցe6`bSh$t8БsY&…o Z@xp<6=<[Jֲp%}Ey5qA0Jwؠ뵨ueKr +9uLH1k>g䙁kGdk;׬. T3zf ^Z'mO+/̲7 =ОfWh@ThD$(tt\_Onѓ+dLד*=IG#}OxsB ki"H(T7N J?s@ZLepރe;v]GONp#/qp#UuD8GF~4]Ɣx:8n2;WI3 QB}M+Ao露l3WIdц$f $.ls9N kdDM\ܤ7*J y#e(}/BwsƂydy3LOe",Q0iV]Raz .["c]ĕk3,3j6\?yh5q|$|Qk7ECi: ㋵?>lj1TY(Ϥb}{[Q 2Ǝv'M)OqU_%ө͚P:$of%?1MېΫNM^QyE~3z|yX۴Ģ=ז]tv:u§QҫQ7B2M=ɊdfOCz%J%pHحaIpTdQ*QnO&A \JM^Y DٱCOT/ ͠HM.*[KjJX5}lvOqR^Zp뼲Swlqʩ}oV$v+&o&/ur;o ?U>޵eE ƔMΤ-NC}"X-@V3$povsc$Le4q(@!.XP`(D҂ ĐUZEjBшJ=jA3tHME$*@T2wÒ~Nk?ǵsnY9E!A<0CXȁšM~2:{wpq.k%iizjM䫃jN ֜'VoZ9&Q%:~7[Hi衴3qz[*#Ej~ S' Z`zGǗjs.8Uy#r?@,p"_]?~'[7\% chnKrG`a}еldd Y,ZYKVjMD-^Oz}@TDQJ*rsDK$.Dr@ d&((yn`IW5[qdP;M\IB 1~1}xѢ7TjMKa(Q8i,^td+o88mWV~-Cg߰5cs_+ֶ.-'m8dW]}"'CkQD )TR2'+"Xl6TCcg}>5%$נ׋^:/sK ]׺8W{FjFe !×=F3c&d0\q?6m Hv Ec d];V4p9EϣmeqtD*;_ߺҜy#*W%wL_>3f;[@E9|_y鏂ls6 'YثkFǫ[׆m.'$vحv5k'Q{jFkhcZyRi<9Z_%.V,t s#sn贵BI}Z~|Hf?7^ qiwܻ`U6M<א3ƫ%h1!@Zc5:c-k%a1XkYqXcQc8Nb ]k{ M;3^g^>^l,H6a*̆za?G6{I4U;JOچS N//*R դ J×35~?/>a!=?A&n5'Ѣ<_ ?Sd#ߠU_H#ŒжKm+F#c#zVy|Ca?sDcC>˯uƙícF/:O)PGrY ~iڪE$SjtZ} UʗY x)Շ/L%Ȍ:Η))?8 /_UjX}xWωv½Oފuwݻ]{׽uwݻ]_zw?y};_cS_jsV:Kr%q\Cqppb +q=\7:]g| C޻ ׿%(Jx׈g]3{׽?ǿ9qpooF㏐>xw&jUM:«1p Kn( ^-Yi D:`O*In=bnnOM<c^ׯ !Ŏ1ل"7Y1na^'NƭdVM0nc '%DOr8IvdSKK\_ĥ%nIKK\_k!k!6t ]7t =nf<t ,`_{!e+<xHEoK<"V(E)'EԋY)CpYzD`QKMzQwK{:+wŠdU70Oiࣃo5xV'\GJOZ݃w jWǼExL/}GTj:*XʻSƶEl:J>Ն:2 Y_+[˔iU–Ac8d"T*<{o:5:Pjwc<خC sOQ@""g$_`3ìCS`JG E?+X{t,[."C? m{׆po}oYwA9foA~,Ua۾T R Y}3#[*xݍFٮ;޹Hig:YlSi_VE'JYWc?sCSR˱ .}a ^DznQ,G}SoR{,CF=6N ߗ`dgqBQΛc3Nyte*yVzgӘJѿGX횻ϵmJg-~ ߕ M.<_ 7C?^讚+UrWJ┃jLDN8ɇzRC1*vvRaXscz÷t e+Yˀ;Fu{#hWB:gv3K<32Rjhd)h{|,X٢.h4_e؉G^橠I?Jch?ynTpԅesMX2:=ρ8qUcdp9r_ FJh;_-TĺW{N 0el#HQݑVw}ÎoQ 2̝:~B爯T:etnwMV4T\NvR(C_KC"PRbة}K6<^ǫ2(C`]w?ݪ;2t햘v|c`7`a J92=& _vY9t}.n>UN8sUw{n]#to_8cIPP3ш=Opc|ZnRB3${wk̏sʙcH:mDPHwDzR|!Ra,͜i*}-#/WV,Mɉh'({#iwZIO emȪdlOڮK)KA{{IPHw+j0|Yztiak0k<:!9R@t9FH"#߆j%>v!f\3ߍskz^<OVWUϪ)ՇVTWTWe++)uzAi]iҒL=""t>ͪVWVOOUKctb߫ޝG3ʚr=jr@Q]^N+ +P>eյʊE1#TcRzZRTe3jKiU%z=1Pϫ\ZUW^WZX\ZRRZWJ^RZ79JJ**2V`"PXT^]vwYrdAiE"L?Y1$!%}limhf^LHҟ, &ͮ-QQ5EYV*=bryueQ]>brE>yzݴ ]V]U?S=Mh> '[Y'ז՗f%u5^TUVu2.kJk_Yl52!jFPi3t"͠1 ،rh" LZQ5rZ \@YzzEՐA@-#'A^+0K} ZR=|PSV_ .)%5OyieMGbaU2C)(̙reՕ3:Z] k~*sF 5%EյSycI{9,H0bj3zQdЉLS:q);Q:^&(Ũ)ELI^V5\^T;:a+xb*2J@Z@EuuX9%ՓtPQ ˤ꣍DsKTRJB~r"[n$}q*&^2b^DabuIEեliP,XO[GD#JP׆U.xL)aibFy@GZj L)3(FBfYNX{#K*xe/*^_ ђayhմGTW^K;ܢEkiz\,^hG)|zp0}h}Tȱóe 2SGg9goЇU0lh}d>QyÇ6<hޘOC0.$X`Z8R VÇ&fO+C /|&C^OjpAc9z9eؓ 31+hG,Б)Dn;2/{CACɩмßг?9a=| }pd`OĈI.djGЅnj.Ky5v P~ g頄-"DŽҿB槿i 1m5W}vO`_z[{oſs6&uͿ6ao-ƗK;e+O\t3t)AWOij8Gtc35267WJ&/t1JT+E|.$q qЖa53dy bI7cxR}+C}](Xp^Ϗy{b0~j"U/?.T0kյ^{uA]M6&0HQw7=<ݔf?7n[-dn5 eP-3-;3KP,oXײ;c[ z0Y.c}}0ٳ?o>PParXaEH(^(^ ] [*ea˅)lEaXlLIl,-K/@Z *߷uM(Z~r;&}. fiyx-o; }UhZ^ e 2fօ(SS@)^!&f+yJR[(/-+4BB3ϜرSa( [+"LDw&aCtE5uxⲖ9\.|Jr5Hp\nr/K/e_.pͫ" 3FQ,7w* S1cý)Оohow9UGcfZ7j%Z@[5h>`B˥ h#6tfjk;?}Ku%j_ [X[`!߯2Wmx_cHtrߛYnFרvѰ͈)⦻kԙF=ۨpo'b_j"*/sB {zIl;n_YqQ\3F?iFnszQ7o.6꽲~Q4òbз7Ӳjз.4곲q^`X4d1h/b(I1qFǞ8Tz+YeR`p4$ؖg7eFmX`{QߐO ^;㠬mԆf?kua7ҌڰNb;~lXjYq͉FmThd/#z.YQo?oѿ(p.Ng{d}Qo3ۨKڐ;AC_%(nȭ}Ip✀gJOQo!Ë kFmyxQuQj1>bգ*;{?. _/A^Yy2w+rUqCFGOD@|duJUiT}!Ҫ\P*7۪Y WjMPlՈըDO{%N_F Cn5j#7"ae#Ӝ3nd}X7肑.wᒬkDo =..G/Al+|4j#c]535>ݲnOu)Yبo岾ae*+dYFmAb@uˬNyCwf_i:sX 2ccҕM{;qvJ2ZuQ_QO0]Ed2<2urDL^f;xAuxx}ڎze]c;x~I{o~;'xz);tx=}VF񬄛KSG,ApDT=Cypɱxk;lMi}óc-ܶ;h _~)"2&KlNYR9pҥd-9/6yfR))RrR*S4J;DGóJ}Ա[S/5g/_~c}R/҅~|B]oG7kn\6۲nsݎu;s~q @ǜp/`>=`>uEdHյk,xV‘S{D#&!o  '.cRv‸3YPx%AIQR>< Z٩B9WN)+Q>P>T>U]{UjQm]u7BRc85;o= ?f`7_}LRuu u:X>=LQPsjTGOhPUVǩϨg sDyuZjZNU_P+*ZQ uP].µ-Rt~Aa5_ цnמ&hi3,m2kՖhKZFV[P7k[׵h?~7]ڛϱjhwگ֎hGcqvR;jڇEvEN]4S0S9f aZXXXxXDXdXTXtXL3 Ņ=l([OM_QU- &}c" A=0M7j}˵~?ŀVUjk@`-EatU   dr&h8ap"Qh@ pxvЦ,'n!5Blh݅FШ5BFh QW&mj-Уyk"B'LBO ' mVX>?'D $ad1C6h9\@_z Ƣ$_IsnX z xp7]@Sh@!` q<`P xP ` ig&x ig&x ig06 cFCN9:-N iUaXViEhhX4k4-rh/ˡrh/ˡrhR/0ޏxL3l;44< OC44< OC44< a 4l-аEBFh¿ A 4 o-ҷ@H?-iۼ%'5ΑUnDV#lhخ3,7l\ơ x_fu9pVЈ(*{G~ױ~]7_]e^^2"=zQp`s;znG3+Unw%ДLÀG}р8Ht<#[Nϔ j!ߩjL>WDZL,WٿZ gǠg?~7iO:-Nˡr? +îk9t\:1'bNFF@ᙩj"fބ?-C׵I:FՙĨe?##5=?׳ - 8C^1\'!^ bxx9;9:'p-м@fG}o'"ࡂs-<PUOxk3,ciz6ë$OP@VؤE{ hH[nH;WOWQ^mG[2'1Isߓ{=NbI:)bdiׂ. KRX]p2d\ /ZXeȺyȐ[Fw9PDsۿ\JH4w{rݳdh.>8c}PB~翌:}h#^/[KwM$jׂ,C>}Y _>0~1q ܟDMO0f>Gۇ[q"? ˀ+X*ҮC;o'A$w |!qȗَCӐ8d;D 8 8 8hߠ>y2cU5'!%Wξyϰ+(YG"84=4 @$uS7X[GoQP SF,r Udz 8_>aX(q8 kN$ ig-~p|~CKˀWk5߬ \g@:44xao$y?C{WXvaqTghpm,2{1.f?nބ`lj^xffcxmz:Qrܘ-x8P3|߄uK^݃> wɐXcFifHRG=x}2@9OE"``:BLy/OE!EX"#DrsY$KSB?б @{O:r,1eu1OդCg DžOz^>hIC^O"d$5tA75rR脕l0 NiCNX8eSۈA[0I`3̚Ȑ  eDQ{VKV{ɧ%>rLLbYC6sOH8 N2rvCa,$?r\P ("POH:)/CJyCj PiDڟ9!RH6R-5x Y{Hn* 0#S5!49 5lyrG-%̘Jgڥ VdrKw34w+&aM- (75UN4QHڠºHÿO 3lKC^0g7dj:C̠}G<3bJ,9'<˹(/(Vi]pC{/.z g#dT;)طRhDZcD0gC > )i3`wH^x9YXXsa;i( jhޮ=3{]Ig9l.#C^"L u<ϊ30r4QKt|)q,e|~j0A!%1}zHP B\bA-1>1R4.W*ģMhޡ5ſIIiAE=E"ߣYQSG?I{IZ,Ba/pxb~<}籈O$DC֛;c$0 :L7V7m|Ӵ,[%ztmۣmHDЀNI[ % ~n͘NiwF:NSwvwf8{;;:#BXP&8Q;y΅%L\\\P߉^a؁@39yyysgLvynW+͕tAוr3ҕsƹ&J\SAqMwA|"2*k]ɵյõ˵۵uuS(λ.n`-p[n z+ǝNvOvpq݅I΋( ?t׺g:G8݋+;v2H@Lrosq7B;Jw{>rsFy9}}r^w ۱*3X8gX{lsl۹3vp_̈;06;vbرtq428<*>vv<\$v9vMzl nQK3cdJsQ={ث6N6J='95'N$z<.'ӓYz{2ݍAoI/O֓Ҥ O533SL,,seyVyz6x6yzv=Vsqox<wwsww ʕcAl]Ɍmnf{{wz{LCs{^YWc3AanށΝ^78D9^˥h[^dDWjڀ95(NshtWw`|f|Y9-~n/p@8KOev9Xc;w/_Jwǯrv]܎uoJ@]]%qğrso?,ĸKo  ww'}^=Kr?ۆ!6_.F滷nU_oH[{WWoDNy7׷zm-l(VxODLߓ[\[ۆLXkkqv2|q c}ǜ[|}gsj]]acyw;AMS$'D&| zBwT  ;g' 25v(L0*alBw'ͳ)a&96k|aBsgB}lD/avd{B6H[%Na꙰ę2'=1aMzDv 'lI;۝v:&4'u__p9GO8p= L91 ]H΄ %|}k͉Zbt'111-1=13ĬA9y '&$NMI8'q~e$uKܐ)qkX=qs,2گw'>{AnDSα罣(~/6$^"&^Hx˵!Ix6%Y“IĤnI=3z%C<9w:igICr} b#bߚT)<9x\nR~v#W(_TH8sپI%#b5'i+]!4 q52'&U%LPܹ2)?iAƤI+\bn{yc}Iyͮigm{{;11KSOjLjrHCéBZW iq3J:lF^ޖ0kMr''N:tUti j\pYm32k1tKY&ݹ(#q 0%ok;09#7X}]5ӓqޣXBF幨LZ_׎Q8wӞޜܐ T%oOI9i{ɞqͮE΁{i5%H'\O$U&KGys)7uIg|Hcc}ϒ?wI[YEer[ =ŜY9ֻ393.E2wrJt' OJbJp՚|gY}!~D~.9.~NJV 9)y( RƥLLIKR2{.DJMtsR;Sa LY<*!;e-)໴M)[qz?, Rv8פBLM'})|O1N8)kDIip3eK\) x\.=\Ӿz9&웋u#yhB[ Jtύ]gq/)w>>5\=+t \Fx&<oQ'^z?|}{]x.[z>^]>^gs̤uأ~t}1C/[ǟ"gUϧiiRshZߴAi9iyii&MMI6'm~"YJ>iVMې)m+͛N2y:=5&IҤ$%I;(ӎ1J֋)#։ܧ;g.}XƳ?KF9<7R}.Ƨ1~u,{-F0e/ɳes[/>esD HD1@=M6[%L{ľ#F G)jt Y(Jfƿ=1)Y?K9)&GLceܲXJcdG]EAǙ4Lb[j(5V1{RyD9 ,>Jk!ӽͱ7=SOqX[J$u&ZaI,72n/\7eCRk /,3䘹[% YR9gDy'a9m0pj"k rD$o)TP]#zF"D= BdîP~иNdcd\1wGPUFaGGq4g~DaT20>)ʈڀ݃17?u@F""fF̽c[^8bEuCKgYǮ٠O~,f쏐`li؏il:Gp񺡣A(~hl$WA\4Eq(X鈳j.j}y:yWGz߭6ץX{Ρu!v3vi'###>>.#|;͂r>2;rDȱ"#as\ƘȪzηLjI#gGcYF. kk"G6@;##F`8y"L9]Jﳈ^/;6:tkk#eҢ.NAQ9QyQQ&FDMOE͉(jYԪlgl+jW}W] `7TTkKQעnD݊{i5:<5?tF's-gѽ# z@jѓˢ+kgFύ^8zEu7Gonn?Pι/ԝ~v熄[y^iO dU^,߁:LϻXu:{;'rRn&w{j:7$߅ֆOU;/D_}3vJk!1݃g_@LFL9Bg`1ccFČ<[LXsBL)SSa vO<S3;pmȇ1Z-(Xx=M*dJV?Zy-X~zۓ?d%­I)pqq>:[mKOW},ckhv*QigflKbjىb:QL}ϟq\dnlm-v<FƏrg/oe>>q0>>GTO|3ㅬ),?kh{t0^œ ˘[2SR$3e<9,4ӫdkGLo0ΟUqfp cO3ud&&E~ az=ӳXfc{3]YtT] ƛqڙbY.T,1[#%mƕ%cqqו_*@SiD?E,= X/[vS483✸(M1+8D+%4@/,e@LTJJ eHYW ࢏qNYFE_L }ai~w.z /k?yb+F_G͏+z 8JTQ>1_,*~Il;.[CyPD8e.gioV'SһVt•$n?.sJNo0\:-+1ߝ9tQW}.5Ic-򷥳L, k5Z`gh-NX[X_d]Xe] Z&V.>GKkn=osڼd[7̓eYZp^^#.qͱ. ن/๶|[2Fe+AAp"5s\B !E%[Cۃ֩I?FY}km3us@ #XVAOp͵-@結f#(-F_ B@2`z8VT նu}ummm,وɶglYйCcM>f,?Ydrvd`bn0v]Z& /@rm m1n ;f kn>u=6VF{[K:FGbkض,i/G[fya_b_i_c_oooo7Xw؛{03AmU6a [hrSg> ƕb~~"~:gy1koõV0D'8Q{ A Gu#^Hwrd::98DG p>sl1߱ȱ̱ ql30f^ 6:vƱ tPM _^ 4uVijZ6ӶM,6SD_i`e>-n}#񲧦wYsFGQlL/ұJV*mL`ns23sʳnC,Dv[>oo]Ybc?:S>y8Ò9!yfo[kQų_ez3b*A2G~>o,^.0e vgU2ldn5,,ҜɲĚ,L.agdQZٟ+TxիU{ w r%r) <=óXd os3m Mc483nC$qI\Crk$IBL˸6B=$IҐ$!$!}Μ̔^k?yڗ/khМ)`IzH@bp$:\[mr {iʥSkKUx=ۤp_:ґƴ+Rrt׭*~'V?r?%KTa˛4? '##1v=oާ!4$[\oiOQ8 Irg#$^W9LQ] TPac׈J']%-^¯{i9z&E.\!rŮ/ ΙW+`9؛OWNvl "0res~u9_Eg<2|5|:_r̯nkDy&NƝMF)@Ko[T8_JKpOn@ Ln(ʍ!7AѦk-d+q~+d׫k9Օo~uhc~iJ r_z9=zrm#NnO@~*~rG+|dup)|!`x蛮҉ "B.< [_X\D@69pt@>Q^֕Gr {MW` 6ړ%׃\r BnQ7P?7*mk {>ɿ2^|w'{%CJf?ܫWJ8/~|W.p,dre[EA Xx&G;bt| xo=' PWrpJjnBtxlHp&(CW~;x 0]x(Ek ` -qaᙶ}<4`]@evEGHY !CGn!LɝQV0O[n&Rb;]By+i6_rks83$31Ya~h~B5:VvaG3t9X52XuW7Wgqb#׉\^ 7Pr#uo Lrs- Jrm&\:=;8N4s.ܼ4r3ZrA2}Z)jf4/Oh#$r)<1ҷB]|w-n*wmYIUԍؗܦԥC GmB][> +#?(t 5k%?JgV)7 g&>`#R~mg Y1sZJs5W;cX)Y9WwWwV!WâP7r8K +F.ϕ!WGBP}l#r͕:qU]}2J}5y~֘Y^fMy^5EyQ0;^g1/Z$kKҬ/˲ּf 4oh˓y2{FړY;>fO|:kO$I~HT։ϥYFg>cbͺ4E8f 2S|_z5| {X/Fև?f}'֏?eg,?g/,ɿd|'wy3^߱?^ ~f/v^Vcb˵d9{Bmyk7νo9cS'WE(*㺬kbXxE\KbL) 2YfO}Q '_f{P2(1Fk㩇Md.A!f |tϡhkm+ >׎:}O Ѭ4qrp9AE6NLjic+&mCۭiIg?t[z^H/eGbz#Zow^z>@L}@_mzG߯яs FdF9#ˆ2FcRFCc1@c1e5,c^NKyyoxW7o"c`l1?t{s/?[>LJpGKaf3 5Kf5Yψ5ͼd4̮ۚ^g7p3w|Qh5 R1(?Bm̄fgA/@wA׎ŻacЪ|K aL<_@P^eR ~3weipw]he"K0GN 23B|)a4W*|gрb y r# }3{@QC^CcC#Qi}t.rh Q'(ˠ}$5 vzk߂K |wȡ;Frh뱉C[\^@G?jGQ]Mj&!?BSQF> ]h"*|_=6zgMRLk5 Taћ5By+u߭=o{zBp}o.mmfݪ89ajn*q⿇bwhpЄ+]f"4KwtWAVa $7fM6NZT:<-"Yk`m1[[.k9H1gk4XG͕ rg(e!yul0Y%WͫwGeGWVV]G3GKG[GGW2GO댣? ܩ!DpGc511ّHu[XXNd&DZprVIJ:69:v8v;9p2+i9 g!g13Y,,I5Y1;klYYHi)u۝=FR*CfsgsJAygkg;r6'8rT5wƛzI9 ;G:ǐ 8J5*iL~{a&ԞR`+LjAղ̻@ { LX&cg2(u`KjSH[j6o:26iEJC~2.|`/âʨtXu(g.}'v%*JDvɸk#R~iWʸX3-ZvmW}BR#<̄/uؤ, ZmgVa/ڂvJ/B@=r3E:f ,uh)K=ZĤNKCEbС ]i!(YLՠedY|#=U203#hlm4iOG;2WfF%(mIkZNGdBr,m f،5GZAQirp`UQbz X@0Ho9=H,ۮFjK=c>2w*]JF׷&PvZDW I+X5]& F)5LMҦvȰ_ S AJi-bI66LF cCrq^_)'5n&cMIT7얻y.aO6xsޚx^<P>!>\hKJ^|3FtGq~JMӜZhZ9-BҢZ:'NkܕҦj=>/KKɛoZ#{L6i盵!0m6O,mH[آmϖ =jI=,;NGXU}WÎ^uYTrT~V6ynpڃM+wjөa#+O0P;DR{T|{([q2 F犝<'A<{pgT턖{q)+jc)t=:Α.߉{}4Ͳlk$׮~ZM*qxWυZ6F{Jkaۮ"-]dث\ 0VO9qPfʵ/.kswedp U)I7d|W/B\o}.~~a_` J<H}6MUxs^w)ڋaq_4=>Td⌋LX9_Ljwv֚|T{.̞xP#`ٺ)Q4DTsD/J~Y7%߫@y-e]a1OH$M&J.Sbt-&uRpV\" C.BF]ȱKhYf/!7.⤎h .bE4A?2yG뗯u [:BȘF&ϏXh\wWNȒ{NYۇ4%JҮrQ}ad%K~KX֞6& Wy3:LΧ|{jtx7o09߾oߺo7vMb<;/\-=&wkӅwp]6n+kN t۳ԛxƞ+!(4Q+,F~1Hs dAR)d@ :T,O܂MrS@K37b1K3,ɷp˽~tK0JG~ݼGhwzw1 |9&.Qi5{@8툱8#gUZ"4 PYN\NS8g,ChbZVFyhFZsNixm6XJo1mzk6mR[mֶimvD;itMwZE=HrzGu7Л1z=>z>x%gڜCa(}OaCKk rV;+Y@PG_a(gv:JA?rUÜ[؁AO+'b ƬXg@2kJ{KjNuո"t{IM.9øMUqI EiFh\uD˥R!bm׮cd~\Å̒ DIm5/ / 'OO>cy2gyvޚsZ)tɗ3ߋt4ף[l<ѫsB6g,3^],+ȂY cYyv7ªS)L ,d%WvNVJ|mݲ2!֘܄ b,$AYv"YuV݋gʪI"ԃg%IfTX Au}]8K,3z;?ӵO<%5ЎE+Xr>֢6lҫo^Z y6c{8pX?NLiv.\]i<쳞HF X0`U=` `͞T5`;NK`2|@ԧ;S*Ae^Ei7e\SJnCd{3˧d&Pz>Ί^Ԩ]sV*OPr'>g \j悯^m֟ cX*[F=nvx>oH}POʗ|7?i5{QT=M_o' FjDu( b`q'}>ef6o8V Fs;\C{^mL{=Nv^h/߷K2{^o$vGy%^©Gkd[vI].c ]vEn;®dW;Ҏv mײٍQq}}nmcOv#ani[؏m6v{=;ng׵_;vn_//ٯ؉P?H{=֞bO'Nvg͎{ڽv~ƎA`e{=m'ocqv=~Nd{-o0Uם<,E4JNc4Jj{hz/e]VtqzwaoR| JB3O~ƶi&cdl'܃fcG;γٟ"׸-ߺyA^EN^%yi^w|&4J3,}x|)>7'S;n{~ظuT[j~^=B݄,jY|&zQݢnl]2*vhf6GTmy}Pmo{#M^7C͎݂TݖB=*벦wQu2EswydKcbAGiZV4fqꁞYٓ)֓=Mse3,=s4?}z@"^l,`؇+=;~8S?verA}Vx!껂y1C F^u>OS46. | /+jy&l[if4ÿiq1%0#|rv'DY+BUg/0۾N6m ~Pla\qJ]srvqtG++=HwYw9wywwE]JX'ʕyih+YI3HMۉUCv!dv1;}>jwm܍M5Zqpr?Li "G@դT{g38WL(*eJQZn#6@ e/we] KF5A;Ӿft:J4QA$$ܝ-p.ҜB[n;@oн[Jnۤ嶸.@RпwѿnmYG@w翚DweNҿ8BnGvN4D_ڿD?*r' 9#SxeQ0 \$>PC4I>1AI$'HXCZ|J3BI9F?do׮6יĵny:cjb-Hڜv˴+uG#W0^ra_f-pȐj`O{/7xWڵndeR݋]䁪:yϭʶe1: :Vab]4.WmvfOD)] s_ywk2| } PYùNzPxcJ/oC+yGdY\V?o5bꜬ ]ڻOFҸyG8y5ˍQDDGRiR]e;! Ls[@Y+rSẝsqKhdqʜA4}WZt9UQyPr~xHk#AC$>$J(5`la6$t6c2m`[veQva%np\LTy5^ y3ޒxWhNx"Γ8>TM$^"͚_r..' …\\aC]\sE{\5\]]\] \M\]]]]m]\]>570CDI% 7@y7@Pƀ&(o&(cA XP-Pe(@xPƃ2 Le(ADPAI%ILe(S@PR@I%e(3A LPPe(@lPf2TPRAIe(s@\P2wAywAP>e(@YrPe(!(e(@YjPV5e (@P6le(@A͠le3( (-lSP>SP||vP/@/@Pv%(_%(頤NPv]e(_(_ݠkPkP=|7|^PoAoA>P(߁(A~Pr (AAC(A9 aPr#QPrcE 1g17sx6訫4Nu3,p 20 3,p 20 3,p 20 3,p 20 3,p 20 3,p 20C̶2ɠ@[bFLe(@tP2AyAA`w̰;X3,z 0B`w̰;X3,z 0B`w̰;X3,z 0B`w̰;X3isڏN\'Z.`-׉ Duf8rhN\'Z.`-׉ Duf8rhbK3AsaJP "`#~ψ2렼(PF2 5f1jrcFsf0qvN>C>kȚ%kڱ,1i{"c||̍yXsmwGJ&IBCk Mi vvb٭P+ b F*g dY"SeƱd6`-h[]YhvJ3ݹ4qм} ј!id@p,y‰]).Oo"I TT̑9.*Z/s*ocIh򆛼O{*G1Y<(cJ1/XuVOOM^隯MJ1qsPOK[jUX*5 _jRz>-em,efj(kh˵e.%Wb^ 4W9yBo6jil+LT|v>ǡHvFgN9-<.ۄws<߽m<.ϯn>1n>1{o>Ss}޶sg ?cjl%2o~V 1B+1 ;]4*|g*2&O Ocv; >.Ҙ*F,`i͝:!k ?Ɋ`T}\/m>2yxoM˴bVk|sz|/;wI~'-sr]#7.!wNx()!녋eoTnZ%t/<=aEEԌY=Zvtwyeq,c<YSV^y~=Ň7h`׎VA5֨0S-d( .<siF7-gzqG]2dv7Tө]#8eiq*(O'g?k*W>ab1/ /m *]7-g?ܽhc%=%=M=SJyp=ծRK|ʽTKU=V߷s]+FQ(DO[I4n4jq*B7uyڷi~??IwTDLo}R+ڽz(ͷ}r:pwѡwNybjG;_hF>ǂ.%9WN~!#YA-TSշ<5+y!;IG=4V^7F œghz6~oM=UHө?]n2g4Uf咞&yߔ̠^>⹧p[w_gAgIV;k7ωc3ݵؓmkj{+&mMpTz=Ժ'yJΩկ-ʷ7^TFz#okZSϒ6vaiϔ_wpܵ cl1/O>qrv&y.kSGkg7Jǃ˷Z" RoţUO-޺:a'hiFGh;ֹ\;ֵz=i6hȮ3g ~ᅰm{GU#z8޶lFK.eq=hØ] zC{g~}o8a'xd ېL}9G5М؎klo֩6F)UVwKY&Klۗd'Uݧ9[%T B{jzyFFUըH\Vn}=^؊|kxR1mX2O~9f%x "Yr{/91‘@#m%>ZigvŏkΌJ:ppǢS{ Z4lV.6G[#9fk}3'.(9qu.oջǼ݈5y(`N::U?oV,Sߟ.0r.|Zv3jM^cz}!Qol?޻[7?vI^Q毿q;/뿸U|ɓtk/j J·qmTr ֢Mƴ~zj {שSf=aK$v>8*xuS?*z=1}fN]7Ӆ^k-*iܚi_+UXq,e+Ǥ[4{%) fk+U%jOʮK95tk?=䓍ƻ~iS[9>gȻ2?~*hӟ~'1Гhz1 W(xdFdք (OZӨH',}&jsO_ {{K,pg-v;+>cy+ʗ7}WͅOGod-R+Y|/t}mNez-x=:jq7޽`k)1FTrbIOTYn~~>pi}V%:x#W_c֦~~{OyvJ<ܦ́ ^޸Þ~WyqА33<~d=Tm򺗢v.}±уӧ y7KV/V/Z95{wٕѿudӺ-7}}e_|W{2/9Ͼ_%vv}%khcftV46<́u '}|jCc߾:Í=s'8 /|(G_y?7=9+y#ct\\e׏Mm ~ oDQ#}#6}kU4~rGݻhxӛhuÔ^wםouK,/kabbf(eHbpfpDW1*eO"~_-?nxQ RP.n$ioYᝒxx##p5T!UsNUsx/1hrKt)ၤll`Q̘P7+%?Ģb\L&rF > 9 1LdJ tA"x|9Av)b눥j[:AH%J >L4iZJ St~.o SW.y;y鱩-jBoL$-Ʃ;†ُ4g?^zelG/SNVT)]6i5&7C]d6WZZyQ3=ir?S]t w?󭟩1}/-'k$}8{.zOKc\]tۢFRc9cSdgF^%Xٖ1ji5 qaLhZ%M6vRxv bGic+gȶT^d_bwIxOݽT7ӿ-vB/ AK]L]Zh~U/+7|cv^VHye>RU?oY|/Mh{qnpFU6^qfI <\8]rY'y6l0hbYh8u+.죁G@4s2 ϻ]q ˊ(#46I&6<_|N,^)HZx Bh5h03V0nҀ ҁ@VCB9; ?( Rnfibd=ѠLӡdb |N_]r}j$M["T;eV>:b+ބW͘bktTI+cknE[VymZQP#_Ϲx_+7,3h~|!,9!^JLu{{7ߊ{|r,ִ%|c*]t?fZՉ,-U\-/gsڛ} Wlg&wf-r6|KaE*>#x&& `DGlML@!Ap8y64k $@2qc` 0Rd`6[cc%Nr8G.(YWѨ9'kʥ/5Ͼ6L-3ij'W;zS~?[c'>׊~}03;W>MʩTL~tTԒ/9U,^vfnW^~us~'t7Xֹ9 4VMNV.H}y%sƩn9fhvȎ n1)-|3Y/BtAI_5m~uӌDh{&yaM*-Z endstream endobj 117 0 obj << /BaseFont /CIDFont+F10 /DescendantFonts [ << /BaseFont /CIDFont+F10 /CIDSystemInfo << /Ordering 110 0 R /Registry 111 0 R /Supplement 0 >> /CIDToGIDMap /Identity /FontDescriptor << /Ascent 905 /CapHeight 715 /Descent -211 /Flags 6 /FontBBox 112 0 R /FontFile2 114 0 R /FontName /CIDFont+F10 /ItalicAngle 0 /StemV 113 0 R /Type /FontDescriptor >> /Subtype /CIDFontType2 /Type /Font /W 115 0 R >> ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 116 0 R /Type /Font >> endobj 118 0 obj << /Filter /FlateDecode /Length 3910 >> stream x]͏ ȹ < 9V` LPQ%;3")Ǐ3";A?g^!Zy߉hϟ9U!;<7~|z2>|s7 Jt ҇S>Y!=;K>^>\=_\Z^uJ9HAv lJHUbF3(w/q75R.SC8=}YR[gPfǩ+hd@~ LrGx9E"iL++TFMP-(,NE0;\+-S2H Ee*&m%. &QLTkُJ(Ο3r@*cKG&و`AK5fWP oIݼ mކw&۠Y[qA״LU )J˼CBP&oԙPl lHup(D@+~őw1i3;ѫ_Δ޴ 49shQ7/BBu!m I.ܐC˺ (4t2LݨW@m8S^̔;FuB%5m$1ߢ{ F !@Cբn DUq\m ay8X^Ax1(o ϐ0x4,[;qESF IeS.kM|˖*0[ k~6". Jr).H]ɢ߈NhG\H 4tM|qD..Ľ0[/vs (B[b\ƅ?4ZJnC\F18wMzoh =#VSc Rϝo4ɸhR{998?N?/@ 5@I~eYM$;e5f :Q@gKVJJE-x:]z b0rGz=w\;AZE2ϋyU^ $4f@m֫s)3!`DCk=4![K+VuUDEkxbA/ wu ,$ʁ1SG+H(َF.I9/F¤Qs 5&uk*Uރ$CLtD^!^zl(4$A[PIe#Gw)>A/o 52̛\M``Ou࿜Bw_KDե_sl,~XٵpnVW "FPݰK.AGs}sC@umcl e?ɹ)q_}o٢R"&[4$[llq-~;90F)- ܨBeν`~Աex[/}Μiȶlkx#kZt}Μ eq$=IpQzuZhdyЦw:H@˵gX;ل_ME>Snʪ!/])y8p"ZIɲC?~8εq~eE$^ːZ>EQokM&Zb`T&!/8Ȯ0PULUU_Es7 pckѿM|6mR!n|i(L;>£ ѱ#QC#ي0 Z(em i7E)ʹ5n !;I3"Ur^ ނrÀ`ޅIS-Q밖ZT;rV!"4ZgRpb~E6-&I;ڲ&4jF:15"|P6l,-iXws1bkW#[4O[ۧ) є+S{cnpdZsdڈէ O? {5plm:fE*40 RpK3 ]IFHE8A׶XU-Im^[-;X$V,.3xhF6kwXL'wz TPUT>˜%KY[k99Ekzw#\~:X5[ȻY*#Xv3nB SB@ig&Xn6i}WSJF\L}N/..r. hCΨzw@Z/7͏K1hq{9k[Gך&[ ~k C|i} Pmfjki^κ*-Ө/8[j9 =1V>g:g n mGA觌~NNBvxۀA{Yȕ֖.`RwgZ!h9ύ%}/I.V./&2yؗV\_7Ɨ/},#0\?GSz:Uҳ^T!Uz{Xv.Xn.<`QQwL+o^`{}_Ʉ<ӃQ^AFLN#K[nқֺj=-7/NjWF L e&ʠt'*%POP : \[`qj岹 ސ1 Mr 6w5PrMGkjy'Gfnh3co9Kcn3I _oMaWZś{ stzWpknm{! )R˚ y~~}jƢyVܴ* nٶ6ڐ ,Z4r/5~ŏ (-MKһZ?8]Wp~KW9|ocΜ?:J)\A;Jmlh2d\HzMٺ> >> endobj 101 0 obj << /Contents [ 118 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 119 0 R /Rotate 0 /Type /Page >> endobj 121 0 obj << /Filter /FlateDecode /Length 3526 >> stream xn8^_W[@NR9m{Z`v݇?,.EUV:HMJ3 DsykBR//z^pO^)7| og0vI]߾?gI^ANK~qc6ް .ST}1h Z#SJ0qtB TR `.ADd4RE~+yTSayOq^o%y]d$LOZ?/->"_st5GoqЦ@'vtx_<zkMi WÍowF[ǐ :v]nf%b RΌ(K2{1(xR%YTsSƼMoiв#Kh&sg%(4\RmV?nrBQ'yimC!H qa]6Ⱥ)LCAAd>|(עEMdt&&tf_CTt( ȰN)FԩfuUjVA%OeHUI"y B1.Õ.jt]; hqvOF@ r}A)j{UjZjXV7X 2% BR@iI>jeLf8(vfu|);ť<KlUĨȄ+|ACz>]rQD4I­?љ3buSvI| ӌIn+jL EJ3bLm4zgdf1驗;Z@_+&o<mVyI^*$!SᥲGC &a.YUJ9<4棴% `{[͙ 0^ ͕m -NIVˢ0KYV1b]|[rG[F(n5MiDvន :m\6GsR(#>Ό)-u7/_QءP7퐆 \Zr;͘ ^Ckc䞐Wy;{>X_|@͎X,>#NM mwHH𴑢x\džNֈAJh`l}&QxMm9)) z>vFu9h|g$++س➒< L*k;> 罹]E(Of=jf=ao2ɪ§hJju\^1–ڥJnuT͍F._ t7)sȨq଱'.S:Aر.ba0g=~Qf Jz8o䐩1?h޳rᆷd$l5t̜9V"]칼۫k^k^ m_5oDV8w[Sk]~|xsvV ^^3Zug[BeG;mQjݝ\xJFڀ vwےIO۝-yy x-y-kwvwzg}axwׅ1i>aH/HG FxC)*[ALdL/4Иycxekz{rVfoI 5;<BJ6VxleHՌl^C ޝy-X oX谨0G%{vpXΞ9ΞI?{b6P|RWl;X0x" !kݢ/Y妺OaM L]p<$Lh8GVsַ+ML;MY#5ÝX"ytuҮq=Rs UGf\ì;:&`&酲uF̡Ffr+wM ynVGmXw<4J[~:Ԏ]\_RWN(K#(Wz[-Vx?ӛZZSrؒK]g35+%ݗhHBmFȼsfO7)FY(iȶ*{Ht&IÏ͏*墶åE)ͷ uu}ZO:*|B=K 1 v鼔wnz)I%=>JQKSsACٔ8#[|YEl_9_ԛl90Sro,XX6׎ vU pzU 0;6Br9 .1p)|cORuL65XT5;EF-"ȔƲ scPkAfkEEby;R=e&r*$$ӂcՃ6zi!en8>N8k+qFA34Mlll:-us|c|I(WM#[q~|\#/gF [*Q  gg @0 LQm or`䱩 ^;̜VqBRv|:tϩ^{ T=h>Y>'M-vKe&۾Y/)\7 Ѓ|9-#?s>r6eGf2vY]s^i򝛡"n̯GD2xGN{">bJIsG{"K\\ R-ң_]yfjhJG3wsNd>| vkR.ÿ?|rG^sRD-mŒL=vFCm3EFK 4_TB endstream endobj 122 0 obj << /Font << /F1 27 0 R /F2 45 0 R /F3 11 0 R /F4 62 0 R /F5 90 0 R /F6 98 0 R /F7 117 0 R >> >> endobj 120 0 obj << /Contents [ 121 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 122 0 R /Rotate 0 /Type /Page >> endobj 124 0 obj << /Filter /FlateDecode /Length 4306 >> stream x]Kޟ_q(^RRO"+.*M3`cMO>zQaw`,Kp1w+_Z??nÃY?wq3Gk6\Ȼf-uߙ ~g/_zy…dWrͭZk_hk~Bz7yv.Ki#wO0X,6pXݭ{_;3ԗonH9:gvF>l;M9wfin ^esz"sF\@,0F,WID5!J|ŵJjVV]4*k!S & ='uXe ^R5H"N r?t]F:M( ^n})ݒ/JAd4OWqGP+:k4zs享{Z`6ٮ'["nnGaPp@r!{8ƪ6= |,J1}ej?&biSk 0:m} kvI ? dۊ $owdĶϫ#SH=Z>e n|mX~7o);God>)u:x~"kx-=Z֐-k&7o$zZxZYAeT&f9LPGAC^C[o9^/Co9 d0=2Q.D7m>^{t],%/|?8oF?G{k-m@ [+jO*,3_H^GC뚁QčqѶW|ۘw&m[ =Uمun(A]Ml;Fӕ`I[4ywǹS#䧘G`: j Y$M脪Ie=HqNcI4n-ǴτE\.$\"-x  T^SyԺpwAA/[p!U$:ve0Mwh[föZ)$_Β*H"OB\,o6Q>DvJ *Ё6~o[lGYU|{݈+uQ$xl 'PmgUhaliu'sӖiS9|YfZfia.˚_ہlmڱ)\4nƃLąT\qXXaNEwwz9$V#ʻH'щ;l]$pcLsAb΂"N)infBJAz$O|,PTq%hz1{e{T]xтh@K^|^Om<IfK2 Y JRdefgt٤Fǖ` YoSzhr_.AUMiqxVL[ޕl]Pg%sʲTc# ʕMdJseRe߹P4 W5`(pr`xe٦&ȳgӌW6lH]Q6+V9y9:pl>V6 ɭ0\^1YPɂl ˖+UmvҪ,fqI.e]4y&QdRZ˼ӛuTwgFmOHв`*NSr|7ukGl'L`Nr{OzXCB$ >RrvKТIIi4/nkd''OeqTv*bl gN+0mB]H$$[Hsԧۤ[YjmNV[@{VPlcC`=>pbx辌Z ? QxSt$.jT{$ދ |69V|m=h[5)夗l1jmi`Hpu!Mإ{+ݏ0qsIPz!i_~ 3areM'L3legYy|Iud1G]"hd ]~&M3a+ z~{)\%{^i m'%OQѠf7r*X[[mIt['/ G,wUl.kBܵ^q쓮 ;kbQ f'F))(gui4=٪Gf. M}WE|ž&[ )A^T%\iz9*8'3Ͽ6W\SbŚmCR-<Ď1ʖY$/ЀYYu NL߽e׃ endstream endobj 125 0 obj << /Font << /F1 27 0 R /F2 90 0 R /F3 98 0 R /F4 45 0 R /F5 11 0 R /F6 62 0 R /F7 117 0 R /F8 19 0 R >> >> endobj 123 0 obj << /Contents [ 124 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 125 0 R /Rotate 0 /Type /Page >> endobj 127 0 obj << /Filter /FlateDecode /Length 3276 >> stream x]n S ĕD($d+*pE΢-Qd{܋U3QHJuZ2:? u)}i^?Oo̱cΟ9$43XϿgO\ߏа=?A?? 򊣨Qp?N60 <Z0g@?Y֡C\yVHmҼBE =RYܾ89t?'Xn6x_}y{ϵrc^>.T(뷘05`sq/ec L@-1A:HD0xDJ.}~^@B-K"=~|W_1)??4BP9~nq:)z4gl't7eZp[ܩ~W9c&QCaJ{p\IW[uTUfU".2J>R~$bUbIP&j1YҼmޒFۈA&A|{.$O]@7܊1h;K }*X~/%m_mos`}Ll.~ %bƃ]PhwC)^"H$+5z5TBŜ a~wkHp1(HlrqU]GR '\}IdyL{"{)jK4ٌc2\BH@H 8d(rqN-lZUXV@.7y5K\O2/s9|~{rQXF\޷ۃ(SŏvqzQ :bq%K6Gvre~nʨ,%Oǯm|T\gaIw5W$ԃkHlȗ[%m%N]qUWL)`{(86q qb`W>mZ /3T=[Wځ^z}Ue^S}~Ҁ89PV?oh{)}bJ>81!gb|暚l^ʠw49C.HN2(IR(-N~Z%i,bja*}<ߦCq{lFIk}+Sv<(-W\eN"&Ȃd'_'0CC+~:Wy8N|k֮:mtu[w.TwX@/Lh[q>ڲ,sdB;%h5 @ʗd1!n0EY 覐Įpw ))%x9F7 K_ ,*{7xX!hGMr1Agm@8ՃJTY[w* `JAWԟ>W,0I\Gpٸ^wyꆐq,e&G߄u?}`k1[2B[]TQ*9Nc >_O;ZX ?[;ٜec.h#`|`|1',,)Jm>WƤA}'b'Լd\c͇ Gy3 0q&b6LK@Bi?< y{@ޕ:):EW+_Ji HDp^JSޚrL~aPJAD.氡Fԭ`+"ڊP+ kV$p"Hl:@e+Xv&K{8RZdM n7cUB"#ʪ)h^ՃҼݰƜy^vUU(Ū^ ۛ4 T˜j+J9#TIEr 71|θf(aVc98sKʞJ{cOT\%޴nؓs& gcO4ES9!>ch>zflyTi#4Сr,2ӯ0'仳ĚDA F=ZnIzzo$5umvBHa.X;#>CKCh`phgڄa/S~t$0G8i5d2am+SCo8+M؛%/43k$[aš7.=9>P!x㶗dչ|p*b .WNO'ׇ\>|˹|*87ːRryK衁X&BbcntF*! q̎F˿EF&Od]Z aѥ&5jUzjcS?^\ j}qbbB;hhSB{?7>sӷ8Fo|rS&]ڃifyb] o|'鷶6ƧW\+'x~܍Oɺk] chT=lQ֌"VU9Bc֤u. wR6W[ r֡*RV矻+the,6d~+.c9Z(&[pcWR?#"Z~/*<>㴲UNy(rN{$2GTsl^"mzv饶֮M/ck(צlզ=NDæ=ׁh#+\wqg]M{KiRfiִ~r( Z!\GDpAZ,9R!uV J"@*MrĜkXEQp[Er͟\ vkJHYsr 'TpxePZeu&L4,NdU]۬A,gے!Z a5T!Ojh"юn3h 9[k9S[k9 ?HZ!rnB]yw1mirR9v endstream endobj 128 0 obj << /Font << /F1 27 0 R /F2 11 0 R /F3 90 0 R /F4 45 0 R /F5 98 0 R /F6 19 0 R >> >> endobj 126 0 obj << /Contents [ 127 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 128 0 R /Rotate 0 /Type /Page >> endobj 130 0 obj << /Filter /FlateDecode /Length 3775 >> stream xn,16 H#)p2Ñz8auOOK*R_$`ݬQSp*=I)ϏYzJF)ks]?{ة?w{ON*<;TO8E'&)=} ^?=^o?U߲s1OZk^Ϟ?kKzK `NzcDp0ʋi6g~Wdb^y;h=c)sI_DPtP)z+jv@3az^!I0VLH| d֞W/т$zc8\^ѣ^>0UB|\_&ݦ_?)>{~8=nzQ%~E >4 S" {s`fCcIg^Is,Pf64 ~B(Km^f{QEDc= kں۬WOǷp{qp86:WosgٺFK\oֲ}6MO왛g/cm;衦^,WʾBSfLj/Ք Qw0dZ,7K`{]{M>t<E˛ɹ,H:#,@%0(1| FZ8nh>;26p=osGR R|_S;6. |n.xm.p;V5!\c%1▂`!xS_F_3g}g4so=34J4LjQmII'vmG)9~L١}q aE,Ya#wh5:1'Lo Cp2^l^\b^bd Q`Q2 a(H D_/?hNehxىUXX/a<70+0i'tynOOLQP"2nmy_qPrCƇjMw_LAd7@Z%}5\BZ#f g*U>+_l,OnSB e PD<@*xP3j0Emm*fnj##/$'gWFЕju#hepD뼇9 4(Ru@P1؋8; 댚q!zϧ BX6;6yeZK>AY5,dY`Tt6yL1Y\|_jo N?%lޥbE^F8-B *UUCA\-VK )'tpN[,E0gh:;I(V'pcc`4b.4~\Y= W6 2͠]<ؓtzFrqkQY_#luN{!W; aQ^ _#-}͜h;7KmF*>Oi/,>ŅZ C޳NFTs; `$1p_ܣh+Vo9> zgbzq+*xpEl[ +IUCŻ[:4;i1؄7UUai -'U^j,Y3M[e*PQUFp{TU,KU_* Rl!hJԶs^~eظEJYgj;|pVj*fON$9>#24$XI_`)~OUVax̳|KeLF$ΝܸE3:tc}][_P 3R,hz!^{*pÌGZV|$=qשCW'lT;Ok 5a:/2@ϳ!ف$pڤXքqq)Ҁ۴GÛWR*@hԵS-87BɩϥUYԄƝlQ[vUV\Y.Bs\~X\BX R v6Ųm"^JJG@'*Vz HbUt\QӠL( 4O.GaiϤBq^g(нN0gD6%8^$5<P2ˆQ^nϳGiUγ&l6ZOx'<*~*i$`)OU`#Qy%ݨu sd?]o~P `(8%H7@szyD_XNz1QlonpwڐMcY;훎2ag2 2;sT?};p8g] ޷/XVK? ۾69K]ڏRm}HB<?wIsO:NuAL2b"b"f/ub&ݮʁN8@B>ZGnq⡉v8*CV&s}nbJ=YuK?-qz f"3z B]R2S]7dHM? 9% ($|W xbg87^ү2GX0?(DZY?bqCXnxN@$]dE.\E U`AϠz̧<܀(呾Hds,c6|vϨ{/< B&o87ޞX܎RNhjF5rMbc b1ŇL-h贲xGQxTQjgl"˥-4ĺǎr>c 3Ҕs2zG8##q3:_`9=٭ZZI-f<> >> endobj 129 0 obj << /Contents [ 130 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 131 0 R /Rotate 0 /Type /Page >> endobj 133 0 obj << /Filter /FlateDecode /Length 2846 >> stream x\Ɏ8+f hP< 4@ORi#EJ%WR`ċbZy)9K!yߡ?=~ot$a_:ӄ .^Q>?{텴s|~N\o\W$|f,ys ӷRW%$ 5*Xq!M5t'[<ϜEd>\~ݝ|/8Go4)mC`pݖHp;$ᯇj,a$ m*l]4ΕDd"|nɵWr}ߙ CV[GHdM>e8٠(6d?7 ) G{ l' ]G$LP)Ŕ'isCNh>G3 .K'H9ʸW pM 2He#, |9RxnQIx#rqzi7z#86ӱzv;9{: *87TGdG XCSYx  x E!G~g L|{|?K?~dPQ=}Y1_Q;JP`,t!)m mrBד5Eweu$5JWa6ӏ=%D%ݩ6 ݍJQ5jѽ3B#Ȼ6ghUBh+j9x;D-(LJ-UrԤ"y472jd_Q-zĈB~K!x>*HӬJZabP$XO}~OGyWaQŧCG( zP{wF/^h¥f鍟h ./2v6@M-ƹEg& ^I3***ĐqSAF"T0RA $2ްxC$5T6Rμŕ0WҖ"\<資-|nEW8!h4͸OXh_)Cͥ))p2RpbEaoT$Z pMZ\,gI9f:F'C2a7*ȰW#}I)1 gةXYF]܀bnq8wDD'R'"N(]o(Z F4ZHg!(ϦL%X XƳsss, (Ǣ<}L[EsN2aR.A\ 'LNr=X H$16qZSy֫2t_E ?E Xrܢ8Hߘ] x3ɵ\6E"pClT*s=W)/r[1)vry]Qz[*7ZZnze4[5S:cm.q N5SY){n)"qR tB1๱f :Ǣ*IlGk akiTxˆFxnH'Vjڼv#9{dU/"+x)ywXZn˃kSʂ*;q(>j7ŤK?,c֊[_+rΰZu:˸xvh~xm ^g{'E!SRᕼ^'35d<bbLD fvg=*DX^`)Q โhf|dyVWC$U9k-wlQ! Lhh֤J[td4DW"yGKQlH[*YT!="=!z^1^բEm[Z-MȖ&-.6n:?~ ZIo֤r !W͙ /@iTUOBu::ZY- '[B[dmT;$ 5"NN 6'[aUjJZe1d0fQOӱ}5‚|I`1wӊf892H.j(" 4A x9bTr6=?@5s+kf>YKQ&@Q]{.ZՉ{iŕv[({kՐU7WTS6*/x5Fj ݆*\,*DGs'"J[>PzML 2\bd$TP`c͠d\H'g׆Mk{0mac5.? 1Jgy%FCXSҴiΊuFeկ&J,A)Aaߨ@KQ*fgbFA}PMx1s>`.'>y6Z&tjfD$D#ydHdHѳ޺vBqCrIo,"Z=ز@d.cOGɐ Гɐ"GgSyjYX㝮 ŗ-ޛ hf#+MXk,2T4ZQ*[ tSEI2<j!9 VVIp(l/ ;^#Ͻ< 9jW8Wvhū4XqI,WV[re!?vY!p٢'=6{nNUCZ_k7>֏`-Q.zp5ֺ~2Uuj]+u%XrYKf ]FAx%9[ueMn h\2s5^WVy?7r endstream endobj 134 0 obj << /Font << /F1 27 0 R /F2 90 0 R /F3 98 0 R /F4 45 0 R >> >> endobj 132 0 obj << /Contents [ 133 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 134 0 R /Rotate 0 /Type /Page >> endobj 136 0 obj << /Filter /FlateDecode /Length 4093 >> stream x]$+&#xk y '1chʃ 2yWCVgeV2x U3;R޳;'K??9t2?noZp!uZ:wߘ| @_t G@:_䚿k9]˯U O׏IC!NQ̝sA9hg^H/܅ }:ۅvҩ @N]J 6cK:al)0o=-\p"-' FqDddRg:fgdC?{'|pJI`^q%@ RߚfRrVD Tb"0-hv CL~ Czb~0q2[&L}"1 p\9 Ώz4H6= @o_죽9AyP T{{6, t G<0̫ -21{Bv r_鴐 @&?^T۲< RmcկF<f#.R)AMS.cT<\Jۀ*L=*mX<]& Dyu5/d,Rlz-1,tQ'Wuk&B&?ؤ[iN6SEPC^4rvjˬˌ@"D[DžUS`3XC"YݳܗQ ;w6m"xi69!+DlyL?̟r:~m;zzW_ݑ'Xb ,Lv 'vm: &u[s),?elnB K}Tb)o{iiOWTEr`iYj=< x@t1A\o K'^N+ L KN`wL_fpK]m/?A%x] m{}ΔKP'ʪ^=o~s^0yKdk LgF;.ńiY,j[K0g[1xQ]Ջ[jH(HiGncB@Y$Ǭ"^&䤴B8G:En<# a Ӳ]Мc2AA˦4 9+ܫE kdTʼ"wi)+PSi-xFXĕ%yCLOW Tt;myU.x>\7΋'eKZ \WVpؿ Z6V)Rϖ$gu1sOpVtM'%DnQX'-Ip]Tt1PpV V3c ^e`L̯Vn:<_7R? {.HZv gOdQ#`RHt) S2IgJd˅ µ;G 4 )v0j؎6퉲T&U\+s&fÍp}DWcBn+'l8Kl0[3<&8q**VK-5;cݖ|DuΤp:3~Psa Y^r#ǁbIWzq]< Q,?Ȁ_;8az) YP?47(OX*G\.>Vlj#.o=)_r?EP'L &^`i3۫_j/-5Wd2xV.(1O Vto:@R_ýZⵡ\#-Qp},) LYn>;Ty2B5>q(a"hS ZExN!L*>ԭBثp±P5*PZS%Con->B Q )φ|r(!]9 =]T5 WjB%fs)ELUAǒ*f$*eiѡ9] C= .CU o T!D"]QuDڶ~n%"S4[СT5,~b[5IHA0c~H> 4Gz/)Jr1`@%-*pH:Z t- ٬:u6{X멗Jn|60.8ZZlvF `X:9J'+"2kXD9״4!j#{{a)nBtj!8y,g;/JkWф_qb\p*'R93#֬qLTc|lb6VHvlQ<˲>k Wn}p?G5pzz7dž175!H[^&!OUe-B=gy+3$yu NM}`\AГ 93*rjFl*}v? 6`c>U9ڼ4rғ=+6Osg7[7LK5}!sJ,c  YBL-1V*'T0ڎBz^ g~m1hβj>Vͪ]|agYAm{KIrY6VϞV~O{!ZE8|˫ZcV(J+E3Z4K(Vbqήfb(yaaV%Azc?}R `%\e#jQKJam=8gn䩄ZD,-fyUVmQU6vTJ\5`&>%vIEl}OTpdP.G#: !7&#m u0}  gN3@*bϽ^Q !amHJ[|rE#ԇ> >> endobj 135 0 obj << /Contents [ 136 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 137 0 R /Rotate 0 /Type /Page >> endobj 139 0 obj << /Filter /FlateDecode /Length 3730 >> stream x];Wtl`e>{fglf82p`:$%U)RjI7QjY,#WtΈ,:!wd+!{4N'q,^?~?3&^zVtV4 7?7~(:P?N?N/ kC4!ЌCh2/: >2TU.K^+\RJt҆IKЂA{x3~xy>Bwp!$ ȓ6TW6|/hsW:y?Jj=? r~yu{>8\2=\a[}6\Xţ{A/-z *(}l'A:?ILT>4NPwUtT,'w ޙ0Ҏro6콎&Y"MlbJKsKZ㏺ !^TNx>Qatb|u]v о L&;*1X& :% rVL;v"OV D4΅htc2<)߉$ɫ aP?Y9mFgg{) i(yF6~߇'SWJ>Nz?|Ok}_ k5㽑G9s*KW>NFxlBvVersRF*=+2Kvfrٮ|XUIg:5ዬ&2WYY&j/KZ]勂zKVYIm-fi1tJΐP+cllMcV#<@jMTb~|UR%]bIl18@i  _Ò9$C\Y MU;T'IJ30I{ 떟$k!k1 WIfUZ9AΛ3Ok;ͧj).|wSuO%/YlEs?+G'!uSŗ)3܏b?~Ei%M3\6stw S.Ai_gъzdی zyW]mh8X%J{\[F䱡ᶺ/x5[|t\*bdkJdűRBCjΎ^ GOqQ#wƒFQ3΅<ƵpG6q&bPu}H=ʶa!.R eNMR7*'9VrDٽފmBI wz t*vϸүQ{^^DęL1&ǔŨH[;0D, ezNjZaxYt*xQ~V0+w$ ]Gv2٭NJC rg rd괍;˥r<}KVၱf'J}B_m54vϭuy]- O} LW8 '|Ms{Cɨ|.EGG(\N;}oY4?!C8)!98Fg6HEN>{]j@|hGQp?E{6T53v}m~&B'A~Pq\ `9N1 4e1Dj^^V,DbKǩ1"4bD>>hX j\lI #"+;{p뿀GrvVxЪ1~0rW/=D!~YSjUc2v=-ԾִB@^žE&ۃVuϷô/}-ƀO7旪DcWyP>5 CrnyCU,wi3_侅^ksxIk7yM^xZr%;R7~>W-,pe;^.2C*xRî& ^a"w&h^^:׻,=|a{5 h5ȠvqJ]~p>I7?x x OwUY0pNf0?AH2lF촨;E;\%?x(連Z(5P >c*>!<'Ђ|6s^4]{4 SF{C~|ܾzt"1謀ZQ1Z{ʶQ+ x]NTn2̅itj[>n&^n&L x32_dcY%QN +$f*p\E>y ČtݫwW&']a΃^2z$XwmoQ֩kZobpK"P> >> endobj 138 0 obj << /Contents [ 139 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 140 0 R /Rotate 0 /Type /Page >> endobj 142 0 obj << /Filter /FlateDecode /Length 5165 >> stream xn캱Wp7| _HtA7X_Di8HiW+!gy]b]K-8^h|a?|'pa^_1f-{ߙ~0}9<_&gq!1?sOT,3 \&ϗkI$3.YJCv1b@pןx|Gp,UīQ=׋J̠;vp6'ÿ^VF0]&:>9$N'?m9J &.PD#U):̠s[GɤH)?8/QM8vi_4pzg?9ŗLO jeo<=_U\a+#'S@599a)ȏd`"%(姶f ?TLg"'6#z\*_BZ Ҍ{2 D'a%_Vb;U-B=ذزoz}+v*)oZXLVXMDD#þb4?:憻 wOҷHZOe}6/B<=T 8CqD| H=@ w=y 26h.*xP$xp hB߿\In&7gQ>=[Pn6@j`=(@T ` ܨԦV0Ei( ::qyUCH:u %':7r&)OpzvLJ !=xU3 D @3vPcG 1 ,:{;RǐGGGf3Js܉|Gn$RouQm\$qhYt%¡~ vŅH7mAAS|h\`ۖ;K;ڦ|ˉ;BQIMi8V]KLh8TDHlakaDdyst:*kǪZy $l"H" t/-6T2: 3x jқF t LrmzlD4t32;6f2;QR< &M`=Ri`7@ +̓+i`t D;.Ưo8^\*m*ZG` i^FϰO>7x4_T%QEE7Win[d1ނ?OԑKAKl]hh,{ 58 bo&+fE[ {?T3Nb}g{C\MhS i `2"94@;̎C3a5:1O/֟yl=7C1 ҖQQT}$4;8{S%OB^@cg1ٮVRJww-vDu&՜uC !f ;Qِargub^T^ZIeI$yLзCGT][5=F)[oA"bK4ҿA7N; ǙiHvkkJc_=٣%JNR[4"4\Ux͘zGzsw >nmhhTvR|uY4cKRux GO9%+а JLJOިbR$-Y+շ:mRjwV]HgyDS&ݎ6/'oP?>>UsIfYȻ.T7\y J(Ȩߙ^R|k(k!c-jG&ud>[SFrJV_rolrM 1\Ĥ\ Kh{(F>a3SY=^t 56+N+qWicEOG[[l ]Yuu^vMoLn] L 9j<^ S؜ "*5=y/ X䷠=2zoʴ11&s+ .*DhSYX^;3N:u^bnmX%ғ[*"xh|?ˤGH`QJQ2376c@- ;#Vr>& B%|u8iteyDȱr.opGy)7"*aIF <?z ͏ہL|:EI:8R]r;\)y:{^RL$Hb_M1dY h jiגYM#yV ƓRNeFjە,[,AZmJņU;B&};׫[<**u菌;m}mmEtL:"(![j$*!*-G%( VAFz$ ,Iwo37$[p%Msw ObM^ؘCa>" %_lZw4_rPx4W]{>9+N[ fs(ĘH=;+kT$%5Ю?$5L{ؒb҇,wTXebgNs8o>#>,-}T_=K4y{ftFSa6{:?zͨ<"23Fy ]fͲ|rQ߅vCQ#-7!VWJ#ZHPbs\02)_/}/0 #<$;f:yy|dž<.fNd#ߛ^-`j? ˵.{_^zI:k|D$T"f-ZGד~Ց*zj/.}ޙC.ч.mP9wxmv4z;n4UZY[p+"m[݀Y/Sf ws o6[WKO0Vot'oՃzdשM򪇅q[HI:ᚾHzXfӅ"e(otJfK=|凫wݭr.hX`[fbSvfgh!{:nP/> >> endobj 141 0 obj << /Contents [ 142 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 143 0 R /Rotate 0 /Type /Page >> endobj 145 0 obj << /Filter /FlateDecode /Length 1914 >> stream xZ;#7 +\؉(zk{Hw)T.WR3zόY+4Q||,>{1Qy眆K!}߮Q}'?v _w/Фs7b0g}o<蜞ޏCډ<^_T|z~OH_w/R^m{1h mT?[C[ ]̿W5]`XCł,X@HXQ/%e DPh߇ <׈*cA؀y7##G 7=|PhZzshh cýWօ9hi<*G`<޿U xz:i-m>e%łmݻsLmksƼm=~m Hjm[xFxM31(-tq4Ӊh*H#Q۲Vimf/ʽ>C Fuyi4rAڂt{kc8?yu҂"/J^&mkYFzjt.:e&}qJWq2 5L9b ch?˲%QB)xQ6L/$C$J4 RV^n=<~Li7zژbԮfTa  ։ECYy\rbCOާ:?y9&Blo s>i䁲V.,᭦ycMa%?RSu(RH\z&Ǎ)Ipl.tk͋^2/qxxy0HڟΎ.t=oߋOI ) U#g}1d}e_m{p::\OgI](rBjJ^WRzFmBK~yϕ(%9[9UUzիVG7%W{?OITP$,fQlB'SЁXEr v,-PGJcUBQ$k$k9qoDxXMuW98F`9:ҭly0A^`<"jS#)sM|؂H-&{r&K4,JǮ\X- S1@L ĴΖ)NE-1U5DZ 17:BV09!a$&8B }\—[q>?.e/[4rݚxddј<~.ǹM0sN5`F.zX @!bZgS6!\X]ky-! , 2taܠ.X,\S?Af\م/./RRNre50uҞ/o7gV3cwpK-XkM)*xe%(Rc*STRVGw Ǣj KZ( Qr7W@8# p\OQ9.g!XDGo[ka4][?_3+]A/Kl doS(: E{&:}B VpXqT:s"oyj JΆl*^ΜAvt H[Y| `%ļΦ}b7@?_ uH a yR LBbo OhSt=DfN!vb^gKn,r ԬMT/ dEA]:lȧ][wkku endstream endobj 146 0 obj << /Font << /F1 27 0 R /F2 90 0 R /F3 117 0 R /F4 98 0 R >> >> endobj 144 0 obj << /Contents [ 145 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 146 0 R /Rotate 0 /Type /Page >> endobj 148 0 obj (Identity) endobj 149 0 obj (Adobe) endobj 152 0 obj << /Filter /FlateDecode /Length 8473 /Length1 32372 /Type /Stream >> stream x} |Tŵ&O" 1y d  @ 7nh>>4JԿ~l@1X> VDKPEj-dߙ77 eΜ9s3gν{7x"yQn"5mVIe;Wf5b}Ӭe!LUYUߚ YPO@B&V$9hYj2zX޽f۞8~'VڼnH( V6W!bfм"N$螽8vu6֦H7l3mɶ=Emi 7ۚ蟸]^_WTB8܇?߼W`Nθ/Ea:rYq()wXUY~?Ct Z/KҦ% &&k1@.Op %m J(rR 6:FQJT;!:`E/3 rIff ]YF{Jl+ K$?IBo[f`( 4PJCi( 4PJCi($AiӿeX#.eaa ;xCa}+y"pmJAqϺD[ 'V[W.ZX`sfͲ,a.>ߧ^7&_;1/7gBvƚƤS ât(PٲԔqrf*5&d˥)Β ٥&K_~, SY'l~Fg`aӐkfiV8ANb 0%&,]hE|KZy3xe8VӱY+-5h#i=4=!ڣ!: 1N2ҩtÙZiXh--IMO=k*M0GGrr3۳n0@mMVd-mJ[[oeǛJןL;٦R:wQPܐJLr1}IOMD3| ә~ȚR}j1ɖ֚V[G$L11Rt7TXQDG;R~CLVnY4ןNR_)}Jjz\"\3[9t;:PBR6usnIb---5&۹V8nTo躑M=nj˭2Z5 tva]Z {^)>MEqu&䔚Jk)(@FGe)7 b3Vڞ=l58a %|2&?T]fViCwQgN-J.m)QL`L { >IN]1'((mƚT;zٚ7W Wjv<8y,έ4]:E5Di`qĘ @nNT H- #0PTF3RG=J,fuK`U3,5:]I)6˪bcN-nm t382l59L&7WXؘ{UgpsGM,tcsw9oJ:?׃ղ^ͳVine+nRZ>,SR^ ^ـK/v-fT&4jrnO#u=sqk+n7M dSR^6-ά)nmֽ2~hp*eTFdUEXqԽx i"'z]NuuPfPepEf B, *uV^K&j1Œ2 )9Re$F- f:!U`VHd5lPXEL5ٻ!`tψ#%((`& (ƲXa}ye\$_Td?ddxrq~: } 1YquFO̟-'3fFI2[}KNW:Ot ΂ɖNKqI$&c U'iǫ`F׾'}4(wuTt;Z:sZGx)"?UTSĖ'ORgv d92cOc~!=(}_{NObqYQIbG3X2y?0 'Y-Qc,?VhF)BYji!L~۞&h1oKNEν w[?MbEb2kK{6FmmնVVy[S,yw[Vlqm6f͂y!bx Q bbtd P_Tp]nASFpMqIE$"R0 W'ę G8`6?ݛ;2{{bU]sȞ/^C*qG_ >&j5"b# I+ ȊjqY]bi 3'(ɉ#L9xJL.ȟ| ӘaֽlZӉ>q5ݵ߾x4L ?ew_z>23e8؈Ȥ tQua>" @;;LmziRhtZ?BLrW"S'l\'|.V8%$9W1l\3t ]C5t ]C5t ]C5t ]C5t3Ѥ9>~Cl*N!7F" baG p W(%U1 kq*N`SL~⢆G4!&x$Mu)exI?UhT`:eA|8ܨ$]aea.m>\r4t̺r~ËjC]Qjq{^gCJkk^^!6mi56éhrcZ4mk]RjnohF}66jrGeyB*@cUbD'z]^pؽl8F섊]ؐ]4sN]jaWlq0Wݪ&6=]_q: ܍6Ji8}>5k Aɹunvlj,ǙmfsO3rv>ʐ-wĜ tU64<+sXʡ`%f;ȘmX!V.xցs9*C&Rcy0 ˅_{6.͐Ѽ[ZQ{g#6סҁ5;BL\jy=Mytqi+afC%GyxtK_GJ|Y܄4_r4295;dW!G%=٨}\[3Z܏y|*];U݈p _ؼz-Fܫڮ^nF:;9n~cR<޵2v5<96ͪVYjc6q̭ee=c~V [}xSWEAZ^c&} ͞Q^J1<:[0_أUrXt3{5̿Grkttyi#L#׫̭s+iըjRFTϣKK7>U[dg? (¾,*G]_ٸ]j?7-|-M|58ya*ⵆ_9<kN]!9͹\{B힠&U:ZYuĝrr/ l &=(j>cX PCr˄~$Py-kU9//?/GRE"]bn 'tO{RwN]'P{QD-Z4jQT%⋣u}2"( [iX)N1O=((]“i$RĉL韼K)Q)Ӓ9nAcxR.#^Ω7w2l1yˤ3M19Mvjzkl"g5JR^])'GE&ޥXN@oýp?z>zNW)ĐxBFxWG&BRL|,%54YKn&I+i#y.$$jz rOTJį32U~m!4][qxtA#3j.e|<g^4\`T[Ϲp3 AxM6PsGoq7 6~ C|0efrGv!ArL oN|FD EM 414It*5S -.vz#uVI{AE;~}o}zWB/5B0Y(2aXX*B 7 VM. GׄwGYpANő,fb8E.s qLb׋-mf.>qxlX؉ssǏpiI|縦o\trR+黏xF~7/xI\KKx %u}a<Sk[+ǿ0سRx8Qpgv#!]dgB<6{r8Y#E30\wjl$C{S3TK|i' ($___#)% ~{_^*`f`F.k1x#f7՘ٻɷ{R<냡`&c9~opǿ>>U!( A-\v>s'D-IGҩ!(re,y_?H@y Q,T=68H9{{R8kZ I@e@ص}ktz̝I~gc4vjpq/|HxT|Mx\xJo#cSxFL<'^GUE-0"f71>Z~Oq3Wڽ@bV3.`O>_<}=[c/)J,2_el<&+mc?sTkD«q;_7jAs߂p0G]6z.H@M7&ҿ5dh5̂oĂcRzn#w6#o'?!>AZyb??h}dyo n٣}$Y$ KT,")Z9A1[d?0?@?Tgt _$ۃg4cpg2Iǡ " s<Էjx~oW}{-͘ ~mcށٷ;mOcf>}k0 },Bgaʀ9 qM2Ux?:%}@p*OIHJ9g_o"0.p|_;t{?a4.|DyW3 |^f>7֒p\a gf=[!/d{E tBx j="aW ;(~&Ἱ%2*#TACQ~#5UƆk;J6B:jNĻ̱2de!{[?u,V3\xa2roa~hŸ-O} pۉ3~ކMq$YHagq*- b}:b/91㈟սl;Ďj^fn7ob!?`cc<H x6ky5Z{=~ b boq,5d(BAkqK/uo endstream endobj 155 0 obj << /BaseFont /CIDFont+F11 /DescendantFonts [ << /BaseFont /CIDFont+F11 /CIDSystemInfo << /Ordering 148 0 R /Registry 149 0 R /Supplement 0 >> /CIDToGIDMap /Identity /FontDescriptor << /Ascent 832 /CapHeight 658 /Descent -300 /Flags 6 /FontBBox 150 0 R /FontFile2 152 0 R /FontName /CIDFont+F11 /ItalicAngle 0 /StemV 151 0 R /Type /FontDescriptor >> /Subtype /CIDFontType2 /Type /Font /W 153 0 R >> ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 154 0 R /Type /Font >> endobj 156 0 obj << /Filter /FlateDecode /Length 3874 >> stream x]MsQ-E4V)(H=)6U{qiUc,$B~ ϣ?O;?Z?ϓx$,{$0sCOV?&_ko~" Or-kRt,L͛_CYkǸqϿ?O 7 <8/_7 Cj3xi C@_UIsc $A+e))%҆I 0αCB}t ᾥ5 {uφlyTXpFZ +Ik,g6~&`BXyjoXA~:/Y]͸鷽 g.]j9+3SVi`a/xt-}9>ٍ͖toDۢCVfu<1&`aW!_ B K T/8@'sNg8tלCzr]1~V/Wvu|5W'dΥ0_L~ yah~=b&sTz}\q{#vݵdFz frm5uQf+]r]mr]j$0kH1Áܡ,Myzf1%;ꇊ'\f!7B]L񙠋'.6]oe]eyKii@Vh{1'hI*ܦ+=heAJ`&eD~\/5Xwƫ*a1[.ia'K a>KY[ZUҚanG=^kؼ;FWSWڔ3ζjqU*)ۀ7rŤI9a-ugHyڋRiJȳ d|1Ƒ 9^(鬶 ՍAaU510\0aDz`O?4aIC_+S5 M7{5RqvP'g_H c1S?+cM×l E#jeSS?=Y4hTb"Ѷpkd(> 5JMu=ny~z҂._prü(~H̚spay^~SSOYL.ɢ:~3^Ĺ_S;WۘH]-z~< ?$bL@{V>#=eFn`h07UڣF@h]SFm|kIകDˊhx;"Aܡx`WD\Ν#_ ɷzgF8{,:x1|0㗷k#I<WQ8K$m*)F6Dڕ>쬈Zkx wOBFP%THQ݌jv3ƯrT.yA䠝Aut1@wN3`N`~(σO nBAwf4aztkjIQo#Ma8yO^| ^su)+;#i &!abBHr\+9ݎe{ёXҾ D6LqTˢ.qoȹ`Uy>}FC_7&螪ݼi 7\$hpڽ8sV/h?NrPLi J,_[!ęr;|gl*X"0tu^|xi.p[Vil9BTFEx)X 6m?+foHr6/.{''ɗP1os2PuR@M1@6ٱz+l>3+^ƂuQI`֫PhET|yquWK FN:h!}vFĆꄮlL*Ln,ߣa' È_ %%6H.U#C#SSlȮE3GQӂK&5!ܩ2 Gatt) A:j'HJTߓ(H_W<9_Fg_x%oJ H՟]Jn@: ]BOÁt>qN/*(`,5? g.|E:yKq0%q:%5<Qfނ(N8Av(AwCaqARr),.,ދvBf(<ޔ|LBr.hD!j'B+An,&$ c1bj',~:Y[Dxq/Dax0P#E mQH! BHh6N6tnH 0O橎y]+t0]e_MB4W@ý !C@!( QyOBA{ ၀Bp E[6B(@a>} ](4tf@w*6q:DgYp󩅇8e謌謌7BgyȱQJx 8{<:Z1>-,+ : t\}%, UEYL(,"j,~zx/ ~$:xSJ,knoAgY,: tYEڻvPUz(bBgmgn0qVx3:gcgyoBۗdBAYY\Fg),Ȟ%=K$Lv,ڇ%& laR3XKVi] |ϖjAͽk)IlJ>0pӑlx8T>Ő$Ԏ23! 0fki-f si&axI~QŠ mR&;GfmSY/XK {ƾ``B"gU`-rb tWL$Z~ *e/QI}QIj8}=b}Ӈ7c/Of*j%;^hㅸ B_5 AݰO_Z Z/4N{u?/Nb~Z_9q`D님CzM #+TLYAd endstream endobj 157 0 obj << /Font << /F1 27 0 R /F2 45 0 R /F3 90 0 R /F4 98 0 R /F5 155 0 R >> >> endobj 147 0 obj << /Contents [ 156 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 157 0 R /Rotate 0 /Type /Page >> endobj 159 0 obj << /Filter /FlateDecode /Length 4129 >> stream x]Ku{BAF-sHW"*,DEc(0w^1PTQs]d]V3C|ϟ{(Z_G 3uxLq7!(a?o{~?7aI?}7Nq#j`~KL`uT*աWsVg3@_7:|dQ5+wuZU B{KٛD|n XO*BcIꣴ)ƿE;PbWSRZPI^t!M(o°g8']H$|+,Fui̥?e~MSA,aO5ԍ>^i˲rC`(vGmNyd'A5{EvGη~||~"ʱ:~!涡|);}>0{th*i"Vo0f^F,7XՏhnqTC\Gw_~Aւv .NpdAw|(K-} oPkJ *)c[#F6~lY x8xrF26.I'N20ĔOD%{ (Dr n;>%eX3AWErsv|'Z;K;C!~1͆R!eC =(ZTrX-]A3Ea*p˜X.nI=t/[rK ΃Koe= T#ʘ!c/r$7 x/*oz]sR[;m8_=@7H v8Y`[CA_e $29C>XF2n|q:̏-[,+>XVg˸+,nOaǧ=,sqEA= 7ޔʀeY0`# ,'1rvBHc.nd9K;!qg( l;Nz4`z)%coql Xg2fm,ڇsF ,#1~vr#,'qiI$1˶x w!s}[;,iπe"3`Y>XVt_XVLӀe˒:fjyD:40UpÊ<0OެŶ*XyL;DxUҜ^~^xY)<|jOW^^u,s\J(EBvD3h6G:BB+tC@8 "vqm&AT[^بDsHXFx_I7)FWC TpѲdQ';*0 /OEN[`"ޚ:KnA_ڿ!|0ߓ-XyS!hQ@u&(J @GUY^e|ة|n |O*L* +yWۍ@!" @]/E| ¿0Owgf0~ˆ9Ҥ 6Ǝר6S ? 3ךBI i ;#r=tjlHljEz 2BV 0\5`J~/ݯ+ݥ(B9 -n0SdxEg0$q/)6H:<P D9_9MIDB+a&a@$L2j⑵tfP[έ35:~d5x.<(qv-;]<|B*T=GGJ5YW;:`j)ptPk? 4K+e;x֪u0:^O[ˆZ'"&?u|H/stX@۴˟Ǿ86KN@9k7UT Ƌ?3+{L%exSE4@`u8/zPNDNM 76S$;],ḂI$k:OG~eE(^Q&ɦ4E3;eǰP͉J+A[zL;`oytn^Mio"}_N|&PRdklW@bhJ[#ʌ5>> m5hmC+ 釐΁NÝ=.^ftщ8VqQd캖tz\pk#':;P`hҁlʤ8yN*WwHZҝ(g[7CtbP*LʮkE[J+vf4䍮L7^3m[Qئ&}^KT-p> >> endobj 158 0 obj << /Contents [ 159 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 160 0 R /Rotate 0 /Type /Page >> endobj 162 0 obj << /Filter /FlateDecode /Length 2027 >> stream x[n+7 +J@ EwȮ@_Di=N2RG!u8Q~Q*奼n"(eC/=>UiTP?}L<:8syw14T =XH|X i인ޯ;spޟeD/fAx:up8`XjMce|C0AwiMG#OHkWWgyPH{6 w&HQNE3FI ϧt3ҋ3h_lT݆\PD !g$Aqہ&Mak3QE}m״LUEǷW=m".'Pt : ӐvNJrT=xy$Pm Iu۝v;CiY*9g{ʽ p-usÊ93zf[MR7:%)g>v9-GQjyBL蚶GA&&$E5+Q흤l"la휦ps2,< ?169:="0XKSLÚIչ; [0@~j9ES#{1d RsO%n |³3שD!叓Ô]N^MW'^7+CUD9D9MU/Γf°c\A14cCcPt $@a\`1Hk礞! !ir͚#x͔{.܌I儉z\&cRRJ*ά!?auIHiDL60ow㕦]鯘_bv}y}9|E| #mf|aeiҪ(?w)gmq]h6o]$KK˯λ S_zYhGTq8I{iTq*-/nGQ}dOu6xdj£<"&pj5Tv.xqev]RvV Aƒ&Fc ՉqG4a tJ+;bɳkTDCK{Md) օscR,) RX傧ZjFweS!Km<+>%L Uj_ߩ;z+xGR:B1BIڪc|LLX6ccr̶swDgCoPMTsnBh} f~!q< nU77Gֶ6 "1Ya) <,V۹:}MYu+4+:EJ~VEH*vPf3IG*^3>\xTPkUY-û-k],6 6(_HD+9RD|(SMb(Nod0|MGHI,|\HHUŸ.NhmE Yy0^摈,'r-p5qZAr$5nZ@f`B@]-ӒDP:$SM+qBSϤ3by.,%G9s $SJRZ̝ܳQqo0rJD"qhl-"X[)sYU]!Pwt9Fd(P?_H endstream endobj 163 0 obj << /Font << /F1 27 0 R /F2 90 0 R /F3 98 0 R >> >> endobj 161 0 obj << /Contents [ 162 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 163 0 R /Rotate 0 /Type /Page >> endobj 165 0 obj << /Filter /FlateDecode /Length 3753 >> stream x];8+FG{:;\t;ŶdHmɽtԲ,WObZHs(Q xC?qǏ8ˣA8_w 0s 81_鳗oR;O׏tzY|S?E%?y8}~[FFO5O!^XE:5\4UQ0LUէ$fSPɁ 7Rb_`> ~3MBK8%@n U5BP|cAZ_paZV̪ ZjMzjZL#5IJȴ2 &4;RЈ6͟?I-)B^M M:yHoz %I(*))i$n$pԕHTGs:HYפj}sNpN l*لk@FlBH-CbN6 3f3,IJ+W`MB<(>Q /@9'1H(E IDW?OSL"kQ(Hq,@NM$|21<(|˻^;ZRR+= $4H122d QRM/|/hnb\iXYa{E"q3&Ax` rAO+MXeTcCSt^5t>Li^KH!4e50I$BR(zvq) 6 BJ,>\^o?D+JGxnܰsC湁X@˱L"*\hB,"Qd}UMy/`:V\YρȊLSƐL JQh$0ĆH;ELPJAQ 9EnEG&TNB .t"eZ5¯ΰ'yKS.P8) IWhY֎Hx<9FFA7k&*A2c>#J1je;&%/r 9ϘQ //a';H%DfpQsiF ]Vޗ^'-&`I"\Ou2SMOMP3صĿؿQ6x= P/tfj,3IQ+h23}r&,Y',Z㼂;3vEuOw-6)y"n4~ѴnkB93𞚅&x-03o.] VX^ಸeK*@g@S|; ?\SpzP6$jƣ\>ZbX>4&E`"4En5Tp䃃'Iƃ&0rN6PVɿ+GڕlO)5< =2MCHSCPVVVOM˨&. OЖ,O,N,z|uo/ VREE6#ZiĪ;{x;JXvukl[='4,Ƹ6Zs\kN̑iIAWzQn`sGld >NX+TC敥#XL`6^#B  y vh^1m"g}%FU;6.ّ]! -|1KJP2s,a$1-ee~Y],<2sGhs|>, ~B-3XOEV>4hYgUm(wBgeoͼ{ʕ+lCZzT*AǞRaϦjT%=zw`}q|޹O,4o6a.=-z\6ۡZg+F=;Ce3Vt0ztiKoY*(aEm\Ox۹[% l}7BX#TtIѱF9q-B4N IZkZAA1>}Z=)`9"SX DYG#n &pJ5 +C~!('b]b'?=Љ<_ #Z==ȴ #%+WX[7Bl8!g xgבJ@$W#[6A'/g Hp l~ĠadrH1m@PT~&TrƋEkH4 LJKDb4&־2}w zgN=0AM(h24 Sn3mPI86j^L̇tT'J(No%,Jddߺyk̓颯TCL5U܍\3mߨ-c!O_nK{jƊ.9ǛP%*O_L3.Hio+D&>JmmA߽ J]MG:$=c&{W.E(ķ0 }Ou\{|쿧? ܎@F/ЁS ?gj^sr"⸜d  h,y }҆ʸ DՔM>ْ㣝A3X7NUTr{K< i֭ <άyŖ$v/h[XQ53kR+|ˏY0wv˜fwOE223b  !Cr QMev*<]@ E0sQj~7htg7fdIZ|f4 &F)|~ǾPv2.nj7(WިY?a,Z6iqɔ5ļ{wM5ac1 tVdeMx JAٝ{!hQL?enL#ń+k5sVhl> >> endobj 164 0 obj << /Contents [ 165 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 166 0 R /Rotate 0 /Type /Page >> endobj 168 0 obj << /Filter /FlateDecode /Length 2551 >> stream x\Ko6WJrEN-[S(MdGɲ%r8oU_a4J!vP>tA߿~yG *.w|렏6@ptjp0ƿ*x}}x9^/vy +c|א\;s~mq}}>>' | )"G<=q|~(Wahfj@\[X=Dq7ZHp ;s2v ϟ<9]tfLVk:!0DsI0W0~J,o*)f;'xm'&D'FW7 ĦT|C&osE)ZJlie,G`Z&ۅqP3V/>RB778Iit,=% 7*Ɯ`W8Rų VAG|`ndr3%J??9R2^˄6Hrӆj[,T˸jž]Drk59X{zMY,ӫEuM\ j Y82*$l#prᑛ˃QRrn[RE9=a0)!e J~ԴX(t:' ph$zB a60A ^>V^q>P/)+̠GՈVH2&PyŢ t b@9C<=g2xK5JөQbC(a3lЯ+vn0IDO\$/lo&_xV<RGFc>['_9L8YEp͂$ƶ4sn 4; P5&$JSGZ2't`h:Z2]=CjAW73 3&CMɇhs/3OlEi<[PC EK;sqS\fv\?t\?6ń_ Y3 a3ba84? 8>t|b//$, \>1q䞁V<kF+]5:tah @:@ 4b. v%~;PBG,Jƽ6B=VœV)d':eGJd֮N4d!*Zݑ3 X#x{n/HbX,'-1weɹi$7m!^'t}TZ5(/m5IK{/x0)ŕI)<6ȩ}>iFeL~Fboh> Dxj-뱳xn;#iCҸuPG~NT)ujݜQ7VVuye8h]9v4WŹ|[ K.7>ɒ5xFeQf&>fkIM¸YRn[I*)Ή iD Zκ3ͺѰo`o7r%\tptkg:5rY6nf0-TdQe@}.qraxo0.Hw[f=lFOeG;F;\Ա˅lsb#n_(`|FF3,죗&WΥrߓAcq:/V endstream endobj 169 0 obj << /Font << /F1 27 0 R /F2 90 0 R /F3 98 0 R /F4 117 0 R >> >> endobj 167 0 obj << /Contents [ 168 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 169 0 R /Rotate 0 /Type /Page >> endobj 171 0 obj << /Filter /FlateDecode /Length 3655 >> stream x]8+WG,L6@g؍&`lE%M3-[z?i19#ILB( 8ϗ^ʇnߗ_/ogy0 /uS: ~>Y1Yϗ#~CaxqH|oQс__>>_]S®, 봃 ~ P"oS Y]6)ސ_M@Hj~~6d3g %P#ޞ j0 ?Id߇dRd$$W`~L߯SG\RN0.@>#D)RjG" I&'oR:!x/}UByj4jJNi݊k=%mKkI*,ي&r]h*ѥiQF>N^K4`ۅ%6W5B[giXwuQPmf FHP1X6SLe|R_=ĔQŤuqlXۉG9286/JẑFrm1A*13!8k7'vɘIG}1Ac&6J@1B`K2& ?Mᇙs@͞xJp"}$ʫZl~`821h(8;q.C0`fS'4D۟? 1Hb6Y*\N 5i..jk6ƛDm4f: {Wq5ӳQa P&n,@Cj/tcBat/pq@׉ӢJlLʔZvJXu+N# pw:S : +(@BXpNZZ -hG\[_YZ֤S bH^Okqq}bQVzskLXIE.glZdmRmõDk=X`;yce^ xJ𱽡퍂? a:J҇kep6teH1RviYIÇis-+P\uk -y '",栰0%/RGČ)M[8A5^@ T<K*I~P޶/%:"&v@)k⊤mxבX%" dCly$N4̧B(jfU2 UvE?\^6`RhؙH4Q$.dv\@"=i:F=k b . ?1#s0PX4Z%TmU sf:b# ۚB4`b-׽ۿXZMԦZtUpa \8 Xs`NNWP|xt++)ki}zsC #DD Tݮ{yYY*' b1\M#ZEva3Y@'R SJy؉}@\BV3ܮi2G@>Zo1_TӴVKe(2{Aj؈S\\R-^>jV!L7$nLu׊zJ-$\ (ە GtGK k?驋)Ր麟5ՃU5P&G =͌Rj)ƞ}alA`N4 Pk=m:u,&Hy&$C&,#17+xUtKZ[=#G[p2^Gc\% \ 'ȋLfE2>%J-ˁT?1E1XAkP=g ((C8Hq8q4trź9-2( 9ًT*t[sƨ;G›" f2Ld{A@hU̢>|7rimf1^Y: 3n\bi~+4N\`$A{8VRā~NS'D #IaH{0y=cn7ע׈Ԓ=Mp6O 6^9Hou]qf{Ń.|11M\/HLכiQK H }*l̾eV׳hR''Ɛk%38(kH5Ig EB6{޽pKhtkFg|ϱVq ֔9܋E b:(ev{bUC(%ug?og{3Ej= lW2:b1 OjK3 X΅)q =1sf,u = (_ %KQ w!L C6\&D2\0\'_vɻ@a!$pIs?$m{["tZhƹ=B-ͧe.ҮG)/ep;Yk7-|zUq_%*n~1{N뢼}>u߿VnVQ̷=~Ru?۞α%(w~:fm>9JIn WK.~lK1mO mOmOӛIU7̫8è<ܳ[8ab!phMvߚT;Ѳ#Z Uw A;;o+ YNrmo>e`L\6֣ŷOЎ, ȝՎe{1,CjoB6reˆڴ ZԣIs1UM|y/UfUx`#v4֩e-(:CjUfzfNs9 ÃbK%Ia{̞!]+PpE=klr@O8Ѱ@E %G![$+I/Y endstream endobj 172 0 obj << /Font << /F1 27 0 R /F2 90 0 R /F3 98 0 R /F4 117 0 R >> >> endobj 170 0 obj << /Contents [ 171 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 172 0 R /Rotate 0 /Type /Page >> endobj 174 0 obj << /Filter /FlateDecode /Length 2712 >> stream x\nS W?p2,+]hgѾf<(YIoU}C ; rU M~3Y`ht~MDl!VpDzL+e|b xBk=ݒppz KH> /Ajǜ@ G 'x }~*@6~AD|Zqx`rbKAM?4sya_f=t9z&~m[- 9oJy͇u{q|O`UcgOwtC|r BUipP&՘*kՂr)5 5hj'qOYYǑ8AΪ#.e'aHN#np5UY]]^)׿A~A~򫰁,i)xUN̿ $gR+ؕɄ.! נm`v!8o[i 3):h=g>"?<ͦNOKh8Q,c6ᗔ6W{,C[(w, uN%K@BuR\|5])@N(MR@vHwQd(${)2|' AH5N\3WN1+7n!}G!?WٰZxs׻o; ~J/AE]"x-OabuP\(AB_{9<}'Hl0Ao|(ͿvU[~𼩼?|-"Ȑ=s8IA6~ߎyGGFt@KKz`r[ DVur!Ǭj1SĄ KFfpm {ԄM4*CzygIإ:|f OD;}kCGKuX^z󃲉:( ǖNՙA)Z*7{:N=uqbT䤮!6%ș:DeB_YH`aѕ X+jJdXA_^+YڧI@{ItaG/xaJD踍F뵑5ugSjEMNmqRֲM>KJ STzv5 )^yL7쎝kmFu;WʉTNuv3(1:UU0PLV!̰$披bbUbxX#Y_AFAT7mA%zF CiW*x{+kQl^mֿ7zԮl חWYC,Qc3};*wKsSz^mP-аXFDKN~Ra Bi˛>)7s$sk/^Uyj!J'_ZيS"Ӧ{`kZ' =fO;eU 1bYҮ=F |R:@xAKgAd&> (3"O50b<*Q䲜L=6?;.j >@M10z`\g$M(Ѓ2˷v*Z [\4bl{[)FUƌ-żhNG-0A|SGf) ?-B)tiXS*h>=7}wyVuAm FkƧ?ؖ-r&t^ܻ" =ڻs=cհ.dS_% cM UzY~6 endstream endobj 175 0 obj << /Font << /F1 27 0 R /F2 90 0 R /F3 117 0 R /F4 98 0 R /F5 45 0 R >> >> endobj 173 0 obj << /Contents [ 174 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 175 0 R /Rotate 0 /Type /Page >> endobj 177 0 obj << /Filter /FlateDecode /Length 1912 >> stream x[;6+Vp8|k];` UK\Efhyŗ$kٹ[HCso:/71?YՋ>~s}_/XW 1<}ek:W}g Mjx6/X':!~u_}N+;rOdy ؄~XyaӔ&+x@0D|¹`3>zzS^dU8N$6>:_/쟬8[ﵖNnttl}BQ~yB 3t+M$γQjو4uMNH@nCc2wȕdXלMqRdeY ,1B:ߐ_`EvtA%#tLq#'9Vzo8HRǐ`3)/2QZ,pcK3 #S14`x0ȁr- ?ŊHT*IUɿԗ_Z8>ѣkǞΗOBϥyjQ9s߫EP:YyL@ɔxa_rxLہ3oiƨaVA›~A= {`v$ڣ>y p>I'U9hPUske`kgw/;J6wNoİLDUEM/B .nd ~q {7S+cj5s' O<='3c2 m.YGzy']/u~..f4=TVhkf"9Hɧl5e)3W3a;ctH73"3#UZ]W2ӴHdׇ2L@OQE@yֳÈWydClzSc%XƦ%lf%>Q8aw%XYZ þ%}<[ʠz#aYh,gJ,.ARQդͰGЍrC1X&1Wl3–OI|fز.ilA╱2H,%¡!$kT9[v~Ca% =,Bر3u yB9f;;##1+Cl*6`لv tY=|_i 'SN?.{y-Y*Yxb/ voȑ7ZvV b?!zZ@~9`pe9`R.3;Ȇw ۬")D3|K~z=*KP.,t>|X'欯%/ݼ`Ag(TxЏPpz.FaʘkQ6MQ- 36OF==Өgf"*`c٧oUv~F_ԩw#ߺSOxs-+&;W-)ܿ5YI偽ys,={Ry={ĵ}<{]zyOOԛW~GD'UͤC UBsNw]]r4тsM{T33XߨΩV9)϶5jsjIxFP+ 8[ 1Emu-N&E2-PcمF2V3Ԭ4])*] { endstream endobj 178 0 obj << /Font << /F1 27 0 R /F2 90 0 R /F3 117 0 R /F4 98 0 R /F5 45 0 R >> >> endobj 176 0 obj << /Contents [ 177 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 178 0 R /Rotate 0 /Type /Page >> endobj 180 0 obj << /Filter /FlateDecode /Length 4281 >> stream xˎu__?8ٍtwQV&Y]dl  v^5p8U b^ȕj_KP_^Ͽ./_&u/?Зx{Qv"U2c^~a_oO7}#O>aLt̢1cDKþ.㛻_Kb/1va!8Pϗ|&rn_$Y-ڮv럟u;6G[V&(jPXB)8-TN=N-Ʒv2x>l98+ {?OO#a?txnyo]p;BRKsc3wb&;hYe:.Pcͣ(谨q$*E>Rv7f+ jauqjRa `O6]G.g$ a힬N6/ `US'â1=t.:HA'Wo`ӷů/~+owp!A!]}sO8Cݬ-Q+,כy,y|>-bn?먃 2?TX4suBw-R|#3x73K>|bb~GԬfN^ǼLޒy|wŗhopF=G:e^uV0.S$3M1M ݏ*U| bZĪYe\Яjɮcx7ı.QWru|/[[Z-sPŸ3/G8#.1S0oZlJ|4r4<[0`udtgKE}4qI<:zrm>~%/.Vj~Dx;zI5LL7dca*/F"o,LpWD9;(1 ݹs˙/E &2sK)a.||)50E1r0|p'dt&+R=|&5WpLZrK3@xڰrOZ?f7Wޡr8"i.Gv>%caWPރ}>29L:\:Y-%>ޝ;!ұ;2fZbG+"^v8qeְbjv鼝`܄@]X>L[݉yϳ_! I.]ppWp9 2J gxledVƻ!`Z6jp$h?/dq2~&e*= &lm@2^B:7.ӹsr3 nhuga8a8;~,r;d<9ML'˘z¥ǀ8UƟʬz웰2EmĚ d/5*:  <]U;߹y'{2 Se̞'i  G Ab|v)]h K@ d`\w{IUs 5zDD[٘j5Y:Sc $1 2/dq^5WQ pȬm@^ltRa4*]&DьҊB^0:πJŇ*¥~DKm0!RVH?o+&C_I~0㉾Io2G+([`/+;3Q9Y#*ߤt0K@]vhdWIU+M:rIYV6^U_c~Rgt6qJ[ f/+EP>^-[#{TA1Bb2hTwTL mfoIWF{ߎwKaS|y we)fBr]rqP[ uL-5vGY+uޞd%檄8 lJzV!󆬤 4p"dDg'u*.:!* >c\Yh9&bWfӧEf,?q6g$ !k6[e[XUЊj^H$uTX7+jBQifp,&AUl~ˑoV}QtT= ƞ!DrHlv+YEԆB)k=@+*~;IhOT-=QgqS@`\ v.Z:y*̪>NC~T1:g:e ]XoVU jk >]7C*zN4R!j#jU+|fG]>>kQطhw AEҀlRTkqPȐ ʠqPTq5P^3( L%2UcD!hTԗ;pƑVnZ9emlU{6Zh[OIij9c!&T`VGJMj=,6(t,x[=JvZq',n]h{oTKꐨl?%e gPl%iݍBWѱ.8'h''.`_ҟNim(+#Nh]b"RAs'ڃD1gPN΁'Y.ͨayQ 9-" vٷ)HXBK R%OJ$ɹ~bބz mKSQi!vO2Mnۊ)?ôSެƒrq, pfL-!\# B(dOX*DLw}+V||[)/Óo!ؤb'haɷFAnȒjP|\8U Pl!ŌYRfaOʬx&)BT.Sq%e0I1#)4zp^BkaDcctʝJlˎ;|; MF5(&4JM+}^y\ϞdL\|A^*양=n4JU!qK63S(7-CO)YB$d gl+ ʦMfپ@E5NqwiB̼C%ɥ%7Mƕ=@(2٤1z&M$:??5x>BR倄`>KVi9B}M=ݬA #Ѥn,OK!Q%18Ny O23px2+շJ6)*'IvrR\;%<'h3®a׮:K!R.+{j ,ǩytC@I(KsB# 8չ1L#RV罿ʡ_j}Ü9}KE*եOebUh8gp3rbz6un]$xe53p/[꾕c9}ID/p1!7\x&sY" 'Nw4-7"RaiVoDV*0§yv7j|()Z_DAgZ}Gw``h2q}ZgUT=YYVfWvJ|41vN|"j61[xī+^u:(xW}Q~S}WN pSg>J]ݡ+7SxÃ?4n].|,})~ *.VǪRAw!Y*CJ*TXa/}~q.A&WltX&ãAve-JhҪVA6'ox GA/bѵ%Y-ADZ`HPٕMٕQVԍ("jڟVt!T');'kO)Dktپ= pY, .R: ^_Jٝ24;ݟr2hp";3"bD!1( a*Ҡ©IASm .2fV3bzr e̷#jB(s endstream endobj 181 0 obj << /Font << /F1 27 0 R /F2 11 0 R /F3 62 0 R /F4 90 0 R /F5 98 0 R >> >> endobj 179 0 obj << /Contents [ 180 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 181 0 R /Rotate 0 /Type /Page >> endobj 183 0 obj << /Filter /FlateDecode /Length 2101 >> stream x[r6 )Q Lćv&NOiO{G$H)SdߏgT;A'Vz??=@*9x }.'n\&cTIL__NЗH•B/4}tSXE-rFby華a io+֗1T +L cR`6 JZ2@ @Ph܃ DB"5u=b~L|A?A@]jJ)E[~s)ޠn{mļFB!KpfZ Ey>b5{[Q\Y0ocgaDHf`} :\Ʌ4lBZd<yIi͛<LVxsqFEzOud3VV '?[+QTH:--hDP'BԯZ Pz&09Z3 7Upnp&ܪ Ԁ޺iDڴR%o#dsFh[@a}Yz/®˕Ff@2,n!M⍾@.Fڃhkmǫ=Dm|*B6 .CP.9Uƛi{(gFwF%ViJ[*cHЛB6 NT }gL\ T16ʟ92JHIYA!Vv 6TN!<(-2 o. ) "c/ɿ:"nr"*1/9Z 1OGJB9 N:dwy U+ 8LI+/dn6AM河`Y6p4[RM} nb&*Q &hpl^&ZgecQGE!GMD@Q95k5X9E/.N( EE#|iYq_Y/W5Ꞇ YYhv{EgE&޿製-ŽEj߿PgLEJ.cGO81` JH$1 RV⒋:OڤJC:,(k"Xϯ򫛍rzH5tw&Vo:"KZR黠Hfe,V7/#o0/"syQz,^J?Ma rdo7on907&[Q^=G<DbX'_2j-F5E p.t,iLfD 1H8ÿ0vߍ)am]I{wFJZ>YZB[L x* Վr&3Q,L5' wrqbgu5UE |bz{\l ed ,PQl!2]2 U2e8Z-<)Mk9{:AW īp7-JrvX8"iz_q`G!#ikyA 6莈Fؠ #hX93ud[^vʰa OIFb%Am=hq{f5Z R'‚h:x,05c0bd6>P'^bDdF\O8Җ\Fbѫkə+N@EIZ>ۙwH֛0B˘^ v>u zI"%wkcXSɛw(ڙNj>Iݡ&t|i+YHY~+]N endstream endobj 184 0 obj << /Font << /F1 27 0 R /F2 90 0 R /F3 98 0 R /F4 11 0 R >> >> endobj 182 0 obj << /Contents [ 183 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 184 0 R /Rotate 0 /Type /Page >> endobj 186 0 obj << /Filter /FlateDecode /Length 1915 >> stream x[n6+Q9x+*pEEghK"z8vܬ,R33gFUWR叧/ tQ+}߮~hu?waةu;Pg] ws&}ʼ7'FMHBGlymkW3^{'DrJf~>)*_?v/X3^+ p"=gU@0g\g~;ĀxRRvI+he`˸W)#!C$|@PhTv},Pq0kɏ]/O1;"E"mbxJܟo+9/i@m 8d{K@Vlc=yvB*O£8wo&ެJ 2X#Fg\o/؞j8GXS!s=Ps,B{A]p,? P.EЫ4lRф*`p*}£L9\fN,33m U&kLvE:\#얪ujjUMes Uus0c;E:dm]3y!uuC71 a&f[6*pvS2eʥTl>S2wg aڡiL lN$U X6C|.$ MakXѺC$el}c'o203tklIGMxG3[p DTs`S NopU(ܵ¦X;sY[n=V'oiA`TY~l.J~`5|`ϯTx25պ>7#ī F#Qũg 8J#q~E4P3(|ŵ u<*+这G⅂>uGM6u/?Y6G  %+t|ѾЈ0dB.6yjCYH(΄|Kq>"u(XF&0 )/n܍>~#[ăɱ' endstream endobj 187 0 obj << /Font << /F1 27 0 R /F2 11 0 R /F3 62 0 R /F4 90 0 R /F5 98 0 R >> >> endobj 185 0 obj << /Contents [ 186 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 187 0 R /Rotate 0 /Type /Page >> endobj 189 0 obj << /Filter /FlateDecode /Length 3953 >> stream x]ˎ W̍ h0Zd7@L^dzQu_+j۷,"Ef'N~f` _TQ_~zĞO,{Λ=YϚt_O^~ZXne|MkAķh@pǵ0;0 k2uEt~mH҈bk^LoO+O'?{{vg3_O_(uv芿$?*|~bɗ 潘-n!Z¤B|2|A& k*bzUL/^ϱd||U|J6 \@)P8":%{Ʉ)p ϯ `QaPqU=|$/=Ī1S|ubˉ؂>qUTcee6Ѭ e1Cǡr%,ҕh(GMlh2uc>K? xd]_[Y߾ :|Ic1]QwKQ{̗\|KWyƕst m{9mGfbֶ_ZG0@e)Nuj**縒:tM_b1U \k d2Ҷ59 #A'KLS!# 5T\Ǵ !ث)Xĉ ?Q+}b'pږ4kF5IÈq{5=z@HX^Td켎o߶5adU#>-*W5Ĉ.T^j0$O}2IƬ:\HOMFuW5RxmiػBBlT5rkMez&NO#{dҹ爬M{rOxz-`%V=z 4)6vL ޭ/N.txm_* 'Q֞<Lr95lMǂb|W6(mx$0e ϩʳpHT]PKҁ5 oZ<9JD@<+#e!-zG- 7M~n0yq0_,Yܷ|Xש`. R2N ,-M!B'/Cy+DsRXNF_94߉:>>M+v$`tTWfh Ba iT``J;=RDӳ؟'mNi"ѽ+f;ÞKz]:jn|NǓ^7c_ |_Kz/>!E_Xcu_r+>3YoRƆ-cϼNXE5 uª VDb8mh@ 'l٢(LYy}P>(Ǭ;_"E"}Pn\tʞgxN- xO~bR3gt܇Pil@Yâg’afk̃%gC汋(gM2m W&MO;96a6k6[61$N6Aɸ&`RP&l3/٦m%?YR ?Xxɏ <&*"sT2s%&z+P+Ap贃,WSH*Mim[T_e#vrD:dNAY(GV0 &Tl fюH[8 EB3 )3{4)msBct2^1ؙSi@YKơ[2q޾KmM@j}WEc V\T gckG{->@J,`|p U,Ei0t VAkMܧ)}FyxM> >> endobj 188 0 obj << /Contents [ 189 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 190 0 R /Rotate 0 /Type /Page >> endobj 192 0 obj << /Filter /FlateDecode /Length 2425 >> stream x\n8W#z7Ibv d7Uݽ]LbHǒ%J.&\YŮEKbrFYLB<ԟy-NE:=ӿ,$۝_ ~VL_Ax|j=kxJĔǐ z,Gyl^r=~$ضdǞqZO a Qgo'?,޺O-hh1{arZ~tX 87/E~F L,՗ @9tJIã]B ˴B4LLkMOOh1w1q\DC$il=Ct )08kBnHgLX-a("ʛh5,ciDQWrtȨ &R 4`؂E%XP" (tlhK'WR= &O*ʹv\dd$E;aRHfMAL, &RO6 _0WJ# <T3 C C2ºa%K&e(xCVM=)m _|N&y"eȾ[y"}gN?׃~$Py:θNRBs݂4@z'4Ӝ uZSW"/1{`$1%K{-#ǐ,4ְav~ݔBJ8u4jn8MUB;l>}&@ss[Ū{uw$W@=Pfu::_pzzm=*8X,~&M (7?%$O2IZVIGEnaJG}Y1:Rqt.[ }:zGV20~"8oAۗ4CJZ0 ١ǷN-+8[ \Ħ֢ҬK0/QRkQ̻*Z|o}ZZlѰbk:X}+;ic: q$zHՑ%NĹb'N%V6uXtixtiu-}`s=_&N#A5|YѸQb#gw8T%a \(8U(.pnԚ\'`b/~PTKDȘ# fdgmRV(tƣDqcrxɧYڽqt Vw9*h0k J:2rZ|_W ^>UពgRQY,oy>T0f]@U\`#U]iTJo{TѸJ;^ST58 #5f?PSRsSz4Ued3T-PTM8 6{ɊoT{׶TRS-RS;j*Όj:UTT5JSUHMUÝj*ngoMUƽ5 :͏mMչ[v&K ˚!qmNeL\"0 ȅ|QC|`Dz#!E|9!}!gLX7Mٴf^;=-RzvBjNӛ ]LO}W ]M峘[{Y{;U#joKUwDz'|Kv?){)bsgk-rw˥]ٚ72CoDj K96`2ۖ,(}-ٌ.͏r3?ȶ!C|U Aa~xٯv}p-J-mٗB}8nⓏI m{f59dO\5lP?w66sc~My7|_۱gDzoX8F2܋|'rD\ 2($V7jlThv?+)Qz!c@f6^Q Fm1qc-qrwzjSJf`Ѯ.\C2EtR H2/)'7x!a@Y|j?W*s`7*WcnӃ 'ºRy"Sё߿or銋- w ABپ^O@^iL,?`9;y.xbcRKHĥ\+L / rv蜋]NOD[LSr'j9; UfI/Jq"gTJX#~*L& '.S{ _eҎ0ճ~Oo[;~7VRo0Z'JM1!qb>Y)빭b;G%6ZՖxL> >> endobj 191 0 obj << /Contents [ 192 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 193 0 R /Rotate 0 /Type /Page >> endobj 195 0 obj << /Filter /FlateDecode /Length 4267 >> stream x]n )ǕD($.pvEWn8g[(ٚdg,E~$%JLΈ,&!Ӭ߿T(^>=C>K= /$i3lda??x4%E/߀|/Ihi%=ϧ?nS6;ʆWUkgë^i>+<,y_|߄YoCK_ vdGCMFptϿiGM..I:\!C-<{#2 rt:r͙ϝtMf" $ϥ?ϙHFK7IѾ 5k.K`0jyo?|9`Lvh7N-L<~P|:zbxm6RqAo}~?FyB(_`:`Ϣ;޵ggR]j;)4\5䪺pTmmzہݦ/me~Z:[17`PH 5QWHmo)&CejR % 4# )yд`w R2YB ],`'1Y1%u΀ 3Z6~2f$_hri.^Br ~1#7OBW1ưktpMV&?\ht\خ¦ @^x]؝ud5͓EqFbw01? dA{x(cAm*{Wv&kx?0ĵFujrWi+51D*I+b)k\!}+^L-d!jqד Yb04|9vߣ*Pg"AHP-?mG>>ze 0KўAH}*g3*FIBbXTы] ow`К: _ vm!-P.# @v }жw|5YԀ ^Ddi Kovʚ>&, ~*t;5i;&Jymx0 }Q6(nˌ("Ao.'H=%ףdʟCߚ&j>edk PGov`ؠoNhncK W wJo1L>-Wu_h,npWb}dL3ݧFTe׉˞OXR5s]DTʢ+Wl ^C[{4YD`V6o JgդPEqR͉@Oo0_4ȭ-kHkaq{wfk JkO{εi+^f2"ōɺd&5BF&%+$f`a~D>rB4]ٯyٛcx#]]fwUvvV3{y<;ǀ`nLP~<v-|A,PS3;G #<:m6l6&a\F =dʃ5Aܥ2 F(:t6XEu0V[KJx8@ʧ5A^K`ULxDǿټ\,+kUﻏuSq:&$"`NRaUq 6+' IWK%PWBXJ +;YKY+B{ eAL-6rxVZ*נa? ~syk. ,iCas k^~>>OڕGU) *Eэ@ s}='H{.n,2_bBu)MU;j~;3˅7hiw-})x8Ak";K{2jqNՅR^#w)|x[%;%UC=jDpAsˇ[]fKG 0,wRg :, r$}ѹ|28 o3.uV+muL< DO?]}֢z1RB5NPTJ :+w} J' nHK7m['&}恼0_tY҃J4^+%N̬=vԓ''O"z>1$N<-Nhqk۵p 4K\аtXۋ"N/s%<)/((5eAՀmTs"8|VLm,~IȕmϺWa4:qaq%'&D|%Xc9yd'p5SsKFcW輜$@E |П1X!'CBП/CX-@Cbt.|ME8qi,lIСܤ.%\8/ 5}/_Y8µDKb })ʸ8R]gq ̱k&bΆXKv@֤zRUb{Ve4J rJDbrŁj;VT.U57sL }UTp!h*rxl*I@U>0ZZAUgn dFzz}YhYȡf[A>x>ϡ Vܜg8U$Z˯mjy#<_#xcxkx+vw70'eڎҭWd[Zk.k6܃}ǶHXr“J$ZHdusc|sO*v L(OG i㛢֫w!Q7Iusz*k6kڰtvl`c}&,}?-||'9զehHxn;$j,;00[^"W^8ÓRN 'dն۵eldU`al `ypv[r|X|f[t:{k`c%,#Kv`#:),}'L%mlҙYE6v,({=̙td xQI8,\V0MQXGC#u8 i,P8%[ `nMxi|Xv_G&gO"J: uR1[@c*ӹ̂1[qslň $X~ቁ'L(`nx ,×dކ'/K: Kocқ=4S%, =--HC~c1bpu:_7 nŅ-tG+>૬tsm^ endstream endobj 196 0 obj << /Font << /F1 27 0 R /F2 90 0 R /F3 98 0 R /F4 117 0 R /F5 45 0 R >> >> endobj 194 0 obj << /Contents [ 195 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 196 0 R /Rotate 0 /Type /Page >> endobj 198 0 obj << /Filter /FlateDecode /Length 3754 >> stream x]n+M  =Ev]U,<_JEŶWKŪsHJTS NjR>fs|C퟿A~>i7v>N4%,SPS3~|ߕ}=?=:wghr~U ?eْ~oe\~ciݷb= N?_8v &`\1:tC/? \VVԨ 4mp.˼g_>2gHyvz&P}gc!70xتݭ\ߠժΖ6GVeTWh}%ب6o Bzi33he,lo`}!jyUc ma)}D o~sy=G;}~ȟxhxe;"&xrwޟǧG`nS }4xQnMQؓdS[ann{pu(grPX5I@f Ci̋1v*(E?Y-(YȌQ av Kk0bC'Xh4.0|;L*8G:dK ûےh[P]</!J{UvJvϗ+hL4G7Z jN_tm},,4![(HvecEr.*4#Ǡ΍҉D=C'-D+qIP $O"T|,'ak_3][yܬ@!h.1>ь]B|A>!qA|¹5b '$IOp$9 K^$[Ox;VN]x'^cd'|u%'jH EEDL̀+}SLi \=NlrܸCX&W^ݨxGu߭\/ſ}HoiGXGw$wށ:vT$1/\ZMy{\O۾ Wu!,?)pXv::WDv>M` و'*j$~02 g_ݓ''#=R,]ĸͿ[ uW]ڨ٥b{dxb]e;eRvް2T"2'euySmv‰+vXw4*q/P΋矢b~O(3+vkeIdz;v%þEڿ]lͷ.YfV~eJf51,ۖy/dΰmݍ#{gvoF./,Gۮ=_ާ^{'7?j>n"W} bϽNW0,;UpOtc oRXs;kFTw#"wȣͰ[q7[۝=F[U*VC͚1[. kํy6o.bl\>itΆd {˾.c8 Ѫo U.hC%k K! f (*d+*w_.Xl "ȫSYuq._qUm0tz`* ޏnF9-/OKЪYJxeJ7A~B1N`@ƒ]}$Q-@nt2t-,]ߛ}z:]*'ͅy?gK]ݩ$s&ѮEz.goӿ¾#zZݿP^./ޢ'n! endstream endobj 199 0 obj << /Font << /F1 27 0 R /F2 90 0 R /F3 98 0 R /F4 117 0 R /F5 11 0 R >> >> endobj 197 0 obj << /Contents [ 198 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 199 0 R /Rotate 0 /Type /Page >> endobj 201 0 obj << /Filter /FlateDecode /Length 3238 >> stream x]+F&Y xLf82`;`-JdTࢵq9Uuj!a{bcA>Yӿ~w*Z~׉=}u'81\sO_>wјxRlR`ݿߧzа||z"#G>}u ?-jN C3B2Tl\<6 d*?!ʌw@QWeš`dP3Y_ܡ,9~PwthnUdO ï'`j2sNwHr_% PilYҬQ׳\o:GK[O{ɯ{Qhzg‰km[gn7pd=j$wnaJ"y-|Ϗi~O&pJ(gmD3wmȬUe2WCc3CB;a=PUKsRJS$B"et2L4nCFJՌBԀ2ye#ӇF{C q4\H ՇI*Pc؉Tځ5>܆ڋo Z[nZ_ayө[ Im'%{Ѭq$9!f{{ Yօ߿ÕnXw؈qZV#={jٜ>8 g[V ga'+9Y8PI 9IW糪rj"T+˽җ\6& vt-M3 w]CZw[y10F6aoҷ[8 2ȅ{t`J:VZBjZD་ȱe/+]fuٌjpGnү`975g;(e8:.)|n,ي1,\@S)nB[T7^lB+RW{KWˆ]1o B!:H%:Λ )R!i0^p0LyG^zp^v,Gj_#> &yu=߿ϫy܎9Wq;yq.h^Ew /2rjXݑ7QͱM4v^›%2&ImWWKp;/)xF*;VUFjy40񥆍hu'pW XW\xW#l09ɀCӂo՞BtNP _տ۬,EhjZ^SxB)V ,*5zCcK ԬC+5z fU;zWj7yW2 KdSXIH1͢b@׸k;+o_QC׀.ɍWwjkxg/o8Zbӣ;V}~0LuqY+>}VB-S0X3(қ\/ +;kKtP,FBC.EM[hh#_9Xi.:*b|l[x%,+k&fk8VB8)F{ʼn`pD a6>'e1Ưrl3&Q@u h( w@!1> $KE8wcP_Hi9J>c6crsa~tvLG9lM\Ia9 8\[Yi2Iҟ֓v?9+CEN@/Qz0,nɝ@zZ'$S%3 [Vy5I7.xKA;k"ҭQCG/rIjfq#L,+fzk9L(,`Hj⼪dARet.oC\?kW+WAj파W Vc3 \JBmrҺ@w뺨%I!cXuF5HQ@u h( w@!1> Ur;?/"k'9ZA*yȵ$'C1"2|K!yIa9,Ut:BS4>ן/{Z]CEN.`a]qi;|I&D_2/RL;uL X0/@ sI^m`2^&kjzQXT$e5'9զ^O?#N~+|_>}oK_ﻧvK1CoH]€:xr*SA/+RQ-Nt) /+żdZ_@{ by1տV [PmYco0<v T f~1(5\!10_-aT:"y}\:3V}:, ډ+`Pj'CdvB8NVO'*kf'2F ;X,{ ۉE=J\hg([v֛j[fz> :Q0^=/å>KѤDά^1{"~n. ay5j) endstream endobj 202 0 obj << /Font << /F1 27 0 R /F2 11 0 R /F3 62 0 R /F4 90 0 R /F5 45 0 R >> >> endobj 200 0 obj << /Contents [ 201 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 202 0 R /Rotate 0 /Type /Page >> endobj 204 0 obj << /Filter /FlateDecode /Length 3769 >> stream x]͎*S `'Կ]F]Y}@:!`T ҽR>dM?3'+C|.?~};81~yq3Cc&/AIu~_aw?v `-p͍u_5D]v)|~Kߟ.h2 F~Q` `0=/h4ҏl}"5hko_E2͹})?㣳Cood Hܿx_N}4OϨ~}w!ݕ䶘v_?*|݋Ns71BGMg˧nh MOd/0xfNrΒ 2d|bC~EĘYx} r Ҁn=p?&~#/TE&R )0^׬DlC*0~!G / K=rG }u%V'&ay>pXFA\y‘ur K}E}N4xqS;p648+|,S<|lC~YOi@"\ٔ?+KLl`r0ڔ놷`y$cClI6Ӆ+SgX|Vh‹N̯KJrUMXt%yx)_ꚼ|(آZ}tl)Rii%n\: ,B 5)6S8?h=;y7A4\A;<{Fw Ҩ6clXP49G)v`6IQ>y5zqĉ@V.cKv'WCє%FBO̱Wi'06j'_]{rp3,0UYSHGN>I) x q*ބ{x q*~pU#R+P )QwE |gt 9JzRE\WgƲ]6-1C^W%S$E\#-)r|`8XM %9e( aa",_vd9u5|wJ[HfAazy!;kI|s]Aib SPL0L똗ߝO7")d9K4 V!FV;|[O)yxx^H]RhTALRE+UuCQtVɞYL]f6WBUymVmys)4ϮYė-ِCtjbWҚM*T7KY,R)%]>*t|5],Ųb$,޾ƺVXEgSnx]Sw/K=3E]Q23u%NfnCQ%*;2!vi̹t}4Ϲtvpd+lc8PPX2PYX0J%V''_p pb8]KV" 9F]Wqj_|}RI6Tjʤg«3@q"uږRG$3Q d']gT|'ڶ[؄T Зlq#~{ӛ}3=la?`mvA-T+Ed 3DN'73 ܪh&_ļYy3VݢQ NQ篩DR l|1ն[8,Jwkrͨ h΂`3sQi%LhEʪ7ڶt y틖ru&C y[۳Yp6%/b^B2llx{0[PԄ_Ѳ:~Gѓ(^ܤ?D &OH JBwS2KF{~h2o?sB|֝"6e8E,QșX S]gv`8H ,82,(9(C+B$1z\b+WrTȵ-g׹?F`V龜Qt}(#,Jm}Wsoقm3j?9łq>,ϙ3,jW)Cej"[ BuRwzY'd\vf]i+QncH0Xv#k#}Ot#j6s6[`7X0UJcYUYfY,Y%X`s2(ŪG12j=)nSVo޿qt+Kϵ SZ'7~}t<)^/BNtAţ.7np}_VBAݰҏtD.prD6Qe tNٶ3Xi<[Mޜ'=2^Sg[bd&2gJ4xq@ce*Xٸǭ+[m)BE@H ~c󚹕kZTCAB=KնyDi~+HICzg eK*җ>2M/g 6hm+>mDBƝzgG +?o?vNt0s(~Ot{->lf)"6"sH5p+ֲ]`ӑevI%b4cJRF*Vb$g-gœ/`_Amif܀ptn~3y#e2F !S>=?QE'T1ok~QB v:M۲֖ N'Vpau))P0J®K^; 6a$z)ܨ6:6pG-@fӮZY>bY渻[7ܓY::1T}I݌ . >~ـn/ Μ\Үw[`}^dgf!BkZ%޽H3UQ=uiץ&O~ JCM0)SxN]c|7-wjG~@q¼*}p%B',U>(,woKquT7X8æu Э@Lqz%$opE>jx'--+dq?7 endstream endobj 205 0 obj << /Font << /F1 27 0 R /F2 90 0 R /F3 98 0 R /F4 117 0 R >> >> endobj 203 0 obj << /Contents [ 204 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 205 0 R /Rotate 0 /Type /Page >> endobj 207 0 obj << /Filter /FlateDecode /Length 1359 >> stream xZ6ze8!9nwAE %EleHC~ͯZ~VZO/9&PhM?Noo)Z;K12ɬ8`r??c'|"ힰ'3Hd ^ݷ`>@;2E#;e:eѢ E^s =gh'w.hbɁv i9( bbX93^NB4s6j"pI4z U aƻV H;l=D8Z9;kLb6I4hg pzpJk3C1EnݸD%X+:0萌2qb}2L+rb/U s>Kc&jš &h}nruq8Kd" o}#mDC#öFlrlwQœkCzA4"kFШ(.;3ذhܖ))40~Ԑ o7s֞"JѭB\KѭhfcNBh}ХpoB ިD?YxOc?bjL*E7cDCArrX ynh D{.8ZٱΩC$ Pm 5Nʤʛ$<ڏ> >> endobj 206 0 obj << /Contents [ 207 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 208 0 R /Rotate 0 /Type /Page >> endobj 210 0 obj << /Filter /FlateDecode /Length 3996 >> stream x]˒*Wt pDGGTWW-fw#z71s݋;3Ur:A:jq"KzϡJė>/^Ͽ// ^K tq50ߗ}!}ALo߾E*GHZ5a}^۸Kݟ[,!bQ/?yyyl0UpETp[ЎzX/Զ=CP2^P&($e2mZ{q~0`2r=/ E[:5@ϯ M` 80W1ǿg >ɐߞBL=φEc6Ff(A(TCa/_P6p"i>y 7=b1<ѪmAM:BG$8?* L+;=C~ j44#`$r-`ȖVºlBEVc3q%m`A .+ӮEf{`9Ig8F^v۶Q>!A$|Aa{*s> R6 6FG{׺?ˆ1 KjPJCQ,:g xDK"U|YUk7%y~݁Z(n"QM4 cD%'0N; ᬚ 9ثc=ۦ:I=q I1TX@N*_1MR"r\S}J>^ چy>6J>t^M1ql\ C?՗a7m7I0wQS?ϺW| _q}fd4"'xIl躡EV(Dmb'}m^?2Eok^|{Ǿ:g $wן*AHO_̚K$$I9u 4QKe +M27cEWͧmq.)eR%~6>y`R$LHN}Τ]/_(MJVn- Ί/\ohn^.IK9uKBTkD( 0Ӫ7%IΜRS'NP˛Ws CTܩ:A)VԊԲc$~> l"aXTsm[%PT!~WtujcN#Y^tZ\}M;Qj_7t)Ӓ=ۧ2>};&mܢ SS]$//K49Ywڶ*knO̎m2 禅:qq ,Z4rs*3pSxSϪ!0sܡnR'WN_?[!6ɡ{V3Z E'<0ﴳ<v=ck(['AF|fz݉)A{Mxφ}x|KV:iB] wv&"R5}_%c2ˊ9*,vQƅ#> >> endobj 209 0 obj << /Contents [ 210 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 211 0 R /Rotate 0 /Type /Page >> endobj 213 0 obj << /Filter /FlateDecode /Length 3894 >> stream x]Mo#7WLII 00r bO ds6dmUZ&>Y$ꑚaS@fvS_i~x˳y04aN{xda?CB~o_?O}}d#iĕ~xɆy޸.WlBdh^.bg7.I˫Ko2 ;)={Wfl-.J{ίWz4os8^ZmH:?W+?~Βr3-!N/iɒ;pl:伛,_Z^U9<14v^v6iq'b޶;Kĝ%b[VPڠͺ4!)9+m_fymZL fPO .O?Z!Z avFJd8@6C-B⡋0`]hݘ7S) EQĴK%]/4]Ȝ~j(*i#ZTAxAjuD()iעCZ۸<9ec@f~=ijx82lybʆ` 6X8@㱔p^IZ sAy{ɮE"xQ8rJ8녣 #b5a<-_a0BB&&!'ܯW%¨nKsz{ B y_ZTPaqF/$Mw(u,: :]PL]C).W8]ЂijA VgYbã|t|XjK9A9Z/m/n.-J-V Si򰸮U{ @J@r Z&h`@T< U {ZjC݀ȥn/ {ߪ6)!M?Ɣ9W ~fq2VpۍjHNhlܧY^ Df~K; Fz=UjNn$tHVFx+=6fA2@%rK|Qj;!lȑ654ӁGh*-G|I92i]KFҰ4pQ1_Zߘܔ)煝P ]#C-k/bx bvcjJp Cz>U zHD~h QMZx .)'CW%Vt@WeX ~;a{T#Q@+/}#RlGiҁ9h1}a/m,^> `g-;X*1Y$F:!]:l%a(rpbO1CHld Q*(6{^ d5|S]:=돉|GC=x%Nk|ׁ_S/ӸrrówKWgݴ!TcVak \B9d hAHZnct0|/a~ylLZ)pz"1RZnveVP`"a[KnDaM~DfJpvkVuWOl;U 9B4v-CR IǕ[f  žVʁCUvÔWG=yOM=-f {UAKi }y|7}0^Eo\* ,Xa1o?KnlCp}f\g<Cl& 'L~YFBr(+xYvWo1lrI#5Y2JZ$&6g_RmD7Z#M {oB*g$ f 2 Q\c*nC;?a:\DM^=1{U JQ"DCw!FU.(=CcW\1y|Nr)"rd*>&9dߜF:R5Kvc'@ hׇ>CL@76Sh11꓍F9@p* Aã9@/B@!GgdhJ<{˔F@ʿEl}DKaIox%ޠpN.!wr IE)6fB}5K1lqwK3a۰^l 7laW)G9_NP*rY @BGPHj2`R0S'BLs|X&Sw6µ<*cFO1I*li1Jx}]lʂ";.v5#'\f)6+]+OgD?%f|k"]VEQщׁ5G0gqNڕ='2ESoJVhQlŗv_(AE]%A*[i 0GyJADj|G\ !GҊPTu_,8 `ݟ2=(8.6ьtG܉,.Et K^4㟧Ǵn2&_8()y.+ -8œ Hn6˽[Rӻ}9M: L$F ][,''12yu#ߤWmC D {GNP}kDK&ڥ Tf} Ju.˕cZŕq|yh-Ui~mӛ`  Zݏ~m|j)r *$2VsroqIbX3ۊ%2nUh;paO; Gؒ?r9D8u5mMe_^_;brq 8l Lz{Ư.=&("U=ezOo[' m砊3ߧ\Aȋ;G~QYy2>Z"{2 (8%qV.YO4e h$/$uB|F񺘄R2;,ed%ڹM-B8%Z5%&!K~. __pwE(3vW}4`:xzΔ~e۝W\hb n<悯.;z;yj)@J|j% endstream endobj 214 0 obj << /Font << /F1 27 0 R /F2 45 0 R >> >> endobj 212 0 obj << /Contents [ 213 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 214 0 R /Rotate 0 /Type /Page >> endobj 216 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 732 /Length 99634 /Subtype /Image /Type /XObject /Width 1419 >> stream JFIFC     C   " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((?U_~'jk~#_ɼ1GKJ y#kο??UǭğP隕$,$p8ȯjoٯ}O|<wooQyDlkBO ?ƀ=s߉G>*  ?Əfφ,G}'~&UNM/fφ,G?>бh??T~;7>бhlkBO}<wooQyDlkBO ?ƀ=s߉G>*  ?Əfφ,G}'~&UNM/fφ,G?>бh??T~;7>бhlkBO}<wooQyDlkBO ?ƀ=s߉G>*  ?Əfφ,G7ZZce{ivr^HHqHcyeownKyYcqѕA9ߴׁt/DѬ dW\[y濡 k)h(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((($ l2(f8{ A+?@W8u=]X%Dy_>6(]?ǚrpMy7XSǪ+~MFezN|{iwO,dMcp@"+O_S7?Zg[3If@ E&)&Ev= @qJ+UxPvG2Q6U?}<#E5yB2.G@WhI>'kI=`GapEmdFI ǩ׷◅c_cAP^VGA@Uxׅ>#4:\ƻE)(_\Fs:nt=KYQ*LU@N\~= 0F @X֝4}+ZY%21Rz}K~b=[VԼCX'ce!=FFOAா;xMR;z8e[e:CgTϥ_/ŏ H3&-#sH$ P]Er>9O z2.䶍KG}j߂~"xw%>ԣXǂG:袊ל߯ k)kל߯ k)h((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,w ~Ξ;6=O]Kkcic 8ܠ+߂/ tgZ6y"VxTZ7ȿM5{L]DK ÿx.ڵq#4k#_Wͦg̓ngY `|w su}E0Yoɵ9Ru4%%C]ss?kgk#3}Վk3MM?:Y"?jǪIK-k,P @ϧ4K&%,ֆW@]8Q`8؍־Y)_*W8/UPɲV?b?:_-xDݿ&f.s$(c6SR|vAF3{_|YšG0x?)lu q K5:WK=ǂ~/J`Li_?iV[/5.~e%\*¯8o|;*>buϟqlQMgNkWW|K }YA)#c:44[xLhk[Qs0$cڥl|MfAaۥ͓4O$r<}FEzm kr" q=HqVq|AׄBc6{|d679&->NKf6x xc]~%)s^x_! ,k(hל߯ k)kל߯ k)h(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ]&IC~kxg ߣ3w1gIea-w?d_&_ 런kw)t f_Ijkv{^Ѡe'0=VV͍b+~w O 9!cƐ"ř&kKyLzPҾ4//U׏/M}'n.tb+io#23+Gz[_ |{_ 0x};L˽J+m^@?"+O_S7?Zյ/?'t.muǻUeFPTQHnO#z_ 7?[﷣-4W oKޏnзo@`_px/z?> z_px/zל߯ k)k;SM&].+{oḋwI濤o[g:OGF"PAhr(((((+˿iό|Y㥵[۝.Ե $B3@E|/[ 'oS/fڦ&fmӦ o 05EVW z\Vj6Ko㔅T^9"Xioa;Kӭ{'wY l*7ź9>s#k*Zoi^AwS\18$" $ d׆Ak?V]~ߚ3F$ !E5;R_a [}{D\*_Ы߁G*_Ы߁@>1o&}?bMs|B~|B~t?&ſ?׿L 5¥W _¥W _Ah^0-= _*Q _*PCa [}{D\*_Ы߁G*_Ы߁@>1o&}?bMs|B~|B~t?&ſ?׿L 5¥W _¥W _Ah^0-= _*Q _*PCa [}{D\*_Ы߁G*_Ы߁@>1o&}?bMs|B~|B~t?&ſ?׿L 5¥W _¥W _Ah^0-= _*Q _*PCa [}{D\*_Ы߁G*_Ы߁@>1o&}?bMs|B~xY? wjZuη+m9~˿N|uiKu$Me3heCQЃ]~&xOᝌ,iˊMFaދ_)#t ^K\ ॓Ot:߆?ZmL/4JX+;EqI*Â*|m//?gM_HY&|MiF-YYT5Mdz?}j0iX\6X=j n?ejiz#7{HߤyaGK <~9<:$cEICy9,ArǟZ((((((((((((((((((((((((((((((((((((((((((((((((ɿx3Eiؿe~zJ$ɿx3Eiؿe~zJ$(~Mּ3Av"鹔Vo)"xgEvtP _=G)"xgEvtP _=G)"xgEvtP _=G)"xgEvtP _=G)"xgEvtP _=_Əx )]:Z^CaEBJ83PbFGa,g+-.[TRW-+W>"xŗ3^ `?PsF'22-A~|}G^_߆%h%;1䎃]F,vmj4<!'ܨVW~RN>>ĩ21<Љ—{B'OoWgEqЉ—{B'OoWgEqЉ—{B'OoWgEqЉ—{B'OoWgEr? |Ewe]Bۣ .t>d袀 ( ( ( *if5[m6.nXA@E|.EPQ^w ?eG4W(>_Uz%x'W.z^kR[ͥ餉Äv_ sQTE|.EP =cjv>״ 7ᆀkZ͓[yjLd;v5퍧[7_π\xZ}=ln[~:%(A]Eß2+w/*)Y|w>2X;k\E]djZ~^0c#!B :p_ sQTE|/EPwORm?AZ o?i>,5-Rմ' ug(6!TppkFƀ+(Z݀ [A! _ʬ:v4݉/ _a|`_ Č>F>ڐWlx+_[ߵMG"ǃv4j]'3Yic#z@Er>+xw⥄4m?BGq+M2}]`2úI; MzoU=CE3\@?7`[ >;xk⦥queۍkzYHǧwRj0RKXx*# Ait 6NMxEկg2/r77?]ߊ]];TWX|U⫥d\Jߐ0;`{)^ յH-%R!m^,zu{u' @3RA޾rw4VbO<שv%p&!,.p?wT^5گmoh\HbNHZ{5֗m3Zu흮2ɩ'9VP9#nx..PHlR+PTS Ow5f]fGql*wۃ^y->յ+ֵcbyFemT(zPWEx^xPbKMVڗ1+q_>&h|3Z|,q[p#^/~֞R=F ^HOR3+#v+u F%[r8 ]^#u/i͕ofYW_6s%ͥ "3u# 7Br 䐧h/$oak kdgG +_Sh쓭|/ ^x_SRԵ3YO@>%x[O1xVѾh^BӮڔ9޿.~/d&xε>6͸[Gڮq?*ښ]#΋>lqeU$ =z f3xᾑ3x #rᢾA Sß It.~+mi0Pr ((((((((((((((((((((((((((((((((((((((((((((((((ɿx3Eiؿe~zJ$ɿx3Eiؿe~zJ$(((((( wM^+M5<##~W[~yh7"N$0Yآ;ww¿ hٗD=[[(ӵK"KM¨ 8q>]"% =S[:F=gMc/k% #A'Б򷸯/%;TѼui#ΌY_jPEPEPEPEPEPEPEPEPoO$Px55l V@ f%G|{cm7HZU[;ic9}uwi׬-|]n{ǟ_HZ?Ⱦ+E~DÑ|WE#G9h"FrE_)?_PO9R4r'-~Q@?߀o%m;R fݺFU3_s75ORm?AZ ?l\j5$=#,x0?Wc4cʼn|{w\_ߖgD"v@7|P׼QNqHЕZ+G%W k/#33\ll1 =RqA8W [HЎk*P\zOدOүtO :U bw g+o,ײݢi.#2=e=Vn5m[LZhrP }(6o퇫[xl"dL'K`y`iޕkyǗs[4SHW)/%m<{] \_{Aj/l~^֭u%//ߚOzߵ_X]-XQF$rLXc59*+<%Z?uèqu ںp ,~ X|\3̨2z?NGӣi&Mj~pBJ||geI|CɈk{Ƒ$|w~#az#ԧ~F@w?k|S[|YZ5 ؎Q+zױ&do5:&ms 4i8E{HojjӟP=3=83|;fRK b>CwvyoKN?7.<>i{n_EҴb8&/׆HSUῶ7v?tz+a3㰌+#i9J/Kߎ |m?qA5p$oak k:Y5Q:mg -IokW} xl771B6w%| |c+^F.8>aב~߂$tK7̀ R~aӦM|AD_寚4:wxS =f0CkEc&A@tQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE~wk߼c"4_K2wWk߼c"4_K2wPQEQEQEQEQEQEQEsLHuo_7@*>x#x+]揦xfD]6y=  &@^@Sº/<9{A^E Js#rqֺ[!Km2}Jcoqm)ԛ{\7v }PB-Q@Q@Q@Q@Q@Q@Z7ƟO-zLGy?M?1^-_u@YEQEQEQEUjOVokÿ~cj^#oW) d dW;!$)(+ohߌ2ѿ? ' d &7'$LG4oOI?€>ɢ6@I?S'Q Oh~2OFd$)(+ohߌ2ѿ? ' d &7'$LG4oOI?€>ɢ6@I?S'Q Oh~2OFd$)(+ohߌ2ѿ? ' d &7'$LG4oOI?€>ɢ6@I?S'Q Oh~2OFd$)(+ohߌ2ѿ? ' d &7B(@I?S'W3 g՛#n9~#t ^K_iK_~ K]wMҮoS0 O#8'QEPVH ۺD?+QɤPQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Z7ȿM5{L]DZ7ȿM5{L]DuQ@Q@:<5ዥA?U~~ZvC.sq=1d@Ut |J i~'Z OCW1`' ?ሼKC?PO.*\>A?U~ x /Ŀ0i~'Z OCW1`' ?ሼKC?PO.*\>A?U~ x /Ŀ0i~'Z G+-NMI7N v\q޼Ŀ0i>$K_(?re_ |Cx>"m>4=_Gx6:5 ѷn[)M~jx /Ŀ0i~Y%*xWZw]#E4ֺu*U1?W7?S/\]pj}g_^%K("/ _@ ok G4 ](rԿ| C("/ _G1`' OCQ 9h?2ob/Ox /ޟ\>A?U| C("/ _G1`' (;Uz-ԧlp;@d+W#]&]R9/X%W,SH^̳ͧ$՘ƤQ@Q@Q@Z7ƟO-zLGy?M?1^-_u@YEQEdx?7xNgmă&5*+P|5%`7aԣE*!8W+hņAB3%Ԃ8g,x|7/RxoWKi[}%_.C>ae8n5Y xf(ĚG#b/91yo¿<)_WBU!ҥѬbc>l_R}3O[? l[~O.eܟG0C.~ u g_ a]/k2O[? l[~O.eܟG0C.~ u g_ a]/k2O[? l[~O.eܟG0C.~ u g_ a]/k2O[? l[~O.eܟG0C.~ u g_ a]/k2O[? l[~O.eܟG0C.~ u g_ a]/k2O[? l[~O.eܟG0C.~ u g_ a]/k2O[? l[~O.eܟG0C.~ uп>38h_k>eZh_k>eZ(zcٽޣyoakMs*ƃn=n= MKL{OEJ; zWc|о4x*kڽֱe3\&>xs^@Vo=O^}`;BzMsPm'CD=L.^a R/t}=nKv,Nj~&xwmݾW`ʜ^ͅ$)`3@ ⿎|Q4 nJ?/L>'`~"kCT*p34xj\e6v`G&Լ1/?D%ty $q}3Ey?ld27'R4E~FMDIM/e/@TWo>KK7'R4E~FMDIM/e/@TWo>KK7'R4E~FMDIM/e/@TWo>KK7'R4E~FMDIM/e/@TWo>KK7'R4E~FMDIM/e/@TWo>KK7'R4E~FMDIM}i ~Ia^xIoZ!KhfoA}}E&~ ?Wǚ}m43E #9M{ȥII#u70<> ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (?;o j/%_޻҉+o j/%_޻҉(( (?/EWc4ʾo~HfWր(( O.RBGs^_2xk73xz̪Oz#",#PZF:Վb|KosL$#h\>0r|hv]Şg$c^;~?u "a:r2![1\˧ մۻ{yW2"(7O`~u rJ~w9Fw;xkL}ChI 0 dRچj~s'wH.?+c+loB+sI@_Xd$pճwUOUm:tW|>NxwT]V->M5"5Lctn ( (>6X "3u?9⾞DL>"P֭iO((e33_1?U4/C5Ţ?BxFLLꗳ9?h^2յc*G5Ž=Sgğ:B$$|9>W| NOłs'{?'º}.[ 믵Ff/0@ϟT]}g!wO>3Ӽ}cG kzr6CSsҾg4OCO=?੿?ߴ{M+?Щ&V~SDt?M~~'ڗ񍦘}ྋ>Ţ0# x+ 'f:sYi#=֟$Ұv,AԚiAt?M]k NA㴍H@z-@M'RT?ioi?"/]?|-غ[ߕ ]~E/G=s&)Qυ_z-@M'RT?ioi?"/]?|-غ[ߕ ]~E/G=s&)Qυ_|Yh-5֎ojmF;NIycσ?_בXa։""A*(Qҽ5 $;Z&>HG!> ܟcѼX Ǯ:\\ѮܓBCc;]Xu|7ƭ/ź&錌TlC<@牼G|`𕮁Oc,HB".d ^s߀jZnEAF]꺷O^7V١'rr{qWl_~ |8ezori)~~+մ| >]il;[I0ppy޸Qx:[;mHԜ_QxoVִWn"-NʂOԚx~8t|?d#,#5_w_><sj6)Z2?*8]0}#^5 oO-m/%#lgҾU|`|e|Ͽ={P?$ڸ|)5[W (ՈTcޭ:m՜ө"a=kc; jz|2=2-[K FEqt_|a/I4=.UШ; #c]~.|C=wZen+oZ1,U|I5#,k|CsFñ| IcX Ŗc#KRgåx]?T8.ds޹ ;=x[wɖ;XDU&5CYoOWD]"O&88Ͻz«9lxn&,9f/cՄZEµp><7>ֽ5opGJ7Ifג)6oLj4 ].z!(dIg#5^iwIk5/R*XP}sW@xZZ8VV}sћM|#ymRz< ??rM q&[w޾e =(+Ec&A_uPVH ۺ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (>+ɛѵ&x?A>Υ}U Wl=# &oZF߱h?[K'(o/4_𮊊[K'(o/4_𮊊[K'(o/4_ˋ[6 va&PM6-Jv x 'b· O/Q _,i?]hwXVcIE 's%BƓ· O/WEӎP; _,i?G+ б"tTP; _,i?G+ б"tTP; _,i?G+ б"tTP; _,i?G+ б"tTP,^,euPT8#%s"[>47]XD+ Ze b7ix#Sfς> _x;CVw]214@X*]BkհfYV?QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE~wk߼c"4_K2wWk߼c"4_K2wPQEQE~_xi?}LxkϏ՗^2u$,F5C9⪵@Ey47ßm& ?᡾k7PQ^w _ɿho?4M蔻dҼ 6i()K0NEy47ßm& ?᡾k7P>#x:sCMG47ßm& VbNhv~_U4/C5|D+QY4_E{*/Wܜƿi~8^h:zvGN9?&~ \j[BduX(8@3q㿆)^ $,sN־/׿dbPmo rD4@bG}w]|?#K7KgrF$(AyZMxgLB2x|2#~t63Y(v8Ffw'<ײHx2ƞj4lkrxk!hO3C$lA[#5袊(((((;`;Bz?xgd9&@ɮcTcw gJu5 cG4_P*0 zWL|5; c@W5 cG4_P3sK7+˿>?i1P$94 ct%YeݤNT`\/4_Q 111y^] 11L|5;z^AzטL|5; c@+~~3eo!Ky.#-Mv cG4_Pio c$Dj>?¼g/ Ҭt WPyɅ<@K2K|-Ot_XEP_j/4 OڃRG~EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE_LUc$a?Wo3}W?6??fK@EPEP_<c#GspҀqEe&ZO/de|̐0~V-|JML=LP@WE w&4c[֡[+h'鼜|TA",ͤNZPyqY> kvog#m[mҶ~*xO;k-[JMLIcip,0_=zWe૩- Ԯ/F!1"[h Oaڸ/GPwP&η:} ?:_*$,J6. :5K {o{\sqϳsBDC@? j0u8I`Gk?Z^}ohu&G>4^VfǦI|)<=pdSfh4_ύ^ u .-jYak(&Rrzq~K rfǍ-6} pp! 8c?@?xgQ?xgQ~wÐu)?.*rE>P5w)G5w)_c} OsT# ]sge ]sgew9Xc S\U~W|G4W|G5A??:?@??xgQ?xgQ~wÐu)?.*rE>P5w)G5w)_c} OsT# ]sge ]sgew9Xc S\U~W|G4W|G5A??:?@??xgQj_iMb'Bރse9Xc| lo 15^ipm.((((((((( ]&Ib]ɕ(w ]&Ib]ɕ(>(( ?_Wzľ}ݤpe#8?_G/(G(4Ñ|EY8ƿKr/?E}g.?4Ⱦ_G9Qk"?>q_G/(G(5Oů[ڳyL!uƭg$Ns_3L,8Գ1&ڃįگ?ln]L:d~V"=27Ս~Ñ|EY8Ər/?$Z]E/::ƛ ` E0+{Ɵ)|IOG}kë?'l~-R5HOր:r/?E}gW/X )₋]ƽY{H+r/?E}gЍSikAmdKvԗJޖitaO/?s}}79"?4Ⱦ_G9Qk"?>q_G/(A)N֬yC*%à5/accmgl]kiPP;=O9haTy2Ft3alRL+O |ߍ]*.O4 '_U(((((((j'jYxX- [m%OD%FO#ּG߸¿F(r_|G~ ? y+b'5߸¿F(r_|G~ ? y+b'5߸¿F(r_|G~ ? y+b'5߸¿F(Y yd߈4p=+#QPxoŚޠWNhn2 [=/n0¿NnόekWN9ϟjG߅~<6~1 <+5Nhץl# }OkCψ?\}RM>._iz\Z#k7n/ >ZR  inU )2O@ +Qh|u""8y) y+¾ ?h>*{T0^#o0/*[Qϵz=~sÓ?QOkCψ?_P?9?>#p<1E~sÓ?QOkCψ?_P?9?>#p<1E~sÓ?R"\dg/Eq?~ REiQyqtĖgcܒIk(+Ec&A_uPVH ۺ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (>+ɛѵwq$#7Z &oZF߱hܨ4f (:OO[n,Ҁ =:Wh[GÝ+⏆duo1"%x~N:0 d*F{ [0y꩜ڽ4f<|(%ťE$BP;?G_yXfD(3<J<њ_ݧJtׯ5k+>q#9xzFv?5]Jlͧrw#^3@%Gi7ʹkgc>=]X+;oaVK2nx{Ɵߊn&WWupG> ]F+Tym8޽4ffE4OuA5H ț׌ o"Gψ_G~o<3xO{ kKլa`Y *(3 k^2|Sh?XӯCVh6&د ( ( ( ( ( ( ( ( G?$?4ۊG?$?4ۊ( ( ( ( ( ( ( (?;o j/%_޻҉+o j/%_޻҉(( ( ( ( ( ( (>}/߲ߍuuSM>WX$c_ſ~ k˨_[49#8+G_ex_4;P?%]ϾW}3dnpoۿ~m}~>ӿ;kz(+Y$'_u^m{l;yVhuVR?1~7vǏ&ieBL Rj;#ߴG./CV-4XՇ@J?(I= 8exjWC׾-tK-v6&Kk[ ! G ς~#gJ:\hICG ̋0 Šgᮓ==|AH!}d;qʺj((*NZ6?h\> +źki󽴗vvgF*K0$=1c%T؇gh?[ъj>Tz9w ,P.j_38|xS1c%T⫷P.j_3uB9(|G???_O*uB9(P.j_38|xS1c%T⫷P.j_3uB9(|G???_O*uB9(P.j_38|xSaE мsnuOif,g {OuB9+kzwtO L odAž2;f>(((((((((((((((((*7uȺˍ^P$.G' v/5ЎTGy+_7jʀ?o^_ O&^_ O&Q?*yy|v.I??y|v.I?vGF $h $kڿWo$o$gjt~Tm_ʀ?o^_ O&^_ O&Q?*yy|v.I??y|v.I?vGF $h $kڿWo$o$gjt~Tm_ʀ?o^_ O&^_ O&Q?*yy|v.I??y|v.I?vGF>2|q.l6@4I*FE>*<[B4l%wmPgy[ڿ/NQEQEQEQEQEQEQEQEQEQE|IIi_|IIi@QEQEQEQEQEQEQEQE~wk߼c"4_K2wWk߼c"4_K2wPQEQEQEQEQEQEQEsLHum_7@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEaxD:E5~=?*!uj{,x?|TCEPEPEPU/'MYڗn&?}zPZk?fk((((((((&N+nب2H *b@u&:_G?9FŽ~{h+*IuP?g_n?_7Wƙ:L}KT4YA=1o?'I=Kq|G=/u~p3zG1o?'I=Kq|G=/u~p3zG1o?'I=Kq|G=/u~p3zG1o?'I=Kq|G=/u~p3zG1o?'I=Kq|G=/u~p3zG1o?'I=Kq|G=/u~p3zG1o?'I=Kq|G=/u~p3zG1o?'I=Kq|G=/u~p3zG1o?'I=Kq|G=/u~p3zG1o?'I=Kq|G=/u-gO?¼<=Vy{qq%VݥI/xx_TX̷9WAҺ C&eѦo'o|(Yv*{8cIu|Kq|XWb^-ckߊ^mkT]-ڠyyoz_C ?7/+? ?|A=SOz_C ?7/+? ?|A=SOz_C ?7/+? ?|A=SOz_C ?7/+? ?|A=SOz_C ?7/+? ?|A=SOz_C ?7/+? ?|A=SOz_C ?7/+? ?|A=SOz_C ?7/+? ?|A=SOz_C ?7/+? ?|A=SOz_C ?7/+? ?|A=SOz_C Ï:P^!Xm<1sD;Xppp}4RR[ki&@pJ#'>;3nG/,d@GE?\?tj|b 0Ģ<xC)ן~o_ [auY#'<5 SG@ރ /!^:t@z ( ( ]8TnGp-ωB wcҸi>{&Iuf^\r=q@SR'ynxKY:Vp^S5|`:o--GY~&xŗ: SRӆhJ,6Rz9EyfO?#AŰ.6">Y>*|U8x; 0^#X` jWvw8@8Ⓘ^~:/%X!(Fp\½ා QӵH0OFQ\u;( ( _;IU@j_;IU@j Pəo?m}_(7_ן6 fm| ɛѵwq$#7Z*( ( +̿hj _u藍a~ 'i/xZ޵P K_5@ g:`*Nebk&lJw*A՗+RԮ}3Os;]̀Ol@4V kwOˢp!K7wޛAkg<<C=h> Z=eLJVִS&-W U/6;={Ělk˖TP2zPQU4ZZӯSo tu<7ugïRzۢZQ%́LV?hjǫIȡfFd>Q@Q@47]XD+ Zx$\`Hz-xd_>,_i*E޺$~z^kgdյ ]20Yb@^X[ g>]:yB]ñGh=C߃OntH%}dn۷&3|۹]d߅))V|'?º[%7WRD#<*u68(aT`@)Q@Q@Q@Q@Q@Q@Q@Q@~#{W~#{PQ@Q@Q@Q@Q@Q@Q@Q@Z7ȿM5{L]DZ7ȿM5{L]DuQ@Q@Q@Q@Q@Q@Q@~$~;/?~G)3#iQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@^;?M_E8ع'a· _&oï?OBooh'!ƿnOBooh'!ƿnOBooh'!ƿnOBooi _۪ll\P(Ak-Ԣ9;y7{k>Rt#J*Qפ?QEP:E|0N4>kM[R۫Fhm@ǖ}~".i:mƷ&o5A۰A>i 3|"7 GWZ|&[Ւ`6)z@ O+?{%n$+dX +'z|1NyC{q 3Ic_t|X>FӕZ TGSڼIogZk}v)2,rñjgu¿GPiWr,Xu-׉_h cioxcī&]P[$Ï"e<3 d>9a1yxN?jdAo{?~J ^L:%kcsRi[>=Kmj:l E~u=?j ~ʿ5xIkOEgQA#)O#]2b>O] Z5Yi:ީm#VI(YKmL g+'_S"R!:ჾoA& MxG|7ڦel`@p @@EPEP_|j|77>j\o+G|*Kr09k Gg~"ZC[:}χ?R/<5=~ڇ_\FYiA_?]O hŔ:Fg!UR ǥ;u= UF?,*#?*owu}KU!W>9^@Q@~pn?=z8E~LM/2߇_%}?_04~ |QEQEQEQEQESG_  6ɜPwKHW];ЬKf40E?XY@IQ_6ph?,Af}%E| г}ҏn o7BJ?4oPTWͿ 7(Y@IQ_6ph?,Af}%E| г}ҏn o7BJ?4oPTWͿ 7(Y@IQ_6ph?,Af}%^ g* X 7+~;~gůY6tYV#}h C&gѵ|}V_YR3{xFGQk &oZF߱k/+b j$t~W| 4>mGKOj3Y>r ,|+e>Vat{N֎5]u_w\zWd6in +NOw`9BkOe>/*> oGDŽ,:y&iK#,ǎkRSՍ|úeX[YZ_@+b|7M+jyA$r:֕axď _xwU_&ˋ+ܒ 99nοM]}h o5{R44.//$kỿg*?ۿ{7[oA ?g*?ۿ{7[oA ?g*?ۿ{7[oA ?g*?ۿ{7[oA ?g*?ۿ{7[oA ?g*?ۿ{7[oA ?g*?ۿ{7[oA ?g*?ۿ{7[oA ?g*?ۿ{7[oA ?g*?ۿ{7[oA ?g*O߀v7𽟇^3j e~>"4=fOng|˹Iϭ}wCg"O&P>!._T!wCg"O&s?<+5E~HÝ)_O DM~@6xW$h;S¿'_P9 ??lI}!wCg"O&s?<+5E~HÝ)_O DM~@6xW$h;S¿'_P9 ??lI}!wCg"O&s?<+5E~HÝ)oM{ 2|Wb#Fs'@3jqoy\$ ~K"/5?-i][ʕ# 85c_J>_?l((-_0?=~WHI= &[O䯧&oï?((((ς_jZGvZ]Oqq,8r S^5[p3:CI/B1t/II@'ONuiqn`o 9cCd ׯ?,lX7(a&_Ö?6Mz~rɿG0 /^>`o 9cCd ׯ?,lX7(a&_Ö?6Mz~rɿ^}mG5u5&mf2  Uea$?Cw>]8i{WLyk* +6b#kmcMɧ8{%`>PsLUc$a% 9cCd ףrɿ_O@0X7oPÖ?6Mz?,l0 /^a&_}?E| 9cCd ףrɿ_O@0X7oPÖ?6Mz?,l0 /^a&_}?E| 9cCd ףrɿ_O@0X7oPz[eh2m`y}{iHhXx$L0q[Ɵ&7k%s"@Fh,lo׎Mi.}<=+,VAJ+3WM|J~#{௉k(l<6%ː 9<*(((((. @J7DW`X&..+_M_cК=ڊ(((oI=}?+oI=}?(((((((((-w?d_& w&W_J-w?d_& w&W_J(((((((?noINm mox-*VEY!fs2:ᐂ(]|oc7G>+ 9_XQBLJo*?h|W+ 9_XQBLJo*?h|W+ 9_XQBLJo*?h|W+ 9_XQBLJo*?G?lN jھuiM9U$ݸᾙ[wƏ" 3VF.ӵIww?jۋOmFZMu 63*A,Ohe?__ǃ-|G>{"iܒ]NNOw%QVX)6|?,q_x&c2#O_#gq|HL^CjL3"U{ncwG|f4{xbwN5~@%Hnڀ;#o9E`;_~ߴw|csOmB>$&aQ<@Z ~:þ'i_|_qìׅk2r3hci3KcR28ۿ<{ K)yⲘ\ 8"ڛI wRԵ^xե@+[2$XsiPb#.G)CRWr>{>/j1뺌j-lf08&g%Fo-P>oدO[p=cƷZѭ/s _3^\ k?%}y~WOW=OC (+ q$W_+giɖ:+ &[O䯧((~gS!ѣk뛯!eFUPn0~lUfkob)r*I@YH }?᫿m's $ֻ߈> i-l5+4ԴI~m+*85/Չ<;M;"|A'[1vKv;+20e%YNA@""dA nLa:_N_O Jh:SSvѦ^ܒzmAMc<{6Eoc(o2/5PNF;"^pW޳;"^pS/ր>`׾2O/k~𘲸6?(;A9b}闟&|/ol>h JGq>(|#Nx1u$-'(2zVߋ<=[tMkÃM6"dI9c7o*O˄Y"R20;Euo<W$greҾW!KJHmHA2굏C5|?/4NSׯ-H=y9Ҁ=mBa זzG$ʬj)_ skk$і}k毆mį\'5{yVL|ޯ|5mfelO#m2$>O'5]Z4Wd6׬+,e 5J⧃5sTOmgk.Y dagoAgY^Io)рb${P7:sYT?5_ϧoTei x)eRAGTv_0&F8u߳fO⇊_IZne$d=2@X爿/;h5(OsŏM|[&%Z{7א+n%_$ N>g߲o1=w៉_uKkmB-Y>h’G⏂֙j/oІ);=W >8-B#nܑ'=i߲_+Ś\] {7LDA,:f>h]HMѰajBhQ5pX~7~j>>;nfbx!#ikCMExǚH_\4F$[s€>YI?zYI?п%FULyk* &oZF߱k+ak_~H3%]W<oS˲=v~-|c5m3EF*ػ>ǒ>ıg{ĸ~|6?t{tVi9,w'4|9/+&#( WDj쨋35 ~Z+ҭu}ē)B*Ysi5m_O᭦.$O`zVwnqL$C3־IgۏGۈ5rQZ],WWSIBNAړW>uh䕕c'z[Mkt6 .mYLbyXY |ExW߁|#]Ie&gnn.#b(\NOҲ)~p|++xRKL,JZY\*D3#5!{Rg#i3}k~:zwh1ђgVL=NIJy ?*{{Y9aG,Yjmv1|y߅|jw(#%k;Bیs[2?a5M]Kx#<}AzĒqfό_Ey-~q$-p[_#QE]M|efEI{cv6x{y,ȫi$@& f+J'u7tM>9Fei-\]ۛ%]s_4k|Zn|D-3Z2[^Q5{gmhBTYyrym&rP7ր>ht$Kt~϶[anXP3WoOOD$u~ޖ227~# #ϏumfP^8A#~TT_^447]XD+ Zx o^ּwcZw51HE0ѡ#{nhi8e׀` |ERϞ?K ^OxFFVP΄m"oK߆<൳-R]CZt7+8;7&Bou^Rn80)W-lO^"5#EG\hJkмY=Ep g}@Q@Q@Q@\~o/&M_p?]]_WWٿ'¿5{Q@Q@Q@~#{W~#{PQ@Q@Q@Q@Q@Q@Q@Q@Z7ȿM5{L]DZ7ȿM5{L]DuQ@Q@Q@Q@Q@Q@Q@P3ZtK/}n}7Eat? л _Mn@_g'bh?.?[P ~wI? O]/&@3B15Eat? л _Mn@_g'bh?.?[P ~wI? O]/&@3B15Eat? л _Mn@_g'bh?.?[P ~wI? O]/&@3B15Eat? л _Mn@_g'bh?.?[P{^ mSTVbG`>_#_į~:Y6 [B~w=ʧؚ>C?ɸ&?i$ޙQ5?xg~ݖZvi݅XtPz}y$7-@_F((5;+m.x&Uk%ѵ@~+5>.xM+>;c>bc9@JJ W?r>4I_k? [ @ڪ5z2v?QCH̪轇PP>52|;Ӧ4I!p˴r90H|H<#+w=kB}ZD8 @  &qd@WGn;nzfÐA>DpTsaize?f8;XxOX/ޙ};{i/͉ vW$+nwgoV94M$$gMĜP +t<?h>9zic‰}d }e$OدmEsy?l)bT;X$1$z_jysߧ';>?`x{U{cz][@HTĘ;X+? tjI-ۭ.|=GOZW;Y&ly3N( I>Y"mKu A_7Ins?&?}y~WOW=OC (+ q$W_+giɖ:+ &[O䯧((-'k~wK ڀ?Z>~x@O=Jϖ+W$ ]M~6I>$x[&x7CծciRD;#^ ־;)ȹ.sǴ5gB~dtP7x'MOc X۬KӅN_1_Z?8o {kt@0>t@@?xω4k]-Ι,:%^6ck"9<w/xw6-IS zğ:Q.lgˑyVdWIE|rx>^;MIqpsӿ=+[CR? k$63ܒI&4P? 7twc iA  ]"ϊIyuy`0r噱z (5X"B\F|WI᷊D5*Xm¸w_E;7/|cԡ'?  »/4k5zި$Tro9fltJz(g>#^y.[}#3?1Q~Ο IxYҥP!a+:(g i&sɈVb96\&fBiMK-q!0.ÞA8*;~k4;@='#Yjzd vɸ3bӑʾ_4MT,c9?  Xx^#5,<\/bI _;IU@j_;IU@j Pəo?m}_(7_ן6 fm| ɛѵwq$#7ZOx6 QȷL sU5| mFҡ>3ʢULdϥ}ME|g#Euhpn؈3Ԏ]/n|u@Fٵ0k6 ї=ry>]>)~Кam? RyO g,qk$< olt9"9h!<xI%G<\O%W?i:V-b#Mu?>֐t;hbE^+@oCZcѭ]^j2 (^|4|࿅%k)5iۭxV,k}sĿMS7~UPx\[(b6`ۃr+fٿB#O&\C |ʚ&_"\{x =_MǪrG~zgް4~2i--šC"#hqFR9nMK 002zÝ2E/a6vQ,MXEtTP7?Mo7#J/Eֶ|i"n^3&W>!/xZlt[nE*_[| 3WGs^xR$I>7XȊZv_$?<'h7ot bY 1D,ѷ\~51x:G]|'SjIɛЖͯ_fGo%h((( ">? /. @J7DW`X&v(((ROc_zOJROc_zOJ(((((((( ]&Ib]ɕ(w ]&Ib]ɕ(>(((((((((((((((((((((((?w%+;: 4GG#o䶞#붖壁ƹwnǮ*9SPw~Tt/G7wEGKR5?O?[G9SPw~Tt/G7wEGKR5?O?[G9SPw~Tt/G7wEGKR5?O?[G9SPw~Tt/G7wEGKR5?O?[G9SPw~Tt/G7wEGKR5?O?[G9SPw~Tt/G7wEGKR5?O?[G9SPw~Tt/G7wEGKR5?O?[G9SPw~Tt/G7wEGKR5?O?[G9SPw~Tt/G7wEGKR5?O?[_?ڟN~ |N<qF̍ >_&?g,~(iePzREyojW8u#qєA־Gk{/j^&֦kHd!H#b aT=3_\PEP_?["`&z-_0?={KLpG_OM/2߇_%}?@Q@Q@~7i?x]Fട_ `OIg/BWS@Q@~3io =FzdiGMVKkKeF $u\([I eICoM2V???? |)M6=+CФђqI$I$I>+ɛѵ=~Оg}`^y$n]9^+¿ i>驪w|!YH0 ktG@{ؾhu?_wâ?g5o&tG@{ؾhu?_wâ?g5o&tG@{ؾhu?_wâ?g5o&tG@{ؾhu?_wâ?g5o&tG@{ؾhu?_wâ?g5o&tG@{ؾhu?_wâ?g5o&tG@{ؾhu?_wâ?g5o&tG@{ؾhu?_wâ?g5o&tG@{ؾhu?_wâ?g5o&tG@{ؾhu?_wâ?g5o&tG@{" x{Su/:Ht H?%s"_Xâ?g5o&w~ښxJR./'3M"%Wq蠓QE3MNÖɫO &mao7?+{-}_$ K_fEPEPEPE߅y%} ɢ|+,W_\~o/&M@EPEPEP_q_qEPEPEPEPEPEPEPEPM2/M^+/Q%xM2/M^+/Q%}]EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP__Yo?|lмi>? Yy*LU@~kͯtKVU .f)bO+"x홥u)qk QP_,~@Լ%tsy/ ?hOO_]M.fW];KW"8~=9$Ahۏ MvMnǏG~5::ƣYZ^|WW0hX1BGH@ h:n4Yu?36qdT~9k_'Ϗ4߰/|I]N'C[=8 Wp5NJ<3ؼGfωlNZ;oA?sߴo犮tSbEZJr[pjeՇ:OU$mY-@nio[A{:F\CG-dr+oh0hzf;Ek ;ld N:s@5+g{c34~ ||-u\'WQEQEWwK ڿdk I%@ ?!+[_D%u4QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWX5;o[&+?,g-PKLHm|e7?+{-}@Q@Q@Q@\~o/&M_p?]]_WWٿ'¿5{Q@Q@Q@~#{W~#{PQ@Q@Q@Q@Q@Q@Q@Q@Z7ȿM5{L]DZ7ȿM5{L]DuQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ri:;Ѯ5kHx8\W#r?&1Jۯxf} E;Wфi=+WE?uր>7#~TWx"uK D_kYykyn>e8 rѷck?`3O ިa[xE<7J [/و'q1ԑ7:Ƒ'{oKnKXv8W`vt[GvK8V1,ՏmE+g{c34~ ||-u\'WQEQEWwK ڿdk I%@ ?!+[_D%u4QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWX5;o[&+?,g-PKLHm|e7?+{-}@Q@Q@Q@\~o/&M_p?]]_WWٿ'¿5{Q@Q@Q@~#{W~#{PQ@Q@Q@Q@Q@Q@Q@Q@Z7ȿM5{L]DZ7ȿM5{L]DuQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@~`oSa__-k۪O鿳OI)=7g2!<+s'㍵<_xi-R"fb0P(?W`tڽ꿞?S eΛcqucgnSdώ2d4WmmHbH_)q 4QEQE+i+~3yX$c("*7VZogo[xWlrm+aG8$~4{-u\'W|~:h|=G cJ,tR#2IeI1]6GΓc^˩_Z@3w'' Z( ( n~7?WS|Iw.ƿ|Sxn}Q[ ~@[ /~8׬Zj8%5H./ 3qhIW$ ]M~şg|$O6jY@~Mj(((((((((((((((((((((((((((+O,g-W՟4|_mZC7ti֘#Wr#1OsCoxE8oJmYW h '&oVBZ6?7o=< [E$}Ml*NIC? kAgjs^ Ou <ɮ #D((+?n? G#|>0^hvX鶖\=‘ğzV cHV((ďyE4,|4|;$vmdWH*y 0~Gx||Aw7]Nr³u ٨xʌßC,d?}@ ~?7w2GH_ǨXE_λB| ,;g~%^+DW^UhP˔ ھ('gËO |Y Mj cp?3zwsQ/0X\Ik Wu8<-4Ď=<};/r1PG?ك Q3Oßp ½gM5mqƃ,b`&<7 'Ŀe xź޹u=y8,p WUwz//4V{3f*JQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE2W{|?joCח?ږ8|`_-~^k>C_Ļ82r6~x* {[Xn w\?W_=~n~`1W[6cOv?g#ػ|%9׾3|11ŧ>i67W,0_QW_g&Gi4+y$bmВI{מ۟ Eg)k+o ,JfFlp87nyំ~+?HX0YVuQEOXҭ"MA-o*0*T(O6 'Qbݘk#Nh{]iirj)|c|}i?ٛkQa(5/+?nZWrڜ$#[@hW⏍v>\vt|okV]ۋGR>LQ_U7S;omy nC0=4Q@Q@ t|fhCjPBc'χ:DpF TFFѺFYOBjtjπ k/xOrm.5_(h{?:ߎ{k*a١.[;%y ΙQ#7 > 8C~ EXm:VѤ <\kox4Qχ1Q-=B=ӵ")B,BFݎ=+fa_ÿ0}HȒ>^G}=@Q@Q@Q@Q@Q@Q@Z7ȿM5{L]DZ7ȿM5{L]DuQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@2&h^ 51-Xcwf>־SiL.bMr\?>?Pѐѿd TԾhiqss,lZY0Y=IiL.bMxWc |?o-o⽶kŐǴyI~ _ߦ1DAMPt-7e:~icP_8*+S}051%o*cKU|?]?g _N _ߦ1DAMP_3 W}051%o*cKU|?] eړ֣:eijEsi~oUQ6cKU??ß |  G//;wkiL.bM}; c/~~_7@1&1_&}D+ӿ???~/T>LawhiL.bM}; c/~~_7@1&1_&wo"y_.,__+\<*9/1%o*cKU|¿[.5 T` 43 W~/T _ߦ/} WeKCCW{K1 _Df߅ eg]𷁴 YP/m#"EV'J4ɭ0\J( ( ( ( ( ( ((ůt[R;(gv)f?=c s}V-7ՂHVDytb8ϥ~hC_]薯/%|Z+xvvv27YcQ`">iL.bM3 W~/T _ߦ/}D+?]w_7G1%o*>biL.bM3 W~/T _ߦ/}D+?]w_7G1%o*>biL.bM3 W~/T _ߦ/}D+?]w_7G1%o*>biL.bM3 W~/T _ߦ/}D+?]w_7G1%o*>biL.bM3 W~/T _ߦ/}D+?]w_7G1%o*>biL.bMz`ǏZ7o{۝bO"+4flק| i~A`?7z_ચ^Ŧ6>3m HtQ@QEQEQEQEQEQExA=@&B"%Xw3ƾ-"FA0P&1_&x{1?|7ӄ {ƽWҮuo_Hp̿]g 𾓠 .a-bP@"_Y _ߦ1DAMP_3 W}051%o*cKU|?]?g _N _ߦ1DAMP_3 W}051%o*cKU|?]?go Vݩ75M c/~~_7@1&1_&}[ w51%o*J~_ >~ϺfЇ1ܕ);uhG{;+ַX!k!#uzU|u3/?e}@Q@Q@Q@Q@Q@Q@Z7ȿM5{L]DZ7ȿM5{L]DuQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@W?Bzz 堹7_Ƞmx# 0TG a #2qyeb'7aMVR+ #Y\$ gi8_}?o? ]B=((~C:{Ov,hZ &&v]؅r2Hލ icW=P3_?;R[Kۛy& sm8k&x>#=/f*>Mzk/{^[?UV|ˍ+PrиS⽆ ( (>Lɝi[?`qgzpo$<9ٓRյ3Lmz7wq|pP}G a4/PYErC?mx# 0TQ\-*[^B?UuW' k8h_1ׂ?pпc@eG a4/PYI\-*%|roiC$&MQS(O˝:Ҁ?AWόmU=xfO"? ?Q_q|xk/]V= GƏ$>55j7`WJUwoo9X떏ݰf[*X4OǸk ȱ.2IXPt;6˻4HՂcW=P3+xU>k{o;ČܹFi3'ώ&~ͫ:WY֖L8vKsJ*>5ΧѧoZ$,:uM,s|ǷJm p:J?Vr~ j~#[iք3JbY3z W$xO⟅j7ZĖztqٳD { 89h,LxuСR4Vb3wc$T??j_n (ο|;)qs_Pù?WqC*+4urCA?|S 3}?xr όSkGK+M ?)硔E~~6߳gÖ|c4 rך~qwl>%G a4/PYErC?mx# 0TQ\-*[^B?UuW' k8h_1ׂ?pпc@e~6h<`O k8h_1ȏ,O|xTFgB0VuTpJ@xO%k{ "Dt4Tڅoicɪ%Ԯt -,zHJE~Bj~1_(kM.Ku9+9(AVY#YHS#+?ƟơG?> oX+ՐrqRs^Osï:7eW.,-d}E|A> h(ׯ/|=9ż WlZsPk #/≴}%䅎ELJNW/;ZѼ%ŭZ#L~naH:g?KgGVs0S2c{h~=v{V ucuܞd=8?Կ4{X֗KdHc)p=;s@E^#cHjzO;XϦVZ0pY;0g5WW6='S57MII3?BFWصwLѕ-QEQEQEQEQEQE~wk߼c"4_K2wWk߼c"4_K2wPQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEG;Fʿ//mXv\}Twud\Ǽ_N_H]5@/9Ow^gh)NM~Q@wA<5c-sN֤ x[Ob6E!x_/lP:|(~!?g7_.@EPEPֿg:G|[ <wi}y5m*7N6|=yf.Elgnc{o⧍k֤3Jf1{'8(>3J OO'y y8-/c(=VnB+n!edC3c_O'࿋xK 7}ZۇY-sº7zW"[rVQ^2;{}kӿm/_?|6~*|TG ٻ{s^Y~VDkIG$Dv_;qSH6P: g_.=+X5;t,G(He9 ZӾ&_5k˸ZO*pxl57ԥĶ_e'n^y'Yֱ6GxNacӈH PwM}_ :?M7/ZiAJ.v$5HoY|B犯UTmRrYA G>~O~#j^1ǫh>!*ҘsR8;A㎙Ya[jvD7p6$c}K@Q@Q@&R? _N~_<_xH,{o|$.YUIoWT3{v?8W>%\r|Mk-w}sn"&(?z?G9Ow^gk>W~1>$k>/Ӿ-3EZ@6w_SC׆?z?_sͧk7ٮZ&M~ jRMi=D`S>W:ى$>s@Ô'&r7|SC׆?z?_P9Ow^gh)NM~Q@?=xo?>/< x>$u[@i:qxoe*ht 揧iRV|Fw>*` bpk65+Io('.fp 2h cLuo~WCvPƱD T@~N8Uo|CoO]xbG#bcBB85?x+ _??%;%ǟ_(Ja(( ~0| =v`#H[p7PW ~Gy&F;/)g#N)I?Z/.-]Ls4vs3 z׸~0"Kg(O-LScZn¿ğ |)%u{{9Wpy iK%q`a-5l3~{;)@%Q@{ ;/|\%בT&H xi_Oa.?OYgRg=x_!?7`?k_(o8bT4X|Gp [ui5&`htGu{)-Ʊ^%ƅP9^Gߴ7xoIUmI:?\sy#7_xKˋ EqMl}u~+x/ƛo#REUޡWB% -]⏂ .88{08 Z\٭iЌ$cv8n|⿆Im-{;C  n=z0~ xHmHcŞF',{MuQE?u/)֩赋ɩxKuqlO{_ÿw4|zAV-~!),5IZ:\b}>$| +񇄵]D8-ul3qοh:odt]J@U໅eB\P_w|+IBþ0eZYkVqo:;5a +oŅФv&HПMuhT+oG> MJ;$4@?=xo; 35!E~7Ô'&r7|SC׆?z?_ǭ7mCQ}EAiamk,i' yoZxƿn>QE<[6 sճc*IhOr7|SC׆{,x7Vףӛ+2Yrv p2y=C; 35Q(_OxXnu Qx Ӹk4}Zh7v_$^r;`BĖ(5߱VCxJxLUHϚr 8`zq@5|%o?|/ھqoZI%4Y=?O0xu3g%uc|),}C H%:ď[I,pW9$+St3Ώ+(: Y)`*æyhgPs=gҵKMgA99Uqw0 gK{G^<~__]6=/nǘ7tdo$Ҟ&}dLHk`>yN/+[MԤҚ{UHJ3?842kk) S rX&11 tV~5^%W@/n#b?I_io'֒u:C[XQ6w|?xw uk F[ 5-O?yW}#R>.k^\y ' ¾_k%|=֡%F #O4߲u?4N9a4 UQl̃X|7N 5>|U||b]MBkTHLž2z[^$|_} xT T"˅$ǚ/m7W!iui|R;X[~~lgxK mtXtKWAy{R[ 8#5S_3^Wt\\ (ExOm;*?sIx&"ab9'Է1J?6]^7kpA`q^EyO:d"ڶTfNw>@_zPEPEPEPEPEPI wZ#XLRHX #W5żWpI$ȥ^92V? \Z\1yv _lU#:m}y]/,~$VjzT䍈>bF!׌W>ɇKoJ͆Gg89߭h[oIu4Huk)DҠКsr]IZC,I9Gd`WgN_U'ki3oE:y)bZs$+] ~@hToxkGh,m!bFyW?_Y['ZB΋?Əz?t_4\? <oŽVtP3c@_I3?BFWصgz?c XOO=q\!G1+`}g@Q@Q@Q@Q@Q@Q@Z7ȿM5{L]DZ7ȿM5{L]DuQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@\Ǽ_N_H]5_?/WR=MojPQ@^Xjn⹄c g#NHµ Dt/ Q@];# ר/aln.G;U';Ok?ᖅ;2G;r[ѼCil:W0$@÷C7Ư5^=OR~ vD=EZM|`.hW3yNpO>C|Ȱ?[Ӿό<}7>i>)}+_RbxK~ѼGi:|niHE 3^y}{Z/~ |SyYy}6dc6 |ύçJ{{'O@t8'FFrGB.|f~tl|5s[ ]l%u_5~ -O?+%YGPM9KKKWҭ5 ~7m8si㏇χ[WNHbBF$d? U_gakhfBGٟ_u~lOAЬJ_["`&zd?oGC浣Z_ŢWw+[)[>.3Y0m 2UUsDk~i]gv=W|:t?wď;ӵ3yvt6`$ր?Nп $U5o iVz]ŧ4ۻg B#'חMaxַ֍/Ɲ{\a_h] Oo>!xGkuO6,m ď[heRſxgQmc2ܘ@h db8":t*M;:>4riVVR#e 3iG":t(GB.k@_ӿ? wGEd#NHµ" c8aUFS(((((((.<3Lͤ+[#1+NGB._ӿ?z(# wG#NHµ Dt/ Q@];# ע2?п $G":t+^GB.C}is\ilpAK#`*==nQ?e?A?j "iy|蛏@ ~ߴ/(7lD̳ZE,(R6m% Yп $_̗_|8i4WE7#L UMdլcm4?o ^+DOxOUF|[n;6)\񯲴:֗iYiZlwpʶє2pE~Ghi6<]]fMՁNE~| |;kv9&VBGǠ*sHGB.|W^xǣA6$caeocz)\|k4Cj^a y9i_ o<=kw>57-+UA|%2G#NHµ Dt/ Q@];# ע2?п $VtM;Kv{; [Gah!T'Wh((((((i"+XnvOuϮPG":t(GB.k@_ӿ? wGEd#NHDt/ V@];# ?п $ZPG":t(GB.k@?[-"KXZn`P:W߰t{+t63J4eKdf'srI'E߅y%} ɢ|+,Pk3ğ>Nľ1д[rnmǢ c _ 9xmIjHa@8R ־6P7E$ ezooSBK=ʱk[u \LG@f#NH²|Q<j՞B$D' `ɮ+eᎡ넲o 74y?+ 4૿|BZX$7.2k{ wG翳_~;Mۢk&Dwݬ~]h# wG#NHµ Dt/ Q@];# עUP@EQEQEQEQEQEQE~gW. I^;~gW. I@WQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE{Uj?[ڕas~'9#ڏt~EQEW?Qߌe?17}qi/?n0!ҦyOFv9>`,J~+f %&9|>ԗȏ̍vQaSQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@~Cj>0k &nڭ)Ηp^k/L߅!k86ͬjL$Cx4 UTm)_IO??f|?u?J rgBW_~g~7~w? 5/S񾤪ֲ|dfb?f?؟_6WZIەHϡ4z"񭯋?'Tu#G$v'[7^mQq+kvSx8[;7POEp/x]8|NUMAvݐ9T"Q Z(((((((((((((((( ">? /. @J7DW`X& 3r##X 25 /Y\-͍t'ko>Cn}on:g E܂ zPZ̆U'&8?-_ ֺ_?}gƺV?IFZs"XdŸ7Oa^EQEQEQEQEQEQEQEQEQEQE~wk߼c"4_K2wWk߼c"4_K2wPQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEEs~'9?#ڏt~Hdn7+?55蚀K멭5|2A* % o37xoTkE~%›?緌h??M4U~__o?*S @X᧼&qFG5~xD:E-~!j?YUnj4-R2C^,98ܯEt=kKh.7m@(B((8E~L=_ZOne_-q,jJU- = &[O䯧'3~^Ҽ%J:Gsr 3}wğ|=f+]'_٨|Ij?E}wğ|=f+]'_٨|Ij?E}wğ|=f+]'_٨|Ij?E}wğ|=f+]'_٨|Ij?E}wğ|=f+]'_٨|Ij?E}wğ|=f+]'_٨|Ij?E}wğ|=f+]'_٨|Ij?EB7h>_2hz{+xNJWBy'QEQE{KU-Eu~&9?#׿[RW~#9?#׿[RWQEQEQEWT׾l<oz6خ5\%e Bz?}wğ|=f+]'_٨|Ij?E}wğ|=f+]'_٨|Ij?E}wğ|=f+]'_٨|Ij?E}wğ|=f+]'_٨|Ij?E}wğ|=f+]'_٨|Ij?E}wğ|=f+]'_٨|Ij?E}wğ|=f+]'_٨|Ij. @J7DW`X&/?c={v4ie t˽Q_g}m%z,[J2Nl(((((((((((( ]&Ib]ɕ(w ]&Ib]ɕ(>(((((((((((((((((((((((((((((((((((((eYIgIY#o պ(CO;??P"(CO;??P"(CO;??P"(CO;??P"(CO;??P"(CO;??P"(CO;??P"(CO;??P"(CO;??P"(CO;??P"(CO;??P"(CO;??P"(CO;??P"(CO;??P"(CO;? 1ZwMay0DT_`?RO[?G@M/ E~W'4R<8n?+n ( ( =p*d?ʀ??)+?ǯ&J(((vpEum7lHf@DY =S?J +*=?Wr?| BWUG*ЕAW]Er?| BWUG*ЕAW]Er?| BWUG*ЕAW]Er?| BWUG*ЕAW]Er?| BWUG*ЕAW]Er?| BWUG*ЕAW]Er?| BWUG*ЕAW]Er?| BWUG*ЕAW]Er?| BWUG*ЕAW]Er?| BWUG*ЕAW]Er?| BWUG*ЕAW]Er?| BWUG*ЕAW]Er?| BWUG*ЕAW]Erq%42+<>*˥>䮩UcP uQEQEQEQEQEQEQEQEQEQEQEQE~gW. I^;~gW. I@WQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEOxcVỚkY Kg$L@u='5|bvV7<_{R9)* Hip= vyzo0}1C4π5ᝌf[ܴk#:W xg^ukkėK}uLd|8LTw7De./x|g~њ72]z_Ijpiv i/Z cH-|̫,Fh?K-rJXNk1I<^?:/^U]+ᆝ&$"ݮ); @5'߳Zx3PݖQSW_-ѿi/X><k<xcUUIjrN?8WR ѫx]-.b?yHƿVu(ZO]eQAf)1#< 8mdy$ev$(R"S%7 %('|'/M;TJ$)J6G }/__-ѿi/G?KtoKP_Am1^_u_ſ~(| 9准|ENFSɇ++%7 %(xnIu"+[_"~4Z+YA$3#Fz2?*xnI?+[_">H6>u6]xm,UT`@ёk=rtHP1FA_> 7j-t =ŏۤ1M&Rfm =++[_"> gZxGOMB}֚⼢?.7=pXq_RWW?KtoKW˟V?zeiLA*YGHGLQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Z7ȿM5{L]DZ7ȿM5{L]DuQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ShdWuFs pXZ}Q@gƏ$>55juJ/þ5{oh:noEl*7rqk+GD~Gx+ @~8 ?B0q_EQ@/(_N<+P ?xWQwTP  //'T_]k%6VHǁX lhjT  //'T_]]B[ Ѿ)…aD¿*( DžUWuEp~8 ?B0q_EZ+jZsqαUqȠ/'T_G(_N<+»( DžUQ  /.2xҀ8OP ?xWQ~8 /'T_G(_N<+»(4`87E6:Ol-ikl#GEU_ÿw4EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPM2/M]'oȞ 5ZF 2ޤrǛ#rAklNW5gאָG*\E|!A?._jR3(ƏjR3(ƿ::?TÐu) W ? ?ίrE?OtUs~?=E+?2h=E+?2kO]'G9\O*?E᪾J ?᪾J  SIQA?._jR3(ƏjR3(ƿ::?TÐu) W ? ?ίrE?OtUs~?=E+?2h=E+?2kO]'G9\O*?E᪾J ?᪾J  SIQA?._jR3(ƏjR3(ƿ::?TÐu) W ? ?ίrE?OtUs~?=E+?2h=E+?2kO]'G9\O*?E᪾J ?᪾J  SIQA?._jR3(ƏjR3(ƿ::?TÐu) W ? ?ίrE?OtUs~?=E+?2h=E+?2kO]'G9\O*?E᪾J ?᪾J  SIQA?._jR3(ƏjR3(ƿ::?TÐu) W ? ?ίrE?OtUs~?=E+?2h=E+?2kO]'G9\O*?E᪾J ?᪾J  SIQA?._jR3(ƏjR3(ƿ::?TÐu) W ? ?ίrE?OtUs~?=E+?2h=E+?2kO]'G9\O*?E᪾J ?᪾J  SIQA?._jR3(ƏjR3(ƿ::?TÐu) W ? ?ίrE?OtUs~?=E+?2h=E+?2kO]'G9\O*?E᪾J ?᪾J  SIQA?._jR3(ƏjR3(ƿ::?TÐu) W ? ?ίrE?OtUs~?=E+?2h=E+?2kO]'G9\O*?E᪾J ?᪾J  SIQA?._jR3(ƏjR3(ƿ::?TÐu) W ? ?ίrE?OtUs~?=E+?2h=E+?2kO]'G9\O*?E᪾J ?᪾J  SIQA?._jR3(ƏjR3(ƿ::?TÐu) W ? ?ίrE?OtUs~?=E+?2h=E+?2kO]'G9\O*?E᪾J ?᪾J  SIQA?._jR3(ƏjR3(ƿ::?TÐu) W ? ?ίrE?OtUs~?=E+?2h=E+?2kO]'G9\O*?E᪾J ?᪾J  SIQA?._jR3(ƏjR3(ƿ::?TÐu) W ? ?ίrE?OtUs~?=E+?2h=E+?2kO]'G9\O*?E᪾J ?᪾J  SIQA?._jR3(ƏjR3(ƿ::?TÐu) W ?񯉿_/E_MwZݟR|zO"Q(rHps~??# ߨ~ȿtx).-hx1||r[[j566La?e">µẒ>-!'g8XR־;ȟKuΓK5k-P3x??Z_"}YX?*7`WJS?Hx>'o uk=.ukh,Lva^Wp%~EQE##;A 0t_!~O?g}ƚ5'W'x1l+a^k(GOZOo~; ѼRuOc`7m@7+ho(M~OF a9xH$ % 8p?੿ t}m5ٵ]6."_2O@/=W쯦jukE/5G@ 8U>/>:|juM.RH \+*M 1 ߶oυ |3=-VM@ʧ?>c"YwU:ƱHXAarp5H#Tq%7@-aq/kYxV?aTbmUa>_v1@w)~S<$ϗ/}v^/};RukT=/`22O|y>'e7l:#u}Tt~"Cgdqfx(ςPOݏ 鶞xǛƀ> q6 UoN;}iGpݐ7f=_&k_ۃD_xN/]-~dr}X7v=C{Ѿ2xFoVY0AMydGw_1b )- ,'ހ?A#koxWGSAiE{ W DI0 ;0ZmU(LmǶ;Q@Q@|_c(?uK?;¢(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((?e \Ci&+ m9l-==+ب"O'$l#KFoOrs|wvxoxo÷^y_]YMkV[e՚9DV|OM^~ յVPhϗzGD~Gx+ _??%;%ǟ_E4QFhqk~կnt?VTYnl™Sk QU~g߆:w]BSeTUYXۈ!xAPG1^;~E cCֵ=VJ%V" .z׸f~߲ipOkZŁ qnURyk~1|_ ?ψW{yT7 |+<ѻ h/]^ hHy}G~ .jm$ci syᔐq> |O/,'[xF.vh',zpO溬  ,WLG7m>WL_P7m>WLG7m>WL_P7m>WLG7m>WL_P7m>WLG7m>WL_P7m>WLG7m>WL_P7m>WLG7m>WL_P7m>WLG7m>WL_P7m>WLG7m>WL_P7m>WLG7m>WL_P7m>WLG7m>WL_Pߵaeys"?ůۣ¾8<5kX=1r;آ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (? endstream endobj 217 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 489 /Length 64352 /Subtype /Image /Type /XObject /Width 1369 >> stream JFIFC     C   Y" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((9&(RpI+~}>{xe6ڵW#ǹ_+T>c u%3޺ 8(.hגGEzCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;^q? ?zCT+;X O#/+ *.;khχ?t_Vn7ݚ;]U~vq%ךKmoF8U'5"yZ( ( ( ( +_COhjL_nG< ItNY!@c=!1$TtG//_8~Q_?!CGP :G//_8~Q_?!CGP :G//_8~Q_?!CGP :G//_8~Q_?!CGP :G//_8~Q_?!CGP :G//_8~Q_?!CGP :G//_8~Q_?!CGP :G//_8~Q_?!CGP :G//_8~Q_?!CGP :G//_8~Q_?!CGP :G//_8~Q_?!CGP :G//_8~Q_?!_K|G%XYf?B#Mi.#hSG vb5QEOaz54bH%i?ּ~_8?^Q@U/K/bCURij:Tvcg| d[$:i4m>O?k|6CFkA?@{_i4m>O?k|6CFkA?@{_i4m>O?k|6CFkA?@{_i4m>O?k|6CFkA?@{_i4m>O?k|6CFkA?@{_i4m>O?k|6CFkA?@{_i4m>O?k|6CFkA?@{_i4m>O?k|6CFkA?@{_i4m>O?k|6CFkA?@{_i4m>O?k|6CFkA?@{_i4m>O?k|6CFkA?@{_i4m>O?k|0GQI_FMLN {Ǖ5] 2ֲrdttS6*4((((((((((((((((((((((((((l8#_ kWIyl v'157W9`QEQEQES&my\DR}4,^>9R:a>/|DGWF6$*p1xk6ƣ uQEQEQEQEQEQEQEQEQEQN^YA@ IOǯ;oyzJjlyJq`6k,@HQU5b]֥\gajI(Wɟ?kkڛi6#h۩<뵿*m|Qr?I~_ay58PKmJ/.|zQ@i\> h;dIqC]QEQEQEQEQEQEQEQEQEQEIoq576ҵ.$d8d`r5/GKei1x?~rWJC|< Q@~Կ\5++j_.s@W|"i)Pnn`㏩Uk־B鑎m hQEQEQEQEQEQEQEQ_7_⍾mɮVG g¾[o 7R^eêLbE0(T __ PN^%N|9TM"ԭ$7yޫH:}:OG1Τ~ ((((((((((((((((((((((((((l_BZ<_BZAEPEPEP\_Ə$-l ҸI[`ٿ@WKHu~QEQEQEQEQEQEQEQEQESXQ/:sO k?7k_~77G@~?f׭g)vJ*0ʩtcS^6[Mj%&A6Ѱʢ0I|g_s][?+)U/ =(,rXMr>_5>D)6wy=|g|Cg5ӵ( Gʹ?O Wތ6q_ ~ZRhv}n?F kOmx7Al Aր6((((((((((+oE?W7"?(>kWWԿ\5+(}9Nh_1 ];E](((((((?(M>;hwQ٤>o¹&7χznes܋Ucϑ/p?Ğ5]5Teaԏ_g/Gݭ.nvخ}ljk/+mOoEgg Q?Xu1)n&½u %Ld@uP? ڧ[MKUT>g~UZg'4 zc Ou4"z;;WğK߆?aTy3#*?%m6:TI|lw&"1ξdYx'˰Nj :\{s$>hmyc%/$ҰTEY ?<}%mbxثd4т:WO>*Z}ةUgrI#  k&_TZAn29cz O\X_^oS WgZ74X5ꖺΗ>|7ތGQQ_8 Y4 x/% H&>1kl|/lkộ.mxB_?F5{wKó߳=Km('A%67[ӵ? jZ,ug4hZ&z?{=3#W;>"|9._Gֵ2g/yU3'!@~9|<m ZHe'G @G^$WlkE".zo?*K¾߁m ޅaCp'Ԋ+ O>LQIkI_eǔ~uG$|MNc;?u_7a~>8/6l'\5gO[\K[c޹?€:O[7/~[L620I2TσY |:XjW67")W.*1&{,Gw>?NyCIf6c y 봯@F>xK‘ɶ]NK%?`GTxS&>ca3Uۓ_?iM H$MX[2,~xgCtke oZg1{NÚ=g.qG%bJ9;H 漃g{Ww72$ZlKkMF*?+md_j*pk|}60$xtgu}l?q no_.Mv_joE)cH2+TUeet`YH!AE~w~OuoTZ EpFsڽ _W|E-ZK)e2C&CG!#&>͢((((((("\+>.ȗ'vJz((((((((((((((((((((((((((l_BZ<_BZAEPEPEP\oH_>,Uc쪖Ůh|1]Bє@Rև4 뺆z; Sx?B0Ϡ(((((((((M>w?O k奔Iu'tn̚Mgkt.[v3zР#?ׄJamy 2^c&1OB+'W0)-^%8 ڎLȯ]RխanF#@3[Vehi>Uɟf|k=LӞ >Gt=YS+I?c3CK2g'?,O{J>NZi"OZ*)o.oG򶱞d((((((((((+mHߊ)C_"3ROW?{Y1]겵+u@ge(ڗK\yEzK%]pxWz9?WcƲDŽ0l<>POEPEPEPEPEPEPEPLP2td`A4( 焵?֥"{`2'=]?ú]YA鶩il#s(?\6_I'AIeZviuuzJ?mcWޞ%j+gF8 _ |?kdž ;ifP9P޽ᏉƁ:K3%FZR'\GPFE}?)COkZE?y$qm#O.[jv Iky6 x֫LxfcwRD48xrH#B쏒}Ze }J'?e"gNGQcկn>HQbV;^~x.tktv{7?G^(¢(U㻎Kyt3#Dz`AueCW_#x;գR@xBS>M~WwbV<z40<P5%+c&ˉ?;&N%G^M-W$53:39ḩ+a(cP *@S\ti7:Vaoi#lwI99~K>9n]Ei!OA_Ė$MrS_K{ i: 2\X[nɐv򮪀>Jk~Ϳ4M>Mּ9{W*Q1drW? M^ҮqE:؂+xGA񅙴״M?Zj}G_W)3V$ iyu/_4ZS} <BI?KM/֏RSKuͿ5?УO+Th/_4ZS} <BI?KM/֏RSKuͿ5?УO+Th/_4ZS} <BI?KM/֏RSKuͿ5?УO+Th/_4ZS} <BI?KM/֏RSKuͿ5?УO+Th/_4ZS} <BI?KM/֏RSKuͿ5?УO+Th/_4ZS} <BI?KM/֏RSKuͿ5?УO+Th/_4ZS} <BI?KM/֏RSKuͿ5?УO+Th/_4ZS} <BI?KM/֏RSKuͿ5?УO+Th/_4ZS} <BI?KM/֏RSKuͿ5?УO+Th/_4ZS} <BI?KM/֏RSKuͿ5?УO+Th/_4ZS} <BI?KM/֏RSKuͿ5?УO+Th/_4ZS} Ta+x G _*i=χig [a>J1+ԭuZIh0,@#Sɯ#!#BF*@)PEP/ w ڗK\yEx'3xGR@mdf?xzq\ދxfYA1_b+GktUͽ֮Z6ԐkU|ijWC4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4?| A;ƏM 4W [V%g<xt..c)&pxǘƲdbŏ έqjS^9yloaTh ( ( ( ( ( ( ( ( (5Vљ&8#d¨Ikukq ҃`8ח~ԙ?˟ [pbwc}kڥՕ7f:WkIKN8;z󿇾0griu@KF񞒠PW70[[3EXV%x-mMMrNkr'gCcC{{9_;;_A܎I< Y_?cK~4]eugkS M8@dWCqwof.!Bpi}2M| Xk/X!`'2kh(֭bBimw'$Pq bH&<|.~k'u|-<<_+L,~O_R<3*"1IPVXfW zg¿ eMz̢q3cJN)L%}qcЕOTִ .Zu2ǟO5>1l>~Y,L}Xw#sƿ- uGOimFƝ(;OEo&YI_]"ȌF*_._~ðŹh! k?gO'_MgxZX2Dݴ9tQEQEQEQEQEQEQEQEQE6omӏ!xs-komӏ!xs-h((((tOzu6V""h7 ,>'UWL؂(N{Mr0[[7GBz7'v(?j_.s@/ w (i:Mַ}M,@[UGi#$\*Ƿt7Bvjv?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtOf?}'@_DtO^:)+\.I θo|`DgfQk!?Jz)F9GYN4(((((((((ړHv]-jל:)X( K9 ~'z7I$;^5kDE&/,cfr.4}J*B8?6{ᯆl-"%Np:v?RMu-̏ +PA赭߁T";X~X^At+`~ڭqm`{g9h=}@ ÿ/'=Z_uOw)H;[o͡x\-?W|#o| -젾f]y g6t]ImrÖ$̚j" QW>𮓡 8|bM|u4sH7oK&S|?}YM?Ϧ&;8೉s-'oҾo=b+{tUGp9?Rr?Qmᗿpq@p+Uaр#CX?k/1tS-BҭջB42Vq^! ?rAVݘnlmWvU=E|3x9c?Z~2NߛT_&}OP㨯+?~7J5Zwm~Q_~|Uu&BMnN$(U2~Vzgt:GrdFScu((((+~>|\O /ɦa,#f%y;*+f_?Ct~Iߔݻ8ҽ ( ( ( (_ZQ8kO5xX|_'{OqOXنGEl}Z^ke"$='jOX'-|'k+?xK0_i|5Ey$Y&\cפPǟ {o*||%|I>x͜lN_ Nk5/7]^KAc!3߆oMۭjs($sU?3U5SHҭ-&Mʳ`=FV_=ω_iP4zlfcf9}9>ՋҴxshj"ԂFtYJk|q-Ɨs@?Jݼ>XۙŖRd,ና-'εmqѨ3^m^gc^\T54^?gg#jfG/$g(((((((((o~|= ßk_o~|= ßk@Q@Q@Q@q?&J4'ofM~n'_-"}REPEPEPEP\%^/kO.ß "kX|7keќcvN=tتRW:ÿYGv/+aLR?؊?i/:Hͧʕ8l}T^oRͣxHvPkoHb_ TJCbZ䍧n?P|HOl0giO _`kM@I-;mj(5$vԑkaW?٧poއͨ噘@ֿb t}# X. 16xk#]W/;iM %)?Þ9|sjn76l}JЉ nʀ>_~ tV +pA`WCój)' }Z_ /J5v=K/O;_'~?]{Z%Ԛ4oLm qZm{~mz9Vu0X3ZhP'MOI5e?>|sp؋RF+ \G9i%}7~ |7_WBP]NOz!Qc|Glq+|9No (Y?{pP &]'~?_[cm(jk|K xee]̻_v=NkW앬摫GAfl$mѨ;}h]l;k8RzU.E :2 I} 6]鱉.$9w8(O}C_CURAfc5챝7"8ʻU qꟍĺO^9D:.Z[||љkWWԿ\5+(}9Nh_1 ];E]((Uw0}+[sC2\Vw5~ƔVl+ⷈ |c!%s:Fun?~}oxj-f ~>li2?u}Mzf|3WW¿ _|]) 3([O|Eg|W_6^mJyE9P3޻(M4J^Ie`:ύo~ $qv-:@?|5+GRCcl&v\) a]𝟃[J]jK;ipaXd#>==[_ojQi5m؞kՃ*0 `zGQ_ ~OZx/Q'5ԴؚmyLsǐs7hCXw/C]hLtUH2z>eE}yweqwp]m4E' h_ɴx꺌05Ԑglj@-㌊c"_s]xxONj y;Y!vVf?M#~ܿiw:ƷB%FHuBBh|+|^աtn]7WW37QTl&¿#xVtmb/oL_0~%ݿ.1QjluO| l㞴?'xg/W]f4n'4 `sx7~6xVV}N$P \7$)G|_Z<~ դԯsJHvs}F> 隋Zuh37_Jiwku"u "jr|_E~}vcoU?mώ?x|Aۋ/3`Q.{dҀ=y&횴GXiQHϣvqSַ|j>=c8|_|>|wac۹Io0p IaAh<-,g&姅ȍ؞kA״ڶjeA$n>/ֿ*d">;KO<fc+nړ ~OPؠʟikUq_ʟikUq_ۓO:O7g''wJSk}hVfeTPY@I=xnou l&LVHh :0k?v_ (j̚GK`G8GrºOM i~%Iwk1La#!,<=/P~/ԣ}^@C$mvßwR@kko-a;iItOFV_"`o^B#2pf`3Tw1wsd#=}+ImAK#c;UFIy_K zM>!の^lse"ȥ>E~*|(> wYL?Ţ;_ˑ f?P"~ܟ~i,RXhxu-Sv8QF{ hM|6)qOx#11w5J>0G&q5#ٯ?cٲOGmZm;OC'Eĥ 0נO ,5u<Ϯh[I|8u? x;Ka47G$=@^_9|)3O|++iHGAI}@Q@Q@g^^sD?WEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPfVJ8{"?oֿ1VJ8{"?oր: ( ( ( 4'ofMvhOOJZD((((?J_`]er?xs&>mugIUzgE:4TU)>R鞀hK[Wsޓ ,V $0{~⿰-A}csǻ/q[x[ %Η[}Aª/]8{~Mᵵ~^~lC<^9c!1-RF7*/I _Aj>6I@uq;)wAFwsDrD̬>+Gk1@=~?Y;'2O1 V^N?(7o X 33(׷_'Mk- -${?*y5s7 WYک}~ן_O9(dcf$dz30 |Xvkk{(4k;dmm*+;:oM/^kyh$FNF}pE}{\Ao 4;'otx$g€pèzWyC%4 ƽ_%h0}|yNZoI_r'{bDGqK/`~u?aSQӮKfm@#3@¿xWß?ͷo4iUЮhƌ1O;)3mf]'t-}7??ξ?hI?7֚F'W?W_{}?@XcqFm|D`'-_lYéYYܧmqC* wԣN, UTS uo@!amXϮ1"kOWpmkY-K8>@jR=7@{;?5?)xa_ĭSw5yuwb3c8 f+!KQ@Q@Q@Q@Q@}s Ƚ@j^g~C5};EP/ w ڗK\yE9~4O+5¾? ״ QEQEp%ovЂdC|!5ʽnU3܂>[OFm!{y.OǿjןwUnuX=U{K=^ՉR1a@TTVwڕwesG),l2-%)*Xk\EsJbg(e8e?%艨4W^/1_@+G2Hz~G\/K@>,3JK.6ɚpvw GB=hbP*ʷA$>~V_r٪yY66+ώZyG *ewn_,ܚnm{ͩ^J;I?Uh|A܏/n@hDOBGƿKdۼC趯%-wSjBzԬO2cVnɬkѢ9O&,NB믏 &_\_HNa)]+x8?h^WD7^05⡞F5E|(oV U&^ -SPDQ= ƁG<ߝ^ɷaM61}2Կh];Kg>En`]_~7moԴv7b;= 6l mbU^ꔓO]%rSoomdY!ҭؕ9êuR+u(iQ["kxx̀-~T}s~e;-gs3_PEOڂ}*t[],,+:Kxº~ٿHO ~_LMc]үk~TLMc]үk~V~ܟy$aC=7f*nOjU<6>j|B?&^go~'ѭOAr1#`HrK~u ozMg}7V Эnq$SCbÐ"6|`|/: ڿ0,3U\9O_BJ?gw"ľ_c%<܏騠&}:G^麄gj7S^ƾ?:G?xrVi\pدI]Z|AXv<oL!8s鲖[bn14>(ռ)ZkýџGCʟ$~xU-6j ݼ%eap<3mQ@Q@g^^sD?WEPEPEu 6n7mRqPWg-n#B@TQ_+qc k@?47R[Gq僝63 QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQESuk? |lC''Slmǖ\=}UE-%QEQEQE6omӏ!xs-komӏ!xs-h(((7$m7Ÿ"g쪖Ei7u فր?-c/xz^ t[1XN8=x?B0͠((((?J_`]ero~.'L -uk[wgqer2WRKןMu "69>T_<[6'6cz-+ O>Ũͭjq|؏>`NO& =~&h+FY8;m9W_F3/9H}͹}8@ #^]qok/緗J$_0 P>s9 {7BOkڼ4cig3Gwct+3Ğ|aɦkV6.rbt=OP}i@-s!|;!ԭ9@dW|:k.ѧCpmfo݀8>@*~,iʲQU ז ;FdS ~b>Jihse463ϧ[{mɿk;&=q(J=Iyg MHi3Fɘ*e1{ (Y1&_)(6wOxL7C?2|&(Ş AWG"|:K)ԝϒgo8q1 8pkTzk_/ß>Mh:m=ż̐w o?VO5h+UsE@C?eSE7–R_^!lmԴ'U p\?)!=VN[X5UcpkIXdO~? _m.^0;RS_; ? ]?:-i/ s4gjt7BzOهQcxVmeyu[[6 HY@ϭ} ËtnΕ~VbI9'4/G`˟?|o~\C$RQцC)k'ǻ|1n[Oaj;!9*HmɬѢϧ:m`o"FXT>Pa:[D{oWK]oiЇ@Ok_ٽYh?X"Flcq+N?%?goc W^Z^JM-o4GC}_w a{ge%ծ>.8=+_x7,uKYF_Z$?Dg'OF CLv_ۢT6V |)#zv#|_JYb,u*Y$ #:6z]zGl>âiVz='٬bǸ-~T^ d~ V)PxFvw@Om=z?,?f?_x'֣Ep R~}6OW|Fh~fsFV+YElOl^X⾷|R,𮛭̣ q<8SA~xn;/is܍tZp8bO_./2sHmNv@1=8WbNI94Ha("5ڑơUtω? ]U?Ku|5}޹?t4O%>WFIC:5_ v^_ӵtd?웨M!x:ѮHZKeApGR1^U' -5m+ AעӔ[} W"@ q_XpGB+>oZts h S ԽیqETl~kJXO6cӜp1^?t˅/| \$^JňѴ-"tş(m_LST?$#IxE9 R0A9oZBƓݲbrvGLޞn/7Z(`Ws^Hʮ]Uц zz~ZphV1OS/*,tC𶱩jOAn#'J#_Lk>ngnEobྏv0|?ӤK>>Oĭ~բm! DS¯Q݌zɪ66U6V6XB6mm48((x5f\WWl֑aґ&|vגPEPEPT/▽ L,&y7-i?E*OGLhZui|;BB2y\V*H<TӘس}}yZimwywORZZxKA+0T%ob;}%*Ŀִ=g`Y!{Y!:`J k#^~P:͎Xd-X]koំ/kF@\no]EDuxKmZC"7BdFinmKcpD^:i*bTd8zk=_O$)iZ]\Ҭ*}0:05i[,tyOϦ0=6!?]>ުwZ֛c1R Da3~t|bk2ni<'%CxM+ko>fMVM%Pe=Z"~ Ѧּ5,-˝TrY10i+_$> "ay!ʪ>h r5St}F~u ;׀sUuh~PubL[=CY n`d,Cc3ҽ@|I㘆?x<օ8?9g}Eh,躥n}I|_;TW?rXmJ=NۘD(U 57SM@qsEk <ŸzY\"ԞU5?:M֩Ge.f==z_QOx/ xf[}7ܐTuvCր>ȵ4BSiw(vVMFI[[Jek]~yua]QEQE6omӏ!xs-komӏ!xs-h((((h|LkoH#ʗ:+;¾.״ O·Yk:}ƙuW=kjZ.ާe\~?-<B:?ЯSbOtU gKe } <BKTx7/(Qt_/:?УΏz/+GYU gKe } <BKTx7/(Qt_/:?Ф2DѰ=CAU gKe ?Tx7/(=>Lb媮*_Wo*г2<BΗG=yE~¨o :_/Q , @^tG_Wo*г2<BΗG=yE~¨o :_/Q , @^tG_Wo*г2<BΗG=yE~¨o :_/Q , @^tG_Wo*г2<BΗG=yE~¨o :_/Q , @^tG_Wo*г2<BΗG=yE~¨o :_/Q , @^tG_Wo*г2<BΗG=yE~¨o :_/Q , @kr@1QRܷ&~¨o :_/Q , @mpʧ>a'_Wo*г2<BΗG=yE~¨o :_/Q , @^tG_Wo*г2<BΗG=yE~¨o :_/Q , @^tG_Wo*г2<BΗG=ss*]+GY´:k7uV3@%|?jVOѡe->anožǓ_f",hT`*=)PEP/ w ڗK\yE/ԄY'_>xomDw}6Ո^q]Rv}pdovvj֡5ә&S((*]ou@4ᴟci7"bw7Z+q?k+ZׅOj/Ghܶw]*vtG~"x?Aodr48+냟ν/5Jo@ j~k?<)u+nb"*?@+/ړMeA~?A}_ #ǧ)XGI5φ]]Dܵ2?UhY(t߆#afx5īɓ|V_cF>g2=|Hg셡Yy2LvDwdžH˨/dp ?bO46#\]Z_t[6GG:O[OڿP$>]67ګo[6Ҝ*19}sr6 ON}kckcX"E誣~2pA;#NUAS@jP?%a֑Gh݁bvt$H1~xu4"-ҸAۇ>{WG$WΓpɘCcvaußG Ci-ū[#POEPEPEPfVJ8{"?oֿ1VJ8{"?oր: ( ( ( ( +FCޠM|G0죹aMŗR4Z -th.y4E~g1c\jOY~0W*+7_?j) j23e_Y~0W*+7_?j) j23e_Y~0W*+7_?j) j23e_Y~0W*+7_?j) j23e_Y~0W*+7_?j) j23e_Y~0W*+7_?j) j23e_Y~0W*+7_?j) j23e_Y~0W*+7_?j) j23e_Y~0W*+7_?j) j23e_Y~0W*+7_?j) j23e_Y~0W*+7_?j) j23e_Y~0W*+7_?j)[CβZP>] A+~v/!мE Z^']AWPEP/ w ڗK\yEQW}]#,;{A@iq]'KZB0'`;5}5 U΀>ioʾoH]7. oʏ-*c#vQޑ@O(-*goʾoH]7. oʏ-*c#vQޑ@O(-*goʾoH]7. oʏ-*c#vQޑ@O(-*goʾoH]7. oʏ-*c#vQޑ@O(-*goʾoH]7. oʏ-*c#vQޑ@O(-*goʾoH]7. oʏ-*c#vQޑ@O(-*ƀ<|22:u8 ((*]o/-gѴnF |=f+:/|9Y-#H \k 1^@x$fOCkګׁt ѵʳO-/Nhʿc_$?@JZ<Fk4VM3NVyL>:_wZëR6&$Dٻ x.ko]H} ?$'HeE(2#G_Iue-dm WҀ4|qSx#ºLzS|{U>^؎Ӛŋx=S]C Q88zboE{W鎓ǡ6:t#lVpGnz"rW]<>mt FF9Tzs_G,\-Ȓ(,gEjS_5F9uO_G|DWߊVPx [6f19 M~saA40\J&)W?뛩m|oBkmJ`npOҾM*,H^92=A ׆l^3TB!hFu>_|7\'\֥BPa$c{1ASI/.亴zYh$C3,h~y"pC|5t/W~ OM5}ƬUG5V?|x {$I;qӓu/hհ(۳¾1Դ~㺶vҮ 7qه"o?H6-匧;sFOU#Wִi,yo'tqq]p>Ꮖzj6Ȇ9bR ( (7W9`7W9`QEQEQETw ko,ƥُ`MI\˧aАNA ⧏n%xS./!6;x: ],VеnULh<_@~Ÿ#ew#nU#X8u@OER5O z~+>KݎDRP+sUe/%נyQ.2j`~+3X'~&VX &T]GQEQEQEQY^.b 48#m@쑹WV?&n|r1BM32||1((_ʣ՘nU#ॗtmn&r1 yOA u=˦q<N/3n4QEQEQEQEQEQEQEQExOK7U"x?qG ((((((((((((((((((((((((((qD/:E~cqD/:EtQEQEQEhO+$ſ A4u~WK@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@C_a[$yFH ho|T Uӗ۷j#QEy/i :eׇ F{} PF3C@EGkpIYT7PAM[ֵX..-Ze%7n ۊꨮ+oō'ޛ}}[][Eg* FF1ր ( ( ( ( ( ( ( ( ( ( {ş5}s Ƚ@jv(?j_.s@/ w r_Nhӿ?Wk}9Nh@h(((()W%*:nnMz X%׭cd+#5|eQ}FIb)sݿcq?WFc򊽫 ]'~'N:%o7"?@JR0A.o.CyU~ _G|UQC8:IkO?Y[_4!# Ho Mxφ?~zvey*Q"7W_W&G=|?<;|*lGc\Z@D*<^mU,z^/k]kPLu5$;gjl3t||#-Kx.֖P{|Ŷ_4wٮ΁$2w8Pк7&z]Tl~5ȣ܅9}%h_45 ֺ֕7ܹ}ksEk=#Jڞ- [#0^_߈^&h :i?m~f pY^)^}[o62]>w$U2: A~?▽I~p|>۽Ƒ +Owmfݑ}S QRkk{m{Q[iQOV9z?h|tW0iw>pUCnJdW -֊_\N4^By5g+tMc!Ɏ@PVh`v7O+ mgHё醵YsE5}FQanW?e/xY4;D/#ua_9jR D|vpLx CK^6EUb\Ҿ [ZZ\xz[.ù@\ _ 6_&kmk5HC958<c;O]WVJZk ܉ yu=KTI._IIO/_ph?@t'oa;ME=t?N6 /w( m.;;t2Mqp#GVf<_6x !ZZ]~"xm:Ie܌|WQ>$jz>/YO%%pB4oUP+`K,&Y| x#$4IW Z̖eQv߉^1q#1H# =~>Z?]'X*x^ 5TsB]|GF'_$|pWa|ӄ,7,>U⿴¿|$mFHbrJD QwC:w05MiUV\0jG^|Vs󎾐<^篛Nܼq__#W?фH}'C ۢpͅtf53Ò|Dvy7!8oٳ8]VA ˊ\ -sh04Z!mKGGvAʿMk S⸼M@hni&ÜL_: _)>c\\ EpO%k `*o?{=R7}rIMo~lPHa_?欿 ?k}Ran y1)ܪ?P?Z_⮇~JA%֑s)d:pkot#ҦUJ"rHo}q[񤐒tY?$??]#l ٗmw2H÷ ~|~?V3Ssg5s MįC_Bt'So sz_x[[4KQeQuJ) Rö8Y3| { 1|R__Khu{6["cʾ63[JBMl7f/.nz0g\FgEcEJFM(CclL_So :GcO]MKJ@t'oa;u>ѼEWu V[kg%vRКo-|KE/M_"[[F:G0QՊ W#o>xMQy): -"tE}3ku7Vuk: ")Â+Oڣw~3,ZE֑ ^\iWKrJk^_dҼ!ϒJIq((((((3rdx"\+((((((((((((((((((((((((((o~|= ßk_o~|= ßk@Q@Q@Q@qixe>cT}6-cJwCs Bفq~ x?zz.l'hX?eEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_]qߊblTrkgx3M]^r5z`64QE|Rp(WkWP oA`ÐmE|8澁k&Keݙs ?(((((~Q@[sd$6W|Kuu_ _M]xkHHjɠ R6d(7|9e :G+ ~49u $4]?Ws}fٞw3V:I 8#$h3hj nt8^85)>٥\I‰ Dz85_[ƿxCGid-$,xWs4sO|9›p7Q~8i3GŭHV(z+?cٿğ('Ŀp:7Cj;PzXt_o抗(r,FP1">4Xb(b5ڑơU(Fi7s_?/gN]s|`1z#*FU2=A(φTK;mm PMCJ.Hu|q\w_8/E<+ih4d5-wPw95ok=K;Ƭ}JwtO h:&cxcր?5dF>?us4][oqo24r*b eh nZH\i'+H+b?->:~:>*Gw qa[!}0H2i't$H_)ϨGsEYb)%AC+BWk߳?^=֣EZHa1d">[w:[<~$vxI{T]9^Hoztq-Q[p5x[O̳hg^DHl|XQ`j n|8']J`0sK}h:_VV,[em +*Z>ًM.LV,- v2h?rhۭ7}ݍy<2rt5S<)ql^,q}$m5jkYZeC05^k߇z+L%ş?o| vڰ=̂k'`nz{ T|In:爌e,d{{d]19`{> >w8^ ȸ4͒? Y1$䞤QEQEQEQEQEQE^` pzˏּ"gf,o>P;gy5QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE6omӏ!xs-komӏ!xs-h((((ho>(ºΌ6|%-Ig:ePiF#?4aE~WE|_ ğiuOwoȌ>?УϏz/+߅^??W~{LT } <BM][3S(woȌ>?УϏz/+߅^??W~{LT } <BM][3S(woȌ>?УϏz/+߅^??W~{LT } <BM][3S(woȌ>?УϏz/+߅^??W~{LT } <BM][3S(woȌ>?УϏz/+߅^??W~{LT } <BM][3S(woȌ>?УϏz/+߅^??W~{LT } <BM][3S(woȌ>?УϏz/+߅^??W~{LT } <BM][3S(woȌ>?УϏz/+߅^??W~{LT } <BM][3S(woȌ>?УϏz/+߅^??W~{LT } <BM][3S(woȌ>?УϏz/+߅^??W~{LT } <BM][3S(woȌ>?УϏz/+߅^??W~{LT } <BM][3S(woȌ>?Щmce).c.e /i Zw!Xvv:40* 3?쳨ڕ+ScB,\.r W۩B(UT : Z((ڗK\yEzQGZ,0}6cW[uτ1q!XIkWCV(K;.z! lloʾUg#aЃW[?}9OG?| 6?ƏIO4ז?[tU/$ڧ?&?^[tTymW̿jq[hT@MymQO_2MomS?}5OG?| 6?ƏIO4ז?[tU/$ڧ?&?^[tTymW̿jq[hT@MymQO_2MomS?}5OG?| 6?ƏIO4ז?[tU/$ڧ?&?^[tTymW̿jq[hT@MymQO_2MomS?}5OG?| 6?ƏIO4ז?[tU/$ڧ?&?^[tTymW̿jq[hT@MymQO_2MomS?}5OG?| 6?ƏIO4ז?[tU/$ڧ?&?^[tTymW̿jq[hT@MymQO_2MomS?}5OG?| 6?ƏIO4ז?[tU/$ڧ?&?^[tTymW̿jq[hT@L*2FOD|7ny}UO^6\ Iy;F,ŎIɠ Zq_Mwu!i[s1Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ [}+^uZǗ[}+1>0.nZޢ(((((((((((((((((((((((((((((((W3ZIT1/\8vE$~W5{ i9@'M;{y=ζk:&!{ r0Ң HIG>gy}1@Gt_Py}1G)t_QG>gy}1@Gt_Py}1G)t_QG>gy}1@Gt_Py}1G)t_QG>gy}1@Gt_Py}1G)t_QG>gy}1@Gt_Py}1G)t_QG>gy}1@Gt_Py}1G)t_QG>gy}1@Gt_Py}1G)t_QG>gy}1@Gt_Py}1G)t_QG>gy}1@Gt_Py}1G)t_QG>gy}1@Gt_Py}1G)t_QG>gy}1@Gt_Py}1G)t_QG>gy}1@Gt_Py}1G)t_QG>gy}1@Gt_Py}1G)t_η|+{ZUΣ3n}PϏH((((((((((((((((((((((((((((((() # P5׃t qi7 U+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP? ?/ G+ п*tP Qe4Ð~ʟZ6v[P(OEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQX^6÷:ֵr-Vv=Gv>E|%GoJ  쾬3Ǟ'MY[~_x-Whğ1j@W&$U?6' Z? ?b1ƏMIC4yE~amO_ oŪc~Q_x-Whğ1j@W&$U?6' Z? ?b1ƏMIC4yE~amO_ oŪc~Q_x-Whğ1j@W&$U?6' Z? ?b1ƏMIC4yE~amO_ oŪc~Q_x-Whğ1j@W&$U?6' Z? ?b1ƏMIC4yE~amO_ oŪc~Q_x-Whğ1j@W&$U?6' Z? ?b1ƏMIC4yE~amO_ oŪc~Q_x-Whğ1j@W&$U?6' Z? ?b1ƏMIC4yE~fXHv2k}Z)1 {ORM Aědȣh-YC")J ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( k7"3D?-OBƾ̏ yy+Ҁ0(( GYg_ CBJOy) TY22+,Qvo4uU!: Un>x ~1זbL{h+_񯂼5?jfu9q̓袊((((((( 1>G—ixј pWWҕwtbi+~?Ҿ(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((0m##qZO+:x75()~$MἿaŪm-~jF]Ǹr+¿e?6NڴDvr%UF8Hxúv4{e߯;^< Hk; U#XFaTz8m 2ŔSFՠzf6+[???nTK^ci qgֺ SmqZ<ÑٖZJ9<9"?%l 6Mǥt4]S_e%Y)Eų28k{-ḁC4k,gX?C@0~ܟ^#H߄m^1rǯ~-{?#~!w ^i'䲼P#8{]=D7@k~,?𝾑L`̇ #Вqy3xG-iቴdv4[ڞ}hڴgfcoҽ[?32Y|?}2{f.?g?x#uq揆?􏄺{\IgB} l7qǞ>hK4vM* GRǚGčcDyZDZ.3+?lo$F/HFD5sEF-2 '>W߶^y}smcou OaU##]?xC4"/mq?xC4w/$?Okw,ѼG\allLOP:j/$?Oj m.ny|i@i]z ~dMbSimZ!Q fEo"-fq۬oa[ə 'Gz=еB- v~k{cs)`Ǐu;2źzqW߱/^2$0ϓ<|Ls+x7G斺Vi5eC@ sAybk_<;}}n'Z[74?I-~˼ݍ25ɗ)χ쵭){ &67 TvZmo[X7-h#kr#Fgil&}DP-,I_xN?9?gxĚ_u=f:?4͌@:+IF8|)sźǿZ[3u>? J~'>=Tepi8$`yu{wè(Ur{gPO\uӶ?oۧxKCxN9`, zh  cz;w"XZ2I _Gώqs4Vg9i`nܩϡҿD|K_0P~2xW:oiuq}Hy^{~> o;=KUE83ƫpW<X0=U5tm_9jeog`-O˃q5OGC@~xwRZx,o+v^_|pk'Z}΅u2M y+r=| ~i죠b>a%4 i2z:u\4du'Wꟶw;M?TPy>kkv_ ,,##/!@+|//zl6z:WMux|rB3@Z?MBU OKqHE]Iլ.Q[İ̽OzO~f=qc1O } |+q5vf]$ƀ6(KMxKAo^3* ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( !j6m^?Z5$j}?W?o CV`G^ifx&]o:o-2IJR}$C&9߱K}S>WP-Q2BܰO84eQ@Eyyg=oknYfQ@$n [-c .m +ڣ"uywq[Gۨ*zw8?u6%T1DB/:5Ə˱tWmOM5~[(s$W!}xR%œE %{_ g;_u7uiL$1_)~^$V涗d^s+?:_ .@_'zGq׳7W~ܟ^#H߄m@/~_|f[FcAgH$_fxwOGcGA_~ٚ,o T/o(ݷHa}ePxnHm#݃BpA (\Dgmާ4=pu(0 o~o@}WmրÞOO5}ÞOO5}/]' Τz5o}jkF-l& xՏY6B>W߲{^)KKa_ɏ̝s텯^ط r~cW_u-z$W_cP_kWz !X`fH}\lovipKn0>cПAw_-ax~W4xG"i  =k= 29޾?g]>_xrID Ōϼ.sOUХ@7~"P)p^)κTJ6{~u<>%U;g,p r~G]~P:*8)(u߀:jzKX.n4$R7!{z{ՒcǪME-Ԟn=|_Fx~f?[Q{C7֡| J%~* ^Fiy%yxqۊ_ .@yLycmx0q @ȩ!c8aQvnOeh$o??6Mv;{w+dqR v,PƐģ jTz:P.kΫioe9~~s+?k)|%$Bk_(G(!xX8F蒃?W߈']^㐈4뜨}F8 5ODTkYꊣ n {vj]V~1|=ĦmzE1׊|^. ./6B1rec? xVo7GM=aoBzVa{@H ȉ[襯nOw+EUFT`AyYj;~emw%}댎(,s8z3^+a|*5lc[з[_G%A%$ӊn(cb5$XPҟ0}OT4hjưym(Gt  O-cwtfҴ۶]ئy־>+V7W眜$ⷴNٴ>Mv뎴!|=HZZ筞-oٹs¾qt7?iN;C;պsOqk47RGt!"uCsWmmr8Uo<1p"_] SR2U Q%#5Vrwe#gO~څ2AEZF!_`j \-LNK)?\b? ǣVzZXB=h>-lfdWa= ~ZʎZcre?Ph؃EO?Wui--JSS<%]gK^>zizAygov28aacB@2|V5XP16(3:sZn+vc ҕ3_HY[jP7^C<"WZo&f" #@&RK? z>pl,?@]3|7o="] A>EޑzFg)?e!?Sִh(?cOѰL[U|xXZm{b Fa9p?: ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ,|,-x^M'P3!-oI=AGq]ϋ>^='ffH\zr dUqV5 :V Yx_F>bO˯9?(¿P ,G"Zt(O > stream xZKo#7 ϯsz(z(Cܢ%&)Y;qӌfFȏZWբ%lrn?OI͛ӧG=k`ym3]$3\e7o}TW^+so9uk} 0>d,Q4y[:1q V_sn-@FmAF6 DnoP*2fEG岀!iJб&)<׊QYO"wJ8 0eA<@=:w=Us[U&p\lzԭLrJxłzn2@%u"f \>C@ /bPqJ{[>H ŝO,tjt̉@4}C,~ZS>[&(u51m+>B-9li~#A6QU@ t԰ KثyH )08[7!_/[lTj ښJ]X[% " N-f<#"m\ 4@sP9&u`SLYYp/N^"Wk튑^xXZh !T%":xvw;eA+ Gh]*;Ve};P%Q>e{m^$Ó̮֔[F-w7(젶)|$풿'Nґ@%XdbL r !eRTvr r/-SRKٻXSt_{2WmzӚTه9*H.:!KnP,Qw鋣 =9%Ԉ0JCG[<(dJe󡟬i`mb 6 =./;hWv?iPz?K?veLo_灀gI}Yվ}%m_N%̍qT9n.NYd(E?lhce?~lGRzxPzt1#_q DHEDTG:UѩaQ- )vry_`!.d`Ӟv_2b+f/9*yMZ~sĽ?$Srztg'Q7]xSfRRx#yLRKM/j??':n_L,9 6~ Q>hδ _n+3 endstream endobj 219 0 obj << /Font << /F1 27 0 R /F2 45 0 R /F3 11 0 R /F4 62 0 R /F5 98 0 R >> /XObject << /Image1 216 0 R /Image2 217 0 R >> >> endobj 215 0 obj << /Contents [ 218 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 219 0 R /Rotate 0 /Type /Page >> endobj 221 0 obj (Identity) endobj 222 0 obj (Adobe) endobj 225 0 obj << /Filter /FlateDecode /Length 66797 /Length1 343416 /Type /Stream >> stream x} `Tw+f7!ܗldCH6' d,$ĪGRV³ԀVBQZVXk w Y-?d3ߙyow_ VULk[Әj.mJʃׅ;@̟VS]': ӁxZ@u[š3˗k|)WU_7}(BήgGtaAM5%U_!pޜҙ ,m^߲ٽ qS-VM~E݋-kAo\H`\ܱrѕŒ}][S: ^r~8u_CoÈq3Ó1L'-;%wo㑎"U&[96bL`~i)nR;qt|eOw~%y]}DhIwfp=n\hf _"n0)XVw}G1;~,?rmp5w}xxH 2PW,B#8r.(N @ :2I&X$ hDJ- zC}p9Ɠ@<^#0d#7|b)_m,$qL9> T5PC xvuW :FvCNwyQ]*z0iAL-!mJp/>!;xA d7 kc 3 fD Z}&;[t #g4Ag?H H@H `j\!)\ZF՞wa\z .CxށZr+ԍp%p_Hok0ϯ~t#\t?mp=;w"}! ރp/}p#&_/1ACWH`6" nې> {ބG2:w"=oF> =x~xEK!%H?fOE ^E=/A=/ Kx_-_ <Hfx=s-CF ~{~="H=YnmM3112)2)2)2)0 0 b6O̦?11313111M?lf0!2!200>3>3̦g6}?Ml>f1MNMQ8)o1o1o1o2&o2&o2o0o0{f2̦e6uf_g6uf_g65f_c65f_c6Uf_e6Uf_c6Uf_e6Uf_e6}{Mlf_a6f_a6f_f6ef_f6ef_f6}7黙MlK̦l̦l̦l̦lK̦l̦l̦l ̦l ̦?l̦?l̦??dӭܦs?coolCܦtp?sͿ?v'/4qC8Z8U 7.%>5pppp,M'<)phߒy)Ny8N@ QX&qCO'y-q|#'1?OZ9!V>>;ڷA7pF'ǩ`?΂Tq0J>ϓâ[<8`IQdG~gzql8,qhr'o8Iq KO,QY988Naϓâ[I3'_8_uϓbǎyg0NyRp*⋁cy;jO<888NI3bӼ8~?vSqVhMqN#Xi#>~kHIcG1?3'jh8UP''Do󤴱}mՑqǩ&s>ppppppppp|bɎ'$oYmhߒ?/No8Ip*h[cKvԌӌ)3y^CrJƎ-c~g5xU|p*h:Q3N3 C988N}eGwܱ}k3'$8U' Rg0J.ϥ?N>G% Ǝ.Ǐq#ZTAWr9pppppppppxv<߇?888`Tnȧ_&˿/Q2vtOT8qS 12ZvL G"VsxV2cti%q^?8N6'yڿ_q?4<_vhߒ?w ex8U?#XN3Ҁd,4YIѾ%=Lg, Fguǩ9 쨙_;0}጑'@:̄V8#Jm> & nfNVsչcŋ\ fWrN=`JI&dϲgfجi)Ixil4tZZ% le&ɝV%[OϠa#"FqKM,Ӂ9!t$F 2lREr.HCd ((2y&1Yr|<Lm4IemeMXݖK?[P @aB"&e[S(Ki;RJ{ʜJ3lnRbivm,PšqkJZ֌NGWK[l;2Bs5`+[ꎸh+)ibtj8Pfjhp` ɽѩ66bn!iUXY'a[7Y JttL\2ӴDrY-mKpbP2~0*ʱݳʤK0, +F:Hߔ c-AEԏ\#iLb٩TY;WB{dZ$I4$hK[q>~%M|7nu" | 88Mk"]%# ӽju->Ne Cmul̷^=f W %hhNoJlڛ2Rɂxan]ȟ>-MEKNT=A*hRt[Y')PnUj‚Kv^?uRi:n5{\I-4ʒ-pi]$ [CZ.`CriL㏳АKZŔ1)>aaUPY?o`'@E*hpyV7[$e` 6 t5yȳhw586aД- &7[&\讶6ZVKlɇ(b!WAװ ]Y0(qK"5lԳXHh* AAGoۓ,U"Xey rQn(5rːJNqxs0N'ǭs*ub);el@}#11U(P#4jcv`)N%$z Yˢ-SYMJ՘ƭÞl*>:NQQLA%vbN`fK[9MՖ[YV 3,ezBL^K4:?D|8"J7^l SԤLyzfk3޽$htnpӕ O 74'[,1#F^WeʼnT#-;>U'شD^]#55JMY Q%' 5xjV!saYhvkb-r,t{ڗm 75!z`2&Ťř'5_tc"z qWH.-߈Y$KTfJZ!gA5<2 M7*eNn`(KlB%TP#V$gK*0bB({Ы=;e;[3LMNb#/<)`ƈ},bEț$ [3_ {>HKmMHD 6_M=? '> !8GćgCQS;AU% _!*0 51ϻ}ЫPaRJ{%x7׈! y1>m|%r~Ñߨ_`|4 Jx)|;g6a> z֣ӻDL`-mA|Q] []5"2{"TEPs .PΓ!^y.<b Q+Yb/K KE{/ƻDůA&WӰWWKS͸o.|L\vFf_w4O" h^Kum Q2\K zB&E_^% &;YL *aJJ*%!OP 3@k7}7uU8څ/_zY:)`Ȉ~1d|gX4RR }I)S05cp~I%30ǐCz̵G8=4"נY~HZބ^ҿdyiZV8찥L#4)K#¢lGU=Td+UU]UĉC[Yٌ'$Q`dTDC!BC/}!.*!fnv/D{YIYh.ϩ.vl!EL喥X~_HeDVڎy 5/D}7z5"E;֏ԌCU )Ө(U1(PJgpljBe]1qTBe}{rHuOVu54l!F +ެ(ZMmBkMkH*;[YrS#Y M!S'Aj= u4L u /ѝdJ4 ipUV]覬a;@8Ɠ JGAЍ٠2m+$l@lU,ׄ- rḦ́)i\W"6uPل8Hdu% q4. 2Xe@`L[0-f21 3lIȒ?lII$!hT9.%0c#oeaԅw,]I]]w-Iթw -Iw{=~l1'SwK' ^) V\Wd+ „U %lLx(AC7<(ؘW"C#?%6u~_ïO%Eh jqcԠ6?\.`S;`g[SG_F-i4MIӭYY٤[YV) +P6k zSG;ZM9, f?]o-i(J<_H8!,!C:a$E8A,FJqGumuک_~SYÎ]W &Yi)]\\؄!xû9xs; WZr-{p%InD7m܀Vc7J&!qp w&UB Ζ[Гx7['{E<$Y#5—lqq",\;^ C'B ڴ2&l0/~Zl[q&ڠ`"]\ٰ%zVV3~ 7`'h-#oz.$-\~ XA$Y;!UESQejB{5VX[סD[q5Cl? 6 {-e>K2<.a>QMG˻G؎ר*&>_@ ]2հsg>ނyA3ypQr>OvjmGI{?U DY{C}$*zN%lxg{?y:>wih{VC.\W Y4'v()U gEy˄hMG:ޙ6xwn29g)=xsV5:tyi=l+W;xsrvCl5\'+ x߿OF&GOk)g+Y vx~; xb/xol~<} s :k [އi'A& iOb"I? &# f)Bϊ![;ho `[֬IHH K5|kz)QWE>@1pVKR.Yd 0yB I$$$@?fm<=yjŨ 䃡"  QQTOG,`6:f} LV7 |s08d]&PxJ[<~ZT;FĔ y9aZ1^y¤Lq'OȰG220?? l)S'^{kh.? _k8'o{ГQ[}%&M2GMI01ՐfHWeiC"gM]ʹ̾ rEۦOȟMzGhʳs 6(M3Ӕ.셻w/uNƵɐ-dpJH' Ӊ9-Δq%%E*JK)PHT~o16x4E/h2$fDVK$$vJ{$Ԕ-=dzfρzpE ?spA0-eI2s,6nDk8ueƶmaAW]GZjUéUͦʚ#U+'NM /:l-OW-Q&>%дs+RÍҲCb7\i x]k##񊰻v/wk0j@DW:$:BHPg+|M35$f;SI-աcϷ (1~c=c_pAk ؊\pd ˊ:X !]ĶFsCGY]$=_DXp̛J`Þ;~;Ϟcٰ]k7T7k߮>qyv$ryDLA|KxW{C$A:ߗQGsy *v`x)?ZFwetQeQaxs7!.Wg 2vMO!i:R.2RM8KRp?מ8x>x0zb?.` 8!,4H\S\hA'81'{j $`0n;ʍ:O ϵM2-y$?j_pnӹ$_JTARN2c`9)jբ!imC澩D|Uǝ 9E YAp9G`b6 &/1R0㓭EIz)-@J$pTf,.cU&,qQ$:1?6&f)2R5>NճȮ we G8? A#ktu7= V9kp *`ʘbhU0Y|Mk.K=aJIvLbbĊ4aĘ#:,1nQqLep#|rm(:0I lF[x0g7*0N/Wvq~{,Bg^*tAɞgqL?N'bCաRHȤ!R4|ϫ1g3"ozRukCs{#doDxkt*q"N D);N[q+Lk;;;;N;m܇^;; O-@bͣ28AS/ :EVʣ"k A֣#yt%f)\v(>H~_~ L" *EVʣ)#yt`"AI9 aMLVlq3Yw2Y_b0;'&(CYu(˲eY5*CYu(˲eY֡,:e}PhL7\Ox@F7q(X3:*,de>D:cG1YYLNg|&g0y uV@X ! D \gBgnS)ub|;ˑ)EЁNZ^r!waH[YN=jXjV{'m k_uc=ۅuC -(wcZH;H 6'{7OQduR% a,M>Bb 7c u2MQKZ6^Zu=,s2I N;+t;w.XmRM2*)=X|/Puq>E;E-ԣ`2Z̻i;.'eelKxz:6gG-r?):v6ήޑQOĶJ9Z\њkk?!]r﯃TV ۉ]2 uN˅UtAk3SZboeqNXtdtXZ52Jld72k]bv|ۘ-dt1e[Z=v-\},jfҧWIb6>.nn2T6eн(ki驅횱tBi;Olxwb+`%]MMص}8Yݎ޵}u?_SFT4esVӅZd#מgUɻKdڟn .mr=4'ujS{wHfUkWN8+CQ ໪mlfLnUٱ;!Yu:|s1k>U'Q -4Rc,d{Zy{s"נR1uTybGVɻj\z١\+uљ9N |˫Neml=5F=28ycy]u+.Ut'ks1!';[b[ڂ/Sή_lm*}zlQ+tؙ_[7vu~̸9c8z%!+N;vZ!n3ԋʽnf}q)WmK9+3vIHw-VG_Q飚X${5'NE3Q=heyT/K0G˨kG߿Dzoe#^}kbg쳵|^em:1M-l9J #ڿ*tGGUt0J`i0y,9Yy:F:*::*yJhVJQ~ڈjR;6_eGs+Rh \5 ɳQBlhMZ6c[VbUF\7B{]rգz"Gvt_Z{Dś>[jn9^);kD%[Jzz}]RQGT۾WuzZ3%~ǵBvu2UΕ]}RG{e-#rd&ڤZgGw4ղcgtuJ[{iKmRzuH-IitaRoWO ٢8*ꥪWgkrIeͮVW!Jޖn:@FћY߾+Vj9;i[NZY*u-a=y#󏭣ֵ#loMMOb17\S7~vREw)CsvvVb'zQk6iN{KOiIhǑ/˔eΕR?ꠏjFK}]RKI86*ucj f;{nWϲ>y%ӴW}lNwOWkKMhoҊQ=[wtE}WgJ)=MQٱ[yPmzzPo8 GG4ڎYiV[Vtvt9[}UӅM!eäy\ݾŭչRN'+D7c3zuutt&5;{]#K; m}}v3sEnWk3gs)pzٲՌ})9hשtᘨj\]3}9UNk]F'j&-]ٳLuRW3N'4uvrv;hj_3 B{j&3ZN15vy'hkѣMYnv\r۴b lڤe](w1tz؆Ū*q.XkEKcvUؤiMNhZ/HAO'v*hB]`G1.v%lZu@G dGWQ5|vs@{hh(qp_)eR]uyܢ2NSQZV*a8&ͭ^=^ErhVTY1&5ԖIյR̚2UR5b4ͪ SDZ *UUfՖL`QqEUE}M*E,JUERښ2lU1[)Y6>[8lEUUZֿڊiUeY\=+**ATU̴IE3RXK-˦n2_I}E,:Y(kGέ+IEuT!X=U'f`Yer-TՒό`]Wv/eEUXW-<:s&yd)Ͱnd ?ewRtV)xE$?%))'R~f>)g?-OrXkΟ>1j?5OS/ 7]>.;LvSWUB:.۫6&ga[u(c@0-G0=0oY>@\PVWaXR{A@g~ڬ,z6"T Dff)[[Pp+/Q-!b8ib%U(W x#^ >U~J/P]OTף|g(WG窟AmPOQ]))`*Lgiܠi@Qs5}(kQ^Y h.\򕚫P D{.]u}D!ÚVw~=A(䠜p7Q[ ؅3/Rn^O>_Q&w  O |:YW 8z>R?9($!9tAE 8Qvbo1٠w1潠Pgx aO >1| 3g(ـ0%ZވD`P"F1(cGظ嶐)@B B*@ 2ʐ*\Y*e f_wyƕFעu8j^7Ow.N] Enu+^SW.AzRLwkt|*tWW 8kVmvyV@ut 3gQTaH}&e<g3H-+{:=0j[:}pĀ3%ȭU*cvm)ՀR5(EրGeFXZu iU'`C ЪyccBUt k+%rN-@+!O$&NkRAD[{mF0RJNh"3F]Fou3/160h1s]l鲥Ō^赌n`VF73 02[ =5+Hbp^⽿L"xtq z .KNz$HƓg*b֑,F 9 ccyq^ōc舉I4r)NS5J!AZYSxG +q3IUl ^T=zKQ..ҙtv!. 3`js:n=@]`^`y5 +po*}>Q/[}5;{TAAAAAAA}Ak-hWޠA *C!ѐk(5Z }5; {  NRcg\c\oӸ͸˸xF 3YX a&?7*'7QaLQa 8HK|$m߰QNNm -?aTx}G`?dz •/*nXXzݒ;"_NS_M ߡ5VgAYJ{ +<[.7w—+?~ ?$+ ?^|_`| ȼQfc >W%?% P} a((`f)S*1sT~g/s&cHE[ʜ5}[SޟYls•orJOŻYx{.,p1x+lx x>/kIPC>(V•1*myuS2_/R[+X|¿yv¿y{oQgl 0'\=p\ p }0;)x^ EH:& NnWzUKEW|Y•/SvDg•ܩ.e'u)#Vv|rEcJ^ES}•rЯcb& Wv #3j\wElef\ʾD%d~KU}iw)\e WejlӚD_p7x3K)a>+ZY*VJ?p+\YWU 7֞JU.Pa{ \kXla{8!8LT$Hl$RRE|J:H!אr'yl#]%C!rXP B#$ 6!W(J*W+=zH,O pm•_)^Y7/+2M|C• E77*Fݤ2ST/}q˝2O\npe%mTƱoRm>oWpw*נroV}2λܣ\Ut*Ke~•@ŸdT{H}V-J-vk}$pY<~twfvٮqbv,gy8cPFCc^}L<֣{{)WJ=zw܏wUoUbPNOQatOGxԉɆc 7n;&yLDŽ/<&|1DŽ/=&DŽ=&DŽ7=DŽ<&cswvvf7fGH)F1EDĈ1""""E))RJyRJ)E^)Q)EH)"'{.v|YIъhg-nفVV6m^+ =@\ǹT:CsR8Ȏ3keClCV_q|.ke^8'kF\Ǚ!1SGgRڊ18'^ˍ0q'vc1r&|LjK8Wqd.ܞ À pտ_;}~U/Ύ j_èhI_#QV=o?SvfGtQtiY"}SD~Y.jGK;hh/ N(C {+KgXR:LYh<괥}"څ]HKI 5H4ퟸO(֩/눾|,qj < ~Ѯ>Ѯk|D803t~YY_47xq`*g4ϐ2G:xApˍrË}!]\G*a/_Z|.\U9OGf|FqKLp=L8hs^g~J//.gpDE7,ދSQo܌tǩ3:^T_峆0l(no_~hiMC?4s}77$OC3?t/@__5/ jVE__*~S8= h_.i組 ބ9Τ1oA%;#1NT3 >-9 obb$% :?.~S+lI.gZ*W/l2dԕǒϖˎR9s|~PZڹY䅝Fs`m'KW :Kcf1#Y.x.4\F7H*FӯG|?4yUȌR9g:"˞9Ɣے}v}oWߍrZTȜ#q+ϓ7gD"ψ{d|x1ܦ:u;>y3>d{F>f;߭ICی҅#zV ?fS p}+tFh4Ы6e<2f⢙3^`O<*9aHn9 8rwFd'/8g;/9=\HYE/Ի_o^_VTׁx\C'?*8ﮤqbw>ɣj$GUy}&#>gH_{`Z" q~;+}Iwt~5)v)_3&lsho\zcuul./y8O\>N_e{5o~acr.Y;_gȉO>4duN*KKD5ͳyDv>]??@+z%c89?MxsA.8o"*~|1=YqڋOV|t~)>ǹ{^e\ssLw^}=euy|gsWKh^#s3?q^M/.-VNkM;'G5nRLŷK8̽7Ń_h g D/.'уG1;EKrZ3oX]=HRy ˠ_qq~_9/7_W,3C]0'33<_[S8[a4^H&N\+ "߿jHwa x?a˻qH )޹r7PNa/7_+%DGqK#z W<(~.ebxMb"Uq+%MIWJHJlUW|Q>VSRViv$)]-=2(Se,/dKyFm[NvˎI(;o.&Y,o]-D*KmL~GvC){ʻd/y,^Y!~G> r(cr"'9AVGǸ~=Tg{{2o7wwtu}Oa#?qץV=Iwn]=OzHOgz~Lo_W-V5z[A^ߠwo;7zWD/RP?ԏtלNja>m#4k[N{H> VK:zayυ! xE$,>҄m>R::=tI7~݊[x)i"HN8K79T!ҟ"F:tM*Rה(--mm[}}({|,IMySDLE(D6UV9VFzivG$IG) IW] WhF:Ion7]GUY%rH"n)!ZfmXE[okIۋv E~hO:Q\Gz@I\OH6Y@vmщmRbq#Y͢3YEWm[_"KD[.&D1YK,;+YM7q Y-)dG=mdKw%Ȧ!*ȶ!~o[N6vNvV! [O {w/zw= zw d=dEoGŽdDc>~!pꓢ/ OV*HџwxlbsIb Q )1b0JԽWܿ'pT{QC)Qs}|D|lS1]%[qwU %'IO"Sw'b]|G '_O?ST=bq41C:O#Wdﳿc\!&L&B!qKHQJ_1t; t¯D/:tE9v2o /љ#~KgXBgXJ̇,B+๚s5WZ*-E jjqJi%kkUJkhQP(q Dm\ [8Wz$d >#~  N2$Cdy G`Ef 2e&/+s <ʇ=W, e!y+TJ3ٌ|Asٜ@ ق+z( G}ʇQAx h0!'abx~jSS#S)}xQn:U''f5j k|[~j<&O= ?5~SOUOUzxjIE5m . )zs9qgM&u짪&O?5Eo&{I-sMԯ#5 k"J3 Z"hGi:ҵ A kOz1fP}oj^Gu4xS],0a5hש)pz XMPKx =Wi{>@ç>F0:E8n?&`0HX@k*`kkBb2J:F-]&stqwջUuk렻u]hI:J@{{{8q&S\iD3$9 Eـ%D3,w5ak%` [i6ǰ۵ݽ۽?~Qt0uMyςɫrmt.t_Bt7zo@w_GFxOL P/Wj ͔ayQ~L?f 类xX?PTѵ <O[tmO}SKO)݄^ qO_w`0haDzF=c<= S)Ԅ=fHfXF4AxV0xQuųܳ˄} ;8^@ z'^ "\LhiB[p@:QoWoC{{b :~`wwmJMO;{dɤ Q9>G䝯,2aw9`w-Cݻ^D4VvMOLgrf1Hn,暰> yEZnpO1lp{]G0ǒO7 l=ӽ}P+k|}}|4gZEP^__o~"vu T1XP IcLWq7=50hX}oU MepW1P|]- ŀxX{o[qm#ضb<vO89`G3ɎvnvA \k|G\}G~U_ p#S.\}ВhD4w1ԡή rw4ܗ1 Ck6{>`[8䝮*qdw?O{9#j"w3rV%gOVxȷ0i~دO"8JQ\GUb_p6|-kw u0:hhEh~Dmat.tp|#?C%D'hҏnR@O}5W£\C>}b>'?0 dG  рxstJ9S`.0WX@2a,uWVP K]+\SUZw9`˰d-340!b:6 p _D( чE:k5sO9zk6oPDo֮`lGk&C; 7 86{KkE!V:qsݔT?QsіTOqЖW*ƔibIxw(GTR-qfȰ-ϱd͌s3[}iSB#BmFQ*&EL+E_r|pgZp1u>ufZ)3֌99 >GUoVj-չ̱8vt3@|>~Fsv!zq6o 7Z ̹Ͷ3wDOBwY&j*E]-Ry$4B@uP,%g@ Ywzӭ?ES%G1r1tKq>AOZOB⻭716z7/3Gy9q'v˟)%:e^>g\eMڪP}}deʵ|Je muPnT&bD}-rx8k>}tGћ!@2>} MAoݘmo˽]ܪV@g#tc-CM>}h)$Γps0&m]bGCCs $?HGO%~ZAEn_`5kקYPH64juΥ+-|p Ir~t%}O`Gnȓꩼe+:A=y(k{ Q'@?>M%st-gA H4o~ }&)׸7mg)EY}1Т+g5Gy~tVcd I>$ݎfs},/zN,ӟ5K.Sxz!_KDOClWۭGR%kiu)j;뺂_+*A/Rި w@^A`0Ke}yOh}hc o|K'xɛmEАr}Ы_BUYQz7IX*X#1h{FmHix 4~u=+ Qw ;2hHy4tCM',֛Q!ۡi;Z:4p;__.]QߍjkZ}=߭B<T(6%yݠiZZQ2dN~% )dW #qW)xJOiL3I|Ak SM7u+8ͪ+Nn!5HL|ZV&#h]X XkO&CП`P⏠hfN(r6:5Q k W^ca6yM>CY[ߪoC|!׌OB˵v,-|l9b-ުhmkZ'xnzYyͳ"^>~尯w+,;Lm^@rIUpOsU3Ty1tH%ORT,jG# _.FXJSɋzHuJ^'P̫A5is<:4<: $:D%bM7UbB6[H=J@TZ)]2OP)teF\*WղVn (zIn;Z-o'QJ]#j\K뉚+)frHd9M.Gx9K֐rK5DV%r2Q 9<r 5_QQd9ιX݉&dAJHj^ND)%'v4:Vy1qNΓ ܞ5B*;Ү&jrPW2_Y$*˕U$Tș d^NW* 9AYAle$j2RpDeTY@9WjW2LIrjC?9{AGqJn33>l%xNJ+9^S :[ }X1#*<g ^uV9쒱e-̷`,WzQ2 Tt%uZO«&j{aƨ\g{6X)Viu7RuźpvD;*HO%me0-,[ s#Bx 'Bi),^ <: )E9nGQoyƸ[,%[-x:4unJ[R6n׺kwhwhq7΅Η5ΗiyYҨw}^kiyg{vx|гaouWwEE\j[ND @&&($(2  J "0y``a&0=2X S f&gu k,),&XF` ρ[ v] p)ӕYɛ^Z6+сa?T6KiĐs 0lY{iDDcV.{֗F48!6"qc]ɍr lj[ q$81HuxWM^Bi"6Ԁ.ӒyZ] zwoWA_CQn7 4l?pp/KHը4I@k3!LV*6ua̩FF[䭐!!=@/Ty3PST/yA+;?uW_wzy׳oݞ=އ} ))R i h [y*9JKL> _k^hF.6%`w(la:tf&+hd.j;^53I1?9 ՜Úg9b8-!2zHy=6硖rZya*ygu:B?V,T{{{+EAco"1I}}OJtQzU+w{! FJZbqgD[=I?wQBM8_C=X'#ćq6DT;}e:P}:) oo%n Ky.ՁfcC@యYnc,bSoJ8%paJf#:}2֥tJ)N)nJd \2 e08RFM_OHJ8erk݁gKY8eYJ_/vMʺCaK])R:6-:RT%AG #-Li@[&``Jw6鮄˂=|}`e/80iћ!Qqdߌ^CovQn9\UM)n~!JuJcMKLj.=h]ەdIdLOmA5_ʿvR۱v4$څ1[-'(7(OCRSֲM z2::uoWj(ub`DAbɤeɰ.`}N V.uO]:0+u XVnNWXX;AM=z8Xꉐ Cz( B,P!$6*Tt5)CIL`#! \ϥs_蠻3&p,T*>* u- U\a塑|lLaRS&΄CSC3|CCk-- d1كֱ-6荡-).}LgȇCY45͑M E`#} KM5ȟa{im:uN6hrZYZʴ~ȡiC@= TiU;^iJF~kLfN:ȴtx'C{Ӗw-O[M[k"âSsrN[s&^ִi9{7,XnѺ}Fۧ2jb bzԍ,fӎ6Vy®/ gSf[, w w {{KO?<(ecxhxDxtx|xbxJoxoXxVxnxŬAsExu6{69-38?7| |8|,|"]@z8=;K/ dNo;FwO^7}@a#džR SH>/}ae+פwKߒ6}1ZK?~$xz}D8҂oxC& F"i#f$-6tttEzDʍ"~ IYZEE"#"3#s"#"K"#"k##"[#;"##"G#u"Cpe2B"UF)Y1^gt ( n]=3htB!CRih?\FΌACي3Fd11cJd~Ys9O3c`cFMҌi<16e( yGeԲη|?I  3glfo ^:c'Ӧ-`^:coƁ2NdDi3̢̎2[se`N)ĖYl;Q{ό>2K33{eVd%< /kWB2;sppȖ̱;33f0y 3g.\&s]9o-3we<y$Zy<*i),5"ˑ fE.g&7YVˬY:gu.adg*ϪLꗲ0k`֐YeUeMΚ?kf溬9Y$ky֪YxS֬Yg:Uu([dձ}ekٮl_vę-LUv B˲;vew.3wvكgM4{8{J1({z칄p/d/^a̢Wgfo0䜽9{[g>#Ir9zN RC8';0(uNNDtS7g@a9#sLȩΙ3#gvμ9sY.gcΖwUܭi[ݠ>{4ί<_cWIy@X)`3Vy@wd:%.J|3Ruץn=}gzHng#yA/' Lo;yMwț7=01uYLs?ot>^[*5?o)9W$1aVڑܙLΫ௤ox W[v6axU_yRyV|o'/s ,{W~t6B҆w"@~q~iZe~^}37`h2;azaozuȟ?/ae+ߘ:{}ϯOo K)D r +\cmUmk*eAkUc7 :t-(+-(/,g`ZCd*]R0`T8la\nAFC& lID}G|Z#4|{9Klyy(o`cRȿ<{E Q8k l2NO 0N.OvgƪPтBQHTg }; JUY_"5UaŽ] K7v+Y؛Po .~:r1vjzL̜x>}q׉&g ƳGxf<}U8z(9D|G>ʹceQ*BO &`9-=Wˋƾ9#38|jcmT,Aap0ҟ9Z=s,H ~zC۷Rw#~iA@3h䳅9nlA#٨ZhVu4luuʆ_X:!N=88!;8G'qB9􁿟OکE /}CȈ y)9bRّjNNe}rv!\\#i JrI9Szķc] fI j$--׀;@>YwS'eLsUgp,-:}Qɿk;=(eriPs(ǎ魌 F'y- a}lXa(E"3yދی"cĄV2Vw 1or6z/cj#cKc?Em_m!X[wE {KslBkȡ); m{g+5߃GY ga+-'J$C]hc|ځ=r s, emyʽ'zpⴂFns-QQߝN#ӰCFn-fSC9K; |CwyW|HH=&m}?|bAh ڟA=зޕy[v@s!I C6})l˭VJqXZ+lqzOVifls$ 1`K[hT Ɩ6򮭅V7E a{(g٫XFZ1~KR%T @OPh (q,7 Nu'4V*w!FnG;dsVU!~rro(mX:Fqm&F(k;*zwFg`ݝjl9lgKVSpj39@ }x k"^-yTx~1iZ#d8b.cy35! 8 B zwmṫڳSβg9|3LIu=wOB8k`n0CeR?%Гϋ,OK Eh\Sd##͔֩ Ε\,ڕ Hpr>3 8x~ ñ)wZO3J~aXG(s%؁#M!Lf)oZr>-^!]Fݶ`&5YK?+4 1K@l_Z's~E03#h/a },N)W,SC% :bu+B4T:z5(ơ ?*Z>>w1˿`~'fl4xԅ?Se$ͨrpJ)9΅jNmh o(v} c~wel/;myW˾A9@>&o?%G |-/ÕЍݍE_ Cňwbycb |\wlsUd*gRpi1G9-d>fRAB1O}wT!Crz]WB`"S;Soxn:o쮽EÖg+=lWخwٮ])zٮwZZ{llDo[[qb+UmJw-H}1qtvEWJXTBU:)J]T(}`~KLP e2OY,Ve:eEٮR)#qJp4ϩ,~n7zii$\LmI:ݲΙLSϖ,<]/װXɦ 5sM\AZiƴw*ًSgP)vUd Sc[ϴyng=aN‡jzJx,~/闖yw[P[H[ː%XX b_#@pγg3X}u`u ՟yͺa5'׍י,Ɇ㲩2G,ψc^cu-7O''\cuY^+IbW.ˏ]cu[tRMwW݇GӤ-k= y7W]=׏Nkrod#]4k.ר9zN}@ikzMLNب_?w9F&FqE}9ϟuk|>5&%>Jlǩʹ5jk/lߧ`]Gj},^/ d4Q߫ެol;FcS|:a<ׯۄqt|b/b`F3ͼ-m]Qi[_{+KC}wYz\iޙ^nww~}kqi5bטݨM}rfh:ޞi}sll:nѴ?MۊGfU9]ؗ+1MxVλy-i*C c?dz݋iuO~)B-L[2G*޳Wβ\ǴZm}6e,~pjX;#m5r[ ?^BP:*;1j?CF6ڂ:\ףע"-$2Z1k@3iu:Hđq]6ˆ S8UaH? |km#A>7";tGY(t}vGOɴ=kG"OyECg/5Rn?50R}Ϡ&QՠFTNE į@IO=H H 9q;t1b C#O~q!N?׀疏A ?im%H[ZC6BTCjAgY#A Wa㖿"h1GJ\[nS\ ;Z̷[f>w(' <+5^lʿ}[^֙حʻ寖Wc{M_YX71h!DKqFגf|K^J=^*D7qCw^nҭab-׃QFyQּ~DTb8(:lj*10 n EΪؾy#_X=./!:cO9s}^ cS;nA׀=\2$f /g,km6HFk)tmsZ ,% O6j1➰I]jma[6B[Xl+uURAT 纔r |t0HbTyjKmWyQ5 :Y湴0IKmTm)TYycZmmɣ⮴\OPabXJu[mkml fF`b۶sR8pl:reE̢jg;H׀6vvVoWAvm݋Q{`&ڛilݹbGj9u; NŷwwnT@^fa/0Ng/V^ɽl׃ec>>tq}v0>$<^eOϴϱצٗؗCIOkIK[mExn~>J>LIZ+ɗJLOj*]RǤ.I%Iݒz&N?֓\=7iCФÒF'7ǐhLzF }j)֭IlC'ͲͰJ9;$-@ m|Rv8iiҊ$٤ZX҆tnU's/IdHMO:F tlvd "GkG{:;9^ G_[1111RvLuT:9f8f;ݱ>Au,v,stqK:!]}#-zt ;Π3%@llll$]cr9{8˝hDa=~1+rtqw\Oq*d4L|"Z1@˝K˝kO"^\ĺ4t(F/pvwruֹߥ\hGL?|[+ރt`w|W W+W;d[1 퀫 {[Wk5jky Ѯd6B^Dޘ|555r(8ƺj\K]+ԵU@ͮmI]3\]\'mNuwFlw|.nm+4r&_Z.vnV(Ff{'>Qy xh-8{r* "x;p1ׂ)޺@"iQL q31 5;kځ9rt!Ґ[ PރuF>`W>o{pd߆7`#-'0ykȳ%ow yGD!${{}CO;ː6RCOpF lDՍwux=2+ M6]x8?Bv8Wv{Cs>.B$n X7wA<1R#()wڎg6>G,LP;Hal+jX~hC $bvW_A ÿ}i; l=sp_#m3<T/BVY݄OƛUƳr;hF}D*gFK s߀$RנWzY"HEYGu9rVr |8\7]Z#X]̻$H@ GZ"2eb;-FԳ?Js_j|Q`EkAV@1NCEYƎ&͹-AC'P Va$QV Rԕ(yytOJXt톖tA9L?"?CO-6`ٲ wWgM܌Ey/d x4pĬAO"~3SmF){Pz2($ Wt8z/}M]Bq&uOe>NPon61 8] U5if& a10kېg#xfM ,Pzmwf[ 1}}: D!s$I1MNӐ.G=^6,^v ў 0d&L.]`uGyE.γN*T*s0*kUY柂sssZg!^}y+8c:hoۊz 0Ʃ=ݎnyl3u3}SzS {r|yw=͐WF#Ud!sB๧~|4 7%Et:4:Ly-F=I>w3fj{T+/CgA[R^6A|5^GMWOG&t.^ Fiu0DG8-]k~ڟ-÷& &hJԉ9bf"> V.,맑BiCeDN-HE'^A+?b|zꅡjMK|*C, ݯ?t8RMk%| "ߒDVHED Fwz!MHg"r/R#BL2B|G#Qp6s'#99q>|\|q>|Ǔ5dHm%u܇܇H]cs-gf٤i]IXeܿ&1`3l08Х`VY &Fer.]ʞ0]sT'& 0NPĹXB~q֔p/n{Mh,U֣h3ڊ" ߙ&4:cxL'r?"0Tf{`6<9jW?۷kH}p Zܳ~M4K]%ǽOw,g45A9$…ɐ]@W<Ō3 g^gosSrp/^OE:7JX vA5H' f"'2q<@wa"qDD(ܕBD^ӓNԦ20!D}Pq8XΡGHss41α;&qZ'AR\g#U2VrmE%n51@ T& L4 jyH7UܐC / KCU% =EZ?垟rq' JH&o%#t!0Yuhg4fN8ǽ3gL:t, tCs\ [|:$tAޓ.I/.!~_JzTH}I?I ~DЏ$@ OC3I&2~N?' B_tFĒab~Kw?{=AҽQՒ[cx"aZD Oj_@8i3:mNݯq#Fs1-ƜB5TG!vh/ hj綰҆iVP;8Vٓ'Զ#3KR;ȜN02sgiFf6]aù =.lHǡMRuw>ں'GJ )(ްa^FF[H}aocFz \/cs5 隴uF#}~[]H/ֵ\ƽy0lo[t^)#i#Zd~{={~{=|~{}~{ac?o*{l~{}m~{}n~{ȧ6e:FpRހ0򅳗O>z:;Љ"{-dvWwt^н]L<`h4WR #my!XRgV?3zJ|A*ShaA=^Erq%? 9,8m l\g!Ws^@.0ܦѠ/V300|'4\~9|*jMˆ,m#Wc*-W$H2ܩU$XWs]>6<6 |. =/hAou9稒@"8[~牋"'"i@~!nFq\@vk;,*8+W2|aȽ@ )ʦ%ڔ#@;H#81NB#JN~5G9.W@fov6nJ5tkUbεPBJ!QDrMuuruupswR]s( oV%UѕAu#B$MdegY,|iw_Z.BI+;/Vi>l^;Hԫ@ޑ] 6C*t(@9{3dSTB5Cst$PCF*(qj+VWbu7s54"!X\нAa C 5%)A r`7WBBbMj?/ISp(Ŀ]()diW"iF`:뱸A^b `:qvC 0]JH99%rrJ4p)ᔓS)''L.o [yrW@gU^Cqre-yn[E[ME [bAW7WOWk+55eI*_FľXbrYGERFI\XnGcp *}>dy).O^yrDb/]w,t}W+ 5_~:tץQT}a7mOKp2_˜ IZ&[̾xmEjQ {۵_'Kuh'?LI#obɿRv,\x~>RbKw7,WkE@şYbތY+R5hLiu %Rÿ!CBο 9@,M9FN7Ɛ[.˦k.7{!m!}OO6ض;!5<=ݻ}4qDOѧ %x%Uw# [[,=l߿m`;ς<;Rя+SZMwgq>xTdh}<6&/P;^{wocy3E Lzp m}yлd|/EHoxz6g%BH|$Kqpx/4/:n>>G x6φ|K|PVlps('4JT9]@dlSOٌ? Qʇq8p^2nO|NrA3hO܁FJ[6JzF9 B9D#"9_EmuQCpD9E {9;C.Oق6 y":.rv{D垈rBm3a䮜v>9>fvb.X"qFԼJbm'^PsB]/>{ CC >9{roH5C mp9h!g=sE˼5Ѡ&D~ 'dyĄKʝ(_+Kp߇9ctP(Xyۋp+ùE|=vj|jkMbv mrKD&Vt3>_oMb6mzM}W3U=|9f R t3X6>arþ6ʚ99HnxsY] D;q42\r} ŗ\a/,/<0L*f.F*=1LW*s-WŤ\+Z7\X[,h܉d⋃ KH'C()dӉKH6CT  oqb%X,li2! /=Ū:Ѯ[ WdT0.?/~:~i+in #Pk? \%|DOƤX/!n*X+)7bJZW|/݈= UZ? = 7a}E$Zb'j^#o(dU%X__, F~`3 ¿D8zJrgUdvyz@r-hu_p_űQȋ8p)Rqi@ -e'_R!tTtiD]ZpGC=PGҘUۗbFGki]EEX>琢kX5wbiU!1PNıDu#x T0K.y}/#/ȫ| 3'\2_:.2w9]˹]eb|M|ıd2).ODr9>)O#rϣ) pvO\KM]Figr6{1I; Rt}3;ƒcfQ_Rz a03'T}_'sn e qQO B9Xs&s)_T*D [@+-v!>Рp&y;ai0l m +W.tC'K9 GirXeYקYc-h[֚cX" EMWXur %-t2\=VXʵcڭ\Č;yՓrsS]i׳IDF:NlNT mTureZo8P.zUr%6i#Dd++Jqoʼ?a!!Nl2{Ma/c/oOY6%ZU(Zs&rZ0XDMт}y.d OR,0I)*-FhkkvpHpqPXyUu#QgΡܞN1BK~gL%X^(e޷÷'__S..viLEޠQ \E=<BSt=a;jU6k=ME\wf/_/7C<`)NIg9ijrqP)%iVO=ZV=t!GJ1'+ԊϒKRY/j}yEDFC1W  cOlOR~ uH$@au@{T7H'qϿ[GO咞aqГ?CB.tkp~:NC:,zE{'RÆ=><ےtrJX9CO ?n!N +†_K4MLRl j_ "LqOx;z)FLŢY,gUeX#X+`4ά=’.gUYp , c#6B,M6zlbK pi5F!xa@.t/J%wѢhS:,HGNOQH_G&%eSB\vBc"\; 95[+fF~G_"R'^6.8_Q'%b^Zj'/S2j-Q8bt6E+z#b9p'% 1fdRp/\▌AnX=.lJ.ev_gz7@?$$|ҟ0L$~}|2oy<g׋K4*ggTUI(}Z\"](PBOB{e-Eۈk?xlac% v0FFZp\ edUL.ƴt_K 9/-Nբ joEtjl{:po$Ė+Ƕl;pdxZF`NC( vac\[+ \QN[q2Li6ď;WzK!cG UL 2je0 l6,gr7kJ<&(x^P>*?wpߨlȘ rN70&r0DL$u};1ᯃe2bbeP|ĖL*` [r6aI;_j ~a=)g8cFs{ Sނ_| } jlMze|]26#b r2c%Ok,`YiײZJ 7d.tSY ʚ(Qvaŗbc UL`YV~ ǧDUpm=wYF.NvF={HZ ;WrO4==l>ԿM8ា-HN&Y&D+ɵv$z[ mH3i< iHӑ(|tIH3$|!XʑF8SdbQ Ls%/UfP^E)d(*TR /!1A1MBçy< >([A=ɒWPT%++zɯْ7UxǷAS<=Tɡ#~qFz]FFqd$,zz14F8q򧺊%gllkE(^_,ŷImʿu&yTMP +nPi>뎐VH~cXhhx81y[݃ N>o"}bܟ cP2x Їk }ЋKH2L$d&K2ddE#R;RϖMH~ ;U,-(K~*WRݮ*VnݪjL9~*i, {6}ih-\+K%OUݫWJ+%Oi{UtTQiIN*uQ<]qoPmI]Rxғ(LwU!Uԃ*_=RFqw1IA=F%ڡnj Cu96Կryy~~}oo:ORr'}PU}>\̤ԟ'ٞΎΎJpRqr $)R999q#5׻nKZR6I}+Һ4Ze&wHS&R;8ܰZV !mF[3M : Z$Igҹ4.h6mߝ=LÛz8= f2,Uf5qe Z]XO֏l4&֕%T6dc6`s=m`9lcZiU4/yZ5S+hZUhk7vkhowԺi}A0m6Veh3mR[m$14hχ h׶hnvT; z^]7[mYO9 j]Y9CرBzBJY/rB!aW]BY/rEرlg9nBzYO/rl@^w& g9>Gȱb`{#_RUoo;^UZ~ۡFiSk;n^>JJQJ^^^^?^G^(*~UzSzzWzbIߟ{)-O+VڝQzz9I^N&rjB/.rrӐz9R/St R/[4^NK崥^N)r^!rFI^R/B/g-cyYNZĞ3Fj,UZVP])***)Q+*+SzUQz]zUWzPzTzRzy^Q J:JJzJQJJJT5R݌XwүүԏP.ChMV]5DKhw<5JL2,jlVr ! X"hiK@9݈';'_ͭx6mWWϛx%}7wN{w;{}w \l!uyngu^w#~{>EklJVusBWq'bހq Ĝ68=;v&b7ul{cP~ )%(\ؑ0@ ;k NqtW 7ѝch>aGv:}/pAJ54Ʋ"jDаlqўaҎL$0j{o\Z9[9-qǟK?a5$phZUϥF`՗@!z e.Jݯg?(o{R7s$"uAnxC#}mOݯyN .ÂVlUO<2%j?55gT$!NՄa=m6ڂM64hΆv|hfi5 ڃEm6k96mKۣkG}xW7[xSޜo;]~?{޼?cq/?şgsyOKe ;]Zo[xʷ|7(?O1tiFe6rFqqqqQͨa2j7uM-FSe v/q/u/3imFerfYɬlV1MYǬo62-Vfhv4̞fb?f?a?i?m?c?g?o`O_____߰o؟؟ٟ_o#(>c8>O$>O/ '39 ?C0܆Ǹ(e52xQŨjT7j^#c7&F3ir~פ&7]e^a^i^e7+ךיכZ M-fs6.>~AkGڣx{=ɞlO3,{=Ǟkϳ؋vlW={,yCǞOtDvdTd#{D]U!ꚨ뢮U;.dV ze^v\M2,m6aˡ'b뵍 }a~v_ϫiO;^މ?^/C#Q*!*g |6{~~ ~1aweJFb3n*=n:Mӌ4Kfgƛ5ll63[Ͷf{j&f_)YE{27E& +k[\|Oggggg';2"+#"b*EU卪%Va)H끔~K߅I }ԉ)3l,<5R tHs׈L*jkߴګڻڇrik_j۵oﴽڏn )*6Oyލ}x 0>s|ŗ@-U#7i_ͿOg+6 ai?6ƕή6x"\u5dS3̲fY۬k60o6wwf/s=c'4{&v^f7[v B[w|Y銌,=WTQFUuCԍ_niJK"Ցr$l1{~7*h=BFl?Ch}nCϢ# ҠG0>X2I6lHPbQ(8Z.#b>brHg1 ޝoF6}Ӗ#^`#;bX~' ZJ ҟ@ih0Á65Fmn/' Z+>wO⻧S#GŻHQHG:H 1 9'AըOg v H8 DkB8ZnUUx;CΣruO|7-0 @$GmO'cW*?x}& RWAۨ6~|K%ŘNfId6Pmh{Jvvqxb4n5 =jqht2'׸Suuf{4l枯=?#i$%#E#"{G&ENXt>ҽH)9+I^mh5ryLq*{/#݅;3ޝVWB=+3Eqk@ZF:Mrj1){/`+;}!ݍtO~?#=}ҍJ&:ճ:$C*o22Xl ;!]!l\%6uPNK./@+Aӈԃ#&5Y Ukd !G)5 R\g1%Ai"B4H=$t@_ց·%D3ܸxe]YZQO"m3!_ TWQcQr6<*z+ض8C%Y?={Nx]e_;h%B l[U0HmORˎ۱v;ήhWkuvz]ͮnװkڵl]Nouzv}оndl7oMfvs}Ҿnen19Vl].m_iW_iu&65[mimev}ec`+du֣H1k5zck=i[O[gֳ$9k5zz՚o-޶X/ZӭV5ǚafͶ^YoXsBM-kʰXXSVZng-޵VZXYk//WvkgY߭fkZmu'zSkbX۬\kk:`d~XǬ֟)ukZ\\]5js.sĚDs!a- YO>3(i6s(񿀲Kj=PH~"8I$):5ZC蕴,^M "^K4fitIgtKt͢.COgt~Ah.A^jŦLߝٓbOanM*AZ)YY*7m'uennP:Io:R`WjЃ&1 L3 ]}J!mz.߄R[@^AyҺLϧtq&hH-ȭPew$P5P;CɽP3A'.Nu@#`Ak0%u} z #_^or Z?gYQN4Ԇj/eh4-Gch,hj8(D5dF~;-C]6wӂK' jP ,[NwZyR=SֹR~]m%y8_F̏#oűG&N4?(zI1iru䓻 wñ 1%'a`?rJ6`,nn#Y.Cln[З&KSH"IŬ}q)sI( .n9}/T1Ot's:H]lHL ^  _SM.!9.eJ4<_):I o 9}gXiRa}/uq:iI.4XܱqqZ8%+A>!Hq)%/NJi=$, EzIʩc\'_)F/Єx:#:C y~-6MnoDj#]+hD) >xKK>$s|,#+t3Jvd?9L ieXS֤uhCڄmh{ڑv=quP:D:N\=gM 23ѓ yУ zг "!VDh/b!~8k˜-]X{~W]\}hW׃@tu bC\7h\=]r%Mv[C*v-{1^+hkT@h]\}ȕ45`W:tϰkk(С&aH'Lk#I)I[%)zxR~ .AO(2v$ 1j3FwaY5f-Y[!dUDՙl.b*6mlώfhVF*k5zZcVuՒTm62hUL[ekm.mv$dTe^OozGcz>SgUzYߦGnpᱼ2Ƽ%o;<|$' .ʇpDzƲ:u1&aL܍F{{0> eH:G1nFB%M7ԛ-[퀷;f='}T)z:t>(w$=}> L}><UWkz-6;w!맀Ӊwrɣx42>O>OӀg||/ˁ/+9|5o[6[.~:I~iNaQ#2F rFQxQՀ޶QS^hlhehotb$f$)Ӎ(#1x㌉Lc&lcc 2c%j#Zcfcx|q8q8 q!Vt"C~u;*(Q!V 5r4sx;GG{:9Ou;|cc" ɎLg9:X:V:UBpc3q⺼=n0;8+N9Xԝbv+XZYYErVw&RzFf@kDggg7]=q:Ӂ99G|ss2I Ӝsqw.\\|sjXzf9\ۜ;{vw~yyq)~ƥ"rlWiQhW,W%W]] z.&.qt WkW[W"t#iJsֶ?!= \@&"2gyg$D&!"!"dDGyDGd "S d H"/"""/"H&"LEd*"StD#2 YBD^ADf#2وȫD 2y \D":"#:""țț,@d" YBD"H"Y,Bd"YbD#"o!"KYDFmDFd)"KY;;,Cd"ywy,Gd9"!"!@}DG}DV"||D YZD"uC$lDOGd="")"" l@d"لfD6#قD "_ "9 ȗ|ȗlEd+"[  mlCd"َvDrE$فDv " ";ىNDE[DEd"مw|wFd7"=Ad"{ً^D!}~D#AXX"= / v@FDػ ( qNΈ܏܏HD yHWD" "" "H7D#$!H"=HDz"B"IF$dDz#ވA"}H_D"~C?"H ") 2 @$TDR@D"2A B!DB!DIC$ Fd0"鈤#D/ Eӏ |RǹKZ7HucIKZ7HucIKZ7HW! U|ȇ|jDV# ?A>s'#B"9aD~FgD~F"G9//E("}Eꑆ1iFZ֤-iO:Τ+I"D2"!č:1J#bEX[%>{4&bWcŬwUN8@ePJ5IfY8EEл =yq@=l1,6<Џ_A #𴽑d <f,ߏ1Lea2(OhE#NMA;*!3Vx:ևӱ&؞u`&t2Z.kL2IJ|:]X_ebR&EXdb?kLm2Z%y8l&=(H] 2Q8{ 1lE-Zx [3CFhķ&al"uVQDR\_["}ggො`?l# <4W'(t稄S|15T|l.%ɦ@|KL6q#7D1KM\ro!W%5. 1HUE/{w(Vrp!OV-KOyrT!6){T.x)ys#TT!.e Kzb`J֗ውD5d)i|txjGmd4*yrV.Ra/ d-K]߿s5kmR h`h1\wzc-4e5_~Qgpdt&p$9ҔX߸dϯ.=/=w;.=k{/K8>R#>ˆ7܃D;kuh-B+#,8B ^QI`%w (kGOܹ( \a%%pNBʞE:Pi] '^N*!_hrESMvESgn1|D<ؽ>v_Nة@{f!uMO}(u rؿ:07>H"퇴?"t҇!4鐋)`"=/ ҳj)EʐF u#55"}'DhpOq!qh־ُ}HNh? mvhb3>zz^||@ثH|}\侴Yl*IGjRJ[ ObW,UAwC߄wDzDɜb"{ =KU&nqQIK #p&[^ +@dA@90fsTwXu+H!4H\BZ e+%8`{&[`?TAA(vj;JڎRmGTQ(vjC,t ]NWѵtBѝt2sLj0EhYUųfV-KdY7RXF1l<3 (o,ve'i/ @"@*`ӑB H_E:kH"}~i7ݑ&!'^HFi_B+ `HQrP(?kA16nM7Ʀcӽ HW# 4 rv !_*ZO綋gGz/S"[3%.3X^7SiڀqIKw[ZtOOm VU4i LBp>.?pUjuY?cnl݈fPQ}=޻Ճٱbw$2 IXKZ~)O}in?6=vvլщhm,QJ"Z.pRr=bjF)v=Ky7Ruܧoj􁩵#{00gXo@"J]ygiLNk>0m}ፊ V*:<^5I>`P\Me 7־n:7vAǗd7BaZ=RjSԿ@k JsH(b#eMh8!_rTomm?p8zrZ]$A̓5JiԼMW#oayʞz69a_=ݶnsKO9u^ޛcԣ;UK}zIϵWa>M;Օg}nXXTdz;M`5%-k |ȝ/<´̸--ޝ<ͬU7#[Jklֿ4.y3']8{8ڧSqW>˷or˧3zu1c[oY|mi|g;=?`J?CN6(b?bkztKRldUeg{NWE9] kN¿,O= x~h;6+OWv:m\S_}q\Fw_j5fsNxhʭ6i{W\9R?(qMѪgSφ~wh۰3ϜZmW!+׍ykRֶEվLMM{C?{94}wݭJַjGXmĹƔhۆOnz7XU5ɒUlx"~?\}W!kEq㯘8?jAgے|ޭtY~\*^G5=O#V WHQ0CâwWf DN#,eR S޷.ԫ 2fѸix)ٱu Hrr {ڹъ~Wp̗WVM˅4\0i71OZ,0n;rJIV>=:T!+m- w5\%qs扨eVTK3}Q*!W5V!On @F7P1H{?;RR$G__ue""q8Nb%PoMC//yvuuRuq 3w@$H) @`H~Sks)_{?1(;dt[LwYf_:l8ĪXf _&DM-&<ž0{ 3LY_2ͭם T7*T GR~qKH<#-FR+r^_'6SK@B u6ҒI8]2P_~Sm3)PBdNȅ_A?eV{ywHZqA")h; @Dx9.웝w&0z@@X7oKB'MRg跅')-fpyvwybiKprKϙoÝoP@*E(fˆ{V@@_rPvԿ5m+"cJz txYK }QoCp| |f,ƾ ҭE>trʬy ѡQd6&ӕۯҳ{Շ ^>ݘm0wƝ牣xWcG΄AG,]I+ឭ*r`L2/N*(McyI#,$̵k]=C3c+s !Kfi5Ê,7ar錄X?P@0ˀ#[@ Q ؐARߠqlxł;; {A>lQϻXZɇk͹sYO߭!a!F=B1.n*(Cv̀P>y!ɖף,ucDQhK;"AeR,lYH" VV[‰\ֻ1ǭpL_8{:JuB3V0-''/FRh 嫈& 2MPГ<ȫlݍ:\#QٹD`/K~߁BLA 2A* ?sߠ|;A$<` ѝ\(hGâ%7&}qKZB~/Jl $rsg`t`Na;y.T , &p@ `&*c}N~?@rAN gdޟTv9 ۟`{bU!8>8̓ M9<鐘2HdUlDŽ4(kԸsku̡G*?RBH(Uo,Zl'c ʿO+2ȳU'N;vCn'mtCԅ]ͻCB+(nrp<2.j[^jœtL`o|,]05MZF=قdZm̾|LqIl1(5sY Wǹ\*,]< }[N *`uЦJټC Ϟvbmhp,5b.C`I~j߲Ҵ%ٍHR~R'C]Ǭ JԂ˯S?RFA#8z| -MP>P>3_Q͟C͛9AA<59hB?do6WkN `Y\Nd)*W ,k_8\2k 5Fr(QY72Κj5)wf t\u?%.0Kk#a{)T^gEP2;#vdgM?뽾?ϊ"fZ 8؅ a  tˇb{k7hB8}/p? vw.@(l@o/g;o@L_[\A@ ПOiXɻN|BNWkz@"9C5"V*S}&Ų+mM Yx(XoNu fh4y\sOYWdOyZjUj -*ꃱVw׷uGfkSWv=Oy>@?9rt\ l[By0rڝK@Ǔ/{ζ/WSh^~ {E) a$tޗBMZ% `& } fFHJJğ0{?&GSɴЉ.|&a3H‹˧0Mmt; '{,]KPdYnXٲuok+7Ux> /CIDToGIDMap /Identity /FontDescriptor << /Ascent 891 /CapHeight 662 /Descent -216 /Flags 6 /FontBBox 223 0 R /FontFile2 225 0 R /FontName /CIDFont+F12 /ItalicAngle 0 /StemV 224 0 R /Type /FontDescriptor >> /Subtype /CIDFontType2 /Type /Font /W 226 0 R >> ] /Encoding /Identity-H /Subtype /Type0 /ToUnicode 227 0 R /Type /Font >> endobj 229 0 obj << /Filter /FlateDecode /Length 4610 >> stream x]n$9坙@C@c[KfUI2PFbq4)hƴ|gL/?fm8`01%=~彧/6s8}V[<}׷/~8g?~_&gzkL7-O_|J?9=gYKU8zg7|j|@i 0>^_Zy9JP}j?^)?^{_}~^@k 1?Uey#>S|t~T^?Twf~ɯFsErgz)[ k_!vjhv%T]/T뙷ָ\^|i?bׯKlW z 0ysC Z:/>s\}³G_RK! n(;ez-nVй%U4V?O-K\9yށ_L{NpijtfNRi/Pr1rMZ+Z)\8+k.*\P!p!W*$/R^SrpSe2dvl0B.`ޤM.UJz]"ϩJd9f7BM&uYCd>~N1 D%'Zn0 )MYxs6m$Z tƩ~l|(= |bzqs \hA8j ~\AȲU<:[ðm~92<rtVJR )Sq ;"@nH{qP0..U+Ȃ$uqZ=PO_TĊ-}*bK(ERuלɨ-akZ] `AF:+j9|)3Jr-maK|<-!иhvK:HѼ6XaB2Q :?0*~VL^I <:o9Hೌ!\&[9lk frPY.VjIGSbm-{qԢ_`E`?J4ALZ%F1m뉌56@h OIEz缕Wo21| IWhH>W$S~0[IN<65/-b9ym]o 0wsVe)乫JG94 `u 2#nNsf”9cmL,H2q& qD+ UG"%22(p:8+6gQ1]v9[Zw!c=†&&E\px~ a O VX2O*dA%GAiK׆xWUˢKK,C"B,I::a$7SAS,t>+؞cfmX'ڌB-%l2' ( uiٟPE€|xJ@`lhTC$^OMTV;|H-B؜E?%+6@ (AgqԲ:,T2` 2لk%k!hY%S:7?aXo㴒pXLf-0it^1Ɖb)YֵzP+.*[Ʋwhס-/Ih܅Iz!'  7,XI B4MĤipH%g@n[{.= Su3x$;P)GwRi9{@sZ)ee`VhjL%{COoO_ !7T7ʃ b'pPJs(7y=j "AXkM(JL&HD@]n_GUXPch_cI`IkTIkqh\也ĦGSE rxi9 -AJ}y0-("/< ɴϭ g6Gҫrp*Q>~ tzF{48ze .1mTSΜmfF M.--K (G#pډүk$0$HnK.ݓj y_iHC2c* l`G?3%_ק$ [ II[jP-cHTypS ,fٿy}3Ҷel\P &Ӣ*Ts%LljWjgc{Rխiчaz:-é!1ǒ2P[{!@[$9dD4ZU flؒ)P,z(؜UҸo`6W)<6}ZrVZ9\Ot[c (G'7P(8}6?8.ۯ|*t,O]ܚ,O:ڳdd0WnD*D_眄ow,5I?P&-q#^Q61bƽ3I r:Js#Ld%2sXލ@rv$*>()![hOe߰>VF+DӅUyP`670Mk5R5:/))غhN)osf3 X 57̿"e"9fȇ9ős}K:to%ǧOkx~r[W=я00mv4ڕh8Ix^8@7`Q8;GC_6u1繓&m̛V6$wOJo!ױF'MqLbCKx @X\P-b*:"=-s'{$Ε+QY\(Gg5qҡLF\Z*5+Cm_<d2̮#T?&7HoB?r& BF&zGʽmUcz >_sA<)ʸ>NrDJdĨg+Ajøu2z@mZZ]aX2ong(v}R*/p,8PՔ ,PHk'h=M4T>nx'*t#˲DCO.3"p~4Om UhQ8*w7BºDZ6ЊH: hυ'Bkz}K>js |d*2Gl/0 #v Y8 t+IlLAx_*I #sKǣ3gi:ڕp*`/pjDjWտʹnuSY?jc>(5FozV-Ѳwlmg"_4ًOmM*y7z(]ϑd+f/:><r81abgFl`Gw )bDw*Cj*Q C᧨\2ڪڴ{r7#{\/ÅU0h ܲc.4󘣿KP> >> endobj 220 0 obj << /Contents [ 229 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 230 0 R /Rotate 0 /Type /Page >> endobj 232 0 obj << /Filter /FlateDecode /Length 4484 >> stream x]% +$Rpdvx?pot"ho=$NSi|NK'ӯ?>Gx|a}?3|=[Ҳ{?~ z}i(#:HkFN|T6d fRd>Ox8ז\}{*T?4 ƥ~_?:a Azz|I-PDp/d-1I{Ýzpe7^Mz8Jo`"+Z5!'-'DA8vKsJ-:M#;ɽ% rO$9Zk d5f75H%GP5 MEu-x!*?{W ̠thp섞}%@^F+vZIv90 ig adK.ȽcȲ.DE{!<!Rf25уа_<ф! ^[d0pʖ27 BiPrml܏ L {gpZal@5,a[a }>G[Mv7]`5*}51u n(l|dԾ0oe*i#/]7 )r R-pS_z2u ~B%+d,*pYJ ""s㚆yM\ W٣WkhI4' 5G-DPzH |ʰ^}R11~е} ǝ΃U: ɩ`xLl 3gha:;)]԰,31:w ?rSI@P>{i&C6u 11W%'BV"Z>׎wȯV.וcKr;SΠv@vt@7WW.@ }u?ýF^3{mBmzyN0::Ț>;&} +bӍd͉4t1:rQ`H|k?WO/؞XOaexNYK^}U'm鴬ihBJE(蜀sͫ=Yu1٫yU3Q'pq-un'H/,($zw;p'b"%eB$Or8fKI9fu՘LEG<9ykzr}ji>(+Xi̦&E4=3R=mNlqQeBTZ<-l}F ɈAyƐ S{qVw?0?_OcN-z'$ړ7nIq٧ۤ҃ 9|zEo}ŻvLRwh)xOMz 2Kz(U+5dH4*biTe S}@ V3R.ncebGF>L*=V>Z1Q%qU|]uYqyv&PWg<ַؙ֙OVd18'-)T*I{%խGZ_z ^ʶcTUƋrN;(Ts0P͎=S=>TOn?Ӻj/0hlMf73O51Uh 5;*.hi1}oWD=#ߋ)= 0 YOlykuṮNS3H= DRMuJ2-5놽L9rwS鼾ߩAj5o̔hO=SF hGWy6>[Aw ~ d ffnS@?mc z@5͸vƴwYou:GNLr^gl D=9T?S(Ã;وxU0uQVjҎZa'Y`؊:k5 >MR̫;s΃KK+~> nVqeO3VthCGAfω21Q> 5.Z1EsT{_h6_#!އˣfQec-֖ՖXih*{?# endstream endobj 233 0 obj << /Font << /F1 27 0 R /F2 98 0 R /F3 11 0 R /F4 62 0 R >> >> endobj 231 0 obj << /Contents [ 232 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 233 0 R /Rotate 0 /Type /Page >> endobj 235 0 obj << /Filter /FlateDecode /Length 2427 >> stream x\Ko6WDPƇȭ@{CJ*C~3H%oN|JG5(UDύNQʹ?M~{vPǷNݠFw|砏kq|`O'ѝF='['7FgZQAd@0trg26}E/6q_y{?ZMV v^bBZGqy1&/`B[`մƐh5mOa^O 9}ƚ7W\ׂJjǚWgwv0WRbPYK͠`}!0֚ 6JALot̒줂Yz%@Z\[mTrwV2ĭ݀ k$P%Ed(eZojk `Vy}o7i? 6yӵXh3MC3;T]hKh3j-0gDJfhu·umq"llF@C+!F  r_X,Vş +oej=E8^e;y)J&/)tx\s3|ЊP F<&M=_.'1:0c~VJ2"`|| lVV^AŪ\Bf.E (0&h"]ʣ1 e6)spdBʫi{C3%*(UR r`>VC HCRˡf \Mbev{! fП~1z!Zٲif,<>BεZU!'/{$ %"XצG H Oٔf3Z+Vn MPVqy%!ۘiQ $#!ixVzSE&&q:^w6-_K=m@ơ`l B2t&o?ꏸcnܡ2Qh}j0#X>aRu_HՕvuR/m$B-l& t\? lrzlG_p_} ӵ7zi~m=Cdg\_p1}Ν-;`Mg^E(T.J8DM.@CŪ7ڮlh'$M:9Ar$YR拪d] pmI| G6ؚ#/u ɍp\Sz05;턢Q_ E9AsM_H'o"XoEN[G "ZD`U+-}--3h.CĂIFŒ>7V}8Ohp|<|0~ , r1p숬S:T Oj5affsRx|>cS{ͯ`,PM?::X~eZb`ǜ*chr=D/BG_l^H]Iz>>,Yo;sw!jaܳhW{g$nIzqKBe@0kK龓HtRh/̯Ŕ *#zܐ0B!0/q_#r7@lo/Jͨ?"4+~a;[^ LxBTx6X\Ȯz#KߗFxv![7+3EkB#a)WK!ħ׻v'2/5^VGC넺to''4h~!Skf43&$H]yKaѭ,,na#<'VƕjeĠVfS ֙N'6%nf{woA-he1+440{1-f'z.6S7dWc׆zlӻKd~uW ʁ;)$v6=wbzF-= \ԼeD[1{H7ǑzI8G-Y%/~قKW?\Sm u$,`vdxv pl9]~w̱{tq:w= [iQAs\d%m$ZVu NPG” 4_ikOW IyduB5H -'^oCOpAdFZW|X9(Zd[{[^ex˸f endstream endobj 236 0 obj << /Font << /F1 27 0 R /F2 98 0 R >> >> endobj 234 0 obj << /Contents [ 235 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 236 0 R /Rotate 0 /Type /Page >> endobj 238 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter /DCTDecode /Height 1029 /Length 89733 /Subtype /Image /Type /XObject /Width 1419 >> stream JFIF``C   !(!0*21/*.-4;K@48G9-.BYBGNPTUT3?]c\RbKSTQC''Q6.6QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?类{[f;W8|E QxzLԌVߝ󬨖Yd(]JAs[{M)BOT[~u &$:&Vߝd$ąR.懏c@?[~u>5!տ!7G$:&'4@[~u 9 A :?!տ!7Y=&Dz HuoM A : oHuoM A :,8dVߝFI{ѻQVߝGkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhkCBoΏHuoMNhtzw-,d[_>9Cn 3רNC/;S@yOTݿg:ƽXxAvuN͂BǠ52vM$B`BGoZc!.>nNiF˼wR7d2(벼QKOȱۤ0VB+/VUw&-%긬k&s>Ӈuɯ܌8bceb}ǻ{Xp}ǵl?-uǷ#HMPvFm`V%k>hg}/kut:E> նʲt~PԦvd5gE.xeP)+CZTzul-ߌj~5k*|9O%t(] =aNF5^ٿR]);bQ=f4ai0o"ƾ_5h﹇wپİ5v4խE'=Y%*xVZV0bkN)7E{K[zo;- ql;[[TəNǞk!5[ST{ĭn1Uk¾dxef,\(X& foF?4f3LCFi4њfh?4f3@fFi4њfh?4f3@fFi4њfh?4f3@fFi4њfh?4f3@fFi4њfh?4f3@fFi4њfh?4f3@fFi4њfh?4f3@fFi4њfh?4f3@fFi4њfh?4f3@fFi4њfh?4f3@fFi4њfh?4f3@fFi4њfh?4f3@fFi4њfh?4f3@fFi4њfh?4f3@fFi4њfh?4f3@fFi4њfh?4f3@fFi4њfh?4f3@fFi4њfh?5> Zy^k"Cjh;P4VTZwYͭJ1 zf fz?rK g Q/_^ uSU2F:S33]08ՕYsHs((,;y0/&$phMQb!֢34f,q ʲ-cf4uNXkgҳsFhZ;}?iQnLuau3Z5,N;v}k4fȴg31Uuk+˅F6vhPZ{%Ѵ Ps_7<7LLӾzMF[U5 X8b=+IctyJJs-7ݦco߽,e%T[Ϝ<њ5tQ Ig1C֙jVR&fmLg'=d;RM#{5pwrKHuV6wњ:],ZԮ{R#nkK& Vܴc ¹慢ޮV\Plp>Wn KhqfIHō34њq4њ~h34f3L3Fh4њ~h34f3L3Fh4њ~h34f3L3Fh4њ~h34f3L3Fh4њ~h34f3L3Fh4њ~h34f3L3Fh4њ~h34f3L3Fh4њ~h34f3L3Fh4њ~h34f3L3Fh4њ~h34f3L3Fh4њ~h34f3L3Fh4њ~h34f3L3Fh4њ~h34f3L3Fh4њ~h34f3L3Fh4њ~h34f3L3Fh4њ~h34f3L3Fh4њ~h34f3L3Fh4њ~h34f3LW_*Y6'zǂSSB)3LYf+`5_=#{OTxP3Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?κ(Hti:h[=#{O=멢9o@?G zG󮦊?tE:fqI%MDxR4QET2[Be6cRF*é :iZ]Mm o2WiQT-fdB|c-vQ@EYV0x3O@ EPEPEPEPEPEPEPEPEPEPEPEPEW{8ثD:*H L&?s@QEQEQEQEQQ40͕cqeVvs1>Z PTwq_Zߑ(0(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((JWƒ«mfuE$Uqm$-)=n`v6_='nͧM=n {Rhw6H6fl.u5[0z⯮XOgs;Jk nm5KQ!K(0 fua^4,ܐ:*Ϫ; #x4@Rk+[bUE>cI{{{4:{Epi;;ꦫ94BZK<2GK_jc(H3*rI% %m2mͲJu5GIDk$dQt!hx,Rwjڎk-*I%(PUAsy[JZѷfm]$/vA/ 7g*NbcԓMif)nѓ.6n#ڣ$tJL9Ky)•U[H'_Lba^Ķ3Nm]d.}X_^A㛀PcSmYs^ @D\b]eDPqMt?B֥1}˒ 2ήlVsVvAm 0fgE7ٮ#O4BR}~CV!)ui/k۪+Cm `)ӤMo)ߎ*te/G~ϙ/-t0PˏziHc2yOoہve&첺jq^j`f}cQfOw8 RhmP@˥LWdFAkTr^b}uj=F( Oe£^uT+<|/zK{ꨢC ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ?*?)i) (P h'֣<, uIp*LQwa,u+ҵ"QbsFHF(!MF(bPEQF(Q1@bPEQF(Q1@bPEPEPEPҊcI3}<8*?>yE 2Me\i6&c֏Z<p%,M&qQEχz- $$2qχz-|?h@qҌGZ=}j?>yE ֣GZχz-|?hJ*?>yE (=$GZχz-|?hJ*?>yE (=$GZχz-|?hJ*?>yE (=$GZχz-|?hJ*?>yE (=$GZχz-|?hJ*?>yE (=$GZχz-|?hJ*?>yE (=$GZχz-|?hJ*?>yE (=$GZχz-|?hJ*?>yE (=$GZχz-|?hJ*?>yE (=$GZχz-|?hJ*?>yE (=$GZχz-|?hJ*?>yE (=$GZχz-|?hJ*?>yE (=$GZχz-|?hJ*?>yE (=$GZχz-|?hJ*?>yE (=$GZχz-|?hJ*?>yE (=$OVVRQE4pT54pSIJxRT6֩, S~YǢ~4a}QXia}Tx 2+/?GbUb _f _f _f _f _f _f _f _f _f _f"-Mhj=azQg<LKwg9un#&;UA@#=*Prfrb$)_jnP~N/_ʣT*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}*/濕T_*}YVGhn58PZoOEPSGCSG1?4%!~RZNJ )L?Ch{xn{{KYv*Xi"PmdETv{T6R_hC`321{e%vp;uW_C9aՇ?b?iqϥQIWRpnhoa^޸"ږ e49Fzj5,"T.N~e[ůI KwLS;;?W3G`q| mLޘ닛!S֫ɨ[Uot5kx xq W. vMt28V俀Rj_i6+HoQ\Kpd>-Z]L8\ 4bCgħ/<7ڌMZZ5̠dpYjfY TFOjծ5PD9cT m~q$._W70ycy݌SEqb0<>[ `ctޗzn o|UHu`HpR%φb, ;zogrW3}CfGҗ4vh74f3MfsFh٪Zzߟ_P1U/AK5ӓ#@ SCU;GTͺaߚ@KEP?[ʞTQEQEQEQEQEQEQEQEQEQEUDA((̳v+7JY {TɵdSIݾ:GQcUE.Y01?u7[Q/x/`h? oG}Vr?:GQcUE`f@oQG,91?u7[Q/x/`h? oG}Vr?:GQcU{MEQm/w 9eG}Q1ޢY0s3GQcTh? o귨__:(-Z1ĖI<GZvnݣoj- >EF:"lxdU_yhO,$˂4#)ꨙءsU?}A?ZY&ǝ I ӑR5*/ - >2*>7lr2:Pʎ0uSA?Z?} 2 I1p 1QxUoyhO-U]WtUv_AU?}A?ZQW mJ A- >њ- >њ- >њ- >њ- >[_S}娦j:ẵZ?m5U? 6~za4EP?[ʞTWSKm6h$EʓT;O}hV쏴 cܬ{ia}quЕu{ID}zu" 2ŹVtvcc?|guZqڸ1$υnC4Kl?SDQDB?֞}L2Kn"ҚomijD\w8AY{*PV!K`XԵ>|hv30?BΉ>ȒF UG‘xy<2r96=R]:Dl1;ϽzoGa-uHH*yU[NEB StX .(deYzU;cBE d}hk[V[opizՅ\\",T)elWK.M#F"C7R{u U7OUG4(pOF+u 4ԖB;>^կ@tX* ;kr!2qoջ-`|G;uͼ)fU@8Y1>WFB]ͻRۻnC Xj [\]{sGf??+7Iơ6)K*lj1&1gt#=:>|+'cۤ hHw8F[3F4as 1Zא]#4O 1\?|?U&mJ_BlEkZ< o#{Xk(#]˃c&?J6|=ɫ~(,mt$75n K;Bsjac3C>i~鹽 \=OjCrZKpFDj[n5Fw 0F{7q+;^>l`bY`Viyֱ7ht'CYl:7Ip^*qY׶.ӐZ7); G&r?wDA_?ҶA_?Ҷ ((((((((* 5fj_S/4ZW* _*(|(sߨQEDQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@PZoN:@ETPLDO%+IHbU/d?JoJ8xM>XZ.uj׈i4% .Sw}í{RqY6Kuoausr`w֌3:@8$v+GxH9%&}kEu'KSj2qQOq @N>Ʃaj̗I( zQǥTQnUK[̖,;b,qG Zpv5ɬhCw nU_HfqT纞͚X|MQӯCKaëEOqI3HbG4zQǥ&h/qI3@ ǥzRfG4zTwOE9@ߍYj/[~5e}mz/ӛSl?~(S?[ʞ#`xeD Ky N!"u&HTVxa#=M] %\9\pEA&g%]U~z`QlN6 EEr|QH!A*Ƈas1EcEC¯AQ/vLʃjjb!nbu&Xfhv3dW=v6*) cHۘg[E=s#ڦRZV\Wn=XKhF4Rge'qW袘Vn!CZUj2c[g=,ͩ+%֊nEIJy\TƳD8ʸ jӿG=J=;ɆfgRAj&yTӿG=J~Qؽ3n ҟk;du=J?RƧf@'VY@vP簪w(ӿOG{Y8#"N%W,{n5=J?Q tYTkO i!r&Tw(ӿKC{v+Xa UӿP^k6%m LEP5$&?k\,60Qpjz葜QE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (֠Vu-?շƀ'(JWƒ=ҩ}M\=Ҩv>9٭>٨jTP\]DPls] Q[OtzqS\FI N)t9#PsMgK"p Q4pA XEӗfjmN-t #|d֭#U4 }ͯ"AKiaY'Q-_+xi-2=y Fd+yFm[gyq6 LVvQGIzPvh.0s?$Pw`LvjƕoOgL1'i ٣~ fbOKq!t$Rz)mlyL0Uu hiڶ*d~u*,4n20Ec"$.3,ո .&fd"9Fr*j0mhm)Z_ ķ?hݷu sњC3MfsFh٣4њvh74f3Mf1WKd@%oƬz/[~5e}mz/ӛSl?~(S?[ʞ ( ( ( *?7$B|J*=|J*=|J*=|J*=|J*=|J*=|JFTqEa4|`xGox=E]y0It)Z #`"&*JB@=( L/Q<"E )0E]y0F?ѾOYy0Gox=E]y0F?ѾOYy0Gox=E]y0F?ѾOYy0GV^^&Qj@A,gy0RQE]0G%Yy0RQE]0G%Yy0RQE]0G%YJrƧ->,aEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPE5Pd|;@PT{F?T{F?T{F?T{F?T{F?T{F?T{F?T{F?T{F?T{F?S@t#:SxQE4pT54pSIJxRPX0{UOҖ= [_QߕjLFV~Tm7ZPV~Tm7ZPV~Tm7ZPV~Tm7ZPV~Tm7ZPV~Tm7ZPV~Tm7ZPV~Tm7ZPV~Tm7ZPV~Tm7ZPV~Tm7ZPV~Tm7ZPV~TR>aZZ=eoƬz/[~5e}mz/ӛSl?~(S?[ʞ B@'C+ŧ+’=)Iʄyܺo-m}W.Ɲ`R=OzDX ~R]ι$E/4X:Q~+$晔*FK\d% 9e({9^Zs ˹ UfUğG2:_9>^~Ju?DFXmdL8 ;k!7L, |Oa@݅WϺFTՊEX.ZZ*66iɬM̈́1PT0a(eo :mqyql4$Ic'a`Gր,QLޙ֔Qnw!tBh<N)>L0aګŠRzՖVCU-(j=XR' * $|I1!-3/D^^+`*?+J[j'QE!Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@^܅\K6 YɫpWR*\tu7=FY^ߣ\X?wOJ_8*]>rms.3nkvo~G,8~kcJ}GQp^F8H׫P+&YygXgkP`>E7#ǭ&7~)P}3F@@uu *hSAr=7qs}ZG9Ft:vi wg O"-њyNC)2h٢YGVh ʰ?C@Y"q'L<>PZoN:@袊*hjh"'ƒ1?J[OƐDiEPEPEPEPEPEPEPEPEPEPEPEPEPU*VqeoƬz/[~5e2}mz/ӛSl?~(S?[ʞ 1,VkI.ZҦ);TG1yMiN7[H0)f[- Y.0+9Qcay.#pXW չ>T6<9V8,ަK\ԚAmk9~Fg+xE[?ʹC~tq]&υ**HGkgky4pZnj[愺ƫ;df8!n9{Z-nlHӲ {{tsVP?(uO9.jp R2}iG$=CSXNdhByj J/X8jiҬ7<ҵՇF%4h֌=Q֏hwRϽ36MI#YM9EPcz̞"6|1H |ǃR/=8-U'b*8)Ϧ\_ ]&?SWS6EM nFWx1yw4\,bhf\ڋ;V͜3+tKn%Gܗ3`펝$[[(|ʹ\iگPFd(zmҾᵉ˳OR3Y-A. SkY"Di{Ux5s^m#Bz:D?JgMAeq(OU_U{"/ӧ\ZWLj0v3^+,wtzlM/L]׋650؇&m⳴YXE+c2@!'- %V=~ )㰹sp>y;f.=wwjz 6Lwz0Gټ8b1jt32O7N"?CGk'NB5Df%Sޭ7֟=s4oVm3ROj|{зA[ZR'X4u;] {cCmQiW[y{L3K\cDcl5-ޞ7$pIը)$WE4h]G$0?Z̎k%k\[CU}f B;>- ~bCZ5FC{|DıF00>?_5@(5FC{|淆wMCR}f=h_f=h٨xEW٨x6j/,QUj/捚 U}f=hðHj;E@Dmf \̦1c5lGS$e @ tDT[[Cgm%袘!d:n4l?_NJ=jC{|ѳP4bP4l?_X?_5@(5FC{|*C{|ѳP4iNj%4ƊEǴ91D#NEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPd8W4]7Wl&[JU{5ŏh 4FB|5@}٨x6j/*i4b)Ӫ5FC{|*C{|ѳP4bP4l?_XwMʟCL٨x6j/,QUj/捚 U}f=h_f=h٨xEW٨x6j/,RP4l?_:Kewd[ex㠫4xAi4=Q@M MDxR4!Kc~?蟍1(((((((((((((*{Y? mՖUoƬzWoMEs}mz/Q@o*zSE$JpƧа)t?/SdGfڀ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (֠Vu-?շƀ'(JWƒ?t)l?COҖ="Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Vq5Z=€!?괿mՖ@SNoME@X(OPo*z+f |C4R)Ϋj6ic-/dwQZ@Cxm[kxOf|-c]gb޺*c3 22w |:~k:02~a'I&q"61nD eyįћvk_X$ɨ'"Hz6cSk>oē"ƕrR[[R{̲Ds.RΉC0hsHmw՘,yTt_i"H*#JUVX2~Kj]r+8FؼsKMn@bod !6IbFO5op1۲b-;kv  QE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (֠Vu-?շƀ'(JWƒ?t)l?COҖ="Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Vq5Z=€!?괿mՖ@SNoME@X(OPo*z((uekxnazecfۭ9tP{+[ s#!bmJ[#wkfkU25zɲ0Um2S-[( ) ((((((((((((((((((((((((((((((((((((((((((((((((((((((((( Dnz*74f)sfu-?շƧj Om 袊*hjh"'ƒ1?J[OƐDiEPEPEPEPEPEPEPEPEPEPEPEPEPU*VqeoƬz/[~5e2}mz/ӛSl?~(S?[ʞ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (Iy|P&eojϖ_ʘt_y|U-?Zq*EGYQ t_y|U-?Zq*EGYQ t_y|U-?3uq@|QEV|Tyit_gO/G_ʀ+y|QEV|Tyit_gO/G_ʀ+y|QEV|Tyqq*FAȥZq*b+y|QEV|Tyit_gO/G_ʀ+y|QEV|Tyit_gO/G_ʀ+y|QEVJFJ(J`6p D P>t_y|U-?Zq*EGYQ t_y|U-?Zq*EGYQ t_y|U-?Zq* Rrd_T2NgAPZSbTeOnPIAu$m\zճ֐u-?շƧj Om 袊*hjh"'ƒ1?J[OƐDiEPEPEPEPEPEPEPEPEPEPEPEPEPU*VqeoƬz/[~5e2}mz/ӛSl?~(S?[ʞ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (l mY!sFi3FhsFi3FhsFi3FhsHK1hax1u"1]|cnˉ MqgX̛ۿާhrZ. ^ jBtYu]p}𗏯-.Ԝ7^,k"0eaE|g}nvӔװx<-n 4ffff{SOQſԝg곚dyqY2YR^Y* &~#^ nEq>+aIW(ƦonzQ!:o~5hj7yXP;\voG;VC@A<_9[Zi,Hw='t3I3@ 3I3@ 3I3@ 3I3W?btZR mjj̟@Cwu30X&q^AH87ľ;u;C8zֵHޗVΘuďSzʿZn}SI @ܷ71麙 /פoh ֙XL}5Ώk3}A3I3@ 3I3@ 3I3@ 3I3@?A>YoIH[7T栵[7T nTPVֿ֭RZJzxAi4=Q@M MDxR4!Kc~?蟍1(((((((((((((*{Y? mՖUoƬzWoMEs}mz/Q@o*zSEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP66*jN ~7Ty4u<њc$F4f$X,mC÷P m$ G3Zk1B2#ֽFMQ,i5|n{0Gڸ lImS]SIGnaE@CAO¿)T#9XMz88$F4fnuG3@nuG3@nQfMMޫO `U?~&hIh0$F4f$\ߏl_P]iR2`x7.llc c6-A(}+\=ޖArc=_km&ϱ}h`$eb^V-m߆ Ga/"Uc+Ih$F4f$F4f7R(<9L!píW Xi Oj`s't514nFi&7Ty4&7Ty4&&XI{,jXr@4cIxP_i~'ԣފ2g;n/d*B{_>;ϥHgsx?[@ЃhbK_˳L"fJiv ? ȷ=kIh$F4f$F4f$K(45W?|ՎK_}ENj _}ENikT-S5BZZJzKϽk]*H`:SxQE4pT54pSIJxR-z'H~RǢ~4X(((((((((((((?f_ǸxP2V[V?]6=iSH QE@?ܩO@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@#10HN7~U`x#4\?*O?*>i=V<~Tyq4m?mXiQ< m~UcˏyGOʀ+{ʏqU.?柕\?*O?*_ZqT\?*<~T_~T}{ʬyq4iPqQO?*<ˏy@i=Gm?ǗOʏ.?柕Wmm\?*<~T_~Tm~Tyq4ln@?}M\ԴOyԇvڟO?*@<iPqQO?*<ˏy@i=Gm?ǗOʏ.?柕Wmm\?*<~T_~T}{ʬyq4iPqR{ʧiQ< m~UcˏyGOʀ+{ʏqU.?柕\?*O?*>i=V<~Tyq4m?mXiQ< mu~Tyq4.M"lRt Zd㗈 R}{ʬiPqQO?*<ˏy@i=Gm?ǗOʏ.?柕Wmm\?*<~T_~T}{ʬyq4iPqR{ʧiQ< m~UcˏyGOʀ+{ʏqU.?柕\?*O?*>i=V<~Tyq4m?mXiQ< mmO<ˏy@ʺGoSWi`-%پ5پ45jy[=j޵l0j OmZ[hz( ?*?)i) COҖ=?t)l?b,QEQEQEQEQEQEQEQEQEQEQEQEQEUk U(-ޫKߍYn za4_( Tr(((((((((((((((((((((((((((<1I)7<L Ub*o}ڍϻUњ_j7>V v|n_j7>V v|n_j7>V v|n_j7>V v|nMϻQ_o4PT?E>pÂ}!y1=7<jK+H8fv|o4f*o}ڍϻU(ݨ/[*o}ڍϻU(ݨ/[*o}ڍϻU(ݨ/[*o}ڍϻU(ݨ/[4S|ouY8E:]n5 27+msz 7>NۈY"ݨ/[ v|n_j7>V v|n_j7>V v|n_j7>V v|n_j7>VT/ VEUIЎLf >ĵ[7T栵[7T昆B3T-@>tgTֿҭAi58PZoOEPSGCSG1?4%!~RǢ~4-z'LE( ( ( ( ( ( ( ( ( ( ( ( ( Vj{C/[~5ei-ށSl?~za4EP?[ʞTQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE6gp;H56AWcة_]H#th֚ `>6[ >PbG|VڄO1d=HF˶%2hg=jk.( +7b}itEK| ZaiE2KqzfVh~FmBiZ1cdgg0Z>].i`5.cNV?MzFs>\P#V&2CiLp"ӯR;6V%`Q\꺔`_!T*9Twql.csҊ紹n&9'cn6CN¸c?UeE,:Um*=j[2H`8s܈Kf>^{c7,%>lCy@U)A3%'7676$}}{Tqяzi}mz?Z`U[JV҇í2TI]wֲ7Oɺ$o޷UaKanSZ6Qg5Ƨgml2ˈsXť޴w30&C5t`|!Ijɿ&'Z$jr2+I#`Ą Jt⎁uњu&F&Dcp@ $8gOj>m/_[<޶@M?Gv-@ S"Ey*I5^[Mu`+I _,\>髷p[[_5FZtXDžnBhckDdPpN:qiӼbD8`U%SiVfلho]֎jCSsQ]D$">)☰8lv+NЎs%􏟼@ؿ5 QNpԣc54GoW 5̟ i"O1b} $_F?1R*{ fTn6X<.!B 3Mq-[@'|gkoNʚ5[8|>l.l^c&I؛z/=vf3$1NY ʳ)nWtoDcoo- :!]rE>pn$GeZߡ{YCױzOCv&TKo[*LpK $VX&?W5\3 ۇWP3q'o5 Kasẕl|C-$d۞^ckusk h}߽l6}RWt#% zh7Q_[dW$gE6dL=j} f3HQM6Ug\#$<2'QRV͵]BRzWY꒐ koAko0 TPVֿҭRZJzxAi4=Q@M MDxR4!Kc~?蟍1(((((((((((((*{Y? mՖUoƬzWoMEs}mz/Q@o*zSEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP;/%bP>w5t5cˆ]<ڢbsҷ/o+ܝU>KؼSXWVWRk{&ͽZKa3ϒI#]";fʾ9Z-8HeVި[Z$wfQrsT_N:mB/IQrh1- RYW%)P.蹔/_֖0%B179j*l lѓLFnm5ڼ۾b*++kq0@I G+^C9ƚ,m]!'֊},"sqp-%횎s14c}\B֮ͤYC.,J|dU֌b0t8KҝR;Zsu[n8E~p+Sq&2t͵֝l̨1jqvKyY nѸwW".R.y>Pn|2hϪin4pA5jkè^|cZ4t%PGG-zR"V$Uzɣ&(C_"a%pP֎fM1L縎#,:f40ƀ)ǮȨ85kZ](% }_{i5c !?~ ֘2ĺo rhqQ#P6՝֌b0Y5{}1'}vu-;P1Z٣& U"f~7S3Fi ~Xتz4]VD(OO* Uk6KAfijQTWo揷}TaU}oa_@~ih}X_FP_}>ijQTWo揷}TaU}oa_@~ih}X_FP[}nv@}*آluN}_Q0_ʀ*|OV_ʌ/W o4}|լ/W ?o5k u*OG?Zu*0_ʀ*|OV_ʌ/W o4}|լ/W ?o5k u*OG?Zu*0_ʀ*|OV_ʌ/W o4}|լ/W lw C'ff֨Zj޵lzU֐u-?շƧj Om 袊*hjh"'ƒ1?J[OƐDiEPEPEPEPEPEPEPEPEPEPEPEPEPU*VqeoƬz/[~5e2}mz/ӛSl?~(S?[ʞ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (.nVQU ʏ ;Ҁ/Q*z (Ǹ(({yhCASVhZ/0y72 e{pESZ~z( yl™ϡ|W+F2?}ε(#/ϽCϡRty:Ԣ2?}ε( >}G{>J(/ϽCϡRty:Ԣ2?}ε( >}G{>J(/ϽCϡRo9:qu$RZ[yqw>}Ri\uhP_{>ϽC֥e:}ZP_{>ϽC֥ϡuEe:}ZP_{>ϽC֥ϡuEe:<.J(vA]At9 %y8c20Mu.r_F_{>ϽC֥ty:Ԣ2?}ε( >}G{>J(/ϽCϡRty:Ԣ2?}ε( >}G{>J(/ϽCϡRty:Ԣ3 QУpjE{ͩK`Խ K_}ENj _}ENikT-S5BZZJzKϽk]*H`:SxQE4pT54pSIJxR-z'H~RǢ~4X(((((((((((((?f_ǸxP2V[V?]6=iSH QE@?ܩO@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Tu[?qf4hb3l>; *ğPYjy??Tr^Y]f,v1ϥ^1yi)!f_6Z[Pق-q}} ̞M~c4ZB+< d\"1cWtݽĊBñY7llTmʬ:A`]cp;z1PvɤAper8֮cx֞anT&B{w ZӏjΫo[,vZ &Xwn SO l[ ch~z֣jzQgfq~5=>ҧMկ@~lz闖vyCPƮU=[%q_T4=9qaV,%qL2 h T]6K^]>F Ҽ[KI.d[2D!#5cZ;&.ߪVx9V -5>ч[ԯdzՙ0^g?A Kލ)q Aolh{& kbdB7DX2`k0X3M pV5=B(0(((((((RSR KHdO롬OP 6d88geZO롪$ &йIy1RjȋF;PzT|>u+?y֛ đG8, [$մ頓P&r\l'tu(3*2[`O"EQEQEQEQEQEQEQERO{WiU/z@%پ5پ4kT-S5BZZJzKϽk]*H`:SxQE4pT54pSIJxR-z'H~RǢ~4X(((((((((((((?f_ǸxP2V[V?]6=iSH QE@?ܩO@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@A"H w>(DPFcVBm- ɵ]DD]F)P$)c W8Gm+b r:S`A)̐weIE Q (GA+*(aE-݉v.O㨠"8Q@ (ϕGF3O`VTkF B~5 n-^A*Z@%mC[&rOKam{?-?i=֘i5/>"m.~ҫhZuB{YhZuB{YhZuB{YhZuB{YhZuB{YhZuB{YhZuB{YhZuB{YhZuB{Gyuys\j!~zߴ#n^uiq7v_hl[iQeb9'/Z?-?EKoַ_h6@fAZ[B{h[B{h[B{h[B{h[B{h[B{h[B{h[B{h[B{h9)NƋZHdO롦:- x(I,;2ہ=Z}6$ GkZ$clO/Z?-?E[O3io OhZuYhZuB{YhZuB{YhZuB{YhZuB{YhZuB{YhZuB{YhZuB{YhZuB{G}RHwQjHbZ*sPZ*sLCZj ןzU֪^}_VZC֠Vu-?շƀ'(JWƒ?t)l?COҖ="Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Vq5Z=€!?괿mՖ@SNoME@X(OPo*z(((((((((((((((((((((((((((B I?柕E=B*/>P4Oʪo>P4Oʪo>P4Oʪo>P4Oʪo>P4Oʪo>P4Oʪo>P4Oʪo>P4Oʪo>P4Oʪo>P4Oʪo>PP-ĮҬ:P~d?*klU.\%ZOʍ4I(\%ZOʍ4I(\%ZOʍ4I(\%ZOʍ4I(\%ZOʍ4I(\%ZOʍ4I(\%ZOʍ4I(\%ZOʍ4I(\%ZOʍ4I(\%ZOʍ4I(\%[NuM@BPQI?柕A-G؈daS>7I@G7I@G7I@G7I@G7I@G7I@G7I@G7I@G7I@G7I@G7I@aT)j {1*T5=%پ5پ45jy[=j޵l0j OmZ[hz( ?*?)i) COҖ=?t)l?b,QEQEQEQEQEQEQEQEQEQEQEQEQEUk U(-ޫKߍYn za4_( Tr((((((((((((((((((((Ήw'a&rڨB"057]#Owl($هtlU-uKLvuz#-QgTn6Zzw6(6nύьOCXY4-%x2\meSGkCNŸUXﵛ_.Bm0ɟZvAE`jWk ea-Jb MZ:`DnoS0)yzÖO+ JxYX~k&V(PEPEPEPEP@]N0STSR KHe}(mf*_j\XaNgu6P&t < ПjUm6~T_SM REsښ` iQE(((((((x[0,5/z>-??ʥH[7T栵[7T jfZ+^}_VZy[=i Z[jq֠V( b"i)_JCDi?J[OƘQEQEQEQEQEQEQEQEQEQEQEQEQEZ=¬k _jwV[+ަYYi96n~yU?[ʞTQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEUbl> =^VPݴM6ݜ:9ֳֿŹMs2F;tt)1g}Eb M`/V;%HnYիMK3St/eZis F{#b{ޯd`zޠ, 4[x-,AlYO&կ,d<Puzqc*tϵi_0gHq3Nಅ.3[K2mbtjUgCeeP_<SǡZs$D{\*K"a3I,2t4ޮZ+{5-F9R3Ӗ% krEY#iMX2`,  gԤmU|f[h |dlZtVU{^ț9# 5,иtMi K]zoOwzҴuYk`ΫTno+y[۬r[s"MnpNE7m7hijWLV#ʨ'Mb|+d` }kYQHaEPEPEPEPEPEPEP+xԵ~ZjZWMWS[%DmԜ$cuTե=sZ-n\{SH`t!FiR6Kkd]'қKC_ZKK i9[;sow`qsV,Iuknu-&@41BKc$g!?E|ؖmiuH%7);k-8V91C]lsGq.֒b長mSA7iu@o?/nї(NvԴuKl4"Nc/$IX4=E]_CR6ʠieB(GZ.<9m< X>km *QEQEQEQEJ Cj))2 'TѨ=;,ͳ)@?O롨/t {Qqq o"|@,vGw ᥊L<>#$4i:`r1ZvvY[!\)OSҭa)h[9 s֛!"$I,,P;^VU,DIeeF_kS ) (((((((}R^K_}ENj _}ENi֨Zj޵lzU֐u-?շƧj Om 袊*hjh"'ƒ1?J[OƐDiEPEPEPEPEPEPEPEPEPEPEPEPEPU*VqeoƬz/[~5e2}e&za4cNUp~(~=@?ܩ(((((((((((((((((((((((((((,)ڮ8'\]OΣeGu = 7?*Mo쟝k?g Q{0&]OΏd=Oʀ&]OΏd=Oʀ&]OΏd=Oʀ&]OΏd=Oʀ&]OΏd=Oʀ&]OΏd=Oʀ&]OΏd=Oʀ&]OΏd=Oʀ&]OΏd=Oʀ$q{X$t骪PS hos8r W>m=zo~TMo쟝k?g Q{Mo쟝k?g Q{Mo쟝k?g Q{Mo쟝k?g Q{Mo쟝k?g Q{Mo쟝k?g Q{Mo쟝k?g Q{Mo쟝k?g Q{Mo쟝k?g Q{Mo쟝k?g Q{GO~%CE+ƬR*PRVDcۆ{ϵd77}E7?*Mo쟝k?g Q{0&]OΏd=Oʀ&]OΏd=Oʀ&]OΏd=Oʀ&]OΏd=Oʀ&]OΏd=Oʀ&]OΏd=Oʀ&]OΏd=Oʀ&]OΏd=Oʀ&]OΏd=Oʀ#E;3Ҭw" "ClQSlQSb TPVֿҭRZJzxAi4=Q@M MDxR4!Kc~?蟍1(((((((((((((*{Y? mՖUoƬzWoMEs}mz/Q@o*zSEPEPEPEPEPEPEPECI&(VavQÎ} WKUiQYޝs8)z\TZ) ) EgǭBҬ*`U-g+LE*ޱag/fqx֐*Aq(Bwޢլoe1[K̸"FXʞRW8pʓIJۑCEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPZ*sPZ*sLCZj ןzU֪^}_VZC֠Vu-?շƀ'(JWƒ?t)l?COҖ="Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Vq5Z=€!?괿mՖ@SNoME@X(OPo*z(((((((+#^[qZͤ7MbmZ:tq QK,bT39)KZ)FQ6piEqoiiCu)UD V#5( f*· jhFT&1;5, gfM&^`vg(kH^.>r.{b髩B{I r?6+c@kkwy8jugy1̑ջkhaDQ[ܚ(0((((s8:+v`Lm0Z32K(mZwPғ{H@Eik>L<+hH.g3f 9RI[<gV ә FŸ¬iֶvo ~$z>Zd-"Dނl.j2, )̸\11{Ge`d^+8!iN_= SOv$f?J^^3n&w|jޫG鎈p1Z2,w1Ri %9LSǞȝXEJgOh5ދgw1F vQ.^=(C ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (lQSlQSb Sg#,M3!2T"+}_VZy[=i Z[jq֠V( b"i)_JCDi?J[OƘQEQEQEQEQEQEQEQEQEQEQEQEQEZ=¬k _jwV[+ަǢM96=ib(~=@?ܩ(v)s?hJ*<<Z3?hJ*<<ZT>GFE>)ytyEJ*?>违|?:χz/G_΀$= (tyEJ*?>违|?:χz/G_΀$= (tyEJ*?>违9YXeH#ڀEe^XQQET~|?:<t%_Ώ>违IEG=χz/@QQET~|?:<t%_Ώ>违IEG=χz/@QQET~|?:rʐG:(ccRfTyxfTyxfTyxfTyxfTyxfTyxfTyxfTyxfTyxfTyxfS@@ɵj3?hJ*<<Z3?hJ*<<Z3?hJ*<<Z3?hJ*<<Z3?hJ*<<Z3?hJ*<<Z3?hJ*<<Z3?hJ*<<Z3?hJ*<<Z3?hJ*<<Z38?IE18:}Tm&b3ր$3ր$3ր$3ր$3ր$3ր$3ր$3ր$3ր$3ր$3ր$n$j}%پqX[Jb( iff_L6#Ωa?.7[|gd\Gsڸ62/|G^({d6 &At8~w;v[kZ@Ai58PZo'(JWƒ?t)l?COҖ="Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Vq5Z=€!?괿mՖ@SNoME@X(OPo*z)BzS9Oh{pc:I(̒8T^I&o+#x{AI ~#ƅk)m^Gjm3tkn4{x&u_HRIkhCp$ 27J.Gg5_wGi?+}Ny4)%P W*!f!Gm{.&(/ mcQvWr[B?@!m?琣6y ETʹBO*cYK>DcN}hmf!Vc,ѩur=)S6y ?m?琦5yT>уƯ@?m?琣6y ETʹBO*S6y ?m?琫tPOO(ʹB@?m?琨|iu!;%h+x*'lg}jzԆKi<ٶS5ZMmQwʦMnbi<ٶT-,5o=:q I.΍mf!M5[M9AsdM;VA;\m??6y ?m?琪^"ӭg0H8%]bQ;pi<ٶU;y.խCUӔ9n'f!Gm;VA;GUqU^zui@m?琣6y xaYap(?m?琣6y ETʹBO*S6y [;d{VR]k](Z(=M2\ UmIwu iت~O3Um==MK ko:c5ŜrHx,ÚlQS`q8 i k_tCU7V]BaNH4ݪ*Rh;8@`WZy[=i Z[jq֠V( b"i)_JCDi?J[OƘQEQEQEQEQEQEQEQEQEQEQEQEQEZ=¬k _jwV[+ަǢM96=ib(~=@?ܩ?OMIQ\Ǵxb4쎢,ʾyXvАmbA^+m']pəv1 ڷ׳AGqP iֱ.g\i֋!CWu"4"vd+^r->hic^NdP+?C@}/\JUs]){*z? ޺{VaUͩ < =-,5ӧp+_ W2o.b9 Vsqaays^?ֳΝg*. hKdEE@/4mR %MV2:GYFvs–o"֛.x\ܾc稣Fo^Qmh[}SZ֗f\pSUFc9Uߞ-wHcZ?5DIn;f_vK1iNqv=ݹ %y̨3g"z%nx[p{4sI|MqZߝ=vImXe}8tqnH⫡>C ( ( {Wj~ZKޫuXUzV 3Fj0F1:d_jFNf2 I63S74wLœA3Jhc3c\ƿya$\0kBa1c>-kVYEo jfE Ā.ђ~hx{vh+8?j_J6$ꫴjΨ5D(_z'u+Ң?g$IX|9m CdI$.Sҝ<2qZ7R1t hL2CĆѣ &d2[G\jWQcS֒yxt'KI-?ȫW̐9[5X>dcH]ؚѪAERQEU+ֿҮ+ֿҀ%=MC"ۏ^)mYN=y:* j[<h`3e]b_ݸc }ȫI|,/Cy{*ti}=Ž[$Ϋ˟ZlHf~-Aat-)cYyKtF+zIee~ug J&8jtGa Kca40"í`ߴL57j 4E6C;In6-sT>@?N/ߵKL2P)֥mF]&_08G# *k]%mHem`(<giyi""vcM*ڐīu53CksmnC>ۊ5/` qɬƋe=~VH\ZjHu$Q$Q$2dhdcr;&⣮ѓ@OƋZtҬq Ev-P_LJIFw)(vϥ,ÎYsK@Ծo\^ 3.+KӨVnP NxSMa u.-REy!CI 1Ƨiusp$[jjAYG%), N `4`t>iSup.eأN)%ҦM>;E(w'cr:n# =M7 ԧ.jR7#ެ=餰H}-X7ُ|U(륏KR69OЃC[ROOsiu-F;R[IueԖ˹d=Z[}Fk}E|oX6Ԛft5i\.N ֢m9!Fm܅P$7rhӑIɯ.;nLFVeӥFR2[7MC&vڒt|?E-5e3"15jk/#4i<__Zwo,+ڠS\{t[>Rd1~z<Ы=IY|~V5ޣͪ5BvR}cw$&ZܿjkKe%9+ Ю_嬪yKxzֶk?M[Qs"Lњu&hG&i&:MӬ%G\ ηйKs׭l,4  Z꒡?~HbZ*sPZ*sLCZj ןzU֪^}_VZC֠Vu-?շƀ'(JWƒ?t)l?COҖ="Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Vq5Z=€!?괿mՖ@SNoME@X(OPo*z*+R2R<d?Zujq,z9K`R$lbͽDcl򖷺(v29C 7JO6}T*tC946}[ ݤ@2Lz+?ʺv*b4(*<hBʺv4(*<hBʺv4(*<hBʺv4(*<hBʺv4*&];NdO2CWP=/|=N3C/YUyW_EgW_];@Vu?UEgW_];@Vu?UEgW_];@Vu?UEgW_];@Vu?UTn-@4*tP2Y<14QE!??ASUApgQaܗ 5[ܗܗ 95[ܗܗ 95[ܗܗ 95[ܗܗ 95[ܗܗ 95[ܗܗ 95[ܗܗ 95[ܗܗ 9Uo>oemmrҮU~o?-/@\O;ncOZ@.ߩ^w|8 ܗ 95[ܗܗ 95[ܗܗ 95[ܗܗ 95[ܗܗ 95[ܗܗ 95[ܗܗ 95[ܗܗ 95[ܗܗ 9 2`=Aoo6O%pK|oS5`u$h{U`s/t595T3ᔴ,wdAB4g4f}4}4g4f}4}4g4f}4}4g4f}4}4g4f}4}4g4f}4}4g4f}4}4g4f}4}4g4Uor_揷r_mܢ|۰_r_ K|gީžJTIAM\[7T栵[7T昆B3T-@>tgTֿҭAi58PZoOEPSGCSG1?4%!~RǢ~4-z'LE( ( ( ( ( ( ( ( ( ( ( ( ( Vj{C/[~5ei-ށSl?~za4EP?[ʞTQEQQy &qӶ5S65s<L_΄~AaP袘"rրEC꿝jT?j꿝MEC꿝jT?j꿝MEC꿝jT?j꿝MEC꿝jT?j꿝MEC꿝jT?jԈ*juS]Y*[UU_΀&U_Ώ[Uj*[UU_΀&U_Ώ[Uj*[UU_΀&U_Ώ[Uj*[UU_΀&U_Ώ[Uj*[UU_΀&U_ΤGGF{P(1@,z 1s<65s<65s<65s<65s<65s<65s<65s<65s<65s<65_΀EFn%Hè(5ɆsԻntњf۟濝ntњf۟濝ntњf۟濝ntњf۟濝ntњf۟濝ntњf۟濝ntњf۟濝ntњf۟濝ntњf۟濝n~tњf۟濝:}p@Cb.s<64s<65s<65s<65s<65s<65s<65s<65s<65s<65s<Q ;Tff֨Zj޵lzU֐u-?շƧj Om 袊*hjh"'ƒ1?J[OƐDiEPEPEPEPEPEPEPEPEPEPEPEPEPU*VqeoƬz/[~5e}mz/ӛSl?~QE@?ܩO@2c\S9Oh{pc4M,di/gN#i. VҘB: #zF;{n+MFmG78]麖a7܌8jDs\<@ގv+Z.O\ITa[yyB_K?Ґkkkk#syv2]`y^Ҏ _}xgK9&5kQ@3r547- _jasEߕVS˴i?Z>kжXK7Fl m#\B A nҺ(֫"EQEQEQEQEQEJ?-??ʮ+x*Wޫu!xe©=)txQ 7nko6]Lr=LҒ46HЊKl֫xjeҟvHYS9;t2xwlTzeŖC7Kl.o35[ JߕVaq&D@ߍdOij-m 4mqWSw Uc}hS3MK͜UNm6,Yip0Z}Q%U(H#-匞$v'@ * ,%f`?VPѴY]Oq ) (*_Wj_@SE![U?7ݹN Y7c@-$OK"i:`F@Ӑ=FkHuG|ǮjiWKcc ) w=tiTM^n֗Y!Uk+-df-yn-R'r )7"+t4oD7$psnKŊ2z$cWTĉ3Fj<њ@I3Q5hI3Q5hIdщh2ʞi3Q\\ xLvQ@m=ekp$@skc5짙b)ݭp(+m5'zMMސ.ߪ#?g?}^[Xs)>S;ѢYD!U4PqT#ӮJ$P.1}s/'Sw BT mDY5~Qԥ1$Oi3f'67^̈́pivq7 <>cK- gvv#GnU1"Lњ4ffy4&hG3@fy4&hG3@fnhU84.+MVlntg!PZoN:@ETPLDO%+IHb~?蟍!Kcb(((((((((((((_ǸxU@o4 x1E2eXPxE7~[MHHŚ1(ZQq75U=GZ8"%LP@IEP?[ʞTTW?/ 2# @bpTVڅAuȇtb<=(ߨ:㮾iZ0{ft_iz\f_>Aw" ~h+ހ/Ygd<'LFyOʏ?~TEg쟕hQYgd<'@V?*>?PyOʏ?~TEg쟕hQYgd<'@V?*>?PR9$g?*|Pػ9WV*-6h^^?QyOʘ +?쟕gdB<'G?*Т?QyOʀ4(?~T} +?쟕gdB<'G?*Т?QyOʀ4(?~T} |sqj|>?Sb!nn)hC+Ӯ~&ʲCpgnVu?oʀ,Uܟ7Gۣy*EVt7Gۣy*EVt7Gۣy*EVt7Gۣy*EVt7Gۣy*EVt7Gۣy*EVt7Gۣy*EVt7Gۣy*Fjۣy*>PaUjۣy*>ޟ?@\O $._[ _g[N(/u'}[ϼߕn}[ϼߕn}[ϼߕn}[ϼߕn}[ϼߕn}[ϼߕn}[ϼߕn}[ϼߕn}!y?ϼߕPZ;IsVUt7G}IOLapՁ֪$Jn%IU`s/t595W?^9 oO 4Uo'Qoʀ,UQoʀ,UQoʀ,UQoʀ,UQoʀ,UQoʀ,UQoʀ,UQoʀ,њoʏG>~TEco "bF*jۣy*>ޟ?@]jzq3H)W(-9-9!PL PkϽk]*U/>tg!PZoN:@ETPLDO%+IHb~?蟍!L-Z@@T?jzjz0&ThThj*A=VA=V&ThThj*A=VA=V&ThThj*A=VA=V&ThThj*A=VA=V&ThThj*A=VA=V&ThThj*A=VA=V&?A=V)! 8ٿj;G75-l2}Sv0  [ņvf=I4C}i$?$ڠP@@?ܩO@RPQ+I!>R杲(S6\%.}E3eϢR#%XaQ@QE2Ice }_Qo(_Qo(_Qo(_Qo(_Qo(_Qo(_Qo(_Qo(_Qo(_TȒ.Q(I*F2Um=Em,QUm=Em,QUm=Em,QUm=Em,QUm=Em,QUm=Em,QUm=Em,QUm=Em,QUm=Em,QUm=EK(0"EPFOFTQj]>@'֙(sZ2}i.}>@'֙(sZ2}i.}>@'֙(sZ2}i.}>@'֙(sZ2}i.}>@'֙(sZ2}i.}>@I b6RPFOFwEʗeϢP}hlJ6\%?'֌Zf˟DeϢP}hlJ6\%?'֌Zf˟DeϢP}hlJ6\%?'֌Zf˟DeϢP}hlJ6\%?'֌Zf˟DeϢP}hlJ6\%?'֌Zf˟De)0èFOF]r{—eעP}hlJ6\%?'֌Zf˟DeϢP}hlJ6\%?'֌Zf˟DeϢP}hlJ6\%?'֌Zf˟DeϢP}hlJ6\%?'֌Zf˟DeϢP}hlJ6\%?'֌Zf˟DeעPb6(-9-9!PL PkϽk]*U/>tg!PZoN:@ETPLDO%+IHaLkSyQ5kSyQ5kSyQ5kSyQ5kSyQ5kSyQ5kSyQ5kSyQ5kSyQ5kSyQ5kSyQ5kSyQ5kSyQ5kSyQ59>)TQ@/@Җ((OPo*z)q j}G?{OonJK- i$XQIFOEx?Mjc 4v4X%)qV=WE9Ow~,ຒݼ&Z yѢaڀ>]DG|湯~ }cHgm'>hJg\37lmvu_K?Ґ_d)QK⟕Ao,I f+=JK*kxG-~Ujv`o ucYīg]ߖc?*>oo$UK"MZԵx (IaP[%Oʪi7RA088hⲸT5P-~T}xIgw eO}*z,!%Oʏ[j([%Oʦ!%OʪZD ;VR]k](S֊SE!o`*T}yLEYڬjWGXT6sΰi=1p5Fk6W_%b^:wy-o-13Q ' ,k\c$S,AеS4f5m02"?Obs$2 3P]%V1ZOU]oh(w4fjbr7T~  򁐲 (3O4f/5m0G 2/Xq@5KO#V+r*(sFiKT6xdm9_x5twis/]_Ak$QHْCQր-fR$ET-D@YTZl u9shcsq5VSh[xXj㱡3Y6y) W&3AB64Fk"](m+C U?RpX'^P ٣5WPO{S5"70#G^(o4f_0X._*jf~̞Tʽc`Ѹ4u 6]2!PhT茄n~"4fU5 F+R3}Aɠ 5cw0CVQ EԐEas7xmfAʃdgèv ֭{ a#lSSffPL PkϽk]*U/>tg!PZoN:@ETPLDO%+IHc]!cڈXyQǻqOȿJb!?~t}:fbBvz'?_GkoÖ2M1tc\=p8μ1!K]Y8_zuu ah/GkΌ?cΏSf?cΏSf?cΏSf?cΏSf1 D[/橧}O-QHd?[ʞTTw'_M<_+ʾyl\g;澆Şx m3X{Ȑ&;7-?yWQF%YcX^ĚՔ\0H~ 7S5i`̞Xڪ;]]yL?r4'4>u<+?C@}/\JUs]) ̴ldϧCqNYcFJoOڋ~UbYZ>;G:B#cƫijqՠA<}*LOm&-Z3o4֫ |˝ئv690bR{v-5cjVq[!%cɪa)ג2b-{4Š-2[V=Am.K; .j[dgqPyKo *5=׉De#"8ԿWm:XUROj ?M7oԥFTt 'Ylzϸ!q5[[gu)g_I-ghXgptm4ȝع]XDeA[! 89ޫ=>F̍܎MdmW/9w~Itc!G/A[ؙ@FB9+Kd]]sAMnky%}2qJfOb۬4"aI sU5˶4ۛ8e#|֖nz|6fpN*guwghX_w{l,˥Tvx0y)aoyO+?6޸.+$1ȧ;0:XEswPݰ)j|zg8uqigų7fHV4 &jQ֡ L:̟lջ?롨 I.,F@=RL-]YB 1)8#5!svm~w#LVKx[e>+}P`TG HPjDD[^5$">Ϊ:2'HzMynq(m%b4I ^k+PԮ,Keݻ=Y94aqʁz[FT_D#OxШhwG.v֖eAy ݈5%ؼ7 ]~zWVv_xy4@M^8e*yXSƛ4F'9/*QLֻPu\ƙn=ٺCk77l^V|f=im,`@ԗY{3tL48(sLC Q6 ET>DzVE3ֽkl-Jd?ZSKiqe3IjRFL_qbO-a.M D$I}(98*,mv40gZfF wVk A4 ZOc+d6@=(;oJ!koAko1 jfZ+^}_VZy[=i Z[jq֠V( b"i)_JC!W* {U~E b\gm|:"͂GvIxu!;=(𷃵=KڔJH |w fT<0s\-KOxw_@F{o{52ȯVnM!O'ז|3_}B@JGXj Z#؉8ݓnԞ i*M_Ga:R8=mphnvgejK㽸HXq!ަ;M`jd[\D|}{Fr\v 4ԞsZ4VMCwO j>v%+q@L}Oo,HqXrtHة\kA֩[}롫jyN׬9~ ?SOҙ$_}⋃#z cwd0=r?dK,ʔS[Vm>Nب=q@-=qҁTaz/O!% )fg"GvjX>Xa2zqJ[ t-.]4\޲e ҴluY2F\;ji02/~QI+A,BИcOtV:}5TMviӼօʶ[XKE,77 8.xZӁy?#_ qĈO֮si4'q*tmQ8j=@w3IZ^\>Ȝy늿$ M7iu{yt_"2~V9֧4+jlzwajM;CɿcXퟸ*Wl-ڡ8?ߑJ&};V5V5wo* `~Z1GekqպO8Ȩ#h pZt_sOq"z(d?[ʞTTW?/ A#,˫h/ hn"Y ` fBڏ@Ý&y = N "dGL}` #ۊs`:sMkvI[tb|Ip] ]oU@~ѢoΏ +;~!:ѢoΏ +;~!:ѢoΏ +;~!:ѢoΏ +;~!:ѪWguJOGC?s~u$P,D꾝=KfT2[;FޫHe+;~!:b4h~F!:>o΀4h~F!:>o΀4h~F!:>o΀4h~F!:>o΀4jͲH|GC?s~u$P,D7Z4QE!럠$da֓>r*|>SP*|>SP*|>SP*|>SP*|>SP*|>SP*|>SP*|>SP3U5lo F;5`)cϔ%Jު@K7&]}t^$)[>SQ)[>SQ)[>SQ)[>SQ)[>SQ)[>SQ)[>SQ)[>SQ)[>SQ)h[3I5`U_7MG| %?2VZm#\L0TZL]̊d:揶7M@)j|>SP*|>SP*|>SP*|>SP*|>SP*|>SP*|>SP*|>SP3U5lo\SRjuʻ~Z*sPZ*sLCZj ןzU֪^}_VZC֠Vu-?շƀ'(JWƒ'V3/n񎦡K>[1kt(2mm'^s\iEJv?W-:ˠ#q^O?*>i=L2H[[D j9 =N95m?m 'x iQkF#"mm Q~hRsC7_m~T8U =.jlmZC frn?롦STq;zPl SnLoZzty)\&x&/~T}{ʀ9xLn_ϕyU\ޕm?mN0Q9)^:m~T*$q8`}*qQO?*`tP3( u>i=Gm?]!?Ӊ`A AO?*>i=@(€vR2D፛ՔP{ʏqPiC=(Q@o*zSEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPFOPFOPFOPFOPFOPFOPFOPFOPFOPFOPFOPEPFOPFOPFOPFOPFOPFOPFOPFOPFOPFOPFOPEPFOPFOPFOPFOPFOPFOPFOPFOPFOPFOPFOPEPZ*sPZ*sLCZj ןzU֪^}_VZC֠Vu-?շƀ'(JWƒšU[)u1D8fiQ/~.?柕\?*gJ/~9)68 I@!UoE-5SP<ˏyLGJOʏ.?柕3ϋߥ|_(\?*<~T>/~y{q4iS/~y{qf:NShS?[ʞ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (lQSlQSb TPVֿҭRZJzxAi4=Q@M MDxR4G3T‰$I_\_ALEKKMqwov_z7-^C7G _\}1LdpFrOڿy>jmՂ =Ӓ/q{-m_ʍR+P# @ڿs@ڿ6GF{cKw|K %Wր$I"**e';N*Th^Ԗ,ԧQb GT_XcvGVS Ws׿=]GiRHm@W"MφZ OjŝmA؟-Ezڿoi*%NڿW, MnbWjt~TsFhڿW(ڿW򨥹Gp֥hiQU"sV'}\}EKERr~=QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE%پ5پ45jy[=j޵l0j OmZ[hz( ?*?)i) W*y#70X]ǰ/OF=GJS=E1Yk]k;o6|=ym"@Bxzz:eK˿f3]H$1#C;@ &wFS&ܬMue3J|g=^I[{\7[ zrYIE\sYmƫ[DCi-о3\7aɉnP[jj cDf=Yo,B[!9*.bOվY=}دo^Ɨ0Ʉ*9>nT=>XiY76੫lav.Yݥ>+VmzX}o6Ԛ:rYK , b[?ƴڟtgJR4k(7WN37RӯWSFegf;\]5tg!PZoN:@ETPLDO%+IHe{SeK#t5Y^`3сw_-=*"};}TWioꏴϋTVivKit6=M;hc&oꏴ\ϋT`*,c9>q>-}Qo,f֫|[Xɠ;ϋT}|[ h~aU~] 1v-7ҧ! A35x?άU0Z9 h6kiawy5j3)ːq]nſ>s>-}P OȫZoû=:ԦsnFhc4hEH >AhE@Y3yjoꏴ\ϋTѓUq>-}Qo2EiM@U9xS [2'Mmv叭MEP?[ʞTQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEff昆B3T-@>tgTֿ֭Ai58PZoOEPSGCSG1?4%!Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@o*zSEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPZ*sPZSb*&RJ޵lzU֐u-?շƧj Om 袊*hjh"'ƒ0((((((((((((((((((OPo*z(iQN $PT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7GPT^zz7OGWS@ y7ETFW֠+-1qQ5G˷= r[a^Yqjz0$H'MHu-?շƧj Om 袊*hjh"'ƒ0((((((((((((((((((OPo*zdV&a 5H P\ǴJtS))(RQ@ E%RQ@ E%RQ@ E%RQ@ E%RQ@ E%RQ@ E%RQ@ E%RQ@ E%RQ@ E%RQ@ E%RQ@ E%RQ@ E%RQ@ E%RQ@ E%RQ@^ZXA^Nstv"nX-%*M`|R1^mߠ|J(hJ(hJ(hJ(hJ(hJ(hJ(hJ(hJ(hJ(hJ(hJ(hJ(hJ(hJ3@ UnG*:UBK 8~V}°qOG`{{Aq{ilq<4][\2IUF-üϖ) ֫a[Yl0ݐh.E2:}ahkEPd_zݠ`:Sxz( ?*?)i) ((((((((((((((((((S?[ʞ!Y>f#Z=%v?E0$RnD57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57PrLOev7qVQF ݁H8}JM'R% zWo ~S6b!EqZ.>% y?1>v+d;e]EdZM% 2ަwP57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P57P5]&jQI?](֧"^[YtrvP?7_ >$hj)ylöV7e鏔Ϸ5Cƚ7_^55^{cmn剟` /[&&; ;>#fר! ]1q4z{W^zj OmZ[h=Q@M MDxR4QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE@?ܩO@ i{xʜSD'Xҫ>y?Xҫ>y?0,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv,`QUy?gv%$&T 0AsOgF-)-U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? >`U;G? Avۥ}|S̒4ғ SȨum*?;@}BS,֡\*:Ԛgt-{UوU;G?]mx>=UdxAi4=Q@M MDxR4QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE@?ܩ[|_6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违>g_Ώ6/违> stream xXKo0 W< DJ [; ;  ?RӚȀn9E}Ǘb Z#6lo汩7><7ƴh[: kO?ce]|_{:7<ƹYFl7%G[U1/edHQz?~;}p3dĠ 6}iOܑg۔bjMO-b] GH\_|zGGH{!@^r&[O198Zqad2 W3 l2Wu>W98~綠 Ȧu}`AKX:W?yf|oAePo_Ͽ⣲Oٿj?*Kw/n_[۫ʷkϳ%Ծ`TK[˽~//]\ e2, @&K IM } l¸iJXϪTmb"HPU(N'mƼ̨UOEBke3W-rPE\djQ]GEX5R]-98 >-h3Dׁ66Yy[çfXa^oB;ּs_w^y#/q޽N ۽DGېTpzݶ߄P endstream endobj 240 0 obj << /Font << /F1 27 0 R /F2 98 0 R >> /XObject << /Image1 238 0 R >> >> endobj 237 0 obj << /Contents [ 239 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 240 0 R /Rotate 0 /Type /Page >> endobj 242 0 obj << /Filter /FlateDecode /Length 2270 >> stream x\#7i>CF8[`2Ñш|XMHU_* V9Sy)>xRǿ;/}|o? ߾1&tnpB o'S_R~#;=:<"УUo.o/}pƁ5'޻Qc.E)yd,=*cy_'C|u mlodn2.v-L%/JNuKu$xT^u~,ho _}=k'kLahM5㻄xLy[Ar pI(\+>tK4pb ʓa W9yuѝQ'mYEOqk/F2;TWj5_^ t)lاmuTET=GkT]. ' ?q&*c {8B9L˝y_h-Wd}ؠ3W$XM\̘?S]G |7h(Ynfk~0{?ڪjJMt{MTޟ>p򎶄i/@yۇ5msGE?kA3?Q h ]&m]X65PؗL}KPI//#Q.G@\\cl F ;,bB*).~WSHә%&řk<95-ꟐQ5"h[]>HzjEZN70Q_yQ_lyH:gP^z厰(WEITP?#Fb Pz10BPEe`Uatpxw=\ )抜L>_ b8հe#+]5lל _ӎ;ɸg.װpb+|[O ;IkJ"."8ؔ ŶA:L e7˶mM틤ɓ9k8J.P >'G^.Fb8Hj/ 58iãuupeG1s6*ˊm˰:Χv>) yol >t3 ExFD; D!3ҌRQBC_/:B*Oʫד!26Oh>f 3˄ HtX W :=LL6'Cd{cz}ٞjͩn6'*q?ʸɍy! YKnRsPhcGD>WbQ tpp Aܹª?^a6y;XV9 ȞPU_uK`_-VO1@{j=bF@4)qzز?km@"+[+n hB-^rmQՅdUk3s6`Hۏx31ΚS)ö5hZ`fO)9I M$+W٘TL|`Q T\p:7-*U9 :_sk[+m'6'i;l].W9(}G*ިAVat'cd~,5pipM,NlqB0*Z@}pR3Q>]F'{:O\DjW8\#!klR[Ť&EsT&l`t֝+8ϞD05 kΆz57TB*\.][0?g'igSl{HOz ӈ=Juwx;T<QJGupϝor~d80cK7/':ȟpkI_AB(/&'MeB LPsc?&BT ߨ?BKaU6U|9 endstream endobj 243 0 obj << /Font << /F1 27 0 R /F2 45 0 R >> >> endobj 241 0 obj << /Contents [ 242 0 R ] /CropBox [ 0.0 0.0 595.32001 841.92004 ] /MediaBox [ 0.0 0.0 595.32001 841.92004 ] /Parent 2 0 R /Resources 243 0 R /Rotate 0 /Type /Page >> endobj 10 0 obj << /Length 1025 >> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 50 beginbfchar <0003> <0020> <0004> <0041> <0011> <0042> <0012> <0043> <0018> <0044> <001C> <0045> <0026> <0046> <0027> <0047> <002C> <0048> <002F> <0049> <003A> <004A> <003E> <004C> <0044> <004D> <0045> <004E> <004B> <004F> <0057> <0050> <005A> <0052> <005E> <0053> <0064> <0054> <0068> <0055> <0073> <0056> <0074> <0057> <0079> <0058> <007A> <0059> <0102> <0061> <010F> <0062> <0110> <0063> <015A> <0068> <015D> <0069> <016C> <006B> <016F> <006C> <0175> <006D> <0190> <0073> <01C6> <0078> <01C7> <0079> <01CC> <007A> <0355> <002C> <0357> <003A> <0358> <002E> <0372> <002D> <0374> <2013> <037E> <0028> <037F> <0029> <03EC> <0030> <03ED> <0031> <03EE> <0032> <03EF> <0033> <03F0> <0034> <03F1> <0035> <03F4> <0038> endbfchar endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 9 0 obj [ 3 3 226 4 4 605 17 17 560 18 18 529 24 24 630 28 28 487 38 38 458 39 39 637 44 44 630 47 47 266 58 58 331 62 62 422 68 68 874 69 69 658 75 75 676 87 87 532 90 90 562 94 94 472 100 100 495 104 104 652 115 115 591 116 116 906 121 121 550 122 122 519 258 258 493 271 271 536 272 272 418 346 346 536 349 349 245 364 364 479 367 367 245 373 373 813 400 400 398 454 454 459 455 455 473 460 460 397 853 853 257 855 855 275 856 856 267 882 882 306 884 884 498 894 894 311 895 895 311 1004 1004 506 1005 1005 506 1006 1006 506 1007 1007 506 1008 1008 506 1009 1009 506 1012 1012 506 ] endobj 6 0 obj [ -906 -268 906 952 ] endobj 7 0 obj 906 endobj 18 0 obj << /Length 675 >> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 25 beginbfchar <0003> <0020> <0004> <0041> <0044> <004D> <005E> <0053> <0079> <0058> <0102> <0061> <0110> <0063> <011A> <0064> <011E> <0065> <0128> <0066> <015A> <0068> <015D> <0069> <016F> <006C> <0175> <006D> <0176> <006E> <017D> <006F> <0189> <0070> <018C> <0072> <0190> <0073> <019A> <0074> <01B5> <0075> <01C1> <0077> <01C7> <0079> <0372> <002D> <03ED> <0031> endbfchar endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 17 0 obj [ 3 3 226 4 4 605 68 68 874 94 94 465 121 121 550 258 258 527 272 272 411 282 282 527 286 286 491 296 296 316 346 346 527 349 349 245 367 367 245 373 373 803 374 374 527 381 381 527 393 393 527 396 396 352 400 400 394 410 410 346 437 437 527 449 449 745 455 455 470 882 882 306 1005 1005 506 ] endobj 14 0 obj [ -874 -268 874 952 ] endobj 15 0 obj 874 endobj 26 0 obj << /Length 1697 >> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 98 beginbfchar <0003> <0020> <0004> <0041> <000C> <00C5> <0011> <0042> <0012> <0043> <0018> <0044> <001C> <0045> <0026> <0046> <0027> <0047> <002C> <0048> <002F> <0049> <003A> <004A> <003C> <004B> <003E> <004C> <0044> <004D> <0045> <004E> <004B> <004F> <0057> <0050> <0059> <0051> <005A> <0052> <005E> <0053> <005F> <015A> <0064> <0054> <0068> <0055> <0073> <0056> <0074> <0057> <0079> <0058> <007A> <0059> <007F> <005A> <0102> <0061> <010F> <0062> <0110> <0063> <0115> <00E7> <011A> <0064> <011E> <0065> <0123> <00EB> <0128> <0066> <0150> <0067> <015A> <0068> <015D> <0069> <0169> <006A> <016C> <006B> <016F> <006C> <0175> <006D> <0176> <006E> <017D> <006F> <0189> <0070> <018B> <0071> <018C> <0072> <0190> <0073> <019A> <0074> <01B5> <0075> <01C0> <0076> <01C1> <0077> <01C6> <0078> <01C7> <0079> <01CC> <007A> <0277> <03B4> <0355> <002C> <0356> <003B> <0357> <003A> <0358> <002E> <0359> <2026> <035E> <201C> <035F> <201D> <036C> <002F> <0370> <005C> <0372> <002D> <0374> <2013> <037A> <005F> <037E> <0028> <037F> <0029> <0380> <005B> <0381> <005D> <038E> <002A> <0395> <007E> <0396> <0027> <0397> <0022> <0398> <0026> <039B> <0040> <03A0> <00AE> <03B7> <0023> <03EC> <0030> <03ED> <0031> <03EE> <0032> <03EF> <0033> <03F0> <0034> <03F1> <0035> <03F2> <0036> <03F3> <0037> <03F4> <0038> <03F5> <0039> <03F8> <00B2> <0439> <0025> <043D> <002B> <0441> <003D> <0445> <003E> <0457> <2192> endbfchar endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 25 0 obj [ 3 3 226 4 4 578 12 12 578 17 17 543 18 18 533 24 24 615 28 28 488 38 38 459 39 39 630 44 44 623 47 47 251 58 58 318 60 60 519 62 62 420 68 68 854 69 69 645 75 75 662 87 87 516 89 89 672 90 90 542 94 94 459 95 95 459 100 100 487 104 104 641 115 115 567 116 116 889 121 121 519 122 122 487 127 127 468 258 258 479 271 271 525 272 272 422 277 277 422 282 282 525 286 286 497 291 291 497 296 296 305 336 336 470 346 346 525 349 349 229 361 361 239 364 364 454 367 367 229 373 373 798 374 374 525 381 381 527 393 393 525 395 395 525 396 396 348 400 400 391 410 410 334 437 437 525 448 448 451 449 449 714 454 454 433 455 455 452 460 460 395 631 631 523 853 853 249 854 854 267 855 855 267 856 856 252 857 857 690 862 862 418 863 863 418 876 876 386 880 880 386 882 882 306 884 884 498 890 890 498 894 894 303 895 895 303 896 896 306 897 897 306 910 910 498 917 917 498 918 918 220 919 919 400 920 920 682 923 923 894 928 928 506 951 951 498 1004 1004 506 1005 1005 506 1006 1006 506 1007 1007 506 1008 1008 506 1009 1009 506 1010 1010 506 1011 1011 506 1012 1012 506 1013 1013 506 1016 1016 335 1081 1081 714 1085 1085 498 1089 1089 498 1093 1093 498 1111 1111 905 ] endobj 22 0 obj [ -905 -268 905 952 ] endobj 23 0 obj 905 endobj 44 0 obj << /Length 773 >> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 32 beginbfchar <0003> <0020> <000B> <0028> <000C> <0029> <000F> <002C> <0011> <002E> <0012> <002F> <0013> <0030> <0018> <0035> <001A> <0037> <0028> <0045> <0029> <0046> <002B> <0048> <0032> <004F> <0035> <0052> <0036> <0053> <0044> <0061> <0045> <0062> <0047> <0064> <0048> <0065> <004C> <0069> <004D> <006A> <0051> <006E> <0052> <006F> <0053> <0070> <0054> <0071> <0055> <0072> <0056> <0073> <0057> <0074> <0058> <0075> <005B> <0078> <0094> <2264> <0133> <03C6> endbfchar endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 43 0 obj [ 3 3 277 11 11 333 12 12 333 15 15 277 17 17 277 18 18 277 19 19 556 24 24 556 26 26 556 40 40 666 41 41 610 43 43 722 50 50 777 53 53 722 54 54 666 68 68 556 69 69 556 71 71 556 72 72 556 76 76 222 77 77 222 81 81 556 82 82 556 83 83 556 84 84 556 85 85 333 86 86 500 87 87 277 88 88 556 91 91 500 148 148 548 307 307 648 ] endobj 40 0 obj [ -777 -211 777 905 ] endobj 41 0 obj 777 endobj 61 0 obj << /Length 338 >> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 1 beginbfchar <0003> <0020> endbfchar endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 60 0 obj [ 3 3 277 ] endobj 57 0 obj [ -277 -211 277 905 ] endobj 58 0 obj 277 endobj 69 0 obj << /Length 352 >> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 2 beginbfchar <0087> <2022> <00A6> <2013> endbfchar endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 68 0 obj [ 135 135 406 166 166 500 ] endobj 65 0 obj [ -500 -250 500 1079 ] endobj 66 0 obj 500 endobj 89 0 obj << /Length 1719 >> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 99 beginbfchar <0004> <0041> <0005> <0042> <0009> <0046> <000A> <0047> <000C> <0049> <0010> <004D> <0011> <004E> <0013> <0050> <0014> <0051> <0015> <0052> <0016> <0053> <0017> <0054> <0018> <0055> <0019> <0056> <001A> <0057> <0083> <0061> <0084> <0062> <0085> <0063> <0086> <0064> <0087> <0065> <0088> <0066> <0089> <0067> <008B> <0069> <008E> <006C> <008F> <006D> <0090> <006E> <0091> <006F> <0092> <0070> <0094> <0072> <0095> <0073> <0096> <0074> <0098> <0076> <0099> <0077> <009A> <0078> <009B> <0079> <009C> <007A> <01E1> <002C> <01E3> <003A> <01E4> <002E> <01E5> <2026> <0201> <007C> <0202> <2013> <021C> <0391> <0229> <039E> <0230> <03A6> <023D> <03B1> <0241> <03B4> <0245> <03B8> <0249> <03BB> <024E> <03C0> <024F> <03C1> <0250> <03C3> <0254> <03C6> <0257> <03C8> <0372> <0030> <0373> <0031> <0374> <0032> <0375> <0033> <0376> <0034> <0377> <0035> <03A4> <2044> <03BE> <221A> <03C3> <2211> <0504> <03D5> <0522> <2032> <0526> <20D7> <0538> <211C> <055A> <2190> <055C> <2192> <059C> <21D2> <05D0> <2208> <05DB> <2217> <05DC> <2218> <05DF> <221D> <05E8> <2227> <05EC> <222B> <0604> <2243> <0628> <226B> <0684> <22C5> <06AD> <22EE> <06AE> <22EF> <06C1> <230A> <06C2> <230B> <0736> <074E> <0D45> <002B> <0D46> <2212> <0D4C> <003D> <0D4F> <003C> <0D50> <003E> <0E13> <23DF> <1004> <2A7D> <1005> <2A7E> <123A> <0028> <123B> <0029> <123C> <007B> <123D> <007D> <123E> <005B> <123F> <005D> endbfchar endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 88 0 obj [ 4 4 623 5 5 611 9 9 536 10 10 610 12 12 324 16 16 815 17 17 680 19 19 567 20 20 653 21 21 621 22 22 496 23 23 592 24 24 647 25 25 603 26 26 921 131 131 488 132 132 547 133 133 440 134 134 554 135 135 487 136 136 302 137 137 494 139 139 277 142 142 271 143 143 832 144 144 558 145 145 530 146 146 556 148 148 413 149 149 429 150 150 338 152 152 503 153 153 774 154 154 483 155 155 503 156 156 454 481 481 205 483 483 263 484 484 205 485 485 751 513 513 316 514 514 500 540 540 623 553 553 571 560 560 785 573 573 574 577 577 524 581 581 546 585 585 487 590 590 602 591 591 532 592 592 571 596 596 711 599 599 719 882 882 553 883 883 553 884 884 553 885 885 553 886 886 553 887 887 553 932 932 553 958 958 656 963 963 708 1284 1284 706 1314 1314 258 1318 1318 368 1336 1336 759 1370 1370 837 1372 1372 837 1436 1436 865 1488 1488 623 1499 1499 482 1500 1500 443 1503 1503 702 1512 1512 589 1516 1516 586 1540 1540 747 1576 1576 890 1668 1668 282 1709 1709 310 1710 1710 856 1729 1729 350 1730 1730 350 1846 1846 595 1870 1870 475 2868 2868 578 2869 2869 578 2870 2870 578 2871 2871 578 2872 2872 578 2873 2873 578 2876 2876 578 2878 2878 727 2879 2879 727 2880 2880 727 2885 2885 698 2886 2886 681 2891 2891 685 2893 2893 364 2894 2894 353 2897 2897 898 2898 2898 755 2900 2900 635 2901 2901 729 2902 2902 688 2903 2903 560 2904 2904 649 2906 2906 678 2907 2907 1023 2910 2910 598 2911 2911 552 2912 2912 617 2913 2913 500 2914 2914 625 2915 2915 552 2916 2916 353 2917 2917 558 2918 2918 621 2919 2919 324 2920 2920 312 2921 2921 587 2922 2922 316 2923 2923 926 2924 2924 628 2925 2925 598 2928 2928 471 2929 2929 488 2930 2930 391 2933 2933 869 2934 2934 547 2935 2935 575 2936 2936 516 2964 2964 591 2968 2968 614 2971 2971 572 2976 2976 675 2977 2977 598 2978 2978 648 2990 2990 748 3014 3014 879 3116 3116 594 3117 3117 594 3118 3118 594 3119 3119 594 3120 3120 594 3127 3127 747 3128 3128 747 3133 3133 756 3139 3139 739 3149 3149 785 3150 3150 737 3159 3159 601 3165 3165 607 3167 3167 360 3168 3168 349 3171 3171 995 3172 3172 681 3182 3182 596 3226 3226 706 3238 3238 762 3282 3282 691 3364 3364 430 3365 3365 566 3397 3397 747 3398 3398 747 3404 3404 747 3407 3407 749 3408 3408 749 3493 3493 742 3495 3495 750 3533 3533 1325 3603 3603 617 4100 4100 747 4101 4101 747 4593 4593 356 4666 4666 415 4667 4667 415 4668 4668 387 4669 4669 387 4670 4670 350 4671 4671 350 ] endobj 85 0 obj [ -1325 -222 1325 777 ] endobj 86 0 obj 1325 endobj 97 0 obj << /Length 1221 >> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 64 beginbfchar <0003> <0020> <0004> <0041> <0012> <0043> <0018> <0044> <001C> <0045> <0026> <0046> <0027> <0047> <002F> <0049> <003C> <004B> <0044> <004D> <004B> <004F> <0057> <0050> <005A> <0052> <005E> <0053> <0064> <0054> <0068> <0055> <0074> <0057> <0079> <0058> <0102> <0061> <010F> <0062> <0110> <0063> <011A> <0064> <011E> <0065> <0128> <0066> <0150> <0067> <015A> <0068> <015D> <0069> <0169> <006A> <016C> <006B> <016F> <006C> <0175> <006D> <0176> <006E> <017D> <006F> <0189> <0070> <018B> <0071> <018C> <0072> <0190> <0073> <019A> <0074> <01B5> <0075> <01C0> <0076> <01C1> <0077> <01C6> <0078> <01C7> <0079> <0355> <002C> <0357> <003A> <0358> <002E> <0372> <002D> <037E> <0028> <037F> <0029> <0382> <007B> <0383> <007D> <038E> <002A> <0396> <0027> <03EC> <0030> <03ED> <0031> <03EE> <0032> <03EF> <0033> <03F0> <0034> <03F1> <0035> <03F2> <0036> <03F3> <0037> <03F4> <0038> <03F5> <0039> <0441> <003D> endbfchar endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 96 0 obj [ 3 3 226 4 4 578 18 18 522 24 24 615 28 28 488 38 38 459 39 39 630 47 47 251 60 60 519 68 68 854 75 75 654 87 87 516 90 90 542 94 94 452 100 100 487 104 104 641 116 116 890 121 121 519 258 258 514 271 271 514 272 272 416 282 282 514 286 286 477 296 296 305 336 336 514 346 346 514 349 349 229 361 361 239 364 364 454 367 367 229 373 373 791 374 374 514 381 381 513 393 393 514 395 395 514 396 396 342 400 400 389 410 410 334 437 437 514 448 448 445 449 449 714 454 454 433 455 455 447 853 853 249 855 855 267 856 856 252 882 882 306 894 894 303 895 895 303 898 898 314 899 899 314 910 910 498 918 918 220 1004 1004 506 1005 1005 506 1006 1006 506 1007 1007 506 1008 1008 506 1009 1009 506 1010 1010 506 1011 1011 506 1012 1012 506 1013 1013 506 1089 1089 498 ] endobj 93 0 obj [ -890 -268 890 952 ] endobj 94 0 obj 890 endobj 108 0 obj << /Length 338 >> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 1 beginbfchar <0003> <0020> endbfchar endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 107 0 obj [ 3 3 277 ] endobj 104 0 obj [ -277 -211 277 905 ] endobj 105 0 obj 277 endobj 116 0 obj << /Length 549 >> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 16 beginbfchar <0003> <0020> <000B> <0028> <000C> <0029> <0011> <002E> <0014> <0031> <0015> <0032> <0017> <0034> <0018> <0035> <0019> <0036> <001A> <0037> <001C> <0039> <0045> <0062> <0048> <0065> <0052> <006F> <0054> <0071> <005B> <0078> endbfchar endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 115 0 obj [ 3 3 277 11 11 333 12 12 333 17 17 277 20 20 556 21 21 556 23 23 556 24 24 556 25 25 556 26 26 556 28 28 556 69 69 556 72 72 556 82 82 556 84 84 556 91 91 500 ] endobj 112 0 obj [ -556 -211 556 905 ] endobj 113 0 obj 556 endobj 154 0 obj << /Length 338 >> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 1 beginbfchar <0003> <0020> endbfchar endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 153 0 obj [ 3 3 600 ] endobj 150 0 obj [ -600 -300 600 832 ] endobj 151 0 obj 600 endobj 227 0 obj << /Length 352 >> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 2 beginbfchar <000D> <002A> <0131> <03C3> endbfchar endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 226 0 obj [ 13 13 500 305 305 539 ] endobj 223 0 obj [ -539 -216 539 891 ] endobj 224 0 obj 539 endobj 2 0 obj << /Count 47 /Kids [ 3 0 R 31 0 R 34 0 R 37 0 R 48 0 R 51 0 R 54 0 R 73 0 R 76 0 R 79 0 R 82 0 R 101 0 R 120 0 R 123 0 R 126 0 R 129 0 R 132 0 R 135 0 R 138 0 R 141 0 R 144 0 R 147 0 R 158 0 R 161 0 R 164 0 R 167 0 R 170 0 R 173 0 R 176 0 R 179 0 R 182 0 R 185 0 R 188 0 R 191 0 R 194 0 R 197 0 R 200 0 R 203 0 R 206 0 R 209 0 R 212 0 R 215 0 R 220 0 R 231 0 R 234 0 R 237 0 R 241 0 R ] /Type /Pages >> endobj 1 0 obj << /Pages 2 0 R /Type /Catalog >> endobj 244 0 obj << /Author (Mathijs.dumon) /CreationDate (D:20200107084239+01'00') /ModDate (D:20200107084239+01'00') /Producer (Microsoft: Print To PDF) /Title (Microsoft Word - PyXRD Manual.odt) >> endobj xref 0 245 0000000000 65535 f 0001688684 00000 n 0001688266 00000 n 0000615620 00000 n 0000000009 00000 n 0000000035 00000 n 0001673273 00000 n 0001673310 00000 n 0000000058 00000 n 0001672680 00000 n 0001671602 00000 n 0000207013 00000 n 0000207485 00000 n 0000207512 00000 n 0001674366 00000 n 0001674404 00000 n 0000207536 00000 n 0001674056 00000 n 0001673329 00000 n 0000325743 00000 n 0000326221 00000 n 0000326248 00000 n 0001677354 00000 n 0001677392 00000 n 0000326272 00000 n 0001676174 00000 n 0001674424 00000 n 0000563582 00000 n 0000564060 00000 n 0000614518 00000 n 0000615523 00000 n 0000618643 00000 n 0000615799 00000 n 0000618598 00000 n 0000619661 00000 n 0000618823 00000 n 0000619616 00000 n 0000708801 00000 n 0000619841 00000 n 0000619868 00000 n 0001678579 00000 n 0001678617 00000 n 0000619892 00000 n 0001678237 00000 n 0001677412 00000 n 0000706067 00000 n 0000706545 00000 n 0000708745 00000 n 0000711866 00000 n 0000708981 00000 n 0000711810 00000 n 0000713936 00000 n 0000712046 00000 n 0000713891 00000 n 0000806732 00000 n 0000714116 00000 n 0000714143 00000 n 0001679055 00000 n 0001679093 00000 n 0000714167 00000 n 0001679027 00000 n 0001678637 00000 n 0000780902 00000 n 0000781380 00000 n 0000781407 00000 n 0001679561 00000 n 0001679600 00000 n 0000781431 00000 n 0001679517 00000 n 0001679113 00000 n 0000802774 00000 n 0000803253 00000 n 0000806643 00000 n 0000810165 00000 n 0000806912 00000 n 0000810076 00000 n 0000811295 00000 n 0000810345 00000 n 0000811239 00000 n 0000814035 00000 n 0000811475 00000 n 0000813968 00000 n 0001135552 00000 n 0000814215 00000 n 0000814242 00000 n 0001683786 00000 n 0001683826 00000 n 0000814266 00000 n 0001681392 00000 n 0001679620 00000 n 0000979467 00000 n 0000979945 00000 n 0000979972 00000 n 0001685899 00000 n 0001685937 00000 n 0000979996 00000 n 0001685121 00000 n 0001683847 00000 n 0001130229 00000 n 0001130707 00000 n 0001135462 00000 n 0001216011 00000 n 0001135733 00000 n 0001135761 00000 n 0001686377 00000 n 0001686416 00000 n 0001135786 00000 n 0001686348 00000 n 0001685957 00000 n 0001147603 00000 n 0001148089 00000 n 0001148117 00000 n 0001687218 00000 n 0001687257 00000 n 0001148142 00000 n 0001687039 00000 n 0001686437 00000 n 0001211434 00000 n 0001211923 00000 n 0001215908 00000 n 0001219908 00000 n 0001216194 00000 n 0001219795 00000 n 0001224596 00000 n 0001220091 00000 n 0001224472 00000 n 0001228231 00000 n 0001224779 00000 n 0001228130 00000 n 0001232365 00000 n 0001228414 00000 n 0001232264 00000 n 0001235548 00000 n 0001232548 00000 n 0001235469 00000 n 0001240023 00000 n 0001235731 00000 n 0001239899 00000 n 0001244124 00000 n 0001240206 00000 n 0001244011 00000 n 0001249638 00000 n 0001244307 00000 n 0001249547 00000 n 0001251890 00000 n 0001249821 00000 n 0001251810 00000 n 0001265232 00000 n 0001252073 00000 n 0001252101 00000 n 0001687698 00000 n 0001687737 00000 n 0001252126 00000 n 0001687669 00000 n 0001687278 00000 n 0001260703 00000 n 0001261192 00000 n 0001265141 00000 n 0001269710 00000 n 0001265415 00000 n 0001269619 00000 n 0001272063 00000 n 0001269893 00000 n 0001271995 00000 n 0001276175 00000 n 0001272246 00000 n 0001276074 00000 n 0001279064 00000 n 0001276358 00000 n 0001278984 00000 n 0001283057 00000 n 0001279247 00000 n 0001282977 00000 n 0001286118 00000 n 0001283240 00000 n 0001286027 00000 n 0001288379 00000 n 0001286301 00000 n 0001288288 00000 n 0001293008 00000 n 0001288562 00000 n 0001292918 00000 n 0001295446 00000 n 0001293191 00000 n 0001295367 00000 n 0001297709 00000 n 0001295629 00000 n 0001297619 00000 n 0001302000 00000 n 0001297892 00000 n 0001301920 00000 n 0001304751 00000 n 0001302183 00000 n 0001304683 00000 n 0001309367 00000 n 0001304934 00000 n 0001309276 00000 n 0001313470 00000 n 0001309550 00000 n 0001313379 00000 n 0001317056 00000 n 0001313653 00000 n 0001316966 00000 n 0001321163 00000 n 0001317239 00000 n 0001321083 00000 n 0001322848 00000 n 0001321346 00000 n 0001322780 00000 n 0001327203 00000 n 0001323031 00000 n 0001327102 00000 n 0001331412 00000 n 0001327386 00000 n 0001331355 00000 n 0001497766 00000 n 0001331595 00000 n 0001431401 00000 n 0001495925 00000 n 0001497629 00000 n 0001570170 00000 n 0001497949 00000 n 0001497977 00000 n 0001688206 00000 n 0001688245 00000 n 0001498002 00000 n 0001688163 00000 n 0001687758 00000 n 0001564905 00000 n 0001565394 00000 n 0001570079 00000 n 0001574991 00000 n 0001570353 00000 n 0001574912 00000 n 0001577733 00000 n 0001575174 00000 n 0001577676 00000 n 0001668834 00000 n 0001577916 00000 n 0001667822 00000 n 0001668746 00000 n 0001671419 00000 n 0001669017 00000 n 0001671362 00000 n 0001688733 00000 n trailer << /Info 244 0 R /Root 1 0 R /Size 245 >> startxref 1688935 %%EOF PyXRD-0.8.4/README.md000066400000000000000000000105201363064711000137470ustar00rootroot00000000000000PyXRD ===== PyXRD is a python implementation of the matrix algorithm for computer modeling of X-ray diffraction (XRD) patterns of disordered lamellar structures. It's goals are to: - provide an easy user-interface for end-users - provide basic tools for displaying and manipulating XRD patterns - produce high-quality (publication-grade) figures - make modelling of XRD patterns for mixed-layer clay minerals 'easy' - be free and open-source (open box instead of closed box model) PyXRD was written with the multi-specimen full-profile fitting method in mind. A direct result is the ability to 'share' parameters among similar phases. This allows for instance to have an air-dry and a glycolated illite-smectite share their coherent scattering domain size, but still have different basal spacings and interlayer compositions for the smectite component. Or play with the di/tri-octahedral composition of a chlorite with ease. Other features are (incomplete list): - Import/export several common XRD formats (.RD, .RAW, .CPI, ASCII) - Simple background subtraction/addition (linear or custom patterns) - Smoothing patterns and adding noise to patterns - Peak finding and annotating (markers) - Peak stripping and peak area calculation tools - Custom line colors, line widths, pattern positions, ... - Goniometer settings (wavelengths, geometry settings, ...) - Specimen settings (sample length, absorption, ...) - Automatic parameter refinement using several algorithms, e.g.: - L BFGS B - Brute Force - Covariation Matrix Adapation Evolutionary Strategy (CMA-ES; using DEAP 1.0) - Multiple Particle Swarm Optimization (MPSO; using DEAP 1.0) - Particle-swarm CMA-ES (PS-CMA-ES; using DEAP 1.0) - Scripting support DISCLAIMER ========== PyXRD is still very much work in progress. Currently there is no strict development cycle as it is still a one-man project. This also means little time is going into testing and adding new tests for new features. Most of the codebase therefore remains untested at this point and Things May Break as a result. If you encounter bugs please: * create a new [issue](https://github.com/PyXRD/PyXRD/issues/new) or; * send me an e-mail INSTALLATION ============ As of version 0.5.0 PyXRD (finally) supports standard python packaging, meaning it is available from the [Python package index](https://pypi.python.org/pypi) and has become very easy to install for most Python users once the dependencies are installed. If you're not used to (installing) Python software, see below for specific instructions. Dependencies ------------ This is what should be present on your system. * Python 3.4 or later * Setuptools * GTK3 and pygobject 3.2 or later * cairocffi * Numpy 1.11 or later * Scipy 1.1 or later * Matplotlib 2.2 or later * Pyro4 4.41 or later * DEAP 1.0 or later Additionally, to be able to run the unit tests, you'll need to install pyton-mock (>= 1.3.0). To just run PyXRD, you won't need it though. Windows ------- PyXRD is developed on Ubuntu Linux, and thus has a number of dependencies which are not native to windows. Because of the difficulties in installing these correctly, from version 0.8 onwards an all-in-one stand alone installer is provided for windows users. Previous installations should not interfere, but it's better to remove them (including python, numpy, scipy and pygtk installed along with pre-v0.8 versions). You can choose for a local installation or a portable (single-folder) installation. The latter is just a zip-file which can be extracted e.g. onto an usb-drive. The downside is you don't get start menu entries. The installers are made available here: https://github.com/mathijs-dumon/PyXRD/releases After installation there should be a start menu entry available. If PyXRD does not launch, run cmd.exe (the command prompt), enter the following and send me the output (right-click to copy after selecting): ```"C:\Program Files (x86)\PyXRD\bin\python3.exe" -m pyxrd``` Linux ----- It should be as easy as: ``` python3 -m ensurepip --upgrade python3 -m pip install pyxrd ``` To run PyXRD: ``` python3 -m pyxrd ``` Mac OS X -------- Currently no support is given for iOS. If anyone is interested in getting this to work feel free to contact me. CREDITS ======= - [xylib](http://github.com/wojdyr/xylib/) - Has been a great help at unravelling some common XRD formats PyXRD-0.8.4/bump_version_to.py000066400000000000000000000016441363064711000162630ustar00rootroot00000000000000#!/usr/bin/env python2 import sys, os import subprocess import re import fileinput def update_version(filename, version): for line in fileinput.input(filename, inplace=True): rexp = "__version__\s*=.*" line = re.sub(rexp, "__version__ = \"%s\"" % version, line), print(line[0], end='') assert len(sys.argv) > 1, "You need to specify the version (e.g. 6.6.6)" assert "v" not in sys.argv[1], "You need to specify the version (e.g. 6.6.6)" update_version(os.path.abspath("pyxrd/__version.py"), sys.argv[1]) update_version(os.path.abspath("mvc/__version.py"), sys.argv[1]) print( subprocess.check_output(['git', 'add', 'pyxrd/__version.py'])) print( subprocess.check_output(['git', 'add', 'mvc/__version.py'])) print( subprocess.check_output(['git', 'commit', '-m', 'Version bump', '--allow-empty'])) print( subprocess.check_output(['git', 'tag', '-a', 'v%s' % sys.argv[1], '-m', 'v%s' % sys.argv[1]])) PyXRD-0.8.4/docs/000077500000000000000000000000001363064711000134225ustar00rootroot00000000000000PyXRD-0.8.4/docs/Makefile000066400000000000000000000126701363064711000150700ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyXRD.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyXRD.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/PyXRD" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyXRD" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." PyXRD-0.8.4/docs/api/000077500000000000000000000000001363064711000141735ustar00rootroot00000000000000PyXRD-0.8.4/docs/api/atoms.rst000066400000000000000000000003051363064711000160460ustar00rootroot00000000000000Atoms module ============ .. module:: pyxrd.atoms.models AtomType -------- .. autoclass:: AtomType :members: Atom ---- .. autoclass:: Atom :members: :exclude-members: on_removedPyXRD-0.8.4/docs/api/calculations.rst000066400000000000000000000015521363064711000174110ustar00rootroot00000000000000Calculations ============ .. automodule:: pyxrd.calculations :members: Atoms ----- .. automodule:: pyxrd.calculations.atoms :members: Components ---------- .. automodule:: pyxrd.calculations.components :members: Phases and CSDS --------------- .. automodule:: pyxrd.calculations.CSDS :members: .. automodule:: pyxrd.calculations.phases :members: Goniometer ---------- .. automodule:: pyxrd.calculations.goniometer :members: Specimen -------- .. automodule:: pyxrd.calculations.specimen :members: Statistics ---------- .. automodule:: pyxrd.calculations.statistics :members: Improve ------- .. automodule:: pyxrd.calculations.improve :members: Exceptions ---------- .. automodule:: pyxrd.calculations.exceptions :members: Data Objects ------------ .. automodule:: pyxrd.calculations.data_objects :members: PyXRD-0.8.4/docs/api/goniometer.rst000066400000000000000000000001311363064711000170700ustar00rootroot00000000000000Goniometer module ================= .. automodule:: pyxrd.goniometer.models :members:PyXRD-0.8.4/docs/api/index.rst000066400000000000000000000007071363064711000160400ustar00rootroot00000000000000.. PyXRD documentation master file, created by sphinx-quickstart on Wed Feb 12 10:27:39 2014. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Library API Reference ===================== .. automodule:: pyxrd .. sectionauthor:: Mathijs Dumon .. toctree:: :maxdepth: 2 atoms probabilities phases goniometer specimen mixture project calculations PyXRD-0.8.4/docs/api/mixture.rst000066400000000000000000000025701363064711000164260ustar00rootroot00000000000000Mixture module ============== The mixture module contains a number of classes that manage 'mixtures'. Mixtures combine multiple specimens and phases with each other. Mixtures are part of the project, which also holds a reference to the phases and specimens (and possible others as well) in the mixture. The combination of phases and specimens is achieved using a kind of combination 'matrix', in which rows are phases and columns are specimens. In other words, each column gets a specimen asigned to it, and each slot in the matrix gets a phase asigned to it. This way it is possible to have the same phase for different specimens of your sample if that pĥase is believed to be 'immune' to the treatments, or to have different (or at least partially different) phases when it is believed to be affected by the treatment in some way. For an explanation on how to create and link phases see the documentation on :doc:`phases`. TODO: add example code on how to use mixtures, optimizers and refiners Mixture ------- .. module:: pyxrd.mixture.models .. autoclass:: Mixture :members: Optimizer --------- .. module:: pyxrd.mixture.models.optimizers .. autoclass:: Optimizer :members: Refiner ------- .. module:: pyxrd.mixture.models.refiner .. autoclass:: Refiner :members: RefineContext ------------- .. module:: pyxrd.mixture.models.refiner .. autoclass:: RefineContext :members:PyXRD-0.8.4/docs/api/phases.rst000066400000000000000000000016101363064711000162060ustar00rootroot00000000000000Phases module ============= The phases module contains a number of classes that allow to create complex mixed-layer phases. TODO: add example code on how to use them! Phase ----- .. module:: pyxrd.phases.models.phase .. autoclass:: Phase :members: CSDS ---- .. module:: pyxrd.phases.models.CSDS .. autoclass:: LogNormalCSDSDistribution :members: .. autoclass:: DritsCSDSDistribution :members: Unit-cell properties -------------------- .. module:: pyxrd.phases.models.unit_cell_prop .. autoclass:: UnitCellProperty :members: Component --------- .. module:: pyxrd.phases.models.component .. autoclass:: Component :members: Atom Relations -------------- .. module:: pyxrd.phases.models.atom_relations .. autoclass:: AtomRelation :members: .. autoclass:: AtomRatio :members: .. autoclass:: AtomContentObject :members: .. autoclass:: AtomContents :members: PyXRD-0.8.4/docs/api/probabilities.rst000066400000000000000000000174661363064711000175730ustar00rootroot00000000000000Probabilities module ==================== The probabilities module contains a classes that allow the calculation of weigth and probability matrixes for mixed-layer minerals. Theory ------ Mixed-layer probabilities ^^^^^^^^^^^^^^^^^^^^^^^^^ These probability classes use the Reichweite (= R) concept and Markovian statistics to calculate how the layer stacking sequence is ordered (or disordered). The value for R denotes what number of previous layers (in a stack of layers) still influence the type of the following component. With other words, for: - R=0; the type of the next component does not depend on the previous components, - R=1; the type of the next component depends on the type of the previous component, - R=2; the type of the next component depends on the type of the previous 2 components, - ... We can describe the stacking sequence using two types of statistics: weight fractions and probabilities. Some examples: - the fraction of A type layers would be called :math:`W_A` - the probability of finding an A type layer in a stack would be called :math:`P_A` - the fraction of A type layers immediately followed by a B type layer would be called :math:`W_{AB}` - the probability of finding an A type layer immediately followed by a B type layer would be called :math:`P_{AB}` There exist a number of general relations between the weight fractions W and probabilities P which are detailed below. They are valid regardless of the value for R or the number of components G. Some of them are detailed below. For a more complete explanation: see Drits & Tchoubar (1990). For stacks composed of G types of layers, we can write (with :math:`N` the number of layers): .. math:: :nowrap: \begin{align*} & \begin{aligned} & W_{i} = \frac{N_{i}}{N_{max}} &\forall i \in \left[{1,2,\dots,G}\right] \\ & W_{ij} = \frac{N_{ij}}{N_{max}-1} &\forall i, j \in \left[{1,2,\dots,G}\right] \\ & W_{ijk} = \frac{N_{ijk}}{N_{max}-2} &\forall i, j, k \in \left[{1,2,\dots,G}\right] \\ & \text{etc.} \\ \end{aligned} \quad \quad \begin{aligned} & W_{ij} = W_i \cdot P_{ij} \\ & W_{ijk} = W_{ij} \cdot P_{ijk} \\ & \text{etc.} \\ \end{aligned} \quad \quad \begin{aligned} & \sum_{i=1}^{G}{W_i} = 1 \\ & \sum_{i=1}^{G}{\sum_{j=1}^{G}{W_{ij}}} = 1 \\ & \text{etc.} \\ \end{aligned} \quad \quad \begin{aligned} & \sum_{j=1}^{G}{P_{ij}} = 1 \\ & \sum_{k=1}^{G}{P_{ijk}} = 1 \\ & \text{etc.} \\ \end{aligned} \\ \end{align*} Because of these relationships it is not neccesary to always give all of the possible weight fractions and probability combinations. Each class contains a description of the number of 'independent' variables required for a certain combination of R ang G. It also details which ones were chosen and how the others are calculated from them. More often than not, ratios of several weight fractions are used, as they make the calculations somehwat easier. On the other hand, the actual meaning of these fractions is a little harder to grasp at first. Class functionality ^^^^^^^^^^^^^^^^^^^ The classes all inherit from an 'abstract' base class which provides a number of common functions. One of the 'handy' features are its indexable properties `mW` and `mP`. These allow you to quickly get or set an element in one of the matrixes:: >>> from pyxrd.probabilities.models import R1G3Model >>> prob = R1G3Model() >>> prob.mW[0] = 0.75 # set W1 >>> print prob.mW[0] 0.75 >>> prob.mW[0,1] = 0.5 # set W12 >>> print prob.mW[0,1] 0.5 Note however, that doing so might produce invalid matrices and produce strange X-ray diffraction patterns (or none at all). It is therefore recommended to use the attributes of the selected 'independent' parameters (see previous section) as setting these will trigger a complete re-calculation of the matrices. If however, you do want to create a matrix manually, you can do so by setting all the highest-level elements, which are: - for an R0 class only the Wi values - for an R1 class the Wi and Pij values - for an R2 class the Wij and Pijk values - for an R3 class the Wijk and Pijkl values After this you can call the `solve` and `validate` methods, which will calculate the other values (e.g. for an R2 it will calculate Wi, Wijk and Pij values). An example:: >>> from pyxrd.probabilities.models import R1G2Model >>> prob = R1G2Model() >>> prob.mW[0] = 0.75 # set W1 >>> prob.mW[1] = 0.25 # set W2 (needs to be 1 - W1 !) >>> prob.mP[1,1] = 0.3 # set P22 >>> prob.mP[1,0] = 0.7 # set P21 (needs to be 1 - P22 !) >>> prob.mP[0,1] = 0.7 / 3.0 # set P12 (needs to be P21 * W2 / W1!) >>> prob.mP[0,0] = 2.3 / 3.0 # set P11 (needs to be 1 - P12 !) >>> prob.solve() >>> prob.validate() >>> print prob.get_distribution_matrix() [[ 0.75 0. ] [ 0. 0.25]] >>> print prob.get_probability_matrix() [[ 0.76666667 0.23333333] [ 0.7 0.3 ]] Note that at the end we print the validation matrixes to be sure that we did a good job: if all is valid, we should see only "True" values. For more details on what elements produced an invalid results, you can look at the W_valid_mask and P_valid_mask properties. The exact same result could have been achieved using the independent parameter properties:: >>> from pyxrd.probabilities.models import R1G2Model >>> prob = R1G2Model() >>> prob.W1 = 0.75 >>> prob.P11_or_P22 = 0.3 >>> print prob.get_distribution_matrix() [[ 0.75 0. ] [ 0. 0.25]] >>> print prob.get_probability_matrix() [[ 0.76666667 0.23333333] [ 0.7 0.3 ]] For more information see the :class:`~pyxrd.probabilities.models.base_models._AbstractProbability` class Models ------ Base Models ^^^^^^^^^^^ .. module:: pyxrd.probabilities.models.base_models .. autoclass:: _AbstractProbability :members: R0 Models ^^^^^^^^^ R0 models have :math:`G - 1` independent parameters, :math:`G` being the number of components. Partial weight fractions were chosen as independent parameters, as this approach scales very well to a large number of components: If we define a partial weight fraction as :math:`F_i = \frac{W_i}{\sum_{j=i}^{G}{W_j}} \forall i \in \left[ {1,G} \right]`, and keep in mind the general rule :math:`\sum_{i=1}^{G}{W_i} = 1`, we can calculate all the weight fractions from these partial weight fractions progressively, since: - :math:`F_1` will acutally be equal to :math:`W_1`. - the denominator of every fraction :math:`F_i` is equal to :math:`1 - \sum_{j=1}^{i-1}{W_j}`, and you are able to calculate this: - for :math:`F_2`, it would be :math:`1 - W_1`, and you know :math:`W_1` from the first fracion - for :math:`F_3` it would be :math:`1 - W_1 - W_2`, and you can get :math:`W_1` and :math:`W_2` from the previous two fractions. - once the weight fractions of the first :math:`G - 1` components are known, then the weight fractions of the last component can be calculated as :math:`W_g = 1 - \sum_{i=1}^{G}{W_i}`. .. module:: pyxrd.probabilities.models.R0models .. autoclass:: R0G1Model :members: .. autoclass:: R0G2Model :members: .. autoclass:: R0G3Model :members: .. autoclass:: R0G4Model :members: .. autoclass:: R0G5Model :members: .. autoclass:: R0G6Model :members: R1 Models ^^^^^^^^^ .. module:: pyxrd.probabilities.models.R1models .. autoclass:: R1G2Model :members: .. autoclass:: R1G3Model :members: .. autoclass:: R1G4Model :members: R2 Models ^^^^^^^^^ .. module:: pyxrd.probabilities.models.R2models .. autoclass:: R2G2Model :members: .. autoclass:: R2G3Model :members: R3 Models ^^^^^^^^^ .. module:: pyxrd.probabilities.models.R3models .. autoclass:: R3G2Model :members: PyXRD-0.8.4/docs/api/project.rst000066400000000000000000000001411363064711000163670ustar00rootroot00000000000000Project module ============== Project ------- .. automodule:: pyxrd.project.models :members:PyXRD-0.8.4/docs/conf.py000066400000000000000000000203301363064711000147170ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # PyXRD documentation build configuration file, created by # sphinx-quickstart on Wed Feb 12 10:27:39 2014. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. top_dir = os.path.dirname(os.path.abspath(".")) if os.path.exists(os.path.join(top_dir, "pyxrd")): sys.path.insert(0, top_dir) pass import pyxrd # @UnusedImport # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', 'sphinx.ext.autosummary', 'sphinx.ext.pngmath', 'sphinx.ext.intersphinx' ] intersphinx_mapping = {'python': ('http://docs.python.org/2.7', None)} pngmath_latex_preamble = r""" \usepackage{mathtools} \usepackage{units} \usepackage[document]{ragged2e} \usepackage[fontsize=8pt]{scrextend} """ # User __init__ docstrings: autoclass_content = 'both' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'PyXRD' copyright = u'2014, Mathijs Dumon' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '.'.join(map(str, pyxrd.__version__.split('.')[0:2])) # The full version, including alpha/beta/rc tags. release = pyxrd.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'nature' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'pyxrddoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'PyXRD.tex', u'PyXRD Documentation', u'Mathijs Dumon', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'pyxrd', u'PyXRD Documentation', [u'Mathijs Dumon'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'PyXRD', u'PyXRD Documentation', u'Mathijs Dumon', 'PyXRD', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' autodoc_member_order = 'bysource' PyXRD-0.8.4/docs/index.rst000066400000000000000000000041251363064711000152650ustar00rootroot00000000000000.. PyXRD documentation master file, created by sphinx-quickstart on Wed Feb 12 10:27:39 2014. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. .. |pyxrd| replace:: *pyxrd* .. |PyXRD| replace:: *PyXRD* Welcome to the PyXRD docs! ========================== |PyXRD| is a python implementation of the matrix algorithm for computer modeling of X-ray diffraction (XRD) patterns of disordered lamellar structures. It's goals are to: #. provide an easy user-interface for end-users #. provide basic tools for displaying and manipulating XRD patterns #. produce high-quality (publication-grade) figures #. make modelling of XRD patterns for mixed-layer clay minerals 'easy' #. be free and open-source Motivation ========== |PyXRD| was written with the multi-specimen full-profile fitting method in mind. The direct result of this is the ability to 'share' parameters among similar phases. This allows for instance to have an air-dry and a glycolated illite-smectite share their coherent scattering domain size, but still have different basal spacings and interlayer compositions for the smectite component. Other features are (incomplete list): - Import/export several common XRD formats (.RD, .RAW, .CPI, ASCII) - simple background subtraction/addition (linear or custom patterns) - smoothing patterns and adding noise to patterns - peak finding and annotating (markers) - custom line colors, line widths, pattern positions, ... - goniometer settings (wavelengths, geometry settings, ...) - specimen settings (sample length, absorption, ...) - automatic parameter refinement using several algorithms, e.g.: - L BFGS B - Brute Force - Covariation Matrix Adapation Evolutionary Strategy (CMA-ES; using DEAP) - Multiple Particle Swarm Optimization (MPSO; using DEAP) - scripting support Contents ======== .. toctree:: :maxdepth: 2 api/index scripts-tutorial/index Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` PyXRD-0.8.4/docs/make.bat000066400000000000000000000117461363064711000150400ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PyXRD.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PyXRD.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end PyXRD-0.8.4/docs/scripts-tutorial/000077500000000000000000000000001363064711000167525ustar00rootroot00000000000000PyXRD-0.8.4/docs/scripts-tutorial/helloworld.rst000066400000000000000000000042631363064711000216640ustar00rootroot00000000000000Hello World script ================== Fire up your favorite text editor and copy the following piece of code: .. code-block:: python #!/usr/bin/python # coding=UTF-8 import logging logger = logging.getLogger(__name__) def run(args): """ Run as python core.py -s path/to/hello_world.py """ logging.info("Creating a new project") from pyxrd.project.models import Project project = Project(name="Hello World", description="This is a hello world project") from pyxrd.scripts.tools import reload_settings, launch_gui reload_settings() launch_gui(project) # from this point onwards, the GUI takes over! What this script does is very simple: it will create a new project, with it's name and title set to "Hello World" and "This is a hello world project" respectively. Then it will launch the gui as it would normally start but pass in this newly created project. What you should see is PyXRD loading as usual but with this new project pre-loaded. Running the script ================== Save the script somewhere (e.g. on your desktop) and name it "hello_world.py". To run this script you have to tell PyXRD where to find it first. So instead of starting PyXRD as you would usually do, open up a command line (Windows) or terminal (Linux), and follow the instructions below. Windows ------- On windows the following command should start PyXRD with the script: .. code-block:: bat C:\Python27\Scripts\PyXRD.exe -s "C:\path\to\script\hello_world.py" Replace the path\\to\\script part with the actual path where you saved the script. The above example also assumes you have installed python in C:\\Python27 (the default). Linux ----- On linux the following command should start PyXRD with the script: .. code-block:: bash PyXRD -s "/path/to/script/hello_world.py"' Replace the /path/to/script/ part with the actual path where you saved the script. This assumes you have installed PyXRD using pip so that the PyXRD command is picked up by the terminal. If you get an error like 'PyXRD: command not found', you will need to find out where PyXRD was installed and use the full path instead. PyXRD-0.8.4/docs/scripts-tutorial/index.rst000066400000000000000000000005361363064711000206170ustar00rootroot00000000000000.. PyXRD documentation master file, created by sphinx-quickstart on Wed Feb 12 10:27:39 2014. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Script Tutorial =============== .. sectionauthor:: Mathijs Dumon .. toctree:: :maxdepth: 2 introduction helloworld PyXRD-0.8.4/docs/scripts-tutorial/introduction.rst000066400000000000000000000006201363064711000222230ustar00rootroot00000000000000Introduction ============ It is possible to write scripts for PyXRD (projects). This allows anybody to make PyXRD do things it wasn't really intended to do or to automate certain tasks. Parts of the official PyXRD code are scripts themselves. This tutorial will provide an introduction on how to setup such a script. We assume the interested reader has already made himself familiar with Python.PyXRD-0.8.4/mvc/000077500000000000000000000000001363064711000132575ustar00rootroot00000000000000PyXRD-0.8.4/mvc/LICENSE000066400000000000000000000554411363064711000142750ustar00rootroot00000000000000 GNU LIBRARY GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the library GPL. It is numbered 2 because it goes with version 2 of the ordinary GPL.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Library General Public License, applies to some specially designated Free Software Foundation software, and to any other libraries whose authors decide to use it. You can use it for your libraries, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library, or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link a program with the library, you must provide complete object files to the recipients so that they can relink them with the library, after making changes to the library and recompiling it. And you must show them these terms so they know their rights. Our method of protecting your rights has two steps: (1) copyright the library, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the library. Also, for each distributor's protection, we want to make certain that everyone understands that there is no warranty for this free library. If the library is modified by someone else and passed on, we want its recipients to know that what they have is not the original version, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that companies distributing free software will individually obtain patent licenses, thus in effect transforming the program into proprietary software. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License, which was designed for utility programs. This license, the GNU Library General Public License, applies to certain designated libraries. This license is quite different from the ordinary one; be sure to read it in full, and don't assume that anything in it is the same as in the ordinary license. The reason we have a separate public license for some libraries is that they blur the distinction we usually make between modifying or adding to a program and simply using it. Linking a program with a library, without changing the library, is in some sense simply using the library, and is analogous to running a utility program or application program. However, in a textual and legal sense, the linked executable is a combined work, a derivative of the original library, and the ordinary General Public License treats it as such. Because of this blurred distinction, using the ordinary General Public License for libraries did not effectively promote software sharing, because most developers did not use the libraries. We concluded that weaker conditions might promote sharing better. However, unrestricted linking of non-free programs would deprive the users of those programs of all benefit from the free status of the libraries themselves. This Library General Public License is intended to permit developers of non-free programs to use free libraries, while preserving your freedom as a user of such programs to change the free libraries that are incorporated in them. (We have not seen how to achieve this as regards changes in header files, but we have achieved it as regards changes in the actual functions of the Library.) The hope is that this will lead to faster development of free libraries. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, while the latter only works together with the library. Note that it is possible for a library to be covered by the ordinary General Public License rather than by this special one. GNU LIBRARY GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Library General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also compile or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. c) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. d) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Library General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONSPyXRD-0.8.4/mvc/__init__.py000066400000000000000000000040001363064711000153620ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- # FIXME clean this mess up """ Shortcuts are provided to the following classes defined in submodules: .. class:: Model :noindex: .. class:: TreeStoreModel :noindex: .. class:: ListStoreModel :noindex: .. class:: TextBufferModel :noindex: .. class:: ModelMT :noindex: .. class:: Controller :noindex: .. class:: View :noindex: .. class:: Observer :noindex: .. class:: Observable :noindex: """ from .support.version import LooseVersion from .__version import __version__ # Class shortcuts: from .observers import Observer from .support.observables import Signal, Observable from .models import * try: from .view import View from .controller import Controller except ImportError: import logging logging.getLogger(__name__).warning("ImportError when trying to load View and/or Controller: do you have PyGTK/GObject installed?") PyXRD-0.8.4/mvc/__version.py000066400000000000000000000024301363064711000156130ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (c) 2007 by Guillaume Libersat # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- __version__ = "0.8.4" PyXRD-0.8.4/mvc/adapters/000077500000000000000000000000001363064711000150625ustar00rootroot00000000000000PyXRD-0.8.4/mvc/adapters/__init__.py000066400000000000000000000026461363064711000172030ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2007 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .adapter_registry import AdapterRegistry from .abstract_adapter import AbstractAdapter from .dummy_adapter import DummyAdapter from .model_adapter import ModelAdapter __all__ = [ "AdapterRegistry", "AbstractAdapter", "DummyAdapter", "ModelAdapter", ]PyXRD-0.8.4/mvc/adapters/abstract_adapter.py000066400000000000000000000126361363064711000207470ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import logging logger = logging.getLogger(__name__) import weakref from .metaclasses import MetaAdapter class AbstractAdapter(object, metaclass=MetaAdapter): """ An semi-abstract class all Adapters have to derive from. """ widget_types = [] __prop = None @property def _prop(self): if callable(self.__prop): return self.__prop() else: return self.__prop @_prop.setter def _prop(self, value): if value is None: self.__prop = None else: self.__prop = weakref.ref(value, lambda: self.disconnect()) __controller = None @property def _controller(self): if callable(self.__controller): return self.__controller() else: return self.__controller @_controller.setter def _controller(self, value): if value is None: self.__controller = None else: self.__controller = weakref.ref(value, lambda c: self.disconnect()) __widget = None @property def _widget(self): if callable(self.__widget): return self.__widget() else: return self.__widget @_widget.setter def _widget(self, value): if value is None: self.__widget = None else: self.__widget = weakref.ref(value, lambda w: self.disconnect(widget=w())) # ---------------------------------------------------------------------- # Construction: # ---------------------------------------------------------------------- def __init__(self, controller, prop, widget, *args, **kwargs): super(AbstractAdapter, self).__init__(*args, **kwargs) self._prop = prop self._controller = controller self._widget = widget # ---------------------------------------------------------------------- # Public interface: # ---------------------------------------------------------------------- def update_model(self): """Forces the property to be updated from the value hold by the widget. This method should be called directly by the user in very unusual conditions.""" self._write_property(self._read_widget()) return def update_widget(self): """Forces the widget to be updated from the property value. This method should be called directly by the user when the property is not observable, or in very unusual conditions.""" self._write_widget(self._read_property()) return def disconnect(self, model=None, widget=None): """Disconnects the adapter from the model and the widget.""" self._disconnect_model(model=model) self._disconnect_widget(widget=widget) # ---------------------------------------------------------------------- # Widget connecting & disconnecting: # ---------------------------------------------------------------------- def _connect_widget(self): raise NotImplementedError("Please Implement this method") def _disconnect_widget(self, widget=None): raise NotImplementedError("Please Implement this method") # ---------------------------------------------------------------------- # Model connecting & disconnecting: # ---------------------------------------------------------------------- def _connect_model(self): raise NotImplementedError("Please Implement this method") def _disconnect_model(self, model=None): raise NotImplementedError("Please Implement this method") # ---------------------------------------------------------------------- # Widget-side reading and writing # ---------------------------------------------------------------------- def _read_widget(self): raise NotImplementedError("Please Implement this method") def _write_widget(self, val): raise NotImplementedError("Please Implement this method") # ---------------------------------------------------------------------- # Model-side reading and writing # ---------------------------------------------------------------------- def _read_property(self, *args): raise NotImplementedError("Please Implement this method") def _write_property(self, value, *args): raise NotImplementedError("Please Implement this method") pass # end of class PyXRD-0.8.4/mvc/adapters/adapter_registry.py000066400000000000000000000117141363064711000210100ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import importlib import logging logger = logging.getLogger(__name__) from ..settings import TOOLKIT from ..support import gui_loop class ToolkitRegistry(dict): """ Dict subclass used to store all AdapterRegistry's. Keys are toolkit names, values are AdapterRegistry instances """ # These will be loaded automatically when this module is first loaded: toolkit_modules = [ ".gtk_support" ] selected_toolkit = None def get_or_create_registry(self, toolkit_name): if not toolkit_name in self: adapter_registry = AdapterRegistry() self.register(toolkit_name, adapter_registry) return self[toolkit_name] def register(self, toolkit_name, adapter_registry): self[toolkit_name] = adapter_registry def select_toolkit(self, toolkit_name): if not toolkit_name in self: raise ValueError("Cannot select unknown toolkit '%s'" % toolkit_name) else: self.selected_toolkit = toolkit_name tkar = self.get_selected_adapter_registry() tkar.load_toolkit_functions() def get_selected_adapter_registry(self): if self.selected_toolkit is None: raise ValueError("No toolkit has been selected!") else: return self[self.selected_toolkit] class AdapterRegistry(dict): """ A dict which maps Adapter class types to the widget types they can handle. This relies on these classes being registered using the 'register' decorator also provided by this class. """ toolkit_registry = ToolkitRegistry() @classmethod def get_selected_adapter_registry(cls): return cls.toolkit_registry.get_selected_adapter_registry() @classmethod def get_adapter_for_widget_type(cls, widget_type): return cls.toolkit_registry.get_selected_adapter_registry()[widget_type] def set_toolkit_functions(self, *args, **kwargs): setattr(self, "_toolkit_functions", (args, kwargs)) def load_toolkit_functions(self): """ This function loads the toolkit functions passed to set_toolkit_functions with the support module. """ args, kwargs = getattr(self, "_toolkit_functions", ([],{})) gui_loop.load_toolkit_functions(*args, ** kwargs) @classmethod def register(cls, adapter_cls): """ This is called from metaclasses or used as a decorator. An example metaclass is at mvc.adapters.metaclasses and a model implementing it at adapters.gtk_support.basic """ if hasattr(adapter_cls, "widget_types") and hasattr(adapter_cls, "toolkit"): adapter_registry = cls.toolkit_registry.get_or_create_registry(adapter_cls.toolkit) logger.debug("Registering %s as handler for widget types '%s' in toolkit '%s'" % (adapter_cls, adapter_cls.widget_types, adapter_cls.toolkit)) for widget_type in adapter_cls.widget_types: adapter_registry[widget_type] = adapter_cls else: logger.debug("Ignoring '%s' as handler: no 'toolkit' or 'widget_types' defined" % adapter_cls) return adapter_cls pass # end of class for toolkit_module in ToolkitRegistry.toolkit_modules: if toolkit_module.startswith('.'): package = __name__.rpartition('.')[0] try: # Import and load toolkit module: tk_mod = importlib.import_module(toolkit_module, package=package) # Get or create the associated toolkit registry: tkreg = AdapterRegistry.toolkit_registry.get_or_create_registry(tk_mod.toolkit) # Load the toolkit and pass the registry: tk_mod.load(tkreg) except ImportError: logger.warning("Could not load toolkit support module '%s'" % (toolkit_module,)) AdapterRegistry.toolkit_registry.select_toolkit(TOOLKIT) PyXRD-0.8.4/mvc/adapters/dummy_adapter.py000066400000000000000000000036531363064711000202760ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .abstract_adapter import AbstractAdapter class DummyAdapter(AbstractAdapter): """ An adapter that does nothing. Really nothing. """ def __init__(self, controller=None, prop=None, widget=None): super(DummyAdapter, self).__init__(controller, prop, widget) def _connect_widget(self): pass # nothing to do def _disconnect_widget(self, widget=None): pass # nothing to do def _connect_model(self): pass # nothing to do def _disconnect_model(self, model=None): pass # nothing to do def _read_widget(self): pass # nothing to do def _write_widget(self, val): pass # nothing to do def _read_property(self, *args): pass # nothing to do def _write_property(self, value, *args): pass # nothing to do pass # end of classPyXRD-0.8.4/mvc/adapters/gtk_support/000077500000000000000000000000001363064711000174435ustar00rootroot00000000000000PyXRD-0.8.4/mvc/adapters/gtk_support/__init__.py000066400000000000000000000045561363064711000215660ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- toolkit = "gtk" def load(tkreg): # Load adapters: from .adjustment_adapter import AdjustmentAdapter from .arrow_adapter import ArrowAdapter from .check_menu_item_adapter import CheckMenuItemAdapter from .color_button_adapter import ColorButtonAdapter from .color_selection_adapter import ColorSelectionAdapter from .combo_box_adapter import ComboBoxAdapter from .entry_adapter import EntryAdapter from .expander_adapter import ExpanderAdapter from .file_chooser_adapter import FileChooserAdapter from .float_entry_adapter import FloatEntryAdapter from .label_adapter import LabelAdapter from .link_button_adapter import LinkButtonAdapter from .scale_adapter import ScaleEntryAdapter from .text_view_adapter import TextViewAdapter from .toggle_button_adapter import ToggleButtonAdapter from .tree_view_adapters import XYListViewAdapter, ObjectListViewAdapter # Load main loop functions from .toolkit_functions import ( add_idle_call, remove_source, add_timeout_call, start_event_loop, stop_event_loop ) # Register them tkreg.set_toolkit_functions( add_idle_call, remove_source, add_timeout_call, remove_source, start_event_loop, stop_event_loop ) PyXRD-0.8.4/mvc/adapters/gtk_support/_gtk_color_utils.py000066400000000000000000000030701363064711000233570ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') from gi.repository import Gdk def _parse_color_string(value): """Converts a hex-formatted (e.g. #FFFFFF) string to a Gdk color object""" color = Gdk.RGBA() color.parse(value) return color # @UndefinedVariable def _parse_color_value(value): """Converts a Gdk color object to a hex-formatted string (e.g. #FFFFFF)""" return "#%02x%02x%02x" % (int(value.red * 255), int(value.green * 255), int(value.blue * 255))PyXRD-0.8.4/mvc/adapters/gtk_support/adjustment_adapter.py000066400000000000000000000030231363064711000236710ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from .basic import GtkAdapter class AdjustmentAdapter(GtkAdapter): """ An adapter for a Gtk.Adjustment widget """ widget_types = ["spin", "spin_button"] _wid_read = GtkAdapter.static_to_class(Gtk.Adjustment.get_value) _wid_write = GtkAdapter.static_to_class(Gtk.Adjustment.set_value) _signal = "value-changed" pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/arrow_adapter.py000066400000000000000000000030121363064711000226430ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from .basic import GtkAdapter class ArrowAdapter(GtkAdapter): """ An adapter for a Gtk.Arrow widget """ widget_types = ["arrow"] _check_widget_type = Gtk.Arrow _wid_read = lambda a: a.get_property("arrow-type") _wid_write = lambda a, v: a.set(v, a.get_property("shadow-type")) _signal = "changed" pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/basic.py000066400000000000000000000131211363064711000210740ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import logging logger = logging.getLogger(__name__) from contextlib import contextmanager from ...adapters.model_adapter import ModelAdapter from ...support.utils import not_none class GtkAdapter(ModelAdapter): """ A base class for Gtk-widget Adapters """ toolkit = "gtk" # Widget-side value handling: _wid_read = None _wid_write = None _signal = None _signal_id = None _signal_args = [] _check_widget_type = None def __init__(self, controller, prop, widget, prop_read=None, prop_write=None, value_error=None, spurious=False, wid_read=None, wid_write=None, signal=None, signal_args=None, update=True): """ wid_read and wid_write are the methods used for reading and writing the widget's value. signal is the signal name to listen to for widget updates signal_args is the (optional) (list of) argument(s) that will be passed when connecting the signal. Finally, if update is false, the widget will not be initially updated """ super(GtkAdapter, self).__init__( controller, prop, widget, prop_read=prop_read, prop_write=prop_write, value_error=value_error, spurious=spurious ) # Widget-side value handling: self._wid_read = not_none(wid_read, self._wid_read) self._wid_write = not_none(wid_write, self._wid_write) self._signal = not_none(signal, self._signal) self._signal_id = None self._signal_args = not_none(signal_args, self._signal_args) self._update = update if self._check_widget_type is not None: widget_type = type(widget) if not isinstance(widget, self._check_widget_type): msg = "Property '%s' from model '%s' has a widget type %s, " \ "which can only be used for (a subclass of) a %s " \ "widget, and not for a %s widget!" % ( prop.label, controller.model, type(self), self._check_widget_type, widget_type ) raise TypeError(msg) # Connect the widget: self._connect_widget() # ---------------------------------------------------------------------- # Widget connecting & disconnecting: # ---------------------------------------------------------------------- def _connect_widget(self): """Called when the adapter is ready to connect to the widget""" # Connect the widget if self._signal: try: self._signal_id = self._widget.connect( self._signal, self._on_wid_changed, self._signal_args) except TypeError: logger.error("Failed to connect signal named '%s' on widget '%s' for property '%s'!" % (self._signal, self._widget, self._prop.label)) raise # Updates the widget: if self._update: self.update_widget() return def _on_wid_changed(self, wid, *args): """Called when the widget is changed""" if self._ignoring_notifs: return self.update_model() return def _disconnect_widget(self, widget=None): """Disconnects the widget""" if self._signal is not None and self._signal_id is not None: widget = not_none(self._widget, widget) if widget is not None: widget.disconnect(self._signal_id) self._signal, self._signal_id = None, None # ---------------------------------------------------------------------- # Widget-side reading and writing # ---------------------------------------------------------------------- @contextmanager def _block_widget_signal(self): if self._signal_id is not None: self._widget.handler_block(self._signal_id) yield if self._signal_id is not None: self._widget.handler_unblock(self._signal_id) def _read_widget(self): """Returns the value currently stored into the widget.""" return self._wid_read(self._widget) def _write_widget(self, val): """Writes value into the widget. If specified, user setter is invoked.""" with self._ignore_notifications(): return self._wid_write(self._widget, val) @staticmethod def static_to_class(func): def wrapper(c, *args, **kwargs): return func(*args, **kwargs) return wrapper pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/check_menu_item_adapter.py000066400000000000000000000030311363064711000246310ustar00rootroot00000000000000# ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from .basic import GtkAdapter class CheckMenuItemAdapter(GtkAdapter): """ An adapter for a Gtk.CheckMenuItem widget """ widget_types = ["check_menu"] _check_widget_type = Gtk.CheckMenuItem _wid_read = GtkAdapter.static_to_class(Gtk.CheckMenuItem.get_active) _wid_write = GtkAdapter.static_to_class(Gtk.CheckMenuItem.set_active) _signal = "toggled" pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/color_button_adapter.py000066400000000000000000000033271363064711000242330ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from .basic import GtkAdapter from ._gtk_color_utils import _parse_color_string, _parse_color_value class ColorButtonAdapter(GtkAdapter): """ An adapter for a Gtk.Label widget """ widget_types = ["color", "color_button"] _check_widget_type = Gtk.ColorButton _wid_read = lambda s, w: w.get_rgba() _wid_write = lambda s, w, v: w.set_rgba(v) if w.get_realized() else None _signal = "color-set" _prop_read = lambda s, *a: _parse_color_string(*a) _prop_write = lambda s, *a: _parse_color_value(*a) pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/color_selection_adapter.py000066400000000000000000000033401363064711000247000ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from .basic import GtkAdapter from ._gtk_color_utils import _parse_color_string, _parse_color_value class ColorSelectionAdapter(GtkAdapter): """ An adapter for a Gtk.Label widget """ widget_types = ["color_selection"] _check_widget_type = Gtk.ColorSelection _wid_read = GtkAdapter.static_to_class(Gtk.ColorSelection.get_current_color) _wid_write = GtkAdapter.static_to_class(Gtk.ColorSelection.set_current_color) _signal = "color-set" _prop_read = _parse_color_string _prop_write = _parse_color_value pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/combo_box_adapter.py000066400000000000000000000064401363064711000234700ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import logging logger = logging.getLogger(__name__) import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from ...support.utils import not_none from .basic import GtkAdapter class ComboBoxAdapter(GtkAdapter): """ An adapter that adapts a ComboBox widget to an property which has a choices attribute containing dictionary with allowed (value, description) pairs as keys and values. """ widget_types = ["option_list", ] _check_widget_type = Gtk.ComboBox _wid_read = lambda c, w, *a: Gtk.ComboBox.get_active_iter(w, *a) _wid_write = lambda c, w, *a: Gtk.ComboBox.set_active_iter(w, *a) _signal = "changed" _prop_cast = False def _parse_prop(self, prop, model): """Parses (optional) prop strings for the given model""" prop, model = super(ComboBoxAdapter, self)._parse_prop(prop, model) if not isinstance(prop.choices, dict): raise ValueError("ComboBox widget handler requires a property with a 'choices' dictionary!") else: self._store = Gtk.ListStore(str, str) for key, value in prop.choices.items(): self._store.append([str(key), str(value)]) return prop, model def _prop_write(self, itr): if itr is not None: return self._store.get_value(itr, 0) def _prop_read(self, val): for row in self._store: if self._store.get_value(row.iter, 0) == str(val): return row.iter def _connect_widget(self): # Set up the combo box layout: cell = Gtk.CellRendererText() self._widget.clear() self._widget.pack_start(cell, True) self._widget.add_attribute(cell, 'text', 1) cell.set_property('family', 'Monospace') cell.set_property('size-points', 10) # Set the model: self._widget.set_model(self._store) # Continue as usual: super(ComboBoxAdapter, self)._connect_widget() def disconnect(self, model=None, widget=None): widget = not_none(self._widget, widget) if widget is not None: widget.set_model(None) super(ComboBoxAdapter, self).disconnect(model=model, widget=widget) pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/dialogs/000077500000000000000000000000001363064711000210655ustar00rootroot00000000000000PyXRD-0.8.4/mvc/adapters/gtk_support/dialogs/__init__.py000066400000000000000000000000001363064711000231640ustar00rootroot00000000000000PyXRD-0.8.4/mvc/adapters/gtk_support/dialogs/dialog_factory.py000066400000000000000000000217161363064711000244340ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import sys, html from contextlib import contextmanager import gi from mvc.adapters.gtk_support.widgets.threaded_task_box import ThreadedTaskBox from mvc.support.gui_loop import run_when_idle, add_timeout_call,\ remove_timeout_call from mvc.support.cancellable_thread import CancellableThread gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk, GObject # @UnresolvedImport from .message_dialog import MessageDialog from .file_chooser_dialog import FileChooserDialog class DialogFactory(object): # ------------------------------------------------------------ # File dialog creators # ------------------------------------------------------------ @staticmethod def get_file_dialog_from_context(context, parent=None, **kwargs): return FileChooserDialog(context, parent=parent, **kwargs) @staticmethod def get_file_dialog( action, title, parent=None, current_name=None, current_folder=None, extra_widget=None, filters=[], multiple=False, confirm_overwrite=True, persist=False): """ Generic file dialog creator """ return FileChooserDialog( title=title, action=action, parent=parent, buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT, Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT), current_name=current_name, current_folder=current_folder, extra_widget=extra_widget, filters=filters, multiple=multiple, confirm_overwrite=confirm_overwrite, persist=persist ) @staticmethod def get_save_dialog( title, parent=None, current_name=None, current_folder=None, extra_widget=None, filters=[], confirm_overwrite=True, persist=False): """ Save file dialog creator """ # Forces save action # Does not allow selecting multiple files return DialogFactory.get_file_dialog( action=Gtk.FileChooserAction.SAVE, title=title, parent=parent, current_name=current_name, current_folder=current_folder, extra_widget=extra_widget, filters=filters, multiple=False, confirm_overwrite=confirm_overwrite, persist=persist) @staticmethod def get_load_dialog( title, parent=None, current_name=None, current_folder=None, extra_widget=None, filters=[], multiple=True, persist=False): """ Load file dialog creator """ # Forces open action # Disables overwrite confirmation (doesn't matter really) return DialogFactory.get_file_dialog( action=Gtk.FileChooserAction.OPEN, title=title, parent=parent, current_name=current_name, current_folder=current_folder, extra_widget=extra_widget, filters=filters, multiple=multiple, confirm_overwrite=False, persist=persist) # ------------------------------------------------------------ # Message dialog creators # ------------------------------------------------------------ @staticmethod def get_message_dialog(message, type, buttons=Gtk.ButtonsType.YES_NO, persist=False, parent=None, title=None): # @ReservedAssignment """ Generic message dialog creator """ return MessageDialog( message=message, parent=parent, type=type, flags=Gtk.DialogFlags.DESTROY_WITH_PARENT, buttons=buttons, persist=persist, title=title) @staticmethod def get_confirmation_dialog(message, persist=False, parent=None, title=None): """ Confirmation dialog creator """ return DialogFactory.get_message_dialog( message, parent=parent, type=Gtk.MessageType.WARNING, persist=persist, title=title ) @staticmethod def get_information_dialog(message, persist=False, parent=None, title=None): """ Information dialog creator """ return DialogFactory.get_message_dialog( message, parent=parent, type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK, persist=persist, title=title ) @staticmethod def get_error_dialog(message, persist=False, parent=None, title=None): """ Error dialog creator """ return DialogFactory.get_message_dialog( message, parent=parent, type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK, persist=persist, title=title ) # ------------------------------------------------------------ # Custom dialog creator # ------------------------------------------------------------ @staticmethod def get_custom_dialog(content, parent=None): window = Gtk.Window() window.set_border_width(10) window.set_modal(True) window.set_transient_for(parent) window.connect('delete-event', lambda widget, event: True) window.add(content) return window pass #end of class @staticmethod @contextmanager def error_dialog_handler(message, parent=None, title=None, reraise=True, print_tb=True): """ Context manager that can be used to wrap error-prone code. If an error is risen, a dialog will inform the user, optionally the error can be re-raised """ try: yield except: msg = message.format(html.escape("%s" % sys.exc_info()[1])) DialogFactory.get_error_dialog( msg, title=title, parent=parent ).run() if reraise: raise # This should be handled by the default UI bug dialog elif print_tb: from traceback import print_exc print_exc() @staticmethod def get_progress_dialog(action, complete_callback=None, gui_message="Processing ...", toplevel=None): """ Returns a callable that will show a progress dialog for the given action - which will be run in a different thread from the GUI. The action is expected to take a single argument: `status_dict` which is used to format the gui_message (new-style formatting). toplevel is the top level window. complete_callback is called when the action has completed with its return value. When interrupted or cancelled by the user, the dialog just hides. """ def run_action_and_show_progress(): taskgui = ThreadedTaskBox() window = DialogFactory.get_custom_dialog( taskgui, parent=toplevel) # Status: status_dict = dict() # Task: def load_peak_thresholds(stop=None): action(status_dict) # Cancel & stop events: def on_interrupted(*args, **kwargs): window.hide() # Status label update: def gui_callback(): taskgui.set_status(gui_message.format(**status_dict)) return True add_timeout_call(250, gui_callback) # Complete event: @run_when_idle def on_complete(*args, **kwargs): remove_timeout_call(gui_callback) taskgui.stop() window.destroy() if callable(complete_callback): complete_callback(*args, **kwargs) # Run thread: thread = CancellableThread(load_peak_thresholds, on_complete) thread.start() # Run task box: taskgui.connect("cancelrequested", on_interrupted) taskgui.connect("stoprequested", on_interrupted) taskgui.set_status("Loading ...") taskgui.start() window.show_all() return run_action_and_show_progress PyXRD-0.8.4/mvc/adapters/gtk_support/dialogs/file_chooser_dialog.py000066400000000000000000000145211363064711000254220ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk # @UnresolvedImport from .utils import adjust_filename_to_globs, retrieve_lowercase_extension, run_dialog import os class FileChooserDialog(Gtk.FileChooserDialog): accept_responses = ( Gtk.ResponseType.ACCEPT, # @UndefinedVariable Gtk.ResponseType.YES, # @UndefinedVariable Gtk.ResponseType.APPLY, # @UndefinedVariable Gtk.ResponseType.OK # @UndefinedVariable ) persist = False @property def parser(self): try: return getattr(self.get_filter(), "parser") except AttributeError: return None @property def filename(self): """ Extracts the selected filename from a Gtk.Dialog """ filename = super(FileChooserDialog, self).get_filename() if filename is not None: filename = adjust_filename_to_globs(filename, self.selected_globs) self.set_filename(filename) return filename @property def selected_globs(self): """ Returns the extension glob corresponding to the selected filter """ fltr = self.get_filter() # THIS RETURNS NONE?? if fltr is None: return None else: selected_name = fltr.get_name() for fltr in self.filters: try: name, globs = fltr except TypeError: # filter is not a tuple, perhaps it is a FileFilter from a parser parser = getattr(fltr, "parser") name, globs = parser.description, parser.extensions if selected_name == name: if len(globs) and globs[0] != "*.*": return [retrieve_lowercase_extension(glob) for glob in globs] else: return None def __init__(self, title, action, parent=None, buttons=None, current_name=None, current_folder=os.path.expanduser('~'), extra_widget=None, filters=[], multiple=False, confirm_overwrite=True, persist=False): super(FileChooserDialog, self).__init__( title=title, action=action, parent=parent, buttons=buttons ) self.update( multiple=multiple, confirm_overwrite=confirm_overwrite, extra_widget=extra_widget, filters=filters, current_name=current_name, current_folder=current_folder, persist=persist ) def update(self, **kwargs): """ Updates the dialog with the given set of keyword arguments, and then returns itself """ if "title" in kwargs and kwargs["title"] is not None: self.set_title(kwargs.pop("title")) if "action" in kwargs and kwargs["action"] is not None: self.set_action(kwargs.pop("action")) if "parent" in kwargs and kwargs["parent"] is not None: self.set_parent(kwargs.pop("parent")) if "buttons" in kwargs: self.get_action_area().foreach(lambda w: w.destroy()) self.add_buttons(*kwargs.pop("buttons")) # Multiple files are allowed or not: if "multiple" in kwargs: self.set_select_multiple(kwargs.pop("multiple")) # Ask before overwriting yes/no if "confirm_overwrite" in kwargs: self.set_do_overwrite_confirmation(kwargs.pop("confirm_overwrite")) # Extra widget packed at the bottom: if "extra_widget" in kwargs and kwargs["extra_widget"] is not None: self.set_extra_widget(kwargs.pop("extra_widget")) # Set suggested file name if "current_name" in kwargs and kwargs["current_name"] is not None: self.set_current_name(kwargs.pop("current_name")) # Set suggested folder if "current_folder" in kwargs and kwargs["current_folder"] is not None: self.set_current_folder(kwargs.pop("current_folder")) # Add file filters if "filters" in kwargs: # Clear old filters: for fltr in self.list_filters(): self.remove_filter(fltr) # Set new filters: self.filters = list(kwargs.pop("filters")) for fltr in self._get_object_file_filters(self.filters): self.add_filter(fltr) self.persist = kwargs.pop("persist", self.persist) return self def _get_object_file_filters(self, filters=[]): """ Parses a list of textual file filter globs or Gtk.FileFilter objects into Gtk.FileFilter objects """ for obj in filters: if isinstance(obj, Gtk.FileFilter): yield obj else: # if not a Gtk.FileFilter we assume it is a glob tuple name, re = obj ffilter = Gtk.FileFilter() ffilter.set_name(name) if isinstance(re, str): ffilter.add_pattern(re) else: # if not a single glob, assume an iterable is given for expr in re: ffilter.add_pattern(expr) yield ffilter #override def run(self, *args, **kwargs): kwargs['destroy'] = not self.persist return run_dialog(self, *args, **kwargs) pass #end of class PyXRD-0.8.4/mvc/adapters/gtk_support/dialogs/message_dialog.py000066400000000000000000000043251363064711000244060ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk # @UnresolvedImport from .utils import run_dialog class MessageDialog(Gtk.MessageDialog): accept_responses = ( Gtk.ResponseType.ACCEPT, # @UndefinedVariable Gtk.ResponseType.YES, # @UndefinedVariable Gtk.ResponseType.APPLY, # @UndefinedVariable Gtk.ResponseType.OK # @UndefinedVariable ) def __init__(self, message, parent=None, type=Gtk.MessageType.INFO, # @ReservedAssignment flags=Gtk.DialogFlags.DESTROY_WITH_PARENT, buttons=Gtk.ButtonsType.NONE, persist=False, title=None): super(MessageDialog, self).__init__( parent=parent, type=type, flags=flags, buttons=buttons) self.persist = persist self.set_markup(message) if title is not None: self.set_title(title) #override def run(self, *args, **kwargs): kwargs['destroy'] = not self.persist return run_dialog(self, *args, **kwargs) pass #end of class PyXRD-0.8.4/mvc/adapters/gtk_support/dialogs/utils.py000066400000000000000000000074031363064711000226030ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- def _toggle_cb(dialog, event, cb): cb_id_name = "%s_cb_id" % event.replace("-", "_") cb_id = getattr(dialog, cb_id_name, None) if cb_id is not None: dialog.disconnect(cb_id) cb_id = dialog.connect(event, cb) setattr(dialog, cb_id_name, cb_id) def run_dialog(dialog, on_accept_callback=None, on_reject_callback=None, destroy=True): """ Helper method - do not call directly """ if not (on_accept_callback is None or callable(on_accept_callback)): raise ValueError("Accept callback must be None or callable") if not (on_reject_callback is None or callable(on_reject_callback)): raise ValueError("Reject callback must be None or callable") def _dialog_response_cb(dialog, response): if response in dialog.accept_responses and on_accept_callback is not None: on_accept_callback(dialog) elif on_reject_callback is not None: on_reject_callback(dialog) if destroy: dialog.destroy() else: dialog.hide() return not destroy _toggle_cb(dialog, "response", _dialog_response_cb) # Adding the delete event prevents the dialog from being destroyed if the # user indicated it should persist def delete_event_cb(dialog, event): if on_reject_callback is not None: on_reject_callback(dialog) if destroy: dialog.destroy() else: dialog.hide() return not destroy _toggle_cb(dialog, "delete_event", delete_event_cb) # Present the dialog dialog.set_modal(True) dialog.show_all() def retrieve_lowercase_extension(glob): '''Ex: '*.[oO][rR][aA]' => '*.ora' ''' return ''.join([ c.replace("[", "").replace("]", "")[:-1] for c in glob.split('][')]) def adjust_filename_to_globs(filename, globs): """ Adjusts a given filename so it ends with the proper extension """ if globs: # If given use file extensions globs possible_fns = [] # Loop over globs, if the current filenames extensions matches # a given glob, the filename is returned as is, otherwise # the extension of the first glob is added at the end of filename for glob in globs: if glob is not None: extension = glob[1:] if filename[len(filename) - len(extension):].lower() != extension.lower(): possible_fns.append("%s%s" % (filename, glob[1:])) else: return filename # matching extension is returned immediately return possible_fns[0] # otherwise add extension of the first filter else: # If no globs are given, return filename as is return filename PyXRD-0.8.4/mvc/adapters/gtk_support/entry_adapter.py000066400000000000000000000030111363064711000226510ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from .basic import GtkAdapter class EntryAdapter(GtkAdapter): """ An adapter for a Gtk.Entry widget """ widget_types = ["entry", "input"] _check_widget_type = Gtk.Entry _wid_read = lambda c, w: Gtk.Entry.get_text(w) _wid_write = lambda c, w, v: Gtk.Entry.set_text(w, str(v)) _signal = "changed" pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/expander_adapter.py000066400000000000000000000030501363064711000233210ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from .basic import GtkAdapter class ExpanderAdapter(GtkAdapter): """ An adapter for a Gtk.Expander widget """ widget_types = ["expander"] _check_widget_type = Gtk.Expander _wid_read = GtkAdapter.static_to_class(Gtk.Expander.get_expanded) _wid_write = GtkAdapter.static_to_class(Gtk.Expander.set_expanded) _signal = "activate" pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/file_chooser_adapter.py000066400000000000000000000031031363064711000241530ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from .basic import GtkAdapter class FileChooserAdapter(GtkAdapter): """ An adapter for a Gtk.FileChooser widget """ widget_types = ["file", "file_chooser"] _check_widget_type = Gtk.FileChooser _wid_read = GtkAdapter.static_to_class(Gtk.FileChooser.get_filename) _wid_write = GtkAdapter.static_to_class(Gtk.FileChooser.set_filename) _signal = "file-set" pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/float_entry_adapter.py000066400000000000000000000052461363064711000240520ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import re import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from .entry_adapter import EntryAdapter class FloatEntryAdapter(EntryAdapter): """ An adapter for a Gtk.Entry widget holding a float. """ widget_types = ["float_entry", "float_input"] _check_widget_type = Gtk.Entry _signal = "changed" def __init__(self, *args, **kwargs): super(FloatEntryAdapter, self).__init__(*args, **kwargs) numeric_const_pattern = r""" [-+]? # optional sign (?: (?: \d* \. \d+ ) # .1 .12 .123 etc 9.1 etc 98.1 etc | (?: \d+ \.? ) # 1. 12. 123. etc 1 12 123 etc ) # followed by optional exponent part if desired (?: [Ee] [+-]? \d+ ) ? """ self.rx = re.compile(numeric_const_pattern, re.VERBOSE) def _prop_read(self, *args): return str(*args) def _prop_write(self, *args): try: return float(*args) except ValueError: return self._get_property_value() def _on_wid_changed(self, widget, *args): """Called when the widget is changed""" with self._block_widget_signal(): if self._ignoring_notifs: return self._validate_float(widget) super(FloatEntryAdapter, self)._on_wid_changed(widget, *args) def _validate_float(self, entry): entry_text = entry.get_text() newtext = self.rx.findall(entry_text) if len(newtext) > 0: entry.set_text(newtext[0]) else: entry.set_text("") pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/label_adapter.py000066400000000000000000000027741363064711000226060ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from .basic import GtkAdapter class LabelAdapter(GtkAdapter): """ An adapter for a Gtk.Label widget """ widget_types = ["label"] _check_widget_type = Gtk.Label _wid_read = lambda c, w: Gtk.Label.get_text(w) _wid_write = lambda c, w, v: Gtk.Label.set_text(w, str(v)) _signal = None pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/link_button_adapter.py000066400000000000000000000030621363064711000240460ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from .basic import GtkAdapter class LinkButtonAdapter(GtkAdapter): """ An adapter for a Gtk.LinkButton widget """ widget_types = ["link", "link_button"] _check_widget_type = Gtk.LinkButton _wid_read = GtkAdapter.static_to_class(Gtk.LinkButton.get_uri) _wid_write = GtkAdapter.static_to_class(Gtk.LinkButton.set_uri) _signal = "clicked" pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/scale_adapter.py000066400000000000000000000027751363064711000226170ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .basic import GtkAdapter from .widgets.scale_entry import ScaleEntry class ScaleEntryAdapter(GtkAdapter): """ An adapter for a ScaleEntry widget. """ widget_types = ["scale", ] _check_widget_type = ScaleEntry _wid_read = GtkAdapter.static_to_class(ScaleEntry.get_value) _wid_write = GtkAdapter.static_to_class(ScaleEntry.set_value) _signal = "changed" pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/text_view_adapter.py000066400000000000000000000061171363064711000235400ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from .basic import GtkAdapter class TextViewAdapter(GtkAdapter): """ An adapter for a TextView widget. """ widget_types = ["text_view", ] _check_widget_type = Gtk.TextView _prop_cast = False def _read_widget(self): """Returns the value currently stored into the widget.""" return str(self._buffer.get_text(*self._buffer.get_bounds())) def _write_widget(self, val): """Writes value into the widget. If specified, user setter is invoked.""" with self._ignore_notifications(): return self._buffer.set_text(val) _signal = "changed" def __init__(self, controller, prop, widget, value_error=None, spurious=False, update=True): if prop.data_type == object: # assume TextBuffer type # TODO self._buffer = self._read_property() else: # assume string type self._buffer = Gtk.TextBuffer() super(TextViewAdapter, self).__init__(controller, prop, widget, value_error=value_error, spurious=spurious, update=update) self._widget.set_buffer(self._buffer) def _connect_widget(self): """Called when the adapter is ready to connect to the widget""" # Connect the widget if self._signal: self._signal_id = self._buffer.connect( self._signal, self._on_wid_changed, self._signal_args) # Updates the widget: if self._update: self.update_widget() return def _disconnect_widget(self, widget=None): """Disconnects the widget""" if self._signal is not None and self._signal_id is not None: self._buffer.disconnect(self._signal_id) def _set_property_value(self, val): """Private method that sets the property value stored in the model, without transformations.""" return setattr(self._model, self._prop.label, val) pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/toggle_button_adapter.py000066400000000000000000000031061363064711000243710ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from .basic import GtkAdapter class ToggleButtonAdapter(GtkAdapter): """ An adapter for a Gtk.ToggleButton widget """ widget_types = ["toggle", "toggle_button"] _check_widget_type = Gtk.ToggleButton _wid_read = GtkAdapter.static_to_class(Gtk.ToggleButton.get_active) _wid_write = GtkAdapter.static_to_class(Gtk.ToggleButton.set_active) _signal = "toggled" pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/toolkit_functions.py000066400000000000000000000033501363064711000235730ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4r:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk, GLib # @UnresolvedImport def add_idle_call(func, *args): source = GLib.MainContext.default().find_source_by_id( GLib.idle_add(func, *args, priority=GLib.PRIORITY_HIGH_IDLE)) return source def remove_source(source): return source.destroy() def add_timeout_call(timeout, func, *args): source = GLib.MainContext.default().find_source_by_id( GLib.timeout_add(timeout, func, priority=GLib.PRIORITY_HIGH, *args)) return source def start_event_loop(): return Gtk.main() def stop_event_loop(): return Gtk.main_quit()PyXRD-0.8.4/mvc/adapters/gtk_support/tree_view_adapters.py000066400000000000000000000141151363064711000236730ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import logging logger = logging.getLogger(__name__) import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from ..abstract_adapter import AbstractAdapter def wrap_property_to_treemodel_type(model, prop, treemodel_type): prop_value = getattr(model, prop.label) if not isinstance(prop_value, Gtk.TreeModel): wrapper = getattr(model, "__%s_treemodel_wrapper" % prop.label, None) if wrapper is None or not wrapper.is_wrapping(model, prop.label): wrapper = treemodel_type(model, prop) setattr(model, "__%s_treemodel_wrapper" % prop.label, wrapper) prop_value = wrapper return prop_value def wrap_treenode_property_to_treemodel(model, prop): """ Convenience function that (sparsely) wraps a TreeNode property to an ObjectTreeStore. If the property is a Gtk.TreeModel instance, it returns it without wrapping. """ from .treemodels import ObjectTreeStore return wrap_property_to_treemodel_type(model, prop, ObjectTreeStore) def wrap_list_property_to_treemodel(model, prop): """ Convenience function that (sparsely) wraps a list property to an ObjectListStore. If the property is an Gtk.TreeModel instance, it returns it without wrapping. """ from .treemodels import ObjectListStore return wrap_property_to_treemodel_type(model, prop, ObjectListStore) def wrap_xydata_to_treemodel(model, prop): """ Convenience function that (sparsely) wraps an XYData model to an XYListStore. If the property is an Gtk.TreeModel instance, it returns it without wrapping. """ from .treemodels import XYListStore return wrap_property_to_treemodel_type(model, prop, XYListStore) class AbstractTreeViewAdapter(AbstractAdapter): """ Abstract base class for the ObjectTreeViewAdapter and XYTreeViewAdapter. """ toolkit = "gtk" _check_widget_type = Gtk.TreeView _signal = "changed" def __init__(self, controller, prop, widget): super(AbstractTreeViewAdapter, self).__init__(controller, prop, widget) if self._check_widget_type is not None: widget_type = type(widget) if not isinstance(widget, self._check_widget_type): raise TypeError("A '%s' can only be used for (a subclass of) a '%s' widget, and not for a '%s'!" % ( type(self), self._check_widget_type, widget_type )) self._connect_widget() def _connect_widget(self): self._widget.set_model(self._treestore) setup = getattr(self._controller, "setup_%s_tree_view" % self._prop.label, None) if callable(setup): setup(self._treestore, self._widget) else: logger.error("Could not find setup callable for tree view widget '%s' adapted to '%s'" % ( self._widget, self._prop.label )) def _disconnect_widget(self, widget=None): # TODO reset_tree_view support self._widget.set_model(None) def _connect_model(self): pass # nothing to do def _disconnect_model(self, model=None): pass # nothing to do def _read_widget(self): pass # nothing to do def _write_widget(self, val): pass # nothing to do def _read_property(self, *args): pass # nothing to do def _write_property(self, value, *args): pass # nothing to do pass # end of class class ObjectListViewAdapter(AbstractTreeViewAdapter): """ An adapter for a TreeView widget, representing a list of objects. """ widget_types = ["object_list_view", ] def __init__(self, controller, prop, widget): assert hasattr(prop, "data_type"), "ObjectTreeViewAdapter requires the " + \ "'data_type' attribute to be set on the property descriptor.\n" + \ "Controller: '%s', Model: '%s', Property: '%s'" % (controller, controller.model, prop.label) self._treestore = wrap_list_property_to_treemodel(controller.model, prop) super(ObjectListViewAdapter, self).__init__(controller, prop, widget) pass # end of class class XYListViewAdapter(AbstractTreeViewAdapter): # TODO move this back outside this namespace or move the XYData object back in here... """ An adapter for a TreeView widget, representing an XYData model. """ widget_types = ["xy_list_view", ] def __init__(self, controller, prop, widget): self._treestore = wrap_xydata_to_treemodel(controller.model, prop) super(XYListViewAdapter, self).__init__(controller, prop, widget) pass # end of class class ObjectTreeViewAdapter(AbstractTreeViewAdapter): """ An adapter for a TreeView widget, representing a tree of objects. """ widget_types = ["object_tree_view", ] def __init__(self, controller, prop, widget): self._treestore = wrap_treenode_property_to_treemodel(controller.model, prop) super(ObjectTreeViewAdapter, self).__init__(controller, prop, widget) pass # end of classPyXRD-0.8.4/mvc/adapters/gtk_support/treemodels/000077500000000000000000000000001363064711000216065ustar00rootroot00000000000000PyXRD-0.8.4/mvc/adapters/gtk_support/treemodels/__init__.py000066400000000000000000000025651363064711000237270ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .object_list_store import ObjectListStore from .base_models import BaseObjectListStore from .object_tree_store import ObjectTreeStore from .xy_list_store import XYListStore __all__ = [ "BaseObjectListStore", "ObjectListStore", "ObjectTreeStore", "XYListStore" ] PyXRD-0.8.4/mvc/adapters/gtk_support/treemodels/base_models.py000066400000000000000000000110261363064711000244350ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import weakref import types import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk # @UnresolvedImport from .generic_tree_model import GenericTreeModel class BaseObjectListStore(GenericTreeModel): """ Base mixin for creating GenericTreeModel implementations for lists of objects. It maps the columns of the store with properties of the object. If the PyGTK modules are not available (e.g. on a headless HPC cluster), a dummy gtk module is loaded. Not all (GTK) functionality is enabled in that case. """ # PROPERTIES _columns = None # list of tuples (name, type) _class_type = None __weakref_model = None @property def _model(self): if self.__weakref_model is not None: return self.__weakref_model() else: return None @_model.setter def _model(self, value): if value is not None: self.__weakref_model = weakref.ref(value) else: self.__weakref_model = None # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, class_type): GenericTreeModel.__init__(self) self.set_property("leak-references", False) if class_type is None: raise ValueError( 'Invalid class_type for %s! Expecting object, but None was given' % type(self) ) elif not hasattr(class_type, "Meta"): raise ValueError( 'Invalid class_type for `%s`! `%s` does not have a `Meta` attribute!' % (type(self), class_type) ) elif not hasattr(class_type.Meta, 'get_column_properties'): raise ValueError( 'Invalid class_type for %s! %s.Meta does not have get_column_properties method!' % (type(self), class_type) ) else: self.setup_class_type(class_type) def setup_class_type(self, class_type): self._class_type = class_type self._columns = [] for item in self._class_type.Meta.get_column_properties(): title, col_type = item if isinstance(col_type, str): col_type = 'gchararray' # TODO map other types we might encounter... self._columns.append((title, col_type)) i = 0 for col in self._columns: setattr(self, "c_%s" % col[0], i) i += 1 # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def on_get_flags(self): return Gtk.TreeModelFlags.LIST_ONLY | Gtk.TreeModelFlags.ITERS_PERSIST def on_get_n_columns(self): return len(self._columns) def on_get_column_type(self, index): return self._columns[index][1] def get_user_data_from_path(self, path): return self.on_get_iter(path) def convert(self, col, new_val): if isinstance(self._columns[col][1], str): return str(new_val) else: return self._columns[col][1](new_val) def get_objects(self): raise NotImplementedError() def iter_objects(self): raise NotImplementedError() def __reduce__(self): raise NotImplementedError() pass # end of class PyXRD-0.8.4/mvc/adapters/gtk_support/treemodels/generic_tree_model.py000066400000000000000000000342661363064711000260060ustar00rootroot00000000000000# -*- Mode: Python; py-indent-offset: 4 -*- # generictreemodel - GenericTreeModel implementation for pygtk compatibility. # Copyright (C) 2013 Simon Feltman # # generictreemodel.py: GenericTreeModel implementation for pygtk compatibility # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, see . import logging logger = logging.getLogger(__name__) # System import sys import random import collections import ctypes import platform import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import GObject # @UnresolvedImport from gi.repository import Gtk # @UnresolvedImport class _CTreeIter(ctypes.Structure): _fields_ = [('stamp', ctypes.c_int), ('user_data', ctypes.c_void_p), ('user_data2', ctypes.c_void_p), ('user_data3', ctypes.c_void_p)] @classmethod def from_iter(cls, iter): offset = sys.getsizeof(object()) # size of PyObject_HEAD return ctypes.POINTER(cls).from_address(id(iter) + offset) if platform.python_implementation() == "PyPy": def _get_user_data_as_pyobject(iter): raise NotImplementedError("Not yet supported under PyPy") else: def _get_user_data_as_pyobject(iter): citer = _CTreeIter.from_iter(iter) try: return ctypes.cast(citer.contents.user_data, ctypes.py_object).value except ValueError as err: logger.warning("Could not cast ctypes PyObject to native python object: %s" % err) return None def handle_exception(default_return): """Returns a function which can act as a decorator for wrapping exceptions and returning "default_return" upon an exception being thrown. This is used to wrap Gtk.TreeModel "do_" method implementations so we can return a proper value from the override upon an exception occurring with client code implemented by the "on_" methods. """ def decorator(func): def wrapped_func(*args, **kargs): try: return func(*args, **kargs) except: # Use excepthook directly to avoid any printing to the screen # if someone installed an except hook. sys.excepthook(*sys.exc_info()) return default_return return wrapped_func return decorator class GenericTreeModel(GObject.GObject, Gtk.TreeModel): """A base implementation of a Gtk.TreeModel for python. The GenericTreeModel eases implementing the Gtk.TreeModel interface in Python. The class can be subclassed to provide a TreeModel implementation which works directly with Python objects instead of iterators. All of the on_* methods should be overridden by subclasses to provide the underlying implementation a way to access custom model data. For the purposes of this API, all custom model data supplied or handed back through the overridable API will use the argument names: node, parent, and child in regards to user data python objects. The create_tree_iter, set_user_data, invalidate_iters, iter_is_valid methods are available to help manage Gtk.TreeIter objects and their Python object references. GenericTreeModel manages a pool of user data nodes that have been used with iters. This pool stores a references to user data nodes as a dictionary value with the key being the integer id of the data. This id is what the Gtk.TreeIter objects use to reference data in the pool. References will be removed from the pool when the model is deleted or explicitly by using the optional "node" argument to the "row_deleted" method when notifying the model of row deletion. """ leak_references = GObject.Property(default=True, type=bool, blurb="If True, strong references to user data attached to iters are " "stored in a dictionary pool (default). Otherwise the user data is " "stored as a raw pointer to a python object without a reference.") # # Methods # def __init__(self): """Initialize. Make sure to call this from derived classes if overridden.""" super(GenericTreeModel, self).__init__() self.stamp = 0 #: Dictionary of (id(user_data): user_data), used when leak-refernces=False self._held_refs = dict() # Set initial stamp self.invalidate_iters() def iter_depth_first(self): """Depth-first iteration of the entire TreeModel yielding the python nodes.""" stack = collections.deque([None]) while stack: it = stack.popleft() if it is not None: yield self.get_user_data(it) children = [self.iter_nth_child(it, i) for i in range(self.iter_n_children(it))] stack.extendleft(reversed(children)) def invalidate_iter(self, iter): """Clear user data and its reference from the iter and this model.""" iter.stamp = 0 if iter.user_data: if iter.user_data in self._held_refs: del self._held_refs[iter.user_data] iter.user_data = None def invalidate_iters(self): """ This method invalidates all TreeIter objects associated with this custom tree model and frees their locally pooled references. """ self.stamp = random.randint(-2147483648, 2147483647) self._held_refs.clear() def iter_is_valid(self, iter): """ :Returns: True if the Gtk.TreeIter specified by iter is valid for the custom tree model. """ return iter.stamp == self.stamp def get_user_data(self, iter): """Get the user_data associated with the given TreeIter. GenericTreeModel stores arbitrary Python objects mapped to instances of Gtk.TreeIter. This method allows to retrieve the Python object held by the given iterator. """ if self.leak_references: return self._held_refs[iter.user_data] else: return _get_user_data_as_pyobject(iter) def set_user_data(self, iter, user_data): """Applies user_data and stamp to the given iter. If the models "leak_references" property is set, a reference to the user_data is stored with the model to ensure we don't run into bad memory problems with the TreeIter. """ iter.user_data = id(user_data) if user_data is None: self.invalidate_iter(iter) else: iter.stamp = self.stamp if self.leak_references: self._held_refs[iter.user_data] = user_data def create_tree_iter(self, user_data): """Create a Gtk.TreeIter instance with the given user_data specific for this model. Use this method to create Gtk.TreeIter instance instead of directly calling Gtk.Treeiter(), this will ensure proper reference managment of wrapped used_data. """ iter = Gtk.TreeIter() self.set_user_data(iter, user_data) return iter def _create_tree_iter(self, data): """Internal creation of a (bool, TreeIter) pair for returning directly back to the view interfacing with this model.""" if data is None: return (False, None) else: it = self.create_tree_iter(data) return (True, it) def row_deleted(self, path, node=None): """Notify the model a row has been deleted. Use the node parameter to ensure the user_data reference associated with the path is properly freed by this model. :Parameters: path : Gtk.TreePath Path to the row that has been deleted. node : object Python object used as the node returned from "on_get_iter". This is optional but ensures the model will not leak references to this object. """ super(GenericTreeModel, self).row_deleted(path) node_id = id(node) if node_id in self._held_refs: del self._held_refs[node_id] # # GtkTreeModel Interface Implementation # @handle_exception(0) def do_get_flags(self): """Internal method.""" return self.on_get_flags() @handle_exception(0) def do_get_n_columns(self): """Internal method.""" return self.on_get_n_columns() @handle_exception(GObject.TYPE_INVALID) def do_get_column_type(self, index): """Internal method.""" return self.on_get_column_type(index) @handle_exception((False, None)) def do_get_iter(self, path): """Internal method.""" return self._create_tree_iter(self.on_get_iter(path)) @handle_exception(False) def do_iter_next(self, iter): """Internal method.""" if iter is None: next_data = self.on_iter_next(None) else: next_data = self.on_iter_next(self.get_user_data(iter)) self.set_user_data(iter, next_data) return next_data is not None @handle_exception(None) def do_get_path(self, iter): """Internal method.""" path = self.on_get_path(self.get_user_data(iter)) if path is None: return None else: return Gtk.TreePath(path) @handle_exception(None) def do_get_value(self, iter, column): """Internal method.""" return self.on_get_value(self.get_user_data(iter), column) @handle_exception((False, None)) def do_iter_children(self, parent): """Internal method.""" data = self.get_user_data(parent) if parent else None return self._create_tree_iter(self.on_iter_children(data)) @handle_exception(False) def do_iter_has_child(self, parent): """Internal method.""" return self.on_iter_has_child(self.get_user_data(parent)) @handle_exception(0) def do_iter_n_children(self, iter): """Internal method.""" if iter is None: return self.on_iter_n_children(None) return self.on_iter_n_children(self.get_user_data(iter)) @handle_exception((False, None)) def do_iter_nth_child(self, parent, n): """Internal method.""" if parent is None: data = self.on_iter_nth_child(None, n) else: data = self.on_iter_nth_child(self.get_user_data(parent), n) return self._create_tree_iter(data) @handle_exception((False, None)) def do_iter_parent(self, child): """Internal method.""" return self._create_tree_iter(self.on_iter_parent(self.get_user_data(child))) @handle_exception(None) def do_ref_node(self, iter): self.on_ref_node(self.get_user_data(iter)) @handle_exception(None) def do_unref_node(self, iter): self.on_unref_node(self.get_user_data(iter)) # # Python Subclass Overridables # def on_get_flags(self): """Overridable. :Returns Gtk.TreeModelFlags: The flags for this model. See: Gtk.TreeModelFlags """ raise NotImplementedError def on_get_n_columns(self): """Overridable. :Returns: The number of columns for this model. """ raise NotImplementedError def on_get_column_type(self, index): """Overridable. :Returns: The column type for the given index. """ raise NotImplementedError def on_get_iter(self, path): """Overridable. :Returns: A python object (node) for the given TreePath. """ raise NotImplementedError def on_iter_next(self, node): """Overridable. :Parameters: node : object Node at current level. :Returns: A python object (node) following the given node at the current level. """ raise NotImplementedError def on_get_path(self, node): """Overridable. :Returns: A TreePath for the given node. """ raise NotImplementedError def on_get_value(self, node, column): """Overridable. :Parameters: node : object column : int Column index to get the value from. :Returns: The value of the column for the given node.""" raise NotImplementedError def on_iter_children(self, parent): """Overridable. :Returns: The first child of parent or None if parent has no children. If parent is None, return the first node of the model. """ raise NotImplementedError def on_iter_has_child(self, node): """Overridable. :Returns: True if the given node has children. """ raise NotImplementedError def on_iter_n_children(self, node): """Overridable. :Returns: The number of children for the given node. If node is None, return the number of top level nodes. """ raise NotImplementedError def on_iter_nth_child(self, parent, n): """Overridable. :Parameters: parent : object n : int Index of child within parent. :Returns: The child for the given parent index starting at 0. If parent None, return the top level node corresponding to "n". If "n" is larger then available nodes, return None. """ raise NotImplementedError def on_iter_parent(self, child): """Overridable. :Returns: The parent node of child or None if child is a top level node.""" raise NotImplementedError def on_ref_node(self, node): pass def on_unref_node(self, node): passPyXRD-0.8.4/mvc/adapters/gtk_support/treemodels/object_list_store.py000066400000000000000000000136651363064711000257100ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import logging logger = logging.getLogger(__name__) import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import GObject # @UnresolvedImport from ....observers import ListObserver, ListItemObserver from .base_models import BaseObjectListStore from weakref import WeakKeyDictionary class ObjectListStore(BaseObjectListStore): """ GenericTreeModel implementation that wraps a python list of mvc model objects. In addition, it expects all objects to be of a certain type, which needs to be passed to the __init__ as the first argument.This way, the wrapper can inspect the type and find out what properties can be represented as columns and report this to Gtk. """ # PROPERTIES: _deleted_paths = None @property def _data(self): if self._model is not None: return getattr(self._model, self._prop_name, None) else: return [] def is_wrapping(self, model, prop_name): return self._model == model and self._prop_name == prop_name # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, model, prop): BaseObjectListStore.__init__(self, prop.data_type) self._model = model self._prop_name = prop.label self._deleted_paths = [] self._observer = ListObserver( self.on_item_inserted, self.on_item_deleted, on_deleted_before=self.on_item_deleted_before, prop_name=self._prop_name, model=self._model ) self._list_item_observers = WeakKeyDictionary() for item in self._data: self._observe_item(item) def _observe_item(self, item): obs = ListItemObserver(self.on_item_changed, model=item) self._list_item_observers[item] = obs def _unobserve_item(self, item): observer = self._list_item_observers.get(item, None) if observer is not None: observer.clear() def on_item_changed(self, item): itr = self.create_tree_iter(item) path = self.get_path(itr) try: self.row_changed(path, itr) except TypeError as err: err.args += ("when emitting row_changed using:", path, itr) raise def on_item_inserted(self, item): try: itr = self.create_tree_iter(item) path = self.get_path(itr) self._observe_item(item) self.row_inserted(path, itr) except ValueError: logger.debug("Invalid rowref passed: %s", item) pass # invalid rowref def on_item_deleted_before(self, item): self._unobserve_item(item) self._deleted_paths.append((self._data.index(item),)) def on_item_deleted(self, item): for path in self._deleted_paths: self.row_deleted(path) self._deleted_paths = [] # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def on_get_iter(self, path): try: return self._data[path[0]] except IndexError: return None def on_get_path(self, rowref): try: return (self._data.index(rowref),) except ValueError: logger.exception("ValueError in on_get_path of %s caused by %s" % (self, rowref)) def set_value(self, itr, column, value): user_data = self.get_user_data(itr) setattr(user_data, self._columns[column][0], value) self.row_changed(self.get_path(itr), itr) def on_get_value(self, rowref, column): value = getattr(rowref, self._columns[column][0]) try: default = self._columns[column][1]() except TypeError: default = "" return value if value is not None else default def on_iter_next(self, rowref): n, = self.on_get_path(rowref) try: return self._data[n + 1] except IndexError: pass def on_iter_children(self, rowref): if rowref: return None if self._data: return self.on_get_iter((0,)) return None def on_iter_has_child(self, rowref): if rowref: return False if len(self._data) > 0: return True return False def on_iter_n_children(self, rowref): if rowref: return 0 return len(self._data) def on_iter_nth_child(self, parent, n): if parent: return None if n < 0 or n >= len(self._data): return None return self._data[n] def on_iter_parent(self, rowref): return None pass # end of class GObject.type_register(ObjectListStore) # @UndefinedVariable PyXRD-0.8.4/mvc/adapters/gtk_support/treemodels/object_tree_store.py000066400000000000000000000137121363064711000256650ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from traceback import print_exc import logging logger = logging.getLogger(__name__) import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk, GObject # @UnresolvedImport from .base_models import BaseObjectListStore from ....observers import TreeObserver class ObjectTreeStore(BaseObjectListStore): """ GenericTreeModel implementation that holds a tree with objects. It expects all objects to be of a certain type, which needs to be passed to the __init__ as the first argument. """ # PROPERTIES: _object_node_map = None @property def _root_node(self): return getattr(self._model, self._prop_name, None) def is_wrapping(self, model, prop_name): return self._model == model and self._prop_name == prop_name # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, model, prop): _root = getattr(model, prop.label, None) # Then continue: try: BaseObjectListStore.__init__(self, prop.data_type) except ValueError as err: msg = "ValueError (%r) was raised when initializing ObjectTreeStore for model '%s' and data type '%s'" % (err, model, prop.data_type) msg += "\n Did you forget to set the data_type on the list property '%s'?" % prop.label raise ValueError(msg) self._model = model self._prop_name = prop.label self._object_node_map = dict() self._observer = TreeObserver( self.on_item_inserted, self.on_item_deleted, on_deleted_before=self.on_item_deleted_before, prop_name=self._prop_name, model=self._model ) def on_item_inserted(self, item): try: itr = self.create_tree_iter(item) path = self.get_path(itr) # self._observe_item(item) self.row_inserted(path, itr) except ValueError: logger.debug("Invalid rowref passed: %s", item) pass # invalid rowref _deleted_paths = [] def on_item_deleted_before(self, item): # self._unobserve_item(item) self._deleted_paths.append(self.on_get_path(item)) def on_item_deleted(self, item): for path in self._deleted_paths: self.row_deleted(path) self._deleted_paths = [] # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def on_get_flags(self): return Gtk.TreeModelFlags.ITERS_PERSIST def on_get_iter(self, path): try: if hasattr(path, 'split'): path = list(map(int, path.split(":"))) return self._root_node.get_child_node(*path) except IndexError as err: err.args = "IndexError in on_get_iter of %s caused by %s" % (self, path) print_exc() return None def on_get_path(self, node): try: return ":".join(map(str, node.get_indices())) except ValueError as err: err.args = "ValueError in on_get_path of %s caused by %s" % (self, node) print_exc() return None def set_value(self, itr, column, value): user_data = self.get_tree_node_object(itr) setattr(user_data, self._columns[column][0], value) self.row_changed(self.get_path(itr), itr) def on_get_value(self, node, column): try: return getattr(node.object, self._columns[column][0]) except: return "" def on_iter_next(self, node): return node.get_next_node() def on_iter_children(self, node): node = node or self._root_node return node.get_first_child_node() def on_iter_has_child(self, node): node = node or self._root_node return node.has_children def on_iter_n_children(self, node): node = node or self._root_node return node.child_count def on_iter_nth_child(self, parent, n): node = parent or self._root_node try: return node.get_child_node(n) except: return None def on_iter_parent(self, node): return node.parent def iter_objects(self): for node in self._root_node.iter_children(): yield node.object def get_tree_node(self, itr): return BaseObjectListStore.get_user_data(self, itr) def get_tree_node_from_path(self, path): return BaseObjectListStore.get_user_data_from_path(self, path) def get_tree_node_object(self, itr): return self.get_tree_node(itr).object def get_tree_node_object_from_path(self, path): return self.get_tree_node_from_path(path).object pass # end of class GObject.type_register(ObjectTreeStore) # @UndefinedVariable PyXRD-0.8.4/mvc/adapters/gtk_support/treemodels/utils.py000066400000000000000000000072151363064711000233250ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk # @UnresolvedImport import os import csv ################################################################################ # Array-like item repositioning: ################################################################################ def repos(ln, old_pos, new_pos): """ Return a new list in which each item contains the index of the item in the old order. Uses the ranges approach (best for large arrays). """ lb = min(new_pos, old_pos) ub = max(new_pos, old_pos) adj_range = [] if new_pos < old_pos: adj_range.append(old_pos) adj_range += list(range(lb, ub)) else: adj_range += list(range(lb + 1, ub + 1)) adj_range.append(old_pos) return list(range(0, lb)) + adj_range + list(range(ub, ln - 1)) def simple_repos(ln, old_pos, new_pos): """ Return a new list in which each item contains the index of the item in the old order. Uses the delete/insert approach (best for small arrays). """ r1 = list(range(ln)) val = r1[old_pos] del r1[old_pos] r1.insert(new_pos, val) return r1 def smart_repos(ln, old_pos, new_pos): """ Return a new list in which each item contains the index of the item in the old order. Decides which algorithm to use based on the size of the arrays. """ if ln > 65: return repos(ln, old_pos, new_pos) else: return simple_repos(ln, old_pos, new_pos) ################################################################################ # Treestore creation from filesystem ################################################################################ def create_valuestore_from_file(filename, data_type=float): liststore = Gtk.ListStore(str, data_type) with open(filename, 'r') as f: reader = csv.reader(f) next(reader) # skip header for row in reader: row[1] = data_type(row[1]) liststore.append(row) return liststore def create_treestore_from_directory(directory): treestore = Gtk.TreeStore(str, str, bool) treestore.append(None, ("", "", True)) parents = {} for root, dirnames, filenames in os.walk(directory): for dirname in dirnames: parents[os.path.join(root, dirname)] = treestore.append(parents.get(root, None), (dirname, "", False)) for filename in filenames: treestore.append(parents.get(root, None), (os.path.splitext(filename)[0], "%s/%s" % (root, filename), True)) return treestore PyXRD-0.8.4/mvc/adapters/gtk_support/treemodels/xy_list_store.py000066400000000000000000000167131363064711000250770ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from collections import namedtuple from ....observers import Observer from ....models.xydata import XYData import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk, GLib, GObject # @UnresolvedImport from .base_models import BaseObjectListStore class PointMeta(): @classmethod def get_column_properties(cls): return [ ('x', float), ('y', float) ] Point = namedtuple('Point', ['x', 'y']) Point.Meta = PointMeta class XYListStore(BaseObjectListStore, Observer): """ GenericTreeModel implementation that wraps an XYData model. """ _model = None _prop_name = None _last_lenght = 0 __gsignals__ = { 'columns-changed' : (GObject.SignalFlags.RUN_LAST, None, ()) # @UndefinedVariable } @property def _data(self): return getattr(self._model, self._prop_name, None) def is_wrapping(self, model, prop_name): return self._model == model and self._prop_name == prop_name # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def __init__(self, model, prop): # Continue initialisation: BaseObjectListStore.__init__(self, Point) # Check this really is an XYData property: self._flush() self._model = model self._prop_name = prop.label _data = getattr(self._model, self._prop_name, None) assert isinstance(_data, XYData), \ "Can only wrap XYData (or subclasses) instances to a XYListStore," + \ "but got '%s' instead from property '%s' on model '%s'." % ( _data, self._prop_name, self._model) Observer.__init__(self, model=self._data) self.set_property("leak-references", False) self._last_length = len(self) self._last_num_col = self._data.num_columns # Force update: self._emit_update() @Observer.observe("data_changed", signal=True) def on_data_changed(self, model, name, info): if model == self._data: self._emit_update() def _emit_update(self): # Invalidate iters, we're (probably) changing stuff: self._schedule_flush() # 1. check if number of columns has changed since last update # if it has changed, emit the corresponding event if self._last_num_col != self._data.num_columns: self.emit("columns-changed") self._last_num_col = self._data.num_columns # 2. check if length has changed, if shorter emit removed signals # for the lost elements, if longer emit insert signals row_diff = len(self._data) - self._last_length if row_diff > 0: for i in range(self._last_length, self._last_length + row_diff, 1): path = self.on_get_path(i) itr = self.get_iter(path) self.row_inserted(path, itr) elif row_diff < 0: for i in range(self._last_length, self._last_length + row_diff - 1, -1): path = self.on_get_path(i) self.row_deleted(path) self._last_length = len(self._data) # 3. Emit row-changed signals for all other rows: for i in range(0, len(self._data)): path = self.on_get_path(i) itr = self.get_iter(path) self.row_changed(path, itr) # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def on_get_flags(self): return Gtk.TreeModelFlags.LIST_ONLY def on_get_iter(self, path): # returns a rowref, they're actually just paths if hasattr(path, "get_indices"): path = path.get_indices() sp = ":".join(map(lambda i: "%d" % i, path)) if not sp in self._cache: try: i = path[0] if i >= 0 and i < len(self): self._cache[sp] = [i, ] except IndexError: pass return self._cache.get(sp, None) self._schedule_flush() return def _schedule_flush(self): if not self._flush_scheduled: def idle_add(): GLib.idle_add(self._flush) return False # delete timeout GLib.timeout_add(500, idle_add) self._flush_scheduled = True def _flush(self): self.invalidate_iters() self._cache = {} # del _cache - keep no ref to this dict self._flush_scheduled = False return False # In case we are called from idle signal def on_get_value(self, rowref, column): if column == self.c_x: return self._data.data_x[rowref[0]] elif column >= self.c_y: return self._data.data_y[rowref[0], column - 1] else: raise AttributeError def on_get_path(self, rowref): # rowrefs are paths, unless they're None if rowref is None: return None if isinstance(rowref, tuple): return rowref elif isinstance(rowref, list): return tuple(rowref) else: return rowref, def on_iter_next(self, rowref): if rowref is not None: itr = self.on_get_iter((rowref[0] + 1,)) return itr else: return None def on_iter_children(self, rowref): if rowref is not None: return None elif len(self) > 0: return self.on_get_iter((0,)) return None def on_iter_has_child(self, rowref): if rowref is not None: return False elif len(self) > 0: return True return False def on_iter_n_children(self, rowref): if rowref is not None: return 0 return len(self) def on_iter_nth_child(self, rowref, n): if rowref is not None: return None if n < 0 or n >= len(self): return None return self.on_get_iter((n,)) def on_iter_parent(self, rowref): return None def on_get_n_columns(self): return self._data.num_columns def on_get_column_type(self, index): return float def __len__(self): return self._data.size pass # end of class GObject.type_register(XYListStore) # @UndefinedVariable PyXRD-0.8.4/mvc/adapters/gtk_support/widgets/000077500000000000000000000000001363064711000211115ustar00rootroot00000000000000PyXRD-0.8.4/mvc/adapters/gtk_support/widgets/__init__.py000066400000000000000000000000441363064711000232200ustar00rootroot00000000000000 from .scale_entry import ScaleEntryPyXRD-0.8.4/mvc/adapters/gtk_support/widgets/scale_entry.py000066400000000000000000000122401363064711000237720ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et: # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk, GObject, GLib # @UnresolvedImport from mvc.support.utils import round_sig class ScaleEntry(Gtk.HBox): """ The ScaleEntry combines the generic GtkEntry and GtkScale widgets in one widget, with synchronized values and one changed signal. """ __gsignals__ = { 'changed' : (GObject.SignalFlags.RUN_LAST, None, []), #@UndefinedVariable } @property def lower(self): return self.adjustment.get_lower() @lower.setter def lower(self, value): return self.adjustment.set_lower(value) @property def upper(self): return self.adjustment.get_upper() @upper.setter def upper(self, value): return self.adjustment.set_upper(value) def __init__(self, lower=0, upper=10, enforce_range=False): GObject.GObject.__init__(self, spacing=5) self.enforce_range = enforce_range if lower == None: lower = 0 if upper == None: upper = 10 lower = min(upper, lower) upper = max(upper, lower) step = max((upper - lower) / 200.0, 0.01) self.adjustment = Gtk.Adjustment( 0.0, lower, upper, step, step, 0.0) self.adjustment.connect('value-changed', self.on_adj_value_changed) self.scale = Gtk.Scale.new(Gtk.Orientation.HORIZONTAL, self.adjustment) self.scale.set_draw_value(False) self.scale.set_size_request(50, -1) self.entry = Gtk.SpinButton() self.entry.set_adjustment(self.adjustment) self.entry.set_digits(5) self.entry.set_numeric(True) self.entry.set_size_request(150, -1) self.set_value(self.scale.get_value()) Gtk.HBox.pack_start(self, self.scale, False, True, 0) Gtk.HBox.pack_start(self, self.entry, False, True, 0) self.set_focus_chain((self.entry,)) _idle_changed_id = None def _idle_emit_changed(self): if self._idle_changed_id is not None: GLib.source_remove(self._idle_changed_id) self._idle_changed_id = GLib.idle_add(self._emit_changed) def _emit_changed(self): self.emit('changed') self._idle_changed_id = None; def on_adj_value_changed(self, adj, *args): self._idle_emit_changed() def _update_adjustment(self, lower, upper): step = round_sig(max((upper - lower) / 200.0, 0.0005)) self.adjustment.configure(lower, upper, step, step, 0.0) def _update_range(self, value): lower, upper = self.lower, self.upper if not self.enforce_range: if value < (lower + abs(lower) * 0.05): lower = value - abs(value) * 0.2 if value > (upper - abs(lower) * 0.05): upper = value + abs(value) * 0.2 self._update_adjustment(lower, upper) def set_value(self, value): self._update_range(value) self.adjustment.set_value(value) def get_value(self): return self.adjustment.get_value() def get_children(self, *args, **kwargs): return [] def add(self, *args, **kwargs): raise NotImplementedError def add_with_properties(self, *args, **kwargs): raise NotImplementedError def child_set(self, *args, **kwargs): raise NotImplementedError def child_get(self, *args, **kwargs): raise NotImplementedError def child_set_property(self, *args, **kwargs): raise NotImplementedError def child_get_property(self, *args, **kwargs): raise NotImplementedError def remove(self, *args, **kwargs): raise NotImplementedError def set_child_packing(self, *args, **kwargs): raise NotImplementedError def query_child_packing(self, *args, **kwargs): raise NotImplementedError def reorder_child(self, *args, **kwargs): raise NotImplementedError def pack_start(self, *args, **kwargs): raise NotImplementedError def pack_end(self, *args, **kwargs): raise NotImplementedError pass # end of class GObject.type_register(ScaleEntry) #@UndefinedVariable PyXRD-0.8.4/mvc/adapters/gtk_support/widgets/threaded_task_box.py000066400000000000000000000074431363064711000251450ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Author: Mathijs Dumon # ThreadedTaskBox based on code from Rick Spencer # This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License. # To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ or send # a letter to Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA. import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GObject from mvc.support.cancellable_thread import CancellableThread from mvc.support.gui_loop import add_timeout_call, remove_timeout_call,\ run_when_idle class ThreadedTaskBox(Gtk.Table): """ ThreadedTaskBox: encapsulates a spinner, label and a cancel button for threaded tasks. """ __gsignals__ = { 'cancelrequested' : (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT,)), #@UndefinedVariable 'stoprequested' : (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT,)) #@UndefinedVariable } def __init__(self, cancelable=True, stoppable=False): """ Create a ThreadedTaskBox Keyword arguments: cancelable -- optional value to determine whether to show cancel button. Defaults to True. stoppable -- optional value to determine whether to show the stop button. Default to False. """ super(ThreadedTaskBox, self).__init__() self.setup_ui(cancelable=cancelable, stoppable=stoppable) def setup_ui(self, cancelable=True, stoppable=False): GObject.GObject.__init__(self, 3, 3) self.set_row_spacings(10) self.set_col_spacings(10) self.descrlbl = Gtk.Label(label="Status:") self.descrlbl.show() self.attach(self.descrlbl, 0, 3, 0, 1, xoptions=Gtk.AttachOptions.FILL, yoptions=0) self.spinner = Gtk.Spinner() self.spinner.show() self.attach(self.spinner, 0, 1, 1, 3, xoptions=0, yoptions=0) self.label = Gtk.Label() self.label.show() self.attach(self.label, 1, 2, 1, 3, xoptions=Gtk.AttachOptions.FILL, yoptions=0) self.cancel_button = Gtk.Button(stock=Gtk.STOCK_CANCEL) self.cancel_button.set_sensitive(False) self.cancel_button.connect("clicked", self.__cancel_clicked) if cancelable: self.attach(self.cancel_button, 2, 3, 1, 2, xoptions=0, yoptions=0) self.stop_button = Gtk.Button(stock=Gtk.STOCK_STOP) self.stop_button.set_sensitive(False) self.stop_button.connect("clicked", self.__stop_clicked) if stoppable: self.attach(self.stop_button, 2, 3, 2, 3, xoptions=0, yoptions=0) self.set_no_show_all(False) self.set_visible(True) self.show_all() def start(self): # Start the spinner self.spinner.start() # Enable the buttons so the user can try to cancel the task self.cancel_button.set_sensitive(True) self.stop_button.set_sensitive(True) def set_status(self, caption): self.label.set_text(caption) def stop(self, join=False, cancel=False): """ Stops spinning the spinner and emits the correct event. """ # disable the cancel button since the task is about to be told to stop self.cancel_button.set_sensitive(False) self.stop_button.set_sensitive(False) if cancel: self.emit("cancelrequested", self) else: self.emit("stoprequested", self) self.spinner.stop() self.label.set_text("Done") def cancel(self): self.stop(cancel=True) def __cancel_clicked(self, widget): self.cancel() def __stop_clicked(self, widget): self.stop() pass # end of class GObject.type_register(ThreadedTaskBox) #@UndefinedVariable PyXRD-0.8.4/mvc/adapters/metaclasses.py000066400000000000000000000027761363064711000177540ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .adapter_registry import AdapterRegistry class MetaAdapter(type): """ A meta class for adapter classes that automatically registers them in the global 'adapter_registry'. """ def __new__(cls, clsname, bases, attrs): newclass = super(MetaAdapter, cls).__new__(cls, clsname, bases, attrs) AdapterRegistry.register(newclass) return newclass pass # end of classPyXRD-0.8.4/mvc/adapters/model_adapter.py000066400000000000000000000201551363064711000202370ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from contextlib import contextmanager from ..support.utils import not_none from ..observers import Observer from ..models import Model from ..models.properties import LabeledProperty from .abstract_adapter import AbstractAdapter class ModelAdapter(Observer, AbstractAdapter): """ Model-side implementation of the _AbstractAdapter interface. """ # Mode-side property handling: _prop_read = None _prop_write = None _value_error = None @property def property_name(self): """Returns the property name the adapter is connected to""" return self._prop.label _ignoring_notifs = False @contextmanager def _ignore_notifications(self): """Context manager to temporarily (and exception-safe) ignore property changed notifications (e.g. when we're setting the model from the widget and vice versa)""" self._ignoring_notifs = True yield self._ignoring_notifs = False def __init__(self, *args, **kwargs): """ Abstract class that implements the model side of the adapter. For a fully implemented class check the corresponding widget toolkit modules. This class handles only assignments to properties. For other kinds of setting (e.g. user-defined classes used as observable properties, containers, etc.) use other types of Adapters derived from this class. Controller is the controller creating this Adapter. Prop is either a :class:`~..models.properties.LabeledProperty` instance taken from the controller's model, or a 'dotted string' describing a property from the controller's model. E.g. it is possible to pass: a.b.c which will then result in property c from attribute b from attribute a from the controller's model to be observed. This way nested properties can also be handled. prop_{read, write} are two optional functions that apply custom modifications to the value of the property after reading and before setting it on the model (e.g. transforming a float to a string and vice-versa). Both take a value and must return a transformed value. value_error can be a function to be called when a ValueError exception occurs while trying to set an invalid value for the property on the model. The function will receive: the adapter, the property name and the value coming from the widget that offended the model. spurious controls if the adapter should receive spurious changes from the model (see spuriousness in class Observer for further information). """ controller = args[0] prop = args[1] widget = args[2] prop_read = kwargs.pop("prop_read", None) prop_write = kwargs.pop("prop_write", None) value_error = kwargs.pop("value_error", None) spurious = kwargs.get("spurious", False) # First parse (optional) property strings: prop, self._model = self._parse_prop(prop, controller.model) # Call base __init__'s: super(ModelAdapter, self).__init__(*args, **kwargs) # Mode-side property handling: self._prop_read = not_none(prop_read, self._prop_read) self._prop_write = not_none(prop_write, self._prop_write) self._value_error = not_none(value_error, self._value_error) # Connect the model self._connect_model() return # ---------------------------------------------------------------------- # Model connecting & disconnecting: # ---------------------------------------------------------------------- def _parse_prop(self, prop, model): """Parses (optional) prop strings for the given model""" if not isinstance(prop, LabeledProperty): parts = prop.split(".") if len(parts) > 1: # identifies the model models = parts[:-1] for name in models: model = getattr(model, name) if not isinstance(model, Model): raise TypeError( "Attribute '%s' was expected to be a " + "Model, but found: '%s'" % (name, model)) prop = getattr(type(model), parts[-1]) else: prop = parts[0] return prop, model def _connect_model(self): """Used internally to connect the property into the model, and register self as a value observer for that property""" # prop is inside model? if not hasattr(self._model, self._prop.label): raise ValueError("Attribute '" + self._prop.label + "' not found in model " + str(self._model)) # is it observable? if self._prop.observable: self.observe(self._on_prop_changed, self._prop.label, assign=True) self.observe_model(self._model) def _on_prop_changed(self, *args, **kwargs): """Called by the observation code, when the value in the observed property is changed""" if not self._ignoring_notifs: self.update_widget() def _disconnect_model(self, model=None): # disconnects the model model = not_none(self._model, model) if model is not None: self.relieve_model(self._model) self._model = None # ---------------------------------------------------------------------- # Model-side reading and writing # ---------------------------------------------------------------------- def _get_property_value(self): """Private method that returns the property value currently stored in the model, without transformations.""" return getattr(self._model, self._prop.label) def _set_property_value(self, val): """Private method that sets the property value stored in the model, without transformations.""" return setattr(self._model, self._prop.label, val) def _read_property(self, *args): """Returns the (possibly transformed) property value stored in the model""" if self._prop_read: return self._prop_read(self._get_property_value(*args)) return self._get_property_value(*args) def _write_property(self, value, *args): """Sets the value of property. The given value is transformed using the prop_write function passed to the constructor. An attempt at casting the value to the property type is also made.""" raw_value = value try: # transform if needed: if self._prop_write: value = self._prop_write(value) # set the property, ignore updates: with self._ignore_notifications(): self._set_property_value(value, *args) except ValueError: # let the user handle the error if he wants that: if self._value_error: self._value_error(self, self._prop.label, raw_value) else: raise pass # end of class PyXRD-0.8.4/mvc/controller.py000066400000000000000000000336771363064711000160340ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import logging from mvc.support.gui_loop import add_idle_call logger = logging.getLogger(__name__) from .observers import Observer from .support.exceptions import TooManyCandidatesError class Controller(Observer): auto_adapt = True auto_adapt_included = None auto_adapt_excluded = None register_lazy = True __adapters = None __parsed_user_props = None _controller_scope_aplied = False @property def __user_props(self): assert not (self.auto_adapt_included is not None and self.auto_adapt_excluded is not None), \ "Controller '%s' has set both auto_adapt_included and auto_adapt_excluded!" % self assert self.model is not None, \ "Controller '%s' has None as model! Did you forget to pass it as a keyword argument?" % self if not self._controller_scope_aplied: props = [prop.label for prop in self.model.Meta.all_properties] if self.auto_adapt_included is not None: self.__parsed_user_props = self.__parsed_user_props.union(set([p for p in props if p not in self.auto_adapt_included])) elif self.auto_adapt_excluded is not None: self.__parsed_user_props = self.__parsed_user_props.union(set([p for p in props if p in self.auto_adapt_excluded])) self._controller_scope_aplied = True return self.__parsed_user_props @__user_props.setter def __user_props(self, value): return # ignore self.__parsed_user_props = set(value) _model = None def _get_model(self): return self._model def _set_model(self, model): if self._model is not None: self._clear_adapters() self.relieve_model(self._model) self._model = model if self._model is not None: self.observe_model(self._model) if self.view is not None: self.register_adapters() if self.auto_adapt: self.adapt() def _del_model(self): del self._model self._model = None model = property(_get_model, _set_model, _del_model) __view = None def _get_view(self): return self.__view def _set_view(self, view): if self.__view != view: self.__view = view if self.__view is not None: if self.register_lazy: add_idle_call(self._idle_register_view, self.__view) else: self._idle_register_view(self.__view) def _del_view(self): self.__view = None view = property(_get_view, _set_view, _del_view, "This controller's view") def __init__(self, *args, **kwargs): """ Two positional and two optional keyword arguments. *model* will have the new instance registered as an observer. It is made available as an attribute. *view* may contain signal connections loaded from XML. The handler methods have to exist in this class. *spurious* denotes whether notifications in this class will be called if a property of *model* is set to the same value it already has. *auto_adapt* denotes whether to call :meth:`adapt` with no arguments as part of the view registration process. View registration consists of connecting signal handlers, :meth:`register_view` and :meth:`register_adapters`, and is scheduled with the GTK main loop. It happens as soon as possible but after the constructor returns. When it starts *view* is available as an attribute. Subclasses can also override either the `auto_adapt_included` or `auto_adapt_excluded` class attributes. These should be either None or contain a list of properties to exclude (or include) from auto adaptation for this controller. If not specified, all properties are adapted if auto_adapt is True. """ # set of properties explicitly adapted by the user: self.__adapters = [] self.__parsed_user_props = set() # Some general keyword arguments: self.parent = kwargs.pop("parent", None) self.auto_adapt = kwargs.pop("auto_adapt", self.auto_adapt) self.register_lazy = kwargs.pop("register_lazy", self.register_lazy) # Pop the view, keep the model as we pass it down to the observer _view = kwargs.pop("view", None) _model = kwargs.get("model", None) # Init base classes super(Controller, self).__init__(*args, **kwargs) # Set model (will register controller as observer) self.model = _model # Set view (will register & adapt to view) self.view = _view return def _idle_register_view(self, view): """Internal method that calls register_view""" assert(self.view is not None) if self.model is not None: self.__autoconnect_signals() self.register_view(self.view) self.register_adapters() if self.auto_adapt: self.adapt() return False else: return True def _clear_adapters(self): """Clears & disconnects all adapters from this controller""" if self.__adapters: for ad in self.__adapters: ad.disconnect() self.__adapters[:] = [] def register_view(self, view): """ This does nothing. Subclasses can override it to connect signals manually or modify widgets loaded from XML, like adding columns to a TreeView. No super call necessary. *view* is a shortcut for ``self.view``. """ assert(self.model is not None) assert(self.view is not None) return def register_adapters(self): """ This does nothing. Subclasses can override it to create adapters. No super call necessary. """ assert(self.model is not None) assert(self.view is not None) return def adapt(self, *args): """ There are four ways to call this: .. method:: adapt() :noindex: Take properties from the model for which ``adapt`` has not yet been called, match them to the view by name, and create adapters fitting for the respective widget type. That information comes from :mod:`mvc.adapters.default`. See :meth:`_find_widget_match` for name patterns. .. versionchanged:: 1.99.1 Allow incomplete auto-adaption, meaning properties for which no widget is found. .. method:: adapt(ad) :noindex: Keep track of manually created adapters for future ``adapt()`` calls. *ad* is an adapter instance already connected to a widget. .. method:: adapt(prop_name) :noindex: Like ``adapt()`` for a single property. *prop_name* is a string. .. method:: adapt(prop_name, wid_name) :noindex: Like ``adapt(prop_name)`` but without widget name matching. *wid_name* has to exist in the view. """ # checks arguments n = len(args) if n not in list(range(3)): raise TypeError("adapt() takes 0, 1 or 2 arguments (%d given)" % n) if n == 0: adapters = [] props = self.model.Meta.get_viewable_properties() # matches all properties not previously adapted by the user: for prop in [p for p in props if p.label not in self.__user_props]: try: wid_name = self._find_widget_match(prop) except TooManyCandidatesError as e: # multiple candidates, gives up raise e except ValueError as e: # no widgets found for given property, continue after emitting a warning if e.args: logger.warn(e[0]) else: logger.warn("No widget candidates match property '%s'" % prop.label) else: logger.debug("Auto-adapting property %s and widget %s" % \ (prop.label, wid_name)) adapters += self.__create_adapters__(prop, wid_name) pass pass elif n == 1: # one argument from .adapters import AbstractAdapter if isinstance(args[0], AbstractAdapter): adapters = (args[0],) elif isinstance(args[0], str): prop = getattr(type(self.model), args[0]) wid_name = self._find_widget_match(prop) adapters = self.__create_adapters__(prop, wid_name) pass else: raise TypeError("Argument of adapt() must be either an Adapter or a string") else: # two arguments if not (isinstance(args[0], str) and isinstance(args[1], str)): raise TypeError("Arguments of adapt() must be two strings") # retrieves both property and widget, and creates an adapter prop_name, wid_name = args #@UnusedVariable adapters = self.__create_adapters__(prop, wid_name) pass for ad in adapters: self.__adapters.append(ad) # remember properties added by the user if n > 0: self.__user_props.add(ad.get_property_name()) pass return def _find_widget_match(self, prop): """ Checks if the view has defined a 'widget_format' attribute (e.g. "view_%s") If so, it uses this format to search for the widget in the view, if not it takes the *first* widget with a name ending with the property name. """ widget_name = None widget_format = getattr(self.view, 'widget_format', "%s") if widget_format: widget_name = widget_format % prop.label widget = self.view[widget_name] if widget is None: # not in view if prop is not None and prop.widget_type == 'scale': self.view.add_scale_widget(prop) else: widget_name = None else: for wid_name in self.view: if wid_name.lower().endswith(prop.label.lower()): widget_name = wid_name break if widget_name == None: logger.setLevel(logging.INFO) raise ValueError("No widget candidates match property '%s'" % prop.label) return widget_name # performs Controller's signals auto-connection: def __autoconnect_signals(self): """This is called during view registration, to autoconnect signals in glade file with methods within the controller""" dic = {} for name in dir(self): method = getattr(self, name) if (not callable(method)): continue assert(name not in dic) # not already connected! dic[name] = method pass # Auto connect builder if available: if self.view._builder is not None: self.view._builder.connect_signals(dic) pass return def _get_handler_list(self): from .adapters import AdapterRegistry # Add default widget handlers: local_handlers = {} adapter_registry = AdapterRegistry.get_selected_adapter_registry() local_handlers.update(adapter_registry) # Override with class instance widget handlers: for widget_type, handler in self.widget_handlers.items(): if isinstance(handler, str): self.widget_handlers[widget_type] = getattr(self, handler) local_handlers.update(self.widget_handlers) return local_handlers def __create_adapters__(self, prop, wid_name): """ Private service that looks at property and widgets types, and possibly creates one or more (best) fitting adapters that are returned as a list. """ try: logger.debug("Adapting property %s to widget names '%s'" % (prop.label, wid_name)) if prop.visible: wid = self.view[wid_name] if wid == None: raise ValueError("Widget '%s' not found in view '%s' by controller '%s'" % (wid_name, self.view, self)) local_handlers = self._get_handler_list() handler = local_handlers.get(prop.widget_type) ad = handler(self, prop, wid) return [ad] else: return [] except BaseException as error: raise RuntimeError("Unhandled error in '%s'.__create_adapters__ for property '%s' and widget '%s'!" % (type(self), prop.label, wid_name)) from error pass # end of class Controller PyXRD-0.8.4/mvc/models/000077500000000000000000000000001363064711000145425ustar00rootroot00000000000000PyXRD-0.8.4/mvc/models/__init__.py000066400000000000000000000023501363064711000166530ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import logging logger = logging.getLogger(__name__) from .base import Model from .treenode import TreeNode __all__ = [ "Model", "TreeNode" ] PyXRD-0.8.4/mvc/models/base.py000066400000000000000000000524431363064711000160360ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import inspect import logging from mvc.support.gui_loop import add_idle_call logger = logging.getLogger(__name__) from weakref import WeakKeyDictionary try: import threading as threading except ImportError: import dummy_threading as threading try: from fastrlock.rlock import FastRLock as RLock except ImportError: from threading import RLock from ..support.collections.weak_list import WeakList from ..support.observables import ObsWrapperBase, Signal from ..observers import Observer, NTInfo from .metaclasses import ModelMeta from .properties import UUIDProperty class Model(Observer, metaclass=ModelMeta): """ .. attribute:: __observables__ Class attribute. A list or tuple of name strings. The metaclass :class:`~mvc.support.metaclasses.ObservablePropertyMeta` uses it to create properties. *Value properties* have to exist as an attribute with an initial value, which may be ``None``. *Logical properties* require a getter and may have a setter method in the class. """ """A base class for models whose observable properties can be changed by threads different than the (gtk) main thread. Notification is performed by exploiting the gtk idle loop only if needed, otherwise the standard notification system (direct method call) is used. In this model, the observer is expected to run in the gtk main loop thread.""" class Meta(object): """ A meta-data class providing some basic functionality """ @classmethod def get_column_properties(cls): if not hasattr(cls, "all_properties"): raise RuntimeError("Meta class '%s' has not been initialized" \ " properly: 'all_properties' is not set!" % type(cls)) else: cls._mem_columns = getattr(cls, "_mem_columns", None) if cls._mem_columns is None: cls._mem_columns = [(attr.label, attr.data_type) for attr in cls.all_properties if attr.tabular] return cls._mem_columns @classmethod def get_local_persistent_properties(cls): return [attr for attr in cls.properties if attr.persistent] @classmethod def get_viewless_properties(cls): if not hasattr(cls, "all_properties"): raise RuntimeError("Meta class '%s' has not been initialized" \ " properly: 'all_properties' is not set!" % type(self)) else: return [attr for attr in cls.all_properties if not attr.visible] @classmethod def get_viewable_properties(cls): if not hasattr(cls, "all_properties"): raise RuntimeError("Meta class '%s' has not been initialized" \ " properly: 'all_properties' is not set!" % type(self)) else: return [attr for attr in cls.all_properties if attr.visible] pass # end of class uuid = UUIDProperty(persistent=True, observable=False) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): super(Model, self).__init__(*args, **kwargs) self._prop_lock = RLock() # @UndefinedVariable self.__observers = WeakList() self.__observer_threads = WeakKeyDictionary() # keys are properties names, values are pairs (method, # kwargs|None) inside the observer. kwargs is the keyword # argument possibly specified when explicitly defining the # notification method in observers, and it is used to build # the NTInfo instance passed down when the notification method # is invoked. If kwargs is None (special case), the # notification method is "old style" (property__...) and # won't be receiving the property name. self.__value_notifications = {} self.__instance_notif_before = {} self.__instance_notif_after = {} self.__signal_notif = {} for attr in self.Meta.all_properties: self.register_property(attr) return def register_property(self, prop): """Registers an existing attribute to be monitored, and sets up notifiers for notifications""" if prop.label not in self.__value_notifications: self.__value_notifications[prop.label] = [] pass # registers observable wrappers propval = getattr(type(self), prop.label)._get(self) if isinstance(propval, ObsWrapperBase): propval.__add_model__(self, prop.label) if isinstance(propval, Signal): if prop.label not in self.__signal_notif: self.__signal_notif[prop.label] = [] pass pass else: if prop.label not in self.__instance_notif_before: self.__instance_notif_before[prop.label] = [] pass if prop.label not in self.__instance_notif_after: self.__instance_notif_after[prop.label] = [] pass pass pass return def has_property(self, label): """Returns true if given property name refers an observable property inside self or inside derived classes.""" for prop in self.Meta.all_properties: if prop.label == label: return True def register_observer(self, observer): """Register given observer among those observers which are interested in observing the model.""" if observer in self.__observers: return # not already registered assert isinstance(observer, Observer) self.__observers.append(observer) self.__observer_threads[observer] = threading.current_thread() # @UndefinedVariable for prop in self.Meta.all_properties: self.__add_observer_notification(observer, prop) pass return def unregister_observer(self, observer): """Unregister the given observer that is no longer interested in observing the model.""" assert isinstance(observer, Observer) if observer not in self.__observers: return for prop in self.Meta.all_properties: self.__remove_observer_notification(observer, prop) pass self.__observers.remove(observer) del self.__observer_threads[observer] return def _reset_property_notification(self, prop, old=None): """Called when an assignment has been done that changes the type of a property or the instance of the property has been changed to a different instance. In this case it must be unregistered and registered again. Optional parameter old has to be used when the old value is an instance (derived from ObsWrapperBase) which needs to unregistered from the model, via a call to method old.__remove_model__(model, prop_name)""" # unregister_property if isinstance(old, ObsWrapperBase): old.__remove_model__(self, prop.label) pass self.register_property(prop) for observer in self.__observers: self.__remove_observer_notification(observer, prop) self.__add_observer_notification(observer, prop) pass return def __add_observer_notification(self, observer, prop): """ Find observing methods and store them for later notification. *observer* an instance. *label* a string. This checks for magic names as well as methods explicitly added through decorators or at runtime. In the latter case the type of the notification is inferred from the number of arguments it takes. """ value = getattr(type(self), prop.label)._get(self) # --- Some services --- def getmeth(format, numargs): # @ReservedAssignment name = format % prop.label meth = getattr(observer, name) args, varargs, _, _ = inspect.getargspec(meth) if not varargs and len(args) != numargs: logger.warn("Ignoring notification %s: exactly %d arguments" " are expected", name, numargs) raise AttributeError return meth def add_value(notification, kw=None): pair = (notification, kw) if pair in self.__value_notifications[prop.label]: return logger.debug("Will call %s.%s after assignment to %s.%s", observer.__class__.__name__, notification.__name__, self.__class__.__name__, prop.label) self.__value_notifications[prop.label].append(pair) return def add_before(notification, kw=None): if (not isinstance(value, ObsWrapperBase) or isinstance(value, Signal)): return pair = (notification, kw) if pair in self.__instance_notif_before[prop.label]: return logger.debug("Will call %s.%s before mutation of %s.%s", observer.__class__.__name__, notification.__name__, self.__class__.__name__, prop.label) self.__instance_notif_before[prop.label].append(pair) return def add_after(notification, kw=None): if (not isinstance(value, ObsWrapperBase) or isinstance(value, Signal)): return pair = (notification, kw) if pair in self.__instance_notif_after[prop.label]: return logger.debug("Will call %s.%s after mutation of %s.%s", observer.__class__.__name__, notification.__name__, self.__class__.__name__, prop.label) self.__instance_notif_after[prop.label].append(pair) return def add_signal(notification, kw=None): if not isinstance(value, Signal): return pair = (notification, kw) if pair in self.__signal_notif[prop.label]: return logger.debug("Will call %s.%s after emit on %s.%s", observer.__class__.__name__, notification.__name__, self.__class__.__name__, prop.label) self.__signal_notif[prop.label].append(pair) return # --------------------- try: notification = getmeth("property_%s_signal_emit", 3) except AttributeError: pass else: add_signal(notification) try: notification = getmeth("property_%s_value_change", 4) except AttributeError: pass else: add_value(notification) try: notification = getmeth("property_%s_before_change", 6) except AttributeError: pass else: add_before(notification) try: notification = getmeth("property_%s_after_change", 7) except AttributeError: pass else: add_after(notification) # here explicit notification methods are handled (those which # have been statically or dynamically registered) type_to_adding_method = { 'assign' : add_value, 'before' : add_before, 'after' : add_after, 'signal' : add_signal, } for meth in observer.get_observing_methods(prop.label): added = False kw = observer.get_observing_method_kwargs(prop.label, meth) for flag, adding_meth in type_to_adding_method.items(): if flag in kw: added = True adding_meth(meth, kw) pass pass if not added: raise ValueError("In %s notification method %s is " "marked to be observing property " "'%s', but no notification type " "information were specified." % (observer.__class__, meth.__name__, prop.label)) pass return def __remove_observer_notification(self, observer, prop): """ Remove all stored notifications. *observer* an instance. *prop* a LabeledProperty instance. """ def side_effect(seq): for meth, kw in reversed(seq): if meth.__self__ is observer: seq.remove((meth, kw)) yield meth for meth in side_effect(self.__value_notifications.get(prop.label, ())): logger.debug("Stop calling '%s' after assignment", meth.__name__) for meth in side_effect(self.__signal_notif.get(prop.label, ())): logger.debug("Stop calling '%s' after emit", meth.__name__) for meth in side_effect(self.__instance_notif_before.get(prop.label, ())): logger.debug("Stop calling '%s' before mutation", meth.__name__) for meth in side_effect(self.__instance_notif_after.get(prop.label, ())): logger.debug("Stop calling '%s' after mutation", meth.__name__) return def __notify_observer__(self, observer, method, *args, **kwargs): """This makes a call either through the Gtk.idle list or a direct method call depending whether the caller's thread is different from the observer's thread""" assert observer in self.__observer_threads if threading.currentThread() == self.__observer_threads[observer]: # @UndefinedVariable self.__idle_notify_observer(observer, method, args, kwargs) else: add_idle_call(self.__idle_notify_observer, observer, method, args, kwargs) def __idle_notify_observer(self, observer, method, args, kwargs): method(*args, **kwargs) # ------------------------------------------------------------- # Notifiers: # ------------------------------------------------------------- def notify_property_value_change(self, prop_name, old, new): """ Send a notification to all registered observers. *old* the value before the change occured. """ assert(prop_name in self.__value_notifications) for method, kw in self.__value_notifications[prop_name] : obs = method.__self__ # notification occurs checking spuriousness of the observer if old != new or obs.accepts_spurious_change(): if kw is None: # old style call without name self.__notify_observer__(obs, method, self, old, new) elif 'old_style_call' in kw: # old style call with name self.__notify_observer__(obs, method, self, prop_name, old, new) else: # New style explicit notification. # notice that named arguments overwrite any # existing key:val in kw, which is precisely what # it is expected to happen info = NTInfo('assign', kw, model=self, prop_name=prop_name, old=old, new=new) self.__notify_observer__(obs, method, self, prop_name, info) pass pass pass return def notify_method_before_change(self, prop_name, instance, meth_name, args, kwargs): """ Send a notification to all registered observers. *instance* the object stored in the property. *meth_name* name of the method we are about to call on *instance*. """ assert(prop_name in self.__instance_notif_before) for method, kw in self.__instance_notif_before[prop_name]: obs = method.__self__ # notifies the change if kw is None: # old style call without name self.__notify_observer__(obs, method, self, instance, meth_name, args, kwargs) elif 'old_style_call' in kw: # old style call with name self.__notify_observer__(obs, method, self, prop_name, instance, meth_name, args, kwargs) else: # New style explicit notification. # notice that named arguments overwrite any # existing key:val in kw, which is precisely what # it is expected to happen info = NTInfo('before', kw, model=self, prop_name=prop_name, instance=instance, method_name=meth_name, args=args, kwargs=kwargs) self.__notify_observer__(obs, method, self, prop_name, info) pass pass return def notify_method_after_change(self, prop_name, instance, meth_name, res, args, kwargs): """ Send a notification to all registered observers. *args* the arguments we just passed to *meth_name*. *res* the return value of the method call. """ assert(prop_name in self.__instance_notif_after) for method, kw in self.__instance_notif_after[prop_name]: obs = method.__self__ # notifies the change if kw is None: # old style call without name self.__notify_observer__(obs, method, self, instance, meth_name, res, args, kwargs) elif 'old_style_call' in kw: # old style call with name self.__notify_observer__(obs, method, self, prop_name, instance, meth_name, res, args, kwargs) else: # New style explicit notification. # notice that named arguments overwrite any # existing key:val in kw, which is precisely what # it is expected to happen info = NTInfo('after', kw, model=self, prop_name=prop_name, instance=instance, method_name=meth_name, result=res, args=args, kwargs=kwargs) self.__notify_observer__(obs, method, self, prop_name, info) pass pass return def notify_signal_emit(self, prop_name, arg): """ Emit a signal to all registered observers. *prop_name* the property storing the :class:`~mvc.observable.Signal` instance. *arg* one arbitrary argument passed to observing methods. """ assert(prop_name in self.__signal_notif) for method, kw in self.__signal_notif[prop_name]: obs = method.__self__ # notifies the signal emit if kw is None: # old style call, without name self.__notify_observer__(obs, method, self, arg) elif 'old_style_call' in kw: # old style call with name self.__notify_observer__(obs, method, self, prop_name, arg) else: # New style explicit notification. # notice that named arguments overwrite any # existing key:val in kw, which is precisely what # it is expected to happen info = NTInfo('signal', kw, model=self, prop_name=prop_name, arg=arg) self.__notify_observer__(obs, method, self, prop_name, info) pass pass return pass # end of class Model # ---------------------------------------------------------------------- PyXRD-0.8.4/mvc/models/metaclasses.py000066400000000000000000000223601363064711000174230ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import types import logging logger = logging.getLogger(__name__) from collections import OrderedDict from .properties import LabeledProperty from .object_pool import ThreadedObjectPool from ..support.utils import get_unique_list, get_new_uuid class ModelMeta(type): """ This is a meta-class that provides support to quickly set up a model with observable properties. For the simplest attributes you can omit the usual setters and getters if nothing else needs to be done but setting and getting the property. You can also provide your own implementation of the setters and getters (see below). Classes instantiated by this meta-class will be also be registered in an object pool using a UUID attribute. You do not need to add this property to your inner 'Meta' class. Classes instantiated by this metaclass must provide a method named notify_property_value_change(self, prop_name, old, new), or notification of property changes using 'Observer' won't work. ModelMeta also provides multi-threading support for accessing properties, through a (very basic) locking mechanism. It is assumed a lock is owned by the class that uses it. A Lock object called _prop_lock is assumed to be a member of the using class. How can you use this metaclass? First, '__metaclass__ = ModelMeta' must be class member of the class you want to make the automatic properties handling. Second, the model class needs to define an inner 'Meta' class in which you define meta-data which is used by this class (also see 'ModelMeta.Meta' class). This class defines several properties, the most important being 'properties'. This attribute is a list of descriptor objects, each of which describe a single property. Aside from the basics (name, default, etc.) it can contain additional information describing how/if other parts of the framework can use this attribute (e.g. is it supposed to be stored?, does it have a visual representation?). That's all: after the instantiation, your class will contain all properties you named inside 'properties'. Each of them will be also associated to a couple of automatically-generated functions which get and set the property value inside a generated member variable. Custom setters and getters: Suppose the property is called 'x'. The generated variable (which keeps the real value of the property x) is called _x. The getter is called 'get_x(self)', and the setter is called 'set_x(self, value)'. The base implementation of this getter is to return the value stored in the variable associated with the property. The setter simply sets its value. Programmers can override basic behavior for getters or setters simply by defining their getters and setters using the name convention above. The customized function can lie anywhere in the user classes hierarchy. Every overridden function will not be generated by the metaclass. For some properties it can be interesting to create a new descriptor class, and XXXXX TODO TODO """ object_pool = ThreadedObjectPool() # ------------------------------------------------------------ # Type creation: # ------------------------------------------------------------ def __new__(cls, name, bases, _dict): # find all data descriptors, auto-set their labels properties = {} for label, _property in list(_dict.items()): if isinstance(_property, LabeledProperty): _property.label = label properties[label] = _property # Create the class type: new_class = super(ModelMeta, cls).__new__(cls, name, bases, _dict) # Get the meta class: meta = cls.get_meta(new_class, name, bases, _dict) # Set the properties for future reference: meta.properties = sorted(properties.values(), key=lambda prop: getattr(prop, 'declaration_index', 0)) # Return our new class: return new_class def __init__(cls, name, bases, _dict): cls.process_properties(name, bases, _dict) return super(ModelMeta, cls).__init__(name, bases, _dict) # ------------------------------------------------------------ # Instance creation: # ------------------------------------------------------------ def __call__(cls, *args, **kwargs): # @NoSelf """ This method checks if the passed keyword args contained a "uuid" key, if so it is popped (the actual class's __init__ doesn't get it). If not a new UUID is created. The class instance is then created and the UUID is set accordingly. """ # Check if uuid has been passed (e.g. when restored from disk) # if not, generate a new one try: uuid = kwargs.pop("uuid") except KeyError: uuid = get_new_uuid() # Create instance: instance = type.__call__(cls, *args, **kwargs) # Set the UUID instance.uuid = uuid return instance # ------------------------------------------------------------ # Services # ------------------------------------------------------------ def get_meta(cls, name, bases, _dict): #@NoSelf """ Extracts or creates the meta class for this new model """ try: meta = _dict["Meta"] except KeyError: if len(bases) == 1: meta = type("Meta", (bases[0].Meta,), dict(properties=[])) cls.set_attribute(_dict, "Meta", meta) _dict["Meta"] = meta else: raise TypeError("Class %s.%s has not defined an inner Meta class, and has multiple base classes!" % (cls.__module__, cls.__name__)) return meta def process_properties(cls, name, bases, _dict): # @NoSelf """Processes the properties defined in the class's metadata class.""" # Get the meta class: meta = cls.get_meta(name, bases, _dict) # Get the list of properties for this class type (excluding bases): properties = get_unique_list(meta.properties) # Check the list of observables is really an iterable: if not isinstance(properties, list): raise TypeError("%s.%s.Meta 'properties' must be a list, not '%s'" % (cls.__module__, cls.__name__, type(properties))) # Generates the list of _all_ properties available for this class's bases all_properties = OrderedDict() # Loop over bases in reverse order: for class_type in bases[::-1]: # Loop over their properties, and update the dictionary: if hasattr(class_type, "Meta"): for attr in getattr(class_type.Meta, "all_properties", []): all_properties[attr.label] = attr # Add new/Override old attributes: for attr in properties: all_properties[attr.label] = attr # Set all_properties on the metadata class: meta.all_properties = list(all_properties.values()) logger.debug("Class %s.%s has properties: %s" \ % (cls.__module__, cls.__name__, all_properties)) pass # end of method def check_value_change(cls, old, new): # @NoSelf """Checks whether the value of the property changed in type or if the instance has been changed to a different instance. If true, a call to model._reset_property_notification should be called in order to re-register the new property instance or type""" from ..support import observables return type(old) != type(new) or \ isinstance(old, observables.ObsWrapperBase) and (old != new) def set_attribute(cls, _dict, name, value): # @NoSelf """Sets an attribute on the class and the dict""" _dict[name] = value setattr(cls, name, value) def del_attribute(cls, _dict, name): # @NoSelf """Deletes an attribute from the class and the dict""" del _dict[name] delattr(cls, name) pass # end of class # ---------------------------------------------------------------------- PyXRD-0.8.4/mvc/models/object_pool.py000066400000000000000000000113031363064711000174110ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from weakref import WeakValueDictionary import threading import multiprocessing import logging logger = logging.getLogger(__name__) from ..support.utils import get_new_uuid class ObjectPool(object): """ This class allows to fetch mvc model objects using their UUID. This requires to model to have a property called "uuid". All class inheriting from the base 'Model' class will have this. If implementing a custom model, the UUID property is responsible for the removal and addition to the pool when it changes values. Also see the UUIDProperty descriptor for an example implementation. We can use this to store complex relations between objects where references to each other can be replaced with the UUID. For a multi-threaded version see ThreadedObjectPool. """ def __init__(self, *args, **kwargs): object.__init__(self) self._objects = WeakValueDictionary() def add_or_get_object(self, obj): try: self.add_object(obj, force=False, silent=False) return obj except KeyError: return self.get_object(obj.uuid) def add_object(self, obj, force=False, fail_on_duplicate=False): if not obj.uuid in self._objects or force: self._objects[obj.uuid] = obj elif fail_on_duplicate: raise KeyError("UUID %s is already taken by another object %s, cannot add object %s" % (obj.uuid, self._objects[obj.uuid], obj)) else: # Just change the objects uuid, will break refs, but # it prevents issues with inherited properties etc. logger.warning("A duplicate UUID was passed to an ObjectPool for a %s object." % obj) obj.uuid = get_new_uuid() def change_all_uuids(self): # first get a copy off all uuids & objects: items = list(self._objects.items()) for uuid, obj in items: # @UnusedVariable obj.uuid = get_new_uuid() def remove_object(self, obj): if obj.uuid in self._objects and self._objects[obj.uuid] == obj: del self._objects[obj.uuid] def get_object(self, uuid): obj = self._objects.get(uuid, None) return obj def clear(self): self._objects.clear() class ThreadedObjectPool(object): def __init__(self, *args, **kwargs): object.__init__(self) self.pools = {} def clean_pools(self): for ptkey in list(self.pools.keys()): if (ptkey == (None, None) or not ptkey[0].is_alive() or not ptkey[1].is_alive()): del self.pools[ptkey] # clear this sucker def get_pool(self): process = multiprocessing.current_process() thread = threading.current_thread() pool = self.pools.get((process, thread), ObjectPool()) self.pools[(process, thread)] = pool return pool def add_or_get_object(self, *args, **kwargs): pool = self.get_pool() return pool.add_or_get_object(*args, **kwargs) def add_object(self, *args, **kwargs): pool = self.get_pool() return pool.add_object(*args, **kwargs) def change_all_uuids(self, *args, **kwargs): pool = self.get_pool() return pool.change_all_uuids(*args, **kwargs) def remove_object(self, *args, **kwargs): pool = self.get_pool() return pool.remove_object(*args, **kwargs) def get_object(self, *args, **kwargs): pool = self.get_pool() return pool.get_object(*args, **kwargs) def clear(self, *args, **kwargs): pool = self.get_pool() return pool.clear(*args, **kwargs) pass # end of class PyXRD-0.8.4/mvc/models/properties/000077500000000000000000000000001363064711000167365ustar00rootroot00000000000000PyXRD-0.8.4/mvc/models/properties/__init__.py000066400000000000000000000042761363064711000210600ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .labeled_property import LabeledProperty from .signal_property import SignalProperty from .cast_property import CastProperty from .cast_choice_property import CastChoiceProperty from .list_property import ListProperty from .uuid_property import UUIDProperty from .bool_property import BoolProperty from .integer_properties import IntegerChoiceProperty, IntegerProperty from .float_properties import FloatChoiceProperty, FloatProperty from .string_properties import StringChoiceProperty, StringProperty, ColorProperty from .signal_mixin import SignalMixin from .read_only_mixin import ReadOnlyMixin from .action_mixins import GetActionMixin, SetActionMixin from .observe_mixin import ObserveMixin __all__ = [ "LabeledProperty", "SignalProperty", "CastProperty", "CastChoiceProperty", "ListProperty", "UUIDProperty", "BoolProperty", "IntegerChoiceProperty", "IntegerProperty", "FloatChoiceProperty", "FloatProperty", "StringChoiceProperty", "StringProperty", "ColorProperty", "SignalMixin", "ReadOnlyMixin", "GetActionMixin", "SetActionMixin", "ObserveMixin", ]PyXRD-0.8.4/mvc/models/properties/action_mixins.py000066400000000000000000000060521363064711000221570ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from mvc.support.utils import rec_getattr class SetActionMixin(object): """ A descriptor mixin that will invoke a method on the instance owning this property after setting it. Expects two more keyword arguments to be passed to the property constructor: - set_action_name: a dotted string describing where to get the method from the instance - set_action_before: flag indicating whether this action should be invoked before setting the property (default False) """ set_action_name = None set_action_before = False def __set__(self, instance, value): action = rec_getattr(instance, self.set_action_name, None) assert callable(action), "The action in a SetActionMixin (%s) should be callable!" % self.label if self.set_action_before: action() super(SetActionMixin, self).__set__(instance, value) if not self.set_action_before: action() pass # end of class class GetActionMixin(object): """ A descriptor mixin that will invoke a method on the instance owning this property before getting it. Expects two more keyword arguments to be passed to the property constructor: - get_action_name: a dotted string describing where to get the method from the instance - get_action_after: flag indicating whether this action should be invoked after setting the property (default False) """ get_action_name = None get_action_after = False def __get__(self, instance, owner=None): if instance is None: return self action = rec_getattr(instance, self.get_action_name, None) assert callable(action), "The action in a GetActionMixin (%s) should be callable!" % self.label if self.get_action_after: action() value = super(GetActionMixin, self).__get__(instance, owner=owner) if not self.get_action_after: action() return value pass # end of class PyXRD-0.8.4/mvc/models/properties/bool_property.py000066400000000000000000000027651363064711000222210ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .cast_property import CastProperty class BoolProperty(CastProperty): """ A descriptor that will cast values to booleans. Expects its label to be set or passed to __init__. """ data_type = bool widget_type = 'toggle' # check_menu | expander def __init__(self, *args, **kwargs): super(BoolProperty, self).__init__(cast_to=bool, *args, **kwargs) pass #end of classPyXRD-0.8.4/mvc/models/properties/cast_choice_property.py000066400000000000000000000037711363064711000235300ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .cast_property import CastProperty class CastChoiceProperty(CastProperty): """ A descriptor that can cast the values to a given type, clamp values to a minimum and maximum. It also expects the (cast and clamped) value to be in a list or dict of choices or it will raise a ValueError. Expects its label to be set or passed to __init__. """ choices = None widget_type = 'option_list' def __init__(self, choices=[], *args, **kwargs): super(CastChoiceProperty, self).__init__(*args, **kwargs) self.choices = choices def __set__(self, instance, value): value = self.__cast_and_clamp__(instance, value) if value in self.choices: super(CastProperty, self).__set__(instance, value) #Call grandparent __set__ else: raise ValueError("'%s' is not a valid value for %s!" % (value, self.label)) pass #end of class PyXRD-0.8.4/mvc/models/properties/cast_property.py000066400000000000000000000042551363064711000222140ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .labeled_property import LabeledProperty class CastProperty(LabeledProperty): """ A descriptor that can cast the values to a given type and clamp values to a minimum and maximum. Expects its label to be set or passed to __init__. """ def __init__(self, minimum=None, maximum=None, cast_to=None, *args, **kwargs): super(CastProperty, self).__init__(*args, **kwargs) self.minimum = minimum self.maximum = maximum self.cast_to = cast_to def __cast_and_clamp__(self, instance, value): if self.minimum is not None: value = max(value, self.minimum) if self.maximum is not None: value = min(value, self.maximum) if self.cast_to is not None and value is not None: value = self.cast_to(value) return value def __set__(self, instance, value): value = self.__cast_and_clamp__(instance, value) if getattr(instance, self.label) != value: super(CastProperty, self).__set__(instance, value) pass #end of classPyXRD-0.8.4/mvc/models/properties/float_properties.py000066400000000000000000000043501363064711000226730ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .cast_property import CastProperty from .cast_choice_property import CastChoiceProperty class FloatProperty(CastProperty): """ A descriptor that will cast values to floats and can optionally clamp values to a minimum and maximum. Expects its label to be set or passed to __init__. """ data_type = float widget_type = 'scale' # | float_entry | entry | spin | label def __init__(self, *args, **kwargs): super(FloatProperty, self).__init__(cast_to=float, *args, **kwargs) pass #end of class class FloatChoiceProperty(CastChoiceProperty): """ A descriptor that will cast values to floats and can optionally clamp values to a minimum and maximum. It also expects the (cast and clamped) value to be in a set of choices or it will raise a ValueError. Expects its label to be set or passed to __init__. """ data_type = float widget_type = 'option_list' def __init__(self, choices=[], *args, **kwargs): super(FloatChoiceProperty, self).__init__( cast_to=float, choices=choices, *args, **kwargs ) pass #end of classPyXRD-0.8.4/mvc/models/properties/integer_properties.py000066400000000000000000000043371363064711000232300ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .cast_property import CastProperty from .cast_choice_property import CastChoiceProperty class IntegerProperty(CastProperty): """ A descriptor that will cast values to integers and can optionally clamp values to a minimum and maximum. Expects its label to be set or passed to __init__. """ data_type = int widget_type = 'spin' # | label | entry def __init__(self, *args, **kwargs): super(IntegerProperty, self).__init__(cast_to=int, *args, **kwargs) pass #end of class class IntegerChoiceProperty(CastChoiceProperty): """ A descriptor that will cast values to integers and can optionally clamp values to a minimum and maximum. It also expects the (cast and clamped) value to be in a list or dict of choices or it will raise a ValueError. Expects its label to be set or passed to __init__. """ data_type = int widget_type = 'option_list' def __init__(self, choices=[], *args, **kwargs): super(IntegerChoiceProperty, self).__init__( cast_to=int, choices=choices, *args, **kwargs ) pass #end of class PyXRD-0.8.4/mvc/models/properties/labeled_property.py000066400000000000000000000237201363064711000226500ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from mvc.support.utils import rec_getattr, rec_setattr from mvc.support.observables.value_wrapper import ValueWrapper import inspect class Mixable(object): """ Base class that allows to mix-in any other new-style class passed in the constructor, using the mix_with keyword argument (tuple of class types). """ def __init__(self, mix_with=None, *args, **kwargs): super(Mixable, self).__init__(*args, **kwargs) # Nifty hook so we can mix-in any other class: if mix_with is not None: name = type(self).__name__ for klass in mix_with: name = "%s_%s" % (klass.__name__.replace("Mixin", ""), name) bases = tuple(mix_with) + (type(self),) self.__class__ = type(name, bases, {}) pass #end of class class LabeledProperty(Mixable): """ Property descriptor base class to be used in combination with a ~:class:`mvc.models.Model` (sub)class. Expects it's label (attribute name) to be set or passed to __init__, for ~:class:`mvc.models.Model` (sub)class this is done automagically using its metaclass. Additional keyword arguments will be set as attributes on the descriptor. Some of these keywords have been given sane default values, even though they are not required for the implementation: - title = label - math_title = label - description = label - persistent (False) - store_private = (False) - visible (False) - data_type (object) - observable (True) - widget_type ('custom') To use this class, use it like the regular Property or property decorators. E.g.: attribute = LabeledProperty(...) or @LabeledProperty(...) def get_attribute(self): return self.attribute The setter function is expected to accept either a single argument (the new value) or two arguments (the property descriptor instance and the new value). Similarly, the getter and deleter function are expected to accept no arguments or a single argument (the property descriptor function instance). """ #: The actual attribute name used to access the value on the class instance. _label = None @property def label(self): return self._label @label.setter def label(self, value): self._label = value # Wrap the underlying variable if needed # (e.g. if it's a list, tuple, dict, or other mutable class): if self.default is not None: self.default = ValueWrapper.wrap_value(self.label, self.default, verbose=bool(value == "specimens")) #: Whether this attribute is persistent (needs to be stored) persistent = False #: Either False (use the actual attribute), True (use the private attribute value) or #: the name of the attribute to use when storing this attribute (if persistent = True) store_private = False #: A short textual description of this attribute title = None #: A short MathText description of this attribute math_title = None #: A flag indicating whether this attribute is visible in a GUI visible = False #: A string describing what kind of GUI widget can be used to display the #: contents of this property. Check the :mod:`mvc.adapters` module. widget_type = 'custom' #: A flag indicating whether this attribute should be visible in a table GUI tabular = False #: The type of the value stored in the attribute, or in the case of a #: collection (i.e. lists, dicts and tuples) the type of the collection #: items. data_type = object #: A flag indicating whether this attribute can be observed for changes observable = True #: The default value for this property default = None #: Declaration index: used to allow sorting in order of declaration declaration_index = 0 ############################################################################ # Generic private attribute getters and setters ############################################################################ private_attribute_format = "_%(label)s" def _get_private_label(self): """ Private attribute label (holds the actual value on the model) """ return self.private_attribute_format % { 'label': self.label } def _set(self, instance, value): """ Private setter """ rec_setattr(instance, self._get_private_label(), value) def _get(self, instance): """ Private getter """ return rec_getattr(instance, self._get_private_label(), self.default) ############################################################################ # Decorator calls: ############################################################################ def __call__(self, fget): return self.getter(fget) def _inject_self(self, f): """ Injects self into the arguments of function `f` (first argument after self) """ def wrapper(*args, **kwargs): return f(*(args[0] + (self,) + args[1:]), **kwargs) return wrapper def getter(self, fget): # Getter expects to be passed the descriptor if len(inspect.getargspec(fget).args) > 1: fget = self._inject_self(fget) self.fget = fget return self def setter(self, fset): # Setter expects to be passed the descriptor if len(inspect.getargspec(fset).args) > 2: fset = self._inject_self(fset) self.fset = fset return self def deleter(self, fdel): # Deleter expects to be passed the descriptor if len(inspect.getargspec(fdel).args) > 1: fdel = self._inject_self(fdel) self.fdel = fdel return self ############################################################################ # Initialization: ############################################################################ def __init__(self, fget=None, fset=None, fdel=None, doc=None, default=None, label=None, mix_with=None, **kwargs): super(LabeledProperty, self).__init__(mix_with=mix_with) #Increment declaration counter LabeledProperty.declaration_index += 1 self.declaration_index = LabeledProperty.declaration_index self.fget = fget self.fset = fset self.fdel = fdel if doc is None and fget is not None: doc = fget.__doc__ self.__doc__ = doc self.label = self.math_title = self.title = self.description = label self.persistent_label = label self.default = default for key, value in kwargs.items(): setattr(self, key, value) ############################################################################ # Comparison protocol: ############################################################################ def __eq__(self, other): # Descriptors are equal if they describe the same attribute return other is not None and self.label == other.label def __hash__(self): return hash(self.label) def __neq__(self, other): # Descriptors are equal if they describe the same attribute return not self.__eq__(other) ############################################################################ # Descriptor protocol: ############################################################################ def __get__(self, instance, owner): if instance is None: return self with instance._prop_lock: if self.fget is None: return self._get(instance) else: return self.fget(instance) def __set__(self, instance, value): with instance._prop_lock: # Get the old value old = getattr(instance, self.label) # Wrap the new value value = ValueWrapper.wrap_value(self.label, value, instance) # Set the new value if self.fset is None: self._set(instance, value) else: self.fset(instance, value) # Notify any observers if self.observable: # Check if we've really changed it, and send notifications if so: if type(instance).check_value_change(old, value): instance._reset_property_notification(self, old) pass # Notify any interested party we have set this property! if hasattr(instance, 'notify_property_value_change'): instance.notify_property_value_change(self.label, old, value) def __delete__(self, instance): if self.fdel is None: raise AttributeError("Can't delete attribute `%s`!" % self.label) self.fdel(instance) def __repr__(self): return '<%s %s>' % (self.__class__.__name__, self.label) pass #end of class PyXRD-0.8.4/mvc/models/properties/list_property.py000066400000000000000000000034131363064711000222300ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .cast_property import CastProperty class ListProperty(CastProperty): """ A descriptor that will cast values to lists. Expects its label to be set or passed to __init__. """ widget_type = 'object_list_view' # | object_tree_view | xy_list_view | custom def __cast_and_clamp__(self, instance, value): if self.cast_to is not None and value is not None and not isinstance(value, self.cast_to): value = self.cast_to(value) return value def __init__(self, *args, **kwargs): if not "cast_to" in kwargs: kwargs["cast_to"] = list super(ListProperty, self).__init__(*args, **kwargs) pass #end of classPyXRD-0.8.4/mvc/models/properties/observe_mixin.py000066400000000000000000000033501363064711000221620ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- class ObserveMixin(object): """ A descriptor mixin that will make the instance observe and relieve the objects set. """ def __relieve_old(self, instance, old, new): if old is not None: instance.relieve_model(old) def __observe_new(self, instance, old, new): if new is not None: instance.observe_model(new) def _set(self, instance, value): old = getattr(instance, self.label) if old != value: self.__relieve_old(instance, old, value) super(ObserveMixin, self)._set(instance, value) self.__observe_new(instance, old, value) pass PyXRD-0.8.4/mvc/models/properties/read_only_mixin.py000066400000000000000000000026641363064711000225000ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from warnings import warn class ReadOnlyMixin(object): """ A descriptor mixin that will make the property read only and raise a warning whenever somebody tries to set the property. """ def __set__(self, instance, value): warn("The `%s` property can not be set!" % self.label, RuntimeWarning) pass # end of class PyXRD-0.8.4/mvc/models/properties/signal_mixin.py000066400000000000000000000045001363064711000217700ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from mvc.support.utils import rec_getattr from mvc.support import observables class SignalMixin(object): """ A descriptor mixin that will invoke a signal on the instance owning this property when set. Expects two more keyword arguments to be passed to the property constructor: - signal_name: a dotted string describing where to get the signal object from the instance """ signal_name = "data_changed" def __set__(self, instance, value): signal = rec_getattr(instance, self.signal_name, None) if signal is not None: # Get the old value old = getattr(instance, self.label) with signal.ignore(): super(SignalMixin, self).__set__(instance, value) # Get the new value new = getattr(instance, self.label) # Check if we're dealing with some special class (in case we # emit the signal anyway) or immutables (in case we only emit # when the value has changed) if isinstance(old, observables.ObsWrapperBase) or old != new: signal.emit() else: super(SignalMixin, self).__set__(instance, value) pass # end of class PyXRD-0.8.4/mvc/models/properties/signal_property.py000066400000000000000000000035551363064711000225410ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from mvc.support.observables.signal import Signal from .labeled_property import LabeledProperty class SignalProperty(LabeledProperty): """ A descriptor for signals. Expects a single additional keyword argument (or not for default of Signal): - data_type: the type of signal to initialize this property with. """ data_type = Signal def _get(self, instance): signal = getattr(instance, self._get_private_label(), None) if signal is None: # If accesed for the first time set the Signal signal = self.data_type() setattr(instance, self._get_private_label(), signal) return signal def __set__(self, instance, value): raise AttributeError("Cannot set a Signal property!") pass # end of class PyXRD-0.8.4/mvc/models/properties/string_properties.py000066400000000000000000000051661363064711000231020ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .cast_property import CastProperty from .cast_choice_property import CastChoiceProperty class StringProperty(CastProperty): """ A descriptor that will cast values to strings and can optionally clamp values to a minimum and maximum. Expects its label to be set or passed to __init__. """ data_type = str widget_type = 'entry' # | label | color | color-selection | file | link | text_view def __init__(self, *args, **kwargs): super(StringProperty, self).__init__(cast_to=str, *args, **kwargs) pass #end of class class ColorProperty(StringProperty): """ A descriptor that will cast values to strings and can optionally clamp values to a minimum and maximum. Has a color widget as the default widget. Expects its label to be set or passed to __init__. """ widget_type = 'color' # entry | label | color-selection | file | link | text_view pass #end of class class StringChoiceProperty(CastChoiceProperty): """ A descriptor that will cast values to strings and can optionally clamp values to a minimum and maximum. It also expects the (cast and clamped) value to be in a set of choices or it will raise a ValueError. Expects its label to be set or passed to __init__. """ data_type = str widget_type = 'option_list' def __init__(self, choices=[], *args, **kwargs): super(StringChoiceProperty, self).__init__( cast_to=str, choices=choices, *args, **kwargs ) pass #end of class PyXRD-0.8.4/mvc/models/properties/tools.py000066400000000000000000000030141363064711000204460ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from copy import deepcopy def modify(descriptor, **kwargs): """ Makes a deep copy of descriptor and then applies all passed keywords. Can be used to modify the descriptor attributes in subclasses, without changing the behavior of the superclass. """ new_descriptor = deepcopy(descriptor) for name, value in kwargs.items(): setattr(new_descriptor, name, value) return new_descriptor PyXRD-0.8.4/mvc/models/properties/uuid_property.py000066400000000000000000000032421363064711000222230ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .labeled_property import LabeledProperty class UUIDProperty(LabeledProperty): """ A descriptor that handles removing and adding objects to a pool of unique objects. Expects the class type of the instance having this property to implement a 'remove_object' and 'add_object' method. """ def __set__(self, instance, value): type(instance).object_pool.remove_object(instance) retval = super(UUIDProperty, self).__set__(instance, value) type(instance).object_pool.add_object(instance) return retval pass #end of class PyXRD-0.8.4/mvc/models/treenode.py000066400000000000000000000131571363064711000167300ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- class TreeNode(object): """ A TreeNode can be used to build a Tree of objects with parents & children and support for inserting, appending, removing, iterating and inspecting them. This is the type that models should use. The root object can than be wrapped easily by a UI-toolkit specific wrapper, like the gtk ObjectTreeStore wrapper. """ def insert(self, index, child_node): """ Inserts the 'child_node' in this TreeNode at the given index. If the passed argument is not a TreeNode instance, it is wrapped in one. So you can safely pass in arbitrary objects. This functions returns the (wrapped) child_node. """ assert isinstance(child_node, type(self)) child_node.parent = self self._children.insert(index, child_node) self.on_grandchild_inserted(child_node) return child_node def append(self, child_node): """ Appends the 'child_node' to this TreeNode. If the passed argument is not a TreeNode instance, it is wrapped in one. So you can safely pass in arbitrary objects. This functions returns the (wrapped) child_node. """ assert isinstance(child_node, TreeNode) return self.insert(len(self._children), child_node) def remove(self, child_node): """ Removes the given child_node from this TreeNode's list of children. 'child_node' *must* be a TreeNode instance. """ assert isinstance(child_node, type(self)) self.on_grandchild_removed(child_node) self._children.remove(child_node) child_node._parent = None def on_grandchild_removed(self, child_node): if self.parent is not None: self.parent.on_grandchild_removed(child_node) def on_grandchild_inserted(self, child_node): if self.parent is not None: self.parent.on_grandchild_removed(child_node) _parent = None @property def parent(self): return self._parent @parent.setter def parent(self, parent): if self._parent: self._parent.remove(self) self._parent = parent @property def has_children(self): return bool(self._children) @property def child_count(self): return len(self._children) def __init__(self, obj=None, children=()): super(TreeNode, self).__init__() self.object = obj self._children = list() for child in children: self.append(child) def get_child_node_index(self, child_node): return self._children.index(child_node) def get_root_node(self): parent = self.parent while parent.parent is not None: parent = parent.parent return parent def get_next_node(self): try: return self.parent.get_child_node( self.parent.get_child_node_index(self) + 1) except IndexError: return None def get_prev_node(self): try: return self.parent.get_child_node( self.parent.get_child_node_index(self) - 1) except IndexError: return None def get_indices(self): parent = self.parent node = self indeces = tuple() while parent is not None: indeces += (parent.get_child_node_index(node),) node = parent parent = parent.parent return indeces[::-1] or None def get_child_node(self, *indeces): node = self for index in indeces: try: node = node._children[index] except IndexError: return None return node def get_first_child_node(self): try: return self._children[0] except IndexError: return None def get_child_object(self, *indeces): return self.get_child_node(*indeces).object def clear(self): for c in self.iter_children(reverse=True): c.parent = None def iter_children(self, reverse=False, recursive=True): children = self._children if not reverse else self._children[::-1] for child_node in children: if recursive and child_node.has_children: for c in child_node.iter_children(reverse=reverse, recursive=recursive): yield c yield child_node def __repr__(self): return '%s(%s - %s)' % (type(self).__name__, self.object, "%d child nodes" % len(self._children)) PyXRD-0.8.4/mvc/models/xydata.py000066400000000000000000000251721363064711000164150ustar00rootroot00000000000000 import logging logger = logging.getLogger(__name__) import types, json import numpy as np from ..support.utils import pop_kwargs from .properties.labeled_property import LabeledProperty from .base import Model class XYData(Model): """ An XYData is a mixin model holding a list of x-y numbers. Its values can be indexed, e.g.: >>> xydata = XYData(data=([1, 2, 3], [[4, 5], [6, 7], [8, 9]])) >>> xydata[0] (1, [4, 5]) and iterated: >>> xydata = XYData(data=([1, 2, 3], [[4, 5], [6, 7], [8, 9]])) >>> for row in xydata: ... print row ... (1, [4, 5]) (2, [6, 7]) (3, [8, 9]) You can also associate names with each column: >>> xydata = XYData(data=([1, 2, 3], [[4, 5], [6, 7], [8, 9]])) >>> xydata.y_names = ["First Column", "Second Column"] >>> xydata.y_names.get(0, "") 'First Column' """ # OBSERVABLE PROPERTIES: #: The X Data data_x = LabeledProperty(default=None, text="X data") @data_x.setter def data_x(self, value): self.set_data(value, self.data_y) #: The Y Data data_y = LabeledProperty(default=None, text="Y data") @data_y.setter def data_y(self, value): self.set_data(self.data_x, value) # REGULAR PROPERTIES: _y_names = [] @property def y_names(self): if len(self) < len(self._y_names): return self._y_names[:len(self)] else: return self._y_names @y_names.setter def y_names(self, names): self._y_names = names if names is not None else [] @property def size(self): return len(self) @property def num_columns(self): return 1 + self.data_y.shape[1] @property def max_y(self): if len(self.data_y) > 1: return np.max(self.data_y) else: return 0 @property def min_y(self): if len(self.data_x) > 1: return np.min(self.data_y) else: return 0 @property def abs_max_y(self): if len(self.data_x) > 1: return np.max(np.absolute(self.data_y)) else: return 0 @property def abs_min_y(self): if len(self.data_x) > 1: return np.min(np.absolute(self.data_y)) else: return 0 # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): """ Valid keyword arguments for an XYData are: data: the actual data containing x and y values, this can be a: - JSON string: "[[x1, x2, ..., xn], [y11, y21, ..., yn1], ..., [y1m, y2m, ..., ynm]]" - A dictionary from a (deprecated) XYObjectListStore, containing a data property, which contains a JSON string as above. - A 2D-numpy array, in which its first axes contains the data rows, and its second axes contains the columns, first column being the x-data, and following columns the y-data, e.g.: np.array([[x1,y11,...,y1m], [x2,y21,...,y2m], ..., [xn,yn1,...,ynm]]) - An iterable containing the x-data and y-data as if it would be passed to set_data(*data), e.g.: ([1, 2, 3], [[4, 5], [6, 7], [8, 9]]) names: names for the y columns (optional) """ XYData.set_data(self, np.array([], dtype=float), np.zeros(shape=(0, 0), dtype=float)) my_kwargs = pop_kwargs(kwargs, "names", "data", *[prop.label for prop in type(self).Meta.get_local_persistent_properties()] ) super(XYData, self).__init__(*args, **kwargs) kwargs = my_kwargs self.y_names = kwargs.get("names", self.y_names) data = kwargs.get("data", None) if data is not None: if isinstance(data, str): XYData._set_from_serial_data(self, data) elif isinstance(data, dict): XYData._set_from_serial_data(self, data["properties"]["data"]) elif isinstance(data, np.ndarray): XYData.set_data(self, data[:, 0], data[:, 1:]) elif hasattr(data, '__iter__'): XYData.set_data(self, *data) # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ def _serialize_data(self): """ Internal method, should normally not be used! If you want to write data to a file, use the save_data method instead! """ conc = np.insert(self.data_y, 0, self.data_x, axis=1) return "[" + ",".join( ["[" + ",".join(["%f" % val for val in row]) + "]" for row in conc] ) + "]" def _deserialize_data(self, data): """ Internal method, should normally not be used! If you want to load data from a file, use the generic.io.file_parsers classes in combination with the load_data_from_generator instead! 'data' argument should be a json string, containing a list of lists of x and y values, i.e.: [[x1, x2, ..., xn], [y11, y21, ..., yn1], ..., [y1m, y2m, ..., ynm]] If there are n data points and m+1 columns. """ data = data.replace("nan", "'nan'") data = data.replace("-inf", "'-inf'") data = data.replace("+inf", "'+inf'") data = data.replace("inf", "'inf'") data = json.JSONDecoder().decode(data) return data def _set_from_serial_data(self, sdata): """Internal method, do not use!""" data = [] try: data = self._deserialize_data(sdata) except ValueError: logger.exception("Failed to deserialize xy-data string `%s`" % sdata) if data != []: data = np.array(data, dtype=float) try: x = data[:, 0] y = data[:, 1:] except IndexError: logger.exception("Failed to load xy-data from serial string: %s" % sdata) else: XYData.set_data(self, x, y) def load_data_from_generator(self, generator, clear=True): if clear: self.clear() for x, y in generator: self.append(x, y) # ------------------------------------------------------------ # X-Y Data Management Methods & Functions # ------------------------------------------------------------ def _y_from_user(self, y_value): return np.array(y_value, ndmin=2, dtype=float) def set_data(self, x, y): """ Sets data using the supplied x, y1, ..., yn arrays. """ tempx = np.asarray(x) tempy = np.asarray(y) if tempy.ndim == 1: tempy = tempy.reshape((tempy.size, 1)) if tempx.shape[0] != tempy.shape[0]: raise ValueError("Shape mismatch: x (shape = %s) and y (shape = %s) data need to have compatible shapes!" % (tempx.shape, tempy.shape)) self._data_x = tempx self._data_y = tempy def set_value(self, i, j, value): if i < len(self): if j == 0: self.data_x[i] = value elif j >= 1: self.data_y[i, j - 1] = np.array(value, dtype=float) else: raise IndexError("Column indices must be positive values (is '%d')!" % j) else: raise IndexError("Row index '%d' out of bound!" % i) def append(self, x, y): """ Appends data using the supplied x, y1, ..., yn arrays. """ data_x = np.append(self.data_x, x) _y = self._y_from_user(y) if self.data_y.size == 0: data_y = _y else: data_y = np.append(self.data_y, _y, axis=0) self.set_data(data_x, data_y) def insert(self, pos, x, y): """ Inserts data using the supplied x, y1, ..., yn arrays at the given position. """ self.data_x = np.insert(self.data_x, pos, x) self.data_y = np.insert(self.data_y, pos, self._y_from_user(y), axis=0) def remove_from_indeces(self, *indeces): if indeces != []: indeces = np.sort(indeces)[::-1] for index in indeces: self.set_data( np.delete(self.data_x, index, axis=0), np.delete(self.data_y, index, axis=0) ) def clear(self): """ Clears all x and y values. """ self.set_data(np.zeros((0,), dtype=float), np.zeros((0, 0), dtype=float)) # ------------------------------------------------------------ # Convenience Methods & Functions # ------------------------------------------------------------ def get_xy_data(self, column=1): """ Returns a two-tuple containing 1D-numpy arrays with the x-data and the y-data for a given column. If the column keyword is not passed, the first column is returned. """ if len(self) > 0: return self.data_x, self.data_y[:, column - 1] else: return np.array([], dtype=float), np.array([], dtype=float) def get_y_at_x(self, x, column=0): """ Get the (interpolated) value for the y-column 'column' for a given x value """ if self._data_x.size: return np.interp(x, self._data_x, self._data_y[:, column]) else: return 0 def get_y_name(self, column): """ Returns the name of the given column. If the y_names attribute is not properly set (e.g. too small or empty), it will return an empty string. This method is 'safer' to use then directly accessing the y_names attribute (may result in an IndexError). """ try: return self.y_names[column] except IndexError: return "" # ------------------------------------------------------------ # Iterable & Indexable implementation # ------------------------------------------------------------ def __len__(self): return len(self.data_x) def __getitem__(self, index): return self.data_x[index], self.data_y[index].tolist() def __iter__(self): for i in range(len(self)): yield self[i] pass # end of class PyXRD-0.8.4/mvc/observers/000077500000000000000000000000001363064711000152715ustar00rootroot00000000000000PyXRD-0.8.4/mvc/observers/__init__.py000066400000000000000000000027421363064711000174070ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2006 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .base import Observer, NTInfo from .dict_observer import DictObserver from .list_item_observer import ListItemObserver from .list_observer import ListObserver from .tree_observer import TreeObserver __all__ = [ "NTInfo", "Observer", "DictObserver", "ListItemObserver", "ListObserver", "TreeObserver" ] PyXRD-0.8.4/mvc/observers/base.py000066400000000000000000000437131363064711000165650ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2006 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from ..support import decorators import inspect class NTInfo (dict): """ A container for information attached to a notification. This class is a dictionary-like object used: 1. As class when defining notification methods in observers, as it contains the flags identifying the notification types. 2. As class instance as parameter when a notification methods is called in observers. **Notification Type Flags** Notification methods are declared either statically or dynamically through :meth:`Observer.observe`. In both cases the type of the notification is defined by setting to `True` some flags. Flags can be set in any combination for multi-type notification methods. Flags are: assign For notifications issued when OPs are assigned. before For notifications called before a modifying method is called. after For notifications called after a modifying method is called. signal For notifications called when a signal is emitted. **Instance content** Instances of class `NTInfo` will be received as the last argument (`info`) of any notification method:: def notification_method(self, model, name, info) NTInfo is a dictionary (with some particular behaviour added) containing some information which is independent on the notification type, and some other information wich depends on the notification type. **Common to all types** For all notification types, NTInfo contains: model the model containing the observable property triggering the notification. `model` is also passed as first argument of the notification method. prop_name the name of the observable property triggering the notification. `name` is also passed as second argument of the notification method. Furthermore, any keyword argument not listed here is copied without modification into `info`. There are further information depending on the specific notification type: **For Assign-type** assign flag set to `True` old the value that the observable property had before being changed. new the new value that the observable property has been changed to. **For Before method call type** before flag set to `True` instance the object instance which the method that is being called belongs to. method_name the name of the method that is being called. args tuple of the arguments of the method that is being called. kwargs dictionary of the keyword arguments of the method that is being called. **For After method call type** after flag set to `True` instance the object instance which the method that has been called belongs to. method_name the name of the method that has been called. args tuple of the arguments of the method that has been called. kwargs dictionary of the keyword arguments of the method that has been called. result the value returned by the method which has been called. **For Signal-type** signal flag set to `True` arg the argument which was optionally specified when invoking emit() on the signal observable property. **Information access** The information carried by a NTInfo instance passed to a notification method can be retrieved using the instance as a dictionary, or accessing directly to the information as an attribute of the instance. For example:: # This is a multi-type notification @Observer.observe("op1", assign=True, hello="Ciao") @Observer.observe("op2", after=True, before=True) def notify_me(self, model, name, info): assert info["model"] == model # access as dict key assert info.prop_name == name # access as attribute if "assign" in info: assert info.old == info["old"] assert "hello" in info and "ciao" == info.hello print "Assign from", info.old, "to", info.new else: assert "before" in info or "after" in info assert "hello" not in info print "Method name=", info.method_name if "after" in info: print "Method returned", info.result pass return As already told, the type carried by a NTInfo instance can be accessed through boolean flags `assign`, `before`, `after` and `signal`. Furthermore, any other information specified at declaration time (keyword argument 'hello' in the previous example) will be accessible in the corresponding notification method. .. versionadded:: 1.99.1 """ # At least one of the keys in this set is required when constructing __ONE_REQUESTED = frozenset("assign before after signal".split()) __ALL_REQUESTED = frozenset("model prop_name".split()) def __init__(self, _type, *args, **kwargs): dict.__init__(self, *args, **kwargs) # checks the content provided by the user if not (_type in self and self[_type]): raise KeyError("flag '%s' must be set in given arguments" % _type) # all requested are provided by the framework, not the user assert NTInfo.__ALL_REQUESTED <= set(self) # now removes all type-flags not related to _type for flag in NTInfo.__ONE_REQUESTED: if flag != _type and flag in self: del self[flag] pass return def __getattr__(self, name): """ All dictionary keys are also available as attributes. """ try: return self[name] except KeyError: raise AttributeError("NTInfo object has no attribute '%s'.\n" "Existing attributes are: %s" % (name, str(self))) pass pass # end of class NTInfo # ---------------------------------------------------------------------- class Observer (object): """ .. note:: Most methods in this class are used internally by the framework. Do not override them in subclasses. """ # this is internal _CUST_OBS_ = "__custom_observes__" # ---------------------------------------------------------------------- @classmethod @decorators.good_decorator_accepting_args def observe(cls, *args, **kwargs): """ Mark a method as recieving notifications. Comes in two flavours: .. method:: observe(name, **types) :noindex: A decorator living in the class. Can be applied more than once to the same method, provided the names differ. *name* is the property we want to be notified about as a string. *types* are boolean values denoting the types of notifications desired. At least one of the following has to be passed as True: assign, before, after, signal. Excess keyword arguments are passed to the method as part of the info dictionary. .. method:: observe(callable, name, **types) :noindex: An instance method to define notifications at runtime. Works as above. *callable* is the method to send notifications to. The effect will be as if this had been decorated. In all cases the notification method must take exactly three arguments: the model object, the name of the property that changed, and an :class:`NTInfo` object describing the change. .. warning:: Due to limitation in the dynamic registration (in version 1.99.1), declarations of dynamic notifications must occur before registering self as an observer of the models whose properties the notifications are supposed to be observing. A hack for this limitation, is to first relieve any interesting model before dynamically register the notifications, and then re-observe those models. .. versionadded:: 1.99.1 """ @decorators.good_decorator def _decorator(_notified): # marks the method with observed properties _list = getattr(_notified, Observer._CUST_OBS_, list()) _list.append((name, kwargs)) setattr(_notified, Observer._CUST_OBS_, _list) return _notified # handles arguments if args and isinstance(args[0], cls): # used as instance method, for declaring notifications # dynamically if len(args) != 3: raise TypeError("observe() takes exactly three arguments" " when called (%d given)" % len(args)) self = args[0] notified = args[1] name = args[2] assert isinstance(self, Observer), "Method Observer.observe " \ "must be called with an Observer instance as first argument" if not callable(notified): raise TypeError("Second argument of observe() must be a callable") if type(name) != str: raise TypeError("Third argument of observe() must be a string") self.__register_notification(name, notified, kwargs) return None # used statically as decorator if len(args) != 1: raise TypeError("observe() takes exactly one argument when used" " as decorator (%d given)" % len(args)) name = args[0] if type(name) != str: raise TypeError("First argument of observe() must be a string") return _decorator # ---------------------------------------------------------------------- def __init__(self, *args, **kwargs): """ *model* is passed to :meth:`observe_model` if given. *spurious* indicates interest to be notified even when the value hasn't changed, like for: :: model.prop = model.prop .. versionadded:: 1.2.0 Before that observers had to filter out spurious notifications themselves, as if the default was `True`. With :class:`~mvc.observable.Signal` support this is no longer necessary. """ model = kwargs.pop("model", None) spurious = kwargs.pop("spurious", False) super(Observer, self).__init__(*args, **kwargs) # --------------------------------------------------------- # # This turns the decorator 'observe' an instance method def __observe(*args, **kwargs): self.__original_observe(self, *args, **kwargs) __observe.__name__ = self.observe.__name__ __observe.__doc__ = self.observe.__doc__ self.__original_observe = self.observe self.observe = __observe # --------------------------------------------------------- # self.__accepts_spurious__ = spurious # NOTE: In rev. 202 these maps were unified into # __CUST_OBS_MAP only (the map contained pairs (method, # args). However, this broke backward compatibility of code # accessing the map through # get_observing_methods. Now the informatio is split # and the original information restored. To access the # additional information (number of additional arguments # required by observing methods) use the newly added methods. # Private maps: do not change/access them directly, use # methods to access them: self.__CUST_OBS_MAP = {} # prop name --> set of observing methods self.__CUST_OBS_KWARGS = {} # observing method --> flag processed_props = set() # tracks already processed properties # searches all custom observer methods for cls in inspect.getmro(type(self)): # list of (method-name, method-object, list of (prop-name, kwargs)) meths = [ (name, meth, getattr(meth, Observer._CUST_OBS_)) for name, meth in cls.__dict__.items() if (inspect.isfunction(meth) and hasattr(meth, Observer._CUST_OBS_)) ] # props processed in this class. This is used to avoid # processing the same props in base classes. cls_processed_props = set() # since this is traversed top-bottom in the mro, the # first found match is the one to care for name, meth, pnames_ka in meths: _method = getattr(self, name) # the most top avail method # WARNING! Here we store the top-level method in the # mro, not the (unbound) method which has been # declared by the user with the decorator. for pname, ka in pnames_ka: if pname not in processed_props: self.__register_notification(pname, _method, ka) cls_processed_props.add(pname) pass pass pass # accumulates props processed in this class processed_props |= cls_processed_props pass # end of loop over classes in the mro if model is not None: self.observe_model(model) return def observe_model(self, model): """Starts observing the given model""" return model.register_observer(self) def relieve_model(self, model): """Stops observing the given model""" return model.unregister_observer(self) def accepts_spurious_change(self): """ Returns True if this observer is interested in receiving spurious value changes. This is queried by the model when notifying a value change.""" return self.__accepts_spurious__ def get_observing_methods(self, prop_name): """ Return a possibly empty set of callables registered with :meth:`observe` for *prop_name*. .. versionadded:: 1.99.1 Replaces :meth:`get_custom_observing_methods`. """ return self.__CUST_OBS_MAP.get(prop_name, set()) # this is done to keep backward compatibility get_custom_observing_methods = get_observing_methods def get_observing_method_kwargs(self, prop_name, method): """ Returns the keyword arguments which were specified when declaring a notification method, either statically of synamically with :meth:`Observer.observe`. *method* a callable that was registered with :meth:`observes`. :rtype: dict """ return self.__CUST_OBS_KWARGS[(prop_name, method)] def remove_observing_method(self, prop_names, method): """ Remove dynamic notifications. *method* a callable that was registered with :meth:`observe`. *prop_names* a sequence of strings. This need not correspond to any one `add` call. .. note:: This can revert the effects of a decorator at runtime. Don't. """ for prop_name in prop_names: _set = self.__CUST_OBS_MAP.get(prop_name, set()) if method in _set: _set.remove(method) key = (prop_name, method) if key in self.__CUST_OBS_KWARGS: del self.__CUST_OBS_KWARGS[key] pass return def is_observing_method(self, prop_name, method): """ Returns `True` if the given method was previously added as an observing method, either dynamically or via decorator. """ return (prop_name, method) in self.__CUST_OBS_KWARGS def __register_notification(self, prop_name, method, kwargs): """Internal service which associates the given property name to the method, and the (prop_name, method) with the given kwargs dictionary. If needed merges the dictionary, if the given (prop_name, method) pair was already registered (in this case the last registration wins in case of overlapping.) If given prop_name and method have been already registered, a ValueError exception is raised.""" key = (prop_name, method) if key in self.__CUST_OBS_KWARGS: raise ValueError("In %s method '%s' has been declared " "to be a notification for property '%s' " "multiple times (only one is allowed)." % \ (self.__class__, method.__name__, prop_name)) # fills the internal structures if prop_name not in self.__CUST_OBS_MAP: self.__CUST_OBS_MAP[prop_name] = set() pass self.__CUST_OBS_MAP[prop_name].add(method) self.__CUST_OBS_KWARGS[key] = kwargs return pass # end of class # ---------------------------------------------------------------------- PyXRD-0.8.4/mvc/observers/dict_observer.py000066400000000000000000000055101363064711000204760ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .base import Observer class DictObserver(Observer): """ An observer that wraps the in-instance changes of a dict to on_inserted and on_deleted handlers. """ _deleted = [] def __init__(self, on_inserted, on_deleted, prop_name, model=None, spurious=False): super(DictObserver, self).__init__(model=model, spurious=spurious) self.on_inserted = on_inserted self.on_deleted = on_deleted self.observe(self.on_prop_mutation_before, prop_name, before=True) self.observe(self.on_prop_mutation_after, prop_name, after=True) def on_prop_mutation_before(self, model, prop_name, info): if info.method_name in ("__setitem__", "__delitem__", "pop", "setdefault"): key = info.args[0] if key in info.instance: self._deleted.append(info.instance[key]) if info.method_name == "update": if len(info.args) == 1: iterable = info.args[0] elif len(info.kwargs) > 0: iterable = info.kwargs if hasattr(iterable, "iteritems"): iterable = iter(iterable.items()) for key, value in iterable: # @UnusedVariable if key in info.instance: self._deleted.append(info.instance[key]) if info.method_name == "clear": self._deleted.extend(list(info.instances.values())) def on_prop_mutation_after(self, model, prop_name, info): if self._deleted: for old_item in self._deleted: self.on_deleted(old_item) self._deleted = [] if info.method_name == "popitem": old_item = info.result[1] self.on_deleted(old_item) pass # end of classPyXRD-0.8.4/mvc/observers/list_item_observer.py000066400000000000000000000052161363064711000215470ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import weakref from .base import Observer class ListItemObserver(Observer): """ An observer that observes a single item in a list and informs us of changes. The observed properties are defined in the list type's meta class by setting their property descriptors 'tabular' attribute to True. """ _previous_model_ref = None @property def _previous_model(self): if self._previous_model_ref is not None: return self._previous_model_ref() else: return None @_previous_model.setter def _previous_model(self, value): self._previous_model_ref = weakref.ref(value, self.clear) def __init__(self, on_changed, model=None, spurious=False): super(ListItemObserver, self).__init__(spurious=spurious) self.on_changed = on_changed self.observe_model(model) def observe_model(self, model): if self._previous_model is not None: self.relieve_model(self._previous_model) if model is not None: for prop_name, data_type in model.Meta.get_column_properties(): # @UnusedVariable self.observe(self.on_prop_mutation, prop_name, assign=True) self._previous_model = model super(ListItemObserver, self).observe_model(model) def clear(self, *args): self.on_changed = None if len(args) == 0: self.observe_model(None) def on_prop_mutation(self, model, prop_name, info): if callable(self.on_changed): self.on_changed(model) pass # end of classPyXRD-0.8.4/mvc/observers/list_observer.py000066400000000000000000000066521363064711000205360ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .base import Observer import types class ListObserver(Observer): """ An observer that wraps the in-instance changes of a list to on_inserted and on_deleted handlers. """ _deleted = [] def __init__(self, on_inserted, on_deleted, prop_name, on_deleted_before=None, model=None, spurious=False): super(ListObserver, self).__init__(spurious=spurious) self.on_inserted = on_inserted self.on_deleted = on_deleted self.on_deleted_before = on_deleted_before self.observe(self.on_prop_mutation_before, prop_name, before=True) self.observe(self.on_prop_mutation_after, prop_name, after=True) self.observe_model(model) def on_prop_mutation_before(self, model, prop_name, info): if info.method_name in ("__setitem__", "__delitem__"): i = info.args[0] if isinstance(i, slice): self._deleted = info.instance[i] elif i <= len(info.instance): # setting an existing item: need a on_delete as well self._deleted = [info.instance[i], ] if info.method_name == "pop": if len(info.instance) > 0: self._deleted = [info.instance[-1], ] if info.method_name == "remove": self._deleted = [info.args[0], ] if callable(self.on_deleted_before): for old_item in self._deleted[::-1]: self.on_deleted_before(old_item) def on_prop_mutation_after(self, model, prop_name, info): if callable(self.on_deleted): for old_item in self._deleted[::-1]: self.on_deleted(old_item) self._deleted = [] if info.method_name == "__setitem__": i = info.args[0] if type(i) is slice: for item in info.instance[i]: self.on_inserted(item) else: new_item = info.args[1] self.on_inserted(new_item) if info.method_name == "append": new_item = info.args[0] self.on_inserted(new_item) if info.method_name == "extend": items = info.args[0] for new_item in items: self.on_inserted(new_item) if info.method_name == "insert": new_item = info.args[1] self.on_inserted(new_item) pass # end of classPyXRD-0.8.4/mvc/observers/tree_observer.py000066400000000000000000000052321363064711000205130ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .base import Observer class TreeObserver(Observer): """ An observer that wraps the in-instance changes of a Tree made by TreeNodes to on_inserted and on_deleted handlers. """ _deleted = [] def __init__(self, on_inserted, on_deleted, prop_name, on_deleted_before=None, model=None, spurious=False): super(TreeObserver, self).__init__(spurious=spurious) self.on_inserted = on_inserted self.on_deleted = on_deleted self.on_deleted_before = on_deleted_before self.observe(self.on_prop_mutation_before, prop_name, before=True) self.observe(self.on_prop_mutation_after, prop_name, after=True) self.observe_model(model) def on_prop_mutation_before(self, model, prop_name, info): if info.method_name == "on_grandchild_removed": self._deleted = [info.args[0], ] if info.method_name == "remove": self._deleted = [info.args[0], ] if callable(self.on_deleted_before): for old_item in self._deleted[::-1]: self.on_deleted_before(old_item) def on_prop_mutation_after(self, model, prop_name, info): if callable(self.on_deleted): for old_item in self._deleted[::-1]: self.on_deleted(old_item) self._deleted = [] if info.method_name == "on_grandchild_removed": new_item = info.args[0] self.on_inserted(new_item) if info.method_name == "insert": new_item = info.args[1] self.on_inserted(new_item) pass # end of classPyXRD-0.8.4/mvc/settings.py000066400000000000000000000022451363064711000154740ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- TOOLKIT = "gtk"PyXRD-0.8.4/mvc/support/000077500000000000000000000000001363064711000147735ustar00rootroot00000000000000PyXRD-0.8.4/mvc/support/__init__.py000066400000000000000000000022271363064711000171070ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- PyXRD-0.8.4/mvc/support/cancellable_thread.py000066400000000000000000000030031363064711000211150ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Author: Mathijs Dumon # Based on code from Rick Spencer # Copyright (c) 2013, Mathijs Dumon, Rick Spencer # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) from threading import Thread, Event class CancellableThread(Thread): """ Class for use by ThreadedTaskBox. """ def __init__(self, run_function, on_complete): Thread.__init__(self) self.setDaemon(True) self.run_function = run_function self.on_complete = on_complete # Internal flag, for checking wether the user stopped. self.__stop = Event() # Internal flag, for checking wether the user cancelled. self.__cancel = Event() def run(self): try: # Tell the function to run data = self.run_function(stop=self.__stop) # Return function results, if not cancelled if not self.__cancel.is_set(): self.on_complete(data) except KeyboardInterrupt: self.cancel() except: logger.exception("Unhandled exception in CancellableThread run()") def stop(self): """ Stops the thread, and calls the on_complete callback """ self.__stop.set() def cancel(self): """ Stops the thread, and does not call the on_complete callback. """ self.__cancel.set() self.__stop.set() pass #end of class PyXRD-0.8.4/mvc/support/collections/000077500000000000000000000000001363064711000173115ustar00rootroot00000000000000PyXRD-0.8.4/mvc/support/collections/__init__.py000066400000000000000000000000001363064711000214100ustar00rootroot00000000000000PyXRD-0.8.4/mvc/support/collections/weak_list.py000066400000000000000000000051331363064711000216470ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et: # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import weakref class WeakList(list): """ A list subclass referring to its items using weak references if possible """ def __init__(self, items=list()): list.__init__(self) tuple(map(self.append, items)) def get_value(self, item): try: item = item() finally: return item def make_ref(self, item): try: item = weakref.ref(item, self.remove) finally: return item def __contains__(self, item): return list.__contains__(self, self.make_ref(item)) def __getitem__(self, key): if isinstance(key, slice): return type(self)(self.get_value(item) for item in list.__getitem__(self, key)) return self.get_value(list.__getitem__(self, key)) def __getslice__(self, i, j): return self.__getitem__(slice(i, j)) def __setitem__(self, key, item): return list.__setitem__(self, key, self.make_ref(item)) def __iter__(self): return iter([self[key] for key in range(len(self))]) def append(self, item): list.append(self, self.make_ref(item)) def remove(self, item): item = self.make_ref(item) while list.__contains__(self, item): list.remove(self, item) def index(self, item): return list.index(self, self.make_ref(item)) def pop(self, item): return list.pop(self, self.make_ref(item)) pass #end of class PyXRD-0.8.4/mvc/support/decorators.py000066400000000000000000000061431363064711000175160ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2006 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- # This file contains decorators to be used (privately) by other parts # of the framework import types def good_decorator(decorator): """This decorator makes decorators behave well wrt to decorated functions names, doc, etc.""" def new_decorator(f): g = decorator(f) g.__name__ = f.__name__ g.__doc__ = f.__doc__ g.__dict__.update(f.__dict__) return g new_decorator.__name__ = decorator.__name__ new_decorator.__doc__ = decorator.__doc__ new_decorator.__dict__.update(decorator.__dict__) return new_decorator def good_classmethod_decorator(decorator): """This decorator makes class method decorators behave well wrt to decorated class method names, doc, etc.""" def new_decorator(cls, f): g = decorator(cls, f) g.__name__ = f.__name__ g.__doc__ = f.__doc__ g.__dict__.update(f.__dict__) return g new_decorator.__name__ = decorator.__name__ new_decorator.__doc__ = decorator.__doc__ new_decorator.__dict__.update(decorator.__dict__) return new_decorator def good_decorator_accepting_args(decorator): """This decorator makes decorators behave well wrt to decorated functions names, doc, etc. Differently from good_decorator, this accepts decorators possibly receiving arguments and keyword arguments. This decorato can be used indifferently with class methods and functions.""" def new_decorator(*f, **k): g = decorator(*f, **k) if 1 == len(f) and isinstance(f[0], types.FunctionType): g.__name__ = f[0].__name__ g.__doc__ = f[0].__doc__ g.__dict__.update(f[0].__dict__) pass return g new_decorator.__name__ = decorator.__name__ new_decorator.__doc__ = decorator.__doc__ new_decorator.__dict__.update(decorator.__dict__) # Required for Sphinx' automodule. new_decorator.__module__ = decorator.__module__ return new_decorator PyXRD-0.8.4/mvc/support/exceptions.py000066400000000000000000000035131363064711000175300ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- class DecoratorError (SyntaxError): """Used to report syntax errors occurring when decorators are used in models and observers.""" pass class TooManyCandidatesError (ValueError): """This class is used for distinguishing between a multiple candidates matched and no candidates matched. The latter is not necessarily an issue, as a missed match can be skipped when searching for a match for *all* the properties in the model (no params to adapt()), which may fail in one single view, as multiple views may be used to represent different parts of the model""" pass class ViewError (ValueError): """General issue with view content""" pass PyXRD-0.8.4/mvc/support/file_utils.py000066400000000000000000000055451363064711000175150ustar00rootroot00000000000000import os def get_case_insensitive_glob(*strings): '''Ex: '*.ora' => '*.[oO][rR][aA]' ''' return ['*.%s' % ''.join(["[%s%s]" % (c.lower(), c.upper()) for c in string.split('.')[1]]) for string in strings] def retrieve_lowercase_extension(glob): '''Ex: '*.[oO][rR][aA]' => '*.ora' ''' return ''.join([ c.replace("[", "").replace("]", "")[:-1] for c in glob.split('][')]) # ====================================================================== # This is taken from python 2.6 (os.path.relpath is supported in 2.6) # # Copyright (c) 2001 Python Software Foundation; All Rights Reserved # This code about relpath is covered by the Python Software Foundation # (PSF) Agreement. See http://docs.python.org/license.html for details. # ====================================================================== def __posix_relpath(path, start=os.curdir): """Return a relative version of a path""" if not path: raise ValueError("no path specified") start_list = os.path.abspath(start).split(os.sep) path_list = os.path.abspath(path).split(os.sep) # Work out how much of the filepath is shared by start and path. i = len(os.path.commonprefix([start_list, path_list])) rel_list = [os.pardir] * (len(start_list) - i) + path_list[i:] if not rel_list: return os.curdir return os.path.join(*rel_list) # This is taken from python 2.6 (os.path.relpath is supported in 2.6) # This is for windows def __nt_relpath(path, start=os.curdir): """Return a relative version of a path""" if not path: raise ValueError("no path specified") start_list = os.path.abspath(start).split(os.sep) path_list = os.path.abspath(path).split(os.sep) if start_list[0].lower() != path_list[0].lower(): unc_path, rest = os.path.splitunc(path) unc_start, rest = os.path.splitunc(start) if bool(unc_path) ^ bool(unc_start): raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)" \ % (path, start)) else: raise ValueError("path is on drive %s, start on drive %s" \ % (path_list[0], start_list[0])) # Work out how much of the filepath is shared by start and path. for i in range(min(len(start_list), len(path_list))): if start_list[i].lower() != path_list[i].lower(): break else: i += 1 pass rel_list = [os.pardir] * (len(start_list) - i) + path_list[i:] if not rel_list: return os.curdir return os.path.join(*rel_list) try: import os.path.relpath relpath = os.path.relpath except ImportError: if os.name == 'nt': relpath = __nt_relpath else: relpath = __posix_relpath pass pass # ====================================================================== # End of code covered by PSF License Agreement # ======================================================================PyXRD-0.8.4/mvc/support/gui_loop.py000066400000000000000000000060571363064711000171720ustar00rootroot00000000000000__add_idle_call = None __remove_idle_call = None __add_timeout_call = None __remove_timeout_call = None __setup_event_loop = None __start_event_loop = None __stop_event_loop = None __idle_call_dict = {} __timeout_call_dict = {} def add_idle_call(func, *args, **kwargs): global __add_idle_call global __idle_call_dict if __add_idle_call is not None: __idle_call_dict[func] = __add_idle_call(func, *args, **kwargs) else: # toolkit does not support this or is not loaded: func(*args, **kwargs) def remove_idle_call(func): global __remove_idle_call global __idle_call_dict if __remove_idle_call is not None: __remove_idle_call(__idle_call_dict[func]) def add_timeout_call(timeout, func, *args, **kwargs): global __add_timeout_call global __timeout_call_dict if __add_timeout_call is not None: __timeout_call_dict[func] = __add_timeout_call(timeout, func, *args, **kwargs) else: # toolkit does not support this or is not loaded: func(*args, **kwargs) def remove_timeout_call(func): global __remove_timeout_call global __timeout_call_dict if __remove_timeout_call is not None: __remove_timeout_call(__timeout_call_dict[func]) def start_event_loop(): global __start_event_loop if __start_event_loop is not None: return __start_event_loop() def stop_event_loop(): global __stop_event_loop if __stop_event_loop is not None: return __stop_event_loop() def load_toolkit_functions( add_idle_call, remove_idle_call, add_timeout_call, remove_timeout_call, start_event_loop, stop_event_loop): """ 'add_idle_call' should take a function as 1st argument, the return value is passed back to 'remove_idle_call'. Internally a cache is maintained in which keys are functions and values are return values. 'add_timeout_call' and 'remove_timeout_call' work analogously start_event_loop and stop_event_loop don't take arguments and should be self-explanatory. """ global __add_idle_call global __remove_idle_call global __add_timeout_call global __remove_timeout_call global __start_event_loop global __stop_event_loop assert callable(add_idle_call) assert callable(remove_idle_call) assert callable(add_timeout_call) assert callable(remove_timeout_call) assert callable(start_event_loop) assert callable(stop_event_loop) __add_idle_call = add_idle_call __remove_idle_call = remove_idle_call __add_timeout_call = add_timeout_call __remove_timeout_call = remove_timeout_call __start_event_loop = start_event_loop __stop_event_loop = stop_event_loop """ Decorators: """ def run_when_idle(func): def callback(*args, **kwargs): return add_idle_call(func, *args, **kwargs) return callback def run_every(timeout): def wrapper(func): def callback(*args, **kwargs): return add_timeout_call(func, timeout, *args, **kwargs) return callback return wrapper PyXRD-0.8.4/mvc/support/observables/000077500000000000000000000000001363064711000173025ustar00rootroot00000000000000PyXRD-0.8.4/mvc/support/observables/__init__.py000066400000000000000000000032011363064711000214070ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .base import ObsWrapperBase from .obs_wrapper import ObsWrapper from .obs_seq_wrapper import ObsSeqWrapper from .obs_map_wrapper import ObsMapWrapper from .obs_treenode_wrapper import ObsTreeNodeWrapper from .obs_list_wrapper import ObsListWrapper from .observable import Observable from .signal import Signal __all__ = [ "ObsWrapperBase", "ObsWrapper", "ObsSeqWrapper", "ObsMapWrapper", "ObsTreeNodeWrapper", "ObsListWrapper", "Observable", "Signal", ]PyXRD-0.8.4/mvc/support/observables/base.py000066400000000000000000000061541363064711000205740ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Tobias Weber # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import weakref class ObsWrapperBase (object): """ This class is a base class wrapper for user-defined classes and containers like lists, maps, signals, etc. """ def __init__(self): # all model instances owning self (can be multiple due to # inheritance). Each element of the set is a pair (model, # property-name) self.__models = set() return def __add_model__(self, model, prop_name): """Registers the given model to hold the wrapper among its properties, within a property whose name is given as well""" ref = weakref.ref(model) self.__models.add((ref, prop_name)) return def __remove_model__(self, model, prop_name): """Unregisters the given model, to release the wrapper. This method reverts the effect of __add_model__""" delete_models = set() for ref, prop_name in self.__models: if ref() == model and prop_name == prop_name: delete_models.add((ref, prop_name)) self.__models -= delete_models return def __get_models__(self): delete_models = set() for ref, prop_name in self.__models: model = ref() if model is not None: yield model, prop_name else: delete_models.add((ref, prop_name)) self.__models -= delete_models def _notify_method_before(self, instance, name, args, kwargs): for m, n in self.__get_models__(): m.notify_method_before_change(n, instance, name, args, kwargs) pass return def _notify_method_after(self, instance, name, res_val, args, kwargs): for m, n in self.__get_models__(): m.notify_method_after_change(n, instance, name, res_val, args, kwargs) pass return pass # end of class PyXRD-0.8.4/mvc/support/observables/obs_list_wrapper.py000066400000000000000000000042211363064711000232310ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Tobias Weber # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .obs_seq_wrapper import ObsSeqWrapper from .value_wrapper import ValueWrapper @ValueWrapper.register_wrapper(position=0) class ObsListWrapper (ObsSeqWrapper): @classmethod def wrap_value(cls, label, value, model=None): if isinstance(value, list): res = cls(value) if model: res.__add_model__(model, label) return res def __init__(self, l): methods = ("append", "extend", "insert", "pop", "remove", "reverse", "sort") ObsSeqWrapper.__init__(self, l, methods) for _m in ("add", "mul"): meth = "__%s__" % _m assert hasattr(self._obj, meth), "Not found method %s in %s" % (meth, str(type(self._obj))) setattr(self.__class__, meth, getattr(self._obj, meth)) pass return def __radd__(self, other): return other.__add__(self._obj) def __rmul__(self, other): return self._obj.__mul__(other) pass # end of class PyXRD-0.8.4/mvc/support/observables/obs_map_wrapper.py000066400000000000000000000034001363064711000230310ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Tobias Weber # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .obs_seq_wrapper import ObsSeqWrapper from .value_wrapper import ValueWrapper @ValueWrapper.register_wrapper(position=1) class ObsMapWrapper (ObsSeqWrapper): @classmethod def wrap_value(cls, label, value, model=None): if isinstance(value, dict): res = cls(value) if model: res.__add_model__(model, label) return res def __init__(self, m): methods = ("clear", "pop", "popitem", "update", "setdefault") ObsSeqWrapper.__init__(self, m, methods) return pass # end of class PyXRD-0.8.4/mvc/support/observables/obs_seq_wrapper.py000066400000000000000000000045351363064711000230560ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Tobias Weber # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .obs_wrapper import ObsWrapper class ObsSeqWrapper (ObsWrapper): """ Base class for ObsListWrapper, ObsMapWrapper, ... Use sub-classes, not this base class! """ def __init__(self, obj, method_names): ObsWrapper.__init__(self, obj, method_names) for _m in "lt le eq ne gt ge len iter".split(): meth = "__%s__" % _m assert hasattr(self._obj, meth), "Not found method %s in %s" % (meth, str(type(self._obj))) setattr(self.__class__, meth, getattr(self._obj, meth)) pass return def __setitem__(self, key, val): self._notify_method_before(self._obj, "__setitem__", (key, val), {}) res = self._obj.__setitem__(key, val) self._notify_method_after(self._obj, "__setitem__", res, (key, val), {}) return res def __delitem__(self, key): self._notify_method_before(self._obj, "__delitem__", (key,), {}) res = self._obj.__delitem__(key) self._notify_method_after(self._obj, "__delitem__", res, (key,), {}) return res def __getitem__(self, key): return self._obj.__getitem__(key) pass # end of class PyXRD-0.8.4/mvc/support/observables/obs_treenode_wrapper.py000066400000000000000000000034351363064711000240710ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Tobias Weber # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .obs_wrapper import ObsWrapper from .value_wrapper import ValueWrapper @ValueWrapper.register_wrapper(position=2) class ObsTreeNodeWrapper(ObsWrapper): @classmethod def wrap_value(cls, label, value, model=None): from ...models.treenode import TreeNode if isinstance(value, TreeNode): res = cls(value) if model: res.__add_model__(model, label) return res def __init__(self, t): methods = ("insert", "remove", "on_grandchild_inserted", "on_grandchild_removed") ObsWrapper.__init__(self, t, methods) pass # end of class PyXRD-0.8.4/mvc/support/observables/obs_wrapper.py000066400000000000000000000047601363064711000222060ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Tobias Weber # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from .base import ObsWrapperBase class ObsWrapper (ObsWrapperBase): """ Base class for wrappers, like user-classes and sequences. Use sub-classes! """ def __init__(self, obj, method_names): ObsWrapperBase.__init__(self) self._obj = obj self.__doc__ = obj.__doc__ # Creates a derived class which is a singleton which self is # going to be an instance of. All method_names are then # wrapped within it. # See http://stackoverflow.com/questions/1022499/emulating-membership-test-in-python-delegating-contains-to-contained-object d = dict((name, self.__get_wrapper(name)) for name in method_names) self.__class__ = type(self.__class__.__name__, (self.__class__,), d) return def __get_wrapper(self, name): def _wrapper_fun(self, *args, **kwargs): self._notify_method_before(self._obj, name, args, kwargs) res = getattr(self._obj, name)(*args, **kwargs) self._notify_method_after(self._obj, name, res, args, kwargs) return res return _wrapper_fun # For all fall backs def __getattr__(self, name): return getattr(self._obj, name) def __repr__(self): return self._obj.__repr__() def __str__(self): return self._obj.__str__() pass # end of class PyXRD-0.8.4/mvc/support/observables/observable.py000066400000000000000000000037021363064711000220020ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Tobias Weber # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from ..decorators import good_classmethod_decorator from .base import ObsWrapperBase class Observable (ObsWrapperBase): @classmethod @good_classmethod_decorator def observed(cls, _func): """ Decorate methods to be observable. If they are called on an instance stored in a property, the model will emit before and after notifications. """ def wrapper(*args, **kwargs): self = args[0] assert(isinstance(self, Observable)) self._notify_method_before(self, _func.__name__, args, kwargs) res = _func(*args, **kwargs) self._notify_method_after(self, _func.__name__, res, args, kwargs) return res return wrapper pass # end of classPyXRD-0.8.4/mvc/support/observables/signal.py000066400000000000000000000073461363064711000211430ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Tobias Weber # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import threading from .observable import Observable class Signal(Observable): """ A Signal that can either: - be 'held' from firing until a code block has finished, e.g. to prevent numerous events from firing, when one final event would be enough - be stopped from firing altogether, even after the code block has finished Holding signals back: ... object.hold_signal = HoldableSignal() ... with object.hold_signal.hold(): # this code block can call emit() on the hold_signal but it will not # actually emit the signal until the 'with' block is left ... Ignoring signals: ... object.hold_signal = HoldableSignal() ... with object.hold_signal.ignore(): # this code block can call emit() on the hold_signal but it will not # actually emit the signal, even after leaving the 'with' block """ # PROPERTIES: clock = threading.RLock() # INIT: def __init__(self, *args, **kwargs): super(Signal, self).__init__(*args, **kwargs) self._counter = 0 self._emissions_pending = False self._ignore_levels = [] # WITH FUNCTIONALITY: def __enter__(self): with self.clock: self._counter += 1; def __exit__(self, *args): with self.clock: self._counter -= 1; if self._counter < 0: raise RuntimeError("Negative counter in CounterLock object! Did you call __exit__ too many times?") if len(self._ignore_levels) > 0 and self._counter == self._ignore_levels[-1]: self._ignore_levels.pop() elif self._counter == 0 and self._emissions_pending: # Fire the signal when we leave the with block self.emit() def hold(self): return self def ignore(self): self._ignore_levels.append(self._counter) return self def hold_and_emit(self): self._emit_pending() # set our pending flag return self def emit(self, arg=None): if self._counter == 0: self._emissions_pending = False for model, name in self.__get_models__(): model.notify_signal_emit(name, arg) pass else: self._emit_pending() def _emit_pending(self): if not self._emissions_pending: self._emissions_pending = bool(len(self._ignore_levels) == 0) pass #end of classPyXRD-0.8.4/mvc/support/observables/value_wrapper.py000066400000000000000000000052071363064711000225340ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from mvc.support.utils import not_none class ValueWrapper(object): wrappers = [] @staticmethod def register_wrapper(cls=None, position=None): """ Decorator that can be applied to wrapper classes (need to define a wrap_value(label, value, model=None) method). Can also be called with a position keyword argument to force that wrapper to be at a certain position in the list of wrappers. Otherwise appends the wrapper to the end of wrapper list. """ def inner(cls, position=None): position = not_none(position, len(ValueWrapper.wrappers)) ValueWrapper.wrappers.insert(position, cls.wrap_value) if cls == None: return inner else: return inner(cls, position=position) @staticmethod def wrap_value(label, val, model=None, verbose=False): """This is used to wrap a value to be assigned to a property. Depending on the type of the value, different values are created and returned. For example, for a list, a ListWrapper is created to wrap it, and returned for the assignment. model is different from None when the value is changed (a model exists). Otherwise, during property creation model is None""" for wrapper in ValueWrapper.wrappers: wrapped = wrapper(label, val, model) if wrapped is None: continue else: return wrapped return val pass #end of class PyXRD-0.8.4/mvc/support/utils.py000066400000000000000000000063631363064711000165150ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2007 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import os from uuid import uuid4 as get_uuid def rec_setattr(obj, attr, value): """Set object's attribute. May use dot notation. >>> class C(object): pass >>> a = C() >>> a.b = C() >>> rec_setattr(a, 'b.c', 4) >>> a.b.c 4 """ if obj is None: raise AttributeError("Cannot recursively set attribute (%s) on NoneType" % attr) else: if '.' not in attr: setattr(obj, attr, value) else: attr, attrs = attr.split('.', 1) rec_setattr(getattr(obj, attr), attrs, value) def rec_getattr(obj, attr, default): """Get object's attribute. May use dot notation. >>> class C(object): pass >>> a = C() >>> a.b = C() >>> a.b.c = 4 >>> rec_getattr(a, 'b.c') 4 """ if obj is None: return default else: if '.' not in attr: return getattr(obj, attr, default) else: attr, attrs = attr.split('.', 1) return rec_getattr(getattr(obj, attr), attrs, default) def round_sig(x, sig=1): if x == 0: return 0 else: return round(x, sig - int(floor(log10(abs(x)))) - 1) def not_none(passed, default): """Returns `passed` if not None, else `default` is returned""" return passed if passed is not None else default def getmembers(_object, _predicate): """This is an implementation of inspect.getmembers, as in some versions of python it may be buggy. See issue at http://bugs.python.org/issue1785""" # This should be: # return inspect.getmembers(_object, _predicate) # ... and it is re-implemented as: observers = [] for key in dir(_object): try: m = getattr(_object, key) except AttributeError: continue if _predicate(m): observers.append((key, m)) pass return observers def get_new_uuid(): return str(get_uuid().hex) def get_unique_list(seq): seen = set() seen_add = seen.add return [x for x in seq if x not in seen and not seen_add(x)] def pop_kwargs(kwargs, *keys): return { key: kwargs.pop(key) for key in keys if key in kwargs }PyXRD-0.8.4/mvc/support/version.py000066400000000000000000000036051363064711000170360ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- from distutils.version import LooseVersion def _cmp (self, other): if isinstance(other, str): other = LooseVersion(other) stypes = map(lambda c: str if isinstance(c, str) else int, self.version) otypes = map(lambda c: str if isinstance(c, str) else int, other.version) for i, (stype, otype) in enumerate(zip(stypes, otypes)): if stype == str and otype == int: other.version[i] = str(other.version[i]) if stype == int and otype == str: self.version[i] = str(self.version[i]) if self.version == other.version: return 0 if self.version < other.version: return -1 if self.version > other.version: return 1 LooseVersion._cmp = _cmpPyXRD-0.8.4/mvc/view.py000066400000000000000000000177131363064711000146140ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (c) 2007 by Guillaume Libersat # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- # TODO remove gladeXML: deprecated! from .support.exceptions import ViewError import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk Builder = Gtk.Builder # ---------------------------------------------------------------------- class View (object): top = None builder = None def __init__(self, top=None, parent=None, builder=None, *args, **kwargs): """ Only the first three may be given as positional arguments. If an argument is empty a class attribute of the same name is used. This does not work for *parent*. *builder* is a path to a Glade XML file. *top* is a string containing the name of our top level widget. *parent* is used to call :meth:`set_parent_view`. The last two only work if *builder* is used, not if you intend to create widgets later from code. """ super(View, self).__init__(*args, **kwargs) self.manualWidgets = {} self.autoWidgets = {} self.__autoWidgets_calculated = False if top: self._top = top else: self._top = self.top # retrieves objects from builder if available if builder: _builder = builder else: _builder = self.builder if _builder is not None: # if the user passed a Builder, use it as it is, otherwise # build one if isinstance(_builder, Builder): self._builder = _builder else: self._builder = Gtk.Builder() self._builder.add_from_file(_builder) pass pass else: self._builder = None # no gtk builder if parent is not None: self.set_parent_view(parent) return def __getitem__(self, key): """ Return the widget named *key*, or ``None``. .. note:: In future versions this will likely change to raise ``KeyError``. """ wid = None # first try with manually-added widgets: if key in self.manualWidgets: wid = self.manualWidgets[key] pass if wid is None: # then try with glade and builder, starting from memoized if key in self.autoWidgets: wid = self.autoWidgets[key] else: # try with Gtk.builder if wid is None and self._builder is not None: wid = self._builder.get_object(key) if wid is not None: self.autoWidgets[key] = wid pass pass pass pass return wid def __setitem__(self, key, wid): """ Add a widget. This overrides widgets of the same name that were loaded from XML. It does not affect GTK container/child relations. If no top widget is known, this sets it. """ self.manualWidgets[key] = wid return def show(self): """ Call `show()` on each top widget or `show_all()` if only one is known. Otherwise does nothing. """ top = self.get_top_widget() if type(top) in (list, tuple): for t in top: if t is not None: t.show() pass elif (top is not None): top.show_all() return def hide(self): """ Call `hide()` on all known top widgets. """ top = self.get_top_widget() if type(top) in (list, tuple): for t in top: if t is not None: t.hide() pass elif top is not None: top.hide() return def get_top_widget(self): return self[self._top] def set_parent_view(self, parent_view): """ Set ``self.``:meth:`get_top_widget` transient for ``parent_view.get_top_widget()``. """ top = self.get_top_widget() if type(top) in (list, tuple): for t in top: if t is not None and hasattr(t, "set_transient_for"): t.set_transient_for(parent_view.get_top_widget()) pass pass elif (top is not None) and hasattr(top, "set_transient_for"): top.set_transient_for(parent_view.get_top_widget()) pass return def set_transient(self, transient_view): """ Set ``transient_view.get_top_widget()`` transient for ``self.``:meth:`get_top_widget`. """ top = self.get_top_widget() if type(top) in (list, tuple): for t in top: if t is not None: transient_view.get_top_widget().set_transient_for(t) pass pass elif (top is not None): transient_view.get_top_widget().set_transient_for(top) pass return # Finds the right callback for custom widget creation and calls it # Returns None if an undefined or invalid handler is found def _custom_widget_create(self, glade, function_name, widget_name, str1, str2, int1, int2): # This code was kindly provided by Allan Douglas if function_name is not None: handler = getattr(self, function_name, None) if handler is not None: return handler(str1, str2, int1, int2) pass return None def __iter__(self): """ Return an iterator over widgets added with :meth:`__setitem__` and those loaded from XML. .. note:: In case of name conflicts this yields widgets that are not accessible via :meth:`__getitem__`. """ # precalculates if needed self.__extract_autoWidgets() import itertools for i in itertools.chain(self.manualWidgets, self.autoWidgets): yield i return def __extract_autoWidgets(self): """Extract autoWidgets map if needed, out of the glade specifications and gtk builder""" if self.__autoWidgets_calculated: return if self._builder is not None: for wid in self._builder.get_objects(): # General workaround for issue # https://bugzilla.gnome.org/show_bug.cgi?id=607492 try: name = Gtk.Buildable.get_name(wid) except TypeError: continue if name in self.autoWidgets and self.autoWidgets[name] != wid: raise ViewError("Widget '%s' in builder also found in glade specification" % name) self.autoWidgets[name] = wid pass pass self.__autowidgets_calculated = True return pass # end of class View PyXRD-0.8.4/pyxrd/000077500000000000000000000000001363064711000136405ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/__init__.py000066400000000000000000000005471363064711000157570ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. """ A python implementation of the matrix algorithm developed for the X-ray diffraction analysis of disordered lamellar structures """ import pkg_resources from pyxrd.__version import __version__ PyXRD-0.8.4/pyxrd/__main__.py000066400000000000000000000006101363064711000157270ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os, sys # Make sure the current path is used for loading PyXRD modules: mod = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if not mod in sys.path: sys.path.insert(1, mod) from pyxrd.core import run_main run_main() PyXRD-0.8.4/pyxrd/__version.py000066400000000000000000000002761363064711000162020ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. __version__ = "0.8.4" PyXRD-0.8.4/pyxrd/application/000077500000000000000000000000001363064711000161435ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/application/__init__.py000066400000000000000000000000001363064711000202420ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/application/controllers.py000066400000000000000000000541501363064711000210700ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os from os.path import basename, dirname from functools import wraps from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory from mvc.support.gui_loop import stop_event_loop, add_idle_call from pyxrd.data import settings from pyxrd.generic.controllers import BaseController from pyxrd.generic.plot.controllers import MainPlotController from pyxrd.generic.plot.eye_dropper import EyeDropper from pyxrd.file_parsers.project_parsers import JSONProjectParser from pyxrd.file_parsers.project_parsers import project_parsers from pyxrd.project.controllers import ProjectController from pyxrd.project.models import Project from pyxrd.specimen.controllers import SpecimenController, MarkersController from pyxrd.mixture.controllers import MixturesController #, InSituBehavioursController from pyxrd.phases.controllers import PhasesController from pyxrd.atoms.controllers import AtomTypesController class AppController (BaseController): """ Controller handling the main application interface. In essence this delegates actions to its child controllers for Project, Mixture, Specimen, Phase, Marker and Atoms actions. """ # ------------------------------------------------------------ # Dialog properties # ------------------------------------------------------------ _save_project_dialog = None @property def save_project_dialog(self): """ Creates & returns the 'save project' dialog """ if self._save_project_dialog is None: # Check to see if we have a project loaded, if so, # set the paths to match current_name, current_folder = None, None if self.model.current_filename is not None: current_name = basename(self.model.current_filename) current_folder = dirname(self.model.current_filename) # Create the dialog once, and re-use its context self._save_project_dialog = DialogFactory.get_save_dialog( title="Save project", current_name=current_name, current_folder=current_folder, filters=project_parsers.get_export_file_filters(), persist=True, parent=self.view.get_top_widget() ) return self._save_project_dialog _load_project_dialog = None @property def load_project_dialog(self): """ Creates & returns the 'load project' dialog """ if self._load_project_dialog is None: # Check to see if we have a project loaded, if so, # set the paths to match current_folder = None if self.model.current_filename is not None: current_folder = dirname(self.model.current_filename) # Create the dialog once, and re-use self._load_project_dialog = DialogFactory.get_load_dialog( title="Load project", current_folder=current_folder, filters=project_parsers.get_import_file_filters(), persist=True, multiple=False, parent=self.view.get_top_widget() ) return self._load_project_dialog # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, model, view, gtk_exception_hook=None, spurious=False, auto_adapt=False, parent=None): """ Initializes an AppController with the given arguments. """ super(AppController, self).__init__(model=model, view=view, spurious=spurious, auto_adapt=auto_adapt, parent=parent) self.gtk_exception_hook = gtk_exception_hook self.gtk_exception_hook.parent_view = view.get_toplevel() # Plot controller: self.plot_controller = MainPlotController( self.update_plot_status, self.show_marker ) # Child controllers: self.project = None self.specimen = None self.markers = None self.phases = None self.atom_types = None self.mixtures = None self.idle_redraw_plot() if self.model.project_loaded: self.reset_project_controller() self.push_status_msg("Done.") def register_view(self, view): """ Registers the view with this controller """ view.setup_plot(self.plot_controller) if self.model.project_loaded: self.update_sensitivities() view.set_layout_mode(self.model.current_project.layout_mode) else: view.set_layout_mode(settings.DEFAULT_LAYOUT) def set_model(self, model): """ Sets the model in this controller """ super(self, AppController).set_model(model) self.reset_project_controller() def reset_project_controller(self): """ Recreates all child controllers """ self.view.reset_all_views() self.project = ProjectController(model=self.model.current_project, view=self.view.project, parent=self) self.phases = PhasesController(model=self.model.current_project, view=self.view.phases, parent=self) #self.behaviours = InSituBehavioursController(model=self.model.current_project, view=self.view.behaviours, parent=self) self.atom_types = AtomTypesController(model=self.model.current_project, view=self.view.atom_types, parent=self) self.mixtures = MixturesController(model=self.model.current_project, view=self.view.mixtures, parent=self) self.reset_specimen_controller() self.view.update_project_sensitivities(self.model.project_loaded) self.set_layout_mode(self.model.current_project.layout_mode) self.update_title() def reset_specimen_controller(self): """ Recreates only the specimen controllers """ if self.model.specimen_selected: specimen_view = self.view.reset_child_view("specimen") self.specimen = SpecimenController(model=self.model.current_specimen, view=specimen_view, parent=self) self.show_markers_for(self.model.current_specimen) else: self.specimen = None self.markers = None self.view.update_specimen_sensitivities( self.model.single_specimen_selected, self.model.multiple_specimens_selected ) self.idle_redraw_plot() def show_markers_for(self, specimen): markers_view = self.view.reset_child_view("markers") self.markers = MarkersController(model=specimen, view=markers_view, parent=self) def show_marker(self, marker): markers_view = self.view.reset_child_view("markers") self.markers = MarkersController(model=marker.specimen, view=markers_view, parent=self) self.view.markers.present() add_idle_call(self.markers.select_object, marker) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @BaseController.observe("needs_plot_update", signal=True) def notif_needs_plot_update(self, model, prop_name, info): """ This handles needs_plot_update signals emitted by the Application model, in effect it is either a forwarded 'data_changed' or 'visuals_changed' signal coming from the :class:`pyxrd.project.models.Project` model. """ self.idle_redraw_plot() @BaseController.observe("current_project", assign=True, after=True) def notif_project_update(self, model, prop_name, info): self.reset_project_controller() @BaseController.observe("current_specimen", assign=True, after=True) @BaseController.observe("current_specimens", assign=True, after=True) def notif_specimen_changed(self, model, prop_name, info): self.reset_specimen_controller() # ------------------------------------------------------------ # View updating # ------------------------------------------------------------ _idle_redraw_id = None _needs_redraw = False def idle_redraw_plot(self): """Adds a redraw plot function as 'idle' action to the main GTK loop.""" if self._idle_redraw_id is None: self._idle_redraw_id = add_idle_call(self.redraw_plot) self._needs_redraw = True @BaseController.status_message("Updating display...") def redraw_plot(self): """Updates the plot""" if self._needs_redraw == True: self._needs_redraw = False self.plot_controller.update( clear=True, project=self.model.current_project, specimens=self.model.current_specimens[::-1] ) if self._needs_redraw: return True else: self._idle_redraw_id = None return False def update_title(self): """Convenience method for setting the application view's title""" self.view.set_title(self.model.current_project.name) def update_sensitivities(self): """Convenience method for updating the application view's sensitivities""" self.view.update_project_sensitivities(self.model.project_loaded) self.view.update_specimen_sensitivities( self.model.single_specimen_selected, self.model.multiple_specimens_selected ) def update_plot_status(self, x_pos, event): if x_pos > 0 and self.model.current_specimen is not None: # Get experimental data at the sampled point exp_y = self.model.current_specimen.experimental_pattern.get_y_at_x(x_pos) dspacing = self.model.current_specimen.goniometer.get_nm_from_2t(x_pos) # Get calculated data if applicable if self.model.current_project.layout_mode == "FULL": calc_y = self.model.current_specimen.calculated_pattern.get_y_at_x(x_pos) self.view.update_plot_status(x_pos, dspacing, exp_y, calc_y) else: self.view.update_plot_status(x_pos, dspacing, exp_y, None) else: self.view.update_plot_status(None, None, None, None) def set_layout_mode(self, mode): """Convenience method for updating the application view's layout mode""" self.view.set_layout_mode(mode) # ------------------------------------------------------------ # Loading and saving of projects # ------------------------------------------------------------ def _save_project(self, filename=None): # Set the filename to the current location if None or "" was given: filename = filename or self.model.current_filename # Try to save the project: with DialogFactory.error_dialog_handler( "An error has occurred while saving!\n{0}", parent=self.view.get_toplevel(), reraise=False): JSONProjectParser.write(self.model.current_project, filename, zipped=True) self.model.current_project.filename = filename self.model.update_project_last_save_hash() # Update the title self.update_title() def confirm_discard_unsaved_changes(self, confirm_msg="The current project has unsaved changes,\n" "are you sure you want to continue?", on_reject=None): """ Function decorator which will check if a project is opened with unsaved changes and ask the user to confirm the action without first saving the changes. """ def accept_decorator(on_accept): @wraps(on_accept) def accept_wrapper(self, *args, **kwargs): if self.model.check_for_changes(): return DialogFactory.get_confirmation_dialog( confirm_msg, parent=self.view.get_top_widget() ).run(lambda d: on_accept(self, *args, **kwargs), on_reject) else: return on_accept(self, *args, **kwargs) return accept_wrapper return accept_decorator @confirm_discard_unsaved_changes( "The current project has unsaved changes,\n" "are you sure you want to quit?") def quit(self, *args, **kwargs): stop_event_loop() return False @confirm_discard_unsaved_changes( "The current project has unsaved changes,\n" "are you sure you want to load another project?") def load_project(self): """Convenience function for loading projects from different sources following similar user interaction paths""" def on_accept(dialog): # Try to load the project: with DialogFactory.error_dialog_handler( "An error has occurred:\n{0}\n Your project was not loaded!", parent=self.view.get_toplevel(), title="Parsing error", reraise=False): self.model.current_project = dialog.parser.parse(dialog.filename) self.model.current_project.parent = self.model self.model.update_project_last_save_hash() # Update the title self.update_title() # Run the open/import project dialog: self.load_project_dialog.run(on_accept) @confirm_discard_unsaved_changes( "The current project has unsaved changes,\n" "are you sure you want to create a new project?") def new_project(self, *args, **kwargs): # Create a new project self.model.current_project = Project(parent=self.model) # Set the current filename property and update the title self.update_title() # Show the edit project dialog self.view.project.present() # ------------------------------------------------------------ # GTK Signal handlers - general # ------------------------------------------------------------ def on_manual_activate(self, widget, data=None): try: import webbrowser webbrowser.open(settings.MANUAL_URL) except: pass # ignore errors return True def on_about_activate(self, widget, data=None): self.view["about_window"].show() return True def on_main_window_destroy_event(self, widget, event): self.quit() return True def on_main_window_delete_event(self, widget, event): self.quit() return True def on_menu_item_quit_activate (self, widget, data=None): self.quit() return True def on_refresh_graph(self, event): if self.model.current_project: with self.model.current_project.data_changed.hold(): self.model.current_project.update_all_mixtures() self.redraw_plot() def on_save_graph(self, event): filename = None if self.model.single_specimen_selected: filename = os.path.splitext(self.model.current_specimen.name)[0] else: filename = self.model.current_project.name self.plot_controller.save( parent=self.view.get_toplevel(), current_name=filename, num_specimens=len(self.model.current_specimens), offset=self.model.current_project.display_plot_offset) def on_toggled_plot_toolbar(self, action): if action.get_active(): self.view.show_plot_toolbar() else: self.view.hide_plot_toolbar() @BaseController.status_message("Sampling...", "sampling") def on_sample_point(self, event): """ Sample a point on the plot and display the (calculated and) experimental data values in an information dialog. """ self.edc = None def parse_x_pos(x_pos, event): # Clear the eye dropper controller self.edc.enabled = False self.edc.disconnect() del self.edc # Get experimental data at the sampled point exp_y = self.model.current_specimen.experimental_pattern.get_y_at_x(x_pos) message = "Sampled point:\n" message += "\tExperimental data:\t( %.4f , %.4f )\n" % (x_pos, exp_y) # Get calculated data if applicable if self.model.current_project.layout_mode == "FULL": calc_y = self.model.current_specimen.calculated_pattern.get_y_at_x(x_pos) message += "\tCalculated data:\t\t( %.4f , %.4f )" % (x_pos, calc_y) # Display message dialog DialogFactory.get_information_dialog( message, parent=self.view.get_toplevel() ).run() self.edc = EyeDropper(self.plot_controller, parse_x_pos) # ------------------------------------------------------------ # GTK Signal handlers - Project related # ------------------------------------------------------------ @BaseController.status_message("Creating new project...", "new_project") def on_new_project_activate(self, widget, data=None): self.new_project() @BaseController.status_message("Displaying project data...", "edit_project") def on_edit_project_activate(self, widget, data=None): self.view.project.present() @BaseController.status_message("Open project...", "open_project") def on_open_project_activate(self, widget, data=None): """Open an existing project. Asks the user if (s)he's sure when an unsaved project is loaded.""" self.load_project() @BaseController.status_message("Save project...", "save_project") def on_save_project_activate(self, widget, *args): # No filename yet: show a dialog if not self.model.current_filename: self.save_project_dialog.update(title="Save project").run( lambda dialog: self._save_project(filename=dialog.filename) ) else: # we already have a filename, overwrite: self._save_project() @BaseController.status_message("Save project as...", "save_project_as") def on_save_project_as_activate(self, widget, *args): self.save_project_dialog.update(title="Save project as").run( lambda dialog: self._save_project(filename=dialog.filename) ) # ------------------------------------------------------------ # GTK Signal handlers - Mixtures related # ----------------------------------------------------------- def on_edit_mixtures(self, widget, data=None): if self.model.project_loaded: self.view.mixtures.present() pass # ------------------------------------------------------------ # GTK Signal handlers - Behaviour related # ------------------------------------------------------------ def on_edit_behaviours(self, widget, data=None): #if self.model.project_loaded: # self.view.behaviours.present() return True # ------------------------------------------------------------ # GTK Signal handlers - Specimen related # ------------------------------------------------------------ def on_edit_specimen_activate(self, event): self.project.edit_specimen() return True def on_add_specimen_activate(self, event): self.project.add_specimen() return True def on_add_multiple_specimens(self, event): self.project.import_multiple_specimen() return True def on_del_specimen_activate(self, event): self.project.delete_selected_specimens() return True def on_replace_specimen_data_activate(self, event): if self.model.single_specimen_selected: self.specimen.on_replace_experimental_data() return True def on_export_specimen_data_activate(self, event): if self.model.single_specimen_selected: self.specimen.on_export_experimental_data() return True def on_convert_to_fixed_activate(self, event): for specimen in self.model.current_specimens: specimen.convert_to_fixed() def on_convert_to_ads_activate(self, event): for specimen in self.model.current_specimens: specimen.convert_to_ads() def on_remove_background(self, event): if self.model.single_specimen_selected: self.specimen.remove_background() else: self.project.remove_backgrounds(self.model.current_specimens) return True def on_smooth_data(self, event): if self.model.single_specimen_selected: self.specimen.smooth_data() return True def on_add_noise(self, event): if self.model.single_specimen_selected: self.specimen.add_noise() return True def on_shift_data(self, event): if self.model.single_specimen_selected: self.specimen.shift_data() return True def on_strip_peak(self, event): if self.model.single_specimen_selected: self.specimen.strip_peak() return True def on_peak_properties(self, event): if self.model.single_specimen_selected: self.specimen.peak_properties() return True # ------------------------------------------------------------ # GTK Signal handlers - Phases related # ------------------------------------------------------------ def on_edit_phases_activate(self, event): if self.model.project_loaded: self.view.phases.present() return True # ------------------------------------------------------------ # GTK Signal handlers - Atom Types related # ------------------------------------------------------------ def on_edit_atom_types_activate(self, event): if self.model.project_loaded: self.view.atom_types.present() return True # ------------------------------------------------------------ # GTK Signal handlers - Markers related # ------------------------------------------------------------ def on_edit_markers_activate(self, event): if self.model.current_specimen is not None: self.view.markers.present() return True def edit_marker(self, marker): self.show_markers_for(marker.specimen) pass # end of class PyXRD-0.8.4/pyxrd/application/glade/000077500000000000000000000000001363064711000172175ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/application/glade/application.glade000066400000000000000000002026041363064711000225240ustar00rootroot00000000000000 PyXRD_Main New Project New Create a new project 424-new-file True Open Project Open Open a previously saved project 144-folder-open True Exit Exit Quits this program 063-power True Show plot toolbar Show plot toolbar Show the plot toolbar False PyXRD_Main Edit Mixtures Mixtures Edit the mixtures and find optimal solutions using the multi-specimen method 427-blender True Import specimens Import specimens Import multiple specimens using multiple experimental data files 358-file-import Edit Atom Types Atoms Edit the atom types in this project 426-atom True Add Specimen Add Specimen Add a new specimen to this project 190-circle-plus True Auto-update calculated patterns Auto-update calculated patterns Save Project Save Save this project 342-hdd True Save Project as Save as Save this project using another filename 342-hdd-as True Edit Project Edit Project Edit the properties of this project 280-settings True Edit Phases Phases Edit the phases in this project 139-adjust-alt True Edit Behaviours Behaviours 431-behaviour False False True False PyXRD_Main Shift pattern Shift Shift pattern using a reference peak 422-shift True Smooth data Smooth Smooth noisy data 421-smooth True Edit specimen Edit specimen 150-edit True Add Marker Add Marker Add Marker 190-circle-plus True Edit Markers Edit Markers Edit Markers 066-tags True Sample Point Sample Point Sample Point 090-eyedropper True View Statistics Statistics View Statistics gtk-about True False True Replace data Replace data 358-file-import True Export data Export data 359-file-export True Strip peak Strip peak 428-strip True Add noise Noise Add noise 429-noisify Peak properties Peak properties 430-area True False PyXRD_Main Remove Background Background Remove the background 420-background True Save Graph Save Graph Save Graph 423-save-graph True Refresh Graph Refresh Refresh Graph 081-refresh True Remove Specimen Remove Specimen Remove a specimen from this project 191-circle-minus True Convert data to fixed slit Convert to fixed slit Converts the data to fixed slit. This is taking the ADS settings in the specimen's goniometer tab into account. The ADS checkbox does not need to be checked however. gtk-convert True Convert data to ADS Convert to ADS Converts the data to ADS. This is taking the ADS settings in the specimen's goniometer tab into account. The ADS checkbox does not need to be checked however, and this will not be changed! gtk-convert True True False 071-book True False 049-star 900 400 True False PyXRD PyXRD_Main center 800 600 True False True False True False _Project True True False PyXRD_Main True new_project True False True True True True open_project True False True True True True save_project True False True True True True save_project_as True False True True True True False edit_project True False True True True True False gtk-quit exit True False True True True True False View True True False PyXRD_Main refresh_graph True False True True True save_graph True False True True True True False remove_bg True False True True smooth_data True False True True shift_data True False True True True False True False Toolbars True True False show_plot_toolbar True False Show plot toolbar True True False _Data True True False PyXRD_Main True add_specimen True False <PyXRD_Main>/project_actions/add_specimen True True True True import_specimens True False True True True True convert_to_fixed True False True True True True convert_to_ads True False True True True True False edit_phases True False True True True False edit_atom_types True False True True True True False edit_mixtures True False True True True True False Help True True False Manual True False image2 False True About True False image3 False False True 0 True False True False icons new_project True False New True 424-new-file False True open_project True False Open True 144-folder-open False True save_project True False Save True 342-hdd False True True False False True save_graph True False Save Graph True 423-save-graph False True True False False False remove_bg True False Background True 420-background False True smooth_data True False Smooth data True 421-smooth False True add_noise True False Add noise to experimental data Add noise True 429-noisify False True shift_data True False Shift data True 422-shift False True strip_peak True False Strip peaks True 428-strip False True peak_area True False Peak area True 430-area False True True False False True edit_phases True False Phases Phases True 139-adjust-alt False True edit_behaviours False False True Behaviours True 431-behaviour False True edit_mixtures True False Mixtures True 427-blender False True edit_atom_types True False Atoms True 426-atom False True True False False True edit_markers True False True Edit Marker Edit Marker Edit markers True 066-tags False True sample_point True False True Identify Points Identify Points Select Point True 090-eyedropper False True refresh_graph True False True Refresh the graph Refresh the graph Refresh True 081-refresh False True True True 0 True False end False 1 0 0 True True end 1 False True 1 True False False True 2 True False 6 6 True False True 100 True True False False False True False 2 1 1 True False 3 3 <b>Output</b> True 0 GTK_FILL True False vertical 600 True False 1 1 1 1 True True 0 1 2 True False True True 3 True False True False 5 2 True False 137-cogwheels False True 0 False True True 2 True True 0 True False True False False 10 end 1 True False False False 5 end 2 False True end 4 False 5 True True dialog main_window PyXRD Copyright 2018 - Mathijs Dumon http://users.ugent.be/~madumon/pyxrd/ Online Manual Copyright (c) 2013, Mathijs Dumon All rights reserved - BSD-2-Clause ("FreeBSD") License. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Mathijs Dumon image-missing False False False True end 0 PyXRD-0.8.4/pyxrd/application/icons.py000066400000000000000000000021431363064711000176300ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. #TODO make this gtk agnostic from pkg_resources import resource_filename # @UnresolvedImport import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import GdkPixbuf # @UnresolvedImport def get_icon_list(): return [ GdkPixbuf.Pixbuf.new_from_file(resource_filename(__name__, "icons/pyxrd_icon_16x16.png")), #@UndefinedVariable GdkPixbuf.Pixbuf.new_from_file(resource_filename(__name__, "icons/pyxrd_icon_24x24.png")), #@UndefinedVariable GdkPixbuf.Pixbuf.new_from_file(resource_filename(__name__, "icons/pyxrd_icon_32x32.png")), #@UndefinedVariable GdkPixbuf.Pixbuf.new_from_file(resource_filename(__name__, "icons/pyxrd_icon_48x48.png")), #@UndefinedVariable GdkPixbuf.Pixbuf.new_from_file(resource_filename(__name__, "icons/pyxrd_icon_64x64.png")), #@UndefinedVariable GdkPixbuf.Pixbuf.new_from_file(resource_filename(__name__, "icons/pyxrd_icon_128x128.png")) #@UndefinedVariable ] PyXRD-0.8.4/pyxrd/application/icons/000077500000000000000000000000001363064711000172565ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/application/icons/pyxrd.ico000066400000000000000000006454461363064711000211430ustar00rootroot00000000000000hF 00V( *( ###)))***rrrZRJB:1,'"bYKQ0'" hAP=89niaOI@&!uC aTH80&yWjAN)xrmg`XPJ>7/*% zsh\XM 62)# ~xmcaV;60+$}tkf_ =3.(fW E=5-Bw)]LG<4{xd^SLF?=qke[VLFDvplb\VL|wojb\U( @       "  #! "$##%$($&$'%*')&('&'+')((*)-+')+*/,.0.*,.,-0.302402021132243459973475968;956978:9=:;><8:;?:=;<><B>@=@>>@??A@@BAACAHEAGDFCEDDGEEHFFIGFHLHJIMKFILJPMOLNMLMRRPLNPOORPUSNPSQWSURSXRUSTVUZWSUWV[XTVXWVW[\XZXZ^Z\[_]Y`\^[^\a_Z]`^c_b^_ddb]_b`adbcedhfadfedejehfgihgjhmkfhkinjlikpjmklommpnsoqnotprvqtrwsursxrusxuqsvtzvxuxvvw|vywwzxx{y}xz}{|~{|{~|}~~ĿyqiobaWNMJ?56./&$ {{jfd_ZNLC>?61+& }}upoa^YQLHD<8/(%! |tnha^UNIF?<9./$  }{smfc]YQKFC563+ {uoid^WQJ?B85.-#!! }yqnfe]UOIE>;4/)% }xtoac]YPID?56/)$! wqlfb[UOHF@:3.(#ɘ}tqnfd_\XSPLC>@850/,$!Ža}sooia^WKIF>55/(#Ŀ |}xrng`ZPQHD<42-&"ſ[?`}Pcl APJ/'OFTLxjn&9+ >~XPpo ]|aFT3Q^q^}eMyH5+tʾ>!UgSS@]avt[y5c? D{ wOp>mJGƯɿ}oUQM~}vpqnjfea^ZWRNJIGCA<;941.,KPTZƽv{kie\ž}}ujoaɽzulhƺ}xtozrſ}xƿ(0`»˕xqrȽ~xyiabjbc ʾð׊}~Ӗ2%')žºxrs8,- /&'HBCjgg3'(+LBCe\], ldeҾ͡:.0C:;TMNQJKICDKFFVRRpnnٗ  &ټ揋̍ smnursljjvuuleec[\yrsoghꏋ>24J@A9-.uno A56˽I>?}tu럙 jbcг 1%&A56YQQ¸ۼ$ 䥣===QRRIHH֡*oghƻ !軹MMMLKL߻ȹYYYNNNjjjC89ẶwuuMMMQRRfffddddddijjgggcbbooottthhhiiiϨlllpppqqqnnnpppͱggg```jbcݶUUUYYXiiidddiiiggglllrrrsssrrrjjjsssЭggg___yoqKJJTTTddddddooo|||vvvmllkkkvvv}}}yyyæhhhXXX```^^^^^^JJJdddXXXeeekkkooodddnnngggtttzzzuuuĪhhhܛ^^^bbb\\\[[[[[[|||nnngggtttppptttǮgggLLLSSSKKKhhhoooUUUPPPOOOiiidedooopppzzz|||gggxxx޿qqqsssƮhhhKKKYXXiiiڧOOOHGGȼJJJQQQaaa{{{oooyyy~~~~~~hhhpppyyyǫhhh```YYYiii淵vmn[[[yyyxxxmmmqqqwxwhhhvvv}}}pppjjj___```^^^fff"dddwwweeeuuuϭnnnhhh앏 vvvuuuuuutttvvvwwwvvvvvvuuu˯mmmrqrwww{{zzzz廉*ƹŸ򹶵;./MBDf\^}vvؓީ㼹(zstLAB6)+- "+ -!3&(A57h`a¾z{E:;'  4')e]^ýaYZ-!  <01ŽþLAB!3&(vnoòSHI3&(, 8,.nfgýE:;.!#smnæg_`0#% (_UWĿD9: 1#%xy ODF#(oghVLM6)+smn^WWOGHG?@D:;A78>45<34>56@79B:JCDQJKXRR^YYgbbxttàKAA3&(ĵohh& E:;pijOEF1%')'(&&&&'(+ "-#%/%'1(*5,-901=55@99D<=G@AJDDOIJ\WXpllƨNDE  I>?ſ7*, bZ[g_`8,-)#    !#%') ,!#.#%0&'2(*4+,7-.901;44=66>77@9:B;F?@HABJDEMGHPKKUQQ]YYifg|yz2%'=12ƻ,  6)+) H=>tmo>24%     "$'(* !-#$/$&1')3*,5,-8//912<45>67@99B;>F@AICCKEEMHHOJJQLLTOOXTT^Z[c``pmnȬMBC e]^ŮRHI (_VW}~6)+$phi{vv=12$    #%'(+ ".#%0&'1(*4+,6..8/0:23=55>77A9:C<=E>?GABIDDLFFMHIOJKRMNTOPWRSYUU[WW_[[fccnll~}}ʾyy(5)*.!#&jbc}}0#$9-.K@B(   !$&') ,!#.$&1'(2)*5+-7./:12;34=66@89B;;D=>F@@HBCJEELGGNIJQLLSNOUPQWSSYUV[WX^ZZ`\]c``geepmm}}Ǫ?34!ogiDZVMN  ULMïlde%`WX`XY/"$    "$'(* "-#$/%&1')3*+5,-8/0912;45>67@89B;>G@AICDKEFMHHOJJQLMTOPVQRXSTZVW\XY^[[`]^c``ebbheemjkspq~||˿'8+-ž3&(3'(PFG 0"$|}<12    #%() ,"#.$%1'(2)+5,-7./:01:23<56?78A::C<=E??HABJDDLFGNIIPKKRMNUPPWSSYUU[WW]YY_\\a^_daafccheejhhljjpnnwvv˵UKL "mefȻyrs# jbcŽ8,- OEF_VW- "    !#'+ ".$%2()5+,7-/8/1;24<34>56>66?78@89A:;C==F?@IBBKEEMGHOIJQLMSNOUPQWSTZUV\XX^ZZ`]]b__dabfcdhffjhimklomnrppvst|z{ɢ2%' B67ǫC8:6*,ĸztu)(zuuB78   %.#%6+-?56G>?LDEQIJUNOWPRWPRXQSXRRVPPTNORLLOIILFFICDHABICCLFFNHIPJKRMMTPPVRRXTT[WW]YY_[[a^^c``ebcgdeiggkijnklpmnrpptsswuv{z{̻lcd *”, bY[éTIK  A57}wx8+-  &.!#6+-QHJupqnjk\XXXSSWRSUPQSNNSMNUPPWSTYUV[WX^Z[`\\b^_dabfcdhefjhhljkolmqnosqqutswvvzxy}{{˰D89  PEFȸlce!1#%ž4'(!jbch`a.!# $8-/]UV{|vstebb]YY[VVZUV[VW\XY^Z[a]]b__dabgddiggkiinklpmnropsrruttxvvzxy|{{~~ǡ/#$ 2$&ȰH=>  SHIŴldf!3&(UKM% (B78smnzxxkhheaaa]]`\]b__c`aebcgdeighljjollqnospqtssvuuyxx{zz}||~~zst#'NDE &|}Ǥ1#%%|}>24  XNOE:<$B78{||xylhiieffccgddiffkhimjkpmmrootqrvtuxvwzxy|{{~}}̱F;< LAB=02 VKMʽxqr#2%&ź|uv)*|vw23&~)     %QFH̾wop% OEG9,.6)+PGH$ 7+,~xx~~̤/!#0#%ɥ8+-   #%'))+H<>εQGH#vnoƸrjk% B78¿F;=C89|tv$F:;εQFG&%*/!#4&(7)+9+-:-/;./NCDϮ>24  /"#ħMBC  G==¾|vw8+- %VLMϸSHJ "umnÅ~9,.0"$8+-A46H;=L@BPDEQFGRGH[RS̟?35%   F;<Ǿ4&( H>>`WX,- g^_α=12-!ϰPEF?24J>?VJL`TVf\^kabndeoefrhi̐J>@3%'*$ #umoǶpij% ;/0xy~wxA564'(xrs˦0"$9,.ummTIJ]RSlbcypqz{ύYNPE9;9,./!#& , ĨQGH 'E:;rkl|vwQFH*  9-.ǎ( KAB˝j`aofgz{Ւmde\QRMAB@243%'(   :-.Ü;/1 !&$  ?34Ѿe\] jbcԾ|tuwx۠}~tklcYZRGHC674&() "VLMȼ2$&H>>ѵ?35(̖¾޴{rsh^_UIJC784')(3%'Ƶskl*!QGHϫ0$%2$&׹¾KJJ;;;===>==>>>@??BBBHHHWWWwwwŹvwi_`TIJB574&(E9:ƭd[\'(]STȕ*B67ϫť$$$ !!!%%%%%%%%%%%%%%%$$$%%%'''211ZZZƾ~uvg]^RFGD8:_UVīf]^(-!jbd|tu%`XXٵʧ#""!!!mmmKKK000###+++ihhȿzqrcXYXMOxyĬh``.!" 6)+vpqӾaWX$zz½ͩ$$$111}}}111QPPɽtklkbcŲ{}9,.      %C89ԹH<> *ͪ$$$333䨨;;;^\\ȷ}~wxǼRIJ(       %;./jacԳ8+-/!#ͪ$$$333쭭888%$${zz°|uvB79)     #&'()1#%?34\RSӦ4') 7*,Ω$$$---흝(((333˾ǼOEG7)+2$&2$&4&(6)+:-/@45D8:I=?SHIphi͊2$&  UJLΧ#""HHHUUUXXXddd}}}```ZYYǾǿxzxqruno{st~qhi1#% #|tu˪<;;$$$,,,...///---())*++___333---¿dZ\4')$  , uvv333```lll###lkkdZ[9,.( 2&'¾ᳲ888***'''222e[\=02+  ?34߻℄ MMMGGG! 澼g]^B57/!#"  PDF޼)))+++(((TTT}}}iiiiiiiiihhhhhhhhhhhhhhhiiihhhiiiyyy㮪g]^E9;1#%# d[](((+++555000}}}iiihhhhhhhhhhhhhhhhhhhhhhhhiiihhhiii|||}}}hhhggghhhhhhhhhhhhhhhhhhhhhhhhhhhiiizzzqqqghghhhhhhhhhhhhhhhhhhhhhhhhhhhlll❝mmmggggggggghhghggggggggggggggggghhhppp㈈jjjggggggggggggggggggggggggggggggggghhhjjjmmmrrrxxx¼444---000///////////////000---...ߝe\]G;=3%'$ %{|~vwzqr}}} RRR]]]!!!vvvkkk!!!!!!,,,///////////////000///)))&&&ttt߃+++(((...//////////////////---"""]]]}}}$$$---/////////////////////+++JJJ<<<---000//////////////////---###***FFF)))////////////////////////////////////000//////...---+++)))''''''+++999gggý''' AAA&&&ېdZ[I<>3&($ , sklkab}tuBAA+++))):::𼼼888MMM...,,,444(((OOO111uuu NNN""">>?߂"""DDDXXX@@@;;;"""{{{jjjUUU>>>...)))%%%&&&***000LLLĽ'''***\\\***؎eZ[J=?4')$ 3&'qhibYYtllڛ(((GGGEEE)))撒###111߃###AAAKKKjjj길555!!!uuu%%%ppp---???<<<'''???"""<<>>(((PPP&&&<<<222գIII ...uuu»(((+++aaa***lbcQEG>>eeeº(((+++aaa*+*彸tjkcXYVJLLABF:;A46?24XMNԞ˻>==***jjj ggg虙***666늊%%%:::??? ;;;%%%ttt&&&vvv000@@@FFF'''SSS$$$<<<222}}}777+++++++++++++++...===]]]򴴴999&&&Ǿ(((+++aaa+++ð{qroeff\]_TUYOOXNOxpq۵͓'''JJJ扉,,,555lllMMM```cccmmmLLLbbb___ttt&&&vvv000@@@葑%%%FFF򣣣(((888;;;111kkk(((******)))'''(((***+++)))888{{{,,,333ú(((+++aaa+++z||stwmnyqqǾFEE HHHJJJPPP'''>>> :::'''🟟+++444댌&&&999sss&&&vvv000@@@CCC###TTT""";;;111kkk###mmmuuuBBB&&&!!!>>>ttt SSSǽ(((+++aaa+++ÿʟ&&&666󥥥###***돏&&&VVV飣,,,444(((;;;>>>$$$:::)))sss&&&vvv000@@@똘%%%DDD+++:::;;;111kkk'''ϥSSS&&&)))qqq999&&&¹(((+++aaa***fee%$$xxxQQQ 000,,,hhhLLLbbbRRRddd\\\RRRdddsss&&&vvv000AAA<<<$$$OOOxxx;;;111kkk'''ѕ555"""lllꆆ"""QQQż(((+++aaa)))׵000+++--- :::aaa"""wwwDDD!!!<<<CCC999芊###<<>>}}}놆111rrr&&&vvv״sss444888:::111kkk'''뵵%%%...///kkk(((+++www333((((((((((((((((((***000UUU󱱱+++666񤤤***KKK222,,,666$$$LLL(((111픔"""***rrr&&&vvveee(((,,,:::111kkk'''겲%%%444///nnn(((+++```)))++++++++++++***&&&""")))RRRRRR"""LLL"""ꂂ!!!TTTuuu&&&sss숈 KKKjjjKKKXXXeeerrr&&&vvvOOO000333333333444666:::KKKwwwڑ///---:::111kkk'''榦'''EEE퓓...www(((+++aaa(((ϸAAAAAA񜜜&&&MMM󮮮(((666III)))򵵵+++BBB333++++++444888(((rrr&&&vvv---333;;;;;;:::666000***!!!:::ↆ%%%;;;:::111kkk'''ⓓ)))bbb}}}---(((+++aaa+++TTTccc///777nnn%%%xxx,,,>>>===+++hhh&&&>>>"""쎎'''<<L@B}~焄$$$AAAnnnJJJLLLqqqEEErrrttt&&&vvv𺺺𡡡###999:::111襥888###rrrŽ(((+++䓓+++555}~}tuqije[\WKMI=??24G;=򳳳555---󫫫...333𰰰555,,,111000ttt&&&vvvMMM""":::111ݒ555%%%mmm(((+++▖111'''ⷳ~vwpfgkbce[\]RTTIII=>?248+,E9:NNNpppAAA |||も###???vvvDDDuuu&&&vvvqqq???:::111rrr***(((rrrü(((+++΂///&&&nnnݰjabYNOVKLQEGK@AF9:>027)+2%'B78|||!!!AAAsssNNNWWWjjjIIIwwwuuu&&&vvvvvv&&&777:::111қ===444ž(((+++љFFF"""111ڬ[PQG;=E:;C68>33:./6(*1$&/!#B57򲲲222...𜜜,,,555ﵵ777)))򬬬222...uuu&&&vvvաFFF 777:::111ȀAAA'''!!!HHH((()))ʧuuu@@@%%%###HHHڪQFG;.0:-/8+-6)+4'(1#%.!#- "B67GGGddd???$$$卍$$$<<骪{{{rrrrrrqqqpppppppppqqqqqqqqqqqqqqqxxx妦zzzqqqpppqqqqqqqqqqqqqqqqqqqqqqqqqqqxxxyyyppppppppppppppppppppppppppppppppppppppppppppppppqqqsssxxx݌rrrooooooooooooooooooooooooooooooooopppqqquuuyyy޺MAC5')3&(3&(3&(3%'3%'3%'3&(K@AĿ࿽PEF7*+6)+6)+6)+6)+6)*6)+6)+OEF¾SIJ:-/9,-9,-9,-9,-8,-8,-:./ZPRVKM=02;/0;.0;.0;.0;.0;.0?35h_`¾¾½YOP@45?24>13>13>13>13=13D8:vno¿`VWB67@45@46@46@45A56A46I=>{|޼kacG;I=>I=>I=>I=>H<=VKLֻՎSHJK@AK@AK@AK@AK@AJ?@ZPQսؘWMNNBDNCDNCDNCDNBDMBC_UWۣ\QSPEFQFGQFGQFGQFGQEGd[\ޭ_VWSHITIJTIJTIJTIJUIKj`a¾ඳdZ[VKLWLMVLMVLMWLNXNOofg¿g^^XNOYOPYOPYOPYOP[QRtlllbc[QR\RS\QS\QS\RS_UVyqsqhi^TU^TV^TU^TU_UVcXZ~vxvmobXYaWYaWXaWXbXYf]^{}{suf\^d[\d[\d[\e[\j`aсyzjabh^_g]^g]^h^_mdeӆndekabj`aj`akabqghՋqhinefmdemdeneftkl֑ulnqgipfhpfhpghwnoוxpqtklsiksiksikzrsٚ}tuwnovmovmnvmn}vwڞxyzqrypqxpqxpqyzܤ{||stzrszrs{rs|}ު~vw}uv}uv}uv~౬yzyyyyxx⸴|}||{|z{㽻~~’ȕΘӜ؞ۡܤީఫᶲ㼸ðɴηҺֽ¿PyXRD-0.8.4/pyxrd/application/icons/pyxrd.png000066400000000000000000002745551363064711000211540ustar00rootroot00000000000000PNG  IHDRN5(sBIT|d pHYs\N\N0K$tEXtSoftwarewww.inkscape.org< IDATxyUS묙`X} x\bP\$" Aْ WW@Ⱦ ( $f,9}lU]]3 F8P /N1  /N1 @,p  /a$'/T^ @8D1e=C\@|j*rz  QNENNxb '@|Z8"@K@|@@Lp > 'X@|@@Lp >@8'f)NćS:k4NLS N1ive|⸬# lj4AD>kNć'5Z'Cq5@ pYKDn. "" xPMDN@<&F 8'@p  T @8q1Năz t r@8q1Nă [ 1N'@z-}PN4xb 洼pbUf2@8ĜNXUPoᔩZNWI'1zIq5^8 &Q,IE+"d4xbΨNCēn;ADz' !qpVNZ!󌈜!B@#1JU <}F4)E B@:@iDD[;۾P"KhTh|u 洂pbCwTUy ՗$I2=_RAsZA8QIɼJ*I=FѪMuDzaV= ‰P=iR&M 6FRЊixztY[# $ Z,J065yoGTj&M QI+'VL&Q$;N1! uip6;F--"Rt~&a@Edv=׉WH3Ҟ ԞdBQE'ʗ)*[ήvѪ ZF 'F%}UGMAѾfy -A7ޥ6pE'=N:Ex.I'^B"iGB¼)W$''I5F: $-"X1{j9h X:)p}ST&xH@HZD8Iخ5|/O2R H0h xP#f׃pI'HTCx^un'"'޳ N=$*v"Z.͠h{qMUyqzBtEHU2߅ s:CՃpI 'SqJD,O1@8ZB8`"7ެ% ;N$JsS)$ɖzͥ^rc3o%G/% 0"ɺ&IGuB$liITtA}#uh6vFryϼhse * T5]2@$j*dy')xI(/G* Pz2o}3(#1;{ڊϹϻYpoZ<5 w4'!)hes8 'ۂm:x: xS-M3<C^… _~yސ޸!ZMm &"Zӄ=oa[NMfd_5\A+ı Q#{-BS !dcL8%O*y'$y&aG 4[8Lm5ر78BH~i*yJp^!1FZ lli*¸MTqFH YfLVԛl[kQD^q˥XD#q,]iS^idiiDf-=6!zgYQK)lʲoy„K^ɘ&uEIi&!+̶Ɨp"UZf4HARI1N&Sesm 5ekݶNH)"g =kD^MeEt̶FTk-6Z)M"= e۶ʦfFm{49D$[*xLz+'<KVWz>]oһA F5uIK#2R*fMՄ$A˼x\4N6bIY<&+s&}ͳ1TiJh>q9_nvVIuLI[=-ݪ}EcAE,&e#N6Zdmݖٌy8%氲P ~WݪBwh4ۊ#hAA 9DBQsh7 @e]Vk$Ҫr*?a4mս>'ɶ) qk=*lJ~wL;x)8T˓S^B(QeV<Qìz+.b[YZ9tu\`JE $(8>4DH S6u?6 mD$m2NǚI+ydeF4ٔsGs-Ti+ri+2kuٶ>yA 8<8AGCa+~F$Ed-Qՙ*;Ivm1}q"gP b;$>R%TFBPUMIqQ"JW廛b1"\ w~V A+گ3}L,FC~O$+A˻ JuP&T7E3cJ($R&wi JO\<mnۦ s\Ufd T M"Z8auG\GOld*x&xG٪rf&TQi\L]PǤb/4K8Va+Em8l~HPQ85&Tm Sm*[_|Ÿc$/L-b+d랯F\^WEb{aVҶΔ}gtYg\: bCɎLyKU'.6RlCY]Bww7+ )JEϨg%\ec(myom#!ҋ!p![1e[QfW ce=ɂ>w~1mh3lBĒI*-Yt_06F]aecɶm,>STk{M2Q:^~wTSwB>)h~ dϏ'c-M#ide+v%I+.oSGeJv1VQ9Ȧ-J 'RԦML1QbShm#~9s6BSʡ^w:@VM]ROfjeP,+|*P`}V (ȧCVj1dy48'F.]a s϶3Y|luʅ<ɶmIv1O0_l_%HdzMI:.;FZz ']3'Y#JTOOsGc1{*]1^>hy罞~tZۊæ-p!q'p;dp~QOjN^+Dd@roq"ƀLap}yEl1&s5=_[1m$wZꭸDİݲmϘ*uʺre*[)?/Hޒ3-N0բI~'_4U.""ch}HB'lqE}~6"}ϊ9,ՊmjHѬ|t%|d$YۍԨʜ˔œCIQꞡ7\|i!x aSK{MmW-}a*b>/t.ʕ|J4ik&&l+ll!ۼDޒu0D1oYՌ8&$VIg܀wvv&2Ljѐˉ?I!5/̰ؒO_^,QnE ȶm* Ǧmk$\!Q IDAT=hxj \adDgMAW5bES*œ/$$}wFL4:L47O(%|Fx #lڰbɦ2 )~t\GvXLeKWFp+so6y/_g&Ѯϗ*4s:r"e24\!ۢUik˦2LfxTSykxyUu*: W*EQ5N8ZٶPUAZ n3EnJjzO2cSwĒXw ©2w;]g4Wϗ*0lg(tm2Y2~ Z?$URydD}m*'c%QM2V&Ii)862/Ʒڲt:rǡa##F %>_TߪJɾLKҰ2AMAgN^49$0FO^B#+FIF=N2dEVzSed2TsDPl:4r*Khqkఅ/K2znhY䯯Nq: R7Ͱ׺KlLmWvKמ9dnTƫI8F (UGMl淃)<F0ԉ*yyNL7m%^gQ/$+XW baL)aUqd2L&4ޣ s}|#H z`S$Š&%9&k<_<'BhǩIet6F,6lOFkM)/, M9>ߩj4eޖ=/&zA骥jsⶩint햮6Su,6I,[|ZS70)/Q'1Dh\Gr]>̲+$TjTlYWB:JɴÆT{:;;+LզTa؈&Y`uBO1dJ]OQI+ I@QRa^bzd7t[ʠˉi҅(pi L^BSXē(lD0i5ZUؼ7#&=6핿=ј 4mn,~|fSW (jU.b '** S`S ؊&~#7>{in6$?*U%lP= '>t/xՈ%M,z.r#eEfL<ka:85ΰ¸l6:lڭ Zۭ䳩ݒeHy+w2&#M^yc>6yGjOaDI&T:ґ v;?W G舋oxHf#:Hd:zu*bPOn:NQt!D%wtW "?0q<6U]3z4D*D$dT-DRqâI%duCe_Uf5Fg$&41vU,*h_&t4(œ.\OlD^3NZē6t/l &GYs'>pnQIWTIezx݊Ϯpq8iH(ziX@q۵?P>RԜ 'haRIf (tJ=?}ns=չ^x [by 8X_2 FH©V$T"#1}X[[tMfϞEШZK.?߷N;'X'DĭeI8e?iuI5jN8aHMӦM+?m.c̉8I#AdD!t:mVSMVfm{n̙r`OWO2V(~F[o]zSN-L:5?mڴ$>]]{TS6z@uj0È%q "T,4KMU jP(Лos=s̚5k0=e$6>byD(zw߁ ~"]b``͟?“O>:2)kyz/2{d!yx'xӻ[;= В%Kk1JGA__ [u]vm3f瞹{`wtǦNBUO>VH<]i=PFi©QxG֭s֭[s$m6ӧnoc=rnmn5o+9v ӳD;ʦgTfēو'+ :|ԃ>wۑL&;??6|s>vﱓyd[|Q61>4__Zw&S,_L5(vSJJ|SyWT]|s=m+VH*΋->ljѢE=^xz{l˜q"8qMLٽDXzuSNW-j* .~ŏ~A-B^~/XlYhĉŃ:hC;wnGGn&U6Wv˥d#U \V\XreoXiKnv͚5kpwQ8$v4^OY)L kMy񤲑Fxj'F@zjb ƐS˗/O}_g}/͛7ʔ)9R7B:jL:A¶us4Uw8Eϓ &<I^~&V<"d 4-YdݬY&r9Ir~3fV?7섓C n2h_WǭYfDtnwaW&t쀈Xzs7gol*>zGuTogg'_ēLlϰ' 'UhTF)zz{{?|˝rcǎ->Yf ~{]U 'P|- ʧ׾=uI?CmT٫}K\2¶dٸmz Vu9L c9[^|cß*P# 'S%iqZ 7e"I- Ua=̟uYA.:jj,դ 2A+C2i5Oa2`;#jD.]d2d f- ӧrJoWq=\eI|g *v7v&]e/Nj֭s9ӹ0a#1O_"N)ˋUu5$FQul>6{V="rAmoO_1( 400@400P5kЦM|~d~dvu#ؔNp~56l r0(U7{II$[NjbKy1r"1:-{kqE{sؼBm>uХ| kbyR(tooygu>1nTʪU[bEsާhoqY/*'"gOR݋n={Ց?*y晅3g7?c 2eJr9\.W=88H7nk֭ٸq#ۮ}gN=#zc|{Ƕc:ʦXEL01DD4sLl޽S,󼊥X,?H^~b~z[nr-voΜ9~xaxtF'A(~ψ"|fu #$䶡%KDC6V~RvڸqDv4ׅ^Zzuyywhʕb zWߎ6lⷿ _W7:#m(-2ۖ-gg18TӶ|y,rqLuQX!-o>[~2aoĐ\] 1a bO}A/rjƌ=҆b7^zzzz={Ћ/(=.=Ckg͚Oe/7by/P=pRMᯥᓟ8Y}l2lqעO|ʱ?9L2W~߲C9ĕ|7ӑG{FfZv-Y֬YC___^{5zw#]v%x̙G2!{)ˠi!Fn޳׋bYLQ[n<#ۯ~+?Őhߧ 6ЪUޣUVѪUwi8iҤ7ӦM9ƈ)lK}q/I䴵b+q1)[ uCԩSiԩ6lW^yz)zGG -}]N+/ΝGyxlʠײ/{>Lv% ey;wnŋ?~\wC2ȿGh%"%t:MtƎK[l]z-z뭷+\V\I _>Z߱x9s }gϞO|?^^Hۡ:o\J` IDAT9_l:X8 ' B줙3g̙3餓N""|r;u/8xW^yӧOxao WFYt!{"8].W>쩽J9|!U<+'=gh?CD~7 ۲FvU"UUi,.a %1O7ԣ>*#>a&;\WvuW:Γ38󪫮)LoSD[o%8 ^ǏO^{ŝ .v-ZN;6<*biww޹{}Q;iC~e᷾qAOY煿/{Ka#Eĸqhܸq4cƌcW+Vг>KO?4=3ꫯRXݱiKX{7}os=w'>~i ([ߧu4,X' ;ltGВ%KhժUt '@'Ot/=cƌ?Onp$Ua Tib&H&d"T-%10uY"QZ8uuq"\Ƙ?D$C9yDH-ndq:"bVM!cd**a`#<ҥK7++O'ٳOgb i}>O|A4n}|)TZ5)~͎*wTy28%#E8@H$wMxO>{ܹ{ &QI6X1 nUdI"J:ۙL&$:H$ Me1% (uYuDugsEī'_4%ZtbI&(E]j8SRU$(om)^ve!"5kq:\WflLF>ytvmذAWUjv6+hS}7Gd~mmmt҂V E2E&CGqvmzjZd Ma}ݗu]'l#M@3&Ov64^hoo}ݗ٪Xv/|aW\Eƶ 6‹L|2wL$ҩXҥ%Jd**ՐQe%@9\<LJ ͓0]*OV+XQوna`̘1JcSOuNOynyY{UǦQU%1XEw]6mR6]w{osN/)R }Hٸ3&2c Zl=34g\}K_w뭷IissycuT՝*8EUHl VdtYgkV~-/Rb}t v)BG|, GGEh!6ɧNiL>~O[n_җ T 65toжI'St&KbqRTyI/tʧK.s\q^'M%rI&lL ~LJBeN0=1TO5Im11Fp>`EuW\!}mօ`ۈ'G4iD,)h:p V/q/~=C Uy A/P^Rd{&q2NL632LYLR)NL:^d*xSr(u]G<1L<8=Tڎe&tElBRU^{m~ĉJ3Π7x#%#NNr뭷o6?dO&>O7VfT՘ g] p D_Ow}>jzoj팏  7@r ut(QP#8bo:T,{:,f<P#ڋ.z'<ɽk6 &d>ɟ"ȯN tww7Lv3F{~q7!G6'1/e]'tD%lP̦l6243)'M;td,NT*R.yJ'~2 s0&`C㞈[clU&r0^>X5ytqS\".'Nש,XP:jo O>sժUIS5 M{E/wݎ~Z9$[oE_הǎ]wuME~"mU(n_Ewܭ_g?;W:U,3:ȶW')г{C=Dvm?CvaַyLAD<|=օ؛<˛N V̛7?+omѢEd7^4fC'"[VZݓfdBL&ò,d2,Ͱt4BRIIT*R$K&KE$ J&X2H$u]*{JcL 2 T&W6)*H?Q}s#xƍuM[ WyJmdQD;8_!@nw]{\yM7u+ē/m}mܱ6>ٹ>pl6ale-N6q2STI<9L4iاT:Et&DeIq]G ۫O 6z-m¹djT3 )nꑕUޮ+0eqws=VX<^Oϟp;H-]4KroX~44˹q馛^ԓ+|A;,, ¢Tp=ccz[o5cGmES-C`'Ѕ^Hiv2=gϞ=~բG&lϦMJC(y Ēɓ'?ly'w\2MG+"3u$GA56e_X[[f!ɤY&t&Mt|Ž<F$\=9aoy MXeT򨻟1QLo{Hki㧝vZo:z#mRUy緿⋮* .叚W_}>lI&y\sM?U %bqR(svuW}ө:O%~T"J|Osgvz=\t~Y& EJbM[ Dꛚ!@lioo_yN__[`bhщ'"3UKl;ˏR[[V\l6 ncl{PCәH8CcX"Q]/14I0qrz?IqHu^9Nve'~;U2tw+ ڧz.b2i$O~9?OǍ'B&WM&4;w.]r%of /_Jm-J{d@TPR};ߡ{WiFD믻ffH>䁲P32O[ @IR_Z~'=$h^E*|w{{I4ka!47O&e~KsKc$x>zb eZ>f"ո= =yd^(=xRV?(N6My.=쳶y<~}%j,{qbʗ+]\{y/`7u]Zt)eZ~~h.Rz'ϟg>3HՓ>J,0&tAW\qEڵkUI&ct 8?=SaVr'DfRo?NS/KՇcu]<,Xy,LS:X'⊔*=;]p5 QGE*ct7nօDJo SS &SE%T'YO4ֺ袋o/?OSs1˗/wInل B"r{{{c9&zU2KR*UueѢE+p sBI'mӤO#o|4i${Ϲۨu6([1F/ŋ+;Jyqܹ֮]$}` :I)m^p#K/fE{"1f08N{-ˌK4xEuJtd2'p˰pűd8M:DHNDDE+%+V1V%$6&on@(S_K&Ptע!aSk q=;쳽|;㯿:ytWו.馛hΜ9$c:c=Vt]WϦ#"v'暤*}3f̠s9'O>?> ]ץ%KBLco{*g#B0]tE|H6Xb qiHQxt%ƍ={/1y".U=d}ӾckhO~AqSu]~vp"x)ӻVtS'r]7f(<#R8yB ϻ:s8.1c+ސXM""X,*&Bclj|(TDii2 U &WUxL6ZDŽmyW뮻gyFj\uUt衇 ;\W:?O>\r%s=77&/۰a;cSI4-]T޺zsgӫ<~iϚ5͠2#{d]aϪ7֓{acMF;͚5/V[mՐKOO͛7n馪cb~_fO9;p'7R"({\%c%›UK'$NeaPF6{jRI5K9I [ z6z+;XbXjV|/0UkթSG?~<[ ܹsx)s"444וYbQeӦMƸ8RXߴiS曾=hrEV+/EoV|8!Iۇ#Frʘ4i1hӦ RBdwy Y DQĵkװ~̛7CE-PD TT }Ųep@_j[oaРAn{%nܸ!'(9N]N~B_^y)&$I͛Ѹqc|& $xiWXaEQ-ע2qϳ# ,qh4[0e$y ŽXjj SbX-X, dbS]"Mfd&`2$=&ȶρEr|gڦW$*RYAV̒_R[ 4zhg&MȎn\\6l@sZdȑ#¸qd79۷P,Z(`#Gę3gHرcS"##YXIM҉moDSg)));v,֭~!Зł:0m6M8tx?^G3Hs!..=z@xx8{18zh/k̜9/i|!&&FVI4cye7 9Hp`hܸq5ƾFzc1m.\~Ge05" }]~Iaرr| puaРAd\S"EpBCˤ?_N;ho%ѓj<$zRMGh4Ν;gKRRp8dŋcğYF-o^*Wk:u*>ܻI/PN1mG1ACKgu0~cЁ4̛7ժUCll,@_5jYH{RR0|7QqNd'%^nYQٯ$ {UlY-[VƍcܸqC3Ν;s aJJ :tݻw,*T@-s,/ J*TgP}cmgdT$"73q28dƒOrW :EA4[tSE=)J( (@33e qnΨ RWzqqëe'굞m<]d H .׭[ך<ѐ!CвeKx!y_bg̘!K-[8pugᡇZ}իWc夽YfѣG߇4OMcYd(lY߁:cZA{ HM&Md~Y{b7|#?ժU*шZj_b;m5N&e2yw=_",, zδ tfIiiiHMMfý{p%\|Yt˸pԩSl_n_Eaذa0aBgϞذaVZŴ^ЦMB:tQ('2|9u4s)TkVʶ+IK(xJѣouVèQG%+۷ogž}z}3Fv veRQ3?~\PUYGuxϓmPp D?9Lխ[Wzbfr)s't:449a2%YY4gZ$(/Vf=='MMG}TƍfWt9:TJPFA]΁ Bnח;jhUq*Vcbb,/޼yIa2Hҧ&L49rDF˕Xlfyk9lիLR -ZtW&I=;J(I\*괼5:TmAx'OJ~ SNx}]t '7TXIܹ#$'' .ɔ(iI䎥C@y Q,NS:yȑ#Çgܿ[2p8_`ݺu3gڶm9s`ݤ7_/ϦL&2!sȤXO$כT&%yRyIBX--qmuA3PL_xo6-[ŭ7oNv%DGG $I0u<_˖-'<ݺd6 PIrO+Ntdr #$NAt:!f %Ș$޽{cynN%J/DΝW_1b]xzd$',m/D`SۡU1-dlLV4eǶm<+oNs  0vѣTti^^pܵS0MhLF`43݉Lpe\z&d22&d2c\f+xke:i{QLR& &?eϟo+Y$0^u;w7o&6mJΝ+lٲq5ܾ}<nj3P|y^^cM6C QJx2=*C::2MA^^Ҥe~Fؾ}#0aܿߛk9*VHΜ9d > -<~!!!g}駟?~xҾ}cccӋ+Fĉg_FǎI ޽@^O84i8i(w&i$Oi>u>G֭[WvfIFۻ, Ɲ;w?0*ƒy +W.dd1#3LY4TY\$*#IDzz:9ބ %11Q[ߕƖ"E՚)v+W_fùp] x?&&FR.\6m"K/W^91bi/^ӎ/+Eeգc=K?CI.ל{qͻw夅 ԨQCWԩShܸqP _~%ܸq0iҤ" V-J I *1L6-B <^:hOMRiy{&S 0bYm߾2 &m&#ēt8Zu:$3i2Gtttcǒ.]zN:*)) /O!*Tmم(SvXy+zLo?H8NbF4!äʖn-龍v+Áӧ'$ܺ}M?xʗ//y3""fL&F@TѽxD*sq:&Lm^||mٳghdIx;g֬Yʕ#oӧOFu`Ȑ!hݺ5i_x1֭[-I$W{.ٳgL2X={6~/ڢR@[&5aR@QeCYƨ:ȿg9ט1c$j^]vģ+MMMeuxSYЈO-@ .%}N޽{4cƌ"Ep (x_op;wӦM CVOJQ^'="O~%QA┷!TxYi$JN 9Cjjj{M*3))SHtfrʤQ~ɼ+2'g'<){d'ʜo$Ia{(;<ٚ<AЯ_th&4ITɒ%K)%%QQQ&A@\\-J3`wYfqG_{5+>N=믿*UJ?]xב&&wWBo81~ybŊJu=zT=t'-x,hĈZJŧR`<-k"kִ߸ aÆ۷ $9j-Zi1cF'L<5-kzۤy+Lʒ-'=P*-j4JihҤ3H۶mRHܹx̷ojn* 6mH9NN8&N8NNű(`Ș)#+9B1\iIQ#-'30tؿ&@''l֬Zjr| Oϒ!WVu*j^Ǽh"q߾}:u*.7QbE|aگ]b+W\d(O>dfJ.S 5ѭΓZzdx{ L-ϓ[`lSOaƍ?sNH&F Q@,'zͰ.ؼy3iM4gMIXcӧ߱cܹsqơCk֬!c~.1?>xܺD.>3ݻw-K.|zk֬ƒ%%`ERPlz* *AR%H>Q0ݡ Z۷' ֭[}^h4"<}ύѣG{q֬YJdzcAPI+^A#RjdGG~f͚ n޼OSVSx[/(e6i6d6גXySx$ j׮]+VHjm6vd_<cdBG~B=N8'bQ^|:ɯQ)ArIwuE"XVߟ<̙3}r]zPBoߞis8Xj  8'yLO a P*L%%-L¤'{tQѣGeˢe˖|l\,\u^paJ'`י3ئMA&Lْ=6mZ.\6z -^8v'33I}{ ֭[^ַ!X,ԻgiW*7U~6b;xE\jUǎ;/_$OOƀ|HrbŊqHHH[a^:yU, < OWAPJ`M럒0dn龍ԉrSR?xjLA81l_3fťB={{:=NFjoEm\dOԍ7˂qSWdz6lיZj96mjIo%5nGhh3...i^'71k,-[Vױ3`9 e^W4ɓڦ&QAի;֬Ys)y uAf͘۷o6nh۫@mx#ٓ*H 2Y'xZ2A7I <  q(RUJsɒ%+:G7l0˖-(hD߾}I%KTR=)« 9nju:<%=)ehMk|Iwx)kL[ZZ!I4ϛ_)MΛi֬Y~B6mtZR2 hL&d2]I23N$K=Qޑ&zj25?ez j׮]zٲe`7jjD&R'p7VD뙛(71}tTPi1bZh+={矓5j8'Mt2E(cpm1?d/^Tti=IOB_8NZr=J>!!d"\ѣz+QF^BeDe<;C˃GV(|8 f37$aq A0`LF`4$&l2 &Q0͂lD3N0R={=11LOԳgOhTkY EF!:ܢh]^ SNEժUdd~jԨQ@c֭={6iǏOwqM;gyԲK,zC_;$7{!\ %vA}QY:̒B}6)?wrjU!dףYV 8qD2QD\\޽STP>(o߾m>/^&L~u ! 28< Pˮ fHA&VRxp8LΓ'O2/3&8@ΝI?NfÆH``pd4 &F1#=;ɔ%I"If:d믿2J*qƾ}87ߐ~ gj#5v>]p#F HHH@zCڭV+M&S$GXIo=1Mѻ޻w/y!͛7]gaPGx RH+c2րVR*1Ҿ}{{Z^d,Z((xꩧN'~G$/I7)`=AYeO2%wnI^&7R)`0xx QEz™3gF>&/I?SWZZbl6h<_~ -Z U04qCXKY qe|p-y $I C'<ȗK&Q)U=v-,,L֭[*h 7 $հj5 T\>eRw5 8Ы生qunP)>>>`0IoYMX=aW5d4,'!#I>cDysK.%m:utx23-2Eiy qN"Qz˼d+I .\k1$IX~}SZjLۑ#G)J+ m]) qO&`0xh`0^LT\uR 0('?ҥKc`]KG-kk׮u`ZѻwoҞu20&5zfI8x1-ʕ+|RΝ; emCJ&(H8hРmےD,[Lc4h]F'NRzu;؞&,Ex#N/X]PӘ&M9lF߾}S9i+RԱcGdIbu)^~.{}򞣺> M K/]@JPj$ԩSy&"\ZDJKSSgH_R(KIܤE\Ol3丧 " N$ cƌ!g}gnoʕ+{5fX'[f ߿~9'Ou1Lp:]6=a'N/&111Jo%Rƾ V'>F1.\LI8`Сt钾OtR$&&֭[ۇ.g5eyx=VMu>:Nth(ϒyڵ;C6B=z@JMܡC'&=<<%J U|n^X1{رc)"##I!ף֔)/ q'2[8'+Ú;S8)ȧ(w L>xA@<O'4hD&߽{7y}VZx'T$&&BI~zݾ}.]ĕ-ZTZp=Ato%;Нi"O<2/"&N(tU$#4 ;vlv;G8u,YBţXԃQi A4jn}a2$Ja`@yTzꑶ'O'ֶ'ӞcS>қ$JJ]_R(Vrɠ"b $$I2|7QFަ͚5׃0 *Ux}p+ݟN23 YAY=%I23S+A%zn]h_ʕ+ؼy3f2зoT&uDF@z2y/S Y&G*X~żyߓj&w&VvA dHL$Ir& rY2 Yg>>uvzz:}aڴiwN&O߿Ԉc-j(\Ʌ~-MNM HHHz/i_| EѽfS׻7h1 ؁}ٳ́5j}ABB9۶m[[xx4X1-dK"?+<;%¤:IφܨRҥsI}嗙-Wrr2cǎ/FEK,)͟??z 2Ljȕ29e? ^f$ Grr2wݻ8q9"={֫k(]4[nݺŕ֭[ޭ[7dZ*zF"MJEI!O5y|>x]`TZ$֫WQP!e@{A@jp,/*Iբ0ApSGeb' 7%dt[l;#=z{]+Vħ~û E9j.ݛ={6F )F$qZj%>d"  E2ˊ$I0 c䵒D;N`p@#sʕ6lz}eeuJxI*a#3vL=6t)gϞ%K0~'̘1#F~xy$!::#ꫯʕ+G=+-9˛D&g{"Ma_!"ڷojEbbBHn>,Y^MG@θ:Gk9CCLRgk@.Z^LȈs[Y'Nn~a&q~t FD 끲cz =:5g,ڵkQV-ᥗ^$MJ֭[QlYܵ6nH\I j$ڵD"nݺhԨv=^Ly2*z2e{Jr(fdyH$I2{ɓL[2eĎ;ezZަ2(g|հf)3gLP;|.ӦMΝ;I{׮]]vM=٥HUXIS+Yȯ<ڵkFǍm:t`kӦM<+Ӯ+8)#ۣ7.e%)SزeKHӌ3mg_}U[߾}"z8g f() jU{ࡇʳxݺuK/+#Sed/HbO>ݻQj@_~g:uiVԨQ#rb5*Ɋl~KMΓ-[ 6[|]H $znw^ŋԣ>ZjYH E:igt .bt]wm^/?$*U>sK>E>kx @Ѯ];[Xd =#G`ш59N1#FHǢES%-r#P-xxFk Eπ!\1%J;w?f>Q 8{ѢE:Fd;#wxJA 2:tɹA@hh()^PdI+V'W8{,z yʗ//~BBBX[lj2)7e4_v(ew,x>s-~WPti򽤤hSܹCy$v&()-[0`*T!o>lٲi={TjXS,?Ç;{̚5oA1m .D=\3nC&MT{C,p6uVH'EXH O._M#t-&Lp|wcǎ1ɓ'_$27i>|󩧞*yxFXU^cFV} ~ҥK1d$%%q+[m۶{˗WZW#$K@ֺGO.KۼEϨy?q JA"EH[JJ OB]!RAaaahժ~mlذϟG IDAT}IѢо}{U;Zv,[,pwx_{{SdVT\c.={.]bVRCsp8HHH wPI+C#VN'Պ5p! .`ԩz'|PzvJvk ĉhӦ zIʔ)#m߾~ZXuOŋcҒkk% 喺oj$;/޺ b@9YL)%UjժƍSkԨ4)Yu^{.;+sTDCBB$)ĉQo3 A`)Ro$%%CΆ :o0(h4hc{\ e'Ӓ%KbdQٖIȠY-f͚'L/**BC' e5Pt:O?qϋ*ASO7? <^Z;;A0:u 3f_ ͦ;͚5׭[g+U<@y%X(ʛ'%f'gLϛGXmV?$˾s#q/!cȑpilЖAPgΝ3guqD7n~KN̸ZH sNDFFJ*,z*6nȴL&9iJ1i$T\O8xAި%! r6 k֬A۶mQfM|WIӐ!C۷oO/U2EI+%&J?kJ '6RS6mzyO-[eˢsضmN`2eqFǗ_~ZWŋEzs_7Xg澴4)[5,/%u ;=z… KӦMZ;=hy܍U&MJiii7o^NoWzB 1m?#]IM$I0:N$&&8阻?<~`wLxs$~nnG޽͔D•E%]^@믓G Z#B=w=>߉7dJ} ,T$Iسg k-L ӧ::t#)Ԣi' Y!^:i 2%6Q!L$(I^'NfvO"$NAy\z=QB !>> >E}( F)Ҥ~Fd}%[VqM,qi|ᇨUׯS:W͚5tRb&MXdDy~0)a.!bYlzu=ve"H8iID}Y.R)<{ᥗ^c+&-\0lς( kQ u-Kzf̘-Z@5+W`8d%c__>V\Is7b$mbM&V\B,pZZg`0`ѢEodE 0f_ Gߩyr>ɛQ,/_d8q"+VׯO {B^Tx_=.BRRY_P|qreTBK" eX}|SyW^E˖-uVш|PwJRXO' b&SzDx ;v ]4n+Vի̴5h#vM K٠:&b[K*;vl'NoMkᅬzٳgYGun}E:ZS)SԩS8v>4j([^xؿ?ׯ/̙3Ǜ+ omj}Yxٳgd?J r P=FT3  N:&MСCMܱcI0ɓzxI;Q'< Y;C,lExx8M6ƍXjuU 40o]#zD ZS6<|L/3gȾ?T},[ <#JB'XaÆ'\0k,&oݺ5T´]p7oV"m9NǓj&"Oz3Z-o${,oa˖-k"##;4Mۿ[n}'{{A^bAvqFEy/wZY:dGu5opWWdy'O$z8QS@xxnl7 Wg"H:~:ubx6TwI0+iDvnT^7.^ _~[ϗn'S r(_ǥKʕ+;[nmLzlj5%z ӝ;w 111nV+7w~E'+W * }ֳ({tvj_>֮];wjUweUVf EH|jB^IYEҽ~Y+WETt%D*rJx!I>%TAq9 O<vءydܹs|wBIX(͵xKy,CW>}M6$m۶L[kج4d6) Vq{d&ulIXqwh\ vE裏PNyg >Z"V\{ޑlHNky͛7ÇѫW/q׮]^|E*tvZFA,ZdY\-5OK٨Iw_|G 4# |AJ'/z&rWS~իcɒ%Fϕ([aÆO05YRZ=!CPYݫ!(W^xtz$"2ׯcÆ LhD߾}S=w7r=LOzK,Jxq#ڵkK,!%z͚5èQg!#C )|[R-d k5H8-̙3+;~н{w(ZwTk%PbyiX%yk5ȒA1=1_~!38]v nbjԨ!]SQtA9cڵ1c~g۱cǴ.]r&|MMy =(;OptɶrJf:3gr '%v ,;COOOǷ~˴Ft9שŋnggz+T>D%(oC%0\~蒢1EQrI4Nk K+.^V+/Ez,ٝ__0`f3bccAeouԩrkvH.ˢ eYY=iXVTSW"R,Y&yڿ?/4qMPzu9[˻jkH #OAaٰe^ׯ̒F"E?<_~rq*i<`ˉou<|8-_SLAҥzޢm۶X"S|l۶ m۶c֭sֲeK-[';ַo_VRj[-D%PBZ*fBl%`LoE^J)Sjժ*HڵkD'7n,\0Tv6ߴ0[Veyok;_`ʔ)XzOAhذ!5j b,WzO?4lѥK'qvg]]SA/iR>'*I?x=jVk(rȰϛ멥zwE+P)l޽{صk>3t="##1{lܽ{7З灨(|crE#x#c'q/hLͣԊ^Ū+cǎO>GFFdɒ9yF9.@ڵ)5ȩ&LzI A)Hׯ_Ǯ]0}t+([,j֬XX^aÆ]vXvʕ+۠K@Tb?"L\4dȐgϞ( ~-7f^~e\# , mݺuY,eEEx8iyƗP= )+x%ӈ#¨PhQ,\0`&^̙3I{RR]ڱ [R 'Io!11(رc6~~A(%P@!L2)H7o駟j*L6 D-Pti)S-[믿DnG\ ?sɒ%y)77dGXI#x(ح[7[2ezE$&&fYE о}{ncѢEw{rtuֹ.5LII+H{LL +)^] פQU&W֭[dgfڴi믿3blxWv\4'=k:\^ŢXk'KrJRh┒iY#44Dy(^k#99]tt:lHKKX֭[~4k,}ذatb0N/ˤ&U5K$'P#S@ X=TA`)9s&vظq#_cLoEez$qtԫWez ݻ2d,Y VZł5kǕ+Wnj;6m۶jՒ㝔m#T\AO.OKk˻LlCu>u\K~ѣGR~u8@ 4i&-D)Pȑ T8زeK//X,R.]x6d fII(¤^ߑϣ$N2ԍZFyEiիO,@NM^~l=CС"""s7>};w27NZZd2SNqИIolOK#Y΄DߤG.SgϞ5=^Of+HhwŊԩ_KƜ9sH{%AV&%q"=Z/}٦fgZ¾}иqc?{EսnzBtE"RE$Y$A +R Ml(Al(~Rd6wsf7}yfvf3sν9~ЩSrZjիW^r&=:믿v$<.iED @3=[736QJ@> =$0+L/ &xY۷oO' ^}8U"X,j.] 8^zu2D'$!NIw4i`&tu\.x ymf,]. hYfϞ,'|Ƨ{!L=v|W̲H599Y#NZ'oaB`NawUU<ԐY|9ԪU Pbɒ%_;v,tԩ\0tPذa|޽ L4)6@3a)OzZ π5B&IUd5@NN\rEɁ?ZH}޽;lٲī7$5j:7X233#SRR ߨ' =MXmEXOyo¿7Ĕ5iF,-l 't]zO˦'I=:z ks S[%OĩAjUv4hmU&zyhҳO0aIv\0rdJ|Mx饗B/se{peV\yeddFS~ b#LZ_"yA7m/]4/D}' ;L:K`ԨQRj,[ vލ%4s̘$GӦMɁ:(6bC+FfV$HMM1f|:@} IDATؿuVظq-nÀ ++ ~\rxbصk_g}6󀿁N/WjU#&^K`4B/%rd-[nY_VZHU_wz YYY6 5~-K]<dffiiiv`FΚ5 Fc =z:uTtۚ5k95:()))`٘}2R ^-Rrrr(,eԢ]1|1`1A5|{Q_}U̙3׫_=EÇÞ={Bsu֡.ϝ;g6mZ,I^ %Fymh~ g)Met҇H KA hBX֭[ƴN0T ܹv;쮻"D$lL&I(a*sթ8UBn&HJJW_} ~ǜ˗/O/̘1J.] "##i!J y4){,҄-け#GuYVGn%10׭[79g}{LG iX&RRR Q_/5<^|E4 N8QFW=z#<8p<{8y4F,z, 6lСC=VƎR;N0`sDǎ)W\uA &mdUh]M '=ҤEF &L@զo͐[MKԤ7IŐuc9tիe?"jGBbbuHLL!11{\N_:QUTUX$It $ sB; d{F^7;e6`2 )7Ԩ3ӧw U>}f͚p9#G={}e7MO') a禧4"h2Vx䉧48 ,Pr8|0<˃T/emEǏgϝ;7wގ֭[6pk ݦjy3ݠՀ%JG<`”C~oR\n0|غu+3](0iҤ]vi"Hw=m%ݱ\D5hJxS^}ntݳc)5kDݻ5f͚UnkiPUo,ezMOjT&ح粼C/PI*0F ˖-}hd /V޵^ VR\.HNNffd/%K?+Gll,dddw͌s:j172k*2@}n{`Fݻ矫}5~'8qbeHLL*_|a"w%R>XɌX*9t[y&,D&zw?께'!99V޽{C 9r$: [qF(,,dwByya8ڵ#{|yqMج-c֘_<|HSRRHk׮ yc5k:tL2ܹ3L8-_3f̈gK!J1,SҒ-Jc\q[/BE3իaΝ0rH;SjNф&,>RJTR}<mx0nrD$K% Ngs8 -n_aE0D_4S햢z/!Nի'B>btR85jz\fz v>|8Kms48]iK뢗O?1c{թ`0o<g!99LX;w.7բE NzSEE;] `& 5ZlRxȨQ ???w"I`ɒ%h͛7ovB#M,D=Ly6G+z!^R 4n顇P;55ڴiSں)5w'L"!< sգ'\ʈ0i$N,DdMx($AI7>Yu#O4i&,]9{n[J+NF>-[0c ^ k dkcd]21k# ib%MG:th~ƴ֫W^zJm6fiu7kt\1oPI*8$U5傧nvJR$ v{YҎ\αOOE!Kz.zdV>є<<#JH/ȏ$lZ=r8i+*Omz?4+NNSfŪ`.z3$tbϞ=xb[nt,(K8P]*=h-_|y_|粇h\]/\ X0DG L}*I_wcNԤIHIIA,XXcir&Ex:u"K% |{f2dH,o&"Wۗ>#Gg},P Bρ&e|HU%q h{f$IaMlr7$(Il6;fK.[1;i&֢zR{\qݪfh^zC͚5WKMjt{{,kժu,7@e 6Ļco|W,zM6Yf\hPXX)b6!++KP@m׮Sv~W>}z YYY`'w'3XF&Oy2S쇫Qk$I<1_={6P{nرcG(M?G7nܨAZ)) asջ',ÞcTe9;;ۜ{=Ğ={[;vU% f}뭷>r[rLHyT&JTa6.,&˲KUU4W@8N&Y*HD# i$hm'7IQX70)<5]Ή&BI&۷BuuxM6E6k,!%%%ZOe(r߹sg3Ŕ)Sf1ˮ:ֈ^x7=z T*s1˲Gy.o2gKMsϳM8A*!IPPPON1Ҥτf!MK76;''/4yJ6<&_jBsN8y$~Jnh7@&b/Fb E^f͚U[H}vnٝw &MUL&dffzx;-- ]$Ԙ8q"t -߼ys;C/HJXsؓpWسFI&!C"̙&>) ԯ_aoΜ9UxnkWS;cgMFbJUϟ7/\0iӦU oqc4L4 mN0<,cyFbE&2azJ+W<5y-]v̲XٳgH5kвaÆ%I\x(E5HBm۶Efff~bDB˗/sIttt4dff*&7רQ#_D-ӧOøqJúd2AFF`ƎwyS!y$}h{= \YDyz7ݧ,V-[61uT(vy/"VW"y.XsVRpno@,4uKc!oݺ5o߾qի2iҤ]Ȗ,xdX톱cǢ. 6TƎ';a2_h(W7=JT`:jg!'Imɂ%k?&~(i&D]ɍ=v=赞XDIOa"I7u_~.\0{qUV2`m۶YLrv~g6lXo6;w.wyZVlŔ%tK#MئrC%qeK,NI\k7N&- y8l8&,UΏ?x;cO1^DIdG s ZLal/$<ӷ:nz7nDtՠA76/6\0C7vgϢ}ᦛnw߅͛7]tQ'Le*$ /ɘ1cٳC=z4t -"uuXd]:ih^ۡilڈϔ)SX,ё#GdbiӦ%?=Oas4ADx63/X䉾Gn|I~kYreرc;vWJ͛M4)rǎffV k֬]vAÆ sAơC`hy.}ZoK`ۘ]ƋsPDW"vm(R,[~~$ˏ4N."i8^ߐȘG横꣘hU|:؍5P\2=*tcr3ا/T XtSbp:~|߾}|x ﹸ8ѣG'MoF\y0P6@¨Ѳe˖z /{wu*?ƌڵkbI |Ǡ^z%Kux%xG`۶mF/=(Xf 4o\,8qb\׮]uݤcXj' +=\YQʼn՟b61~zСC]L4k,8p`ӨQ#xaYfEۗH.Xc^:(,ۙɓ'(Cq(>̙3ٳg/JX, PVnr %% e-Z l{K%X<)N 9*S(Tś@Ylj$P."kiIe(̺jժho3FFU NA,cc*iԨ1k֬ީA4nx|֬Yv- t%! kթz0~x`͚5aAlK,A˛4i={"GzSi2W*SjGEEGQEnJu"]4T(w\@tm6pʕǏJ&Lk1_X #M"I/g[ 'Bѣ7o ~;;2IMeݻvnӴB7xnzzJ&Yi4)###>B]zͪJ:uXjUuںqz 5j(+W/F^^[U 11-4iRm^Ҏ]'LtJXʄ)-o?iN$BUUnǵbŊ&:t(DEEA߾}K lݺ]çE6m;$6i`S~i*'O<^ժU}\##GDz,Y[.Kc=V\CU_~Y&PXQX-˓RRRTU'ImԆ :aE*O>I&VXթiӦ0p@<== %)²)5jԀ!C@ff&:u o Zޗ&sA^ ??ΤIE2%yqMGBĕ<$Nz|;.c%ʓӏ4{>T19r} J_Y[@1(aU"$>ljO\Ms'gΜwyH;1*UAM/55U/9X YXjie1bDBNNj|PNN-o|hy߾}aÆ@Yς.[Q^=tP2e --AC=זW^y%ؙX s wa&UO^Qu`ǎAec1cg/;6$&bC ͚5~^z%8x ={6l"гgO8~8.]8gϞlzoMщp !*0uؘ<2]w%(*҈ne?22 UOm:u#MGZD Kl9c tp?}l--Df,R m`ҥ\.xǡYf!'N]eVUMNNia2DLjΝ;)ǁ?WU! O=ZZb; ׳aVJu͚5=zDl6$''޽{AC^{ KtMiӦE%%%I^ڢ7 gSڳ"vf}*yзo_֭[fzz:7=}Ѽysׯw /Xvؤ g`Zf͚PN뮃͛7VJ\. 8^S:u<[ll2xD9aEym<)$NഁN*I(w'ox"Rĩ)'; (JPjp:Up_pMRm&e85y2}h,T0кu6@FFһwogݠ6B2TYIO4Ƨ;v:yXkԨ+WCUU>|8Y|F7AGT[nG}Լb f͛3fFKUի!))Yn٤䘽{d'G&P߇x}+o3fVVgؽ{7}ݥR`̙(qM_}ܹsgݕ{^DUXZV&Y!** "##!** NUY- 曗kԨ\P @&DLJ`11dr)b)!O%YH#LSN.NI$ Ӕ(iSI <\&.]RW #Cg֞34tJȲ̍qSk(`2ZЯ_x-XlYS ºu#F@bn`,ҤA3Lc ¥^PPPzIBe˖_~?;>\&HHjF饗^رC>vZ5k Zj={4t<S婟 +N/)D7l0'@OCTfتU+gRR>BU$Nz+],==ݲk.ɱ /=FU8ԫW/!t:aGqdɒ;jqM K}4faC<቎ʡcH/&M$*Q| k+QVrG̵G`̀p%yu?~"ЖR4\R;w-صkrkуv&xqnig鸦@HM .ݳgSEO{J=z&O׮][]|9_FǼ59x8&&ƝYe:s\0l0p8/sħG>| xv32>6t8Dz 6'4ތ3 /o ]_+ƒ?% vV _4M<9̘1"K/&u\&$N\&..6v :!FpRU]w/-#LodNs)q%%6A-!RFr<%6ld2#MzTyVD豌^ӑ#G3gΌnݺ1NQHMM͆~gժUժUcZ`<۷w=v~rq_p@rrr&<!KB5ȩ,%:6Lmٽ{ aM6гgO<==]#N!2J0E3Y%(dgg}1{C ͛7/ /8y fBJ>` ٚ Rd_BH:~MR% aSΌz@cuY9|qw\R+W yj`#;;ݗ$ FZ6xɈD&0))) v54֬YUTѯ`…o><55ٻwo`r,}ٳg6k֌Eo Kƍ:tHNOO~*h#+ S E8R=ENDN؅~w̙3Ѳ>?`ssdU* wuڣGǺu벁&F^oS**Sņ+ZZlRiW|ҝ6P/(5cjO呧P5}}~Fg'49uo4۳'?5o6mv;[\ܹaÆn+|"$,AOmҋoB9s߿=ztH_~{-k=K,\2, yQkZ=YYYX*)))PPzWχo-7o^DCRLz@8 r 9Θ^۷wt}JOOvq3'G{,¤;'@ئo/wy2߿l6T^ :pUav }\f$NFmH16lݑ1O=L*7qwqHO]:'zWs|LJh6쪪 lE.[,:[4GL %M?uΜ9^ `… nHIIA$IիWc3%N60[j6mG_qSѐ X,VqG:`n|^mI ED'==1c 45'|ʍ }vFJ9-[;wgrx/GDDГzcD'n " )<%OqqwʇO@Q,j(5Nx#GM8a#M™8Aczt'^.];vDUuj߾=t-5kVi0s-lC to.[VLS&07=^|m2E%q \Ԅ*h}IZKMX~,#dKÿ5qc.xʓw?;Z}Òr*ZZuV^6 <sufI/1wͦ3g>|uћ8q"tIBœ9s * .\H06z5,˞̼(=NKKjHOO. /[[elzYh<^Fp4l>SIQ=ՉnYm&e۶m:t7^lm&d0N* U Zl owǍW^Ha18}I sc(Ѩ$N&^%8VXx;-EЛM`kQ~ד1d"IXeznQE[>5ဍ7#GXh X@Q&W~i߾}-I&0w\ 3pL&]6/&&|#vwѺ@iҤsܹ!{)nPjBVVX,ln-,, #jTD'ꄑ$V|ӯw}vUXթSNйsgf0k,-U;W 8q"}_qk2ॗ^]lIX^zIӀ瞇(>,7TOYƥh@DEVg $~:@qg,p8٩< ܁tĉ]tA M6;cJVZ?S4idH'8RT[/R.xN믿kU0ŦMqưtR? 'D< I aTqbك,թ U%q!j||h#))ə6!iд {ҋ 5&O Z3k zznsТE 8ؽ{7hy˖-?|"iSx>ofÉ'+)x'ChڵkM}H3S3KSx/3A c0 A*V4Kh)/3JcXݍ$/GD]!WONwǏ3֭ɵ'dM1vLqիQm³>+T7F^^ >}VkXHM(Y2L*ԪUka˽PB[+&]y"J60CpHGxi$jРAM2U'EQ]uٳ'_W|}6*Aয়~ &@:u`ذap῍S,XgϞ7*+⒭6ItU% QI#"L@&ڨ7 ݻwۤAÆ _DϜ9=EJi\mi;T'?%IWuڰa:TVN7nMC-2\&K/A֭uְl2tKC=z3Xz5Z~LB艐&^|Si',Ik֬oY/Exz" (={9~(75kW^y-/Nz[b Xf=Ry'6SXϕhذa7p\.X`Apj$@7o,s\0o<\cO?KK.7~.\_u&:;u/w{YLy r3:2UvXQl~|:ukSȸ'z jQIw[[2b- EIHJۓO&Sfe2wÇe^R$8#O۳b٤ѣG'\p-Z 4c֭qFCΧzJ[V$^/H%?|,X_??mhk֬A숎SxD|wd}K_ >(Z[$I50wi oj%f!3Vm111yrs\ ~i$HOOݻwC~>~{]^=Vk׆;䜜rv4$&R5sM٦d(j17iĈs̉ٳgs5:t 0U8N7okqŕ+W*T2"Yރvvqaa!myyyϟϣvq-m)))5ktbsɦ]bX{I/HX + * qS_Gͦl6]v)o!d&s.''bBLB8I^<ڽK0i۹Y,fX,ś1IJL=Ga _\t)lذx-zlY!O$xniSN'L۷kذ!Ir<3zn„:vh8qbŋyWq1biǎn:`Yqq_\Sx&N Dȓfhd6gy&ܸq+V+JY%Ú5kLӦMԩS49JAڵ=}k!MH&by"83*:*a)0{$`rJT$AH&&&eK۰TmDF<6i4 ݜg+XZȓFh"EiMϷv箻blNV\)PM/55e a,\x#Lv؁Ud$$$x֮]{ s&Jdl`vGDD.[,5<3cǎ!AOl,$2GV멧*}`J9a3gngf6R8W fȸrsW^y%C$ŢXIxD KAzq~Ց&%Na)J14 >P5κuk%MC4y$ \ "x64ъSEܮk5a-">YIQ(_qndyƏΆZLS￰cfj;۫"Тq?vgx-/-Zts]@#JaI vmʔ)A2DAA$''[<kFVt 7JxGDȓ>1crk֬f E+$f3xsO/]xfee]JMMͫWKG0SI$K"M {@>WHG$*@ɒ%&X6fF7S=UUU&e$ GU[_#DI-V#Ma>a Jͦ_/k@h{dTk|qzU)zl#ob'z#ϛuAdd0>N)Q$2x'IJfG̜9BlҥeBTU |dRIDcb `{FsEBՕիWR q\w 1zQUeOff6mԵaG&Ȥ$GLj8,Ê7a3zT+5vu/R¥K>Uc)a٤ X/^AUհ`d2AllV^CnիuQׯ\Zj=3F4<&Mmn=a^d,}^ĉLs^%#M" M֭[^:u IE$D (b&:ocp<^pK4ĸ&@ ZN<#KMO/!yΥK:/y|Ho=OSId>̙3GRЮ]U4 _};vYVvmORRHdrSD0>Op8>_A{辡J̫]:Fzĉ(xC$0-Rrw:DꫯL͛77:կ__[HUw*1L;1'M$EX$;.^]b @/O(6PwÆ aĈGDDjdd$HTiX'IW8L>k${ǜCN֮]=nYyJfY0K dDDD(V: nc!AO$hzeuO+NuӤ~E]X/\NIIxfe[oյf B@Ql/c6ъmkɹP2nXh۶m۶FU &]a7<')KF6ɺW:f}(KDgl @k}$x7Ɏ8^P'v۰ԩbp% #R{qA$Un ^|5J An^;kӊbfe&Md4'TiUDt'vU&#Ni?>F'@~K# ĺOToSJ(y+<1E$Jxd)ؤI<>&uͻI@e# yg(`3$arzHG}i!JxcJF7x0O t@t]у^,%8FHDYDy[q+\c0BXmtn@箇(s1V;W(&V,C0џY}/>(JDMd0҄)H>PSzP6ĉ$Jymc옱@7xul@>MhW=#Nb֎>:&Dvf@!\81/L*k9ώN(34zTܒoYe&41Nd< )6mBAY~_~`A^AUU(r'#d>+cڻ&@HS0l0'zA zrc}qɈ=X<Yq#N4 9L.:랰 :ql[#]uN0_4F<&)kau};wV==#OFCvh4!:Aq|d5 rfp'?ǣy(&`OA!K$JdVU c<O㶗[3"̐G*J& l6$Qr>O>Z [n-B nl6[DttT`fkHd[4a⑦`O]+77`^\A{$eǼ>~5DN|$(hP&Az^,bOBy3,ڈ(k''%HrlYEC(&+kzI6c1҄ ,yJ!N'dee=l#LtMwt_A>G >a_&Nz7`\xĉEH+&`=jjp%N 1)P,Չ8% t }%M9YEEF4)Q4P1bx g(h1MDoJLvξ{ayĉ|XoƸэ )?i{^o]`@.s* q=#=IDy!P&juc-R0Ijh!w18pM& :4(߇ 7n n7x<p:G5L"EG8:w,4 z='CaN,KbϑDyGFh~7Dy=:X&N8똮啎cILJAv`mz0(q_k줁%O:yEvVVdvGcfEE'=OY* a⑥4v4E聒=P@5(֬MMQ%Z n"DNOwqAVT#{ڌN^u,iZ`]L&V4BTtnO$I 2={y& f͚Uѣk.fjxZ<{pEh#G>&JzePYFX3q$6P3]N=G %1jH+x'6KPBsMbk<8=E}"G!N"4'D|f6,T2ƫ[޹Gyc ոLPX?|O'rpŌ' z {Df ?.^%NRͮ,%zEdG0ɏD]xOlSqZM/a>+G̵0I$8{, 0@\ M6 E}zl2 "'ˋ2'8}ϒ6tDʋ4'h2Pzo K&ϛ 4l8.="M"lkd_#N5#z&@e* 81].BDHFhd@n,. ȣσ<.>,((";@ K>=]u?]ݷ~?Z[Ws 5~}oM~ ^ћ{,p676<IJh]t0)On'Dk6/q] 2]7'm+ld '#ײxUV:M\s/ǍiNvur?| ;.G?^јNK^ݿ/N85hk}Repra #DZj9 IDAT*ҋxq]wa-ZTU W_awzX))DY< to"vĒ_o^D} w5&t⹼>3:zMInR]2*aS?JV"/ݟmkQMaC|F-%J$&0񚼼O~o_Ya=L^Bt-Nsr/M$_#)8D3FY Q#e}]`<5hEM-`D'dQߘi  [ 9BkPLxr?¼t-u]WgrXzU'Lޑz0tAD=O39SYj6_o|OLL+я~BoSP@v0A` 1u5 =zy&^?8w/?ɹeMh}nw}', g~#~r TޓNv$03/a2~?\*^*H<WAIN"Ϊg_`Ѳ[@uU:*+Z+?z;CPzv0q8a/$3b}zi={6.]Kbɒ%Xp>T+VcgId2R r/OS>G>ǦM;)NHЛf?ri&Au=  Mw4AF4y]vxn}UW\$s 4kN9ۃJۿK=~}>מKO{zHHnu4:Èt;P½l Mj.a&~ײ{[KDymE 7w/QH]n)^o*@GuEhWt+=7IN$eS״gD'Z$yÌ3~ߟWE4ֻn ˖5cƋi8s<ǐ3g>summd6l7o09=N·(b))yࢇqtow ZD`uk^qO{ߕ?||PyM:D᯦iV5Ma.$m2?mltlIv0_<}rڌiOo?lYg}^a^~ݫeK4y '{n?EPgGw!O3œ=+jRaQf*Z>ei-JǤzdVxa~a@‰4i4T=v JQ2A^'rj"wLq۶e*&z!\y6=L&g_8o~\A/*0")) t~[/Q gsiv;htG ?@S8_ޔEC*X0%c9>>~yg_硇%~uy|oQk~~ݯ/X ;?{ΰUPIRۡ- q+[d{YvߔI$x+AqPvSj]]O M kO4c8O@U:n۶dxq{wpꩧyvk8L$V~uǷ;;UpL200Q*~ۂ"[ܔL;͛֝&=ʢ`fiRI@9=KU&M^n;wq%Kve߼EMz\?AаkS$C5JffOD"S@y-GV^7呙UU/p~ҫmQ\aGPfVlڴ9hj)iVeٸqSS-͛8Kiojr=Muwby &hN~z6~(F7>ꄀ]fۻ"JeqTj@Q,$s%?%{Λ?N9Ϧ$LcԫaY\wPr (͵v2Em;(U?6H '6{Y-HQ++B 4{'ѺEyrE'ݕ PYᅩdVt~o4F|٧?V8MB|25BPV,Jldgojgc>'W_}3< {キ#DϨJQ-'xjCɝ:}_EvR ^k;ˢy]]Sl"3v,O6x2j/pr/Zzy_ξǾ_:X= sozݫr j;xMVȧau.QyF JG*4>ʯs/Jɹ8}_{4yoZ85[4|`N Д8{<瞸/V$p{ CQOC4&/Tm;vSNzՂ$ j% w?a_o3{{.cE@q'Xw&Jes%/6`0}ܷ`?9s.S{q~儱-~]!GT!U"¬{m_Q#Bˆ''n7w$WxQ~aP<9m4TfB ʶQ s9yp㢋R%lѳ=Lι-⩀r8GP-yA 8Io=(o># sWՕ!4O@o`l?J@وҒ[sTxl!<{o[?u%z^[`'oCPAۂjCx٣SA8DPT;WĀ>N^ _9_# `D!^ZC:8jf?'ܴB n۶G)j*vixG|{}pUW0DY0JnR$}ܱ0u[ ;g SD2]ڟһ7g3 ͸gk)'JؿW!͓zdQoå{qc1@ [ڄ`^A?p'4x! _Q q311+_1<<{.뮻===~LeSp#X*&q_ٵKFmr z=NnA@-9oaøQ0'嗉OW{e ##_9c߲~}䡇! N=A)IE*'VLzGM/mGϯx%t] 1zv4KT $cDd \Q~(4Ə rS A)X0>80' yxj5f\}ոknݺP^{C&(*$&&&Cӿ8<ϝ~^V$z <x`j4A@9J@>O34OsOw|3 m!xsekLO G=")&H@9q&kzoCz-/ X&7YI"{eJa੧ߎ[o>h$[;0翨sTK%v@7n>5m*+I֛.͜MYcYPl z-W^yvlD=~/Ty)ꦑkž /}@&l¾ N )"1S؁pӕ/.!z" -[k{?8VX4 |3 {^TL%q41Q^wnM|B;]{ s8㴵/D7MpXD`]c8aBA`0ڍOm^{q{D}ARyکQ4_WAD, $U89'f߀$8(7+Amřk2Mi6Ў;8ttM%dFGG122ɺf~<9r&S!/$yyE#Da38˞go`^VFD}ii1ƃr,+5dF幪ۜLXjO4 ~)H+'7~7U+K=cJM|a{Wzehʳ$LA^}Moo/>8묏|"*izʱr)_]anI(n.^DDިA '2z 4Сyc໲i;Kh_L!4UiiE䇺0a,Ja@G #kb^He5w Si6]1sf_aEdMEHInzBH&м^=M=)Ly0dd!ma \/\a u !0 ^ZNpi;nN;c4M+X>_a{ަr򠶓e r&gF>g)dLĿ{Bz>ˊ w}El6$u,Z( yizA!I}J$F81"J1s!SV-<&~.1 Cw{ldLÀiJ뚦4MFu,Ylٲ?~|}D1# !J%DUz&1>>Qh9ESd"˽K+"A#;TT4C0Q;mBh.мX/ϷLҒ'% zm 7'#_O=eic/S(uv&a "̝wbuw?vo/k4 C4Qj^ateR:=./>YWEf-'ۖ)NZ"BX.F2=F! % C// gŻrK0Pv8>>1G+yC*YN%b@O^  '~©ceO9Bv;/R9xYD^&{=H4S{`||bNE0=A=Wּrr{ z) '&&\M uҿl[h_Sz{s_mB"w(/S[@Hpڋhcu#(J\ʫʞO=1'ɞMnB׍Сz^tuu2M|)"L8f̜ill|wN3\]UzަjN:rVT &EhHdۡSf{bLP($`y0&L>MJD%%(pJaۡpnC23Z |QQhIazI0i<"H8g2av6w@>Pl; &&N[dۣP(Q2 gW_˵(EPi¼u@vm o 8$^TΚv&{8:*-{4wb_D3fL#9E P-c^YqæL) &c'MdeS ߡ""D-T񖂁5eέmBH0 @]ug"DTQiJÏD;w3>Zy?tvuPHA-ᠹF4 FEw_(0=9gvN-!z"ZU6=8S_̵c{܉"*29LnB&xvX6ڧֹ>;<._I-Xeg>!BQM˔F)RC ) =4V8r7ıNBBC27 t |F>My=[&)L&-cùV:E`*y山qD8E{{M)T9lٸmz0Ԅxl;@4^ҧM5 `;2Uv,*!"R䣄Ƅ\)ی 4Sdc (/zqnzӍ۞*S4u0 R)躎T*U m ֻvQ6XdǿU4U? 1WCPuy;:evwPH(`{L@"yE=l0< 6lC!aDGPU(`r{ @*e *M=Q60IӴ^X0>yBpChgiv)Q(T/g@![@?tVN4Nv &T?O o۶#|>/,Iѫӫ'.UXoݟU$ȅjϽ>smwP(4$aye Du6Np9C"JE툁Wо<6?9w7һ&Jp9˲Q(`ח hRzIB )_+{fAG dgdr뱆a2no[4RheӪG9ŒH0=[Qݿ^7\ϝ]B8u.;['''g$xe ͫLmESD>}v۶m Dh둠l ҄92w ;o ,tهePDvDCL(" דNSlN̓m o$Nf qdoy6gFI +x`y ]E/D/(Lv[ VTP`BH::vhGZ4Y"} sA#puz+ڒ0{v8W[v mK[̇ ,)zmxJMypR=Hpd~ۻTB7L*J(929U j$.y(cf0D(IOm408h׉ҿ"{D7t|dP*D4,/S'턊CˍH>Dk&"prw2n웦T*4@E/h['LDQB ;wΓ/œp7xGD4&ɴA2'}D1 ER +4'C˵zR^((~N|*Dё Txb@2 5NSpI!`L^a''&̃~"*z=Ȍc- [,EI?n_~&aVDymSf''O0ń'j>AS2.mGx9[d[y4Le 8] Q(F)w`3Wp"|*k*\$&,K!#7𞐇hh>;ygH4 d|KS*? uvr!`2{9ٙJWΪ'To]<0D7tl;"DVMa Xvc6;t|A! Eh :x EL%(qʡ0((a h+{[iiƓcns=~jnýNA7p$RayBNv4 :ܢ 2/-2~OgMtB:Alg T1d>Z k2 j +d<8Dp+Q>DJ0`OyK, JD(?o푪N'[tP y{yD ,{O=ƫeB4}XO;֠}yEنC0a ?\ Q(jEn:p8LEȹQak>J8)*`kن(QШSνrXdq#UPIJ8)*Hb?'XoKjT-̒%Hyyos=PvF4Sо0AS|vL ],ێ8adsٶ4n)sN]1 uvhԹ'wX 6 F5gfS&yJ I9b¾D静sig*1ֺoH݇1~5_u>#u/&AרYD z_mZ |J!d u 3 f8S}B9l$PL 4a %:P~_I})'c *|񁠨b?'eFD3o'*(gj;/ԗata&`]]`[0UC:FirR Ck25̛660"ۖvylC ШKӨk1w@ Vk$9l*єpRTb& ɓ+6 ^}xĔSP\ 28\Bˢ$sb21~j!I. 36aRr艶;Ы9%wQUDwe6l; sI xL- Be)lcSQN 0%@tx \l@ 8&PMlt m#1eBӑ y AA`m"߆ڄS1|U] {*6 d$ۈF?*ێ`x#3O9ڮo)sN7Шk!,t8EpBݱbf6ǧ`U(ᤨy.7fp= ަ?渀irB}bŒms6рd"vA>̼-qF`^;}vu>;u,mШkF]ǮDM'ۈ$ap*ۖpNM1CnҨkW+,oBp%s[5 l󋘶B 'M\~{ u=LG6^'""U:˜ɶ#i;㞍}X_zhQwF8VHD5M 8Ś`ܦҌJ8)*_4 3q i6^44OAS9^ nՇ㧲mixAgtx5m{FQ! qXcC60>VJ8)ldt Ui+?{@Qaz dQ BF={h~XP]hj\*)}$,4L5jESZXYrÄk | `(g$ېZ ,d# }Ol[ #z0ĂN `d`W ήk" ,,2~Cd> 8F߳̚helc<`@ %ME tFBc=ڪ Н0[G8FDACSIM؀i%l[ 8:en)slh3[rv<]E/<<dvצIk-J47lp*<. |l#d&ֈ%4 Lь9*ێyQFg8oe KI)'V펄Q4zӨ>Ґ/cXKM& V1NRČ SBmS-aB_ļuنHK <9mH:_Y`DaI+{HN17{R*?+s#Ҭhԛ'7ֽ%90ʉ%\s?#0\ozB/Cђs孈 pd_Z]4-S4 82@&*X βK5p/StJ$ɶ)FF5}?8,73\Y^ǒ~\b khz("DsiȯClC{ m STnx7ɶ "tm,Dn?O `#iG? 7~op.'6C ,q$v?'-7/mG ܢD Ύ8Cr!٤Fገ*SV_duJVRG Ω.4`o{X 9Eu]ˠ1C\O9Dsx-Ay!Yv$6s/6$Nt|V.i01o"':4v0f{nS捰m a=[}3xh]S?MBhZ>bX1(9bV50TϿL7{PE (ᤈN#ێ&gmGIkg.JemGjpd)3MmKLDnLln/zࡶENiݍ@55Lk>\}0bRT":e?H*:7ʶh+G˶Cwd^;&"Ԑy ॲm =WqGN8O-1FX!M1^X.΅")P\n1|<;ǰ5 E| qE|(ᤈDq\aR+*4۞'dRʶC }ɶBEI4˶C!F+ۖ:Z k+ *-Of;YB3wEH u}ŒujX Z-gl`)Nh|hDfj06-L0D]43zdfrv()/ȶbn{66a @xZY9| $f`+l͝2;ҴF>uzB f0,~-;&2UX"$J8)"|l; ڇyv4oA(p&Yk@*?$y[m"24i+@*8ScĊ('JyYϛ*,OM@NS2g& mSz &M,w dY Nc :`:nxc>gÿ˰DŽ8`S&SIQ J8)"cC/eۑ0m@3030>`Bф샊p5 ^ X!fD2ʉ,֚`1-!֊ǐ6x&7_E 'EM_ȶ!A l#3OY^0€4^"5Z B恭PԆ`ӊamqmEx>@* pɓoϭB"J8)jyEmGByÈl#fH$ 9E4< }#fW+z@Ny[ ξ '`iEZ? -!D< n'ט<"TrEͤt_'𸉎]7O>_DiH `l[DQ(ᤨf.}vHd„~l#d` h$/nGLC2{512d%F(Y%N-1$TVgEX3a ^{ɓO<9snpRԅ *S9_üuVȀ9?ȶÇiJgqUMP48n$4[BًI)ib ɓ2y N(fl;$0iBL2aȶ!ӉD3暠k`O ϚȵBtE 38{>C<. h K[ <i`ɓ$EѢ(ᤨLxby`l+d<I;CDsr2oܤhXᴊ $# njXSG5Rg?{΁bm;L$ۊ)N$ێ&76"$D{h#!]ǹ s.v(P4JQ=>H^X+@M kka L_tX ʼA%ێ&sg6" QJCzdygX?(ўF2adέm"y;`dSOzvK +ѤH㤈 ~)/ ~LI(֬PX5&̹fѐz٬3ѱy6ن(Iqx<5oU=OM6 r S^2y2C*U4_%ێb0s6| ts0:RC&pM ? ξdpX> sLʩɳ`{s9X&lJ%IGyB4s}%~ٶ4_l#QzW 2M Q:C cMgHOCoL 2p)Tdޟyc5ycp}w,'MAh!Zhv4Ä F$ I !}3ѼiDsi{[I4`|M<2Cw6Dh:e#` єJWl)y-&oSC ( F 'E)s+t5,3{#37yM&Ex9ՙ4msoJ\ezb]CphT;<YX<>= n 8wqI}4ɘ{*ɩ);Uc#Cmݷ+#;mSrN-acG_c78=%~~n2R8v|G:-pJ~J}g ߯'5kW'*Ӊ$_e.%Be image/svg+xml PyXRD PyXRD PyXRD-0.8.4/pyxrd/application/icons/pyxrd2.svg000066400000000000000000000371711363064711000212400ustar00rootroot00000000000000 image/svg+xmlPyXRD PyXRD-0.8.4/pyxrd/application/icons/pyxrd_icon_128x128.png000066400000000000000000000110101363064711000231500ustar00rootroot00000000000000PNG  IHDR>asBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDATx{pUyydT Ku5K1 AٻT$(eE *<jT*L==3L9<9tF"\JB *P @8T¡pC *P @8T¡pC *P @8T¡pC *P @8T¡pC *P @:P HFC H#P @8T¡p-ЊE]׃<㸽0RoRG@(^dY;$!p8n=PGp*?DE8 5MSDQi n#p8k* p>︪4ׂ$jm 6[xhX0== fŠ IUUEM0Z{nϐ$tϞ=Q***$I6SMǏ)>OF{-.fSRRO>8f\zuqqhpW6wܸ&%%aJJ v {8f\bE\Yg!%0Ν;;dQo6A~2eJ8X__/#ʋ~׮]ˢ(֘5ċiӦ 2"kͅdx?3Q+8  ,EQFĔ,yuӇBlѿ>OJKK)7qD""S;eYiL4),Iiڼv // ~<ܹsN)$(:zdԇwyGasT(?3&&FFr'G'n.YYYh2^kjAaf%ikjj`0u PSS,{zg|>wϟ f؁L*"27X ̙0vHxoc~A}vŨχdYst۷/`7Y .{u2d<_Rl6""u= B$NKJJ={+x<ooB$IݻaK> |`Yv&.I򊢼]$IիWLF(z1 <vc=aCDkk8uyXE%33lQ-Eq$Iƍ5fxN,˞"i/_>`T4gΜ Μ9Fii),Q3Lo֭C.E'OfsL?6oެX.QFŤwAqQ?Z) "ĉe#N<gPgPGͶtݒjH8}4Ȳq,;;V^Lp3~4O?IXp0{V#uosOLBmm-p{ nk0O/]4xӦMF7F+a~,,,H?}4\r#˲,֭[-Z$IŲ9++ԪUNl6î]N:-MnOzxO|>W(zjjjL4[PPƍ 1 R[KDJJʑP0dFl3ɓ'պ:1cǎq/%%%>1EQiR#_|' ,?/NUUՔ9W'Ds-s9;L= \.7(((lX,f3N>%IFm\3@c,ؼyss={D4C̝p:PVV&\ޝooiݺu# kXرc#ڝ3gNd2mkAHBDA$Hئ_$~Fin3fʕ+eyt> V vk(" ܹ3=zT($VEEEj(Byyy8 >x񲲲jٳuf`E񃒒[SѢy,ϟiҖ]ڵ+Gw\ P،233uElВp\8w0˲ l0DuUE^CA~jRUU^),,S @`ӧ%wٳ'(#bN[$ٳgNۧ<_纴3g4ٳgYDѦ)+AnCZZt=0l0yРAi SpK/+ɉ'.999UUUA,X 6~`dɎp8rT뮻ᅬxt,YYv۝ O-vmpܹ$IW- 0Lʾ}E9\כ,۫~=hР\ki!f+IZ#Z:,~z6ādYހ!W^p0q{}>ѩSFedd2&"_-Z't:Z޲e@ee(JqkGdŋݺuCYUDn  .=|ht~ÇG.Diͬ:8;;nܸ6PUUUQrE-[LDunYRnQk>󼌈w\ IқeeeO?qܡvw 37mڤ'NܹS>x`̫˗DKP_$IبQP:Dt#b5`ڴiaAI+"Hq1eiXe`;+<+7?++ %I1IN,80|2eJDu ٳc JIIA##(n%Ї~(KR;@`ف YdIe[*@8! D\z [}>yUUoz뭷|Ʉ;uiZ֭[cda"{r0,W,/TVV^;v( l*pq„ 1tY1۽NEQܘrv `Xp͚5_|Q_frJ?>>m7/oX2\8P0+ap\;]1D[n)IX Fm6`䌌rǏGA.cY(h޽{ \tβlEs%7|SLpu}O<իa 7~Qwϛ7ior&lPN:۶m[LZEE:t(]<֏=:[L&hs4%//Ξ=L8A# #[@9s cʊ _***_ +cż61zFD_;w4/_8f3g͚ΝA ,YcDq:-#URRcǎͭHׯdgg#rq: .]do׮]aȑ^fnå__;>}:EIOOom6+cǎ]ZRRG9NkrrLjnnw0999rݘ=zp~_(:*kr4K/ B̨ڐڵk{npkt icܙ pIGt4;땈0:8RZZ1lϳcVh_x/+}(jժ3gd:6Nwo}>|1ył5556]_멧iZ׋rJp| U7{!S@0M q,**NC7[DХK0`C;@m)MHY;uP( XV~UU/7V&C t/: ;NPA*p:z3PHhq0FC @8t *P @8T¡pC *P @8T¡pC *P @8T¡pC *P @8T¡pC *P @8T5KIENDB`PyXRD-0.8.4/pyxrd/application/icons/pyxrd_icon_16x16.png000066400000000000000000000007401363064711000230100ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs:tEXtSoftwarewww.inkscape.org<]IDAT8AKpߖjP"#pb` :$A[ }:,u/A `n ;6BCQ,z)O(2 )sA4T.O]= l6/4MVrNs'!Df 0qjzq q_aCy*,>T*u5 쇮{;Lu}'em s}Mn"˲D"a8 mSpx<4۶%B-(hRK *c4%,KXl-ϷDQxX,hyhRl$Ik/,ZzF h󡫜IENDB`PyXRD-0.8.4/pyxrd/application/icons/pyxrd_icon_24x24.png000066400000000000000000000014311363064711000230040ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYs6GtEXtSoftwarewww.inkscape.org<IDATHoHqǟ.I7ph՛24w/|.$Bp/p/܋$)b)a,Gno@,†e)\rz%7A|_=CIDaC {I&IRaM5q\h ɜ{D`qgggz~~ޙvfTUA09vuu øggg\E${ssp8l[]] &EL&~( >q8S+555H$":T*u=Lz;::^zJDYr6UWWWVV.FFFݓEQڪT*BU566~Epxx||\|YT*Uag_=zzz'&ɴr\Z > ݡ( !!@A<7o.BHDzlDFGGoXMMM(Alii *,."BnZ< EmZ,">c٢> xh~g6wHܥiZ^WA#B^w*]O!NwN;{/fQMEIENDB`PyXRD-0.8.4/pyxrd/application/icons/pyxrd_icon_32x32.png000066400000000000000000000021201363064711000227760ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<IDATX[HcWI4Q_' hDh/IG؈E0`Z(Z& iI\Zˀ#b/s2Af"ԇNÆ?kM "DSb1@A73">ch8H3 V#"du3yww7kff}7wrssϫ8jQ~~>h4~ Ϲ\C\nmll| 6L|^?q~~MKK{v677T*Xrf^__'?NLL_[[;::R=11+,,<>==M5ڼ3X,~YRRduu=JV; ŏb+>bXtpol@ M˥\YYyG(*C Irl6k'|5V@p8000aKFCPPD"1@BB³i߿I[[4M綶ڼ^o[.M&[^N$P( =OƆp8|tWTT ; ~EEE''')2l{a4M"(a~$9CD$IEGGHwwSSSCUUU/Z폈Ⱥ-FܢP(T󬺺zC7E "-q! > bQ ;oIENDB`PyXRD-0.8.4/pyxrd/application/icons/pyxrd_icon_48x48.png000066400000000000000000000032771363064711000230320ustar00rootroot00000000000000PNG  IHDR00WsBIT|d pHYs11(RtEXtSoftwarewww.inkscape.org<ɛs9sssr" +tx+ 4^ W@hDaN ?NDi8<0… 9jKer|(88Jj{ f9h4f\'?\򈧭`hh%D⛜|!n`RCTފ'o``U5kֈ\.3gNOdd N,%''555Gψvm@Fc ˗Dhݖ-[ny~XMM.\,--+,,lh4X,oPt:z}˲V -'%%ُ?n$"FJ@zX,&Fkkkwy*gϞD#t#Q8d2m۶nRfєEp8|q\^ٙVUUUU`0ի?***jBzzܹӐ{$o߾jqOjLReݳz{{&{.rssZ@ꏌ"5kD}`0椥=;06HTTwRtnGxx{ɒ%ckyEEE,߿C_FGGN`L' 6 `6r?::*ڰaݒCnn-166vOffܹs/_+v#..5((gsNheeenRlfO<`dRRRBCC]&) 8Hm6;w4" %"ɻMyyy ڱcǍBp755k??^󣪪*t5P]]]Gt:݁jeeNNmtR{[[۪-⩢혘\.'e)//#Z[[[qLJ***j9tPݩ(hfqF#ڵk]+V LF_?s.4=zʺu,|begg[vz :u ___DtĉaHPŋݛ6ms̙OHxeYF|``uXWW/555ٲeZm‚ ^v-/d2 /^p8dYQ绻q: ˛ n7)A+@".,7'$4SD+WhJJJ֮]{ajδ3ҥKPLḶ~N.`6_y-Ri8ux*0Z0D4**nO0-++kP(e˖Qvvx}}\K{a\."N3"Zm%::Ҟ.1n,;3aZp!I$R*TPPliiir:?'XSN ^_vq$Is:.zzzr"""H.SHH ^=zxxX @PP RaaW:kܾ}`6ܸq/ϟNl6aٶlWDrr|DDσ Ǔ2 \ogϞ5t:/d4 }RM^T]2l \@KKQzzS.ZZZeY oZ_,˲l&"x<ӧgÇ/]EDuswwwP(-ZFc/[n]*if.U`ll,tPRRd2?&7Lp\6l8q1(((P8q"z`rroƊ> ;;{(%%uxV\{С@wwwLR_ Yvadd$B'wuuX,L&EFF~cV|…_>#**j*vmgY\NeOetn7@.S^^/so0" n;. --m|޽FH90@"XV1h>TDn0'HrR,0_j/^ ٳgϾaf͚/sLMMMy<'''111ʭhEE{q̩[R9N fp- d#|r,իW#''S\\X%&&$t:]*?69W&铢 C@@S^^{~2[n}RVVvwǎnH-[(Fcppp Q*~*m6-D"},qgΜ.]J.W&:w'ɓ\ʞhD"8::d&ǁǚ9AS)))3󫫫bΫssqq-^t:]k׮5ڼyb˲В%K׭fyJ@eP`8|M6}rss,/Ê}= ?$x/U*OPdd$r7m۶lF1Ν;]kgHOOu֑T*}a3R޼y}'D&af.rʕ6.#ő#GXUU¸L*))sqZm%W@WWWU @@f8Oǭڵk̵ǟJkll JMMbg_jj]G(2 bqwFFiF  t8X¡h !!aze:!ώ;y|J(> _bCRRRoM`w%-j(;l%1;m>sQggիw 233=*ms%WSSs 1 # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, project=None): """ Initializes the AppModel with the given Project. """ super(AppModel, self).__init__() self.current_project = project if project: project.parent = self # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @PyXRDModel.observe("data_changed", signal=True) @PyXRDModel.observe("visuals_changed", signal=True) def notify_needs_update(self, model, prop_name, info): self.needs_plot_update.emit() def clear_selected(self): self.current_specimens = None # ------------------------------------------------------------ # Project change management: # ------------------------------------------------------------ _project_hash = None def update_project_last_save_hash(self): if self.current_project is not None: dump = PyXRDEncoder.dump_object(self.current_project).encode(errors='ignore') self._project_hash = hashlib.sha224(dump).hexdigest() def check_for_changes(self): current_hash = None if self.current_project is not None: dump = PyXRDEncoder.dump_object(self.current_project).encode(errors='ignore') current_hash = hashlib.sha224(dump).hexdigest() return current_hash != self._project_hash pass # end of class PyXRD-0.8.4/pyxrd/application/splash.py000066400000000000000000000157271363064711000200230ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk, Gdk, GdkPixbuf, GObject # @UnresolvedImport from time import time, sleep from pyxrd.application.icons import get_icon_list def scale_ratio(src_width, src_height, dest_width, dest_height): """Return a size fitting into dest preserving src's aspect ratio.""" if src_height > dest_height: if src_width > dest_width: ratio = min(float(dest_width) / src_width, float(dest_height) / src_height) else: ratio = float(dest_height) / src_height elif src_width > dest_width: ratio = float(dest_width) / src_width else: ratio = 1 return int(ratio * src_width), int(ratio * src_height) class ScalableImage(Gtk.Image): """A Gtk.Image that rescales to fit whatever size is available. Only Pixbuf data is supported; it can be loaded from a file or passed directly. """ def __init__(self, pixbuf=None): super(ScalableImage, self).__init__() self._pixbuf = None self.connect('size-allocate', self._on_size_allocate) self.set_size_request(1, 1) self._hyper_id = None if pixbuf is not None: self.set_from_pixbuf(pixbuf) def _on_timeout_hyper(self): """Perform a delayed high-quality scale.""" self._hyper_id = None allocation = self.get_allocation() target_width, target_height = scale_ratio( self._pixbuf.get_width(), self._pixbuf.get_height(), allocation.width, allocation.height) if target_width > 0 and target_height > 0: pixbuf = self._pixbuf.scale_simple( target_width, target_height, GdkPixbuf.InterpType.HYPER) # @UndefinedVariable super(ScalableImage, self).set_from_pixbuf(pixbuf) def _on_size_allocate(self, image, allocation, force=False): """Scale the internal pixbuf copy to a new size.""" if self._pixbuf is None: return pix_width = self._pixbuf.get_width() pix_height = self._pixbuf.get_height() target_width, target_height = scale_ratio( pix_width, pix_height, allocation.width, allocation.height) old_pix = self.get_pixbuf() if target_width < pix_width or target_height < pix_height: if (force or not old_pix or old_pix.get_width() != target_width or old_pix.get_height() != target_height): # If we're forcing an update we have a new image, # since that's a "big" event we can afford to # hyper-scale right away. On the other hand if it's # not a forced update the window just resized, that # needs responsiveness immediately, so delay the hyper # scale until the window is stationary for at least # 1/10 of a second. if self._hyper_id: GObject.source_remove(self._hyper_id) self._hyper_id = None if target_width > 0 and target_height > 0: pixbuf = self._pixbuf.scale_simple( target_width, target_height, GdkPixbuf.InterpType.HYPER if force else GdkPixbuf.InterpType.NEAREST) # @UndefinedVariable if not force: self._hyper_id = GObject.timeout_add(100, self._on_timeout_hyper) super(ScalableImage, self).set_from_pixbuf(pixbuf) elif old_pix != self._pixbuf: if self._hyper_id: GObject.source_remove(self._hyper_id) self._hyper_id = None super(ScalableImage, self).set_from_pixbuf(self._pixbuf) def set_from_file(self, filename): """Set the image by loading a file.""" pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) #@UndefinedVariable self.set_from_pixbuf(pixbuf) def set_from_pixbuf(self, pixbuf): """Set the image from a GdkPixbuf.Pixbuf.""" self._pixbuf = pixbuf self._on_size_allocate(None, self.get_allocation(), force=True) def __not_implemented(self, *args): """This Gtk.Image storage type is not supported.""" raise NotImplementedError("only pixbuf images are currently supported") set_from_animation = __not_implemented set_from_gicon = __not_implemented set_from_icon_name = __not_implemented set_from_icon_set = __not_implemented set_from_image = __not_implemented set_from_pixmap = __not_implemented set_from_stock = __not_implemented class SplashScreen(object): def __init__(self, filename, version=""): # DONT connect 'destroy' event here! self.window = Gtk.Window(Gtk.WindowType.TOPLEVEL) self.window.set_auto_startup_notification(False) self.window.set_default_icon_list(get_icon_list()) self.window.set_icon_list(get_icon_list()) self.window.set_title('PyXRD') self.window.set_skip_taskbar_hint(True) self.window.set_position(Gtk.WindowPosition.CENTER) self.window.set_decorated(False) self.window.set_resizable(False) self.window.set_border_width(1) self.window.modify_bg(Gtk.StateType.NORMAL, Gdk.color_parse('white')) # @UndefinedVariable ebox = Gtk.EventBox() # prevent the black color from showing through... self.window.add(ebox) main_vbox = Gtk.VBox(False, 1) main_vbox.set_border_width(10) ebox.add(main_vbox) self.img = ScalableImage() self.img.set_from_file(filename) self.img.set_size_request(500, 300) main_vbox.pack_start(self.img, True, True, 0) self.lbl = Gtk.Label() self.lbl.set_markup("Loading ...") self.lbl.set_alignment(0.5, 0.5) main_vbox.pack_end(self.lbl, True, True, 0) self.version_lbl = Gtk.Label() self.version_lbl.set_markup("Version %s" % version) self.version_lbl.set_alignment(0.5, 0.5) main_vbox.pack_end(self.version_lbl, True, True, 0) self.window.show_all() while Gtk.events_pending(): Gtk.main_iteration() self.start_time = time() self.closed = False def set_message(self, message): self.lbl.set_markup("%s" % message) while Gtk.events_pending(): Gtk.main_iteration() def close(self): if not self.closed: self.window.set_auto_startup_notification(True) while (max(5 - (time() - self.start_time), 0) != 0): sleep(0.1) while Gtk.events_pending(): Gtk.main_iteration() self.window.destroy() del self.window, self.lbl, self.img, self.version_lbl self.closed = True pass # end of class PyXRD-0.8.4/pyxrd/application/views.py000066400000000000000000000171261363064711000176610ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk, GdkPixbuf # @UnresolvedImport from pyxrd.data import settings from pyxrd.generic.views import ObjectListStoreView, BaseView, HasChildView, FormattedTitleView from pyxrd.project.views import ProjectView from pyxrd.specimen.views import SpecimenView, EditMarkersView from pyxrd.application.icons import get_icon_list class AppView(HasChildView, FormattedTitleView): """ The main application interface view. Attributes: project: the project view specimen: the specimen view markers: the markers view phases: the phases view atom_types: the atom_types view statistics: the statistics view mixtures: the mixtures view """ builder = resource_filename(__name__, "glade/application.glade") top = "main_window" title_format = "PyXRD - %s" child_views = { "project": ProjectView, "specimen": SpecimenView, "markers": EditMarkersView, # FIXME this should be part of the specimen view/controller code "phases": ObjectListStoreView, "atom_types": ObjectListStoreView, "behaviours": ObjectListStoreView, "mixtures": ObjectListStoreView } widget_groups = { 'full_mode_only': [ "tbtn_edit_phases", #"tbtn_edit_behaviours", "tbtn_edit_atom_types", "tbtn_edit_mixtures", "tbtn_separator1", "btn_sample", "separator3", "separator4", "separator5", "main_menu_item_edit_phases", "main_menu_item_edit_atom_types", "main_menu_item_edit_mixtures" ] } # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): super(AppView, self).__init__(*args, **kwargs) # Setup about window: def on_aboutbox_response(dialog, response, *args): if response < 0: dialog.hide() dialog.emit_stop_by_name('response') def on_aboutbox_close(widget, event=None): self["about_window"].hide() return True self["about_window"].set_version(settings.VERSION) self["about_window"].set_website("https://github.com/PyXRD/PyXRD/blob/v%s/Manual.pdf" % settings.VERSION); pixbuf = GdkPixbuf.Pixbuf.new_from_file(resource_filename(__name__, "icons/pyxrd.png")) # @UndefinedVariable scaled_buf = pixbuf.scale_simple(212, 160, GdkPixbuf.InterpType.BILINEAR) # @UndefinedVariable self["about_window"].set_logo(scaled_buf) self["about_window"].connect("response", on_aboutbox_response) self["about_window"].connect("close", on_aboutbox_close) self["about_window"].connect("delete_event", on_aboutbox_close) self["main_window"].set_icon_list(get_icon_list()) self.reset_all_views() if not settings.DEBUG: self.get_top_widget().maximize() self.set_layout_mode(settings.DEFAULT_LAYOUT) self._clear_gdk_windows() self.get_top_widget().show() return def _clear_gdk_windows(self): gdktops = Gtk.Window.list_toplevels() gtktop = self["main_window"] our_gdktop = gtktop.get_window() for gdktop in gdktops: if not our_gdktop == gdktop: gdktop.hide() def setup_plot(self, plot_controller): # Get plot canvas widget self.canvas_widget = plot_controller.get_canvas_widget() self.canvas_widget.set_name("matplotlib_box2") self["matplotlib_box2"] = self.canvas_widget # Get plot toolbar widget self.nav_toolbar = plot_controller.get_toolbar_widget(self.get_top_widget()) self.nav_toolbar.set_name("navtoolbar") self["navtoolbar"] = self.nav_toolbar # Insert into the window hierarchy: self["matplotlib_box"].add(self.canvas_widget) self["matplotlib_box"].show_all() self["navtoolbar_box"].add(self.nav_toolbar) self.nav_toolbar.hide() def reset_child_view(self, view_name, class_type=None): if getattr(self, view_name, None) is not None: getattr(self, view_name).hide() setattr(self, view_name, None) if class_type == None: class_type = self.child_views[view_name] view = class_type(parent=self) setattr(self, view_name, view) view.set_layout_mode(self.current_layout_state) if view_name.lower() == "project": # Plug in this tree view in the main application: self._add_child_view( view.specimens_treeview_container, self["specimens_container"]) return view def reset_all_views(self): for view_name, class_type in self.child_views.items(): self.reset_child_view(view_name, class_type) # ------------------------------------------------------------ # Sensitivity updates # ------------------------------------------------------------ def update_project_sensitivities(self, project_loaded): """ Updates the views sensitivities according to the flag 'project_loaded' indicating whether or not there's a project loaded. """ self["main_pained"].set_sensitive(project_loaded) self["project_actions"].set_sensitive(project_loaded) for action in self["project_actions"].list_actions(): action.set_sensitive(project_loaded) def update_specimen_sensitivities(self, single_specimen_selected, multiple_specimen_selected): """ Updates the views sensitivities according to the flags 'single_specimen_active' indicating whether or not there's a single specimen selected (= active) and 'multiple_specimen_active' indicating whether or not there are multiple specimen selected. """ self["specimen_actions"].set_sensitive(single_specimen_selected) self["specimens_actions"].set_sensitive(single_specimen_selected or multiple_specimen_selected) def update_plot_status(self, angularpos, dspacing, experimental, calculated=None): wrapper = "%s" text = "" if angularpos is not None: text = "20=% 3.2f ° d=% 3.2f nm Ie=% 5d" % (angularpos, dspacing, experimental) if calculated is not None: text += " Ic=% 5d" % calculated self["lbl_plot_info"].set_markup(wrapper % text) # ------------------------------------------------------------ # View update methods # ------------------------------------------------------------ def set_layout_mode(self, mode): super(AppView, self).set_layout_mode(mode) for view_name in self.child_views: getattr(self, view_name).set_layout_mode(mode) def show_plot_toolbar(self): self.nav_toolbar.show() def hide_plot_toolbar(self): self.nav_toolbar.hide() def show(self, *args, **kwargs): BaseView.show(self, *args, **kwargs) def get_toplevel(self): return self["main_window"] pass # end of class PyXRD-0.8.4/pyxrd/atoms/000077500000000000000000000000001363064711000147635ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/atoms/__init__.py000066400000000000000000000000001363064711000170620ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/atoms/controllers.py000066400000000000000000000120711363064711000177040ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from contextlib import contextmanager from os.path import dirname import numpy as np from mvc import Controller from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory from pyxrd.generic.controllers import BaseController, ObjectListStoreController from pyxrd.atoms.models import AtomType from pyxrd.atoms.views import EditAtomTypeView from pyxrd.data import settings from pyxrd.file_parsers.atom_type_parsers import atom_type_parsers class EditAtomTypeController(BaseController): """ The controller for the AtomType model and EditAtomTypeView view. """ # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def register_adapters(self): self.update_plot() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update_plot(self): x, y = (), () if self.model is not None: x = np.arange(0, 90.0, 90.0 / 100.0) y = self.model.get_atomic_scattering_factors(2 * np.sin(np.radians(x / 2)) / settings.DEFAULT_LAMBDA) self.view.update_figure(x, y) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @Controller.observe("parameters_changed", signal=True) def notif_parameter_changed(self, model, prop_name, info): self.update_plot() pass # end of class class AtomTypesController(ObjectListStoreController): # FIXME THIS NEES CLEAN-UP AND TESTING!! """ Controller for an AtomType ObjectListStore model and view. """ file_filters = property(fget=lambda *a: atom_type_parsers.get_file_filters()) export_filters = property(fget=lambda *a: atom_type_parsers.get_export_file_filters()) treemodel_property_name = "atom_types" treemodel_class_type = AtomType columns = [ ("Atom type name", "c_name") ] delete_msg = "Deleting an atom type is irreversible!\nAre You sure you want to continue?" obj_type_map = [ (AtomType, EditAtomTypeView, EditAtomTypeController), ] title = "Edit Atom Types" _export_atomtypes_dialog = None @property def export_atomtypes_dialog(self): """ Creates & returns the 'export atom types' dialog """ if self._export_atomtypes_dialog is None: # Default location of the database: current_folder = dirname(settings.DATA_REG.get_file_path("ATOM_SCAT_FACTORS")) # Create the dialog once, and re-use self._export_atomtypes_dialog = DialogFactory.get_save_dialog( title="Export atom types", current_folder=current_folder, persist=True, filters=self.export_filters, parent=self.view.get_top_widget() ) return self._export_atomtypes_dialog _import_atomtypes_dialog = None @property def import_atomtypes_dialog(self): """ Creates & returns the 'import atom types' dialog """ if self._import_atomtypes_dialog is None: # Default location of the database: current_folder = dirname(settings.DATA_REG.get_file_path("ATOM_SCAT_FACTORS")) # Create the dialog once, and re-use self._import_atomtypes_dialog = DialogFactory.get_load_dialog( title="Import atom types", current_folder=current_folder, persist=True, multiple=False, filters=self.file_filters, parent=self.view.get_top_widget() ) return self._import_atomtypes_dialog # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def get_atom_types_tree_model(self, *args): return self.treemodel def create_new_object_proxy(self): return AtomType(name="New Atom Type", parent=self.model) @contextmanager def _multi_operation_context(self): with self.model.data_changed.hold(): yield # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_load_object_clicked(self, event): self.import_atomtypes_dialog.run( lambda dialog: self.model.load_atom_types( dialog.filename, dialog.parser ) ) def on_save_object_clicked(self, event): self.export_atomtypes_dialog.run( lambda dialog: dialog.parser.write( dialog.filename, self.get_selected_objects(), AtomType.Meta.get_local_persistent_properties() ) ) pass # end of class PyXRD-0.8.4/pyxrd/atoms/glade/000077500000000000000000000000001363064711000160375ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/atoms/glade/atoms.glade000066400000000000000000000674471363064711000202020ustar00rootroot00000000000000 True False 5 7 2 5 True False 0 Name GTK_FILL GTK_FILL 12 True False 0 0 True True 15 True False False 1 2 GTK_FILL 12 True False 0 0 none True False 6 4 5 5 True False a<sub>1</sub> True 1 2 GTK_FILL True False a<sub>2</sub> True 2 3 GTK_FILL True False a<sub>3</sub> True 3 4 GTK_FILL True False a<sub>4</sub> True 4 5 GTK_FILL True False a<sub>5</sub> True 5 6 GTK_FILL True False b<sub>1</sub> True 2 3 1 2 True False b<sub>2</sub> True 2 3 2 3 True False b<sub>3</sub> True 2 3 3 4 True False b<sub>4</sub> True 2 3 4 5 True False b<sub>5</sub> True 2 3 5 6 True False c True GTK_FILL True True 15 False False 3 4 5 6 True True 15 False False 3 4 4 5 True True 15 False False 3 4 3 4 True True 15 False False 3 4 2 3 True True 15 False False 3 4 1 2 True True 15 False False 1 2 5 6 True True 15 False False 1 2 4 5 True True 15 False False 1 2 3 4 True True 15 False False 1 2 2 3 True True 15 False False 1 2 1 2 True False 0 0 True True 15 False False 1 4 GTK_FILL True False 0 5 <b>Parameters</b> True 2 5 6 GTK_FILL 10 200 200 True False 2 6 7 10 10 True False 0 Atomic weight 1 2 GTK_FILL GTK_FILL 12 True False 0 0 True True 15 True False False 1 2 1 2 GTK_FILL 12 True False 0 Atom nr 2 3 GTK_FILL GTK_FILL 12 True False 0 Debye-Waller factor 4 5 GTK_FILL GTK_FILL 12 True False 0 0 True True 15 True False False 1 2 2 3 GTK_FILL 12 True False 0 0 True True 15 True False False 1 2 4 5 GTK_FILL 12 True False 0 0 True True 15 True False False 1 2 3 4 GTK_FILL 12 True False 0 Charge 3 4 GTK_FILL GTK_FILL 12 PyXRD-0.8.4/pyxrd/atoms/models.py000066400000000000000000000463751363064711000166370ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from functools import partial from warnings import warn import numpy as np from pyxrd.generic.io import storables, Storable, get_case_insensitive_glob from pyxrd.generic.models import DataModel from pyxrd.generic.models.mixins import CSVMixin from pyxrd.calculations.data_objects import AtomTypeData, AtomData from pyxrd.calculations.atoms import get_atomic_scattering_factor, get_structure_factor from mvc import Observer from mvc.models.properties.string_properties import StringProperty from mvc.models.properties.signal_mixin import SignalMixin from mvc.models.properties.float_properties import FloatProperty from mvc.models.properties.bool_property import BoolProperty from mvc.models.properties.read_only_mixin import ReadOnlyMixin from mvc.models.properties.labeled_property import LabeledProperty from mvc.models.properties.integer_properties import IntegerProperty @storables.register() class AtomType(CSVMixin, DataModel, Storable): """ An AtomType model contains all the physical & chemical information for one ion, e.g. Fe3+ & Fe2+ are two different AtomTypes. """ # MODEL METADATA: class Meta(DataModel.Meta): store_id = "AtomType" #: The project this AtomType belongs to or None. Effectively an alias for `parent`. project = property(DataModel.parent.fget, DataModel.parent.fset) _data_object = None @property def data_object(self): """ The data object that is used in the calculations framework (see :mod:`pyxrd.generic.calculations.atoms`). Is an instance of :class:`~pyxrd.generic.calculations.data_objects.AtomTypeData` """ self._data_object.par_c = self.par_c self._data_object.debye = self.debye self._data_object.charge = self.charge self._data_object.weight = self.weight return self._data_object #: Name of the AtomType (e.g. :math:`Fe^{2+}`) name = StringProperty( default="", text="Name", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: The atomic number, or an arbitrarily high number (+300) for compounds atom_nr = IntegerProperty( default=0, text="Atom Nr", visible=True, persistent=True, widget_type="entry" ) def __get_par_a(self, index=0): return self._data_object.par_a[index] def __set_par_a(self, value, index=0): assert (index >= 0 and index < 5) self._data_object.par_a[index] = value def __get_par_b(self, index=0): return self._data_object.par_b[index] def __set_par_b(self, value, index=0): assert (index >= 0 and index < 5) self._data_object.par_b[index] = value #: Atomic scattering factor :math:`a_1` par_a1 = FloatProperty( default=0.0, text="a1", visible=True, persistent=True, signal_name="data_changed", widget_type="entry", mix_with=(SignalMixin,), fget=partial(__get_par_a, index=0), fset=partial(__set_par_a, index=0) ) #: Atomic scattering factor :math:`a_2` par_a2 = FloatProperty( default=0.0, text="a2", visible=True, persistent=True, signal_name="data_changed", widget_type="entry", mix_with=(SignalMixin,), fget=partial(__get_par_a, index=1), fset=partial(__set_par_a, index=1) ) #: Atomic scattering factor :math:`a_3` par_a3 = FloatProperty( default=0.0, text="a3", visible=True, persistent=True, signal_name="data_changed", widget_type="entry", mix_with=(SignalMixin,), fget=partial(__get_par_a, index=2), fset=partial(__set_par_a, index=2) ) #: Atomic scattering factor :math:`a_4` par_a4 = FloatProperty( default=0.0, text="a4", visible=True, persistent=True, signal_name="data_changed", widget_type="entry", mix_with=(SignalMixin,), fget=partial(__get_par_a, index=3), fset=partial(__set_par_a, index=3) ) #: Atomic scattering factor :math:`a_5` par_a5 = FloatProperty( default=0.0, text="a5", visible=True, persistent=True, signal_name="data_changed", widget_type="entry", mix_with=(SignalMixin,), fget=partial(__get_par_a, index=4), fset=partial(__set_par_a, index=4) ) #: Atomic scattering factor :math:`b_1` par_b1 = FloatProperty( default=0.0, text="b1", visible=True, persistent=True, signal_name="data_changed", widget_type="entry", mix_with=(SignalMixin,), fget=partial(__get_par_b, index=0), fset=partial(__set_par_b, index=0) ) #: Atomic scattering factor :math:`b_2` par_b2 = FloatProperty( default=0.0, text="b2", visible=True, persistent=True, signal_name="data_changed", widget_type="entry", mix_with=(SignalMixin,), fget=partial(__get_par_b, index=1), fset=partial(__set_par_b, index=1) ) #: Atomic scattering factor :math:`b_3` par_b3 = FloatProperty( default=0.0, text="b3", visible=True, persistent=True, signal_name="data_changed", widget_type="entry", mix_with=(SignalMixin,), fget=partial(__get_par_b, index=2), fset=partial(__set_par_b, index=2) ) #: Atomic scattering factor :math:`b_4` par_b4 = FloatProperty( default=0.0, text="b4", visible=True, persistent=True, signal_name="data_changed", widget_type="entry", mix_with=(SignalMixin,), fget=partial(__get_par_b, index=3), fset=partial(__set_par_b, index=3) ) #: Atomic scattering factor :math:`b_5` par_b5 = FloatProperty( default=0.0, text="b5", visible=True, persistent=True, signal_name="data_changed", widget_type="entry", mix_with=(SignalMixin,), fget=partial(__get_par_b, index=4), fset=partial(__set_par_b, index=4) ) #: Atomic scattering factor c par_c = FloatProperty( default=0.0, text="c", visible=True, persistent=True, signal_name="data_changed", widget_type="entry", mix_with=(SignalMixin,) ) #: Debye-Waller scattering factor debye = FloatProperty( default=0.0, text="c", visible=True, persistent=True, signal_name="data_changed", widget_type="entry", mix_with=(SignalMixin,) ) #: The charge of the ion (eg. 3.0 for :math:`Al^{3+}`) charge = FloatProperty( default=0.0, text="Charge", visible=True, persistent=True, signal_name="data_changed", widget_type="entry", mix_with=(SignalMixin,) ) #: The atomic weight for this ion weight = FloatProperty( default=0.0, text="Weight", visible=True, persistent=True, signal_name="data_changed", widget_type="entry", mix_with=(SignalMixin,) ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): """ Constructor takes any of its properties as a keyword argument. Any other arguments or keywords are passed to the base class. """ keys = [ "data_%s" % prop.label for prop in type(self).Meta.get_local_persistent_properties()] keys.extend([ prop.label for prop in type(self).Meta.get_local_persistent_properties()]) my_kwargs = self.pop_kwargs(kwargs, *keys) super(AtomType, self).__init__(*args, **kwargs) kwargs = my_kwargs # Set up data object self._data_object = AtomTypeData( par_a=np.zeros(shape=(5,), dtype=float), par_b=np.zeros(shape=(5,), dtype=float), par_c=0.0, debye=0.0, charge=0.0, weight=0.0 ) # Set attributes: self.name = str(self.get_kwarg(kwargs, "", "name", "data_name")) self.atom_nr = int(self.get_kwarg(kwargs, 0, "atom_nr", "data_atom_nr")) self.weight = float(self.get_kwarg(kwargs, 0, "weight", "data_weight")) self.charge = float(self.get_kwarg(kwargs, 0, "charge", "data_charge")) self.debye = float(self.get_kwarg(kwargs, 0, "debye", "data_debye")) for kw in ["par_a%d" % i for i in range(1, 6)] + ["par_b%d" % i for i in range(1, 6)] + ["par_c"]: setattr( self, kw, self.get_kwarg(kwargs, 0.0, kw, "data_%s" % kw) ) def __str__(self): return "" % (self.name, id(self)) def get_atomic_scattering_factors(self, stl_range): """ Returns the atomic scattering factor for this `AtomType` for the given range of sin(theta)/lambda (`stl_range`) values. """ angstrom_range = ((stl_range * 0.05) ** 2) return get_atomic_scattering_factor(angstrom_range, self.data_object) pass # end of class @storables.register() class Atom(DataModel, Storable): """ Atom objects combine structural information (z coordinate and proportion) and an AtomType. """ # MODEL METADATA: class Meta(DataModel.Meta): store_id = "Atom" layer_filters = [ ("Layer file", get_case_insensitive_glob("*.lyr")), ] _data_object = None @property def data_object(self): """ The data object that is used in the calculations framework (see :mod:`pyxrd.generic.calculations.atoms`). Is an instance of :class:`~pyxrd.generic.calculations.data_objects.AtomData` """ self._data_object.default_z = self.default_z self._data_object.stretch_z = self.stretch_z self._data_object.pn = self.pn self._data_object.atom_type = getattr(self.atom_type, "data_object", None) return self._data_object component = property(DataModel.parent.fget, DataModel.parent.fset) # PROPERTIES: #: The name of the Atom name = StringProperty( default="", text="Name", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: Default z coordinate for this Atom. Also see :attr:`~z` default_z = FloatProperty( default=0.0, text="Default z", visible=True, persistent=True, tabular=True, signal_name="data_changed", mix_with=(SignalMixin,) ) #: Flag indicating whether or not z coordinates should be stretched #: using the silicate lattice and unit cell dimensions from the Component. #: Should be set for interlayer atoms, so their z coordinates are adjusted #: when the component basal spacing is changed. Also see `z`. stretch_z = BoolProperty( default=False, text="Stretch z values", visible=False, persistent=True, signal_name="data_changed", mix_with=(SignalMixin,) ) #: The z coordinate for this atom. If `stretch_values` is False or if #: this Atom's component is None, then this will return the `default_z` #: value. If `stretch_values` is True and a component is set on this Atom, #: it is calculated as:: #: #: `lattice_d + (default_z - lattice_d) * factor` #: #: where `lattice_d` and `factor` are given by calling #:`get_interlayer_stretch_factors` on the :class:`~pyxrd.phases.models.Component`. @FloatProperty( default=None, text="Z", visible=True, tabular=True, mix_with=(ReadOnlyMixin,) ) def z(self): if self.stretch_values and self.component is not None: lattice_d, factor = self.component.get_interlayer_stretch_factors() return float(lattice_d + (self.default_z - lattice_d) * factor) return self.default_z #: The # of atoms (projected onto the c-axis for the considered unit cell) pn = FloatProperty( default=None, text="Multiplicity", visible=True, persistent=True, tabular=True, signal_name="data_changed", mix_with=(SignalMixin,) ) def _get_weight(self): if self.atom_type is not None: return self.pn * self.atom_type.weight else: return 0.0 #: The total weight for this Atom, taking `pn` into consideration. weight = FloatProperty( default=None, text="Weight", fget=_get_weight, visible=False, persistent=False, mix_with=(ReadOnlyMixin,) ) _atom_type_uuid = None _atom_type_name = None def _set_atom_type(self, value): old = type(self).atom_type._get(self) if old is not None: self.relieve_model(old) type(self).atom_type._set(self, value) if value is not None: self.observe_model(value) #: The AtomType to be used for this Atom. atom_type = LabeledProperty( default=None, text="Atom Type", visible=True, persistent=False, tabular=True, data_type=AtomType, signal_name="data_changed", fset=_set_atom_type, mix_with=(SignalMixin,) ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): """ Constructor takes any of its properties as a keyword argument. In addition to the above, the constructor still supports the following deprecated keywords, maping to a current keyword: - z: maps to the 'default_z' keyword. Any other arguments or keywords are passed to the base class. """ my_kwargs = self.pop_kwargs(kwargs, "data_name", "data_z", "z", "data_pn", "data_atom_type", "stretch_z", "atom_type_uuid", "atom_type_name", "atom_type_index", "atom_type", *[prop.label for prop in type(self).Meta.get_local_persistent_properties()] ) super(Atom, self).__init__(*args, **kwargs) kwargs = my_kwargs # Set up data object self._data_object = AtomData( default_z=0.0, pn=0.0 ) # Set attributes self.name = str(self.get_kwarg(kwargs, "", "name", "data_name")) self.stretch_values = bool(self.get_kwarg(kwargs, False, "stretch_values")) self.default_z = float(self.get_kwarg(kwargs, 0.0, "default_z", "data_z", "z")) self.pn = float(self.get_kwarg(kwargs, 0.0, "pn", "data_pn")) self.atom_type = self.get_kwarg(kwargs, None, "atom_type") self._atom_type_uuid = self.get_kwarg(kwargs, None, "atom_type_uuid") self._atom_type_name = self.get_kwarg(kwargs, None, "atom_type_name") def __str__(self): return "" % (self.name, repr(self)) def _unattach_parent(self): self.atom_type = None super(Atom, self)._unattach_parent() # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @Observer.observe("removed", signal=True) def on_removed(self, model, prop_name, info): """ This method observes the Atom types 'removed' signal, as such, if the AtomType gets removed from the parent project, it is also cleared from this Atom """ if model == self.atom_type: self.atom_type = None # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def get_structure_factors(self, stl_range): """ Get the atom's structure factor for a given range of 2*sin(θ) / λ values. Expects λ to be in nanometers! """ if self.atom_type is not None: return float(get_structure_factor(stl_range, self.data_object)) else: return 0.0 # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ def resolve_json_references(self): if getattr(self, "_atom_type_uuid", None) is not None: self.atom_type = type(type(self)).object_pool.get_object(self._atom_type_uuid) if getattr(self, "_atom_type_name", None) is not None: assert(self.component is not None) assert(self.component.phase is not None) assert(self.component.phase.project is not None) for atom_type in self.component.phase.project.atom_types: if atom_type.name == self._atom_type_name: self.atom_type = atom_type break self._atom_type_uuid = None self._atom_type_name = None def json_properties(self): retval = super(Atom, self).json_properties() if self.component is None or self.component.export_atom_types: retval["atom_type_name"] = self.atom_type.name if self.atom_type is not None else "" else: retval["atom_type_uuid"] = self.atom_type.uuid if self.atom_type is not None else "" return retval @staticmethod def get_from_csv(filename, callback=None, parent=None): """ Returns a list of atoms fetched from the .CSV file `filename`. If parent is passed, this will be used to resolve AtomType references, and will be passed to the constructor of the Atom as a keyword. If callback is passes it will be called with the loaded atom as the first and only argument. """ import csv atl_reader = csv.reader(open(filename, 'rb'), delimiter=',', quotechar='"') # TODO create csv dialect! header = True atoms = [] types = dict() if parent is not None: for atom_type in parent.phase.project.atom_types: if not atom_type.name in types: types[atom_type.name] = atom_type for row in atl_reader: if not header and len(row) >= 4: if len(row) == 5: name, z, def_z, pn, atom_type = row[0], float(row[1]), float(row[2]), float(row[3]), types[row[4]] if parent is not None else None else: name, z, pn, atom_type = row[0], float(row[1]), float(row[2]), types[row[3]] if parent is not None else None def_z = z if atom_type in types: atom_type = types[atom_type] new_atom = Atom(name=name, z=z, default_z=def_z, pn=pn, atom_type=atom_type, parent=parent) atoms.append(new_atom) if callback is not None and callable(callback): callback(new_atom) del new_atom header = False return atoms @staticmethod def save_as_csv(filename, atoms): """ Saves a list of atoms to the passed filename. """ import csv atl_writer = csv.writer(open(filename, 'wb'), delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) atl_writer.writerow(["Atom", "z", "def_z", "pn", "Element"]) for item in atoms: if item is not None and item.atom_type is not None: atl_writer.writerow([item.name, item.z, item.default_z, item.pn, item.atom_type.name]) pass # end of class PyXRD-0.8.4/pyxrd/atoms/views.py000066400000000000000000000034261363064711000164770ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport from matplotlib.figure import Figure # TODO: move this to mvc somehow: from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvasGTK from pyxrd.generic.views import BaseView class EditAtomTypeView(BaseView): builder = resource_filename(__name__, "glade/atoms.glade") top = "edit_atom_type" widget_format = "atom_%s" # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): BaseView.__init__(self, *args, **kwargs) self.graph_parent = self["view_graph"] self.setup_matplotlib_widget() def setup_matplotlib_widget(self): self.figure = Figure(dpi=72) self.plot = self.figure.add_subplot(111) self.figure.subplots_adjust(bottom=0.20) self.matlib_canvas = FigureCanvasGTK(self.figure) self.plot.autoscale_view() self.graph_parent.add(self.matlib_canvas) self.graph_parent.show_all() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update_figure(self, x, y): self.plot.cla() self.plot.plot(x, y, 'k-', aa=True) self.plot.set_ylabel('Scattering factor', size=14, weight="heavy") self.plot.set_xlabel('2θ', size=14, weight="heavy") self.plot.autoscale_view() if self.matlib_canvas is not None: self.matlib_canvas.draw() PyXRD-0.8.4/pyxrd/calculations/000077500000000000000000000000001363064711000163215ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/calculations/CSDS.py000066400000000000000000000021301363064711000174230ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import numpy as np from math import sqrt, log from .math_tools import lognormal def calculate_distribution(CSDS): r""" Returns a tuple containing a numpy array with the (log normal) CSDS distribution and a float containing the arithmetic mean of that distribution. Takes a single :class:`~CSDSData` object as argument. """ a = CSDS.alpha_scale * log(CSDS.average) + CSDS.alpha_offset b = sqrt(CSDS.beta_scale * log(CSDS.average) + CSDS.beta_offset) steps = int(CSDS.maximum - CSDS.minimum) + 1 maxT = 0 smq = 0 q_log_distr = [] TQDistr = dict() for i in range(steps): T = max(CSDS.minimum + i, 1e-50) q = lognormal(T, a, b) smq += q TQDistr[int(T)] = q maxT = T TQarr = np.zeros(shape=(maxT + 1,), dtype=float) Rmean = 0 for T, q in TQDistr.items(): TQarr[T] = q / smq Rmean += T * q Rmean /= smq return TQarr, Rmean PyXRD-0.8.4/pyxrd/calculations/__init__.py000066400000000000000000000023071363064711000204340ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. """ This module contains the basic implementation of the matrix formalism as detailed in Drits and Tchoubar (1990) and Plançon (2001). It was chosen to implement this using 'loose' function calls. The disadvantage of this approach is that the functions are no longer bound to class instances, which makes them less intuitive to use. The advantage is we can more easily call these functions asynchronously (e.g. using :py:class:`~multiprocessing.Pool`) Despite all this, most function calls in this module do expect to be passed a :class:`~pyxrd.calculations.DataObject` sub-class, which wraps all the data in a single object. These :class:`~pyxrd.calculations.DataObject` s map onto the different models used. As such this module is also largely independent from the MVC framework used. Drits, V.A., and Tchoubar, C., 1990. X-Ray Diffraction by Disordered Lamellar Structures: Theory and Applications to Microdivided Silicates and Carbons. Springer-Verlag, Berlin, Germany. Plançon, A., 2001. Order-disorder in clay mineral structures. Clay Miner 36, 1–14. """PyXRD-0.8.4/pyxrd/calculations/atoms.py000066400000000000000000000046341363064711000200250ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import numpy as np from math import pi import logging logger = logging.getLogger(__name__) def get_atomic_scattering_factor(angstrom_range, atom_type): r""" Calculates the atomic scatter factor for - angstrom_range: numpy array of (2*sin(θ) / λ)² values (= 1/A² units) - atom_type: an :class:`~pyxrd.calculations.data_objects.AtomTypeData` instance The atomic scattering factor is calculated as follows: .. math:: :nowrap: \begin{flalign*} & ASF = \left[ c + \sum_{i=1}^{5}{ \left( a_i \cdot e ^ { - b_i \cdot {\left(2 \cdot \frac{sin(\theta)}{\lambda}\right)}^2 } \right) } \right] \cdot e ^ { B \cdot {\left(2 \cdot \frac{sin(\theta)}{\lambda}\right)}^2 } \end{flalign*} Where a_i, b_i and c are the scattering factors taken from `atom_type` """ if atom_type is not None: asf = np.sum(atom_type.par_a * np.exp(-atom_type.par_b * angstrom_range[..., np.newaxis]), axis=1) + atom_type.par_c asf = asf * np.exp(-atom_type.debye * angstrom_range) return asf else: logger.warning("get_atomic_scattering_factor reports: 'None found!'") return np.zeros_like(angstrom_range) def get_structure_factor(range_stl, atom): r""" Calculates the atom's structure factor for - range_stl: numpy array of 2*sin(θ) / λ values (= 1/nm units) - atom_type: an :class:`~pyxrd.calculations.data_objects.AtomData` instance The structure factor is calculated using the atomic scattering factor ASF (calculated by :meth:`~pyxrd.calculations.atoms.get_atomic_scattering_factor`) as follows: .. math:: :nowrap: \begin{flalign*} & SF = ASF \cdot p \cdot e ^ { 2 \cdot \pi \cdot z \cdot i \cdot \frac{2 \cdot sin(\theta)}{\lambda} } \end{flalign*} """ if atom is not None and atom.atom_type is not None: angstrom_range = ((range_stl * 0.05) ** 2) asf = get_atomic_scattering_factor(angstrom_range, atom.atom_type) return asf * atom.pn * np.exp(2 * pi * atom.z * range_stl * 1j) else: logger.warning("get_structure_factor reports: 'None found!'") return np.zeros_like(range_stl) PyXRD-0.8.4/pyxrd/calculations/components.py000066400000000000000000000036531363064711000210670ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from itertools import chain from math import pi import numpy as np from .atoms import get_structure_factor def calculate_z(default_z, lattice_d, z_factor): return lattice_d + z_factor * (default_z - lattice_d) def get_factors(range_stl, component): r""" Returns a tuple containing the structure factor and phase difference for - range_stl: numpy array of 2*sin(θ) / λ values - component: a :class:`~pyxrd.calculations.data_objects.ComponentData` instance containing two lists of layer and interlayer :class:`~pyxrd.calculations.data_objects.Atom`'s. This function calls :meth:`~pyxrd.calculations.atoms.get_structure_factor` for each atom in the layer and interlayer list of the component data object and sums the result to obtain the component's structure factors. The component's phase differences are calculated as follows: .. math:: :nowrap: \begin{flalign*} & \psi = e^ { 2 \pi \cdot { \frac { 2 \cdot sin(\theta)} {\lambda} } \cdot \left( d_{001} \cdot i - \pi \cdot \delta d_{001} \cdot { \frac {2 \cdot sin(\theta)} {\lambda} } \right) } \end{flalign*} """ z_factor = (component.d001 - component.lattice_d) / (component.default_c - component.lattice_d) num_layer_atoms = len(component.layer_atoms) sf_tot = 0.0 + 0.0j for i, atom in enumerate(chain(component.layer_atoms, component.interlayer_atoms)): atom.z = atom.default_z if i >= num_layer_atoms: atom.z = calculate_z(atom.z, component.lattice_d, z_factor) sf_tot += get_structure_factor(range_stl, atom) phi_tot = np.exp(2.*pi * range_stl * (component.d001 * 1j - pi * component.delta_c * range_stl)) return sf_tot, phi_tot PyXRD-0.8.4/pyxrd/calculations/data_objects.py000066400000000000000000000151331363064711000213200ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. """ The following classes are not meant to be used directly, rather you should create the corresponding model instances and retrieve the DataObject from them. The rationale behind not using the model instances directly is that they are difficult to serialize or pickle (memory-)efficiently. This is mainly due to all of the boiler-plate code that takes care of references, saving, loading, calculating properties from other properties etc. A lot of this is not needed for the actual calculation. The data objects below, on the other hand, only contain the data needed to be able to calculate XRD patterns. """ class DataObject(object): """ The base class for all DataObject instances. The constructor takes any number of keyword arguments it will set as attributes on the instance. """ def __init__(self, **kwargs): for key, val in kwargs.items(): setattr(self, key, val) pass # end of class class AtomTypeData(DataObject): """ The DataObject describing an AtomType. """ #: a numpy array of `a` scattering factors par_a = None #: a numpy array of `b` scattering factors par_b = None #: the `c` scattering constant par_c = None #: the debye-waller temperature factor debye = None pass # end of class class AtomData(DataObject): """ The DataObject describing an Atom. """ #: an :class:`~AtomTypeData` instance atom_type = None #: the # of atoms projected to this z coordinate pn = None #: the default z coordinate default_z = None #: the actual z coordinate z = None pass # end of class class ComponentData(DataObject): """ The DataObject describing an Atom """ #: a list of :class:`~AtomData` instances layer_atoms = None #: a list of :class:`~AtomData` instances interlayer_atoms = None #: the component volume volume = None #: the component weight weight = None #: the d-spacing of the component d001 = None #: the default d-spacing of the component default_c = None #: the variation in d-spacing of the component delta_c = None #: the height of the silicate lattice (excluding the interlayer space) lattice_d = None pass # end of class class CSDSData(DataObject): """ The DataObject describing the CSDS distribution. """ #: average CSDS average = None #: maximum CSDS maximum = None #: minimum CSDS minimum = None #: the alpha scale factor for the log-normal distribution alpha_scale = None #: the alpha offset factor for the log-normal distribution alpha_offset = None #: the beta scale factor for the log-normal distribution beta_scale = None #: the beta offset factor for the log-normal distribution beta_offset = None pass # end of class class GonioData(DataObject): """ The DataObject describing the Goniometer setup. """ #: Lower 2-theta bound for calculated patterns min_2theta = None #: Upper 2-theta bound for calculated patterns max_2theta = None #: The number of steps in between the lower and upper 2-theta bounds steps = None #: If the first soller slits are present has_soller1 = False #: The first soller slit size soller1 = None #: If the first soller slits are present has_soller2 = False #: The second soller slit size soller2 = None #: The divergence slit mode divergence_mode = "FIXED" #: The divergence size (degrees (fixed) or mm (auto)) divergence = None #: The Bragg angle of the monochromator (or 0° if not present) mcr_2theta = 0 #: Flag indicating if intensities need to be corrected for absorption has_absorption_correction = None #: The sample mass absorption coefficient (mg/cm²) absorption = 45.0 #: The sample surface density (cm²/g) sample_surf_density = 20.0 #: The goniometer radius radius = None #: The goniometer wavelength wavelength = None #: The goniometer wavelength distribution wavelength_distribution = None #: The sample length sample_length = None pass # end of class class ProbabilityData(DataObject): """ The DataObject describing the layer stacking probabilities """ #: Whether this probability is really a valid one valid = None #: The number of components this probability describes G = None #: The weight fractions matrix W = None #: The probabilities matrix P = None pass # end of class class PhaseData(DataObject): """ The DataObject describing a phase """ #: A flag indicating whether to apply Lorentz-polarization factor or not apply_lpf = True #: A flag indicating whether to apply machine corrections or not apply_correction = True #: A list of :class:`~ComponentData` instances components = None #: A :class:`~ProbabilityData` instance probability = None #: The sigma start value sigma_star = None #: A :class:`~CSDSData` instance csds = None pass # end of class class SpecimenData(DataObject): """ The DataObject describing a specimen """ #: A :class:`~GonioData` instance goniometer = None #: The sample absorption absorption = None #: A list of :class:`~PhaseData` instances phases = None #: A numpy array with the observed intensities observed_intensity = None #: A numpy array with the calculated intensity total_intensity = None #: A nummpy array with the calculated phase profiles phase_intensities = None #: A numpy array with a correction factor taking the sample & goniometer # properties into account correction = None pass # end of class class MixtureData(DataObject): """ The DataObject describing a mixture """ #: A list of :class:`~SpecimenData` instances specimens = None #: A numpy array with the phase fractions fractions = None #: A numpy array with the specimen background shifts bgshifts = None #: A numpy array with the specimen absolute scales scales = None #: Whether this MixtureData object has been parsed (internal flag) parsed = False #: Whether this MixtureData object has been calculated (internal flag) calculated = False #: Whether this MixtureData object has been optimized (internal flag) optimized = False #: The number of specimens n = 0 #: The number of phases m = 0 pass # end of class PyXRD-0.8.4/pyxrd/calculations/exceptions.py000066400000000000000000000021661363064711000210610ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import multiprocessing from functools import wraps import traceback, sys class WrapException(Exception): """ A wrapped exception used by the :meth:`~wrap_exceptions` decorator. """ def __init__(self): exc_type, exc_value, exc_tb = sys.exc_info() self.exception = exc_value self.formatted = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb)) def __str__(self): return '%s\nOriginal traceback:\n%s' % (Exception.__str__(self), self.formatted) def wrap_exceptions(func): """ Function decorator that allows to provide useable tracebacks when the function is called asynchronously and raises an error. """ @wraps(func) def exception_wrapper(*args, **kwargs): try: return func(*args, **kwargs) except: if multiprocessing.current_process().daemon: raise WrapException() else: raise return exception_wrapperPyXRD-0.8.4/pyxrd/calculations/goniometer.py000066400000000000000000000046631363064711000210540ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import numpy as np from scipy.special import erf from math import sqrt from .math_tools import sqrt2pi, sqrt8 def get_S(soller1, soller2): _S = sqrt((soller1 * 0.5) ** 2 + (soller2 * 0.5) ** 2) _S1S2 = soller1 * soller2 return _S, _S1S2 def get_T(range_theta, sigma_star, soller1, soller2): sigma_star = float(max(sigma_star, 1e-18)) S, _ = get_S(soller1, soller2) range_st = np.sin(range_theta) Q = S / (sqrt8 * range_st * sigma_star) return erf(Q) * sqrt2pi / (2.0 * sigma_star * S) - 2.0 * range_st * (1.0 - np.exp(-(Q ** 2.0))) / (S ** 2.0) def get_lorentz_polarisation_factor(range_theta, sigma_star, soller1, soller2, mcr_2theta): """ Get the lorentz polarisation factor for the given sigma-star value, soller slits, monochromator Bragg angle and the given theta range """ T = get_T(range_theta, sigma_star, soller1, soller2) pol = np.cos(np.radians(mcr_2theta)) ** 2 return T * (1.0 + pol * (np.cos(2.0 * range_theta) ** 2)) / np.sin(range_theta) def get_fixed_to_ads_correction_range(range_theta, goniometer): return np.sin(range_theta) def get_nm_from_t(theta, wavelength=0.154056, zero_for_inf=False): """ Convert the given theta angles (scalar or np array) to nanometer spacings using the given wavelength """ return get_nm_from_2t(2.0 * theta, wavelength=wavelength, zero_for_inf=zero_for_inf) def get_nm_from_2t(twotheta, wavelength=0.154056, zero_for_inf=False): """ Convert the given 2-theta angles (scalar or np array) to nanometer spacings using the given wavelength """ if twotheta == 0: return 0. if zero_for_inf else 1e16 else: return wavelength / (2.0 * np.sin(np.radians(twotheta / 2.0))) def get_t_from_nm(nm, wavelength=0.154056): """ Convert the given nanometer spacings (scalar or np array) to theta angles using the given wavelength """ return get_2t_from_nm(nm, wavelength=wavelength) / 2 def get_2t_from_nm(nm, wavelength=0.154056): """ Convert the given nanometer spacings (scalar or np array) to 2-theta angles using the given wavelength """ twotheta = 0.0 if nm != 0: twotheta = np.degrees(np.arcsin(max(-1.0, min(1.0, wavelength / (2.0 * nm))))) * 2.0 return twotheta PyXRD-0.8.4/pyxrd/calculations/improve.py000066400000000000000000000042471363064711000203630ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from io import StringIO from scipy.optimize import fmin_l_bfgs_b from .exceptions import wrap_exceptions def setup_project(projectf): from pyxrd.file_parsers.json_parser import JSONParser from pyxrd.project.models import Project type(Project).object_pool.clear() f = StringIO(projectf) project = JSONParser.parse(f) f.close() return project @wrap_exceptions def run_refinement(projectf, mixture_index): """ Runs a refinement setup for - projectf: project data - mixture_index: what mixture in the project to use """ if projectf is not None: from pyxrd.data import settings settings.initialize() # Retrieve project and mixture: project = setup_project(projectf) del projectf import gc gc.collect() mixture = project.mixtures[mixture_index] mixture.refinement.update_refinement_treestore() refiner = mixture.refinement.get_refiner() refiner.refine() return list(refiner.history.best_solution), refiner.history.best_residual @wrap_exceptions def improve_solution(projectf, mixture_index, solution, residual, l_bfgs_b_kwargs={}): if projectf is not None: from pyxrd.data import settings settings.initialize() # Retrieve project and mixture: project = setup_project(projectf) del projectf mixture = project.mixtures[mixture_index] with mixture.data_changed.ignore(): # Setup context again: mixture.update_refinement_treestore() refiner = mixture.refinement.get_refiner() # Refine solution vals = fmin_l_bfgs_b( refiner.get_residual, solution, approx_grad=True, bounds=refiner.ranges, **l_bfgs_b_kwargs ) new_solution, new_residual = tuple(vals[0:2]) # Return result return new_solution, new_residual else: return solution, residual PyXRD-0.8.4/pyxrd/calculations/math_tools.py000066400000000000000000000060571363064711000210540ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from math import exp, sqrt, log, pi import numpy as np sqrtpi = sqrt(pi) sqrt2pi = sqrt(2 * pi) sqrt8 = sqrt(8) def mmult(A, B): return np.sum(np.transpose(A, (0, 2, 1))[:, :, :, np.newaxis] * B[:, :, np.newaxis, :], -3) def mdot(A, B): C = np.zeros(shape=A.shape, dtype=np.complex) for i in range(A.shape[0]): C[i] = np.dot(A[i], B[i]) return C def mtim(A, B): # currently unused? C = np.zeros(shape=A.shape, dtype=np.complex) for i in range(A.shape[0]): C[i] = np.multiply(A[i], B[i]) return C def solve_division(A, B): # currently unused? bt = np.transpose(B, axes=(0, 2, 1)) at = np.transpose(A, axes=(0, 2, 1)) return np.array([np.transpose(np.linalg.lstsq(bt[i], at[i])[0]) for i in range(bt.shape[0])]) def lognormal(T, a, b): return exp(-(log(T) - a) ** 2 / (2.0 * (b ** 2))) / (sqrt2pi * abs(b) * T) def add_noise(x, noise_fraction=0.05): if x.size > 0: abs_value = noise_fraction * np.amax(x) return x + np.random.standard_normal(x.shape) * abs_value else: return x def smooth(x, half_window_len=3, window='blackman'): """smooth the data using a window with requested size. This method is based on the convolution of a scaled window with the signal. The signal is prepared by introducing reflected copies of the signal (with the window size) in both ends so that transient parts are minimized in the beginning and end part of the output signal. input: x: 1D array like (assumed to be spaced equally) half_window_len: half of the dimension of the smoothing window, actual window is calculated from this so that: window_len = 2*half_window_len + 1, this ensures that the window is always an odd number, regardless of user input; window: the type of window from 'flat', 'hanning', 'hamming', 'bartlett', 'blackman' flat window will produce a moving average smoothing. output: the smoothed signal example: t=linspace(-2,2,0.1) x=sin(t)+randn(len(t))*0.1 y=smooth(x) see also: numpy.hanning, numpy.hamming, numpy.bartlett, numpy.blackman, numpy.convolve scipy.signal.lfilter """ window_len = half_window_len * 2 + 1 if x.ndim != 1: x = np.ndarray.flatten(x) if x.size < window_len: raise ValueError("Input vector needs to be bigger than window size.") if window_len < 3: return x if not window in ['flat', 'hanning', 'hamming', 'bartlett', 'blackman']: raise ValueError("Window is on of 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'") s = np.r_[x[window_len - 1:0:-1], x, x[-1:-window_len:-1]] if window == 'flat': # moving average w = np.ones(window_len, 'd') else: w = eval('np.' + window + '(window_len)') y = np.convolve(w / w.sum(), s, mode='valid') return y[half_window_len:-half_window_len]PyXRD-0.8.4/pyxrd/calculations/mixture.py000066400000000000000000000245771363064711000204070ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import time import logging logger = logging.getLogger(__name__) import numpy as np from scipy.optimize import fmin_l_bfgs_b from pyxrd.data import settings from .specimen import calculate_phase_intensities, calculate_scaled_intensities, get_clipped_intensities from .exceptions import wrap_exceptions from .statistics import Rp, Rpw, Rpder, Rphase __residual_method_map = { "Rp" : Rp, "Rpw" : Rpw, "Rpder": Rpder, "Rphase": Rphase } #=============================================================================== # Solution vector handling: #=============================================================================== def parse_solution(x, mixture): # Get the original solution fractions = mixture.fractions scales = mixture.scales bgshifts = mixture.bgshifts # We normalize the new fractions so the sum of all fractions # (including static fractions) is always 1 scaled_fracs = x[:mixture.m] * (1.0 - mixture.sum_static_fractions) / np.sum(x[:mixture.m]) # Store the variables in the right positions: np.put(fractions, mixture.selected_fractions, scaled_fracs) np.put(scales, mixture.selected_scales, x[mixture.m:mixture.m + mixture.n]) np.put(bgshifts, mixture.selected_bgshifts, x[-mixture.o:]) # Return the complete arrays (including static factors) return fractions, scales, bgshifts def get_solution(mixture): return np.concatenate(( np.take(mixture.fractions, mixture.selected_fractions), np.take(mixture.scales, mixture.selected_scales), np.take(mixture.bgshifts, mixture.selected_bgshifts) )) def set_solution(x, mixture): mixture.fractions, mixture.scales, mixture.bgshifts = parse_solution(x, mixture) def get_zero_solution(mixture): # Create complete solution array x0 = np.ones(shape=(mixture.m+mixture.n+mixture.o,)) # Set all bg shifts to zero x0[-mixture.o:] = 0.0 # Set all fractions to equal size (of the remainder) x0[:mixture.m] = 1.0 / (1.0 - mixture.sum_static_fractions) return x0 #=============================================================================== # Solution bounds handling: #=============================================================================== def get_bounds(mixture): bounds = [(0., 1.) for _ in range(mixture.m)] # allow zero fractions bounds += [(1e-3, None) for _ in range(mixture.n)] # don't allow zero scales bounds += [(0., None) for _ in range(mixture.o)] return bounds #=============================================================================== # Residual calculations: #=============================================================================== def _get_specimen_residual(specimen): """ Returns the residual error for the given specimen and the (otionally) given calculated data. If no calculated data is passed, the calculated data stored in the specimen object is used (and assumed to be set). """ exp, cal = get_clipped_intensities(specimen) return __residual_method_map[settings.RESIDUAL_METHOD](exp, cal) def _get_residuals(x, mixture): set_solution(x, mixture) mixture = calculate_mixture(mixture) return mixture.residuals def get_residual(mixture): return _get_residuals(get_solution(mixture), mixture) #=============================================================================== # Mixture calculations: #=============================================================================== def parse_mixture(mixture, force=False): """ This calculates the phase intensities. """ if not mixture.parsed or force: mixture.parsed = False # Sanity checks: assert mixture.n > 0, "Need at least 1 specimen to optimize phase fractions, scales and background." assert mixture.n == len(mixture.specimens), "Invalid specimen count on mixture data object." assert mixture.m > 0, "Need at least 1 phase in one of the specimen to optimize phase fractions, scales and background." # We have some fixed fractions: mixture.selected_fractions = np.asanyarray(np.nonzero(mixture.fractions_mask)).flatten() mixture.selected_scales = np.asanyarray(np.nonzero(mixture.scales_mask)).flatten() mixture.selected_bgshifts = np.asanyarray(np.nonzero(mixture.bgshifts_mask)).flatten() mixture.real_m = mixture.m mixture.real_n = mixture.n mixture.real_o = mixture.n mixture.m = len(mixture.selected_fractions) mixture.n = len(mixture.selected_scales) mixture.o = len(mixture.selected_bgshifts) # The first term should be 1.0, unless the user entered a custom fraction sm = np.sum(mixture.fractions) mixture.sum_static_fractions = sm - np.sum(np.take(mixture.fractions, mixture.selected_fractions)) if sm != 1.0 and mixture.sum_static_fractions < 0.0 or mixture.sum_static_fractions > 1.0: mixture.fractions = mixture.fractions / sm sm = 1.0 mixture.sum_static_fractions = sm - np.sum(np.take(mixture.fractions, mixture.selected_fractions)) mixture.z = 0 for specimen in mixture.specimens: if specimen is not None: mixture.z = len(specimen.z_list) break assert mixture.z > 0, "Need at least 1 pattern in one of the specimens to optimize phase fractions, scales and background." for specimen in mixture.specimens: if specimen is not None: specimen.correction, specimen.phase_intensities = \ calculate_phase_intensities(specimen) else: logger.warning("parse_mixture reports: 'None found!'") mixture.parsed = True @wrap_exceptions def optimize_mixture(mixture, force=False): """ Optimizes the mixture fractions, scales and bg shifts. Returns the mixture data object. """ if not mixture.optimized or force: # 0. Calculate phase intensitities try: parse_mixture(mixture) except AssertionError: if settings.DEBUG: raise return mixture # ignore and return the original object back # 1. setup start point: x0 = get_zero_solution(mixture) bounds = get_bounds(mixture) # 2. Define target function: def _get_average_residual(x): # Reset this flag so we actually recalculated stuff: mixture.calculated = False res = _get_residuals(x, mixture)[0] return res # 3. Optimize: t1 = time.time() lastx, residual, info = fmin_l_bfgs_b( _get_average_residual, x0, approx_grad=True, bounds=bounds, iprint=-1 ) if np.isscalar(residual): # Make sure this is an array: residual = np.array([residual]) t2 = time.time() logger.debug('%s took %0.3f ms' % ("optimize_mixture", (t2 - t1) * 1000.0)) logger.debug(' Solution: %s' % lastx) logger.debug(' Average residual: %s' % residual) logger.debug(' Info dict: %s' % info) # 4. rescale scales and fractions so they fit into [0-1] range, # and round them to have 6 digits max: fractions, scales, bgshifts = parse_solution(lastx, mixture) fractions = fractions.flatten() sum_frac = np.sum(fractions) if sum_frac == 0.0 and len(fractions) > 0: # prevent NaN errors fractions[0] = 1.0 sum_frac = 1.0 fractions = np.around((fractions / sum_frac), 6) scales *= sum_frac scales = scales.round(6) # 5. Set properties on data object mixture.fractions = fractions mixture.scales = scales mixture.bgshifts = bgshifts mixture.residual = residual # We still need to recalculated with the last solution mixture.calculated = False # But we do have optimized fractions etc. mixture.optimized = True return mixture @wrap_exceptions def calculate_mixture(mixture, force=False): """ Calculates total intensities for the current mixture, without optimizing fractions, scales & background shifts. Returns the mixture data object. """ if not mixture.calculated or force: try: parse_mixture(mixture) except AssertionError: for specimen in mixture.specimens: if specimen is not None: specimen.total_intensity = None # clear pattern specimen.scaled_phase_intensities = None # clear pattern return mixture fractions = np.asanyarray(mixture.fractions) # This will contain the following residuals: # Average, Specimen1, Specimen2, ... mixture.residuals = [0.0, ] for scale, bgshift, specimen in zip(mixture.scales, mixture.bgshifts, mixture.specimens): specimen_residual = 0.0 if specimen is not None: bgshift = bgshift if settings.BGSHIFT else 0.0 specimen = calculate_scaled_intensities(specimen, scale, fractions, bgshift) if specimen.observed_intensity.size > 0: specimen_residual = _get_specimen_residual(specimen) else: logger.warning("calculate_mixture reports: 'Zero observations found!'") mixture.residuals.append(specimen_residual) mixture.residuals[0] = np.average(mixture.residuals[1:]) mixture.calculated = True return mixture @wrap_exceptions def calculate_and_optimize_mixture(mixture): """ Calculates total intensities for the current mixture, after optimizing fractions, scales & background shifts. Returns the mixture data object. """ return calculate_mixture(optimize_mixture(mixture)) @wrap_exceptions def get_optimized_residual(mixture): """ Calculates total intensities for the current mixture, after optimizing fractions, scales & background shifts. Returns the average residual instead of the mixture data object. """ mixture = calculate_and_optimize_mixture(mixture) return mixture.residual PyXRD-0.8.4/pyxrd/calculations/peak_detection.py000066400000000000000000000261251363064711000216570ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import numpy as np from scipy import stats from .math_tools import smooth def find_closest(value, array, col=0): """ Find closest value to another value in an array """ nparray = np.array(list(zip(*array))[col]) idx = (np.abs(nparray - value)).argmin() return array[idx] def score_minerals(peak_list, minerals): """ Scoring function for mineral peaks peak_list: list of tuples containing observed peak position and (abs) intensity minerals: dict with the mineral name as key and a peak list as value, analogeous to the first argument but with relative intensities Uses a simple appoach: loop over the reference minerals, loop over first 10 strongest peaks for that mineral (starting at stronger reflections towards weaker) find a peak in the observed peak data set that matches if matching add to the list of matched peaks if not matching and this is the strongest reflections, ignore mineral after this initial loop we have a list of matched peaks, this list is then used to calculate a score for the mineral by looking at how well positions and intensities match with the reference and how many peaks are actually matched of course. Higher values means a higher likelihood this mineral is present. """ max_pos_dev = 0.01 # fraction scores = [] for mineral, abbreviation, mpeaks in minerals: tot_score = 0 p_matches = [] i_matches = [] already_matched = [] mpeaks = sorted(mpeaks, key=lambda peak: peak[0], reverse=True) if len(mpeaks) > 15: mpeaks = mpeaks[:15] for i, (mpos, mint) in enumerate(mpeaks): epos, eint = find_closest(mpos, peak_list) if abs(epos - mpos) / mpos <= max_pos_dev and not epos in already_matched: p_matches.append([mpos, epos]) i_matches.append([mint, eint]) already_matched.append(epos) elif i == 0: break # if strongest peak does not match, ignore mineral if len(p_matches) > 3: p_matches = np.array(p_matches) i_matches = np.array(i_matches) i_matches[:, 1] = i_matches[:, 1] / np.max(i_matches[:, 1]) p_slope, p_intercept, p_r_value, p_value, p_std_err = stats.linregress(p_matches) # @UnusedVariable i_slope, i_intercept, i_r_value, p_value, i_std_err = stats.linregress(i_matches) # @UnusedVariable p_factor = (p_r_value ** 2) * min(1.0 / (abs(1.0 - p_slope) + 1E-50), 1000.) / 1000.0 i_factor = (1.0 - min(i_std_err / 0.25, 5.0) / 5.0) * min(1.0 / (abs(1.0 - i_slope) + 1E-50), 1000.) / 1000.0 # * max(1. / (abs(i_intercept) + 1E-50), 100.) / 100. tot_score = len(p_matches) * p_factor * i_factor if tot_score > 0: scores.append((mineral, abbreviation, mpeaks, p_matches, tot_score)) scores = sorted(scores, key=lambda score: score[-1], reverse=True) return scores def peakdetect(y_axis, x_axis=None, lookahead=500, delta=0): """ single run of multi_peakdetect """ mintabs, maxtabs = multi_peakdetect(y_axis, x_axis, lookahead, [delta]) return mintabs[0], maxtabs[0] def multi_peakdetect(y_axis, x_axis=None, lookahead=500, deltas=[0]): """ Converted from/based on a MATLAB script at http://billauer.co.il/peakdet.html Algorithm for detecting local maximas and minmias in a signal. Discovers peaks by searching for values which are surrounded by lower or larger values for maximas and minimas respectively keyword arguments: y_axis -- A list containg the signal over which to find peaks x_axis -- A x-axis whose values correspond to the 'y_axis' list and is used in the return to specify the postion of the peaks. If omitted the index of the y_axis is used. (default: None) lookahead -- (optional) distance to look ahead from a peak candidate to determine if it is the actual peak (default: 500) '(sample / period) / f' where '4 >= f >= 1.25' might be a good value deltas -- (optional) this specifies a minimum difference between a peak and the following points, before a peak may be considered a peak. Useful to hinder the algorithm from picking up false peaks towards to end of the signal. To work well delta should be set to 'delta >= RMSnoise * 5'. (default: 0) Delta function causes a 20% decrease in speed, when omitted Correctly used it can double the speed of the algorithm return -- two lists [maxtab, mintab] containing the positive and negative peaks respectively. Each cell of the lists contains a tupple of: (position, peak_value) to get the average peak value do 'np.mean(maxtab, 0)[1]' on the results """ rlen = list(range(len(deltas))) maxtab = [ [] for i in rlen] # @UnusedVariable mintab = [ [] for i in rlen] # @UnusedVariable dump = [ [] for i in rlen] # Used to pop the first hit which always if false @UnusedVariable length = len(y_axis) y_axis = y_axis / np.max(y_axis) if x_axis is None: x_axis = list(range(length)) # perform some checks if length != len(x_axis): raise ValueError("Input vectors y_axis and x_axis must have same length") if lookahead < 1: raise ValueError("Lookahead must be above '1' in value") # needs to be a numpy array y_axis = np.asarray(y_axis) # Only detect peak if there is 'lookahead' amount of points after it for j, delta in enumerate(deltas): # maxima and minima candidates are temporarily stored in # mx and mn respectively mn, mx = np.Inf, -np.Inf for index, (x, y) in enumerate(zip(x_axis[:-lookahead], y_axis[:-lookahead])): if y > mx: mx = y mxpos = x if y < mn: mn = y mnpos = x ####look for max#### if y < mx - delta and mx != np.Inf: # Maxima peak candidate found # look ahead in signal to ensure that this is a peak and not jitter if y_axis[index:index + lookahead].max() < mx: maxtab[j].append((mxpos, mx)) dump[j].append(True) # set algorithm to only find minima now mx = np.Inf mn = np.Inf ####look for min#### if y > mn + delta and mn != -np.Inf: # Minima peak candidate found # look ahead in signal to ensure that this is a peak and not jitter if y_axis[index:index + lookahead].min() > mn: mintab[j].append((mnpos, mn)) dump[j].append(False) # set algorithm to only find maxima now mn = -np.Inf mx = -np.Inf # Remove the false hit on the first value of the y_axis for j in rlen: try: if dump[j][0]: maxtab[j].pop(0) else: mintab[j].pop(0) # del dump[j] except IndexError: # no peaks were found, should the function return empty lists? pass return maxtab, mintab def peakdetect_zero_crossing(y_axis, x_axis=None, window=49): """ Algorithm for detecting local maximas and minmias in a signal. Discovers peaks by dividing the signal into bins and retrieving the maximum and minimum value of each the even and odd bins respectively. Division into bins is performed by smoothing the curve and finding the zero crossings. Suitable for repeatable sinusoidal signals with some amount of RMS noise tolerable. Excecutes faster than 'peakdetect', although this function will break if the offset of the signal is too large. It should also be noted that the first and last peak will probably not be found, as this algorithm only can find peaks between the first and last zero crossing. keyword arguments: y_axis -- A list containg the signal over which to find peaks x_axis -- A x-axis whose values correspond to the 'y_axis' list and is used in the return to specify the postion of the peaks. If omitted the index of the y_axis is used. (default: None) window -- the dimension of the smoothing window; should be an odd integer (default: 49) return -- two lists [maxtab, mintab] containing the positive and negative peaks respectively. Each cell of the lists contains a tupple of: (position, peak_value) to get the average peak value do 'np.mean(maxtab, 0)[1]' on the results """ if x_axis is None: x_axis = list(range(len(y_axis))) length = len(y_axis) if length != len(x_axis): raise ValueError('Input vectors y_axis and x_axis must have same length') # needs to be a numpy array y_axis = np.asarray(y_axis) zero_indices = zero_crossings(y_axis, window=window) period_lengths = np.diff(zero_indices) bins = [y_axis[indice:indice + diff] for indice, diff in zip(zero_indices, period_lengths)] even_bins = bins[::2] odd_bins = bins[1::2] # check if even bin contains maxima if even_bins[0].max() > abs(even_bins[0].min()): hi_peaks = [even.max() for even in even_bins] lo_peaks = [odd.min() for odd in odd_bins] else: hi_peaks = [odd.max() for odd in odd_bins] lo_peaks = [even.min() for even in even_bins] hi_peaks_x = [x_axis[np.where(y_axis == peak)[0]] for peak in hi_peaks] lo_peaks_x = [x_axis[np.where(y_axis == peak)[0]] for peak in lo_peaks] maxtab = [(x, y) for x, y in zip(hi_peaks, hi_peaks_x)] mintab = [(x, y) for x, y in zip(lo_peaks, lo_peaks_x)] return maxtab, mintab def zero_crossings(y_axis, x_axis=None, window=24): """ Algorithm to find zero crossings. Smoothens the curve and finds the zero-crossings by looking for a sign change. keyword arguments: y_axis -- A list containg the signal over which to find zero-crossings x_axis -- A x-axis whose values correspond to the 'y_axis' list and is used in the return to specify the postion of the zero-crossings. If omitted then the indice of the y_axis is used. (default: None) window -- half of the dimension of the smoothing window; (default: 24) return -- the x_axis value or the indice for each zero-crossing """ # smooth the curve length = len(y_axis) if x_axis == None: x_axis = list(range(length)) x_axis = np.asarray(x_axis) y_axis = smooth(y_axis, window) zero_crossings = np.where(np.diff(np.sign(y_axis)))[0] times = [x_axis[indice] for indice in zero_crossings] # check if zero-crossings are valid diff = np.diff(times) if diff.std() / diff.mean() > 0.1: raise ValueError("smoothing window too small, false zero-crossings found") return times PyXRD-0.8.4/pyxrd/calculations/phases.py000066400000000000000000000134731363064711000201660ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import numpy as np from scipy.interpolate import interp1d from .math_tools import mmult from .CSDS import calculate_distribution from .goniometer import get_lorentz_polarisation_factor from .components import get_factors def get_structure_factors(range_stl, G, comp_list): """ Calculates the structure factor and phase factor for: - range_stl: numpy array of 2*sin(θ) / λ values - G: the number of components (layer types) - comp_list: list of :class:`~pyxrd.calculations.data_objects.ComponentData` instances This function calls :meth:`~pyxrd.calculations.components.get_factors` for each component in `comp_list` and stores the returned structure factors and phase difference factors in a numpy array (of type complex) with shape (X, G) where X is expanded to fit the shape of `range_stl`. """ shape = range_stl.shape + (G,) SF = np.zeros(shape, dtype=np.complex_) PF = np.zeros(shape, dtype=np.complex_) for i, comp in enumerate(comp_list): SF[:, i], PF[:, i] = get_factors(range_stl, comp) # @UndefinedVariable return SF, PF def get_Q_matrices(Q, CSDS_max): Qn = np.zeros((CSDS_max + 1,) + Q.shape, dtype=complex) Qn[0, ...] = np.copy(Q) for n in range(1, CSDS_max + 1): Qn[n, ...] = mmult(Qn[n - 1, ...], Q) return Qn def get_absolute_scale(components, CSDS_real_mean, W): W = np.diag(W) mean_volume = 0.0 mean_d001 = 0.0 mean_density = 0.0 for i, comp in enumerate(components): if comp != None: mean_volume += comp.volume * W[i] mean_d001 += comp.d001 * W[i] mean_density += (comp.weight * W[i] / comp.volume) else: logger.debug("- calc: get_absolute_scale reports: 'Zero observations found!'") mean_mass = (CSDS_real_mean * mean_volume ** 2 * mean_density) if mean_mass != 0.0: return mean_d001 / mean_mass else: return 0.0 def get_diffracted_intensity(range_theta, range_stl, phase): """ Gets intensity for a single phase without taking the lorentz polarization factor into account. """ if phase.type == "AbtractPhase": raise NotImplementedError elif phase.type == "RawPatternPhase": return _get_raw_intensity(range_theta, range_stl, phase) else: return _get_diffracted_intensity(range_theta, range_stl, phase) def get_intensity(range_theta, range_stl, soller1, soller2, mcr_2theta, phase): """ Gets intensity for a single phase taking the lorentz polarization factor into account. """ intensity = get_diffracted_intensity(range_theta, range_stl, phase) if phase.apply_lpf: return intensity * get_lorentz_polarisation_factor( range_theta, phase.sigma_star, soller1, soller2, mcr_2theta ) else: return intensity def _get_raw_intensity(range_theta, range_stl, phase): assert phase.type == "RawPatternPhase", "Must be RawPatternPhase!" f = interp1d( phase.raw_pattern_x, phase.raw_pattern_y, bounds_error=False, fill_value=0 ) i = f(np.rad2deg(2 * range_theta)) return i def _get_diffracted_intensity(range_theta, range_stl, phase): assert phase.type == "Phase", "Must be Phase!" # Check probability model, if invalid return zeros instead of the actual pattern: if not phase.valid_probs: logger.debug("- calc: get_diffracted_intensity reports: 'Invalid probability found!'") return np.zeros_like(range_stl) else: # Calculate CSDS distribution CSDS_arr, CSDS_real_mean = calculate_distribution(phase.CSDS) # Get absolute scale abs_scale = get_absolute_scale(phase.components, CSDS_real_mean, phase.W) # Create a helper function to 'expand' certain arrays, for # results which are independent of the 2-theta range stl_dim = range_stl.shape[0] repeat_to_stl = lambda arr: np.repeat(arr[np.newaxis, ...], stl_dim, axis=0) # Repeat junction probabilities & weight fractions W = repeat_to_stl(phase.W).astype(np.complex_) P = repeat_to_stl(phase.P).astype(np.complex_) # Repeat & get SFa and SFb (transpose conjugate) structure factor matrices: SF, PF = get_structure_factors(range_stl, phase.G, phase.components) SFa = np.repeat(SF[..., np.newaxis, :], SF.shape[1], axis=1) SFb = np.transpose(np.conjugate(SFa), axes=(0, 2, 1)) # Calculate the repetition factor for R+ probabilities: rank = P.shape[1] reps = rank / phase.G # Calculate the structure factor matrix: F = np.repeat(np.repeat(np.multiply(SFb, SFa), reps, axis=2), reps, axis=1) # Create Q phase factor matrices: PF = np.repeat(PF[..., np.newaxis, :], PF.shape[1], axis=1) Q = np.multiply(np.repeat(np.repeat(PF, reps, axis=2), reps, axis=1), P) Qn = get_Q_matrices(Q, phase.CSDS.maximum) # Calculate the intensity: sub_total = np.zeros(Q.shape, dtype=np.complex) for n in range(phase.CSDS.minimum, phase.CSDS.maximum + 1): progression_factor = 0 for m in range(n + 1, phase.CSDS.maximum + 1): progression_factor += (m - n) * CSDS_arr[m] sub_total += 2 * progression_factor * Qn[n - 1, ...] CSDS_I = repeat_to_stl(np.identity(rank, dtype=np.complex) * CSDS_real_mean) sub_total = (CSDS_I + sub_total) sub_total = mmult(mmult(F, W), sub_total) intensity = np.real(np.trace(sub_total, axis1=2, axis2=1)) return intensity * abs_scale PyXRD-0.8.4/pyxrd/calculations/specimen.py000066400000000000000000000072131363064711000205010ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import numpy as np from math import radians, tan from scipy.interpolate.interpolate import interp1d from .goniometer import get_fixed_to_ads_correction_range from .phases import get_intensity def get_clipped_intensities(specimen): exp = specimen.observed_intensity.transpose()[...,specimen.selected_range] cal = specimen.total_intensity[...,specimen.selected_range] return exp, cal def get_machine_correction_range(specimen): """ Calculate a correction factor for a certain sample length, sample absorption and machine setup. """ goniometer = specimen.goniometer range_st = np.sin(specimen.range_theta) correction_range = np.ones_like(specimen.range_theta) # Correct for automatic divergence slits first: if goniometer.divergence_mode == "AUTOMATIC": correction_range *= get_fixed_to_ads_correction_range( specimen.range_theta, goniometer) # Then correct for sample absorption: if goniometer.has_absorption_correction > 0.0: absorption = goniometer.absorption * goniometer.sample_surf_density * 1e-3 if absorption > 0.0: correction_range *= np.minimum(1.0 - np.exp(-2.0 * absorption / range_st), 1.0) # And finally correct for sample length, if fixed slits were used: if goniometer.divergence_mode == "FIXED" and goniometer.divergence > 0: L_Rta = goniometer.sample_length / (goniometer.radius * tan(radians(goniometer.divergence))) correction_range *= np.minimum(range_st * L_Rta, 1) return correction_range def calculate_phase_intensities(specimen): """ Gets phase intensities for the provided phases Returns a 2-tuple containing 2-theta values and phase intensities. """ range_stl = 2 * np.sin(specimen.range_theta) / specimen.goniometer.wavelength correction_range = get_machine_correction_range(specimen) def get_phase_intensities(phases): for phase in phases: if phase != None: correction = correction_range if phase.apply_correction else 1.0 yield get_intensity( specimen.range_theta, range_stl, specimen.goniometer.soller1, specimen.goniometer.soller2, specimen.goniometer.mcr_2theta, phase ) * correction else: yield np.zeros_like(range_stl) def interpolate_wavelength(I, new_wavelength, fraction): new_theta = np.arcsin(range_stl * new_wavelength * 0.5) f = interp1d(new_theta, I * fraction, bounds_error=False, fill_value=0.0) return f(specimen.range_theta) def apply_wavelength_distribution(I): for new_wavelength, fraction in specimen.goniometer.wavelength_distribution: I += interpolate_wavelength(I, new_wavelength, fraction) return I return ( correction_range, np.swapaxes(np.array([ [ apply_wavelength_distribution(I) for I in get_phase_intensities(specimen.phases[z_index]) ] for z_index in range(len(specimen.z_list)) ], dtype=np.float_), 0, 1) # is of shape num_phases, num_patterns, num_observations ) def calculate_scaled_intensities(specimen, scale, fractions, bgshift): specimen.background_intensity = bgshift * specimen.correction specimen.scaled_phase_intensities = (fractions * specimen.phase_intensities.transpose()).transpose() * scale specimen.total_intensity = np.sum(specimen.scaled_phase_intensities, axis=0) + specimen.background_intensity return specimen PyXRD-0.8.4/pyxrd/calculations/statistics.py000066400000000000000000000060721363064711000210720ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from math import sqrt import numpy as np from .math_tools import smooth def R_squared(exp, calc, *args): """ Calculate the 'coefficient of determination' (aka R²) for the given experimental and calculated patterns (1D numpy arrays, y-values only) """ avg = sum(exp) / exp.size sserr = np.sum((exp - calc) ** 2) sstot = np.sum((exp - avg) ** 2) return 1 - (sserr / sstot) def Rp(exp, calc, *args): """ Calculate the 'pattern R factor' (aka Rp) for the given experimental and calculated patterns (1D numpy arrays, y-values only) """ return np.sum(np.abs(exp - calc)) / np.sum(np.abs(exp)) * 100 def smooth_pattern(pattern): """ Smooth the given pattern. """ return smooth(pattern, 15) def derive(pattern): """ Calculate the first derivative pattern pattern. Smoothes the input first, so noisy patterns shouldn't be much of a problem. """ return np.gradient(smooth_pattern(pattern)) def Rpder(exp, calc): """ Calculated the 'derived pattern R factor' (aka Rp') for the given experimental and calculated patterns. """ return Rp(derive(exp), derive(calc)) def Rpw(exp, calc): """ Calculates the 'weighted pattern R factor' (aka Rwp) for the given experimental and calculated patterns (1D numpy arrays, y-values only) The weights are set equal to the inverse of the observed intensities. """ # weighted Rp: # Rwp = Sqrt ( Sum[w * (obs - calc)²] / Sum[w * obs²] ) w = 1 / Iobs sm1 = 0 sm2 = 0 for i in range(exp.size): t = (exp[i] - calc[i]) ** 2 / exp[i] if not (np.isnan(t) or np.isinf(t)): sm1 += t sm2 += abs(exp[i]) try: return sqrt(sm1 / sm2) * 100 except: return 0 def Rpe(exp, calc, num_params): """ Calculate the 'expected pattern R factor' (Rpe) for the given experimental and calculated pattern and the number of refined parameters. """ # R expected: # Re = Sqrt( (Points - Params) / Sum[ w * obs² ] ) num_points = exp.size return np.sqrt((num_points - num_params) / np.sum(exp ** 2)) * 100 def Rphase(exp, calc, phase): """ Calculates the 'weighted phase R factor' (aka Rphase) for the given experimental and calculated patterns (1D numpy arrays, y-values only) The weights are set equal to the inverse of the observed intensities. """ # weighted Rp: # Rwp = Sqrt ( Sum[w * (obs - calc)²] / Sum[w * obs²] ) w = 1 / Iobs sm1 = 0 sm2 = 0 exp = exp.flatten() calc = calc.flatten() phase = phase.flatten() for i in range(exp.size): t = (exp[i] - calc[i]) ** 2 * phase[i] / (exp[i]**2) if not (np.isnan(t) or np.isinf(t)): sm1 += t sm2 += abs(phase[i]) try: return sqrt(sm1 / sm2) * 100 except: return 0 PyXRD-0.8.4/pyxrd/core.py000066400000000000000000000113171363064711000151450ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import warnings import os, sys import logging logger = logging.getLogger(__name__) from mvc.support.gui_loop import start_event_loop def _run_user_script(): """ Runs the user script specified in the command-line arguments. """ available, nv, cv = check_for_updates() if available: print("An update is available: current version is %s upstream version is %s - consider upgrading!" % cv, nv) from pyxrd.data import settings try: import imp user_script = imp.load_source('user_script', settings.ARGS.script) except BaseException as err: err.args = "Error when trying to import %s: %s" % (settings.ARGS.script, err.args) raise user_script.run(settings.ARGS) def _run_gui(project=None): # Display a splash screen showing the loading status... from pkg_resources import resource_filename # @UnresolvedImport from pyxrd.application.splash import SplashScreen from pyxrd import __version__ filename = resource_filename(__name__, "application/icons/pyxrd.png") splash = SplashScreen(filename, __version__) # Run GUI: splash.set_message("Checking for updates ...") update_available, nv, cv = check_for_updates() splash.set_message("Loading GUI ...") # Now we can load these: from pyxrd.data import settings from pyxrd.file_parsers.json_parser import JSONParser from pyxrd.application.models import AppModel from pyxrd.application.views import AppView from pyxrd.application.controllers import AppController # TODO move this to mvc: from pyxrd.generic.gtk_tools.gtkexcepthook import plugin_gtk_exception_hook filename = settings.ARGS.filename #@UndefinedVariable # Check if a filename was passed, if so try to load it if filename != "": try: logging.info("Opening project: %s" % filename) project = JSONParser.parse(filename) except IOError: logging.info("Could not load project file %s: IOError" % filename) # FIXME the user should be informed of this in a dialog... # Disable unity overlay scrollbars as they cause bugs with modal windows os.environ['LIBOVERLAY_SCROLLBAR'] = '0' os.environ['UBUNTU_MENUPROXY'] = "" if not settings.DEBUG: warnings.filterwarnings(action='ignore', category=Warning) # Close splash screen if splash: splash.close() # Nice GUI error handler: gtk_exception_hook = plugin_gtk_exception_hook() # setup MVC: m = AppModel(project=project) v = AppView() AppController(m, v, gtk_exception_hook=gtk_exception_hook) # Free this before continuing del splash if update_available: from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory DialogFactory.get_information_dialog( "An update is available (%s) - consider upgrading!" % nv, False, v.get_toplevel(), title = "Update available" ).run() else: print("PyXRD is up to date (current = %s)" % cv) # lets get this show on the road: start_event_loop() def check_for_updates(): """ Checks for updates and returns a tuple: update_available, latest_version, current_version """ from pyxrd.generic.outdated import check_outdated from pyxrd.__version import __version__ is_outdated, latest_version = check_outdated('pyxrd', __version__) return is_outdated, latest_version, __version__ def run_main(): """ Parsers command line arguments and launches PyXRD accordingly. """ # Make sure the current path is used for loading PyXRD modules: mod = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if not mod in sys.path: sys.path.insert(1, mod) # Init settings, first import will trigger initialization from pyxrd.data import settings settings.initialize() # Setup basic logging from pyxrd.logs import setup_logging setup_logging(basic=True) if settings.DEBUG: from pyxrd import stacktracer stacktracer.trace_start( "trace.html", interval=5, auto=True) # Set auto flag to always update file! try: if settings.ARGS.script: # Run the specified user script: _run_user_script() else: # Run the GUI: _run_gui() except: raise # re-raise the error finally: for finalizer in settings.FINALIZERS: finalizer() if settings.DEBUG: stacktracer.trace_stop() if __name__ == "__main__": run_main() PyXRD-0.8.4/pyxrd/data/000077500000000000000000000000001363064711000145515ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/data/__init__.py000066400000000000000000000000001363064711000166500ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/data/appdirs.py000066400000000000000000000503051363064711000165700ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c) 2005-2010 ActiveState Software Inc. # Copyright (c) 2013 Eddy Petrișor """Utilities for determining application-specific dirs. See for details and usage. """ # Dev Notes: # - MSDN on where to store app data files: # http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120 # - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html # - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html __version_info__ = (1, 3, 0) __version__ = '.'.join(map(str, __version_info__)) import sys import os PY3 = sys.version_info[0] == 3 if PY3: unicode = str def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): r"""Return full path to the user-specific data dir for this application. "appname" is the name of application. If None, just the system directory is returned. "appauthor" (only required and used on Windows) is the name of the appauthor or distributing body for this application. Typically it is the owning company name. This falls back to appname. "version" is an optional version path element to append to the path. You might want to use this if you want multiple versions of your app to be able to run independently. If used, this would typically be ".". Only applied when appname is present. "roaming" (boolean, default False) can be set True to use the Windows roaming appdata directory. That means that for users on a Windows network setup for roaming profiles, this user data will be sync'd on login. See for a discussion of issues. Typical user data directories are: Mac OS X: ~/Library/Application Support/ Unix: ~/.local/share/ # or in $XDG_DATA_HOME, if defined Win XP (not roaming): C:\Documents and Settings\\Application Data\\ Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\ Win 7 (not roaming): C:\Users\\AppData\Local\\ Win 7 (roaming): C:\Users\\AppData\Roaming\\ For Unix, we follow the XDG spec and support $XDG_DATA_HOME. That means, by deafult "~/.local/share/". On all platforms we support overriding all of this by setting the $PYXRD_USER_DATA_DIR environment variable to the value of choice. """ path = os.getenv("PYXRD_USER_DATA_DIR", None) if path is None: if sys.platform == "win32": if appauthor is None: appauthor = appname const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" path = os.path.normpath(_get_win_folder(const)) if appname: path = os.path.join(path, appauthor, appname) elif sys.platform == 'darwin': path = os.path.expanduser('~/Library/Application Support/') if appname: path = os.path.join(path, appname) else: path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share")) if appname: path = os.path.join(path, appname) if appname and version: path = os.path.join(path, version) return path def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): """Return full path to the user-shared data dir for this application. "appname" is the name of application. If None, just the system directory is returned. "appauthor" (only required and used on Windows) is the name of the appauthor or distributing body for this application. Typically it is the owning company name. This falls back to appname. "version" is an optional version path element to append to the path. You might want to use this if you want multiple versions of your app to be able to run independently. If used, this would typically be ".". Only applied when appname is present. "multipath" is an optional parameter only applicable to *nix which indicates that the entire list of data dirs should be returned. By default, the first item from XDG_DATA_DIRS is returned, or '/usr/local/share/', if XDG_DATA_DIRS is not set Typical user data directories are: Mac OS X: /Library/Application Support/ Unix: /usr/local/share/ or /usr/share/ Win XP: C:\Documents and Settings\All Users\Application Data\\ Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) Win 7: C:\ProgramData\\ # Hidden, but writeable on Win 7. For Unix, this is using the $XDG_DATA_DIRS[0] default. WARNING: Do not use this on Windows. See the Vista-Fail note above for why. """ if sys.platform == "win32": if appauthor is None: appauthor = appname path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) if appname: path = os.path.join(path, appauthor, appname) elif sys.platform == 'darwin': path = os.path.expanduser('/Library/Application Support') if appname: path = os.path.join(path, appname) else: # XDG default for $XDG_DATA_DIRS # only first, if multipath is False path = os.getenv('XDG_DATA_DIRS', os.pathsep.join(['/usr/local/share', '/usr/share'])) pathlist = [ os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) ] if appname: if version: appname = os.path.join(appname, version) pathlist = [ os.sep.join([x, appname]) for x in pathlist ] if multipath: path = os.pathsep.join(pathlist) else: path = pathlist[0] return path if appname and version: path = os.path.join(path, version) return path def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): r"""Return full path to the user-specific config dir for this application. "appname" is the name of application. If None, just the system directory is returned. "appauthor" (only required and used on Windows) is the name of the appauthor or distributing body for this application. Typically it is the owning company name. This falls back to appname. "version" is an optional version path element to append to the path. You might want to use this if you want multiple versions of your app to be able to run independently. If used, this would typically be ".". Only applied when appname is present. "roaming" (boolean, default False) can be set True to use the Windows roaming appdata directory. That means that for users on a Windows network setup for roaming profiles, this user data will be sync'd on login. See for a discussion of issues. Typical user data directories are: Mac OS X: same as user_data_dir Unix: ~/.config/ # or in $XDG_CONFIG_HOME, if defined Win *: same as user_data_dir For Unix, we follow the XDG spec and support $XDG_DATA_HOME. That means, by deafult "~/.local/share/". """ if sys.platform in [ "win32", "darwin" ]: path = user_data_dir(appname, appauthor, None, roaming) else: path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")) if appname: path = os.path.join(path, appname) if appname and version: path = os.path.join(path, version) return path def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): """Return full path to the user-shared data dir for this application. "appname" is the name of application. If None, just the system directory is returned. "appauthor" (only required and used on Windows) is the name of the appauthor or distributing body for this application. Typically it is the owning company name. This falls back to appname. "version" is an optional version path element to append to the path. You might want to use this if you want multiple versions of your app to be able to run independently. If used, this would typically be ".". Only applied when appname is present. "multipath" is an optional parameter only applicable to *nix which indicates that the entire list of config dirs should be returned. By default, the first item from XDG_CONFIG_DIRS is returned, or '/etc/xdg/', if XDG_CONFIG_DIRS is not set Typical user data directories are: Mac OS X: same as site_data_dir Unix: /etc/xdg/ or $XDG_CONFIG_DIRS[i]/ for each value in $XDG_CONFIG_DIRS Win *: same as site_data_dir Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False WARNING: Do not use this on Windows. See the Vista-Fail note above for why. """ if sys.platform in [ "win32", "darwin" ]: path = site_data_dir(appname, appauthor) if appname and version: path = os.path.join(path, version) else: # XDG default for $XDG_CONFIG_DIRS # only first, if multipath is False path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') pathlist = [ os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) ] if appname: if version: appname = os.path.join(appname, version) pathlist = [ os.sep.join([x, appname]) for x in pathlist ] if multipath: path = os.pathsep.join(pathlist) else: path = pathlist[0] return path def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): r"""Return full path to the user-specific cache dir for this application. "appname" is the name of application. If None, just the system directory is returned. "appauthor" (only required and used on Windows) is the name of the appauthor or distributing body for this application. Typically it is the owning company name. This falls back to appname. "version" is an optional version path element to append to the path. You might want to use this if you want multiple versions of your app to be able to run independently. If used, this would typically be ".". Only applied when appname is present. "opinion" (boolean) can be False to disable the appending of "Cache" to the base app data dir for Windows. See discussion below. Typical user cache directories are: Mac OS X: ~/Library/Caches/ Unix: ~/.cache/ (XDG default) Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Cache Vista: C:\Users\\AppData\Local\\\Cache On Windows the only suggestion in the MSDN docs is that local settings go in the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming app data dir (the default returned by `user_data_dir` above). Apps typically put cache data somewhere *under* the given dir here. Some examples: ...\Mozilla\Firefox\Profiles\\Cache ...\Acme\SuperApp\Cache\1.0 OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. This can be disabled with the `opinion=False` option. On all platforms we support overriding all of this by setting the $PYXRD_USER_CACHE_DIR environment variable to the value of choice. """ path = os.getenv("PYXRD_USER_CACHE_DIR", None) if path is None: if sys.platform == "win32": if appauthor is None: appauthor = appname path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) if appname: path = os.path.join(path, appauthor, appname) if opinion: path = os.path.join(path, "Cache") elif sys.platform == 'darwin': path = os.path.expanduser('~/Library/Caches') if appname: path = os.path.join(path, appname) else: path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')) if appname: path = os.path.join(path, appname) if appname and version: path = os.path.join(path, version) return path def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): r"""Return full path to the user-specific log dir for this application. "appname" is the name of application. If None, just the system directory is returned. "appauthor" (only required and used on Windows) is the name of the appauthor or distributing body for this application. Typically it is the owning company name. This falls back to appname. "version" is an optional version path element to append to the path. You might want to use this if you want multiple versions of your app to be able to run independently. If used, this would typically be ".". Only applied when appname is present. "opinion" (boolean) can be False to disable the appending of "Logs" to the base app data dir for Windows, and "log" to the base cache dir for Unix. See discussion below. Typical user cache directories are: Mac OS X: ~/Library/Logs/ Unix: ~/.cache//log # or under $XDG_CACHE_HOME if defined Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Logs Vista: C:\Users\\AppData\Local\\\Logs On Windows the only suggestion in the MSDN docs is that local settings go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in examples of what some windows apps use for a logs dir.) OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` value for Windows and appends "log" to the user cache dir for Unix. This can be disabled with the `opinion=False` option. """ if sys.platform == "darwin": path = os.path.join( os.path.expanduser('~/Library/Logs'), appname) elif sys.platform == "win32": path = user_data_dir(appname, appauthor, version); version = False if opinion: path = os.path.join(path, "Logs") else: path = user_cache_dir(appname, appauthor, version); version = False if opinion: path = os.path.join(path, "log") if appname and version: path = os.path.join(path, version) return path class AppDirs(object): """Convenience wrapper for getting application dirs.""" def __init__(self, appname, appauthor=None, version=None, roaming=False, multipath=False): self.appname = appname self.appauthor = appauthor self.version = version self.roaming = roaming self.multipath = multipath @property def user_data_dir(self): return user_data_dir(self.appname, self.appauthor, version=self.version, roaming=self.roaming) @property def site_data_dir(self): return site_data_dir(self.appname, self.appauthor, version=self.version, multipath=self.multipath) @property def user_config_dir(self): return user_config_dir(self.appname, self.appauthor, version=self.version, roaming=self.roaming) @property def site_config_dir(self): return site_data_dir(self.appname, self.appauthor, version=self.version, multipath=self.multipath) @property def user_cache_dir(self): return user_cache_dir(self.appname, self.appauthor, version=self.version) @property def user_log_dir(self): return user_log_dir(self.appname, self.appauthor, version=self.version) #---- internal support stuff def _get_win_folder_from_registry(csidl_name): """This is a fallback technique at best. I'm not sure if using the registry for this guarantees us the correct answer for all CSIDL_* names. """ import winreg shell_folder_name = { "CSIDL_APPDATA": "AppData", "CSIDL_COMMON_APPDATA": "Common AppData", "CSIDL_LOCAL_APPDATA": "Local AppData", }[csidl_name] key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, # @UndefinedVariable r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") dir, _ = winreg.QueryValueEx(key, shell_folder_name) # @UndefinedVariable return dir def _get_win_folder_with_pywin32(csidl_name): from win32com.shell import shellcon, shell # @UnresolvedImport dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) # Try to make this a unicode path because SHGetFolderPath does # not return unicode strings when there is unicode data in the # path. try: dir = unicode(dir) # Downgrade to short path name if have highbit chars. See # . has_high_char = False for c in dir: if ord(c) > 255: has_high_char = True break if has_high_char: try: import win32api dir = win32api.GetShortPathName(dir) except ImportError: pass except UnicodeError: pass return dir def _get_win_folder_with_ctypes(csidl_name): import ctypes csidl_const = { "CSIDL_APPDATA": 26, "CSIDL_COMMON_APPDATA": 35, "CSIDL_LOCAL_APPDATA": 28, }[csidl_name] buf = ctypes.create_unicode_buffer(1024) ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) # Downgrade to short path name if have highbit chars. See # . has_high_char = False for c in buf: if ord(c) > 255: has_high_char = True break if has_high_char: buf2 = ctypes.create_unicode_buffer(1024) if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): # @UndefinedVariable buf = buf2 return buf.value if sys.platform == "win32": try: import win32com.shell # @UnusedImport _get_win_folder = _get_win_folder_with_pywin32 except ImportError: try: import ctypes # @UnusedImport _get_win_folder = _get_win_folder_with_ctypes except ImportError: _get_win_folder = _get_win_folder_from_registry #---- self test code if __name__ == "__main__": appname = "MyApp" appauthor = "MyCompany" props = ("user_data_dir", "site_data_dir", "user_config_dir", "site_config_dir", "user_cache_dir", "user_log_dir") print("-- app dirs (with optional 'version')") dirs = AppDirs(appname, appauthor, version="1.0") for prop in props: print(("%s: %s" % (prop, getattr(dirs, prop)))) print("\n-- app dirs (without optional 'version')") dirs = AppDirs(appname, appauthor) for prop in props: print(("%s: %s" % (prop, getattr(dirs, prop)))) print("\n-- app dirs (without optional 'appauthor')") dirs = AppDirs(appname) for prop in props: print(("%s: %s" % (prop, getattr(dirs, prop)))) PyXRD-0.8.4/pyxrd/data/atomic scattering factors.atl000066400000000000000000000651441363064711000223070ustar00rootroot00000000000000atom_nr,name,charge,weight,debye,par_c,par_a1,par_a2,par_a3,par_a4,par_a5,par_b1,par_b2,par_b3,par_b4,par_b5,#D. Waasmaier & A. Kirfel. 1,H,0,1.00794,0,0.000049,0.413048,0.294953,0.187491,0.080701,0.023736,15.569946,32.398468,5.711404,61.889874,1.334118 1,H1-,-1,1.00794,2,0.000425,0.70226,0.763666,0.248678,0.261323,0.023017,23.945604,74.897919,6.773289,233.58345,1.337531 2,He,0,4.002602,0,0.000487,0.732354,0.753896,0.283819,0.190003,0.039139,11.553918,4.595831,1.546299,26.463964,0.377523 3,Li,0,6.941,0,0.002542,0.974637,0.158472,0.811855,0.262416,0.790108,4.334946,0.342451,97.102966,201.363831,1.409234 3,Li1+,1,6.941,1.5,0.001764,0.432724,0.549257,0.376575,-0.336481,0.97606,0.260367,1.042836,7.885294,0.260368,3.042539 4,Be,0,9.012182,0,0.002511,1.533712,0.638283,0.601052,0.106139,1.118414,42.662079,0.59542,99.106499,0.15134,1.843093 4,Be2+,2,9.012182,1.5,-0.653773,3.05543,-2.372617,1.044914,0.544233,0.381737,0.001226,0.001227,1.542106,0.456279,4.047479 5,B,0,10.811,0,0.003823,2.085185,1.06458,1.062788,0.140515,0.641784,23.494068,1.137894,61.238976,0.114886,0.399036 6,C,0,12.0107,0,4.297983,2.657506,1.078079,1.490909,-4.24107,0.713791,14.780758,0.776775,42.086842,-0.000294,0.239535 6,Cval,2,12.0107,0,0.019722,1.258489,0.728215,1.119856,2.168133,0.705239,10.683769,0.208177,0.836097,24.603704,58.954273 7,N,0,14.0067,0,-11.804902,11.89378,3.277479,1.858092,0.858927,0.912985,0.000158,10.232723,30.34469,0.656065,0.217287 8,O,0,15.9994,0,0.027014,2.960427,2.508818,0.637853,0.722838,1.142756,14.182259,5.936858,0.112726,34.958481,0.39024 8,O1-,-1,15.9994,2,0.046136,3.106934,3.235142,1.148886,0.783981,0.676953,19.86808,6.960252,0.170043,65.693512,0.630757 8,O2-,-2,15.9994,2,0.025429,3.990247,2.300563,0.6072,1.907882,1.16708,16.639956,5.636819,0.108493,47.299709,0.379984 9,F,0,18.9984032,0,0.032557,3.511943,2.772244,0.678385,0.915159,1.089261,10.687859,4.380466,0.093982,27.255203,0.313066 9,F1-,-1,18.9984032,2,0.069525,0.457649,3.841561,1.432771,0.801876,3.395041,0.917243,5.507803,0.164955,51.076206,15.821679 10,Ne,0,20.1797,0,0.025576,4.183749,2.905726,0.520513,1.135641,1.228065,8.175457,3.252536,0.063295,21.81391,0.224952 11,Na,0,22.98976928,0,0.079712,4.910127,3.081783,1.262067,1.098938,0.560991,3.281434,9.119178,0.102763,132.013947,0.405878 11,Na1+,1,22.98976928,1.5,0.0453,3.14869,4.073989,0.767888,0.995612,0.968249,2.594987,6.046925,0.070139,14.122657,0.217037 12,Mg,0,24.305,0,0.126842,4.708971,1.194814,1.558157,1.170413,3.239403,4.875207,108.506081,0.111516,48.292408,1.928171 12,Mg2+,2,24.305,1.5,0.058851,3.062918,4.135106,0.853742,1.036792,0.85252,2.015803,4.417941,0.065307,9.66971,0.187818 13,Al,0,26.9815386,0,0.139509,4.730796,2.313951,1.54198,1.117564,3.154754,3.628931,43.051167,0.09596,108.932388,1.555918 13,Al3+,3,26.9815386,1.5,0.019397,4.132015,0.912049,1.102425,0.614876,3.219136,3.528641,7.378344,0.133708,0.039065,1.644728 14,Si,0,28.0855,0,0.145073,5.275329,3.191038,1.511514,1.356849,2.519114,2.631338,33.730728,0.081119,86.288643,1.170087 14,Siva,2,28.0855,0,0.14603,2.879033,3.07296,1.515981,1.39003,4.995051,1.239713,38.706276,0.081481,93.616333,2.770293 14,Si4+,4,28.0855,1.5,0.097266,3.676722,3.828496,1.258033,0.419024,0.720421,1.446851,3.013144,0.064397,0.206254,5.970222 15,P,0,30.973762,0,0.155233,1.950541,4.14693,1.49456,1.522042,5.729711,0.908139,27.044952,0.07128,67.520187,1.981173 16,S,0,32.065,0,0.154722,6.372157,5.154568,1.473732,1.635073,1.209372,1.514347,22.092527,0.061373,55.445175,0.646925 17,Cl,0,35.453,0,0.146773,1.446071,6.870609,6.151801,1.750347,0.634168,0.052357,1.193165,18.343416,46.398396,0.401005 17,Cl1-,-1,35.453,2,-34.916603,1.061802,7.139886,6.524271,2.355626,35.829403,0.144727,1.171795,19.467655,60.320301,0.000436 18,Ar,0,39.948,0,0.265954,7.188004,6.638454,0.45418,1.929593,1.523654,0.956221,15.339877,15.339862,39.043823,0.062409 19,K,0,39.0983,0,0.253614,8.163991,7.146945,1.07014,0.877316,1.486434,12.816323,0.808945,210.327011,39.597652,0.052821 19,K1+,1,39.0983,2,0.257164,-17.609339,1.494873,7.150305,10.899569,15.808228,18.840979,0.053453,0.81294,22.264105,14.351593 20,Ca,0,40.078,0,0.196255,8.593655,1.477324,1.436254,1.182839,7.113258,10.460644,0.041891,81.390381,169.847839,0.688098 20,Ca2+,2,40.078,2,-21.013187,8.501441,12.880483,9.765095,7.156669,0.71116,10.525848,-0.004033,0.010692,0.684443,27.231771 21,Sc,0,44.955912,0,0.157765,1.476566,1.487278,1.600187,9.177463,7.09975,53.131023,0.035325,137.319489,9.098031,0.602102 21,Sc3+,3,44.955912,2,0.118642,7.104348,1.511488,-53.669773,38.404816,24.53224,0.601957,0.033386,12.572138,10.859736,14.12523 22,Ti,0,47.867,0,0.102473,9.818524,1.522646,1.703101,1.768774,7.082555,8.001879,0.029763,39.885422,120.157997,0.532405 22,Ti2+,2,47.867,1.5,0.150362,7.040119,1.496285,9.657304,0.006534,1.649561,0.537072,0.031914,8.009958,201.800293,24.039482 22,Ti3+,3,47.867,1.5,-35.111282,36.587933,7.230255,-9.086077,2.084594,17.294008,0.000681,0.522262,5.262317,15.881716,6.149805 22,Ti4+,4,47.867,1.5,-0.110628,45.355537,7.0929,7.483858,-43.498817,1.678915,9.252186,0.523046,13.082852,10.193876,0.023064 23,V,0,50.9415,0,0.067744,10.473575,1.547881,1.986381,1.865616,7.05625,7.08194,0.02604,31.909672,108.022842,0.474882 23,V2+,2,50.9415,1.5,-0.533379,7.754356,2.0641,2.576998,2.011404,7.126177,7.066315,0.014993,7.066308,22.055786,0.467568 23,V3+,3,50.9415,1.5,0.474921,9.95848,1.59635,1.483442,-10.846044,17.332867,6.763041,0.056895,17.750029,0.328826,0.388013 23,V5+,5,50.9415,1.5,0.552676,15.575018,8.448095,1.61204,-9.721855,1.534029,0.682708,5.56664,10.527077,0.907961,0.066667 24,Cr,0,51.9961,0,0.06551,11.007069,1.555477,2.985293,1.347855,7.034779,6.366281,0.023987,23.244839,105.774498,0.429369 24,Cr2+,2,51.9961,1.5,0.04987,10.598877,1.565858,2.72828,0.098064,6.959321,6.151846,0.023519,17.432816,54.002388,0.426301 24,Cr3+,3,51.9961,1.5,-0.192123,7.98931,1.765079,2.627125,1.82938,6.980908,6.068867,0.018342,6.068887,16.309284,0.420864 25,Mn,0,54.938045,0,-0.147293,11.709542,1.733414,2.673141,2.023368,7.00318,5.59712,0.0178,21.78842,89.517914,0.383054 25,Mn2+,2,54.938045,1.5,-24.566132,11.287712,26.042414,3.058096,0.090258,7.088306,5.506225,0.000774,16.158575,54.766354,0.37558 25,Mn3+,3,54.938045,1.5,-0.093713,6.926972,2.081342,11.128379,2.375107,-0.419287,0.378315,0.015054,5.379957,14.429586,0.004939 25,Mn4+,4,54.938045,1.5,0.672146,12.409131,7.466993,1.809947,-12.138477,10.780248,0.3004,0.112814,12.520756,0.168653,5.173237 26,Fe,0,55.845,0,-0.304931,12.311098,1.876623,3.066177,2.070451,6.975185,5.009415,0.014461,18.74304,82.767876,0.346506 26,Fe2+,2,55.845,1.5,-9.676919,11.776765,11.165097,3.533495,0.165345,7.036932,4.912232,0.001748,14.166556,42.381958,0.341324 26,Fe3+,3,55.845,1.5,-61.930725,9.721638,63.403847,2.141347,2.629274,7.033846,4.869297,0.000293,4.867602,13.539076,0.33852 27,Co,0,58.933195,0,-0.936572,12.91451,2.481908,3.466894,2.106351,6.960892,4.507138,0.009126,16.438129,76.98732,0.314418 27,Co2+,2,58.933195,1.5,-24.796852,6.99384,26.285812,12.254289,0.246114,4.017407,0.310779,0.000684,4.400528,35.741447,12.536393 27,Co3+,3,58.933195,1.5,-1.147345,6.861739,2.67857,12.281889,3.501741,-0.179384,0.309794,0.008142,4.331703,11.914167,11.914167 28,Ni,0,58.6934,0,-2.762697,13.521865,6.947285,3.866028,2.1359,4.284731,4.077277,0.286763,14.622634,71.96608,0.004437 28,Ni2+,2,58.6934,1.5,-36.344471,12.519017,37.832058,4.387257,0.661552,6.949072,3.933053,0.000442,10.449184,23.860998,0.283723 28,Ni3+,3,58.6934,1.5,-0.317618,13.579366,1.902844,12.859268,3.811005,-6.838595,0.31314,0.012621,3.906407,10.894311,0.344379 29,Cu,0,63.546,0,-3.254477,14.014192,4.784577,5.056806,1.457971,6.932996,3.73828,0.003744,13.034982,72.554794,0.265666 29,Cu1+,1,63.546,1.5,-14.84932,12.960763,16.34215,1.110102,5.520682,6.915452,3.57601,0.000975,29.523218,10.114283,0.261326 29,Cu2+,2,63.546,1.5,-14.878383,11.895569,16.344978,5.799817,1.048804,6.789088,3.378519,0.000924,8.133653,20.526524,0.254741 30,Zn,0,65.38,0,-36.915829,14.741002,6.907748,4.642337,2.191766,38.424042,3.388232,0.243315,11.903689,63.31213,0.000397 30,Zn2+,2,65.38,1.5,-8.945248,13.340772,10.428857,5.544489,0.762295,6.869172,3.215913,0.001413,8.54268,21.891756,0.239215 31,Ga,0,69.723,0,-0.847395,15.758946,6.841123,4.121016,2.714681,2.395246,3.121754,0.226057,12.482196,66.203621,0.007238 31,Ga3+,3,69.723,1.5,-33.875122,13.123875,35.288189,6.126979,0.611551,6.724807,2.80996,0.000323,6.831534,16.784311,0.212002 32,Ge,0,72.64,0,0.018726,16.540613,1.5679,3.727829,3.345098,6.785079,2.866618,0.012198,13.432163,58.866047,0.210974 32,Ge4+,4,72.64,1.5,1.086542,6.876636,6.779091,9.969591,3.135857,0.152389,2.025174,0.17665,3.573822,7.685848,16.677574 33,As,0,74.9216,0,-2.984117,17.025642,4.503441,3.715904,3.9372,6.790175,2.597739,0.003012,14.272119,50.437996,0.193015 34,Se,0,78.96,0,-3.160982,17.354071,4.653248,4.259489,4.136455,6.749163,2.349787,0.00255,15.57946,45.181202,0.177432 35,Br,0,79.904,0,-2.492088,17.55057,5.411882,3.93718,3.880645,6.707793,2.119226,16.557184,0.002481,42.164009,0.162121 35,Br1-,-1,79.904,2,1.152674,17.71431,6.466926,6.947385,4.402674,-0.697279,2.122554,19.050768,0.152708,58.690361,58.690372 36,Kr,0,83.798,0,-2.810592,17.655279,6.848105,4.171004,3.44676,6.6852,1.908231,16.606236,0.001598,39.917473,0.146896 37,Rb,0,85.4678,0,1.139548,8.123134,2.138042,6.761702,1.156051,17.679546,15.142385,33.542667,0.129372,224.132507,1.713368 37,Rb1+,1,85.4678,1.5,1.133263,17.68432,7.761588,6.680874,2.668883,0.070974,1.710209,14.919863,0.128542,31.654478,0.128543 38,Sr,0,87.62,0,1.140251,17.730219,9.795867,6.099763,2.620025,0.600053,1.56306,14.310868,0.120574,135.771317,0.120574 38,Sr2+,2,87.62,1.5,1.125309,17.694973,1.275762,6.154252,9.234786,0.515995,1.550888,30.133041,0.118774,13.821799,0.118774 39,Y,0,88.90585,0,1.131787,17.79204,10.253252,5.714949,3.170516,0.918251,1.429691,13.132816,0.112173,108.197029,0.112173 40,Zr,0,91.224,0,1.124859,17.859772,10.911038,5.821115,3.512513,0.746965,1.310692,12.319285,0.104353,91.777542,0.104353 40,Zr4+,4,91.224,1.5,0.827902,6.802956,17.699253,10.650647,-0.248108,0.250338,0.096228,1.296127,11.240715,-0.219259,-0.219021 41,Nb,0,92.90638,0,1.123452,17.958399,12.063054,5.007015,3.287667,1.531019,1.21159,12.246687,0.098615,75.011948,0.098615 41,Nb3+,3,92.90638,1.5,-8.339573,17.714323,1.675213,7.483963,8.322464,11.143573,1.172419,30.102791,0.080255,-0.002983,10.456687 41,Nb5+,5,92.90638,1.5,-68.02478,17.580206,7.633277,10.793497,0.180884,67.837921,1.165852,0.078558,9.507652,31.621656,-0.000438 42,Mo,0,95.96,0,1.10877,6.236218,17.987711,12.973127,3.451426,0.210899,0.09078,1.10831,11.46872,66.684151,0.09078 42,Mo3+,3,95.96,1.5,-1.898764,7.44705,17.778122,11.886068,1.997905,1.789626,0.072,1.073145,9.83472,28.221746,-0.011674 42,Mo5+,5,95.96,1.5,-78.056595,7.929879,17.667669,11.515987,0.500402,77.444084,0.068856,1.068064,9.046229,26.558945,-0.000473 42,Mo6+,6,95.96,1.5,1.141916,34.757683,9.653037,6.584769,-18.628115,2.490594,1.30177,7.123843,0.094097,1.617443,12.335434 43,Tc,0,95.96,0,1.074784,17.840963,3.428236,1.373012,12.947364,6.335469,1.005729,41.901382,119.320541,9.781542,0.083391 44,Ru,0,101.07,0,1.043992,6.271624,17.906738,14.123269,3.746008,0.908235,0.07704,0.928222,9.555345,35.86068,123.552246 44,Ru3+,3,101.07,1.5,-51.905243,17.894758,13.579529,10.729251,2.474095,48.227997,0.902827,8.740579,0.045125,24.764954,-0.001699 44,Ru4+,4,101.07,1.5,-17.241762,17.845776,13.455084,10.229087,1.653524,14.059795,0.90107,8.482392,0.045972,23.015272,-0.004889 45,Rh,0,102.9055,0,0.995452,6.216648,17.919739,3.854252,0.840326,15.173498,0.070789,0.856121,33.889484,121.686691,9.029517 45,Rh3+,3,102.9055,1.5,0.960843,17.758621,14.569813,5.29832,2.533579,0.879753,0.841779,8.319533,0.06905,23.709131,0.06905 45,Rh4+,4,102.9055,1.5,0.959941,17.716188,14.446654,5.185801,1.703448,0.989992,0.840572,8.100647,0.068995,22.357307,0.068995 46,Pd,0,106.42,0,0.883099,6.121511,4.784063,16.631683,4.318258,13.246773,0.062549,0.784031,8.751391,34.489983,0.784031 46,Pd2+,2,106.42,1.5,0.879336,6.122282,15.651012,3.513508,9.06079,8.771199,0.062424,8.018296,24.784275,0.776457,0.776457 46,Pd4+,4,106.42,1.5,0.915874,6.152421,-96.069023,31.622141,81.578255,17.801403,0.063951,11.090354,13.466152,9.758302,0.783014 47,Ag,0,107.8682,0,0.756603,6.073874,17.155437,4.173344,0.852238,17.988686,0.055333,7.896512,28.443739,110.376106,0.716809 47,Ag1+,1,107.8682,1.5,0.785127,6.091192,4.019526,16.948174,4.258638,13.889437,0.056305,0.71934,7.758938,27.368349,0.71934 47,Ag2+,2,107.8682,1.5,1.068247,6.401808,48.699802,4.799859,-32.332523,16.35671,0.068167,0.94227,20.639496,1.100365,6.883131 48,Cd,0,112.411,0,0.603504,6.080986,18.019468,4.018197,1.30351,17.974669,0.04899,7.273646,29.119284,95.831207,0.661231 48,Cd2+,2,112.411,1.5,0.664795,6.093711,43.909691,17.041306,-39.675117,17.958918,0.050624,8.654143,15.621396,11.082067,0.667591 49,In,0,114.818,0,0.333097,6.196477,18.816183,4.050479,1.638929,17.962912,0.042072,6.695665,31.00979,103.284348,0.610714 49,In3+,3,114.818,1.5,0.293677,6.206277,18.497746,3.078131,10.524613,7.401234,0.041357,6.605563,18.79225,0.608082,0.608082 50,Sn,0,118.71,0,0.119024,19.325171,6.281571,4.498866,1.856934,17.917318,6.118104,0.036915,32.529045,95.037186,0.565651 50,Sn2+,2,118.71,1.5,-0.042519,6.353672,4.770377,14.672025,4.235959,18.002131,0.03472,6.167891,6.167879,29.006456,0.561774 50,Sn4+,4,118.71,1.5,-0.172219,15.445732,6.420892,4.56298,1.713385,18.033537,6.280898,0.033144,6.280899,17.983601,0.55798 51,Sb,0,121.76,0,-0.290506,5.394956,6.54957,19.650681,1.82782,17.867832,33.326523,0.030974,5.564929,87.130966,0.523992 51,Sb3+,3,121.76,1.5,1.516108,10.189171,57.461918,19.356573,4.862206,-45.394096,0.089485,0.375256,5.357987,22.153736,0.297768 51,Sb5+,5,121.76,1.5,-0.445371,17.920622,6.647932,12.724075,1.555545,7.600591,0.522315,0.029487,5.71821,16.433775,5.718204 52,Te,0,127.6,0,-0.806668,6.660302,6.940756,19.847015,1.557175,17.802427,33.031654,0.02575,5.065547,84.101616,0.48766 53,I,0,126.90447,0,-0.448811,19.884502,6.736593,8.110516,1.170953,17.548716,4.628591,0.027754,31.849096,84.406387,0.46355 53,I1-,-1,126.90447,2,-3.341004,20.01033,17.835524,8.10413,2.231118,9.158548,4.565931,0.444266,32.430672,95.14904,0.014906 54,Xe,0,131.293,0,-6.065902,19.97892,11.774945,9.332182,1.244749,17.737501,4.143356,0.010142,28.7962,75.280685,0.413616 55,Cs,0,132.9054519,0,-2.322802,17.418674,8.314444,10.323193,1.383834,19.876251,0.399828,0.016872,25.605827,233.339676,3.826915 55,Cs1+,1,132.9054519,1.5,-19.394306,19.939056,24.967621,10.375884,0.454243,17.660248,3.770511,0.00404,25.311275,76.537766,0.38473 56,Ba,0,137.327,0,-5.183497,19.747343,17.368477,10.465718,2.592602,11.003653,3.481823,0.371224,21.226641,173.834274,0.010719 56,Ba2+,2,137.327,1.5,-59.618172,19.7502,17.513683,10.884892,0.321585,65.149834,3.430748,0.36159,21.358307,70.309402,0.001418 57,La,0,138.90547,0,-21.745489,19.966019,27.329655,11.018425,3.086696,17.335455,3.197408,0.003446,19.955492,141.381973,0.341817 57,La3+,3,138.90547,1.5,-76.846909,19.688887,17.345703,11.356296,0.099418,82.358124,3.146211,0.339586,18.753832,90.345459,0.001072 58,Ce,0,140.116,0,-38.386017,17.355122,43.988499,20.54665,3.13067,11.353665,0.328369,0.002047,3.088196,134.907654,18.83296 58,Ce3+,3,140.116,1.5,-80.313423,26.593231,85.866432,-6.677695,12.111847,17.401903,3.280381,0.001012,4.313575,17.868504,0.326962 58,Ce4+,4,140.116,1.5,-3.515096,17.457533,25.659941,11.691037,19.695251,-16.994749,0.311812,-0.003793,16.568687,2.886395,-0.008931 59,Pr,0,140.90765,0,-3.871068,21.551311,17.16173,11.903859,2.679103,9.564197,2.995675,0.312491,17.716705,152.192825,0.010468 59,Pr3+,3,140.90765,1.5,-30.500784,20.879841,36.035797,12.135341,0.283103,17.167803,2.870897,0.002364,16.615236,53.909359,0.306993 59,Pr4+,4,140.90765,1.5,-9.016722,17.496082,21.538509,20.403114,12.062211,-7.492043,0.294457,-0.002742,2.772886,15.804613,-0.013556 60,Nd,0,144.242,0,-57.189842,17.331244,62.783924,12.160097,2.663483,22.23995,0.300269,0.00132,17.026001,148.748993,2.910268 60,Nd3+,3,144.242,1.5,-50.541992,17.120077,56.038139,21.468307,10.000671,2.905866,0.291295,0.001421,2.743681,14.581367,22.485098 61,Pm,0,144.242,0,-45.973682,17.286388,51.560162,12.478557,2.675515,22.960947,0.28662,0.00155,16.223755,143.984512,2.79648 61,Pm3+,3,144.242,1.5,-46.767181,22.221066,17.068142,12.805423,0.435687,52.23877,2.635767,0.277039,14.927315,45.768017,0.001455 62,Sm,0,150.36,0,-17.452166,23.700363,23.072214,12.777782,2.684217,17.204367,2.689539,0.003491,15.495437,139.862473,0.274536 62,Sm3+,3,150.36,1.5,-9.714854,15.618565,19.538092,13.398946,-4.358811,24.490461,0.006001,0.306379,14.979594,0.748825,2.454492 63,Eu,0,151.964,0,-31.586687,17.186195,37.156837,13.103387,2.707246,24.419271,0.261678,0.001995,14.78736,134.816299,2.581883 63,Eu2+,2,151.964,1.5,-26.204315,23.899035,31.657497,12.955752,1.700576,16.992199,2.467332,0.00223,13.625002,35.089481,0.253136 63,Eu3+,3,151.964,1.5,-19.768026,17.758327,33.498665,24.067188,13.436883,-9.019134,0.244474,-0.003901,2.487526,14.568011,-0.015628 64,Gd,0,157.25,0,-43.505684,24.898117,17.104952,13.222581,3.266152,48.995213,2.435028,0.246961,13.996325,110.863091,0.001383 64,Gd3+,3,157.25,1.5,-88.147179,24.344999,16.945311,13.866931,0.481674,93.506378,2.333971,0.239215,12.982995,43.876347,0.000673 65,Tb,0,158.92535,0,-26.851971,25.910013,32.344139,13.765117,2.751404,17.064405,2.373912,0.002034,13.481969,125.83651,0.236916 65,Tb3+,3,158.92535,1.5,-33.950317,24.878252,16.856016,13.663937,1.279671,39.271294,2.223301,0.22729,11.812528,29.910065,0.001527 66,Dy,0,162.5,0,-83.279831,26.671785,88.687576,14.065445,2.768497,17.067781,2.282593,0.000665,12.92023,121.937187,0.225531 66,Dy3+,3,162.5,1.5,-85.15065,16.864344,90.383461,13.675473,1.687078,25.540651,0.216275,0.000593,11.121207,26.250975,2.13593 67,Ho,0,164.93032,0,-41.165253,27.15019,16.999819,14.059334,3.386979,46.546471,2.16966,0.215414,12.213148,100.506783,0.001211 67,Ho3+,3,164.93032,1.5,-58.026505,16.837524,63.221336,13.703766,2.061602,26.202621,0.206873,0.000796,10.500283,24.031883,2.05506 68,Er,0,167.259,0,-77.135223,28.174887,82.493271,14.624002,2.802756,17.018515,2.120995,0.00064,11.915256,114.529938,0.207519 68,Er3+,3,167.259,1.5,-17.51346,16.810127,22.681061,13.864114,2.294506,26.864477,0.198293,0.002126,9.973341,22.836388,1.979442 69,Tm,0,168.93421,0,-70.839813,28.925894,76.173798,14.904704,2.814812,16.998117,2.046203,0.000656,11.465375,111.41198,0.199376 69,Tm3+,3,168.93421,1.5,-10.192087,16.7875,15.350905,14.182357,2.299111,27.573771,0.190852,0.003036,9.602934,22.52688,1.912862 70,Yb,0,173.054,0,-60.313812,29.67676,65.624069,15.160854,2.830288,16.99785,1.97763,0.00072,11.044622,108.139153,0.19211 70,Yb2+,2,173.054,1.5,-23.214935,28.443794,16.849527,14.165081,3.445311,28.308853,1.863896,0.183811,9.225469,23.691355,0.001463 70,Yb3+,3,173.054,1.5,-18.103676,28.191629,16.828087,14.167848,2.744962,23.171774,1.842889,0.182788,9.045957,20.799847,0.001759 71,Lu,0,174.9668,0,-51.049416,30.122866,15.099346,56.314899,3.54098,16.943729,1.88309,10.342764,0.00078,89.55925,0.183849 71,Lu3+,3,174.9668,1.5,-20.626528,28.828693,16.823227,14.247617,3.079559,25.647667,1.776641,0.17556,8.575531,19.693701,0.001453 72,Hf,0,178.49,0,-49.719837,30.617033,15.145351,54.933548,4.096253,16.896156,1.795613,9.934469,0.000739,76.189705,0.175914 72,Hf4+,4,178.49,1.5,-18.820383,29.267378,16.792543,14.78531,2.184128,23.791996,1.697911,0.168313,8.190025,18.277578,0.001431 73,Ta,0,180.94788,0,-44.119026,31.066359,15.341823,49.278297,4.577665,16.828321,1.708732,9.618455,0.00076,66.346199,0.168002 73,Ta5+,5,180.94788,1.5,-11.542459,29.539469,16.741854,15.18207,1.642916,16.437447,1.612934,0.16046,7.654408,17.070732,0.001858 74,W,0,183.84,0,-32.864574,31.5079,15.682498,37.960129,4.885509,16.792112,1.629485,9.446448,0.000898,59.980675,0.160798 74,W6+,6,183.84,1.5,3.945157,29.729357,17.247808,15.184488,1.154652,0.739335,1.501648,0.140803,6.880573,14.299601,14.299618 75,Re,0,186.207,0,-37.412682,31.888456,16.117104,42.390297,5.211669,16.767591,1.549238,9.233474,0.000689,54.516373,0.152815 76,Os,0,190.23,0,-43.677956,32.210297,16.67844,48.559906,5.455839,16.735533,1.473531,9.049695,0.000519,50.210201,0.145771 76,Os4+,4,190.23,1.5,3.98839,17.113485,15.79237,23.342392,4.090271,7.671292,0.13185,7.288542,1.389307,19.629425,1.389307 77,Ir,0,192.217,0,4.018893,32.004436,1.975454,17.070105,15.939454,5.990003,1.353767,81.014175,0.128093,7.661196,26.659403 77,Ir3+,3,192.217,1.5,4.009459,31.537575,16.363338,15.597141,5.051404,1.436935,1.334144,7.451918,0.127514,21.705648,0.127515 77,Ir4+,4,192.217,1.5,4.006865,30.391249,16.146996,17.019068,4.458904,0.975372,1.328519,7.181766,0.127337,19.060146,1.328519 78,Pt,0,195.084,0,4.050394,31.273891,18.44544,17.063745,5.555933,1.57527,1.316992,8.797154,0.124741,40.177994,1.316997 78,Pt2+,2,195.084,1.5,4.032512,31.986849,17.249048,15.269374,5.760234,1.694079,1.281143,7.625512,0.123571,24.190826,0.123571 78,Pt4+,4,195.084,1.5,4.094551,41.932713,16.339224,17.653894,6.01242,-12.036877,1.111409,6.466086,0.128917,16.954155,0.778721 79,Au,0,196.966569,0,-6.279078,16.77739,19.317156,32.979683,5.595453,10.576854,0.122737,8.62157,1.256902,38.00882,0.000601 79,Au1+,1,196.966569,1.5,4.040792,32.124306,16.716476,16.8141,7.311565,0.993064,1.216073,7.165378,0.118715,20.442486,53.095985 79,Au3+,3,196.966569,1.5,4.042679,31.704271,17.545767,16.819551,5.52264,0.361725,1.215561,7.220506,0.118812,20.05097,1.215562 80,Hg,0,200.59,0,4.076478,16.83989,20.023823,28.428564,5.881564,4.714706,0.115905,8.256927,1.19525,39.247227,1.19525 80,Hg1+,1,200.59,1.5,4.06843,28.866837,19.27754,16.776051,6.281459,3.710289,1.173967,7.583842,0.115351,29.055994,1.173968 80,Hg2+,2,200.59,1.5,4.052869,32.411079,18.690371,16.711773,9.974835,-3.847611,1.16298,7.329806,0.114518,22.009489,22.009493 81,Tl,0,204.3833,0,4.066939,16.630795,19.386616,32.808571,1.747191,6.356862,0.110704,7.181401,1.11973,90.660263,26.014978 81,Tl1+,1,204.3833,1.5,4.05403,32.295044,16.570049,17.991013,1.535355,7.554591,1.101544,0.11002,6.528559,52.495068,20.338634 81,Tl3+,3,204.3833,1.5,-9.256075,32.525639,19.139185,17.100321,5.891115,12.599463,1.094966,6.900992,0.103667,18.489614,-0.001401 82,Pb,0,207.2,0,4.049824,16.419567,32.73859,6.530247,2.342742,19.916475,0.105499,1.055049,25.02589,80.906593,6.664449 82,Pb2+,2,207.2,1.5,4.065623,27.392647,16.496822,19.984501,6.813923,5.23391,1.058874,0.106305,6.708123,24.395554,1.058874 82,Pb4+,4,207.2,1.5,4.044678,32.505657,20.01424,14.645661,5.029499,1.760138,1.047035,6.670321,0.105279,16.52504,0.105279 83,Bi,0,208.9804,0,4.040914,16.282274,32.725136,6.678302,2.69475,20.576559,0.10118,1.002287,25.714146,77.057549,6.291882 83,Bi3+,3,208.9804,1.5,4.043703,32.461437,19.438683,16.302486,7.322662,0.431704,0.99793,6.038867,0.101338,18.371586,46.361046 83,Bi5+,5,208.9804,1.5,4.113663,16.734028,20.580494,9.452623,61.155834,-34.041023,0.105076,4.773282,11.762162,1.211775,1.619408 84,Po,0,208.9804,0,4.046556,16.289164,32.807171,21.095163,2.505901,7.254589,0.098121,0.966265,6.046622,76.598068,28.096128 85,At,0,208.9804,0,3.995684,16.011461,32.615547,8.113899,2.884082,21.377867,0.092639,0.904416,26.543257,68.372963,5.499512 86,Rn,0,208.9804,0,4.020977,16.070229,32.641106,21.489658,2.299218,9.480184,0.090437,0.876409,5.239687,69.188477,27.632641 87,Fr,0,208.9804,0,4.003472,16.007385,32.66383,21.594351,1.598497,11.121192,0.087031,0.840187,4.954467,199.805801,26.905106 88,Ra,0,208.9804,0,3.981773,32.56369,21.396671,11.298093,2.834688,15.914965,0.80198,4.590666,22.758972,160.404388,0.083544 88,Ra2+,2,208.9804,1.5,3.956572,4.986228,32.474945,21.947443,11.800013,10.807292,0.082597,0.791468,4.608034,24.792431,0.082597 89,Ac,0,208.9804,0,3.939212,15.914053,32.535042,21.553976,11.433394,3.612409,0.080511,0.770669,4.352206,21.381622,130.500748 89,Ac3+,3,208.9804,1.5,3.838984,15.584983,32.022125,21.456327,0.757593,12.341252,0.077438,0.739963,4.040735,47.525002,19.406845 90,Th,0,232.03806,0,3.922533,15.784024,32.454899,21.849222,4.239077,11.736191,0.077067,0.735137,4.097976,109.464111,20.512138 90,Th4+,4,232.03806,1.5,3.831122,15.515445,32.090691,13.996399,12.918157,7.635514,0.074499,0.711663,3.871044,18.596891,3.871044 91,Pa,0,231.03588,0,3.886066,32.740208,21.973675,12.957398,3.683832,15.744058,0.709545,4.050881,19.231543,117.255005,0.07404 92,U,0,238.02891,0,3.854444,15.679275,32.824306,13.660459,3.687261,22.279434,0.071206,0.681177,18.236156,112.500038,3.930325 92,U3+,3,238.02891,1.5,3.706622,15.360309,32.395657,21.96129,1.325894,14.251453,0.067815,0.654643,3.643409,39.604965,16.33057 92,U4+,4,238.02891,1.5,3.705863,15.355091,32.235306,0.557745,14.396367,21.751173,0.067789,0.652613,42.354237,15.908239,3.553231 92,U6+,6,238.02891,1.5,3.700591,15.333844,31.770849,21.274414,13.872636,0.048519,0.067644,0.646384,3.317894,14.65025,75.339699 93,Np,0,238.02891,0,3.769391,32.999901,22.638077,14.219973,3.67295,15.683245,0.657086,3.854918,17.435474,109.464485,0.068033 93,Np3+,3,238.02891,1.5,3.60337,15.378152,32.572132,22.206125,1.413295,14.828381,0.064613,0.63142,3.561936,37.875511,15.546129 93,Np4+,4,238.02891,1.5,3.603039,15.373926,32.423019,21.969994,0.662078,14.96935,0.064597,0.629658,3.476389,39.438942,15.135764 93,Np6+,6,238.02891,1.5,3.600942,15.359986,31.992825,21.412458,0.066574,14.568174,0.064528,0.624505,3.253441,67.658318,13.980832 94,Pu,0,238.02891,0,3.6642,33.281178,23.148544,15.153755,3.031492,15.704215,0.634999,3.856168,16.849735,121.292038,0.064857 94,Pu3+,3,238.02891,1.5,3.428895,15.356004,32.769127,22.68021,1.351055,15.416232,0.06059,0.604663,3.491509,37.260635,14.981921 94,Pu4+,4,238.02891,1.5,3.480408,15.416219,32.610569,22.256662,0.719495,15.518152,0.061456,0.607938,3.411848,37.628792,14.46436 94,Pu6+,6,238.02891,1.5,3.502325,15.436506,32.289719,14.726737,15.012391,7.024677,0.061815,0.606541,3.245363,13.616438,3.245364 95,Am,0,238.02891,0,3.54116,33.435162,23.657259,15.576339,3.027023,15.7461,0.612785,3.792942,16.195778,117.757004,0.061755 96,Cm,0,238.02891,0,3.39084,15.804837,33.480801,24.150198,3.655563,15.499866,0.058619,0.59016,3.67472,100.736191,15.408296 97,Bk,0,238.02891,0,3.213169,15.889072,33.625286,24.710381,3.707139,15.839268,0.055503,0.569571,3.615472,97.694786,14.754303 98,Cf,0,238.02891,0,3.005326,33.794075,25.467693,16.048487,3.657525,16.008982,0.550447,3.581973,14.357388,96.064972,0.05245 300,OH1-,-1,17.00734,2,0.0817716077,3.7112086044,2.9808357753,1.3234818897,0.781899673,1.1189575326,15.6328903403,4.9075336219,-0.0927836086,67.2137935891,29.8955961228 14,Si2+,2,28.0855,1.5,0.3963295865,4.7298377194,0.6519626421,3.3278304574,1.6223740315,1.2593895831,2.9267346036,52.6132881596,0.5050129897,31.6813758444,2.6993888649 13,Al1.5+,1.5,26.9815386,1.5,0.1315188639,1.2350269021,0.5698803269,7.21364941,-2.9599280486,5.3108185306,32.9722977364,58.4965856924,0.3080753308,-0.1724362195,3.9590276059 301,H2O,0,18.01528,2,0.025429,3.990247,2.300563,0.6072,1.907882,1.16708,16.639956,5.636819,0.108493,47.299709,0.379984 26,Fe1.5+,1.5,55.845,1.5,-61.44564969,12.78432135,62.56327581,1.683751473,1.432130736,6.958358598,6.234159837,-0.000399296,1.0632928865,21.58593936,0.374772458 302,Glycol,0,31.035,11,180.8052097,7.887361193,-184.185669,4.442661888,3.68850792,4.349643565,13.09791052,0.001710201,1.978601512,36.20862715,-0.16291182 12,Mg1+,2,24.305,1.5,1.54721309,5.401034124,-52.05602002,54.84239699,0.6078769288,0.6571202564,2.419904697,8.528164558,8.405980557,109.6784057,43.68695258 PyXRD-0.8.4/pyxrd/data/composition_conversion.csv000066400000000000000000000003021363064711000220710ustar00rootroot00000000000000#atom_nr (as in atomic scattering factors) - Oxide name - Wt% conversion element to oxide 14,SiO2,2.1392 13,Al2O2,1.8895 26,Fe2O3,1.4297 20,CaO,1.3992 12,MgO,1.6582 11,Na2O,1.3480 19,K2O,1.2046 PyXRD-0.8.4/pyxrd/data/default components/000077500000000000000000000000001363064711000203435ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/data/default components/Chlorite.cmp000066400000000000000000000475621363064711000226330ustar00rootroot00000000000000PK TF)NN a22e1bf32a474c5783d4f48558117e76{ "type": "Component", "properties": { "uuid": "a22e1bf32a474c5783d4f48558117e76", "name": "Chlorite", "d001_ref_info": [ 1.4, 1.44, false ], "d001": 1.42, "default_c": 1.42, "delta_c_ref_info": [ 0.0, 0.05, false ], "delta_c": 0.0, "ucp_a_ref_info": [ 0.0, 0.0, false ], "ucp_a": { "type": "UnitCellProperty", "properties": { "uuid": "342dc5cc7b3041cf9f76b846687fe18c", "value_ref_info": [ 0.0, 1.0, false ], "value": 0.5323167000000001, "factor": 0.57735, "constant": 0.0, "prop": [ "a22e1bf32a474c5783d4f48558117e76", "cell_b" ], "enabled": true } }, "ucp_b_ref_info": [ 0.0, 0.0, false ], "ucp_b": { "type": "UnitCellProperty", "properties": { "uuid": "0c4d5a6ae2e8495fa83ffc786ea97ff2", "value_ref_info": [ 0.0, 1.0, false ], "value": 0.922, "factor": 0.0014, "constant": 0.922, "prop": [ "3546c01839964083ad8bb6685587cb35", "pn" ], "enabled": true } }, "inherit_d001": false, "inherit_ucp_b": false, "inherit_ucp_a": false, "inherit_default_c": false, "inherit_delta_c": false, "inherit_layer_atoms": false, "inherit_interlayer_atoms": false, "inherit_atom_relations": false, "atom_relations_ref_info": [ 0.0, 0.0, false ], "atom_relations": [ { "type": "AtomContents", "properties": { "uuid": "99d72fb36cf94b0ebd08e000855cec8f", "name": "OctFe Ratio Si sheet", "value_ref_info": [ 0.0, 1.0, false ], "value": 0.125, "enabled": true, "atom_contents": [ [ "b6128d65a98949058e4fbc4b50a17116", "value", 1.0 ], [ "936201874fa44ddfa0ed4dcdcf4e9764", "value", 1.0 ] ] } }, { "type": "AtomRatio", "properties": { "uuid": "53b544bcea7d45568625c70ecbc463b8", "name": "Di/Tri Ratio Si sheet", "value_ref_info": [ 0.0, 1.0, false ], "value": 0.0, "enabled": true, "sum": 1.0, "atom1": [ "3ae5fe47f1524e78aea6f1971580f8e1", "value" ], "atom2": [ "b9da9c13191043b8aa37e5c6fd317458", "value" ] } }, { "type": "AtomContents", "properties": { "uuid": "451dbdcacd954d3f8401cd4077323932", "name": "Completeness OH sheet", "value_ref_info": [ 0.0, 1.0, false ], "value": 1.0, "enabled": true, "atom_contents": [ [ "866c53fdb1554f7d8e41e907386618e1", "pn", 6.0 ], [ "4ba6115c389241f2b532ab38a1baff12", "pn", 6.0 ], [ "e2c2ec543d2645908f584d6e906812a7", "__internal_sum__", 1.0 ] ] } }, { "type": "AtomRatio", "properties": { "uuid": "e2c2ec543d2645908f584d6e906812a7", "name": "Di/Tri Ratio OH sheet", "value_ref_info": [ 0.0, 1.0, false ], "value": 0.0, "enabled": true, "sum": 1.0, "atom1": [ "0cea50b29a204110a9f207461772f582", "value" ], "atom2": [ "8c9a8980a4e247c497b66f6b82e613c3", "value" ] } }, { "type": "AtomContents", "properties": { "uuid": "17c3b8962a6546df93332cc5dcb1e686", "name": "OctFe OH sheet", "value_ref_info": [ 0.0, 1.0, false ], "value": 0.125, "enabled": true, "atom_contents": [ [ "6b85b8ad67d0468496421337a2e2fe8d", "value", 1.0 ], [ "d6cc8583f5074f1bb0cca6f48e0896e7", "value", 1.0 ] ] } }, { "type": "AtomContents", "properties": { "uuid": "3ae5fe47f1524e78aea6f1971580f8e1", "name": "DiOct content Si sheet", "value_ref_info": [ 0.0, 0.0, false ], "value": 0.0, "enabled": true, "atom_contents": [ [ "b6128d65a98949058e4fbc4b50a17116", "__internal_sum__", 4.0 ], [ "9f24619213b74cf6a73e0db1ba8cab05", "pn", 4.0 ], [ "317807bf81d745ad983c1507074ff770", "pn", 4.0 ] ] } }, { "type": "AtomContents", "properties": { "uuid": "b9da9c13191043b8aa37e5c6fd317458", "name": "TriOct content Si Sheet", "value_ref_info": [ 0.0, 0.0, false ], "value": 1.0, "enabled": true, "atom_contents": [ [ "936201874fa44ddfa0ed4dcdcf4e9764", "__internal_sum__", 6.0 ], [ "0e53d15c8f5a4fe6b29c7b46f1433332", "pn", 4.0 ], [ "0c31fdcadc944afb9a2614c3daefde63", "pn", 4.0 ] ] } }, { "type": "AtomRatio", "properties": { "uuid": "b6128d65a98949058e4fbc4b50a17116", "name": "DiOctFe Si Sheet", "value_ref_info": [ 0.0, 1.0, false ], "value": 0.125, "enabled": true, "sum": 0.0, "atom1": [ "3546c01839964083ad8bb6685587cb35", "pn" ], "atom2": [ "1ee73f4207ab4eb5895ab2c2fbb3418b", "pn" ] } }, { "type": "AtomRatio", "properties": { "uuid": "936201874fa44ddfa0ed4dcdcf4e9764", "name": "TriOctFe Si Sheet", "value_ref_info": [ 0.0, 0.0, false ], "value": 0.125, "enabled": true, "sum": 6.0, "atom1": [ "20d1fcef51df4c60ace7849e96d55900", "pn" ], "atom2": [ "e3d8362fbe2d47f98605c2e6bce2fd15", "pn" ] } }, { "type": "AtomContents", "properties": { "uuid": "0cea50b29a204110a9f207461772f582", "name": "DiOct contents OH sheet", "value_ref_info": [ 0.0, 0.0, false ], "value": 0.0, "enabled": true, "atom_contents": [ [ "6b85b8ad67d0468496421337a2e2fe8d", "__internal_sum__", 4.0 ] ] } }, { "type": "AtomContents", "properties": { "uuid": "8c9a8980a4e247c497b66f6b82e613c3", "name": "TriOct contents OH sheet", "value_ref_info": [ 0.0, 0.0, false ], "value": 1.0, "enabled": true, "atom_contents": [ [ "d6cc8583f5074f1bb0cca6f48e0896e7", "__internal_sum__", 6.0 ] ] } }, { "type": "AtomRatio", "properties": { "uuid": "6b85b8ad67d0468496421337a2e2fe8d", "name": "DiOctFe OH sheet", "value_ref_info": [ 0.0, 0.0, false ], "value": 0.125, "enabled": true, "sum": 0.0, "atom1": [ "4bdc509fed8d4126bee0ac423676b8c1", "pn" ], "atom2": [ "dfe621f0ea0a49f99927220c43464f1f", "pn" ] } }, { "type": "AtomRatio", "properties": { "uuid": "d6cc8583f5074f1bb0cca6f48e0896e7", "name": "TriOctFe OH sheet", "value_ref_info": [ 0.0, 0.0, false ], "value": 0.125, "enabled": true, "sum": 6.0, "atom1": [ "61613c6a38194612abe510ca246f5695", "pn" ], "atom2": [ "daf389ab2af54818a5479536c4676ae3", "pn" ] } } ], "layer_atoms": [ { "type": "Atom", "properties": { "uuid": "7a0402b8ff534c98bd5a00043b889d1d", "name": "O", "default_z": 0.654, "pn": 6.0, "atom_type_name": "O1-" } }, { "type": "Atom", "properties": { "uuid": "9f24619213b74cf6a73e0db1ba8cab05", "name": "DiSi", "default_z": 0.597, "pn": 0.0, "atom_type_name": "Si2+" } }, { "type": "Atom", "properties": { "uuid": "0e53d15c8f5a4fe6b29c7b46f1433332", "name": "TriSi", "default_z": 0.602, "pn": 4.0, "atom_type_name": "Si2+" } }, { "type": "Atom", "properties": { "uuid": "39ee193cd30e42069e806f746f4622ac", "name": "O", "default_z": 0.433, "pn": 4.0, "atom_type_name": "O1-" } }, { "type": "Atom", "properties": { "uuid": "0212781ff0fe439891b4c9714030bd87", "name": "OH", "default_z": 0.433, "pn": 2.0, "atom_type_name": "OH1-" } }, { "type": "Atom", "properties": { "uuid": "3546c01839964083ad8bb6685587cb35", "name": "Fe3+", "default_z": 0.327, "pn": 0.0, "atom_type_name": "Fe1.5+" } }, { "type": "Atom", "properties": { "uuid": "1ee73f4207ab4eb5895ab2c2fbb3418b", "name": "Al", "default_z": 0.327, "pn": 0.0, "atom_type_name": "Al1.5+" } }, { "type": "Atom", "properties": { "uuid": "20d1fcef51df4c60ace7849e96d55900", "name": "Fe2+", "default_z": 0.327, "pn": 0.75, "atom_type_name": "Fe1.5+" } }, { "type": "Atom", "properties": { "uuid": "e3d8362fbe2d47f98605c2e6bce2fd15", "name": "Mg", "default_z": 0.327, "pn": 5.25, "atom_type_name": "Mg1+" } }, { "type": "Atom", "properties": { "uuid": "f0a7a61a9967470aa62832ffb649fa27", "name": "OH", "default_z": 0.221, "pn": 2.0, "atom_type_name": "OH1-" } }, { "type": "Atom", "properties": { "uuid": "d387cf1cd3004ff08f43ee719844bbac", "name": "O", "default_z": 0.221, "pn": 4.0, "atom_type_name": "O1-" } }, { "type": "Atom", "properties": { "uuid": "317807bf81d745ad983c1507074ff770", "name": "DiSi", "default_z": 0.057, "pn": 0.0, "atom_type_name": "Si2+" } }, { "type": "Atom", "properties": { "uuid": "0c31fdcadc944afb9a2614c3daefde63", "name": "TriSi", "default_z": 0.052, "pn": 4.0, "atom_type_name": "Si2+" } }, { "type": "Atom", "properties": { "uuid": "96e96df24f45437ca808039697e4e4b6", "name": "O", "default_z": 0.0, "pn": 6.0, "atom_type_name": "O1-" } } ], "interlayer_atoms": [ { "type": "Atom", "properties": { "uuid": "866c53fdb1554f7d8e41e907386618e1", "name": "OH", "default_z": 1.134, "pn": 6.0, "atom_type_name": "OH1-" } }, { "type": "Atom", "properties": { "uuid": "dfe621f0ea0a49f99927220c43464f1f", "name": "Al", "default_z": 1.032, "pn": 0.0, "atom_type_name": "Al1.5+" } }, { "type": "Atom", "properties": { "uuid": "4bdc509fed8d4126bee0ac423676b8c1", "name": "Fe3+", "default_z": 1.032, "pn": 0.0, "atom_type_name": "Fe1.5+" } }, { "type": "Atom", "properties": { "uuid": "daf389ab2af54818a5479536c4676ae3", "name": "Mg", "default_z": 1.032, "pn": 5.25, "atom_type_name": "Mg1+" } }, { "type": "Atom", "properties": { "uuid": "61613c6a38194612abe510ca246f5695", "name": "Fe2+", "default_z": 1.032, "pn": 0.75, "atom_type_name": "Fe1.5+" } }, { "type": "Atom", "properties": { "uuid": "4ba6115c389241f2b532ab38a1baff12", "name": "OH", "default_z": 0.93, "pn": 6.0, "atom_type_name": "OH1-" } } ] } }PK TF)NN a22e1bf32a474c5783d4f48558117e76PKNOPyXRD-0.8.4/pyxrd/data/default components/Di-Smectite/000077500000000000000000000000001363064711000224525ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/data/default components/Di-Smectite/Di-Smectite - Ca 1GLY.cmp000066400000000000000000000026201363064711000264000ustar00rootroot00000000000000PKTF¬" 4c392d11aff3477bbfed464900731d98YMo60rm7ނ۽袧ba ak@ [..K+'EIv /Aѐ||o8C0I_էn㼼yYK\3\u_ݿBܲ@)ȅE B K4X8?]=/_З'elP5O]|+B]Ϸ}kc[aSS9V0Eks̚$mOW‘󎟶:ֺ!ßYyES 3x#;"Sa)T1þ`~ 7fGڐtA^E}.ZEϴjkkKH?EfvGɬ=q:[+:ri#aBu@PdШB3N$mWoP=OBFOOb3(hFe$68 r~6jjVNӮdg1jb|֤{\M\ܭfW#m+Y:.˶٤|q6*I_.ҞS*mKgd0T3i@a R! Y6-Lv1YӑjP|ZwZWVk\L:YէCa~v FH IO));P6A & nzZ={3yi :@LDD*)AmR&Ž_/K`?m 2^^nzk!N)WҔ=xwn|;@$QCDžIi|{AzTͳuy20A /"e*6|9T;q[oR$YfqWYY +@&D CB_АvBxTvl0lfSDO)2 $k4T2$8?MT?ծIxVy6J`3^|ʋt`; AHJH(ҠtĂsb&܀" 19C9БDd!4*'QfB'@Ay3I, %g&̼H wMB`˜0o!Y0[HEg|txF1E!XqRTPU/咡!$cA8&)h`_@By@=>"'C`}P y Tr0FGdݥPC> =}xi?u K#oOGj65eC0oo~G3a94PKTF¬" 4c392d11aff3477bbfed464900731d98PKN,PyXRD-0.8.4/pyxrd/data/default components/Di-Smectite/Di-Smectite - Ca 1WAT.cmp000066400000000000000000000025301363064711000264000ustar00rootroot00000000000000PK:TF0Sg c457e5c5702b49788cee46e919ce35f3YMo60|m7P$X֢AC0Hj plÖ[٢d;N ||Aqf~pVmFe+^uW3(*(̈́ 6TQ6I3w_/KCY8%I1sBGnmϷ]d&V7F96-$7ŮGdmi;r×nǬ(op:m#6ЈfN8PV9pI:k1}Ɠ#mJimwDrZvb|*oSft?>8s~՝u*PQ:8+41k`1W ݻe\铱LkĀ<3K0+i ڤ<Wf1E9kN\y51> \N\9_Y4:ueAW܅l#^͕6Ҷᜍ_̛9yV:qkL\: Z( LR%ڔ.7n6IB UKDպDպigC \J #h%e3Li`G5PS$o Po>zwV|+:1*T 45Thb!bQ8~^K+Ր7֯3#!Y4jZ%r1GU~x5WAx! ˌqT *s&]"45_| eZ;u{rVO*ת<|!ѿ!᭕Ig =c9mCC.]J;J'1$݊QJI'I"ZJf(פ:d(/ 7/ \0b/ *&8&:+dRY䀧+OF<Ϲ2 #mE2 Iői<I#U0674SKzWP"< ݞIqGCTm*wo.CJ]|d^ODfOMOtƁ(E`Rew#[6;A6!MXn2sUãևL C=yBo34ګLz ";_1/BTpa@4EBH+AmXM&yz)mC~|@y>`g]rOJV!.JղPK:TF0Sg c457e5c5702b49788cee46e919ce35f3PKNPyXRD-0.8.4/pyxrd/data/default components/Di-Smectite/Di-Smectite - Ca 2GLY.cmp000066400000000000000000000031471363064711000264060ustar00rootroot00000000000000PKTFÚ - 20c32770d08d44fc8ecbe72aea924451ZKo60rmcނZtQP5؁#Hk'EQ ʛ98߼oF4Ϊxitv1:qzr_E57[3/P!Bk ɛ]#ZPBK3jiUմ#wy "t$Q=XWSZlM%,ofiUZǨ|L,~XV8yؘm@3ۊiڀmt|侇o۬޺fzmϦE,_WFкF0c'7qިĀ"acr{\^}҆ ͽ*}LB_+!en*$fm_F3{G1]ĝv8CWΪŲݶ g5PjjpI鬊ek)]qd[٨H{rY~NoV=od6q1ƃ yB(8uk ,GcV@PsGAԣIԣ@54BmnW$Y 罇5lD^YH fY'=.:> l=4yA1^YGwS3ޡ h~i܀y)S:~EĠ=z;IǾ{g]$/.{7o? Lwށah1կ/}a?PKTFÚ - 20c32770d08d44fc8ecbe72aea924451PKNPyXRD-0.8.4/pyxrd/data/default components/Di-Smectite/Di-Smectite - Ca 2WAT.cmp000066400000000000000000000024501363064711000264020ustar00rootroot00000000000000PKYTF" 6f39a2ad18d94e958de6646a1c558b20X]1}WlU%hoOƐ{HvШ[!:aE C{۞s{s~GOW׋Ŧze;RAWYfPa>`|bkxRt5jj+ -PBN&/ m>r(kiUWvwU[CQTFP"oT8S<2p^[%7ͬ}ˍ>FC)$ Aʣu6.SD0~8_NEf7%=jzXJ 1މŦZfyNr4k.W }w IJw PҖ Jx"'"?! Sg0{ e1us2iΚMp~-18 '^N]\ fMðk@٢ىFazwqidQܴ7=U\s:0>rF #Tfۊz'x7C뉡 Ov@ddg;Pj24îfgGFDNB¢50r~A {(.&4 rH PAiStU>W/T>ͩԫ'LK"SR3EÐ`V@S q0DJdYG~{}鬒9&&;ecsЊCQVM"x B[fuU0C?'?ZkHU$U %tTbm@fݧ4x`0Q8 ,Rكd>eh (;YO/M7&2[LDfIRD$m *g2id`DUύJ(2 1JIq4M1yL%IL@dbڳs?IuϏVHV.S*' ^|d>D>ɔ!𖼆Ҹ&grmrIVAN J%+=a0_o<$Iy AgؐX~0$~BT&g4 O"zGӤW|^ o#\Ndqo*aMmL W;2A.%څh`X]"PK?YTF" 6f39a2ad18d94e958de6646a1c558b20PKNPyXRD-0.8.4/pyxrd/data/default components/Di-Smectite/Di-Smectite - Ca Dehydr.cmp000066400000000000000000000023771363064711000271140ustar00rootroot00000000000000PKTFϩ] 9b137590119941b493a7fb00361a5a5cYn8+ o; |t(`Y PdÖډQHUL]^,y} fBUVV*"lvn g!%ކf VTuXmI%E!dbȑGezj\՛@stf2H%cHI\H#5_pvdG&fC3 ICtYYަ_ +! #T ǔakB䃁@& aufz;NFVnT$"@X12 I~u|"~ lxd$zꚎɥ}x5#{!62"ɑBJi`0")L:;4Y[NJO`>*%z"E  %dgLkќzqؔ~x|J7&M}" 'bqH Iꌹ$8I0jɉH2y]Jj2Z6Oǧj2/cjҥ&HA *<18MyxoLL|P#]DJ;%j=yYL~ k$jŬ0 l*=R 5$/SA #HHD:rI#lgɷ^xб8D-˄EUn:[ ͛!n ǘt\(m*gdiχ_~ZyH5WjscƯSnPKTFϩ] 9b137590119941b493a7fb00361a5a5cPKNPyXRD-0.8.4/pyxrd/data/default components/Di-Smectite/Di-Smectite - Ca Heated.cmp000066400000000000000000000023661363064711000270650ustar00rootroot00000000000000PKؑTF6ST d5a210a5982447268bd947f467166435XKo60|mw " z*Ɛ9"9Xx'H?~3ѷԖ--?/uIeuݬoiSMߚ˳zFPpY`>s`"hõڅI%4P-+:1P\e\'߇ON'>g"[:|= fN7E*˜YQ14[?]i + j2Oyu7Ny}AU]ÿc wuQJLI0FZMp L?g'PǸ'c3N'zqtlfh/e#B G5% թBC$HdX߳Nюˌ4(gMJHcD~R Тj@d$ Z蚏O]3﹫kĸAA#Z&ʜM gĴa~ԅ r(}N <*Q89pJaA_#CgBkUzuؔ|~ 7&(jFz)2~sYπf|&ʙQ&'RaPKؑTF6ST d5a210a5982447268bd947f467166435PKNPyXRD-0.8.4/pyxrd/data/default components/Di-Vermiculite/000077500000000000000000000000001363064711000231655ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/data/default components/Di-Vermiculite/Di-Vermiculite - Ca 1GLY.cmp000066400000000000000000000026201363064711000276260ustar00rootroot00000000000000PK͓TFgWY" d3b5b5f3316345dbbffef5e16756309aZKoFW1p斢A{)a-Z$wH)sII {p|BM]U 0-3UW?O6#|˪k{e*.d>%HRqb];Q5RwEXEwy߱Ҵ9~vn'֛.v+W;=ΊN7$.i++7 MkО6 ݙa1u6--otf8w`IR JDWqG<75䏞?phv I4]4~*&P͗#yMVCixkQ=AJ.N 3K}V-]v)JP e$ KpjH$CI3 2T{+νw2-:Kl,f~ixKA7d0jc|֤tj~2+f, tqelR~=lU~_̳j+76[t`ZxEJ"BG>2%jܺIl&kg`[>8- $'ǝ59rV7C딡GtD)bL^0 a 1>8`1A_{yYțF8qnXZmۥ{QW (U*ѡ d$; V*&j C$zI6H3h?,=f1;.jrL7$>ǵGh"%ͲOVqql--*]m =C;s嗸jwV@Ff[($ 15Y\"e^`dEL 'zD=D=&uNg9ƛ~㔾WE: F$,璣WTDs o`A?ۛkZVbYrĊ$W4H?3)PR]Uyez`E RA#,O]:g?D\sauA2{i"%7_}KK3i\ݠZ^ɭZ]GAl lukbiо٬oPtw)b($e6Jz^:zJl*)z/#ϯI}PyJ7R[ʼT(l24ʛ;}/DRl 9m$Ai4a<?؞K葖!ǡ3$hl0`:B[R=]DJÛ}(Fz!.Z|`^'l6 #!]!u! [R٣ QGNWs'%0tph%)LNCs:sRT^{/X) Jl<{*_ TmcPCuwi]_xYK{;c8ȱBIotD;T&5˼lt\QPy9Ɍl=`SJ( J+IG'e%16/M;tE.P& P1%'nowY.)m: 6RFbK0$`Jl: 4CNabi}~(GxULF <*ɡ(E|"00/ʓ3łt.ց $m9"L\iAo< D88R2ucD|pjY'=.:> l=4yAs{}1Nx6Gm'oͱ-a΀dChמcGy"L?=-}$/6d½ihwνջ˱_Gxq4wwPKɓTF_C - 11a0332622af40b686410e0d14ca55e1PKNPyXRD-0.8.4/pyxrd/data/default components/Di-Vermiculite/Di-Vermiculite - Ca 2WAT.cmp000066400000000000000000000026051363064711000276320ustar00rootroot00000000000000PKTFJt" fa547f7ae91b40e68e9c55668bffb88aXKo60|mc[b]ba1 p[RډlQ8p›ѐ}3C0Jm\h|[q^mF;\_PECBJbtZۭ.o %ؿlBtqZ"njt.lohtcY9ЬS_S8bI ߦM8غ{K?Ey܏<'Kf#!`%@C"JF9w\1fGA\E}.Iϴ֗H)jUixgQQBkNpvsfXY\7z}=SȝDIM4`52eK }5>~2xzJ$B`I9&nFbE9kLƷ$4]OSG6gMfS[.nW]fż@ qtf"qelR>XH{8*J^/ҞU=*mJ@Hh 4靂TZW ZSQ`[\ۑ,&k:00T-UkUkjISg94Pvi+(`4DrV L0.1A =& vz={3yE$*EECQvK%Oq.I[M^Nq>d 0d#sߞ#pCC]<8xzK#=*<[t40%T~A3p /?g؛H=Spm kE͟\b?UnZ^tUVh,AM.0#i @{uլ*- I$PH1)jP5s/Ŷq4V&OY@:C^''LrKt,*J{Z DRlW#٣&_ ˥A{j[[DJ|*? =/.Z|d^OD&;̋E7;Iτ>j̈́Yyٝ@ )9אl^ %OS (ĨWkRvt!"~ːE!Hnب3 Nʹvro)}?PKѓTFyP\ 55e92aa69b9847a2a3821a4452815d67PKNPyXRD-0.8.4/pyxrd/data/default components/Di-Vermiculite/Di-Vermiculite - Ca Heated.cmp000066400000000000000000000024071363064711000303070ustar00rootroot00000000000000PKՓTFpe 28fd4c3614dd48c7978f65a22ff668a1YKo8Wn-pQEa !V"@_Je9")O19| |7Kc^߬qq6_׫ z~_YoVknv`&Ṣ"aD%2@;E c>uweQ Г 㲨*m ra' zA8Pn0dzaWK?Xְc#Ҍ<ê5w~3ϝ~lvcşUQ/,sf~9G;?b!eQs\sI4.+n7 ͮ!oEI1me"z Ԑ_UR р|tib&ffGBE ~A.P2NjF( MO 3x%&A;,=ՋQMQ/ӡd0&pyu5>+R nPcbEUfl#8ZB]I%^,>]_U򹪷x"[C+BW r 5$t:@0,`dE'fC3EC tgY:'ϳTMeJ(ՍԿ(+$:˥ 9k"@䃂T@N$߆{0{ۛ=:yh8d@ TC(nUFX/s3iR6ԼZiZ:Vnjw: 2pH"@$F0$S{AR`Y% & ȘʻI 5ti- SG3gj_PaTn^i (&q! RT xP 6ۇZ$OdjɯA4%fQ3 3RQ@{ I! 9x4ޤG"dgJńI? UpꙐdg!y|[sjчN`:^Wm0/ge#Q'& qQ2R_s;;vcAq@!1),"gk<|2ZᘶRǨzZN k_R.F˃E! ,HNʩ2Wz징{&m ?7=D|{PKՓTFpe 28fd4c3614dd48c7978f65a22ff668a1PKNPyXRD-0.8.4/pyxrd/data/default components/Illite.cmp000066400000000000000000000017101363064711000222650ustar00rootroot00000000000000PKYX$G,& 878298324e9e11e2b238150ae229a525Xn0Wئ<"Ehժ*BH3bLʿ8pA: >>ڞ?ub\].A*ú|`5j/ls J\c 1f!!gL'ɷUOBd  B8* 2ĊÐUpsgm){8=O\T#vHy)TtQbzěfU;A= .#ӶFolhCe@/?)*:ՑȦv_\нt낍2~J 5}0"ºp)OT^@ߧ${ťjlsо7Y;1ΰ䱀þl:\F"_G(HM4DO\F'ݟ> 5cbe111a07e711e28873782bcbaf1941{ "type": "Component", "properties": { "uuid": "5cbe111a07e711e28873782bcbaf1941", "name": "Kaolinite", "d001_ref_info": [ 0.71, 0.73, false ], "d001": 0.716, "default_c": 0.716, "delta_c_ref_info": [ 0.0, 0.05, false ], "delta_c": 0.0, "ucp_a_ref_info": [ 0.0, 0.0, false ], "ucp_a": { "type": "UnitCellProperty", "properties": { "uuid": "5cbe0abc07e711e28873782bcbaf1941", "value_ref_info": [ 0.0, 1.0, false ], "value": 0.5161509000000001, "factor": 0.57735, "constant": 0.0, "prop": [ "5cbe111a07e711e28873782bcbaf1941", "cell_b" ], "enabled": true } }, "ucp_b_ref_info": [ 0.0, 0.0, false ], "ucp_b": { "type": "UnitCellProperty", "properties": { "uuid": "5cbe20ce07e711e28873782bcbaf1941", "value_ref_info": [ 0.89, 0.9, false ], "value": 0.894, "factor": 1.0, "constant": 0.0, "prop": null, "enabled": false } }, "inherit_d001": false, "inherit_ucp_b": false, "inherit_ucp_a": false, "inherit_default_c": false, "inherit_delta_c": false, "inherit_layer_atoms": false, "inherit_interlayer_atoms": false, "inherit_atom_relations": false, "atom_relations_ref_info": [ 0.0, 0.0, false ], "atom_relations": [], "layer_atoms": [ { "type": "Atom", "properties": { "uuid": "5cbde35c07e711e28873782bcbaf1941", "name": "Al1", "default_z": 0.3378, "pn": 2.0, "atom_type_name": "Al1.5+" } }, { "type": "Atom", "properties": { "uuid": "5cbe0de607e711e28873782bcbaf1941", "name": "Al2", "default_z": 0.3363, "pn": 2.0, "atom_type_name": "Al1.5+" } }, { "type": "Atom", "properties": { "uuid": "5cbe015207e711e28873782bcbaf1941", "name": "Si1", "default_z": 0.065, "pn": 2.0, "atom_type_name": "Si2+" } }, { "type": "Atom", "properties": { "uuid": "5cbddd1207e711e28873782bcbaf1941", "name": "Si2", "default_z": 0.0653, "pn": 2.0, "atom_type_name": "Si2+" } }, { "type": "Atom", "properties": { "uuid": "5cbde03c07e711e28873782bcbaf1941", "name": "O", "default_z": 0.2268, "pn": 2.0, "atom_type_name": "O1-" } }, { "type": "Atom", "properties": { "uuid": "5cbdd0f607e711e28873782bcbaf1941", "name": "O", "default_z": 0.2272, "pn": 2.0, "atom_type_name": "O1-" } }, { "type": "Atom", "properties": { "uuid": "5cbdf35607e711e28873782bcbaf1941", "name": "O", "default_z": 0.0, "pn": 2.0, "atom_type_name": "O1-" } }, { "type": "Atom", "properties": { "uuid": "5cbe1db807e711e28873782bcbaf1941", "name": "O", "default_z": 0.0177, "pn": 2.0, "atom_type_name": "O1-" } }, { "type": "Atom", "properties": { "uuid": "5cbdfe3207e711e28873782bcbaf1941", "name": "O", "default_z": 0.0023, "pn": 2.0, "atom_type_name": "O1-" } }, { "type": "Atom", "properties": { "uuid": "5cbde9e207e711e28873782bcbaf1941", "name": "OH", "default_z": 0.2304, "pn": 2.0, "atom_type_name": "OH1-" } }, { "type": "Atom", "properties": { "uuid": "5cbe178207e711e28873782bcbaf1941", "name": "OH", "default_z": 0.433, "pn": 2.0, "atom_type_name": "OH1-" } }, { "type": "Atom", "properties": { "uuid": "5cbdf7e807e711e28873782bcbaf1941", "name": "OH", "default_z": 0.4351, "pn": 2.0, "atom_type_name": "OH1-" } }, { "type": "Atom", "properties": { "uuid": "5cbdd62807e711e28873782bcbaf1941", "name": "OH", "default_z": 0.4361, "pn": 2.0, "atom_type_name": "OH1-" } } ], "interlayer_atoms": [] } }PKaTF_6>> 5cbe111a07e711e28873782bcbaf1941PKN|PyXRD-0.8.4/pyxrd/data/default components/Leucophyllite.cmp000066400000000000000000000167151363064711000237000ustar00rootroot00000000000000PKfTFM++ f6bfa54f207744b494d9b567b3436ce4{ "type": "Component", "properties": { "uuid": "f6bfa54f207744b494d9b567b3436ce4", "name": "Leucophyllite", "d001_ref_info": [ 0.984, 1.004, false ], "d001": 0.986, "default_c": 0.986, "delta_c_ref_info": [ 0.0, 0.05, false ], "delta_c": 0.0, "ucp_a_ref_info": [ 0.0, 0.0, false ], "ucp_a": { "type": "UnitCellProperty", "properties": { "uuid": "a6dd7817abac4a5ebd8d764c22f9824a", "value_ref_info": [ 0.0, 0.0, false ], "value": 0.519615, "factor": 0.57735, "constant": 0.0, "prop": [ "f6bfa54f207744b494d9b567b3436ce4", "cell_b" ], "enabled": true } }, "ucp_b_ref_info": [ 0.0, 0.0, false ], "ucp_b": { "type": "UnitCellProperty", "properties": { "uuid": "ce7ffa79e97b4d05ac0bf0ba6ae89eac", "value_ref_info": [ 0.0, 0.0, false ], "value": 0.9021, "factor": 0.0042, "constant": 0.9, "prop": [ "7dd82d9315524fa58cebf443a5bcef5d", "pn" ], "enabled": true } }, "inherit_d001": false, "inherit_ucp_b": false, "inherit_ucp_a": false, "inherit_default_c": false, "inherit_delta_c": false, "inherit_layer_atoms": false, "inherit_interlayer_atoms": false, "inherit_atom_relations": false, "atom_relations_ref_info": [ 0.0, 0.0, false ], "atom_relations": [ { "type": "AtomContents", "properties": { "uuid": "e68323a108644a2382ba44c813d85452", "name": "K Contents", "value_ref_info": [ 0.5, 2.0, false ], "value": 1.5, "enabled": true, "atom_contents": [ [ "e258a6fc196b4938b03cd7975b37e333", "pn", 1.0 ] ] } }, { "type": "AtomRatio", "properties": { "uuid": "af1a1d4ded974f35a41eb0ad36a548d4", "name": "Oct Fe", "value_ref_info": [ 0.0, 1.0, false ], "value": 0.125, "enabled": true, "sum": 4.0, "atom1": [ "7dd82d9315524fa58cebf443a5bcef5d", "pn" ], "atom2": [ "1fc4ed23782b480c81138ffc227a86c3", "pn" ] } } ], "layer_atoms": [ { "type": "Atom", "properties": { "uuid": "db7cf95b77914d09916b7292d82b83f9", "name": "O", "default_z": 0.6599999999999999, "pn": 6.0, "atom_type_name": "O1-" } }, { "type": "Atom", "properties": { "uuid": "cfe74e9dc1994596b7c5ed592dfef218", "name": "Si", "default_z": 0.602, "pn": 4.0, "atom_type_name": "Si2+" } }, { "type": "Atom", "properties": { "uuid": "3b18e80b066c4d789dcb2dac811a0719", "name": "O", "default_z": 0.4365, "pn": 4.0, "atom_type_name": "O1-" } }, { "type": "Atom", "properties": { "uuid": "1dcc6f542c4e4524a9c7382e34a74ed0", "name": "OH", "default_z": 0.4365, "pn": 2.0, "atom_type_name": "OH1-" } }, { "type": "Atom", "properties": { "uuid": "7dd82d9315524fa58cebf443a5bcef5d", "name": "Fe", "default_z": 0.32999999999999996, "pn": 0.5, "atom_type_name": "Fe1.5+" } }, { "type": "Atom", "properties": { "uuid": "1fc4ed23782b480c81138ffc227a86c3", "name": "Al", "default_z": 0.32999999999999996, "pn": 3.5, "atom_type_name": "Al1.5+" } }, { "type": "Atom", "properties": { "uuid": "d8c52365633f4b8394efd280376ec3f4", "name": "OH", "default_z": 0.22349999999999998, "pn": 2.0, "atom_type_name": "OH1-" } }, { "type": "Atom", "properties": { "uuid": "4f28a428e0ac440caa1f740442fb6342", "name": "O", "default_z": 0.22349999999999998, "pn": 4.0, "atom_type_name": "O1-" } }, { "type": "Atom", "properties": { "uuid": "8c89287cd3914700bd96e78f7aacf850", "name": "Si", "default_z": 0.057999999999999996, "pn": 4.0, "atom_type_name": "Si2+" } }, { "type": "Atom", "properties": { "uuid": "066400ed9d1046928e460f55d65a961e", "name": "O", "default_z": 0.0, "pn": 6.0, "atom_type_name": "O1-" } } ], "interlayer_atoms": [ { "type": "Atom", "properties": { "uuid": "e258a6fc196b4938b03cd7975b37e333", "name": "K", "default_z": 0.8230000000000001, "pn": 1.5, "atom_type_name": "K1+" } } ] } }PKfTFM++ f6bfa54f207744b494d9b567b3436ce4PKNiPyXRD-0.8.4/pyxrd/data/default components/Margarite.cmp000066400000000000000000000167371363064711000227750ustar00rootroot00000000000000PKm?DHgw== c3a4a0e6e0524ce294738e8677048184{ "type": "Component", "properties": { "uuid": "c3a4a0e6e0524ce294738e8677048184", "name": "Margarite", "d001_ref_info": [ 0.0, 5.0, false ], "d001": 0.9560000000000001, "default_c": 0.9560000000000001, "delta_c_ref_info": [ 0.0, 0.05, false ], "delta_c": 0.0, "ucp_a_ref_info": [ 0.0, 0.0, false ], "ucp_a": { "type": "UnitCellProperty", "properties": { "uuid": "767868aeffd8484ead0b6dcbeb574d6b", "value_ref_info": [ 0.0, 0.0, false ], "value": 0.5208274350000001, "factor": 0.57735, "constant": 0.0, "prop": [ "c3a4a0e6e0524ce294738e8677048184", "cell_b" ], "enabled": true } }, "ucp_b_ref_info": [ 0.0, 0.0, false ], "ucp_b": { "type": "UnitCellProperty", "properties": { "uuid": "bde910197f8c43078c450dc8dde4121f", "value_ref_info": [ 0.0, 0.0, false ], "value": 0.9021, "factor": 0.0042, "constant": 0.9, "prop": [ "6052f2a422914270b9b5ff75bc337c4c", "pn" ], "enabled": true } }, "inherit_d001": false, "inherit_ucp_b": false, "inherit_ucp_a": false, "inherit_default_c": false, "inherit_delta_c": false, "inherit_layer_atoms": false, "inherit_interlayer_atoms": false, "inherit_atom_relations": false, "atom_relations_ref_info": [ 0.0, 0.0, false ], "atom_relations": [ { "type": "AtomContents", "properties": { "uuid": "683aced3827e4e1ba81f66c1fddacb09", "name": "Ca Contents", "value_ref_info": [ 0.0, 0.0, false ], "value": 2.0, "enabled": true, "atom_contents": [ [ "ab9255366ff84c658469acb8ddd225a9", "pn", 1.0 ] ] } }, { "type": "AtomRatio", "properties": { "uuid": "0f44ed11d2e140b8b9738231391e7020", "name": "Oct Fe", "value_ref_info": [ 0.0, 0.0, false ], "value": 0.125, "enabled": true, "sum": 4.0, "atom1": [ "6052f2a422914270b9b5ff75bc337c4c", "pn" ], "atom2": [ "6c579bf11df54c508b03bde4d7162d9c", "pn" ] } } ], "layer_atoms": [ { "type": "Atom", "properties": { "uuid": "aa081827f5554e58bf1f2f4342dee96f", "name": "O", "default_z": 0.6599999999999999, "pn": 6.0, "atom_type_name": "O1-" } }, { "type": "Atom", "properties": { "uuid": "0965d4da6b5d429ebe49a9987e08fd0e", "name": "Si", "default_z": 0.602, "pn": 4.0, "atom_type_name": "Si2+" } }, { "type": "Atom", "properties": { "uuid": "b20360efb0f4435c821518db3479453a", "name": "O", "default_z": 0.4365, "pn": 4.0, "atom_type_name": "O1-" } }, { "type": "Atom", "properties": { "uuid": "b610db1fd8f348d6948c06f4631d5ebf", "name": "OH", "default_z": 0.4365, "pn": 2.0, "atom_type_name": "OH1-" } }, { "type": "Atom", "properties": { "uuid": "6052f2a422914270b9b5ff75bc337c4c", "name": "Fe", "default_z": 0.32999999999999996, "pn": 0.5, "atom_type_name": "Fe1.5+" } }, { "type": "Atom", "properties": { "uuid": "6c579bf11df54c508b03bde4d7162d9c", "name": "Al", "default_z": 0.32999999999999996, "pn": 3.5, "atom_type_name": "Al1.5+" } }, { "type": "Atom", "properties": { "uuid": "dac1236a11dd49faaab3bd59c3dcd593", "name": "OH", "default_z": 0.22349999999999998, "pn": 2.0, "atom_type_name": "OH1-" } }, { "type": "Atom", "properties": { "uuid": "b150454d1c934277b0413fa11fc10e04", "name": "O", "default_z": 0.22349999999999998, "pn": 4.0, "atom_type_name": "O1-" } }, { "type": "Atom", "properties": { "uuid": "ef9f68acb7884f1ab99f2326eb6ccc38", "name": "Si", "default_z": 0.057999999999999996, "pn": 4.0, "atom_type_name": "Si2+" } }, { "type": "Atom", "properties": { "uuid": "1f2f36cff6794a03910df2b478bea696", "name": "O", "default_z": 0.0, "pn": 6.0, "atom_type_name": "O1-" } } ], "interlayer_atoms": [ { "type": "Atom", "properties": { "uuid": "ab9255366ff84c658469acb8ddd225a9", "name": "Ca", "default_z": 0.808, "pn": 2.0, "atom_type_name": "Ca2+" } } ] } }PKm?DHgw== c3a4a0e6e0524ce294738e8677048184PKN{PyXRD-0.8.4/pyxrd/data/default components/Muscovite.cmp000066400000000000000000000020171363064711000230220ustar00rootroot00000000000000PKpoIנ4m 878298324e9e11e2b238150ae229a525X]o0}﯈xV{oQR5uZiB @RW6B؇s\X3_WMɨyU|G >_UɆ=Y'"Ӄ6!n2|(J(F7 qdq`!Gp5V/iIڞ X\Iz08XO7Ӿ X\9QE9iEtiOEwYBoH~D H q F /]AL9 /6BA,͋fw&ʳb.ΧvgLaL2o-*}kUQF("59d?IveI3"kp5e^ I3)LmK2JX`M1M2b1+$"GWq}t(C|E[E,o82!N' CՖ!,mba' `];O &!3'tһzə3Za }k Wc yCKjQ΢fOV|81vM؎7e0DiO`r?U]uϤ ,`v-9 Xu81921 %Rzg gÇc>] @XIZ-gSyDQL?R$H.=՚E܀e.MS7O%ȾDO8\y4\ Ҍ8B?Mogp}:./J-O\o!şt }u^wA4N%•3r^pED))TD(1Lz_!hO5**K-AvǸei!me'eqJ8g[cd=v;~ ϪO?0-upbiVԆϪszlz^43q]vMs7aBju v3gb;@O m` G<,4+}=^ۃtMS5~rfSY×&!>3K6=r8bJ`cN?~/Qʣig7FQrRrgL2~w# _P 2WB7hJp 1#R{c潥GI6'Mr˙)珫>(a9Ҷ2MMl[iޡԭ=qETk-pƭ3 - `E_)nS{sg#DUґ*hQQ.xVL:KYջ鶛ҷtK3Wmy洳j&#Pי)d$l֍Asyok7 }R9Τ+=EZ` XO]ͶA`TƵ 숤e$0YꈊKܓgM2 Ą #/D tf@:nBR@YTB:J ~u姴:yŦ'1Zz+]^N5]fUT~R+w B&F= ;jGXg"Jf9ᘏ;r5,6&@>1ȯYnRp :)X$P5oRyAD ɎAkF~5(=U[%ͭkZ-R]v]F9z)QLJi%,^1B'@AycvQ=DJJ卦zSxd?g/–OѦ$ڇ~y7)V,,pUa4F!T\Nt`6?id# ˌs6vZ/cO!$iĩbD0L̘di(0Ɇe>:CB?|R+Ŋii,^!u7݋q2^N}/z7 pPyXRD-0.8.4/pyxrd/data/default components/Tri-Smectite/Tri-Smectite - Ca 1WAT.cmp000066400000000000000000000025601363064711000270070ustar00rootroot00000000000000PKMTFoyy e813270a1fab444eb72818537c7f7f69YKo6|m7[`ˢEӢba1 plÖ[dˉlQ9?~3}6)0br].0/'?^lGVekVM`(g8BP#:l4w27ʢ +[R:]A@CU>uكfk|9TZqluCrY9 cMC(rI yMPo[dSw㗭εnǼ(`6uˁɁ9HBbXj@[ļV*'fo=H.hV"93Vj-\(ּS&,ҡk^3ϓ<0OϠ:rict?$QWA)# "%mib{L [72ӹѽf+w)EˉCRzL גZ(k18Y^&gﮏ:_H:+Hs{^Rro>`fc;eϫ1ղa}?PKMTFoyy e813270a1fab444eb72818537c7f7f69PKN PyXRD-0.8.4/pyxrd/data/default components/Tri-Smectite/Tri-Smectite - Ca 2GLY.cmp000066400000000000000000000032101363064711000270010ustar00rootroot00000000000000PKĒTF8- bfa5535be54542688b6064275f13955aZKo6|mcˢEEsk@G..;c'EIY^#roﮰM8jr]&?^Gn۸ǿ5}Mj5.Y!pQ&vH`J$ʍv3QQioeXW}-[R:[4ioFR<^w)ӕlq=_vYq-[1UQ|`QٙϫEdGUO8o{_uM:okΩiG AR&8rϕRb[oxhv I4m-si{H%r-h\ [k/G8squEOZ=lz%;+E+PT9@{pZHpꈣAoF!KM2smZH/Q[[ԭcԭˀ4kr"m;I57ҷHUQlY ]Āҍ^Cagע: 2 = =)xY+l e_-AR2!&il'8=h^'Jspѐ}r5'TX)hMҞD$(Y9ǔN5M$UBl&R)w$s2v!F_Sv}+ V䕽C(;x4X4kyZp[TX`E29&k{>lvܛU ;b 56`KIW_}7RN3 JȬ]^e!b ERB@;B* 6ТO Bw.sO6)A"hFa1A䰙d%675d$A~]϶'p9HƼJR*1!|$dI8såAi"3@s㸋>dVS>r:9:#GLlmB2Tv!x |"/Xk\?C~(4{cߣ~`{# (Ƨ~wz 2KК$L3X- #{pt䵽t5Y[g屯GxK³^/U:gC)zPKĒTF8- bfa5535be54542688b6064275f13955aPKN$PyXRD-0.8.4/pyxrd/data/default components/Tri-Smectite/Tri-Smectite - Ca 2WAT.cmp000066400000000000000000000026271363064711000270140ustar00rootroot00000000000000PKHTF*# 1d609ca0acd54332941f1cede6c597ebYn#7W&#p)n  rB,NȒ!8{حn-y<"{*ۇF5~:/sW#5-)oM_ӿZMc CT pI 9^5~PM+:bղɒd:Olnn| K|wZp5&khV$los̊dmOU>muM ̧9fp;\ƽ E w&aM1#W{$;n>g+*omő6$]дWќ:xtUP-k3cdMXo*?E gqG=q>‰_Q}jrnnDU)߀18RJa^ IyxuBk,2vSsnHSE^b\2cE^_ߴVMo2 $=Fې_4--'X-n̦3,G i{PV?/ySol d.ޢK`Y$vʾqMd,S4=0Jԭ)VuuCɤUsa{(};,4@Fk V'p U,sȀIF8Gwߊd$"lG:ĐA{PomliE2F`s$mҨ|/DzӞ'Uu6<9l>J7V^_M!IH\4x SŃ_АvVxTl`d>ggLFcaH*gzcN JA&v<{(ץrdWռ^H 'S:Ix s1dFB⅐G!yqzP>d\ fcijpd?Sٴb_Aʴʵ BxQ]*\z\/P>)+ 21X)FCH(IKS}:7!0]TdgHL>Cr"̹1Qc@>Ldٻ룎-^B81'ܧ>?x?If#s< ԷY3NƬֿPKHTF*# 1d609ca0acd54332941f1cede6c597ebPKN3PyXRD-0.8.4/pyxrd/data/default components/Tri-Smectite/Tri-Smectite - Ca Dehydr.cmp000066400000000000000000000024261363064711000275130ustar00rootroot00000000000000PKϒTF-t e23099fb09b3427390afb3193b6541fcXKo7W1r+\-Tdy%Hn^+{j%فݹ"oﮒLN>\Mnwye5j;X \X3^^c#q`bp'[C͢5,ޡ ~oa (<-{7U 6v-vȗ}l2dtg]ϪUv"{.qW ZbjOw6-%*g߷DY1S'D@VFBN^:Ancgk=Vhv I4]/{ihErrU_Ө̞pnp&΋u3oZmv6$r/JQ(Es>7AQ$F$)*!#j?AG)(i^ruQO.jP[ *!d@MYVՐZQV[+$lUKjX\8ҵmVәjGBiD]`sATڠRY̍[g#D-LQK>[ҟ1j GϚ(v*8i5K:a~19040GiwRRց41bvK?~fo#E= $R J10R{{@|4i|ȻZ% a7,+zd!bat 䄁Z c8s9t,s}6/f)#1!DY$і2J֧6oࢤs15&xEA*oEjX?m1Ia p A"Nr8AȤbMXNݷ+$qHx1=ҍLl ǁLH<jBpx_߶"~#xu~n-a3a+қ+ن_髃TKB$ J ?_3e"򉥒9B(n]Hz$TgBW#-5i5S4r%Vx !1kILH!) Cm q2x?tW'_FYPO(L7vs!PKϒTF-t e23099fb09b3427390afb3193b6541fcPKNPyXRD-0.8.4/pyxrd/data/default components/Tri-Smectite/Tri-Smectite - Ca Heated.cmp000066400000000000000000000024101363064711000274570ustar00rootroot00000000000000PKTTFJf 0b073bb69ba84ac883a7cceae1f652dcXKo7W6  /A=0]@^ Ҫdɖ;sEÏ<]dT z1,%ff/hYߛf|.byfzt&rēV"fQnm\o(TEEW*-.)M2wSV?Ҿ7lE=CHɭg4t*7 C^:֏-Ma1u'{6[BU%fohp;ٳǏ<!HC"N%)!nfk?z{gڐtA%Z8F+P͗5cdNڿF g9O ~ryt~FUuݯ}՞R  +,|Bm5inT+\uQh12!bB$R|1?,}o2nP )<.%J۔߫2sj~R+ʊG 3W4/wD6+/eʑotv)0JY1&22LDv3"lU=2Kҟ)jtgZ&ʡOg}Zm 1 :sg w3Eo3ȈJFwk7~wGGg3W[@Za" 'g)rŁqPF2K>ћ&-<7YCٰB,*zBP;$".** |䘀3{۰CcН۵D0<9=|^F5g'/2_CF@S3ʯi}nSt $i-*r րF&CNEq<\G"DNA6(!姆E$2 Zy6N7+$Qpsi*D|+-yu&$IH^>(bjhC)pxן6'ի [mA:HXG!A"~3"UP\kP2Ͻ2]X:_4䛯F5f` Co#IL `되5|ĔBiY\J P䥈`r51p*'{qz}>W#F6VO(L>@nPKTTFJf 0b073bb69ba84ac883a7cceae1f652dcPKNPyXRD-0.8.4/pyxrd/data/default goniometers/000077500000000000000000000000001363064711000205115ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/data/default goniometers/D5000 fixed slit.gon000066400000000000000000000007331363064711000237250ustar00rootroot00000000000000{ "type": "Goniometer", "properties": { "uuid": "69924b1777424b809c04ac95ddeb60b7", "radius": 21.75, "divergence": 1.0, "soller1": 6.6, "soller2": 2.0, "min_2theta": 2.0, "max_2theta": 45.019999, "steps": 2151, "wavelength": 0.179026, "has_ads": false, "ads_fact": 1.0, "ads_phase_fact": 1.0, "ads_phase_shift": 0.0, "ads_const": 0.0 } }PyXRD-0.8.4/pyxrd/data/default goniometers/D8 ECO Lynxeye XE.gon000066400000000000000000000012521363064711000237730ustar00rootroot00000000000000{ "type": "Goniometer", "properties": { "uuid": "f801a737cd1b4a6ea8320a0475ac3e42", "min_2theta": 3.0, "max_2theta": 3.0, "steps": 6895, "wavelength_distribution": "[[0.157244,0.015700],[0.154432,0.334560],[0.154054,0.637230],[0.139200,0.012500]]", "has_soller1": true, "soller1": 2.5, "has_soller2": true, "soller2": 2.5, "radius": 25.0, "divergence_mode": "AUTOMATIC", "divergence": 17.0, "has_absorption_correction": false, "sample_length": 2.5, "sample_surf_density": 20.0, "absorption": 45.0, "mcr_2theta": 0 } }PyXRD-0.8.4/pyxrd/data/default goniometers/D8 ECO.gon000066400000000000000000000007251363064711000220640ustar00rootroot00000000000000{ "type": "Goniometer", "properties": { "uuid": "7d9efb0575ee4cb2868945d8b4851128", "radius": 25.0, "divergence": 1.94, "soller1": 2.5, "soller2": 3.5, "min_2theta": 3.0, "max_2theta": 45.0, "steps": 2500, "wavelength": 0.154056, "has_ads": true, "ads_fact": 1.0, "ads_phase_fact": 1.0, "ads_phase_shift": 0.0, "ads_const": 0.0 } }PyXRD-0.8.4/pyxrd/data/default goniometers/Default.gon000066400000000000000000000007441363064711000226070ustar00rootroot00000000000000{ "type": "goniometer.models/Goniometer", "properties": { "soller1": 2.3, "soller2": 2.3, "lambda": 0.154056, "radius": 24.0, "divergence": 0.5, "min_2theta": 3.0, "max_2theta": 45.0, "steps": 2500, "has_ads": false, "ads_fact": 1.0, "ads_phase_fact": 1.0, "ads_phase_shift": 0.0, "ads_const": 0.0, "uuid": "e93e4084b63311e19e5e782bcbaf1941" } } PyXRD-0.8.4/pyxrd/data/default goniometers/Philips X'PERT PW3710 Cu.gon000066400000000000000000000011621363064711000247320ustar00rootroot00000000000000{ "type": "Goniometer", "properties": { "uuid": "55badd29548a4cd0a0bf7ed99126d1b5", "min_2theta": 2.0, "max_2theta": 52.0, "steps": 2500, "wavelength_distribution": "[[0.154056,1.000000]]", "has_soller1": true, "soller1": 2.3, "has_soller2": true, "soller2": 2.3, "radius": 17.3, "divergence_mode": "AUTOMATIC", "divergence": 12.0, "has_absorption_correction": false, "sample_length": 1.25, "sample_surf_density": 20.0, "absorption": 45.0, "mcr_2theta": 0.0 } }PyXRD-0.8.4/pyxrd/data/default goniometers/Sybilla.gon000066400000000000000000000007311363064711000226160ustar00rootroot00000000000000{ "type": "Goniometer", "properties": { "uuid": "c7dd65f56b61487381466bdd8d33788c", "radius": 17.3, "divergence": 0.5, "soller1": 2.3, "soller2": 2.3, "min_2theta": 1.975, "max_2theta": 52.025, "steps": 1001, "wavelength": 0.154178, "has_ads": false, "ads_fact": 1.0, "ads_phase_fact": 1.0, "ads_phase_shift": 0.0, "ads_const": 0.0 } }PyXRD-0.8.4/pyxrd/data/default wavelength distributions/000077500000000000000000000000001363064711000232055ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/data/default wavelength distributions/Co.wld000066400000000000000000000000361363064711000242550ustar00rootroot00000000000000Wavelength,Factor 0.179026,1.0PyXRD-0.8.4/pyxrd/data/default wavelength distributions/Cr.wld000066400000000000000000000000371363064711000242610ustar00rootroot00000000000000Wavelength,Factor 0.229100, 1.0PyXRD-0.8.4/pyxrd/data/default wavelength distributions/Cu LynxEye XE.wld000066400000000000000000000001511363064711000261340ustar00rootroot00000000000000Wavelength, Factor 0.1572438221, 0.0157 0.1544322932, 0.33456 0.1540537072, 0.63723 0.1391999691, 0.0125 PyXRD-0.8.4/pyxrd/data/default wavelength distributions/Cu.wld000066400000000000000000000000511363064711000242600ustar00rootroot00000000000000Wavelength, Factor 0.15418000,1.00000000 PyXRD-0.8.4/pyxrd/data/default wavelength distributions/Mo.wld000066400000000000000000000000371363064711000242700ustar00rootroot00000000000000Wavelength,Factor 0.071073, 1.0PyXRD-0.8.4/pyxrd/data/icons/000077500000000000000000000000001363064711000156645ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/data/icons/000-glass.png000066400000000000000000000025161363064711000200040ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons IDATxk 0P H@N$ *ܖ Bh2< C aͬ5,LXX5tƯvȄ6e@gS4}Տ-t pPA#w+6 H} lR>@F¶Y@#9> Bb&hE 5iBh(bQҞ]<߾a,s3EBfw+jh&aGt{ 0IENDB`PyXRD-0.8.4/pyxrd/data/icons/001-leaf.png000066400000000000000000000026231363064711000176020ustar00rootroot00000000000000PNG  IHDR++]tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons Kp;IDATxڴ푂0ɍpv,,;*w77CU; >LTscZ,雴߽hϻFґ4W;A* Ap/2@F/>%Y (rX5x!GZ}%R0wRS=[ -h"uǥnQ[\S@p8֧ybaye,M{!𐝦FE\1fOx>)۴WȻ_p-@8O6jSx4eF* iVXxIENDB`PyXRD-0.8.4/pyxrd/data/icons/002-dog.png000066400000000000000000000025361363064711000174500ustar00rootroot00000000000000PNG  IHDR/>`tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons IDATx ݠn#t:#t7rH.xG/yу>RZ=BߎiUt(Ɍˑ @ha&d@ /ѪN28SH#g#9M :)dz~-q ;+x 6rm@@.cKu{_og:S(``A]t*p+hY)tr?( 40_<%GebIENDB`PyXRD-0.8.4/pyxrd/data/icons/003-user.png000066400000000000000000000024031363064711000176470ustar00rootroot00000000000000PNG  IHDR+vtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons EIDATx씋 0 D'pG(nQ#8N UP<8PJi&yr$ͳ'߁G ]qQDV(7_wFPk239$cd nfhQK5H5y8 ݻ%ZBSK{?[6%4 0'QIENDB`PyXRD-0.8.4/pyxrd/data/icons/004-girl.png000066400000000000000000000024731363064711000176360ustar00rootroot00000000000000PNG  IHDRĴl;tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons IDATxQ ! I@I@IIA ,%) (e/k\Jc=A(PDAǨU@=ֺu K!Lcxz[0?ZNj04+՘70ĉw@N`fCoΞIENDB`PyXRD-0.8.4/pyxrd/data/icons/005-car.png000066400000000000000000000025041363064711000174420ustar00rootroot00000000000000PNG  IHDRވtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons FIDATxb?2 O!.0*X f0( +i{X j( Kzv"(}|p,T]p.c3 KaA>F f,|bA@v0 FF@9@_eZ8HEj@W@F3cLnq4,<ˢGXa&ZYQA.F7 +nmkIENDB`PyXRD-0.8.4/pyxrd/data/icons/006-user-add.png000066400000000000000000000024671363064711000204120ustar00rootroot00000000000000PNG  IHDRӖtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons IDATx E!꽌Qݠ݀Q2#d%"! PK~ ;+4(Dߚ@m_ &d0,Mi?/Ġ.Rx-vm'@eUH.01_բAjS+Rx)a:WI^I9u0ayݼўa mZֱURҠW7IENDB`PyXRD-0.8.4/pyxrd/data/icons/007-user-remove.png000066400000000000000000000024261363064711000211530ustar00rootroot00000000000000PNG  IHDRވtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons @LIDATx앁 E:0#88#$hm<M~,|KVdIɿ1 @VY."b8ւlD.1u{k31ӤFSTznҶ}pF~)u <9 H سpRMBƓƝ4i<ĵ_b^\dUIENDB`PyXRD-0.8.4/pyxrd/data/icons/008-film.png000066400000000000000000000022721363064711000176310ustar00rootroot00000000000000PNG  IHDRv@tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons D5bIDATxb```X0BT, 0FFFE~$ &$WR͠1`4Q,K=GˊѲb4dY Ċ@l@esgSplhIENDB`PyXRD-0.8.4/pyxrd/data/icons/009-magic.png000066400000000000000000000026041363064711000177620ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons жo,IDATxڤU  S$  HJ aJvaGr^2!QFZ5AlٕeY"p=)HF&#-)P2G,wSb#H)lŵ̉ "*9I(A8eRR ,LxfM=ۛEL;UqJZYO6K#'*V^K6l'#8tj9ͩ,4ߊliu;D{A_(F<殎^`axC>"o@IENDB`PyXRD-0.8.4/pyxrd/data/icons/010-envelope.png000066400000000000000000000023241363064711000205060ustar00rootroot00000000000000PNG  IHDR $tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons Th|IDATxڼT Ҟ/%m Tbc:<` 5e }m$p>+`y#0+kw"XN$U 5VΊlw&fӑ˥+\㞬_>`1ߙ&ܔIENDB`PyXRD-0.8.4/pyxrd/data/icons/011-camera.png000066400000000000000000000024231363064711000201220ustar00rootroot00000000000000PNG  IHDR@-tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons |JIDATxb?. @ 5x"`",dY $X x2 %OASK1B4JoB$ #X(E xRS,Zdh4opjXd \ y$y0QUH"FFFaH/@S @M K؂L[}`^o@0IENDB`PyXRD-0.8.4/pyxrd/data/icons/015-print.png000066400000000000000000000024171363064711000200350ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons V4IDATxU 0:8#8B7xOjKF8$ r#ǓDP D@|L@0`6Wms.2F 7,5X`s:"PmR;\n @jFŗc '*;Ca󥅛:izd ==IENDB`PyXRD-0.8.4/pyxrd/data/icons/016-bin.png000066400000000000000000000023501363064711000174460ustar00rootroot00000000000000PNG  IHDRaڟ`tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons oIDATxb?6  @n022 xP#6q]#в30d;H3?uG&*BO!'B1G ̆brİ&1ˣ8j V8R 6D XP`7kRIENDB`PyXRD-0.8.4/pyxrd/data/icons/020-home.png000066400000000000000000000023351363064711000176240ustar00rootroot00000000000000PNG  IHDRF'tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons %RRIDATx P}h.{PC9=C3mR9)"˘`b cƴ!g1 G ꂞPzCOJwo.@;5;kWIENDB`PyXRD-0.8.4/pyxrd/data/icons/021-snowflake.png000066400000000000000000000026311363064711000206650ustar00rootroot00000000000000PNG  IHDRr ߔtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons adAIDATxV0 vF8n#t0AmR/z#%.ԭ9/g}r5,K!b3H!D 0F8mr@ 6uBTR glyphicons 'MbNIDATxڬU vn`GnP7 :B] {'BxMBx(k1? +Je(_H 6T ]5ԏL~H1] +?Қ!u|&fh ~2%08.|E vCu>H0>YBF&G":G:'f &jCF ЙZ[)jɭmΈFӋtj dz E?%(GɝH;B:A@!RJ8#3y tN6q-x EFIENDB`PyXRD-0.8.4/pyxrd/data/icons/023-magnet.png000066400000000000000000000024441363064711000201530ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons S-AIDATxԔa *@$  HJ@$T$ a(;{HGlz ,D,NM`1OO5@4jlT{PB})>UO㉊ЛƟVVz(y :&H\!? Byd/$5?˗`ep@w&k+6EyXF%p^\S\7 w2IENDB`PyXRD-0.8.4/pyxrd/data/icons/024-parents.png000066400000000000000000000025521363064711000203550ustar00rootroot00000000000000PNG  IHDR YDtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ŔIDATxUQ -SI$T$TB* 0 LAzn{@ 1A WIq&3Cb߂p|+Ѝb7IDXwB?ڊVPA"TJiĒӏ_yt%a"v.-uMhRLR^dPw:a/r17}5b glyphicons TJkIDATxU /n Q2 ݈ R )!z҉3~mJ֚ #cvK Zԩ5{/@\D$t%yzi޸2`e5~`@y2%]r -K.e l:^ K!)8.&`i̠dI*? fZ'k6:  pv &QTNIENDB`PyXRD-0.8.4/pyxrd/data/icons/027-search.png000066400000000000000000000026151363064711000201510ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ΁#5IDATxڴVQ0 mQ0 0 8L &8$Lp0:n<6F$/yMSlZϞӕ)n2c5ȎxB_ W$^c&9HDp4x# 1[K8ϝ<(Q+-d1+h^l[S*tG!x]$Ss賃9L?ޙ'TaglHQKUi -EgH>k:_D缁Bm*TU _ZL2nI[S q8r&g V 15˭bu/7YD’^&4V~5?aW5IENDB`PyXRD-0.8.4/pyxrd/data/icons/028-cars.png000066400000000000000000000027661363064711000176440ustar00rootroot00000000000000PNG  IHDR$YtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ϕ;IDATxV;N@#: \-H$NGI$.(8:MhMƚ in!D 9 GD1JI,84a' Gбiyb bQ1atP7s@S`G^*ci} cM+SN2j1u>t$_'Ɂ ґ"`1=-s̵qX[!+%~9hrj9"ZZR|%sR7&85\W/jZPCloP{D+0>ۺpfBpLh LKv\iO3Xi$OcHA;Ad !10DPgq} 0Ts.e'IENDB`PyXRD-0.8.4/pyxrd/data/icons/029-notes-2.png000066400000000000000000000023471363064711000201770ustar00rootroot00000000000000PNG  IHDR|߯tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 'IDATxUA pScV, V D4#0Q,' 4ND ibm#Jgnɂs(#gZ'' kvg:٣bpd鱙~u[ZpvC뗰[^.aIENDB`PyXRD-0.8.4/pyxrd/data/icons/030-pencil.png000066400000000000000000000024441363064711000201500ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons IDATxڴ E:#08B7#tFq<,ҫ%|sX1{ pJ:´3d}/!f>k'e \\bq x.~HLgpg~IP K(@OH ?h$ X+ເ[/V x*xJU"Uņ}a hw]rYLTIENDB`PyXRD-0.8.4/pyxrd/data/icons/034-old-man.png000066400000000000000000000024751363064711000202350ustar00rootroot00000000000000PNG  IHDR|futEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons }IDATx̕ 0 .а#0BG`F(aF(ـd`KOC<@ҩbURJF*x @]Yg8LjHkYuJOfkE[++)˷řE/C=+'d$Лu8R%BiA[MD4rk5@?>$wBx/Ar7lKlM IENDB`PyXRD-0.8.4/pyxrd/data/icons/035-woman.png000066400000000000000000000024561363064711000200270ustar00rootroot00000000000000PNG  IHDRĴl;tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons NݿIDATxڴ Mp#8#0#8#0 )Ɛk/ygHG4ӳ@GM4{ H`v$m80 ˶=[;zm1 vz8jQ@ ; ͭ\(cX2E$ռ[YLڼP([l{n W 0FP0 E;~IENDB`PyXRD-0.8.4/pyxrd/data/icons/036-file.png000066400000000000000000000022751363064711000176250ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons eIDATxb```@ht@AHf %aHa(R0, 䁏@̏ďgdd$0&+AI@E0jبaFadG<1qIENDB`PyXRD-0.8.4/pyxrd/data/icons/037-coins.png000066400000000000000000000030001363064711000200050ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons NIDATxڬVq0 ęl6(4#tF0@'p̩@w.1V|c,<4s2>+E|\߬Z#"h9P|_ڲMR#P#SxbZJQQTiaj̳ >RfZ#T tkD1 5]H}cQG"iOKkDYYS]B:f[3[obPKƩ6/ط+hi|2{yza{ 7D$F ; OjS}hIIl+COɜ}J1Ji]}9eX װ#:Kgb5$NzH-Z" p}WV]srz" 'p}J 0IENDB`PyXRD-0.8.4/pyxrd/data/icons/040-stats.png000066400000000000000000000027011363064711000200310ustar00rootroot00000000000000PNG  IHDR/>`tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons .iIDATxڼNAg E0 P 8O*HB C* gIY,HIf0YnI~vgg="OFn1eo~#Е=pk='%tt62iS眛H47` AU$v29 8%ks $%LdG)Z^5}m1h'l3~c~+SE_dEXU~6=%eC#0PLS~Fr!%Դ#n+xQe3uKrϖnyIENDB`PyXRD-0.8.4/pyxrd/data/icons/041-charts.png000066400000000000000000000023021363064711000201550ustar00rootroot00000000000000PNG  IHDR @tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons '[jIDATxb?9G4K ,4&:QF-g PJEheg'gh S˨?h J@9@\]9' >IENDB`PyXRD-0.8.4/pyxrd/data/icons/042-pie-chart.png000066400000000000000000000026631363064711000205600ustar00rootroot00000000000000PNG  IHDR/>`tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons <[IDATxڴN0c Q0H 5}1De¬8ͻn|zBX=FHR c%Y aO g<Fx)ѭlחgSUsL glyphicons ᏟCIDATxU ~G`GpGq7`7<$}?&(sk#9"ȘȀ K@X%dl"m$ƢP)O}jFVM*}^MfͭA{mepM{4 KcN o/8NĒoJ0TfMt^X/0L1:x7ff h\GrGb+!!emV@X"58bLYHE$/d%܁F*3g͙?bofIENDB`PyXRD-0.8.4/pyxrd/data/icons/044-keys.png000066400000000000000000000027101363064711000176520ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 8pIDATxڬUm ellPp# 2evAߏ@&LdT@r~Rj ԯcjb^[!i9a5̀g|ri]+IrNebC9S$^gQޓF\}Ǹ K r/$$: H;$;X&f%!Vv ISgogn>drnb{NFV{PL. κBBk$EPa$pL&!]7gOxab?;l/cXrsŀM T= \GT73hp~Y`>a@*K sw*Ԫ8նxIENDB`PyXRD-0.8.4/pyxrd/data/icons/048-dislikes.png000066400000000000000000000026161363064711000205170ustar00rootroot00000000000000PNG  IHDRVtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 26IDATxڴ #8# N@%j#1go8e0`Bsچ7zV*Y \d Td˭*՝0H0&F#肹 5)swrX,n|&Kr ;RLJW'.̼OI+V0?Bt`[ޛ>RܫL;>)7s&5# ̽[XI$MC|L' E ~4$ XjD?!+rG K2=@+'#J|`(3IENDB`PyXRD-0.8.4/pyxrd/data/icons/049-star.png000066400000000000000000000025311363064711000176560ustar00rootroot00000000000000PNG  IHDRVtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons IDATxڴ  * 0 H$L&*P܅[Q]4/Axba,@ 64_g dL_p%RE75+Ub5`ˎ[玄UBg! TK&2s6weP4B;$Ha=AkϐPe+"Pd'|R .9/߷aA~IENDB`PyXRD-0.8.4/pyxrd/data/icons/050-link.png000066400000000000000000000026021363064711000176310ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 4N*IDATxڬ 0J$ sJ@B H@:\rk c˒z;3iBhu? 5F-UPhNa靽 L<hK_>s@j`S t+hyGfyr>HP@P}4,DE>)^=l|2%h Z maÖy&{&MWr4}69xF3VLM1ur2+጖ +1T%?1`9s*Zvn~Ly;lՔ`=^^KIENDB`PyXRD-0.8.4/pyxrd/data/icons/051-eye-open.png000066400000000000000000000026511363064711000204220ustar00rootroot00000000000000PNG  IHDR%wItEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons QIDATxڬU l$ NB%TB%  $  glyphicons XIDATxڼVm0 S p#0#dn R,m$#M ka[5 <`]VS6S3+po0WTB.EПF#r"8jY k| `QR hL:cVH5T*p_z^a*=†2A(Nh_$;x 9d*7{ac*%YS XX]VI8̓oRbҜs:5Aر-S!p(΂鿕S$ȍg=b{eiK4oH'@FT_^Ў>|n]Zn+96ki'Fz_:o{IENDB`PyXRD-0.8.4/pyxrd/data/icons/053-alarm.png000066400000000000000000000027251363064711000200010ustar00rootroot00000000000000PNG  IHDRU^tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons +eF}IDATxڬVq ,P6G`Fp6( #8PѓSUD8%<$$9 0< !"Q3x'~''#`dAmuGVł`P9ԕ|LDO $Egy h `g%Җ),6Ob3VKԏ)dBY@&mx?? "[-vQ܀^fZ:*N>5_#, ckdԈ=d(hR3'# kG#TihzB( pBd7Y80;}`ݥ@/@@K_ԨJ}8Z{]5y"v{X~:B m$#zc{V#ˏ跑ßdJU<-t IENDB`PyXRD-0.8.4/pyxrd/data/icons/054-clock.png000066400000000000000000000025401363064711000177740ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons hgIDATxڼV ;BG`P7 npnw1%w}/$! 2$JHkC`p9< \7R/~Nh9ȉˈLT RK׎r<<=G2GM '+ĽOLTs6&0Nю@2l^8SQYKru*2m:hyоiJ? u ˕oSTE$IENDB`PyXRD-0.8.4/pyxrd/data/icons/055-stopwatch.png000066400000000000000000000027161363064711000207230ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons >vIDATxڴVq 5#xF]J< ?83~Q2?+oCc@<ЮeU-z`s"zP{Z:6CVe~Ee9xkVNB-RzTf{n`7c6Q vwvI$}aXNW9K'FGbl<.܍.+$Ϋn'7;E`l. %X[JOC,9̢2CI1ū,_ONWŚ~<';MD'.[1\ M7N .8]$x hSKjMޓIENDB`PyXRD-0.8.4/pyxrd/data/icons/056-projector.png000066400000000000000000000026751363064711000207230ustar00rootroot00000000000000PNG  IHDR"N'tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons WeIDATxU=N0;vcǎlUG6ppN@Yh:r:dsbNȏmOz_ DVR5|j6RWȇ [JsE>@)۔swd`1i`-8WV9Ӡ-E}6:?v扼eRbv)9X(ݢ?vH|gS~:5۔J7:_A5$D< m~oK-OjjJSEW81R =J]90U٥45. }Td7_Rti{h8aan|핻hX+yLj*k 0[6RdIENDB`PyXRD-0.8.4/pyxrd/data/icons/057-history.png000066400000000000000000000027231363064711000204100ustar00rootroot00000000000000PNG  IHDRB}tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons G{IDATxڬVё ?;%K@ @ ؁YY̼ITط˃j]צ) \ $` vgX[c'xB|,E`*s, /e9PojMD|(ԍkx#! N@@t$Uס@7@hdzI `͊ /oae-E_V&|pQ[o"͜fa^? W„u}5' t]?LU.̄_I}bkp &6*b͙[;w wE\V8x, r g*H- e:Sm֞p!/Do:", #3 ‰?1TVi/'8IENDB`PyXRD-0.8.4/pyxrd/data/icons/062-paperclip.png000066400000000000000000000025571363064711000206670ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons #c$IDATxڴ  D `#F @>7'wd 'T+ߔrD|VNjo?DO +,d% (L"ȾBTfo4/n{A m#oN>cQ6/> $eQ  ?dQp4@ģ֑)tk@ɢjC"hŹ?BAzSD= $SK撫@;)zrAD ujwjW<_E 71ܖ 0g!IENDB`PyXRD-0.8.4/pyxrd/data/icons/063-power.png000066400000000000000000000025561363064711000200440ustar00rootroot00000000000000PNG  IHDR KtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ;>IDATxUm S$LILNI \wֵe\/PJsZ U3ƴjG 'Zul^—K0~ glyphicons ?u&IDATxڬUK@ej@(""nf :]{$.Nup念b.%.N+$_rwuDK!g$R1`F]((!2C("k@H%Zk-XqY,b|d]6b5Ko'cSN|Zasv=H&M)krg2Z:{+e%S%I8Qk'|LkSY1xZ 1/y2`&O p4T=TŽUȤtxRT@e縡cr&-vUt 3*WB@ֽ@?Fei)*P5gRBǤΛү{&] ec<#l(6τ9q=z-VcM֯K*NIENDB`PyXRD-0.8.4/pyxrd/data/icons/065-tag.png000066400000000000000000000024241363064711000174570ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons FIDATxڴ bLGrFpFp6Qlk1# c!6\p6q=p3@~MK(Y"L$B" dx--ST zi#Kn5j fok r@^ddY@) R-4I3㽯_[cIENDB`PyXRD-0.8.4/pyxrd/data/icons/066-tags.png000066400000000000000000000004401363064711000176370ustar00rootroot00000000000000PNG  IHDR;0bKGD pHYs  tIME/wIDATH 0 N:ZG(ݠ#xzq$]!Hd`FS\Frb䍗͵i\& Q_3ow߃TKo[BWgӨ7C -< glyphicons 7IDATxb```ľF-z1B"uqF `زt#@m-AIIENDB`PyXRD-0.8.4/pyxrd/data/icons/070-umbrella.png000066400000000000000000000025641363064711000205100ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons . NIDATx KH sKsL ` ),Dv 2b$;EEcBtx&oH+1 7lIO`9 !I]$U(,O⼛O ){:@ X() 6"Y[%YQ*R?2SzDKȂi AYox glyphicons WIDATxb? sQP5/@ H\`P4 ̢XT3l8`#tJ`Y `hF 5xQ UlG*@i h zj 1@11IENDB`PyXRD-0.8.4/pyxrd/data/icons/072-bookmark.png000066400000000000000000000022721363064711000205100ustar00rootroot00000000000000PNG  IHDR%itEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ʙ\ bIDATxb``` Qͣf~y HGqY#4" 5iab5P` :LIENDB`PyXRD-0.8.4/pyxrd/data/icons/074-cup.png000066400000000000000000000025301363064711000174710ustar00rootroot00000000000000PNG  IHDR KtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons IDATxԕ EK'pG`G`QnX&A];qSJBpN1ZEq١Ŵ{N7, 6G; sݲ.``v%hf h,hiALHGd!(i*Z2n )NW8chP=]ݩH]Uk)ɂOJ)ͷϊIENDB`PyXRD-0.8.4/pyxrd/data/icons/078-warning-sign.png000066400000000000000000000025771363064711000213240ustar00rootroot00000000000000PNG  IHDR^tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 'IDATxڼ 0 9 J@P H@B% 99`.u!ܭ)urb~E/p)@t[U^8=S䄁 1`CPꇡxf*QiN3kws_8a[Dk@1Pqj(Dr M8U/C ;(V&JcԭFT)3S,, Sbd B^jSp wI IpB녓Є/hV@EsIؘ-% ;ԨM Y#IENDB`PyXRD-0.8.4/pyxrd/data/icons/081-refresh.png000066400000000000000000000026451363064711000203450ustar00rootroot00000000000000PNG  IHDRB}tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons [>MIDATxڼV0 , 2##P6`hҘ֢ܽ;4/ZeRRIBkm5Ŀ\oK}@ߎ/$6;%53~"yL0 Į_f"TЌ<NlF -k9J?;$Bm A,ltJDNՆ;0z%seu9, Hy|X n!U2gucV?:O4ѳ1K'ܛ{Uek?E!b5׮#r['XsC2EÝzf!n+^$-ۂ^Ta}pRψHt(n >IJIENDB`PyXRD-0.8.4/pyxrd/data/icons/082-roundabout.png000066400000000000000000000027241363064711000210700ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 7|IDATxڴq0 efnݠ#0#d809SAbK% 9bAfdƹ>ecEzuxLZ] 2DU`+G뿛1`J<<^vsX)> %{J] h(*:oP A] f = Hɻ0uo.|BH֓b'-#w|.#5O0B6{# xT=%62W0>ȼ2ji\a%, $xpS &@/ar #m"YҘ+ zؘG6ADPD3P$s"42Ͱ2pI`!IENDB`PyXRD-0.8.4/pyxrd/data/icons/085-repeat.png000066400000000000000000000026151363064711000201700ustar00rootroot00000000000000PNG  IHDR\~tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons >5IDATxڴV -^u2#tnn`'I/5}ჼ Z{ Tc(=[ 0jo} 7ohC߃:RJP1$H'sQ ׀/-7K5[ ĹL3ѱJ$<8 R-"Ϩ.֌HZA "N~Xdtu\hirs$+\rȳk 4ɽL˸aZh쀤mp,45[ Y'67J6zƳiчHԖG>x($0i鉤CHY]IENDB`PyXRD-0.8.4/pyxrd/data/icons/086-display.png000066400000000000000000000023151363064711000203530ustar00rootroot00000000000000PNG  IHDR霝tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons C]uIDATxb```X0BtخM `Q G-pBLJgDp!̓ TReĈ _0 as At$͇ot+[OIENDB`PyXRD-0.8.4/pyxrd/data/icons/087-log-book.png000066400000000000000000000024521363064711000204220ustar00rootroot00000000000000PNG  IHDR KtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons `qIDATxԔ ! 0#88#(ɹ#856^;(9g HByPʤ{Z"<fᚾxqr3qWobrsv9u3HxTb>.TG%Պy׽^XiQlϚԶ!ɓwcut*b nVˊUlVfڈ}3EvAo-^ۙ' :Cv^FiIENDB`PyXRD-0.8.4/pyxrd/data/icons/088-adress-book.png000066400000000000000000000024601363064711000211220ustar00rootroot00000000000000PNG  IHDR KtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons vIDATx̕ EMp#(7\!4i<5(>ic$;*@ֲu]Mi4 c"W;רq`)R\nvD,pީƟ?ΊMklR柿&xk0"aIENDB`PyXRD-0.8.4/pyxrd/data/icons/090-eyedropper.png000066400000000000000000000007571363064711000210670ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME;#_|IDATHֿKVQOCD`X[RA46(TKm"(AD8<.<>pk9﹜7.2lc= އՀĘ~USډ_/+PK +>kV0X~%Ibְ*1lx#,0M%|ua* BZz.F$7Jq1-]Y@" pӸ?<]8xލ SshIݎGp /u5!IENDB`PyXRD-0.8.4/pyxrd/data/icons/091-adjust.png000066400000000000000000000025031363064711000201730ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons Y\IDATxڼ k'?ʏ:#0B7jS!!M BS(th=49H59LG xFW} NM6K(8qQm,ٚ*!;34v򷑵_$VvKL{DS޴|xU6 CmAg6Vi e3v\(X{9B->$6TEׄ]墏Q-%<Tfq9IENDB`PyXRD-0.8.4/pyxrd/data/icons/092-tint.png000066400000000000000000000026471363064711000176710ustar00rootroot00000000000000PNG  IHDR++]tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons lDOIDATxڬV 0 MK`PWP0PP!9y>{qO}ӚsۙK)q3,Zc*cc΀J#IX5WhU@.dW.--ʼb!*1)V͊v1t]W\㝌Ő@ Śe qwͬH@$Z,&ƧK؍W…o glyphicons =LIDATxܖ E[㿫覎H:A- "P{ڴ$"Jjj6 glyphicons 85oIDATxڜU1v 5cxVݚcFƌ>B#7pocƌ֍~RR3 /UN!-Ԓj:CQ)ು6oB-`%|4g&2Lř9013ϮdLC`M/DSX+td>r{;؞LDP{OA 8mߞ9]1.phye|2J{ɍ2JKnTd%EH{mDwl$ЖpkuU.f]WTF@M9+R0#l1bl/8gi6)\fl[83a5Zcoڡ0?c{B%^i{˾xҬIENDB`PyXRD-0.8.4/pyxrd/data/icons/101-italic.png000066400000000000000000000024321363064711000201370ustar00rootroot00000000000000PNG  IHDRWtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons IDATxb?:`ddD  GrX5B57iZ,τ9 h(<<6^@QY[` @uL84 ]@WˏTj62r> =>kTΤFB!J( Pzݶ9#j$l#0`<P@X7`+!x(0PǛ녕yIENDB`PyXRD-0.8.4/pyxrd/data/icons/102-bold.png000066400000000000000000000025001363064711000176070ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons zIDATxڬa 0 S$LN$  H8 H@$6]6ֆteب5"" )Q6aKL&]"=^~A]c & 3̽o,Ǟ 撍p\ @S *CPclaB'M(2|a+r?ka+k6aL Dz~HO-̝sX)',ib?IENDB`PyXRD-0.8.4/pyxrd/data/icons/103-text-underline.png000066400000000000000000000023311363064711000216410ustar00rootroot00000000000000PNG  IHDRKvtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 3IDATxbd``@E@ I;@@ CWHH¼| >5'`ddDQTHH5pQ!,rrXb+mK%3`5a8k=Nd`IENDB`PyXRD-0.8.4/pyxrd/data/icons/104-text-strike.png000066400000000000000000000023211363064711000211550ustar00rootroot00000000000000PNG  IHDRAtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons JREyIDATxbd``@E@ I;@@ CWHH¼| >5'`ddDQTHH22lF ^/X>@Ng݁%h8 IENDB`PyXRD-0.8.4/pyxrd/data/icons/105-text-height.png000066400000000000000000000024101363064711000211240ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons &IDATxV N(aMR&M)zZ !ٸL+a"%,| Y.l,|7-X9b$PID".M/H9HTjшϊ;N2 D ~İ[0["^gNŖZR KƭS׺dIENDB`PyXRD-0.8.4/pyxrd/data/icons/106-text-width.png000066400000000000000000000024201363064711000207750ustar00rootroot00000000000000PNG  IHDR KtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons >?IDATxU N0o7Q[SL_C?\4=\! 5ap :Q"בt<+¾5_N K_nΞ+]^]Ua"B y S AM s ~# KW!@>0 QsAёkm`oe3p7IENDB`PyXRD-0.8.4/pyxrd/data/icons/107-text-resize.png000066400000000000000000000024111363064711000211600ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons h("IDATxV 0cAOL(RG; ن@FD~VGJ-r*;Hc(U&4lO-h"Ti=c4bƚK.2<ݰwnsiG뮧cIENDB`PyXRD-0.8.4/pyxrd/data/icons/108-left-indent.png000066400000000000000000000024001363064711000211050ustar00rootroot00000000000000PNG  IHDR}\tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons &K:IDATxT Ŀ(茂֨!*UH|xEJsw_R 32 w$ 5sφ glyphicons OIDATxT Q&E!D"|I.QDJ HŌC6RerS\+5[EXL2ROY,<΄-Ψ\=r%?bjd|p7x<bG:_y" w`\6IENDB`PyXRD-0.8.4/pyxrd/data/icons/110-align-left.png000066400000000000000000000023201363064711000207100ustar00rootroot00000000000000PNG  IHDR\.&tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons &xIDATxQ CS/z@B/#>MOA6XLJ5n<}^LKJM٦7jNhMYEL)ˢl'd? PmIENDB`PyXRD-0.8.4/pyxrd/data/icons/111-align-center.png000066400000000000000000000023111363064711000212370ustar00rootroot00000000000000PNG  IHDR\.&tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ,CqIDATxb?2@L glyphicons {iIDATx101uo9)fXFfZO=qi@71Ow0u 3.Zʞ\s\ă: vPWGBx(geIENDB`PyXRD-0.8.4/pyxrd/data/icons/113-justify.png000066400000000000000000000022301363064711000203660ustar00rootroot00000000000000PNG  IHDR\.&tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons Hc@IDATxbd`` &-44#4CT4FSh*MEC!W[]IENDB`PyXRD-0.8.4/pyxrd/data/icons/114-list.png000066400000000000000000000022521363064711000176510ustar00rootroot00000000000000PNG  IHDR\.&tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons (zRIDATx Cc LC{u6 =#ef* +;U [vi^L(EH( 2Ȭ%sIENDB`PyXRD-0.8.4/pyxrd/data/icons/115-text-smaller.png000066400000000000000000000023341363064711000213210ustar00rootroot00000000000000PNG  IHDR&tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons &2IDATxS .tiT(R)Ӌa,f^φIU Gkf Po݊-41R'5IĖ~ ,k ǽP o 5'ij덏^ U~wIENDB`PyXRD-0.8.4/pyxrd/data/icons/116-text-bigger.png000066400000000000000000000023641363064711000211250ustar00rootroot00000000000000PNG  IHDR}\tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 9uIDATxbd``@EFd>@r2B4yG^ܾbl*x5`BPqFF"8y$~ @C?X,b@-*ȳx"*@TFV`˝F*:0u[u:IENDB`PyXRD-0.8.4/pyxrd/data/icons/117-embed.png000066400000000000000000000024621363064711000177600ustar00rootroot00000000000000PNG  IHDR89tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons $aIDATxڬUm )P P P 0 0 -mޏHS"bFE\XfAhD~ c`RXmw"Tk#i1!_ DcĊiIk-kax=Yk}TyQ}[믖d[NR5/"svd7F<⑘4;MK.YC~IENDB`PyXRD-0.8.4/pyxrd/data/icons/118-embed-close.png000066400000000000000000000027051363064711000210640ustar00rootroot00000000000000PNG  IHDRiMtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons /nmIDATxڬV0  <J@NNNK;WNIkf20%6&;k$;jvA >rCVx {>i9ۡ!eP5ڦ$7պ4v.}R}Tg4cR m;y!K{m1 do@'`Y)p *` b0hNF{W++c? wWmD/%Z侞Hc1=R?jjMD`ؼ5R QtdNȌL`Y mHit؅iI$%פ7KOokjEЅAno_4RiHSIENDB`PyXRD-0.8.4/pyxrd/data/icons/136-cogwheel.png000066400000000000000000000026111363064711000204760ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ]YB1IDATxU0 \Q0 0 A%L& H@sP.㲐e;r-RWH[9G79ϓ*⠐MiP1~Um6k8!, 2းdBa-u"ִh/&ET7(sNEx;WH9-tCۖh glyphicons lv@IDATxڼU ^#tG`4G#Arȇw<Btb"3+>Ey~7IM\x_7  pRDx Y?/(Z{!5IU>K`Dy䯼6JHWJ{B:Ƅ.!/.2Ɔv DQ>j؉woŎ'{IENDB`PyXRD-0.8.4/pyxrd/data/icons/139-adjust-alt.png000066400000000000000000000023521363064711000207560ustar00rootroot00000000000000PNG  IHDR|futEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons NIDATxU[ 3^|&2eS+~ (H "jDDMx5Tp\Y\eبʽհ(vO BR IENDB`PyXRD-0.8.4/pyxrd/data/icons/141-database-plus.png000066400000000000000000000025041363064711000214230ustar00rootroot00000000000000PNG  IHDRUtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons }tIDATxb?50 nE,Ai4 Y" .~h&#TLTH"CEHB,P @u}A )@iy glyphicons > IDATx q7n(#8BG`Gn@!w"ǽ3t0 1BSS̜< TNw 4;B=mxEp;V`DFR-uMQ\3raKYrDSpmwYn?g2 ˍ6*%DXЕ#[L4bekiii{ׁ#IENDB`PyXRD-0.8.4/pyxrd/data/icons/143-database-ban.png000066400000000000000000000025351363064711000212060ustar00rootroot00000000000000PNG  IHDRbktEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons EIDATxV lP7#0#tGt<+XKTD ONB$'p'XJu/# O"nX + QchUJ9С WB/%6LQwO'_eNk~YlG',v#8 H40]Jbp(IqY>Xg6y#Wlnv;Ib1 U*sIQ#$ vD2ePaet[QdW^ ! IENDB`PyXRD-0.8.4/pyxrd/data/icons/144-folder-open.png000066400000000000000000000005721363064711000211160ustar00rootroot00000000000000PNG  IHDR;0bKGD pHYs  tIME.IDATH=n@F Wl(9^ q$SSl(־ømE%3{3 *[TF`ً>k:\sc&7i"s'VTD>&mU4 *S'uإAe-mQﳪcBg{Ba ⽏ ʶЬC;)5OzJsSt>'K$ kNzvH4d[>s%hs5N,/tyiKIENDB`PyXRD-0.8.4/pyxrd/data/icons/145-folder-plus.png000066400000000000000000000024341363064711000211400ustar00rootroot00000000000000PNG  IHDR%tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ^IDATx tFpG8#t MXCj]{!5g)Qw}ZkgӚ[@MzR(|)C*-j5)!k?t {;88*@R#cOT/cŖN.h^w@thvhp͵~Z%(SdIENDB`PyXRD-0.8.4/pyxrd/data/icons/146-folder-minus.png000066400000000000000000000024221363064711000213060ustar00rootroot00000000000000PNG  IHDR%tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons IDATxb?51 nȖt a]_$n@Dt3"o'!Xar,O$󁉁: J/$Bzg P5'j]8LAPB3f*&?bL F-tQK ih>V zb%IENDB`PyXRD-0.8.4/pyxrd/data/icons/147-folder-lock.png000066400000000000000000000023671363064711000211140ustar00rootroot00000000000000PNG  IHDRT<tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons L IDATx Eţtf7(`913 eM[Q$5gE!j0因Er>+ 6TT- 7T5x G.zyu0jӡ)pk`qLq]8IENDB`PyXRD-0.8.4/pyxrd/data/icons/148-folder-flag.png000066400000000000000000000023631363064711000210720ustar00rootroot00000000000000PNG  IHDRU^tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons n9MIDATxb?###VC2 (`E `H^Ƣk QͲ@<``G!#a‚"z%t>,% v-c*@R\Z6j٨elBb6@ CIENDB`PyXRD-0.8.4/pyxrd/data/icons/149-folder-new.png000066400000000000000000000024151363064711000207510ustar00rootroot00000000000000PNG  IHDR霝tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons cQIDATx FM0#8pmP?GrCc<-YIi\R=z+'r:/P!-o/!3B`JZZ[*$\YCC7 * ,׉KLjBe /us4a6a>T8_nMZuIENDB`PyXRD-0.8.4/pyxrd/data/icons/150-edit.png000066400000000000000000000024771363064711000176340ustar00rootroot00000000000000PNG  IHDR\.&tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons Z IDATxڴ &FpGb(VzjpMh!:Dt=EH4?QIOp2)9,Q"<(h $H9n.0e5fG8zRɠ^mP 2 T'k2>\zѡh2r١Y D5>56dDZK/Si9/"tfyk ~8(gƧ&P,e6tIENDB`PyXRD-0.8.4/pyxrd/data/icons/151-new-window.png000066400000000000000000000025401363064711000207750ustar00rootroot00000000000000PNG  IHDRވtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons }4k.IDATxڼ Er0m#8#87m9(Xhڼ V`/Ѓ{H_Pa頕D? .VDRـv` Ao.A|%&ӞV(LB+]&N4"[ C 7qcm 3:R1@*Ցᛀkg {rJ(@"kۋob@ 0quSw\|%+ʜIENDB`PyXRD-0.8.4/pyxrd/data/icons/152-check.png000066400000000000000000000024221363064711000177540ustar00rootroot00000000000000PNG  IHDR_%.-tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons =z6IDATxڬQ `$T$T$ &*0YJ]dK`%%@'bԿ`NZFa [*D@9~Zta֘h9 y CizXQ ~9]0-3 q73PecﱀCpK?SS9}BKn.WOz#TE+IENDB`PyXRD-0.8.4/pyxrd/data/icons/153-unchecked.png000066400000000000000000000022631363064711000206340ustar00rootroot00000000000000PNG  IHDRVΎWtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons `M?[IDATxb? O" 6?d% @&Az`.c2@ dFFF~&*QF =d3+FUTzΜIENDB`PyXRD-0.8.4/pyxrd/data/icons/154-more-windows.png000066400000000000000000000024031363064711000213320ustar00rootroot00000000000000PNG  IHDR*ԠtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons P,IDATxUQ :Ghy@tˤޚ3^p8̠F9$dbs^R'1mƒ@D5θ*w#w,[\MX,D[^'WC4A-a6Xc66>,.'hwfcQsUIENDB`PyXRD-0.8.4/pyxrd/data/icons/155-show-big-thumbnails.png000066400000000000000000000022401363064711000225630ustar00rootroot00000000000000PNG  IHDRĴl;tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons " 7HIDATx1 0?Rwj$p8nQ6uNyMHx%~(@ 51+$@ glyphicons ilPIDATxb```30Pj1022%@#04 E#6И:A0Gh:M2$IENDB`PyXRD-0.8.4/pyxrd/data/icons/157-show-thumbnails-with-lines.png000066400000000000000000000022461363064711000241150ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ;NIDATxRA 0V#[d BXNif>^)< glyphicons ɚ;IDATxbd`` &-44#4CT4FSS@: ąRIENDB`PyXRD-0.8.4/pyxrd/data/icons/170-step-backward.png000066400000000000000000000023431363064711000214300ustar00rootroot00000000000000PNG  IHDRk\1tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons DIDATxb```Aa H`LH k F p@q@7ф MAMaIL dt@XICQt%4М~d9@HRIENDB`PyXRD-0.8.4/pyxrd/data/icons/171-fast-backward.png000066400000000000000000000024001363064711000214050ustar00rootroot00000000000000PNG  IHDR.utEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons i,IDATxb```Aa H'E-OF X@Z, @ZR%@ЀEjZ@&bX@&2qp)6LZFML tG"kd^ !/tKhX@%t)VЂjVԓSi wIENDB`PyXRD-0.8.4/pyxrd/data/icons/172-rewind.png000066400000000000000000000024051363064711000201720ustar00rootroot00000000000000PNG  IHDREtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons IDATxb?1?)jQ( 0Y-! @|3?(a8. nPp|j 'F-@bĪEN:*`R2L R!ɘ hUբE"ц4)YR@B&* pf4[p ]p*FVb> 9IENDB`PyXRD-0.8.4/pyxrd/data/icons/173-play.png000066400000000000000000000023001363064711000176420ustar00rootroot00000000000000PNG  IHDRR;^jtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons fzhIDATxb```(b3AD~r ɠJ @@DDd(1D1B(HvB";)((@mIENDB`PyXRD-0.8.4/pyxrd/data/icons/174-pause.png000066400000000000000000000022201363064711000200140ustar00rootroot00000000000000PNG  IHDR /@tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons &48IDATxb```@]ʀ"###8`TR `ȸ4IENDB`PyXRD-0.8.4/pyxrd/data/icons/175-stop.png000066400000000000000000000021741363064711000176750ustar00rootroot00000000000000PNG  IHDRH-tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons $IDATxb```I Qt@n"0L'r#IENDB`PyXRD-0.8.4/pyxrd/data/icons/176-forward.png000066400000000000000000000023761363064711000203610ustar00rootroot00000000000000PNG  IHDREtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons sѴ|IDATxb``` g IQ U 4H1/TpDW-.aq%d8o@[Rc8?b" GQKᰰM E-pF-D&ځK)j $DR"fILT0ҎĖH,>RY^tύIENDB`PyXRD-0.8.4/pyxrd/data/icons/177-fast-forward.png000066400000000000000000000023721363064711000213110ustar00rootroot00000000000000PNG  IHDR.utEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons G9AIDATxb``` g ITL#/ Rbp.IQyρ DXBZAx a !x5 6 |j j}ja601kG D-0@B_ 'i HȌXe" HR HHH wxJIENDB`PyXRD-0.8.4/pyxrd/data/icons/178-step-forward.png000066400000000000000000000023211363064711000213220ustar00rootroot00000000000000PNG  IHDRk\1tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ]yIDATxb``` g T=3@x#a V G#hr5  G#?br4,M*`b # r(K4b5> ?7RZIENDB`PyXRD-0.8.4/pyxrd/data/icons/179-eject.png000066400000000000000000000023661363064711000200110ustar00rootroot00000000000000PNG  IHDRZtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons }qTIDATxb?>xD9U2(n!HpIbHL(@/`1Ar!0A@@!0 0 6@1.0,`m K OPŀP^AdO9Fr@;&IENDB`PyXRD-0.8.4/pyxrd/data/icons/180-facetime-video.png000066400000000000000000000023141363064711000215610ustar00rootroot00000000000000PNG  IHDR $tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons =?7tIDATxb```0@ g\1x c7@k$yY hԱp' 7fB+@c@- IHK2KFUTмiq `ҋ:'IENDB`PyXRD-0.8.4/pyxrd/data/icons/181-download-alt.png000066400000000000000000000024111363064711000212640ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons WIDATxb?!H2KXc  @('@7# /p}V aZ5$Ԓ x Z 5 '$K/m@^bA-1@āX}PM4.:eF@G̯]9IENDB`PyXRD-0.8.4/pyxrd/data/icons/186-move.png000066400000000000000000000024101363064711000176510ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons OLIDATxV AQ:#A BS>6i;읭@Dk.vQ t)Up68S"{8u(Yq%-A;zqMs W%kauh[l`ճ%ܼ}z(Z gʔ.c?}ߖGh<IENDB`PyXRD-0.8.4/pyxrd/data/icons/187-more.png000066400000000000000000000022751363064711000176570ustar00rootroot00000000000000PNG  IHDRbntEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons HTeIDATxb?4(n!c K4G '<ȡ I#YPj1022%oDb#b Ei1IENDB`PyXRD-0.8.4/pyxrd/data/icons/190-circle-plus.png000066400000000000000000000025021363064711000211220ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons :IDATxڼV L9ʍ(n)*mҘ(} x@C;@ jy.]~C"t{$y5]Hr> I~dvHTRSd?Q_VIF|l9yKItmJ ߷iHcP,Z'Ʊ5[{;r U16XG9du ._-IENDB`PyXRD-0.8.4/pyxrd/data/icons/191-circle-minus.png000066400000000000000000000024441363064711000213000ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 0^IDATxڼ :hR ޝ&/&?8f^, הɔ@@=i$ ŵ#R0x /%1 E @2J/Bviu$&R *Ca汝'mG~<dT.h]0և| W F7,f*tMtC/'[u0³yIENDB`PyXRD-0.8.4/pyxrd/data/icons/192-circle-remove.png000066400000000000000000000025361363064711000214450ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons -qSIDATxڼ E'`68GpDGpF`M i\-ZK^-fq(ćĉqAteljk1KSc@`#_da(d#X.@;)֕ )Sڽ4sUqd?֟q<\-`lifsu4d`o V>[G60u(S HZ`:͠Vg UcBS=U/'O]̅ dθ`:rl;IENDB`PyXRD-0.8.4/pyxrd/data/icons/193-circle-ok.png000066400000000000000000000025331363064711000205570ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons (-IDATxڼ $ a&% L^$ a͗gZC,-ι2@$ڽXoI4AXkTV Q1t$ ekݹB ]d>9cSpA=1]c$tɳP!4frɥL4r 8q!'蕙7e9A%J5ĠY(;!` Xn%aPrZ/CaЧ|hsvK44Ĭ`ޢj!U IENDB`PyXRD-0.8.4/pyxrd/data/icons/194-circle-question-mark.png000066400000000000000000000026231363064711000227460ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons z?;IDATxڼV 2n hbcKiܽ<0d%o e@;4$#6 R6@EKDD2 Y1dn؞߈BwjJI!tgoDSh<tMft Wn*LȫUW'7`o]:C*t2,~ı"6't: _Nx=?y&6>-&֣cZz:r&F{{y ( :s1Zy~G2ucB9+~L6>NG=  ?e IENDB`PyXRD-0.8.4/pyxrd/data/icons/195-circle-info.png000066400000000000000000000025241363064711000211030ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons Q!IDATxڼV &p8(nn~xHJ +ĬdQ"*( M- 8U 9*A>#Od@r`Zo Iw6#'쇈%Ziv]U>a=tJ\E8.&SlQ&mn9"LNVso$ glyphicons řuIDATxڼV TtAu&GpnKM߯ɥDc4Հ_VpU!8 6(ɜn I?H2LpEDE3"bFU~}DOt,`S?2NFp\iW:滕hw4UtJ+-`b$݂pn؍3YLܰ:W^}L >Q8ܲZȏo glyphicons F:R*IDATxV }``z>N)=~ ;' XDlRHV%M?RJGe| Հ0VG*=Ill?D@㪴GЇ rIH트'`[#,d\;/ͬJW^*#6Ė!]EԦ$s6yELB4RNRYV]eHMxYRobx2ő4bv #qYч$ckڡt2gNn3mo+3Ć5!}qrsKI= _ i0yIENDB`PyXRD-0.8.4/pyxrd/data/icons/198-ok.png000066400000000000000000000026301363064711000173230ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons o@IDATxV l`Gn 0WW$$/M)ߕ !" @2pƈ\~!]$ "ޖ8F r4O 3H_Y]Ԓ`%^kezDLƔ 6!avUdL,Dv!nY5}-cRn+D&>#]|,y7uu;Y%EB]y^.{t[75`rkP&ތ+¦wIXb׌܂6k[|kuR[]  ,IENDB`PyXRD-0.8.4/pyxrd/data/icons/199-ban.png000066400000000000000000000026051363064711000174550ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 5`&-IDATxĖ d#t7:JG8#0nab rk .cr&7{ d[C|(l 7CiMZ͐iE26*8JJ 6}1Q-0]6wMl}qsKl| 0rEMIENDB`PyXRD-0.8.4/pyxrd/data/icons/200-download.png000066400000000000000000000026411363064711000205030ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons biIIDATxV 0 ]J`e@(BA18A(A=yRڮ];KOZۯ&.s$@U/%T iMD ྀ J.In}HH8=OjD*#A=[FNR$r)P:BDݥ0Nz2 81 D$khwK V~Pt!*t"SE-43 Sp1|l),iȲ.)mdq#w QW;޲@g~F_A5KfhnqLl}rumu`. IENDB`PyXRD-0.8.4/pyxrd/data/icons/201-upload.png000066400000000000000000000026331363064711000201620ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons i63CIDATxV 0 $#tF`#ta2Ap$#N%ҩRk#1!fc<y߫D" x6K }V}@Bh\I(|̈t'!-Ew;-ݙͤcrr&ArJ6auiH͠$s) g 2fz}L =d,'8.r{$+N}{Dۨ]貯i_K?TMFl/X!t|h *oas#а*ZVGаkj ^[|>w9ulu| 0!MfO~+IENDB`PyXRD-0.8.4/pyxrd/data/icons/203-lock.png000066400000000000000000000024251363064711000176270ustar00rootroot00000000000000PNG  IHDRXACtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons +QIDATx (8#t$G(b7 bzw<|bXVbщCGLkyPYR ܃/7TvdM^dPc l͡ ~Po9 n e;Ug GXy Qgl%Ң[ӻzvQޚ&\WU͍y`ut1 IENDB`PyXRD-0.8.4/pyxrd/data/icons/204-unlock.png000066400000000000000000000024321363064711000201710ustar00rootroot00000000000000PNG  IHDRXACtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons IDATxܕ Eq{%#dQ: :RŎX /1EIwR&e7ԲP^Q'f *;&Y/N<_V| ^zr(V6dWM0yU IgjEҪZӛxvQ֚fLW傖`E4ƿIENDB`PyXRD-0.8.4/pyxrd/data/icons/205-electricity.png000066400000000000000000000024241363064711000212200ustar00rootroot00000000000000PNG  IHDR ,tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons LIDATxڤ 0 EL$LN& H@I@pȺkzko5 $BExf9'^nf~8`afain'8G`i !fxN_1ϗq8ڍiWA3VIO/z`u)L~_CIENDB`PyXRD-0.8.4/pyxrd/data/icons/206-ok-2.png000066400000000000000000000024051363064711000174500ustar00rootroot00000000000000PNG  IHDRertEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 'IDATxڴ @h`#b#(Fw 2v? ҿ@2`i Ĝ״(^@` \H I< )CT>"@ CoX!p׶@NԂ*Z6 ڙ#[0N]ϴfIENDB`PyXRD-0.8.4/pyxrd/data/icons/207-remove-2.png000066400000000000000000000023261363064711000203370ustar00rootroot00000000000000PNG  IHDRVΎWtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons Pw~IDATxڬ C'pA@, g~TQUMt!' -@ YaDž  ӛZS<-Z6C~tv֠F)6 |"GFX#⃕0EIENDB`PyXRD-0.8.4/pyxrd/data/icons/210-left-arrow.png000066400000000000000000000023131363064711000207530ustar00rootroot00000000000000PNG  IHDR[tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons BsIDATxb?1 RGA@|B? @aQd ԋ #@/`3d 9`M dۋ`&j{&BdCMG&9,@ SIENDB`PyXRD-0.8.4/pyxrd/data/icons/211-right-arrow.png000066400000000000000000000023071363064711000211420ustar00rootroot00000000000000PNG  IHDR[tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ʧT(oIDATxb?! Ĩ%@qi bj6` J  A `dS OP \db)TO6%a ,@UҋlIENDB`PyXRD-0.8.4/pyxrd/data/icons/212-down-arrow.png000066400000000000000000000023731363064711000210000ustar00rootroot00000000000000PNG  IHDRmJtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons uIDATx 8 0 88) *ƨ( IqC]@A_]-S glyphicons "ZIDATx NQQ:#8 M)-hPۯÁ"NZ I}FmdQ 疌W{H%3RA5$,D,H[ZD"VUDq Ey.؉\Ҁ(ɼD$BOHyAmEh5 PKE_m#9bIENDB`PyXRD-0.8.4/pyxrd/data/icons/214-resize-small.png000066400000000000000000000025711363064711000213120ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ^6!IDATxڴV Aݠt;B'hGp#8n@G9E=H4c`%jL$I 0"# 'SD ZHnjFy[j ʋ#&Js@8;' ݥ@yc]Lw9R""amb$7bߤO:{IS"3 d"K]E 8Z~ ty  O/\ca/X|W2U(}_ιIENDB`PyXRD-0.8.4/pyxrd/data/icons/215-resize-full.png000066400000000000000000000025001363064711000211350ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons =IDATxڴ  ؤF7@6ԉTCld9ĔR&0x<ޑ-iHF + ۬_dk#&TņpU/FrȱCd?:yD|$cU1TAYw "'v5cF$z ;#Y@r}:wwDnKȋ5O ] %%-d9HEY Ĥy 0i/UIENDB`PyXRD-0.8.4/pyxrd/data/icons/216-circle-arrow-left.png000066400000000000000000000025631363064711000222270ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons qIDATxڴ @)@$  'a0 I$ \I؅p)#i--~&b e ! glyphicons A@&IDATxڴ E $p& 0 ;0 H@t¥PפY>`DaBCfwD9|1bl1A` w:ƍA7 $,.7Q} 盥Ւ` Nk 3ޤ u/Z{jv@JݞU-sE,S glyphicons y#IDATxڼ 0 E9p&g H@I@89] #tkw4$d[;7SJxWD9|wa) k{^#f)}p$(Bv,Z($8%dJ A>  ewqh>&D1G9-)-#StQ`ms'bǵq/K7_'(61{jي95t%=*(?/XwN ݐp S{)dԶ ZnGJ15@9IENDB`PyXRD-0.8.4/pyxrd/data/icons/219-circle-arrow-down.png000066400000000000000000000025721363064711000222470ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons X"IDATxڼ 8BG`GqGpGp؀Rσ.>Ea!&/BXP+C#nA+ Ȥ[te9]lyb!Bes¢lYA EA{^ zuYin$_Cw@;-tsVY͹@khL@F2gI2U.+x w Tj[`e[yKںgXvS&mK^OM }0#o=͛hIENDB`PyXRD-0.8.4/pyxrd/data/icons/220-play-button.png000066400000000000000000000025611363064711000211550ustar00rootroot00000000000000PNG  IHDRJLtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ABTIDATxV 0.  d#00# pz5GR_!GcBHH2^X6592[S A4i[P^'`DqE {3]{0ά  !/!"1 tDmftd[B6!D( !T glyphicons 5wIDATxڴUQ0 m0:$L$Ls$ aI` [8zkI|IKsάUl0Z ]O3_qJY8-̍ħ?\kЮ#EiH,ɗd5enS?*@aŁHldR:M:GһI=9'/XhI֋w5bY7gVN: 4TJh-b&WIENDB`PyXRD-0.8.4/pyxrd/data/icons/222-share.png000066400000000000000000000024761363064711000200100ustar00rootroot00000000000000PNG  IHDRAtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ѾIDATxڴU 2BG`2B6hG!#t ؀%#EPR |#s$g\~'"3A ? ,0P2wOvP!=+ )L!.0i[&S0$> V~-cɧhLa y;0p]ј83]LC[RcsJͪ8qC 8({vё#t%qs)IENDB`PyXRD-0.8.4/pyxrd/data/icons/223-chevron-right.png000066400000000000000000000023271363064711000214610ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons IDATxb? A 1qX5'l6?9L d'lH2[ mP$&J՜MvUd''% @EFم @CZ"Jo IENDB`PyXRD-0.8.4/pyxrd/data/icons/224-chevron-left.png000066400000000000000000000023171363064711000212760ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 2RawIDATxb?1EY@f4 G#r5ˑ\~&E#fR55< @ QEq"8ybX"%hWP\ + )}"HDIENDB`PyXRD-0.8.4/pyxrd/data/icons/225-bluetooth.png000066400000000000000000000024311363064711000207050ustar00rootroot00000000000000PNG  IHDRWtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons &IDATxb?:Q".9A?9a8C#9@@@#O@RT$@4/dASSCtQhf졊&fb ڀ-8 4'`S;:h2 * JrPDNQEF& "X<CA IENDB`PyXRD-0.8.4/pyxrd/data/icons/233-direction.png000066400000000000000000000025111363064711000206560ustar00rootroot00000000000000PNG  IHDRB}tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons dIDATxڴk  HJP P A$u %efooIBv⼄7V(@}>QQKEp97?R4D:ST$?(6I<3p}d 5=aÉW}[7qKqD- GS4}' %V#jOa< ѱQ0$$bT6֙$bDhU+lJk$ݮvhMfN ou 0HţijIENDB`PyXRD-0.8.4/pyxrd/data/icons/234-brush.png000066400000000000000000000025051363064711000200250ustar00rootroot00000000000000PNG  IHDR57tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 'IDATxڴm X%LP HII$ %XAR(ܽ 9caL((GJ:g &S7??;^ߞFJ[}N"}hC I%:˳7:(&C1,vL<k@6CMErm*\^MTC싥d-lS2~2aZ̈~٩Z1NF{mFS}EN3IENDB`PyXRD-0.8.4/pyxrd/data/icons/235-pen.png000066400000000000000000000024471363064711000174720ustar00rootroot00000000000000PNG  IHDR*ԠtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons sIDATxڴ `I`:Hi,RRBJR ͅpwZ1Rd4rK$Ў9jX h3ey4;9*y܂7õ8tŵ/\%'pOWX^qiJa0o[žvrn7v:y[|IENDB`PyXRD-0.8.4/pyxrd/data/icons/236-zoom-in.png000066400000000000000000000026701363064711000202770ustar00rootroot00000000000000PNG  IHDRctEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons Ҹ`IDATxڬVQq0 s!`P@t CYriReݻ$K|)I潏2VTXŵP` Ks3 %ʽD;ABP΀H\lMA@A"8A,O-ـ]T5 |v>`r}F˫0\Qd F=nrxbgfc^[2{kЙH?5qܾؖ\DEiڰ6[OO%?'X[rVA#j֭Wz)z-DZIAnX|!$tv >T:,%/*74i7`i*s釦IENDB`PyXRD-0.8.4/pyxrd/data/icons/237-zoom-out.png000066400000000000000000000026441363064711000205020ustar00rootroot00000000000000PNG  IHDRctEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons =LIDATxڬ0 E) *P ' H@J@sU&;e'Ihz? kҐmɜsԱ4X !T`7ЊC͠Kɰ8W4C@ACGtv竩Cyt\ <af%$I@R'ҼHEOMX)HZ>16EIѬm[$XN/C~&H¾Y_{.To-Q؆J~]K^0 @,ua t4x!u a=iTBR$w!( |쮈Ds^ !ޒ/KnIENDB`PyXRD-0.8.4/pyxrd/data/icons/238-pin.png000066400000000000000000000024361363064711000174770ustar00rootroot00000000000000PNG  IHDR ,l-tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ^IDATx쒱0 E-2#0eJJ (SfA؀NHo _dَ٩$h1P\M`J 2.b(|iEklD|mOKh >e3s1Y< n=wP4NM,(7t-NKk<O}Aҟt` $f6IENDB`PyXRD-0.8.4/pyxrd/data/icons/239-albums.png000066400000000000000000000024571363064711000202000ustar00rootroot00000000000000PNG  IHDRHtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons LIDATxڼU 0K}vBNI;!=at'FT&F Y%1"5$@pI䮓=˳/z&iŀG ƃlFJ$u U$`8ҹ!O5UTɂ;Za=`N8-T*ZU:*{VWe&ث™U! Äz-ҿ:3K}x_OOߞIENDB`PyXRD-0.8.4/pyxrd/data/icons/240-rotation-lock.png000066400000000000000000000026571363064711000214740ustar00rootroot00000000000000PNG  IHDR @tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons WIDATxڼV S0P 8H@B`*S0`p.Ksho{wm%!TJII1!| $@ !ێ@.H8.5 ^2 C.˯Z`AP1>GUJFQʢ-wˈkĉt+u<#D92WiDT ]L}YN6΋'6VFO[Gd4ڱF&JIuHS[Pwc'̬k;ݰzNfFpAv" Dڱ3֚5W >]F[&)1Scyx^$[ׂ $~ׅ@*]ݍ`w/zBIENDB`PyXRD-0.8.4/pyxrd/data/icons/241-flash.png000066400000000000000000000024341363064711000177760ustar00rootroot00000000000000PNG  IHDRWtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons pA9IDATxڔ 0 E$Lp0 H@0.Y{M^ %Q@pmL|\sH^?rbE%6DPlrՇ].mJ.aJ.DHQsD,}GB"XGr!B]\19DhZK`UƓ|,zW^"ƧIENDB`PyXRD-0.8.4/pyxrd/data/icons/242-google-maps.png000066400000000000000000000025111363064711000211100ustar00rootroot00000000000000PNG  IHDR} tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons oIDATxڔSQ 0  I$  HxR0 8ےR+&[ws9gQ"| @U/ JlKjrcD (X qgMydw:CcuLRnT$Mm/LܥA nVĽ#+Vϔ^o-Qol^x7N\j*tA*tP[ szZwX 0Fy/9IENDB`PyXRD-0.8.4/pyxrd/data/icons/243-anchor.png000066400000000000000000000025601363064711000201550ustar00rootroot00000000000000PNG  IHDR++]tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons /IDATxU d!#d2 #d6(ڕzh-YAGB'Ԁ_-L<:8^Q6AMA0Q(t/p̂I7&Jvُti*E0VylT$UFZFk?پQ::{(E+,alF&vK 'b7xnj%7 VSVAJo9&'сYL,O'cdK2ʟhI># kюĖxaX `|եs`"/ZIENDB`PyXRD-0.8.4/pyxrd/data/icons/244-conversation.png000066400000000000000000000025501363064711000214150ustar00rootroot00000000000000PNG  IHDR.utEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons $3 IDATxڴU @7:BG`n  >HJK%{JAC% FBO8}+$29\ X 0Q$_MHl%aG@ A8F"-Ɛ˻YSAQd FF=׈Vr na( fflS.\ 1d "Q twS!.^^JnbQ8`ڤ.Q8Қ][<(a{l IENDB`PyXRD-0.8.4/pyxrd/data/icons/245-chat.png000066400000000000000000000024551363064711000176270ustar00rootroot00000000000000PNG  IHDR;tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons d}IDATxڴa Y3H@0 HALP v5?q {WSJ1-A9@m4.4 ]( z_K!<2A?&*YQUA&\h+VlxȞeT,K+0&vb4W'E <.D`̜G r9/> QIENDB`PyXRD-0.8.4/pyxrd/data/icons/248-asterisk.png000066400000000000000000000024011363064711000205270ustar00rootroot00000000000000PNG  IHDR tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons IDATxڴ !EhGtu* iCk*rCa&D|CS34?+.hACQo". k\|9Ab# +A7:%_/-`lWu4],D^)[IoG/rpP glyphicons y>1bIDATxb?.HCjp%`$P-#.L T Mר"(c\dA5&BA]5@? v^0}(nBIENDB`PyXRD-0.8.4/pyxrd/data/icons/256-delete.png000066400000000000000000000024041363064711000201460ustar00rootroot00000000000000PNG  IHDRÍ tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons DIDATxڼ  ndF7`GDD]#y?HC*W[ -b\c݈@BHLAX(]sA'è: ֤NzTsdU3veLAAQ]3LyFEFEa(pS,?"ig@!1cョ-0zP>['ЃIENDB`PyXRD-0.8.4/pyxrd/data/icons/257-sheriffs-star.png000066400000000000000000000026271363064711000214740ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons С?IDATxڬV . #8#8BGp#B7p z|GP9#P&h44(\ľK!,`G҃` &QgPqQK `e`g@EFUρ:{VOz 8Xx#fHSbDGdϖ`:*i5vAidJñ@1H\NroJv7?-]LnrׂW< glyphicons N=IDATxT[0ż~`Š1gKQָgPdUHxE6*~qThT2C*^4>Kgp#:U" Z}+jk4|MQR6\D]e$>[4NTl5%Y=yx*12IENDB`PyXRD-0.8.4/pyxrd/data/icons/259-barcode.png000066400000000000000000000022331363064711000203060ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons N^CIDATxb```@F`ԂQ F-`ԂQ F- 0B+hZIJDhDqPۼIENDB`PyXRD-0.8.4/pyxrd/data/icons/262-spade.png000066400000000000000000000025241363064711000200000ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons .IDATxڴ E :#  .X=K~T$q9W@%漾`bdHzmhk@ݯI)8 HwCpo%Wd G 9e<0 ZҐ_ ԲݩSp_6Fg IIHW|gIJ4)Z6ÐtH(Φ[Gs`R(CʪceɫI= nIENDB`PyXRD-0.8.4/pyxrd/data/icons/266-flag.png000066400000000000000000000024001363064711000176120ustar00rootroot00000000000000PNG  IHDRXACtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons cIDATxb```R1( @.1u*@A0 glyphicons mc!IDATxڴ  nv6F|>2p+4$A/_A 8zcEt8ўԄ 8]q 0L& օRa1OyP1M0 /T<؟;ﹺ;3܉D5)[> N@%LۂX "߼ YpfUd9zy0\AVw`4z Ej&jX<=r2ƺeɖE]\2z: ̭ 0%\uIENDB`PyXRD-0.8.4/pyxrd/data/icons/280-settings.png000066400000000000000000000025741363064711000205510ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons w$IDATxڬV nFpG`G& -z$ %/|B]c V@Hn̰" o$e^+lR^V".m,ZBE|{MM6D|j=A-""ox,neLy>C_Dz9IENDB`PyXRD-0.8.4/pyxrd/data/icons/282-cardio.png000066400000000000000000000026041363064711000201460ustar00rootroot00000000000000PNG  IHDRB}tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons fB,IDATxڴ n eFpvGp@' "x. s-;j1am`h\!0P!7Gaݡv\ p !5M_bLET}@4T6.V<K(m !pfRQB;XTm3{V{^Cq8A ~G6twCPyD갷qZ@uؓkD_|+% @Qy/o"TρHʖR1s<#!ڐ2|p6UφIENDB`PyXRD-0.8.4/pyxrd/data/icons/286-fabric.png000066400000000000000000000022641363064711000201410ustar00rootroot00000000000000PNG  IHDR1JtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons E'u\IDATxb? A2b X@314 ^@m1= h |FmW"fb'k0&2@FN <,W إIENDB`PyXRD-0.8.4/pyxrd/data/icons/287-leather.png000066400000000000000000000025301363064711000203340ustar00rootroot00000000000000PNG  IHDR|futEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 2tIDATxڴU0 5dx)=BF`tUR 2%s2>  KV" q!nDl*|6)یDzT0I2lGxu|3>c~`T쥡y UfoGI::3[昸,$/rQ g>EtqWsY<ww`ʐ/.>[vؼ* _D' zjȎV oIENDB`PyXRD-0.8.4/pyxrd/data/icons/289-bomb.png000066400000000000000000000026021363064711000176310ustar00rootroot00000000000000PNG  IHDRU^tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons '<$*IDATxڼ  F E `"\#`"؀[Ax2]dYk?E*dQ 6#Hh}$ðҠ7H G 6" @0y6!OءSR%Ha` Er0D7]3\Xf7'FVʔ]g^j@f`bN 7L ;r_g` }T6Soˢ6uWU!H)bD.^~m:y,`*i PIENDB`PyXRD-0.8.4/pyxrd/data/icons/290-skull.png000066400000000000000000000025771363064711000200470ustar00rootroot00000000000000PNG  IHDR KtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ^ 'IDATxڴa0 * `$TJ@(` vY@ٺܽGڏ&m@46CTzp{ &hz(r8}]衎cF~b)+Y}́1͖5F/ M`_9 ϭ"v&cZL$IjWYo׭ GQ'_(W ,FayIENDB`PyXRD-0.8.4/pyxrd/data/icons/291-celebration.png000066400000000000000000000024571363064711000212020ustar00rootroot00000000000000PNG  IHDR++]tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons IDATxb?%$YlȈaLFƀb,php[  `$.VGpȶ&0'%M|-AIx# {A Xpx| s%=@5G,j@bр\#P9s2 @X ga7juR 7h  irIENDB`PyXRD-0.8.4/pyxrd/data/icons/298-hospital.png000066400000000000000000000022751363064711000205430ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 6eIDATxb```@df#@j 1ψMh F-x P)BHk4FZ*-F-]*ih>l6[ ˱@`IIENDB`PyXRD-0.8.4/pyxrd/data/icons/299-hospital-h.png000066400000000000000000000022771363064711000207730ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons RgIDATxb```@df#@j :L 4 ,(–ZF〾q@JNQ 42A>HCf;а`+uA IENDB`PyXRD-0.8.4/pyxrd/data/icons/306-bicycle.png000066400000000000000000000027321363064711000203160ustar00rootroot00000000000000PNG  IHDR!tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons mŅIDATxڬVm0 #hzz:BF!#T'z56pQ4~:Џ%ְӱ'vgE'rR眑8ԩ5,p1'+sk oN-iHb|H;7&TiE&ݝA  $36'ڶRk՜`,*Ey I7 p )V ;&oW-m\">+{Dž2m/8n- 8zż BJ5$|F5\wpiȎmPeA4»E KId6"K4A\Lpք.բlťY_s c{אc}q{=Y`*GSIENDB`PyXRD-0.8.4/pyxrd/data/icons/307-life-preserver.png000066400000000000000000000025701363064711000216370ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons $ IDATxڼVQ )@V $ )J  zaq1]/Ykpez=!~;' .iHNP^yz+I-B\' <s @,o]P+dBuFI Zb F z _:TAmp=r#k3ZM[:5+rB)x%nh)ݴUxkNφF|cEh+'ff`Z~IM?*vk¹2_>#!׶oIENDB`PyXRD-0.8.4/pyxrd/data/icons/318-more-items.png000066400000000000000000000022701363064711000207650ustar00rootroot00000000000000PNG  IHDRHtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 31`IDATxb?2`ddB@R B!( S PFd$LG 5tQC>P$P74#a.`U.ŻIENDB`PyXRD-0.8.4/pyxrd/data/icons/319-sort.png000066400000000000000000000023431363064711000176750ustar00rootroot00000000000000PNG  IHDRctEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons . IDATxA R_j JUL]6F bH}".q+'L+7MJ HBѩߺJ42Hu܉ eOVr)#:={Rj2f OL'Q)WnWhIENDB`PyXRD-0.8.4/pyxrd/data/icons/320-filter.png000066400000000000000000000025011363064711000201570ustar00rootroot00000000000000PNG  IHDRKvtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 1LIDATx ` ܠnP'y(hI䙈$"UpL|9L OhL`Pqf*.`jq<Ѿ<9[M~0/Z +q#Z.*v[6B#ZEA$ C` ZwڭpO@:L w/"ZMۈܿ?[qtpcsT030 {*"IENDB`PyXRD-0.8.4/pyxrd/data/icons/323-calculator.png000066400000000000000000000023501363064711000210300ustar00rootroot00000000000000PNG  IHDRHtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons G/IDATx E-vP:n$ZT01X1)ZRI(Xyb6u":?H v촘 qh`r8W)u%qT2U9O"41Rn/u});[% 3h|їyUKVIENDB`PyXRD-0.8.4/pyxrd/data/icons/331-dashboard.png000066400000000000000000000030421363064711000206240ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ZIDATxڜV-O@UX*IsY 'xz$␇C pM%ζa^wow5MXȁ 0vy%IB4 D\@n ǵATMHnpdbD9 `ϥJ̈́tЋ[teҀ\yNzexEjA5׈ eBS]aT|e'ofVMRt wܒ5?ۣ (fYlNknVhpѓE^T.H^geDO}VAF8gޯ<|O;'yzhYlMA 'ל_Gppq4|wX?o[['d+6x:j8^{ga=Wf uͭnHqmI9s=.b- IENDB`PyXRD-0.8.4/pyxrd/data/icons/335-pushpin.png000066400000000000000000000025051363064711000203720ustar00rootroot00000000000000PNG  IHDR*ԠtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons \_IDATxڴ eGQn# Zw{1!WumկW,E`r~lAC`J?G9 bTOw^qx2ps`űEpJMjZ5]ޱDFh4r!jho/zf=kĸ6B<{0cЪ=V?r-p_o28 0P}ZXIENDB`PyXRD-0.8.4/pyxrd/data/icons/337-pin-flag.png000066400000000000000000000024341363064711000204040ustar00rootroot00000000000000PNG  IHDRc,tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons σ.IDATxڼQ c *ja*aR*&a0 up ^BpM:E'5"c+3D nnGCGU|$ZYE8Ω'ѿ8 XoA8tN|Zm.?%AP`8܂p A3x@8pN^VG`oHSrSåk`DIENDB`PyXRD-0.8.4/pyxrd/data/icons/338-turtle.png000066400000000000000000000025031363064711000202240ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 2IDATxڴ 0WJI$L,`8@$Aq09ȭ>h%4{5ι&&d@ A:c/jAqZ>$˯3ATSIL+H✼ t!PHcJE% ! SJnN{d؈ܱvh"+r[n_nJ|B~ ,OH/|IENDB`PyXRD-0.8.4/pyxrd/data/icons/339-rabbit.png000066400000000000000000000026421363064711000201550ustar00rootroot00000000000000PNG  IHDRވtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons _QJIDATxڴU0 w@7L#0#0# 7 j=RsNP擅2-Ȯ `U+JA$q#MH-"A-Eg4cLl}T,L, B9u#:J +Y xw$Z)d"O(1<i--@1EE ç^Fb[ܿA\.)J]AT3P&gfȉ2fyPMt=_{zzOmVoG:4h!:)U!S0\k,9i0}BL)R&7ش5~/F1IENDB`PyXRD-0.8.4/pyxrd/data/icons/340-globe.png000066400000000000000000000026671363064711000200010ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons QB_IDATxڴVQq0 s0!X !! 2BxN)$;;_c[O熜sY!0:Z"`:@ `#98(7~=hbs "dFAkF" L蝔$܁L$X'@@QS d$?VU(7-ɾXQ±*ڔmhYw&J:*G[ty_#wSf""N'"h \qƮ9o*S3kWVY"~F;40W0.{oT3-ʃSEBiYNœ",G?o˟h՟IENDB`PyXRD-0.8.4/pyxrd/data/icons/341-briefcase.png000066400000000000000000000024161363064711000206250ustar00rootroot00000000000000PNG  IHDR\.&tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons YIIDATxb?. @| 9fpK(f,r ``*F(@5 Xrq#A3@c0jQ|AH7>_;xr9@|D@Z r#6<\Ԟ  b#fhD;F<)|IENDB`PyXRD-0.8.4/pyxrd/data/icons/342-hdd-as.png000066400000000000000000000005321363064711000200400ustar00rootroot00000000000000PNG  IHDRw=bKGDC pHYs  tIME9&GIDATHݖ 0 EQt2@& NPVt( ! IK(cv#@~ïx BTIP|;Ma;BR"|IleYTEToږ]o@!}$_A?{Ij3EV&{yzu\f%, _gIENDB`PyXRD-0.8.4/pyxrd/data/icons/342-hdd.png000066400000000000000000000005001363064711000174320ustar00rootroot00000000000000PNG  IHDRw=bKGDC pHYs  tIMEdIDATHݖ 0 D/adtFa q$+DZqHKG IAb76ޯN@lX4W8$L v/AleIEKF=A!HhBAdR5 i͐{_]9))c ̎/+9y4A&dAy53ƯIENDB`PyXRD-0.8.4/pyxrd/data/icons/343-thumbs-up.png000066400000000000000000000026321363064711000206300ustar00rootroot00000000000000PNG  IHDR\.&tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons /UBIDATxڬU1n1<]*"M ~pm$HPxOp) 6sbNZ9>4ecf)DHZ@1,1f w7H7Kk eŜ#j6lU.s* 8YP~5cRsyfM;;yʍ-5:oGyT!9>\ŚOT)9 nRkc#oMGϖH1^gqR P滐e@LZv5 >+3vD98X޿<8uq 3FbIENDB`PyXRD-0.8.4/pyxrd/data/icons/344-thumbs-down.png000066400000000000000000000026371363064711000211610ustar00rootroot00000000000000PNG  IHDR\.&tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons a6GIDATxڬUN0 m*^ ,,LEH]nak$F?-Hec~ Hj&Uc)Ifι,H X)hZY;p!(]nz7SH<  QVq{f+A8 {Fg 'aaj݆J"rDzۦ"(GHGdE`ND,>ƪ~j]>Rۿ>yZ'Iv3IENDB`PyXRD-0.8.4/pyxrd/data/icons/345-hand-right.png000066400000000000000000000025521363064711000207340ustar00rootroot00000000000000PNG  IHDREtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons :IDATxb?QH%14AM Pd(' ,c 6P,"`T$"B \@ & l$`m`@ePrrf06hb31th @\ğE"P=1 Y7 SGPZ y Sb06.@E.(mI7RrZ (n"%SaZT9030z 2>IENDB`PyXRD-0.8.4/pyxrd/data/icons/346-hand-left.png000066400000000000000000000025771363064711000205610ustar00rootroot00000000000000PNG  IHDREtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons B'IDATxb?!( /@*YȒ P $NH/9DXɒZHE?bPБc!1 *@ą/$ ıY*.8+d@aLq1+g1A8 6@Ħy| \؀+%([\1) KPZJsF@i bN_=T!."Ā%yc) ,[ugmIENDB`PyXRD-0.8.4/pyxrd/data/icons/347-hand-up.png000066400000000000000000000026751363064711000202530ustar00rootroot00000000000000PNG  IHDRU_gtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ~!eIDATxĕ=N1mUEԈmHH)P#ln`hҦE4KGby<c;8 1?M3^ն +hI/cgbN!sov l_*bqZfK=s㐭?l1xhdX6r?~inقsP 0]j u"1.wԇ`0gm^`. #pxD<.}(\r\'kQ'0%syVWI8w`34 Xջc3?Y$6b)&lӻ:Cv}WH,lߴQ$)v>1mIENDB`PyXRD-0.8.4/pyxrd/data/icons/348-hand-down.png000066400000000000000000000026661363064711000205770ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons A^IDATxĕAn0Eq6]^Ȳ" PGMo#N(! um88qOx"R#ۄ݇G܌ЮDrjŦa\멁ҾiVD &r{![LɌOCG]_TEDxt4=]g{ݪ3If>km'*,jŖZ'Wl#t2YUhv_~*bx ,Ȍi> c Ȥ!X ͙ih~-'u6 # =S&?zV2_Aڰ[3uQg;rֺ}%!#ـ^Vz[&Pѵ_'7h~_WIENDB`PyXRD-0.8.4/pyxrd/data/icons/349-fullscreen.png000066400000000000000000000024141363064711000210520ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ; IDATxV ۼ98$`:`\XZɄH0!Bۚ8 HT`dN'- y @v=( 5)=H Ow `՚X% uR3^1~MOь"OLh[v?T 0%i1IENDB`PyXRD-0.8.4/pyxrd/data/icons/351-book-open.png000066400000000000000000000025221363064711000205720ustar00rootroot00000000000000PNG  IHDR}\tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 4IDATxUQ 00CB%T&I@u  $cKxKxsɨg:ye6lPm*&%ZxS)N xLNkF贘BЕ /P R~4wXksuZGF?`o7 ʓ?BQ0 )@[T:^#ҏ0\'6V)aRŇr b(dD8;$\< {lxx 0w}IENDB`PyXRD-0.8.4/pyxrd/data/icons/352-nameplate.png000066400000000000000000000024561363064711000206560ustar00rootroot00000000000000PNG  IHDR\.&tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 'rIDATxT i`2JGa%#9NTtBw!<(^ 8$xxQ²ސV=#*gr Bw Hڽz16ju`+97XlƧ,f:dSφt6GXETo㶟^"]=#ث}k{?gу^1v 0\IENDB`PyXRD-0.8.4/pyxrd/data/icons/353-nameplate-alt.png000066400000000000000000000024251363064711000214310ustar00rootroot00000000000000PNG  IHDR}\tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons S{͍IDATxb```h4 P-`@ gA0!)2@G31p'`qa ?@ 8  'Pr26Xyz|Y`x׆h)~jiqr Z&FK0<IENDB`PyXRD-0.8.4/pyxrd/data/icons/355-bullhorn.png000066400000000000000000000025561363064711000205410ustar00rootroot00000000000000PNG  IHDRxwtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons BIDATxڼU0 e@7P' t75pJ-_{G[(͇PJs"(2bW4W5& HLE?#!lo ǎqȈwD,(z [+B1I>ƴ*Bp1yJ'6bZ_8AGyqU-&`4OyѢrw"pPb޵_rgi)XFn}'p$bpH?d^ !"l>rHm@Il 09.["IENDB`PyXRD-0.8.4/pyxrd/data/icons/358-file-import.png000066400000000000000000000024541363064711000211430ustar00rootroot00000000000000PNG  IHDR KtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons ֔#IDATxڴ 0 D#dnRFQ:#0B:T2)Nt?x28"r%g6V=Hx͹@ d8Frfu9gNZ 8+ӎlIENDB`PyXRD-0.8.4/pyxrd/data/icons/359-file-export.png000066400000000000000000000024511363064711000211500ustar00rootroot00000000000000PNG  IHDR|futEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons `IDATxڴ Eϒ 2BF( :BG(ݠ#$`ᠵ|$`ίDD;Baל,Ac`g -IZ1s3~U{kXV BKxOh9-Xrzn;4enKSEG m|8jR58G(_9,#Ƴ=8rέnID嘤?G#`ܟ#>IENDB`PyXRD-0.8.4/pyxrd/data/icons/360-bug.png000066400000000000000000000025621363064711000174620ustar00rootroot00000000000000PNG  IHDR KtEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons AYbIDATxڬ LI$$L& 'a0 ut] A%kv+&dj-f1P]d[8?`tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons W/kIDATxڴVq 5#x2B7hA6GH7do@& *0خ!!= 1T 9 p,Y+` KoʴVTer7YQC cK8&Dzp*n:B 7aƉPc1.a!8]89i10$#9מ9£|)T\a e=qT`8;Ps16:^Lۼ?xc k؄~DR|z?K064h::˝u |Q9NvAꚬ3sA6EF#ݰa?'%V ~Þ Q4SIENDB`PyXRD-0.8.4/pyxrd/data/icons/365-restart.png000066400000000000000000000026711363064711000203770ustar00rootroot00000000000000PNG  IHDRctEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons 6(aIDATxڼ A٠np7#8#tF`Gp½{a>H iy&#˱H2Ͼ4;mێuǫf}dXGVuVX9A[ޓx<+Vm=, 6bN\uO>U0Q?9 n`gfH(DBw#䒐y "79䉩!\ ; v%lY+lW> *PVw&W6<*/@\n~br-lbk&磚WȿԡEBU$0mOZ]rRkĢ+շ=mCGx~IENDB`PyXRD-0.8.4/pyxrd/data/icons/367-expand.png000066400000000000000000000023731363064711000201730ustar00rootroot00000000000000PNG  IHDRrP6tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons IDATx̔ ENQ8n DGEE@DaXE :9D$ב.98 @8`c6C+z˙p34nu]sVeڜ>hH5W.CF '뒱~N>Tg;GY*@H{IENDB`PyXRD-0.8.4/pyxrd/data/icons/368-collapse.png000066400000000000000000000023711363064711000205150ustar00rootroot00000000000000PNG  IHDRrP6tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons IDATx̔ D['pQQ`7-R~Y "ZYEuk$H8WGNo%CYPc`y sE?]k j) `3*K)f{\R3uvn)g/!mON>IENDB`PyXRD-0.8.4/pyxrd/data/icons/369-collapse-top.png000066400000000000000000000023721363064711000213170ustar00rootroot00000000000000PNG  IHDRrP6tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons <IDATxԔ EtQ glyphicons !|Dq|IDATxڬV m0 TP CBB! B :CoR,=EM?ű) G+a%8ڿ0A d|f$sq  k|+`n% Sk%2GWH܎ڢT"/9?e<.\S|$\K06Tl*ۙE%|#’9TT[Nwl3K8Iz Q!]3τw&9NY! p^9'FH+E3TM?*/N"SJ0-Mdr=[y]Oו]i.Qyrbigw6S󠖩=M_mRqa+G@IENDB`PyXRD-0.8.4/pyxrd/data/icons/371-global.png000066400000000000000000000025561363064711000201520ustar00rootroot00000000000000PNG  IHDRw=tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp glyphicons f>IDATxڼV 'uG(#n@C9zrN{~"0X9i 9%@_  glyphicons &]]IDATxb?5## Z @|b b]@]L@8d0:fXA>0q ƇIv1 :dTQq> 5(H"\[Ej L 4 glyphicons >6IDATxV 0 Lt؀n@F#0#t6dvTgɊ i*y8wk(! eN1ة26j*l 'V/MmA ^ k .v^(*̳ 3-|&_0nS!,2u`ߎo r9`ͥKq*A"1o\iiً3ժF#">x5|dΤgMNr BCn\*3$kdj7juI`%r5~n bcc  Jmy 0ne[aIENDB`PyXRD-0.8.4/pyxrd/data/icons/420-background.png000066400000000000000000000004121363064711000210110ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME 3*تtEXtCommentCreated with GIMPWrIDATH Ec:ɯuOi }sBRn/Y d(]l.<X\/%z*#Tz-&lk9k" QZoJEڨs^N !紒IENDB`PyXRD-0.8.4/pyxrd/data/icons/421-smooth.png000066400000000000000000000005471363064711000202150ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME2tEXtCommentCreated with GIMPWIDATHݕA ƫ87)T  C_w-.NKݹ  ĐLPntm9:> @ x`Zj%@ (Ie5M8aR|%2qG֫\X7U%fw V Kқ,qC.YwcH[ŻM=J,f1-u\ӭIENDB`PyXRD-0.8.4/pyxrd/data/icons/422-shift.png000066400000000000000000000004061363064711000200140ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME8 &DtEXtCommentCreated with GIMPWnIDATHc`?R 0ڷLC%DDȆ2RBaP%XRFZEcccF2##UK s-:BnqHMè{ji$dIENDB`PyXRD-0.8.4/pyxrd/data/icons/423-save-graph.png000066400000000000000000000005071363064711000207370ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME*8݊tEXtCommentCreated with GIMPWIDATHT!J'j'z9c$zf~D]wyZuY,y *6,5LR姏Y]E/ RJVnhb @"U 9W랙 Y@{`Z^[fv~-/3U mؑwt;}vOdJ'~IENDB`PyXRD-0.8.4/pyxrd/data/icons/424-new-file.png000066400000000000000000000004651363064711000204140ustar00rootroot00000000000000PNG  IHDRw=bKGDC pHYs  tIME<+JtEXtCommentCreated with GIMPWIDATHV 0+ M"r(B}vtC)e $ydYqzkI- LUh̢!@ߵڢe[h%m_pZEnLb֯]H"Jx\M:jA }CX7TIENDB`PyXRD-0.8.4/pyxrd/data/icons/425-goniometer.png000066400000000000000000000004201363064711000210460ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME 3IDATH D[j0Ý?j Pc4ޗI]mBv7?(q ."n& 68QJ' 6Ao{&80ؘ_k :[V |8cr+ 01Y?R=?5^mU|uUPkdC=nRC+!d7VtIENDB`PyXRD-0.8.4/pyxrd/data/icons/426-atom.png000066400000000000000000000015721363064711000176500ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDATH[Vefs"@Dh^%QBEPsUTDEM! PQQޘa)L 3ŷ~a]߻޵.UJc:%(c9NaWDw~{ыWxsD_X G"b}m]c4 4KK)47a_3a{ ~;KЏm.CY욈ոX6`+IAT]8M؍1VJy:r]6@j|m|u) &}-u+\ \߳ #|=yZm\R qYWjwKBg]5qs0l~I1]-簽W}CuL܉S'1?Y܅' a}DVj 78Li6Xُ-j2"iӽ؃q]&KY>uvTh6I[L k\HoLN7YF5sO=4'RNq߃[ Lv{]&kZgV{VJ{X2 Xʨ8<nܡ<1l%PUUM262Ցoѹy>$Tg8;uW,]x.n}#IENDB`PyXRD-0.8.4/pyxrd/data/icons/427-blender.png000066400000000000000000000011201363064711000203110ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDATH;kQ$6F05 5Z؊?Z["@IaBQ b#AEbae]|ց̙= 1FUb)أ60^!|;_acWWX˫[6a7:8&=יpB)BcxZa<b  bK!o{iZ$Bz x^Pʃ2)Aܯ( nS{1"$X1VwXHe󝂻!h`#v`se[2Xyc & fuCЩ^|^QSL1` IU6H=`j];b(H$iS5ԛlN7R[kӟöu ]^hwE.a}eZ&"V,CdoC71@i̍@:?:U; jPq1d0:D2)_>6Z#>`#MP}Ũۅ"M'2ˏ"imXsHm'^ҕ0liLι+FXL=x3FrR=%t*}s&(Zi kd"P)>W) 4eQOOdE>hɁ*=kDWDw<̵> rH"A~b d-d|IENDB`PyXRD-0.8.4/pyxrd/data/icons/429-restrict.png000066400000000000000000000003031363064711000205410ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME :6s5PIDATH1 .*&ĭTce'Q %@f)/+`>ȃםvK Ӻy  8IENDB`PyXRD-0.8.4/pyxrd/data/icons/430-area.png000066400000000000000000000004011363064711000176010ustar00rootroot00000000000000PNG  IHDRw=bKGD pHYs  tIME ,ntEXtCommentCreated with GIMPWiIDATHQ Ca}ْWPb2G?>nP#.VPkx x 9w|"5soLCr5& kEiwN8u2NIENDB`PyXRD-0.8.4/pyxrd/data/icons/431-behaviour.png000066400000000000000000000006341363064711000206660ustar00rootroot00000000000000PNG  IHDRw=bKGDC pHYs B(xtIME z)IDATH1N@(%qTR:DM 8C*4H4c)Y;&b$kgBa-$M],ꜽ8dOr 5ȱvA'#\.F L=LpYȷ%hAɶZO&EJFm`@*j?O _ g>p:4h+e(~ʿAGqUFɃ4X>Tfm߃quY]=8{}cY.IENDB`PyXRD-0.8.4/pyxrd/data/mineral_references.csv000066400000000000000000001641321363064711000211250ustar00rootroot00000000000000Adularia (DEL) 02-0472 Adl 4.68 20 4.21 60 3.94 10 3.77 40 3.61 20 3.48 20 3.31 100 3.23 80 2.99 40 2.9 20 2.76 20 2.56 60 2.38 20 2.32 10 2.26 10 2.17 40 2.12 20 2.05 10 2 20 1.97 20 1.92 20 1.88 10 1.85 20 1.79 80 1.77 20 1.74 10 1.72 10 1.69 10 1.67 10 1.64 10 1.62 20 Albite,calcian,ordered 41-1480 Fs 6.36558 6 6.28905 1 5.86616 1 4.03136 27 3.87179 6 3.75875 13 3.70624 12 3.65374 14 3.4901 3 3.44232 1 3.36936 9 3.19733 68 3.18167 100 3.14865 14 2.99359 7 2.93496 13 2.84382 8 2.64281 7 2.53832 7 2.5238 2 2.49193 2 2.44536 1 2.43959 1 2.42496 3 2.39997 1 2.38584 4 2.33805 2 2.32757 1 2.30012 3 2.28665 1 2.2695 2 2.20664 3 2.15571 1 2.13383 4 2.10584 5 2.01873 1 2.01066 1 1.88012 3 1.85068 3 1.82702 7 1.79673 4 1.77779 7 1.72205 2 1.4574 5 Albite, disordered 10-0393 Fs 6.428 8 6.357 10 5.841 2 5.666 2 4.69 2 4.04 16 3.881 12 3.752 30 3.639 12 3.476 6 3.37 8 3.211 30 3.176 100 3.129 12 3.016 8 2.95 10 2.927 12 2.917 2 2.83 12 2.654 4 2.518 8 2.506 8 2.45 2 2.369 4 2.301 2 2.281 2 2.266 2 2.245 2 2.182 2 2.14 8 2.12 6 2.1 4 1.992 2 1.987 2 1.941 2 1.924 2 1.873 6 1.85 2 1.826 8 1.819 1 1.792 10 1.774 7 Albite, ordered 19-1184 Fs 6.37 12 5.91 8 5.59 8 4.03 100 3.85 10 3.77 25 3.68 25 3.66 60 3.5 12 3.48 10 3.37 14 3.22 65 3.2 55 3.19 55 3.17 20 3.15 50 2.964 25 2.957 20 2.927 30 2.862 8 2.639 10 2.562 25 2.538 6 2.443 35 2.429 10 2.407 6 2.388 8 2.318 18 2.245 8 2.186 8 2.125 16 2.115 6 2.075 8 2.001 6 1.894 20 1.888 8 1.853 20 1.841 6 1.829 12 1.823 10 1.818 12 1.785 10 1.741 10 1.72 8 1.585 6 1.456 6 1.437 6 Albite, ordered 09-0466 Fs 6.39 20 5.94 2 5.59 2 4.03 16 3.857 8 3.78 25 3.684 20 3.663 16 3.509 10 3.484 2 3.375 8 3.196 100 3.151 10 2.964 10 2.933 16 2.866 8 2.843 2 2.787 2 2.639 6 2.563 8 2.538 2 2.511 2 2.496 6 2.46 6 2.443 4 2.431 2 2.405 2 2.388 4 2.32 4 2.278 2 2.189 4 2.125 8 2.119 6 2.076 2 2.035 2 2 2 1.98 4 1.927 2 1.889 8 1.851 2 1.844 3 1.829 4 1.824 18 1.804 6 1.785 8 Allophane 38-0449 All 3.3 100 2.25 20 1.86 2 Alunite 14-0136 Alu 5.77 30 5.72 14 4.96 55 3.49 20 2.99 100 2.89 100 2.477 6 2.293 80 2.211 6 2.038 2 2.022 2 1.926 70 1.903 30 1.762 2 1.746 16 1.684 2 1.667 2 1.648 2 1.572 2 1.509 4 1.503 35 1.494 10 Alunite_2 04-0865 Alu 5.76 9 4.99 20 3.51 32 3.01 85 2.9 17 2.48 20 2.29 73 2.26 28 2.21 8 2.04 6 1.9 100 1.75 88 Aluminum, syn [NR] 04-0787 Al 2.338 100 2.024 47 Analcime-C 41-1478 Anl 5.59005 60 4.84377 11 3.66709 5 3.4254 100 2.9209 40 2.797 6 2.68748 12 2.50066 11 2.42307 8 2.22226 9 2.02044 1 1.93751 1 1.9012 10 1.86527 7 1.7406 20 1.71279 8 1.68682 6 1.66117 3 1.61503 3 1.5933 4 1.49531 2 1.47754 3 1.46191 1 1.44407 2 Analcime, magnesian 42-1378 Anl 5.85 31 3.52 100 2.99 40 2.92 10 2.85 5 2.74 17 2.55 12 2.47 6 2.35 22 2.25 5 2.21 4 2.03 34 Anatase, syn 21-1272 Ant 3.52 100 2.431 10 2.378 20 2.332 10 1.892 35 1.6999 20 1.6665 20 1.493 4 1.4808 14 Andalusite 39-0376 And 5.549 100 4.531 82 3.929 33 3.897 6 3.521 30 3.494 24 2.774 74 2.494 22 2.4833 17 2.4688 20 2.3799 7 2.3541 6 2.2736 28 2.2632 15 2.2561 18 2.1819 12 2.1754 27 2.1709 39 1.9742 4 1.9489 2 1.9141 1 1.8916 3 1.8565 4 1.849 2 1.8455 1 1.8102 5 1.8035 6 1.7906 5 1.7614 1 1.7541 6 1.716 1 1.6791 1 1.667 1 1.6096 2 1.5949 8 1.5486 3 1.5388 10 1.5291 4 1.5196 2 1.5153 3 1.5126 3 1.5082 4 1.5009 2 1.4914 12 1.4875 29 1.4817 10 1.4794 10 1.4752 9 1.4635 3 1.4499 1 Andesine, low (DEL) 10-0359 Fs 6.41 50 5.86 20 5.66 20 4.68 20 4.04 80 3.88 50 3.76 70 3.72 60 3.65 70 3.47 50 3.44 30 3.37 60 3.21 100 3.18 90 3.14 70 3 60 2.93 70 2.84 60 2.65 50 2.53 70 2.49 60 Anhydrite, syn 37-1496 Anh 3.87851 5 3.4988 100 3.12077 2 2.84946 29 2.79708 3 2.47348 7 2.32823 20 2.20904 20 2.18361 8 2.08647 8 1.99404 4 1.9388 3 1.91756 1 1.86916 16 1.85269 3 1.74996 11 1.74812 10 1.73249 1 1.6483 15 1.59444 2 1.56474 4 1.52525 3 1.51581 1 1.49047 5 Anorthite, sod., ord. 09-0465 Fs 6.48 40 4.68 40 4.04 80 3.89 50 3.75 80 3.64 70 3.47 50 3.43 30 3.37 60 3.26 50 3.23 80 3.2 100 3.18 90 3.14 70 3.02 50 2.95 70 2.66 60 2.53 70 2.51 60 Anorthite, sod., ord. 20-0528 Fs 6.498 9 6.417 9 5.809 8 5.64 5 4.041 82 3.913 26 3.761 48 3.675 4 3.631 20 3.36 7 3.249 27 3.209 93 3.197 100 3.181 85 3.132 21 3.036 10 2.954 15 2.938 14 2.905 7 2.832 5 2.813 4 2.513 19 2.501 10 2.469 10 2.459 17 2.413 4 2.392 7 2.368 9 2.308 5 2.28 9 2.264 8 2.139 19 2.105 4 2.098 5 2.09 4 2.02 8 1.937 4 1.927 8 1.88 7 1.853 8 1.839 10 1.816 4 1.798 8 1.77 11 1.759 6 1.748 4 1.734 4 1.71 9 1.494 5 1.469 4 Anorthite, sod., int. 18-1202 Fs 6.49 2 4.685 6 4.042 35 3.904 16 3.759 70 3.632 12 3.471 20 3.426 2 3.365 30 3.241 40 3.21 70 3.203 70 3.181 100 3.132 35 3.027 25 2.951 30 2.936 30 2.91 10 2.834 30 2.822 16 2.65 18 2.547 6 2.515 40 2.465 2 2.45 4 2.439 4 2.418 4 2.308 4 2.282 2 2.228 4 2.21 2 2.158 2 2.14 16 2.132 35 2.126 20 2.101 12 2.019 2 1.927 4 1.882 6 1.879 2 1.848 2 1.844 2 1.835 10 1.834 2 1.819 2 1.799 10 1.772 12 1.716 4 1.711 4 1.653 6 Anorthite, sod.,disord. 41-1481 Fs 6.49581 3 6.40686 1 4.69379 5 4.04405 17 3.90368 8 3.76346 18 3.62586 18 3.47402 8 3.36436 10 3.24527 23 3.20522 79 3.17723 100 3.12702 19 3.03237 11 2.95106 15 2.93307 10 2.8324 10 2.65192 7 2.51353 19 2.27887 3 2.26566 2 2.23228 2 2.13913 10 2.09793 7 2.02129 3 1.98685 2 1.92897 3 1.88012 3 1.84856 3 1.8346 7 1.81308 2 1.79607 7 1.77232 10 1.75897 2 1.73324 3 1.71071 6 1.68224 2 1.60497 2 1.48948 4 1.45475 3 1.3746 3 1.35184 5 1.32061 2 1.29707 2 1.26875 2 1.21948 2 1.16844 2 1.16636 2 1.16232 2 1.15348 2 Anorthite, calc., ord. 41-1486 Fs 6.52921 4 4.68887 9 4.61156 2 4.04041 22 3.91046 5 3.77923 14 3.75249 10 3.62296 15 3.46604 4 3.36311 10 3.25807 20 3.20861 88 3.19621 69 3.18056 100 3.12487 15 3.03842 9 2.95201 17 2.93401 11 2.88864 3 2.82891 12 2.80727 3 2.65574 7 2.54389 3 2.5238 10 2.50539 9 2.43575 2 2.38401 2 2.3593 2 2.32295 2 2.26457 3 2.23653 2 2.1401 14 2.11902 7 2.09607 6 2.02386 2 1.98603 3 1.93168 4 1.92357 2 1.8772 3 1.84681 4 1.83634 5 1.79574 9 1.76944 8 1.76181 4 1.75646 2 1.71815 3 1.71308 3 1.4988 3 Anorthoclase, disord. 09-0478 Fs 6.49 14 6.42 12 5.83 2 5.78 2 4.106 16 3.895 8 3.854 8 3.768 14 3.726 14 3.622 4 3.59 4 3.467 8 3.44 8 3.243 90 3.211 100 2.987 8 2.931 8 2.905 6 2.78 2 2.734 4 2.547 10 2.436 2 2.327 4 2.162 16 2.14 2 1.924 2 1.874 4 1.861 4 1.825 6 1.786 8 Ankerite 41-0586 Ank 5.392 1 4.051 1 3.714 3 2.906 100 2.693 1 2.556 1 2.414 3 2.203 5 2.073 1 2.024 4 2.02 3 1.856 1 1.818 5 1.7974 6 1.7947 3 1.7557 1 1.573 1 1.5503 2 1.5068 1 1.4718 2 1.4522 2 1.4402 1 Fluorapatite, syn 15-0876 Ap 8.12 8 5.25 4 4.684 1 4.055 8 3.872 8 3.494 1 3.442 40 3.167 14 3.067 18 2.8 100 2.772 55 2.702 60 2.624 30 2.517 6 2.289 8 2.25 20 2.218 4 2.14 6 2.128 4 2.061 6 2.028 2 1.997 4 1.937 25 1.884 14 1.862 4 1.837 30 1.797 16 1.771 14 1.748 14 1.722 16 1.684 1 1.637 6 1.607 4 1.58 2 1.562 1 1.534 6 1.524 4 1.501 4 1.497 4 1.468 8 Hydroxylapatite, chlo. 25-0166 Ap 8.24 8 5.28 4 4.12 6 3.9 4 3.45 35 3.16 6 3.11 16 2.827 100 2.775 30 2.73 75 2.632 16 2.544 4 2.298 4 2.281 10 2.162 4 2.062 2 1.997 4 1.95 30 1.901 8 1.883 10 1.842 50 1.819 10 1.794 10 1.761 6 1.718 8 1.653 4 1.614 6 1.6 2 1.542 2 1.514 2 1.499 4 1.481 2 1.453 8 1.445 4 Aragonite 41-1475 Arg 4.212 3 3.397 100 3.274 50 2.872 6 2.733 9 2.702 60 2.481 40 2.411 14 2.373 45 2.342 25 2.33 25 2.19 12 2.168 2 2.108 20 1.9774 55 1.8821 25 1.8775 25 1.8616 2 1.8275 4 1.8149 20 1.7598 3 1.743 25 1.729 12 1.7257 16 1.6984 2 1.6369 3 1.6198 2 1.5588 4 1.5357 2 1.4993 4 1.4764 2 1.4672 4 Augite, aluminian 41-1483 Aug 4.44673 7 3.34698 7 3.22226 67 2.99065 100 2.94156 63 2.89595 33 2.56007 39 2.53138 28 2.51081 51 2.29843 27 2.21651 8 2.19994 8 2.14934 23 2.12951 30 2.10678 13 2.03596 17 2.02001 11 2.00728 9 1.96808 3 1.89523 2 1.85421 4 1.83149 11 1.74771 17 1.72235 2 1.67149 5 1.66589 5 1.6284 16 1.62287 18 1.61192 12 1.58156 5 1.55893 10 1.54448 10 1.53172 8 1.52033 5 1.50582 10 1.48242 5 Augite 24-0201 6.479 10 4.703 13 3.246 20 2.999 100 2.961 50 2.9 28 2.573 40 2.526 80 2.163 13 2.139 30 2.114 15 2.085 7 2.05 20 2.013 20 1.843 6 1.762 16 1.63 22 1.555 6 1.53 12 Augite 24-0203 Aug 4.464 12 3.234 75 2.994 100 2.949 65 2.895 34 2.839 7 2.566 55 2.516 65 2.497 4 2.3 22 2.213 10 2.2 6 2.153 22 2.134 35 2.108 16 2.073 6 2.041 25 2.016 17 1.835 12 1.812 6 1.753 30 1.625 35 1.616 17 1.612 9 1.587 8 1.562 13 1.55 13 1.527 8 1.487 16 Barite, syn 24-1035 Brt 4.44 16 4.339 30 3.899 50 3.773 12 3.577 30 3.445 100 3.319 70 3.103 95 2.836 50 2.735 15 2.729 45 2.482 13 2.325 14 2.305 6 2.282 8 2.211 25 2.121 80 2.106 75 2.057 19 1.9317 7 1.8575 18 1.7889 4 1.7616 8 1.7584 10 1.754 8 1.7284 4 1.7239 5 1.6823 8 1.6741 14 1.6699 11 1.6378 8 1.5944 8 1.5906 6 1.5876 4 1.535 15 1.5274 8 1.4751 10 1.457 4 Bassanite, syn 41-0224 Bas 6.01277 80 4.37084 5 4.2791 5 3.99376 5 3.82575 5 3.61137 5 3.46737 50 3.21998 5 3.04248 10 3.0064 100 2.80298 90 2.71522 10 2.61511 5 2.56931 5 2.34098 5 2.27114 5 2.227 5 2.18212 5 2.1372 10 2.11524 10 2.02817 5 2.00181 5 1.95326 5 1.90834 10 1.84892 20 1.8447 30 1.81275 5 1.78884 5 1.75113 5 1.73538 10 1.73294 10 1.69954 5 1.69258 20 1.66534 20 1.61089 5 1.57835 5 1.54589 5 1.53287 5 1.52078 5 1.5012 5 1.47797 5 1.47397 10 1.45516 5 1.44427 5 1.44128 5 Beidellite-12A 43-0688 Bei 12.4 100 6.2 50 4.48 80 3.1 60 2.57 25 2.238 5 2.064 10 1.693 10 1.496 10 Berthierine-1M 07-0315 K 7.05 100 4.67 20 4.58 20 4.28 5 3.9 10 3.52 100 2.8 1 2.68 40 2.52 90 2.4 40 2.34 5 2.27 5 2.14 60 2.01 10 1.894 10 1.769 40 1.7 1 1.665 5 1.555 70 1.521 30 1.473 10 Berthierine-1H 31-0618 K 7.12 100 4.68 50 4.3 20 3.93 30 3.55 100 3.07 20 2.71 50 2.53 100 2.15 70 1.779 60 1.563 70 1.526 50 1.481 60 Biotite-1M 42-1437 M 10.05 100 5.02 2 4.62 5 4.57 4 3.66 3 3.353 35 2.932 1 2.631 28 2.518 4 2.445 17 2.305 2 2.286 2 2.269 5 2.181 13 2.014 6 2.001 5 1.915 3 1.678 13 1.542 15 1.525 4 1.478 1 1.438 1 Biotite-1M, ferr. (DEL) 42-0603 M 10.02 100 4.58 30 3.93 25 3.669 35 3.399 35 3.334 80 3.142 35 2.919 30 2.702 10 2.626 60 2.504 15 2.438 55 2.305 5 2.265 5 2.209 2 2.175 30 2.002 25 1.993 15 1.906 10 1.671 50 1.647 2 1.617 10 1.582 5 1.539 60 1.534 40 1.522 15 1.472 15 Biotite-2M1 42-1339 M 10.0094 100 4.60186 7 4.56216 3 4.4989 4 3.79678 7 3.6591 3 3.52527 13 3.3935 5 3.33646 26 3.26467 15 3.14263 5 3.02483 11 2.91169 3 2.8057 6 2.65495 3 2.62424 39 2.60757 5 2.51186 2 2.50234 3 2.43604 23 2.43121 15 2.30609 2 2.2933 2 2.2641 6 2.22872 2 2.18164 2 2.17301 18 2.00187 3 1.99353 8 1.96617 5 1.90691 4 1.89839 2 1.67086 14 1.66448 8 1.6622 2 1.63234 2 1.59936 2 1.53851 14 1.53464 14 1.52763 5 1.52072 4 1.51241 2 1.47227 2 Hydrobiotite 13-0465 MV 15 100 12.5 80 4.5 60 3.4 40 2.75 20 2.6 40 Bornite 42-0586 Bn 6.31708 4 5.4707 3 4.78876 5 4.06223 10 3.86808 1 3.29928 30 3.26091 12 3.15861 38 3.00782 3 2.92436 1 2.80282 20 2.7335 42 2.6547 1 2.63288 5 2.5096 22 2.49363 22 2.44702 1 2.17692 3 2.10621 7 1.99622 1 1.95813 3 1.9345 100 1.89714 1 1.84961 3 1.79269 1 1.66883 3 1.66517 2 1.65027 8 1.64849 7 1.59254 1 1.59049 1 1.57957 4 1.53317 3 1.5302 4 1.47298 3 1.4713 3 Bornite_2 42-1405 Bn 6.304 12 5.474 10 4.782 10 4.07 9 3.868 9 3.3 36 3.267 13 3.159 56 3.01 8 2.804 15 2.737 35 2.5119 25 2.4966 23 2.1788 5 2.107 7 1.9591 10 1.9351 100 1.9057 8 1.8498 5 1.6707 5 1.6513 11 1.592 4 1.5802 3 1.5337 4 1.5302 4 1.4727 3 Brookite 29-1360 Brk 3.512 100 3.465 80 2.9 90 2.729 4 2.476 25 2.409 18 2.37 6 2.344 4 2.332 4 2.296 5 2.254 8 2.244 18 2.133 16 1.9685 16 1.8934 30 1.8514 18 1.8332 3 1.7568 3 1.6908 20 1.6617 30 1.6486 5 1.6098 13 1.5968 2 1.5408 7 1.4942 10 1.4729 4 1.4656 9 1.4609 12 1.4515 12 1.4415 6 Pseudobrookite, syn 41-1432 PBrk 4.99 13 4.901 42 3.486 100 3.294 6 2.859 4 2.752 77 2.496 1 2.458 23 2.451 16 2.407 22 2.386 1 2.224 10 2.204 3 2.199 12 2.029 1 1.9766 14 1.9733 22 1.8657 23 1.848 7 1.7516 8 1.7473 6 1.743 4 1.7335 1 1.7092 1 1.6634 10 1.6455 2 1.6383 11 1.6327 1 1.6112 3 1.5752 2 1.5521 8 1.5493 3 1.5474 3 1.5441 18 1.5387 17 1.5013 3 1.4939 1 1.4838 3 1.4678 1 1.4657 1 Brucite, syn 07-0239 Brc 4.77 90 2.725 6 2.365 100 1.794 55 1.573 35 1.494 18 Calcite, syn 05-0586 Cc 3.86 12 3.035 100 2.845 3 2.495 14 2.285 18 2.095 18 1.927 5 1.913 17 1.875 17 1.626 4 1.604 8 1.587 2 1.525 5 1.518 4 1.51 3 1.473 2 1.44 5 Calcite, magnesian 43-0697 Cc 3.82045 5 3.0042 100 2.80777 3 2.47217 12 2.26251 22 2.07418 13 1.90811 7 1.88915 28 1.85528 21 1.60992 3 1.58864 9 1.56825 1 1.50994 6 1.50135 3 1.49219 3 1.45861 2 Celestine, syn 05-0593 Clt 4.23 11 3.77 35 3.57 2 3.433 30 3.295 98 3.177 59 2.972 100 2.731 63 2.674 49 2.582 6 2.388 7 2.377 17 2.253 18 2.208 5 2.164 7 2.141 25 2.045 55 2.041 57 2.006 40 1.999 48 1.947 15 1.857 7 1.769 17 1.728 2 1.715 3 1.691 3 1.679 9 1.64 5 1.625 2 1.604 7 1.601 15 1.596 10 1.555 11 1.521 1 1.475 16 1.447 6 1.444 5 Chabazite, strontian 45-1427 Cbz 9.43 100 5.559 50 5.121 50 4.709 20 4.327 80 3.966 30 3.889 30 3.638 50 3.233 20 3.133 30 2.96 20 2.926 100 2.777 10 2.719 20 2.6 30 2.528 50 2.297 30 2.093 30 1.816 30 1.72 30 Chabazite 34-0137 Cbz 9.34 54 6.89 13 6.36 7 5.552 26 5.001 29 4.667 6 4.323 100 4.053 2 3.978 4 3.865 21 3.576 42 3.446 19 3.236 5 3.176 11 2.927 93 2.908 21 2.882 45 2.838 6 2.775 4 2.693 4 2.678 9 2.606 20 2.572 4 2.497 21 2.351 3 2.31 4 2.298 5 2.275 3 2.231 1 2.159 2 2.122 1 2.087 9 2.06 1 2.013 1 1.9455 1 1.9126 2 1.8664 9 1.8515 4 1.8035 18 1.7857 2 1.7689 3 1.7306 6 1.7236 10 1.6921 4 1.666 4 1.6454 7 1.5864 2 1.5559 7 1.5204 3 1.5155 4 Chalcopyrite 37-0471 Ccp 4.72 1 3.039 100 2.905 1 2.645 3 2.606 3 2.307 1 1.955 1 1.94 1 1.87 16 1.8561 25 1.6726 1 1.5926 12 1.5757 6 1.5723 2 1.5641 1 1.5193 1 1.4523 1 Chamosite-1MIbb 46-1324 Chm 14.2 90 7.1 100 4.73 30 4.67 30 3.543 80 2.835 30 2.696 30 2.649 20 2.52 90 2.341 10 2.149 60 2.023 5 1.953 5 1.778 40 1.77 1 1.62 5 1.558 55 1.52 40 1.48 30 Chlorellestadite 25-0167 Esd 8.24 8 5.31 4 4.12 8 3.92 6 3.45 40 3.18 6 3.11 18 2.835 95 2.796 40 2.746 100 2.645 25 2.554 4 2.312 2 2.284 40 2.168 4 2.07 2 2.009 2 1.958 30 1.909 15 1.85 25 1.823 10 1.797 10 1.768 8 1.725 8 1.658 4 1.621 4 1.603 4 1.557 4 1.545 2 1.518 4 1.51 2 1.486 6 1.461 8 1.446 8 Chl.-verm.-montm. 39-0381 CVM 14.2 10 7.7 50 4.78 10 4.44 50 3.5 100 2.88 6 2.56 15 2.5 15 2.34 20 1.98 6 1.82 4 1.66 6 Chromite, syn 34-0140 Chr 4.839 13 2.962 33 2.526 100 2.418 7 2.0943 22 1.7105 11 1.6125 39 1.4812 48 Chrysotile-2M1 43-0662 Ctl 7.302 100 4.585 40 3.652 80 2.655 30 2.587 20 2.543 30 2.452 80 2.276 10 2.209 10 2.091 60 1.763 30 1.535 30 Clinochlore-1MIIb 46-1323 Clc 14.2 90 7.1 100 4.72 40 4.62 5 4.52 3 4.25 3 4.05 1 3.88 3 3.69 1 3.535 90 3.3 1 3.14 1 2.95 1 2.83 20 2.665 10 2.592 55 2.55 60 2.445 55 2.387 45 2.31 3 2.262 30 2.2 1 2.065 7 2.02 10 2.005 40 1.882 20 1.824 15 1.745 5 1.714 3 1.662 20 1.564 40 1.543 65 1.508 30 1.468 10 Clinochlore-1MIIb_2 46-1322 Clc 14.2 90 7.1 100 4.75 50 4.58 25 4.39 10 4.22 2 4.06 7 3.86 2 3.67 2 3.554 70 3.31 2 3.13 1 2.97 1 2.84 20 2.655 2 2.58 25 2.54 50 2.438 40 2.38 20 2.257 18 2.195 2 2.065 2 2.03 2 2.01 20 2 30 1.884 18 1.875 5 1.831 5 1.823 15 1.74 5 1.718 1 1.71 1 1.67 1 1.662 3 1.614 5 1.568 15 1.561 5 1.537 50 1.518 2 1.5 20 1.482 1 1.46 8 1.438 1 Clinochlore-1MIIb,mang. 45-1321 Clc 14.2 70 7.11 100 4.741 25 4.643 5 3.559 80 2.846 10 2.66 5 2.605 35 2.562 50 2.46 35 2.399 25 2.274 15 2.067 5 2.017 40 1.894 10 1.834 5 1.671 5 1.574 20 1.551 30 1.518 15 Clinochlore-1MIIb,ferr. 29-0701 Clc 14.1 35 7.07 100 4.71 30 4.64 2 3.54 60 2.828 15 2.597 9 2.558 6 2.453 6 2.387 6 2.357 1 2.263 5 2.07 2 2.02 3 2.006 9 1.886 5 1.823 3 1.748 1 1.727 2 1.661 3 1.563 7 1.5477 4 1.51 1 Clinochlore-1MIa,ferr. 16-0362 Clc 14.2 60 7.1 100 4.73 40 4.63 10 3.55 80 2.84 30 2.66 40 2.59 5 2.55 10 2.395 60 2.27 10 2.07 5 2.01 30 1.89 5 1.76 5 1.67 25 1.57 10 1.549 60 1.515 30 1.472 10 1.439 5 Clinochlore-1MIb,ferr. 16-0351 Clc 14.4 60 7.15 100 4.79 40 4.63 40 3.59 70 2.87 25 2.68 40 2.61 15 2.55 5 2.475 60 2.39 10 2.29 10 2.2 5 2.105 20 2.045 5 2.01 10 1.91 5 1.758 20 1.74 10 1.548 60 1.515 30 1.478 10 Clinochlore-2A, chrom. 20-0671 Clc 14.5 90 7.21 100 4.81 80 4.62 80 4.41 20 4.18 30 3.9 20 3.6 90 3.33 10 3.08 5 2.878 40 2.664 30 2.626 80 2.575 40 2.504 80 2.424 100 2.34 70 2.249 10 2.143 10 2.05 60 1.958 50 1.786 40 1.749 15 1.738 15 1.704 30 1.629 45 1.558 30 1.541 90 1.507 45 1.468 20 1.437 10 Clinoptilolite 39-1383 Cpt 8.95 100 7.93 13 6.78 9 5.94 3 5.59 5 5.24 10 5.12 12 4.65 19 4.35 5 3.976 61 3.955 63 3.905 48 3.835 7 3.738 6 3.707 5 3.554 9 3.513 4 3.424 18 3.392 12 3.316 6 3.17 16 3.12 15 3.074 9 2.998 18 2.971 47 2.795 16 2.73 16 2.667 4 2.527 6 2.485 3 2.458 3 2.437 8 2.422 5 2.319 2 2.089 3 2.056 2 2.016 2 1.974 4 Corrensite 31-0794 Crr 29 30 14 100 7.08 60 4.72 30 4.62 30 3.53 60 3.34 10 2.83 30 2.66 10 2.61 10 2.57 30 2.42 30 2.32 10 2.24 10 2.03 30 1.54 30 1.51 10 Corundum, syn 43-1484 Crn 3.48 72 2.551 98 2.38 44 2.165 1 2.086 100 1.9643 2 1.7401 48 1.6015 96 1.5467 3 1.5149 4 1.5109 9 Mg Al2 O4 (DEL) 10-0062 MgAl2O4 4.66 60 2.85 50 2.43 90 2.01 70 1.674 20 1.555 70 Cristobalite, syn 39-1425 Crs 4.03974 100 3.5147 1 3.13592 8 2.84116 9 2.4874 13 2.4675 4 2.3417 1 2.11791 2 2.01957 2 1.92935 4 1.87147 4 1.75907 1 1.73033 1 1.69221 2 1.63488 1 1.61217 3 1.60131 1 1.57207 1 1.56745 1 1.53356 2 1.4952 2 Daubreelite, syn 04-0651 Dbr 3.53 80 3.01 100 2.89 40 2.5 80 2.29 40 2.04 60 1.92 80 1.77 100 1.69 40 1.58 60 1.524 60 1.51 40 1.44 60 Dawsonite, syn 45-1359 Daw 5.67437 100 4.92295 1 3.38038 12 3.31883 5 3.09054 4 2.95135 1 2.83561 1 2.78747 50 2.60631 16 2.50491 12 2.22376 2 2.15285 14 2.07139 2 1.99258 12 1.95359 6 1.90456 1 1.89063 1 1.83225 2 1.73014 10 1.69137 6 1.6599 9 1.62179 2 1.60789 1 1.56596 1 1.54562 2 1.53065 1 1.47543 2 Dawsonite 19-1175 Daw 5.67 100 4.94 5 4.51 5 3.38 60 3.31 20 3.09 20 2.95 5 2.834 5 2.784 90 2.601 70 2.5 40 2.221 30 2.151 60 2.066 20 1.988 70 1.949 40 1.909 5 1.892 5 1.836 10 1.728 70 1.689 50 1.656 60 1.62 20 1.607 10 1.565 5 1.542 30 1.527 10 1.473 30 Dickite-2M1 10-0446 Dck 7.15 100 4.46 40 4.438 40 4.366 40 4.267 30 4.124 70 3.961 20 3.799 60 3.58 100 3.43 30 3.262 20 3.099 20 2.938 20 2.796 20 2.562 20 2.556 20 2.528 5 2.51 50 2.411 5 2.386 10 2.326 90 2.212 20 2.106 10 2.025 5 1.975 50 1.937 10 1.898 20 1.859 30 1.805 10 1.785 10 1.762 5 1.72 10 1.686 10 1.652 50 1.613 10 1.586 10 1.555 40 1.508 5 1.489 50 Diopside 41-1370 Di 4.683 3 4.434 6 4.411 3 3.666 2 3.339 6 3.22 50 2.985 100 2.943 55 2.891 30 2.819 2 2.555 20 2.54 20 2.53 16 2.512 30 2.382 2 2.301 14 2.228 4 2.223 9 2.217 5 2.204 6 2.146 16 2.125 18 2.102 12 2.032 12 2.018 7 2.012 7 2.003 3 1.969 6 1.855 2 1.832 9 1.742 9 1.671 5 1.63 6 1.624 4 1.62 10 1.61 5 1.56 3 1.5418 4 1.5291 3 1.5236 5 1.5075 3 1.5047 5 1.4779 3 Dolomite 36-0426 Dol 4.033 1 3.699 4 2.888 100 2.67 4 2.539 3 2.404 7 2.193 19 2.065 3 2.015 10 2.006 1 1.8473 3 1.8049 10 1.787 13 1.78 2 1.7461 1 1.5667 2 1.5446 4 1.5403 1 1.4955 1 1.4652 2 1.4435 2 Clinoenstatite, syn 35-0610 Cen 4.41001 9 4.37194 3 4.29306 8 3.53033 4 3.31621 8 3.28038 30 3.17021 47 3.04062 4 2.97621 69 2.87394 100 2.79836 3 2.53895 23 2.52291 19 2.51579 17 2.47157 13 2.45518 35 2.43296 14 2.37523 11 2.2805 4 2.20909 9 2.20361 9 2.14425 6 2.13795 5 2.11659 26 2.09005 5 2.02475 6 2.01931 10 2.01141 9 1.98618 6 1.96545 4 1.93486 8 1.92384 5 1.78616 9 1.76401 7 1.7586 8 1.73109 7 1.65947 5 1.64801 5 1.64124 7 1.6054 30 1.592 4 1.5852 5 1.52568 9 1.52006 7 1.48572 10 1.47567 10 1.46947 11 Enstatite, syn 19-0768 En 6.33 4 4.41 20 3.3 10 3.17 75 3.15 50 2.936 2 2.87 100 2.822 8 2.796 2 2.702 8 2.53 18 2.49 8 2.468 10 2.378 2 2.353 2 2.277 8 2.248 2 2.2286 2 2.1105 8 2.0919 10 2.057 2 2.0513 2 2.023 2 2.0136 2 1.9823 4 1.9782 4 1.9548 10 1.8841 2 1.8499 2 1.8342 2 1.7849 6 1.7697 2 1.7308 6 1.6995 2 1.6785 2 1.6062 2 1.6014 2 1.5948 2 1.5868 4 1.5833 2 1.5253 4 1.5185 14 1.4839 20 1.4696 25 Erionite 39-1379 Eri 11.56 76 9.18 5 7.55 9 6.65 48 6.31 6 5.76 28 5.39 20 4.6 26 4.58 30 4.35 78 4.18 28 3.839 60 3.771 94 3.586 57 3.423 5 3.323 45 3.291 17 3.277 13 3.193 13 3.153 26 3.122 11 2.936 13 2.88 76 2.851 100 2.826 65 2.688 28 2.513 33 2.494 28 2.215 19 2.125 13 2.091 7 1.994 7 Fayalite, syn 34-0178 Fa 5.242 6 4.38 9 3.974 11 3.783 7 3.556 55 3.064 6 3.046 7 2.829 86 2.633 32 2.619 23 2.565 45 2.5 100 2.407 25 2.351 10 2.311 23 2.303 21 2.1927 9 2.1532 7 2.0723 8 1.9855 5 1.922 5 1.8418 12 1.8327 10 1.7781 79 1.7735 65 1.7624 6 1.7032 13 1.6789 15 1.6496 17 1.6253 17 1.6044 9 1.5885 6 1.5367 11 1.5331 10 1.5225 26 1.5151 31 1.4896 3 1.4602 9 1.4383 2 Fayalite, magnesian 31-0633 Fa 5.21 20 3.945 20 3.762 10 3.535 30 3.045 5 3.027 10 2.81 100 2.616 20 2.599 10 2.549 60 2.489 70 2.4 5 2.389 20 2.34 5 2.297 20 2.286 30 2.184 10 2.062 5 1.974 5 1.908 10 1.834 5 1.82 10 1.769 40 1.761 10 1.753 5 1.693 10 1.665 20 1.64 10 1.615 10 1.595 10 1.531 5 1.525 5 1.515 20 1.504 20 Ferrihydrite 1, syn 46-1315 Fh 4.7 54 3.13 89 2.52 14 2.23 2 1.98 1 1.88 100 1.72 1 1.51 2 Ferrihydrite 2, syn 29-0712 Fh 2.5 100 2.21 80 1.96 80 1.72 50 1.51 70 1.48 80 Ferrohexahydrite 15-0393 Fhh 6.05 20 5.47 30 5.12 20 4.89 60 4.43 100 4.32 10 4.17 20 4.05 40 3.62 40 3.49 20 3.4 20 3.21 40 3.05 20 2.97 70 2.93 70 2.8 50 2.76 50 2.6 20 2.55 40 2.3 50 2.22 30 2.16 10 2.09 40 2.07 10 2.03 60 1.998 10 1.981 20 1.935 20 1.881 60 1.862 60 1.825 10 1.805 10 1.783 10 1.768 20 1.736 20 1.672 30 1.65 20 1.625 30 1.601 10 1.541 10 1.516 30 1.503 30 1.484 10 1.471 30 1.455 30 Fluorite, syn 35-0816 Fl 3.15461 92 2.73141 1 1.9316 100 1.64714 33 1.57706 1 Galena, syn 05-0592 Gn 3.429 84 2.969 100 2.099 57 1.79 35 1.714 16 1.484 10 Gehlenite, syn 35-0755 Gh 5.43297 4 5.06853 3 4.23113 2 3.84196 1 3.70691 15 3.43651 2 3.06288 21 2.8446 100 2.71754 5 2.53388 4 2.43055 17 2.40699 11 2.39437 12 2.29681 7 2.28689 11 2.19173 2 2.11546 1 2.03982 9 1.96513 2 1.92134 8 1.86409 4 1.85347 2 1.81154 8 1.7964 1 1.7542 25 1.74946 17 1.71861 6 1.70591 3 1.6891 1 1.64983 1 1.63136 2 1.6271 2 1.61295 2 1.54626 1 1.53094 1 1.51621 9 1.50741 1 1.50131 1 1.47353 2 1.47101 2 1.44477 2 Gibbsite, syn 33-0018 Gbs 4.8486 100 4.3711 70 4.3187 50 3.359 17 3.3122 30 3.1829 25 3.1054 13 2.4658 25 2.4522 40 2.4224 15 2.3851 55 2.3471 4 2.2899 15 2.2464 20 2.1924 2 2.1647 27 2.0845 4 2.0489 40 2.0234 3 1.9944 28 1.9637 6 1.92 15 1.8042 30 1.7517 30 1.7365 4 1.6974 4 1.6845 30 1.6576 9 1.5926 7 1.5865 7 1.5739 8 1.5525 3 1.4846 4 1.4577 30 1.4405 18 Glauconite-1M 09-0439 Glt 10.1 100 4.98 1 4.53 80 4.35 20 4.12 10 3.63 40 3.33 60 3.09 40 2.89 5 2.67 10 2.587 100 2.396 60 2.263 20 2.213 10 2.154 20 1.994 20 1.817 5 1.715 10 1.66 30 1.511 60 1.495 10 Glauconite-1M, chrom. 45-1337 Glt 9.97 100 4.98 5 4.53 87 4.37 42 4.15 15 3.66 60 3.34 100 3.09 47 2.87 8 2.68 19 2.59 100 2.4 39 2.25 22 2.21 15 2.16 28 1.993 31 1.514 47 1.496 12 Goethite 29-0713 Gth 4.98 12 4.183 100 3.383 10 2.693 35 2.583 12 2.527 4 2.489 10 2.45 50 2.303 1 2.253 14 2.19 18 2.089 1 2.011 2 1.92 5 1.802 6 1.7728 1 1.7192 20 1.6906 6 1.6593 3 1.6037 4 1.5637 10 1.5614 8 1.5091 8 1.4675 2 1.4541 5 Graphite-2H 41-1487 Gr 3.37563 100 2.13865 2 2.03901 6 1.80737 1 1.6811 4 1.54777 1 Graphite-3R, syn [NR] 26-1079 Gr 3.348 100 2.081 11 1.958 9 1.674 6 1.623 4 1.46 2 Greigite 16-0713 Grg 5.72 8 3.5 30 2.98 100 2.855 4 2.47 55 2.26 2 2.017 10 1.901 30 1.746 75 1.671 1 1.5625 10 1.5058 10 1.4883 2 Gypsum 21-0816 Gp 7.61 45 4.74 4 4.28 90 3.8 8 3.17 4 3.07 30 2.871 100 2.788 20 2.684 50 2.595 2 2.496 20 2.475 2 2.454 6 2.406 2 2.22 6 2.142 2 2.087 14 2.073 20 2.048 4 1.993 2 1.954 6 1.9 4 1.88 6 1.865 4 1.812 4 1.798 6 1.778 4 1.685 2 1.664 4 1.646 2 1.622 4 1.587 2 1.552 2 1.521 2 1.44 4 Halite, syn 05-0628 Hl 3.26 13 2.821 100 1.994 55 1.701 2 1.628 15 Halloysite-10A 29-1489 Hal 10 100 4.36 70 3.35 40 2.54 35 1.672 14 1.48 30 Halloysite-10A_2 09-0451 Hal 10.1 90 4.42 100 3.34 90 2.56 80 2.36 60 2.23 20 2.06 10 1.678 80 1.481 90 Halloysite-7A 29-1487 Hal 7.3 65 4.42 100 3.62 60 2.56 25 2.37 1 1.681 16 1.483 30 Halloysite-7A_2 09-0453 Hal 7.5 90 4.42 100 3.63 90 2.56 80 2.36 60 2.23 20 2.06 10 1.678 80 1.481 90 Hectorite-15A 09-0031 Sme 15.8 80 4.58 100 3.24 40 3.09 40 2.66 80 2.48 60 2.29 20 1.72 60 1.69 60 1.53 100 Hematite, syn 33-0664 Hem 3.684 30 2.7 100 2.519 70 2.292 3 2.207 20 2.0779 3 1.8406 40 1.6941 45 1.6367 1 1.6033 5 1.5992 10 1.4859 30 1.4538 30 Hercynite, syn 34-0192 Hc 4.709 3 2.883 58 2.4597 100 2.0382 17 1.8711 5 1.6649 16 1.5691 36 1.4414 42 Heulandite 41-1357 Hul 8.96 100 7.94 12 6.81 6 6.65 5 5.94 4 5.59 3 5.33 7 5.26 10 5.12 16 5.08 14 4.65 32 4.47 5 4.37 9 3.98 65 3.897 43 3.843 11 3.735 9 3.717 9 3.564 9 3.479 8 3.429 21 3.405 15 3.325 7 3.181 19 3.125 22 3.075 12 2.992 29 2.972 91 2.89 4 2.806 23 2.725 20 2.667 7 2.527 5 2.519 10 2.486 6 2.458 7 2.434 10 2.374 5 2.296 5 2.229 9 2.18 5 2.088 4 2.075 3 2.019 4 1.96 9 Hexahydrite, syn 24-0719 Hh 6.05 6 5.8 8 5.58 4 5.45 50 5.1 45 5.01 4 4.88 30 4.56 8 4.39 100 4.16 35 4.04 45 3.893 10 3.61 20 3.569 2 3.459 14 3.387 12 3.293 4 3.197 16 3.098 4 3.034 8 2.968 4 2.941 30 2.901 30 2.828 4 2.793 8 2.773 12 2.732 2 2.691 8 2.678 8 2.597 4 2.576 6 2.524 12 2.469 2 2.441 2 2.417 2 2.337 4 2.318 2 2.28 8 2.205 4 2.069 4 2.012 8 1.993 4 1.979 4 1.879 8 1.865 6 1.819 4 1.8 4 1.783 4 1.763 4 1.697 4 Hornblende (Fe), sod. 29-1258 Hbl 8.52 100 4.94 10 4.83 10 4.54 25 4.03 1 3.93 2 3.413 50 3.309 30 3.159 90 2.967 10 2.831 15 2.765 5 2.728 75 2.61 50 2.565 25 2.406 10 2.36 30 2.309 10 2.18 40 2.057 10 2.034 25 1.876 1 1.835 2 1.815 2 1.7 1 1.667 30 1.651 1 1.626 10 1.605 5 1.593 5 1.57 1 1.552 1 1.526 15 1.515 15 1.481 2 1.468 2 1.452 40 Hornblende (Mg) 20-0481 Hbl 8.96 6 8.4 100 5.06 2 4.9 4 4.5 10 4.19 4 4.02 2 3.87 2 3.39 10 3.26 20 3.1 70 2.939 10 2.789 12 2.697 20 2.587 8 2.537 6 2.366 4 2.325 8 2.302 2 2.272 2 2.21 2 2.155 8 2.051 2 2.012 4 1.988 2 1.957 2 1.924 2 1.88 4 1.858 2 1.798 2 1.737 2 1.707 2 1.681 2 1.644 6 1.627 2 1.61 2 1.582 4 1.555 2 1.524 2 Hornblende (Mg,Fe) 45-1371 Hbl 9.02862 6 8.40676 95 5.09397 4 4.92002 5 4.76175 2 4.51803 10 4.21045 3 3.89641 3 3.38888 15 3.27594 35 3.12273 100 2.94232 15 2.80624 14 2.73963 10 2.71138 39 2.59958 15 2.55802 10 2.5483 33 2.54459 5 2.38328 8 2.37685 3 2.34391 12 2.32699 8 2.28726 5 2.22017 2 2.18348 3 2.1651 11 2.04856 5 2.01847 10 2.00269 6 1.893 3 1.86829 3 1.81437 4 1.65028 11 1.64392 3 1.63878 6 1.61938 5 1.58576 6 1.55725 3 1.53851 4 1.52011 3 1.50673 3 1.50129 5 1.45892 4 1.44064 19 1.43943 4 1.4366 3 Huntite 14-0409 Hun 5.67 2 4.75 2 3.64 2 3.533 1 2.888 20 2.833 100 2.744 2 2.604 12 2.432 10 2.375 8 2.284 6 2.19 6 1.991 10 1.972 30 1.896 6 1.835 2 1.821 2 1.796 2 1.765 20 1.757 20 1.7 2 1.656 1 1.611 1 1.584 10 1.526 2 1.518 2 1.481 4 1.453 2 1.445 2 Illite-2M1 26-0911 Ilt 10 90 5.02 50 4.48 16 4.44 14 3.89 8 3.72 12 3.46 14 3.34 100 3.2 16 2.988 18 2.867 12 2.799 12 2.558 12 2.509 8 2.463 8 2.241 4 2.005 50 1.499 14 Illite-2M2 43-0685 Ilt 10.25 61 5.06 47 4.49 67 4.37 17 4.31 6 3.966 6 3.921 23 3.681 48 3.52 37 3.348 64 3.211 31 3.066 44 2.946 17 2.869 24 2.812 20 2.583 100 2.513 11 2.45 18 2.426 17 2.402 16 2.285 4 2.252 9 2.21 8 2.186 8 2.082 22 2.055 17 2.006 33 1.75 3 1.717 11 1.694 17 1.668 17 1.633 9 1.614 9 1.585 9 1.569 6 1.5 33 1.481 4 Illite-1M 29-1496 Ilt 10.7 35 5 30 4.43 100 4.33 30 4.11 14 3.66 40 3.31 35 3.06 40 2.931 16 2.675 12 2.56 85 2.445 16 2.386 25 2.242 12 2.216 10 2.151 12 2.093 8 2.023 8 1.989 12 1.957 8 1.903 3 1.82 3 1.688 10 1.663 12 1.631 12 1.585 4 1.536 4 1.496 40 Illite-1M, ammonian 46-1344 Ilt 10.3 100 5.13 42 4.49 100 4.36 55 4.12 21 3.69 72 3.41 58 3.1 55 2.97 9 2.718 17 2.57 75 2.492 8 2.451 17 2.409 20 2.37 9 2.25 9 2.222 8 2.17 12 2.049 12 1.968 8 1.919 4 1.845 5 1.691 19 1.503 31 Illite-montmorillonite 35-0652 I-S 12.6 70 9.5 90 5.17 40 4.46 30 3.33 100 Ilmenite, syn 29-0733 Ilm 3.737 30 2.754 100 2.544 70 2.349 2 2.237 30 2.1772 2 2.1032 2 1.8683 40 1.8309 1 1.7261 55 1.6535 2 1.6354 9 1.6206 3 1.5057 30 1.4686 35 Iron, syn 06-0696 Fe 2.0268 100 1.4332 20 Jarosite, syn 22-0827 Jrs 5.93 45 5.72 25 5.09 70 3.65 40 3.55 4 3.11 75 3.08 100 3.02 6 2.965 15 2.861 30 2.542 30 2.368 4 2.302 12 2.287 40 1.977 45 1.937 10 1.909 8 1.825 45 1.776 6 1.738 6 1.717 6 1.69 2 1.656 2 1.621 6 1.595 6 1.572 4 1.56 6 1.552 6 1.536 20 1.507 20 1.48 8 1.442 4 Kaolinite-1A 14-0164 Kln 7.17 100 4.478 35 4.366 60 4.186 45 4.139 35 3.847 40 3.745 25 3.579 80 3.376 35 3.155 20 3.107 20 2.754 20 2.566 35 2.553 25 2.535 35 2.495 45 2.385 25 2.347 40 2.338 40 2.293 35 2.253 20 2.197 20 2.186 20 2.133 20 2.064 20 1.997 35 1.987 35 1.974 20 1.952 20 1.939 35 1.921 20 1.897 25 1.87 20 1.845 25 1.838 35 1.789 25 1.71 25 1.689 25 1.681 25 1.669 40 1.66 40 1.649 40 1.633 30 1.62 70 1.607 30 1.586 60 1.553 30 1.545 40 1.537 40 1.489 90 Kaolinite-1Md 29-1488 Kln 7.1 100 4.41 60 3.56 100 2.551 25 2.491 30 2.43 5 2.375 20 2.327 40 2.2 5 1.98 10 1.888 6 1.787 6 1.679 12 1.665 16 1.54 3 1.488 30 1.455 5 Kaolinite-montmorill. 29-1490 K-S 9.5 12 7.24 100 4.31 65 3.55 65 3.19 8 2.553 35 2.498 35 2.319 45 2.18 5 1.99 10 1.83 4 1.665 16 1.626 8 1.55 3 1.488 25 Kyanite 11-0046 Ky 6.7 3 5.89 3 4.42 5 4.3 25 3.77 20 3.44 5 3.35 65 3.18 100 3.02 15 2.947 20 2.727 9 2.699 25 2.694 25 2.612 7 2.602 3 2.52 30 2.509 20 2.46 5 2.355 30 2.35 30 2.331 20 2.272 11 2.233 9 2.214 15 2.181 7 2.168 5 2.163 20 2.151 3 2.006 7 1.973 3 1.962 55 1.935 50 1.93 50 1.883 5 1.865 3 1.846 3 1.764 11 1.747 3 1.676 9 1.65 3 1.621 7 1.593 20 1.506 7 1.502 9 1.477 5 1.475 15 1.452 5 Larnite, syn 33-0302 Lrn 4.641 9 3.824 5 3.786 5 3.378 7 3.241 6 3.176 5 3.049 9 2.877 21 2.814 22 2.79 97 2.783 100 2.745 83 2.718 30 2.61 42 2.545 9 2.448 12 2.433 9 2.41 13 2.403 18 2.301 4 2.281 22 2.189 51 2.165 13 2.129 7 2.091 6 2.083 6 2.05 14 2.037 9 2.027 15 2.02 15 1.987 20 1.982 24 1.9115 6 1.8979 9 1.8935 11 1.8441 4 1.8051 9 1.8018 9 1.7899 7 1.727 5 1.7067 10 1.6964 5 1.6889 5 1.6282 12 1.6146 8 1.611 10 1.604 11 1.5874 6 1.5839 7 1.5738 5 Laumontite, syn 26-1047 Lmt 9.5 60 6.84 40 6.19 9 5.04 12 4.72 17 4.49 25 4.16 100 3.77 7 3.66 35 3.51 80 3.4 13 3.36 30 3.27 55 3.2 30 3.1 2 3.03 35 2.95 7 2.88 25 2.798 25 2.649 2 2.631 2 2.579 25 2.539 3 2.52 4 2.519 10 2.457 8 2.441 25 2.392 3 2.359 13 2.271 9 2.219 10 2.18 9 2.154 13 2.09 6 1.992 5 1.961 11 1.89 2 Laumontite_1 45-1325 Lmt 9.48157 100 6.87043 38 6.20602 9 5.04929 3 4.74078 11 4.50502 14 4.16158 22 3.77175 3 3.67675 24 3.50707 48 3.41314 7 3.36801 9 3.275 13 3.20888 8 3.16052 5 3.10301 2 3.04221 11 2.89045 4 2.80425 7 2.63745 2 2.57947 8 2.54336 3 2.51994 5 2.4646 5 2.44553 10 2.37039 4 2.3631 4 2.35189 2 2.27422 2 2.22499 5 2.18333 3 2.16181 8 2.14802 4 2.0932 2 2.08789 2 1.99697 3 1.97033 2 1.96288 3 1.88588 2 1.87306 4 1.8535 4 1.84265 3 1.83463 2 1.76373 3 1.64153 3 1.63758 4 1.63037 4 1.52499 4 1.52163 3 1.44277 3 Laumontite_2 46-1389 Lmt 9.48 100 6.85 26 5.05 3 4.73 14 4.51 3 4.16 23 3.664 6 3.509 10 3.414 4 3.367 2 3.275 12 3.202 5 3.153 11 3.033 13 2.882 4 2.8 2 2.575 5 2.441 5 2.364 6 2.271 3 2.182 3 2.154 7 Lepidocrocite, syn 44-1415 Lep 6.27 61 3.294 100 2.981 8 2.473 76 2.434 34 2.362 36 2.086 9 1.9404 53 1.9351 72 1.8502 12 1.8375 8 1.735 21 1.6238 5 1.565 3 1.5344 33 1.5248 30 1.4915 5 1.451 5 1.4357 8 Lepidocrocite 08-0098 Lep 6.26 100 3.29 90 2.97 10 2.47 80 2.36 20 2.09 20 1.937 70 1.848 20 1.732 40 1.566 20 1.535 20 1.524 40 1.496 10 1.449 10 Lepidolite-12O 15-0062 Lpd 9.94 80 4.98 80 4.45 40 4.14 10 3.78 10 3.51 40 3.36 10 3.31 100 3.16 80 3.09 20 2.9 60 2.85 40 2.79 40 2.59 100 2.55 20 2.48 40 2.41 20 2.25 40 2.19 20 2.15 20 2.08 20 2.02 10 1.982 100 1.698 40 1.674 10 1.665 40 1.654 10 1.628 20 1.59 20 1.557 10 1.526 10 1.5 80 Lepidolite-1M 38-0425 Lpd 9.97 40 4.981 35 4.498 50 4.329 20 4.107 20 3.847 20 3.615 70 3.329 70 3.086 70 2.895 35 2.669 30 2.594 80 2.565 100 2.479 20 2.452 30 2.389 40 2.247 25 2.217 7 2.193 10 2.141 30 2.125 20 2.102 10 2.051 2 1.994 40 1.955 20 1.809 10 1.734 5 1.715 12 1.657 30 1.637 25 1.619 10 1.582 20 1.546 10 1.524 2 1.501 50 1.48 10 1.461 3 1.444 3 Lepidolite-2M2 43-0692 Lpd 10.01 70 5.002 40 4.464 70 4.338 10 3.883 20 3.638 40 3.495 40 3.338 60 3.205 40 3.119 5 3.075 40 2.91 25 2.872 30 2.794 30 2.578 100 2.502 8 2.429 40 2.317 3 2.293 3 2.253 10 2.211 4 2.182 6 2.07 10 2.041 10 2.001 40 1.821 5 1.701 10 1.689 5 1.674 5 1.633 10 1.611 5 1.584 8 1.572 5 1.545 3 1.521 2 1.502 60 1.478 2 Lepidolite-2M1 24-0594 Lpd 9.98 40 4.98 25 4.48 50 4.39 12 4.12 6 3.97 25 3.88 25 3.73 50 3.59 6 3.48 50 3.32 6 3.2 60 3.12 12 2.981 60 2.866 25 2.782 40 2.668 6 2.589 100 2.561 100 2.489 6 2.463 12 2.383 75 2.251 40 2.232 6 2.211 25 2.199 6 2.178 12 2.137 50 2.071 12 2.053 6 2.032 6 1.988 25 1.967 12 1.957 6 1.769 6 1.743 6 1.725 12 1.713 6 1.701 12 1.646 40 1.626 25 1.608 12 1.597 6 1.584 6 1.567 12 1.562 12 1.553 6 1.531 6 1.516 6 1.504 100 Lime, syn 37-1497 Lime 2.77737 36 2.40587 100 1.70093 54 1.4505 16 Magnetite, syn 19-0629 Mag 4.852 8 2.967 30 2.532 100 2.4243 8 2.0993 20 1.7146 10 1.6158 30 1.4845 40 Maghemite-C syn 39-1346 Mgh 5.918 5 4.822 4 3.74 5 3.411 5 2.953 35 2.784 2 2.6435 2 2.5177 100 2.4119 3 2.3163 1 2.232 1 2.0886 16 2.0255 1 1.9685 1 1.8224 2 1.7045 10 1.6703 1 1.6379 1 1.6073 24 1.5507 1 1.5248 2 1.4758 34 1.4537 1 Magnesite, syn 08-0479 Mgs 2.742 100 2.503 18 2.318 4 2.102 45 1.939 12 1.769 4 1.7 35 1.51 4 1.488 6 Hydromagnesite 25-0513 Hmgs 9.2 40 6.4 40 5.79 100 4.58 7 4.46 17 4.186 30 4.09 12 4.022 8 3.812 13 3.503 14 3.317 30 3.207 16 3.142 20 3.101 11 3.088 10 3.063 6 2.919 11 2.899 80 2.84 6 2.779 7 2.692 25 2.637 5 2.556 5 2.543 4 2.529 9 2.504 20 2.478 10 2.469 10 2.442 5 2.417 7 2.387 2 2.35 14 2.298 35 2.233 4 2.207 25 2.189 11 2.185 11 2.174 7 2.161 20 2.154 25 2.146 19 2.137 12 2.04 6 2.025 8 2.012 5 1.994 25 1.988 19 1.974 4 1.968 4 1.962 5 Marcasite 37-0475 Mrc 3.439 60 2.873 2 2.712 35 2.693 100 2.413 45 2.315 40 2.221 1 2.055 5 1.912 30 1.757 70 1.718 10 1.693 20 1.675 16 1.595 25 1.5325 7 1.5187 6 1.5015 9 1.4362 6 Microcline, ordered 19-0926 Mc 6.75 6 6.48 8 5.94 6 5.81 4 4.22 100 3.99 8 3.92 4 3.86 8 3.83 25 3.7 40 3.66 10 3.6 10 3.57 4 3.49 30 3.47 20 3.37 40 3.29 60 3.26 80 3.25 80 3.24 40 3.03 14 2.967 8 2.954 20 2.907 18 2.89 10 2.784 4 2.755 8 2.621 20 2.587 12 2.57 4 2.552 4 2.54 4 2.522 25 2.501 4 2.433 12 2.43 12 2.336 6 2.332 6 2.159 30 2.118 4 2.112 6 2.044 4 2.032 4 1.992 4 1.985 10 1.927 8 1.914 6 1.805 18 1.802 18 1.793 6 Microcline, intermed. 19-0932 Mc 6.66 2 6.57 2 6.48 8 5.89 4 5.84 2 4.59 2 4.22 45 3.96 8 3.93 6 3.85 2 3.8 20 3.74 14 3.63 6 3.59 4 3.56 2 3.51 2 3.48 16 3.47 12 3.33 14 3.29 50 3.24 100 3.01 10 2.974 14 2.949 2 2.916 2 2.902 14 2.896 8 2.773 6 2.761 8 2.608 6 2.591 14 2.566 2 2.556 6 2.55 6 2.522 2 2.505 2 2.423 2 2.401 4 2.361 2 2.328 4 2.277 2 2.207 2 2.161 30 2.111 6 2.05 4 1.996 4 1.912 4 1.802 25 1.795 6 1.621 4 Montmorillonite-14A 13-0259 Mnt 13.6 100 4.47 18 3.34 10 3.23 10 2.92 8 2.59 5 2.49 5 Montmorillonite-15A 29-1498 Mnt 13.6 100 5.16 12 4.46 65 2.56 18 1.69 8 1.495 12 Montmorillonite-15A 13-0135 Mnt 15 100 5.01 60 4.5 80 3.77 20 3.5 10 3.3 10 3.02 60 2.58 40 2.5 40 2.26 10 2.15 10 1.88 10 1.7 30 1.5 50 1.493 50 Montmorillonite-18A 12-0219 Mnt 17.6 100 9 50 5.99 10 4.49 80 3.58 40 2.99 30 2.57 40 2.242 10 1.989 5 1.699 20 1.504 60 Montmorillonite-21A 29-1499 Mnt 21.5 100 10.6 18 4.45 55 2.56 35 1.69 8 1.495 25 Mordenite_1 29-1257 Mor 13.6 18 10.3 5 9.06 100 6.59 14 6.4 17 6.07 4 5.8 18 4.88 3 4.6 2 4.53 30 4.46 2 4.15 8 4 70 3.84 7 3.77 4 3.63 3 3.57 4 3.53 2 3.48 45 3.42 11 3.39 35 3.29 3 3.22 40 3.2 35 3.16 2 3.1 4 3.02 2 2.942 5 2.895 13 2.741 2 2.715 2 2.701 5 2.633 3 2.565 10 2.521 7 2.459 4 2.436 2 2.232 2 2.166 2 2.052 7 2.035 2 1.954 5 1.935 3 1.932 3 1.882 4 1.811 10 1.807 6 1.795 3 1.528 4 1.447 4 Mordenite_2 06-0239 Mor 13.7 50 9.1 90 6.61 90 6.38 40 6.1 50 5.79 50 5.03 10 4.87 20 4.53 80 4.14 30 4 90 3.84 60 3.76 20 3.62 10 3.56 10 3.48 100 3.39 90 3.31 10 3.22 100 3.1 20 2.946 20 2.896 60 2.743 10 2.7 30 2.639 10 2.56 40 2.522 50 2.465 20 2.437 20 2.343 20 2.299 10 2.275 10 2.228 20 2.162 20 2.123 10 2.047 40 2.019 40 1.998 40 1.953 40 1.936 10 1.917 20 1.883 40 1.865 10 1.85 10 1.813 30 1.795 30 1.765 10 1.738 10 1.72 20 Mullite, syn 15-0776 Mul 5.39 50 3.774 8 3.428 95 3.39 100 2.886 20 2.694 40 2.542 50 2.428 14 2.393 2 2.308 4 2.292 20 2.206 60 2.121 25 2.106 8 1.969 2 1.923 2 1.887 8 1.863 2 1.841 10 1.7954 2 1.7125 6 1.7001 14 1.694 10 1.5999 20 1.5786 12 1.5644 2 1.5461 2 1.5242 35 1.5067 2 1.4811 2 1.4731 2 1.4605 8 1.4421 18 Muscovite-1M, syn 07-0025 Ms 10.1 100 5.04 35 4.49 90 4.35 25 4.11 16 3.66 60 3.36 100 3.07 50 2.929 6 2.689 16 2.582 50 2.565 90 2.55 20 2.45 12 2.405 4 2.38 12 2.246 8 2.219 8 2.191 4 2.156 20 2.109 6 2.013 30 1.957 8 1.9 4 1.668 18 1.653 12 1.635 12 1.514 4 1.499 35 Muscovite-1M, magn. 21-0993 Ms 9.91 85 4.95 30 4.5 100 4.33 45 4.1 18 3.82 2 3.62 65 3.33 55 3.29 55 3.06 65 2.887 18 2.657 30 2.587 50 2.564 75 2.471 6 2.447 10 2.389 40 2.37 25 2.25 18 2.225 12 2.194 16 2.168 4 2.137 16 2.11 2 2.094 4 1.976 20 1.945 8 1.806 6 1.704 8 1.691 2 1.671 2 1.666 2 1.65 20 1.588 6 1.542 2 1.502 35 1.484 4 Muscovite-2M, ammonian 46-1311 Ms 10.19 100 5.01 35 4.479 21 4.112 2 3.873 4 3.697 8 3.492 3 3.339 52 3.209 3 3.077 5 2.993 8 2.868 7 2.585 3 2.564 18 2.507 2 2.455 6 2.387 9 2.247 2 2.139 8 2.004 27 1.652 13 1.501 8 Muscovite-2M1 06-0263 Ms 9.95 95 4.97 30 4.47 20 4.3 4 4.11 4 3.95 6 3.88 14 3.73 18 3.48 20 3.34 25 3.32 100 3.19 30 2.987 35 2.859 25 2.789 20 2.596 16 2.566 55 2.505 8 2.491 14 2.465 8 2.45 8 2.398 10 2.384 25 2.254 10 2.208 8 2.149 16 2.132 20 2.053 6 1.993 45 1.972 10 1.951 6 1.731 8 1.71 6 1.704 6 1.662 12 1.646 25 1.631 6 1.62 6 1.603 6 1.559 8 1.524 12 1.504 30 Muscovite-2M2 34-0175 Ms 9.98 100 4.99 26 4.444 71 4.337 19 4.27 32 3.938 2 3.878 39 3.78 3 3.655 75 3.555 2 3.493 83 3.327 46 3.181 80 3.124 9 3.035 72 2.928 22 2.845 47 2.795 51 2.576 69 2.566 86 2.554 89 2.512 5 2.495 4 2.438 34 2.412 28 2.384 33 2.328 2 2.297 6 2.266 6 2.241 20 2.231 16 2.205 10 2.195 10 2.178 9 2.171 11 2.138 7 2.076 37 2.065 30 2.058 6 2.042 24 2.03 6 2.016 11 2.01 28 Muscovite-3T 07-0042 Ms 9.97 100 4.99 55 4.49 20 4.46 20 3.873 10 3.596 8 3.331 100 3.11 10 2.884 16 2.589 16 2.564 25 2.499 12 2.457 8 2.384 8 2.254 6 2.222 4 2.197 4 2.136 12 2.056 4 1.999 45 1.966 8 1.885 2 1.654 10 1.638 4 1.614 4 1.551 2 1.521 6 1.502 12 Nahcolite, syn 15-0700 Nah 5.91 16 4.84 25 4.06 4 3.731 4 3.482 30 3.271 8 3.224 8 3.082 25 3.059 35 2.963 20 2.956 70 2.936 100 2.824 1 2.684 30 2.6 100 2.53 1 2.442 1 2.409 4 2.394 2 2.365 4 2.305 30 2.212 40 2.08 4 2.035 35 2.031 4 1.987 8 1.969 18 1.936 12 1.913 6 1.904 20 1.875 6 1.866 8 1.832 1 1.823 1 1.779 6 1.737 12 1.719 2 1.71 2 1.689 10 Nontronite-15A 34-0842 Non 14.6 100 7.56 10 4.98 10 4.53 100 3.67 20 3.01 30 2.6 50 2.27 10 1.72 20 1.523 80 Nontronite-15A_2 29-1497 Non 15.2 100 7.44 8 4.48 55 3.58 20 3.05 20 2.564 25 2.56 25 1.51 14 Opal 38-0448 Opl 4.08 100 3.14 9 2.86 10 2.51 30 2.13 4 2.03 4 1.937 5 1.878 5 Orthoclase 31-0966 Fs 6.62 6 6.48 12 5.86 12 4.58 4 4.22 70 3.94 16 3.85 6 3.77 80 3.61 16 3.54 12 3.47 45 3.31 100 3.29 60 3.25 20 3.24 65 2.992 50 2.934 8 2.901 30 2.769 20 2.601 18 2.576 4 2.571 30 2.553 8 2.515 8 2.48 4 2.415 10 2.38 10 2.328 6 2.2 4 2.163 25 2.124 8 2.113 4 2.108 4 2.051 4 2.005 12 1.971 8 1.922 10 1.911 6 1.884 4 1.856 4 1.807 4 1.801 16 1.798 8 1.77 6 1.675 4 1.628 4 1.593 4 1.571 4 1.531 4 1.495 12 Palygorskite 29-0855 Plg 10.34 100 6.34 15 5.38 7 4.47 12 4.27 3 4.11 3 3.95 1 3.66 4 3.45 1 3.35 2 3.23 3 3.17 19 3.09 4 2.746 1 2.675 2 2.607 1 2.583 8 2.536 6 2.508 3 2.341 2 2.23 16 2.161 2 2.141 2 2.113 6 2.076 1 2.029 2 1.9649 1 1.8139 1 1.8005 2 1.7523 1 1.7217 1 1.6723 1 1.6104 1 1.5851 1 1.5631 1 1.5371 2 1.5314 2 1.5077 2 1.4881 2 Paragonite-1M, syn 24-1047 Pg 9.67 80 4.82 50 4.44 35 4.24 20 4.03 4 3.5 18 3.21 100 3.06 30 2.778 6 2.602 8 2.557 12 2.523 20 2.426 10 2.364 4 2.336 4 2.164 4 2.116 4 2.083 6 1.9279 25 1.6851 4 1.6653 4 1.6268 4 1.6045 6 1.4816 10 1.4623 4 Paragonite-2M1, syn 42-0602 Pg 9.633 31 4.814 21 4.437 37 4.392 42 4.27 7 4.14 4 4.04 7 3.763 7 3.654 6 3.368 10 3.267 21 3.207 100 3.163 19 3.02 3 2.91 16 2.822 16 2.68 10 2.557 22 2.528 48 2.427 19 2.416 20 2.35 11 2.335 6 2.172 10 2.133 2 2.1 12 2.073 6 2.018 3 1.9248 31 1.8413 2 1.8275 4 1.6771 7 1.6114 8 1.4814 31 1.4685 17 Paragonite-2M1 12-0165 Pg 9.7 80 4.9 60 4.44 100 4.27 40 4.15 20 4.06 60 3.79 60 3.68 60 3.48 20 3.39 50 3.3 50 3.22 60 3.18 50 2.921 70 2.831 70 2.706 60 2.571 60 2.536 90 2.43 80 2.356 60 2.222 30 2.186 60 2.146 20 2.103 60 2.067 20 2.027 30 1.989 30 1.933 60 1.895 20 1.834 30 1.744 20 1.728 10 1.687 50 1.658 20 1.639 20 1.616 60 1.583 30 1.549 20 1.514 20 1.486 80 Pentlandite 08-0090 Pn 5.78 30 5.01 5 3.55 5 3.03 80 2.9 40 2.51 5 2.3 30 2.25 5 1.931 50 1.775 100 1.697 5 1.53 10 1.514 10 Argentopentlandite 25-0406 Pn 6.06 20 5.25 20 3.71 20 3.17 100 3.024 20 2.61 5 2.41 10 2.35 10 2.15 20 2.018 40 1.858 100 1.602 20 1.584 10 Cobaltpentlandite 12-0723 Pn 5.75 60 4.97 20 3.52 20 3.008 100 2.878 60 2.496 5 2.288 50 2.237 5 1.918 80 1.763 100 1.686 20 1.522 40 1.505 40 Cobaltpentlandite 30-0444 Pn 5.757 30 4.985 6 3.526 6 3.008 98 2.8788 30 2.4941 5 2.2885 16 2.2308 3 2.0361 2 1.9202 33 1.7635 100 1.6863 4 1.6626 1 1.5775 1 1.5213 8 1.5039 8 1.44 1 Periclase, syn 45-0946 Per 2.43163 4 2.10564 100 1.48905 39 Perovskite, syn 42-0423 Prv 3.82597 10 3.82111 5 3.42042 3 2.72019 26 2.70342 100 2.69078 24 2.56306 1 2.42764 1 2.41191 2 2.3139 2 2.30252 4 2.29969 3 2.21611 5 2.20007 4 2.1201 2 2.04921 2 2.03956 1 1.913 44 1.91034 23 1.8593 2 1.85574 2 1.7575 1 1.71855 2 1.71052 2 1.70904 2 1.67666 4 1.56721 15 1.56334 10 1.55758 10 1.55591 22 Phlogopite-1M, syn 16-0344 Phl 9.96 100 4.99 6 4.59 6 3.91 6 3.65 6 3.38 10 3.33 65 3.13 10 2.903 8 2.696 4 2.641 2 2.612 4 2.498 6 2.426 4 2.165 4 1.996 16 1.74 1 1.665 6 1.53 4 1.491 2 Phlogopite-2M1 10-0493 Phl 10.1 100 5.056 20 4.612 20 4.515 8 4.079 6 3.814 20 3.54 35 3.362 100 3.283 40 3.156 10 3.04 40 2.926 10 2.818 20 2.651 20 2.624 100 2.522 30 2.439 40 2.361 18 2.304 10 2.27 10 2.18 45 2.039 20 2.017 65 2 20 1.914 6 1.751 4 1.737 6 1.677 45 1.538 50 1.521 10 Phlogopite-3T 10-0492 Phl 10.1 100 5.022 25 4.596 10 4.558 10 3.941 4 3.663 8 3.408 45 3.354 100 3.148 25 2.917 20 2.71 14 2.643 10 2.618 30 2.511 50 2.429 18 2.293 4 2.257 4 2.241 4 2.17 20 2.009 100 1.994 14 1.907 6 1.746 4 1.673 35 1.535 25 1.515 4 Phillipsite_1 39-1375 Php 8.11 8 7.18 63 7.16 66 6.42 17 5.38 19 5.07 23 4.94 27 4.67 4 4.31 8 4.29 10 4.13 36 4.12 41 4.06 18 3.971 3 3.922 6 3.688 3 3.481 4 3.433 3 3.277 37 3.206 100 3.136 35 3.129 34 3.09 6 2.929 15 2.893 7 2.764 20 2.753 36 2.712 20 2.702 36 2.683 21 2.658 10 2.572 5 2.539 8 2.534 9 2.468 1 2.388 9 2.34 7 2.256 4 2.239 3 2.169 2 2.158 5 2.133 2 2.057 4 2.006 3 1.984 4 Phillipsite_2 46-1427 Php 8.164 10 7.107 48 6.412 16 5.373 15 5.044 30 4.997 11 4.312 13 4.097 39 3.958 5 3.696 9 3.466 21 3.26 16 3.191 100 3.106 11 2.945 15 2.748 19 2.688 20 2.544 9 2.498 6 2.396 9 2.225 5 Pyrite 42-1340 Py 3.128 31 2.7055 100 2.4209 53 2.2107 40 1.916 36 1.8061 1 1.6333 69 1.5639 11 1.5023 13 1.4479 16 Pyrophyllite-1A 25-0022 Prl 9.2 90 4.6 30 4.42 100 4.26 80 4.23 80 4.06 55 3.77 8 3.49 6 3.45 6 3.18 20 3.07 85 2.953 17 2.741 5 2.71 8 2.569 25 2.547 25 2.532 35 2.416 75 2.359 6 2.341 4 2.3 5 2.172 14 2.152 18 2.083 15 2.07 15 2.054 16 2.026 3 1.998 3 1.9 4 1.883 4 1.875 4 1.841 12 1.812 3 1.689 12 1.677 12 1.661 12 1.65 12 1.633 20 1.585 4 1.532 3 1.493 30 1.487 10 1.472 11 Pyrophyllite-2M 46-1308 Prl 9.167 82 4.59 52 4.444 13 4.243 10 4.172 9 3.341 19 3.062 100 2.57 7 2.549 8 2.531 11 2.425 9 2.411 12 2.297 6 2.165 4 2.158 4 2.148 5 2.084 6 2.061 5 1.891 3 1.838 10 1.687 3 1.644 5 1.631 5 1.494 6 1.489 6 1.47 3 1.437 2 Ferripyrophyllite 42-0569 Prl 9.6 80 4.54 100 4.25 20 3.17 70 2.62 40 2.47 40 1.89 10 1.725 30 1.665 30 1.518 80 Pyroxene 25-0306 Px 6.456 3 4.719 5 4.425 6 3.228 16 2.996 100 2.965 34 2.909 26 2.581 39 2.56 28 2.535 44 2.329 11 2.258 9 2.23 6 2.152 8 2.13 15 2.113 11 2.045 9 2.034 21 1.9836 6 1.8681 2 1.8563 2 1.8463 4 1.831 2 1.7397 10 1.6852 4 1.6799 6 1.6739 4 1.6497 12 1.6268 10 1.614 5 1.5901 2 1.5732 3 1.5468 6 1.5426 8 1.5399 7 1.5313 3 1.5237 10 1.5146 2 1.475 2 Pyrrhotite-4H 22-1120 Po 5.94 10 5.74 20 5.27 7 3.44 7 2.98 40 2.87 10 2.64 50 2.255 10 2.207 7 2.064 100 1.987 7 1.909 7 1.769 7 1.72 40 1.636 10 1.606 7 1.49 10 1.476 7 1.442 10 Pyrrhotite-4M 29-0723 Po 5.77 12 5.71 15 5.27 8 4.7 8 3.62 6 3.4 3 3.29 4 3.22 3 3.19 2 3.13 2 2.979 85 2.851 9 2.644 100 2.633 75 2.549 4 2.376 4 2.357 3 2.335 1 2.239 1 2.206 2 2.166 2 2.155 2 2.069 55 2.064 90 2.054 100 2.046 35 1.98 2 1.954 2 1.948 2 1.816 2 1.768 1 1.718 80 1.681 1 1.609 6 1.605 10 1.598 10 1.573 2 1.488 2 1.475 2 1.471 1 1.444 3 1.441 5 1.438 6 Pyrrhotite-3T, syn 24-0220 Po 5.947 3 5.687 8 5.616 16 4.879 10 4.11 2 3.466 7 3.366 2 3.185 2 2.974 54 2.844 5 2.675 4 2.635 55 2.42 3 2.229 2 2.174 4 2.09 2 2.055 100 1.9886 4 1.931 2 1.8772 2 1.872 2 1.7977 2 1.7168 33 1.7141 22 1.64 2 1.5985 6 1.4868 2 1.4385 4 Pyrrhotite-6T 29-0725 Po 5.89 2 5.66 1 4.96 1 3.14 1 2.987 35 2.877 3 2.653 65 2.441 1 2.141 1 2.072 100 1.923 2 1.724 35 1.613 1 1.492 1 1.446 5 Pyrrhotite-7T, syn 20-0534 Po 5.72 40 5.16 10 4.76 10 4.39 10 3.4 10 2.982 80 2.861 30 2.647 80 2.473 10 2.394 20 2.241 10 2.165 20 2.067 100 2.017 10 1.981 10 1.927 10 1.866 10 1.818 20 1.746 10 1.721 80 1.609 50 1.492 40 1.477 10 1.444 50 Quartz, syn 46-1045 Qz 4.25499 16 3.34347 100 2.45687 9 2.28149 8 2.23613 4 2.12771 6 1.97986 4 1.81796 13 1.80174 1 1.67173 4 1.65919 2 1.60827 1 1.54153 9 1.45289 2 Rectorite 25-0781 Rec 23.8 100 11.9 65 7.91 1 5.95 1 4.75 10 3.96 2 3.4 16 2.971 16 2.643 2 2.375 1 2.164 1 1.97 12 1.827 4 1.584 2 1.484 1 1.44 2 Rectorite_2 29-1495 Rec 26.4 30 12.2 40 4.99 25 4.45 100 3.655 12 3.145 25 2.552 65 2.44 14 2.235 6 2.15 8 1.819 5 1.683 12 1.65 12 1.54 5 1.492 40 Rhodochrosite, syn 44-1472 Rds 3.667 29 2.85 100 2.617 1 2.395 15 2.178 19 2.005 17 1.8337 7 1.7734 23 1.767 29 1.5599 2 1.5375 8 1.4671 2 1.456 5 Rutile, syn 21-1276 Rt 3.247 100 2.487 50 2.297 8 2.188 25 2.054 10 1.6874 60 1.6237 20 1.4797 10 1.4528 10 Sanidine 19-1227 Fs 6.54 8 6.5 14 6.44 6 5.83 12 4.57 2 4.16 70 3.91 25 3.76 75 3.61 12 3.5 8 3.45 50 3.27 75 3.26 100 3.25 75 3.22 90 2.976 35 2.914 10 2.901 20 2.884 12 2.763 20 2.573 10 2.56 30 2.549 6 2.532 4 2.477 4 2.465 4 2.461 2 2.416 10 2.355 8 2.317 6 2.244 2 2.223 2 2.194 4 2.167 18 2.11 6 2.079 4 2.054 6 1.98 8 1.953 4 1.891 8 1.88 4 1.842 4 1.804 6 1.797 4 1.791 25 1.751 4 1.581 4 1.562 4 1.514 4 1.493 12 Sanidine, disordered 25-0618 Fs 6.65 6 6.52 4 4.24 55 3.95 10 3.87 6 3.79 55 3.62 11 3.55 12 3.46 30 3.33 100 3.28 60 3.26 18 3.23 50 2.997 30 2.933 7 2.909 14 2.89 6 2.766 14 2.608 17 2.585 35 2.583 35 2.549 6 2.529 8 2.492 3 2.423 7 2.392 11 2.317 5 2.313 5 2.27 8 2.173 16 2.131 7 2.126 3 2.071 4 2.07 4 2.059 4 2.014 12 1.973 8 1.933 10 1.918 8 1.914 8 1.854 6 1.851 6 1.81 5 1.802 5 1.794 30 1.793 30 1.792 30 1.781 6 1.677 4 1.634 4 Sanidine,pot,disord,syn 10-0357 Fs 6.48 20 5.806 4 4.118 25 3.88 12 3.746 35 3.6 4 3.453 12 3.245 100 3.212 40 2.962 12 2.9 10 2.757 6 2.547 16 2.5 4 2.41 4 2.334 4 2.161 20 Saponite-15A 29-1491 Sap 15.5 100 7.73 1 5.11 4 4.57 6 3.83 1 3.07 8 2.61 3 1.73 1 1.533 4 Saponite-15A_2 13-0086 Sap 14.2 100 7.4 10 4.96 40 4.57 50 3.67 80 2.99 10 2.58 20 2.42 20 2.3 20 2.22 20 2.09 30 1.84 30 1.73 20 1.71 20 1.53 90 1.46 20 Saponite-15A, aluminian 30-0789 Sap 14.5 80 7.38 40 4.97 10 4.48 100 3.6 20 3.34 10 2.61 70 2.51 70 2.41 60 2.351 60 1.527 70 Saponite-15A, ferroan 13-0305 Sap 15.4 100 7.9 50 5.28 10 4.6 50 3.93 10 3.13 50 2.648 50 2.56 50 2.452 10 2.298 10 1.74 10 1.541 65 Sepiolite 26-1226 Sep 12.2 50 7.53 25 6.73 30 5.04 13 4.52 40 4.32 40 4 11 3.76 55 3.54 19 3.36 100 3.2 55 3.05 20 2.831 5 2.69 50 2.623 30 2.565 40 2.449 20 2.405 20 2.26 30 2.124 11 2.072 30 1.879 8 1.759 1 1.7 16 1.594 35 1.589 35 1.58 35 1.55 16 1.519 11 Sepiolite_2 13-0595 Sep 12.1 100 7.47 10 6.73 6 5.01 8 4.5 25 4.31 40 4.02 8 3.75 30 3.53 12 3.37 30 3.2 35 3.05 12 2.932 4 2.825 8 2.771 4 2.691 20 2.617 30 2.586 2 2.56 55 2.479 6 2.449 25 2.406 16 2.263 30 2.206 4 2.125 8 2.069 20 2.033 4 1.957 4 1.921 2 1.881 8 1.818 2 1.76 6 1.7 10 1.637 4 1.592 10 1.55 16 1.518 16 1.502 8 1.468 4 Sepiolite_3 29-1492 Sep 12.8 100 7.6 4 5.07 8 4.41 35 3.77 20 3.35 30 2.58 45 2.45 12 2.26 16 2.08 5 1.73 4 1.7 4 1.59 6 1.55 6 1.52 10 Sepiolite, ferrian 29-0863 Sep 11.9 100 7.38 4 4.96 2 4.46 10 4.27 4 3.98 2 3.72 15 3.34 60 3.17 4 3 10 2.67 15 2.607 2 2.55 4 2.241 6 2.119 2 2.067 2 1.918 4 1.871 4 1.812 4 1.751 2 1.581 6 1.542 2 1.495 6 1.44 4 Sepiolite-(Mn) 25-1371 Sep 16 10 13 100 10.5 10 6.77 5 6.04 20 5.49 10 4.85 10 4.6 50 4.42 30 4.22 30 4.04 30 3.85 10 3.74 20 3.64 10 3.48 10 3.36 20 3.29 10 3.02 20 2.94 30 2.78 80 2.66 90 2.52 30 2.44 70 2.22 50 1.602 60 1.568 20 Siderite 29-0696 Sd 3.593 25 2.795 100 2.564 1 2.346 20 2.134 20 1.965 20 1.7968 12 1.7382 30 1.7315 35 1.5291 3 1.5063 14 1.439 3 Silicon, syn 27-1402 Si 3.1355 100 1.9201 55 1.6375 30 Sillimanite 38-0471 Sil 5.366 12 4.575 2 3.839 14 3.743 3 3.415 100 3.366 35 3.196 1 2.939 1 2.907 1 2.886 7 2.68 16 2.542 20 2.43 3 2.421 20 2.373 1 2.307 2 2.289 3 2.233 1 2.204 30 2.193 2 2.112 12 2.092 1 1.9836 1 1.9653 1 1.8715 2 1.8637 1 1.8568 1 1.833 2 1.8195 1 1.7863 2 1.7693 1 1.707 7 1.6938 2 1.6821 3 1.6765 1 1.6704 1 1.5982 12 1.5703 1 1.5629 3 1.5238 1 1.52 14 1.5105 1 1.5037 1 1.4947 1 1.4696 1 1.4613 1 1.4543 1 1.4493 1 1.4434 5 Sphalerite, syn 05-0566 Sp 3.123 100 2.705 10 1.912 51 1.633 30 1.561 2 Cuprospinel 25-0283 Spl 4.79 30 2.96 50 2.517 100 2.417 10 2.1 30 1.613 40 1.479 60 Spinel, syn 21-1152 Spl 4.66 35 2.858 40 2.437 100 2.335 4 2.02 65 1.65 10 1.5554 45 Spinel, ferrian 21-0540 Spl 4.73 20 2.9 40 2.47 100 2.05 45 1.672 10 1.576 40 1.448 60 Sylvite, syn 41-1476 Syl 3.633 1 3.146 100 2.2251 37 1.8972 1 1.8169 10 1.573 5 Talc-2M 19-0770 Tlc 9.35 100 4.67 8 4.59 45 4.56 25 4.53 12 4.33 6 4.12 6 3.87 2 3.69 2 3.52 2 3.12 40 2.643 6 2.635 18 2.627 8 2.61 14 2.597 20 2.589 14 2.496 20 2.479 30 2.464 14 2.457 10 2.36 2 2.337 2 2.234 4 2.227 6 2.219 6 2.208 6 2.13 4 2.104 4 1.87 4 1.731 10 1.726 4 1.714 4 1.708 4 1.692 4 1.684 6 1.668 6 1.529 55 1.524 12 1.511 12 Talc-2M_2 13-0558 Tlc 9.34 100 4.66 90 4.55 30 3.51 4 3.43 1 3.116 100 2.892 1 2.629 12 2.595 30 2.476 65 2.335 16 2.212 20 2.196 10 2.122 8 2.103 20 1.93 6 1.87 40 1.725 2 1.682 20 1.557 20 1.527 40 1.509 10 1.46 8 Hydrotalcite 41-1428 Htc 16.116 55 7.844 100 4.341 10 3.914 80 2.835 5 2.771 5 2.608 30 2.516 10 2.402 3 2.37 9 2.311 9 2.199 10 2.191 9 2.023 12 1.973 9 1.957 10 1.862 15 1.664 4 1.576 12 1.54 6 1.506 7 Theresemagnanite 46-1277 Tmt 13.1 100 6.552 25 6.33 10 4.371 5 4.177 25 3.742 5 3.523 30 2.985 30 2.736 25 2.681 40 2.527 90 2.329 20 2.104 10 1.894 5 1.711 10 1.576 20 1.546 10 Tridymite-2H 18-1169 Trd 4.37 80 4.118 100 3.862 60 2.995 35 2.523 25 2.325 20 2.152 2 2.114 8 2.059 12 1.93 2 1.863 6 1.71 8 1.651 4 1.619 6 1.542 10 1.531 2 Tridymite-M 18-1170 Trd 4.328 90 4.236 2 4.107 100 3.867 20 3.818 50 3.642 4 3.461 2 3.396 4 3.25 4 3.215 2 3.171 1 3.126 1 3.087 1 3.017 4 2.975 25 2.95 2 2.776 8 2.609 2 2.54 1 2.5 16 2.49 14 2.385 2 2.342 2 2.308 16 2.294 2 2.238 2 2.205 2 2.137 2 2.117 4 2.086 8 2.049 8 1.874 2 1.855 2 1.829 2 1.783 4 1.715 2 1.695 12 1.654 2 1.635 8 1.6 10 1.546 2 1.534 10 1.517 2 1.51 2 1.467 2 1.443 2 1.439 2 1.434 2 1.413 2 1.402 4 Tridymite-O 42-1401 Trd 4.28472 93 4.08029 100 3.80152 68 3.24174 48 2.96117 11 2.8523 9 2.48123 35 2.38248 21 2.2977 19 2.07715 12 2.03849 11 1.9594 14 1.82722 9 1.68522 8 1.61933 10 1.59214 11 1.54362 6 1.43056 7 1.41308 6 1.39543 5 1.35141 5 1.22656 5 1.18956 6 Tridymite-20H 14-0260 Trd 4.527 20 4.268 100 4.075 90 4.002 20 3.832 50 3.8 90 3.609 40 3.432 40 3.374 40 3.337 20 3.277 20 3.229 40 3.162 20 3.067 20 3.001 40 2.986 20 2.955 60 2.939 40 2.83 20 2.762 40 2.591 30 2.534 20 2.493 60 2.48 60 2.454 20 2.367 30 2.331 30 2.303 50 2.286 40 2.13 30 2.118 30 2.111 30 2.088 40 2.074 40 2.045 30 1.967 20 1.928 40 1.87 30 1.851 30 1.827 30 1.782 20 1.771 20 1.714 20 1.708 20 1.691 40 1.676 20 1.635 40 1.63 30 1.622 20 1.604 30 Vermiculite-2M 16-0613 Vrm 14.2 100 7.14 15 4.76 10 4.57 60 4.41 10 4.35 10 4.25 10 3.56 25 2.85 30 2.615 50 2.57 50 2.525 45 2.43 5 2.38 35 2.365 35 2.265 5 2.2 5 2.17 5 2.08 5 2.04 10 2.01 10 1.975 5 1.82 5 1.79 5 1.725 10 1.715 10 1.695 5 1.665 15 1.543 10 1.528 70 1.514 25 1.502 15 Witherite, syn 45-1471 Wth 4.56294 8 4.45206 4 3.72159 100 3.66031 50 3.2164 20 3.0251 6 2.75184 4 2.65719 18 2.62903 30 2.60743 16 2.59127 40 2.28177 12 2.22602 4 2.15059 50 2.10358 20 2.04872 18 2.01791 40 1.94079 30 1.93198 14 1.86108 6 1.83046 3 1.73762 3 1.70641 3 1.6774 12 1.6492 14 1.63317 12 1.60822 3 1.56264 6 1.52874 3 1.52119 7 1.51682 5 1.50743 3 1.49507 3 1.48395 3 Wustite, syn 06-0615 Ws 2.49 80 2.153 100 1.523 60 Wustite 46-1312 Ws 2.47 80 2.14 100 1.514 80 Zincite, syn 36-1451 Znc 2.8143 57 2.60332 44 2.47592 100 1.91114 23 1.62472 32 1.47712 29 Chlorite Ia Chl 2.65 30 2.59 15 2.39 60 2.27 10 2.07 5 2.01 30 Chlorite Ib (B=90\B0) Chl 2.69 20 2.65 15 2.51 100 2.34 10 2.15 40 1.96 5 Chlorite Ib (B=97\B0) Chl 2.68 25 2.60 15 2.55 5 2.47 70 2.40 5 2.30 5 2.11 20 2.01 10 Chlorite IIb Chl 2.66 15 2.59 60 2.55 50 2.45 50 2.39 50 2.26 40 2.07 10 2.01 60 Dickite Dck 3.26 10 3.10 10 2.94 10 2.80 10 2.32 95 2.21 15 1.97 40 Kaolinite Kln 3.84 45 3.12 55 2.75 35 2.34 90 2.29 80 2.18 30 1.99 50 1.84 40 Nacrite Nac 3.44 40 3.09 30 2.93 10 2.41 100 2.26 10 2.09 20 1.92 45 Metahalloysite MHal 4.45 100 2.57 40 2.22 5 1.69 20 Mica 1M tv M 4.35 15 4.12 10 3.66 50 3.07 50 2.93 10 Mica 1M cv M 3.88 40 3.58 30 3.12 50 2.86 55 2.68 20 Mica 3T M 3.87 35 3.60 30 3.11 30 2.88 40 2.68 10 Mica 2M1 M 4.29 10 4.09 10 3.88 30 3.72 30 3.49 30 3.20 30 2.98 35 2.86 30 2.79 25 PyXRD-0.8.4/pyxrd/data/settings.py000066400000000000000000000213101363064711000167600ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import argparse, os from pyxrd.__version import __version__ from .appdirs import user_data_dir, user_log_dir ### General Information ### VERSION = __version__ DEBUG = False FINALIZERS = [] #A list of callables that are called before the main function is left BGSHIFT = True LOG_FILENAME = os.path.join(user_log_dir('PyXRD'), 'errors.log') ### The URL where PyXRD looks for updates & the online manual ### UPDATE_URL = 'http://users.ugent.be/~madumon/pyxrd/' MANUAL_URL = UPDATE_URL ### Factor to multiply the CSDS average with to obtain the maximum CSDS ### LOG_NORMAL_MAX_CSDS_FACTOR = 2.5 ### What type of residual error we use: ### # "Rp" = 'standard' pattern Rp factor # "Rpw" = 'weighted' pattern Rp factor # "Rpder" = Rp factor of first-derivatives RESIDUAL_METHOD = "Rp" ### Default wavelength if no Goniometer is available ### DEFAULT_LAMBDA = 0.154056 ### GUI Mode (for HPC turn to False) ### GUI_MODE = True ### Size of parameter space record in bytes ### PAR_SPACE_MEMORY_LIMIT = 25 * (1024 * 1024) ### Default Styles & Colors ### DEFAULT_LAYOUT = "VIEWER" # one of the keys in DEFAULT_LAYOUTS! DEFAULT_LAYOUTS = { "FULL": "Full", "VIEWER": "View-mode" } AXES_MANUAL_XMIN = 0.0 AXES_MANUAL_XMAX = 70.0 AXES_XSTRETCH = False AXES_DSPACING = False AXES_DEFAULT_WAVELENGTH = 0.154056 AXES_XLIMIT = 0 AXES_XLIMITS = { 0: "Automatic", 1: "Manual" } AXES_MANUAL_YMIN = 0.0 AXES_MANUAL_YMAX = 0.0 AXES_YVISIBLE = False AXES_YNORMALIZE = 0 AXES_YNORMALIZERS = { 0: "Multi normalised", 1: "Single normalised", 2: "Unchanged raw counts", } AXES_YLIMIT = 0 AXES_YLIMITS = { 0: "Automatic", 1: "Manual" } EXPERIMENTAL_COLOR = "#000000" CALCULATED_COLOR = "#FF0000" EXPERIMENTAL_LINEWIDTH = 1.0 CALCULATED_LINEWIDTH = 2.0 CALCULATED_LINESTYLE = "-" EXPERIMENTAL_LINESTYLE = "-" CALCULATED_MARKER = "" EXPERIMENTAL_MARKER = "" PATTERN_LINE_STYLES = { "": "Nothing", "-": "Solid", "--": "Dashed", "-.": "Dash Dot", ":": "Dotted" } PATTERN_MARKERS = { "": "No marker", ".": "Point", ",": "Pixel", "+": "Plus", "x": "Cross", "D": "Diamond", "o": "Circle", "v": "Triangle down", "^": "Triangle up", "<": "Triangle left", ">": "Triangle right", "8": "Octagon", "s": "Square", "p": "Pentagon", "*": "Star", "h": "Hexagon", } PATTERN_SHIFT_POSITIONS = { 0.42574: "Quartz 0.42574 SiO2", 0.3134: "Silicon 0.31355 Si", 0.2476: "Zincite 0.24759 ZnO", 0.2085: "Corundum 0.2085 Al2O3", 0.4183: "Goethite 0.4183 FeO(OH)", 0.48486: "Gibbsite 0.48486 Al(OH)3", } PATTERN_SHIFT_TYPE = "Displacement" # or "Linear" PATTERN_SMOOTH_TYPES = { 0: "Moving Triangle" } PATTERN_BG_TYPES = { 0: "Linear", 1: "Pattern" } DIVERGENCE_MODES = { "AUTOMATIC": "Automatic divergence", "FIXED": "Fixed divergence" } DEFAULT_DIVERGENCE_MODE = "FIXED" DEFAULT_SAMPLE_LENGTH = 1.25 # in cm PLOT_OFFSET = 0.75 PATTERN_GROUP_BY = 1 LABEL_POSITION = 0.35 MARKER_VISIBLE = True MARKER_X_OFFSET = 0.0 MARKER_Y_OFFSET = 0.05 MARKER_POSITION = 0.0 MARKER_INHERIT_COLOR = True MARKER_COLOR = "#000000" MARKER_INHERIT_ANGLE = True MARKER_ANGLE = 0.0 MARKER_INHERIT_TOP_OFFSET = True MARKER_TOP_OFFSET = 0.0 MARKER_INHERIT_BASE = True MARKER_BASE = 1 MARKER_BASES = { 0: "X-axis", 1: "Experimental profile", 2: "Calculated profile", 3: "Lowest of both", 4: "Highest of both" } MARKER_INHERIT_TOP = True MARKER_TOP = 0 MARKER_TOPS = { 0: "Relative to base", 1: "Top of plot" } MARKER_INHERIT_STYLE = True MARKER_STYLE = "none" MARKER_STYLES = { "none": "None", "solid": "Solid", "dashed": "Dash", "dotted": "Dotted", "dashdot": "Dash-Dotted", "offset": "Display at Y-offset" } MARKER_INHERIT_ALIGN = True MARKER_ALIGN = "left" MARKER_ALIGNS = { "left": "Left align", "center": "Centered", "right": "Right align" } EXCLUSION_FOREG = "#999999" EXCLUSION_LINES = "#333333" ### Plot Information ### PLOT_TOP = 0.85 MAX_PLOT_RIGHT = 0.95 PLOT_BOTTOM = 0.0 PLOT_LEFT = 0.05 PLOT_HEIGHT = PLOT_TOP - PLOT_BOTTOM OUTPUT_PRESETS = [ ("Landscape Large print", 8000, 4800, 300.0), ("Landscape Medium print", 6000, 3800, 300.0), ("Landscape Small print", 4000, 2800, 300.0), ("Portrait Large print", 4800, 8000, 300.0), ("Portrait Medium print", 3800, 6000, 300.0), ("Portrait Small print", 2800, 4000, 300.0), ] ### Default Directories & Files ### DATA_REG = None # set at run-time DATA_DIRS = [ ("DEFAULT_DATA", "./", None), ("USER_DATA", user_data_dir('PyXRD'), None), ("LOG_DIR", user_log_dir('PyXRD'), None), ("DEFAULT_PHASES", "default phases/", "USER_DATA"), ("DEFAULT_COMPONENTS", "default components/", "DEFAULT_DATA"), ("DEFAULT_GONIOS", "default goniometers/", "DEFAULT_DATA"), ("DEFAULT_WL_DISTR", "default wavelength distributions/", "DEFAULT_DATA"), ("APPLICATION_ICONS", "icons/", "DEFAULT_DATA"), ] DATA_FILES = [ ("COMPOSITION_CONV", "composition_conversion.csv", "DEFAULT_DATA"), ("ATOM_SCAT_FACTORS", "atomic scattering factors.atl", "DEFAULT_DATA"), ("MINERALS", "mineral_references.csv", "DEFAULT_DATA"), ] ### Async calculation providers ### ASYNC_SERER_PROVIDERS = [ "pyxrd.server.provider.Pyro4AsyncServerProvider", "pyxrd.generic.asynchronous.dummy_async_provider.DummyAsyncServerProvider", ] ASYNC_SERVER_PRELOAD = True ### Runtime Settings Retrieval ### SETTINGS_APPLIED = False ARGS = None def _parse_args(): """ Parses command line arguments """ parser = argparse.ArgumentParser() parser.add_argument( "filename", nargs="?", default="", help="A PyXRD project filename" ) parser.add_argument( "-s", "--script", default="", help="Can be used to pass a script containing a run() function" ) parser.add_argument( "-d", "--debug", dest='debug', action='store_const', const=True, default=False, help='Run in debug mode' ) args = parser.parse_args() del parser # free some memory return args __apply_lock__ = False def initialize(override_debug=DEBUG): """Apply runtime settings, can and needs to be called only once""" global __apply_lock__, SETTINGS_APPLIED if not __apply_lock__ and not SETTINGS_APPLIED: __apply_lock__ = True # Get command line arguments global ARGS ARGS = _parse_args() # Set gui flag global GUI_MODE GUI_MODE = not bool(ARGS.script) # Set debug flag global DEBUG DEBUG = ARGS.debug or override_debug # Setup data registry: global DATA_REG, DATA_DIRS, DATA_FILES from pyxrd.generic.io.data_registry import DataRegistry DATA_REG = DataRegistry(dirs=DATA_DIRS, files=DATA_FILES) # If we are running in GUI mode, setup GUI stuff: if GUI_MODE: import matplotlib import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GdkPixbuf # Setup matplotlib fonts: font = { 'weight' : 'heavy', 'size': 14, 'sans-serif' : 'Helvetica, Arial, sans-serif', 'family' : 'sans-serif', } matplotlib.rc('font', **font) mathtext = { 'default': 'regular', 'fontset': 'stixsans', } matplotlib.rc('mathtext', **mathtext) # matplotlib.rc('text', **{'usetex':True}) # Load our own icons: iconfactory = Gtk.IconFactory() icons_path = DATA_REG.get_directory_path("APPLICATION_ICONS") for root, dirnames, filenames in os.walk(icons_path): for filename in filenames: if filename.endswith(".png"): stock_id = filename[:-4] # remove extensions filename = "%s/%s" % (icons_path, filename) pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) iconset = Gtk.IconSet(pixbuf) iconfactory.add(stock_id, iconset) iconfactory.add_default() # Make sure default directories exist: for path in DATA_REG.get_all_directories(): try: os.makedirs(path) except OSError: pass # Free some memory at this point: import gc gc.collect() # Log that we did all of this: import logging logger = logging.getLogger(__name__) logger.info("Runtime settings applied") __apply_lock__ = False SETTINGS_APPLIED = True # ## end of settings PyXRD-0.8.4/pyxrd/file_parsers/000077500000000000000000000000001363064711000163165ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/file_parsers/__init__.py000066400000000000000000000000001363064711000204150ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/file_parsers/ascii_parser.py000066400000000000000000000023021363064711000213310ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import numpy as np from .base_parser import BaseParser class ASCIIParser(BaseParser): """ ASCII Parser """ description = "ASCII data" mimetypes = ["text/plain", ] can_write = True @classmethod def get_last_line(cls, f): i = -1 f.seek(0) for i, l in enumerate(f): pass return i + 1, l @classmethod def write(cls, filename, x, ys, header="", delimiter=",", **kwargs): """ Writes the header to the first line, and will write x, y1, ..., yn rows for each column inside the x and ys arguments. Header argument should not include a newline, and can be a string or any iterable containing strings. """ f = open(filename, 'w') if not isinstance(header, str): header = delimiter.join(header) # assume this is an iterable f.write("%s\n" % header) np.savetxt(f, np.insert(ys, 0, x, axis=0).transpose(), fmt="%.8f", delimiter=delimiter) f.close() pass # end of class PyXRD-0.8.4/pyxrd/file_parsers/atom_type_parsers/000077500000000000000000000000001363064711000220565ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/file_parsers/atom_type_parsers/__init__.py000066400000000000000000000004251363064711000241700ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .namespace import atom_type_parsers from .atl_parser import ATLAtomTypeParser from .json_parser import JSONAtomTypeParserPyXRD-0.8.4/pyxrd/file_parsers/atom_type_parsers/atl_parser.py000066400000000000000000000100471363064711000245660ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import csv, os from pyxrd.generic.io import get_case_insensitive_glob from pyxrd.generic.utils import u from pyxrd.file_parsers.csv_base_parser import CSVBaseParser from .namespace import atom_type_parsers @atom_type_parsers.register_parser() class ATLAtomTypeParser(CSVBaseParser): """ Atomic scattering factors CSV file parser """ namespace = "atl" description = "Atom types CSV file" extensions = get_case_insensitive_glob("*.ATL") default_fmt_params = { "delimiter": ',', "doublequote": True, "escapechar": None, "quotechar": "\"", "quoting": csv.QUOTE_MINIMAL, "skipinitialspace": True, "strict": False } @classmethod def _parse_header(cls, filename, fp, data_objects=None, close=False, **fmt_params): try: fmt_params = dict(cls.default_fmt_params, **fmt_params) # Goto start of file fp.seek(0) # Get base filename: basename = u(os.path.basename(filename)) # Read in the first and last data line and put the file cursor back # at its original position header = cls.parse_raw_line(fp.readline().strip(), str) replace = [ "a%d" % i for i in range (1, 6) ] + [ "b%d" % i for i in range (1, 6) ] + [ "c", ] header = [ "par_%s" % val if val in replace else val for val in header ] data_start_pos = fp.tell() line_count, _ = cls.get_last_line(fp) fp.seek(data_start_pos) # Adapt DataObject list data_objects = cls._adapt_data_object_list(data_objects, num_samples=line_count) # Fill in header info: for i in range(line_count): data_objects[i].update( filename=basename, header=header ) finally: if close: fp.close() return data_objects @classmethod def _parse_data(cls, filename, fp, data_objects=None, close=False, **fmt_params): if fp is not None: for row, data_object in zip(csv.reader(fp, **fmt_params), data_objects): if row: for key, val in zip(data_object.header, row): setattr(data_object, key, val) data_object.is_json = False if close: fp.close() return data_objects @classmethod def parse(cls, filename, fp, data_objects=None, close=True, **fmt_params): """ Files are sniffed for the used csv dialect, but an optional set of formatting parameters can be passed that will override the sniffed parameters. """ filename, fp, close = cls._get_file(filename, fp, close=close) # Guess dialect fmt_params, _, _ = cls.sniff(fp, **fmt_params) # Parse header data_objects = cls.parse_header(filename, fp, data_objects=data_objects, **fmt_params) # Parse data data_objects = cls.parse_data(filename, fp, data_objects=data_objects, **fmt_params) if close: fp.close() return data_objects @classmethod def write(cls, filename, items, props): """ Writes the header to the first line, and will write x, y1, ..., yn rows for each column inside the x and ys arguments. Header argument should not include a newline, and can be a string or any iterable containing strings. """ atl_writer = csv.writer(open(filename, 'wb'), delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) atl_writer.writerow([ name for prop, name in props]) for item in items: prop_row = [] for prop, name in props: prop_row.append(getattr(item, prop)) atl_writer.writerow(prop_row) pass # end of class PyXRD-0.8.4/pyxrd/file_parsers/atom_type_parsers/json_parser.py000066400000000000000000000022701363064711000247560ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import zipfile from pyxrd.generic.io import get_case_insensitive_glob, COMPRESSION from pyxrd.file_parsers.json_parser import JSONParser from .namespace import atom_type_parsers @atom_type_parsers.register_parser() class JSONAtomTypeParser(JSONParser): """ Atomic scattering factors JSON file parser """ description = "Atom types JSON file *.ZTL" extensions = get_case_insensitive_glob("*.ZTL") mimetypes = ["application/octet-stream", "application/zip"] __file_mode__ = "rb" @classmethod def _parse_header(cls, filename, fp, data_objects=None, close=False): return data_objects # just pass it on, nothing to do @classmethod def write(cls, filename, items, props): """ Saves multiple AtomTypes to a single (JSON) file. """ with zipfile.ZipFile(filename, 'w', compression=COMPRESSION) as zfile: for i, atom_type in enumerate(items): zfile.writestr("%d###%s" % (i, atom_type.uuid), atom_type.dump_object()) pass # end of class PyXRD-0.8.4/pyxrd/file_parsers/atom_type_parsers/namespace.py000066400000000000000000000004051363064711000243630ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pyxrd.file_parsers.registry import ParserNamespace atom_type_parsers = ParserNamespace("AtomTypeParsers")PyXRD-0.8.4/pyxrd/file_parsers/base_group_parser.py000066400000000000000000000117321363064711000223760ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import types import fnmatch from .base_parser import BaseParser class BaseGroupBarser(BaseParser): """ Base class for parsers that are composed of several sub-parsers (parsers class attribute). This allows to quickly find the correct parser based on the filename. When a (python) file object is passed to the parse functions, the 'name' attribute must be present on the object, otherwise an error is raised. Alternatively, you can pass in a file object, together with a (fake) filename argument. """ parsers = None @classmethod def _get_file(cls, fp, close=None): """ Returns a three-tuple: if fp is a filename: filename, filename, close or if fp is file-like object: filename, file, close where filename can be None Opening of the file is left to the actual parser class if a filename was passed. If a file is passed, care must be taken to ensure it is opened in the correct mode (the __file_mode__ attribute on the actual parsers class called). Therefore, it is safer to pass filenames to a BaseGroupParser sub-class than it is to pass a file-like object. """ if isinstance(fp, str): return fp, fp, True if close is None else close else: return getattr(fp, 'name', None), fp, False if close is None else close @classmethod def get_parser(cls, filename, fp=None): if not type(filename) is str and hasattr(fp, 'name'): filename = fp.name if not type(filename) is str: raise TypeError("Wrong type for filename (%s), must be a string, but %s was given" % (cls.description, type(filename))) else: try: import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gio # @UnresolvedImport giof = Gio.File.new_for_path(filename) # @UndefinedVariable file_mime = giof.query_info( 'standard::content-type', Gio.FileQueryInfoFlags.NONE, None ).get_content_type() del giof except ImportError: file_mime = "NONE/NONE" pass # TODO init file_mime if importerror was raised... for parser in cls.parsers: passed = False for mime in parser.mimetypes: if file_mime.split('/')[0] == mime.split('/')[0]: # TODO if an exact match can be made, even better passed = True break else: passed = False for extension in parser.extensions: if fnmatch.fnmatch(filename, extension): passed = True break else: passed = False if passed: return parser break # just for the sake of clarity @classmethod def parse(cls, fp, data_objects=None, close=True): """ Parses the file 'fp' using one of the parsers in this group. 'fp' should preferably be a filename (str), but can be a file-like object. Take care to open the file in the correct mode when passing a file-like object. """ filename, fp, close = cls._get_file(fp, close=close) parser = cls.get_parser(filename, fp=fp) return parser.parse(fp, data_objects=data_objects, close=close) @classmethod def setup_file_filter(cls): """ Creates a file filter based on a list of extensions set in the 'extensions' attribute of the class using the 'description' attribute as the name for the filter. If the 'mimetypes' attribute is also set, it will also set these. If additional properties are needed, this function should be overriden by subclasses. """ try: import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk except ImportError: pass else: if cls.file_filter == None and cls.description and cls.parsers: cls.file_filter = Gtk.FileFilter() cls.file_filter.set_name(cls.description) for parser in cls.parsers: for mtpe in parser.mimetypes: # cls.file_filter.add_mime_type(mtpe) pass for expr in parser.extensions: cls.file_filter.add_pattern(expr) setattr(cls.file_filter, "parser", cls) pass # end of class PyXRD-0.8.4/pyxrd/file_parsers/base_parser.py000066400000000000000000000132361363064711000211630ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .meta_parser import MetaParser from .data_object import DataObject import types class BaseParser(object, metaclass=MetaParser): """ Base class providing some common attributes and functions. Do not register this class or subclasses without overriding the following functions: - parse_header - parse_data - parse (optional) - setup_file_filter (optional) """ # This should be changed by sub-classes description = "Base Parser" extensions = [] mimetypes = [] @property def can_write(self): return getattr(self, "write", None) is not None @property def can_read(self): return getattr(self, "parse", None) is not None data_object_type = DataObject # This should be changed by sub-classes __file_mode__ = "r" file_filter = None @classmethod def _get_file(cls, fp, close=None): """ Returns a three-tuple: filename, file-object, close """ if isinstance(fp, str): return fp, open(fp, cls.__file_mode__), True if close is None else close else: return getattr(fp, 'name', None), fp, False if close is None else close @classmethod def _adapt_data_object_list(cls, data_objects, num_samples, only_extend=False): # If not yet created, create data_objects list: if data_objects == None: data_objects = [None, ] # If not yet the same length, adapt: num_data_objects = len(data_objects) if num_data_objects < num_samples: data_objects.extend([None] * int(num_samples - num_data_objects)) if not only_extend and num_data_objects > num_samples: data_objects = data_objects[:num_samples] # If not yet initialized, initialize: for i in range(num_samples): if not data_objects[i]: data_objects[i] = cls.data_object_type() return data_objects @classmethod def _parse_header(cls, filename, fp, data_objects=None, close=False): """ This method is implemented by sub-classes. It should parse the file and returns a list of DataObjects with the header properties filled in accordingly. The filename argument is always required. If no file object is passed as keyword argument, it only serves as a label. Otherwise a new file object is created. File objects are not closed unless close is set to True. Existing DataObjects can be passed as well and will then be used instead of creating new ones. """ # This should be implemented by sub-classes raise NotImplementedError @classmethod def _parse_data(cls, filename, fp, data_objects=None, close=False): """ This method is implemented by sub-classes. It should parse the file and return a list of DataObjects with the data properties filled in accordingly. The filename argument is always required. If no file object is passed as keyword argument, it only serves as a label. Otherwise a new file object is created. File objects are not closed unless close is set to True. Existing DataObjects can be passed as well and will then be used instead of creating new ones. """ # This should be implemented by sub-classes raise NotImplementedError @classmethod def parse(cls, fp, data_objects=None, close=True): """ This method parses the file and return a list of DataObjects with both header and data properties filled in accordingly. The filename argument is always required. If no file object is passed as keyword argument, it only serves as a label. Otherwise a new file object is created. File objects are closed unless close is set to False. Existing DataObjects can be passed as well and will then be used instead of creating new ones. """ filename, fp, close = cls._get_file(fp, close=close) data_objects = cls._parse_header(filename, fp, data_objects=data_objects) data_objects = cls._parse_data(filename, fp, data_objects=data_objects) if close: fp.close() return data_objects @classmethod def setup_file_filter(cls): """ Creates a file filter based on a list of extensions set in the 'extensions' attribute of the class using the 'description' attribute as the name for the filter. If the 'mimetypes' attribute is also set, it will also set these. If additional properties are needed, this function should be overriden by subclasses. """ if cls.file_filter == None and cls.description != "" and cls.extensions: try: import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk except ImportError: pass else: # Init file filter: cls.file_filter = Gtk.FileFilter() cls.file_filter.set_name(cls.description) for mtpe in cls.mimetypes: # cls.file_filter.add_mime_type(mtpe) pass for expr in cls.extensions: cls.file_filter.add_pattern(expr) setattr(cls.file_filter, "parser", cls) pass # end of classPyXRD-0.8.4/pyxrd/file_parsers/csv_base_parser.py000066400000000000000000000046541363064711000220420ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import csv import logging logger = logging.getLogger(__name__) from .ascii_parser import ASCIIParser class CSVBaseParser(ASCIIParser): """ CSV parser base functionality """ default_fmt_params = { "delimiter": ',', "doublequote": True, "escapechar": None, "quotechar": "\"", "quoting": csv.QUOTE_MINIMAL, "skipinitialspace": True, "strict": False } @classmethod def parse_raw_line(cls, line, conv, **fmt_params): """ Parses a single raw line (read as a string) """ fmt_params = dict(cls.default_fmt_params, **fmt_params) for row in csv.reader([line, ], **fmt_params): return list(map(conv, row)) break # stop after first 'line' (there should only be one anyway) @classmethod def sniff(cls, f, **fmt_params): """ CSV Dialect guessing - f needs to be a file object! """ # Guess dialect, and then: # - update default class dialect parameters with the sniffed dialect # - override the obtained dialect with any user-passed parameters dialect = None has_header = True file_start = 0 if f is not None: f.seek(file_start) while True: file_start = f.tell() line = f.readline() if not line: break if not line.strip().startswith("#"): break # first non-comment line, assume no more comments after this try: sniffer = csv.Sniffer() f.seek(file_start) has_header = sniffer.has_header(f.read(1024)) f.seek(file_start) f.readline() # skip (potential) header as these are sometimes formatted differently dialect = sniffer.sniff(f.read(1024)) f.seek(file_start) except: logger.warning("Errors encountered while sniffing CSV dialect!") pass # ignore failures default_fmt_params = dict(cls.default_fmt_params, **{ param: getattr(dialect, param) for param in list(cls.default_fmt_params.keys()) if hasattr(dialect, param) }) return dict(default_fmt_params, **fmt_params), has_header, file_start pass # end of class PyXRD-0.8.4/pyxrd/file_parsers/data_object.py000066400000000000000000000011271363064711000211300ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. class DataObject(object): """ A generic class holding all the information retrieved from a file using a BaseParser class. """ # general information filename = None def __init__(self, *args, **kwargs): super(DataObject, self).__init__() self.update(**kwargs) def update(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) pass # end of classPyXRD-0.8.4/pyxrd/file_parsers/exc_parsers/000077500000000000000000000000001363064711000206345ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/file_parsers/exc_parsers/__init__.py000066400000000000000000000012001363064711000227360ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pyxrd.generic.io.utils import get_case_insensitive_glob from pyxrd.file_parsers.registry import ParserNamespace from pyxrd.file_parsers.xrd_parsers.csv_parser import GenericXYCSVParser exc_parsers = ParserNamespace("EXCParsers") @exc_parsers.register_parser() class EXCParser(GenericXYCSVParser): """ ASCII *.DAT, *.CSV and *.TAB format parser """ description = "Exclusion range file" extensions = get_case_insensitive_glob("*.EXC") pass # end of classPyXRD-0.8.4/pyxrd/file_parsers/goniometer_parsers/000077500000000000000000000000001363064711000222255ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/file_parsers/goniometer_parsers/__init__.py000066400000000000000000000012321363064711000243340ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import zipfile from pyxrd.generic.io import get_case_insensitive_glob, COMPRESSION from pyxrd.file_parsers.json_parser import JSONParser from pyxrd.file_parsers.registry import ParserNamespace goniometer_parsers = ParserNamespace("GoniometerParsers") @goniometer_parsers.register_parser() class JSONGoniometerParser(JSONParser): description = "Goniometer file *.GON" extensions = get_case_insensitive_glob("*.GON") mimetypes = ["application/octet-stream", "application/zip"] pass #end of class PyXRD-0.8.4/pyxrd/file_parsers/json_parser.py000066400000000000000000000150041363064711000212150ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) from distutils.version import LooseVersion import json import zipfile import os, io from shutil import move from pyxrd.__version import __version__ from pyxrd.file_parsers.base_parser import BaseParser from pyxrd.generic.io.json_codec import PyXRDDecoder, PyXRDEncoder from pyxrd.generic.io.custom_io import storables, COMPRESSION from pyxrd.generic.io.utils import unicode_open class JSONParser(BaseParser): """ PyXRD Object JSON Parser """ description = "PyXRD Object JSON" mimetypes = ["application/octet-stream", "application/zip"] __file_mode__ = "r" @classmethod def _get_file(cls, fp, close=None): """ Returns a three-tuple: filename, zipfile-object, close """ if isinstance(fp, str): # fp is a filename filename = fp if zipfile.is_zipfile(filename): fp = zipfile.ZipFile(filename, cls.__file_mode__) else: fp = unicode_open(filename, cls.__file_mode__) close = True if close is None else close else: # fp is a file pointer filename = getattr(fp, 'name', None) if zipfile.is_zipfile(fp): fp = zipfile.ZipFile(fp) close = False if close is None else close return filename, fp, close @classmethod def _parse_header(cls, filename, fp, data_objects=None, close=False): return data_objects # just pass it on, nothing to do @classmethod def _parse_data(cls, filename, fp, data_objects=None, close=True): # At this point filename is just there for information; fp can safely be # assumed to be a file pointer - if not, not our problem is_zipfile = isinstance(fp, zipfile.ZipFile) if is_zipfile: # ZIP files namelist = fp.namelist() if 'content' in namelist: # Multi-part object (e.g. project) obj = None decoder = json.JSONDecoder() def get_named_item(fpt, name): try: cf = fpt.open(name, cls.__file_mode__) obj = decoder.decode(cf.read().decode("utf-8")) finally: cf.close() return obj # Parse the content file obj = get_named_item(fp, 'content') # Check for a version tag: if 'version' in namelist: namelist.remove('version') version = get_named_item(fp, 'version') if LooseVersion(version) > LooseVersion(__version__.replace("v", "")): raise RuntimeError("Unsupported project" + \ "version '%s', program version is '%s'" % ( version, __version__ )) else: logging.warn("Loading pre-v0.8 file format, " + "might be deprecated!") # Make sure we have a dict at this point if not hasattr(obj, "update"): raise RuntimeError("Decoding a multi-part JSON " + \ "object requires the root to be a dictionary object!") # Parse all the other files, and set accordingly in the content dict for sub_name in namelist: if sub_name != "content": obj["properties"][sub_name] = get_named_item( fp, sub_name) # Now parse the JSON dict to a Python object data_objects = PyXRDDecoder(mapper=storables).__pyxrd_decode__(obj) or obj else: # Multiple objects in one zip file (e.g. phases) data_objects = [] for sub_name in namelist: zpf = fp.open(sub_name, cls.__file_mode__) data_objects.append(cls.parse(zpf)) zpf.close() elif hasattr(fp, 'seek'): # Regular file try: fp.seek(0) # reset file position except io.UnsupportedOperation: pass # ignore these data_objects = PyXRDDecoder.decode_file(fp, mapper=storables) else: pass # use filename? if close: fp.close() return data_objects @staticmethod def write(obj, file, zipped=False): # @ReservedAssignment """ Saves the output from dump_object() to `filename`, optionally zipping it. File can be either a filename or a file-like object. If it is a filename extra precautions are taken to prevent malformed data overwriting a good file. With file objects this is not the case. """ filename = None if isinstance(file, str): # We have a filename, not a file object filename = file # Create temporary filenames for output, and a backup filename if # the file already exists. file = filename + ".tmp" # @ReservedAssignment backup_name = filename + "~" try: if zipped: # Try to safe the file as a zipfile: with zipfile.ZipFile(file, mode="w", compression=COMPRESSION) as f: for partname, json_object in obj.to_json_multi_part(): f.writestr(partname, PyXRDEncoder.dump_object(json_object)) else: # Regular text file: if filename is not None: with unicode_open(file, 'w') as f: PyXRDEncoder.dump_object_to_file(obj, f) else: PyXRDEncoder.dump_object_to_file(obj, file) except: # In case saving fails, remove the temporary file: if filename is not None and os.path.exists(file): os.remove(file) raise if filename is not None: # If target file exists, back it up: if os.path.exists(filename): move(filename, backup_name) # Rename temporary saved file: move(file, filename) pass # end of class PyXRD-0.8.4/pyxrd/file_parsers/meta_parser.py000066400000000000000000000007121363064711000211720ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. class MetaParser(type): """ Metatype for the parser sub classes, allowing for auto file filter creation. """ def __new__(meta, name, bases, attrs): # @NoSelf res = super(MetaParser, meta).__new__(meta, name, bases, attrs) res.setup_file_filter() return resPyXRD-0.8.4/pyxrd/file_parsers/phase_parsers/000077500000000000000000000000001363064711000211555ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/file_parsers/phase_parsers/__init__.py000066400000000000000000000012011363064711000232600ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import zipfile from pyxrd.generic.io import get_case_insensitive_glob, COMPRESSION from pyxrd.file_parsers.json_parser import JSONParser from pyxrd.file_parsers.registry import ParserNamespace phase_parsers = ParserNamespace("PhaseParsers") @phase_parsers.register_parser() class JSONPhaseParser(JSONParser): description = "Phase file *.PHS" extensions = get_case_insensitive_glob("*.PHS") mimetypes = ["application/octet-stream", "application/zip"] pass #end of class PyXRD-0.8.4/pyxrd/file_parsers/project_parsers/000077500000000000000000000000001363064711000215235ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/file_parsers/project_parsers/__init__.py000066400000000000000000000004511363064711000236340ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .namespace import project_parsers from .json_project_parser import JSONProjectParser from .sybilla_project_parser import SybillaProjectParserPyXRD-0.8.4/pyxrd/file_parsers/project_parsers/json_project_parser.py000066400000000000000000000014641363064711000261550ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pyxrd.generic.io import get_case_insensitive_glob from pyxrd.file_parsers.json_parser import JSONParser from .namespace import project_parsers @project_parsers.register_parser() class JSONProjectParser(JSONParser): description = "Project file *.PYXRD" extensions = get_case_insensitive_glob("*.PYXRD") mimetypes = ["application/octet-stream", "application/zip"] @classmethod def _parse_data(cls, filename, fp, data_objects=None, close=True): project = super(JSONProjectParser, cls)._parse_data(filename, fp, data_objects=data_objects, close=close) project.filename = filename return project pass #end of class PyXRD-0.8.4/pyxrd/file_parsers/project_parsers/namespace.py000066400000000000000000000004021363064711000240250ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pyxrd.file_parsers.registry import ParserNamespace project_parsers = ParserNamespace("ProjectParsers")PyXRD-0.8.4/pyxrd/file_parsers/project_parsers/sybilla_project_parser.py000066400000000000000000000026611363064711000266430ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pyxrd.generic.io import get_case_insensitive_glob from pyxrd.project.importing import create_project_from_sybilla_xml from ..base_parser import BaseParser from ..xml_parser_mixin import XMLParserMixin from .namespace import project_parsers @project_parsers.register_parser() class SybillaProjectParser(XMLParserMixin, BaseParser): description = "Sybilla XML files" extensions = get_case_insensitive_glob("*.XML") mimetypes = ["application/xml", "text/xml"] @classmethod def parse(cls, fp, data_objects=None, close=True): """ This method parses the file and return a list of DataObjects with both header and data properties filled in accordingly. The filename argument is always required. If no file object is passed as keyword argument, it only serves as a label. Otherwise a new file object is created. File objects are closed unless close is set to False. Existing DataObjects can be passed as well and will then be used instead of creating new ones. """ filename, fp, close = cls._get_file(fp, close=close) data_objects = create_project_from_sybilla_xml(filename) if close: fp.close() return data_objects pass #end of class PyXRD-0.8.4/pyxrd/file_parsers/registry.py000066400000000000000000000036411363064711000205440ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .base_group_parser import BaseGroupBarser class ParserNamespace(object): """ ParserNamespace instances can be used to register parsers and bundle them for easier retrieval of file filters etc. """ def __init__(self, name, group_description="All supported files"): self._parsers = [] self.name = name self.group_description = group_description def register_parser(self, first=False): """ Register a parsers to this namespace """ def wrapped_register(cls): if first: self._parsers.insert(0, cls) else: self._parsers.append(cls) self._update_group_parser() return cls return wrapped_register def get_file_filters(self): """ Returns all the file filter object for the parsers registered in this namespace """ for parser in [self._group_parser, ] + self._parsers: yield parser.file_filter def get_export_file_filters(self): for parser in self._parsers: if parser.can_write: yield parser.file_filter def get_import_file_filters(self): for parser in [self._group_parser, ] + self._parsers: if parser.can_read: yield parser.file_filter def _update_group_parser(self): """ Factory function for creating BaseGroupParser sub-classes, using the namespace's name as the class name and the list of parser classes as arguments. """ self._group_parser = type(self.name, (BaseGroupBarser,), dict( description=self.group_description, parsers=self._parsers )) pass #end of class PyXRD-0.8.4/pyxrd/file_parsers/wld_parsers/000077500000000000000000000000001363064711000206435ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/file_parsers/wld_parsers/__init__.py000066400000000000000000000011671363064711000227610ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pyxrd.generic.io.utils import get_case_insensitive_glob from pyxrd.file_parsers.registry import ParserNamespace from pyxrd.file_parsers.xrd_parsers.csv_parser import GenericXYCSVParser wld_parsers = ParserNamespace("WLDParsers") @wld_parsers.register_parser() class WLDParser(GenericXYCSVParser): """ ASCII *.WLD format parser """ description = "Wavelength distribution file" extensions = get_case_insensitive_glob("*.WLD") pass # end of classPyXRD-0.8.4/pyxrd/file_parsers/xml_parser_mixin.py000066400000000000000000000031611363064711000222510ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. try: from lxml import ET except ImportError: try: # Python 2.5 import xml.etree.cElementTree as ET except ImportError: try: # Python 2.5 import xml.etree.ElementTree as ET except ImportError: try: # normal cElementTree install import cElementTree as ET except ImportError: try: # normal ElementTree install import elementtree.ElementTree as ET except ImportError: print("Failed to import ElementTree from any known place") class XMLParserMixin(object): """ XML Parser Mixin class """ @classmethod def get_xml_for_string(cls, s): """ Returns a tuple containing the XML tree and root objects """ root = ET.fromstring(s) return ET.ElementTree(element=root), root @classmethod def get_xml_for_file(cls, f): """ Returns a tuple containing the XML tree and root objects """ tree = ET.parse(f) return tree, tree.getroot() @classmethod def get_val(cls, root, path, attrib, default=None): """ Returns the attribute `attrib` from the first element found in `root` using the given `path`or default if not found """ element = root.find(path) if element is not None: return element.get(attrib) else: return default pass #end of class PyXRD-0.8.4/pyxrd/file_parsers/xrd_parsers/000077500000000000000000000000001363064711000206525ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/file_parsers/xrd_parsers/__init__.py000066400000000000000000000006231363064711000227640ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .namespace import xrd_parsers from .brk_brml_parser import BrkBRMLParser from .brk_raw_parser import BrkRAWParser from .cpi_parser import CPIParser from .csv_parser import CSVParser from .rd_parser import RDParser from .udf_parser import UDFParserPyXRD-0.8.4/pyxrd/file_parsers/xrd_parsers/brk_brml_parser.py000066400000000000000000000314711363064711000244000ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os, types from zipfile import ZipFile from pyxrd.generic.io.utils import get_case_insensitive_glob from pyxrd.generic.utils import not_none from ..base_parser import BaseParser from ..xml_parser_mixin import XMLParserMixin from .xrd_parser_mixin import XRDParserMixin from .namespace import xrd_parsers @xrd_parsers.register_parser() class BrkBRMLParser(XRDParserMixin, XMLParserMixin, BaseParser): """ Bruker *.BRML format parser """ description = "Bruker BRML files *.BRML" extensions = get_case_insensitive_glob("*.BRML") mimetypes = ["application/zip", ] __file_mode__ = "r" @classmethod def _get_file(cls, fp, close=None): """ Returns a three-tuple: filename, zipfile-object, close """ if isinstance(fp, str): return fp, ZipFile(fp, cls.__file_mode__), True if close is None else close else: return getattr(fp, 'name', None), ZipFile(fp, cls.__file_mode__), False if close is None else close @classmethod def _get_raw_data_files(cls, f, folder): """ Processes DataContainer.xml and returns a list of xml raw data filepaths and the sample name """ contf = f.open(r"%s/DataContainer.xml" % folder, cls.__file_mode__) data = contf.read() contf.close() _, root = cls.get_xml_for_string(data) sample_name = root.find("./MeasurementInfo").get("SampleName") raw_data_files = [] for child in root.find("./RawDataReferenceList"): raw_data_files.append(child.text) return raw_data_files, sample_name @classmethod def _get_header_dict(cls, f, folder): header_d = {} contf = f.open(r"%s/MeasurementContainer.xml" % folder, cls.__file_mode__) data = contf.read() contf.close() _, root = cls.get_xml_for_string(data) radius_path = "./HardwareLogicExt/Instrument/BeamPathContainers" + \ "/BeamPathContainerAbc[@VisibleName='PrimaryTrack']/%s" tube_path = "./HardwareLogicExt/Instrument/BeamPathContainers" + \ "/BeamPathContainerAbc[@VisibleName='PrimaryTrack']/BankPositions/" + \ "BankPosition/MountedComponent/MountedTube/%s" soller1_path = "./HardwareLogicExt/Instrument/BeamPathContainers" + \ "/BeamPathContainerAbc[@VisibleName='PrimaryTrack']/BankPositions/" + \ "BankPosition/MountedComponent[@VisibleName='SollerMount']/%s" soller2_path = "./HardwareLogicExt/Instrument/BeamPathContainers" + \ "/BeamPathContainerAbc[@VisibleName='SecondaryTrack']/BankPositions/" + \ "BankPosition/MountedComponent[@VisibleName='SollerMount']/%s" divergence_path = "./HardwareLogicExt/Instrument/BeamPathContainers" + \ "/BeamPathContainerAbc[@VisibleName='PrimaryTrack']/BankPositions/" + \ "BankPosition/MountedComponent/Restrictions[@FieldName='OpeningDegree']/%s" header_d.update( alpha1=float(cls.get_val(root, tube_path % "WaveLengthAlpha1", "Value")) / 10, alpha2=float(cls.get_val(root, tube_path % "WaveLengthAlpha2", "Value")) / 10, alpha_average=float(cls.get_val(root, tube_path % "WaveLengthAverage", "Value")) / 10, beta=float(cls.get_val(root, tube_path % "WaveLengthBeta", "Value")) / 10, alpha_factor=cls.get_val(root, tube_path % "WaveLengthRatio", "Value"), target_type=cls.get_val(root, tube_path % "TubeMaterial", "Value"), soller1=cls.get_val(root, soller1_path % "Deflection", "Value"), soller2=cls.get_val(root, soller2_path % "Deflection", "Value"), radius=float(cls.get_val(root, radius_path % "Radius", "Value", 0)) / 10.0, #convert to cm divergence=cls.get_val(root, divergence_path % "Data", "Value") ) return header_d @classmethod def parse(cls, fp, data_objects=None, close=False): filename, fp, close = cls._get_file(fp, close=close) try: basename = os.path.basename(filename) except AttributeError: basename = None num_samples = 0 zipinfos = fp.infolist() processed_folders = [] data_objects = not_none(data_objects, []) for zipinfo in zipinfos: if zipinfo.filename.count('/') == 1 and "DataContainer.xml" in zipinfo.filename: folder = os.path.dirname(zipinfo.filename) if not folder in processed_folders: processed_folders.append(folder) header_d = cls._get_header_dict(fp, folder) raw_data_files, sample_name = cls._get_raw_data_files(fp, folder) for raw_data_filename in raw_data_files: contf = fp.open(raw_data_filename) _, root = cls.get_xml_for_file(contf) isScan = not ("NonAmbientModeData" in root.find("./DataRoutes/DataRoute/ScanInformation").get("ScanName")) if isScan: for route in root.findall("./DataRoutes/DataRoute"): # Adapt XRDFile list & get last addition: data_objects = cls._adapt_data_object_list( data_objects, num_samples=(num_samples + 1), only_extend=True ) data_object = data_objects[num_samples] # Get the Datum tags: datums = route.findall("Datum") data = [] # Parse the RawDataView tags to find out what index in # the datum is used for what type of data: enabled_datum_index = None twotheta_datum_index = None intensity_datum_index = None steptime_datum_index = None relative_humidity_data, relative_humidity_index = None, None temperature_data, temperature_index = None, None temperature_index = None for dataview in route.findall("./DataViews/RawDataView"): index = int(dataview.get("Start", 0)) name = dataview.get("LogicName") or "Undefined" xsi_type = dataview.get("{http://www.w3.org/2001/XMLSchema-instance}type") or "Undefined" if name == "MeasuredTime": steptime_datum_index = index elif name == "AbsorptionFactor": enabled_datum_index = index elif name == "Undefined" and xsi_type == "VaryingRawDataView": for i, definition in enumerate(dataview.findall("./Varying/FieldDefinitions")): if definition.get("TwoTheta"): index += i break twotheta_datum_index = index elif name == "Undefined" and xsi_type == "RecordedRawDataView": logic_name = dataview.find("Recording").get("LogicName") if logic_name == "ScanCounter": intensity_datum_index = index elif logic_name == "modeActualHum": relative_humidity_index = index relative_humidity_data = [] elif logic_name == "modeActualTemp": temperature_index = index temperature_data = [] # Parse the SubScanInfo list (usually only one), and # then parse the datums accordingly twotheta_min = None twotheta_max = None twotheta_count = 0 for subscan in route.findall("./SubScans/SubScanInfo"): # Get the steps, where to start and the planned # time per step (measuredTimePerStep deviates # if the recording was interrupted): steps = int(subscan.get("MeasuredSteps")) start = int(subscan.get("StartStepNo")) steptime = float(subscan.get("PlannedTimePerStep")) for datum in datums[start:start + steps]: values = datum.text.split(",") if values[enabled_datum_index] == "1": # Fetch values from the list: datum_steptime = float(values[steptime_datum_index]) intensity = float(values[intensity_datum_index]) intensity /= float(steptime * datum_steptime) twotheta = float(values[twotheta_datum_index]) # If we have temperature or RH data, get them as well: if temperature_index is not None: temperature = float(values[temperature_index]) temperature_data.append(temperature) if relative_humidity_index is not None: relative_humidity = float(values[relative_humidity_index]) relative_humidity_data.append(relative_humidity) # Keep track of min 2theta: if twotheta_min is None: twotheta_min = twotheta else: twotheta_min = min(twotheta_min, twotheta) # Keep track of max 2theta: if twotheta_max is None: twotheta_max = twotheta else: twotheta_max = min(twotheta_max, twotheta) # Append point and increase count: data.append([twotheta, intensity]) twotheta_count += 1 #Update header: data_object.update( filename=basename, name=sample_name, time_step=1, # we converted to CPS twotheta_min=twotheta_min, twotheta_max=twotheta_max, twotheta_count=twotheta_count, **header_d ) data_object.data = data # These might be None: data_object.temperature_data = temperature_data data_object.relative_humidity_data = relative_humidity_data num_samples += 1 #end for contf.close() #end for #end if #end if #end for if close: fp.close() return data_objects pass # end of class PyXRD-0.8.4/pyxrd/file_parsers/xrd_parsers/brk_raw_parser.py000066400000000000000000000266001363064711000242330ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os, struct from io import SEEK_SET, SEEK_CUR import numpy as np from pyxrd.generic.io.utils import get_case_insensitive_glob from pyxrd.generic.utils import u from ..base_parser import BaseParser from .namespace import xrd_parsers from .xrd_parser_mixin import XRDParserMixin @xrd_parsers.register_parser() class BrkRAWParser(XRDParserMixin, BaseParser): """ Bruker *.RAW format parser """ description = "Bruker/Siemens Binary V1/V2/V3 *.RAW" extensions = get_case_insensitive_glob("*.RAW") mimetypes = ["application/octet-stream", ] __file_mode__ = "rb" @classmethod def _clean_bin_str(cls, val): return u(val.replace("\0".encode(), "".encode()).strip()) @classmethod def _parse_header(cls, filename, fp, data_objects=None, close=False): f = fp try: basename = u(os.path.basename(filename)) except: basename = None # Go to the start of the file f.seek(0, SEEK_SET) # Read file format version: version = f.read(4).decode("utf-8") if version == "RAW ": version = "RAW1" elif version == "RAW2": version = "RAW2" elif version == "RAW1" and str(f.read(3)) == ".01": version = "RAW3" if version == "RAW1": # This format does not allow getting the exact number of samples, # so start with one and append where needed: isfollowed = 1 num_samples = 0 while isfollowed > 0: twotheta_count = int(struct.unpack("I", f.read(4))[0]) # Check if this is an early "RAW " formatted file where the # "RAW " is repeated for each sample: if num_samples > 0 and twotheta_count == int(struct.unpack("I", "RAW ")[0]): twotheta_count = int(struct.unpack("I", f.read(4))[0]) # Step counting time, 2-theta step size and scanning mode: time_step, twotheta_step, scan_mode = struct.unpack("fff", f.read(12)) #@UnusedVariable # Skip 4 bytes, and read 2-theta starting position: f.seek(4, SEEK_CUR) twotheta_min, = struct.unpack("f", f.read(4)) twotheta_max = twotheta_min + twotheta_step * float(twotheta_count) # Skip 12 bytes # (contain theta, khi and phi start point for eularian craddles) f.seek(12, SEEK_CUR) # Read sample name and wavelengths: sample_name = cls._clean_bin_str(f.read(32)) alpha1, alpha2 = struct.unpack("ff", f.read(8)) # Skip 72 bytes: f.seek(72, SEEK_CUR) isfollowed, = struct.unpack("I", f.read(4)) # Get data position and skip for now: data_start = f.tell() f.seek(twotheta_count * 4, SEEK_CUR) # Adapt XRDFile list data_objects = cls._adapt_data_object_list( data_objects, num_samples=(num_samples + 1), only_extend=True ) data_objects[num_samples].update( filename=basename, version=version, name=sample_name, time_step=time_step, twotheta_min=twotheta_min, twotheta_max=twotheta_max, twotheta_step=twotheta_step, twotheta_count=twotheta_count, alpha1=alpha1, alpha2=alpha2, data_start=data_start ) num_samples += 1 elif version == "RAW2": # Read number of sample ranges: num_samples, = struct.unpack("H", f.read(2)) # Adapt XRDFile list data_objects = cls._adapt_data_object_list(data_objects, num_samples=num_samples) # Read sample name: f.seek(8, SEEK_SET) sample_name = cls._clean_bin_str(f.read(32)) # Meta-data description, skip for now: # description = u(str(f.read(128)).replace("\0", "").strip()) # date = u(str(f.read(10)).replace("\0", "").strip()) # time = u(str(f.read(5)).replace("\0", "").strip()) # Read wavelength information: f.seek(148, SEEK_CUR) target_type = u(str(f.read(2)).replace("\0", "").strip()) #@UnusedVariable alpha1, alpha2, alpha_factor = struct.unpack("fff", f.read(12)) # Total runtime in seconds: (not used fttb) f.seek(8, SEEK_CUR) time_total, = struct.unpack("f", f.read(4)) #@UnusedVariable # Move to first sample header start: f.seek(256, SEEK_SET) # Read in per-sample meta data for i in range(num_samples): header_start = f.tell() header_length, twotheta_count = struct.unpack("HH", f.read(4)) data_start = header_start + header_length # Read step size and start angle: f.seek(header_start + 12) # = 256 + 4 + 8 skipped bytes twotheta_step, twotheta_min = struct.unpack("ff", f.read(8)) twotheta_max = twotheta_min + twotheta_step * float(twotheta_count) # Read up to end of data: f.seek(data_start + twotheta_count * 4, SEEK_SET) # Update XRDFile object: data_objects[i].update( filename=basename, version=version, name=sample_name, twotheta_min=twotheta_min, twotheta_max=twotheta_max, twotheta_step=twotheta_step, twotheta_count=twotheta_count, alpha1=alpha1, alpha2=alpha2, alpha_factor=alpha_factor, data_start=data_start ) elif version == "RAW3": # Read file status: f.seek(8, SEEK_SET) file_status = { #@UnusedVariable 1: "done", 2: "active", 3: "aborted", 4: "interrupted" }[int(struct.unpack("I", f.read(4))[0])] # Read number of samples inside this file: f.seek(12, SEEK_SET) num_samples, = struct.unpack("I", f.read(4)) # Read in sample name: f.seek(326, SEEK_SET) sample_name = cls._clean_bin_str(f.read(60)) # Goniometer radius: f.seek(564, SEEK_SET) radius = float(struct.unpack("f", f.read(4))[0]) # Fixed divergence: f.seek(568, SEEK_SET) divergence = float(struct.unpack("f", f.read(4))[0]) # Primary soller f.seek(576, SEEK_SET) soller1 = float(struct.unpack("f", f.read(4))[0]) # Secondary soller f.seek(592, SEEK_SET) soller2 = float(struct.unpack("f", f.read(4))[0]) # Get anode type: f.seek(608, SEEK_SET) target_type = str(f.read(4)) #@UnusedVariable # Get wavelength info: f.seek(616, SEEK_SET) alpha_average, alpha1, alpha2, beta, alpha_factor = (#@UnusedVariable struct.unpack("ddddd", f.read(8 * 5))) # Get total recording time: f.seek(664, SEEK_SET) time_total, = struct.unpack("f", f.read(4)) #@UnusedVariable # Adapt XRDFile lis & Skip to first block:t data_objects = cls._adapt_data_object_list(data_objects, num_samples=num_samples) f.seek(712, SEEK_SET) # Read in per-sample meta data for i in range(num_samples): # Store the start of the header: header_start = f.tell() # Get header length f.seek(header_start + 0, SEEK_SET) header_length, = struct.unpack("I", f.read(4)) assert header_length == 304, "Invalid format!" # Get data count and f.seek(header_start + 4, SEEK_SET) twotheta_count, = struct.unpack("I", f.read(4)) # Get theta start positions f.seek(header_start + 8, SEEK_SET) theta_min, twotheta_min = struct.unpack("dd", f.read(8 * 2))#@UnusedVariable # Read step size f.seek(header_start + 176, SEEK_SET) twotheta_step, = struct.unpack("d", f.read(8)) # Read counting time f.seek(header_start + 192, SEEK_SET) time_step, = struct.unpack("d", f.read(8)) # Read the used wavelength f.seek(header_start + 240, SEEK_SET) alpha_used, = struct.unpack("d", f.read(8))#@UnusedVariable # Supplementary header size: f.seek(header_start + 256, SEEK_SET) supp_headers_size, = struct.unpack("I", f.read(4)) data_start = header_start + header_length + supp_headers_size # Move to the end of the data: f.seek(data_start + twotheta_count * 4) # Calculate last data point twotheta_max = twotheta_min + twotheta_step * float(twotheta_count - 0.5) data_objects[i].update( filename=basename, version=version, name=sample_name, twotheta_min=twotheta_min, twotheta_max=twotheta_max, twotheta_step=twotheta_step, twotheta_count=twotheta_count, alpha1=alpha1, alpha2=alpha2, alpha_factor=alpha_factor, data_start=data_start, radius=radius, soller1=soller1, soller2=soller2, divergence=divergence ) else: raise IOError("Only verson 1, 2 and 3 *.RAW files are supported!") if close: f.close() return data_objects @classmethod def _parse_data(cls, filename, fp, data_objects=None, close=False): for data_object in data_objects: if data_object.data == None: data_object.data = [] # Parse data: if fp is not None: if data_object.version in ("RAW1", "RAW2", "RAW3"): fp.seek(data_object.data_start) n = 0 while n < data_object.twotheta_count: y, = struct.unpack("f", fp.read(4)) x = data_object.twotheta_min + data_object.twotheta_step * float(n + 0.5) data_object.data.append([x,y]) n += 1 else: raise IOError("Only verson 1, 2 and 3 *.RAW files are supported!") data_object.data = np.array(data_object.data) if close: fp.close() return data_objects pass # end of class PyXRD-0.8.4/pyxrd/file_parsers/xrd_parsers/cpi_parser.py000066400000000000000000000100051363064711000233470ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os from datetime import date import numpy as np from pyxrd.generic.io.utils import get_case_insensitive_glob from pyxrd.generic.utils import u from ..base_parser import BaseParser from .namespace import xrd_parsers from .xrd_parser_mixin import XRDParserMixin @xrd_parsers.register_parser() class CPIParser(XRDParserMixin, BaseParser): """ ASCII Sietronics *.CPI format parser """ description = "Sietronics *.CPI" extensions = get_case_insensitive_glob("*.CPI", "*.CPD", "*.CPS") @classmethod def _parse_header(cls, filename, fp, data_objects=None, close=False): f = fp try: basename = u(os.path.basename(filename)) except: basename = None # Adapt XRDFile list data_objects = cls._adapt_data_object_list(data_objects, num_samples=1) # Move to the start of the file f.seek(0) # Skip a line: file type header f.readline() # Read data limits twotheta_min = float(f.readline().replace(",", ".").strip()) twotheta_max = float(f.readline().replace(",", ".").strip()) twotheta_step = float(f.readline().replace(",", ".").strip()) twotheta_count = int((twotheta_max - twotheta_min) / twotheta_step) # Read target element name target_type = f.readline() # Read wavelength alpha1 = float(f.readline().replace(",", ".").strip()) # Read up to SCANDATA and keep track of the line before, # it contains the sample description name = "" while True: line = f.readline().strip() if line == "SCANDATA" or line == "": data_start = f.tell() break; else: name = line data_objects[0].update( filename=basename, name=name, target_type=target_type, alpha1=alpha1, twotheta_min=twotheta_min, twotheta_max=twotheta_max, twotheta_step=twotheta_step, twotheta_count=twotheta_count, data_start=data_start, ) if close: f.close() return data_objects @classmethod def _parse_data(cls, filename, fp, data_objects=None, close=False): f = fp # CPI files are singletons, so no need to iterate over the list, # there is only one data object instance: if data_objects[0].data == None: data_objects[0].data = [] if f is not None: f.seek(data_objects[0].data_start) n = 0 while n <= data_objects[0].twotheta_count: line = f.readline().strip("\n").replace(",", ".") if line != "": data_objects[0].data.append([ float(data_objects[0].twotheta_min + data_objects[0].twotheta_step * n), float(line) ]) n += 1 data_objects[0].data = np.array(data_objects[0].data) if close: f.close() return data_objects @classmethod def write(cls, filename, x, ys, radiation="Cu", wavelength=1.54060, tps=48.0, sample="", **kwargs): """ Writes a SIETRONICS cpi text file. x and ys should be numpy arrays. """ start_angle = x[0] end_angle = x[-1] step_size = (end_angle - start_angle) / (x.size - 1) with open(filename, 'w') as f: f.write("SIETRONICS XRD SCAN\n") f.write("%.4f\n" % start_angle) f.write("%.4f\n" % end_angle) f.write("%.4f\n" % step_size) f.write("%s\n" % radiation) f.write("%.5f\n" % wavelength) f.write("%s\n" % date.today().strftime('%d/%m/%y %H:%M:%S')) f.write("%.1f\n" % tps) f.write("%s\n" % sample) f.write("SCANDATA\n") for y in ys[0, :]: f.write("%.7f\n" % y) pass # end of class PyXRD-0.8.4/pyxrd/file_parsers/xrd_parsers/csv_parser.py000066400000000000000000000117671363064711000234070ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os, csv import numpy as np from pyxrd.generic.io.utils import get_case_insensitive_glob from pyxrd.generic.utils import u from ..csv_base_parser import CSVBaseParser from .namespace import xrd_parsers from .xrd_parser_mixin import XRDParserMixin class GenericXYCSVParser(XRDParserMixin, CSVBaseParser): """ Generic xy-data CSV parser. Does not care about extensions. Should be sub-classed! """ description = "ASCII XY data" default_fmt_params = { "delimiter": ',', "doublequote": True, "escapechar": None, "quotechar": "\"", "quoting": csv.QUOTE_MINIMAL, "skipinitialspace": True, "strict": False } @classmethod def _parse_header(cls, filename, fp, data_objects=None, close=False, split_columns=True, has_header=True, file_start=0, **fmt_params): fmt_params = dict(cls.default_fmt_params, **fmt_params) f = fp # Goto start of file f.seek(file_start) # Get base filename: try: basename = u(os.path.basename(filename)) except: basename = None # Read in the first and last data line and put the file cursor back # at its original position header = f.readline().strip() if not has_header: f.seek(file_start) # go back to the start, we still use the first line as header, but also as data data_start_pos = f.tell() first_line = f.readline().strip() twotheta_count, last_line = cls.get_last_line(f) last_line = last_line.strip() f.seek(data_start_pos) # Extract the data from the first & last data lines: first_line_vals = cls.parse_raw_line(first_line, float, **fmt_params) last_line_vals = cls.parse_raw_line(last_line, float, **fmt_params) num_samples = len(first_line_vals) - 1 # first column is 2-theta twotheta_min = first_line_vals[0] twotheta_max = last_line_vals[0] twotheta_step = int((twotheta_max - twotheta_min) / twotheta_count) # Parse the header line: sample_names = cls.parse_raw_line(header, lambda s: s, **fmt_params)[1:] if len(sample_names) < num_samples: sample_names.extend([""] * (num_samples - len(sample_names))) if len(sample_names) > num_samples: sample_names = sample_names[:num_samples] # Adapt DataObject list data_objects = cls._adapt_data_object_list(data_objects, num_samples=num_samples) # Fill in header info: for i, sample_name in enumerate(sample_names): data_objects[i].update( filename=basename, name=sample_name, twotheta_min=twotheta_min, twotheta_max=twotheta_max, twotheta_step=twotheta_step, twotheta_count=twotheta_count ) if close: f.close() return data_objects @classmethod def _parse_data(cls, filename, fp, data_objects=None, close=False, split_columns=True, has_header=True, **fmt_params): f = fp if f is not None: for row in csv.reader(f, **fmt_params): if row: data = list(map(float, row)) x, ay = data[0], data[1:] # ay contains columns with y values for data_object, y in zip(data_objects, ay): if getattr(data_object, "data", None) is None: data_object.data = [] data_object.data.append([x, y]) for data_object in data_objects: data_object.data = np.array(data_object.data) if close: f.close() return data_objects @classmethod def parse(cls, fp, data_objects=None, close=True, split_columns=True, **fmt_params): """ Files are sniffed for the used csv dialect, but an optional set of formatting parameters can be passed that will override the sniffed parameters. """ filename, f, close = cls._get_file(fp, close=close) # Guess dialect: fmt_params, has_header, file_start = cls.sniff(f, **fmt_params) # Parse header: data_objects = cls._parse_header(filename, f, data_objects=data_objects, split_columns=split_columns, has_header=has_header, file_start=file_start, **fmt_params) # Parse data: data_objects = cls._parse_data(filename, f, data_objects=data_objects, split_columns=split_columns, has_header=has_header, **fmt_params) if close: f.close() return data_objects pass # end of class @xrd_parsers.register_parser() class CSVParser(GenericXYCSVParser): """ ASCII *.DAT, *.CSV, *.TAB and *.XY format parser """ description = "CSV XRD data" extensions = get_case_insensitive_glob("*.DAT", "*.CSV", "*.TAB", "*.XY") pass # end of class PyXRD-0.8.4/pyxrd/file_parsers/xrd_parsers/namespace.py000066400000000000000000000003721363064711000231620ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pyxrd.file_parsers.registry import ParserNamespace xrd_parsers = ParserNamespace("XRDParsers")PyXRD-0.8.4/pyxrd/file_parsers/xrd_parsers/rd_parser.py000066400000000000000000000111761363064711000232130ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os, struct from io import SEEK_SET #, SEEK_CUR, SEEK_END import numpy as np from pyxrd.generic.io.utils import get_case_insensitive_glob from pyxrd.generic.utils import u from ..base_parser import BaseParser from .namespace import xrd_parsers from .xrd_parser_mixin import XRDParserMixin def cap(lower, value, upper, out=None): if value < lower or value > upper: return out if out is not None else min(max(value, lower), upper) else: return value @xrd_parsers.register_parser() class RDParser(XRDParserMixin, BaseParser): """ Philips Binary V3 & V5 *.RD format parser """ description = "Phillips Binary V3/V5 *.RD" extensions = get_case_insensitive_glob("*.RD") mimetypes = ["application/octet-stream", ] __file_mode__ = "rb" @classmethod def _parse_header(cls, filename, fp, data_objects=None, close=False): f = fp try: basename = u(os.path.basename(filename)) except: basename = None # Adapt XRDFile list data_objects = cls._adapt_data_object_list(data_objects, num_samples=1) # Go to the start of the file f.seek(0, SEEK_SET) # Read file format version: version = f.read(2).decode() if version in ("V3", "V5"): # Read diffractometer, target and focus type: f.seek(84, SEEK_SET) diffractomer_type, target_type, focus_type = struct.unpack("bbb", f.read(3)) diffractomer_type = { 0: b"PW1800", 1: b"PW1710 based system", 2: b"PW1840", 3: b"PW3710 based system", 4: b"Undefined", 5: b"X'Pert MPD" }[cap(0, diffractomer_type, 5, 4)] target_type = { 0: b"Cu", 1: b"Mo", 2: b"Fe", 3: b"Cr", 4: b"Other" }[cap(0, target_type, 3, 4)] focus_type = { 0: b"BF", 1: b"NF", 2: b"FF", 3: b"LFF", 4: b"Unkown", }[cap(0, focus_type, 3, 4)] # Read wavelength information: f.seek(94, SEEK_SET) alpha1, alpha2, alpha_factor = struct.unpack("ddd", f.read(24)) # Read sample name: f.seek(146, SEEK_SET) sample_name = u(f.read(16).replace(b"\0", b"")) # Read data limits: f.seek(214) twotheta_step, twotheta_min, twotheta_max = struct.unpack("ddd", f.read(24)) twotheta_count = int((twotheta_max - twotheta_min) / twotheta_step) # Set data start: data_start = { "V3": 250, "V5": 810 }[version] data_objects[0].update( filename=basename, name=sample_name, twotheta_min=twotheta_min, twotheta_max=twotheta_max, twotheta_step=twotheta_step, twotheta_count=twotheta_count, target_type=target_type, alpha1=alpha1, alpha2=alpha2, alpha_factor=alpha_factor, data_start=data_start, version=version ) else: raise IOError("Only V3 and V5 *.RD files are supported!") if close: f.close() return data_objects @classmethod def _parse_data(cls, filename, fp, data_objects=None, close=False): f = fp # RD files are singletons, so no need to iterate over the list, # there is only one XRDFile instance: if data_objects[0].data == None: data_objects[0].data = [] # Parse data: if f is not None: if data_objects[0].version in ("V3", "V5"): # Move to start of data: f.seek(data_objects[0].data_start) n = 0 while n < data_objects[0].twotheta_count: y, = struct.unpack("H", f.read(2)) data_objects[0].data.append([ data_objects[0].twotheta_min + data_objects[0].twotheta_step * float(n + 0.5), float(y) ]) n += 1 else: raise IOError("Only V3 and V5 *.RD files are supported!") data_objects[0].data = np.array(data_objects[0].data) if close: f.close() return data_objects pass # end of class PyXRD-0.8.4/pyxrd/file_parsers/xrd_parsers/udf_parser.py000066400000000000000000000073611363064711000233650ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os import numpy as np from pyxrd.generic.io.utils import get_case_insensitive_glob from pyxrd.generic.utils import u from ..base_parser import BaseParser from .namespace import xrd_parsers from .xrd_parser_mixin import XRDParserMixin @xrd_parsers.register_parser() class UDFParser(XRDParserMixin, BaseParser): """ ASCII Philips *.UDF format """ description = "Philips *.UDF" extensions = get_case_insensitive_glob("*.UDF") @classmethod def _parse_header(cls, filename, fp, data_objects=None, close=False): f = fp try: basename = u(os.path.basename(filename)) except: basename = None # Adapt XRDFile list data_objects = cls._adapt_data_object_list(data_objects, num_samples=1) # Move to the start of the file f.seek(0) # Go over the header: header_dict = {} for lineno, line in enumerate(f): # Start of data after this line: if line.strip() == "RawScan": data_start = f.tell() break else: # Break header line into separate parts, and strip trailing whitespace: parts = list(map(str.strip, line.split(','))) # If length is shorter then three, somethings wrong if len(parts) < 3: raise IOError("Header of UDF file is malformed at line %d" % lineno) # Handle some of the header's arguments manually, the rest is # just passed to the data object as keyword arguments... if parts[0] == "SampleIdent": name = parts[1] elif parts[0] == "DataAngleRange": twotheta_min = float(parts[1]) twotheta_max = float(parts[2]) elif parts[0] == "ScanStepSize": twotheta_step = float(parts[1]) # TODO extract other keys and replace with default names header_dict[parts[0]] = ','.join(parts[1:-1]) twotheta_count = int((twotheta_max - twotheta_min) / twotheta_step) data_objects[0].update( filename=basename, name=name, twotheta_min=twotheta_min, twotheta_max=twotheta_max, twotheta_step=twotheta_step, twotheta_count=twotheta_count, data_start=data_start, **header_dict ) if close: f.close() return data_objects @classmethod def _parse_data(cls, filename, fp, data_objects=None, close=False): f = fp # UDF files are singletons, so no need to iterate over the list, # there is only one data object instance: if data_objects[0].data == None: data_objects[0].data = [] if f is not None: f.seek(data_objects[0].data_start) n = 0 last_value_reached = False while n <= data_objects[0].twotheta_count and not last_value_reached: parts = list(map(str.strip, f.readline().split(','))) for part in parts: # Last value ends with a slash: if part.endswith('/'): part = part[:-1] # remove the ending "/" last_value_reached = True n += 1 data_objects[0].data.append([ float(data_objects[0].twotheta_min + data_objects[0].twotheta_step * n), float(part) ]) data_objects[0].data = np.array(data_objects[0].data) if close: f.close() return data_objects pass # end of class PyXRD-0.8.4/pyxrd/file_parsers/xrd_parsers/xrd_data_object.py000066400000000000000000000042651363064711000243470ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from io import StringIO from ..data_object import DataObject from pyxrd.generic.utils import not_none class XRDDataObject(DataObject): """ A generic class holding all the information retrieved from an XRD data file using an XRD-parser class. """ # General information name = None date = None # x-axis range twotheta_min = None twotheta_max = None twotheta_count = None twotheta_step = None # Wavelength and target information target_type = None alpha1 = None alpha2 = None alpha_average = None beta = None alpha_factor = None # Goniometer setup radius = None soller1 = None soller2 = None divergence = None # Generator or list of x,y data data = None def create_gon_file(self): output = """ { "type": "Goniometer", "properties": { "radius": %(radius)f, "divergence": %(divergence)f, "soller1": %(soller1)f, "soller2": %(soller2)f, "min_2theta": %(twotheta_min)f, "max_2theta": %(twotheta_max)f, "steps": %(twotheta_count)f, "wavelength": %(alpha_average)f, "has_ads": false, "ads_fact": 1.0, "ads_phase_fact": 1.0, "ads_phase_shift": 0.0, "ads_const": 0.0 } }""" % dict( radius=float(not_none(self.radius, 25)), divergence=float(not_none(self.divergence, 0.5)), soller1=float(not_none(self.soller1, 2.5)), soller2=float(not_none(self.soller2, 2.5)), twotheta_min=float(not_none(self.twotheta_min, 3.0)), twotheta_max=float(not_none(self.twotheta_max, 45.0)), twotheta_count=float(not_none(self.twotheta_count, 2500)), alpha_average=float(not_none(self.alpha_average, 0.154056)), ) f = StringIO(output) f.flush() return f pass #end of class PyXRD-0.8.4/pyxrd/file_parsers/xrd_parsers/xrd_parser_mixin.py000066400000000000000000000006441363064711000246050ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .xrd_data_object import XRDDataObject class XRDParserMixin(object): """ This is a mixin class which provides common functionality and attributes for XRD-data parser classes. """ data_object_type = XRDDataObject pass #end of class PyXRD-0.8.4/pyxrd/generic/000077500000000000000000000000001363064711000152545ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/__init__.py000066400000000000000000000000001363064711000173530ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/asynchronous/000077500000000000000000000000001363064711000200075ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/asynchronous/__init__.py000066400000000000000000000000001363064711000221060ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/asynchronous/cancellable.py000066400000000000000000000006361363064711000226130ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. class Cancellable(object): """ Object which has a (threaded) action that can be cancelled by the user. """ _stop = None def _user_cancelled(self): return bool(self._stop is not None and self._stop.is_set()) pass #end of classPyXRD-0.8.4/pyxrd/generic/asynchronous/dummy_async_provider.py000066400000000000000000000015631363064711000246300ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon from .dummy_async_server import DummyAsyncServer class DummyAsyncServerProvider(object): _server = DummyAsyncServer() @classmethod def get_status(cls): """ should return a three-tuple consisting of the status colour, label and a description: ("#FF0000", "Error", "Nameserver not running") """ return ("#00FF00", "Connected (Dummy)", "Succesfully connected to Dummy PyXRD Server") @classmethod def get_server(cls): return cls._server @classmethod def launch_server(cls): if cls._server is None: cls._server = DummyAsyncServer() @classmethod def stop_server(cls): cls._server.shutdown() del cls._server pass #end of classPyXRD-0.8.4/pyxrd/generic/asynchronous/dummy_async_result.py000066400000000000000000000005301363064711000243050ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon class DummyAsyncResult(object): """ A non-asynchronous dummy implementation of the AsyncResult object """ def __init__(self, func): self.result = func() def get(self): return self.result pass #end of classPyXRD-0.8.4/pyxrd/generic/asynchronous/dummy_async_server.py000066400000000000000000000006671363064711000243100ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon from .dummy_async_result import DummyAsyncResult class DummyAsyncServer(object): """ A non-asynchronous dummy implementation of the AsyncServer object """ def loopCondition(self): return True def submit(self, func): return DummyAsyncResult(func) def shutdown(self): pass pass #end of classPyXRD-0.8.4/pyxrd/generic/asynchronous/exceptions.py000066400000000000000000000001561363064711000225440ustar00rootroot00000000000000 class ServerNotRunningException(Exception): pass class ServerStartTimeoutExcecption(Exception): passPyXRD-0.8.4/pyxrd/generic/asynchronous/has_async_calls.py000066400000000000000000000020661363064711000235130ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) from .providers import get_provider from .exceptions import * from .dummy_async_server import DummyAsyncServer class HasAsyncCalls(object): """ Class providing utility functions for async calls """ def submit_async_call(self, func): """ Utility that passes function calls down to the async server """ try: self._async_server = get_provider().get_server() except (ServerNotRunningException, ServerStartTimeoutExcecption): logger.warning("Could not get the default provided async server, using dummy implementation") self._async_server = DummyAsyncServer() return self._async_server.submit(func) def fetch_async_result(self, result): """ Utility that parses the result objects returned by submit_async_call""" return result.get() pass #end of classPyXRD-0.8.4/pyxrd/generic/asynchronous/providers.py000066400000000000000000000024341363064711000224010ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon import logging logger = logging.getLogger(__name__) from pyxrd.data.settings import ASYNC_SERER_PROVIDERS, ASYNC_SERVER_PRELOAD _async_provider = None def load_provider(): global _async_provider logger.info("Loading async server provider") for class_path in ASYNC_SERER_PROVIDERS: logger.info("Trying to load async server provider at %s" % class_path) try: components = class_path.split('.') class_name = components[-1] mod = __import__('.'.join(components[:-1]), fromlist=[class_name]) klass = getattr(mod, class_name) except (ImportError, AttributeError): logger.warning("Failed to import async provider %s!" % class_path) continue _async_provider = klass break logger.info("Loaded async server provider '%s'" % _async_provider) # Pre-load async server if needed: if ASYNC_SERVER_PRELOAD: load_provider() if _async_provider is not None: _async_provider.launch_server() def get_provider(): global _async_provider if _async_provider is None: load_provider() return _async_provider def get_status(): return get_provider().get_status()PyXRD-0.8.4/pyxrd/generic/controllers/000077500000000000000000000000001363064711000176225ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/controllers/__init__.py000066400000000000000000000012671363064711000217410ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .objectliststore_controllers import ( TreeViewMixin, TreeModelMixin, TreeControllerMixin, ObjectListStoreController, ChildObjectListStoreController, InlineObjectListStoreController, ) from .base_controller import BaseController from .dialog_controller import DialogController __all__ = [ "TreeViewMixin", "TreeModelMixin", "TreeControllerMixin", "ObjectListStoreController", "ChildObjectListStoreController", "InlineObjectListStoreController", "BaseController", "DialogController" ] PyXRD-0.8.4/pyxrd/generic/controllers/base_controller.py000066400000000000000000000006621363064711000233550ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from mvc.controller import Controller from .status_bar_mixin import StatusBarMixin class BaseController(StatusBarMixin, Controller): file_filters = ("All Files", "*.*") widget_handlers = {} # handlers can be string representations of a class method pass # end of class PyXRD-0.8.4/pyxrd/generic/controllers/dialog_controller.py000066400000000000000000000020171363064711000236760ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import gi gi.require_version('Gtk', '3.0') from gi.repository import Gdk from pyxrd.generic.controllers.base_controller import BaseController class DialogController(BaseController): """ Simple controller which has a DialogView subclass instance as view. """ # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_btn_ok_clicked(self, event): self.on_cancel() return True def on_keypress(self, widget, event): if event.keyval == Gdk.keyval_from_name("Escape"): self.on_cancel() return True def on_window_edit_dialog_delete_event(self, event, args=None): self.on_cancel() return True # do not propagate def on_cancel(self): self.view.hide() pass #end of classPyXRD-0.8.4/pyxrd/generic/controllers/line_controllers.py000066400000000000000000000246201363064711000235550ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory from pyxrd.generic.controllers import BaseController, DialogController from pyxrd.generic.plot.eye_dropper import EyeDropper from pyxrd.file_parsers.xrd_parsers import xrd_parsers from pyxrd.data import settings class LinePropertiesController(BaseController): """ Controller for Line models' general properties """ auto_adapt_excluded = [ "noise_fraction", "shift_value", "shift_position", "smooth_type", "smooth_degree", "strip_startx", "strip_endx", "noise_level", "bg_type", "bg_position", "bg_scale", "peak_startx", "peak_endx", "peak_area_result", "peak_fwhm_result" ] # these are handled by other controllers __LINEID__ = "TRUE" # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def register_adapters(self): super(LinePropertiesController, self).register_adapters() self.update_sensitivities() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update_sensitivities(self): """ Updates the views sensitivities according to the model state. """ self.view[self.view.widget_format % "color"].set_sensitive(not self.model.inherit_color) self.view["spb_%s" % self.view.widget_format % "lw"].set_sensitive(not self.model.inherit_lw) self.view[self.view.widget_format % "ls"].set_sensitive(not self.model.inherit_ls) self.view[self.view.widget_format % "marker"].set_sensitive(not self.model.inherit_marker) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @BaseController.observe("inherit_color", assign=True) @BaseController.observe("inherit_lw", assign=True) @BaseController.observe("inherit_ls", assign=True) @BaseController.observe("inherit_marker", assign=True) def notif_color_toggled(self, model, prop_name, info): self.update_sensitivities() pass # end of class class PatternActionController(DialogController): """ General class for actions that can be applied on ExperimentalPattern models. Attributes: model_setup_method: the name of the method to call on the model to auto-setup any variables for the action. Optional; can be None. model_action_method: the name of the method to call on the model when the user decides to apply the action. Required, cannot be None. model_cancel_method: the name of the method to call on the model when the user decides to cancel the action. Optional; can be None. """ model_setup_method = None model_action_method = None model_cancel_method = None def register_adapters(self): if self.model_setup_method is not None: getattr(self.model, self.model_setup_method)() return super(PatternActionController, self).register_adapters() # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_btn_ok_clicked(self, event): try: action = getattr(self.model, self.model_action_method) except AttributeError: raise ValueError("Subclasses of PatternActionController should specify a valid action method name (was '%s')!" % self.model_action_method) else: action() return super(PatternActionController, self).on_btn_ok_clicked(event) def on_cancel(self): if self.model_cancel_method is not None: getattr(self.model, self.model_cancel_method)() return super(PatternActionController, self).on_cancel() pass # end of class class AddNoiseController(PatternActionController): """ Controller for the experimental pattern 'add noise' action view. """ auto_adapt_included = [ "noise_fraction", ] model_setup_method = None model_action_method = "add_noise" model_cancel_method = "clear_noise_variables" pass # end of class class ShiftDataController(PatternActionController): """ Controller for the experimental pattern 'shift data' action view. """ auto_adapt_included = [ "shift_value", "shift_position", ] model_setup_method = "setup_shift_variables" model_action_method = "shift_data" model_cancel_method = "clear_shift_variables" pass # end of class class SmoothDataController(PatternActionController): auto_adapt_included = [ "smooth_type", "smooth_degree", ] model_setup_method = "setup_smooth_variables" model_action_method = "smooth_data" model_cancel_method = "clear_smooth_variables" pass # end of class class CalculatePeakPropertiesController(PatternActionController): auto_adapt_included = [ "peak_startx", "peak_endx", "peak_fwhm_result", "peak_area_result" ] model_setup_method = None model_action_method = "clear_peak_properties_variables" model_cancel_method = "clear_peak_properties_variables" # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_sample_start_clicked(self, event): self.sample("peak_startx") return True def on_sample_end_clicked(self, event): self.sample("peak_endx") return True def sample(self, attribute): def onclick(x_pos, *args): if self.edc is not None: self.edc.enabled = False self.edc.disconnect() self.edc = None if x_pos != -1: setattr(self.model, attribute, x_pos) self.view.get_toplevel().present() self.edc = EyeDropper( self.parent.parent.plot_controller, onclick ) self.view.get_toplevel().hide() self.parent.parent.view.get_toplevel().present() pass # end of class class StripPeakController(PatternActionController): auto_adapt_included = [ "strip_startx", "strip_endx", "noise_level", ] model_setup_method = None model_action_method = "strip_peak" model_cancel_method = "clear_strip_variables" # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_sample_start_clicked(self, event): self.sample("strip_startx") return True def on_sample_end_clicked(self, event): self.sample("strip_endx") return True def sample(self, attribute): def onclick(x_pos, *args): if self.edc is not None: self.edc.enabled = False self.edc.disconnect() self.edc = None if x_pos != -1: setattr(self.model, attribute, x_pos) self.view.get_toplevel().present() self.edc = EyeDropper( self.parent.parent.plot_controller, onclick ) self.view.get_toplevel().hide() self.parent.parent.view.get_toplevel().present() pass # end of class class BackgroundController(PatternActionController): """ Controller for the experimental pattern 'remove background' action view. """ file_filters = xrd_parsers.get_import_file_filters() auto_adapt_included = [ "bg_type", "bg_position", "bg_scale", ] model_setup_method = "find_bg_position" model_action_method = "remove_background" model_cancel_method = "clear_bg_variables" def register_view(self, view): super(BackgroundController, self).register_view(view) view.set_file_dialog( DialogFactory.get_load_dialog( title="Open XRD file for import", parent=view.get_top_widget(), filters=self.file_filters ), self.on_pattern_file_set ) view.select_bg_view(self.model.get_bg_type_lbl().lower()) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @PatternActionController.observe("bg_type", assign=True) def notif_bg_type_changed(self, model, prop_name, info): self.view.select_bg_view(self.model.get_bg_type_lbl().lower()) return # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_pattern_file_set(self, button, dialog): # TODO # This should allow more flexibility: # Patterns should be allowed to not have the exact same shape, # add an x-shift variable to align them filename = dialog.filename parser = dialog.parser data_objects = None message = "An unexpected error has occurred when trying to parse %s:\n\n" % os.path.basename(filename) message += "{}\n\n" message += "This is most likely caused by an invalid or unsupported file format." with DialogFactory.error_dialog_handler(message, parent=self.view.get_toplevel(), reraise=settings.DEBUG): # Parse the pattern file data_objects = parser.parse(filename) pattern = data_objects[0].data bg_pattern_x = pattern[:, 0].copy() bg_pattern_y = pattern[:, 1].copy() del pattern # Interpolate/Extrapolate where needed to match data shape and range from scipy.interpolate import interp1d f = interp1d( bg_pattern_x, bg_pattern_y, bounds_error=False, fill_value=0 ) bg_xnew = self.model.data_x bg_ynew = f(bg_xnew) self.model.bg_pattern = bg_ynew pass # end of class PyXRD-0.8.4/pyxrd/generic/controllers/objectliststore_controllers.py000066400000000000000000000474621363064711000260560ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from contextlib import contextmanager import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from mvc.adapters.gtk_support.tree_view_adapters import wrap_list_property_to_treemodel from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory from pyxrd.generic.utils import not_none from pyxrd.generic.views.treeview_tools import new_text_column, setup_treeview from .base_controller import BaseController from .dialog_controller import DialogController class TreeModelMixin(object): """ A mixin providing functionality to get a TreeModel property from a model. If that property is an actual TreeModel, it will use it directly. Otherwise it will first wrap it in an ObjectListStore """ treemodel_getter_format = "get_%s_tree_model" treemodel_property_name = "" treemodel_class_type = None _treemodel = None @property def treemodel(self): self._update_treemodel_property() return self._treemodel @property def treemodel_data(self): if getattr(self, "model", None) is not None: return getattr(self.model, self.treemodel_property_name, None) else: return None def __init__(self, treemodel_property_name=None, treemodel_class_type=None, *args, **kwargs): super(TreeModelMixin, self).__init__(*args, **kwargs) self.treemodel_property_name = not_none(treemodel_property_name, self.treemodel_property_name) self.treemodel_class_type = not_none(treemodel_class_type, self.treemodel_class_type) def _update_treemodel_property(self): if getattr(self, "model", None) is not None: self._treemodel = wrap_list_property_to_treemodel( self.model, getattr(type(self.model), self.treemodel_property_name) ) class TreeViewMixin(object): """ Mixin that provides some generic methods to access or set the objects selected in a treeview. """ def get_selected_object(self, tv): # call this implementation, not the overriden method: objects = TreeViewMixin.get_selected_objects(self, tv) if objects is not None and len(objects) == 1: return objects[0] return None def get_selected_objects(self, tv): selection = tv.get_selection() if selection.count_selected_rows() >= 1: model, paths = selection.get_selected_rows() if hasattr(model, "get_tree_node_object_from_path"): return list(map(model.get_tree_node_object_from_path, paths)) else: return list(map(model.get_user_data_from_path, paths)) return None def get_selected_paths(self, tv): selection = tv.get_selection() if selection.count_selected_rows() >= 1: model, paths = selection.get_selected_rows() # @UnusedVariable return paths return None def get_all_objects(self, tv): return tv.get_model()._data def set_selected_paths(self, tv, paths): selection = tv.get_selection() selection.unselect_all() for path in paths: selection.select_path(path) class TreeControllerMixin(TreeViewMixin, TreeModelMixin): """ Mixin that can be used for regular ObjectListStoreControllers (two-pane view). Attributes: model_property_name: the property name in the model corresponding to the ObjectListStore multi_selection: whether or not to allow multiple items to be selected columns: a list of tuples (name, column index or name) detailing which columns should be added to the TreeView. If a column name is passed, it is translated to the corresponding index. By default a text column is added, for custom setups you can define a custom method name according to this format: setup_treeview_col_name_%s Replace the %s with the column name you specified in this list. delete_msg: the default message to display when a user wants to delete one or more items. obj_type_map: a list of three-tuples (object type, view type, controller type) used to create the controller and view for editing a selected object. """ multi_selection = True columns = [ ("Object name", 0) ] delete_msg = "Deleting objects is irreversible!\nAre You sure you want to continue?" obj_type_map = [] # list of three-tuples (object type, view type, controller type) _edit_controller = None _edit_view = None def __init__(self, *args, **kwargs): self.multi_selection = kwargs.pop("multi_selection", True) self.columns = kwargs.pop("columns", self.columns) self.delete_msg = kwargs.pop("delete_msg", self.delete_msg) super(TreeControllerMixin, self).__init__(*args, **kwargs) __row_signal_ids = None def _update_treemodel_property(self): # If we've connected to a treemodel before, clean up first: if self.__row_signal_ids is not None: old_treemodel, deleted_id, inserted_id = self.__row_signal_ids old_treemodel.disconnect(deleted_id) old_treemodel.disconnect(inserted_id) self.__row_signal_ids = None # If the new treemodel is set, connect it up: if getattr(self, "model", None) is not None: super(TreeControllerMixin, self)._update_treemodel_property() # Use private _treemodel attribute, otherwise we get infinite recursions self.__row_signal_ids = ( self._treemodel, self._treemodel.connect("row-deleted", self.on_item_removed), self._treemodel.connect("row-inserted", self.on_item_inserted) ) def get_new_edit_view(self, obj): """ Gets a new 'edit object' view for the given obj, view and parent view. Default implementation loops over the `obj_type_map` attribute until it encounters a match. """ if obj == None: return self.view.none_view else: for obj_tp, view_tp, ctrl_tp in self.obj_type_map: # @UnusedVariable if isinstance(obj, obj_tp): return view_tp(parent=self.view) raise NotImplementedError("Unsupported object type %s; subclasses of" " TreeControllerMixin need to define an obj_type_map attribute!" % obj) def get_new_edit_controller(self, obj, view, parent=None): """ Gets a new 'edit object' controller for the given obj, view and parent controller. Default implementation loops over the `obj_type_map` attribute until it encounters a match. """ if obj == None: return None else: for obj_tp, view_tp, ctrl_tp in self.obj_type_map: # @UnusedVariable if isinstance(obj, obj_tp): return ctrl_tp(model=obj, view=view, parent=parent) raise NotImplementedError("Unsupported object type; subclasses of" " TreeControllerMixin need to define an obj_type_map attribute!") def edit_object(self, obj): self._edit_view = self.view.set_edit_view(self.get_new_edit_view(obj)) self._edit_controller = self.get_new_edit_controller(obj, self._edit_view, parent=self.parent) self._edit_view.show_all() return True def register_adapters(self): # connects the treeview to the treemodel self.setup_treeview(self.view.treeview) # we can now edit 'nothing': self.view.set_selection_state(None) self.edit_object(None) def setup_treeview(self, tv): """ Sets up the treeview with columns based on the columns-tuple passed to the __init__ or set in the class definition. Subclasses can override either this method completely or provide custom column creation code on a per-column basis. To do this, create a method for e.g. column with colnr = 2: def setup_treeview_col_2(self, treeview, name, col_descr, col_index, tv_col_nr): ... If a string description of the column number was given, e.g. for the column c_name the definition should be: def setup_treeview_col_c_name(self, treeview, name, col_descr, col_index, tv_col_nr): ... The method should return True upon success or False otherwise. """ sel_mode = 'MULTIPLE' if self.multi_selection else 'SINGLE' # @UndefinedVariable setup_treeview( tv, self.treemodel, sel_mode=sel_mode, on_selection_changed=self.objects_tv_selection_changed) tv.set_model(self.treemodel) # reset: for col in tv.get_columns(): tv.remove_column(col) # add columns for tv_col_nr, (name, col_descr) in enumerate(self.columns): try: col_index = int(col_descr) except: col_index = getattr(self.treemodel, str(col_descr), col_descr) handled = False if hasattr(self, "setup_treeview_col_%s" % str(col_descr)): handler = getattr(self, "setup_treeview_col_%s" % str(col_descr)) if callable(handler): handled = handler(tv, name, col_descr, col_index, tv_col_nr) # custom handler failed or not present, default text column: if not handled: tv.append_column(new_text_column( name, text_col=col_index, resizable=(tv_col_nr == 0), expand=(tv_col_nr == 0), xalign=0.0 if tv_col_nr == 0 else 0.5)) return True def get_selected_index(self): cur_obj = self.get_selected_object() if cur_obj is not None: return self.treemodel_data.index(cur_obj) else: return None def get_selected_object(self): return super(TreeControllerMixin, self).get_selected_object(self.view.treeview) def get_selected_objects(self): return super(TreeControllerMixin, self).get_selected_objects(self.view.treeview) def get_all_objects(self): return super(TreeControllerMixin, self).get_all_objects(self.view.treeview) def select_object(self, obj, path=None, unselect_all=True): selection = self.view.treeview.get_selection() if unselect_all: selection.unselect_all() if obj is not None or path is not None: if path is None: path = self.treemodel.on_get_path(obj) if path is not None: selection.select_path(path) def select_objects(self, objs): for obj in objs: self.select_object(obj, unselect_all=False) def add_object(self, new_object): if new_object is not None: index = self.get_selected_index() if index is not None: self.treemodel_data.insert(index + 1, new_object) else: self.treemodel_data.append(new_object) # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_item_removed(self, *args): self.select_object(None, unselect_all=True) def on_item_inserted(self, model, path, iter): self.select_object(None, path=path, unselect_all=True) def objects_tv_selection_changed(self, selection): obj = self.get_selected_object() objs = self.get_selected_objects() self.view.set_selection_state(len(objs) if objs is not None else None) if self._edit_controller == None or obj != self._edit_controller.model: self.edit_object(obj) def on_load_object_clicked(self, event): raise NotImplementedError def on_save_object_clicked(self, event): raise NotImplementedError def create_new_object_proxy(self): raise NotImplementedError def on_add_object_clicked(self, event): new_object = self.create_new_object_proxy() if new_object: self.add_object(new_object) self.select_object(new_object) return True @contextmanager def _multi_operation_context(self): """ This method should be called as a context manager (with self._multi_...) anytime more then one object is changed at the same time. Default implementation does not do anything, but this can be used to e.g. hold signals from firing until all objects have changed. """ yield # default implementation doesn't do anything. def on_del_object_clicked(self, event, del_callback=None, callback=None): tv = self.view.treeview selection = tv.get_selection() if selection.count_selected_rows() >= 1: def delete_objects(dialog): with self._multi_operation_context(): for obj in self.get_selected_objects(): if callable(del_callback): del_callback(obj) else: self.treemodel_data.remove(obj) if callable(callback): callback(obj) self.edit_object(None) parent = self.view.get_top_widget() if not isinstance(parent, Gtk.Window): # @UndefinedVariable parent = None DialogFactory.get_confirmation_dialog( message=self.delete_msg, parent=parent ).run(delete_objects) class ObjectListStoreController(DialogController, TreeControllerMixin): """ A stand-alone, regular ObjectListStore controller (left pane with objects and right pane with object properties) """ title = "Edit Dialog" auto_adapt = False def __init__(self, *args, **kwargs): self.title = not_none(kwargs.pop("title", None), self.title) super(ObjectListStoreController, self).__init__(*args, **kwargs) def register_view(self, view): super(ObjectListStoreController, self).register_view(view) view.set_title(self.title) @DialogController.model.setter def _set_model(self, model): super(ObjectListStoreController, self)._set_model(model) if self.view is not None: self._update_treemodel_property() def register_adapters(self): TreeControllerMixin.register_adapters(self) class ChildObjectListStoreController(BaseController, TreeControllerMixin): """ An embeddable, regular ObjectListStore controller (left pane with objects and right pane with object properties) """ auto_adapt = False @DialogController.model.setter def _set_model(self, model): super(ObjectListStoreController, self)._set_model(model) if self.view is not None: self._update_treemodel_property() def register_adapters(self): TreeControllerMixin.register_adapters(self) class InlineObjectListStoreController(BaseController, TreeControllerMixin): """ ObjectListStore controller that consists of a single TreeView, with import & export and add & delete buttons and an optional combo box for type selection Subclasses should override the _setup_treeview method to setup their columns and edit support. """ treeview = None enable_import = False enable_export = False add_types = list() auto_adapt = False _edit_dict = None def _edit_item(self, item): item_type = type(item) if self._edit_dict is None: # Create a edit dict which keeps track of our controllers self._edit_dict = {} # If the first time, create the view & controller if not item in self._edit_dict: for name, tpe, view, ctrl in self.add_types: # @UnusedVariable if tpe == item_type: vw = view() ctrl = ctrl(model=item, view=vw, parent=self) self._edit_dict[item] = (vw, ctrl) break # Re-use previously created controllers vw, ctrl = self._edit_dict[item] vw.present() def _setup_combo_type(self, combo): # TODO this is view-related! if self.add_types: store = Gtk.ListStore(str, object, object, object) # @UndefinedVariable for name, type, view, ctrl in self.add_types: # @ReservedAssignment store.append([name, type, view, ctrl]) combo.set_model(store) cell = Gtk.CellRendererText() # @UndefinedVariable combo.pack_start(cell, True) combo.add_attribute(cell, 'text', 0) def on_changed(combo, user_data=None): itr = combo.get_active_iter() if itr is not None: val = combo.get_model().get_value(itr, 1) self.add_type = val combo.connect('changed', on_changed) combo.set_active_iter(store[0].iter) combo.set_visible(True) combo.set_no_show_all(False) combo.show_all() def _setup_treeview(self, tv, model): raise NotImplementedError def __init__(self, *args, **kwargs): self.enable_import = kwargs.pop("enable_import", self.enable_import) self.enable_export = kwargs.pop("enable_export", self.enable_export) super(InlineObjectListStoreController, self).__init__(*args, **kwargs) @BaseController.model.setter def _set_model(self, model): super(ObjectListStoreController, self)._set_model(model) if self.view is not None: self._update_treemodel_property() def register_adapters(self): if self.treemodel is not None: self.treeview = self.view.treeview self.treeview.connect('cursor-changed', self.on_treeview_cursor_changed, self.treemodel) self._setup_treeview(self.treeview, self.treemodel) self.type_combobox = self.view.type_combobox_widget self._setup_combo_type(self.type_combobox) self.update_sensitivities() return def update_sensitivities(self): self.view.del_item_widget.set_sensitive((self.treeview.get_cursor() != (None, None))) self.view.add_item_widget.set_sensitive((self.treemodel is not None)) self.view.export_items_widget.set_visible(self.enable_export) self.view.export_items_widget.set_sensitive(len(self.treemodel_data) > 0) self.view.import_items_widget.set_visible(self.enable_import) def get_selected_object(self): return TreeViewMixin.get_selected_object(self, self.treeview) def get_selected_objects(self): return TreeViewMixin.get_selected_objects(self, self.treeview) def get_all_objects(self): return TreeViewMixin.get_all_objects(self, self.treeview) def select_object(self, obj, path=None, unselect_all=True): selection = self.treeview.get_selection() if unselect_all: selection.unselect_all() if obj is not None and hasattr(self.treemodel, "on_get_path"): selection.select_path(self.treemodel.on_get_path(obj)) elif path is not None: selection.select_path(path) def create_new_object_proxy(self): raise NotImplementedError def edit_object(self, obj): pass # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_treeview_cursor_changed(self, widget, model): self.update_sensitivities() def on_item_cell_edited(self, cell, path, new_text, model, col): model.set_value(model.get_iter(path), col, model.convert(col, new_text)) pass pass # end of class PyXRD-0.8.4/pyxrd/generic/controllers/status_bar_mixin.py000066400000000000000000000027621363064711000235560ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from functools import wraps class StatusBarMixin(object): @property def statusbar(self): if self.parent is not None: return self.parent.statusbar elif self.view is not None: return self.view['statusbar'] else: return None @property def status_cid(self): if self.statusbar is not None: return self.statusbar.get_context_id(self.__class__.__name__) else: return None @staticmethod def status_message(message, cid=None): def decorator(func): @wraps(func) def wrapper(self, *args, **kwargs): self.push_status_msg(message, cid) res = func(self, *args, **kwargs) self.pop_status_msg(cid) return res return wrapper return decorator def push_status_msg(self, msg, cid=None): if cid is not None: cid = self.statusbar.get_context_id(cid) else: cid = self.status_cid if cid is not None: self.statusbar.push(cid, msg) def pop_status_msg(self, cid=None): if cid is not None: cid = self.statusbar.get_context_id(cid) else: cid = self.status_cid if cid is not None: self.statusbar.pop(cid) pass # end of class PyXRD-0.8.4/pyxrd/generic/exceptions.py000066400000000000000000000003611363064711000200070ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. class AlreadyRegistered(Exception): pass class NotRegistered(Exception): pass PyXRD-0.8.4/pyxrd/generic/gtk_tools/000077500000000000000000000000001363064711000172615ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/gtk_tools/__init__.py000066400000000000000000000000001363064711000213600ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/gtk_tools/dummy_gobject.py000066400000000000000000000004061363064711000224630ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. SIGNAL_RUN_LAST = None TYPE_NONE = None TYPE_PYOBJECT = None def type_register(self, *args, **kwargs): passPyXRD-0.8.4/pyxrd/generic/gtk_tools/dummy_gtk.py000066400000000000000000000041131363064711000216320ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from mvc.support.utils import get_new_uuid __all__ = [ "gobject", "GtkTreeIter", "GenericTreeModel" "TREE_MODEL_LIST_ONLY" ] TREE_MODEL_LIST_ONLY = 0x00 TREE_MODEL_ITERS_PERSIST = 0x00 events_pending = lambda: False class GtkTreeIter(): def __init__(self, user_data, path=None): self.user_data = user_data self.path = path pass # end of class class GenericTreeModel(object): __connected_signals__ = None def __init__(self): self.__connected_signals__ = {} def connect(self, signal_name, handler, *args): handlers = self.__connected_signals__.get(signal_name, {}) handler_id = get_new_uuid() handlers[handler_id] = (handler, args) self.__connected_signals__[signal_name] = handlers return handler_id def disconnect(self, signal_name, handler_id): try: handlers = self.__connected_signals__.get(signal_name, {}) del handlers[handler_id] except KeyError: pass return def emit(self, signal_name, args=()): handlers = self.__connected_signals__.get(signal_name, {}) for id, (handler, user_args) in handlers.items(): # @ReservedAssignment handler(self, *((args,) + user_args)) pass def set_property(self, *args, **kwargs): pass def create_tree_iter(self, user_data): return GtkTreeIter(user_data) def get_path(self, itr): return self.on_get_path(itr.user_data) def get_iter(self, path): return self.create_tree_iter(self.on_get_iter(path)) def row_inserted(self, path, itr): self.emit("row-inserted", (path, itr)) def row_deleted(self, indeces): self.emit("row-deleted", (indeces,)) def invalidate_iters(self): pass # TOD0! def iter_is_valid(self, itr): return True # TODO! def __len__(self): return len(self._model_data) pass # end of class PyXRD-0.8.4/pyxrd/generic/gtk_tools/gtkexcepthook.py000066400000000000000000000221651363064711000225200ustar00rootroot00000000000000# (c) 2003 Gustavo J A M Carneiro gjc at inescporto.pt # 2004-2005 Filip Van Raemdonck # # http://www.daa.com.au/pipermail/pygtk/2003-August/005775.html # Message-ID: <1062087716.1196.5.camel@emperor.homelinux.net> # "The license is whatever you want." # # This file was downloaded from http://www.sysfs.be/downloads/ # Adaptions 2009-2010 by Martin Renold: # - let KeyboardInterrupt through # - print traceback to stderr before showing the dialog # - nonzero exit code when hitting the "quit" button # - suppress more dialogs while one is already active # - fix Details button when a context in the traceback is None # - remove email features # - fix lockup with dialog.run(), return to mainloop instead # see also http://faq.pyGtk.org/index.py?req=show&file=faq20.010.htp # Changes 2012 by Mathijs Dumon: # - removed the gtkcompat import statement import inspect, linecache, pydoc, sys, traceback from io import StringIO from gettext import gettext as _ import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk, Gdk, Pango # @UnresolvedImport original_excepthook = sys.excepthook class GtkExceptionHook(): """ Exception handling GTK-dialog hook """ # Function that will be called when the user presses "Quit" # Return True to confirm quit, False to cancel quit_confirmation_func = None exception_dialog_active = False RESPONSE_QUIT = 1 def analyze_simple (self, exctyp, value, tb): """ Analyzes the exception into a human readable stack trace """ trace = StringIO() traceback.print_exception (exctyp, value, tb, None, trace) return trace def lookup (self, name, frame, lcls): '''Find the value for a given name in the given frame''' if name in lcls: return 'local', lcls[name] elif name in frame.f_globals: return 'global', frame.f_globals[name] elif '__builtins__' in frame.f_globals: builtins = frame.f_globals['__builtins__'] if type (builtins) is dict: if name in builtins: return 'builtin', builtins[name] else: if hasattr (builtins, name): return 'builtin', getattr (builtins, name) return None, [] _parent_window = [None] _level = 0 @property def parent_window(self): return self._parent_window[self._level] @parent_window.setter def parent_window(self, value): self._parent_window[self._level] = value def overriden_parent_window(self, parent): """ Sets the parent window temporarily to another value and returns itself, so this can be used as a context manager. """ self._parent_window.append(parent) self._level = len(self._parent_window) return self def __enter__(self): pass def __exit__(self): if self._level > 0: self._parent_window.pop(self._level) self._level = len(self._parent_window) def analyze (self, exctyp, value, tb): """ Analyzes the exception into a human readable stack trace """ import tokenize, keyword trace = StringIO() nlines = 3 frecs = inspect.getinnerframes (tb, nlines) trace.write ('Traceback (most recent call last):\n') for frame, fname, lineno, funcname, context, _ in frecs: trace.write (' File "%s", line %d, ' % (fname, lineno)) args, varargs, varkw, lcls = inspect.getargvalues (frame) def readline (lno=[lineno], *args): if args: print(args) try: return linecache.getline (fname, lno[0]) finally: lno[0] += 1 all, prev, name, scope = {}, None, '', None for ttype, tstr, stup, etup, line in tokenize.generate_tokens (readline): if ttype == tokenize.NAME and tstr not in keyword.kwlist: if name: if name[-1] == '.': try: val = getattr (prev, tstr) except AttributeError: # XXX skip the rest of this identifier only break name += tstr else: assert not name and not scope scope, val = self.lookup(tstr, frame, lcls) name = tstr if val is not None: prev = val elif tstr == '.': if prev: name += '.' else: if name: all[name] = (scope, prev) prev, name, scope = None, '', None if ttype == tokenize.NEWLINE: break try: details = inspect.formatargvalues (args, varargs, varkw, lcls, formatvalue=lambda v: '=' + pydoc.text.repr (v)) except: # seen that one on Windows (actual exception was KeyError: self) details = '(no details)' trace.write (funcname + details + '\n') if context is None: context = ['\n'] trace.write (''.join ([' ' + x.replace ('\t', ' ') for x in [a for a in context if a.strip()]])) if len (all): trace.write (' variables: %s\n' % str (all)) trace.write ('%s: %s' % (exctyp.__name__, value)) return trace def __call__ (self, exctyp, value, tb): """ This is called when an exception occurs. """ if exctyp is KeyboardInterrupt: return original_excepthook(exctyp, value, tb) sys.stderr.write(self.analyze_simple (exctyp, value, tb).getvalue()) if self.exception_dialog_active: return Gdk.pointer_ungrab(Gdk.CURRENT_TIME) # @UndefinedVariable Gdk.keyboard_ungrab(Gdk.CURRENT_TIME) # @UndefinedVariable self.exception_dialog_active = True # Create the dialog dialog = Gtk.MessageDialog( parent=self.parent_window, flags=0, type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.NONE ) dialog.set_title (_("Bug Detected")) primary = _("A programming error has been detected.") secondary = _("It probably isn't fatal, but the details should be reported to the developers nonetheless.") try: setsec = dialog.format_secondary_text except AttributeError: raise dialog.vbox.get_children()[0].get_children()[1].set_markup ('%s\n\n%s' % (primary, secondary)) else: del setsec dialog.set_markup (primary) dialog.format_secondary_text (secondary) dialog.add_button (Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE) dialog.add_button (Gtk.STOCK_QUIT, self.RESPONSE_QUIT) # Add an expander with details of the problem to the dialog def expander_cb(expander, *ignore): # Ensures that on deactivating the expander, the dialog is resized down if expander.get_expanded(): dialog.set_resizable(True) else: dialog.set_resizable(False) details_expander = Gtk.Expander() details_expander.set_label(_("Details...")) details_expander.connect("notify::expanded", expander_cb) textview = Gtk.TextView(); textview.show() textview.set_editable (False) textview.modify_font (Pango.FontDescription ("Monospace")) sw = Gtk.ScrolledWindow(); sw.show() sw.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.set_size_request(800, 400) sw.add (textview) details_expander.add (sw) details_expander.show_all() dialog.get_content_area().pack_start(details_expander, True, True, 0) # Get the traceback and set contents of the details try: trace = self.analyze(exctyp, value, tb).getvalue() except: trace = _("Exception while analyzing the exception.") buf = textview.get_buffer() buf.set_text (trace) # Connect callback and present the dialog dialog.connect('response', self._dialog_response_cb, trace) dialog.set_modal(True) dialog.show() dialog.present() def _dialog_response_cb(self, dialog, resp, trace): if resp == self.RESPONSE_QUIT and Gtk.main_level() > 0: if not callable(self.quit_confirmation_func): sys.exit(1) # Exit code is important for IDEs else: if self.quit_confirmation_func(): sys.exit(1) # Exit code is important for IDEs else: dialog.destroy() self.exception_dialog_active = False else: dialog.destroy() self.exception_dialog_active = False def plugin_gtk_exception_hook(): hook = GtkExceptionHook() sys.excepthook = hook return hook PyXRD-0.8.4/pyxrd/generic/gtk_tools/utils.py000066400000000000000000000015271363064711000210000ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import gi gi.require_version('Gtk', '3.0') from gi.repository import Gdk def convert_string_to_gdk_color_int(color): """ Converts a string hexadecimal color description to an integer that can be used by gdk functions. """ color = Gdk.color_parse(color) # @UndefinedVariable return (int(color.red_float * 255) << 24) + (int(color.green_float * 255) << 16) + (int(color.blue_float * 255) << 8) + 255 def get_color_pb(color, width, height): """ Gets a Gdk.Pixbuf filled with color (str) """ pb = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, width, height) # @UndefinedVariable pb.fill(convert_string_to_gdk_color_int(color)) return pbPyXRD-0.8.4/pyxrd/generic/io/000077500000000000000000000000001363064711000156635ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/io/__init__.py000066400000000000000000000004131363064711000177720ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from io import * from .custom_io import * from .utils import get_case_insensitive_glob, retrieve_lowercase_extension PyXRD-0.8.4/pyxrd/generic/io/custom_io.py000066400000000000000000000240741363064711000202450ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from collections import OrderedDict import logging logger = logging.getLogger(__name__) from io import StringIO from zipfile import ZipFile try: # Check if zlib is available, if so, we can use compression when saving import zlib #@UnusedImport from zipfile import ZIP_DEFLATED as COMPRESSION except ImportError: from zipfile import ZIP_STORED as COMPRESSION from ..utils import not_none from .json_codec import PyXRDDecoder, PyXRDEncoder class StorableRegistry(dict): """ Basically a dict which maps class names to the actual class types. This relies on the classes being registered using the 'register' decorator provided in this class type. It also has a number of aliases, for backwards-compatibility (e.g. when class names change or from the era before using this method when we stored the entire class path, we might remove these at some point) """ # For backwards compatibility: aliases = { 'generic.treemodels/XYListStore': 'XYListStore', 'generic.treemodels/ObjectListStore': 'ObjectListStore', 'generic.treemodels/ObjectTreeStore': 'ObjectTreeStore', 'generic.treemodels/IndexListStore': 'IndexListStore', 'generic.models.treemodels/ObjectListStore': 'ObjectListStore', 'generic.models.treemodels/ObjectTreeStore': 'ObjectTreeStore', 'generic.models.treemodels/IndexListStore': 'IndexListStore', 'generic.models.treemodels/XYListStore': 'XYListStore', 'generic.models/PyXRDLine': 'PyXRDLine', 'generic.models/CalculatedLine': 'CalculatedLine', 'generic.models/ExperimentalLine': 'ExperimentalLine', 'goniometer.models/Goniometer': 'Goniometer', 'specimen.models/Specimen': 'Specimen', 'specimen.models/Marker': 'Marker', 'mixture.models/Mixture': 'Mixture', 'atoms.models/AtomType': 'AtomType', 'atoms.models/Atom': 'Atom', 'probabilities.R0models/R0G1Model': 'R0G1Model', 'probabilities.R0models/R0G2Model': 'R0G2Model', 'probabilities.R0models/R0G3Model': 'R0G3Model', 'probabilities.R0models/R0G4Model': 'R0G4Model', 'probabilities.R0models/R0G5Model': 'R0G5Model', 'probabilities.R0models/R0G6Model': 'R0G6Model', 'probabilities.R1models/R1G2Model': 'R1G2Model', 'probabilities.R1models/R1G3Model': 'R1G3Model', 'probabilities.R1models/R1G4Model': 'R1G4Model', 'probabilities.R2models/R2G2Model': 'R2G2Model', 'probabilities.R2models/R2G3Model': 'R2G3Model', 'probabilities.R3models/R3G2Model': 'R3G2Model', 'phases.CSDS_models/LogNormalCSDSDistribution': 'LogNormalCSDSDistribution', 'phases.CSDS_models/DritsCSDSDistribution': 'DritsCSDSDistribution', 'phases.atom_relations/AtomRelation': 'AtomRelation', 'phases.atom_relations/AtomRatio': 'AtomRatio', 'phases.atom_relations/AtomContents': 'AtomContents', 'phases.models/UnitCellProperty': 'UnitCellProperty', 'phases.models/Component': 'Component', 'phases.models/Phase': 'Phase', 'project.models/Project': 'Project', 'InSituMixture': None } def __getitem__(self, key): key = self.aliases.get(key, key) return super(StorableRegistry, self).__getitem__(key) if key is not None else None def register(self): """ Returns a decorator that will register Storable sub-classes. """ return self.register_decorator def register_decorator(self, cls): if hasattr(cls, 'Meta') and hasattr(cls.Meta, 'store_id'): logger.debug("Registering %s as storage type with id '%s'" % (cls, cls.Meta.store_id)) self[cls.Meta.store_id] = cls else: raise TypeError("Cannot register type '%s' without a Meta.store_id!" % cls) return cls pass # end of class # This is filled using register decorator storables = StorableRegistry() # Needs to be importable, could be used for more compact Python pickling: def __map_reduce__(json_obj): decoder = PyXRDDecoder(mapper=storables) return decoder.decode(json_obj) class Storable(object): """ A class with a number of default implementations to serialize objects to JSON strings. It used the PyXRDDecoder en PyXRDEncoder. Subclasses should override their 'Meta.store_id' property and register themselves by calling the storables.register method and applying it as decorator to the subclass: @storables.register() class StorableSubclass(Storable, ...): ... Sub-classes can optionally implement the following methods: - 'json_properties' or for more fine-grained control 'to_json' - 'from_json' """ __storables__ = [] class Meta(): # Sub classes need to override this and set store_id!! store_id = None ########################################################################### # High-level JSON (de)serialisiation related methods & functions: ########################################################################### def dump_object(self, zipped=False): """ Returns this object serialized as a JSON string. If `zipped` is true it returns an in-memory ZipFile. """ content = PyXRDEncoder.dump_object(self) if zipped: f = StringIO() with ZipFile(f, mode="w", compression=COMPRESSION) as z: z.writestr('content', content) return f else: return content def print_object(self): """ Prints the output from dump_object(). """ print(self.dump_object()) ########################################################################### # Low-level JSON (de)serialisiation related methods & functions: ########################################################################### def to_json(self): """ Method that should return a dict containing two keys: - 'type' -> registered class Meta.store_id - 'properties' -> a dict containg all the properties neccesary to re-create the object when serialized as JSON. """ return { "type": self.Meta.store_id, "properties": self.json_properties() } def to_json_multi_part(self): """ This should generate a list of two-tuples: (partname, json_dict), (partname, json_dict), ... These can then be saved as seperate files (e.g. in a ZIP file) """ yield ('content', self.to_json()) def json_properties(self): """ Method that should return a dict containing all the properties necessary to re-create the object when serialized as JSON. """ retval = OrderedDict() def add_prop(label, store_private): if not store_private: retval[label] = getattr(self, label) else: try: retval[label] = getattr(self, store_private) except (TypeError, AttributeError): retval[label] = getattr(type(self), label)._get(self) from mvc.models import Model if isinstance(self, Model): for prop in self.Meta.all_properties: if prop.persistent: add_prop(prop.label, not_none(prop.store_private, False)) if getattr(prop, 'refinable', False): add_prop(prop.get_refinement_info_name(), False) elif hasattr(self, "__storables__"): # Fallback: for val in self.__storables__: add_prop(val, False) else: raise RuntimeError("Cannot find either a '__storables__' or Meta class attribute on Storable '%s' instance!" % type(self)) return retval def parse_init_arg(self, arg, default, child=False, default_is_class=False, **kwargs): """ Can be used to transform an argument passed to a __init__ method of a Storable (sub-)class containing a JSON dict into the actual object it is representing. *arg* the passed argument *default* the default value if argument is None **child* boolean flag indicating wether or not the object is a child, if true, self is passed as the parent keyword to the JSON decoder if the passed argument is a JSON dict **default_is_class** boolean flag indicating whether or not the passed default value is an unitialized type. If True, the type will be initialized using the kwargs passed to this function. **kwargs* any other kwargs are passed to the JSON decoder if the passed argument is a JSON dict :rtype: the argument (not a JSON dict), the actual object (argument was a JSON dict) or the default value (argument was None) """ if arg == None: if not default_is_class: return default else: if child: kwargs["parent"] = self return default(**kwargs) elif isinstance(arg, dict) and "type" in arg and "properties" in arg: arg = PyXRDDecoder(mapper=storables, parent=self if child else None).__pyxrd_decode__(arg, **kwargs) return arg else: return arg @classmethod def from_json(cls, *args, **kwargs): """ Class method transforming JSON kwargs into an instance of this class. By default this assumes a 1-on-1 mapping to the __init__ method. """ return cls(*args, **kwargs) ########################################################################### # Others: ########################################################################### def __reduce__(self): props = self.dump_object() return __map_reduce__, (props,), None pass # end of class PyXRD-0.8.4/pyxrd/generic/io/data_registry.py000066400000000000000000000116451363064711000211050ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os from pkg_resources import resource_filename # @UnresolvedImport from pyxrd.generic.exceptions import AlreadyRegistered, NotRegistered class DataRegistry(object): """ Class for registering data directories and files so they're not hard-coded everywhere. """ __data_directories = None __data_files = None _base_dir = None def __init__(self, dirs=[], files=[], *args, **kwargs): super(DataRegistry, self).__init__(*args, **kwargs) self.__data_directories = {} self.__data_files = {} for name, path, parent in dirs: self.register_data_directory(name, path, parent=parent) for name, path, parent in files: self.register_data_file(name, path, parent=parent) def __parse_parent(self, path, parent=None): # Adds parent path: if parent is not None: if parent is not None: try: path = os.path.join(self.__data_directories[parent], path) except KeyError: raise NotRegistered("The data directory named '%s' was not found in the registry" % parent) elif path.startswith("./"): path = resource_filename("pyxrd.data", path) return path def register_data_file(self, name, path, parent=None): """ Registers a data file at 'path' called 'name'. If this file is inside a registered data directory, you can use a relative path by setting the 'parent' keyword argument to the name of the parent data-directory. The parent path will then be appended to the file's path. Paths are considered to be relative to data package. Note: names are always made full-caps! If you try to re-register an existing data file, an AlreadyRegistered exception will be raised. Similarly if you pass in an unregistered parent directory name, NotRegistered will be raised. """ name = name.upper() if not name in self.__data_files: path = self.__parse_parent(path, parent=parent) self.__data_files[name] = path else: raise AlreadyRegistered("the data file named '%s' was already registered" % name) def register_data_directory(self, name, path, parent=None): """ Registers a data directory at 'path' called 'name'. If this is a sub-directory of another registered data directory, you can use a relative path by setting the 'parent' keyword argument to the name of the parent data-directory. The parent path will then be appended to the child's path. Paths are considered to be relative to data package. Note: names are always made full-caps! If you try to re-register an existing data directory, an AlreadyRegistered exception will be raised. Similarly if you pass in an unregistered parent directory name, NotRegistered will be raised. """ name = name.upper() if not name in self.__data_directories: path = self.__parse_parent(path, parent=parent) self.__data_directories[name] = path try: # Try to create this path: os.makedirs(path) except OSError: pass else: raise AlreadyRegistered("The data directory named '%s' was already registered" % name) def get_directory_path(self, name): """ Gets the absolute path to a data directory named 'name' """ try: path = self.__data_directories[name] if not os.path.isdir(path): return path else: return path except KeyError: raise NotRegistered("The data directory named '%s' was not found in the registry" % name) def get_all_directories(self): """ Returns a generator looping over all directories in the registry, excluding the project path. """ for path in list(self.__data_directories.values()): yield path def get_file_path(self, name): """ Gets the absolute path to a data file named 'name' """ try: return self.__data_files[name] except KeyError: raise NotRegistered("The data file named '%s' was not found in the registry" % name) def get_all_files(self): """ Returns a generator looping over all directories in the registry, excluding the project path. """ for path in list(self.__data_directories.values()): yield resource_filename("pyxrd.data", path) pass # end of class PyXRD-0.8.4/pyxrd/generic/io/json_codec.py000066400000000000000000000125561363064711000203540ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import json import numpy as np class PyXRDEncoder(json.JSONEncoder): """ A custom JSON encoder that checks if: - the object has a to_json callable method, if so it is called to convert the object to a JSON encodable object. E.g. the default implementation from the Storable class is a dict object containing: - a 'type' key mapped to the storage type id of the storable class - a 'properties' key mapped to a dict of name-values for each property that needs to be stored in order to be able to recreate the object. If the user registered this class as a storable (see the 'registes_storable' method or the Storable class) then the JSON object is transformed back into the actual Python object using its from_json(...) method. Default implementation finds the registered class using the 'type' value and passes the 'properties' value to its constructor as keyword arguments. - if the object is a numpy array, it is converted to a list - if the object is a wrapped list, dictionary, ... (ObsWrapper subclass) then the wrapped object is returned, as these are directly JSON encodable. - fall back to the default JSONEncoder methods """ ########################################################################### # Convenience functions: use these! ########################################################################### @classmethod def dump_object(cls, obj): """ Serialize an object using this encoder and return it as a string """ return json.dumps(obj, indent=4, cls=cls) @classmethod def dump_object_to_file(cls, obj, f): """ Serialize an object using this encoder and dump it into a file""" return json.dump(obj, f, indent=4, cls=cls) ########################################################################### # Sub class implementation: ########################################################################### def default(self, obj): from mvc.support.observables import ObsWrapper if hasattr(obj, "to_json") and callable(getattr(obj, "to_json")): return obj.to_json() if isinstance(obj, np.ndarray): return obj.tolist() if isinstance(obj, ObsWrapper): return obj._obj # return the wrapped object return json.JSONEncoder(self).default(obj) class PyXRDDecoder(json.JSONDecoder): """ A custom JSON decoder that can decode objects, following these steps: - decode the JSON object at once using the default decoder - the resulting dict is then parsed: - if a valid 'type' and a 'properties' key is given, the object is translated using the mapped class type's 'from_json' method. This mapping is done using a dict mapping the json class name to an actual class type (`mapper` __init__ keyword) - parent keyword arguments are passed on (e.g. a project) to the from_json method as well """ def __init__(self, mapper=None, parent=None, **kwargs): super(PyXRDDecoder, self).__init__(**kwargs) self.mapper = mapper self.parent = parent ########################################################################### # Convenience functions: use these! ########################################################################### @classmethod def decode_file(cls, f, mapper, parent=None): data = f.read() data = data.decode("utf-8") if isinstance(data, bytes) else data return json.loads(data, cls=PyXRDDecoder, mapper=mapper, parent=parent) @classmethod def decode_string(cls, string, mapper, parent=None): return json.loads(string, cls=PyXRDDecoder, mapper=mapper, parent=parent) ########################################################################### # Sub class implementation: ########################################################################### def decode(self, string): """ Decodes a json string into an object """ # First use a regular decode: obj = super(PyXRDDecoder, self).decode(string) # Then parse this dict into an actual python object: return self.__pyxrd_decode__(obj) or obj def __pyxrd_decode__(self, obj, **kwargs): """ Decodes the PyXRD JSON object serialization """ if isinstance(obj, list): for index, subobj in enumerate(obj): obj[index] = self.__pyxrd_decode__(subobj) or subobj return obj if "type" in obj: objtype = self.mapper[obj["type"]] if "properties" in obj and hasattr(objtype, "from_json"): if self.parent is not None and not "parent" in kwargs: kwargs["parent"] = self.parent return objtype.from_json(**dict(obj["properties"], **kwargs)) logger.warn("__pyxrd_decode__ will return None for %s!" % str(obj)[:30] + "..." + str(obj)[:-30]) return None PyXRD-0.8.4/pyxrd/generic/io/utils.py000066400000000000000000000030241363064711000173740ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. """ Input/Output related utility functions """ import os, sys from mvc.support.file_utils import get_case_insensitive_glob, retrieve_lowercase_extension, relpath # Small workaround to provide a unicode-aware open method: if sys.version_info[0] < 3: # Pre Python 3.0 import codecs _open_func_bak = open # Make a back up, just in case open = codecs.open #@ReservedAssignment def sizeof_fmt(num): ''' Returns a human-friendly string when given a size in bytes ''' for x in ['bytes', 'kB', 'MB', 'GB', 'TB']: if num < 1024.0: return "%3.1f %s" % (num, x) num /= 1024.0 def get_size(path='.', maxsize=None): ''' Gets the recursive size of a path, can be limited to a maxsize ''' total_size = 0 for dirpath, dirnames, filenames in os.walk(path): #@UnusedVariable for f in filenames: fp = os.path.join(dirpath, f) total_size += os.path.getsize(fp) if maxsize is not None and total_size > maxsize: break if maxsize is not None and total_size > maxsize: break return total_size def unicode_open(*args, **kwargs): """ Opens files in UTF-8 encoding by default, unless an 'encoding' keyword argument is passed. Returns a file object. """ if not "encoding" in kwargs: kwargs["encoding"] = "utf-8" return open(*args, **kwargs)PyXRD-0.8.4/pyxrd/generic/mathtext_support.py000066400000000000000000000131571363064711000212670ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import re from fractions import Fraction import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk, GdkPixbuf try: gi.require_foreign("cairo") except ImportError as orig: try: import cairocffi as cairo except ImportError as snd: logger.error("No cairo integration :(") raise snd from orig from matplotlib import rcParams import matplotlib.mathtext as mathtext pbmt_cache = dict() # maybe use a weak ref dict or a slowly GC-ed one? display = Gdk.Display.get_default() # @UndefinedVariable screen = display.get_default_screen() dpi = screen.get_resolution() or 96 def create_pb_from_mathtext(text, align='center', weight='heavy', color='b', style='normal'): """ Create a Gdk.Pixbuf from a mathtext string """ global pbmt_cache global dpi if not text in pbmt_cache: parts, fontsize = _handle_customs(text) pbs = [] width = 0 height = 0 # heights = [] # Temporarily set font properties: old_params = rcParams["font.weight"], rcParams["text.color"], rcParams["font.style"] rcParams["font.weight"] = weight rcParams["text.color"] = color rcParams["font.style"] = style # Create parser and load png fragments parser = mathtext.MathTextParser("Bitmap") for part in parts: png_loader = GdkPixbuf.PixbufLoader.new_with_type('png') # @UndefinedVariable parser.to_png(png_loader, part, dpi=dpi, fontsize=fontsize) png_loader.close() pb = png_loader.get_pixbuf() w, h = pb.get_width(), pb.get_height() width = max(width, w) height += h pbs.append((pb, w, h)) # Restore font properties rcParams["font.weight"], rcParams["text.color"], rcParams["font.style"] = old_params surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) cr = cairo.Context(surface) cr.save() cr.set_operator(cairo.OPERATOR_CLEAR) cr.paint() cr.restore() cr.save() offsetx = 0 offsety = 0 for pb, w, h in pbs: if align == 'center': offsetx = int((width - w) / 2) if align == 'left': offsetx = 0 if align == 'right': offsetx = int(width - w) Gdk.cairo_set_source_pixbuf(cr, pb, offsetx, offsety) cr.rectangle(offsetx, offsety, w, h) cr.paint() offsety += h del pbs cr.restore() pbmt_cache[text] = Gdk.pixbuf_get_from_surface(surface, 0, 0, width, height) return pbmt_cache[text] def create_image_from_mathtext(text, align='center', weight='heavy', color='b', style='normal'): """ Create a Gtk.Image widget from a mathtext string """ image = Gtk.Image() image.set_from_pixbuf(create_pb_from_mathtext(text, align=align)) return image ############################### # Some convenience functions: # ############################### def _handle_customs(text): text = text.decode('utf-8') if r"\larger" in text: fontsize = 20 elif r"\large" in text: fontsize = 15 else: fontsize = 10 replacers = [ (r"²", r"$^{2}$"), (r"³", r"$^{3}$"), (r"α", r"$\alpha$"), (r"β", r"$\beta$"), (r"γ", r"$\gamma$"), (r"δ", r"$\delta$"), (r"γ", r"$\digamma$"), (r"η", r"$\eta$"), (r"ι", r"$\iota$"), (r"κ", r"$\kappa$"), (r"λ", r"$\lambda$"), (r"μ", r"$\mu$"), (r"ω", r"$\omega$"), (r"φ", r"$\phi$"), (r"π", r"$\pi$"), (r"ψ", r"$\psi$"), (r"ρ", r"$\rho$"), (r"σ", r"$\sigma$"), (r"τ", r"$\tau$"), (r"θ", r"$\theta$"), (r"υ", r"$\upsilon$"), (r"ξ", r"$\xi$"), (r"ζ", r"$\zeta$"), (r"\larger", r""), (r"\large", r""), (r"\newline", r"$\newline$"), ] for val, rep in replacers: text = text.replace(val, rep) parts = text.replace("$$", "").split(r"\newline") while "$$" in parts: parts.remove("$$") return parts, fontsize def mt_frac(val): val = Fraction(val).limit_denominator() if val.denominator > 1: return r"\frac{%d}{%d}" % (val.numerator, val.denominator) else: return r"%d" % val.numerator def mt_range(lower, name, upper): return r"\left({ %s \leq %s \leq %s }\right)" % (mt_frac(lower), name, mt_frac(upper)) def get_plot_safe(expression): return r"".join(_handle_customs(expression)[0]) def get_string_safe(expression): replacers = [ (r"$", r""), (r"\larger", r""), (r"\left", r""), (r"\right", r""), (r"\leq", r"≤"), (r"\geq", r"≥"), (r"\large", r""), (r"\newline", "\n"), ] for val, rep in replacers: expression = expression.replace(val, rep) regex_replacers = [ (r"\\sum_\{(\S+)\}\^\{(\S+)\}", r"Σ(\1->\2)"), (r"(\S+)_(?:\{(\S+)\})", r"\1\2"), (r"(\S+)_(\S+)", r"\1\2"), (r"\\frac\{([^}])\}\{([^}])\}", r"\1\\\2"), # single characters (r"\\frac\{(.+)\}\{(.+)\}", r"(\1)\\(\2)"), # multi charachters (r"\(\{([^})]+)\}\)", r"(\1)") ] for regexpr, sub in regex_replacers: pattern = re.compile(regexpr) expression = pattern.sub(sub, expression) return expressionPyXRD-0.8.4/pyxrd/generic/models/000077500000000000000000000000001363064711000165375ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/models/__init__.py000066400000000000000000000005741363064711000206560ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .base import PyXRDModel, ChildModel, DataModel from .lines import PyXRDLine, CalculatedLine, ExperimentalLine __all__ = [ "PyXRDModel", "ChildModel", "DataModel", "PyXRDLine", "CalculatedLine", "ExperimentalLine", ] PyXRD-0.8.4/pyxrd/generic/models/base.py000066400000000000000000000120221363064711000200200ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import weakref from warnings import warn from mvc import Model from mvc.models.properties import LabeledProperty, SignalProperty from mvc.support.utils import pop_kwargs, not_none class PyXRDModel(Model): """ A UUIDModel with some common PyXRD functionality """ class Meta(Model.Meta): @classmethod def get_refinable_properties(cls): if not hasattr(cls, "all_properties"): raise RuntimeError("Meta class '%s' has not been initialized" \ " properly: 'all_properties' is not set!" % type(self)) else: return [attr for attr in cls.all_properties if getattr(attr, "refinable", False)] pass # end of class # ------------------------------------------------------------ # Methods & functions # ------------------------------------------------------------ def pop_kwargs(self, kwargs, *keys): return pop_kwargs(kwargs, *keys) def get_kwarg(self, fun_kwargs, default, *keywords): """ Convenience function to get a certain keyword 'kw' value from the passed keyword arguments 'fun_kwargs'. If the key 'kw' is not in 'fun_kwargs' a list of deprecated keywords to be searched for can be passed as an optional argument list 'depr_kws'. If one of these is found, its value is returned and a deprecation warning is emitted. If neither the 'kw' nor any of the 'depr_kws' are found the 'default' value is returned. """ if len(keywords) < 1: raise AttributeError("get_kwarg() requires at least one keyword (%d given)" % (len(keywords))) value = default for i, key in enumerate(keywords[::-1]): if key in fun_kwargs: value = not_none(fun_kwargs[key], default) if i != 0: warn("The use of the keyword '%s' is deprecated for %s!" % (key, type(self)), DeprecationWarning) return value def get_list(self, fun_kwargs, default, *keywords, **kwargs): """ Convenience function to get a 'list' type keyword. Supports deprecated serialized ObjectListStores (replaced by regular lists). """ return self.parse_list(self.get_kwarg(fun_kwargs, default, *keywords), **kwargs) def parse_list(self, list_arg, **kwargs): """ Parses a list keyword argument (be it an actual list, or a former JSON-serialized ObjectListStore object). """ if isinstance(list_arg, dict) and "type" in list_arg: list_arg = list_arg["properties"]["model_data"] if list_arg is not None: return [ self.parse_init_arg(json_obj, None, child=True, **kwargs) for json_obj in list_arg ] else: return list() pass # end of class class ChildModel(PyXRDModel): """ A PyXRDModel with child-parent relation support. """ # MODEL INTEL: class Meta(PyXRDModel.Meta): @classmethod def get_inheritable_properties(cls): # TODO MOVE THIS TO THE CHILD MODEL!! if not hasattr(cls, "all_properties"): raise RuntimeError("Meta class '%s' has not been initialized" \ " properly: 'all_properties' is not set!" % type(cls)) else: return [attr for attr in cls.all_properties if getattr(attr, "inheritable", False)] # SIGNALS: removed = SignalProperty() added = SignalProperty() # PROPERTIES: __parent = None def __get_parent(self): if callable(self.__parent): return self.__parent() else: return self.__parent def __set_parent(self, value): if not self.parent == value: if self.parent is not None: self.removed.emit() try: self.__parent = weakref.ref(value, self.__on_parent_finalize) except TypeError: self.__parent = value if self.parent is not None: self.added.emit() def __on_parent_finalize(self, ref): self.removed.emit() self.__parent = None parent = LabeledProperty(fget=__get_parent, fset=__set_parent) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, parent=None, *args, **kwargs): super(ChildModel, self).__init__(*args, **kwargs) self.parent = parent pass # end of class class DataModel(ChildModel): """ A ChildModel with support for having 'calculation data' and 'visual data' """ # SIGNALS: data_changed = SignalProperty() visuals_changed = SignalProperty() pass # end of class PyXRD-0.8.4/pyxrd/generic/models/event_context_manager.py000066400000000000000000000023541363064711000234740ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from contextlib import contextmanager, ExitStack class EventContextManager(object): """ Event context manager class, to be used as follows: ecm = EventContextManager(model.event1, model.event2, ...) with ecm.ignore(): pass #do something here that will cause events to be ignored with ecm.hold(): pass #do something here that will cause events to be held back """ events = [] def __init__(self, *events): self.events = events @contextmanager def ignore(self): if len(self.events): with ExitStack() as stack: for event in self.events: stack.enter_context(event.ignore()) yield else: yield @contextmanager def hold(self): if len(self.events): with ExitStack() as stack: for event in self.events: stack.enter_context(event.hold()) yield else: yield pass #end of classPyXRD-0.8.4/pyxrd/generic/models/lines/000077500000000000000000000000001363064711000176515ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/models/lines/__init__.py000066400000000000000000000004151363064711000217620ustar00rootroot00000000000000from .storable_xy_data import StorableXYData from .pyxrd_line import PyXRDLine from .calculated_line import CalculatedLine from .experimental_line import ExperimentalLine __all__ = [ "StorableXYData", "PyXRDLine", "CalculatedLine", "ExperimentalLine" ]PyXRD-0.8.4/pyxrd/generic/models/lines/calculated_line.py000066400000000000000000000031741363064711000233400ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging from mvc.models.properties.tools import modify logger = logging.getLogger(__name__) from mvc.models.properties import ( ListProperty, SignalMixin ) from pyxrd.data import settings from pyxrd.generic.io import storables from pyxrd.generic.models.base import DataModel from .pyxrd_line import PyXRDLine @storables.register() class CalculatedLine(PyXRDLine): # MODEL INTEL: class Meta(PyXRDLine.Meta): store_id = "CalculatedLine" inherit_format = "display_calc_%s" specimen = property(DataModel.parent.fget, DataModel.parent.fset) # PROPERTIES: phase_colors = ListProperty( default=[], test="Phase colors", mix_with=(SignalMixin,), signal_name="visuals_changed", ) #: The line color color = modify(PyXRDLine.color, default=settings.CALCULATED_COLOR, inherit_from="parent.parent.display_calc_color" ) #: The linewidth in points lw = modify(PyXRDLine.lw, default=settings.CALCULATED_LINEWIDTH, inherit_from="parent.parent.display_calc_lw" ) #: A short string describing the (matplotlib) linestyle ls = modify(PyXRDLine.ls, default=settings.CALCULATED_LINESTYLE, inherit_from="parent.parent.display_calc_ls" ) #: A short string describing the (matplotlib) marker marker = modify(PyXRDLine.marker, default=settings.CALCULATED_MARKER, inherit_from="parent.parent.display_calc_marker" ) pass # end of classPyXRD-0.8.4/pyxrd/generic/models/lines/experimental_line.py000066400000000000000000000435031363064711000237340ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import numpy as np from scipy.integrate import trapz from scipy.interpolate import UnivariateSpline from mvc.models.properties.tools import modify from mvc.models.properties import ( FloatProperty, LabeledProperty, IntegerProperty, FloatChoiceProperty, IntegerChoiceProperty, SetActionMixin, SignalMixin ) from pyxrd.data import settings from pyxrd.generic.io import storables from pyxrd.calculations.math_tools import smooth, add_noise from pyxrd.generic.models.base import DataModel from .pyxrd_line import PyXRDLine @storables.register() class ExperimentalLine(PyXRDLine): # MODEL INTEL: class Meta(PyXRDLine.Meta): store_id = "ExperimentalLine" specimen = property(DataModel.parent.fget, DataModel.parent.fset) # PROPERTIES: #: The line color color = modify(PyXRDLine.color, default=settings.EXPERIMENTAL_COLOR, inherit_from="parent.parent.display_exp_color" ) #: The linewidth in points lw = modify(PyXRDLine.lw, default=settings.EXPERIMENTAL_LINEWIDTH, inherit_from="parent.parent.display_exp_lw" ) #: A short string describing the (matplotlib) linestyle ls = modify(PyXRDLine.ls, default=settings.EXPERIMENTAL_LINESTYLE, inherit_from="parent.parent.display_exp_ls" ) #: A short string describing the (matplotlib) marker marker = modify(PyXRDLine.marker, default=settings.EXPERIMENTAL_MARKER, inherit_from="parent.parent.display_exp_marker" ) #: The value to cap the pattern at (in raw values) cap_value = FloatProperty( default=0.0, text="Cap value", persistent=True, visible=True, widget_type="float_entry", signal_name="visuals_changed", mix_with=(SignalMixin,) ) @property def max_display_y(self): max_value = super(ExperimentalLine, self).max_display_y # Only cap single and multi-line patterns, not 2D images: if self.cap_value > 0 and not (self.num_columns > 2 and len(self.z_data)): max_value = min(max_value, self.cap_value) return max_value ########################################################################### #: The background offset value bg_position = FloatProperty( default=0.0, text="Background offset", persistent=False, visible=True, widget_type="float_entry", signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: The background scale bg_scale = FloatProperty( default=1.0, text="Background scale", persistent=False, visible=True, widget_type="float_entry", signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: The background pattern or None for linear patterns bg_pattern = LabeledProperty( default=None, text="Background pattern", persistent=False, visible=False, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: The background type: pattern or linear bg_type = IntegerChoiceProperty( default=0, text="Background type", choices=settings.PATTERN_BG_TYPES, persistent=False, visible=True, signal_name="visuals_changed", set_action_name="find_bg_position", mix_with=(SignalMixin, SetActionMixin,) ) def get_bg_type_lbl(self): return settings.PATTERN_BG_TYPES[self.bg_type] ########################################################################### #: Pattern smoothing type smooth_type = IntegerChoiceProperty( default=0, text="Smooth type", choices=settings.PATTERN_SMOOTH_TYPES, persistent=False, visible=True, signal_name="visuals_changed", set_action_name="setup_smooth_variables", mix_with=(SignalMixin, SetActionMixin,) ) smooth_pattern = None #: The smooth degree smooth_degree = IntegerProperty( default=0, text="Smooth degree", persistent=False, visible=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) ########################################################################### #: The noise fraction to add noise_fraction = FloatProperty( default=0.0, text="Noise fraction", persistent=False, visible=True, widget_type="spin", signal_name="visuals_changed", mix_with=(SignalMixin,) ) ########################################################################### #: The pattern shift correction value shift_value = FloatProperty( default=0.0, text="Shift value", persistent=False, visible=True, widget_type="float_entry", signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: Shift reference position shift_position = FloatChoiceProperty( default=0.42574, text="Shift position", choices=settings.PATTERN_SHIFT_POSITIONS, persistent=False, visible=True, signal_name="visuals_changed", set_action_name="setup_shift_variables", mix_with=(SignalMixin, SetActionMixin,) ) ########################################################################### #: The peak properties calculation start position peak_startx = FloatProperty( default=0.0, text="Peak properties start position", persistent=False, visible=True, widget_type="float_entry", set_action_name="update_peak_properties", mix_with=(SetActionMixin,) ) #: The peak properties calculation end position peak_endx = FloatProperty( default=0.0, text="Peak properties end position", persistent=False, visible=True, widget_type="float_entry", set_action_name="update_peak_properties", mix_with=(SetActionMixin,) ) #: The peak fwhm value peak_fwhm_result = FloatProperty( default=0.0, text="Peak FWHM value", persistent=False, visible=True, widget_type="label", ) #: The peak area value peak_area_result = FloatProperty( default=0.0, text="Peak area value", persistent=False, visible=True, widget_type="label", ) #: The patterns peak properties are calculated from peak_properties_pattern = LabeledProperty( default=None, text="Peak properties pattern", persistent=False, visible=False, signal_name="visuals_changed", mix_with=(SignalMixin,) ) ########################################################################### #: The strip peak start position strip_startx = FloatProperty( default=0.0, text="Strip peak start position", persistent=False, visible=True, widget_type="float_entry", set_action_name="update_strip_pattern", mix_with=(SetActionMixin,) ) #: The strip peak end position strip_endx = FloatProperty( default=0.0, text="Strip peak end position", persistent=False, visible=True, widget_type="float_entry", set_action_name="update_strip_pattern", mix_with=(SetActionMixin,) ) #: The stripped peak pattern stripped_pattern = LabeledProperty( default=None, text="Strip peak pattern", persistent=False, visible=False, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: The stripped peak pattern noise noise_level = FloatProperty( default=0.0, text="Strip peak noise level", persistent=False, visible=True, widget_type="float_entry", set_action_name="update_strip_pattern_noise", mix_with=(SetActionMixin,) ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, cap_value=0.0, *args, **kwargs): """ Valid keyword arguments for a ExperimentalLine are: cap_value: the value (in raw counts) at which to cap the experimental pattern """ super(ExperimentalLine, self).__init__(*args, **kwargs) self.cap_value = cap_value # ------------------------------------------------------------ # Background Removal # ------------------------------------------------------------ def remove_background(self): with self.data_changed.hold_and_emit(): bg = None if self.bg_type == 0: bg = self.bg_position elif self.bg_type == 1 and self.bg_pattern is not None and not (self.bg_position == 0 and self.bg_scale == 0): bg = self.bg_pattern * self.bg_scale + self.bg_position if bg is not None and self.data_y.size > 0: self.data_y[:, 0] -= bg self.clear_bg_variables() def find_bg_position(self): try: self.bg_position = np.min(self.data_y) except ValueError: return 0.0 def clear_bg_variables(self): with self.visuals_changed.hold_and_emit(): self.bg_pattern = None self.bg_scale = 0.0 self.bg_position = 0.0 # ------------------------------------------------------------ # Data Smoothing # ------------------------------------------------------------ def smooth_data(self): with self.data_changed.hold_and_emit(): if self.smooth_degree > 0: degree = int(self.smooth_degree) self.data_y[:, 0] = smooth(self.data_y[:, 0], degree) self.smooth_degree = 0.0 def setup_smooth_variables(self): with self.visuals_changed.hold_and_emit(): self.smooth_degree = 5.0 def clear_smooth_variables(self): with self.visuals_changed.hold_and_emit(): self.smooth_degree = 0.0 # ------------------------------------------------------------ # Noise adding # ------------------------------------------------------------ def add_noise(self): with self.data_changed.hold_and_emit(): if self.noise_fraction > 0: noisified = add_noise(self.data_y[:, 0], self.noise_fraction) self.set_data(self.data_x, noisified) self.noise_fraction = 0.0 def clear_noise_variables(self): with self.visuals_changed.hold_and_emit(): self.noise_fraction = 0.0 # ------------------------------------------------------------ # Data Shifting # ------------------------------------------------------------ def shift_data(self): with self.data_changed.hold_and_emit(): if self.shift_value != 0.0: if settings.PATTERN_SHIFT_TYPE == "Linear": self.data_x = self.data_x - self.shift_value if self.specimen is not None: with self.specimen.visuals_changed.hold(): for marker in self.specimen.markers: marker.position = marker.position - self.shift_value elif settings.PATTERN_SHIFT_TYPE == "Displacement": position = self.specimen.goniometer.get_t_from_nm(self.shift_position) displacement = 0.5 * self.specimen.goniometer.radius * self.shift_value / np.cos(position / 180 * np.pi) correction = 2 * displacement * np.cos(self.data_x / 2 / 180 * np.pi) / self.specimen.goniometer.radius self.data_x = self.data_x - correction self.shift_value = 0.0 def setup_shift_variables(self): with self.visuals_changed.hold_and_emit(): position = self.specimen.goniometer.get_2t_from_nm(self.shift_position) if position > 0.1: max_x = position + 0.5 min_x = position - 0.5 condition = (self.data_x >= min_x) & (self.data_x <= max_x) section_x, section_y = np.extract(condition, self.data_x), np.extract(condition, self.data_y[:, 0]) try: #TODO to exclude noise it'd be better to first interpolate # or smooth the data and then find the max. actual_position = section_x[np.argmax(section_y)] except ValueError: actual_position = position self.shift_value = actual_position - position def clear_shift_variables(self): with self.visuals_changed.hold_and_emit(): self.shift_value = 0 # ------------------------------------------------------------ # Peak area calculation # ------------------------------------------------------------ peak_bg_slope = 0.0 avg_starty = 0.0 avg_endy = 0.0 def update_peak_properties(self): with self.visuals_changed.hold_and_emit(): if self.peak_endx < self.peak_startx: self.peak_endx = self.peak_startx + 1.0 return # previous line will have re-invoked this method # calculate average starting point y value condition = (self.data_x >= self.peak_startx - 0.1) & (self.data_x <= self.peak_startx + 0.1) section = np.extract(condition, self.data_y[:, 0]) self.avg_starty = np.min(section) # calculate average ending point y value condition = (self.data_x >= self.peak_endx - 0.1) & (self.data_x <= self.peak_endx + 0.1) section = np.extract(condition, self.data_y[:, 0]) self.avg_endy = np.min(section) # Calculate new bg slope self.peak_bg_slope = (self.avg_starty - self.avg_endy) / (self.peak_startx - self.peak_endx) # Get the x-values in between start and end point: condition = (self.data_x >= self.peak_startx) & (self.data_x <= self.peak_endx) section_x = np.extract(condition, self.data_x) section_y = np.extract(condition, self.data_y) bg_curve = (self.peak_bg_slope * (section_x - self.peak_startx) + self.avg_starty) #Calculate the peak area: self.peak_area_result = abs(trapz(section_y, x=section_x) - trapz(bg_curve, x=section_x)) # create a spline of of the peak (shifted down by half of its maximum) fwhm_curve = section_y - bg_curve peak_half_max = np.max(fwhm_curve)*0.5 spline = UnivariateSpline(section_x, fwhm_curve-peak_half_max, s=0) roots = spline.roots() # find the roots = where the splin = 0 self.peak_fwhm_result = np.abs(roots[0] - roots[-1]) if (len(roots) >= 2) else 0 # Calculate the new y-values: x values, bg_curve y values, original pattern y values, x values for the FWHM, y values for the FWHM self.peak_properties_pattern = (section_x, bg_curve, section_y, roots, spline(roots)+peak_half_max) def clear_peak_properties_variables(self): with self.visuals_changed.hold_and_emit(): self._peak_startx = 0.0 self._peak_properties_pattern = None self._peak_endx = 0.0 self.peak_properties = 0.0 # ------------------------------------------------------------ # Peak stripping # ------------------------------------------------------------ def strip_peak(self): with self.data_changed.hold_and_emit(): if self.stripped_pattern is not None: stripx, stripy = self.stripped_pattern indeces = ((self.data_x >= self.strip_startx) & (self.data_x <= self.strip_endx)).nonzero()[0] np.put(self.data_y[:, 0], indeces, stripy) self._strip_startx = 0.0 self._stripped_pattern = None self.strip_endx = 0.0 strip_slope = 0.0 avg_starty = 0.0 avg_endy = 0.0 block_strip = False def update_strip_pattern_noise(self): with self.visuals_changed.hold_and_emit(): # Get the x-values in between start and end point: condition = (self.data_x >= self.strip_startx) & (self.data_x <= self.strip_endx) section_x = np.extract(condition, self.data_x) # Calculate the new y-values, add noise according to noise_level noise = self.avg_endy * 2 * (np.random.rand(*section_x.shape) - 0.5) * self.noise_level section_y = (self.strip_slope * (section_x - self.strip_startx) + self.avg_starty) + noise self.stripped_pattern = (section_x, section_y) def update_strip_pattern(self): with self.visuals_changed.hold_and_emit(): if self.strip_endx < self.strip_startx: self.strip_endx = self.strip_startx + 1.0 return # previous line will have re-invoked this method if not self.block_strip: self.block_strip = True # calculate average starting point y value condition = (self.data_x >= self.strip_startx - 0.1) & (self.data_x <= self.strip_startx + 0.1) section = np.extract(condition, self.data_y[:, 0]) self.avg_starty = np.average(section) noise_starty = 2 * np.std(section) / self.avg_starty # calculate average ending point y value condition = (self.data_x >= self.strip_endx - 0.1) & (self.data_x <= self.strip_endx + 0.1) section = np.extract(condition, self.data_y[:, 0]) self.avg_endy = np.average(section) noise_endy = 2 * np.std(section) / self.avg_starty # Calculate new slope and noise level self.strip_slope = (self.avg_starty - self.avg_endy) / (self.strip_startx - self.strip_endx) self.noise_level = (noise_starty + noise_endy) * 0.5 self.update_strip_pattern_noise() def clear_strip_variables(self): with self.visuals_changed.hold_and_emit(): self._strip_startx = 0.0 self._strip_pattern = None self.strip_start_x = 0.0 pass # end of class PyXRD-0.8.4/pyxrd/generic/models/lines/pyxrd_line.py000066400000000000000000000313331363064711000224030ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import numpy as np from scipy import stats from scipy.interpolate import interp1d from mvc.models.properties import ( StringProperty, BoolProperty, FloatProperty, StringChoiceProperty, SignalMixin, ListProperty ) from pyxrd.data import settings from pyxrd.generic.utils import not_none from pyxrd.generic.io.custom_io import storables from pyxrd.generic.models.properties import InheritableMixin from pyxrd.calculations.peak_detection import multi_peakdetect from .storable_xy_data import StorableXYData #from pyxrd.file_parsers.ascii_parser import ASCIIParser @storables.register() class PyXRDLine(StorableXYData): """ A PyXRDLine is an abstract attribute holder for a real 'Line' object, whatever the plotting library used may be. Attributes are line width and color. """ # MODEL INTEL: class Meta(StorableXYData.Meta): store_id = "PyXRDLine" # OBSERVABLE PROPERTIES: #: The line label label = StringProperty( default="", text="Label", persistent=True ) #: The line color color = StringProperty( default="#000000", text="Label", visible=True, persistent=True, widget_type="color", inherit_flag="inherit_color", inherit_from="parent.parent.display_exp_color", signal_name="visuals_changed", mix_with=(InheritableMixin, SignalMixin) ) #: Flag indicating whether to use the grandparents color yes/no inherit_color = BoolProperty( default=True, text="Inherit color", visible=True, persistent=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: The linewidth in points lw = FloatProperty( default=2.0, text="Linewidth", visible=True, persistent=True, widget_type="spin", inherit_flag="inherit_lw", inherit_from="parent.parent.display_exp_lw", signal_name="visuals_changed", mix_with=(InheritableMixin, SignalMixin), ) #: Flag indicating whether to use the grandparents linewidth yes/no inherit_lw = BoolProperty( default=True, text="Inherit linewidth", visible=True, persistent=True, signal_name="visuals_changed", mix_with=(SignalMixin,), ) #: A short string describing the (matplotlib) linestyle ls = StringChoiceProperty( default=settings.EXPERIMENTAL_LINESTYLE, text="Linestyle", visible=True, persistent=True, choices=settings.PATTERN_LINE_STYLES, mix_with=(InheritableMixin, SignalMixin,), signal_name="visuals_changed", inherit_flag="inherit_ls", inherit_from="parent.parent.display_exp_ls", ) #: Flag indicating whether to use the grandparents linestyle yes/no inherit_ls = BoolProperty( default=True, text="Inherit linestyle", visible=True, persistent=True, mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: A short string describing the (matplotlib) marker marker = StringChoiceProperty( default=settings.EXPERIMENTAL_MARKER, text="Marker", visible=True, persistent=True, choices=settings.PATTERN_MARKERS, mix_with=(InheritableMixin, SignalMixin,), signal_name="visuals_changed", inherit_flag="inherit_marker", inherit_from="parent.parent.display_exp_marker", ) #: Flag indicating whether to use the grandparents linewidth yes/no inherit_marker = BoolProperty( default=True, text="Inherit marker", visible=True, persistent=True, mix_with=(SignalMixin,), signal_name="visuals_changed", ) #: z-data (e.g. relative humidity, temperature, for multi-column 'lines') z_data = ListProperty( default=None, text="Z data", data_type=float, persistent=True, visible=False ) # REGULAR PROPERTIES: @property def max_display_y(self): if self.num_columns > 2: # If there's several y-columns, check if we have z-data associated with them # if so, it is a 2D pattern, otherwise this is a multi-line pattern if len(self.z_data) > 2: return np.max(self.z_data) else: return self.max_y else: # If there's a single comumn of y-data, just get the max value return self.max_y @property def min_intensity(self): if self.num_columns > 2: return np.min(self.z_data) else: return self.min_y @property def abs_max_intensity(self): return self.abs_max_y # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): """ Valid keyword arguments for a PyXRDLine are: data: the actual data containing x and y values label: the label for this line color: the color of this line inherit_color: whether to use the parent-level color or its own lw: the line width of this line inherit_lw: whether to use the parent-level line width or its own ls: the line style of this line inherit_ls: whether to use the parent-level line style or its own marker: the line marker of this line inherit_marker: whether to use the parent-level line marker or its own z_data: the z-data associated with the columns in a multi-column pattern """ my_kwargs = self.pop_kwargs(kwargs, *[prop.label for prop in PyXRDLine.Meta.get_local_persistent_properties()] ) super(PyXRDLine, self).__init__(*args, **kwargs) kwargs = my_kwargs with self.visuals_changed.hold(): self.label = self.get_kwarg(kwargs, self.label, "label") self.color = self.get_kwarg(kwargs, self.color, "color") self.inherit_color = bool(self.get_kwarg(kwargs, self.inherit_color, "inherit_color")) self.lw = float(self.get_kwarg(kwargs, self.lw, "lw")) self.inherit_lw = bool(self.get_kwarg(kwargs, self.inherit_lw, "inherit_lw")) self.ls = self.get_kwarg(kwargs, self.ls, "ls") self.inherit_ls = bool(self.get_kwarg(kwargs, self.inherit_ls, "inherit_ls")) self.marker = self.get_kwarg(kwargs, self.marker, "marker") self.inherit_marker = bool(self.get_kwarg(kwargs, self.inherit_marker, "inherit_marker")) self.z_data = list(self.get_kwarg(kwargs, [0], "z_data")) # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ @classmethod def from_json(cls, **kwargs): # @ReservedAssignment if "xy_store" in kwargs: if "type" in kwargs["xy_store"]: kwargs["data"] = kwargs["xy_store"]["properties"]["data"] elif "xy_data" in kwargs: if "type" in kwargs["xy_data"]: kwargs["data"] = kwargs["xy_data"]["properties"]["data"] kwargs["label"] = kwargs["data_label"] del kwargs["data_name"] del kwargs["data_label"] del kwargs["xy_data"] return cls(**kwargs) # ------------------------------------------------------------ # Convenience Methods & Functions # ------------------------------------------------------------ def interpolate(self, *x_vals, **kwargs): """ Returns a list of (x, y) tuples for the passed x values. An optional column keyword argument can be passed to select a column, by default the first y-column is used. Returned y-values are interpolated. """ column = kwargs.get("column", 0) f = interp1d(self.data_x, self.data_y[:, column]) return list(zip(x_vals, f(x_vals))) def get_plotted_y_at_x(self, x): """ Gets the (interpolated) plotted value at the given x position. If this line has not been plotted (or does not have access to a '__plot_line' attribute set by the plotting routines) it will return 0. """ try: xdata, ydata = getattr(self, "__plot_line").get_data() except AttributeError: logging.exception("Attribute error when trying to get plotter data at x position!") else: if len(xdata) > 0 and len(ydata) > 0: return np.interp(x, xdata, ydata) return 0 def calculate_npeaks_for(self, max_threshold, steps): """ Calculates the number of peaks for `steps` threshold values between 0 and `max_threshold`. Returns a tuple containing two lists with the threshold values and the corresponding number of peaks. """ length = self.data_x.size resolution = length / (self.data_x[-1] - self.data_x[0]) delta_angle = 0.05 window = int(delta_angle * resolution) window += (window % 2) * 2 steps = max(steps, 2) - 1 factor = max_threshold / steps deltas = [i * factor for i in range(0, steps)] numpeaks = [] maxtabs, mintabs = multi_peakdetect(self.data_y[:, 0], self.data_x, 5, deltas) for maxtab, _ in zip(maxtabs, mintabs): numpeak = len(maxtab) numpeaks.append(numpeak) numpeaks = list(map(float, numpeaks)) return deltas, numpeaks def get_best_threshold(self, max_threshold=None, steps=None, status_dict=None): """ Estimates the best threshold for peak detection using an iterative algorithm. Assumes there is a linear contribution from noise. Returns a 4-tuple containing the selected threshold, the maximum threshold, a list of threshold values and a list with the corresponding number of peaks. """ length = self.data_x.size steps = not_none(steps, 20) threshold = 0.1 max_threshold = not_none(max_threshold, threshold * 3.2) def get_new_threshold(threshold, deltas, num_peaks, ln): # Left side line: x = deltas[:ln] y = num_peaks[:ln] slope, intercept, R, _, _ = stats.linregress(x, y) return R, -intercept / slope if length > 2: # Adjust the first distribution: deltas, num_peaks = self.calculate_npeaks_for(max_threshold, steps) # Fit several lines with increasing number of points from the # generated threshold / marker count graph. Stop when the # R-coefficiënt drops below 0.95 (past linear increase from noise) # Then repeat this by increasing the resolution of data points # and continue until the result does not change anymore last_threshold = None solution = False max_iters = 10 min_iters = 3 itercount = 0 if status_dict is not None: status_dict["progress"] = 0 while not solution: # Number of points to use for the lin regress: ln = 4 # Maximum number of points to use: max_ln = len(deltas) # Flag indicating if we can stop searching for the linear part stop = False while not stop: R, threshold = get_new_threshold(threshold, deltas, num_peaks, ln) max_threshold = threshold * 3.2 if abs(R) < 0.98 or ln >= max_ln: stop = True else: ln += 1 itercount += 1 # Increase # of iterations if last_threshold: # Check if we have run at least `min_iters`, at most `max_iters` # and have not reached an equilibrium. solution = bool( itercount > min_iters and not ( itercount <= max_iters and last_threshold - threshold >= 0.001 ) ) if not solution: deltas, num_peaks = self.calculate_npeaks_for(max_threshold, steps) last_threshold = threshold if status_dict is not None: status_dict["progress"] = float(itercount / max_iters) return (deltas, num_peaks), threshold, max_threshold else: return ([], []), threshold, max_threshold pass # end of classPyXRD-0.8.4/pyxrd/generic/models/lines/storable_xy_data.py000066400000000000000000000101321363064711000235440ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import numpy as np from mvc.models.xydata import XYData from pyxrd.generic.io import storables, Storable from pyxrd.generic.models.base import DataModel from pyxrd.generic.utils import not_none #from pyxrd.file_parsers.ascii_parser import ASCIIParser @storables.register() class StorableXYData(DataModel, XYData, Storable): """ A storable XYData model with additional I/O and CRUD abilities. """ class Meta(XYData.Meta): store_id = "StorableXYData" # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): """ Valid keyword arguments for a PyXRDLine are: data: the actual data containing x and y values label: the label for this line color: the color of this line inherit_color: whether to use the parent-level color or its own lw: the line width of this line inherit_lw: whether to use the parent-level line width or its own """ if "xy_store" in kwargs: kwargs["data"] = kwargs.pop("xy_store") super(StorableXYData, self).__init__(*args, **kwargs) # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ def json_properties(self): props = super(XYData, self).json_properties() props["data"] = self._serialize_data() return props def apply_correction(self, correction): self.data_y = self.data_y * correction[:, np.newaxis] def save_data(self, parser, filename, **kwargs): if self.data_y.shape[1] > 1: kwargs["header"] = ["2θ", ] + (not_none(self.y_names, [])) parser.write(filename, self.data_x, self._data_y.transpose(), **kwargs) def load_data(self, parser, filename, clear=True): """ Loads data using passed filename and parser, which are passed on to the load_data_from_generator method. If clear=True the x-y data is cleared first. """ xrdfiles = parser.parse(filename) if xrdfiles: self.load_data_from_generator(xrdfiles[0].data, clear=clear) def load_data_from_generator(self, generator, clear=True): with self.data_changed.hold_and_emit(): with self.visuals_changed.hold_and_emit(): super(StorableXYData, self).load_data_from_generator(generator, clear=clear) def set_data(self, x, y): """ Sets data using the supplied x, y1, ..., yn arrays. """ with self.data_changed.hold_and_emit(): with self.visuals_changed.hold_and_emit(): super(StorableXYData, self).set_data(x, y) def set_value(self, i, j, value): with self.data_changed.hold_and_emit(): with self.visuals_changed.hold_and_emit(): super(StorableXYData, self).set_value(i, j, value) def append(self, x, y): """ Appends data using the supplied x, y1, ..., yn arrays. """ with self.data_changed.hold_and_emit(): with self.visuals_changed.hold_and_emit(): super(StorableXYData, self).append(x, y) def insert(self, pos, x, y): """ Inserts data using the supplied x, y1, ..., yn arrays at the given position. """ with self.data_changed.hold_and_emit(): with self.visuals_changed.hold_and_emit(): super(StorableXYData, self).insert(pos, x, y) def remove_from_indeces(self, *indeces): with self.data_changed.hold_and_emit(): with self.visuals_changed.hold_and_emit(): super(StorableXYData, self).remove_from_indeces(*indeces) pass # end of classPyXRD-0.8.4/pyxrd/generic/models/mathtext_support.py000066400000000000000000000000001363064711000225310ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/models/mixins.py000066400000000000000000000023011363064711000204140ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import csv class CSVMixin(object): """ Model mixin providing CSV export and import functionality """ @classmethod def save_as_csv(cls, filename, items): atl_writer = csv.writer(open(filename, 'w'), delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) labels = [prop.label for prop in cls.Meta.get_local_persistent_properties()] atl_writer.writerow(labels) for item in items: prop_row = [] for label in labels: prop_row.append(getattr(item, label)) atl_writer.writerow(prop_row) @classmethod def get_from_csv(cls, filename, parent=None): with open(filename, 'r') as csvfile: atl_reader = csv.DictReader(csvfile, delimiter=',', quotechar='"') labels = [prop.label for prop in cls.Meta.get_local_persistent_properties()] for row in atl_reader: yield cls(parent=parent, **{ prop: row[prop] for prop in labels if prop in row }) pass # end of class PyXRD-0.8.4/pyxrd/generic/models/properties.py000066400000000000000000000073261363064711000213150ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from mvc.models.properties.observe_mixin import ObserveMixin from mvc.support.utils import rec_getattr class ObserveChildMixin(ObserveMixin): """ A descriptor mixin that will make the instance observe and relieve the objects set and clear and set the parent property on the old and new object respectively """ def __relieve_old(self, instance, old, new): if old is not None: instance.relieve_model(old) old.parent = None def __observe_new(self, instance, old, new): if new is not None: new.parent = instance instance.observe_model(new) pass class InheritableMixin(object): """ Mixing for the ~:class:`mvc.models.properties.LabeledProperty` descriptor that allows the property to be inheritable from another property. When this Mixin is used, the user should pass two additional keyword arguments to the descriptor: - inheritable: boolean set to True if inheriting should be enabled - inherit_flag: dotted string describing where to get the flag indicating the property is inherited yes/no - inherit_from: dotted string describing where to get the attribute if the inherit_flag is True """ inheritable = True inherit_flag = None inherit_from = None def __get__(self, instance, owner=None): if instance is None: return self value = self.get_uninherited(instance, owner) if self.inheritable and rec_getattr(instance, self.inherit_flag, False): value = rec_getattr(instance, self.inherit_from, value) return value def get_uninherited(self, instance, owner=None): return super(InheritableMixin, self).__get__(instance, owner) pass #end of class class IndexProperty(object): """Descriptor used to create indexable properties (e.g. W[1,1])""" def __init__(self, fget=None, fset=None, fdel=None, doc=None): if doc is None and fget is not None and hasattr(fget, "__doc__"): doc = fget.__doc__ self._get = fget self._set = fset self._del = fdel self.__doc__ = doc def __get__(self, instance, owner): if instance is None: return self else: return BoundIndexProperty(self, instance) def __set__(self, instance, value): raise AttributeError("can't set attribute") def __delete__(self, instance): raise AttributeError("can't delete attribute") def getter(self, fget): return IndexProperty(fget, self._set, self._del, self.__doc__) def setter(self, fset): return IndexProperty(self._get, fset, self._del, self.__doc__) def deleter(self, fdel): return IndexProperty(self._get, self._set, fdel, self.__doc__) pass #end of class class BoundIndexProperty(object): def __init__(self, item_property, instance): self.__item_property = item_property self.__instance = instance def __getitem__(self, key): fget = self.__item_property._get if fget is None: raise AttributeError("unreadable attribute item") return fget(self.__instance, key) def __setitem__(self, key, value): fset = self.__item_property._set if fset is None: raise AttributeError("can't set attribute item") fset(self.__instance, key, value) def __delitem__(self, key): fdel = self.__item_property._del if fdel is None: raise AttributeError("can't delete attribute item") fdel(self.__instance, key) pass #end of class PyXRD-0.8.4/pyxrd/generic/outdated/000077500000000000000000000000001363064711000170655ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/outdated/__init__.py000066400000000000000000000072511363064711000212030ustar00rootroot00000000000000import json from datetime import datetime from threading import Thread from warnings import warn from . import utils from .mywarnings import * __version__ = '0.1.2' def check_outdated(package, version): """ Given the name of a package on PyPI and a version (both strings), checks if the given version is the latest version of the package available. Returns a 2-tuple (is_outdated, latest_version) where is_outdated is a boolean which is True if the given version is earlier than the latest version, which is the string latest_version. Attempts to cache on disk the HTTP call it makes for 24 hours. If this somehow fails the exception is converted to a warning (OutdatedCacheFailedWarning) and the function continues normally. """ from pkg_resources import parse_version parsed_version = parse_version(version) latest = None with utils.cache_file(package, 'r') as f: content = f.read() if content: # in case cache_file fails and so f is a dummy file latest, cache_dt = json.loads(content) if not utils.cache_is_valid(cache_dt): latest = None def get_latest(): url = 'https://pypi.python.org/pypi/%s/json' % package response = utils.get_url(url) return json.loads(response)['info']['version'] if latest is None: latest = get_latest() parsed_latest = parse_version(latest) if parsed_version > parsed_latest: # Probably a stale cached value latest = get_latest() parsed_latest = parse_version(latest) # Don't be stupid - I'm building more recent version locally that have not been released yet #if parsed_version > parsed_latest: # raise ValueError('Version %s is greater than the latest version on PyPI: %s' % # (version, latest)) is_latest = parsed_version == parsed_latest #assert is_latest or parsed_version < parsed_latest again don't be stupid with utils.cache_file(package, 'w') as f: data = [latest, utils.format_date(datetime.now())] json.dump(data, f) return not is_latest, latest def warn_if_outdated(package, version, raise_exceptions=False, background=True, ): """ Higher level convenience function using check_outdated. The package and version arguments are the same. If the package is outdated, a warning (OutdatedPackageWarning) will be emitted. Any exception in check_outdated will be converted to a warning (OutdatedCheckFailedWarning) unless raise_exceptions if True. If background is True (the default), the check will run in a background thread so this function will return immediately. In this case if an exception is raised and raise_exceptions if True the traceback will be printed to stderr but the program will not be interrupted. This function doesn't return anything. """ def check(): # noinspection PyUnusedLocal is_outdated = False with utils.exception_to_warning('check for latest version of %s' % package, OutdatedCheckFailedWarning, always_raise=raise_exceptions): is_outdated, latest = check_outdated(package, version) if is_outdated: warn('The package %s is out of date. Your version is %s, the latest is %s.' % (package, version, latest), OutdatedPackageWarning) if background: thread = Thread(target=check) thread.start() else: check() warn_if_outdated('outdated', __version__) PyXRD-0.8.4/pyxrd/generic/outdated/mywarnings.py000066400000000000000000000013011363064711000216300ustar00rootroot00000000000000from warnings import filterwarnings class OutdatedWarningBase(Warning): """ Base class for warnings in this module. Use this to filter all warnings from this module. """ class OutdatedPackageWarning(OutdatedWarningBase): """ Warning emitted when a package is found to be out of date. """ filterwarnings("always", category=OutdatedPackageWarning) class OutdatedCheckFailedWarning(OutdatedWarningBase): """ Warning emitted when checking the version of a package fails with an exception. """ class OutdatedCacheFailedWarning(OutdatedWarningBase): """ Warning emitted when writing to or reading from the cache fails with an exception. """ PyXRD-0.8.4/pyxrd/generic/outdated/utils.py000066400000000000000000000116351363064711000206050ustar00rootroot00000000000000import os from time import sleep import tempfile import functools from contextlib import contextmanager from datetime import datetime, timedelta from warnings import warn from .mywarnings import OutdatedCacheFailedWarning def retry(num_attempts=3, exception_class=Exception, log=None, sleeptime=1): """ >>> def fail(): ... runs[0] += 1 ... {}[1] >>> runs = [0]; retry(sleeptime=0)(fail)() Traceback (most recent call last): ... KeyError: 1 >>> runs [3] >>> runs = [0]; retry(2, sleeptime=0)(fail)() Traceback (most recent call last): ... KeyError: 1 >>> runs [2] >>> runs = [0]; retry(exception_class=IndexError, sleeptime=0)(fail)() Traceback (most recent call last): ... KeyError: 1 >>> runs [1] >>> logger = DoctestLogger() >>> runs = [0]; retry(log=logger, sleeptime=0)(fail)() Traceback (most recent call last): ... KeyError: 1 >>> runs [3] >>> logger.print_logs() Failed with error KeyError(1,), trying again Failed with error KeyError(1,), trying again """ def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): for i in range(num_attempts): try: return func(*args, **kwargs) except exception_class as e: if i == num_attempts - 1: raise else: if log: log.warn('Failed with error %r, trying again', e) sleep(sleeptime) return wrapper return decorator def format_date(dt): return dt.strftime('%Y-%m-%d %H:%M:%S') def cache_is_valid(cache_dt): return format_date(datetime.now() - timedelta(days=1)) < cache_dt # noinspection PyCompatibility @retry() def get_url(url): try: from urllib.request import urlopen except ImportError: # noinspection PyUnresolvedReferences from urllib2 import urlopen return urlopen(url).read().decode('utf8') @contextmanager def cache_file(package, mode): """ Yields a file-like object for the purpose of writing to or reading from the cache. The code: with cache_file(...) as f: # do stuff with f is guaranteed to convert any exceptions to warnings (*), both in the cache_file(...) call and the 'do stuff with f' block. The file is automatically closed upon exiting the with block. If getting an actual file fails, yields a DummyFile. :param package: the name of the package being checked as a string :param mode: the mode to open the file in, either 'r' or 'w' """ f = DummyFile() # We have to wrap the whole function body in this block to guarantee # catching all exceptions. In particular the yield needs to be inside # to catch exceptions coming from the with block. with exception_to_warning('use cache while checking for outdated package', OutdatedCacheFailedWarning): try: cache_path = os.path.join(tempfile.gettempdir(), get_cache_filename(package)) if mode == 'w' or os.path.exists(cache_path): f = open(cache_path, mode) finally: # Putting the yield in the finally section ensures that exactly # one thing is yielded once, otherwise @contextmanager would # raise an exception. with f: # closes the file afterards yield f def get_cache_filename(package): return 'outdated_cache_' + package @contextmanager def exception_to_warning(description, category, always_raise=False): """ Catches any exceptions that happen in the corresponding with block and instead emits a warning of the given category, with a message containing the given description and the exception message, unless always_raise is True or the environment variable OUTDATED_RAISE_EXCEPTION is set to 1, in which caise the exception will not be caught. """ try: yield except Exception as e: # We check for the presence of various globals because we may be seeing the death # of the process if this is in a background thread, during which globals # get 'cleaned up' and set to None if always_raise or os and os.environ and os.environ.get('OUTDATED_RAISE_EXCEPTION') == '1': raise if warn: warn('Failed to %s:\n' '%s\n' 'Set the environment variable OUTDATED_RAISE_EXCEPTION=1 for a full traceback.' % (description, e), category) def constantly(x): return lambda *_, **__: x class DummyFile(object): """ File-like object that does nothing. All methods take any arguments and return an empty string. """ write = read = close = __enter__ = __exit__ = constantly('') PyXRD-0.8.4/pyxrd/generic/plot/000077500000000000000000000000001363064711000162325ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/plot/__init__.py000066400000000000000000000000001363064711000203310ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/plot/axes_setup.py000066400000000000000000000144011363064711000207640ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from functools import partial import numpy as np from matplotlib.ticker import FixedLocator, FixedFormatter from pyxrd.calculations.goniometer import get_2t_from_nm, get_nm_from_2t from pyxrd.data import settings def _get_ticks(): def create_flat_array(*arrays): arr = np.array([], dtype=float) for new_arr in arrays: arr = np.append(arr, new_arr) return arr minor_ticks_nm = create_flat_array( np.arange(6.0, 2.0, -0.5), np.arange(2.0, 1.0, -0.1), np.arange(1.0, 0.8, -0.05), np.arange(0.80, 0.00, -0.01) ) major_ticks_nm = create_flat_array( np.arange(6.0, 1.0, -1.0), np.arange(2.0, 1.0, -0.5), np.arange(1.0, 0.8, -0.1), np.arange(0.80, 0.00, -0.05) ) label_ticks_nm = create_flat_array( np.arange(6.0, 1.0, -2.0), np.arange(2.0, 1.0, -0.5), np.arange(1.0, 0.8, -0.1), np.arange(0.80, 0.40, -0.1), np.arange(0.40, 0.00, -0.05) ) return minor_ticks_nm, major_ticks_nm, label_ticks_nm def set_nm_ticks(axis, wavelength, xmin, xmax): """ Sets the tick positions and labels for a nanometer x-axes using the given lower & upper limits and the wavelength """ np_nm2a = np.vectorize(partial(get_2t_from_nm, wavelength=wavelength)) def get_tick_labels(a, b): def in_close(value, arr): for val in arr: if np.isclose(value, val): return True return False return [ "%g" % val if in_close(val, b) else "" for val in a ] minor_ticks_nm, major_ticks_nm, label_ticks_nm = _get_ticks() dmax = min(get_nm_from_2t(xmin, wavelength), 100) #limit this so we don't get an "infinite" scale dmin = get_nm_from_2t(xmax, wavelength) # Extract the part we need: selector = (minor_ticks_nm >= dmin) & (minor_ticks_nm <= dmax) minor_ticks_pos = np_nm2a(minor_ticks_nm[selector]) selector = (major_ticks_nm >= dmin) & (major_ticks_nm <= dmax) major_ticks_pos = np_nm2a(major_ticks_nm[selector]) major_ticks_labels = get_tick_labels(major_ticks_nm[selector], label_ticks_nm) # Set the ticks helper = axis.get_helper() helper.axis.minor.locator = FixedLocator(minor_ticks_pos) helper.axis.minor.formatter = FixedFormatter([""] * len(minor_ticks_pos)) helper.axis.major.locator = FixedLocator(major_ticks_pos) helper.axis.major.formatter = FixedFormatter(major_ticks_labels) pass #end of func def update_xaxis(axes, title='Angle (°2$\\theta$)', weight='heavy', rotation=0, ha="center", va="center", pad=0, size=16, nm_ticks=False, wavelength=None): axis = axes.axis["bottom"] axis.major_ticks.set_tick_out(True) axis.minor_ticks.set_tick_out(True) axis.label.set_text(title) axis.label.set_weight(weight) axis.label.set_size(size) axis.label.set_pad(pad) axis.major_ticklabels.set_visible(True) axis.major_ticklabels.set_rotation(rotation) axis.major_ticklabels.set_ha(ha) axis.major_ticklabels.set_va(va) if nm_ticks: set_nm_ticks(axis, wavelength, *axes.get_xlim()) def update_lim(axes, pos_setup, project): # Autoscale the view: axes.autoscale_view(tight=True) xmin, xmax = axes.get_xlim() axes.set_ylim(bottom=0, auto=True) # Adjust x limits if needed: if project is None or project.axes_xlimit == 0: xmin, xmax = max(xmin, 0.0), max(xmax, 20.0) else: xmin, xmax = max(project.axes_xmin, 0.0), project.axes_xmax axes.set_xlim(left=xmin, right=xmax, auto=False) # Adjust y limits if needed if project is not None and project.axes_ylimit != 0: scale, _ = project.get_scale_factor() ymin = max(project.axes_ymin, 0.0) ymax = project.axes_ymax if ymax <= 0: ymax = axes.get_ylim()[1] else: ymax = ymax * scale ymin = ymin * scale axes.set_ylim(bottom=ymin, top=ymax, auto=False) # Update plot position setup: pos_setup.xdiff = xmax - xmin pos_setup.xstretch = project.axes_xstretch if project is not None else False def update_axes(axes, pos_setup, project, specimens): """ Internal generic plot update method. """ update_lim(axes, pos_setup, project) axes.axis["right"].set_visible(False) axes.axis["top"].set_visible(False) axes.get_xaxis().tick_bottom() axes.get_yaxis().tick_left() if project is None or not project.axes_yvisible: axes.axis["left"].set_visible(False) else: axes.axis["left"].set_visible(True) axes.set_position(pos_setup.position) if project is not None and project.axes_dspacing: if specimens is None or len(specimens) == 0: wavelength = settings.AXES_DEFAULT_WAVELENGTH else: wavelength = specimens[0].goniometer.wavelength update_xaxis(axes, title='d (nm)', rotation=-90, ha="left", pad=25, nm_ticks=True, wavelength=wavelength, ) else: update_xaxis(axes) class PositionSetup(object): """ Keeps track of the positioning of a plot """ left = settings.PLOT_LEFT top = settings.PLOT_TOP bottom = settings.PLOT_BOTTOM xdiff = 20 xstretch = settings.AXES_XSTRETCH @property def right(self): return self.left + self.width @property def width(self): MAX_PLOT_WIDTH = settings.MAX_PLOT_RIGHT - self.left if self.xstretch: return MAX_PLOT_WIDTH else: return min((self.xdiff / 70), 1.0) * MAX_PLOT_WIDTH @property def height(self): return abs(self.top - self.bottom) @property def position(self): return [self.left, self.bottom, self.width, self.height] def to_string(self): return ":".join(map(str, [self.left,self.right,self.top,self.bottom,self.xdiff,self.xstretch])) @property def default_bottom(self): return settings.PLOT_BOTTOM @property def default_left(self): return settings.PLOT_LEFT @property def default_top(self): return settings.PLOT_TOP pass #end of class PyXRD-0.8.4/pyxrd/generic/plot/click_catcher.py000066400000000000000000000026171363064711000213700ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .event_delegator import MPLCanvasEventDelegator class ClickCatcher(): """ This class can be used to register matplotlib artists, which will fire the update_callback method when clicked. When registering the artist, an arbitrary object can be passed which is passed to the callback. """ def __init__(self, plot_controller, update_callback=None): self.plot_controller = plot_controller self._canvas = plot_controller.canvas self._window = self._canvas.get_window() self._update_callback = update_callback self.connect() self._artists = {} def register_artist(self, artist, obj): self._artists[artist] = obj artist.set_picker(True) def _on_pick(self, event): if event.artist is not None: obj = self._artists.get(event.artist, None) self._update_callback(obj) return False def connect(self): delegator = MPLCanvasEventDelegator.wrap_canvas(self._canvas) delegator.connect('pick_event', self._on_pick, first=True) def disconnect(self): delegator = MPLCanvasEventDelegator.wrap_canvas(self._canvas) delegator.disconnect('pick_event', self._on_pick) pass #end of classPyXRD-0.8.4/pyxrd/generic/plot/controllers.py000066400000000000000000000267231363064711000211640ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport import gi from pyxrd.generic.plot.click_catcher import ClickCatcher gi.require_version('Gtk', '3.0') from gi.repository import Gtk import logging logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) import matplotlib import matplotlib.transforms as transforms from matplotlib.figure import Figure from matplotlib.tight_layout import get_renderer from matplotlib.backends.backend_gtk3 import NavigationToolbar2GTK3 as NavigationToolbar from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvasGTK try: from matplotlib.pyparsing import ParseFatalException except ImportError: from pyparsing import ParseFatalException from mpl_toolkits.axisartist import Subplot from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory from pyxrd.data import settings from pyxrd.generic.plot.motion_tracker import MotionTracker from pyxrd.generic.plot.axes_setup import PositionSetup, update_axes from pyxrd.generic.plot.plotters import plot_specimens, plot_mixtures class MainPlotController(object): """ A controller for the main plot canvas. Sets up the widgets and has image exporting functionality. """ file_filters = ("Portable Network Graphics (PNG)", "*.png"), \ ("Scalable Vector Graphics (SVG)", "*.svg"), \ ("Portable Document Format (PDF)", "*.pdf") _canvas = None @property def canvas(self): if not self._canvas: self.setup_figure() self.setup_canvas() self.setup_content() return self._canvas # ------------------------------------------------------------ # View integration getters # ------------------------------------------------------------ def get_toolbar_widget(self, window): return NavigationToolbar(self.canvas, window) def get_canvas_widget(self): return self.canvas # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, status_callback, marker_callback, *args, **kwargs): self.setup_layout_cache() self.setup_figure() self.setup_canvas() self.setup_content(status_callback, marker_callback) def setup_layout_cache(self): self.position_setup = PositionSetup() self.labels = list() self.marker_lbls = list() self._proxies = dict() self.scale = 1.0 self.stats = False self._last_pos = None def setup_figure(self): self.figure = Figure(dpi=72, facecolor="#FFFFFF", linewidth=0) self.figure.subplots_adjust(hspace=0.0, wspace=0.0) def setup_canvas(self): self._canvas = FigureCanvasGTK(self.figure) def setup_content(self, status_callback, marker_callback): # Create subplot and add it to the figure: self.plot = Subplot(self.figure, 211, facecolor=(1.0, 1.0, 1.0, 0.0)) self.plot.set_autoscale_on(False) self.figure.add_axes(self.plot) # Connect events: self.canvas.mpl_connect('draw_event', self.fix_after_drawing) self.canvas.mpl_connect('resize_event', self.fix_after_drawing) self.mtc = MotionTracker(self, status_callback) self.cc = ClickCatcher(self, marker_callback) #self.update() # ------------------------------------------------------------ # Update methods # ------------------------------------------------------------ def draw(self): self._last_pos = self.fix_before_drawing() self.figure.canvas.draw() def fix_after_drawing(self, *args): _new_pos = self.fix_before_drawing() if _new_pos != self._last_pos: self._last_pos = _new_pos self._redraw_later() return False def _redraw_later(self): self.timer = self.figure.canvas.new_timer(interval=10) self.timer.single_shot = True self.timer.add_callback(lambda : self.figure.canvas.draw_idle()) self.timer.start() def fix_before_drawing(self, *args): """ Fixes alignment issues due to longer labels or smaller windows Is executed after an initial draw event, since we can then retrieve the actual label dimensions and shift/resize the plot accordingly. """ renderer = get_renderer(self.figure) if not renderer or not self._canvas.get_realized(): return False # Fix left side for wide specimen labels: if len(self.labels) > 0: bbox = self._get_joint_bbox(self.labels, renderer) if bbox is not None: self.position_setup.left = self.position_setup.default_left + bbox.width # Fix top for high marker labels: if len(self.marker_lbls) > 0: bbox = self._get_joint_bbox([ label for label, flag, _ in self.marker_lbls if flag ], renderer) if bbox is not None: self.position_setup.top = self.position_setup.default_top - bbox.height # Fix bottom for x-axis label: bottom_label = self.plot.axis["bottom"].label if bottom_label is not None: bbox = self._get_joint_bbox([bottom_label], renderer) if bbox is not None: self.position_setup.bottom = self.position_setup.default_bottom + (bbox.ymax - bbox.ymin) * 2.0 # somehow we need this? # Calculate new plot position & set it: plot_pos = self.position_setup.position self.plot.set_position(plot_pos) # Adjust specimen label position for label in self.labels: label.set_x(plot_pos[0] - 0.025) # Adjust marker label position for label, flag, y_offset in self.marker_lbls: if flag: newy = plot_pos[1] + plot_pos[3] + y_offset - 0.025 label.set_y(newy) _new_pos = self.position_setup.to_string() return _new_pos def update(self, clear=False, project=None, specimens=None): """ Updates the entire plot with the given information. """ if clear: self.plot.cla() if project and specimens: self.labels, self.marker_lbls = plot_specimens( self.plot, self.position_setup, self.cc, project, specimens ) # get mixtures for the selected specimens: plot_mixtures(self.plot, project, [ mixture for mixture in project.mixtures if any(specimen in mixture.specimens for specimen in specimens) ]) update_axes( self.plot, self.position_setup, project, specimens ) self.draw() # ------------------------------------------------------------ # Plot position and size calculations # ------------------------------------------------------------ def _get_joint_bbox(self, container, renderer): bboxes = [] try: for text in container: bbox = text.get_window_extent(renderer=renderer) # the figure transform goes from relative coords->pixels and we # want the inverse of that bboxi = bbox.inverse_transformed(self.figure.transFigure) bboxes.append(bboxi) except (RuntimeError, ValueError): logger.exception("Caught unhandled exception when joining boundig boxes") return None # don't continue # this is the bbox that bounds all the bboxes, again in relative # figure coords if len(bboxes) > 0: bbox = transforms.Bbox.union(bboxes) return bbox else: return None # ------------------------------------------------------------ # Graph exporting # ------------------------------------------------------------ def save(self, parent=None, current_name="graph", size="auto", num_specimens=1, offset=0.75): """ Displays a save dialog to export an image from the current plot. """ # Parse arguments: width, height = 0, 0 if size == "auto": descr, width, height, dpi = settings.OUTPUT_PRESETS[0] else: width, height, dpi = list(map(float, size.replace("@", "x").split("x"))) # Load gui: builder = Gtk.Builder() builder.add_from_file(resource_filename("pyxrd.specimen", "glade/save_graph_size.glade")) # FIXME move this to this namespace!! size_expander = builder.get_object("size_expander") cmb_presets = builder.get_object("cmb_presets") # Setup combo with presets: cmb_store = Gtk.ListStore(str, int, int, float) for row in settings.OUTPUT_PRESETS: cmb_store.append(row) cmb_presets.clear() cmb_presets.set_model(cmb_store) cell = Gtk.CellRendererText() cmb_presets.pack_start(cell, True) cmb_presets.add_attribute(cell, 'text', 0) def on_cmb_changed(cmb, *args): itr = cmb.get_active_iter() w, h, d = cmb_store.get(itr, 1, 2, 3) entry_w.set_text(str(w)) entry_h.set_text(str(h)) entry_dpi.set_text(str(d)) cmb_presets.connect('changed', on_cmb_changed) # Setup input boxes: entry_w = builder.get_object("entry_width") entry_h = builder.get_object("entry_height") entry_dpi = builder.get_object("entry_dpi") entry_w.set_text(str(width)) entry_h.set_text(str(height)) entry_dpi.set_text(str(dpi)) # What to do when the user wants to save this: def on_accept(dialog): # Get the width, height & dpi width = float(entry_w.get_text()) height = float(entry_h.get_text()) dpi = float(entry_dpi.get_text()) i_width, i_height = width / dpi, height / dpi # Save it all right! self.save_figure(dialog.filename, dpi, i_width, i_height) # Ask the user where, how and if he wants to save: DialogFactory.get_save_dialog( "Save Graph", parent=parent, filters=self.file_filters, current_name=current_name, extra_widget=size_expander ).run(on_accept) def save_figure(self, filename, dpi, i_width, i_height): """ Save the current plot Arguments: filename: the filename to save to (either .png, .pdf or .svg) dpi: Dots-Per-Inch resolution i_width: the width in inch i_height: the height in inch """ # Get original settings: original_dpi = self.figure.get_dpi() original_width, original_height = self.figure.get_size_inches() # Set everything according to the user selection: self.figure.set_dpi(dpi) self.figure.set_size_inches((i_width, i_height)) self.figure.canvas.draw() # replot bbox_inches = matplotlib.transforms.Bbox.from_bounds(0, 0, i_width, i_height) # Save the figure: self.figure.savefig(filename, dpi=dpi, bbox_inches=bbox_inches) # Put everything back the way it was: self.figure.set_dpi(original_dpi) self.figure.set_size_inches((original_width, original_height)) self.figure.canvas.draw() # replot pass # end of class PyXRD-0.8.4/pyxrd/generic/plot/draggables.py000066400000000000000000000163051363064711000207040ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import gi gi.require_version('Gtk', '3.0') from gi.repository import Gdk from .event_delegator import MPLCanvasEventDelegator class DraggableMixin(object): """ A mixin that can be used to make any matplotlib artist draggable. The constructor takes two callbacks, `on_dragged` and `on_released` which are called when the artist is dragged and released respectively. These callbacks are passed 3 coordinate tuples: the starting point of the drag operation, the previous cursor position and the current cursor position. All of these are in display coordinates. If these need to be converted you can use the `convert_display_to_*` methods where * should be one of figure, axes or data. Aside from these coordinates, the callbacks are also passed the last event object. For more advanced usage the user can also override the `_draggable_on_press`, `_draggable_on_motion` and `_draggable_on_release` event handlers, be sure to call the base implementation if so. """ lock = None # only one can be animated at a time _prev_event_data = None _draggable_artist = None _draggable_figure = None _draggable_canvas = None _draggable_axes = None _on_dragged = None _on_released = None _first_event_data = None _prev_event_data = None @property def _is_dragging(self): return not bool( self._draggable_artist is None or \ self._first_event_data is None or \ self._prev_event_data is None ) def __init__(self, artist=None, on_dragged=None, on_released=None, *args, **kwargs): super(DraggableMixin, self).__init__(*args, **kwargs) self.update(artist, on_dragged, on_released) def update(self, artist=None, on_dragged=None, on_released=None): self._on_dragged = on_dragged self._on_released = on_released self.set_draggable_artist(artist) def disconnect(self): self.set_draggable_artist(None) def set_draggable_artist(self, artist): if self._draggable_artist is not None: self._draggable_disconnect() self._draggable_artist = artist self._draggable_figure = artist.figure if artist is not None else None self._draggable_canvas = artist.figure.canvas if artist is not None else None self._draggable_axes = artist.axes if artist is not None else None if self._draggable_artist is not None: self._draggable_connect() def _convert_to_inverse_transform(self, x, y, transf): inverse = transf.inverted() return inverse.transform((x, y)) def convert_display_to_figure(self, x, y): return self._convert_to_inverse_transform( x, y, self._draggable_figure.transFigure) def convert_display_to_axes(self, x, y): return self._convert_to_inverse_transform( x, y, self._draggable_axes.transAxes) def convert_display_to_data(self, x, y): return self._convert_to_inverse_transform( x, y, self._draggable_axes.transData) # -- EVENT HANDLERS: def _draggable_on_press(self, event): if self._draggable_figure is None: return False contains, attrd = self._draggable_artist.contains(event) # @UnusedVariable if not contains: return False self._prev_event_data = event.x, event.y self._first_event_data = self._prev_event_data return True def _draggable_on_motion(self, event): if not self._is_dragging: return x0, y0 = self._first_event_data x1, y1 = self._prev_event_data x2, y2 = event.x, event.y if callable(self._on_dragged): self._on_dragged( (x0, y0), (x1, y1), (x2, y2), event=event ) self._prev_event_data = x2, y2 def _draggable_on_release(self, event): if not self._is_dragging: return x0, y0 = self._first_event_data x1, y1 = self._prev_event_data x2, y2 = event.x, event.y if callable(self._on_released): self._on_released( (x0, y0), (x1, y1), (x2, y2), event=event ) self._prev_event_data = None self._first_event_data = None # -- CONECTION & DISCONNECTION: __connected = False def _draggable_connect(self): if not self.__connected: delegator = MPLCanvasEventDelegator.wrap_canvas(self._draggable_canvas) delegator.connect('button_press_event', self._draggable_on_press) delegator.connect('button_release_event', self._draggable_on_release) delegator.connect('motion_notify_event', self._draggable_on_motion) self.__connected = True def _draggable_disconnect(self): if self.__connected: delegator = MPLCanvasEventDelegator.wrap_canvas(self._draggable_canvas) delegator.disconnect('button_press_event', self._draggable_on_press) delegator.disconnect('button_release_event', self._draggable_on_release) delegator.disconnect('motion_notify_event', self._draggable_on_motion) self.__connected = False pass #end of class class DraggableVLine(DraggableMixin): """ A draggable vertical line with a callback called with the new x position of the line after the user released it. """ def __init__(self, line, callback=None, window=None): super(DraggableVLine, self).__init__( artist=line, on_dragged=self._on_dragged, on_released=self._on_released) self.line = line self.line_x0 = None self.callback = callback self.window = window def _check_cursor(self, event): if self.window is not None: change_cursor, _ = self.line.contains(event) if not change_cursor: self.window.set_cursor(None) else: arrows = Gdk.Cursor.new(Gdk.CursorType.SB_H_DOUBLE_ARROW) #@UndefinedVariable self.window.set_cursor(arrows) def _draggable_on_motion(self, event): super(DraggableVLine, self)._draggable_on_motion(event) self._check_cursor(event) def _on_dragged(self, x0_y0, x1_y1, x2_y2, event): (x0, y0) = x0_y0 (x1, y1) = x1_y1 (x2, y2) = x2_y2 self._check_cursor(event) if self.line_x0 == None: self.line_x0 = self.line.get_xdata()[0] diff = self.convert_display_to_data(x2, 0)[0] - self.convert_display_to_data(x0, 0)[0] x = max(self.line_x0 + diff, 0) self.line.set_xdata((x, x)) self.line.figure.canvas.draw() def _on_released(self, x0_y0, x1_y1, x2_y2, event): """(x0, y0) = x0_y0 (x1, y1) = x1_y1 (x2, y2) = x2_y2""" self._check_cursor(event) if callable(self.callback): self.callback(self.line.get_xdata()[0]) self.line_x0 = None # redraw the full figure self.line.figure.canvas.draw() pass #end of class PyXRD-0.8.4/pyxrd/generic/plot/event_delegator.py000066400000000000000000000065031363064711000217570ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) class MPLCanvasEventDelegator(object): """ An event delegator object that supports stopping event propagation by returning 'True' and setting event handlers as the first. """ canvas = None _handler_dict = None @classmethod def wrap_canvas(cls, canvas): delegator = getattr(canvas, "__mpl_canvas_delegator", None) if delegator is None: delegator = MPLCanvasEventDelegator(canvas) setattr(canvas, "__mpl_canvas_delegator", delegator) else: assert (delegator.canvas == canvas) return delegator def __init__(self, canvas): self.canvas = canvas self._handler_dict = { "button_press_event": [], "button_release_event": [], "draw_event": [], "key_press_event": [], "key_release_event": [], "motion_notify_event": [], "pick_event": [], "resize_event": [], "scroll_event": [], "figure_enter_event": [], "figure_leave_event": [], "axes_enter_event": [], "axes_leave_event": [], "close_event": [], } def _get_handlers(self, event_name): try: handlers = self._handler_dict[event_name] except KeyError: raise ValueError("Unknown event name!") return handlers def _handle_event(self, event_name): handlers = self._get_handlers(event_name) def _event_handler(event): for handler in handlers: if handler(event): # Returning true stops propagation break else: continue return _event_handler def connect(self, event_name, handler, first=False): """ Connects the given handler with the given event_name. If first is set to True the handler will be the first in the list of event handlers. There is no absolute guarantee the handler will remain the first (e.g. later handlers can also call this with first=True). """ handlers = self._get_handlers(event_name) if len(handlers) == 0: setattr(self, "_event_id_%s" % event_name, self.canvas.mpl_connect( event_name, self._handle_event(event_name) )) if not first: handlers.append(handler) else: handlers.insert(0, handler) def disconnect(self, event_name, handler): """ Disconnects the given handler for the given event_name. """ handlers = self._get_handlers(event_name) try: handlers.remove(handler) except ValueError: logger.warning( "Tried to disconnect an unregistered handler `%r` on `%r`" % ( handler, self)) if len(handlers) == 0: event_id = getattr(self, "_event_id_%s" % event_name, None) if event_id is not None: self.canvas.mpl_disconnect(event_id) setattr(self, "_event_id_%s" % event_name, None) pass # end of class PyXRD-0.8.4/pyxrd/generic/plot/eye_dropper.py000066400000000000000000000033601363064711000211230ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import gi gi.require_version('Gtk', '3.0') from gi.repository import Gdk from .event_delegator import MPLCanvasEventDelegator class EyeDropper(): """ A wrapper that makes eye-dropping a plot possible. Will call the click_callback with an x position and the click event object as arguments. """ def __init__(self, plot_controller, click_callback=None): self._canvas = plot_controller.canvas self._window = self._canvas.get_window() self._click_callback = click_callback self.connect() def _on_motion(self, event): if self._window is not None: self._window.set_cursor(Gdk.Cursor.new(Gdk.CursorType.CROSSHAIR)) # @UndefinedVariable return False def _on_click(self, event): x_pos = -1 if event.inaxes: x_pos = event.xdata if callable(self._click_callback): self._click_callback(x_pos, event) if self._window is not None: self._window.set_cursor(None) return True def connect(self): delegator = MPLCanvasEventDelegator.wrap_canvas(self._canvas) delegator.connect('motion_notify_event', self._on_motion, first=True) delegator.connect('button_press_event', self._on_click, first=True) def disconnect(self): if self._window is not None: self._window.set_cursor(None) delegator = MPLCanvasEventDelegator.wrap_canvas(self._canvas) delegator.disconnect('motion_notify_event', self._on_motion) delegator.disconnect('button_press_event', self._on_click) pass #end of class PyXRD-0.8.4/pyxrd/generic/plot/motion_tracker.py000066400000000000000000000023011363064711000216200ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .event_delegator import MPLCanvasEventDelegator class MotionTracker(): """ A wrapper that tracks mouse movements on a plot. Will call the update_callback with an x position and the click event object as arguments. """ def __init__(self, plot_controller, update_callback=None): self._canvas = plot_controller.canvas self._window = self._canvas.get_window() self._update_callback = update_callback self.connect() def _on_motion(self, event): x_pos = -1 if event.inaxes: x_pos = event.xdata if callable(self._update_callback): self._update_callback(x_pos, event) return False def connect(self): delegator = MPLCanvasEventDelegator.wrap_canvas(self._canvas) delegator.connect('motion_notify_event', self._on_motion, first=True) def disconnect(self): delegator = MPLCanvasEventDelegator.wrap_canvas(self._canvas) delegator.disconnect('motion_notify_event', self._on_motion) pass #end of class PyXRD-0.8.4/pyxrd/generic/plot/plotters.py000066400000000000000000000652711363064711000204730ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import numpy as np import matplotlib import matplotlib.colors as mcolors import matplotlib.transforms as transforms from matplotlib.patches import FancyBboxPatch, Rectangle from matplotlib.offsetbox import VPacker, HPacker, AnchoredOffsetbox, TextArea, AuxTransformBox from matplotlib.text import Text from pyxrd.data import settings from pyxrd.calculations.math_tools import smooth, add_noise from .draggables import DraggableMixin def getattr_or_create(obj, attr, create): value = getattr(obj, attr, None) if value == None: class_type, args, kwargs = create value = class_type(*args, **kwargs) return value def plot_marker_text(project, marker, offset, marker_scale, base_y, axes): """ Plots a markers text using the given offset and scale """ text = getattr(marker, "__plot_text", None) within_range = bool( project.axes_xlimit == 0 or (marker.position >= project.axes_xmin and marker.position <= project.axes_xmax) ) if marker.visible and marker.style != "offset" and within_range: # prevent empty $$ from causing an error: save_label = marker.label.replace("$$", "") # Calculate position and set transform: x = float(marker.position) + float(marker.x_offset) if marker.top == 0: # relative to base y = base_y + (marker.top_offset + marker.y_offset) * marker_scale transform = axes.transData elif marker.top == 1: # top of plot y = settings.PLOT_TOP + float(marker.y_offset) transform = transforms.blended_transform_factory(axes.transData, axes.get_figure().transFigure) kws = dict(text=save_label, x=x, y=y, clip_on=False, transform=transform, horizontalalignment=marker.align, verticalalignment="center", rotation=(90 - marker.angle), rotation_mode="anchor", color=marker.color, weight="heavy") if text: for key in kws: getattr(text, "set_%s" % key)(kws[key]) else: text = Text(**kws) if not text in axes.get_children(): axes.add_artist(text) elif text: try: text.remove() except: pass marker.__plot_text = text return text def plot_marker_line(project, marker, offset, base_y, axes): """ Plots a markers connector line using the given offset """ line = getattr(marker, "__plot_line", None) within_range = bool( project.axes_xlimit == 0 or (marker.position >= project.axes_xmin and marker.position <= project.axes_xmax) ) if marker.visible and within_range: # We need to strip away the units for comparison with # non-unitized bounds trans = transforms.blended_transform_factory(axes.transData, axes.transAxes) # Calculate top and bottom positions: ymin, ymax = axes.get_ybound() y = base_y y0 = (y - ymin) / (ymax - ymin) if marker.top == 0: # relative to base y1 = y0 + (marker.top_offset - ymin) / (ymax - ymin) elif marker.top == 1: # top of plot y1 = 1.0 # If style is 'offset', re-calculate positions accordingly style = marker.style if style == "offset": style = "solid" y0 = (offset - ymin) / (ymax - ymin) y1 = y0 + (marker.y_offset - ymin) / (ymax - ymin) data = [y0, y1] if line: line.set_xdata(np.array([marker.position, marker.position])) line.set_ydata(np.array(data)) line.set_transform(trans) line.set_color(marker.color) line.set_linestyle(style) else: line = matplotlib.lines.Line2D([marker.position, marker.position], data , transform=trans, color=marker.color, ls=style) line.y_isdata = False if not line in axes.get_lines(): axes.add_line(line) elif line: try: line.remove() except: pass marker.__plot_line = line def plot_markers(cc, project, specimen, marker_lbls, offset, scale, marker_scale, axes): """ Plots a specimens markers using the given offset and scale """ for marker in specimen.markers: base_y = 0 if marker.base == 1: base_y = specimen.experimental_pattern.get_plotted_y_at_x(marker.position) elif marker.base == 2: base_y = specimen.calculated_pattern.get_plotted_y_at_x(marker.position) elif marker.base == 3: base_y = min( specimen.experimental_pattern.get_plotted_y_at_x(marker.position), specimen.calculated_pattern.get_plotted_y_at_x(marker.position) ) elif marker.base == 4: base_y = max( specimen.experimental_pattern.get_plotted_y_at_x(marker.position), specimen.calculated_pattern.get_plotted_y_at_x(marker.position) ) plot_marker_line(project, marker, offset, base_y, axes) text = plot_marker_text(project, marker, offset, marker_scale, base_y, axes) if text is not None: cc.register_artist(text, marker) marker_lbls.append((text, marker.base == 0, marker.y_offset)) def plot_hatches(project, specimen, offset, scale, axes): """ Plots a specimens exclusion 'hatched' areas using the given offset and scale """ # calculate the Y limits y0 = offset y1 = offset + max(specimen.max_display_y * scale, 1.0) # these are easier to just remove for now, not too expensive leftborder, hatch, rightborder = getattr(specimen, "__plot_hatches_artists", (None, None, None)) if leftborder: try: leftborder.remove() except: pass if hatch: try: hatch.remove() except: pass if rightborder: try: rightborder.remove() except: pass # Create & add new hatches: for x0, x1 in zip(*specimen.exclusion_ranges.get_xy_data()): leftborder = axes.plot([x0, x0], [y0, y1], c=settings.EXCLUSION_LINES) axes.add_patch(Rectangle( (x0, y0), x1 - x0, y1 - y0, fill=True, hatch="/", linewidth=0, facecolor=settings.EXCLUSION_FOREG, edgecolor=settings.EXCLUSION_LINES) ) rightborder = axes.plot([x1, x1], [y0, y1], c=settings.EXCLUSION_LINES) def plot_label(specimen, labels, label_offset, plot_left, axes): text = getattr(specimen, "__plot_label_artist", None) # prevent empty $$ from causing an error: save_label = specimen.label.replace("$$", "") props = dict( text=save_label, x=plot_left - 0.05, y=label_offset, clip_on=False, horizontalalignment='right', verticalalignment='center', transform=transforms.blended_transform_factory(axes.get_figure().transFigure, axes.transData) ) if text: for key in props: getattr(text, "set_%s" % key)(props[key]) else: text = Text(**props) if not text in axes.get_children(): axes.add_artist(text) labels.append(text) specimen.__plot_label_artist = text def apply_transform(data, scale=1, offset=0, cap=0): data_x, data_y = data data_y = np.array(data_y) # make a copy if cap > 0: np.copyto(data_y, [cap], where=(data_y >= cap)) # copy the cap where values are larger then cap data_y = data_y * scale + offset # scale and offset the capped data return data_x, data_y def plot_pattern(pattern, axes, scale=1, offset=0, cap=0, z_data=[None], **kwargs): if len(z_data) > 1 and pattern.data_y.size > 0: # Update the 2d image colors = [mcolors.colorConverter.to_rgba(pattern.color, 0), mcolors.colorConverter.to_rgba(pattern.color, 1)] cmap = mcolors.LinearSegmentedColormap.from_list('mycmap', colors, N=100) angles = np.array(pattern.data_x, dtype=float) intensities = np.array(pattern.data_y[:,:len(z_data)], dtype=float) * scale + offset rh = np.array(list(map(float, z_data)), dtype=float) rh = rh * scale + offset cols = intensities.shape[0] rows = intensities.shape[1] X = np.ones(shape=(rows, cols), dtype=float) * angles #ANGLE Y = (np.ones(shape=(cols, rows), dtype=float) * rh).transpose() #RH Z = intensities.transpose() axes.pcolormesh(X,Y,Z, cmap=cmap, shading='flat', edgecolors='None', rasterized=False) else: # setup or update the line line = getattr_or_create(pattern, "__plot_line", (matplotlib.lines.Line2D, ([], []), {})) if kwargs: line.update(kwargs) line.update(dict( data=apply_transform(pattern.get_xy_data(), scale=scale, offset=offset, cap=cap), color=pattern.color, linewidth=pattern.lw, ls=getattr(pattern, "ls", "-"), marker=getattr(pattern, "marker", "") )) if not line in axes.get_lines(): axes.add_line(line) pattern.__plot_line = line def make_draggable(artist, drag_x_handler=None, drag_y_handler=None): if artist != None: draggable = getattr(artist, "__draggable", None) if draggable == None: draggable = DraggableMixin(artist, drag_x_handler, drag_y_handler) else: draggable.update(artist, drag_x_handler, drag_y_handler) artist.__draggable = draggable def plot_specimen(cc, project, specimen, labels, marker_lbls, label_offset, plot_left, offset, scale, marker_scale, axes): """ Plots a specimens patterns, markers and hatches using the given offset and scale """ # Plot the patterns; z_data = specimen.get_z_list() if specimen.display_experimental: pattern = specimen.experimental_pattern # plot the experimental pattern: plot_pattern(pattern, axes, scale=scale, offset=offset, cap=pattern.cap_value, z_data=z_data) #make_draggable(getattr(pattern, "__plot_line", None), drag_y_handler=specimen.on_pattern_dragged) # get some common data for the next lines: x_data, y_data = pattern.get_xy_data() xmin, xmax = (np.min(x_data), np.max(x_data)) if x_data.size > 0 else (0, 0) ymin, ymax = (np.min(y_data), np.max(y_data)) if y_data.size > 0 else (0, 0) ######################################################################## # plot the background pattern: bg_line = getattr_or_create(pattern, "__plot_bg_line", (matplotlib.lines.Line2D, ([], []), dict(c="#660099", lw="2", zorder=10))) if pattern.bg_type == 0 and pattern.bg_position != 0.0: bg_line.update(dict( data=apply_transform(([xmin, xmax], [pattern.bg_position, pattern.bg_position]), scale=scale, offset=offset), visible=True )) elif pattern.bg_type == 1 and pattern.bg_pattern is not None: bg_line.update(dict( data=apply_transform((x_data, (pattern.bg_pattern * pattern.bg_scale) + pattern.bg_position), scale=scale, offset=offset), visible=True )) else: bg_line.update(dict( data=([], []), visible=True )) if bg_line.get_visible() and not bg_line in axes.get_lines(): axes.add_line(bg_line) elif not bg_line.get_visible(): try: bg_line.remove() except: pass pattern.__plot_bg_line = bg_line ######################################################################## ######################################################################## # plot the smooth pattern: smooth_line = getattr_or_create(pattern, "__plot_smooth_line", (matplotlib.lines.Line2D, ([], []), dict(c="#660099", lw="2", zorder=10))) if int(pattern.smooth_degree) > 1: data = x_data, smooth(y_data, pattern.smooth_degree) else: data = [], [] smooth_line.update(dict( data=apply_transform(data, scale=scale, offset=offset), visible=bool(pattern.smooth_degree > 1) )) if smooth_line.get_visible() and not smooth_line in axes.get_lines(): axes.add_line(smooth_line) elif not smooth_line.get_visible(): try: smooth_line.remove() except: pass pattern.__plot_smooth_line = smooth_line ######################################################################## ######################################################################## # plot the noisified pattern: noise_line = getattr_or_create(pattern, "__plot_noise_line", (matplotlib.lines.Line2D, ([], []), dict(c="#660099", lw="2", zorder=10))) if pattern.noise_fraction > 0.0: data = x_data, add_noise(y_data, pattern.noise_fraction) else: data = [], [] noise_line.update(dict( data=apply_transform(data, scale=scale, offset=offset), visible=bool(pattern.noise_fraction > 0.0) )) if noise_line.get_visible() and not noise_line in axes.get_lines(): axes.add_line(noise_line) elif not noise_line.get_visible(): try: noise_line.remove() except: pass pattern.__plot_noise_line = noise_line ######################################################################## ######################################################################## # plot the shift & reference lines: shifted_line = getattr_or_create(pattern, "__plot_shifted_line", (matplotlib.lines.Line2D, ([], []), dict(c="#660099", lw="2", zorder=10))) reference_line = getattr_or_create(pattern, "__plot_reference_line", (matplotlib.lines.Line2D, ([], []), dict(c="#660099", lw="2", ls="--", zorder=10))) if pattern.shift_value != 0.0: shifted_line.update(dict( data=apply_transform((x_data - pattern._shift_value, y_data.copy()), scale=scale, offset=offset), visible=True )) position = specimen.goniometer.get_2t_from_nm(pattern.shift_position) reference_line.update(dict( data=apply_transform(([position, position], [0, ymax]), scale=scale, offset=offset), visible=True )) if not shifted_line in axes.get_lines(): axes.add_line(shifted_line) if not reference_line in axes.get_lines(): axes.add_line(reference_line) else: shifted_line.set_data([], []) shifted_line.set_visible(False) try: shifted_line.remove() except: pass reference_line.set_data([], []) reference_line.set_visible(False) try: reference_line.remove() except: pass pattern.__plot_shifted_line = shifted_line pattern.__plot_reference_line = reference_line ######################################################################## ######################################################################## # plot the pattern after peak stripping: stripped_line = getattr_or_create(pattern, "__plot_stripped_line", (matplotlib.lines.Line2D, ([], []), dict(c="#660099", lw="1", zorder=10))) if pattern.strip_startx != 0.0 and pattern.strip_endx != 0.0: strip_xdata, strip_ydata = pattern.stripped_pattern stripped_line.update(dict( data=apply_transform((strip_xdata.copy(), strip_ydata.copy()), scale=scale, offset=offset), visible=True )) if not stripped_line in axes.get_lines(): axes.add_line(stripped_line) else: stripped_line.set_data([], []) stripped_line.set_visible(False) try: stripped_line.remove() except: pass pattern.__plot_stripped_line = stripped_line ######################################################################## ######################################################################## # plot the pattern after peak stripping: artists = getattr(specimen, "__plot_peak_area", None) peak_area, peak_fwhm_line = artists if artists is not None else (None, None) if peak_area is not None and peak_area in axes.get_children(): peak_area.remove() if peak_fwhm_line is not None and peak_fwhm_line in axes.get_children(): peak_fwhm_line.remove() if pattern.peak_startx != 0.0 and pattern.peak_endx != 0.0 and pattern.peak_properties_pattern is not None: peak_xdata, peak_bg, peak_ydata, peak_rootsx, peak_rootsy = pattern.peak_properties_pattern _, peak_bg = apply_transform((peak_xdata.copy(), peak_bg.copy()), scale=scale, offset=offset) peak_xdata, peak_ydata = apply_transform((peak_xdata.copy(), peak_ydata.copy()), scale=scale, offset=offset) peak_rootsx, peak_rootsy = apply_transform((peak_rootsx.copy(), peak_rootsy.copy()), scale=scale, offset=offset) peak_area = axes.fill_between(peak_xdata, peak_bg, peak_ydata, interpolate=True, facecolor="#660099", zorder=10) peak_fwhm_line = axes.plot(peak_rootsx, peak_rootsy, color="#330033", zorder=11) setattr(specimen, "__plot_peak_area", (peak_area, peak_fwhm_line)) if specimen.display_calculated: pattern = specimen.calculated_pattern plot_pattern(pattern, axes, scale=scale, offset=offset, z_data=z_data) #if not specimen.display_experimental: # make_draggable(getattr(pattern, "__plot_line", None), drag_y_handler=specimen.on_pattern_dragged) # setup or update the calculated lines (phases) if specimen.display_phases: phase_lines = getattr(specimen, "__plot_phase_lines", []) # Clear previous phase lines: for phase_line in phase_lines: if phase_line in axes.get_lines(): axes.remove_line(phase_line) # Update & add phase lines: for i in range(2, pattern.num_columns): phase_data = pattern.get_xy_data(i) # Get the line object or create it: try: phase_line = phase_lines[i - 2] except IndexError: phase_line = matplotlib.lines.Line2D(*phase_data) phase_lines.append(phase_line) # Get the phase color or use a default color: try: phase_color = pattern.phase_colors[i - 2] except IndexError: phase_color = pattern.color # Update the line object properties: phase_line.update(dict( data=apply_transform(phase_data, scale=scale, offset=offset), color=phase_color, linewidth=pattern.lw )) # Add to axes: axes.add_line(phase_line) specimen.__plot_phase_lines = phase_lines # mineral preview sticks if hasattr(specimen, "mineral_preview") and specimen.mineral_preview is not None: name, peaks = specimen.mineral_preview lines = getattr(specimen, "__plot_mineral_preview", []) for line in lines: try: line.remove() except: pass lines = [] for position, intensity in peaks: position = specimen.goniometer.get_2t_from_nm(position / 10.) intensity /= 100. trans = transforms.blended_transform_factory(axes.transData, axes.transAxes) ymin, ymax = axes.get_ybound() style = "solid" color = "#FF00FF" y0 = (offset - ymin) / (ymax - ymin) y1 = y0 + (intensity - ymin) / (ymax - ymin) line = matplotlib.lines.Line2D( [position, position], [y0, y1], transform=trans, color=color, ls=style ) axes.add_line(line) lines.append(line) setattr(specimen, "__plot_mineral_preview", lines) # exclusion ranges; plot_hatches(project, specimen, offset, scale, axes) # markers; plot_markers(cc, project, specimen, marker_lbls, offset, scale, marker_scale, axes) # & label: plot_label(specimen, labels, label_offset, plot_left, axes) #make_draggable(getattr(specimen, "__plot_label_artist", None), drag_y_handler=project.on_label_dragged) def plot_statistics(project, specimen, spec_scale, stats_y_pos, stats_height, axes): # Scales & shifts the pattern so the zero line plots in the middle. def plot_pattern_middle(pattern, axes, height, vscale, offset, **kwargs): """ Height is the fraction of the plot reserved for the residual pattern vscale is a user scaling factor applied to the residual pattern offset is the offset of the residual pattern position from the x-axis of the plot """ # Offset to the middle of the available space: offset = offset + 0.5 * height # If the intensity difference is smaller then the space available, don't scale scale = spec_scale * 0.5 * vscale plot_pattern(pattern, axes, scale=scale, offset=offset, **kwargs) if specimen.display_residuals and specimen.statistics.residual_pattern is not None: plot_pattern_middle( specimen.statistics.residual_pattern, axes, height=stats_height, vscale=specimen.display_residual_scale, offset=stats_y_pos, alpha=0.75 ) if specimen.display_derivatives: for pattern in ( specimen.statistics.der_residual_pattern, specimen.statistics.der_exp_pattern, specimen.statistics.der_calc_pattern): if pattern is not None: plot_pattern_middle( pattern, axes, height=stats_height, vscale=specimen.display_residual_scale, offset=stats_y_pos, alpha=0.65 ) def plot_specimens(axes, pos_setup, cc, project, specimens): """ Plots multiple specimens within the context of a project """ base_offset = project.display_plot_offset base_height = 1.0 label_offset = project.display_label_pos scale, scale_unit = project.get_scale_factor() labels, marker_lbls = list(), list() current_y_pos = 0 lbl_y_offset = 0 group_counter = 0 # 'group by' specimen counter ylim = 0 # used to keep track of maximum y-value, for a tight y-axis for _, specimen in enumerate(specimens): spec_max_display_y = float(specimen.max_display_y) # single specimen normalization: if project.axes_ynormalize == 1: scale = (1.0 / spec_max_display_y) if spec_max_display_y != 0.0 else 1.0 spec_y_offset = specimen.display_vshift * scale_unit spec_y_pos = current_y_pos * scale_unit + spec_y_offset spec_alloc_height = base_height * scale_unit spec_reqst_height = spec_alloc_height * specimen.display_vscale lbl_y_offset = (label_offset + specimen.display_vshift) * scale_unit lbl_y_pos = current_y_pos * scale_unit + lbl_y_offset # For the y-limit we do not add the specimens vscale or vshift: ylim = current_y_pos * scale_unit + spec_alloc_height # Specimen scale = global scale, adjusted by specimen vscale spec_scale = scale * specimen.display_vscale # when statistics are plotted, # 65% of the height goes to the actual specimen plots # 35% goes to the statistics plot: if project.layout_mode == "FULL" and (specimen.display_residuals or specimen.display_derivatives): stats_y_pos = spec_y_pos stats_height = 0.35 * spec_reqst_height spec_y_pos = spec_y_pos + stats_height spec_scale = spec_scale * 0.65 plot_statistics( project, specimen, spec_scale, stats_y_pos, stats_height, axes ) plot_specimen( cc, project, specimen, labels, marker_lbls, lbl_y_pos, pos_setup.left, spec_y_pos, spec_scale, scale_unit, axes ) # increase offsets: group_counter += 1 if group_counter >= project.display_group_by: group_counter = 0 current_y_pos += base_offset axes.set_ylim(top=ylim) return labels, marker_lbls def plot_mixtures(axes, project, mixtures): legend = getattr(project, "__plot_mixture_legend", None) if legend: try: legend.remove() except ValueError: pass figure = axes.get_figure() trans = figure.transFigure def create_rect_patch(ec="#000000", fc=None): _box = AuxTransformBox(transforms.IdentityTransform()) rect = FancyBboxPatch( xy=(0, 0), width=0.02, height=0.02, boxstyle='square', ec=ec, fc=fc, mutation_scale=14, # font size transform=trans, alpha=1.0 if (ec is not None or fc is not None) else 0.0 ) _box.add_artist(rect) return _box legends = [] for mixture in mixtures: legend_items = [] # Add title: title = TextArea(mixture.name) title_children = [create_rect_patch(ec=None) for spec in mixture.specimens] title_children.insert(0, title) title_box = HPacker(children=title_children, align="center", pad=5, sep=3) legend_items.append(title_box) # Add phase labels & boxes for i, (phase, fraction) in enumerate(zip(mixture.phases, mixture.fractions)): label_text = "{}: {:>5.1f}".format(phase, fraction * 100.0) label = TextArea(label_text) phase_children = [ create_rect_patch(fc=phase.display_color) for phase in mixture.phase_matrix[:, i].flat if phase is not None ] phase_children.insert(0, label) legend_items.append( HPacker(children=phase_children, align="center", pad=0, sep=3) ) # Add created legend to the list: legends.append( VPacker(children=legend_items, align="right", pad=0, sep=3) ) # Only add this if there's something to add! if legends: # Pack legends & plot: legend = AnchoredOffsetbox( loc=1, pad=0.1, borderpad=0.1, frameon=False, child=VPacker(children=legends, align="right", pad=0, sep=5) ) axes.add_artist(legend) setattr(project, "__plot_mixture_legend", legend) PyXRD-0.8.4/pyxrd/generic/utils.py000066400000000000000000000011621363064711000167660ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import time from mvc.support.utils import not_none # @UnusedImport but keep it as an alias def u(string): if isinstance(string, bytes): return string.decode(errors='replace') else: return string def print_timing(func): def wrapper(*args, **kwargs): t1 = time.time() res = func(*args, **kwargs) t2 = time.time() print('%s took %0.3f ms' % (func.__name__, (t2 - t1) * 1000.0)) return res return wrapperPyXRD-0.8.4/pyxrd/generic/views/000077500000000000000000000000001363064711000164115ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/views/__init__.py000066400000000000000000000366761363064711000205440ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) from pkg_resources import resource_filename # @UnresolvedImport from warnings import warn import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk # @UnresolvedImport from mvc.view import View from mvc.adapters.gtk_support.widgets import ScaleEntry from pyxrd.data import settings from pyxrd.generic.mathtext_support import get_string_safe class BaseView(View): """ Basic view providing some common code TODO attribute docs! """ builder = "" modal = False resizable = True widget_format = "%s" container_format = "container_%s" # A mapping of layout states and actions to be taken when switching to them. # Actions should be of the form `action::widget_group`, where the action is one # of either hide or show and the widget_group is a key in the `widget_groups` # mapping, with the value being the list of widgets to apply the action on to. current_layout_state = "FULL" layout_state_actions = { 'FULL': [ 'show::full_mode_only' ], 'VIEWER': [ 'hide::full_mode_only' ] } widget_groups = { } def __init__(self, *args, **kwargs): super(BaseView, self).__init__(*args, **kwargs) self.parent = kwargs.get("parent", None) top = self.get_toplevel() if isinstance(top, Gtk.Window): top.set_resizable(self.resizable) top.set_modal(self.modal) if self.parent: self.set_layout_mode(self.parent.current_layout_state) def create_mathtext_widget(self, text, fallback_text=""): # TODO move these to a separate controller namespace module! try: from pyxrd.generic.mathtext_support import create_image_from_mathtext widget = create_image_from_mathtext(text) except: if fallback_text: text = fallback_text widget = Gtk.Label(label=text) widget.set_use_markup(True) widget.set_justify(Gtk.Justification.CENTER) return widget def _get_widget_container(self, prop): return self[self.container_format % prop.label] def _add_widget_to_container(self, widget, container): if container is not None: child = container.get_child() if child is not None: container.remove(child) container.add(widget) widget.show_all() return widget def add_widget(self, prop, *args, **kwargs): method = { str: self.add_entry_widget, float: self.add_scale_widget, }.get(prop.data_type, None) if method is not None: return method(prop, *args, **kwargs) def add_scale_widget(self, prop, enforce_range=True): # Create the widget: inp = ScaleEntry(prop.minimum, prop.maximum, enforce_range=enforce_range) inp.set_tooltip_text(prop.title) self[self.widget_format % prop.label] = inp # Add & return the widget return self._add_widget_to_container(inp, self._get_widget_container(prop)) def add_entry_widget(self, prop): # Create the widget: inp = Gtk.Entry() inp.set_tooltip_text(prop.title) self[self.widget_format % prop.label] = inp # Add & return the widget return self._add_widget_to_container(inp, self._get_widget_container(prop)) def create_input_table(self, table, props, num_columns = 1, widget_callbacks=[]): """ Places widgets (returned by the widget_callbacks) in the given table for each property in props. The widget groups (i.e. list of widgets returned by the callbacks for a single property) can be placed in a multi-column layout by increasing the num_columns value. """ # total number of properties num_props = len(props) # number of widgets in each column column_width = len(widget_callbacks) # 2D array of widgets (each row is a group of widgets returned for a property) widgets = [[None for _ in range(column_width)] for _ in range(num_props)] # The actual number of rows needed in the table num_table_rows = int(num_props / num_columns) # The actual number of columns needed in the table num_table_columns = num_columns*column_width # Resize the table widget accordingly table.resize(num_table_rows, num_table_columns) for i, prop in enumerate(props): # i = 0, 1, 2, 3, ... # Calculate the column: column_index = 0, cw, 2*cw, 3*cw, ... column_index = (i % num_columns) * column_width for widget_index in range(column_width): # Create the widget: widget = widget_callbacks[widget_index](prop) # Store it for the return value: widgets[i][widget_index] = widget # Calculate where to place the widget: column_offset = column_index + widget_index row_offset = int(i / num_columns) # Attach it to the table table.attach( widget, column_offset, column_offset + 1, row_offset, row_offset + 1, xpadding=2, ypadding=2 ) return widgets def set_layout_mode(self, state): self.current_layout_state = state for action in self.layout_state_actions.get(state, []): parts = tuple(action.split("::", 1)) command, group_name = parts widgets = [] if group_name != "all": widget_names = self.widget_groups.get(group_name, []) widgets = map(lambda name: self[name], widget_names) else: widgets = self._builder.get_objects() if command == "show": for widget in widgets: try: widget.set_no_show_all(False) widget.show_all() except AttributeError: pass elif command == "hide": for widget in widgets: try: widget.set_no_show_all(True) widget.set_visible(False) except AttributeError: pass else: raise ValueError("Unknown layout state command `%s`!" % command) def show_all(self, *args, **kwargs): self.show(*args, **kwargs) def present(self): toplevel = self.get_toplevel() toplevel.set_resizable(self.resizable) toplevel.set_modal(self.modal) toplevel.show_all() toplevel.present() self.show() def get_toplevel(self): for w in [self.top,] + list(self): try: return self[w].get_toplevel() except AttributeError: pass else: break # just for ref class TitleView(BaseView): """ Mix-in that provides title support for views. The class attribute 'title' can be set, if so, this class will attempt to set the title attribute upon initialization unless it is 'None'. """ title = None def __init__(self, *args, **kwargs): super(TitleView, self).__init__(*args, **kwargs) if self.title is not None: self.set_title(self.title) def set_title(self, title): self.title = title self.get_toplevel().set_title(title) pass # end of class class FormattedTitleView(TitleView): """ Mix-in that provides a formatted title support for views. The 'title_format' class attribute should be set to a string format containing a single string (%s) specifier. When set_title is called, only that part of the string is updated. """ title_format = "%s" title = "" def set_title(self, title, *args, **kwargs): self.title = title self.get_toplevel().set_title(self.title_format % self.title) pass # end of class class HasChildView(object): """ Mixin that provides a function to add childviews to containers """ def _add_child_view(self, new_child, container): child = container.get_child() if child is not None: container.remove(child) if isinstance(container, Gtk.ScrolledWindow) and not (type(new_child) in (Gtk.TextView, Gtk.TreeView, Gtk.IconView, Gtk.Viewport)): container.add_with_viewport(new_child) else: container.add(new_child) new_child.show_all() return new_child pass # end of class class DialogView(HasChildView, TitleView): """ Generalised view for editing stuff with an OK button """ builder = resource_filename(__name__, "glade/edit_dialog.glade") top = "window_edit_dialog" container_widget = "edit_child_box" # These should be overriden by the subclass: subview_builder = "" subview_toplevel = None def __init__(self, container_widget=None, subview_builder=None, subview_toplevel=None, *args, **kwargs): self.container_widget = container_widget or self.container_widget self.subview_builder = subview_builder or self.subview_builder self.subview_toplevel = subview_toplevel or self.subview_toplevel super(DialogView, self).__init__(*args, **kwargs) self._builder.add_from_file(self.subview_builder) self._add_child_view(self[self.subview_toplevel], self[self.container_widget]) return class ObjectListStoreViewMixin(HasChildView): edit_view = None edit_view_container = "vwp_edit_object" extra_widget_builder = None extra_widget_toplevel = "" treeview_widget = "edit_objects_treeview" @property def treeview(self): return self[self.treeview_widget] @property def load_label(self): return self["button_load_object"].get_label() @load_label.setter def load_label(self, value): self["button_load_object"].set_label(value) @property def save_label(self): return self["button_save_object"].get_label() @save_label.setter def save_label(self, value): self["button_save_object"].set_label(value) @property def extra_widget(self): return self.extra_widget_box.get_child() @extra_widget.setter def extra_widget(self, widget): child = self.extra_widget_box.get_child() if child: self.extra_widget_box.remove(child) if widget is not None: self["extra_box"].add(widget) _none_view = None @property def none_view(self): return NoneView() def set_selection_state(self, value): """ Sets the state of the view to correspond with the number of currently selected objects. Value is either None or a number indicating the number of selected objects. """ self["button_del_object"].set_sensitive(value is not None) self["button_save_object"].set_sensitive(value is not None) def __init__(self, edit_view_container=None, display_buttons=True, load_label=None, save_label=None, **kwargs): self.edit_view_container = edit_view_container or self.edit_view_container self.load_label = load_label or self.load_label self.save_label = save_label or self.save_label if not display_buttons: self["vbox_objects"].remove(self["table_data"]) self.extra_widget_box = self["extra_box"] if self.extra_widget_builder is not None: self._builder.add_from_file(self.extra_widget_builder) self.extra_widget = self._builder.get_object(self.extra_widget_toplevel) return _on_sr_id = None def on_size_requested(self, *args): sr = self.child_view.size_request() self[self.edit_view_container].set_size_request(sr.height + 20, -1) def set_edit_view(self, view): if self._on_sr_id is not None and self.child_view is not None: self.child_view.disconnect(self._on_sr_id) self.edit_view = view self.child_view = view.get_top_widget() self._add_child_view(self.child_view, self[self.edit_view_container]) if isinstance(self[self.edit_view_container], Gtk.ScrolledWindow): sr = self.child_view.get_size_request() self[self.edit_view_container].set_size_request(sr[0], -1) self[self.edit_view_container].set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self._on_sr_id = self.child_view.connect("size-allocate", self.on_size_requested) return self.edit_view class ObjectListStoreView(ObjectListStoreViewMixin, DialogView): """ Generalised view for editing objects inside an ObjectListStore (using a customisable child view) - Standalone version (inside a DialogView) """ subview_builder = resource_filename(__name__, "glade/object_store.glade") subview_toplevel = "edit_object_store" def __init__(self, edit_view_container=None, display_buttons=True, load_label=None, save_label=None, **kwargs): DialogView.__init__(self, **kwargs) ObjectListStoreViewMixin.__init__(self, edit_view_container=edit_view_container, display_buttons=display_buttons, load_label=load_label, save_label=save_label, **kwargs) class ChildObjectListStoreView(ObjectListStoreViewMixin, BaseView): """ Generalised view for editing objects inside an ObjectListStore (using a customisable child view) - Child version (to be embedded by a controller) """ edit_view_container = "frame_object_param" builder = resource_filename(__name__, "glade/object_store.glade") top = "edit_object_store" def __init__(self, edit_view_container=None, display_buttons=True, load_label=None, save_label=None, **kwargs): BaseView.__init__(self, **kwargs) ObjectListStoreViewMixin.__init__(self, edit_view_container=edit_view_container, display_buttons=display_buttons, load_label=load_label, save_label=save_label, **kwargs) self["frm_objects_tv"].set_size_request(150, 150) class InlineObjectListStoreView(BaseView): builder = resource_filename(__name__, "glade/inline_ols.glade") top = "edit_item" @property def treeview(self): return self['tvw_items'] @property def del_item_widget(self): return self['btn_del_item'] @property def add_item_widget(self): return self['btn_add_item'] @property def export_items_widget(self): return self['btn_export_item'] @property def import_items_widget(self): return self['btn_import_item'] @property def type_combobox_widget(self): return self['cmb_add_type'] class NoneView(BaseView): builder = resource_filename(__name__, "glade/none.glade") top = "lbl_caption" caption_widget = "lbl_caption" def __init__(self, label=None, **kwargs): BaseView.__init__(self, **kwargs) self._label = self[self.caption_widget] if label is not None: self.label = label _label = None @property def label(self): return self._label.get_label() @label.setter def label(self, value): self._label.set_label(value) PyXRD-0.8.4/pyxrd/generic/views/cell_renderer_tools.py000066400000000000000000000044651363064711000230210ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk renderer_map = { 'text': Gtk.CellRendererText, 'accel': Gtk.CellRendererAccel, 'combo': Gtk.CellRendererCombo, 'spin': Gtk.CellRendererSpin, 'pixbuf': Gtk.CellRendererPixbuf, 'progress': Gtk.CellRendererProgress, 'spinner': Gtk.CellRendererSpinner, 'toggle': Gtk.CellRendererToggle, } def get_default_renderer(type, **kwargs): """ Creates a CellRendere of type 'type' and sets any attributes passed with the key-word arguments. Underscores in variable names are replaced with dashes in the proces. """ rend = renderer_map.get(type, type)() for key, val in kwargs.items(): rend.set_property(key.replace("_", "-"), val) return rend def parse_callback(callback, reduce=True): """ Parses callbacks for CellRenderers: it splits the callback from its arguments if present. Additionally this method will not create singleton argument lists, but pass them as a single argument. Returns the callback and its argument(s) (or an empty tuple) """ args = tuple() try: callback, args = callback except TypeError as ValueError: pass #deconvolve things: if reduce and len(args) == 1: args = args[0] return callback, args def parse_kwargs(**kwargs): """ Parses key-word arguments. It checks for the presence of key-words ending with '_col', these are popped and stored in a seperate dictionary, as they are to be passed to the constructor of the actual column or combobox (attribute mappings) In addition it sets a number of default attributes for the CellRenderer. Returns a tuple containing a dict with the CellRenderer attributes and a dict with the TreeViewColumn attribute mappings """ kwargs["xalign"] = kwargs.get("xalign", 0.5) kwargs["yalign"] = kwargs.get("yalign", 0.5) col_attrs = dict() for key, value in dict(kwargs).items(): if key.endswith("_col"): col_attrs[key[:-4]] = value kwargs.pop(key) return kwargs, col_attrs PyXRD-0.8.4/pyxrd/generic/views/combobox_tools.py000066400000000000000000000014731363064711000220200ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .cell_renderer_tools import get_default_renderer, parse_callback, parse_kwargs def add_renderer_with_attrs(combo, col_attrs, rend): combo.pack_start(rend, True) for attr, val in col_attrs.items(): combo.add_attribute(rend, attr, val) def add_combo_text_column(combo, data_func=None, **kwargs): kwargs["xalign"] = kwargs.get("xalign", 0.0) kwargs, col_attrs = parse_kwargs(**kwargs) rend = get_default_renderer('text', **kwargs) add_renderer_with_attrs(combo, col_attrs, rend) if data_func!=None: callback, args = parse_callback(data_func) combo.set_cell_data_func(rend, callback, args) return rend PyXRD-0.8.4/pyxrd/generic/views/glade/000077500000000000000000000000001363064711000174655ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/views/glade/csv_import.glade000066400000000000000000000157671363064711000226700ustar00rootroot00000000000000 False 5 CSV File Import dialog CSVFileFilter False True False 2 True False end False True end 0 True True True False 3 2 10 True False Separator sign GTK_FILL 10 True False 0 Decimal sign 1 2 GTK_FILL 10 First row contains headers True True False False True 2 2 3 GTK_FILL 5 True True False False True True 1 2 True True False False True True 1 2 1 2 True False Advanced False False 5 2 PyXRD-0.8.4/pyxrd/generic/views/glade/edit_dialog.glade000066400000000000000000000056711363064711000227400ustar00rootroot00000000000000 True False 206-ok-2 False 12 Edit Dialog True dialog True False True False True True 0 True False False True 3 1 True False 1 0 Ok 75 True True True img_ok False True 2 PyXRD-0.8.4/pyxrd/generic/views/glade/inline_ols.glade000066400000000000000000000150011363064711000226130ustar00rootroot00000000000000 True False 2 2 True False 5 12 end True True False False end 0 Add True True True False img_add_item False False 1 Remove True False True True False img_del_item False False 2 1 2 True True never in True True True False vertical icons False 1 True False False Import items True 221-unshare True True True False False False Export items True 222-share True True 1 2 GTK_FILL True False 1 2 1 2 True False 190-circle-plus True False 192-circle-remove True False 359-file-export PyXRD-0.8.4/pyxrd/generic/views/glade/lines/000077500000000000000000000000001363064711000205775ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/generic/views/glade/lines/add_noise.glade000066400000000000000000000036751363064711000235350ustar00rootroot00000000000000 1 0.10000000000000001 0.050000000000000003 0.20000000000000001 320 True False 10 2 10 5 True False 1 Fraction GTK_FILL True True 0.5 True 3.7252902984619141e-09 noise_fraction 3 True 1 2 PyXRD-0.8.4/pyxrd/generic/views/glade/lines/background.glade000066400000000000000000000157711363064711000237270ustar00rootroot00000000000000 True False 2 5 5 True False 1 Background value GTK_FILL True True True False False 1 2 True False 3 2 6 6 True False 1 Pattern file GTK_FILL True False 1 Scale factor 1 2 GTK_FILL True False 1 Offset value 2 3 GTK_FILL True True True False False 1 2 2 3 True True True False False 1 2 1 2 True False 1 2 320 True False 10 3 2 10 5 True False 1 Type GTK_FILL True False 1 2 True False 2 1 2 True False 2 2 3 PyXRD-0.8.4/pyxrd/generic/views/glade/lines/calculated_props.glade000066400000000000000000000145521363064711000251300ustar00rootroot00000000000000 1 20 1 10 True False 4 2 True False True True #000000000000 1 2 GTK_FILL 10 Use default colour True True False 0 True GTK_FILL True True False False cal_lw 1 True True 1 2 1 2 10 Use default linewidth True True False 0 True 1 2 GTK_FILL Use default linestyle True True False 0 True 2 3 GTK_FILL Use default marker True True False 0 True 3 4 GTK_FILL True False 0 200 True False 1 2 2 3 GTK_FILL 10 True False 0 200 True False 1 2 3 4 GTK_FILL 10 PyXRD-0.8.4/pyxrd/generic/views/glade/lines/experimental_props.glade000066400000000000000000000204451363064711000255220ustar00rootroot00000000000000 1 20 1 10 True False 5 3 True False True True #000000000000 2 3 GTK_FILL 10 Use default colour True True False 0 True 2 GTK_FILL True True False False exp_lw 1 True True 2 3 1 2 10 Use default linewidth True True False 0 True 2 1 2 GTK_FILL True False 0 True True 6 False False 2 3 4 5 GTK_FILL 10 True False 0 6 Cut-off value [counts] True 2 4 5 GTK_FILL Use default linestyle True True False 0 True 2 2 3 GTK_FILL Use default marker True True False 0 True 2 3 4 GTK_FILL True False 0 200 True False 2 3 2 3 GTK_FILL 10 True False 0 200 True False 2 3 3 4 GTK_FILL 10 PyXRD-0.8.4/pyxrd/generic/views/glade/lines/peak_properties.glade000066400000000000000000000162721363064711000250010ustar00rootroot00000000000000 True False 090-eyedropper True False 090-eyedropper 320 True False 10 4 3 10 5 True False Start position 1 GTK_FILL True True False False 1 2 50 True True True True Select the position directly on the pattern Select the position directly on the pattern img_sample_graph2 right 2 3 True False End position 1 1 2 GTK_FILL True True False False 1 2 1 2 50 True True True True Select the position directly on the pattern Select the position directly on the pattern img_sample_graph1 right 2 3 1 2 True False 0.0 0 1 3 2 3 GTK_FILL True False Peak area: 1 2 3 GTK_FILL True False FWHM [°2T]: 1 3 4 GTK_FILL True False 0.0 0 1 3 3 4 GTK_FILL PyXRD-0.8.4/pyxrd/generic/views/glade/lines/shifting.glade000066400000000000000000000060121363064711000234070ustar00rootroot00000000000000 320 True False 10 4 2 10 5 True False 1 Position GTK_FILL True False 1 Value 2 3 GTK_FILL True True True False False 1 2 2 3 True False 1 2 True False 2 1 2 PyXRD-0.8.4/pyxrd/generic/views/glade/lines/smoothing.glade000066400000000000000000000064511363064711000236120ustar00rootroot00000000000000 1 600 3 1 10 320 True False 10 3 2 10 5 True False 1 Type GTK_FILL True False 1 Window size 2 3 GTK_FILL True False 1 2 True False 2 1 2 True True 0.5 3.7252902984619141e-09 smooth_degree True True 1 2 2 3 PyXRD-0.8.4/pyxrd/generic/views/glade/lines/strip_peak.glade000066400000000000000000000145731363064711000237500ustar00rootroot00000000000000 True False 090-eyedropper True False 090-eyedropper 320 True False 10 4 3 10 5 True False 1 Start position GTK_FILL True True True False False 1 2 50 True True True True Select the position directly on the pattern Select the position directly on the pattern img_sample_graph2 right 2 3 True False 1 End position 1 2 GTK_FILL True True True False False 1 2 1 2 50 True True True True Select the position directly on the pattern Select the position directly on the pattern img_sample_graph1 right 2 3 1 2 True False 1 Noise level 2 3 GTK_FILL True True True False False 1 3 2 3 PyXRD-0.8.4/pyxrd/generic/views/glade/none.glade000066400000000000000000000010741363064711000214240ustar00rootroot00000000000000 300 300 True False No object selected. PyXRD-0.8.4/pyxrd/generic/views/glade/object_store.glade000066400000000000000000000224071363064711000231520ustar00rootroot00000000000000 True False 190-circle-plus True False 192-circle-remove True False 358-file-import True False 359-file-export True True 200 True True False 6 150 360 True False 0 none True False 6 True True never in True True True True False 0 <b>Objects</b> True False False 0 True False 2 2 6 6 Remove True True True img_del 1 2 GTK_FILL GTK_FILL Add True True True img_add GTK_FILL Export True False True True img_export 1 2 1 2 GTK_FILL Import True True True img_import True 1 2 GTK_FILL False True 1 True False False True 2 False False True False 0 none True True 5 never True False <b>Properties</b> True True False PyXRD-0.8.4/pyxrd/generic/views/line_views.py000066400000000000000000000055051363064711000211340ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from pyxrd.generic.views import DialogView, BaseView class CalculatedLinePropertiesView(BaseView): builder = resource_filename(__name__, "glade/lines/calculated_props.glade") top = "cal_line_props" widget_format = "cal_%s" widget_groups = { 'full_mode_only': [ "cal_line_props" ] } pass # end of class class ExperimentalLinePropertiesView(BaseView): builder = resource_filename(__name__, "glade/lines/experimental_props.glade") top = "exp_line_props" widget_format = "exp_%s" pass # end of class class BackgroundView(DialogView): title = "Remove Background" subview_builder = resource_filename(__name__, "glade/lines/background.glade") subview_toplevel = "edit_background" modal = True resizable = False def_bg_view = "bg_linear" bg_view_cont = "bg_view_container" def select_bg_view(self, bg_view=None): if bg_view is not None: bg_view = "bg_%s" % bg_view else: bg_view = self.def_bg_view self._add_child_view(self[bg_view], self[self.bg_view_cont]) def set_file_dialog(self, dialog, callback): fcb_bg_pattern = Gtk.FileChooserButton(dialog) fcb_bg_pattern.connect("file-set", callback, dialog) self["fcb_bg_container"].add(fcb_bg_pattern) pass #end of class class AddNoiseView(DialogView): title = "Add Noise" subview_builder = resource_filename(__name__, "glade/lines/add_noise.glade") subview_toplevel = "add_noise" modal = True resizable = False pass # end of class class SmoothDataView(DialogView): title = "Smooth Data" subview_builder = resource_filename(__name__, "glade/lines/smoothing.glade") subview_toplevel = "smooth_data" modal = True resizable = False pass # end of class class ShiftDataView(DialogView): title = "Shift Pattern" subview_builder = resource_filename(__name__, "glade/lines/shifting.glade") subview_toplevel = "shift_pattern" modal = True resizable = False pass # end of class class StripPeakView(DialogView): title = "Strip Peak" subview_builder = resource_filename(__name__, "glade/lines/strip_peak.glade") subview_toplevel = "strip_peak" modal = True resizable = False pass # end of class class CalculatePeakPropertiesView(DialogView): title = "Calculate Peak Properties" subview_builder = resource_filename(__name__, "glade/lines/peak_properties.glade") subview_toplevel = "peak_properties" modal = True resizable = False pass # end of class PyXRD-0.8.4/pyxrd/generic/views/treeview_tools.py000066400000000000000000000250521363064711000220410ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk sel_modes = { 'NONE': Gtk.SelectionMode.NONE, 'SINGLE': Gtk.SelectionMode.SINGLE, 'BROWSE': Gtk.SelectionMode.BROWSE, 'MULTIPLE': Gtk.SelectionMode.MULTIPLE } sort_types = { 'ASCENDING': Gtk.SortType.ASCENDING, 'DESCENDING': Gtk.SortType.DESCENDING } from .cell_renderer_tools import get_default_renderer, parse_callback, parse_kwargs class PyXRDTreeViewColumn(Gtk.TreeViewColumn): """ A custom TreeViewColumn that stores information about its attribute mappings and provides acces to them with the get_col_attr function. """ def __init__(self, title=None, cell_renderer=None, **kwargs): Gtk.TreeViewColumn.__init__(self, title, cell_renderer) self._attrs = dict() self.set_attributes(cell_renderer, **kwargs) def set_attributes(self, cell_renderer, **kwargs): for key, val in kwargs.items(): self._attrs[key] = val Gtk.TreeViewColumn.set_attributes(self, cell_renderer, **kwargs) def add_attribute(self, cell_renderer, attribute, column): self._attrs[attribute] = column Gtk.TreeViewColumn.set_attributes(self, cell_renderer, attribute, column) def clear_attributes(self, cell_renderer): self._attrs = dict() Gtk.TreeViewColumn.clear_attributes(self, cell_renderer) def get_col_attr(self, attr): return self._attrs.get(attr, -1) def _get_default_column(title, rend, data_func=None, spacing=0, visible=True, resizable=True, sizing=1, fixed_width=-1, min_width=-1, max_width=-1, expand=True, clickable=False, alignment=0.0, reorderable=False, sort_column_id=-1, sort_indicator=False, sort_order='ASCENDING', col_attrs={}): """ Creates a PyXRDTreeViewColumn using the arguments passed. Column attribute mappings are to be passed as a single dict, not as key-word arguments. """ try: sort_order = sort_types[sort_order] except KeyError as err: raise ValueError("Invalid value '%s' for sort order!" % sort_order) from err col = PyXRDTreeViewColumn(title, rend, **col_attrs) if data_func is not None: callback, args = parse_callback(data_func) col.set_cell_data_func(rend, callback, args) col.set_spacing(spacing) col.set_visible(visible) col.set_resizable(resizable) col.set_sizing(sizing) if fixed_width >= 0: col.set_sizing(Gtk.TreeViewColumnSizing.Fixed) col.set_fixed_width(fixed_width) else: col.set_sizing(Gtk.TreeViewColumnSizing.GROW_ONLY) col.set_min_width(min_width) col.set_max_width(max_width) col.set_title(title) col.set_expand(expand) col.set_clickable(clickable) col.set_alignment(alignment) col.set_reorderable(reorderable) col.set_sort_column_id(sort_column_id) col.set_sort_indicator(sort_indicator) col.set_sort_order(sort_order) col.set_resizable(resizable) col.set_expand(expand) col.set_alignment(alignment) return col def new_text_column(title, edited_callback=None, data_func=None, spacing=0, visible=True, resizable=True, sizing=1, fixed_width=-1, min_width=-1, max_width=-1, expand=True, clickable=False, alignment=None, reorderable=False, sort_column_id=-1, sort_indicator=False, sort_order='ASCENDING', **kwargs): """ Creates a TreeViewColumn packed with a CellRendererText . """ kwargs, col_attrs = parse_kwargs(**kwargs) alignment = alignment if alignment is not None else kwargs["xalign"] rend = get_default_renderer(Gtk.CellRendererText, **kwargs) if edited_callback is not None: callback, args = parse_callback(edited_callback, reduce=False) rend.connect('edited', callback, *args) col = _get_default_column( title, rend, data_func=data_func, spacing=spacing, visible=visible, resizable=resizable, sizing=sizing, fixed_width=fixed_width, min_width=min_width, max_width=max_width, expand=expand, clickable=clickable, alignment=alignment, reorderable=reorderable, sort_column_id=sort_column_id, sort_indicator=sort_indicator, sort_order=sort_order, col_attrs=col_attrs) return col def new_pb_column(title, data_func=None, spacing=0, visible=True, resizable=True, sizing=1, fixed_width=-1, min_width=-1, max_width=-1, expand=True, clickable=False, alignment=None, reorderable=False, sort_column_id=-1, sort_indicator=False, sort_order='ASCENDING', **kwargs): """ Creates a TreeViewColumn packed with a CellRendererPixbuf. """ kwargs, col_attrs = parse_kwargs(**kwargs) alignment = alignment if alignment is not None else kwargs["xalign"] rend = get_default_renderer(Gtk.CellRendererPixbuf, **kwargs) col = _get_default_column( title, rend, data_func=data_func, spacing=spacing, visible=visible, resizable=resizable, sizing=sizing, fixed_width=fixed_width, min_width=min_width, max_width=max_width, expand=expand, clickable=clickable, alignment=alignment, reorderable=reorderable, sort_column_id=sort_column_id, sort_indicator=sort_indicator, sort_order=sort_order, col_attrs=col_attrs) return col def new_toggle_column(title, data_func=None, toggled_callback=None, spacing=0, visible=True, resizable=True, sizing=1, fixed_width=-1, min_width=-1, max_width=-1, expand=True, clickable=False, alignment=None, reorderable=False, sort_column_id=-1, sort_indicator=False, sort_order='ASCENDING', **kwargs): """ Creates a TreeViewColumn packed with a CellRendererToggle. """ kwargs, col_attrs = parse_kwargs(**kwargs) alignment = alignment if alignment is not None else kwargs["xalign"] rend = get_default_renderer(Gtk.CellRendererToggle, **kwargs) if toggled_callback is not None: callback, args = parse_callback(toggled_callback, reduce=False) rend.connect('toggled', callback, *args) col = _get_default_column( title, rend, data_func=data_func, spacing=spacing, visible=visible, resizable=resizable, sizing=sizing, fixed_width=fixed_width, min_width=min_width, max_width=max_width, expand=expand, clickable=clickable, alignment=alignment, reorderable=reorderable, sort_column_id=sort_column_id, sort_indicator=sort_indicator, sort_order=sort_order, col_attrs=col_attrs) return col def new_combo_column(title, data_func=None, changed_callback=None, edited_callback=None, editing_started_callback=None, editing_canceled_callback=None, spacing=0, visible=True, resizable=True, sizing=1, fixed_width=-1, min_width=-1, max_width=-1, expand=True, clickable=False, alignment=None, reorderable=False, sort_column_id=-1, sort_indicator=False, sort_order='ASCENDING', **kwargs): """ Creates a TreeViewColumn packed with a CellRendererCombo. """ kwargs, col_attrs = parse_kwargs(**kwargs) alignment = alignment if alignment is not None else kwargs["xalign"] rend = get_default_renderer(Gtk.CellRendererCombo, **kwargs) if changed_callback is not None: callback, args = parse_callback(changed_callback, reduce=False) rend.connect('changed', callback, *args) if edited_callback is not None: callback, args = parse_callback(edited_callback, reduce=False) rend.connect('edited', callback, *args) if editing_started_callback is not None: callback, args = parse_callback(editing_started_callback, reduce=False) rend.connect('editing-started', callback, *args) if editing_canceled_callback is not None: callback, args = parse_callback(editing_canceled_callback, reduce=False) rend.connect('editing-canceled', callback, *args) col = _get_default_column( title, rend, data_func=data_func, spacing=spacing, visible=visible, resizable=resizable, sizing=sizing, fixed_width=fixed_width, min_width=min_width, max_width=max_width, expand=expand, clickable=clickable, alignment=alignment, reorderable=reorderable, sort_column_id=sort_column_id, sort_indicator=sort_indicator, sort_order=sort_order, col_attrs=col_attrs) return col def create_float_data_func(attribute='text', fmt="%.5f", invalid="#NA#"): """ Creates a data function that can be used to render floats as formatted strings, with detection of invalid values (e.g. None) """ def float_renderer(column, cell, model, itr, args=None): nr = model.get_value(itr, column.get_col_attr(attribute)) try: cell.set_property('text', fmt % nr) except: cell.set_property('text', invalid) return float_renderer def reset_columns(tv): """ Remove all columns from the treeview """ for col in tv.get_columns(): tv.remove_column(col) def setup_treeview(tv, model, reset=False, on_cursor_changed=None, on_selection_changed=None, sel_mode='SINGLE'): """ Sets up a treeview (signal connection, sets selection mode). """ try: sel_mode = sel_modes[sel_mode] except KeyError as err: raise ValueError("Invalid value '%s' for selection mode!" % sel_mode) from err if reset: reset_columns(tv) sel = tv.get_selection() sel.set_mode(sel_mode) ids = () if on_cursor_changed is not None: ids += (tv.connect('cursor_changed', on_cursor_changed),) if on_selection_changed is not None: ids += (sel.connect('changed', on_selection_changed),) return ids PyXRD-0.8.4/pyxrd/generic/views/validators.py000066400000000000000000000042661363064711000211430ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from mvc.support.gui_loop import add_idle_call class FloatEntryValidator: def __init__(self, entry): self.last_valid_val = 0 self.has_valid_val = True self.entry = entry self.entry.connect("activate", self.entry_activate) self.entry.connect("focus_out_event", self.entry_focus_out) self.insert_handlerid = self.entry.connect("insert-text", self.entry_insert_text) self.delete_handlerid = self.entry.connect("delete-text", self.entry_delete_text) def validate(self, text=None, reset_if_invalid=False): text = text or self.entry.get_chars(0, -1) try: self.last_valid_val = float(text) self.has_valid_val = True except Exception as e: self.has_valid_val = False if reset_if_invalid and not self.has_valid_val: self.entry.handler_block(self.insert_handlerid) self.entry.set_text("%f" % self.last_valid_val) self.has_valid_val = True self.entry.handler_unblock(self.insert_handlerid) def entry_activate(self, entry): self.validate(reset_if_invalid=True) def entry_focus_out(self, entry, event): self.validate(reset_if_invalid=True) return False def entry_insert_text(self, entry, new_text, new_text_length, position): self.entry.stop_emission('insert-text') self.entry.handler_block(self.insert_handlerid) pos = self.entry.get_position() text = self.entry.get_chars(0, -1) old_text = text text = text[:pos] + new_text + text[pos:] self.validate(text) if self.has_valid_val: new_text = text self.entry.set_text(new_text) add_idle_call(lambda: self.entry.set_position(pos + (len(new_text) - len(old_text)))) #while Gtk.events_pending(): # Gtk.main_iteration(False) self.entry.handler_unblock(self.insert_handlerid) def entry_delete_text(self, entry, start, end): self.validate() PyXRD-0.8.4/pyxrd/goniometer/000077500000000000000000000000001363064711000160105ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/goniometer/__init__.py000066400000000000000000000000001363064711000201070ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/goniometer/controllers.py000066400000000000000000000245421363064711000207370ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os, locale import logging logger = logging.getLogger(__name__) from mvc.adapters.gtk_support.treemodels.utils import create_treestore_from_directory from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory from pyxrd.generic.views.cell_renderer_tools import get_default_renderer from pyxrd.file_parsers.wld_parsers import wld_parsers from pyxrd.file_parsers.goniometer_parsers import goniometer_parsers from pyxrd.data import settings from pyxrd.generic.controllers import BaseController from pyxrd.generic.controllers.dialog_controller import DialogController from pyxrd.generic.controllers.objectliststore_controllers import TreeViewMixin from pyxrd.generic.views.treeview_tools import setup_treeview, new_text_column from pyxrd.goniometer.views import WavelengthDistributionView class InlineGoniometerController(BaseController): """ Goniometer controller. Is not expected to be used with a dialog view, but rather in another view. """ auto_adapt_excluded = [ 'wavelength_distribution', ] # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def register_view(self, view): self.generate_import_combo() self.wld_view = WavelengthDistributionView() self.wld_ctrl = WavelengthDistributionController(model=self.model, view=self.wld_view, parent=self) self.update_soller_sensitivity() self.update_divergence_label() self.update_absorption_sensitivity() def update_soller_sensitivity(self): self.view["gonio_soller1_spb"].set_sensitive(self.model.has_soller1) self.view["gonio_soller2_spb"].set_sensitive(self.model.has_soller2) def update_divergence_label(self): if self.model.divergence_mode == "AUTOMATIC": self.view["unit_divergence_lbl"].set_markup("[mm]") elif self.model.divergence_mode == "FIXED": self.view["unit_divergence_lbl"].set_markup("[°]") def update_absorption_sensitivity(self): self.view["sample_surf_density_spb"].set_sensitive(self.model.has_absorption_correction) self.view["absorption_spb"].set_sensitive(self.model.has_absorption_correction) def generate_import_combo(self): # TODO seperate this more the gtk level... self.view.import_combo_box.clear() path = settings.DATA_REG.get_directory_path("DEFAULT_GONIOS") cmb_model = create_treestore_from_directory(path) self.view.import_combo_box.set_model(cmb_model) cell = get_default_renderer('text') self.view.import_combo_box.pack_start(cell, True) self.view.import_combo_box.add_attribute(cell, 'text', 0) self.view.import_combo_box.add_attribute(cell, 'sensitive', 2) # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_btn_export_gonio_clicked(self, widget, *args): def on_accept(dialog): dialog.parser.write(self.model, dialog.filename) self.generate_import_combo() DialogFactory.get_save_dialog( title="Select the goniometer setup file to save to", filters=goniometer_parsers.get_export_file_filters(), current_folder=settings.DATA_REG.get_directory_path("DEFAULT_GONIOS"), parent=self.view.parent.get_top_widget() ).run(on_accept) def on_cmb_import_gonio_changed(self, combobox, *args): model = combobox.get_model() itr = combobox.get_active_iter() if itr: # first column is the name, second column the path and third column # a flag indicating if this can be selected path = model.get_value(itr, 1) if path: def on_accept(dialog): self.model.reset_from_file(path) DialogFactory.get_confirmation_dialog( "Are you sure?\nYou will loose the current settings!", parent=self.view.get_toplevel() ).run(on_accept) combobox.set_active(-1) # deselect def on_btn_edit_wld_clicked(self, widget, *args): self.wld_view.show_all() # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @BaseController.observe("has_soller1", assign=True, after=True) def notif_has_soller1(self, model, prop_name, info): self.update_soller_sensitivity() @BaseController.observe("has_soller2", assign=True, after=True) def notif_has_soller2(self, model, prop_name, info): self.update_soller_sensitivity() @BaseController.observe("divergence_mode", assign=True, after=True) def notif_divergence(self, model, prop_name, info): self.update_divergence_label() @BaseController.observe("has_absorption_correction", assign=True, after=True) def notif_absorption(self, model, prop_name, info): self.update_absorption_sensitivity() pass # end of class class WavelengthDistributionController(DialogController, TreeViewMixin): """ Wavelength distribution controller. """ auto_adapt_included = [ "wavelength_distribution", ] widget_handlers = { 'custom': 'custom_handler', } # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ @staticmethod def custom_handler(self, intel, widget): print("CUSTOM HANDLER CALLED FOR %s" % intel.name) # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def setup_wavelength_distribution_tree_view(self, store, widget): """ Creates the wavelength distribution TreeView layout and behavior """ setup_treeview(widget, store, on_cursor_changed=self.on_wld_tv_cursor_changed, sel_mode='MULTIPLE') widget.set_model(store) def data_func(col, cell, model, iter, colnr): cell.set_property("text", "%g" % model.get(iter, colnr)[0]) # X Column: widget.append_column(new_text_column( 'Wavelength (nm)', text_col=store.c_x, editable=True, data_func = (data_func, (store.c_x,)), edited_callback=(self.on_xy_data_cell_edited, (self.model.wavelength_distribution, 0)))) # Y Column: widget.append_column(new_text_column( 'Fraction', text_col=store.c_y, editable=True, data_func = (data_func, (store.c_y,)), edited_callback=(self.on_xy_data_cell_edited, (self.model.wavelength_distribution, 1)))) # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_xy_data_cell_edited(self, cell, path, new_text, model, col): try: value = float(locale.atof(new_text)) except ValueError: logger.exception("ValueError: Invalid literal for float(): '%s'" % new_text) else: model.set_value(int(path), col, value) return True def on_wld_tv_cursor_changed(self, tv): path, col = tv.get_cursor() # @UnusedVariable self.view["btn_del_wavelength_distribution"].set_sensitive(path is not None) return True def on_add_wavelength_distribution_clicked(self, widget): self.model.wavelength_distribution.append(0, 0) return True def on_del_wavelength_distribution_clicked(self, widget): paths = self.get_selected_paths(self.view["wld_wavelength_distribution"]) if paths is not None: self.model.wavelength_distribution.remove_from_indeces(*paths) return True def on_import_wavelength_distribution_clicked(self, widget, data=None): def on_confirm(dialog): def on_accept(dialog): filename = dialog.filename parser = dialog.parser message = "An unexpected error has occurred when trying to parse %s:\n\n" % os.path.basename(filename) message += "{}\n\n" message += "This is most likely caused by an invalid or unsupported file format." with DialogFactory.error_dialog_handler(message, parent=self.view.get_toplevel(), reraise=False): self.model.wavelength_distribution.load_data(parser, filename, clear=True) DialogFactory.get_load_dialog( title="Import wavelength distribution", parent=self.view.get_top_widget(), filters=wld_parsers.get_import_file_filters(), current_folder=settings.DATA_REG.get_directory_path("DEFAULT_WL_DISTR") ).run(on_accept) DialogFactory.get_confirmation_dialog( "Importing a wavelength distribution will erase all current data.\nAre you sure you want to continue?", parent=self.view.get_top_widget() ).run(on_confirm) def on_export_wavelength_distribution_clicked(self, widget, data=None): def on_accept(dialog): filename = dialog.filename parser = dialog.parser message = "An unexpected error has occurred when trying to save '%s'.\n" % os.path.basename(filename) message += "Contact the developer about this!" with DialogFactory.error_dialog_handler(message, parent=self.view.get_toplevel(), reraise=False): header = "%s, %s" % ("Wavelength", "Factor") self.model.wavelength_distribution.save_data(parser, filename, header=header) DialogFactory.get_save_dialog( "Select file for wavelength distribution export", parent=self.view.get_top_widget(), filters=wld_parsers.get_export_file_filters(), current_folder=settings.DATA_REG.get_directory_path("DEFAULT_WL_DISTR") ).run(on_accept) pass # end of class PyXRD-0.8.4/pyxrd/goniometer/glade/000077500000000000000000000000001363064711000170645ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/goniometer/glade/goniometer.glade000066400000000000000000001467021363064711000222440ustar00rootroot00000000000000 10000 45 0.10000000000000001 0.5 100 1 10 100 50 1 10 90 1 10 160 3 1 10 10000 1 10 100 1.5 1 10 10000 78.079999999999998 1 10 10000 10 1 10 100 2.5 1 10 100 2.5 1 10 1 5000 3500 1 10 True False gtk-floppy True False gtk-floppy True False 2 True False 2 True True False 50 True False 5 3 12 6 150 True False Radius <i>[cm]</i> True 1 1 2 GTK_FILL True False Minimal 2θ <i>[°]</i> True 1 2 3 GTK_FILL True False Maximal 2θ <i>[°]</i> True 1 3 4 GTK_FILL True False 2θ Steps True 1 4 5 GTK_FILL True False 0 0 True True 2500 False False gonio_steps True True if-valid 2500 1 2 4 5 GTK_FILL GTK_FILL True False <span size="large"><b>General information</b></span> True 0 3 True False <i>[°]</i> True 0 2 3 2 3 GTK_FILL GTK_FILL True False <i>[°]</i> True 0 2 3 3 4 GTK_FILL GTK_FILL True False <i>[cm]</i> True 0 2 3 1 2 GTK_FILL GTK_FILL True False True 0 2 3 4 5 GTK_FILL GTK_FILL 175 True True 0.10 gonio_min_2theta 2 True 0.10000000000000001 1 2 2 3 GTK_FILL 175 True True 0.10 gonio_max_2theta 2 True 0.10000000000000001 1 2 3 4 GTK_FILL 175 True True 0.00 gonio_radius 2 True 1 2 1 2 False True 0 True False 5 3 12 6 True False <span size="large"><b>Sample information</b></span> True 0 3 150 True False Sample length True 1 1 2 GTK_FILL True False <i>[cm]</i> True 0 2 3 1 2 GTK_FILL GTK_FILL 175 True True 0.00 gonio_sample_length 3 True 1 2 1 2 GTK_FILL 175 True False True 1.50 gonio_sample_surf_density 3 True 1.5 1 2 3 4 GTK_FILL True False <i>[mg/cm²]</i> True 0 2 3 3 4 GTK_FILL GTK_FILL True False Surface density True right 1 3 4 GTK_FILL Absorption correction: True True False 1 True 2 2 3 GTK_FILL True False True 0 2 3 2 3 GTK_FILL GTK_FILL True False <i>[cm²/g]</i> True 0 2 3 4 5 GTK_FILL GTK_FILL 175 True False True 0.000 gonio_absorption 3 True 1 2 4 5 GTK_FILL True False Mass attenuation True right 1 4 5 GTK_FILL False True 1 False True 5 1 True False 50 True False 5 3 12 6 150 True False Wavelength (λ) True 1 1 2 GTK_FILL True False Divergence mode True 1 2 3 GTK_FILL True False 0 0 Edit emmision spectrum True True True Stores the current goniometer setup as a default. 1 3 1 2 GTK_FILL GTK_FILL True False <i>[°]</i> True 0 2 3 4 5 GTK_FILL GTK_FILL Soller 1 True True False 0 True 4 5 GTK_FILL GTK_FILL 175 True True 0.1000 gonio_divergence 4 True 0.10000000000000001 1 2 3 4 GTK_FILL True False <i>[°]</i> True 0 2 3 3 4 GTK_FILL GTK_FILL 200 True False True False 1 3 2 3 GTK_FILL True False Value True 1 3 4 GTK_FILL 175 True False True 0.100 gonio_soller1 3 True 0.10000000000000001 1 2 4 5 GTK_FILL True False <span size="large"><b>Primary beam information</b></span> True 0 3 False True 0 True False 3 3 12 6 150 True False The Bragg angle of the monochromator crystal. Zet to zero if not present, use 28.44 for silicon and 26.53 for carbon crystals. Monochromator 2θ True 1 1 2 GTK_FILL GTK_FILL Soller 2 True True False 1 True 2 3 GTK_FILL GTK_FILL 175 True False True 0.100 gonio_soller2 0.029999999999999999 3 True 0.10000000000000001 1 2 2 3 GTK_FILL True False <i>[°]</i> True 0 2 3 2 3 GTK_FILL GTK_FILL 175 True True 0.100 gonio_mcr_2theta 3 True 0.10000000000000001 1 2 1 2 GTK_FILL True False <i>[°]</i> True 0 2 3 1 2 GTK_FILL GTK_FILL True False <span size="large"><b>Secondary beam information</b></span> True 0 3 False True 1 False True 5 2 True True 0 True False 2 Store setup True True True Stores the current goniometer setup as a default. 5 img_export2 False True 10 end 0 200 True False True False False True end 1 True False Load setup: True 1 False True 10 end 2 False True 5 end 1 True False False True 6 end 2 PyXRD-0.8.4/pyxrd/goniometer/glade/wavelength_distribution.glade000066400000000000000000000170371363064711000250350ustar00rootroot00000000000000 350 True False 12 6 True True in True True True True 0 True False Add True True True img_add_exp_data False False 0 True True True True False True False 359-file-export False True 0 True False _Export True True True 1 False False 1 Remove True True True img_del_exp_data False False 20 end 1 True True True True True False True False 358-file-import False True 0 True False _Import True True True 1 False False 2 False True 1 True False 190-circle-plus True False 191-circle-minus PyXRD-0.8.4/pyxrd/goniometer/models.py000066400000000000000000000343611363064711000176540ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from math import radians import numpy as np from mvc.models.properties import ( LabeledProperty, FloatProperty, IntegerProperty, BoolProperty, StringChoiceProperty, SignalMixin, ReadOnlyMixin, ObserveMixin ) from pyxrd.data import settings from pyxrd.refinement.refinables.properties import DataMixin from pyxrd.generic.models import DataModel from pyxrd.generic.io import storables, Storable from pyxrd.generic.models.lines import StorableXYData from pyxrd.file_parsers.json_parser import JSONParser from pyxrd.calculations.goniometer import ( get_lorentz_polarisation_factor, get_fixed_to_ads_correction_range, get_nm_from_2t, get_nm_from_t, get_2t_from_nm, get_t_from_nm, ) from pyxrd.calculations.data_objects import GonioData @storables.register() class Goniometer(DataModel, Storable): """ The Goniometer class contains all the information related to the X-ray diffraction goniometer, e.g. wavelength, radius, slit sizes, ... """ # MODEL INTEL: class Meta(DataModel.Meta): store_id = "Goniometer" _data_object = None @property def data_object(self): self._data_object.wavelength = self.wavelength x, y = self.wavelength_distribution.get_xy_data() self._data_object.wavelength_distribution = list(zip(x.tolist(), y.tolist())) return self._data_object specimen = property(DataModel.parent.fget, DataModel.parent.fset) # PROPERTIES: #: Start angle (in °2-theta, only used when calculating without #: experimental data) min_2theta = FloatProperty( default=3.0, text="Start angle", minimum=0.0, maximum=180.0, tabular=True, persistent=True, visible=True, signal_name="data_changed", widget_type="spin", mix_with=(DataMixin, SignalMixin) ) #: End angle (in °2-theta, only used when calculating without #: experimental data) max_2theta = FloatProperty( default=3.0, text="End angle", minimum=0.0, maximum=180.0, tabular=True, persistent=True, visible=True, signal_name="data_changed", widget_type="spin", mix_with=(DataMixin, SignalMixin) ) #: The number of steps between start and end angle steps = IntegerProperty( default=2500, text="Steps", minimum=0, maximum=10000, tabular=True, persistent=True, visible=True, signal_name="data_changed", widget_type="spin", mix_with=(DataMixin, SignalMixin) ) #: The wavelength distribution wavelength_distribution = LabeledProperty( default=None, text="Wavelength distribution", tabular=False, persistent=True, visible=True, signal_name="data_changed", widget_type="xy_list_view", mix_with=(SignalMixin, ObserveMixin) ) @FloatProperty( default=0.154056, text="Wavelength", tabular=True, persistent=False, visible=False, signal_name="data_changed", mix_with=(ReadOnlyMixin,) ) def wavelength(self): """The wavelength of the generated X-rays (in nm)""" # Get the dominant wavelength in the distribution: x, y = self.wavelength_distribution.get_xy_data() wl = float(x[np.argmax(y)]) return wl #: Flag indicating if the first soller slit is present or not has_soller1 = BoolProperty( default=True, text="Soller 1", tabular=True, persistent=True, visible=True, signal_name="data_changed", mix_with=(DataMixin, SignalMixin) ) #: The first Soller slit size (in °) soller1 = FloatProperty( default=2.3, text="Soller 1", minimum=0.0, maximum=10.0, tabular=True, persistent=True, visible=True, signal_name="data_changed", widget_type="spin", mix_with=(DataMixin, SignalMixin) ) #: Flag indicating if the second soller slit is present or not has_soller2 = BoolProperty( default=True, text="Soller 2", tabular=True, persistent=True, visible=True, signal_name="data_changed", mix_with=(DataMixin, SignalMixin) ) #: The second Soller slit size (in °) soller2 = FloatProperty( default=2.3, text="Soller 2", minimum=0.0, maximum=10.0, tabular=True, persistent=True, visible=True, signal_name="data_changed", widget_type="spin", mix_with=(DataMixin, SignalMixin) ) #: The radius of the goniometer (in cm) radius = FloatProperty( default=24.0, text="Radius", minimum=0.0, maximum=200.0, tabular=True, persistent=True, visible=True, signal_name="data_changed", widget_type="spin", mix_with=(DataMixin, SignalMixin) ) #: The divergence slit mode of the goniometer divergence_mode = StringChoiceProperty( default=settings.DEFAULT_DIVERGENCE_MODE, text="Divergence mode", visible=True, persistent=True, choices=settings.DIVERGENCE_MODES, signal_name="data_changed", mix_with=(DataMixin, SignalMixin,) ) #: The divergence slit size (if fixed) or irradiated sample length (if automatic) divergence = FloatProperty( default=0.5, text="Divergence", minimum=0.0, maximum=90.0, tabular=True, persistent=True, visible=True, signal_name="data_changed", widget_type="spin", mix_with=(DataMixin, SignalMixin) ) #: Flag indicating if the second soller slit is present or not has_absorption_correction = BoolProperty( default=False, text="Absorption correction", tabular=True, persistent=True, visible=True, signal_name="data_changed", mix_with=(DataMixin, SignalMixin) ) #: The actual sample length sample_length = FloatProperty( default=1.25, text="Sample length [cm]", minimum=0.0, visible=True, persistent=True, tabular=True, signal_name="data_changed", widget_type="spin", mix_with=(DataMixin, SignalMixin) ) #: The sample surface density sample_surf_density = FloatProperty( default=20.0, text="Sample surface density [mg/cm²]", minimum=0.0, visible=True, persistent=True, tabular=True, signal_name="data_changed", widget_type="spin", mix_with=(DataMixin, SignalMixin) ) #: The sample mass absorption coefficient absorption = FloatProperty( default=45.0, text="Mass attenuation coeff. [cm²/g]", minimum=0.0, visible=True, persistent=True, tabular=True, signal_name="data_changed", widget_type="spin", mix_with=(DataMixin, SignalMixin) ) #: Angular value (in degrees) for a monochromator correction - use 28.44 for silicon and 26.53 for carbon. mcr_2theta = FloatProperty( default=0.0, text="Monochromator 2θ", minimum=0.0, maximum=90.0, tabular=True, persistent=True, visible=True, signal_name="data_changed", widget_type="spin", mix_with=(DataMixin, SignalMixin,) ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): """ Constructor takes any of its properties as a keyword argument. In addition to the above, the constructor still supports the following deprecated keywords, mapping to a current keyword: - lambda: maps to wavelength Any other arguments or keywords are passed to the base class. """ my_kwargs = self.pop_kwargs(kwargs, "data_radius", "data_divergence", "data_soller1", "data_soller2", "data_min_2theta", "data_max_2theta", "data_lambda", "lambda", "wavelength", "has_ads", "ads_fact", "ads_phase_fact", "ads_phase_shift", "ads_const", *[prop.label for prop in Goniometer.Meta.get_local_persistent_properties()] ) super(Goniometer, self).__init__(*args, **kwargs) kwargs = my_kwargs self._data_object = GonioData() with self.data_changed.hold(): self.radius = self.get_kwarg(kwargs, 24.0, "radius", "data_radius") #: Parse divergence mode (including old-style keywords): new_div_mode = self.get_kwarg(kwargs, None, "divergence_mode") if new_div_mode is None: # old style project old_ads = self.get_kwarg(kwargs, None, "has_ads") if old_ads is not None and old_ads: # if we have ads, set as such: new_div_mode = "AUTOMATIC" else: # otherwise it was angular fixed slits new_div_mode = settings.DEFAULT_DIVERGENCE_MODE self.divergence_mode = new_div_mode # Divergence value: self.divergence = self.get_kwarg(kwargs, 0.5, "divergence", "data_divergence") # Monochromator correction: self.mcr_2theta = float(self.get_kwarg(kwargs, 0, "mcr_2theta")) # Soller slits: self.has_soller1 = self.get_kwarg(kwargs, True, "has_soller1") self.soller1 = float(self.get_kwarg(kwargs, 2.3, "soller1", "data_soller1")) self.has_soller2 = self.get_kwarg(kwargs, True, "has_soller2") self.soller2 = float(self.get_kwarg(kwargs, 2.3, "soller2", "data_soller2")) # Angular range settings for calculated patterns: self.min_2theta = float(self.get_kwarg(kwargs, 3.0, "min_2theta", "data_min_2theta")) self.max_2theta = float(self.get_kwarg(kwargs, 45.0, "max_2theta", "data_max_2theta")) self.steps = int(self.get_kwarg(kwargs, 2500, "steps")) # Sample characteristics self.sample_length = float(self.get_kwarg(kwargs, settings.DEFAULT_SAMPLE_LENGTH, "sample_length")) self.absorption = float(self.get_kwarg(kwargs, 45.0, "absorption")) self.sample_surf_density = float(self.get_kwarg(kwargs, 20.0, "sample_surf_density")) self.has_absorption_correction = bool(self.get_kwarg(kwargs, False, "has_absorption_correction")) wavelength = self.get_kwarg(kwargs, None, "wavelength", "data_lambda", "lambda") if not "wavelength_distribution" in kwargs and wavelength is not None: default_wld = [ [wavelength,1.0], ] else: # A Cu wld: default_wld = [ [0.1544426,0.955148885], [0.153475,0.044851115], ] self.wavelength_distribution = StorableXYData( data=self.get_kwarg(kwargs, list(zip(*default_wld)), "wavelength_distribution") ) # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ def __reduce__(self): return (type(self), ((), self.json_properties())) def json_properties(self): props = Storable.json_properties(self) props["wavelength_distribution"] = self.wavelength_distribution._serialize_data() return props def reset_from_file(self, gonfile): """ Loads & sets the parameters from the goniometer JSON file specified by `gonfile`, can be a filename or a file-like object. """ new_gonio = JSONParser.parse(gonfile) with self.data_changed.hold(): for prop in self.Meta.all_properties: if prop.persistent: if prop.label == "wavelength_distribution": self.wavelength_distribution.clear() self.wavelength_distribution.set_data( *new_gonio.wavelength_distribution.get_xy_data()) elif prop.label != "uuid": setattr(self, prop.label, getattr(new_gonio, prop.label)) # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def get_nm_from_t(self, theta): """Converts a theta position to a nanometer value""" return get_nm_from_t( theta, wavelength=self.wavelength, zero_for_inf=True ) def get_nm_from_2t(self, twotheta): """Converts a 2-theta position to a nanometer value""" return get_nm_from_2t( twotheta, wavelength=self.wavelength, zero_for_inf=True ) def get_t_from_nm(self, nm): """ Converts a nanometer value to a theta position""" return get_t_from_nm(nm, wavelength=self.wavelength) def get_2t_from_nm(self, nm): """ Converts a nanometer value to a 2-theta position""" return get_2t_from_nm(nm, wavelength=self.wavelength) def get_default_theta_range(self, as_radians=True): """ Returns a numpy array containing the theta values as radians from `min_2theta` to `max_2theta` with `steps` controlling the interval. When `as_radians` is set to False the values are returned as degrees. """ def torad(val): if as_radians: return radians(val) else: return val min_theta = torad(self.min_2theta * 0.5) max_theta = torad(self.max_2theta * 0.5) delta_theta = float(max_theta - min_theta) / float(self.steps) theta_range = (min_theta + delta_theta * np.arange(0, self.steps, dtype=float)) + delta_theta * 0.5 return theta_range def get_lorentz_polarisation_factor(self, range_theta, sigma_star): """ Calculates Lorentz polarization factor for the given theta range and sigma-star value using the information about the goniometer's geometry. """ return get_lorentz_polarisation_factor( range_theta, sigma_star, self.soller1, self.soller2, self.mcr_2theta ) def get_ADS_to_fixed_correction(self, range_theta): """ Returns a correction range that will convert ADS data to fixed slit data. Use with caution. """ return 1.0 / get_fixed_to_ads_correction_range(range_theta, self.data_object) pass # end of class PyXRD-0.8.4/pyxrd/goniometer/views.py000066400000000000000000000015721363064711000175240ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport from pyxrd.generic.views import BaseView, DialogView class InlineGoniometerView(BaseView): """ The inline Goniometer view. """ builder = resource_filename(__name__, "glade/goniometer.glade") top = "edit_goniometer" widget_format = "gonio_%s" @property def import_combo_box(self): return self["cmb_import_gonio"] pass # end of class class WavelengthDistributionView(DialogView): """ The wavelength distribution view. """ subview_builder = resource_filename(__name__, "glade/wavelength_distribution.glade") subview_toplevel = "edit_wld" widget_format = "wld_%s" pass # end of classPyXRD-0.8.4/pyxrd/logs.py000066400000000000000000000040121363064711000151530ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os import queue import logging from logging.handlers import QueueHandler, QueueListener def setup_logging(basic=False, prefix=None): """ Setup logging module. """ from pyxrd.data import settings # Whether PyXRD should spew out debug messages debug = settings.DEBUG # Filename used for storing the logged messages log_file = settings.LOG_FILENAME # Flag indicating if a full logger should be setup (False) or # if simple, sparse logging is enough (True) basic = not settings.GUI_MODE fmt = '%(name)s - %(levelname)s: %(message)s' if prefix is not None: fmt = prefix + " " + fmt if log_file is not None and not os.path.exists(os.path.dirname(log_file)): os.makedirs(os.path.dirname(log_file)) if not basic: # Setup file log: file_handler = logging.FileHandler(log_file, 'w') disk_fmt = logging.Formatter( '%(asctime)s %(levelname)-8s %(name)-40s %(message)s', datefmt='%m-%d %H:%M') file_handler.setFormatter(disk_fmt) # Setup console log: log_handler = logging.StreamHandler() full = logging.Formatter(fmt) log_handler.setFormatter(full) # Setup queue handler: log_que = queue.Queue(-1) queue_handler = QueueHandler(log_que) queue_listener = QueueListener(log_que, file_handler, log_handler, respect_handler_level=True) queue_listener.start() # Add queue handler: logger = logging.getLogger('') logger.setLevel(logging.DEBUG if debug else logging.INFO) logger.addHandler(queue_handler) else: # Very basic output for the root object: logging.basicConfig(format=fmt) logger = logging.getLogger('') logger.addHandler(queue_handler) settings.FINALIZERS.append(queue_listener.stop) PyXRD-0.8.4/pyxrd/mixture/000077500000000000000000000000001363064711000153355ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/mixture/__init__.py000066400000000000000000000000001363064711000174340ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/mixture/controllers/000077500000000000000000000000001363064711000177035ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/mixture/controllers/__init__.py000066400000000000000000000012321363064711000220120ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .edit_mixture_controller import EditMixtureController from .mixtures_controller import MixturesController #from .edit_insitu_mixture_controller import EditInSituMixtureController #from .insitu_behaviours_controller import InSituBehavioursController #from .edit_insitu_behaviour_controller import EditInSituBehaviourController __all__ = [ #"EditInSituMixtureController", "EditMixtureController", "MixturesController", #"EditInSituBehaviourController" #"InSituBehavioursController", ]PyXRD-0.8.4/pyxrd/mixture/controllers/add_insitu_behaviour_controller.py000066400000000000000000000036631363064711000267170ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk as gdk import logging logger = logging.getLogger(__name__) from pyxrd.generic.views.combobox_tools import add_combo_text_column from pyxrd.generic.controllers import DialogController class AddInSituBehaviourController(DialogController): """ Controller for the add InSituBehaviour dialog """ auto_adapt = False def __init__(self, model=None, view=None, parent=None, callback=None): super(AddInSituBehaviourController, self).__init__( model=model, view=view, parent=parent) self.callback = callback def generate_combo(self): cmb_model = Gtk.ListStore(str, object) print("Adding rows from:", self.parent.obj_type_map) for cls, _, _ in self.parent.obj_type_map: print("Adding row:", cls) cmb_model.append([cls.Meta.store_id, cls]) self.view.behaviour_combo_box.set_model(cmb_model) add_combo_text_column( self.view.behaviour_combo_box, text_col=0) def register_view(self, view): self.generate_combo() # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_btn_ok_clicked(self, event): self.view.hide() self.callback(self.view.get_behaviour_type()) return True def on_keypress(self, widget, event): if event.keyval == Gdk.KEY_Escape: self.view.hide() return True if event.keyval == Gdk.KEY_Return: return self.on_btn_ok_clicked(event) def on_window_edit_dialog_delete_event(self, event, args=None): self.view.hide() return True # do not propagate pass # end of classPyXRD-0.8.4/pyxrd/mixture/controllers/add_mixture_controller.py000066400000000000000000000025761363064711000250370ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk import logging logger = logging.getLogger(__name__) from pyxrd.generic.controllers import DialogController class AddMixtureController(DialogController): """ Controller for the add mixture dialog """ auto_adapt = False def __init__(self, model=None, view=None, parent=None, callback=None): super(AddMixtureController, self).__init__( model=model, view=view, parent=parent) self.callback = callback # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_btn_ok_clicked(self, event): self.view.hide() self.callback(self.view.get_mixture_type()) return True def on_keypress(self, widget, event): if event.keyval == Gdk.keyval_from_name("Escape"): self.view.hide() return True if event.keyval == Gdk.keyval_from_name("Return"): return self.on_btn_ok_clicked(event) def on_window_edit_dialog_delete_event(self, event, args=None): self.view.hide() return True # do not propagate pass # end of classPyXRD-0.8.4/pyxrd/mixture/controllers/edit_insitu_behaviour_controller.py000066400000000000000000000005301363064711000271020ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pyxrd.generic.controllers import BaseController class EditInSituBehaviourController(BaseController): """ The controller for the Behaviour model """ pass # end of classPyXRD-0.8.4/pyxrd/mixture/controllers/edit_insitu_mixture_controller.py000066400000000000000000000167301363064711000266240ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) from mvc import Controller from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory from pyxrd.generic.controllers import BaseController from pyxrd.generic.controllers.objectliststore_controllers import wrap_list_property_to_treemodel from pyxrd.refinement.views.refinement_view import RefinementView from pyxrd.refinement.controllers.refinement_controller import RefinementController class EditInSituMixtureController(BaseController): """ The controller for the edit InSituMixture view """ auto_adapt_excluded = [ "refine_method_index", "refinables", "make_psp_plots" ] ref_view = None @property def specimens_treemodel(self): if self.model.project is not None: return wrap_list_property_to_treemodel(self.model.project, type(self.model.project).specimens) else: return None @property def phases_treemodel(self): if self.model.project is not None: return wrap_list_property_to_treemodel(self.model.project, type(self.model.project).phases) else: return None @property def behavs_treemodel(self): if self.model.project is not None: return wrap_list_property_to_treemodel(self.model.project, type(self.model.project).behaviours) else: return None def register_adapters(self): self.create_ui() def create_ui(self): """ Creates a complete new UI for the Mixture model """ self.view.reset_view() for index in range(len(self.model.phases)): self._add_phase_view(index) for index in range(len(self.model.specimens)): self._add_specimen_view(index) def _is_behav_visible(self, model, itr, indcs): behav = self.behavs_treemodel.get_user_data(itr) if itr is not None else None if behav is not None: phase = self.model.phase_matrix[indcs[0], indcs[1]] return behav.is_compatible_with(phase) else: return False def _add_phase_view(self, phase_slot): """ Adds a new view for the given phase slot. """ def on_label_changed(editable): self.model.phases[phase_slot] = editable.get_text() def on_fraction_changed(editable): try: self.model.fractions[phase_slot] = float(editable.get_text()) except ValueError: return # ignore ValueErrors def on_phase_delete(widget): self.model.del_phase_slot(phase_slot) widget.disconnect(getattr(widget, "deleventid")) self.view.add_phase_slot( self.phases_treemodel, self.behavs_treemodel, on_phase_delete, on_label_changed, on_fraction_changed, None, self.on_phase_combo_changed, self._is_behav_visible, self.on_behav_combo_changed, label=self.model.phases[phase_slot], fraction=self.model.fractions[phase_slot], phases=self.model.phase_matrix, behavs=self.model.behaviour_matrix) def _add_specimen_view(self, specimen_slot): """ Adds a new view for the given specimen slot """ def on_scale_changed(editable): try: self.model.scales[specimen_slot] = float(editable.get_text()) except ValueError: return # ignore ValueErrors def on_bgs_changed(editable): try: self.model.bgshifts[specimen_slot] = float(editable.get_text()) except ValueError: return # ignore ValueErrors def on_specimen_changed(combobox): itr = combobox.get_active_iter() specimen = self.specimens_treemodel.get_user_data(itr) if itr is not None else None self.model.set_specimen(specimen_slot, specimen) def on_specimen_delete(widget): self.model.del_specimen_slot(specimen_slot) widget.disconnect(getattr(widget, "deleventid")) self.view.add_specimen_slot( self.phases_treemodel, self.behavs_treemodel, self.specimens_treemodel, on_specimen_delete, on_scale_changed, on_bgs_changed, on_specimen_changed, None, self.on_phase_combo_changed, self._is_behav_visible, self.on_behav_combo_changed, scale=self.model.scales[specimen_slot], bgs=self.model.bgshifts[specimen_slot], specimen=self.model.specimens[specimen_slot], phases=self.model.phase_matrix, behavs=self.model.behaviour_matrix) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @Controller.observe("data_changed", signal=True) def notif_has_changed(self, model, prop_name, info): self.view.update_all(self.model.fractions, self.model.scales, self.model.bgshifts) @Controller.observe("needs_reset", signal=True) def notif_needs_reset(self, model, prop_name, info): self.create_ui() # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_phase_combo_changed(self, combobox, row, col): itr = combobox.get_active_iter() phase = self.phases_treemodel.get_user_data(itr) if itr is not None else None self.model.set_phase(row, col, phase) def on_behav_combo_changed(self, combobox, row, col): itr = combobox.get_active_iter() behav = self.behavs_treemodel.get_user_data(itr) if itr is not None else None self.model.set_behaviour(row, col, behav) def on_add_phase(self, widget, *args): with self.model.data_changed.hold(): index = self.model.add_phase_slot("New Phase", 1.0) self._add_phase_view(index) def on_add_specimen(self, widget, *args): with self.model.data_changed.hold(): index = self.model.add_specimen_slot(None, 1.0, 0.0) self._add_specimen_view(index) def on_add_both(self, widget, *args): with self.model.data_changed.hold(): self.on_add_specimen(widget, *args) self.on_add_phase(widget, *args) def on_optimize_clicked(self, widget, *args): self.model.optimize() def on_refine_clicked(self, widget, *args): self.model.refinement.update_refinement_treestore() if self.ref_view is not None: self.ref_view.hide() self.ref_ctrl.cleanup() self.view.parent.hide() self.ref_view = RefinementView(parent=self.parent.view) self.ref_ctrl = RefinementController(model=self.model.refinement, view=self.ref_view, parent=self) self.ref_view.present() def on_composition_clicked(self, widget, *args): comp = "The composition of the specimens in this mixture:\n\n\n" comp += "" # get the composition matrix (first columns contains strings with elements, others are specimen compositions) import re for row in self.model.get_composition_matrix(): comp += "%s %s\n" % (re.sub(r'(\d+)', r'\1', row[0]), " ".join(row[1:])) comp += "" DialogFactory.get_information_dialog( comp, parent=self.view.get_toplevel() ).run() pass # end of class PyXRD-0.8.4/pyxrd/mixture/controllers/edit_mixture_controller.py000066400000000000000000000146331363064711000252310ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) from mvc import Controller from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory from pyxrd.generic.controllers import BaseController from pyxrd.generic.controllers.objectliststore_controllers import wrap_list_property_to_treemodel from pyxrd.refinement.views.refinement_view import RefinementView from pyxrd.refinement.controllers.refinement_controller import RefinementController class EditMixtureController(BaseController): auto_adapt_excluded = [ "refine_method_index", "refinables", "make_psp_plots" ] ref_view = None @property def specimens_treemodel(self): if self.model.project is not None: return wrap_list_property_to_treemodel(self.model.project, type(self.model.project).specimens) else: return None @property def phases_treemodel(self): if self.model.project is not None: return wrap_list_property_to_treemodel(self.model.project, type(self.model.project).phases) else: return None def register_adapters(self): self.create_ui() def create_ui(self): """ Creates a complete new UI for the Mixture model """ self.view.reset_view() for index in range(len(self.model.phases)): self._add_phase_view(index) for index in range(len(self.model.specimens)): self._add_specimen_view(index) def _add_phase_view(self, phase_slot): """ Adds a new view for the given phase slot. """ def on_label_changed(editable): self.model.phases[phase_slot] = editable.get_text() def on_check_toggled(check): self.model.fractions_mask[phase_slot] = 1 if check.get_active() else 0 def on_fraction_changed(editable): try: self.model.fractions[phase_slot] = float(editable.get_text()) except ValueError: return # ignore ValueErrors def on_phase_delete(widget): self.model.del_phase_slot(phase_slot) widget.disconnect(getattr(widget, "deleventid")) self.view.add_phase_slot(self.phases_treemodel, on_phase_delete, on_label_changed, on_check_toggled, on_fraction_changed, self.on_combo_changed, label=self.model.phases[phase_slot], fraction=self.model.fractions[phase_slot], phases=self.model.phase_matrix) def _add_specimen_view(self, specimen_slot): """ Adds a new view for the given specimen slot """ def on_scale_changed(editable): try: self.model.scales[specimen_slot] = float(editable.get_text()) except ValueError: return # ignore ValueErrors def on_bgs_changed(editable): try: self.model.bgshifts[specimen_slot] = float(editable.get_text()) except ValueError: return # ignore ValueErrors def on_specimen_changed(combobox): itr = combobox.get_active_iter() specimen = self.specimens_treemodel.get_user_data(itr) if itr is not None else None self.model.set_specimen(specimen_slot, specimen) def on_specimen_delete(widget): self.model.del_specimen_slot(specimen_slot) widget.disconnect(getattr(widget, "deleventid")) self.view.add_specimen_slot(self.phases_treemodel, self.specimens_treemodel, on_specimen_delete, on_scale_changed, on_bgs_changed, on_specimen_changed, self.on_combo_changed, scale=self.model.scales[specimen_slot], bgs=self.model.bgshifts[specimen_slot], specimen=self.model.specimens[specimen_slot], phases=self.model.phase_matrix) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @Controller.observe("data_changed", signal=True) def notif_has_changed(self, model, prop_name, info): self.view.update_all(self.model.fractions, self.model.scales, self.model.bgshifts) @Controller.observe("needs_reset", signal=True) def notif_needs_reset(self, model, prop_name, info): self.create_ui() # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_combo_changed(self, combobox, row, col): itr = combobox.get_active_iter() phase = self.phases_treemodel.get_user_data(itr) if itr is not None else None self.model.set_phase(row, col, phase) def on_add_phase(self, widget, *args): with self.model.data_changed.hold(): index = self.model.add_phase_slot("New Phase", 1.0) self._add_phase_view(index) def on_add_specimen(self, widget, *args): with self.model.data_changed.hold(): index = self.model.add_specimen_slot(None, 1.0, 0.0) self._add_specimen_view(index) def on_add_both(self, widget, *args): with self.model.data_changed.hold(): self.on_add_specimen(widget, *args) self.on_add_phase(widget, *args) def on_optimize_clicked(self, widget, *args): self.model.optimize() def on_refine_clicked(self, widget, *args): self.model.refinement.update_refinement_treestore() if self.ref_view is not None: self.ref_view.hide() self.ref_ctrl.cleanup() self.view.parent.hide() self.ref_view = RefinementView(parent=self.parent.view) self.ref_ctrl = RefinementController(model=self.model.refinement, view=self.ref_view, parent=self) self.ref_view.present() def on_composition_clicked(self, widget, *args): comp = "The composition of the specimens in this mixture:\n\n\n" comp += "" # get the composition matrix (first columns contains strings with elements, others are specimen compositions) import re for row in self.model.get_composition_matrix(): comp += "%s %s\n" % (re.sub(r'(\d+)', r'\1', row[0]), " ".join(row[1:])) comp += "" DialogFactory.get_information_dialog( comp, parent=self.view.get_toplevel() ).run() pass # end of class PyXRD-0.8.4/pyxrd/mixture/controllers/insitu_behaviours_controller.py000066400000000000000000000054461363064711000262730ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from contextlib import contextmanager from mvc.models.base import Model from pyxrd.generic.controllers import ObjectListStoreController from pyxrd.mixture.models import InSituBehaviour, insitu_behaviours from pyxrd.mixture.views import EditInSituBehaviourView from pyxrd.mixture.views.add_insitu_behaviour_view import AddInSituBehaviourView from .edit_insitu_behaviour_controller import EditInSituBehaviourController from .add_insitu_behaviour_controller import AddInSituBehaviourController class InSituBehavioursController(ObjectListStoreController): treemodel_property_name = "behaviours" treemodel_class_type = InSituBehaviour columns = [ ("Mixture name", "c_name") ] delete_msg = "Deleting a mixture is irreverisble!\nAre You sure you want to continue?" obj_type_map = [ (cls, EditInSituBehaviourView, EditInSituBehaviourController) for name, cls in list(insitu_behaviours.__dict__.items()) if hasattr(cls, 'Meta') and cls.Meta.concrete ] def get_behaviours_tree_model(self, *args): return self.treemodel # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_load_object_clicked(self, event): pass # cannot load behaviours def on_save_object_clicked(self, event): pass # cannot save behaviours def get_new_edit_view(self, obj): """ Gets a new 'edit object' view for the given obj, view and parent view. """ if obj == None: return self.view.none_view else: for obj_tp, view_tp, ctrl_tp in self.obj_type_map: # @UnusedVariable if isinstance(obj, obj_tp): return view_tp(obj.Meta, parent=self.view) raise NotImplementedError("Unsupported object type; subclasses of" " TreeControllerMixin need to define an obj_type_map attribute!") def create_new_object_proxy(self): def on_accept(behaviour_type): if behaviour_type is not None: self.add_object(behaviour_type(parent=self.model)) # TODO re-use this and reset the COMBO etc. self.add_model = Model() self.add_view = AddInSituBehaviourView(parent=self.view) self.add_ctrl = AddInSituBehaviourController( model=self.add_model, view=self.add_view, parent=self, callback=on_accept ) self.add_view.present() return None @contextmanager def _multi_operation_context(self): with self.model.data_changed.hold(): yield pass # end of classPyXRD-0.8.4/pyxrd/mixture/controllers/mixtures_controller.py000066400000000000000000000046071363064711000244070ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from contextlib import contextmanager from mvc.models.base import Model from pyxrd.generic.controllers import ObjectListStoreController from pyxrd.mixture.models import Mixture #, InSituMixture from pyxrd.mixture.views import EditMixtureView, AddMixtureView #EditInSituMixtureView, from .edit_mixture_controller import EditMixtureController #from .edit_insitu_mixture_controller import EditInSituMixtureController from .add_mixture_controller import AddMixtureController class MixturesController(ObjectListStoreController): treemodel_property_name = "mixtures" treemodel_class_type = Mixture columns = [ ("Mixture name", "c_name") ] delete_msg = "Deleting a mixture is irreverisble!\nAre You sure you want to continue?" obj_type_map = [ #(InSituMixture, EditInSituMixtureView, EditInSituMixtureController), (Mixture, EditMixtureView, EditMixtureController), ] def get_mixtures_tree_model(self, *args): return self.treemodel # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_load_object_clicked(self, event): pass # cannot load mixtures def on_save_object_clicked(self, event): pass # cannot save mixtures def create_new_object_proxy(self): """def on_accept(mixture_type): if mixture_type == "mixture": self.add_object(Mixture(parent=self.model)) #elif mixture_type == "insitu": # self.add_object(InSituMixture(parent=self.model)) # TODO re-use this and reset the COMBO etc. self.add_model = Model() self.add_view = AddMixtureView(types_dict={ 'mixture': "Create a regular mixture", #'insitu': "Create an in-situ mixture" }, parent=self.view) self.add_ctrl = AddMixtureController( model=self.add_model, view=self.add_view, parent=self.parent, callback=on_accept ) self.add_view.present()""" return Mixture(parent=self.model) #None @contextmanager def _multi_operation_context(self): with self.model.data_changed.hold(): yield pass # end of classPyXRD-0.8.4/pyxrd/mixture/models/000077500000000000000000000000001363064711000166205ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/mixture/models/__init__.py000066400000000000000000000005311363064711000207300ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .mixture import Mixture #from .insitu_mixture import InSituMixture #from .insitu_behaviours import InSituBehaviour __all__ = [ "Mixture", #"InSituMixture", #"InSituBehaviour" ] PyXRD-0.8.4/pyxrd/mixture/models/insitu_behaviours/000077500000000000000000000000001363064711000223625ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/mixture/models/insitu_behaviours/SSSR0Behaviour.py000066400000000000000000000134671363064711000254660ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import scipy.stats from math import sqrt from mvc.models.properties.float_properties import FloatProperty from pyxrd.generic.io.custom_io import storables from pyxrd.refinement.refinables.properties import RefinableMixin from .insitu_behaviour import InSituBehaviour BEHAVIOUR_CLASS = "SSSR0Behaviour" @storables.register() class SSSR0Behaviour(InSituBehaviour): """ Behaviour class for R0 Smectite with 3 components """ # MODEL INTEL: class Meta(InSituBehaviour.Meta): store_id = "SSSR0Behaviour" concrete = True # indicates this can be instantiated and added in the UI cation_hydration_factor = FloatProperty( description="Related to the cation hydration enthalpy", default=0.54, text="Cation hydration factor", minimum=0.0, maximum=2.0, refinable=True, persistent=True, visible=True, mix_with=(RefinableMixin,) ) min_swelling_layer_charge = FloatProperty( description="The minimum layer charge required for swelling", default=-0.1, text="Minimum swelling layer charge", minimum = -2, maximum=0, refinable=True, persistent=True, visible=True, mix_with=(RefinableMixin,) ) layer_charge_mean = FloatProperty( description="The mean of the normal layer charge distribution", default=-0.4, text="Mean layer charge", minimum = -2, maximum=0, refinable=True, persistent=True, visible=True, mix_with=(RefinableMixin,) ) layer_charge_stdev = FloatProperty( default=0.05, text="The standard deviation of the layer charges", minimum = 0.0001, maximum=0.75, refinable=True, persistent=True, visible=True, mix_with=(RefinableMixin,) ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): my_kwargs = self.pop_kwargs(kwargs, *[prop.label for prop in SSSR0Behaviour.Meta.get_local_persistent_properties()] ) super(SSSR0Behaviour, self).__init__(*args, **kwargs) kwargs = my_kwargs with self.data_changed.hold(): self.cation_hydration_factor = self.get_kwarg(kwargs, self.cation_hydration_factor, "cation_hydration_factor") self.min_swelling_layer_charge = self.get_kwarg(kwargs, self.min_swelling_layer_charge, "min_swelling_layer_charge") self.layer_charge_mean = self.get_kwarg(kwargs, self.layer_charge_mean, "layer_charge_mean") self.layer_charge_stdev = self.get_kwarg(kwargs, self.layer_charge_stdev, "layer_charge_stdev") pass #end of constructor # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def get_layer_charge_distribution(self, mean = -0.4, stdev = 0.05): distr = scipy.stats.norm(mean, stdev) return distr def get_layer_type_distribution(self, distr, boundaries): """ Returns a 3-tuple with the % of layers having: 0 layers of water 1 layers of water 2 layers of water """ (B0to1, B1to2, B2to0) = boundaries cdfB0to1 = distr.cdf(B0to1) # Get # layers with B0to1 or smaller layer charge cdfB1to2 = distr.cdf(B1to2) # Get # layers with B1to2 or smaller layer charge cdfB2to0 = distr.cdf(B2to0) # Get # layers with B2to0 or smaller layer charge total = distr.cdf(0) # Get # layers with B2 or smaller layer charge # returns fraction of 0w, 1w and 2w layers: return (cdfB0to1+total-cdfB2to0)/total,(cdfB1to2-cdfB0to1)/total,(cdfB2to0-cdfB1to2)/total def get_layer_type_boundaries(self, RH): """ Gets the layer type boundaries (expressed as layer charge) in function of RH (expressed as a fraction) """ RH_step_size = (self.cation_hydration_factor*sqrt(RH)) b2to0_RH_factor = self.min_swelling_layer_charge b1to2_RH_factor = b2to0_RH_factor - RH_step_size b0to1_RH_factor = b1to2_RH_factor - RH_step_size return b0to1_RH_factor, b1to2_RH_factor, b2to0_RH_factor # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def apply(self, phase, RH): super(SSSR0Behaviour, self).apply(phase) RH = RH / 100. print("Applying SSSR0Behaviour to %s" % phase, "For RH: %.2f" % RH) # Get layer charge coundaries for the given relative humidity boundaries = self.get_layer_type_boundaries(RH) print(" boundaries:", boundaries) # Get the layer charge distribution for the given phase lc_distr = self.get_layer_charge_distribution(self.layer_charge_mean, self.layer_charge_stdev) print(" lc_distr:", lc_distr) # Calculate the layer type distribution for the calculated boundaries and distribution W0, W1, W2 = self.get_layer_type_distribution(lc_distr, boundaries) print(" W0:", W0) print(" W1:", W1) print(" W2:", W2) # Set probability model factors: phase.probabilities.F0 = W2 phase.probabilities.F1 = W1/(W1+W0) # TODO update d-spacings def is_compatible_with(self, phase): # TODO check names of components try: return (phase.R == 0 and phase.G == 3) except: return False pass PyXRD-0.8.4/pyxrd/mixture/models/insitu_behaviours/__init__.py000066400000000000000000000004521363064711000244740ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .insitu_behaviour import InSituBehaviour from .SSSR0Behaviour import SSSR0Behaviour __all__ = [ "InSituBehaviour", "SSSR0Behaviour" ] PyXRD-0.8.4/pyxrd/mixture/models/insitu_behaviours/insitu_behaviour.py000066400000000000000000000046551363064711000263250ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from mvc.models.properties import StringProperty from pyxrd.generic.io.custom_io import storables, Storable from pyxrd.generic.models.base import DataModel from pyxrd.refinement.refinables.mixins import RefinementGroup @storables.register() class InSituBehaviour(DataModel, RefinementGroup, Storable): """ Interface class for coding in-situ behaviour scripts. Sub-classes should override or implement the methods below. """ # MODEL INTEL: class Meta(DataModel.Meta): store_id = "InSituBehaviour" # Override this so it is a unique string concrete = False # Indicates this cannot be instantiated and added in the UI mixture = property(DataModel.parent.fget, DataModel.parent.fset) # REFINEMENT GROUP IMPLEMENTATION: @property def refine_title(self): return "In-situ behaviour" @property def refine_descriptor_data(self): return dict( phase_name=self.phase.refine_title, component_name="*" ) #: The name of this Behaviour name = StringProperty( default="New Behaviour", text="Name", visible=True, persistent=True, tabular=True ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): my_kwargs = self.pop_kwargs(kwargs, *[prop.label for prop in InSituBehaviour.Meta.get_local_persistent_properties()] ) super(InSituBehaviour, self).__init__(*args, **kwargs) kwargs = my_kwargs with self.data_changed.hold(): self.name = self.get_kwarg(kwargs, self.name, "name") pass #end of constructor # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def apply(self, phase): assert phase is not None, "Cannot apply on None" assert self.is_compatible_with(phase), "`%r` is not compatible with phase `%r`" % (self, phase) def is_compatible_with(self, phase): return False # sub classes need to override this pass #end of classPyXRD-0.8.4/pyxrd/mixture/models/insitu_mixture.py000066400000000000000000000233131363064711000222640ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. raise NotImplemented("This module is not yet implemented") import sys, imp from contextlib import contextmanager import numpy as np from pyxrd.generic.io.custom_io import storables from pyxrd.mixture.models.mixture import Mixture from copy import deepcopy @storables.register() class InSituMixture(Mixture): """ Advanced model for optimizing and refining of calculated in-situ XRD data. Is a sub-class of the basic Mixture class. Each phase is linked with an additional Behaviour instance - these are created at the project level. """ # MODEL INTEL: class Meta(Mixture.Meta): store_id = "InSituMixture" _data_object = None @property def data_object(self): self._data_object = Mixture.data_object.fget(self) # @UndefinedVariable return self._data_object def get_phase_data_object(self, specimen_index, z_index, phase_index): behav = self.behaviour_matrix[specimen_index, ...].flatten()[phase_index] if self.behaviour_matrix is not None else None phase = self.phase_matrix[specimen_index, ...].flatten()[phase_index] if self.phase_matrix is not None else None if behav is not None and phase is not None: behav.apply(phase, self.specimens[specimen_index].get_z_list()[z_index]) return deepcopy(phase.data_object) if phase is not None else None # Lists and matrices: #: A 2D numpy object array containing the behaviour paths behaviour_matrix = None # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): my_kwargs = self.pop_kwargs(kwargs, "behaviour_uuids", *[prop.label for prop in InSituMixture.Meta.get_local_persistent_properties()] ) super(InSituMixture, self).__init__(*args, **kwargs) kwargs = my_kwargs with self.data_changed.hold(): # 2D matrix, rows match specimens, columns match mixture 'phase behaviours'; contains the actual behaviour objects behaviour_uuids = self.get_kwarg(kwargs, None, "behaviour_uuids") if behaviour_uuids is not None: self.behaviour_matrix = np.array([[type(type(self)).object_pool.get_object(uuid) if uuid else None for uuid in row] for row in behaviour_uuids], dtype=np.object_) else: self.behaviour_matrix = np.empty(shape=(0, 0), dtype=np.object_) print(behaviour_uuids, self.behaviour_matrix) pass # end hold data_changed # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ def json_properties(self): retval = super(InSituMixture, self).json_properties() retval["behaviour_uuids"] = [[item.uuid if item else None for item in row] for row in map(list, self.behaviour_matrix)] return retval @staticmethod def from_json(**kwargs): # Remove this deprecated kwarg: if "refinables" in kwargs: del kwargs["refinables"] return InSituMixture(**kwargs) # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def get_behaviour(self, specimen_slot, phase_slot): """Returns the behaviour at the given slot positions or None if not set""" return self.behaviour_matrix[specimen_slot, phase_slot] def set_behaviour(self, specimen_slot, phase_slot, behaviour): """Sets the behaviour at the given slot positions""" if self.parent is not None: #no parent = no valid phases with self.needs_update.hold_and_emit(): with self.data_changed.hold(): if behaviour is None: raise RuntimeError("Behaviour can not be set to None, use unset_behaviour to clear!") self.behaviour_matrix[specimen_slot, phase_slot] = behaviour self.refinement.update_refinement_treestore() def unset_behaviour(self, behaviour): """ Clears a behaviour slot in the phase matrix """ with self.needs_update.hold_and_emit(): with self.data_changed.hold(): shape = self.behaviour_matrix.shape for i in range(shape[0]): for j in range(shape[1]): if self.behaviour_matrix[i, j] == behaviour: self.behaviour_matrix[i, j] = None self.refinement.update_refinement_treestore() @contextmanager def _relieve_and_observe_behaviours(self): self._relieve_behaviours() yield self._observe_behaviours() def _observe_behaviours(self): """ Starts observing behaviour in the behaviour matrix""" for behaviour in self.behaviour_matrix.flat: if behaviour is not None: self.observe_model(behaviour) def _relieve_behaviours(self): """ Relieves behaviour observer calls """ for behaviour in self.behaviour_matrix.flat: if behaviour is not None: self.relieve_model(behaviour) def add_phase_slot(self, phase_name, fraction): """ Adds a new phase column to the phase matrix """ with self.needs_update.hold_and_emit(): with self.data_changed.hold(): n, m = self.behaviour_matrix.shape if self.behaviour_matrix.ndim == 2 else (0, 0) if self.behaviour_matrix.size == 0: self.behaviour_matrix = np.resize(self.behaviour_matrix.copy(), (n, m + 1)) self.behaviour_matrix[:] = None else: self.behaviour_matrix = np.concatenate([self.behaviour_matrix.copy(), [[None]] * n ], axis=1) self.behaviour_matrix[:, m] = None return super(InSituMixture, self).add_phase_slot(phase_name, fraction) def del_phase_slot(self, phase_slot): """ Deletes a phase column using its index """ with self.needs_update.hold_and_emit(): with self.data_changed.hold(): with self._relieve_and_observe_behaviours(): self.behaviour_matrix = np.delete(self.behaviour_matrix, phase_slot, axis=1) return super(InSituMixture, self).del_phase_slot(phase_slot) def add_specimen_slot(self, specimen, scale, bgs): """ Adds a new specimen to the phase matrix (a row) and specimen list """ with self.needs_update.hold_and_emit(): with self.data_changed.hold(): n, m = self.behaviour_matrix.shape if self.behaviour_matrix.ndim == 2 else (0, 0) if self.behaviour_matrix.size == 0: self.behaviour_matrix = np.resize(self.behaviour_matrix.copy(), (n + 1, m)) self.behaviour_matrix[:] = None else: self.behaviour_matrix = np.concatenate([self.behaviour_matrix.copy(), [[None] * m] ], axis=0) self.behaviour_matrix[n, :] = None return super(InSituMixture, self).add_specimen_slot(specimen, scale, bgs) def del_specimen_slot(self, specimen_slot): """ Deletes a specimen slot using its slot index """ with self.needs_update.hold_and_emit(): with self.data_changed.hold(): self.behaviour_matrix = np.delete(self.behaviour_matrix, specimen_slot, axis=0) return super(InSituMixture, self).del_specimen_slot(specimen_slot) # ------------------------------------------------------------ # Refinement stuff: # ------------------------------------------------------------ def set_data_object(self, mixture, calculate=False): """ Sets the fractions, scales and bgshifts of this mixture. """ if mixture is not None: with self.needs_update.ignore(): with self.data_changed.hold_and_emit(): self.fractions[:] = list(mixture.fractions) self.scales[:] = list(mixture.scales) self.bgshifts[:] = list(mixture.bgshifts) if calculate: # (re-)calculate if requested: mixture = self.optimizer.calculate(mixture) for i, (specimen_data, specimen) in enumerate(zip(mixture.specimens, self.specimens)): if specimen is not None: with specimen.data_changed.ignore(): specimen.update_pattern( specimen_data.total_intensity, specimen_data.phase_intensities * self.fractions[:, np.newaxis] * self.scales[i], self.phase_matrix[i, :] ) def load_behaviour_from_module(self, path): """Loads the behaviour class from the given filepath""" mod = imp.load_source('Behaviour', path) del sys.modules['Behaviour'] # move the module to a better spot sys.modules[mod.BEHAVIOUR_CLASS] = mod klass = mod[mod.BEHAVIOUR_CLASS] behaviour = klass() behaviour.__path = path # FIXME return behaviour pass # end of classPyXRD-0.8.4/pyxrd/mixture/models/mixture.py000066400000000000000000000666331363064711000207050ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import csv from warnings import warn from itertools import chain from collections import OrderedDict from contextlib import contextmanager import logging import numpy as np from mvc.models.properties import ( SignalProperty, LabeledProperty, SignalMixin, StringProperty, BoolProperty, IntegerProperty ) logger = logging.getLogger(__name__) from pyxrd.data import settings from pyxrd.generic.io import storables, Storable from pyxrd.generic.models import DataModel from pyxrd.refinement.refinement import Refinement from pyxrd.calculations.data_objects import MixtureData from pyxrd.phases.models import Phase from .optimizers import Optimizer @storables.register() class Mixture(DataModel, Storable): """ The base model for optimization and refinement of calculated data and experimental data. This is the main model you want to interact with, lower-level classes' functionality (mainly :class:`~pyxrd.mixture.models.optimizers.Optimizer` and :class:`~pyxrd.mixture.models.refiner.Refinement`) are integrated into this class. The Mixture is responsible for managing the phases and specimens lists and combination-matrix and for delegating any optimization, calculation or refinement calls to the appropriate helper class. """ # MODEL INTEL: class Meta(DataModel.Meta): store_id = "Mixture" _data_object = None @property def data_object(self): self._data_object.parsed = False self._data_object.optimized = False self._data_object.calculated = False self._data_object.specimens = [None] * len(self.specimens) self._data_object.n = len(self.specimens) self._data_object.m = len(self.phases) self._data_object.scales_mask = np.ones_like(self.scales) if self.auto_scales: self._data_object.scales_mask = np.ones_like(self.bgshifts) else: self._data_object.scales_mask = np.zeros_like(self.bgshifts) if self.auto_bg: self._data_object.bgshifts_mask = np.ones_like(self.bgshifts) else: self._data_object.bgshifts_mask = np.zeros_like(self.bgshifts) for i, specimen in enumerate(self.specimens): if specimen is not None: data_object = specimen.data_object data_object.phases = [[None] * self._data_object.m for _ in data_object.z_list] for z_index in range(len(specimen.get_z_list())): for phase_index in range(self.phase_matrix.shape[1]): data_object.phases[z_index][phase_index] = self.get_phase_data_object(i, z_index, phase_index) self._data_object.specimens[i] = data_object else: self._data_object.specimens[i] = None return self._data_object def get_phase_data_object(self, specimen_index, z_index, phase_index): phase = self.phase_matrix[specimen_index, ...].flatten()[phase_index] return phase.data_object if phase is not None else None project = property(DataModel.parent.fget, DataModel.parent.fset) # SIGNALS: #: Signal, emitted when the # of phases or specimens changes needs_reset = SignalProperty() #: Signal, emitted when the patterns of the specimens need an update needs_update = SignalProperty() #: The name of this Mixture name = StringProperty( default="", text="Name", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: Flag, True if the mixture will automatically adjust phase fractions and scales auto_run = BoolProperty( default=False, text="Auto Run", visible=True, persistent=True, tabular=True ) #: Flag, True if the mixture is allowed to also update the background level auto_bg = BoolProperty( default=False, text="Auto Bg", visible=True, persistent=True, tabular=True ) #: Flag, True if the mixture is allowed to also update the background level auto_scales = BoolProperty( default=True, text="Auto Scales", visible=True, persistent=True, tabular=True ) #: The tree of refinable properties @LabeledProperty( default=None, text="", visible=True, persistent=False, tabular=True, data_type=object, ) def refinables(self): return self.refinement.refinables #: An integer describing which method to use for the refinement (see #: mixture.models.methods.get_all_refine_methods) @IntegerProperty( default=0, text="Refinement method index", visible=False, persistent=True, ) def refine_method_index(self): return self.refinement.refine_method_index #: A dict containing the current refinement options @LabeledProperty( default=None, text="Current refinement method options", visible=False, persistent=True, tabular=False, data_type=object, store_private="all_refine_options" ) def refine_options(self): return self.refinement.refine_options #: A dict containing all refinement options @LabeledProperty( default=None, text="All refinement methods options", visible=False, persistent=False, tabular=False, data_type=object ) def all_refine_options(self): return self.refinement.all_refine_options # Lists and matrices: #: A 2D numpy object array containing the combination matrix phase_matrix = None #: The list of specimen objects specimens = None #: The list of phase names phases = None @property def scales(self): """ A list of floats containing the absolute scales for the calculated patterns """ return self._data_object.scales @scales.setter def scales(self, value): self._data_object.scales = value @property def bgshifts(self): """ A list of background shifts for the calculated patterns """ return self._data_object.bgshifts @bgshifts.setter def bgshifts(self, value): self._data_object.bgshifts = value @property def fractions(self): """ A list of phase fractions for this mixture """ return self._data_object.fractions @fractions.setter def fractions(self, value): self._data_object.fractions = value @property def fractions_mask(self): """ A mask indicating which fractions are to be optimized """ return self._data_object.fractions_mask @fractions_mask.setter def fractions_mask(self, value): self._data_object.fractions_mask = value # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): """ Constructor takes any of its properties as a keyword argument. It also support two UUID list keyword arguments: - phase_uuids: a list of UUID's for the phases in the mixture - specimen_uuids: a list of UUID's for the specimens in the mixture These should be *instead* of the phases and specimens keywords. In addition to the above, the constructor still supports the following deprecated keywords, mapping to a current keyword: - phase_indeces: a list of project indices for the phases in the mixture - specimen_indeces: a list of project indices for the specimens in the mixture Any other arguments or keywords are passed to the base class. """ my_kwargs = self.pop_kwargs(kwargs, "data_name", "phase_uuids", "phase_indeces", "specimen_uuids", "specimen_indeces", "data_phases", "data_scales", "data_bgshifts", "data_fractions", "refine_method", "data_refine_method", "fractions", "fractions_mask", "bgshifts", "scales", "phases", *[prop.label for prop in Mixture.Meta.get_local_persistent_properties()] ) super(Mixture, self).__init__(*args, **kwargs) kwargs = my_kwargs with self.data_changed.hold(): self._data_object = MixtureData() self.name = self.get_kwarg(kwargs, "New Mixture", "name", "data_name") self.auto_run = self.get_kwarg(kwargs, False, "auto_run") self.auto_bg = self.get_kwarg(kwargs, True, "auto_bg") self.auto_scales = self.get_kwarg(kwargs, True, "auto_scales") # 2D matrix, rows match specimens, columns match mixture 'phases'; contains the actual phase objects phase_uuids = self.get_kwarg(kwargs, None, "phase_uuids") phase_indeces = self.get_kwarg(kwargs, None, "phase_indeces") if phase_uuids is not None: self.phase_matrix = np.array([[type(type(self)).object_pool.get_object(uuid) if uuid else None for uuid in row] for row in phase_uuids], dtype=np.object_) elif phase_indeces and self.parent is not None: warn("The use of object indices is deprecated since version 0.4. Please switch to using object UUIDs.", DeprecationWarning) self.phase_matrix = np.array([[self.parent.phases[index] if index != -1 else None for index in row] for row in phase_indeces], dtype=np.object_) else: self.phase_matrix = np.empty(shape=(0, 0), dtype=np.object_) # list with actual specimens, indexes match with rows in phase_matrix specimen_uuids = self.get_kwarg(kwargs, None, "specimen_uuids") specimen_indeces = self.get_kwarg(kwargs, None, "specimen_indeces") if specimen_uuids: self.specimens = [type(type(self)).object_pool.get_object(uuid) if uuid else None for uuid in specimen_uuids] elif specimen_indeces and self.parent is not None: warn("The use of object indices is deprecated since version 0.4. Please switch to using object UUIDs.", DeprecationWarning) self.specimens = [self.parent.specimens[index] if index != -1 else None for index in specimen_indeces] else: self.specimens = list() # list with mixture phase names, indexes match with cols in phase_matrix self.phases = self.get_kwarg(kwargs, list(), "phases", "data_phases") # list with scale values, indexes match with rows in phase_matrix (= specimens) self.scales = np.asarray(self.get_kwarg(kwargs, [1.0] * len(self.specimens), "scales", "data_scales")) # list with specimen background shift values, indexes match with rows in phase_matrix (=specimens) self.bgshifts = np.asarray(self.get_kwarg(kwargs, [0.0] * len(self.specimens), "bgshifts", "data_bgshifts")) # list with phase fractions, indexes match with cols in phase_matrix (=phases) self.fractions = np.asarray(self.get_kwarg(kwargs, [0.0] * len(self.phases), "fractions", "data_fractions")) # list with phase fractions mask, indexes match with cols in phase_matrix (=phases) self.fractions_mask = np.asarray(self.get_kwarg(kwargs, [1] * len(self.phases), "fractions_mask")) # sanity check: n, m = self.phase_matrix.shape if self.phase_matrix.ndim == 2 else (0, 0) if len(self.scales) != n or len(self.specimens) != n or len(self.bgshifts) != n: raise IndexError("Shape mismatch: scales (%d), background shifts (%d) or specimens (%d) list lengths do not match with row count (%d) of phase matrix" % (len(self.scales), len(self.specimens), len(self.bgshifts), n)) if len(self.phases) != m or len(self.fractions) != m: raise IndexError("Shape mismatch: fractions (%s) or phases (%d) lists do not match with column count of phase matrix (%d)" % (len(self.fractions), len(self.phases), m)) self._observe_specimens() self._observe_phases() self.optimizer = Optimizer(parent=self) self.refinement = Refinement( refine_method_index=self.get_kwarg(kwargs, 0, "refine_method_index", "refine_method", "data_refine_method"), refine_options=self.get_kwarg(kwargs, dict(), "refine_options"), parent=self) self.update() self.observe_model(self) pass # end hold data_changed # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @DataModel.observe("removed", signal=True) def notify_removed(self, model, prop_name, info): if model == self: self._relieve_phases() self._relieve_specimens() @DataModel.observe("needs_update", signal=True) def notify_needs_update(self, model, prop_name, info): with self.data_changed.hold(): self.update() @DataModel.observe("data_changed", signal=True) def notify_data_changed(self, model, prop_name, info): if not model == self and not ( info.arg == "based_on" and model.based_on is not None and model.based_on in self.phase_matrix): self.needs_update.emit() @DataModel.observe("visuals_changed", signal=True) def notify_visuals_changed(self, model, prop_name, info): if isinstance(model, Phase) and \ not (info.arg == "based_on" and model.based_on is not None and model.based_on in self.phase_matrix): for i, specimen in enumerate(self.specimens): if specimen is not None: specimen.update_visuals(self.phase_matrix[i, :]) # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ def json_properties(self): self.refinement.update_refinement_treestore() retval = Storable.json_properties(self) retval["phase_uuids"] = [[item.uuid if item else "" for item in row] for row in map(list, self.phase_matrix)] retval["specimen_uuids"] = [specimen.uuid if specimen else "" for specimen in self.specimens] retval["phases"] = self.phases retval["fractions"] = self.fractions.tolist() retval["fractions_mask"] = self.fractions_mask.tolist() retval["bgshifts"] = self.bgshifts.tolist() retval["scales"] = self.scales.tolist() return retval @staticmethod def from_json(**kwargs): # Remove this deprecated kwarg: if "refinables" in kwargs: del kwargs["refinables"] return Mixture(**kwargs) # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def unset_phase(self, phase): """ Clears a phase slot in the phase matrix """ with self.needs_update.hold_and_emit(): with self.data_changed.hold(): shape = self.phase_matrix.shape with self._relieve_and_observe_phases(): for i in range(shape[0]): for j in range(shape[1]): if self.phase_matrix[i, j] == phase: self.phase_matrix[i, j] = None self.refinement.update_refinement_treestore() def unset_specimen(self, specimen): """ Clears a specimen slot in the specimen list """ with self.needs_update.hold_and_emit(): with self.data_changed.hold(): with self._relieve_and_observe_specimens(): for i, spec in enumerate(self.specimens): if spec == specimen: self.specimens[i] = None def get_phase(self, specimen_slot, phase_slot): """Returns the phase at the given slot positions or None if not set""" return self.phase_matrix[specimen_slot, phase_slot] def set_phase(self, specimen_slot, phase_slot, phase): """Sets the phase at the given slot positions""" if self.parent is not None: #no parent = no valid phases with self.needs_update.hold_and_emit(): with self.data_changed.hold(): with self._relieve_and_observe_phases(): if phase is not None and not phase in self.parent.phases: raise RuntimeError("Cannot add a phase to a Mixture which is not inside the project!") self.phase_matrix[specimen_slot, phase_slot] = phase self.refinement.update_refinement_treestore() def get_specimen(self, specimen_slot): """Returns the specimen at the given slot position or None if not set""" return self.specimens[specimen_slot] def set_specimen(self, specimen_slot, specimen): """Sets the specimen at the given slot position""" if self.parent is not None: #no parent = no valid specimens with self.needs_update.hold_and_emit(): with self.data_changed.hold(): with self._relieve_and_observe_specimens(): if specimen is not None and not specimen in self.parent.specimens: raise RuntimeError("Cannot add a specimen to a Mixture which is not inside the project!") self.specimens[specimen_slot] = specimen @contextmanager def _relieve_and_observe_specimens(self): self._relieve_specimens() yield self._observe_specimens() def _observe_specimens(self): """ Starts observing specimens in the specimens list""" for specimen in self.specimens: if specimen is not None: self.observe_model(specimen) def _relieve_specimens(self): """ Relieves specimens observer calls """ for specimen in self.specimens: if specimen is not None: self.relieve_model(specimen) @contextmanager def _relieve_and_observe_phases(self): self._relieve_phases() yield self._observe_phases() def _observe_phases(self): """ Starts observing phases in the phase matrix""" for phase in self.phase_matrix.flat: if phase is not None: self.observe_model(phase) def _relieve_phases(self): """ Relieves phase observer calls """ for phase in self.phase_matrix.flat: if phase is not None: self.relieve_model(phase) def add_phase_slot(self, phase_name, fraction): """ Adds a new phase column to the phase matrix """ with self.needs_update.hold_and_emit(): with self.data_changed.hold(): self.phases.append(phase_name) self.fractions = np.append(self.fractions, fraction) self.fractions_mask = np.append(self.fractions_mask, 1) n, m = self.phase_matrix.shape if self.phase_matrix.ndim == 2 else (0, 0) if self.phase_matrix.size == 0: self.phase_matrix = np.resize(self.phase_matrix.copy(), (n, m + 1)) self.phase_matrix[:] = None else: self.phase_matrix = np.concatenate([self.phase_matrix.copy(), [[None]] * n ], axis=1) self.phase_matrix[:, m] = None self.refinement.update_refinement_treestore() return m def del_phase_slot(self, phase_slot): """ Deletes a phase column using its index """ with self.needs_update.hold_and_emit(): with self.data_changed.hold(): with self._relieve_and_observe_phases(): # Remove the corresponding phase name, fraction & references: del self.phases[phase_slot] self.fractions = np.delete(self.fractions, phase_slot) self.fractions_mask = np.delete(self.fractions_mask, phase_slot) self.phase_matrix = np.delete(self.phase_matrix, phase_slot, axis=1) # Update our refinement tree store to reflect current state self.refinement.update_refinement_treestore() # Inform any interested party they need to update their representation self.needs_reset.emit() def del_phase_slot_by_name(self, phase_name): """ Deletes a phase slot using its name """ self.del_phase_slot(self.phases.index(phase_name)) def add_specimen_slot(self, specimen, scale, bgs): """ Adds a new specimen to the phase matrix (a row) and specimen list """ with self.needs_update.hold_and_emit(): with self.data_changed.hold(): self.specimens.append(None) self.scales = np.append(self.scales, scale) self.bgshifts = np.append(self.bgshifts, bgs) n, m = self.phase_matrix.shape if self.phase_matrix.ndim == 2 else (0, 0) if self.phase_matrix.size == 0: self.phase_matrix = np.resize(self.phase_matrix.copy(), (n + 1, m)) self.phase_matrix[:] = None else: self.phase_matrix = np.concatenate([self.phase_matrix.copy(), [[None] * m] ], axis=0) self.phase_matrix[n, :] = None if specimen is not None: self.set_specimen(n, specimen) return n def del_specimen_slot(self, specimen_slot): """ Deletes a specimen slot using its slot index """ with self.needs_update.hold_and_emit(): with self.data_changed.hold(): # Remove the corresponding specimen name, scale, bg-shift & phases: with self._relieve_and_observe_specimens(): del self.specimens[specimen_slot] self.scales = np.delete(self.scales, specimen_slot) self.bgshifts = np.delete(self.bgshifts, specimen_slot) self.phase_matrix = np.delete(self.phase_matrix, specimen_slot, axis=0) # Update our refinement tree store to reflect current state self.refinement.update_refinement_treestore() # Inform any interested party they need to update their representation self.needs_reset.emit() def del_specimen_slot_by_object(self, specimen): """ Deletes a specimen slot using the actual object """ try: self.del_specimen_slot(self.specimens.index(specimen)) except ValueError: logger.exception("Caught a ValueError when deleting a specimen from mixture '%s'" % self.name) # ------------------------------------------------------------ # Refinement stuff: # ------------------------------------------------------------ def set_data_object(self, mixture, calculate=False): """ Sets the fractions, scales and bgshifts of this mixture. """ if mixture is not None: with self.needs_update.ignore(): with self.data_changed.hold_and_emit(): self.fractions[:] = list(mixture.fractions) self.scales[:] = list(mixture.scales) self.bgshifts[:] = list(mixture.bgshifts) # reset flag when needed: mixture.calculated = mixture.calculated and not calculate mixture = self.optimizer.calculate(mixture) for i, (specimen_data, specimen) in enumerate(zip(mixture.specimens, self.specimens)): if specimen is not None: with specimen.data_changed.ignore(): specimen.update_pattern( specimen_data.total_intensity, specimen_data.scaled_phase_intensities, self.phase_matrix[i, :] ) def optimize(self): """ Optimize the current solution (fractions, scales, bg shifts & calculate phase intensities) """ with self.needs_update.ignore(): with self.data_changed.hold(): # no need to re-calculate, is already done by the optimization self.set_data_object(self.optimizer.optimize()) def apply_current_data_object(self): """ Recalculates the intensities using the current fractions, scales and bg shifts without optimization """ with self.needs_update.ignore(): with self.data_changed.hold(): self.set_data_object(self.data_object, calculate=True) # @print_timing def update(self): """ Optimizes or re-applies the current mixture 'solution'. Effectively re-calculates the entire patterns. """ with self.needs_update.ignore(): with self.data_changed.hold(): if self.auto_run: self.optimize() else: self.apply_current_data_object() # ------------------------------------------------------------ # Various other things: # ------------------------------------------------------------ def get_composition_matrix(self): """ Returns a matrix containing the oxide composition for each specimen in this mixture. It uses the COMPOSITION_CONV file for this purpose to convert element weights into their oxide weight equivalent. """ # create an atom nr -> (atom name, conversion) mapping # this is used to transform most of the elements into their oxides atom_conv = OrderedDict() with open(settings.DATA_REG.get_file_path("COMPOSITION_CONV"), 'r') as f: reader = csv.reader(f) next(reader) # skip header for row in reader: nr, name, fact = row atom_conv[int(nr)] = (name, float(fact)) comps = list() for i, row in enumerate(self.phase_matrix): comp = dict() for j, phase in enumerate(row): phase_fract = self.fractions[j] for k, component in enumerate(phase.components): comp_fract = phase.probabilities.mW[k] * phase_fract for atom in chain(component.layer_atoms, component.interlayer_atoms): nr = atom.atom_type.atom_nr if nr in atom_conv: wt = atom.pn * atom.atom_type.weight * comp_fract * atom_conv[nr][1] comp[nr] = comp.get(nr, 0.0) + wt comps.append(comp) final_comps = np.zeros(shape=(len(atom_conv) + 1, len(comps) + 1), dtype='a15') final_comps[0, 0] = " "*8 for j, comp in enumerate(comps): fact = 100.0 / sum(comp.values()) for i, (nr, (oxide_name, conv)) in enumerate(atom_conv.items()): wt = comp.get(nr, 0.0) * fact # set relevant cells: if i == 0: final_comps[i, j + 1] = self.specimens[j].name.ljust(15)[:15] if j == 0: final_comps[i + 1, j] = ("%s " % oxide_name).rjust(8)[:8] final_comps[i + 1, j + 1] = ("%.1f" % wt).ljust(15)[:15] return final_comps pass # end of class PyXRD-0.8.4/pyxrd/mixture/models/optimizers.py000066400000000000000000000046461363064711000214110ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pyxrd.generic.models import ChildModel from pyxrd.calculations.mixture import ( calculate_mixture, calculate_and_optimize_mixture, get_residual, get_optimized_residual ) class Optimizer(ChildModel): """ A simple model that plugs onto the Mixture model. It provides the functionality related to optimizing the weight fractions, scales and background shifts and residual calculation for the phases. """ parent_alias = "mixture" # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def get_current_residual(self): """ Gets the residual for the current mixture solution. Convenience function. """ return self.get_residual() def get_optimized_residual(self, data_object=None): """ Gets an optimized residual for the current mixture setup. If no data_object is passed it is retrieved from the mixture. """ return get_optimized_residual(*self.get_data_object(data_object))[0] def get_residual(self, data_object=None): """ Calculates the residual for the given solution in combination with the given optimization arguments. If no data_object is passed it is retrieved from the mixture. """ return get_residual(*self.get_data_object(data_object))[0] def calculate(self, data_object=None): """ Calculates the total and phase intensities. If no data_object is passed it is retrieved from the mixture. """ return calculate_mixture(*self.get_data_object(data_object)) def optimize(self, data_object=None): """ Optimizes the mixture fractions, scales and bg shifts and returns the optimized result. If no data_object is passed it is retrieved from the mixture. """ try: return calculate_and_optimize_mixture(*self.get_data_object(data_object)) except AssertionError: return None def get_data_object(self, data_object=None): return (data_object if data_object is not None else self.parent.data_object,) pass # end of class PyXRD-0.8.4/pyxrd/mixture/views/000077500000000000000000000000001363064711000164725ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/mixture/views/__init__.py000066400000000000000000000007511363064711000206060ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .edit_mixture_view import EditMixtureView #from .edit_insitu_mixture_view import EditInSituMixtureView #from .edit_insitu_behaviour_view import EditInSituBehaviourView from .add_mixture_view import AddMixtureView __all__ = [ "EditMixtureView", #"EditInSituMixtureView", "AddMixtureView", #"EditInSituBehaviourView" ]PyXRD-0.8.4/pyxrd/mixture/views/add_insitu_behaviour_view.py000066400000000000000000000015331363064711000242670ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport from pyxrd.generic.views import DialogView class AddInSituBehaviourView(DialogView): title = "Add Behaviour" subview_builder = resource_filename(__name__, "glade/add_behaviour.glade") subview_toplevel = "add_behaviour_container" def __init__(self, *args, **kwargs): DialogView.__init__(self, *args, **kwargs) def get_behaviour_type(self): itr = self.behaviour_combo_box.get_active_iter() val = self.behaviour_combo_box.get_model().get_value(itr, 1) if itr else None return val @property def behaviour_combo_box(self): return self["cmb_behaviours"] pass # end of classPyXRD-0.8.4/pyxrd/mixture/views/add_mixture_view.py000066400000000000000000000027511363064711000224100ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from pyxrd.generic.views import DialogView class AddMixtureView(DialogView): title = "Add Mixture" subview_builder = resource_filename(__name__, "glade/add_mixture.glade") subview_toplevel = "add_mixture_container" active_type = "mixture" # | insitu def __init__(self, type_dict, *args, **kwargs): DialogView.__init__(self, *args, **kwargs) self.type_dict = type_dict self.active_type=list(type_dict.keys())[0] self.create_radios() def create_radios(self): box = self["add_mixture_box"] box.clear() group = None self.radios = [] for mixture_type, label in self.type_dict.items(): radio = Gtk.RadioButton.new(group, label) radio.mixture_type = mixture_type group = radio if group is None else group self.radios.append(radio) radio.connect('toggled', self.on_rdb_toggled) box.pack_start(radio, False, False, 2) def get_mixture_type(self): return self.active_type def on_rdb_toggled(self): for radio in self.radios: if radio.get_active(): self.active_type = radio.mixture_type pass # end of classPyXRD-0.8.4/pyxrd/mixture/views/edit_insitu_behaviour_view.py000066400000000000000000000025621363064711000244670ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from pyxrd.generic.views import BaseView class EditInSituBehaviourView(BaseView): builder = resource_filename(__name__, "glade/edit_insitu_behaviour.glade") top = "edit_insitu_behaviour" widget_format = "behaviour_%s" parameter_table = "edit_insitu_behaviour" def __init__(self, meta, **kwargs): assert (meta is not None), "EditInSituBehaviourView needs a model's Meta class!" BaseView.__init__(self, **kwargs) self.props = [ prop for prop in meta.all_properties if getattr(prop, "visible", False) ] def create_label(prop): new_lbl = Gtk.Label(label=prop.text) new_lbl.set_use_markup(True) return new_lbl def create_input(prop): new_inp = self.add_widget(prop) new_inp.set_name(self.widget_format % prop.label) return new_inp self.create_input_table( self[self.parameter_table], self.props, num_columns=1, widget_callbacks = [ create_label, create_input ] ) pass #end of classPyXRD-0.8.4/pyxrd/mixture/views/edit_insitu_mixture_view.py000066400000000000000000000235101363064711000241740ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk import numpy as np from pyxrd.generic.views import BaseView from pyxrd.generic.views.validators import FloatEntryValidator class EditInSituMixtureView(BaseView): builder = resource_filename(__name__, "glade/edit_mixture.glade") top = "edit_mixture" base_width = 4 base_height = 5 matrix_widget = "tbl_matrix" wrapper_widget = "tbl_wrapper" widget_format = "mixture_%s" def __init__(self, *args, **kwargs): BaseView.__init__(self, *args, **kwargs) self.parent.set_title("Edit In Situ Mixtures") self.matrix = self[self.matrix_widget] self.wrapper = self[self.wrapper_widget] self.labels = [ self["lbl_scales"], self["lbl_fractions"], self["lbl_phases"], self["lbl_bgshifts"], self["lbl_specimens"] ] self["scolled_window"].set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self.matrix.connect("size-request", self.on_size_requested) self.reset_view() def reset_view(self): def remove(item): if not item in self.labels: self.matrix.remove(item) self.matrix.foreach(remove) self.matrix.resize(self.base_height, self.base_width) self.phase_inputs = [] self.fraction_inputs = [] self.specimen_combos = [] self.scale_inputs = [] self.bgs_inputs = [] self.phase_combos = np.empty(shape=(0, 0), dtype=np.object_) # 2D list self.behav_combos = np.empty(shape=(0, 0), dtype=np.object_) # 2D list self.on_size_requested() def on_size_requested(self, *args): sr = self.matrix.size_request() self[self.top].set_size_request(sr[0] + 100, -1) def set_edit_view(self, view): if self._on_sr_id is not None and self.child_view is not None: self.child_view.disconnect(self._on_sr_id) self.edit_view = view self.child_view = view.get_top_widget() self._add_child_view(self.child_view, self[self.edit_view_container]) if isinstance(self[self.edit_view_container], Gtk.ScrolledWindow): sr = self.child_view.get_size_request() self[self.edit_view_container].set_size_request(sr[0], -1) def update_all(self, fractions, scales, bgs): for i, fraction in enumerate(fractions): if not i >= len(self.fraction_inputs): self.fraction_inputs[i].set_text(str(fraction)) for i, scale in enumerate(scales): if not i >= len(self.scale_inputs): self.scale_inputs[i].set_text(str(scale)) for i, bgs in enumerate(bgs): if not i >= len(self.bgs_inputs): self.bgs_inputs[i].set_text(str(bgs)) def add_phase_slot(self, phase_store, behav_store, del_phase_callback, label_callback, fraction_callback, phase_visible_callback, phase_combo_callback, behav_visible_callback, behav_combo_callback, label, fraction, phases, behavs): r, c = self.matrix.get_property('n_rows'), self.matrix.get_property('n_columns') self.matrix.resize(r + 1, c) del_icon = Gtk.Image() del_icon.set_from_stock ("192-circle-remove", Gtk.IconSize.SMALL_TOOLBAR) new_phase_del_btn = Gtk.Button() new_phase_del_btn.set_image(del_icon) rid = new_phase_del_btn.connect("clicked", del_phase_callback) setattr(new_phase_del_btn, "deleventid", rid) self.matrix.attach(new_phase_del_btn, 0, 1, r, r + 1, Gtk.AttachOptions.FILL, 0) new_phase_input = self._get_new_input(label, callback=label_callback) self.phase_inputs.append(new_phase_input) self.matrix.attach(new_phase_input, 1, 2, r, r + 1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0) new_fraction_input = self._get_new_input(str(fraction), callback=fraction_callback) FloatEntryValidator(new_fraction_input) self.fraction_inputs.append(new_fraction_input) self.matrix.attach(new_fraction_input, 2, 3, r, r + 1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0) self.phase_combos.resize((c - self.base_width, r + 1 - self.base_height)) self.behav_combos.resize((c - self.base_width, r + 1 - self.base_height)) for col in range(c - self.base_width): mcol, mrow = r - self.base_height, col self._add_new_phase_behav_combo( mrow, mcol, phase_store, phase_store.c_name, phases[mrow, mcol], phase_visible_callback, phase_combo_callback, behav_store, behav_store.c_name, behavs[mrow, mcol], behav_visible_callback, behav_combo_callback ) self.wrapper.show_all() def add_specimen_slot(self, phase_store, behav_store, specimen_store, del_specimen_callback, scale_callback, bgs_callback, specimen_callback, phase_visible_callback, phase_combo_callback, behav_visible_callback, behav_combo_callback, scale, bgs, specimen, phases, behavs): r, c = self.matrix.get_property('n_rows'), self.matrix.get_property('n_columns') self.matrix.resize(r, c + 1) del_icon = Gtk.Image() del_icon.set_from_stock("192-circle-remove", Gtk.IconSize.SMALL_TOOLBAR) new_specimen_del_btn = Gtk.Button() new_specimen_del_btn.set_image(del_icon) rid = new_specimen_del_btn.connect("clicked", del_specimen_callback) setattr(new_specimen_del_btn, "deleventid", rid) self.matrix.attach(new_specimen_del_btn, c, c + 1, 0, 1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0) new_specimen_combo = self._get_new_combo(specimen_store, specimen_store.c_name, specimen, None, specimen_callback) self.specimen_combos.append(new_specimen_combo) self.matrix.attach(new_specimen_combo, c, c + 1, 1, 2, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0) new_bgs_input = self._get_new_input(str(bgs), callback=bgs_callback) FloatEntryValidator(new_bgs_input) self.bgs_inputs.append(new_bgs_input) self.matrix.attach(new_bgs_input, c, c + 1, 2, 3, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0) new_scale_input = self._get_new_input(str(scale), callback=scale_callback) FloatEntryValidator(new_scale_input) self.scale_inputs.append(new_scale_input) self.matrix.attach(new_scale_input, c, c + 1, 3, 4, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0) self.phase_combos.resize((c + 1 - self.base_width, r - self.base_height)) self.behav_combos.resize((c + 1 - self.base_width, r - self.base_height)) for row in range(r - self.base_height): mcol, mrow = row, c - self.base_width self._add_new_phase_behav_combo( mrow, mcol, phase_store, phase_store.c_name, phases[mrow, mcol], phase_visible_callback, phase_combo_callback, behav_store, behav_store.c_name, behavs[mrow, mcol], behav_visible_callback, behav_combo_callback ) self.wrapper.show_all() def _get_new_input(self, text="", width=7, callback=None): """ Creates a new text input box. """ new_input = Gtk.Entry() new_input.set_text(text) new_input.set_alignment(0.0) new_input.set_width_chars(width) if callback is not None: new_input.connect("changed", callback) return new_input def _add_new_phase_behav_combo(self, r, c, phase_model, phase_text_column, default_phase, phase_visible_callback, phase_callback, behav_model, behav_text_column, default_behav, behav_visible_callback, behav_callback): """ Creates new 'phase' and 'behaviour' combo boxes, and adds them to the table at the given row and column indices. """ new_phase_combo = self._get_new_combo(phase_model, phase_text_column, default_phase, phase_visible_callback, phase_callback, r, c) self.phase_combos[r, c] = new_phase_combo new_behav_combo = self._get_new_combo(behav_model, behav_text_column, default_behav, behav_visible_callback, behav_callback, r, c) self.behav_combos[r, c] = new_behav_combo box = Gtk.HBox(spacing = 5) box.pack_start(new_phase_combo, True, True, padding = 2) box.pack_end(new_behav_combo, True, True, padding = 2) self.matrix.attach(box, self.base_width + r, self.base_width + r + 1, self.base_height + c, self.base_height + c + 1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0) def _get_new_combo(self, model, text_column, default, visible_callback, callback, *args): """ Creates a new combo box with the given model as ListStore, setting the given column as text column, the given default value set as active row, and connecting the given callback with 'changed' signal. """ combobox = Gtk.ComboBox(model) combobox.set_size_request(75, -1) cell = Gtk.CellRendererText() combobox.pack_start(cell, True) def combo_func(cmb, cell, model, itr, args): text = model.get_value(itr, text_column) cell.set_property('text', text) if callable(visible_callback): cell.set_property('sensitive', visible_callback(model, itr, args)) combobox.set_cell_data_func(cell, combo_func, args) if default is not None: index = model.on_get_path(default)[0] combobox.set_active(index) combobox.connect("changed", callback, *args) return combobox pass # end of class PyXRD-0.8.4/pyxrd/mixture/views/edit_mixture_view.py000066400000000000000000000213551363064711000226060ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk import numpy as np from pyxrd.generic.views import BaseView from pyxrd.generic.views.validators import FloatEntryValidator class EditMixtureView(BaseView): builder = resource_filename(__name__, "glade/edit_mixture.glade") top = "edit_mixture" base_width = 4 base_height = 5 matrix_widget = "tbl_matrix" wrapper_widget = "tbl_wrapper" widget_format = "mixture_%s" def __init__(self, *args, **kwargs): BaseView.__init__(self, *args, **kwargs) self.parent.set_title("Edit Mixtures") self.matrix = self[self.matrix_widget] self.wrapper = self[self.wrapper_widget] self.labels = [ self["lbl_scales"], self["lbl_fractions"], self["lbl_phases"], self["lbl_bgshifts"], self["lbl_specimens"], self["mixture_auto_scales"], self["mixture_auto_bg"] ] self["scolled_window"].set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) # TODO FIXME self.matrix.connect("size-request", self.on_size_requested) self.reset_view() def reset_view(self): def remove(item): if not item in self.labels: self.matrix.remove(item) self.matrix.foreach(remove) self.matrix.resize(self.base_height, self.base_width) self.phase_inputs = [] self.fraction_inputs = [] self.fraction_checks = [] self.specimen_combos = [] self.scale_inputs = [] self.bgs_inputs = [] self.phase_combos = np.empty(shape=(0, 0), dtype=np.object_) # 2D list self.on_size_requested() def on_size_requested(self, *args): sr = self.matrix.size_request() self[self.top].set_size_request(sr.width + 100, -1) def set_edit_view(self, view): if self._on_sr_id is not None and self.child_view is not None: self.child_view.disconnect(self._on_sr_id) self.edit_view = view self.child_view = view.get_top_widget() self._add_child_view(self.child_view, self[self.edit_view_container]) if isinstance(self[self.edit_view_container], Gtk.ScrolledWindow): sr = self.child_view.get_size_request() self[self.edit_view_container].set_size_request(sr[0], -1) def update_all(self, fractions, scales, bgs): for i, fraction in enumerate(fractions): if not i >= len(self.fraction_inputs): self.fraction_inputs[i].set_text(str(fraction)) for i, scale in enumerate(scales): if not i >= len(self.scale_inputs): self.scale_inputs[i].set_text(str(scale)) for i, bgs in enumerate(bgs): if not i >= len(self.bgs_inputs): self.bgs_inputs[i].set_text(str(bgs)) def add_phase_slot(self, phase_store, del_phase_callback, label_callback, check_callback, fraction_callback, combo_callback, label, fraction, phases): r, c = self.matrix.get_property('n_rows'), self.matrix.get_property('n_columns') self.matrix.resize(r + 1, c) del_icon = Gtk.Image.new() del_icon.set_from_stock ("192-circle-remove", Gtk.IconSize.SMALL_TOOLBAR) new_phase_del_btn = Gtk.Button.new() new_phase_del_btn.set_image(del_icon) rid = new_phase_del_btn.connect("clicked", del_phase_callback) setattr(new_phase_del_btn, "deleventid", rid) self.matrix.attach(new_phase_del_btn, 0, 1, r, r + 1, Gtk.AttachOptions.FILL, 0) new_phase_input = self._get_new_input(label, callback=label_callback) self.phase_inputs.append(new_phase_input) self.matrix.attach(new_phase_input, 1, 2, r, r + 1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0) new_fraction_check = self._get_new_check(callback=check_callback) self.fraction_checks.append(new_fraction_check) self.matrix.attach(new_fraction_check, 2, 3, r, r + 1, Gtk.AttachOptions.FILL, 0) new_fraction_input = self._get_new_input(str(fraction), callback=fraction_callback) FloatEntryValidator(new_fraction_input) self.fraction_inputs.append(new_fraction_input) self.matrix.attach(new_fraction_input, 3, 4, r, r + 1, Gtk.AttachOptions.FILL, 0) self.phase_combos.resize((c - self.base_width, r + 1 - self.base_height)) for col in range(c - self.base_width): mcol, mrow = r - self.base_height, col self._add_new_phase_combo(phase_store, phase_store.c_name, phases[mrow, mcol], mrow, mcol, combo_callback) self.wrapper.show_all() def add_specimen_slot(self, phase_store, specimen_store, del_specimen_callback, scale_callback, bgs_callback, specimen_callback, combo_callback, scale, bgs, specimen, phases): r, c = self.matrix.get_property('n_rows'), self.matrix.get_property('n_columns') self.matrix.resize(r, c + 1) del_icon = Gtk.Image.new() del_icon.set_from_stock("192-circle-remove", Gtk.IconSize.SMALL_TOOLBAR) new_specimen_del_btn = Gtk.Button.new() new_specimen_del_btn.set_image(del_icon) rid = new_specimen_del_btn.connect("clicked", del_specimen_callback) setattr(new_specimen_del_btn, "deleventid", rid) self.matrix.attach(new_specimen_del_btn, c, c + 1, 0, 1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0) new_specimen_combo = self._get_new_combo(specimen_store, specimen_store.c_name, default=specimen, callback=specimen_callback) self.specimen_combos.append(new_specimen_combo) self.matrix.attach(new_specimen_combo, c, c + 1, 1, 2, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0) new_bgs_input = self._get_new_input(str(bgs), callback=bgs_callback) FloatEntryValidator(new_bgs_input) self.bgs_inputs.append(new_bgs_input) self.matrix.attach(new_bgs_input, c, c + 1, 2, 3, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0) new_scale_input = self._get_new_input(str(scale), callback=scale_callback) FloatEntryValidator(new_scale_input) self.scale_inputs.append(new_scale_input) self.matrix.attach(new_scale_input, c, c + 1, 3, 4, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0) self.phase_combos.resize((c + 1 - self.base_width, r - self.base_height)) for row in range(r - self.base_height): mcol, mrow = row, c - self.base_width self._add_new_phase_combo(phase_store, phase_store.c_name, phases[mrow, mcol], mrow, mcol, combo_callback) self.wrapper.show_all() def _get_new_check(self, callback=None): """ Creates a new toggle button. """ new_check = Gtk.CheckButton.new() new_check.set_active(True) new_check.set_tooltip_text("Tick this box if you want to include this fraction in optimizations and refinements") if callback is not None: new_check.connect("toggled", callback) return new_check def _get_new_input(self, text="", width=7, callback=None): """ Creates a new text input box. """ new_input = Gtk.Entry.new() new_input.set_text(text) new_input.set_alignment(0.0) new_input.set_width_chars(width) if callback is not None: new_input.connect("changed", callback) return new_input def _add_new_phase_combo(self, model, text_column, default, r, c, callback): """ Creates a new 'phase slot' combo box, and adds it to the table at the given row and column indices. """ new_phase_combo = self._get_new_combo(model, text_column, default, callback, r, c) self.phase_combos[r, c] = new_phase_combo self.matrix.attach(new_phase_combo, self.base_width + r, self.base_width + r + 1, self.base_height + c, self.base_height + c + 1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 0) def _get_new_combo(self, model, text_column, default, callback, *args): """ Creates a new combo box with the given model as ListStore, setting the given column as text column, the given default value set as active row, and connecting the given callback with 'changed' signal. """ combobox = Gtk.ComboBox.new_with_model(model) combobox.set_size_request(75, -1) cell = Gtk.CellRendererText.new() combobox.pack_start(cell, True) combobox.add_attribute(cell, 'text', text_column) if default is not None: index = model.on_get_path(default)[0] combobox.set_active(index) combobox.connect("changed", callback, *args) return combobox pass # end of class PyXRD-0.8.4/pyxrd/mixture/views/glade/000077500000000000000000000000001363064711000175465ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/mixture/views/glade/add_behaviour.glade000066400000000000000000000035411363064711000233430ustar00rootroot00000000000000 True False True False True False Choose the type of behaviour: True True 0 True False True True 1 1 6 1 1 10 4 1 10 PyXRD-0.8.4/pyxrd/mixture/views/glade/add_behaviour.glade~000066400000000000000000000341221363064711000235400ustar00rootroot00000000000000 True False True False Create a new phase: True True False False 0 True True False False 2 0 True False True False 10 True False 20 # of components False False 0 True False 1 0 True True 5 True False False True True adj_G True True if-valid True True 1 False True 0 True False 10 True False 20 Reichweite False False 0 True False 1 0 True True 5 True False False True True adj_R True True if-valid True True 1 False True 1 False True 1 Choose a default phase: True True False False 0 True True rdb_empty_phase False False 2 2 True False False 5 True False 0 20 True False True True 0 True False True False True False 10 False True True True 0 True False 085-repeat True True 1 False True True Generating phases ... False True 2 False True 1 False True 3 Add a raw pattern True True False False 0 True rdb_empty_phase False False 2 4 1 6 1 1 10 4 1 10 PyXRD-0.8.4/pyxrd/mixture/views/glade/add_mixture.glade000066400000000000000000000044261363064711000230570ustar00rootroot00000000000000 True False True False Create a regular mixture False True True False 0 True True False False 2 0 Create an in-situ mixture False True True False 0 True True rdb_mixture False False 2 1 PyXRD-0.8.4/pyxrd/mixture/views/glade/edit_insitu_behaviour.glade000066400000000000000000000016721363064711000251360ustar00rootroot00000000000000 True False 5 7 2 5 PyXRD-0.8.4/pyxrd/mixture/views/glade/edit_mixture.glade000066400000000000000000000435551363064711000232620ustar00rootroot00000000000000 True False 190-circle-plus True False 211-right-arrow True False 212-down-arrow True False 323-calculator True False 009-magic 380 True False 10 5 True False 10 True False Mixture name False True 0 True True 20 False False False True 1 False True 0 True False False True True False True Run automatically when patterns have updated. Run automatically when patterns have updated. 0 True False False 0 False True True True True False 10 True False 040-stats True True 0 True False Optimize 1 True True 3 False True 1 Refine False True True True img_refine False True 10 2 Composition False True True True img_composition False True end 3 False True 1 True False 2 2 False 24 True True True True Add a specimen column to this mixture Add a specimen column to this mixture img_button_add_phase 1 2 GTK_FILL GTK_FILL False 24 True True True True Add a phase row to this mixture Add a phase row to this mixture img_button_add_specimen 1 2 GTK_FILL GTK_FILL 280 True True 700 700 True False 5 5 4 5 5 True False Specimens 1 1 4 1 2 GTK_FILL True False Fractions 1 1 2 4 4 5 GTK_SHRINK | GTK_FILL GTK_FILL True False Name 1 1 2 4 5 GTK_FILL False True True False Tick this box if you want to turn on automatic background refinement. 0 True 2 3 3 4 True False Abs. scale 1 3 4 3 4 GTK_FILL False True True False Tick this box if you want to turn on automatic background refinement. 0 True 2 3 2 3 True False Bg. shift 1 3 4 2 3 GTK_FILL False 24 True True True True Add a phase column and a specimen row to this mixture Add a phase column and a specimen row to this mixture img_button_add_both 1 2 1 2 GTK_FILL GTK_FILL True True 2 PyXRD-0.8.4/pyxrd/phases/000077500000000000000000000000001363064711000151235ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/phases/__init__.py000066400000000000000000000000001363064711000172220ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/phases/controllers/000077500000000000000000000000001363064711000174715ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/phases/controllers/CSDS_controllers.py000066400000000000000000000103621363064711000232270ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from mvc import Controller from pyxrd.generic.controllers import BaseController from pyxrd.phases.models.CSDS import CSDS_distribution_types class EditCSDSTypeController(BaseController): """ Controller for the selection of the type of CSDS Model """ auto_adapt = False distributions_controller = None def reset_type_store(self): if self.view is not None: combo = self.view["cmb_type"] store = Gtk.ListStore(str, object) # @UndefinedVariable for cls in CSDS_distribution_types: store.append([cls.Meta.description, cls]) combo.set_model(store) for row in store: if type(self.model.CSDS_distribution) == store.get_value(row.iter, 1): combo.set_active_iter(row.iter) break return store def register_view(self, view): self.view = view combo = self.view["cmb_type"] combo.connect('changed', self.on_changed) cell = Gtk.CellRendererText() # @UndefinedVariable combo.pack_start(cell, True) combo.add_attribute(cell, 'markup', 0) self.reset_type_store() self.reset_distributions_controller() @BaseController.model.setter def _set_model(self, model): super(EditCSDSTypeController, self)._set_model(model) self.reset_distributions_controller() def reset_distributions_controller(self): if self.view is not None: if self.distributions_controller is None: self.distributions_controller = EditCSDSDistributionController( model=self.model.CSDS_distribution, view=self.view, parent=self) else: self.distributions_controller.model = self.model.CSDS_distribution # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_changed(self, combo, user_data=None): itr = combo.get_active_iter() if itr is not None: cls = combo.get_model().get_value(itr, 1) if not type(self.model.CSDS_distribution) == cls: new_csds_model = cls(parent=self.model) self.model.CSDS_distribution = new_csds_model self.distributions_controller.model = new_csds_model pass # end of class class EditCSDSDistributionController(BaseController): """ Controller for the CSDS Models Handles the creation of widgets based on the property descriptor settings """ auto_adapt = False def reset_view(self): if self.view is not None: self.view.reset_params() for prop in self.model.Meta.all_properties: if getattr(prop, "refinable", False): self.view.add_param_widget( self.view.widget_format % prop.label, prop.label, prop.minimum, prop.maximum ) self.view.update_figure(self.model.distrib[0]) self.register_adapters() self.adapt() def register_view(self, view): if self.model is not None: self.reset_view() @BaseController.model.setter def model(self, model): super(EditCSDSDistributionController, self)._set_model(model) self.reset_view() # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @Controller.observe("updated", signal=True) def notif_updated(self, model, prop_name, info): if self.model.distrib is not None and not self.model.phase.project.before_needs_update_lock: try: self.view.update_figure(self.model.distrib[0]) except BaseException as error: logger.exception("Caught unhandled exception: %s" % error) pass # end of class PyXRD-0.8.4/pyxrd/phases/controllers/__init__.py000066400000000000000000000014661363064711000216110ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .atom_relation_controllers import ( EditUnitCellPropertyController, EditAtomRatioController, EditAtomContentsController, ContentsListController, EditAtomRelationsController ) from .CSDS_controllers import ( EditCSDSTypeController, EditCSDSDistributionController ) from .layer_controllers import EditLayerController from .component_controllers import ( ComponentsController, EditComponentController ) from .add_phase_controller import AddPhaseController from .edit_phase_controller import EditPhaseController from .raw_pattern_phase_controller import EditRawPatternPhaseController from .phases_controller import PhasesControllerPyXRD-0.8.4/pyxrd/phases/controllers/add_phase_controller.py000066400000000000000000000101371363064711000242200ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk import logging logger = logging.getLogger(__name__) from mvc.adapters.gtk_support.treemodels.utils import create_treestore_from_directory from pyxrd.data import settings from pyxrd.generic.views.combobox_tools import add_combo_text_column from pyxrd.generic.controllers import DialogController from pyxrd.probabilities.models import get_Gbounds_for_R, get_Rbounds_for_G class AddPhaseController(DialogController): """ Controller for the add phase dialog """ auto_adapt = False def __init__(self, model=None, view=None, parent=None, callback=None): super(AddPhaseController, self).__init__( model=model, view=view, parent=parent) self.callback = callback def register_view(self, view): self.update_bounds() self.generate_combo() def register_adapters(self): pass # has no intel, or a model! def update_R_bounds(self): if self.view is not None: min_R, max_R, R = get_Rbounds_for_G( self.view.get_G(), self.view.get_R()) self.view["adj_R"].set_upper(max_R) self.view["adj_R"].set_lower(min_R) self.view["R"].set_value(R) def update_G_bounds(self): if self.view is not None: min_G, max_G, G = get_Gbounds_for_R( self.view.get_R(), self.view.get_G()) self.view["adj_G"].set_upper(max_G) self.view["adj_G"].set_lower(min_G) self.view["G"].set_value(G) def update_bounds(self): self.update_G_bounds() self.update_R_bounds() def generate_combo(self): self.reload_combo_model() add_combo_text_column( self.view.phase_combo_box, text_col=0, sensitive_col=2) def reload_combo_model(self): cmb_model = create_treestore_from_directory( settings.DATA_REG.get_directory_path("DEFAULT_PHASES")) self.view.phase_combo_box.set_model(cmb_model) # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_btn_ok_clicked(self, event): self.view.hide() self.callback( self.view.get_phase_type(), self.view.get_G(), self.view.get_R()) return True def on_rdb_toggled(self, widget): self.view.update_sensitivities() def on_btn_generate_phases_clicked(self, event): from pyxrd.scripts.generate_default_phases import run def ui_callback(progress): self.view["gen_progress_bar"].set_fraction(progress) while Gtk.events_pending(): Gtk.main_iteration() self.view["img_repeat"].set_visible(False) self.view["gen_spinner"].start() self.view["gen_spinner"].set_visible(True) self.view["gen_progress_bar"].set_visible(True) while Gtk.events_pending(): Gtk.main_iteration() run(ui_callback=ui_callback) self.view["gen_progress_bar"].set_visible(False) self.view["img_repeat"].set_visible(True) self.view["gen_spinner"].stop() self.view["gen_spinner"].set_visible(False) self.reload_combo_model() return True def on_r_value_changed(self, *args): self.update_G_bounds() return True def on_g_value_changed(self, *args): self.update_R_bounds() return True def on_keypress(self, widget, event): if event.keyval == Gdk.keyval_from_name("Escape"): self.view.hide() return True if event.keyval == Gdk.keyval_from_name("Enter"): self.view.hide() self.callback( self.view.get_phase_type(), self.view.get_G(), self.view.get_R()) return True def on_window_edit_dialog_delete_event(self, event, args=None): self.view.hide() return True # do not propagate pass # end of classPyXRD-0.8.4/pyxrd/phases/controllers/atom_relation_controllers.py000066400000000000000000000261701363064711000253340ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Pango from pyxrd.generic.views.treeview_tools import ( new_text_column, new_pb_column, new_combo_column, create_float_data_func, setup_treeview ) from pyxrd.generic.views.combobox_tools import add_combo_text_column from pyxrd.generic.views import InlineObjectListStoreView from pyxrd.generic.controllers import ( DialogController, InlineObjectListStoreController, BaseController, ) from pyxrd.phases.views import EditAtomRatioView, EditAtomContentsView from pyxrd.phases.models.atom_relations import AtomRelation, AtomRatio, AtomContents, AtomContentObject class AtomComboMixin(object): extra_props = [] custom_handler_names = [] def reset_combo_box(self, name): if self.model.component is not None: # Get store, reset combo store = self.model.create_prop_store(self.extra_props) combo = self.view[self.view.widget_format % name] combo.clear() combo.set_model(store) # Add text column: def get_name(layout, cell, model, itr, data=None): obj, lbl = model.get(itr, 0, 2) if callable(lbl): lbl = lbl(obj) cell.set_property("markup", lbl) add_combo_text_column(combo, data_func=get_name) # Set the selected item to active: prop = getattr(self.model, name) if prop is not None: prop = tuple(prop) for row in store: if tuple(store.get(row.iter, 0, 1)) == prop: combo.set_active_iter(row.iter) break return combo, store else: return None, None @staticmethod def custom_handler(controller, prop, prefix): if prop.label in controller.custom_handler_names: combo, store = controller.reset_combo_box(prop.label) # @UnusedVariable if combo is not None and store is not None: def on_changed(combo, user_data=None): itr = combo.get_active_iter() if itr is not None: val = combo.get_model().get(itr, 0, 1) setattr(controller.model, getattr(combo, 'model_prop'), val) setattr(combo, 'model_prop', prop.label) combo.connect('changed', on_changed) def on_item_changed(*args): controller.reset_combo_box(prop.label) if controller.is_observing_method("atoms_changed", on_item_changed): controller.remove_observing_method("atoms_changed", on_item_changed) controller.observe(on_item_changed, "atoms_changed", signal=True) else: return False return True pass # end of class class EditUnitCellPropertyController(BaseController, AtomComboMixin): """ Controller for the UnitCellProperty models (a and b cell lengths) """ custom_handler_names = ["prop", ] widget_handlers = { 'combo': 'custom_handler', } def __init__(self, extra_props, **kwargs): super(EditUnitCellPropertyController, self).__init__(**kwargs) self.extra_props = extra_props def register_adapters(self): BaseController.register_adapters(self) self.update_sensitivities() def update_sensitivities(self): self.view['ucp_value'].set_sensitive(not self.model.enabled) self.view['box_enabled'].set_sensitive(self.model.enabled) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @BaseController.observe("enabled", assign=True) def notif_enabled_changed(self, model, prop_name, info): self.update_sensitivities() pass # end of class class EditAtomRatioController(DialogController, AtomComboMixin): """ Controller for the atom ratio edit dialog """ custom_handler_names = ["atom1", "atom2"] widget_handlers = { 'custom': 'custom_handler', } pass # end of class class EditAtomContentsController(DialogController): """ Controller for the atom contents edit dialog """ auto_adapt_excluded = [ "atom_contents", ] contents_list_view = None contents_list_controller = None def __init__(self, *args, **kwargs): super(EditAtomContentsController, self).__init__(*args, **kwargs) # Create atom contents controller: self.contents_list_view = InlineObjectListStoreView(parent=self.view) self.contents_list_controller = ContentsListController("atom_contents", model=self.model, view=self.contents_list_view, parent=self) # Set subview: self.view.set_contents_list_view(self.contents_list_view.get_top_widget()) pass # end of class class ContentsListController(InlineObjectListStoreController): """ Controller for the atom contents ListStore """ new_val = None auto_adapt = False # FIXME treemodel_class_type = AtomContentObject def _reset_treeview(self, tv, model): setup_treeview(tv, model, sel_mode='MULTIPLE', reset=True) tv.set_model(model) # Atom column: self.combo_model = self.model.create_prop_store() self.combo_model2 = Gtk.ListStore(str) for row in self.combo_model: self.combo_model2.append(row[2:3]) def atom_renderer(column, cell, model, itr, *args): obj = model.get_value(itr, 0) if hasattr(obj, "name"): cell.set_property('text', obj.name) else: cell.set_property('text', '#NA#') tv.append_column(new_combo_column( "Atoms", data_func=atom_renderer, changed_callback=self.on_atom_changed, edited_callback=self.on_atom_edited, xalign=0.0, expand=False, has_entry=False, model=self.combo_model2, text_column=0, editable=True)) # Content column: def on_float_edited(rend, path, new_text, col): itr = model.get_iter(path) try: model.set_value(itr, col, float(new_text)) except ValueError: logger.exception("Invalid value entered ('%s')!" % new_text) return True tv.append_column(new_text_column('Default contents', text_col=2, xalign=0.0, editable=True, data_func=create_float_data_func(), edited_callback=(on_float_edited, (2,)))) def _setup_treeview(self, tv, model): self._reset_treeview(tv, model) def __init__(self, treemodel_property_name, **kwargs): super(ContentsListController, self).__init__( treemodel_property_name=treemodel_property_name, enable_import=False, enable_export=False, **kwargs ) def create_new_object_proxy(self): return AtomContentObject(None, None, 1.0) def on_atom_changed(self, combo, path, new_iter): # translate dummy iter to real iter: new_iter = self.combo_model.get_iter(self.combo_model2.get_path(new_iter)) self.new_val = self.combo_model.get(new_iter, 0, 1) pass def on_atom_edited(self, combo, path, new_text, model=None): if self.new_val: new_atom, new_prop = self.new_val self.model.set_atom_content_values(path, new_atom, new_prop) self.new_val = None return True pass # end of class class EditAtomRelationsController(InlineObjectListStoreController): """ Controller for the components' atom relations ObjectListStore """ file_filters = AtomRelation.Meta.file_filters auto_adapt = False treemodel_class_type = AtomRelation add_types = [ ("Ratio", AtomRatio, EditAtomRatioView, EditAtomRatioController), ("Contents", AtomContents, EditAtomContentsView, EditAtomContentsController), ] def _reset_treeview(self, tv, model): setup_treeview(tv, model, sel_mode='MULTIPLE', reset=True) tv.set_model(model) # Name column: def text_renderer(column, cell, model, itr, args=None): driven_by_other = model.get_value(itr, model.c_driven_by_other) cell.set_property('editable', not driven_by_other) cell.set_property('style', Pango.Style.ITALIC if driven_by_other else Pango.Style.NORMAL) col = new_text_column( 'Name', data_func=text_renderer, editable=True, edited_callback=(self.on_item_cell_edited, (model, model.c_name)), resizable=False, text_col=model.c_name) setattr(col, "col_descr", 'Name') tv.append_column(col) # Value of the relation: float_rend = create_float_data_func() def data_renderer(column, cell, model, itr, args=None): text_renderer(column, cell, model, itr, args) float_rend(column, cell, model, itr, args) col = new_text_column( 'Value', data_func=data_renderer, editable=True, edited_callback=(self.on_item_cell_edited, (model, model.c_value)), resizable=False, text_col=model.c_value) setattr(col, "col_descr", 'Value') tv.append_column(col) # Up, down and edit arrows: def setup_image_button(image, colnr): col = new_pb_column(" ", resizable=False, expand=False, stock_id=image) setattr(col, "col_descr", colnr) tv.append_column(col) setup_image_button("213-up-arrow", "Up") setup_image_button("212-down-arrow", "Down") setup_image_button("030-pencil", "Edit") def _setup_treeview(self, tv, model): tv.connect('button-press-event', self.tv_button_press) self._reset_treeview(tv, model) def __init__(self, **kwargs): super(EditAtomRelationsController, self).__init__( enable_import=False, enable_export=False, **kwargs) def create_new_object_proxy(self): return self.add_type(parent=self.model) def tv_button_press(self, tv, event): relation = None ret = tv.get_path_at_pos(int(event.x), int(event.y)) if ret is not None: path, col, x, y = ret model = tv.get_model() relation = model.get_user_data_from_path(path) column = getattr(col, "col_descr") if event.button == 1 and relation is not None: column = getattr(col, "col_descr") if column == "Edit": self._edit_item(relation) return True elif column == "Up": self.model.move_atom_relation_up(relation) return True elif column == "Down": self.model.move_atom_relation_down(relation) return True pass # end of class PyXRD-0.8.4/pyxrd/phases/controllers/component_controllers.py000066400000000000000000000217101363064711000244740ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory logger = logging.getLogger(__name__) from mvc import Controller from mvc.adapters.dummy_adapter import DummyAdapter from pyxrd.generic.views import InlineObjectListStoreView from pyxrd.generic.views.combobox_tools import add_combo_text_column from pyxrd.generic.controllers import BaseController, ChildObjectListStoreController from pyxrd.generic.io.utils import get_case_insensitive_glob from pyxrd.phases.controllers import EditLayerController, EditAtomRelationsController, EditUnitCellPropertyController from pyxrd.phases.views import EditComponentView, EditUnitCellPropertyView from pyxrd.phases.models import Phase, Component from pyxrd.generic.controllers.objectliststore_controllers import wrap_list_property_to_treemodel class EditComponentController(BaseController): """ Controller for the component edit view """ layer_view = None layer_controller = None interlayer_view = None interlayer_controller = None atom_relations_view = None atom_relations_controller = None ucpa_view = None ucpa_controller = None ucpb_view = None ucpb_controller = None widget_handlers = { 'custom': 'custom_handler', } @property def components_treemodel(self): if self.model.phase.based_on: return wrap_list_property_to_treemodel( self.model.phase.based_on, Phase.components ) else: return None def reset_combo_box(self): """ Reset the `linked_with` combo box. """ if self.model is not None and self.model.parent is not None: combo = self.view["component_linked_with"] combo.clear() combo.set_model(self.components_treemodel) if self.components_treemodel is not None: add_combo_text_column(combo, text_col=self.components_treemodel.c_name) for row in self.components_treemodel: comp = self.components_treemodel.get_user_data(row.iter) if comp == self.model.linked_with: combo.set_active_iter (row.iter) break @staticmethod def custom_handler(self, prop, widget): if prop.label == "layer_atoms": self.view.set_layer_view(self.layer_view.get_top_widget()) elif prop.label == "interlayer_atoms": self.view.set_interlayer_view(self.interlayer_view.get_top_widget()) elif prop.label == "atom_relations": self.view.set_atom_relations_view(self.atom_relations_view.get_top_widget()) elif prop.label in ("ucp_a", "ucp_b"): self.view.set_ucpa_view(self.ucpa_view.get_top_widget()) self.view.set_ucpb_view(self.ucpb_view.get_top_widget()) elif prop.label == "linked_with": self.reset_combo_box() return DummyAdapter(controller=self, prop=prop) def register_view(self, view): super(EditComponentController, self).register_view(view) self.layer_view = InlineObjectListStoreView(parent=view) self.layer_controller = EditLayerController(treemodel_property_name="layer_atoms", model=self.model, view=self.layer_view, parent=self) self.interlayer_view = InlineObjectListStoreView(parent=view) self.interlayer_controller = EditLayerController(treemodel_property_name="interlayer_atoms", model=self.model, view=self.interlayer_view, parent=self) self.atom_relations_view = InlineObjectListStoreView(parent=view) self.atom_relations_controller = EditAtomRelationsController(treemodel_property_name="atom_relations", model=self.model, view=self.atom_relations_view, parent=self) self.ucpa_view = EditUnitCellPropertyView(parent=view) self.ucpa_controller = EditUnitCellPropertyController(extra_props=[(self.model, "cell_b", "B cell length"), ], model=self.model._ucp_a, view=self.ucpa_view, parent=self) self.ucpb_view = EditUnitCellPropertyView(parent=view) self.ucpb_controller = EditUnitCellPropertyController(extra_props=[(self.model, "cell_a", "A cell length"), ], model=self.model._ucp_b, view=self.ucpb_view, parent=self) def register_adapters(self): self.update_sensitivities() def update_sensitivities(self): can_inherit = (self.model.linked_with is not None) def update(widget, name): self.view[widget].set_sensitive(not (can_inherit and getattr(self.model, "inherit_%s" % name))) self.view[widget].set_visible(not (can_inherit and getattr(self.model, "inherit_%s" % name))) self.view["component_inherit_%s" % name].set_sensitive(can_inherit) for name in ("default_c", "delta_c", "d001"): update("container_%s" % name, name) for name in ("interlayer_atoms", "layer_atoms", "atom_relations", "ucp_a", "ucp_b"): update(self.view.widget_format % name, name) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @Controller.observe("inherit_layer_atoms", assign=True) @Controller.observe("inherit_interlayer_atoms", assign=True) @Controller.observe("inherit_atom_relations", assign=True) @Controller.observe("inherit_ucp_a", assign=True) @Controller.observe("inherit_ucp_b", assign=True) @Controller.observe("inherit_d001", assign=True) @Controller.observe("inherit_default_c", assign=True) @Controller.observe("inherit_delta_c", assign=True) def notif_change_inherit(self, model, prop_name, info): self.update_sensitivities() @Controller.observe("linked_with", assign=True) def notif_linked_with_changed(self, model, prop_name, info): self.reset_combo_box() # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_linked_with_changed(self, combo, user_data=None): itr = combo.get_active_iter() if itr is not None: val = combo.get_model().get_user_data(itr) self.model.linked_with = val self.update_sensitivities() return combo.set_active(-1) self.update_sensitivities() self.model.linked_with = None class ComponentsController(ChildObjectListStoreController): """ Controller for the components ObjectListStore """ treemodel_property_name = "components" treemodel_class_type = Component columns = [ ("Component name", "c_name") ] delete_msg = "Deleting a component is irreversible!\nAre You sure you want to continue?" file_filters = [("Component file", get_case_insensitive_glob("*.CMP")), ] obj_type_map = [ (Component, EditComponentView, EditComponentController), ] def load_components(self, filename): old_comps = self.get_selected_objects() if old_comps: num_oc = len(old_comps) new_comps = list() for comp in Component.load_components(filename, parent=self.model): comp.resolve_json_references() new_comps.append(comp) num_nc = len(new_comps) if num_oc != num_nc: DialogFactory.get_information_dialog( "The number of components to import must equal the number of selected components!" ).run() return else: self.select_object(None) logger.info("Importing components...") # replace component(s): for old_comp, new_comp in zip(old_comps, new_comps): i = self.model.components.index(old_comp) self.model.components[i] = new_comp else: DialogFactory.get_information_dialog( "No components selected to replace!" ).run() # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_save_object_clicked(self, event): def on_accept(dialog): logger.info("Exporting components...") Component.save_components(self.get_selected_objects(), filename=dialog.filename) DialogFactory.get_save_dialog( "Export components", parent=self.view.get_toplevel(), filters=self.file_filters ).run(on_accept) return True def on_load_object_clicked(self, event): def on_accept(dialog): self.load_components(dialog.filename) DialogFactory.get_load_dialog( "Import components", parent=self.view.get_toplevel(), filters=self.file_filters ).run(on_accept) return True pass #end of class PyXRD-0.8.4/pyxrd/phases/controllers/edit_phase_controller.py000066400000000000000000000161241363064711000244170ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) from mvc import Controller from mvc.adapters.dummy_adapter import DummyAdapter from pyxrd.generic.views import ChildObjectListStoreView from pyxrd.generic.views.combobox_tools import add_combo_text_column from pyxrd.generic.controllers import BaseController from pyxrd.generic.controllers.objectliststore_controllers import wrap_list_property_to_treemodel from pyxrd.probabilities.controllers import EditProbabilitiesController from pyxrd.probabilities.views import EditProbabilitiesView from pyxrd.phases.controllers import ( EditCSDSTypeController, ComponentsController ) from pyxrd.phases.views import EditCSDSDistributionView class EditPhaseController(BaseController): """ Controller for the phase edit view """ probabilities_view = None probabilities_controller = None csds_view = None csds_controller = None components_view = None components_controller = None widget_handlers = { 'custom': 'custom_handler', } @property def phases_treemodel(self): if self.model.project is not None: return wrap_list_property_to_treemodel( self.model.project, type(self.model.project).phases) else: return None def register_view(self, view): BaseController.register_view(self, view) self.csds_view = EditCSDSDistributionView(parent=self.view) self.view.set_csds_view(self.csds_view) if self.model.G > 1: self.probabilities_view = EditProbabilitiesView(parent=self.view) self.view.set_probabilities_view(self.probabilities_view) else: self.view.remove_probabilities() self.components_view = ChildObjectListStoreView(parent=self.view) self.components_view["button_add_object"].set_visible(False) self.components_view["button_add_object"].set_no_show_all(True) self.components_view["button_del_object"].set_visible(False) self.components_view["button_del_object"].set_no_show_all(True) self.view.set_components_view(self.components_view) @staticmethod def custom_handler(self, prop, widget): # TODO split out these 4 properties in their own adapters if prop.label in ("CSDS_distribution", "components", "probabilities", "based_on"): if prop.label == "CSDS_distribution": self.reset_csds_controller() elif prop.label == "components": self.reset_components_controller() elif prop.label == "probabilities": self.reset_probabilities_controller() elif prop.label == "based_on" and self.phases_treemodel is not None: combo = self.view["phase_based_on"] combo.set_model(self.phases_treemodel) combo.connect('changed', self.on_based_on_changed) def phase_renderer(celllayout, cell, model, itr, user_data=None): phase = model.get_user_data(itr) if phase: # FIXME an error can occur here if the phase list is cleared and the view is still open cell.set_sensitive(phase.R == self.model.R and phase.G == self.model.G and phase.get_based_on_root() != self.model) add_combo_text_column(combo, data_func=phase_renderer, text_col=self.phases_treemodel.c_name) for row in self.phases_treemodel: if self.phases_treemodel.get_user_data(row.iter) == self.model.based_on: combo.set_active_iter (row.iter) break return DummyAdapter(controller=self, prop=prop) def reset_csds_controller(self): if self.csds_controller is None: self.csds_controller = EditCSDSTypeController( model=self.model, view=self.csds_view, parent=self) else: self.csds_controller.model = self.model def reset_components_controller(self): self.components_controller = ComponentsController( model=self.model, view=self.components_view, parent=self) def reset_probabilities_controller(self): if self.probabilities_controller is None: if self.model.G > 1: # False if model is a multi-component phase self.probabilities_controller = EditProbabilitiesController( model=self.model.probabilities, view=self.probabilities_view, parent=self) else: self.probabilities_controller.model = self.model.probabilities def register_adapters(self): self.update_sensitivities() def update_sensitivities(self): can_inherit = (self.model.based_on is not None) for name in ("sigma_star", "display_color"): widget_name = "container_%s" % name self.view[widget_name].set_sensitive(not (can_inherit and getattr(self.model, "inherit_%s" % name))) self.view[widget_name].set_visible(not (can_inherit and getattr(self.model, "inherit_%s" % name))) self.view["phase_inherit_%s" % name].set_sensitive(can_inherit) for name in ("CSDS_distribution",): sensitive = not (can_inherit and getattr(self.model, "inherit_%s" % name)) self.view["phase_inherit_%s" % name].set_sensitive(can_inherit) self.view.set_csds_sensitive(sensitive) self.reset_csds_controller() # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @Controller.observe("inherit_display_color", assign=True) @Controller.observe("inherit_sigma_star", assign=True) @Controller.observe("inherit_CSDS_distribution", assign=True) def notif_change_inherit(self, model, prop_name, info): self.update_sensitivities() return @Controller.observe("probabilities", assign=True) def notif_change_probabilities(self, model, prop_name, info): self.reset_probabilities_controller() return @Controller.observe("name", assign=True) def notif_name_changed(self, model, prop_name, info): self.phases_treemodel.on_item_changed(self.model) return # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_based_on_changed(self, combo, user_data=None): itr = combo.get_active_iter() if itr is not None: val = combo.get_model().get_user_data(itr) # cannot be based on itself == not based on anything # cannot be based on a model with a different # of components if val != self.model and val.get_based_on_root() != self.model and val.G == self.model.G: self.model.based_on = val self.update_sensitivities() return combo.set_active(-1) self.update_sensitivities() self.model.based_on = None PyXRD-0.8.4/pyxrd/phases/controllers/layer_controllers.py000066400000000000000000000133771363064711000236200ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory from pyxrd.generic.views.treeview_tools import new_text_column, new_combo_column, create_float_data_func, setup_treeview from pyxrd.generic.controllers.objectliststore_controllers import wrap_list_property_to_treemodel from pyxrd.generic.controllers import InlineObjectListStoreController from pyxrd.atoms.models import Atom from pyxrd.project.models import Project class EditLayerController(InlineObjectListStoreController): """ Controller for the (inter)layer atom ObjectListStores """ auto_adapt = False enable_import = True enable_export = True treemodel_class_type = Atom file_filters = Atom.Meta.layer_filters new_atom_type = None @property def atom_types_treemodel(self): return wrap_list_property_to_treemodel( self.model.phase.project, Project.atom_types) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def _setup_treeview(self, tv, model): setup_treeview(tv, model, sel_mode='MULTIPLE', reset=True) tv.set_model(model) # Add Atom name, default z, calculated z and #: def add_text_col(title, colnr, is_float=True, editable=True): tv.append_column(new_text_column( title, data_func=create_float_data_func() if is_float else None, editable=editable, edited_callback=(self.on_item_cell_edited, (model, colnr)) if editable else None, resizable=True, text_col=colnr)) add_text_col('Atom name', model.c_name, is_float=False) add_text_col('Def. Z (nm)', model.c_default_z) add_text_col('Calc. Z (nm)', model.c_z, editable=False) add_text_col('#', model.c_pn) # Add atom type column (combo box with atom types from pyxrd.project level): def atom_type_renderer(column, cell, model, itr, col=None): try: name = model.get_user_data_from_path(model.get_path(itr)).atom_type.name except: name = '#NA#' cell.set_property('text', name) return def adjust_combo(cell, editable, path, data=None): editable.set_wrap_width(10) tv.append_column(new_combo_column( "Element", data_func=(atom_type_renderer, (3,)), changed_callback=self.on_atom_type_changed, edited_callback=(self.on_atom_type_edited, (model,)), editing_started_callback=adjust_combo, model=self.atom_types_treemodel, text_column=self.atom_types_treemodel.c_name, editable=True, has_entry=True)) # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def create_new_object_proxy(self): return Atom(name="New Atom", parent=self.model) # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_save_object_clicked(self, widget, user_data=None): def on_accept(save_dialog): Atom.save_as_csv(save_dialog.filename, self.get_all_objects()) current_name = "%s%s" % ( self.model.name.lower(), self.treemodel_property_name.replace("data", "").lower() ) DialogFactory.get_save_dialog( "Export atoms", parent=self.view.get_toplevel(), current_name=current_name, filters=self.file_filters, ).run(on_accept) def on_load_object_clicked(self, widget, user_data=None): def import_layer(dialog): def on_accept(dialog): del self.treemodel_data[:] # clears the list Atom.get_from_csv(dialog.filename, self.treemodel_data.append, self.model) DialogFactory.get_load_dialog( "Import atoms", parent=self.view.get_toplevel(), filters=self.file_filters ).run(on_accept) DialogFactory.get_confirmation_dialog( message="Are you sure?\nImporting a layer file will clear the current list of atoms!", parent=self.view.get_toplevel() ).run(import_layer) def on_atom_type_changed(self, combo, path, new_iter, user_data=None): """Called when the user selects an AtomType from the combo box""" self.new_atom_type = self.atom_types_treemodel.get_user_data(new_iter) return True def on_atom_type_edited(self, combo, path, new_text, user_data=None): """Called when the user has closed the AtomType combo box (so after the on_atom_type_changed call)""" atom = self.treemodel_data[int(path)] if atom is not None: # If new_atom_type is not set, but the user has typed in the name # of an atom_type, find it (index search): if self.new_atom_type is None and not new_text in (None, ""): for atom_type in self.model.phase.project.atom_types: if atom_type.name == new_text: self.new_atom_type = atom_type # Set the new atom type if it is not None: if self.new_atom_type is not None: atom.atom_type = self.new_atom_type # Clear variable and leave self.new_atom_type = None return True return False pass # end of class PyXRD-0.8.4/pyxrd/phases/controllers/phases_controller.py000066400000000000000000000111351363064711000235720ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from contextlib import contextmanager import logging logger = logging.getLogger(__name__) from mvc import Model from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory from pyxrd.generic.views.treeview_tools import new_text_column from pyxrd.generic.controllers import ObjectListStoreController from pyxrd.generic.utils import not_none from pyxrd.phases.views import ( EditPhaseView, AddPhaseView, EditRawPatternPhaseView ) from pyxrd.file_parsers.json_parser import JSONParser from pyxrd.file_parsers.phase_parsers import phase_parsers from ..models import Phase, RawPatternPhase from .edit_phase_controller import EditPhaseController from .raw_pattern_phase_controller import EditRawPatternPhaseController from .add_phase_controller import AddPhaseController class PhasesController(ObjectListStoreController): """ Controller for the phases list """ file_filters = Phase.Meta.file_filters treemodel_property_name = "phases" treemodel_class_type = Phase obj_type_map = [ (Phase, EditPhaseView, EditPhaseController), (RawPatternPhase, EditRawPatternPhaseView, EditRawPatternPhaseController), ] multi_selection = True columns = [ ("Phase name", "c_name"), (" ", "c_display_color"), ("R", "c_R"), ("#", "c_G"), ] delete_msg = "Deleting a phase is irreversible!\nAre You sure you want to continue?" title = "Edit Phases" def get_phases_tree_model(self, *args): return self.treemodel def load_phases(self, filename, parser=JSONParser): index = self.get_selected_index() if index is not None: index += 1 self.model.load_phases(filename, parser=parser, insert_index=index) def setup_treeview_col_c_display_color(self, treeview, name, col_descr, col_index, tv_col_nr): def set_background(column, cell_renderer, tree_model, iter, col_index): try: color = tree_model.get_value(iter, col_index) except TypeError: pass # invalid iter else: cell_renderer.set_property('background', color) cell_renderer.set_property('text', "") treeview.append_column(new_text_column( name, data_func=(set_background, (col_index,)), text_col=col_index, resizable=False, expand=False)) return True def create_new_object_proxy(self): def on_accept(phase_type, G, R): index = int(not_none(self.get_selected_index(), -1)) + 1 if phase_type == "empty": self.add_object(Phase(G=int(G), R=int(R))) elif phase_type == "raw": self.add_object(RawPatternPhase()) else: filename = phase_type if filename != None: self.model.load_phases(filename, parser=JSONParser, insert_index=index) # TODO re-use this and reset the COMBO etc. self.add_model = Model() self.add_view = AddPhaseView(parent=self.view) self.add_ctrl = AddPhaseController( model=self.add_model, view=self.add_view, parent=self.parent, callback=on_accept ) self.add_view.present() return None @contextmanager def _multi_operation_context(self): with self.model.hold_mixtures_data_changed(): with self.model.hold_mixtures_needs_update(): with self.model.hold_phases_data_changed(): yield # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_save_object_clicked(self, event): def on_accept(dialog): logger.info("Exporting phases...") Phase.save_phases(self.get_selected_objects(), filename=dialog.filename) DialogFactory.get_save_dialog( "Export phase", parent=self.view.get_top_widget(), filters=phase_parsers.get_export_file_filters() ).run(on_accept) return True def on_load_object_clicked(self, event): def on_accept(dialog): logger.info("Importing phases...") self.load_phases(dialog.filename, parser=dialog.parser) DialogFactory.get_load_dialog( "Import phase", parent=self.view.get_top_widget(), filters=phase_parsers.get_import_file_filters() ).run(on_accept) return True PyXRD-0.8.4/pyxrd/phases/controllers/raw_pattern_phase_controller.py000066400000000000000000000143671363064711000260270ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os, locale import logging logger = logging.getLogger(__name__) from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory from mvc.adapters.gtk_support.tree_view_adapters import wrap_xydata_to_treemodel, \ wrap_list_property_to_treemodel from pyxrd.generic.controllers import BaseController from pyxrd.generic.controllers.objectliststore_controllers import TreeViewMixin from pyxrd.generic.views.treeview_tools import setup_treeview, new_text_column from ..models import RawPatternPhase class EditRawPatternPhaseController(TreeViewMixin, BaseController): """ Controller for the phase edit view """ file_filters = RawPatternPhase.Meta.rp_filters rp_export_filters = RawPatternPhase.Meta.rp_export_filters widget_handlers = { 'custom': 'custom_handler', } @property def phases_treemodel(self): if self.model.project is not None: prop = self.model.project.Meta.get_prop_intel_by_name("phases") return wrap_list_property_to_treemodel(self.model.project, prop) else: return None # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ @staticmethod def custom_handler(self, intel, widget): pass # nothing to do def setup_raw_pattern_tree_view(self, store, widget): """ Creates the raw pattern TreeView layout and behavior """ setup_treeview(widget, store, on_cursor_changed=self.on_raw_pattern_tv_cursor_changed, sel_mode='MULTIPLE') # X Column: widget.append_column(new_text_column( '°2θ', text_col=store.c_x, editable=True, edited_callback=(self.on_xy_data_cell_edited, (self.model.raw_pattern, 0)), resizable=True, expand=True)) # Y Column: widget.append_column(new_text_column( 'Intensity', text_col=store.c_y, editable=True, edited_callback=(self.on_xy_data_cell_edited, (self.model.raw_pattern, 1)), resizable=True, expand=True)) # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def get_raw_pattern_tree_model(self): return wrap_xydata_to_treemodel(self.model, self.model.Meta.get_prop_intel_by_name("raw_pattern")) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @BaseController.observe("name", assign=True) def notif_name_changed(self, model, prop_name, info): self.phases_treemodel.on_item_changed(self.model) return # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_raw_pattern_tv_cursor_changed(self, tv): path, _ = tv.get_cursor() self.view["btn_del_raw_data"].set_sensitive(path is not None) return True def on_add_raw_pattern_clicked(self, widget): self.model.raw_pattern.append(0, 0) return True def on_del_raw_pattern_clicked(self, widget): paths = self.get_selected_paths(self.view["phase_rp_raw_pattern"]) if paths is not None: self.model.raw_pattern.remove_from_indeces(*paths) return True def on_xy_data_cell_edited(self, cell, path, new_text, model, col): try: value = float(locale.atof(new_text)) except ValueError: logger.exception("ValueError: Invalid literal for float(): '%s'" % new_text) else: model.set_value(int(path), col, value) return True def on_replace_raw_pattern(self, *args, **kwargs): def on_accept(dialog): filename = dialog.filename parser = dialog.parser try: self.model.raw_pattern.load_data(parser, filename, clear=True) except Exception: message = "An unexpected error has occured when trying to parse '%s'.\n" % os.path.basename(filename) message += "This is most likely caused by an invalid or unsupported file format." DialogFactory.get_information_dialog( message=message, parent=self.view.get_toplevel() ).run() raise DialogFactory.get_load_dialog( "Open XRD file for import", parent=self.view.get_toplevel(), filters=self.file_filters ).run(on_accept) return True def on_btn_import_raw_pattern_clicked(self, widget, data=None): def on_confirm(dialog): self.on_replace_raw_pattern() DialogFactory.get_confirmation_dialog( "Importing a new experimental file will erase all current data.\nAre you sure you want to continue?", parent=self.view.get_toplevel() ).run(on_confirm) return True def on_export_raw_pattern(self, *args, **kwargs): return self._export_data(self.model.raw_pattern) def on_btn_export_raw_pattern_clicked(self, widget, data=None): return self.on_export_raw_pattern() def _export_data(self, line): def on_accept(dialog): filename = dialog.filename parser = dialog.parser try: line.save_data(parser, filename, **self.model.get_export_meta_data()) except Exception: message = "An unexpected error has occured when trying to save to '%s'." % os.path.basename(filename) DialogFactory.get_information_dialog( message=message, parent=self.view.get_toplevel() ).run() raise ext_less_fname = os.path.splitext(self.model.name)[0] DialogFactory.get_save_dialog( "Select file for export", parent=self.view.get_toplevel(), filters=self.rp_export_filters, current_name=ext_less_fname ).run(on_accept) pass #end of class PyXRD-0.8.4/pyxrd/phases/glade/000077500000000000000000000000001363064711000161775ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/phases/glade/addphase.glade000066400000000000000000000341221363064711000207500ustar00rootroot00000000000000 True False True False Create a new phase: True True False False 0 True True False False 2 0 True False True False 10 True False 20 # of components False False 0 True False 1 0 True True 5 True False False True True adj_G True True if-valid True True 1 False True 0 True False 10 True False 20 Reichweite False False 0 True False 1 0 True True 5 True False False True True adj_R True True if-valid True True 1 False True 1 False True 1 Choose a default phase: True True False False 0 True True rdb_empty_phase False False 2 2 True False False 5 True False 0 20 True False True True 0 True False True False True False 10 False True True True 0 True False 085-repeat True True 1 False True True Generating phases ... False True 2 False True 1 False True 3 Add a raw pattern True True False False 0 True rdb_empty_phase False False 2 4 1 6 1 1 10 4 1 10 PyXRD-0.8.4/pyxrd/phases/glade/component.glade000066400000000000000000000531071363064711000212050ustar00rootroot00000000000000 True False 10 14 4 2 True False 1 Name 2 GTK_FILL GTK_FILL 12 True False 1 Linked with 2 1 2 GTK_FILL GTK_FILL 12 True False 0 Cell length c <i>[nm]</i> True 1 2 5 6 GTK_FILL GTK_FILL True False 0 1 2 4 8 9 GTK_FILL GTK_FILL 12 True True False Inherit cell length c 0 True 5 6 GTK_FILL True False 4 2 3 GTK_FILL GTK_FILL True False 0 1 0 0 Layer atoms True True False Inherit layer atoms 0 0 True 2 8 9 GTK_FILL GTK_FILL True False 0 0 200 True True True False False 2 4 GTK_FILL GTK_FILL True False 0 0 200 True False 2 4 1 2 GTK_FILL GTK_FILL True False 0 1 2 4 10 11 GTK_FILL GTK_FILL 10 True False 0 1 0 Interlayer atoms True True False Inherit interlayer atoms 0 0 True 2 10 11 GTK_FILL GTK_FILL 150 True False 4 11 12 5 5 150 True False 4 9 10 5 5 True False 0 Cell length b <i>[nm]</i> True 1 2 4 5 GTK_FILL GTK_FILL True True False Inherit cell length b 0 True 4 5 GTK_FILL True False 0 Cell length a <i>[nm]</i> True 1 2 3 4 GTK_FILL GTK_FILL True True False Inherit cell length a 0 True 3 4 GTK_FILL True False 2 4 3 4 GTK_FILL 5 5 True False 2 4 4 5 GTK_FILL 5 5 150 True False 4 13 14 5 5 True False 2 0 1 0 0 Atom relations True True False Inherit atom ratios 1 0 True 2 12 13 GTK_FILL GTK_FILL True False 0 1 2 4 12 13 GTK_FILL GTK_FILL 10 True True False 0 True 6 7 GTK_FILL True False 0 Default length c <i>[nm]</i> True 1 2 6 7 GTK_FILL GTK_FILL True False 0 Δc spacing <i>[nm]</i> True 1 2 7 8 GTK_FILL GTK_FILL True True False 0 True 7 8 GTK_FILL True False 2 4 7 8 GTK_FILL 5 5 True False 2 4 5 6 GTK_FILL 5 5 True False 2 4 6 7 GTK_FILL 5 5 PyXRD-0.8.4/pyxrd/phases/glade/contents.glade000066400000000000000000000132721363064711000210370ustar00rootroot00000000000000 True False 5 4 2 12 6 True False 1 True 2 3 GTK_FILL True False 0 0 True True False False 1 2 3 4 GTK_FILL GTK_FILL True False 1 Content True 3 4 GTK_FILL 150 True False 1 2 2 3 True False 0 0 True True False False 1 2 GTK_FILL GTK_FILL True False 1 Name True GTK_FILL True False 1 True 1 2 GTK_FILL Enabled True True False 0.5 True 1 2 1 2 PyXRD-0.8.4/pyxrd/phases/glade/csds.glade000066400000000000000000000103151363064711000201310ustar00rootroot00000000000000 True False True False 5 4 2 5 5 True False 1 Type: GTK_FILL True False 2 1 2 GTK_FILL GTK_FILL True False 0 0 True False 1 2 GTK_FILL True False 2 5 5 2 2 3 200 120 True False 2 3 4 False True 0 PyXRD-0.8.4/pyxrd/phases/glade/layer.glade000066400000000000000000000136131363064711000203150ustar00rootroot00000000000000 350 True False 2 2 True False 5 12 end gtk-add False True True True False True False False 0 gtk-remove False True False True True False True False False 1 1 2 True True never in True True True True False vertical icons False 1 False True False False Import layer True gtk-go-back True True False True False False False Export layer True gtk-go-forward True True 1 2 GTK_FILL True False 1 2 1 2 PyXRD-0.8.4/pyxrd/phases/glade/phase.glade000066400000000000000000000422721363064711000203040ustar00rootroot00000000000000 True False 5 14 3 5 True False 1 Name & colour GTK_FILL GTK_FILL 12 True False 1 Based on phase 1 2 GTK_FILL GTK_FILL 12 True False 1 σ<sup>*</sup> <i>[°]</i> True 4 5 GTK_FILL GTK_FILL 12 True False 0 0 200 True True True False False 1 2 GTK_FILL GTK_FILL True False 0 0 200 True False 1 3 1 2 GTK_FILL GTK_FILL True False 1 Nr. of components True 2 3 GTK_FILL GTK_FILL 12 True False 0 0 200 True False False False False none True False False 1 3 2 3 GTK_FILL GTK_FILL True False 1 Reichweite True 3 4 GTK_FILL GTK_FILL 12 True False 0 0 200 True False False False False none True False False 1 3 3 4 GTK_FILL GTK_FILL True False 0 0 10 True False True True False Inherit the colour from the "based on" phase. 0.5 True False False 0 True False 75 True True True #ffffb6f20000 False True 1 2 3 GTK_FILL True False True True False 0.5 True False False 0 True False 0 0 12 True True 1 1 3 4 5 GTK_FILL True True True False False True False 25 True False 0.47999998927116394 0 0 True True False True Inherit the CSDS distribution from the "based on" phase. Inherit the CSDS distribution from the "based on" phase. 0 True False True 0 True False CSDS Distribution True True 1 False True False False 1 True False Probabilities & weight fractions 1 False True False 0 none 2 True False Components False 2 False 3 5 14 PyXRD-0.8.4/pyxrd/phases/glade/ratio.glade000066400000000000000000000227241363064711000203220ustar00rootroot00000000000000 350 True False 5 6 2 12 6 True False 0 0 True False 1 2 2 3 GTK_FILL GTK_FILL True False 1 Substituting atom True 2 3 GTK_FILL True False 0 0 True False 1 2 3 4 GTK_FILL GTK_FILL True False 1 Original atom True 3 4 GTK_FILL True False 0 0 True True True False False 1 2 4 5 GTK_FILL GTK_FILL True False 1 Ratio True 4 5 GTK_FILL True False 0 0 True True True False False 1 2 5 6 GTK_FILL GTK_FILL True False 1 Sum True 5 6 GTK_FILL True False 1 Name True GTK_FILL True False 0 0 True True True False False 1 2 GTK_FILL GTK_FILL True False 1 True 1 2 GTK_FILL Enabled False True True False False 0.5 True 1 2 1 2 PyXRD-0.8.4/pyxrd/phases/glade/raw_pattern_phase.glade000066400000000000000000000317031363064711000227070ustar00rootroot00000000000000 True False 5 3 3 5 True False 1 Name & colour GTK_FILL GTK_FILL 12 True False 0 0 200 True True True False False True True 1 2 GTK_FILL GTK_FILL True False 0 0 10 True False True False 75 True True True False #ffffb6f20000 False True 1 2 3 GTK_FILL True False 12 6 True True in True True True True 0 True False Add True True True False img_add_excl False False 0 True True True False True False True False 359-file-export False True 0 True False _Export True True True 1 False False 1 Remove True True True False img_del_excl False False 20 end 1 True True True True False True False True False 358-file-import False True 0 True False _Import True True True 1 False False 2 False True 1 3 2 3 True False 0 Data: 3 1 2 GTK_FILL GTK_FILL 12 True False 190-circle-plus True False 191-circle-minus PyXRD-0.8.4/pyxrd/phases/glade/unit_cell_prop.glade000066400000000000000000000127211363064711000222160ustar00rootroot00000000000000 True False 2 2 True False 75 True True 8 True False False False True 0 True False 3 x False True 1 75 True False False True 2 True False 3 + False True 3 75 True True 8 True False False False True 4 1 2 1 2 GTK_FILL GTK_FILL True False 0 0 75 True True 8 True False False 2 GTK_FILL GTK_FILL True False False 0 True 1 2 PyXRD-0.8.4/pyxrd/phases/models/000077500000000000000000000000001363064711000164065ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/phases/models/CSDS.py000066400000000000000000000200411363064711000175110ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pyxrd.data import settings from pyxrd.calculations.CSDS import calculate_distribution from pyxrd.calculations.data_objects import CSDSData from pyxrd.generic.models import DataModel from pyxrd.generic.io import storables, Storable from pyxrd.refinement.refinables.mixins import RefinementGroup, RefinementValue from pyxrd.refinement.refinables.metaclasses import PyXRDRefinableMeta from pyxrd.refinement.refinables.properties import DataMixin, RefinableMixin from mvc.models.properties import ( GetActionMixin, LabeledProperty, BoolProperty, FloatProperty, ReadOnlyMixin, SetActionMixin ) from mvc.models.properties.signal_mixin import SignalMixin class _AbstractCSDSDistribution(DataModel, Storable, metaclass=PyXRDRefinableMeta): # MODEL INTEL: class Meta(DataModel.Meta): description = "Abstract CSDS distr." explanation = "" phase = property(DataModel.parent.fget, DataModel.parent.fset) # PROPERTIES: _data_object = None @property def data_object(self): return self._data_object inherited = BoolProperty( default=False, text="Inherited", visible=False, persistent=False, signal_name="data_changed", mix_with=(SignalMixin,) ) distrib = LabeledProperty( default=None, text="CSDS Distribution", tabular=True, visible=False, persistent=False, get_action_name="_update_distribution", signal_name="data_changed", mix_with=(SignalMixin, GetActionMixin,) ) # PROPERTIES: #: The maximum value of this distribution maximum = FloatProperty( default=0.0, text="Maximum CSDS", minimum=1, maximum=1000, tabular=True, persistent=False, visible=False, mix_with=(ReadOnlyMixin, DataMixin) ) #: The minimum value of this distribution minimum = FloatProperty( default=0.0, text="Maximum CSDS", minimum=1, maximum=1000, tabular=True, persistent=False, visible=False, mix_with=(ReadOnlyMixin, DataMixin) ) average = FloatProperty( default=0.0, text="Average CSDS", minimum=1, maximum=200, tabular=True, persistent=True, visible=True, refinable=True, signal_name="data_changed", set_action_name="_update_distribution", mix_with=(SignalMixin, DataMixin, RefinableMixin, SetActionMixin) ) alpha_scale = FloatProperty( default=0.0, text="α scale factor", minimum=0.0, maximum=10.0, tabular=True, persistent=True, visible=True, refinable=True, signal_name="data_changed", set_action_name="_update_distribution", mix_with=(SignalMixin, DataMixin, RefinableMixin, SetActionMixin) ) alpha_offset = FloatProperty( default=0.0, text="α offset factor", minimum=-5, maximum=5, tabular=True, persistent=True, visible=True, refinable=True, signal_name="data_changed", set_action_name="_update_distribution", mix_with=(SignalMixin, DataMixin, RefinableMixin, SetActionMixin) ) beta_scale = FloatProperty( default=0.0, text="β² scale factor", minimum=0.0, maximum=10.0, tabular=True, persistent=True, visible=True, refinable=True, signal_name="data_changed", set_action_name="_update_distribution", mix_with=(SignalMixin, DataMixin, RefinableMixin, SetActionMixin) ) beta_offset = FloatProperty( default=0.0, text="β² offset factor", minimum=-5, maximum=5, tabular=True, persistent=True, visible=True, refinable=True, signal_name="data_changed", set_action_name="_update_distribution", mix_with=(SignalMixin, DataMixin, RefinableMixin, SetActionMixin) ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, average=10, alpha_scale=0.9485, alpha_offset=-0.0017, beta_scale=0.1032, beta_offset=0.0034, *args, **kwargs): super(_AbstractCSDSDistribution, self).__init__(*args, **kwargs) self._data_object = CSDSData() type(self).average._set(self, average) type(self).maximum._set(self, int(settings.LOG_NORMAL_MAX_CSDS_FACTOR * average)) type(self).minimum._set(self, 1) type(self).alpha_scale._set(self, alpha_scale) type(self).alpha_offset._set(self, alpha_offset) type(self).beta_scale._set(self, beta_scale) type(self).beta_offset._set(self, beta_offset) self._update_distribution() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def _update_distribution(self): type(self).maximum._set(self, int(settings.LOG_NORMAL_MAX_CSDS_FACTOR * self.average)) self._distrib = calculate_distribution(self.data_object) pass # end of class @storables.register() class LogNormalCSDSDistribution(_AbstractCSDSDistribution, RefinementGroup): # MODEL INTEL: class Meta(_AbstractCSDSDistribution.Meta): description = "Generic log-normal CSDS distr. (Eberl et al. 1990)" store_id = "LogNormalCSDSDistribution" # REFINEMENT GROUP IMPLEMENTATION: @property def refine_title(self): return "CSDS Distribution" @property def refine_descriptor_data(self): return dict( phase_name=self.phase.name, component_name="*" ) pass # end of class @storables.register() class DritsCSDSDistribution(_AbstractCSDSDistribution, RefinementValue): # MODEL INTEL: class Meta(_AbstractCSDSDistribution.Meta): description = "Log-normal CSDS distr. (Drits et. al, 1997)" store_id = "DritsCSDSDistribution" # PROPERTIES: alpha_scale = FloatProperty( default=0.9485, text="α scale factor", minimum=0.0, maximum=10.0, tabular=True, persistent=False, visible=False, refinable=False, mix_with=(ReadOnlyMixin, DataMixin, RefinableMixin) ) alpha_offset = FloatProperty( default=0.017, text="α offset factor", minimum=-5, maximum=5, tabular=True, persistent=False, visible=False, refinable=False, mix_with=(ReadOnlyMixin, DataMixin, RefinableMixin) ) beta_scale = FloatProperty( default=0.1032, text="β² scale factor", minimum=0.0, maximum=10.0, tabular=True, persistent=False, visible=False, refinable=False, mix_with=(ReadOnlyMixin, DataMixin, RefinableMixin) ) beta_offset = FloatProperty( default=0.0034, text="β² offset factor", minimum=-5, maximum=5, tabular=True, persistent=False, visible=False, refinable=False, mix_with=(ReadOnlyMixin, DataMixin, RefinableMixin) ) # REFINEMENT VALUE IMPLEMENTATION: @property def refine_title(self): return "Average CSDS" @property def refine_descriptor_data(self): return dict( phase_name=self.phase.name, component_name="*", property_name=self.refine_title ) @property def refine_value(self): return self.average @refine_value.setter def refine_value(self, value): self.average = value @property def refine_info(self): return self.average_ref_info @property def is_refinable(self): return not self.inherited # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): for key in ["alpha_scale", "alpha_offset", "beta_scale", "beta_offset"]: kwargs.pop(key, None) super(DritsCSDSDistribution, self).__init__(*args, **kwargs) pass # end of class CSDS_distribution_types = [ LogNormalCSDSDistribution, DritsCSDSDistribution ] PyXRD-0.8.4/pyxrd/phases/models/__init__.py000066400000000000000000000005031363064711000205150ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .phase import Phase from .raw_pattern_phase import RawPatternPhase from .component import Component __all__ = [ "Phase", "RawPatternPhase", "Component", ] PyXRD-0.8.4/pyxrd/phases/models/abstract_phase.py000066400000000000000000000105011363064711000217400ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import zipfile from random import choice from mvc.models.properties import ( StringProperty, SignalMixin, IntegerProperty, ReadOnlyMixin ) from pyxrd.generic.io import storables, Storable, COMPRESSION from pyxrd.generic.models import DataModel from pyxrd.calculations.data_objects import PhaseData from pyxrd.calculations.phases import get_diffracted_intensity from pyxrd.file_parsers.json_parser import JSONParser @storables.register() class AbstractPhase(DataModel, Storable): # MODEL INTEL: class Meta(DataModel.Meta): store_id = "AbstractPhase" _data_object = None @property def data_object(self): return self._data_object project = property(DataModel.parent.fget, DataModel.parent.fset) # PROPERTIES: #: The name of this Phase name = StringProperty( default="New Phase", text="Name", visible=True, persistent=True, tabular=True, ) #: The # of components @IntegerProperty( default=0, text="# of components", visible=True, persistent=True, tabular=True, widget_type="entry", mix_with=(ReadOnlyMixin,) ) def G(self): return 0 #: The Reichweite @IntegerProperty( default=0, text="Reichweite", visible=True, persistent=False, tabular=True, widget_type="entry", mix_with=(ReadOnlyMixin,) ) def R(self): return 0 #: The color this phase's X-ray diffraction pattern should have. display_color = StringProperty( default="#FFB600", text="Display color", visible=True, persistent=True, tabular=True, widget_type='color', signal_name="visuals_changed", mix_with=(SignalMixin,) ) line_colors = [ "#004488", "#FF4400", "#559911", "#770022", "#AACC00", "#441177", ] # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): my_kwargs = self.pop_kwargs(kwargs, "data_name", "data_G", "data_R", *[prop.label for prop in AbstractPhase.Meta.get_local_persistent_properties()] ) super(AbstractPhase, self).__init__(*args, **kwargs) kwargs = my_kwargs with self.data_changed.hold(): self._data_object = PhaseData() self.name = self.get_kwarg(kwargs, self.name, "name", "data_name") self.display_color = self.get_kwarg(kwargs, choice(self.line_colors), "display_color") def __repr__(self): return "AbstractPhase(name='%s')" % (self.name) # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ def resolve_json_references(self): pass # nothing to do, sub-classes should override def _pre_multi_save(self, phases, ordered_phases): pass # nothing to do, sub-classes should override def _post_multi_save(self): pass # nothing to do, sub-classes should override @classmethod def save_phases(cls, phases, filename): """ Saves multiple phases to a single file. """ ordered_phases = list(phases) # make a copy for phase in phases: phase._pre_multi_save(phases, ordered_phases) with zipfile.ZipFile(filename, 'w', compression=COMPRESSION) as zfile: for i, phase in enumerate(ordered_phases): zfile.writestr("%d###%s" % (i, phase.uuid), phase.dump_object()) for phase in ordered_phases: phase._post_multi_save() # After export we change all the UUID's # This way, we're sure that we're not going to import objects with # duplicate UUID's! type(cls).object_pool.change_all_uuids() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def get_diffracted_intensity(self, range_theta, range_stl, *args): return get_diffracted_intensity(range_theta, range_stl, self.data_object) pass # end of class PyXRD-0.8.4/pyxrd/phases/models/atom_relations.py000066400000000000000000000467551363064711000220210ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import types from functools import partial import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk # @UnresolvedImport from mvc.models.properties import ( LabeledProperty, StringProperty, BoolProperty, FloatProperty, SignalMixin, ReadOnlyMixin, ListProperty ) from mvc.observers import ListObserver from mvc import Model from pyxrd.generic.models import DataModel from pyxrd.generic.io import storables, Storable, get_case_insensitive_glob from pyxrd.refinement.refinables.mixins import RefinementValue from pyxrd.refinement.refinables.properties import RefinableMixin from pyxrd.refinement.refinables.metaclasses import PyXRDRefinableMeta class ComponentPropMixin(object): """ A mixin which provides some common utility functions for retrieving properties using a string description (e.g. 'layer_atoms.1' or 'b_cell') """ def _parseattr(self, attr): """ Function used for handling (deprecated) 'property strings': attr contains a string (e.g. cell_a or layer_atoms.2) which can be parsed into an object and a property. Current implementation uses UUID's, however this is still here for backwards-compatibility... Will be removed at some point! """ if not isinstance(attr, str): return attr if attr == "" or attr == None: return None def get_atom_by_index(atoms, index): for atom in atoms: if atom.atom_nr == index: return atom return None attr = attr.replace("data_", "", 1) # for backwards compatibility attrs = attr.split(".") if attrs[0] == "layer_atoms": atom = get_atom_by_index(self.component._layer_atoms, int(attrs[1])) if atom is not None: return atom, "pn" else: return None elif attrs[0] == "interlayer_atoms": atom = get_atom_by_index(self.component._interlayer_atoms, int(attrs[1])) if atom is not None: return atom, "pn" else: return None else: return self.component, attr @storables.register() class AtomRelation(ComponentPropMixin, RefinementValue, DataModel, Storable, metaclass=PyXRDRefinableMeta): # MODEL INTEL: class Meta(DataModel.Meta): store_id = "AtomRelation" file_filters = [ ("Atom relation", get_case_insensitive_glob("*.atr")), ] allowed_relations = {} component = property(DataModel.parent.fget, DataModel.parent.fset) # PROPERTIES: #: The name of this AtomRelation name = StringProperty( default="", text="Name", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: The value of this AtomRelation value = FloatProperty( default=0.0, text="Value", visible=True, persistent=True, tabular=True, widget_type='float_entry', signal_name="data_changed", refinable=True, mix_with=(SignalMixin, RefinableMixin) ) #: Flag indicating whether this AtomRelation is enabled or not enabled = BoolProperty( default=True, text="Enabled", visible=True, persistent=True, tabular=True, signal_name="data_changed", mix_with=(SignalMixin,) ) #: Is True when this AtomRelation's value is driven by another AtomRelation. #: Should never be set directly or things might break! driven_by_other = BoolProperty( default=False, text="Driven by other", visible=False, persistent=False, tabular=True ) @property def applicable(self): """ Is True when this AtomRelation was passed a component of which the atom ratios are not set to be inherited from another component. """ return (self.parent is not None and not self.parent.inherit_atom_relations) # REFINEMENT VALUE IMPLEMENTATION: @property def refine_title(self): return self.name @property def refine_descriptor_data(self): return dict( phase_name=self.component.phase.refine_title, component_name=self.component.refine_title ) @property def refine_value(self): return self.value @refine_value.setter def refine_value(self, value): self.value = value @property def inside_linked_component(self): return (self.component.linked_with is not None) and self.component.inherit_atom_relations @property def is_refinable(self): return self.enabled and not self.driven_by_other and not self.inside_linked_component @property def refine_info(self): return self.value_ref_info # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): """ Valid keyword arguments for an AtomRelation are: name: the name of this AtomRelation value: the value for this AtomRelation enabled: boolean indicating whether or not this AtomRelation is enabled """ my_kwargs = self.pop_kwargs(kwargs, "data_name", "data_ratio", "ratio", *[prop.label for prop in AtomRelation.Meta.get_local_persistent_properties()] ) super(AtomRelation, self).__init__(*args, **kwargs) kwargs = my_kwargs self.name = self.get_kwarg(kwargs, "", "name", "data_name") self.value = self.get_kwarg(kwargs, 0.0, "value", "ratio", "data_ratio") self.enabled = bool(self.get_kwarg(kwargs, True, "enabled")) # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ def resolve_relations(self): raise NotImplementedError("Subclasses should implement the resolve_relations method!") # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def create_prop_store(self, prop=None): if self.component is not None: store = Gtk.ListStore(object, str, str) for atom in self.component._layer_atoms: store.append([atom, "pn", atom.name]) for atom in self.component._interlayer_atoms: store.append([atom, "pn", atom.name]) for relation in self.component._atom_relations: tp = relation.Meta.store_id if tp in self.Meta.allowed_relations: for prop, name in self.Meta.allowed_relations[tp]: if callable(name): name = name(relation) store.append([relation, prop, name]) return store def iter_references(self): raise NotImplementedError("'iter_references' should be implemented by subclasses!") def _safe_is_referring(self, value): if value is not None and hasattr(value, "is_referring"): return value.is_referring([self, ]) else: return False def is_referring(self, references=None): """ Checks whether this AtomRelation is causing circular references. Can be used to check this before actually setting references by setting the 'references' keyword argument to a list containing the new reference value(s). """ if references == None: references = [] # 1. Bluntly check if we're not already somewhere referred to, # if not, add ourselves to the list of references if self in references: return True references.append(self) # 2. Loop over our own references, check if they cause a circular # reference, if not add them to the list of references. for reference in self.iter_references(): if reference is not None and hasattr(reference, "is_referring"): if reference.is_referring(references): return True else: references.append(reference) return False def _set_driven_flag_for_prop(self, prop=None): """Internal method used to safely set the driven_by_other flag on an object. Subclasses can override to provide a check on the property set by the driver.""" self.driven_by_other = True def apply_relation(self): raise NotImplementedError("Subclasses should implement the apply_relation method!") pass # end of class @storables.register() class AtomRatio(AtomRelation): # MODEL INTEL: class Meta(AtomRelation.Meta): store_id = "AtomRatio" allowed_relations = { "AtomRatio": [ ("__internal_sum__", lambda o: "%s: SUM" % o.name), ("value", lambda o: "%s: RATIO" % o.name), ], "AtomContents": [("value", lambda o: o.name)], } # SIGNALS: # PROPERTIES: #: The sum of the two atoms sum = FloatProperty( default=1.0, text="Sum", minimum=0.0, visible=True, persistent=True, tabular=True, widget_type='float_entry', signal_name="data_changed", mix_with=(SignalMixin,) ) def __internal_sum__(self, value): """ Special setter for other AtomRelation objects depending on the value of the sum of the AtomRatio. This can be used to have multi-substitution by linking two (or more) AtomRatio's. Eg Al-by-Mg-&-Fe: AtomRatioMgAndFeForAl -> links together Al content and Fe+Mg content => sum = e.g. 4 set by user AtomRatioMgForFe -> links together the Fe and Mg content => sum = set by previous ratio. """ self._sum = float(value) self.apply_relation() __internal_sum__ = property(fset=__internal_sum__) def _set_driven_flag_for_prop(self, prop, *args): """Internal method used to safely set the driven_by_other flag on an object. Subclasses can override to provide a check on the property set by the driver.""" if prop != "__internal_sum__": super(AtomRatio, self)._set_driven_flag_for_prop(prop) def _set_atom(self, value, label=None): if not self._safe_is_referring(value[0]): getattr(type(self), label)._set(self, value) #: The substituting atom atom1 = LabeledProperty( default=[None, None], text="Substituting Atom", visible=True, persistent=True, tabular=True, signal_name="data_changed", fset=partial(_set_atom, label="atom1"), mix_with=(SignalMixin,) ) #: The Original atom atom2 = LabeledProperty( default=[None, None], text="Original Atom", visible=True, persistent=True, tabular=True, signal_name="data_changed", fset=partial(_set_atom, label="atom2"), mix_with=(SignalMixin,) ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): # @ReservedAssignment """ Valid keyword arguments for an AtomRatio are: sum: the sum of the atoms contents atom1: a tuple containing the first atom and its property name to read/set atom2: a tuple containing the first atom and its property name to read/set The value property is the 'ratio' of the first atom over the sum of both """ my_kwargs = self.pop_kwargs(kwargs, "data_sum", "prop1", "data_prop1", "data_prop2", "prop2", *[prop.label for prop in AtomRatio.Meta.get_local_persistent_properties()] ) super(AtomRatio, self).__init__(*args, **kwargs) kwargs = my_kwargs self.sum = self.get_kwarg(kwargs, self.sum, "sum", "data_sum") self._unresolved_atom1 = self._parseattr(self.get_kwarg(kwargs, [None, None], "atom1", "prop1", "data_prop1")) self._unresolved_atom2 = self._parseattr(self.get_kwarg(kwargs, [None, None], "atom2", "prop2", "data_prop2")) # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ def json_properties(self): retval = Storable.json_properties(self) retval["atom1"] = [retval["atom1"][0].uuid if retval["atom1"][0] else None, retval["atom1"][1]] retval["atom2"] = [retval["atom2"][0].uuid if retval["atom2"][0] else None, retval["atom2"][1]] return retval def resolve_relations(self): if isinstance(self._unresolved_atom1[0], str): self._unresolved_atom1[0] = type(type(self)).object_pool.get_object(self._unresolved_atom1[0]) self.atom1 = list(self._unresolved_atom1) del self._unresolved_atom1 if isinstance(self._unresolved_atom2[0], str): self._unresolved_atom2[0] = type(type(self)).object_pool.get_object(self._unresolved_atom2[0]) self.atom2 = list(self._unresolved_atom2) del self._unresolved_atom2 # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def apply_relation(self): if self.enabled and self.applicable: for value, (atom, prop) in [(self.value, self.atom1), (1.0 - self.value, self.atom2)]: if atom and prop: # do not fire events, just set attributes: with atom.data_changed.ignore(): setattr(atom, prop, value * self.sum) if hasattr(atom, "_set_driven_flag_for_prop"): atom._set_driven_flag_for_prop(prop) def iter_references(self): for atom in [self.atom1[0], self.atom2[0]]: yield atom pass # end of class class AtomContentObject(Model): """ Wrapper around an atom object used in the AtomContents model. Stores the atom, the property to set and it's default amount. """ atom = LabeledProperty( default=None, text="Atom", visible=False, persistent=False, tabular=True, ) prop = LabeledProperty( default=None, text="Prop", visible=False, persistent=False, tabular=True, ) amount = FloatProperty( default=0.0, text="Amount", minimum=0.0, visible=False, persistent=False, tabular=True, ) def __init__(self, atom, prop, amount, *args, **kwargs): super(AtomContentObject, self).__init__(*args, **kwargs) self.atom = atom self.prop = prop self.amount = amount def update_atom(self, value): if not (self.atom == "" or self.atom is None or self.prop is None): with self.atom.data_changed.ignore(): setattr(self.atom, self.prop, self.amount * value) if hasattr(self.atom, "_set_driven_flag_for_prop"): self.atom._set_driven_flag_for_prop(self.prop) pass @storables.register() class AtomContents(AtomRelation): # MODEL INTEL: class Meta(AtomRelation.Meta): store_id = "AtomContents" allowed_relations = { "AtomRatio": [ ("__internal_sum__", lambda o: "%s: SUM" % o.name), ("value", lambda o: "%s: RATIO" % o.name), ], } # SIGNALS: # PROPERTIES: atom_contents = ListProperty( default=None, text="Atom contents", visible=True, persistent=True, tabular=True, data_type=AtomContentObject, mix_with=(ReadOnlyMixin,) ) # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): """ Valid keyword arguments for an AtomContents are: atom_contents: a list of tuples containing the atom content object uuids, property names and default amounts """ my_kwargs = self.pop_kwargs(kwargs, *[prop.label for prop in AtomContents.Meta.get_local_persistent_properties()] ) super(AtomContents, self).__init__(*args, **kwargs) kwargs = my_kwargs # Load atom contents: atom_contents = [] for uuid, prop, amount in self.get_kwarg(kwargs, [], "atom_contents"): # uuid's are resolved when resolve_relations is called atom_contents.append(AtomContentObject(uuid, prop, amount)) type(self).atom_contents._set(self, atom_contents) def on_change(*args): if self.enabled: # no need for updates otherwise self.data_changed.emit() self._atom_contents_observer = ListObserver( on_change, on_change, prop_name="atom_contents", model=self ) # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ def json_properties(self): retval = Storable.json_properties(self) retval["atom_contents"] = list([ [ atom_contents.atom.uuid if atom_contents.atom else None, atom_contents.prop, atom_contents.amount ] for atom_contents in retval["atom_contents"] ]) return retval def resolve_relations(self): # Disable event dispatching to prevent infinite loops enabled = self.enabled self.enabled = False # Change rows with string references to objects (uuid's) for atom_content in self.atom_contents: if isinstance(atom_content.atom, str): atom_content.atom = type(type(self)).object_pool.get_object(atom_content.atom) # Set the flag to its original value self.enabled = enabled # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def apply_relation(self): if self.enabled and self.applicable: for atom_content in self.atom_contents: atom_content.update_atom(self.value) def set_atom_content_values(self, path, new_atom, new_prop): """ Convenience function that first checks if the new atom value will not cause a circular reference before actually setting it. """ with self.data_changed.hold(): atom_content = self.atom_contents[int(path[0])] if atom_content.atom != new_atom: old_atom = atom_content.atom atom_content.atom = None # clear... if not self._safe_is_referring(new_atom): atom_content.atom = new_atom else: atom_content.atom = old_atom else: atom_content.atom = None atom_content.prop = new_prop def iter_references(self): for atom_content in self.atom_contents: yield atom_content.atom pass # end of class PyXRD-0.8.4/pyxrd/phases/models/component.py000066400000000000000000000625021363064711000207670ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import zipfile from warnings import warn from mvc.models.properties import ( FloatProperty, SignalMixin, BoolProperty, StringProperty, LabeledProperty, SignalProperty, ObserveMixin, ListProperty, ) from mvc.observers.list_observer import ListObserver from pyxrd.generic.io import storables, Storable, COMPRESSION from pyxrd.generic.models import DataModel from pyxrd.generic.models.properties import InheritableMixin from pyxrd.calculations.components import get_factors from pyxrd.calculations.data_objects import ComponentData from pyxrd.refinement.refinables.mixins import RefinementGroup from pyxrd.refinement.refinables.metaclasses import PyXRDRefinableMeta from pyxrd.refinement.refinables.properties import RefinableMixin from pyxrd.atoms.models import Atom from pyxrd.file_parsers.json_parser import JSONParser from .atom_relations import AtomRelation from .unit_cell_prop import UnitCellProperty @storables.register() class Component(RefinementGroup, DataModel, Storable, metaclass=PyXRDRefinableMeta): # MODEL INTEL: class Meta(DataModel.Meta): store_id = "Component" _data_object = None @property def data_object(self): weight = 0.0 self._data_object.layer_atoms = [None] * len(self.layer_atoms) for i, atom in enumerate(self.layer_atoms): self._data_object.layer_atoms[i] = atom.data_object weight += atom.weight self._data_object.interlayer_atoms = [None] * len(self.interlayer_atoms) for i, atom in enumerate(self.interlayer_atoms): self._data_object.interlayer_atoms[i] = atom.data_object weight += atom.weight self._data_object.volume = self.get_volume() self._data_object.weight = weight self._data_object.d001 = self.d001 self._data_object.default_c = self.default_c self._data_object.delta_c = self.delta_c self._data_object.lattice_d = self.lattice_d return self._data_object phase = property(DataModel.parent.fget, DataModel.parent.fset) # SIGNALS: atoms_changed = SignalProperty() # UNIT CELL DIMENSION SHORTCUTS: @property def cell_a(self): return self._ucp_a.value @property def cell_b(self): return self._ucp_b.value @property def cell_c(self): return self.d001 # PROPERTIES: #: The name of the Component name = StringProperty( default="", text="Name", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: Flag indicating whether to inherit the UCP a from :attr:`~linked_with` @BoolProperty( default=False, text="Inh. cell length a", visible=True, persistent=True, tabular=True, ) def inherit_ucp_a(self): return self._ucp_a.inherited @inherit_ucp_a.setter def inherit_ucp_a(self, value): self._ucp_a.inherited = value #: Flag indicating whether to inherit the UCP b from :attr:`~linked_with` @BoolProperty( default=False, text="Inh. cell length b", visible=True, persistent=True, tabular=True, ) def inherit_ucp_b(self): return self._ucp_b.inherited @inherit_ucp_b.setter def inherit_ucp_b(self, value): self._ucp_b.inherited = value #: Flag indicating whether to inherit d001 from :attr:`~linked_with` inherit_d001 = BoolProperty( default=False, text="Inh. cell length c", visible=True, persistent=True, tabular=True, signal_name="data_changed", mix_with=(SignalMixin,) ) #: Flag indicating whether to inherit default_c from :attr:`~linked_with` inherit_default_c = BoolProperty( default=False, text="Inh. default length c", visible=True, persistent=True, tabular=True, signal_name="data_changed", mix_with=(SignalMixin,) ) #: Flag indicating whether to inherit delta_c from :attr:`~linked_with` inherit_delta_c = BoolProperty( default=False, text="Inh. c length dev.", visible=True, persistent=True, tabular=True, signal_name="data_changed", mix_with=(SignalMixin,) ) #: Flag indicating whether to inherit layer_atoms from :attr:`~linked_with` inherit_layer_atoms = BoolProperty( default=False, text="Inh. layer atoms", visible=True, persistent=True, tabular=True, signal_name="data_changed", mix_with=(SignalMixin,) ) #: Flag indicating whether to inherit interlayer_atoms from :attr:`~linked_with` inherit_interlayer_atoms = BoolProperty( default=False, text="Inh. interlayer atoms", visible=True, persistent=True, tabular=True, signal_name="data_changed", mix_with=(SignalMixin,) ) #: Flag indicating whether to inherit atom_relations from :attr:`~linked_with` inherit_atom_relations = BoolProperty( default=False, text="Inh. atom relations", visible=True, persistent=True, tabular=True, signal_name="data_changed", mix_with=(SignalMixin,) ) _linked_with_index = None _linked_with_uuid = None #: The :class:`~Component` this component is linked with linked_with = LabeledProperty( default=None, text="Linked with", visible=True, persistent=True, signal_name="data_changed", mix_with=(SignalMixin,) ) @linked_with.setter def linked_with(self, value): old = type(self).linked_with._get(self) if old != value: if old is not None: self.relieve_model(old) type(self).linked_with._set(self, value) if value is not None: self.observe_model(value) else: for prop in self.Meta.get_inheritable_properties(): setattr(self, prop.inherit_flag, False) #: The silicate lattice's c length lattice_d = FloatProperty( default=0.0, text="Lattice c length [nm]", visible=False, persistent=True, signal_name="data_changed" ) ucp_a = LabeledProperty( default=None, text="Cell length a [nm]", visible=True, persistent=True, tabular=True, refinable=True, inheritable=True, inherit_flag="inherit_ucp_a", inherit_from="linked_with.ucp_a", signal_name="data_changed", mix_with=(SignalMixin, InheritableMixin, ObserveMixin, RefinableMixin) ) ucp_b = LabeledProperty( default=None, text="Cell length b [nm]", visible=True, persistent=True, tabular=True, refinable=True, inheritable=True, inherit_flag="inherit_ucp_b", inherit_from="linked_with.ucp_b", signal_name="data_changed", mix_with=(SignalMixin, InheritableMixin, ObserveMixin, RefinableMixin) ) d001 = FloatProperty( default=1.0, text="Cell length c [nm]", minimum=0.0, maximum=5.0, visible=True, persistent=True, tabular=True, refinable=True, inheritable=True, inherit_flag="inherit_default_c", inherit_from="linked_with.d001", signal_name="data_changed", mix_with=(SignalMixin, InheritableMixin, RefinableMixin) ) default_c = FloatProperty( default=1.0, text="Default c length [nm]", minimum=0.0, maximum=5.0, visible=True, persistent=True, tabular=True, inheritable=True, inherit_flag="inherit_default_c", inherit_from="linked_with.default_c", signal_name="data_changed", mix_with=(SignalMixin, InheritableMixin) ) delta_c = FloatProperty( default=0.0, text="C length dev. [nm]", minimum=0.0, maximum=0.05, visible=True, persistent=True, tabular=True, inheritable=True, inherit_flag="inherit_delta_c", inherit_from="linked_with.delta_c", signal_name="data_changed", mix_with=(SignalMixin, InheritableMixin, RefinableMixin) ) layer_atoms = ListProperty( default=None, text="Layer atoms", visible=True, persistent=True, tabular=True, widget_type="custom", inheritable=True, inherit_flag="inherit_layer_atoms", inherit_from="linked_with.layer_atoms", signal_name="data_changed", data_type=Atom, mix_with=(SignalMixin, InheritableMixin) ) interlayer_atoms = ListProperty( default=None, text="Interlayer atoms", visible=True, persistent=True, tabular=True, widget_type="custom", inheritable=True, inherit_flag="inherit_interlayer_atoms", inherit_from="linked_with.interlayer_atoms", signal_name="data_changed", data_type=Atom, mix_with=(SignalMixin, InheritableMixin) ) atom_relations = ListProperty( default=None, text="Atom relations", widget_type="custom", visible=True, persistent=True, tabular=True, refinable=True, inheritable=True, inherit_flag="inherit_atom_relations", inherit_from="linked_with.atom_relations", signal_name="data_changed", data_type=AtomRelation, mix_with=(SignalMixin, InheritableMixin, RefinableMixin) ) # Instance flag indicating whether or not linked_with & inherit flags should be saved save_links = True # Class flag indicating whether or not atom types in the component should be # exported using their name rather then their project-uuid. export_atom_types = False # REFINEMENT GROUP IMPLEMENTATION: @property def refine_title(self): return self.name @property def refine_descriptor_data(self): return dict( phase_name=self.phase.refine_title, component_name=self.refine_title ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, **kwargs): """ Valid keyword arguments for a Component are: *ucp_a*: unit cell property along a axis *ucp_b*: unit cell property along b axis *d001*: unit cell length c (aka d001) *default_c*: default c-value *delta_c*: the variation in basal spacing due to defects *layer_atoms*: ObjectListStore of layer Atoms *interlayer_atoms*: ObjectListStore of interlayer Atoms *atom_relations*: ObjectListStore of AtomRelations *inherit_ucp_a*: whether or not to inherit the ucp_a property from the linked component (if linked) *inherit_ucp_b*: whether or not to inherit the ucp_b property from the linked component (if linked) *inherit_d001*: whether or not to inherit the d001 property from the linked component (if linked) *inherit_default_c*: whether or not to inherit the default_c property from the linked component (if linked) *inherit_delta_c*: whether or not to inherit the delta_c property from the linked component (if linked) *inherit_layer_atoms*: whether or not to inherit the layer_atoms property from the linked component (if linked) *inherit_interlayer_atoms*: whether or not to inherit the interlayer_atoms property from the linked component (if linked) *inherit_atom_relations*: whether or not to inherit the atom_relations property from the linked component (if linked) *linked_with_uuid*: the UUID for the component this one is linked with Deprecated, but still supported: *linked_with_index*: the index of the component this one is linked with in the ObjectListStore of the parent based on phase. """ my_kwargs = self.pop_kwargs(kwargs, "data_name", "data_layer_atoms", "data_interlayer_atoms", "data_atom_relations", "data_atom_ratios", "data_d001", "data_default_c", "data_delta_c", "lattice_d", "data_cell_a", "data_ucp_a", "data_cell_b", "data_ucp_b", "linked_with_uuid", "linked_with_index", "inherit_cell_a", "inherit_cell_b", *[prop.label for prop in Component.Meta.get_local_persistent_properties()] ) super(Component, self).__init__(**kwargs) kwargs = my_kwargs # Set up data object self._data_object = ComponentData( d001=0.0, delta_c=0.0 ) # Set attributes: self.name = self.get_kwarg(kwargs, "", "name", "data_name") # Load lists: self.layer_atoms = self.get_list(kwargs, [], "layer_atoms", "data_layer_atoms", parent=self) self.interlayer_atoms = self.get_list(kwargs, [], "interlayer_atoms", "data_interlayer_atoms", parent=self) self.atom_relations = self.get_list(kwargs, [], "atom_relations", "data_atom_relations", parent=self) # Add all atom ratios to the AtomRelation list for atom_ratio in self.get_list(kwargs, [], "atom_ratios", "data_atom_ratios", parent=self): self.atom_relations.append(atom_ratio) # Observe the inter-layer atoms, and make sure they get stretched for atom in self.interlayer_atoms: atom.stretch_values = True self.observe_model(atom) # Observe the layer atoms for atom in self.layer_atoms: self.observe_model(atom) # Resolve their relations and observe the atom relations for relation in self.atom_relations: relation.resolve_relations() self.observe_model(relation) # Connect signals to lists and dicts: self._layer_atoms_observer = ListObserver( self._on_layer_atom_inserted, self._on_layer_atom_removed, prop_name="layer_atoms", model=self ) self._interlayer_atoms_observer = ListObserver( self._on_interlayer_atom_inserted, self._on_interlayer_atom_removed, prop_name="interlayer_atoms", model=self ) self._atom_relations_observer = ListObserver( self._on_atom_relation_inserted, self._on_atom_relation_removed, prop_name="atom_relations", model=self ) # Update lattice values: self.d001 = self.get_kwarg(kwargs, self.d001, "d001", "data_d001") self._default_c = float(self.get_kwarg(kwargs, self.d001, "default_c", "data_default_c")) self.delta_c = float(self.get_kwarg(kwargs, self.delta_c, "delta_c", "data_delta_c")) self.update_lattice_d() # Set/Create & observe unit cell properties: ucp_a = self.get_kwarg(kwargs, None, "ucp_a", "data_ucp_a", "data_cell_a") if isinstance(ucp_a, float): ucp_a = UnitCellProperty(name="cell length a", value=ucp_a, parent=self) ucp_a = self.parse_init_arg( ucp_a, UnitCellProperty, child=True, default_is_class=True, name="Cell length a [nm]", parent=self ) type(self).ucp_a._set(self, ucp_a) self.observe_model(ucp_a) ucp_b = self.get_kwarg(kwargs, None, "ucp_b", "data_ucp_b", "data_cell_b") if isinstance(ucp_b, float): ucp_b = UnitCellProperty(name="cell length b", value=ucp_b, parent=self) ucp_b = self.parse_init_arg( ucp_b, UnitCellProperty, child=True, default_is_class=True, name="Cell length b [nm]", parent=self ) type(self).ucp_b._set(self, ucp_b) self.observe_model(ucp_b) # Set links: self._linked_with_uuid = self.get_kwarg(kwargs, "", "linked_with_uuid") self._linked_with_index = self.get_kwarg(kwargs, -1, "linked_with_index") # Set inherit flags: self.inherit_d001 = self.get_kwarg(kwargs, False, "inherit_d001") self.inherit_ucp_a = self.get_kwarg(kwargs, False, "inherit_ucp_a", "inherit_cell_a") self.inherit_ucp_b = self.get_kwarg(kwargs, False, "inherit_ucp_b", "inherit_cell_b") self.inherit_default_c = self.get_kwarg(kwargs, False, "inherit_default_c") self.inherit_delta_c = self.get_kwarg(kwargs, False, "inherit_delta_c") self.inherit_layer_atoms = self.get_kwarg(kwargs, False, "inherit_layer_atoms") self.inherit_interlayer_atoms = self.get_kwarg(kwargs, False, "inherit_interlayer_atoms") self.inherit_atom_relations = self.get_kwarg(kwargs, False, "inherit_atom_relations") def __repr__(self): return "Component(name='%s', linked_with=%r)" % (self.name, self.linked_with) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @DataModel.observe("data_changed", signal=True) def _on_data_model_changed(self, model, prop_name, info): # Check whether the changed model is an AtomRelation or Atom, if so # re-apply the atom_relations. with self.data_changed.hold(): if isinstance(model, AtomRelation) or isinstance(model, Atom): self._apply_atom_relations() self._update_ucp_values() if isinstance(model, UnitCellProperty): self.data_changed.emit() # propagate signal @DataModel.observe("removed", signal=True) def _on_data_model_removed(self, model, prop_name, info): # Check whether the removed component is linked with this one, if so # clears the link and emits the data_changed signal. if model != self and self.linked_with is not None and self.linked_with == model: with self.data_changed.hold_and_emit(): self.linked_with = None def _on_layer_atom_inserted(self, atom): """Sets the atoms parent and stretch_values property, updates the components lattice d-value, and emits a data_changed signal""" with self.data_changed.hold_and_emit(): with self.atoms_changed.hold_and_emit(): atom.parent = self atom.stretch_values = False self.observe_model(atom) self.update_lattice_d() def _on_layer_atom_removed(self, atom): """Clears the atoms parent, updates the components lattice d-value, and emits a data_changed signal""" with self.data_changed.hold_and_emit(): with self.atoms_changed.hold_and_emit(): self.relieve_model(atom) atom.parent = None self.update_lattice_d() def _on_interlayer_atom_inserted(self, atom): """Sets the atoms parent and stretch_values property, and emits a data_changed signal""" with self.data_changed.hold_and_emit(): with self.atoms_changed.hold_and_emit(): atom.stretch_values = True atom.parent = self def _on_interlayer_atom_removed(self, atom): """Clears the atoms parent property, and emits a data_changed signal""" with self.data_changed.hold_and_emit(): with self.atoms_changed.hold_and_emit(): atom.parent = None def _on_atom_relation_inserted(self, item): item.parent = self self.observe_model(item) self._apply_atom_relations() def _on_atom_relation_removed(self, item): self.relieve_model(item) item.parent = None self._apply_atom_relations() # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ def resolve_json_references(self): for atom in type(self).layer_atoms._get(self): atom.resolve_json_references() for atom in type(self).interlayer_atoms._get(self): atom.resolve_json_references() type(self).ucp_a._get(self).resolve_json_references() type(self).ucp_a._get(self).update_value() type(self).ucp_b._get(self).resolve_json_references() type(self).ucp_b._get(self).update_value() if getattr(self, "_linked_with_uuid", None): self.linked_with = type(type(self)).object_pool.get_object(self._linked_with_uuid) del self._linked_with_uuid elif getattr(self, "_linked_with_index", None) and self._linked_with_index != -1: warn("The use of object indeces is deprected since version 0.4. Please switch to using object UUIDs.", DeprecationWarning) self.linked_with = self.parent.based_on.components.get_user_from_index(self._linked_with_index) del self._linked_with_index @classmethod def save_components(cls, components, filename): """ Saves multiple components to a single file. """ Component.export_atom_types = True for comp in components: comp.save_links = False with zipfile.ZipFile(filename, 'w', compression=COMPRESSION) as zfile: for component in components: zfile.writestr(component.uuid, component.dump_object()) for comp in components: comp.save_links = True Component.export_atom_types = False # After export we change all the UUID's # This way, we're sure that we're not going to import objects with # duplicate UUID's! type(cls).object_pool.change_all_uuids() @classmethod def load_components(cls, filename, parent=None): """ Returns multiple components loaded from a single file. """ # Before import, we change all the UUID's # This way we're sure that we're not going to import objects # with duplicate UUID's! type(cls).object_pool.change_all_uuids() if zipfile.is_zipfile(filename): with zipfile.ZipFile(filename, 'r') as zfile: for uuid in zfile.namelist(): obj = JSONParser.parse(zfile.open(uuid)) obj.parent = parent yield obj else: obj = JSONParser.parse(filename) obj.parent = parent yield obj def json_properties(self): if self.phase == None or not self.save_links: retval = Storable.json_properties(self) for prop in self.Meta.all_properties: if getattr(prop, "inherit_flag", False): retval[prop.inherit_flag] = False else: retval = Storable.json_properties(self) retval["linked_with_uuid"] = self.linked_with.uuid if self.linked_with is not None else "" return retval # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def get_factors(self, range_stl): """ Get the structure factor for the given range of sin(theta)/lambda values. :param range_stl: A 1D numpy ndarray """ return get_factors(range_stl, self.data_object) def get_interlayer_stretch_factors(self): z_factor = (self.cell_c - self.lattice_d) / (self.default_c - self.lattice_d) return self.lattice_d, z_factor def update_lattice_d(self): """ Updates the lattice_d attribute for this :class:`~.Component`. Should normally not be called from outside the component. """ for atom in self.layer_atoms: self.lattice_d = float(max(self.lattice_d, atom.default_z)) def _apply_atom_relations(self): """ Applies the :class:`~..atom_relations.AtomRelation` objects in this component. Should normally not be called from outside the component. """ with self.data_changed.hold_and_emit(): for relation in self.atom_relations: # Clear the 'driven by' flags: relation.driven_by_other = False for relation in self.atom_relations: # Apply the relations, will also take care of flag setting: relation.apply_relation() def _update_ucp_values(self): """ Updates the :class:`~..unit_cell_prop.UnitCellProperty` objects in this component. Should normally not be called from outside the component. """ with self.data_changed.hold(): for ucp in [self._ucp_a, self._ucp_b]: ucp.update_value() def get_volume(self): """ Get the volume for this :class:`~.Component`. Will always return a value >= 1e-25, to prevent division-by-zero errors in calculation code. """ return max(self.cell_a * self.cell_b * self.cell_c, 1e-25) def get_weight(self): """ Get the total atomic weight for this :class:`~.Component`. """ weight = 0 for atom in (self.layer_atoms + self.interlayer_atoms): weight += atom.weight return weight # ------------------------------------------------------------ # AtomRelation list related # ------------------------------------------------------------ def move_atom_relation_up(self, relation): """ Move the passed :class:`~..atom_relations.AtomRelation` up one slot """ index = self.atom_relations.index(relation) del self.atom_relations[index] self.atom_relations.insert(max(index - 1, 0), relation) def move_atom_relation_down(self, relation): """ Move the passed :class:`~..atom_relations.AtomRelation` down one slot """ index = self.atom_relations.index(relation) del self.atom_relations[index] self.atom_relations.insert(min(index + 1, len(self.atom_relations)), relation) pass # end of class PyXRD-0.8.4/pyxrd/phases/models/phase.py000066400000000000000000000346111363064711000200650ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from warnings import warn from random import choice from mvc import Observer from mvc.observers import ListObserver from mvc.models.properties import ( StringProperty, SignalMixin, BoolProperty, LabeledProperty, FloatProperty, ListProperty ) from pyxrd.generic.io import storables, get_case_insensitive_glob from pyxrd.generic.models.properties import InheritableMixin, ObserveChildMixin from pyxrd.refinement.refinables.properties import RefinableMixin from pyxrd.refinement.refinables.mixins import RefinementGroup from pyxrd.refinement.refinables.metaclasses import PyXRDRefinableMeta from pyxrd.probabilities.models import get_correct_probability_model from .abstract_phase import AbstractPhase from .CSDS import DritsCSDSDistribution from .component import Component @storables.register() class Phase(RefinementGroup, AbstractPhase, metaclass=PyXRDRefinableMeta): # MODEL INTEL: class Meta(AbstractPhase.Meta): store_id = "Phase" file_filters = [ ("Phase file", get_case_insensitive_glob("*.PHS")), ] _data_object = None @property def data_object(self): self._data_object.type = "Phase" self._data_object.valid_probs = (all(self.probabilities.P_valid) and all(self.probabilities.W_valid)) if self._data_object.valid_probs: self._data_object.sigma_star = self.sigma_star self._data_object.CSDS = self.CSDS_distribution.data_object self._data_object.G = self.G self._data_object.W = self.probabilities.get_distribution_matrix() self._data_object.P = self.probabilities.get_probability_matrix() self._data_object.components = [None] * len(self.components) for i, comp in enumerate(self.components): self._data_object.components[i] = comp.data_object else: self._data_object.sigma_star = None self._data_object.CSDS = None self._data_object.G = None self._data_object.W = None self._data_object.P = None self._data_object.components = None return self._data_object project = property(AbstractPhase.parent.fget, AbstractPhase.parent.fset) # PROPERTIES: #: Flag indicating whether the CSDS distribution is inherited from the #: :attr:`based_on` phase or not. @BoolProperty( default=False, text="Inh. mean CSDS", visible=True, persistent=True, tabular=True ) def inherit_CSDS_distribution(self): return self._CSDS_distribution.inherited @inherit_CSDS_distribution.setter def inherit_CSDS_distribution(self, value): self._CSDS_distribution.inherited = value #: Flag indicating whether to inherit the display color from the #: :attr:`based_on` phase or not. inherit_display_color = BoolProperty( default=False, text="Inh. display color", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: Flag indicating whether to inherit the sigma start value from the #: :attr:`based_on` phase or not. inherit_sigma_star = BoolProperty( default=False, text="Inh. sigma star", visible=True, persistent=True, tabular=True, signal_name="data_changed", mix_with=(SignalMixin,) ) _based_on_index = None # temporary property _based_on_uuid = None # temporary property #: The :class:`~Phase` instance this phase is based on based_on = LabeledProperty( default=None, text="Based on phase", visible=True, persistent=False, tabular=True, signal_name="data_changed", mix_with=(SignalMixin, ObserveChildMixin) ) @based_on.setter def based_on(self, value): old = type(self).based_on._get(self) if value == None or value.get_based_on_root() == self or value.parent != self.parent: value = None if value != old: type(self).based_on._set(self, value) for component in self.components: component.linked_with = None # INHERITABLE PROPERTIES: #: The sigma star orientation factor sigma_star = FloatProperty( default=3.0, text="σ* [°]", math_text="$\sigma^*$ [°]", minimum=0.0, maximum=90.0, visible=True, persistent=True, tabular=True, refinable=True, inheritable=True, inherit_flag="inherit_sigma_star", inherit_from="based_on.sigma_star", signal_name="data_changed", mix_with=(SignalMixin, RefinableMixin, InheritableMixin) ) # A :class:`~pyxrd.phases.models.CSDS` instance CSDS_distribution = LabeledProperty( default=None, text="CSDS Distribution", visible=True, persistent=True, tabular=True, refinable=True, inheritable=True, inherit_flag="inherit_CSDS_distribution", inherit_from="based_on.CSDS_distribution", signal_name="data_changed", mix_with=(SignalMixin, RefinableMixin, InheritableMixin, ObserveChildMixin) ) # A :class:`~pyxrd._probabilities.models._AbstractProbability` subclass instance probabilities = LabeledProperty( default=None, text="Probablities", visible=True, persistent=True, tabular=True, refinable=True, signal_name="data_changed", mix_with=(SignalMixin, RefinableMixin, ObserveChildMixin) ) @probabilities.setter def probabilities(self, value): type(self).probabilities._set(self, value) if value is not None: value.update() #: The color this phase's X-ray diffraction pattern should have. display_color = StringProperty( fset=AbstractPhase.display_color.fset, fget=AbstractPhase.display_color.fget, fdel=AbstractPhase.display_color.fdel, doc=AbstractPhase.display_color.__doc__, default="#008600", text="Display color", visible=True, persistent=True, tabular=True, widget_type='color', inheritable=True, inherit_flag="inherit_display_color", inherit_from="based_on.display_color", signal_name="visuals_changed", mix_with=(SignalMixin, InheritableMixin) ) #: The list of components this phase consists of components = ListProperty( default=None, text="Components", visible=True, persistent=True, tabular=True, refinable=True, widget_type="custom", data_type=Component, mix_with=(RefinableMixin,) ) #: The # of components @AbstractPhase.G.getter def G(self): if self.components is not None: return len(self.components) else: return 0 #: The # of components @AbstractPhase.R.getter def R(self): if self.probabilities: return self.probabilities.R # Flag indicating whether or not the links (based_on and linked_with) should # be saved as well. save_links = True # REFINEMENT GROUP IMPLEMENTATION: @property def refine_title(self): return self.name @property def refine_descriptor_data(self): return dict( phase_name=self.refine_title, component_name="*" ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): my_kwargs = self.pop_kwargs(kwargs, "data_CSDS_distribution", "data_sigma_star", "data_components", "data_G", "G", "data_R", "R", "data_probabilities", "based_on_uuid", "based_on_index", "inherit_probabilities", *[prop.label for prop in Phase.Meta.get_local_persistent_properties()] ) super(Phase, self).__init__(*args, **kwargs) kwargs = my_kwargs with self.data_changed.hold(): CSDS_distribution = self.get_kwarg(kwargs, None, "CSDS_distribution", "data_CSDS_distribution") self.CSDS_distribution = self.parse_init_arg( CSDS_distribution, DritsCSDSDistribution, child=True, default_is_class=True, parent=self ) self.inherit_CSDS_distribution = self.get_kwarg(kwargs, False, "inherit_CSDS_distribution") self.display_color = self.get_kwarg(kwargs, choice(self.line_colors), "display_color") self.inherit_display_color = self.get_kwarg(kwargs, False, "inherit_display_color") self.sigma_star = self.get_kwarg(kwargs, self.sigma_star, "sigma_star", "data_sigma_star") self.inherit_sigma_star = self.get_kwarg(kwargs, False, "inherit_sigma_star") self.components = self.get_list(kwargs, [], "components", "data_components", parent=self) G = int(self.get_kwarg(kwargs, 1, "G", "data_G")) R = int(self.get_kwarg(kwargs, 0, "R", "data_R")) if G is not None and G > 0: for i in range(len(self.components), G): new_comp = Component(name="Component %d" % (i + 1), parent=self) self.components.append(new_comp) self.observe_model(new_comp) # Observe components for component in self.components: self.observe_model(component) # Connect signals to lists and dicts: self._components_observer = ListObserver( self.on_component_inserted, self.on_component_removed, prop_name="components", model=self ) self.probabilities = self.parse_init_arg( self.get_kwarg(kwargs, None, "probabilities", "data_probabilities"), get_correct_probability_model(R, G), default_is_class=True, child=True) self.probabilities.update() # force an update inherit_probabilities = kwargs.pop("inherit_probabilities", None) if inherit_probabilities is not None: for prop in self.probabilities.Meta.get_inheritable_properties(): setattr(self.probabilities, prop.inherit_flag, bool(inherit_probabilities)) self._based_on_uuid = self.get_kwarg(kwargs, None, "based_on_uuid") self._based_on_index = self.get_kwarg(kwargs, None, "based_on_index") def __repr__(self): return "Phase(name='%s', based_on=%r)" % (self.name, self.based_on) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ def on_component_inserted(self, item): # Set parent and observe the new component (visuals changed signals): if item.parent != self: item.parent = self self.observe_model(item) def on_component_removed(self, item): with self.data_changed.hold_and_emit(): # Clear parent & stop observing: item.parent = None self.relieve_model(item) @Observer.observe("data_changed", signal=True) def notify_data_changed(self, model, prop_name, info): if isinstance(model, Phase) and model == self.based_on: with self.data_changed.hold(): # make sure inherited probabilities are up-to-date self.probabilities.update() self.data_changed.emit(arg="based_on") else: self.data_changed.emit() @Observer.observe("visuals_changed", signal=True) def notify_visuals_changed(self, model, prop_name, info): self.visuals_changed.emit() # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ def resolve_json_references(self): # Set the based on and linked with variables: if hasattr(self, "_based_on_uuid") and self._based_on_uuid is not None: self.based_on = type(type(self)).object_pool.get_object(self._based_on_uuid) del self._based_on_uuid elif hasattr(self, "_based_on_index") and self._based_on_index is not None and self._based_on_index != -1: warn("The use of object indices is deprecated since version 0.4. Please switch to using object UUIDs.", DeprecationWarning) self.based_on = self.parent.phases.get_user_from_index(self._based_on_index) del self._based_on_index for component in self.components: component.resolve_json_references() with self.data_changed.hold(): # make sure inherited probabilities are up-to-date self.probabilities.update() def _pre_multi_save(self, phases, ordered_phases): ## Override from base class if self.based_on != "" and not self.based_on in phases: self.save_links = False Component.export_atom_types = True for component in self.components: component.save_links = self.save_links # Make sure parent is first in ordered list: if self.based_on in phases: index = ordered_phases.index(self) index2 = ordered_phases.index(self.based_on) if index < index2: ordered_phases.remove(self.based_on) ordered_phases.insert(index, self.based_on) def _post_multi_save(self): ## Override from base class self.save_links = True for component in self.components: component.save_links = True Component.export_atom_types = False def json_properties(self): retval = super(Phase, self).json_properties() if not self.save_links: for prop in self.Meta.all_properties: if getattr(prop, "inherit_flag", False): retval[prop.inherit_flag] = False retval["based_on_uuid"] = "" else: retval["based_on_uuid"] = self.based_on.uuid if self.based_on else "" return retval # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def _update_interference_distributions(self): return self.CSDS_distribution.distrib def get_based_on_root(self): """ Gets the root object in the based_on chain """ if self.based_on is not None: return self.based_on.get_based_on_root() else: return self pass # end of class PyXRD-0.8.4/pyxrd/phases/models/raw_pattern_phase.py000066400000000000000000000073031363064711000224710ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from random import choice import numpy as np from mvc.models.properties import LabeledProperty from mvc.models.properties.signal_mixin import SignalMixin from mvc.models.properties.observe_mixin import ObserveMixin from pyxrd.generic.io import storables, get_case_insensitive_glob from pyxrd.generic.models.lines import PyXRDLine from pyxrd.refinement.refinables.metaclasses import PyXRDRefinableMeta from pyxrd.refinement.refinables.mixins import RefinementGroup from pyxrd.file_parsers.xrd_parsers import xrd_parsers from .abstract_phase import AbstractPhase @storables.register() class RawPatternPhase(RefinementGroup, AbstractPhase, metaclass=PyXRDRefinableMeta): # MODEL INTEL: class Meta(AbstractPhase.Meta): store_id = "RawPatternPhase" file_filters = [ ("Phase file", get_case_insensitive_glob("*.PHS")), ] rp_filters = xrd_parsers.get_import_file_filters() rp_export_filters = xrd_parsers.get_export_file_filters() _data_object = None @property def data_object(self): self._data_object.type = "RawPatternPhase" self._data_object.raw_pattern_x = self.raw_pattern.data_x self._data_object.raw_pattern_y = self.raw_pattern.data_y[:, 0] self._data_object.apply_lpf = False self._data_object.apply_correction = False return self._data_object project = property(AbstractPhase.parent.fget, AbstractPhase.parent.fset) @property def refine_title(self): return "Raw Pattern Phase" @property def is_refinable(self): return False @property def children_refinable(self): return False @property def refinables(self): return [] raw_pattern = LabeledProperty( default=None, text="Raw pattern", visible=True, persistent=True, tabular=True, inheritable=True, inherit_flag="inherit_CSDS_distribution", inherit_from="based_on.CSDS_distribution", signal_name="data_changed", mix_with=(SignalMixin, ObserveMixin,) ) @property def spec_max_display_y(self): """The maximum intensity (y-axis) of the current loaded profile""" _max = 0.0 if self.raw_pattern is not None: _max = max(_max, np.max(self.raw_pattern.max_display_y)) return _max # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): my_kwargs = self.pop_kwargs(kwargs, *[prop.label for prop in RawPatternPhase.Meta.get_local_persistent_properties()] ) super(RawPatternPhase, self).__init__(*args, **kwargs) kwargs = my_kwargs with self.data_changed.hold(): self.raw_pattern = PyXRDLine( data=self.get_kwarg(kwargs, None, "raw_pattern"), parent=self ) self.display_color = self.get_kwarg(kwargs, choice(self.line_colors), "display_color") self.inherit_display_color = self.get_kwarg(kwargs, False, "inherit_display_color") def __repr__(self): return "RawPatternPhase(name='%s')" % (self.name) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @AbstractPhase.observe("data_changed", signal=True) def notify_data_changed(self, model, prop_name, info): self.data_changed.emit() # propagate signal pass #end of class PyXRD-0.8.4/pyxrd/phases/models/unit_cell_prop.py000066400000000000000000000164061363064711000220050ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk import logging logger = logging.getLogger(__name__) from mvc.models.properties import ( StringProperty, BoolProperty, LabeledProperty, FloatProperty, SignalMixin, SetActionMixin ) from pyxrd.generic.io import storables, Storable from pyxrd.generic.models import DataModel from pyxrd.refinement.refinables.properties import RefinableMixin from pyxrd.refinement.refinables.mixins import RefinementValue from pyxrd.refinement.refinables.metaclasses import PyXRDRefinableMeta from .atom_relations import ComponentPropMixin @storables.register() class UnitCellProperty(ComponentPropMixin, RefinementValue, DataModel, Storable, metaclass=PyXRDRefinableMeta): """ UnitCellProperty's are an integral part of a component and allow to calculate the dimensions of the unit cell based on compositional information such as the iron content. This class is not responsible for keeping its value up-to-date. With other words, it is the responsibility of the higher-level class to call the 'update_value' method on this object whenever it emits a 'data_changed' signal. The reason for this is to prevent infinite recursion errors. """ class Meta(DataModel.Meta): store_id = "UnitCellProperty" component = property(DataModel.parent.fget, DataModel.parent.fset) # PROPERTIES: #: The UnitCellProperty name name = StringProperty( default="", text="Name", visible=False, persistent=False, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: Flag indicating if this UnitCellProperty is enabled enabled = BoolProperty( default=False, text="Enabled", visible=True, persistent=True, set_action_name="update_value", mix_with=(SetActionMixin,) ) #: Flag indicating if this UnitCellProperty is inherited inherited = BoolProperty( default=False, text="Inherited", visible=False, persistent=False, set_action_name="update_value", mix_with=(SetActionMixin,) ) #: The value of the UnitCellProperty value = FloatProperty( default=0.0, text="Value", visible=True, persistent=True, refinable=True, widget_type='float_entry', set_action_name="update_value", mix_with=(SetActionMixin, RefinableMixin) ) #: The factor of the UnitCellProperty (if enabled and not constant) factor = FloatProperty( default=1.0, text="Factor", visible=True, persistent=True, widget_type='float_entry', set_action_name="update_value", mix_with=(SetActionMixin,) ) #: The constant of the UnitCellProperty (if enabled and not constant) constant = FloatProperty( default=0.0, text="Constant", visible=True, persistent=True, widget_type='float_entry', set_action_name="update_value", mix_with=(SetActionMixin,) ) _temp_prop = None # temporary, JSON-style prop prop = LabeledProperty( default=None, text="Property", visible=True, persistent=True, widget_type='combo', set_action_name="update_value", mix_with=(SetActionMixin,) ) # REFINEMENT VALUE IMPLEMENTATION: @property def refine_title(self): return self.name @property def refine_descriptor_data(self): return dict( phase_name=self.component.phase.refine_title, component_name=self.component.refine_title ) @property def refine_value(self): return self.value @refine_value.setter def refine_value(self, value): if not self.enabled: self.value = value @property def refine_info(self): return self.value_ref_info @property def is_refinable(self): return not (self.enabled or self.inherited) # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): keys = [prop.label for prop in UnitCellProperty.Meta.get_local_persistent_properties()] keys.extend(["data_%s" % prop.label for prop in UnitCellProperty.Meta.get_local_persistent_properties()]) my_kwargs = self.pop_kwargs(kwargs, "name", *keys) super(UnitCellProperty, self).__init__(*args, **kwargs) kwargs = my_kwargs with self.data_changed.hold_and_emit(): self.name = self.get_kwarg(kwargs, self.name, "name", "data_name") self.value = self.get_kwarg(kwargs, self.value, "value", "data_value") self.factor = self.get_kwarg(kwargs, self.factor, "factor", "data_factor") self.constant = self.get_kwarg(kwargs, self.constant, "constant", "data_constant") self.enabled = self.get_kwarg(kwargs, self.enabled, "enabled", "data_enabled") self._temp_prop = self.get_kwarg(kwargs, self.prop, "prop", "data_prop") # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ def json_properties(self): retval = Storable.json_properties(self) if retval["prop"]: # Try to replace objects with their uuid's: try: retval["prop"] = [getattr(retval["prop"][0], 'uuid', retval["prop"][0]), retval["prop"][1]] except: logger.exception("Error when trying to interpret UCP JSON properties") pass # ignore return retval def resolve_json_references(self): if getattr(self, "_temp_prop", None): self._temp_prop = list(self._temp_prop) if isinstance(self._temp_prop[0], str): obj = type(type(self)).object_pool.get_object(self._temp_prop[0]) if obj: self._temp_prop[0] = obj self.prop = self._temp_prop else: self._temp_prop = None self.prop = self._temp_prop del self._temp_prop # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def create_prop_store(self, extra_props=[]): assert(self.component is not None) store = Gtk.ListStore(object, str, str) # use private properties so we connect to the actual object stores and not the inherited ones for atom in self.component._layer_atoms: store.append([atom, "pn", atom.name]) for atom in self.component._interlayer_atoms: store.append([atom, "pn", atom.name]) for prop in extra_props: store.append(prop) return store def get_value_of_prop(self): try: return getattr(*self.prop) except: return 0.0 def update_value(self): if self.enabled: self._value = float(self.factor * self.get_value_of_prop() + self.constant) self.data_changed.emit() pass # end of class PyXRD-0.8.4/pyxrd/phases/views.py000066400000000000000000000200131363064711000166260ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk # @UnresolvedImport from matplotlib.figure import Figure from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvasGTK from pyxrd.generic.views import BaseView, HasChildView, DialogView,\ ObjectListStoreView from mvc.adapters.gtk_support.widgets import ScaleEntry class EditPhaseView(HasChildView, BaseView): title = "Edit Phases" builder = resource_filename(__name__, "glade/phase.glade") top = "edit_phase" widget_format = "phase_%s" csds_view = None csds_view_container = widget_format % "CSDS_distribution" probabilities_view = None probabilities_view_container = widget_format % "probabilities" components_view = None components_view_container = widget_format % "components" def set_csds_view(self, view): self.csds_view = view if view is not None: self._add_child_view(view.get_top_widget(), self[self.csds_view_container]) return view def set_csds_sensitive(self, sens): self[self.csds_view_container].set_sensitive(sens) def set_probabilities_view(self, view): self.probabilities_view = view if view is not None: self._add_child_view(view.get_top_widget(), self[self.probabilities_view_container]) return view def remove_probabilities(self): num = self["book_wrapper"].page_num(self[self.probabilities_view_container]) self["book_wrapper"].remove_page(num) def set_components_view(self, view): self.components_view = view if view is not None: self._add_child_view(view.get_top_widget(), self[self.components_view_container]) return view class EditRawPatternPhaseView(BaseView): title = "Edit Raw Pattern Phase" builder = resource_filename(__name__, "glade/raw_pattern_phase.glade") top = "edit_raw_pattern_phase" widget_format = "rp_phase_%s" class EditAtomRatioView(DialogView): title = "Edit Atom Ratio" subview_builder = resource_filename(__name__, "glade/ratio.glade") subview_toplevel = "edit_ratio" modal = True widget_format = "ratio_%s" @property def atom1_combo(self): return self["ratio_atom1"] @property def atom2_combo(self): return self["ratio_atom2"] pass # end of class class EditAtomContentsView(DialogView): title = "Edit Atom Contents" subview_builder = resource_filename(__name__, "glade/contents.glade") subview_toplevel = "edit_contents" modal = True widget_format = "contents_%s" contents_list_view_container = "atom_contents_container" def set_contents_list_view(self, view): self[self.widget_format % "atom_contents"] = view return self._add_child_view(view, self[self.contents_list_view_container]) @property def atom_contents_container(self): return self["container_atom_contents"] pass # end of class class EditComponentView(HasChildView, BaseView): title = "Edit Component" builder = resource_filename(__name__, "glade/component.glade") top = "edit_component" widget_format = "component_%s" layer_view = None layer_view_container = widget_format % "layer_atoms" interlayer_view = None interlayer_view_container = widget_format % "interlayer_atoms" atom_relations_view = None atom_relations_view_container = widget_format % "atom_relations" ucpa_view = None ucpa_view_container = widget_format % "ucp_a" ucpb_view = None ucpb_view_container = widget_format % "ucp_b" def __init__(self, *args, **kwargs): BaseView.__init__(self, *args, **kwargs) def set_layer_view(self, view): self.layer_view = view return self._add_child_view(view, self[self.layer_view_container]) def set_atom_relations_view(self, view): self.atom_relations_view = view return self._add_child_view(view, self[self.atom_relations_view_container]) def set_interlayer_view(self, view): self.interlayer_view = view return self._add_child_view(view, self[self.interlayer_view_container]) def set_ucpa_view(self, view): self.ucpa_view = view return self._add_child_view(view, self[self.ucpa_view_container]) def set_ucpb_view(self, view): self.ucpb_view = view return self._add_child_view(view, self[self.ucpb_view_container]) class EditUnitCellPropertyView(BaseView): builder = resource_filename(__name__, "glade/unit_cell_prop.glade") top = "box_ucf" widget_format = "ucp_%s" class EditCSDSDistributionView(BaseView): builder = resource_filename(__name__, "glade/csds.glade") top = "tbl_csds_distr" def __init__(self, *args, **kwargs): BaseView.__init__(self, *args, **kwargs) self.graph_parent = self["distr_plot_box"] self.setup_matplotlib_widget() def setup_matplotlib_widget(self): #style = Gtk.Style() self.figure = Figure(dpi=72) #, edgecolor=str(style.bg[2]), facecolor=str(style.bg[2])) self.plot = self.figure.add_subplot(111) self.figure.subplots_adjust(bottom=0.20) self.matlib_canvas = FigureCanvasGTK(self.figure) self.plot.autoscale_view() self.graph_parent.add(self.matlib_canvas) self.graph_parent.show_all() def update_figure(self, distr): self.plot.cla() self.plot.hist(list(range(len(distr))), len(distr), weights=distr, normed=1, ec='b', histtype='stepfilled') self.plot.set_ylabel('') self.plot.set_xlabel('CSDS', size=14, weight="heavy") self.plot.relim() self.plot.autoscale_view() if self.matlib_canvas is not None: self.matlib_canvas.draw() def reset_params(self): tbl = self["tbl_params"] for child in tbl.get_children(): tbl.remove(child) tbl.resize(1, 2) def add_param_widget(self, name, label, minimum, maximum): tbl = self["tbl_params"] rows = tbl.get_property("n-rows") + 1 tbl.resize(rows, 2) lbl = Gtk.Label(label=label) lbl.set_alignment(1.0, 0.5) tbl.attach(lbl, 0, 1, rows - 1, rows, Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL) inp = ScaleEntry(minimum, maximum, enforce_range=True) tbl.attach(inp, 1, 2, rows - 1, rows, Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL) tbl.show_all() self[name] = inp inp.set_name(name) return inp class AddPhaseView(DialogView): title = "Add Phase" subview_builder = resource_filename(__name__, "glade/addphase.glade") subview_toplevel = "add_phase_container" active_type = "empty" # | default | raw def __init__(self, *args, **kwargs): DialogView.__init__(self, *args, **kwargs) def get_G(self): return int(self["G"].get_value_as_int()) def get_R(self): return int(self["R"].get_value_as_int()) def get_phase_type(self): if self.active_type == "empty": return "empty" elif self.active_type == "default": itr = self.phase_combo_box.get_active_iter() val = self.phase_combo_box.get_model().get_value(itr, 1) if itr else None return val else: return "raw" def update_sensitivities(self): self["cont_empty_phase"].set_sensitive(False) self["cont_default_phase"].set_sensitive(False) if self["rdb_empty_phase"].get_active(): self.active_type = "empty" self["cont_empty_phase"].set_sensitive(True) elif self["rdb_default_phase"].get_active(): self.active_type = "default" self["cont_default_phase"].set_sensitive(True) else: self.active_type = "raw" @property def phase_combo_box(self): return self["cmb_default_phases"] pass # end of class PyXRD-0.8.4/pyxrd/probabilities/000077500000000000000000000000001363064711000164705ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/probabilities/__init__.py000066400000000000000000000000001363064711000205670ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/probabilities/controllers.py000066400000000000000000000066221363064711000214160ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from mvc import Controller from pyxrd.generic.controllers import BaseController from pyxrd.probabilities.views import get_correct_probability_views from pyxrd.probabilities.models import RGbounds def get_correct_probability_controllers(probability, parent_controller, independents_view, dependents_view): if probability is not None: G = probability.G R = probability.R if (RGbounds[R, G - 1] > 0): return BaseController(model=probability, parent=parent_controller, view=independents_view), \ MatrixController(current=R, model=probability, parent=parent_controller, view=dependents_view) else: raise ValueError("Cannot (yet) handle R%d for %d layer structures!" % (R, G)) class EditProbabilitiesController(BaseController): independents_view = None matrix_view = None auto_adapt = False def __init__(self, *args, **kwargs): BaseController.__init__(self, *args, **kwargs) self._init_views(kwargs["view"]) self.update_views() def _init_views(self, view): self.independents_view, self.dependents_view = get_correct_probability_views(self.model, view) self.independents_controller, self.dependents_controller = get_correct_probability_controllers(self.model, self, self.independents_view, self.dependents_view) view.set_views(self.independents_view, self.dependents_view) @BaseController.model.setter def model(self, model): if self.view is not None: self.independents_controller.model = None # model self.dependents_controller.model = None # model super(EditProbabilitiesController, self)._set_model(model) if self.view is not None: self.independents_controller.model = model self.dependents_controller.model = model self.update_views() def update_views(self): # needs to be called whenever an independent value changes with self.model.data_changed.hold(): self.dependents_view.update_matrices(self.model) self.independents_view.update_matrices(self.model) def register_adapters(self): return # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @Controller.observe("data_changed", signal=True) def notif_updated(self, model, prop_name, info): self.update_views() return pass # end of class class MatrixController(BaseController): auto_adapt = False def __init__(self, current, *args, **kwargs): BaseController.__init__(self, *args, **kwargs) self.current_W = current self.current_P = current def register_adapters(self): return def on_w_prev_clicked(self, widget, *args): self.current_W = self.view.show_w_matrix(self.current_W - 1) def on_w_next_clicked(self, widget, *args): self.current_W = self.view.show_w_matrix(self.current_W + 1) def on_p_prev_clicked(self, widget, *args): self.current_P = self.view.show_p_matrix(self.current_P - 1) def on_p_next_clicked(self, widget, *args): self.current_P = self.view.show_p_matrix(self.current_P + 1) pass # end of class PyXRD-0.8.4/pyxrd/probabilities/glade/000077500000000000000000000000001363064711000175445ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/probabilities/glade/R0_independents.glade000066400000000000000000000027401363064711000235660ustar00rootroot00000000000000 True False True False Independent parameters True True 0 True False False True 1 True False True True 2 PyXRD-0.8.4/pyxrd/probabilities/glade/matrix.glade000066400000000000000000000303151363064711000220500ustar00rootroot00000000000000 True False 0 0.25 0 True False 5 6 2 5 5 True False 2 3 GTK_FILL True False 1 2 2 3 GTK_FILL True False 2 5 6 GTK_FILL True False Matrices 2 GTK_FILL True False 0 0 0 True False 1 2 3 4 GTK_FILL 5 True False valid 4 5 True False valid 1 2 4 5 True False 0 0 0 True False 3 4 GTK_FILL 5 True False True True True False True False gtk-go-back False False 0 True False <b>W<sub>ij</sub></b> True True False 1 True True True False True False gtk-go-forward False False 2 1 2 GTK_FILL True False True True True False True False gtk-go-back False False 0 True False <b>P<sub>ijk</sub></b> True True False 1 True True True False True False gtk-go-forward False False 2 1 2 1 2 GTK_FILL PyXRD-0.8.4/pyxrd/probabilities/glade/probabilities.glade000066400000000000000000000023051363064711000233720ustar00rootroot00000000000000 True False 10 True False False False 0 True False False False 1 PyXRD-0.8.4/pyxrd/probabilities/models/000077500000000000000000000000001363064711000177535ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/probabilities/models/R0models.py000066400000000000000000000116051363064711000220150ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import numpy as np from mvc.models.properties import SetActionMixin, FloatProperty, BoolProperty from pyxrd.generic.io import storables from pyxrd.generic.utils import not_none from pyxrd.generic.models.properties import InheritableMixin from pyxrd.refinement.refinables.properties import RefinableMixin from .base_models import _AbstractProbability def R0_model_generator(pasG): class _R0Meta(_AbstractProbability.Meta): store_id = "R0G%dModel" % pasG class _BaseR0Model(object): """ Probability model for Reichweite = 0 (g-1) independent variables: W0 = W0/sum(W0>Wg) W1/sum(W1>Wg) W2/sum(W2>Wg) etc. Pij = Wj ∑W = 1 ∑P = 1 indexes are NOT zero-based in external property names! """ # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): my_kwargs = self.pop_kwargs(kwargs, *[prop.label for prop in self.Meta.get_local_persistent_properties()]) super(_BaseR0Model, self).__init__(R=0, *args, **kwargs) with self.data_changed.hold(): if self.G > 1 and "W1" in my_kwargs: # old-style model for i in range(self.G - 1): name = "W%d" % (i + 1) self.mW[i] = not_none(my_kwargs.get(name, None), 0.8) name = "F%d" % (i + 1) setattr(self, name, self.mW[i] / (np.sum(np.diag(self._W)[i:]) or 1.0)) else: for i in range(self.G - 1): name = "inherit_F%d" % (i + 1) setattr(self, name, my_kwargs.get(name, False)) name = "F%d" % (i + 1) setattr(self, name, not_none(my_kwargs.get(name, None), 0.8)) self.update() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update(self): with self.monitor_changes(): if self.G > 1: for i in range(self.G - 1): name = "F%d" % (i + 1) if i > 0: self.mW[i] = getattr(self, name) * (1.0 - np.sum(np.diag(self._W)[0:i])) else: self.mW[i] = getattr(self, name) self.mW[self.G - 1] = 1.0 - np.sum(np.diag(self._W)[:-1]) else: self.mW[0] = 1.0 self._P[:] = np.repeat(np.diag(self._W)[np.newaxis, :], self.G, 0) self.solve() self.validate() pass # end of class _dict = dict() _dict["Meta"] = _R0Meta def set_attribute(name, value): # @NoSelf """Sets an attribute on the class and the dict""" _dict[name] = value setattr(_BaseR0Model, name, value) set_attribute("G", pasG) # PROPERTIES: for g in range(pasG - 1): label = "F%d" % (g + 1) text = "W%(g)d/Sum(W%(g)d+...+W%(G)d)" % {'g':g + 1, 'G':pasG } math_text = r"$\large\frac{W_{%(g)d}}{\sum_{i=%(g)d}^{%(G)d} W_i}$" % {'g':g + 1, 'G':pasG } inh_flag = "inherit_F%d" % (g + 1) inh_from = "parent.based_on.probabilities.F%d" % (g + 1) set_attribute(label, FloatProperty( default=0.8, text=text, math_text=math_text, refinable=True, persistent=True, visible=True, minimum=0.0, maximum=1.0, inheritable=True, inherit_flag=inh_flag, inherit_from=inh_from, is_independent=True, store_private=True, set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) )) label = "inherit_F%d" % (g + 1) text = "Inherit flag for F%d" % (g + 1) set_attribute(label, BoolProperty( default=False, text=text, refinable=False, persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) )) # CREATE TYPE AND REGISTER AS STORABLE: cls = type("R0G%dModel" % pasG, (_BaseR0Model, _AbstractProbability), _dict) storables.register_decorator(cls) return cls R0G1Model = R0_model_generator(1) R0G2Model = R0_model_generator(2) R0G3Model = R0_model_generator(3) R0G4Model = R0_model_generator(4) R0G5Model = R0_model_generator(5) R0G6Model = R0_model_generator(6) __all__ = [ "R0G1Model", "R0G2Model", "R0G3Model", "R0G4Model", "R0G5Model", "R0G6Model" ] PyXRD-0.8.4/pyxrd/probabilities/models/R1models.py000066400000000000000000000774611363064711000220320ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from mvc.models.properties import FloatProperty, BoolProperty from pyxrd.generic.mathtext_support import mt_range from pyxrd.generic.io import storables from pyxrd.generic.utils import not_none from pyxrd.generic.models.properties import InheritableMixin from pyxrd.refinement.refinables.properties import RefinableMixin from .base_models import _AbstractProbability from mvc.models.properties.action_mixins import SetActionMixin __all__ = [ "R1G2Model", "R1G3Model", "R1G4Model", ] @storables.register() class R1G2Model(_AbstractProbability): r""" Probability model for Reichweite 1 with 2 components. The 2(=g*(g-1)) independent variables are: .. math:: :nowrap: \begin{flalign*} & W_1 \\ & \text{$P_{11} (W_1 < 0.5)$ or $P_{22} (W_1 > 0.5)$} \end{flalign*} Calculation of the other variables happens as follows: .. math:: :nowrap: \begin{align*} & W_2 = 1 – W_1 \\ & \begin{aligned} & \text{$P_{11}$ is given:} \\ & \quad P_{12} = 1 - P_{11} \\ & \quad P_{21} = \frac{W_1 \cdot P_{12}}{W2} \\ & \quad P_{22} = 1 - P_{21} \\ \end{aligned} \quad \quad \begin{aligned} & \text{$P_{22}$ is given:} \\ & \quad P_{21} = 1 - P_{22} \\ & \quad P_{12} = \frac{W_2 \cdot P_{21}}{W1} \\ & \quad P_{11} = 1 - P_{12} \\ \end{aligned} \\ \end{align*} """ # MODEL METADATA: class Meta(_AbstractProbability.Meta): store_id = "R1G2Model" # PROPERTIES: _G = 2 inherit_W1 = BoolProperty( default=False, text="Inherit flag for W1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) W1 = FloatProperty( default=0.0, text="W1", math_text=r"$W_1$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_W1", inherit_from="parent.based_on.probabilities.W1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_P11_or_P22 = BoolProperty( default=False, text="Inherit flag for P11_or_P22", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) P11_or_P22 = FloatProperty( default=0.0, text="P11_or_P22", math_text=r"$P_{11} %s$ or $\newline P_{22} %s$" % ( mt_range(0.0, "W_1", 0.5), mt_range(0.5, "W_1", 1.0)), persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_P11_or_P22", inherit_from="parent.based_on.probabilities.P11_or_P22", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, W1=0.75, P11_or_P22=0.5, inherit_W1=False, inherit_P11_or_P22=False, *args, **kwargs): super(R1G2Model, self).__init__(R=1, *args, **kwargs) with self.data_changed.hold(): self.W1 = not_none(W1, 0.75) self.inherit_W1 = inherit_W1 self.P11_or_P22 = not_none(P11_or_P22, 0.5) self.inherit_P11_or_P22 = inherit_P11_or_P22 self.update() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update(self): with self.monitor_changes(): self.mW[0] = self.W1 self.mW[1] = 1.0 - self.mW[0] if self.mW[0] <= 0.5: self.mP[0, 0] = self.P11_or_P22 self.mP[0, 1] = 1.0 - self.mP[0, 0] self.mP[1, 0] = self.mW[0] * self.mP[0, 1] / self.mW[1] self.mP[1, 1] = 1.0 - self.mP[1, 0] else: self.mP[1, 1] = self.P11_or_P22 self.mP[1, 0] = 1.0 - self.mP[1, 1] self.mP[0, 1] = self.mW[1] * self.mP[1, 0] / self.mW[0] self.mP[0, 0] = 1.0 - self.mP[0, 1] self.solve() self.validate() pass # end of class @storables.register() class R1G3Model(_AbstractProbability): r""" Probability model for Reichweite 1 with 3 components. The 6 (=g*(g-1)) independent variables are: .. math:: :nowrap: \begin{align*} & W_1 & \text{$P_{11} (W_1 < 0.5)$ or $P_{xx} (W_1 > 0.5)$} with P_{xx} = \frac {W_{22} + W_{23} + W_{32} + W_{33} + W_{42} + W_{43}}{W_2 + W_3} \\ & G_1 = \frac{W_2}{W_2 + W_3} & G_2 = \frac{W_{22} + W_{23}}{W_{22} + W_{23} + W_{32} + W_{33}} \\ & G_3 = \frac{W_{22}}{W_{22} + W_{23}} & G_4 = \frac{W_{32}}{W_{32} + W_{33}} \\ \end{align*} Calculation of the other variables happens as follows: .. math:: :nowrap: \begin{align*} & \text{Calculate the 'inverted' ratios of $G_2$, $G_3$ and $G_4$ as follows:} \\ & \quad G_i^{\text{-1}} = \begin{cases} G_i^{-1} - 1.0, & \text{if } G_i > 0 \\ 0, & \text{otherwise} \end{cases} \quad \forall i \in \left\{ {2, 3, 4}\right\} \\ & \\ & \text{Calculate the base weight fractions of each component:} \\ & \quad W_2 = (1 - W_1) \cdot G_1\\ & \quad W_3 = 1.0 - W_1 - W_2 \\ & \\ & \text{if $W_1 \leq 0.5$:} \\ & \quad \text{$P_{11}$ is given and W_xx is derived as} \\ & \quad W_{xx} = W_{22} + W_{23} + W_{32} + W_{23} = W_1 \cdot (1 - P_{11}) + W_2 + W_3 \\ & \\ & \text{if $W_1 > 0.5$:} \\ & \quad \text{$P_{xx}$ is given and $P_{11}$ is derived further down} \\ & \quad W_{xx} = W_{22} + W_{23} + W_{32} + W_{23} = P_{xx} \cdot (W_2 + W_3) \\ & \\ & W_{22} = W_{xx} \cdot G_2 \cdot G_3 \\ & W_{23} = W_{22} \cdot G_3^{-1} \\ & W_{32} = W_{xx} \cdot (1 - G_2) \\ & W_{33} = G_4^{-1} \cdot W_{32} \\ & \\ & P_{23} = \begin{dcases} \dfrac{W_{23}}{W_2}, & \text{if $W_2 > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & P_{12} = 1 - P_{22} - P_{23} \\ & \\ & P_{32} = \begin{dcases} \frac{W_{32}}{W_3}, & \text{if $W_3 > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & P_{33} = \begin{dcases} \frac{W_{33}}{W_3}, & \text{if $W_3 > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & P_{31} = 1 - P_{32} - P_{33} \\ & \\ & P_{12} = \begin{dcases} \frac{W_2 - W_{22} - W_{32}}{W_1}, & \text{if $W_1 > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & P_{13} = \begin{dcases} \frac{W_3 - W_{23} - W_{33}}{W_1}, & \text{if $W_1 > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & \\ & \text{if $W_1 > 0.5$}: \\ & \quad P_{11} = 1 - P_{12} - P_{13} \\ & \\ & \text{Remainder of weight fraction can be calculated as follows:} \\ & \quad W_{ij} = {W_{ii}} \cdot {P_{ij}} \quad \forall {i,j} \in \left[ {1, 3} \right] \\ \end{align*} """ # MODEL METADATA: class Meta(_AbstractProbability.Meta): store_id = "R1G3Model" # PROPERTIES _G = 3 inherit_W1 = BoolProperty( default=False, text="Inherit flag for W1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) W1 = FloatProperty( default=0.8, text="W1", math_text=r"$W_1$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_W1", inherit_from="parent.based_on.probabilities.W1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_P11_or_P22 = BoolProperty( default=False, text="Inherit flag for P11_or_P22", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) P11_or_P22 = FloatProperty( default=0.7, text="P11_or_P22", math_text=r"$P_{11} %s$ or $\newline P_{22} %s$" % ( mt_range(0.0, "W_1", 0.5), mt_range(0.5, "W_1", 1.0)), persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_P11_or_P22", inherit_from="parent.based_on.probabilities.P11_or_P22", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_G1 = BoolProperty( default=False, text="Inherit flag for G1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) G1 = FloatProperty( default=0.7, text="W2/(W2+W3)", math_text=r"$\large\frac{W_2}{W_3 + W_2}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G1", inherit_from="parent.based_on.probabilities.G1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_G2 = BoolProperty( default=False, text="Inherit flag for G2", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) G2 = FloatProperty( default=0.7, text="(W22+W23)/(W22+W23+W32+W33)", math_text=r"$\large\frac{W_{22} + W_{23}}{W_{22} + W_{23} + W_{32} + W_{33}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G2", inherit_from="parent.based_on.probabilities.G2", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_G3 = BoolProperty( default=False, text="Inherit flag for G3", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) G3 = FloatProperty( default=0.7, text="W22/(W22+W23)", math_text=r"$\large\frac{W_{22}}{W_{22} + W_{23}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G3", inherit_from="parent.based_on.probabilities.G3", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_G4 = BoolProperty( default=False, text="Inherit flag for G4", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) G4 = FloatProperty( default=0.7, text="W23/(W32+W33)", math_text=r"$\large\frac{W_{22}}{W_{22} + W_{23}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G4", inherit_from="parent.based_on.probabilities.G4", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, W1=0.8, P11_or_P22=0.7, G1=0.7, G2=0.7, G3=0.7, G4=0.7, inherit_W1=False, inherit_P11_or_P22=False, inherit_G1=False, inherit_G2=False, inherit_G3=False, inherit_G4=False, *args, **kwargs): super(R1G3Model, self).__init__(R=1, *args, **kwargs) with self.data_changed.hold(): self.W1 = not_none(W1, 0.8) self.inherit_W1 = bool(inherit_W1) self.P11_or_P22 = not_none(P11_or_P22, 0.7) self.inherit_P11_or_P22 = bool(inherit_P11_or_P22) self.G1 = not_none(G1, 0.7) self.inherit_G1 = bool(inherit_G1) self.G2 = not_none(G2, 0.7) self.inherit_G2 = bool(inherit_G2) self.G3 = not_none(G3, 0.7) self.inherit_G3 = bool(inherit_G3) self.G4 = not_none(G4, 0.7) self.inherit_G4 = bool(inherit_G4) self.update() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update(self): with self.monitor_changes(): self.mW[0] = self.W1 self.mW[1] = (1 - self.mW[0]) * self.G1 self.mW[2] = 1.0 - self.mW[0] - self.mW[1] W0inv = 1.0 / self.mW[0] if self.mW[0] > 0.0 else 0.0 Wxx = 0 if self.mW[0] <= 0.5: # P00 given self.mP[0, 0] = self.P11_or_P22 # Wxx = W11 + W12 + W21 + W22 Wxx = self.mW[0] * (self.mP[0, 0] - 1) + self.mW[1] + self.mW[2] else: # Pxx given # Wxx = W11 + W12 + W21 + W22 Wxx = (1.0 - self.mW[0]) * self.P11_or_P22 # W11 + W12 = Wxx * G2: self.mW[1, 1] = Wxx * self.G2 * self.G3 self.mW[1, 2] = Wxx * self.G2 * (1 - self.G3) self.mP[1, 1] = self.mW[1, 1] / self.mW[1] if self.mW[1] > 0.0 else 0.0 self.mW[2, 1] = Wxx * (1 - self.G2) * self.G4 self.mW[2, 2] = Wxx * (1 - self.G2) * (1 - self.G4) self.mP[1, 2] = (self.mW[1, 2] / self.mW[1]) if self.mW[1] > 0.0 else 0.0 self.mP[1, 0] = 1 - self.mP[1, 1] - self.mP[1, 2] self.mP[2, 1] = (self.mW[2, 1] / self.mW[2]) if self.mW[2] > 0.0 else 0.0 self.mP[2, 2] = (self.mW[2, 2] / self.mW[2]) if self.mW[2] > 0.0 else 0.0 self.mP[2, 0] = 1 - self.mP[2, 1] - self.mP[2, 2] self.mP[0, 1] = (self.mW[1] - self.mW[1, 1] - self.mW[2, 1]) * W0inv self.mP[0, 2] = (self.mW[2] - self.mW[1, 2] - self.mW[2, 2]) * W0inv if self.mW[0] > 0.5: self.mP[0, 0] = 1 - self.mP[0, 1] - self.mP[0, 2] for i in range(3): for j in range(3): self.mW[i, j] = self.mW[i, i] * self.mP[i, j] self.solve() self.validate() pass # end of class @storables.register() class R1G4Model(_AbstractProbability): r""" Probability model for Reichweite 1 with 4 components. The independent variables (# = g*(g-1) = 12) are: .. math:: :nowrap: \begin{align*} & W_1 & P_{11} (W_1 < 0,5)\text{ or }P_{xx} (W_1 > 0,5) with P_{xx} = \frac {W_{22} + W_{23} + W_{24} + W_{32} + W_{33} + W_{34} + W_{42} + W_{43} + W_{44}}{W_2 + W_3 + W_4} \\ & R_2 = \frac{ W_2 }{W_2 + W_3 + W_4} & R_3 = \frac{ W_3 }{W_3 + W_4} \\ & G_2 = \frac{W_{22} + W_{23} + W_{24}}{\sum_{i=2}^{4}\sum_{j=2}^4{W_{ij}}} & G_3 = \frac{W_{32} + W_{33} + W_{34}}{\sum_{i=3}^{4}\sum_{j=2}^4{W_{ij}}} \\ & G_{22} = \frac{W_{22}}{W_{22} + W_{23} + W_{24}} & G_{23} = \frac{W_{23}}{W_{23} + W_{24}} \\ & G_{32} = \frac{W_{32}}{W_{32} + W_{33} + W_{34}} & G_{33} = \frac{W_{33}}{W_{33} + W_{34}} \\ & G_{42} = \frac{W_{42}}{W_{42} + W_{43} + W_{44}} & G_{44} = \frac{W_{43}}{W_{43} + W_{44}} \end{align*} Calculation of the other variables happens as follows: .. math:: :nowrap: \begin{align*} & \text{Calculate the base weight fractions of each component:} \\ & W_2 = (1 - W_1) \cdot R_1 \\ & W_3 = (1 - W_1 - W_2) \cdot R_2 \\ & W_4 = (1 - W_1 - W_2 - W_3) \\ & \\ & \text{if $W_1 \leq 0.5$:} \\ & \quad \text{$P_{11}$ is given}\\ & \quad W_{xx} = W_{22} + W_{23} + W_{24} + W_{32} + W_{33} + W_{34} + W_{42} + W_{43} + W_{44} = W_1 \cdot (1 - P_{11}) + W_2 + W_3 + W_4 \\ & \text{if $W_1 > 0.5$:} \\ & \quad \text{$P_{xx}$ is given and $P_{11}$ is derived further down} \\ & \quad W_{xx} = W_{22} + W_{23} + W_{24} + W_{32} + W_{33} + W_{34} + W_{42} + W_{43} + W_{44} = P_{xx} \cdot (W_2 + W_3 + W_4) \\ & \\ & \text{Caclulate a partial sum of the $2^{nd}$ component's contributions: } \\ & W_{2x} = W_{xx} \cdot G_2 \\ & \text{Calculate a partial sum of the $3^{d}$ and $4^{th}$ component's contributions:} \\ & W_{yx} = W_{xx} - W_{2x} \\ & \text{Calculate a partial sum of the $3^{d}$ component's contributions:} \\ & W_{3x} = W_{yx} \cdot G_3 \\ & \text{Calculate a partial sum of the $4^{th}$ component's contributions:} \\ & W_{4x} = W_{yx} - W_{3x} \\ & \\ & W_{22} = G_{22} \cdot W_{2x} \\ & W_{23} = G_{23} \cdot (W_{2x} - W_{22}) \\ & W_{24} = W{2x} - W_{22} - W_{23} \\ & \\ & W_{32} = G_{32} \cdot W_{3x} \\ & W_{33} = G_{33} \cdot (W_{3x} - W_{32}) \\ & W_{34} = W{3x} - W_{32} - W_{33} \\ & \\ & W_{42} = G_{42} \cdot W_{4x} \\ & W_{43} = G_{43} \cdot (W_{4x} - W_{42}) \\ & W_{44} = W{4x} - W_{42} - W_{43} \\ & \\ & \text{ From the above weight fractions the junction probabilities for any combination of $2^{nd}$, $3^{d}$ and $4^{th}$ type components can be calculated. } \\ & \text{ The remaining probabilities are: } \\ & P_{12} = \begin{dcases} \frac{W_2 - W_{22} - W_{32} - W_{42}}{W_1}, & \text{if $W_1 > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & P_{13} = \begin{dcases} \frac{W_3 - W_{23} - W_{33} - W_{43}}{W_1}, & \text{if $W_1 > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & P_{14} = \begin{dcases} \frac{W_4 - W_{24} - W_{34} - W_{44}}{W_1}, & \text{if $W_1 > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & \\ & \text{if $W_1 \leq 0.5$}: \\ & \quad P_{11} = 1 - P_{12} - P_{13} - P_{14} \\ & \text{Remainder of weight fraction can now be calculated as follows:} \\ & \quad W_{ij} = {W_{ii}} \cdot {P_{ij}} \quad \forall {i,j} \in \left[ {1, 4} \right] \\ \end{align*} """ # MODEL METADATA: class Meta(_AbstractProbability.Meta): store_id = "R1G4Model" # PROPERTIES _G = 4 inherit_W1 = BoolProperty( default=False, text="Inherit flag for W1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) W1 = FloatProperty( default=0.6, text="W1", math_text=r"$W_1$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_W1", inherit_from="parent.based_on.probabilities.W1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_P11_or_P22 = BoolProperty( default=False, text="Inherit flag for P11_or_P22", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) P11_or_P22 = FloatProperty( default=0.25, text="P11_or_P22", math_text=r"$P_{11} %s$ or $\newline P_{22} %s$" % ( mt_range(0.0, "W_1", 0.5), mt_range(0.5, "W_1", 1.0)), persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_P11_or_P22", inherit_from="parent.based_on.probabilities.P11_or_P22", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_R1 = BoolProperty( default=False, text="Inherit flag for R1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) R1 = FloatProperty( default=0.5, text="W2/(W2+W3+W4)", math_text=r"$\large\frac{W_2}{W_2 + W_3 + W_4}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_R1", inherit_from="parent.based_on.probabilities.R1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_R2 = BoolProperty( default=False, text="Inherit flag for R2", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) R2 = FloatProperty( default=0.5, text="W3/(W3+W4)", math_text=r"$\large\frac{W_3}{W_3 + W_4}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_R2", inherit_from="parent.based_on.probabilities.R2", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_G1 = BoolProperty( default=False, text="Inherit flag for G1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) G1 = FloatProperty( default=0.5, text="(W22+W23+W24)/(W22+W23+W24+W32+W33+W34+W42+W43+W44)", math_text=r"$\large\frac{\sum_{j=2}^{4} W_{2j}}{\sum_{i=2}^{4} \sum_{j=2}^{4} W_{ij}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G1", inherit_from="parent.based_on.probabilities.G1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_G2 = BoolProperty( default=False, text="Inherit flag for G2", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) G2 = FloatProperty( default=0.4, text="(W32+W33+W34)/(W32+W33+W34+W42+W43+W44)", math_text=r"$\large\frac{\sum_{j=2}^{4} W_{3j}}{\sum_{i=3}^{4} \sum_{j=2}^{4} W_{ij}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G2", inherit_from="parent.based_on.probabilities.G2", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_G11 = BoolProperty( default=False, text="Inherit flag for G11", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) G11 = FloatProperty( default=0.5, text="W22/(W22+W23+W24)", math_text=r"$\large\frac{W_{22}}{\sum_{j=2}^{4} W_{2j}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G11", inherit_from="parent.based_on.probabilities.G11", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_G12 = BoolProperty( default=False, text="Inherit flag for G12", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) G12 = FloatProperty( default=0.5, text="W23/(W23+W24)", math_text=r"$\large\frac{W_{23}}{\sum_{j=3}^{4} W_{2j}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G12", inherit_from="parent.based_on.probabilities.G12", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_G21 = BoolProperty( default=False, text="Inherit flag for G21", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) G21 = FloatProperty( default=0.8, text="W32/(W32+W33+W34)", math_text=r"$\large\frac{W_{32}}{\sum_{j=2}^{4} W_{3j}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G21", inherit_from="parent.based_on.probabilities.G21", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_G22 = BoolProperty( default=False, text="Inherit flag for G22", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) G22 = FloatProperty( default=0.8, text="W33/(W32+W34)", math_text=r"$\large\frac{W_{33}}{\sum_{j=3}^{4} W_{3j}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G22", inherit_from="parent.based_on.probabilities.G22", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_G31 = BoolProperty( default=False, text="Inherit flag for G31", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) G31 = FloatProperty( default=0.7, text="W42/(W42+W43+W44)", math_text=r"$\large\frac{W_{42}}{\sum_{j=2}^{4} W_{4j}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G31", inherit_from="parent.based_on.probabilities.G31", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_G32 = BoolProperty( default=False, text="Inherit flag for G32", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) G32 = FloatProperty( default=0.5, text="W43/(W43+W44)", math_text=r"$\large\frac{W_{43}}{\sum_{j=3}^{4} W_{4j}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G32", inherit_from="parent.based_on.probabilities.G32", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, W1=0.6, P11_or_P22=0.25, R1=0.5, R2=0.5, G1=0.5, G2=0.4, G11=0.5, G12=0.5, G21=0.8, G22=0.75, G31=0.7, G32=0.5, inherit_W1=False, inherit_P11_or_P22=False, inherit_R1=False, inherit_R2=False, inherit_G1=False, inherit_G2=False, inherit_G11=False, inherit_G12=False, inherit_G21=False, inherit_G22=False, inherit_G31=False, inherit_G32=False, *args, **kwargs): super(R1G4Model, self).__init__(R=1, *args, **kwargs) with self.data_changed.hold(): self.W1 = not_none(W1, 0.6) self.inherit_W1 = inherit_W1 self.P11_or_P22 = not_none(P11_or_P22, 0.25) self.inherit_P11_or_P22 = inherit_P11_or_P22 self.R1 = not_none(R1, 0.5) self.inherit_R1 = inherit_R1 self.R2 = not_none(R2, 0.5) self.inherit_R2 = inherit_R2 self.G1 = not_none(G1, 0.5) self.inherit_G1 = inherit_G1 self.G2 = not_none(G2, 0.4) self.inherit_G2 = inherit_G2 self.G11 = not_none(G11, 0.5) self.inherit_G11 = inherit_G11 self.G12 = not_none(G12, 0.5) self.inherit_G12 = inherit_G12 self.G21 = not_none(G21, 0.8) self.inherit_G21 = inherit_G21 self.G22 = not_none(G22, 0.75) self.inherit_G22 = inherit_G22 self.G31 = not_none(G31, 0.7) self.inherit_G31 = inherit_G31 self.G32 = not_none(G32, 0.5) self.inherit_G32 = inherit_G32 self.update() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update(self): with self.monitor_changes(): self.mW[0] = self.W1 self.mW[1] = (1.0 - self.mW[0]) * self.R1 self.mW[2] = (1.0 - self.mW[0] - self.mW[1]) * self.R2 self.mW[3] = 1.0 - self.mW[0] - self.mW[1] - self.mW[2] W0inv = 1.0 / self.mW[0] if self.mW[0] > 0.0 else 0.0 if self.mW[0] < 0.5: # P11 is given self.mP[0, 0] = self.P11_or_P22 Wxx = self.mW[0] * (self.mP[0, 0] - 1) + self.mW[1] + self.mW[2] + self.mW[3] else: # P22 is given Wxx = self.P11_or_P22 * (self.mW[1] + self.mW[2] + self.mW[3]) W1x = Wxx * self.G1 # = W11 + W12 + W13 Wyx = (Wxx - W1x) # = W21 + W22 + W23 + W31 + W32 + W33 W2x = Wyx * self.G2 # = W21 + W22 + W23 W3x = Wyx - W2x # = W31 + W32 + W33 self.mW[1, 1] = self.G11 * W1x self.mW[1, 2] = self.G12 * (W1x - self.mW[1, 1]) self.mW[1, 3] = W1x - self.mW[1, 1] - self.mW[1, 2] self.mW[2, 1] = self.G21 * W2x self.mW[2, 2] = self.G22 * (W2x - self.mW[2, 1]) self.mW[2, 3] = W2x - self.mW[2, 1] - self.mW[2, 2] self.mW[3, 1] = self.G31 * W3x self.mW[3, 2] = self.G32 * (W3x - self.mW[3, 1]) self.mW[3, 3] = W3x - self.mW[3, 1] - self.mW[3, 2] for i in range(1, 4): self.mP[i, 0] = 1 for j in range(1, 4): self.mP[i, j] = self.mW[i, j] / self.mW[i] if self.mW[i] > 0 else 0 self.mP[i, 0] -= self.mP[i, j] self.mW[i, 0] = self.mW[i] * self.mP[i, 0] self.mP[0, 1] = (self.mW[1] - self.mW[1, 1] - self.mW[2, 1] - self.mW[3, 1]) * W0inv self.mP[0, 2] = (self.mW[2] - self.mW[1, 2] - self.mW[2, 2] - self.mW[3, 2]) * W0inv self.mP[0, 3] = (self.mW[3] - self.mW[1, 3] - self.mW[2, 3] - self.mW[3, 3]) * W0inv if self.mW[0] >= 0.5: self.mP[0, 0] = 1 - self.mP[0, 1] - self.mP[0, 2] - self.mP[0, 3] for i in range(4): for j in range(4): self.mW[i, j] = self.mW[i] * self.mP[i, j] self.solve() self.validate() pass # end of class PyXRD-0.8.4/pyxrd/probabilities/models/R2models.py000066400000000000000000000503761363064711000220270ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from mvc.models.properties import BoolProperty, FloatProperty from pyxrd.generic.mathtext_support import mt_range from pyxrd.generic.io import storables from pyxrd.generic.utils import not_none from pyxrd.generic.models.properties import InheritableMixin from pyxrd.refinement.refinables.properties import RefinableMixin from .base_models import _AbstractProbability from mvc.models.properties.action_mixins import SetActionMixin __all__ = [ "R2G2Model", "R2G3Model" ] @storables.register() class R2G2Model(_AbstractProbability): r""" Probability model for Reichweite 2 with 2 components. The 4 (=g^2) independent variables are: .. math:: :nowrap: \begin{align*} & W_1 & P_{112} (W_1 leq \nicefrac{2}{3}) \text{ or }P_{211} (W_1 > \nicefrac{2}{3}) \\ & P_{21} & P_{122} (P_{21} leq \nicefrac{1}{2}) \text{ or }P_{221} (P_{21} > \nicefrac{1}{2}) \\ \end{align*} Calculation of the other variables happens as follows: .. math:: :nowrap: \begin{align*} & W_2 = 1 - W_1 \\ & P_{22} = 1 - P_{21} \\ & \\ & W_{21} = W_2 \cdot P_{21} \\ & W_{21} = W_{12} \\ & W_{11} = W_1 - W_{21} \\ & W_{22} = W_{2} \cdot P_{22} \\ & \\ & \text{if $W_1 leq \nicefrac{2}{3}$:} \\ & \quad \text{$P_{112}$ is given}\\ & \quad P_{211} = \begin{dcases} \frac{W_{11}}{W_{21}} \cdot P_{112} , & \text{if $W_{21} > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & \\ & \text{if $W_1 > \nicefrac{2}{3}$:} \\ & \quad \text{$P_{211}$ is given}\\ & \quad P_{112} = \begin{dcases} \frac{W_{21}}{W_{11}} \cdot P_{211} , & \text{if $W_{11} > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & \\ & P_{212} = 1 - P_{211} \\ & P_{111} = 1 - P_{112} \\ & \\ & \text{if $P_{21} leq \nicefrac{1}{2}$:} \\ & \quad \text{$P_{122}$ is given}\\ & \quad P_{221} = \begin{dcases} \frac{W_{12}}{W_{22}} \cdot P_{122} , & \text{if $W_{22} > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & \\ & \text{if $P_{21} > \nicefrac{1}{2}$:} \\ & \quad \text{$P_{221}$ is given}\\ & \quad P_{122} = \begin{dcases} \frac{W_{22}}{W_{12}} \cdot P_{221} , & \text{if $W_{12} > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & P_{121} = 1 - P_{122} \\ & P_{222} = 1 - P_{221} \\ \end{align*} """ # MODEL METADATA: class Meta(_AbstractProbability.Meta): store_id = "R2G2Model" # PROPERTIES: _G = 2 twothirds = 2.0 / 3.0 inherit_W1 = BoolProperty( default=False, text="Inherit flag for W1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) W1 = FloatProperty( default=0.75, text="W1 (> 0.5)", math_text=r"$W_1 (> 0.5)$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.5, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_W1", inherit_from="parent.based_on.probabilities.W1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_P112_or_P211 = BoolProperty( default=False, text="Inherit flag for P112_or_P211", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) P112_or_P211 = FloatProperty( default=0.75, text="P112 (W1 < 2/3) or\nP211 (W1 > 2/3)", math_text=r"$P_{112} %s$ or $\newlineP_{211} %s$" % ( mt_range(1.0 / 2.0, "W_1", 2.0 / 3.0), mt_range(2.0 / 3.0, "W_1", 1.0)), persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_P112_or_P211", inherit_from="parent.based_on.probabilities.P112_or_P211", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_P21 = BoolProperty( default=False, text="Inherit flag for P21", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) P21 = FloatProperty( default=0.75, text="P21", math_text=r"$P_{21}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_P21", inherit_from="parent.based_on.probabilities.P21", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_P122_or_P221 = BoolProperty( default=False, text="Inherit flag for P122_or_P221", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) P122_or_P221 = FloatProperty( default=0.75, text="P112 (W1 < 1/2) or\nP221 (W1 > 1/2)", math_text=r"$P_{122} %s$ or $\newlineP_{221} %s$" % ( mt_range(0.0, "W_1", 1.0 / 2.0), mt_range(1.0 / 2.0, "W_1", 1.0)), persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_P122_or_P221", inherit_from="parent.based_on.probabilities.P122_or_P221", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, W1=0.75, P112_or_P211=0.75, P21=0.75, P122_or_P221=0.75, inherit_W1=False, inherit_P112_or_P211=False, inherit_P21=False, inherit_P122_or_P221=False, *args, **kwargs): super(R2G2Model, self).__init__(R=2, *args, **kwargs) with self.data_changed.hold(): self.W1 = not_none(W1, 0.75) self.inherit_W1 = inherit_W1 self.P112_or_P211 = not_none(P112_or_P211, 0.75) self.inherit_P112_or_P211 = inherit_P112_or_P211 self.P21 = not_none(P21, 0.75) self.inherit_P21 = inherit_P21 self.P122_or_P221 = not_none(P122_or_P221, 0.75) self.inherit_P122_or_P221 = inherit_P122_or_P221 self.update() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update(self): with self.monitor_changes(): self.mW[0] = self.W1 self.mW[1] = 1.0 - self.mW[0] self.mP[1, 0] = self.P21 self.mP[1, 1] = 1.0 - self.mP[1, 0] self.mW[1, 0] = self.mW[1] * self.mP[1, 0] self.mW[1, 1] = self.mW[1] * self.mP[1, 1] self.mW[0, 1] = self.mW[1, 0] self.mW[0, 0] = self.mW[0] - self.mW[1, 0] if self.mW[0] <= self.twothirds: self.mP[0, 0, 1] = self.P112_or_P211 if self.mW[1, 0] == 0.0: self.mP[1, 0, 0] = 0.0 else: self.mP[1, 0, 0] = self.mP[0, 0, 1] * self.mW[0, 0] / self.mW[1, 0] else: self.mP[1, 0, 0] = self.P112_or_P211 if self.mW[0, 0] == 0.0: self.mP[0, 0, 1] = 0.0 else: self.mP[0, 0, 1] = self.mP[1, 0, 0] * self.mW[1, 0] / self.mW[0, 0] self.mP[1, 0, 1] = 1.0 - self.mP[1, 0, 0] self.mP[0, 0, 0] = 1.0 - self.mP[0, 0, 1] if self.mP[1, 0] <= 0.5: self.mP[0, 1, 1] = self.P122_or_P221 self.mP[1, 1, 0] = self.mP[0, 1, 1] * self.mW[0, 1] / self.mW[1, 1] else: self.mP[1, 1, 0] = self.P122_or_P221 self.mP[0, 1, 1] = self.mP[1, 1, 0] * self.mW[1, 1] / self.mW[0, 1] self.mP[0, 1, 0] = 1.0 - self.mP[0, 1, 1] self.mP[1, 1, 1] = 1.0 - self.mP[1, 1, 0] self.solve() self.validate() pass # end of class @storables.register() class R2G3Model(_AbstractProbability): r""" (Restricted) probability model for Reichweite 2 with 3 components. The (due to restrictions only) 6 independent variables are: .. math:: :nowrap: \begin{align*} & W_{1} & P_{111} \text{(if $\nicefrac{1}{2} \leq W_1 < \nicefrac{2}{3}$) or} P_{x1x} \text{(if $\nicefrac{2}{3} \leq W_1 \leq 1)$ with $x \in \left\{ {2,3} \right\}$} \\ & G_1 = \frac{W_2}{W_2 + W_3} & G_2 = \frac{W_{212} + W_{213}}{W_{212} + W_{213} + W_{312} + W_{313}} \\ & G_3 = \frac{W_{212}}{W_{212} + W_{213}} & G_4 = \frac{W_{312}}{W_{312} + W_{313}} \\ \end{align*} This model can not describe mixed layers in which the last two components occur right after each other in a stack. In other words there is always an alternation between (one or more) layers of the first component and a single layer of the second or third component. Therefore, the weight fraction of the first component (:math:`W_1`) needs to be > than 1/2. The restriction also translates in the following: .. math:: :nowrap: \begin{align*} & P_{22} = P_{23} = P_{32} = P_{33} = 0 \\ & P_{21} = P_{31} = 1 \\ & \\ & P_{122} = P_{123} = P_{132} = P_{133} = 0 \\ & P_{121} = P_{131} = 1 \\ & \\ & P_{222} = P_{223} = P_{232} = P_{233} = 0 \\ & P_{221} = P_{231} = 1 \\ & \\ & P_{322} = P_{323} = P_{332} = P_{333} = 0 \\ & P_{321} = P_{331} = 1 \\ \end{align*} Using the above, we can calculate a lot of the weight fractions of stacks: .. math:: :nowrap: \begin{align*} & W_{22} = W_{23} = W_{32} = W_{33} 0 \\ & W_{21} = W_{2} \\ & W_{31} = W_{3} \\ & \\ & W_{122} = W_{123} = W_{132} = W_{133} = 0 \\ & W_{121} = W_{12} = W_{21} = W_2 \\ & W_{131} = W_{13} = W_{31} = W_3 \\ & W_{11} = W_1 - W_{12} - W_{13} \\ & \\ & W_{221} = W_{231} = W_{222} = W_{223} = W_{232} = W_{233} = 0 \\ & W_{331} = W_{331} = W_{322} = W_{323} = W_{332} = W_{333} = 0 \\ \end{align*} Then the remaining fractions and probablities can be calculated as follows: .. math:: :nowrap: \begin{align*} & W_2 = G_1 * (1 - W_1) \\ & W_3 = 1 - W_1 - W_2 \\ & \\ & W_x = W_2 + W_3 & & \text{if $W_1 < \nicefrac{2}{3}$:} \\ & \quad \text{$P_{111}$ is given}\\ & \quad P_{x1x} = \begin{dcases} 1 - \frac{W_1 - W_x}{W_x} \cdot (1 - P_{111}, & \text{if $W_x > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & \\ & \text{if $W_1 \geq \nicefrac{2}{3}$:} \\ & \quad \text{$P_{x1x}$ is given}\\ & \quad P_{111} = \begin{dcases} 1 - \frac{W_x}{W_1 - W_x} \cdot (1 - P_{x1x}, & \text{if $(W_1 - W_x) > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ & \\ & W_{x1x} = W_x \cdot P_{x1x} \\ & W_{21x} = G_2 \cdot W_{x1x} \\ & W_{31x} = W_{x1x} - W_{21x} \\ & \\ & W_{212} = G_3 \cdot W_{21x} \\ & W_{213} = (1 - G_3) \cdot W_{21x} \\ & W_{211} = W_{21} - W_{212} - W_{213} \\ & \\ & W_{312} = G_4 \cdot W_{31x} \\ & W_{313} = (1 - G_4) \cdot W_{31x} \\ & W_{311} = W_{31} - W_{312} - W_{313} \\ & \\ & W_{111} = W_{11} \cdot P_{111} \\ & W_{112} = W_{12} - W_{212} - W_{312} \\ & W_{112} = W_{13} - W_{213} - W_{313} \\ & \\ & \text{Calculate the remaining P using:} \\ & P_{ijk} = \begin{dcases} \frac{W_{ijk}}{W_{ij}}, & \text{if $W_{ij} > 0$} \\ 0, & \text{otherwise} \end{dcases} \\ \end{align*} """ # MODEL METADATA: class Meta(_AbstractProbability.Meta): store_id = "R2G3Model" # PROPERTIES: _G = 3 twothirds = 2.0 / 3.0 inherit_W1 = BoolProperty( default=False, text="Inherit flag for W1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) W1 = FloatProperty( default=0.8, text="W1 (> 0.5)", math_text=r"$W_1 (> 0.5)$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.5, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_W1", inherit_from="parent.based_on.probabilities.W1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_P111_or_P212 = BoolProperty( default=False, text="Inherit flag for P112_or_P211", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) P111_or_P212 = FloatProperty( default=0.9, text="P111 (W1 < 2/3) or\nPx1x (W1 > 2/3)", math_text=r"$P_{111} %s$ or $\newline P_{x1x} %s$" % ( mt_range(0.5, "W_1", 2.0 / 3.0), mt_range(2.0 / 3.0, "W_1", 1.0)), persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_P111_or_P212", inherit_from="parent.based_on.probabilities.P111_or_P212", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_G1 = BoolProperty( default=False, text="Inherit flag for G1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) G1 = FloatProperty( default=0.9, text="W2/(W2+W3)", math_text=r"$\large\frac{W_2}{W_3 + W_2}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G1", inherit_from="parent.based_on.probabilities.G1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_G2 = BoolProperty( default=False, text="Inherit flag for G2", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) G2 = FloatProperty( default=0.9, text="(W212+W213)/(W212+W213+W312+W313)", math_text=r"$\large\frac{W_{212} + W_{213}}{W_{212} + W_{213} + W_{312} + W_{313}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G2", inherit_from="parent.based_on.probabilities.G2", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_G3 = BoolProperty( default=False, text="Inherit flag for G3", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) G3 = FloatProperty( default=0.9, text="W212/(W212+W213)", math_text=r"$\large\frac{W_{212}}{W_{212} + W_{213}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G3", inherit_from="parent.based_on.probabilities.G3", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_G4 = BoolProperty( default=False, text="Inherit flag for G4", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) G4 = FloatProperty( default=0.9, text="W312/(W312+W313)", math_text=r"$\large\frac{W_{312}}{W_{312} + W_{313}}$", persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_G4", inherit_from="parent.based_on.probabilities.G4", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, W1=0.8, P111_or_P212=0.9, G1=0.9, G2=0.9, G3=0.9, G4=0.9, inherit_W1=False, inherit_P111_or_P212=False, inherit_G1=False, inherit_G2=False, inherit_G3=False, inherit_G4=False, *args, **kwargs): super(R2G3Model, self).__init__(R=2, *args, **kwargs) with self.data_changed.hold(): self.W1 = not_none(W1, 0.8) self.inherit_W1 = inherit_W1 self.P111_or_P212 = not_none(P111_or_P212, 0.9) self.inherit_P111_or_P212 = inherit_P111_or_P212 self.G1 = not_none(G1, 0.9) self.inherit_G1 = inherit_G1 self.G2 = not_none(G2, 0.9) self.inherit_G2 = inherit_G2 self.G3 = not_none(G3, 0.9) self.inherit_G3 = inherit_G3 self.G4 = not_none(G4, 0.9) self.inherit_G4 = inherit_G4 self.update() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update(self): with self.monitor_changes(): # calculate Wx's: self.mW[0] = self.W1 self.mW[1] = (1.0 - self.mW[0]) * self.G1 self.mW[2] = 1.0 - self.mW[0] - self.mW[1] # consequences of restrictions: self.mW[1, 1] = 0 self.mW[1, 2] = 0 self.mW[2, 1] = 0 self.mW[2, 2] = 0 self.mW[0, 1, 0] = self.mW[0, 1] = self.mW[1, 0] = self.mW[1] self.mW[0, 2, 0] = self.mW[0, 2] = self.mW[2, 0] = self.mW[2] self.mW[0, 0] = self.mW[0] - self.mW[0, 1] - self.mW[0, 2] # continue calculations: Wx = self.mW[1] + self.mW[2] if self.mW[0] < self.twothirds: self.mP[0, 0, 0] = self.P111_or_P212 Px0x = 1 - (self.mW[0] - Wx) / Wx * (1 - self.mP[0, 0, 0]) if Wx != 0 else 0.0 else: Px0x = self.P111_or_P212 self.mP[0, 0, 0] = 1 - Wx / (self.mW[0] - Wx) * (1 - Px0x) if (self.mW[0] - Wx) != 0 else 0.0 Wx0x = Wx * Px0x W10x = self.G2 * Wx0x W20x = Wx0x - W10x self.mW[1, 0, 1] = self.G3 * W10x self.mW[1, 0, 2] = (1 - self.G3) * W10x self.mW[1, 0, 0] = self.mW[1, 0] - self.mW[1, 0, 1] - self.mW[1, 0, 2] self.mW[2, 0, 1] = self.G4 * W20x self.mW[2, 0, 2] = (1 - self.G4) * W20x self.mW[2, 0, 0] = self.mW[2, 0] - self.mW[2, 0, 1] - self.mW[2, 0, 2] self.mW[0, 0, 0] = self.mW[0, 0] * self.mP[0, 0, 0] self.mW[0, 0, 1] = self.mW[0, 1] - self.mW[1, 0, 1] - self.mW[2, 0, 1] self.mW[0, 0, 2] = self.mW[0, 2] - self.mW[1, 0, 2] - self.mW[2, 0, 2] # Calculate remaining P: for i in range(3): for j in range(3): for k in range(3): self.mP[i, j, k] = self.mW[i, j, k] / self.mW[i, j] if self.mW[i, j] > 0 else 0.0 self.solve() self.validate() pass # end of class PyXRD-0.8.4/pyxrd/probabilities/models/R3models.py000066400000000000000000000165301363064711000220220ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from mvc.models.properties import BoolProperty, FloatProperty from pyxrd.generic.mathtext_support import mt_range from pyxrd.generic.io import storables from pyxrd.generic.utils import not_none from pyxrd.generic.models.properties import InheritableMixin from pyxrd.refinement.refinables.properties import RefinableMixin from .base_models import _AbstractProbability from mvc.models.properties.action_mixins import SetActionMixin __all__ = [ "R3G2Model" ] @storables.register() class R3G2Model(_AbstractProbability): r""" (Restricted) probability model for Reichweite 3 with 2 components. The (due to restrictions only) 2 independent variables are: .. math:: :nowrap: \begin{align*} & W_1 & P_{1111} \text{(if $\nicefrac{2}{3} \leq W_1 < \nicefrac{3}{4}$) or} P_{2112} \text{(if $\nicefrac{3}{4} \leq W_1 \leq 1)$} \\ \end{align*} This model can only describe mixed layers with more than :math:`\nicefrac{2}{3}` of the layers being of the first type, no two layers of the second type occur after each other, and in which the probability of finding a layer of the first type in between two layers of the second type is zero. This translates to the following conditions: .. math:: :nowrap: \begin{align*} & \nicefrac{2}{3} <= W_1 <= 1 \\ & P_{22} = 0 \\ & P_{212} = 0 \\ & \\ & \text{Since $P_{22} = 0$ and $P_{212} = 0$:} \\ & P_{1122} = P_{1212} = 0 \\ & \text{And thus:} \\ & P_{1121} = P_{1211} = 1 \\ \end{align*} The following probabilities are undefined, but are set to zero or one to make the validation correct. This doesn't matter much, since the weight fractions these probabilities are multiplied with, equal zero anyway (e.g. :math:`W_{2211} = W_{22} * P_{221} * P_{2211}` and :math:`W_{22}` is zero since :math:`P_{22}` is zero): .. math:: :nowrap: \begin{align*} & P_{1121} &= P_{1211} &= P_{2211} &= P_{2121} &= P_{2221} &= P_{1221} = 0 \\ & P_{1122} &= P_{1212} &= P_{2212} &= P_{2122} &= P_{2222} &= P_{1222} = 1 \\ \end{align*} The remaining probabilities and weight fractions can be calculated as follows: .. math:: :nowrap: \begin{align*} & W_2 = 1 - W_1 \\ & \\ & \text{if $W_1 < \nicefrac{3}{4}$:} \\ & \quad \text{$P_{1111}$ is given}\\ & \quad P_{1112} = 1 - P_{1111} \\ & \quad P_{2111} = P_{1112} * \frac{W_1 - 2 \cdot W_2}{W_2} \\ & \quad P_{2112} = 1 - P_{2111} \\ & \\ & \text{if $W_1 \geq \nicefrac{3}{4}$:} \\ & \quad \text{$P_{2112}$ is given}\\ & \quad P_{2111} = 1 - P_{2112} \\ & \quad P_{1111} = P_{2111} * \frac{W_2}{W_1 - 2 \cdot W_2} \\ & \quad P_{1112} = 1 - P_{1111} \\ & \\ & W_{111} = 3 \cdot W_1 - 2 \\ & W_{212} = W_{221} = W_{222} = W_{122} = 0 \\ & W_{211} = W_{121} = W_{112} = 1 - W_1 \\ \end{align*} """ # MODEL METADATA: class Meta(_AbstractProbability.Meta): store_id = "R3G2Model" # PROPERTIES: _G = 2 inherit_W1 = BoolProperty( default=False, text="Inherit flag for W1", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) W1 = FloatProperty( default=0.85, text="W1 (> 2/3)", math_text=r"$W_1 (> \frac{2}{3})$", persistent=True, visible=True, refinable=True, store_private=True, minimum=2.0 / 3.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_W1", inherit_from="parent.based_on.probabilities.W1", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) inherit_P1111_or_P2112 = BoolProperty( default=False, text="Inherit flag for P1111_or_P2112", persistent=True, visible=True, set_action_name="update", mix_with=(SetActionMixin,) ) P1111_or_P2112 = FloatProperty( default=0.75, text="P1111 (W1 < 3/4) or\nP2112 (W1 > 3/4)", math_text=r"$P_{1111} %s$ or $\newline P_{2112} %s$" % ( mt_range(2.0 / 3.0, "W_1", 3.0 / 4.0), mt_range(3.0 / 4.0, "W_1", 1.0)), persistent=True, visible=True, refinable=True, store_private=True, minimum=0.0, maximum=1.0, is_independent=True, inheritable=True, inherit_flag="inherit_P1111_or_P2112", inherit_from="parent.based_on.probabilities.P1111_or_P2112", set_action_name="update", mix_with=(SetActionMixin, RefinableMixin, InheritableMixin) ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, W1=0.85, P1111_or_P2112=0.75, *args, **kwargs): super(R3G2Model, self).__init__(R=3, *args, **kwargs) with self.data_changed.hold(): self.W1 = not_none(W1, 0.85) self.P1111_or_P2112 = not_none(P1111_or_P2112, 0.75) self.update() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update(self): with self.monitor_changes(): self.mW[0] = self.W1 self.mW[1] = 1.0 - self.W1 if self.mW[0] <= 0.75: # 0,0,0,0 is given self.mP[0, 0, 0, 0] = self.P1111_or_P2112 self.mP[0, 0, 0, 1] = max(min(1.0 - self.mP[0, 0, 0, 0], 1.0), 0.0) self.mP[1, 0, 0, 0] = max(min(self.mP[0, 0, 0, 1] * (self.mW[0] - 2 * self.mW[1]) / self.mW[1], 1.0), 0.0) self.mP[1, 0, 0, 1] = max(min(1.0 - self.mP[1, 0, 0, 0], 1.0), 0.0) else: # 1,0,0,1 is given self.mP[1, 0, 0, 1] = self.P1111_or_P2112 self.mP[1, 0, 0, 0] = max(min(1.0 - self.mP[1, 0, 0, 1], 1.0), 0.0) self.mP[0, 0, 0, 0] = max(min(1.0 - self.mP[1, 0, 0, 0] * self.mW[1] / (self.mW[0] - 2 * self.mW[1]), 1.0), 0.0) self.mP[0, 0, 0, 1] = max(min(1.0 - self.mP[0, 0, 0, 0], 1.0), 0.0) # since P11=0 and P101=0, actual values don't matter: self.mP[0, 0, 1, 0] = 1.0 self.mP[0, 0, 1, 1] = 0.0 self.mP[0, 1, 0, 0] = 1.0 self.mP[0, 1, 0, 1] = 0.0 self.mP[0, 1, 1, 0] = 1.0 self.mP[0, 1, 1, 1] = 0.0 self.mP[1, 0, 1, 0] = 1.0 self.mP[1, 0, 1, 1] = 0.0 self.mP[1, 1, 0, 0] = 1.0 self.mP[1, 1, 0, 1] = 0.0 self.mP[1, 1, 1, 0] = 1.0 self.mP[1, 1, 1, 1] = 0.0 self.mW[0, 0, 0] = max(min(3 * self.mW[0] - 2, 1.0), 0.0) self.mW[1, 0, 1] = self.mW[1, 1, 0] = self.mW[1, 1, 1] = self.mW[0, 1, 1] = 0.0 self.mW[1, 0, 0] = self.mW[0, 1, 0] = self.mW[0, 0, 1] = max(min(1 - self.mW[0], 1.0), 0.0) self.solve() self.validate() pass # end of class PyXRD-0.8.4/pyxrd/probabilities/models/__init__.py000066400000000000000000000034401363064711000220650ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import numpy as np from .R0models import R0G1Model, R0G2Model, R0G3Model, R0G4Model, R0G5Model, R0G6Model # @UnusedImport from .R1models import R1G2Model, R1G3Model, R1G4Model # @UnusedImport from .R2models import R2G2Model, R2G3Model # @UnusedImport from .R3models import R3G2Model # @UnusedImport # Overview of what is: # x = currently implemented # np = not possible # -/o = not yet implemented # o = priority # # G1 G2 G3 G4 G5 G6 # R0 x x x x x x # R1 np x x x - - # R2 np x x - - - # R3 np x - - - - RGbounds = np.array([ [1, 1, 1, 1, 1, 1], [-1, 1, 1, 1, 0, 0], [-1, 1, 1, 0, 0, 0], [-1, 1, 0, 0, 0, 0], ]) def get_Gbounds_for_R(R, G): global RGbounds maxR, maxG = RGbounds.shape low, upp = 1, 6 if R >= 0 and R < maxR: bounds = RGbounds[R] low, upp = 1 + np.argmax(bounds == 1), maxG - np.argmax(bounds[::-1] == 1) else: raise ValueError("Cannot yet handle R%d!" % R) return (low, upp, max(min(G, upp), low)) def get_Rbounds_for_G(G, R): global RGbounds maxR, maxG = RGbounds.shape low, upp = 0, 0 if G >= 1 and G <= maxG: bounds = RGbounds[:, G - 1] low, upp = np.argmax(bounds == 1), maxR - np.argmax(bounds[::-1] == 1) - 1 else: raise ValueError("Cannot yet handle %d layer structures!" % G) return (low, upp, max(min(R, upp), low)) def get_correct_probability_model(R, G): global RGbounds if (RGbounds[R, G - 1] > 0): return globals()["R%dG%dModel" % (R, G)] else: raise ValueError("Cannot (yet) handle R%d for %d layer structures!" % (R, G)) PyXRD-0.8.4/pyxrd/probabilities/models/base_models.py000066400000000000000000000255311363064711000226100ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from itertools import product from copy import deepcopy from contextlib import contextmanager import numpy as np from mvc.models.properties import StringProperty, LabeledProperty from pyxrd.generic.io import Storable from pyxrd.generic.models import DataModel from pyxrd.generic.models.properties import IndexProperty from pyxrd.refinement.refinables.mixins import RefinementGroup from pyxrd.refinement.refinables.metaclasses import PyXRDRefinableMeta class _AbstractProbability(RefinementGroup, DataModel, Storable, metaclass=PyXRDRefinableMeta): # MODEL INTEL: class Meta(DataModel.Meta): pass phase = property(DataModel.parent.fget, DataModel.parent.fset) # PROPERTIES: name = StringProperty(default="Probabilities", text="Name") W_valid = LabeledProperty(default=None, text="Valid W matrix") W_valid_mask = None P_valid = LabeledProperty(default=None, text="Valid P matrix") P_valid_mask = None _R = -1 @property def R(self): return self._R @property def rank(self): return self.G ** max(self.R, 1) _G = 0 @property def G(self): return self._G _W = None _P = None @IndexProperty def mP(self, indeces): r, ind = self._get_Pxy_from_indeces(indeces) return self._lP[r][ind] @mP.setter def mP(self, indeces, value): r, ind = self._get_Pxy_from_indeces(indeces) self._lP[r][ind] = value @IndexProperty def mW(self, indeces): r, ind = self._get_Wxy_from_indeces(indeces) return self._lW[r][ind] @mW.setter def mW(self, indeces, value): r, ind = self._get_Wxy_from_indeces(indeces) self._lW[r][ind] = value @property def _flags(self): return [ 1 if getattr(self, prop.inherit_flag, False) else 0 for prop in self.Meta.all_properties if getattr(prop, "inheritable", False) ] # REFINEMENT GROUP IMPLEMENTATION: @property def refine_title(self): return self.name @property def refine_descriptor_data(self): return dict( phase_name=self.phase.name, component_name="*" ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, R=-1, *args, **kwargs): super(_AbstractProbability, self).__init__(*args, **kwargs) self._R = R self._create_matrices() def _create_matrices(self): """ Creates a list of matrices for different 'levels' of R: e.g. when R=3 with g layers there can be 4 different W matrixes: Wi = gxg matrix Wij = g²xg² matrix Wijk = g³xg³ matrix Wijkl = another g³xg³ matrix (= Wijk * Pijkl) and 3 different P matrices: Pij = gxg matrix Pijk = g²xg² matrix Pijkl = g³xg³ matrix """ R = max(self.R, 1) self._lW = [None] * (R + 1) self._lP = [None] * R for r in range(R): lrank = self.G ** (r + 1) self._lW[r] = np.zeros(shape=(lrank, lrank), dtype=float) self._lP[r] = np.zeros(shape=(lrank, lrank), dtype=float) self._lW[-1] = np.zeros(shape=(lrank, lrank), dtype=float) self._W = self._lW[-2] self._P = self._lP[-1] # validity matrices: self.W_valid = np.array([False] * (R + 1)) self.W_valid_mask = np.array([None] * (R + 1)) self.P_valid = np.array([False] * R) self.P_valid_mask = np.array([None] * R) # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update(self): raise NotImplementedError def _clamp_set_and_update(self, name, value, minimum=0.0, maximum=1.0): clamped = min(max(value, minimum), maximum) if getattr(self, name) != clamped: setattr(self, name, clamped) self.update() def solve(self): """ This 'solves' the other W and P matrices using the 'top' P and W matrix calculated in the update method. """ for num in range(1, self.R): # W matrices: for base in product(list(range(self.G)), repeat=num): self.mW[base] = 0 for i in range(self.G): self.mW[base] += self.mW[(i,) + base] # P matrices: p_num = num + 1 for base in product(list(range(self.G)), repeat=p_num): W = self.mW[base[:-1]] self.mP[base] = self.mW[base] / W if W > 0 else 0.0 # one extra W matrix: self._lW[-1][:] = np.dot(self._W, self._P) def validate(self): """ Checks wether the calculated matrices are valid, and stores the validation results in 'masks': matrices of the same size, in which the values correspond with 1 minus the number of validation rules that specific W or P value has failed for. """ def _validate_WW(W, R): """Validation rules for the product of a W and P matrix""" W_valid_mask = np.ones_like(W) rank = self.G ** max(R, 1) # sum of the cols (W...x's) need to equal W... for i in range(rank): if abs(np.sum(W[..., i]) - self._W[i, i]) > 1e4: W_valid_mask[..., i] -= 1 # sum of the entire matrix must equal one: if abs(np.sum(W) - 1.0) > 1e4: W_valid_mask -= 1 # values need to be between 0 and 1 for i in range(rank): for j in range(rank): if W[i, j] < 0.0 or W[i, j] > 1.0: W_valid_mask[i, i] -= 1 # if the sum of the mask values equals the square of the rank, # no rules have been broken: W_valid = (np.sum(W_valid_mask) == rank ** 2) return W_valid, W_valid_mask def _validate_W(W, R): """Validation rules for a diagonal W matrix""" W_valid_mask = np.ones_like(W) rank = self.G ** max(R, 1) # sum of the diagonal nees to be one if abs(np.sum(W) - 1.0) > 1e6: for i in range(rank): W_valid_mask[i, i] -= 1 # values need to be between 0 and 1 for i in range(rank): for j in range(rank): if W[i, j] < 0.0 or W[i, j] > 1.0: W_valid_mask[i, i] -= 1 # if the sum of the mask values equals the square of the rank, # no rules have been broken: W_valid = (np.sum(W_valid_mask) == rank ** 2) return W_valid, W_valid_mask def _validate_P(P, R): P_valid_mask = np.ones_like(P) rank = self.G ** max(R, 1) # sum of the rows need to be one for i in range(rank): if abs(np.sum(P[i, ...]) - 1.0) > 1e6: P_valid_mask[i, ...] -= 1 # values need to be between 0 and 1 for i in range(rank): for j in range(rank): if P[i, j] < 0.0 or P[i, j] > 1.0: P_valid_mask[i, j] -= 1 # if the sum of the mask values equals the square of the rank, # no rules have been broken: P_valid = (np.sum(P_valid_mask) == rank ** 2) return P_valid, P_valid_mask for i in range(max(self.R, 1)): self.W_valid[i], self.W_valid_mask[i] = _validate_W(self._lW[i], i + 1) self.P_valid[i], self.P_valid_mask[i] = _validate_P(self._lP[i], i + 1) # the extra W matrix validates differently: self.W_valid[-1], self.W_valid_mask[-1] = _validate_WW(self._lW[-1], self.R) def _get_Pxy_from_indeces(self, indeces): if not hasattr(indeces, "__iter__"): indeces = [indeces] l = len(indeces) assert(l > 1), "Two or more indeces needed to acces P elements, not %s" % indeces assert(l <= max(self.R, 1) + 1), "Too many indeces for an R%d model: %s" % (self.R, indeces) R = max(l - 1, 1) x, y = 0, 0 for i in range(1, R + 1): f = self.G ** (R - i) x += indeces[i - 1] * f y += indeces[i] * f return (l - 2), (x, y) def _get_Wxy_from_indeces(self, indeces): if not hasattr(indeces, "__iter__"): indeces = [indeces] l = len(indeces) assert(l > 0), "One or more indeces needed to acces W elements" assert(l <= max(self.R, 1) + 1), "Too many indeces for an R%d model: %s" % (self.R, indeces) if l == (max(self.R, 1) + 1): R = max(l - 1, 1) x, y = 0, 0 for i in range(1, R + 1): f = self.G ** (R - i) x += indeces[i - 1] * f y += indeces[i] * f return (l - 1), (x, y) else: R = max(l, 1) x = 0 for i in range(R): x += indeces[i] * self.G ** (R - (i + 1)) return (l - 1), (x, x) def get_all_matrices(self): return self._lW, self._lP def get_distribution_matrix(self): return self._W def get_distribution_array(self): return np.diag(self._W) def get_probability_matrix(self): return self._P _stashed_lP = None _stashed_lW = None _stashed_flags = None def _stash_matrices(self): """ Stashes the matrices for an update """ self._stashed_lW = deepcopy(np.asanyarray(self._lW)) self._stashed_lP = deepcopy(np.asanyarray(self._lP)) self._stashed_flags = deepcopy(np.asarray(self._flags)) def _compare_stashed_matrices(self): """ Unstashed matrices and compares with current values, if identical returns True """ if self._stashed_lP is not None and self._stashed_lW is not None: result = np.array_equal(self._stashed_lW, np.asanyarray(self._lW)) result = result and np.array_equal(self._stashed_lP, np.asanyarray(self._lP)) result = result and np.array_equal(self._stashed_flags, np.asanyarray(self._flags)) self._stashed_lW = None self._stashed_lP = None return result else: return False @contextmanager def monitor_changes(self): with self.data_changed.hold(): self._stash_matrices() yield if not self._compare_stashed_matrices(): self.data_changed.emit() pass # end of class PyXRD-0.8.4/pyxrd/probabilities/models/properties.py000066400000000000000000000011461363064711000225230ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from mvc.models.properties import FloatProperty class ProbabilityProperty(FloatProperty): """ A descriptor that will invoke the 'update' method on the instance it belongs to. """ def __init__(self, clamp=False, **kwargs): super(ProbabilityProperty, self).__init__(**kwargs) def __set__(self, instance, value): super(ProbabilityProperty, self).__set__(instance, value) instance.update() pass # end of class PyXRD-0.8.4/pyxrd/probabilities/views.py000066400000000000000000000313661363064711000202100ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from mvc.adapters.gtk_support.widgets import ScaleEntry from mvc.support.utils import rec_getattr from pyxrd.generic.views import BaseView, HasChildView from pyxrd.probabilities.models import RGbounds def get_correct_probability_views(probability, parent_view): """ Convenience function that creates both an `IndependentsView` and `MatrixView` based on the probability model passed. """ if probability is not None: G = probability.G R = probability.R rank = probability.rank if (RGbounds[R, G - 1] > 0): return IndependentsView(meta=probability.Meta, parent=parent_view), MatrixView(R=R, G=G, rank=rank, parent=parent_view) else: raise ValueError("Cannot (yet) handle R%d for %d layer structures!" % (R, G)) class EditProbabilitiesView(HasChildView, BaseView): """ Container view containing one `MatrixView` and one `IndependentsView` """ builder = resource_filename(__name__, "glade/probabilities.glade") top = "edit_probabilities" independents_container = "independents_box" independents_view = None dependents_container = "dependents_box" dependents_view = None widget_format = "prob_%s" def set_views(self, independents_view, dependents_view): self.independents_view = independents_view self._add_child_view(independents_view.get_top_widget(), self[self.independents_container]) self.dependents_view = dependents_view self._add_child_view(dependents_view.get_top_widget(), self[self.dependents_container]) self.show_all() return self.independents_view, self.dependents_view class ProbabilityViewMixin(): """ Mixin class providing interface code for controllers of both `MatrixView` and `IndependentsView` """ def update_matrices(self, W, P): raise NotImplementedError class IndependentsView(HasChildView, ProbabilityViewMixin, BaseView): """ Generic view that is able to generate an two-column list of inputs and labels using the models Meta (passed to the constructor). """ builder = resource_filename(__name__, "glade/R0_independents.glade") top = "R0independents_box" # generated table of weight fractions! (split in two columns) lbl_widget = "lbl_independents" sep_widget = "seperator_i" widget_format = "prob_%s" def __init__(self, meta, **kwargs): assert (meta is not None), "IndependentsView needs a model's Meta class!" BaseView.__init__(self, **kwargs) self.props = [ prop for prop in meta.all_properties if getattr(prop, "is_independent", False) ] all_props = { prop.label: prop for prop in meta.all_properties } N = len(self.props) def create_inputs(table): input_widgets = [None] * N check_widgets = [None] * N num_columns = 2 column_width = 3 for i, prop in enumerate(self.props): new_lbl = self.create_mathtext_widget(prop.math_title, prop.label) new_inp = ScaleEntry(lower=prop.minimum, upper=prop.maximum, enforce_range=True) new_inp.set_tooltip_text(prop.title) new_inp.set_name(self.widget_format % prop.label) self[self.widget_format % prop.label] = new_inp input_widgets[i] = new_inp j = (i % num_columns) * column_width table.attach(new_lbl, 0 + j, 1 + j, i / num_columns, (i / num_columns) + 1, xpadding=2, ypadding=2) table.attach(new_inp, 2 + j, 3 + j, i / num_columns, (i / num_columns) + 1, xpadding=2, ypadding=2) if prop.inheritable is not None: inh_prop = all_props.get(prop.inherit_flag, None) if inh_prop is None: raise ValueError("The inherit flag property `%s` is missing for `%s` on meta model with store id `%s`" % ( prop.inherit_flag, prop.label, meta.store_id )) new_check = Gtk.CheckButton(label="") new_check.set_tooltip_text(inh_prop.title) new_check.set_name(self.widget_format % inh_prop.label) new_check.set_sensitive(False) self[self.widget_format % inh_prop.label] = new_check check_widgets[i] = new_check table.attach(new_check, 1 + j, 2 + j, i / num_columns, (i / num_columns) + 1, xpadding=2, ypadding=2, xoptions=Gtk.AttachOptions.FILL) del new_inp, new_lbl return input_widgets, check_widgets self.i_box = self['i_box'] num_rows = int((N + 1) / 2) if not num_rows == 0: self.i_table = Gtk.Table(num_rows, 4, False) self.i_inputs, self.i_checks = create_inputs(self.i_table) else: self.i_inputs, self.i_checks = [], [] if len(self.i_inputs) == 0: self[self.lbl_widget].set_no_show_all(True) self[self.sep_widget].set_no_show_all(True) self[self.lbl_widget].hide() self[self.sep_widget].hide() else: self._add_child_view(self.i_table, self.i_box) def update_matrices(self, model): for i, (inp, check) in enumerate(zip(self.i_inputs, self.i_checks)): prop = self.props[i] inp.set_value(getattr(model, prop.label)) if prop.inherit_flag is not None: # Set checkbox sensitivity: inh_from = rec_getattr(model, prop.inherit_flag, None) check.set_sensitive(not inh_from is None) # Set checkbox state: inh_value = getattr(model, prop.inherit_flag) check.set_active(inh_value) # Set inherit value sensitivity inp.set_sensitive(not inh_value) elif check is not None: check.set_senstive(False) pass # end of class class MatrixView(HasChildView, ProbabilityViewMixin, BaseView): """ Generic view that is able to generate and update a P and W 'matrix' table with labels having correct tooltips (e.g. P110). Can be used for any combination of R, G and rank. """ builder = resource_filename(__name__, "glade/matrix.glade") top = "base_matrix_table" def __init__(self, R, G, rank, **kwargs): """ Eventhough only two of R,G and rank are required theoretically, they are still required by the __init__ function as a validity check. """ BaseView.__init__(self, **kwargs) # make sure valid params are passed: assert(rank == (G ** max(R, 1))) self.create_matrices(R, G, rank) def create_matrices(self, R, G, rank): # calculate moduli for parameter index calculation: lR = max(R, 1) mod = [0] * lR for i in range(lR): mod[i] = rank / (G ** (i + 1)) title_indeces = "".join([chr(105 + i) for i in range(lR + 1)]) # Generic function for both the W and P matrix labels setup def create_labels(rank, table, current_lR, fmt, tooltip=lambda x, y, current_lR, fmt: ""): labels = [[None] * rank for _ in range(rank)] for x in range(rank): for y in range(rank): new_lbl = Gtk.Label(label="") new_lbl.set_tooltip_markup(tooltip(x, y, current_lR, fmt)) new_lbl.set_justify(Gtk.Justification.CENTER) table.attach(new_lbl, y, y + 1, x, x + 1, xpadding=5, ypadding=5) labels[x][y] = new_lbl del new_lbl return labels # Generic functions for the tooltips: def diagonal_tooltips(x, y, current_lR, fmt): if x == y: indeces = [0] * current_lR for i in range(current_lR): indeces[i] = (int(x / mod[i + (lR - current_lR)]) % G) + 1 return fmt % tuple(indeces) else: return "-" def subdiagonal_tooltips(x, y, current_lR, fmt): rowsuf = [0] * current_lR # e.g. i,j,k colsuf = [0] * current_lR # e.g. l,m,n for i in range(current_lR): rowsuf[i] = (int(x / mod[i + (lR - current_lR)]) % G) + 1 colsuf[i] = (int(y / mod[i + (lR - current_lR)]) % G) + 1 # check if last n-1 and first n-1 of the suffices equal each other: visible = True for i in range(current_lR - 1): if rowsuf[i + 1] != colsuf[i]: visible = False if visible: return fmt % (tuple(rowsuf) + (colsuf[-1],)) else: return "-" # Create the matrices: self.w_tables = [] self.w_labels = [] self.w_titles = [] self.w_valids = [] self.p_tables = [] self.p_labels = [] self.p_titles = [] self.p_valids = [] def setup_everything(tables, titles, valids, labels, title, rank, current_lR, lbl_fmt, tooltips): w_table = Gtk.Table(rank, rank, True) tables.append(w_table) titles.append(title) valids.append("") labels.append(create_labels( rank, w_table, current_lR, lbl_fmt, tooltips )) for current_lR in range(1, lR + 1): rank = G ** current_lR setup_everything( self.w_tables, self.w_titles, self.w_valids, self.w_labels, "W" + title_indeces[:current_lR] + "", rank, current_lR, "W" + "%d"*current_lR + "", diagonal_tooltips ) setup_everything( self.p_tables, self.p_titles, self.p_valids, self.p_labels, "P" + title_indeces[:current_lR + 1] + "", rank, current_lR, "P" + "%d"*(current_lR + 1) + "", subdiagonal_tooltips ) # Add one extra W matrix: setup_everything( self.w_tables, self.w_titles, self.w_valids, self.w_labels, "W" + title_indeces + "", G ** lR, lR, "W" + "%d"*(lR + 1) + "", subdiagonal_tooltips ) self.show_w_matrix(len(self.w_tables) - 2) self.show_p_matrix(len(self.w_tables) - 1) return def update_matrices(self, model): lW, lP = model.get_all_matrices() def update_matrix(matrix, labels, mask=None, valid=False): shape = matrix.shape for i in range(shape[0]): for j in range(shape[1]): markup = "%.3f" if mask is not None: fgcol = "#AA0000" if mask[i, j] < 1 else "#00AA00" else: fgcol = "#000000" labels[i][j].set_markup(markup % (fgcol, matrix[i, j])) for i, W in enumerate(lW): update_matrix(W, self.w_labels[i], model.W_valid_mask[i]) fgcol, msg = ("#00AA00", "valid") if model.W_valid[i] else ("#AA0000", "invalid") self.w_valids[i] = "%s" % (fgcol, msg) self["lbl_W_valid"].set_markup(self.w_valids[i]) for i, P in enumerate(lP): update_matrix(P, self.p_labels[i], model.P_valid_mask[i]) fgcol, msg = ("#00AA00", "valid") if model.P_valid[i] else ("#AA0000", "invalid") self.p_valids[i] = "%s" % (fgcol, msg) self["lbl_P_valid"].set_markup(self.p_valids[i]) def show_w_matrix(self, index): index = max(min(index, len(self.w_tables) - 1), 0) self._add_child_view(self.w_tables[index], self['w_box']) self["lbl_W_title"].set_markup(self.w_titles[index]) self["lbl_W_valid"].set_markup(self.w_valids[index]) self.show_all() return index def show_p_matrix(self, index): index = max(min(index, len(self.p_tables) - 1), 0) self._add_child_view(self.p_tables[index], self['p_box']) self["lbl_P_title"].set_markup(self.p_titles[index]) self["lbl_P_valid"].set_markup(self.w_valids[index]) self.show_all() return index pass # end of class PyXRD-0.8.4/pyxrd/project/000077500000000000000000000000001363064711000153065ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/project/__init__.py000066400000000000000000000000001363064711000174050ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/project/controllers.py000066400000000000000000000344011363064711000202300ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import os from contextlib import contextmanager import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import GObject, Pango, Gdk # @UnresolvedImport from mvc import Controller from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory from mvc.adapters.gtk_support.widgets.threaded_task_box import ThreadedTaskBox from mvc.support.cancellable_thread import CancellableThread from mvc.support.gui_loop import run_when_idle from pyxrd.generic.controllers.line_controllers import BackgroundController from pyxrd.generic.views.line_views import BackgroundView from pyxrd.generic.views.treeview_tools import new_text_column, new_toggle_column, new_pb_column from pyxrd.generic.controllers import BaseController, ObjectListStoreController from pyxrd.file_parsers.xrd_parsers import xrd_parsers from pyxrd.specimen.models import Specimen class ProjectController(ObjectListStoreController): treemodel_property_name = "specimens" treemodel_class_type = Specimen columns = [ ] delete_msg = "Deleting a specimen is irreversible!\nAre You sure you want to continue?" auto_adapt = True def register_view(self, view): if view is not None and self.model is not None: if self.parent is not None: # is this still needed? tv = self.view["project_specimens"] tv.set_model(self.treemodel) self.view.treeview = tv self.view.set_x_range_sensitive(self.model.axes_xlimit == 1) self.view.set_y_range_sensitive(self.model.axes_ylimit == 1) return def _idle_register_view(self, *args, **kwargs): super(ProjectController, self)._idle_register_view(*args, **kwargs) def adapt(self, *args, **kwargs): super(ProjectController, self).adapt(*args, **kwargs) def setup_treeview(self, widget): super(ProjectController, self).setup_treeview(widget) store = self.treemodel widget.connect('button-press-event', self.specimen_tv_button_press) # First reset & then (re)create the columns of the treeview: for col in widget.get_columns(): widget.remove_column(col) # Name column: col = new_text_column('Name', text_col=store.c_name, min_width=125, xalign=0.0, ellipsize=Pango.EllipsizeMode.END) setattr(col, "colnr", store.c_name) widget.append_column(col) # Check boxes: def toggle_renderer(column, cell, model, itr, data=None): active = False if model.iter_is_valid(itr): col = column.get_col_attr("active") active = model.get_value(itr, col) cell.set_property('active', active) return def setup_check_column(title, colnr): col = new_toggle_column(title, toggled_callback=(self.specimen_tv_toggled, (store, colnr)), data_func=toggle_renderer, resizable=False, expand=False, activatable=True, active_col=colnr) setattr(col, "colnr", colnr) widget.append_column(col) setup_check_column('Exp', store.c_display_experimental) if self.model.layout_mode == "FULL": setup_check_column('Cal', store.c_display_calculated) setup_check_column('Sep', store.c_display_phases) # Up and down arrows: def setup_image_button(image, colnr): col = new_pb_column("", resizable=False, expand=False, stock_id=image) setattr(col, "colnr", colnr) widget.append_column(col) setup_image_button("213-up-arrow", 501) setup_image_button("212-down-arrow", 502) def edit_object(self, obj): pass # clear this method, we're not having an 'edit' view pane... @BaseController.status_message("Importing multiple specimens...", "add_specimen") def import_multiple_specimen(self): def on_accept(dialog): ## TODO MOVE THIS (PARTIALLY?) TO THE MODEL LEVEL ## filenames = dialog.get_filenames() parser = getattr(dialog.get_filter(), "parser") task = ThreadedTaskBox() window = DialogFactory.get_custom_dialog( task, parent=self.view.get_top_widget()) # Status: status_dict = dict( total_files=len(filenames), current_file=0, specimens=[] ) # Task: def load_specimens(stop=None): for filename in filenames: if stop is not None and stop.is_set(): return # Error message is case parsing fails: message = "An unexpected error has occurred when trying to parse %s:\n\n" % os.path.basename(filename) message += "%s\n\n" message += "This is most likely caused by an invalid or unsupported file format." # Run & report any errors: with DialogFactory.error_dialog_handler( message, self.view.get_top_widget(), title="Failed to load file", reraise=False): specimens = Specimen.from_experimental_data(filename=filename, parent=self.model, parser=parser) status_dict["specimens"] += specimens status_dict["current_file"] += 1 # Cancel & stop events: def on_interrupted(*args, **kwargs): window.hide() # Status label update: def gui_callback(): task.set_status("Loading file %d/%d ..." % ( status_dict["current_file"], status_dict["total_files"] )) return True gui_timeout_id = GObject.timeout_add(250, gui_callback) # Complete event: @run_when_idle def on_complete(*args, **kwargs): last_iter = None for specimen in status_dict["specimens"]: last_iter = self.model.specimens.append(specimen) if last_iter is not None: self.view["project_specimens"].set_cursor(last_iter) GObject.source_remove(gui_timeout_id) window.hide() window.destroy() # Run task box: task.connect("cancelrequested", on_interrupted) task.connect("stoprequested", on_interrupted) task.set_status("Loading ...") task.start() window.show_all() # Run thread: self.thread = CancellableThread(load_specimens, on_complete) self.thread.start() DialogFactory.get_load_dialog(title="Select XRD files for import", filters=xrd_parsers.get_import_file_filters(), parent=self.view.get_top_widget(), multiple=True).run(on_accept) @BaseController.status_message("Deleting specimen...", "del_specimen") def delete_selected_specimens(self): """ Asks the user for confirmation and if positive deletes all the selected specimens. Does nothing when no specimens are selected. """ selection = self.get_selected_objects() if selection is not None and len(selection) >= 1: def delete_objects(dialog): for obj in selection: if obj is not None: self.model.specimens.remove(obj) DialogFactory.get_confirmation_dialog( message='Deleting a specimen is irreversible!\nAre You sure you want to continue?', parent=self.view.get_top_widget() ).run(delete_objects) @BaseController.status_message("Removing backgrounds...", "del_bg_specimen") def remove_backgrounds(self, specimens): """ Opens the 'remove background' dialog for the given specimens, raises a ValueError error if (one of) the specimens is not part of this project. """ def on_automated(dialog): for specimen in specimens: if not specimen in self.model.specimens: raise ValueError("Specimen `%s` is not part of this Project!" % specimen) else: specimen.experimental_pattern.bg_type = 0 # Linear see settings specimen.experimental_pattern.find_bg_position() specimen.experimental_pattern.remove_background() specimen.experimental_pattern.clear_bg_variables() def on_not_automated(dialog): for specimen in specimens: if not specimen in self.model.specimens: raise ValueError("Specimen `%s` is not part of this Project!" % specimen) else: bg_view = BackgroundView(parent=self.parent.view) BackgroundController(model=specimen.experimental_pattern, view=bg_view, parent=self) bg_view.present() # Ask user if he/she wants automation: DialogFactory.get_confirmation_dialog( "Do you want to perform an automated linear background subtraction?", parent=self.parent.view.get_top_widget() ).run(on_automated, on_not_automated) def edit_specimen(self): selection = self.get_selected_objects() if selection is not None and len(selection) == 1: # TODO move the specimen view & controller into the project level self.parent.view.specimen.present() @BaseController.status_message("Creating new specimen...", "add_specimen") def add_specimen(self): specimen = Specimen(parent=self.model, name="New Specimen") self.model.specimens.append(specimen) self.view.specimens_treeview.set_cursor(self.treemodel.on_get_path(specimen)) self.edit_specimen() return True @contextmanager def _multi_operation_context(self): with self.model.hold_mixtures_data_changed(): with self.model.data_changed.hold(): yield # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @Controller.observe("name", assign=True) def notif_change_name(self, model, prop_name, info): self.parent.update_title() return @Controller.observe("axes_xlimit", assign=True) def notif_xlimit_toggled(self, model, prop_name, info): self.view.set_x_range_sensitive(int(self.model.axes_xlimit) == 1) @Controller.observe("axes_ylimit", assign=True) def notif_ylimit_toggled(self, model, prop_name, info): self.view.set_y_range_sensitive(int(self.model.axes_ylimit) == 1) @Controller.observe("layout_mode", assign=True) def notif_layout_mode(self, model, prop_name, info): self.parent.set_layout_mode(self.model.layout_mode) if self.view is not None: self.setup_treeview(self.view.treeview) # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def specimen_tv_toggled(self, cell, path, model, colnr): if model is not None: itr = model.get_iter(path) model.set_value(itr, colnr, not cell.get_active()) return True return False def specimen_tv_button_press(self, tv, event): specimen = None current_specimens = self.parent.model.current_specimens or [] ret = tv.get_path_at_pos(int(event.x), int(event.y)) if ret is not None: path, col, x, y = ret specimen = self.treemodel.get_user_data_from_path(path) if event.button == 3: if specimen is not None: # clicked a specimen which is not in the current selection, # so clear selection and select it if not specimen in current_specimens: self.select_object(specimen) else: # clicked an empty space, so clear selection self.select_object(None) self.view.show_specimens_context_menu(event) return True elif event.type == Gdk.EventType._2BUTTON_PRESS and specimen is not None and getattr(col, "colnr") == self.treemodel.c_name: # @UndefinedVariable self.parent.on_edit_specimen_activate(event) return True elif (event.button == 1 or event.type == Gdk.EventType._2BUTTON_PRESS) and specimen is not None: # @UndefinedVariable column = getattr(col, "colnr") if column in (self.treemodel.c_display_experimental, self.treemodel.c_display_calculated, self.treemodel.c_display_phases): if column == self.treemodel.c_display_experimental: specimen.display_experimental = not specimen.display_experimental elif column == self.treemodel.c_display_calculated: specimen.display_calculated = not specimen.display_calculated elif column == self.treemodel.c_display_phases: specimen.display_phases = not specimen.display_phases # TODO FIXME self.treemodel.on_row_changed(ret) return True elif column == 501: self.model.move_specimen_down(specimen) self.parent.model.current_specimens = self.get_selected_objects() return True elif column == 502: self.model.move_specimen_up(specimen) self.parent.model.current_specimens = self.get_selected_objects() return True def objects_tv_selection_changed(self, selection): ObjectListStoreController.objects_tv_selection_changed(self, selection) self.parent.model.current_specimens = self.get_selected_objects() return True pass # end of class PyXRD-0.8.4/pyxrd/project/glade/000077500000000000000000000000001363064711000163625ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/project/glade/project.glade000066400000000000000000001762671363064711000210510ustar00rootroot00000000000000 True False 190-circle-plus 2 True False 192-circle-remove 2 True False 150-edit 2 True False 359-file-export 2 True False 358-file-import 2 True False 358-file-import 2 False <PyXRDGroup>/ Add specimen False True False <PyXRD_Main>/project_actions/add_specimen img_add_specimen False True Import specimens False True False img_import_specimen False True False Edit specimen False True False img_edit_specimen False Replace data False True False img_replace_data False Export data False True False img_export_data False True False Delete specimen False True False img_del_specimen False True 180 1 10 180 1 10 999999995904 100 500 1000000000000 100 500 1 100 1 1 10 1 100 1 1 10 1 100 1 1 10 True True 280 True False 10 5 2 10 5 True False 1 Name GTK_FILL True False 1 Author 1 2 GTK_FILL True False 1 Date 2 3 GTK_FILL True True False False 1 2 2 3 True True False False 1 2 1 2 True True 15 False False 1 2 True False 1 0 0 0 True False Description 3 4 GTK_FILL GTK_FILL 10 True True in True True 2 word 6 6 1 2 3 4 True False 1 Layout mode 4 5 GTK_FILL True False 1 2 4 5 GTK_FILL True False General False True False 10 12 2 10 2 True False 1 Experimental color GTK_FILL True False 1 Calculated color 4 5 GTK_FILL True False 1 Pattern offset 8 9 GTK_FILL True False 1 Group patterns by 9 10 GTK_FILL True False 1 Experimental linewidth 1 2 GTK_FILL True False 1 Calculated linewidth 5 6 GTK_FILL True False 1 Default label position [0-1] 10 11 GTK_FILL True False 1 2 11 12 GTK_FILL True False 0 0 False True True True #000000000000 1 2 GTK_FILL True False 0 0 False True True True #000000000000 1 2 4 5 GTK_FILL True False 0 0 True True 4 False False project_display_exp_lw 1 True 1 2 1 2 GTK_FILL True False 0 0 True True 4 False False project_display_calc_lw 1 True 1 2 5 6 GTK_FILL True False 0 0 True True 5 False False 1 2 8 9 GTK_FILL True False 0 0 True True 4 0 False False project_display_group_by True 1 2 9 10 GTK_FILL True False 0 0 True True 5 False False 1 2 10 11 GTK_FILL True False 1 Y-scale normalization 11 12 GTK_FILL 200 True False 1 2 2 3 200 True False 1 2 6 7 200 True False 1 2 3 4 200 True False 1 2 7 8 True False 1 Experimental linestyle 2 3 GTK_FILL True False 1 Experimental marker 3 4 GTK_FILL True False 1 Calculated linestyle 6 7 GTK_FILL True False 1 Calculated marker 7 8 GTK_FILL 1 True False Patterns 1 False True False 10 9 3 5 5 True False 0 X scale GTK_FILL Stretch X-axis to fit window False True True False 0 True 3 3 4 GTK_FILL True False 3 5 6 True False 0 Y scale 6 7 GTK_FILL True False 1 3 GTK_FILL True False 1 3 6 7 GTK_FILL Y-axis visible False True True False 0 True 3 8 9 GTK_FILL True False 1 3 True False False 10 2 2 10 5 True False 1 max. [°2θ] 13 1 2 GTK_FILL True False 1 min. [°2θ] 13 GTK_FILL True False 0 0 True True False False project_axes_xmin 2 1 2 True False 0 0 True True False False project_axes_xmax 2 1 2 1 2 1 3 1 3 GTK_FILL True False False 10 2 2 10 5 True False 1 max. [counts] 13 1 2 GTK_FILL True False 1 min. [counts] 13 GTK_FILL True False 0 0 True True False False project_axes_ymin True if-valid 1 2 True False 0 0 True True False False project_axes_ymax True if-valid 1 2 1 2 1 3 7 8 GTK_FILL True False 7 8 Show d-spacing (in nm) False True True False Will use the wavelength of the first specimen's goniometer setup 0 True 3 4 5 GTK_FILL 2 True False Plot 2 False True False 10 7 3 10 5 True False 1 Angle GTK_FILL True False 1 Line style 2 3 GTK_FILL 100 True False 1 3 2 3 GTK_FILL True False 0 0 True True 5 False False 1 2 GTK_FILL True False 1 Base connection 3 4 GTK_FILL True False 1 Colour 6 7 GTK_FILL True False 1 Label alignment 1 2 GTK_FILL 100 True False 1 3 1 2 GTK_FILL 100 True False 1 3 3 4 GTK_FILL True False 0 ° 2 3 True False 0 0 False 50 True True True #ffff0000ffff 1 3 6 7 True False 1 Top connection 4 5 GTK_FILL 100 True False 1 3 4 5 GTK_FILL True False 1 Offset from base 5 6 GTK_FILL True False 0 0 True True 5 False False 1 3 5 6 GTK_FILL 3 True False Markers 3 False True False True False 0 3 3 <b>Specimens</b> True False True 0 True True never in True True GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK True both True True 1 PyXRD-0.8.4/pyxrd/project/importing.py000066400000000000000000000203051363064711000176700ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os, string import numpy as np import xml.etree.ElementTree as ET from pyxrd.mixture.models import Mixture from pyxrd.project.models import Project from pyxrd.specimen.models import Specimen from pyxrd.phases.models import Phase from pyxrd.atoms.models import Atom def safe_float(num): return float(num.replace(",", ".")) def create_project_from_sybilla_xml(filename, **kwargs): """ Creates a new project structure from a Sybilla XML file. Some information (e.g. the actual XRD pattern) is not present and will still need to be imported manually. """ tree = ET.parse(filename) root = tree.getroot() basename = os.path.basename(filename) # Create the project: if "name" in kwargs: kwargs.pop("name") if "layout_mode" in kwargs: kwargs.pop("layout_mode") project = Project(name=basename, layout_mode="FULL", **kwargs) # Add a specimen: specimen = Specimen(name=basename, parent=project) project.specimens.append(specimen) # Add a mixture: mixture = Mixture(name=basename, auto_run=False, parent=project) mixture.add_specimen_slot(specimen, 1.0, 0.0) project.mixtures.append(mixture) with project.data_changed.ignore(): with mixture.data_changed.ignore(): for child in root: if child.tag == "basic_params": # Goniometer parameters: step_size = safe_float(child.attrib['step_size']) wavelength = safe_float(child.attrib['lambda']) / 10.0 steps = int(1 + (specimen.goniometer.max_2theta - specimen.goniometer.min_2theta) / step_size) specimen.goniometer.min_2theta = safe_float(child.attrib['min2theta']) specimen.goniometer.max_2theta = safe_float(child.attrib['max2theta']) specimen.goniometer.steps = steps specimen.goniometer.wavelength = wavelength elif child.tag == "diffractometer": # Some more goniometer parameters, and specimen parameters: specimen.goniometer.radius = safe_float(child.attrib['gonio_radius']) specimen.goniometer.divergence = safe_float(child.attrib['diverg_slit']) specimen.goniometer.soller1 = safe_float(child.attrib['Soller1']) specimen.goniometer.soller2 = safe_float(child.attrib['Soller2']) specimen.sample_length = safe_float(child.attrib['sample_length']) elif child.tag == "content": # Content tag contains 'Mixture' data for xmlPhaseContent in child: name = xmlPhaseContent.attrib['name'] fraction = safe_float(xmlPhaseContent.attrib['content']) / 100. mixture.add_phase_slot(name, fraction) elif child.tag == "mixture": # Mixture tag corresponds with the phases in the project level, # not an actual Mixture object: for xmlPhase in child: name = xmlPhase.attrib['name'] sigma = xmlPhase.attrib['sigma_star'] csds = safe_float(xmlPhase.find('distribution').attrib['Tmean']) G = 1 R = 0 W = [1.0, ] if xmlPhase.attrib['type'] != 'mono': prob = xmlPhase.find('probability') G = int(prob.attrib['no_of_comp']) R = int(prob.attrib['R']) # create phase and add to project: phase = Phase(name=name, sigma_star=sigma, G=G, R=R, parent=project) phase.CSDS_distribution.average = csds project.phases.append(phase) # set probability: if R == 0 and G != 1: xmlW = prob.find('W') W = np.array([ float(int(safe_float(xmlW.attrib[string.ascii_lowercase[i]]) * 1000.)) / 1000. for i in range(G) ]) for i in range(G - 1): setattr(phase.probabilities, "F%d" % (i + 1), W[i] / np.sum(W[i:])) if R == 1 and G == 2: pass # TODO # ... TODO other probs # parse components: for i, layer in enumerate(xmlPhase.findall("./layer_and_edge/layer")): component = phase.components[i] component.name = layer.attrib['name'] component.d001 = safe_float(layer.attrib['d_spacing']) / 10.0 component.default_c = safe_float(layer.attrib['d_spacing']) / 10.0 component.delta_c = safe_float(layer.attrib['d_spacing_delta']) / 10.0 component.ucp_b.value = 0.9 component.ucp_a.factor = 0.57735 component.ucp_a.prop = (component, 'cell_b') component.ucp_a.enabled = True atom_type_map = { # "NH4": "FIXME" "K": "K1+", "O": "O1-", "Si": "Si2+", "OH": "OH1-", "Fe": "Fe1.5+", "Al": "Al1.5+", "Mg": "Mg1+", "H2O": "H2O", "Gly": "Glycol", "Ca": "Ca2+", "Na": "Na1+", } # add atoms: fe_atom = None encountered_oxygen = False for atom in layer.findall("atom"): atom_type_name = atom_type_map.get(atom.attrib['type'], None) if atom_type_name: if atom_type_name == "O1-": # From this point we're dealing with layer atoms encountered_oxygen = True atom = Atom( name=atom.attrib['type'], default_z=safe_float(atom.attrib['position']) / 10.0, pn=safe_float(atom.attrib['content']), atom_type_name=atom_type_name, parent=component ) if encountered_oxygen: component.layer_atoms.append(atom) else: component.interlayer_atoms.append(atom) atom.resolve_json_references() # Assume this is the octahedral iron... if encountered_oxygen and atom_type_name == "Fe1.5+": fe_atom = atom # Set the atom relation if fe_atom is not None: component.ucp_b.constant = 0.9 component.ucp_b.factor = 0.0043 component.ucp_b.prop = (fe_atom, 'pn') component.ucp_b.enabled = True pass # end of if pass # end of for # Map phases onto mixture names: for phase in project.phases: for slot, phase_name in enumerate(mixture.phases): if phase.name == phase_name: mixture.set_phase(0, slot, phase) return project PyXRD-0.8.4/pyxrd/project/models.py000066400000000000000000000744161363064711000171570ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import time from contextlib import contextmanager from mvc.models.properties import ( FloatProperty, BoolProperty, StringProperty, ListProperty, IntegerProperty, StringChoiceProperty, IntegerChoiceProperty, SignalMixin ) from mvc.observers import ListObserver from pyxrd.__version import __version__ from pyxrd.data import settings from pyxrd.generic.models import DataModel from pyxrd.generic.models.event_context_manager import EventContextManager from pyxrd.generic.io import storables, Storable, get_case_insensitive_glob from pyxrd.generic.utils import not_none from pyxrd.atoms.models import AtomType from pyxrd.phases.models import Phase from pyxrd.specimen.models import Specimen from pyxrd.mixture.models.mixture import Mixture #from pyxrd.mixture.models.insitu_behaviours import InSituBehaviour @storables.register() class Project(DataModel, Storable): """ This is the top-level object that servers the purpose of combining the different objects (most notably :class:`~.atoms.models.AtomType`'s, :class:`~.phases.models.Phase`'s, :class:`~.specimen.models.Specimen`'s and :class:`~.mixture.models.Mixture`'s). It also provides a large number of display-related 'default' properties (e.g. for patterns and their markers, axes etc.). For more details: see the property descriptions. Example usage: .. code-block:: python >>> from pyxrd.project.models import Project >>> from pyxrd.generic.io.xrd_parsers import XRDParser >>> from pyxrd.specimen.models import Specimen >>> project = Project(name="New Project", author="Mr. X", layout_mode="FULL", axes_dspacing=True) >>> for specimen in Specimen.from_experimental_data("/path/to/xrd_data_file.rd", parent=project): ... project.specimens.append(specimen) ... """ # MODEL INTEL: class Meta(DataModel.Meta): store_id = "Project" file_filters = [ ("PyXRD Project files", get_case_insensitive_glob("*.pyxrd", "*.zpd")), ] import_filters = [ ("Sybilla XML files", get_case_insensitive_glob("*.xml")), ] # PROPERTIES: filename = None #: The project name name = StringProperty( default="", text="Name", visible=True, persistent=True ) #: The project data (string) date = StringProperty( default="", text="Date", visible=True, persistent=True ) #: The project description description = StringProperty( default=None, text="Description", visible=True, persistent=True, widget_type="text_view", ) #: The project author author = StringProperty( default="", text="Author", visible=True, persistent=True ) #: Flag indicating whether this project has been changed since it was last saved. needs_saving = BoolProperty( default=True, visible=False, persistent=False ) #: The layout mode this project should be displayed in layout_mode = StringChoiceProperty( default=settings.DEFAULT_LAYOUT, text="Layout mode", visible=True, persistent=True, choices=settings.DEFAULT_LAYOUTS, mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The manual lower limit for the X-axis axes_xmin = FloatProperty( default=settings.AXES_MANUAL_XMIN, text="min. [°2T]", visible=True, persistent=True, minimum=0.0, widget_type="spin", mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The manual upper limit for the X-axis axes_xmax = FloatProperty( default=settings.AXES_MANUAL_XMAX, text="max. [°2T]", visible=True, persistent=True, minimum=0.0, widget_type="spin", mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: Whether or not to stretch the X-axis over the entire available display axes_xstretch = BoolProperty( default=settings.AXES_XSTRETCH, text="Stetch x-axis to fit window", visible=True, persistent=True, mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: Flag toggling between d-spacing (when True) or 2-Theta axes (when False) axes_dspacing = BoolProperty( default=settings.AXES_DSPACING, text="Show d-spacing in x-axis", visible=True, persistent=True, mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: Whether or not the y-axis should be shown axes_yvisible = BoolProperty( default=settings.AXES_YVISIBLE, text="Y-axis visible", visible=True, persistent=True, mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The manual lower limit for the Y-axis (in counts) axes_ymin = FloatProperty( default=settings.AXES_MANUAL_YMIN, text="min. [counts]", visible=True, persistent=True, minimum=0.0, widget_type="spin", mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The manual upper limit for the Y-axis (in counts) axes_ymax = FloatProperty( default=settings.AXES_MANUAL_YMAX, text="max. [counts]", visible=True, persistent=True, minimum=0.0, widget_type="spin", mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: What type of y-axis to use: raw counts, single or multi-normalized units axes_ynormalize = IntegerChoiceProperty( default=settings.AXES_YNORMALIZE, text="Y scaling", visible=True, persistent=True, choices=settings.AXES_YNORMALIZERS, mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: Whether to use automatic or manual Y limits axes_ylimit = IntegerChoiceProperty( default=settings.AXES_YLIMIT, text="Y limit", visible=True, persistent=True, choices=settings.AXES_YLIMITS, mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The offset between patterns as a fraction of the maximum intensity display_plot_offset = FloatProperty( default=settings.PLOT_OFFSET, text="Pattern offset", visible=True, persistent=True, minimum=0.0, widget_type="float_entry", mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The number of patterns to group ( = having no offset) display_group_by = IntegerProperty( default=settings.PATTERN_GROUP_BY, text="Group patterns by", visible=True, persistent=True, minimum=1, widget_type="spin", mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The relative position (from the pattern offset) for pattern labels #: as a fraction of the patterns intensity display_label_pos = FloatProperty( default=settings.LABEL_POSITION, text="Default label position", visible=True, persistent=True, widget_type="float_entry", mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: What type of scale to use for X-axis, automatic or manual axes_xlimit = IntegerChoiceProperty( default=settings.AXES_XLIMIT, text="X limit", visible=True, persistent=True, choices=settings.AXES_XLIMITS, mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The default angle at which marker labels are displayed display_marker_angle = FloatProperty( default=settings.MARKER_ANGLE, text="Angle", visible=True, persistent=True, widget_type="float_entry", mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The default offset for marker labels display_marker_top_offset = FloatProperty( default=settings.MARKER_TOP_OFFSET, text="Offset from base", visible=True, persistent=True, widget_type="float_entry", mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The default marker label alignment (one of settings.MARKER_ALIGNS) display_marker_align = StringChoiceProperty( default=settings.MARKER_ALIGN, text="Label alignment", visible=True, persistent=True, choices=settings.MARKER_ALIGNS, mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The default marker label base (one of settings.MARKER_BASES) display_marker_base = IntegerChoiceProperty( default=settings.MARKER_BASE, text="Base connection", visible=True, persistent=True, choices=settings.MARKER_BASES, mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The default marker label top (one of settings.MARKER_TOPS) display_marker_top = IntegerChoiceProperty( default=settings.MARKER_TOP, text="Top connection", visible=True, persistent=True, choices=settings.MARKER_TOPS, mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The default marker style (one of settings.MARKER_STYLES) display_marker_style = StringChoiceProperty( default=settings.MARKER_STYLE, text="Line style", visible=True, persistent=True, choices=settings.MARKER_STYLES, mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The default marker color display_marker_color = StringProperty( default=settings.MARKER_COLOR, text="Color", visible=True, persistent=True, widget_type="color", mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The default calculated profile color display_calc_color = StringProperty( default=settings.CALCULATED_COLOR, text="Calculated color", visible=True, persistent=True, widget_type="color", mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The default experimental profile color display_exp_color = StringProperty( default=settings.EXPERIMENTAL_COLOR, text="Experimental color", visible=True, persistent=True, widget_type="color", mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The default calculated profile line width display_calc_lw = IntegerProperty( default=settings.CALCULATED_LINEWIDTH, text="Calculated line width", visible=True, persistent=True, widget_type="spin", mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The default experimental profile line width display_exp_lw = IntegerProperty( default=settings.EXPERIMENTAL_LINEWIDTH, text="Experimental line width", visible=True, persistent=True, widget_type="spin", mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The default calculated profile line style display_calc_ls = StringChoiceProperty( default=settings.CALCULATED_LINESTYLE, text="Calculated line style", visible=True, persistent=True, choices=settings.PATTERN_LINE_STYLES, mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The default experimental profile line style display_exp_ls = StringChoiceProperty( default=settings.EXPERIMENTAL_LINESTYLE, text="Experimental line style", visible=True, persistent=True, choices=settings.PATTERN_LINE_STYLES, mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The default calculated profile line style display_calc_marker = StringChoiceProperty( default=settings.CALCULATED_MARKER, text="Calculated line marker", visible=True, persistent=True, choices=settings.PATTERN_MARKERS, mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The default calculated profile line style display_exp_marker = StringChoiceProperty( default=settings.EXPERIMENTAL_MARKER, text="Experimental line marker", visible=True, persistent=True, choices=settings.PATTERN_MARKERS, mix_with=(SignalMixin,), signal_name="visuals_changed" ) #: The list of specimens specimens = ListProperty( default=[], text="Specimens", data_type=Specimen, visible=True, persistent=True, ) #: The list of phases phases = ListProperty( default=[], text="Phases", data_type=Phase, visible=False, persistent=True ) #: The list of atom types atom_types = ListProperty( default=[], text="Atom types", data_type=AtomType, visible=False, persistent=True ) #: The list of Behaviours #behaviours = ListProperty( # default=[], text="Behaviours", data_type=InSituBehaviour, # visible=False, persistent=True #) #: The list of mixtures mixtures = ListProperty( default=[], text="Mixture", data_type=Mixture, visible=False, persistent=True ) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): """ Constructor takes any of its properties as a keyword argument except for: - needs_saving In addition to the above, the constructor still supports the following deprecated keywords, mapping to a current keyword: - goniometer: the project-level goniometer, is passed on to the specimens - axes_xscale: deprecated alias for axes_xlimit - axes_yscale: deprecated alias for axes_ynormalize Any other arguments or keywords are passed to the base class. """ my_kwargs = self.pop_kwargs(kwargs, "goniometer", "data_goniometer", "data_atom_types", "data_phases", "axes_yscale", "axes_xscale", "filename", "behaviours", *[prop.label for prop in Project.Meta.get_local_persistent_properties()] ) super(Project, self).__init__(*args, **kwargs) kwargs = my_kwargs with self.data_changed.hold(): with self.visuals_changed.hold(): self.filename = self.get_kwarg(kwargs, self.filename, "filename") self.layout_mode = self.get_kwarg(kwargs, self.layout_mode, "layout_mode") self.display_marker_align = self.get_kwarg(kwargs, self.display_marker_align, "display_marker_align") self.display_marker_color = self.get_kwarg(kwargs, self.display_marker_color, "display_marker_color") self.display_marker_base = self.get_kwarg(kwargs, self.display_marker_base, "display_marker_base") self.display_marker_top = self.get_kwarg(kwargs, self.display_marker_top, "display_marker_top") self.display_marker_top_offset = self.get_kwarg(kwargs, self.display_marker_top_offset, "display_marker_top_offset") self.display_marker_angle = self.get_kwarg(kwargs, self.display_marker_angle, "display_marker_angle") self.display_marker_style = self.get_kwarg(kwargs, self.display_marker_style, "display_marker_style") self.display_calc_color = self.get_kwarg(kwargs, self.display_calc_color, "display_calc_color") self.display_exp_color = self.get_kwarg(kwargs, self.display_exp_color, "display_exp_color") self.display_calc_lw = self.get_kwarg(kwargs, self.display_calc_lw, "display_calc_lw") self.display_exp_lw = self.get_kwarg(kwargs, self.display_exp_lw, "display_exp_lw") self.display_calc_ls = self.get_kwarg(kwargs, self.display_calc_ls, "display_calc_ls") self.display_exp_ls = self.get_kwarg(kwargs, self.display_exp_ls, "display_exp_ls") self.display_calc_marker = self.get_kwarg(kwargs, self.display_calc_marker, "display_calc_marker") self.display_exp_marker = self.get_kwarg(kwargs, self.display_exp_marker, "display_exp_marker") self.display_plot_offset = self.get_kwarg(kwargs, self.display_plot_offset, "display_plot_offset") self.display_group_by = self.get_kwarg(kwargs, self.display_group_by, "display_group_by") self.display_label_pos = self.get_kwarg(kwargs, self.display_label_pos, "display_label_pos") self.axes_xlimit = self.get_kwarg(kwargs, self.axes_xlimit, "axes_xlimit", "axes_xscale") self.axes_xmin = self.get_kwarg(kwargs, self.axes_xmin, "axes_xmin") self.axes_xmax = self.get_kwarg(kwargs, self.axes_xmax, "axes_xmax") self.axes_xstretch = self.get_kwarg(kwargs, self.axes_xstretch, "axes_xstretch") self.axes_ylimit = self.get_kwarg(kwargs, self.axes_ylimit, "axes_ylimit") self.axes_ynormalize = self.get_kwarg(kwargs, self.axes_ynormalize, "axes_ynormalize", "axes_yscale") self.axes_yvisible = self.get_kwarg(kwargs, self.axes_yvisible, "axes_yvisible") self.axes_ymin = self.get_kwarg(kwargs, self.axes_ymin, "axes_ymin") self.axes_ymax = self.get_kwarg(kwargs, self.axes_ymax, "axes_ymax") goniometer = None goniometer_kwargs = self.get_kwarg(kwargs, None, "goniometer", "data_goniometer") if goniometer_kwargs: goniometer = self.parse_init_arg(goniometer_kwargs, None, child=True) # Set up and observe atom types: self.atom_types = self.get_list(kwargs, [], "atom_types", "data_atom_types", parent=self) self._atom_types_observer = ListObserver( self.on_atom_type_inserted, self.on_atom_type_removed, prop_name="atom_types", model=self ) # Resolve json references & observe phases self.phases = self.get_list(kwargs, [], "phases", "data_phases", parent=self) for phase in self.phases: phase.resolve_json_references() self.observe_model(phase) self._phases_observer = ListObserver( self.on_phase_inserted, self.on_phase_removed, prop_name="phases", model=self ) # Set goniometer if required & observe specimens self.specimens = self.get_list(kwargs, [], "specimens", "data_specimens", parent=self) for specimen in self.specimens: if goniometer: specimen.goniometer = goniometer self.observe_model(specimen) self._specimens_observer = ListObserver( self.on_specimen_inserted, self.on_specimen_removed, prop_name="specimens", model=self ) # Observe behaviours: #self.behaviours = self.get_list(kwargs, [], "behaviours", parent=self) #for behaviour in self.behaviours: # self.observe_model(behaviour) #self._behaviours_observer = ListObserver( # self.on_behaviour_inserted, # self.on_behaviour_removed, # prop_name="behaviours", # model=self #) # Observe mixtures: self.mixtures = self.get_list(kwargs, [], "mixtures", "data_mixtures", parent=self) for mixture in self.mixtures: self.observe_model(mixture) self._mixtures_observer = ListObserver( self.on_mixture_inserted, self.on_mixture_removed, prop_name="mixtures", model=self ) self.name = str(self.get_kwarg(kwargs, "Project name", "name", "data_name")) self.date = str(self.get_kwarg(kwargs, time.strftime("%d/%m/%Y"), "date", "data_date")) self.description = str(self.get_kwarg(kwargs, "Project description", "description", "data_description")) self.author = str(self.get_kwarg(kwargs, "Project author", "author", "data_author")) load_default_data = self.get_kwarg(kwargs, True, "load_default_data") if load_default_data and self.layout_mode != 1 and \ len(self.atom_types) == 0: self.load_default_data() self.needs_saving = True pass # end with visuals_changed pass # end with data_changed def load_default_data(self): for atom_type in AtomType.get_from_csv(settings.DATA_REG.get_file_path("ATOM_SCAT_FACTORS")): self.atom_types.append(atom_type) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ def on_phase_inserted(self, item): # Set parent on the new phase: if item.parent != self: item.parent = self item.resolve_json_references() def on_phase_removed(self, item): with self.data_changed.hold_and_emit(): # Clear parent: item.parent = None # Clear links with other phases: if getattr(item, "based_on", None) is not None: item.based_on = None for phase in self.phases: if getattr(phase, "based_on", None) == item: phase.based_on = None # Remove phase from mixtures: for mixture in self.mixtures: mixture.unset_phase(item) def on_atom_type_inserted(self, item, *data): if item.parent != self: item.parent = self # We do not observe AtomType's directly, if they change, # Atoms containing them will be notified, and that event should bubble # up to the project level. def on_atom_type_removed(self, item, *data): item.parent = None # We do not emit a signal for AtomType's, if it was part of # an Atom, the Atom will be notified, and the event should bubble # up to the project level def on_specimen_inserted(self, item): # Set parent and observe the new specimen (visuals changed signals): if item.parent != self: item.parent = self self.observe_model(item) def on_specimen_removed(self, item): with self.data_changed.hold_and_emit(): # Clear parent & stop observing: item.parent = None self.relieve_model(item) # Remove specimen from mixtures: for mixture in self.mixtures: mixture.unset_specimen(item) def on_mixture_inserted(self, item): # Set parent and observe the new mixture: if item.parent != self: item.parent = self self.observe_model(item) def on_mixture_removed(self, item): with self.data_changed.hold_and_emit(): # Clear parent & stop observing: item.parent = None self.relieve_model(item) def on_behaviour_inserted(self, item): # Set parent and observe the new mixture: if item.parent != self: item.parent = self self.observe_model(item) def on_behaviour_removed(self, item): with self.data_changed.hold_and_emit(): # Clear parent & stop observing: item.parent = None self.relieve_model(item) @DataModel.observe("data_changed", signal=True) def notify_data_changed(self, model, prop_name, info): self.needs_saving = True if isinstance(model, Mixture): self.data_changed.emit() @DataModel.observe("visuals_changed", signal=True) def notify_visuals_changed(self, model, prop_name, info): self.needs_saving = True self.visuals_changed.emit() # propagate signal # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ @classmethod def from_json(type, **kwargs): # @ReservedAssignment project = type(**kwargs) project.needs_saving = False # don't mark this when just loaded return project def to_json_multi_part(self): to_json = self.to_json() properties = to_json["properties"] for name in ("phases", "specimens", "atom_types", "mixtures"): #"behaviours" yield (name, properties.pop(name)) properties[name] = "file://%s" % name yield ("content", to_json) yield ("version", __version__) @staticmethod def create_from_sybilla_xml(filename, **kwargs): from pyxrd.project.importing import create_project_from_sybilla_xml return create_project_from_sybilla_xml(filename, **kwargs) # ------------------------------------------------------------ # Draggable mix-in hook: # ------------------------------------------------------------ def on_label_dragged(self, delta_y, button=1): if button == 1: self.display_label_pos += delta_y pass # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def get_scale_factor(self, specimen=None): """ Get the factor with which to scale raw data and the scaled offset :rtype: tuple containing the scale factor and the (scaled) offset """ if self.axes_ynormalize == 0 or (self.axes_ynormalize == 1 and specimen is None): return (1.0 / (self.get_max_display_y() or 1.0), 1.0) elif self.axes_ynormalize == 1: return (1.0 / (specimen.get_max_display_y or 1.0), 1.0) elif self.axes_ynormalize == 2: return (1.0, self.get_max_display_y()) else: raise ValueError("Wrong value for 'axes_ysnormalize' in %s: is `%d`; should be 0, 1 or 2" % (self, self.axes_ynormalize)) def get_max_display_y(self): max_display_y = 0 if self.parent is not None: for specimen in self.parent.current_specimens: max_display_y = max(specimen.max_display_y, max_display_y) return max_display_y @contextmanager def hold_child_signals(self): logger.info("Holding back all project child object signals") with self.hold_mixtures_needs_update(): with self.hold_mixtures_data_changed(): with self.hold_phases_data_changed(): with self.hold_specimens_data_changed(): with self.hold_atom_types_data_changed(): yield @contextmanager def hold_mixtures_needs_update(self): logger.info("Holding back all 'needs_update' signals from Mixtures") with EventContextManager(*[mixture.needs_update.hold() for mixture in self.mixtures]): yield @contextmanager def hold_mixtures_data_changed(self): logger.info("Holding back all 'data_changed' signals from Mixtures") with EventContextManager(*[mixture.data_changed.hold() for mixture in self.mixtures]): yield @contextmanager def hold_phases_data_changed(self): logger.info("Holding back all 'data_changed' signals from Phases") with EventContextManager(*[phase.data_changed.hold() for phase in self.phases]): yield @contextmanager def hold_atom_types_data_changed(self): logger.info("Holding back all 'data_changed' signals from AtomTypes") with EventContextManager(*[atom_type.data_changed.hold() for atom_type in self.atom_types]): yield @contextmanager def hold_specimens_data_changed(self): logger.info("Holding back all 'data_changed' signals from Specimens") with EventContextManager(*[specimen.data_changed.hold() for specimen in self.specimens]): yield def update_all_mixtures(self): """ Forces all mixtures in this project to update. If they have auto optimization enabled, this will also optimize them. """ for mixture in self.mixtures: with self.data_changed.ignore(): mixture.update() def get_mixtures_by_name(self, mixture_name): """ Convenience method that returns all the mixtures who's name match the passed name as a list. """ return [mixture for mixture in self.mixtures if (mixture.name == mixture_name)] # ------------------------------------------------------------ # Specimen list related # ------------------------------------------------------------ def move_specimen_up(self, specimen): """ Move the passed :class:`~pyxrd.specimen.models.Specimen` up one slot. Will raise and IndexError if the passed specimen is not in this project. """ index = self.specimens.index(specimen) self.specimens.insert(min(index + 1, len(self.specimens)), self.specimens.pop(index)) def move_specimen_down(self, specimen): """ Move the passed :class:`~pyxrd.specimen.models.Specimen` down one slot Will raise and IndexError if the passed specimen is not in this project. """ index = self.specimens.index(specimen) self.specimens.insert(max(index - 1, 0), self.specimens.pop(index)) pass # ------------------------------------------------------------ # Phases list related # ------------------------------------------------------------ def load_phases(self, filename, parser, insert_index=0): """ Loads all :class:`~pyxrd.phase.models.Phase` objects from the file 'filename'. An optional index can be given where the phases need to be inserted at. """ # make sure we have no duplicate UUID's insert_index = not_none(insert_index, 0) type(Project).object_pool.change_all_uuids() for phase in parser.parse(filename): phase.parent = self self.phases.insert(insert_index, phase) insert_index += 1 # ------------------------------------------------------------ # AtomType's list related # ------------------------------------------------------------ def load_atom_types(self, filename, parser): """ Loads all :class:`~pyxrd.atoms.models.AtomType` objects from the file specified by *filename*. """ # make sure we have no duplicate UUID's type(Project).object_pool.change_all_uuids() for atom_type in parser.parse(filename): atom_type.parent = self self.atom_types.append(atom_type) pass # end of class PyXRD-0.8.4/pyxrd/project/views.py000066400000000000000000000043431363064711000170210ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport from pyxrd.generic.views import DialogView class ProjectView(DialogView): title = "Edit Project" subview_builder = resource_filename(__name__, "glade/project.glade") subview_toplevel = "nbk_edit_project" resizable = False widget_format = "project_%s" widget_groups = { 'full_mode_only': [ "algn_calc_color", "lbl_calccolor", "algn_calc_lw", "calc_lw_lbl" ] } @property def specimens_treeview_container(self): return self["vbox_specimens"] @property def specimens_treeview(self): return self["project_specimens"] def __init__(self, *args, **kwargs): DialogView.__init__(self, *args, **kwargs) self["popup_menu_item_add_specimen"].set_related_action(self.parent["add_specimen"]) self["popup_menu_item_edit_specimen"].set_related_action(self.parent["edit_specimen"]) self["popup_menu_item_import_specimens"].set_related_action(self.parent["import_specimens"]) self["popup_menu_item_replace_data"].set_related_action(self.parent["replace_specimen_data"]) self["popup_menu_item_export_data"].set_related_action(self.parent["export_specimen_data"]) self["popup_menu_item_del_specimen"].set_related_action(self.parent["del_specimen"]) def present(self, *args, **kwargs): super(ProjectView, self).present(*args, **kwargs) self["nbk_edit_project"].set_current_page(0) def show_specimens_context_menu(self, event): self["specimen_popup"].popup(None, None, None, None, event.button, event.time) def hide_specimens_context_menu(self): self["specimen_popup"].hide() def set_x_range_sensitive(self, sensitive): self["box_manual_xrange"].set_sensitive(sensitive) def set_y_range_sensitive(self, sensitive): self["box_manual_yrange"].set_sensitive(sensitive) def set_selection_state(self, value): if value is None: self.hide_specimens_context_menu() pass pass # end of class PyXRD-0.8.4/pyxrd/refinement/000077500000000000000000000000001363064711000157745ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/refinement/__init__.py000066400000000000000000000000001363064711000200730ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/refinement/async_evaluatable.py000066400000000000000000000035741363064711000220410ustar00rootroot00000000000000 # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import functools from pyxrd.generic.asynchronous.cancellable import Cancellable from pyxrd.generic.asynchronous.has_async_calls import HasAsyncCalls class AsyncEvaluatable(HasAsyncCalls, Cancellable): # self.refiner.get_pickled_data_object_for_solution def do_async_evaluation(self, iter_func, eval_func, data_func, result_func): """ Utility that combines a submit and fetch cycle in a single function call. iter_func is a generation callback (generates solutions) data_func transforms the given solutions to something eval_func can work with (this can be a pass-through operation) eval_func evaluates a single (generated) solution (this must be picklable) result_func receives each solution and its residual as arguments """ assert callable(iter_func) assert callable(eval_func) assert callable(data_func) assert callable(result_func) results = [] solutions = [] for solution in iter_func(): result = self.submit_async_call(functools.partial( eval_func, data_func(solution) )) solutions.append(solution) results.append(result) if self._user_cancelled(): # Stop submitting new individuals break for solution, result in zip(solutions, map(self.fetch_async_result, results)): result_func(solution, result) del results # Run the garbage collector once for good measure import gc gc.collect() return solutionsPyXRD-0.8.4/pyxrd/refinement/controllers/000077500000000000000000000000001363064711000203425ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/refinement/controllers/__init__.py000066400000000000000000000000001363064711000224410ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/refinement/controllers/refinement_controller.py000066400000000000000000000270221363064711000253160ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk import sys from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory from mvc.support.cancellable_thread import CancellableThread from mvc.support.gui_loop import run_when_idle, add_timeout_call,\ remove_timeout_call from pyxrd.generic.asynchronous.providers import get_status from pyxrd.generic.views.treeview_tools import new_text_column, new_pb_column, new_toggle_column from pyxrd.generic.mathtext_support import create_pb_from_mathtext from pyxrd.generic.controllers import DialogController from pyxrd.refinement.views.refiner_view import RefinerView from pyxrd.refinement.controllers.refiner_controller import RefinerController class RefinementController(DialogController): auto_adapt_included = [ "refine_method_index", "refinables", "make_psp_plots", ] @property def treemodel(self): return self.model.refinables def setup_refinables_tree_view(self, store, widget): """ Setup refinables TreeView layout """ widget.set_show_expanders(True) if sys.platform == "win32": def get_label(column, cell, model, itr, user_data=None): ref_prop = model.get_tree_node_object(itr) cell.set_property("text", ref_prop.text_title) return widget.append_column(new_text_column('Name/Prop', xalign=0.0, data_func=get_label)) else: # Labels are parsed for mathtext markup into pb's: def get_pb(column, cell, model, itr, user_data=None): ref_prop = model.get_tree_node_object(itr) try: if not hasattr(ref_prop, "pb") or not ref_prop.pb: ref_prop.pb = create_pb_from_mathtext( ref_prop.title, align='left', weight='medium' ) cell.set_property("pixbuf", ref_prop.pb) except RuntimeError: logger.warning("An error occured when trying to convert a property title to a PixBuf") raise return widget.append_column(new_pb_column('Name/Prop', xalign=0.0, data_func=get_pb)) # Editable floats: def get_value(column, cell, model, itr, *args): col = column.get_col_attr('markup') try: value = model.get_value(itr, col) value = "%.5f" % value except TypeError: value = "" cell.set_property("markup", value) return def on_float_edited(rend, path, new_text, model, col): itr = model.get_iter(path) try: model.set_value(itr, col, float(new_text)) except ValueError: return False return True def_float_args = { "sensitive_col": store.c_refinable, "editable_col": store.c_refinable, "visible_col": store.c_refinable, "data_func": get_value } widget.append_column(new_text_column( "Value", markup_col=store.c_value, edited_callback=( on_float_edited, (store, store.c_value,) ), **def_float_args )) widget.append_column(new_text_column( "Min", markup_col=store.c_value_min, edited_callback=( on_float_edited, (store, store.c_value_min,) ), **def_float_args )) widget.append_column(new_text_column( "Max", markup_col=store.c_value_max, edited_callback=( on_float_edited, (store, store.c_value_max,) ), **def_float_args )) # The 'refine' checkbox: widget.append_column(new_toggle_column( "Refine", toggled_callback=(self.refine_toggled, (store,)), resizable=False, expand=False, active_col=store.c_refine, sensitive_col=store.c_refinable, activatable_col=store.c_refinable, visible_col=store.c_refinable )) def _update_method_options_store(self): """ Update the method options tree store (when a new method is selected) """ tv = self.view['tv_method_options'] store = Gtk.ListStore(str, str) method = self.model.get_refinement_method() for arg in method.options: description = getattr(type(method), arg).description store.append([arg, description]) tv.set_model(store) return tv def _setup_method_options_treeview(self): """ Initial method options tree view layout & behavior setup """ # Update the method options store to match the currently selected # refinement method tv = self._update_method_options_store() # The name of the option: tv.append_column(new_text_column("Name", text_col=1)) # The value of the option: def get_value(column, cell, model, itr, *args): option_name, = tv.get_model().get(itr, 0) method = self.model.get_refinement_method() cell.set_property("sensitive", True) cell.set_property("editable", True) cell.set_property("markup", "%g" % getattr(method, option_name)) return def on_value_edited(rend, path, new_text, col): store = tv.get_model() itr = store.get_iter(path) option_name, = store.get(itr, 0) method = self.model.get_refinement_method() try: setattr(method, option_name, new_text) except ValueError: pass return True tv.append_column(new_text_column( "Value", text_col=0, data_func=get_value, edited_callback=(on_value_edited, (0,)), )) def register_view(self, view): # Create the method treeview: self._setup_method_options_treeview() # Update the server status: self.view.update_server_status(get_status()) def cleanup(self): if hasattr(self, "view"): del self.view if hasattr(self, "results_view"): del self.results_view if hasattr(self, "results_controller"): del self.results_controller if hasattr(self, "model"): self.relieve_model(self.model) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @DialogController.observe("refine_method_index", assign=True) def on_prop_changed(self, model, prop_name, info): self._update_method_options_store() # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_cancel(self): if self.view is not None: self.view.hide() self.parent.view.parent.show() def refine_toggled(self, cell, path, model): if model is not None: itr = model.get_iter(path) model.set_value(itr, model.c_refine, not cell.get_active()) return True def on_btn_randomize_clicked(self, event): self.model.randomize() def on_auto_restrict_clicked(self, event): self.model.auto_restrict() def _launch_gui_updater(self, refiner): def _on_update_gui(): if self.view is not None and refiner is not None: self.view.update_refinement_info( refiner.history.last_residual, refiner.status.message, get_status() ) return True else: return False add_timeout_call(500, _on_update_gui) return _on_update_gui def _launch_refine_thread(self, refiner, gui_timeout_id): @run_when_idle def thread_completed(*args, **kwargs): """ Called when the refinement is completed """ self.thread = None remove_timeout_call(gui_timeout_id) self.view.stop_spinner() # Make some plots: if self.model.make_psp_plots: self.view.update_refinement_status("Processing...") self.results_controller.generate_images() # Set the labels: self.results_controller.update_labels() # Hide our shit: self.view.hide_refinement_info() self.view.hide() # Show results: self.results_view.present() thread = CancellableThread(refiner.refine, thread_completed) thread.start() return thread def _connect_cancel_button(self, refiner, gui_timeout_id, thread): # Connect the cancel button (custom widget): def thread_cancelled(*args, **kwargs): """ Called when the refinement is cancelled by the user """ remove_timeout_call(gui_timeout_id) self.view.stop_spinner() self.view.update_refinement_status("Cancelling...") thread.cancel() self.view.hide_refinement_info() self.view.connect_cancel_request(thread_cancelled) @DialogController.status_message("Refining mixture...", "refine_mixture") def on_refine_clicked(self, event): with self.model.mixture.needs_update.hold(): with self.model.mixture.data_changed.hold(): if len(self.model.mixture.specimens) > 0: # Create the refiner object with DialogFactory.error_dialog_handler( "There was an error when creating the refinement setup:\n{}", parent=self.view.get_toplevel(), reraise=False): refiner = self.model.get_refiner() # Setup results controller self.results_view = RefinerView(parent=self.view.parent) self.results_controller = RefinerController( refiner=refiner, model=self.model, view=self.results_view, parent=self ) # Gtk timeout loop for our GUI updating: gui_timeout_id = self._launch_gui_updater(refiner) # This creates a thread that will run the refiner.refine method: thread = self._launch_refine_thread(refiner, gui_timeout_id) # Connect the cancel button: self._connect_cancel_button(refiner, gui_timeout_id, thread) # Show the context updates in the gui: self.view.show_refinement_info() self.view.start_spinner() else: DialogFactory.get_information_dialog( "Cannot refine an empty mixture!", parent=self.view.get_toplevel() ).run() pass # end of classPyXRD-0.8.4/pyxrd/refinement/controllers/refiner_controller.py000066400000000000000000000052401363064711000246120ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pyxrd.generic.controllers.dialog_controller import DialogController from pyxrd.refinement.parspace import ParameterSpaceGenerator class RefinerController(DialogController): """ A controller for a Refiner object that keeps track of the solutions and residuals generated by the refinement algorithm. This allows to show a nice dialog with the end results and some graphs about the parameter space. """ auto_adapt_excluded = [ "refine_method_index", "refinables", "make_psp_plots", ] register_lazy = False samples = None def __init__(self, refiner, *args, **kwargs): super(RefinerController, self).__init__(*args, **kwargs) self.refiner = refiner # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_cancel(self): self.view.hide() self.parent.parent.view.parent.show() def on_btn_initial_clicked(self, event): self.refiner.apply_initial_solution() del self.refiner self.on_cancel() return True def on_btn_best_clicked(self, event): self.refiner.apply_best_solution() del self.refiner self.on_cancel() return True def on_btn_last_clicked(self, event): self.refiner.apply_last_solution() del self.refiner self.on_cancel() return True # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def update_labels(self): self.view.update_labels( self.refiner.history.initial_residual, self.refiner.history.best_residual, self.refiner.history.last_residual, ) def generate_images(self, output_dir="", density=200): """ Generate the parameter space plots """ samples = self.refiner.get_plot_samples() labels = self.refiner.get_plot_labels() truths = self.refiner.history.best_solution psg = ParameterSpaceGenerator() psg.initialize(self.refiner.ranges, 199) for sample in samples: psg.record(sample[:-1], sample[-1]) psg.plot_images(self.view.figure, truths, labels[:-1]) def clear_images(self): self.view.figure.clear() self.view.figure.text(0.5, 0.5, "no plots to show", va="center", ha="center") pass # end of classPyXRD-0.8.4/pyxrd/refinement/methods/000077500000000000000000000000001363064711000174375ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/refinement/methods/__init__.py000066400000000000000000000031161363064711000215510ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import traceback import sys import importlib, pkgutil from imp import find_module import logging logger = logging.getLogger(__name__) """ This scans the module for submodules and imports them. This will trigger the registration of any refinement method classes (i.e. RefineRun subclasses). Every RefineRun class is callable. When calling a RefineRun sub-class, you should pass the RefineContext as the first argument, a stop signal, and an optional dict of options (see the class definitions for what options you can use). Internally, this will set-up the class and then call its own `run()` method, starting the refinement. As such, to an external user, these 'classes' appear as simple functions. """ def import_submodules(package_name): """ Import all submodules of a module, recursively :param package_name: Package name :type package_name: str :rtype: dict[types.ModuleType] """ package = sys.modules[package_name] modules = {} for _, name, _ in pkgutil.walk_packages(package.__path__): try: modules[name] = importlib.import_module(package_name + '.' + name) except: logger.warning("Could not import %s refinement method modules, are all dependencies installed? Error was:" % name) _, _, tb = sys.exc_info() traceback.print_tb(tb) return modules __all__ = list(import_submodules(__name__).keys()) PyXRD-0.8.4/pyxrd/refinement/methods/custom_brute.py000066400000000000000000000041511363064711000225250ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from itertools import product, combinations import numpy as np from ..refine_method import RefineMethod from ..refine_method_option import RefineMethodOption class RefineBruteForceRun(RefineMethod): name = "Brute force algorithm" description = "Refinement using a Brute Force algorithm" index = 3 disabled = False num_samples = RefineMethodOption('Number of samples', 11, [3, 1000], int) def run(self, refiner, num_samples=11, stop=None, **kwargs): """ Refinement using a Brute Force algorithm """ self.refiner = refiner num_params = len(refiner.ranges) npbounds = np.array(refiner.ranges, dtype=float) npmins = npbounds[:, 0] npranges = npbounds[:, 1] - npbounds[:, 0] def generate(): # Generates the solutions for async evaluation if num_params == 1: for index in range(num_samples): npindex = np.array([index / float(num_samples - 1)], dtype=float) solution = npmins + npranges * npindex yield solution else: # Generate a grid for each possible combination of parameters: for par1, par2 in combinations(list(range(num_params)), 2): # produce the grid indices for those parameters # keep the others half-way their range: indeces = np.ones(shape=(num_params,), dtype=float) * 0.5 for par_indeces in product(list(range(num_samples)), repeat=2): indeces[par1] = par_indeces[0] / float(num_samples - 1) indeces[par2] = par_indeces[1] / float(num_samples - 1) # Make the solution: solution = npmins + npranges * indeces yield solution self.do_async_evaluation(generate) refiner.apply_initial_solution() pass #end of class PyXRD-0.8.4/pyxrd/refinement/methods/deap_cma.py000066400000000000000000000410521363064711000215440ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) from math import sqrt import numpy as np import scipy from deap import cma, base, creator, tools #@UnresolvedImport from pyxrd.refinement.refine_method import RefineMethod from pyxrd.refinement.refine_method_option import RefineMethodOption from pyxrd.refinement.refine_async_helper import RefineAsyncHelper from .deap_utils import pyxrd_array, PyXRDParetoFront, FitnessMin, result_func # Default settings: NGEN = 100 STAGN_NGEN = 10 STAGN_TOL = 0.001 class Strategy(cma.Strategy): """ This evolutionary strategy supports the hybrid PSO-CMA runs using the rotate_and_bias function (should be called after an update). """ def __init__(self, centroid, sigma, ranges, **kwargs): self.ranges = ranges super(Strategy, self).__init__(centroid, sigma, **kwargs) def update(self, population): """Update the current covariance matrix strategy from the *population*. :param population: A list of individuals from which to update the parameters. """ population.sort(key=lambda ind: ind.fitness, reverse=True) selected_pop = self._translate_external( np.array([ind.to_ndarray() for ind in population[0:self.mu]])) old_centroid = self._translate_external(self.centroid) centroid = np.dot(self.weights, selected_pop) c_diff = centroid - old_centroid # Cumulation : update evolution path self.ps = (1 - self.cs) * self.ps \ + sqrt(self.cs * (2 - self.cs) * self.mueff) / self.sigma \ * np.dot(self.B, (1. / self.diagD) \ * np.dot(self.B.T, c_diff)) hsig = float((np.linalg.norm(self.ps) / sqrt(1. - (1. - self.cs) ** (2. * (self.update_count + 1.))) / self.chiN < (1.4 + 2. / (self.dim + 1.)))) self.update_count += 1 self.pc = (1 - self.cc) * self.pc + hsig \ * sqrt(self.cc * (2 - self.cc) * self.mueff) / self.sigma \ * c_diff # Update covariance matrix artmp = selected_pop - old_centroid new_C = (1 - self.ccov1 - self.ccovmu + (1 - hsig) \ * self.ccov1 * self.cc * (2 - self.cc)) * self.C \ + self.ccov1 * np.outer(self.pc, self.pc) \ + self.ccovmu * np.dot((self.weights * artmp.T), artmp) \ / self.sigma ** 2 self.sigma *= np.exp((np.linalg.norm(self.ps) / self.chiN - 1.) \ * self.cs / self.damps) try: self.diagD, self.B = np.linalg.eigh(new_C) except np.linalg.LinAlgError: logger.warning( "LinAlgError occurred when calculating eigenvalues" \ " and vectors for matrix C!\n%r" % new_C ) else: self.C = new_C indx = np.argsort(self.diagD) self.cond = self.diagD[indx[-1]] / self.diagD[indx[0]] self.diagD = self.diagD ** 0.5 self.B = self.B[:, indx] self.BD = self.B * self.diagD self.centroid = self._translate_internal(centroid) def rotate_and_bias(self, global_best, tc=0.1, b=0.5, cp=0.5): """ Rotates the covariance matrix and biases the centroid of this CMA population towards a global mean. Can be used to implement a PSO-CMA hybrid algorithm. """ global_best = self._translate_external(global_best) centroid = self._translate_external(self.centroid) # Rotate towards global: pg = np.array(global_best) - np.array(centroid) Brot = self.__rotation_matrix(self.B[:, 0], pg) * self.B Crot = Brot * (self.diagD ** 2) * Brot.T self.C = cp * self.C + (1.0 - cp) * Crot # Bias our mean towards global best mean: npg = np.linalg.norm(pg) nsigma = np.amax(self.sigma) if nsigma < npg: if nsigma / npg <= tc * npg: bias = b * pg else: bias = nsigma / npg * pg else: bias = 0 centroid = centroid + bias self.centroid = self._translate_internal(centroid) pass def _translate_internal(self, solutions): # rule is: anything given as an argument in a public function or # available as a public property should be within the external boundaries return self.ranges[:, 0] + (self.ranges[:, 1] - self.ranges[:, 0]) * (1.0 - np.cos(solutions * np.pi)) / 2.0 def _translate_external(self, solutions): return np.arccos(1 - 2 * (solutions - self.ranges[:, 0]) / (self.ranges[:, 1] - self.ranges[:, 0])) / np.pi def generate(self, ind_init): """Generate a population from the current strategy using the centroid individual as parent. :param ind_init: A function object that is able to initialize an individual from a list. :returns: an iterator yielding the generated individuals. """ centroid = self._translate_external(self.centroid) arz = np.random.standard_normal((self.lambda_, self.dim)) #@UndefinedVariable arz = np.array(centroid) + self.sigma * np.dot(arz, self.BD.T) #@UndefinedVariable arz = self._translate_internal(arz) for arr in arz: yield ind_init(arr) def __rotation_matrix(self, vector, target): """ Rotation matrix from one vector to another target vector. The solution is not unique as any additional rotation perpendicular to the target vector will also yield a solution) However, the output is deterministic. """ R1 = self.__rotation_to_pole(target) R2 = self.__rotation_to_pole(vector) return np.dot(R1.T, R2) def __rotation_to_pole(self, target): """ Rotate to 1,0,0... """ n = len(target) working = target rm = np.eye(n) for i in range(1, n): angle = np.arctan2(working[0], working[i]) rm = np.dot(self.__rotation_matrix_inds(angle, n, 0, i), rm) working = np.dot(rm, target) return rm def __rotation_matrix_inds(self, angle, n, ax1, ax2): """ 'n'-dimensional rotation matrix 'angle' radians in coordinate plane with indices 'ax1' and 'ax2' """ s = np.sin(angle) c = np.cos(angle) i = np.eye(n) i[ax1, ax1] = s i[ax1, ax2] = c i[ax2, ax1] = c i[ax2, ax2] = -s return i pass #end of class class Algorithm(RefineAsyncHelper): """ This algorithm implements the ask-tell model proposed in [Colette2010]_, where ask is called `generate` and tell is called `update`. Modified (Mathijs Dumon) so it checks for stagnation. """ @property def ngen(self): return self._ngen @ngen.setter def ngen(self, value): self._ngen = value logger.info("Setting ngen to %d" % value) _ngen = 100 gen = -1 halloffame = None refiner = None toolbox = None stats = None stagn_ngen = None stagn_tol = None verbose = False #-------------------------------------------------------------------------- # Initialization #-------------------------------------------------------------------------- def __init__(self, toolbox, halloffame, stats, ngen=NGEN, verbose=__debug__, stagn_ngen=STAGN_NGEN, stagn_tol=STAGN_TOL, refiner=None, stop=None): """ :param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution operators. :param ngen: The number of generations. :param halloffame: A :class:`~deap.tools.ParetoFront` object that will contain the best individuals. :param stats: A :class:`~deap.tools.Statistics` object that is updated inplace. :param verbose: Whether or not to log the statistics. :param stagn_gens: The minimum number of generations to wait before checking for stagnation :param stagn_tol: The stagnation tolerance. Higher values means a harsher tolerance, values should fall between 0 and 1 :param refiner: PyXRD refiner object :returns: The best individual and the final population. The toolbox should contain a reference to the generate and the update method of the chosen strategy. Call the run() method when the algorithm should be run. .. [Colette2010] Collette, Y., N. Hansen, G. Pujol, D. Salazar Aponte and R. Le Riche (2010). On Object-Oriented Programming of Optimizers - Examples in Scilab. In P. Breitkopf and R. F. Coelho, eds.: Multidisciplinary Design Optimization in Computational Mechanics, Wiley, pp. 527-565; """ self.stats = stats self.toolbox = toolbox self.ngen = ngen self.halloffame = halloffame self.verbose = verbose self.stagn_ngen = stagn_ngen self.stagn_tol = stagn_tol self.refiner = refiner self.gen = 0 self._stop = stop #-------------------------------------------------------------------------- # Run method: #-------------------------------------------------------------------------- def run(self): """Will run this algorithm""" if self.verbose: column_names = ["gen", "evals", "best"] if self.stats is not None: column_names += list(self.stats.functions.keys()) self.logbook = tools.Logbook() self.logbook.header = column_names for _ in range(self.ngen): # Check if the user has cancelled: if self._user_cancelled(): self.refiner.status.message = "Stopping..." logger.info("User cancelled execution, stopping ...") break #ASK: Generate a new population: population = self._ask() #TELL: Update the strategy with the evaluated individuals self._tell(population) #RECORD: For stagnation checking & logging: self._record(population) #CHECK: whether we are stagnating: if self._is_stagnating(): logging.info("CMA: stagnation detected!") break return self.refiner.history.best_solution, population #-------------------------------------------------------------------------- # Stagnation calls: #-------------------------------------------------------------------------- def _is_flat(self, yvals, xvals, slope_tolerance=0.001): slope, intercept, r_value, p_value, std_err = scipy.stats.linregress(xvals, yvals) #@UndefinedVariable @UnusedVariable val = bool(abs(slope) <= slope_tolerance) return val def _is_stagnating(self): self.refiner.status.message = "Checking for stagnation" if self.gen >= self.stagn_ngen: # 10 std, best = self.logbook.select("std", "best") std = np.array(std)[:, 0] yvals1 = std[-(self.stagn_ngen - 1):] xvals1 = list(range(len(yvals1))) yvals2 = best[-(self.stagn_ngen - 1):] xvals2 = list(range(len(yvals2))) return self._is_flat(yvals1, xvals1, self.stagn_tol) and \ self._is_flat(yvals2, xvals2, self.stagn_tol) else: return False #-------------------------------------------------------------------------- # Ask, tell & record: #-------------------------------------------------------------------------- def _ask(self): self.gen += 1 self.refiner.status.message = "Creating generation #%d" % self.gen def result_f(*args): self.refiner.update(*args) result_func(*args) population = self.do_async_evaluation( self.toolbox.generate, result_func=result_f ) if self.halloffame is not None: self.halloffame.update(population) return population def _tell(self, population): self.refiner.status.message = "Updating strategy" self.toolbox.update(population) def _record(self, population): self.refiner.status.message = "Processing ..." # Get the best solution so far: best = self.halloffame.get_best() best_f = best.fitness.values[0] pop_size = len(population) # Calculate stats & print something if needed: record = self.stats.compile(population) if self.verbose: self.logbook.record(gen=self.gen, evals=pop_size, best=best_f, **record) print(self.logbook.stream) self.refiner.status.message = "Refiner update ..." # Update the refiner: self.refiner.update(best, iteration=self.gen, residual=best_f) pass #end of class class RefineCMAESRun(RefineMethod): """ The DEAP CMA-ES algorithm implementation with added stagnation thresholds """ name = "CMA-ES refinement" description = "This algorithm uses the CMA-ES refinement strategy as implemented by DEAP" index = 1 disabled = False ngen = RefineMethodOption('Maximum # of generations', NGEN, [1, 10000], int) stagn_ngen = RefineMethodOption('Minimum # of generations', STAGN_NGEN, [1, 10000], int) stagn_tol = RefineMethodOption('Fitness slope tolerance', STAGN_TOL, [0., 100.], float) def _individual_creator(self, refiner, bounds): creator.create( "Individual", pyxrd_array, fitness=FitnessMin, # @UndefinedVariable refiner=refiner, min_bounds=bounds[:, 0].copy(), max_bounds=bounds[:, 1].copy(), ) def create_individual(lst): arr = np.array(lst).clip(bounds[:, 0], bounds[:, 1]) #@UndefinedVariable return creator.Individual(arr) # @UndefinedVariable return create_individual def _create_stats(self): stats = tools.Statistics(lambda ind: ind.fitness.values) stats.register("avg", np.mean, axis=0) #@UndefinedVariable stats.register("std", np.std, axis=0) #@UndefinedVariable stats.register("min", np.min, axis=0) #@UndefinedVariable stats.register("max", np.max, axis=0) #@UndefinedVariable return stats _has_been_setup = False def _setup(self, refiner, ngen=NGEN, stagn_ngen=STAGN_NGEN, stagn_tol=STAGN_TOL, **kwargs): if not self._has_been_setup: logger.info("Setting up the DEAP CMA-ES refinement algorithm (ngen=%d)" % ngen) refiner.status.message = "Setting up algorithm..." # Process some general stuff: bounds = np.array(refiner.ranges) #@UndefinedVariable create_individual = self._individual_creator(refiner, bounds) # Setup strategy: centroid = create_individual(refiner.history.initial_solution) strat_kwargs = {} if "lambda_" in kwargs: strat_kwargs["lambda_"] = kwargs.pop("lambda_") strategy = Strategy( centroid=centroid, sigma=1.0 / 10.0, ranges=bounds, stop=self._stop, **strat_kwargs ) # Toolbox setup: toolbox = base.Toolbox() toolbox.register("generate", strategy.generate, create_individual) toolbox.register("update", strategy.update) # Hall of fame & stats: logger.info("Creating hall-off-fame and statistics") halloffame = PyXRDParetoFront(similar=lambda a1, a2: np.all(a1 == a2)) #@UndefinedVariable stats = self._create_stats() # Create algorithm self.algorithm = Algorithm( toolbox, halloffame, stats, ngen=ngen, stagn_ngen=stagn_ngen, stagn_tol=stagn_tol, refiner=refiner, stop=self._stop) self._has_been_setup = True return self.algorithm def run(self, refiner, **kwargs): logger.info("CMA-ES run invoked with %s" % kwargs) self._has_been_setup = False #clear this for a new refinement algorithm = self._setup(refiner, **kwargs) # Get this show on the road: logger.info("Running the CMA-ES algorithm...") algorithm.run() pass # end of class PyXRD-0.8.4/pyxrd/refinement/methods/deap_pso_cma.py000066400000000000000000000262101363064711000224240ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) import random, copy import numpy as np from deap.tools import HallOfFame from deap import creator, base, tools #@UnresolvedImport from pyxrd.generic.asynchronous.cancellable import Cancellable from pyxrd.refinement.refine_async_helper import RefineAsyncHelper from ..refine_method import RefineMethod from ..refine_method_option import RefineMethodOption from .deap_utils import pyxrd_array, FitnessMin, result_func from .deap_cma import Strategy # Default settings: NGEN = 100 NGEN_COMM = 5 NSWARMS = 4 class SwarmStrategy(Cancellable): def __create_strategy(self, parent, sigma, ranges, **kwargs): return Strategy( centroid=parent, sigma=sigma, ranges=ranges, stop=self._stop, **kwargs ) def __init__(self, parents, sigma, ranges, stop, ** kwargs): self.nswarms = len(parents) self._stop = stop self.strategies = [self.__create_strategy(parents[i], sigma, ranges, **kwargs) for i in range(self.nswarms)] self.global_best = None def update(self, swarms, communicate=False): if self._user_cancelled(): return for i, population in enumerate(swarms): self.strategies[i].update(population) # Keep track of the global best: best = population[0] try: if self.global_best is None or np.all(self.global_best.fitness < best.fitness): self.global_best = copy.deepcopy(best) except ValueError: logger.warn("Got a value error comparing '%s' and '%s'" % (self.global_best.fitness, best.fitness)) if communicate: for i, population in enumerate(swarms): self.strategies[i].rotate_and_bias(self.global_best) def generate(self, ind_init): for strategy in self.strategies: yield strategy.generate(ind_init) pass #end of class class SwarmAlgorithm(RefineAsyncHelper): @property def ngen(self): return self._ngen @ngen.setter def ngen(self, value): self._ngen = value logger.info("Setting ngen to %d" % value) _ngen = 100 gen = -1 halloffame = None refiner = None toolbox = None stats = None verbose = False #-------------------------------------------------------------------------- # Initialization #-------------------------------------------------------------------------- def __init__(self, toolbox, halloffame, stats, ngen=NGEN, ngen_comm=NGEN_COMM, verbose=__debug__, refiner=None, stop=None, eval_func=None, result_func=None): """ :param toolbox: A :class:`~deap.base.Toolbox` that contains the evolution operators. :param ngen: The number of generations. :param ngen_comm: At each multiple generation of this number swarms will communicate :param halloffame: A :class:`~deap.tools.ParetoFront` object that will contain the best individuals. :param stats: A :class:`~deap.tools.Statistics` object that is updated inplace. :param verbose: Whether or not to log the statistics. :param refiner: PyXRD refiner object :returns: The best individual and the final population. The toolbox should contain a reference to the generate and the update method of the chosen strategy. Call the run() method when the algorithm should be run. .. [Colette2010] Collette, Y., N. Hansen, G. Pujol, D. Salazar Aponte and R. Le Riche (2010). On Object-Oriented Programming of Optimizers - Examples in Scilab. In P. Breitkopf and R. F. Coelho, eds.: Multidisciplinary Design Optimization in Computational Mechanics, Wiley, pp. 527-565; """ self.stats = stats self.toolbox = toolbox self.ngen = ngen self.ngen_comm = ngen_comm self.halloffame = halloffame self.verbose = verbose self.refiner = refiner self.eval_func = eval_func self.result_func = result_func self.gen = 0 self._stop = stop #-------------------------------------------------------------------------- # Run method: #-------------------------------------------------------------------------- def run(self): """Will run this algorithm""" if self.verbose: column_names = ["gen", "evals", "best"] if self.stats is not None: column_names += list(self.stats.functions.keys()) self.logbook = tools.Logbook() self.logbook.header = column_names for _ in range(self.ngen): # Check if the user has cancelled: if self._user_cancelled(): self.refiner.status.message = "Stopping..." logger.info("User cancelled execution, stopping ...") break #ASK: Generate a new population: swarms = self._ask() #TELL: Update the strategy with the evaluated individuals self._tell(swarms) #RECORD: For logging: self._record(swarms) return self.refiner.history.best_solution, [ind for population in swarms for ind in population] #-------------------------------------------------------------------------- # Ask, tell & record: #-------------------------------------------------------------------------- def _ask(self): self.gen += 1 self.refiner.status.message = "Creating generation #%d" % self.gen swarms = [] def iter_func(): for generator in self.toolbox.generate(): swarm = [] for solution in generator: swarm.append(solution) yield solution swarms.append(swarm) if self.result_func is None: self.result_func = result_func def result_f(*args): self.refiner.update(*args) self.result_func(*args) population = self.do_async_evaluation( iter_func=iter_func, eval_func=self.eval_func, result_func=result_f ) if self.halloffame is not None: self.halloffame.update(population) del population return swarms def _tell(self, swarms): self.refiner.status.message = "Updating strategy" communicate = bool(self.gen > 0 and self.gen % self.ngen_comm == 0) self.toolbox.update(swarms, communicate=communicate) def _record(self, swarms): self.refiner.status.message = "Processing ..." # Get the best solution so far: if hasattr(self.halloffame, "get_best"): best = self.halloffame.get_best() else: best = self.halloffame[0] best_f = best.fitness.values[0] flat_pop = [ind for population in swarms for ind in population] pop_size = len(flat_pop) # Calculate stats & print something if needed: record = self.stats.compile(flat_pop) if self.verbose: self.logbook.record(gen=self.gen, evals=pop_size, best=best_f, **record) print(self.logbook.stream) self.refiner.status.message = "Refiner update ..." # Update the refiner history: self.refiner.update(best, iteration=self.gen, residual=best_f) pass #end of class class RefinePSOCMAESRun(RefineMethod): """ The PS-CMA-ES hybrid algorithm implementation """ name = "PS-CMA-ES refinement" description = "This algorithm uses the PS-CMA-ES hybrid refinement strategy" index = 6 disabled = False ngen = RefineMethodOption('Maximum # of generations', NGEN, [1, 10000], int) nswarms = RefineMethodOption('# of CMA swarms', NSWARMS, [1, 100], int) ngen_comm = RefineMethodOption('Communicate each x gens', NGEN_COMM, [1, 10000], int) def _individual_creator(self, refiner, bounds): creator.create( "Individual", pyxrd_array, fitness=FitnessMin, # @UndefinedVariable refiner=refiner, min_bounds=bounds[:, 0].copy(), max_bounds=bounds[:, 1].copy(), ) def create_individual(lst): arr = np.array(lst).clip(bounds[:, 0], bounds[:, 1]) #@UndefinedVariable return creator.Individual(arr) # @UndefinedVariable return create_individual def _create_stats(self): stats = tools.Statistics(lambda ind: ind.fitness.values) stats.register("avg", np.mean, axis=0) #@UndefinedVariable stats.register("std", np.std, axis=0) #@UndefinedVariable stats.register("min", np.min, axis=0) #@UndefinedVariable stats.register("max", np.max, axis=0) #@UndefinedVariable return stats _has_been_setup = False def _setup(self, refiner, ngen=NGEN, ngen_comm=NGEN_COMM, nswarms=NSWARMS, **kwargs): if not self._has_been_setup: logger.info("Setting up the DEAP CMA-ES refinement algorithm (ngen=%d)" % ngen) refiner.status.message = "Setting up algorithm..." # Process some general stuff: bounds = np.array(refiner.ranges) #@UndefinedVariable create_individual = self._individual_creator(refiner, bounds) # Setup strategy: #TODO make the others random parents = [None] * nswarms parents[0] = create_individual(refiner.history.initial_solution) for i in range(1, nswarms): parents[i] = create_individual([ random.uniform(bounds[j, 0], bounds[j, 1]) for j in range(len(refiner.history.initial_solution)) ]) strategy = SwarmStrategy( parents=parents, sigma=1.0 / 10.0, ranges=bounds, stop=self._stop, ) # Toolbox setup: toolbox = base.Toolbox() toolbox.register("generate", strategy.generate, create_individual) toolbox.register("update", strategy.update) # Hall of fame & stats: logger.info("Creating hall-off-fame and statistics") halloffame = HallOfFame(1, similar=lambda a1, a2: np.all(a1 == a2)) #PyXRDParetoFront(similar=lambda a1, a2: np.all(a1 == a2)) stats = self._create_stats() # Create algorithm self.algorithm = SwarmAlgorithm( toolbox, halloffame, stats, ngen=ngen, ngen_comm=ngen_comm, refiner=refiner, stop=self._stop) self._has_been_setup = True return self.algorithm def run(self, refiner, **kwargs): logger.info("CMA-ES run invoked with %s" % kwargs) self._has_been_setup = False #clear this for a new refinement algorithm = self._setup(refiner, **kwargs) # Get this show on the road: logger.info("Running the CMA-ES algorithm...") algorithm.run() pass # end of class PyXRD-0.8.4/pyxrd/refinement/methods/deap_swarm.py000066400000000000000000000366451363064711000221510ustar00rootroot00000000000000# This file is part of DEAP. # # DEAP is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation, either version 3 of # the License, or (at your option) any later version. # # DEAP is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with DEAP. If not, see . import logging logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) import numpy as np import scipy import itertools import random from pyxrd.refinement.refine_method import RefineMethod from pyxrd.refinement.refine_method_option import RefineMethodOption from pyxrd.refinement.refine_async_helper import RefineAsyncHelper from deap import base, creator, tools #@UnresolvedImport from .deap_utils import pyxrd_array, PyXRDParetoFront, FitnessMin, result_func # Default settings: NGEN = 100 NSWARMS = 1 NEXCESS = 3 NPARTICLES = 15 CONV_FACTR = 0.3 # 5.*1e-2 class MultiPSOStrategy(object): def generate_particle(self, pclass, dim, pmin, pmax, smin, smax): """ Generate a particle """ part = pclass(random.uniform(pmin[i], pmax[i]) for i in range(dim)) part.speed = np.array([random.uniform(smin[i], smax[i]) for i in range(dim)]) return part def update_particle(self, part, best, chi, c): """ Update a particle's position & speed part: the particle best: the global best chi ~ recombination factor c ~ scale factor(s) """ part_pos = np.asarray(part) best_pos = np.asarray(best) pers_pos = np.asarray(part.best) speed = np.asarray(part.speed) ce1 = c * np.random.uniform(0, 1, size=len(part)) ce2 = c * np.random.uniform(0, 1, size=len(part)) ce1_p = ce1 * np.array(best_pos - part_pos) ce1_g = ce2 * np.array(pers_pos - part_pos) f = (ce1_p + ce1_g) # Calculate velocity: a = chi * (f + speed) - speed # Adjust speed: speed = a + speed # Set position & speed: part[:] = part_pos + speed part.speed = speed # Clear fitness: del part.fitness.values def create_swarm(self, container, iterable): """ Returns a swarm container using the iterable """ return container(iterable) def generate_particles(self, func, n): """ Returns a particle generator """ for _ in range(n): yield func() def update_swarm(self, swarm, part): """ Update swarm's attractors personal best and global best """ if not part.fitness.valid: raise RuntimeError("Particles need to have a valid fitness before calling update_swarm!") if part.best == None or part.fitness > part.bestfit: part.best = creator.Particle(part) # Get the position @UndefinedVariable part.bestfit.values = part.fitness.values # Get the fitness if swarm.best == None or part.fitness > swarm.bestfit: swarm.best = creator.Particle(part) # Get the position @UndefinedVariable swarm.bestfit.values = part.fitness.values # Get the fitness def give_reinit_swarms(self, population): """ Gives a set of swarm indeces that need to be reinitialized (overlap)""" reinit_swarms = set() for s1, s2 in itertools.combinations(list(range(len(population))), 2): # Swarms must have a best and not already be set to reinitialize if population[s1].best is not None and population[s2].best is not None and not (s1 in reinit_swarms or s2 in reinit_swarms): # if t-test is True, then we reinit the worst of the two swarms t, _ = scipy.stats.ttest_ind(population[s1], population[s2]) #@UndefinedVariable if np.all(t < 0.1): if population[s1].bestfit <= population[s2].bestfit: reinit_swarms.add(s1) else: reinit_swarms.add(s2) return reinit_swarms def give_converged_swarms(self, population, conv_factr, converged_bests=[]): """ Returns the number of converged swarms and the worst swarm index """ # Convergence check: not_converged = 0 worst_swarm_idx = None worst_swarm = None for i, swarm in enumerate(population): # Compute the diameter of the swarm: std = np.std([ind.fitness.values for ind in swarm]) # If it's larger then a given factor, we've not converged yet: if std > conv_factr: not_converged += 1 if worst_swarm is None or swarm.bestfit < worst_swarm.bestfit: worst_swarm_idx = i worst_swarm = swarm else: converged_bests.append(swarm.best) converged_bests.sort(key=lambda i: i.fitness) return not_converged, worst_swarm_idx, converged_bests pass #end of class class MPSOAlgorithm(RefineAsyncHelper): """ Multi-particle-swarm optimization method adapted from the examples found in the DEAP project. Employs a T-test (two independent sample lists) to differentiate between swarms instead of the diameter and average of the swarms. Seemed to work better for scaled parameters (YMMV). Implementation of the Multiswarm Particle Swarm Optimization algorithm as presented in *Blackwell, Branke, and Li, 2008, Particle Swarms for Dynamic Optimization Problems.* """ gen = -1 converged_bests = None #-------------------------------------------------------------------------- # Initialization #-------------------------------------------------------------------------- def __init__(self, toolbox, bounds, norms, ngen=NGEN, nswarms=NSWARMS, nexcess=NEXCESS, nparticles=NPARTICLES, conv_factr=CONV_FACTR, stats=None, halloffame=None, verbose=True, refiner=None, stop=None): """ TODO """ self.converged_bests = [] self.toolbox = toolbox self.bounds = bounds self.norms = norms self.ngen = ngen self.nswarms = nswarms self.nexcess = nexcess self.nparticles = nparticles self.conv_factr = conv_factr self.stats = stats self.halloffame = halloffame self.verbose = verbose self.refiner = refiner self._stop = stop #-------------------------------------------------------------------------- # Convenience functions: #-------------------------------------------------------------------------- def _evaluate_swarms(self, population): # Only evaluate invalid particles: def give_unevaluated_particles(): for p in itertools.chain(*population): if p.fitness.valid: continue else: yield p self.do_async_evaluation(iter_func=give_unevaluated_particles, result_func=result_func) for swarm in population: for part in swarm: self.toolbox.update_swarm(swarm, part) return population def _create_and_evaluate_swarm(self): """ Helper function that creates, evaluates and returns a new swarm """ particles = self.do_async_evaluation(iter_func=self.toolbox.generate_particles, result_func=result_func) return self.toolbox.swarm(particles) def _create_and_evaluate_population(self): """ Helper function that creates, evaluates and returns a population of swarms """ population = [self._create_and_evaluate_swarm() for _ in range(self.nswarms)] if self.halloffame is not None: self.halloffame.update(itertools.chain(*population)) return population #-------------------------------------------------------------------------- # Run method: #------------------------------------------------------------------------- def run(self): """Will run this algorithm""" self._setup_logging() population = [] for _ in range(self.ngen): # Check if the user has cancelled: if self._user_cancelled(): logger.info("User cancelled execution of PCMA-ES, stopping ...") break #ASK: Generate a new population: population = self._ask(population) #RECORD: For stagnation checking & logging: self._record(population) #CHECK: whether we are stagnating: if self._is_stagnating(): break #TELL: Update the strategy with the evaluated individuals self._tell(population) return ( self.refiner.history.best_solution, list(itertools.chain(*population)), self.converged_bests ) #-------------------------------------------------------------------------- # Ask, tell & record: #-------------------------------------------------------------------------- def _ask(self, population): """ Calculates how many swarms have converged, and keeps track of the worst swarm. If all swarms have converged, it will add a new swarm. If too many swarms are roaming, it will remove the worst. """ self.gen += 1 self.refiner.status.message = "Creating generation #%d" % (self.gen + 1) if not population: # First iteration: create a new population of swarms population = self._create_and_evaluate_population() else: # Second and later iterations: check for overlapping swarms reinit_swarms = self.toolbox.give_reinit_swarms(population) # Reinitialize and evaluate swarms for sindex in reinit_swarms: population[sindex] = self._create_and_evaluate_swarm() # Get unconverged swarm count and worst swarm id: not_converged, worst_swarm_idx, self.converged_bests = self.toolbox.give_converged_swarms(population, self.conv_factr, self.converged_bests) # If all swarms have converged, add a swarm: if not_converged == 0: population.append(self._create_and_evaluate_swarm()) # If too many swarms are roaming, remove the worst swarm: elif not_converged > self.nexcess: population.pop(worst_swarm_idx) return population def _tell(self, population): # Update and evaluate the swarm for swarm in population: # Update particles and swarm: for part in swarm: if swarm.best is not None and part.best is not None: self.toolbox.update_particle(part, swarm.best) self._evaluate_swarms(population) self.halloffame.update(itertools.chain(*population)) def _is_stagnating(self): return False # TODO FIXME def _setup_logging(self): if self.verbose: column_names = ["gen", "nswarm", "indiv"] if self.stats is not None: column_names += list(self.stats.functions.keys()) self.logbook = tools.Logbook() self.logbook.header = column_names def _record(self, population): # Get pop size: pop_size = len(population) # Get the best solution so far: best = self.halloffame.get_best() best_f = best.fitness.values[0] # Calculate stats & print something if needed: pop = list(itertools.chain(*population)) record = self.stats.compile(pop) if self.verbose: self.logbook.record(gen=self.gen, nswarm=pop_size, indiv=len(pop), **record) print(self.logbook.stream) # Update the context: self.refiner.update(best, iteration=self.gen, residual=best_f) pass #end of class class RefineMPSORun(RefineMethod): """ The DEAP MPSO algorithm implementation """ name = "MPSO refinement" description = "This algorithm uses the MPSO refinement strategy" index = 2 disabled = False ngen = RefineMethodOption('Maximum # of generations', NGEN, [1, 1000], int) nswarms = RefineMethodOption('Start # of swarms', NSWARMS, [1, 50], int) nexcess = RefineMethodOption('Max # of unconverged swarms', NEXCESS, [1, 50], int) nparticles = RefineMethodOption('Swarm size', NPARTICLES, [1, 50], int) conv_factr = RefineMethodOption('Convergence tolerance', CONV_FACTR, [0., 10.], float) def _individual_creator(self, context, bounds): creator.create( "Particle", pyxrd_array, fitness=FitnessMin, #@UndefinedVariable speed=list, best=None, bestfit=FitnessMin, #@UndefinedVariable context=context, min_bounds=bounds[:, 0], max_bounds=bounds[:, 1], ) creator.create("Swarm", list, best=None, bestfit=FitnessMin) #@UndefinedVariable def _create_stats(self): stats = tools.Statistics(lambda ind: ind.fitness.values) stats.register("avg", np.mean, axis=0) stats.register("std", np.std, axis=0) stats.register("min", np.min, axis=0) stats.register("max", np.max, axis=0) return stats def run(self, refiner, ngen=NGEN, nswarms=NSWARMS, nexcess=NEXCESS, conv_factr=CONV_FACTR, nparticles=NPARTICLES, **kwargs): logger.info("Setting up the DEAP MPSO refinement algorithm") # Process some general stuff: ndim = len(refiner.refinables) bounds = np.array(refiner.ranges) norms = np.abs(bounds[:, 1] - bounds[:, 0]) self._individual_creator(refiner, bounds) # Strategy setup strategy = MultiPSOStrategy() # Hall of fame & stats: logger.info("Creating hall-off-fame and statistics") halloffame = PyXRDParetoFront(similar=lambda a1, a2: np.all(a1 == a2)) stats = self._create_stats() # Our toolbox: toolbox = base.Toolbox() toolbox.register( "particle", strategy.generate_particle, creator.Particle, #@UndefinedVariable dim=ndim, pmin=bounds[:, 0], pmax=bounds[:, 1], smin=-norms / 2.0, smax=norms / 2.0 ) toolbox.register("update_particle", strategy.update_particle, chi=0.729843788, c=norms / np.amax(norms)) toolbox.register("generate_particles", strategy.generate_particles, toolbox.particle, n=NPARTICLES) toolbox.register("swarm", strategy.create_swarm, creator.Swarm) #@UndefinedVariable toolbox.register("update_swarm", strategy.update_swarm) toolbox.register("give_reinit_swarms", strategy.give_reinit_swarms) toolbox.register("give_converged_swarms", strategy.give_converged_swarms) # Create algorithm algorithm = MPSOAlgorithm( toolbox, bounds, norms, ngen, nswarms, nexcess, nparticles, conv_factr, stats=stats, halloffame=halloffame, refiner=refiner, stop=self._stop, **kwargs ) # Get this show on the road: logger.info("Running the MPSO algorithm...") best, population, converged_bests = algorithm.run() # returns (best, population) tuple return best, population, converged_bests pass # end of class PyXRD-0.8.4/pyxrd/refinement/methods/deap_utils.py000066400000000000000000000103411363064711000221410ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) from math import sqrt import sys from operator import mul, truediv import numpy as np from deap import creator, base from deap.tools import ParetoFront class pyxrd_array(creator._numpy_array): """ Helper DEAP.creator._numpy_array subclass that tracks changes to its underlying numerical data object, and if changed gets the corresponding data object from the context (if set). Allows for async evaluation of fitnesses for PyXRD parameter solutions. """ min_bounds = None max_bounds = None def to_ndarray(self): return np.ndarray.copy(self) def __setitem__(self, i, y): y = min(y, self.max_bounds[i]) y = max(y, self.min_bounds[i]) creator._numpy_array.__setitem__(self, i, y) def __setslice__(self, i, j, y): y = np.array(y) np.clip(y, self.min_bounds[i:j], self.max_bounds[i:j], y) creator._numpy_array.__setslice__(self, i, j, y) pass # end of class def result_func(individual, fitness): individual.fitness.values = fitness class FitnessMin(base.Fitness): weights = [] def getValues(self): return tuple(map(truediv, self.wvalues, (-1,) * len(self.wvalues))) def setValues(self, values): try: self.wvalues = tuple(map(mul, values, (-1,) * len(values))) except TypeError: _, _, traceback = sys.exc_info() raise TypeError("Both weights and assigned values must be a " "sequence of numbers when assigning to values of " "%r. Currently assigning value(s) %r of %r to a fitness with " "weights %s." % (self.__class__, values, type(values), (-1,) * len(values))).with_traceback(traceback) values = property(getValues, setValues, base.Fitness.delValues, ("Fitness values. Use directly ``individual.fitness.values = values`` " "in order to set the fitness and ``del individual.fitness.values`` " "in order to clear (invalidate) the fitness. The (unweighted) fitness " "can be directly accessed via ``individual.fitness.values``.")) def __eq__(self, other): return tuple(self.wvalues) == tuple(other.wvalues) pass #end of class class PyXRDParetoFront(ParetoFront): def get_best_n(self, n): inds = [] for ind in self: d = 0.0 for f in ind.fitness.wvalues: d += f ** 2 d = sqrt(d) inds.append((d, ind)) inds.sort(key=lambda ind:-ind[0]) return inds[:n] def get_best(self): _, ind = self.get_best_n(1)[0] return ind def update(self, population): """Update the Pareto front hall of fame with the *population* by adding the individuals from the population that are not dominated by the hall of fame. If any individual in the hall of fame is dominated it is removed. :param population: A list of individual with a fitness attribute to update the hall of fame with. """ for ind in population: is_dominated = False has_twin = False to_remove = [] for i, hofer in enumerate(self): # hofer = hall of famer try: if hofer.fitness.dominates(ind.fitness): is_dominated = True break elif ind.fitness.dominates(hofer.fitness): to_remove.append(i) elif ind.fitness == hofer.fitness and self.similar(ind, hofer): has_twin = True break except ValueError: print(ind, ind.fitness) print(hofer, hofer.fitness) raise for i in reversed(to_remove): # Remove the dominated hofer self.remove(i) if not is_dominated and not has_twin: self.insert(ind) # Run the garbage collector once for good measure import gc gc.collect() PyXRD-0.8.4/pyxrd/refinement/methods/scipy_runs.py000066400000000000000000000053771363064711000222230ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import numpy as np import scipy from ..refine_method import RefineMethod from ..refine_method_option import RefineMethodOption import logging logger = logging.getLogger(__name__) MAXFUN = 15000 MAXITER = 15000 IPRINT = 0 class RefineLBFGSBRun(RefineMethod): """ An implementation of the L BFGS B refinement algorithm. """ name = "L BFGS B algorithm" description = "Refinement using the L BFGS B algorithm" index = 0 disabled = False maxfun = RefineMethodOption('Maximum # of function calls', MAXFUN, [1, 1000000], int) maxiter = RefineMethodOption('Maximum # of iterations', MAXITER, [1, 1000000], int) iprint = RefineMethodOption('Output level [-1,0,1]', IPRINT, [-1, 1], int) def run(self, refiner, maxfun=MAXFUN, maxiter=MAXITER, iprint=IPRINT, **kwargs): """ Refinement using the L BFGS B algorithm """ solution, residual, d = scipy.optimize.fmin_l_bfgs_b(# @UnusedVariable @UndefinedVariable refiner.get_residual, refiner.history.initial_solution, approx_grad=True, bounds=refiner.ranges, iprint=iprint, epsilon=1e-4, callback=refiner.update, maxfun=maxfun, maxiter=maxiter ) refiner.update(solution, residual=residual) logger.debug("fmin_l_bfgs_b returned: %s" % d) pass # end of class class RefineBasinHoppingRun(RefineMethod): """ An implementation of the Basin Hopping refinement algorithm. """ name = "Basin Hopping Algorithm" description = "Refinement using a basin hopping algorithm" index = 4 disabled = False niter = RefineMethodOption('Number of iterations', 100, [10, 10000], int) T = RefineMethodOption('Temperature criterion', 1.0, [0.0, None], int) stepsize = RefineMethodOption('Displacement step size', 0.5, [0.0, None], float) def run(self, refiner, niter=100, T=1.0, stepsize=0.5, **kwargs): """ Refinement using a Basin Hopping Algorithm """ vals = scipy.optimize.basinhopping( # @UndefinedVariable refiner.get_residual, refiner.history.initial_solution, niter=niter, T=T, # this can be quite large stepsize=stepsize, minimizer_kwargs={ 'method': 'L-BFGS-B', 'bounds': refiner.ranges, }, callback=lambda s,r, a: refiner.update(s,r), ) solution = np.asanyarray(vals.x) residual = vals.fun refiner.update(solution, residual=residual) pass # end of class PyXRD-0.8.4/pyxrd/refinement/parspace.py000066400000000000000000000334541363064711000201550ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from traceback import format_exc import itertools from math import sqrt import logging from pyxrd.data import settings logger = logging.getLogger(__name__) import numpy as np from scipy.interpolate.ndgriddata import griddata from mpl_toolkits.axes_grid1 import ImageGrid import mpl_toolkits.axes_grid1.axes_size as Size from mpl_toolkits.axes_grid1 import Divider from pyxrd.generic.mathtext_support import get_plot_safe class ParameterSpaceGenerator(object): num_cross_sections = 0 grid = None def initialize(self, ranges, density): """ Create a new generator with the given minimum and maximum values and the given grid density (expressed as # of data points for that parameter axis). """ self.num_params = len(ranges) assert self.num_params >= 1, "Need to have at least one refinable parameter!" ranges = np.asarray(ranges, dtype=float) self.minima = np.asarray(ranges[:, 0]) self.maxima = np.asarray(ranges[:, 1]) self.center_point = 0.5 * (self.minima + self.maxima) self.num_cross_sections = int(self.num_params * (self.num_params + 1) / 2) self.grid_dtype = np.dtype([("point", object), ("value", float), ("distance", float)]) self.density = self._calculate_density(density) if self.num_params == 1: self.grid = np.empty(shape=(self.density,), dtype=self.grid_dtype) else: self.grid = np.empty(shape=(self.num_cross_sections, self.density, self.density), dtype=self.grid_dtype) def _calculate_density(self, density, memory_limit=settings.PAR_SPACE_MEMORY_LIMIT): block_size = self.grid_dtype.itemsize limited = sqrt(memory_limit / (block_size * self.num_cross_sections)) return max(min(density, limited), 3) def _find_closest_grid_index(self, solution): """ Returns the closest grid point's indexes """ # Transform solution so center of grid is zero point and min-max goes from -1 to +1 transf = (np.asarray(solution) - self.center_point) / (self.maxima - self.minima) if self.num_params > 1: # Calculate distance to each cross section and find closest smallest_distance = None closest_cross_section = None for index, (par1, par2) in enumerate(itertools.combinations(list(range(self.num_params)), 2)): # Project on to the normal of the A/B plan at the center point: projected = transf.copy() projected[par1] = 0 projected[par2] = 0 distance = np.linalg.norm(projected) if smallest_distance is None or smallest_distance > distance: smallest_distance = distance closest_cross_section = index, (par1, par2), distance index, (par1, par2), distance = closest_cross_section # Move back to 0->density space (was in -1 -> 1 space) gridded_location = np.array(np.round(0.5 * (transf + 1) * (self.density - 1)), dtype=int) closest_index = (index, int(gridded_location[par1]), int(gridded_location[par2])) else: # Only a single parameter, so only a single index: gridded_location = 0.5 * (transf + 1.0) * (self.density - 1.0) distance = np.abs(np.round(gridded_location) - gridded_location) closest_index = (int(np.round(gridded_location)[0]),) return closest_index, distance total_record_calls = 0 total_actual_records = 0 def record(self, new_solution, new_residual): """ Add a new solution to the list of solutions """ self.total_record_calls += 1 # Get the best spot to store this one: closest_index, new_distance = self._find_closest_grid_index(new_solution) old_item = self.grid[closest_index] old_solution = old_item["point"] #old_residual = old_item["value"] old_distance = old_item["distance"] # If we have a previous point, check which one is closer: if (old_solution is not None and old_solution[0] is not None) and new_distance > old_distance: return else: self.total_actual_records += 1 # If we got here, we can store the result: self.grid[closest_index] = (new_solution, new_residual, new_distance) def clear(self): del self.grid def clear_image(self, figure, message="Interpolation Error"): figure.clear() figure.text(0.5, 0.5, message, va="center", ha="center") def _setup_image_grid(self, figure, dims): """ An example of how grid, parameter and view numbers change for dims = 4 The numbers in the grid are: parameter x, parameter y grid x, grid y ----------------------------------------------------- | | | | | | 0, 0 | 1, 0 | 2, 0 | 3, 0 | | -, - | -, - | -, - | -, - | | | | | | ==============------------|------------|------------| I I | | | I 0, 1 I 1, 1 | 2, 1 | 3, 1 | I 0, 0 I 1, 0 | 2, 0 | -, - | I I | | | I------------==============------------|------------| I | I | | I 0, 2 | 1, 2 I 2, 2 | 3, 2 | I 0, 1 | 1, 1 I 2, 1 | -, - | I | I | | I------------|------------==============------------| I | | I | I 0, 3 | 1, 3 | 2, 3 I 3, 3 | I 0, 2 | 1, 2 | 2, 2 I -, - | I | | I | I======================================I------------| From the above it should be clear that: parameter x = grid x parameter y = grid y + 1 grid nr = grid y + grid x * (dims - 1) view nr = grid nr - (grid nr / dims) * ((grid nr / dims) +1) / 2 """ image_grid = ImageGrid( figure, 111, nrows_ncols=(dims - 1, dims - 1), cbar_location="right", cbar_mode="single", # add_all=False, aspect=False, axes_pad=0.1, direction="column" ) rect = (0.1, 0.1, 0.8, 0.8) horiz = [Size.Fixed(.1)] + [Size.Scaled(1.), Size.Fixed(.1)] * max(dims - 1, 1) + [Size.Fixed(0.15)] vert = [Size.Fixed(.1)] + [Size.Scaled(1.), Size.Fixed(.1)] * max(dims - 1, 1) # divide the axes rectangle into grid whose size is specified by horiz * vert divider = Divider(figure, rect, horiz, vert) # , aspect=False) # Helper to get the axis for par x and y: def get_grid(parx, pary): gridx = parx gridy = pary - 1 return image_grid[gridy + gridx * (dims - 1)] # Helper to get the grid locator for par x and par y def get_locator(parx, pary): gridx = parx gridy = pary - 1 nx = 1 + gridx * 2 ny = 1 + (dims - gridy - 2) * 2 return divider.new_locator(nx=nx, ny=ny) # Hide the unused plots & setup the used ones: for parx, pary in itertools.product(list(range(self.num_params - 1)), list(range(1, self.num_params))): # Calculate the grid position: ax = get_grid(parx, pary) # Setup axes: if pary <= parx: ax.set_visible(False) else: ax.set_axes_locator(get_locator(parx, pary)) ax.set_visible(True) return image_grid, divider, get_grid def plot_images(self, figure, centroid, labels, density=200, smooth=0.5): """ Generate the parameter space plots """ try: def extraxt_points_from_grid(grid2D, parx=None, pary=None): """ Helper function that extract x,y(,z) points from all the data """ grid2D = grid2D.flatten() if parx is not None and pary is not None: points_x = np.array([item["point"][parx] for item in grid2D if item["point"] is not None]) points_y = np.array([item["point"][pary] for item in grid2D if item["point"] is not None]) points_z = np.array([item["value"] for item in grid2D if item["point"] is not None]) return points_x, points_y, points_z else: points_x = np.array([item["point"] for item in grid2D if item["point"] is not None]) points_y = np.array([item["value"] for item in grid2D if item["point"] is not None]) points_x = points_x.flatten() points_y = points_y.flatten() xy = np.array(list(zip(points_x, points_y)), dtype=[('x', float), ('y', float)]) xy.sort(order=['x'], axis=0) points_x = xy['x'] points_y = xy['y'] return points_x, points_y if self.num_params == 1: # Only one parameter refined: points_x, points_y = extraxt_points_from_grid(self.grid) ax = figure.add_subplot(1, 1, 1) ax.plot(points_x, points_y) ax.set_ylabel("Residual error") ax.set_xlabel(get_plot_safe(labels[0])) else: # Multi-parameter space: image_grid, divider, get_grid = self._setup_image_grid(figure, self.num_params) # Keep a reference to the images created, # so we can add a scale bar for all images (and they have the same range) ims = [] tvmin, tvmax = None, None for index, (parx, pary) in enumerate(itertools.combinations(list(range(self.num_params)), 2)): # Get the data for this cross section: grid2D = self.grid[index, ...] points_x, points_y, points_z = extraxt_points_from_grid(grid2D, parx, pary) # Setup axis: ax = get_grid(parx, pary) extent = ( self.minima[parx], self.maxima[parx], self.minima[pary], self.maxima[pary] ) aspect = 'auto' # Try to interpolate the data: xi = np.linspace(self.minima[parx], self.maxima[parx], density) yi = np.linspace(self.minima[pary], self.maxima[pary], density) zi = griddata((points_x, points_y), points_z, (xi[None, :], yi[:, None]), method='cubic') # Plot it: im = ax.imshow(zi, origin='lower', aspect=aspect, extent=extent, alpha=0.75) ims.append(im) im.set_cmap('gray_r') # Get visual limits: vmin, vmax = im.get_clim() if index == 0: tvmin, tvmax = vmin, vmax else: tvmin, tvmax = min(tvmin, vmin), max(tvmax, vmax) # Try to add a contour & labels: try: ct = ax.contour(xi, yi, zi, colors='k', aspect=aspect, extent=extent, origin='lower') ax.clabel(ct, colors='k', fontsize=10, format="%1.2f") except ValueError: pass #ignore the "zero-size array" error for now. # Add a red cross where the 'best' solution is: ax.plot((centroid[parx],), (centroid[pary],), 'r+') # Rotate x labels for lbl in ax.get_xticklabels(): lbl.set_rotation(90) # Reduce number of ticks: ax.locator_params(axis='both', nbins=5) # Set limits: ax.set_xlim(extent[0:2]) ax.set_ylim(extent[2:4]) # Add labels to the axes so the user knows which is which: # TODO add some flags/color/... indicating which phase & component each parameter belongs to... if parx == 0: ax.set_ylabel(str("#%d " % (pary + 1)) + get_plot_safe(labels[pary])) if pary == (self.num_params - 1): ax.set_xlabel(str("#%d " % (parx + 1)) + get_plot_safe(labels[parx])) # Set the data limits: for im in ims: im.set_clim(tvmin, tvmax) # Make it look PRO: if im is not None: cbar_ax = image_grid.cbar_axes[0] nx = 1 + (self.num_params - 1) * 2 ny1 = (self.num_params - 1) * 2 cbar_ax.set_axes_locator(divider.new_locator(nx=nx, ny=1, ny1=ny1)) cb = cbar_ax.colorbar(im) # @UnusedVariable except: print("Unhandled exception while generating parameter space images:") print(format_exc()) # ignore error, tell the user via the plot and return self.clear_image(figure) return pass #end of class PyXRD-0.8.4/pyxrd/refinement/refinables/000077500000000000000000000000001363064711000201065ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/refinement/refinables/__init__.py000066400000000000000000000000001363064711000222050ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/refinement/refinables/metaclasses.py000066400000000000000000000033571363064711000227740ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from mvc.models.metaclasses import ModelMeta from .models import RefinementInfo class PyXRDRefinableMeta(ModelMeta): """ A metaclass for regular mvc Models with refinable properties. """ # ------------------------------------------------------------ # Instance creation: # ------------------------------------------------------------ def __call__(cls, *args, **kwargs): # @NoSelf """ Strips the refinement info data from the keyword argument dictionary, passes the stripped dictionary to create the actual class instance, creates the attributes on the instance and returns it. """ # Pop & parse any refinement info keyword arguments that might be present: prop_infos = dict() for prop in cls.Meta.all_properties: if getattr(prop, "refinable", False): ref_info_name = prop.get_refinement_info_name() info_args = kwargs.pop(ref_info_name, None) if info_args: prop_infos[ref_info_name] = RefinementInfo.from_json(*info_args) else: prop_infos[ref_info_name] = RefinementInfo(prop.minimum, prop.maximum, False) # Create the instance passing the stripped keyword arguments: instance = ModelMeta.__call__(cls, *args, **kwargs) # Set the refinement attributes on the newly created instance: for ref_info_name, ref_info in prop_infos.items(): setattr(instance, ref_info_name, ref_info) # Return the instance: return instance pass # end of class PyXRD-0.8.4/pyxrd/refinement/refinables/mixins.py000066400000000000000000000055251363064711000217760ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. class _RefinementBase(object): """ Base class for `RefinementGroup` and `RefinementValue` mixins. It's used to provide common functionality and a way to check for the kind of refinement class we're dealing with when building the refinement tree. .. attribute:: refine_title A string used as the title for the group in the refinement tree .. attribute:: refine_descriptor A longer title string which gives more information (phase, component, etc) .. attribute:: is_refinable Whether or not this instance is refinable .. attribute:: refinables An iterable with the names of the refinable properties .. attribute:: refine_value Mapper for the actual refinable value (if available). This should be overriden by deriving classes. """ @property def refine_title(self): return "Refinement Base" @property def refine_descriptor_data(self): return dict() @property def is_refinable(self): return True @property def refinables(self): return [] @property def refine_info(self): return None @property def refine_value(self): return None @refine_value.setter def refine_value(self, value): pass pass # end of class class RefinementGroup(_RefinementBase): """ Mixin for objects that are not refinable themselves, but have refinable properties. They are presented in the refinement tree using their title value. Subclasses should override refine_title to make it more descriptive. .. attribute:: children_refinable Whether or not the child properties of this group can be refinable. This should normally always be True, unless for example if the entire group of properties have a single inherit property. """ @property def refine_title(self): return "Refinement Group" @property def is_refinable(self): return False @property def children_refinable(self): return True @property def refinables(self): return self.Meta.get_refinable_properties() pass # end of class class RefinementValue(_RefinementBase): """ Mixin for objects that hold a single refinable property. They are collapsed into one line in the refinement tree. Subclasses should override both the refine_title property to make it more descriptive, and the refine_value property to return and set the correct (refinable) attribute. """ @property def refine_title(self): return "Refinement Value" pass # end of class PyXRD-0.8.4/pyxrd/refinement/refinables/models.py000066400000000000000000000033031363064711000217420ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from mvc.models.properties import FloatProperty, BoolProperty from pyxrd.generic.models.base import PyXRDModel from pyxrd.generic.io import storables, Storable from pyxrd.generic.utils import not_none @storables.register() class RefinementInfo(PyXRDModel, Storable): """ A model that is used to store the refinement information for each refinable value (in other models): minimum and maximum value and a flag to indicate whether this value is selected for refinement. """ # MODEL INTEL: class Meta(PyXRDModel.Meta, Storable.Meta): store_id = "RefinementInfo" minimum = FloatProperty(default=0.0, text="Minimum", persistent=True) maximum = FloatProperty(default=1.0, text="Maximum", persistent=True) refine = BoolProperty(default=False, text="Refine", persistent=True) def __init__(self, minimum, maximum, refine, *args, **kwargs): """ Valid *positional* arguments for a RefinementInfo are: refine: whether or not the linked parameter is selected for refinement minimum: the minimum allowable value for the linked parameter maximum: the maximum allowable value for the linked parameter """ super(RefinementInfo, self).__init__() self.refine = refine self.minimum = not_none(minimum, 0.0) self.maximum = not_none(maximum, 1.0) def to_json(self): return self.json_properties() def json_properties(self): return [self.minimum, self.maximum, self.refine] pass # end of class PyXRD-0.8.4/pyxrd/refinement/refinables/properties/000077500000000000000000000000001363064711000223025ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/refinement/refinables/properties/__init__.py000066400000000000000000000004301363064711000244100ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .data_mixin import DataMixin from .refinable_mixin import RefinableMixin __ALL__ = [ "DataMixin", "RefinableMixin" ]PyXRD-0.8.4/pyxrd/refinement/refinables/properties/data_mixin.py000066400000000000000000000016001363064711000247660ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. class DataMixin(object): """ Mixing for the ~:class:`mvc.models.properties.LabeledProperty` descriptor that allows this property to be set on the `data_object` object of the instance this property belongs to, instead of a private attribute. When this Mixin is used, the user can pass an additional keyword argument to the descriptor: - data_object_label: the private attribute label for the data object, defaults to '_data_object' """ data_object_label = "_data_object" def _get_private_label(self): """ Private attribute label (holds the actual value on the model) """ return "%s.%s" % ( self.data_object_label, self.label ) passPyXRD-0.8.4/pyxrd/refinement/refinables/properties/refinable_mixin.py000066400000000000000000000016641363064711000260160ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. class RefinableMixin(object): """ Mixing for the ~:class:`mvc.models.properties.LabeledProperty` descriptor that allows the property to be refinable. When this Mixin is used, the user should pass 4 additional keyword arguments to the descriptor: - refinable: boolean set to True if the property is refinable - refinable_info_format: the format for the refinement info attribute - minimum: the minimum allowed value (or None as default) - maximum: the maximum allowed value (or None as default) """ refinable = True refinable_info_format = "%(label)s_ref_info" minimum = None maximum = None def get_refinement_info_name(self): return self.refinable_info_format % { 'label': self.label } pass #end of classPyXRD-0.8.4/pyxrd/refinement/refinables/wrapper.py000066400000000000000000000167421363064711000221520ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) from mvc.models.properties import ( LabeledProperty, StringProperty, BoolProperty, ReadOnlyMixin ) from pyxrd.generic.models.base import ChildModel from .mixins import _RefinementBase, RefinementValue, RefinementGroup class RefinableWrapper(ChildModel): """ Wrapper class for refinables easing the retrieval of certain properties for the different types of refinables. Can be used with an ObjectTreeStore. """ # MODEL INTEL: class Meta(ChildModel.Meta): parent_alias = "mixture" # PROPERTIES: #: The wrapped object obj = LabeledProperty( default=None, text="Wrapped object", tabular=True) #: The property descriptor object for the attribute prop_descr = LabeledProperty( default=None, text="Property descriptor", tabular=True) #: The Property label: @StringProperty( default="", text="Property label", tabular=True, mix_with=(ReadOnlyMixin,)) def label(self): return self.prop_descr.label #: A flag indicating whether this is wrapper is representing the group #: (True) or a member of the group (False): is_grouper = BoolProperty( default=False, text="Is grouper", tabular=True, mix_with=(ReadOnlyMixin,)) #: The inherit attribute name: @LabeledProperty( default=None, text="Inherit from label", mix_with=(ReadOnlyMixin,)) def inherit_from(self): return self.prop_descr.inherit_from if self.prop_descr else None #: The (possibly mathtext) label for the refinable property: @StringProperty( default="", text="Title", tabular=True, mix_with=(ReadOnlyMixin,)) def title(self): if (isinstance(self.obj, RefinementGroup) and self.is_grouper) or isinstance(self.obj, RefinementValue): return self.obj.refine_title else: if getattr(self.prop_descr, "math_text", None) is not None: return self.prop_descr.math_text else: return self.prop_descr.text #: The (pure text) label for the refinable property: @StringProperty( default="", text="Text title", tabular=True, mix_with=(ReadOnlyMixin,)) def text_title(self): if (isinstance(self.obj, RefinementGroup) and self.is_grouper) or isinstance(self.obj, RefinementValue): return self.obj.refine_title else: return self.prop_descr.text @StringProperty( default="", text="Descriptor", tabular=True, mix_with=(ReadOnlyMixin,)) def text_descriptor(self): """ Return a longer title that also describes this property's relations """ # This gets the phase and/or component name for the group or value: data = self.obj.refine_descriptor_data # Here we still need to get the actual property title: data["property_name"] = self.text_title return "%(phase_name)s | %(component_name)s | %(property_name)s" % data #: The actual value of the refinable property: @LabeledProperty( default=None, text="Value", tabular=True) def value(self): if isinstance(self.obj, RefinementValue): return self.obj.refine_value elif not self.is_grouper: return getattr(self.obj, self.label) else: return "" @value.setter def value(self, value): value = max(min(value, self.value_max), self.value_min) if self.is_grouper: raise AttributeError("Cannot set the value for a grouping RefinableWrapper") elif isinstance(self.obj, RefinementValue): self.obj.refine_value = value else: setattr(self.obj, self.label, value) #: Whether or not this property is inherited from another object @BoolProperty( default=False, text="Inherited", tabular=True, mix_with=(ReadOnlyMixin,)) def inherited(self): return self.inherit_from is not None and hasattr(self.obj, self.inherit_from) and getattr(self.obj, self.inherit_from) #: Whether or not this property is actually refinable @BoolProperty( default=False, text="Refinable", tabular=True, mix_with=(ReadOnlyMixin,)) def refinable(self): if isinstance(self.obj, _RefinementBase): # We have a _RefinementBase property (group or value) if isinstance(self.obj, RefinementGroup): if self.is_grouper: # the grouper itself return False else: # attribute of the grouper return (not self.inherited) and self.obj.children_refinable elif isinstance(self.obj, RefinementValue): return (not self.inherited) and self.obj.is_refinable else: # This is actually impossible, but what the hack... return (not self.inherited) #: The refinement info object for the refinable property @LabeledProperty( default=None, text="Refinement info", tabular=True, mix_with=(ReadOnlyMixin,)) def ref_info(self): if (isinstance(self.obj, RefinementGroup) and self.is_grouper) or isinstance(self.obj, RefinementValue): return self.obj.refine_info else: name = self.prop_descr.get_refinement_info_name() if name is not None: ref_info = getattr(self.obj, name) return ref_info else: raise AttributeError("Cannot find refine info model for attribute '%s' on '%s'" % (self.label, self.obj)) #: The minimum value for the refinable property @LabeledProperty( default=None, text="Minimum value", tabular=True) def value_min(self): return self.ref_info.minimum if self.ref_info else None @value_min.setter def value_min(self, value): if self.ref_info: self.ref_info.minimum = value #: The maximum value of the refinable property @LabeledProperty( default=None, text="Maximum value", tabular=True) def value_max(self): return self.ref_info.maximum if self.ref_info else None @value_max.setter def value_max(self, value): if self.ref_info: self.ref_info.maximum = value #: Wether this property is selected for refinement @BoolProperty( default=False, text="Refine", tabular=True) def refine(self): return self.ref_info.refine if self.ref_info else False @refine.setter def refine(self, value): if self.ref_info: self.ref_info.refine = value and self.refinable # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): """ Valid keyword arguments for a RefinableWrapper are: obj: the object we are wrapping a parameter for prop or prop_descr: the property descriptor is_grouper: whether or not this is a grouper object """ my_kwargs = self.pop_kwargs(kwargs, "obj", "prop", "prop_descr", "is_grouper") super(RefinableWrapper, self).__init__(**kwargs) kwargs = my_kwargs self.obj = self.get_kwarg(kwargs, None, "obj") self.prop_descr = self.get_kwarg(kwargs, None, "prop_descr", "prop") self._is_grouper = self.get_kwarg(kwargs, False, "is_grouper") pass # end of class PyXRD-0.8.4/pyxrd/refinement/refine_async_helper.py000066400000000000000000000016361363064711000223600ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pyxrd.generic.utils import not_none from pyxrd.refinement.async_evaluatable import AsyncEvaluatable class RefineAsyncHelper(AsyncEvaluatable): """ Helper class which can help classes having a refiner object """ def do_async_evaluation(self, iter_func, eval_func=None, data_func=None, result_func=None): assert self.refiner is not None, "RefineAsyncHelper can only work when a refiner is set!" eval_func = not_none(eval_func, self.refiner.residual_callback) data_func = not_none(data_func, self.refiner.get_data_object) result_func = not_none(result_func, self.refiner.update) return super(RefineAsyncHelper, self).do_async_evaluation( iter_func, eval_func, data_func, result_func )PyXRD-0.8.4/pyxrd/refinement/refine_history.py000066400000000000000000000077271363064711000214140ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import numpy as np class RefineHistory(object): """ A history tracking class for refinements """ INITIAL_ITERATION_INDEX = -1 LAST_ITERATION_INDEX = -1 ITERATION_INDEX = 0 RESIDUAL_INDEX = -1 SOLUTION_SELECTOR = np.s_[ITERATION_INDEX+1:RESIDUAL_INDEX] PLOT_SAMPLE_SELECTOR = np.s_[ITERATION_INDEX+1:] samples = None _closed = False @property def best_entry(self): if self._closed: samples = self.samples else: samples = np.asanyarray(self.samples) residuals = samples[:,self.RESIDUAL_INDEX] return samples[np.where(residuals == residuals.min()),:][-1].flatten() @property def initial_entry(self): if self._closed: samples = self.samples else: samples = np.asanyarray(self.samples) iterations = samples[:,self.ITERATION_INDEX] return samples[np.where(iterations == self.INITIAL_ITERATION_INDEX),:][-1].flatten() @property def last_entry(self): if self._closed: samples = self.samples else: samples = np.asanyarray(self.samples) iterations = samples[:,self.ITERATION_INDEX] return samples[np.where(iterations == self.LAST_ITERATION_INDEX),:][-1].flatten() @property def best_solution(self): return self.best_entry[self.SOLUTION_SELECTOR].flatten() @property def initial_solution(self): return self.initial_entry[self.SOLUTION_SELECTOR] @property def last_solution(self): return self.last_entry[self.SOLUTION_SELECTOR] @property def initial_residual(self): return float(self.initial_entry[self.RESIDUAL_INDEX]) @property def best_residual(self): return float(self.best_entry[self.RESIDUAL_INDEX]) @property def last_residual(self): return float(self.last_entry[self.RESIDUAL_INDEX]) # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self): self.samples = [] def _sort_solutions_by_iteration(self): self.samples.sort(key=lambda s: s[0]) # ------------------------------------------------------------ # ContextManager implementation # ------------------------------------------------------------ def close(self): self._closed = True self._sort_solutions_by_iteration() self.samples = np.asanyarray(self.samples) def __enter__(self): assert self._closed is False, "Cannot use a closed refinement history!" return self def __exit__(self, tp, value, traceback): self.close() # ------------------------------------------------------------ # Solution registration: # ------------------------------------------------------------ def set_initial_solution(self, solution, residual): if self._closed: raise RuntimeError("Cannot change a closed refinement history!") self.register_solution(self.INITIAL_ITERATION_INDEX, solution, residual) def register_solution(self, iteration, solution, residual): if self._closed: raise RuntimeError("Cannot change a closed refinement history!") sample = [iteration,]+list(solution)+[residual,] self.samples.append(sample) if iteration > self.LAST_ITERATION_INDEX: self.LAST_ITERATION_INDEX = iteration def get_residual_per_iteration(self): if not self._closed: raise RuntimeError("Cannot perform analysis on an open refinement history") return self.samples[:,[self.ITERATION_INDEX,self.RESIDUAL_INDEX]].tolist() pass #end of class PyXRD-0.8.4/pyxrd/refinement/refine_method.py000066400000000000000000000035541363064711000211650ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) from .refine_method_meta import RefineMethodMeta from .refine_async_helper import RefineAsyncHelper from pyxrd.calculations.mixture import get_optimized_residual class RefineMethod(RefineAsyncHelper, metaclass=RefineMethodMeta): """ The `RefineMethod` class is the base class for refinement methods. Sub-classes will be registered in the metaclass. """ name = "Name of the algorithm" description = "A slightly longer explanation of algorithm" # The value of this index is important; # Some ranges are reserved to prevent immediate overlaps: # - negative values should not be used (not enforced) # - the range 0 - 999 is reserved for built-in methods # - all other values can be used for third-party methods, it is up to the # final user to check if they don't overlap. If these methods become # a built-in method, they'll receive a new index in the preserved range index = -1 disabled = True residual_callback = property(fget=lambda *s: get_optimized_residual) def __call__(self, refiner, stop=None, **kwargs): self._stop = stop options = self.get_options() for arg in self.options: options[arg] = kwargs.get(arg, getattr(self, arg)) return self.run(refiner, **options) def run(self, refiner, **kwargs): raise NotImplementedError("The run method of RefineRun should be implemented by sub-classes...") def get_options(self): """ Returns a dict containing the option attribute names as keys and their values as values """ return { name: getattr(self, name) for name in self.options } pass #end of class PyXRD-0.8.4/pyxrd/refinement/refine_method_manager.py000066400000000000000000000033341363064711000226530ustar00rootroot00000000000000from pyxrd.refinement.refine_method_meta import RefineMethodMeta from .methods import * # @UnusedWildImport class RefineMethodManager(object): @classmethod def initialize_methods(cls, refine_options): """ Returns a dict of refine methods as values and their index as key with the passed refine_options dict applied. """ # 1. Create a list of refinement instances: refine_methods = {} for index, method in cls.get_all_methods().items(): refine_methods[index] = method() # 2. Create dict of default options default_options = {} for method in list(refine_methods.values()): default_options[method.index] = { name: getattr(type(method), name).default for name in method.options } # 3. Apply the refine options to the methods if not refine_options == None: for index, options in zip(list(refine_options.keys()), list(refine_options.values())): index = int(index) if index in refine_methods: method = refine_methods[index] for arg, value in zip(list(options.keys()), list(options.values())): if hasattr(method, arg): setattr(method, arg, value) return refine_methods @classmethod def get_all_methods(cls): """ Returns all the registered refinement methods """ return RefineMethodMeta.registered_methods @classmethod def get_method_from_index(cls, index): """ Returns the actual refinement method defined by the index """ return cls.get_all_methods()[index] pass # end of classPyXRD-0.8.4/pyxrd/refinement/refine_method_meta.py000066400000000000000000000021641363064711000221670ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) from .refine_method_option import RefineMethodOption class RefineMethodMeta(type): """ The metaclass for creating a RefineMethod (sub)class Will register the class type so we can build a list of RefineMethod classes dynamically. If the (sub)class does not want to be registered, it should set the 'disabled' class attribute to True. """ registered_methods = {} def __new__(meta, name, bases, class_dict): # @NoSelf options = [] for name, value in class_dict.items(): if isinstance(value, RefineMethodOption): options.append(name) setattr(value, 'label', name) class_dict['options'] = options cls = type.__new__(meta, name, bases, class_dict) if not getattr(cls, 'disabled', False): meta.registered_methods[getattr(cls, 'index')] = cls return cls pass #end of class PyXRD-0.8.4/pyxrd/refinement/refine_method_option.py000066400000000000000000000031501363064711000225450ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. class RefineMethodOption(object): """ Descriptor for refinement methods """ # TODO MERGE THIS WITH THE DESCRIPTORS FROM THE OTHER BRANCH label = None def __init__(self, description, default=None, limits=[None, None], value_type=object, fget=None, fset=None, fdel=None, doc=None, label=None): self.description = description self.limits = limits self.value_type = value_type self.fget = fget self.fset = fset self.fdel = fdel if doc is None and fget is not None: doc = fget.__doc__ self.__doc__ = doc self.label = label self.default = default def __get__(self, instance, owner): if instance is None: return self if self.fget is None: return getattr(instance, "_%s" % self.label, self.default) else: return self.fget(instance) def __set__(self, instance, value): _min, _max = self.limits if self.value_type in (str, int, float): value = self.value_type(value) if _min is not None: value = max(value, _min) if _max is not None: value = min(value, _max) if self.fset is None: setattr(instance, "_%s" % self.label, value) else: return self.fset(instance, value) def __delete__(self, instance): if self.fdel is None: raise AttributeError("can't delete attribute") self.fdel(instance) pass # end of class PyXRD-0.8.4/pyxrd/refinement/refine_status.py000066400000000000000000000077211363064711000212300ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on import time # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. class RefineStatus(object): """ A status object for a refinement. This should provide some hints for the UI to keep track of the status of the refinement. The error, running and finished flags are mutually exclusive but can be False together. When the cancelled flag is set, the user cancelled the refinement (the stop signal was set). When the error flag is true, an error was encountered during the refinement. When the finished flag is true the refinement has finished successfully. When the running flag is true the refinement is still running. When all three flags are False, the refinement has not started yet. The status message should be a textual description of these three flags, but can be set to any value. The current error is retrieved from the RefinementHistory instance passed in the constructor (it is the residual of the last solution registered). The best way to use the status object is with a context, like this: with RefineHistory() as history: with RefineStatus(history) as status: run_refinement() """ _error = False @property def error(self): return self._error @error.setter def error(self, value): self._error = bool(value) if self._error: self.running = False self.cancelled = False self.finished = False _cancelled = False @property def cancelled(self): return self._cancelled @cancelled.setter def cancelled(self, value): self._cancelled = bool(value) if self._cancelled: self.running = False self.error = False self.finished = False _running = False @property def running(self): return self._running @running.setter def running(self, value): self._running = bool(value) if self._running: self.error = False self.cancelled = False self.finished = False _finished = False @property def finished(self): return self._finished @finished.setter def finished(self, value): self._finished = bool(value) if self._finished: self.error = False self.cancelled = False self.running = False message = "Not initialized." @property def current_error(self): return self.history.last_solution[self.history.RESIDUAL_INDEX] def __init__(self, history, stop_signal=None): assert history is not None, "The RefinementStatus needs a RefinementHistory instance!" self.history = history self.stop_signal = stop_signal self.message = "Initialized." self.start_time = -1 self.end_time = -1 def __enter__(self): # Set flag self.running = True # Set message self.message = "Running..." # Record start time self.start_time = time.time() # Return us return self def __exit__(self, tp, value, traceback): # Record end time self.start_time = -1 if tp is not None: self.message = "Refinement error!" self.error = True else: if self.stop_signal is not None and self.stop_signal.is_set(): self.message = "Refinement cancelled!" self.cancelled = True else: self.message = "Refinement finished!" self.finished = True def get_total_time(self): """ Gets the total time the refinement has run in ms """ if self.start_time == -1: return 0 else: return (self.end_time - self.start_time) * 1000.0 pass #end of classPyXRD-0.8.4/pyxrd/refinement/refinement.py000066400000000000000000000205341363064711000205060ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import random from mvc.models.properties import ( LabeledProperty, ListProperty, IntegerChoiceProperty, BoolProperty, ReadOnlyMixin ) from mvc.models import TreeNode from pyxrd.generic.models.event_context_manager import EventContextManager from pyxrd.generic.models.properties import InheritableMixin from pyxrd.generic.models import ChildModel from .refinables.mixins import RefinementValue, RefinementGroup from .refinables.wrapper import RefinableWrapper from .refine_method_manager import RefineMethodManager from .refiner import Refiner class Refinement(ChildModel): """ A simple model that plugs onto the Mixture model. It provides the functionality related to refinement of parameters. """ # MODEL INTEL: class Meta(ChildModel.Meta): store_id = "Refinement" mixture = property(ChildModel.parent.fget, ChildModel.parent.fset) #: Flag, True if after refinement plots should be generated of the parameter space make_psp_plots = BoolProperty( default=False, text="Make parameter space plots", tabular=False, visible=True, persistent=True ) #: TreeNode containing the refinable properties refinables = ListProperty( default=None, text="Refinables", tabular=True, persistent=False, visible=True, data_type=RefinableWrapper, cast_to=None, widget_type="object_tree_view" ) #: A dict containing an instance of each refinement method refine_methods = None #: An integer describing which method to use for the refinement refine_method_index = IntegerChoiceProperty( default=0, text="Refinement method index", tabular=True, persistent=True, visible=True, choices={ key: method.name for key, method in RefineMethodManager.get_all_methods().items() } ) #: A dict containing the current refinement options @LabeledProperty( default=None, text="Refine options", persistent=False, visible=False, mix_with=(ReadOnlyMixin,) ) def refine_options(self): return self.get_refinement_method().get_options() #: A dict containing all refinement options @property def all_refine_options(self): return { method.index : method.get_options() for method in list(self.refine_methods.values()) } def __init__(self, *args, **kwargs): my_kwargs = self.pop_kwargs(kwargs, "refine_method_index", "refine_method", "refine_options" ) super(Refinement, self).__init__(*args, **kwargs) kwargs = my_kwargs # Setup the refinables treestore self.refinables = TreeNode() self.update_refinement_treestore() # Setup the refine methods try: self.refine_method_index = int(self.get_kwarg(kwargs, None, "refine_method_index", "refine_method")) except ValueError: self.refine_method_index = self.refine_method_index pass # ignore faulty values, these indices change from time to time. self.refine_methods = RefineMethodManager.initialize_methods( self.get_kwarg(kwargs, None, "refine_options") ) # ------------------------------------------------------------ # Refiner methods # ------------------------------------------------------------ def get_refiner(self): """ This returns a Refiner object which can be used to refine the selected properties using the selected algorithm. Just call 'refine(stop)' on the returned object, with stop a threading.Event or multiprocessing.Event which you can use to stop the refinement before completion. The Refiner object also has a RefineHistory and RefineStatus object that can be used to track the status and history of the refinement. """ return Refiner( method = self.get_refinement_method(), data_callback = lambda: self.mixture.data_object, refinables = self.refinables, event_cmgr = EventContextManager(self.mixture.needs_update, self.mixture.data_changed), metadata = dict( phases = self.mixture.phases, num_specimens = len(self.mixture.specimens), ) ) # ------------------------------------------------------------ # Refinement Methods Management # ------------------------------------------------------------ def get_refinement_method(self): """ Returns the actual refinement method by translating the `refine_method` attribute """ return self.refine_methods[self.refine_method_index] # ------------------------------------------------------------ # Refinables Management # ------------------------------------------------------------ # TODO set a restrict range attribute on the PropIntels, so we can use custom ranges for each property def auto_restrict(self): """ Convenience function that restricts the selected properties automatically by setting their minimum and maximum values. """ with self.mixture.needs_update.hold(): for node in self.refinables.iter_children(): ref_prop = node.object if ref_prop.refine and ref_prop.refinable: ref_prop.value_min = ref_prop.value * 0.8 ref_prop.value_max = ref_prop.value * 1.2 def randomize(self): """ Convenience function that randomize the selected properties. Respects the current minimum and maximum values. Executes an optimization after the randomization. """ with self.mixture.data_changed.hold_and_emit(): with self.mixture.needs_update.hold_and_emit(): for node in self.refinables.iter_children(): ref_prop = node.object if ref_prop.refine and ref_prop.refinable: ref_prop.value = random.uniform(ref_prop.value_min, ref_prop.value_max) def update_refinement_treestore(self): """ This creates a tree store with all refinable properties and their minimum, maximum and current value. """ if self.parent is not None: # not linked so no valid phases! self.refinables.clear() def add_property(parent_node, obj, prop, is_grouper): rp = RefinableWrapper(obj=obj, prop=prop, parent=self.mixture, is_grouper=is_grouper) return parent_node.append(TreeNode(rp)) def parse_attribute(obj, prop, root_node): """ obj: the object attr: the attribute of obj or None if obj contains attributes root_node: the root TreeNode new iters should be put under """ if prop is not None: if isinstance(prop, InheritableMixin): value = prop.get_uninherited(obj) else: value = getattr(obj, prop.label) else: value = obj if isinstance(value, RefinementValue): # AtomRelation and UnitCellProperty new_node = add_property(root_node, value, prop, False) elif hasattr(value, "__iter__"): # List or similar for new_obj in value: parse_attribute(new_obj, None, root_node) elif isinstance(value, RefinementGroup): # Phase, Component, Probability if len(value.refinables) > 0: new_node = add_property(root_node, value, prop, True) for prop in value.refinables: parse_attribute(value, prop, new_node) else: # regular values new_node = add_property(root_node, obj, prop, False) for phase in self.mixture.project.phases: if phase in self.mixture.phase_matrix: parse_attribute(phase, None, self.refinables) pass # end of class PyXRD-0.8.4/pyxrd/refinement/refiner.py000066400000000000000000000147601363064711000200100ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import numpy as np from .refine_history import RefineHistory from .refine_status import RefineStatus class RefineSetupError(ValueError): """ Raised if an error exists in the refinement setup """ pass class Refiner(object): """ A model for the refinement procedure. """ method = None history = None status = None refinables = None def __init__(self, method, data_callback, refinables, event_cmgr, metadata={}): super(Refiner, self).__init__() assert method is not None, "Cannot refine without a refinement method!" residual_callback = method.residual_callback assert callable(residual_callback), "Cannot refine without a residual callback!" assert callable(data_callback), "Cannot refine without a data callback!" assert refinables is not None, "Cannot refine without refinables!" assert event_cmgr is not None, "Cannot refine without an event context manager!" # Set these: self.method = method self.residual_callback = residual_callback self.data_callback = data_callback self.event_cmgr = event_cmgr self.metadata = metadata # Create the refinement history object: logger.info("Setting up the refinement history.") self.history = RefineHistory() # Create the refinement status object: logger.info("Setting up the refinement status object.") self.status = RefineStatus(self.history) # Setup the refinable property list: logger.info("Refinement with the following refinables:") self.refinables = [] self.ranges = () self.labels = () initial_values = [] for node in refinables.iter_children(): refinable = node.object if refinable.refine and refinable.refinable: logger.info(" - %s from %r" % (refinable.text_title, refinable.obj)) self.refinables.append(refinable) initial_values.append(refinable.value) if not (refinable.value_min < refinable.value_max): logger.info("Error in refinement setup!") self.status.error = True self.status.message = "Invalid parameter range for '%s'!" % (refinable.text_descriptor,) raise RefineSetupError("Invalid parameter range for '%s'!" % (refinable.text_descriptor,)) self.ranges += ((refinable.value_min, refinable.value_max),) self.labels += ((refinable.text_title, refinable.title),) # Make sure we can refine something: if len(self.refinables) == 0: logger.error("No refinables selected!") self.status.error = True self.status.message = "No parameters selected!" raise RefineSetupError("No parameters selected!") # Register the initial solution: initial_solution = np.array(initial_values, dtype=float) self.history.set_initial_solution( initial_solution, self.get_history_residual(self.get_residual(initial_solution)) ) def apply_solution(self, solution): """ Applies the given solution """ solution = np.asanyarray(solution) with self.event_cmgr.hold(): for i, ref_prop in enumerate(self.refinables): if not (solution.shape == ()): ref_prop.value = float(solution[i]) else: ref_prop.value = float(solution[()]) def get_data_object(self, solution): """ Gets the mixture data object after setting the given solution """ with self.event_cmgr.ignore(): self.apply_solution(solution) return self.data_callback() def get_residual(self, solution): """ Gets the residual for the given solution after setting it """ return self.residual_callback(self.get_data_object(solution)) def get_history_residual(self, residual): try: return residual[0] except IndexError: return residual def update(self, solution, residual=None, iteration=0): """ Update's the refinement contect with the given solution: - applies the solution & gets the residual if not given - stores it in the history """ residual = self.get_history_residual( residual if residual is not None else self.get_residual(solution) ) self.history.register_solution(iteration, solution, residual) def apply_best_solution(self): self.apply_solution(self.history.best_solution) def apply_last_solution(self): self.apply_solution(self.history.last_solution) def apply_initial_solution(self): self.apply_solution(self.history.initial_solution) def get_plot_samples(self): return self.history.samples[:,self.history.PLOT_SAMPLE_SELECTOR] def get_plot_labels(self): return [plot_label for plot_label, _ in self.labels] + ["Rp",] def refine(self, stop): # Suppress updates: with self.event_cmgr.hold(): # Make sure the stop signal is not set from a previous run: stop.clear() # Log some information: logger.info("-"*80) logger.info("Starting refinement with this setup:") msg_frm = "%22s: %s" logger.info(msg_frm % ("refinement method", self.method)) logger.info(msg_frm % ("number of parameters", len(self.refinables))) # Run the refinement: with self.status: with self.history: self.method(self, stop=stop) # Log some more information: logger.info('Total refinement took %0.3f ms' % self.status.get_total_time()) logger.info('Best solution found was:') for i, ref_prop in enumerate(self.refinables): logger.info("%25s: %f" % ( ref_prop.text_descriptor, self.history.best_solution[i] )) logger.info("-"*80) # Return us to whatever called this return self pass # end of class PyXRD-0.8.4/pyxrd/refinement/views/000077500000000000000000000000001363064711000171315ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/refinement/views/__init__.py000066400000000000000000000000001363064711000212300ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/refinement/views/glade/000077500000000000000000000000001363064711000202055ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/refinement/views/glade/refine_method.glade000066400000000000000000000054301363064711000240150ustar00rootroot00000000000000 200 False 2 2 10 5 True False Method: GTK_FILL GTK_FILL True False 1 2 GTK_FILL True True True 320 True True in True True True False Method options: True 2 1 2 GTK_FILL PyXRD-0.8.4/pyxrd/refinement/views/glade/refine_results.glade000066400000000000000000000261151363064711000242410ustar00rootroot00000000000000 False 12 Refinement results True True dialog True True False False 5 2 5 True False 0 Which solution do you want to keep? 2 GTK_FILL GTK_FILL True False True True 450 300 True False True False Parameter space 2 4 5 True False 0 20 Initial solution's residual: 1 2 GTK_FILL True False 0 20 Best solution's residual: 2 3 GTK_FILL True False 0 20 Last solution's residual: 3 4 GTK_FILL True False 0 0.51999998092651367 10 label 1 2 1 2 True False 0 10 label 1 2 2 3 True False 0 10 label 1 2 3 4 True True 0 True False False True 3 1 True False 10 Best 75 True True True False True end 0 Last 75 True True True False True end 0 Initial 75 True True True False True end 1 False True 2 PyXRD-0.8.4/pyxrd/refinement/views/glade/refine_status.glade000066400000000000000000000127361363064711000240670ustar00rootroot00000000000000 False 3 5 5 True False 1 Initial R<sub>p</sub>: True GTK_FILL True False 1 Current R<sub>p</sub>: True 1 2 GTK_FILL True False 1 10 0.00 1 2 1 2 True False True 1 10 0.00 1 2 True False 0 % 2 3 GTK_FILL 5 True False 0 % 2 3 1 2 GTK_FILL 5 True False 3 4 3 GTK_FILL True False 10 4 5 3 GTK_FILL True False 1 True 3 2 3 GTK_FILL PyXRD-0.8.4/pyxrd/refinement/views/glade/refinement.glade000066400000000000000000000223231363064711000233410ustar00rootroot00000000000000 True False 5 241-flash True False 5 027-search True False 5 429-restrict True False 5 7 3 5 5 True False 0 none 320 240 True True in True True True False 3 <b>Parameter selection:</b> True 3 True False 10 True False server status True 0 False True 0 Refine True True True img_refine False True end 0 Create plots True True False Whether to create parameter space plots after the refinement is finished. Use Brute Force for best results. 0.5 True False True end 1 3 3 5 GTK_FILL True False Inherited properties are disabled. 0 1 2 5 True False 3 6 7 GTK_FILL 5 True False 3 2 3 5 True False 3 5 6 Restrict values True True True img_restrict 2 3 1 2 GTK_FILL GTK_FILL 5 Randomize True True True img_randomize 1 2 1 2 GTK_FILL GTK_FILL 5 PyXRD-0.8.4/pyxrd/refinement/views/refinement_view.py000066400000000000000000000067251363064711000227030ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport from math import isnan from mvc.adapters.gtk_support.widgets.threaded_task_box import ThreadedTaskBox from pyxrd.generic.views import DialogView from pyxrd.generic.utils import not_none class RefinementView(DialogView): title = "Refine Phase Parameters" subview_builder = resource_filename(__name__, "glade/refinement.glade") subview_toplevel = "refine_params" modal = True refine_status_builder = resource_filename(__name__, "glade/refine_status.glade") refine_status_toplevel = "tbl_refine_info" refine_status_container = "refine_status_box" refine_spin_container = "refine_spin_box" refine_spin_box = None refine_method_builder = resource_filename(__name__, "glade/refine_method.glade") refine_method_toplevel = "tbl_refine_method" refine_method_container = "refine_method_box" def __init__(self, *args, **kwargs): super(RefinementView, self).__init__(*args, **kwargs) # Add the status box self._builder.add_from_file(self.refine_status_builder) self._add_child_view(self[self.refine_status_toplevel], self[self.refine_status_container]) # Add the method and options box self._builder.add_from_file(self.refine_method_builder) self._add_child_view(self[self.refine_method_toplevel], self[self.refine_method_container]) # Add the refinement thread box self.refine_spin_box = ThreadedTaskBox() self._add_child_view(self.refine_spin_box, self[self.refine_spin_container]) self.hide_refinement_info() def connect_cancel_request(self, callback): return self.refine_spin_box.connect("cancelrequested", callback) def show_refinement_info(self,): self["hbox_actions"].set_sensitive(False) self["btn_auto_restrict"].set_sensitive(False) self[self.refine_method_toplevel].set_sensitive(False) self["refinables"].set_visible(False) self["refinables"].set_no_show_all(True) self[self.refine_status_toplevel].show_all() def hide_refinement_info(self): self[self.refine_status_toplevel].hide() self["hbox_actions"].set_sensitive(True) self["btn_auto_restrict"].set_sensitive(True) self[self.refine_method_toplevel].set_sensitive(True) self["refinables"].set_visible(True) self["refinables"].set_no_show_all(False) def update_refinement_info(self, current_rp=None, message=None, server_status=None): if not isnan(current_rp): self["current_residual"].set_text("%.2f" % current_rp) self["message"].set_text(not_none(message, "")) self.update_server_status(server_status) def update_server_status(self, server_status): color, title, descr = server_status self["lbl_server_status"].set_markup("%s" % (color, title)) self["lbl_server_status"].set_property("tooltip-text", descr) self["lbl_server_status"].set_property("tooltip-text", descr) self["lbl_server_status"].set_tooltip_text(descr) def update_refinement_status(self, status): self.refine_spin_box.set_status(status) def start_spinner(self): self.refine_spin_box.start() def stop_spinner(self): self.refine_spin_box.stop() pass # end of classPyXRD-0.8.4/pyxrd/refinement/views/refiner_view.py000066400000000000000000000045161363064711000221750ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk # @UnresolvedImport import matplotlib from matplotlib.figure import Figure from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvasGTK from matplotlib.backends.backend_gtk3 import NavigationToolbar2GTK3 as NavigationToolbar from pyxrd.generic.views import BaseView class RefinerView(BaseView): """ A view for the Refiner object """ builder = resource_filename(__name__, "glade/refine_results.glade") top = "window_refine_results" modal = True graph_parent = "plot_box" def __init__(self, *args, **kwargs): BaseView.__init__(self, *args, **kwargs) self.graph_parent = self[self.graph_parent] self.get_toplevel().set_transient_for(self.parent.get_toplevel()) self.setup_matplotlib_widget() def update_labels(self, initial, best, last): self["initial_residual"].set_text("%f" % initial) self["best_residual"].set_text("%f" % best) self["last_residual"].set_text("%f" % last) def setup_matplotlib_widget(self): # TODO Create a mixin for this kind of thing!! #style = Gtk.Style() self.figure = Figure(dpi=72) #, edgecolor=str(style.bg[2]), facecolor=str(style.bg[2])) self.figure.subplots_adjust(bottom=0.20) self.canvas = FigureCanvasGTK(self.figure) box = Gtk.VBox() box.pack_start(NavigationToolbar(self.canvas, self.get_top_widget()), False, True, 0) box.pack_start(self.canvas, True, True, 0) self.graph_parent.add(box) self.graph_parent.show_all() cdict = {'red': ((0.0, 0.0, 0.0), (0.5, 1.0, 1.0), (1.0, 0.0, 0.0)), 'green': ((0.0, 0.0, 0.0), (0.5, 1.0, 1.0), (1.0, 0.0, 0.0)), 'blue': ((0.0, 0.0, 0.0), (0.5, 1.0, 1.0), (1.0, 0.0, 0.0))} self.wbw_cmap = matplotlib.colors.LinearSegmentedColormap('WBW', cdict, 256) pass # end of classPyXRD-0.8.4/pyxrd/scripts/000077500000000000000000000000001363064711000153275ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/scripts/__init__.py000066400000000000000000000000001363064711000174260ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/scripts/generate_default_phases.py000066400000000000000000000641371363064711000225550ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os from pyxrd.data import settings from pyxrd.project.models import Project from pyxrd.phases.models import Component, Phase def generate_expandables( filename_format, phase_name, maxR, phase_kwargs_AD, phase_kwargs_EG, phase_kwargs_350, code_AD, code_EG, code_350, comp_kwargs_AD, comp_kwargs_EG, comp_kwargs_350): """ Generates a list of phase descriptions for a combination of an AD, EG and 350° Ca-saturated phase linked together """ return [ ('%s' + (filename_format % R), [ (dict(R=R, name=phase_name + (' R%d Ca-AD' % R), **phase_kwargs_AD), code_AD, comp_kwargs_AD), (dict(R=R, name=phase_name + (' R%d Ca-EG' % R), based_on=phase_name + (' R%d Ca-AD' % R), **phase_kwargs_EG), code_EG, comp_kwargs_EG), (dict(R=R, name=phase_name + (' R%d Ca-350' % R), based_on=phase_name + (' R%d Ca-AD' % R), **phase_kwargs_350), code_350, comp_kwargs_350) ]) for R in range(maxR) ] def run(args=None, ui_callback=None): """ How this script works: - 'code_length' is the length of the aliases keys (see below) - 'aliases' is a dictionary contain 4-character long keys describing a specific layer-type (or with other words: a Component object) E.g. dS2w stands for Di-octahedral Smectite with 2 layers of water. The values are file path formats, in which a single '%s' string placeholder will be filled with the absolute path to the default components folder. - 'default_phases' is an initially empty list that will be filled with two- tuples. The first element in this tuple is the filename of the generated phases, the second element is describing what this phase contains. This second element is again a tuple, containing three parts: - A dictionary of key-word arguments passed on to the Phase constructor. If a 'based_on' keyword is defined, an attempt is made to translate it to an earlier generated phase. This way, it is possible to pass the name of an earlier generated phase, and the script will pass in the actual Phase object instead. - A component code (string) built by the keys of the 'aliases' dictionary. This string's length should be a multiple of 'code_length'. There is no separator, rather, the 'code_length' is used to split the code into its parts. - Component keyword arguments dictionaries: this is a dictionary in which the keys match with the components code parts. The values are property-value dictionaries used to set Component properties after importing them. Similarly to the Phases' 'based_on' keyword, the value for the 'linked_with' key is translated to the actual Component named as such. ### Setup: """ code_length = 4 aliases = { 'C ': '%sChlorite.cmp', 'K ': '%sKaolinite.cmp', 'I ': '%sIllite.cmp', 'Se ': '%sSerpentine.cmp', 'T ': '%sTalc.cmp', 'Ma ': '%sMargarite.cmp', 'Pa ': '%sParagonite.cmp', 'L ': '%sLeucophyllite.cmp', 'dS2w': '%sDi-Smectite/Di-Smectite - Ca 2WAT.cmp', 'dS1w': '%sDi-Smectite/Di-Smectite - Ca 1WAT.cmp', 'dS0w': '%sDi-Smectite/Di-Smectite - Ca Dehydr.cmp', 'dS2g': '%sDi-Smectite/Di-Smectite - Ca 2GLY.cmp', 'dS1g': '%sDi-Smectite/Di-Smectite - Ca 1GLY.cmp', 'dSht': '%sDi-Smectite/Di-Smectite - Ca Heated.cmp', 'tS2w': '%sTri-Smectite/Tri-Smectite - Ca 2WAT.cmp', 'tS1w': '%sTri-Smectite/Tri-Smectite - Ca 1WAT.cmp', 'tS0w': '%sTri-Smectite/Tri-Smectite - Ca Dehydr.cmp', 'tS2g': '%sTri-Smectite/Tri-Smectite - Ca 2GLY.cmp', 'tS1g': '%sTri-Smectite/Tri-Smectite - Ca 1GLY.cmp', 'tSht': '%sTri-Smectite/Tri-Smectite - Ca Heated.cmp', 'dV2w': '%sDi-Vermiculite/Di-Vermiculite - Ca 2WAT.cmp', 'dV1w': '%sDi-Vermiculite/Di-Vermiculite - Ca 1WAT.cmp', 'dV0w': '%sDi-Vermiculite/Di-Vermiculite - Ca Dehydr.cmp', 'dV2g': '%sDi-Vermiculite/Di-Vermiculite - Ca 2GLY.cmp', 'dV1g': '%sDi-Vermiculite/Di-Vermiculite - Ca 1GLY.cmp', 'dVht': '%sDi-Vermiculite/Di-Vermiculite - Ca Heated.cmp', } default_phases = [] """ ### Commonly used inherit flag dicts: """ inherit_S = dict( inherit_ucp_a=True, inherit_ucp_b=True, inherit_delta_c=True, inherit_layer_atoms=True, ) inherit_all = dict( inherit_d001=True, inherit_default_c=True, inherit_interlayer_atoms=True, inherit_atom_relations=True, **inherit_S ) inherit_phase = dict( inherit_display_color=True, inherit_sigma_star=True, inherit_CSDS_distribution=True, inherit_probabilities=True ) """ ### Single-layer phases: """ default_phases += [ ('%sKaolinite.phs', [(dict(R=0, name='Kaolinite'), 'K ', {}), ]), ('%sIllite.phs', [(dict(R=0, name='Illite'), 'I ', {})]), ('%sSerpentine.phs', [(dict(R=0, name='Serpentine'), 'Se ', {})]), ('%sTalc.phs', [(dict(R=0, name='Talc'), 'T ', {})]), ('%sChlorite.phs', [(dict(R=0, name='Chlorite'), 'C ', {})]), ('%sMargarite.phs', [(dict(R=0, name='Margarite'), 'Ma ', {})]), ('%sLeucophyllite.phs', [(dict(R=0, name='Leucophyllite'), 'L ', {})]), ('%sParagonite.phs', [(dict(R=0, name='Paragonite'), 'Pa ', {})]), ] """ ### Dioctahedral smectites: """ S_code_AD = 'dS2w' S_code_EG = 'dS2g' S_code_350 = 'dSht' S_inh_comp_args = { 'dS2g': dict(linked_with='dS2w', **inherit_S), 'dSht': dict(linked_with='dS2w', **inherit_S), } SS_code_AD = S_code_AD + 'dS1w' SS_code_EG = S_code_EG + 'dS1g' SS_code_350 = S_code_350 + 'dS1g' SS_inh_comp_args = dict(S_inh_comp_args) SS_inh_comp_args.update({ 'dS1g': dict(linked_with='dS1w', **inherit_S), }) SSS_code_AD = SS_code_AD + 'dS0w' SSS_code_EG = SS_code_EG + 'dS0w' SSS_code_350 = SS_code_350 + 'dS0w' SSS_inh_comp_args = dict(SS_inh_comp_args) SSS_inh_comp_args.update({ 'dS0w': dict(linked_with='dS0w', **inherit_S), }) default_phases += [ ('%sSmectites/Di-Smectite Ca.phs', [ (dict(R=0, name='S R0 Ca-AD'), S_code_AD, {}), (dict(R=0, name='S R0 Ca-EG', based_on='S R0 Ca-AD', **inherit_phase), S_code_EG, S_inh_comp_args), (dict(R=0, name='S R0 Ca-350', based_on='S R0 Ca-AD', **inherit_phase), S_code_350, S_inh_comp_args) ]), ] default_phases += generate_expandables( 'Smectites/SS/Di-SS R%d Ca.phs', 'SS', 4, {}, inherit_phase, inherit_phase, SS_code_AD, SS_code_EG, SS_code_350, {}, SS_inh_comp_args, SS_inh_comp_args, ) default_phases += generate_expandables( 'Smectites/SSS/Di-SSS R%d Ca.phs', 'SSS', 3, {}, inherit_phase, inherit_phase, SSS_code_AD, SSS_code_EG, SSS_code_350, {}, SSS_inh_comp_args, SSS_inh_comp_args, ) """ ### Trioctahedral smectites: """ tS_code_AD = 'tS2w' tS_code_EG = 'tS2g' tS_code_350 = 'tSht' tS_inh_comp_args = { 'tS2g': dict(linked_with='tS2w', **inherit_S), 'tSht': dict(linked_with='tS2w', **inherit_S), } tSS_code_AD = tS_code_AD + 'tS1w' tSS_code_EG = tS_code_EG + 'tS1g' tSS_code_350 = tS_code_350 + 'tS1g' tSS_inh_comp_args = dict(S_inh_comp_args) tSS_inh_comp_args.update({ 'tS1g': dict(linked_with='tS1w', **inherit_S), }) tSSS_code_AD = tSS_code_AD + 'tS0w' tSSS_code_EG = tSS_code_EG + 'tS0w' tSSS_code_350 = tSS_code_350 + 'tS0w' tSSS_inh_comp_args = dict(SS_inh_comp_args) tSSS_inh_comp_args.update({ 'tS0w': dict(linked_with='tS0w', **inherit_S), }) default_phases += [ ('%sSmectites/Tri-Smectite Ca.phs', [ (dict(R=0, name='S R0 Ca-AD'), tS_code_AD, {}), (dict(R=0, name='S R0 Ca-EG', based_on='S R0 Ca-AD', **inherit_phase), tS_code_EG, tS_inh_comp_args), (dict(R=0, name='S R0 Ca-350', based_on='S R0 Ca-AD', **inherit_phase), tS_code_350, tS_inh_comp_args) ]), ] default_phases += generate_expandables( 'Smectites/SS/Tri-SS R%d Ca.phs', 'SS', 4, {}, inherit_phase, inherit_phase, tSS_code_AD, tSS_code_EG, tSS_code_350, {}, tSS_inh_comp_args, tSS_inh_comp_args, ) default_phases += generate_expandables( 'Smectites/SSS/Tri-SSS R%d Ca.phs', 'SSS', 3, {}, inherit_phase, inherit_phase, tSSS_code_AD, tSSS_code_EG, tSSS_code_350, {}, tSSS_inh_comp_args, tSSS_inh_comp_args, ) """ ### Dioctahedral vermiculites: """ V_code_AD = 'dV2w' V_code_EG = 'dV2g' V_code_350 = 'dVht' V_inh_comp_args = { 'dV2g': dict(linked_with='dV2w', **inherit_S), 'dVht': dict(linked_with='dV2w', **inherit_S), } VV_code_AD = V_code_AD + 'dV1w' VV_code_EG = V_code_EG + 'dV1g' VV_code_350 = V_code_350 + 'dV1g' VV_inh_comp_args = dict(V_inh_comp_args) VV_inh_comp_args.update({ 'dV1g': dict(linked_with='dV1w', **inherit_S), }) VVV_code_AD = VV_code_AD + 'dV0w' VVV_code_EG = VV_code_EG + 'dV0w' VVV_code_350 = VV_code_350 + 'dV0w' VVV_inh_comp_args = dict(VV_inh_comp_args) VVV_inh_comp_args.update({ 'dV0w': dict(linked_with='dV0w', **inherit_S), }) default_phases += [ ('%sVermiculites/Di-Vermiculite Ca.phs', [ (dict(R=0, name='V R0 Ca-AD'), V_code_AD, {}), (dict(R=0, name='V R0 Ca-EG', based_on='V R0 Ca-AD', **inherit_phase), V_code_EG, V_inh_comp_args), (dict(R=0, name='V R0 Ca-350', based_on='V R0 Ca-AD', **inherit_phase), V_code_350, V_inh_comp_args) ]), ] default_phases += generate_expandables( 'Vermiculites/VV/Di-VV R%d Ca.phs', 'VV', 4, {}, inherit_phase, inherit_phase, VV_code_AD, VV_code_EG, VV_code_350, {}, VV_inh_comp_args, VV_inh_comp_args, ) default_phases += generate_expandables( 'Vermiculites/VVV/Di-VVV R%d Ca.phs', 'VVV', 3, {}, inherit_phase, inherit_phase, VVV_code_AD, VVV_code_EG, VVV_code_350, {}, VVV_inh_comp_args, VVV_inh_comp_args, ) """ ### Kaolinite - Smectites: """ K_code = 'K ' K_inh_comp_args = { 'K ': dict(linked_with='K ', **inherit_all), } KS_code_AD = K_code + S_code_AD KS_code_EG = K_code + S_code_EG KS_code_350 = K_code + S_code_350 KS_inh_comp_args = dict(S_inh_comp_args) KS_inh_comp_args.update(K_inh_comp_args) KSS_code_AD = K_code + SS_code_AD KSS_code_EG = K_code + SS_code_EG KSS_code_350 = K_code + SS_code_350 KSS_inh_comp_args = dict(SS_inh_comp_args) KSS_inh_comp_args.update(K_inh_comp_args) KSSS_code_AD = K_code + SSS_code_AD KSSS_code_EG = K_code + SSS_code_EG KSSS_code_350 = K_code + SSS_code_350 KSSS_inh_comp_args = dict(SSS_inh_comp_args) KSSS_inh_comp_args.update(K_inh_comp_args) default_phases += generate_expandables( 'Kaolinite-Smectites/KS/KS R%d Ca.phs', 'KS', 4, {}, inherit_phase, inherit_phase, KS_code_AD, KS_code_EG, KS_code_350, {}, KS_inh_comp_args, KS_inh_comp_args, ) default_phases += generate_expandables( 'Kaolinite-Smectites/KSS/KSS R%d Ca.phs', 'KSS', 3, {}, inherit_phase, inherit_phase, KSS_code_AD, KSS_code_EG, KSS_code_350, {}, KSS_inh_comp_args, KSS_inh_comp_args, ) default_phases += generate_expandables( 'Kaolinite-Smectites/KSSS/KSSS R%d Ca.phs', 'KSSS', 2, {}, inherit_phase, inherit_phase, KSSS_code_AD, KSSS_code_EG, KSSS_code_350, {}, KSSS_inh_comp_args, KSSS_inh_comp_args, ) """ ### Illite - Smectites: """ I_code = 'I ' I_inh_comp_args = { 'I ': dict(linked_with='I ', **inherit_all), } IS_code_AD = I_code + S_code_AD IS_code_EG = I_code + S_code_EG IS_code_350 = I_code + S_code_350 IS_inh_comp_args = dict(S_inh_comp_args) IS_inh_comp_args.update(I_inh_comp_args) ISS_code_AD = I_code + SS_code_AD ISS_code_EG = I_code + SS_code_EG ISS_code_350 = I_code + SS_code_350 ISS_inh_comp_args = dict(SS_inh_comp_args) ISS_inh_comp_args.update(I_inh_comp_args) ISSS_code_AD = I_code + SSS_code_AD ISSS_code_EG = I_code + SSS_code_EG ISSS_code_350 = I_code + SSS_code_350 ISSS_inh_comp_args = dict(SSS_inh_comp_args) ISSS_inh_comp_args.update(I_inh_comp_args) default_phases += generate_expandables( 'Illite-Smectites/IS/IS R%d Ca.phs', 'IS', 4, {}, inherit_phase, inherit_phase, IS_code_AD, IS_code_EG, IS_code_350, {}, IS_inh_comp_args, IS_inh_comp_args, ) default_phases += generate_expandables( 'Illite-Smectites/ISS/ISS R%d Ca.phs', 'ISS', 3, {}, inherit_phase, inherit_phase, ISS_code_AD, ISS_code_EG, ISS_code_350, {}, ISS_inh_comp_args, ISS_inh_comp_args, ) default_phases += generate_expandables( 'Illite-Smectites/ISSS/ISSS R%d Ca.phs', 'ISSS', 2, {}, inherit_phase, inherit_phase, ISSS_code_AD, ISSS_code_EG, ISSS_code_350, {}, ISSS_inh_comp_args, ISSS_inh_comp_args, ) """ ### Chlorite - Smectites: """ C_code = 'C ' C_inh_comp_args = { 'C ': dict(linked_with='C ', **inherit_all), } CS_code_AD = C_code + tS_code_AD CS_code_EG = C_code + tS_code_EG CS_code_350 = C_code + tS_code_350 CS_inh_comp_args = dict(tS_inh_comp_args) CS_inh_comp_args.update(C_inh_comp_args) CSS_code_AD = C_code + tSS_code_AD CSS_code_EG = C_code + tSS_code_EG CSS_code_350 = C_code + tSS_code_350 CSS_inh_comp_args = dict(tSS_inh_comp_args) CSS_inh_comp_args.update(C_inh_comp_args) CSSS_code_AD = C_code + tSSS_code_AD CSSS_code_EG = C_code + tSSS_code_EG CSSS_code_350 = C_code + tSSS_code_350 CSSS_inh_comp_args = dict(tSSS_inh_comp_args) CSSS_inh_comp_args.update(C_inh_comp_args) default_phases += generate_expandables( 'Chlorite-Smectites/CS/CS R%d Ca.phs', 'CS', 4, {}, inherit_phase, inherit_phase, CS_code_AD, CS_code_EG, CS_code_350, {}, CS_inh_comp_args, CS_inh_comp_args, ) default_phases += generate_expandables( 'Chlorite-Smectites/CSS/CSS R%d Ca.phs', 'CSS', 3, {}, inherit_phase, inherit_phase, CSS_code_AD, CSS_code_EG, CSS_code_350, {}, CSS_inh_comp_args, CSS_inh_comp_args, ) default_phases += generate_expandables( 'Chlorite-Smectites/CSSS/CSSS R%d Ca.phs', 'CSSS', 2, {}, inherit_phase, inherit_phase, CSSS_code_AD, CSSS_code_EG, CSSS_code_350, {}, CSSS_inh_comp_args, CSSS_inh_comp_args, ) """ ### Talc - Smectites: """ T_code = 'T ' T_inh_comp_args = { 'T ': dict(linked_with='T ', **inherit_all), } TS_code_AD = T_code + S_code_AD TS_code_EG = T_code + S_code_EG TS_code_350 = T_code + S_code_350 TS_inh_comp_args = dict(S_inh_comp_args) TS_inh_comp_args.update(T_inh_comp_args) TSS_code_AD = T_code + SS_code_AD TSS_code_EG = T_code + SS_code_EG TSS_code_350 = T_code + SS_code_350 TSS_inh_comp_args = dict(SS_inh_comp_args) TSS_inh_comp_args.update(T_inh_comp_args) TSSS_code_AD = T_code + SSS_code_AD TSSS_code_EG = T_code + SSS_code_EG TSSS_code_350 = T_code + SSS_code_350 TSSS_inh_comp_args = dict(SSS_inh_comp_args) TSSS_inh_comp_args.update(T_inh_comp_args) default_phases += generate_expandables( 'Talc-Smectites/TS/TS R%d Ca.phs', 'TS', 4, {}, inherit_phase, inherit_phase, TS_code_AD, TS_code_EG, TS_code_350, {}, TS_inh_comp_args, TS_inh_comp_args, ) default_phases += generate_expandables( 'Talc-Smectites/TSS/TSS R%d Ca.phs', 'TSS', 3, {}, inherit_phase, inherit_phase, TSS_code_AD, TSS_code_EG, TSS_code_350, {}, TSS_inh_comp_args, TSS_inh_comp_args, ) default_phases += generate_expandables( 'Talc-Smectites/TSSS/TSSS R%d Ca.phs', 'TSSS', 2, {}, inherit_phase, inherit_phase, TSSS_code_AD, TSSS_code_EG, TSSS_code_350, {}, TSSS_inh_comp_args, TSSS_inh_comp_args, ) """ ### Illite - Chlorite - Smectites: """ IC_code = I_code + C_code IC_inh_comp_args = dict(I_inh_comp_args) IC_inh_comp_args.update(C_inh_comp_args) ICS_code_AD = IC_code + S_code_AD ICS_code_EG = IC_code + S_code_EG ICS_inh_comp_args = dict(S_inh_comp_args) ICS_inh_comp_args.update(IC_inh_comp_args) ICSS_code_AD = IC_code + SS_code_AD ICSS_code_EG = IC_code + SS_code_EG ICSS_inh_comp_args = dict(SS_inh_comp_args) ICSS_inh_comp_args.update(IC_inh_comp_args) ICSSS_code_AD = IC_code + SSS_code_AD ICSSS_code_EG = IC_code + SSS_code_EG ICSSS_inh_comp_args = dict(SSS_inh_comp_args) ICSSS_inh_comp_args.update(IC_inh_comp_args) default_phases += [ ('%sIllite-Chlorite-Smectites/ICS/ICS R0 Ca.phs', [ (dict(R=0, name='ICS R0 Ca-AD'), ICS_code_AD, {}), (dict(R=0, name='ICS R0 Ca-EG', based_on='ICS R0 Ca-AD', **inherit_phase), ICS_code_EG, ICS_inh_comp_args) ]), ('%sIllite-Chlorite-Smectites/ICS/ICS R1 Ca.phs', [ (dict(R=1, name='ICS R1 Ca-AD'), ICS_code_AD, {}), (dict(R=1, name='ICS R1 Ca-EG', based_on='ICS R1 Ca-AD', **inherit_phase), ICS_code_EG, ICS_inh_comp_args) ]), ('%sIllite-Chlorite-Smectites/ICS/ICS R2 Ca.phs', [ (dict(R=2, name='ICS R2 Ca-AD'), ICS_code_AD, {}), (dict(R=2, name='ICS R2 Ca-EG', based_on='ICS R2 Ca-AD', **inherit_phase), ICS_code_EG, ICS_inh_comp_args) ]), ('%sIllite-Chlorite-Smectites/ICSS/ICSS R0 Ca.phs', [ (dict(R=0, name='ICSS R0 Ca-AD'), ICSS_code_AD, {}), (dict(R=0, name='ICSS R0 Ca-EG', based_on='ICSS R0 Ca-AD', **inherit_phase), ICSS_code_EG, ICSS_inh_comp_args) ]), ('%sIllite-Chlorite-Smectites/ICSS/ICSS R1 Ca.phs', [ (dict(R=1, name='ICSS R1 Ca-AD'), ICSS_code_AD, {}), (dict(R=1, name='ICSS R1 Ca-EG', based_on='ICSS R1 Ca-AD', **inherit_phase), ICSS_code_EG, ICSS_inh_comp_args) ]), ('%sIllite-Chlorite-Smectites/ICSSS/ICSSS R0 Ca.phs', [ (dict(R=0, name='ICSSS R0 Ca-AD'), ICSSS_code_AD, {}), (dict(R=0, name='ICSSS R0 Ca-EG', based_on='ICSSS R0 Ca-AD', **inherit_phase), ICSSS_code_EG, ICSSS_inh_comp_args) ]), ] """ ### Kaolinite - Chlorite - Smectites: """ KC_code = K_code + C_code KC_inh_comp_args = dict(K_inh_comp_args) KC_inh_comp_args.update(C_inh_comp_args) KCS_code_AD = KC_code + S_code_AD KCS_code_EG = KC_code + S_code_EG KCS_inh_comp_args = dict(S_inh_comp_args) KCS_inh_comp_args.update(KC_inh_comp_args) KCSS_code_AD = KC_code + SS_code_AD KCSS_code_EG = KC_code + SS_code_EG KCSS_inh_comp_args = dict(SS_inh_comp_args) KCSS_inh_comp_args.update(KC_inh_comp_args) KCSSS_code_AD = KC_code + SSS_code_AD KCSSS_code_EG = KC_code + SSS_code_EG KCSSS_inh_comp_args = dict(SSS_inh_comp_args) KCSSS_inh_comp_args.update(KC_inh_comp_args) default_phases += [ ('%sKaolinite-Chlorite-Smectites/KCS/KCS R0 Ca.phs', [ (dict(R=0, name='KCS R0 Ca-AD'), KCS_code_AD, {}), (dict(R=0, name='KCS R0 Ca-EG', based_on='KCS R0 Ca-AD', **inherit_phase), KCS_code_EG, KCS_inh_comp_args) ]), ('%sKaolinite-Chlorite-Smectites/KCS/KCS R1 Ca.phs', [ (dict(R=1, name='KCS R1 Ca-AD'), KCS_code_AD, {}), (dict(R=1, name='KCS R1 Ca-EG', based_on='KCS R1 Ca-AD', **inherit_phase), KCS_code_EG, KCS_inh_comp_args) ]), ('%sKaolinite-Chlorite-Smectites/KCS/KCS R2 Ca.phs', [ (dict(R=2, name='KCS R2 Ca-AD'), KCS_code_AD, {}), (dict(R=2, name='KCS R2 Ca-EG', based_on='KCS R2 Ca-AD', **inherit_phase), KCS_code_EG, KCS_inh_comp_args) ]), ('%sKaolinite-Chlorite-Smectites/KCSS/KCSS R0 Ca.phs', [ (dict(R=0, name='KCSS R0 Ca-AD'), KCSS_code_AD, {}), (dict(R=0, name='KCSS R0 Ca-EG', based_on='KCSS R0 Ca-AD', **inherit_phase), KCSS_code_EG, KCSS_inh_comp_args) ]), ('%sKaolinite-Chlorite-Smectites/KCSS/KCSS R1 Ca.phs', [ (dict(R=1, name='KCSS R1 Ca-AD'), KCSS_code_AD, {}), (dict(R=1, name='KCSS R1 Ca-EG', based_on='KCSS R1 Ca-AD', **inherit_phase), KCSS_code_EG, KCSS_inh_comp_args) ]), ('%sKaolinite-Chlorite-Smectites/KCSSS/KCSSS R0 Ca.phs', [ (dict(R=0, name='KCSSS R0 Ca-AD'), KCSSS_code_AD, {}), (dict(R=0, name='KCSSS R0 Ca-EG', based_on='KCSSS R0 Ca-AD', **inherit_phase), KCSSS_code_EG, KCSSS_inh_comp_args) ]), ] """ ### Actual object generation routine: """ import queue import threading def ioworker(in_queue, stop): """ Saves Phase objects from the in_queue. If the Queue is empty this function will only stop if the 'stop' event is set. """ while True: try: phases_path, phases = in_queue.get(timeout=0.5) create_dir_recursive(phases_path) Phase.save_phases(phases, phases_path) in_queue.task_done() except queue.Empty: if not stop.is_set(): continue else: return save_queue = queue.Queue() io_stop = threading.Event() iothread = threading.Thread(target=ioworker, args=(save_queue, io_stop)) iothread.start() def phaseworker(in_queue, save_queue, stop): """ Parses Phase descriptions into actual objects and passes them to the save_queue. 'stop' should be a threading.Event() that should be toggled once all elements have been Queued. This way, the worker will only stop once the Queue is really empty, and not when it's processing faster than the Queue can be filled. """ while True: try: phases_path, phase_descr = in_queue.get(timeout=0.5) project = Project() phase_lookup = {} component_lookup = {} for phase_kwargs, code, comp_props in phase_descr: # create phase: G = len(code) / code_length based_on = None if "based_on" in phase_kwargs: based_on = phase_lookup.get(phase_kwargs.pop("based_on"), None) phase = Phase(G=G, parent=project, **phase_kwargs) phase.based_on = based_on phase_lookup[phase.name] = phase # derive upper and lower limits for the codes using code lengths: limits = list(zip( list(range(0, len(code), code_length)), list(range(code_length, len(code) + 1, code_length)) )) # create components: phase.components[:] = [] for ll, ul in limits: part = code[ll: ul] for component in Component.load_components(aliases[part] % (settings.DATA_REG.get_directory_path("DEFAULT_COMPONENTS") + "/"), parent=phase): component.resolve_json_references() phase.components.append(component) props = comp_props.get(part, {}) for prop, value in props.items(): if prop == 'linked_with': value = component_lookup[value] setattr(component, prop, value) component_lookup[part] = component # put phases on the save queue: phases_path = phases_path % (settings.DATA_REG.get_directory_path("DEFAULT_PHASES") + "/") save_queue.put((phases_path, list(phase_lookup.values()))) # Flag this as finished in_queue.task_done() except queue.Empty: if not stop.is_set(): continue else: return phase_queue = queue.Queue() phase_stop = threading.Event() phasethread = threading.Thread(target=phaseworker, args=(phase_queue, save_queue, phase_stop)) phasethread.start() # Queue phases: for phases_path, phase_descr in default_phases: phase_queue.put((phases_path, phase_descr)) # Signal phaseworker it can stop if the phase_queue is emptied: phase_stop.set() while phasethread.is_alive(): # Try to join the thread, but don't block, inform the UI # of our progress if a callback is provided: phasethread.join(timeout=0.1) if callable(ui_callback): progress = float(len(default_phases) - phase_queue.qsize()) / float(len(default_phases)) ui_callback(progress) if callable(ui_callback): ui_callback(1.0) # Signal the IO worker the phaseworker has stopped, so it can stop # if the save_queue is empty io_stop.set() while iothread.is_alive(): # Try to join the thread, but don't block iothread.join(timeout=0.1) pass # end of run def create_dir_recursive(path): """ Creates the path 'path' recursively. """ to_create = [] while not os.path.exists(path): to_create.insert(0, path) path = os.path.dirname(path) for path in to_create[:-1]: os.mkdir(path) PyXRD-0.8.4/pyxrd/scripts/import_from_xml.py000066400000000000000000000017631363064711000211250ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os, sys from pyxrd.project.models import Project def run(args): # generates a project file containing the phases as described by the Sybilla XML output: if args and args.filename != "": # Import: project = Project.create_from_sybilla_xml(args.filename) # Save this right away: project_filename = "%s/%s" % (os.path.dirname(args.filename), os.path.basename(args.filename).replace(".xml", ".pyxrd", 1)) from pyxrd.file_parsers.json_parser import JSONParser JSONParser.write(project, project_filename, zipped=True) # Relaunch processs args = [sys.argv[0], project_filename, ] args.insert(0, sys.executable) if sys.platform == 'win32': args = ['"%s"' % arg for arg in args] os.execv(sys.executable, args) sys.exit(0) PyXRD-0.8.4/pyxrd/scripts/interpolate_new_asf.py000066400000000000000000000026071363064711000217360ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import numpy as np from scipy.optimize import fmin_l_bfgs_b as optim asf1 = np.array([0.126842, 4.708971, 1.194814, 1.558157, 1.170413, 3.239403, 4.875207, 108.506081, 0.111516, 48.292408, 1.928171]) asf2 = np.array([0.058851, 3.062918, 4.135106, 0.853742, 1.036792, 0.85252, 2.015803, 4.417941, 0.065307, 9.66971, 0.187818]) def func(asf, stl_range): f = np.zeros(stl_range.shape) for i in range(1, 6): f += asf[i] * np.exp(-asf[5 + i] * (stl_range) ** 2) f += asf[0] return f stl_range = np.arange(0, 1, 0.01) expected1 = func(asf1, stl_range) expected2 = func(asf2, stl_range) expected = (expected1 + expected2) / 2.0 def calculate_R2(x0, *args): global stl_range global expected calculated = func(x0, stl_range) return np.sum((calculated - expected) ** 2) bounds = [ (0, None), (None, None), (None, None), (None, None), (None, None), (None, None), (0.001, None), (0.001, None), (0.001, None), (0.001, None), (0.001, None), ] x0 = asf2 lastx, lastR2, info = optim(calculate_R2, x0, approx_grad=True, pgtol=10e-24 , factr=2, iprint=-1, bounds=bounds) print(lastR2) print("\t".join([("%.10g" % fl).replace(".", ",") for fl in lastx])) print(info) PyXRD-0.8.4/pyxrd/scripts/refinement_generator.py000066400000000000000000000145211363064711000221060ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. B_DO_PROFILING = False if B_DO_PROFILING: import cProfile, pstats import logging logger = logging.getLogger(__name__) import multiprocessing import os import codecs import numpy as np """ ADDED 0.01 NOISE """ def get_uniform_solutions(refiner, num): """ Returns `num` solutions (uniformly distributed within their ranges) for the selected parameters. """ start_solutions = np.random.random_sample((num, len(refiner.ref_props))) ranges = np.asarray(refiner.ranges, dtype=float) return ranges[:, 0] + start_solutions * (ranges[:, 1] - ranges[:, 0]) def run(args): """ This is a simple script that will open a PyXRD project file, will run a refinement for a certain mixture, and store the results in an overview file and the best solution as a new project file. The refinement setup is left unchanged, so be sure you have correctly defined parameter ranges and chosen a good refinement strategy (CMA-ES is recommended). To use this script, launch it using PyXRD's core.py launcher script as: python core.py -s pyxrd/scripts/refinement_generator.py "$FILENAME###$I###$J" in which: - $FILENAME can be replaced with the absolute path to the actual project filename - $I is the index of the mixture to refine and - $J is the 'trial' number, which is added to the record file and to the best solution project file. - leave the three # (hashes) where they are, they are used as separators You can use this script (e.g. on high-performance computing clusters) to run several iterations of the same project. Reasaons why you would want to do this are for benchmarking, checking solution reliability, ... Just change the trial number from e.g. 0 to 49 to have 50 iterations. """ ## TODO: ## - use a uniform distribution of starting solutions: ## xs = np.random.uniform(size=50) ## ys = np.random.uniform(size=50) ## zs = np.random.uniform(size=50) ## ## When the jobs are submitted, load the project and mixture once, ## create the # of staring solutions and store them in a file (using np IO) ## Then here we can load them and pick the one we need. ## if args and args.filename != "": logging.info("Proccessing args...") project_file, k, mixture_index = tuple(args.filename.split("###", 2)) base_path = os.path.dirname(args.filename) start_solutions_fname = os.path.join( base_path, "start_solutions %s mixture %s" % (os.path.basename(project_file), mixture_index) ) stop_event = multiprocessing.Event() logging.info("Loading project file...") from pyxrd.file_parsers.json_parser import JSONParser project = JSONParser.parse(project_file) logging.info(" ".join(["Running Project", os.path.basename(project_file), "Trial", k])) for i, mixture in enumerate(project.mixtures): if i == int(mixture_index): if B_DO_PROFILING: pr = cProfile.Profile() pr.enable() try: with mixture.data_changed.hold(): mixture.refinement.update_refinement_treestore() refiner = mixture.refinement.get_refiner() if int(k) == 0: #First run, create solutions & store for later use: start_solutions = get_uniform_solutions(refiner, 50) np.savetxt(start_solutions_fname, start_solutions) else: start_solutions = np.loadtxt(start_solutions_fname) refiner.update(start_solutions[k, ...], iteration=-1) mixture.optimizer.optimize() refiner.refine(stop_event) except: raise finally: if B_DO_PROFILING: pr.disable() with open("pyxrd_stats", "w+") as f: sortby = 'cumulative' ps = pstats.Stats(pr, stream=f).sort_stats(sortby) ps.print_stats() recordf = os.path.basename(project_file).replace(".pyxrd", "") recordf = base_path + "/" + "record#" + str(k) + " " + recordf + " " + mixture.name with codecs.open(recordf, 'w', 'utf-8') as f: f.write("################################################################################\n") f.write(recordf + "\n") f.write("Mixture " + str(i) + " and trial " + str(k) + "\n") f.write("Property name, initial, best, min, max" + "\n") for j, ref_prop in enumerate(refiner.refinable_properties): line = ", ".join([ ref_prop.get_descriptor(), str(refiner.history.initial_solution[j]), str(refiner.history.best_solution[j]), str(ref_prop.value_min), str(ref_prop.value_max), ]) f.write(line + "\n") f.write("################################################################################\n") def write_records(f, record_header, records): f.write(", ".join(record_header) + "\n") for record in records: f.write(", ".join(["%.7f" % f for f in record]) + "\n") f.write("################################################################################\n") # Apply found solution and save: refiner.apply_best_solution() mixture.optimizer.optimize() project_file_output = base_path + "/" + os.path.basename(project_file).replace(".pyxrd", "") + " - mixture %s - trial %s.pyxrd" % (str(i), str(k)) JSONParser.write(project, project_file_output, zipped=True) pass # end PyXRD-0.8.4/pyxrd/scripts/test_gtk_less.py000066400000000000000000000004531363064711000205550ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pyxrd.project.models import Project def run(args): project = Project(name="Test") project.name = "Test123" pass PyXRD-0.8.4/pyxrd/scripts/tools.py000066400000000000000000000021531363064711000170420ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. """ Tools for making scripting easier """ def reload_settings(clear_script=True): """ This will reload the PyXRD settings after clearing the script path from the command line arguments. This allows to run the GUI when needed. """ import sys from copy import copy # Make a copy to prevent errors args = copy(sys.argv) for i, arg in enumerate(args): if arg == "-s": # Clear the next key (contains the script name del sys.argv[i + 1] # Clear the flag del sys.argv[i] # Exit the loop break from pyxrd.data import settings settings.SETTINGS_APPLIED = False # clear this flag to reload settings settings.initialize() # reload settings def launch_gui(project=None): """ Launches the GUI, you should run reload_settings before calling this! """ from pyxrd.core import _run_gui _run_gui(project=project) # launch guiPyXRD-0.8.4/pyxrd/server/000077500000000000000000000000001363064711000151465ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/server/__init__.py000066400000000000000000000000001363064711000172450ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/server/kill_server.py000066400000000000000000000010071363064711000200370ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) from traceback import print_exc from . import settings import Pyro4 if __name__ == "__main__": try: server = Pyro4.Proxy("PYRONAME:%s" % settings.PYRO_NAME) server.shutdown() except: logging.error("Error when trying to shut down Pyro server!") print_exc()PyXRD-0.8.4/pyxrd/server/provider.py000066400000000000000000000137671363064711000173700ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import time, atexit, os from traceback import print_exc import logging logger = logging.getLogger(__name__) import Pyro4 import Pyro4.naming try: import threading as threading except ImportError: import dummy_threading as threading try: from fastrlock.rlock import FastRLock as RLock except ImportError: from threading import RLock from pyxrd.generic.asynchronous.exceptions import * from pyxrd.data.appdirs import user_log_dir from . import settings from .utils import start_script from .status_thread import StatusThread class Pyro4AsyncServerProvider(object): """ Provider for the Pyro4 PyXRD server """ _STATUS_NS_NOT_RUNNING = ("#FFFF00", "Nameserver Error", "Pyro4 Nameserver not running") _STATUS_ERR_NS_RUNNING = ("#FF0000", "Nameserver Exception", "Exception when checking if Pyro4 Nameserver is running") _STATUS_NS_NOT_LISTED = ("#FFFF00", "Nameserver Error", "Pyro4 PyXRD server not listed") _STATUS_ERR_NS_LISTED = ("#FF0000", "Nameserver Exception", "Exception when checking if Pyro4 PyXRD server is listed") _STATUS_NO_CONN_PYXRD_SERVER = ("#FFFF00", "PyXRD Server Error", "Cannot connect to Pyro4 PyXRD server") _STATUS_ERR_CONN_PYXRD_SERVER = ("#FF0000", "PyXRD Server Exception", "Exception when connecting to Pyro4 PyXRD server") _STATUS_SUCCESS = ("#00FF00", "Connected (Pyro4)", "Succesfully connected to Pyro4 PyXRD Server") _updater = None status = _STATUS_NS_NOT_RUNNING status_lock = RLock() NS_CACHE_TIMEOUT = 30 ns = None ns_ts = 0 @classmethod def _locate_ns(cls): if cls.ns is None or time.time() - cls.ns_ts >= cls.NS_CACHE_TIMEOUT: cls.ns = Pyro4.naming.locateNS() cls.ns_ts = time.time() return cls.ns PROXY_CACHE_TIMEOUT = 30 proxy = None proxy_ts = 0 @classmethod def _get_proxy(cls): if cls.proxy is None or time.time() - cls.proxy_ts >= cls.PROXY_CACHE_TIMEOUT: if cls.proxy is None: cls.proxy = Pyro4.Proxy("PYRONAME:%s" % settings.PYRO_NAME) else: cls.proxy.pyroBind() cls.proxy_ts = time.time() return cls.proxy @classmethod def check_nameserver_alive(cls): try: ns = cls._locate_ns() ns.ping() return True except: print_exc() return False @classmethod def check_server_is_listed(cls): try: ns = cls._locate_ns() objects = ns.list() if settings.PYRO_NAME in objects: return True else: return False except: print_exc() return False """ Async Provider Implementation: """ @classmethod def get_status(cls): """ Should return a three-tuple consisting of the status colour, label and a description: ("#FF0000", "Error", "Nameserver not running") Status is updated periodically. """ # first call: if cls._updater == None: cls._update_status(cls._get_status()) cls._updater = StatusThread(5, cls) cls._updater.setDaemon(True) cls._updater.start() return cls.status @classmethod def _update_status(cls, status): with cls.status_lock: cls.status = status @classmethod def _get_status(cls): try: if not cls.check_nameserver_alive(): return cls._STATUS_NS_NOT_RUNNING except: print_exc() return cls._STATUS_ERR_NS_RUNNING try: if not cls.check_server_is_listed(): return cls._STATUS_NS_NOT_LISTED except: print_exc() return cls._STATUS_ERR_NS_LISTED try: if not cls.check_server_is_alive(): return cls._STATUS_NO_CONN_PYXRD_SERVER except: print_exc() return cls._STATUS_ERR_CONN_PYXRD_SERVER return cls._STATUS_SUCCESS @classmethod def check_server_is_alive(cls): try: server = cls.get_server(auto_start=False) return server.loopCondition() except: logging.info("Pyro4 PyXRD server not (yet) running!") return False @classmethod def get_server(cls, auto_start=True): if auto_start: cls.launch_server() try: return Pyro4.Proxy("PYRONAME:%s" % settings.PYRO_NAME) except: print_exc() logging.error("Could not connect to Pyro4 PyXRD server.") raise ServerNotRunningException("Pyro4 PyXRD Server is not running!") @classmethod def launch_server(cls): if not cls.check_server_is_alive(): log_file = os.path.join(user_log_dir('PyXRD'), 'server.log') start_script("run_server.py", auto_kill=not settings.KEEP_SERVER_ALIVE, log_file=log_file) ttl = 15 delay = 0.2 while not cls.check_server_is_alive(): time.sleep(delay) # wait ttl -= 1 if ttl == 0: raise ServerStartTimeoutExcecption("Pyro4 PyXRD Server is not running!") logging.info("Pyro4 PyXRD server is running!") if not settings.KEEP_SERVER_ALIVE: atexit.register(cls.stop_server) @classmethod def stop_server(cls): try: server = cls.get_server(auto_start=False) server.shutdown() except: logging.error("Error when trying to shut down Pyro4 PyXRD server!") print_exc() raise ServerNotRunningException("Pyro4 PyXRD Server is not running!") pass #end of class PyXRD-0.8.4/pyxrd/server/pyxrd_server.py000066400000000000000000000053511363064711000202600ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import sys, os import logging logger = logging.getLogger(__name__) import multiprocessing def _worker_initializer(pool_stop, debug, *args): # Spoof command line arguments so settings are loaded with correct # debugging flag if debug and not "-d" in sys.argv: sys.argv.insert(1, "-d") if not debug and "-d" in sys.argv: sys.argv.remove("-d") # Load settings from pyxrd.data import settings settings.initialize() if settings.DEBUG: from pyxrd import stacktracer stacktracer.trace_start( "trace-worker-%s.html" % multiprocessing.current_process().name, interval=5, auto=True) # Set auto flag to always update file! logger.info("Worker process initialized, DEBUG=%s" % debug) pass class PyXRDServer(object): pool = None pool_stop = None running = True def loopCondition(self): return self.running def __init__(self): from pyxrd.data import settings settings.initialize() logger.warning("Creating pool, DEBUG=%s" % settings.DEBUG) self.pool_stop = multiprocessing.Event() self.pool_stop.clear() maxtasksperchild = 10 if 'nt' == os.name else None self.pool = multiprocessing.Pool( initializer=_worker_initializer, maxtasksperchild=maxtasksperchild, initargs=( self.pool_stop, settings.DEBUG, ) ) # register the shutdown settings.FINALIZERS.append(self.shutdown) def submit(self, func): """ The callback 'func' will be submitted to a multiprocessing pool created by the server. The result object will be returned. """ result = self.pool.apply_async(func) self._pyroDaemon.register(result) return result def submit_sync(self, func): """ This will run the 'func' callback directly on the server process. Use this with care as it will block the server. Can be used to pass in a full project refinement using the pyxrd.calculations.run_refinement method. """ result = func() self._pyroDaemon.register(result) return result def shutdown(self): """ Shuts down the server. """ # Close the pool: logging.info("Closing multiprocessing pool ...") if self.pool is not None: self.pool_stop.set() self.pool.close() self.pool.join() self.running = False pass #end of class PyXRD-0.8.4/pyxrd/server/run_server.py000066400000000000000000000026031363064711000177130ustar00rootroot00000000000000#!/usr/bin/env python3 # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. if __name__ == "__main__": import logging logger = logging.getLogger(__name__) import os, sys sys.path.insert(1, os.path.join(sys.path[0], '../..')) from pyxrd.data.appdirs import user_log_dir from pyxrd.server import settings from pyxrd.server.pyxrd_server import PyXRDServer from pyxrd.server.utils import start_script from pyxrd.logs import setup_logging setup_logging(basic=True, prefix="PYRO SERVER:") import Pyro4 try: from Pyro4.naming import NamingError except (AttributeError, ImportError): from Pyro4.errors import NamingError server = PyXRDServer() daemon = Pyro4.Daemon() try: ns = Pyro4.locateNS() except NamingError: logger.info("NamingError encountered when trying to locate the nameserver") log_file = os.path.join(user_log_dir('PyXRD'), 'nameserver.log') start_script("start_nameserver.py", auto_kill=not settings.KEEP_SERVER_ALIVE, log_file=log_file) ns = Pyro4.locateNS() server_uri = daemon.register(server) ns.register(settings.PYRO_NAME, server_uri) # settings.PYRO_NAME) try: daemon.requestLoop(server.loopCondition) finally: daemon.shutdown() PyXRD-0.8.4/pyxrd/server/settings.py000066400000000000000000000010541363064711000173600ustar00rootroot00000000000000 import Pyro4 Pyro4.config.SERIALIZERS_ACCEPTED = ["json", "marshal", "serpent", "pickle"] Pyro4.config.SERIALIZER = "pickle" Pyro4.config.PICKLE_PROTOCOL_VERSION = 3 Pyro4.config.COMPRESSION = True Pyro4.config.SERVERTYPE = "multiplex" Pyro4.config.COMMTIMEOUT = 3.5 Pyro4.config.REQUIRE_EXPOSE = False Pyro4.config.SOCK_REUSE = True import platform if platform.system() == "Windows" and float(platform.release()) >= 6: USE_MSG_WAITALL = True PYRO_NAME = "pyxrd.server" KEEP_SERVER_ALIVE = False # setting this to false may produce unwanted results!PyXRD-0.8.4/pyxrd/server/start_nameserver.py000066400000000000000000000010201363064711000210750ustar00rootroot00000000000000#!/usr/bin/env python3 # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. if __name__ == "__main__": # Add parent dir to the path: import os, sys sys.path.insert(1, os.path.join(sys.path[0], '../..')) from pyxrd.logs import setup_logging from pyxrd.server import settings # @UnusedImport setup_logging(basic=True, prefix="PYRO NAMESERVER:") import Pyro4.naming Pyro4.naming.startNSloop() PyXRD-0.8.4/pyxrd/server/status_thread.py000066400000000000000000000012601363064711000203710ustar00rootroot00000000000000import threading import time class StatusThread(threading.Thread): def __init__(self, interval, provider): """ @param interval: In seconds: how often to update the status. """ assert(interval > 0.1) self.interval = interval self.stop_requested = threading.Event() self.provider = provider threading.Thread.__init__(self) def run(self): while not self.stop_requested.isSet(): time.sleep(self.interval) self.provider._update_status(self.provider._get_status()) def stop(self): self.stop_requested.set() self.join() pass #end of classPyXRD-0.8.4/pyxrd/server/utils.py000066400000000000000000000022641363064711000166640ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import sys, os, time, subprocess, platform import signal import atexit pythonexe = "python3" if platform.system() == "Windows": pythonexe = "python3w" def kill_child(child_pid): if child_pid is None: pass else: os.kill(child_pid, signal.SIGTERM) def start_script(local_script_name, auto_kill=True, log_file=None): global pythonexe if hasattr(sys, "frozen"): module_path = os.path.dirname(sys.executable) else: module_path = os.path.dirname(__file__) path = os.path.join(module_path, local_script_name) logging.info("Starting server using script: '%s', logging to '%s'" % (path, log_file)) log_file = log_file if log_file is not None else os.devnull with open(log_file, 'w') as output: proc = subprocess.Popen([pythonexe, path], stdout=output) # Register this child pid to be killed when the parent dies: if auto_kill: atexit.register(kill_child, proc.pid) # Give it a sec time.sleep(1) PyXRD-0.8.4/pyxrd/specimen/000077500000000000000000000000001363064711000154435ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/specimen/__init__.py000066400000000000000000000000001363064711000175420ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/specimen/controllers/000077500000000000000000000000001363064711000200115ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/specimen/controllers/__init__.py000066400000000000000000000015371363064711000221300ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .specimen_controllers import SpecimenController, StatisticsController from .marker_controllers import ( MarkersController, EditMarkerController, MatchMineralController, ThresholdController ) from pyxrd.generic.controllers.line_controllers import ( BackgroundController, SmoothDataController, AddNoiseController, ShiftDataController, StripPeakController ) __all__ = [ "SpecimenController", "StatisticsController", "MarkersController", "EditMarkerController", "MatchMineralController", "ThresholdController", "BackgroundController", "SmoothDataController", "AddNoiseController", "ShiftDataController", "StripPeakController", ] PyXRD-0.8.4/pyxrd/specimen/controllers/marker_controllers.py000066400000000000000000000405121363064711000242740ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from contextlib import contextmanager from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory from mvc import Controller from pyxrd.generic.plot.eye_dropper import EyeDropper from pyxrd.generic.plot.draggables import DraggableVLine from pyxrd.generic.controllers import DialogController, BaseController, ObjectListStoreController from pyxrd.generic.views.treeview_tools import setup_treeview, new_text_column, new_toggle_column from pyxrd.generic.io.utils import get_case_insensitive_glob from pyxrd.specimen.models import Marker, ThresholdSelector, MineralScorer from pyxrd.specimen.views import ( EditMarkerView, DetectPeaksView, MatchMineralsView ) from pyxrd.generic.utils import not_none from pyxrd.data import settings class EditMarkerController(BaseController): def register_view(self, view): self.update_sensitivities() self.update_nanometer() def update_sensitivities(self): for name in ("style", "base", "align", "top", "color"): wid = self.view["marker_%s" % name] wid.set_sensitive(not getattr(self.model, "inherit_%s" % name)) for name in ("angle", "top_offset"): self.view["spb_%s" % name].set_sensitive(not getattr(self.model, "inherit_%s" % name)) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @Controller.observe("position", assign=True, after=True) def notif_parameter_changed(self, model, prop_name, info): if prop_name == "position": self.update_nanometer() @Controller.observe("inherit_style", assign=True) @Controller.observe("inherit_align", assign=True) @Controller.observe("inherit_base", assign=True) @Controller.observe("inherit_top", assign=True) @Controller.observe("inherit_top_offset", assign=True) @Controller.observe("inherit_angle", assign=True) @Controller.observe("inherit_color", assign=True) def notif_angle_toggled(self, model, prop_name, info): self.update_sensitivities() # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_nanometer_changed(self, widget): try: position = float(widget.get_value()) except ValueError: logger.exception("User set nanometers to an invalid value: %s", widget.get_value()) else: self.model.set_nm_position(position) pass def update_nanometer(self): self.view["entry_nanometer"].set_value(self.model.get_nm_position()) def on_sample_clicked(self, widget): def click_callback(x_pos, event): if self.edc is not None: self.edc.disconnect() self.view.get_toplevel().present() if x_pos != -1: self.model.position = x_pos self.edc = EyeDropper( self.parent.plot_controller, click_callback ) self.view.get_toplevel().hide() self.parent.view.get_toplevel().present() class MarkersController(ObjectListStoreController): """ Controller for the markers list """ file_filters = ("Marker file", get_case_insensitive_glob("*.MRK")), treemodel_property_name = "markers" treemodel_class_type = Marker columns = [ (" ", "c_visible"), ("Marker label", "c_label") ] delete_msg = "Deleting a marker is irreversible!\nAre You sure you want to continue?" obj_type_map = [ (Marker, EditMarkerView, EditMarkerController), ] title = "Edit Markers" def get_markers_tree_model(self, *args): return self.treemodel def setup_treeview_col_c_visible(self, treeview, name, col_descr, col_index, tv_col_nr): def toggle_renderer(column, cell, model, itr, data=None): try: col = column.get_col_attr("active") value = model.get_value(itr, col) cell.set_property('active', not_none(value, False)) except TypeError: if settings.DEBUG: raise pass col = new_toggle_column(" ", toggled_callback=(self.on_marker_visible_toggled, (treeview.get_model(), col_index)), data_func=toggle_renderer, resizable=False, expand=False, activatable=True, active_col=col_index) setattr(col, "colnr", col_index) treeview.append_column(col) return True def select_markers(self, markers): self.set_selected_objects() @contextmanager def _multi_operation_context(self): with self.model.visuals_changed.hold(): yield # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_load_object_clicked(self, event): def on_accept(dialog): with self._multi_operation_context(): for marker in Marker.get_from_csv(dialog.filename, self.model): self.model.markers.append(marker) DialogFactory.get_load_dialog( "Import markers", parent=self.view.get_top_widget(), filters=self.file_filters ).run(on_accept) def on_save_object_clicked(self, event): def on_accept(dialog): Marker.save_as_csv(dialog.filename, self.get_selected_objects()) DialogFactory.get_save_dialog( "Export markers", parent=self.view.get_top_widget(), filters=self.file_filters ).run(on_accept) def create_new_object_proxy(self): return Marker(label="New Marker", parent=self.model) def on_marker_visible_toggled(self, cell, path, model, colnr): if model is not None: itr = model.get_iter(path) model.set_value(itr, colnr, not cell.get_active()) return True return False @BaseController.status_message("Finding peaks...", "find_peaks") def on_find_peaks_clicked(self, widget): def after_cb(threshold): self.model.auto_add_peaks(threshold) sel_model = ThresholdSelector(parent=self.model) sel_view = DetectPeaksView(parent=self.view) sel_ctrl = ThresholdController(model=sel_model, view=sel_view, parent=self, callback=after_cb) #@UnusedVariable show_threshold_plot = DialogFactory.get_progress_dialog( action=sel_model.update_threshold_plot_data, complete_callback=lambda *a, **k: sel_view.present(), gui_message="Finding peaks {progress:.0f}%...", toplevel=self.view.get_top_widget() ) if len(self.model.markers) > 0: def on_accept(dialog): self.model.clear_markers() show_threshold_plot() def on_reject(dialog): show_threshold_plot() DialogFactory.get_confirmation_dialog( "Do you want to clear the current markers for this pattern?", parent=self.view.get_top_widget() ).run(on_accept, on_reject) else: show_threshold_plot() def on_match_minerals_clicked(self, widget): def apply_cb(matches): with self._multi_operation_context(): for name, abbreviation, peaks, matches, score in matches: #@UnusedVariable for marker in self.get_selected_objects(): for mpos, epos in matches: #@UnusedVariable if marker.get_nm_position() * 10. == epos: marker.label += ", %s" % abbreviation def close_cb(): self.model.visuals_changed.emit() self.view.show() marker_peaks = [] # position, intensity for marker in self.get_selected_objects(): intensity = self.model.experimental_pattern.get_y_at_x( marker.position) marker_peaks.append((marker.get_nm_position() * 10., intensity)) scorer_model = MineralScorer(marker_peaks=marker_peaks, parent=self.model) scorer_view = MatchMineralsView(parent=self.view) scorer_ctrl = MatchMineralController(model=scorer_model, view=scorer_view, parent=self, apply_callback=apply_cb, close_callback=close_cb) #@UnusedVariable self.view.hide() scorer_view.present() pass # end of class class MatchMineralController(DialogController): apply_callback = None close_callback = None # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def __init__(self, model, view, spurious=False, auto_adapt=False, parent=None, apply_callback=None, close_callback=None): DialogController.__init__(self, model=model, view=view, spurious=spurious, auto_adapt=auto_adapt, parent=parent) self.apply_callback = apply_callback self.close_callback = close_callback def register_adapters(self): super(MatchMineralController, self).register_adapters() if self.model is not None: self.reload_minerals() self.reload_matches() def register_view(self, view): super(MatchMineralController, self).register_view(view) if view is not None: # MATCHES Treeview: tv = self.view['tv_matches'] setup_treeview(tv, None, reset=True, on_selection_changed=self.selection_changed, ) tv.append_column(new_text_column( "Name", markup_col=0, xalign=0, )) tv.append_column(new_text_column( "Abbr.", markup_col=1, expand=False, )) def get_value(column, cell, model, itr, *args): value = model.get_value(itr, column.get_col_attr('markup')) try: value = "%.5f" % value except TypeError: value = "" cell.set_property("markup", value) return tv.append_column(new_text_column( "Score", markup_col=4, expand=False, data_func=get_value )) # ALL MINERALS Treeview: tv = self.view['tv_minerals'] setup_treeview(tv, None, reset=True, on_selection_changed=self.selection_changed, ) tv.append_column(new_text_column( "Name", markup_col=0, xalign=0, )) tv.append_column(new_text_column( "Abbr.", markup_col=1, expand=False, )) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @Controller.observe("matches_changed", signal=True) def notif_parameter_changed(self, model, prop_name, info): self.reload_matches() # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def selection_changed(self, selection, *args): if selection.count_selected_rows() >= 1: model, paths = selection.get_selected_rows() itr = model.get_iter(paths[0]) name, _, peaks = model.get(itr, 0, 1, 2) self.model.specimen.mineral_preview = (name, peaks) self.model.specimen.visuals_changed.emit() def on_auto_match_clicked(self, event): self.model.auto_match() def on_add_match_clicked(self, event): selection = self.view.tv_minerals.get_selection() if selection.count_selected_rows() >= 1: model, paths = selection.get_selected_rows() itr = model.get_iter(paths[0]) name, abbreviation, peaks = model.get(itr, 0, 1, 2) self.model.add_match(name, abbreviation, peaks) def on_del_match_clicked(self, event): selection = self.view.tv_matches.get_selection() if selection.count_selected_rows() >= 1: _, paths = selection.get_selected_rows() self.model.del_match(*paths[0]) def on_apply_clicked(self, event): if self.apply_callback is not None and callable(self.apply_callback): self.model.specimen.mineral_preview = None self.apply_callback(self.model.matches) self.view.hide() def on_cancel(self): if self.close_callback is not None and callable(self.close_callback): self.model.specimen.mineral_preview = None self.close_callback() self.view.hide() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def reload_matches(self): if not hasattr(self, 'tv_matches_model'): self.tv_matches_model = Gtk.ListStore(str, str, object, object, float) else: self.tv_matches_model.clear() for name, abbreviation, peaks, matches, score in self.model.matches: self.tv_matches_model.append([name, abbreviation, peaks, matches, score]) tv = self.view.tv_matches tv.set_model(self.tv_matches_model) def reload_minerals(self): if not hasattr(self, 'tv_matches_model'): self.tv_minerals_model = Gtk.ListStore(str, str, object) else: self.tv_minerals_model.clear() for name, abbreviation, peaks in self.model.minerals: self.tv_minerals_model.append([name, abbreviation, peaks]) tv = self.view.tv_minerals tv.set_model(self.tv_minerals_model) pass # end of class class ThresholdController(DialogController): auto_adapt_included = [ "pattern", "sel_threshold", "max_threshold", "sel_num_peaks", "steps" ] callback = None dline = None def __init__(self, *args, **kwargs): callback = kwargs.pop("callback", None) super(ThresholdController, self).__init__(*args, **kwargs) self.callback = callback self.dline = None def update_plot(self): if self.view is not None: self.view.plot.cla() if self.dline is not None: self.dline.disconnect() self.dline = None def dline_cb(x): self.model.sel_threshold = x if self.model is not None and self.model.threshold_plot_data is not None: x, y = self.model.threshold_plot_data self.view.plot.plot(x, y, 'k-') self.line = self.view.plot.axvline(x=self.model.sel_threshold, color="#0000FF", linestyle="-") self.dline = DraggableVLine(self.line, callback=dline_cb, window=self.view.matlib_canvas.get_window()) self.view.plot.set_ylabel('# of peaks', labelpad=1) self.view.plot.set_xlabel('Threshold', labelpad=1) self.view.figure.subplots_adjust(left=0.15, right=0.875, top=0.875, bottom=0.15) self.view.plot.autoscale_view() self.view.matlib_canvas.draw() def register_adapters(self): self.update_plot() # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @Controller.observe("sel_threshold", assign=True) @Controller.observe("threshold_plot_data", assign=True) def notif_parameter_changed(self, model, prop_name, info): self.update_plot() # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_btn_ok_clicked(self, event): if self.callback is not None and callable(self.callback): self.callback(self.model) return DialogController.on_btn_ok_clicked(self, event) pass # end of class PyXRD-0.8.4/pyxrd/specimen/controllers/specimen_controllers.py000066400000000000000000000360451363064711000246240ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os, locale import logging logger = logging.getLogger(__name__) from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory from mvc.adapters.gtk_support.tree_view_adapters import wrap_xydata_to_treemodel from mvc.adapters import DummyAdapter from pyxrd.generic.controllers import BaseController, DialogController, TreeViewMixin from pyxrd.generic.views.treeview_tools import setup_treeview, new_text_column from pyxrd.file_parsers.xrd_parsers import xrd_parsers from pyxrd.file_parsers.exc_parsers import exc_parsers from pyxrd.goniometer.controllers import InlineGoniometerController from pyxrd.generic.controllers.line_controllers import ( LinePropertiesController, BackgroundController, SmoothDataController, AddNoiseController, ShiftDataController, StripPeakController, CalculatePeakPropertiesController ) from pyxrd.generic.views.line_views import ( BackgroundView, SmoothDataView, AddNoiseView, ShiftDataView, StripPeakView, CalculatePeakPropertiesView ) class SpecimenController(DialogController, TreeViewMixin): """ Specimen controller. """ widget_handlers = { 'custom': 'custom_handler', } # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ @staticmethod def custom_handler(self, prop, widget): if prop.label in ("goniometer"): self.gonio_ctrl = InlineGoniometerController(view=self.view.gonio_view, model=self.model.goniometer, parent=self) ad = DummyAdapter(controller=self, prop=prop) # TODO FIXME return ad def setup_experimental_pattern_tree_view(self, store, widget): """ Creates the experimental data TreeView layout and behavior """ setup_treeview(widget, store, on_cursor_changed=self.on_exp_data_tv_cursor_changed, sel_mode='MULTIPLE') store.connect('columns_changed', self.on_exp_columns_changed) self.update_exp_treeview(widget) # Other properties: self.exp_line_ctrl = LinePropertiesController(model=self.model.experimental_pattern, view=self.view.exp_line_view, parent=self) def setup_calculated_pattern_tree_view(self, store, widget): """ Creates the calculated data TreeView layout and behavior """ setup_treeview(widget, store, on_cursor_changed=self.on_exp_data_tv_cursor_changed, sel_mode='NONE') store.connect('columns_changed', self.on_calc_columns_changed) self.update_calc_treeview(widget) # Other properties: self.calc_line_ctrl = LinePropertiesController(model=self.model.calculated_pattern, view=self.view.calc_line_view, parent=self) def setup_exclusion_ranges_tree_view(self, store, widget): """ Creates the exclusion ranges TreeView layout and behavior """ setup_treeview(widget, store, on_cursor_changed=self.on_exclusion_ranges_tv_cursor_changed, sel_mode='MULTIPLE') def data_func(col, cell, model, iter, colnr): cell.set_property("text", "%g" % model.get(iter, colnr)[0]) widget.append_column(new_text_column( 'From [°2θ]', text_col=store.c_x, editable=True, data_func = (data_func, (store.c_x,)), edited_callback=(self.on_xy_data_cell_edited, (self.model.exclusion_ranges, 0)), resizable=True, expand=True)) widget.append_column(new_text_column( 'To [°2θ]', text_col=store.c_y, editable=True, data_func = (data_func, (store.c_y,)), edited_callback=(self.on_xy_data_cell_edited, (self.model.exclusion_ranges, 1)), resizable=True, expand=True)) # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def get_experimental_pattern_tree_model(self): return wrap_xydata_to_treemodel(self.model, type(self.model).experimental_pattern) def get_calculated_pattern_tree_model(self): return wrap_xydata_to_treemodel(self.model, type(self.model).calculated_pattern) def get_exclusion_ranges_tree_model(self): return wrap_xydata_to_treemodel(self.model, type(self.model).exclusion_ranges) #used to keep a permanent fix on a child controller, prevents early GC _child_ctrl_ref = None def update_calc_treeview(self, tv): """ Updates the calculated pattern TreeView layout """ model = self.get_calculated_pattern_tree_model() for column in tv.get_columns(): tv.remove_column(column) def get_num(column, cell, model, itr, col_id): cell.set_property('text', '%.3f' % model.get_value(itr, col_id)) tv.append_column(new_text_column('2θ', data_func=(get_num, (model.c_x,)))) tv.append_column(new_text_column('Cal', data_func=(get_num, (model.c_y,)) )) for i in range(model.get_n_columns() - 2): tv.append_column(new_text_column( self.model.calculated_pattern.get_y_name(i), data_func=(get_num, (i+2,)))) def update_exp_treeview(self, tv): """ Updates the experimental pattern TreeView layout """ model = self.get_experimental_pattern_tree_model() for column in tv.get_columns(): tv.remove_column(column) def get_num(column, cell, model, itr, col_id): cell.set_property('text', '%.3f' % model.get_value(itr, col_id)) n_columns = model.get_n_columns() if n_columns > 2: for i in range(n_columns): tv.append_column(new_text_column( self.model.calculated_pattern.get_y_name(i), editable=True, edited_callback=(self.on_xy_data_cell_edited, (self.model.experimental_pattern, i)), data_func=(get_num, (i,)) )) else: # X Column: tv.append_column(new_text_column( '°2θ', editable=True, data_func=(get_num, (model.c_x,)), edited_callback=(self.on_xy_data_cell_edited, (self.model.experimental_pattern, 0)))) # Y Column: tv.append_column(new_text_column( 'Intensity', editable=True, data_func=(get_num, (model.c_y,)), edited_callback=(self.on_xy_data_cell_edited, (self.model.experimental_pattern, 1)))) def remove_background(self): """ Opens the 'remove background' dialog. """ bg_view = BackgroundView(parent=self.view) self._child_ctrl_ref = BackgroundController(model=self.model.experimental_pattern, view=bg_view, parent=self) bg_view.present() def add_noise(self): """ Opens the 'add noise' dialog. """ an_view = AddNoiseView(parent=self.view) self._child_ctrl_ref = AddNoiseController(model=self.model.experimental_pattern, view=an_view, parent=self) an_view.present() def smooth_data(self): """ Opens the 'smooth data' dialog. """ sd_view = SmoothDataView(parent=self.view) self._child_ctrl_ref = SmoothDataController(model=self.model.experimental_pattern, view=sd_view, parent=self) sd_view.present() def shift_data(self): """ Opens the 'shift data' dialog. """ sh_view = ShiftDataView(parent=self.view) self._child_ctrl_ref = ShiftDataController(model=self.model.experimental_pattern, view=sh_view, parent=self) sh_view.present() def strip_peak(self): """ Opens the 'strip peak' dialog. """ st_view = StripPeakView(parent=self.view) self._child_ctrl_ref = StripPeakController(model=self.model.experimental_pattern, view=st_view, parent=self) st_view.present() def peak_properties(self): """ Opens the 'peak properties' dialog. """ pa_view = CalculatePeakPropertiesView(parent=self.view) self._child_ctrl_ref = CalculatePeakPropertiesController(model=self.model.experimental_pattern, view=pa_view, parent=self) pa_view.present() # ------------------------------------------------------------ # GTK Signal handlers # ------------------------------------------------------------ def on_calc_columns_changed(self, *args, **kwargs): self.update_calc_treeview(self.view["specimen_calculated_pattern"]) def on_exp_columns_changed(self, *args, **kwargs): self.update_exp_treeview(self.view["specimen_experimental_pattern"]) def on_btn_ok_clicked(self, event): self.parent.pop_status_msg('edit_specimen') return super(SpecimenController, self).on_btn_ok_clicked(event) def on_exclusion_ranges_tv_cursor_changed(self, tv): path, col = tv.get_cursor() # @UnusedVariable self.view["btn_del_exclusion_ranges"].set_sensitive(path is not None) return True def on_exp_data_tv_cursor_changed(self, tv): path, col = tv.get_cursor() # @UnusedVariable self.view["btn_del_experimental_data"].set_sensitive(path is not None) return True def on_add_experimental_data_clicked(self, widget): self.model.experimental_pattern.append(0, 0) return True def on_add_exclusion_range_clicked(self, widget): self.model.exclusion_ranges.append(0, 0) return True def on_del_experimental_data_clicked(self, widget): paths = self.get_selected_paths(self.view["specimen_experimental_pattern"]) if paths is not None: self.model.experimental_pattern.remove_from_indeces(*paths) return True def on_del_exclusion_ranges_clicked(self, widget): paths = self.get_selected_paths(self.view["specimen_exclusion_ranges"]) if paths is not None: self.model.exclusion_ranges.remove_from_indeces(*paths) return True def on_xy_data_cell_edited(self, cell, path, new_text, model, col): try: value = float(locale.atof(new_text)) except ValueError: logger.exception("ValueError: Invalid literal for float(): '%s'" % new_text) else: model.set_value(int(path), col, value) return True def on_import_exclusion_ranges_clicked(self, widget, data=None): def on_confirm(dialog): def on_accept(dialog): filename = dialog.filename parser = dialog.parser message = "An unexpected error has occured when trying to parse %s:\n\n" % os.path.basename(filename) message += "{}\n\n" message += "This is most likely caused by an invalid or unsupported file format." with DialogFactory.error_dialog_handler(message, parent=self.view.get_toplevel(), reraise=False): self.model.exclusion_ranges.load_data(parser, filename, clear=True) DialogFactory.get_load_dialog( title="Import exclusion ranges", parent=self.view.get_top_widget(), filters=exc_parsers.get_import_file_filters() ).run(on_accept) DialogFactory.get_confirmation_dialog( "Importing exclusion ranges will erase all current data.\nAre you sure you want to continue?", parent=self.view.get_top_widget() ).run(on_confirm) def on_export_exclusion_ranges_clicked(self, widget, data=None): def on_accept(dialog): filename = dialog.filename parser = dialog.parser message = "An unexpected error has occurred when trying to save '%s'.\n" % os.path.basename(filename) message += "Contact the developer about this!" with DialogFactory.error_dialog_handler(message, parent=self.view.get_toplevel(), reraise=False): header = "%s %s" % (self.model.name, self.model.sample_name) self.model.exclusion_ranges.save_data(parser, filename, header=header) DialogFactory.get_save_dialog( "Select file for exclusion ranges export", parent=self.view.get_top_widget(), filters=exc_parsers.get_export_file_filters() ).run(on_accept) def on_replace_experimental_data(self, *args, **kwargs): def on_accept(dialog): filename = dialog.filename parser = dialog.parser message = "An unexpected error has occurred when trying to parse '%s'.\n" % os.path.basename(filename) message += "This is most likely caused by an invalid or unsupported file format." with DialogFactory.error_dialog_handler(message, parent=self.view.get_toplevel(), reraise=False): self.model.experimental_pattern.load_data(parser, filename, clear=True) DialogFactory.get_load_dialog( "Open XRD file for import", parent=self.view.get_top_widget(), filters=xrd_parsers.get_import_file_filters() ).run(on_accept) return True def on_btn_import_experimental_data_clicked(self, widget, data=None): def on_confirm(dialog): self.on_replace_experimental_data() DialogFactory.get_confirmation_dialog( "Importing a new experimental file will erase all current data.\nAre you sure you want to continue?", parent=self.view.get_top_widget() ).run(on_confirm) return True def on_export_experimental_data(self, *args, **kwargs): return self._export_data(self.model.experimental_pattern) def on_btn_export_experimental_data_clicked(self, widget, data=None): return self.on_export_experimental_data() def on_btn_export_calculated_data_clicked(self, widget, data=None): return self._export_data(self.model.calculated_pattern) def _export_data(self, line): def on_accept(dialog): filename = dialog.filename parser = dialog.parser message = "An unexpected error has occurred when trying to save to '%s'." % os.path.basename(filename) with DialogFactory.error_dialog_handler(message, parent=self.view.get_toplevel(), reraise=False): line.save_data(parser, filename, **self.model.get_export_meta_data()) ext_less_fname = os.path.splitext(self.model.name)[0] DialogFactory.get_save_dialog( "Select file for export", parent=self.view.get_top_widget(), filters=xrd_parsers.get_export_file_filters(), current_name=ext_less_fname ).run(on_accept) pass # end of class class StatisticsController(BaseController): def register_adapters(self): if self.model is not None: for name in self.model.get_properties(): if name in self.model.__have_no_widget__: pass else: self.adapt(name) return pass # end of class PyXRD-0.8.4/pyxrd/specimen/glade/000077500000000000000000000000001363064711000165175ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/specimen/glade/edit_marker.glade000066400000000000000000001015061363064711000220060ustar00rootroot00000000000000 100 0.050000000000000003 1 True False 090-eyedropper -360 360 5 45 180 1 10 -10 10 0.10000000000000001 0.25 -120 120 0.5 5 -10 10 0.10000000000000001 0.25 True True 5 16 6 5 5 True False 1 Label 1 2 GTK_FILL True False 1 Colour 12 13 GTK_FILL True False 1 Line base right 9 10 GTK_FILL True False 1 Line style 8 9 GTK_FILL True False 1 Label Angle True 6 7 GTK_FILL True False 1 Position True 2 3 GTK_FILL GTK_FILL True False 0 °2θ 3 4 2 3 GTK_FILL True False 1 GTK_FILL Visible True True False 0.5 True 1 4 GTK_FILL default True True False 0 True True 4 6 6 7 True False 6 4 6 GTK_FILL True False 0 0 50 True True True #ffff0000ffff 1 2 12 13 GTK_FILL True False 0 0 200 True True False False 1 4 1 2 GTK_FILL True False 0 True True 4 marker_angle 1 True 1 3 6 7 GTK_FILL True False 0 200 True False 1 3 9 10 GTK_FILL True False 0 200 True False 1 3 8 9 GTK_FILL True False 6 13 14 GTK_FILL True False 1 X Offset True 14 15 GTK_FILL True False 1 Y Offset True 15 16 GTK_FILL True False 0 °2θ 3 6 14 15 True False 1 Label alignment True 7 8 GTK_FILL True False 0 200 True False 1 3 7 8 GTK_FILL True False 0 nm True 3 4 3 4 GTK_FILL 50 True False 0 0 0 0 50 True True True Select the position directly on the pattern img_sample_graph right True 1 2 2 5 GTK_FILL GTK_FILL True False 0 200 True True etched-in marker_y_offset 2 True 1 3 15 16 GTK_FILL True False 1 True 3 4 GTK_FILL GTK_FILL default True True False 0 True True 4 6 7 8 True False 0 °2θ 3 4 6 7 GTK_FILL default True True False 0 True True 4 6 8 9 default True True False 0 True True 4 6 9 10 default True True False 0 True True 4 6 12 13 True False 0 200 True True marker_x_offset 2 True 1 3 14 15 GTK_FILL True False 1 Line top right 10 11 GTK_FILL True False 0 200 True False 1 3 10 11 GTK_FILL default True True False 0 True True 4 6 10 11 50 True False 0 Offset from base 5 11 12 GTK_FILL True False 0 True True 4 marker_top_offset 2 True 1 3 11 12 GTK_FILL default True True False 0 True True 4 6 11 12 True False 0 3 6 15 16 True False 0 3 4 6 13 GTK_FILL True False 0 2 3 12 13 GTK_FILL True False 0 4 6 5 GTK_FILL True True marker_position 5 True 2 3 2 3 True True entry_nanometer 5 True 2 3 3 4 PyXRD-0.8.4/pyxrd/specimen/glade/find_peaks.glade000066400000000000000000000034631363064711000216260ustar00rootroot00000000000000 True False True False False True 0 Find peaks True True True False True 1 Match minerals True False True True False True 2 PyXRD-0.8.4/pyxrd/specimen/glade/find_peaks_dialog.glade000066400000000000000000000165011363064711000231420ustar00rootroot00000000000000 3 50 7 1 10 True False 8 2 10 5 True False 0 Pattern GTK_FILL True False 1 2 True False 0 Generate threshold histogram: 2 2 3 GTK_FILL True False 1 Maximum 3 4 GTK_FILL True True True 1 2 3 4 True False 1 Steps 4 5 GTK_FILL True True True steps True 1 2 4 5 True True True 1 2 6 7 GTK_FILL True False 2 1 2 GTK_FILL 200 True False 2 5 6 True False 1 Selected threshold: 6 7 True False 1 # of peaks 7 8 True False 0 1 2 7 8 PyXRD-0.8.4/pyxrd/specimen/glade/match_minerals.glade000066400000000000000000000170131363064711000225050ustar00rootroot00000000000000 True False 210-left-arrow True False 211-right-arrow True False 027-search True False 066-tags True False 4 3 10 5 True False Matched minerals: 0 GTK_FILL 320 160 True True in True True 1 3 320 160 True True in True True 2 3 1 3 True True True img_left 1 2 1 2 True True True img_right 1 2 2 3 True False All minerals: 0 2 3 GTK_FILL True False 0 0 True False 5 Auto match True True True img_search True True 0 Append labels True True True img_tags True True 1 3 4 PyXRD-0.8.4/pyxrd/specimen/glade/save_graph_size.glade000066400000000000000000000172421363064711000226740ustar00rootroot00000000000000 True True 6 True 6 320 True False 4 3 6 6 True True 2480 True False False 1 2 1 2 True True 3508 True False False 1 2 2 3 True False 1 Width True 1 2 GTK_FILL True False 1 Height True 2 3 GTK_FILL True False 1 DPI 3 4 GTK_FILL True True 300 True False False 1 3 3 4 True False 1 Load preset: GTK_FILL True False 1 3 GTK_FILL True False 0 px 2 3 1 2 GTK_FILL True False 0 px 2 3 2 3 GTK_FILL True False Settings PyXRD-0.8.4/pyxrd/specimen/glade/specimen.glade000066400000000000000000001247601363064711000213320ustar00rootroot00000000000000 True False 190-circle-plus True False 190-circle-plus True False 191-circle-minus True False 191-circle-minus 1000 1 10 -10000000 10000000 10 100 175 True True specimen_bg_shift 2 True 1000000000 1 0.10000000000000001 0.5 1000000000 1 0.10000000000000001 0.5 -10 10 0.050000000000000003 0.20000000000000001 True True True True True False 12 2 2 6 6 True False 6 Name 0 GTK_FILL GTK_FILL True False 6 Sample 0 1 2 GTK_FILL GTK_FILL True True 15 False False 1 2 GTK_FILL True True 15 False False 1 2 1 2 GTK_FILL True False General True False True False 10 14 3 5 Display experimental diffractogram True True False 0 True 3 GTK_FILL GTK_FILL Display calculated diffractogram True True False 0 True 3 3 4 GTK_FILL GTK_FILL Display phases seperately True True False 0 True 3 6 7 GTK_FILL GTK_FILL True True False 0 True True False Add R<sub>p</sub> value to the specimen label True 3 9 10 GTK_FILL GTK_FILL True False 6 Vertical shift of the plot True 0 2 10 11 GTK_FILL GTK_FILL True False 0 0 175 True True specimen_display_vshift 3 True 2 3 10 11 GTK_FILL GTK_FILL True False 0 0 175 True True specimen_display_vscale 2 True 2 3 11 13 GTK_FILL GTK_FILL True False 6 Experimental scale factor True 0 2 11 13 GTK_FILL GTK_FILL True False 5 1 3 True False 5 4 6 True True False 0 True True False Display derivative patterns True 3 8 9 GTK_FILL GTK_FILL True True False 0 True True False Display residual patterns True 3 7 8 GTK_FILL GTK_FILL True False 1 3 4 6 10 True False 1 3 1 3 10 True False 6 Residuals scale factor True 0 2 13 14 GTK_FILL GTK_FILL True False 0 0 175 True True 1.00 specimen_display_residual_scale 2 True 1 2 3 13 14 GTK_FILL GTK_FILL 1 True False Display 1 False 350 True False 12 6 True True in True True True True 0 True False Add True True True img_add_exp_data False False 0 True True True True False True False 359-file-export False True 0 True False _Export True True True 1 False False 1 Remove True True True img_del_exp_data False False 20 end 1 True True True True True False True False 358-file-import False True 0 True False _Import True True True 1 False False 2 False True 1 2 True False Experimental 2 True False True False 12 6 True True in True True True True 0 True False True True True True False True False 359-file-export False True 0 True False _Export True True True 1 False False end 0 False True end 1 3 True False Calculated 3 False True False 12 6 True True in True True True True 0 True False Add True True True img_add_excl False False 0 True True True True False True False 359-file-export False True 0 True False _Export True True True 1 False False 1 Remove True True True img_del_excl False False 20 end 1 True True True True True False True False 358-file-import False True 0 True False _Import True True True 1 False False 2 False True 1 4 True False Exclusion ranges 4 False True False 12 5 True False Goniometer 5 False PyXRD-0.8.4/pyxrd/specimen/glade/statistics.glade000066400000000000000000000234541363064711000217170ustar00rootroot00000000000000 300 True False 5 4 True False 0 5 5 <small><b>Statistics:</b></small> True 4 2 GTK_FILL True False 1 5 &#967;² True 2 3 4 5 GTK_FILL GTK_FILL True False 1 5 <small>Data points</small> True 2 3 GTK_FILL GTK_FILL True False 0 10 0 1 2 2 3 GTK_FILL 3 True False 0 10 0 3 4 4 5 GTK_FILL 3 True False 0 10 0 1 2 3 4 GTK_FILL 3 True False 1 5 <small>Rp [%]</small> True 4 5 GTK_FILL GTK_FILL True False 0 10 0 1 2 4 5 GTK_FILL 3 True False 0 10 0 3 4 3 4 GTK_FILL 3 True False 0 10 0 3 4 2 3 GTK_FILL 3 True False 1 5 <small>R²</small> True 3 4 GTK_FILL GTK_FILL True False 1 5 <small>Rwp [%]</small> True 2 3 2 3 GTK_FILL GTK_FILL True False 1 5 <small>Re [%]</small> True 2 3 3 4 GTK_FILL GTK_FILL PyXRD-0.8.4/pyxrd/specimen/models/000077500000000000000000000000001363064711000167265ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/specimen/models/__init__.py000066400000000000000000000005761363064711000210470ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .base import Specimen from .markers import ThresholdSelector, Marker, MineralScorer from .statistics import Statistics __all__ = [ "Specimen", "Thresholdselector", "Marker", "MineralScorer", "Statistics" ] PyXRD-0.8.4/pyxrd/specimen/models/base.py000066400000000000000000000570621363064711000202240ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from math import pi, log import numpy as np from mvc.observers import ListObserver from mvc.models.properties import ( StringProperty, SignalMixin, ReadOnlyMixin, FloatProperty, LabeledProperty, ObserveMixin, ListProperty, BoolProperty ) from pyxrd.data import settings from pyxrd.generic.io import storables, Storable from pyxrd.generic.models import ExperimentalLine, CalculatedLine, DataModel from pyxrd.generic.utils import not_none from pyxrd.generic.models.lines import PyXRDLine from pyxrd.calculations.peak_detection import peakdetect from pyxrd.calculations.data_objects import SpecimenData from pyxrd.goniometer.models import Goniometer from pyxrd.file_parsers.xrd_parsers import xrd_parsers from pyxrd.file_parsers.exc_parsers import exc_parsers from .markers import Marker from .statistics import Statistics @storables.register() class Specimen(DataModel, Storable): # MODEL INTEL: class Meta(DataModel.Meta): store_id = "Specimen" export_filters = xrd_parsers.get_export_file_filters() excl_filters = exc_parsers.get_import_file_filters() _data_object = None @property def data_object(self): self._data_object.goniometer = self.goniometer.data_object self._data_object.range_theta = self.__get_range_theta() self._data_object.selected_range = self.get_exclusion_selector() self._data_object.z_list = self.get_z_list() try: self._data_object.observed_intensity = np.copy(self.experimental_pattern.data_y) except IndexError: self._data_object.observed_intensity = np.array([], dtype=float) return self._data_object def get_z_list(self): return list(self.experimental_pattern.z_data) project = property(DataModel.parent.fget, DataModel.parent.fset) # PROPERTIES: #: The sample name sample_name = StringProperty( default="", text="Sample", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: The sample name name = StringProperty( default="", text="Name", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) @StringProperty( default="", text="Label", visible=False, persistent=False, tabular=True, mix_with=(ReadOnlyMixin,) ) def label(self): if self.display_stats_in_lbl and (self.project is not None and self.project.layout_mode == "FULL"): label = self.sample_name label += "\nRp = %.1f%%" % not_none(self.statistics.Rp, 0.0) label += "\nRwp = %.1f%%" % not_none(self.statistics.Rwp, 0.0) return label else: return self.sample_name display_calculated = BoolProperty( default=True, text="Display calculated diffractogram", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) display_experimental = BoolProperty( default=True, text="Display experimental diffractogram", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) display_vshift = FloatProperty( default=0.0, text="Vertical shift of the plot", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", widget_type="spin", mix_with=(SignalMixin,) ) display_vscale = FloatProperty( default=0.0, text="Vertical scale of the plot", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", widget_type="spin", mix_with=(SignalMixin,) ) display_phases = BoolProperty( default=True, text="Display phases seperately", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) display_stats_in_lbl = BoolProperty( default=True, text="Display Rp in label", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) display_residuals = BoolProperty( default=True, text="Display residual patterns", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) display_residual_scale = FloatProperty( default=1.0, text="Residual pattern scale", minimum=0.0, visible=True, persistent=True, tabular=True, signal_name="visuals_changed", widget_type="spin", mix_with=(SignalMixin,) ) display_derivatives = BoolProperty( default=False, text="Display derivative patterns", visible=True, persistent=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: A :class:`~pyxrd.generic.models.lines.CalculatedLine` instance calculated_pattern = LabeledProperty( default=None, text="Calculated diffractogram", visible=True, persistent=True, tabular=True, signal_name="data_changed", widget_type="xy_list_view", mix_with=(SignalMixin, ObserveMixin,) ) #: A :class:`~pyxrd.generic.models.lines.ExperimentalLine` instance experimental_pattern = LabeledProperty( default=None, text="Experimental diffractogram", visible=True, persistent=True, tabular=True, signal_name="data_changed", widget_type="xy_list_view", mix_with=(SignalMixin, ObserveMixin,) ) #: A list of 2-theta ranges to exclude for the calculation of the Rp factor exclusion_ranges = LabeledProperty( default=None, text="Excluded ranges", visible=True, persistent=True, tabular=True, signal_name="data_changed", widget_type="xy_list_view", mix_with=(SignalMixin, ObserveMixin) ) #: A :class:`~pyxrd.goniometer.models.Goniometer` instance goniometer = LabeledProperty( default=None, text="Goniometer", visible=True, persistent=True, tabular=True, signal_name="data_changed", mix_with=(SignalMixin, ObserveMixin,) ) #: A :class:`~pyxrd.specimen.models.Statistics` instance statistics = LabeledProperty( default=None, text="Markers", visible=False, persistent=False, tabular=True, ) #: A list :class:`~pyxrd.specimen.models.Marker` instances markers = ListProperty( default=None, text="Markers", data_type=Marker, visible=False, persistent=True, tabular=True, signal_name="visuals_changed", widget_type="object_list_view", mix_with=(SignalMixin,) ) @property def max_display_y(self): """ The maximum intensity or z-value (display y axis) of the current profile (both calculated and observed) """ _max = 0.0 if self.experimental_pattern is not None: _max = max(_max, np.max(self.experimental_pattern.max_display_y)) if self.calculated_pattern is not None: _max = max(_max, np.max(self.calculated_pattern.max_display_y)) return _max # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): """ Valid keyword arguments for a Specimen are: name: the name of the specimen sample_name: the sample name of the specimen calculated_pattern: the calculated pattern experimental_pattern: the experimental pattern exclusion_ranges: the exclusion ranges XYListStore goniometer: the goniometer used for recording data markers: the specimen's markers display_vshift: the patterns vertical shift from its default position display_vscale: the patterns vertical scale (default is 1.0) display_calculated: whether or not to show the calculated pattern display_experimental: whether or not to show the experimental pattern display_residuals: whether or not to show the residuals display_derivatives: whether or not to show the 1st derivative patterns display_phases: whether or not to show the separate phase patterns display_stats_in_lbl: whether or not to display the Rp values in the pattern label """ my_kwargs = self.pop_kwargs(kwargs, "data_name", "data_sample", "data_sample_length", "data_calculated_pattern", "data_experimental_pattern", "calc_color", "calc_lw", "inherit_calc_color", "inherit_calc_lw", "exp_color", "exp_lw", "inherit_exp_color", "inherit_exp_lw", "project_goniometer", "data_markers", "bg_shift", "abs_scale", "exp_cap_value", "sample_length", "absorption", "sample_z_dev", *[prop.label for prop in Specimen.Meta.get_local_persistent_properties()] ) super(Specimen, self).__init__(*args, **kwargs) kwargs = my_kwargs self._data_object = SpecimenData() with self.visuals_changed.hold_and_emit(): with self.data_changed.hold_and_emit(): self.name = self.get_kwarg(kwargs, "", "name", "data_name") sample_name = self.get_kwarg(kwargs, "", "sample_name", "data_sample") if isinstance(sample_name, bytes): sample_name = sample_name.decode("utf-8", "ignore") self.sample_name = sample_name calc_pattern_old_kwargs = {} for kw in ("calc_color", "calc_lw", "inherit_calc_color", "inherit_calc_lw"): if kw in kwargs: calc_pattern_old_kwargs[kw.replace("calc_", "")] = kwargs.pop(kw) self.calculated_pattern = self.parse_init_arg( self.get_kwarg(kwargs, None, "calculated_pattern", "data_calculated_pattern"), CalculatedLine, child=True, default_is_class=True, label="Calculated Profile", parent=self, **calc_pattern_old_kwargs ) exp_pattern_old_kwargs = {} for kw in ("exp_color", "exp_lw", "inherit_exp_color", "inherit_exp_lw"): if kw in kwargs: exp_pattern_old_kwargs[kw.replace("exp_", "")] = kwargs.pop(kw) self.experimental_pattern = self.parse_init_arg( self.get_kwarg(kwargs, None, "experimental_pattern", "data_experimental_pattern"), ExperimentalLine, child=True, default_is_class=True, label="Experimental Profile", parent=self, **exp_pattern_old_kwargs ) self.exclusion_ranges = PyXRDLine(data=self.get_kwarg(kwargs, None, "exclusion_ranges"), parent=self) # Extract old kwargs if they are there: gonio_kwargs = {} sample_length = self.get_kwarg(kwargs, None, "sample_length", "data_sample_length") if sample_length is not None: gonio_kwargs["sample_length"] = float(sample_length) absorption = self.get_kwarg(kwargs, None, "absorption") if absorption is not None: # assuming a surface density of at least 20 mg/cm²: gonio_kwargs["absorption"] = float(absorption) / 0.02 # Initialize goniometer (with optional old kwargs): self.goniometer = self.parse_init_arg( self.get_kwarg(kwargs, None, "goniometer", "project_goniometer"), Goniometer, child=True, default_is_class=True, parent=self, **gonio_kwargs ) self.markers = self.get_list(kwargs, None, "markers", "data_markers", parent=self) for marker in self.markers: self.observe_model(marker) self._specimens_observer = ListObserver( self.on_marker_inserted, self.on_marker_removed, prop_name="markers", model=self ) self.display_vshift = float(self.get_kwarg(kwargs, 0.0, "display_vshift")) self.display_vscale = float(self.get_kwarg(kwargs, 1.0, "display_vscale")) self.display_calculated = bool(self.get_kwarg(kwargs, True, "display_calculated")) self.display_experimental = bool(self.get_kwarg(kwargs, True, "display_experimental")) self.display_residuals = bool(self.get_kwarg(kwargs, True, "display_residuals")) self.display_residual_scale = float(self.get_kwarg(kwargs, 1.0, "display_residual_scale")) self.display_derivatives = bool(self.get_kwarg(kwargs, False, "display_derivatives")) self.display_phases = bool(self.get_kwarg(kwargs, False, "display_phases")) self.display_stats_in_lbl = bool(self.get_kwarg(kwargs, True, "display_stats_in_lbl")) self.statistics = Statistics(parent=self) pass # end of with pass # end of with pass # end of __init__ def __str__(self): return "" % (self.name, repr(self)) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @DataModel.observe("data_changed", signal=True) def notify_data_changed(self, model, prop_name, info): if model == self.calculated_pattern: self.visuals_changed.emit() # don't propagate this as data_changed else: self.data_changed.emit() # propagate signal @DataModel.observe("visuals_changed", signal=True) def notify_visuals_changed(self, model, prop_name, info): self.visuals_changed.emit() # propagate signal def on_marker_removed(self, item): with self.visuals_changed.hold_and_emit(): self.relieve_model(item) item.parent = None def on_marker_inserted(self, item): with self.visuals_changed.hold_and_emit(): self.observe_model(item) item.parent = self # ------------------------------------------------------------ # Input/Output stuff # ------------------------------------------------------------ @staticmethod def from_experimental_data(filename, parent, parser=xrd_parsers._group_parser, load_as_insitu=False): """ Returns a list of new :class:`~.specimen.models.Specimen`'s loaded from `filename`, setting their parent to `parent` using the given parser. If the load_as_insitu flag is set to true, """ specimens = list() xrdfiles = parser.parse(filename) if len(xrdfiles): if getattr(xrdfiles[0], "relative_humidity_data", None) is not None: # we have relative humidity data specimen = None # Setup list variables: x_data = None y_datas = [] rh_datas = [] for xrdfile in xrdfiles: # Get data we need: name, sample, xy_data, rh_data = ( xrdfile.filename, xrdfile.name, xrdfile.data, xrdfile.relative_humidity_data ) # Transform into numpy array for column selection xy_data = np.array(xy_data) rh_data = np.array(rh_data) if specimen is None: specimen = Specimen(parent=parent, name=name, sample_name=sample) specimen.goniometer.reset_from_file(xrdfile.create_gon_file()) # Extract the 2-theta positions once: x_data = np.copy(xy_data[:,0]) # Add a new sub-pattern: y_datas.append(np.copy(xy_data[:,1])) # Store the average RH for this pattern: rh_datas.append(np.average(rh_data)) specimen.experimental_pattern.load_data_from_generator(zip(x_data, np.asanyarray(y_datas).transpose()), clear=True) specimen.experimental_pattern.y_names = ["%.1f" % f for f in rh_datas] specimen.experimental_pattern.z_data = rh_datas specimens.append(specimen) else: # regular (might be multi-pattern) file for xrdfile in xrdfiles: name, sample, generator = xrdfile.filename, xrdfile.name, xrdfile.data specimen = Specimen(parent=parent, name=name, sample_name=sample) # TODO FIXME: specimen.experimental_pattern.load_data_from_generator(generator, clear=True) specimen.goniometer.reset_from_file(xrdfile.create_gon_file()) specimens.append(specimen) return specimens def json_properties(self): props = Storable.json_properties(self) props["exclusion_ranges"] = self.exclusion_ranges._serialize_data() return props def get_export_meta_data(self): """ Returns a dictionary with common meta-data used in export functions for experimental or calculated data """ return dict( sample=self.label + " " + self.sample_name, wavelength=self.goniometer.wavelength, radius=self.goniometer.radius, divergence=self.goniometer.divergence, soller1=self.goniometer.soller1, soller2=self.goniometer.soller2, ) # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def clear_markers(self): with self.visuals_changed.hold(): for marker in list(self.markers)[::-1]: self.markers.remove(marker) def auto_add_peaks(self, tmodel): """ Automagically add peak markers *tmodel* a :class:`~specimen.models.ThresholdSelector` model """ threshold = tmodel.sel_threshold base = 1 if (tmodel.pattern == "exp") else 2 data_x, data_y = tmodel.get_xy() maxtab, mintab = peakdetect(data_y, data_x, 5, threshold) # @UnusedVariable mpositions = [marker.position for marker in self.markers] with self.visuals_changed.hold(): i = 1 for x, y in maxtab: # @UnusedVariable if not x in mpositions: nm = self.goniometer.get_nm_from_2t(x) if x != 0 else 0 new_marker = Marker(label="%%.%df" % (3 + min(int(log(nm, 10)), 0)) % nm, parent=self, position=x, base=base) self.markers.append(new_marker) i += 1 def get_exclusion_selector(self): """ Get the numpy selector array for non-excluded data :rtype: a numpy ndarray """ x = self.__get_range_theta() * 360.0 / pi # convert to degrees selector = np.ones(x.shape, dtype=bool) data = np.sort(np.asarray(self.exclusion_ranges.get_xy_data()), axis=0) for x0, x1 in zip(*data): new_selector = ((x < x0) | (x > x1)) selector = selector & new_selector return selector def get_exclusion_xy(self): """ Get an numpy array containing only non-excluded data X and Y data :rtype: a tuple containing 4 numpy ndarray's: the experimental X and Y data and the calculated X and Y data """ ex, ey = self.experimental_pattern.get_xy_data() cx, cy = self.calculated_pattern.get_xy_data() selector = self.get_exclusion_selector(ex) return ex[selector], ey[selector], cx[selector], cy[selector] # ------------------------------------------------------------ # Draggable mix-in hook: # ------------------------------------------------------------ def on_pattern_dragged(self, delta_y, button=1): if button == 1: self.display_vshift += delta_y elif button == 3: self.display_vscale += delta_y elif button == 2: self.project.display_plot_offset += delta_y pass def update_visuals(self, phases): """ Update visual representation of phase patterns (if any) """ if phases is not None: self.calculated_pattern.y_names = [ phase.name if phase is not None else "" for phase in phases ] self.calculated_pattern.phase_colors = [ phase.display_color if phase is not None else "#FF00FF" for phase in phases ] # ------------------------------------------------------------ # Intensity calculations: # ------------------------------------------------------------ def update_pattern(self, total_intensity, phase_intensities, phases): """ Update calculated patterns using the provided total and phase intensities """ if len(phases) == 0: self.calculated_pattern.clear() else: maxZ = len(self.get_z_list()) new_data = np.zeros((phase_intensities.shape[-1], maxZ + maxZ*len(phases))) for z_index in range(maxZ): # Set the total intensity for this z_index: new_data[:, z_index] = total_intensity[z_index] # Calculate phase intensity offsets: phase_start_index = maxZ + z_index * len(phases) phase_end_index = phase_start_index + len(phases) # Set phase intensities for this z_index: new_data[:,phase_start_index:phase_end_index] = phase_intensities[:,z_index,:].transpose() # Store in pattern: self.calculated_pattern.set_data( self.__get_range_theta() * 360. / pi, new_data ) self.update_visuals(phases) if settings.GUI_MODE: self.statistics.update_statistics(derived=self.display_derivatives) def convert_to_fixed(self): """ Converts the experimental data from ADS to fixed slits in-place (disregards the `has_ads` flag in the goniometer, but uses the settings otherwise) """ correction = self.goniometer.get_ADS_to_fixed_correction(self.__get_range_theta()) self.experimental_pattern.apply_correction(correction) def convert_to_ads(self): """ Converts the experimental data from fixed slits to ADS in-place (disregards the `has_ads` flag in the goniometer, but uses the settings otherwise) """ correction = 1.0 / self.goniometer.get_ADS_to_fixed_correction(self.__get_range_theta()) self.experimental_pattern.apply_correction(correction) def __get_range_theta(self): if len(self.experimental_pattern) <= 1: return self.goniometer.get_default_theta_range() else: return np.radians(self.experimental_pattern.data_x * 0.5) def __repr__(self): return "Specimen(name='%s')" % self.name pass # end of class PyXRD-0.8.4/pyxrd/specimen/models/markers.py000066400000000000000000000420061363064711000207460ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import numpy as np from scipy.interpolate import interp1d from mvc.models.properties import ( LabeledProperty, StringProperty, StringChoiceProperty, ColorProperty, FloatProperty, IntegerProperty, IntegerChoiceProperty, BoolProperty, ListProperty, SignalProperty, SignalMixin, ReadOnlyMixin, SetActionMixin ) from pyxrd.data import settings from pyxrd.calculations.peak_detection import score_minerals from pyxrd.generic.io import storables, Storable from pyxrd.generic.models import ChildModel, DataModel from pyxrd.generic.models.properties import InheritableMixin from pyxrd.generic.models.mixins import CSVMixin from pyxrd.generic.io.utils import unicode_open class MineralScorer(DataModel): specimen = property(DataModel.parent.fget, DataModel.parent.fset) matches_changed = SignalProperty() matches = ListProperty( default=None, text="Matches", visible=True, persistent=False, mix_with=(ReadOnlyMixin,) ) @ListProperty( default=None, text="Minerals", visible=True, persistent=False, mix_with=(ReadOnlyMixin,) ) def minerals(self): # Load them when accessed for the first time: _minerals = type(self).minerals._get(self) if _minerals == None: _minerals = list() with unicode_open(settings.DATA_REG.get_file_path("MINERALS")) as f: mineral = "" abbreviation = "" position_flag = True peaks = [] for line in f: line = line.replace('\n', '') try: number = float(line) if position_flag: position = number else: intensity = number peaks.append((position, intensity)) position_flag = not position_flag except ValueError: if mineral != "": _minerals.append((mineral, abbreviation, peaks)) position_flag = True if len(line) > 25: mineral = line[:24].strip() if len(line) > 49: abbreviation = line[49:].strip() peaks = [] sorted(_minerals, key=lambda mineral:mineral[0]) type(self).minerals._set(self, _minerals) return _minerals # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def __init__(self, marker_peaks=[], *args, **kwargs): super(MineralScorer, self).__init__(*args, **kwargs) self._matches = [] self.marker_peaks = marker_peaks # position, intensity # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def auto_match(self): self._matches = score_minerals(self.marker_peaks, self.minerals) self.matches_changed.emit() def del_match(self, index): if self.matches: del self.matches[index] self.matches_changed.emit() def add_match(self, name, abbreviation, peaks): matches = score_minerals(self.marker_peaks, [(name, abbreviation, peaks)]) if len(matches): name, abbreviation, peaks, matches, score = matches[0] else: matches, score = [], 0. self.matches.append([name, abbreviation, peaks, matches, score]) sorted(self._matches, key=lambda match: match[-1], reverse=True) self.matches_changed.emit() pass # end of class class ThresholdSelector(ChildModel): # MODEL INTEL: specimen = property(DataModel.parent.fget, DataModel.parent.fset) # PROPERTIES: pattern = StringChoiceProperty( default="exp", text="Pattern", visible=True, persistent=False, choices={ "exp": "Experimental Pattern", "calc": "Calculated Pattern" }, set_action_name="update_threshold_plot_data", mix_with=(SetActionMixin,) ) max_threshold = FloatProperty( default=0.32, text="Maximum threshold", visible=True, persistent=False, minimum=0.0, maximum=1.0, widget_type="float_entry", set_action_name="update_threshold_plot_data", mix_with=(SetActionMixin,) ) steps = IntegerProperty( default=20, text="Steps", visible=True, persistent=False, minimum=3, maximum=50, set_action_name="update_threshold_plot_data", mix_with=(SetActionMixin,) ) sel_num_peaks = IntegerProperty( default=0, text="Selected number of peaks", visible=True, persistent=False, widget_type="label" ) def set_sel_threshold(self, value): _sel_threshold = type(self).sel_threshold._get(self) if value != _sel_threshold and len(self.threshold_plot_data[0]) > 0: _sel_threshold = value if _sel_threshold >= self.threshold_plot_data[0][-1]: self.sel_num_peaks = self.threshold_plot_data[1][-1] elif _sel_threshold <= self.threshold_plot_data[0][0]: self.sel_num_peaks = self.threshold_plot_data[1][0] else: self.sel_num_peaks = int(interp1d(*self.threshold_plot_data)(_sel_threshold)) type(self).sel_threshold._set(self, _sel_threshold) sel_threshold = FloatProperty( default=0.1, text="Selected threshold", visible=True, persistent=False, widget_type="float_entry", fset = set_sel_threshold ) threshold_plot_data = LabeledProperty( default=None, text="Threshold plot data", visible=False, persistent=False ) # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): super(ThresholdSelector, self).__init__(*args, **kwargs) self.max_threshold = self.get_kwarg(kwargs, self.max_threshold, "max_threshold") self.steps = self.get_kwarg(kwargs, self.steps, "steps") self.sel_threshold = self.get_kwarg(kwargs, self.sel_threshold, "sel_threshold") if self.parent.experimental_pattern.size > 0: self.pattern = "exp" else: self.pattern = "calc" # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def get_xy(self): if self.pattern == "exp": data_x, data_y = self.parent.experimental_pattern.get_xy_data() elif self.pattern == "calc": data_x, data_y = self.parent.calculated_pattern.get_xy_data() if data_y.size > 0: data_y = data_y / np.max(data_y) return data_x, data_y _updating_plot_data = False def update_threshold_plot_data(self, status_dict=None): if self.parent is not None and not self._updating_plot_data: self._updating_plot_data = True if self.pattern == "exp": p, t, m = self.parent.experimental_pattern.get_best_threshold( self.max_threshold, self.steps, status_dict) elif self.pattern == "calc": p, t, m = self.parent.calculated_pattern.get_best_threshold( self.max_threshold, self.steps, status_dict) self.threshold_plot_data = p self.sel_threshold = t self.max_threshold = m self._updating_plot_data = False pass # end of class @storables.register() class Marker(DataModel, Storable, CSVMixin): # MODEL INTEL: class Meta(DataModel.Meta): store_id = "Marker" specimen = property(DataModel.parent.fget, DataModel.parent.fset) # PROPERTIES: #: This marker's label label = StringProperty( default="New Marker", text="Label", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: Flag indicating whether the color of this marker is inherited inherit_color = BoolProperty( default=settings.MARKER_INHERIT_COLOR, text="Inherit color", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: This maker's color: color = ColorProperty( default=settings.MARKER_COLOR, text="Color", persistent=True, visible=True, tabular=True, inheritable=True, signal_name="visuals_changed", inherit_flag="inherit_color", inherit_from="specimen.project.display_marker_color", mix_with=(InheritableMixin, SignalMixin,) ) #: Flag indicating whether the angle of this marker is inherited inherit_angle = BoolProperty( default=settings.MARKER_INHERIT_ANGLE, text="Inherit angle", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: This maker's angle: angle = FloatProperty( default=settings.MARKER_ANGLE, text="Angle", widget_type="spin", persistent=True, visible=True, tabular=True, inheritable=True, signal_name="visuals_changed", inherit_flag="inherit_angle", inherit_from="specimen.project.display_marker_angle", mix_with=(InheritableMixin, SignalMixin,) ) #: Flag indicating whether the top offset of this marker is inherited inherit_top_offset = BoolProperty( default=settings.MARKER_INHERIT_TOP_OFFSET, text="Inherit top offset", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: This maker's top offset: top_offset = FloatProperty( default=settings.MARKER_TOP_OFFSET, text="Top offset", widget_type="spin", persistent=True, visible=True, tabular=True, inheritable=True, signal_name="visuals_changed", inherit_flag="inherit_top_offset", inherit_from="specimen.project.display_marker_top_offset", mix_with=(InheritableMixin, SignalMixin,) ) #: Whether this marker is visible visible = BoolProperty( default=settings.MARKER_VISIBLE, text="Visible", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: The marker's position position = FloatProperty( default=settings.MARKER_POSITION, text="Position", widget_type="spin", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: The marker's x offset x_offset = FloatProperty( default=settings.MARKER_X_OFFSET, text="X offset", widget_type="spin", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: The marker's y offset y_offset = FloatProperty( default=settings.MARKER_Y_OFFSET, text="Y offset", widget_type="spin", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: Flag indicating whether the alignment of this marker is inherited inherit_align = BoolProperty( default=settings.MARKER_INHERIT_ALIGN, text="Inherit align", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: This marker's alignment align = StringChoiceProperty( default=settings.MARKER_ALIGN, text="Align", choices=settings.MARKER_ALIGNS, persistent=True, visible=True, tabular=True, inheritable=True, signal_name="visuals_changed", inherit_flag="inherit_align", inherit_from="specimen.project.display_marker_align", mix_with=(InheritableMixin, SignalMixin,) ) #: Flag indicating whether the base of this marker is inherited inherit_base = BoolProperty( default=settings.MARKER_INHERIT_BASE, text="Inherit base", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: This marker's base base = IntegerChoiceProperty( default=settings.MARKER_BASE, text="Base", choices=settings.MARKER_BASES, persistent=True, visible=True, tabular=True, inheritable=True, signal_name="visuals_changed", inherit_flag="inherit_base", inherit_from="specimen.project.display_marker_base", mix_with=(InheritableMixin, SignalMixin,) ) #: Flag indicating whether the top of this marker is inherited inherit_top = BoolProperty( default=settings.MARKER_INHERIT_TOP, text="Inherit top", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: This marker's top top = IntegerChoiceProperty( default=settings.MARKER_TOP, text="Top", choices=settings.MARKER_TOPS, persistent=True, visible=True, tabular=True, inheritable=True, signal_name="visuals_changed", inherit_flag="inherit_top", inherit_from="specimen.project.display_marker_top", mix_with=(InheritableMixin, SignalMixin,) ) #: Flag indicating whether the line style of this marker is inherited inherit_style = BoolProperty( default=settings.MARKER_INHERIT_STYLE, text="Inherit line style", persistent=True, visible=True, tabular=True, signal_name="visuals_changed", mix_with=(SignalMixin,) ) #: This marker's line style style = StringChoiceProperty( default=settings.MARKER_STYLE, text="Line style", choices=settings.MARKER_STYLES, persistent=True, visible=True, tabular=True, inheritable=True, signal_name="visuals_changed", inherit_flag="inherit_style", inherit_from="specimen.project.display_marker_style", mix_with=(InheritableMixin, SignalMixin,) ) # ------------------------------------------------------------ # Initialisation and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): my_kwargs = self.pop_kwargs(kwargs, "data_label", "data_visible", "data_position", "data_x_offset", "data_y_offset" "data_color", "data_base", "data_angle", "data_align", *[prop.label for prop in type(self).Meta.get_local_persistent_properties()] ) super(Marker, self).__init__(*args, **kwargs) kwargs = my_kwargs self.label = self.get_kwarg(kwargs, "", "label", "data_label") self.visible = self.get_kwarg(kwargs, True, "visible", "data_visible") self.position = float(self.get_kwarg(kwargs, 0.0, "position", "data_position")) self.x_offset = float(self.get_kwarg(kwargs, 0.0, "x_offset", "data_x_offset")) self.y_offset = float(self.get_kwarg(kwargs, 0.05, "y_offset", "data_y_offset")) self.top_offset = float(self.get_kwarg(kwargs, 0.0, "top_offset")) self.color = self.get_kwarg(kwargs, settings.MARKER_COLOR, "color", "data_color") self.base = int(self.get_kwarg(kwargs, settings.MARKER_BASE, "base", "data_base")) self.angle = float(self.get_kwarg(kwargs, 0.0, "angle", "data_angle")) self.align = self.get_kwarg(kwargs, settings.MARKER_ALIGN, "align") self.style = self.get_kwarg(kwargs, settings.MARKER_STYLE, "style", "data_align") # if top is not set and style is not "none", # assume top to be "Top of plot", otherwise (style is not "none") # assume top to be relative to the base point (using top_offset) self.top = int(self.get_kwarg(kwargs, 0 if self.style == "none" else 1, "top")) self.inherit_align = self.get_kwarg(kwargs, True, "inherit_align") self.inherit_color = self.get_kwarg(kwargs, True, "inherit_color") self.inherit_base = self.get_kwarg(kwargs, True, "inherit_base") self.inherit_top = self.get_kwarg(kwargs, True, "inherit_top") self.inherit_top_offset = self.get_kwarg(kwargs, True, "inherit_top_offset") self.inherit_angle = self.get_kwarg(kwargs, True, "inherit_angle") self.inherit_style = self.get_kwarg(kwargs, True, "inherit_style") # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def get_nm_position(self): if self.parent is not None: return self.parent.goniometer.get_nm_from_2t(self.position) else: return 0.0 def set_nm_position(self, position): if self.parent is not None: self.position = self.parent.goniometer.get_2t_from_nm(position) else: self.position = 0.0 pass # end of class PyXRD-0.8.4/pyxrd/specimen/models/statistics.py000066400000000000000000000123341363064711000214750ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import logging logger = logging.getLogger(__name__) from mvc.models.properties import ( IntegerProperty, ReadOnlyMixin, FloatProperty, LabeledProperty ) from pyxrd.generic.models import PyXRDLine, ChildModel from pyxrd.calculations.statistics import Rpw, Rp, derive class Statistics(ChildModel): # PROPERTIES: specimen = property(ChildModel.parent.fget, ChildModel.parent.fset) @IntegerProperty(default=0, label="Points", visible=False, mix_with=(ReadOnlyMixin,)) def points(self): try: e_ex, e_ey, e_cx, e_cy = self.specimen.get_exclusion_xy() #@UnusedVariable return e_ex.size except: pass return 0 Rp = FloatProperty(default=None, label="Rp", visible=True) Rwp = FloatProperty(default=None, label="Rwp", visible=True) Rpder = FloatProperty(default=None, label="Rpder", visible=True) residual_pattern = LabeledProperty(default=None, label="Residual pattern") der_exp_pattern = LabeledProperty(default=None, label="Derived experimental pattern") der_calc_pattern = LabeledProperty(default=None, label="Derived calculated pattern") der_residual_pattern = LabeledProperty(default=None, label="Derived residual pattern") # ------------------------------------------------------------ # Initialization and other internals # ------------------------------------------------------------ def __init__(self, *args, **kwargs): super(Statistics, self).__init__(*args, **kwargs) self.observe_model(self.parent) # ------------------------------------------------------------ # Notifications of observable properties # ------------------------------------------------------------ @ChildModel.observe("parent", assign=True, after=True) def on_parent_changed(self, model, prop_name, info): self.update_statistics() # ------------------------------------------------------------ # Methods & Functions # ------------------------------------------------------------ def _get_experimental(self): if self.specimen is not None: x, y = self.specimen.experimental_pattern.get_xy_data() return x.copy(), y.copy() else: return None, None def _get_calculated(self): if self.specimen is not None: x, y = self.specimen.calculated_pattern.get_xy_data() return x.copy(), y.copy() else: return None, None def scale_factor_y(self, offset): return self.specimen.scale_factor_y(offset) if self.specimen else (1.0, offset) def update_statistics(self, derived=False): # Clear factors: self.Rp = 0 self.Rwp = 0 self.Rpder = 0 # Setup lines if not yet done: if self.residual_pattern == None: self.residual_pattern = PyXRDLine(label="Residual", color="#000000", lw=0.5, parent=self) if self.der_exp_pattern == None: self.der_exp_pattern = PyXRDLine(label="Exp. 1st der.", color="#000000", lw=2, parent=self) if self.der_calc_pattern == None: self.der_calc_pattern = PyXRDLine(label="Calc. 1st der.", color="#AA0000", lw=2, parent=self) if self.der_residual_pattern == None: self.der_residual_pattern = PyXRDLine(label="1st der. residual", color="#AA00AA", lw=1, parent=self) # Get data: exp_x, exp_y = self._get_experimental() cal_x, cal_y = self._get_calculated() der_exp_y, der_cal_y = None, None del cal_x # don't need this, is the same as exp_x # Try to get statistics, if it fails, just clear and inform the user try: if cal_y is not None and exp_y is not None and cal_y.size > 0 and exp_y.size > 0: # Get the selector for areas to consider in the statistics: selector = self.specimen.get_exclusion_selector() if derived: # Calculate and set first derivate patterns: der_exp_y, der_cal_y = derive(exp_y), derive(cal_y) self.der_exp_pattern.set_data(exp_x, der_exp_y) self.der_calc_pattern.set_data(exp_x, der_cal_y) # Calculate and set residual pattern: self.residual_pattern.set_data(exp_x, exp_y - cal_y) if derived: self.der_residual_pattern.set_data(exp_x, der_exp_y - der_cal_y) # Calculate 'included' R values: self.Rp = Rp(exp_y[selector], cal_y[selector]) self.Rwp = Rpw(exp_y[selector], cal_y[selector]) if derived: self.Rpder = Rp(der_exp_y[selector], der_cal_y[selector]) else: self.residual_pattern.clear() self.der_exp_pattern.clear() self.der_calc_pattern.clear() except: self.residual_pattern.clear() self.der_exp_pattern.clear() self.der_calc_pattern.clear() logger.error("Error occurred when trying to calculate statistics, aborting calculation!") raise pass # end of class PyXRD-0.8.4/pyxrd/specimen/views/000077500000000000000000000000001363064711000166005ustar00rootroot00000000000000PyXRD-0.8.4/pyxrd/specimen/views/__init__.py000066400000000000000000000006451363064711000207160ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .specimens import SpecimenView from .markers import ( DetectPeaksView, EditMarkersView, EditMarkerView, MatchMineralsView ) __all__ = [ "SpecimenView", "DetectPeaksView", "EditMarkersView", "EditMarkerView", "MatchMineralsView" ]PyXRD-0.8.4/pyxrd/specimen/views/markers.py000066400000000000000000000057221363064711000206240ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import Gtk # @UnresolvedImport from matplotlib.figure import Figure from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvasGTK from pyxrd.generic.views import ObjectListStoreView, DialogView, BaseView class EditMarkerView(BaseView): builder = resource_filename(__name__, "../glade/edit_marker.glade") top = "edit_marker" widget_format = "marker_%s" def __init__(self, *args, **kwargs): BaseView.__init__(self, *args, **kwargs) self.parent.set_title("Edit Markers") class EditMarkersView(ObjectListStoreView): extra_widget_builder = resource_filename(__name__, "../glade/find_peaks.glade") extra_widget_toplevel = "vbox_find_peaks" resizable = False def __init__(self, *args, **kwargs): ObjectListStoreView.__init__(self, *args, **kwargs) self[self.subview_toplevel].child_set_property(self["frame_object_param"], "resize", False) def set_selection_state(self, value): super(EditMarkersView, self).set_selection_state(value) self["cmd_match_minerals"].set_sensitive(value is not None) pass # end of class class MatchMineralsView(DialogView): title = "Match minerals" subview_builder = resource_filename(__name__, "../glade/match_minerals.glade") subview_toplevel = "tbl_match_minerals" modal = True def __init__(self, *args, **kwargs): DialogView.__init__(self, *args, **kwargs) self.tv_minerals = self["tv_minerals"] self.tv_matches = self["tv_matches"] pass # end of class class DetectPeaksView(DialogView): title = "Auto detect peaks" subview_builder = resource_filename(__name__, "../glade/find_peaks_dialog.glade") subview_toplevel = "tbl_find_peaks" modal = True resizable = False widget_groups = { 'full_mode_only': [ "pattern", "lbl_pattern", "hseparator1" ] } def __init__(self, *args, **kwargs): DialogView.__init__(self, *args, **kwargs) self.graph_parent = self["view_graph"] self.setup_matplotlib_widget() def setup_matplotlib_widget(self): #style = Gtk.Style() self.figure = Figure(dpi=72) #, edgecolor=str(style.bg[2]), facecolor=str(style.bg[2])) self.plot = self.figure.add_subplot(111) self.plot.set_ylabel('# of peaks', labelpad=1) self.plot.set_xlabel('Threshold', labelpad=1) self.plot.autoscale_view() self.figure.subplots_adjust(left=0.15, right=0.875, top=0.875, bottom=0.15) self.matlib_canvas = FigureCanvasGTK(self.figure) self.graph_parent.add(self.matlib_canvas) self.graph_parent.show_all() pass # end of classPyXRD-0.8.4/pyxrd/specimen/views/specimens.py000066400000000000000000000057361363064711000211530ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from pkg_resources import resource_filename # @UnresolvedImport from pyxrd.generic.views import DialogView, BaseView, HasChildView from pyxrd.goniometer.views import InlineGoniometerView from pyxrd.generic.views.line_views import CalculatedLinePropertiesView, ExperimentalLinePropertiesView class SpecimenView(DialogView, HasChildView): title = "Edit Specimen" subview_builder = resource_filename(__name__, "../glade/specimen.glade") subview_toplevel = "edit_specimen" resizable = False widget_format = "specimen_%s" widget_groups = { 'full_mode_only': [ "specimen_display_calculated", "specimen_display_stats_in_lbl", "specimen_display_phases", "vbox_calculated_data_tv", "lbl_specimen_calculated", "vbox_exclusion_ranges_tv", "lbl_tabexclusions", "general_separator", "specimen_display_residuals", "specimen_display_derivatives" ] } gonio_container = widget_format % "goniometer" gonio_view = None calc_line_container = widget_format % "calc_line" calc_line_view = None exp_line_container = widget_format % "exp_line" exp_line_view = None def __init__(self, *args, **kwargs): super(SpecimenView, self).__init__(*args, **kwargs) self._init_child_views() self._add_child_views() _children_ready = False def _init_child_views(self): if not self._children_ready: self.gonio_view = InlineGoniometerView(parent=self) self.calc_line_view = CalculatedLinePropertiesView(parent=self) self.exp_line_view = ExperimentalLinePropertiesView(parent=self) self._children_ready = True def _add_child_views(self): self._init_child_views() # Add in-line gonio view: top = self.gonio_view.get_top_widget() self._add_child_view(top, self[self.gonio_container]) # Add in-line calculated line properties view: top = self.calc_line_view.get_top_widget() self._add_child_view(top, self[self.calc_line_container]) # Add in-line experimental line properties view: top = self.exp_line_view.get_top_widget() self._add_child_view(top, self[self.exp_line_container]) def set_layout_mode(self, state): super(SpecimenView, self).set_layout_mode(state) if self._children_ready: self.gonio_view.set_layout_mode(state) self.calc_line_view.set_layout_mode(state) self.exp_line_view.set_layout_mode(state) pass # end of class class StatisticsView(BaseView): builder = resource_filename(__name__, "../specimen/glade/statistics.glade") top = "statistics_box" def __init__(self, *args, **kwargs): BaseView.__init__(self, *args, **kwargs) pass # end of class PyXRD-0.8.4/pyxrd/stacktracer.py000066400000000000000000000056431363064711000165300ustar00rootroot00000000000000"""Stack tracer for multi-threaded applications. Usage: import stacktracer stacktracer.start_trace("trace.html",interval=5,auto=True) # Set auto flag to always update file! .... stacktracer.stop_trace() """ import sys import traceback from pygments import highlight from pygments.lexers import PythonLexer # @UnresolvedImport from pygments.formatters import HtmlFormatter # @UnresolvedImport # Taken from http://bzimmer.ziclix.com/2008/12/17/python-thread-dumps/ def stacktraces(): code = [] for threadId, stack in list(sys._current_frames().items()): code.append("\n# ThreadID: %s" % threadId) for filename, lineno, name, line in traceback.extract_stack(stack): code.append('File: "%s", line %d, in %s' % (filename, lineno, name)) if line: code.append(" %s" % (line.strip())) return highlight("\n".join(code), PythonLexer(), HtmlFormatter( full=False, # style="native", noclasses=True, )) # This part was made by nagylzs import os import time import threading import logging logger = logging.getLogger(__name__) class TraceDumper(threading.Thread): """Dump stack traces into a given file periodically.""" def __init__(self, fpath, interval, auto): """ @param fpath: File path to output HTML (stack trace file) @param auto: Set flag (True) to update trace continuously. Clear flag (False) to update only if file not exists. (Then delete the file to force update.) @param interval: In seconds: how often to update the trace file. """ assert(interval > 0.1) self.auto = auto self.interval = interval self.fpath = os.path.abspath(fpath) logger.info("Tracing at file %s" % self.fpath) self.stop_requested = threading.Event() threading.Thread.__init__(self) def run(self): while not self.stop_requested.isSet(): time.sleep(self.interval) if self.auto or not os.path.isfile(self.fpath): self.stacktraces() def stop(self): self.stop_requested.set() self.join() try: if os.path.isfile(self.fpath): os.unlink(self.fpath) except: pass def stacktraces(self): fout = open(self.fpath, "w+") try: fout.write(stacktraces()) finally: fout.close() _tracer = None def trace_start(fpath, interval=5, auto=True): """Start tracing into the given file.""" global _tracer if _tracer is None: _tracer = TraceDumper(fpath, interval, auto) _tracer.setDaemon(True) _tracer.start() else: raise Exception("Already tracing to %s" % _tracer.fpath) def trace_stop(): """Stop tracing.""" global _tracer if _tracer is None: raise Exception("Not tracing, cannot stop.") else: _tracer.stop() _tracer = None PyXRD-0.8.4/run_tests.py000066400000000000000000000005411363064711000150720ustar00rootroot00000000000000#!/usr/bin/env python3 # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import sys, os base = os.path.dirname(__file__) sys.path.insert(0, os.path.join(base, "pyxrd")) if __name__ == "__main__": from test import run_all_tests run_all_tests() PyXRD-0.8.4/setup.py000066400000000000000000000036411363064711000142100ustar00rootroot00000000000000#!/usr/bin/env python3 import os from setuptools import setup, find_packages def get_version(): from pyxrd.__version import __version__ if __version__.startswith("v"): __version__ = __version__.replace("v", "") return "%s" % __version__ def get_install_requires(): return [ 'setuptools', 'numpy>=1.11', 'scipy>=1.1.0', 'matplotlib>=2.2.2', 'Pyro4>=4.41', 'deap>=1.0.1', 'cairocffi', 'pygobject>=3.20' ] def read(fname): with open(os.path.join(os.path.dirname(__file__), fname)) as f: return f.read() setup( name="PyXRD", version=get_version(), description="PyXRD is a python implementation of the matrix algorithm developed for the X-ray diffraction analysis of disordered lamellar structures", long_description=read('README.md'), keywords="XRD disorder mixed-layers", author="Mathijs Dumon", author_email="mathijs.dumon@gmail.com", url="http://github.org/mathijs-dumon/PyXRD", license="BSD", setup_requires=[ "setuptools_git >= 1.2", ], packages=find_packages(exclude=["test.*", "test", "tests_mvc", "tests_mvc.*"]), include_package_data=True, install_requires=get_install_requires(), zip_safe=False, classifiers=[ "Development Status :: 4 - Beta", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.4", "Environment :: Win32 (MS Windows)", "Environment :: X11 Applications :: Gnome", "Environment :: X11 Applications :: GTK", "Intended Audience :: End Users/Desktop", "Intended Audience :: Science/Research", "Topic :: Utilities", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Visualization", "Natural Language :: English", "License :: OSI Approved :: BSD License", ], ) PyXRD-0.8.4/test/000077500000000000000000000000001363064711000134515ustar00rootroot00000000000000PyXRD-0.8.4/test/__init__.py000066400000000000000000000005601363064711000155630ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest def run_all_tests(*args, **kwargs): all_tests = get_all_tests() unittest.TextTestRunner().run(all_tests) def get_all_tests(): return unittest.TestLoader().discover('.') PyXRD-0.8.4/test/setup.py000066400000000000000000000005411363064711000151630ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. """ This file contains some flags which (partially) control if certain tests are run. This way lengthy tests can be skipped in the default set. """ SKIP_REFINEMENT_TEST = True PyXRD-0.8.4/test/test_atoms/000077500000000000000000000000001363064711000156335ustar00rootroot00000000000000PyXRD-0.8.4/test/test_atoms/__init__.py000066400000000000000000000001231363064711000177400ustar00rootroot00000000000000#TODO: # - Combined AtomType & Atom tests # - Test Controller & View creation etc. PyXRD-0.8.4/test/test_atoms/test_atom.py000066400000000000000000000064471363064711000202170ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest from mock import Mock from test.utils import create_object_attribute_test from pyxrd.atoms.models import Atom __all__ = [ 'TestAtom', ] class TestAtom(unittest.TestCase): atom_type = None def setUp(self): self.atom = Atom() def tearDown(self): del self.atom def get_mocked_hierarchy(self): oxygen = Mock() oxygen.name = "O1-" hydrogen = Mock() hydrogen.name = "H+" project = Mock() project.atom_types = [oxygen, hydrogen] phase = Mock() phase.attach_mock(project, 'project') component = Mock() component.attach_mock(phase, 'phase') return oxygen, hydrogen, component, phase, project def test_not_none(self): self.assertIsNotNone(self.atom) def test_data_object(self): self.assertIsNotNone(self.atom.data_object) test_name = create_object_attribute_test("atom", "name", "Test Name") test_pn = create_object_attribute_test("atom", "pn", 3) test_default_z = create_object_attribute_test("atom", "default_z", 5.3) test_stretch_values = create_object_attribute_test("atom", "stretch_values", True) def test_parent(self): parent_atom = Atom(name="Parent") self.atom.parent = parent_atom self.assertEqual(self.atom.parent, parent_atom) def test_set_atom_type(self): oxygen, hydrogen, component, _, _ = self.get_mocked_hierarchy() atom = Atom( parent = component, name = "O", atom_type = oxygen, ) atom.atom_type = hydrogen self.assertEqual(atom.atom_type, hydrogen) def test_z_calculations(self): # Checks wether the atom can calculate stretched values: # 1. When everything is set up the way it should be: default_z = 9.0 lattice_d = 5.4 factor = 0.5 parent = Mock() parent.configure_mock(**{ 'get_interlayer_stretch_factors.return_value': (lattice_d, factor) }) atom = Atom(parent=parent) atom.stretch_values = True atom.default_z = default_z z = atom.z self.assertEqual(z, lattice_d + (default_z - lattice_d) * factor) # 2. When no component is set, but stretched is True: should not raise an error, but simple ignore the stretching atom.parent = None z = atom.z def test_structure_factors(self): import numpy as np rng = 2.0 * np.sin(np.arange(30)) / 0.154056 res = self.atom.get_structure_factors(rng) self.assertIsNotNone(res) def test_loads_atom_type_by_name(self): atom_json_dict = { "uuid": "878341b04e9e11e2b238150ae229a525", "name": "O", "default_z": 0.66, "pn": 6.0, "atom_type_name": "O1-" } oxygen, _, component, _, _ = self.get_mocked_hierarchy() atom = Atom.from_json(parent = component, **atom_json_dict) atom.resolve_json_references() self.assertEqual(atom.atom_type, oxygen) pass # end of class PyXRD-0.8.4/test/test_atoms/test_atom_type.py000066400000000000000000000054071363064711000212530ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest from test.utils import create_object_attribute_test from pyxrd.atoms.models import AtomType __all__ = [ 'TestAtomType', ] class TestAtomType(unittest.TestCase): atom_type = None def setUp(self): self.atom_type = AtomType() def tearDown(self): del self.atom_type def test_not_none(self): self.assertIsNotNone(self.atom_type) def test_data_object(self): self.assertIsNotNone(self.atom_type.data_object) test_name = create_object_attribute_test("atom_type", "name", "Test Name") test_charge = create_object_attribute_test("atom_type", "charge", -5) test_debye = create_object_attribute_test("atom_type", "debye", 1.0) test_weight = create_object_attribute_test("atom_type", "weight", 60.123) test_atom_nr = create_object_attribute_test("atom_type", "atom_nr", 20) test_par_c = create_object_attribute_test("atom_type", "par_c", 10.2) test_par_a1 = create_object_attribute_test("atom_type", "par_a1", 10.2) test_par_a2 = create_object_attribute_test("atom_type", "par_a2", 10.2) test_par_a3 = create_object_attribute_test("atom_type", "par_a3", 10.2) test_par_a4 = create_object_attribute_test("atom_type", "par_a4", 10.2) test_par_a5 = create_object_attribute_test("atom_type", "par_a5", 10.2) test_par_b1 = create_object_attribute_test("atom_type", "par_b1", 10.2) test_par_b2 = create_object_attribute_test("atom_type", "par_b2", 10.2) test_par_b3 = create_object_attribute_test("atom_type", "par_b3", 10.2) test_par_b4 = create_object_attribute_test("atom_type", "par_b4", 10.2) test_par_b5 = create_object_attribute_test("atom_type", "par_b5", 10.2) def test_parent(self): parent_atom_type = AtomType(name="Parent") self.atom_type.parent = parent_atom_type self.assertEqual(self.atom_type.parent, parent_atom_type) def test_get_atomic_scattering_factors(self): import numpy as np stl_range = np.array([ 0.1152, 0.5756, 1.1469 ]) self.atom_type.par_a1 = 10.2 self.atom_type.par_a2 = 10.2 self.atom_type.par_a3 = 10.2 self.atom_type.par_a4 = 10.2 self.atom_type.par_a5 = 10.2 self.atom_type.par_b1 = 10.2 self.atom_type.par_b2 = 10.2 self.atom_type.par_b3 = 10.2 self.atom_type.par_b4 = 10.2 self.atom_type.par_b5 = 10.2 self.atom_type.par_c = 20.2 self.assertListEqual( self.atom_type.get_atomic_scattering_factors(stl_range).tolist(), [ 71.1827439324707, 70.77093939463964, 69.51772020477823 ] ) pass # end of class PyXRD-0.8.4/test/test_calculations/000077500000000000000000000000001363064711000171715ustar00rootroot00000000000000PyXRD-0.8.4/test/test_calculations/__init__.py000066400000000000000000000000001363064711000212700ustar00rootroot00000000000000PyXRD-0.8.4/test/test_calculations/test_CSDS.py000066400000000000000000000023741363064711000213440ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest import numpy as np from pyxrd.calculations.data_objects import CSDSData from pyxrd.calculations.CSDS import calculate_distribution __all__ = [ 'TestCSDSCalcs', ] class TestCSDSCalcs(unittest.TestCase): def setUp(self): self.CSDS_data_kwargs = dict( average = 10, maximum = 50, minimum = 1, alpha_scale = 0.9485, alpha_offset = 0.017, beta_scale = 0.1032, beta_offset = 0.0034 ) self.CSDS_data = CSDSData(**self.CSDS_data_kwargs) def tearDown(self): del self.CSDS_data def test_not_none(self): self.assertIsNotNone(self.CSDS_data) def test_attributes(self): for key, value in self.CSDS_data_kwargs.items(): self.assertEquals(getattr(self.CSDS_data, key), value) def test_calculate_distribution(self): result = calculate_distribution( self.CSDS_data ) self.assertIsNotNone(result) self.assertEquals(len(result), 2) pass # end of class calculate_distributionPyXRD-0.8.4/test/test_calculations/test_atoms.py000066400000000000000000000033231363064711000217260ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest import numpy as np from pyxrd.calculations.data_objects import AtomData, AtomTypeData from pyxrd.calculations.atoms import get_atomic_scattering_factor, get_structure_factor __all__ = [ 'TestAtomCalcs', ] class TestAtomCalcs(unittest.TestCase): def setUp(self): self.atom_type_data = AtomTypeData( # this is the data for a H atom par_a = np.asanyarray([0.413048,0.294953,0.187491,0.080701,0.023736]), par_b = np.asanyarray([15.569946,32.398468,5.711404,61.889874,1.334118]), par_c = 0.000049, debye = 0 ) self.atom_data = AtomData( atom_type = self.atom_type_data, pn = 1, default_z = 0, z = 0 ) def tearDown(self): del self.atom_data del self.atom_type_data def test_not_none(self): self.assertIsNotNone(self.atom_type_data) self.assertIsNotNone(self.atom_data) def test_scattering_factor(self): result = get_atomic_scattering_factor( np.asanyarray([2.2551711385, 2.478038901, 2.7001518288, 2.9214422642, 3.1418428, 3.3612863, 3.5797059197, 3.7970351263]), self.atom_type_data ) self.assertIsNotNone(result) def test_structure_factor(self): result = get_structure_factor( np.asanyarray([2.2551711385, 2.478038901, 2.7001518288, 2.9214422642, 3.1418428, 3.3612863, 3.5797059197, 3.7970351263]), self.atom_data ) self.assertIsNotNone(result) pass # end of class PyXRD-0.8.4/test/test_calculations/test_components.py000066400000000000000000000037511363064711000227750ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest import numpy as np from pyxrd.calculations.data_objects import AtomData, AtomTypeData, ComponentData from pyxrd.calculations.components import get_factors __all__ = [ 'TestComponentCalcs', ] class TestComponentCalcs(unittest.TestCase): def setUp(self): self.atom_type_data = AtomTypeData( # this is the data for a H atom par_a = np.asanyarray([0.413048,0.294953,0.187491,0.080701,0.023736]), par_b = np.asanyarray([15.569946,32.398468,5.711404,61.889874,1.334118]), par_c = 0.000049, debye = 0 ) self.layer_atom = AtomData( atom_type = self.atom_type_data, pn = 1, default_z = 0, z = 0 ) self.interlayer_atom = AtomData( atom_type = self.atom_type_data, pn = 1, default_z = 0.55, z = 0.55 ) self.component_data = ComponentData( layer_atoms = [self.layer_atom, ], interlayer_atoms = [self.interlayer_atom, ], volume = 1.0, weight = 1.0, d001 = 0.55, default_c = 0.6, delta_c = 0.02, lattice_d = 0.45 ) def tearDown(self): del self.interlayer_atom del self.layer_atom del self.atom_type_data def test_not_none(self): self.assertIsNotNone(self.atom_type_data) self.assertIsNotNone(self.layer_atom) self.assertIsNotNone(self.interlayer_atom) def test_get_factors(self): result = get_factors( np.asanyarray([2.2551711385, 2.478038901, 2.7001518288, 2.9214422642, 3.1418428, 3.3612863, 3.5797059197, 3.7970351263]), self.component_data ) self.assertIsNotNone(result) pass # end of class PyXRD-0.8.4/test/test_calculations/test_goniometer.py000066400000000000000000000133271363064711000227600ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest import numpy as np from pyxrd.calculations.data_objects import GonioData from pyxrd.calculations.goniometer import ( get_2t_from_nm, get_nm_from_2t, get_nm_from_t, get_t_from_nm, get_S, get_fixed_to_ads_correction_range, get_lorentz_polarisation_factor ) __all__ = [ 'TestGoniometerCalcs', ] class TestGoniometerCalcs(unittest.TestCase): def setUp(self): self.goniometer_data_kwargs = dict( min_2theta = 3.0, max_2theta = 45, mcr_2theta = 0, steps = 2500, soller1 = 2.3, soller2 = 2.3, divergence = 0.5, has_ads = False, ads_fact = 1.0, ads_phase_fact = 1.0, ads_phase_shift = 0.0, ads_const = 0.0, radius = 24.0, wavelength_distribution = [ [0.1544426,0.955148885], [0.153475,0.044851115], ] ) self.goniometer_data = GonioData(**self.goniometer_data_kwargs) def tearDown(self): del self.goniometer_data def test_not_none(self): self.assertIsNotNone(self.goniometer_data) def test_attributes(self): for key, value in self.goniometer_data_kwargs.items(): self.assertEquals(getattr(self.goniometer_data, key), value) def test_fixed_to_ads_correction_range(self): result = get_fixed_to_ads_correction_range( np.asanyarray([2.2551711385, 2.478038901, 2.7001518288, 2.9214422642, 3.1418428, 3.3612863, 3.5797059197, 3.7970351263]), self.goniometer_data ) self.assertIsNotNone(result) self.assertEquals(np.allclose( result, [7.74814435e-01, 6.15920411e-01, 4.27242611e-01, 2.18376385e-01, -2.50146408e-04, -2.17930643e-01, -4.24231682e-01, -6.09510086e-01] ), True) def test_lorentz_polarisation_factor(self): result = get_lorentz_polarisation_factor( np.asanyarray([2.2551711385, 2.478038901, 2.7001518288, 2.9214422642, 3.1418428, 3.3612863, 3.5797059197, 3.7970351263]), 12, self.goniometer_data.soller1, self.goniometer_data.soller2, self.goniometer_data.mcr_2theta ) self.assertIsNotNone(result) self.assertEquals(np.allclose( result, [3.00643375e-03, 4.83799816e-03, 1.33173586e-02, 6.56714627e-02, 5.11941694e+02, 6.59637604e-02, 1.35695934e-02, 4.97673826e-03] #[3.67253512e-01, 5.84371208e-01, 1.55735810e+00, 6.54044238e+00, 7.99531670e+03, 6.56417033e+00, 1.58546668e+00, 6.00742559e-01] ), True) pass def test_S(self): # result = get_S(2.5, 2.5); self.assertIsNotNone(result) self.assertEquals(len(result), 2) S, S1S2 = result self.assertAlmostEquals(S, 1.7677669529663689) self.assertAlmostEquals(S1S2, 6.25) def test_2t_from_nm_positive(self): # boundary condition: positive result = get_2t_from_nm(0.716) self.assertIsNotNone(result) self.assertAlmostEquals(result, 12.351779659) def test_2t_from_nm_zero(self): # boundary condition: 0 result = get_2t_from_nm(0) self.assertIsNotNone(result) self.assertAlmostEquals(result, 0) def test_2t_from_nm_negative(self): # boundary condition: negative result = get_2t_from_nm(-0.716) self.assertIsNotNone(result) self.assertAlmostEquals(result, -12.351779659) def test_nm_from_2t_positive(self): # boundary condition: positive result = get_nm_from_2t(12.351779659) self.assertIsNotNone(result) self.assertAlmostEquals(result, 0.716) def test_nm_from_2t_zero(self): # boundary condition: 0 result = get_nm_from_2t(0) self.assertIsNotNone(result) self.assertAlmostEquals(result, 1e+16) result = get_nm_from_2t(0, zero_for_inf=True) self.assertIsNotNone(result) self.assertAlmostEquals(result, 0) def test_nm_from_2t_negative(self): # boundary condition: negative result = get_nm_from_2t(-12.351779659) self.assertIsNotNone(result) self.assertAlmostEquals(result, -0.716) def test_nm_from_t_positive(self): # boundary condition: positive result = get_nm_from_t(6.17588983) self.assertIsNotNone(result) self.assertAlmostEquals(result, 0.716) def test_nm_from_t_zero(self): # boundary condition: 0 result = get_nm_from_t(0) self.assertIsNotNone(result) self.assertAlmostEquals(result, 1e+16) result = get_nm_from_t(0, zero_for_inf=True) self.assertIsNotNone(result) self.assertAlmostEquals(result, 0) def test_nm_from_t_negative(self): # boundary condition: negative result = get_nm_from_t(-6.17588983) self.assertIsNotNone(result) self.assertAlmostEquals(result, -0.716) def test_t_from_nm_positive(self): # boundary condition: positive result = get_t_from_nm(0.716) self.assertIsNotNone(result) self.assertAlmostEquals(result, 6.17588983) def test_t_from_nm_zero(self): # boundary condition: 0 result = get_t_from_nm(0) self.assertIsNotNone(result) self.assertAlmostEquals(result, 0) def test_t_from_nm_negative(self): # boundary condition: negative result = get_t_from_nm(-0.716) self.assertIsNotNone(result) self.assertAlmostEquals(result, -6.17588983) pass # end of class PyXRD-0.8.4/test/test_generic/000077500000000000000000000000001363064711000161245ustar00rootroot00000000000000PyXRD-0.8.4/test/test_generic/__init__.py000066400000000000000000000000001363064711000202230ustar00rootroot00000000000000PyXRD-0.8.4/test/test_generic/test_io/000077500000000000000000000000001363064711000175725ustar00rootroot00000000000000PyXRD-0.8.4/test/test_generic/test_io/__init__.py000066400000000000000000000000001363064711000216710ustar00rootroot00000000000000PyXRD-0.8.4/test/test_generic/test_io/test_custom_io.py000066400000000000000000000044601363064711000232100ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os import unittest from io import StringIO from pyxrd.generic.io import storables, Storable from pyxrd.file_parsers.json_parser import JSONParser __all__ = [ 'TestParserMixin', ] def load_data_from_files(*files): basepath = os.path.realpath(os.getcwd()) for fname in files: with open(basepath + "/" + fname, 'rb') as f: yield f.read() class TestStorable(unittest.TestCase): @storables.register() class DummyStorable(Storable): __storables__ = [ "name", "data", "my_daddy" ] class Meta(Storable.Meta): store_id = "DummyStorable" def __init__(self, name, data, my_daddy): super(TestStorable.DummyStorable, self).__init__() self.name = name self.data = data self.my_daddy = my_daddy def setUp(self): self.daddy = self.DummyStorable('Daddy Dummy', list(range(50)), None) self.child = self.DummyStorable('Child Dummy', list(range(5)), self.daddy) def tearDown(self): del self.daddy del self.child def test_setup(self): self.assertNotEqual(self.daddy, None) self.assertNotEqual(self.child, None) def test_encoding(self): self.daddy_dump = self.daddy.dump_object() self.child_dump = self.child.dump_object() self.assertIn("".join(self.daddy_dump.split()), "".join(self.child_dump.split())) def test_decoding(self): child_encoded = """ { "type": "DummyStorable", "properties": { "name": "Child Dummy", "data": [ 0, 1, 2, 3, 4 ], "my_daddy": { "type": "DummyStorable", "properties": { "name": "Daddy Dummy", "data": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ], "my_daddy": null } } } }""" f = StringIO(child_encoded) decoded_child = JSONParser.parse(f) self.assertNotEqual(decoded_child, None) self.assertNotEqual(decoded_child.my_daddy, None) pass # end of class PyXRD-0.8.4/test/test_generic/test_io/test_data_registry.py000066400000000000000000000026451363064711000240530ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import shutil import os import tempfile import unittest from pyxrd.generic.exceptions import AlreadyRegistered, NotRegistered from pyxrd.generic.io.data_registry import DataRegistry __all__ = [ 'TestDataRegistry', ] class TestDataRegistry(unittest.TestCase): def setUp(self): self.base_temp_dir = tempfile.mkdtemp() self.dirs = [ ("TEST1", os.path.join(self.base_temp_dir, "test1/"), None), ("TEST2", "test2/", "TEST1"), ] self.files = [ ("ROOTFILE", "root.txt", None), ("TEST1_FILE", "test1file.txt", "TEST1"), ("TEST2_FILE", "test2file.txt", "TEST2"), ] self.data_reg = DataRegistry(dirs=self.dirs, files=self.files) pass def tearDown(self): shutil.rmtree(self.base_temp_dir) del self.data_reg pass def test_exceptions(self): with self.assertRaises(NotRegistered): self.data_reg.get_directory_path("TEST3") with self.assertRaises(AlreadyRegistered): self.data_reg.register_data_directory("TEST1", "false_test1/", None) def test_iter(self): for path in self.data_reg.get_all_files(): self.assertNotIn(path, [None, ""]) pass # end of class PyXRD-0.8.4/test/test_generic/test_io/test_file_parsers.py000066400000000000000000000020571363064711000236650ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import os, unittest from pyxrd.file_parsers.base_parser import BaseParser __all__ = [ 'TestParserMixin', ] def load_data_from_files(*files): basepath = os.path.realpath(os.getcwd()) for fname in files: with open(basepath + "/" + fname, 'rb') as fp: yield fp class BaseTestParsers(object): class BaseTestParser(unittest.TestCase): parser_class = BaseParser file_data = [ "", ] def test_description(self): self.assertNotEqual(self.parser_class.description, "") def test_filters(self): self.assertIsNotNone(self.parser_class.file_filter) def test_parsing(self): for fp in self.file_data: data_objects = self.parser_class.parse(fp) self.assertGreater(len(data_objects), 0) # TODO: # - check arguments such as close. PyXRD-0.8.4/test/test_generic/test_io/test_xrd_parsers/000077500000000000000000000000001363064711000231655ustar00rootroot00000000000000PyXRD-0.8.4/test/test_generic/test_io/test_xrd_parsers/__init__.py000066400000000000000000000000001363064711000252640ustar00rootroot00000000000000PyXRD-0.8.4/test/test_generic/test_io/test_xrd_parsers/brk_raw1.raw000066400000000000000000000071141363064711000254130ustar00rootroot00000000000000RAW l@ #=)\@VINAIGRE a2??q P RAW hq 8;CH EXE ̈́@&DD@DD@)D DDDDDDDD DD D@DDD@D@DD DDDDCDCCCCCDCCCCCCCCCCCDCCCCCCCCCCCC@DCDC DD DDDD-D@D6D;D@:DAD@UD@bDrD|D`DDD`DD@DD EE%E?ECECE&E'E;EPIE `E0EȔEةEXEEF%F(?F@IF9F< FErEDrD@BD@DDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC~CCCCCvCCtCtC~C^ChCCC~CCkCCCCCtCCCCyC|CYCrCCCrCwC{CmCjCqCsCtCtC~CCiCvCCCCjC}CCCCCCCCCCC DD/DZDDD DDBDCCCjCxCkCWC^CeCcCTC}CCUCaCsCsCCCCCCCCCCCCDD1D=D`D@DDD@jD"D DCCCCCChC\ChCwCFCUC;ChCWCQCoCeCCvCCCCCCCC DDCCCCCCCCCCDDDCCCD-DZD@YD.DCCCCCDD0D@9D-DCCCtCfCjCrCMCsCxCpCCCCCC@DD+DCCCgCkCNCZCJC8C@CZC;C=C5C-CTC,CCFCFC:C:C-CSCPC3CWCLCUCPCiCUC[CmCSCMC\C_CeCsCICNCYC|CuCCCCCCCCCCtCCwCwCnCkCiCCWCTCoCC`CCuCCCCCCCCD@D5D@DDDDeD@!DDCCD@,DQDgD[DDDD@DCCCCCCD@D DCCCCCCCCCCCCCC@D@-DGD4D2D4D[DD D`DD@DhDDCCCCCCCCCCCCCC DDCD[D@jD@FD!DDDD@D@DCCyCCCCCCCCCCCCDDhDDDDD D DADCCCCCCCCCCCDD@"DCCCCCCCCDDDCCC DIDDDDDQD DCCCCCCRCnC[CnCZCVCACKCXCWCgCfCoCYCvC|CCCCsCZCCCCPyXRD-0.8.4/test/test_generic/test_io/test_xrd_parsers/brk_raw2.raw000066400000000000000000000712101363064711000254120ustar00rootroot00000000000000RAW2ZINCITE.DAT Converted from UXD format by XCH Version 1 19-12-199721:57 Cua2???*:?$t<S @@ ף<@ @$t$t$t$t^C;CFCFCFCC;C>C.CCDC0C#C)CCC6C;C#C+CC)C&CCC&C#C)C)CCCCCCCCCC#C#C CC.CCC CCCC C CBCCC CCCCCCC CCCB!CCC C CBCB CBBBBBBCCBBCBBCCBBCBBCCBBBBBBBCCCBBBBBBBBBBCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBxBBBBBBBBBBBBBBxBBBBBBBBBBBBBBBBBBBBdBBBBBBBlBBBBBBxBBBBBBBBBBBBBxBBBxB`BxBlBBBBxBBpBBBBBxBBBBBBBBBBpBlBBBBBBxBBBBBBBxBBBBBBTBBBBBBBBBBBBBBBBBBBBBdBBBBBHBBBBxBBBBBBBBBBBBBBpBxBpBBlBXBCOCgCCCCCCCCCCD.DYDDD`D#EVEEE< F6F _FhqFdlFYFC0CCCC!CCCCB)CCCBCCCBCCCC CCC C#CC)C C+C#C&C#C0C;CXCOCaCdCOC^CCjCUCCCCCCCCCCDD"DAD@sD`DDDE)EaEpEEF(GFFFFFFŸFfFfF:F4FE0E'EDDD{DPD@*DD DCCCCCCCvCXCFC8C>CC0CCCCCCCCCCBCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB`BBBBBxBTBHBBBlBBB`B`BBxBBLBBBBpBxB`BpBXBxB,BBB`B`BBxBTBC8C8C&CCC CCCCCC!C#C#C6C0C0C>COCjCgCCCCCDMD@DDEE7E1EE D@DDDD`DD D@D@DnD/DCCCXC8CC)CBCBBBBBBBBBBBBBBBBBBBlBBBxBB`BBBBBBxBBBBBxBBBlBxBBB`BBBlBBdBpBxBBBHB`BBB0BxBHBxBBLBCXCvCyCCCCCCCCmCjCCCBBBBBBBBBBBBBBBBBBBCBCC&C)CXCCCCC.DUD DDDDDeD@&DDCCCCCCCCCCCD@D DDCCCpC;CDCCBBBBBBBBBBBBBBTBBBpBpBBxBBB`BTBlBxBBxBBBlBdBdBHBBxBlBHBB,BxBTB8BBxBTBBXBBDB`B`BHBTBBBdBTBBD@DDCCC;CDC>C&C)CFCDCCCCCCCCCCCCFC0CCCBBBBBBBBBBlBBBBBBBHBBBBBxBBBBxBlBlBBpB`B`BBHBlBTBxBBdBLBDB`B`BHBDBlBC>CmCyCCCC@D#DFDD@DDD`D`DDDD`DSD DCCCCCCCCCCCD3DPDhD@sDxDgDBD+DDCCCyCXCICDC!CCCBBBBBBBBBBBBBBBBBBB`BBBBBBBBxB`BB`BpBBBBBBBBBpBBTBBCDC3COCUCCCCCCCD DDCCCCpCFCCCCBBBBBBBBBBBBBBdBBpBBBBxBBlBxBxBxBBpBdB`BlBHB`BDB`BdBpBlB`BBTBTBXBlBpBxBBpBHB`BBCOCCCCCCD2D@EDSDgDeD\D@OD)D@DCCCCC|CmCOC0C0C.CC!CC#CCCCOC>CIC[CCCCCCCCCCCCCCgCUCOC;CCCBCBBBBBBBBBBBBBBBBBBBBBBBTBBB`BBBBdBB`BBTBxBBBB,BBBlBBpBBBdBBBBdBlBBBBBBBBBBBBBBBBBBBBBBBBC&C.C;CUCpCvCaCgCOCLC8C#CCBBCBBBBBBBBBBBBBBBBBBBBC CBCCCCCBBBBBBBBBBBBBBBBpBBBBxBBBBBHBBpBBBBBBBB`BBBBBBBBBBBBBBBBCC)CACRCjCCCCCCCCCpCFCLC>CC#CCBBBBBBBBBBBBBBBBBBBBCCC)CFC8C.CRCAC#C0CCCCBBBBBBBBBBBBBBBBBBdBHBBBBxBxBBBBBxBdBTBdBBTBBBBHBBBBBxBBTBBBBBlBTBBB`BBlBB`BBBBTBxBxBlBlBHBBdBlB`BBBxBBxBBdBxBBlBpBBHBlBBpBBBBB`BBBBBxBBBdBHB`BBxBBBBBBBBBBBBB`BpBBxBxBBBxBBBBlBBBBBBBBBxBBBBBBBBBBBBBBBBBBBCBCCBC&C&CUC^CpCCCCCC DD+D2D%DDD@DCCCCCCgCFC8COCC!CCBBCCCBBBBBBBCBCC!C6CFCOCCCCCCCCCCCyCCgCXC>CC)CCBCBBBBBBBBBBBBBBBBBBBBBBTBBBBBBBBBBBxBBlBBBBBBxBBBxBBB`BBBBBlBBXBBxB`BBB`BBBBLBBBBXB`BBBBBxB\007\00A\00I\00B\00<\00J\00G\00D\00J\00E\00E\00E\00L\00B\00F\00F\00E\00B\00D\008\00A\00;\00C\00G\00I\00E\00F\00E\00;\00J\00E\00D\00D\00D\00I\00?\00R\00;\00H\00;\00>\00B\00I\00@\00G\00<\00@\00D\00;\00>\00C\00I\00G\00:\00I\00A\00E\00B\00G\00L\00A\00I\00C\00J\00G\00B\00B\00E\00A\00B\00>\00A\00?\00<\00E\00>\00@\00:\00A\00@\00B\00<\00F\00H\00:\00>\00<\00B\00A\00E\00:\00K\00C\00?\007\00B\00A\00>\00B\00G\00C\00B\00B\007\00?\00I\00E\00B\00E\00>\00:\00M\00L\00?\00A\00B\00E\003\009\009\00B\00>\00A\009\00>\00?\00=\00B\00>\00G\006\00D\00?\006\00E\009\00G\00>\00;\00:\00?\00<\00E\009\00@\00B\00?\00=\00=\00@\009\00@\00D\00E\00A\00;\00=\00;\002\00<\00=\00G\00>\00B\00@\00=\00G\00;\00>\00=\00B\001\006\008\00<\00D\00=\009\00J\006\00;\009\009\00;\00>\00E\009\00=\001\009\00A\00?\00:\00>\00>\00A\009\00E\008\006\00:\009\00=\00<\00?\009\00?\00B\006\00=\00=\00@\008\00:\00;\00=\00@\004\00=\004\006\00;\00B\009\007\00A\009\005\005\00I\00@\00A\00<\009\00A\007\00:\005\00/\007\004\005\00C\009\00>\008\002\00/\009\009\005\00.\007\00B\00/\009\00?\00/\002\00B\00>\00<\009\005\00,\009\003\00;\00/\00.\00:\00>\00>\004\00:\00;\00<\00:\00<\003\009\006\00H\008\005\008\009\006\000\005\004\000\00;\00".encode()) , ] pass # end of class PyXRD-0.8.4/test/test_generic/test_io/test_xrd_parsers/test_udf_parser.py000066400000000000000000000016741363064711000267400ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest from io import StringIO from test.test_generic.test_io.test_file_parsers import BaseTestParsers from pyxrd.file_parsers.xrd_parsers import UDFParser __all__ = [ 'TestUDFParser', ] class TestUDFParser(BaseTestParsers.BaseTestParser): parser_class = UDFParser file_data = [ StringIO(r"""SampleIdent,Sample5 ,/ Title1,Dat2rit program ,/ Title2,Sample5 ,/ DataAngleRange, 5.0000, 5.6400,/ ScanStepSize, 0.020,/ RawScan 8000, 7000, 6000, 5000, 4000, 3000, 2000, 1000 800, 700, 600, 500, 400, 300, 200, 100 80, 70, 60, 50, 40, 30, 20, 10 8, 7, 6, 5, 4, 3, 2, 1 0/"""), ] pass # end of class PyXRD-0.8.4/test/test_generic/test_models/000077500000000000000000000000001363064711000204465ustar00rootroot00000000000000PyXRD-0.8.4/test/test_generic/test_models/__init__.py000066400000000000000000000000001363064711000225450ustar00rootroot00000000000000PyXRD-0.8.4/test/test_generic/test_models/test_childmodel.py000066400000000000000000000047541363064711000241750ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest from mvc import Observer from pyxrd.generic.models import ChildModel __all__ = [ 'TestChildModel', ] class TestChildModel(unittest.TestCase): def setUp(self): self.childmodel = ChildModel() class ChildObserver(Observer): added_recieved = False @Observer.observe("added", signal=True) def notify_added(self, model, prop_name, info): self.added_recieved = True removed_recieved = False @Observer.observe("removed", signal=True) def notify_removed(self, model, prop_name, info): self.removed_recieved = True self.observer = ChildObserver(model=self.childmodel) def tearDown(self): self.observer.relieve_model(self.childmodel) del self.observer del self.childmodel def test_not_none(self): self.assertIsNotNone(self.childmodel) def test_signals(self): # Reset flags: self.observer.removed_recieved = False self.observer.added_recieved = False # No parent set, only the added signal is fired, self.childmodel.parent = object() self.assertFalse(self.observer.removed_recieved) self.assertTrue(self.observer.added_recieved) # Reset flags: self.observer.removed_recieved = False self.observer.added_recieved = False # Setting a parent when there is one set; both signals should be fired: parent = object() self.childmodel.parent = parent self.assertTrue(self.observer.removed_recieved) self.assertTrue(self.observer.added_recieved) # Reset flags: self.observer.removed_recieved = False self.observer.added_recieved = False # Setting an identical parent does not fire anything: self.childmodel.parent = parent self.assertFalse(self.observer.removed_recieved) self.assertFalse(self.observer.added_recieved) # Reset flags: self.observer.removed_recieved = False self.observer.added_recieved = False # Setting the parent to None when it was set; should only fire the removed signal: self.childmodel.parent = None self.assertTrue(self.observer.removed_recieved) self.assertFalse(self.observer.added_recieved) pass # end of class PyXRD-0.8.4/test/test_generic/test_models/test_lines.py000066400000000000000000000074321363064711000231770ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest from test.utils import create_object_attribute_test from mvc import Observer from pyxrd.generic.models import ( PyXRDLine, CalculatedLine, ExperimentalLine, ) __all__ = [ 'TestPyXRDLine', 'TestCalculatedLine', 'TestExperimentalLine', ] class LineObserver(Observer): needs_update_recieved = False @Observer.observe("data_changed", signal=True) @Observer.observe("visuals_changed", signal=True) def on_update_needed(self, model, prop_name, info): self.needs_update_recieved = True class BaseTestLines(): class BaseTestLine(unittest.TestCase): def setUp(self): self.line = self.line_type() self.observer = LineObserver(model=self.line) def tearDown(self): self.observer.relieve_model(self.line) del self.observer del self.line def test_not_none(self): self.assertIsNotNone(self.line) test_lw = create_object_attribute_test('line', 'lw', 5) test_color = create_object_attribute_test('line', 'color', '#FF0000') test_label = create_object_attribute_test('line', 'label', '#FF0000') def _set_some_data(self): x = [1, 2, 3, 4, 5, 6, 7 , 8 , 9 , 10, 11, 12 , 13, 14, 15, 16, 17, 18, 19, 20] y = [0, 0, 0, 0, 0, 0, 10, 20, 30, 40, 80, 160, 80, 40, 30, 20, 10, 0 , 0 , 0 ] y = list(zip(y, y)) self.line.set_data(x, y) def test_updates(self): self.observer.needs_update_recieved = False self._set_some_data() self.assertTrue(self.observer.needs_update_recieved) def test_data(self): self._set_some_data() self.assertEqual(self.line.num_columns, 3) self.assertEqual(self.line.max_display_y, 160) self.assertEqual(self.line.size, 20) self.assertEqual(self.line.get_y_at_x(7), 10) self.assertEqual(self.line.get_y_at_x(10.5), 60) def test_names(self): self.observer.needs_update_recieved = False self._set_some_data() names = ["TestName"] self.line.y_names = names self.assertEqual(self.line.get_y_name(0), names[0]) def test_append_valid(self): self.line.append(0, 0) self.assertEqual(self.line[0], (0.0, [0.0])) def test_append_valid_multi(self): self.line.append(0, [0, 1, 2]) self.assertEqual(self.line[0], (0.0, [0.0, 1.0, 2.0])) def test_signal(self): self.observer.needs_update_recieved = False self.line.lw = 10 self.assertTrue(self.observer.needs_update_recieved) def test_serialisation(self): x = [1, 2, 3, 4, 5, 6, 7 , 8 , 9 , 10, 11, 12 , 13, 14, 15, 16, 17, 18, 19, 20] y = [0, 0, 0, 0, 0, 0, 10, 20, 30, 40, 80, 160, 80, 40, 30, 20, 10, 0 , 0 , 0 ] self.line.set_data(x, y) serialised1 = self.line._serialize_data() self.line._set_from_serial_data(serialised1) serialised2 = self.line._serialize_data() self.assertEqual(serialised1, serialised2) pass # end of class pass # end of class class TestPyXRDLine(BaseTestLines.BaseTestLine): line_type = PyXRDLine pass # end of class class TestCalculatedLine(BaseTestLines.BaseTestLine): line_type = CalculatedLine pass # end of class class TestExperimentalLine(BaseTestLines.BaseTestLine): line_type = ExperimentalLine # TODO: # test bg substr # test smooth # test strip # test capping -> max intensity pass # end of class PyXRD-0.8.4/test/test_generic/test_models/test_pyxrdmodel.py000066400000000000000000000010501363064711000242420ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest from pyxrd.generic.models import PyXRDModel __all__ = [ 'TestPyXRDModel', ] class TestPyXRDModel(unittest.TestCase): def setUp(self): self.pyxrdmodel = PyXRDModel() def tearDown(self): del self.pyxrdmodel def test_not_none(self): self.assertIsNotNone(self.pyxrdmodel) # TODO everything else pass # end of class PyXRD-0.8.4/test/test_generic/test_models/test_treemodels/000077500000000000000000000000001363064711000236505ustar00rootroot00000000000000PyXRD-0.8.4/test/test_generic/test_models/test_treemodels/__init__.py000066400000000000000000000000001363064711000257470ustar00rootroot00000000000000PyXRD-0.8.4/test/test_generic/test_models/test_treemodels/test_base.py000066400000000000000000000032271363064711000261770ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import GObject # @UnresolvedImport import unittest from mvc.adapters.gtk_support.treemodels import BaseObjectListStore __all__ = [ 'TestBaseObjectListStore', ] class _DummyObject(object): class Meta(object): @classmethod def get_column_properties(cls): return [ ["name", str], ["number", float], ["test", object] ] def __init__(self, *args, **kwargs): super(_DummyObject, self).__init__() for key, val in kwargs.items(): setattr(self, key, val) pass # end of class class TestBaseObjectListStore(unittest.TestCase): def setUp(self): self.store = BaseObjectListStore(_DummyObject) def tearDown(self): del self.store def test_setup(self): self.assertNotEqual(self.store, None) def test_columns(self): self.assertEqual(self.store.get_n_columns(), len(self.store._class_type.Meta.get_column_properties())) self.assertEqual(self.store.get_column_type(self.store.c_name), GObject.type_from_name("gchararray")) self.assertEqual(self.store.get_column_type(self.store.c_number), GObject.type_from_name("gdouble")) self.assertEqual(self.store.get_column_type(self.store.c_test), GObject.type_from_name("PyObject")) def test_convert(self): self.assertEqual(self.store.convert(1, "0.5"), 0.5) pass # end of class PyXRD-0.8.4/test/test_generic/test_models/test_treemodels/test_objectliststore.py000066400000000000000000000035671363064711000305130ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import gi gi.require_version('Gtk', '3.0') # @UndefinedVariable from gi.repository import GObject # @UnresolvedImport import unittest from pyxrd.generic.models import DataModel from mvc.adapters.gtk_support.treemodels import ObjectListStore from mvc.models.properties import ( LabeledProperty, StringProperty, FloatProperty ) __all__ = [ 'TestObjectListStore', ] class _DummyObject(DataModel): name = StringProperty(text="Name", tabular=True, default="") number = FloatProperty(text="Number", tabular=True, default=0) test = LabeledProperty(text="Test", tabular=True, default=[]) pass # end of class class _DummyParent(DataModel): attrib = LabeledProperty(text="Attrib", default=[], tabular=True, data_type=_DummyObject) pass # end of class class TestObjectListStore(unittest.TestCase): def setUp(self): self.model = _DummyParent() prop = type(self.model).attrib self.store = ObjectListStore(self.model, prop) def tearDown(self): super(TestObjectListStore, self).tearDown() del self.model def test_columns(self): self.assertEqual(self.store.get_n_columns(), len(_DummyObject.Meta.get_column_properties())) self.assertEqual(self.store.get_column_type(self.store.c_name), GObject.type_from_name("gchararray")) self.assertEqual(self.store.get_column_type(self.store.c_number), GObject.type_from_name("gdouble")) self.assertEqual(self.store.get_column_type(self.store.c_test), GObject.type_from_name("PyObject")) def test_convert(self): self.assertEqual(self.store.convert(self.store.c_number, "0.5"), 0.5) # TODO: # - test JSON serialisation # - test raw data pass # end of class PyXRD-0.8.4/test/test_mixture/000077500000000000000000000000001363064711000162055ustar00rootroot00000000000000PyXRD-0.8.4/test/test_mixture/__init__.py000066400000000000000000000000001363064711000203040ustar00rootroot00000000000000PyXRD-0.8.4/test/test_mixture/test refinement.pyxrd000066400000000000000000007556261363064711000224160ustar00rootroot00000000000000PKkDA~>phases][o8~_ax^^opFc0y ^Ӆuvzл*T*(RD%>޾t\^?>SjQ_u*+CqЄz˝kt v\p_=ei鳾}z֏>ܮCF*weZ:7~ѝR.˝>=TY.WW<7zmXW`_vѽnTϏ+yp`'c{Άu@svUv `}|IǭɳYMw?['FDp΋m׭vb;ylg3ڬVU ΊƝ}{p. Sg>y6m;ﶧy mZnW A~Yu!{Fn!V -ϡ,d;67լ*{=aMi>-;b+T~\~_nu:cյDl#ݯ/}@ ~!cMa뻯tש{֕_:uzy s̐BR{i}e\z苩m}Eo{nIηY[Ӡv {4ޫq3rD]T?H ~nwlZ,k8c fk|wwq!yg5ƻU~8`7ř#$+MZFMWt qu텫^6qYZ g|]뷤4,g<~ZIgOeXq<}L Y?8I8L&B$? CVIA=h&^n{P녾~Ag,ϐAڲ ]SqlXaP-ɫ;12aܪc"ٟѸ\9c1F^|H\|DWЩi9gAF33C.tT풥t/D#gvHkgjgh&qb6\l[EOzyMN֤whXMD(ҧAҧ9EfAthJTdOU*}!~LI~gjXimVȞ NRC/tvҹȞ9aӗ+{:Q= Gvm㥡ġVCMwd@ [o wƃTS#!\H]IȝaMiSllE>Vj;M(wI&gS`;-S EԸS۟_NJ 4ȝ ^S`g'@m/rVgwYֺiQciȝWm3Nq86(rE@rF.reIe)ܩYbhw,Cq7Mc6.W?|y/ȧ^X";8LN}k ̅B[kNB4r8{)coB}\qjK䈝ԠPcND}spGčD]FW;7 iNNvg智pwa ;mPU,$#C6.%ⳗxva^4]pWy8*徴['.-Fp\ψM3V<&!DJ/mH1Y[D^aҰgȾ}Z 7,b$Ϩsbj2M9uL@s1hAD[SQa4 t` #tY I.e0Y2*"baP1-+T@@Ⱦw Ke0ylۏSQPF-,4<3btqҳ;.We1RR61j;8(~;i)HY|=(=p@F-+v70\pN`D7C ۔[#aJ7gu2¢L8aĉgq-ɫPhFWA`Ddwz87sƂv Q ^g,g, 3<]`}Bǭ#-',k}X'}6&<{OXv-',A yq;NX⧻rBgrB9aao:aaH,¨LQ Mg!ʻ>8J8T[O<}q eьMgǍ’=hF?|6'p6~ь6+=3  {E3zL( #$2aeEhFWͧ+``$%E3zL`>nь6+=e;m7.hFW~X4Ͳ2C |FQfز*CT5*=H,TQHe_Mz,C]K%K5_·aчh??\|DWdPX|Z}oc?᠛*YϻLFo.ҭƭ}y@h̻F]WaW7o*ǕZqdo!eqў*&b7D2Ƒ 8`f5FA iwbC QHyvcD}i]X%&hvXfupV@4ۃ󝫦Ip33 N1:J*$j/_n?)_{޷ tyMN֤wbٮ1_suT۵r^޶no" 72wcJƁ:4D]D \Z~=֩JAt,UZyl(C2D: )X۬hAI) up-3qPD-%!B: 5Q$5vgam)hN1(l2RY/ b#< ‚ YuH.=-plߟ "}=}I7 ңTipcWD 8Z pTr>ua`FF4W=11й 6rqUi+`άtl ,*E&qTKifUYFFd9} o'\NPm2)i{g@!A,Ok˓@pڄɅ 8t\!R`Z"i~phcX]|f5@7K'$T(i!Re0x8A52@]xm"e0ik{6Mr]-)(ȵAk"mLA 44*Lmpf" ԖO2 pZ.B%JW}ua0Є -A^2`THQC#E8pQ2ln&҅&3%Jv Q;$Rg%JW`2$Îdwީ!iq9C=} 疙:1)8DΡ2Lo-:u?Tuf"朢#%D)(~4g/#YlmV̎ NQgD̃Z Z!7\swK֙dp I¢36(:3)KKfEg?L>X`ST#b?tI?ؗSifHWU |ۄE_ՙM_kYv *&z+6V cbŠ`ZpA>@ՔTIJ2Xvv|hDqQqm}U}ua0p1S!h,@ P$8ZѮ , C"j Rblȏ9o.lE_ո0}U` $h0S B6-d314(Q: VR# +aۢX LONA$ XDPaG}U`=VPPU8D/LSW*ǖT]MH \t5-ɛvBjpPt5}osiS,ᾣ5t_⯿EڇEns$8mDd˿^_ݧZSv욾2-[%彺yS{iITZ gtl]`Ud"e0k{,2M[T-i҇`tQRʂQZVŠs&9U0(CĖ `R2 ai,5. I d}a0vL! p`jD"sbBEE& *M`=vwtWQB%fH{Bfd.+O^?ވKP-oI9U56Xg#MaΏ\6zmV N@AәDƁOv%P'GA="eڹ)H1S@P?)(CF:MgAi̻8&7YuaјNFׅ"S.LIֺUd6" ψU@;UYQ@tf%H}u0ȲY4.9%H``%a`}G%Hҳ B>"7B,6H-QE$JmrT+j? r5  Vq+#r!=1\{Izy z]fN$+M Ģ1_䦰sgN.Z=6+ZuHqf ~BDRd'Hb/bA,Z1 U 9ʩ Ԙ㋶M}تM]V#K9WZyaLBޗΐVMM \SoOo50ZyVݕN0с`RR4؏/mA61΃*CuDDc?7P7Y0%a P4 ¸|-2tn2\S̺SY("HjK#x 9vVdSV@"bt$ ZͿ"NE@AH2+ $`BbO:/JRT^(8$ R[|EҳDHfC4!2Hg#-&.2`mף,W\"h^] ;>TU\^;(q, D0qaGp:Rg.EV48U0*0a'&EfQ_] V sBPJrg`&bY|2\[eE;m\J ~"cJ*&O7ʾX\9,iҮƥO@_PKkDըaaK specimensKnɑ7h?gz&X'gAju$AVCYRK9;޽+wן~O>?O?~=cq֮orzZ?S_=O?sݿÿ`_>o?闟=|/~!=Ԅ0O??>_~/ǟh(?z63/?Q^#?ӿ=O?s?\jv[?C`~~4>?.z~蟴ܿ\s?so<_~ìyϘamoOo=y~@sܒXBM9m_{~~~Q??^A͏?r7R 7C 5>k^j~֘=.}պO{/Z=v;}fY;jߥӻv|Kb#JeۧW2O+K[WcV=UHLgvzǚcر8Rz"$wߎo:JLc}s<yԋkѳ1B,d=SGS泶ԚlU"'GN56}^ЎeYc)yX䅢gMF0a6sJz"M<%32cfLI;{6-є<:Cҩ'nS#H1M:JKnK8f-iB=bJ߽%:?:zKfO~I13HKSuB h=qVLtwGMVh/P+gDc}+3 qրwNӅgH,}gUr_[ ŧx|hNX{soݾֽ255E/y:,vZc#,#AS&y<$jl]qƗq}蜞s6:7T?['F66Kw,Yү'r68̷ں4.HwЭss6[mޘ|+ zh2 4Bmhk[f"ur6ж֐1kkk],hoR9F[ ^ήŅRڽS*gP3(HlBWir6[R:Էh6 Y#)ْ: vU|\COҵ=<2u\-r6N׼X$mCxa6wTƪ.8n5?a=`uTu4hv7.ݗC9izJ fE٢d.ںl]r2-j{X}tL%/]RW5f# U/{c*dGuy=!QW%eSja4KR r$gIB:'ݴ$ K1e#prI<9B8Uc U7ޤӿ<[@=%3Pǯb=g; vSV;$lf!]r|\'V*ӪCӶ%7&o_f 7JiV#AiWmNҁu,3B('ByD\V:YVJgD Ma =\ ʉXGd]8)KvgfB9Zt|Ҳ&d":O&Bed(!Zʉuޓ7eaaD`HX(ѪT^Vb"p *Scq 1lR-6fJHAoʉu$K)%4<]]sZ葐MvP)9-5!CD@xʉ3e&~v}Em[0i3hН> !ٙNC{|ʙ-?BphF%+!iʖZdJ $R,4x\.-jWgV&h!NMikY>*dZNJ |zL3I>%3aض ײU->6AKHSDkY>DC;BڅSd[,:&1{ײ|e!;DR'pO,;]iAH GBZΈQ[H4:1{M`|$ {1GL,V@GBSl {+>1Ih mXDB,Bm3:n5W>_kjša 4#tjEaD/~z;Zm4ZZl#Oi7 E숎6 .y\U%w&u`QeIbp̱W#f<AX]{Gh6Ƅ09kt]y}Z]B=) #U Ų,I:WN>dLRe)!L6I=!m?s\%qɰzM:w_! d,6l.'k?:){._v&.o)]&uQv ֬е#gievϣu|S3H[Xr\ &6'_7F|r2;ʏٱ_66B]<:)vDz3P]KdgPH˒&Iu"0cHure}Daqq!wN{:;"? >_׋IҜ@ײԁ!~TX~DtĘeIȯg׵xLuc΋5x-K} QHJ!'G M>xGB?̽ײԎ|:PpL l' Jk&4FTz9OKkbLײ$o{ "Dz [yײdI#Ӳp9raZh_eC@U%6rolEc.|~C`\YʱZ(T!BN _E"рD/"|I2nwzRBZ #bd-8ײ.&B2Ug{6й<1/eG,ީAHŖ61 sJ,,[ &~aذײ\3%TFWqcu?'>Z$$ Tܻs+=U.h;Z[!P eǘeytn 8\,qܯeyĥicn 7Q˹9YI!z@=1{5!mR)"btpOq<8 j`I*Bz #M9WMG8&!h6!zSs={S#&Bio(M$Yoz3NhV2bwƎ MKFͱqٗگX,e9!PKs#$3O-ߡ NKK .gSZtW٦er]j3h_9hn`yeSs* l3 A3Hl0 eqijh=1dڼi^rZimPth|kYʃZ.|',%<<- D/a*7֐g %,Wf#p 1C(Ay ÝvdHB״ĐPAV<^qPNTM氽F RK} wz֤Q3ƊpZltr ǼVȣ _ %``D,b ܏]Gʲ;J /{N)Jm$+&BhSZ:iSDZyOG#kti獫#z[D2r Tlp|ř4 _a֎ TCm\|iQ&Bq% َͦ.9bX(-a7ca{勸LxӼdfZ+BW6+\P,iY2=2 ɒn!]6jge]|BrHhaT~ײ"AY i(v 7;aӲ^C&A=4tK]ѵ64x:)7>Ӳ$&0-4=Sdv"\\\HUaPp-ƄsxJís={L<ه@~A>y_%.ُBډR1NHq&-FHaj=&6Ϸ%2(J66*|yzcFbu |66X,\mJ-rC r)V7L-1&},EB7fIԏHUY7qtWE&Z,ԌF:V2=z[By7fb(=#Q`X7f }.OMNsmV~ލYb=2-Tˇ J?/;]Y.( lH.gu12}WLh18Dx+g%n^{rQ,M $AV;GON`Z9:-tvED0Kk#≗m]˒Z"A l{|mK˒Q"j8D@? Ъ =eP^3;HZmJ!9DF?UQyHF`Art7Cy\DeIlB8jbNJkiYD=7B,A'vyZe>5t`¨c|SNYa潇G؞=-K*'x^=0# 8iXRA$tƪqcM>29 m9dX{(CHʦYI ׬&kIS&]?bҪ$aFQU$#r-$=a]kT~E69fy0/ëAoƷMYC6s)m`*4)qI*!=y,{r|QYE'P( j4^tVat]p \'{T_-CC9I 6SC;ŀY':LO&AM cZ]ǘ7ֱ>iLV|g:E@7R0kK Wb:Aq$~[A!Nv| a2[Nr}R_WI`S9!f' Ic˴J- L̠{'H`^ ܥǣ; 7`P\mYH3i܍ep] l 4ӊt >,4 F3PDy QWgHGE^>81s?蜓.b~Jw+#'2son31}}`zBo{TGHS< 2r=VʝdJ9sH2ڏ~ORr"tdNl2H2W&]3 ˢ#sUxd}|=J ruoibvdGޞX֫k:Fq4u+*hEu#g~kiE/?#3p;S>(vޝ>B!sA5ցnߍqGs]> nG/DsurTwnY^}0o ^١}FPI;sP!#i){/Xqtd m#5B@e qWCU,=eٯH n\!dU2aIXk0-;׺%鬑ӓwfxFp}锡.\cKXiX#>ϔsE +xE!C>B{evKژ#B'I6X!cJ i(p@#T {vIDWY'B׳6Yj Z4 Y(Wh;7N@>,1DHې-BCq\(6 hE}tÈK ,*c]Pj~ޅND+,1׭b{#]FGm֫7 W$8o|bobuqirrƌA2I C/Lq(q㄰S:k[t m0:imS^nx)L@;d:6"'ȦR_'m $ Ƅ?=p~S#ӓoGJ ^Ž#OEoz:ۀFFHФ!]QNUd5!9sN8f L3R/$U:{Z2CH9:q^3/~cD*Ǿpvdc"*=6 Rݶ8DB׀pYø1qUSy Ij0TAP=*| ID!#/|ۆsˤ!IXc laؘӺ%EN`#"(xty1n˹f$"a kkf1|.zε"Np:!ѪXb m"21k.B6$l${ ;x#i:ׄBo>tRxlJ9rً߮3>+B@~Ӷv!\#Bhuy)tj5w.ӿC\!,]bQ \qHÕ'R d GjQnܤc3xr9uW>Ŭ"X (Y2 1xc#4!)=̄ C՘L/̑sFUKHiŗ#|ҧHYCa9): ĨKЊ< dC`]]B#6סL-LE6l!paoP+,χ$%`_bݔ}X u+M5@܉9qhFma (SQ|^3j-PF0bx+IN\o*BcN\N0/8B A\ DW l?~ ;Ru0")J7H8+i :fboS 79W.dIduD@@JΘ ptAFĥx"^7 \p#gHWzl8@`Od] lsK~BĚWEm<Af?5VBѮϜTs`kAwR17/+#J|$ MyXu{>'r cf;f+IC7('4cUF?N`HMQ?n-D(if,bH*$e{SޠDӁ|YjDP8 6M/@dK@֛Zd<3_nCt$M+Wc߷ !ЊsLxbkϠ;'GA~yÛN֯8䄣!dj<(J?07ltPWIޫy<]i~"UȒ4HֿH ^ \w:sjڲ;t값xGNp~lc7K`(;qC* 2}hM͕:QR[D7 ܝ6W~UyG>tTB&S}.8p-JJ#WMdȩ2r99?-!XBK#_R2<yn}7%ޑoELRrM*WwG53A4,\uV&J r߷Fݯ! طi;ɘ&ZGW6ъ:]4Ve|Q;w t;O P;rTWgq%C:NFjȍmx5)xy|e1 3 KQa+tM?DHɚY,G"@VJ͂1b޻x^w-2jwJʷ.B۰\k1FxN$&n!:DU.쫥YX:! \AMB#18`[A%URq*4Wwk HxTRur{rIXg.Eʗ>!A%%ps=-5҈Ax|Jy=AtB&ѭZ~r,58Z3J{zW0m8jz9(U,&49᜻2^[o~Xg0?~U|.WޝpR^9{(R &<oM,h,R"F`5%>(~1r z D:)F ;KIIiOgESqC"F3}kvV:5H(MkHlfu(DxsWimH~8q`rI%%2L`쥪Sw@"`W G 91-͚e '3aOHr ϲsfabۚ4!qsKRm;x vjfd))҄! zUM V`+[ Y:ﱺ>20v>j Wqվ74eR.s|ߏʤm߰Se>}F(,WEtUSeRԽɨe./d9: ]dtYܘG!ÈbX e5+7AGq <6B:lJP* G>Et!\_zEVM ⣩6\\oq Vf(KC!\% M*@z{tm e{ow|hc&&Kk6b^>5CjdO):jf` #0S0ض Ttc"g_Gs~:27AuK4 ぁ=NHVqeSȦOpNk kS+|MP r"Th_r}s@_ʶKI281 KY9Ls#9sSr&vDcyQ+6:ӗ(e.L hz4 ?\C2j"05bm +op2UX\5-kRڣzKF{h-P>XmR#J:NP<\Q8V-.F}@kOIi;6m̂Ppgډ!V)g_瀅%#qk':bH})^s=9k'(XN$8%IWݍCIym ixҐy+,lG3xۮņ8e'I&Znuz1PXo`tN|[Cw1ŴS4F<3ۧl;t wy""Y-qIi~KF5d4elg>'*%][76p 'fx:3$=@C"C #*1s_ =iqx}+MPѹ8&MS"c#8(r̃4 Ou!xu^,(ifu6 <78{U< [H%3I0$&"h7CܙT7px4R/IgOBfd @{?Ph+% se9nb78LLts`SP'O󃃷up v~LGU+R.kZJ7tD1j o]QR:s4VJ֞mX6V dܯl-͋4ztG ꛐxP$arK@u Գ?;{Ye pW[ėp)7ns͝1XTZ=w#d/6&L> UPv#8$a xte&+cK.$&R^ǥ%$c5Vn"lTd&1TG>w(.w+T[MwX%.ʭ~+5`' eD$HSJ8etN߅-ccGJ\1ݎ!AMA36MB_,29ܚj'ޯ{JI?KzeMbb9QĔisAb &48Aߚ8}KWGӄuXn]FdWi)S}%$t7cgI,8B7 'w bG_]uCL}%e>7d!%nY 5#xuDĴ݈yG5ىUמj#ǘI0ƚ#-ݧ! Lؽpu`:&}AvC"QA+Yxr׌nxMOƷJqi{KW ìy1=Qׄ1Xn˕_u 8?7!U\Fj9n1ڻ1Wq"^D3*Ie;jxHSHr]WS䕷1hج825I~Na{1=)6!%>#n@m.P}NC r)o_Xs=EZ]$\3rb\>e=~Ey;x.niNI1 .yY6Tz@68)Ȉ:cz$t '4]O1ٝF,`lL H4A52%ޑC3 *GN%aĜQa_Qiӊ9Cnb.o$3H%dp}Nf۹vG? @|T/eT;BQ4ݔxo5ZU8k՛jc=WnCn{oB;Q/ (g9Zs_r1`P<r',a7',P=o-x<`ď%Hʹ2PdpnhL{F3$0KW#8`=(S3OˁWVoK$h,Qv M_}*V>HQ;d4eQ#o t(j_pQ10A*Zb>6QX Rnn*@=Hہč`noݍo{kA@KGr9Q!R~Xfq*5wzvAZFLtzZ~zvr3F.#}q,i;7ei%Գu YdF;rw,_eIKn}G>뫑8՘/jA]Rߑ˼!C@A;]bR9+8=anO+[v;FbQ*V>A۷۱w7=PN<k-[:lnL_ڦ14LufP PѨ( 48mP@_Gw8qTʋ+H)3)\ʪD_#RD.dy˧f=>%"|U}9Wo9QtI&f4vZ'l 8la_vzo ԀY}' 0#M3vnէfɷzw$lK͑'NF)qQk+P;/bޞm&T4Tw.EHnY΂+s8oػZ /VYO%C!츨j䕼pOm: Ht?$,9s%.0\iV)9덵k쨑kM O]r=EW@t|7G3;rh)rdg9Zl꫷1zûMr8gmF=G4!1E'KM<H2Wtjpv+\ؼ`3U5H ϣ:n_ƻWJƬ9KꃚUh t2<)@ʽbP"I 긬Gw{$ GC'/OJ\2}% Dr/ĮS;2c^kBw, 8]HIV=rNQٽMLV}:E"u2JOTRǻbvӗ?d~vzW{^-%ͩIMX4C0;tF44rcoQZeHƙ;CEuyuPXAx˥̍CKWJz7w=ƒcijMK~%R>tZHyz]wٽ,XvBͤk^}6K6"3H);)qUQuJ l)\OUNhL7ՠ5TbGnPQQрVې*X0=W!h_&{yTv#:j}_VnՁP鱂uD,op5r9#$ T|P0e8}J`Ұq\A߷աTlaÔXQ% _O?a+fLF|r,孉Ė\m'G;>` _`xR*Dɷ=G[dƬ>20GwXo7a(SzŃ_6} '4X}E 6IH 3B7Wh2܏jrY?9rVm>-r|f 2I YsFX|IlP;/ ]Z75p&Nn/ vSb@[Z7UL4bBii'dp$eJb4EHp: Bd:h oE ,5HcCʲEҴs_^CRJէRnfƮWo;Xsbۉ.\/y.`r - -yc3Tbqh/)n)oPR6B9P Eю0{^6 zv7ه䪵O @*҃vRy?2r:bT f=*pB&[ۙ6],KL3!ϒ[i%ȸ5tyJnp}\ۈ,;T6))(< ]v_s[ObE>nD}]FD.pX(2`(G䷽.lw73:yhVm3!䷋zoFa^a#goȼU`if |B1*!e2)_ (/wq1NY2韰/#^N<@JB3>ЇFۇ4]XnB{脙,|PQ|^ڮR+ OKP/甥@N@RAh[X+jT&qiCHaa8CD#UzZt1h膋-hc]hSR$oi`Wmر9Aҟ&X/oI -\iгCnJQ6R1LC,uf> ?r؈=.A,Ȍw; LBta..ȭw2F.zV蔬hE.tw }A+ Oo_@AHSo2>.+]׷,<(5[XG;8r<ߝ)m?Ny[h̻N>mA+gs|LNjo26E>E! 6 %фv9eOou2ň Ձu֠79Ǘ鎜4H/PDޚxGq·,sF}'dw`D'KѝxA[7v ;љ sᔚ㜾\NM hSQJ6"%hf^HlA6s9?9%eXzE\!3 [~U#ا>EG^w7- 0f/p\QJY ?!Y OyH S(4׹|v |wQ ,+ a o>FsLj?͔yy(o shV`}khqRA@ L,=wM=8d=QeX;t}}e:gJ ʹe EW3bLcv?}*G]y^v3͡-&&!~ΙAS>fbԲԢq\/F& $-yV8uBbnjk♷$vMH&k{cURE F#zFJh#j\ۥ¥$LwpcRխ9e04Z˄5@a4cFbß~5pAmTէċӃ S:K<0}Wl]F5xI9-z@w:\GP!)b̈OXgʸ/t`n`s&n^U[Gnʤ1x<` D,\l/Z\N8"DuK$SAhVyԴί#Zq l5x~U! ~0Qt|sc;%^|}zxM2GP(1:{Nԫ7kS~Lt]KOC QKJ#܁brȦڌ:bzSAYQOԫ@? TClۨ>%\B/G=6w亯\=̪a T jT55غ2J"C:j0/B5 z,:df7 qs%\ MGIBlD#G!Ď Ȇ빤aG}v&ý=֢-uZJr[Q %@+ֱo9[HTW>nkT/>0H-}jqbq]% Mj }s3k;r}ŒKy2 l6ɚ;Gr"czJD u˩2<\5JRnwou(myz(ӕ`ɉԴ&$v4^ɰ^)pC6|wh˙!j9Zڨ :'Iu{;h wfo8v0- }IӰPYu ){/J+Xh \uf)]iMC.?]\AQ#,H0[@FcJxV}6 Qh=ڡjA2O6 \\ @շsla(ǝĖ$>A(pρFho7L*B)/m4%YF>ۍru+)vX@k_p?O/Yp%[1wԇ4Ux \O3IZ=w1Zc\V]5w$'Go7!'4/0 bV3]NydZTāIϤ)ݴ4XD k:׊Nxo,Y#<^Ęg~㕊=[a"i<̤Ϋog @E|αQ-bo_)HT>Y]w5~߽Ӎ~/4 둯|\A&< 6q[a7q= gfSiNwpJ||"/ڮ!r3Îi;xa&@g=9lnp'w®DkM- xk ricНEiv7CRߦE \Ym=K細[y?h"QrylԷpr#A)6~l Ðǽ|"3[[&]m+Z@&@瘺$=wR5wh:?+yRy:Y-狴+XuKsA?ūQWw}K_UjϘYYe, oLJhԃɼDE6c/x'Km3[tNwFE?*-եRmP0A^W7W5玷a ׍aykO2 շpuW^?mNP.mf*%$zVfJJ Yu6Jbz]^7!6qwl~Hc;s>ַFUYcFy-9;^`^wgNI?@*td"@uu4OtlL'p맫ݼQ xfNsyOriN> ˸\%)s:.sP*@1&Qu߄f>絉wTHr/R%}֮ĻGrn R5bF I/1?O3M}wp UAxMp]^6=P8x/e/ ŝ**˽^JF0{iCk "J;^h]GX&i3$G/з04 %7/Mq?V͎S=DSèox-+{h@W}6t73/}cj=R]pN/p~xzzvlhvy\<'z|泙x:7©'6Oxۜ䥿M_F"f~xC@ cQmT;ށQd?2n_LDljw^QEϽxi]c Wj|ǻ!rtSPnڅis?W_9f~t3rvֵ Es}|nO> +rYiT]mѨ}e# o1% }f{hr2}tIX9ri(}3Kjga[Ͽ.Or+W$RwV׾Y=U{Oҭ'|+!}p0v2Z>}sJC%ʒ fZrKLwxl?Bq(y?u[s>ړVY׽4} u4|&V}%=rⅶ-xAKx ӛ<0dr{Ǿ@+\p}exiZ1[K=Ǿuܕb qFq_tt̤f; ~PY>5B.e%D3=5S<[\ˆݏqDb LLI}[%Pd~0|{J>44p2?JG"D6oE= _￝h]f0 4P=`X|Ԗ]cB"ʺLh?>Ưr.:۟g(2m[٢\sID&7^'cu4<2-X͊xNn$U+J Y/*R'oC(nk1՝pA9Np' Y% Xcf| .,4+]G wx)x3J5q!\Fڛŵۯ.h니zU/C4ߡ  fu, ݀[ǝ | IH{F0~ݻsULmf c#(ae|\>W6!-KG6>[&(27HPY&mx n x%^ es| `}=ojJK$Ȝ Kc>$. 3ze;Jhk ÇRua7)E ?c1)DAm0Mh*eF pӂ0q g ʌ@$1,a0R_uCe@S(Alfm;3% MS0k:کݒWJdFMF-ۻmS0jg0zq蟹2o|M 8EDm8R uqQ mS0]IcE|OA̱S" hJa[=%uX}%2`gM(- WV u|]]!ו` OICQ6;FVhs\)) ,ۂ2cx[K ]6vؔ8̔ۦF-¹fN_БMC<0<4}ΫC rN)ȺN;2(l-^+no~ 5C ~u\/Sy9g]맙'zGA"WOYܨ'd-غy\%P5_@~ǰkǼ+:+鼒K2wqWUi=H)C؋!_5-@S,qðJ2u]]Xrĉ0~c< L̅d$:Y':+OGYb?nac* b!c6m qԊl@orV]2[3pgpK[)sPs$Yݶ5)$(sM iM9n~۵:.˔%r\xOoym *.Puw %6th6YVev7^v;_jISyZM&uw\OʼeH NfF~^Ap<mL'KV”1QBs֟hnP۾- hmM.d*Uz0Q<{g6!X-Zsq նLQḽ/7$y Uqx&v_7:|Ff\oԩ9ӁuO;fB/_';TKx\KQ 9&=Zb^׊Us}.=+ɮ- OR'qם˴f]g;Ƃқ%9fJߦ=Bcpy-e>yHh (\W"I=e.tNl"e\} JsPԆ̵GuT #(e-}}`?-v"Cf5,Và`\oE@4^,hrZ=o2g6qF`m>kɮ6r7UtGy L& ݻY+2kSl ??ʪl"#Vso( 䔆3N!Qzm4cﴀI ̢zG`_vI>C=Z4'i,]_Lxi$c"8*!:c(s4"n% &8H6#,Դe;UGgSVh| lju .cpR@f]M MԞ8Дp/ /-|*hI8g>N! +5WDX8t=\VqQGFAr mf 8)Vjm49eiXHA,f>W .8v#t+ UQeJM%F%,q΄la qILW.rPV&E|־ 3I('n3 þs]ǖ<%$՜*p<' Uq {ۮ}{Sw'" uׯFw_@N8s+"*urQ±Ig #; vK*+A+[5s"oL ᬒq!Mp/9 4I,9MnS«RR`:RUB+1ua.@b.V]f@Os$ ظ٫􃡮bѝ 񓥷@VV3-BA|顮4H A+hIU;=;S7*k-k(kEOX-gk'U UXTf6EkvQY*%,Cu Xo kFk. $@NZ@ pF^:,QP1Zea)IY 騚7a&η@:89$xg,qM,M."Nt^K/F|5uPN?<{T%N,%d#rMk\O뒛@o>OוcQ3?/篸&6"O ɍ o&vC5c @ʂ;V\W^㾶,Л` ssjKw\@N9p6XmŃE$>ľ}ˋe=6EA" :?(_KPBD0ejdC ѲŗҺz[^ӹQcS;fm rrr@x<]i{&d}k+'GMJs_  yY8m># 'v c#DĚIIFL(M娊Tʆ=$еqIj2ɳDNqIDJΑ QhZJ!C3q U 1g 9bX^Be}L GZ 1*ę8:_(ljT|o/2-fF9!0, ^QH27ݨ@wЃ .[̘*gBy.2dgFl09vit$Uᴱ2LDzKK3"$쀘8zEi~kQ).y 6kCg]VO$ab]6-kdI<&s^Xd`UgШvnMA ;f@ۢ@㒗IēY:Y.)~"H'6'FeCf|.V M16RĸRF.iI ceRŶ,EH^s -bbҸeWT9aGD4JL˲@J 1/vk6c x0o§! .pG|RRE'=&[k lSM5g|8{Yr Vm_*EN-ω/%K#tפd pHH 4⯩I3n!P)PiL,Dic" o*dE?K]`(Wo<a5M̢a^U1Sr<-K~VIote6V635t1,?6с. (*O }n b„2NCb+jOO677&"UXqׄ5'gc2j]h[I]&1g TsjpLOIsN*s@֊+NOެQIRzD?n2lZƇΛ#Z[%xe<͔$wBAL:uqEPUŰJG鳲v%ԛ&Ϥa vmΧS`ۏdE(@"FW "=D=FEZ+`ks^6uKC-bc+bP 4R )(rp)uP,=?q;BdTH{ML|;g@y^մk)9:Kϙ9phdB[O0ʳhl3k+'׏2qKj#Xoqba*PWo*qe;,Hж-K< gݳhXdf a-\SÝ(z#hKNReeg(-‘_aT KN'wQF={IH- K8l_[.eHs$wE&eGL~c+hY4-ue/j2}0R/\sH\3bҞRH1A~Yr1%*OX3*چ)>J:ۣZDL,A)Y/SƋ*ej%vVfZ柔1Gc>cV jžIE,u^E?K.DC,0$gƳR3A>I-#JT8)yٽ \m݇KKqͅ0 "9Ib0Dl"V;)o =uXo;4_"&}rE-SB 57ܠwI[@X~F3^.B|: Q/q_/%QߖY%׻]/hYEIh.p(<@xk*VFVbC'29z,=R gK0%( 4Tu3Pi2K>K ^M957%1bG q&n<88w=tDݒ!7֨w=xԸYgv%$τ -|e#!U-,a35HCzVjV~A E_\0O**iW)뗫Ϧ=Ry,ZN@J-5{}YjKLb3wp3|R6ƩHry0;P0)TN˿sNQ7t]+ d"Ζw'J8ePouK'e4Pܹ@$c-,g*9q]iEs`$ʙx p,._ِo\o%^mc1CU+l1/P6i.@|'xҚ((1Ch,sEQi6`xo2IF{ z=Ƞ؜].%Cӓ^B7{{Ar}gi;mm]鵸hiY5e'^~9?oHf<1gρVg`W1yҕ7*^J5 V!L\_IH 1F@ȵޘs1+-ɢt ao1z>l2EZpnd1æ㊈L;`գ}b@,z+"6&j:ڠrcJR>LqŕycIHPR 6OVb_7;aw)2p18p¢ح+rXpfUVbGh@vg{QMWALYi ; .18ݐi #,C:< ^D?; g)Nht".M9̕v#N'3՗MO:KD*k~(T`%Hcۆ3>HT$uEAc9kޒD-<% gsrHp>00eT ۰H6L],,Nlyc3RB"9epS] ڐ;+Mo8)~:' wZ"ZDl(V1H%Fϯ NWd`i.{ɶ:UXW V(=A}㊧ l3ݳYa|U2Z6my0 ' W|77tJ?CGo0約%M'J)i?l/ŮӧW%,e⑬@" _qø%9%lKұ> @zCIEOwϚ`yYsyp 1Jl&r)|EaJ?o~|D}%Y0l'vFYQbI>!%<ҿLMMb$i3DEFXU#RH2u0l_kN`q(Ed⚐gԏ3b{$0>kFSpXnbaJS9Vxk788acW<)7-X Z)#OnJ+tN v_ӗ҃tanZD?y/Gb[|Zw@s2x}|7u&?,-z~i3O"rgqru$m곡w,\ߒa7'X#o-vKrԹrWHA7>'B5!<^$NXM%!Η%MXU^_xǘJ ,q3^zپnڳ~K\h8;iMKrCіD)ی$_pN"j@핽Sy5{j?/ C%ūmQA ɼRxL%˱P ~j*0$έm X5IMyQHb_EA- vXhyhNbI}D~>4bGُW[:F8J^-+lXزS$nI8C?WKwX"h0u;]=hQOKC=ͲŢn%^(9c/tlś^wgNaPm̝Q qyQQY(,B.yshzI^eVyA o`m9˿B HMٗhjܶ5w $4&^_UIYbjM0\rĜ#F28y}pe?(P-C 1v-@X~-]zW GSR{c#-WE} zן= @hRoU&}y֪ϋK;I49 뫁G?avt=$X(ПTuN,{aE'eyMHѾ`EDi@T$ylIsCwb=X%]ϋJ{od`)W)5 EQ)8ZTt{Lc,Bq9rI~6_i!3o!k;ƴ]m{v ͱS=EŨ%]QEUËEf yQJj'/3FˋȂ!%5&4R6'pN_|﷿ 3 =>3XAێ'(1>xڇ{@?f<٦ۧO|f`~Lg9( G_FT-τXY}VAi0j4]}N,ux?:/_FgD?9EB33R&}1R oyׇKMVӾ+؉|Y@B2=j:IPhOWO_<(`%r1GSɥjEO|!>7NBV> !8~G:vyņ^ӅqwVK9E]G~65#7uYK4=lw>B-Y%}ߙQw_J)"㓳@ôm;W92BsgG‹ EChZemp%Byn?:]/h5 V=z}5~}ްlL" +~X(y4#{fErZwYMnLb5\wM_(VzRhfM||Ecge}{;=o>/X aEp痤M;6Ѩ~kL:ǟ yHUg-!{ُσvr"7{kVߢbEKB{xfoJX˽E0Z%mO4I%NA2/U~?`lYwD:Q{F*잕2<"fn xF4] {ZxgQ4狞?]s)@l .q n6h|~^UBٜӺ_yyF~OyMTs.TY w'jυ+ <%! h__M|χo=|ʷRy#]_|؟/-md$H|_FFĚAݍy1N/G4o~Un4fdu/o[dp]鑿j_|+Yu.dl&* +ovi*. (i_,9vW[(|P:kjCL˲WWvb7 Zn_UVVfۍAc]5Wj⫳N?@ZMC{!qϣ{~6Z,}P+\Jtf9Y?c¶sԩmZn稇~lso?qi9EzrϦ {WgٹpmjfbOʷol/k_eFg~BXl2D, YIٮcݖ[3oIK5F׎w+'VR=狯+Iw;WgI!άl|n?@ZWgۚfUr;MC!^/H5<*ŧ `54ba:H ΃{%y6. j/pDA;Y'<ۛk 3{ub`y^|6,rN-3|>Yp1_|5@.. 6} )E/v Xj|Έm}/H5IJ[ीnw?z1|9kpE>}FM])+MWNܓM7֫_o<:Knzx/xvҒͫ+:-wPC%+6?62z ¿w< ǜmG[]|v[./\-jǙ2nj~F @gD <cI~e&ѾzyF+7 tW=^UeSL듽LC=>z?RE iߠ:E[=JSAkh: FkUI8\ׇg`eY}_Vx7Zh!ɒ4gбC K ҅703T1o`#uv&栚/b >n<2o/~_If/)ؕ{tU)x ɺxvfpHmX Txp6cKl/Yh 3D,_|xH<Xg?w}x^kEsgZ0@˫ҏP%R V =Y?ψrLΌӎ?O,p9eAM^0v iG?5""T30,VKl$Dv~l'>vgcYnX/FvXT]zwpJ7"$7mֱج7c\@=Md'뼇SW\oQ0Ji=x'mFbl6pP$xnvCmZl=p$ `z/ ?4}ۺtٯXj.YV{\<2xl2JySwBsBa^@4I~=g# m`{Ʊ$/[b$=kY1QiLkQp` ' '¹&p qU3ɐnsfߗ5=Łnxbf? ;Z,+ jMx a %BYME ӂi&z^}P&}WORF$',Ix,>',ϳyXn@EbRp=oa}+]rFG՛M_phQPLп"#bY߻MRɹAn7zpEhaN3ّb;mv&b(rf vU3@,p8pz#uRk:fȦ oV_ Y sNj9/2;1мcٳ_dVe6"vT/4膤"k3uFqd囝>UZ΢xa [[t†@"11_찺n[0>9ٌ|ZEg;o9Iu]܌UHg>`w ɠD..\ \-YT$μ̊F n鷿}ߤLaHQ46дhH?q%E$t<NƂu%d*MnStPX$H1KKU(]O2]#.Оdm:|mMo4xa-2gsW|*P !P;W@z-&򩺢Hɹ'/ٖR|3+n) }hd>Xi Ξ7BKe$eKo~K)h@ĢߠbI]74#`6F5_=/| ^im%\`=ə> .9S# '{ɚ L?sT "ݨ{늞M>sHǭ9) ׊T0}ŅG!u*WWD7%3JWМ^)#T~mt#WDҗXbhF~Ņy*ӽ"$ޏRS/`g]|:W CG0+@J=jBNѯšW=ʪ>@[W5`ZwoDq[DUH8cT++.⤖I og;ohLhcF]x=QHc7*{tyv%+oZ W.K7Fuya\ZrZJ\q}eF>?VaTn\5Ԙ. ubZt.嘁\0\oSP=;'.Q#-eק/ a'ˡ+Z"q@wgtE|w ,r6Tnt(hޝϚrw omG#JCPW`#\&ed;\jxh'JY(7@WRN+R %#VKW >u+*Zc=/!"AsƸ)!%/t3[ğ@ Ht$wb)%`G4EYP#ZTc/ WO_TH_8/ؤ(膥#S't^"zxsξ"ݷes%AH0O_^R9|HDs<6oif=x8;٫ 2|(Oؕu-I-8t$jQ =u'&ɰa@f BOiì,N}gdWWJ@mNLM{-qQqw`X+p+˃>:2}kn:r_h1c:̉oGyS5NF9Y?]̇4Wi?G ڿ#)zOO3pW`ql~VhS풎[tw;%_2:#z7R3.լ`~@%IUCPK͗rcgx{{&crⓛy<չu_BН*ƈ&l`]I?_PoQ\#VB 9}]WV2ȏSP+}.8Ajd{OE:Yl~ɶY\aUϳp2$2Ym}*fy)'_Eءfi˹Z<$!A+Ȣ>WԍlfH *]xlKQ}$,8ys0jDWȧL$I#?"ySBJĦeU]xlbEJ,F 0c}۪ g`[< {烤U|9vWGÕc~oϊty"ͦe8oh_yt ̄}DN9ѾFmTRUqƳ<'I#Z<'{H ;{^LAI1MS5e IƧ#O:_TD{@O}}cAL&k=Sg])1k{>a=>ϕWR:EگݚhܸUb; O&:R%L6oۜ_nj1UD2'2{.2D g}e\oj/G*ӦD0rvg*=MCZ1S ?z:fE`)qxX*ȕRp -/WC:F{ j $[3"A #oXmOS؈scl?ƯDo3G_5 6O|ߋ#1o0'Fg)쉇ˊ{Qc Y-uL?? A }Diss;ǎiǫp̑l=[աpxTbϞY/׬jh`Ѿ'3"qbҴ\lϐQERsTh{?IzSPxx AT1Svʽ2^6O&Bx۩^n=?*CӖEDx[$@:DܯU /~-aڻ돳)=.P3HŐ|oO^ kާ! =OƁ8ڿe@ʎ/bӿoQin "d]}7V}}>{vע.  jO͞پ1L9u6Ӳ}@EXCu [d/]+Z|bwljmLUgFmzc8UjSϏz½ k?2FR _e)mR+} e_|udk pB WPu@$T8LZg1~^| א%O!G__h44BVfzgQ9e(G5}rAӲ>_|uWgx!"c;>`PQ :oV=[G7Tf~wB~{Ƹ! b6˾s߿T& >jcJ3!6YEao<7V)CVp[(p1~ѨoE!_PVplS;A蝏[~Bf9!_Wg65Ҁ,#ۏ mWWgL ߪSFrHOo8b[_lMvNpV ?_|u:,}$fmw.Fh4&Yٵݳk;>vg/UtUGsb\¸~%۴bүB,e狯ئ=zTe2/[\_.;rZo,O_c/uGۆ(*;/dqUw7rg?[+_r;/i*8)?_U(ȶ)'`PZ⫊1XP~U_g (e˧-,&`qNk-ZG }L>)y_{;ΧQx__3 O8R_7z5+&`W 3=eТ0rk)* [ V;h#-d?qL}V|1NRb<|]"z c#qE${}]s]fIQ?C[%.SKM;c) yRLgaM~>q:}"y✞>q ?c}0p R\ՕV`ڜ*φKh7., %4c=ǣnKPT8Gʻo8y˟ĆCjXs.90j T9jWfq +޸Ŝy+{Z&9PSͳ/9s^bdGτ !)P^_E~;РbxLv=yTZWo|`Ͽ?nD-NفJ,Rܽ'\mo9VKdZ<93Wv$:HoKsǾo}U}]%h1K|B)wX#ЅhW R/yS,NCaz~Ga!^nO K%>B;F`F#bˡ&1'`H6U.E=oW d,"2ңz^_# ja? {L,d#M/%Wk1TaU'>?pDwWD߷]jJ֞~Qt Rݪl:/be`Humw7CՍQ]>oBdrn%IKX!vN?_l>T!]v`ya,* \|tA323kO|~b}}Z! O< 7|XLpV;n!~|&^dN q^7SȌg bnnMe;񩹦x'lv\X Adf|@$ch+),hrz}$b En'Kݜ$o:ܳ[YUYB= ,"_[WNs6n^YGN*4>^7ىpe2 n#^霔ڰJ+V1_ m9;ش`v-+͹>}1>VfT5{V_kѠHo(+>h"NJ.~cmɀOֻG]Bq|l1ZF[{z-7T]}, %Ӓ$հμ6"tDj]o [¢h@lLŒf7GWZ',< H3`A8Mɵ[lO>K>dC)y q55f؞:=SZTY@8 Qў<'23Vmk&sjvo0AAOsg9˿gF͎@' :MZj_xnH 8qv %-}Jh;UU :G1VM-邸P lF[Vec9OU҈|q!n\g)v!18B[ݾލk-1ZAsqtջq)8N5>W6}h9Jb(Z̄G۰^O V3B5[+;'g< I:|z &Zv{Ei춒תvik dMURΈZK19D_ vz0"޸?$;_ GqC^%Um{#TԾBbdM{*{7b"J LR:*â,G [H~*|i* *ۏ/Юt#XrAa%R?GP2-@:pr~ q.O r۪gsPkh^1>/c<1D|qy #ϮZE"R^-A<)K9䐷 Ew~qyª[k{*0:R:e)՞J=v*V$3$)G_5a K?-7wϧ%!J0` .G{P>#*bq9 'A >ϧ%vJ0XמJ|"Q\@Ӿr W,P1W5~Fg۬i8G^q~۞J=*]<; z,Z~ xe%B4=z.Zڰ M^J=39T uvr.u'3O~妇k*@˔ +q6[[`h -} t O~X\x]$[3ro_U~JeBL Ί h+8-˙Kϟ% W09nTQ[Gd8)ܞJ=VpNDCS[wLG{"h/Q?! $3o7;knnVҶR/pMdޫ٪;youbͪ(ZeKhg='S%g][Qr@(];lY|\0 =]PKbۚeԶ;}.|ᒤX>s Jq.NۓjK!^Qk.U";{-.%Fi!v}Bo'Y" ~zfy{~'^׶&4 ĻTb{|̔M (x-ߨu8(i"CfHQOeªE&Oռ{\zdƺ/>Gw!CJ8c=o<@$D^z>~1*onN'{ ;7W^")BO'tӃЃ4@AJ*SVb x!}.1ޒskM㊳&#ES$㓿\-_e+Ktՠ"*ٺuYdS +QsBǺݐ4kOҾK,8m[Mi+"8L{[`)z|'kρh0z0WD/Wd:=PM{+"Hf1zI>~yaa$Uh?7m Ww+"0 uIuBQH!mO~.^Cgvj~l퓸_bc֘J#ͭ,!T}3}-ӳ7rӶ/lZ߱ [Hb ? oդ׊pС(sW&^pBlh[.Hi0g+9m% 49>v]v 4 .,THuƮN62~D[Ǖ"1*µDܭx[ /]0,ku«?aMX oP}- sڼo11иʆZەwuö3E[jwCkʼ{wZ9M[&k9s4"F-u#yZh]?2 JC pl1"Uе11!D(Q6"u :vWG?d+cɞNpHVhOkXnfyTmw㩮fl{!VN8NW)M35~cY7=|1&JsKE hֈ*GW/w&&H%cy7DPڐBA:FU @/I~ BYۍbS?_snƝ bGWpdr0[ Jj'~K,rUz0.pW_ [:猠+q5ʄ nᒨq-_B$pɎ^*\K?Y8fhù_B &7j]]$#$.Gh~j,ڿֹ0`҄5yW&]ܾ$9X(PT3vny߄5ٱұ>< dTW$ѕ֏p**v/|FWZֶI9^K,#a rEJ헭jgńa?DyVǸCA 0G@>E9xO_kF(}[0&N^Qu3]EciOPKLE-$dG\{p{DjIM @[Qd#!xhhH4{ʥ)ܔ4kO͊r+5K63wOZH;p2V$':``xRƪ >yXzy?uZ` YI-~BjllA ~` IHdưo~I2ėD(wy2LYutܷ_PVr!"@CcXV[,rB8nXm@V{HPj~|f 2U>Z }!n aОdJ6Ca\-AwR{n3~$Ɓl4DI0\?g)~uzm1/vz<ϳ~<{tQU_1jM~qjg?!6?x`+=GL93p؍n'A 5*;3¶:Zn߷}UviY.9rY?`贶cӁ"~fnB#P n0Q5ޢdq'#ǫP2H !hVhYRoזuۏ(}7ohL_|%Gb 0JZ\W7[$c/ߨs"cPe=f KOr/_|u;e22Wg^avN<_|xXq+u|YI:e^s;}_|Ϯ`G$ t.`8_aW]}PXaU ބ}5s + YL{ *x|׫_l A>E{={+¦K}G ؏h[[kxl {OGˡe#9Œ=4<#jJZWj`ƐZ5c? \h6$6&{gvs-S237;aъ-R-r_16nQWx N`?+r'4;یYkZN0!?G d<q,1MBWAo 0 <-v3pl0^=p"_[o--|A0 V8PR%gݺ׾}NȯR?f{&˷/u) 0M-_{Dfa$ZS 嶾kuzۣSK}tudsEa-1~ےo;ޞT.fxMw}{ ;VOӄC3|=2k 9 -3/@$n_(UbF"ﭩw=)@ʉvtf{rITNxN(s:sµ[pStYGam3vGXo{EaL jY$exF^QVZ!Bʉlj)`+tG:σ`߲t/y~8*hm@CSQ;՘t/v:ƪYtWk@N\6sآxt_:t`Zz)tOa6B`>L@߶Ig"at2yԱumh.,}#u#ݣ?!gpRIBwC9 UB̚@t٪!)K_?t^>|,ZNe@&]{-ZDuuڤuGt ǺD%kx{҄yQ{c, ?z?m:B {?sJ" wfcGk3[xTS=Ӵo霂o6z8aRs8{BG|6ix@޿ZhcIE8ٿ%s??NF5Snm356 EKInRDG~'>(c:qp@t?>H &o"SLn#P2yЩB+zM\UD^DR riS`ڻdkؿS.f%Oda4jO}~(0& ix?[QKu^84.Ej7QSRFH[^}ս O4zNUEbxT1>/YwKc˻/>묧z8 ZiuxR!ArxEf3@KD_05hY<=y *$3Hr=y_D&/ {`6:Ef6N؋h+"򚄜ZW|u&:GfZp{yj1_|J$w&:o|U"mgB}E^-󬯿JHpJb42Y~|>vd_1vi:#&wdMg)r /wʦ 7mJo_-*ƞ7V9UM1"; |hRv~_xpq (r_մxݶg>.~z⫳v |)qf& ?-. w#s#h/\ʗ"5/:nTsp!sЎyBX@&~)_=[XguN(=\ 4(==Nx}b X/鍔v'Hȣտ=]\vS>MUE/L5^߉`]/(&}$?S֮;㞚q#ם#~C^}Nk 3_<.цSlv)"'IDD?;=4G.ŀ-"A_YGdjf=c ]@Ez @*RYm4j^1&B*Z&l8PTmч+ &,ќ~8!I+.Q $ns6ΫYJAX: Kt1¹"^rXRzz ';$Mށe-twO+yF1$0 ;XÚ vm9Ļ G-e+#a ?S*=+TE:Xk֖4H8BT)қ'\1J>c$|JGsEl>rnW#^:1qY1F)ZR`#_u \J!z~5pvH f" b 3=wRd o:u5h. huaY< $s;~=Gk =}J eWH˹bD" :sM=ǃfD|#<['T kS8һ"RHc O~@vnSCǘ䶭1?ns_mc 3x0"Cpu:)% q+TtPrр^W9`g(qśecfqtX+<' 5ϱtDyX?zV z iOK<^H &s5{St<=SzF/N'),XLjhCz*W'TuyE0茵ò*~ Z(*<^y@,I剘BAمk(hKO^&A†-i_R|\C[׬՝쏓t=I3N%Gl nm*qǓ}ukN+QIж63EZ^(Cgf"ݔy^ёLK Uz25GCo4jPv{ τd@JQ dlg#S*V?"볼͘F$!~d糼ƘG,Rgy 0F6oA}Cj KJ'hqG+y޳,h,5h$;0'Q&JQ "t<.<'[^Nh"}>koϓG-L܆ƙjM;gyMlPWѺ۞?>C5*oGQe=2WqRMf,5U0%Zw2YlPRbϒiPPq{G=X4_w_w?/_O?Ϳ/__R_?Lm"m.ywt_OUU}7?/{_|48߽FOo߾/?< 3irUϨQ4lώ`ӝ[g:pNhw}5c@/ &O)v-3蹖E  K Ze(DCpkedrFLc%Ajzܪ[ yݟh W>Q4Ty/8~herN 7DNVU#+.(')mҾk^hzypϗ0LD~ &ψ'SÅEe'~*o"RC*5]}!vCu ՗{< )zq }s5~mMG_]Ĵwg\ē*}*oqwfA$+ 6~ (ȷŵj+ޚ!K,o$O 5 h{(u *Ԟ$91nWhh.w5~-K@O@&F_K*u8T~R'CٹƯ%&gWiS.b-9y< ">p# SW?o%ĮRiv %g9F ӧ\&k`J<Ư n+6kpQ獵,Ⱥq%e-?s*ew_p xA,x ǥ9m4q9[Ԋ֯9rزQ3B e h9K;qv*:kpU1ӤUU3keܷkM2RʒBLv%Sqފ%||$dk}{|EȒ~QGXQMY[HS1ߐ%7Iy?ySW~汤'K4H%d*GU,iZ$9T).dk}rOR4J?eI#+#ָBmq@K*WIY"%&͍DkJ˲Z~ wƼ,6ItboY2['e”l3z8$e֨j?ې%ao_Y&Юr>v|qȒ]mB v }:˭5ɭ dkWjk_첤ǪUK284躼Gi >7l}Q<@uamW35!Wg;f2m=o~6ѺRN,lƁ}Co]I5 DeZZT/WggגtoZy b1L\|"r!|bnõiVWyD"c6- &72OAah꓈ ophmĹOkO2 -%o$D۲RJf nԞ/A` ɦ Zv4褘[;MIBAI%ǃ<cKRyLHh\nI<2EN(!mb15Ã!e7OR25̓ȣvz3l~0<0&I9* 8ރZ+ BZTfg%YFmmo7 lUƲLep"I,D~.nV- oHξ-`cX(皎",>;BPC@DQ.wƅE퇀jMekJ'\;*i="nI2-iZ_oP3͐ 6ֺsI=ښea!ZL-2iXka%VЪ,I]cm":J.b f2䭷4*QZcO}AɄЮ5M/\ Wjv" l5kjKN&ivUG134-F ot2ixW^?3"Zىm5%B[MLfD dbW}Sg!g c8멾Nv|#dGR\1~ },[:;vGX!l LuXIp*]5StN%Lhu:±fRfrKO6{xNf1M^ЖeoƷ K/ `W3ͫk4{hN)[]YuY\ޚ~lޚAE^5df”͚iQ.`58ǥmA}BdjPf/ސk5Sf) CykVtט°Ji[OxCH*kEU=^өs|Obw%O%ø{c.CSZoJc*"tXvB?Gjh)y?c7AV3dhTZ3CQL$FP[^VULٚfjYL's1v?;J4WON ٚ 8P#cQZyxc< J(jP,Fނ^ Z)O:׺_4vY}Qo9zXڙ 9 7Ïf#*s4/vTwxD($zg*4C2=Ert99z2~^uŢEw M/_3,>ʞ*혳&ڙ^nβڞW70׮aF\MkW*ck͡,Rha XW|]9n9. 5ѮJכCuǭ˰ջ x%tuo-kB^Ga]j~~I ~h4ŋNaFo{%xZCU>%+S?~I&0 c;9jtZsL. mԼC;.oIe%k"`UZbu{yp&a`)~Kk`Qz!}6Z5ڞ{^s8ˎ r6qՄhȎeH,>89O{\J4Z- *9k>#.{An-\}]l h*kae` 8[n|ֈqJ-ӧ)MЀ5ѰZ^ahXJּ508̥[Vrl%rzCj[ >a%+hMKNu4,8n42аK3tqDw`k)>DUnа$(:a@X歁aN=CjoˇՒc{?kaqo-<)0G렙&cʽff!F2Nz֌+j  )sǞ iHnn$#B5ѰZlſxgoe!P&`3,+|dq<4mka]̑X 5Ѱ$k_mQp7@bnmՕMY@Hu|[po}iPhXxo}Q(Z?O@au{\ s Z KiGe ZGr۲7]ޖreoшZ&]/RWߞ8Ѱ6<b$&Vj>;)fexEw k|x8ح& wdo679Wjm5/_ dyDΣ%9!Ke['K`OYlJ!G,d ucR=EN9D N9)+ٮޚA |dQ=Z7yU_m yo'{;q|oFk2|<1GAɁGզuO [ eG}~㛷fohvʚhJJnye^au,!Sg7p rƗZZ_>eo\u LDCu;aa);̿"F4?1,Idj&^5xo"둿FS8àCEaq뉆m{ vjauH1g5Ѱ Ř1+{Cߏ_Ot"Rܴt읈cVDEN}RdƉ _Chح\>c9>)VWN &^Ĝ!+Z%Om8WAz%ͧQz[S3"N693J4lSieÚhX[ D61eM4,.9z7b6ZK7$]m0hX]7+!W K2㨾S +ڦ!l" ƹW>ѰY> D6l'{kXHmG D6EXWM4,`#W,zua JHdBҷ!d RɁz|{kZkpxk qbYhNlaka.H5}06xCDzGhmF$o>}adԨG=ɇ2ʾn ªk c~#1-ѭ&RyhNPZD8yf aCFԇU᱃4N5-B}=1孲dDP'hwiа2C{]VW~K/QM"2 sYq]C |I[3&zoQ2l} '6&l&՜x~"[ztgNݒka4¬h5uW7 @?FuYDo.KN[ \ ^>%J/GX {`4;zsYr_h~pYrՎ25ѰC=4Fu >Y;I9p-}SP*>axKsv?@*/y=*VG~s!#\oN?,=~Z5)o}VRDŽ\9[+L?V"c7Gw]lοD.M\25egx"c'qD`{kdh] =6ïHocsX>o}d^}TD%2 t[UȉXscd㢋<5 E.,%2VzOc]]oMmj"HMVfznHyuZL-joM@$-ׇUi9CM~Xl95m!ff-G5i5P(Ki1g!O@, _I5oMkh;Aow2Q1ސbwF^|wZ=t;㴄[D釕+ޚJө2 u.5b]rU:^w;ZNƚCcկkF>9};!Jp _5eIF(A)ֽ|%6Hf@Wp ?,1Cd NCؽ5{#v} ᇭ3%RV}8cinᇭj'~W"Jt3yTܶ5oJgXÒJtJN(Ֆ,U,h#5i(!J񦂢[7ط \6 YNlM,̾>.I.V͠&ljr9p9 +?vGG bfOtԔ#ʸĊ [ot9&lqO2̈yr- ľ ot$* p'?%joHn8Cno%dV`mU nq0+/:K_FG{ϲ [xtO3B 2!ά@ߙHEY5bBԽ-aXǤc+<ԡD3k@m*R8Г\xʫ%4Ǚu&E7FGbq ƙed&[ymս~DctTgi#-D[ݚ<>Vx$2IWh[DWj %&71`\KH,Y%"So(F}BvtmM<щh>?DGcxct4~0${Qռёr$H[HN3Z`_TKmU/!JS &Vj ϯx_!78i{W43KiJ QK+c1WPx/[k|%́J}RcCCPwcGLثF,%ꕀ1}K+U-1xcv$q순T|dG[]it.`WPmu!K<%Еݖ0W&̎V7&ĕlF!3+;=˚*bj&{"h]\6> 6yoOP0ZVdVd9=3%n2,QJʢ!O2 }d,Vd>Δ CoLY89~ ړ T%מdc7ړ ҭ)oQ{̬^j6C{PXɔol T% 42+[.YgJwڇ ߣT"2oyc*' %,uwb=I-!(Qo[R O:xrb>p~RնL b@G8K*LES%&~ORA~]~ @ }=%YfUK5waN|i?TuZkblzSyb;'eƢ$Tח? YiKiU7\lj-`ɮ1k1zӖpS}'-\!4LZpMq7@5,(8ZMPxk&DԳh="Dl^8J> {koM'ܔ(#6; 7U?) XZMq?-1AV4gAu:cvG&ox<X͉ozh|<]{5@{ տ9ŐnND}`\W4cO ;WnέՇO!DWRpG(V8}UWP,%`|d/4ф(" v 3$oL.%ܴmǜ}$(9$9N5o}nJo]zOP$1WDRi 7bn 9n>t(1ΫCl%tVb.ZMjLn4bZMh9|MqW«9%@;ZfZt-ᦤ:dmPl[CtfieX .^QÜvF:;1J{S-n@;䅿fٯ=Q帶<~ O"֪%w=bAZ5~e7(h\u]=}|B#?> x'rU M @#2Kbġn~Xg_(b{hz(x&:3YZbWaXįPMq-ӻL4>Wzi\oaǚF_+23D}`ɖVUK b."T;nyXl=ƁZ;0N 87!q""dˈ{$D`YQ;p;Ŝ$h |^. Ҫp?VFPԪ$r,VwYSKTV?eQ%ċ]PK!`-im;5պzVCbz{~F{Vc_4|7#x+")-QP/G`D*&yRr_4m-Q4hjGT auoyZ8\Zq1jt-AʊB}ֻucd+o5A:Ǟ:I>`^+:@M%C-i|-;tɪn/e쒧k2]C/jؓ 5ႜ=[w 7w HV-#=$38<=$+9ܘJ$Dn5OͨZ"YQ-x,^ָc .a@HVթiC!{}B =ސuTC#Db@cP"Y;r, bƒW҄lx5vHSH7ԖD1#"< YzPta *9sY+-w1@#dv| )s<b9? h~\!'jEȨa.!Kd:݉Q[Wi7Eˎ[sU͛Zf8e:jysѕ0|=#( w )3|(%?ҰǍ]$DjƴnEWOlZ>o}}KoZ\•ǯ73ib%㏥./h,%q˺%Ң'(Vo:b:&Je=.\"#񲦉EN>aX `{U[ C @ ɍYqL7 %+ꭙG"{Ɯ4yiWL9{H'^ffѯc33v{d(8w D3ݿ8ChM{ +"f=@mŮN5r[d/xey<\xc"_arO̬f?%V 86ug]Oجr:'nVz3u/@8Ys{BgE[ E>ΪimzBg$F<11QgXY(YU,'tVƀ:z4=0{Yz1Y>Pzc^Y4[JO,` 6o9)r5WXL^~>a2hTOVo^~tK~xTߝ`']@KyZ'tVmT/­T7&ֽQu> w= E׊lk Ag{cBg"[ T7mI u :QcB]: s*&*t΂6: aN쀕R$zBgaYf 'tv,~'2iYiN\Bg, @b<ќ^*ͱ+clBg^<0x7þB"S,y't{^Q`tT|I7FGRp~Y0`r-?Wˍ?Wc]>WO]p$DE'tkQGG>ˈ{Bga;5Dct$bzBgAmWM9iBAzf6Kꉚmh/o4eŘ{Bf +G&b5+,&0?v쥈V'\a"-ˀ׈/ ReF.GHYޞ@Y`vL8YKl8V$Ka(FN ՝fYPbXXX)JveGpzGark7k\؀ƣAC1AlQp ]=X,A3<"), d?L،?H[1!#;jfqfGrb>"Rg)k잍i1u߅iĺGtW{# %D6O1;JR2;jJKc1;;Ê$C+{Iz猙OP''D ? bɰ5?PdkhLɠT^Δ 9L@RmKAnmld=xAxD 3FƓ mh8ZO2,Y>daX[O2>dEf}< }U>dk's>K)dx|]0>d`,ôlXWxV9J"C2P. ώܾR2r{l $&.<%CWoLY'5] C 1%ӈ{xaj>dwd)ƼzG)XBg>%\x)(1)6|H)DN؊)AdzƓ , bÞ5:lTfv1ጞԮu&g3:c2zbR+'HM<*bZ hT(U/EW6;YKѭ\/EO/] M#}]Y1::b9T(Ȁ+'v E#?dq .ϗw#Jtrq?/'5/77fnhr3f)s>!OJ3tLwx8v헗=%4>ķJhatPCp(ԣ3 e8QqO(h .nT=['7C|@;i_Hz|uڷ_ȁ+D yUr v52xm828TTiaCbњ6?ueQ'0Ti ;KrQ >V6J) Jh8APMCkH8d:Xo, 9NO`6? m3;T7~O`j<qv,߼'0TgaJ`hעL@ -O+m8FgqXȢSz%/٣Ne&WW[>AjY9P$ Up<{ V  Ц mԵ=Θ=lHTGNk YByO`Vn&0% + χ,Yě!TVRfRkxkku.~^.=^MO*P=Sy-h>QBhE e}}?Ya:MxROhE9 ʽky=|E+θh[(â5-gXq(tNJdL= /߮u4">uy!OBn *z2dQ rIxMB;rL)dXw&pk>lVcHcoȀj'?峔=Tm.)8EtvSt21Mxq7 8e[P.Zz<.βǣR, `Sy`` ȉBb8)s1d,u_pZLJN*n?&$r):M+#:1fٔ_â?&t˪e(b3 ߂R*6ÏTǠݱ;QvrJi7RJa4҇(MJф3A`VDÃ7ǹ#R٣1;hKfU7&9|hP] Vp]h5X\mv' nv4*{RxO\fBy,4 iz$(SaIvd*_KGB2A5Qo6K Ȭč̵=I4j‘pL]w$L{JJ{O8QJt_@j)KO2Y''?! |HEF a''w> nwfGQ?$Xj$!]F}1( v: _>,[q'n{INvRJ{O f{J!ecGkp'neްrnXOQIJ+>)d[QQ?tN@voLWeJM퍩 } .̔ QxgtE pGG)2\'$%ý"7hrQEE+4Q_Xb| & 0GXtuv#,zY=\ޘ7 xF+ٞ> *þaQҁ{E?3 Sc{5As/Nqt K"u^ غH%HfJԨ/,*jȲa؝dUȲ\ޘhN a#ޘ K1]~,)nPHEU$g, 16sYsH%4NG,q>'Qzt|v7UN|yGͫ.{'N6Ǚ(p?YRX'1,>3I!m{>  _;tŇ¢ >Go ɠjQ&'%?L!F{)8>GPJ%GP)> JǍJE 㓠tfJQ#(%!pZJE'Ai1~_ˌ#SC4:QEš@ԬskT쭯}<%}5l-AiZA) E -eKDZ|/ky1v諭DcY|ɦ֘vhqˍ,+5_j45,e'Px3SsTcRG]}S;6 xL+_VG߳RVrjQab||Vzی쥵Ʀ~ȓ>P*68 @wz̮4c%"A͗e0ĘG"Ț懱 SzL"B#EF`2tk~Ps,[ 0]xgN3(q4̅V͙CՕP5D7Dy%92i+Z@U фȀJ'mPBтo#QXv%L2x8 bY,EG"5>gWnAdb5/;MHRG7 `G8l&^i'leI bJ;FW}Jcj>zPv6?A):?|V7Wzc-"Cۓ rg쒠>n{<fؠ1?XG'F@4f=U_>UR!d zbW̞"D,f_S;^1{WlhMɅU}<&.s@;ݐxMpmڤ`r>*(KDwv@d|mCQ$ ZkCr 68֧^by3cEv-XI#ˇ[SW攋5V2!f(S~0lzR;_ѕHd@TU grGiT'ֻQ)0Đ 򎂛OJHքlRg3H&@i#!^;r :m2S̷M攷ss/eN9{ϜoLXm2޶3n/92)|6hLwX`_zE\y9#BݩH=WOP)K͇@8+LO9pBeKSݿ/ Dd[ /dM=e Ѥz!΢19])%[(d% xZ[v|sbTcAx-㹽&`ngdhPfHqzG)*QJBǙ/0 SZ)Ĩq f ;{)&y)( d#6XJI@3e>umBV/yc U(>)/C6K`A AU Ը?/_B1:qΰ4Rqq@ˑ:Wn:>{Q< f}HiAν1:RCJKt'$yh:CshRն}N^4fGY}!.zULHHhF#Ϛ 8Ӛ@(ugMR㗹D(^(v;ie} UO󿏡A1r !=CXž`1pQ$摀K uk\QV{s$Rؖz7pɁE,'/z+4dF2%&+bJώ8QbS2jV|A܍" LJA6%\O2Yd蠭17vqB2XY>$úVEf'dDDI<;~z7fxTƔ bY}'Ĵ\ꯟỊ̂E4gAx%JϿ(?(?Н3,%2 w&-7Cc c#nD7,/*S~GNJڙ 5\@fޮ dt8a.B.m,͇\2]{>إRXxt"qͶ{#sҀzoTf=FԬm"/5_٭x#ⱔGCa0c⥖9dq>RwJy|z4ij|^j9=8Uf mforczZ|hL"m1a15ґdn-+xQ\7\%ސdig*k$Wܓ8Iݬ^6y.hŒ2X?r@WC8>Ng8ž S@32fHԼ)yf{f}St0'>T1dSY |绾Fm~`B9}i|L1Qrqi|6òkπk|^"/bg=sf{[-I|֗5&e{4l?ښGgהY@PDwqfWܥl/oGh%Cw?۟~7'C:^_7?}Eן}?~o?_?G߾_]>w?Tow߾ݿg?~?OC_~OEL~ ?~_IooI&y韾_W/_??~_CʯF>Ưo??ˠ;^_ÿ|_/~WC=6׿W6vvEu!w_zou/3k^>4~S_,}埾U׋~?Ϳß'>~˷<7Ebsڏem#'lE${?;AVl/+X42ёN+U%>z;eŵ<7!U&MaF<&էqJj%J1.XFiE $1İ{=6QUcJQ)RgeDe ޼],y]G_Vqlsg\ZUkm$B)cH08Z)B~Y/iLT/jx`/ ;e)QY4.oۑՐLՉYDt8o,`̃ŒVq}D+@ o>֢ n.[PfP"HשZeRLQ\_:wbbՐ.ؾߝ,i-bPbb{[:Skb|#4R]"2\%ʠ @VC,HWSXE0 %?IjiNj\4y3%`/jv 屢!MyV=j^MmҥLgr|rJElPnuv\F×[҇㏪&/oA)ZhyGd#G}hM9WM.s^*)rj0w]EyJ-ҕ w4QUΗ9oܱ(cS, e+Ke_wzˬ~9W3PWJ Y&*FqFp4,\V;g!]ܐF:KK?-^\L|5A]K%ʹyÝU,P*5  r,9$Y{Ȫt-TIiZ)*Ѩa~]}y1ʦt,]]Dَ {UuP`?ܺ]b ~"3_E_.n6- vĞsj)2*fD*Ka뻺9)B!Ǡ 9Ugd4,Mu6˰XA2%I xT02f-@ 3ND "GiB8EFmHOdcV&>nQ@=jnUg$|3QHeF'oXI.{B5hp". >l &7.Zon+ڗSX'=&)°!ȶFoo:4{wh X 9zo ,(Ȇ[CE^$ʡ r ,̂f Sњ tZh !*Z NO}/&K)ESopլjb*Ņd_kvVJ7IoqEOrW4R|ˎ BSSCCfb'!jJC-3')C6}H'IiY^ i'l#iHtD DOyȥymhW`Ʀ (ޚ(դ5Tđ1n&φE-PMP0EY m (+((r*"X  ӡ,7m"j-ljWO'AajU OxT!~7l\_D?\D'< EjTB<$gg:coh9?-Hæ\mN<"c #*lS'XE/c˽rͪ cL֫^}Hnm^MYh'cA#Q-:Q%O Z/&˜rrLe{ɨgԺ(HAp(ABPLDWf9mmPuW8^a:);_n7+,( aLu{S ҽDIQbuԋAK>)MxP0x(̢aﲣh*@&b=|Oq_&@Qr6-nr eOw @ƕCo8|m & I1`~USFr}Wj8Y\0G9CS3!<+5K_\+h`6lʬCtsA^&wXxOhCwgL͒b2zT2exMJtsE,o*fY)]N^% 6 #^)SKCr% J VE$Z~0-?T{35KүU/oZ)aeA, ?(cޡ=C@ˎLP( C3RV`6.)x*bҜ{1N<֯2oSjoz _`RbnԈʼh&L͒[D99C 74xqp,*zLgUۢAIS,+Wo%'DvuTW(I"u:bRQE5HjECr7_eCZnfɔK_z]k&[}W8}2|Qo _itk)@YRB+":\Gt>u?j.aC Oj=.B?>RC֘6ч&wzժk:]f1ע;m^ zj`X5|u޴S;}IRY/E/ʋfh/J9t@sZ$ *$h!NÆ\U3daã$Q[̪!3S\ߴ'EeGP9@UmUŒ-ukU%G#KQATkqȊy!l#b?:;M"sJsC! R=+q7H=N`f/4Kbظa+7U9iK9Pҫ{O>GRExe!'?KWY_B\JdVhP^Uz P;^hW  i״ͪbd9Cfylӭ֥ϦU)D#Rw]PABn=A5QӂvӬ{!@ӇHY:{([W4^rX9L{T Θx 9buh,rs; 6*T;tqE&eGiHt;Qx7J!RK{Ÿ+fGFaA \bo*y*'Q9p=mC`x`@dS5Izj4lJdC6z{Q,plVG=" 7j iRL?CƿDD%"Iw&raF7Ce9D]S! FpPӈ`q-V2%xtm=T/"S}x(""M*.iz[ċsU":|ڑԶ'L.CK<Ԩ5uĐ;y;B6^Ϡn!&`)b>lUnV4!joUNhr] 5 Ϫ了1F9ZtqYzJ;9-bF6!R7,?aqrBxzΥ'$ة$]OKm V0I9ԩ@L>$3:9 RsB,CnR!\:"`]з&4[HWBjvZUAu܏Ed&|LꐢW=yYP% p,9=Tk=WJw@f% y+T$CD߸5.nyUZXU׋kmsB=JYSwFY.ȓfj`GKn' &,3VkMb%Vl9TcaJJvY"!\]`.ǒ9) _;>$g-(þ?slq[6tdrQԳ G,-⡮qk2D4bP,')#t4_YM|mm\&Q(\{ nj ^=UT;܇gِvrDz!߮q]>KBA<\SЇ`q|`6fiyz`К#Diӎ|[ ۖST%s e1/9F#8ݰE  mQtPWU? A#|1yW &Z7/**5; O&BDIb'Zo$M(nFH|"4* H>E UׇkIȹEp>YMsd]H%BGUCԇk3[^dlCyCjj!1[6н[6=ɎegXٗ/~M5;oS;_=waͩã.6{Mq8iًlq|,|*l|_S$/NwUqu+;^O;~>$ V:>fCA;q) StB߄>5aVՈD5; JOD$+)pkn AR&DDC%KusZk>% p`, &-I9#l-R:a!?/ ]]YmRU-5]|GbAw؂@B\Rv(ڹ," ]M{|YEhul{%r* q5CGo33Fj*[P/@XL4]sc>z-H_p*yEq?Ek̮B8F}H8V+%MzkQ ÞJl޲ԄklO i+C"*v7ա́ A~"tzݕHc\(q`TM$d⥣ iknQԲF{7Pl}4@<6%2R[*ְ a:zQvm)߲1OˏE`,E1Yql4|J@W#*Cç$}B*,xku&Vk?rPJ! YRHim/2D7E+xKGGNWӅa9)y*ak& 8bC'$vbyaP1$T[J/,án@d jsE@@oJl^\$ۏ\FY_bHARLN.łhajTx?1EL!@o=V C =A[SZ8@K xE!X RT:pۊ@) #;:(o\THi}AS&C떃k*w5$)@@-s?W YMׇ+E(¢f˪,(йf.LA0G(<(.Bi,M)o3^͓~sϠfup q(n8M5 jo :1v^S0|3)L]LdzAT8ʺ+fxDܺr!5DΖayvU^e:e(ҼeF[\Q5 _0K:2'.$<"bk\oDsyOSHÆ?Fq>;͓wBՠ֣>ݾ˭'Q3@Q SMs@zȐal]P 3Ț 14}YZZ-$Z29P h5R~Ѫ~,a[؍Z._P ~̫)7?)ks?@ṃh&,wdQL@rXW3W#VqS5 [9x- uZRĚN%ļF c>Ty"~ѵ|>MOjψ0blBH׮oY5dla K 1k׃$v#|Si+Rݞ @q2m؞i@rvrߢafcE4(%5zڏ؋Jc5I2sKf\YSqr$%~ڇ,jm~zj[)|Dɻ.Zxd{(|6NJ=?0 (j{PaZIqlC9jaԢ-fd pmC9.kI!j3[XV`Qp`l>@6ס:S(iUmB9Tx0BU|8u(1RN"$:E5WY=I8 rjCka>6sbp!̝1;)n>Z>v4{hhB6w$iP%;D\MaucmyE݄OhhcxO] zgw?!b$ko|t"R6;7@S}n00 biu7)EMD1L &"ao}nmsi.l թ|p|BA^/cϚE$6]WŷWy$m C'Y)ȑlԂͨ3uQu%qWQ㿒 %>P) %{s&n4 -6֍@nulg~&e ; v//WĎ -]UMiURn!xA܆c7- (o!@Gt$sT^OT-u޴ǀY!4L~b.]BezAt7#5\m*héI+q19 ?mTRfCv2YOx>H5$@'FcLA8>V) m)PzʉcUs7@(bk9h *\n4ߙl+L .`Nx+"ܫED(vzwxl,i7PP G Nx-V5Kob %(\ 1U6f($;YvZ C2ΪkUN<,dP4NHViHYsAГǁ~t >ez걿l "U 8J|O9:yF&A2 ڈJS*Oi'*z$J#D_&vCNgPM^q2`}M-bp ӈKФKK5;WE qkp0ESrwI>|JG.zs*kW.;^2zb?nP'+h^/VMiJ>7MK;z9Hyֲ q)9db?@:'*MM.hECF|uRiv<ūqȝΗ\yDf__!.>sA\aMg*ɔjRF{\r2{>KJ jVN%= 8RvNBNܾ-X{B(<⭺@Rr̀OA4YK 1@,h04GfL)nSn¬2sO¢9t]Y%- s:?;ZxDT632C2dž&3TC;Jۃ ů(!DOȭ$O]hPAnHΩ ë/$vBh ,a_F<ؕ EH iAb> }EY2{ta0p!ԳSaAUfQq-l֕$ +Ma A(5n6dqbwN .c7^0i7%i>IM3SthW(`Zl$Ȣ ΀@! 8pX;.W L$%hB&fӆmG.k[|ːҰ^ϼtŻJJ]ae Ψ0`fVcEԦFCrHaHСU k .|V>8 27VoǴ\K9!DNK,-KWiRgo쑉 d̖tv;PolwˊKT`:]7o;k5M06 $pڮMAvo'Q~0]r֖F#YwL$*K6e;y&7Pq]BxgVNUM\|s $"OImC2 aVg퇄5/,X-R\˹ʽ-6 P,^mw{[;k|6_|l;켂ck@Dda?%u7.4)r =yV3*~'ۃQ 6IF^W PYdc{"XG,d:␮'#l bI-?nNB;Pm4yUy7t4'&6DhLs:`j]Ad ]Q/%UG+$$6)" y%K]@?3ce4rlmH\jc?Re}n1PڀxV|w'_I P? E>mEXQ]e!>wSIL*Z7߫TÃd&0*n"I#o6[ar.җ99Rf{x_RA[ՔvQ-6+^awTUAB[+MS=,K&>z1a ZTء=7ڮ2Bџ'j`U t 8q}t28xSрC$4.Au^]rU7ֆ\ V-Dx7K[ NN-^i?v}u_#@DKcYԶvS.M"<) HgQ]ŞdGc7)| H|lk{[rcZ#.(hpA1CȺD'eC$tpQWO* M3)TlloJ9%}!?I%IW[u ®.X%E ѸZr]ӂ["C9XBP^'śZDE\Y̸ƼB #`TύFhq\oW;) hEU_ث{}>,$kd߅$ sF= t, no{ԥp|`u5OIcK{ۿ=qFO@'6QC#$Dd 3-\hpW"F Z|d Y:ERXVt: ]ch#X~K( EgT@ 2ƹ;0!C~Lu{4 g83!>MRw^Au25h[gؗC7iʲAN|*+jqpAΛY.JB=.u4]H[.@bqTAmduhQG^ (PAO  Ѡ/Ot%"^$s=ݩ!Hˎ&`W!JjhW䚫VaL ydvBb m՚8J4uE208y=a`)"G`HEwh$\}_BZ>>Kfzfi>7-[cBRHT#Axu( -!ܵ6!wMЫ`Q 9a _oKv㹚PCj6YH9sؘ5[ƉYؾ(&';RbZT\>?B5 {RSjFL"yԳ|tA}_'kygv)jU3akΫ/<&.r[{&S_*;𔒿+}ܭUrӴX]1"KQ3xbP`: v рx&5hy&%So ᲍ecQkIͥf3-P $[0otNjɓ8Ԣ5]g-ϮD̋}d5ϫ Zpj!.|H GwRڵ3 {F9Ws)aUWi-AѨ47t7cQcZ4*Q@I`;0.Ien<]Ðon+Gۿ9 I!㤩[˽!3}tk/pC5p'Ry (p+&ќR?C] mEUg9^F$!cwCnFg&&zDU躱NyV HdJwt@noN LHlS Uk;|Ƿ| ¥Gdsb^5g3h}~䞘uXdazMZ\ʅ/Y?ƺvjZ ɜ4K~^Є Q lޟԵqML }B IfU@%--+G'Bt#Bԃ.nM-*CZ; ^yp2@ *%rdং!| gm~? McҜx.l$kӠbGr}"Hzc'/(}!#z+~ zn_oqq _,<:Պh5Gؚ4&Vd@Z+.lre.!}QŔrtlwU Ka&x=/2=*я%xT^]xRx[Z*IU0.FL\E=hXS>N 3lo_oe Nf;"C1 [#e4:9)ͥ`DȊRFbsǼo;(݊%p , =D E\ͬLΐG kG|-T ̰ꅕx{[ewldKtPA$7"wu%8QCg.}ViQ$WVUA''1SW>YrVj ˊӞgE^΂wNZv$Wrg<6Qha= z=2#X6IxIH'ChW-=ui>>f2VA1nK!7򡙀 :8ةU/(@\_@qv 1+&@:!>(EPH;k٨H8D Ji[=q dqhT 43}%3)B\w1 ]#Q-)h,V#B'8n?VdhLW)TK^}V̄[v7 J=[ƯDwcAſ z*2DM젙Jx7jkg@zoGwW KQT D[keb}N% (E/6ϻcv.}ltz3~W\21 4tTs¥OL ZcEDƐ# :AE!ʤ(4jLڶ+Nƶe7ʤz{LkL>nq"1W؎:BP~vP]R#SHvxrb뒍RM 8rW#֙Dip{pplܭa 2,P?e NOR HMH7 ̰ɷZx=,"uįe>"7/엗sT-ևj0en?üqcەoTh[I|繡{Nv†eo"ߡ0ăBJTHZrk<{hy rTE=(]ԌaJ:v#dMRNy$!@E$ 1,ĂEvQ|f?"[ƺGՔ(B牱&[4lDg>2cf&V6%Gvު@on?%;AQC)cw8я= dDjEm-*"9q^:aˌ`(ُ;7`}D`j$-wmJp>2t`+o!.& ȚjqTfʞ>yˋlуv]"$A$.R_\ hq3ND2ǒ7za{.5jp UÊl!gtC;hfTTה.)Y%&7gpф f30%P볷Pݛ| -nR)^:oU_jnR,yj1V_6΂*&mCC0'TsF-M3Z` @ _lnmR ^udyD{狡.PS 24Z$+#@1Y *!UyOLL\Ф0rCӓZ7HH8Y[i ݋"s 3XؼAax # ?6"v7 lI4hFN':d1J S/pWҀEDI)mBէn7ƟkAZ7KG*SlJ,D5%Xac`Ĝs|VO.U$;LO Sbj<IH3nAe\p.v6HqAG Jm~@s vH\k1gL!v|Lo H ;[5jzM1)um<µ+ayWv6&rsEv9ga|;vV&?o uD1>!.0zB0iY#084Q^dcun}jb J9ϣ [whL^{gz|<6#iNYp: 3Ш)2G#+31 hJpl4||%Xsg`R7SY.)!; %;W~QL&d^5TxQܮ[Ryʕnko€uy),$νpf^6{#yp7 ym KBi.ާQ3ndP'C~eB ʘ>G͙ ,G@N%%^;č糿 :4fJX򐒏NSJ<'UtCDL4wNLob|Z?UzL-hF?8F 'hB|/kX! R^o_%J`]n͟o|aC c輀z祡cC蠔5BYh_tSwe@]tB<2zʫ ?Eӵ\pRm<ŧg ю܎_:-y5@Tdwq?чAp +#ﮕ ~,$^5D:QaNQǰBkRe39Ah?dP ΀?v_{5]I Hy?uwqי/2JN(DBgHQ\C?sqJiT^]Vk)ԞH(n弽ċbl /^& J .Y#  z<5&5Tylh[[$1tWȡ\KN|綳G-خ#fU`㭒b+-DiTͭWp "nbx$8/_:1-J͉cf\ykZ;´乤3ole$xJ5*,vV~t;08Iy]xu1F7`tuKVxqE-(]^o5QIc9==׆&mH\@朗Prx+4Վr-BDҰyBiqeqDs'H$=xrP2VОa׺Q['8`(&l/8q~=`Ұ5U18lXs=i(Z  HV G7)]Ԟ$ih7-p]Pvn9o9`}!9 |Ku_`9w+Q\{|$Bq6g,rV~@ ],4ٮ C"ڳiP{kbtT9hϥAzO۳wvuPbAYC/9 oj֞G[A}tsyYR0Zsd`>E͙K!sm˼h&R>h/o)4;2[+jna搜~Fm \ȶ={$[qA8n ֨8o*BeA5 }|'M|Yv8 @N4*@{׺e\0F^Dr~%;2Stiu5ҠV j;y4rVh5*xt51;:⼩G|NPފ{bry1Q |3dPl;rY@]{~=-'Cbٷ+O9ulϒA0\TG-aJ?1YRRe9Vb8ĭ#qǢ:=?"uTie۳c0CbKٟTK]~@ro{,J~n+lc{ ΥioPڕIuL{V Rmw%H-bGqM ܍@0v"*R6;Pw9M<̇??]Ip@s#]}b`Hq" mctZ<+Hqemb3;?k$WO;CO!.fR[#59e]~gIQNu|=JHOI{R(mDHBV-Pē,b{ l2[<| p,1'k"VzFTۮ%tE*$]|6)(9xڞ}.&0Y adSbC 86b8=g%i۶ BN_HNy/jAxw,W$#9zj/aṄ+#<ߣkP ;9ܚ$sAX_/)eBWa#9 BnMH9T%Ͽ G>M#ߓϹh+I`*P( EV0s-2a8"\PWh(I;`6b  /Rzоʆؙs(1Ԉ43⹵/¦"t7+aQm틯&* ?Ħ䝎|/! +eQn#cs& {**:%oϕr} $PEv^IP Zjx I ?s TsU-o0H` rlگJW˰1/)Q?PIrϱ+zWğ Ayn╬:?d]qKɵj}09&mn{rI@aHvϜzPqѭ 頡1>>yĢڪn;Ic ?>h9 XZUF,b@a/* d-5"x+9U; M>@;zh1J:hkO-D4|_\Ԟ zdG ٚwՙ@h]uW ,uBh_x5x;Gr!"O*0sT҂ " <}P7p=CY T\^%~=G9v3wli@ E ^ uigNAߔJu|9_ N-)jB!uuUdh%EzV| ݋}Bm}0pRyK\҈Β1 rDa0P~яDoeh춺mB/m?1iPyێ{i];z)Co-g,ɵcޫ݁ʫoc{2 YtUPdg-^X?L/\~ܧX˥85,ܷ9|E*.^([ L>ss_@Pjۑ@+Ng/ ZgKPa ` ~uOrud(`vfi{P;es V~I q=W{N# bsJrJwxIy ŭA2toD|,LNh ΦI5%ht`45\4\[} wLciJ"'Å|z,e)+Rw+nͱP{F]7bgU''P_ۊwkޮ#z/㎜%'Ars16C(ApkmH:(tq)mZmMՅe&#x.Co#vo+BmL~%,`EuF8LJ!X4Ho s OrȔɨEUY_EpҫSy&",/-"v⏣a!W"C6DK[bhVWB7;W%yp=BPx0%;0EŢR=ؤi?ψJ ƣe9!>I/= 3 OqtG#**}Pǣ'-"ǛS-V*uySs2' '2bvU3 [Hl=첉a'Iz@zyrFҐNbsMrD"Y_#$>13[8Z00~GA-lnv[s+=ӣ8Ŷs|g>;~'81hY[P,*SE$dw[u{daEj`#Wb<#t7ќ]fWW^oaf`$ -gmIIDIVu$-!Gj?&4olx8p{uqtd[P*d#O ?c I睊4(PrѳrDGlZdfy,Fߚ\du$N傅3\y#5-s2R}dwȖѲQ C*SC)&EN3Q5Gµ~gؠI=Ai쟺IbU#v-8Ar9BӨTl Ad7m:}KhyA v ])tx7MGN %YI l_x,!EM3Cajq/i" I)XPs| ^'Ҫ.nq+N;/rp^)bppc3St$hqy- i&(M-ti`+=nnCd&,٠hƸ+b1$$mI.iϸ"Qlݰg .&%Y+Ms- DKv{6`#a=5<],ƣ!/iٳt7L$]ɵBg+&0%hڿ鏅 r_ qKhrU*oW(+81yuM^? BT\9 #"M32`ºn:XE*&/<"옱qx4v{gfK(ZcP|e1xR&F@\+hKgWI|*ăΒ=W,eGDrG XF<TO/BDTR@c(6`؀6v2N(%&? 8n6{1UH7Gae&%iR8?9QR R$x(b3ڝmm9l@zOCRx~\CbtAd6,g|dXĖc2 F9@kiݷ\b$J &"8sWXx]>u}<aٹ]UʈxL  %q) &|*pYQbm[ !` kDia8I!Ė!i*Ђݝ(|nBQ=u 3kXDgXl3k(h1` א5v{b m`7,̂nj\O_ҥHdIfQ8'+eJ2|Օ$ʳ<s 5E(l\,khh{ ݖ4YJ[ ,'k9&0@.p G8>^oE<>9 C]V 2"c`4 Yx>[J,1<; iױ7Ԅ]% *)v3@"63OEjs{{f7iS uRp,i v($sv8HV̈-l񲰷dϢR&~_V+c}eq/*q &Sf9y~fo0jƼ=PPwr[ayo! zBLQ" :REcAzُ1p ϲႎH0B ,ӜHDE7XBQPue}Ż:3nX82\pIbhTv7jxe^}cb|U%;j6,@!7{&z52zp#a_kwEq@m6l5$Q %1dg0]dj,SKBA4ۨlQQ~MwYH\_9'_pIWrC %m\4BUԑNjGu#\O2H:hy {١BNO٩.XC̄wd0d J#w>RTRihX\K_IХ!:g_Z1#Đ+f#I4Q7gz E%.ga,,b&,YZ֤߆mG`Ei (dt7T;v$=w/BlD<+Gu=|EF *v0Ŧεص 'UKNU(u-F\8E+[S NJnd]9UmA/#G4!~Iشko+&sC6ׅzߡHG{Ş)I4{vgz¾"V43_+{l6U2tQMh;*)*a~.ٞ I8ؔ oWv76#JbJ*rh\;JOۭ%cbbU$}NJyΙy@ }j@JzҊ0~O8`K7=gL mMp6ӬaVW]n wjoEqc,qƦ]܉E.;=Г0R%vqdS<ǥO gU ^P m}\&k~Aq~ԇȵ`3Kc 3`>8#WTky6GoDϻnrr@EE6i_U7p{]]Ld4]^e /ox DJ#T-RF-jRmg(rG~|rjrDdbELíRR(mo"]4,)HC1~#ohVmS!%щ+j/Dk[W{sԑ]݄ۑ8SU,;Oz>Y"L&)e?>>֞nH"GosĘCeª^y|>HO؟m>K:ro1w?# 8WOFj]%Ҥ{8fgDJ"Ojcsɾ77pe ZgDo~Crja{}=Zk Z4آQ߅N }E N Cu^Dˏ [LWc t^ G堻-tҢ/pC^~^6(ja,ư-^ϹWj7^مtFEPtR~r&= 7^;}ŝhY~^ oI#iP2 52JOr_4~j0Op0_DBgVZUB]l.bjv_N#&-]A^,;ijflC(t=:F:=-خ︒ji ^ J%K$xgkX al4Ll+~+cP . k wYҳvm<j}jQ6nчυs8IX9WH47ƚ\XnԇH|S}X%:?."'rݎl'Bku-&=tC G ,욞Eig:!sSa1J;vM4; N2buomKz4jnZ |IYȦG2B@>8vŗ^_T|'-}!Bן߄7Jv "(uk|54at&",íJM'mds(uZ;?%ﰺ 0&efKu4kΔk&t5Tk.⶗_0BGWյElP^fnGM)i~ v,l? ަeZj#N7esۧ]Xku~VgW]%p8Z|=G[LIzAʛ8SL\{h~=̈߸_m oUIƬ_5ⴋݵR$︻?yu${1F~/}h rՀѨE;~0@?Q2)5wj®;G7 8ˎ 6ˌZQuc.x&R@`\6Xa`*{r^lMP'D|F-(mKDs\u=4=n_zB0Ϯ{ baϩ-'Nݯo69;GZ@ K){ lϢ?WwS/o9<6"jя{/XmѠe ? Ό5S`7v)[J^GoEk/meײG/\H2K~F*Դt Mw٪$[mȒ KE^iHz{]kןlhn gp = ,:ן A0tɳ⧞rSן@ #+ ?oEme]lKeQs dw5Uv/n;itJ4W7 ,|,{cB o5~PSzMRSYb~AE 35U'snQMۑZ,ei{|u;Z*sD}]Aן Lۈg/|62 c_vXtg8Հp~h=boG'd8opT6} ⲕ;]9]F(MK:ݹן^* .K28'*}b4NԢq󝡳~Ԣ錧܍.ַZQ}]>KM=W뽱cTϹ#7]Ңl?.lAA4Fd]dRK P6]"?1ayh yEfjq@9cPɽpW7w| Wop.jHsyHkgL?{;Rje!Dd>7I8Q}`[o7cn 4_jQS"/~b8r6]S]yX'z[E ]RpX1^ҪG,A;wQ=VPrgW^o !oyvnQKP$F[V? O r{dۍ~|11R='[ߣ\Qn/bE-Gds bn, eop.'M nA KN ݠz5NLo[\Ex=8iA]cpHLtkνpvsf5T!trdWg;yY[SFpA?/H. Y2&4< .Ph3>~j$VJoBų(-r$BI;wytʊUH )uB8ٝd@FF72şq㤬$E\k!NpVly(0/I}B !W @be򦀰A<\F[q"n ~"fdl6/1ؚ>,QQk˩H<՟.bvx8 Gv矫97nDT d\ĄݕYri ,P&أā]_i<\z+r? [x\'ͨfDx ψic)#{YUp3&V(1>eMi9Ee3 ֆJ|\[Ǹ{Wb|?cIC|;i.U@PGIEeCqwrI_[^ayX x/c78`wfINWHx~e>7lo,$kPc}/(ָfGV$ﵲMtm=?vslh?o]!p$'6 h;_{\?n"aߊU<\HE-}x0fG^RJDI/|(= #rJR2Q e/T.\i|/)rE:BiNϓp|ۓEx TO0m[{h|>9Ãꛗs:⺴1R/>Yܸ`牊m;a}tEM ٫^򔋰 Rwr _''i-v߳_ (?u xyOHڭ+ҦM\糿P:ү^$8*t9S''u@5画0tC=9<'JnLaD[tE:f1\o>_,^uggZ}nl$x~2{Aqij1u#FgMg Ix>j˕|k_"^QmH4ǎWs=On(z{>c 2]yuEIQ.h+R\݉Urbp g'bZ/\΍mV㣇OfPISmqψ?6}n`qW_tV||N O[B>-EGbNO-xdyeH)#Sj|/yFHQXcJJMRz{EUߋ{u,N%z/>@U?}Dm՗v/]\rhOy-g0n.{.>E":y*}9;'wlIzC3.fM5R}; a VOMDJ ͹eo*er1L`6HN_:٨4LnoHd寔qT<\W/B.WCOΌQtYBB3@ŗ M"-~ =;n|T'I7Ͽ1SӣFފS R`@_lm16*p,P B5103Cu5Yi@sWw8wőٵ a5ZUy{9:(|{ {{  tG&fLcUI~-qKžE` 6l5=cC{,5vs·\r) iv5v2? Αu#R&"V;=t: Яtf7<7vH“8j0$JgαK xklAQg7~Z;DfD!!WY6;d'"c̞ruk#+ .Nj&uyovUIjDԾ]haUM[8DB9A|M:_ CmS`}FrL:z 0_ݾfku2x}/␣/K&J$#ME& OFIBn_d>GuۈNP! OSCoܱ3y 8`t~|-c_)T1("BR Y#2q@o~Ơooסpg$!=` hԢ;IbK{8ݶ\&GlWf@G:`t.J;'3+bZ%#D r(N*̮jczz{=v0pKA} rT0_Ϡa䑋xo8}pht@}ثuH錘2/ Qv !PĪ ېvg& @a_cP>,o}16 .o'.p @@}D[p3?hIPqUACHiZ^hȭq2dhv y8wvȼ&B▪`ƍii)+1ea KAVG}ga]޴;w=[_~`ן**&.`f~s| /ejZ YC߇RwCkE8[BOb`?U'ߖp3`ׅI>u6TC}rm%1z$;w:.z3&M:֊?G@IS$H6u"\(@αO Zr nj壆AЏcT]:-*Ö8;i~{g7BW ^תQG$D@u= d*[əPgj|u7%.HTܳtb+!srxxt4pD"QzX|B bUCch.u](ӏG.%e,)ji@j hZ:x_a(C5+ }u/p( ͩo b!_9,7f0BMp^W&_hgE&Ö!2AkJ#?"[9DR y  ~/XS"U/ś_PQ$ 7HVV-OQb:u;" n"=jk_m׹7*_/BM  fJQ'Fp yaʐ6\4&1"YYГacdT:w瑠<8!Z [·]z-&)?x7s^G|L$%A+nmΕZn(,NkEƧtn_wR+RC:3ZY<8){֣TΣ,L9Ani, R GAgگ"h7GGi+J~PAׄd뭎]W9H:<1|g:JH|rԘh[zu(Bx :{\$Ȁ_L-'L3;ci'?H>B 2Uq&{n1&*)b. TgouZ|g~Hon|f(*`\}q)բC#rifs`g2PNUq^a}4roBA)B8䴊 BSV;?,^j!Z 0f5&pۮ =2aYD~OW =HeTlt:)*`XIk"'Kz`hIOad}q!$˂2T`.Yw$wǐGyAV0I"~I}6cU!5k@E+E_1(E_:]{>DڃO߷/GqHC :?OvYPC΃@̀s@i(lხ,2ߔ]?WaIH=ўaSR ) <.keLp+GU5gʆԘ/.O~/ ޝw=}RY 1]ETz sRЧ֢`j}xmo~o" 2fV30dȴi}`t/5\2S|j|nsz8u43JV!l`w%K>߁r?$SB%$ Afn ?JR﷉߻vil6ڷ1\|׿P"?i]H=C`fFRqk(l`!60ƪhuQPlki#&5wQa$ςrbzRyHoC&S0ϲWTq=rگQ foYL> 9Oj_k_-]2k/ 0%ڏ^li램~'(8f51C}ߢǝEME@ڿ_1 ,g}ߡ0΀ޅY=z{uI(O|&$D_uds[lY$ҿ_و n9uobro] P&$k"u̷wwߓ?\j Ac͇ d7ŝ ,> wk.Z"T۟Ux!Z$?GU9@ S^zo?:YCX 3H*xn_#J#2jGsW_DBOUHuο++|{@=s7 .P@XL ŻOW1>_끛̤U#g|!wf@WE{}WڐbGB&Bq?D?|菫tAJ͈9^^Ijj##~ZHaw ٞU`r_}頙 5ϒ}ƯJ#03jbjb-+rwMk WSz\GG^߷x2l?<ު%{RW%l9m$/'>;R \@G{=}$ŰSgnő@pMIH[SضQ‹Ť*b_(~J0l'#NH'=V%R7˫LJ B`P]"w4Ut'eU(r'}ѴAISGht'=X˜M+{ 8,d;fm.Q _1iöSՎ%˃؃< T`.A>n D˃p'q|ﱿe!{?PD1^yakCߎ`|$!wľ)$KYoB'9,$_/bij^I.)kCQS$&R!\YRhkUgheJqȇACJaU%Fk@K3Ezn]pEq>C!HowAEn!@2l8/ho Wd:GU$ 퓜 jOƆU,k~HYA>wG{-b7}|J@2b D~ '2wz7z|NPoB51N\״/*7ZkZG? 7bQBphR8 d UKw?hsug!eAA c߸vMEdE0a# #*Tx5,O 4E.O_"膝J{AzCDؓQ.k}nȣvjnnb}v(?*͕oy|"~WW6h"nŹ4TtAQ#2|FVxD5]2݇JzV7B( R<+ Zd\SĵbᅯP5@ ]P1s'=Ԕĩn?{_dIuΆVYw%ufU Kj}#? ܻ3T^,3 *ˁcE?!x98S(~<:)DPa.Eo_$%RB@"}b |EdX>T.Kl;v?t Juw(&'=?|{>|s[HABpi} ߼Р6Ljh%XQ+\jg93: =-hپ"IBe$.`=Q6C01DiJ+*}{^%1p!+vʮngשHɢcCD@~q.6BD9h&yMO&AH FIt9p^GNֽ/A/*|n,q8 KR@=|[ǧ)k{]=;_`92-/D rnoS.z=Bb/cb@R}*^'X݈(bNPiIRCkq?TWE"RƺPb=拝 *F*ur'Hb$zfkQ3#{MC])B;9Ƒ韀|#~1sNh$v]T>⇮Il3,䂤=2;UԾ "6ԵMag ېԻ67R2"L NkOb{g@@Ѹ1`=I R3yp"!&ShX@ڝ%ۃ Įgi{>+J!Q$^i?qx :>IRH)$qZ|$^ǰa#WК?WɄH9̀vsP+ȆL8n$DpxLBOጂ 71WAp|H",d/NcwŠBFx|}!NPņsQfxضWyڕJg8&} (lG9^Ob]hPì^anGJEɣ8U\$dF`F%=Oha7nR']C` *ö_>=f] (z CקX!Y; &/(P0:;%1b@MϟC$hR.;@{ccn D-V PW4m+, N^Bam %Zb/eX% yHn|ny)]\2&hXr/$۶D#Ž 7qU$'8{MR^S@ =7q=cu(uT8?>lz2^QHF]cDրdQ}`^:yɘ|.D}Oh6s vPVq3b <"oyb+3?PKfs<r !_td D)rS}!%מW+U/n)J8mUR?UtG- .)3j'!8|8U]!q9 I%1}?IQ~Y>?<4 ~"-Wz>Edm~y$-Iȫ->y>գIIM$uR+cy$-Q !ǚπQ o']QwlHtrk~p0l=h >|DBڅٻԏIZ{ T V5 3wwrSC>/g ZP",`;w*ÁV(,I4܋[< xc 7J ls8<1ɗ8lj#)ǁ`]?O%nl}F*?΍(|674ɗ}GC@Y< 4XBrR/S AnK5Y~FO2ϵ ѬRWv3C+57 TY=;mFG斖Râ}L@ 1`|;1@d=Cm ? SBVU*Ix<5*ON~kk~IO0uD&@*,mA֩/ %陁rI(?0?j啀'D~^/ ،TMQΚʧi$Hp"vQ+o@CS ]Mt1]'"Q*!QyE8 LaR$4>Qiq!ʧ)3ɾ#~&wxE~#z@t'+ќ[b}={zV r/k>@W)E)#(&;xgq=kaB^<ɾʵFɺGP=yd_9r<6a!l ]%hg~сJH$M,Mh`c*E~\nunʢrMv'drb|R):J=@FO} gL+Bϊe=ev:AmY䍼#sq\@Eʹ"T.zd_(Na@LФ Qlu* t }-3oCȾ`7Ƅ_O/@@#8*XX"mx=źSBY^COMO&7VK?%=nŒw<nE"#ZءK{*@IV(

#joAКeSrIN]5fN$Q8>ڕНU0K&>3F_psbXoԈhn&}g$7)JdC>]s)𞶇9:Pa{e$ ccɡ wuSJXIrҠ|\ND:و>{ bA9t|,?{29Xxܒ$cI :H1vz l"G۾EM7^EJ W|MԾJu~ˀq; _(xʵw?jeI.$Z2C6}bI,a]豿ddQq4$A CaKwb`=~{& .N4QC5GOH-NB pۤTQ5(*d>͇' %#4|?<Aa*yq{®w(rgX ->/$PU] =1>'ϿwFZ5P䳿IgdOPnXLnx`1W/ilI Aٻj`bcDNkD(Aq\1XOk[ B|'=~JjuX <8ež* ✼_AUȋQ"#)o;U1dE hF S_:EWU#bҀʺ}skpGZ*5+Az'^~ Y[=%@{.+ hrI:¡R"UרBFzPLJ D|&a= uѶf)! =Gu`lճB,bw^=`bOZCTo"Y`jwqj@_`O LbE- dKh,FLd`r^b@`װiD)Yʄ ֚r{E^qP&PQmFGa#rRYš32-ЂG3چPݩH@)ĊG]B]f#MC?a YǷYrrO@PxnA3LaWPmCCh!H@L_$GzE'}rX}UYA+X ;>$85ИP{?z%'IݼWv04Q axJTW[= 7xx[miNs[I@VrJR'i=G*p' lu1; I1 `H bGP%/:$Gj 9 W[ݯ۔q%$oj$zA :.*瞼,yXuq*:;qdlA|qhxv7plO=0jτe_Ur}H?~];&O<M0[/?U @(=V\ԼJ%9]uKzJjGKo.2AHTn[4O'*#lMWNB@%^ j ][},Ŋ wժ$o=tQ٪W_*{F).=N$o"6C*lp ڌwS8 0"oJzaKRPa ?L6yG*"WZ`TOD[wfeMDXվ ^ٟۇk nE > mޗC +мnkkOK~kN.^N噁ƞOZ"U&xmrDQrOP2yJtr<:@Uʐ7 uOxt9ˇ\'hTr[ TeeGu em))pOZHB$x ePbWH("wlB fګO(O]FIDMkF e w_M}t՟ZBYϒji9y(=togNU'9zTaGpuվWyBhGp}m3 AD0].B3y7?@M G"ƈK'*D$B&IcTەC?/Ux7JR]0чkyK">IJgn:J*Ŭ&O)>g kxt2 "C+B-CwP}ŌQ}5BH'o=3H`Ew oAK l[җkȟ{L5?Fc=JӤ3\ uGQ-b* lU >u۷!R?޾J;C ]/<p}6`ZQZQO(*@UZwбl+CShY|icghF0 +yq؏O~҇YFԾF3h4 O{B^5Qs)E>k IZ)E:}|.\pV6nh# <3Ѐ"\"]*‚߿?IyԵH!B&R j,#t,3\]j{ `yx!<#H g l"W>#h7>kORރkG ث4>IsۇBwg1v_]rp1*2 }!?gqhy;?<#BYraȬcuWd(k#PH=;!I HyF9R4Us!EUx䢬 q8Pn*RpAͮ/!.oɃg/ x+ r|T[6BZ¨@ nbtƣGlӗtvlHti_l|#JChHW->ˡLx4L2_Bg¼,; ~0󦴟W.=ST9B2l/yr_$Zu@ 8ЌG9-tpze Gi?_g"mn-F7ACP?Er3SzSQ Vk/z{>~mY .==$^m"ML"17/0cXg$BgBP(:[Vv! b`?~{>2O}ɞ#R2= $/}W8wAq{$@p cz>$ /"wǣhG}Z' /'Rx<{G)~SW#Z ̘iU<1("93ũ0ޣ꯳< MbJ:FRރZiIH)5wA_пH;DQ R  e<#-ܞ4ެ}KF(pq?RS~HIk9s7= ?O'UMێtd  m1 ܏G GY&%Qg񌷫M-VL-~G\6dd X?Ll-dGX`|+ean1ۛۿh6Nᖀv)8A\a#+bX˻hv>ghB;5TI-z_wbo>5]H,a+ K(k_M$b\L*1}pbdpfdB26 $>p1q!p`0zI !V>݅TL*ux|"+8}C4@lU$ƈ#qHv? a >q,UG&braU,,<WnŷQY}3QB!]RQ|hb=vR3ϋA=>>W~Tmrˋ5 XUcEU(Z2ʋ1ǁTG CU=n@kD5_釵5&U!G0#zBX*{gĐ?EDaqwB`G#YI-$dt$(;?$ sض>UY/ xXd2b~A!׀ˡ@FQCu AiBLޑ4<) >UDK+ juTZwf=Q?oݿ?___/os?n/y3/nտ[xW|o=7~?O?_}_*mp}{_o׿sYjLhL J) ]rPtWUG6 S"&90(KTwBTj2HL(v敌L.`wYszZDI^WgL"m[ӛٙ4Jyȑ\} :Fwטs<-y $#CrwT#ZEtlEd)vqY[g"ۇ*AgOpR:WB^ԷϑNd>U,% %Ttțacd{PcDZo%$wgʳB}z3nͣO1{;b"Dd}O#K|OézY}#Dy5r9_DƦּLze*ū\׵W."RȻTwrr77FU?Cz EWߡAgNDP޳y2Ƀ}Wq]׃ҧd ! ,RK,lnm#«Qpvwh-ʊaWwI!қE4uO[("LjnY(.J@n$ 5pWe%w =L62)ar,ߣJh1 1Xe誟6@v}C(),2>@#i[RY+iLfOJjtlK*P<\%-F%P=iE@62ʍU(= C<ߠ(b8qO;AV@nhK*j5iEJp" Aĥ40V4 kܳ䲰(ؕiK Y< [¨=j< aaK!KWW^퀡]=y\"Bۦ-g|- fdVT–|ujڒ3 )կ[$)mɷ D!m V{u >iz}Җ|KnP\%toaK<aKHY\Ñq<>aKt1-– 2iK\)7']aK@PO [Rݴp+jK#z*cҖԏM<[BNtgz|Ӗ`ﮦhyo6\l v{`-im ~= Vg',mIqYL)mɵXaK(ڻ*Җt<Öe9g`㯋=+\:šdc}0 wݔGxڒh9CؒI.Dp.$ }I밡rq dOTߝ4V 6-%-@+vwVaKg>|H6!J?|}B~_{'i5$œ Q,MevَWw}({ D .ɲ]PQw\Źحy5Jp+*̬s¦Oa,e *>9Yqƹ+]w3NҍIUkҳ5Hߙ\Uq:tnIF=Hj˫ bZKk"i`<@n|ɚy!e3j=fqioNӑЌ3Lޟtb a}Fճ/8 ~(6W 3 TIՐDYƜ?|g|ͣ º[]-AGoAT;㠦kv;mgi@gUWCFA;#!8x3(jq(򭺯ߺNg6zd4ͦ_Md} -zK0D-x5F;_Ҡ\TK!'bzKja~_RX}u<ͭo1(~ɇX;g^xtPhfB]u!FWU~`/lEY&!䲏!%lH#+ ] l.; a]uz Dt玡Ջl G!嬫~َk$MN!c b]d:jڌ|gWƒp_ 8IjIi1&Mi8! g"'5=K:9c k=iS"HcMsM < A#cj1IZ]}Çb١CO|,hϽ, f3\ 06OFLDk%%or/sُC['}嫱*ki2~w%mK%]d Hp}돹Bz融TqP_ S u{P$ˡ/T]€tIgoѨqfa@ݷ0  .ၬ DdjmIm,+q"H2ަ8;#"HRS_Lm~B$y–Ҙ%*wx,3/[Y2\эvj7zKya! u;FSںh4ySE\FO7 ]L!0Fc,(?w/4|Ew_o#Hx_t*SQCҳ=JQ(o!Ң#+)ÄJ _,:RQY:X+n[Y9@a?k9CW84ږYz {8Ўh"(ᣏ<(;?\P'.VB)U9Y(Ѕ"V̺RdANARRnp%X)v~WY(lq|jjz`UQӧj˫R3 PIye6L4V߷Ӡ1^j>m1&OĊ+PPdJӒXl[甬}~{xyoD-Y!Jzdz|B7iy$-YJRnKf hx|f J=#g PΜΜ3Bc=gM_ϴʧZ|vh)YTunm=K%cDŽG}Q^A†  /_q3*S,HLojK p|]!b’0_bWr^~%˫R+Y1pJ*(Y1H2iJlеWpgPn;+hȾ,8,TrӑdZW9C'v_ dA<)iG~Z(kt(Y|*HE\R48] 2-{qۖ= Ya8Xv\"c}O "`N2CLID~D\ʋ}Tc 2XoяBzC:g[+ mEY$T&AYR@:+2ėYv^(D#ycRpER%pﺩ1" Ty+.K^L c ?(R\ӧ塲+ ~]<5TuJB'`)Ї^a DGvhJ+1T \CԇHC T*T".oE XD63$)cb\/ryR|$EDPz>dn+s&+twy*+ mI+.0<`>(s.@!yGF*x#}[v/cV/ xBy`#<.)56& F>f_T_cH8xFC-`DNΤlM"tD,cGBkF(`%. X ]C;DL ěET(cɧAy"ɧ!'MɓOu|r@H!aq%aܸJJ`i苪1J`ibG%a\c}m߷ 3K,_?M~64 [$ru0BqP N!$K`l}I,w*o_ [­_7ó%`x~߰%HXj -L.J` rtȣj@2u$50XؾsJ|v4},KƒQ|<,& |bJ,=W/q5{:*Fy0X-(W O*`6#z6sa(狶WOL`;` ,BE~Xq&BVr(^ y,%ʃ"J8JX`ljɠFgI0zB I3t U5e<ʍBToW(F0~HɣNlzkZPIl7S- m"+~8yZj=Uє6٨|ځM # $% Xq2nq~`K|=T 6wk{0Ϊ} f7cs$K|$T F$j> OK,cjm,+Y2Qy`Д56sNYs[2-Kwߗ%/njyW._Db̒RɃssuzT|E= -O,ÑcKډ4% nLR- :U1e Nv"K1;NT^$ ;Z#TTRF4|z*HkMl\W6`$Fy0XemsY!`]⾽3fs 5|5cDi>,8pj^LVu5`@P Q.jE܋ws!>ӹoY^dyj8s@[$y0X*(__RAD>~=:/! a/5f`OK,l{t⌃9UU -U`2$W5@+X}W> doil+q7Tv>;a0*ʹ7awS_QO P=VwoJ׾Sq$FCCՀfSBV.6ZhhDU% " f+(O)6l9B6=C i{u>>~{\sCᎶ)|xK_(0W#F;dT߳/a!Rh{<K+BAH<ҲmFڴ_0؉V<-(hSH {p[ےr; 0ڼo&  |Mf岓 %U)_Q-AJlNsNW OEe)WHXx+Er%kba70U`͝QDnĕVe[?VMےwo%d5$"vRdG 6;c̔+6b*gjBj MD,x |9VX`val{Qn"7*Q4.؊;+ST W_%ܻgm+ƾWޕ]8gY%,D"/=,~ZK o䮒QyXF+7%=,%JFhQ;B%;DZ.5fVƗOH=V"vTB[~fk% RY?BL8 =mGe%_Vd`ϽL˷ ANO ȑ| Sb<cuf_ r%1d"oB02X_9DRy?]A1}uŬ>%0Ga`=.惾8*h4N_\=Wh$u# ߳Hfo|b}:[[iMl%Χ-B* `"+%A$f~[> >©E Y>PĤZ[F,Bi)\u& ꘹=/%êU-Y5(OK#"K} ^[}2HZ8d{QCϼU' mm# HSX}S ҖmyujdoINy_̀,i0K$NRouWiaUD]en `LǨ 8h8nfP4& J&-J[Cw.S=C{=)a궶%ie['*x}"}N!XzD}\US}N'T^ 8].+˶6aq1Q&, 8=zQr&V_PLreyAN3/\?2/^Pm8UI,B1UՇ;Ei:wJ HY;zӎcފOffVxrU&2|u8?A!Y/G""#X* 1&f|ُD]m՟!ˆd)Pyjv>c1J:5qsI<:/6Dc,IhIDȞf+|D04_ A?Kғ%SM<*.#QU'tBjU!z#1dsBqARM*E /jU2]pʚpUIh«pN)"u|亘i'\)TLu>"0"j[M­EdKdu"ӽɓ+Ÿ#2:_A Vr dy=KIBWNB Wrd9`=Jyb.[3r~.Շ_НJ%ndv=^&~"QmO*1Nr#í=Sv I,fsZN k;n?v;Ć쯞͖!~[7nCG(V/n͆n%n͢Un̈́>&>H4Opk:"jÁ̸ 6+CλuXffp|-w瑾$!jzJ1 $ . x=Ķg+: Jg888VM낉\"ۭ x]:N$u17O^,}l PT6Q:Ys#V]MfƎ9 ׻Xj^Z9CT =cMgAu4 JF@r16m]~3B7)iWgNgf^@1ڬ1.(Υl}EQGY/xtݝw>x]X7&U$cA ;^<4ʷC`e%'S OãEx 7bJ瑌/0,kh^D%ywiZjWCY?8q_ _v_ d_M)-ٶ!Y!mP3ω&6Tyt"괘GZ~#u]#y$bC""Vk}(`oSQ.v|st ޣ}soâpvkbUX+ݷY'HL, #2~~Hl9r~H9z~HNϷ+9AAQɉE֪^K= ;iюq dj⒉IDc~Of5Ep/F> tF>/&:ݷO`}% gy>, EK{(Y2#>,;fjdQVz(*Hʧg^JiV> t1mw>J*(Hd_dOؓOlsۓOC(pi<;4HhYⳇ$Cw.}?9i40ѾCݴ}?9#[!W_NzAhgj>mMjz%qJP",lp|lhO˷s,-ěn81Km\ǨK&2Qm $Dɒe7.$T,`-oO!N lO!=}{ZS}{0w!qie>_Tej44.|Gī~G;,7Ok#A›U['m)Ӿ_=\ǸH^}{{U}?$H{}?$H}}$H3#Aɫigp~d)  ql+?udJ-?Ό[2U~-kn[r`IlDRg[hlI_"kؒ*4MEɂR{~l=ʦ[6͝cK4ˏ-oϖ,,NΣ8x+?d&G+?亹:cK!cv)%vfi81ǜnŷ򱗀{w/T^2\qv~%V?tdAnc/@u^Gbg{e{ qh^ x^R śݽlDganc/)}G{ iw{ 3g/y(}Kh%{[9x6%ٍ;75p՛W`f7δʆ}v5o0q[ l۴*.]4%VJ!g!`ŲDv.P*!D҈yLPjYa%vg6L~~ (pRVAĉ E>%6cӣ,^oge\iڕ{EwY"lԃh~{^)KmY"RrY",==!3ʯ[)6?L`;L ( Lx-q5 IpTRU> ZBkSw˗R=Zk}۹xpfuJ g]5z~%vxPmS\ >7OQ1xW'Ƴg9bVc=[i!\^K;#@VĕZ"i]D}K c;σؒGש[hĔv[ةA?Gڂ D׽a ϣkF3*N= +Īg&/[ {IgK,Bg[g 3t0^W, i=O3xT-F`adK,:;uu1d.g%xYh  -ѲKI SV<^~`Kb;mh~2/Ko6b1'Ky :aMT=%ˬI#k3|UQ7w5^P'X*֩ΈAYϴ9@%Yb_veW9=~snv\}>ǽ]A:f]+rvvqu{AƃV>h~d7*"9Fs7/TGNb[Q "[a͊ZaOyTB..eO˶jVX.٪$3ZAsgX,sgRbnAH}x |WL+($7%&*Y͚7tٖU#.sGAsQ=[*˸6CA{/`JX 8ChO5 [K>Qvq<.Lo}\ZNUT,/bj#4f{?B,);k[ 7cZTŨ\,'4g[Tݹ a^*;p|m|_ e17 K?pK֮grsQ? [nw{%S{F_9$ovn~_{ZT 9fBԶ~L̰-A kq0@^{ U. R% |D ctaQfw~ q?6cdzqP=V8[ͣu%FHs{&#&jqz&iR3M Ӥ@qG7=E>eR:hSXy4)`+4) rNcm^msZ)j8Xt39E-y0? ^q8Ss.`[sbPx3kɐ<sb%KE*.%3 ' 1 81[iz_(e%q#6]hͿ ^L8$^DLGI7a#/vvtkhX7I :jzoع8R= #d]D v gkR`] ϖRzx %UN duS x_w82zv ZG5"pxоL-MD82A;lv=sMYp{(ݺ6MKh3$(>=>Ngd'so~7{Hx?=z|6E%pY6g\5{w1'=-w8JɌM(_ U)%z6[ă8*ڏD-Ny4gK`f մlr刖Qg?m ws׶>^0ٶ,xW΅Ud:p!vv37Rctn"0߈mǸ߸zֻMuYXպC}p4cGWTqh>0U0q"OO҇x q|1N7 0'=3iky..aJ|ZdfZa8x[xFwn c %R db$&I2 yӶn þ7aʈg  d!sva]P]'g 8σl4 r!yG|mu3sA}E{svƱQ_]|3POLDv>h>%@O~{5qԠ$GN M,=љE=x^<ˍat?&@1L;yaeKܙ=avXc@2Ixg s~ܴbnfZt+7E^<ބr1L[[h&؟xLԐ*&~̽Äiwg^ڂʹ|j iK܊{"/ǟF\\ ]{|I_ SWsN; /)kzl|t'ߩqv}DJnm1|[i/)#Qql%8}Ps^LBDeIl:COT&1ѣ=Q6vva\ |:㗴Ȁjyi|pOT&EiU=QGQ{!Kj,wʐ%{ YV̑ YR0vO,Ij YJlh!Kw H2q枘 =!AKT&+~s1G !~3f3Qjvӳܔ0GDeyb @L[|DeڙW|2d:6Qf7JTvQ2Q@es{.)͸;σl;uǞLd%Wz2iT . O%.f2 ѭ{2}IT&Ъ@De|W6u^^zܙwσD̈́Pi'^3 'zw&zݼ;M/ɌǼq- c|UcXJt[|Vr @o NXOL[Vl6 Î,n:5~aK~<Ll]\<}^\yL | :*!) ф}uOhDv; 'HSDn=s$B_&эɾ~q$gfحutHM5ljbSq5 Kkvū{5wߍ-cL|}cNK Nz6Ԅu*,Pɳb&dsbTD so~hz\ω=a{ 2,#V7=!{r͹}cl׳q8p*ya՘ ܪp7S}VD|f|d݌.3(Ge헯_Q~?+̇4*߰cfs;ּ%14f ) o0qb X9'? ߽ dH}ގ)U=Apm>!=Acf{s/)vqȞ Ni}D>A ˡ3`v= ЗĉE/SFS DRXr\M1gq5π>ꊫ 0GMH OC5zK'WsmOr1p+~l#6;\~=3< /"J@p.1i~H5*͏nfB!k HK 2~-|.&= }?&"_oR?&b˴J"8Aj"85M"lN}ވ| 8)Oݼa;\ ȍ~ =y۹ %AYEɢFst3x\K(]{ߏV¾}/v;R{^b)Zs^~BqwfQuP+f]1tYᬣ2b;_{yb"cjb8eUb*w@Y~@27Kxy@s}Aa=]ЋE6t̋i6=+TYb=vY. K=~m\ 35E~5mv$JɬsZ~8/TSd̋}~=c{b~Wr43n^Ďsg̻3(#;4G3rywMf 43AuFk޼ygM7/Rm 1t9gSaϐN! iei1":?BJG{Dd—yd~%5Ʉc8LiZN0&~ 3ct;ur.SL|q_þp41;  b8OdH\6F<(=}O `4Ϩi]?e9n49BΓRKc{/7l\H%Չ>ۧ} ~3.C*!= Y]#d /uEѯxϽib>qP|bϹT;p:~:yoJcMros񌢝ϙ)f: %Q}>Wj=wB=[L[BQ3C"DЇT=2n}H۷LT}A3.nd/B0~3y(sY@ȼG-Lu, ŻIP5;^:F<= (Vi~v#$+޸xKÐ <?3JX 3ew r:/E%qFg4}` ?åń,Pt,:xt,Ő|3cZ^r惍aDu,jg~c(L\=O4ѣt,`D;yX%JwfIXq<}jAʃϭ6 w߈RU,x<|с3[m&83aV;s31uV^ sQUxAYm>g=Vo`(>i;&x>qq1LAZDQAFoփtN=W3xcB8K1!†\_ᶥiYѹl$Gezz|#(f-7̛[n._="4IJԞZMOӫfG`FEi_D\toU2i螁壘t=U`U ҿeՔ>Q58Տ(l_jBO,k`\VGzH0=2-{'qC~ޮŔG#;jA81ESQt b)Pl*jogr\f<(O 葐McFB6{{>Z\̈1ʹ-63Hѣ-ю- |=.f:sSQ~&s$dmgΝ'!z: `mhrMMA7.s&gq)1}jƥ$s)c '?Օw߿??_۟_OyW??}?}??Գ_?ѰoZforv'~OĂDꎿ|?ƿ_~No?g5v??~?t?_!a~?㷿h~]OoXWk4~?ˏg{~g=o?/~?} ѿ'tۿ13o)}߿S[3oՄ<}_w??oS??M߾ן>ڽŧOt{ kfƿ_yչWwiawnwwgg_?-ҋovSO~~/# WwJxG~i__>t߿C)[Ӡ6mf3%dQKY$N`Ub5*!Dݵu -d4YUp.iO OwJ[94ϥJ̄Dd4\f:`V p)CΚk}1#U^f>,j !DYY?HK)m'SQY~Zr:j\M {w<X*%A=m-̹^Yf; ԭg_P˚QBF.C?3 cڮ5aY::CCY_eACjTd5\n{MbV Lφa[3bIOB_+d)ffMUٯҌUˆe`evBXDAwZ3s!A3ؾf-g=%jNuǬ-ݭ͛#29giGsSW%уV%u,M2i[5~{ڦr.usfy%.}|bHT]g.|9?mۦqC 6w R'o_`S]gyT%Mw"!A\3[ځqtB:Rx. k%L|OSK }\oi\Ux )b[2d_B\ J^/ik%u(]dtsKY{l [c+P)g՞El6+6_l[dX[k%z;$LA遲aglflxĚ˶l4ԡV*QM%n%j*6e,S+$2_&u7_?ì<թ>D^{ Bˬe߳3ʴtJ~媨0?H6ۿ df &ԇ}2lɇo22F-&ԟ l`Oaci-PHŌK =h^KGm۹Mi>`G}hgY`-@3V9eߕ@AM mu]5_j# dnՓ|Nt~IX"mlc@0vg;m `[l7Iq'ZcFh&̶+J%xӱ6^tRF\ѩ^Ćg_ < X#j]I"Uī]찷k qOC1"`aYHMS!?̶mWb 81 @I!1%Yz[VCI1)}Ŧg;,/,uKϽCA 9f 1̞jDb3R,lyMjN8HUIsTP~v8e)ngE;*(y>>!?˃a[e$d^JzGy@l|IRSd ;ms٬n$SOz {ܺ' e`3fQotR% G"gF?ju$$) jP3U_U$ v^⩿Q widZ"Vap4^ UTϼ?I~:ZN.iF6dAދengk[hc3r8aF1UlѾt*GeJ*f5m۶0=a|  B>z' ArܵWƂ}F|ZPT6cbvBͶ`[m(bBE#$W H=(bکMNQ`?2Lpf[w%9oKUĴ-<漻y1xbK.KY/W} $g4B6ph C[޶QcFe6cu}@68\vQrs4ÿA-^y b;|lho=bcHVyJAq_|{%>L#8(.+l>hޢ _*0MfcAx(lvo̤VQﶾ: -ͼķ`*&g͙Clf fJ5xś:aUf8Hsw0Z3S3(M bKwyjlÖyl*zoϢq\WͿ6hI-ڜJ[X Ceӌ.|ۛ8&ҾKĜ)[Rۀ>g*u6_CScj*`%`#2D(JB|zSٙ9^)Whp-,w'9$Hۧp`]l38Jݱ$;b'g6_$A7UsK*DC< q\yp!p ['/Qhc97czwz)wL0a9fCA/٘O\& k9(D|k2aaH$+a,6xHe^iuY{%y̟6Ji֢ͻ3u:H =k:} T_EE6L9c@Wp]}CP)_bf뒘ewe2`JF'+1mi53D"Z`Ś uY*0d=Rx6 #&mPkp4u܁_=^3)wn?\~- @P!!w[|֋6%gݫSǖm7n3A~^8AM1sܷ/IE@u˝>2amSbsz`j6xAQpR0 Z@4A8-V-FImlʟ,Oz26P$oҬŽjv 9Wk%ϙ64FeT ԗD h`T%)1Blm\8Zm *8Gz;Cۡ-wG^AF|ژ/cGm T.2I gc:%ުli8K<(8+ %h;!mfЀƬWFj SogHk UK"Ԏ~J'"AvBH6Nb۹$%,LH1A}) N6uj%w[9V&*DfI?B41T1RKcJGRMl{:+aI<4vM>"psxĿǏny9>Y! Xu"=!6'IX+B=;l\ bOi68Nd\:P\w"jo;yϴeP pdv|dGa0m lcȾMr/=Qq=.MFMqXYdZ_/>X&(W̤H 6qNm:B̶oCѹ>urUhdR`gO"i,RK{Îw^y NG(1ڼD09| X 2~p&kCG>AiІyd0iJ#R3U~ufd;?^F m4p4ۥs|";i(U69ma" <*XsN`c}2?ξk#b9vA[Ӏ(7&BC#ͮ88ĪIu̳oE$Bfz8[1Wg956#$(EMUsd!h.+"9 z!S79A&m̭ut35'MK#$! !"i'Hy%1+e3/6\;NDeޔ% RC#2(5 x8SYI2 P}Xg= ְK2/ifefVAn 享KeJ$wn ǻJ+G9Y;\Hyu/ME )>6%B%};֗T)̴US;+"'3 hcvbv$C:lO_AF^lrlcڵ`؉mةPEk~ii`b?=#b! Ie8mMcѧHAY9лw+I4<WLↈd]pD*ǔc'VY,xB {[ɘ z4 PBTjVqa& &'.o6GV9[jOfTnJ8.AV\d6ZqgR!;๰c}GE-c:m˙*076ƠShcA[QP$8U1QmNW_WACTӦ Q6f2B+xTd5jcSt*rQɉe#]8UmT#JH AfyR}e3ލ%'W!M@˭)IRЧB`2 SJp^WZeYPZӯ SP-s}e&s:Lʀ 4T)VG 3k?d"P%C7UYhm7Nfi[yh,j/ٜİkkџmlQu;l`8xҌ Y`lCmlKߘ8?YF̕OTHis؉ﲦ̒>ֶb9-汴dޢ C v$m**JsDQbY` 5eAS<x!˨Q6fxr9ihc.jOX`fHAţhq;v"XCA^aH:| P$AlnׯB*L0 ۮDJzSH+dmsDB+3I~Ť:aO2 'beג {EXhPg5a1挺(eҕ{Աγ>'}lEoiȼ]Z5)m}m YPЅQo6ؠMe(KW5a%:DDK+M_-ei >a!nHiR TQ%(N5@ #Yc5;PD6{anHQ<#٬ e/n߭5vo#mV̡}~07Lb1c=shBeV5i?w8f[}!eVNe{xp.+)\2N&]SVVAVQMX>vwjR 2ˉͿLv؄h5-VT}Ӣ& 9!C3y9m"VM {fǼ~B!] `-1Gb;߷(v`ydꖏyb78-m0C+L~VYteVWK=MˊF;v җڔ~ .=}SMİ$lzJ!%zӋ%u^h=9aເ's';L\ H lS_ɭ~2dB%I7UU 0P'/Mrn퇬Mw/XLBARʏ;.CN2.]֞tEY>]lú"ҏXRS`C*~i%Oy@"m]*,[HGy{O8=ӳo6*ic륏 S1}*%Klj>OI˫(r'A}U6)2.e>x>AY-i)tS")Uř$66kTp/NBbjTX6ϟfn~#2+kIQU@&|g˖V~D+Ri5s&~7rj+qf~8ȩzQw53WF N- f *⻙Vb~EuTjuh뭊*UJҸIU;FFI}z@)N|Sg e!)rcffoxwh_ gNpch.YWSTMF+6&=87)VQC ="G^]/d'UyIʒjk1*琑PfG/Ʃ|歖+j*Fvo Mםx oϩnʲSCTqj͛iފsTT+hpx+Ҫ-uL2t>QmVcSm~QB+;sc=U*W"Be2 oC}46e)]8ÅؒѺm摍r[y{mȘQVSQ=4;N/v^`݊W1+ީC~kÍ]4+IؾxvǑUSS݈dfgcfٲsR,Jg]* <:{/ZzZkTnuۖ(,Y"7t>nVL 3".&8S)f`FUŋCI z_胶ˤ%7EZa,[5@EHf*V_cAL pC]2㫜]hAC2YSlfyg&8Mi3?^EIP'3HVŘ"=0;P>Y'u;C0A,BA0'V &tsڶܥxO8kMftNvL[w;s~zɦʩIHtNk"J]{+}C8±nuh<Wz9ɼh;4H9E ^MB,tAVsBQpPޥ7b0;9hد+%S00r+%Lbl3-^_eYBi ՋJHC`$ɉVR>Y6QAw\|C7l|}r)flgNyLP Gnypȶg\'m).^'VZ\<< MݪQD}mέѳG]aГ2+ݧ#wVGm nOǕ<>VGC\lLPV!ܨg8Dh2.g!Ў`2#$P^!U|2b"룷o-Q~Ȇ9oWobꭺhZRǒ# (U9S[0kd&- 'W?aUv}Apod)&_PVͤ]2KL؛e9|B8-&" t!]4K^MˁOZ%ꉃV HFG9-\;#ξTIWꀨDb4ZT(i!oZDsaaqIff}UKr+P Q]x=cqu vKL<]B epj-Kɱ2ݮ͓ѕY_ .Wbm7%Ds$gtL^I9nf9ޝJ/}iz P kr0?v4BBsf^GeTozdcjN 8|3= 5͟; #`E`I} ![!KCm W$3eV Zy|F={cg+Di{j7;;Ffsf<0;GaAPRӂ VM|dCcao-罔@I/TW[3JT҂%Yc9"ܙGBa`L8=`! ZްWh& 7?J *Pl[$W-6G h!nUNoR]t Ubnj}qnep'y CR5O97l!^=f048p2%׶٦)\\<۬.:alfEZ5Kc܌peßIyӣzytAsEOeU/яtY8ʣJ[VPpAUƊ:2[̇; Zou42&/P+]C 2L8^D>ob1"=VTpT5TH+ԽY>V)#whiAOjkF`T).L$gD'Q!Tтt:RSx]^y0'XC(of_EW],X/KnÔ9RfҪ&D&\JMEcGp0qH|J.n1Ɨ"=Tj]U]YK5/)4_b~ NՂBɲAC2Űb͡%hqx 0 Zt- +eN sE;˂E(tЁGJ}QMz:zђrE/yFԲ tkP18?i T_crtSZ?-"#ͅȚДnJl]T 3VA kÆSl¨3U۸ &i0#ȱH'U'>C*pl?ڮ*1S(l <X{(m˲* ,Qo~bpUF8jPEhEtՒMe#L)Ɗ(1Ďp;6, O+_)4 $P=XQFSMoYӾ]ء3oHAQzq>K"U*9hSM*IW%*&7/arkhŕ*v[͛96#<@L2;ä#CVbN89HANR֩D7)AIuuKX(PfP-!w V W+^Zrk_WRA f^,^x?(cpZC,IrvЄmPR䩝sx~6V2GA n8)5JQӣo&70\\g؅٣p*My!+dt yѰ2V.yL\/<#fcI؞۽$\i0ļKM!|EP9LཟpRm>0Gg|CTH4> bi}?0EK)D¬#1Ӷ9&՟—r4qx i1?Hk A{4qڟB6mRpG%R*ޣ, Z)~7ϭGGSPq(G@BJJW%Z B?Ur!BE ؂oR&ϴyw19?[)QUAکg)ހeVi\^u3njH?y !9m"*$;lCCJƊYn*3{TqMPXUuݒ(.l,y4Ai2zOc c P:|ë1Bug,ҏ#xJ(@61۰u5{ΟxF?%CY[šq3yF!ov:R d,'"h4 mb&B7nS ls [8d夦iQY.Mbog/)vB5 tVC0;' z t+Vq=<2┢"^8 Տ/~x–pA4^XRNTM 1e2 ̷י^XT;MT$\PNPޒ&E5CWB7hI0J TɊ 6L` y^l7gJ#* d bC *2ҠRF M''x p&B@x‰=ʑ mazQ6Wb Wvak> PZrJĠﺟy5زD/ R4QKe` c6/[vaXWK$M%5qj$@`MM^(ilkaX[ﱋKb~0q#v0b hղmo_欛0ҖeA3s`[| GhH33frD% E<kgli<RIH3͢T2w O FWFAJ _tWCPs"vb}zW _[ao=HAWBO os?\ (7"WLvіm袂+ ݗ"⥲/UMf?P(A?lAzxc>﮾n[jTq/A=)qzd QN0}_P>MO5c䥒% zcy=# ,hBVt?psNf5_yq^ n,k?|TCaKRnq`MB8B~u퍷PhP8/,^$+ck}ϮXBspD?þlMo5Y/ڙ?ؓx\(/pI>mYj'1}b#"5éOt6h]ǝxv W"ZHz2;7(|%jT+?4vI olߟ35QRpMrSl<1SNm--2!~6aoIW#8z4Kl` 4O@*v!֊PN+ 7P8e?򤞂0s@B˯vy>Kd?!Sߊ*Cܟ p4G4?Al/*Th]A&^w+E4Sݔs꼳e<v>(1ljonRdV h?#s\kov=ϛoQm:kIa *?5av@{a;浻pk}ɒcRSQƦrx>"c&y-oRό_|UGH $BB12kp5#V[zZ$,G9{0y_;bZIχ4Bp3~eEYsZJs_*|47RvzWJ4ȳz>v"Dj;F@UИèDQއN4NϹ2듲/I< ;mv l +E}X-d7ҪVz{iC"dA z4' YTjz`5"xJVj^'`VիmUpsUy)0ASբ R iZ>}0-J5;mIK0XSGXtjT8/!m]Sv(Iߡfr!r]Xm~h^u %܊H ) dxhpHH!KMD6HD0d եR镝*ATVrE)@L[ۊ::%[NE\"#S%d—YcC%OQTjQ4~RyRJFE6g.GfW|tF"zBڃ=4[IN+ArQDZ$DL.K%;4bTM$ϱNtTao^ kb4QGI#ژTt?<\Kiip`Av_ry_i^I|'(*@^,Pdvl),K'Hfx|m {  ( -RK$/N o WQpd%&iG,rW: ܭ?1OU#T*B0T}g>$[t&Px_gͥ}&nǫ&O]DSg+;(0I}wy/@F!|S $8aJHm0^ i;]*1QQ%͟xɺW1 [%0y^VϺ;/HTIJp`z\@`ϱkKNZb :|  0%HVG i.׉-q~u28O=Q џ"A.UR\B˿\+Y [mZӼZP-VT+盟iۿY@{4Ui f0ZӂLHvR(EA7qjU{3eQCaȽ  Nst$Ϛn"Q"VNk*% -:̍TiU}U3),:ov,F 3" 7nL`nR$xVϏO,v^% ߺaaSFBMP/ ?)MI}W7֦^lA)بժyY?a_FzڢItKRn۰o-n(h϶4yּX aEU K K4Y|T;lYRV5$]jɽTjE2p^Dx@*|yH\.8M`TEn]" !0T©)lхt^"rk 7a&S". \/Y4R)f2z&0+&kK=-p&Y;,|ENޅRw"K0@:= <}@%k;)$* M a6&F9u$6eL/Lr(eT_,H]=0NAò;?}O0!?zJ *e#`0Fҗh4M;} FDS2b*9SS8Y>a3> ELrL1)B鴝m%s\B,ȑ~z^Mc(XHF](y#e"^ p^(?TBKrZr{㚁/bb⻅((\XvVa6)bzra(i,0qo_aK"VON@~*|௼Ǚ7hqZ_ו}*D];l3*oXIVQН fVITV4?# -dp ;12IxTAM5k[ P?sctFMs@˧18:^:^IJ@8 O*[W$ HF߬\>U%E<@hHAΞׇ[$ bQ%ܘ%G$2ѴDT̒PD)TZNR'N,=uBM3瘙0 P:.o{#wvB1DDv?h0yK"K:ޯ7`o2MouJQf mv?AV`.g$z)WXfAoٹH)t9燇bI)[Y񅏽by9 U+54}&}ҨξTFt[hl$3(sXjQ|S[HxH [j)^tLlGЂ+M|mTk Rpٯߵ_lwmr?Г7\:^vE[Rh,uaO¨lLu&UglH$asK]ŕhv0oJ#S0W9žcR>rUu_YIմ |72R1#rW MtVV\0M"5h؄df88FeL./aYhŪ#c f'k(U*6x¢lޥˉ#ǿO3Z/=;?BdLsXo7cK_4M_"V0KD[ﯿf{X!ſ^D}^pKf5ɳG K?M>ϕ?y'`rb)xs&MuҾ5`dt7(l>sLJ<3ut䇃c`@wϽ>;؋ix`0>wjE/L[shJyҶ+* BÁ j(":}pEDgJ;h]gJ?|,|_}p^3 ^^ ۤ\\o_|*Z(;~/oHeL:*7u$bBU(V]_|UE*5xWz_ym~AANI!w{9)O!(;x?1Pg{ 0+"si'۳>9/}$7oı$Dycs.@Jl GVu$ ԦIV]:?44Pѓчp;DHϩ#-H1dͨ2);ґbXni<ӢgI ^aU(hXl0b99>ziU}#'k1NE"|Q7!4|>|;SĚoޮl_΅ -*Ca:X/>_d}CEF 5QG_/{ωrQ}Η9Y/.ȆH710rQY_ 4'!'xRr×B,Y9~kc͵_d`ƅWLcRE:jϗ0TMh3fZ~+W]}~lqXLZ77nUQ5;'k-m'tR$5ַ׊3ȩ}]xuDT5 O>-=?-rfUg2eYSs'u 8M?ȟ^Os}<}ϪIb[BW%tr%^o(?h3G^9["5&vͻr*:@R8Lo'6>Ӏ.CKN?+Pfşy@~W(iْ3`n߆ĮV*O,/HRqHSmzY 9FvOn|CxH>OcU3xw1|Lp"toÄ*Ӷ lp- Ueā`+^7(xba2Ҋ;8 %zA}04NwI#Gl.}| 7Nֆq~FHc=>ϭ}$o Hg[پQ~&xo{(I|ݶojqm5=ȗoǵ)n Gsտ_}ްVŷ׌=x$X*L>}G#J+4dN@:yo}9O}oi˚t< !W(b3&^&n[ܒᩏq7 {Oh#|?1}FNȌ՟|ythSKթx[1u9w:՞Vpd^Ҟ"OK4_MOŷݭ|EGŇK<(Mg.+P8z[->i-Gsi?/zj%bW̋T$?jpGHq/R R\iw}{M''F\x#i%%c3]=fTw +J:9?UNWgW.s?5X0V^n.D Jf٫ӥ4|)c.,0Gɛv;1cUe <Q 'U BK*e̳E`Cv)c`$hAܲDC>T2t9uZX\MI'_l NY50Oom* ҀôJ~/DĎ6 D AAí7b'7 aA?}VįSIz7}1Y`$,V;>+:{FDrNُRAaq( $Ҍdԧ/wM&d} ,v16F+ˬT%q0ˣqsFMo-x qJ~>jJaa[IMa[ozX$$cٟ>.hJfX&7SGq]m>h_ S,+ HFJ7(Ks2W)  `B},a/+`0ܗ sv'7Td&ɠTLufmh[*w~2]eh)@X6W.˹= ߘR쾪#6#,?Ae?nUQK=Eږ] S;?ub|VF8*-|?\"L46Ou덱 N-/{t?x!fW2ɘV@xxf?n.yGRu3U=TpF;(G߾<{??XY$I^y"[yQ?BEyNƧ%OZK9ۯw:EhJ6iLP?îiX6?]E&Bu띮Y"3%|j%œ>+pp<*9 C| c(e莤ɋ(~\`WdhuDMk#e+^T0o-y䲆3OmkC$em)O( n<&jBx;g/; 8yS fq2yV Kw`g?.AQ *i Edt14?ygw3yJv7{j3p K`116ƣrqRXH@"='b@@91b2 Aܷ(H)@B<Ьy͍ܩ$؜09bb+GK7';M.mXh7)=O]ow6ALOjrüKR7 b~H$ЌGZ% /r,UYE {ĕA^GWQmk.p_FSP ByPk ]>ޤ:`Ad%O:UcWho_ YUr ܹ_G^ "āUJUJkr 0Kdڒ MX<䄈-n@9Ap 57D>8yE'Кrv/|5@4Fewyu톀&WÁPt&ǀ>w^q `w;'i#-I{ߒrkrM\T+ ЅCᄼ== WzL<,qؾ)=i(nBg5Pꑨ-iHB@4qtLfsz~ I^c~v`V=0b!FeaoA(N2tj _s}g+@lф)El860qK[ILyAf Y> Qz%8GwhԴ89n7NHVƻ Ċ~ |2]PH +~H^}$-X|mXL_}+X⡻ؑdTqb(%@@3Y@.^TCi{rnH6VLqy n=Ɖ:) >#s)L._7k\ GH+ k^cefHezqݶ:vUA$KDР wF_]54722"l=C"AtCb@Aod!OՈ'Y$>l$bMY l #m+o2H3-X"am2k͸pN!@?yH3.$!t@xnp>ؚ=ϘpJ6Gh0Xé{7Lɦ}ݯk>) ]gIL])XƃwoLR&U=gIFC6uvFķNsާ|EƂC,*3 8OnfW\SwwHp(Bڦ-ԫJ 0TQݱIh2ƎS i2Vz*/C_&ge@S]߆h>Յá?$iIBs e^`ƇԴՋ1ԝXHvhD7{WMn3{FG."BCDD?WlC:q^TaAmhoA#KƜk wk%+#:Me$gsn؆A^pzjP+o!G.^9K:e mS$9`9x/#ؚ/,nK+k.grЕ;˺kW%,>GB3u!-S ɃkQ%+,l$G,3ݒ cw[k.@;6=EY~(Lz(N >E/T{[ZAdnMic#K9U#^$_U_=o4?ŞTū%b,9MÅp> ɗ+ dI|n"su9/Rdى q-k*&"%wK(-9]y^ mx@C:nW;&Cpe8 pkfjs/IΖW"p5IV4HN ." 6**_9H}#q"[Pi_sݐCÀzAf8Uls|)s"Bw5lO\Agi|L6-RőG$b :w'fM#1^^nM=7bFDRK'aHtEM= HV []1}く%(  )̒}~ ( gWI$qv2#g5a,~7SMs݊02}ɡn%{ Y>/j2-MݪsJcw u%sW#E_t?W&IuKÛ0 x$Fl3~:|@ UNg>֭lq(^t ߳1 ӕ>IVX>kz,#pl^_H=#ȮRB|sԿk7ߢ^Eԏ (ɓ S`l7 ")p$|Q aG u!r?+wK%n`QC2qdə%2d?mJK4p)\uVQ]i#B!wSJ\\Dd93"A^g9Z̴T/}{AECKA|dя ]8?\ ;`Ï jdo~Jƒ4a&IQ%ê/꿁GtbCgUN|Tp c҂Kq۷vcy:x]$3rnXdMSO!%AE/R˸a%z$t &+cvR'RP'iOo)T;YIpϯaQ H[z?DBI˽ Ke-8ȁMv-%v)x(wF\džtQAL7 5%6=:՟ekDdz$e+nNd gC~VLc/b6g@^EPV2easbZ \(LUmm8mq3׽%%HAc58 zG=ud/-)MNqu $ibU /uhiiBr\NMcE8 p[{O~K eWKoΛF &,6'+M}3Nkް.ŀt}c%?kް$3%( 3 atve)8B>Hu[(a3AI{BL\)b5qAL=%o$dv%L_7dlfJR&'cQJK2k{̘H6yl1 ]: :N8v63)(y"a=@Jovh.ɞywfiSu\5lf%B)6WۦA@uV5gߠB`!a~fDmo`UT1Qp_6'+EϜf_k\;7`r T-n$G&e۳p Ő0J~HlD\wnF;"쮬qg悻2tnT1xT -o.]E՛=Fb2#m@,ޱ~~)NE ,"E_)l i2[IF@%xGla]H=˟݁OɂP_2+&)4(BI6T rR-kت Trq|}lqcb"OݹCbxVRQhޟyE`)K[z.|1 3n<~'A^$̠cGH8[w:1,"*3b-my o:ѿ]LQ=,&Cldrd2&YZ+~̑Pd/3ypAl/>UƐSr))(2jtE"v <Լ2G{7ϋUi-r:޺5X,p$50RdHۉ^}+MQJּ -V(M=mqG"7'Q&VS_Te$ZFXX\K)a s7B}1c!hr0 O^eNuN֑y25-4=y "Gt ?yN:OW-ɻX<%)PȓRo? '6b(2'X%lӲ u}{bЭf#3v|{&ig \'V]}*1V\ C%)Ǐl{wߡ$)㪙ynV_WYBCiP۞^f(9e(aڳ aܭ\L6E=і?Oqg TML UrTT ˮ2yQ)mYiI>z1ݺ=Ў(f6FW= bR3ݿr=/~Gr~|</;bBo瑲8f-Nt\y|ގ Gbj@V$wlwԾ|^SRUN|plT;|ެծ$ںLrلX d_{akVQ|nz~p7;%(qc*}l1}?<Э*{E(5vMk_'' Tgo'ϋkw  >ÛlDz;b.W,DsuLe3[~dp㋞Qy_9H,tbbwtT쵠̧E^f:ZgcϳMⰈX-HrS}?{8Yl.E[*<&Z@U %` rp Pp(}xvbqtwB:SJr{țNL.ɑ_jrcRʎzOjzI3R}$CefN}.#g{XNXVyo+mAi&Ȯ,C 9Ŗǀv4K$=[繼 :Yuyh1L䉥`{Ţr+%_yb2RAVX@~>'Fh}c+:gΟ<1ok%ͅ>(l1CfSWrsz~<6WO}P"py[%fSp&PH{dLXWz]Ϗ]/pٮb"iT$bGA?N9{!?PߏJ#٨HF=[Re]oX<]yPi20(s42Z=U+1`>qiy]J pNC:Nśѳ`lfH6nK1=UŢyQ oϾX(9<{-lԫ]a]"b}={- mC!+x1f?R&u3ey9ؗlWn;=ܶvg_ gSm繇| x wgUh߿\}r=kQ JD!(VDű8$|*g]3Z}J 9^i*}}>KҔL e h٩PHnQ2.?#|?5y.R@l?n{'ܾlLj"%/%ycWNJRnn.egW!97}fչ(tg_zaN]Lo?PYi>Ĩ_  BQqc #?}'[.\T>A0vP~)bAI;Bhy(cf+ó/]0Fn{KzXm7۞o_n9 ?%FNNsٗ"5 O*Nn _\"G<ͷ})"/vY B&o?U U#W?g`{aN7VnKqzUn)=Ͼ4n2oud&VQ>Rr7ޗx#ӵSr<҉x::h;\3# +9>ϾƟ;d(<΃=>R ]3\h LA{oQcپz/5sPsũ6}]=u#ٗT -f1JqBǖٗk0KGMWWݰ%u"f?R<-epI"דg_/!9?Ͼ4N4'DUF(qdy.gTx_u%2Qpx颔z)σ3 [ .:G½I3% [0/&yJWY9?oe=ؚϹ-ŦP&g9r}Ѫ.?qgbڣyճoănr9Κow=l r?{lKBݳ=3we}WH,iDc[~4]@0sOmk uaw(6QZWhd%ct_Qݫ;%!-9{WMn?4Psܱ?%k$jzP|z3tyi!_|)\? |UEA㉏S@Ran/3]}|*17nxkGRNB}7OqYƸWKaF+w}D7ђ}Us4u`p/_*r8/׋H8^^g/M6+r^ QRg06x}L{N;┺Rr<?ўfˀO5Ry>Y8`Vς7?_B4ɜO@ QW;GS%狯JN" = x 4{{#(VOABR.7_|Uu{3/mK{}*6ۨ@՞p_%]]Q>uW\ / Og_|U|`|x=~.Dv_j0Z#Y"u9P"pYh3_|l=?Yhƒ߭1VdԌ/*v;k/"xONĪ/T|αp*/o"!l_~繁N5zYآ boNs AW EMߟXohU;.ET~NnՉ)g_|5H\N}C_ce݉M'iO| 뉟@!t̔mO|5,??(wuU':c _1vfxH}尧g4̎mO|N;Z\yU_k蘦2řƟ0>PBa(²t_dfgL Ɨ /Oqiʃ3.e_>'nᝈKG&o/¥[YI~_!"ЌyWBy8Rs>j/?D$'NY+y^px0/iE+5N|60X_|50WyXiY5^ ŅbeHÞ}."Ex3CٿBLv/q6U"_=xg |/g H O ̝l:?"(*n W[?捌~nk]D}\i"tZs:_bkx#>'/g.ŋ(>hTlfW/HI^S,YC{W?c[PÈ=m;hE͌163ۗlKt? nWSiA:8Lk{9=7&e̊htxFTFqY}_ww#QHWy@Vh?b I&F^B4{{#%aOH8fz&zjO\ Xm$yx|"x_/; ab|{nwgv,c{/mD5bn:ox`#]`z]{(p&#^M0ҿg9ynG\pZΟgM!~1Ӟ-ImTO||CAC +ϑ`Q\w@#H|?]ry-;ΰ@on|NÀqU\s|+3cq|:Z/iDzǩԜCb+ؚ>]gv;;ֻ^&m::ydUWў(B:W'p'5>3~6F:}dfooxrɄ ZAVR&5塊&tˈV_#ŮvGr5>7D|^ƒHG|}kH3.e'a hI z?."y(%Nӑ ym?"{Bţ"|j~ X?al&nJYvUb^+ Tqt3NV{H~n{S1Mk&>X^z1I.͑E]/ȫc kc@'2Pw?cg?|K~+WМQϟ͎'9VtXGGKl="3Pf<=>̣8$4?]cDYb_s@,e`I;&ůz3{)EB9%tl~rHޗGbQgւsK?K͕Gf}%?T!'h g8$kB3L+ lnrjEY?Hǡ7{{Sz|Rx|菺Hؒ'U;͑_g&P/I6tTC/Nof`7􆉪Qb,YL={W<*O!yq[IP Y@8tc9PQF.Egׇhr)jp5<ϿeGuf癿i v*&.Q8nA, yn=\ǎ·y۱6:¤όg|!Jt%-qfh{}vNk\]ha#NL7c+P|WK6y͟-lШ^E>gHsM@Ę=n*)sF m}F UtcmʺOUj,GRH+9T7:5NVQS+^ILSշnTa5SSep;iׇ}k^I!Y݈6DftE"Bvab+F>#=%LdB@ڦ#%ړmIu2CqƸW\29cuƱ2mB)kqEO}Tp] uaf.z:;Ϋ3>d’_1J)yZV iojowヨu|'JQ|y[AZُPTXv}Q\EzyE|U \1kL+C|2 > alYNŪOT~SekyeS]"JgXw^1s߽"{Hk}||S ^"jGjȫǷ32%׫Wb'3ǫ+QB寏uȣqS]AКsK#6rηND9>74k会4uIψS!"k>'X#Ǜog&k#1邅jD_oS%;~,W?Bc@H1#s` 澣^ϑ<>b]4qo9w/26dyP4+9HNPE~1t i=,FN\;,e_qROof;~ W)PD+"smJ z Jjz-srt1 3X.ʞ7ـ*8dZs=|qg :}A Uy>|;ر,:GA.+]EmmKIj_e9##JݖmWƪ2)˅Z7} 28b!]nyE;Ǘ[VA 5|Y]+oDrA HJӤL,~NZ <1t)c #|VWC\qt+ᐘl&XGiҾW< i+[w El>>4ڎ%|ؓ">#GDGxPFTb#×y/trt _ Y'?-|B3c0(ifϯx*"UZ*n Z+))Xڻcg?|hYҩnh:^K mk#LJ.5G?G珘]U `mĂAlaG0y@na9;~MJ\X!ħ :,赁lΤx@+򝰯`JNRp>͑3qPCOڧX)B WƖԜ &y-yxh†y!s{KpOÅYD 3ݭޞ=HkI+b?>ϓfYq$-\ i #A6%gڮ sX甖VZΟ'/K7Otfw]R(FONC,"a{w%x" :S8aZ>@ ww<_AosN8~Z`kb9qhoiO1>qsހ\OL?9p ^d֍Rj="r-r|<:8q(Lʓr6¡DԨT,ؿK A #~5:~yr|<rP{?2,1> 8?vGTM\x>.&ײa*ZXPAt$r ?_ez.5Q{ UiLWM.tZbo ټh,xso D1<{_m߫HO<9fO2T>o7GOJ=ĂaAY _腑O DSM]~_WK]Sz^R|DZ,`~YsXy7Μݥ&]eVkJ~]*_s"B]?Okw/x ̦h}P{S5͟U lvP9/lT.񌟺ժ&2AA,r.Ȋbmvvd%6PNhXׁO^+IQ?Ѻy׵{.9/qN+4^g-| 2}ue1 ϳs/L /jMQs{t'?_dHTCéh?x!bķ5O_۲唀DPv|/EoZu^) ڗSS^ϳ5.u؝_kK`eyD8ۿ/??V`6+&\$Hz[sgnޟ|Bw!9y?y&s%Zظ&7n18'?Os10=Z]i_ 6W6FhO! Kς@vRm%.P/Yovb0|()ѿqѽ W?vOUxhJ ?_X5Nu}+fN]ɖzUrYO#ldvV <^ޏH_|ˏp4 F]0ۿ(Nm'6#[ a&>O_Q.iݷp7z":Լw) ϑU^;z/#WXe>T{ko0|l :1m6bs[#bNZ+w|^|E{?O^ϩ3y_|{NO6.vv ?O-؁w/K- a?/2~M^Xown%򋯚2jnl򋯀N;yۂT_|6[L 4>TJ{[︧!Yy_|-GuayU/_.u 7Dw)ߴ:q3?ϱo/JO 3杧!F_AYW:%~{Xi|6%_gL2ey18 8ݐ|jOy1f2nlme~!&=歖LFGDZRZOl~FbּzWKWWzuV>E8iNr+J<)q[9oL:(&= nF[GO݅X9jUMURl U7YeJNLyU8lPxcOzi/4$ }b7c҇`^;e<Pڧr/*>^]?X3q!/}UG fO" \o0^ 2/雵ykaXN8Jғ,/+U9 H} ޠ-* !mt"wۿ.j6ρ&_v=m4H5|޿SZ뛱[ /ۋ"nL+U7`16p/*K&>_elWw.Pհ|h=Ц6C'j6mvRg·'p"Ϙdgxh%gܧlCl*3uy{ߘƠ ~Y_| g@Ćyo*Vu UdbrAtmxj(dld탦c-Gq%ۿx2?|b OƑaUތpW (u+s_|;1oؚ?ez}jbf9m]4RdۇŽ*'d *JCy27Hk?5W:ѯ,>` ^նK23|l-VDet=j_m N<#9/TW-xYrWw9_|[ð8D}˿|W8x3%%`[0r_\ml將}{W sP WQ@yNti{M_blӾNjt WIY~'h~O_|k8HVSˇ\_|Erq1[ս/rDNnL"6bv}+`iF+uUKx|Wo|B Ӿe~/U5qO9-G]ӗ yЏa,S35*LQۆ' cO~F8 ˲?yTMLF*>_R\>Qfi\fA}%kB+?z%V'O󵦼"6VUJu_1`yc\ܞg,&mBigZV5~Ϻд|'44@@LӨgٲOIi- ξht*8=yi[ϓGv韶 Dmв^G~vݜRK[Xj5דG[Hxvt;+x֗ez:!{E{|urv-ªT Tdw}"i_Jۯ'G >ZD9>^I{G9 dO>3"9޶`k=2/$T/&TO Y>,FA,÷#nRqeSYC8]ߞ<*+M:qi/3Y! Z>ϳKi?"} Ywqi`Xο<*ٛNU_?yTd*fڣQDj@9ߞ<1KnKb~|YGڹAA봽UB_ riS놁2:ܕ>M*r~_841CI%Q#:4z;d/34?y l˯c#ZrBnRVhWUOnj*cT5'ڰI;"21ħ'cm7¥&MUJO|u ` SG.+ڃBW9? jtƘ;?~3T[%^~"!Ejㄒ/_a3B~[Fe:C7_  %-:I)/?T(lO"ՓSK 1׫y5|".^(wןR 1aJnwɼ䄻 ,泽Fof\{5Пd 0 |XJ*7m"6kAgŒי?K;o}iN ;8*dySABcRőISjm˱ԽT Pd|*LN9FOfVk)+&;;7g7Z~T 0PēG9S=C& ܿh[briݫJNymX'G.]-D`o+&+U_p-k%B=t"{Z$0aj, 1+i)S0} G^N`q@lƹ \-"s/HߒOQcIDHVch&۟F}ּ9v`"Et9WS3q")Yw+20Y"4dUt$Uۨmw_ 4  3Hnx2ʵZzjCʶ% g ;k6٬-ܙHm0FGX?b&SMuލʎue7Yb

}NzqBs7nOu(4>#br%H9**;4st3`1 L>IU1?YHk@a䉏DR;ɻޔ@$-9Qi,ꋤtLO)IDmVcѽi3h6>b6WLr^ l̓Ə6b,eo z̗BGc7Wٟ= vfST?lQܟ}e|ሌ*鏻N&jV«-(y8"S:WƢO۾V)BŁ&]敆h8=& 8@%P`>?eITn(>t!ЋKhpd/(,8{ˬ%rWe~?m^q\M6$#~V[ZBtd]yE{W@pωg2-4+^6Tcԝ%u\VKdgT}\xfVH5hSE7ٟ}9]b!"bŚCr1f~\~ j0 NH˨oIdyIC@=J~]s@{&̵5v O gY6U6,`yhwE$i#zݜ&ʭ ̱MqR_XZw>q?r[@e }Ιտ2Rf #x="*}?_Q_c03oLcѫ:N+xFWl{ Eɞ_ڐ̅+ȭ8=`9Yo.6VW2eKZ2nbp,c;&IOud{[PWr}c]*fi(!]LQ[Z5ɾ?|`;ϨX$d$:ޥ8W[g7 BgI"glȈlҖ"έ_WuuuVVfdĦH|dkB&7a{9iz9o$Nza6Koԩ:*(Ҧ"<~1Y-2t"T1sֻA 5~ϚL(y_Q.|3R, +JQyÜo@w/nۧY]~{AD߻R/&M+k]~ zX\p ;Dξ$"^_j+g psUD1I,Vu]zt_ke07(xsR `:GGasl{|[v#g`:@x!8G4~!]- U4F.*{B`IQ>F%}w G#8p^fj3S$Z@=d@x Nk!RM>)(}-@Ut|nmx '5LTņ3>Tu(#Tjy}ԍԉ4a!A~cLDod""u|OY =?^H 1>W>DPMP5`~**p5u?0 $x9qϋA,хc^*~|^XC7'䳏ͯ&U6|3@w0x=t$;&-H 6}_ItQEr躾Q (niMMHY/~izbfbDϲWItaE靖رOE;%*9QVojNODX?ۙz:Maʦ鉧%zlej* s]x3s%rfw"2+[ܝsc9x⹦VTX%..,cEi9I] >7X֧VU =;ԭnvTBi^6Wo#&1z)R'=WWfKAۯ?oW YނwaO@k&ȄP3A2> 塭lCخLJ롵Ձ^]︢޼* _tEJd0/¿~䴉}qÿU؇޻uAn3UHzӿzRJIy%?#4ڐP̒DV񆤋]iO aeY ]ThܳR2_% 1+tqeD28.N2@k^_<*ÚHK[@{|_ϰ&]3< jGhuM g_]@y:oqVa"jcW0B#RTrTBG"֋Ņ7آB_{X$;](fbL٠S;t3\ÄֈS\(O.E@kͺI-9flU+cEN }(t djBGl2` T.c;i`_{kc8U |BO>yDy6@4(TAGsCx^|NM Ǩ|\nݯCV$g(yk{}WI ,[wyD`]3_{wyDXj8j ֓{0cB **.e<˄HDqT:f Po}SsӘ+#nS/6*9ס4d4>><l'3 '\!G}ē?wߪ=I@p-W34(=t}0x >Qnaq:@_;ziT(B~ &&&p( ~ '5k/J`]śI{1^zV0~x`fUum"+QXG<3mdp*Rz} SX2 Q2څ4FS.4Sg@Eo{?@^lE"-ѧzyH~]&~)>1LY&ޮd?7tOz^aLgI~JGP+ձAcXZW@GL˨`]Κos: ` V}dQU`.8*w?Mg R^Zp$`B/KJw7n ;iʁt}o`.jwN;fۼZlwJķJȂ1US蚉-䶦өS;au3XQ,5+'!k Mwƍ)a yp0el_||TH&ȃT&7'fguP>|~T* 3)տI6k]!J4B!IdXVSuح]?`%pS7p 8F#f]蹮.C-S VPяgSћ;}ǂȸY$!+U||]tFVuĨ!tB_:eV?ɪ&T{ VJg=z<ߴ׿_?Ͽ?_>?/?_/?-??_̮_~LYs 7gn~_~Co_? ?w(߻w7OϿ ן31TPMh%<iPD@?>=uw4"4 ҅JVB@GU0UF`77RU5-3  ~{(-[[#ƾsEG-[\I{@nvy z],2qzjc~cW6yVQ!|p-șO4&aVMͳVճyy1s^li6f]Xۢs~2?ES~ݠF09~r /Iss^g [gٴj]LU;;#x&WS" R"Ȱˑ9ADagH"VÖ{0A WК-Au,L؛ u[[~ j Zm ץ ڑAV1DA&Z*K$ Z%TuF;N5"BZ%NtchEbCP}Vah zlެ'Wy_%,cnK2%IU[>lnK^+42%}ݖ]6mI')$*p;}$*݄:{IJ6򕼯ے@ D1X'v[RL?pnK YmIAV{#Ȟ}P8vF+>ymIzk1:Z Ve|7H/g0G \i3gk2#՝\Vq0HE*"rQ<6 ޡbX`f8}_.e8v z WDGXlDG ΂۶FG{6/fGuP1;qN8 Aiƞn6FG^2:֕l 9(3-+c1\1V]bc:ջLk\`cG9lڡ$kkZ~(^vߜ{Qk)PH>9Wk)|i)P 1] f&,|i@dD50 ;-^vzi)eb~5w/D'n :ۂ϶rgCZ?Mx3^`&¤0=TXIe!0svh@G7E}f'h']D'|Նޛ{ke2)"[+Pq^Pt3pzLa Z?P[,a(TbmNBm.O›\AP-Y8ك4`EIIЅ7V㟌 ޹OFC`& xq075 hՐ*Q#6Q^ek/Y*,(2aD~Q ddxY=`~ cET֣#rv %},_Jpd٪ޖz)P- RoSgCΎyx7y?J"Kk4? PS+54Cdi4]yc6a^5 {Ch 7kvLi/8Gx/8؛%l=ϻs~/??dmzWP={l)_^Ţ8[w ҵ 7`YZu{ho4%;Q 'XwQpGUNҏ(_zÏ"HN dب}ٻwc! LN .M5{>PUi]FҝT0w7-*fQBߡo =A]M3Uz(MQ!I|-ZD\Q9׭‘(Z`ODZ:DZ׀DUq1u[d)R 1zsasJvǵ[A d;q03FZgkl-zkV٧V>w.0cd8%Ӻo!֏Ԩjq|&{;{1 }=[8F_397:;.֨#X̼TqV5ꁺt3ט"rjX|F>}`2)Jv~kCz5l>d5;;jkg~F T}"\2=Z>FN[V䢄q/uԬn/న&_^V̓, NBE5BpUf'-[Pfjw2[ܷ^EQɳQa@M+5-XMݪu+jMLs5j#)\$5SU[ Q0j knzeE[֨9XH?9MgLʖ,z a5jl j^52ْ}ZNj%.ȖkmT1۩zOhX;ԚS$1;]x8F`̛rE՚xPuD%Pl]4ltw:%bk͑hWzF٠Cq+`=@U;j$"9T@o?O xuCFIq}Q5!׶#qȀlwi]͡.VU}87q\*JYBix0TԊCy ۂ% v1Qڍ 0Y J{Hryd[m$+ߋHv]"Ϧ@bdko~ETJiTTٷjqͫt?#2qmWA{w1EOU>1,>D k@5'do.`82/nj] @B5Ю@!"u5-{-1jYD8|l {4@皊WtO\NmI]tcJ$7?%c}GP*8z5T@_{X)EVWTv +q8AYYZ-K y yXc>2> SR_MW `luXAFA枼4_x%u k-,iQo1$q(Dߦa +Dzryr"7xr%D׀ʍY D5@\] 9G!ɂQbUJ +2 J[Q- @q`WNu҅uEiP;%t?6D1#0Wdrݠ +>-0^FxFh|A +rتѬoE²; x+>zM 1:jVTQڊF(1:zr=:U!pX7fG|5fG[c++QR3:*P@g"4X <\(yc,1W,]dvZK;Bm壟X܆j}ާFG Q3O|G xGQ` jP՘"US n.2ziPޯ 0}=u0AjM9`<kV-zi~EI̪R)PzL;_FgWՀ:jUp V=H]BS%d6^fQcz;uhwT1 G5zGv~|Y;¡1=,ZCԀ~[d6=(DbYO5h Lj'F &'_wd:rOᰴu8)3@hg+ګW>pKO>úB׀"9S He(+BPf"!5ѨzFQM#˅Szj=V\ܨ'WaDk,Ш0B`4ꛣ˔`QA濹|Шӫ_k [yoHhT*qQ~F5icĝТ7T,=qޙu}][1QUp3hT 'd_]Fo|~FD|}P1`}&:iqBqA1L f]d@njܒ'pT3 JG~: 'ZWVɀ<*[&%)@M*6SnM-` 15{PI]a;}ITsMp:>;ob1Z؀5o6%Pvpj٢j5ȷ4bkj \ϥ 6%TۚueJ#`Ӡ"n]}A9!%70j(X1 y5T'joWiGzWghb̥{:SDę֛ Z؍AuX[6dsU+&%A j3_4zGovԭwRWVCT%fQgbBzxcF1:z.Q=[teG~mK*d $-W_˯3ueqAT1u)U5;| *f5@BF-]|W4c Cb}V@ZE<݆ϼSAWwt̶43Ge-a4a@{X:%&ՒM Pqe@qtXQF50A(9j,WzGo)QqV $*"iDR^0Y$ShA(Q e![OAt( ~ o0}cyQZ[^MT~=E|: bycl|MPNbl"^l:Eueςo@E!Sdio8ŷ[9v)R 8]@3>;1>Uj aTW a 㢎Nj/5Ɇ/KK ^}סl_^ju:VR=ƿiu V 041̪wtܯ8:H Ia\bYs()r/(U<:FQ#&n 4֦Ae8OK--Z2 0-PAo3yPAm0e٫n\yqwT*[DN_wk<*a6- ;UR&,<5ޗ]{2 T4I*J'SNzZ2FSmrk@#d@'9o+%sCk Ȁ|Tc2f%ި_ +PHQAU:aZ>R,jm0vE]X.Ү@I4o3AvZ`=PO6)Z = V͒xX-Or\OʵCYIʘUk+,h'zy"5ښk牪7魙}_Ys'!k3~y"Rq/mY-2iƠz_%;za|V -q*g2g/z'w6}S dK'=g1X\! )%jtRH''8r]3g 0j 읫O6O$ Y$9*:h ~@>$صgKߒ%ɩU@ؒ̆9NrzM ׯ;P$fpc@Xh-VG|A=0?pu,&+[>Q6mz631}U4 pjzj8{b®Z ?^hs~/6˜jBZ?l}5PRlu'C6Ҙo='. v "ޜ!X2ZZaIc9GKӲ-ݒatxUsjItZ(Q,35nQd.˚[r|j)XdCӇ#@WKSпR8nV1>VYeBYrhCM5UAmj>vAXZ EAbbZn^)C2 -:6 ,8)= sZOX uCMggt&0ݝ-}9c8D$&HK3>@75ϱK˛*xtᇄf#c*M;>!qd%?0w5c3;j~vW7=v mf%%-(B] .5f_xJ-6Vn$1ʉqR>e{Lh)bJ 7~{mu"qHגupHے{B~;k* _[ңiD^В}66NtOQj /WT`gtv$- ǔ㯿? !H,x$miB'D g<} -uQ,9H6&(ApX~)A`3ǙX%-Ш&LVm5E. ъw~;z31O@:ga*M`Bz+=;5plchTHBjջS]CPY@i$6&Kv'0p`&7\I^ߋc1bU y [d Lcmb:&eLo@J4$N`RVƦA L*Z`R7okVEj2Sb I}K C:Q}(RQY(JQzA }]R*[oG"DNGs҆{4M=J_ʞ~f "*xa"%>3G;s&@'oȈ ӟ}t2839(wrou 0ϰ~,ZbUK%  5nO@TfOxZwt].0kN2lYĬfR%h7fuiEUЎUݵxfTvJEV&EWV"DRG lx__[WALiM#К z{3H,pu!%? OUCur\$u(…y*R.nYг˷{.D.u*Z.JښЎ0yH^Nn^IXH9XSOgqκ"1ۼ:쵄]$ ~9rgupV8wBpֳ}D_?M. e{>HS3b:7i g=YEc :u&җI8+VfEiAUY%4Ϡx]\ чdgV_RvⓕGg68IjjCeb|yxoG9df H7%5k&wMxy\UI-P]6ɘIf `+$ u8hkBO7FtD@B͗V#T>Wj V6(}xPE|QUMW-;n7͒CoC |bS{F zSբu#o1_l΂*!F̠C4C-Q~:uh |+.[j6;u['6" ,BhQ$[ݤiEl>gI,Xۇ\ lI ~ >oIj¦q2MS'p+fLEzHEKg`LV(U ͻo\A6'JXmIӊcf'MzIӊƞZݓW9ino  VxMV W\@8xI2`\Qo5HQ&M+a'żB-iZ&qGޒO#;$ȁNۉ4YKVI wWo :z7Ĕ[`]B@W]3z })pW+q~o֘SD18( f)Λ=Og}R(?WcpX쵚Xzbw[\ޢBQBTop)]=P>ǍT~#UxT.ySG*Pմ VὛ0^!&wlMM$p%YE˪q PاdY2S$D0"|rUķF=aW  z [Wew_̯"LЕb ~j bATb2KX ]{4|Q=8$}bb_fu]si ]6|-??@(B:MhkkXBj8bpOs"(:aA:NK۞M</ o{+|tZݬ&9-Ց3B9@F5 !|5vL^'z_'NXc%  %rY,f'bF|5(AּQ,!(eT x8M2+?2bu$]?x^aurbkB VC@Ik&$4WfNT|2-euen[@#laf޲X6etWcG)2oFG!N,y@Ҳ2noY,sXpݲXf;,ٖqbc5(VcF3WHfwM]ˠ~| tOX'/&ݖ![AMv.etP3HF﨟IJ޲e;զetA{i:1:zX ϪF @ڥAa3C?ˀ̇bMC О;QXvIi jgOF/ $4)IzOO!ew г2,C{.ԅehvRctwˀY 9 )#S5FGo?k~2z o/l2Iz"[QLPtO`+폿 􄵢MHX MHX7v{bZُ3::7D1}ɝ{ZX}%exqex? yzOS_u';Z25`{ZhyG7;z/A?ݟ?d:2w)":Ȕw,.C}ex.8H%X~ji%qegt);z#~ov02t՘a41>aV 2<3Os1>Bke8}|,sz-H-JӞTT'2&,CG8}Ioz o$'Ɯhi ʭYtM]Xa@<,(bNdT }pׯ 3,2[ư 82, kHa6D:Nz2o }exaԅeoMwMHX2w>|o[$i=qWv4?R~3 lUO-;7exc߈,E@ Kr^8ÑKgW1_\`J LRs&yzI5=+-I^$%KD@~8iECFp_5lk,/n&)}jyog-EQӡxGۧ.KӢ}C(I3]gA.x6V3€T4(͇ѐ4XѳFqqhkY+ NO,#4T|mG/w%ZZmR, +}>`c@&=  J^3܎E(v@UGv3܎HE(ظ5u}^ K8z05%g ɪH$J'"ӂH~e$F! 3rL"FG(mB weZ"Rbab8Ll$` HJx=Q:SC 4r/yRb.D~Ago9$f rV(ug9[CEtiANWGDJ|:47voI~K@I%Lr7kK(E˷¡ DAlh-r^-A{  'QVZ>k0쉴DrL |;8>NaBȍ%(`VZC=0K?DfiZ/Ėrby5Zw^?Ȕ47:GctԀQctT嵏Y0 %_YVk0 i^arF,qzG5:ZAwp1:0ˊX=`%IQ&`Vq1{t&QF,(0KxTl=fjgy(i0 (+2 ȩkHap*Q2;gӳex.?}Q2Ij> ^c%-C?(bgcXǰzw D|heGR%-CzX3,[~eX RXLeiwvT>JZZ/ ڜZ a`S ߟoG>#!FIC(iJini:?SaԴ oY1jZ䦮wD"Q2- Ĩi6jZqno̎..exk9Q2#ipR)ޘۭMMueFMБ8:jj-c`jM,2Q2kʏe20Ae3>?ɲhi!8ZZm sy ]l %&| swFK0)l 0rGK0A2 X@5K-ɚ2cs{cxN;؎a!{e뽇eX^qKB--L7FGgJh i-dꁍ4[tg{i❎^G&y3^G&P{t饓YBC,3o4Ʃ 0>0KT0ӯ,d2>>0˺ kjf+LU)60K$*3f9(s40KgWc4 -GөG1Y>k1&$T B{j`/+ >TH.oX[>8LW{*n3>0wdbXw|`gb|`@Smg|Lu# 0AـLg. &#^W`#YCQCDŽHPd3Zcfi!SqOv0\DrT(" m|޷u;ZHtٳS.1Lu(H0Ձ79pE[7ʈ1>E[oF:-jXFmLY5je 27 )T{z0F`*Ħ{6ς?0iQ#TLD:1`ZD@RJc|9/$`^H0U7) (7zn3sg#xI:fdvY6ҳ-=_={HAoAD3 N~i$vZ#ݎ5F:="6,=V_v8ot;DG̽P\ D\gOo*w18mc z`BVڟ#a<2둸KF+p[ZFH%5Hti&=/0ꕽrw.KЃIwp>Ã5lƂCF;N.?x]fѬӠ#@y4eE*S1z[rykd镒#7za >Sɯ|ThFrujKN{Ɠ?4bRo)͝7F%^MNC֛g7iUQo_ [H&vM/*)mp$#8Ɍc\,(N/`XJ8fۈF|o} X:i^1)_st3 &D/f'SO  :ید IzV"' 4>l7~Y8)ז\Zz1F;6!ogq-]:Y8ު,Ms#&H NߺBPГxP8eμǛ2LCTTww@p,iSIYu)$иEv'~aLy@XT0REO[=MqNQAoƦ&:+sIz+dzVf^z̠jhHAoWx۷u6NCuψHlI ~$6p$6?W `:  *<<,O]LMrDJew8YvPƫ!E;UQv '$*R|* Cee`3ZQv Ds]}Qvڪcl7q"VmVB|a 7}B5 "f3E(1O=-%߇P3R,4ľ2{FvR]}opd.ouvӫ'*dͯ\d'$~OvA3Lzv։ɮbSG]}kcfvܼ'zOvm̮Mvj.P螙]Ev@OZ%}o j_渂Cܿɮdi|GDQ#zzD餯̮Z|_ s3kH]We(/acXuѶx_a+#sGe1jc; W{ 3 ˰!т?]gтT3dѷRc,HGydA:ԯ% k~wSEdAy{f/5)H@| A`G(WV~&|r(K2s( 5>]ЦT{Am9Ud2-ؚQ2 gU ) ҡ?tm[?t'CC7ug<~4KgAxVC^ &TYf5癉| Uhtuz(pL0X@2  43(C# >;Blۄ_k}[;t«25 TUΈF7 CtG3I]πt"X; $59u4ApR΀tZ>qoUsumzKΩ.|>q8c?A_}@iH$sf7dn9tw=t0J:/.\u<Ē͚d.SKX>1?gJYB8?N7'9 hߴ׿/˿??OO/¿o_xoPKkD,fWa atom_types}m-m_3Dq8hKZMbqRE{͌CJ9s ~޳D-/򻏟?Oۿ7/9G?]Hz^Kӗ7\/~[_?ϟGo_˿o~[K%?^E?=\6 %򵰾+.H@W/xoqA*%^BJt|ǵT.-/9~) zw=^#:f8B|Ɖ_C̔q @ҁb穁5\ XJDrdwKq%_B b` 7?M8~'pc8ˑGs``&\CB!B:Cy,2HVHC4aOq\0gc~H HP(/ݾ spx~/;oLh~A0t`sI7.`cE>cCp=Sq (W8B1DY,=\+| c׈]cov" '4=Kx`GXV4fZSܵ J /c/!r˜MIt xqS.Lc;Ux)bQA,4w^ZWtREInPkL $wׁ!tl";ǜ^O@76ݧϿgepyš-2gE!;lSv:n` 3h/l,&U[qWdةJe+pi'ϸ͕y*mcF_P&/Mq/7K zQ u>g,Ւ񺔼닧:BI4ݠQ:s"iq48NJ8U"S|HJF)T;y|OˎٿR~/8doO0sfJp\_JCauۄ$ BO+jAjQ Qzo|֛2]}3'$jq1aVVrize t9ZE`sV}c?GNTK W٪ԁـ:tc[ڽn bMs[3 -7a3M1o`!_l]uC6!hRڊ")(*) V`ㆹqccS;VXz1q]u^{m 5MIIBdO* VcOqbrʖ38amz[5DC;f8J8] ?Y+rwN1~)~ dZ*s#o yŲ6lS׸xG1@*V|N5l|swI 'UKBR,=KWHRܮF+b/3 'UɞrΔ%C@VG,";"z'lXԐ #F'g!욒VϧvH !GBinz^dŬT>M|ݳ/eH{3_ƽ"x[C Xh#D/pDz 6@v1-v3()W:PߩoH벯 z(:1ܾFt[Iu 9͔, lӢRtpQHvԳ5'fyP /ioe.9XVJKS3:zN  zN!uq֒Gr{~qS2X/~=95y~Cʆ8.&VuAY)GWQ fdQ$F::Qt9}r }>ɻH,Zsʩr1/O)js9p4WzְUC2挺D?=(ֻ|#Sm$lڮRyҵ;D=Z~cK.ھgm36@$OտOqfxgX,_s,VpADf6 wѢ/9aSD[|9)Ke~KWlk.۰<8uHK/SUXó/"jSĐ0n9MRyyEbbJrM.j >Kf -a2ŘMi|)32gѺE;ɉ#:CKH؏,Vz,auuB5?}B|A\?zMk̊[QeZBsPx nϛ84- Qz3Wn†^)VRv#_sCRl|G;sFKlX- !,Lh<9,*Rہ,[;"ۦ%C(o?86@vҿ1fF2mʹ\Xc=zQ/B<6Ңc"m=\4B Ô33!Wstd+',s̘MC/1W,;JTNrcSj0yO aʌ"id!؝ 1Z* (QrY6x7/9|BlMs瓁w.~ʷM0(3J6)aR#Px.T7 z#[ BB?E+bOc}SXRȷkYSS#^&Y,= F7@<+alX449 o;~ _eF%#y<5v3*WKض@;@N1^}k%Og^|>ͰjxbYT:XYNa L&R϶m 8lI~еTI W:Ŏ9ֻ_"E+s4ϲ{#D5 |z5gr](${X 'Nx@{ KɆ׊oK`f{AQ 1o"Ǘc%4U 5N&9p/X?{l;5iSZZ4%L|F{5Sd$ SV:Q 54g8ݼ6JSA XN)Qҹ ̈́3e ij6buQg搭ѠK\@g=H6'x44q2O#:Nz1#rEZ5ΩeBvXN/rÿWw;8 ,@[x ? :E8".+#ۋ3tw"҃f|'-,6̍S._XS?M.mfBt©]lV3z̖-ʺ.qN$ kz%}Hrƭzŝ{3uBEaDt0y/ЎlDQeKP/o~V9nNv)Xc&dU1{r]L`j311ʮ "oCLZ,YZL ]:]8=J5λ;˥;#3UPܒ0rl:*pY >S]~9F4&%ַ"PֱbvCLy}H;(a+:YJ=5D:cSOrօeԴ'[هԽD{N9\$]]3`b)?8sCb~}!B]Z?6Foh@g <(t=MFzSG˒fJ4-{b&A9|`zV/)^Ȕ}*µtOa;p)Ӕ@4 Т!0ut`"6Z $]D\so̐| Z{9{SBr2&+I]^϶M3V4:x]%PIe9&=Ğ lǯw <,ֳzbO .$*q]C$Jaw <T20p=\Q KtŃfΙAw׬]k9,V0-ME_ "efL44t4vow{5: Bc6~S!^ 8gr g1ȩ6nb[!g<8At;ON|z305KRJ8OiJ-UR#Ep)u@o{ev&Xeb lBu6#gA6ۀ- m9Cl+z)n'q/;S6}uKKBN+Dh!F8jk3 k.os :ӴxxdI[;otY(`&| 9?)0*h[#ғsY_uRԇvq ސVUxĹx!v;<{lg"GAezC1vѹhӲ|r8:b|hfX|8_Xn̐r$Hy V1e!}7c WeR%!{1.>ϖ|a#aڮ/NئkRȭq%^976)2'1p#A6G_+ = w7l[p# {4d^)ckdfqb^Xrgq#prsJs&7Y|c<ڈ630DAZt2Ơ []U\#`#KMDzb%" f'Jj2H(2ocpw C`ue_[4Uf#{6dRH,0+z.[|]w>@@ipkz#jKj>dDH o3m=PM@>py֣5bN(ȉuBA2H׃:ݽ0QOЯ89@OJ\2y D*Cxд%L;z-Z7s lOۀdӼ-lCnVf!CDRs[3{ qʵ&TSD[tCJxPO|!b6$N|E(Wu:!z!oTozB'ƕ̃u%H]'ma)8 5r>ͮ܌9xk-A}HqW=8fjgi600#FoqR (T޺9hF9[:rB*9滥[˒ű|9b*řn_ߵiȹICb~&<9)sL#eJdn tXeV2# :ХkL"Ȏzɽ/W"Ov)RhMW"gGALdڋ& ssM n8^Jٰ{CuGUty̹obiN9ݕRǃ"U ܢ&}fV8..L?>HidZЮ6i3fμŵ Ka$}h a,)h9E^?]ObךOTfF^Xc_yN 0HЊlwN&eǁlݙsGX|cpoh ȮlAv'4w5Y.>35+Sl]ΊV'Pd_+5ƢF.-|fѿuZ-7Պ9˦3*;Eo|rY}/,VC8lI$?m/iئh.!-&IcvS{ +mLzOJrmj%%2 n::\V^bd r4!=TeqzRg>asDcKO]Tmyбu2VgfOs,VsAEFTMOMoH]:7H`eۺlK#Xk^e|)hereNtP3O}5$Gʷ7:"IXLϠ04ZY Ͼ)%ՑB%('י^"9qJ:iV wvtX;~)~`AJpsM"iZ/Y)\fYY{FG NU`G _q.,{]M#S{E%cGGey۶s?e*@jfof3 I $i۪#0LzYӺW:27@ ӷ`G۽95aͩ 1T sqoSȄu}Kզ`HgWrO]Q:wB](M R5b8\4+,4 H [܄ay yG_OF̹ [ !>Y|K0:|^TtJR26-.Q|af`@uQ;ku9{pex>'rBq͹_+)CζBy zldS:^CT4 Sh!@JN=9ʜgFCOn5a*Casd I&b$sзgdi@ɃQ\03ĞH]R֦Ιa $8Իs4=jY'ˣt{ rgN}[<"t[1>5TY,TSK4E9\2Ik\v7B"(lhH/)/4kptޱ"UP'~C 8#W,S&DsEm)H]𨯴1v 4 =0F-X xVB"eъoܐnr4cVaAyl`6UA& 3l]/Y1ҝm p&rm9{V8g~Z#c #3)E='YU<|M-}m`wT| vE#nmxy(G$+}~Uve6[ Yhp7'boUg XQ8[w`{_}:W|zk`|KMp{u!^DOSAD=uR]Y #1=:8(8 ' os&gGlT\!pf)z{[c#XG7腼,m\l뤳mG1ԃvXuH_5}NѮZǞ^{?ާ-4Ų\9F.٦/Vn:s{+E4|gm|;vcPZ;KEh,]w{XS$Mob_7|83?,gJ^nC$!2a%_1buq]@~~`P'b:"9չ9g^1uש[ ?-83Qs{>abyҢh3XEY](m Xj=ΰKС-ygR\뜭2sƔ9s m"kŨ@c"u' _uԞg0_gC50:xؖlD L η^51 ]/y=Uyh^y 5;ΌK,+q0V̚j m}&+wٺlx^9C k2+#DE 5g_ΰjiA>XLdo(Zs|XDKu~ujz ۘ5ʮp66u8U }&=mHL(&C9-LXO6߄~Po]dQ71w va!47RSc hY4YV$f oҤKj瓻| Ҝ:)߽RZtn΀h6\lv:|ӊȦP|G. )4w:$f"Nku L'2]:`m{unC ֡sܔT@ǒRݭdRF/ul:SzD$P.mFC썙oq9S踘Z51{˽Y?~M@. f"40?LGhiXsc2@DG:04$2snVq^E DMĥͻ;2.44=&vb߁W~kj tzL Հ*|"O=:0 xv`n) ]-Q ߏ#Va1Ѓ[.)Zk 6s׷t/+q!1t,% _(MЊbGzAXrg~#3g{z3cȤa]} 0%z4scqNkEMgs]iQqA^ {Bm:cؓZxha:#ixȮ[lrKn4Oϔ:xP[Z >ծ 4DMT~ž|1;oo֊s>X6S=)-|k OQl)Q |jB\y[b{60"b g{pll%kskfwx3Y g©`-)PCrFr q*Te6`'}S 8: \'8ZH:r=&TR"{}}=ҽr/+pKD>ԵE+$.uMx~{Jih4,WٶCm'&yV.4͌sq5]| & HRb'܁,|v,D3GY}L+ۉ{-Yfz7Ç)Ye: 0*zҨbva=)9Ez<2NY:1M6V!Vw`c[woy.Zrb-%}4"&C%Q0JwbBοa+Y,;БXiZe}˫ŐS(#[ Ǹ&%fuQ$S-DF>dbb@a"XLN-ñ9GmŎEvGC2P2NZ|wp-Y3p,;VMa&D|cis!?e kS#4 uN4tCZS][[F,qĚFzPS{f)H.;Pi6>*ЧE@/t  Ԡc6NyȨ-VEvժkS dFNR+N]n|@@:lN˾0(Et.[n6[lr{xɒ׶-gVg5]ɧRbmƁE$\` v۔zCZ ܀oGdɮtS~No8Ů  1lu%(֐h.&ʼnؖSA [\ػ6TZ$DF܀)ѯ3~Rl>gWӞ6Zpct5m,w9L9T[2"iX9e.|lč4,[W|Dz Ez=HHqg%{~>5b/ifO˜9k`|9!rw/$6k0)s!sd,ܧNQ_uG\:Gp$\WI ?|?-PiIʖH[#S 'V4g1 `F R@j5 e]ͧV%Jn`8hw\JHD6e8~tzng|"E@3ײ,[Zm~1F5.3w=Z|h .˻7)xXzlU4N0P~G㐦NҾ 3> i8;^ӰАϨtא\tክix;(;aӘ b\x1xHpƩ`tjs댧+(ccqmu \.;WN#{hv4qkYYpJw(CL*h9~sW`CnwuvLkIg\}o2C=_n:Ȝ js}lyZlC ˁKL("ˈ?2) pS&ڵYӈ:3g$!&ӀDד7EC3#{Kʩ@<טF9A pCV=u6T@]m ޢç OjۧlZ ݪ`dĜ[gzuy zX:ggJaA?K0c]84k !R,ٔq,Bj$X-k/ZZ+2Ʌ +8c ֈɏʦ0<w-мoB~]W[-h"WIz"Zw=>'E"gGUȲj@f^wJ$sAsP_gkج(r`bN5#g~]@:?PT1zNIhmr*v=Z2FQ5jVΖ~MF"kHQxQosf fS.w&:0RgVWcUN,A^wT-l++lx!0-C/Z.7"s >wA/3FGqKVFSz!y ql~F-`&튮))*Z>ne[@sjz"Aq9^H {>>96P;z-ܚJwטJ‷Llft_ B,o#hʼLNkk;32B* LtE[CkNyl?=|X(>w6!wá?pm\`ƤTjK9쑗b%CmK?d=Abe{$3;d6uh'oDsxS AgSEN{6SR<'=?%0mz`{HvmK5Vu&(([EΝy-6; .4G?e3~0/y2iOWdj7 !شnt©kjVI90PvK35#u 30rhv]]3&*CIGpw~鐞E F7T#0 2{[9pW> G͸,[pD5,\AYCa}w=n*) Eeڄd40/MDd,]LjvI+L:׳]ꯖFz&gfs蓠jӭEkV +OZ"zcqBc..2>giZS KͼȬA¦E)HLrgt&$y38_xdsϠl57YEu vL;ǝA+z? YHH:G-x ikD1{1ZՑ L}꬧ؑC=Rn]ĂC/9?;>b9Ccl腞epH υ! P~2uCgo]G$Aѵ OA bo4S8*eWE :SMppvZ}uZY˩5]岈wpX86M"ك6Gb*#; $hC##2ȫ&~3O,cM8"v!mC~-ta ӌ`I%Me?g8ߩJՒ3DNl[:q`r$@al/&Bhh63?AEB65>c>C;IUB;loӡ]>,syzvۛgΙofd׹#L62|W5'n+vuG\f\-0{Lmm8O_6{!Ffe+~d&yi5+sR(5Ly>WvrvAUA@)2XW{ʣAf1:c.5&N 1h#0I='#BG}кZ 9;^ Gh a&Lm+WL+Ȯtx܎q"{CR͞ͷFtƳϮXe;@kMN8`,OĒ/vY*ЫfTdCQok_2|H]ERGRj| !N5V 3A/e÷q>ĎўJYxdZVNo9:zvr@+pM T>L_GB+e85E.Ov3MX*?R qP)C9D0!Ћcgqsء[,P%1+" ;-rI'ײsL{7{:i]eXOx$sF!(cv4L@˰+">X(A\h,43[9dD)3P x8V;  6umjyhfvHGq I1:kH&Y420K@B;WKLz2TVIfŽ?l\EIgɍdRL6skf#y+fMkuL(cGy_+nYU*s5[-/mgY~݅jlxCyISjiW|[+A\V" H`+F~'Ȓ?܊,9_jM2cjg]h$9rY˃W}z_F|+++k-%i$K Nv¸0E !<70 H#-<&4V̹vI)nY}KSJηs!? Y`&&W{c{K,s!cgb? -W!4aaV4 -y w]yC Uz?[Cx= c/hJ4q\ L@&VL8ei97qdFU֘ Uў[qٯ;܉Hd\}!@e"{FpSf/,m1Cgxy+9j5ThЎ{[LBؿ:m1&h/m,xpSg_cNfFDi*Fxwk/Pȶt=q1ڷˈE-^v.) G]TK-D.(FJU1|,MykS ƝH={VFJvbz [|68U*lCN/5ՖTfK )b xbl7,Gs gjgk_/E H}*_UxT aw>[-?k)Q*(Rl9 a(mG䛰7挞8~& KR)ĕe$kW] UK iBhu !_[YD7$W_Lg{)Zʼn bv׍28ݲ.LS!B˝e9Q tms r9>l9HuTz' fvofYD1Iaft嚧D"p`MN$z;HȚ&엩Dׄᵼ ޷#z!XH"Wtk{=qLMx]Ŀ2'ú įXΆq'C .-K 'ey`T꺁hd/v#Bda597#Of˄2 $y@evƻA.h~kh}\[:]&UnYq<f^_`.ykI|_tZݴ9.)APk?a,])Is>sY{pɍ_I)>6GfV@T&]mB*\(5|`^zk8:dƬ|Cg$8v5[ȎβBkތC69(XVbfL*1qTCK/g9s<6fNyXY@+9ݽnKnXB΢JnͅSOSduJzTGP\GpB,2Cs!QƓ'29L80 K94܄deRk `̓o#!GC!,*:["_&scCM}<6@M*ȗETbbvWxʅ_<~eqMJ7Asv~^\H;EhQ,"-y7A89~nY$`ks]y;НU T$GJcSsrI&{`|t\exW~,U@[p7MU/ 坋U̙?1('4ЊdF2ޅaR!r"cL9%h1eqhF굹mt:k$6]VSSCx{I:P򫿝 /~^OO,^ذ(O )Jg`k_7Z=[j|ٵfʗ8)m&K* ]obq,n*.k nB=vE_<B['UlD`>}T[׊@{[HV'1ob0Ws&Cӡ9+^MX~{)3Y?} 8LE醽~Q8,*rEu_>0!FatrR);1 -VGo2 fZde%py?gp:d5?1$vNc2! xK [|\ s![C:K D2Q.)T|nq!rtۀry`&i i-  Yfe罀_t} zUk2UbֻP/*qзw]֑Tw'|1[7/ߊ c_͹ /V/u?0 9hv5ץˁٯjQ~.!۱ol]Qvs0@FG,@wg]q`n(?T^ucPFՁ@-=4̬<"A0LAwþ='} MiN,ަ(?}NBxcn@ܔҀNn|wғ}RA0qJ-fvZ*} VcܖM({4&\X>bdטڻ{)`D8&X"5v,{BqdN;Mlc>gN|뷝"#tInj{DpSŒY>C`+:^vzW H'CNi](z⣚dŁ/[uD<ut;=pIqWĔa* ߝ!kSXZj1?9Pfm` kNH !AH3qA&Tש _hLm&2 8t\S>?L'̧o=d\Zʢ=d\|A,fB +K?cMGÙxYk&mwCg fqBQ ɸ/Do'U`j 'ţiaG0TܵF$)ãcvzjAusH#E sXk#Oiu >bS0DRi[|#zݏr*#9J٣1oN $#*(%$J4^6m""5eApsuv |9"4#cDdՌOO$>n߃0R+c3t궜̘eEY[2Mu.ڌk>=equ v ԑ.¡$ &KRwmxB65+v컠1end(ÖP^BS?3c7u4;5c&RcNF11--_uI\O13]- 6cC{9xY k"9:Zd a*7sJ_=y)*‡dO ئҸg̫oi5;F%p,KB:h~<Lda@"|u"~Q+m0d-(W9v=6ٗSd5e/P1I~'6`i_̍iP.wT,OI-lٻKutdoE=Q=d/ f׀-)бD|v-:()|Ni71>R@ag,*6H٩сNITBtCQn]tHnFQom8t@eí:Pt G |Jţ B/=2cl/C"J| 1(H>3TU=3.ϿX!+Jג?$qkB3+gnBx3pӿc(w@xbl5sf,YH]M:^yi>G5;ե_dJLh]͔qd˵C(9_+³5Ou7SDžpՉXX^IRw"7hw8U~N#i둝ReU/ bDlkZ_U㞵YtC|o1;t% a(fW˝ m<. U{0Kɻʷ<ա?y"m[ENcg !_!@u9.쒫Ў|!>b3rc^ G;b7mB˒(13|81i8G>k7#L9?a6H~eց(EޕMb4FR-#_,A%LSrbzkk+X:#[*3kuemBV";1p[ZHxgEr`8GPKkD+)g7mixturesSj0+%XR^cac[BA!/gI* I}b챦l.L9#n34j!Fl6 ,T (NGbCdq>m_1 eQ\M-ZWz~[;Ӂf C8ygiF,PFTvefxF[ET%'q82q s0J ($*8Gpiנq;.<4J\ea[=nmj^w@)f "pA)ẄÌ?DgKW9Q㧲5l[0U<"7@[uOgQb]Us*ecf__s PKkD*q:rcontentOo0Y`ɭRTZU 3,NOF5&m i0~ a% $w$y &oX7V9bx=>/p`jeWP£Mv{2ɧV[$Sïb$b4epr\zOԉ1 H:*f`3~'G;:LAɸ3)%_L2kEmJ40lH S|4չnp˶ 6>fJV؍Q&9W'EK=`(g˺S/n՝)+6$A+.zgWKOp$WU}E͡dm*3quZV3s~=#/J߸U<#gLIgv”q؟ڬjY7=vvF.E'PKkDA~>phasesPKkDըaaK specimensPKkD,fWa qatom_typesPKkD+)g7mixturesPKkD*q:rcontentPKrPyXRD-0.8.4/test/test_mixture/test_mixture.py000066400000000000000000000224171363064711000213210ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest from test.utils import create_object_attribute_test, mock_settings from pyxrd.phases.models import Phase from pyxrd.specimen.models import Specimen from pyxrd.project.models import Project from pyxrd.mixture.models import Mixture from _collections import defaultdict __all__ = [ 'TestMixture', ] # Requires properly working: # - Phase # - Specimen # - Project class TestMixture(unittest.TestCase): atom_type = None def setUp(self): mock_settings() self.project = Project(name="TestProject") self.mixture = Mixture(name="TestMixture", parent=self.project) def tearDown(self): del self.mixture del self.project def test_not_none(self): self.assertIsNotNone(self.mixture) def test_data_object(self): self.assertIsNotNone(self.mixture.data_object) test_name = create_object_attribute_test("mixture", "name", "Test Name") def test_parent(self): parent_project = Project(name="Parent2") self.mixture.parent = parent_project self.assertEqual(self.mixture.parent, parent_project) def test_add_phase_slot(self): index = self.mixture.add_phase_slot("TestPhase", 0.5) self.assertEqual(index, 0, "Adding a phase slot should return the correct index!") def test_add_specimen_slot(self): index = self.mixture.add_specimen_slot(None, 1.0, 0) self.assertEqual(index, 0, "Adding a specimen slot should return the correct index!") def test_add_order1(self): """Test if addition works when 1st specimen slot is added before 1st phase slot""" self.mixture.add_specimen_slot(None, 1.0, 0) self.assertEqual(len(self.mixture.specimens), 1) self.assertEqual(len(self.mixture.phases), 0) self.assertEqual(len(self.mixture.fractions), 0) self.assertEqual(self.mixture.phase_matrix.shape, (1, 0)) self.mixture.add_phase_slot("TestPhase", 0.5) self.assertEqual(len(self.mixture.specimens), 1) self.assertEqual(len(self.mixture.phases), 1) self.assertEqual(len(self.mixture.fractions), 1) self.assertEqual(self.mixture.phase_matrix.shape, (1, 1)) self.assertEqual(self.mixture.phase_matrix[0, 0], None) def test_add_order2(self): """Test if addition works when 1st phase slot is added before 1st specimen slot""" self.mixture.add_phase_slot("TestPhase", 0.5) self.assertEqual(len(self.mixture.specimens), 0) self.assertEqual(len(self.mixture.phases), 1) self.assertEqual(len(self.mixture.fractions), 1) self.assertEqual(self.mixture.phase_matrix.shape, (0, 1)) self.mixture.add_specimen_slot(None, 1.0, 0) self.assertEqual(len(self.mixture.specimens), 1) self.assertEqual(len(self.mixture.phases), 1) self.assertEqual(len(self.mixture.fractions), 1) self.assertEqual(self.mixture.phase_matrix.shape, (1, 1)) self.assertEqual(self.mixture.phase_matrix[0, 0], None) def test_add_multiple(self): """Test if addition for multiple phases and specimens works as expected""" self.mixture.add_phase_slot("TestPhase", 0.5) self.mixture.add_specimen_slot(None, 1.0, 0) self.mixture.add_specimen_slot(None, 1.0, 0) self.mixture.add_phase_slot("TestPhase2", 0.5) self.mixture.add_phase_slot("TestPhase3", 0.5) self.assertEqual(len(self.mixture.specimens), 2) self.assertEqual(len(self.mixture.phases), 3) self.assertEqual(len(self.mixture.fractions), 3) self.assertEqual(self.mixture.phase_matrix.shape, (2, 3)) def test_del_phase_slot(self): """Test if deleting a phase works as expected""" self.mixture.add_phase_slot("TestPhase1", 0.1) self.mixture.add_phase_slot("TestPhase2", 0.1) self.mixture.del_phase_slot(1) self.assertEqual(len(self.mixture.phases), 1) self.assertEqual(len(self.mixture.fractions), 1) self.assertEqual(self.mixture.phase_matrix.shape, (0, 1)) self.mixture.del_phase_slot(0) self.assertEqual(len(self.mixture.phases), 0) self.assertEqual(len(self.mixture.fractions), 0) self.assertEqual(self.mixture.phase_matrix.shape, (0, 0)) def test_del_specimen_slot(self): """Test if deleting a specimen works as expected""" self.mixture.add_specimen_slot(None, 0.5, 0) self.mixture.add_specimen_slot(None, 0.5, 0) self.mixture.del_specimen_slot(1) self.assertEqual(len(self.mixture.specimens), 1) self.assertEqual(self.mixture.phase_matrix.shape, (1, 0)) self.mixture.del_specimen_slot(0) self.assertEqual(len(self.mixture.specimens), 0) self.assertEqual(self.mixture.phase_matrix.shape, (0, 0)) def test_del_phase_slot_by_name(self): self.mixture.add_phase_slot("TestPhase1", 0.1) self.mixture.del_phase_slot_by_name("TestPhase1") self.assertEqual(len(self.mixture.phases), 0) self.assertEqual(len(self.mixture.fractions), 0) self.assertEqual(self.mixture.phase_matrix.shape, (0, 0)) def test_del_specimen_slot_by_object(self): dummy = Specimen(name="Test Specimen", parent=self.project) self.project.specimens.append(dummy) self.mixture.add_specimen_slot(dummy, 0.5, 0) self.mixture.del_specimen_slot_by_object(dummy) self.assertEqual(len(self.mixture.specimens), 0) self.assertEqual(self.mixture.phase_matrix.shape, (0, 0)) def test_set_specimen(self): dummy = Specimen(name="Test Specimen", parent=self.project) self.project.specimens.append(dummy) self.mixture.add_specimen_slot(None, 0.5, 0) self.mixture.set_specimen(0, dummy) self.assertEqual(self.mixture.specimens[0], dummy) def test_unset_specimen(self): dummy = Specimen(name="Test Specimen", parent=self.project) self.project.specimens.append(dummy) self.mixture.add_specimen_slot(dummy, 0.5, 0) self.mixture.unset_specimen(dummy) self.assertEqual(self.mixture.specimens[0], None) def test_unset_phase(self): specimen = Specimen(name="Test Specimen", parent=self.project) self.project.specimens.append(specimen) self.mixture.add_specimen_slot(specimen, 0.5, 0) self.mixture.add_phase_slot("Test Phase1", 0.5) dummy = Phase(name="Test Phase", parent=self.project) self.project.phases.append(dummy) self.mixture.set_phase(0, 0, dummy) self.mixture.unset_phase(dummy) self.assertEqual(self.mixture.phase_matrix[0, 0], None) def test_randomize_empty_mixture(self): self.mixture.refinement.randomize() def _refinement_setup(self): # TODO maybe add some more variation in the type of Phases? specimen = Specimen(name="Test Specimen", parent=self.project) self.project.specimens.append(specimen) phase1 = Phase(name="Test Phase1", parent=self.project) self.project.phases.append(phase1) phase2 = Phase(name="Test Phase2", parent=self.project) self.project.phases.append(phase2) self.mixture.add_specimen_slot(specimen, 0.5, 0) self.mixture.add_phase_slot("Test Phase1", 0.5) self.mixture.add_phase_slot("Test Phase2", 0.5) self.mixture.set_phase(0, 0, phase1) self.mixture.set_phase(0, 1, phase2) def test_randomize(self): self._refinement_setup() # Mark the attribute(s) for refinement & get their values: refinables = [] for node in self.mixture.refinables.iter_children(): ref_prop = node.object if ref_prop.refinable: ref_prop.refine = True refinables.append((ref_prop, ref_prop.value)) # Randomize: self.mixture.refinement.randomize() # Check all of them have been randomized: # It is possible (but unlikely) that the randomized value # is the same as the pre-randomized value. If so run this test again # to make sure it is really failing. for ref_prop, pre_val in refinables: self.assertNotEqual(pre_val, ref_prop.value) def test_auto_restrict_empy_mixture(self): self.mixture.refinement.auto_restrict() def test_auto_restrict(self): self._refinement_setup() # Mark the attribute(s) for refinement & get their values: refinables = [] for node in self.mixture.refinables.iter_children(): ref_prop = node.object if ref_prop.refinable: ref_prop.refine = True refinables.append((ref_prop, ref_prop.value)) # Randomize: self.mixture.refinement.auto_restrict() # Check all of them have been restricted: for ref_prop, pre_val in refinables: self.assertEqual(pre_val * 0.8, ref_prop.value_min) self.assertEqual(pre_val * 1.2, ref_prop.value_max) # TODO: # - set_data_object # - optimize # - apply_current_data_object # - update # - get_refinement_method # - setup_refine_options pass # end of class if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main() PyXRD-0.8.4/test/test_mixture/test_refinement.py000066400000000000000000000026651363064711000217630ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest from pkg_resources import resource_filename # @UnresolvedImport from test.setup import SKIP_REFINEMENT_TEST from pyxrd.file_parsers.json_parser import JSONParser __all__ = [ 'TestRefinement', ] # Requires properly working: # - Phase # - Specimen # - Project # - Mixture class TestRefinement(unittest.TestCase): atom_type = None def setUp(self): self.project = JSONParser.parse(resource_filename("test.test_mixture", "test refinement.pyxrd")) self.mixture = self.project.mixtures[0] def tearDown(self): del self.mixture del self.project def test_not_none(self): self.assertIsNotNone(self.mixture) def test_data_object(self): self.assertIsNotNone(self.mixture.data_object) @unittest.skipIf(SKIP_REFINEMENT_TEST, "Skipping refinement test") def test_refine_methods(self): for index, method in enumerate(self.mixture.refinement.refine_methods): self.mixture.refinement.refine_method_index = index self.mixture.refinement.randomize() refiner = self.mixture.refinement.get_refiner() refiner.refine(stop=None) pass # end of class if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main()PyXRD-0.8.4/test/test_phases/000077500000000000000000000000001363064711000157735ustar00rootroot00000000000000PyXRD-0.8.4/test/test_phases/__init__.py000066400000000000000000000000001363064711000200720ustar00rootroot00000000000000PyXRD-0.8.4/test/test_phases/test_CSDS.py000066400000000000000000000031221363064711000201360ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest from test.utils import create_object_attribute_test from pyxrd.phases.models.CSDS import LogNormalCSDSDistribution, DritsCSDSDistribution __all__ = [ 'TestLogNormalCSDSDistribution', 'TestDritsCSDSDistribution' ] class TestLogNormalCSDSDistribution(unittest.TestCase): component = None def setUp(self): self.CSDS = LogNormalCSDSDistribution() def tearDown(self): del self.CSDS def test_not_none(self): self.assertIsNotNone(self.CSDS) def test_data_object(self): self.assertIsNotNone(self.CSDS.data_object) test_average = create_object_attribute_test("CSDS", "average", 15) test_alpha_scale = create_object_attribute_test("CSDS", "alpha_scale", 0.5) test_alpha_offset = create_object_attribute_test("CSDS", "alpha_offset", 0.6) test_beta_scale = create_object_attribute_test("CSDS", "alpha_scale", 0.5) test_beta_offset = create_object_attribute_test("CSDS", "alpha_offset", 0.6) pass # end of class class TestDritsCSDSDistribution(unittest.TestCase): component = None def setUp(self): self.CSDS = DritsCSDSDistribution() def tearDown(self): del self.CSDS def test_not_none(self): self.assertIsNotNone(self.CSDS) def test_data_object(self): self.assertIsNotNone(self.CSDS.data_object) test_average = create_object_attribute_test("CSDS", "average", 15) pass # end of classPyXRD-0.8.4/test/test_phases/test_atom_relations.py000066400000000000000000000042211363064711000224230ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest from test.utils import create_object_attribute_test from pyxrd.phases.models.atom_relations import AtomRatio __all__ = [ 'TestAtomRatio', ] class DummyHoldableSignal(): def ignore(self): return self def __enter__(self): pass def __exit__(self, *args): pass class DummyAtom(): data_changed = DummyHoldableSignal() attribute = 0.0 class DummyParent(): inherit_atom_relations = False class TestAtomRatio(unittest.TestCase): phase = None def setUp(self): self.atom1 = DummyAtom() self.atom2 = DummyAtom() self.parent = DummyParent() self.atom_ratio = AtomRatio( name="TestRatio", sum=2, value=0.5, atom1=[self.atom1, "attribute"], atom2=[self.atom2, "attribute"], parent=self.parent ) self.atom_ratio.resolve_relations() def tearDown(self): del self.atom1 del self.atom2 del self.atom_ratio def test_not_none(self): self.assertIsNotNone(self.atom_ratio) self.assertIsNotNone(self.atom_ratio.atom1[0]) self.assertIsNotNone(self.atom_ratio.atom2[0]) def test_apply_relation(self): self.atom_ratio.enabled = True self.atom_ratio.apply_relation() self.assertEqual(self.atom1.attribute, 1.0) self.assertEqual(self.atom2.attribute, 1.0) self.atom_ratio.value = 0.1 self.atom_ratio.apply_relation() self.assertEqual(self.atom1.attribute, 0.2) self.assertEqual(self.atom2.attribute, 1.8) test_name = create_object_attribute_test("atom_ratio", "name", "Test Name") test_name = create_object_attribute_test("atom_ratio", "value", 0.5) test_name = create_object_attribute_test("atom_ratio", "sum", 6) test_name = create_object_attribute_test("atom_ratio", "atom1", (None, "Test")) test_name = create_object_attribute_test("atom_ratio", "atom2", (None, "Test")) pass # end of class PyXRD-0.8.4/test/test_phases/test_component.py000066400000000000000000000054461363064711000214170ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest from test.utils import create_object_attribute_test from pyxrd.phases.models import Component __all__ = [ 'TestComponent', ] class TestComponent(unittest.TestCase): component = None def setUp(self): self.component = Component() def tearDown(self): del self.component def test_not_none(self): self.assertIsNotNone(self.component) def test_data_object(self): self.assertIsNotNone(self.component.data_object) test_name = create_object_attribute_test("component", "name", "Test Name") test_d001 = create_object_attribute_test("component", "d001", 0.789) test_default_c = create_object_attribute_test("component", "default_c", 0.646) test_delta_c = create_object_attribute_test("component", "delta_c", 0.002) test_inherit_atom_relations = create_object_attribute_test("component", "inherit_atom_relations", True) test_inherit_interlayer_atoms = create_object_attribute_test("component", "inherit_interlayer_atoms", True) test_inherit_layer_atoms = create_object_attribute_test("component", "inherit_layer_atoms", True) test_inherit_delta_c = create_object_attribute_test("component", "inherit_delta_c", True) test_inherit_default_c = create_object_attribute_test("component", "inherit_default_c", True) test_inherit_ucp_a = create_object_attribute_test("component", "inherit_ucp_a", True) test_inherit_ucp_b = create_object_attribute_test("component", "inherit_ucp_b", True) test_inherit_d001 = create_object_attribute_test("component", "inherit_d001", True) def _setup_inheritance(self): self.component2 = Component() self.component2.linked_with = self.component self.component2.inherit_atom_relations = True # TODO self.component2.inherit_interlayer_atoms = True # TODO self.component2.inherit_layer_atoms = True # TODO self.component2.inherit_delta_c = True self.component2.inherit_default_c = True self.component2.inherit_ucp_a = True # TODO self.component2.inherit_ucp_b = True # TODO self.component2.inherit_d001 = True def test_inheritance_for_delta_c(self): self._setup_inheritance() self.component.delta_c = 0.005 self.assertEqual(self.component2.delta_c, 0.005) def test_inheritance_for_default_c(self): self._setup_inheritance() self.component.default_c = 0.750 self.assertEqual(self.component2.default_c, 0.750) def test_inheritance_for_d001(self): self._setup_inheritance() self.component.d001 = 0.750 self.assertEqual(self.component2.d001, 0.750) pass # end of classPyXRD-0.8.4/test/test_phases/test_phase.py000066400000000000000000000052731363064711000205130ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest from test.utils import create_object_attribute_test from pyxrd.data import settings from pyxrd.phases.models import Phase from pyxrd.file_parsers.phase_parsers import JSONPhaseParser __all__ = [ 'TestPhase', ] class TestPhase(unittest.TestCase): phase = None def setUp(self): settings.initialize() self.phase = Phase(R=0, G=1) def tearDown(self): del self.phase def test_not_none(self): self.assertIsNotNone(self.phase) def test_data_object(self): self.assertIsNotNone(self.phase.data_object) def test_R_G(self): self.assertIsNotNone(Phase(R=0, G=1)) self.assertIsNotNone(Phase(R=0, G=2)) self.assertIsNotNone(Phase(R=0, G=3)) self.assertIsNotNone(Phase(R=0, G=4)) self.assertIsNotNone(Phase(R=0, G=5)) self.assertIsNotNone(Phase(R=0, G=6)) self.assertIsNotNone(Phase(R=1, G=2)) self.assertIsNotNone(Phase(R=1, G=3)) self.assertIsNotNone(Phase(R=1, G=4)) self.assertIsNotNone(Phase(R=2, G=2)) self.assertIsNotNone(Phase(R=2, G=3)) self.assertIsNotNone(Phase(R=3, G=2)) test_name = create_object_attribute_test("phase", "name", "Test Name") test_display_color = create_object_attribute_test("phase", "display_color", "#FF00FF") test_default_c = create_object_attribute_test("phase", "default_c", 0.646) test_sigma_star = create_object_attribute_test("phase", "sigma_star", 12.5) test_inherit_display_color = create_object_attribute_test("phase", "inherit_display_color", True) test_inherit_CSDS_distribution = create_object_attribute_test("phase", "inherit_CSDS_distribution", True) test_inherit_sigma_star = create_object_attribute_test("phase", "inherit_sigma_star", True) test_inherit_probabilities = create_object_attribute_test("phase", "inherit_probabilities", True) def test_import_export(self): from io import BytesIO phases = [Phase(R=0, G=1), Phase(R=1, G=2)] fn = BytesIO() Phase.save_phases(phases, filename=fn) loaded_phases = list(JSONPhaseParser.parse(fn)) def strip_uuid(data): new_data = [] for line in data.split('\n'): if "uuid" not in line: new_data.append(line) return "\n".join(new_data) outp1 = [strip_uuid(phase.dump_object()) for phase in phases] outp2 = [strip_uuid(phase.dump_object()) for phase in loaded_phases] self.assertEqual(outp1, outp2) pass # end of class PyXRD-0.8.4/test/test_phases/test_unit_cell_property.py000066400000000000000000000043571363064711000233370ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest from test.utils import create_object_attribute_test from pyxrd.phases.models.unit_cell_prop import UnitCellProperty __all__ = [ 'TestPhase', ] class TestPhase(unittest.TestCase): phase = None def setUp(self): self.ucp = UnitCellProperty( name="TestUCP", value=0.0, enabled=False, factor=0.0, constant=0.0, prop=None, parent=None ) def tearDown(self): del self.ucp def test_not_none(self): self.assertIsNotNone(self.ucp) def test_value_of_prop(self): class Dummy(): attribute = "Test123" dummy = Dummy() self.ucp.prop = (dummy, "attribute") self.assertEqual(self.ucp.get_value_of_prop(), dummy.attribute) self.ucp.prop = (None, "attribute") self.assertEqual(self.ucp.get_value_of_prop(), 0.0) def test_update_value(self): class Dummy(): attribute = 0.5 dummy = Dummy() self.ucp.prop = (dummy, "attribute") self.ucp.factor = 0.5 self.ucp.constant = 1.0 #Check that if the prop is disabled, it can be set manually: self.ucp.value = 0.075 self.assertEqual(self.ucp.value, 0.075) #Check that if the prop is enabled, it is calculated automatically: self.ucp.enabled = True self.assertEqual(self.ucp.value, 0.5 * 0.5 + 1.0) #Check that if the prop is enabled, it can't be set manually: self.ucp.value = 0.075 self.assertNotEqual(self.ucp.value, 0.075) test_name = create_object_attribute_test("ucp", "name", "Test Name") test_name = create_object_attribute_test("ucp", "value", 0.5) test_name = create_object_attribute_test("ucp", "factor", 0.5) test_name = create_object_attribute_test("ucp", "constant", 0.5) test_name = create_object_attribute_test("ucp", "prop", (None, "")) test_name = create_object_attribute_test("ucp", "enabled", True) test_name = create_object_attribute_test("ucp", "inherited", True) pass # end of class PyXRD-0.8.4/test/test_probabilities/000077500000000000000000000000001363064711000173405ustar00rootroot00000000000000PyXRD-0.8.4/test/test_probabilities/__init__.py000066400000000000000000000000001363064711000214370ustar00rootroot00000000000000PyXRD-0.8.4/test/test_probabilities/base.py000066400000000000000000000010071363064711000206220ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. __all__ = [ 'AbstractTestProbModel', ] class AbstractTestProbModel(): prob_model = None prob_model_type = None def setUp(self): self.prob_model = self.prob_model_type() def tearDown(self): del self.prob_model def test_not_none(self): self.assertIsNotNone(self.prob_model) pass # end of class PyXRD-0.8.4/test/test_probabilities/test_R0Models.py000066400000000000000000000042631363064711000224030ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .base import AbstractTestProbModel from test.utils import create_object_attribute_test from pyxrd.probabilities.models.R0models import * import unittest __all__ = [ 'TestR0G1Model', 'TestR0G2Model', 'TestR0G3Model', 'TestR0G4Model', 'TestR0G5Model', 'TestR0G6Model' ] class TestR0G1Model(AbstractTestProbModel, unittest.TestCase): prob_model_type = R0G1Model pass # end of class class TestR0G2Model(AbstractTestProbModel, unittest.TestCase): prob_model_type = R0G2Model test_F1 = create_object_attribute_test("prob_model", "F1", 0.7) pass # end of class class TestR0G3Model(AbstractTestProbModel, unittest.TestCase): prob_model_type = R0G3Model test_F1 = create_object_attribute_test("prob_model", "F1", 0.7) test_F2 = create_object_attribute_test("prob_model", "F2", 0.7) pass # end of class class TestR0G4Model(AbstractTestProbModel, unittest.TestCase): prob_model_type = R0G4Model test_F1 = create_object_attribute_test("prob_model", "F1", 0.7) test_F2 = create_object_attribute_test("prob_model", "F2", 0.7) test_F3 = create_object_attribute_test("prob_model", "F3", 0.7) pass # end of class class TestR0G5Model(AbstractTestProbModel, unittest.TestCase): prob_model_type = R0G5Model test_F1 = create_object_attribute_test("prob_model", "F1", 0.7) test_F2 = create_object_attribute_test("prob_model", "F2", 0.7) test_F3 = create_object_attribute_test("prob_model", "F3", 0.7) test_F4 = create_object_attribute_test("prob_model", "F4", 0.7) pass # end of class class TestR0G6Model(AbstractTestProbModel, unittest.TestCase): prob_model_type = R0G6Model test_F1 = create_object_attribute_test("prob_model", "F1", 0.7) test_F2 = create_object_attribute_test("prob_model", "F2", 0.7) test_F3 = create_object_attribute_test("prob_model", "F3", 0.7) test_F4 = create_object_attribute_test("prob_model", "F4", 0.7) test_F5 = create_object_attribute_test("prob_model", "F5", 0.7) pass # end of class PyXRD-0.8.4/test/test_probabilities/test_R1Models.py000066400000000000000000000042241363064711000224010ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .base import AbstractTestProbModel from test.utils import create_object_attribute_test from pyxrd.probabilities.models.R1models import * import unittest __all__ = [ 'TestR1G2Model', 'TestR1G3Model', 'TestR1G4Model', ] class TestR1G2Model(AbstractTestProbModel, unittest.TestCase): prob_model_type = R1G2Model test_W1 = create_object_attribute_test("prob_model", "W1", 0.7) test_P11_or_P22 = create_object_attribute_test("prob_model", "P11_or_P22", 0.7) pass # end of class class TestR1G3Model(AbstractTestProbModel, unittest.TestCase): prob_model_type = R1G3Model test_W1 = create_object_attribute_test("prob_model", "W1", 0.7) test_P11_or_P22 = create_object_attribute_test("prob_model", "P11_or_P22", 0.7) test_G1 = create_object_attribute_test("prob_model", "G1", 0.7) test_G2 = create_object_attribute_test("prob_model", "G2", 0.7) test_G3 = create_object_attribute_test("prob_model", "G3", 0.7) test_G4 = create_object_attribute_test("prob_model", "G4", 0.7) pass # end of class class TestR1G4Model(AbstractTestProbModel, unittest.TestCase): prob_model_type = R1G4Model test_W1 = create_object_attribute_test("prob_model", "W1", 0.7) test_P11_or_P22 = create_object_attribute_test("prob_model", "P11_or_P22", 0.7) test_R1 = create_object_attribute_test("prob_model", "R1", 0.7) test_R2 = create_object_attribute_test("prob_model", "R2", 0.7) test_G1 = create_object_attribute_test("prob_model", "G1", 0.7) test_G2 = create_object_attribute_test("prob_model", "G2", 0.7) test_G11 = create_object_attribute_test("prob_model", "G11", 0.7) test_G12 = create_object_attribute_test("prob_model", "G12", 0.7) test_G21 = create_object_attribute_test("prob_model", "G21", 0.7) test_G22 = create_object_attribute_test("prob_model", "G22", 0.7) test_G31 = create_object_attribute_test("prob_model", "G31", 0.7) test_G22 = create_object_attribute_test("prob_model", "G32", 0.7) pass # end of class PyXRD-0.8.4/test/test_probabilities/test_R2Models.py000066400000000000000000000025361363064711000224060ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .base import AbstractTestProbModel from test.utils import create_object_attribute_test from pyxrd.probabilities.models.R2models import * import unittest __all__ = [ 'TestR2G2Model', 'TestR2G3Model', ] class TestR2G2Model(AbstractTestProbModel, unittest.TestCase): prob_model_type = R2G2Model test_W1 = create_object_attribute_test("prob_model", "W1", 0.7) test_P112_or_P211 = create_object_attribute_test("prob_model", "P112_or_P211", 0.7) test_P21 = create_object_attribute_test("prob_model", "P21", 0.7) test_P122_or_P221 = create_object_attribute_test("prob_model", "P122_or_P221", 0.7) pass # end of class class TestR2G3Model(AbstractTestProbModel, unittest.TestCase): prob_model_type = R2G3Model test_W1 = create_object_attribute_test("prob_model", "W1", 0.7) test_P111_or_P212 = create_object_attribute_test("prob_model", "P111_or_P212", 0.7) test_G1 = create_object_attribute_test("prob_model", "G1", 0.7) test_G2 = create_object_attribute_test("prob_model", "G2", 0.7) test_G3 = create_object_attribute_test("prob_model", "G3", 0.7) test_G4 = create_object_attribute_test("prob_model", "G4", 0.7) pass # end of class PyXRD-0.8.4/test/test_probabilities/test_R3Models.py000066400000000000000000000012071363064711000224010ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. from .base import AbstractTestProbModel from test.utils import create_object_attribute_test from pyxrd.probabilities.models.R3models import * import unittest __all__ = [ 'TestR3G2Model' ] class TestR3G2Model(AbstractTestProbModel, unittest.TestCase): prob_model_type = R3G2Model test_W1 = create_object_attribute_test("prob_model", "W1", 0.7) test_P1111_or_P2112 = create_object_attribute_test("prob_model", "P1111_or_P2112", 0.7) pass # end of class PyXRD-0.8.4/test/test_project/000077500000000000000000000000001363064711000161565ustar00rootroot00000000000000PyXRD-0.8.4/test/test_project/__init__.py000066400000000000000000000000001363064711000202550ustar00rootroot00000000000000PyXRD-0.8.4/test/test_project/test_project.py000066400000000000000000000061271363064711000212430ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import unittest from test.utils import create_object_attribute_test, mock_settings from pyxrd.project.models import Project __all__ = [ 'TestProject', ] class TestProject(unittest.TestCase): project = None def setUp(self): mock_settings(); self.project = Project(name="Test Project") def tearDown(self): del self.project def test_not_none(self): self.assertIsNotNone(self.project) test_name = create_object_attribute_test("project", "name", "Test Name") test_date = create_object_attribute_test("project", "date", "19/09/1987") test_description = create_object_attribute_test("project", "description", "Test Description") test_author = create_object_attribute_test("project", "author", "Test Author") test_layout_mode = create_object_attribute_test("project", "layout_mode", "FULL") test_display_marker_align = create_object_attribute_test("project", "display_marker_align", "right") test_display_marker_color = create_object_attribute_test("project", "display_marker_color", "#FF00FF") test_display_marker_base = create_object_attribute_test("project", "display_marker_base", 2) test_display_marker_top = create_object_attribute_test("project", "display_marker_top", 1) test_display_marker_top_offset = create_object_attribute_test("project", "display_marker_top_offset", 0.5) test_display_marker_angle = create_object_attribute_test("project", "display_marker_angle", 45.6) test_display_marker_style = create_object_attribute_test("project", "display_marker_style", "dashed") test_display_calc_color = create_object_attribute_test("project", "display_calc_color", "#FF0099") test_display_exp_color = create_object_attribute_test("project", "display_exp_color", "#9900FF") test_display_calc_lw = create_object_attribute_test("project", "display_calc_lw", 5) test_display_exp_lw = create_object_attribute_test("project", "display_exp_lw", 1) test_display_plot_offset = create_object_attribute_test("project", "display_plot_offset", 1.5) test_display_group_by = create_object_attribute_test("project", "display_group_by", 3) test_display_label_pos = create_object_attribute_test("project", "display_label_pos", 0.75) test_axes_xscale = create_object_attribute_test("project", "axes_xscale", 1) test_axes_xmin = create_object_attribute_test("project", "axes_xmin", 15) test_axes_xmax = create_object_attribute_test("project", "axes_xmax", 52) test_axes_xstretch = create_object_attribute_test("project", "axes_xstretch", True) test_axes_yscale = create_object_attribute_test("project", "axes_yscale", 1) test_axes_yvisible = create_object_attribute_test("project", "axes_yvisible", True) # TODO # - addition of phases, specimens & atom_types # - loading of phases, specimens & atom_types # - testing inherit properties (markers) # - testing initialization deprecated keywords etc. pass # end of class PyXRD-0.8.4/test/utils.py000066400000000000000000000020721363064711000151640ustar00rootroot00000000000000#!/usr/bin/python # coding=UTF-8 # ex:ts=4:sw=4:et=on # Copyright (c) 2013, Mathijs Dumon # All rights reserved. # Complete license can be found in the LICENSE file. import time import mock import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk def create_object_attribute_test(object_name, attribute, value): """ Helper function to create simple attribute setter/getter tests. """ def test_attribute(self): obj = getattr(self, object_name) setattr(obj, attribute, value) self.assertEqual(getattr(obj, attribute), value) return test_attribute # Stolen from Kiwi def refresh_gui(delay=0): while Gtk.events_pending(): Gtk.main_iteration_do(block=False) time.sleep(delay) def _mocked_parse_args(): args = mock.Mock() args.script.return_value = True args.script.filename = "" args.script.debug = False return args def mock_settings(): from pyxrd.data import settings settings._parse_args = mock.Mock(return_value=_mocked_parse_args()) settings.initialize() PyXRD-0.8.4/tests_mvc/000077500000000000000000000000001363064711000145015ustar00rootroot00000000000000PyXRD-0.8.4/tests_mvc/__init__.py000066400000000000000000000025351363064711000166170ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2014 by Mathijs Dumon # Copyright (C) 2005 by Roberto Cavada # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import unittest def run_all_tests(*args, **kwargs): all_tests = get_all_tests() unittest.TextTestRunner().run(all_tests) def get_all_tests(): return unittest.TestLoader().discover('.') PyXRD-0.8.4/tests_mvc/adapters/000077500000000000000000000000001363064711000163045ustar00rootroot00000000000000PyXRD-0.8.4/tests_mvc/adapters/__init__.py000066400000000000000000000000001363064711000204030ustar00rootroot00000000000000PyXRD-0.8.4/tests_mvc/adapters/gtk_support/000077500000000000000000000000001363064711000206655ustar00rootroot00000000000000PyXRD-0.8.4/tests_mvc/adapters/gtk_support/__init__.py000066400000000000000000000000001363064711000227640ustar00rootroot00000000000000PyXRD-0.8.4/tests_mvc/adapters/gtk_support/dialogs/000077500000000000000000000000001363064711000223075ustar00rootroot00000000000000PyXRD-0.8.4/tests_mvc/adapters/gtk_support/dialogs/__init__.py000066400000000000000000000000001363064711000244060ustar00rootroot00000000000000PyXRD-0.8.4/tests_mvc/adapters/gtk_support/dialogs/dialog_factory.py000066400000000000000000000051451363064711000256540ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2016 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import unittest from mvc.adapters.gtk_support.dialogs.dialog_factory import DialogFactory from .file_chooser_dialog_args import get_file_chooser_kwags __all__ = [ 'DialogFactoryTest', ] class DialogFactoryTest(unittest.TestCase): def setUp(self): pass def tearDown(self): pass def test_get_file_dialog(self): kwargs = get_file_chooser_kwags() dialog = DialogFactory.get_file_dialog(**kwargs) self.assertEqual(dialog.get_action(), kwargs["action"], "Action attribute is not set correctly") self.assertEqual(dialog.get_title(), kwargs["title"], "Title attribute is not set correctly") self.assertEqual(dialog.get_parent(), kwargs["parent"], "Parent window is not set correctly") self.assertEqual(dialog.get_current_name(), kwargs["current_name"], "Current name attribute is not set correctly") self.assertEqual(dialog.get_current_folder(), kwargs["current_folder"], "Current folder attribute is not set correctly") self.assertEqual(dialog.get_extra_widget(), kwargs["extra_widget"], "Extra widget attribute is not set correctly") self.assertEqual(dialog.filters, kwargs["filters"], "Filters attribute is not set correctly") self.assertEqual(dialog.get_select_multiple(), kwargs["multiple"], "Multiple attribute is not set correctly") self.assertEqual(dialog.get_do_overwrite_confirmation(), kwargs["confirm_overwrite"], "Confirm overwrite attribute is not set correctly") if __name__ == "__main__": unittest.main() PyXRD-0.8.4/tests_mvc/adapters/gtk_support/dialogs/file_chooser_dialog.py000066400000000000000000000053271363064711000266500ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2016 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import unittest from mvc.adapters.gtk_support.dialogs.file_chooser_dialog import FileChooserDialog from .file_chooser_dialog_args import get_file_chooser_kwags from mock.mock import Mock __all__ = [ 'FileChooserDialogTest', ] class FileChooserDialogTest(unittest.TestCase): def setUp(self): self.kwargs = get_file_chooser_kwags() self.dialog = FileChooserDialog(**self.kwargs) def tearDown(self): pass def test_file_dialog_selected_globs(self): # Set filter: filter = self.dialog.list_filters()[0] #this is a *.txt filter, at least it should be... @ReservedAssignment self.dialog.set_filter(filter) #Act sel_globs = self.dialog.selected_globs #Assert self.assertEqual(filter.get_name(), "Text File") self.assertEqual(sel_globs, ["*.txt"]) def test_file_dialog_applies_filename_filters(self): no_ext_name = "test name without extension" # Set filter: filter = self.dialog.list_filters()[0] #this is a *.txt filter, at least it should be... @ReservedAssignment self.dialog.set_filter(filter) # Mock current filename self.dialog.get_filename = Mock(return_value=no_ext_name) # Assert we are working with the filter we expect self.assertEqual(filter.get_name(), "Text File") # Assert name is changed correctly self.assertEqual("%s.txt" % no_ext_name, self.dialog.filename) self.dialog.get_filename.assert_called_once_with() if __name__ == "__main__": unittest.main()PyXRD-0.8.4/tests_mvc/adapters/gtk_support/dialogs/file_chooser_dialog_args.py000066400000000000000000000032311363064711000276540ustar00rootroot00000000000000# coding=UTF-8 # ex:ts=4:sw=4:et=on # ------------------------------------------------------------------------- # Copyright (C) 2016 by Mathijs Dumon # # mvc is a framework derived from the original pygtkmvc framework # hosted at: # # mvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # mvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110, USA. # ------------------------------------------------------------------------- import os import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from pyxrd.generic.io.utils import get_case_insensitive_glob def get_file_chooser_kwags(): return dict( action=Gtk.FileChooserAction.SAVE, title="The dialog title", parent=Gtk.Window(), current_name="suggested_file_name", current_folder=os.path.expanduser("~"), extra_widget=Gtk.Label(label="Test Label"), filters=[ ("Text File", get_case_insensitive_glob("*.txt")) ], multiple=False, confirm_overwrite=True ) PyXRD-0.8.4/versioning.py000066400000000000000000000030471363064711000152330ustar00rootroot00000000000000#!/usr/bin/env python2 """ Git Versioning Script Will transform stdin to expand some keywords with git version/author/date information. Specify --clean to remove this information before commit. Setup: 1. Copy versioning.py into your git repository 2. Run: git config filter.versioning.smudge 'python versioning.py' git config filter.versioning.clean 'python versioning.py --clean' echo 'version.py filter=versioning' >> .gitattributes git add versioning.py 3. add a version.py file with this contents: __version__ = "" """ import sys import subprocess import re def main(): clean = False if len(sys.argv) > 1: if sys.argv[1] == '--clean': clean = True # initialise empty here. Otherwise: forkbomb through the git calls. subst_list = { "version": "", } for line in sys.stdin: if not clean: subst_list = { # '--dirty' could be added to the following, too, but is not supported everywhere "version": subprocess.check_output(['git', 'describe', '--always']), } for k, v in subst_list.iteritems(): v = re.sub(r'[\n\r\t"\']', "", v) rexp = "__%s__\s*=[\s'\"]+" % k line = re.sub(rexp, "__%s__ = \"%s\"\n" % (k, v), line) sys.stdout.write(line) else: for k in subst_list: rexp = "__%s__\s*=.*" % k line = re.sub(rexp, "__%s__ = \"\"" % k, line) sys.stdout.write(line) if __name__ == "__main__": main() PyXRD-0.8.4/win_installer/000077500000000000000000000000001363064711000153445ustar00rootroot00000000000000PyXRD-0.8.4/win_installer/README.rst000066400000000000000000000055111363064711000170350ustar00rootroot00000000000000=============================== Windows Installer Build Scripts =============================== We use `msys2 `__ for creating the Windows installer and development on Windows. The setup was modified from `QuodLibet ` to our needs. For those interested, the main changes were related to fixing dependencies and restraining some over-zealous clean up actions before packaging. These resulted in missing python modules (i.e. numpy.testing and matplotlib.testing). Development ----------- For developing on Windows you have two choices. 1) Just use an existing PyXRD installation plus a git checkout: * Clone the git repo with some git client * Download and install the latest installer build: https://github.com/mathijs-dumon/pyxrd/releases/download/latest/pyxrd-latest-installer.exe * Go to pyxrd.py in the git checkout and run ``%PROGRAMFILES(X86)%\PyXRD\bin\python.exe pyxrd.py``. 2) Use proper msys2 environment Setting Up the MSYS2 Environment -------------------------------- * Download msys2 64-bit from https://msys2.github.io/ * Follow instructions on https://msys2.github.io/ * Execute ``C:\msys64\mingw32.exe`` * Run ``pacman -S git`` to install git * Run ``git clone https://github.com/mathijs-dumon/pyxrd.git`` * Run ``cd pyxrd/win_installer`` to end up where this README exists. * Execute ``./bootstrap.sh`` to install all the needed dependencies. * Now go to the git root ``cd ..`` * To run PyXRD execute ``python3 -m pyxrd`` Creating an Installer --------------------- Simply run ``./build.sh [git-tag]`` and both the installer and the portable installer should appear in this directory. You should occasionally update the matplotlib package to a more recent version (see below). You can pass a git tag ``./build.sh release-3.8.0`` to build a specific tag or pass nothing to build master. Note that it will clone from this repository and not from github so any commits present locally will be cloned as well. Updating MSYS2 matplotlib ------------------------- The default MSYS2 matplotlib package is compiled against Qt and not GTK. This installs a bunch of unneeded stuff and also means that matplotlib won't have GTK support. A modified build file is available at misc/mingw-w64-python-matplotlib. To build this: * Run ``cd pyxrd/win_installer/misc/mingw-w64-python-matplotlib`` * Run ``makepkg`` * Run ``cp mingw-w64-i686-python3-matplotlib-*-any.pkg.tar ../`` Updating an Existing Installer ------------------------------ We directly follow msys2 upstream so building the installer two weeks later might result in newer versions of dependencies being used. To reduce the risk of stable release breakage you can use an existing installer and just install a newer PyXRD version into it and then repack it. ``./rebuild.sh pyxrd-3.8.0-installer.exe [git-tag]`` PyXRD-0.8.4/win_installer/_base.sh000066400000000000000000000251431363064711000167560ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2018 Mathijs Dumon # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # Exit when any process fails set -e # Get directory of filename, move into it and return the current dir DIR="$( cd "$( dirname "$0" )" && pwd )" cd "${DIR}" # CONFIG START ARCH="i686" BUILD_VERSION="0" # CONFIG END MISC="${DIR}"/misc if [ "${ARCH}" = "x86_64" ]; then MINGW="mingw64" else MINGW="mingw32" fi PYXRD_VERSION="0.0.0" PYXRD_VERSION_DESC="UNKNOWN" function set_build_root { BUILD_ROOT="$1" REPO_CLONE="${BUILD_ROOT}"/pyxrd MINGW_ROOT="${BUILD_ROOT}/${MINGW}" } set_build_root "${DIR}/_build_root" function build_pacman { pacman --root "${BUILD_ROOT}" "$@" } function build_pip { "${BUILD_ROOT}"/"${MINGW}"/bin/python3.exe -m pip "$@" } function build_python { "${BUILD_ROOT}"/"${MINGW}"/bin/python3.exe "$@" } function build_compileall { MSYSTEM= build_python -m compileall -b "$@" } function install_pre_deps { pacman -S --needed --noconfirm p7zip git dos2unix \ mingw-w64-"${ARCH}"-nsis wget intltool mingw-w64-"${ARCH}"-toolchain } function create_root { mkdir -p "${BUILD_ROOT}" mkdir -p "${BUILD_ROOT}"/var/lib/pacman mkdir -p "${BUILD_ROOT}"/var/log mkdir -p "${BUILD_ROOT}"/tmp build_pacman -Syu build_pacman --noconfirm -S base } function extract_installer { [ -z "$1" ] && (echo "Missing arg"; exit 1) mkdir -p "$BUILD_ROOT" 7z x -o"$MINGW_ROOT" "$1" rm -rf "$MINGW_ROOT"/'$PLUGINSDIR' "$MINGW_ROOT"/*.txt "$MINGW_ROOT"/*.nsi } function install_deps { # We don't use the fontconfig backend, and this skips the lengthy # cache update step during package installation export MSYS2_FC_CACHE_SKIP=1 build_pacman --noconfirm -S \ git \ mingw-w64-"${ARCH}"-gdk-pixbuf2 \ mingw-w64-"${ARCH}"-gtk3 \ mingw-w64-"${ARCH}"-python3 \ mingw-w64-"${ARCH}"-python3-setuptools \ mingw-w64-"${ARCH}"-python3-gobject \ mingw-w64-"${ARCH}"-python3-cffi \ mingw-w64-"${ARCH}"-python3-cairo \ mingw-w64-"${ARCH}"-python3-pip \ mingw-w64-"${ARCH}"-python3-pytest \ mingw-w64-"${ARCH}"-python3-numpy \ mingw-w64-"${ARCH}"-python3-scipy \ mingw-w64-"${ARCH}"-python3-dateutil \ mingw-w64-"${ARCH}"-python3-pyparsing \ mingw-w64-"${ARCH}"-python3-cycler \ mingw-w64-"${ARCH}"-python3-kiwisolver \ mingw-w64-"${ARCH}"-freetype \ mingw-w64-"${ARCH}"-libpng \ mingw-w64-"${ARCH}"-qhull \ mingw-w64-"${ARCH}"-python3-matplotlib PIP_REQUIREMENTS="\ Pyro4>=4.41 deap>=1.0.1 setuptools cairocffi" build_pip install --upgrade --no-deps \ --force-reinstall $(echo "$PIP_REQUIREMENTS" | tr ["\\n"] [" "]) build_pacman --noconfirm -Rdds \ mingw-w64-"${ARCH}"-shared-mime-info \ mingw-w64-"${ARCH}"-tk \ mingw-w64-"${ARCH}"-tcl build_pacman --noconfirm -Rdds mingw-w64-"${ARCH}"-python2 || true build_pacman -S --noconfirm mingw-w64-"${ARCH}"-python3-setuptools } function install_pyxrd { [ -z "$1" ] && (echo "Missing arg"; exit 1) rm -Rf "${REPO_CLONE}" git clone "${DIR}"/.. "${REPO_CLONE}" # checkout correct version (cd "${REPO_CLONE}" && git checkout "$1") || exit 1 # install it (cd "${REPO_CLONE}" && build_pip install . || exit 1) #build_python "${REPO_CLONE}"/setup.py install # Create launchers PYXRD_VERSION=$(MSYSTEM= build_python -c \ "from pyxrd import __version__; import sys; sys.stdout.write(__version__)") PYXRD_VERSION_DESC="$PYXRD_VERSION" if [ "$1" = "master" ] then local GIT_REV=$(git rev-list --count HEAD) local GIT_HASH=$(git rev-parse --short HEAD) PYXRD_VERSION_DESC="$PYXRD_VERSION-rev$GIT_REV-$GIT_HASH" fi python3 "${MISC}"/create-launcher.py \ "${PYXRD_VERSION}" "${MINGW_ROOT}"/bin # Copy over theme: cp -Rf "$MISC"/gtk-3.0 "$MINGW_ROOT"/share/ cp -Rf "$MISC"/themes "$MINGW_ROOT"/share/ "${MINGW_ROOT}"/bin/gtk-update-icon-cache-3.0.exe \ "${MINGW_ROOT}"/share/themes/Windows8 # Compile python files build_compileall -d "" -f -q "$(cygpath -w "${MINGW_ROOT}")" } function cleanup_before { ## these all have svg variants #find "${MINGW_ROOT}"/share/icons -name "*.symbolic.png" -exec rm -f {} \; ## remove some larger ones rm -Rf "${MINGW_ROOT}/share/icons/Adwaita/512x512" rm -Rf "${MINGW_ROOT}/share/icons/Adwaita/256x256" rm -Rf "${MINGW_ROOT}/share/icons/Adwaita/96x96" rm -Rf "${MINGW_ROOT}/share/icons/Adwaita/48x48" "${MINGW_ROOT}"/bin/gtk-update-icon-cache-3.0.exe \ "${MINGW_ROOT}"/share/icons/Adwaita # remove some gtk demo icons find "${MINGW_ROOT}"/share/icons/hicolor -name "gtk3-*" -exec rm -f {} \; "${MINGW_ROOT}"/bin/gtk-update-icon-cache-3.0.exe \ "${MINGW_ROOT}"/share/icons/hicolor # python related, before installing pyxrd rm -f "${MINGW_ROOT}"/lib/python3.*/lib-dynload/_tkinter* find "${MINGW_ROOT}"/lib/python3.* -type d \( -iname "test*" ! \( -path "*testing*" ! -path "*testing/tests*" \) \) \ -prune -exec rm -rf {} \; find "${MINGW_ROOT}"/lib/python3.* -type d -name "*_test*" \ -prune -exec rm -rf {} \; find "${MINGW_ROOT}"/bin -name "*.pyo" -exec rm -f {} \; find "${MINGW_ROOT}"/bin -name "*.pyc" -exec rm -f {} \; find "${MINGW_ROOT}" -type d -name "__pycache__" -prune -exec rm -rf {} \; build_compileall -d "" -f -q "$(cygpath -w "${MINGW_ROOT}")" find "${MINGW_ROOT}" -name "*.py" -exec rm -f {} \; } function cleanup_after { # delete translations we don't support #for d in "${MINGW_ROOT}"/share/locale/*/LC_MESSAGES; do # if [ ! -f "${d}"/pyxrd.mo ]; then # rm -Rf "${d}" # fi #done find "${MINGW_ROOT}" -regextype "posix-extended" -name "*.exe" -a ! \ -iregex ".*/(pyxrd|python)[^/]*\\.exe" \ -exec rm -f {} \; rm -Rf "${MINGW_ROOT}"/libexec rm -Rf "${MINGW_ROOT}"/share/gtk-doc rm -Rf "${MINGW_ROOT}"/include rm -Rf "${MINGW_ROOT}"/var rm -Rf "${MINGW_ROOT}"/etc rm -Rf "${MINGW_ROOT}"/share/zsh rm -Rf "${MINGW_ROOT}"/share/pixmaps rm -Rf "${MINGW_ROOT}"/share/gnome-shell rm -Rf "${MINGW_ROOT}"/share/dbus-1 rm -Rf "${MINGW_ROOT}"/share/gir-1.0 rm -Rf "${MINGW_ROOT}"/share/doc rm -Rf "${MINGW_ROOT}"/share/man rm -Rf "${MINGW_ROOT}"/share/info rm -Rf "${MINGW_ROOT}"/share/mime rm -Rf "${MINGW_ROOT}"/share/gettext rm -Rf "${MINGW_ROOT}"/share/libtool rm -Rf "${MINGW_ROOT}"/share/licenses rm -Rf "${MINGW_ROOT}"/share/appdata rm -Rf "${MINGW_ROOT}"/share/aclocal rm -Rf "${MINGW_ROOT}"/share/vala rm -Rf "${MINGW_ROOT}"/share/readline rm -Rf "${MINGW_ROOT}"/share/xml rm -Rf "${MINGW_ROOT}"/share/bash-completion rm -Rf "${MINGW_ROOT}"/share/common-lisp rm -Rf "${MINGW_ROOT}"/share/emacs rm -Rf "${MINGW_ROOT}"/share/gdb rm -Rf "${MINGW_ROOT}"/share/libcaca rm -Rf "${MINGW_ROOT}"/share/gettext rm -Rf "${MINGW_ROOT}"/share/libgpg-error rm -Rf "${MINGW_ROOT}"/share/p11-kit rm -Rf "${MINGW_ROOT}"/share/pki rm -Rf "${MINGW_ROOT}"/share/thumbnailers rm -Rf "${MINGW_ROOT}"/share/nghttp2 rm -Rf "${MINGW_ROOT}"/share/fontconfig rm -Rf "${MINGW_ROOT}"/share/gettext-* rm -Rf "${MINGW_ROOT}"/share/installed-tests find "${MINGW_ROOT}"/share/glib-2.0 -type f ! \ -name "*.compiled" -exec rm -f {} \; rm -Rf "${MINGW_ROOT}"/lib/cmake rm -Rf "${MINGW_ROOT}"/lib/gettext rm -Rf "${MINGW_ROOT}"/lib/gtk-3.0 rm -Rf "${MINGW_ROOT}"/lib/p11-kit rm -Rf "${MINGW_ROOT}"/lib/pkcs11 rm -Rf "${MINGW_ROOT}"/lib/ruby rm -Rf "${MINGW_ROOT}"/lib/engines rm -f "${MINGW_ROOT}"/bin/libharfbuzz-icu-0.dll rm -Rf "${MINGW_ROOT}"/lib/python2.* find "${MINGW_ROOT}" -name "*.a" -exec rm -f {} \; find "${MINGW_ROOT}" -name "*.whl" -exec rm -f {} \; find "${MINGW_ROOT}" -name "*.h" -exec rm -f {} \; find "${MINGW_ROOT}" -name "*.la" -exec rm -f {} \; find "${MINGW_ROOT}" -name "*.sh" -exec rm -f {} \; find "${MINGW_ROOT}" -name "*.jar" -exec rm -f {} \; find "${MINGW_ROOT}" -name "*.def" -exec rm -f {} \; find "${MINGW_ROOT}" -name "*.cmd" -exec rm -f {} \; find "${MINGW_ROOT}" -name "*.cmake" -exec rm -f {} \; find "${MINGW_ROOT}" -name "*.pc" -exec rm -f {} \; find "${MINGW_ROOT}" -name "*.desktop" -exec rm -f {} \; find "${MINGW_ROOT}" -name "*.manifest" -exec rm -f {} \; find "${MINGW_ROOT}" -name "*.pyo" -exec rm -f {} \; find "${MINGW_ROOT}"/bin -name "*-config" -exec rm -f {} \; find "${MINGW_ROOT}" -regex ".*/bin/[^.]+" -exec rm -f {} \; find "${MINGW_ROOT}" -regex ".*/bin/[^.]+\\.[0-9]+" -exec rm -f {} \; find "${MINGW_ROOT}" -name "gtk30-properties.mo" -exec rm -rf {} \; find "${MINGW_ROOT}" -name "gettext-tools.mo" -exec rm -rf {} \; find "${MINGW_ROOT}" -name "libexif-12.mo" -exec rm -rf {} \; find "${MINGW_ROOT}" -name "xz.mo" -exec rm -rf {} \; find "${MINGW_ROOT}" -name "libgpg-error.mo" -exec rm -rf {} \; find "${MINGW_ROOT}" -name "old_root.pem" -exec rm -rf {} \; find "${MINGW_ROOT}" -name "weak.pem" -exec rm -rf {} \; find "${MINGW_ROOT}"/bin -name "*.pyo" -exec rm -f {} \; find "${MINGW_ROOT}"/bin -name "*.pyc" -exec rm -f {} \; find "${MINGW_ROOT}" -type d -name "__pycache__" -prune -exec rm -rf {} \; # I think this breaks things for me: build_python "${MISC}/depcheck.py" --delete find "${MINGW_ROOT}" -type d -empty -delete } function build_installer { (cd "${MINGW_ROOT}"/lib/python3.*/site-packages/PyXRD/ && build_compileall -d "" -q -f -l .) cp misc/pyxrd.ico "${BUILD_ROOT}" (cd "$BUILD_ROOT" && makensis -NOCD -DVERSION="$PYXRD_VERSION_DESC" "${MISC}"/win_installer.nsi) mv "$BUILD_ROOT/pyxrd-LATEST.exe" "$DIR/pyxrd-$PYXRD_VERSION_DESC-installer.exe" } function build_portable_installer { (cd "${MINGW_ROOT}"/lib/python3.*/site-packages/PyXRD/ && build_compileall -d "" -q -f -l .) local PORTABLE="$DIR/pyxrd-$PYXRD_VERSION_DESC-portable" rm -rf "$PORTABLE" mkdir "$PORTABLE" cp "$MISC"/pyxrd.lnk "$PORTABLE" cp "$MISC"/README-PORTABLE.txt "$PORTABLE"/README.txt unix2dos "$PORTABLE"/README.txt mkdir "$PORTABLE"/config cp -RT "${MINGW_ROOT}" "$PORTABLE"/data rm -Rf 7zout 7z1604.exe 7z a payload.7z "$PORTABLE" wget -P "$DIR" -c http://www.7-zip.org/a/7z1604.exe 7z x -o7zout 7z1604.exe cat 7zout/7z.sfx payload.7z > "$PORTABLE".exe rm -Rf 7zout 7z1604.exe payload.7z "$PORTABLE" } PyXRD-0.8.4/win_installer/bootstrap.sh000066400000000000000000000023271363064711000177210ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2016 Christoph Reiter # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. set -e function main { pacman --noconfirm -Suy # GTK deps: pacman --noconfirm -S --needed \ git mingw-w64-i686-gdk-pixbuf2 \ mingw-w64-i686-gtk3 \ base-devel mingw-w64-i686-toolchain #Python and related deps: pacman --noconfirm -S --needed \ mingw-w64-i686-python3 \ mingw-w64-i686-python3-setuptools \ mingw-w64-i686-python3-gobject \ mingw-w64-i686-python3-cffi \ mingw-w64-i686-python3-cairo \ mingw-w64-i686-python3-pip \ mingw-w64-i686-python3-pytest \ mingw-w64-i686-python3-numpy \ mingw-w64-i686-python3-scipy \ mingw-w64-i686-python3-dateutil \ mingw-w64-i686-python3-pyparsing \ mingw-w64-i686-python3-cycler \ mingw-w64-i686-python3-kiwisolver \ mingw-w64-i686-python3-matplotlib \ mingw-w64-i686-freetype \ mingw-w64-i686-libpng \ mingw-w64-i686-qhull pip3 install --user cairocffi deap Pyro4\>\=4.41 } main; PyXRD-0.8.4/win_installer/build.sh000066400000000000000000000015171363064711000170030ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2016 Christoph Reiter # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. DIR="$( cd "$( dirname "$0" )" && pwd )" source "$DIR"/_base.sh function main { local GIT_TAG=${1:-"master"} [[ -d "${BUILD_ROOT}" ]] && (echo "${BUILD_ROOT} already exists"; exit 1) # started from the wrong env -> switch if [ $(echo "$MSYSTEM" | tr '[A-Z]' '[a-z]') != "$MINGW" ]; then "/${MINGW}.exe" "$0" exit $? fi install_pre_deps create_root install_deps cleanup_before install_pyxrd "$GIT_TAG" cleanup_after build_installer build_portable_installer } main "$@"; PyXRD-0.8.4/win_installer/misc/000077500000000000000000000000001363064711000162775ustar00rootroot00000000000000PyXRD-0.8.4/win_installer/misc/README-PORTABLE.txt000066400000000000000000000006141363064711000212040ustar00rootroot00000000000000============== PyXRD Portable ============== Content ------- * 'config' contains all user configuration * 'data' contains the program * The links, pyxrd, point to data/bin/.exe How to update to a newer version? --------------------------------- 1) Download and extract the new version 2) Replace the 'config' folder in the new version with the 'config' folder of the older version. PyXRD-0.8.4/win_installer/misc/create-launcher.py000066400000000000000000000123261363064711000217170ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright 2016 Christoph Reiter # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. """Creates simple Python .exe launchers for gui and cli apps ./create-launcher.py "3.8.0" """ import os import sys import subprocess import shlex import tempfile import shutil import struct def build_resource(rc_path, out_path): """Raises subprocess.CalledProcessError""" def is_64bit(): return struct.calcsize("P") == 8 subprocess.check_call( ["windres", "-O", "coff", "-F", "pe-x86-64" if is_64bit() else "pe-i386", rc_path, "-o", out_path]) def get_build_args(): python_name = os.path.splitext(os.path.basename(sys.executable))[0] python_config = os.path.join( os.path.dirname(sys.executable), python_name + "-config") cflags = subprocess.check_output( ["sh", python_config, "--cflags"]).strip() libs = subprocess.check_output( ["sh", python_config, "--libs"]).strip() cflags = os.fsdecode(cflags) libs = os.fsdecode(libs) return shlex.split(cflags) + shlex.split(libs) def build_exe(source_path, resource_path, is_gui, out_path): args = ["gcc", "-s"] if is_gui: args.append("-mwindows") args.append("-municode") args.extend(["-o", out_path, source_path, resource_path]) args.extend(get_build_args()) subprocess.check_call(args) def get_launcher_code(entry_point): module, func = entry_point.split(":", 1) template = """\ #include "Python.h" #define WIN32_LEAN_AND_MEAN #include int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow) { int result; Py_NoUserSiteDirectory = 1; Py_IgnoreEnvironmentFlag = 1; Py_DontWriteBytecodeFlag = 1; Py_Initialize(); PySys_SetArgvEx(__argc, __wargv, 0); result = PyRun_SimpleString("%s"); Py_Finalize(); return result; } """ launch_code = "import sys; from %s import %s; sys.exit(%s())" % ( module, func, func) return template % launch_code def get_resource_code(filename, file_version, file_desc, icon_path, product_name, product_version, company_name): template = """\ 1 ICON "%(icon_path)s" 1 VERSIONINFO FILEVERSION %(file_version_list)s PRODUCTVERSION %(product_version_list)s FILEOS 0x4 FILETYPE 0x1 BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904E4" BEGIN VALUE "CompanyName", "%(company_name)s" VALUE "FileDescription", "%(file_desc)s" VALUE "FileVersion", "%(file_version)s" VALUE "InternalName", "%(internal_name)s" VALUE "OriginalFilename", "%(filename)s" VALUE "ProductName", "%(product_name)s" VALUE "ProductVersion", "%(product_version)s" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END """ def to_ver_list(v): return ",".join(map(str, (list(v.split(".")) + [0] * 4)[:4])) file_version_list = to_ver_list(file_version) product_version_list = to_ver_list(product_version) return template % { "icon_path": icon_path, "file_version_list": file_version_list, "product_version_list": product_version_list, "file_version": file_version, "product_version": product_version, "company_name": company_name, "filename": filename, "internal_name": os.path.splitext(filename)[0], "product_name": product_name, "file_desc": file_desc, } def build_launcher(out_path, icon_path, file_desc, product_name, product_version, company_name, entry_point, is_gui): src_ico = os.path.abspath(icon_path) target = os.path.abspath(out_path) file_version = product_version dir_ = os.getcwd() temp = tempfile.mkdtemp() try: os.chdir(temp) with open("launcher.c", "w") as h: h.write(get_launcher_code(entry_point)) shutil.copyfile(src_ico, "launcher.ico") with open("launcher.rc", "w") as h: h.write(get_resource_code( os.path.basename(target), file_version, file_desc, "launcher.ico", product_name, product_version, company_name)) build_resource("launcher.rc", "launcher.res") build_exe("launcher.c", "launcher.res", is_gui, target) finally: os.chdir(dir_) shutil.rmtree(temp) def main(): argv = sys.argv version = argv[1] target = argv[2] company_name = "Ghent University" product_name = "PyXRD" misc = os.path.dirname(os.path.realpath(__file__)) build_launcher( os.path.join(target, "pyxrd.exe"), os.path.join(misc, "pyxrd.ico"), product_name, product_name, version, company_name, "pyxrd.core:run_main", True) build_launcher( os.path.join(target, "pyxrd-cmd.exe"), os.path.join(misc, "pyxrd.ico"), product_name, product_name, version, company_name, "pyxrd.core:run_main", False) if __name__ == "__main__": main() PyXRD-0.8.4/win_installer/misc/depcheck.py000066400000000000000000000077261363064711000204330ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright 2016,2017 Christoph Reiter # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. """ Deletes unneeded DLLs and checks DLL dependencies. Execute with the build python, will figure out the rest. """ import subprocess import os import sys from multiprocessing import Process, Queue import gi gi.require_version("GIRepository", "2.0") from gi.repository import GIRepository def _get_shared_libraries(q, namespace, version): repo = GIRepository.Repository() repo.require(namespace, version, 0) lib = repo.get_shared_library(namespace) q.put(lib) def get_shared_libraries(namespace, version): # we have to start a new process because multiple versions can't be loaded # in the same process q = Queue() p = Process(target=_get_shared_libraries, args=(q, namespace, version)) p.start() result = q.get() p.join() return result def get_required_by_typelibs(): deps = set() repo = GIRepository.Repository() for tl in os.listdir(repo.get_search_path()[0]): namespace, version = os.path.splitext(tl)[0].split("-", 1) lib = get_shared_libraries(namespace, version) if lib: libs = lib.lower().split(",") else: libs = [] for lib in libs: deps.add((namespace, version, lib)) return deps def get_dependencies(filename): deps = [] try: data = subprocess.check_output(["objdump", "-p", filename], stderr=subprocess.STDOUT) except subprocess.CalledProcessError: # can happen with wrong arch binaries return [] data = data.decode("utf-8") for line in data.splitlines(): line = line.strip() if line.startswith("DLL Name:"): deps.append(line.split(":", 1)[-1].strip().lower()) return deps def find_lib(root, name): system_search_path = os.path.join("C:", os.sep, "Windows", "System32") if get_lib_path(root, name): return True elif os.path.exists(os.path.join(system_search_path, name)): return True elif name in ["gdiplus.dll"]: return True elif name.startswith("msvcr"): return True return False def get_lib_path(root, name): search_path = os.path.join(root, "bin") if os.path.exists(os.path.join(search_path, name)): return os.path.join(search_path, name) def get_things_to_delete(root): extensions = [".exe", ".pyd", ".dll"] all_libs = set() needed = set() for base, dirs, files in os.walk(root): for f in files: lib = f.lower() path = os.path.join(base, f) ext_lower = os.path.splitext(f)[-1].lower() if ext_lower in extensions: if ext_lower == ".exe": # we use .exe as dependency root needed.add(lib) all_libs.add(f.lower()) for lib in get_dependencies(path): all_libs.add(lib) needed.add(lib) if not find_lib(root, lib): print("MISSING:", path, lib) for namespace, version, lib in get_required_by_typelibs(): all_libs.add(lib) needed.add(lib) if not find_lib(root, lib): print("MISSING:", namespace, version, lib) to_delete = [] for not_depended_on in (all_libs - needed): path = get_lib_path(root, not_depended_on) if path: to_delete.append(path) return to_delete def main(argv): libs = get_things_to_delete(sys.prefix) if "--delete" in argv[1:]: while libs: for l in libs: print("DELETE:", l) os.unlink(l) libs = get_things_to_delete(sys.prefix) if __name__ == "__main__": main(sys.argv) PyXRD-0.8.4/win_installer/misc/gtk-3.0/000077500000000000000000000000001363064711000173625ustar00rootroot00000000000000PyXRD-0.8.4/win_installer/misc/gtk-3.0/settings.ini000066400000000000000000000000751363064711000217250ustar00rootroot00000000000000[Settings] gtk-theme-name=Windows8 #gtk-theme-name=FlatStudioPyXRD-0.8.4/win_installer/misc/pyxrd.ico000066400000000000000000006454461363064711000201640ustar00rootroot00000000000000hF 00V( *( ###)))***rrrZRJB:1,'"bYKQ0'" hAP=89niaOI@&!uC aTH80&yWjAN)xrmg`XPJ>7/*% zsh\XM 62)# ~xmcaV;60+$}tkf_ =3.(fW E=5-Bw)]LG<4{xd^SLF?=qke[VLFDvplb\VL|wojb\U( @       "  #! "$##%$($&$'%*')&('&'+')((*)-+')+*/,.0.*,.,-0.302402021132243459973475968;956978:9=:;><8:;?:=;<><B>@=@>>@??A@@BAACAHEAGDFCEDDGEEHFFIGFHLHJIMKFILJPMOLNMLMRRPLNPOORPUSNPSQWSURSXRUSTVUZWSUWV[XTVXWVW[\XZXZ^Z\[_]Y`\^[^\a_Z]`^c_b^_ddb]_b`adbcedhfadfedejehfgihgjhmkfhkinjlikpjmklommpnsoqnotprvqtrwsursxrusxuqsvtzvxuxvvw|vywwzxx{y}xz}{|~{|{~|}~~ĿyqiobaWNMJ?56./&$ {{jfd_ZNLC>?61+& }}upoa^YQLHD<8/(%! |tnha^UNIF?<9./$  }{smfc]YQKFC563+ {uoid^WQJ?B85.-#!! }yqnfe]UOIE>;4/)% }xtoac]YPID?56/)$! wqlfb[UOHF@:3.(#ɘ}tqnfd_\XSPLC>@850/,$!Ža}sooia^WKIF>55/(#Ŀ |}xrng`ZPQHD<42-&"ſ[?`}Pcl APJ/'OFTLxjn&9+ >~XPpo ]|aFT3Q^q^}eMyH5+tʾ>!UgSS@]avt[y5c? D{ wOp>mJGƯɿ}oUQM~}vpqnjfea^ZWRNJIGCA<;941.,KPTZƽv{kie\ž}}ujoaɽzulhƺ}xtozrſ}xƿ(0`»˕xqrȽ~xyiabjbc ʾð׊}~Ӗ2%')žºxrs8,- /&'HBCjgg3'(+LBCe\], ldeҾ͡:.0C:;TMNQJKICDKFFVRRpnnٗ  &ټ揋̍ smnursljjvuuleec[\yrsoghꏋ>24J@A9-.uno A56˽I>?}tu럙 jbcг 1%&A56YQQ¸ۼ$ 䥣===QRRIHH֡*oghƻ !軹MMMLKL߻ȹYYYNNNjjjC89ẶwuuMMMQRRfffddddddijjgggcbbooottthhhiiiϨlllpppqqqnnnpppͱggg```jbcݶUUUYYXiiidddiiiggglllrrrsssrrrjjjsssЭggg___yoqKJJTTTddddddooo|||vvvmllkkkvvv}}}yyyæhhhXXX```^^^^^^JJJdddXXXeeekkkooodddnnngggtttzzzuuuĪhhhܛ^^^bbb\\\[[[[[[|||nnngggtttppptttǮgggLLLSSSKKKhhhoooUUUPPPOOOiiidedooopppzzz|||gggxxx޿qqqsssƮhhhKKKYXXiiiڧOOOHGGȼJJJQQQaaa{{{oooyyy~~~~~~hhhpppyyyǫhhh```YYYiii淵vmn[[[yyyxxxmmmqqqwxwhhhvvv}}}pppjjj___```^^^fff"dddwwweeeuuuϭnnnhhh앏 vvvuuuuuutttvvvwwwvvvvvvuuu˯mmmrqrwww{{zzzz廉*ƹŸ򹶵;./MBDf\^}vvؓީ㼹(zstLAB6)+- "+ -!3&(A57h`a¾z{E:;'  4')e]^ýaYZ-!  <01ŽþLAB!3&(vnoòSHI3&(, 8,.nfgýE:;.!#smnæg_`0#% (_UWĿD9: 1#%xy ODF#(oghVLM6)+smn^WWOGHG?@D:;A78>45<34>56@79B:JCDQJKXRR^YYgbbxttàKAA3&(ĵohh& E:;pijOEF1%')'(&&&&'(+ "-#%/%'1(*5,-901=55@99D<=G@AJDDOIJ\WXpllƨNDE  I>?ſ7*, bZ[g_`8,-)#    !#%') ,!#.#%0&'2(*4+,7-.901;44=66>77@9:B;F?@HABJDEMGHPKKUQQ]YYifg|yz2%'=12ƻ,  6)+) H=>tmo>24%     "$'(* !-#$/$&1')3*,5,-8//912<45>67@99B;>F@AICCKEEMHHOJJQLLTOOXTT^Z[c``pmnȬMBC e]^ŮRHI (_VW}~6)+$phi{vv=12$    #%'(+ ".#%0&'1(*4+,6..8/0:23=55>77A9:C<=E>?GABIDDLFFMHIOJKRMNTOPWRSYUU[WW_[[fccnll~}}ʾyy(5)*.!#&jbc}}0#$9-.K@B(   !$&') ,!#.$&1'(2)*5+-7./:12;34=66@89B;;D=>F@@HBCJEELGGNIJQLLSNOUPQWSSYUV[WX^ZZ`\]c``geepmm}}Ǫ?34!ogiDZVMN  ULMïlde%`WX`XY/"$    "$'(* "-#$/%&1')3*+5,-8/0912;45>67@89B;>G@AICDKEFMHHOJJQLMTOPVQRXSTZVW\XY^[[`]^c``ebbheemjkspq~||˿'8+-ž3&(3'(PFG 0"$|}<12    #%() ,"#.$%1'(2)+5,-7./:01:23<56?78A::C<=E??HABJDDLFGNIIPKKRMNUPPWSSYUU[WW]YY_\\a^_daafccheejhhljjpnnwvv˵UKL "mefȻyrs# jbcŽ8,- OEF_VW- "    !#'+ ".$%2()5+,7-/8/1;24<34>56>66?78@89A:;C==F?@IBBKEEMGHOIJQLMSNOUPQWSTZUV\XX^ZZ`]]b__dabfcdhffjhimklomnrppvst|z{ɢ2%' B67ǫC8:6*,ĸztu)(zuuB78   %.#%6+-?56G>?LDEQIJUNOWPRWPRXQSXRRVPPTNORLLOIILFFICDHABICCLFFNHIPJKRMMTPPVRRXTT[WW]YY_[[a^^c``ebcgdeiggkijnklpmnrpptsswuv{z{̻lcd *”, bY[éTIK  A57}wx8+-  &.!#6+-QHJupqnjk\XXXSSWRSUPQSNNSMNUPPWSTYUV[WX^Z[`\\b^_dabfcdhefjhhljkolmqnosqqutswvvzxy}{{˰D89  PEFȸlce!1#%ž4'(!jbch`a.!# $8-/]UV{|vstebb]YY[VVZUV[VW\XY^Z[a]]b__dabgddiggkiinklpmnropsrruttxvvzxy|{{~~ǡ/#$ 2$&ȰH=>  SHIŴldf!3&(UKM% (B78smnzxxkhheaaa]]`\]b__c`aebcgdeighljjollqnospqtssvuuyxx{zz}||~~zst#'NDE &|}Ǥ1#%%|}>24  XNOE:<$B78{||xylhiieffccgddiffkhimjkpmmrootqrvtuxvwzxy|{{~}}̱F;< LAB=02 VKMʽxqr#2%&ź|uv)*|vw23&~)     %QFH̾wop% OEG9,.6)+PGH$ 7+,~xx~~̤/!#0#%ɥ8+-   #%'))+H<>εQGH#vnoƸrjk% B78¿F;=C89|tv$F:;εQFG&%*/!#4&(7)+9+-:-/;./NCDϮ>24  /"#ħMBC  G==¾|vw8+- %VLMϸSHJ "umnÅ~9,.0"$8+-A46H;=L@BPDEQFGRGH[RS̟?35%   F;<Ǿ4&( H>>`WX,- g^_α=12-!ϰPEF?24J>?VJL`TVf\^kabndeoefrhi̐J>@3%'*$ #umoǶpij% ;/0xy~wxA564'(xrs˦0"$9,.ummTIJ]RSlbcypqz{ύYNPE9;9,./!#& , ĨQGH 'E:;rkl|vwQFH*  9-.ǎ( KAB˝j`aofgz{Ւmde\QRMAB@243%'(   :-.Ü;/1 !&$  ?34Ѿe\] jbcԾ|tuwx۠}~tklcYZRGHC674&() "VLMȼ2$&H>>ѵ?35(̖¾޴{rsh^_UIJC784')(3%'Ƶskl*!QGHϫ0$%2$&׹¾KJJ;;;===>==>>>@??BBBHHHWWWwwwŹvwi_`TIJB574&(E9:ƭd[\'(]STȕ*B67ϫť$$$ !!!%%%%%%%%%%%%%%%$$$%%%'''211ZZZƾ~uvg]^RFGD8:_UVīf]^(-!jbd|tu%`XXٵʧ#""!!!mmmKKK000###+++ihhȿzqrcXYXMOxyĬh``.!" 6)+vpqӾaWX$zz½ͩ$$$111}}}111QPPɽtklkbcŲ{}9,.      %C89ԹH<> *ͪ$$$333䨨;;;^\\ȷ}~wxǼRIJ(       %;./jacԳ8+-/!#ͪ$$$333쭭888%$${zz°|uvB79)     #&'()1#%?34\RSӦ4') 7*,Ω$$$---흝(((333˾ǼOEG7)+2$&2$&4&(6)+:-/@45D8:I=?SHIphi͊2$&  UJLΧ#""HHHUUUXXXddd}}}```ZYYǾǿxzxqruno{st~qhi1#% #|tu˪<;;$$$,,,...///---())*++___333---¿dZ\4')$  , uvv333```lll###lkkdZ[9,.( 2&'¾ᳲ888***'''222e[\=02+  ?34߻℄ MMMGGG! 澼g]^B57/!#"  PDF޼)))+++(((TTT}}}iiiiiiiiihhhhhhhhhhhhhhhiiihhhiiiyyy㮪g]^E9;1#%# d[](((+++555000}}}iiihhhhhhhhhhhhhhhhhhhhhhhhiiihhhiii|||}}}hhhggghhhhhhhhhhhhhhhhhhhhhhhhhhhiiizzzqqqghghhhhhhhhhhhhhhhhhhhhhhhhhhhlll❝mmmggggggggghhghggggggggggggggggghhhppp㈈jjjggggggggggggggggggggggggggggggggghhhjjjmmmrrrxxx¼444---000///////////////000---...ߝe\]G;=3%'$ %{|~vwzqr}}} RRR]]]!!!vvvkkk!!!!!!,,,///////////////000///)))&&&ttt߃+++(((...//////////////////---"""]]]}}}$$$---/////////////////////+++JJJ<<<---000//////////////////---###***FFF)))////////////////////////////////////000//////...---+++)))''''''+++999gggý''' AAA&&&ېdZ[I<>3&($ , sklkab}tuBAA+++))):::𼼼888MMM...,,,444(((OOO111uuu NNN""">>?߂"""DDDXXX@@@;;;"""{{{jjjUUU>>>...)))%%%&&&***000LLLĽ'''***\\\***؎eZ[J=?4')$ 3&'qhibYYtllڛ(((GGGEEE)))撒###111߃###AAAKKKjjj길555!!!uuu%%%ppp---???<<<'''???"""<<>>(((PPP&&&<<<222գIII ...uuu»(((+++aaa***lbcQEG>>eeeº(((+++aaa*+*彸tjkcXYVJLLABF:;A46?24XMNԞ˻>==***jjj ggg虙***666늊%%%:::??? ;;;%%%ttt&&&vvv000@@@FFF'''SSS$$$<<<222}}}777+++++++++++++++...===]]]򴴴999&&&Ǿ(((+++aaa+++ð{qroeff\]_TUYOOXNOxpq۵͓'''JJJ扉,,,555lllMMM```cccmmmLLLbbb___ttt&&&vvv000@@@葑%%%FFF򣣣(((888;;;111kkk(((******)))'''(((***+++)))888{{{,,,333ú(((+++aaa+++z||stwmnyqqǾFEE HHHJJJPPP'''>>> :::'''🟟+++444댌&&&999sss&&&vvv000@@@CCC###TTT""";;;111kkk###mmmuuuBBB&&&!!!>>>ttt SSSǽ(((+++aaa+++ÿʟ&&&666󥥥###***돏&&&VVV飣,,,444(((;;;>>>$$$:::)))sss&&&vvv000@@@똘%%%DDD+++:::;;;111kkk'''ϥSSS&&&)))qqq999&&&¹(((+++aaa***fee%$$xxxQQQ 000,,,hhhLLLbbbRRRddd\\\RRRdddsss&&&vvv000AAA<<<$$$OOOxxx;;;111kkk'''ѕ555"""lllꆆ"""QQQż(((+++aaa)))׵000+++--- :::aaa"""wwwDDD!!!<<<CCC999芊###<<>>}}}놆111rrr&&&vvv״sss444888:::111kkk'''뵵%%%...///kkk(((+++www333((((((((((((((((((***000UUU󱱱+++666񤤤***KKK222,,,666$$$LLL(((111픔"""***rrr&&&vvveee(((,,,:::111kkk'''겲%%%444///nnn(((+++```)))++++++++++++***&&&""")))RRRRRR"""LLL"""ꂂ!!!TTTuuu&&&sss숈 KKKjjjKKKXXXeeerrr&&&vvvOOO000333333333444666:::KKKwwwڑ///---:::111kkk'''榦'''EEE퓓...www(((+++aaa(((ϸAAAAAA񜜜&&&MMM󮮮(((666III)))򵵵+++BBB333++++++444888(((rrr&&&vvv---333;;;;;;:::666000***!!!:::ↆ%%%;;;:::111kkk'''ⓓ)))bbb}}}---(((+++aaa+++TTTccc///777nnn%%%xxx,,,>>>===+++hhh&&&>>>"""쎎'''<<L@B}~焄$$$AAAnnnJJJLLLqqqEEErrrttt&&&vvv𺺺𡡡###999:::111襥888###rrrŽ(((+++䓓+++555}~}tuqije[\WKMI=??24G;=򳳳555---󫫫...333𰰰555,,,111000ttt&&&vvvMMM""":::111ݒ555%%%mmm(((+++▖111'''ⷳ~vwpfgkbce[\]RTTIII=>?248+,E9:NNNpppAAA |||も###???vvvDDDuuu&&&vvvqqq???:::111rrr***(((rrrü(((+++΂///&&&nnnݰjabYNOVKLQEGK@AF9:>027)+2%'B78|||!!!AAAsssNNNWWWjjjIIIwwwuuu&&&vvvvvv&&&777:::111қ===444ž(((+++љFFF"""111ڬ[PQG;=E:;C68>33:./6(*1$&/!#B57򲲲222...𜜜,,,555ﵵ777)))򬬬222...uuu&&&vvvաFFF 777:::111ȀAAA'''!!!HHH((()))ʧuuu@@@%%%###HHHڪQFG;.0:-/8+-6)+4'(1#%.!#- "B67GGGddd???$$$卍$$$<<骪{{{rrrrrrqqqpppppppppqqqqqqqqqqqqqqqxxx妦zzzqqqpppqqqqqqqqqqqqqqqqqqqqqqqqqqqxxxyyyppppppppppppppppppppppppppppppppppppppppppppppppqqqsssxxx݌rrrooooooooooooooooooooooooooooooooopppqqquuuyyy޺MAC5')3&(3&(3&(3%'3%'3%'3&(K@AĿ࿽PEF7*+6)+6)+6)+6)+6)*6)+6)+OEF¾SIJ:-/9,-9,-9,-9,-8,-8,-:./ZPRVKM=02;/0;.0;.0;.0;.0;.0?35h_`¾¾½YOP@45?24>13>13>13>13=13D8:vno¿`VWB67@45@46@46@45A56A46I=>{|޼kacG;I=>I=>I=>I=>H<=VKLֻՎSHJK@AK@AK@AK@AK@AJ?@ZPQսؘWMNNBDNCDNCDNCDNBDMBC_UWۣ\QSPEFQFGQFGQFGQFGQEGd[\ޭ_VWSHITIJTIJTIJTIJUIKj`a¾ඳdZ[VKLWLMVLMVLMWLNXNOofg¿g^^XNOYOPYOPYOPYOP[QRtlllbc[QR\RS\QS\QS\RS_UVyqsqhi^TU^TV^TU^TU_UVcXZ~vxvmobXYaWYaWXaWXbXYf]^{}{suf\^d[\d[\d[\e[\j`aсyzjabh^_g]^g]^h^_mdeӆndekabj`aj`akabqghՋqhinefmdemdeneftkl֑ulnqgipfhpfhpghwnoוxpqtklsiksiksikzrsٚ}tuwnovmovmnvmn}vwڞxyzqrypqxpqxpqyzܤ{||stzrszrs{rs|}ު~vw}uv}uv}uv~౬yzyyyyxx⸴|}||{|z{㽻~~’ȕΘӜ؞ۡܤީఫᶲ㼸ðɴηҺֽ¿PyXRD-0.8.4/win_installer/misc/pyxrd.lnk000066400000000000000000000031141363064711000201520ustar00rootroot00000000000000LF KjKj g,'-PO :i+00/C:\R1B8Windows<:B8*Windowsb2'Y>+ explorer.exeF@ O@ O*explorer.exeF-EC:\Windows\explorer.exeCreated by RelativePrimedata\bin\pyxrd.exe!%SystemRoot%\system32\SHELL32.dll%windir%\explorer.exe%windir%\explorer.exe$ CBg (# 1SPSXFL8C&mm.S-1-5-21-309868742-1720036613-2531456424-1001`Xxy-pcn cqG#^AF'n cqG#^AF'PyXRD-0.8.4/win_installer/misc/themes/000077500000000000000000000000001363064711000175645ustar00rootroot00000000000000PyXRD-0.8.4/win_installer/misc/themes/Windows8/000077500000000000000000000000001363064711000213065ustar00rootroot00000000000000PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/000077500000000000000000000000001363064711000223715ustar00rootroot00000000000000PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/apps/000077500000000000000000000000001363064711000233345ustar00rootroot00000000000000PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/apps/gnome-applications.css000066400000000000000000000715421363064711000276500ustar00rootroot00000000000000@define-color documents_emblem_bg #3465a4; @define-color documents_collection_bg #d3d7cf; @define-color gedit_not_found_bg rgb (237, 54, 54); @define-color gedit_not_found_fg white; * { -GtkIMHtml-hyperlink-color: @link_color; -GtkHTML-link-color: @link_color; -WnckTasklist-fade-overlay-rect: 0; } /**************** * Applications * ****************/ /* * Evolution */ /* needed for webkit/GtkStyle/Evolution compatibility */ GtkHTML:active, GtkHTML:active:backdrop { color: @theme_unfocused_selected_fg_color; background-color: @theme_unfocused_selected_bg_color; } /* * Sushi */ /* used by gnome-font-viewer and sushi */ SushiFontWidget { padding: 6px 12px; } /* * GNOME Terminal */ VteTerminal { background-color: @theme_base_color; color: @theme_fg_color; } TerminalWindow GtkNotebook.notebook { border-bottom-width: 0; border-right-width: 0; border-left-width: 0; } /* * Nautilus */ .nautilus-canvas-item { border-radius: 5px; } .nautilus-desktop.nautilus-canvas-item { color: @theme_selected_fg_color; text-shadow: 1px 1px black; } .nautilus-desktop.nautilus-canvas-item:active { color: @theme_text_color; } .nautilus-desktop.nautilus-canvas-item:selected { color: @theme_selected_fg_color; } .nautilus-desktop.nautilus-canvas-item:active, .nautilus-desktop.nautilus-canvas-item:prelight, .nautilus-desktop.nautilus-canvas-item:selected { text-shadow: none; } .nautilus-desktop.nautilus-canvas-item:selected:backdrop { color: @theme_unfocused_selected_fg_color; } NautilusWindow .sidebar .frame { border-style: none; } NautilusWindow > GtkGrid > .pane-separator, NautilusWindow > GtkGrid > .pane-separator:hover { border-width: 0 1px 0 0; border-style: solid; border-color: @borders; background-color: @sidebar_bg; color: shade (@theme_bg_color, 0.9); } NautilusWindow > GtkGrid > .pane-separator:backdrop, NautilusWindow > GtkGrid > .pane-separator:hover:backdrop { border-color: @unfocused_borders; background-color: @sidebar_bg_unfocused; } NautilusNotebook.notebook { border-right-width: 0; border-left-width: 0; border-bottom-width: 0; } NautilusNotebook .frame { border-width: 0; } NautilusToolbar .button { icon-shadow: 0 1px @button_text_shadow; } NautilusToolbar .button:active { icon-shadow: 0 1px @button_active_text_shadow; } NautilusToolbar .button:insensitive, NautilusToolbar .button:active *:insensitive { icon-shadow: none; } NautilusQueryEditor .search-bar.toolbar { border-top-width: 0; border-bottom-width: 0; } NautilusQueryEditor .toolbar { padding-top: 3px; padding-bottom: 2px; border-width: 1px 0 0 0; border-style: solid; } NautilusQueryEditor .toolbar:nth-child(2) { border-color: @borders; } NautilusQueryEditor .toolbar:last-child, NautilusQueryEditor .search-bar.toolbar:only-child { border-bottom-width: 1px; border-bottom-color: @borders; } NautilusQueryEditor .toolbar:backdrop:nth-child(2) { border-color: @unfocused_borders; } /* * Gedit */ GeditWindow .pane-separator, GeditWindow .pane-separator:hover { border-width: 0 1px 1px 1px; border-style: solid; border-color: @borders; background-color: shade(@theme_bg_color, 0.95); color: @borders; } .gedit-document-panel { background-color: @sidebar_bg; } .gedit-document-panel-group-row, .gedit-document-panel-group-row:hover { border-top: 1px solid shade(@sidebar_bg, 0.90); background-color: @sidebar_bg; } .gedit-document-panel-document-row:hover { background-color: shade(@sidebar_bg, 0.95); } .gedit-document-panel-document-row:selected, .gedit-document-panel-document-row:selected:hover { background-color: @theme_selected_bg_color; } /* sidepane close button styling (copied from the gtk tab close button) */ .gedit-document-panel .list-row .button { color: transparent; border-image: none; background-image: none; background-color: transparent; border-radius: 3px; border-style: solid; border-color: transparent; border-width: 1px; padding: 1px; icon-shadow: none; } .gedit-document-panel .prelight-row .button { color: mix(@theme_fg_color, @sidebar_bg, 0.6); border-color: alpha(black, 0.1); transition: all 200ms ease-in; } .gedit-document-panel .list-row .button:hover, .gedit-document-panel .prelight-row .button:hover { color: @theme_fg_color; border-color: alpha(black, 0.1); transition: all 200ms ease-in; } .gedit-document-panel .prelight-row .button:active { color: @button_active_text; background-color: alpha(black, 0.08); box-shadow: inset 0 1px alpha(black, 0.05); icon-shadow: 0 1px @button_active_text_shadow; border-color: alpha(black, 0.27) alpha(black, 0.13) alpha(black, 0.13) alpha(black, 0.13); } .gedit-document-panel .prelight-row .button:backdrop { color: mix(@theme_unfocused_fg_color, @theme_unfocused_base_color, 0.7); icon-shadow: none; } .gedit-document-panel .prelight-row .button:backdrop:hover { color: @theme_unfocused_fg_color; transition: all 200ms ease-out; } .gedit-document-panel-dragged-row { border: 1px solid @borders; background-color: shade(@sidebar_bg, 0.90); color: @theme_fg_color; } .gedit-document-panel-placeholder-row { border: none; background-color: mix(@sidebar_bg, @theme_selected_bg_color, 0.20); transition: all 200ms ease-in; } GeditStatusbar { border-top: 1px solid @borders; } GeditStatusbar GeditSmallButton, GeditStatusMenuButton { text-shadow: none; } GeditStatusbar GeditSmallButton.button:backdrop, GeditStatusbar GeditSmallButton.button:backdrop:hover, GeditStatusbar GeditSmallButton.button, GeditStatusbar GeditSmallButton.button:hover, GeditStatusbar GeditSmallButton.button:active, GeditStatusbar GeditSmallButton.button:active:hover, GeditStatusMenuButton.button:backdrop, GeditStatusMenuButton.button:backdrop:hover, GeditStatusMenuButton.button, GeditStatusMenuButton.button:hover, GeditStatusMenuButton.button:active, GeditStatusMenuButton.button:active:hover { border-image: none; border-style: solid; border-width: 0 1px; border-radius: 0; padding: 1px 8px 2px 4px; } GeditStatusbar GeditSmallButton.button:hover, GeditStatusbar GeditSmallButton.button:active, GeditStatusbar GeditSmallButton.button:active:hover, GeditStatusMenuButton.button:hover, GeditStatusMenuButton.button:active, GeditStatusMenuButton.button:active:hover { border-color: @borders; } GeditStatusbar GeditSmallButton.button:active, GeditStatusMenuButton.button:active { background-image: linear-gradient(to bottom, @borders, shade(@theme_bg_color, 0.95)); background-color: transparent; color: @theme_selected_fg_color; text-shadow: 0 1px @button_text_shadow; } GeditStatusbar GeditSmallButton.button:backdrop, GeditStatusbar GeditSmallButton.button:backdrop:hover, GeditStatusMenuButton.button:backdrop, GeditStatusMenuButton.button:backdrop:hover { border-color: @unfocused_borders; } GeditViewFrame .gedit-search-slider { background-color: @theme_base_color; padding: 6px; border-color: shade (@notebook_tab_gradient_b, 0.80); border-radius: 0 0 3px 3px; border-width: 0 1px 1px 1px; border-style: solid; } GeditViewFrame .gedit-search-slider .not-found { color: @gedit_not_found_fg; background-image: none; background-color: @gedit_not_found_bg; } GeditViewFrame .gedit-search-slider .not-found:selected { background-color: @theme_selected_bg_color; color: @theme_selected_fg_color; } GeditFileBrowserWidget .toolbar { padding: 3px; border-bottom: 1px solid @borders; box-shadow: inset 0 3px alpha(black, 0.03), inset 0 2px alpha(black, 0.03), inset 0 1px alpha(black, 0.03); background-color: shade(@theme_bg_color, 0.95); } .gedit-search-entry-occurrences-tag { color: shade (@theme_unfocused_fg_color, 0.8); margin: 2px; padding: 2px; } /* * GNOME Documents */ .documents-dropdown, .documents-dropdown .view { background-color: shade (@theme_bg_color, 1.02); } .documents-dropdown.frame { padding: 6px; border-width: 0 1px 1px 1px; border-style: solid; border-radius: 0 0 5px 5px; } .documents-dropdown .view.radio, .documents-dropdown .view.radio:focused, .documents-dropdown .view.radio:selected { background-image: none; background-color: alpha(@theme_base_color, 0.0); } .documents-dropdown .view.radio:active, .documents-dropdown .view.radio:active:focused, .documents-dropdown .view.radio:active:prelight { background-image: url("assets/sidebar-radio-checked.svg"); } .documents-dropdown .view.radio:prelight { background-image: url("assets/sidebar-radio-prelight.svg"); } .documents-dropdown .view.radio:active:selected, .documents-dropdown .view.radio:active:selected:focused { background-image: url("assets/sidebar-radio-selected.svg"); } .documents-dropdown .view.radio:selected:prelight, .documents-dropdown .view.radio:selected:focused { background-image: url("assets/sidebar-radio-selected-prelight.svg"); } .documents-load-more.button { border-image: none; border-color: @borders; border-width: 1px 0 0; border-radius: 0; } .documents-scrolledwin.frame { border-width: 1px 0 0; border-radius: 0; } .documents-icon-bg { background-color: @documents_emblem_bg; border-radius: 4px; color: @theme_base_color; } .documents-collection-icon { background-color: @documents_collection_bg; border-radius: 8px; } .documents-counter { background-image: url('assets/dnd-counter.svg'); background-size: contain; background-color: transparent; color: @theme_base_color; font: bold; } .documents-favorite.button:active, .documents-favorite.button:active:hover { color: shade(@theme_selected_bg_color, 1.20); } .documents-entry-tag { background-color: @entry_tag_bg; color: @entry_tag_fg; border-radius: 4px; border-width: 0; margin: 2px; padding: 4px; } .documents-entry-tag:hover { background-color: shade(@entry_tag_bg, 1.10); color: @entry_tag_fg; } .documents-entry-tag.button, .documents-entry-tag.button:hover, .documents-entry-tag.button:active, .documents-entry-tag.button:active:hover { background-color: transparent; background-image: none; border-image: none; border-width: 0; } .documents-entry-tag.button:hover { color: shade(@entry_tag_bg, 2.10); } /* * Baobab */ .cell.baobab-level-cell, .cell.baobab-level-cell:hover, .cell.baobab-level-cell:selected, .cell.baobab-level-cell:selected:hover { border-color: darker(@borders); border-width: 1px; border-radius: 3px; border-style: solid; background-color: white; } .cell.baobab-level-cell.fill-block, .cell.baobab-level-cell.fill-block:selected, .cell.baobab-level-cell.fill-block:selected:hover { background-color: #edd400; } .cell.baobab-level-cell.fill-block.level-low, .cell.baobab-level-cell.fill-block.level-low:hover { background-color: #73d216; } .cell.baobab-level-cell.fill-block.level-high, .cell.baobab-level-cell.fill-block.level-high:hover { background-color: #cc0000; } .cell.baobab-level-cell.fill-block:backdrop, .cell.baobab-level-cell.fill-block:hover:backdrop, .cell.baobab-level-cell.fill-block.level-low:backdrop, .cell.baobab-level-cell.fill-block.level-high:backdrop { background-color: @theme_unfocused_text_color; } .cell.baobab-cell-error { color: @error_color; } .cell.baobab-cell-warning { color: @warning_color; } .cell.baobab-cell-warning:selected, .cell.baobab-cell-error:selected { color: @theme_selected_fg_color; } .cell.baobab-cell-warning:backdrop, .cell.baobab-cell-error:backdrop, .cell.baobab-cell-warning:selected:backdrop, .cell.baobab-cell-error:selected:backdrop { color: @theme_unfocused_text_color; } BaobabWindow.background GtkStack > GtkGrid > GtkScrolledWindow.frame { border-radius: 0; border-width: 0 1px 0 0; } BaobabWindow GtkInfoBar.warning, BaobabWindow GtkInfoBar.error { border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: @borders; } BaobabRingschart { background-color: @theme_bg_color; padding: 13px 13px 13px 13px; } BaobabRingschart.subfolder-tip { border-radius: 3px; border-style: none; padding: 3px 3px 3px 3px; background-color: alpha(@theme_tooltip_bg_color, 0.90); color: @theme_tooltip_fg_color; text-shadow: 1px 1px black; } /* * Epiphany */ EphyToolbar .entry:first-child, EphyToolbar .entry:focus:first-child, EphyToolbar .entry:backdrop:first-child { border-image-width: 3px 0 4px 3px; border-right-width: 0; border-bottom-right-radius: 0; border-top-right-radius: 0; padding-left: 4px; padding-right: 4px; } EphyToolbar .entry:last-child, EphyToolbar .entry:focus:last-child, EphyToolbar .entry:backdrop:last-child { border-image-width: 3px 3px 4px 0; border-left-width: 0; border-bottom-left-radius: 0; border-top-left-radius: 0; padding-left: 4px; padding-right: 4px; } EphyToolbar .entry:focus { box-shadow: inset 1px 2px alpha(@theme_selected_bg_color, 0.1), inset 1px 1px alpha(@theme_selected_bg_color, 0.1), inset 0 -1px alpha(@theme_selected_bg_color, 0.2); } EphyToolbar .entry:focus:last-child { box-shadow: inset 0 2px alpha(@theme_selected_bg_color, 0.1), inset 0 1px alpha(@theme_selected_bg_color, 0.1), inset -1px -1px alpha(@theme_selected_bg_color, 0.2); } EphyToolbar .location-entry .button { color: @internal_element_color; -GtkButton-child-displacement-y: 0; border-image-source: -gtk-scaled(url("borders/generic-border.png"),url("borders/generic-border@2.png")); border-image-slice: 3 3 4 3; border-image-repeat: stretch; border-width: 1px 1px 2px 1px; border-radius: 3px; padding-left: 4px; padding-right: 4px; box-shadow: inset 1px 0 @inset_dark_color, inset 0 1px @entry_inset, inset 0 2px alpha(@entry_inset, 0.4); } EphyToolbar .location-entry .button:backdrop, EphyToolbar .location-entry .button:backdrop:first-child, EphyToolbar .location-entry .button:backdrop:hover, EphyToolbar .location-entry .button:backdrop:hover:first-child { border-image-source: -gtk-scaled (url("borders/generic-border-backdrop.png"),url("borders/generic-border-backdrop@2.png")); box-shadow: none; background-image: none; background-color: @theme_unfocused_base_color; } EphyToolbar .location-entry .button:last-child { border-image-width: 3px 3px 4px 0; border-left-width: 0; border-bottom-left-radius: 0; border-top-left-radius: 0; } EphyToolbar .location-entry .button:first-child { border-image-width: 3px 0 4px 3px; border-right-width: 0; border-bottom-right-radius: 0; border-top-right-radius: 0; /* flip the box-shadow division*/ box-shadow: inset -1px 0 @inset_dark_color, inset 0 1px @entry_inset, inset 0 2px alpha(@entry_inset, 0.4); } EphyToolbar .location-entry .button, EphyToolbar .location-entry .button:hover { icon-shadow: none; background-image: -gtk-gradient(linear, left top, left bottom, from(@entry_background_a), to(@entry_background_b)); } EphyToolbar .location-entry .button:active, EphyToolbar .location-entry .button:active:hover { background-image: -gtk-gradient(linear, left top, left bottom, from(shade(@entry_background_a, 0.9)), to(@entry_background_b)); } EphyToolbar .location-entry .button:hover, EphyToolbar .location-entry .button:active { color: @theme_text_color; } EphyNotebook.notebook { border-width: 1px 0 1px 0; } EphyNotebook.notebook tab { border-width: 0; } EphyToolbar.toolbar .button { padding-left: 4px; padding-right: 4px; } #ephy-page-menu-button.active-menu { background-image: none; background-color: @menu_bg_color; border-image: none; border-color: @menu_bg_color; border-radius: 4px 4px 0 0; } EphyOverview GtkScrolledWindow { background-color: @theme_base_color; } EphyOverview GtkScrolledWindow:backdrop { background-color: @theme_unfocused_base_color; } /* sets top and bottom borders on the main scrolled window for toolbar visual * division and search/downloadbar */ EphyWindow.background EphyEmbed.vertical GtkScrolledWindow.frame { border-color: @borders; border-width: 1px 0; border-radius: 0; } /* removes any border from the overview scrolled window, since it's overlaid */ EphyWindow.background EphyEmbed.vertical EphyOverview .documents-scrolledwin { border-style: none; } /* remove top and bottom borders from the main scrolled window when inside a notebook tab */ EphyWindow.background EphyNotebook.notebook EphyEmbed.vertical GtkScrolledWindow { border-top-width: 0; border-bottom-width: 0; } /* remove bottom borders from the main scrolled window when no bars at the bottom of the screen are shown */ EphyWindow.background EphyEmbed.vertical GtkScrolledWindow, EphyWindow.background { border-bottom-width: 0; } /* * GNOME Contacts */ /* Line at top in contacts pane, similar to .documents-scrolledwin.frame */ .contacts-spinner.frame { border-width: 0 1px 0 0; border-style: solid; border-color: @borders; border-image: none; border-radius: 0; padding: 0; } /* Background color in contacts pane, similar to .documents-main-view.view */ .contacts-main-view.view { background-color: #f1f2f1; } .contacts-suggestion { background-color: #D3D7CF; border-radius: 4px; } /* Border on the right in the left menu toolbar */ .contacts-left-header-bar:dir(ltr) { border-right-width: 1px; } .contacts-left-header-bar:dir(rtl) { border-left-width: 1px; } .contacts-left-header-bar:dir(ltr), .contacts-right-header-bar:dir(rtl) { border-top-right-radius: 0; } .contacts-right-header-bar:dir(ltr), .contacts-left-header-bar:dir(rtl) { border-top-left-radius: 0; } .contacts-avatar-frame.frame { border-width: 1px 1px 0 1px; border-style: solid; border-color: @borders; border-image: none; border-radius: 0; padding: 0; } .main-avatar-frame.frame { border-width: 1px; border-style: solid; border-color: @borders; border-radius: 6px; } /* Primary toolbar with no line at top to avoid conflicts with frame border */ ContactsWindow .primary-toolbar.toolbar { border-width: 0 0 1px 0; } .contacts-button:active { border-color: #000000; border-image: none; } .contacts-entry { box-shadow: none; border-image: none; border-width: 1px; border-radius: 4px; border-style: solid; border-color: #bbbeb7; background-image: none; background-color: #ffffff; } .contacts-entry:selected, .contacts-entry:selected:focus { background-color: @theme_selected_bg_color; color: @theme_selected_fg_color; } .contacts-entry.contacts-postal-entry { border-radius: 0 0 0 0; border-width: 1px 1px 0 1px; } .contacts-entry.contacts-postal-entry:nth-child(first) { border-radius: 4px 4px 0 0; } .contacts-entry.contacts-postal-entry:nth-child(last) { border-radius: 0 0 4px 4px; border-width: 1px; } .button.contacts-square { padding: 0px; } .contacts-combo .button { border-image: none; border-width: 1px; border-style: solid; border-color: #bbbeb7; background-image: none; background-color: #ffffff; } .toolbar.contacts-edit-toolbar { padding: 6px; background-color: #E2E4E2; border-width: 1px 0 0 0; border-style: solid; border-color: @borders; border-image: none; } .toolbar.contacts-edit-toolbar .button { padding-left: 6px; padding-right: 6px; } .toolbar.contacts-selection-toolbar { border-width: 1px 0 0 0; border-style: solid; border-color: @borders; border-image: none; } .contacts-watermark { color: #bebebe; text-shadow: 1px 1px alpha(white, 0.6); } /* * GNOME Photos */ .photos-icon-bg { icon-shadow: 0 1px #000000; } /* * Gucharmap */ GucharmapChartable:active, GucharmapChartable:focus, GucharmapChartable:selected { background-color: @theme_selected_bg_color; color: @theme_selected_fg_color; } /* * Evince */ EvWindow.background > GtkBox.vertical > GtkPaned.horizontal > GtkBox.vertical > GtkScrolledWindow.frame { border-width: 0; border-radius: 0; } EvWindow.background EvSidebar.vertical .frame { border-width: 1px 0 0; border-radius: 0; } EvWindow.background EvSidebar.vertical .notebook { border-width: 1px 0 0; } EvWindow.background EvSidebarAnnotations.vertical GtkToolPalette > GtkToolItemGroup > .button { border-image: none; border-radius: 0; border-style: solid; border-width: 0 0 1px; border-color: @borders; } EvWindow.background EvSidebar.vertical .notebook .frame { border-width: 0; } EvWindow .pane-separator, EvWindow .pane-separator:hover { border-width: 0 1px; border-style: solid; border-color: @borders; background-color: shade(@theme_bg_color, 0.95); color: @borders; } EvWindow.background EggFindBar.toolbar { border-width: 1px 0 0; border-style: solid; border-color: @borders; } /* gcalctool */ MathWindow.background > GtkBox.vertical > GtkBox.vertical > GtkScrolledWindow { padding: 4px; background-color: @theme_base_color; border-radius: 3px; } MathWindow.background > GtkBox.vertical > GtkBox.vertical > GtkScrolledWindow:backdrop { padding: 4px; background-color: @theme_unfocused_base_color; border-radius: 3px; } /* * GNOME Bluetooth */ GtkEntry.entry.pin-entry { font: regular 50; padding-left: 25px; padding-right: 25px; } GtkLabel.pin-label { font: regular 50; } /* * Fallback Mode Panel */ .gnome-panel-menu-bar, PanelApplet > GtkMenuBar.menubar, PanelToplevel, PanelWidget, PanelAppletFrame, PanelApplet { background-color: @os_chrome_bg_color; background-image: none; color: @os_chrome_fg_color; } ClockBox, .gnome-panel-menu-bar.menubar, PanelApplet > GtkMenuBar.menubar { font: bold; } .gnome-panel-menu-bar.menubar .menuitem:hover, PanelApplet > GtkMenuBar.menubar .menuitem:hover { text-shadow: 0 1px @os_chrome_bg_color; } .gnome-panel-menu-bar.menubar .menu, PanelApplet > GtkMenuBar.menubar .menu { font: regular; } .gnome-panel-menu-bar.menubar .menu:hover, PanelApplet > GtkMenuBar.menubar .menu:hover { text-shadow: none; } .gnome-panel-menu-bar .menuitem:hover, PanelApplet > GtkMenuBar.menubar .menuitem:hover, .gnome-panel-menu-bar .menuitem:hover, PanelApplet > GtkMenuBar.menubar .menuitem:hover { background-color: @os_chrome_selected_bg_color; color: @os_chrome_selected_fg_color; } .gnome-panel-menu-bar .menuitem:hover, PanelApplet > GtkMenuBar.menubar .menuitem:hover { color: @os_chrome_selected_fg_color; } PanelApplet .button, PanelApplet .button:hover { padding: 4px; border-image: none; border-width: 0; border-radius: 0; background-image: none; background-color: @os_chrome_bg_color; color: @os_chrome_fg_color; text-shadow: none; } PanelApplet .button:active:hover, PanelApplet .button:active { border-image: none; background-image: none; background-color: @os_chrome_selected_bg_color; border-width: 0; border-radius: 0; } PanelApplet:hover { color: @os_chrome_selected_fg_color; } PanelApplet:active, PanelApplet:hover:active { color: @os_chrome_selected_fg_color; text-shadow: 0 1px @os_chrome_bg_color; } WnckPager { background-color: lighter(@os_chrome_selected_bg_color); } NaTrayApplet { -NaTrayApplet-icon-padding: 12; -NaTrayApplet-icon-size: 16; } /* * Fail Whale */ GsmFailWhaleDialog { background-color: @os_chrome_bg_color; background-image: none; color: @os_chrome_fg_color; } GsmFailWhaleDialog .button, GsmFailWhaleDialog .button:active { border-image: none; border-color: @borders; border-width: 1px; } /**************** * Widgets * ****************/ /* * Floating Bar */ .floating-bar { background-image: linear-gradient(to bottom, @theme_base_color 20%, shade(@theme_base_color, 0.9) ); background-color: @theme_base_color; border-color: @borders; color: @theme_text_color; text-shadow: 0 1px @button_text_shadow; border-radius: 3px; border-width: 1px; border-style: solid; box-shadow: inset 1px 1px @inset_light_color, -1px -1px @inset_light_color; } .floating-bar.top { border-top-width: 0; border-top-right-radius: 0; border-top-left-radius: 0; } .floating-bar.right { border-right-width: 0; border-top-right-radius: 0; border-bottom-right-radius: 0; } .floating-bar.bottom { border-bottom-width: 0; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .floating-bar.left { border-left-width: 0; border-top-left-radius: 0; border-bottom-left-radius: 0; } .floating-bar.bottom.right { box-shadow: inset 1px 1px @inset_light_color; } .floating-bar.bottom.left { box-shadow: inset -1px 1px @inset_light_color; } .floating-bar:backdrop { background-color: @theme_unfocused_base_color; border-color: shade(@theme_unfocused_base_color, 0.9); background-image: none; box-shadow: none; } .floating-bar .button { background-color: alpha (@theme_base_color, 0.0); background-image: none; border-style: none; border-image: none; -GtkButton-image-spacing: 0; -GtkButton-inner-border: 0; } /* FIXME: why do we still need this? */ GtkClutterOffscreen { background-color: @theme_bg_color; color: @theme_fg_color; } /* * Egg */ EggListBox { background-color: @list_box_bg; } EggListBox:hover { background-color: @content_view_bg; } EggListBox:selected { background-color: @theme_selected_bg_color; } /* * Content view */ .content-view.subtitle { font: 9; padding: 0px 12px 0px 12px; } .content-view.view.rubberband { background-color: alpha (@theme_selected_bg_color, 0.35); border-color: @theme_selected_bg_color; border-style: solid; border-width: 1px; border-radius: 2px; } .content-view.view { background-color: @content_view_bg; } .content-view.view:insensitive { background-color: @theme_unfocused_base_color; background-image: none; } .content-view.view:backdrop { background-color: @sidebar_bg_unfocused; background-image: none; } .content-view.view:selected { background-color: @theme_selected_bg_color; background-image: none; } .content-view.view:selected:backdrop { background-color: @theme_unfocused_selected_bg_color; background-image: none; } /* FIXME: EggListBox should set the .cell style class on * the background it renders for the children, like * GtkIconView and GtkTreeView do */ .content-view.cell { background-color: transparent; background-image: none; } EggListBox.content-view:hover, .content-view.cell:hover { background-color: @theme_bg_color; } EggListBox.content-view:selected, EggListBox.content-view:active, .content-view.cell:selected, .content-view.cell:active { background-color: @theme_selected_bg_color; background-image: none; } EggListBox.content-view:selected:backdrop, .content-view.cell:selected:backdrop { background-color: @theme_unfocused_selected_bg_color; background-image: none; } GdMainIconView.content-view { -GdMainIconView-icon-size: 40; } GtkIconView.content-view.cell.check, GtkIconView.content-view.cell.check:backdrop { background-image: url("assets/grid-selection-unchecked.svg"); background-color: transparent; } GtkIconView.content-view.cell.check:active { background-image: url("assets/grid-selection-checked.svg"); background-color: transparent; } /* Make spinner visible on both dark and bright backgrounds w/o making * it look ugly/weird. */ GdMainIconView.content-view.cell:active { color: gray; } .content-view.view.check, .content-view.view.check:active { background-color: transparent; } .content-view.view .separator:backdrop { color: @theme_unfocused_bg_color; } GtkIconView.content-view.check:hover, GtkIconView.content-view.check:insensitive, GtkIconView.content-view.check:backdrop, GtkIconView.content-view.check:selected { background-color: transparent; } /* used by Documents and Evince */ .content-view.document-page { border-style: solid; border-width: 3px 3px 6px 4px; border-image: url("assets/thumbnail-frame.png") 3 3 6 4; } /* * App Notifications */ .app-notification { border-style: solid; border-color: @app_notification_border; border-width: 0 1px 1px 1px; border-radius: 0 0 5px 5px; padding: 8px; background-image: linear-gradient(to bottom, @app_notification_a, @app_notification_b 18%, @app_notification_c); color: @theme_text_color; text-shadow: 0 1px @primary_toolbar_button_text_shadow; } PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/apps/granite-widgets.css000066400000000000000000000152111363064711000271430ustar00rootroot00000000000000/********************* * dynamic notebooks * *********************/ .dynamic-notebook { background-color: shade(@theme_bg_color, 1.12); background-image: none; } .dynamic-notebook .notebook { -GtkNotebook-tab-overlap: 3px; -GtkNotebook-initial-gap: 5px; border-width: 1px 0 0 0; border-radius: 0; } .dynamic-notebook .notebook tab { padding: 2px 3px 0 3px; border-width: 0 1px 0 0; border-color: shade(@theme_bg_color, 0.8); border-radius: 0; background-color: transparent; background-image: none; color: @theme_fg_color; } .dynamic-notebook .notebook tab:nth-child(first) { border-width: 0 1px 0 1px; } .dynamic-notebook .notebook tab:active { border-width: 1px; border-color: shade(@theme_bg_color, 0.8); border-radius: 2px; background-color: shade(@theme_bg_color, 1.12); color: @theme_fg_color; } .dynamic-notebook .button, .dynamic-notebook .button:hover, .dynamic-notebook .button:hover:active, .dynamic-notebook .notebook .button, .dynamic-notebook .notebook .button:hover, .dynamic-notebook .notebook .button:hover:active { padding: 0; background-color: transparent; background-image: none; } /**************** * content view * ****************/ .content-view, .content-view *, .content-view GtkViewport { background-color: @theme_base_color; } .content-view-window { border-width: 1px; border-style: solid; border-color: shade(@theme_bg_color, 0.8); border-radius: 5px; background-image: -gtk-gradient(linear, left top, left bottom, from (@theme_base_color), to (shade(@theme_base_color, 0.97))); } .content-view .entry { background-image: -gtk-gradient(linear, left top, left bottom, from (shade(@theme_base_color, 0.97)), to (@theme_base_color)); } .content-view .title, .content-view .option-title { color: @theme_text_color; } .content-view .subtitle, .content-view .option-description { color: mix(@theme_fg_color, @theme_bg_color, 0.5); } .content-view .button { border-style: solid; border-radius: 2px; background-color: alpha(@theme_bg_color, 0.0); background-image: none; color: @theme_fg_color; } .content-view .button:active, .content-view .button:active:hover { background-color: alpha(@theme_bg_color, 0.5); background-image: none; } .content-view .button:insensitive { background-color: alpha(@theme_base_color, 0.0); background-image: none; } .content-view .help_button * { color: @theme_text_color; } .content-view .toolbar { padding: 1px; border-width: 0 0 1px 0; border-style: solid; border-color: shade(@theme_bg_color, 0.8); background-image: -gtk-gradient(linear, left top, left bottom, from (shade(@theme_bg_color, 1.0)), to (shade(@theme_bg_color, 1.2))); -GtkWidget-window-dragging: true; } /******************* * album list view * *******************/ .album-list-view, .album-list-view * { border-color: shade(@theme_bg_color, 0.8); border-radius: 0; background-color: @theme_base_color; color: @theme_fg_color; } .album-list-view GtkTreeView { -GtkTreeView-vertical-separator: 1; -GtkTreeView-grid-line-width: 0; background-color: @theme_base_color; color: @theme_fg_color; } .album-list-view GtkTreeView row:nth-child(even) { border-width: 0; border-style: none; background-color: shade(@theme_base_color, 0.97); } .album-list-view GtkTreeView row:nth-child(odd) { border-width: 0; border-style: none; background-color: shade(@theme_base_color, 1.0); } /************* * statusbar * *************/ GraniteWidgetsStatusBar { padding: 1px; background-image: -gtk-gradient(linear, left top, left bottom, from (shade(shade(@theme_bg_color, 1.06), 1.12)), to (shade(shade(@theme_bg_color, 1.06), 0.97))); box-shadow: inset 0 1px shade(@theme_bg_color, 0.8); } /*********** * popover * ***********/ GraniteWidgetsPopOver { -GraniteWidgetsPopOver-arrow-width: 24; -GraniteWidgetsPopOver-arrow-height: 12; -GraniteWidgetsPopOver-border-radius: 5px; -GraniteWidgetsPopOver-border-width: 1; -GraniteWidgetsPopOver-shadow-size: 15; margin: 0; border-style: solid; border-color: shade(@menu_bg_color, 0.8); color: @menu_fg_color; } .popover_bg { background-color: transparent; background-image: -gtk-gradient(linear, left top, left bottom, from (shade(@menu_bg_color, 1.40)), to (shade(@menu_bg_color, 1.30))); } GraniteWidgetsPopOver .sidebar.view, GraniteWidgetsPopOver * { background-color: transparent; color: @menu_fg_color; } GraniteWidgetsPopOver .button { border-color: shade(@menu_bg_color, 0.8); background-color: transparent; background-image: none; color: @menu_fg_color; } GraniteWidgetsPopOver .button:active, GraniteWidgetsPopOver .button:hover:active { background-color: @menu_bg_color; background-image: none; } GraniteWidgetsPopOver .entry { background-image: -gtk-gradient(linear, left top, left bottom, from (shade(@theme_base_color, 0.97)), to (@theme_base_color)); color: @theme_text_color; } .button.app { border-width: 0; border-radius: 2px; } .app:hover, .app:focus { background-image: -gtk-gradient(linear, left top, left bottom, from (shade(@theme_selected_bg_color, 1.1)), to (shade(@theme_selected_bg_color, 0.9))); color: @theme_selected_fg_color; } /************* * wingpanel * *************/ .panel { background-color: alpha (#000, 0.6); color: #fff; } .shadow { background-color: transparent; background-image: -gtk-gradient(linear, left top, left bottom, from (rgba(0, 0, 0, 0.3)), to (transparent)); } .composited-indicator, .wingpanel-app-button, .wingpanel-indicator-button { padding: 0 3px; background-color: transparent; color: #fff; } .composited-indicator.menuitem:active, .composited-indicator.menuitem:prelight { border-style: none; background-image: none; } /********** * notify * **********/ .notify { border-width: 1px; border-style: solid; border-color: shade(@theme_bg_color, 0.8); border-radius: 5px; background-color: @theme_base_color; color: @theme_text_color; } .notify .low { } .notify .critical { } PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/apps/unity.css000066400000000000000000000055021363064711000252200ustar00rootroot00000000000000 UnityDecoration { /* Border properties (top, right, bottom, left) */ -UnityDecoration-extents: 28px 8px 8px 8px; /* the size of the decorations */ -UnityDecoration-input-extents: 10px; -UnityDecoration-title-alignment: 0.5; } /* This will theme the top decoration, so the whole space above the window */ UnityDecoration.top { border: 1px solid @wm_outborder_focused; border-bottom-width: 0; border-radius: 0; /*border-radius: 8px 8px 0 0; Corner radius, only the top ones should be */ padding: 1px 1px 0 1px; /* This padding will be applied to the content of the top layout */ background-image: none; background-color: @wm_bg_focused; /* Decoration background */ color: @wm_title_focused; /* The foreground color will be used to paint the text */ /*text-shadow: 1px 0 #333, -1px 0 #333, 0 1px #333, 0 -1px #333;*/ box-shadow: none; } /* Top decoration for inactive windows */ UnityDecoration.top:backdrop { border: 1px solid @wm_outborder_unfocused; border-bottom-width: 0; background-color: @wm_bg_unfocused; color: @wm_title_unfocused; } /* Left decoration, it themes only the space at the left of the window */ UnityDecoration.left, /* Right decoration, it themes only the space at the right of the window */ UnityDecoration.right, /* Bottom decoration, it themes all the space below the window */ UnityDecoration.bottom { background-image: none; background-color: @wm_bg_focused; } UnityDecoration.left { border-left: 1px solid @wm_outborder_focused; } UnityDecoration.right { border-right: 1px solid @wm_outborder_focused; } UnityDecoration.bottom { border: 1px solid @wm_outborder_focused; border-top: 0px; } /* Left, right and bottom decorations themes for inactive windows */ UnityDecoration.left:backdrop, UnityDecoration.right:backdrop, UnityDecoration.bottom:backdrop { background-color: @wm_bg_unfocused; border-color: @wm_outborder_unfocused; } UnityPanelWidget, .unity-panel { border-width: 0 0 1px 0; border-style: solid; border-color: shade(@panel_bg_color, 0.8); background-image: -gtk-gradient(linear, left top, left bottom, from (shade(@panel_bg_color, 1.1)), to (shade(@panel_bg_color, 0.9))); color: @panel_fg_color; } .unity-panel.menubar, .unity-panel .menubar { background-color: @panel_bg_color; } .unity-panel.menuitem, .unity-panel .menuitem { border-width: 0 1px; color: @panel_fg_color; } .unity-panel.menubar.menuitem:hover, .unity-panel.menubar .menuitem *:hover { border-color: shade(@panel_bg_color, 0.7); background-image: -gtk-gradient(linear, left top, left bottom, from (shade(@panel_bg_color, 0.97)), to (shade(@panel_bg_color, 0.82))); color: @panel_fg_color; } PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/000077500000000000000000000000001363064711000236735ustar00rootroot00000000000000PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/checkbox-checked-insensitive.png000066400000000000000000000005471363064711000321170ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME 01`TtEXtCommentCreated with GIMPWIDAT8Ւ!0}٤7d]-7vuܠ ;7 ޠYZYeL-|Y7q? v]w?M\TeY)%TR 1FJycRs4 @Ι)%mS5"BΙs!`g`{Z?S?mۗ+l{T3 XER;IENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/checkbox-checked-over.png000066400000000000000000000006511363064711000305260ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME 1 y.tEXtCommentCreated with GIMPWIDAT8c`02000TgX`f"V)=}!Nb>e"CH7|􉡦!6>ӧLdq_SYgu M̜|X_QQ񿮞߸uEEO_O:XcA6ٍa|73020000d3hhja || -g015g``` tla0or`n todx 9mч3VIENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/checkbox-checked.png000066400000000000000000000005551363064711000275600ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME /.2?tEXtCommentCreated with GIMPWIDAT8Ւm0EFNLK00T:0%P%UN'{IDAT8c`02000ٳ?]\\X`ZZZDkvDF 5*`KC + RIENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/checkbox-unchecked-over.png000066400000000000000000000003231363064711000310650ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME 3tEXtCommentCreated with GIMPW;IDAT8c`02000TgX`f @X%ϡ?IENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/checkbox-unchecked.png000066400000000000000000000003251363064711000301160ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME 2U!tEXtCommentCreated with GIMPW=IDAT8c`02000'U X`Dk.,,(¨PlshVq4e9IENDB`menuitem-checkbox-checked-insensitive.png000066400000000000000000000006061363064711000336550ustar00rootroot00000000000000PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assetsPNG  IHDR asBITO pHYs B(xtEXtSoftwarewww.inkscape.org<uPLTE3'tRNS &*29@CFKLMOPQUXY\^_acdf7PIDATS!ٔQOt6+yh3-aׂ#2k!ɭw  %IENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/menuitem-checkbox-checked-selected.png000066400000000000000000000004031363064711000331570ustar00rootroot00000000000000PNG  IHDR |lsBITUF pHYs B(xtEXtSoftwarewww.inkscape.org<IDATcπ2&pb ,ta?u ;sÅ C,m@!ˁg?3̹<$?#Dv4֑ǶIENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/menuitem-checkbox-checked.png000066400000000000000000000006161363064711000313770ustar00rootroot00000000000000PNG  IHDR asBITO pHYs B(xtEXtSoftwarewww.inkscape.org<{PLTEMi(tRNS "+,6=BGO^j~r%cQIDAT)X݂z @{X2.a׌;骪q9mSɭ^CHIENDB`menuitem-checkbox-mixed-insensitive.png000066400000000000000000000002771363064711000334010ustar00rootroot00000000000000PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assetsPNG  IHDR Vu\sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<l6,_9$I)"BJ$m1ƈs A۶S,PJ}A1r\PJQb&P3@)EeXkm֗yD1dB8KaH2p8ZO(Ds֚BhIDs(rq'(2뺎#]a=0ƈRip1#41#9A)1ˇ} cﺮ/}?vEx7G}}ևIENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/radio-mixed.png000066400000000000000000000007001363064711000266000ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME xtEXtCommentCreated with GIMPW(IDAT8œ@?S%dB7">AK},d%+E@n`a͈eyak-9(Zc!˲h}w>)_c]RUt]'4~i:J,IENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/radio-selected-insensitive.png000066400000000000000000000010551363064711000316240ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME $7'utEXtCommentCreated with GIMPWIDAT8͓PEOy(BYA :6q d6A3M/.oU].UZODi˲pdzo^B8NFJRjR c m"L "AQqq]!'8rBn3iv;g,,K|'0u]<1 x(0 \.+al0$Oil ˲iaȲ ۶if PJPzJ) X1x,˚ 8C۶Ar6,$ڶqP2Y.a8+HUUq~[(Z~N/5!.GP]}^4@߼pVz zVfGqxNϧ}SA߿9kwK`ޘ_KO|*b"{fY66dfllnɞYޓ;"Kol1[?ņaH<0&fKdf ^jOy@r8P0?z'#ږ,,&GxIf3(btf]98熽$ΰۏT9X?{sk3t+g~v..d| LIENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/radio-selected.png000066400000000000000000000007251363064711000272710ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME ${2tEXtCommentCreated with GIMPW=IDAT8œ@?NAoBMI3wGZ* rNWh!3GqMf27‹5nl,ˍ{OZk{uws?o( 1ƈsN*MӈsN1R(łnGEVat:q\`cED,K\y.eYHb`3Q[k974j54MGsNrӾxI~UU$I @qqBRm v} ۶E)5h,R,Â_7w'_ @tIp< IENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/radio-unselected-insensitive.png000066400000000000000000000007301363064711000321660ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME #h2tEXtCommentCreated with GIMPW@IDAT8͓0EO!"!?Am#PMڅ@FHel0K`W^=\7ga8mۇH)Bkst8ޖn˃z>sUUH)nH)9c~&1{)4MR M rAAu%Xb;4!h0m^J9KzB맮^5!PRʧRJRJkRS3J5@kM)@;Yk)\JZs=0H!8gg; ߛ{oO>ZgDZr;~I"i՛ԏ'&~~+Yҭ1>V~-Z#5Oj/nS㚲ߊ|rd%[o&$:rfblVmMhn̈Q\kK3bw«,v/[rf62 ZK##<:uGPr֚2 EHM}eӜ1Mg@&ù3Ph8i\@0֣표3ZT|p Yt8DkN{wv"h|B+b O %H$YFX)ZO!I)J&SιZ\RǒK)heە6| MZlVZtC{깗^~#4(N;q8̳̺pWXJ+bV}f}쥚JPޔ)n' *`qN5; rQQ! `ɲ> g Q}+vzxoI.ԅwdˌo eXZ/ 3Ȳȅ?3MLcdL+Y MŸ&{[#$Ʌu^k3F̝ܷᣰDc>8jI,FyÚ07.=m֗'XjoPNjlnSÜG1k֎N`U $:+RM *AL ]gDT%7Ab\&J/~i:^ -&PTӶ-$Nz$fcpHnq~|ᙩ&Uaԋ"ï˃P@˷ޘ_:ͧ/#NnSx4+âNFP[eڡnYo֠}rayv]N@ 'I$3][Pxi7/|{)緊ikP*ްNҋb~jAfQ6Wl(_t =QW׆:̱3aE`Q15/.vkk;3ı1:[;Xbq7! Θ @bKGD pHYs  tIME *A tEXtCommentCreated with GIMPWyIDAT(ՒA 0PW ~>ĦiRe$TJ1. Hk+l_3_pcn9?1c3KB'Z[3L iپn lwm AD6"b|*OgPy2==Ç 00 _ @MIENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/slider-horiz-over.png000066400000000000000000000005311363064711000277640ustar00rootroot00000000000000PNG  IHDR F)bKGDC pHYs  tIME 1+SCtEXtCommentCreated with GIMPWIDAT(c?!AIA'IQ@3?N|W"6/~2?d|W/~2ٰ121000@5>fqKE6lpg@5x8Bg^( myU\\ v {T ba £*IENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/slider-horiz.png000066400000000000000000000004221363064711000270120ustar00rootroot00000000000000PNG  IHDR F)bKGDC pHYs  tIME 1#KtEXtCommentCreated with GIMPWzIDAT(Ւ 0 4G(R/w@)$FIPw$@D.Z#/<У7T{:`Ie[ϰO܃6f 9Z;0+ypcX|IENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/slider-vert-dark.png000066400000000000000000000004011363064711000275530ustar00rootroot00000000000000PNG  IHDR ն:bKGD pHYs  tIME 8#tEXtCommentCreated with GIMPWiIDAT(ϭ1 @Ϡ^D?1dP7vIt:B`Ώv=1 p~B_i-(c 2ւttNe&X(T5GIENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/slider-vert-insensitive-dark.png000066400000000000000000000003621363064711000321170ustar00rootroot00000000000000PNG  IHDR ն:bKGD pHYs  tIME 64(7"tEXtCommentCreated with GIMPWZIDAT(ϭA@@D NMiֵaEBlPoQk0NjRkH逎-$r]搄pep~G,Ү ZIENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/slider-vert-insensitive.png000066400000000000000000000111521363064711000311770ustar00rootroot00000000000000PNG  IHDR ն:vzTXtRaw profile type exifxڭWY( s#Ba{s9KUWudVmB(Bo Ck,h)>YRM=Oۿ|H\|G&C.t r/q~QRD7J6$KQk|>XG*G] Տ (tdߦdsE 6JxZ`~ʩ%_(ƈߧRE?Yn/ }}Zgv-> TG~WH7n/;r8eȅ>Yy \CGw+磺VYǽFBHXq-}qLWA) 07H*` D(=l{[ۍX5=ʜ "d_rf G.pDhSy;~8 vs]JA$^y)`ØQbX!DAb#+j0RX"3MI9pk$nQb:M ` 5NpeVR+Eg&IBM5WJUkS҄ˊxԪa ZSϝ{kF<ʐQ6i8eʬSg[qJ+/^eɪKW3P͒ec+&VMjZP 5*v̀)9fG9#J &Ό+[|`DC*n@\p~ϸ5jl0t gr4;~pNytExzp`)Ȇ*I{իdXWid4쵫{'"Ƽu}a_0Hm'd*6G ;k[UFjĪHI+|nT0Y覾<}1>8X +ۂ m=E-"W&HxщU@1I qj<Vs Vz*m$}^hWޣbZi|7N #EǣfF:|?&3 b|rsCȻHΠ- pPVpOh !8~hWmp1Bɴ.nFFQF/M>灐&0c)K>J9"(|<3,]6AZӌ). яZGP׺~)`W t7Kr$BVjGht‹# j:9GNkX0xoۙ}tGx :I JYdNbPD]87sK*"?c͂`3#p2*0OB"d3_#+G`:Ko=͹VtC1bxa0GS65YBQD cP,Vo]%8%ék lUYYzU/ÙĀg "  ̰2z)^ rꠝD(۬t V~Ɩ v _N*vuEzg#$, 1E@@ O%hᭌFfGB醈(*Z`䜕vAaoXs~b4~b]߯xfYxlq։ERϰ$}etyAxxY36`@p강u-\@8Imz/ 1; /x[y<:a3x9q5>{ F]ﹳcC9Iӌ9mnw_P=]Q/O +Yr骾ٽ$-2  wIk= iv_v+E~ū\]I1ڢ6ݫk7c>|7 ͻ\Qյ 1TazTXtRaw profile type iptcx= @ LIl%pdu϶mi(&WhO쪅#@j9& Zu0ہa jiTXtXML:com.adobe.xmp @bKGD pHYs  tIME %J4tEXtCommentCreated with GIMPWmIDAT(ϭ+ EY_]$hàHJ̙05UqеD" s^1*1bVhb !\J fǾ`Վx#;AIENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/slider-vert-over-dark.png000066400000000000000000000004441363064711000305330ustar00rootroot00000000000000PNG  IHDR ն:bKGD pHYs  tIME 5tEXtCommentCreated with GIMPWIDAT(c: ՠ D8`.F͗$𸙉$&jDAx P`_J1/0#l #`PWW@=L (<IENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/slider-vert-over.png000066400000000000000000000005321363064711000276120ustar00rootroot00000000000000PNG  IHDR ն:bKGDC pHYs  tIME 6$ntEXtCommentCreated with GIMPWIDAT(ϭӭ `.`/Ԫ7a[v‚{Yj6) + [^O9OpH$'SyIn!ΫQcҫYh5 ,lnIx5 ,wm` 0/cwkcmX6AfJPDAFC`4>AqP) GjjBgIENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/assets/slider-vert.png000066400000000000000000000004131363064711000266370ustar00rootroot00000000000000PNG  IHDR ն:bKGDC pHYs  tIME 58BOtEXtCommentCreated with GIMPWsIDAT(ϭӱ DoF^j B(Usdɲ1xN~BD`|w3}A)?=*413;Y8U;u `8Ze\:PT IENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/gtk-contained.css000066400000000000000000002135001363064711000256330ustar00rootroot00000000000000/* # name: Windows 8 # author: Maxime Doyen */ * { padding: 0; -GtkToolButton-icon-spacing: 4; -GtkTextView-error-underline-color: #cc0000; -GtkScrolledWindow-scrollbar-spacing: 0; -GtkToolItemGroup-expander-size: 11; -GtkWidget-text-handle-width: 20; -GtkWidget-text-handle-height: 24; -GtkDialog-button-spacing: 4; -GtkDialog-action-area-border: 0; outline-color: @borders; outline-style: dashed; outline-offset: -3px; outline-width: 1px; -gtk-outline-radius: 2px; } /*************** * Base States * ***************/ .background { color: @theme_fg_color; background-color: @theme_bg_color; } /* These wildcard seems unavoidable, need to investigate. Wildcards are bad and troublesome, use them with care, or better, just don't. Everytime a wildcard is used a kitten dies, painfully. */ *:disabled { -gtk-icon-effect: dim; } .gtkstyle-fallback { background-color: @theme_fg_color; color: @theme_fg_color; } .gtkstyle-fallback:hover { background-color: @theme_fg_color; color: @theme_fg_color; } .gtkstyle-fallback:active { background-color: #e6e6e6; color: @theme_fg_color; } .gtkstyle-fallback:disabled { background-color: @theme_fg_color; color: #7f7f7f; } .gtkstyle-fallback:selected { background-color: @theme_selected_bg_color; color: @theme_selected_fg_color; } .view, iconview, .view text, iconview text, textview text { color: @theme_fg_color; background-color: @theme_base_color; } .view:hover:not(:selected), iconview:hover:not(:selected) { background-color: shade(@theme_base_color, 0.92); } .view:selected { background-color: @theme_selected_bg_color; } .view:selected:hover { background-color: alpha(@theme_selected_bg_color, 0.33); } .view:disabled, iconview:disabled, .view text:disabled, iconview text:disabled, textview text:disabled { color: @insensitive_fg_color; background-color: @insensitive_bg_color; } .rubberband, rubberband, flowbox rubberband, treeview.view rubberband, .content-view rubberband { border: 1px solid @borders; background-color: rgba(0, 0, 0, 0.2); } flowbox flowboxchild { padding: 3px; } flowbox flowboxchild:selected { outline-offset: -2px; } label selection { background-color: alpha(@theme_selected_bg_color, 0.9); color: @theme_selected_fg_color; outline-color: rgba(255, 255, 255, 0.3); } label:disabled { color: @insensitive_fg_color; } .dim-label, label.separator, .titlebar:not(headerbar) .subtitle, headerbar .subtitle { opacity: 0.55; text-shadow: none; } assistant .sidebar { background-color: shade(@theme_bg_color, 0.87); border-top: 1px solid @borders; } assistant.csd .sidebar { border-top-style: none; } assistant .sidebar label { padding: 6px 12px; } assistant .sidebar label.highlight { background-color: @theme_selected_bg_color; } /********************* * Spinner Animation * *********************/ @keyframes spin { to { -gtk-icon-transform: rotate(1turn); } } spinner { background: none; opacity: 0; -gtk-icon-source: -gtk-icontheme("process-working-symbolic"); } spinner:checked { opacity: 1; animation: spin 1s linear infinite; } spinner:checked:disabled { opacity: 0.5; } /**************** * Text Entries * ****************/ entry { min-height: 23px; padding: 2px 2px; transition: all 200ms ease-out; background-color: @theme_base_color; border-style: solid; border-width: 1px; border-color: @borders; } entry:not(.vertical) { padding: 2px 6px; } entry undershoot.left { background-color: transparent; background-image: linear-gradient(to top, rgba(255, 255, 255, 0.2) 50%, rgba(0, 0, 0, 0.2) 50%); padding-left: 1px; background-size: 1px 10px; background-repeat: repeat-y; background-origin: content-box; background-position: left center; border: none; box-shadow: none; } spinbutton:not(.vertical) undershoot.right, entry undershoot.right { background-color: transparent; background-image: linear-gradient(to top, rgba(255, 255, 255, 0.2) 50%, rgba(0, 0, 0, 0.2) 50%); padding-right: 1px; background-size: 1px 10px; background-repeat: repeat-y; background-origin: content-box; background-position: right center; border: none; box-shadow: none; } entry.flat, entry.flat:focus { padding: 2px; border-style: solid; border-width: 1px; border-color: @borders; border: none; border-radius: 0; } entry:focus { border-style: solid; border-width: 1px; border-color: @border_active; box-shadow: inset 0 0 0 1px alpha(@border_active, 0.50); } entry:disabled { border-style: solid; border-width: 1px; color: @insensitive_fg_color; background-color: @insensitive_bg_color; border-color: @insensitive_borders; } entry progress { margin: 1px; border-radius: 0; border-width: 0 0 2px; border-color: @progress_color; border-style: solid; background-image: none; background-color: transparent; box-shadow: none; } .linked entry:first-child { border-top-right-radius: 0; border-bottom-right-radius: 0; } .linked entry:first-child:dir(rtl) { border-right-style: none; } .linked entry:last-child { border-top-left-radius: 0; border-bottom-left-radius: 0; border-left-style: none; } .linked entry:last-child:dir(rtl) { border-left-style: solid; } entry.error { color: #cc0000; border-color: #cc0000; } entry.error:focus { background-color: transparent; border-style: solid; border-width: 1px; background-image: linear-gradient(to bottom, #f7f7f7, @theme_fg_color 90%); border-color: #cc0000; } entry.error:selected, entry.error:selected:focus { background-color: #cc0000; } entry.warning { color: #f57900; border-color: #f57900; } entry.warning:focus { background-color: transparent; border-style: solid; border-width: 1px; background-image: linear-gradient(to bottom, #f7f7f7, @theme_fg_color 90%); border-color: #f57900; } entry.warning:selected, entry.warning:selected:focus { background-color: #f57900; } /*********** * Buttons * ***********/ button { transition: all 200ms ease-out; min-width:19px; min-height:19px; padding: 2px 2px; border-width: 1px; border-style: solid; color: @theme_fg_color; background-image: linear-gradient(to bottom, @gradient_default_s, @gradient_default_e); border-color: @borders; } button:hover, button.flat:hover { border-width: 1px; border-style: solid; border-color: @border_hover; background-image: linear-gradient(to bottom, @gradient_hover_s, @gradient_hover_e); -gtk-icon-effect: highlight; } button:active, button.flat:active, button:checked, button.flat:checked { border-width: 1px; border-style: solid; border-color: @border_active; background-image: linear-gradient(to bottom, @gradient_active_s, @gradient_active_e); transition-duration: 50ms; } button:disabled { border-width: 1px; border-style: solid; color: @insensitive_fg_color; border-color: @insensitive_borders; background-color: @insensitive_bg_color; background-image: none; text-shadow: none; -gtk-icon-shadow: none; } button:disabled:active, button:disabled.flat:active { border-width: 1px; border-style: solid; color: @insensitive_fg_color; border-color: @insensitive_borders; background-color: shade (@theme_bg_color, 0.99) } osd button { padding: 2px; border-width: 1px 1px 2px 1px; border-width: 1px; border-style: solid; background-image: none; background-color: rgba(0, 0, 0, 0.8); border-color: rgba(255, 255, 255, 0.2); box-shadow: none; } osd button:hover { border-width: 1px; border-style: solid; background-color: rgba(255, 255, 255, 0.01); border-color: rgba(255, 255, 255, 0.2); box-shadow: none; } osd button:active, osd button:checked { border-width: 1px; border-style: solid; background-color: rgba(255, 255, 255, 0.03); border-color: rgba(255, 255, 255, 0.2); box-shadow: none; } osd button:disabled { border-width: 1px; border-style: solid; border-color: rgba(255, 255, 255, 0.2); color: #7f7f7f; } button.suggested-action { border-width: 1px; border-style: solid; color: @theme_fg_color; background-image: linear-gradient(to bottom, #5f9ddd, #4a90d9 40%, #3583d5); border-color: #1c5187; } button.suggested-action:hover { border-width: 1px; border-style: solid; color: @theme_fg_color; border-color: #1c5187; background-image: linear-gradient(to bottom, #85b4e5, #5b9add 40%, #4a90d9); } button.suggested-action:active, button.suggested-action:checked { border-width: 1px; border-style: solid; border-color: #1c5187; background-color: #1c5187; color: @theme_fg_color; } button.suggested-action:disabled { border-width: 1px; border-style: solid; color: #7f7f7f; border-color: @borders; background-color: #f1f1f1; text-shadow: none; -gtk-icon-shadow: none; } button.destructive-action { border-width: 1px; border-style: solid; color: @theme_fg_color; background-image: linear-gradient(to bottom, #f14141, #ef2929 40%, #ed1212); border-color: #8e0b0b; } button.destructive-action:hover { border-width: 1px; border-style: solid; color: @theme_fg_color; border-color: #8e0b0b; background-image: linear-gradient(to bottom, #f46b6b, #f03c3c 40%, #ef2929); } button.destructive-action:active, button.destructive-action:checked { border-width: 1px; border-style: solid; border-color: #8e0b0b; background-color: #8e0b0b; color: @theme_fg_color; } button.destructive-action:disabled { border-width: 1px; border-style: solid; color: #7f7f7f; border-color: @borders; background-color: @theme_fg_color; text-shadow: none; -gtk-icon-shadow: none; } button.toggle { padding: 4px 8px } button.image-button { padding: 4px; } button.text-button { padding: 4px 4px; } button.text-button > label { padding: 0 8px; } .primary-toolbar button { -gtk-icon-shadow: none; } .inline-toolbar toolbutton > button { border-width: 1px; border-style: solid; color: @theme_fg_color; background-image: linear-gradient(to bottom, @gradient_normal_s, @gradient_normal_e); border-color: @borders; } .inline-toolbar toolbutton > button:hover { border-width: 1px; border-style: solid; color: @theme_fg_color; border-color: @borders; background-image: linear-gradient(to bottom, @gradient_hover_s, @gradient_hover_e); } .inline-toolbar toolbutton > button:active { border-width: 1px; border-style: solid; border-color: @border_active; background-image: linear-gradient(to bottom, @gradient_active_s, @gradient_active_e); color: @theme_fg_color; } .inline-toolbar toolbutton > button:disabled { border-width: 1px; border-style: solid; color: @insensitive_fg_color; border-color: @insensitive_borders; background-color: @insensitive_bg_color; background-image: none; text-shadow: none; -gtk-icon-shadow: none; } .inline-toolbar toolbutton > button:disabled:active { border-width: 1px; border-style: solid; color: @insensitive_fg_color; border-color: @insensitive_borders; background-color: @insensitive_bg_color; background-image: none; } .inline-toolbar.toolbar toolbutton > button.flat { border-radius: 0; border-left-style: none; } .inline-toolbar.toolbar toolbutton:first-child > button.flat { border-radius: 0; border-left-style: solid; } .inline-toolbar.toolbar toolbutton:last-child > button.flat { border-radius: 0; } .inline-toolbar.toolbar toolbutton:last-child:dir(rtl) > button.flat { border-right-style: solid; } .inline-toolbar.toolbar toolbutton:only-child > button.flat { border-radius: 0; border-style: solid; } osd button, osd button:hover, osd button:active, osd button:checked, osd button:disabled, inline-toolbar button, .linked:not(.vertical) button, .linked > combobox > box > button:dir(ltr) { border-radius: 0; border-left-style: none; } osd button:dir(rtl), inline-toolbar button:dir(rtl), .linked button:dir(rtl), .linked > combobox > box > button:dir(rtl) { border-radius: 0; border-right-style: none; border-left-style: solid; } osd button:first-child, inline-toolbar button:first-child, .linked button:first-child, .linked > combobox:first-child > box > button { border-radius: 0; border-left-style: solid; } osd button:last-child, inline-toolbar button:last-child, .linked button:last-child, .linked > combobox:last-child > box > button { border-radius: 0; } osd button:last-child:dir(rtl), inline-toolbar button:last-child:dir(rtl), .linked button:last-child:dir(rtl), .linked > combobox:last-child > box > button:dir(rtl) { border-right-style: solid; } osd button:only-child, inline-toolbar button:only-child, .linked button:only-child, .linked > combobox:only-child > box > button { border-radius: 0; border-style: solid; } .linked.vertical button { border-radius: 0; border-bottom-style: none; } .linked.vertical button:last-child { border-bottom-style: solid; } button.flat, menuitembutton.flat, button:link, button:visited, button:link:hover, button:link:active, button:visited:hover, button:visited:active, notebook tab button, .list-rowbutton, GtkCalendarbutton, GtkCalendarbutton:hover { border-color: transparent; background-color: transparent; background-image: none; box-shadow: inset 0 1px rgba(255, 255, 255, 0); text-shadow: none; -gtk-icon-shadow: none; } .stack-switcher > button > label { padding-left:9px; padding-right:9px; } /* menu buttons */ modelbutton.flat, popover.background checkbutton, popover.background radiobutton, .menuitem.button.flat { padding: 4px; /*min-height: 26px; padding-left: 5px; padding-right: 5px; border-radius: 3px;*/ outline-offset: -1px; } modelbutton.flat:hover, popover.background checkbutton:hover, popover.background radiobutton:hover, .menuitem.button.flat:hover { background-color: alpha( @theme_selected_bg_color, 0.33); } modelbutton.flat check:last-child, popover.background checkbutton check:last-child, popover.background radiobutton check:last-child, modelbutton.flat radio:last-child, popover.background checkbutton radio:last-child, popover.background radiobutton radio:last-child, .menuitem.button.flat check:last-child, .menuitem.button.flat radio:last-child { margin-left: 8px; } modelbutton.flat check:first-child, popover.background checkbutton check:first-child, popover.background radiobutton check:first-child, modelbutton.flat radio:first-child, popover.background checkbutton radio:first-child, popover.background radiobutton radio:first-child, .menuitem.button.flat check:first-child, .menuitem.button.flat radio:first-child { margin-right: 8px; } modelbutton.flat arrow, popover.background checkbutton arrow, popover.background radiobutton arrow { background: none; } modelbutton.flat arrow:hover, popover.background checkbutton arrow:hover, popover.background radiobutton arrow:hover { background: none; } modelbutton.flat arrow.left, popover.background checkbutton arrow.left, popover.background radiobutton arrow.left { -gtk-icon-source: -gtk-icontheme("pan-start-symbolic"); } modelbutton.flat arrow.right, popover.background checkbutton arrow.right, popover.background radiobutton arrow.right { -gtk-icon-source: -gtk-icontheme("pan-end-symbolic"); } button.color { padding: 4px; } /********* * Links * *********/ *:link { color: @theme_selected_bg_color; } *:link:hover, *:link:active, *:link:visited { color: @theme_fg_color; } button:link, button:visited { color: @theme_selected_bg_color; text-shadow: none; } button:link:hover, button:link:active, button:visited:hover, button:visited:active { color: @theme_fg_color; text-shadow: none; } /***************** * GtkSpinButton * *****************/ spinbutton { padding: 0; border: none; } spinbutton:disabled { color: @insensitive_fg_color; } spinbutton button { background-image: linear-gradient(to bottom, @gradient_default_s, @gradient_default_e); border-width: 1px; border-color: @borders; color: @theme_fg_color; border-radius: 0; box-shadow: none; padding: 0; } spinbutton button:dir(rtl) { border-width: 0 1px 0 0; } spinbutton button:hover { color: @theme_fg_color; border-color: @border_hover; background-image: linear-gradient(to bottom, @gradient_hover_s, @gradient_hover_e); } spinbutton button:disabled { background-image: none; color: @insensitive_fg_color; border-color: @insensitive_borders; background-color: @insensitive_bg_color;} spinbutton button:active { background-image: linear-gradient(to bottom, @gradient_active_s, @gradient_active_e); } spinbutton.vertical entry { padding: 0 0; } spinbutton.horizontal button.up { border-width: 1px 1px 1px 0px; } spinbutton.horizontal button.down { border-width: 1px 1px 1px 0px; } spinbutton.vertical button.up { border-width: 1px 1px 0px 1px; } spinbutton.vertical button.down { border-width: 0px 1px 1px 1px; } /************** * ComboBoxes * **************/ combobox arrow { -gtk-icon-source: -gtk-icontheme("pan-down-symbolic"); margin: 2px; min-height: 16px; min-width: 16px; } combobox:drop(active) { box-shadow: none; } /************ * Toolbars * ************/ toolbar, .inline-toolbar, searchbar, .location-bar { -GtkWidget-window-dragging: true; padding: 2px; /*background-color: #e8e8e7;*/ } .osd toolbar { background-color: transparent; } toolbar.osd { background-color: rgba(0, 0, 0, 0.8); border-radius: 0; padding: 5px; } toolbarinline-toolbar { border-width: 0 1px 1px; padding: 3px; border-radius: 0; } searchbar { border-width: 0 0 1px; padding: 3px; } toolbar.inline-toolbar, searchbar.inline-toolbar { border-width: 0 1px 1px 1px; border-style: solid; border-color: @borders; background-color: shade(@theme_bg_color, 0.92); } /*************** * Header bars * ***************/ .titlebar, headerbar { border-width: 0 0 1px; border-style: solid; border-color: shade(@theme_bg_color, 0.95); background-color: @theme_base_color; /*background-image: linear-gradient(to bottom, @theme_base_color 50%, @theme_bg_color);*/ padding: 2px; } titlebar title, headerbar title { font-weight: bold; padding: 0px 6px; } titlebar subtitle, headerbar subtitle { font-size: smaller; padding: 0 6px; } .titlebar headerbar-separator, titlebar > box > separator.vertical, headerbar headerbar-separator, headerbar > box > separator.vertical { border-width: 0 1px; border-image: linear-gradient(to bottom, rgba(127, 127, 127, 0), #7f7f7f 30%, #7f7f7f 70%, rgba(127, 127, 127, 0) 100%) 0 1/0 1px stretch; } titlebar > button, headerbar > button { min-height: 19px; padding: 2px 4px; } titlebar.selection-mode, headerbar.selection-mode { color: @theme_fg_color; text-shadow: 0 1px rgba(0, 0, 0, 0.5); background-image: linear-gradient(to bottom, #0d0d0d, #050505); box-shadow: inset 0 -1px #d8d8d8, inset 0 1px #7f7f7f; } titlebar.selection-mode button, headerbar.selection-mode button { border-width: 1px; border-style: solid; color: @theme_fg_color; background-image: linear-gradient(to bottom, #0d0d0d, @theme_fg_color 40%, @theme_fg_color); border-color: @theme_fg_color; } titlebar.selection-mode button:hover, headerbar.selection-mode button:hover { border-width: 1px; border-style: solid; color: @theme_fg_color; border-color: @theme_fg_color; background-image: linear-gradient(to bottom, #242424, #0a0a0a 40%, @theme_fg_color); } titlebar.selection-mode button:active, headerbar.selection-mode button:active { border-width: 1px; border-style: solid; border-color: @theme_fg_color; background-color: @theme_fg_color; color: @theme_fg_color; } titlebar.selection-mode button:disabled, headerbar.selection-mode button:disabled { border-width: 1px; border-style: solid; color: #7f7f7f; border-color: @borders; background-color: @theme_fg_color; text-shadow: none; -gtk-icon-shadow: none; } titlebar.selection-mode button.suggested-action, headerbar.selection-mode button.suggested-action { border-width: 1px; border-style: solid; color: @theme_fg_color; background-image: linear-gradient(to bottom, @theme_fg_color, @theme_fg_color 40%, #f2f2f2); border-color: @borders; } titlebar.selection-mode button.suggested-action:hover, headerbar.selection-mode button.suggested-action:hover { border-width: 1px; border-style: solid; color: @theme_fg_color; border-color: @borders; background-image: linear-gradient(to bottom, @theme_fg_color, @theme_fg_color 40%, @theme_fg_color); } titlebar.selection-mode button.suggested-action:active, headerbar.selection-mode button.suggested-action:active { border-width: 1px; border-style: solid; border-color: @borders; background-color: #7f7f7f; color: @theme_fg_color; } titlebar.selection-mode button.suggested-action:disabled, headerbar.selection-mode button.suggested-action:disabled { border-width: 1px; border-style: solid; color: #7f7f7f; border-color: @borders; background-color: @theme_fg_color; text-shadow: none; -gtk-icon-shadow: none; } .titlebar.selection-mode selection-menu, headerbar.selection-mode selection-menu { border-width: 0; background-image: none; box-shadow: none; } .tiled .titlebar, .maximized .titlebar, .tiled headerbar, .maximized headerbar { border-radius: 0; } /************ * Pathbars * ************/ .path-bar button.text-button, .path-bar button.image-button, .path-bar button { padding-left: 4px; padding-right: 4px; } .path-bar button.text-button.image-button label { padding-left: 0; padding-right: 0; } .path-bar button.text-button.image-button label:last-child, .path-bar button label:last-child { padding-right: 8px; } .path-bar button.text-button.image-button label:first-child, .path-bar button label:first-child { padding-left: 8px; } .path-bar button image { padding-left: 4px; padding-right: 4px; } .path-bar button.slider-button { padding-left: 0; padding-right: 0; } /************** * Tree Views * **************/ treeview.view { border-left-color: @theme_bg_color; border-top-color: @theme_bg_color; } * { -GtkTreeView-grid-line-width: 1; -GtkTreeView-grid-line-pattern: ''; -GtkTreeView-tree-line-width: 1; -GtkTreeView-tree-line-pattern: ''; -GtkTreeView-expander-size: 16; } treeview.view:selected { border-radius: 0; } treeview.view:selected { border-left-color: @theme_selected_fg_color; border-top-color: @theme_selected_fg_color; } treeview.view:disabled { background-color: @theme_bg_color; color: @insensitive_fg_color; } treeview.view:disabled:selected { color: #666666; } treeview.view.dnd { border-style: solid none; border-width: 1px; border-color: black; } treeview.view.expander { -gtk-icon-source: -gtk-icontheme("pan-end-symbolic"); color: @theme_fg_color; } treeview.view.expander:dir(rtl) { -gtk-icon-source: -gtk-icontheme("pan-end-symbolic-rtl"); } treeview.view.expander:hover { color: @theme_selected_fg_color; } treeview.view.expander:selected { color: @theme_selected_fg_color; } treeview.view.expander:selected:hover { color: #fff; } treeview.view.expander:checked { -gtk-icon-source: -gtk-icontheme("pan-down-symbolic"); } treeview.view.progressbar { color: @theme_fg_color; border: 1px solid @borders; background-color: @progress_color; } treeview.view.progressbar:selected { color: @theme_selected_fg_color; background-color: darker(@theme_selected_fg_color); } treeview.view.progressbar:hover { color: @theme_selected_fg_color; background-color: lighter(@progress_color); } treeview.view.trough { background-color: shade (@theme_bg_color, 0.96); } treeview.view.trough:selected { background-color: alpha(shade (@theme_bg_color, 0.96), 0.5); } treeview.view header button { background-color: @theme_base_color; color: @theme_fg_color; font-weight: bold; text-shadow: none; box-shadow: none; } treeview.view header button:hover { color: #404040; box-shadow: none; } treeview.view header button:active { color: #fff; } treeview.view header button:last-child, treeview.view header button:last-child:hover, treeview.view header button:last-child:backdrop { border-right-style: none; } treeview.view header button, treeview.view header button:hover, treeview.view header button:active { padding: 3px 6px; border-style: none solid solid none; border-radius: 0; border-width: 1px; background-image: none; border-color: @theme_bg_color; text-shadow: none; } treeview.view header button:disabled { border-color: @insensitive_borders; background-image: none; } /********* * Menus * *********/ menubar, .menubar { -GtkWidget-window-dragging: true; padding: 0px; background-color: transparent; } menubar > menuitem, .menubar > menuitem { padding: 2px 4px; border: 1px solid transparent; } menubar > menuitem:hover, .menubar > menuitem:hover { border: 1px solid @menubaritem_border; background-color: @menubaritem_bg_color; color: @theme_fg_color; } menubar > menuitem:disabled, .menubar > menuitem:disabled { color: #7f7f7f; box-shadow: none; } menu, .menu { padding: 0px; background-color: @theme_bg_color; border: 1px solid @borders; } menu menuitem, .menu menuitem { padding: 2px; border: 1px solid transparent; } menu menuitem:hover, .menu menuitem:hover { border: 1px solid @menubaritem_border; background-color: @menubaritem_bg_color; color: @theme_fg_color;} menu menuitem:disabled, .menu menuitem:disabled { color: #7f7f7f; } menu menuitem arrow, .menu menuitem arrow { min-height: 16px; min-width: 16px; } menu menuitem arrow:dir(ltr), .menu menuitem arrow:dir(ltr) { -gtk-icon-source: -gtk-icontheme("pan-end-symbolic"); } menu menuitem arrow:dir(rtl), .menu menuitem arrow:dir(rtl) { -gtk-icon-source: -gtk-icontheme("pan-end-symbolic-rtl"); } menuitem accelerator { color: alpha(currentColor,0.55); } /* todo */ menuitem check, menuitem radio { min-height: 16px; min-width: 16px; } menuitem check:dir(ltr), menuitem radio:dir(ltr) { margin-right: 7px; } menuitem check:dir(rtl), menuitem radio:dir(rtl) { margin-left: 7px; } /*************** * Popovers * ***************/ popover { margin: 3px; padding: 2px; border-color: @borders; border-width: 1px; border-style: solid; border-radius: 3px; background-color: @theme_bg_color; box-shadow: 0 2px 3px rgba(0, 0, 0, 0.5); } popover > .list, popover > .view, popover > toolbar { background-color: transparent; } popover separator { font-size: 80%; font-weight: bold; color: #cccccc; text-shadow: none; background-color: shade (@theme_bg_color, 0.87); -gtk-icon-shadow: none; border: 0; } popover button.flat, popover button.flat:hover { color: @theme_fg_color; text-shadow: none; transition: none; } popoverosd { background-image: none; background-color: rgba(0, 0, 0, 0.8); border: 1px solid #7f7f7f; color: @theme_fg_color; } popoverosd button { color: @theme_fg_color; text-shadow: none; border-width: 1px; border-style: solid; background-image: none; background-color: rgba(0, 0, 0, 0.8); border-color: rgba(255, 255, 255, 0.2); box-shadow: none; } popoverosd button:hover { border-width: 1px; border-style: solid; background-color: rgba(255, 255, 255, 0.01); border-color: rgba(255, 255, 255, 0.2); box-shadow: none; } popoverosd button:active { border-width: 1px; border-style: solid; background-color: rgba(255, 255, 255, 0.03); border-color: rgba(255, 255, 255, 0.2); box-shadow: none; } popoverosd button:disabled { border-width: 1px; border-style: solid; border-color: rgba(255, 255, 255, 0.2); color: #7f7f7f; } .cursor-handle { background-color: transparent; background-image: none; } .cursor-handle.top { -gtk-icon-source: -gtk-icontheme("selection-start-symbolic"); } .cursor-handle.bottom { -gtk-icon-source: -gtk-icontheme("selection-end-symbolic"); } /************* * Notebooks * *************/ notebook { padding: 0; } notebook > header { background-color: @theme_bg_color; } notebook > header.top, notebook > header.bottom { padding-left: 0; padding-right: 0; } notebook > header.left, notebook > header.right { padding-top: 0; padding-bottom: 0; } notebook > header tabs { min-height: 21px; } notebook > header.top { border-bottom-style: solid; } notebook > header.top > tabs { margin-bottom: -1px; } notebook > header > tabs > tab:checked, notebook > header > tabs > tab:checked:hover { color: @theme_fg_color; background-color: @theme_base_color; border: 1px solid shade(@theme_bg_color, 0.72); } notebook > header.top > tabs > tab:hover { box-shadow: none; } notebook > header.top > tabs > tab:checked, notebook > header.top > tabs > tab:checked:hover { border-width: 1px 1px 0 1px; } notebook > header.bottom { border-top-style: solid; } notebook > header.bottom > tabs { margin-top: -1px; } notebook > header.bottom > tabs > tab:hover { } notebook > header.bottom > tabs > tab:checked, notebook > header.bottom > tabs > tab:checked:hover { border-width: 0 1px 1px 1px; } notebook > header.left { border-right-style: solid; } notebook > header.left > tabs { margin-right: -1px; } notebook > header.left > tabs > tab:hover { } notebook > header.left > tabs > tab:checked, notebook > header.left > tabs > tab:checked:hover { border-width: 1px 0 1px 1px; } notebook > header.right { border-left-style: solid; } notebook > header.right > tabs { margin-left: -1px; } notebook > header.right > tabs > tab:hover { } notebook > header.right > tabs > tab:checked, notebook > header.right > tabs > tab:checked:hover { border-width: 1px 1px 1px 0; } notebook > header.top > tabs > arrow { border-top-style: none; } notebook > header.bottom > tabs > arrow { border-bottom-style: none; } notebook > header.top > tabs > arrow, notebook > header.bottom > tabs > arrow { margin-left: -5px; margin-right: -5px; padding-left: 4px; padding-right: 4px; } notebook > header.top > tabs > arrow.down, notebook > header.bottom > tabs > arrow.down { -gtk-icon-source: -gtk-icontheme("pan-start-symbolic"); } notebook > header.top > tabs > arrow.up, notebook > header.bottom > tabs > arrow.up { -gtk-icon-source: -gtk-icontheme("pan-end-symbolic"); } notebook > header.left > tabs > arrow { border-left-style: none; } notebook > header.right > tabs > arrow { border-right-style: none; } notebook > header.left > tabs > arrow, notebook > header.right > tabs > arrow { margin-top: -5px; margin-bottom: -5px; padding-top: 4px; padding-bottom: 4px; } notebook > header.left > tabs > arrow.down, notebook > header.right > tabs > arrow.down { -gtk-icon-source: -gtk-icontheme("pan-up-symbolic"); } notebook > header.left > tabs > arrow.up, notebook > header.right > tabs > arrow.up { -gtk-icon-source: -gtk-icontheme("pan-down-symbolic"); } notebook > header > tabs > arrow { min-height: 16px; min-width: 16px; border-radius: 0; } notebook > header > tabs > arrow:hover:not(:active):not(:backdrop) { background-clip: padding-box; background-image: none; background-color: rgba(255, 255, 255, 0.3); border-color: transparent; box-shadow: none; } notebook > header > tabs > arrow:disabled { border-color: transparent; background-color: transparent; background-image: none; box-shadow: inset 0 1px rgba(255, 255, 255, 0), 0 1px rgba(255, 255, 255, 0); text-shadow: none; -gtk-icon-shadow: none; } notebook > header tab { min-height: 20px; min-width: 20px; padding: 2px 10px; outline-offset: -4px; color: #818181; font-weight: bold; border-width: 1px; border-color: transparent; } notebook > header tab:hover { color: #575757; } notebook > header tab:hover.reorderable-page { border-color: rgba(176, 176, 176, 0); background-color: rgba(247, 247, 247, 0.2); } notebook > header tab:checked { color: #2c2c2c; } notebook > header tab:checked.reorderable-page { border-color: rgba(176, 176, 176, 0.5); background-color: rgba(214, 214, 214, 0.5); } notebook > header tab:checked.reorderable-page:hover { background-color: rgba(214, 214, 214, 0.7); } notebook > header tab button.flat { color: rgba(44, 44, 44, 0.3); padding: 0; margin-top: 4px; margin-bottom: 4px; min-width: 20px; min-height: 20px; } notebook > header tab button.flat:hover { color: currentColor; } notebook > header tab button.flat:last-child { margin-left: 4px; margin-right: -4px; } notebook > header tab button.flat:first-child { margin-left: -4px; margin-right: 4px; } notebook > header.top tabs, notebook > header.bottom tabs { padding-left: 0px; padding-right: 0px; } notebook > header.top tabs:not(:only-child), notebook > header.bottom tabs:not(:only-child) { margin-left: 0px; margin-right: 0px; } notebook > header.top tabs:not(:only-child):first-child, notebook > header.bottom tabs:not(:only-child):first-child { margin-left: 0px; } notebook > header.top tabs:not(:only-child):last-child, notebook > header.bottom tabs:not(:only-child):last-child { margin-right: 0px; } notebook > header.top tabs tab, notebook > header.bottom tabs tab { margin-left: 0px; margin-right: 0px; } notebook > header.top tabs tab.reorderable-page, notebook > header.bottom tabs tab.reorderable-page { border-style: none solid; } notebook > header.left tabs, notebook > header.right tabs { padding-top: 0px; padding-bottom: 0px; } notebook > header.left tabs:not(:only-child), notebook > header.right tabs:not(:only-child) { margin-top: 0px; margin-bottom: 0px; } notebook > header.left tabs:not(:only-child):first-child, notebook > header.right tabs:not(:only-child):first-child { margin-top: 0px; } notebook > header.left tabs:not(:only-child):last-child, notebook > header.right tabs:not(:only-child):last-child { margin-bottom: 0px; } notebook > header.left tabs tab, notebook > header.right tabs tab { margin-top: 0px; margin-bottom: 0px; } notebook > header.left tabs tab.reorderable-page, notebook > header.right tabs tab.reorderable-page { border-style: solid none; } notebook > header.top tab { padding-bottom: 2px; } notebook > header.bottom tab { padding-top: 2px; } notebook > stack:not(:only-child) { border: 1px solid shade(@theme_bg_color, 0.72); background-color: @theme_base_color; } /************** * Scrollbars * **************/ scrollbar, .scrollbar { background-color: @theme_bg_color; border-width: 0; } * { -GtkScrollbar-has-backward-stepper: true; -GtkScrollbar-has-forward-stepper: true; } scrollbar.top, .scrollbar.top { border-bottom: 0px solid @borders; } scrollbar.bottom, .scrollbar.bottom { border-top: 0px solid @borders; } scrollbar.left, .scrollbar.left { border-right: 0px solid @borders; } scrollbar.right, .scrollbar.right { border-left: 0px solid @borders; } scrollbar slider, scrollbar .slider, .scrollbar slider, .scrollbar .slider { min-width: 16px; min-height: 16px; margin: 0px; border: 0; background-clip: padding-box; background-color: shade(@theme_bg_color, 0.7); } scrollbar slider:hover, scrollbar .slider:hover, .scrollbar slider:hover, .scrollbar .slider:hover { background-color: shade(@theme_bg_color, 0.6); } scrollbar slider:hover:active, scrollbar .slider:hover:active, .scrollbar slider:hover:active, .scrollbar .slider:hover:active { background-color: shade(@theme_bg_color, 0.4); } scrollbar slider:disabled, scrollbar .slider:disabled, .scrollbar slider:disabled, .scrollbar .slider:disabled { background-color: transparent; } scrollbar.fine-tune slider, scrollbar.fine-tune .slider, .scrollbar.fine-tune slider, .scrollbar.fine-tune .slider { min-width: 16px; min-height: 16px; } scrollbar.fine-tune.horizontal slider, scrollbar.fine-tune.horizontal .slider, .scrollbar.fine-tune.horizontal slider, .scrollbar.fine-tune.horizontal .slider { border-width: 0; } scrollbar.fine-tune.vertical slider, scrollbar.fine-tune.vertical .slider, .scrollbar.fine-tune.vertical slider, .scrollbar.fine-tune.vertical .slider { border-width: 0; } scrollbar.overlay-indicator:not(.dragging):not(.hovering), .scrollbar.overlay-indicator:not(.dragging):not(.hovering) { border-color: transparent; opacity: 0.4; background-color: transparent; } scrollbar.overlay-indicator:not(.dragging):not(.hovering) slider, scrollbar.overlay-indicator:not(.dragging):not(.hovering) .slider, .scrollbar.overlay-indicator:not(.dragging):not(.hovering) slider, .scrollbar.overlay-indicator:not(.dragging):not(.hovering) .slider { margin: 0; min-width: 16px; min-height: 16px; background-color: #2c2c2c; border: 0px solid white; } scrollbar.overlay-indicator:not(.dragging):not(.hovering) button, scrollbar.overlay-indicator:not(.dragging):not(.hovering) .button, .scrollbar.overlay-indicator:not(.dragging):not(.hovering) button, .scrollbar.overlay-indicator:not(.dragging):not(.hovering) .button { min-width: 16px; min-height: 16px; background-clip: padding-box; border: 1px solid transparent;} scrollbar.overlay-indicator:not(.dragging):not(.hovering).horizontal slider, scrollbar.overlay-indicator:not(.dragging):not(.hovering).horizontal .slider, .scrollbar.overlay-indicator:not(.dragging):not(.hovering).horizontal slider, .scrollbar.overlay-indicator:not(.dragging):not(.hovering).horizontal .slider { margin: 0 0px; border: 0; min-width: 40px; } scrollbar.overlay-indicator:not(.dragging):not(.hovering).horizontal button, scrollbar.overlay-indicator:not(.dragging):not(.hovering).horizontal .button, .scrollbar.overlay-indicator:not(.dragging):not(.hovering).horizontal button, .scrollbar.overlay-indicator:not(.dragging):not(.hovering).horizontal .button { /*margin: -1px;*/ min-width: 16px; } scrollbar.overlay-indicator:not(.dragging):not(.hovering).vertical slider, scrollbar.overlay-indicator:not(.dragging):not(.hovering).vertical .slider, .scrollbar.overlay-indicator:not(.dragging):not(.hovering).vertical slider, .scrollbar.overlay-indicator:not(.dragging):not(.hovering).vertical .slider { margin: 0px 0; border: 0; min-height: 40px; } scrollbar.overlay-indicator:not(.dragging):not(.hovering).vertical button, scrollbar.overlay-indicator:not(.dragging):not(.hovering).vertical .button, .scrollbar.overlay-indicator:not(.dragging):not(.hovering).vertical button, .scrollbar.overlay-indicator:not(.dragging):not(.hovering).vertical .button { /*margin: -1px;*/ min-height: 16px; } scrollbar.overlay-indicator.dragging, scrollbar.overlay-indicator.hovering, .scrollbar.overlay-indicator.dragging, .scrollbar.overlay-indicator.hovering { opacity: 0.8; } scrollbar.horizontal slider, scrollbar.horizontal .slider, .scrollbar.horizontal slider, .scrollbar.horizontal .slider { min-width: 40px; } scrollbar.vertical slider, scrollbar.vertical .slider, .scrollbar.vertical slider, .scrollbar.vertical .slider { min-height: 40px; } scrollbar button, scrollbar .button, .scrollbar button, .scrollbar .button { padding: 0; min-width: 16px; min-height: 16px; border: 1px solid @borders; transition-property: min-height, min-width, color; box-shadow: none; color: #707070; } scrollbar button:hover, scrollbar .button:hover, .scrollbar button:hover, .scrollbar .button:hover { color: #4e4e4e; box-shadow: none; } scrollbar button:active, scrollbar button:checked, scrollbar .button:active, scrollbar .button:checked, .scrollbar button:active, .scrollbar button:checked, .scrollbar .button:active, .scrollbar .button:checked { box-shadow: none; color: #0080ff; } scrollbar button:disabled, scrollbar .button:disabled, .scrollbar button:disabled, .scrollbar .button:disabled { color: rgba(112, 112, 112, 0.2); box-shadow: none; } scrollbar.vertical button.down { -gtk-icon-source: -gtk-icontheme("pan-down-symbolic"); } scrollbar.vertical button.up { -gtk-icon-source: -gtk-icontheme("pan-up-symbolic"); } scrollbar.horizontal button.down { -gtk-icon-source: -gtk-icontheme("pan-end-symbolic"); } scrollbar.horizontal button.up { -gtk-icon-source: -gtk-icontheme("pan-start-symbolic"); } treeview ~ scrollbar.vertical { border-top: 1px solid transparent; margin-top: -1px; } /********** * Switch * **********/ switch { font-weight: bold; font-size: smaller; outline-offset: -4px; border: 1px solid @borders; color: @theme_fg_color; background: shade (@theme_bg_color, 0.96); } switch:checked { color: @theme_fg_selected_color; border-color: @border_active; background-image: linear-gradient(to bottom, @gradient_active_s, @gradient_active_e); background: @theme_selected_bg_color; } switch:disabled { color: @insensitive_fg_color; border-color: @insensitive_borders; background-color: @insensitive_bg_color; background-image: none; } switch slider { margin: -1px; border: 1px solid @borders; transition: all 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); color: @theme_fg_color; background: @theme_bg_color; } switch:hover slider { border-color: @border_hover; background-image: none; background: shade(@theme_bg_color, 1.02); } switch:checked slider { border: 1px solid @border_hover; } switch:disabled slider { border-color: @insensitive_borders; background: @insensitive_bg_color; text-shadow: none; -gtk-icon-shadow: none; } switch:disabled slider label, switch:disabled slider { color: #8b8e8f; } row:selected switch { box-shadow: none; border-color: green; } row:selected switch.slider:dir(rtl) { border-left-color: #9d9d99; } row:selected switch.slider:dir(ltr) { border-right-color: #9d9d99; } row:selected switch.slider:checked, row:selected switch.slider { border-color: #184472; } /************************* * Check and Radio items * *************************/ checkbutton > label, radiobutton > label { padding-left: 6px; } checkbutton.text-button, radiobutton.text-button { padding: 1px; outline-offset: 0; } check { color: @theme_fg_color; background-image: none; -gtk-icon-source: -gtk-icontheme("checkbox-symbolic"); } check:hover { color: @theme_selected_bg_color; } check:checked, check:active { -gtk-icon-source: -gtk-icontheme("checkbox-checked-symbolic"); color: @theme_fg_color; } check:checked:hover, check:active:hover { color: #7f7f7f; } check:checked:disabled, check:active:disabled { color: @insensitive_fg_color; } check:disabled { color: rgba(127, 127, 127, 0.1); } check:indeterminate { -gtk-icon-source: -gtk-icontheme("checkbox-mixed-symbolic"); } radio { color: @theme_fg_color; background-image: none; -gtk-icon-source: -gtk-icontheme("radio-symbolic"); } radio:hover { color: @theme_selected_bg_color; } radio:checked, radio:active { -gtk-icon-source: -gtk-icontheme("radio-checked-symbolic"); color: @theme_fg_color; } radio:checked:hover, radio:active:hover { color: #7f7f7f; } radio:checked:disabled, radio:active:disabled { color: @insensitive_fg_color; } radio:disabled { color: @insensitive_fg_color; } radio:indeterminate { -gtk-icon-source: -gtk-icontheme("radio-mixed-symbolic"); } /************ * GtkScale * ************/ scale trough, scale fill { border: 1px solid @borders; background-color: @theme_bg_color; box-shadow: none; } scale trough:disabled, scale fill:disabled { border-color: @insensitive_borders; background-color: @insensitive_bg_color; } row:selected scale trough, scale row:selected trough, row:selected scale fill, scale row:selected fill, row:selected scale trough:disabled, scale row:selected trough:disabled, row:selected scale fill:disabled, scale row:selected fill:disabled { border-color: @insensitive_borders; } .osd scale trough, scale .osd trough, .osd scale fill, scale .osd fill { border-color: rgba(255, 255, 255, 0.2); background-color: rgba(255, 255, 255, 0); box-shadow: none; outline-color: rgba(255, 255, 255, 0.2); } .osd scale trough:disabled, scale .osd trough:disabled, .osd scale fill:disabled, scale .osd fill:disabled { background-color: @insensitive_borders; } scale highlight { border: 1px solid @borders; background-color: @theme_selected_bg_color; } scale highlight:disabled { background-color: transparent; border-color: transparent; } row:selected scale highlight, scale row:selected highlight, row:selected scale highlight:disabled, scale row:selected highlight:disabled { border-color: #000; } .osd scale highlight, scale .osd highlight { border-color: rgba(255, 255, 255, 0.2); } .osd scale highlight:disabled, scale .osd highlight:disabled { border-color: transparent; } scale { min-height: 10px; min-width: 10px; padding: 12px; } scale fill, scale highlight { margin: -1px; } scale slider { /*min-height: 19px; min-width: 19px;*/ background-repeat: no-repeat; background-position: center; margin: -8px; } scale.fine-tune.horizontal { padding-top: 9px; padding-bottom: 9px; min-height: 16px; } scale.fine-tune.vertical { padding-left: 9px; padding-right: 9px; min-width: 16px; } scale.fine-tune slider { margin: -6px; } scale.fine-tune fill, scale.fine-tune highlight, scale.fine-tune trough { } scale trough { } scale.horizontal slider { background-image: url("assets/slider-horiz.png"); min-width: 11px; min-height: 19px;} scale.horizontal slider:hover { background-image: url("assets/slider-horiz-over.png"); } scale slider:active { } scale.horizontal slider:disabled { background-image: url("assets/slider-horiz-insensitive.png"); } scale.vertical slider { background-image: url("assets/slider-vert.png"); min-width: 19px; min-height: 11px; } scale.vertical slider:hover { background-image: url("assets/slider-vert-over.png"); } scale slider:active { } scale.vertical slider:disabled { background-image: url("assets/slider-vert-insensitive.png"); } row:selected scale slider, row:selected scale slider:disabled { border-color: @insensitive_borders; } scale value { font-size: smaller; color: @theme_fg_color; } scale marks { color: @theme_fg_color; } scale marks.top { margin-bottom: 6px; margin-top: -12px; } scale marks.bottom { margin-top: 6px; margin-bottom: -12px; } scale marks.top { margin-right: 6px; margin-left: -12px; } scale marks.bottom { margin-left: 6px; margin-right: -12px; } scale.fine-tune marks.top { margin-bottom: 6px; margin-top: -9px; } scale.fine-tune marks.bottom { margin-top: 6px; margin-bottom: -9px; } scale.fine-tune marks.top { margin-right: 6px; margin-left: -9px; } scale.fine-tune marks.bottom { margin-left: 6px; margin-right: -9px; } scale.horizontal indicator { min-height: 6px; min-width: 1px; } scale.horizontal.fine-tune indicator { min-height: 3px; } scale.vertical indicator { min-height: 1px; min-width: 6px; } scale.vertical.fine-tune indicator { min-width: 3px; } /***************** * Progress bars * *****************/ progressbar { font-size: smaller; padding: 0; color: @theme_fg_color; } progressbar trough { border-width: 1px; border-style: solid; border-color: @borders; background-color: shade (@theme_bg_color, 0.96); } progressbar.osd trough { background-color: transparent; box-shadow: none; border-width: 0; } progressbar.horizontal trough, progressbar.horizontal progress { min-height: 16px; } progressbar.vertical trough, progressbar.vertical progress { min-width: 16px; } progressbar progress { background-color: @progress_color; } progressbar progress.vertical { } progressbar progress.osd { border-width: 0; border-radius: 0; } /************* * Level Bar * *************/ levelbar trough { padding: 2px; background-color: transparent; border-style: solid; border-color: @borders; } levelbar block { min-width: 34px; min-height: 3px; } levelbar.vertical block { min-width: 3px; min-height: 34px; } levelbar.horizontal.discrete block { margin: 0 1px; } levelbar.vertical.discrete block { margin: 1px 0; } levelbar block.filled.low { border-color: #c26000; background-color: #f57900; } levelbar block.filled.high { border-width: 1px; border-style: solid; border-color: @borders; background-color: @theme_selected_bg_color; } levelbar block.filled.full { border-color: #5aa411; background-color: #73d216; } levelbar block.empty { border-width: 1px; border-style: solid; background-color: transparent; border-color: rgba(0, 0, 0, 0.2); border-radius: 1px; box-shadow: none; } .view:selected, textview text:selected, iconview:selected, calendar:selected, .view:selected:focus, textview text:selected:focus, iconview:selected:focus, calendar:focus:selected, textview text:selected:hover, calendar:hover:selected, textview text selection, textview text selection:focus, textview text selection:hover, flowbox flowboxchild:selected, label selection, label selection:focus, label selection:hover, label selection:backdrop, spinbutton:not(.vertical) selection:focus, spinbutton:not(.vertical) selection, entry selection:focus, entry selection, modelbutton.flat:selected, popover.background checkbutton:selected, popover.background radiobutton:selected, .menuitem.button.flat:selected, treeview.view:selected, row.activatable:selected, .sidebar:selected { background-color: @theme_selected_bg_color; color: @theme_selected_fg_color; } /********** * Frames * **********/ frame > border{ border: 1px solid @borders; padding: 0; } frame > border.flat, .frame.flat { border-style: none; } frame.action-bar { padding: 6px; border-width: 1px 0 0; } .frame { border: 0; } .frame:not(notebook) { border: 1px solid @borders; } actionbar > revealer > box { padding: 2px; border-width: 1px 0 0; border-color: @borders; border-style: solid none none; } scrolledwindow viewport.frame { border-style: none; } scrolledwindow overshoot.top { background-image: -gtk-gradient(radial, center top, 0, center top, 0.5, to(#9d9d99), to(rgba(157, 157, 153, 0))), -gtk-gradient(radial, center top, 0, center top, 0.6, from(rgba(46, 52, 54, 0.07)), to(rgba(46, 52, 54, 0))); background-size: 100% 5%, 100% 100%; background-repeat: no-repeat; background-position: center top; background-color: transparent; border: none; box-shadow: none; } scrolledwindow overshoot.top:backdrop { background-image: -gtk-gradient(radial, center top, 0, center top, 0.5, to(#c0c0bd), to(rgba(192, 192, 189, 0))); background-size: 100% 5%; background-repeat: no-repeat; background-position: center top; background-color: transparent; border: none; box-shadow: none; } scrolledwindow overshoot.bottom { background-image: -gtk-gradient(radial, center bottom, 0, center bottom, 0.5, to(#9d9d99), to(rgba(157, 157, 153, 0))), -gtk-gradient(radial, center bottom, 0, center bottom, 0.6, from(rgba(46, 52, 54, 0.07)), to(rgba(46, 52, 54, 0))); background-size: 100% 5%, 100% 100%; background-repeat: no-repeat; background-position: center bottom; background-color: transparent; border: none; box-shadow: none; } scrolledwindow overshoot.bottom:backdrop { background-image: -gtk-gradient(radial, center bottom, 0, center bottom, 0.5, to(#c0c0bd), to(rgba(192, 192, 189, 0))); background-size: 100% 5%; background-repeat: no-repeat; background-position: center bottom; background-color: transparent; border: none; box-shadow: none; } scrolledwindow overshoot.left { background-image: -gtk-gradient(radial, left center, 0, left center, 0.5, to(#9d9d99), to(rgba(157, 157, 153, 0))), -gtk-gradient(radial, left center, 0, left center, 0.6, from(rgba(46, 52, 54, 0.07)), to(rgba(46, 52, 54, 0))); background-size: 5% 100%, 100% 100%; background-repeat: no-repeat; background-position: left center; background-color: transparent; border: none; box-shadow: none; } scrolledwindow overshoot.left:backdrop { background-image: -gtk-gradient(radial, left center, 0, left center, 0.5, to(#c0c0bd), to(rgba(192, 192, 189, 0))); background-size: 5% 100%; background-repeat: no-repeat; background-position: left center; background-color: transparent; border: none; box-shadow: none; } scrolledwindow overshoot.right { background-image: -gtk-gradient(radial, right center, 0, right center, 0.5, to(#9d9d99), to(rgba(157, 157, 153, 0))), -gtk-gradient(radial, right center, 0, right center, 0.6, from(rgba(46, 52, 54, 0.07)), to(rgba(46, 52, 54, 0))); background-size: 5% 100%, 100% 100%; background-repeat: no-repeat; background-position: right center; background-color: transparent; border: none; box-shadow: none; } scrolledwindow overshoot.right:backdrop { background-image: -gtk-gradient(radial, right center, 0, right center, 0.5, to(#c0c0bd), to(rgba(192, 192, 189, 0))); background-size: 5% 100%; background-repeat: no-repeat; background-position: right center; background-color: transparent; border: none; box-shadow: none; } scrolledwindow undershoot.top { background-color: transparent; background-image: linear-gradient(to left, rgba(255, 255, 255, 0.2) 50%, rgba(0, 0, 0, 0.2) 50%); padding-top: 1px; background-size: 10px 1px; background-repeat: repeat-x; background-origin: content-box; background-position: center top; border: none; box-shadow: none; } scrolledwindow undershoot.bottom { background-color: transparent; background-image: linear-gradient(to left, rgba(255, 255, 255, 0.2) 50%, rgba(0, 0, 0, 0.2) 50%); padding-bottom: 1px; background-size: 10px 1px; background-repeat: repeat-x; background-origin: content-box; background-position: center bottom; border: none; box-shadow: none; } scrolledwindow undershoot.left { background-color: transparent; background-image: linear-gradient(to top, rgba(255, 255, 255, 0.2) 50%, rgba(0, 0, 0, 0.2) 50%); padding-left: 1px; background-size: 1px 10px; background-repeat: repeat-y; background-origin: content-box; background-position: left center; border: none; box-shadow: none; } scrolledwindow undershoot.right { background-color: transparent; background-image: linear-gradient(to top, rgba(255, 255, 255, 0.2) 50%, rgba(0, 0, 0, 0.2) 50%); padding-right: 1px; background-size: 1px 10px; background-repeat: repeat-y; background-origin: content-box; background-position: right center; border: none; box-shadow: none; } scrolledwindow junction { border-color: transparent; border-image: linear-gradient(to bottom, #b6b6b3 1px, transparent 1px) 0 0 0 1/0 1px stretch; background-color: #c3c4c4; } scrolledwindow junction:dir(rtl) { border-image-slice: 0 1 0 0; } scrolledwindow junction:backdrop { border-image-source: linear-gradient(to bottom, #c0c0bd 1px, transparent 1px); background-color: #e1e1df; transition: 200ms ease-out; } separator { background: rgba(0, 0, 0, 0.1); min-width: 1px; min-height: 1px; } /********* * Lists * *********/ list { background-color: @theme_base_color; border-color: @borders; } list row { padding: 2px; } row.activatable, row.activatable:disabled:active, row.activatable:disabled:checked { background-color: @theme_base_color; border-style: none; border-radius: 0; box-shadow: none; } row.activatable:hover { background-color: shade(@theme_base_color, 0.92); } row.activatable:active { } row.activatable:selected { background-color: @theme_selected_bg_color; color: @theme_selected_fg_color; } row.activatable:selected:active { background-color: alpha(@theme_selected_bg_color, 0.15); } row.activatable:selected:hover { background-color: alpha(@theme_selected_bg_color, 0.33); } row, row.activatable { transition: all 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94); } row:hover, row.activatable:hover { transition: none; } /********************* * App Notifications * *********************/ .app-notification, .app-notification.frame { border-width: 0 1px 1px; border-style: solid; border-color: @borders; border-radius: 0 0 6px 6px; background-color: alpha(@theme_fg_color, 0.2); /*padding: 0 4px;*/ } app-notification button { padding: 6px; } /************* * Expanders * *************/ expander arrow { min-width: 16px; min-height: 16px; -gtk-icon-source: -gtk-icontheme("pan-end-symbolic"); } expander arrow:dir(rtl) { -gtk-icon-source: -gtk-icontheme("pan-end-symbolic-rtl"); } expander arrow:hover { color: #4d4d4d; } expander arrow:checked { -gtk-icon-source: -gtk-icontheme("pan-down-symbolic"); } /************ * Calendar * ***********/ calendar { border: 1px solid @borders; } calendar.button { padding: 0 4px; color: shade(@theme_bg_color, 0.85); } calendar.button:hover { color: @theme_fg_color; } calendar.header { border-bottom-color: shade(@theme_bg_color, 0.85); } calendar:disabled { color: @insensitive_fg_color; border-color: @insensitive_borders; } calendar:selected, calendar:selected:focus { background-color: @theme_selected_bg_color; color: @theme_selected_fg_color; } /*********** * Dialogs * ***********/ messagedialog .dialog-action-area button { padding: 6px; } messagedialog { /*-GtkDialog-button-spacing: 0;*/ } messagedialog titlebar { border-style: none; box-shadow: inset 0 1px @theme_fg_color; } message-dialog.csd { border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; } message-dialog.csd dialog-action-area button { padding: 12px; border-radius: 0; border-width: 1px; border-style: solid; color: @theme_fg_color; background-image: linear-gradient(to bottom, @gradient_default_s, @gradient_default_e); border-color: @borders; } message-dialog.csd dialog-action-area button:hover { border-width: 1px; border-style: solid; color: @theme_fg_color; border-color: @borders; background-image: linear-gradient(to bottom, @gradient_hover_s, @gradient_hover_e); } message-dialog.csd dialog-action-area button:active { border-width: 1px; border-style: solid; border-color: @borders; background-image: linear-gradient(to bottom, @gradient_active_s, @gradient_active_e); color: @theme_fg_color; } message-dialog.csd dialog-action-area button:disabled { border-width: 1px; border-style: solid; color: @insensitive_fg_color; border-color: @insensitive_borders; background-color: @insensitive_bg_color; text-shadow: none; -gtk-icon-shadow: none; } messagedialog.csd dialog-action-area button.suggested-action { border-width: 1px; border-style: solid; color: @theme_fg_color; background-image: linear-gradient(to bottom, #5f9ddd, #4a90d9 40%, #3583d5); border-color: #1c5187; } messagedialog.csd dialog-action-area button.suggested-action:hover { border-width: 1px; border-style: solid; color: @theme_fg_color; border-color: #1c5187; background-image: linear-gradient(to bottom, #85b4e5, #5b9add 40%, #4a90d9); } messagedialog.csd dialog-action-area button.suggested-action:active { border-width: 1px; border-style: solid; border-color: #1c5187; background-color: #1c5187; color: @theme_fg_color; } messagedialog.csd dialog-action-area button.suggested-action:disabled { border-width: 1px; border-style: solid; color: #7f7f7f; border-color: @borders; background-color: @theme_fg_color; text-shadow: none; -gtk-icon-shadow: none; } messagedialog.csd dialog-action-area button.destructive-action { border-width: 1px; border-style: solid; color: @theme_fg_color; background-image: linear-gradient(to bottom, #f14141, #ef2929 40%, #ed1212); border-color: #8e0b0b; } message-dialog.csd dialog-action-area button.destructive-action:hover { border-width: 1px; border-style: solid; color: @theme_fg_color; border-color: #8e0b0b; background-image: linear-gradient(to bottom, #f46b6b, #f03c3c 40%, #ef2929); } messagedialog.csd dialog-action-area button.destructive-action:active { border-width: 1px; border-style: solid; border-color: #8e0b0b; background-color: #8e0b0b; color: @theme_fg_color; } message-dialog.csd dialog-action-area button.destructive-action:disabled { border-width: 1px; border-style: solid; color: #7f7f7f; border-color: @borders; background-color: @theme_fg_color; text-shadow: none; -gtk-icon-shadow: none; } message-dialog.csd dialog-action-area button, message-dialog.csd dialog-action-area button:hover, message-dialog.csd dialog-action-area button:active, message-dialog.csd dialog-action-area button:disabled, message-dialog.csd dialog-action-area button.suggested-action, message-dialog.csd dialog-action-area button.suggested-action:hover, message-dialog.csd dialog-action-area button.suggested-action:active, message-dialog.csd dialog-action-area button.suggested-action:disabled, message-dialog.csd dialog-action-area button.destructive-action, .message-dialog.csd .dialog-action-area button.destructive-action:hover, .message-dialog.csd .dialog-action-area button.destructive-action:active, message-dialog.csd dialog-action-area button.destructive-action:disabled { /*border-right-style: none;*/ border-bottom-style: none; } message-dialog.csd dialog-action-area button:last-child { border-bottom-right-radius: 7px; } message-dialog.csd dialog-action-area button:first-child { /*border-left-style: none;*/ border-bottom-left-radius: 7px; } filechooser search-bar { background-color: @theme_bg_color; border-color: @theme_fg_color; box-shadow: none; } GtkFileChooserDialog dialog-action-box { border-top: 1px solid shade (@theme_bg_color, 0.87); } GtkAssistant sidebar { background-color: @insensitive_fg_color; } GtkAssistant sidebar:dir(ltr) { border-right: 1px solid @borders; } GtkAssistant sidebar:dir(rtl) { border-left: 1px solid @borders; } GtkAssistant sidebar label { padding: 6px 12px; } GtkAssistant sidebar label.highlight { font-weight: bold; background-color: shade (@theme_bg_color, 1.02); } /*********** * Sidebar * ***********/ .sidebar { border: none; } .sidebar:backdrop { background-color: @theme_bg_color; } .sidebar > scrolledwindow > .frame { border-right: 1px solid shade (@theme_bg_color, 0.87); } .sidebar > scrolledwindow > .frame:dir(rtl) { border-right: none; border-left: 1px solid shade (@theme_bg_color, 0.87); } stacksidebar row { padding: 5px 2px; } stacksidebar row > label { padding-left: 6px; padding-right: 6px; } stacksidebar row.needs-attention > label { background-size: 6px 6px, 0 0; } /**************** * File chooser * ****************/ /********* * Paned * *********/ paned separator, paned separator.vertical { -gtk-icon-source: none; background-color: shade(@theme_bg_color, 1.0); min-width: 3px; min-height: 3px; } paned separator:hover, paned separator.vertical:hover { background-color: alpha(@theme_selected_bg_color, 0.15); } /************** * GtkInfoBar * **************/ infobar { border-style: none; } .info, .question, .warning, .error { background-color: @theme_selected_bg_color; color: @theme_bg_color; text-shadow: 0 1px @borders; border-color: @theme_fg_color; } .question { background-color: @question_color; } .warning { background-color: @warning_color; } .error { background-color: @error_color; } /************ * Tooltips * ************/ tooltip { border-width: 1px; border-style: solid; border-color: alpha(@tooltip_border, 0.9); border-radius: 3px; /*background-color: alpha(@gradient_tootip_e, 0.9);*/ background-image: -gtk-gradient(linear, left top, left bottom, from (@gradient_tootip_s), to (@gradient_tootip_e) ); color: #414C4F; } tooltip * { background-color: transparent; } /***************** * Color Chooser * *****************/ colorswatch { border: 1px solid @borders; } colorswatch.color-light:hover { background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0) 40%, rgba(255, 255, 255, 0.4)); } colorswatch.color-dark:hover { background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0) 40%, rgba(255, 255, 255, 0.2)); } colorswatch:hover { border-color: @border_hover; } colorswatch.color-active-badge { border-width: 1px; } colorswatch.color-active-badge:hover { background-image: none; } colorswatch.color-active-badge.color-light, colorswatch.color-active-badge.color-light:hover { color: rgba(0, 0, 0, 0.3); border-color: rgba(0, 0, 0, 0.3); } colorswatch.color-active-badge.color-dark, colorswatch.color-active-badge.color-dark:hover { color: rgba(255, 255, 255, 0.5); border-color: rgba(255, 255, 255, 0.5); } GtkColorChooserWidget #add-color-button { border-color: #bfbfbf; background-color: #bfbfbf; color: @theme_fg_color; box-shadow: none; } GtkColorChooserWidget #add-color-button:hover { border-color: @borders; background-color: #7f7f7f; } /******** * Misc * ********/ /************ * Backdrop * ************/ entry:backdrop:not(:disabled), button:backdrop:not(:disabled), check:backdrop:not(:disabled), radio:backdrop:not(:disabled), label:backdrop:not(:disabled), spinbutton:backdrop:not(:disabled), row:backdrop:not(:disabled), view:backdrop:not(:disabled), .view:backdrop:not(:disabled), text:backdrop, calendar:backdrop:not(:disabled), switch:backdrop:not(:disabled), spinner:backdrop, scale:backdrop marks, scale:backdrop value { color: alpha(@theme_fg_color, 0.66); } .view:selected:backdrop, row.activatable:selected:backdrop, calendar:selected:backdrop, switch:checked:backdrop, text selection:backdrop, scale:backdrop highlight:not(:disabled) { background-color: darker(@theme_bg_color); } button:backdrop, switch:checked:backdrop, switch:checked:backdrop slider { border-color: alpha(@borders, 0.66); background-image: none; background-color: @theme_bg_color; } toolbutton .image-button:backdrop { border-color:transparent; } .titlebutton:backdrop { background-color: transparent; } progressbar:backdrop:not(:disabled) progress, button:active:backdrop:not(:disabled), button:checked:backdrop:not(:disabled) { background-image: none; background-color: alpha(darker(@theme_bg_color), 0.66); } /********************** * Window Decorations * *********************/ decoration { /*border-radius: 7px 7px 0 0;*/ border-width: 0px; box-shadow: 0 3px 9px 1px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(0, 0, 0, 0.23); /* this is used for the resize cursor area */ margin: 10px; } decoration.tiled { border-radius: 0; } decoration.csd.popup { border-radius: 0; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.13); } decoration.csd.tooltip { border-radius: 5px; box-shadow: none; } decoration.solid-csd { border-radius: 0; margin: 4px; background-color: #ededed; border: solid 1px #a8a8a8; box-shadow: none; } button.titlebutton { padding: 6px; border-color: transparent; background-image: none; transition-property: border, box-shadow, color; } button.titlebutton:hover { color: @theme_fg_color; background-color: shade(@theme_bg_color,0.96);} button.titlebutton:active { color: @theme_fg_color; background-color: shade(@theme_bg_color,0.85);} button.titlebutton.close:hover { color: @theme_base_color; background-color: #e81123;} button.titlebutton.close:active { color: @theme_base_color; background-color: #f1707a;} /********************** * Touch Copy & Paste * *********************/ button.circular { border-radius: 9999px; -gtk-outline-radius: 9999px; } button.circular label { padding: 0; } PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/gtk-dark.css000066400000000000000000000060101363064711000246040ustar00rootroot00000000000000/* # name: Windows 8 # author: Maxime Doyen */ /* dark expected windows 8 */ @define-color theme_base_color #2C2E2F; @define-color theme_bg_color #383a3b; @define-color theme_fg_color #c0c0c0; @define-color theme_text_color @theme_fg_color; @define-color theme_selected_bg_color #0079cb; @define-color theme_selected_fg_color #e9e9e9; /* dark visualstudio 2013 @define-color @define-color theme_base_color #252526; @define-color theme_bg_color #2d2d30; @define-color theme_fg_color #f0f0f0; @define-color theme_text_color @theme_fg_color; @define-color theme_selected_bg_color #0079cb; @define-color theme_selected_fg_color #fefefe; */ @define-color borders shade(@theme_bg_color, 0.68); /* #ACACAC; */ @define-color insensitive_bg_color shade (@theme_bg_color, 0.99); @define-color insensitive_fg_color shade (@theme_bg_color, 0.72); @define-color insensitive_borders shade (@theme_bg_color, 0.84); /* #bbbbbb; */ /*@define-color unfocused_borders #8b8b8b;*/ @define-color sidebar_bg @theme_base_color; @define-color sidebar_bg_unfocused shade(@theme_bg_color, 0.67); @define-color warning_color #86501b; @define-color question_color #634270; @define-color error_color #6f1616; @define-color success_color #4c7523; /* windows 8 colors */ @define-color gradient_default_s @theme_bg_color; @define-color gradient_default_e shade(@theme_bg_color, 0.90); @define-color border_hover #1484cf; @define-color gradient_hover_s #14537f; @define-color gradient_hover_e #144c74; @define-color border_active #0079cb; @define-color gradient_active_s #004474; @define-color gradient_active_e #003d68; @define-color progress_color #187918; @define-color progress_bg shade(@theme_bg_color, 0.906); /*#e6e6e6;*/ @define-color tootip_border shade(@theme_bg_color, 0.47); /*#767676;*/ @define-color gradient_tootip_s #ffffff; @define-color gradient_tootip_e #e4ecf7; /* menubaritem hover */ @define-color menubaritem_border #007acc; @define-color menubaritem_bg_color #0079cb; /* menuitem hover */ @define-color menuitem_border #007acc; @define-color menuitem_bg_color #0079cb; /* widget colors */ @define-color menubar_bg_color #f5f6f7; @define-color menubar_fg_color @theme_fg_color; @define-color toolbar_bg_color @theme_bg_color; @define-color toolbar_fg_color @theme_fg_color; @define-color menu_border_color #979797; @define-color menu_bg_color @theme_bg_color; @define-color menu_fg_color @theme_fg_color; @define-color panel_bg_color @theme_bg_color; @define-color panel_fg_color @theme_fg_color; /* osd */ @define-color osd_base #333; @define-color osd_fg #eee; @define-color osd_bg alpha(@osd_base, 0.8); /* window manager colors */ @define-color wm_border_focused #007acc; @define-color wm_border_unfocused #2d2d30; @define-color wm_title_focused #9a9a9a; @define-color wm_title_unfocused #9a9a9a; @define-color wm_bg_focused #2d2d30; @define-color wm_bg_unfocused #2d2d30; /*# sourceMappingURL=gtk.css.map */ @import url("gtk-contained.css"); @import url("gtk-widgets-assets-dark.css"); @import url("apps/gnome-applications.css"); @import url("apps/unity.css"); PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/gtk-widgets-assets-dark.css000066400000000000000000000015561363064711000275620ustar00rootroot00000000000000/* # name: Windows 8 # author: Maxime Doyen */ /********************* * GtkScale's slider * *********************/ scale.horizontal slider { background-image: url("assets/slider-horiz-dark.png"); min-width: 11px; min-height: 19px;} scale.horizontal slider:hover { background-image: url("assets/slider-horiz-over-dark.png"); } scale slider:active { } scale.horizontal slider:disabled { background-image: url("assets/slider-horiz-insensitive-dark.png"); } scale.vertical slider { background-image: url("assets/slider-vert-dark.png"); min-width: 19px; min-height: 11px; } scale.vertical slider:hover { background-image: url("assets/slider-vert-over-dark.png"); } scale slider:active { } scale.vertical slider:disabled { background-image: url("assets/slider-vert-insensitive-dark.png"); } PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/gtk-widgets-assets.css000066400000000000000000000106421363064711000266370ustar00rootroot00000000000000/* # name: Windows 8 # author: Maxime Doyen */ /******************* * check and radio * *******************/ /* draw regular check and radio items using our assets */ check, check row:selected, check row:selected:focus { background-image: url("assets/checkbox-unchecked.png"); } check:insensitive, check row:selected:insensitive, check row:selected:focus:insensitive { background-image: url("assets/checkbox-unchecked-insensitive.png"); } check:hover, check row:selected:hover, check row:selected:focus:hover { background-image: url("assets/checkbox-unchecked-over.png"); } check:active, check row:selected:active, check row:selected:focus:active { background-image: url("assets/checkbox-checked.png"); } check:active:hover, check row:selected:active:hover, check row:selected:focus:active:hover { background-image: url("assets/checkbox-checked-over.png"); } check:active:insensitive, check row:selected:active:insensitive, check row:selected:focus:active:insensitive { background-image: url("assets/checkbox-checked-insensitive.png"); } check:inconsistent, check row:selected:inconsistent, check row:selected:focus:inconsistent { background-image: url("assets/checkbox-mixed.png"); } check:inconsistent:insensitive, check row:selected:inconsistent:insensitive, check row:selected:focus:inconsistent:insensitive { background-image: url("assets/checkbox-mixed-insensitive.png"); } radio, .view.cell.radio { background-image: url("assets/radio-unselected.png"); } radio:insensitive { background-image: url("assets/radio-unselected-insensitive.png"); } radio:hover { background-image: url("assets/radio-unselected-over.png"); } radio:active { background-image: url("assets/radio-selected.png"); } radio:active:hover { background-image: url("assets/radio-selected-over.png"); } radio:active:insensitive { background-image: url("assets/radio-selected-insensitive.png"); } radio:inconsistent { background-image: url("assets/radio-mixed.png"); } radio:inconsistent:insensitive { background-image: url("assets/radio-mixed-insensitive.png"); } /********************* * GtkScale's slider * *********************/ .scale.slider.horizontal, .scale.scale-has-marks-above.slider, .scale.scale-has-marks-below.slider, .scale.scale-has-marks-above.slider.horizontal, .scale.scale-has-marks-below.slider.horizontal { background-image: url("./assets/slider-horiz.png"); } .scale.slider.horizontal:hover, .scale.scale-has-marks-above.slider:hover, .scale.scale-has-marks-below.slider:hover, .scale.scale-has-marks-above.slider.horizontal:hover, .scale.scale-has-marks-below.slider.horizontal:hover { background-image: url("./assets/slider-horiz-over.png"); } .scale.slider.horizontal:insensitive, .scale.scale-has-marks-above.slider:insensitive, .scale.scale-has-marks-below.slider:insensitive, .scale.scale-has-marks-above.slider.horizontal:insensitive, .scale.scale-has-marks-below.slider.horizontal:insensitive { background-image: url("./assets/slider-horiz-insensitive.png"); } .scale.slider.vertical, .scale.scale-has-marks-above.slider.vertical, .scale.scale-has-marks-below.slider.vertical { background-image: url("assets/slider-vert.png"); } .scale.slider.vertical:hover, .scale.slider.vertical:active, .scale.slider.vertical:active:hover { background-image: url("assets/slider-vert-over.png"); } .scale.slider.vertical:insensitive, .scale.slider.vertical:insensitive:hover { background-image: url("assets/slider-vert-insensitive.png"); } /******************************** * Touch text selection handles * ********************************/ .cursor-handle.bottom { background-image: url("assets/slider-horiz.png"); } .cursor-handle.top { background-image: url("assets/slider-horiz.png"); } /* menuitem */ .menuitem.check:active { background-image: url("assets/menuitem-checkbox-checked.png"); } .menuitem.check:active:insensitive { background-image: url("assets/menuitem-checkbox-checked-insensitive.png"); } .menuitem.check:inconsistent, .menuitem.radio:inconsistent { background-image: url("assets/menuitem-checkbox-mixed.png"); } .menuitem.check:inconsistent:insensitive, .menuitem.radio:inconsistent:insensitive { background-image: url("assets/menuitem-checkbox-mixed-insensitive.png"); } .menuitem.radio:active { background-image: url("assets/menuitem-radio-checked.png"); } .menuitem.radio:active:insensitive { background-image: url("assets/menuitem-radio-checked-insensitive.png"); } PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/gtk.css000066400000000000000000000054371363064711000237010ustar00rootroot00000000000000/* # name: Windows 8 # author: Maxime Doyen # date: May 20th, 2018 # version: 1.6 # description: Windows 8 gtk theme */ /* GTK NAMED COLORS */ /* @define-color */ @define-color theme_base_color #fefefe; @define-color theme_bg_color #f0f0f0; @define-color theme_fg_color #222222; @define-color theme_text_color @theme_fg_color; @define-color theme_selected_bg_color #3399ff; @define-color theme_selected_fg_color #ffffff; @define-color borders shade(@theme_bg_color, 0.68); /* #ACACAC; */ @define-color insensitive_bg_color shade (@theme_bg_color, 0.99); @define-color insensitive_fg_color shade (@theme_bg_color, 0.72); @define-color insensitive_borders shade (@theme_bg_color, 0.84); /*@define-color unfocused_borders #8b8b8b;*/ @define-color sidebar_bg @theme_base_color; @define-color sidebar_bg_unfocused shade(@theme_bg_color, 0.67); @define-color warning_color #f57900; @define-color question_color #9b59b6; @define-color error_color #cc0000; @define-color success_color #73d216; /* windows 8 colors */ @define-color gradient_default_s @theme_bg_color; @define-color gradient_default_e shade(@theme_bg_color, 0.90); @define-color border_hover #7eb4ea; @define-color gradient_hover_s #ecf4fc; @define-color gradient_hover_e #dcecfc; @define-color border_active #569DE5; @define-color gradient_active_s #DAECFC; @define-color gradient_active_e #C4E0FC; @define-color progress_color #06B025; @define-color progress_bg shade(@theme_bg_color, 0.906); /*#e6e6e6;*/ @define-color tootip_border shade(@theme_bg_color, 0.47); /*#767676;*/ @define-color gradient_tootip_s #ffffff; @define-color gradient_tootip_e #e4ecf7; /* menubaritem hover */ @define-color menubaritem_border #62a3e5; @define-color menubaritem_bg_color #b8d8f9; /* menuitem hover */ @define-color menuitem_border #78aee5; @define-color menuitem_bg_color #d1e2f2; /* widget colors */ @define-color menubar_bg_color #f5f6f7; @define-color menubar_fg_color @theme_fg_color; @define-color toolbar_bg_color @theme_bg_color; @define-color toolbar_fg_color @theme_fg_color; @define-color menu_border_color #979797; @define-color menu_bg_color @theme_bg_color; @define-color menu_fg_color @theme_fg_color; @define-color panel_bg_color @theme_bg_color; @define-color panel_fg_color @theme_fg_color; /* osd */ @define-color osd_base #333; @define-color osd_fg #eee; @define-color osd_bg alpha(@osd_base, 0.8); /* window manager colors */ @define-color wm_border_focused #fbfbfb; @define-color wm_border_unfocused #999999; @define-color wm_title_focused #000000; @define-color wm_title_unfocused #999999; @define-color wm_bg_focused #ffffff; @define-color wm_bg_unfocused #ffffff; /*# sourceMappingURL=gtk.css.map */ @import url("gtk-contained.css"); /*@import url("gtk-widgets-assets.css");*/ /*@import url("apps/gnome-applications.css");*/ @import url("apps/unity.css"); PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/settings.ini000066400000000000000000000003521363064711000247320ustar00rootroot00000000000000[Settings] gtk-color-scheme = "base_color:#fefefe\nbg_color:#f0f0f0\nselected_bg_color:#5588ee\ntext_color:#000000\nfg_color:#222222\nselected_fg_color:#ffffff\nlink_color:#5588ee" gtk-auto-mnemonics = 1 gtk-visible-focus = automatic PyXRD-0.8.4/win_installer/misc/themes/Windows8/gtk-3.0/thumbnail.png000066400000000000000000000023741363064711000250700ustar00rootroot00000000000000PNG  IHDRx#OYbKGD pHYs  tIME  $5JtEXtCommentCreated with GIMPWdIDAThZAh"WmʄHErM` L 6RCҖ%5բC hw)=PRm0`f1O+8IFi`05v><ߛQWxSDc`1јhݎl V6[^wt:]!: uqiK JVFJncE͛VWW"<03jUD"AQ}u(/b^o셢5E<}%֬|>o0(EQehhbPF C>]5G;-wX|5>ՌT*qVyL&YbY6Lxx/J} PnW1]_}Jfo=eYA`Fe9 JyxAXPP~xd2L,B X[  .mA 3ڲbW^pX,6˲zpaMT*tZeY$Itl6[Rt(-@P =[7"@!@1ȕN_76WH^ UcYn,s/2s7??_,SΎHV3P(چTlji+L.||n7)'hz~|2ϭ`iipU4BVXV`vvmHTU8aINyZbcFBiѽ9M4TXVQ@E:òB$sh?1q~XUˇ@Ƅcvk%9_ro$D"!IMӑHvH$BӴ$ID$7, xS'Ń>.na;}֡{ [a^* X:0јh L4&)Dc1ofrޤXIENDB`PyXRD-0.8.4/win_installer/misc/themes/Windows8/index.theme000066400000000000000000000004521363064711000234420ustar00rootroot00000000000000[Desktop Entry] Type=X-GNOME-Metatheme Name=Windows8 Comment=Windows 8 modern ui gtk theme Comment=Author: Maxime DOYEN Version=v1.6 Encoding=UTF-8 [X-GNOME-Metatheme] GtkTheme=Windows8 MetacityTheme=Windows8 IconTheme=ubuntu-mono-dark CursorTheme=DMZ-White ButtonLayout=:minimize,maximize,close PyXRD-0.8.4/win_installer/misc/win_installer.nsi000066400000000000000000000135321363064711000216700ustar00rootroot00000000000000; Copyright 2016 Christoph Reiter ; ; This program is free software; you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation; either version 2 of the License, or ; (at your option) any later version. Unicode true !define PYXRD_NAME "PyXRD" !define PYXRD_ID "pyxrd" !define PYXRD_DESC "X-ray diffraction analysis of disordered lamellar structures" !define PYXRD_PUBLISHER "Ghent University" !define PYXRD_WEBSITE "https://github.com/mathijs-dumon/PyXRD" !define PYXRD_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PYXRD_NAME}" !define PYXRD_INSTDIR_KEY "Software\${PYXRD_NAME}" !define PYXRD_INSTDIR_VALUENAME "InstDir" !include "MUI2.nsh" !include "FileFunc.nsh" Name "${PYXRD_NAME} (${VERSION})" OutFile "pyxrd-LATEST.exe" SetCompressor /SOLID /FINAL lzma SetCompressorDictSize 32 InstallDir "$PROGRAMFILES\${PYXRD_NAME}" RequestExecutionLevel admin Var PYXRD_INST_BIN Var UNINST_BIN !define MUI_ABORTWARNING !define MUI_ICON "pyxrd.ico" !insertmacro MUI_PAGE_LICENSE "pyxrd\LICENSE" !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_LANGUAGE "English" Section "Install" SetShellVarContext all ; Use this to make things faster for testing installer changes ;~ SetOutPath "$INSTDIR\bin" ;~ File /r "mingw32\bin\*.exe" SetOutPath "$INSTDIR" File /r "mingw32\*.*" ; Store installation folder WriteRegStr HKLM "${PYXRD_INSTDIR_KEY}" "${PYXRD_INSTDIR_VALUENAME}" $INSTDIR ; Set up an entry for the uninstaller WriteRegStr HKLM "${PYXRD_UNINST_KEY}" \ "DisplayName" "${PYXRD_NAME} - ${PYXRD_DESC}" WriteRegStr HKLM "${PYXRD_UNINST_KEY}" "DisplayIcon" "$\"$PYXRD_INST_BIN$\"" WriteRegStr HKLM "${PYXRD_UNINST_KEY}" "UninstallString" \ "$\"$UNINST_BIN$\"" WriteRegStr HKLM "${PYXRD_UNINST_KEY}" "QuietUninstallString" \ "$\"$UNINST_BIN$\" /S" WriteRegStr HKLM "${PYXRD_UNINST_KEY}" "InstallLocation" "$INSTDIR" WriteRegStr HKLM "${PYXRD_UNINST_KEY}" "HelpLink" "${PYXRD_WEBSITE}" WriteRegStr HKLM "${PYXRD_UNINST_KEY}" "Publisher" "${PYXRD_PUBLISHER}" WriteRegStr HKLM "${PYXRD_UNINST_KEY}" "DisplayVersion" "${VERSION}" WriteRegDWORD HKLM "${PYXRD_UNINST_KEY}" "NoModify" 0x1 WriteRegDWORD HKLM "${PYXRD_UNINST_KEY}" "NoRepair" 0x1 ; Installation size ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 IntFmt $0 "0x%08X" $0 WriteRegDWORD HKLM "${PYXRD_UNINST_KEY}" "EstimatedSize" "$0" ; Register a default entry for file extensions WriteRegStr HKLM "Software\Classes\${PYXRD_ID}.assoc.ANY\shell\open\command" "" "$\"$PYXRD_INST_BIN$\" $\"%1$\"" WriteRegStr HKLM "Software\Classes\${PYXRD_ID}.assoc.ANY\DefaultIcon" "" "$\"$PYXRD_INST_BIN$\"" WriteRegStr HKLM "Software\Classes\${PYXRD_ID}.assoc.ANY\shell\open" "FriendlyAppName" "${PYXRD_NAME}" ; Add application entry WriteRegStr HKLM "Software\${PYXRD_NAME}\${PYXRD_ID}\Capabilities" "ApplicationDescription" "${PYXRD_DESC}" WriteRegStr HKLM "Software\${PYXRD_NAME}\${PYXRD_ID}\Capabilities" "ApplicationName" "${PYXRD_NAME}" ; Register supported file extensions ; (generated using gen_supported_types.py) !define PYXRD_ASSOC_KEY "Software\${PYXRD_NAME}\${PYXRD_ID}\Capabilities\FileAssociations" WriteRegStr HKLM "${PYXRD_ASSOC_KEY}" ".pyxrd" "${PYXRD_ID}.assoc.ANY" ; Register application entry WriteRegStr HKLM "Software\RegisteredApplications" "${PYXRD_NAME}" "Software\${PYXRD_NAME}\${PYXRD_ID}\Capabilities" ; Register app paths WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\App Paths\pyxrd.exe" "" "$PYXRD_INST_BIN" ; Create uninstaller WriteUninstaller "$UNINST_BIN" ; Create start menu shortcuts CreateDirectory "$SMPROGRAMS\${PYXRD_NAME}" CreateShortCut "$SMPROGRAMS\${PYXRD_NAME}\${PYXRD_NAME}.lnk" "$PYXRD_INST_BIN" SectionEnd Function .onInit ; Read the install dir and set it Var /GLOBAL instdir_temp ReadRegStr $instdir_temp HKLM "${PYXRD_INSTDIR_KEY}" "${PYXRD_INSTDIR_VALUENAME}" StrCmp $instdir_temp "" skip 0 StrCpy $INSTDIR $instdir_temp skip: StrCpy $PYXRD_INST_BIN "$INSTDIR\bin\pyxrd.exe" StrCpy $UNINST_BIN "$INSTDIR\uninstall.exe" ; try to un-install existing installations first IfFileExists "$INSTDIR" do_uninst do_continue do_uninst: ; instdir exists IfFileExists "$UNINST_BIN" exec_uninst rm_instdir exec_uninst: ; uninstall.exe exists, execute it and ; if it returns success proceede, otherwise abort the ; installer (uninstall aborted by user for example) ExecWait '"$UNINST_BIN" _?=$INSTDIR' $R1 ; uninstall suceeded, since the uninstall.exe is still there ; goto rm_instdir as well StrCmp $R1 0 rm_instdir ; uninstall failed Abort rm_instdir: ; either the uninstaller was sucessfull or ; the uninstaller.exe wasn't found RMDir /r "$INSTDIR" do_continue: ; the instdir shouldn't exist from here on FunctionEnd Section "Uninstall" SetShellVarContext all SetAutoClose true ; Remove start menu entries Delete "$SMPROGRAMS\${PYXRD_NAME}\${PYXRD_NAME}.lnk" RMDir "$SMPROGRAMS\${PYXRD_NAME}" ; Remove application registration and file assocs DeleteRegKey HKLM "Software\Classes\${PYXRD_ID}.assoc.ANY" DeleteRegKey HKLM "Software\${PYXRD_NAME}" DeleteRegValue HKLM "Software\RegisteredApplications" "${PYXRD_NAME}" ; Remove app paths DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\App Paths\pyxrd.exe" ; Delete installation related keys DeleteRegKey HKLM "${PYXRD_UNINST_KEY}" DeleteRegKey HKLM "${PYXRD_INSTDIR_KEY}" ; Delete files RMDir /r "$INSTDIR" SectionEnd PyXRD-0.8.4/win_installer/rebuild.sh000066400000000000000000000016501363064711000173300ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2018 Mathijs Dumon # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. DIR="$( cd "$( dirname "$0" )" && pwd )" source "$DIR"/_base.sh set_build_root "${DIR}/_rebuild_root" function main { local INSTALLER_PATH=${1} local GIT_TAG=${2:-"master"} [[ -d "${BUILD_ROOT}" ]] && (echo "${BUILD_ROOT} already exists"; exit 1) # started from the wrong env -> switch if [ $(echo "$MSYSTEM" | tr '[A-Z]' '[a-z]') != "$MINGW" ]; then "/${MINGW}.exe" "$0" exit $? fi install_pre_deps create_root extract_installer "$INSTALLER_PATH" cleanup_before install_pyxrd "$GIT_TAG" cleanup_after build_installer build_portable_installer } main "$@";