// Copyright (c) 2020, Samsung Research America // Copyright (c) 2020, Applied Electric Vehicles Pty Ltd // // 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 #include #include #include #include #include #include #include #include #include #include #include "nav2_smac_planner/a_star.hpp" using namespace std::chrono; // NOLINT namespace nav2_smac_planner { template AStarAlgorithm::AStarAlgorithm( const MotionModel & motion_model, const SearchInfo & search_info) : _traverse_unknown(true), _is_initialized(false), _max_iterations(0), _max_planning_time(0), _x_size(0), _y_size(0), _search_info(search_info), _goal_coordinates(Coordinates()), _start(nullptr), _goal(nullptr), _motion_model(motion_model) { _graph.reserve(100000); } template AStarAlgorithm::~AStarAlgorithm() { } template void AStarAlgorithm::initialize( const bool & allow_unknown, int & max_iterations, const int & max_on_approach_iterations, const double & max_planning_time, const float & lookup_table_size, const unsigned int & dim_3_size) { _traverse_unknown = allow_unknown; _max_iterations = max_iterations; _max_on_approach_iterations = max_on_approach_iterations; _max_planning_time = max_planning_time; if(!_is_initialized) { NodeT::precomputeDistanceHeuristic(lookup_table_size, _motion_model, dim_3_size, _search_info); } _is_initialized = true; _dim3_size = dim_3_size; _expander = std::make_unique>( _motion_model, _search_info, _traverse_unknown, _dim3_size); } template<> void AStarAlgorithm::initialize( const bool & allow_unknown, int & max_iterations, const int & max_on_approach_iterations, const double & max_planning_time, const float & /*lookup_table_size*/, const unsigned int & dim_3_size) { _traverse_unknown = allow_unknown; _max_iterations = max_iterations; _max_on_approach_iterations = max_on_approach_iterations; _max_planning_time = max_planning_time; if (dim_3_size != 1) { throw std::runtime_error("Node type Node2D cannot be given non-1 dim 3 quantization."); } _dim3_size = dim_3_size; _expander = std::make_unique>( _motion_model, _search_info, _traverse_unknown, _dim3_size); } template void AStarAlgorithm::setCollisionChecker(GridCollisionChecker * collision_checker) { _collision_checker = collision_checker; _costmap = collision_checker->getCostmap(); unsigned int x_size = _costmap->getSizeInCellsX(); unsigned int y_size = _costmap->getSizeInCellsY(); clearGraph(); if (getSizeX() != x_size || getSizeY() != y_size) { _x_size = x_size; _y_size = y_size; NodeT::initMotionModel(_motion_model, _x_size, _y_size, _dim3_size, _search_info); } _expander->setCollisionChecker(collision_checker); } template typename AStarAlgorithm::NodePtr AStarAlgorithm::addToGraph( const unsigned int & index) { auto iter = _graph.find(index); if (iter != _graph.end()) { return &(iter->second); } return &(_graph.emplace(index, NodeT(index)).first->second); } template<> void AStarAlgorithm::setStart( const unsigned int & mx, const unsigned int & my, const unsigned int & dim_3) { if (dim_3 != 0) { throw std::runtime_error("Node type Node2D cannot be given non-zero starting dim 3."); } _start = addToGraph(Node2D::getIndex(mx, my, getSizeX())); } template void AStarAlgorithm::setStart( const unsigned int & mx, const unsigned int & my, const unsigned int & dim_3) { _start = addToGraph(NodeT::getIndex(mx, my, dim_3)); _start->setPose( Coordinates( static_cast(mx), static_cast(my), static_cast(dim_3))); } template<> void AStarAlgorithm::setGoal( const unsigned int & mx, const unsigned int & my, const unsigned int & dim_3) { if (dim_3 != 0) { throw std::runtime_error("Node type Node2D cannot be given non-zero goal dim 3."); } _goal = addToGraph(Node2D::getIndex(mx, my, getSizeX())); _goal_coordinates = Node2D::Coordinates(mx, my); } template void AStarAlgorithm::setGoal( const unsigned int & mx, const unsigned int & my, const unsigned int & dim_3) { _goal = addToGraph(NodeT::getIndex(mx, my, dim_3)); typename NodeT::Coordinates goal_coords( static_cast(mx), static_cast(my), static_cast(dim_3)); if (!_search_info.cache_obstacle_heuristic || goal_coords != _goal_coordinates) { if (!_start) { throw std::runtime_error("Start must be set before goal."); } NodeT::resetObstacleHeuristic(_costmap, _start->pose.x, _start->pose.y, mx, my); } _goal_coordinates = goal_coords; _goal->setPose(_goal_coordinates); } template bool AStarAlgorithm::areInputsValid() { // Check if graph was filled in if (_graph.empty()) { throw std::runtime_error("Failed to compute path, no costmap given."); } // Check if points were filled in if (!_start || !_goal) { throw std::runtime_error("Failed to compute path, no valid start or goal given."); } // Check if ending point is valid if (getToleranceHeuristic() < 0.001 && !_goal->isNodeValid(_traverse_unknown, _collision_checker)) { throw std::runtime_error("Failed to compute path, goal is occupied with no tolerance."); } // Check if starting point is valid if (!_start->isNodeValid(_traverse_unknown, _collision_checker)) { throw std::runtime_error("Starting point in lethal space! Cannot create feasible plan."); } return true; } template bool AStarAlgorithm::createPath( CoordinateVector & path, int & iterations, const float & tolerance) { steady_clock::time_point start_time = steady_clock::now(); _tolerance = tolerance; _best_heuristic_node = {std::numeric_limits::max(), 0}; clearQueue(); if (!areInputsValid()) { return false; } // 0) Add starting point to the open set addNode(0.0, getStart()); getStart()->setAccumulatedCost(0.0); // Optimization: preallocate all variables NodePtr current_node = nullptr; NodePtr neighbor = nullptr; NodePtr expansion_result = nullptr; float g_cost = 0.0; NodeVector neighbors; int approach_iterations = 0; NeighborIterator neighbor_iterator; int analytic_iterations = 0; int closest_distance = std::numeric_limits::max(); // Given an index, return a node ptr reference if its collision-free and valid const unsigned int max_index = getSizeX() * getSizeY() * getSizeDim3(); NodeGetter neighborGetter = [&, this](const unsigned int & index, NodePtr & neighbor_rtn) -> bool { if (index >= max_index) { return false; } neighbor_rtn = addToGraph(index); return true; }; while (iterations < getMaxIterations() && !_queue.empty()) { // Check for planning timeout only on every Nth iteration if (iterations % _timing_interval == 0) { std::chrono::duration planning_duration = std::chrono::duration_cast>(steady_clock::now() - start_time); if (static_cast(planning_duration.count()) >= _max_planning_time) { return false; } } // 1) Pick Nbest from O s.t. min(f(Nbest)), remove from queue current_node = getNextNode(); // We allow for nodes to be queued multiple times in case // shorter paths result in it, but we can visit only once if (current_node->wasVisited()) { continue; } iterations++; // 2) Mark Nbest as visited current_node->visited(); // 2.1) Use an analytic expansion (if available) to generate a path expansion_result = nullptr; expansion_result = _expander->tryAnalyticExpansion( current_node, getGoal(), neighborGetter, analytic_iterations, closest_distance); if (expansion_result != nullptr) { current_node = expansion_result; } // 3) Check if we're at the goal, backtrace if required if (isGoal(current_node)) { return current_node->backtracePath(path); } else if (_best_heuristic_node.first < getToleranceHeuristic()) { // Optimization: Let us find when in tolerance and refine within reason approach_iterations++; if (approach_iterations >= getOnApproachMaxIterations()) { return _graph.at(_best_heuristic_node.second).backtracePath(path); } } // 4) Expand neighbors of Nbest not visited neighbors.clear(); current_node->getNeighbors(neighborGetter, _collision_checker, _traverse_unknown, neighbors); for (neighbor_iterator = neighbors.begin(); neighbor_iterator != neighbors.end(); ++neighbor_iterator) { neighbor = *neighbor_iterator; // 4.1) Compute the cost to go to this node g_cost = current_node->getAccumulatedCost() + current_node->getTraversalCost(neighbor); // 4.2) If this is a lower cost than prior, we set this as the new cost and new approach if (g_cost < neighbor->getAccumulatedCost()) { neighbor->setAccumulatedCost(g_cost); neighbor->parent = current_node; // 4.3) Add to queue with heuristic cost addNode(g_cost + getHeuristicCost(neighbor), neighbor); } } } if (_best_heuristic_node.first < getToleranceHeuristic()) { // If we run out of serach options, return the path that is closest, if within tolerance. return _graph.at(_best_heuristic_node.second).backtracePath(path); } return false; } template bool AStarAlgorithm::isGoal(NodePtr & node) { return node == getGoal(); } template typename AStarAlgorithm::NodePtr & AStarAlgorithm::getStart() { return _start; } template typename AStarAlgorithm::NodePtr & AStarAlgorithm::getGoal() { return _goal; } template typename AStarAlgorithm::NodePtr AStarAlgorithm::getNextNode() { NodeBasic node = _queue.top().second; _queue.pop(); node.processSearchNode(); return node.graph_node_ptr; } template void AStarAlgorithm::addNode(const float & cost, NodePtr & node) { NodeBasic queued_node(node->getIndex()); queued_node.populateSearchNode(node); _queue.emplace(cost, queued_node); } template float AStarAlgorithm::getHeuristicCost(const NodePtr & node) { const Coordinates node_coords = NodeT::getCoords(node->getIndex(), getSizeX(), getSizeDim3()); float heuristic = NodeT::getHeuristicCost( node_coords, _goal_coordinates, _costmap); if (heuristic < _best_heuristic_node.first) { _best_heuristic_node = {heuristic, node->getIndex()}; } return heuristic; } template void AStarAlgorithm::clearQueue() { NodeQueue q; std::swap(_queue, q); } template void AStarAlgorithm::clearGraph() { Graph g; std::swap(_graph, g); _graph.reserve(100000); } template int & AStarAlgorithm::getMaxIterations() { return _max_iterations; } template int & AStarAlgorithm::getOnApproachMaxIterations() { return _max_on_approach_iterations; } template float & AStarAlgorithm::getToleranceHeuristic() { return _tolerance; } template unsigned int & AStarAlgorithm::getSizeX() { return _x_size; } template unsigned int & AStarAlgorithm::getSizeY() { return _y_size; } template unsigned int & AStarAlgorithm::getSizeDim3() { return _dim3_size; } // Instantiate algorithm for the supported template types template class AStarAlgorithm; template class AStarAlgorithm; template class AStarAlgorithm; } // namespace nav2_smac_planner