python-motion-planning 1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. curve_generation/__init__.py +9 -0
  2. curve_generation/bezier_curve.py +131 -0
  3. curve_generation/bspline_curve.py +271 -0
  4. curve_generation/cubic_spline.py +128 -0
  5. curve_generation/curve.py +64 -0
  6. curve_generation/dubins_curve.py +348 -0
  7. curve_generation/fem_pos_smooth.py +114 -0
  8. curve_generation/polynomial_curve.py +226 -0
  9. curve_generation/reeds_shepp.py +736 -0
  10. global_planner/__init__.py +3 -0
  11. global_planner/evolutionary_search/__init__.py +4 -0
  12. global_planner/evolutionary_search/aco.py +186 -0
  13. global_planner/evolutionary_search/evolutionary_search.py +87 -0
  14. global_planner/evolutionary_search/pso.py +356 -0
  15. global_planner/graph_search/__init__.py +28 -0
  16. global_planner/graph_search/a_star.py +124 -0
  17. global_planner/graph_search/d_star.py +291 -0
  18. global_planner/graph_search/d_star_lite.py +188 -0
  19. global_planner/graph_search/dijkstra.py +77 -0
  20. global_planner/graph_search/gbfs.py +78 -0
  21. global_planner/graph_search/graph_search.py +87 -0
  22. global_planner/graph_search/jps.py +165 -0
  23. global_planner/graph_search/lazy_theta_star.py +114 -0
  24. global_planner/graph_search/lpa_star.py +230 -0
  25. global_planner/graph_search/s_theta_star.py +133 -0
  26. global_planner/graph_search/theta_star.py +171 -0
  27. global_planner/graph_search/voronoi.py +200 -0
  28. global_planner/sample_search/__init__.py +6 -0
  29. global_planner/sample_search/informed_rrt.py +152 -0
  30. global_planner/sample_search/rrt.py +151 -0
  31. global_planner/sample_search/rrt_connect.py +147 -0
  32. global_planner/sample_search/rrt_star.py +77 -0
  33. global_planner/sample_search/sample_search.py +135 -0
  34. local_planner/__init__.py +19 -0
  35. local_planner/apf.py +144 -0
  36. local_planner/ddpg.py +630 -0
  37. local_planner/dqn.py +687 -0
  38. local_planner/dwa.py +212 -0
  39. local_planner/local_planner.py +262 -0
  40. local_planner/lqr.py +146 -0
  41. local_planner/mpc.py +214 -0
  42. local_planner/pid.py +158 -0
  43. local_planner/rpp.py +147 -0
  44. python_motion_planning-1.0.dist-info/LICENSE +674 -0
  45. python_motion_planning-1.0.dist-info/METADATA +873 -0
  46. python_motion_planning-1.0.dist-info/RECORD +65 -0
  47. python_motion_planning-1.0.dist-info/WHEEL +5 -0
  48. python_motion_planning-1.0.dist-info/top_level.txt +4 -0
  49. utils/__init__.py +19 -0
  50. utils/agent/__init__.py +0 -0
  51. utils/agent/agent.py +135 -0
  52. utils/environment/__init__.py +0 -0
  53. utils/environment/env.py +134 -0
  54. utils/environment/node.py +85 -0
  55. utils/environment/point2d.py +96 -0
  56. utils/environment/pose2d.py +91 -0
  57. utils/helper/__init__.py +3 -0
  58. utils/helper/math_helper.py +65 -0
  59. utils/planner/__init__.py +0 -0
  60. utils/planner/control_factory.py +31 -0
  61. utils/planner/curve_factory.py +29 -0
  62. utils/planner/planner.py +40 -0
  63. utils/planner/search_factory.py +51 -0
  64. utils/plot/__init__.py +0 -0
  65. utils/plot/plot.py +274 -0
@@ -0,0 +1,133 @@
1
+ '''
2
+ @file: s_theta_star.py
3
+ @breif: S-Theta* motion planning
4
+ @author: Wu Maojia
5
+ @update: 2024.6.23
6
+ '''
7
+ import heapq
8
+ from math import acos
9
+
10
+ from .theta_star import ThetaStar
11
+ from python_motion_planning.utils import Env, Node
12
+
13
+
14
+ class SThetaStar(ThetaStar):
15
+ """
16
+ Class for S-Theta* motion planning.
17
+
18
+ Parameters:
19
+ start (tuple): start point coordinate
20
+ goal (tuple): goal point coordinate
21
+ env (Env): environment
22
+ heuristic_type (str): heuristic function type
23
+
24
+ Examples:
25
+ >>> import python_motion_planning as pmp
26
+ >>> planner = pmp.SThetaStar((5, 5), (45, 25), pmp.Grid(51, 31))
27
+ >>> cost, path, expand = planner.plan()
28
+ >>> planner.plot.animation(path, str(planner), cost, expand) # animation
29
+ >>> planner.run() # run both planning and animation
30
+
31
+ References:
32
+ [1] S-Theta*: low steering path-planning algorithm
33
+ """
34
+
35
+ def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean") -> None:
36
+ super().__init__(start, goal, env, heuristic_type)
37
+
38
+ def __str__(self) -> str:
39
+ return "S-Theta*"
40
+
41
+ def plan(self) -> tuple:
42
+ """
43
+ S-Theta* motion plan function.
44
+
45
+ Returns:
46
+ cost (float): path cost
47
+ path (list): planning path
48
+ expand (list): all nodes that planner has searched
49
+ """
50
+ # OPEN list (priority queue) and CLOSED list (hash table)
51
+ OPEN = []
52
+ heapq.heappush(OPEN, self.start)
53
+ CLOSED = dict()
54
+
55
+ while OPEN:
56
+ node = heapq.heappop(OPEN)
57
+
58
+ # exists in CLOSED list
59
+ if node.current in CLOSED:
60
+ continue
61
+
62
+ # goal found
63
+ if node == self.goal:
64
+ CLOSED[node.current] = node
65
+ cost, path = self.extractPath(CLOSED)
66
+ return cost, path, list(CLOSED.values())
67
+
68
+ for node_n in self.getNeighbor(node):
69
+ # exists in CLOSED list
70
+ if node_n.current in CLOSED:
71
+ continue
72
+
73
+ # path1
74
+ node_n.parent = node.current
75
+ node_n.h = self.h(node_n, self.goal)
76
+
77
+ alpha = 0.0
78
+ node_p = CLOSED.get(node.parent)
79
+ if node_p:
80
+ alpha = self.getAlpha(node_p, node_n)
81
+ node_n.g += alpha
82
+
83
+ if node_p:
84
+ self.updateVertex(node_p, node_n, alpha)
85
+
86
+ # goal found
87
+ if node_n == self.goal:
88
+ heapq.heappush(OPEN, node_n)
89
+ break
90
+
91
+ # update OPEN list
92
+ heapq.heappush(OPEN, node_n)
93
+
94
+ CLOSED[node.current] = node
95
+ return [], [], []
96
+
97
+ def updateVertex(self, node_p: Node, node_c: Node, alpha: float) -> None:
98
+ """
99
+ Update extend node information with current node's parent node.
100
+
101
+ Parameters:
102
+ node_p (Node): parent node
103
+ node_c (Node): current node
104
+ alpha (float): alpha angle
105
+ """
106
+ # if alpha == 0 or self.lineOfSight(node_c, node_p): # "alpha == 0" will cause the path to penetrate obstacles
107
+ if self.lineOfSight(node_c, node_p):
108
+ # path 2
109
+ new_g = node_p.g + self.dist(node_c, node_p) + alpha
110
+ if new_g <= node_c.g:
111
+ node_c.g = new_g
112
+ node_c.parent = node_p.current
113
+
114
+ def getAlpha(self, node_p: Node, node_c: Node):
115
+ """
116
+ α(t) represents the deviation in the trajectory to reach the goal node g
117
+ through the node t in relation to the straight-line distance between the parent of its
118
+ predecessor (t ∈ succ(p) and parent(p) = q) and the goal node.
119
+
120
+ Parameters:
121
+ node_p (Node): parent node
122
+ node_c (Node): current node
123
+
124
+ Returns:
125
+ alpha (float): alpha angle
126
+ """
127
+ d_qt = self.dist(node_p, node_c)
128
+ d_qg = self.dist(node_p, self.goal)
129
+ d_tg = self.dist(node_c, self.goal)
130
+ value = (d_qt * d_qt + d_qg * d_qg - d_tg * d_tg) / (2.0 * d_qt * d_qg)
131
+ value = max(-1.0, min(1.0, value))
132
+ cost = acos(value)
133
+ return cost
@@ -0,0 +1,171 @@
1
+ """
2
+ @file: theta_star.py
3
+ @breif: Theta* motion planning
4
+ @author: Yang Haodong, Wu Maojia
5
+ @update: 2024.6.23
6
+ """
7
+ import heapq
8
+
9
+ from .a_star import AStar
10
+ from python_motion_planning.utils import Env, Node
11
+
12
+
13
+ class ThetaStar(AStar):
14
+ """
15
+ Class for Theta* motion planning.
16
+
17
+ Parameters:
18
+ start (tuple):
19
+ start point coordinate
20
+ goal (tuple):
21
+ goal point coordinate
22
+ env (Env):
23
+ environment
24
+ heuristic_type (str):
25
+ heuristic function type
26
+
27
+ Examples:
28
+ >>> import python_motion_planning as pmp
29
+ >>> planner = pmp.ThetaStar((5, 5), (45, 25), pmp.Grid(51, 31))
30
+ >>> cost, path, expand = planner.plan()
31
+ >>> planner.plot.animation(path, str(planner), cost, expand) # animation
32
+ >>> planner.run() # run both planning and animation
33
+
34
+ References:
35
+ [1] Theta*: Any-Angle Path Planning on Grids
36
+ [2] Any-angle path planning on non-uniform costmaps
37
+ """
38
+ def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean") -> None:
39
+ super().__init__(start, goal, env, heuristic_type)
40
+
41
+ def __str__(self) -> str:
42
+ return "Theta*"
43
+
44
+ def plan(self) -> tuple:
45
+ """
46
+ Theta* motion plan function.
47
+
48
+ Returns:
49
+ cost (float): path cost
50
+ path (list): planning path
51
+ expand (list): all nodes that planner has searched
52
+ """
53
+ # OPEN list (priority queue) and CLOSED list (hash table)
54
+ OPEN = []
55
+ heapq.heappush(OPEN, self.start)
56
+ CLOSED = dict()
57
+
58
+ while OPEN:
59
+ node = heapq.heappop(OPEN)
60
+
61
+ # exists in CLOSED list
62
+ if node.current in CLOSED:
63
+ continue
64
+
65
+ # goal found
66
+ if node == self.goal:
67
+ CLOSED[node.current] = node
68
+ cost, path = self.extractPath(CLOSED)
69
+ return cost, path, list(CLOSED.values())
70
+
71
+ for node_n in self.getNeighbor(node):
72
+ # exists in CLOSED list
73
+ if node_n.current in CLOSED:
74
+ continue
75
+
76
+ # path1
77
+ node_n.parent = node.current
78
+ node_n.h = self.h(node_n, self.goal)
79
+
80
+ node_p = CLOSED.get(node.parent)
81
+
82
+ if node_p:
83
+ self.updateVertex(node_p, node_n)
84
+
85
+ # goal found
86
+ if node_n == self.goal:
87
+ heapq.heappush(OPEN, node_n)
88
+ break
89
+
90
+ # update OPEN list
91
+ heapq.heappush(OPEN, node_n)
92
+
93
+ CLOSED[node.current] = node
94
+ return [], [], []
95
+
96
+ def updateVertex(self, node_p: Node, node_c: Node) -> None:
97
+ """
98
+ Update extend node information with current node's parent node.
99
+
100
+ Parameters:
101
+ node_p (Node): parent node
102
+ node_c (Node): current node
103
+ """
104
+ if self.lineOfSight(node_c, node_p):
105
+ # path 2
106
+ if node_p.g + self.dist(node_c, node_p) <= node_c.g:
107
+ node_c.g = node_p.g + self.dist(node_c, node_p)
108
+ node_c.parent = node_p.current
109
+
110
+ def lineOfSight(self, node1: Node, node2: Node) -> bool:
111
+ """
112
+ Judge collision when moving from node1 to node2 using Bresenham.
113
+
114
+ Parameters:
115
+ node1 (Node): start node
116
+ node2 (Node): end node
117
+
118
+ Returns:
119
+ line_of_sight (bool): True if line of sight exists ( no collision ) else False
120
+ """
121
+ if node1.current in self.obstacles or node2.current in self.obstacles:
122
+ return False
123
+
124
+ x1, y1 = node1.current
125
+ x2, y2 = node2.current
126
+
127
+ if x1 < 0 or x1 >= self.env.x_range or y1 < 0 or y1 >= self.env.y_range:
128
+ return False
129
+ if x2 < 0 or x2 >= self.env.x_range or y2 < 0 or y2 >= self.env.y_range:
130
+ return False
131
+
132
+ d_x = abs(x2 - x1)
133
+ d_y = abs(y2 - y1)
134
+ s_x = 0 if (x2 - x1) == 0 else (x2 - x1) / d_x
135
+ s_y = 0 if (y2 - y1) == 0 else (y2 - y1) / d_y
136
+ x, y, e = x1, y1, 0
137
+
138
+ # check if any obstacle exists between node1 and node2
139
+ if d_x > d_y:
140
+ tau = (d_y - d_x) / 2
141
+ while not x == x2:
142
+ if e > tau:
143
+ x = x + s_x
144
+ e = e - d_y
145
+ elif e < tau:
146
+ y = y + s_y
147
+ e = e + d_x
148
+ else:
149
+ x = x + s_x
150
+ y = y + s_y
151
+ e = e + d_x - d_y
152
+ if (x, y) in self.obstacles:
153
+ return False
154
+ # swap x and y
155
+ else:
156
+ tau = (d_x - d_y) / 2
157
+ while not y == y2:
158
+ if e > tau:
159
+ y = y + s_y
160
+ e = e - d_x
161
+ elif e < tau:
162
+ x = x + s_x
163
+ e = e + d_y
164
+ else:
165
+ x = x + s_x
166
+ y = y + s_y
167
+ e = e + d_y - d_x
168
+ if (x, y) in self.obstacles:
169
+ return False
170
+
171
+ return True
@@ -0,0 +1,200 @@
1
+ """
2
+ @file: voronoi.py
3
+ @breif: Voronoi-based motion planning
4
+ @author: Yang Haodong, Wu Maojia
5
+ @update: 2024.6.23
6
+ """
7
+ import heapq, math
8
+ import numpy as np
9
+ from scipy.spatial import cKDTree, Voronoi
10
+
11
+ from .graph_search import GraphSearcher
12
+ from python_motion_planning.utils import Env, Node
13
+
14
+ class VoronoiPlanner(GraphSearcher):
15
+ """
16
+ Class for Voronoi-based motion planning.
17
+
18
+ Parameters:
19
+ start (tuple): start point coordinate
20
+ goal (tuple): goal point coordinate
21
+ env (Env): environment
22
+ heuristic_type (str): heuristic function type, default is euclidean
23
+ n_knn (int): number of edges from one sampled point
24
+ max_edge_len (float): maximum edge length
25
+ inflation_r (float): inflation range
26
+
27
+ Examples:
28
+ >>> import python_motion_planning as pmp
29
+ >>> planner = pmp.VoronoiPlanner((5, 5), (45, 25), pmp.Grid(51, 31))
30
+ >>> cost, path, _ = planner.plan() # planning results only
31
+ >>> planner.plot.animation(path, str(planner), cost, expand) # animation
32
+ >>> planner.run() # run both planning and animation
33
+ """
34
+ def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean", \
35
+ n_knn: int = 10, max_edge_len: float = 10.0, inflation_r: float = 1.0) -> None:
36
+ super().__init__(start, goal, env, heuristic_type)
37
+ # number of edges from one sampled point
38
+ self.n_knn = n_knn
39
+ # maximum edge length
40
+ self.max_edge_len = max_edge_len
41
+ # inflation range
42
+ self.inflation_r = inflation_r
43
+
44
+ def __str__(self) -> str:
45
+ return "Voronoi-based Planner"
46
+
47
+ def plan(self):
48
+ """
49
+ Voronoi-based motion plan function.
50
+
51
+ Returns:
52
+ cost (float): path cost
53
+ path (list): planning path
54
+ expand (list): voronoi sampled nodes
55
+ """
56
+ # sampling voronoi diagram
57
+ vor = Voronoi(np.array(list(self.env.obstacles)))
58
+ vx_list = [ix for [ix, _] in vor.vertices] + [self.start.x, self.goal.x]
59
+ vy_list = [iy for [_, iy] in vor.vertices] + [self.start.y, self.goal.y]
60
+ sample_num = len(vx_list)
61
+ expand = [Node((vx_list[i], vy_list[i])) for i in range(sample_num)]
62
+
63
+ # generate road map for voronoi nodes
64
+ road_map = {}
65
+ node_tree = cKDTree(np.vstack((vx_list, vy_list)).T)
66
+
67
+ for node in expand:
68
+ edges = []
69
+ _, index_list = node_tree.query([node.x, node.y], k=sample_num)
70
+
71
+ for i in range(1, len(index_list)):
72
+ node_n = expand[index_list[i]]
73
+
74
+ if not self.isCollision(node, node_n):
75
+ edges.append(node_n)
76
+
77
+ if len(edges) >= self.n_knn:
78
+ break
79
+
80
+ road_map[node] = edges
81
+
82
+ # calculate shortest path using graph search algorithm
83
+ cost, path = self.getShortestPath(road_map)
84
+ return cost, path, expand
85
+
86
+ def run(self):
87
+ """
88
+ Running both plannig and animation.
89
+ """
90
+ cost, path, expand = self.plan()
91
+ self.plot.animation(path, str(self), cost, expand)
92
+
93
+ def getShortestPath(self, road_map: dict, dijkstra: bool = True) -> list:
94
+ """
95
+ Calculate shortest path using graph search algorithm(A*, Dijkstra, etc).
96
+
97
+ Parameters:
98
+ road_map (dict): road map for voronoi diagram, which store KNN for one voronoi node
99
+ dijkstra (bool): using Dijkstra if true, else A*
100
+
101
+ Returns:
102
+ cost (float): path cost
103
+ path (list): planning path
104
+ """
105
+ # OPEN list (priority queue) and CLOSED list (hash table)
106
+ OPEN = []
107
+ heapq.heappush(OPEN, self.start)
108
+ CLOSED = dict()
109
+
110
+ while OPEN:
111
+ node = heapq.heappop(OPEN)
112
+
113
+ # exists in CLOSED list
114
+ if node.current in CLOSED:
115
+ continue
116
+
117
+ # goal found
118
+ if node == self.goal:
119
+ CLOSED[node.current] = node
120
+ return self.extractPath(CLOSED)
121
+
122
+ for node_n in road_map[node]:
123
+ # exists in CLOSED set
124
+ if node_n.current in CLOSED:
125
+ continue
126
+
127
+ node_n.parent = node.current
128
+ node_n.g = self.dist(node_n, node)
129
+ if not dijkstra:
130
+ node_n.h = self.h(node_n, self.goal)
131
+
132
+ # goal found
133
+ if node_n == self.goal:
134
+ heapq.heappush(OPEN, node_n)
135
+ break
136
+
137
+ # update OPEN set
138
+ heapq.heappush(OPEN, node_n)
139
+
140
+ CLOSED[node.current] = node
141
+ return [], []
142
+
143
+ def extractPath(self, closed_list: dict):
144
+ """
145
+ Extract the path based on the CLOSED list.
146
+
147
+ Parameters:
148
+ closed_list (dict): CLOSED list
149
+
150
+ Returns:
151
+ cost (float): the cost of planning path
152
+ path (list): the planning path
153
+ """
154
+ cost = 0
155
+ node = closed_list[self.goal.current]
156
+ path = [node.current]
157
+ while node != self.start:
158
+ node_parent = closed_list[node.parent]
159
+ cost += self.dist(node, node_parent)
160
+ node = node_parent
161
+ path.append(node.current)
162
+ return cost, path
163
+
164
+ def isCollision(self, node1: Node, node2: Node) -> bool:
165
+ """
166
+ Judge collision when moving from node1 to node2.
167
+
168
+ Parameters:
169
+ node1 (Node): start node
170
+ node2 (Node): end node
171
+
172
+ Returns:
173
+ collision (bool): True if collision exists else False
174
+ """
175
+ if node1.current in self.obstacles or node2.current in self.obstacles:
176
+ return True
177
+
178
+ yaw = self.angle(node1, node2)
179
+ dist = self.dist(node1, node2)
180
+
181
+ if dist >= self.max_edge_len:
182
+ return True
183
+
184
+ d_dist = self.inflation_r
185
+ n_step = round(dist / d_dist)
186
+
187
+ x, y = node1.current
188
+ for _ in range(n_step):
189
+ dist_to_obs, _ = self.env.obstacles_tree.query([x, y])
190
+ if dist_to_obs <= self.inflation_r:
191
+ return True
192
+ x += d_dist * math.cos(yaw)
193
+ y += d_dist * math.sin(yaw)
194
+
195
+ # goal point check
196
+ dist_to_obs, _ = self.env.obstacles_tree.query(node2.current)
197
+ if dist_to_obs <= self.inflation_r:
198
+ return True
199
+
200
+ return False
@@ -0,0 +1,6 @@
1
+ from .rrt import RRT
2
+ from .rrt_connect import RRTConnect
3
+ from .rrt_star import RRTStar
4
+ from .informed_rrt import InformedRRT
5
+
6
+ __all__ = ['RRT', 'RRTConnect', 'RRTStar', 'InformedRRT']
@@ -0,0 +1,152 @@
1
+ """
2
+ @file: informed_rrt.py
3
+ @breif: Informed RRT* motion planning
4
+ @author: Yang Haodong, Wu Maojia
5
+ @update: 2024.6.23
6
+ """
7
+ import numpy as np
8
+ from functools import partial
9
+ import matplotlib.pyplot as plt
10
+
11
+ from .rrt_star import RRTStar
12
+ from python_motion_planning.utils import Env, Node
13
+
14
+
15
+ class Ellipse:
16
+ """
17
+ Ellipse sampling.
18
+ """
19
+ @staticmethod
20
+ def transform(a: float, c: float, p1: tuple, p2: tuple) -> np.ndarray:
21
+ # center
22
+ center_x = (p1[0] + p2[0]) / 2
23
+ center_y = (p1[1] + p2[1]) / 2
24
+
25
+ # rotation
26
+ theta = - np.arctan2(p2[1] - p1[1], p2[0] - p1[0])
27
+
28
+ # transform
29
+ b = np.sqrt(a ** 2 - c ** 2)
30
+ T = np.array([[ a * np.cos(theta), b * np.sin(theta), center_x],
31
+ [-a * np.sin(theta), b * np.cos(theta), center_y],
32
+ [ 0, 0, 1]])
33
+ return T
34
+
35
+
36
+ class InformedRRT(RRTStar):
37
+ """
38
+ Class for Informed RRT* motion planning.
39
+
40
+ Parameters:
41
+ start (tuple): start point coordinate
42
+ goal (tuple): goal point coordinate
43
+ env (Env): environment
44
+ max_dist (float): Maximum expansion distance one step
45
+ sample_num (int): Maximum number of sample points
46
+ r (float): optimization radius
47
+ goal_sample_rate (float): heuristic sample
48
+
49
+ Examples:
50
+ >>> import python_motion_planning as pmp
51
+ >>> planner = pmp.InformedRRT((5, 5), (45, 25), pmp.Map(51, 31))
52
+ >>> cost, path, expand = planner.plan() # planning results only
53
+ >>> planner.plot.animation(path, str(planner), cost, expand) # animation
54
+ >>> planner.run() # run both planning and animation
55
+
56
+ References:
57
+ [1] Optimal Sampling-based Path Planning Focused via Direct Sampling of an Admissible Ellipsoidal heuristic
58
+ """
59
+ def __init__(self, start: tuple, goal: tuple, env: Env, max_dist: float = 0.5,
60
+ sample_num: int = 1500, r: float = 12.0, goal_sample_rate: float = 0.05) -> None:
61
+ super().__init__(start, goal, env, max_dist, sample_num, goal_sample_rate)
62
+ # optimization radius
63
+ self.r = r
64
+ # best planning cost
65
+ self.c_best = float("inf")
66
+ # distance between start and goal
67
+ self.c_min = self.dist(self.start, self.goal)
68
+ # ellipse sampling
69
+ self.transform = partial(Ellipse.transform, c=self.c_min / 2, p1=start, p2=goal)
70
+
71
+ def __str__(self) -> str:
72
+ return "Informed RRT*"
73
+
74
+ def plan(self) -> tuple:
75
+ """
76
+ Informed-RRT* motion plan function.
77
+
78
+ Returns:
79
+ cost (float): path cost
80
+ path (list): planning path
81
+ expand (list): expanded (sampled) nodes list
82
+ """
83
+ # Sampled list
84
+ sample_list = {self.start.current: self.start}
85
+
86
+ best_cost, best_path = float("inf"), None
87
+
88
+ # main loop
89
+ for i in range(self.sample_num):
90
+ # generate a random node in the map
91
+ node_rand = self.generateRandomNode()
92
+
93
+ # visited
94
+ if node_rand.current in sample_list:
95
+ continue
96
+
97
+ # generate new node
98
+ node_new = self.getNearest(list(sample_list.values()), node_rand)
99
+ if node_new:
100
+ sample_list[node_new.current] = node_new
101
+ dist = self.dist(node_new, self.goal)
102
+ # goal found
103
+ if dist <= self.max_dist and not self.isCollision(node_new, self.goal):
104
+ self.goal.parent = node_new.current
105
+ self.goal.g = node_new.g + self.dist(self.goal, node_new)
106
+ sample_list[self.goal.current] = self.goal
107
+ cost, path = self.extractPath(sample_list)
108
+ if path and cost < best_cost:
109
+ best_cost, best_path = cost, path
110
+ self.c_best = best_cost
111
+
112
+ return best_cost, best_path, list(sample_list.values())
113
+
114
+ def run(self) -> None:
115
+ """
116
+ Running both plannig and animation.
117
+ """
118
+ cost, path, expand = self.plan()
119
+ t = np.arange(0, 2 * np.pi + 0.1, 0.1)
120
+ x = [np.cos(it) for it in t]
121
+ y = [np.sin(it) for it in t]
122
+ z = [1 for _ in t]
123
+ fx = self.transform(self.c_best / 2) @ np.array([x, y, z])
124
+ fx[0, :] += x
125
+ fx[1, :] += y
126
+ self.plot.animation(path, str(self), cost, expand, ellipse=fx)
127
+
128
+ def generateRandomNode(self) -> Node:
129
+ """
130
+ Generate a random node to extend exploring tree.
131
+
132
+ Returns:
133
+ node (Node): a random node based on sampling
134
+ """
135
+ # ellipse sample
136
+ if self.c_best < float("inf"):
137
+ while True:
138
+ # unit ball sample
139
+ p = np.array([.0, .0, 1.])
140
+ while True:
141
+ x, y = np.random.uniform(-1, 1), np.random.uniform(-1, 1)
142
+ if x ** 2 + y ** 2 < 1:
143
+ p[0], p[1] = x, y
144
+ break
145
+ # transform to ellipse
146
+ p_star = self.transform(self.c_best / 2) @ p.T
147
+ if self.delta <= p_star[0] <= self.env.x_range - self.delta and \
148
+ self.delta <= p_star[1] <= self.env.y_range - self.delta:
149
+ return Node((p_star[0], p_star[1]), None, 0, 0)
150
+ # random sample
151
+ else:
152
+ return super().generateRandomNode()