Last active
June 26, 2018 14:34
-
-
Save zalavariandris/ce547fbe87af91f4cd6641a94a727f32 to your computer and use it in GitHub Desktop.
A genetic algorithm with c++
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <array> | |
#include <random> | |
#include <limits> | |
#include <iostream> | |
#include <algorithm> | |
namespace GA{ | |
/* | |
* ******************* | |
* Candidate | |
* ******************* | |
*/ | |
template<typename T> | |
bool random_chance(float chance=0.5){ | |
static std::random_device my_random_device; | |
static std::mt19937 random_engine(my_random_device()); // engine | |
static std::uniform_real_distribution<float> dist(0, 1); | |
return dist(random_engine) < chance; | |
} | |
template<class T> | |
T random_value() | |
{ | |
static std::random_device random_device; | |
static std::mt19937 random_engine(random_device()); // engine | |
static std::uniform_int_distribution<T> dist(std::numeric_limits<T>::lowest(), std::numeric_limits<T>::max()); | |
return dist(random_engine); | |
} | |
template<class T, std::size_t SIZE> | |
class Candidate{ | |
public: | |
std::array<T, SIZE> genes; | |
float cost {std::numeric_limits<float>::infinity()}; | |
void randomize() | |
{ | |
std::generate(genes.begin(), genes.end(), [&] () { | |
return random_value<T>(); | |
}); | |
} | |
void mutate(float mutation_rate) | |
{ | |
/*! | |
this is called to mutate a candidate | |
*/ | |
return uniform_mutate(mutation_rate); | |
} | |
template<class TT> | |
static std::array<std::shared_ptr<TT>, 2> crossover(std::shared_ptr<TT> mother, | |
std::shared_ptr<TT> father, | |
float crossover_rate) | |
{ | |
/*! | |
this is called to create children form parents | |
*/ | |
return uniform_crossover(mother, father, crossover_rate); | |
} | |
// Mutation Techniques | |
void tweak_mutate(float mutation_rate) | |
{ | |
for (auto cell_idx=0; cell_idx < genes.size(); cell_idx++) | |
{ | |
if (random_chance<T>(mutation_rate)) | |
{ | |
genes[cell_idx] += random_chance<T>() ? -1 : +1; | |
} | |
} | |
} | |
// Mutation Techniques | |
void uniform_mutate(float mutation_rate) | |
{ | |
auto length = genes.size(); | |
for (auto i=0; i < genes.size(); i++) | |
{ | |
if (random_chance<T>(mutation_rate)) | |
{ | |
genes[i] = random_value<T>(); | |
} | |
} | |
} | |
// Crossover Techniques | |
template<class TT> | |
static std::array<std::shared_ptr<TT>, 2> uniform_crossover(std::shared_ptr<TT> mother, | |
std::shared_ptr<TT> father, | |
float crossover_rate) | |
{ | |
std::array<std::shared_ptr<TT>, 2> children {std::make_shared<TT>(), std::make_shared<TT>()}; | |
if (random_chance<T>(crossover_rate) ) | |
{ | |
for(auto cell=0; cell < mother->genes.size(); cell++) | |
{ | |
if(random_chance<T>()) | |
{ | |
children[0]->genes[cell] = mother->genes[cell]; | |
children[1]->genes[cell] = father->genes[cell]; | |
} | |
else | |
{ | |
children[0]->genes[cell] = father->genes[cell]; | |
children[1]->genes[cell] = mother->genes[cell]; | |
} | |
} | |
} | |
else | |
{ | |
for(auto cell=0; cell < mother->genes.size(); cell++) | |
{ | |
children[0]->genes[cell] = mother->genes[cell]; | |
children[1]->genes[cell] = father->genes[cell]; | |
} | |
} | |
return children; | |
} | |
}; | |
/* | |
* ******************* | |
* Population | |
* ******************* | |
*/ | |
template<class T> | |
class Population | |
{ | |
std::vector<std::shared_ptr<T>> sample(size_t n) | |
{ | |
std::vector<size_t> indices(candidates.size()); | |
std::iota(indices.begin(), indices.end(), 0); | |
std:random_shuffle(indices.begin(), indices.end()); | |
indices.resize(n); | |
std::vector<std::shared_ptr<T>> selection; | |
for(std::size_t ind : indices) | |
selection.push_back(candidates.at(ind)); | |
return selection; | |
} | |
std::array<std::shared_ptr<T>, 2> tournament_selection() | |
{ | |
std::array<std::shared_ptr<T>, 2> selection; | |
for(auto round=0; round<selection.size(); round++) | |
{ | |
std::vector<std::shared_ptr<T>> tournament = sample(3); | |
/* deterministic tournament selection */ | |
std::shared_ptr<T> winner = *std::min_element(tournament.begin(), tournament.end(), [](std::shared_ptr<T> a, std::shared_ptr<T> b){ | |
return a->cost < b->cost; | |
}); | |
selection[round] = winner; | |
} | |
return selection; | |
} | |
public: | |
//FIXME: double buffer? | |
std::vector<std::shared_ptr<T>> candidates; | |
unsigned int generation; | |
explicit Population(std::size_t size): | |
generation{0}, | |
candidates(size) | |
{ | |
std::generate(candidates.begin(), candidates.end(), [](){ | |
return std::make_shared<T>(); | |
}); | |
} | |
void breed(float mutation_rate=0.03, | |
float crossover_rate=0.3) | |
{ | |
// FIXME: Single buffer or double buffer? | |
// single: std::vector<T> offsprings; VS member offspring | |
// double: offsprings.clear(); | |
std::vector<std::shared_ptr<T>> offsprings; | |
offsprings.reserve(candidates.size()); | |
while(offsprings.size() < candidates.size()) | |
{ | |
std::array<std::shared_ptr<T>, 2> parents = tournament_selection(); | |
std::array<std::shared_ptr<T>, 2> children = T::crossover(parents[0], parents[1], crossover_rate); | |
for(std::shared_ptr<T> child : children) | |
{ | |
child->mutate(mutation_rate); | |
} | |
offsprings.insert(offsprings.end(), children.begin(), children.end()); | |
} | |
candidates = offsprings; | |
// candidates.swap(offsprings); | |
generation++; | |
} | |
// Stats | |
std::size_t size() | |
{ | |
return candidates.size(); | |
} | |
std::shared_ptr<T> best() | |
{ | |
return *std::max_element(candidates.begin(), candidates.end(), [](std::shared_ptr<T> a, std::shared_ptr<T> b){ | |
return a->cost < b->cost; | |
}); | |
} | |
float max() const | |
{ | |
auto it = std::max_element(candidates.begin(), candidates.end(), [](std::shared_ptr<T> a, std::shared_ptr<T> b){ | |
return a->cost < b->cost; | |
}); | |
return (*it)->cost; | |
} | |
float min() const | |
{ | |
auto it = std::min_element(candidates.begin(), candidates.end(), [](std::shared_ptr<T> a, std::shared_ptr<T> b){ | |
return a->cost < b->cost; | |
}); | |
return (*it)->cost; | |
} | |
float sum() const | |
{ | |
return std::accumulate(candidates.begin(), candidates.end(),0.0f, [](float lhs, std::shared_ptr<T> b){ | |
return lhs + b->cost; | |
}); | |
} | |
float mean(float sum) const | |
{ | |
return sum / candidates.size(); | |
} | |
float variance(float mean) const | |
{ | |
auto var = 0.0f; | |
for(std::shared_ptr<T> const & candidate : candidates) | |
{ | |
var += (candidate->cost - mean) * (candidate->cost - mean); | |
} | |
var /= candidates.size(); | |
return var; | |
} | |
float sd(float var) const | |
{ | |
return sqrt(var); | |
} | |
}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment