mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
[yul-phaser] GeneticAlgorithms: Add ClassicGeneticAlgorithm
This commit is contained in:
parent
879f6e17e9
commit
f6783c60b2
@ -31,6 +31,7 @@
|
|||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace boost::unit_test::framework;
|
using namespace boost::unit_test::framework;
|
||||||
using namespace boost::test_tools;
|
using namespace boost::test_tools;
|
||||||
|
using namespace solidity::util;
|
||||||
|
|
||||||
namespace solidity::phaser::test
|
namespace solidity::phaser::test
|
||||||
{
|
{
|
||||||
@ -41,6 +42,18 @@ protected:
|
|||||||
shared_ptr<FitnessMetric> m_fitnessMetric = make_shared<ChromosomeLengthMetric>();
|
shared_ptr<FitnessMetric> m_fitnessMetric = make_shared<ChromosomeLengthMetric>();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ClassicGeneticAlgorithmFixture: public GeneticAlgorithmFixture
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
ClassicGeneticAlgorithm::Options m_options = {
|
||||||
|
/* elitePoolSize = */ 0.0,
|
||||||
|
/* crossoverChance = */ 0.0,
|
||||||
|
/* mutationChance = */ 0.0,
|
||||||
|
/* deletionChance = */ 0.0,
|
||||||
|
/* additionChance = */ 0.0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Phaser)
|
BOOST_AUTO_TEST_SUITE(Phaser)
|
||||||
BOOST_AUTO_TEST_SUITE(GeneticAlgorithmsTest)
|
BOOST_AUTO_TEST_SUITE(GeneticAlgorithmsTest)
|
||||||
BOOST_AUTO_TEST_SUITE(RandomAlgorithmTest)
|
BOOST_AUTO_TEST_SUITE(RandomAlgorithmTest)
|
||||||
@ -186,6 +199,197 @@ BOOST_FIXTURE_TEST_CASE(runNextRound_should_generate_individuals_in_the_crossove
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
BOOST_AUTO_TEST_SUITE(ClassicGeneticAlgorithmTest)
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(runNextRound_should_select_individuals_with_probability_proportional_to_fitness, ClassicGeneticAlgorithmFixture)
|
||||||
|
{
|
||||||
|
constexpr double relativeTolerance = 0.1;
|
||||||
|
constexpr size_t populationSize = 1000;
|
||||||
|
assert(populationSize % 4 == 0 && "Choose a number divisible by 4 for this test");
|
||||||
|
|
||||||
|
auto population =
|
||||||
|
Population::makeRandom(m_fitnessMetric, populationSize / 4, 0, 0) +
|
||||||
|
Population::makeRandom(m_fitnessMetric, populationSize / 4, 1, 1) +
|
||||||
|
Population::makeRandom(m_fitnessMetric, populationSize / 4, 2, 2) +
|
||||||
|
Population::makeRandom(m_fitnessMetric, populationSize / 4, 3, 3);
|
||||||
|
|
||||||
|
map<size_t, double> expectedProbabilities = {
|
||||||
|
{0, 4.0 / (4 + 3 + 2 + 1)},
|
||||||
|
{1, 3.0 / (4 + 3 + 2 + 1)},
|
||||||
|
{2, 2.0 / (4 + 3 + 2 + 1)},
|
||||||
|
{3, 1.0 / (4 + 3 + 2 + 1)},
|
||||||
|
};
|
||||||
|
double const expectedValue = (
|
||||||
|
0.0 * expectedProbabilities[0] +
|
||||||
|
1.0 * expectedProbabilities[1] +
|
||||||
|
2.0 * expectedProbabilities[2] +
|
||||||
|
3.0 * expectedProbabilities[3]
|
||||||
|
);
|
||||||
|
double const variance = (
|
||||||
|
(0.0 - expectedValue) * (0.0 - expectedValue) * expectedProbabilities[0] +
|
||||||
|
(1.0 - expectedValue) * (1.0 - expectedValue) * expectedProbabilities[1] +
|
||||||
|
(2.0 - expectedValue) * (2.0 - expectedValue) * expectedProbabilities[2] +
|
||||||
|
(3.0 - expectedValue) * (3.0 - expectedValue) * expectedProbabilities[3]
|
||||||
|
);
|
||||||
|
|
||||||
|
ClassicGeneticAlgorithm algorithm(m_options);
|
||||||
|
Population newPopulation = algorithm.runNextRound(population);
|
||||||
|
|
||||||
|
BOOST_TEST(newPopulation.individuals().size() == population.individuals().size());
|
||||||
|
|
||||||
|
vector<size_t> newFitness = chromosomeLengths(newPopulation);
|
||||||
|
BOOST_TEST(abs(mean(newFitness) - expectedValue) < expectedValue * relativeTolerance);
|
||||||
|
BOOST_TEST(abs(meanSquaredError(newFitness, expectedValue) - variance) < variance * relativeTolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(runNextRound_should_select_only_individuals_existing_in_the_original_population, ClassicGeneticAlgorithmFixture)
|
||||||
|
{
|
||||||
|
constexpr size_t populationSize = 1000;
|
||||||
|
auto population = Population::makeRandom(m_fitnessMetric, populationSize, 1, 10);
|
||||||
|
|
||||||
|
set<string> originalSteps;
|
||||||
|
for (auto const& individual: population.individuals())
|
||||||
|
originalSteps.insert(toString(individual.chromosome));
|
||||||
|
|
||||||
|
ClassicGeneticAlgorithm algorithm(m_options);
|
||||||
|
Population newPopulation = algorithm.runNextRound(population);
|
||||||
|
|
||||||
|
for (auto const& individual: newPopulation.individuals())
|
||||||
|
BOOST_TEST(originalSteps.count(toString(individual.chromosome)) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_crossover, ClassicGeneticAlgorithmFixture)
|
||||||
|
{
|
||||||
|
auto population = Population(m_fitnessMetric, {
|
||||||
|
Chromosome("aa"), Chromosome("aa"), Chromosome("aa"),
|
||||||
|
Chromosome("ff"), Chromosome("ff"), Chromosome("ff"),
|
||||||
|
Chromosome("gg"), Chromosome("gg"), Chromosome("gg"),
|
||||||
|
});
|
||||||
|
|
||||||
|
set<string> originalSteps{"aa", "ff", "gg"};
|
||||||
|
set<string> crossedSteps{"af", "fa", "fg", "gf", "ga", "ag"};
|
||||||
|
|
||||||
|
m_options.crossoverChance = 0.8;
|
||||||
|
ClassicGeneticAlgorithm algorithm(m_options);
|
||||||
|
|
||||||
|
SimulationRNG::reset(1);
|
||||||
|
Population newPopulation = algorithm.runNextRound(population);
|
||||||
|
|
||||||
|
size_t totalCrossed = 0;
|
||||||
|
size_t totalUnchanged = 0;
|
||||||
|
for (auto const& individual: newPopulation.individuals())
|
||||||
|
{
|
||||||
|
totalCrossed += crossedSteps.count(toString(individual.chromosome));
|
||||||
|
totalUnchanged += originalSteps.count(toString(individual.chromosome));
|
||||||
|
}
|
||||||
|
BOOST_TEST(totalCrossed + totalUnchanged == newPopulation.individuals().size());
|
||||||
|
BOOST_TEST(totalCrossed >= 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_mutation, ClassicGeneticAlgorithmFixture)
|
||||||
|
{
|
||||||
|
m_options.mutationChance = 0.6;
|
||||||
|
ClassicGeneticAlgorithm algorithm(m_options);
|
||||||
|
|
||||||
|
constexpr size_t populationSize = 1000;
|
||||||
|
constexpr double relativeTolerance = 0.05;
|
||||||
|
double const expectedValue = m_options.mutationChance;
|
||||||
|
double const variance = m_options.mutationChance * (1 - m_options.mutationChance);
|
||||||
|
|
||||||
|
Chromosome chromosome("aaaaaaaaaa");
|
||||||
|
vector<Chromosome> chromosomes(populationSize, chromosome);
|
||||||
|
Population population(m_fitnessMetric, chromosomes);
|
||||||
|
|
||||||
|
SimulationRNG::reset(1);
|
||||||
|
Population newPopulation = algorithm.runNextRound(population);
|
||||||
|
|
||||||
|
vector<size_t> bernoulliTrials;
|
||||||
|
for (auto const& individual: newPopulation.individuals())
|
||||||
|
{
|
||||||
|
string steps = toString(individual.chromosome);
|
||||||
|
for (char step: steps)
|
||||||
|
bernoulliTrials.push_back(static_cast<size_t>(step != 'a'));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_TEST(abs(mean(bernoulliTrials) - expectedValue) < expectedValue * relativeTolerance);
|
||||||
|
BOOST_TEST(abs(meanSquaredError(bernoulliTrials, expectedValue) - variance) < variance * relativeTolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_deletion, ClassicGeneticAlgorithmFixture)
|
||||||
|
{
|
||||||
|
m_options.deletionChance = 0.6;
|
||||||
|
ClassicGeneticAlgorithm algorithm(m_options);
|
||||||
|
|
||||||
|
constexpr size_t populationSize = 1000;
|
||||||
|
constexpr double relativeTolerance = 0.05;
|
||||||
|
double const expectedValue = m_options.deletionChance;
|
||||||
|
double const variance = m_options.deletionChance * (1 - m_options.deletionChance);
|
||||||
|
|
||||||
|
Chromosome chromosome("aaaaaaaaaa");
|
||||||
|
vector<Chromosome> chromosomes(populationSize, chromosome);
|
||||||
|
Population population(m_fitnessMetric, chromosomes);
|
||||||
|
|
||||||
|
SimulationRNG::reset(1);
|
||||||
|
Population newPopulation = algorithm.runNextRound(population);
|
||||||
|
|
||||||
|
vector<size_t> bernoulliTrials;
|
||||||
|
for (auto const& individual: newPopulation.individuals())
|
||||||
|
{
|
||||||
|
string steps = toString(individual.chromosome);
|
||||||
|
for (size_t i = 0; i < chromosome.length(); ++i)
|
||||||
|
bernoulliTrials.push_back(static_cast<size_t>(i >= steps.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_TEST(abs(mean(bernoulliTrials) - expectedValue) < expectedValue * relativeTolerance);
|
||||||
|
BOOST_TEST(abs(meanSquaredError(bernoulliTrials, expectedValue) - variance) < variance * relativeTolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_addition, ClassicGeneticAlgorithmFixture)
|
||||||
|
{
|
||||||
|
m_options.additionChance = 0.6;
|
||||||
|
ClassicGeneticAlgorithm algorithm(m_options);
|
||||||
|
|
||||||
|
constexpr size_t populationSize = 1000;
|
||||||
|
constexpr double relativeTolerance = 0.05;
|
||||||
|
double const expectedValue = m_options.additionChance;
|
||||||
|
double const variance = m_options.additionChance * (1 - m_options.additionChance);
|
||||||
|
|
||||||
|
Chromosome chromosome("aaaaaaaaaa");
|
||||||
|
vector<Chromosome> chromosomes(populationSize, chromosome);
|
||||||
|
Population population(m_fitnessMetric, chromosomes);
|
||||||
|
|
||||||
|
SimulationRNG::reset(1);
|
||||||
|
Population newPopulation = algorithm.runNextRound(population);
|
||||||
|
|
||||||
|
vector<size_t> bernoulliTrials;
|
||||||
|
for (auto const& individual: newPopulation.individuals())
|
||||||
|
{
|
||||||
|
string steps = toString(individual.chromosome);
|
||||||
|
for (size_t i = 0; i < chromosome.length() + 1; ++i)
|
||||||
|
{
|
||||||
|
BOOST_REQUIRE(chromosome.length() <= steps.size() && steps.size() <= 2 * chromosome.length() + 1);
|
||||||
|
bernoulliTrials.push_back(static_cast<size_t>(i < steps.size() - chromosome.length()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_TEST(abs(mean(bernoulliTrials) - expectedValue) < expectedValue * relativeTolerance);
|
||||||
|
BOOST_TEST(abs(meanSquaredError(bernoulliTrials, expectedValue) - variance) < variance * relativeTolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(runNextRound_should_preserve_elite, ClassicGeneticAlgorithmFixture)
|
||||||
|
{
|
||||||
|
auto population = Population::makeRandom(m_fitnessMetric, 4, 3, 3) + Population::makeRandom(m_fitnessMetric, 6, 5, 5);
|
||||||
|
assert((chromosomeLengths(population) == vector<size_t>{3, 3, 3, 3, 5, 5, 5, 5, 5, 5}));
|
||||||
|
|
||||||
|
m_options.elitePoolSize = 0.5;
|
||||||
|
m_options.deletionChance = 1.0;
|
||||||
|
ClassicGeneticAlgorithm algorithm(m_options);
|
||||||
|
Population newPopulation = algorithm.runNextRound(population);
|
||||||
|
|
||||||
|
BOOST_TEST((chromosomeLengths(newPopulation) == vector<size_t>{0, 0, 0, 0, 0, 3, 3, 3, 3, 5}));
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
@ -64,3 +64,65 @@ Population GenerationalElitistWithExclusivePools::runNextRound(Population _popul
|
|||||||
_population.select(elitePool).mutate(mutationPoolFromElite, mutationOperator) +
|
_population.select(elitePool).mutate(mutationPoolFromElite, mutationOperator) +
|
||||||
_population.select(elitePool).crossover(crossoverPoolFromElite, crossoverOperator);
|
_population.select(elitePool).crossover(crossoverPoolFromElite, crossoverOperator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Population ClassicGeneticAlgorithm::runNextRound(Population _population)
|
||||||
|
{
|
||||||
|
Population elite = _population.select(RangeSelection(0.0, m_options.elitePoolSize));
|
||||||
|
Population rest = _population.select(RangeSelection(m_options.elitePoolSize, 1.0));
|
||||||
|
|
||||||
|
Population selectedPopulation = select(_population, rest.individuals().size());
|
||||||
|
|
||||||
|
Population crossedPopulation = Population::combine(
|
||||||
|
selectedPopulation.symmetricCrossoverWithRemainder(
|
||||||
|
PairsFromRandomSubset(m_options.crossoverChance),
|
||||||
|
symmetricRandomPointCrossover()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
std::function<Mutation> mutationOperator = mutationSequence({
|
||||||
|
geneRandomisation(m_options.mutationChance),
|
||||||
|
geneDeletion(m_options.deletionChance),
|
||||||
|
geneAddition(m_options.additionChance),
|
||||||
|
});
|
||||||
|
|
||||||
|
RangeSelection all(0.0, 1.0);
|
||||||
|
Population mutatedPopulation = crossedPopulation.mutate(all, mutationOperator);
|
||||||
|
|
||||||
|
return elite + mutatedPopulation;
|
||||||
|
}
|
||||||
|
|
||||||
|
Population ClassicGeneticAlgorithm::select(Population _population, size_t _selectionSize)
|
||||||
|
{
|
||||||
|
if (_population.individuals().size() == 0)
|
||||||
|
return _population;
|
||||||
|
|
||||||
|
size_t maxFitness = 0;
|
||||||
|
for (auto const& individual: _population.individuals())
|
||||||
|
maxFitness = max(maxFitness, individual.fitness);
|
||||||
|
|
||||||
|
size_t rouletteRange = 0;
|
||||||
|
for (auto const& individual: _population.individuals())
|
||||||
|
// Add 1 to make sure that every chromosome has non-zero probability of being chosen
|
||||||
|
rouletteRange += maxFitness + 1 - individual.fitness;
|
||||||
|
|
||||||
|
vector<Individual> selectedIndividuals;
|
||||||
|
for (size_t i = 0; i < _selectionSize; ++i)
|
||||||
|
{
|
||||||
|
uint32_t ball = SimulationRNG::uniformInt(0, rouletteRange - 1);
|
||||||
|
|
||||||
|
size_t cumulativeFitness = 0;
|
||||||
|
for (auto const& individual: _population.individuals())
|
||||||
|
{
|
||||||
|
size_t pocketSize = maxFitness + 1 - individual.fitness;
|
||||||
|
if (ball < cumulativeFitness + pocketSize)
|
||||||
|
{
|
||||||
|
selectedIndividuals.push_back(individual);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cumulativeFitness += pocketSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(selectedIndividuals.size() == _selectionSize);
|
||||||
|
return Population(_population.fitnessMetric(), selectedIndividuals);
|
||||||
|
}
|
||||||
|
@ -139,4 +139,59 @@ private:
|
|||||||
Options m_options;
|
Options m_options;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A typical genetic algorithm that works in three distinct phases, each resulting in a new,
|
||||||
|
* modified population:
|
||||||
|
* - selection: chromosomes are selected from the population with probability proportional to their
|
||||||
|
* fitness. A chromosome can be selected more than once. The new population has the same size as
|
||||||
|
* the old one.
|
||||||
|
* - crossover: first, for each chromosome we decide whether it undergoes crossover or not
|
||||||
|
* (according to crossover chance parameter). Then each selected chromosome is randomly paired
|
||||||
|
* with one other selected chromosome. Each pair produces a pair of children and gets replaced by
|
||||||
|
* it in the population.
|
||||||
|
* - mutation: we go over each gene in the population and independently decide whether to mutate it
|
||||||
|
* or not (according to mutation chance parameters). This is repeated for every mutation type so
|
||||||
|
* one gene can undergo mutations of multiple types in a single round.
|
||||||
|
*
|
||||||
|
* This implementation also has the ability to preserve the top chromosomes in each round.
|
||||||
|
*/
|
||||||
|
class ClassicGeneticAlgorithm: public GeneticAlgorithm
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Options
|
||||||
|
{
|
||||||
|
double elitePoolSize; ///< Percentage of the population treated as the elite.
|
||||||
|
double crossoverChance; ///< The chance of a particular chromosome being selected for crossover.
|
||||||
|
double mutationChance; ///< The chance of a particular gene being randomised in @a geneRandomisation mutation.
|
||||||
|
double deletionChance; ///< The chance of a particular gene being deleted in @a geneDeletion mutation.
|
||||||
|
double additionChance; ///< The chance of a particular gene being added in @a geneAddition mutation.
|
||||||
|
|
||||||
|
bool isValid() const
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
0 <= elitePoolSize && elitePoolSize <= 1.0 &&
|
||||||
|
0 <= crossoverChance && crossoverChance <= 1.0 &&
|
||||||
|
0 <= mutationChance && mutationChance <= 1.0 &&
|
||||||
|
0 <= deletionChance && deletionChance <= 1.0 &&
|
||||||
|
0 <= additionChance && additionChance <= 1.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ClassicGeneticAlgorithm(Options const& _options):
|
||||||
|
m_options(_options)
|
||||||
|
{
|
||||||
|
assert(_options.isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
Options const& options() const { return m_options; }
|
||||||
|
|
||||||
|
Population runNextRound(Population _population) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static Population select(Population _population, size_t _selectionSize);
|
||||||
|
|
||||||
|
Options m_options;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user