python-motion-planning 1.0__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.
- curve_generation/__init__.py +9 -0
- curve_generation/bezier_curve.py +131 -0
- curve_generation/bspline_curve.py +271 -0
- curve_generation/cubic_spline.py +128 -0
- curve_generation/curve.py +64 -0
- curve_generation/dubins_curve.py +348 -0
- curve_generation/fem_pos_smooth.py +114 -0
- curve_generation/polynomial_curve.py +226 -0
- curve_generation/reeds_shepp.py +736 -0
- global_planner/__init__.py +3 -0
- global_planner/evolutionary_search/__init__.py +4 -0
- global_planner/evolutionary_search/aco.py +186 -0
- global_planner/evolutionary_search/evolutionary_search.py +87 -0
- global_planner/evolutionary_search/pso.py +356 -0
- global_planner/graph_search/__init__.py +28 -0
- global_planner/graph_search/a_star.py +124 -0
- global_planner/graph_search/d_star.py +291 -0
- global_planner/graph_search/d_star_lite.py +188 -0
- global_planner/graph_search/dijkstra.py +77 -0
- global_planner/graph_search/gbfs.py +78 -0
- global_planner/graph_search/graph_search.py +87 -0
- global_planner/graph_search/jps.py +165 -0
- global_planner/graph_search/lazy_theta_star.py +114 -0
- global_planner/graph_search/lpa_star.py +230 -0
- global_planner/graph_search/s_theta_star.py +133 -0
- global_planner/graph_search/theta_star.py +171 -0
- global_planner/graph_search/voronoi.py +200 -0
- global_planner/sample_search/__init__.py +6 -0
- global_planner/sample_search/informed_rrt.py +152 -0
- global_planner/sample_search/rrt.py +151 -0
- global_planner/sample_search/rrt_connect.py +147 -0
- global_planner/sample_search/rrt_star.py +77 -0
- global_planner/sample_search/sample_search.py +135 -0
- local_planner/__init__.py +19 -0
- local_planner/apf.py +144 -0
- local_planner/ddpg.py +630 -0
- local_planner/dqn.py +687 -0
- local_planner/dwa.py +212 -0
- local_planner/local_planner.py +262 -0
- local_planner/lqr.py +146 -0
- local_planner/mpc.py +214 -0
- local_planner/pid.py +158 -0
- local_planner/rpp.py +147 -0
- python_motion_planning-1.0.dist-info/LICENSE +674 -0
- python_motion_planning-1.0.dist-info/METADATA +873 -0
- python_motion_planning-1.0.dist-info/RECORD +65 -0
- python_motion_planning-1.0.dist-info/WHEEL +5 -0
- python_motion_planning-1.0.dist-info/top_level.txt +4 -0
- utils/__init__.py +19 -0
- utils/agent/__init__.py +0 -0
- utils/agent/agent.py +135 -0
- utils/environment/__init__.py +0 -0
- utils/environment/env.py +134 -0
- utils/environment/node.py +85 -0
- utils/environment/point2d.py +96 -0
- utils/environment/pose2d.py +91 -0
- utils/helper/__init__.py +3 -0
- utils/helper/math_helper.py +65 -0
- utils/planner/__init__.py +0 -0
- utils/planner/control_factory.py +31 -0
- utils/planner/curve_factory.py +29 -0
- utils/planner/planner.py +40 -0
- utils/planner/search_factory.py +51 -0
- utils/plot/__init__.py +0 -0
- utils/plot/plot.py +274 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: aco.py
|
|
3
|
+
@breif: Ant Colony Optimization(ACO) motion planning
|
|
4
|
+
@author: Yang Haodong, Wu Maojia
|
|
5
|
+
@update: 2024.6.23
|
|
6
|
+
"""
|
|
7
|
+
import random
|
|
8
|
+
from bisect import bisect_left
|
|
9
|
+
|
|
10
|
+
from .evolutionary_search import EvolutionarySearcher
|
|
11
|
+
from python_motion_planning.utils import Env, Node
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ACO(EvolutionarySearcher):
|
|
15
|
+
"""
|
|
16
|
+
Class for Ant Colony Optimization(ACO) motion planning.
|
|
17
|
+
|
|
18
|
+
Parameters:
|
|
19
|
+
start (tuple): start point coordinate
|
|
20
|
+
goal (tuple): goal point coordinate
|
|
21
|
+
env (Env): environment
|
|
22
|
+
heuristic_type (str): heuristic function type, default is euclidean
|
|
23
|
+
n_ants (int): number of ants
|
|
24
|
+
alpha (float): pheromone and heuristic factor weight coefficient
|
|
25
|
+
beta (float): pheromone and heuristic factor weight coefficient
|
|
26
|
+
rho (float): evaporation coefficient
|
|
27
|
+
Q (float): pheromone gain
|
|
28
|
+
max_iter (int): maximum iterations
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
>>> import python_motion_planning as pmp
|
|
32
|
+
>>> planner = pmp.ACO((5, 5), (45, 25), pmp.Grid(51, 31))
|
|
33
|
+
>>> cost, path, cost_list = planner.plan() # planning results only
|
|
34
|
+
>>> planner.plot.animation(path, str(planner), cost, cost_curve=cost_list) # animation
|
|
35
|
+
>>> planner.run() # run both planning and animation
|
|
36
|
+
|
|
37
|
+
References:
|
|
38
|
+
[1] Ant Colony Optimization: A New Meta-Heuristic
|
|
39
|
+
"""
|
|
40
|
+
def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean",
|
|
41
|
+
n_ants: int = 50, alpha: float = 1.0, beta: float = 5.0, rho: float = 0.1, Q: float = 1.0,
|
|
42
|
+
max_iter: int = 100) -> None:
|
|
43
|
+
super().__init__(start, goal, env, heuristic_type)
|
|
44
|
+
self.n_ants = n_ants
|
|
45
|
+
self.alpha = alpha
|
|
46
|
+
self.beta = beta
|
|
47
|
+
self.rho = rho
|
|
48
|
+
self.Q = Q
|
|
49
|
+
self.max_iter = max_iter
|
|
50
|
+
|
|
51
|
+
def __str__(self) -> str:
|
|
52
|
+
return "Ant Colony Optimization(ACO)"
|
|
53
|
+
|
|
54
|
+
class Ant:
|
|
55
|
+
def __init__(self) -> None:
|
|
56
|
+
self.reset()
|
|
57
|
+
|
|
58
|
+
def reset(self) -> None:
|
|
59
|
+
self.found_goal = False
|
|
60
|
+
self.current_node = None
|
|
61
|
+
self.path = []
|
|
62
|
+
self.path_set = set()
|
|
63
|
+
self.steps = 0
|
|
64
|
+
|
|
65
|
+
def plan(self) -> tuple:
|
|
66
|
+
"""
|
|
67
|
+
Ant Colony Optimization(ACO) motion plan function.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
cost (float): path cost
|
|
71
|
+
path (list): planning path
|
|
72
|
+
"""
|
|
73
|
+
best_length_list, best_path = [], None
|
|
74
|
+
|
|
75
|
+
# pheromone initialization
|
|
76
|
+
pheromone_edges = {}
|
|
77
|
+
for i in range(self.env.x_range):
|
|
78
|
+
for j in range(self.env.y_range):
|
|
79
|
+
if (i, j) in self.obstacles:
|
|
80
|
+
continue
|
|
81
|
+
cur_node = Node((i, j), (i, j), 0, 0)
|
|
82
|
+
for node_n in self.getNeighbor(cur_node):
|
|
83
|
+
pheromone_edges[(cur_node, node_n)] = 1.0
|
|
84
|
+
|
|
85
|
+
# heuristically set max steps
|
|
86
|
+
max_steps = self.env.x_range * self.env.y_range / 2 + max(self.env.x_range, self.env.y_range)
|
|
87
|
+
|
|
88
|
+
# main loop
|
|
89
|
+
cost_list = []
|
|
90
|
+
for _ in range(self.max_iter):
|
|
91
|
+
ants_list = []
|
|
92
|
+
for _ in range(self.n_ants):
|
|
93
|
+
ant = self.Ant()
|
|
94
|
+
ant.current_node = self.start
|
|
95
|
+
while ant.current_node is not self.goal and ant.steps < max_steps:
|
|
96
|
+
ant.path.append(ant.current_node)
|
|
97
|
+
ant.path_set.add(ant.current_node.current)
|
|
98
|
+
|
|
99
|
+
# candidate
|
|
100
|
+
prob_sum = 0.0
|
|
101
|
+
next_positions, next_probabilities = [], []
|
|
102
|
+
for node_n in self.getNeighbor(ant.current_node):
|
|
103
|
+
# existed
|
|
104
|
+
if node_n.current in ant.path_set:
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
node_n.parent = ant.current_node.current
|
|
108
|
+
|
|
109
|
+
# goal found
|
|
110
|
+
if node_n == self.goal:
|
|
111
|
+
ant.path.append(node_n)
|
|
112
|
+
ant.path_set.add(node_n.current)
|
|
113
|
+
ant.found_goal = True
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
next_positions.append(node_n)
|
|
117
|
+
prob_new = pheromone_edges[(ant.current_node, node_n)] ** self.alpha \
|
|
118
|
+
* (1.0 / self.h(node_n, self.goal)) ** self.beta
|
|
119
|
+
next_probabilities.append(prob_new)
|
|
120
|
+
prob_sum = prob_sum + prob_new
|
|
121
|
+
|
|
122
|
+
if prob_sum == 0 or ant.found_goal:
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
# roulette selection
|
|
126
|
+
next_probabilities = list(map(lambda prob: prob / prob_sum, next_probabilities))
|
|
127
|
+
p0, cp = 0, []
|
|
128
|
+
for prob in next_probabilities:
|
|
129
|
+
p0 = p0 + prob
|
|
130
|
+
cp.append(p0)
|
|
131
|
+
ant.current_node = next_positions[bisect_left(cp, random.random())]
|
|
132
|
+
|
|
133
|
+
ant.steps = ant.steps + 1
|
|
134
|
+
|
|
135
|
+
ants_list.append(ant)
|
|
136
|
+
|
|
137
|
+
# pheromone deterioration
|
|
138
|
+
for key, _ in pheromone_edges.items():
|
|
139
|
+
pheromone_edges[key] *= (1 - self.rho)
|
|
140
|
+
|
|
141
|
+
# pheromone update based on successful ants
|
|
142
|
+
bpl, bp = float("inf"), None
|
|
143
|
+
for ant in ants_list:
|
|
144
|
+
if ant.found_goal:
|
|
145
|
+
if len(ant.path) < bpl:
|
|
146
|
+
bpl, bp = len(ant.path), ant.path
|
|
147
|
+
c = self.Q / len(ant.path)
|
|
148
|
+
for i in range(len(ant.path) - 1):
|
|
149
|
+
pheromone_edges[(ant.path[i], ant.path[i + 1])] += c
|
|
150
|
+
|
|
151
|
+
if bpl < float("inf"):
|
|
152
|
+
best_length_list.append(bpl)
|
|
153
|
+
|
|
154
|
+
if len(best_length_list) > 0:
|
|
155
|
+
cost_list.append(min(best_length_list))
|
|
156
|
+
if bpl <= min(best_length_list):
|
|
157
|
+
best_path = bp
|
|
158
|
+
|
|
159
|
+
if best_path:
|
|
160
|
+
cost = 0
|
|
161
|
+
path = [self.start.current]
|
|
162
|
+
for i in range(len(best_path) - 1):
|
|
163
|
+
cost += self.dist(best_path[i], best_path[i + 1])
|
|
164
|
+
path.append(best_path[i + 1].current)
|
|
165
|
+
return cost, path, cost_list
|
|
166
|
+
return [], [], []
|
|
167
|
+
|
|
168
|
+
def getNeighbor(self, node: Node) -> list:
|
|
169
|
+
"""
|
|
170
|
+
Find neighbors of node.
|
|
171
|
+
|
|
172
|
+
Parameters:
|
|
173
|
+
node (Node): current node
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
neighbors (list): neighbors of current node
|
|
177
|
+
"""
|
|
178
|
+
return [node + motion for motion in self.motions
|
|
179
|
+
if not self.isCollision(node, node + motion)]
|
|
180
|
+
|
|
181
|
+
def run(self) -> None:
|
|
182
|
+
"""
|
|
183
|
+
Running both plannig and animation.
|
|
184
|
+
"""
|
|
185
|
+
cost, path, cost_list = self.plan()
|
|
186
|
+
self.plot.animation(path, str(self), cost, cost_curve=cost_list)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: evolutionary_search.py
|
|
3
|
+
@breif: Base class for planner based on evolutionary searching
|
|
4
|
+
@author: Winter
|
|
5
|
+
@update: 2023.7.13
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
|
|
9
|
+
from python_motion_planning.utils import Env, Node, Planner
|
|
10
|
+
|
|
11
|
+
class EvolutionarySearcher(Planner):
|
|
12
|
+
"""
|
|
13
|
+
Base class for planner based on evolutionary searching.
|
|
14
|
+
|
|
15
|
+
Parameters:
|
|
16
|
+
start (tuple): start point coordinate
|
|
17
|
+
goal (tuple): goal point coordinate
|
|
18
|
+
env (Env): environment
|
|
19
|
+
heuristic_type (str): heuristic function type
|
|
20
|
+
"""
|
|
21
|
+
def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str="euclidean") -> None:
|
|
22
|
+
super().__init__(start, goal, env)
|
|
23
|
+
# heuristic type
|
|
24
|
+
self.heuristic_type = heuristic_type
|
|
25
|
+
# allowed motions
|
|
26
|
+
self.motions = self.env.motions
|
|
27
|
+
# obstacles
|
|
28
|
+
self.obstacles = self.env.obstacles
|
|
29
|
+
|
|
30
|
+
def h(self, node: Node, goal: Node) -> float:
|
|
31
|
+
"""
|
|
32
|
+
Calculate heuristic.
|
|
33
|
+
|
|
34
|
+
Parameters:
|
|
35
|
+
node (Node): current node
|
|
36
|
+
goal (Node): goal node
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
h (float): heuristic function value of node
|
|
40
|
+
"""
|
|
41
|
+
if self.heuristic_type == "manhattan":
|
|
42
|
+
return abs(goal.x - node.x) + abs(goal.y - node.y)
|
|
43
|
+
elif self.heuristic_type == "euclidean":
|
|
44
|
+
return math.hypot(goal.x - node.x, goal.y - node.y)
|
|
45
|
+
|
|
46
|
+
def cost(self, node1: Node, node2: Node) -> float:
|
|
47
|
+
"""
|
|
48
|
+
Calculate cost for this motion.
|
|
49
|
+
|
|
50
|
+
Parameters:
|
|
51
|
+
node1 (Node): node 1
|
|
52
|
+
node2 (Node): node 2
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
cost (float): cost of this motion
|
|
56
|
+
"""
|
|
57
|
+
if self.isCollision(node1, node2):
|
|
58
|
+
return float("inf")
|
|
59
|
+
return self.dist(node1, node2)
|
|
60
|
+
|
|
61
|
+
def isCollision(self, node1: Node, node2: Node) -> bool:
|
|
62
|
+
"""
|
|
63
|
+
Judge collision when moving from node1 to node2.
|
|
64
|
+
|
|
65
|
+
Parameters:
|
|
66
|
+
node1 (Node): node 1
|
|
67
|
+
node2 (Node): node 2
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
collision (bool): True if collision exists else False
|
|
71
|
+
"""
|
|
72
|
+
if node1.current in self.obstacles or node2.current in self.obstacles:
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
x1, y1 = node1.current
|
|
76
|
+
x2, y2 = node2.current
|
|
77
|
+
|
|
78
|
+
if x1 != x2 and y1 != y2:
|
|
79
|
+
if x2 - x1 == y1 - y2:
|
|
80
|
+
s1 = (min(x1, x2), min(y1, y2))
|
|
81
|
+
s2 = (max(x1, x2), max(y1, y2))
|
|
82
|
+
else:
|
|
83
|
+
s1 = (min(x1, x2), max(y1, y2))
|
|
84
|
+
s2 = (max(x1, x2), min(y1, y2))
|
|
85
|
+
if s1 in self.obstacles or s2 in self.obstacles:
|
|
86
|
+
return True
|
|
87
|
+
return False
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@file: pso.py
|
|
3
|
+
@breif: Particle Swarm Optimization (PSO) motion planning
|
|
4
|
+
@author: Yang Haodong, Wu Maojia
|
|
5
|
+
@update: 2024.6.24
|
|
6
|
+
"""
|
|
7
|
+
import random, math
|
|
8
|
+
from copy import deepcopy
|
|
9
|
+
|
|
10
|
+
from .evolutionary_search import EvolutionarySearcher
|
|
11
|
+
from python_motion_planning.utils import Env, MathHelper
|
|
12
|
+
from python_motion_planning.curve_generation import BSpline
|
|
13
|
+
|
|
14
|
+
GEN_MODE_CIRCLE = 0
|
|
15
|
+
GEN_MODE_RANDOM = 1
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PSO(EvolutionarySearcher):
|
|
19
|
+
"""
|
|
20
|
+
Class for Particle Swarm Optimization (PSO) motion planning.
|
|
21
|
+
|
|
22
|
+
Parameters:
|
|
23
|
+
start (tuple): start point coordinate
|
|
24
|
+
goal (tuple): goal point coordinate
|
|
25
|
+
env (Env): environment
|
|
26
|
+
heuristic_type (str): heuristic function type
|
|
27
|
+
n_particles (int): number of particles
|
|
28
|
+
w_inertial (float): inertial weight
|
|
29
|
+
w_cognitive (float): cognitive weight
|
|
30
|
+
w_social (float): social weight
|
|
31
|
+
point_num (int): number of position points contained in each particle
|
|
32
|
+
max_speed (int): The maximum velocity of particle motion
|
|
33
|
+
max_iter (int): maximum iterations
|
|
34
|
+
init_mode (int): Set the generation mode for the initial position points of the particle swarm
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
>>> import python_motion_planning as pmp
|
|
38
|
+
>>> planner = pmp.PSO((5, 5), (45, 25), pmp.Grid(51, 31))
|
|
39
|
+
>>> cost, path, fitness_history = planner.plan(verbose=True) # planning results only
|
|
40
|
+
>>> cost_curve = [-f for f in fitness_history]
|
|
41
|
+
>>> planner.plot.animation(path, str(planner), cost, cost_curve=cost_curve) # animation
|
|
42
|
+
>>> planner.run() # run both planning and animation
|
|
43
|
+
"""
|
|
44
|
+
def __init__(self, start: tuple, goal: tuple, env: Env, heuristic_type: str = "euclidean",
|
|
45
|
+
n_particles: int = 300, point_num: int = 5, w_inertial: float = 1.0,
|
|
46
|
+
w_cognitive: float = 1.0, w_social: float = 1.0, max_speed: int = 6,
|
|
47
|
+
max_iter: int = 200, init_mode: int = GEN_MODE_RANDOM) -> None:
|
|
48
|
+
super().__init__(start, goal, env, heuristic_type)
|
|
49
|
+
self.max_iter = max_iter
|
|
50
|
+
self.n_particles = n_particles
|
|
51
|
+
self.w_inertial = w_inertial
|
|
52
|
+
self.w_social = w_social
|
|
53
|
+
self.w_cognitive = w_cognitive
|
|
54
|
+
self.point_num = point_num
|
|
55
|
+
self.init_mode = init_mode
|
|
56
|
+
self.max_speed = max_speed
|
|
57
|
+
|
|
58
|
+
self.particles = []
|
|
59
|
+
self.inherited_particles = []
|
|
60
|
+
self.best_particle = self.Particle()
|
|
61
|
+
self.b_spline_gen = BSpline(step=0.01, k=4)
|
|
62
|
+
|
|
63
|
+
def __str__(self) -> str:
|
|
64
|
+
return "Particle Swarm Optimization (PSO)"
|
|
65
|
+
|
|
66
|
+
class Particle:
|
|
67
|
+
def __init__(self) -> None:
|
|
68
|
+
self.reset()
|
|
69
|
+
|
|
70
|
+
def reset(self):
|
|
71
|
+
self.position = []
|
|
72
|
+
self.velocity = []
|
|
73
|
+
self.fitness = -1
|
|
74
|
+
self.best_pos = []
|
|
75
|
+
self.best_fitness = -1
|
|
76
|
+
|
|
77
|
+
def plan(self, verbose: bool = False):
|
|
78
|
+
"""
|
|
79
|
+
Particle Swarm Optimization (PSO) motion plan function.
|
|
80
|
+
|
|
81
|
+
Parameters:
|
|
82
|
+
verbose (bool): print the best fitness value of each iteration
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
cost (float): path cost
|
|
86
|
+
path (list): planning path
|
|
87
|
+
"""
|
|
88
|
+
# Generate initial position of particle swarm
|
|
89
|
+
init_positions = self.initializePositions()
|
|
90
|
+
|
|
91
|
+
# Particle initialization
|
|
92
|
+
for i in range(self.n_particles):
|
|
93
|
+
# Calculate fitness
|
|
94
|
+
init_fitness = self.calFitnessValue(init_positions[i])
|
|
95
|
+
|
|
96
|
+
if i == 0 or init_fitness > self.best_particle.fitness:
|
|
97
|
+
self.best_particle.fitness = init_fitness
|
|
98
|
+
self.best_particle.position = deepcopy(init_positions[i])
|
|
99
|
+
|
|
100
|
+
# Create and add particle objects to containers
|
|
101
|
+
p = self.Particle()
|
|
102
|
+
p.position = init_positions[i]
|
|
103
|
+
p.velocity = [(0, 0) for _ in range(self.point_num)]
|
|
104
|
+
p.best_pos = init_positions[i]
|
|
105
|
+
p.fitness = init_fitness
|
|
106
|
+
p.best_fitness = init_fitness
|
|
107
|
+
self.particles.append(p)
|
|
108
|
+
|
|
109
|
+
# Iterative optimization
|
|
110
|
+
fitness_history = []
|
|
111
|
+
for _ in range(self.max_iter):
|
|
112
|
+
for p in self.particles:
|
|
113
|
+
self.optimizeParticle(p)
|
|
114
|
+
fitness_history.append(self.best_particle.fitness)
|
|
115
|
+
if verbose:
|
|
116
|
+
print(f"iteration {_}: best fitness = {self.best_particle.fitness}")
|
|
117
|
+
|
|
118
|
+
# Generating Paths from Optimal Particles
|
|
119
|
+
points = [self.start.current] + self.best_particle.position + [self.goal.current]
|
|
120
|
+
points = sorted(set(points), key=points.index)
|
|
121
|
+
path = self.b_spline_gen.run(points, display=False)
|
|
122
|
+
|
|
123
|
+
return self.b_spline_gen.length(path), path, fitness_history
|
|
124
|
+
|
|
125
|
+
def initializePositions(self) -> list:
|
|
126
|
+
"""
|
|
127
|
+
Generate n particles with pointNum_ positions each within the map range.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
init_positions (list): initial position sequence of particle swarm
|
|
131
|
+
"""
|
|
132
|
+
init_positions = []
|
|
133
|
+
|
|
134
|
+
# Calculate sequence direction
|
|
135
|
+
x_order = self.goal.x > self.start.x
|
|
136
|
+
y_order = self.goal.y > self.start.y
|
|
137
|
+
|
|
138
|
+
# circle generation
|
|
139
|
+
center_x, center_y, radius = None, None, None
|
|
140
|
+
if self.init_mode == GEN_MODE_CIRCLE:
|
|
141
|
+
# Calculate the center and the radius of the circle (midpoint between start and goal)
|
|
142
|
+
center_x = (self.start.x + self.goal.x) / 2
|
|
143
|
+
center_y = (self.start.y + self.goal.y) / 2
|
|
144
|
+
radius = 5 if self.dist(self.start, self.goal) / 2.0 < 5 else self.dist(self.start, self.goal) / 2.0
|
|
145
|
+
|
|
146
|
+
# initialize n_particles positions
|
|
147
|
+
for _ in range(self.n_particles):
|
|
148
|
+
point_id, visited = 0, []
|
|
149
|
+
pts_x, pts_y = [], []
|
|
150
|
+
# Generate point_num_ unique coordinates
|
|
151
|
+
while point_id < self.point_num:
|
|
152
|
+
if self.init_mode == GEN_MODE_RANDOM:
|
|
153
|
+
pt_x = random.randint(self.start.x, self.goal.x)
|
|
154
|
+
pt_y = random.randint(self.start.y, self.goal.y)
|
|
155
|
+
pos_id = pt_x + self.env.x_range * pt_y
|
|
156
|
+
else:
|
|
157
|
+
# Generate random angle in radians
|
|
158
|
+
angle = random.random() * 2 * math.pi
|
|
159
|
+
# Generate random distance from the center within the circle
|
|
160
|
+
r = math.sqrt(random.random()) * radius
|
|
161
|
+
# Convert polar coordinates to Cartesian coordinates
|
|
162
|
+
pt_x = int(center_x + r * math.cos(angle))
|
|
163
|
+
pt_y = int(center_y + r * math.sin(angle))
|
|
164
|
+
# Check if the coordinates are within the map range
|
|
165
|
+
if 0 <= pt_x < self.env.x_range and 0 <= pt_y < self.env.y_range:
|
|
166
|
+
pos_id = pt_x + self.env.x_range * pt_y
|
|
167
|
+
else:
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
# Check if the coordinates have already been used
|
|
171
|
+
if not pos_id in visited:
|
|
172
|
+
point_id = point_id + 1
|
|
173
|
+
visited.append(pos_id)
|
|
174
|
+
pts_x.append(pt_x)
|
|
175
|
+
pts_y.append(pt_y)
|
|
176
|
+
|
|
177
|
+
# sort
|
|
178
|
+
pts_x = sorted(pts_x, reverse=False) if x_order else sorted(pts_x, reverse=True)
|
|
179
|
+
pts_y = sorted(pts_y, reverse=False) if y_order else sorted(pts_y, reverse=True)
|
|
180
|
+
|
|
181
|
+
# Store elements from x and y in particle_positions
|
|
182
|
+
init_positions.append([(ix, iy) for (ix, iy) in zip(pts_x, pts_y)])
|
|
183
|
+
|
|
184
|
+
return init_positions
|
|
185
|
+
|
|
186
|
+
def calFitnessValue(self, position: list) -> float:
|
|
187
|
+
"""
|
|
188
|
+
Calculate the value of fitness function.
|
|
189
|
+
|
|
190
|
+
Parameters:
|
|
191
|
+
position (list): control points calculated by PSO
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
fitness (float): the value of fitness function
|
|
195
|
+
"""
|
|
196
|
+
points = [self.start.current] + position + [self.goal.current]
|
|
197
|
+
points = sorted(set(points), key=points.index)
|
|
198
|
+
try:
|
|
199
|
+
path = self.b_spline_gen.run(points, display=False)
|
|
200
|
+
except:
|
|
201
|
+
return float("inf")
|
|
202
|
+
|
|
203
|
+
# collision detection
|
|
204
|
+
obs_cost = 0
|
|
205
|
+
for i in range(len(path) - 1):
|
|
206
|
+
p1 = (round(path[i][0]), round(path[i][1]))
|
|
207
|
+
p2 = (round(path[i+1][0]), round(path[i+1][1]))
|
|
208
|
+
if self.isCollision(p1, p2):
|
|
209
|
+
obs_cost = obs_cost + 1
|
|
210
|
+
|
|
211
|
+
# Calculate particle fitness
|
|
212
|
+
return 100000.0 / (self.b_spline_gen.length(path) + 50000 * obs_cost)
|
|
213
|
+
|
|
214
|
+
def updateParticleVelocity(self, particle):
|
|
215
|
+
"""
|
|
216
|
+
A function to update the particle velocity
|
|
217
|
+
|
|
218
|
+
Parameters:
|
|
219
|
+
particle (Particle): the particle
|
|
220
|
+
"""
|
|
221
|
+
# update Velocity
|
|
222
|
+
for i in range(self.point_num):
|
|
223
|
+
rand1, rand2 = random.random(), random.random()
|
|
224
|
+
vx, vy = particle.velocity[i]
|
|
225
|
+
px, py = particle.position[i]
|
|
226
|
+
vx_new = self.w_inertial * vx + self.w_cognitive * rand1 * (particle.best_pos[i][0] - px) \
|
|
227
|
+
+ self.w_social * rand2 * (self.best_particle.position[i][0] - px)
|
|
228
|
+
|
|
229
|
+
vy_new = self.w_inertial * vy + self.w_cognitive * rand1 * (particle.best_pos[i][1] - py) \
|
|
230
|
+
+ self.w_social * rand2 * (self.best_particle.position[i][1] - py)
|
|
231
|
+
|
|
232
|
+
# Velocity Scaling
|
|
233
|
+
if self.env.x_range > self.env.y_range:
|
|
234
|
+
vx_new *= self.env.x_range / self.env.y_range
|
|
235
|
+
else:
|
|
236
|
+
vy_new *= self.env.y_range / self.env.x_range
|
|
237
|
+
|
|
238
|
+
# Velocity limit
|
|
239
|
+
vx_new = MathHelper.clamp(vx_new, -self.max_speed, self.max_speed)
|
|
240
|
+
vy_new = MathHelper.clamp(vy_new, -self.max_speed, self.max_speed)
|
|
241
|
+
particle.velocity[i] = (vx_new, vy_new)
|
|
242
|
+
|
|
243
|
+
def updateParticlePosition(self, particle):
|
|
244
|
+
"""
|
|
245
|
+
A function to update the particle position
|
|
246
|
+
|
|
247
|
+
Parameters:
|
|
248
|
+
particle (Particle): the particle
|
|
249
|
+
"""
|
|
250
|
+
# update Position
|
|
251
|
+
for i in range(self.point_num):
|
|
252
|
+
px = particle.position[i][0] + int(particle.velocity[i][0])
|
|
253
|
+
py = particle.position[i][1] + int(particle.velocity[i][1])
|
|
254
|
+
|
|
255
|
+
# Position limit
|
|
256
|
+
px = MathHelper.clamp(px, 0, self.env.x_range - 1)
|
|
257
|
+
py = MathHelper.clamp(py, 0, self.env.y_range - 1)
|
|
258
|
+
|
|
259
|
+
particle.position[i] = (px, py)
|
|
260
|
+
particle.position.sort(key=lambda p: p[0])
|
|
261
|
+
|
|
262
|
+
def optimizeParticle(self, particle):
|
|
263
|
+
"""
|
|
264
|
+
Particle update optimization iteration
|
|
265
|
+
|
|
266
|
+
Parameters:
|
|
267
|
+
particle (Particle): the particle
|
|
268
|
+
"""
|
|
269
|
+
# update speed
|
|
270
|
+
self.updateParticleVelocity(particle)
|
|
271
|
+
# update position
|
|
272
|
+
self.updateParticlePosition(particle)
|
|
273
|
+
|
|
274
|
+
# Calculate fitness
|
|
275
|
+
particle.fitness = self.calFitnessValue(particle.position)
|
|
276
|
+
|
|
277
|
+
# Update individual optima
|
|
278
|
+
if particle.fitness > particle.best_fitness:
|
|
279
|
+
particle.best_fitness = particle.fitness
|
|
280
|
+
particle.best_pos = particle.position
|
|
281
|
+
|
|
282
|
+
# Update global optimal particles
|
|
283
|
+
if particle.best_fitness > self.best_particle.fitness:
|
|
284
|
+
self.best_particle.fitness = particle.best_fitness
|
|
285
|
+
self.best_particle.position = deepcopy(particle.position)
|
|
286
|
+
|
|
287
|
+
def run(self):
|
|
288
|
+
"""
|
|
289
|
+
Running both plannig and animation.
|
|
290
|
+
"""
|
|
291
|
+
cost, path, fitness_history = self.plan(verbose=True)
|
|
292
|
+
cost_curve = [-f for f in fitness_history]
|
|
293
|
+
self.plot.animation(path, str(self), cost, cost_curve=cost_curve)
|
|
294
|
+
|
|
295
|
+
def isCollision(self, p1: tuple, p2: tuple) -> bool:
|
|
296
|
+
"""
|
|
297
|
+
Judge collision when moving from node1 to node2 using Bresenham.
|
|
298
|
+
|
|
299
|
+
Parameters:
|
|
300
|
+
p1 (tuple): start point
|
|
301
|
+
p2 (tuple): end point
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
collision (bool): True if collision exists, False otherwise.
|
|
305
|
+
"""
|
|
306
|
+
if p1 in self.obstacles or p2 in self.obstacles:
|
|
307
|
+
return True
|
|
308
|
+
|
|
309
|
+
x1, y1 = p1
|
|
310
|
+
x2, y2 = p2
|
|
311
|
+
|
|
312
|
+
if x1 < 0 or x1 >= self.env.x_range or y1 < 0 or y1 >= self.env.y_range:
|
|
313
|
+
return True
|
|
314
|
+
if x2 < 0 or x2 >= self.env.x_range or y2 < 0 or y2 >= self.env.y_range:
|
|
315
|
+
return True
|
|
316
|
+
|
|
317
|
+
d_x = abs(x2 - x1)
|
|
318
|
+
d_y = abs(y2 - y1)
|
|
319
|
+
s_x = 0 if (x2 - x1) == 0 else (x2 - x1) / d_x
|
|
320
|
+
s_y = 0 if (y2 - y1) == 0 else (y2 - y1) / d_y
|
|
321
|
+
x, y, e = x1, y1, 0
|
|
322
|
+
|
|
323
|
+
# check if any obstacle exists between node1 and node2
|
|
324
|
+
if d_x > d_y:
|
|
325
|
+
tau = (d_y - d_x) / 2
|
|
326
|
+
while not x == x2:
|
|
327
|
+
if e > tau:
|
|
328
|
+
x = x + s_x
|
|
329
|
+
e = e - d_y
|
|
330
|
+
elif e < tau:
|
|
331
|
+
y = y + s_y
|
|
332
|
+
e = e + d_x
|
|
333
|
+
else:
|
|
334
|
+
x = x + s_x
|
|
335
|
+
y = y + s_y
|
|
336
|
+
e = e + d_x - d_y
|
|
337
|
+
if (x, y) in self.obstacles:
|
|
338
|
+
return True
|
|
339
|
+
# swap x and y
|
|
340
|
+
else:
|
|
341
|
+
tau = (d_x - d_y) / 2
|
|
342
|
+
while not y == y2:
|
|
343
|
+
if e > tau:
|
|
344
|
+
y = y + s_y
|
|
345
|
+
e = e - d_x
|
|
346
|
+
elif e < tau:
|
|
347
|
+
x = x + s_x
|
|
348
|
+
e = e + d_y
|
|
349
|
+
else:
|
|
350
|
+
x = x + s_x
|
|
351
|
+
y = y + s_y
|
|
352
|
+
e = e + d_y - d_x
|
|
353
|
+
if (x, y) in self.obstacles:
|
|
354
|
+
return True
|
|
355
|
+
|
|
356
|
+
return False
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from .a_star import AStar
|
|
2
|
+
from .dijkstra import Dijkstra
|
|
3
|
+
from .gbfs import GBFS
|
|
4
|
+
from .jps import JPS
|
|
5
|
+
from .d_star import DStar
|
|
6
|
+
from .lpa_star import LPAStar
|
|
7
|
+
from .d_star_lite import DStarLite
|
|
8
|
+
from .voronoi import VoronoiPlanner
|
|
9
|
+
from .theta_star import ThetaStar
|
|
10
|
+
from .lazy_theta_star import LazyThetaStar
|
|
11
|
+
from .s_theta_star import SThetaStar
|
|
12
|
+
# from .anya import Anya
|
|
13
|
+
# from .hybrid_a_star import HybridAStar
|
|
14
|
+
|
|
15
|
+
__all__ = ["AStar",
|
|
16
|
+
"Dijkstra",
|
|
17
|
+
"GBFS",
|
|
18
|
+
"JPS",
|
|
19
|
+
"DStar",
|
|
20
|
+
"LPAStar",
|
|
21
|
+
"DStarLite",
|
|
22
|
+
"VoronoiPlanner",
|
|
23
|
+
"ThetaStar",
|
|
24
|
+
"LazyThetaStar",
|
|
25
|
+
"SThetaStar",
|
|
26
|
+
# "Anya",
|
|
27
|
+
# "HybridAStar"
|
|
28
|
+
]
|