Skip to content

RRT

src.python_motion_planning.path_planner.sample_search.rrt.RRT

Bases: BasePathPlanner

Class for RRT (Rapidly-exploring Random Tree) path planner.

Parameters:

Name Type Description Default
*args

see the parent class.

()
max_dist float

Maximum expansion distance for each step (default: 1.0).

5.0
sample_num int

Maximum number of samples to generate (default: 10000).

100000
goal_sample_rate float

Probability of sampling the goal directly (default: 0.05).

0.05
*kwargs

see the parent class.

{}
References

[1] Rapidly-Exploring Random Trees: A New Tool for Path Planning

Examples:

Python Console Session
>>> map_ = Grid(bounds=[[0, 15], [0, 15]])
>>> planner = RRT(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.py
Python
class RRT(BasePathPlanner):
    """
    Class for RRT (Rapidly-exploring Random Tree) path planner.

    Args:
        *args: see the parent class.
        max_dist: Maximum expansion distance for each step (default: 1.0).
        sample_num: Maximum number of samples to generate (default: 10000).
        goal_sample_rate: Probability of sampling the goal directly (default: 0.05).
        *kwargs: see the parent class.

    References:
        [1] Rapidly-Exploring Random Trees: A New Tool for Path Planning

    Examples:
        >>> map_ = Grid(bounds=[[0, 15], [0, 15]])
        >>> planner = RRT(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, 
                 max_dist: float = 5.0, sample_num: int = 100000, 
                 goal_sample_rate: float = 0.05,
                 **kwargs) -> None:
        super().__init__(*args, **kwargs)
        # Maximum expansion distance per step
        self.max_dist = max_dist
        # Maximum number of samples
        self.sample_num = sample_num
        # Goal sampling probability
        self.goal_sample_rate = goal_sample_rate
        # Environment bounds from the map
        self.bounds = self.map_.bounds

    def __str__(self) -> str:
        return "Rapidly-exploring Random Tree (RRT)"

    def plan(self) -> Union[list, dict]:
        """
        RRT path planning algorithm implementation.

        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

            # Add new node to tree
            node_new.parent = node_near.current
            node_new.g = node_near.g + self.get_cost(node_near.current, node_new.current)
            tree[node_new.current] = node_new

            # 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):
                    if node_new.current == self.goal:
                        goal_node = node_new
                    else:
                        goal_node = Node(self.goal, node_new.current, 
                                        node_new.g + dist_to_goal, 0)
                    tree[self.goal] = goal_node
                    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 _generate_random_node(self) -> Node:
        """
        Generate a random node within map bounds as integer grid point.

        Returns:
            node: Generated random node on grid
        """
        # Sample goal directly with specified probability
        if random.random() < self.goal_sample_rate:
            return Node(self.goal, None, 0, 0)

        point = []
        # Generate random integer point within grid bounds
        for d in range(self.map_.dim):
            d_min, d_max = self.bounds[d]
            point.append(random.randint(int(d_min), int(d_max)))
        point = tuple(point)

        return Node(point, None, 0, 0)

    def _get_nearest_node(self, tree: Dict[Tuple[int, ...], Node], 
                         node_rand: Node) -> Node:
        """
        Find the nearest node in the tree to a random sample.

        Args:
            tree: Current tree of nodes
            node_rand: Random sample node

        Returns:
            node: Nearest node in the tree
        """
        min_dist = float('inf')
        nearest_node = None

        for node in tree.values():
            dist = self.get_cost(node.current, node_rand.current)
            if dist < min_dist:
                min_dist = dist
                nearest_node = node

        return nearest_node

    def _steer(self, node_near: Node, 
              node_rand: Node) -> Union[Node, None]:
        """
        Steer from nearest node towards random sample.

        Args:
            node_near: Nearest node in tree
            node_rand: Random sample node

        Returns:
            node: New node in direction of random sample
        """
        # Calculate differences for each dimension
        diffs = [node_rand.current[i] - node_near.current[i] for i in range(self.map_.dim)]

        # Calculate Euclidean distance in n-dimensional space
        dist = math.sqrt(sum(diff**2 for diff in diffs))

        # Handle case where nodes are coincident
        if math.isclose(dist, 0):
            return None

        # If within max distance, use the random node directly
        if dist <= self.max_dist:
            return node_rand

        # Otherwise scale to maximum distance
        scale = self.max_dist / dist
        new_coords = [
            node_near.current[i] + scale * diffs[i] 
            for i in range(self.map_.dim)
        ]

        # Round coordinates if original points were integers
        if all(isinstance(coord, int) for coord in node_near.current):
            new_coords = [round(coord) for coord in new_coords]

        return Node(tuple(new_coords), None, 0, 0)

plan()

RRT path planning algorithm implementation.

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.py
Python
def plan(self) -> Union[list, dict]:
    """
    RRT path planning algorithm implementation.

    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

        # Add new node to tree
        node_new.parent = node_near.current
        node_new.g = node_near.g + self.get_cost(node_near.current, node_new.current)
        tree[node_new.current] = node_new

        # 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):
                if node_new.current == self.goal:
                    goal_node = node_new
                else:
                    goal_node = Node(self.goal, node_new.current, 
                                    node_new.g + dist_to_goal, 0)
                tree[self.goal] = goal_node
                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