From e25dd1fc818dfbcd9e3f1c26d735f29b3245056a Mon Sep 17 00:00:00 2001 From: yue qi <391311qy@gmail.com> Date: Thu, 9 Jul 2020 00:46:50 -0700 Subject: [PATCH] 'changed' --- Search-based Planning/Search_3D/Astar3D.py | 61 ++++++------ Search-based Planning/Search_3D/Dstar3D.py | 8 -- Search-based Planning/Search_3D/LP_Astar3D.py | 91 +++++++++--------- .../Search_3D/LRT_Astar3D.py | 37 ++++--- .../Search_3D/RTA_Astar3D.py | 39 ++++---- .../__pycache__/Astar3D.cpython-37.pyc | Bin 3457 -> 3337 bytes .../__pycache__/utils3D.cpython-37.pyc | Bin 4252 -> 4261 bytes .../Search_3D/bidirectional_Astar3D.py | 82 ++++++++-------- Search-based Planning/Search_3D/utils3D.py | 9 +- 9 files changed, 151 insertions(+), 176 deletions(-) diff --git a/Search-based Planning/Search_3D/Astar3D.py b/Search-based Planning/Search_3D/Astar3D.py index 9fe608b..12b5e2e 100644 --- a/Search-based Planning/Search_3D/Astar3D.py +++ b/Search-based Planning/Search_3D/Astar3D.py @@ -12,7 +12,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.utils3D import getDist, getRay, g_Space, Heuristic, getNearest, isCollide, hash3D, dehash, \ +from Search_3D.utils3D import getDist, getRay, g_Space, Heuristic, getNearest, isCollide, \ cost from Search_3D.plot_util3D import visualization import queue @@ -27,21 +27,21 @@ class Weighted_A_star(object): [1, -1, -1], [-1, 1, -1], [-1, -1, 1], [1, 1, -1], [1, -1, 1], [-1, 1, 1]]) self.env = env(resolution=resolution) - self.Space = g_Space(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.g = g_Space(self) # key is the point, store g value + self.start, self.goal = getNearest(self.g, self.env.start), getNearest(self.g, self.env.goal) # self.AABB = getAABB(self.env.blocks) - self.Space[hash3D(getNearest(self.Space, self.start))] = 0 # set g(x0) = 0 + self.g[getNearest(self.g, self.start)] = 0 # set g(x0) = 0 - self.h = Heuristic(self.Space, self.goal) + self.h = Heuristic(self.g, self.goal) self.Parent = {} self.CLOSED = set() self.V = [] self.done = False self.Path = [] self.ind = 0 - self.x0, self.xt = hash3D(self.start), hash3D(self.goal) + self.x0, self.xt = self.start, self.goal self.OPEN = queue.QueuePrior() # store [point,priority] - self.OPEN.put(self.x0, self.Space[self.x0] + self.h[self.x0]) # item, priority = g + h + self.OPEN.put(self.x0, self.g[self.x0] + self.h[self.x0]) # item, priority = g + h self.lastpoint = self.x0 def children(self, x): @@ -54,29 +54,27 @@ class Weighted_A_star(object): def run(self, N=None): xt = self.xt - strxi = self.x0 + xi = self.x0 while xt not in self.CLOSED and self.OPEN: # while xt not reached and open is not empty - strxi = self.OPEN.get() - xi = dehash(strxi) - if strxi not in self.CLOSED: - self.V.append(xi) - self.CLOSED.add(strxi) # add the point in CLOSED set + xi = self.OPEN.get() + if xi not in self.CLOSED: + self.V.append(np.array(xi)) + self.CLOSED.add(xi) # add the point in CLOSED set visualization(self) allchild = self.children(xi) for xj in allchild: - strxj = hash3D(xj) - if strxj not in self.CLOSED: - gi, gj = self.Space[strxi], self.Space[strxj] + if xj not in self.CLOSED: + gi, gj = self.g[xi], self.g[xj] a = gi + cost(xi, xj) if a < gj: - self.Space[strxj] = a - self.Parent[strxj] = xi - if (a, strxj) in self.OPEN.enumerate(): + self.g[xj] = a + self.Parent[xj] = xi + if (a, xj) in self.OPEN.enumerate(): # update priority of xj - self.OPEN.put(strxj, a + 1 * self.h[strxj]) + self.OPEN.put(xj, a + 1 * self.h[xj]) else: # add xj in to OPEN set - self.OPEN.put(strxj, a + 1 * self.h[strxj]) + self.OPEN.put(xj, a + 1 * self.h[xj]) # For specified expanded nodes, used primarily in LRTA* if N: if len(self.CLOSED) % N == 0: @@ -84,7 +82,7 @@ class Weighted_A_star(object): if self.ind % 100 == 0: print('number node expanded = ' + str(len(self.V))) self.ind += 1 - self.lastpoint = strxi + self.lastpoint = xi # if the path finding is finished if xt in self.CLOSED: self.done = True @@ -98,22 +96,21 @@ class Weighted_A_star(object): def path(self): path = [] - strx = self.lastpoint - # strstart = hash3D(getNearest(self.Space, self.env.start)) - strstart = self.x0 - while strx != strstart: - path.append([dehash(strx), self.Parent[strx]]) - strx = hash3D(self.Parent[strx]) + x = self.lastpoint + start = self.x0 + while x != start: + path.append([x, self.Parent[x]]) + x = self.Parent[x] # path = np.flip(path, axis=0) return path # utility used in LRTA* def reset(self, xj): - self.Space = g_Space(self) # key is the point, store g value + self.g = g_Space(self) # key is the point, store g value self.start = xj - self.Space[hash3D(getNearest(self.Space, self.start))] = 0 # set g(x0) = 0 - self.x0 = hash3D(xj) - self.OPEN.put(self.x0, self.Space[self.x0] + self.h[self.x0]) # item, priority = g + h + self.g[getNearest(self.g, self.start)] = 0 # set g(x0) = 0 + self.x0 = xj + self.OPEN.put(self.x0, self.g[self.x0] + self.h[self.x0]) # item, priority = g + h self.CLOSED = set() # self.h = h(self.Space, self.goal) diff --git a/Search-based Planning/Search_3D/Dstar3D.py b/Search-based Planning/Search_3D/Dstar3D.py index ebb0cfd..2991238 100644 --- a/Search-based Planning/Search_3D/Dstar3D.py +++ b/Search-based Planning/Search_3D/Dstar3D.py @@ -12,14 +12,6 @@ from Search_3D.utils3D import StateSpace, getDist, getRay, isinbound, isinball import pyrr -def getNearest(Space,pt): - '''get the nearest point on the grid''' - mindis,minpt = 1000,None - for pts in Space: - dis = getDist(pts,pt) - if dis < mindis: - mindis,minpt = dis,pts - return minpt def isCollide(initparams, x, child): '''see if line intersects obstacle''' diff --git a/Search-based Planning/Search_3D/LP_Astar3D.py b/Search-based Planning/Search_3D/LP_Astar3D.py index 25ae599..c64e59c 100644 --- a/Search-based Planning/Search_3D/LP_Astar3D.py +++ b/Search-based Planning/Search_3D/LP_Astar3D.py @@ -25,9 +25,9 @@ class Lifelong_Astar(object): self.env = env(resolution=resolution) self.g = g_Space(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.x0, self.xt = self.start, self.goal self.v = g_Space(self) # rhs(.) = g(.) = inf - self.v[hash3D(self.start)] = 0 # rhs(x0) = 0 + self.v[self.start] = 0 # rhs(x0) = 0 self.h = Heuristic(self.g, self.goal) self.OPEN = queue.QueuePrior() # store [point,priority] @@ -50,26 +50,25 @@ class Lifelong_Astar(object): def costset(self): NodeToChange = set() - for strxi in self.CHILDREN.keys(): - children = self.CHILDREN[strxi] - xi = dehash(strxi) + for xi in self.CHILDREN.keys(): + children = self.CHILDREN[xi] toUpdate = [self.cost(xj,xi) for xj in children] - if strxi in self.COST: + if xi in self.COST: # if the old cost not equal to new cost - diff = np.not_equal(self.COST[strxi],toUpdate) + diff = np.not_equal(self.COST[xi],toUpdate) cd = np.array(children)[diff] for i in cd: - NodeToChange.add(hash3D(i)) - self.COST[strxi] = toUpdate + NodeToChange.add(tuple(i)) + self.COST[xi] = toUpdate else: - self.COST[strxi] = toUpdate + self.COST[xi] = toUpdate return NodeToChange - def getCOSTset(self,strxi,xj): - ind, children = 0, self.CHILDREN[strxi] + def getCOSTset(self,xi,xj): + ind, children = 0, self.CHILDREN[xi] for i in children: - if all(i == xj): - return self.COST[strxi][ind] + if i == xj: + return self.COST[xi][ind] ind += 1 @@ -77,15 +76,14 @@ class Lifelong_Astar(object): allchild = [] resolution = self.env.resolution for direc in self.Alldirec: - child = np.array(list(map(np.add,x,np.multiply(direc,resolution)))) + child = tuple(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) + for xi in self.g.keys(): + self.CHILDREN[xi] = self.children(xi) def isCollide(self, x, child): ray , dist = getRay(x, child) , getDist(x, child) @@ -112,56 +110,54 @@ class Lifelong_Astar(object): 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 key(self,xi,epsilion = 1): + return [min(self.g[xi],self.v[xi]) + epsilion*self.h[xi],min(self.g[xi],self.v[xi])] def path(self): path = [] - strx = self.xt - strstart = self.x0 + x = self.xt + start = self.x0 ind = 0 - while strx != strstart: - j = dehash(strx) - nei = self.CHILDREN[strx] - gset = [self.g[hash3D(xi)] for xi in nei] + while x != start: + j = x + nei = self.CHILDREN[x] + gset = [self.g[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) + path.append([x, parent]) + x = 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 UpdateMembership(self, xi, xparent=None): + if xi != self.x0: + self.v[xi] = min([self.g[j] + self.getCOSTset(xi,j) for j in self.CHILDREN[xi]]) + self.OPEN.check_remove(xi) + if self.g[xi] != self.v[xi]: + self.OPEN.put(xi,self.key(xi)) 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) + xi = self.OPEN.get() # if g > rhs, overconsistent - if self.g[strxi] > self.v[strxi]: - self.g[strxi] = self.v[strxi] + if self.g[xi] > self.v[xi]: + self.g[xi] = self.v[xi] # add xi to expanded node set - if strxi not in self.CLOSED: + if xi not in self.CLOSED: self.V.append(xi) - self.CLOSED.add(strxi) + self.CLOSED.add(xi) 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) + self.g[xi] = np.inf + self.UpdateMembership(xi) + for xj in self.CHILDREN[xi]: + self.UpdateMembership(xj) # visualization(self) self.ind += 1 @@ -176,9 +172,8 @@ class Lifelong_Astar(object): self.Path = [] self.CLOSED = set() N = self.costset() - for strxi in N: - xi = dehash(strxi) - self.UpdateMembership(strxi,xi) + for xi in N: + self.UpdateMembership(xi) if __name__ == '__main__': sta = time.time() diff --git a/Search-based Planning/Search_3D/LRT_Astar3D.py b/Search-based Planning/Search_3D/LRT_Astar3D.py index 6dd9632..528bbbb 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 getDist, getRay, g_Space, Heuristic, getNearest, isCollide, hash3D, dehash, \ +from Search_3D.utils3D import getDist, getRay, g_Space, Heuristic, getNearest, isCollide, \ cost, obstacleFree from Search_3D.plot_util3D import visualization import queue @@ -26,40 +26,37 @@ class LRT_A_star2: def updateHeuristic(self): # Initialize hvalues at infinity - for strxi in self.Astar.CLOSED: - self.Astar.h[strxi] = np.inf + for xi in self.Astar.CLOSED: + self.Astar.h[xi] = np.inf Diff = True while Diff: # repeat DP until converge hvals, lasthvals = [], [] - for strxi in self.Astar.CLOSED: - xi = dehash(strxi) - lasthvals.append(self.Astar.h[strxi]) + for xi in self.Astar.CLOSED: + lasthvals.append(self.Astar.h[xi]) # update h values if they are smaller Children = self.Astar.children(xi) - minfval = min([cost(xi, xj, settings=0) + self.Astar.h[hash3D(xj)] for xj in Children]) + minfval = min([cost(xi, xj, settings=0) + self.Astar.h[xj] for xj in Children]) # h(s) = h(s') if h(s) > c(s,s') + h(s') - if self.Astar.h[strxi] >= minfval: - self.Astar.h[strxi] = minfval - hvals.append(self.Astar.h[strxi]) + if self.Astar.h[xi] >= minfval: + self.Astar.h[xi] = minfval + hvals.append(self.Astar.h[xi]) if lasthvals == hvals: Diff = False def move(self): - strst = self.Astar.x0 - st = self.Astar.start + st = self.Astar.x0 ind = 0 # find the lowest path down hill - while strst in self.Astar.CLOSED: # when minchild in CLOSED then continue, when minchild in OPEN, stop - # strChildren = self.children(st) - strChildren = [hash3D(i) for i in self.Astar.children(st)] + while st in self.Astar.CLOSED: # when minchild in CLOSED then continue, when minchild in OPEN, stop + Children = [i for i in self.Astar.children(st)] minh, minchild = np.inf, None - for child in strChildren: + for child in Children: h = self.Astar.h[child] if h <= minh: - minh, minchild = h, dehash(child) + minh, minchild = h, child self.path.append([st, minchild]) - strst, st = hash3D(minchild), minchild - for (_, strp) in self.Astar.OPEN.enumerate(): - if strp == strst: + st = minchild + for (_, p) in self.Astar.OPEN.enumerate(): + if p == st: break ind += 1 if ind > 1000: diff --git a/Search-based Planning/Search_3D/RTA_Astar3D.py b/Search-based Planning/Search_3D/RTA_Astar3D.py index ca6d299..88c18e0 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 getDist, getRay, g_Space, Heuristic, getNearest, isCollide, hash3D, dehash, \ +from Search_3D.utils3D import getDist, getRay, g_Space, Heuristic, getNearest, isCollide, \ cost, obstacleFree from Search_3D.plot_util3D import visualization import queue @@ -23,43 +23,40 @@ class RTA_A_star: self.N = N # node to expand self.Astar = Astar3D.Weighted_A_star(resolution=resolution) # initialize A star self.path = [] # empty path - self.strst = [] + self.st = [] self.localhvals = [] def updateHeuristic(self): # Initialize hvalues at infinity self.localhvals = [] nodeset, vals = [], [] - for (_,strxi) in self.Astar.OPEN.enumerate(): - nodeset.append(strxi) - vals.append(self.Astar.Space[strxi] + self.Astar.h[strxi]) - strj, fj = nodeset[np.argmin(vals)], min(vals) - self.strst = strj + for (_,xi) in self.Astar.OPEN.enumerate(): + nodeset.append(xi) + vals.append(self.Astar.g[xi] + self.Astar.h[xi]) + j, fj = nodeset[np.argmin(vals)], min(vals) + self.st = j # single pass update of hvals - for strxi in self.Astar.CLOSED: - # xi = dehash(strxi) - self.Astar.h[strxi] = fj - self.Astar.Space[strxi] - self.localhvals.append(self.Astar.h[strxi]) + for xi in self.Astar.CLOSED: + self.Astar.h[xi] = fj - self.Astar.g[xi] + self.localhvals.append(self.Astar.h[xi]) def move(self): - strst, localhvals = self.strst, self.localhvals + st, localhvals = self.st, self.localhvals maxhval = max(localhvals) - st = dehash(strst) - sthval = self.Astar.h[strst] + sthval = self.Astar.h[st] # find the lowest path up hill while sthval < maxhval: parentsvals , parents = [] , [] # find the max child for xi in self.Astar.children(st): - strxi = hash3D(xi) - if strxi in self.Astar.CLOSED: + if xi in self.Astar.CLOSED: parents.append(xi) - parentsvals.append(self.Astar.h[strxi]) + parentsvals.append(self.Astar.h[xi]) lastst = st - st, strst = parents[np.argmax(parentsvals)], hash3D(st) + st = parents[np.argmax(parentsvals)] self.path.append([st,lastst]) # add to path - sthval = self.Astar.h[strst] - self.Astar.reset(dehash(self.strst)) + sthval = self.Astar.h[st] + self.Astar.reset(self.st) def run(self): while True: @@ -74,5 +71,5 @@ class RTA_A_star: if __name__ == '__main__': - T = RTA_A_star(resolution=0.5, N=100) + T = RTA_A_star(resolution=1, 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 e71e672364e3529b9aa5d1e9b52c0f1c5c712874..1e8e448c74035f6d2d4992f4ab5777a5154cd60a 100644 GIT binary patch delta 1713 zcmZ`(&u<$=6rMNx!|U~rB#xcf&d-K4je}ASQ9-IwR3HK=K&a@U$P!&P*~PKFvEA_m z5@{_9xg3x)%HQ#*my*Ilv^S+%=7Cx)y zU(MxghQB92o6h$$kMm{t{qeQ)_kelenZf)?xn5SB2bFpSH6B#!)i;=z@~nrfX^RG-I{_+>_7NnxL;Z}437x2SRH~1P1 z&5*SWY^f37Wiov^vPL{)j@@L48JQhXpKB22J>kfdraXj-F^6P>D+VY7rVDg(s63ed!9B=#8!$MDOZt zYYNqGnYbGLqW@Sb@@X)^!Z!IAz=rk6HD{x*jQgbmrV5LmV!FMa-|=duSdV7R*~NP5 zs2c=L&rgh_<7VKAtCXr>Bf4$AK1}17X1<24{|zu&A{*dtTQJZb!P%XVIB%yOfw+$h zwMifAV>Zx2Z4wpx*c-7aQ?V6lV;F1R=~ZkGDB#cj%;MJ?$Um zKo&h+mQ=10{gK*Uij{-V6LDF2q!NP1+7{0KQrgiARuyv5zV-ZYg=(@)P$9U2kZ28G ziYg5^38o2VRKWE-K_8RYCYU9lB8qv0B<*)jPkiA@U#yWhN1_(^9kE2r=!s@WCR$$# z@f^h$5Qd4~bLFvERJpwXP1rx~eq2k7byDC!&;k>F))ZS4(yH@D6I&|*_gP1&2Z+ z=_fTB@COB|%w1N4gPK0sh&n!XRCErg(%Quhf*L`Q;2HD(9d(iP^9T%dNYlqo$nX-Foi@yP~nlJM_sC5G_(z6>zZ&mu6~k%@aU zqt7!-kd3~}Y!9!|H07UNV(OS~5qtV5#WqOGAe6B6$#q!4ie=ebRz~TGSAP&Q63Q ztz}i_!mZI>khs_!km#Wj;s*Z#f5NJV9(zMv5GTa<&8DHM*wuXV&2PS){T{z>zO6o7 zp5B|9vIso?{H8m9l+LHi^x665*_YiVd#PKt%anN3(}PO4YFDMsf@OOdFcz%XD<2Wh z@XT+BXZoun#a{ETc)1SaPyLng%7oZ8f6cQ-)L!@3#~TwuiGMZQ@oVRQ->13c1A4cp zH{GbYv!Cjok6QAOeoue>WtDBvVNU2VX=^Zy5gXB6BCLzGIbtz!@+|_gAf`q*PamT! zdSLI&NPX5rtCGmc6R4cc5oKsM2V-6=j29=kS^|ruaTz4w@{e;Bg3CNY6|zJojDjI< zJC)>j2Zo%7_lsSjnvT zeVFyJ5vvm_qi16?R#3-OjwCj=aCe~fAII8opM3GdKo|K~hhvP`xHu++xezNAbf*v- z6~f5C9OPnCOvNhLvL!OGVk^#d2oGVFR=WiDNP9XqK(k`wD|B5M7bm2n^S`7%1AnkQ z3i>RbI`3Z8=>h&w8Na%Hx%(N3DMWr-l)w&%71KrHu?|D*6Io?XxkNq}^PVaeq^+Jj zF}CUB%CGb!971Xqlw@yT=M1u3{xW5Ww zn5v;GntV;#h5=ABYWBXa=P$^0z|lb)Xv_Ppl=WMD3oTi{ondX89k*b#Q|i7vbjtxV75gI@ZxMSY9?Dbdx$2-WPo+LBxCguYDkG}*OTQDaGxD;XiEY$&|u1{yoj1CI$k3gVUQ~?D*Ya)Ra_&B>MV=yE&?z0KP*#? z`;jktphn0h0)gldij}P8SLt-Jo8KPR0nS+VE^3$~ehN?I{Z zYs)N1JT)Ds>$XlDhZkYDel0Z|$Llp5C)I$e;TbOhoX&P#5e7XGw2p3tXJPU@UzmRz zH&`zMx}HUu3pW$9&>3chMAF*}hrY`jO=oBS7JlxWidL|*pEbz_ey@sF*H87{QQL0_ z=@H*U99qD&wR>1>EJ8Rn!DYecN+Nb=wK1m;JSWZD3-Puve#rYK1lig3p}Wk AGXMYp diff --git a/Search-based Planning/Search_3D/__pycache__/utils3D.cpython-37.pyc b/Search-based Planning/Search_3D/__pycache__/utils3D.cpython-37.pyc index 9b09abdbf58f2e3f4132ec6b9bdb67d18f3c4af2..890cc05c4b865badfb95c71e04783463dfbcd2f0 100644 GIT binary patch delta 982 zcmYjQ%}*0S6rZo%&ThMHv0y6^(gFd>hX+GA5hK!slf=jYG=eE?DQ%&)vuje!)W+0A zj5p@icxV$%^oB9fi#Ovx(F^hJAK<}*Z&pEPGQZjPX6F6g*S^lY%{aFl$3`%AzM2oe zeQ>tuvI!D{wF1jq1aR>K(j-mVn3gDqw$e@JBgrIglcpgJNh(N0VP^#CTlJ{=Ocyv7 zad?_F6=uU~oXxZ%AR@xC4U^+27DRB850S0#wHUw0*?bA!XTm?79%QUw4G6{ z4F6DLkqw=G;)g4xGn}Q$A4;@42w29CE?9eer)AX(2rL9lAH5W^^N7h@Y zaWhbd!SRJ=$d8nru5PnXk2JaDE~!{zGcd%V5iMy+vx3Fu-{+{lxq%wnfrS`YxC<8I zGNdWQ>G_?&=D6rMBpk(=Azn>1;)iqxc9+qBjfMO+l>YvD!^6$-_+gd}Z~n3&AHi&W;) zMFa%}k=co03c7M7f*^?C!as3W{s6a%8^v#KeGQy5bIzIh<~+Wa#b-tDqUX5`tMb8K z|NPopHLj*$nUG76;38C&Zt;jmMsGo~0o!Gl+3HMWh9u2CMOMhAC8^INTiTL0naE+c zIMXk>IrYj|IA_FWv$h??cD1%u4z@%Vprur7-RP7nwU~Fp6}we$SL?xwgWGMMg+Sl0rioeHv$d>XYQs zSY%y)nMr4kK`& z7&npBArcenwAJ?xYk!g)iK6;wJ*6Y+mOV{})jj)oF2R`m@5-q9WWT4Q%6G=q)09h7 z>Sbyxv5PeO=9JSWnpDrzt2Cy5r03|QD!FG*TL4JvJ%I!2rI1P1KwYT(pyU`(4psAp zfvQbiwR+?}oVUaxt_$5Gp}Q6v_4bA+;#4wYx8AOYow6vmf|xc@Q?&JtolLJz}4 z^Igi3xBy?pfYNISqT|{QenKG)B%(hb7@--09S!>dgO$=P-{pNIvrAx)^MHXrJWTKH z;ENU6p_{xP(vje1bq5sNz