| 
 | 1 | +"""  | 
 | 2 | +
  | 
 | 3 | +Path planning Sample Code with Randomized Rapidly-Exploring Random  | 
 | 4 | +Trees with sobol low discrepancy sampler(RRTSobol).  | 
 | 5 | +Sobol wiki https://en.wikipedia.org/wiki/Sobol_sequence  | 
 | 6 | +
  | 
 | 7 | +The goal of low discrepancy samplers is to generate a sequence of points that  | 
 | 8 | +optimizes a criterion called dispersion.  Intuitively, the idea is to place  | 
 | 9 | +samples to cover the exploration space in a way that makes the largest  | 
 | 10 | +uncovered area be as small as possible.  This generalizes of the idea of grid  | 
 | 11 | +resolution.  For a grid, the resolution may be selected by defining the step  | 
 | 12 | +size for each axis.  As the step size is decreased, the resolution increases.  | 
 | 13 | +If a grid-based motion planning algorithm can increase the resolution  | 
 | 14 | +arbitrarily, it becomes resolution complete.  Dispersion can be considered as a  | 
 | 15 | +powerful generalization of the notion of resolution.  | 
 | 16 | +
  | 
 | 17 | +Taken from  | 
 | 18 | +LaValle, Steven M. Planning algorithms. Cambridge university press, 2006.  | 
 | 19 | +
  | 
 | 20 | +
  | 
 | 21 | +authors:  | 
 | 22 | +    First implementation AtsushiSakai(@Atsushi_twi)  | 
 | 23 | +    Sobol sampler Rafael A.  | 
 | 24 | + | 
 | 25 | +
  | 
 | 26 | +
  | 
 | 27 | +"""  | 
 | 28 | + | 
 | 29 | +import math  | 
 | 30 | +import random  | 
 | 31 | +from sobol import sobol_quasirand  | 
 | 32 | +import sys  | 
 | 33 | + | 
 | 34 | +import matplotlib.pyplot as plt  | 
 | 35 | +import numpy as np  | 
 | 36 | + | 
 | 37 | +show_animation = True  | 
 | 38 | + | 
 | 39 | + | 
 | 40 | +class RRTSobol:  | 
 | 41 | +    """  | 
 | 42 | +    Class for RRTSobol planning  | 
 | 43 | +    """  | 
 | 44 | + | 
 | 45 | +    class Node:  | 
 | 46 | +        """  | 
 | 47 | +        RRTSobol Node  | 
 | 48 | +        """  | 
 | 49 | + | 
 | 50 | +        def __init__(self, x, y):  | 
 | 51 | +            self.x = x  | 
 | 52 | +            self.y = y  | 
 | 53 | +            self.path_x = []  | 
 | 54 | +            self.path_y = []  | 
 | 55 | +            self.parent = None  | 
 | 56 | + | 
 | 57 | +    def __init__(self,  | 
 | 58 | +                 start,  | 
 | 59 | +                 goal,  | 
 | 60 | +                 obstacle_list,  | 
 | 61 | +                 rand_area,  | 
 | 62 | +                 expand_dis=3.0,  | 
 | 63 | +                 path_resolution=0.5,  | 
 | 64 | +                 goal_sample_rate=5,  | 
 | 65 | +                 max_iter=500):  | 
 | 66 | +        """  | 
 | 67 | +        Setting Parameter  | 
 | 68 | +
  | 
 | 69 | +        start:Start Position [x,y]  | 
 | 70 | +        goal:Goal Position [x,y]  | 
 | 71 | +        obstacle_list:obstacle Positions [[x,y,size],...]  | 
 | 72 | +        randArea:Random Sampling Area [min,max]  | 
 | 73 | +
  | 
 | 74 | +        """  | 
 | 75 | +        self.start = self.Node(start[0], start[1])  | 
 | 76 | +        self.end = self.Node(goal[0], goal[1])  | 
 | 77 | +        self.min_rand = rand_area[0]  | 
 | 78 | +        self.max_rand = rand_area[1]  | 
 | 79 | +        self.expand_dis = expand_dis  | 
 | 80 | +        self.path_resolution = path_resolution  | 
 | 81 | +        self.goal_sample_rate = goal_sample_rate  | 
 | 82 | +        self.max_iter = max_iter  | 
 | 83 | +        self.obstacle_list = obstacle_list  | 
 | 84 | +        self.node_list = []  | 
 | 85 | +        self.sobol_inter_ = 0  | 
 | 86 | + | 
 | 87 | +    def planning(self, animation=True):  | 
 | 88 | +        """  | 
 | 89 | +        rrt path planning  | 
 | 90 | +
  | 
 | 91 | +        animation: flag for animation on or off  | 
 | 92 | +        """  | 
 | 93 | + | 
 | 94 | +        self.node_list = [self.start]  | 
 | 95 | +        for i in range(self.max_iter):  | 
 | 96 | +            rnd_node = self.get_random_node()  | 
 | 97 | +            nearest_ind = self.get_nearest_node_index(self.node_list, rnd_node)  | 
 | 98 | +            nearest_node = self.node_list[nearest_ind]  | 
 | 99 | + | 
 | 100 | +            new_node = self.steer(nearest_node, rnd_node, self.expand_dis)  | 
 | 101 | + | 
 | 102 | +            if self.check_collision(new_node, self.obstacle_list):  | 
 | 103 | +                self.node_list.append(new_node)  | 
 | 104 | + | 
 | 105 | +            if animation and i % 5 == 0:  | 
 | 106 | +                self.draw_graph(rnd_node)  | 
 | 107 | + | 
 | 108 | +            if self.calc_dist_to_goal(self.node_list[-1].x,  | 
 | 109 | +                                      self.node_list[-1].y) <= self.expand_dis:  | 
 | 110 | +                final_node = self.steer(self.node_list[-1], self.end,  | 
 | 111 | +                                        self.expand_dis)  | 
 | 112 | +                if self.check_collision(final_node, self.obstacle_list):  | 
 | 113 | +                    return self.generate_final_course(len(self.node_list) - 1)  | 
 | 114 | + | 
 | 115 | +            if animation and i % 5:  | 
 | 116 | +                self.draw_graph(rnd_node)  | 
 | 117 | + | 
 | 118 | +        return None  # cannot find path  | 
 | 119 | + | 
 | 120 | +    def steer(self, from_node, to_node, extend_length=float("inf")):  | 
 | 121 | + | 
 | 122 | +        new_node = self.Node(from_node.x, from_node.y)  | 
 | 123 | +        d, theta = self.calc_distance_and_angle(new_node, to_node)  | 
 | 124 | + | 
 | 125 | +        new_node.path_x = [new_node.x]  | 
 | 126 | +        new_node.path_y = [new_node.y]  | 
 | 127 | + | 
 | 128 | +        if extend_length > d:  | 
 | 129 | +            extend_length = d  | 
 | 130 | + | 
 | 131 | +        n_expand = math.floor(extend_length / self.path_resolution)  | 
 | 132 | + | 
 | 133 | +        for _ in range(n_expand):  | 
 | 134 | +            new_node.x += self.path_resolution * math.cos(theta)  | 
 | 135 | +            new_node.y += self.path_resolution * math.sin(theta)  | 
 | 136 | +            new_node.path_x.append(new_node.x)  | 
 | 137 | +            new_node.path_y.append(new_node.y)  | 
 | 138 | + | 
 | 139 | +        d, _ = self.calc_distance_and_angle(new_node, to_node)  | 
 | 140 | +        if d <= self.path_resolution:  | 
 | 141 | +            new_node.path_x.append(to_node.x)  | 
 | 142 | +            new_node.path_y.append(to_node.y)  | 
 | 143 | +            new_node.x = to_node.x  | 
 | 144 | +            new_node.y = to_node.y  | 
 | 145 | + | 
 | 146 | +        new_node.parent = from_node  | 
 | 147 | + | 
 | 148 | +        return new_node  | 
 | 149 | + | 
 | 150 | +    def generate_final_course(self, goal_ind):  | 
 | 151 | +        path = [[self.end.x, self.end.y]]  | 
 | 152 | +        node = self.node_list[goal_ind]  | 
 | 153 | +        while node.parent is not None:  | 
 | 154 | +            path.append([node.x, node.y])  | 
 | 155 | +            node = node.parent  | 
 | 156 | +        path.append([node.x, node.y])  | 
 | 157 | + | 
 | 158 | +        return path  | 
 | 159 | + | 
 | 160 | +    def calc_dist_to_goal(self, x, y):  | 
 | 161 | +        dx = x - self.end.x  | 
 | 162 | +        dy = y - self.end.y  | 
 | 163 | +        return math.hypot(dx, dy)  | 
 | 164 | + | 
 | 165 | +    def get_random_node(self):  | 
 | 166 | +        if random.randint(0, 100) > self.goal_sample_rate:  | 
 | 167 | +            rand_coordinates, n = sobol_quasirand(2, self.sobol_inter_)  | 
 | 168 | + | 
 | 169 | +            rand_coordinates = self.min_rand + \  | 
 | 170 | +                rand_coordinates*(self.max_rand - self.min_rand)  | 
 | 171 | +            self.sobol_inter_ = n  | 
 | 172 | +            rnd = self.Node(*rand_coordinates)  | 
 | 173 | + | 
 | 174 | +        else:  # goal point sampling  | 
 | 175 | +            rnd = self.Node(self.end.x, self.end.y)  | 
 | 176 | +        return rnd  | 
 | 177 | + | 
 | 178 | +    def draw_graph(self, rnd=None):  | 
 | 179 | +        plt.clf()  | 
 | 180 | +        # for stopping simulation with the esc key.  | 
 | 181 | +        plt.gcf().canvas.mpl_connect(  | 
 | 182 | +            'key_release_event',  | 
 | 183 | +            lambda event: [sys.exit(0) if event.key == 'escape' else None])  | 
 | 184 | +        if rnd is not None:  | 
 | 185 | +            plt.plot(rnd.x, rnd.y, "^k")  | 
 | 186 | +        for node in self.node_list:  | 
 | 187 | +            if node.parent:  | 
 | 188 | +                plt.plot(node.path_x, node.path_y, "-g")  | 
 | 189 | + | 
 | 190 | +        for (ox, oy, size) in self.obstacle_list:  | 
 | 191 | +            self.plot_circle(ox, oy, size)  | 
 | 192 | + | 
 | 193 | +        plt.plot(self.start.x, self.start.y, "xr")  | 
 | 194 | +        plt.plot(self.end.x, self.end.y, "xr")  | 
 | 195 | +        plt.axis("equal")  | 
 | 196 | +        plt.axis([-2, 15, -2, 15])  | 
 | 197 | +        plt.grid(True)  | 
 | 198 | +        plt.pause(0.01)  | 
 | 199 | + | 
 | 200 | +    @staticmethod  | 
 | 201 | +    def plot_circle(x, y, size, color="-b"):  # pragma: no cover  | 
 | 202 | +        deg = list(range(0, 360, 5))  | 
 | 203 | +        deg.append(0)  | 
 | 204 | +        xl = [x + size * math.cos(np.deg2rad(d)) for d in deg]  | 
 | 205 | +        yl = [y + size * math.sin(np.deg2rad(d)) for d in deg]  | 
 | 206 | +        plt.plot(xl, yl, color)  | 
 | 207 | + | 
 | 208 | +    @staticmethod  | 
 | 209 | +    def get_nearest_node_index(node_list, rnd_node):  | 
 | 210 | +        dlist = [(node.x - rnd_node.x)**2 + (node.y - rnd_node.y)**2  | 
 | 211 | +                 for node in node_list]  | 
 | 212 | +        minind = dlist.index(min(dlist))  | 
 | 213 | + | 
 | 214 | +        return minind  | 
 | 215 | + | 
 | 216 | +    @staticmethod  | 
 | 217 | +    def check_collision(node, obstacle_list):  | 
 | 218 | + | 
 | 219 | +        if node is None:  | 
 | 220 | +            return False  | 
 | 221 | + | 
 | 222 | +        for (ox, oy, size) in obstacle_list:  | 
 | 223 | +            dx_list = [ox - x for x in node.path_x]  | 
 | 224 | +            dy_list = [oy - y for y in node.path_y]  | 
 | 225 | +            d_list = [dx * dx + dy * dy for (dx, dy) in zip(dx_list, dy_list)]  | 
 | 226 | + | 
 | 227 | +            if min(d_list) <= size**2:  | 
 | 228 | +                return False  # collision  | 
 | 229 | + | 
 | 230 | +        return True  # safe  | 
 | 231 | + | 
 | 232 | +    @staticmethod  | 
 | 233 | +    def calc_distance_and_angle(from_node, to_node):  | 
 | 234 | +        dx = to_node.x - from_node.x  | 
 | 235 | +        dy = to_node.y - from_node.y  | 
 | 236 | +        d = math.hypot(dx, dy)  | 
 | 237 | +        theta = math.atan2(dy, dx)  | 
 | 238 | +        return d, theta  | 
 | 239 | + | 
 | 240 | + | 
 | 241 | +def main(gx=6.0, gy=10.0):  | 
 | 242 | +    print("start " + __file__)  | 
 | 243 | + | 
 | 244 | +    # ====Search Path with RRTSobol====  | 
 | 245 | +    obstacle_list = [(5, 5, 1), (3, 6, 2), (3, 8, 2), (3, 10, 2), (7, 5, 2),  | 
 | 246 | +                     (9, 5, 2), (8, 10, 1)]  # [x, y, radius]  | 
 | 247 | +    # Set Initial parameters  | 
 | 248 | +    rrt = RRTSobol(  | 
 | 249 | +        start=[0, 0],  | 
 | 250 | +        goal=[gx, gy],  | 
 | 251 | +        rand_area=[-2, 15],  | 
 | 252 | +        obstacle_list=obstacle_list)  | 
 | 253 | +    path = rrt.planning(animation=show_animation)  | 
 | 254 | + | 
 | 255 | +    if path is None:  | 
 | 256 | +        print("Cannot find path")  | 
 | 257 | +    else:  | 
 | 258 | +        print("found path!!")  | 
 | 259 | + | 
 | 260 | +        # Draw final path  | 
 | 261 | +        if show_animation:  | 
 | 262 | +            rrt.draw_graph()  | 
 | 263 | +            plt.plot([x for (x, y) in path], [y for (x, y) in path], '-r')  | 
 | 264 | +            plt.grid(True)  | 
 | 265 | +            plt.pause(0.01)  # Need for Mac  | 
 | 266 | +            plt.show()  | 
 | 267 | + | 
 | 268 | + | 
 | 269 | +if __name__ == '__main__':  | 
 | 270 | +    main()  | 
0 commit comments