mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #8515 from imapp-pl/yul-phaser-classic-genetic-algorithm
[yul-phaser] Classic genetic algorithm
This commit is contained in:
commit
703b6efb55
@ -31,6 +31,7 @@
|
||||
using namespace std;
|
||||
using namespace boost::unit_test::framework;
|
||||
using namespace boost::test_tools;
|
||||
using namespace solidity::util;
|
||||
|
||||
namespace solidity::phaser::test
|
||||
{
|
||||
@ -41,6 +42,18 @@ protected:
|
||||
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(GeneticAlgorithmsTest)
|
||||
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()
|
||||
|
@ -212,6 +212,39 @@ BOOST_AUTO_TEST_CASE(alternativeMutations_should_always_choose_second_mutation_i
|
||||
BOOST_TEST(mutation(chromosome) == Chromosome("f"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(mutationSequence_should_apply_all_mutations)
|
||||
{
|
||||
Chromosome chromosome("aaaaa");
|
||||
function<Mutation> mutation = mutationSequence({
|
||||
geneSubstitution(3, Chromosome("g").optimisationSteps()[0]),
|
||||
geneSubstitution(2, Chromosome("f").optimisationSteps()[0]),
|
||||
geneSubstitution(1, Chromosome("c").optimisationSteps()[0]),
|
||||
});
|
||||
|
||||
BOOST_TEST(mutation(chromosome) == Chromosome("acfga"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(mutationSequence_apply_mutations_in_the_order_they_are_given)
|
||||
{
|
||||
Chromosome chromosome("aa");
|
||||
function<Mutation> mutation = mutationSequence({
|
||||
geneSubstitution(0, Chromosome("g").optimisationSteps()[0]),
|
||||
geneSubstitution(1, Chromosome("c").optimisationSteps()[0]),
|
||||
geneSubstitution(0, Chromosome("f").optimisationSteps()[0]),
|
||||
geneSubstitution(1, Chromosome("o").optimisationSteps()[0]),
|
||||
});
|
||||
|
||||
BOOST_TEST(mutation(chromosome) == Chromosome("fo"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(mutationSequence_should_return_unmodified_chromosome_if_given_no_mutations)
|
||||
{
|
||||
Chromosome chromosome("aa");
|
||||
function<Mutation> mutation = mutationSequence({});
|
||||
|
||||
BOOST_TEST(mutation(chromosome) == chromosome);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(randomPointCrossover_should_swap_chromosome_parts_at_random_point)
|
||||
{
|
||||
function<Crossover> crossover = randomPointCrossover();
|
||||
@ -225,6 +258,20 @@ BOOST_AUTO_TEST_CASE(randomPointCrossover_should_swap_chromosome_parts_at_random
|
||||
BOOST_TEST(result2 == Chromosome("cccaaaaaaa"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(symmetricRandomPointCrossover_should_swap_chromosome_parts_at_random_point)
|
||||
{
|
||||
function<SymmetricCrossover> crossover = symmetricRandomPointCrossover();
|
||||
|
||||
SimulationRNG::reset(1);
|
||||
tuple<Chromosome, Chromosome> result1 = crossover(Chromosome("aaaaaaaaaa"), Chromosome("cccccc"));
|
||||
tuple<Chromosome, Chromosome> expectedPair1 = {Chromosome("aaaccc"), Chromosome("cccaaaaaaa")};
|
||||
BOOST_TEST(result1 == expectedPair1);
|
||||
|
||||
tuple<Chromosome, Chromosome> result2 = crossover(Chromosome("cccccc"), Chromosome("aaaaaaaaaa"));
|
||||
tuple<Chromosome, Chromosome> expectedPair2 = {Chromosome("ccccccaaaa"), Chromosome("aaaaaa")};
|
||||
BOOST_TEST(result2 == expectedPair2);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(randomPointCrossover_should_only_consider_points_available_on_both_chromosomes)
|
||||
{
|
||||
SimulationRNG::reset(1);
|
||||
|
@ -119,6 +119,78 @@ BOOST_AUTO_TEST_CASE(materialise_should_return_no_pairs_if_collection_has_one_el
|
||||
BOOST_TEST(RandomPairSelection(2.0).materialise(1).empty());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
BOOST_AUTO_TEST_SUITE(PairsFromRandomSubsetTest)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(materialise_should_return_random_values_with_equal_probabilities)
|
||||
{
|
||||
constexpr int collectionSize = 1000;
|
||||
constexpr double selectionChance = 0.7;
|
||||
constexpr double relativeTolerance = 0.001;
|
||||
constexpr double expectedValue = selectionChance;
|
||||
constexpr double variance = selectionChance * (1 - selectionChance);
|
||||
|
||||
SimulationRNG::reset(1);
|
||||
vector<tuple<size_t, size_t>> pairs = PairsFromRandomSubset(selectionChance).materialise(collectionSize);
|
||||
vector<double> bernoulliTrials(collectionSize, 0);
|
||||
for (auto& pair: pairs)
|
||||
{
|
||||
BOOST_REQUIRE(get<1>(pair) < collectionSize);
|
||||
BOOST_REQUIRE(get<1>(pair) < collectionSize);
|
||||
bernoulliTrials[get<0>(pair)] = 1.0;
|
||||
bernoulliTrials[get<1>(pair)] = 1.0;
|
||||
}
|
||||
|
||||
BOOST_TEST(abs(mean(bernoulliTrials) - expectedValue) < expectedValue * relativeTolerance);
|
||||
BOOST_TEST(abs(meanSquaredError(bernoulliTrials, expectedValue) - variance) < variance * relativeTolerance);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(materialise_should_return_only_values_that_can_be_used_as_collection_indices)
|
||||
{
|
||||
const size_t collectionSize = 200;
|
||||
constexpr double selectionChance = 0.5;
|
||||
|
||||
vector<tuple<size_t, size_t>> pairs = PairsFromRandomSubset(selectionChance).materialise(collectionSize);
|
||||
|
||||
BOOST_TEST(all_of(pairs.begin(), pairs.end(), [&](auto const& pair){ return get<0>(pair) <= collectionSize; }));
|
||||
BOOST_TEST(all_of(pairs.begin(), pairs.end(), [&](auto const& pair){ return get<1>(pair) <= collectionSize; }));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(materialise_should_use_unique_indices)
|
||||
{
|
||||
constexpr size_t collectionSize = 200;
|
||||
constexpr double selectionChance = 0.5;
|
||||
|
||||
vector<tuple<size_t, size_t>> pairs = PairsFromRandomSubset(selectionChance).materialise(collectionSize);
|
||||
set<size_t> indices;
|
||||
for (auto& pair: pairs)
|
||||
{
|
||||
indices.insert(get<0>(pair));
|
||||
indices.insert(get<1>(pair));
|
||||
}
|
||||
|
||||
BOOST_TEST(indices.size() == 2 * pairs.size());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(materialise_should_return_no_indices_if_collection_is_empty)
|
||||
{
|
||||
BOOST_TEST(PairsFromRandomSubset(0.0).materialise(0).empty());
|
||||
BOOST_TEST(PairsFromRandomSubset(0.5).materialise(0).empty());
|
||||
BOOST_TEST(PairsFromRandomSubset(1.0).materialise(0).empty());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(materialise_should_return_no_pairs_if_selection_chance_is_zero)
|
||||
{
|
||||
BOOST_TEST(PairsFromRandomSubset(0.0).materialise(0).empty());
|
||||
BOOST_TEST(PairsFromRandomSubset(0.0).materialise(100).empty());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(materialise_should_return_all_pairs_if_selection_chance_is_one)
|
||||
{
|
||||
BOOST_TEST(PairsFromRandomSubset(1.0).materialise(0).empty());
|
||||
BOOST_TEST(PairsFromRandomSubset(1.0).materialise(100).size() == 50);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
BOOST_AUTO_TEST_SUITE(PairMosaicSelectionTest)
|
||||
|
||||
|
@ -52,6 +52,11 @@ protected:
|
||||
/* gewepDeletionVsAdditionChance = */ 0.3,
|
||||
/* gewepGenesToRandomise = */ 0.4,
|
||||
/* gewepGenesToAddOrDelete = */ 0.2,
|
||||
/* classicElitePoolSize = */ 0.0,
|
||||
/* classicCrossoverChance = */ 0.75,
|
||||
/* classicMutationChance = */ 0.2,
|
||||
/* classicDeletionChance = */ 0.2,
|
||||
/* classicAdditionChance = */ 0.2,
|
||||
};
|
||||
};
|
||||
|
||||
@ -122,6 +127,18 @@ BOOST_FIXTURE_TEST_CASE(build_should_select_the_right_algorithm_and_pass_the_opt
|
||||
BOOST_TEST(gewepAlgorithm->options().deletionVsAdditionChance == m_options.gewepDeletionVsAdditionChance);
|
||||
BOOST_TEST(gewepAlgorithm->options().percentGenesToRandomise == m_options.gewepGenesToRandomise.value());
|
||||
BOOST_TEST(gewepAlgorithm->options().percentGenesToAddOrDelete == m_options.gewepGenesToAddOrDelete.value());
|
||||
|
||||
m_options.algorithm = Algorithm::Classic;
|
||||
unique_ptr<GeneticAlgorithm> algorithm3 = GeneticAlgorithmFactory::build(m_options, 100);
|
||||
BOOST_REQUIRE(algorithm3 != nullptr);
|
||||
|
||||
auto classicAlgorithm = dynamic_cast<ClassicGeneticAlgorithm*>(algorithm3.get());
|
||||
BOOST_REQUIRE(classicAlgorithm != nullptr);
|
||||
BOOST_TEST(classicAlgorithm->options().elitePoolSize == m_options.classicElitePoolSize);
|
||||
BOOST_TEST(classicAlgorithm->options().crossoverChance == m_options.classicCrossoverChance);
|
||||
BOOST_TEST(classicAlgorithm->options().mutationChance == m_options.classicMutationChance);
|
||||
BOOST_TEST(classicAlgorithm->options().deletionChance == m_options.classicDeletionChance);
|
||||
BOOST_TEST(classicAlgorithm->options().additionChance == m_options.classicAdditionChance);
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(build_should_set_random_algorithm_elite_pool_size_based_on_population_size_if_not_specified, GeneticAlgorithmFactoryFixture)
|
||||
|
@ -48,6 +48,14 @@ namespace solidity::phaser::test
|
||||
class PopulationFixture
|
||||
{
|
||||
protected:
|
||||
static ChromosomePair twoStepSwap(Chromosome const& _chromosome1, Chromosome const& _chromosome2)
|
||||
{
|
||||
return ChromosomePair{
|
||||
Chromosome(vector<string>{_chromosome1.optimisationSteps()[0], _chromosome2.optimisationSteps()[1]}),
|
||||
Chromosome(vector<string>{_chromosome2.optimisationSteps()[0], _chromosome1.optimisationSteps()[1]}),
|
||||
};
|
||||
}
|
||||
|
||||
shared_ptr<FitnessMetric> m_fitnessMetric = make_shared<ChromosomeLengthMetric>();
|
||||
};
|
||||
|
||||
@ -104,6 +112,23 @@ BOOST_FIXTURE_TEST_CASE(constructor_should_copy_chromosomes_compute_fitness_and_
|
||||
BOOST_TEST(individuals[2].chromosome == chromosomes[1]);
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(constructor_should_accept_individuals_without_recalculating_fitness, PopulationFixture)
|
||||
{
|
||||
vector<Individual> customIndividuals = {
|
||||
Individual(Chromosome("aaaccc"), 20),
|
||||
Individual(Chromosome("aaa"), 10),
|
||||
Individual(Chromosome("aaaf"), 30),
|
||||
};
|
||||
assert(customIndividuals[0].fitness != m_fitnessMetric->evaluate(customIndividuals[0].chromosome));
|
||||
assert(customIndividuals[1].fitness != m_fitnessMetric->evaluate(customIndividuals[1].chromosome));
|
||||
assert(customIndividuals[2].fitness != m_fitnessMetric->evaluate(customIndividuals[2].chromosome));
|
||||
|
||||
Population population(m_fitnessMetric, customIndividuals);
|
||||
|
||||
vector<Individual> expectedIndividuals{customIndividuals[1], customIndividuals[0], customIndividuals[2]};
|
||||
BOOST_TEST(population.individuals() == expectedIndividuals);
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(makeRandom_should_get_chromosome_lengths_from_specified_generator, PopulationFixture)
|
||||
{
|
||||
size_t chromosomeCount = 30;
|
||||
@ -292,6 +317,61 @@ BOOST_FIXTURE_TEST_CASE(crossover_should_return_empty_population_if_selection_is
|
||||
BOOST_TEST(population.crossover(selection, fixedPointCrossover(0.5)).individuals().empty());
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(symmetricCrossoverWithRemainder_should_return_crossed_population_and_remainder, PopulationFixture)
|
||||
{
|
||||
Population population(m_fitnessMetric, {Chromosome("aa"), Chromosome("cc"), Chromosome("gg"), Chromosome("hh")});
|
||||
PairMosaicSelection selection({{2, 1}}, 0.25);
|
||||
assert(selection.materialise(population.individuals().size()) == (vector<tuple<size_t, size_t>>{{2, 1}}));
|
||||
|
||||
Population expectedCrossedPopulation(m_fitnessMetric, {Chromosome("gc"), Chromosome("cg")});
|
||||
Population expectedRemainder(m_fitnessMetric, {Chromosome("aa"), Chromosome("hh")});
|
||||
|
||||
BOOST_TEST(
|
||||
population.symmetricCrossoverWithRemainder(selection, twoStepSwap) ==
|
||||
(tuple<Population, Population>{expectedCrossedPopulation, expectedRemainder})
|
||||
);
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(symmetricCrossoverWithRemainder_should_allow_crossing_the_same_individual_multiple_times, PopulationFixture)
|
||||
{
|
||||
Population population(m_fitnessMetric, {Chromosome("aa"), Chromosome("cc"), Chromosome("gg"), Chromosome("hh")});
|
||||
PairMosaicSelection selection({{0, 0}, {2, 1}}, 1.0);
|
||||
assert(selection.materialise(population.individuals().size()) == (vector<tuple<size_t, size_t>>{{0, 0}, {2, 1}, {0, 0}, {2, 1}}));
|
||||
|
||||
Population expectedCrossedPopulation(m_fitnessMetric, {
|
||||
Chromosome("aa"), Chromosome("aa"),
|
||||
Chromosome("aa"), Chromosome("aa"),
|
||||
Chromosome("gc"), Chromosome("cg"),
|
||||
Chromosome("gc"), Chromosome("cg"),
|
||||
});
|
||||
Population expectedRemainder(m_fitnessMetric, {Chromosome("hh")});
|
||||
|
||||
BOOST_TEST(
|
||||
population.symmetricCrossoverWithRemainder(selection, twoStepSwap) ==
|
||||
(tuple<Population, Population>{expectedCrossedPopulation, expectedRemainder})
|
||||
);
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(symmetricCrossoverWithRemainder_should_return_empty_population_if_selection_is_empty, PopulationFixture)
|
||||
{
|
||||
Population population(m_fitnessMetric, {Chromosome("aa"), Chromosome("cc")});
|
||||
PairMosaicSelection selection({}, 0.0);
|
||||
assert(selection.materialise(population.individuals().size()).empty());
|
||||
|
||||
BOOST_TEST(
|
||||
population.symmetricCrossoverWithRemainder(selection, twoStepSwap) ==
|
||||
(tuple<Population, Population>{Population(m_fitnessMetric), population})
|
||||
);
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(combine_should_add_two_populations_from_a_pair, PopulationFixture)
|
||||
{
|
||||
Population population1(m_fitnessMetric, {Chromosome("aa"), Chromosome("hh")});
|
||||
Population population2(m_fitnessMetric, {Chromosome("gg"), Chromosome("cc")});
|
||||
|
||||
BOOST_TEST(Population::combine({population1, population2}) == population1 + population2);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
|
@ -25,9 +25,11 @@
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity::util;
|
||||
|
||||
namespace solidity::phaser::test
|
||||
{
|
||||
@ -199,6 +201,60 @@ BOOST_AUTO_TEST_CASE(materialise_should_return_no_indices_if_collection_is_empty
|
||||
BOOST_TEST(RandomSelection(2.0).materialise(0).empty());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
BOOST_AUTO_TEST_SUITE(RandomSubsetTest)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(materialise_should_return_random_values_with_equal_probabilities)
|
||||
{
|
||||
constexpr int collectionSize = 1000;
|
||||
constexpr double selectionChance = 0.7;
|
||||
constexpr double relativeTolerance = 0.001;
|
||||
constexpr double expectedValue = selectionChance;
|
||||
constexpr double variance = selectionChance * (1 - selectionChance);
|
||||
|
||||
SimulationRNG::reset(1);
|
||||
auto indices = convertContainer<set<size_t>>(RandomSubset(selectionChance).materialise(collectionSize));
|
||||
|
||||
vector<double> bernoulliTrials(collectionSize);
|
||||
for (size_t i = 0; i < collectionSize; ++i)
|
||||
bernoulliTrials[i] = indices.count(i);
|
||||
|
||||
BOOST_TEST(abs(mean(bernoulliTrials) - expectedValue) < expectedValue * relativeTolerance);
|
||||
BOOST_TEST(abs(meanSquaredError(bernoulliTrials, expectedValue) - variance) < variance * relativeTolerance);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(materialise_should_return_only_values_that_can_be_used_as_collection_indices)
|
||||
{
|
||||
const size_t collectionSize = 200;
|
||||
vector<size_t> indices = RandomSubset(0.5).materialise(collectionSize);
|
||||
|
||||
BOOST_TEST(all_of(indices.begin(), indices.end(), [&](auto const& index){ return index <= collectionSize; }));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(materialise_should_return_indices_in_the_same_order_they_are_in_the_container)
|
||||
{
|
||||
const size_t collectionSize = 200;
|
||||
vector<size_t> indices = RandomSubset(0.5).materialise(collectionSize);
|
||||
|
||||
for (size_t i = 1; i < indices.size(); ++i)
|
||||
BOOST_TEST(indices[i - 1] < indices[i]);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(materialise_should_return_no_indices_if_collection_is_empty)
|
||||
{
|
||||
BOOST_TEST(RandomSubset(0.5).materialise(0).empty());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(materialise_should_return_no_indices_if_selection_chance_is_zero)
|
||||
{
|
||||
BOOST_TEST(RandomSubset(0.0).materialise(10).empty());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(materialise_should_return_all_indices_if_selection_chance_is_one)
|
||||
{
|
||||
BOOST_TEST(RandomSubset(1.0).materialise(10).size() == 10);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
@ -33,12 +33,39 @@
|
||||
#include <tools/yulPhaser/Mutations.h>
|
||||
#include <tools/yulPhaser/Population.h>
|
||||
|
||||
#include <boost/test/tools/detail/print_helper.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
// OPERATORS FOR BOOST::TEST
|
||||
|
||||
/// Output operator for arbitrary two-element tuples.
|
||||
/// Necessary to make BOOST_TEST() work with such tuples.
|
||||
template<typename T1, typename T2>
|
||||
std::ostream& operator<<(std::ostream& _output, std::tuple<T1, T2> const& _tuple)
|
||||
{
|
||||
_output << "(" << std::get<0>(_tuple) << ", " << std::get<1>(_tuple) << ")";
|
||||
return _output;
|
||||
}
|
||||
|
||||
namespace boost::test_tools::tt_detail
|
||||
{
|
||||
|
||||
// Boost won't find find the << operator unless we put it in the std namespace which is illegal.
|
||||
// The recommended solution is to overload print_log_value<> struct and make it use our global operator.
|
||||
template<typename T1,typename T2>
|
||||
struct print_log_value<std::tuple<T1, T2>>
|
||||
{
|
||||
void operator()(std::ostream& _output, std::tuple<T1, T2> const& _tuple) { ::operator<<(_output, _tuple); }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace solidity::phaser::test
|
||||
{
|
||||
|
||||
|
@ -187,16 +187,16 @@ Population AlgorithmRunner::randomiseDuplicates(
|
||||
if (_population.individuals().size() == 0)
|
||||
return _population;
|
||||
|
||||
vector<Chromosome> chromosomes{_population.individuals()[0].chromosome};
|
||||
vector<Individual> individuals{_population.individuals()[0]};
|
||||
size_t duplicateCount = 0;
|
||||
for (size_t i = 1; i < _population.individuals().size(); ++i)
|
||||
if (_population.individuals()[i].chromosome == _population.individuals()[i - 1].chromosome)
|
||||
++duplicateCount;
|
||||
else
|
||||
chromosomes.push_back(_population.individuals()[i].chromosome);
|
||||
individuals.push_back(_population.individuals()[i]);
|
||||
|
||||
return (
|
||||
Population(_population.fitnessMetric(), chromosomes) +
|
||||
Population(_population.fitnessMetric(), individuals) +
|
||||
Population::makeRandom(_population.fitnessMetric(), duplicateCount, _minChromosomeLength, _maxChromosomeLength)
|
||||
);
|
||||
}
|
||||
|
@ -43,13 +43,12 @@ Population RandomAlgorithm::runNextRound(Population _population)
|
||||
Population GenerationalElitistWithExclusivePools::runNextRound(Population _population)
|
||||
{
|
||||
double elitePoolSize = 1.0 - (m_options.mutationPoolSize + m_options.crossoverPoolSize);
|
||||
RangeSelection elite(0.0, elitePoolSize);
|
||||
|
||||
return
|
||||
_population.select(elite) +
|
||||
_population.select(elite).mutate(
|
||||
RandomSelection(m_options.mutationPoolSize / elitePoolSize),
|
||||
alternativeMutations(
|
||||
RangeSelection elitePool(0.0, elitePoolSize);
|
||||
RandomSelection mutationPoolFromElite(m_options.mutationPoolSize / elitePoolSize);
|
||||
RandomPairSelection crossoverPoolFromElite(m_options.crossoverPoolSize / elitePoolSize);
|
||||
|
||||
std::function<Mutation> mutationOperator = alternativeMutations(
|
||||
m_options.randomisationChance,
|
||||
geneRandomisation(m_options.percentGenesToRandomise),
|
||||
alternativeMutations(
|
||||
@ -57,10 +56,73 @@ Population GenerationalElitistWithExclusivePools::runNextRound(Population _popul
|
||||
geneDeletion(m_options.percentGenesToAddOrDelete),
|
||||
geneAddition(m_options.percentGenesToAddOrDelete)
|
||||
)
|
||||
)
|
||||
) +
|
||||
_population.select(elite).crossover(
|
||||
RandomPairSelection(m_options.crossoverPoolSize / elitePoolSize),
|
||||
randomPointCrossover()
|
||||
);
|
||||
std::function<Crossover> crossoverOperator = randomPointCrossover();
|
||||
|
||||
return
|
||||
_population.select(elitePool) +
|
||||
_population.select(elitePool).mutate(mutationPoolFromElite, mutationOperator) +
|
||||
_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;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -95,10 +95,22 @@ function<Mutation> phaser::alternativeMutations(
|
||||
};
|
||||
}
|
||||
|
||||
function<Mutation> phaser::mutationSequence(vector<function<Mutation>> _mutations)
|
||||
{
|
||||
return [=](Chromosome const& _chromosome)
|
||||
{
|
||||
Chromosome mutatedChromosome = _chromosome;
|
||||
for (size_t i = 0; i < _mutations.size(); ++i)
|
||||
mutatedChromosome = _mutations[i](move(mutatedChromosome));
|
||||
|
||||
return mutatedChromosome;
|
||||
};
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
Chromosome buildChromosomesBySwappingParts(
|
||||
ChromosomePair fixedPointSwap(
|
||||
Chromosome const& _chromosome1,
|
||||
Chromosome const& _chromosome2,
|
||||
size_t _crossoverPoint
|
||||
@ -109,11 +121,19 @@ Chromosome buildChromosomesBySwappingParts(
|
||||
|
||||
auto begin1 = _chromosome1.optimisationSteps().begin();
|
||||
auto begin2 = _chromosome2.optimisationSteps().begin();
|
||||
auto end1 = _chromosome1.optimisationSteps().end();
|
||||
auto end2 = _chromosome2.optimisationSteps().end();
|
||||
|
||||
return Chromosome(
|
||||
return {
|
||||
Chromosome(
|
||||
vector<string>(begin1, begin1 + _crossoverPoint) +
|
||||
vector<string>(begin2 + _crossoverPoint, _chromosome2.optimisationSteps().end())
|
||||
);
|
||||
vector<string>(begin2 + _crossoverPoint, end2)
|
||||
),
|
||||
Chromosome(
|
||||
vector<string>(begin2, begin2 + _crossoverPoint) +
|
||||
vector<string>(begin1 + _crossoverPoint, end1)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@ -129,7 +149,22 @@ function<Crossover> phaser::randomPointCrossover()
|
||||
assert(minPoint <= minLength);
|
||||
|
||||
size_t randomPoint = SimulationRNG::uniformInt(minPoint, minLength);
|
||||
return buildChromosomesBySwappingParts(_chromosome1, _chromosome2, randomPoint);
|
||||
return get<0>(fixedPointSwap(_chromosome1, _chromosome2, randomPoint));
|
||||
};
|
||||
}
|
||||
|
||||
function<SymmetricCrossover> phaser::symmetricRandomPointCrossover()
|
||||
{
|
||||
return [=](Chromosome const& _chromosome1, Chromosome const& _chromosome2)
|
||||
{
|
||||
size_t minLength = min(_chromosome1.length(), _chromosome2.length());
|
||||
|
||||
// Don't use position 0 (because this just swaps the values) unless it's the only choice.
|
||||
size_t minPoint = (minLength > 0? 1 : 0);
|
||||
assert(minPoint <= minLength);
|
||||
|
||||
size_t randomPoint = SimulationRNG::uniformInt(minPoint, minLength);
|
||||
return fixedPointSwap(_chromosome1, _chromosome2, randomPoint);
|
||||
};
|
||||
}
|
||||
|
||||
@ -142,6 +177,6 @@ function<Crossover> phaser::fixedPointCrossover(double _crossoverPoint)
|
||||
size_t minLength = min(_chromosome1.length(), _chromosome2.length());
|
||||
size_t concretePoint = static_cast<size_t>(round(minLength * _crossoverPoint));
|
||||
|
||||
return buildChromosomesBySwappingParts(_chromosome1, _chromosome2, concretePoint);
|
||||
return get<0>(fixedPointSwap(_chromosome1, _chromosome2, concretePoint));
|
||||
};
|
||||
}
|
||||
|
@ -28,8 +28,11 @@
|
||||
namespace solidity::phaser
|
||||
{
|
||||
|
||||
using ChromosomePair = std::tuple<Chromosome, Chromosome>;
|
||||
|
||||
using Mutation = Chromosome(Chromosome const&);
|
||||
using Crossover = Chromosome(Chromosome const&, Chromosome const&);
|
||||
using SymmetricCrossover = ChromosomePair(Chromosome const&, Chromosome const&);
|
||||
|
||||
// MUTATIONS
|
||||
|
||||
@ -55,12 +58,19 @@ std::function<Mutation> alternativeMutations(
|
||||
std::function<Mutation> _mutation2
|
||||
);
|
||||
|
||||
/// Creates a mutation operator that sequentially applies all the operators given in @a _mutations.
|
||||
std::function<Mutation> mutationSequence(std::vector<std::function<Mutation>> _mutations);
|
||||
|
||||
// CROSSOVER
|
||||
|
||||
/// Creates a crossover operator that randomly selects a number between 0 and 1 and uses it as the
|
||||
/// position at which to perform perform @a fixedPointCrossover.
|
||||
std::function<Crossover> randomPointCrossover();
|
||||
|
||||
/// Symmetric version of @a randomPointCrossover(). Creates an operator that returns a pair
|
||||
/// containing both possible results for the same crossover point.
|
||||
std::function<SymmetricCrossover> symmetricRandomPointCrossover();
|
||||
|
||||
/// Creates a crossover operator that always chooses a point that lies at @a _crossoverPoint
|
||||
/// percent of the length of the shorter chromosome. Then creates a new chromosome by
|
||||
/// splitting both inputs at the crossover point and stitching output from the first half or first
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include <tools/yulPhaser/PairSelections.h>
|
||||
|
||||
#include <tools/yulPhaser/Selections.h>
|
||||
#include <tools/yulPhaser/SimulationRNG.h>
|
||||
|
||||
#include <cmath>
|
||||
@ -47,6 +48,43 @@ vector<tuple<size_t, size_t>> RandomPairSelection::materialise(size_t _poolSize)
|
||||
return selection;
|
||||
}
|
||||
|
||||
vector<tuple<size_t, size_t>> PairsFromRandomSubset::materialise(size_t _poolSize) const
|
||||
{
|
||||
vector<size_t> selectedIndices = RandomSubset(m_selectionChance).materialise(_poolSize);
|
||||
|
||||
if (selectedIndices.size() % 2 != 0)
|
||||
{
|
||||
if (selectedIndices.size() < _poolSize && SimulationRNG::bernoulliTrial(0.5))
|
||||
{
|
||||
do
|
||||
{
|
||||
size_t extraIndex = SimulationRNG::uniformInt(0, selectedIndices.size() - 1);
|
||||
if (find(selectedIndices.begin(), selectedIndices.end(), extraIndex) == selectedIndices.end())
|
||||
selectedIndices.push_back(extraIndex);
|
||||
} while (selectedIndices.size() % 2 != 0);
|
||||
}
|
||||
else
|
||||
selectedIndices.erase(selectedIndices.begin() + SimulationRNG::uniformInt(0, selectedIndices.size() - 1));
|
||||
}
|
||||
assert(selectedIndices.size() % 2 == 0);
|
||||
|
||||
vector<tuple<size_t, size_t>> selectedPairs;
|
||||
for (size_t i = selectedIndices.size() / 2; i > 0; --i)
|
||||
{
|
||||
size_t position1 = SimulationRNG::uniformInt(0, selectedIndices.size() - 1);
|
||||
size_t value1 = selectedIndices[position1];
|
||||
selectedIndices.erase(selectedIndices.begin() + position1);
|
||||
size_t position2 = SimulationRNG::uniformInt(0, selectedIndices.size() - 1);
|
||||
size_t value2 = selectedIndices[position2];
|
||||
selectedIndices.erase(selectedIndices.begin() + position2);
|
||||
|
||||
selectedPairs.push_back({value1, value2});
|
||||
}
|
||||
assert(selectedIndices.size() == 0);
|
||||
|
||||
return selectedPairs;
|
||||
}
|
||||
|
||||
vector<tuple<size_t, size_t>> PairMosaicSelection::materialise(size_t _poolSize) const
|
||||
{
|
||||
if (_poolSize < 2)
|
||||
|
@ -69,6 +69,28 @@ private:
|
||||
double m_selectionSize;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A selection that goes over all elements in a container, for each one independently decides
|
||||
* whether to select it or not and then randomly combines those elements into pairs. If the number
|
||||
* of elements is odd, randomly decides whether to take one more or exclude one.
|
||||
*
|
||||
* Each element has the same chance of being selected and can be selected at most once.
|
||||
* The number of selected elements is random and can be different with each call to
|
||||
* @a materialise().
|
||||
*/
|
||||
class PairsFromRandomSubset: public PairSelection
|
||||
{
|
||||
public:
|
||||
explicit PairsFromRandomSubset(double _selectionChance):
|
||||
m_selectionChance(_selectionChance) {}
|
||||
|
||||
std::vector<std::tuple<size_t, size_t>> materialise(size_t _poolSize) const override;
|
||||
|
||||
private:
|
||||
double m_selectionChance;
|
||||
};
|
||||
|
||||
/**
|
||||
* A selection that selects pairs of elements at specific, fixed positions indicated by a repeating
|
||||
* "pattern". If the positions in the pattern exceed the size of the container, they are capped at
|
||||
|
@ -58,6 +58,7 @@ map<Algorithm, string> const AlgorithmToStringMap =
|
||||
{
|
||||
{Algorithm::Random, "random"},
|
||||
{Algorithm::GEWEP, "GEWEP"},
|
||||
{Algorithm::Classic, "classic"},
|
||||
};
|
||||
map<string, Algorithm> const StringToAlgorithmMap = invertMap(AlgorithmToStringMap);
|
||||
|
||||
@ -107,6 +108,11 @@ GeneticAlgorithmFactory::Options GeneticAlgorithmFactory::Options::fromCommandLi
|
||||
_arguments.count("gewep-genes-to-add-or-delete") > 0 ?
|
||||
_arguments["gewep-genes-to-add-or-delete"].as<double>() :
|
||||
optional<double>{},
|
||||
_arguments["classic-elite-pool-size"].as<double>(),
|
||||
_arguments["classic-crossover-chance"].as<double>(),
|
||||
_arguments["classic-mutation-chance"].as<double>(),
|
||||
_arguments["classic-deletion-chance"].as<double>(),
|
||||
_arguments["classic-addition-chance"].as<double>(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -151,6 +157,16 @@ unique_ptr<GeneticAlgorithm> GeneticAlgorithmFactory::build(
|
||||
/* percentGenesToAddOrDelete = */ percentGenesToAddOrDelete,
|
||||
});
|
||||
}
|
||||
case Algorithm::Classic:
|
||||
{
|
||||
return make_unique<ClassicGeneticAlgorithm>(ClassicGeneticAlgorithm::Options{
|
||||
/* elitePoolSize = */ _options.classicElitePoolSize,
|
||||
/* crossoverChance = */ _options.classicCrossoverChance,
|
||||
/* mutationChance = */ _options.classicMutationChance,
|
||||
/* deletionChance = */ _options.classicDeletionChance,
|
||||
/* additionChance = */ _options.classicAdditionChance,
|
||||
});
|
||||
}
|
||||
default:
|
||||
assertThrow(false, solidity::util::Exception, "Invalid Algorithm value.");
|
||||
}
|
||||
@ -475,6 +491,36 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription()
|
||||
;
|
||||
keywordDescription.add(gewepAlgorithmDescription);
|
||||
|
||||
po::options_description classicGeneticAlgorithmDescription("CLASSIC GENETIC ALGORITHM", lineLength, minDescriptionLength);
|
||||
classicGeneticAlgorithmDescription.add_options()
|
||||
(
|
||||
"classic-elite-pool-size",
|
||||
po::value<double>()->value_name("<FRACTION>")->default_value(0),
|
||||
"Percentage of population to regenerate using mutations in each round."
|
||||
)
|
||||
(
|
||||
"classic-crossover-chance",
|
||||
po::value<double>()->value_name("<FRACTION>")->default_value(0.75),
|
||||
"Chance of a chromosome being selected for crossover."
|
||||
)
|
||||
(
|
||||
"classic-mutation-chance",
|
||||
po::value<double>()->value_name("<FRACTION>")->default_value(0.01),
|
||||
"Chance of a gene being mutated."
|
||||
)
|
||||
(
|
||||
"classic-deletion-chance",
|
||||
po::value<double>()->value_name("<PROBABILITY>")->default_value(0.01),
|
||||
"Chance of a gene being deleted."
|
||||
)
|
||||
(
|
||||
"classic-addition-chance",
|
||||
po::value<double>()->value_name("<PROBABILITY>")->default_value(0.01),
|
||||
"Chance of a random gene being added."
|
||||
)
|
||||
;
|
||||
keywordDescription.add(classicGeneticAlgorithmDescription);
|
||||
|
||||
po::options_description randomAlgorithmDescription("RANDOM ALGORITHM", lineLength, minDescriptionLength);
|
||||
randomAlgorithmDescription.add_options()
|
||||
(
|
||||
|
@ -58,6 +58,7 @@ enum class Algorithm
|
||||
{
|
||||
Random,
|
||||
GEWEP,
|
||||
Classic,
|
||||
};
|
||||
|
||||
enum class MetricChoice
|
||||
@ -101,6 +102,11 @@ public:
|
||||
double gewepDeletionVsAdditionChance;
|
||||
std::optional<double> gewepGenesToRandomise;
|
||||
std::optional<double> gewepGenesToAddOrDelete;
|
||||
double classicElitePoolSize;
|
||||
double classicCrossoverChance;
|
||||
double classicMutationChance;
|
||||
double classicDeletionChance;
|
||||
double classicAdditionChance;
|
||||
|
||||
static Options fromCommandLine(boost::program_options::variables_map const& _arguments);
|
||||
};
|
||||
|
@ -117,6 +117,37 @@ Population Population::crossover(PairSelection const& _selection, function<Cross
|
||||
return Population(m_fitnessMetric, crossedIndividuals);
|
||||
}
|
||||
|
||||
tuple<Population, Population> Population::symmetricCrossoverWithRemainder(
|
||||
PairSelection const& _selection,
|
||||
function<SymmetricCrossover> _symmetricCrossover
|
||||
) const
|
||||
{
|
||||
vector<int> indexSelected(m_individuals.size(), false);
|
||||
|
||||
vector<Individual> crossedIndividuals;
|
||||
for (auto const& [i, j]: _selection.materialise(m_individuals.size()))
|
||||
{
|
||||
auto children = _symmetricCrossover(
|
||||
m_individuals[i].chromosome,
|
||||
m_individuals[j].chromosome
|
||||
);
|
||||
crossedIndividuals.emplace_back(move(get<0>(children)), *m_fitnessMetric);
|
||||
crossedIndividuals.emplace_back(move(get<1>(children)), *m_fitnessMetric);
|
||||
indexSelected[i] = true;
|
||||
indexSelected[j] = true;
|
||||
}
|
||||
|
||||
vector<Individual> remainder;
|
||||
for (size_t i = 0; i < indexSelected.size(); ++i)
|
||||
if (!indexSelected[i])
|
||||
remainder.emplace_back(m_individuals[i]);
|
||||
|
||||
return {
|
||||
Population(m_fitnessMetric, crossedIndividuals),
|
||||
Population(m_fitnessMetric, remainder),
|
||||
};
|
||||
}
|
||||
|
||||
namespace solidity::phaser
|
||||
{
|
||||
|
||||
@ -132,6 +163,11 @@ Population operator+(Population _a, Population _b)
|
||||
|
||||
}
|
||||
|
||||
Population Population::combine(std::tuple<Population, Population> _populationPair)
|
||||
{
|
||||
return get<0>(_populationPair) + get<1>(_populationPair);
|
||||
}
|
||||
|
||||
bool Population::operator==(Population const& _other) const
|
||||
{
|
||||
// We consider populations identical only if they share the same exact instance of the metric.
|
||||
|
@ -81,6 +81,9 @@ public:
|
||||
_fitnessMetric,
|
||||
chromosomesToIndividuals(*_fitnessMetric, std::move(_chromosomes))
|
||||
) {}
|
||||
explicit Population(std::shared_ptr<FitnessMetric> _fitnessMetric, std::vector<Individual> _individuals):
|
||||
m_fitnessMetric(std::move(_fitnessMetric)),
|
||||
m_individuals{sortedIndividuals(std::move(_individuals))} {}
|
||||
|
||||
static Population makeRandom(
|
||||
std::shared_ptr<FitnessMetric> _fitnessMetric,
|
||||
@ -97,8 +100,13 @@ public:
|
||||
Population select(Selection const& _selection) const;
|
||||
Population mutate(Selection const& _selection, std::function<Mutation> _mutation) const;
|
||||
Population crossover(PairSelection const& _selection, std::function<Crossover> _crossover) const;
|
||||
std::tuple<Population, Population> symmetricCrossoverWithRemainder(
|
||||
PairSelection const& _selection,
|
||||
std::function<SymmetricCrossover> _symmetricCrossover
|
||||
) const;
|
||||
|
||||
friend Population operator+(Population _a, Population _b);
|
||||
static Population combine(std::tuple<Population, Population> _populationPair);
|
||||
|
||||
std::shared_ptr<FitnessMetric> fitnessMetric() { return m_fitnessMetric; }
|
||||
std::vector<Individual> const& individuals() const { return m_individuals; }
|
||||
@ -112,10 +120,6 @@ public:
|
||||
friend std::ostream& operator<<(std::ostream& _stream, Population const& _population);
|
||||
|
||||
private:
|
||||
explicit Population(std::shared_ptr<FitnessMetric> _fitnessMetric, std::vector<Individual> _individuals):
|
||||
m_fitnessMetric(std::move(_fitnessMetric)),
|
||||
m_individuals{sortedIndividuals(std::move(_individuals))} {}
|
||||
|
||||
static std::vector<Individual> chromosomesToIndividuals(
|
||||
FitnessMetric& _fitnessMetric,
|
||||
std::vector<Chromosome> _chromosomes
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <tools/yulPhaser/SimulationRNG.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity::phaser;
|
||||
@ -58,3 +59,12 @@ vector<size_t> RandomSelection::materialise(size_t _poolSize) const
|
||||
return selection;
|
||||
}
|
||||
|
||||
vector<size_t> RandomSubset::materialise(size_t _poolSize) const
|
||||
{
|
||||
vector<size_t> selection;
|
||||
for (size_t index = 0; index < _poolSize; ++index)
|
||||
if (SimulationRNG::bernoulliTrial(m_selectionChance))
|
||||
selection.push_back(index);
|
||||
|
||||
return selection;
|
||||
}
|
||||
|
@ -118,4 +118,26 @@ private:
|
||||
double m_selectionSize;
|
||||
};
|
||||
|
||||
/**
|
||||
* A selection that goes over all elements in a container, for each one independently deciding
|
||||
* whether to select it or not. Each element has the same chance of being selected and can be
|
||||
* selected at most once. The order of selected elements is the same as the order of elements in
|
||||
* the container. The number of selected elements is random and can be different with each call
|
||||
* to @a materialise().
|
||||
*/
|
||||
class RandomSubset: public Selection
|
||||
{
|
||||
public:
|
||||
explicit RandomSubset(double _selectionChance):
|
||||
m_selectionChance(_selectionChance)
|
||||
{
|
||||
assert(0.0 <= _selectionChance && _selectionChance <= 1.0);
|
||||
}
|
||||
|
||||
std::vector<size_t> materialise(size_t _poolSize) const override;
|
||||
|
||||
private:
|
||||
double m_selectionChance;
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user