Program Listing for File genetic_algorithm.cpp¶
↰ Return to documentation for file (src/genetic_algorithm.cpp
)
#include "path_planning/genetic_algorithm.hpp"
#include <algorithm>
#include <iomanip> // TODO(vss): replace setw
#include <iostream>
#include <limits>
#include <random>
constexpr int random_range_max = 100;
constexpr int inverted_probabilty_mutate = 10;
void GeneticAlgorithm::SetParams(const int generations, const int popsize,
const double c, const bool shorten_chromosome,
const int path_length) {
generations_ = generations;
popsize_ = popsize;
c_ = c;
shorten_chromosome_ = shorten_chromosome;
path_length_ = path_length;
}
std::tuple<bool, std::vector<Node>> GeneticAlgorithm::Plan(const Node &start,
const Node &goal) {
int generation = 0;
start_ = start;
goal_ = goal;
grid_ = original_grid_;
motions_ = GetMotion();
// Create initial chromosome
std::vector<Node> initial_path = GenerateSimplePath();
paths_.push_back(initial_path);
// Check first path to goal
if (CheckPath(initial_path)) {
truepaths_.push_back(initial_path);
}
// Allow while loop to continue beyond path found to find optimum path
while (generation <= generations_) {
size_t paths_size = paths_.size();
std::vector<int> f_vals(paths_size);
std::transform(
paths_.begin(), paths_.end(), f_vals.begin(),
[&](const std::vector<Node> &path) { return CalculateFitness(path); });
f_val = *std::min_element(f_vals.begin(), f_vals.end());
std::vector<std::vector<Node>> new_paths_;
for (int i = 0; i < paths_.size(); ++i) {
// c provides a margin of error from best path
if (f_vals[i] <= f_val * c_) {
if (shorten_chromosome_) {
const int len = std::min(paths_[i].size(), path_length_);
new_paths_.emplace_back(std::vector<Node>(
paths_[i].begin(), std::next(paths_[i].begin(), len)));
} else {
new_paths_.push_back(paths_[i]);
}
}
}
if (!new_paths_.empty()) {
std::swap(paths_, new_paths_);
}
if (paths_.size() < popsize_) {
while (paths_.size() < popsize_) {
if (rand() % inverted_probabilty_mutate == 0) {
paths_.emplace_back(Mutate());
} else {
paths_.emplace_back(Crossover());
}
}
} else {
paths_[rand() % paths_.size()] = Mutate();
}
// TODO(vss): Consider dynamically modifying path length to move towards
// optimality
for (const auto &path_seen : paths_) {
int tmp_length = std::numeric_limits<int>::max();
if (CheckPath(path_seen)) {
found_ = true;
if (truepaths_.size() > (n_ * n_)) {
truepaths_[rand() % (n_ * n_)] = path_seen;
} else {
truepaths_.push_back(path_seen);
}
if (shorten_chromosome_) {
auto tmp = start_;
if (path_seen.size() < tmp_length) {
tmp_length = path_seen.size();
}
if (CompareCoordinates(tmp, goal_)) {
tmp_length = 0;
}
for (size_t i = 0; i < path_seen.size(); i++) {
tmp = tmp + path_seen[i];
if (CompareCoordinates(tmp, goal_)) {
tmp_length = i;
break;
}
}
path_length_ = tmp_length;
}
}
// PrintChromosome(path_seen);
}
generation++;
}
// std::cout << "True paths: " << truepaths_.size() << '\n';
// if(!truepaths_.empty())std::cout << "True paths: " << '\n';
// for(int i=0;i<truepaths_.size();i++) PrintPathOfChromosome(truepaths_[i]);
const auto node_path = ReturnLastPath();
return {!node_path.empty(), node_path};
}
std::vector<Node> GeneticAlgorithm::ReturnLastPath() const {
if (truepaths_.empty()) {
return {};
}
std::vector<int> f_vals(truepaths_.size());
std::transform(truepaths_.begin(), truepaths_.end(), f_vals.begin(),
f_vals.begin(),
[&](const std::vector<Node> &path, const int /* f_vals_v */) {
return CalculateFitness(path);
});
const int best_path_index = std::distance(
f_vals.begin(), std::min_element(f_vals.begin(), f_vals.end()));
std::vector<Node> node_path;
node_path.push_back(start_);
node_path.back().pid_ = node_path.back().id_;
Node current = start_;
for (const auto &motion : truepaths_[best_path_index]) {
current = current + motion;
current.pid_ = node_path.back().id_;
current.id_ = n_ * current.x_ + current.y_;
node_path.push_back(current);
if (CompareCoordinates(current, goal_)) {
break;
}
}
return node_path;
}
#ifdef CUSTOM_DEBUG_HELPER_FUNCION
void GeneticAlgorithm::CheckIfNodesInPathAreAcceptable(
const std::vector<Node> &path) const {
for (const auto &motion : path) {
bool found = false;
for (const auto &m : motions_) {
if (CompareCoordinates(m, motion)) {
found = true;
break;
}
}
if (!found) {
motion.PrintStatus();
exit(0);
}
}
}
void GeneticAlgorithm::PrintChromosome(const std::vector<Node> &path) const {
std::cout << "Chromosome: ";
for (const auto &v : path) {
for (size_t i = 0; i < motions_.size(); i++)
if (CompareCoordinates(v, motions_[i])) {
std::cout << i << " ";
}
}
std::cout << "Fitness value: " << CalculateFitness(path) << '\n';
}
void GeneticAlgorithm::PrintPathOfChromosome(
const std::vector<Node> &path) const {
std::cout << "Path: ";
Node tmp = start_;
std::cout << "(" << tmp.x_ << "," << tmp.y_ << ")" << std::setw(3);
for (auto v : path) {
tmp = tmp + v;
std::cout << "(" << tmp.x_ << "," << tmp.y_ << ")" << std::setw(3);
}
std::cout << '\n';
}
#endif // CUSTOM_DEBUG_HELPER_FUNCION
std::vector<Node> GeneticAlgorithm::GenerateSimplePath() const {
int d_x = goal_.x_ - start_.x_;
int d_y = goal_.y_ - start_.y_;
Node dx(d_x >= 0 ? 1 : -1, 0);
Node dy(0, d_y >= 0 ? 1 : -1);
std::vector<Node> path(path_length_);
auto it = path.begin();
std::fill_n(it, std::abs(d_x), dx);
std::advance(it, std::abs(d_x));
std::fill_n(it, std::abs(d_y), dy);
std::advance(it, std::abs(d_y));
std::generate_n(it, std::distance(it, path.end()),
[&]() { return motions_[rand() % 4]; });
return path;
}
int GeneticAlgorithm::CalculateFitness(const std::vector<Node> &path) const {
int cost = 0;
Node i = start_;
for (const auto &p : path) {
i = i + p;
if (i.x_ < 0 || i.x_ >= n_ || i.y_ < 0 || i.y_ >= n_) {
return std::numeric_limits<int>::max();
}
if (CompareCoordinates(i, goal_)) {
break;
}
if (grid_[i.x_][i.y_] == 1) {
cost += n_ * abs(goal_.x_ - i.x_) + n_ * abs(goal_.y_ - i.y_);
} else {
// Can add a scaling factor here
cost += abs(goal_.x_ - i.x_) + abs(goal_.y_ - i.y_);
}
}
return cost;
}
std::vector<Node> GeneticAlgorithm::GenerateRandomPath() const {
std::random_device rd;
std::mt19937 eng(rd());
std::vector<Node> path(path_length_);
if (path_length_ > 0) {
std::uniform_int_distribution<int> distr(
0, motions_.size() - 1); // define the range
std::generate_n(path.begin(), path_length_,
[&]() { return motions_[distr(eng)]; });
}
return path;
}
std::vector<Node> GeneticAlgorithm::Mutate() const {
std::random_device rd;
std::mt19937 eng(rd());
std::vector<Node> path;
if (truepaths_.size() > 3 && rand() % 4 == 0) {
path = truepaths_[rand() % truepaths_.size()];
} else {
path = GenerateRandomPath();
}
if (path.empty()) {
return path;
}
int index_1 = static_cast<int>(rand() % path.size());
int index_2 = static_cast<int>(rand() % path.size());
if (index_2 < index_1) {
std::swap(index_1, index_2);
}
std::shuffle(path.begin() + index_1, path.begin() + index_2, eng);
return path;
}
std::vector<Node> GeneticAlgorithm::Crossover() const {
int p1 = static_cast<int>(rand() % paths_.size());
int p2 = static_cast<int>(rand() % paths_.size());
const size_t len =
std::max(std::max(paths_[p1].size(), paths_[p2].size()), path_length_);
std::vector<Node> child;
child.reserve(len);
Node current = start_;
int index = 0;
int attempt_count = 0;
while (index < len) {
int random_int = rand() % random_range_max;
Node motion;
if (random_int < random_range_max / 4 && paths_[p1].size() > index) {
motion = paths_[p1][index];
} else if (random_int < random_range_max / 2 && paths_[p2].size() > index) {
motion = paths_[p2][index];
} else {
motion = motions_[rand() % motions_.size()];
}
// Prevents the new chromosome from going beyond the grid
// Added as a very large percentage (majority) of these are out of bounds,
// and a waste of resources Chromosomes that contain paths travelling over
// obstacles are still allowed after a point
if (auto new_node = current + motion; !checkOutsideBoundary(new_node, n_)) {
if (grid_[new_node.x_][new_node.y_] != 1 || attempt_count > n_ * n_) {
current = new_node;
child.push_back(motion);
++index;
}
}
++attempt_count;
}
return child;
}
// NOTE: Consider storing the point where an obstacle is encountereed and forcig
// that gene/motion to randomly mutate for a quicker convergence to a solution
// while maintaining randomness
bool GeneticAlgorithm::CheckPath(const std::vector<Node> &path) const {
Node current = start_;
if (CompareCoordinates(current, goal_)) {
return true;
}
for (const auto &node : path) {
current = current + node;
if (CompareCoordinates(current, goal_)) {
return true;
}
if (checkOutsideBoundary(current, n_)) {
return false;
}
if (grid_[current.x_][current.y_] == 1) {
return false;
}
}
return CompareCoordinates(current, goal_);
}
#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);
constexpr int generations = 10000;
constexpr int popsize = 30;
constexpr double c = 1.05;
constexpr bool shorten_chromosome = true;
constexpr int path_length_x_factor = 4;
GeneticAlgorithm new_genetic_algorithm(grid);
new_genetic_algorithm.SetParams(
generations, popsize, c, shorten_chromosome,
static_cast<int>(path_length_x_factor * start.h_cost_));
const auto [path_found, path_vector] =
new_genetic_algorithm.Plan(start, goal);
PrintPathInOrder(path_vector, start, goal, grid);
}
#endif // BUILD_INDIVIDUAL