/*
* 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/Utils.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 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);
Params _params;
};
template
GeneticAlgorithm::GeneticAlgorithm(const Params& params)
: _params {params}
{
}
template
Individual
GeneticAlgorithm::simulate(const std::vector& initialPopulation)
{
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)
{
std::cout << "Processing generation " << currentGeneration << "..." << std::endl;
// parent selection (elitist selection)
scoredPopulation.resize(scoredPopulation.size() / 2);
// breed the remaining individuals
std::vector children;
children.reserve(initialPopulation.size() - scoredPopulation.size());
while (children.size() + scoredPopulation.size() < initialPopulation.size())
{
// Select two random parents
const auto itParent1 {pickRandom(scoredPopulation)};
const auto itParent2 {pickRandom(scoredPopulation)};
if (itParent1 == itParent2)
continue;
ScoredIndividual child {_params.breedFunction(itParent1->individual, itParent2->individual)};
if (getRandom(0, 100) <= _params.mutationProbability * 100)
_params.mutateFunction(child.individual);
children.emplace_back(std::move(child ));
}
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 << "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; });
}