-
Notifications
You must be signed in to change notification settings - Fork 0
Fix DWA #95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Fix DWA #95
Conversation
it runs in sim but the robots are undergoing brownian motion
…oller for opponents for testing
…and normalized the different factors for scores
…test to use 6 robots
…olerance in tests
|
The failing CI test is due to the new tests merged from main, that doesnt concern this PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR revamps the Dynamic Window Approach (DWA) algorithm for robot motion planning by introducing several significant improvements to collision detection, trajectory sampling, and scoring.
Changes:
- Replaced circular collision detection with rectangular bounding boxes that dynamically adjust based on robot velocity
- Changed from fixed-angle trajectory sampling (24 trajectories) to adaptive random sampling with target-facing angle prioritization
- Redesigned scoring function with three weighted factors (target progress, speed, obstacle penalty) and tuned weights for better obstacle avoidance
- Standardized test endpoint tolerance to 0.1m across all motion planning tests and increased stress testing to 6 robots
- Changed default control scheme from PID to DWA in StrategyRunner
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| utama_core/motion_planning/src/dwa/planner.py | Core implementation of new oriented rectangle collision detection, random trajectory sampling, and updated scoring function |
| utama_core/motion_planning/src/dwa/config.py | Updated configuration with new weight parameters and removed obsolete safety radius settings |
| utama_core/motion_planning/src/dwa/translation_controller.py | Added debug visualization for target positions |
| utama_core/run/strategy_runner.py | Changed default control scheme from "pid" to "dwa" |
| utama_core/tests/motion_planning/random_movement_test.py | Increased robot count to 6 and added collision-free initial placement logic |
| utama_core/tests/motion_planning/strategies/random_movement_strategy.py | Fixed circular import with TYPE_CHECKING pattern |
| utama_core/tests/motion_planning/single_robot_static_obstacle_test.py | Standardized endpoint tolerance to 0.1m |
| utama_core/tests/motion_planning/single_robot_moving_obstacle_test.py | Standardized endpoint tolerance to 0.1m |
| utama_core/tests/motion_planning/multiple_robots_test.py | Standardized endpoint tolerance to 0.1m |
| obstacles = temporary_obstacles or [] | ||
| return self._plan_local(game, robot, target, obstacles) |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The temporary_obstacles parameter is documented and passed to _plan_local, but is never actually used in the implementation. The parameter is assigned to a local variable obstacles on line 234 but this variable is not referenced anywhere in the _plan_local method. Either implement support for temporary obstacles or remove this parameter from the API to avoid misleading users of this method.
| self._w_goal = getattr(self._config, "weight_goal") | ||
| self._w_obstacle = getattr(self._config, "weight_obstacle") | ||
| self._w_speed = getattr(self._config, "weight_speed") |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using getattr here is fragile and unnecessary since the config attributes are defined in the dataclass. If an attribute is missing, this will raise an AttributeError at runtime. Access these attributes directly (e.g., self._config.weight_goal) which provides better type safety and clearer error messages if the attribute is missing.
| self._w_goal = getattr(self._config, "weight_goal") | |
| self._w_obstacle = getattr(self._config, "weight_obstacle") | |
| self._w_speed = getattr(self._config, "weight_speed") | |
| self._w_goal = self._config.weight_goal | |
| self._w_obstacle = self._config.weight_obstacle | |
| self._w_speed = self._config.weight_speed |
| break | ||
| else: | ||
| raise RuntimeError( | ||
| "Unable to find non-colliding start positions; " "consider widening bounds or reducing robot count." |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The string concatenation with a space between the two parts creates an unnecessary mid-sentence space. Combine these into a single string or ensure the concatenation is intentional.
| "Unable to find non-colliding start positions; " "consider widening bounds or reducing robot count." | |
| "Unable to find non-colliding start positions; consider widening bounds or reducing robot count." |
| field_bounds: FieldBounds = Field.FULL_FIELD_BOUNDS, | ||
| opp_strategy: Optional[AbstractStrategy] = None, | ||
| control_scheme: str = "pid", # This is also the default control scheme used in the motion planning tests | ||
| control_scheme: str = "dwa", # This is also the default control scheme used in the motion planning tests |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing the default control scheme from "pid" to "dwa" is a significant behavioral change that affects all code using StrategyRunner without explicitly specifying a control_scheme. This could have wide-reaching implications beyond the motion planning tests. Consider whether this should be done in a separate PR with broader testing, or ensure that all affected callers are identified and tested.
| # This should be commented out in real execution to speed up further | ||
| self._draw_collision_box(obstacle_safety_rect, "red" if intersection_ratio > 0.5 else "orange", 2) | ||
|
|
||
| else: | ||
| # Calculate distance between rectangles | ||
| dist = our_safety_rect.distance_to(obstacle_safety_rect) | ||
|
|
||
| # Apply penalty based on proximity | ||
| # Close but not intersecting still gets some penalty | ||
| if dist < 0.5: # Within 0.5m | ||
| # Exponential decay: closer = higher penalty | ||
| # dist=0.0m: penalty ≈ 1.0 | ||
| # dist=0.2m: penalty ≈ 0.45 | ||
| # dist=0.5m: penalty ≈ 0.08 | ||
| penalty = 1.0 * math.exp(-4.0 * dist) | ||
| obstacle_factor = max(obstacle_factor, penalty) | ||
|
|
||
| # This should be commented out in real execution to speed up further | ||
| self._draw_collision_box(obstacle_safety_rect, "green", 1) |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For better performance, check self._config.show_debug_rectangles before calling _draw_collision_box rather than checking inside the function. This avoids function call overhead when debugging is disabled. Consider wrapping these calls with if self._config.show_debug_rectangles: to short-circuit when the flag is False.
| ObstacleRegion, | ||
| to_rectangles, | ||
| ) | ||
| from utama_core.motion_planning.src.planning.geometry import point_segment_distance |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point_segment_distance function is imported but never used in this file. This import can be removed to keep the code clean.
| weight_goal = 50.0 | ||
| weight_obstacle = 1.5 # Increased to match new (0-10) obstacle cost scale | ||
| weight_speed = 1.0 | ||
| show_debug_rectangles = False # Enable visualization of bounding boxes for testing |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These dataclass fields are missing type annotations. Add explicit type annotations for consistency with the other fields in this class. For example: weight_goal: float = 50.0, weight_obstacle: float = 1.5, weight_speed: float = 1.0, and show_debug_rectangles: bool = False.
| weight_goal = 50.0 | |
| weight_obstacle = 1.5 # Increased to match new (0-10) obstacle cost scale | |
| weight_speed = 1.0 | |
| show_debug_rectangles = False # Enable visualization of bounding boxes for testing | |
| weight_goal: float = 50.0 | |
| weight_obstacle: float = 1.5 # Increased to match new (0-10) obstacle cost scale | |
| weight_speed: float = 1.0 | |
| show_debug_rectangles: bool = False # Enable visualization of bounding boxes for testing |
| score = self._evaluate_segment(game, robot, segment_start, segment_end, target) | ||
|
|
||
| if score > best_score: | ||
| best_score = score | ||
| best_move = segment_end |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the secondary trajectory sampling loop (when best_collides is True), collision checking is not performed before updating the best move. This is inconsistent with the first loop (lines 269-286) which checks collisions. While the _evaluate_segment function does apply obstacle penalties that should discourage colliding trajectories, the logic should be consistent. Consider either: (1) also calling _segment_collides in this loop and tracking collision state, or (2) removing the explicit collision check from the first loop if the obstacle penalties in scoring are sufficient.
| score = self._evaluate_segment(game, robot, segment_start, segment_end, target) | |
| if score > best_score: | |
| best_score = score | |
| best_move = segment_end | |
| score = self._evaluate_segment(game, robot, segment_start, segment_end, target) | |
| collides = self._segment_collides(game, robot, segment_start, segment_end) | |
| if score > best_score: | |
| best_score = score | |
| best_move = segment_end | |
| best_collides = collides |
| @@ -1,25 +1,157 @@ | |||
| import copy | |||
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The copy module is imported but never used in this file. This import can be removed to keep the code clean.
Revamp the DWA algorithm
Collision Detection
show_debug_rectanglesto draw the boxes.Central loop logic in
plan_local():Score function of motion segment
Cleanup
segment_overshoots_target(),segment_too_close()and other ineffective functions.Minor changes to the Motion planning tests: