From c0bf19687ad6cc67fabaf2ed5dbb0ebb02740b3a Mon Sep 17 00:00:00 2001 From: Jukka Aho Date: Tue, 30 May 2017 06:26:12 +0300 Subject: [PATCH] Reading Code Aster med mesh properly - MED file actually supports multiple element sets or node sets for single element/node. This is now implemented. - Minor cleanup of code, etc.. - Fixes issue #111. --- src/preprocess_aster_reader.jl | 132 +++++++++++------- test/test_preprocess.jl | 19 +++ .../hexmeshOverlappingGroups.med | Bin 0 -> 56434 bytes 3 files changed, 101 insertions(+), 50 deletions(-) create mode 100644 test/test_preprocess/hexmeshOverlappingGroups.med diff --git a/src/preprocess_aster_reader.jl b/src/preprocess_aster_reader.jl index 143a485..ae9f91c 100644 --- a/src/preprocess_aster_reader.jl +++ b/src/preprocess_aster_reader.jl @@ -47,7 +47,63 @@ function get_mesh_names(med::MEDFile) return sort(collect(keys(med.data["FAS"]))) end -function get_nodes(med::MEDFile, nsets, mesh_name) +""" Convert vector of Int8 to ASCII string. """ +function to_ascii(data::Vector{Int8}) + return ascii(unsafe_string(pointer(convert(Vector{UInt8}, data)))) +end + +function get_mesh(med::MEDFile, mesh_name::String) + if !haskey(med.data["FAS"], mesh_name) + warn("Mesh $mesh_name not found from med file.") + meshes = get_mesh_names(med) + all_meshes = join(meshes, ", ") + warn("Available meshes: $all_meshes") + error("Mesh $mesh_name not found.") + end + return med.data["FAS"][mesh_name] +end + +""" Return node sets from med file. + +Notes +----- +One node set id can have multiple names. + +""" +function get_node_sets(med::MEDFile, mesh_name::String)::Dict{Int64, Vector{String}} + mesh = get_mesh(med, mesh_name) + node_sets = Dict{Int64, Vector{String}}(0 => ["NALL"]) + if !haskey(mesh, "NOEUD") + return node_sets + end + for (k, v) in mesh["NOEUD"] + nset_id = parse(Int, split(k, "_")[2]) + node_sets[nset_id] = collect(to_ascii(d) for d in v["GRO"]["NOM"]) + end + return node_sets +end + +""" Return element sets from med file. + +Notes +----- +One element set id can have multiple names. + +""" +function get_element_sets(med::MEDFile, mesh_name::String)::Dict{Int64, Vector{String}} + mesh = get_mesh(med, mesh_name) + element_sets = Dict{Int64, Vector{String}}() + if !haskey(mesh, "ELEME") + return element_sets + end + for (k, v) in mesh["ELEME"] + elset_id = parse(Int, split(k, '_')[2]) + element_sets[elset_id] = collect(to_ascii(d) for d in v["GRO"]["NOM"]) + end + return element_sets +end + +function get_nodes(med::MEDFile, nsets::Dict{Int, Vector{String}}, mesh_name::String) increments = keys(med.data["ENS_MAA"][mesh_name]) @assert length(increments) == 1 increment = first(increments) @@ -58,56 +114,23 @@ function get_nodes(med::MEDFile, nsets, mesh_name) node_coords = nodes["COO"] dim = round(Int, length(node_coords)/nnodes) node_coords = reshape(node_coords, nnodes, dim)' - d = Dict{Int64}{Tuple{Symbol, Vector{Float64}}}() + d = Dict{Int64}{Tuple{Vector{String}, Vector{Float64}}}() for i=1:nnodes - nset = Symbol(nsets[nset_ids[i]]) + nset = nsets[nset_ids[i]] d[node_ids[i]] = (nset, node_coords[:, i]) end return d end -function get_node_sets(med::MEDFile, mesh_name) - ns = Dict{Int64, Symbol}(0 => :NALL) - if !haskey(med.data["FAS"], mesh_name) - warn("Mesh $mesh_name not found from med file.") - meshes = get_mesh_names(med) - all_meshes = join(meshes, ", ") - warn("Available meshes: $all_meshes") - error("Mesh $mesh_name not found.") +function get_connectivity(med::MEDFile, elsets::Dict{Int64, Vector{String}}, mesh_name::String) + if !haskey(elsets, 0) + elsets[0] = ["OTHER"] end - haskey(med.data["FAS"][mesh_name], "NOEUD") || return ns - nsets = med.data["FAS"][mesh_name]["NOEUD"] - for nset in keys(nsets) - k = split(nset, "_") - nset_id = parse(Int, k[2]) - nset_name = ascii(unsafe_string(pointer(convert(Vector{UInt8}, nsets[nset]["GRO"]["NOM"][1])))) - ns[nset_id] = Symbol(nset_name) - end - return ns -end - -function get_element_sets(med::MEDFile, mesh_name) - es = Dict{Int64, Symbol}() - if !haskey(med.data["FAS"][mesh_name], "ELEME") - return es - end - elsets = med.data["FAS"][mesh_name]["ELEME"] - for elset in keys(elsets) - k = split(elset, '_') - elset_id = parse(Int, k[2]) - elset_name = ascii(unsafe_string(pointer(convert(Vector{UInt8}, elsets[elset]["GRO"]["NOM"][1])))) - es[elset_id] = Symbol(elset_name) - end - return es -end - -function get_connectivity(med::MEDFile, elsets, mesh_name) - elsets[0] = :OTHER increments = keys(med.data["ENS_MAA"][mesh_name]) @assert length(increments) == 1 increment = first(increments) all_elements = med.data["ENS_MAA"][mesh_name][increment]["MAI"] - d = Dict{Int64, Tuple{Symbol, Symbol, Vector{Int64}}}() + d = Dict{Int64, Tuple{Symbol, Vector{String}, Vector{Int64}}}() for eltype in keys(all_elements) elements = all_elements[eltype] elset_ids = elements["FAM"] @@ -119,7 +142,7 @@ function get_connectivity(med::MEDFile, elsets, mesh_name) for i=1:nelements eltype = Symbol(eltype) elco = element_connectivity[:, i] - elset = Symbol(elsets[elset_ids[i]]) + elset = elsets[elset_ids[i]] d[element_ids[i]] = (eltype, elset, elco) end end @@ -150,12 +173,16 @@ function parse_aster_med_file(fn, mesh_name=nothing) else mesh_name in mesh_names || error("Mesh $mesh_name not found from mesh file $fn. Available meshes: $all_meshes") end - nsets = get_node_sets(med, mesh_name) + + debug("Code Aster .med reader info:") elsets = get_element_sets(med, mesh_name) - elset_names = join(values(elsets), ", ") - debug("Code Aster .med reader: found $(length(elsets)) element sets: $elset_names") - nset_names = join(values(nsets), ", ") - debug("Code ASter .med reader: found $(length(nsets)) node sets: $nset_names") + for (k,v) in elsets + debug("ELSET $k => $v") + end + nsets = get_node_sets(med, mesh_name) + for (k,v) in nsets + debug("NSET $k => $v") + end nodes = get_nodes(med, nsets, mesh_name) conn = get_connectivity(med, elsets, mesh_name) result = Dict("nodes" => nodes, "connectivity" => conn) @@ -215,17 +242,22 @@ global const mapping = Dict( ) +""" Read code aster mesh and return Mesh. """ function aster_read_mesh(fn, mesh_name=nothing; reorder_element_connectivity=true) result = parse_aster_med_file(fn, mesh_name) mesh = Mesh() - for (nid, (nset, ncoords)) in result["nodes"] + for (nid, (nsets, ncoords)) in result["nodes"] add_node!(mesh, nid, ncoords) - add_node_to_node_set!(mesh, nset, nid) + for nset in nsets + add_node_to_node_set!(mesh, Symbol(nset), nid) + end end - for (elid, (eltype, elset, elcon)) in result["connectivity"] + for (elid, (eltype, elsets, elcon)) in result["connectivity"] haskey(mapping, eltype) || error("Code Aster .med reader: element type $eltype not found from mapping") add_element!(mesh, elid, mapping[eltype], elcon) - add_element_to_element_set!(mesh, elset, elid) + for elset in elsets + add_element_to_element_set!(mesh, Symbol(elset), elid) + end end if reorder_element_connectivity reorder_element_connectivity!(mesh, med_connectivity) diff --git a/test/test_preprocess.jl b/test/test_preprocess.jl index a80ef49..b2ad271 100644 --- a/test/test_preprocess.jl +++ b/test/test_preprocess.jl @@ -135,3 +135,22 @@ end @test isapprox(temp[15], 1.0) @test isapprox(temp[95], 2.0) end + +using JuliaFEM.Preprocess: MEDFile, get_element_sets + +@testset "test read element sets from med file, issue #111" begin + meshfile = joinpath(datadir, "hexmeshOverlappingGroups.med") + med = MEDFile(meshfile) + element_sets = get_element_sets(med, "Mesh_1") + @test element_sets[-10] == ["halfhex", "mosthex"] + @test element_sets[-11] == ["halfhex"] +end + +using JuliaFEM.Preprocess: aster_read_mesh + +@testset "test read overlapping ets, issue #111" begin + mesh_file = joinpath(datadir, "hexmeshOverlappingGroups.med") + mesh = aster_read_mesh(mesh_file, "Mesh_1") + @test length(mesh.element_sets[:mosthex]) == 273 + @test length(mesh.element_sets[:halfhex]) == 147 +end diff --git a/test/test_preprocess/hexmeshOverlappingGroups.med b/test/test_preprocess/hexmeshOverlappingGroups.med new file mode 100644 index 0000000000000000000000000000000000000000..9a5cfed235de0991fa97c22f6a9c03257227398e GIT binary patch literal 56434 zcmeI*1$-1&xbWc(A;H~>yAufRkc1>85F>^YEV#S7yE_yL1&S4GaT1_Ni$ifOl)AUj zLTS1GcXv)Qne1ja6ux`gkNx%GJ#*&F%$eCaGMi*O)z8~EL)tuPQ>0F9Qm+@<=2WJ=`|6c!ce={cJ6@^Z~6xz4_xVM|;sIVSTFv~*lZyZ7!*>s4BlRF!gM zHKj~k-SmY`CTCWpmz9B@jZB%jCM0eR#Q8u9?ZT zvedMcn|q>HMQeE%3tBceUDx7&Fg#k=-PJeLFT^CRErls_{3)t7vs`;e%W4-!cBA&hn<1}K#9?RER*xeKJoO6q^QJ}|nS0CBvD8Ryi*zP+ls&t0-|`Kk zlu6Z3YpB{j)!yV_A*LZ5L*9CMjnvw2Uh|U4q~iLKX}JU7CxcD8CB0$IvJO-%NU=I)~mEJGl#v&VdNEN2__RGYQmQ`Iv1rn zaXmz4sukr{Ey}mYz#$<$+VzSat4#}Bi%Bu_+rwV6n2S}9^63%XVL-p$9iuBA(}rhD zIW;O-%GHQ3=kAh2tAy<8lU(aYT0ey(M?eQg@1TglL}TtVx>#*q(J(YPdm61Gs*AHM zR(L!q{3WWS?KdbmaD%pAM=I$Rny8ZIE1a-O4J%~tX0B4ALD}Twl$~M3iI?uGO42#E z$*C4G1WxjgOl2&EQkz_umN}WYs&))q9bcr7FYIeq6y0q)j=~eFU|9bsi*@b(vqc>OJ%@ufa1>RhN|J5t- z64NzDCTCV=kWUlLAS1JBr=4)Rw)MNkjb5Tk@iWM;wf#C$Nylc852L#;dTlevY})Dn zOJ|T9D(~z65^t9yZ;q8a=#K<<&=U19l<*EJ{^Z10FHxoVJE*MrgA0?%kxDvt2f4P) z-1W8HK^^|xchI#BKUIB+x66??$I2b_dxAU2?JcW)(8ESwt_g17*EmHHRy(Ut(Am=J zOFk~>%?DxfYH0~3QwEjL;`*D5R=vbo#^02&T2&o6Z^v%R6VJB{er-49u7CGUS$klm z4KML_Ir8RMxhbD;Uuo|&AFX_%-Ai$w9$RjqCs#5B$xSJ@Tl)N#ytq%NjJKO>8Tq_P zeJ+*D{CQJF_bBaIHA;KNyq(*!n>0OU&V7lqjlVftYadHHa{g9sPV?to2|Mv>zm8jD z@8(n=ld0(Nr#8;%E$6-35=^GeD)Fy~?@g+7u=Xa%P^{%menzoOv~r# zj=Wh`Zc_8k>#9S%WE|dQoaOWMM4uFQa*|&63vO-kLzoE!n9k%WNO`gUMhQi#A>bYQ_(rQ z$;sMf*8ilT5jryr_u|+&A$=q5@qeXQesAX-vDzBm`+1tfx$J3%Z zA4{(7>m9e=!;-RfT&>=A>sQ)CyZ#;<&4~(@{n9SBXv^kUrn1B3EcLQ+z2e%H?e(_m zW8N?IvbWw?Z?&DReAQ0P`_;8aW4kdfYx|39=f-7K-h6yp?Z-;Ks*l=UEvxz^cYiY4 zzlc%oId`mlWcT7l>ewXrW3u)B*vPkX9Xgn<)YdH@s8n;hW-cs7; zzscUdw1;+l8_{?qLwQ@{uF8+=C$#NUzU>%mJ|gE~exG#h*y^$xhgGD!Drc#=o$IT+ zUTQmIJ5|e8_m}AXQT@M)YMeEUTXF5ove&qORlbU*`0W$4cQqbbslPf8b5z$wH!n$e z9;%)y#vjjIPSr!jxQg0HsP?R4+<0mn-&A`y+)r`sS*=&wX;JN0MRgtGVlwNm_M>95 z>lZkUnqN<0A#`+kSx66j~gXa3F zxT!+ie8N~>t9Pune@i>$zEjIuza6)%)hljUJ5H>2JuH#!)v|USViQDN`!MHoA3WE_ z#r2l@$L-&EJhdN7`M2V>vzKo`WTO^ou`LZe-Evk zwwyXqzU4D(L%ZU>w>*}|=bOusukI5y&S~$j$IFht+WVNOeQ57X-0_Ud>bPoqb2Q$M zEL*Nmu~B_O_z7Ut8N3)3&n}vm5uD%>BLU?Tz&;pw&l3<8@T|>iku- zU(RwrJ&Q5-E1RCxH!hpZulVzO$@NKC`Hj%$pno+_&P+FCYlua@o9E7r#PxcX`9Z7!?h7^AAEim$o;HtT1szpZ+z z_gB@^5aaF-#hmUN%qD zU*y-yt9lyiZ*SSyuc+&2D;mouK2PmDJdDx&JhACnTzlA|oxi#cTKU-IMRndP+B@Gw z>pd*Tvvt14{Fgs}^}eg9&Rg{tj-9tUPZblLPpo!6Dtc(k4o1h%&v^f`&m9rI9d3({gy|KJ;y>Z!C-neWm@945|KaMV|{j2h7*%;OOmyO2#z3S!HRj-%b zuW`Hot@E{aTw^_xyKHa&w$|G^jKz*;wAVY%H&qjpfv`TCbwYf7z(^^G~C# znRe80&t@E{YJX__B`3a8`e?1Z&H}UmqWZsAbFf#vjl6@#EL4@|JS(_kp9!#_MI=pE`c1d4)R9`0Jv|tNp0*YFVA1 zT2|$4)z4VoxU9BU`!gO-t+%!RSF=9;IveX}Tvo?Z^;PTD`)zC4SU%yhdLJHZ?f#wUu~!2;}~^aTA6q7NLyC<@#m?^ zsd}oY@>NWjAHRLV{6|{-;_GQ#ul8q&k7Gj2^^32Ux-M$HDzC1mifX;B@|NSO^H%Ll zMRmOR`l{oo`l{{K@zk1|7yB+Jj+pE?Y|Ajm#};zVxqqRiT}1Bbxl-E>e}p#`tMwu2eUE<%I$0Y zs|?j2Lqr5#X<|9&mFp_sPU5dQ?)J76Q-4jNeW8}+t1H8NDt)f~wS@z>lg&S3F#cVn zTa|qo?4ClPr+;DX*gdArzLUf$v}~HhDFg+34-U7iw|)xp2PyL-EG4#Bg6PKo> zhCfKjkS%t(z1I7=)_PS;>JF{c*>apY|E%@?!d?u$kBz_bd5x|2SfigNy_ycTp-*t4 zzO$2~r)ani)_dV^!@VxrdmrVZ%1gzh-p60H`{b1N*O%7sxdc@UvhoK)|0mwZ$DW-R z+HIzMN!`(2mg|%Etywm{3wEkBvNC-w#b#Q9_J@0_!wUA!*vGQM#wB~1(OTO%f7`l8 zJGfWnUk+=>e-qwZfj3v+%@ufa1>RhNH&@{Q?G=!ju{>tWEJ=QcT>cc>6*BvkFYA@> zpOr6lO$V7#$rFTp_irXJl`wzBcUGc2vh(e+CixDkoRIIfkVkm=N)`D6m%PXa`4Wl( zD2PHRj3SUf=PrigD1nlYul<(qFy_l!Ol46HZYYlma0inSQzcYJ6;wqvR7VZegnV^v zZPbB$DMCHSFGhO66JBTlZ}`9$4dDlWG(uwpAP_+aMhKc96!L&1PdD=XB2OyvBqTHY z=4gSIXoc2j19=W^i*{&_4(Nzz$i%ucx}Yn%p*wn@CuEi<4~sHQli5^1^v3`U#2^gD z5DdjI495tJ#3+o$7>va@jK>5_#3W3{6imf5OvenOCl9L&W$%*O&O#3C%l5-i0s zEXNA0#45av)mVdfuomm^F4kiMHsU>O!e(s2R&2xj*p408iCx%@5AY%OU@!JzKMvp^ z4&g9j5Q`%?ievZ)$8iEDaS9*f6P(5woW(hu#|2!(C0xd*xPq&=hU>V2o4AGBxP!a6 zhx_;p5AYD5;}O2Vm-q@_;~RX7@9;f-z>oL|KjRntir?@UPw+ebz*9WKpLmYH@Hbw- zfiD`u5B_L`#t1+lf)I=lG(jlB5RM2$qA8l8Ia;74TA?-CAPQ~K4(-ta9TAOA z=!`Dtif-tR9_Wc)=#4(;i+<>j0T_ru7>pqpieVUz5g3V4kpDHx7>va@jK>5_#3W3{ z6imf5OvenOCl9L&W$%*O&O#3C%l5-i0sEXNA0#45av)mVdfuomm^F4kiMHsU>O z!e(s2R&2xj*p408iCx%@5AY%OU@!JzKMvp^4&g9j5Q`%?ievZ)$8iEDaS9*f6P(5w zoW(hu#|2!(C0xd*xPq&=hU>V2o4AGBxP!a6hx_0^^B9+sSOzXAi*j&<8>CEmR6r`Y zqasqH5@g%TsDc!zifV91b<{vQ)I=@FF>0d@(xWcwLH1oA9>@Ssc%cC@!W&X2ANZmn zGQki2XoSpY45?cH0uh8X2u50jAPbrx6jHY^WJNe4AoYqw4m3qGG>06o1zI8}TA?-C zKa<4cMQ$XGuXJSgo z`{F`$g$n1+}Za*fgv(?hO924Y4OCY_0x8FFp25VJzgIU6xMY^T`OnrDjuA?Ws&;U{gZ^$+EfiD_DuB#vX(FjtP#*nrafItL6+C(t2AO!Lr zG(jljJqXicI57g*5vj$d#Aav?saFfML=Ln4kix4P)J>eVL0Tz8G(@) z1*z+3j6ng6#W+Y^$D<%7U?K`(5++0HI0c0<71K}z(=h`xQ53VZIGZ>Jb5RWQv^bx* z08+1oScKwOj3tmdEyXgFz;diWNvzc3D&pHHh1FX8nfMD*;8*+xCp^XzxZrpEfz)`4 zXOR2zPdtabr+?vZ$b0hwvJ<(VR2$AhTD4)hZ&VwWYp&X`Tuar4<-JsGSl%zyhUGj} z8ER*3MVLJ>kJHqtpC)FiE?%)%NJE9 z@e_WzYVuMssW6iWrL#-i%lermXvfz2v=aXp`n0x( z1Ln)M{O7;RWv$bz;^-+RxL6GuTJQAKJrxnyMtW0TX)1MCYe`8@CvzQVq=d|GTp>f1 z%x~oFm77TBH0dF8Qkg%?yoOuw&FA4{xOohzRr(*_WelBx!{k(i0%k!}S3$X|?DlUPVgRA-XO7d1gMuyc`gLkkN z>+mk5o7jMjcn_Pf8C$Rw+weZNV+VF(7k1+Ve2D+wKL3+8Tn=SX8tS^HBwZO*PzLHc zry*SnHBk-fx>qHwuDvtqijcWlb*T5E25I$vxR7oDFL=TY`u9lskN^ZC2$i6J-{d){ z2|^JJ^**L0T@Q5;0`m3fFN1H<1hDFcuwg3%8L6Q!x$QaR+yi z7t=8VGtm$Ca37x`2Zmw@24fH&;2}OoZcK*sDZ}sxUtl=C#8)VQC0L9x_!{3}G`_`m zD2nA+fm!$-KOjG(-ED54`e5uk|>{RM3D9*dLf*21EM$N z+`}N}?ZdL1voA3R(Us_erlcDZ{m_iGKd}*-lWt55fd1TDkRM215L%KBCWb)nJ@r{i z7t&403q@DbVZ?AqTZ}a#5TxB zT0U!)&ua#fjv}_j0MhMZutXA4qXJQvfTkA^f zhM}ap6MH~^zQf4xNnS4uC*7Oa2Zc$i&x@o@_9d?$q>c6`4nRTD@|m!d8&7&5aS-&^ zXDa!F$r}RcGlmj}p&04xMEUG#E@?Fm&mldMyiu42J!SI^M%3)WBwJ zL0Z&9UASQ@w&8ua!Uw)+2C+G z*o%GezNL!S)SqF!37%`}gSRBDoNFUn(UhsrBj^QI5M@B@W6FQ?YPT(X? zLE3gVbVV0PfAKLs!D*yJ8?;6%MB)t2;vBL=+SdR``)h&oxPXh0KH?HC<5T3oP)NIy zcGebGa23}Oh3mM1o5+9;XpeU2h+DXgw=e+{(H(bi7r8MRlh6zG zj;D+oM+=dbpR;DcRMLZq#&NU=`LbThNt?Gfj!GY)<{VNd{a7k>l{PK?g0*o}e=f@? zGnws;<7h$hGn1bQ(id18N2RY&WB*Fhvne|VB}l7rdJXA!;D++309UNVI#h!67t(J? zJ9`)FQ3o5a5w%ecW#NMNunE$>H)9JtAZ@-bc zf}@B+19-s`GX5OHM>vj-h(;%LhKxZca1y7`9o^6s(thQ>{1~6$H2R?pTB8+m;tbB> z9Hia!#{dk3+-v7?0T(e0mv9-MLfXkt41u(_{J4UvxP~#fjvKg%(ddBoXop<5h1(d9 z37CjNxP!Zxg2|YK!nlX~_zbf!3L`NB1@Hh5@j2#UHs(OuRxv!n7g&fd@f8+f0p_DP zzQ#9LhHvp5mSP;nq9DG<4_JvMSd0?*5kFxyR^e@wf*McDkp2Zf!x?Hkl|1!aD(l=) z5mH8tr{zf(cCtK|Rv`TceupcHl3xsNq#xr6Tu_|65~xnPB(W4~kbZ`zNDDQdN}d`| zWnEp=gOpL@X>HOK*oQmnkp2_TA#J20d6nQvT0NJ_y!n;4(mq6I z_(H~%Nb;mzHze(cR;2xjjnJBOV`2c>kk+5O8c(Ch4`zJ`+LCr9${3ZCbQ5AI+L2bz zrP3B6$csb=()#P5#?y}Er(%6-NSjgPX*B7UZnp1ws|ji-G{x1(%( z^da4W*b%)+%el*S$VOVm({$(r8EXcSmk*sucfknKU5VW=l5}@s4~!zMKVLPTjwZi1 z>-%5~X*He}Al;X|ei%zyJ(o&5)1R*zPo*!AaZ<+1BBa%LI)U_1^5nU6B57GKbZ0V>CvnogXyHzc$$~=Sn}kVb{u4kn@3(Tj3+$-(#|ImCqdf1{#+)LHwBBx zpGuqt>1Q$%Godi)>BJdWL3$=}7M7D%<7rXSv&ox-)uh#UTAXw_l!Xh{;2lVNuZ${4 zjkQ>ZN~ndJkhb|Q)}szKU?XZn`WWeJQsF&pLVaw;7I>fmyx<9GpIfmF@537b2t*Jv zV>@gtBOFIZ^gvJaLLQvJNt{A=NPCg?Bkd_KjALwb@|vM3 za^MWk;v5E{8@i$ka^pNM;39_M5-#IY496%)I~#!lxPq&=hB3H~8@LH+YvV8$1#t_v zF&lisMK8gw^;Bzak~{<7-)#%OM49jjytuZ105R zj<3J4&ChT~aduDw)k&8mmO>5EHBk#`U~7DR%(^FVfg9VEM{Uv-i0-IE`fvP&45&z6 zC3upyHNNV{Mg92t2iyD(S2!}hKBL@Iq=kNb)sGXN)Wr+Zwi^(=A#FVj+2F&nj48g5 z?Ofr4oUCg|^g}Dsw#HZexd&1<2-1Foi6Ll9Iu$WBa*=LA3`INAw#HZexTqgr_1Ddj z@l`)&I5NKWqfU7dP5ty^tA70G%(^ZZK{;FFtNwg@QnnYQ&G#nu!5Gq+h?ybnzb~;L z#*)^Lud-gs6((OlzUr@!t?^YqRyi`hN*^Nq%23FCT|?66-EQo|W{^Mb}xrft$F6+qeUH=DCOa z_ze1InMW*tfiEG?E$TT%J)@{+67>us&j9i)pq?evd|u|+GT)Z@v&^Ia#B<0zUgoqf zAoEQpq=3wkWqvGkW0@nXIbIslG9Q!qqRcndTv5&M)aM*Br;<6Dnop_uka{Lk&p_() zMfJQ>m~D!nD2kytN}wc4p)|^%EXu(R@{Cde?oiJol}T4YRa8TD)PQ=7lbVGOafSPajCfx^WZrz{s01U(+48{-)g?uKp2Yay(`*8pV zaR`SIgIFBFQ5?fZIF1uIiBtF(pWrmk;4IGJJTBlOF5xmh#T8t|HC)FH+{7*1#vR17kJSxB)6;TP5Q3X{|4b@QtHBk$-Q3rKV5B1>zPk5mL zyx{|1G=v}g(FlzZfItKx7$InaP=p~I5r{-nG(&T=KufejYqUWW+M*rWqXRl38lBJ? zUCcO{6TQ$Ieb5*E&>sUZ5Q8unLogJ>FdQQ=5~DC0V=xxuFdh>y5tA?(Q!o|N zFdZ{66SFWIb1)b4FdqxB5R0%FORyBnupBF}607hwR$~p`!CI`tyI7A6*ogPA37fG6 zTd@uAV>@;D{ufaRW&GKB#Lmte>0?6l4wNMi>HkBc-G-RwQM=XnKq~FDQlw^4!7NHat zV+pFVZUZ)=ILk}13?;A}E8xmHH5U4iu0>u=_>!(etc`}G>k;e1jdU?$QEVl>lDG=n zNWV>7jrU2fA-;nwq~-IbP^4hl8(wfi19&2sb-S<|^;uqvb@0HuSdUb!YXpDzLB{c_ zaoot;fQ@($GDdE~W^926<@ZC)gDQ}>72EJW+_4=yuoD?6 ztL6yNq+5{J9GytFBDO?l(rt*X;Z3>*u{vZfwTrkLGN<~0_#uvy-b37rbfnv$HCiDI zA4AQxWUdlGxkm7V5B6a{4nXEJjfwtf2wxn;Asj{~)^|f!bb-uyBFGOz6UaO$k{FIq zv|#x>)Mwy<ZbypWrRZry-`r1kyu@gE5ix zFyc^jB;AbI6t_s9CZ2)J8_yEYK|Z$}g^?J6e$dY)T`1E7-OvTmIFAds2v>ARS9C@v zT*761irnmPGA1Dv%l*+8sWAZkFpPDN@CACZd<9q03)gTR!&&zwWDeV#a2SYo8W_5TiaP+Q7e!Bt$t1Po#QU`!-Ej5rj9NM|Kxfvvu1JY@#5eh`fPP+rze#|+Fw zG-b}?0xlvQ#$Xgipc5`Z^+V}NkH$z0hpoOS7yBNEvFJ%TYkkpZ)~V;`-jun4o9F}8 z7mcCJ*Z2ndzNje6%drCbz9<{p&cY1n`=abD&%|`(hP^&Ve*Ra8az*ef>4L<*2&Ov?Co#Yy!ErQxoOh|EK*U2TewCf&{uqFPc-{SsE9cN1YTn^OT0c)Q&TAa$ zbEMr5U_bpamUKU2Ulb&rnV1RxwBH%Tz8>HqK1V*x#vIJWtLk$yP}hkV5B>b70LM^s zq~!KL^Vs%B{Dfjyg||@}Yw!+i^-)wK?ssNw^-Gg=DPmqS_D z>Z9CPSBy#(g(H2G3+10c<}Z%)QNfhkh25~#N2Ot1EhP2aTA%fMuowGaub=Xu%zhle zK~#|QgRS{cddf6MGc<+0e#)CNYEI}#AEocB9qFTt{h|+b-v>26GS<(Rb^7^{@q8kw zufCsZ!SZ=rz(oYIeGFo81VK28WB3RyDRT*z@hO7IJB|}L3FCYy2W5t02nNGmKc(-( zZ&Cg<`#gi&q|Xx1LEmrN>Z4pJr|A#HgL zq~DVMK+UbBUzm>tkp4lnQ5mDluv{8)pOzz*h1`>>->O4;A$g174)r`I?NIewwJEcd zRkQ%jEu89EBb%?bQNV*=eE`ms_KCBYymE^6$cG7PXS7Qh1HNsO8^~ABbb+Mx&veoT_fzHo4x%NtlD7@-L)zeW?7&V)8+<_-6EQQP1+h7L zkZwh6iJqj}5L=@cY1M}{CcTTi-8ez|1ES2cPLfvhtW%`hpfy?{CqBj}IE{V?U>h}; zb0Mwov!Yqn&;6t=9>gIWhP1_Q?4v8XAU7h&3qupgd`;$d;RuDy;q-l&v`u{<)|2)6 zKCBnZmnd@?(uWPlaq><;`mo+O1@%nVhjbdGMIOqk=een*hmkiF(?~ZXHbr;Rs^7Xp z`Yd_paF_HbjKl~Oz&+f@XOQP2eV>()ZO-EYE<)OVcc?jD2GaUIOZtGx>|+uNqd&`i zkqHCP51FC*t%a1i!tzy2!!=yTRD8+0udoO=SiXrVxP{x8j3umFj1u@BKVT&$v2Fr# z;tuX&HYQ>`a^N2BV-E5mFN}RrMfT;6RG7>9c_@bYSb(Bf%er-_gfc9b#&gp8zNj9{ zb&(zmS-%MG(Dy}~S+@lqSjxI(D39e>0XMwIx=oNdNG+CYBB{Pe-v%A&i}Y>Kn|)jB zi^4f}1hT_cU-SX%K13v{vY*PRh`(8`My!HL_={zkyLrJA8L)x;r23&)${fK_L}4rC zx8Z#RV>@D$^a%I-!|eUZN18T+B$9J3GlA}{u_ul+ax z>4!R^J=!82jQvmt^4cK^=@Ef2G(jpvA{?QRen{qY?a>Z0PqEhzU8l?q+{9?u>xZsT z<|?jX42E#b!7%nkS;@17GlebG#orz02UVjhy}a|*G(2!3b(1&M_r^Oee|g49?@ z-Xgf;pY%D7^pnZ$XR1-Bs%RwjC02$%=}N?k@FOk#g!COUuW_W$aikx9-Th2sIS(|1 zD+166esDn>&ZRZf{ACw;yWwa*6G_CHrl$ZWM`Ujc2 zNWUQcL}AkZwBLEaJ|5z8%)@NV!CVx>tLk&~a~u8qM?XhOZvXQm_4^5{u?laa6xQG! zxWQH*Ri1Sf;0jxP)T??%E5|X)!UeYaDCt9sLgs0X^ifYJ_ZTwwbEJ>jMY-JwgRMTQ z7VByv9c=Ycdsw#@`_KaR`l#`348OQ=9FoMrpN(%{gi%A=tv)>@2ef@qm2Ec zv7a*5&p1Cao{zDgI?p*?z(ov$y?!dW&t*f|=MW4=e%R}$^m9d9eU!f6w$(=&`#)np zWvs^~?)}U76f!?D*4x-my{c!l1zeN)C=OeFl)k^x_fh)t`ZzC(MUYncO{yW#E#pqt6ef_+>O#PiLCuRP(|GBP{ zUzdZ=W#pCxCK;rbw_RA}BoZn)LGH z!?n0Fe~Eb&qK6Nwjb7bB>df1-;a1*B?k zUT?0zn=A0<3cRK(@R)AqiRRyuR`q>d@l{=i*Hyol-S6+b&#%+V*2~fDUT59k?ECfJ z8_B)Cr z0$<`Qe2s7LExyC|_yIrSC;W_G@GE}9V?4p{_ybSz41eM|{=(mQ0p_tLC!~NgQo;qU zNQKl$gS1G8^vHmW$b`(uf~?4f?8t$f$c49%8+niy`H&w4P!NSs7)4MN#ZVk2P!gq3 z8f8!x<=}?$r~r3VL?u*46;wqvR7VZeL@m@t9n?iV)Q1N=;e`h9h7Wwv5PtASBQ!<; z0uh8@grEsR5r%L?AQDZ{49(F3Ezt_C(FRdyi*{&_4(NzzbV6rzL05D`cl1C{^g?g+ zL0|NPJO&NGKn%iQ48c$g!*GniNQ}a0jKNrp!+1=