Skip to content

Instantly share code, notes, and snippets.

@zalavariandris
Last active June 26, 2018 14:34
Show Gist options
  • Save zalavariandris/ce547fbe87af91f4cd6641a94a727f32 to your computer and use it in GitHub Desktop.
Save zalavariandris/ce547fbe87af91f4cd6641a94a727f32 to your computer and use it in GitHub Desktop.
A genetic algorithm with c++
#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