RRTStar¶
src.python_motion_planning.path_planner.sample_search.rrt_star.RRTStar
¶
Bases: RRT
Class for RRT* (Rapidly-exploring Random Tree Star) path planner.
RRT* is an optimized version of RRT that provides asymptotically optimal paths by rewiring the tree to maintain lower-cost connections.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
*args
|
see the parent class. |
()
|
|
radius
|
float
|
Radius for finding nearby nodes during rewiring. |
10.0
|
*kwargs
|
see the parent class. |
{}
|
References
[1] Sampling-based algorithms for optimal motion planning
Examples:
Python Console Session
>>> map_ = Grid(bounds=[[0, 15], [0, 15]])
>>> planner = RRTStar(map_=map_, start=(5, 5), goal=(10, 10))
>>> path, path_info = planner.plan()
>>> print(path_info['success'])
True
Python Console Session
>>> planner.map_.type_map[3:10, 6] = TYPES.OBSTACLE
>>> path, path_info = planner.plan()
>>> print(path_info['success'])
True
Source code in src\python_motion_planning\path_planner\sample_search\rrt_star.py
Python
class RRTStar(RRT):
"""
Class for RRT* (Rapidly-exploring Random Tree Star) path planner.
RRT* is an optimized version of RRT that provides asymptotically optimal paths
by rewiring the tree to maintain lower-cost connections.
Args:
*args: see the parent class.
radius: Radius for finding nearby nodes during rewiring.
*kwargs: see the parent class.
References:
[1] Sampling-based algorithms for optimal motion planning
Examples:
>>> map_ = Grid(bounds=[[0, 15], [0, 15]])
>>> planner = RRTStar(map_=map_, start=(5, 5), goal=(10, 10))
>>> path, path_info = planner.plan()
>>> print(path_info['success'])
True
>>> planner.map_.type_map[3:10, 6] = TYPES.OBSTACLE
>>> path, path_info = planner.plan()
>>> print(path_info['success'])
True
"""
def __init__(self, *args,
radius: float = 10.0,** kwargs) -> None:
super().__init__(*args, **kwargs)
# Radius for finding nearby nodes during rewiring phase
self.radius = radius
def __str__(self) -> str:
return "Rapidly-exploring Random Tree Star (RRT*)"
def plan(self) -> Union[list, dict]:
"""
RRT* path planning algorithm implementation with optimality properties.
Returns:
path: A list containing the path waypoints
path_info: A dictionary containing path information
"""
# Initialize tree with start node
tree = {}
start_node = Node(self.start, None, 0, 0)
tree[self.start] = start_node
# Main sampling loop
for _ in range(self.sample_num):
# Generate random sample node
node_rand = self._generate_random_node()
# Skip if node already exists
if node_rand.current in tree:
continue
# Find nearest node in tree
node_near = self._get_nearest_node(tree, node_rand)
# Create new node towards random sample
node_new = self._steer(node_near, node_rand)
if node_new is None:
continue
# Check if edge is collision-free
if self.map_.in_collision(node_new.current, node_near.current):
continue
# Find all nearby nodes within radius
near_nodes = self._find_near_nodes(tree, node_new)
# Select optimal parent from nearby nodes
node_new, node_near = self._choose_parent(node_new, node_near, near_nodes)
if node_new is None:
continue
# Add new node to tree
tree[node_new.current] = node_new
# Rewire tree to potentially improve existing paths
self._rewire(tree, node_new, near_nodes)
# Check if goal is reachable
dist_to_goal = self.get_cost(node_new.current, self.goal)
if dist_to_goal <= self.max_dist:
# Check final edge to goal
if not self.map_.in_collision(node_new.current, self.goal):
goal_cost = node_new.g + dist_to_goal
# Update goal node if it already exists with a lower cost path
if self.goal in tree:
if goal_cost < tree[self.goal].g:
tree[self.goal].parent = node_new.current
tree[self.goal].g = goal_cost
else:
goal_node = Node(self.goal, node_new.current, goal_cost, 0)
tree[self.goal] = goal_node
# Extract and return the path
path, length, cost = self.extract_path(tree)
return path, {
"success": True,
"start": self.start,
"goal": self.goal,
"length": length,
"cost": cost,
"expand": tree,
}
# Planning failed
self.failed_info[1]["expand"] = tree
return self.failed_info
def _find_near_nodes(self, tree: Dict[tuple, Node], node_new: Node) -> List[Node]:
"""
Find all nodes in the tree within a specified radius of the new node.
Args:
tree: Current tree of nodes
node_new: Newly created node
Returns:
near_nodes: List of nodes within the specified radius
"""
near_nodes = []
for node in tree.values():
if self.get_cost(node.current, node_new.current) <= self.radius:
near_nodes.append(node)
return near_nodes
def _choose_parent(self, node_new: Node, node_near: Node, near_nodes: List[Node]) -> Tuple[Union[Node, None], Node]:
"""
Select the optimal parent for the new node from nearby nodes to minimize cost.
Args:
node_new: Newly created node
node_near: Nearest node in the tree
near_nodes: List of nearby nodes
Returns:
node_new: Updated new node with optimal parent
node_near: The chosen parent node
"""
# Initialize with nearest node as potential parent
node_new.g = node_near.g + self.get_cost(node_near.current, node_new.current)
best_parent = node_near
# Check all nearby nodes for potentially lower-cost paths
for node_near_candidate in near_nodes:
# Skip if candidate is the same as initial nearest node
if node_near_candidate.current == best_parent.current:
continue
# Check if path from candidate to new node is collision-free
if self.map_.in_collision(node_near_candidate.current, node_new.current):
continue
# Calculate cost through this candidate parent
new_cost = node_near_candidate.g + self.get_cost(node_near_candidate.current, node_new.current)
# Update parent if this path is cheaper
if new_cost < node_new.g:
node_new.g = new_cost
best_parent = node_near_candidate
# Set the best parent for the new node
node_new.parent = best_parent.current
return node_new, best_parent
def _rewire(self, tree: Dict[tuple, Node], node_new: Node, near_nodes: List[Node]) -> None:
"""
Rewire the tree to potentially improve paths for existing nodes using the new node.
Args:
tree: Current tree of nodes
node_new: Newly added node
near_nodes: List of nearby nodes
"""
for node_near in near_nodes:
# Skip if node is the parent of the new node
if node_near.current == node_new.parent:
continue
# Check if path from new node to existing node is collision-free
if self.map_.in_collision(node_new.current, node_near.current):
continue
# Calculate potential new cost for existing node through new node
new_cost = node_new.g + self.get_cost(node_new.current, node_near.current)
# Update parent if new path is cheaper
if new_cost < node_near.g:
node_near.parent = node_new.current
node_near.g = new_cost
# Update the node in the tree
tree[node_near.current] = node_near
plan()
¶
RRT* path planning algorithm implementation with optimality properties.
Returns:
Name | Type | Description |
---|---|---|
path |
Union[list, dict]
|
A list containing the path waypoints |
path_info |
Union[list, dict]
|
A dictionary containing path information |
Source code in src\python_motion_planning\path_planner\sample_search\rrt_star.py
Python
def plan(self) -> Union[list, dict]:
"""
RRT* path planning algorithm implementation with optimality properties.
Returns:
path: A list containing the path waypoints
path_info: A dictionary containing path information
"""
# Initialize tree with start node
tree = {}
start_node = Node(self.start, None, 0, 0)
tree[self.start] = start_node
# Main sampling loop
for _ in range(self.sample_num):
# Generate random sample node
node_rand = self._generate_random_node()
# Skip if node already exists
if node_rand.current in tree:
continue
# Find nearest node in tree
node_near = self._get_nearest_node(tree, node_rand)
# Create new node towards random sample
node_new = self._steer(node_near, node_rand)
if node_new is None:
continue
# Check if edge is collision-free
if self.map_.in_collision(node_new.current, node_near.current):
continue
# Find all nearby nodes within radius
near_nodes = self._find_near_nodes(tree, node_new)
# Select optimal parent from nearby nodes
node_new, node_near = self._choose_parent(node_new, node_near, near_nodes)
if node_new is None:
continue
# Add new node to tree
tree[node_new.current] = node_new
# Rewire tree to potentially improve existing paths
self._rewire(tree, node_new, near_nodes)
# Check if goal is reachable
dist_to_goal = self.get_cost(node_new.current, self.goal)
if dist_to_goal <= self.max_dist:
# Check final edge to goal
if not self.map_.in_collision(node_new.current, self.goal):
goal_cost = node_new.g + dist_to_goal
# Update goal node if it already exists with a lower cost path
if self.goal in tree:
if goal_cost < tree[self.goal].g:
tree[self.goal].parent = node_new.current
tree[self.goal].g = goal_cost
else:
goal_node = Node(self.goal, node_new.current, goal_cost, 0)
tree[self.goal] = goal_node
# Extract and return the path
path, length, cost = self.extract_path(tree)
return path, {
"success": True,
"start": self.start,
"goal": self.goal,
"length": length,
"cost": cost,
"expand": tree,
}
# Planning failed
self.failed_info[1]["expand"] = tree
return self.failed_info