Skip to content

Commit c32873d

Browse files
Prototype solving ros-navigation#5192 Issue 2: Reeds-Shepp reduce small reverse expansions (ros-navigation#5207)
* prototype solving 5192 issue 2 Signed-off-by: Steve Macenski <stevenmacenski@gmail.com> * Removing unnecessary variable Signed-off-by: Steve Macenski <stevenmacenski@gmail.com> --------- Signed-off-by: Steve Macenski <stevenmacenski@gmail.com>
1 parent eb2235a commit c32873d

File tree

5 files changed

+108
-23
lines changed

5 files changed

+108
-23
lines changed

‎nav2_bringup/launch/tb3_loopback_simulation_launch.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ def generate_launch_description() -> LaunchDescription:
144144
'use_composition': use_composition,
145145
'use_respawn': use_respawn,
146146
'use_localization': 'False', # Don't use SLAM, AMCL
147+
'use_keepout_zones': 'False',
148+
'use_speed_zones': 'False',
147149
}.items(),
148150
)
149151

‎nav2_bringup/launch/tb3_simulation_launch.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ def generate_launch_description() -> LaunchDescription:
195195
'autostart': autostart,
196196
'use_composition': use_composition,
197197
'use_respawn': use_respawn,
198+
'use_keepout_zones': 'False',
199+
'use_speed_zones': 'False',
198200
}.items(),
199201
)
200202
# The SDF file for the world is a xacro file because we wanted to

‎nav2_smac_planner/include/nav2_smac_planner/analytic_expansion.hpp‎

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
#ifndef NAV2_SMAC_PLANNER__ANALYTIC_EXPANSION_HPP_
1616
#define NAV2_SMAC_PLANNER__ANALYTIC_EXPANSION_HPP_
1717

18+
#include <ompl/base/ScopedState.h>
19+
#include <ompl/base/spaces/DubinsStateSpace.h>
20+
#include <ompl/base/spaces/ReedsSheppStateSpace.h>
21+
1822
#include <functional>
1923
#include <list>
2024
#include <memory>
@@ -61,7 +65,33 @@ class AnalyticExpansion
6165
Coordinates proposed_coords;
6266
};
6367

64-
typedef std::vector<AnalyticExpansionNode> AnalyticExpansionNodes;
68+
/**
69+
* @struct AnalyticExpansionNodes
70+
* @brief Analytic expansion nodes and associated metadata
71+
*
72+
* This structure holds a collection of analytic expansion nodes and the number of direction
73+
* changes encountered during the expansion.
74+
*/
75+
struct AnalyticExpansionNodes
76+
{
77+
AnalyticExpansionNodes() = default;
78+
79+
void add(
80+
NodePtr & node,
81+
Coordinates & initial_coords,
82+
Coordinates & proposed_coords)
83+
{
84+
nodes.emplace_back(node, initial_coords, proposed_coords);
85+
}
86+
87+
void setDirectionChanges(int changes)
88+
{
89+
direction_changes = changes;
90+
}
91+
92+
std::vector<AnalyticExpansionNode> nodes;
93+
int direction_changes{0};
94+
};
6595

6696
/**
6797
* @brief Constructor for analytic expansion object
@@ -136,6 +166,13 @@ class AnalyticExpansion
136166
const NodePtr & node, const NodePtr & goal,
137167
const AnalyticExpansionNodes & expanded_nodes);
138168

169+
/**
170+
* @brief Counts the number of direction changes in a Reeds-Shepp path
171+
* @param path The Reeds-Shepp path to count direction changes in
172+
* @return The number of direction changes in the path
173+
*/
174+
int countDirectionChanges(const ompl::base::ReedsSheppStateSpace::ReedsSheppPath & path);
175+
139176
/**
140177
* @brief Takes an expanded nodes to clean up, if necessary, of any state
141178
* information that may be polluting it from a prior search iteration

‎nav2_smac_planner/src/analytic_expansion.cpp‎

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License. Reserved.
1414

15-
#include <ompl/base/ScopedState.h>
16-
#include <ompl/base/spaces/DubinsStateSpace.h>
17-
#include <ompl/base/spaces/ReedsSheppStateSpace.h>
18-
1915
#include <algorithm>
2016
#include <vector>
2117
#include <memory>
@@ -64,7 +60,7 @@ typename AnalyticExpansion<NodeT>::NodePtr AnalyticExpansion<NodeT>::tryAnalytic
6460
NodeT::getCoords(
6561
current_node->getIndex(), _collision_checker->getCostmap()->getSizeInCellsX(), _dim_3_size);
6662

67-
AnalyticExpansionNodes current_best_analytic_nodes = {};
63+
AnalyticExpansionNodes current_best_analytic_nodes;
6864
NodePtr current_best_goal = nullptr;
6965
NodePtr current_best_node = nullptr;
7066
float current_best_score = std::numeric_limits<float>::max();
@@ -96,7 +92,7 @@ typename AnalyticExpansion<NodeT>::NodePtr AnalyticExpansion<NodeT>::tryAnalytic
9692
getAnalyticPath(
9793
current_node, current_goal_node, getter,
9894
current_node->motion_table.state_space);
99-
if (!analytic_nodes.empty()) {
95+
if (!analytic_nodes.nodes.empty()) {
10096
found_valid_expansion = true;
10197
NodePtr node = current_node;
10298
float score = refineAnalyticPath(
@@ -118,7 +114,7 @@ typename AnalyticExpansion<NodeT>::NodePtr AnalyticExpansion<NodeT>::tryAnalytic
118114
getAnalyticPath(
119115
current_node, current_goal_node, getter,
120116
current_node->motion_table.state_space);
121-
if (!analytic_nodes.empty()) {
117+
if (!analytic_nodes.nodes.empty()) {
122118
NodePtr node = current_node;
123119
float score = refineAnalyticPath(
124120
node, current_goal_node, getter, analytic_nodes);
@@ -134,7 +130,7 @@ typename AnalyticExpansion<NodeT>::NodePtr AnalyticExpansion<NodeT>::tryAnalytic
134130
}
135131
}
136132

137-
if (!current_best_analytic_nodes.empty()) {
133+
if (!current_best_analytic_nodes.nodes.empty()) {
138134
return setAnalyticPath(
139135
current_best_node, current_best_goal,
140136
current_best_analytic_nodes);
@@ -146,6 +142,28 @@ typename AnalyticExpansion<NodeT>::NodePtr AnalyticExpansion<NodeT>::tryAnalytic
146142
return NodePtr(nullptr);
147143
}
148144

145+
template<typename NodeT>
146+
int AnalyticExpansion<NodeT>::countDirectionChanges(
147+
const ompl::base::ReedsSheppStateSpace::ReedsSheppPath & path)
148+
{
149+
const double * lengths = path.length_;
150+
int changes = 0;
151+
int last_dir = 0;
152+
for (int i = 0; i < 5; ++i) {
153+
if (lengths[i] == 0.0) {
154+
continue;
155+
}
156+
157+
int currentDirection = (lengths[i] > 0.0) ? 1 : -1;
158+
if (last_dir != 0 && currentDirection != last_dir) {
159+
++changes;
160+
}
161+
last_dir = currentDirection;
162+
}
163+
164+
return changes;
165+
}
166+
149167
template<typename NodeT>
150168
typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<NodeT>::getAnalyticPath(
151169
const NodePtr & node,
@@ -163,6 +181,12 @@ typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<Node
163181

164182
float d = state_space->distance(from(), to());
165183

184+
auto rs_state_space = dynamic_cast<ompl::base::ReedsSheppStateSpace *>(state_space.get());
185+
int direction_changes = 0;
186+
if (rs_state_space) {
187+
direction_changes = countDirectionChanges(rs_state_space->reedsShepp(from.get(), to.get()));
188+
}
189+
166190
// A move of sqrt(2) is guaranteed to be in a new cell
167191
static const float sqrt_2 = sqrtf(2.0f);
168192

@@ -179,7 +203,7 @@ typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<Node
179203
AnalyticExpansionNodes possible_nodes;
180204
// When "from" and "to" are zero or one cell away,
181205
// num_intervals == 0
182-
possible_nodes.reserve(num_intervals); // We won't store this node or the goal
206+
possible_nodes.nodes.reserve(num_intervals); // We won't store this node or the goal
183207
std::vector<double> reals;
184208
double theta;
185209

@@ -214,7 +238,7 @@ typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<Node
214238
next->setPose(proposed_coordinates);
215239
if (next->isNodeValid(_traverse_unknown, _collision_checker) && next != prev) {
216240
// Save the node, and its previous coordinates in case we need to abort
217-
possible_nodes.emplace_back(next, initial_node_coords, proposed_coordinates);
241+
possible_nodes.add(next, initial_node_coords, proposed_coordinates);
218242
node_costs.emplace_back(next->getCost());
219243
prev = next;
220244
} else {
@@ -264,7 +288,7 @@ typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<Node
264288
}
265289

266290
// Reset to initial poses to not impact future searches
267-
for (const auto & node_pose : possible_nodes) {
291+
for (const auto & node_pose : possible_nodes.nodes) {
268292
const auto & n = node_pose.node;
269293
n->setPose(node_pose.initial_coords);
270294
}
@@ -273,6 +297,7 @@ typename AnalyticExpansion<NodeT>::AnalyticExpansionNodes AnalyticExpansion<Node
273297
return AnalyticExpansionNodes();
274298
}
275299

300+
possible_nodes.setDirectionChanges(direction_changes);
276301
return possible_nodes;
277302
}
278303

@@ -299,9 +324,13 @@ float AnalyticExpansion<NodeT>::refineAnalyticPath(
299324
getAnalyticPath(
300325
test_node, goal_node, getter,
301326
test_node->motion_table.state_space);
302-
if (refined_analytic_nodes.empty()) {
327+
if (refined_analytic_nodes.nodes.empty()) {
303328
break;
304329
}
330+
if (refined_analytic_nodes.direction_changes > analytic_nodes.direction_changes) {
331+
// If the direction changes are worse, we don't want to use this path
332+
continue;
333+
}
305334
analytic_nodes = refined_analytic_nodes;
306335
node = test_node;
307336
} else {
@@ -314,26 +343,27 @@ float AnalyticExpansion<NodeT>::refineAnalyticPath(
314343
// higher than the minimum turning radius and use the best solution based on
315344
// a scoring function similar to that used in traversal cost estimation.
316345
auto scoringFn = [&](const AnalyticExpansionNodes & expansion) {
317-
if (expansion.size() < 2) {
346+
if (expansion.nodes.size() < 2) {
318347
return std::numeric_limits<float>::max();
319348
}
320349

321350
float score = 0.0;
322351
float normalized_cost = 0.0;
323352
// Analytic expansions are consistently spaced
324353
const float distance = hypotf(
325-
expansion[1].proposed_coords.x - expansion[0].proposed_coords.x,
326-
expansion[1].proposed_coords.y - expansion[0].proposed_coords.y);
327-
const float & weight = expansion[0].node->motion_table.cost_penalty;
328-
for (auto iter = expansion.begin(); iter != expansion.end(); ++iter) {
354+
expansion.nodes[1].proposed_coords.x - expansion.nodes[0].proposed_coords.x,
355+
expansion.nodes[1].proposed_coords.y - expansion.nodes[0].proposed_coords.y);
356+
const float & weight = expansion.nodes[0].node->motion_table.cost_penalty;
357+
for (auto iter = expansion.nodes.begin(); iter != expansion.nodes.end(); ++iter) {
329358
normalized_cost = iter->node->getCost() / 252.0f;
330-
// Search's Traversal Cost Function
359+
// Search's Traversal Cost Function
331360
score += distance * (1.0 + weight * normalized_cost);
332361
}
333362
return score;
334363
};
335364

336-
float best_score = scoringFn(analytic_nodes);
365+
float original_score = scoringFn(analytic_nodes);
366+
float best_score = original_score;
337367
float score = std::numeric_limits<float>::max();
338368
float min_turn_rad = node->motion_table.min_turning_radius;
339369
const float max_min_turn_rad = 4.0 * min_turn_rad; // Up to 4x the turning radius
@@ -347,7 +377,21 @@ float AnalyticExpansion<NodeT>::refineAnalyticPath(
347377
}
348378
refined_analytic_nodes = getAnalyticPath(node, goal_node, getter, state_space);
349379
score = scoringFn(refined_analytic_nodes);
350-
if (score <= best_score) {
380+
381+
// Normal scoring: prioritize lower cost as long as not more directional changes
382+
if (score <= best_score &&
383+
refined_analytic_nodes.direction_changes <= analytic_nodes.direction_changes)
384+
{
385+
analytic_nodes = refined_analytic_nodes;
386+
best_score = score;
387+
continue;
388+
}
389+
390+
// Special case: If we have a better score than original (only) and less directional changes
391+
// the path quality is still better than the original and is less operationally complex
392+
if (score <= original_score &&
393+
refined_analytic_nodes.direction_changes < analytic_nodes.direction_changes)
394+
{
351395
analytic_nodes = refined_analytic_nodes;
352396
best_score = score;
353397
}
@@ -365,7 +409,7 @@ typename AnalyticExpansion<NodeT>::NodePtr AnalyticExpansion<NodeT>::setAnalytic
365409
_detached_nodes.clear();
366410
// Legitimate final path - set the parent relationships, states, and poses
367411
NodePtr prev = node;
368-
for (const auto & node_pose : expanded_nodes) {
412+
for (const auto & node_pose : expanded_nodes.nodes) {
369413
auto n = node_pose.node;
370414
cleanNode(n);
371415
if (n->getIndex() != goal_node->getIndex()) {

‎nav2_smac_planner/test/test_a_star.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ TEST(AStarTest, test_a_star_2d)
147147
analytic_expansion_nodes), std::numeric_limits<float>::max());
148148
nav2_smac_planner::AnalyticExpansion<nav2_smac_planner::Node2D>::AnalyticExpansionNodes
149149
expected_nodes = expander.getAnalyticPath(nullptr, nullptr, nullptr, nullptr);
150-
EXPECT_EQ(expected_nodes.size(), 0);
150+
EXPECT_EQ(expected_nodes.nodes.size(), 0);
151151

152152
delete costmapA;
153153
}

0 commit comments

Comments
 (0)