Program Listing for File lpa_star.cpp

Return to documentation for file (src/lpa_star.cpp)

#include "path_planning/lpa_star.hpp"

#include <algorithm>
#include <chrono>
#include <cmath>
#include <thread>
#include <tuple>

#ifdef BUILD_INDIVIDUAL
#include <random>
#endif  // BUILD_INDIVIDUAL

constexpr int pause_time = 500;  // milliseconds

void LPAStar::SetDynamicObstacles(
    const bool create_random_obstacles,
    const std::unordered_map<int, std::vector<Node>>&
        time_discovered_obstacles) {
  create_random_obstacles_ = create_random_obstacles;
  time_discovered_obstacles_ = time_discovered_obstacles;
}

bool LPAStar::IsObstacle(const Node& n) const { return grid_[n.x_][n.y_] == 1; }

double LPAStar::H(const Node& n1, const Node& n2) {
  return std::sqrt(std::pow(n1.x_ - n2.x_, 2) + std::pow(n1.y_ - n2.y_, 2));
}

std::vector<Node> LPAStar::GetNeighbours(const Node& u) const {
  std::vector<Node> neighbours;
  for (const auto& m : motions_) {
    if (const auto neighbour = u + m;
        !checkOutsideBoundary(neighbour, grid_.size())) {
      neighbours.push_back(neighbour);
    }
  }
  return neighbours;
}

std::vector<Node> LPAStar::GetPred(const Node& u) const {
  return GetNeighbours(u);
}

std::vector<Node> LPAStar::GetSucc(const Node& u) const {
  return GetNeighbours(u);
}

double LPAStar::C(const Node& s1, const Node& s2) const {
  if (IsObstacle(s1) || IsObstacle(s2)) {
    return std::numeric_limits<double>::max();
  }
  const Node delta{s2.x_ - s1.x_, s2.y_ - s1.y_};
  return std::find_if(std::begin(motions_), std::end(motions_),
                      [&delta](const Node& motion) {
                        return CompareCoordinates(motion, delta);
                      })
      ->cost_;
}

Key LPAStar::CalculateKey(const Node& s) const {
  return Key{std::min(g_[s.x_][s.y_], rhs_[s.x_][s.y_]) + H(s, goal_),
             std::min(g_[s.x_][s.y_], rhs_[s.x_][s.y_])};
}

std::vector<std::vector<double>> LPAStar::CreateGrid() {
  return std::vector<std::vector<double>>(
      n_, std::vector<double>(n_, std::numeric_limits<double>::max()));
}

void LPAStar::Initialize() {
  motions_ = GetMotion();
  time_step_ = 0;
  U_.clear();
  rhs_ = CreateGrid();
  g_ = CreateGrid();
  rhs_[start_.x_][start_.y_] = 0;
  U_.insert(NodeKeyPair{start_, CalculateKey(start_)});
}

void LPAStar::UpdateVertex(const Node& u) {
  if (grid_[u.x_][u.y_] == 0) {
    grid_[u.x_][u.y_] = 2;
  }
  if (!CompareCoordinates(u, start_)) {
    rhs_[u.x_][u.y_] = std::numeric_limits<double>::max();
    const auto predecessors = GetPred(u);
    for (const auto& sprime : predecessors) {
      rhs_[u.x_][u.y_] =
          std::min(rhs_[u.x_][u.y_], g_[sprime.x_][sprime.y_] + C(sprime, u));
    }
  }
  if (U_.isElementInStruct({u, {}})) {
    U_.remove(NodeKeyPair{u, Key()});
  }
  if (rhs_[u.x_][u.y_] != g_[u.x_][u.y_]) {
    U_.insert(NodeKeyPair{u, CalculateKey(u)});
  }
}

void LPAStar::ComputeShortestPath() {
  while ((!U_.empty() && U_.top().key < CalculateKey(goal_)) ||
         (rhs_[goal_.x_][goal_.y_] != g_[goal_.x_][goal_.y_])) {
    const Node u = U_.top().node;
    U_.pop();
    if (g_[u.x_][u.y_] > rhs_[u.x_][u.y_]) {
      g_[u.x_][u.y_] = rhs_[u.x_][u.y_];
      for (const auto& s : GetSucc(u)) {
        UpdateVertex(s);
      }
    } else {
      g_[u.x_][u.y_] = std::numeric_limits<double>::max();
      for (const auto& s : GetSucc(u)) {
        UpdateVertex(s);
      }
      UpdateVertex(u);
    }
  }
}

std::vector<Node> LPAStar::DetectChanges() {
  std::vector<Node> obstacles;
  if (time_discovered_obstacles_.find(time_step_) !=
      time_discovered_obstacles_.end()) {
    const auto discovered_obstacles_at_time =
        time_discovered_obstacles_[time_step_];
    for (const auto& discovered_obstacle_at_time :
         discovered_obstacles_at_time) {
      if (!((start_.x_ == discovered_obstacle_at_time.x_ &&
             start_.y_ == discovered_obstacle_at_time.y_) ||
            (goal_.x_ == discovered_obstacle_at_time.x_ &&
             goal_.y_ == discovered_obstacle_at_time.y_))) {
        grid_[discovered_obstacle_at_time.x_][discovered_obstacle_at_time.y_] =
            1;
        obstacles.push_back(discovered_obstacle_at_time);
      }
    }
  }
  if (create_random_obstacles_ && rand() > 1.0 / static_cast<double>(n_)) {
    const int x = rand() % n_;
    const int y = rand() % n_;
    if (!((start_.x_ == x && start_.y_ == y) ||
          (goal_.x_ == x && goal_.y_ == y))) {
      grid_[x][y] = 1;
      obstacles.emplace_back(Node(x, y));
    }
  }
  return obstacles;
}

void LPAStar::ClearPathDisplay(const std::vector<Node>& path) {
  for (const auto& node : path) {
    if (grid_[node.x_][node.y_] ==
        3) {  // Don't update if it's a discovered obstacle
      grid_[node.x_][node.y_] = 2;  // It's been explored, but no longer a path
    }
  }
  grid_[start_.x_][start_.y_] = 3;
}

void LPAStar::UpdatePathDisplay(const std::vector<Node>& path) {
  for (const auto& node : path) {
    grid_[node.x_][node.y_] = 3;
  }
}

std::vector<Node> LPAStar::GetNewPath() {
  auto current = goal_;
  std::vector<Node> path;
  path.push_back(current);
  while (!CompareCoordinates(current, start_)) {
    auto min_cost = std::numeric_limits<double>::max();
    Node min_node;
    for (const auto& motion : motions_) {
      auto new_node = current + motion;
      if (!checkOutsideBoundary(new_node, n_) &&
          g_[new_node.x_][new_node.y_] < min_cost) {
        min_cost = g_[new_node.x_][new_node.y_];
        min_node = new_node;
      }
    }
    min_node.id_ = min_node.x_ * n_ + min_node.y_;
    path.back().pid_ = min_node.id_;
    current = min_node;
    path.push_back(current);
  }

  const auto path_cost = path.back().cost_;
  for (auto& node : path) {
    node.cost_ = path_cost - node.cost_;
  }
  path.back().pid_ = path.back().id_;
  return path;
}

std::tuple<bool, std::vector<Node>> LPAStar::Plan(const Node& start,
                                                  const Node& goal) {
  grid_ = original_grid_;
  start_ = start;
  goal_ = goal;
  std::vector<Node> path;
  path.push_back(start_);
  grid_[start_.x_][start_.y_] = 3;
  PrintGrid(grid_);
  Initialize();
  while (time_step_ < max_time_step_) {
    ComputeShortestPath();
    if (g_[goal_.x_][goal_.y_] == std::numeric_limits<double>::max()) {
      ClearPathDisplay(path);
      PrintGrid(grid_);
      std::cout << "No path exists" << '\n';
      return {false, {}};
    }
    ClearPathDisplay(path);
    path = GetNewPath();
    UpdatePathDisplay(path);
    PrintGrid(grid_);
    time_step_++;

#ifndef RUN_TESTS
    std::this_thread::sleep_for(std::chrono::milliseconds(pause_time));
#endif  // RUN_TESTS

    if (const auto changed_nodes = DetectChanges(); !changed_nodes.empty()) {
      for (const auto node : changed_nodes) {
        UpdateVertex(node);
      }
    }
  }
  for (const auto& node : path) {
    node.PrintStatus();
  }
  PrintGrid(grid_);
  return {true, path};
}

#ifdef BUILD_INDIVIDUAL

int main() {
  constexpr int n = 11;
  std::vector<std::vector<int>> grid(n, std::vector<int>(n, 0));
  MakeGrid(grid);

  std::random_device rd;   // obtain a random number from hardware
  std::mt19937 eng(rd());  // seed the generator
  std::uniform_int_distribution<int> distr(0, n - 1);  // define the range

  Node start(distr(eng), distr(eng), 0, 0, 0, 0);
  Node goal(distr(eng), distr(eng), 0, 0, 0, 0);

  start.id_ = start.x_ * n + start.y_;
  start.pid_ = start.x_ * n + start.y_;
  goal.id_ = goal.x_ * n + goal.y_;
  start.h_cost_ = abs(start.x_ - goal.x_) + abs(start.y_ - goal.y_);

  // Make sure start and goal are not obstacles and their ids are correctly
  // assigned.
  grid[start.x_][start.y_] = 0;
  grid[goal.x_][goal.y_] = 0;

  start.PrintStatus();
  goal.PrintStatus();

  PrintGrid(grid);

  const bool create_random_obstacles = false;
  const std::unordered_map<int, std::vector<Node>> time_discovered_obstacles{
      {1, {Node(1, 1)}},
      {2, {Node(2, 2)}},
      {3, {Node(5, 5)}},
      {4,
       {Node(6, 6), Node(7, 7), Node(8, 8), Node(9, 9), Node(10, 10),
        Node(7, 6)}}};

  LPAStar lpa_star(grid);
  lpa_star.SetDynamicObstacles(create_random_obstacles,
                               time_discovered_obstacles);
  const auto [found_path, path_vector] = lpa_star.Plan(start, goal);
  PrintPath(path_vector, start, goal, grid);
  return 0;
}
#endif  // BUILD_INDIVIDUAL