python-motion-planning 0.1__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.
- python_motion_planning/__init__.py +4 -0
- python_motion_planning/curve_generation/__init__.py +9 -0
- python_motion_planning/curve_generation/bezier_curve.py +131 -0
- python_motion_planning/curve_generation/bspline_curve.py +271 -0
- python_motion_planning/curve_generation/cubic_spline.py +128 -0
- python_motion_planning/curve_generation/curve.py +64 -0
- python_motion_planning/curve_generation/dubins_curve.py +348 -0
- python_motion_planning/curve_generation/fem_pos_smooth.py +114 -0
- python_motion_planning/curve_generation/polynomial_curve.py +226 -0
- python_motion_planning/curve_generation/reeds_shepp.py +736 -0
- python_motion_planning/global_planner/__init__.py +3 -0
- python_motion_planning/global_planner/evolutionary_search/__init__.py +4 -0
- python_motion_planning/global_planner/evolutionary_search/aco.py +186 -0
- python_motion_planning/global_planner/evolutionary_search/evolutionary_search.py +87 -0
- python_motion_planning/global_planner/evolutionary_search/pso.py +356 -0
- python_motion_planning/global_planner/graph_search/__init__.py +28 -0
- python_motion_planning/global_planner/graph_search/a_star.py +124 -0
- python_motion_planning/global_planner/graph_search/d_star.py +291 -0
- python_motion_planning/global_planner/graph_search/d_star_lite.py +188 -0
- python_motion_planning/global_planner/graph_search/dijkstra.py +77 -0
- python_motion_planning/global_planner/graph_search/gbfs.py +78 -0
- python_motion_planning/global_planner/graph_search/graph_search.py +87 -0
- python_motion_planning/global_planner/graph_search/jps.py +165 -0
- python_motion_planning/global_planner/graph_search/lazy_theta_star.py +114 -0
- python_motion_planning/global_planner/graph_search/lpa_star.py +230 -0
- python_motion_planning/global_planner/graph_search/s_theta_star.py +133 -0
- python_motion_planning/global_planner/graph_search/theta_star.py +171 -0
- python_motion_planning/global_planner/graph_search/voronoi.py +200 -0
- python_motion_planning/global_planner/sample_search/__init__.py +6 -0
- python_motion_planning/global_planner/sample_search/informed_rrt.py +152 -0
- python_motion_planning/global_planner/sample_search/rrt.py +151 -0
- python_motion_planning/global_planner/sample_search/rrt_connect.py +147 -0
- python_motion_planning/global_planner/sample_search/rrt_star.py +77 -0
- python_motion_planning/global_planner/sample_search/sample_search.py +135 -0
- python_motion_planning/local_planner/__init__.py +19 -0
- python_motion_planning/local_planner/apf.py +144 -0
- python_motion_planning/local_planner/ddpg.py +630 -0
- python_motion_planning/local_planner/dqn.py +687 -0
- python_motion_planning/local_planner/dwa.py +212 -0
- python_motion_planning/local_planner/local_planner.py +262 -0
- python_motion_planning/local_planner/lqr.py +146 -0
- python_motion_planning/local_planner/mpc.py +214 -0
- python_motion_planning/local_planner/pid.py +158 -0
- python_motion_planning/local_planner/rpp.py +147 -0
- python_motion_planning/utils/__init__.py +19 -0
- python_motion_planning/utils/agent/__init__.py +0 -0
- python_motion_planning/utils/agent/agent.py +135 -0
- python_motion_planning/utils/environment/__init__.py +0 -0
- python_motion_planning/utils/environment/env.py +134 -0
- python_motion_planning/utils/environment/node.py +85 -0
- python_motion_planning/utils/environment/point2d.py +96 -0
- python_motion_planning/utils/environment/pose2d.py +91 -0
- python_motion_planning/utils/helper/__init__.py +3 -0
- python_motion_planning/utils/helper/math_helper.py +65 -0
- python_motion_planning/utils/planner/__init__.py +0 -0
- python_motion_planning/utils/planner/control_factory.py +31 -0
- python_motion_planning/utils/planner/curve_factory.py +29 -0
- python_motion_planning/utils/planner/planner.py +40 -0
- python_motion_planning/utils/planner/search_factory.py +51 -0
- python_motion_planning/utils/plot/__init__.py +0 -0
- python_motion_planning/utils/plot/plot.py +274 -0
- python_motion_planning-0.1.dist-info/LICENSE +674 -0
- python_motion_planning-0.1.dist-info/METADATA +873 -0
- python_motion_planning-0.1.dist-info/RECORD +66 -0
- python_motion_planning-0.1.dist-info/WHEEL +5 -0
- python_motion_planning-0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: a_star.py
|
|
3
|
+
@breif: A* motion planning
|
|
4
|
+
@author: Yang Haodong, Wu Maojia
|
|
5
|
+
@update: 2024.6.23
|
|
6
|
+
"""
|
|
7
|
+
import heapq
|
|
8
|
+
|
|
9
|
+
from .graph_search import GraphSearcher
|
|
10
|
+
from python_motion_planning.utils import Env, Node
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AStar(GraphSearcher):
|
|
14
|
+
"""
|
|
15
|
+
Class for A* motion planning.
|
|
16
|
+
|
|
17
|
+
Parameters:
|
|
18
|
+
start (tuple): start point coordinate
|
|
19
|
+
goal (tuple): goal point coordinate
|
|
20
|
+
env (Env): environment
|
|
21
|
+
heuristic_type (str): heuristic function type
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
>>> import python_motion_planning as pmp
|
|
25
|
+
>>> planner = pmp.AStar((5, 5), (45, 25), pmp.Grid(51, 31))
|
|
26
|
+
>>> cost, path, expand = planner.plan() # planning results only
|
|
27
|
+
>>> planner.plot.animation(path, str(planner), cost, expand) # animation
|
|
28
|
+
>>> planner.run() # run both planning and animation
|
|
29
|
+
|
|
30
|
+
References:
|
|
31
|
+
[1] A Formal Basis for the heuristic Determination of Minimum Cost Paths
|
|
32
|
+
"""
|
|
33
|
+
def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean") -> None:
|
|
34
|
+
super().__init__(start, goal, env, heuristic_type)
|
|
35
|
+
|
|
36
|
+
def __str__(self) -> str:
|
|
37
|
+
return "A*"
|
|
38
|
+
|
|
39
|
+
def plan(self) -> tuple:
|
|
40
|
+
"""
|
|
41
|
+
A* motion plan function.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
cost (float): path cost
|
|
45
|
+
path (list): planning path
|
|
46
|
+
expand (list): all nodes that planner has searched
|
|
47
|
+
"""
|
|
48
|
+
# OPEN list (priority queue) and CLOSED list (hash table)
|
|
49
|
+
OPEN = []
|
|
50
|
+
heapq.heappush(OPEN, self.start)
|
|
51
|
+
CLOSED = dict()
|
|
52
|
+
|
|
53
|
+
while OPEN:
|
|
54
|
+
node = heapq.heappop(OPEN)
|
|
55
|
+
|
|
56
|
+
# exists in CLOSED list
|
|
57
|
+
if node.current in CLOSED:
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
# goal found
|
|
61
|
+
if node == self.goal:
|
|
62
|
+
CLOSED[node.current] = node
|
|
63
|
+
cost, path = self.extractPath(CLOSED)
|
|
64
|
+
return cost, path, list(CLOSED.values())
|
|
65
|
+
|
|
66
|
+
for node_n in self.getNeighbor(node):
|
|
67
|
+
# exists in CLOSED list
|
|
68
|
+
if node_n.current in CLOSED:
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
node_n.parent = node.current
|
|
72
|
+
node_n.h = self.h(node_n, self.goal)
|
|
73
|
+
|
|
74
|
+
# goal found
|
|
75
|
+
if node_n == self.goal:
|
|
76
|
+
heapq.heappush(OPEN, node_n)
|
|
77
|
+
break
|
|
78
|
+
|
|
79
|
+
# update OPEN list
|
|
80
|
+
heapq.heappush(OPEN, node_n)
|
|
81
|
+
|
|
82
|
+
CLOSED[node.current] = node
|
|
83
|
+
return [], [], []
|
|
84
|
+
|
|
85
|
+
def getNeighbor(self, node: Node) -> list:
|
|
86
|
+
"""
|
|
87
|
+
Find neighbors of node.
|
|
88
|
+
|
|
89
|
+
Parameters:
|
|
90
|
+
node (Node): current node
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
neighbors (list): neighbors of current node
|
|
94
|
+
"""
|
|
95
|
+
return [node + motion for motion in self.motions
|
|
96
|
+
if not self.isCollision(node, node + motion)]
|
|
97
|
+
|
|
98
|
+
def extractPath(self, closed_list: dict) -> tuple:
|
|
99
|
+
"""
|
|
100
|
+
Extract the path based on the CLOSED list.
|
|
101
|
+
|
|
102
|
+
Parameters:
|
|
103
|
+
closed_list (dict): CLOSED list
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
cost (float): the cost of planned path
|
|
107
|
+
path (list): the planning path
|
|
108
|
+
"""
|
|
109
|
+
cost = 0
|
|
110
|
+
node = closed_list[self.goal.current]
|
|
111
|
+
path = [node.current]
|
|
112
|
+
while node != self.start:
|
|
113
|
+
node_parent = closed_list[node.parent]
|
|
114
|
+
cost += self.dist(node, node_parent)
|
|
115
|
+
node = node_parent
|
|
116
|
+
path.append(node.current)
|
|
117
|
+
return cost, path
|
|
118
|
+
|
|
119
|
+
def run(self):
|
|
120
|
+
"""
|
|
121
|
+
Running both planning and animation.
|
|
122
|
+
"""
|
|
123
|
+
cost, path, expand = self.plan()
|
|
124
|
+
self.plot.animation(path, str(self), cost, expand)
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: d_star.py
|
|
3
|
+
@breif: Dynamic A* motion planning
|
|
4
|
+
@author: Yang Haodong, Wu Maojia
|
|
5
|
+
@update: 2024.6.23
|
|
6
|
+
"""
|
|
7
|
+
from .graph_search import GraphSearcher
|
|
8
|
+
from python_motion_planning.utils import Env, Node
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DNode(Node):
|
|
12
|
+
"""
|
|
13
|
+
Class for D* nodes.
|
|
14
|
+
|
|
15
|
+
Parameters:
|
|
16
|
+
current (tuple): current coordinate
|
|
17
|
+
parent (tuple): coordinate of parent node
|
|
18
|
+
t (str): state of node, including `NEW` `OPEN` and `CLOSED`
|
|
19
|
+
h (float): cost from goal to current node
|
|
20
|
+
k (float): minimum cost from goal to current node in history
|
|
21
|
+
"""
|
|
22
|
+
def __init__(self, current: tuple, parent: tuple, t: str, h: float, k: float) -> None:
|
|
23
|
+
self.current = current
|
|
24
|
+
self.parent = parent
|
|
25
|
+
self.t = t
|
|
26
|
+
self.h = h
|
|
27
|
+
self.k = k
|
|
28
|
+
|
|
29
|
+
def __add__(self, node):
|
|
30
|
+
return DNode((self.x + node.x, self.y + node.y),
|
|
31
|
+
self.parent, self.t, self.h + node.h, self.k)
|
|
32
|
+
|
|
33
|
+
def __str__(self) -> str:
|
|
34
|
+
return "----------\ncurrent:{}\nparent:{}\nt:{}\nh:{}\nk:{}\n----------" \
|
|
35
|
+
.format(self.current, self.parent, self.t, self.h, self.k)
|
|
36
|
+
|
|
37
|
+
class DStar(GraphSearcher):
|
|
38
|
+
"""
|
|
39
|
+
Class for D* motion planning.
|
|
40
|
+
|
|
41
|
+
Parameters:
|
|
42
|
+
start (tuple): start point coordinate
|
|
43
|
+
goal (tuple): goal point coordinate
|
|
44
|
+
env (Env): environment
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
>>> import python_motion_planning as pmp
|
|
48
|
+
>>> planner = pmp.DStar((5, 5), (45, 25), pmp.Grid(51, 31))
|
|
49
|
+
>>> cost, path, _ = planner.plan() # planning results only
|
|
50
|
+
>>> planner.plot.animation(path, str(planner), cost) # animation
|
|
51
|
+
>>> planner.run() # run both planning and animation
|
|
52
|
+
|
|
53
|
+
References:
|
|
54
|
+
[1]Optimal and Efficient Path Planning for Partially-Known Environments
|
|
55
|
+
"""
|
|
56
|
+
def __init__(self, start: tuple, goal: tuple, env: Env) -> None:
|
|
57
|
+
super().__init__(start, goal, env, None)
|
|
58
|
+
self.start = DNode(start, None, 'NEW', float('inf'), float("inf"))
|
|
59
|
+
self.goal = DNode(goal, None, 'NEW', 0, float('inf'))
|
|
60
|
+
# allowed motions
|
|
61
|
+
self.motions = [DNode(motion.current, None, None, motion.g, 0) for motion in self.env.motions]
|
|
62
|
+
# OPEN list and EXPAND list
|
|
63
|
+
self.OPEN = []
|
|
64
|
+
self.EXPAND = []
|
|
65
|
+
# record history infomation of map grids
|
|
66
|
+
self.map = {s: DNode(s, None, 'NEW', float("inf"), float("inf")) for s in self.env.grid_map}
|
|
67
|
+
self.map[self.goal.current] = self.goal
|
|
68
|
+
self.map[self.start.current] = self.start
|
|
69
|
+
# intialize OPEN list
|
|
70
|
+
self.insert(self.goal, 0)
|
|
71
|
+
|
|
72
|
+
def __str__(self) -> str:
|
|
73
|
+
return "Dynamic A*(D*)"
|
|
74
|
+
|
|
75
|
+
def plan(self) -> tuple:
|
|
76
|
+
"""
|
|
77
|
+
D* static motion planning function.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
cost (float): path cost
|
|
81
|
+
path (list): planning path
|
|
82
|
+
_ (None): None
|
|
83
|
+
"""
|
|
84
|
+
while True:
|
|
85
|
+
self.processState()
|
|
86
|
+
if self.start.t == 'CLOSED':
|
|
87
|
+
break
|
|
88
|
+
cost, path = self.extractPath(self.map)
|
|
89
|
+
return cost, path, None
|
|
90
|
+
|
|
91
|
+
def run(self) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Running both plannig and animation.
|
|
94
|
+
"""
|
|
95
|
+
# static planning
|
|
96
|
+
cost, path, _ = self.plan()
|
|
97
|
+
|
|
98
|
+
# animation
|
|
99
|
+
self.plot.connect('button_press_event', self.OnPress)
|
|
100
|
+
self.plot.animation(path, str(self), cost=cost)
|
|
101
|
+
|
|
102
|
+
def OnPress(self, event) -> None:
|
|
103
|
+
"""
|
|
104
|
+
Mouse button callback function.
|
|
105
|
+
|
|
106
|
+
Parameters:
|
|
107
|
+
event (MouseEvent): mouse event
|
|
108
|
+
"""
|
|
109
|
+
x, y = int(event.xdata), int(event.ydata)
|
|
110
|
+
if x < 0 or x > self.env.x_range - 1 or y < 0 or y > self.env.y_range - 1:
|
|
111
|
+
print("Please choose right area!")
|
|
112
|
+
else:
|
|
113
|
+
if (x, y) not in self.obstacles:
|
|
114
|
+
print("Add obstacle at: ({}, {})".format(x, y))
|
|
115
|
+
# update obstacles
|
|
116
|
+
self.obstacles.add((x, y))
|
|
117
|
+
self.env.update(self.obstacles)
|
|
118
|
+
|
|
119
|
+
# move from start to goal, replan locally when meeting collisions
|
|
120
|
+
node = self.start
|
|
121
|
+
self.EXPAND, path, cost = [], [], 0
|
|
122
|
+
while node != self.goal:
|
|
123
|
+
node_parent = self.map[node.parent]
|
|
124
|
+
if self.isCollision(node, node_parent):
|
|
125
|
+
self.modify(node, node_parent)
|
|
126
|
+
continue
|
|
127
|
+
path.append(node.current)
|
|
128
|
+
cost += self.cost(node, node_parent)
|
|
129
|
+
node = node_parent
|
|
130
|
+
|
|
131
|
+
self.plot.clean()
|
|
132
|
+
self.plot.animation(path, str(self), cost, self.EXPAND)
|
|
133
|
+
|
|
134
|
+
self.plot.update()
|
|
135
|
+
|
|
136
|
+
def extractPath(self, closed_list: dict) -> tuple:
|
|
137
|
+
"""
|
|
138
|
+
Extract the path based on the CLOSED list.
|
|
139
|
+
|
|
140
|
+
Parameters:
|
|
141
|
+
closed_list (dict): CLOSED list
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
cost (float): the cost of planning path
|
|
145
|
+
path (list): the planning path
|
|
146
|
+
"""
|
|
147
|
+
cost = 0
|
|
148
|
+
node = self.start
|
|
149
|
+
path = [node.current]
|
|
150
|
+
while node != self.goal:
|
|
151
|
+
node_parent = closed_list[node.parent]
|
|
152
|
+
cost += self.cost(node, node_parent)
|
|
153
|
+
node = node_parent
|
|
154
|
+
path.append(node.current)
|
|
155
|
+
|
|
156
|
+
return cost, path
|
|
157
|
+
|
|
158
|
+
def processState(self) -> float:
|
|
159
|
+
"""
|
|
160
|
+
Broadcast dynamic obstacle information.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
min_k (float): minimum k value of map
|
|
164
|
+
"""
|
|
165
|
+
# get node in OPEN list with min k value
|
|
166
|
+
node = self.min_state
|
|
167
|
+
self.EXPAND.append(node)
|
|
168
|
+
|
|
169
|
+
if node is None:
|
|
170
|
+
return -1
|
|
171
|
+
|
|
172
|
+
# record the min k value of this iteration
|
|
173
|
+
k_old = self.min_k
|
|
174
|
+
# move node from OPEN list to CLOSED list
|
|
175
|
+
self.delete(node)
|
|
176
|
+
|
|
177
|
+
# k_min < h[x] --> x: RAISE state (try to reduce k value by neighbor)
|
|
178
|
+
if k_old < node.h:
|
|
179
|
+
for node_n in self.getNeighbor(node):
|
|
180
|
+
if node_n.h <= k_old and node.h > node_n.h + self.cost(node, node_n):
|
|
181
|
+
# update h_value and choose parent
|
|
182
|
+
node.parent = node_n.current
|
|
183
|
+
node.h = node_n.h + self.cost(node, node_n)
|
|
184
|
+
|
|
185
|
+
# k_min >= h[x] -- > x: LOWER state (cost reductions)
|
|
186
|
+
if k_old == node.h:
|
|
187
|
+
for node_n in self.getNeighbor(node):
|
|
188
|
+
if node_n.t == 'NEW' or \
|
|
189
|
+
(node_n.parent == node.current and node_n.h != node.h + self.cost(node, node_n)) or \
|
|
190
|
+
(node_n.parent != node.current and node_n.h > node.h + self.cost(node, node_n)):
|
|
191
|
+
# Condition:
|
|
192
|
+
# 1) t[node_n] == 'NEW': not visited
|
|
193
|
+
# 2) node_n's parent: cost reduction
|
|
194
|
+
# 3) node_n find a better parent
|
|
195
|
+
node_n.parent = node.current
|
|
196
|
+
self.insert(node_n, node.h + self.cost(node, node_n))
|
|
197
|
+
else:
|
|
198
|
+
for node_n in self.getNeighbor(node):
|
|
199
|
+
if node_n.t == 'NEW' or \
|
|
200
|
+
(node_n.parent == node.current and node_n.h != node.h + self.cost(node, node_n)):
|
|
201
|
+
# Condition:
|
|
202
|
+
# 1) t[node_n] == 'NEW': not visited
|
|
203
|
+
# 2) node_n's parent: cost reduction
|
|
204
|
+
node_n.parent = node.current
|
|
205
|
+
self.insert(node_n, node.h + self.cost(node, node_n))
|
|
206
|
+
else:
|
|
207
|
+
if node_n.parent != node.current and \
|
|
208
|
+
node_n.h > node.h + self.cost(node, node_n):
|
|
209
|
+
# Condition: LOWER happened in OPEN list (s), s should be explored again
|
|
210
|
+
self.insert(node, node.h)
|
|
211
|
+
else:
|
|
212
|
+
if node_n.parent != node.current and \
|
|
213
|
+
node.h > node_n.h + self.cost(node, node_n) and \
|
|
214
|
+
node_n.t == 'CLOSED' and \
|
|
215
|
+
node_n.h > k_old:
|
|
216
|
+
# Condition: LOWER happened in CLOSED list (s_n), s_n should be explored again
|
|
217
|
+
self.insert(node_n, node_n.h)
|
|
218
|
+
return self.min_k
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def min_state(self) -> DNode:
|
|
222
|
+
"""
|
|
223
|
+
Choose the node with the minimum k value in OPEN list.
|
|
224
|
+
"""
|
|
225
|
+
if not self.OPEN:
|
|
226
|
+
return None
|
|
227
|
+
return min(self.OPEN, key=lambda node: node.k)
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def min_k(self) -> float:
|
|
231
|
+
"""
|
|
232
|
+
Choose the minimum k value for nodes in OPEN list.
|
|
233
|
+
"""
|
|
234
|
+
return self.min_state.k
|
|
235
|
+
|
|
236
|
+
def insert(self, node: DNode, h_new: float) -> None:
|
|
237
|
+
"""
|
|
238
|
+
Insert node into OPEN list.
|
|
239
|
+
|
|
240
|
+
Parameters:
|
|
241
|
+
node (DNode): the node to insert
|
|
242
|
+
h_new (float): new or better cost to come value
|
|
243
|
+
"""
|
|
244
|
+
if node.t == 'NEW': node.k = h_new
|
|
245
|
+
elif node.t == 'OPEN': node.k = min(node.k, h_new)
|
|
246
|
+
elif node.t == 'CLOSED': node.k = min(node.h, h_new)
|
|
247
|
+
node.h, node.t = h_new, 'OPEN'
|
|
248
|
+
self.OPEN.append(node)
|
|
249
|
+
|
|
250
|
+
def delete(self, node: DNode) -> None:
|
|
251
|
+
"""
|
|
252
|
+
Delete node from OPEN list.
|
|
253
|
+
|
|
254
|
+
Parameters:
|
|
255
|
+
node (DNode): the node to delete
|
|
256
|
+
"""
|
|
257
|
+
if node.t == 'OPEN':
|
|
258
|
+
node.t = 'CLOSED'
|
|
259
|
+
self.OPEN.remove(node)
|
|
260
|
+
|
|
261
|
+
def modify(self, node: DNode, node_parent: DNode) -> None:
|
|
262
|
+
"""
|
|
263
|
+
Start processing from node.
|
|
264
|
+
|
|
265
|
+
Parameters:
|
|
266
|
+
node (DNode): the node to modify
|
|
267
|
+
node_parent (DNode): the parent node of `node`
|
|
268
|
+
"""
|
|
269
|
+
if node.t == 'CLOSED':
|
|
270
|
+
self.insert(node, node_parent.h + self.cost(node, node_parent))
|
|
271
|
+
while True:
|
|
272
|
+
k_min = self.processState()
|
|
273
|
+
if k_min >= node.h:
|
|
274
|
+
break
|
|
275
|
+
|
|
276
|
+
def getNeighbor(self, node: DNode) -> list:
|
|
277
|
+
"""
|
|
278
|
+
Find neighbors of node.
|
|
279
|
+
|
|
280
|
+
Parameters:
|
|
281
|
+
node (DNode): current node
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
neighbors (list): neighbors of current node
|
|
285
|
+
"""
|
|
286
|
+
neighbors = []
|
|
287
|
+
for motion in self.motions:
|
|
288
|
+
n = self.map[(node + motion).current]
|
|
289
|
+
if not self.isCollision(node, n):
|
|
290
|
+
neighbors.append(n)
|
|
291
|
+
return neighbors
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: d_star_lite.py
|
|
3
|
+
@breif: D* Lite motion planning
|
|
4
|
+
@author: Yang Haodong, Wu Maojia
|
|
5
|
+
@update: 2024.6.23
|
|
6
|
+
"""
|
|
7
|
+
import heapq
|
|
8
|
+
|
|
9
|
+
from .graph_search import GraphSearcher
|
|
10
|
+
from .lpa_star import LPAStar, LNode
|
|
11
|
+
from python_motion_planning.utils import Env
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DStarLite(LPAStar):
|
|
15
|
+
"""
|
|
16
|
+
Class for D* Lite 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.DStarLite((5, 5), (45, 25), pmp.Grid(51, 31))
|
|
27
|
+
>>> cost, path, _ = planner.plan() # planning results only
|
|
28
|
+
>>> planner.plot.animation(path, str(planner), cost) # animation
|
|
29
|
+
>>> planner.run() # run both planning and animation
|
|
30
|
+
|
|
31
|
+
References:
|
|
32
|
+
[1] D* Lite
|
|
33
|
+
"""
|
|
34
|
+
def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean") -> None:
|
|
35
|
+
GraphSearcher.__init__(self, start, goal, env, heuristic_type)
|
|
36
|
+
# start and goal
|
|
37
|
+
self.start = LNode(start, float('inf'), float('inf'), None)
|
|
38
|
+
self.goal = LNode(goal, float('inf'), 0.0, None)
|
|
39
|
+
# correction
|
|
40
|
+
self.km = 0
|
|
41
|
+
# OPEN set and expand zone
|
|
42
|
+
self.U, self.EXPAND = [], []
|
|
43
|
+
|
|
44
|
+
# intialize global information, record history infomation of map grids
|
|
45
|
+
self.map = {s: LNode(s, float('inf'), float('inf'), None) for s in self.env.grid_map}
|
|
46
|
+
self.map[self.goal.current] = self.goal
|
|
47
|
+
self.map[self.start.current] = self.start
|
|
48
|
+
# OPEN set with priority
|
|
49
|
+
self.goal.key = self.calculateKey(self.goal)
|
|
50
|
+
heapq.heappush(self.U, self.goal)
|
|
51
|
+
|
|
52
|
+
def __str__(self) -> str:
|
|
53
|
+
return "D* Lite"
|
|
54
|
+
|
|
55
|
+
def OnPress(self, event) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Mouse button callback function.
|
|
58
|
+
|
|
59
|
+
Parameters:
|
|
60
|
+
event (MouseEvent): mouse event
|
|
61
|
+
"""
|
|
62
|
+
x, y = int(event.xdata), int(event.ydata)
|
|
63
|
+
if x < 0 or x > self.env.x_range - 1 or y < 0 or y > self.env.y_range - 1:
|
|
64
|
+
print("Please choose right area!")
|
|
65
|
+
else:
|
|
66
|
+
print("Change position: x = {}, y = {}".format(x, y))
|
|
67
|
+
|
|
68
|
+
cur_start, new_start = self.start, self.start
|
|
69
|
+
update_start = True
|
|
70
|
+
cost, count = 0, 0
|
|
71
|
+
path = [self.start.current]
|
|
72
|
+
self.EXPAND = []
|
|
73
|
+
|
|
74
|
+
while cur_start != self.goal:
|
|
75
|
+
neighbors = [node_n for node_n in self.getNeighbor(cur_start)
|
|
76
|
+
if not self.isCollision(cur_start, node_n)]
|
|
77
|
+
next_node = min(neighbors, key=lambda n: n.g)
|
|
78
|
+
path.append(next_node.current)
|
|
79
|
+
cost += self.cost(cur_start, next_node)
|
|
80
|
+
count += 1
|
|
81
|
+
cur_start = next_node
|
|
82
|
+
|
|
83
|
+
if update_start:
|
|
84
|
+
update_start = False
|
|
85
|
+
self.km = self.h(cur_start, new_start)
|
|
86
|
+
new_start = cur_start
|
|
87
|
+
|
|
88
|
+
node_change = self.map[(x, y)]
|
|
89
|
+
if (x, y) not in self.obstacles:
|
|
90
|
+
self.obstacles.add((x, y))
|
|
91
|
+
else:
|
|
92
|
+
self.obstacles.remove((x, y))
|
|
93
|
+
self.updateVertex(node_change)
|
|
94
|
+
|
|
95
|
+
self.env.update(self.obstacles)
|
|
96
|
+
for node_n in self.getNeighbor(node_change):
|
|
97
|
+
self.updateVertex(node_n)
|
|
98
|
+
|
|
99
|
+
self.computeShortestPath()
|
|
100
|
+
|
|
101
|
+
# animation
|
|
102
|
+
self.plot.clean()
|
|
103
|
+
self.plot.animation(path, str(self), cost, self.EXPAND)
|
|
104
|
+
self.plot.update()
|
|
105
|
+
|
|
106
|
+
def computeShortestPath(self) -> None:
|
|
107
|
+
"""
|
|
108
|
+
Perceived dynamic obstacle information to optimize global path.
|
|
109
|
+
"""
|
|
110
|
+
while True:
|
|
111
|
+
node = min(self.U, key=lambda node: node.key)
|
|
112
|
+
if node.key >= self.calculateKey(self.start) and \
|
|
113
|
+
self.start.rhs == self.start.g:
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
self.U.remove(node)
|
|
117
|
+
self.EXPAND.append(node)
|
|
118
|
+
|
|
119
|
+
# affected by obstacles
|
|
120
|
+
if node.key < self.calculateKey(node):
|
|
121
|
+
node.key = self.calculateKey(node)
|
|
122
|
+
heapq.heappush(self.U, node)
|
|
123
|
+
# Locally over-consistent -> Locally consistent
|
|
124
|
+
elif node.g > node.rhs:
|
|
125
|
+
node.g = node.rhs
|
|
126
|
+
for node_n in self.getNeighbor(node):
|
|
127
|
+
self.updateVertex(node_n)
|
|
128
|
+
# Locally under-consistent -> Locally over-consistent
|
|
129
|
+
else:
|
|
130
|
+
node.g = float("inf")
|
|
131
|
+
self.updateVertex(node)
|
|
132
|
+
for node_n in self.getNeighbor(node):
|
|
133
|
+
self.updateVertex(node_n)
|
|
134
|
+
|
|
135
|
+
def updateVertex(self, node: LNode) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Update the status and the current cost to node and it's neighbor.
|
|
138
|
+
|
|
139
|
+
Parameters:
|
|
140
|
+
node (LNode): the node to be updated
|
|
141
|
+
"""
|
|
142
|
+
# greed correction(reverse searching)
|
|
143
|
+
if node != self.goal:
|
|
144
|
+
node.rhs = min([node_n.g + self.cost(node_n, node)
|
|
145
|
+
for node_n in self.getNeighbor(node)])
|
|
146
|
+
|
|
147
|
+
if node in self.U:
|
|
148
|
+
self.U.remove(node)
|
|
149
|
+
|
|
150
|
+
# Locally unconsistent nodes should be added into OPEN set (set U)
|
|
151
|
+
if node.g != node.rhs:
|
|
152
|
+
node.key = self.calculateKey(node)
|
|
153
|
+
heapq.heappush(self.U, node)
|
|
154
|
+
|
|
155
|
+
def calculateKey(self, node: LNode) -> list:
|
|
156
|
+
"""
|
|
157
|
+
Calculate priority of node.
|
|
158
|
+
|
|
159
|
+
Parameters:
|
|
160
|
+
node (LNode): the node to be calculated
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
key (list): the priority of node
|
|
164
|
+
"""
|
|
165
|
+
return [min(node.g, node.rhs) + self.h(node, self.start) + self.km,
|
|
166
|
+
min(node.g, node.rhs)]
|
|
167
|
+
|
|
168
|
+
def extractPath(self) -> tuple:
|
|
169
|
+
"""
|
|
170
|
+
Extract the path based on greedy policy.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
cost (float): the cost of planning path
|
|
174
|
+
path (list): the planning path
|
|
175
|
+
"""
|
|
176
|
+
node = self.start
|
|
177
|
+
path = [node.current]
|
|
178
|
+
cost, count = 0, 0
|
|
179
|
+
while node != self.goal:
|
|
180
|
+
neighbors = [node_n for node_n in self.getNeighbor(node) if not self.isCollision(node, node_n)]
|
|
181
|
+
next_node = min(neighbors, key=lambda n: n.g)
|
|
182
|
+
path.append(next_node.current)
|
|
183
|
+
cost += self.cost(node, next_node)
|
|
184
|
+
node = next_node
|
|
185
|
+
count += 1
|
|
186
|
+
if count == 1000:
|
|
187
|
+
return cost, []
|
|
188
|
+
return cost, list(path)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: dijkstra.py
|
|
3
|
+
@breif: Dijkstra 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
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Dijkstra(AStar):
|
|
14
|
+
def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean") -> None:
|
|
15
|
+
super().__init__(start, goal, env, heuristic_type)
|
|
16
|
+
|
|
17
|
+
def __str__(self) -> str:
|
|
18
|
+
return "Dijkstra"
|
|
19
|
+
|
|
20
|
+
def plan(self) -> tuple:
|
|
21
|
+
"""
|
|
22
|
+
Class for Dijkstra motion planning.
|
|
23
|
+
|
|
24
|
+
Parameters:
|
|
25
|
+
start (tuple): start point coordinate
|
|
26
|
+
goal (tuple): goal point coordinate
|
|
27
|
+
env (Env): environment
|
|
28
|
+
heuristic_type (str): heuristic function type
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
>>> import python_motion_planning as pmp
|
|
32
|
+
>>> planner = pmp.Dijkstra((5, 5), (45, 25), pmp.Grid(51, 31))
|
|
33
|
+
>>> cost, path, expand = planner.plan() # planning results only
|
|
34
|
+
>>> planner.plot.animation(path, str(planner), cost, expand) # animation
|
|
35
|
+
>>> planner.run() # run both planning and animation
|
|
36
|
+
"""
|
|
37
|
+
# OPEN list (priority queue) and CLOSED list (hash table)
|
|
38
|
+
OPEN = []
|
|
39
|
+
heapq.heappush(OPEN, self.start)
|
|
40
|
+
CLOSED = dict()
|
|
41
|
+
|
|
42
|
+
while OPEN:
|
|
43
|
+
node = heapq.heappop(OPEN)
|
|
44
|
+
|
|
45
|
+
# exists in CLOSED list
|
|
46
|
+
if node.current in CLOSED:
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
# goal found
|
|
50
|
+
if node == self.goal:
|
|
51
|
+
CLOSED[node.current] = node
|
|
52
|
+
cost, path = self.extractPath(CLOSED)
|
|
53
|
+
return cost, path, list(CLOSED.values())
|
|
54
|
+
|
|
55
|
+
for node_n in self.getNeighbor(node):
|
|
56
|
+
|
|
57
|
+
# hit the obstacle
|
|
58
|
+
if node_n.current in self.obstacles:
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
# exists in CLOSED list
|
|
62
|
+
if node_n.current in CLOSED:
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
node_n.parent = node.current
|
|
66
|
+
node_n.h = 0
|
|
67
|
+
|
|
68
|
+
# goal found
|
|
69
|
+
if node_n == self.goal:
|
|
70
|
+
heapq.heappush(OPEN, node_n)
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
# update OPEN set
|
|
74
|
+
heapq.heappush(OPEN, node_n)
|
|
75
|
+
|
|
76
|
+
CLOSED[node.current] = node
|
|
77
|
+
return [], [], []
|