Skip to content

ThetaStar

src.python_motion_planning.global_planner.graph_search.theta_star.ThetaStar

Bases: AStar

Class for Theta* motion planning.

Parameters:

Name Type Description Default
start tuple

start point coordinate

required
goal tuple

goal point coordinate

required
env Env

environment

required
heuristic_type str

heuristic function type

'euclidean'

Examples:

Python Console Session
>>> import python_motion_planning as pmp
>>> planner = pmp.ThetaStar((5, 5), (45, 25), pmp.Grid(51, 31))
>>> cost, path, expand = planner.plan()
>>> planner.plot.animation(path, str(planner), cost, expand)  # animation
>>> planner.run()       # run both planning and animation
References

[1] Theta*: Any-Angle Path Planning on Grids [2] Any-angle path planning on non-uniform costmaps

Source code in src\python_motion_planning\global_planner\graph_search\theta_star.py
Python
class ThetaStar(AStar):
    """
    Class for Theta* motion planning.

    Parameters:
        start (tuple):
            start point coordinate
        goal (tuple):
            goal point coordinate
        env (Env):
            environment
        heuristic_type (str):
            heuristic function type

    Examples:
        >>> import python_motion_planning as pmp
        >>> planner = pmp.ThetaStar((5, 5), (45, 25), pmp.Grid(51, 31))
        >>> cost, path, expand = planner.plan()
        >>> planner.plot.animation(path, str(planner), cost, expand)  # animation
        >>> planner.run()       # run both planning and animation

    References:
        [1] Theta*: Any-Angle Path Planning on Grids
        [2] Any-angle path planning on non-uniform costmaps
    """
    def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean") -> None:
        super().__init__(start, goal, env, heuristic_type)

    def __str__(self) -> str:
        return "Theta*"

    def plan(self) -> tuple:
        """
        Theta* motion plan function.

        Returns:
            cost (float): path cost
            path (list): planning path
            expand (list): all nodes that planner has searched
        """
        # OPEN list (priority queue) and CLOSED list (hash table)
        OPEN = []
        heapq.heappush(OPEN, self.start)
        CLOSED = dict()

        while OPEN:
            node = heapq.heappop(OPEN)

            # exists in CLOSED list
            if node.current in CLOSED:
                continue

            # goal found
            if node == self.goal:
                CLOSED[node.current] = node
                cost, path = self.extractPath(CLOSED)
                return cost, path, list(CLOSED.values())

            for node_n in self.getNeighbor(node):                
                # exists in CLOSED list
                if node_n.current in CLOSED:
                    continue

                # path1
                node_n.parent = node.current
                node_n.h = self.h(node_n, self.goal)

                node_p = CLOSED.get(node.parent)

                if node_p:
                    self.updateVertex(node_p, node_n)

                # goal found
                if node_n == self.goal:
                    heapq.heappush(OPEN, node_n)
                    break

                # update OPEN list
                heapq.heappush(OPEN, node_n)

            CLOSED[node.current] = node
        return [], [], []

    def updateVertex(self, node_p: Node, node_c: Node) -> None:
        """
        Update extend node information with current node's parent node.

        Parameters:
            node_p (Node): parent node
            node_c (Node): current node
        """
        if self.lineOfSight(node_c, node_p):
            # path 2
            if node_p.g + self.dist(node_c, node_p) <= node_c.g:
                node_c.g = node_p.g + self.dist(node_c, node_p)
                node_c.parent = node_p.current

    def lineOfSight(self, node1: Node, node2: Node) -> bool:
        """
        Judge collision when moving from node1 to node2 using Bresenham.

        Parameters:
            node1 (Node): start node
            node2 (Node): end node

        Returns:
            line_of_sight (bool): True if line of sight exists ( no collision ) else False
        """
        if node1.current in self.obstacles or node2.current in self.obstacles:
            return False

        x1, y1 = node1.current
        x2, y2 = node2.current

        if x1 < 0 or x1 >= self.env.x_range or y1 < 0 or y1 >= self.env.y_range:
            return False
        if x2 < 0 or x2 >= self.env.x_range or y2 < 0 or y2 >= self.env.y_range:
            return False

        d_x = abs(x2 - x1)
        d_y = abs(y2 - y1)
        s_x = 0 if (x2 - x1) == 0 else (x2 - x1) / d_x
        s_y = 0 if (y2 - y1) == 0 else (y2 - y1) / d_y
        x, y, e = x1, y1, 0

        # check if any obstacle exists between node1 and node2
        if d_x > d_y:
            tau = (d_y - d_x) / 2
            while not x == x2:
                if e > tau:
                    x = x + s_x
                    e = e - d_y
                elif e < tau:
                    y = y + s_y
                    e = e + d_x
                else:
                    x = x + s_x
                    y = y + s_y
                    e = e + d_x - d_y
                if (x, y) in self.obstacles:
                    return False
        # swap x and y
        else:
            tau = (d_x - d_y) / 2
            while not y == y2:
                if e > tau:
                    y = y + s_y
                    e = e - d_x
                elif e < tau:
                    x = x + s_x
                    e = e + d_y
                else:
                    x = x + s_x
                    y = y + s_y
                    e = e + d_y - d_x
                if (x, y) in self.obstacles:
                    return False

        return True

lineOfSight(node1, node2)

Judge collision when moving from node1 to node2 using Bresenham.

Parameters:

Name Type Description Default
node1 Node

start node

required
node2 Node

end node

required

Returns:

Name Type Description
line_of_sight bool

True if line of sight exists ( no collision ) else False

Source code in src\python_motion_planning\global_planner\graph_search\theta_star.py
Python
def lineOfSight(self, node1: Node, node2: Node) -> bool:
    """
    Judge collision when moving from node1 to node2 using Bresenham.

    Parameters:
        node1 (Node): start node
        node2 (Node): end node

    Returns:
        line_of_sight (bool): True if line of sight exists ( no collision ) else False
    """
    if node1.current in self.obstacles or node2.current in self.obstacles:
        return False

    x1, y1 = node1.current
    x2, y2 = node2.current

    if x1 < 0 or x1 >= self.env.x_range or y1 < 0 or y1 >= self.env.y_range:
        return False
    if x2 < 0 or x2 >= self.env.x_range or y2 < 0 or y2 >= self.env.y_range:
        return False

    d_x = abs(x2 - x1)
    d_y = abs(y2 - y1)
    s_x = 0 if (x2 - x1) == 0 else (x2 - x1) / d_x
    s_y = 0 if (y2 - y1) == 0 else (y2 - y1) / d_y
    x, y, e = x1, y1, 0

    # check if any obstacle exists between node1 and node2
    if d_x > d_y:
        tau = (d_y - d_x) / 2
        while not x == x2:
            if e > tau:
                x = x + s_x
                e = e - d_y
            elif e < tau:
                y = y + s_y
                e = e + d_x
            else:
                x = x + s_x
                y = y + s_y
                e = e + d_x - d_y
            if (x, y) in self.obstacles:
                return False
    # swap x and y
    else:
        tau = (d_x - d_y) / 2
        while not y == y2:
            if e > tau:
                y = y + s_y
                e = e - d_x
            elif e < tau:
                x = x + s_x
                e = e + d_y
            else:
                x = x + s_x
                y = y + s_y
                e = e + d_y - d_x
            if (x, y) in self.obstacles:
                return False

    return True

plan()

Theta* motion plan function.

Returns:

Name Type Description
cost float

path cost

path list

planning path

expand list

all nodes that planner has searched

Source code in src\python_motion_planning\global_planner\graph_search\theta_star.py
Python
def plan(self) -> tuple:
    """
    Theta* motion plan function.

    Returns:
        cost (float): path cost
        path (list): planning path
        expand (list): all nodes that planner has searched
    """
    # OPEN list (priority queue) and CLOSED list (hash table)
    OPEN = []
    heapq.heappush(OPEN, self.start)
    CLOSED = dict()

    while OPEN:
        node = heapq.heappop(OPEN)

        # exists in CLOSED list
        if node.current in CLOSED:
            continue

        # goal found
        if node == self.goal:
            CLOSED[node.current] = node
            cost, path = self.extractPath(CLOSED)
            return cost, path, list(CLOSED.values())

        for node_n in self.getNeighbor(node):                
            # exists in CLOSED list
            if node_n.current in CLOSED:
                continue

            # path1
            node_n.parent = node.current
            node_n.h = self.h(node_n, self.goal)

            node_p = CLOSED.get(node.parent)

            if node_p:
                self.updateVertex(node_p, node_n)

            # goal found
            if node_n == self.goal:
                heapq.heappush(OPEN, node_n)
                break

            # update OPEN list
            heapq.heappush(OPEN, node_n)

        CLOSED[node.current] = node
    return [], [], []

updateVertex(node_p, node_c)

Update extend node information with current node's parent node.

Parameters:

Name Type Description Default
node_p Node

parent node

required
node_c Node

current node

required
Source code in src\python_motion_planning\global_planner\graph_search\theta_star.py
Python
def updateVertex(self, node_p: Node, node_c: Node) -> None:
    """
    Update extend node information with current node's parent node.

    Parameters:
        node_p (Node): parent node
        node_c (Node): current node
    """
    if self.lineOfSight(node_c, node_p):
        # path 2
        if node_p.g + self.dist(node_c, node_p) <= node_c.g:
            node_c.g = node_p.g + self.dist(node_c, node_p)
            node_c.parent = node_p.current