1+ '''
2+ Author: Karan Chawla
3+ 7th June, '18
4+ Reference: https://arxiv.org/pdf/1404.2334.pdf
5+ '''
6+ import random
7+ import numpy as np
8+ import math
9+ import copy
10+ import matplotlib .pyplot as plt
11+
12+ show_animation = True
13+
14+ class InformedRRTStar ():
15+
16+ def __init__ (self , start , goal , obstacleList , randArea , expandDis = 0.5 , goalSampleRate = 10 , maxIter = 200 ):
17+
18+ self .start = Node (start [0 ], start [1 ])
19+ self .goal = Node (goal [0 ], goal [1 ])
20+ self .minrand = randArea [0 ]
21+ self .maxrand = randArea [1 ]
22+ self .expandDis = expandDis
23+ self .goalSampleRate = goalSampleRate
24+ self .maxIter = maxIter
25+ self .obstacleList = obstacleList
26+
27+ def InformedRRTStarSearch (self , animation = True ):
28+
29+ self .nodeList = [self .start ]
30+ # max length we expect to find in our 'informed' sample space, starts as infinite
31+ cBest = float ('inf' )
32+ pathLen = float ('inf' )
33+ treeSize = 0
34+ pathSize = 0
35+ solutionSet = set ()
36+ path = None
37+
38+ # Computing the sampling space
39+ cMin = math .sqrt (pow (self .start .x - self .goal .x , 2 ) + pow (self .start .y - self .goal .y , 2 ))
40+ xCenter = np .matrix ([[(self .start .x + self .goal .x ) / 2.0 ], [(self .start .y + self .goal .y ) / 2.0 ], [0 ]])
41+ a1 = np .matrix ([[(self .goal .x - self .start .x ) / cMin ], [(self .goal .y - self .start .y ) / cMin ], [0 ]])
42+ id1_t = np .matrix ([1.0 , 0.0 , 0.0 ]) # first column of idenity matrix transposed
43+ M = np .dot (a1 , id1_t )
44+ U , S , Vh = np .linalg .svd (M , 1 , 1 )
45+ C = np .dot (np .dot (U , np .diag ([1.0 , 1.0 , np .linalg .det (U ) * np .linalg .det (np .transpose (Vh ))])), Vh )
46+
47+ for i in range (self .maxIter ):
48+ # Sample space is defined by cBest
49+ # cMin is the minimum distance between the start point and the goal
50+ # xCenter is the midpoint between the start and the goal
51+ # cBest changes when a new path is found
52+
53+ rnd = self .sample (cBest , cMin , xCenter , C )
54+ nind = self .getNearestListIndex (self .nodeList , rnd )
55+ nearestNode = self .nodeList [nind ]
56+ # steer
57+ theta = math .atan2 (rnd [1 ] - nearestNode .y , rnd [0 ] - nearestNode .x )
58+ newNode = self .getNewNode (theta , nind , nearestNode )
59+ d = self .lineCost (nearestNode , newNode )
60+ if self .__CollisionCheck (newNode , self .obstacleList ) and self .check_collision_extend (nearestNode , theta , d ):
61+ nearInds = self .findNearNodes (newNode )
62+ newNode = self .chooseParent (newNode , nearInds )
63+
64+ self .nodeList .append (newNode )
65+ self .rewire (newNode , nearInds )
66+
67+ if self .isNearGoal (newNode ):
68+ solutionSet .add (newNode )
69+ lastIndex = len (self .nodeList ) - 1
70+ tempPath = self .getFinalCourse (lastIndex )
71+ tempPathLen = self .getPathLen (tempPath )
72+ if tempPathLen < pathLen :
73+ path = tempPath
74+ cBest = tempPathLen
75+
76+ if animation :
77+ self .drawGraph (rnd )
78+
79+ return path
80+
81+ def chooseParent (self , newNode , nearInds ):
82+ if len (nearInds ) == 0 :
83+ return newNode
84+
85+ dList = []
86+ for i in nearInds :
87+ dx = newNode .x - self .nodeList [i ].x
88+ dy = newNode .y - self .nodeList [i ].y
89+ d = math .sqrt (dx ** 2 + dy ** 2 )
90+ theta = math .atan2 (dy , dx )
91+ if self .check_collision_extend (self .nodeList [i ], theta , d ):
92+ dList .append (self .nodeList [i ].cost + d )
93+ else :
94+ dList .append (float ('inf' ))
95+
96+ minCost = min (dList )
97+ minInd = nearInds [dList .index (minCost )]
98+
99+ if minCost == float ('inf' ):
100+ print ("mincost is inf" )
101+ return newNode
102+
103+ newNode .cost = minCost
104+ newNode .parent = minInd
105+
106+ return newNode
107+
108+ def findNearNodes (self , newNode ):
109+ nnode = len (self .nodeList )
110+ r = 50.0 * math .sqrt ((math .log (nnode ) / nnode ))
111+ dlist = [(node .x - newNode .x ) ** 2 +
112+ (node .y - newNode .y ) ** 2 for node in self .nodeList ]
113+ nearinds = [dlist .index (i ) for i in dlist if i <= r ** 2 ]
114+ return nearinds
115+
116+ def sample (self , cMax , cMin , xCenter , C ):
117+ if cMax < float ('inf' ):
118+ r = [cMax / 2.0 , math .sqrt (cMax ** 2 - cMin ** 2 )/ 2.0 ,
119+ math .sqrt (cMax ** 2 - cMin ** 2 )/ 2.0 ]
120+ L = np .diag (r )
121+ xBall = self .sampleUnitBall ()
122+ rnd = np .dot (np .dot (C , L ), xBall ) + xCenter
123+ rnd = [rnd [(0 ,0 )], rnd [(1 ,0 )]]
124+ else :
125+ rnd = self .sampleFreeSpace ()
126+
127+ return rnd
128+
129+ def sampleUnitBall (self ):
130+ a = random .random ()
131+ b = random .random ()
132+
133+ if b < a :
134+ a , b = b , a
135+
136+ sample = (b * math .cos (2 * math .pi * a / b ),
137+ b * math .sin (2 * math .pi * a / b ))
138+ return np .array ([[sample [0 ]], [sample [1 ]], [0 ]])
139+
140+ def sampleFreeSpace (self ):
141+ if random .randint (0 ,100 ) > self .goalSampleRate :
142+ rnd = [random .uniform (self .minrand , self .maxrand ),
143+ random .uniform (self .minrand , self .maxrand )]
144+ else :
145+ rnd = [self .goal .x , self .goal .y ]
146+
147+ return rnd
148+
149+ def getPathLen (self , path ):
150+ pathLen = 0
151+ for i in range (1 , len (path )):
152+ node1_x = path [i ][0 ]
153+ node1_y = path [i ][1 ]
154+ node2_x = path [i - 1 ][0 ]
155+ node2_y = path [i - 1 ][1 ]
156+ pathLen += math .sqrt ((node1_x - node2_x )** 2 + (node1_y - node2_y )** 2 )
157+
158+ return pathLen
159+
160+ def lineCost (self , node1 , node2 ):
161+ return math .sqrt ((node1 .x - node2 .x )** 2 + (node1 .y - node2 .y )** 2 )
162+
163+ def getNearestListIndex (self , nodes , rnd ):
164+ dList = [(node .x - rnd [0 ])** 2 +
165+ (node .y - rnd [1 ])** 2 for node in nodes ]
166+ minIndex = dList .index (min (dList ))
167+ return minIndex
168+
169+ def __CollisionCheck (self , newNode , obstacleList ):
170+ for (ox , oy , size ) in obstacleList :
171+ dx = ox - newNode .x
172+ dy = oy - newNode .y
173+ d = dx * dx + dy * dy
174+ if d <= 1.1 * size ** 2 :
175+ return False #collision
176+
177+ return True # safe
178+
179+ def getNewNode (self , theta , nind , nearestNode ):
180+ newNode = copy .deepcopy (nearestNode )
181+
182+ newNode .x += self .expandDis * math .cos (theta )
183+ newNode .y += self .expandDis * math .sin (theta )
184+
185+ newNode .cost += self .expandDis
186+ newNode .parent = nind
187+ return newNode
188+
189+ def isNearGoal (self , node ):
190+ d = self .lineCost (node , self .goal )
191+ if d < self .expandDis :
192+ return True
193+ return False
194+
195+ def rewire (self , newNode , nearInds ):
196+ nnode = len (self .nodeList )
197+ for i in nearInds :
198+ nearNode = self .nodeList [i ]
199+
200+ d = math .sqrt ((nearNode .x - newNode .x )** 2 +
201+ (nearNode .y - newNode .y )** 2 )
202+
203+ scost = newNode .cost + d
204+
205+ if nearNode .cost > scost :
206+ theta = math .atan2 (newNode .y - nearNode .y ,
207+ newNode .x - nearNode .x )
208+ if self .check_collision_extend (nearNode , theta , d ):
209+ nearNode .parent = nnode - 1
210+ nearNode .cost = scost
211+
212+ def check_collision_extend (self , nearNode , theta , d ):
213+ tmpNode = copy .deepcopy (nearNode )
214+
215+ for i in range (int (d / self .expandDis )):
216+ tmpNode .x += self .expandDis * math .cos (theta )
217+ tmpNode .y += self .expandDis * math .sin (theta )
218+ if not self .__CollisionCheck (tmpNode , self .obstacleList ):
219+ return False
220+
221+ return True
222+
223+ def getFinalCourse (self , lastIndex ):
224+ path = [[self .goal .x , self .goal .y ]]
225+ while self .nodeList [lastIndex ].parent is not None :
226+ node = self .nodeList [lastIndex ]
227+ path .append ([node .x , node .y ])
228+ lastIndex = node .parent
229+ path .append ([self .start .x , self .start .y ])
230+ return path
231+ ##################################################################################
232+ def drawGraph (self , rnd = None ):
233+
234+ plt .clf ()
235+ if rnd is not None :
236+ plt .plot (rnd [0 ], rnd [1 ], "^k" )
237+ for node in self .nodeList :
238+ if node .parent is not None :
239+ if node .x or node .y is not None :
240+ plt .plot ([node .x , self .nodeList [node .parent ].x ], [
241+ node .y , self .nodeList [node .parent ].y ], "-g" )
242+
243+ for (ox , oy , size ) in self .obstacleList :
244+ plt .plot (ox , oy , "ok" , ms = 30 * size )
245+
246+ plt .plot (self .start .x , self .start .y , "xr" )
247+ plt .plot (self .goal .x , self .goal .y , "xr" )
248+ plt .axis ([- 2 , 15 , - 2 , 15 ])
249+ plt .grid (True )
250+ plt .pause (0.01 )
251+
252+ class Node ():
253+
254+ def __init__ (self , x , y ):
255+ self .x = x
256+ self .y = y
257+ self .cost = 0.0
258+ self .parent = None
259+
260+
261+ def main ():
262+ print ("Start informed rrt star planning" )
263+
264+ # create obstacles
265+ obstacleList = [
266+ (5 , 5 , 0.5 ),
267+ (9 , 6 , 1 ),
268+ (7 , 5 , 1 ),
269+ (1 , 5 , 1 ),
270+ (3 , 6 , 1 ),
271+ (7 , 9 , 1 )
272+ ]
273+
274+ # Set params
275+ rrt = InformedRRTStar (start = [0 , 0 ], goal = [5 , 10 ],
276+ randArea = [- 2 , 15 ], obstacleList = obstacleList )
277+ path = rrt .InformedRRTStarSearch (animation = show_animation )
278+
279+ # Plot path
280+ if show_animation :
281+ rrt .drawGraph ()
282+ plt .plot ([x for (x , y ) in path ], [y for (x , y ) in path ], '-r' )
283+ plt .grid (True )
284+ plt .pause (0.01 )
285+ plt .show ()
286+
287+
288+ if __name__ == '__main__' :
289+ main ()
0 commit comments