/*
* Copyright (C) 2019 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see .
*/
#include
#include "utils/Random.hpp"
#include "ParallelFor.hpp"
template
class GeneticAlgorithm
{
public:
using Score = float;
using BreedFunction = std::function;
using MutateFunction = std::function;
using ScoreFunction = std::function;
struct Params
{
std::size_t nbWorkers {1};
std::size_t nbGenerations;
float crossoverRatio {0.5};
float mutationProbability {0.05};
BreedFunction breedFunction;
MutateFunction mutateFunction;
ScoreFunction scoreFunction;
};
GeneticAlgorithm(const Params& params);
// Returns the individual that has the maximum score after processing the requested generations
Individual simulate(const std::vector& initialPopulation);
private:
struct ScoredIndividual
{
Individual individual;
std::optional score {};
};
void scoreAndSortPopulation(std::vector& population);
Score getTotalScore(const std::vector& population) const;
typename std::vector::const_iterator pickRandomRouletteWheel(const std::vector& population, Score totalScore);
Params _params;
};
template
GeneticAlgorithm::GeneticAlgorithm(const Params& params)
: _params {params}
{
}
template
Individual
GeneticAlgorithm::simulate(const std::vector& initialPopulation)
{
const std::size_t childrenCountPerGeneration {static_cast(initialPopulation.size() * _params.crossoverRatio)};
if (initialPopulation.size() < 10)
throw std::runtime_error("Initial population must has at least 10 elements");
std::vector scoredPopulation;
scoredPopulation.reserve(initialPopulation.size());
std::transform(std::cbegin(initialPopulation), std::cend(initialPopulation), std::back_inserter(scoredPopulation ),
[](const Individual& individual) { return ScoredIndividual {individual};});
scoreAndSortPopulation(scoredPopulation);
for (std::size_t currentGeneration {}; currentGeneration < _params.nbGenerations; ++currentGeneration)
{
assert(scoredPopulation.size() == initialPopulation.size());
std::cout << "Processing generation " << currentGeneration << "..." << std::endl;
std::cout << "Need to create " << childrenCountPerGeneration << " new children" << std::endl;
// breed
const Score populationTotalScore {getTotalScore(scoredPopulation)};
std::vector children;
children.reserve(childrenCountPerGeneration);
while (children.size() < childrenCountPerGeneration)
{
// Select two random parents using their score as weight
const auto itParent1 {pickRandomRouletteWheel(scoredPopulation, populationTotalScore)};
const auto itParent2 {pickRandomRouletteWheel(scoredPopulation, populationTotalScore)};
if (itParent1 == itParent2)
continue;
ScoredIndividual child {_params.breedFunction(itParent1->individual, itParent2->individual)};
if (Random::getRealRandom(float {}, float {1}) <= _params.mutationProbability)
_params.mutateFunction(child.individual);
children.emplace_back(std::move(child));
}
// Elitist selection
scoredPopulation.resize(initialPopulation.size() - childrenCountPerGeneration);
scoredPopulation.insert(std::end(scoredPopulation), std::make_move_iterator(std::begin(children)), std::make_move_iterator(std::end(children)));
assert(scoredPopulation.size() == initialPopulation.size());
scoreAndSortPopulation(scoredPopulation);
std::cout << "Mean score = " << getTotalScore(scoredPopulation) / scoredPopulation.size() << std::endl;
std::cout << "Current best score = " << *scoredPopulation.front().score << std::endl;
}
std::cout << "Best score = " << *scoredPopulation.front().score << std::endl;
return scoredPopulation.front().individual;
}
template
void
GeneticAlgorithm::scoreAndSortPopulation(std::vector& scoredPopulation)
{
parallel_foreach(_params.nbWorkers, std::begin(scoredPopulation), std::end(scoredPopulation),
[&](ScoredIndividual& scoredIndividual)
{
if (!scoredIndividual.score)
scoredIndividual.score = _params.scoreFunction(scoredIndividual.individual);
});
std::sort(std::begin(scoredPopulation), std::end(scoredPopulation), [](const ScoredIndividual& a, const ScoredIndividual& b) { return a.score > b.score; });
}
template
typename GeneticAlgorithm::Score
GeneticAlgorithm::getTotalScore(const std::vector& scoredPopulation) const
{
return std::accumulate(std::cbegin(scoredPopulation), std::cend(scoredPopulation), Score {}, [](Score score, const ScoredIndividual& individual) { return score + *individual.score; });
}
template
typename std::vector::ScoredIndividual>::const_iterator
GeneticAlgorithm::pickRandomRouletteWheel(const std::vector& population, Score totalScore)
{
const Score randomScore {Random::getRealRandom(Score {}, totalScore)};
Score curScore{};
for (auto itScoredIndividual {std::cbegin(population)}; itScoredIndividual != std::cend(population); ++itScoredIndividual )
{
if (curScore + *itScoredIndividual->score > randomScore)
return itScoredIndividual;
curScore += *itScoredIndividual->score;
}
throw std::runtime_error("bad random or empty population");
}