python-motion-planning 2.0.dev1__py3-none-any.whl → 2.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 +1 -1
- python_motion_planning/common/env/map/base_map.py +2 -8
- python_motion_planning/common/env/map/grid.py +456 -198
- python_motion_planning/common/utils/__init__.py +2 -1
- python_motion_planning/common/utils/child_tree.py +22 -0
- python_motion_planning/common/utils/geometry.py +18 -29
- python_motion_planning/common/visualizer/__init__.py +3 -1
- python_motion_planning/common/visualizer/base_visualizer.py +165 -0
- python_motion_planning/common/visualizer/{visualizer.py → visualizer_2d.py} +97 -220
- python_motion_planning/common/visualizer/visualizer_3d.py +242 -0
- python_motion_planning/controller/base_controller.py +37 -4
- python_motion_planning/controller/path_tracker/__init__.py +2 -1
- python_motion_planning/controller/path_tracker/apf.py +22 -23
- python_motion_planning/controller/path_tracker/dwa.py +14 -17
- python_motion_planning/controller/path_tracker/path_tracker.py +4 -1
- python_motion_planning/controller/path_tracker/pid.py +7 -1
- python_motion_planning/controller/path_tracker/pure_pursuit.py +7 -1
- python_motion_planning/controller/path_tracker/rpp.py +111 -0
- python_motion_planning/path_planner/__init__.py +2 -1
- python_motion_planning/path_planner/base_path_planner.py +45 -11
- python_motion_planning/path_planner/graph_search/__init__.py +4 -1
- python_motion_planning/path_planner/graph_search/a_star.py +12 -14
- python_motion_planning/path_planner/graph_search/dijkstra.py +15 -15
- python_motion_planning/path_planner/graph_search/gbfs.py +100 -0
- python_motion_planning/path_planner/graph_search/jps.py +199 -0
- python_motion_planning/path_planner/graph_search/lazy_theta_star.py +113 -0
- python_motion_planning/path_planner/graph_search/theta_star.py +17 -19
- python_motion_planning/path_planner/hybrid_search/__init__.py +1 -0
- python_motion_planning/path_planner/hybrid_search/voronoi_planner.py +204 -0
- python_motion_planning/path_planner/sample_search/__init__.py +2 -1
- python_motion_planning/path_planner/sample_search/rrt.py +73 -31
- python_motion_planning/path_planner/sample_search/rrt_connect.py +237 -0
- python_motion_planning/path_planner/sample_search/rrt_star.py +220 -150
- python_motion_planning/traj_optimizer/__init__.py +2 -0
- python_motion_planning/traj_optimizer/base_curve_generator.py +53 -0
- python_motion_planning/traj_optimizer/curve_generator/__init__.py +2 -0
- python_motion_planning/traj_optimizer/curve_generator/point_based/__init__.py +2 -0
- python_motion_planning/traj_optimizer/curve_generator/point_based/bspline.py +256 -0
- python_motion_planning/traj_optimizer/curve_generator/point_based/cubic_spline.py +115 -0
- python_motion_planning/traj_optimizer/curve_generator/pose_based/__init__.py +4 -0
- python_motion_planning/traj_optimizer/curve_generator/pose_based/bezier.py +121 -0
- python_motion_planning/traj_optimizer/curve_generator/pose_based/dubins.py +355 -0
- python_motion_planning/traj_optimizer/curve_generator/pose_based/polynomial.py +197 -0
- python_motion_planning/traj_optimizer/curve_generator/pose_based/reeds_shepp.py +606 -0
- {python_motion_planning-2.0.dev1.dist-info → python_motion_planning-2.0.1.dist-info}/METADATA +71 -29
- python_motion_planning-2.0.1.dist-info/RECORD +64 -0
- {python_motion_planning-2.0.dev1.dist-info → python_motion_planning-2.0.1.dist-info}/WHEEL +1 -1
- python_motion_planning/common/env/robot/tmp.py +0 -404
- python_motion_planning/curve_generator/__init__.py +0 -9
- python_motion_planning/curve_generator/bezier_curve.py +0 -131
- python_motion_planning/curve_generator/bspline_curve.py +0 -271
- python_motion_planning/curve_generator/cubic_spline.py +0 -128
- python_motion_planning/curve_generator/curve.py +0 -64
- python_motion_planning/curve_generator/dubins_curve.py +0 -348
- python_motion_planning/curve_generator/fem_pos_smooth.py +0 -114
- python_motion_planning/curve_generator/polynomial_curve.py +0 -226
- python_motion_planning/curve_generator/reeds_shepp.py +0 -736
- python_motion_planning-2.0.dev1.dist-info/RECORD +0 -53
- {python_motion_planning-2.0.dev1.dist-info → python_motion_planning-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {python_motion_planning-2.0.dev1.dist-info → python_motion_planning-2.0.1.dist-info}/top_level.txt +0 -0
|
@@ -1,29 +1,43 @@
|
|
|
1
1
|
"""
|
|
2
2
|
@file: rrt_star.py
|
|
3
|
-
@author: Wu Maojia
|
|
4
|
-
@update:
|
|
3
|
+
@author: Wu Maojia
|
|
4
|
+
@update: 2026.4.12
|
|
5
5
|
"""
|
|
6
6
|
import math
|
|
7
7
|
import random
|
|
8
|
-
from typing import Union, Dict, List, Tuple
|
|
8
|
+
from typing import Union, Dict, List, Tuple, Any
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import faiss
|
|
12
|
+
|
|
13
|
+
from python_motion_planning.common import Node, Grid, TYPES
|
|
14
|
+
from python_motion_planning.path_planner.sample_search import RRT
|
|
15
|
+
from python_motion_planning.common.utils.child_tree import ChildTree
|
|
9
16
|
|
|
10
|
-
from python_motion_planning.common import BaseMap, Node, Grid, TYPES
|
|
11
|
-
from python_motion_planning.path_planner.sample_search.rrt import RRT
|
|
12
17
|
|
|
13
18
|
class RRTStar(RRT):
|
|
14
19
|
"""
|
|
15
20
|
Class for RRT* (Rapidly-exploring Random Tree Star) path planner.
|
|
16
|
-
|
|
17
|
-
RRT*
|
|
18
|
-
|
|
21
|
+
|
|
22
|
+
RRT* extends RRT by:
|
|
23
|
+
1. Selecting the best parent (minimum cost) for a new node.
|
|
24
|
+
2. Rewiring nearby nodes through the new node if it improves their cost.
|
|
19
25
|
|
|
20
26
|
Args:
|
|
21
|
-
*args: see
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
*args: see parent class.
|
|
28
|
+
rewire_radius: Neighborhood radius for rewiring (If None, adaptively calculated with gamma factor).
|
|
29
|
+
gamma: A factor for calculating rewire_radius. For details, see [1]. (Disabled when rewire_radius is not None)
|
|
30
|
+
stop_func: A callable(cur_step, first_success_step, max_sample_step) -> bool that controls when to stop sampling. Called at the beginning of each iteration.
|
|
31
|
+
Arguments:
|
|
32
|
+
- cur_step (int): Number of sampling iterations completed so far (starts from 0, incremented before each sampling, so the first completed sample has cur_step=1).
|
|
33
|
+
- first_success_step (int or None): The cur_step value at which a feasible path was first found, or None if no path has been found yet.
|
|
34
|
+
- max_sample_step (int): The planner's max_sample_step attribute, provided for convenience.
|
|
35
|
+
Returns True to stop, False to continue.
|
|
36
|
+
propagate_cost_to_children: Whether to propagate cost to children. This is a fix for ensuring the correctness of g-value of each node. But it may slow the algorithm a little.
|
|
37
|
+
*kwargs: see parent class.
|
|
24
38
|
|
|
25
39
|
References:
|
|
26
|
-
[1] Sampling-based
|
|
40
|
+
[1] Sampling-based Algorithms for Optimal Motion Planning
|
|
27
41
|
|
|
28
42
|
Examples:
|
|
29
43
|
>>> map_ = Grid(bounds=[[0, 15], [0, 15]])
|
|
@@ -32,178 +46,234 @@ class RRTStar(RRT):
|
|
|
32
46
|
>>> print(path_info['success'])
|
|
33
47
|
True
|
|
34
48
|
|
|
35
|
-
>>> planner
|
|
49
|
+
>>> planner = RRTStar(map_=map_, start=(5, 5), goal=(10, 10), max_sample_step=100000, stop_func=lambda current_step, first_success_step, max_step: (first_success_step is not None) or (current_step >= max_step))
|
|
36
50
|
>>> path, path_info = planner.plan()
|
|
37
51
|
>>> print(path_info['success'])
|
|
38
52
|
True
|
|
39
53
|
"""
|
|
40
|
-
|
|
41
|
-
|
|
54
|
+
|
|
55
|
+
def __init__(self, *args,
|
|
56
|
+
rewire_radius: float = None,
|
|
57
|
+
gamma: float = 50.0,
|
|
58
|
+
stop_func: callable = lambda current_step, first_success_step, max_step: (first_success_step is not None) or (current_step >= max_step),
|
|
59
|
+
propagate_cost_to_children: bool = True,
|
|
60
|
+
**kwargs) -> None:
|
|
42
61
|
super().__init__(*args, **kwargs)
|
|
43
|
-
|
|
44
|
-
self.
|
|
62
|
+
self.rewire_radius = rewire_radius
|
|
63
|
+
self.gamma = gamma
|
|
64
|
+
self.stop_func = stop_func
|
|
65
|
+
|
|
66
|
+
self.failed_info[1]["first_success_step"] = None
|
|
67
|
+
self.failed_info[1]["best_step"] = None
|
|
68
|
+
self.failed_info[1]["total_step"] = None
|
|
69
|
+
|
|
70
|
+
self.best_results = self.failed_info
|
|
71
|
+
self.propagate_cost_to_children = propagate_cost_to_children
|
|
72
|
+
|
|
73
|
+
self._tree = None
|
|
74
|
+
self._child = None
|
|
45
75
|
|
|
46
76
|
def __str__(self) -> str:
|
|
47
|
-
return "
|
|
77
|
+
return "RRT*"
|
|
48
78
|
|
|
49
|
-
def plan(self) -> Union[
|
|
79
|
+
def plan(self) -> Union[List[Tuple[float, ...]], Dict[str, Any]]:
|
|
50
80
|
"""
|
|
51
|
-
RRT* path planning algorithm implementation
|
|
52
|
-
|
|
81
|
+
RRT* path planning algorithm implementation.
|
|
82
|
+
|
|
53
83
|
Returns:
|
|
54
84
|
path: A list containing the path waypoints
|
|
55
85
|
path_info: A dictionary containing path information
|
|
56
86
|
"""
|
|
57
87
|
# Initialize tree with start node
|
|
58
|
-
|
|
88
|
+
self._tree = {}
|
|
89
|
+
self._child = ChildTree()
|
|
59
90
|
start_node = Node(self.start, None, 0, 0)
|
|
60
|
-
|
|
91
|
+
self._tree[self.start] = start_node
|
|
92
|
+
|
|
93
|
+
# Initialize FAISS index
|
|
94
|
+
if self.use_faiss:
|
|
95
|
+
faiss_index = faiss.IndexFlatL2(self.dim)
|
|
96
|
+
faiss_nodes = []
|
|
97
|
+
self._faiss_add_node(start_node, faiss_index, faiss_nodes)
|
|
98
|
+
|
|
99
|
+
i = 0
|
|
100
|
+
first_success_i = None
|
|
101
|
+
while True:
|
|
102
|
+
if self.stop_func(i, first_success_i, self.max_sample_step):
|
|
103
|
+
break
|
|
104
|
+
|
|
105
|
+
i += 1
|
|
61
106
|
|
|
62
|
-
|
|
63
|
-
for _ in range(self.sample_num):
|
|
64
|
-
# Generate random sample node
|
|
107
|
+
# Generate random sample
|
|
65
108
|
node_rand = self._generate_random_node()
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if node_rand.current in tree:
|
|
109
|
+
|
|
110
|
+
if node_rand.current in self._tree:
|
|
69
111
|
continue
|
|
70
|
-
|
|
71
|
-
# Find nearest node
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
#
|
|
75
|
-
node_new = self._steer(
|
|
112
|
+
|
|
113
|
+
# Find nearest node
|
|
114
|
+
node_nearest = self._get_nearest_node(self._tree, node_rand, faiss_index, faiss_nodes)
|
|
115
|
+
|
|
116
|
+
# Steer towards random sample
|
|
117
|
+
node_new = self._steer(node_nearest, node_rand)
|
|
76
118
|
if node_new is None:
|
|
77
119
|
continue
|
|
78
|
-
|
|
79
|
-
# Check if edge is collision-free
|
|
80
|
-
if self.map_.in_collision(node_new.current, node_near.current):
|
|
81
|
-
continue
|
|
82
120
|
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if node_new is None:
|
|
121
|
+
# Collision check
|
|
122
|
+
if self.map_.in_collision(
|
|
123
|
+
self.map_.point_float_to_int(node_nearest.current),
|
|
124
|
+
self.map_.point_float_to_int(node_new.current)
|
|
125
|
+
):
|
|
89
126
|
continue
|
|
90
|
-
|
|
91
|
-
# Add new node to tree
|
|
92
|
-
tree[node_new.current] = node_new
|
|
93
|
-
|
|
94
|
-
# Rewire tree to potentially improve existing paths
|
|
95
|
-
self._rewire(tree, node_new, near_nodes)
|
|
96
127
|
|
|
97
|
-
#
|
|
128
|
+
# Find nearby nodes for choosing best parent
|
|
129
|
+
near_nodes = self._get_near_nodes(node_new, faiss_index, faiss_nodes)
|
|
130
|
+
|
|
131
|
+
# Choose parent with minimum cost
|
|
132
|
+
min_parent = node_nearest
|
|
133
|
+
min_cost = node_nearest.g + self.get_cost(node_nearest.current, node_new.current)
|
|
134
|
+
|
|
135
|
+
for node in near_nodes:
|
|
136
|
+
if self.map_.in_collision(
|
|
137
|
+
self.map_.point_float_to_int(node.current),
|
|
138
|
+
self.map_.point_float_to_int(node_new.current)
|
|
139
|
+
):
|
|
140
|
+
continue
|
|
141
|
+
cost = node.g + self.get_cost(node.current, node_new.current)
|
|
142
|
+
if cost < min_cost:
|
|
143
|
+
min_parent = node
|
|
144
|
+
min_cost = cost
|
|
145
|
+
|
|
146
|
+
# Add new node
|
|
147
|
+
if self.propagate_cost_to_children:
|
|
148
|
+
self._child.remove(node_new.parent, node_new.current)
|
|
149
|
+
self._child.add(min_parent.current, node_new.current)
|
|
150
|
+
|
|
151
|
+
node_new.parent = min_parent.current
|
|
152
|
+
node_new.g = min_cost
|
|
153
|
+
self._tree[node_new.current] = node_new
|
|
154
|
+
if self.use_faiss:
|
|
155
|
+
self._faiss_add_node(node_new, faiss_index, faiss_nodes)
|
|
156
|
+
|
|
157
|
+
# Rewire nearby nodes through new node
|
|
158
|
+
for node in near_nodes:
|
|
159
|
+
if node.current == min_parent.current:
|
|
160
|
+
continue
|
|
161
|
+
if self.map_.in_collision(
|
|
162
|
+
self.map_.point_float_to_int(node.current),
|
|
163
|
+
self.map_.point_float_to_int(node_new.current)
|
|
164
|
+
):
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
new_cost = node_new.g + self.get_cost(node_new.current, node.current)
|
|
168
|
+
if new_cost < node.g:
|
|
169
|
+
if self.propagate_cost_to_children:
|
|
170
|
+
self._child.remove(node.parent, node.current)
|
|
171
|
+
self._child.add(node_new.current, node.current)
|
|
172
|
+
|
|
173
|
+
node.parent = node_new.current
|
|
174
|
+
node.g = new_cost
|
|
175
|
+
self._tree[node.current] = node
|
|
176
|
+
|
|
177
|
+
if self.propagate_cost_to_children:
|
|
178
|
+
self._propagate_cost_to_children(node)
|
|
179
|
+
|
|
180
|
+
# Check goal connection
|
|
98
181
|
dist_to_goal = self.get_cost(node_new.current, self.goal)
|
|
99
182
|
if dist_to_goal <= self.max_dist:
|
|
100
|
-
|
|
101
|
-
|
|
183
|
+
if not self.map_.in_collision(
|
|
184
|
+
self.map_.point_float_to_int(node_new.current),
|
|
185
|
+
self.map_.point_float_to_int(self.goal)
|
|
186
|
+
):
|
|
102
187
|
goal_cost = node_new.g + dist_to_goal
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
188
|
+
if self.goal not in self._tree or goal_cost < self._tree[self.goal].g:
|
|
189
|
+
if node_new.current == self.goal:
|
|
190
|
+
self._tree[self.goal] = node_new
|
|
191
|
+
else:
|
|
192
|
+
self._tree[self.goal] = Node(
|
|
193
|
+
self.goal,
|
|
194
|
+
node_new.current,
|
|
195
|
+
goal_cost,
|
|
196
|
+
0
|
|
197
|
+
)
|
|
198
|
+
path, length, cost = self.extract_path(self._tree)
|
|
199
|
+
|
|
200
|
+
if first_success_i is None:
|
|
201
|
+
first_success_i = i
|
|
202
|
+
|
|
203
|
+
self.best_results = path, {
|
|
204
|
+
"success": True,
|
|
205
|
+
"start": self.start,
|
|
206
|
+
"goal": self.goal,
|
|
207
|
+
"length": length,
|
|
208
|
+
"cost": cost,
|
|
209
|
+
"first_success_step": first_success_i,
|
|
210
|
+
"best_step": i,
|
|
211
|
+
"total_step": i,
|
|
212
|
+
"expand": self._tree,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# Planning stopped
|
|
217
|
+
self.best_results[1]["total_step"] = i
|
|
218
|
+
self.best_results[1]["expand"] = self._tree
|
|
219
|
+
return self.best_results
|
|
220
|
+
|
|
221
|
+
def _get_near_nodes(self, node_new: Node, index=None, nodes=None) -> List[Node]:
|
|
128
222
|
"""
|
|
129
|
-
|
|
130
|
-
|
|
223
|
+
Get nearby nodes within rewiring radius.
|
|
224
|
+
|
|
131
225
|
Args:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
226
|
+
node_new: Newly added node
|
|
227
|
+
index: FAISS index (required when `use_faiss`=True)
|
|
228
|
+
nodes: List of nodes in FAISS index (required when `use_faiss`=True)
|
|
229
|
+
|
|
135
230
|
Returns:
|
|
136
|
-
near_nodes: List of nodes
|
|
231
|
+
near_nodes: List of nearby nodes
|
|
137
232
|
"""
|
|
233
|
+
# Adaptive radius from RRT* theory
|
|
234
|
+
if self.rewire_radius is None:
|
|
235
|
+
n = len(self._tree) + 1
|
|
236
|
+
radius = self.gamma * ((math.log(n) / n) ** (1 / self.dim))
|
|
237
|
+
if self.discrete:
|
|
238
|
+
radius = max(1, radius)
|
|
239
|
+
else:
|
|
240
|
+
radius = self.rewire_radius
|
|
241
|
+
|
|
138
242
|
near_nodes = []
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
243
|
+
|
|
244
|
+
if self.use_faiss:
|
|
245
|
+
# range search using faiss
|
|
246
|
+
query = np.asarray(node_new.current, dtype=np.float32).reshape(1, -1)
|
|
247
|
+
lims, D, I = index.range_search(query, radius * radius)
|
|
248
|
+
for idx in I:
|
|
249
|
+
near_nodes.append(nodes[idx])
|
|
250
|
+
|
|
251
|
+
else:
|
|
252
|
+
# brute force radius search
|
|
253
|
+
for node in self._tree.values():
|
|
254
|
+
if self.get_cost(node.current, node_new.current) <= radius:
|
|
255
|
+
near_nodes.append(node)
|
|
256
|
+
|
|
142
257
|
return near_nodes
|
|
143
258
|
|
|
144
|
-
def
|
|
145
|
-
"""
|
|
146
|
-
Select the optimal parent for the new node from nearby nodes to minimize cost.
|
|
147
|
-
|
|
148
|
-
Args:
|
|
149
|
-
node_new: Newly created node
|
|
150
|
-
node_near: Nearest node in the tree
|
|
151
|
-
near_nodes: List of nearby nodes
|
|
152
|
-
|
|
153
|
-
Returns:
|
|
154
|
-
node_new: Updated new node with optimal parent
|
|
155
|
-
node_near: The chosen parent node
|
|
259
|
+
def _propagate_cost_to_children(self, node: Node):
|
|
156
260
|
"""
|
|
157
|
-
|
|
158
|
-
node_new.g = node_near.g + self.get_cost(node_near.current, node_new.current)
|
|
159
|
-
best_parent = node_near
|
|
160
|
-
|
|
161
|
-
# Check all nearby nodes for potentially lower-cost paths
|
|
162
|
-
for node_near_candidate in near_nodes:
|
|
163
|
-
# Skip if candidate is the same as initial nearest node
|
|
164
|
-
if node_near_candidate.current == best_parent.current:
|
|
165
|
-
continue
|
|
166
|
-
|
|
167
|
-
# Check if path from candidate to new node is collision-free
|
|
168
|
-
if self.map_.in_collision(node_near_candidate.current, node_new.current):
|
|
169
|
-
continue
|
|
170
|
-
|
|
171
|
-
# Calculate cost through this candidate parent
|
|
172
|
-
new_cost = node_near_candidate.g + self.get_cost(node_near_candidate.current, node_new.current)
|
|
173
|
-
|
|
174
|
-
# Update parent if this path is cheaper
|
|
175
|
-
if new_cost < node_new.g:
|
|
176
|
-
node_new.g = new_cost
|
|
177
|
-
best_parent = node_near_candidate
|
|
178
|
-
|
|
179
|
-
# Set the best parent for the new node
|
|
180
|
-
node_new.parent = best_parent.current
|
|
181
|
-
return node_new, best_parent
|
|
261
|
+
Propagate cost update to children of a node.
|
|
182
262
|
|
|
183
|
-
def _rewire(self, tree: Dict[tuple, Node], node_new: Node, near_nodes: List[Node]) -> None:
|
|
184
|
-
"""
|
|
185
|
-
Rewire the tree to potentially improve paths for existing nodes using the new node.
|
|
186
|
-
|
|
187
263
|
Args:
|
|
188
|
-
|
|
189
|
-
node_new: Newly added node
|
|
190
|
-
near_nodes: List of nearby nodes
|
|
264
|
+
node: Node to propagate cost to children
|
|
191
265
|
"""
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
node_near.parent = node_new.current
|
|
207
|
-
node_near.g = new_cost
|
|
208
|
-
# Update the node in the tree
|
|
209
|
-
tree[node_near.current] = node_near
|
|
266
|
+
child_set = self._child[node.current]
|
|
267
|
+
if child_set is None:
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
for child in child_set:
|
|
271
|
+
node_child = self._tree.get(child)
|
|
272
|
+
|
|
273
|
+
if node_child is not None:
|
|
274
|
+
old_g = node_child.g
|
|
275
|
+
node_child.g = node.g + self.get_cost(
|
|
276
|
+
node.current, node_child.current
|
|
277
|
+
)
|
|
278
|
+
if not math.isclose(node_child.g, old_g):
|
|
279
|
+
self._propagate_cost_to_children(node_child)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: base_curve_generator.py
|
|
3
|
+
@author: Yang Haodong, Wu Maojia
|
|
4
|
+
@update: 2026.4.12
|
|
5
|
+
"""
|
|
6
|
+
from typing import List, Tuple, Dict, Any
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
import math
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseCurveGenerator(ABC):
|
|
12
|
+
"""
|
|
13
|
+
Base class for curve generator (trajectory smoother).
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
step: Step size for interpolation or discretization along the curve.
|
|
17
|
+
"""
|
|
18
|
+
def __init__(self, step: float = 0.01) -> None:
|
|
19
|
+
super().__init__()
|
|
20
|
+
self.step = step
|
|
21
|
+
|
|
22
|
+
def __str__(self) -> str:
|
|
23
|
+
return "Base Curve Generator"
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def generate(self, points: List[Tuple[float, ...]]) -> Tuple[List[Tuple[float, ...]], Dict[str, Any]]:
|
|
27
|
+
"""
|
|
28
|
+
Interface for curve generation.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
points: A list of waypoints in world frame. The exact format (2D position
|
|
32
|
+
or 2D pose with orientation) depends on the concrete generator.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
path: A list containing the smoothed path waypoints in world frame.
|
|
36
|
+
curve_info: A dictionary containing the curve information (success, length).
|
|
37
|
+
"""
|
|
38
|
+
raise NotImplementedError
|
|
39
|
+
|
|
40
|
+
def length(self, path: List[Tuple[float, ...]]) -> float:
|
|
41
|
+
"""
|
|
42
|
+
Calculate the length of a path.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
path: A list containing the path waypoints.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
length: Length of the path.
|
|
49
|
+
"""
|
|
50
|
+
dist = 0.0
|
|
51
|
+
for i in range(len(path) - 1):
|
|
52
|
+
dist += math.hypot(path[i + 1][0] - path[i][0], path[i + 1][1] - path[i][1])
|
|
53
|
+
return dist
|