add humble-navigation2

This commit is contained in:
X-lanni
2025-05-27 19:03:40 +08:00
parent 974abb5e1e
commit e74ec539c2
1280 changed files with 204114 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

@@ -0,0 +1,118 @@
# Test costmap downsampler
ament_add_gtest(test_costmap_downsampler
test_costmap_downsampler.cpp
)
ament_target_dependencies(test_costmap_downsampler
${dependencies}
)
target_link_libraries(test_costmap_downsampler
${library_name}
)
# Test Node2D
ament_add_gtest(test_node2d
test_node2d.cpp
)
ament_target_dependencies(test_node2d
${dependencies}
)
target_link_libraries(test_node2d
${library_name}
)
# Test NodeHybrid
ament_add_gtest(test_nodehybrid
test_nodehybrid.cpp
)
ament_target_dependencies(test_nodehybrid
${dependencies}
)
target_link_libraries(test_nodehybrid
${library_name}
)
# Test NodeBasic
ament_add_gtest(test_nodebasic
test_nodebasic.cpp
)
ament_target_dependencies(test_nodebasic
${dependencies}
)
target_link_libraries(test_nodebasic
${library_name}
)
# Test collision checker
ament_add_gtest(test_collision_checker
test_collision_checker.cpp
)
ament_target_dependencies(test_collision_checker
${dependencies}
)
target_link_libraries(test_collision_checker
${library_name}
)
# Test A*
ament_add_gtest(test_a_star
test_a_star.cpp
)
ament_target_dependencies(test_a_star
${dependencies}
)
target_link_libraries(test_a_star
${library_name}
)
# Test SMAC Hybrid
ament_add_gtest(test_smac_hybrid
test_smac_hybrid.cpp
)
ament_target_dependencies(test_smac_hybrid
${dependencies}
)
target_link_libraries(test_smac_hybrid
${library_name}
)
# Test SMAC 2D
ament_add_gtest(test_smac_2d
test_smac_2d.cpp
)
ament_target_dependencies(test_smac_2d
${dependencies}
)
target_link_libraries(test_smac_2d
${library_name}_2d
)
# Test SMAC lattice
ament_add_gtest(test_smac_lattice
test_smac_lattice.cpp
)
ament_target_dependencies(test_smac_lattice
${dependencies}
)
target_link_libraries(test_smac_lattice
${library_name}_lattice
)
# Test SMAC Smoother
ament_add_gtest(test_smoother
test_smoother.cpp
)
ament_target_dependencies(test_smoother
${dependencies}
)
target_link_libraries(test_smoother
${library_name}_lattice
${library_name}
${library_name}_2d
)
#Test Lattice node
ament_add_gtest(test_lattice_node test_nodelattice.cpp)
ament_target_dependencies(test_lattice_node ${dependencies})
target_link_libraries(test_lattice_node ${library_name})
@@ -0,0 +1,207 @@
// Copyright (c) 2020, Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#ifndef DEPRECATED__OPTIONS_HPP_
#define DEPRECATED__OPTIONS_HPP_
#include <string>
#include "rclcpp_lifecycle/lifecycle_node.hpp"
#include "nav2_util/node_utils.hpp"
namespace nav2_smac_planner
{
/**
* @struct nav2_smac_planner::SmootherParams
* @brief Parameters for the smoother cost function
*/
struct SmootherParams
{
/**
* @brief A constructor for nav2_smac_planner::SmootherParams
*/
SmootherParams()
{
}
/**
* @brief Get params from ROS parameter
* @param node_ Ptr to node
* @param name Name of plugin
*/
void get(rclcpp_lifecycle::LifecycleNode * node, const std::string & name)
{
std::string local_name = name + std::string(".smoother.smoother.");
// Smoother params
nav2_util::declare_parameter_if_not_declared(
node, local_name + "w_curve", rclcpp::ParameterValue(1.5));
node->get_parameter(local_name + "w_curve", curvature_weight);
nav2_util::declare_parameter_if_not_declared(
node, local_name + "w_cost", rclcpp::ParameterValue(0.0));
node->get_parameter(local_name + "w_cost", costmap_weight);
nav2_util::declare_parameter_if_not_declared(
node, local_name + "w_dist", rclcpp::ParameterValue(0.0));
node->get_parameter(local_name + "w_dist", distance_weight);
nav2_util::declare_parameter_if_not_declared(
node, local_name + "w_smooth", rclcpp::ParameterValue(15000.0));
node->get_parameter(local_name + "w_smooth", smooth_weight);
nav2_util::declare_parameter_if_not_declared(
node, local_name + "cost_scaling_factor", rclcpp::ParameterValue(10.0));
node->get_parameter(local_name + "cost_scaling_factor", costmap_factor);
}
double smooth_weight{0.0};
double costmap_weight{0.0};
double distance_weight{0.0};
double curvature_weight{0.0};
double max_curvature{0.0};
double costmap_factor{0.0};
double max_time;
};
/**
* @struct nav2_smac_planner::OptimizerParams
* @brief Parameters for the ceres optimizer
*/
struct OptimizerParams
{
OptimizerParams()
: debug(false),
max_iterations(50),
max_time(1e4),
param_tol(1e-8),
fn_tol(1e-6),
gradient_tol(1e-10)
{
}
/**
* @struct AdvancedParams
* @brief Advanced parameters for the ceres optimizer
*/
struct AdvancedParams
{
AdvancedParams()
: min_line_search_step_size(1e-9),
max_num_line_search_step_size_iterations(20),
line_search_sufficient_function_decrease(1e-4),
max_num_line_search_direction_restarts(20),
max_line_search_step_contraction(1e-3),
min_line_search_step_contraction(0.6),
line_search_sufficient_curvature_decrease(0.9),
max_line_search_step_expansion(10)
{
}
/**
* @brief Get advanced params from ROS parameter
* @param node_ Ptr to node
* @param name Name of plugin
*/
void get(rclcpp_lifecycle::LifecycleNode * node, const std::string & name)
{
std::string local_name = name + std::string(".smoother.optimizer.advanced.");
// Optimizer advanced params
nav2_util::declare_parameter_if_not_declared(
node, local_name + "min_line_search_step_size",
rclcpp::ParameterValue(1e-20));
node->get_parameter(
local_name + "min_line_search_step_size",
min_line_search_step_size);
nav2_util::declare_parameter_if_not_declared(
node, local_name + "max_num_line_search_step_size_iterations",
rclcpp::ParameterValue(50));
node->get_parameter(
local_name + "max_num_line_search_step_size_iterations",
max_num_line_search_step_size_iterations);
nav2_util::declare_parameter_if_not_declared(
node, local_name + "line_search_sufficient_function_decrease",
rclcpp::ParameterValue(1e-20));
node->get_parameter(
local_name + "line_search_sufficient_function_decrease",
line_search_sufficient_function_decrease);
nav2_util::declare_parameter_if_not_declared(
node, local_name + "max_num_line_search_direction_restarts",
rclcpp::ParameterValue(10));
node->get_parameter(
local_name + "max_num_line_search_direction_restarts",
max_num_line_search_direction_restarts);
nav2_util::declare_parameter_if_not_declared(
node, local_name + "max_line_search_step_expansion",
rclcpp::ParameterValue(50));
node->get_parameter(
local_name + "max_line_search_step_expansion",
max_line_search_step_expansion);
}
double min_line_search_step_size; // Ceres default: 1e-9
int max_num_line_search_step_size_iterations; // Ceres default: 20
double line_search_sufficient_function_decrease; // Ceres default: 1e-4
int max_num_line_search_direction_restarts; // Ceres default: 5
double max_line_search_step_contraction; // Ceres default: 1e-3
double min_line_search_step_contraction; // Ceres default: 0.6
double line_search_sufficient_curvature_decrease; // Ceres default: 0.9
int max_line_search_step_expansion; // Ceres default: 10
};
/**
* @brief Get params from ROS parameter
* @param node_ Ptr to node
* @param name Name of plugin
*/
void get(rclcpp_lifecycle::LifecycleNode * node, const std::string & name)
{
std::string local_name = name + std::string(".smoother.optimizer.");
// Optimizer params
nav2_util::declare_parameter_if_not_declared(
node, local_name + "param_tol", rclcpp::ParameterValue(1e-15));
node->get_parameter(local_name + "param_tol", param_tol);
nav2_util::declare_parameter_if_not_declared(
node, local_name + "fn_tol", rclcpp::ParameterValue(1e-7));
node->get_parameter(local_name + "fn_tol", fn_tol);
nav2_util::declare_parameter_if_not_declared(
node, local_name + "gradient_tol", rclcpp::ParameterValue(1e-10));
node->get_parameter(local_name + "gradient_tol", gradient_tol);
nav2_util::declare_parameter_if_not_declared(
node, local_name + "max_iterations", rclcpp::ParameterValue(500));
node->get_parameter(local_name + "max_iterations", max_iterations);
nav2_util::declare_parameter_if_not_declared(
node, local_name + "max_time", rclcpp::ParameterValue(0.100));
node->get_parameter(local_name + "max_time", max_time);
nav2_util::declare_parameter_if_not_declared(
node, local_name + "debug_optimizer", rclcpp::ParameterValue(false));
node->get_parameter(local_name + "debug_optimizer", debug);
advanced.get(node, name);
}
bool debug;
int max_iterations; // Ceres default: 50
double max_time; // Ceres default: 1e4
double param_tol; // Ceres default: 1e-8
double fn_tol; // Ceres default: 1e-6
double gradient_tol; // Ceres default: 1e-10
AdvancedParams advanced;
};
} // namespace nav2_smac_planner
#endif // DEPRECATED__OPTIONS_HPP_
@@ -0,0 +1,146 @@
// Copyright (c) 2020, Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#ifndef DEPRECATED__SMOOTHER_HPP_
#define DEPRECATED__SMOOTHER_HPP_
#include <cmath>
#include <vector>
#include <iostream>
#include <memory>
#include <queue>
#include <utility>
#include "nav2_smac_planner/types.hpp"
#include "nav2_smac_planner/smoother_cost_function.hpp"
#include "ceres/ceres.h"
#include "Eigen/Core"
namespace nav2_smac_planner
{
/**
* @class nav2_smac_planner::Smoother
* @brief A Conjugate Gradient 2D path smoother implementation
*/
class Smoother
{
public:
/**
* @brief A constructor for nav2_smac_planner::Smoother
*/
Smoother() {}
/**
* @brief A destructor for nav2_smac_planner::Smoother
*/
~Smoother() {}
/**
* @brief Initialization of the smoother
* @param params OptimizerParam struct
*/
void initialize(const OptimizerParams params)
{
_debug = params.debug;
// General Params
// 2 most valid options: STEEPEST_DESCENT, NONLINEAR_CONJUGATE_GRADIENT
_options.line_search_direction_type = ceres::NONLINEAR_CONJUGATE_GRADIENT;
_options.line_search_type = ceres::WOLFE;
_options.nonlinear_conjugate_gradient_type = ceres::POLAK_RIBIERE;
_options.line_search_interpolation_type = ceres::CUBIC;
_options.max_num_iterations = params.max_iterations;
_options.max_solver_time_in_seconds = params.max_time;
_options.function_tolerance = params.fn_tol;
_options.gradient_tolerance = params.gradient_tol;
_options.parameter_tolerance = params.param_tol;
_options.min_line_search_step_size = params.advanced.min_line_search_step_size;
_options.max_num_line_search_step_size_iterations =
params.advanced.max_num_line_search_step_size_iterations;
_options.line_search_sufficient_function_decrease =
params.advanced.line_search_sufficient_function_decrease;
_options.max_line_search_step_contraction = params.advanced.max_line_search_step_contraction;
_options.min_line_search_step_contraction = params.advanced.min_line_search_step_contraction;
_options.max_num_line_search_direction_restarts =
params.advanced.max_num_line_search_direction_restarts;
_options.line_search_sufficient_curvature_decrease =
params.advanced.line_search_sufficient_curvature_decrease;
_options.max_line_search_step_expansion = params.advanced.max_line_search_step_expansion;
if (_debug) {
_options.minimizer_progress_to_stdout = true;
} else {
_options.logging_type = ceres::SILENT;
}
}
/**
* @brief Smoother method
* @param path Reference to path
* @param costmap Pointer to minimal costmap
* @param smoother parameters weights
* @return If smoothing was successful
*/
bool smooth(
std::vector<Eigen::Vector2d> & path,
nav2_costmap_2d::Costmap2D * costmap,
const SmootherParams & params)
{
_options.max_solver_time_in_seconds = params.max_time;
#ifdef _MSC_VER
std::vector<double> parameters_vec(path.size() * 2);
double * parameters = parameters_vec.data();
#else
double parameters[path.size() * 2]; // NOLINT
#endif
for (unsigned int i = 0; i != path.size(); i++) {
parameters[2 * i] = path[i][0];
parameters[2 * i + 1] = path[i][1];
}
ceres::GradientProblemSolver::Summary summary;
ceres::GradientProblem problem(new UnconstrainedSmootherCostFunction(&path, costmap, params));
ceres::Solve(_options, problem, parameters, &summary);
if (_debug) {
std::cout << summary.FullReport() << '\n';
}
if (!summary.IsSolutionUsable() || summary.initial_cost - summary.final_cost <= 0.0) {
return false;
}
for (unsigned int i = 0; i != path.size(); i++) {
path[i][0] = parameters[2 * i];
path[i][1] = parameters[2 * i + 1];
}
return true;
}
private:
bool _debug;
ceres::GradientProblemSolver::Options _options;
};
} // namespace nav2_smac_planner
#endif // DEPRECATED__SMOOTHER_HPP_
@@ -0,0 +1,542 @@
// Copyright (c) 2020, Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#ifndef DEPRECATED__SMOOTHER_COST_FUNCTION_HPP_
#define DEPRECATED__SMOOTHER_COST_FUNCTION_HPP_
#include <cmath>
#include <vector>
#include <iostream>
#include <unordered_map>
#include <memory>
#include <queue>
#include <utility>
#include "ceres/ceres.h"
#include "Eigen/Core"
#include "nav2_smac_planner/types.hpp"
#include "nav2_costmap_2d/costmap_2d.hpp"
#include "nav2_smac_planner/options.hpp"
#define EPSILON 0.0001
namespace nav2_smac_planner
{
/**
* @struct nav2_smac_planner::UnconstrainedSmootherCostFunction
* @brief Cost function for path smoothing with multiple terms
* including curvature, smoothness, collision, and avoid obstacles.
*/
class UnconstrainedSmootherCostFunction : public ceres::FirstOrderFunction
{
public:
/**
* @brief A constructor for nav2_smac_planner::UnconstrainedSmootherCostFunction
* @param original_path Original unsmoothed path to smooth
* @param costmap A costmap to get values for collision and obstacle avoidance
*/
UnconstrainedSmootherCostFunction(
std::vector<Eigen::Vector2d> * original_path,
nav2_costmap_2d::Costmap2D * costmap,
const SmootherParams & params)
: _original_path(original_path),
_num_params(2 * original_path->size()),
_costmap(costmap),
_params(params)
{
// int height = costmap->getSizeInCellsX();
// int width = costmap->getSizeInCellsY();
// bool** binMap;
// binMap = new bool*[width];
// for (int x = 0; x < width; x++) { binMap[x] = new bool[height]; }
// for (int x = 0; x < width; ++x) {
// for (int y = 0; y < height; ++y) {
// binMap[x][y] = costmap->getCost(x,y) >= 253 ? true : false;
// }
// }
// voronoiDiagram.initializeMap(width, height, binMap);
// voronoiDiagram.update();
// voronoiDiagram.visualize();
}
/**
* @struct CurvatureComputations
* @brief Cache common computations between the curvature terms to minimize recomputations
*/
struct CurvatureComputations
{
/**
* @brief A constructor for nav2_smac_planner::CurvatureComputations
*/
CurvatureComputations()
{
valid = true;
}
bool valid;
/**
* @brief Check if result is valid for penalty
* @return is valid (non-nan, non-inf, and turning angle > max)
*/
bool isValid()
{
return valid;
}
Eigen::Vector2d delta_xi{0.0, 0.0};
Eigen::Vector2d delta_xi_p{0.0, 0.0};
double delta_xi_norm{0};
double delta_xi_p_norm{0};
double delta_phi_i{0};
double turning_rad{0};
double ki_minus_kmax{0};
};
/**
* @brief Smoother cost function evaluation
* @param parameters X,Y pairs of points
* @param cost total cost of path
* @param gradient of path at each X,Y pair from cost function derived analytically
* @return if successful in computing values
*/
virtual bool Evaluate(
const double * parameters,
double * cost,
double * gradient) const
{
Eigen::Vector2d xi;
Eigen::Vector2d xi_p1;
Eigen::Vector2d xi_m1;
unsigned int x_index, y_index;
cost[0] = 0.0;
double cost_raw = 0.0;
double grad_x_raw = 0.0;
double grad_y_raw = 0.0;
unsigned int mx, my;
bool valid_coords = true;
double costmap_cost = 0.0;
// cache some computations between the residual and jacobian
CurvatureComputations curvature_params;
for (int i = 0; i != NumParameters() / 2; i++) {
x_index = 2 * i;
y_index = 2 * i + 1;
gradient[x_index] = 0.0;
gradient[y_index] = 0.0;
if (i < 1 || i >= NumParameters() / 2 - 1) {
continue;
}
xi = Eigen::Vector2d(parameters[x_index], parameters[y_index]);
xi_p1 = Eigen::Vector2d(parameters[x_index + 2], parameters[y_index + 2]);
xi_m1 = Eigen::Vector2d(parameters[x_index - 2], parameters[y_index - 2]);
// compute cost
addSmoothingResidual(_params.smooth_weight, xi, xi_p1, xi_m1, cost_raw);
addCurvatureResidual(_params.curvature_weight, xi, xi_p1, xi_m1, curvature_params, cost_raw);
addDistanceResidual(_params.distance_weight, xi, _original_path->at(i), cost_raw);
if (valid_coords = _costmap->worldToMap(xi[0], xi[1], mx, my)) {
costmap_cost = _costmap->getCost(mx, my);
addCostResidual(_params.costmap_weight, costmap_cost, cost_raw, xi);
}
if (gradient != NULL) {
// compute gradient
gradient[x_index] = 0.0;
gradient[y_index] = 0.0;
addSmoothingJacobian(_params.smooth_weight, xi, xi_p1, xi_m1, grad_x_raw, grad_y_raw);
addCurvatureJacobian(
_params.curvature_weight, xi, xi_p1, xi_m1, curvature_params,
grad_x_raw, grad_y_raw);
addDistanceJacobian(
_params.distance_weight, xi, _original_path->at(
i), grad_x_raw, grad_y_raw);
if (valid_coords) {
addCostJacobian(_params.costmap_weight, mx, my, costmap_cost, grad_x_raw, grad_y_raw);
}
gradient[x_index] = grad_x_raw;
gradient[y_index] = grad_y_raw;
}
}
cost[0] = cost_raw;
return true;
}
/**
* @brief Get number of parameter blocks
* @return Number of parameters in cost function
*/
virtual int NumParameters() const {return _num_params;}
protected:
/**
* @brief Cost function term for smooth paths
* @param weight Weight to apply to function
* @param pt Point Xi for evaluation
* @param pt Point Xi+1 for calculating Xi's cost
* @param pt Point Xi-1 for calculating Xi's cost
* @param r Residual (cost) of term
*/
inline void addSmoothingResidual(
const double & weight,
const Eigen::Vector2d & pt,
const Eigen::Vector2d & pt_p,
const Eigen::Vector2d & pt_m,
double & r) const
{
r += weight * (
pt_p.dot(pt_p) -
4 * pt_p.dot(pt) +
2 * pt_p.dot(pt_m) +
4 * pt.dot(pt) -
4 * pt.dot(pt_m) +
pt_m.dot(pt_m)); // objective function value
}
/**
* @brief Cost function derivative term for smooth paths
* @param weight Weight to apply to function
* @param pt Point Xi for evaluation
* @param pt Point Xi+1 for calculating Xi's cost
* @param pt Point Xi-1 for calculating Xi's cost
* @param j0 Gradient of X term
* @param j1 Gradient of Y term
*/
inline void addSmoothingJacobian(
const double & weight,
const Eigen::Vector2d & pt,
const Eigen::Vector2d & pt_p,
const Eigen::Vector2d & pt_m,
double & j0,
double & j1) const
{
j0 += weight *
(-4 * pt_m[0] + 8 * pt[0] - 4 * pt_p[0]); // xi x component of partial-derivative
j1 += weight *
(-4 * pt_m[1] + 8 * pt[1] - 4 * pt_p[1]); // xi y component of partial-derivative
}
/**
* @brief Cost function term for maximum curved paths
* @param weight Weight to apply to function
* @param pt Point Xi for evaluation
* @param pt Point Xi+1 for calculating Xi's cost
* @param pt Point Xi-1 for calculating Xi's cost
* @param curvature_params A struct to cache computations for the jacobian to use
* @param r Residual (cost) of term
*/
inline void addCurvatureResidual(
const double & weight,
const Eigen::Vector2d & pt,
const Eigen::Vector2d & pt_p,
const Eigen::Vector2d & pt_m,
CurvatureComputations & curvature_params,
double & r) const
{
curvature_params.valid = true;
curvature_params.delta_xi = Eigen::Vector2d(pt[0] - pt_m[0], pt[1] - pt_m[1]);
curvature_params.delta_xi_p = Eigen::Vector2d(pt_p[0] - pt[0], pt_p[1] - pt[1]);
curvature_params.delta_xi_norm = curvature_params.delta_xi.norm();
curvature_params.delta_xi_p_norm = curvature_params.delta_xi_p.norm();
if (curvature_params.delta_xi_norm < EPSILON || curvature_params.delta_xi_p_norm < EPSILON ||
std::isnan(curvature_params.delta_xi_p_norm) || std::isnan(curvature_params.delta_xi_norm) ||
std::isinf(curvature_params.delta_xi_p_norm) || std::isinf(curvature_params.delta_xi_norm))
{
// ensure we have non-nan values returned
curvature_params.valid = false;
return;
}
const double & delta_xi_by_xi_p =
curvature_params.delta_xi_norm * curvature_params.delta_xi_p_norm;
double projection =
curvature_params.delta_xi.dot(curvature_params.delta_xi_p) / delta_xi_by_xi_p;
if (fabs(1 - projection) < EPSILON || fabs(projection + 1) < EPSILON) {
projection = 1.0;
}
curvature_params.delta_phi_i = std::acos(projection);
curvature_params.turning_rad = curvature_params.delta_phi_i / curvature_params.delta_xi_norm;
curvature_params.ki_minus_kmax = curvature_params.turning_rad - _params.max_curvature;
if (curvature_params.ki_minus_kmax <= EPSILON) {
// Quadratic penalty need not apply
curvature_params.valid = false;
return;
}
r += weight *
curvature_params.ki_minus_kmax * curvature_params.ki_minus_kmax; // objective function value
}
/**
* @brief Cost function derivative term for maximum curvature paths
* @param weight Weight to apply to function
* @param pt Point Xi for evaluation
* @param pt Point Xi+1 for calculating Xi's cost
* @param pt Point Xi-1 for calculating Xi's cost
* @param curvature_params A struct with cached values to speed up Jacobian computation
* @param j0 Gradient of X term
* @param j1 Gradient of Y term
*/
inline void addCurvatureJacobian(
const double & weight,
const Eigen::Vector2d & pt,
const Eigen::Vector2d & pt_p,
const Eigen::Vector2d & /*pt_m*/,
CurvatureComputations & curvature_params,
double & j0,
double & j1) const
{
if (!curvature_params.isValid()) {
return;
}
const double & partial_delta_phi_i_wrt_cost_delta_phi_i =
-1 / std::sqrt(1 - std::pow(std::cos(curvature_params.delta_phi_i), 2));
// const Eigen::Vector2d ones = Eigen::Vector2d(1.0, 1.0);
auto neg_pt_plus = -1 * pt_p;
Eigen::Vector2d p1 = normalizedOrthogonalComplement(
pt, neg_pt_plus, curvature_params.delta_xi_norm, curvature_params.delta_xi_p_norm);
Eigen::Vector2d p2 = normalizedOrthogonalComplement(
neg_pt_plus, pt, curvature_params.delta_xi_p_norm, curvature_params.delta_xi_norm);
const double & u = 2 * curvature_params.ki_minus_kmax;
const double & common_prefix =
(1 / curvature_params.delta_xi_norm) * partial_delta_phi_i_wrt_cost_delta_phi_i;
const double & common_suffix = curvature_params.delta_phi_i /
(curvature_params.delta_xi_norm * curvature_params.delta_xi_norm);
const Eigen::Vector2d & d_delta_xi_d_xi = curvature_params.delta_xi /
curvature_params.delta_xi_norm;
const Eigen::Vector2d jacobian = u *
(common_prefix * (-p1 - p2) - (common_suffix * d_delta_xi_d_xi));
const Eigen::Vector2d jacobian_im1 = u *
(common_prefix * p2 + (common_suffix * d_delta_xi_d_xi));
const Eigen::Vector2d jacobian_ip1 = u * (common_prefix * p1);
// Old formulation we may require again.
// j0 += weight *
// (jacobian_im1[0] + 2 * jacobian[0] + jacobian_ip1[0]);
// j1 += weight *
// (jacobian_im1[1] + 2 * jacobian[1] + jacobian_ip1[1]);
j0 += weight * jacobian[0]; // xi x component of partial-derivative
j1 += weight * jacobian[1]; // xi x component of partial-derivative
}
/**
* @brief Cost function derivative term for steering away changes in pose
* @param weight Weight to apply to function
* @param xi Point Xi for evaluation
* @param xi_original original point Xi for evaluation
* @param r Residual (cost) of term
*/
inline void addDistanceResidual(
const double & weight,
const Eigen::Vector2d & xi,
const Eigen::Vector2d & xi_original,
double & r) const
{
r += weight * (xi - xi_original).dot(xi - xi_original); // objective function value
}
/**
* @brief Cost function derivative term for steering away changes in pose
* @param weight Weight to apply to function
* @param xi Point Xi for evaluation
* @param xi_original original point Xi for evaluation
* @param j0 Gradient of X term
* @param j1 Gradient of Y term
*/
inline void addDistanceJacobian(
const double & weight,
const Eigen::Vector2d & xi,
const Eigen::Vector2d & xi_original,
double & j0,
double & j1) const
{
j0 += weight * 2 * (xi[0] - xi_original[0]); // xi y component of partial-derivative
j1 += weight * 2 * (xi[1] - xi_original[1]); // xi y component of partial-derivative
}
/**
* @brief Cost function term for steering away from costs
* @param weight Weight to apply to function
* @param value Point Xi's cost'
* @param params computed values to reduce overhead
* @param r Residual (cost) of term
*/
inline void addCostResidual(
const double & weight,
const double & value,
double & r,
Eigen::Vector2d & xi) const
{
if (value == FREE) {
return;
}
r += weight * value * value; // objective function value
// float obsDst = voronoiDiagram.getDistance((int)xi[0], (int)xi[1]);
// if (abs(obsDst) > 0.3) {
// return;
// }
// r += weight * (abs(obsDst) - 0.3) * (abs(obsDst) - 0.3);
}
/**
* @brief Cost function derivative term for steering away from costs
* @param weight Weight to apply to function
* @param mx Point Xi's x coordinate in map frame
* @param mx Point Xi's y coordinate in map frame
* @param value Point Xi's cost'
* @param params computed values to reduce overhead
* @param j0 Gradient of X term
* @param j1 Gradient of Y term
*/
inline void addCostJacobian(
const double & weight,
const unsigned int & mx,
const unsigned int & my,
const double & value,
double & j0,
double & j1) const
{
if (value == FREE) {
return;
}
const Eigen::Vector2d grad = getCostmapGradient(mx, my);
const double common_prefix = -2.0 * _params.costmap_factor * weight * value * value;
j0 += common_prefix * grad[0]; // xi x component of partial-derivative
j1 += common_prefix * grad[1]; // xi y component of partial-derivative
}
/**
* @brief Computing the gradient of the costmap using
* the 2 point numerical differentiation method
* @param mx Point Xi's x coordinate in map frame
* @param mx Point Xi's y coordinate in map frame
* @param params Params reference to store gradients
*/
inline Eigen::Vector2d getCostmapGradient(
const unsigned int mx,
const unsigned int my) const
{
// find unit vector that describes that direction
// via 7 point taylor series approximation for gradient at Xi
Eigen::Vector2d gradient;
double l_1 = 0.0;
double l_2 = 0.0;
double l_3 = 0.0;
double r_1 = 0.0;
double r_2 = 0.0;
double r_3 = 0.0;
if (mx < _costmap->getSizeInCellsX()) {
r_1 = static_cast<double>(_costmap->getCost(mx + 1, my));
}
if (mx + 1 < _costmap->getSizeInCellsX()) {
r_2 = static_cast<double>(_costmap->getCost(mx + 2, my));
}
if (mx + 2 < _costmap->getSizeInCellsX()) {
r_3 = static_cast<double>(_costmap->getCost(mx + 3, my));
}
if (mx > 0) {
l_1 = static_cast<double>(_costmap->getCost(mx - 1, my));
}
if (mx - 1 > 0) {
l_2 = static_cast<double>(_costmap->getCost(mx - 2, my));
}
if (mx - 2 > 0) {
l_3 = static_cast<double>(_costmap->getCost(mx - 3, my));
}
gradient[1] = (45 * r_1 - 9 * r_2 + r_3 - 45 * l_1 + 9 * l_2 - l_3) / 60;
if (my < _costmap->getSizeInCellsY()) {
r_1 = static_cast<double>(_costmap->getCost(mx, my + 1));
}
if (my + 1 < _costmap->getSizeInCellsY()) {
r_2 = static_cast<double>(_costmap->getCost(mx, my + 2));
}
if (my + 2 < _costmap->getSizeInCellsY()) {
r_3 = static_cast<double>(_costmap->getCost(mx, my + 3));
}
if (my > 0) {
l_1 = static_cast<double>(_costmap->getCost(mx, my - 1));
}
if (my - 1 > 0) {
l_2 = static_cast<double>(_costmap->getCost(mx, my - 2));
}
if (my - 2 > 0) {
l_3 = static_cast<double>(_costmap->getCost(mx, my - 3));
}
gradient[0] = (45 * r_1 - 9 * r_2 + r_3 - 45 * l_1 + 9 * l_2 - l_3) / 60;
gradient.normalize();
return gradient;
}
/**
* @brief Computing the normalized orthogonal component of 2 vectors
* @param a Vector
* @param b Vector
* @param norm a Vector's norm
* @param norm b Vector's norm
* @return Normalized vector of orthogonal components
*/
inline Eigen::Vector2d normalizedOrthogonalComplement(
const Eigen::Vector2d & a,
const Eigen::Vector2d & b,
const double & a_norm,
const double & b_norm) const
{
return (a - (a.dot(b) * b / b.squaredNorm())) / (a_norm * b_norm);
}
std::vector<Eigen::Vector2d> * _original_path{nullptr};
int _num_params;
nav2_costmap_2d::Costmap2D * _costmap{nullptr};
SmootherParams _params;
// DynamicVoronoi voronoiDiagram;
};
} // namespace nav2_smac_planner
#endif // DEPRECATED__SMOOTHER_COST_FUNCTION_HPP_
@@ -0,0 +1,213 @@
// Copyright (c) 2020, Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#ifndef DEPRECATED__UPSAMPLER_HPP_
#define DEPRECATED__UPSAMPLER_HPP_
#include <cmath>
#include <vector>
#include <iostream>
#include <memory>
#include <queue>
#include <algorithm>
#include <utility>
#include "nav2_smac_planner/types.hpp"
#include "nav2_smac_planner/upsampler_cost_function.hpp"
#include "nav2_smac_planner/upsampler_cost_function_nlls.hpp"
#include "ceres/ceres.h"
#include "Eigen/Core"
namespace nav2_smac_planner
{
/**
* @class nav2_smac_planner::Upsampler
* @brief A Conjugate Gradient 2D path upsampler implementation
*/
class Upsampler
{
public:
/**
* @brief A constructor for nav2_smac_planner::Upsampler
*/
Upsampler() {}
/**
* @brief A destructor for nav2_smac_planner::Upsampler
*/
~Upsampler() {}
/**
* @brief Initialization of the Upsampler
*/
void initialize(const OptimizerParams params)
{
_debug = params.debug;
// General Params
// 2 most valid options: STEEPEST_DESCENT, NONLINEAR_CONJUGATE_GRADIENT
_options.line_search_direction_type = ceres::NONLINEAR_CONJUGATE_GRADIENT;
_options.line_search_type = ceres::WOLFE;
_options.nonlinear_conjugate_gradient_type = ceres::POLAK_RIBIERE;
_options.line_search_interpolation_type = ceres::CUBIC;
_options.max_num_iterations = params.max_iterations; // 5000
_options.max_solver_time_in_seconds = params.max_time; // 5.0; // TODO
_options.function_tolerance = params.fn_tol;
_options.gradient_tolerance = params.gradient_tol;
_options.parameter_tolerance = params.param_tol; // 1e-20;
_options.min_line_search_step_size = params.advanced.min_line_search_step_size; // 1e-30;
_options.max_num_line_search_step_size_iterations =
params.advanced.max_num_line_search_step_size_iterations;
_options.line_search_sufficient_function_decrease =
params.advanced.line_search_sufficient_function_decrease; // 1e-30;
_options.max_line_search_step_contraction = params.advanced.max_line_search_step_contraction;
_options.min_line_search_step_contraction = params.advanced.min_line_search_step_contraction;
_options.max_num_line_search_direction_restarts =
params.advanced.max_num_line_search_direction_restarts;
_options.line_search_sufficient_curvature_decrease =
params.advanced.line_search_sufficient_curvature_decrease;
_options.max_line_search_step_expansion = params.advanced.max_line_search_step_expansion;
if (_debug) {
_options.minimizer_progress_to_stdout = true;
} else {
_options.logging_type = ceres::SILENT;
}
}
/**
* @brief Upsampling method
* @param path Reference to path
* @param upsample parameters weights
* @param upsample_ratio upsample ratio
* @return If Upsampler was successful
*/
bool upsample(
std::vector<Eigen::Vector2d> & path,
const SmootherParams & params,
const int & upsample_ratio)
{
_options.max_solver_time_in_seconds = params.max_time;
if (upsample_ratio != 2 && upsample_ratio != 4) {
// invalid inputs
return false;
}
const int param_ratio = upsample_ratio * 2.0;
const int total_size = 2 * (path.size() * upsample_ratio - upsample_ratio + 1);
double parameters[total_size]; // NOLINT
// 20-4hz regularly, but dosnt work in faster cases
// Linearly distribute initial poses for optimization
// TODO(stevemacenski) generalize for 2x and 4x
unsigned int next_pt;
Eigen::Vector2d interpolated;
std::vector<Eigen::Vector2d> temp_path;
for (unsigned int pt = 0; pt != path.size() - 1; pt++) {
next_pt = pt + 1;
interpolated = (path[next_pt] + path[pt]) / 2.0;
parameters[param_ratio * pt] = path[pt][0];
parameters[param_ratio * pt + 1] = path[pt][1];
temp_path.push_back(path[pt]);
parameters[param_ratio * pt + 2] = interpolated[0];
parameters[param_ratio * pt + 3] = interpolated[1];
temp_path.push_back(interpolated);
}
parameters[total_size - 2] = path.back()[0];
parameters[total_size - 1] = path.back()[1];
temp_path.push_back(path.back());
// Solve the upsampling problem
ceres::GradientProblemSolver::Summary summary;
ceres::GradientProblem problem(new UpsamplerCostFunction(temp_path, params, upsample_ratio));
ceres::Solve(_options, problem, parameters, &summary);
path.resize(total_size / 2);
for (int i = 0; i != total_size / 2; i++) {
path[i][0] = parameters[2 * i];
path[i][1] = parameters[2 * i + 1];
}
// 10-15 hz, regularly
// std::vector<Eigen::Vector2d> path_double_sampled;
// for (int i = 0; i != path.size() - 1; i++) { // last term should not be upsampled
// path_double_sampled.push_back(path[i]);
// path_double_sampled.push_back((path[i+1] + path[i]) / 2);
// }
// std::unique_ptr<ceres::Problem> problem = std::make_unique<ceres::Problem>();
// for (uint i = 1; i != path_double_sampled.size() - 1; i++) {
// ceres::CostFunction * cost_fn =
// new UpsamplerConstrainedCostFunction(path_double_sampled, params, 2, i);
// problem->AddResidualBlock(
// cost_fn, nullptr, &path_double_sampled[i][0], &path_double_sampled[i][1]);
// // locking initial coordinates unnecessary since there's no update between terms in NLLS
// }
// ceres::Solver::Summary summary;
// _options.minimizer_type = ceres::LINE_SEARCH;
// ceres::Solve(_options, problem.get(), &summary);
// if (upsample_ratio == 4) {
// std::vector<Eigen::Vector2d> path_quad_sampled;
// for (int i = 0; i != path_double_sampled.size() - 1; i++) {
// path_quad_sampled.push_back(path_double_sampled[i]);
// path_quad_sampled.push_back((path_double_sampled[i+1] + path_double_sampled[i]) / 2.0);
// }
// std::unique_ptr<ceres::Problem> problem2 = std::make_unique<ceres::Problem>();
// for (uint i = 1; i != path_quad_sampled.size() - 1; i++) {
// ceres::CostFunction * cost_fn =
// new UpsamplerConstrainedCostFunction(path_quad_sampled, params, 4, i);
// problem2->AddResidualBlock(
// cost_fn, nullptr, &path_quad_sampled[i][0], &path_quad_sampled[i][1]);
// }
// ceres::Solve(_options, problem2.get(), &summary);
// path = path_quad_sampled;
// } else {
// path = path_double_sampled;
// }
if (_debug) {
std::cout << summary.FullReport() << '\n';
}
if (!summary.IsSolutionUsable() || summary.initial_cost - summary.final_cost <= 0.0) {
return false;
}
return true;
}
private:
bool _debug;
ceres::GradientProblemSolver::Options _options;
};
} // namespace nav2_smac_planner
#endif // DEPRECATED__UPSAMPLER_HPP_
@@ -0,0 +1,366 @@
// Copyright (c) 2020, Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#ifndef DEPRECATED__UPSAMPLER_COST_FUNCTION_HPP_
#define DEPRECATED__UPSAMPLER_COST_FUNCTION_HPP_
#include <cmath>
#include <vector>
#include <iostream>
#include <unordered_map>
#include <memory>
#include <queue>
#include <utility>
#include "ceres/ceres.h"
#include "Eigen/Core"
#include "nav2_smac_planner/types.hpp"
#include "nav2_smac_planner/options.hpp"
#define EPSILON 0.0001
namespace nav2_smac_planner
{
/**
* @struct nav2_smac_planner::UpsamplerCostFunction
* @brief Cost function for path upsampling with multiple terms using unconstrained
* optimization including curvature, smoothness, collision, and avoid obstacles.
*/
class UpsamplerCostFunction : public ceres::FirstOrderFunction
{
public:
/**
* @brief A constructor for nav2_smac_planner::UpsamplerCostFunction
* @param num_points Number of path points to consider
*/
UpsamplerCostFunction(
const std::vector<Eigen::Vector2d> & path,
const SmootherParams & params,
const int & upsample_ratio)
: _num_params(2 * path.size()),
_params(params),
_upsample_ratio(upsample_ratio),
_path(path)
{
}
// TODO(stevemacenski) removed upsample_ratio because temp upsampling on path size
/**
* @struct CurvatureComputations
* @brief Cache common computations between the curvature terms to minimize recomputations
*/
struct CurvatureComputations
{
/**
* @brief A constructor for nav2_smac_planner::CurvatureComputations
*/
CurvatureComputations()
{
valid = false;
}
bool valid;
/**
* @brief Check if result is valid for penalty
* @return is valid (non-nan, non-inf, and turning angle > max)
*/
bool isValid()
{
return valid;
}
Eigen::Vector2d delta_xi{0, 0};
Eigen::Vector2d delta_xi_p{0, 0};
double delta_xi_norm{0};
double delta_xi_p_norm{0};
double delta_phi_i{0};
double turning_rad{0};
double ki_minus_kmax{0};
};
/**
* @brief Smoother cost function evaluation
* @param parameters X,Y pairs of points
* @param cost total cost of path
* @param gradient of path at each X,Y pair from cost function derived analytically
* @return if successful in computing values
*/
virtual bool Evaluate(
const double * parameters,
double * cost,
double * gradient) const
{
Eigen::Vector2d xi;
Eigen::Vector2d xi_p1;
Eigen::Vector2d xi_m1;
uint x_index, y_index;
cost[0] = 0.0;
double cost_raw = 0.0;
double grad_x_raw = 0.0;
double grad_y_raw = 0.0;
// cache some computations between the residual and jacobian
CurvatureComputations curvature_params;
for (int i = 0; i != NumParameters() / 2; i++) {
x_index = 2 * i;
y_index = 2 * i + 1;
gradient[x_index] = 0.0;
gradient[y_index] = 0.0;
if (i < 1 || i >= NumParameters() / 2 - 1) {
continue;
}
// if original point's neighbors TODO
if (i % _upsample_ratio == 1) {
continue;
}
xi = Eigen::Vector2d(parameters[x_index], parameters[y_index]);
// TODO(stevemacenski): from deep copy to make sure no feedback _path
xi_p1 = _path.at(i + 1);
xi_m1 = _path.at(i - 1);
// xi_p1 = Eigen::Vector2d(parameters[x_index + 2], parameters[y_index + 2]);
// xi_m1 = Eigen::Vector2d(parameters[x_index - 2], parameters[y_index - 2]);
// compute cost
addSmoothingResidual(15000, xi, xi_p1, xi_m1, cost_raw);
addCurvatureResidual(60.0, xi, xi_p1, xi_m1, curvature_params, cost_raw);
if (gradient != NULL) {
// compute gradient
addSmoothingJacobian(15000, xi, xi_p1, xi_m1, grad_x_raw, grad_y_raw);
addCurvatureJacobian(60.0, xi, xi_p1, xi_m1, curvature_params, grad_x_raw, grad_y_raw);
gradient[x_index] = grad_x_raw;
gradient[y_index] = grad_y_raw;
}
}
cost[0] = cost_raw;
return true;
}
/**
* @brief Get number of parameter blocks
* @return Number of parameters in cost function
*/
virtual int NumParameters() const {return _num_params;}
protected:
/**
* @brief Cost function term for smooth paths
* @param weight Weight to apply to function
* @param pt Point Xi for evaluation
* @param pt Point Xi+1 for calculating Xi's cost
* @param pt Point Xi-1 for calculating Xi's cost
* @param r Residual (cost) of term
*/
inline void addSmoothingResidual(
const double & weight,
const Eigen::Vector2d & pt,
const Eigen::Vector2d & pt_p,
const Eigen::Vector2d & pt_m,
double & r) const
{
r += weight * (
pt_p.dot(pt_p) -
4 * pt_p.dot(pt) +
2 * pt_p.dot(pt_m) +
4 * pt.dot(pt) -
4 * pt.dot(pt_m) +
pt_m.dot(pt_m)); // objective function value
}
/**
* @brief Cost function derivative term for smooth paths
* @param weight Weight to apply to function
* @param pt Point Xi for evaluation
* @param pt Point Xi+1 for calculating Xi's cost
* @param pt Point Xi-1 for calculating Xi's cost
* @param j0 Gradient of X term
* @param j1 Gradient of Y term
*/
inline void addSmoothingJacobian(
const double & weight,
const Eigen::Vector2d & pt,
const Eigen::Vector2d & pt_p,
const Eigen::Vector2d & pt_m,
double & j0,
double & j1) const
{
j0 += weight *
(-4 * pt_m[0] + 8 * pt[0] - 4 * pt_p[0]); // xi x component of partial-derivative
j1 += weight *
(-4 * pt_m[1] + 8 * pt[1] - 4 * pt_p[1]); // xi y component of partial-derivative
}
/**
* @brief Get path curvature information
* @param pt Point Xi for evaluation
* @param pt Point Xi+1 for calculating Xi's cost
* @param pt Point Xi-1 for calculating Xi's cost
* @param curvature_params A struct to cache computations for the jacobian to use
*/
inline void getCurvatureParams(
const Eigen::Vector2d & pt,
const Eigen::Vector2d & pt_p,
const Eigen::Vector2d & pt_m,
CurvatureComputations & curvature_params) const
{
curvature_params.valid = true;
curvature_params.delta_xi = Eigen::Vector2d(pt[0] - pt_m[0], pt[1] - pt_m[1]);
curvature_params.delta_xi_p = Eigen::Vector2d(pt_p[0] - pt[0], pt_p[1] - pt[1]);
curvature_params.delta_xi_norm = curvature_params.delta_xi.norm();
curvature_params.delta_xi_p_norm = curvature_params.delta_xi_p.norm();
if (curvature_params.delta_xi_norm < EPSILON || curvature_params.delta_xi_p_norm < EPSILON ||
std::isnan(curvature_params.delta_xi_p_norm) || std::isnan(curvature_params.delta_xi_norm) ||
std::isinf(curvature_params.delta_xi_p_norm) || std::isinf(curvature_params.delta_xi_norm))
{
// ensure we have non-nan values returned
curvature_params.valid = false;
return;
}
const double & delta_xi_by_xi_p =
curvature_params.delta_xi_norm * curvature_params.delta_xi_p_norm;
double projection =
curvature_params.delta_xi.dot(curvature_params.delta_xi_p) / delta_xi_by_xi_p;
if (fabs(1 - projection) < EPSILON || fabs(projection + 1) < EPSILON) {
projection = 1.0;
}
curvature_params.delta_phi_i = std::acos(projection);
curvature_params.turning_rad = curvature_params.delta_phi_i / curvature_params.delta_xi_norm;
curvature_params.ki_minus_kmax = curvature_params.turning_rad - _upsample_ratio *
_params.max_curvature;
// TODO(stevemacenski) is use of upsample_ratio correct here? small number?
// TODO(stevemacenski) can remove the subtraction with a
// lower weight value, does have direction issue, maybe just tuning?
if (curvature_params.ki_minus_kmax <= EPSILON) {
// Quadratic penalty need not apply
curvature_params.valid = false;
}
}
/**
* @brief Cost function term for maximum curved paths
* @param weight Weight to apply to function
* @param pt Point Xi for evaluation
* @param pt Point Xi+1 for calculating Xi's cost
* @param pt Point Xi-1 for calculating Xi's cost
* @param curvature_params A struct to cache computations for the jacobian to use
* @param r Residual (cost) of term
*/
inline void addCurvatureResidual(
const double & weight,
const Eigen::Vector2d & pt,
const Eigen::Vector2d & pt_p,
const Eigen::Vector2d & pt_m,
CurvatureComputations & curvature_params,
double & r) const
{
getCurvatureParams(pt, pt_p, pt_m, curvature_params);
if (!curvature_params.isValid()) {
return;
}
r += weight *
curvature_params.ki_minus_kmax * curvature_params.ki_minus_kmax; // objective function value
}
/**
* @brief Cost function derivative term for maximum curvature paths
* @param weight Weight to apply to function
* @param pt Point Xi for evaluation
* @param pt Point Xi+1 for calculating Xi's cost
* @param pt Point Xi-1 for calculating Xi's cost
* @param curvature_params A struct with cached values to speed up Jacobian computation
* @param j0 Gradient of X term
* @param j1 Gradient of Y term
*/
inline void addCurvatureJacobian(
const double & weight,
const Eigen::Vector2d & pt,
const Eigen::Vector2d & pt_p,
const Eigen::Vector2d & /*pt_m*/,
CurvatureComputations & curvature_params,
double & j0,
double & j1) const
{
if (!curvature_params.isValid()) {
return;
}
const double & partial_delta_phi_i_wrt_cost_delta_phi_i =
-1 / std::sqrt(1 - std::pow(std::cos(curvature_params.delta_phi_i), 2));
// const Eigen::Vector2d ones = Eigen::Vector2d(1.0, 1.0);
auto neg_pt_plus = -1 * pt_p;
Eigen::Vector2d p1 = normalizedOrthogonalComplement(
pt, neg_pt_plus, curvature_params.delta_xi_norm, curvature_params.delta_xi_p_norm);
Eigen::Vector2d p2 = normalizedOrthogonalComplement(
neg_pt_plus, pt, curvature_params.delta_xi_p_norm, curvature_params.delta_xi_norm);
const double & u = 2 * curvature_params.ki_minus_kmax;
const double & common_prefix =
(1 / curvature_params.delta_xi_norm) * partial_delta_phi_i_wrt_cost_delta_phi_i;
const double & common_suffix = curvature_params.delta_phi_i /
(curvature_params.delta_xi_norm * curvature_params.delta_xi_norm);
const Eigen::Vector2d & d_delta_xi_d_xi = curvature_params.delta_xi /
curvature_params.delta_xi_norm;
const Eigen::Vector2d jacobian = u *
(common_prefix * (-p1 - p2) - (common_suffix * d_delta_xi_d_xi));
const Eigen::Vector2d jacobian_im1 = u *
(common_prefix * p2 + (common_suffix * d_delta_xi_d_xi));
const Eigen::Vector2d jacobian_ip1 = u * (common_prefix * p1);
j0 += weight * jacobian[0]; // xi y component of partial-derivative
j1 += weight * jacobian[1]; // xi x component of partial-derivative
// j0 += weight *
// (jacobian_im1[0] + 2 * jacobian[0] + jacobian_ip1[0]);
// j1 += weight *
// (jacobian_im1[1] + 2 * jacobian[1] + jacobian_ip1[1]);
}
/**
* @brief Computing the normalized orthogonal component of 2 vectors
* @param a Vector
* @param b Vector
* @param norm a Vector's norm
* @param norm b Vector's norm
* @return Normalized vector of orthogonal components
*/
inline Eigen::Vector2d normalizedOrthogonalComplement(
const Eigen::Vector2d & a,
const Eigen::Vector2d & b,
const double & a_norm,
const double & b_norm) const
{
return (a - (a.dot(b) * b / b.squaredNorm())) / (a_norm * b_norm);
}
int _num_params;
SmootherParams _params;
int _upsample_ratio;
std::vector<Eigen::Vector2d> _path;
};
} // namespace nav2_smac_planner
#endif // DEPRECATED__UPSAMPLER_COST_FUNCTION_HPP_
@@ -0,0 +1,334 @@
// Copyright (c) 2020, Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#ifndef DEPRECATED__UPSAMPLER_COST_FUNCTION_NLLS_HPP_
#define DEPRECATED__UPSAMPLER_COST_FUNCTION_NLLS_HPP_
#include <cmath>
#include <vector>
#include <iostream>
#include <unordered_map>
#include <memory>
#include <queue>
#include <utility>
#include "ceres/ceres.h"
#include "Eigen/Core"
#include "nav2_smac_planner/types.hpp"
#include "nav2_smac_planner/options.hpp"
#define EPSILON 0.0001
namespace nav2_smac_planner
{
/**
* @struct nav2_smac_planner::UpsamplerConstrainedCostFunction
* @brief Cost function for path upsampling with multiple terms using NLLS
* including curvature, smoothness, collision, and avoid obstacles.
*/
class UpsamplerConstrainedCostFunction : public ceres::SizedCostFunction<1, 1, 1>
{
public:
/**
* @brief A constructor for nav2_smac_planner::UpsamplerConstrainedCostFunction
* @param num_points Number of path points to consider
*/
UpsamplerConstrainedCostFunction(
const std::vector<Eigen::Vector2d> & path,
const SmootherParams & params,
const int & upsample_ratio,
const int & i)
: _path(path),
_params(params),
_upsample_ratio(upsample_ratio),
index(i)
{
}
/**
* @struct CurvatureComputations
* @brief Cache common computations between the curvature terms to minimize recomputations
*/
struct CurvatureComputations
{
/**
* @brief A constructor for nav2_smac_planner::CurvatureComputations
*/
CurvatureComputations()
{
valid = true;
}
bool valid;
/**
* @brief Check if result is valid for penalty
* @return is valid (non-nan, non-inf, and turning angle > max)
*/
bool isValid()
{
return valid;
}
Eigen::Vector2d delta_xi{0, 0};
Eigen::Vector2d delta_xi_p{0, 0};
double delta_xi_norm{0};
double delta_xi_p_norm{0};
double delta_phi_i{0};
double turning_rad{0};
double ki_minus_kmax{0};
};
/**
* @brief Smoother cost function evaluation
* @param parameters X,Y pairs of points
* @param cost total cost of path
* @param gradient of path at each X,Y pair from cost function derived analytically
* @return if successful in computing values
*/
bool Evaluate(
double const * const * parameters,
double * residuals,
double ** jacobians) const override
{
Eigen::Vector2d xi = Eigen::Vector2d(parameters[0][0], parameters[1][0]);
Eigen::Vector2d xi_p1 = _path.at(index + 1);
Eigen::Vector2d xi_m1 = _path.at(index - 1);
CurvatureComputations curvature_params;
double grad_x_raw = 0, grad_y_raw = 0, cost_raw = 0;
// compute cost
addSmoothingResidual(15000, xi, xi_p1, xi_m1, cost_raw);
addCurvatureResidual(60.0, xi, xi_p1, xi_m1, curvature_params, cost_raw);
residuals[0] = 0;
residuals[0] = cost_raw; // objective function value x
if (jacobians != NULL && jacobians[0] != NULL) {
addSmoothingJacobian(15000, xi, xi_p1, xi_m1, grad_x_raw, grad_y_raw);
addCurvatureJacobian(60.0, xi, xi_p1, xi_m1, curvature_params, grad_x_raw, grad_y_raw);
jacobians[0][0] = 0;
jacobians[1][0] = 0;
jacobians[0][0] = grad_x_raw; // x derivative
jacobians[1][0] = grad_y_raw; // y derivative
jacobians[0][1] = 0.0;
jacobians[1][1] = 0.0;
}
return true;
}
protected:
/**
* @brief Cost function term for smooth paths
* @param weight Weight to apply to function
* @param pt Point Xi for evaluation
* @param pt Point Xi+1 for calculating Xi's cost
* @param pt Point Xi-1 for calculating Xi's cost
* @param r Residual (cost) of term
*/
inline void addSmoothingResidual(
const double & weight,
const Eigen::Vector2d & pt,
const Eigen::Vector2d & pt_p,
const Eigen::Vector2d & pt_m,
double & r) const
{
r += weight * (
pt_p.dot(pt_p) -
4 * pt_p.dot(pt) +
2 * pt_p.dot(pt_m) +
4 * pt.dot(pt) -
4 * pt.dot(pt_m) +
pt_m.dot(pt_m)); // objective function value
}
/**
* @brief Cost function derivative term for smooth paths
* @param weight Weight to apply to function
* @param pt Point Xi for evaluation
* @param pt Point Xi+1 for calculating Xi's cost
* @param pt Point Xi-1 for calculating Xi's cost
* @param j0 Gradient of X term
* @param j1 Gradient of Y term
*/
inline void addSmoothingJacobian(
const double & weight,
const Eigen::Vector2d & pt,
const Eigen::Vector2d & pt_p,
const Eigen::Vector2d & pt_m,
double & j0,
double & j1) const
{
j0 += weight *
(-4 * pt_m[0] + 8 * pt[0] - 4 * pt_p[0]); // xi x component of partial-derivative
j1 += weight *
(-4 * pt_m[1] + 8 * pt[1] - 4 * pt_p[1]); // xi y component of partial-derivative
}
/**
* @brief Get path curvature information
* @param pt Point Xi for evaluation
* @param pt Point Xi+1 for calculating Xi's cost
* @param pt Point Xi-1 for calculating Xi's cost
* @param curvature_params A struct to cache computations for the jacobian to use
*/
inline void getCurvatureParams(
const Eigen::Vector2d & pt,
const Eigen::Vector2d & pt_p,
const Eigen::Vector2d & pt_m,
CurvatureComputations & curvature_params) const
{
curvature_params.valid = true;
curvature_params.delta_xi = Eigen::Vector2d(pt[0] - pt_m[0], pt[1] - pt_m[1]);
curvature_params.delta_xi_p = Eigen::Vector2d(pt_p[0] - pt[0], pt_p[1] - pt[1]);
curvature_params.delta_xi_norm = curvature_params.delta_xi.norm();
curvature_params.delta_xi_p_norm = curvature_params.delta_xi_p.norm();
if (curvature_params.delta_xi_norm < EPSILON || curvature_params.delta_xi_p_norm < EPSILON ||
std::isnan(curvature_params.delta_xi_p_norm) || std::isnan(curvature_params.delta_xi_norm) ||
std::isinf(curvature_params.delta_xi_p_norm) || std::isinf(curvature_params.delta_xi_norm))
{
// ensure we have non-nan values returned
curvature_params.valid = false;
return;
}
const double & delta_xi_by_xi_p =
curvature_params.delta_xi_norm * curvature_params.delta_xi_p_norm;
double projection =
curvature_params.delta_xi.dot(curvature_params.delta_xi_p) / delta_xi_by_xi_p;
if (fabs(1 - projection) < EPSILON || fabs(projection + 1) < EPSILON) {
projection = 1.0;
}
curvature_params.delta_phi_i = std::acos(projection);
curvature_params.turning_rad = curvature_params.delta_phi_i / curvature_params.delta_xi_norm;
curvature_params.ki_minus_kmax = curvature_params.turning_rad - _upsample_ratio *
_params.max_curvature;
if (curvature_params.ki_minus_kmax <= EPSILON) {
// Quadratic penalty need not apply
curvature_params.valid = false;
}
}
/**
* @brief Cost function term for maximum curved paths
* @param weight Weight to apply to function
* @param pt Point Xi for evaluation
* @param pt Point Xi+1 for calculating Xi's cost
* @param pt Point Xi-1 for calculating Xi's cost
* @param curvature_params A struct to cache computations for the jacobian to use
* @param r Residual (cost) of term
*/
inline void addCurvatureResidual(
const double & weight,
const Eigen::Vector2d & pt,
const Eigen::Vector2d & pt_p,
const Eigen::Vector2d & pt_m,
CurvatureComputations & curvature_params,
double & r) const
{
getCurvatureParams(pt, pt_p, pt_m, curvature_params);
if (!curvature_params.isValid()) {
return;
}
// objective function value
r += weight *
curvature_params.ki_minus_kmax * curvature_params.ki_minus_kmax;
}
/**
* @brief Cost function derivative term for maximum curvature paths
* @param weight Weight to apply to function
* @param pt Point Xi for evaluation
* @param pt Point Xi+1 for calculating Xi's cost
* @param pt Point Xi-1 for calculating Xi's cost
* @param curvature_params A struct with cached values to speed up Jacobian computation
* @param j0 Gradient of X term
* @param j1 Gradient of Y term
*/
inline void addCurvatureJacobian(
const double & weight,
const Eigen::Vector2d & pt,
const Eigen::Vector2d & pt_p,
const Eigen::Vector2d & /*pt_m*/,
CurvatureComputations & curvature_params,
double & j0,
double & j1) const
{
if (!curvature_params.isValid()) {
return;
}
const double & partial_delta_phi_i_wrt_cost_delta_phi_i =
-1 / std::sqrt(1 - std::pow(std::cos(curvature_params.delta_phi_i), 2));
// const Eigen::Vector2d ones = Eigen::Vector2d(1.0, 1.0);
auto neg_pt_plus = -1 * pt_p;
Eigen::Vector2d p1 = normalizedOrthogonalComplement(
pt, neg_pt_plus, curvature_params.delta_xi_norm, curvature_params.delta_xi_p_norm);
Eigen::Vector2d p2 = normalizedOrthogonalComplement(
neg_pt_plus, pt, curvature_params.delta_xi_p_norm, curvature_params.delta_xi_norm);
const double & u = 2 * curvature_params.ki_minus_kmax;
const double & common_prefix =
(1 / curvature_params.delta_xi_norm) * partial_delta_phi_i_wrt_cost_delta_phi_i;
const double & common_suffix = curvature_params.delta_phi_i /
(curvature_params.delta_xi_norm * curvature_params.delta_xi_norm);
const Eigen::Vector2d & d_delta_xi_d_xi = curvature_params.delta_xi /
curvature_params.delta_xi_norm;
const Eigen::Vector2d jacobian = u *
(common_prefix * (-p1 - p2) - (common_suffix * d_delta_xi_d_xi));
const Eigen::Vector2d jacobian_im1 = u *
(common_prefix * p2 + (common_suffix * d_delta_xi_d_xi));
const Eigen::Vector2d jacobian_ip1 = u * (common_prefix * p1);
j0 += weight * jacobian[0]; // xi x component of partial-derivative
j1 += weight * jacobian[1]; // xi y component of partial-derivative
// j0 += weight *
// (jacobian_im1[0] + 2 * jacobian[0] + jacobian_ip1[0]);
// j1 += weight *
// (jacobian_im1[1] + 2 * jacobian[1] + jacobian_ip1[1]);
}
/**
* @brief Computing the normalized orthogonal component of 2 vectors
* @param a Vector
* @param b Vector
* @param norm a Vector's norm
* @param norm b Vector's norm
* @return Normalized vector of orthogonal components
*/
inline Eigen::Vector2d normalizedOrthogonalComplement(
const Eigen::Vector2d & a,
const Eigen::Vector2d & b,
const double & a_norm,
const double & b_norm) const
{
return (a - (a.dot(b) * b / b.squaredNorm())) / (a_norm * b_norm);
}
std::vector<Eigen::Vector2d> _path;
SmootherParams _params;
int _upsample_ratio;
int index;
};
} // namespace nav2_smac_planner
#endif // DEPRECATED__UPSAMPLER_COST_FUNCTION_NLLS_HPP_
Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

@@ -0,0 +1,308 @@
// Copyright (c) 2020, Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#include <math.h>
#include <memory>
#include <string>
#include <vector>
#include <limits>
#include "gtest/gtest.h"
#include "rclcpp/rclcpp.hpp"
#include "nav2_costmap_2d/costmap_2d.hpp"
#include "nav2_costmap_2d/costmap_subscriber.hpp"
#include "nav2_util/lifecycle_node.hpp"
#include "nav2_smac_planner/node_hybrid.hpp"
#include "nav2_smac_planner/node_lattice.hpp"
#include "nav2_smac_planner/a_star.hpp"
#include "nav2_smac_planner/collision_checker.hpp"
#include "ament_index_cpp/get_package_share_directory.hpp"
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
TEST(AStarTest, test_a_star_2d)
{
auto lnode = std::make_shared<rclcpp_lifecycle::LifecycleNode>("test");
nav2_smac_planner::SearchInfo info;
nav2_smac_planner::AStarAlgorithm<nav2_smac_planner::Node2D> a_star(
nav2_smac_planner::MotionModel::TWOD, info);
int max_iterations = 10000;
float tolerance = 0.0;
float some_tolerance = 20.0;
int it_on_approach = 10;
double max_planning_time = 120.0;
int num_it = 0;
a_star.initialize(false, max_iterations, it_on_approach, max_planning_time, 0.0, 1);
nav2_costmap_2d::Costmap2D * costmapA =
new nav2_costmap_2d::Costmap2D(100, 100, 0.1, 0.0, 0.0, 0);
// island in the middle of lethal cost to cross
for (unsigned int i = 40; i <= 60; ++i) {
for (unsigned int j = 40; j <= 60; ++j) {
costmapA->setCost(i, j, 254);
}
}
// functional case testing
std::unique_ptr<nav2_smac_planner::GridCollisionChecker> checker =
std::make_unique<nav2_smac_planner::GridCollisionChecker>(costmapA, 1, lnode);
checker->setFootprint(nav2_costmap_2d::Footprint(), true, 0.0);
a_star.setCollisionChecker(checker.get());
a_star.setStart(20u, 20u, 0);
a_star.setGoal(80u, 80u, 0);
nav2_smac_planner::Node2D::CoordinateVector path;
EXPECT_TRUE(a_star.createPath(path, num_it, tolerance));
EXPECT_EQ(num_it, 2414);
// check path is the right size and collision free
EXPECT_EQ(path.size(), 82u);
for (unsigned int i = 0; i != path.size(); i++) {
EXPECT_EQ(costmapA->getCost(path[i].x, path[i].y), 0);
}
// setting non-zero dim 3 for 2D search
EXPECT_THROW(a_star.setGoal(0, 0, 10), std::runtime_error);
EXPECT_THROW(a_star.setStart(0, 0, 10), std::runtime_error);
path.clear();
// failure cases with invalid inputs
nav2_smac_planner::AStarAlgorithm<nav2_smac_planner::Node2D> a_star_2(
nav2_smac_planner::MotionModel::TWOD, info);
a_star_2.initialize(false, max_iterations, it_on_approach, max_planning_time, 0, 1);
num_it = 0;
EXPECT_THROW(a_star_2.createPath(path, num_it, tolerance), std::runtime_error);
a_star_2.setCollisionChecker(checker.get());
num_it = 0;
EXPECT_THROW(a_star_2.createPath(path, num_it, tolerance), std::runtime_error);
a_star_2.setStart(50, 50, 0); // invalid
a_star_2.setGoal(0, 0, 0); // valid
num_it = 0;
EXPECT_THROW(a_star_2.createPath(path, num_it, tolerance), std::runtime_error);
a_star_2.setStart(0, 0, 0); // valid
a_star_2.setGoal(50, 50, 0); // invalid
num_it = 0;
EXPECT_THROW(a_star_2.createPath(path, num_it, tolerance), std::runtime_error);
num_it = 0;
// invalid goal but liberal tolerance
a_star_2.setStart(20, 20, 0); // valid
a_star_2.setGoal(50, 50, 0); // invalid
EXPECT_TRUE(a_star_2.createPath(path, num_it, some_tolerance));
EXPECT_EQ(path.size(), 21u);
for (unsigned int i = 0; i != path.size(); i++) {
EXPECT_EQ(costmapA->getCost(path[i].x, path[i].y), 0);
}
EXPECT_TRUE(a_star_2.getStart() != nullptr);
EXPECT_TRUE(a_star_2.getGoal() != nullptr);
EXPECT_EQ(a_star_2.getSizeX(), 100u);
EXPECT_EQ(a_star_2.getSizeY(), 100u);
EXPECT_EQ(a_star_2.getSizeDim3(), 1u);
EXPECT_EQ(a_star_2.getToleranceHeuristic(), 20.0);
EXPECT_EQ(a_star_2.getOnApproachMaxIterations(), 10);
delete costmapA;
}
TEST(AStarTest, test_a_star_se2)
{
auto lnode = std::make_shared<rclcpp_lifecycle::LifecycleNode>("test");
nav2_smac_planner::SearchInfo info;
info.change_penalty = 0.1;
info.non_straight_penalty = 1.1;
info.reverse_penalty = 2.0;
info.minimum_turning_radius = 8; // in grid coordinates
info.retrospective_penalty = 0.015;
info.analytic_expansion_max_length = 20.0; // in grid coordinates
info.analytic_expansion_ratio = 3.5;
unsigned int size_theta = 72;
info.cost_penalty = 1.7;
nav2_smac_planner::AStarAlgorithm<nav2_smac_planner::NodeHybrid> a_star(
nav2_smac_planner::MotionModel::DUBIN, info);
int max_iterations = 10000;
float tolerance = 10.0;
int it_on_approach = 10;
double max_planning_time = 120.0;
int num_it = 0;
a_star.initialize(false, max_iterations, it_on_approach, max_planning_time, 401, size_theta);
nav2_costmap_2d::Costmap2D * costmapA =
new nav2_costmap_2d::Costmap2D(100, 100, 0.1, 0.0, 0.0, 0);
// island in the middle of lethal cost to cross
for (unsigned int i = 40; i <= 60; ++i) {
for (unsigned int j = 40; j <= 60; ++j) {
costmapA->setCost(i, j, 254);
}
}
std::unique_ptr<nav2_smac_planner::GridCollisionChecker> checker =
std::make_unique<nav2_smac_planner::GridCollisionChecker>(costmapA, size_theta, lnode);
checker->setFootprint(nav2_costmap_2d::Footprint(), true, 0.0);
// functional case testing
a_star.setCollisionChecker(checker.get());
a_star.setStart(10u, 10u, 0u);
a_star.setGoal(80u, 80u, 40u);
nav2_smac_planner::NodeHybrid::CoordinateVector path;
EXPECT_TRUE(a_star.createPath(path, num_it, tolerance));
// check path is the right size and collision free
EXPECT_EQ(num_it, 3222);
EXPECT_EQ(path.size(), 63u);
for (unsigned int i = 0; i != path.size(); i++) {
EXPECT_EQ(costmapA->getCost(path[i].x, path[i].y), 0);
}
// no skipped nodes
for (unsigned int i = 1; i != path.size(); i++) {
EXPECT_LT(hypotf(path[i].x - path[i - 1].x, path[i].y - path[i - 1].y), 2.1f);
}
delete costmapA;
}
TEST(AStarTest, test_a_star_lattice)
{
auto lnode = std::make_shared<rclcpp_lifecycle::LifecycleNode>("test");
nav2_smac_planner::SearchInfo info;
info.change_penalty = 0.05;
info.non_straight_penalty = 1.05;
info.reverse_penalty = 2.0;
info.retrospective_penalty = 0.1;
info.analytic_expansion_ratio = 3.5;
info.lattice_filepath =
ament_index_cpp::get_package_share_directory("nav2_smac_planner") +
"/sample_primitives/5cm_resolution/0.5m_turning_radius/ackermann" +
"/output.json";
info.minimum_turning_radius = 8; // in grid coordinates 0.4/0.05
info.analytic_expansion_max_length = 20.0; // in grid coordinates
unsigned int size_theta = 16;
info.cost_penalty = 2.0;
nav2_smac_planner::AStarAlgorithm<nav2_smac_planner::NodeLattice> a_star(
nav2_smac_planner::MotionModel::STATE_LATTICE, info);
int max_iterations = 10000;
float tolerance = 10.0;
int it_on_approach = 10;
double max_planning_time = 120.0;
int num_it = 0;
a_star.initialize(
false, max_iterations, std::numeric_limits<int>::max(), max_planning_time, 401, size_theta);
nav2_costmap_2d::Costmap2D * costmapA =
new nav2_costmap_2d::Costmap2D(100, 100, 0.05, 0.0, 0.0, 0);
// island in the middle of lethal cost to cross
for (unsigned int i = 20; i <= 30; ++i) {
for (unsigned int j = 20; j <= 30; ++j) {
costmapA->setCost(i, j, 254);
}
}
std::unique_ptr<nav2_smac_planner::GridCollisionChecker> checker =
std::make_unique<nav2_smac_planner::GridCollisionChecker>(costmapA, size_theta, lnode);
checker->setFootprint(nav2_costmap_2d::Footprint(), true, 0.0);
// functional case testing
a_star.setCollisionChecker(checker.get());
a_star.setStart(5u, 5u, 0u);
a_star.setGoal(40u, 40u, 1u);
nav2_smac_planner::NodeLattice::CoordinateVector path;
EXPECT_TRUE(a_star.createPath(path, num_it, tolerance));
// check path is the right size and collision free
EXPECT_EQ(num_it, 21);
EXPECT_GT(path.size(), 47u);
for (unsigned int i = 0; i != path.size(); i++) {
EXPECT_EQ(costmapA->getCost(path[i].x, path[i].y), 0);
}
// no skipped nodes
for (unsigned int i = 1; i != path.size(); i++) {
EXPECT_LT(hypotf(path[i].x - path[i - 1].x, path[i].y - path[i - 1].y), 2.1f);
}
delete costmapA;
}
TEST(AStarTest, test_se2_single_pose_path)
{
auto lnode = std::make_shared<rclcpp_lifecycle::LifecycleNode>("test");
nav2_smac_planner::SearchInfo info;
info.change_penalty = 0.1;
info.non_straight_penalty = 1.1;
info.reverse_penalty = 2.0;
info.retrospective_penalty = 0.0;
info.minimum_turning_radius = 8; // in grid coordinates
info.analytic_expansion_max_length = 20.0; // in grid coordinates
info.analytic_expansion_ratio = 3.5;
unsigned int size_theta = 72;
info.cost_penalty = 1.7;
nav2_smac_planner::AStarAlgorithm<nav2_smac_planner::NodeHybrid> a_star(
nav2_smac_planner::MotionModel::DUBIN, info);
int max_iterations = 100;
float tolerance = 10.0;
int it_on_approach = 10;
double max_planning_time = 120.0;
int num_it = 0;
a_star.initialize(false, max_iterations, it_on_approach, max_planning_time, 401, size_theta);
nav2_costmap_2d::Costmap2D * costmapA =
new nav2_costmap_2d::Costmap2D(100, 100, 0.1, 0.0, 0.0, 0);
std::unique_ptr<nav2_smac_planner::GridCollisionChecker> checker =
std::make_unique<nav2_smac_planner::GridCollisionChecker>(costmapA, size_theta, lnode);
checker->setFootprint(nav2_costmap_2d::Footprint(), true, 0.0);
// functional case testing
a_star.setCollisionChecker(checker.get());
a_star.setStart(10u, 10u, 0u);
// Goal is one costmap cell away
a_star.setGoal(12u, 10u, 0u);
nav2_smac_planner::NodeHybrid::CoordinateVector path;
EXPECT_TRUE(a_star.createPath(path, num_it, tolerance));
// Check that the path is length one
// With the current implementation, this produces a longer path
// EXPECT_EQ(path.size(), 1u);
EXPECT_GE(path.size(), 1u);
delete costmapA;
}
TEST(AStarTest, test_constants)
{
nav2_smac_planner::MotionModel mm = nav2_smac_planner::MotionModel::UNKNOWN; // unknown
EXPECT_EQ(nav2_smac_planner::toString(mm), std::string("Unknown"));
mm = nav2_smac_planner::MotionModel::TWOD; // 2d
EXPECT_EQ(nav2_smac_planner::toString(mm), std::string("2D"));
mm = nav2_smac_planner::MotionModel::DUBIN; // dubin
EXPECT_EQ(nav2_smac_planner::toString(mm), std::string("Dubin"));
mm = nav2_smac_planner::MotionModel::REEDS_SHEPP; // reeds-shepp
EXPECT_EQ(nav2_smac_planner::toString(mm), std::string("Reeds-Shepp"));
EXPECT_EQ(
nav2_smac_planner::fromString(
"2D"), nav2_smac_planner::MotionModel::TWOD);
EXPECT_EQ(nav2_smac_planner::fromString("DUBIN"), nav2_smac_planner::MotionModel::DUBIN);
EXPECT_EQ(
nav2_smac_planner::fromString(
"REEDS_SHEPP"), nav2_smac_planner::MotionModel::REEDS_SHEPP);
EXPECT_EQ(nav2_smac_planner::fromString("NONE"), nav2_smac_planner::MotionModel::UNKNOWN);
}
@@ -0,0 +1,185 @@
// Copyright (c) 2020 Shivang Patel
// Copyright (c) 2020 Samsung Research
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string>
#include <vector>
#include <memory>
#include "gtest/gtest.h"
#include "nav2_smac_planner/collision_checker.hpp"
#include "nav2_util/lifecycle_node.hpp"
using namespace nav2_costmap_2d; // NOLINT
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
TEST(collision_footprint, test_basic)
{
auto node = std::make_shared<rclcpp_lifecycle::LifecycleNode>("testA");
nav2_costmap_2d::Costmap2D * costmap_ = new nav2_costmap_2d::Costmap2D(100, 100, 0.1, 0, 0, 0);
geometry_msgs::msg::Point p1;
p1.x = -0.5;
p1.y = 0.0;
geometry_msgs::msg::Point p2;
p2.x = 0.0;
p2.y = 0.5;
geometry_msgs::msg::Point p3;
p3.x = 0.5;
p3.y = 0.0;
geometry_msgs::msg::Point p4;
p4.x = 0.0;
p4.y = -0.5;
nav2_costmap_2d::Footprint footprint = {p1, p2, p3, p4};
nav2_smac_planner::GridCollisionChecker collision_checker(costmap_, 72, node);
collision_checker.setFootprint(footprint, false /*use footprint*/, 0.0);
collision_checker.inCollision(5.0, 5.0, 0.0, false);
float cost = collision_checker.getCost();
EXPECT_NEAR(cost, 0.0, 0.001);
delete costmap_;
}
TEST(collision_footprint, test_point_cost)
{
auto node = std::make_shared<rclcpp_lifecycle::LifecycleNode>("testB");
nav2_costmap_2d::Costmap2D * costmap_ = new nav2_costmap_2d::Costmap2D(100, 100, 0.1, 0, 0, 0);
nav2_smac_planner::GridCollisionChecker collision_checker(costmap_, 72, node);
nav2_costmap_2d::Footprint footprint;
collision_checker.setFootprint(footprint, true /*radius / pointcose*/, 0.0);
collision_checker.inCollision(5.0, 5.0, 0.0, false);
float cost = collision_checker.getCost();
EXPECT_NEAR(cost, 0.0, 0.001);
delete costmap_;
}
TEST(collision_footprint, test_world_to_map)
{
auto node = std::make_shared<rclcpp_lifecycle::LifecycleNode>("testC");
nav2_costmap_2d::Costmap2D * costmap_ = new nav2_costmap_2d::Costmap2D(100, 100, 0.1, 0, 0, 0);
nav2_smac_planner::GridCollisionChecker collision_checker(costmap_, 72, node);
nav2_costmap_2d::Footprint footprint;
collision_checker.setFootprint(footprint, true /*radius / point cost*/, 0.0);
unsigned int x, y;
collision_checker.worldToMap(1.0, 1.0, x, y);
collision_checker.inCollision(x, y, 0.0, false);
float cost = collision_checker.getCost();
EXPECT_NEAR(cost, 0.0, 0.001);
costmap_->setCost(50, 50, 200);
collision_checker.worldToMap(5.0, 5.0, x, y);
collision_checker.inCollision(x, y, 0.0, false);
EXPECT_NEAR(collision_checker.getCost(), 200.0, 0.001);
delete costmap_;
}
TEST(collision_footprint, test_footprint_at_pose_with_movement)
{
auto node = std::make_shared<rclcpp_lifecycle::LifecycleNode>("testD");
nav2_costmap_2d::Costmap2D * costmap_ = new nav2_costmap_2d::Costmap2D(100, 100, 0.1, 0, 0, 254);
for (unsigned int i = 40; i <= 60; ++i) {
for (unsigned int j = 40; j <= 60; ++j) {
costmap_->setCost(i, j, 128);
}
}
geometry_msgs::msg::Point p1;
p1.x = -1.0;
p1.y = 1.0;
geometry_msgs::msg::Point p2;
p2.x = 1.0;
p2.y = 1.0;
geometry_msgs::msg::Point p3;
p3.x = 1.0;
p3.y = -1.0;
geometry_msgs::msg::Point p4;
p4.x = -1.0;
p4.y = -1.0;
nav2_costmap_2d::Footprint footprint = {p1, p2, p3, p4};
nav2_smac_planner::GridCollisionChecker collision_checker(costmap_, 72, node);
collision_checker.setFootprint(footprint, false /*use footprint*/, 0.0);
collision_checker.inCollision(50, 50, 0.0, false);
float cost = collision_checker.getCost();
EXPECT_NEAR(cost, 128.0, 0.001);
collision_checker.inCollision(50, 49, 0.0, false);
float up_value = collision_checker.getCost();
EXPECT_NEAR(up_value, 254.0, 0.001);
collision_checker.inCollision(50, 52, 0.0, false);
float down_value = collision_checker.getCost();
EXPECT_NEAR(down_value, 254.0, 0.001);
delete costmap_;
}
TEST(collision_footprint, test_point_and_line_cost)
{
auto node = std::make_shared<rclcpp_lifecycle::LifecycleNode>("testE");
nav2_costmap_2d::Costmap2D * costmap_ = new nav2_costmap_2d::Costmap2D(
100, 100, 0.10000, 0, 0.0, 128.0);
costmap_->setCost(62, 50, 254);
costmap_->setCost(39, 60, 254);
geometry_msgs::msg::Point p1;
p1.x = -1.0;
p1.y = 1.0;
geometry_msgs::msg::Point p2;
p2.x = 1.0;
p2.y = 1.0;
geometry_msgs::msg::Point p3;
p3.x = 1.0;
p3.y = -1.0;
geometry_msgs::msg::Point p4;
p4.x = -1.0;
p4.y = -1.0;
nav2_costmap_2d::Footprint footprint = {p1, p2, p3, p4};
nav2_smac_planner::GridCollisionChecker collision_checker(costmap_, 72, node);
collision_checker.setFootprint(footprint, false /*use footprint*/, 0.0);
collision_checker.inCollision(50, 50, 0.0, false);
float value = collision_checker.getCost();
EXPECT_NEAR(value, 128.0, 0.001);
collision_checker.inCollision(49, 50, 0.0, false);
float left_value = collision_checker.getCost();
EXPECT_NEAR(left_value, 254.0, 0.001);
collision_checker.inCollision(52, 50, 0.0, false);
float right_value = collision_checker.getCost();
EXPECT_NEAR(right_value, 254.0, 0.001);
delete costmap_;
}
@@ -0,0 +1,67 @@
// Copyright (c) 2020, Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#include <memory>
#include <vector>
#include "gtest/gtest.h"
#include "rclcpp/rclcpp.hpp"
#include "nav2_costmap_2d/costmap_2d.hpp"
#include "nav2_util/lifecycle_node.hpp"
#include "nav2_smac_planner/costmap_downsampler.hpp"
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
TEST(CostmapDownsampler, costmap_downsample_test)
{
nav2_util::LifecycleNode::SharedPtr node = std::make_shared<nav2_util::LifecycleNode>(
"CostmapDownsamplerTest");
nav2_smac_planner::CostmapDownsampler downsampler;
// create basic costmap
nav2_costmap_2d::Costmap2D costmapA(10, 10, 0.05, 0.0, 0.0, 0);
costmapA.setCost(0, 0, 100);
costmapA.setCost(5, 5, 50);
// downsample it
downsampler.on_configure(node, "map", "unused_topic", &costmapA, 2);
nav2_costmap_2d::Costmap2D * downsampledCostmapA = downsampler.downsample(2);
// validate it
EXPECT_EQ(downsampledCostmapA->getCost(0, 0), 100);
EXPECT_EQ(downsampledCostmapA->getCost(2, 2), 50);
EXPECT_EQ(downsampledCostmapA->getSizeInCellsX(), 5u);
EXPECT_EQ(downsampledCostmapA->getSizeInCellsY(), 5u);
// give it another costmap of another size
nav2_costmap_2d::Costmap2D costmapB(4, 4, 0.10, 0.0, 0.0, 0);
// downsample it
downsampler.on_configure(node, "map", "unused_topic", &costmapB, 4);
downsampler.on_activate();
nav2_costmap_2d::Costmap2D * downsampledCostmapB = downsampler.downsample(4);
downsampler.on_deactivate();
// validate size
EXPECT_EQ(downsampledCostmapB->getSizeInCellsX(), 1u);
EXPECT_EQ(downsampledCostmapB->getSizeInCellsY(), 1u);
downsampler.resizeCostmap();
}
@@ -0,0 +1,143 @@
// Copyright (c) 2020, Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#include <memory>
#include <string>
#include <vector>
#include "gtest/gtest.h"
#include "rclcpp/rclcpp.hpp"
#include "nav2_costmap_2d/costmap_2d.hpp"
#include "nav2_costmap_2d/costmap_subscriber.hpp"
#include "nav2_util/lifecycle_node.hpp"
#include "nav2_smac_planner/node_2d.hpp"
#include "nav2_smac_planner/collision_checker.hpp"
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
TEST(Node2DTest, test_node_2d)
{
auto node = std::make_shared<rclcpp_lifecycle::LifecycleNode>("test");
nav2_costmap_2d::Costmap2D costmapA(10, 10, 0.05, 0.0, 0.0, 0);
std::unique_ptr<nav2_smac_planner::GridCollisionChecker> checker =
std::make_unique<nav2_smac_planner::GridCollisionChecker>(&costmapA, 72, node);
checker->setFootprint(nav2_costmap_2d::Footprint(), true, 0.0);
// test construction
unsigned char cost = static_cast<unsigned char>(1);
nav2_smac_planner::Node2D testA(1);
testA.setCost(cost);
nav2_smac_planner::Node2D testB(1);
testB.setCost(cost);
EXPECT_EQ(testA.getCost(), 1.0f);
nav2_smac_planner::SearchInfo info;
info.cost_penalty = 1.0;
unsigned int size = 10;
nav2_smac_planner::Node2D::initMotionModel(
nav2_smac_planner::MotionModel::TWOD, size, size, size, info);
// test reset
testA.reset();
EXPECT_TRUE(std::isnan(testA.getCost()));
// check collision checking
EXPECT_EQ(testA.isNodeValid(false, checker.get()), true);
testA.setCost(255);
EXPECT_EQ(testA.isNodeValid(true, checker.get()), true);
testA.setCost(10);
// check traversal cost computation
EXPECT_NEAR(testB.getTraversalCost(&testA), 1.03f, 0.1f);
// check heuristic cost computation
nav2_smac_planner::Node2D::Coordinates A(0.0, 0.0);
nav2_smac_planner::Node2D::Coordinates B(10.0, 5.0);
EXPECT_NEAR(testB.getHeuristicCost(A, B, nullptr), 11.18, 0.02);
// check operator== works on index
unsigned char costC = '2';
nav2_smac_planner::Node2D testC(1);
testC.setCost(costC);
EXPECT_TRUE(testA == testC);
// check accumulated costs are set
testC.setAccumulatedCost(100);
EXPECT_EQ(testC.getAccumulatedCost(), 100.0f);
// check visiting state
EXPECT_EQ(testC.wasVisited(), false);
testC.queued();
EXPECT_EQ(testC.isQueued(), true);
testC.visited();
EXPECT_EQ(testC.wasVisited(), true);
EXPECT_EQ(testC.isQueued(), false);
// check index
EXPECT_EQ(testC.getIndex(), 1u);
// check static index functions
EXPECT_EQ(nav2_smac_planner::Node2D::getIndex(1u, 1u, 10u), 11u);
EXPECT_EQ(nav2_smac_planner::Node2D::getIndex(6u, 43u, 10u), 436u);
EXPECT_EQ(nav2_smac_planner::Node2D::getCoords(436u, 10u, 1u).x, 6u);
EXPECT_EQ(nav2_smac_planner::Node2D::getCoords(436u, 10u, 1u).y, 43u);
EXPECT_THROW(nav2_smac_planner::Node2D::getCoords(436u, 10u, 10u), std::runtime_error);
}
TEST(Node2DTest, test_node_2d_neighbors)
{
auto lnode = std::make_shared<rclcpp_lifecycle::LifecycleNode>("test");
nav2_smac_planner::SearchInfo info;
unsigned int size_x = 10u;
unsigned int size_y = 10u;
unsigned int quant = 0u;
// test neighborhood computation
size_x = 100u;
nav2_smac_planner::Node2D::initMotionModel(
nav2_smac_planner::MotionModel::TWOD, size_x, size_y,
quant, info);
EXPECT_EQ(nav2_smac_planner::Node2D::_neighbors_grid_offsets.size(), 8u);
EXPECT_EQ(nav2_smac_planner::Node2D::_neighbors_grid_offsets[0], -1);
EXPECT_EQ(nav2_smac_planner::Node2D::_neighbors_grid_offsets[1], 1);
EXPECT_EQ(nav2_smac_planner::Node2D::_neighbors_grid_offsets[2], -100);
EXPECT_EQ(nav2_smac_planner::Node2D::_neighbors_grid_offsets[3], 100);
EXPECT_EQ(nav2_smac_planner::Node2D::_neighbors_grid_offsets[4], -101);
EXPECT_EQ(nav2_smac_planner::Node2D::_neighbors_grid_offsets[5], -99);
EXPECT_EQ(nav2_smac_planner::Node2D::_neighbors_grid_offsets[6], 99);
EXPECT_EQ(nav2_smac_planner::Node2D::_neighbors_grid_offsets[7], 101);
nav2_costmap_2d::Costmap2D costmapA(10, 10, 0.05, 0.0, 0.0, 0);
std::unique_ptr<nav2_smac_planner::GridCollisionChecker> checker =
std::make_unique<nav2_smac_planner::GridCollisionChecker>(&costmapA, 72, lnode);
unsigned char cost = static_cast<unsigned int>(1);
nav2_smac_planner::Node2D * node = new nav2_smac_planner::Node2D(1);
node->setCost(cost);
std::function<bool(const unsigned int &, nav2_smac_planner::Node2D * &)> neighborGetter =
[&, this](const unsigned int & index, nav2_smac_planner::Node2D * & neighbor_rtn) -> bool
{
return false;
};
nav2_smac_planner::Node2D::NodeVector neighbors;
node->getNeighbors(neighborGetter, checker.get(), false, neighbors);
delete node;
// should be empty since totally invalid
EXPECT_EQ(neighbors.size(), 0u);
}
@@ -0,0 +1,55 @@
// Copyright (c) 2020, Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#include <math.h>
#include <memory>
#include <string>
#include <vector>
#include "gtest/gtest.h"
#include "rclcpp/rclcpp.hpp"
#include "nav2_costmap_2d/costmap_2d.hpp"
#include "nav2_costmap_2d/costmap_subscriber.hpp"
#include "nav2_util/lifecycle_node.hpp"
#include "nav2_smac_planner/node_basic.hpp"
#include "nav2_smac_planner/node_2d.hpp"
#include "nav2_smac_planner/node_hybrid.hpp"
#include "nav2_smac_planner/node_lattice.hpp"
#include "nav2_smac_planner/collision_checker.hpp"
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
TEST(NodeBasicTest, test_node_basic)
{
nav2_smac_planner::NodeBasic<nav2_smac_planner::NodeHybrid> node(50);
EXPECT_EQ(node.index, 50u);
EXPECT_EQ(node.graph_node_ptr, nullptr);
nav2_smac_planner::NodeBasic<nav2_smac_planner::Node2D> node2(100);
EXPECT_EQ(node2.index, 100u);
EXPECT_EQ(node2.graph_node_ptr, nullptr);
nav2_smac_planner::NodeBasic<nav2_smac_planner::NodeLattice> node3(200);
EXPECT_EQ(node3.index, 200u);
EXPECT_EQ(node3.graph_node_ptr, nullptr);
}
@@ -0,0 +1,349 @@
// Copyright (c) 2020, Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#include <math.h>
#include <cmath>
#include <memory>
#include <string>
#include <vector>
#include "gtest/gtest.h"
#include "rclcpp/rclcpp.hpp"
#include "nav2_costmap_2d/costmap_2d.hpp"
#include "nav2_costmap_2d/costmap_subscriber.hpp"
#include "nav2_util/lifecycle_node.hpp"
#include "nav2_smac_planner/node_hybrid.hpp"
#include "nav2_smac_planner/collision_checker.hpp"
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
TEST(NodeHybridTest, test_node_hybrid)
{
auto node = std::make_shared<rclcpp_lifecycle::LifecycleNode>("test");
nav2_smac_planner::SearchInfo info;
info.change_penalty = 0.1;
info.non_straight_penalty = 1.1;
info.reverse_penalty = 2.0;
info.minimum_turning_radius = 8; // 0.4m/5cm resolution costmap
info.cost_penalty = 1.7;
info.retrospective_penalty = 0.1;
unsigned int size_x = 10;
unsigned int size_y = 10;
unsigned int size_theta = 72;
// Check defaulted constants
nav2_smac_planner::NodeHybrid testA(49);
EXPECT_EQ(testA.travel_distance_cost, sqrt(2));
nav2_smac_planner::NodeHybrid::initMotionModel(
nav2_smac_planner::MotionModel::DUBIN, size_x, size_y, size_theta, info);
nav2_costmap_2d::Costmap2D * costmapA = new nav2_costmap_2d::Costmap2D(
10, 10, 0.05, 0.0, 0.0, 0);
std::unique_ptr<nav2_smac_planner::GridCollisionChecker> checker =
std::make_unique<nav2_smac_planner::GridCollisionChecker>(costmapA, 72, node);
checker->setFootprint(nav2_costmap_2d::Footprint(), true, 0.0);
// test construction
nav2_smac_planner::NodeHybrid testB(49);
EXPECT_TRUE(std::isnan(testA.getCost()));
// test node valid and cost
testA.pose.x = 5;
testA.pose.y = 5;
testA.pose.theta = 0;
EXPECT_EQ(testA.isNodeValid(true, checker.get()), true);
EXPECT_EQ(testA.isNodeValid(false, checker.get()), true);
EXPECT_EQ(testA.getCost(), 0.0f);
// test reset
testA.reset();
EXPECT_TRUE(std::isnan(testA.getCost()));
// Check motion-specific constants
EXPECT_NEAR(testA.travel_distance_cost, 2.08842, 0.1);
// check collision checking
EXPECT_EQ(testA.isNodeValid(false, checker.get()), true);
// check traversal cost computation
// simulated first node, should return neutral cost
EXPECT_NEAR(testB.getTraversalCost(&testA), 2.088, 0.1);
// now with straight motion, cost is 0, so will be neutral as well
// but now reduced by retrospective penalty (10%)
testB.setMotionPrimitiveIndex(1);
testA.setMotionPrimitiveIndex(0);
EXPECT_NEAR(testB.getTraversalCost(&testA), 2.088 * 0.9, 0.1);
// same direction as parent, testB
testA.setMotionPrimitiveIndex(1);
EXPECT_NEAR(testB.getTraversalCost(&testA), 2.297f * 0.9, 0.01);
// opposite direction as parent, testB
testA.setMotionPrimitiveIndex(2);
EXPECT_NEAR(testB.getTraversalCost(&testA), 2.506f * 0.9, 0.01);
// will throw because never collision checked testB
EXPECT_THROW(testA.getTraversalCost(&testB), std::runtime_error);
// check motion primitives
EXPECT_EQ(testA.getMotionPrimitiveIndex(), 2u);
// check operator== works on index
nav2_smac_planner::NodeHybrid testC(49);
EXPECT_TRUE(testA == testC);
// check accumulated costs are set
testC.setAccumulatedCost(100);
EXPECT_EQ(testC.getAccumulatedCost(), 100.0f);
// check visiting state
EXPECT_EQ(testC.wasVisited(), false);
testC.visited();
EXPECT_EQ(testC.wasVisited(), true);
// check index
EXPECT_EQ(testC.getIndex(), 49u);
// check set pose and pose
testC.setPose(nav2_smac_planner::NodeHybrid::Coordinates(10.0, 5.0, 4));
EXPECT_EQ(testC.pose.x, 10.0);
EXPECT_EQ(testC.pose.y, 5.0);
EXPECT_EQ(testC.pose.theta, 4);
// check static index functions
EXPECT_EQ(nav2_smac_planner::NodeHybrid::getIndex(1u, 1u, 4u, 10u, 72u), 796u);
EXPECT_EQ(nav2_smac_planner::NodeHybrid::getCoords(796u, 10u, 72u).x, 1u);
EXPECT_EQ(nav2_smac_planner::NodeHybrid::getCoords(796u, 10u, 72u).y, 1u);
EXPECT_EQ(nav2_smac_planner::NodeHybrid::getCoords(796u, 10u, 72u).theta, 4u);
delete costmapA;
}
TEST(NodeHybridTest, test_obstacle_heuristic)
{
auto node = std::make_shared<rclcpp_lifecycle::LifecycleNode>("test");
nav2_smac_planner::SearchInfo info;
info.change_penalty = 0.1;
info.non_straight_penalty = 1.1;
info.reverse_penalty = 2.0;
info.minimum_turning_radius = 8; // 0.4m/5cm resolution costmap
info.cost_penalty = 1.7;
info.retrospective_penalty = 0.0;
unsigned int size_x = 100;
unsigned int size_y = 100;
unsigned int size_theta = 72;
nav2_smac_planner::NodeHybrid::initMotionModel(
nav2_smac_planner::MotionModel::DUBIN, size_x, size_y, size_theta, info);
nav2_costmap_2d::Costmap2D * costmapA = new nav2_costmap_2d::Costmap2D(
100, 100, 0.1, 0.0, 0.0, 0);
// island in the middle of lethal cost to cross
for (unsigned int i = 20; i <= 80; ++i) {
for (unsigned int j = 40; j <= 60; ++j) {
costmapA->setCost(i, j, 254);
}
}
// path on the right is narrow and thus with high cost
for (unsigned int i = 20; i <= 80; ++i) {
for (unsigned int j = 61; j <= 70; ++j) {
costmapA->setCost(i, j, 250);
}
}
for (unsigned int i = 20; i <= 80; ++i) {
for (unsigned int j = 71; j < 100; ++j) {
costmapA->setCost(i, j, 254);
}
}
std::unique_ptr<nav2_smac_planner::GridCollisionChecker> checker =
std::make_unique<nav2_smac_planner::GridCollisionChecker>(costmapA, 72, node);
checker->setFootprint(nav2_costmap_2d::Footprint(), true, 0.0);
nav2_smac_planner::NodeHybrid testA(0);
testA.pose.x = 10;
testA.pose.y = 50;
testA.pose.theta = 0;
nav2_smac_planner::NodeHybrid testB(1);
testB.pose.x = 90;
testB.pose.y = 51; // goal is a bit closer to the high-cost passage
testB.pose.theta = 0;
// first block the high-cost passage to make sure the cost spreads through the better path
for (unsigned int j = 61; j <= 70; ++j) {
costmapA->setCost(50, j, 254);
}
nav2_smac_planner::NodeHybrid::resetObstacleHeuristic(
costmapA, testA.pose.x, testA.pose.y, testB.pose.x, testB.pose.y);
float wide_passage_cost = nav2_smac_planner::NodeHybrid::getObstacleHeuristic(
testA.pose,
testB.pose,
info.cost_penalty);
EXPECT_NEAR(wide_passage_cost, 91.1f, 0.1f);
// then unblock it to check if cost remains the same
// (it should, since the unblocked narrow path will have higher cost than the wide one
// and thus lower bound of the path cost should be unchanged)
for (unsigned int j = 61; j <= 70; ++j) {
costmapA->setCost(50, j, 250);
}
nav2_smac_planner::NodeHybrid::resetObstacleHeuristic(
costmapA,
testA.pose.x, testA.pose.y, testB.pose.x, testB.pose.y);
float two_passages_cost = nav2_smac_planner::NodeHybrid::getObstacleHeuristic(
testA.pose,
testB.pose,
info.cost_penalty);
EXPECT_EQ(wide_passage_cost, two_passages_cost);
delete costmapA;
}
TEST(NodeHybridTest, test_node_debin_neighbors)
{
nav2_smac_planner::SearchInfo info;
info.change_penalty = 1.2;
info.non_straight_penalty = 1.4;
info.reverse_penalty = 2.1;
info.minimum_turning_radius = 4; // 0.2 in grid coordinates
info.retrospective_penalty = 0.0;
unsigned int size_x = 100;
unsigned int size_y = 100;
unsigned int size_theta = 72;
nav2_smac_planner::NodeHybrid::initMotionModel(
nav2_smac_planner::MotionModel::DUBIN, size_x, size_y, size_theta, info);
// test neighborhood computation
EXPECT_EQ(nav2_smac_planner::NodeHybrid::motion_table.projections.size(), 3u);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[0]._x, 1.731517, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[0]._y, 0, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[0]._theta, 0, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[1]._x, 1.69047, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[1]._y, 0.3747, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[1]._theta, 5, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[2]._x, 1.69047, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[2]._y, -0.3747, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[2]._theta, -5, 0.01);
}
TEST(NodeHybridTest, test_node_reeds_neighbors)
{
auto lnode = std::make_shared<rclcpp_lifecycle::LifecycleNode>("test");
nav2_smac_planner::SearchInfo info;
info.change_penalty = 1.2;
info.non_straight_penalty = 1.4;
info.reverse_penalty = 2.1;
info.minimum_turning_radius = 8; // 0.4 in grid coordinates
info.retrospective_penalty = 0.0;
unsigned int size_x = 100;
unsigned int size_y = 100;
unsigned int size_theta = 72;
nav2_smac_planner::NodeHybrid::initMotionModel(
nav2_smac_planner::MotionModel::REEDS_SHEPP, size_x, size_y, size_theta, info);
EXPECT_EQ(nav2_smac_planner::NodeHybrid::motion_table.projections.size(), 6u);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[0]._x, 2.088, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[0]._y, 0, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[0]._theta, 0, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[1]._x, 2.070, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[1]._y, 0.272, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[1]._theta, 3, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[2]._x, 2.070, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[2]._y, -0.272, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[2]._theta, -3, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[3]._x, -2.088, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[3]._y, 0, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[3]._theta, 0, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[4]._x, -2.07, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[4]._y, 0.272, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[4]._theta, -3, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[5]._x, -2.07, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[5]._y, -0.272, 0.01);
EXPECT_NEAR(nav2_smac_planner::NodeHybrid::motion_table.projections[5]._theta, 3, 0.01);
nav2_costmap_2d::Costmap2D costmapA(100, 100, 0.05, 0.0, 0.0, 0);
std::unique_ptr<nav2_smac_planner::GridCollisionChecker> checker =
std::make_unique<nav2_smac_planner::GridCollisionChecker>(&costmapA, 72, lnode);
checker->setFootprint(nav2_costmap_2d::Footprint(), true, 0.0);
nav2_smac_planner::NodeHybrid * node = new nav2_smac_planner::NodeHybrid(49);
std::function<bool(const unsigned int &, nav2_smac_planner::NodeHybrid * &)> neighborGetter =
[&, this](const unsigned int & index, nav2_smac_planner::NodeHybrid * & neighbor_rtn) -> bool
{
// because we don't return a real object
return false;
};
nav2_smac_planner::NodeHybrid::NodeVector neighbors;
node->getNeighbors(neighborGetter, checker.get(), false, neighbors);
delete node;
// should be empty since totally invalid
EXPECT_EQ(neighbors.size(), 0u);
}
TEST(NodeHybridTest, basic_get_closest_angular_bin_test)
{
// Tests to check getClosestAngularBin behavior for different input types
nav2_smac_planner::HybridMotionTable motion_table;
{
motion_table.bin_size = 3.1415926;
motion_table.num_angle_quantization = 2;
double test_theta = 3.1415926;
unsigned int expected_angular_bin = 1;
unsigned int calculated_angular_bin = motion_table.getClosestAngularBin(test_theta);
EXPECT_EQ(expected_angular_bin, calculated_angular_bin);
}
{
motion_table.bin_size = M_PI;
motion_table.num_angle_quantization = 2;
double test_theta = M_PI / 2.0 - 0.000001;
unsigned int expected_angular_bin = 0;
unsigned int calculated_angular_bin = motion_table.getClosestAngularBin(test_theta);
EXPECT_EQ(expected_angular_bin, calculated_angular_bin);
}
{
motion_table.bin_size = M_PI;
motion_table.num_angle_quantization = 2;
float test_theta = M_PI;
unsigned int expected_angular_bin = 1;
unsigned int calculated_angular_bin = motion_table.getClosestAngularBin(test_theta);
EXPECT_EQ(expected_angular_bin, calculated_angular_bin);
}
{
motion_table.bin_size = 0.0872664675;
motion_table.num_angle_quantization = 72;
double test_theta = 6.28317530718; // 0.0001 less than 2 pi
unsigned int expected_angular_bin = 0; // should be closer to wrap around
unsigned int calculated_angular_bin = motion_table.getClosestAngularBin(test_theta);
EXPECT_EQ(expected_angular_bin, calculated_angular_bin);
}
}
@@ -0,0 +1,369 @@
// Copyright (c) 2021 Joshua Wallace
// Copyright (c) 2021 Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#include <fstream>
#include <string>
#include <vector>
#include <memory>
#include <unordered_map>
#include <limits>
#include "nav2_smac_planner/node_lattice.hpp"
#include "gtest/gtest.h"
#include "ament_index_cpp/get_package_share_directory.hpp"
#include "nav2_util/lifecycle_node.hpp"
using json = nlohmann::json;
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
TEST(NodeLatticeTest, parser_test)
{
std::string pkg_share_dir = ament_index_cpp::get_package_share_directory("nav2_smac_planner");
std::string filePath =
pkg_share_dir +
"/sample_primitives/5cm_resolution/0.5m_turning_radius/ackermann" +
"/output.json";
std::ifstream myJsonFile(filePath);
ASSERT_TRUE(myJsonFile.is_open());
json j;
myJsonFile >> j;
nav2_smac_planner::LatticeMetadata metaData;
nav2_smac_planner::MotionPrimitive myPrimitive;
nav2_smac_planner::MotionPose pose;
json jsonMetaData = j["lattice_metadata"];
json jsonPrimatives = j["primitives"];
json jsonPose = jsonPrimatives[0]["poses"][0];
nav2_smac_planner::fromJsonToMetaData(jsonMetaData, metaData);
// Checks for parsing meta data
EXPECT_NEAR(metaData.min_turning_radius, 0.5, 0.001);
EXPECT_NEAR(metaData.grid_resolution, 0.05, 0.001);
EXPECT_NEAR(metaData.number_of_headings, 16, 0.01);
EXPECT_NEAR(metaData.heading_angles[0], 0.0, 0.01);
EXPECT_EQ(metaData.number_of_trajectories, 80u);
EXPECT_EQ(metaData.motion_model, std::string("ackermann"));
std::vector<nav2_smac_planner::MotionPrimitive> myPrimitives;
for (unsigned int i = 0; i < jsonPrimatives.size(); ++i) {
nav2_smac_planner::MotionPrimitive newPrimative;
nav2_smac_planner::fromJsonToMotionPrimitive(jsonPrimatives[i], newPrimative);
myPrimitives.push_back(newPrimative);
}
// Checks for parsing primitives
EXPECT_EQ(myPrimitives.size(), 80u);
EXPECT_NEAR(myPrimitives[0].trajectory_id, 0, 0.01);
EXPECT_NEAR(myPrimitives[0].start_angle, 0.0, 0.01);
EXPECT_NEAR(myPrimitives[0].end_angle, 13, 0.01);
EXPECT_NEAR(myPrimitives[0].turning_radius, 0.5259, 0.01);
EXPECT_NEAR(myPrimitives[0].trajectory_length, 0.64856, 0.01);
EXPECT_NEAR(myPrimitives[0].arc_length, 0.58225, 0.01);
EXPECT_NEAR(myPrimitives[0].straight_length, 0.06631, 0.01);
EXPECT_NEAR(myPrimitives[0].poses[0]._x, 0.04981, 0.01);
EXPECT_NEAR(myPrimitives[0].poses[0]._y, -0.00236, 0.01);
EXPECT_NEAR(myPrimitives[0].poses[0]._theta, 6.1883, 0.01);
EXPECT_NEAR(myPrimitives[0].poses[1]._x, 0.09917, 0.01);
EXPECT_NEAR(myPrimitives[0].poses[1]._y, -0.00944, 0.01);
EXPECT_NEAR(myPrimitives[0].poses[1]._theta, 6.09345, 0.015);
}
TEST(NodeLatticeTest, test_node_lattice_neighbors_and_parsing)
{
std::string pkg_share_dir = ament_index_cpp::get_package_share_directory("nav2_smac_planner");
std::string filePath =
pkg_share_dir +
"/sample_primitives/5cm_resolution/0.5m_turning_radius/ackermann" +
"/output.json";
nav2_smac_planner::SearchInfo info;
info.minimum_turning_radius = 1.1;
info.non_straight_penalty = 1;
info.change_penalty = 1;
info.reverse_penalty = 1;
info.cost_penalty = 1;
info.retrospective_penalty = 0.0;
info.analytic_expansion_ratio = 1;
info.lattice_filepath = filePath;
info.cache_obstacle_heuristic = true;
info.allow_reverse_expansion = true;
unsigned int x = 100;
unsigned int y = 100;
unsigned int angle_quantization = 16;
nav2_smac_planner::NodeLattice::initMotionModel(
nav2_smac_planner::MotionModel::STATE_LATTICE, x, y, angle_quantization, info);
nav2_smac_planner::NodeLattice aNode(0);
aNode.setPose(nav2_smac_planner::NodeHybrid::Coordinates(0, 0, 0));
nav2_smac_planner::MotionPrimitivePtrs projections =
nav2_smac_planner::NodeLattice::motion_table.getMotionPrimitives(&aNode);
EXPECT_NEAR(projections[0]->poses.back()._x, 0.5, 0.01);
EXPECT_NEAR(projections[0]->poses.back()._y, -0.35, 0.01);
EXPECT_NEAR(projections[0]->poses.back()._theta, 5.176, 0.01);
EXPECT_NEAR(
nav2_smac_planner::NodeLattice::motion_table.getLatticeMetadata(
filePath).grid_resolution, 0.05, 0.005);
}
TEST(NodeLatticeTest, test_node_lattice_conversions)
{
std::string pkg_share_dir = ament_index_cpp::get_package_share_directory("nav2_smac_planner");
std::string filePath =
pkg_share_dir +
"/sample_primitives/5cm_resolution/0.5m_turning_radius/ackermann" +
"/output.json";
nav2_smac_planner::SearchInfo info;
info.minimum_turning_radius = 1.1;
info.non_straight_penalty = 1;
info.change_penalty = 1;
info.reverse_penalty = 1;
info.cost_penalty = 1;
info.retrospective_penalty = 0.0;
info.analytic_expansion_ratio = 1;
info.lattice_filepath = filePath;
info.cache_obstacle_heuristic = true;
unsigned int x = 100;
unsigned int y = 100;
unsigned int angle_quantization = 16;
nav2_smac_planner::NodeLattice::initMotionModel(
nav2_smac_planner::MotionModel::STATE_LATTICE, x, y, angle_quantization, info);
nav2_smac_planner::NodeLattice aNode(0);
aNode.setPose(nav2_smac_planner::NodeHybrid::Coordinates(0, 0, 0));
EXPECT_NEAR(aNode.motion_table.getAngleFromBin(0u), 0.0, 0.005);
EXPECT_NEAR(aNode.motion_table.getAngleFromBin(1u), 0.46364, 0.005);
EXPECT_NEAR(aNode.motion_table.getAngleFromBin(2u), 0.78539, 0.005);
EXPECT_EQ(aNode.motion_table.getClosestAngularBin(0.0), 0u);
EXPECT_EQ(aNode.motion_table.getClosestAngularBin(0.5), 1u);
EXPECT_EQ(aNode.motion_table.getClosestAngularBin(1.5), 4u);
}
TEST(NodeLatticeTest, test_node_lattice)
{
auto node = std::make_shared<rclcpp_lifecycle::LifecycleNode>("test");
std::string pkg_share_dir = ament_index_cpp::get_package_share_directory("nav2_smac_planner");
std::string filePath =
pkg_share_dir +
"/sample_primitives/5cm_resolution/0.5m_turning_radius/ackermann" +
"/output.json";
nav2_smac_planner::SearchInfo info;
info.minimum_turning_radius = 1.1;
info.non_straight_penalty = 1;
info.change_penalty = 1;
info.reverse_penalty = 1;
info.cost_penalty = 1;
info.retrospective_penalty = 0.1;
info.analytic_expansion_ratio = 1;
info.lattice_filepath = filePath;
info.cache_obstacle_heuristic = true;
info.allow_reverse_expansion = true;
unsigned int x = 100;
unsigned int y = 100;
unsigned int angle_quantization = 16;
nav2_smac_planner::NodeLattice::initMotionModel(
nav2_smac_planner::MotionModel::STATE_LATTICE, x, y, angle_quantization, info);
// Check defaults
nav2_smac_planner::NodeLattice aNode(0);
nav2_smac_planner::NodeLattice testA(49);
EXPECT_EQ(testA.getIndex(), 49u);
EXPECT_EQ(testA.getAccumulatedCost(), std::numeric_limits<float>::max());
EXPECT_TRUE(std::isnan(testA.getCost()));
EXPECT_EQ(testA.getMotionPrimitive(), nullptr);
// Test visited state / reset
EXPECT_EQ(testA.wasVisited(), false);
testA.visited();
EXPECT_EQ(testA.wasVisited(), true);
testA.reset();
EXPECT_EQ(testA.wasVisited(), false);
nav2_costmap_2d::Costmap2D * costmapA = new nav2_costmap_2d::Costmap2D(
10, 10, 0.05, 0.0, 0.0, 0);
std::unique_ptr<nav2_smac_planner::GridCollisionChecker> checker =
std::make_unique<nav2_smac_planner::GridCollisionChecker>(costmapA, 72, node);
checker->setFootprint(nav2_costmap_2d::Footprint(), true, 0.0);
// test node valid and cost
testA.pose.x = 5;
testA.pose.y = 5;
testA.pose.theta = 0;
EXPECT_EQ(testA.isNodeValid(true, checker.get()), true);
EXPECT_EQ(testA.isNodeValid(false, checker.get()), true);
EXPECT_EQ(testA.getCost(), 0.0f);
// check collision checking
EXPECT_EQ(testA.isNodeValid(false, checker.get()), true);
// check operator== works on index
nav2_smac_planner::NodeLattice testC(49);
EXPECT_TRUE(testA == testC);
// check accumulated costs are set
testC.setAccumulatedCost(100);
EXPECT_EQ(testC.getAccumulatedCost(), 100.0f);
// check set pose and pose
testC.setPose(nav2_smac_planner::NodeLattice::Coordinates(10.0, 5.0, 4));
EXPECT_EQ(testC.pose.x, 10.0);
EXPECT_EQ(testC.pose.y, 5.0);
EXPECT_EQ(testC.pose.theta, 4);
delete costmapA;
}
TEST(NodeLatticeTest, test_get_neighbors)
{
auto lnode = std::make_shared<rclcpp_lifecycle::LifecycleNode>("test");
std::string pkg_share_dir = ament_index_cpp::get_package_share_directory("nav2_smac_planner");
std::string filePath =
pkg_share_dir +
"/sample_primitives/5cm_resolution/0.5m_turning_radius/ackermann" +
"/output.json";
nav2_smac_planner::SearchInfo info;
info.minimum_turning_radius = 1.1;
info.non_straight_penalty = 1;
info.change_penalty = 1;
info.reverse_penalty = 1;
info.cost_penalty = 1;
info.analytic_expansion_ratio = 1;
info.retrospective_penalty = 0.0;
info.lattice_filepath = filePath;
info.cache_obstacle_heuristic = true;
info.allow_reverse_expansion = true;
unsigned int x = 100;
unsigned int y = 100;
unsigned int angle_quantization = 16;
nav2_smac_planner::NodeLattice::initMotionModel(
nav2_smac_planner::MotionModel::STATE_LATTICE, x, y, angle_quantization, info);
nav2_smac_planner::NodeLattice node(49);
nav2_costmap_2d::Costmap2D * costmapA = new nav2_costmap_2d::Costmap2D(
10, 10, 0.05, 0.0, 0.0, 0);
std::unique_ptr<nav2_smac_planner::GridCollisionChecker> checker =
std::make_unique<nav2_smac_planner::GridCollisionChecker>(costmapA, 72, lnode);
checker->setFootprint(nav2_costmap_2d::Footprint(), true, 0.0);
std::function<bool(const unsigned int &, nav2_smac_planner::NodeLattice * &)> neighborGetter =
[&, this](const unsigned int & index, nav2_smac_planner::NodeLattice * & neighbor_rtn) -> bool
{
// because we don't return a real object
return false;
};
nav2_smac_planner::NodeLattice::NodeVector neighbors;
node.getNeighbors(neighborGetter, checker.get(), false, neighbors);
// should be empty since totally invalid
EXPECT_EQ(neighbors.size(), 0u);
delete costmapA;
}
TEST(NodeLatticeTest, test_node_lattice_custom_footprint)
{
auto lnode = std::make_shared<rclcpp_lifecycle::LifecycleNode>("test");
std::string pkg_share_dir = ament_index_cpp::get_package_share_directory("nav2_smac_planner");
std::string filePath =
pkg_share_dir +
"/sample_primitives/5cm_resolution/0.5m_turning_radius/ackermann" +
"/output.json";
nav2_smac_planner::SearchInfo info;
info.minimum_turning_radius = 0.5;
info.non_straight_penalty = 1;
info.change_penalty = 1;
info.reverse_penalty = 1;
info.cost_penalty = 1;
info.retrospective_penalty = 0.1;
info.analytic_expansion_ratio = 1;
info.lattice_filepath = filePath;
info.cache_obstacle_heuristic = true;
info.allow_reverse_expansion = true;
unsigned int x = 100;
unsigned int y = 100;
unsigned int angle_quantization = 16;
nav2_smac_planner::NodeLattice::initMotionModel(
nav2_smac_planner::MotionModel::STATE_LATTICE, x, y, angle_quantization, info);
nav2_smac_planner::NodeLattice node(49);
nav2_costmap_2d::Costmap2D * costmap = new nav2_costmap_2d::Costmap2D(
40, 40, 0.05, 0.0, 0.0, 0);
std::unique_ptr<nav2_smac_planner::GridCollisionChecker> checker =
std::make_unique<nav2_smac_planner::GridCollisionChecker>(costmap, 72, lnode);
// Make some custom asymmetrical footprint
nav2_costmap_2d::Footprint footprint;
geometry_msgs::msg::Point p;
p.x = -0.1;
p.y = -0.15;
footprint.push_back(p);
p.x = 0.35;
p.y = -0.15;
footprint.push_back(p);
p.x = 0.35;
p.y = 0.22;
footprint.push_back(p);
p.x = -0.1;
p.y = 0.22;
footprint.push_back(p);
checker->setFootprint(footprint, false, 0.0);
// Setting initial robot pose to (1.0, 1.0, 0.0)
node.pose.x = 20;
node.pose.y = 20;
node.pose.theta = 0;
// Test that the node is valid though all motion primitives poses for custom footprint
nav2_smac_planner::MotionPrimitivePtrs motion_primitives =
nav2_smac_planner::NodeLattice::motion_table.getMotionPrimitives(&node);
EXPECT_GT(motion_primitives.size(), 0u);
for (unsigned int i = 0; i < motion_primitives.size(); i++) {
EXPECT_EQ(node.isNodeValid(true, checker.get(), motion_primitives[i], false), true);
EXPECT_EQ(node.isNodeValid(true, checker.get(), motion_primitives[i], true), true);
}
delete costmap;
}
@@ -0,0 +1,137 @@
// Copyright (c) 2020, Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#include <math.h>
#include <memory>
#include <string>
#include <vector>
#include "geometry_msgs/msg/pose_stamped.hpp"
#include "gtest/gtest.h"
#include "nav2_costmap_2d/costmap_2d.hpp"
#include "nav2_costmap_2d/costmap_subscriber.hpp"
#include "nav2_smac_planner/a_star.hpp"
#include "nav2_smac_planner/collision_checker.hpp"
#include "nav2_smac_planner/node_hybrid.hpp"
#include "nav2_smac_planner/smac_planner_2d.hpp"
#include "nav2_smac_planner/smac_planner_hybrid.hpp"
#include "nav2_util/lifecycle_node.hpp"
#include "rclcpp/rclcpp.hpp"
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
// SMAC smoke tests for plugin-level issues rather than algorithms
// (covered by more extensively testing in other files)
// System tests in nav2_system_tests will actually plan with this work
TEST(SmacTest, test_smac_2d) {
rclcpp_lifecycle::LifecycleNode::SharedPtr node2D =
std::make_shared<rclcpp_lifecycle::LifecycleNode>("Smac2DTest");
std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros =
std::make_shared<nav2_costmap_2d::Costmap2DROS>("global_costmap");
costmap_ros->on_configure(rclcpp_lifecycle::State());
node2D->declare_parameter("test.smooth_path", true);
node2D->set_parameter(rclcpp::Parameter("test.smooth_path", true));
node2D->declare_parameter("test.downsample_costmap", true);
node2D->set_parameter(rclcpp::Parameter("test.downsample_costmap", true));
node2D->declare_parameter("test.downsampling_factor", 2);
node2D->set_parameter(rclcpp::Parameter("test.downsampling_factor", 2));
geometry_msgs::msg::PoseStamped start, goal;
start.pose.position.x = 0.0;
start.pose.position.y = 0.0;
start.pose.orientation.w = 1.0;
// goal = start;
goal.pose.position.x = 7.0;
goal.pose.position.y = 0.0;
goal.pose.orientation.w = 1.0;
auto planner_2d = std::make_unique<nav2_smac_planner::SmacPlanner2D>();
planner_2d->configure(node2D, "test", nullptr, costmap_ros);
planner_2d->activate();
try {
planner_2d->createPlan(start, goal);
} catch (...) {
}
planner_2d->deactivate();
planner_2d->cleanup();
planner_2d.reset();
costmap_ros->on_cleanup(rclcpp_lifecycle::State());
node2D.reset();
costmap_ros.reset();
}
TEST(SmacTest, test_smac_2d_reconfigure) {
rclcpp_lifecycle::LifecycleNode::SharedPtr node2D =
std::make_shared<rclcpp_lifecycle::LifecycleNode>("Smac2DTest");
std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros =
std::make_shared<nav2_costmap_2d::Costmap2DROS>("global_costmap");
costmap_ros->on_configure(rclcpp_lifecycle::State());
auto planner_2d = std::make_unique<nav2_smac_planner::SmacPlanner2D>();
planner_2d->configure(node2D, "test", nullptr, costmap_ros);
planner_2d->activate();
auto rec_param = std::make_shared<rclcpp::AsyncParametersClient>(
node2D->get_node_base_interface(), node2D->get_node_topics_interface(),
node2D->get_node_graph_interface(),
node2D->get_node_services_interface());
auto results = rec_param->set_parameters_atomically(
{rclcpp::Parameter("test.tolerance", 1.0),
rclcpp::Parameter("test.cost_travel_multiplier", 1.0),
rclcpp::Parameter("test.max_planning_time", 2.0),
rclcpp::Parameter("test.downsample_costmap", false),
rclcpp::Parameter("test.allow_unknown", false),
rclcpp::Parameter("test.downsampling_factor", 2),
rclcpp::Parameter("test.max_iterations", -1),
rclcpp::Parameter("test.max_on_approach_iterations", -1),
rclcpp::Parameter("test.use_final_approach_orientation", false)});
rclcpp::spin_until_future_complete(
node2D->get_node_base_interface(),
results);
EXPECT_EQ(node2D->get_parameter("test.tolerance").as_double(), 1.0);
EXPECT_EQ(
node2D->get_parameter("test.cost_travel_multiplier").as_double(),
1.0);
EXPECT_EQ(node2D->get_parameter("test.max_planning_time").as_double(), 2.0);
EXPECT_EQ(node2D->get_parameter("test.downsample_costmap").as_bool(), false);
EXPECT_EQ(node2D->get_parameter("test.allow_unknown").as_bool(), false);
EXPECT_EQ(node2D->get_parameter("test.downsampling_factor").as_int(), 2);
EXPECT_EQ(node2D->get_parameter("test.max_iterations").as_int(), -1);
EXPECT_EQ(node2D->get_parameter("test.use_final_approach_orientation").as_bool(), false);
EXPECT_EQ(
node2D->get_parameter("test.max_on_approach_iterations").as_int(),
-1);
results = rec_param->set_parameters_atomically(
{rclcpp::Parameter("test.downsample_costmap", true)});
rclcpp::spin_until_future_complete(
node2D->get_node_base_interface(),
results);
}
@@ -0,0 +1,149 @@
// Copyright (c) 2020, Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#include <math.h>
#include <memory>
#include <string>
#include <vector>
#include "gtest/gtest.h"
#include "rclcpp/rclcpp.hpp"
#include "nav2_costmap_2d/costmap_2d.hpp"
#include "nav2_costmap_2d/costmap_subscriber.hpp"
#include "nav2_util/lifecycle_node.hpp"
#include "geometry_msgs/msg/pose_stamped.hpp"
#include "nav2_smac_planner/node_hybrid.hpp"
#include "nav2_smac_planner/a_star.hpp"
#include "nav2_smac_planner/collision_checker.hpp"
#include "nav2_smac_planner/smac_planner_hybrid.hpp"
#include "nav2_smac_planner/smac_planner_2d.hpp"
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
// SMAC smoke tests for plugin-level issues rather than algorithms
// (covered by more extensively testing in other files)
// System tests in nav2_system_tests will actually plan with this work
TEST(SmacTest, test_smac_se2)
{
rclcpp_lifecycle::LifecycleNode::SharedPtr nodeSE2 =
std::make_shared<rclcpp_lifecycle::LifecycleNode>("SmacSE2Test");
std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros =
std::make_shared<nav2_costmap_2d::Costmap2DROS>("global_costmap");
costmap_ros->on_configure(rclcpp_lifecycle::State());
nodeSE2->declare_parameter("test.downsample_costmap", true);
nodeSE2->set_parameter(rclcpp::Parameter("test.downsample_costmap", true));
nodeSE2->declare_parameter("test.downsampling_factor", 2);
nodeSE2->set_parameter(rclcpp::Parameter("test.downsampling_factor", 2));
geometry_msgs::msg::PoseStamped start, goal;
start.pose.position.x = 0.0;
start.pose.position.y = 0.0;
start.pose.orientation.w = 1.0;
goal.pose.position.x = 1.0;
goal.pose.position.y = 1.0;
goal.pose.orientation.w = 1.0;
auto planner = std::make_unique<nav2_smac_planner::SmacPlannerHybrid>();
planner->configure(nodeSE2, "test", nullptr, costmap_ros);
planner->activate();
try {
planner->createPlan(start, goal);
} catch (...) {
}
planner->deactivate();
planner->cleanup();
planner.reset();
costmap_ros->on_cleanup(rclcpp_lifecycle::State());
costmap_ros.reset();
nodeSE2.reset();
}
TEST(SmacTest, test_smac_se2_reconfigure)
{
rclcpp_lifecycle::LifecycleNode::SharedPtr nodeSE2 =
std::make_shared<rclcpp_lifecycle::LifecycleNode>("SmacSE2Test");
std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros =
std::make_shared<nav2_costmap_2d::Costmap2DROS>("global_costmap");
costmap_ros->on_configure(rclcpp_lifecycle::State());
auto planner = std::make_unique<nav2_smac_planner::SmacPlannerHybrid>();
planner->configure(nodeSE2, "test", nullptr, costmap_ros);
planner->activate();
auto rec_param = std::make_shared<rclcpp::AsyncParametersClient>(
nodeSE2->get_node_base_interface(), nodeSE2->get_node_topics_interface(),
nodeSE2->get_node_graph_interface(),
nodeSE2->get_node_services_interface());
auto results = rec_param->set_parameters_atomically(
{rclcpp::Parameter("test.downsample_costmap", true),
rclcpp::Parameter("test.downsampling_factor", 2),
rclcpp::Parameter("test.angle_quantization_bins", 100),
rclcpp::Parameter("test.allow_unknown", false),
rclcpp::Parameter("test.max_iterations", -1),
rclcpp::Parameter("test.minimum_turning_radius", 1.0),
rclcpp::Parameter("test.cache_obstacle_heuristic", true),
rclcpp::Parameter("test.reverse_penalty", 5.0),
rclcpp::Parameter("test.change_penalty", 1.0),
rclcpp::Parameter("test.non_straight_penalty", 2.0),
rclcpp::Parameter("test.cost_penalty", 2.0),
rclcpp::Parameter("test.tolerance", 0.2),
rclcpp::Parameter("test.retrospective_penalty", 0.2),
rclcpp::Parameter("test.analytic_expansion_ratio", 4.0),
rclcpp::Parameter("test.max_planning_time", 10.0),
rclcpp::Parameter("test.lookup_table_size", 30.0),
rclcpp::Parameter("test.smooth_path", false),
rclcpp::Parameter("test.analytic_expansion_max_length", 42.0),
rclcpp::Parameter("test.max_on_approach_iterations", 42),
rclcpp::Parameter("test.motion_model_for_search", std::string("REEDS_SHEPP"))});
rclcpp::spin_until_future_complete(
nodeSE2->get_node_base_interface(),
results);
EXPECT_EQ(nodeSE2->get_parameter("test.downsample_costmap").as_bool(), true);
EXPECT_EQ(nodeSE2->get_parameter("test.downsampling_factor").as_int(), 2);
EXPECT_EQ(nodeSE2->get_parameter("test.angle_quantization_bins").as_int(), 100);
EXPECT_EQ(nodeSE2->get_parameter("test.allow_unknown").as_bool(), false);
EXPECT_EQ(nodeSE2->get_parameter("test.max_iterations").as_int(), -1);
EXPECT_EQ(nodeSE2->get_parameter("test.minimum_turning_radius").as_double(), 1.0);
EXPECT_EQ(nodeSE2->get_parameter("test.cache_obstacle_heuristic").as_bool(), true);
EXPECT_EQ(nodeSE2->get_parameter("test.reverse_penalty").as_double(), 5.0);
EXPECT_EQ(nodeSE2->get_parameter("test.change_penalty").as_double(), 1.0);
EXPECT_EQ(nodeSE2->get_parameter("test.non_straight_penalty").as_double(), 2.0);
EXPECT_EQ(nodeSE2->get_parameter("test.cost_penalty").as_double(), 2.0);
EXPECT_EQ(nodeSE2->get_parameter("test.retrospective_penalty").as_double(), 0.2);
EXPECT_EQ(nodeSE2->get_parameter("test.tolerance").as_double(), 0.2);
EXPECT_EQ(nodeSE2->get_parameter("test.analytic_expansion_ratio").as_double(), 4.0);
EXPECT_EQ(nodeSE2->get_parameter("test.smooth_path").as_bool(), false);
EXPECT_EQ(nodeSE2->get_parameter("test.max_planning_time").as_double(), 10.0);
EXPECT_EQ(nodeSE2->get_parameter("test.lookup_table_size").as_double(), 30.0);
EXPECT_EQ(nodeSE2->get_parameter("test.analytic_expansion_max_length").as_double(), 42.0);
EXPECT_EQ(nodeSE2->get_parameter("test.max_on_approach_iterations").as_int(), 42);
EXPECT_EQ(
nodeSE2->get_parameter("test.motion_model_for_search").as_string(),
std::string("REEDS_SHEPP"));
}
@@ -0,0 +1,146 @@
// Copyright (c) 2021 Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#include <math.h>
#include <memory>
#include <string>
#include <vector>
#include "gtest/gtest.h"
#include "rclcpp/rclcpp.hpp"
#include "nav2_costmap_2d/costmap_2d.hpp"
#include "nav2_costmap_2d/costmap_subscriber.hpp"
#include "nav2_util/lifecycle_node.hpp"
#include "geometry_msgs/msg/pose_stamped.hpp"
#include "nav2_smac_planner/node_hybrid.hpp"
#include "nav2_smac_planner/a_star.hpp"
#include "nav2_smac_planner/collision_checker.hpp"
#include "nav2_smac_planner/smac_planner_lattice.hpp"
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
// Simple wrapper to be able to call a private member
class LatticeWrap : public nav2_smac_planner::SmacPlannerLattice
{
public:
void callDynamicParams(std::vector<rclcpp::Parameter> parameters)
{
dynamicParametersCallback(parameters);
}
};
// SMAC smoke tests for plugin-level issues rather than algorithms
// (covered by more extensively testing in other files)
// System tests in nav2_system_tests will actually plan with this work
TEST(SmacTest, test_smac_lattice)
{
rclcpp_lifecycle::LifecycleNode::SharedPtr nodeLattice =
std::make_shared<rclcpp_lifecycle::LifecycleNode>("SmacLatticeTest");
std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros =
std::make_shared<nav2_costmap_2d::Costmap2DROS>("global_costmap");
costmap_ros->on_configure(rclcpp_lifecycle::State());
geometry_msgs::msg::PoseStamped start, goal;
start.pose.position.x = 0.0;
start.pose.position.y = 0.0;
start.pose.orientation.w = 1.0;
goal.pose.position.x = 1.0;
goal.pose.position.y = 1.0;
goal.pose.orientation.w = 1.0;
auto planner = std::make_unique<nav2_smac_planner::SmacPlannerLattice>();
try {
// Expect to throw due to invalid prims file in param
planner->configure(nodeLattice, "test", nullptr, costmap_ros);
} catch (...) {
}
planner->activate();
try {
planner->createPlan(start, goal);
} catch (...) {
}
planner->deactivate();
planner->cleanup();
planner.reset();
costmap_ros->on_cleanup(rclcpp_lifecycle::State());
costmap_ros.reset();
nodeLattice.reset();
}
TEST(SmacTest, test_smac_lattice_reconfigure)
{
rclcpp_lifecycle::LifecycleNode::SharedPtr nodeLattice =
std::make_shared<rclcpp_lifecycle::LifecycleNode>("SmacLatticeTest");
std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros =
std::make_shared<nav2_costmap_2d::Costmap2DROS>("global_costmap");
costmap_ros->on_configure(rclcpp_lifecycle::State());
auto planner = std::make_unique<LatticeWrap>();
try {
// Expect to throw due to invalid prims file in param
planner->configure(nodeLattice, "test", nullptr, costmap_ros);
} catch (...) {
}
planner->activate();
auto rec_param = std::make_shared<rclcpp::AsyncParametersClient>(
nodeLattice->get_node_base_interface(), nodeLattice->get_node_topics_interface(),
nodeLattice->get_node_graph_interface(),
nodeLattice->get_node_services_interface());
auto results = rec_param->set_parameters_atomically(
{rclcpp::Parameter("test.allow_unknown", false),
rclcpp::Parameter("test.max_iterations", -1),
rclcpp::Parameter("test.cache_obstacle_heuristic", true),
rclcpp::Parameter("test.reverse_penalty", 5.0),
rclcpp::Parameter("test.change_penalty", 1.0),
rclcpp::Parameter("test.non_straight_penalty", 2.0),
rclcpp::Parameter("test.cost_penalty", 2.0),
rclcpp::Parameter("test.retrospective_penalty", 0.2),
rclcpp::Parameter("test.analytic_expansion_ratio", 4.0),
rclcpp::Parameter("test.max_planning_time", 10.0),
rclcpp::Parameter("test.lookup_table_size", 30.0),
rclcpp::Parameter("test.smooth_path", false),
rclcpp::Parameter("test.analytic_expansion_max_length", 42.0),
rclcpp::Parameter("test.tolerance", 42.0),
rclcpp::Parameter("test.rotation_penalty", 42.0),
rclcpp::Parameter("test.max_on_approach_iterations", 42),
rclcpp::Parameter("test.allow_reverse_expansion", true)});
try {
// All of these params will re-init A* which will involve loading the control set file
// which will cause an exception because the file does not exist. This will cause an
// expected failure preventing parameter updates from being successfully processed
rclcpp::spin_until_future_complete(
nodeLattice->get_node_base_interface(),
results);
} catch (...) {
}
// So instead, lets call manually on a change
std::vector<rclcpp::Parameter> parameters;
parameters.push_back(rclcpp::Parameter("test.lattice_filepath", std::string("HI")));
EXPECT_THROW(planner->callDynamicParams(parameters), std::runtime_error);
}
@@ -0,0 +1,178 @@
// Copyright (c) 2020, Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#include <math.h>
#include <memory>
#include <string>
#include <vector>
#include <limits>
#include "gtest/gtest.h"
#include "rclcpp/rclcpp.hpp"
#include "nav2_costmap_2d/costmap_2d.hpp"
#include "nav2_costmap_2d/costmap_subscriber.hpp"
#include "nav2_util/lifecycle_node.hpp"
#include "nav2_smac_planner/node_hybrid.hpp"
#include "nav2_smac_planner/a_star.hpp"
#include "nav2_smac_planner/collision_checker.hpp"
#include "nav2_smac_planner/smoother.hpp"
#include "ament_index_cpp/get_package_share_directory.hpp"
using namespace nav2_smac_planner; // NOLINT
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
class SmootherWrapper : public nav2_smac_planner::Smoother
{
public:
explicit SmootherWrapper(const SmootherParams & params)
: nav2_smac_planner::Smoother(params)
{}
std::vector<PathSegment> findDirectionalPathSegmentsWrapper(nav_msgs::msg::Path path)
{
return findDirectionalPathSegments(path);
}
};
TEST(SmootherTest, test_full_smoother)
{
rclcpp_lifecycle::LifecycleNode::SharedPtr node =
std::make_shared<rclcpp_lifecycle::LifecycleNode>("SmacSmootherTest");
nav2_smac_planner::SmootherParams params;
params.get(node, "test");
double maxtime = 1.0;
// Make smoother and costmap to smooth in
auto smoother = std::make_unique<SmootherWrapper>(params);
smoother->initialize(0.4 /*turning radius*/);
nav2_costmap_2d::Costmap2D * costmap =
new nav2_costmap_2d::Costmap2D(100, 100, 0.05, 0.0, 0.0, 0);
// island in the middle of lethal cost to cross
for (unsigned int i = 20; i <= 30; ++i) {
for (unsigned int j = 20; j <= 30; ++j) {
costmap->setCost(i, j, 254);
}
}
// Setup A* search to get path to smooth
nav2_smac_planner::SearchInfo info;
info.change_penalty = 0.05;
info.non_straight_penalty = 1.05;
info.reverse_penalty = 2.0;
info.cost_penalty = 2.0;
info.retrospective_penalty = 0.0;
info.analytic_expansion_ratio = 3.5;
info.minimum_turning_radius = 8; // in grid coordinates 0.4/0.05
info.analytic_expansion_max_length = 20.0; // in grid coordinates
unsigned int size_theta = 72;
nav2_smac_planner::AStarAlgorithm<nav2_smac_planner::NodeHybrid> a_star(
nav2_smac_planner::MotionModel::REEDS_SHEPP, info);
int max_iterations = 10000;
float tolerance = 10.0;
int it_on_approach = 10;
double max_planning_time = 120.0;
int num_it = 0;
a_star.initialize(
false, max_iterations, std::numeric_limits<int>::max(), max_planning_time, 401, size_theta);
std::unique_ptr<nav2_smac_planner::GridCollisionChecker> checker =
std::make_unique<nav2_smac_planner::GridCollisionChecker>(costmap, size_theta, node);
checker->setFootprint(nav2_costmap_2d::Footprint(), true, 0.0);
// Create A* search to smooth
a_star.setCollisionChecker(checker.get());
a_star.setStart(5u, 5u, 0u);
a_star.setGoal(45u, 45u, 36u);
nav2_smac_planner::NodeHybrid::CoordinateVector path;
EXPECT_TRUE(a_star.createPath(path, num_it, tolerance));
// Convert to world coordinates and get length to compare to smoothed length
nav_msgs::msg::Path plan;
plan.header.stamp = node->now();
plan.header.frame_id = "map";
geometry_msgs::msg::PoseStamped pose;
pose.header = plan.header;
pose.pose.position.z = 0.0;
pose.pose.orientation.x = 0.0;
pose.pose.orientation.y = 0.0;
pose.pose.orientation.z = 0.0;
pose.pose.orientation.w = 1.0;
double initial_length = 0.0;
double x_m = path[path.size() - 1].x, y_m = path[path.size() - 1].y;
plan.poses.reserve(path.size());
for (int i = path.size() - 1; i >= 0; --i) {
pose.pose = nav2_smac_planner::getWorldCoords(path[i].x, path[i].y, costmap);
pose.pose.orientation = nav2_smac_planner::getWorldOrientation(path[i].theta);
plan.poses.push_back(pose);
initial_length += hypot(path[i].x - x_m, path[i].y - y_m);
x_m = path[i].x;
y_m = path[i].y;
}
// Check that we accurately detect that this path has a reversing segment
EXPECT_EQ(smoother->findDirectionalPathSegmentsWrapper(plan).size(), 2u);
// Test smoother, should succeed with same number of points
// and shorter overall length, while still being collision free.
auto path_size_in = plan.poses.size();
EXPECT_TRUE(smoother->smooth(plan, costmap, maxtime));
EXPECT_EQ(plan.poses.size(), path_size_in); // Should have same number of poses
double length = 0.0;
x_m = plan.poses[0].pose.position.x;
y_m = plan.poses[0].pose.position.y;
for (unsigned int i = 0; i != plan.poses.size(); i++) {
// Should be collision free
EXPECT_EQ(costmap->getCost(plan.poses[i].pose.position.x, plan.poses[i].pose.position.y), 0);
length += hypot(plan.poses[i].pose.position.x - x_m, plan.poses[i].pose.position.y - y_m);
x_m = plan.poses[i].pose.position.x;
y_m = plan.poses[i].pose.position.y;
}
EXPECT_LT(length, initial_length); // Should be shorter
// Try again but with failure modes
// Failure mode: not enough iterations to complete
params.max_its_ = 0;
auto smoother_bypass = std::make_unique<SmootherWrapper>(params);
EXPECT_FALSE(smoother_bypass->smooth(plan, costmap, maxtime));
params.max_its_ = 1;
auto smoother_failure = std::make_unique<SmootherWrapper>(params);
EXPECT_FALSE(smoother_failure->smooth(plan, costmap, maxtime));
// Failure mode: Not enough time
double max_no_time = 0.0;
EXPECT_FALSE(smoother->smooth(plan, costmap, max_no_time));
// Failure mode: Path is in collision, do 2x to exercise overlapping point
// attempts to update orientation should also fail
pose.pose.position.x = 1.25;
pose.pose.position.y = 1.25;
plan.poses.push_back(pose);
plan.poses.push_back(pose);
EXPECT_FALSE(smoother->smooth(plan, costmap, maxtime));
EXPECT_NEAR(plan.poses.end()[-2].pose.orientation.z, 1.0, 1e-3);
EXPECT_NEAR(plan.poses.end()[-2].pose.orientation.x, 0.0, 1e-3);
EXPECT_NEAR(plan.poses.end()[-2].pose.orientation.y, 0.0, 1e-3);
EXPECT_NEAR(plan.poses.end()[-2].pose.orientation.w, 0.0, 1e-3);
delete costmap;
}