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,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: gbfs.py
|
|
3
|
+
@breif: Greedy Best First Search 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 GBFS(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 "Greedy Best First Search(GBFS)"
|
|
19
|
+
|
|
20
|
+
def plan(self) -> tuple:
|
|
21
|
+
"""
|
|
22
|
+
Class for Greedy Best First Search.
|
|
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.GBFS((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 = self.h(node_n, self.goal)
|
|
67
|
+
node_n.g = 0
|
|
68
|
+
|
|
69
|
+
# goal found
|
|
70
|
+
if node_n == self.goal:
|
|
71
|
+
heapq.heappush(OPEN, node_n)
|
|
72
|
+
break
|
|
73
|
+
|
|
74
|
+
# update OPEN set
|
|
75
|
+
heapq.heappush(OPEN, node_n)
|
|
76
|
+
|
|
77
|
+
CLOSED[node.current] = node
|
|
78
|
+
return [], [], []
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: graph_search.py
|
|
3
|
+
@breif: Base class for planner based on graph searching
|
|
4
|
+
@author: Winter
|
|
5
|
+
@update: 2023.1.13
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
from python_motion_planning.utils import Env, Node, Planner
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GraphSearcher(Planner):
|
|
12
|
+
"""
|
|
13
|
+
Base class for planner based on graph searching.
|
|
14
|
+
|
|
15
|
+
Parameters:
|
|
16
|
+
start (tuple): start point coordinate
|
|
17
|
+
goal (tuple): goal point coordinate
|
|
18
|
+
env (Env): environment
|
|
19
|
+
heuristic_type (str): heuristic function type
|
|
20
|
+
"""
|
|
21
|
+
def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str="euclidean") -> None:
|
|
22
|
+
super().__init__(start, goal, env)
|
|
23
|
+
# heuristic type
|
|
24
|
+
self.heuristic_type = heuristic_type
|
|
25
|
+
# allowed motions
|
|
26
|
+
self.motions = self.env.motions
|
|
27
|
+
# obstacles
|
|
28
|
+
self.obstacles = self.env.obstacles
|
|
29
|
+
|
|
30
|
+
def h(self, node: Node, goal: Node) -> float:
|
|
31
|
+
"""
|
|
32
|
+
Calculate heuristic.
|
|
33
|
+
|
|
34
|
+
Parameters:
|
|
35
|
+
node (Node): current node
|
|
36
|
+
goal (Node): goal node
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
h (float): heuristic function value of node
|
|
40
|
+
"""
|
|
41
|
+
if self.heuristic_type == "manhattan":
|
|
42
|
+
return abs(goal.x - node.x) + abs(goal.y - node.y)
|
|
43
|
+
elif self.heuristic_type == "euclidean":
|
|
44
|
+
return math.hypot(goal.x - node.x, goal.y - node.y)
|
|
45
|
+
|
|
46
|
+
def cost(self, node1: Node, node2: Node) -> float:
|
|
47
|
+
"""
|
|
48
|
+
Calculate cost for this motion.
|
|
49
|
+
|
|
50
|
+
Parameters:
|
|
51
|
+
node1 (Node): node 1
|
|
52
|
+
node2 (Node): node 2
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
cost (float): cost of this motion
|
|
56
|
+
"""
|
|
57
|
+
if self.isCollision(node1, node2):
|
|
58
|
+
return float("inf")
|
|
59
|
+
return self.dist(node1, node2)
|
|
60
|
+
|
|
61
|
+
def isCollision(self, node1: Node, node2: Node) -> bool:
|
|
62
|
+
"""
|
|
63
|
+
Judge collision when moving from node1 to node2.
|
|
64
|
+
|
|
65
|
+
Parameters:
|
|
66
|
+
node1 (Node): node 1
|
|
67
|
+
node2 (Node): node 2
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
collision (bool): True if collision exists else False
|
|
71
|
+
"""
|
|
72
|
+
if node1.current in self.obstacles or node2.current in self.obstacles:
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
x1, y1 = node1.x, node1.y
|
|
76
|
+
x2, y2 = node2.x, node2.y
|
|
77
|
+
|
|
78
|
+
if x1 != x2 and y1 != y2:
|
|
79
|
+
if x2 - x1 == y1 - y2:
|
|
80
|
+
s1 = (min(x1, x2), min(y1, y2))
|
|
81
|
+
s2 = (max(x1, x2), max(y1, y2))
|
|
82
|
+
else:
|
|
83
|
+
s1 = (min(x1, x2), max(y1, y2))
|
|
84
|
+
s2 = (max(x1, x2), min(y1, y2))
|
|
85
|
+
if s1 in self.obstacles or s2 in self.obstacles:
|
|
86
|
+
return True
|
|
87
|
+
return False
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: jps.py
|
|
3
|
+
@breif: Jump Point Search 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
|
+
class JPS(AStar):
|
|
13
|
+
"""
|
|
14
|
+
Class for JPS motion planning.
|
|
15
|
+
|
|
16
|
+
Parameters:
|
|
17
|
+
start (tuple): start point coordinate
|
|
18
|
+
goal (tuple): goal point coordinate
|
|
19
|
+
env (Env): environment
|
|
20
|
+
heuristic_type (str): heuristic function type
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
>>> import python_motion_planning as pmp
|
|
24
|
+
>>> planner = pmp.JPS((5, 5), (45, 25), pmp.Grid(51, 31))
|
|
25
|
+
>>> cost, path, expand = planner.plan() # planning results only
|
|
26
|
+
>>> planner.plot.animation(path, str(planner), cost, expand) # animation
|
|
27
|
+
>>> planner.run() # run both planning and animation
|
|
28
|
+
|
|
29
|
+
References:
|
|
30
|
+
[1] Online Graph Pruning for Pathfinding On Grid Maps
|
|
31
|
+
"""
|
|
32
|
+
def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean") -> None:
|
|
33
|
+
super().__init__(start, goal, env, heuristic_type)
|
|
34
|
+
|
|
35
|
+
def __str__(self) -> str:
|
|
36
|
+
return "Jump Point Search(JPS)"
|
|
37
|
+
|
|
38
|
+
def plan(self) -> tuple:
|
|
39
|
+
"""
|
|
40
|
+
JPS motion plan function.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
cost (float): path cost
|
|
44
|
+
path (list): planning path
|
|
45
|
+
expand (list): all nodes that planner has searched
|
|
46
|
+
"""
|
|
47
|
+
# OPEN list (priority queue) and CLOSED list (hash table)
|
|
48
|
+
OPEN = []
|
|
49
|
+
heapq.heappush(OPEN, self.start)
|
|
50
|
+
CLOSED = dict()
|
|
51
|
+
|
|
52
|
+
while OPEN:
|
|
53
|
+
node = heapq.heappop(OPEN)
|
|
54
|
+
|
|
55
|
+
# exists in CLOSED list
|
|
56
|
+
if node.current in CLOSED:
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
# goal found
|
|
60
|
+
if node == self.goal:
|
|
61
|
+
CLOSED[node.current] = node
|
|
62
|
+
cost, path = self.extractPath(CLOSED)
|
|
63
|
+
return cost, path, list(CLOSED.values())
|
|
64
|
+
|
|
65
|
+
jp_list = []
|
|
66
|
+
for motion in self.motions:
|
|
67
|
+
jp = self.jump(node, motion)
|
|
68
|
+
# exists and not in CLOSED list
|
|
69
|
+
if jp and jp.current not in CLOSED:
|
|
70
|
+
jp.parent = node.current
|
|
71
|
+
jp.h = self.h(jp, self.goal)
|
|
72
|
+
jp_list.append(jp)
|
|
73
|
+
|
|
74
|
+
for jp in jp_list:
|
|
75
|
+
# update OPEN list
|
|
76
|
+
heapq.heappush(OPEN, jp)
|
|
77
|
+
|
|
78
|
+
# goal found
|
|
79
|
+
if jp == self.goal:
|
|
80
|
+
break
|
|
81
|
+
|
|
82
|
+
CLOSED[node.current] = node
|
|
83
|
+
return [], [], []
|
|
84
|
+
|
|
85
|
+
def jump(self, node: Node, motion: Node):
|
|
86
|
+
"""
|
|
87
|
+
Jumping search recursively.
|
|
88
|
+
|
|
89
|
+
Parameters:
|
|
90
|
+
node (Node): current node
|
|
91
|
+
motion (Node): the motion that current node executes
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
jump_point (Node): jump point or None if searching fails
|
|
95
|
+
"""
|
|
96
|
+
# explore a new node
|
|
97
|
+
new_node = node + motion
|
|
98
|
+
new_node.parent = node.current
|
|
99
|
+
new_node.h = self.h(new_node, self.goal)
|
|
100
|
+
|
|
101
|
+
# hit the obstacle
|
|
102
|
+
if new_node.current in self.obstacles:
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
# goal found
|
|
106
|
+
if new_node == self.goal:
|
|
107
|
+
return new_node
|
|
108
|
+
|
|
109
|
+
# diagonal
|
|
110
|
+
if motion.x and motion.y:
|
|
111
|
+
# if exists jump point at horizontal or vertical
|
|
112
|
+
x_dir = Node((motion.x, 0), None, 1, None)
|
|
113
|
+
y_dir = Node((0, motion.y), None, 1, None)
|
|
114
|
+
if self.jump(new_node, x_dir) or self.jump(new_node, y_dir):
|
|
115
|
+
return new_node
|
|
116
|
+
|
|
117
|
+
# if exists forced neighbor
|
|
118
|
+
if self.detectForceNeighbor(new_node, motion):
|
|
119
|
+
return new_node
|
|
120
|
+
else:
|
|
121
|
+
return self.jump(new_node, motion)
|
|
122
|
+
|
|
123
|
+
def detectForceNeighbor(self, node, motion):
|
|
124
|
+
"""
|
|
125
|
+
Detect forced neighbor of node.
|
|
126
|
+
|
|
127
|
+
Parameters:
|
|
128
|
+
node (Node): current node
|
|
129
|
+
motion (Node): the motion that current node executes
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
flag (bool): True if current node has forced neighbor else Flase
|
|
133
|
+
"""
|
|
134
|
+
x, y = node.current
|
|
135
|
+
x_dir, y_dir = motion.current
|
|
136
|
+
|
|
137
|
+
# horizontal
|
|
138
|
+
if x_dir and not y_dir:
|
|
139
|
+
if (x, y + 1) in self.obstacles and \
|
|
140
|
+
(x + x_dir, y + 1) not in self.obstacles:
|
|
141
|
+
return True
|
|
142
|
+
if (x, y - 1) in self.obstacles and \
|
|
143
|
+
(x + x_dir, y - 1) not in self.obstacles:
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
# vertical
|
|
147
|
+
if not x_dir and y_dir:
|
|
148
|
+
if (x + 1, y) in self.obstacles and \
|
|
149
|
+
(x + 1, y + y_dir) not in self.obstacles:
|
|
150
|
+
return True
|
|
151
|
+
if (x - 1, y) in self.obstacles and \
|
|
152
|
+
(x - 1, y + y_dir) not in self.obstacles:
|
|
153
|
+
return True
|
|
154
|
+
|
|
155
|
+
# diagonal
|
|
156
|
+
if x_dir and y_dir:
|
|
157
|
+
if (x - x_dir, y) in self.obstacles and \
|
|
158
|
+
(x - x_dir, y + y_dir) not in self.obstacles:
|
|
159
|
+
return True
|
|
160
|
+
if (x, y - y_dir) in self.obstacles and \
|
|
161
|
+
(x + x_dir, y - y_dir) not in self.obstacles:
|
|
162
|
+
return True
|
|
163
|
+
|
|
164
|
+
return False
|
|
165
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: lazy_theta_star.py
|
|
3
|
+
@breif: Lazy Theta* motion planning
|
|
4
|
+
@author: Yang Haodong, Wu Maojia
|
|
5
|
+
@update: 2024.6.23
|
|
6
|
+
"""
|
|
7
|
+
import heapq
|
|
8
|
+
|
|
9
|
+
from .theta_star import ThetaStar
|
|
10
|
+
from python_motion_planning.utils import Env, Node
|
|
11
|
+
|
|
12
|
+
class LazyThetaStar(ThetaStar):
|
|
13
|
+
"""
|
|
14
|
+
Class for Lazy Theta* motion planning.
|
|
15
|
+
|
|
16
|
+
Parameters:
|
|
17
|
+
start (tuple): start point coordinate
|
|
18
|
+
goal (tuple): goal point coordinate
|
|
19
|
+
env (Env): environment
|
|
20
|
+
heuristic_type (str): heuristic function type
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
>>> import python_motion_planning as pmp
|
|
24
|
+
>>> planner = pmp.LazyThetaStar((5, 5), (45, 25), pmp.Grid(51, 31))
|
|
25
|
+
>>> cost, path, expand = planner.plan()
|
|
26
|
+
>>> planner.plot.animation(path, str(planner), cost, expand) # animation
|
|
27
|
+
>>> planner.run() # run both planning and animation
|
|
28
|
+
|
|
29
|
+
References:
|
|
30
|
+
[1] Lazy Theta*: Any-Angle Path Planning and Path Length Analysis in 3D
|
|
31
|
+
"""
|
|
32
|
+
def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean") -> None:
|
|
33
|
+
super().__init__(start, goal, env, heuristic_type)
|
|
34
|
+
|
|
35
|
+
def __str__(self) -> str:
|
|
36
|
+
return "Lazy Theta*"
|
|
37
|
+
|
|
38
|
+
def plan(self) -> tuple:
|
|
39
|
+
"""
|
|
40
|
+
Lazy Theta* motion plan function.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
cost (float): path cost
|
|
44
|
+
path (list): planning path
|
|
45
|
+
expand (list): all nodes that planner has searched
|
|
46
|
+
"""
|
|
47
|
+
# OPEN list (priority queue) and CLOSED list (hash table)
|
|
48
|
+
OPEN = []
|
|
49
|
+
heapq.heappush(OPEN, self.start)
|
|
50
|
+
CLOSED = dict()
|
|
51
|
+
|
|
52
|
+
while OPEN:
|
|
53
|
+
node = heapq.heappop(OPEN)
|
|
54
|
+
|
|
55
|
+
# set vertex: path 1
|
|
56
|
+
node_p = CLOSED.get(node.parent)
|
|
57
|
+
if node_p:
|
|
58
|
+
if not self.lineOfSight(node_p, node):
|
|
59
|
+
node.g = float("inf")
|
|
60
|
+
for node_n in self.getNeighbor(node):
|
|
61
|
+
if node_n.current in CLOSED:
|
|
62
|
+
node_n = CLOSED[node_n.current]
|
|
63
|
+
if node.g > node_n.g + self.dist(node_n, node):
|
|
64
|
+
node.g = node_n.g + self.dist(node_n, node)
|
|
65
|
+
node.parent = node_n.current
|
|
66
|
+
|
|
67
|
+
# exists in CLOSED list
|
|
68
|
+
if node.current in CLOSED:
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
# goal found
|
|
72
|
+
if node == self.goal:
|
|
73
|
+
CLOSED[node.current] = node
|
|
74
|
+
cost, path = self.extractPath(CLOSED)
|
|
75
|
+
return cost, path, list(CLOSED.values())
|
|
76
|
+
|
|
77
|
+
for node_n in self.getNeighbor(node):
|
|
78
|
+
# exists in CLOSED list
|
|
79
|
+
if node_n.current in CLOSED:
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
# path1
|
|
83
|
+
node_n.parent = node.current
|
|
84
|
+
node_n.h = self.h(node_n, self.goal)
|
|
85
|
+
|
|
86
|
+
node_p = CLOSED.get(node.parent)
|
|
87
|
+
|
|
88
|
+
if node_p:
|
|
89
|
+
# path2
|
|
90
|
+
self.updateVertex(node_p, node_n)
|
|
91
|
+
|
|
92
|
+
# goal found
|
|
93
|
+
if node_n == self.goal:
|
|
94
|
+
heapq.heappush(OPEN, node_n)
|
|
95
|
+
break
|
|
96
|
+
|
|
97
|
+
# update OPEN list
|
|
98
|
+
heapq.heappush(OPEN, node_n)
|
|
99
|
+
|
|
100
|
+
CLOSED[node.current] = node
|
|
101
|
+
return [], [], []
|
|
102
|
+
|
|
103
|
+
def updateVertex(self, node_p: Node, node_c: Node) -> None:
|
|
104
|
+
"""
|
|
105
|
+
Update extend node information with current node's parent node.
|
|
106
|
+
|
|
107
|
+
Parameters:
|
|
108
|
+
node_p (Node): parent node
|
|
109
|
+
node_c (Node): current node
|
|
110
|
+
"""
|
|
111
|
+
# path 2
|
|
112
|
+
if node_p.g + self.dist(node_c, node_p) <= node_c.g:
|
|
113
|
+
node_c.g = node_p.g + self.dist(node_c, node_p)
|
|
114
|
+
node_c.parent = node_p.current
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: lpa_star.py
|
|
3
|
+
@breif: Lifelong Planning 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
|
+
class LNode(Node):
|
|
13
|
+
"""
|
|
14
|
+
Class for LPA* nodes.
|
|
15
|
+
|
|
16
|
+
Parameters:
|
|
17
|
+
current (tuple): current coordinate
|
|
18
|
+
g (float): minimum cost moving from start(predict)
|
|
19
|
+
rhs (float): minimum cost moving from start(value)
|
|
20
|
+
key (list): priority
|
|
21
|
+
"""
|
|
22
|
+
def __init__(self, current: tuple, g: float, rhs: float, key: list) -> None:
|
|
23
|
+
self.current = current
|
|
24
|
+
self.g = g
|
|
25
|
+
self.rhs = rhs
|
|
26
|
+
self.key = key
|
|
27
|
+
|
|
28
|
+
def __add__(self, node):
|
|
29
|
+
return LNode((self.x + node.x, self.y + node.y),
|
|
30
|
+
self.g, self.rhs, self.key)
|
|
31
|
+
|
|
32
|
+
def __lt__(self, node) -> bool:
|
|
33
|
+
return self.key < node.key
|
|
34
|
+
|
|
35
|
+
def __str__(self) -> str:
|
|
36
|
+
return "----------\ncurrent:{}\ng:{}\nrhs:{}\nkey:{}\n----------" \
|
|
37
|
+
.format(self.current, self.g, self.rhs, self.key)
|
|
38
|
+
|
|
39
|
+
class LPAStar(GraphSearcher):
|
|
40
|
+
"""
|
|
41
|
+
Class for LPA* motion planning.
|
|
42
|
+
|
|
43
|
+
Parameters:
|
|
44
|
+
start (tuple): start point coordinate
|
|
45
|
+
goal (tuple): goal point coordinate
|
|
46
|
+
env (Env): environment
|
|
47
|
+
heuristic_type (str): heuristic function type
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
>>> import python_motion_planning as pmp
|
|
51
|
+
>>> planner = pmp.LPAStar((5, 5), (45, 25), pmp.Grid(51, 31))
|
|
52
|
+
>>> cost, path, _ = planner.plan() # planning results only
|
|
53
|
+
>>> planner.plot.animation(path, str(planner), cost) # animation
|
|
54
|
+
>>> planner.run() # run both planning and animation
|
|
55
|
+
|
|
56
|
+
References:
|
|
57
|
+
[1] Lifelong Planning A*
|
|
58
|
+
"""
|
|
59
|
+
def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean") -> None:
|
|
60
|
+
super().__init__(start, goal, env, heuristic_type)
|
|
61
|
+
# start and goal
|
|
62
|
+
self.start = LNode(start, float('inf'), 0.0, None)
|
|
63
|
+
self.goal = LNode(goal, float('inf'), float('inf'), None)
|
|
64
|
+
# OPEN set and expand zone
|
|
65
|
+
self.U, self.EXPAND = [], []
|
|
66
|
+
|
|
67
|
+
# intialize global information, record history infomation of map grids
|
|
68
|
+
self.map = {s: LNode(s, float('inf'), float('inf'), None) for s in self.env.grid_map}
|
|
69
|
+
self.map[self.goal.current] = self.goal
|
|
70
|
+
self.map[self.start.current] = self.start
|
|
71
|
+
# OPEN set with priority
|
|
72
|
+
self.start.key = self.calculateKey(self.start)
|
|
73
|
+
heapq.heappush(self.U, self.start)
|
|
74
|
+
|
|
75
|
+
def __str__(self) -> str:
|
|
76
|
+
return "Lifelong Planning A*"
|
|
77
|
+
|
|
78
|
+
def plan(self) -> tuple:
|
|
79
|
+
"""
|
|
80
|
+
LPA* dynamic motion planning function.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
cost (float): path cost
|
|
84
|
+
path (list): planning path
|
|
85
|
+
_ (None): None
|
|
86
|
+
"""
|
|
87
|
+
self.computeShortestPath()
|
|
88
|
+
cost, path = self.extractPath()
|
|
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):
|
|
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
|
+
print("Change position: x = {}, y = {}".format(x, y))
|
|
114
|
+
self.EXPAND = []
|
|
115
|
+
node_change = self.map[(x, y)]
|
|
116
|
+
|
|
117
|
+
if (x, y) not in self.obstacles:
|
|
118
|
+
self.obstacles.add((x, y))
|
|
119
|
+
else:
|
|
120
|
+
self.obstacles.remove((x, y))
|
|
121
|
+
self.updateVertex(node_change)
|
|
122
|
+
|
|
123
|
+
self.env.update(self.obstacles)
|
|
124
|
+
|
|
125
|
+
for node_n in self.getNeighbor(node_change):
|
|
126
|
+
self.updateVertex(node_n)
|
|
127
|
+
|
|
128
|
+
cost, path, _ = self.plan()
|
|
129
|
+
|
|
130
|
+
# animation
|
|
131
|
+
self.plot.clean()
|
|
132
|
+
self.plot.animation(path, str(self), cost, self.EXPAND)
|
|
133
|
+
self.plot.update()
|
|
134
|
+
|
|
135
|
+
def computeShortestPath(self) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Perceived dynamic obstacle information to optimize global path.
|
|
138
|
+
"""
|
|
139
|
+
while True:
|
|
140
|
+
node = min(self.U, key=lambda node: node.key)
|
|
141
|
+
if node.key >= self.calculateKey(self.goal) and \
|
|
142
|
+
self.goal.rhs == self.goal.g:
|
|
143
|
+
break
|
|
144
|
+
|
|
145
|
+
self.U.remove(node)
|
|
146
|
+
self.EXPAND.append(node)
|
|
147
|
+
|
|
148
|
+
# Locally over-consistent -> Locally consistent
|
|
149
|
+
if node.g > node.rhs:
|
|
150
|
+
node.g = node.rhs
|
|
151
|
+
# Locally under-consistent -> Locally over-consistent
|
|
152
|
+
else:
|
|
153
|
+
node.g = float("inf")
|
|
154
|
+
self.updateVertex(node)
|
|
155
|
+
|
|
156
|
+
for node_n in self.getNeighbor(node):
|
|
157
|
+
self.updateVertex(node_n)
|
|
158
|
+
|
|
159
|
+
def updateVertex(self, node: LNode) -> None:
|
|
160
|
+
"""
|
|
161
|
+
Update the status and the current cost to node and it's neighbor.
|
|
162
|
+
|
|
163
|
+
Parameters:
|
|
164
|
+
node (LNode): current node
|
|
165
|
+
"""
|
|
166
|
+
# greed correction
|
|
167
|
+
if node != self.start:
|
|
168
|
+
node.rhs = min([node_n.g + self.cost(node_n, node)
|
|
169
|
+
for node_n in self.getNeighbor(node)])
|
|
170
|
+
|
|
171
|
+
if node in self.U:
|
|
172
|
+
self.U.remove(node)
|
|
173
|
+
|
|
174
|
+
# Locally unconsistent nodes should be added into OPEN set (set U)
|
|
175
|
+
if node.g != node.rhs:
|
|
176
|
+
node.key = self.calculateKey(node)
|
|
177
|
+
heapq.heappush(self.U, node)
|
|
178
|
+
|
|
179
|
+
def calculateKey(self, node: LNode) -> list:
|
|
180
|
+
"""
|
|
181
|
+
Calculate priority of node.
|
|
182
|
+
|
|
183
|
+
Parameters:
|
|
184
|
+
node (LNode): current node
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
key (list): priority of node
|
|
188
|
+
"""
|
|
189
|
+
return [min(node.g, node.rhs) + self.h(node, self.goal),
|
|
190
|
+
min(node.g, node.rhs)]
|
|
191
|
+
|
|
192
|
+
def getNeighbor(self, node: LNode) -> list:
|
|
193
|
+
"""
|
|
194
|
+
Find neighbors of node.
|
|
195
|
+
|
|
196
|
+
Parameters:
|
|
197
|
+
node (LNode): current node
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
neighbors (list): neighbors of node
|
|
201
|
+
"""
|
|
202
|
+
neighbors = []
|
|
203
|
+
for motion in self.motions:
|
|
204
|
+
n = self.map[(node + motion).current]
|
|
205
|
+
if n.current not in self.obstacles:
|
|
206
|
+
neighbors.append(n)
|
|
207
|
+
return neighbors
|
|
208
|
+
|
|
209
|
+
def extractPath(self):
|
|
210
|
+
"""
|
|
211
|
+
Extract the path based on greedy policy.
|
|
212
|
+
|
|
213
|
+
Return:
|
|
214
|
+
cost (float): the cost of planning path
|
|
215
|
+
path (list): the planning path
|
|
216
|
+
"""
|
|
217
|
+
node = self.goal
|
|
218
|
+
path = [node.current]
|
|
219
|
+
cost, count = 0, 0
|
|
220
|
+
while node != self.start:
|
|
221
|
+
neighbors = [node_n for node_n in self.getNeighbor(node) if not self.isCollision(node, node_n)]
|
|
222
|
+
next_node = min(neighbors, key=lambda n: n.g)
|
|
223
|
+
path.append(next_node.current)
|
|
224
|
+
cost += self.cost(node, next_node)
|
|
225
|
+
node = next_node
|
|
226
|
+
count += 1
|
|
227
|
+
if count == 1000:
|
|
228
|
+
return cost, []
|
|
229
|
+
return cost, list(reversed(path))
|
|
230
|
+
|