python-motion-planning 2.0.dev1__py3-none-any.whl → 2.0.1__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.
Files changed (60) hide show
  1. python_motion_planning/__init__.py +1 -1
  2. python_motion_planning/common/env/map/base_map.py +2 -8
  3. python_motion_planning/common/env/map/grid.py +456 -198
  4. python_motion_planning/common/utils/__init__.py +2 -1
  5. python_motion_planning/common/utils/child_tree.py +22 -0
  6. python_motion_planning/common/utils/geometry.py +18 -29
  7. python_motion_planning/common/visualizer/__init__.py +3 -1
  8. python_motion_planning/common/visualizer/base_visualizer.py +165 -0
  9. python_motion_planning/common/visualizer/{visualizer.py → visualizer_2d.py} +97 -220
  10. python_motion_planning/common/visualizer/visualizer_3d.py +242 -0
  11. python_motion_planning/controller/base_controller.py +37 -4
  12. python_motion_planning/controller/path_tracker/__init__.py +2 -1
  13. python_motion_planning/controller/path_tracker/apf.py +22 -23
  14. python_motion_planning/controller/path_tracker/dwa.py +14 -17
  15. python_motion_planning/controller/path_tracker/path_tracker.py +4 -1
  16. python_motion_planning/controller/path_tracker/pid.py +7 -1
  17. python_motion_planning/controller/path_tracker/pure_pursuit.py +7 -1
  18. python_motion_planning/controller/path_tracker/rpp.py +111 -0
  19. python_motion_planning/path_planner/__init__.py +2 -1
  20. python_motion_planning/path_planner/base_path_planner.py +45 -11
  21. python_motion_planning/path_planner/graph_search/__init__.py +4 -1
  22. python_motion_planning/path_planner/graph_search/a_star.py +12 -14
  23. python_motion_planning/path_planner/graph_search/dijkstra.py +15 -15
  24. python_motion_planning/path_planner/graph_search/gbfs.py +100 -0
  25. python_motion_planning/path_planner/graph_search/jps.py +199 -0
  26. python_motion_planning/path_planner/graph_search/lazy_theta_star.py +113 -0
  27. python_motion_planning/path_planner/graph_search/theta_star.py +17 -19
  28. python_motion_planning/path_planner/hybrid_search/__init__.py +1 -0
  29. python_motion_planning/path_planner/hybrid_search/voronoi_planner.py +204 -0
  30. python_motion_planning/path_planner/sample_search/__init__.py +2 -1
  31. python_motion_planning/path_planner/sample_search/rrt.py +73 -31
  32. python_motion_planning/path_planner/sample_search/rrt_connect.py +237 -0
  33. python_motion_planning/path_planner/sample_search/rrt_star.py +220 -150
  34. python_motion_planning/traj_optimizer/__init__.py +2 -0
  35. python_motion_planning/traj_optimizer/base_curve_generator.py +53 -0
  36. python_motion_planning/traj_optimizer/curve_generator/__init__.py +2 -0
  37. python_motion_planning/traj_optimizer/curve_generator/point_based/__init__.py +2 -0
  38. python_motion_planning/traj_optimizer/curve_generator/point_based/bspline.py +256 -0
  39. python_motion_planning/traj_optimizer/curve_generator/point_based/cubic_spline.py +115 -0
  40. python_motion_planning/traj_optimizer/curve_generator/pose_based/__init__.py +4 -0
  41. python_motion_planning/traj_optimizer/curve_generator/pose_based/bezier.py +121 -0
  42. python_motion_planning/traj_optimizer/curve_generator/pose_based/dubins.py +355 -0
  43. python_motion_planning/traj_optimizer/curve_generator/pose_based/polynomial.py +197 -0
  44. python_motion_planning/traj_optimizer/curve_generator/pose_based/reeds_shepp.py +606 -0
  45. {python_motion_planning-2.0.dev1.dist-info → python_motion_planning-2.0.1.dist-info}/METADATA +71 -29
  46. python_motion_planning-2.0.1.dist-info/RECORD +64 -0
  47. {python_motion_planning-2.0.dev1.dist-info → python_motion_planning-2.0.1.dist-info}/WHEEL +1 -1
  48. python_motion_planning/common/env/robot/tmp.py +0 -404
  49. python_motion_planning/curve_generator/__init__.py +0 -9
  50. python_motion_planning/curve_generator/bezier_curve.py +0 -131
  51. python_motion_planning/curve_generator/bspline_curve.py +0 -271
  52. python_motion_planning/curve_generator/cubic_spline.py +0 -128
  53. python_motion_planning/curve_generator/curve.py +0 -64
  54. python_motion_planning/curve_generator/dubins_curve.py +0 -348
  55. python_motion_planning/curve_generator/fem_pos_smooth.py +0 -114
  56. python_motion_planning/curve_generator/polynomial_curve.py +0 -226
  57. python_motion_planning/curve_generator/reeds_shepp.py +0 -736
  58. python_motion_planning-2.0.dev1.dist-info/RECORD +0 -53
  59. {python_motion_planning-2.0.dev1.dist-info → python_motion_planning-2.0.1.dist-info}/licenses/LICENSE +0 -0
  60. {python_motion_planning-2.0.dev1.dist-info → python_motion_planning-2.0.1.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,14 @@
1
1
  """
2
2
  @file: base_controller.py
3
3
  @author: Wu Maojia
4
- @update: 2025.10.3
4
+ @update: 2025.10.16
5
5
  """
6
6
  from typing import List, Tuple
7
7
 
8
8
  import numpy as np
9
9
  from gymnasium import spaces
10
10
 
11
+ from python_motion_planning.common.env import Grid, TYPES, BaseRobot
11
12
  from python_motion_planning.common.utils.geometry import Geometry
12
13
 
13
14
  class BaseController:
@@ -19,17 +20,20 @@ class BaseController:
19
20
  observation_space: observation space ([pos, orient, lin_vel, ang_vel])
20
21
  action_space: action space ([lin_acc, ang_acc])
21
22
  dt: time step for control
22
- path: pose path to follow
23
+ path: pose path to follow (world frame)
23
24
  max_lin_speed: maximum linear speed of the robot
24
25
  max_ang_speed: maximum angular speed of the robot
25
26
  goal_dist_tol: goal distance tolerance
26
27
  goal_orient_tol: goal orient tolerance
28
+ robot_model: robot model for kinematic parameters
29
+ obstacle_grid: occupancy grid map for collision checking
27
30
  eps: epsilon (numerical precision)
28
31
  """
29
32
  def __init__(self, observation_space: spaces.Space, action_space: spaces.Box,
30
33
  dt: float, path: List[Tuple[float, ...]] = [],
31
34
  max_lin_speed: float = np.inf, max_ang_speed: float = np.inf,
32
35
  goal_dist_tol: float = 0.5, goal_orient_tol: float = np.deg2rad(5),
36
+ robot_model: BaseRobot = None, obstacle_grid: Grid = None,
33
37
  eps: float = 1e-8):
34
38
  self.observation_space = observation_space
35
39
  self.action_space = action_space
@@ -57,9 +61,20 @@ class BaseController:
57
61
  self.goal = self.path[-1]
58
62
  else:
59
63
  self.goal = None
60
- self.path = False
64
+ self.path = False
61
65
 
62
66
  self.pred_traj = np.array([])
67
+
68
+ if robot_model and robot_model.dim != self.dim:
69
+ raise ValueError("Dimension of robot model and controller must be the same")
70
+ self.robot_model = robot_model
71
+
72
+ if obstacle_grid and obstacle_grid.dim != self.dim:
73
+ raise ValueError("Dimension of obstacle grid and controller must be the same")
74
+ self.obstacle_grid = obstacle_grid
75
+
76
+ def __str__(self) -> str:
77
+ return "Base Controller"
63
78
 
64
79
  def reset(self):
65
80
  """
@@ -155,4 +170,22 @@ class BaseController:
155
170
  if np.abs(Geometry.regularize_orient(orient - goal_orient)) < self.goal_orient_tol:
156
171
  desired_vel[self.dim:] = 0.0
157
172
 
158
- return desired_vel
173
+ return desired_vel
174
+
175
+ def _get_dist_to_nearest_obstacle(self, pos: np.ndarray) -> float:
176
+ """
177
+ Get distance to nearest obstacle.
178
+
179
+ Args:
180
+ pos: position (length = self.dim)
181
+
182
+ Returns:
183
+ dist: distance to nearest obstacle
184
+ """
185
+ grid_pt = self.obstacle_grid.world_to_map(tuple(pos))
186
+ if not self.obstacle_grid.within_bounds(grid_pt) or self.obstacle_grid.type_map[grid_pt] == TYPES.OBSTACLE:
187
+ dist = 0.0
188
+ else:
189
+ dist = self.obstacle_grid.esdf[grid_pt] * self.obstacle_grid.resolution # using ESDF to compute distance to nearest obstacle
190
+
191
+ return dist
@@ -2,4 +2,5 @@ from .path_tracker import PathTracker
2
2
  from .pure_pursuit import PurePursuit
3
3
  from .pid import PID
4
4
  from .dwa import DWA
5
- from .apf import APF
5
+ from .apf import APF
6
+ from .rpp import RPP
@@ -1,7 +1,7 @@
1
1
  """
2
2
  @file: apf.py
3
3
  @author: Wu Maojia, Yang Haodong
4
- @update: 2025.10.3
4
+ @update: 2025.10.16
5
5
  """
6
6
  from typing import List, Tuple
7
7
  import math
@@ -18,39 +18,41 @@ from .path_tracker import PathTracker
18
18
 
19
19
  class APF(PathTracker):
20
20
  """
21
- Artificial Potential Field (APF) path-tracking controller.
21
+ Artificial Potential Field (APF) path-tracking controller. `robot_model` and `obstacle_grid` must be provided.
22
+ `
22
23
 
23
24
  Args:
24
25
  *args: see the parent class.
25
- robot_model: robot model for kinematic parameters
26
- obstacle_grid: occupancy grid map for collision checking
27
26
  attr_weight: weight factor for attractive potential
28
27
  rep_weight: weight factor for repulsive potential
29
28
  rep_range: influence range for repulsive potential
30
29
  **kwargs: see the parent class.
30
+
31
+ References:
32
+ [1] Real-time obstacle avoidance for manipulators and mobile robots
31
33
  """
32
34
  def __init__(self,
33
35
  *args,
34
- robot_model: BaseRobot,
35
- obstacle_grid: Grid = None,
36
36
  attr_weight: float = 1.0,
37
37
  rep_weight: float = 1.0,
38
38
  rep_range: float = None,
39
39
  **kwargs):
40
40
  super().__init__(*args, **kwargs)
41
- if robot_model.dim != self.dim:
42
- raise ValueError("Dimension of robot model and controller must be the same")
43
- self.robot_model = robot_model
44
41
 
45
- if obstacle_grid and obstacle_grid.dim != self.dim:
46
- raise ValueError("Dimension of obstacle grid and controller must be the same")
47
- self.obstacle_grid = obstacle_grid
42
+ if self.robot_model is None:
43
+ raise ValueError("Robot model is required.")
44
+
45
+ if self.obstacle_grid is None:
46
+ raise ValueError("Obstacle grid is required.")
48
47
 
49
48
  # APF parameters
50
49
  self.attr_weight = attr_weight # Attractive potential weight
51
50
  self.rep_weight = rep_weight # Repulsive potential weight
52
51
  self.rep_range = rep_range if rep_range is not None else self.robot_model.radius * 2.0 # Repulsive influence range
53
52
 
53
+ def __str__(self) -> str:
54
+ return "APF"
55
+
54
56
  def get_action(self, obs: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
55
57
  """
56
58
  Get action from observation using Artificial Potential Field method.
@@ -125,22 +127,19 @@ class APF(PathTracker):
125
127
  if self.obstacle_grid is None:
126
128
  return np.zeros(self.dim)
127
129
 
128
- # Convert world position to grid coordinates
129
- grid_pt = self.obstacle_grid.world_to_map(tuple(current_pos[:2]))
130
- grid_x, grid_y = grid_pt
131
-
132
- # Check if position is out of bounds or in an obstacle
133
- if not self.obstacle_grid.within_bounds(grid_pt) or self.obstacle_grid.type_map[grid_pt] == TYPES.OBSTACLE:
134
- # Large repulsive force if in collision
135
- return np.full(self.dim, self.rep_weight * self.rep_range)
136
-
137
- # Get distance to nearest obstacle from ESDF (in world units)
138
- dist_to_obstacle = self.obstacle_grid.esdf[grid_pt] * self.obstacle_grid.resolution
130
+ dist_to_obstacle = self._get_dist_to_nearest_obstacle(current_pos[:self.dim])
139
131
 
140
132
  # No repulsive force if outside influence range
141
133
  if dist_to_obstacle >= self.rep_range:
142
134
  return np.zeros(self.dim)
143
135
 
136
+ # avoid division by zero
137
+ dist_to_obstacle = max(dist_to_obstacle, self.eps)
138
+
139
+ # Convert world position to grid coordinates
140
+ grid_pt = self.obstacle_grid.world_to_map(tuple(current_pos[:2]))
141
+ grid_x, grid_y = grid_pt
142
+
144
143
  # Calculate gradient of repulsive potential using numpy's gradient function
145
144
  # Extract a small window around current grid point to compute gradient
146
145
  window_size = 3 # 3x3 window for gradient calculation
@@ -1,7 +1,7 @@
1
1
  """
2
2
  @file: dwa.py
3
3
  @author: Wu Maojia
4
- @update: 2025.10.3
4
+ @update: 2025.10.16
5
5
  """
6
6
  from typing import List, Tuple, Optional
7
7
  import math
@@ -18,23 +18,22 @@ from .path_tracker import PathTracker
18
18
 
19
19
  class DWA(PathTracker):
20
20
  """
21
- Dynamic Window Approach (DWA) path-tracking controller.
21
+ Dynamic Window Approach (DWA) path-tracking controller. `robot_model` and `obstacle_grid` must be provided.
22
22
 
23
23
  Args:
24
24
  *args: see the parent class.
25
- robot_model: robot model for kinematic simulation
26
- obstacle_grid: occupancy grid map for collision checking
27
25
  vel_reso: resolution of velocity sampling
28
26
  predict_time: forward simulation time horizon
29
27
  heading_weight: weight for heading term
30
28
  velocity_weight: weight for velocity term
31
29
  clearance_weight: weight for obstacle clearance term
32
30
  **kwargs: see the parent class.
31
+
32
+ References:
33
+ [1] The Dynamic Window Approach to Collision Avoidance
33
34
  """
34
35
  def __init__(self,
35
36
  *args,
36
- robot_model: BaseRobot,
37
- obstacle_grid: Grid = None,
38
37
  vel_reso: float = np.array([0.2, 0.2, np.deg2rad(15)]),
39
38
  predict_time: float = None,
40
39
  heading_weight: float = 0.5,
@@ -42,13 +41,12 @@ class DWA(PathTracker):
42
41
  clearance_weight: float = 0.3,
43
42
  **kwargs):
44
43
  super().__init__(*args, **kwargs)
45
- if robot_model.dim != self.dim:
46
- raise ValueError("Dimension of robot model and controller must be the same")
47
- self.robot_model = robot_model
48
44
 
49
- if obstacle_grid.dim != self.dim:
50
- raise ValueError("Dimension of obstacle grid and controller must be the same")
51
- self.obstacle_grid = obstacle_grid
45
+ if self.robot_model is None:
46
+ raise ValueError("Robot model is required.")
47
+
48
+ if self.obstacle_grid is None:
49
+ raise ValueError("Obstacle grid is required.")
52
50
 
53
51
  self.vel_reso = vel_reso
54
52
  self.predict_time = predict_time if predict_time is not None else self.lookahead_distance / self.max_lin_speed
@@ -56,6 +54,9 @@ class DWA(PathTracker):
56
54
  self.velocity_weight = velocity_weight
57
55
  self.clearance_weight = clearance_weight
58
56
 
57
+ def __str__(self) -> str:
58
+ return "DWA"
59
+
59
60
  def get_action(self, obs: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
60
61
  """
61
62
  Get action from observation using Dynamic Window Approach.
@@ -238,11 +239,7 @@ class DWA(PathTracker):
238
239
 
239
240
  min_dist = float("inf")
240
241
  for p in traj:
241
- grid_pt = self.obstacle_grid.world_to_map(tuple(p[:2]))
242
- if not self.obstacle_grid.within_bounds(grid_pt) or self.obstacle_grid.type_map[grid_pt] == TYPES.OBSTACLE:
243
- return -float("inf") # collision
244
- # update min distance (Euclidean to occupied cells could be added here)
245
- dist = self.obstacle_grid.esdf[grid_pt] * self.obstacle_grid.resolution # using ESDF to compute distance to nearest obstacle
242
+ dist = self._get_dist_to_nearest_obstacle(p[:self.dim])
246
243
  min_dist = min(min_dist, dist)
247
244
 
248
245
  normalized_min_dist = min_dist / self.robot_model.radius
@@ -1,7 +1,7 @@
1
1
  """
2
2
  @file: path_tracker.py
3
3
  @author: Wu Maojia
4
- @update: 2025.10.3
4
+ @update: 2025.10.16
5
5
  """
6
6
  from typing import List, Tuple
7
7
  import math
@@ -35,6 +35,9 @@ class PathTracker(BaseController):
35
35
  self.pose_interp = pose_interp
36
36
  self.current_target_index = 0
37
37
 
38
+ def __str__(self) -> str:
39
+ return "Path Tracker"
40
+
38
41
  def reset(self):
39
42
  """
40
43
  Reset the controller to initial state.
@@ -1,7 +1,7 @@
1
1
  """
2
2
  @file: pid.py
3
3
  @author: Wu Maojia
4
- @update: 2025.10.3
4
+ @update: 2025.10.16
5
5
  """
6
6
  from typing import List, Tuple
7
7
  import math
@@ -22,6 +22,9 @@ class PID(PathTracker):
22
22
  Ki: integral gain
23
23
  Kd: derivative gain
24
24
  **kwargs: see the parent class.
25
+
26
+ References:
27
+ [1] Directional stability of automatically steered bodies
25
28
  """
26
29
  def __init__(self,
27
30
  *args,
@@ -38,6 +41,9 @@ class PID(PathTracker):
38
41
  self.integral_error = np.zeros(self.action_space.shape[0])
39
42
  self.prev_error = np.zeros(self.action_space.shape[0])
40
43
 
44
+ def __str__(self) -> str:
45
+ return "PID"
46
+
41
47
  def reset(self):
42
48
  """
43
49
  Reset the controller to initial state.
@@ -1,7 +1,7 @@
1
1
  """
2
2
  @file: pure_pursuit.py
3
3
  @author: Wu Maojia
4
- @update: 2025.10.3
4
+ @update: 2025.10.16
5
5
  """
6
6
  from typing import List, Tuple
7
7
  import math
@@ -19,12 +19,18 @@ class PurePursuit(PathTracker):
19
19
  Args:
20
20
  *args: see the parent class.
21
21
  **kwargs: see the parent class.
22
+
23
+ References:
24
+ [1] Implementation of the Pure Pursuit Path Tracking Algorithm.
22
25
  """
23
26
  def __init__(self,
24
27
  *args,
25
28
  **kwargs):
26
29
  super().__init__(*args, **kwargs)
27
30
 
31
+ def __str__(self) -> str:
32
+ return "Pure Pursuit"
33
+
28
34
  def _get_desired_vel(self, target_pose: np.ndarray, cur_pose: np.ndarray) -> np.ndarray:
29
35
  """
30
36
  Calculate the desired velocity in robot frame using pure pursuit.
@@ -0,0 +1,111 @@
1
+ """
2
+ @file: rpp.py
3
+ @author: Wu Maojia
4
+ @update: 2025.10.16
5
+ """
6
+ from typing import List, Tuple
7
+ import math
8
+
9
+ import numpy as np
10
+
11
+ from python_motion_planning.common.utils.geometry import Geometry
12
+ from python_motion_planning.common.utils.frame_transformer import FrameTransformer
13
+ from .pure_pursuit import PurePursuit
14
+
15
+
16
+ class RPP(PurePursuit):
17
+ """
18
+ Regulated Pure Pursuit (RPP) path-tracking controller. `obstacle_grid` must be provided.
19
+
20
+ Args:
21
+ *args: see the parent class.
22
+ curvature_thresh (float): minimum curvature threshold T_kappa to trigger speed scaling.
23
+ r_min (float): minimum radius (used in curvature heuristic).
24
+ d_prox (float): proximity distance threshold to start slowing down near obstacles.
25
+ alpha (float): proximity scaling gain (0 < alpha <= 1).
26
+ lookahead_gain (float): lookahead time gain for adaptive lookahead.
27
+ **kwargs: see the parent class.
28
+ """
29
+
30
+ def __init__(self,
31
+ *args,
32
+ curvature_thresh: float = 0.1,
33
+ r_min: float = 1.0,
34
+ d_prox: float = None,
35
+ alpha: float = 0.8,
36
+ lookahead_gain: float = 1.0,
37
+ **kwargs):
38
+ super().__init__(*args, **kwargs)
39
+
40
+ if self.obstacle_grid is None:
41
+ raise ValueError("Obstacle grid is required.")
42
+
43
+ self.curvature_thresh = curvature_thresh
44
+ self.r_min = r_min
45
+ self.d_prox = d_prox if d_prox is not None else self.obstacle_grid.inflation_radius
46
+ self.alpha = alpha
47
+ self.lookahead_gain = lookahead_gain
48
+
49
+ def __str__(self) -> str:
50
+ return "RPP"
51
+
52
+ def _get_desired_vel(self, target_pose: np.ndarray, cur_pose: np.ndarray) -> np.ndarray:
53
+ """
54
+ Calculate the desired velocity in robot frame using Regulated Pure Pursuit.
55
+
56
+ Args:
57
+ target_pose: target pose in world frame (from lookahead)
58
+ cur_pose: current pose in world frame
59
+ obs_dist: distance to nearest obstacle (if available), defaults to infinity
60
+
61
+ Returns:
62
+ desired_vel: desired velocity in robot frame [lin_x, lin_y, ang_z]
63
+ """
64
+ # transform target pose to robot frame
65
+ rel_pose = FrameTransformer.pose_world_to_robot(self.dim, target_pose, cur_pose)
66
+ x = rel_pose[0]
67
+ y = rel_pose[1]
68
+ L = math.hypot(x, y)
69
+
70
+ # if lookahead distance is (nearly) zero, no movement
71
+ if L < self.eps:
72
+ desired_vel = np.zeros(self.action_space.shape[0])
73
+ return self.clip_velocity(desired_vel)
74
+
75
+ # Pure Pursuit curvature: kappa = 2*y / L^2
76
+ # Note: y is lateral offset in robot frame (positive left). For our coordinate,
77
+ # forward x, lateral y. Angular velocity = kappa * v.
78
+ kappa = (2.0 * y) / (L * L)
79
+
80
+ # Base linear speed (before regulation)
81
+ v_t = self.max_lin_speed
82
+
83
+ # Curvature heuristic
84
+ # Slow down in sharp turns: Eq. (5) in the paper
85
+ if abs(kappa) > self.curvature_thresh:
86
+ v_curv = v_t / (self.r_min * max(abs(kappa), self.eps))
87
+ else:
88
+ v_curv = v_t
89
+
90
+ # Proximity heuristic
91
+ # Slow down when close to obstacles: Eq. (6) in the paper
92
+ obs_dist = self._get_dist_to_nearest_obstacle(cur_pose[:self.dim])
93
+ if obs_dist <= self.d_prox:
94
+ v_prox = v_t * self.alpha * obs_dist / max(self.d_prox, self.eps)
95
+ else:
96
+ v_prox = v_t
97
+ v_prox = v_t
98
+
99
+ # Take minimum regulated velocity (for safety)
100
+ v_reg = min(v_curv, v_prox)
101
+
102
+ # Compute angular velocity using regulated linear speed
103
+ omega = np.clip(v_reg * kappa, -self.max_ang_speed, self.max_ang_speed)
104
+
105
+ # Assemble desired velocity vector
106
+ desired_lin_vel = np.array([v_reg * (1.0 if x >= 0 else -1.0), 0.0])
107
+ desired_ang_vel = np.array([omega])
108
+ desired_vel = np.concatenate([desired_lin_vel, desired_ang_vel])
109
+
110
+ desired_vel = self.clip_velocity(desired_vel)
111
+ return desired_vel
@@ -1,3 +1,4 @@
1
1
  from .base_path_planner import *
2
2
  from .graph_search import *
3
- from .sample_search import *
3
+ from .sample_search import *
4
+ from .hybrid_search import *
@@ -1,9 +1,9 @@
1
1
  """
2
- @file: planner.py
2
+ @file: base_path_planner.py
3
3
  @author: Wu Maojia
4
- @update: 2025.10.3
4
+ @update: 2025.12.19
5
5
  """
6
- from typing import Union
6
+ from typing import Union, List, Tuple, Dict, Any, Iterable
7
7
  from abc import ABC, abstractmethod
8
8
 
9
9
  from python_motion_planning.common import BaseMap
@@ -25,8 +25,31 @@ class BasePathPlanner(ABC):
25
25
  self.goal = goal
26
26
  self.failed_info = [], {"success": False, "start": None, "goal": None, "length": 0, "cost": 0, "expand": {}}
27
27
 
28
+ def __str__(self) -> str:
29
+ return "Base Path Planner"
30
+
31
+ @property
32
+ def dim(self) -> int:
33
+ """
34
+ Get the dimension of the map.
35
+
36
+ Returns:
37
+ dim (int): The dimension of the map.
38
+ """
39
+ return self.map_.dim
40
+
41
+ @property
42
+ def bounds(self) -> Iterable:
43
+ """
44
+ Get the bounds of the map.
45
+
46
+ Returns:
47
+ bounds (Iterable): The bounds of the map.
48
+ """
49
+ return self.map_.bounds
50
+
28
51
  @abstractmethod
29
- def plan(self) -> Union[list, dict]:
52
+ def plan(self) -> Union[List[Tuple[float, ...]], Dict[str, Any]]:
30
53
  """
31
54
  Interface for planning.
32
55
 
@@ -63,26 +86,37 @@ class BasePathPlanner(ABC):
63
86
  return self.get_cost(point, self.goal)
64
87
 
65
88
 
66
- def extract_path(self, closed_list: dict) -> tuple:
89
+ def extract_path(self, closed_list: dict, start: tuple = None, goal: tuple = None) -> Tuple[List[Tuple[float, ...]], float, float]:
67
90
  """
68
91
  Extract the path based on the CLOSED list.
69
92
 
70
93
  Args:
71
- closed_list (dict): CLOSED list
94
+ closed_list: CLOSED list
95
+ start: Start point. (default: self.start)
96
+ goal: Goal point. (default: self.goal)
72
97
 
73
98
  Returns:
74
- cost (float): the cost of planned path
75
- path (list): the planning path
99
+ path: A list containing the path waypoints
100
+ length: Length of the path
101
+ cost: Cost of the path
76
102
  """
77
103
  length = 0
78
104
  cost = 0
79
- node = closed_list[self.goal]
105
+
106
+ if start is None:
107
+ start = self.start
108
+ if goal is None:
109
+ goal = self.goal
110
+
111
+ node = closed_list.get(goal)
80
112
  path = [node.current]
81
- while node.current != self.start:
82
- node_parent = closed_list[node.parent]
113
+
114
+ while node.current != start:
115
+ node_parent = closed_list.get(node.parent)
83
116
  length += self.map_.get_distance(node.current, node_parent.current)
84
117
  cost += self.get_cost(node.current, node_parent.current)
85
118
  node = node_parent
86
119
  path.append(node.current)
87
120
  path = path[::-1] # make the order: start -> goal
121
+
88
122
  return path, length, cost
@@ -1,3 +1,6 @@
1
1
  from .dijkstra import *
2
+ from .gbfs import *
2
3
  from .a_star import *
3
- from .theta_star import *
4
+ from .jps import *
5
+ from .theta_star import *
6
+ from .lazy_theta_star import *
@@ -1,9 +1,9 @@
1
1
  """
2
2
  @file: a_star.py
3
3
  @author: Wu Maojia
4
- @update: 2025.10.3
4
+ @update: 2025.10.6
5
5
  """
6
- from typing import Union
6
+ from typing import Union, List, Tuple, Dict, Any
7
7
  import heapq
8
8
 
9
9
  from python_motion_planning.common import BaseMap, Grid, Node, TYPES
@@ -23,12 +23,14 @@ class AStar(Dijkstra):
23
23
  Examples:
24
24
  >>> map_ = Grid(bounds=[[0, 15], [0, 15]])
25
25
  >>> planner = AStar(map_=map_, start=(5, 5), goal=(10, 10))
26
- >>> planner.plan()
27
- ([(5, 5), (6, 6), (7, 7), (8, 8), (9, 9), (10, 10)], {'success': True, 'start': (5, 5), 'goal': (10, 10), 'length': 7.0710678118654755, 'cost': 7.0710678118654755, 'expand': {(5, 5): Node((5, 5), None, 0, 7.0710678118654755), (6, 6): Node((6, 6), (5, 5), 1.4142135623730951, 5.656854249492381), (7, 7): Node((7, 7), (6, 6), 2.8284271247461903, 4.242640687119285), (8, 8): Node((8, 8), (7, 7), 4.242640687119286, 2.8284271247461903), (9, 9): Node((9, 9), (8, 8), 5.656854249492381, 1.4142135623730951), (10, 10): Node((10, 10), (9, 9), 7.0710678118654755, 0.0)}})
28
-
26
+ >>> path, path_info = planner.plan()
27
+ >>> print(path_info['success'])
28
+ True
29
+
29
30
  >>> planner.map_.type_map[3:10, 6] = TYPES.OBSTACLE
30
- >>> planner.plan()
31
- ([(5, 5), (6, 6), (7, 7), (8, 8), (9, 9), (10, 10)], {'success': True, 'start': (5, 5), 'goal': (10, 10), 'length': 7.0710678118654755, 'cost': 7.0710678118654755, 'expand': {(5, 5): Node((5, 5), None, 0, 7.0710678118654755), (6, 6): Node((6, 6), (5, 5), 1.4142135623730951, 5.656854249492381), (7, 7): Node((7, 7), (6, 6), 2.8284271247461903, 4.242640687119285), (8, 8): Node((8, 8), (7, 7), 4.242640687119286, 2.8284271247461903), (9, 9): Node((9, 9), (8, 8), 5.656854249492381, 1.4142135623730951), (10, 10): Node((10, 10), (9, 9), 7.0710678118654755, 0.0)}})
31
+ >>> path, path_info = planner.plan()
32
+ >>> print(path_info['success'])
33
+ True
32
34
  """
33
35
  def __init__(self, *args, **kwargs) -> None:
34
36
  super().__init__(*args, **kwargs)
@@ -36,13 +38,13 @@ class AStar(Dijkstra):
36
38
  def __str__(self) -> str:
37
39
  return "A*"
38
40
 
39
- def plan(self) -> Union[list, dict]:
41
+ def plan(self) -> Union[List[Tuple[float, ...]], Dict[str, Any]]:
40
42
  """
41
43
  Interface for planning.
42
44
 
43
45
  Returns:
44
46
  path: A list containing the path waypoints
45
- path_info: A dictionary containing the path information (success, length, cost, expand)
47
+ path_info: A dictionary containing the path information
46
48
  """
47
49
  # OPEN list (priority queue) and CLOSED list (hash table)
48
50
  OPEN = []
@@ -53,10 +55,6 @@ class AStar(Dijkstra):
53
55
  while OPEN:
54
56
  node = heapq.heappop(OPEN)
55
57
 
56
- # obstacle found
57
- if not self.map_.is_expandable(node.current, node.parent):
58
- continue
59
-
60
58
  # exists in CLOSED list
61
59
  if node.current in CLOSED:
62
60
  continue
@@ -74,7 +72,7 @@ class AStar(Dijkstra):
74
72
  "expand": CLOSED
75
73
  }
76
74
 
77
- for node_n in self.map_.get_neighbors(node):
75
+ for node_n in self.map_.get_neighbors(node, diagonal=self.diagonal):
78
76
  # exists in CLOSED list
79
77
  if node_n.current in CLOSED:
80
78
  continue