From 4bc5db1e07d8982cd65466b0998151f31a72cb4e Mon Sep 17 00:00:00 2001 From: yue qi <391311qy@gmail.com> Date: Thu, 2 Jul 2020 14:41:09 -0700 Subject: [PATCH] LPA* --- .../rrt_3D/__pycache__/env3D.cpython-37.pyc | Bin 1908 -> 1908 bytes .../__pycache__/plot_util3D.cpython-37.pyc | Bin 4804 -> 4804 bytes .../rrt_3D/__pycache__/utils3D.cpython-37.pyc | Bin 5324 -> 5383 bytes Sampling-based Planning/rrt_3D/rrt3D.py | 2 +- Sampling-based Planning/rrt_3D/rrtstar3D.py | 4 +- Sampling-based Planning/rrt_3D/utils3D.py | 6 +- Search-based Planning/Search_3D/Astar3D.py | 8 +- Search-based Planning/Search_3D/LP_Astar3D.py | 190 ++++++++++++++++++ .../Search_3D/LRT_Astar3D.py | 4 +- .../Search_3D/RTA_Astar3D.py | 4 +- .../__pycache__/Astar3D.cpython-37.pyc | Bin 3455 -> 3456 bytes .../__pycache__/env3D.cpython-37.pyc | Bin 1868 -> 2357 bytes .../__pycache__/plot_util3D.cpython-37.pyc | Bin 4783 -> 4783 bytes .../__pycache__/queue.cpython-37.pyc | Bin 2669 -> 2995 bytes .../__pycache__/utils3D.cpython-37.pyc | Bin 4337 -> 4118 bytes Search-based Planning/Search_3D/env3D.py | 19 +- Search-based Planning/Search_3D/queue.py | 45 +++++ Search-based Planning/Search_3D/utils3D.py | 8 +- 18 files changed, 266 insertions(+), 24 deletions(-) create mode 100644 Search-based Planning/Search_3D/LP_Astar3D.py diff --git a/Sampling-based Planning/rrt_3D/__pycache__/env3D.cpython-37.pyc b/Sampling-based Planning/rrt_3D/__pycache__/env3D.cpython-37.pyc index d34d29617282354742d7eaeb24c201b9d0a3d08c..8dc318ed4080fc98860ae1c1d3a2ad080d9d4583 100644 GIT binary patch delta 20 acmeyu_l1w!iISg0KTXcb{VdjOw^-8i6LTh? zVlfjD09nSQ#>fSP984UHJV1IfAL~0t`N@A-Z*VIBWs6urgyQ4}Yz~Y{n-$q7F)}Jm z-olX%R3gLK#HclSKIdL;d5{_o5TP?Uoy&&NXz~)SAaOmAfIg78#Zg?6T2P!>m8!{F zBtMy#+m6w6vJdw`UTvVHCU220M5h#wi2+=XCRY*M)F{^E#G;h2A~TRWbC7|IMMXLw zj^X4wo&ZMM$pWmBlV9)@GipqZ;nf8?t&3Mt+!3gt0Eml04r5>vU}RzBV=Qu_CJ)h;W!3#V5rBvh@~AKw?Qok>=zEzHNdIK!HXeE@lD}42(c!?);UE z%99WBOE9W#zQsSAkf0KL|52LJ#7 delta 459 zcmX}mK}!Nb6bJBqwyW#vn1*3NX@+I183raq>Ppa|h>!w_pg__r2$WebMHjsk9eWp{ zOVYW62nB^imri|vuuGpHI(3ZZ6|}?rhS~YQ$2*O@=xmWOO%fk8zjp50%q~L?ee#mK zCSBQ1pamOJQQnYr1<&QNzBOINM|t5wNxFWEER&##r7F0he1IS4t*?%NXvP&{3`cBP z2zIt@7XU&0RLdf=X>WjzZ}zUkPZ^bn;U_bx> diff --git a/Sampling-based Planning/rrt_3D/rrt3D.py b/Sampling-based Planning/rrt_3D/rrt3D.py index 0261b2b..5c35ea5 100644 --- a/Sampling-based Planning/rrt_3D/rrt3D.py +++ b/Sampling-based Planning/rrt_3D/rrt3D.py @@ -45,7 +45,7 @@ class rrtstar(): if not isCollide(self, xnearest, xnew): self.V.append(xnew) # add point self.wireup(xnew, xnearest) - # visualization(self) + visualization(self) self.i += 1 self.ind += 1 if getDist(xnew, self.env.goal) <= 1: diff --git a/Sampling-based Planning/rrt_3D/rrtstar3D.py b/Sampling-based Planning/rrt_3D/rrtstar3D.py index 3734e8b..318f38a 100644 --- a/Sampling-based Planning/rrt_3D/rrtstar3D.py +++ b/Sampling-based Planning/rrt_3D/rrtstar3D.py @@ -26,7 +26,7 @@ class rrtstar(): self.maxiter = 10000 # at least 4000 in this env self.stepsize = 0.5 self.gamma = 500 - self.eta = 1.1*self.stepsize + self.eta = 2*self.stepsize self.Path = [] self.done = False @@ -61,7 +61,7 @@ class rrtstar(): if not isCollide(self,xnearest,xnew): Xnear = near(self,xnew) self.V.append(xnew) # add point - # visualization(self) + visualization(self) # minimal path and minimal cost xmin, cmin = xnearest, cost(self, xnearest) + getDist(xnearest, xnew) # connecting along minimal cost path diff --git a/Sampling-based Planning/rrt_3D/utils3D.py b/Sampling-based Planning/rrt_3D/utils3D.py index 75ee073..a63385e 100644 --- a/Sampling-based Planning/rrt_3D/utils3D.py +++ b/Sampling-based Planning/rrt_3D/utils3D.py @@ -40,9 +40,9 @@ def sampleFree(initparams): if isinside(initparams, x): return sampleFree(initparams) else: - #if i < 0.05: - # return initparams.env.goal+0.01 - #else: return np.array(x) + if i < 0.05: + return initparams.env.goal+0.01 + else: return np.array(x) return np.array(x) diff --git a/Search-based Planning/Search_3D/Astar3D.py b/Search-based Planning/Search_3D/Astar3D.py index c0a8df6..85c5f06 100644 --- a/Search-based Planning/Search_3D/Astar3D.py +++ b/Search-based Planning/Search_3D/Astar3D.py @@ -12,11 +12,11 @@ import sys sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../../Search-based Planning/") from Search_3D.env3D import env -from Search_3D.utils3D import getAABB, getDist, getRay, StateSpace, Heuristic, getNearest, isCollide, hash3D, dehash, \ +from Search_3D.utils3D import getDist, getRay, StateSpace, Heuristic, getNearest, isCollide, hash3D, dehash, \ cost from Search_3D.plot_util3D import visualization import queue - +import time class Weighted_A_star(object): def __init__(self, resolution=0.5): @@ -29,7 +29,7 @@ class Weighted_A_star(object): self.env = env(resolution=resolution) self.Space = StateSpace(self) # key is the point, store g value self.start, self.goal = getNearest(self.Space, self.env.start), getNearest(self.Space, self.env.goal) - self.AABB = getAABB(self.env.blocks) + # self.AABB = getAABB(self.env.blocks) self.Space[hash3D(getNearest(self.Space, self.start))] = 0 # set g(x0) = 0 self.h = Heuristic(self.Space, self.goal) @@ -120,5 +120,7 @@ class Weighted_A_star(object): if __name__ == '__main__': + sta = time.time() Astar = Weighted_A_star(1) Astar.run() + print(time.time() - sta) \ No newline at end of file diff --git a/Search-based Planning/Search_3D/LP_Astar3D.py b/Search-based Planning/Search_3D/LP_Astar3D.py new file mode 100644 index 0000000..e465ed0 --- /dev/null +++ b/Search-based Planning/Search_3D/LP_Astar3D.py @@ -0,0 +1,190 @@ +import numpy as np +import matplotlib.pyplot as plt + +import os +import sys + +sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../../Search-based Planning/") +from Search_3D.env3D import env +from Search_3D import Astar3D +from Search_3D.utils3D import getDist, getRay, StateSpace, Heuristic, getNearest, isinbound, isinball, hash3D, dehash, \ + cost, obstacleFree +from Search_3D.plot_util3D import visualization +import queue +import pyrr +import time + + +class Lifelong_Astar(object): + def __init__(self,resolution = 1): + self.Alldirec = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 0], [1, 0, 1], [0, 1, 1], [1, 1, 1], + [-1, 0, 0], [0, -1, 0], [0, 0, -1], [-1, -1, 0], [-1, 0, -1], [0, -1, -1], + [-1, -1, -1], + [1, -1, 0], [-1, 1, 0], [1, 0, -1], [-1, 0, 1], [0, 1, -1], [0, -1, 1], + [1, -1, -1], [-1, 1, -1], [-1, -1, 1], [1, 1, -1], [1, -1, 1], [-1, 1, 1]]) + self.env = env(resolution=resolution) + self.g = StateSpace(self) + self.start, self.goal = getNearest(self.g, self.env.start), getNearest(self.g, self.env.goal) + self.x0, self.xt = hash3D(self.start), hash3D(self.goal) + self.v = StateSpace(self) # rhs(.) = g(.) = inf + self.v[hash3D(self.start)] = 0 # rhs(x0) = 0 + self.h = Heuristic(self.g, self.goal) + + self.OPEN = queue.QueuePrior() # store [point,priority] + self.OPEN.put(self.x0, [self.h[self.x0],0]) + self.CLOSED = set() + + # used for A* + self.done = False + self.Path = [] + self.V = [] + self.ind = 0 + + # initialize children list + self.CHILDREN = {} + self.getCHILDRENset() + + # initialize cost list + self.COST = {} + _ = self.costset() + + def costset(self): + NodeToChange = set() + for strxi in self.CHILDREN.keys(): + children = self.CHILDREN[strxi] + xi = dehash(strxi) + toUpdate = [self.cost(xj,xi) for xj in children] + if strxi in self.COST: + # if the old cost not equal to new cost + diff = np.not_equal(self.COST[strxi],toUpdate) + cd = np.array(children)[diff] + for i in cd: + NodeToChange.add(hash3D(i)) + self.COST[strxi] = toUpdate + else: + self.COST[strxi] = toUpdate + return NodeToChange + + def getCOSTset(self,strxi,xj): + ind, children = 0, self.CHILDREN[strxi] + for i in children: + if all(i == xj): + return self.COST[strxi][ind] + ind += 1 + + + def children(self, x): + allchild = [] + resolution = self.env.resolution + for direc in self.Alldirec: + child = np.array(list(map(np.add,x,np.multiply(direc,resolution)))) + if isinbound(self.env.boundary,child): + allchild.append(child) + return allchild + + def getCHILDRENset(self): + for strxi in self.g.keys(): + xi = dehash(strxi) + self.CHILDREN[strxi] = self.children(xi) + + def isCollide(self, x, child): + ray , dist = getRay(x, child) , getDist(x, child) + if not isinbound(self.env.boundary,child): + return True, dist + for i in self.env.AABB: + shot = pyrr.geometric_tests.ray_intersect_aabb(ray, i) + if shot is not None: + dist_wall = getDist(x, shot) + if dist_wall <= dist: # collide + return True, dist + for i in self.env.balls: + if isinball(i, child): + return True, dist + shot = pyrr.geometric_tests.ray_intersect_sphere(ray, i) + if shot != []: + dists_ball = [getDist(x, j) for j in shot] + if all(dists_ball <= dist): # collide + return True, dist + return False, dist + + def cost(self, x, y): + collide, dist = self.isCollide(x, y) + if collide: return np.inf + else: return dist + + def key(self,strxi,epsilion = 1): + return [min(self.g[strxi],self.v[strxi]) + epsilion*self.h[strxi],min(self.g[strxi],self.v[strxi])] + + def path(self): + path = [] + strx = self.xt + strstart = self.x0 + ind = 0 + while strx != strstart: + j = dehash(strx) + nei = self.CHILDREN[strx] + gset = [self.g[hash3D(xi)] for xi in nei] + # collision check and make g cost inf + for i in range(len(nei)): + if self.isCollide(nei[i],j)[0]: + gset[i] = np.inf + parent = nei[np.argmin(gset)] + path.append([dehash(strx), parent]) + strx = hash3D(parent) + if ind > 100: + break + ind += 1 + return path + + #------------------Lifelong Plannning A* + def UpdateMembership(self,strxi, xi, xparent=None): + if strxi != self.x0: + self.v[strxi] = min([self.g[hash3D(j)] + self.getCOSTset(strxi,j) for j in self.CHILDREN[strxi]]) + self.OPEN.check_remove(strxi) + if self.g[strxi] != self.v[strxi]: + self.OPEN.put(strxi,self.key(strxi)) + + def ComputePath(self): + print('computing path ...') + while self.key(self.xt) > self.OPEN.top_key() or self.v[self.xt] != self.g[self.xt]: + strxi = self.OPEN.get() + xi = dehash(strxi) + # if g > rhs, overconsistent + if self.g[strxi] > self.v[strxi]: + self.g[strxi] = self.v[strxi] + # add xi to expanded node set + if strxi not in self.CLOSED: + self.V.append(xi) + self.CLOSED.add(strxi) + else: # underconsistent and consistent + self.g[strxi] = np.inf + self.UpdateMembership(strxi, xi) + for xj in self.CHILDREN[strxi]: + strxj = hash3D(xj) + self.UpdateMembership(strxj, xj) + + # visualization(self) + self.ind += 1 + self.Path = self.path() + self.done = True + visualization(self) + plt.pause(2) + + def change_env(self): + self.env.change() + self.done = False + self.Path = [] + self.CLOSED = set() + N = self.costset() + for strxi in N: + xi = dehash(strxi) + self.UpdateMembership(strxi,xi) + +if __name__ == '__main__': + sta = time.time() + Astar = Lifelong_Astar(1) + Astar.ComputePath() + Astar.change_env() + Astar.ComputePath() + plt.show() + print(time.time() - sta) \ No newline at end of file diff --git a/Search-based Planning/Search_3D/LRT_Astar3D.py b/Search-based Planning/Search_3D/LRT_Astar3D.py index 51a805c..d2dd224 100644 --- a/Search-based Planning/Search_3D/LRT_Astar3D.py +++ b/Search-based Planning/Search_3D/LRT_Astar3D.py @@ -13,7 +13,7 @@ import sys sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../../Search-based Planning/") from Search_3D.env3D import env from Search_3D import Astar3D -from Search_3D.utils3D import getAABB, getDist, getRay, StateSpace, Heuristic, getNearest, isCollide, hash3D, dehash, \ +from Search_3D.utils3D import getDist, getRay, StateSpace, Heuristic, getNearest, isCollide, hash3D, dehash, \ cost, obstacleFree from Search_3D.plot_util3D import visualization import queue @@ -79,5 +79,5 @@ class LRT_A_star2: if __name__ == '__main__': - T = LRT_A_star2(resolution=0.5, N=1) + T = LRT_A_star2(resolution=0.5, N=150) T.run() diff --git a/Search-based Planning/Search_3D/RTA_Astar3D.py b/Search-based Planning/Search_3D/RTA_Astar3D.py index 728b25d..aaf41aa 100644 --- a/Search-based Planning/Search_3D/RTA_Astar3D.py +++ b/Search-based Planning/Search_3D/RTA_Astar3D.py @@ -13,7 +13,7 @@ import sys sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../../Search-based Planning/") from Search_3D.env3D import env from Search_3D import Astar3D -from Search_3D.utils3D import getAABB, getDist, getRay, StateSpace, Heuristic, getNearest, isCollide, hash3D, dehash, \ +from Search_3D.utils3D import getDist, getRay, StateSpace, Heuristic, getNearest, isCollide, hash3D, dehash, \ cost, obstacleFree from Search_3D.plot_util3D import visualization import queue @@ -74,5 +74,5 @@ class RTA_A_star: if __name__ == '__main__': - T = RTA_A_star(resolution=0.5, N=500) + T = RTA_A_star(resolution=0.5, N=100) T.run() \ No newline at end of file diff --git a/Search-based Planning/Search_3D/__pycache__/Astar3D.cpython-37.pyc b/Search-based Planning/Search_3D/__pycache__/Astar3D.cpython-37.pyc index 32a2674d7185f04b3ec20bad22431d6b27a38e3a..fa681606d959ce113c94e6cf45c70f72c73600a9 100644 GIT binary patch delta 1114 zcmY*XId2m|6y6!{Zq`@sI6gw+Ty`A7(Vzqrj-)~qp`bvxV72j%oVC{WYCI7!LL|rv zy|o3cT~en&e0B zUw)S%noC^M&o%Sayt1iZs22L?3*|3tZZJ30szo;MCVHg0$QB1nLyc%`If}4iR_l@e zx5p$N-Xq`1`PnKhkYks=(He$`3ln!(BR$$9+nPxH_N^W$?4+jwrbN1j`)D0;%muwY zuIvfC$DR~%bpuhm)gu&jQ?RDR%pfyFZx$4@gDfEMjRkyiDhGi^GO}j1AtmVJv0V5g zR(h0|v=vCh;oRv;YsdFpkFy%5*d7Bw8qWf!#`uV`JHp{Y>h-qc^EgV(km;T^0p>@I zm%|JFLuL&ejck+K#DcX&QzSu3G(0ul&rIVH3qYDq5U`e8F?lI0nk&gNMii`_JvHN$ zjD;_(>&J^o7ZC~w%K$P~XM*GW_#DEz;vCoIDRgfksMKcAN+RR{WSq4Qn~Xbx@f9Sq zNXC5D;tOaQ0ry%$#yW!YRg7N)crNw85eIx;X#*ch?;NyWRg%1f4B6wDpfupnktgYq z$2ZWYM~$lD8dX~NU^OIhqUxd&!)pBEiF&07dR0PH5oW5gujuLtz6nHC$NQxPsQQ2P zCe&|+uM?m4a9CW`ocVK!8d}so?A*V)c^+MCX@y4b+(+Wd5W?f+T{0SePTn}KU`Vy~ zAzGLb-}zr2RfSv{0F&t8fGm>8Syp1hN-17iw%v5RmTmJfh*nnQh;6&=nr%x1W)pPz zB+zoA>4?B@3*Xzn8FYgpn9>9TcD{uI+U~%dx}7MM_3$)x7+$8bZ`J&lP@xO2$)wrd zZ?KwBZ}VN`VY-eqcCd?F6>|sJD`o$!Lt5L-wtMKa2PP;wMF1T>=M=SwMT;awHsG^H Jdz=BP|1Tmz_&!sUH)0hZRK)l4ZkNJemvhf z$rah>lchIftzx~%G>>_9SZbB)Wu~2coE`b(&v@xRt5^KW>D-x?nCxLXEr<4fQLAlj zB|GdJ`*LwtFSBEjI?|d(6U9fGlks%)ERhv9?YM^edY^4*BKO5)H_#{Jx=#H1(8)ZFtzhO(@8ghYf@Z2EuN8gtAvu$X&Dg%>dA>B2Aadf6tzk1 zOlRcNv8m*{vA8=e^>!>R7r^aFUJFAn0KWk`wgzkjY3;^t!w0VHj&MOpb3bxJFo?1D z!l?1GQ#Yv-Kplml`cWgO%VT~JXm?d57ZJ|J_lsfIkvETNpHrz<^ z_Clsg5e3T^n|4ZP`Q#D5d3=rNd4g$z>j=`o?n8#$8G;qXxt<3(a_Nc}K?Rr2-2W?ga3y1pOaYD(q~svDG;JWhRmtX@&IQ3->JC{vYv)l^Tw zIudo0?w1@Gw}*WT0xE{BwyK-JpHcxqo%T6uH#Wsy=vUlZZ=0NTSR- z8%fSHw~lX8NVT;_7G(qv{+AD`B5oLgrreAeEJ*o9!N}LCeCf z??U6iS=(MExziOvxVAm`F&5~k9ql#!hEVCiHZfL>dJ3wvfA#+@Nb-$V=#T0{|*2E diff --git a/Search-based Planning/Search_3D/__pycache__/env3D.cpython-37.pyc b/Search-based Planning/Search_3D/__pycache__/env3D.cpython-37.pyc index 7b46412f6a57f303c03f9ea7238e4d775f079c9d..07487bbd67a3dacb5a853863ba1bfb3d5a3878c1 100644 GIT binary patch delta 915 zcmZ8f&1w`u5bo-k>FJr>>?XUSyD@6egmsb_5k;b>O^gZ>0^&hf&}EaJOn!8YJwp_k zki*6YkUk692k_uQdb(Jxk~P`mZLo7tAZ-30ac2`3aGLg+=Z5S$kdRk>@rp9@s=A( zRo&ovDRg_R<%NFetrvO#Oz|fyIP+Pjd`o|frtm90?`_M;}*ZxzM0wURqnqUqH>h{eX96*#y*iLn4&WUMRUg) zAJRkg$tsx&nYvkZ=Ha6GQ6`h3jaF+ywbgUF; zKL~6kHpMwTM;*RA5PBGI;9_vnVk=HM%NwUC1YBRiYD;#*H~Ab+P3S=;mRCF7D7=MT zF>RaZRp6Z;cDJSvON)V<{}Ct52=?gJu4 z1P2L6cYnc4oSggxA}(esq?62NcfK>3ncc_iYi4FSo%R`8`T2XN%o%&9;LmKlETPA1 z*E18Pb*4Neu9y;StKx=Sr4B8<9J*~*@j?-L^;8>xDQk-b{=-enGH-SQH}vooki>m1R0&=E9mjIS>SWCZPsGaJB2$-fwGi+VBZ5OqN6xn zO3K{w68PqY7yJs}J38J!jB(N@UxmC`^v3$8sA&&^0mm(bse5Cl>GoeLhI_+?wlEcd z9>=3||AmWTl4{oCv+7apA`CE$sgl_j=`d=}|7SJ=PXp>*$Wb^%7$(qL12+*;M%6|r zW#UWOI5xW?w=_-p0)cvWfyV1W#3Xv`AO!fLqfnzpwH8Sk_~ubej#8N}A}Txz&vCiL bJ<+x3RF9f3QOssA7eMeQcH@DZDKVQJg7!Df~d1D@7ofK~r$^ zZ?>aM^~p>i4M4yG#H>K<48+9-K%zpch9Qf=g&|fUhN+gRmbr$phB=$5hyx_LfU$h0*oAtRpNoAsimm_MVa|UddTV~e_#@m;sYuJIVFWr z5{!WkVDc+s1d2|cz%HwOi@hYjAU->_(iJFD4E7{rl@OAFP-#C+?#X^^n%uHLWkrf0 bLTU0_PDK#`Ad`cIg^7b14p}G5arpxP@Q6;~ delta 84 zcmdli{#Jy~iIT`=@Tt}toOvYhLYb7njzE)Xs@~X5E%Px4U zWvnQ|V`37<^&eRr4)pmnQ5-Ug%9+hLGx4{XOv9wH+}DkMUu#x2WCD@wL6g^I3Vt%q zcK74Jq5O)f1 z;DhjFf)boLvAzes4*!rl(r7V-Jkkl#@W?=O>sk;JPnv^Yct6Z@Gck&OCxk{}4W=_W z4$FKlF&41%C=!AWKbsiGL5Dj)+=>0a_zRXIa*$4*BNSxM!z*)|oq<2h9LqvHIuM$q zxhc39EsUfP`s}O!9nv?8ZpEz?Ta{|<0_;XdPZf|?rEv7ojgS^JcHY3R(cAjRp0WZ{ zvCKl*jXYL7zO8OWyoeWCWwI=or{7VF%DwXE%0?N9%1V4z`SP=^u$%D0p1!ThpN~1N zwPNojJ;Mt>Rkygumz7mb?!nX8>FC}u=iVM1#+FArdW_&Ee71rDk#z4bN|KyKSgoA(=Tz17&1bhv$I6aP z(@c<$$|#8K8`Zn@hA3&WvgzvrqvO@i+E-ZtzO{QY_PY{|A!?T@XGs>rCa=T3U09J2~XlFb{?L^&*p~F!5LS%&NXZ%4+p)$ N4KvC6c*G2G_AfxT;K~31 delta 1223 zcmYjQU1%It6rM9PJDJ^`+3o&hf3mwV8_cF-tu{^7AXQ9S+aghD+C~kr>tyeC9kPGD zvylciRA>cXM8W~7(1zwgd?P#K?)kar_kKFD zSGE^yJEG89{O+Hte{9=%xVt^Q`f6H?-d0vDF(EP{dmC14krR31#Kf2=&>0tHQ4}ST z9H}S7__`|Z2t_>6Otw-hDNqDlQk==Jf1H7vz>m|?Xh&(Eq3yp$&``GE#w&g0tn$v> zmfDB=N)PUbaMSZj7_>wcim&o3+l;Nb^qI?J?4N74 z>VPkE__^9mj?#smTk{=NW{8$q{3V{k7qzIJcuaW=m$ZA3!@smeNaIU-7Rq=h~RWGuR0 zKIeeU5kYAlnx^sJ5i@auxcf$9Y&7#t&ioQa*-CPh@)-thhLum+DsD&f@GU-wmS6{eik6}~hq4{qi#E*te2(F} zRxb23Iei-M6f2r^Hv?UGeorQ`WuMNRC!x