Program Listing for File ant_colony.cpp¶
↰ Return to documentation for file (src/ant_colony.cpp
)
#include "path_planning/ant_colony.hpp"
#include <algorithm>
#include <chrono>
#include <climits>
#include <cmath>
#include <iostream>
#include <random>
#include <thread>
#include <tuple>
#ifdef CUSTOM_DEBUG_HELPER_FUNCION
void AntColony::PrintAntPath(Ant& ant) const {
for (size_t k = 1; k < ant.path_.size(); k++)
ant.path_[k].pid_ = ant.path_[k - 1].id_;
ant.path_.back().id_ = ant.path_.back().x_ * n_ + ant.path_.back().y_;
auto grid_2 = grid_;
PrintPath(ant.path_, start_, ant.path_.back(), grid_2);
#ifndef RUN_TESTS
std::this_thread::sleep_for(std::chrono::seconds(1));
#endif // RUN_TESTS
}
#endif
void AntColony::RemoveLoop(Ant& ant) {
for (auto it = ant.path_.begin(); it != ant.path_.end(); ++it) {
if (CompareCoordinates(*it, ant.current_node_)) {
ant.steps_ = std::distance(ant.path_.begin(), std::prev(it, 1));
ant.path_.erase(it, ant.path_.end());
break;
}
}
}
void AntColony::SetParams(const int n_ants, const double alpha,
const double beta, const double evap_rate,
const double Q, const int iterations) {
n_ants_ = n_ants;
alpha_ = alpha;
beta_ = beta;
evap_rate_ = evap_rate;
Q_ = Q;
iterations_ = iterations;
}
std::tuple<bool, std::vector<Node>> AntColony::Plan(const Node& start,
const Node& goal) {
grid_ = original_grid_;
start_ = start; // Make sure start has id
goal_ = goal;
motions_ = GetMotion();
ants_ = std::vector<Ant>(n_ants_);
for (int i = 0; i < n_; i++) {
for (int j = 0; j < n_; j++) {
const int cur_id = i * n_ + j;
const Node cur_node = Node(i, j);
for (const auto& motion : motions_) {
const Node c = cur_node + motion;
if (!checkOutsideBoundary(c, n_)) {
pheromone_edges_.insert(
{std::make_pair(cur_id, c.x_ * n_ + c.y_), 1.0});
}
}
}
}
// heuristically set max steps
const int max_steps = n_ * n_ / 2 + n_;
std::random_device device;
std::mt19937 engine(device());
// saves best path of last iteration. Not over all.
std::vector<Node> last_best_path;
Node possible_position;
for (int i = 0; i < iterations_; i++) {
for (int j = 0; j < n_ants_; j++) {
// Can assign a thread to each ant if parallel required
Ant ant(start_, j);
while (!CompareCoordinates(ant.current_node_, goal_) &&
ant.steps_ < max_steps) {
ant.path_.push_back(ant.current_node_);
// Get next position
std::vector<Node> possible_positions;
std::vector<double> possible_probabilities;
double prob_sum = 0;
for (const auto& m : motions_) {
possible_position = ant.current_node_ + m;
possible_position.id_ =
possible_position.x_ * n_ + possible_position.y_;
if (checkOutsideBoundary(possible_position, n_)) {
continue;
}
if (CompareCoordinates(possible_position, ant.previous_node_) ||
grid_[possible_position.x_][possible_position.y_] == 1) {
continue;
}
if (CompareCoordinates(possible_position, goal_)) {
ant.path_.push_back(goal_);
ant.found_goal_ = true;
break;
}
possible_positions.push_back(possible_position);
double new_prob =
std::pow(pheromone_edges_[std::make_pair(possible_position.id_,
ant.current_node_.id_)],
alpha_) *
std::pow(
1.0 /
std::sqrt(std::pow((possible_position.x_ - goal_.x_), 2) +
std::pow((possible_position.y_ - goal_.y_), 2)),
beta_);
possible_probabilities.push_back(new_prob);
prob_sum += new_prob;
}
if (prob_sum == 0 || ant.found_goal_) {
// Ant in a cul-de-sac or
// no next node (ie start surrounded by obstacles)
break;
}
std::for_each(possible_probabilities.begin(),
possible_probabilities.end(),
[&](double& p) { p /= prob_sum; });
std::discrete_distribution<> dist(possible_probabilities.begin(),
possible_probabilities.end());
ant.previous_node_ = ant.current_node_;
ant.current_node_ = possible_positions[dist(engine)];
// Removing any loops if reached previously reached point
// TODO(vss): add check to count number of loops removed and stop if
// going into inf. Should be only 1? use hash of visited?
RemoveLoop(ant);
ant.steps_++;
}
ants_[j] = ant;
}
// Pheromone deterioration
for (auto& pheromone_edge : pheromone_edges_) {
pheromone_edge.second *= (1 - evap_rate_);
}
int bpl = std::numeric_limits<int>::max();
std::vector<Node> bp;
// Pheromone update based on successful ants
for (const Ant& ant : ants_) {
if (ant.found_goal_) {
// Use iff goal reached
if (static_cast<int>(ant.path_.size()) < bpl) {
// Save best path yet in this iteration
bpl = ant.path_.size();
bp = ant.path_;
}
// c = cost / reward. Reward here, increased pheromone
double c = Q_ / static_cast<double>(ant.path_.size() - 1);
// start_.PrintStatus();
// goal_.PrintStatus();
for (size_t i_ant = 1; i_ant < ant.path_.size(); i_ant++) {
// Assuming ant can tell which way the food was based on
// how the phermones detereorate. Good for path planning as
// prevents moving in the opposite direction to path and
// improves convergence
// std::cout << ant.path_[i_ant].id_ << ' ' << ant.path_[i_ant -
// 1].id_ << '\n';
auto it = pheromone_edges_.find(
std::make_pair(ant.path_[i_ant].id_, ant.path_[i_ant - 1].id_));
it->second += c;
}
}
}
if (i + 1 == iterations_) {
last_best_path = bp;
}
} // for every iteration loop ends here
if (last_best_path.empty()) {
return {false, {}};
}
for (size_t i = 1; i < last_best_path.size(); i++) {
last_best_path[i].pid_ = last_best_path[i - 1].id_;
}
for (const auto& node : last_best_path) {
node.PrintStatus();
}
return {true, last_best_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);
// Normally as beta increases the solution becomes greedier. However, as the
// heuristic is < 1 here, reducing beta increases the value placed on the
// heuristic
AntColony new_ant_colony(grid);
// new_ant_colony.SetParams(10, 1, 0.2, 0.5, 10, 50);
const auto [found_path, path_vector] = new_ant_colony.Plan(start, goal);
PrintPath(path_vector, start, goal, grid);
return 0;
}
#endif // BUILD_INDIVIDUAL