Merge pull request #8449 from imapp-pl/yul-phaser-multi-program-support

[yul-phaser] Multi-program support
This commit is contained in:
chriseth 2020-03-19 17:07:34 +01:00 committed by GitHub
commit b5e11e7203
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 559 additions and 93 deletions

View File

@ -22,22 +22,30 @@
#include <liblangutil/CharStream.h>
#include <libsolutil/CommonIO.h>
#include <boost/test/unit_test.hpp>
#include <cmath>
using namespace std;
using namespace solidity::langutil;
using namespace solidity::util;
using namespace solidity::yul;
namespace solidity::phaser::test
{
class FitnessMetricFixture
class DummyProgramBasedMetric: public ProgramBasedMetric
{
public:
using ProgramBasedMetric::ProgramBasedMetric;
size_t evaluate(Chromosome const&) override { return 0; }
};
class ProgramBasedMetricFixture
{
protected:
FitnessMetricFixture():
m_sourceStream(SampleSourceCode, ""),
m_program(get<Program>(Program::load(m_sourceStream))) {}
static constexpr char SampleSourceCode[] =
"{\n"
" function foo() -> result\n"
@ -52,57 +60,171 @@ protected:
" mstore(foo(), bar())\n"
"}\n";
CharStream m_sourceStream;
Program m_program;
Program optimisedProgram(Program _program) const
{
[[maybe_unused]] size_t originalSize = _program.codeSize();
Program result = move(_program);
result.optimise(m_chromosome.optimisationSteps());
// Make sure that the program and the chromosome we have chosen are suitable for the test
assert(result.codeSize() != originalSize);
return result;
}
CharStream m_sourceStream = CharStream(SampleSourceCode, "");
Chromosome m_chromosome{vector<string>{UnusedPruner::name, EquivalentFunctionCombiner::name}};
Program m_program = get<Program>(Program::load(m_sourceStream));
Program m_optimisedProgram = optimisedProgram(m_program);
};
class FitnessMetricCombinationFixture: public ProgramBasedMetricFixture
{
protected:
vector<shared_ptr<FitnessMetric>> m_simpleMetrics = {
make_shared<ProgramSize>(m_program, 1),
make_shared<ProgramSize>(m_program, 2),
make_shared<ProgramSize>(m_program, 3),
};
vector<size_t> m_fitness = {
m_simpleMetrics[0]->evaluate(m_chromosome),
m_simpleMetrics[1]->evaluate(m_chromosome),
m_simpleMetrics[2]->evaluate(m_chromosome),
};
};
BOOST_AUTO_TEST_SUITE(Phaser)
BOOST_AUTO_TEST_SUITE(FitnessMetricsTest)
BOOST_AUTO_TEST_SUITE(ProgramBasedMetricTest)
BOOST_FIXTURE_TEST_CASE(optimisedProgram_should_return_optimised_program, ProgramBasedMetricFixture)
{
string code = toString(DummyProgramBasedMetric(m_program).optimisedProgram(m_chromosome));
BOOST_TEST(code != toString(m_program));
BOOST_TEST(code == toString(m_optimisedProgram));
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(ProgramSizeTest)
BOOST_FIXTURE_TEST_CASE(evaluate_should_compute_size_of_the_optimised_program, FitnessMetricFixture)
BOOST_FIXTURE_TEST_CASE(evaluate_should_compute_size_of_the_optimised_program, ProgramBasedMetricFixture)
{
Chromosome chromosome(vector<string>{UnusedPruner::name, EquivalentFunctionCombiner::name});
size_t fitness = ProgramSize(m_program).evaluate(m_chromosome);
Program optimisedProgram = m_program;
optimisedProgram.optimise(chromosome.optimisationSteps());
assert(m_program.codeSize() != optimisedProgram.codeSize());
BOOST_TEST(ProgramSize(m_program).evaluate(chromosome) != m_program.codeSize());
BOOST_TEST(ProgramSize(m_program).evaluate(chromosome) == optimisedProgram.codeSize());
BOOST_TEST(fitness != m_program.codeSize());
BOOST_TEST(fitness == m_optimisedProgram.codeSize());
}
BOOST_FIXTURE_TEST_CASE(evaluate_should_repeat_the_optimisation_specified_number_of_times, FitnessMetricFixture)
BOOST_FIXTURE_TEST_CASE(evaluate_should_repeat_the_optimisation_specified_number_of_times, ProgramBasedMetricFixture)
{
Chromosome chromosome(vector<string>{UnusedPruner::name, EquivalentFunctionCombiner::name});
Program programOptimisedOnce = m_program;
programOptimisedOnce.optimise(chromosome.optimisationSteps());
Program programOptimisedTwice = programOptimisedOnce;
programOptimisedTwice.optimise(chromosome.optimisationSteps());
assert(m_program.codeSize() != programOptimisedOnce.codeSize());
assert(m_program.codeSize() != programOptimisedTwice.codeSize());
assert(programOptimisedOnce.codeSize() != programOptimisedTwice.codeSize());
Program const& programOptimisedOnce = m_optimisedProgram;
Program programOptimisedTwice = optimisedProgram(programOptimisedOnce);
ProgramSize metric(m_program, 2);
size_t fitness = metric.evaluate(m_chromosome);
BOOST_TEST(metric.evaluate(chromosome) != m_program.codeSize());
BOOST_TEST(metric.evaluate(chromosome) != programOptimisedOnce.codeSize());
BOOST_TEST(metric.evaluate(chromosome) == programOptimisedTwice.codeSize());
BOOST_TEST(fitness != m_program.codeSize());
BOOST_TEST(fitness != programOptimisedOnce.codeSize());
BOOST_TEST(fitness == programOptimisedTwice.codeSize());
}
BOOST_FIXTURE_TEST_CASE(evaluate_should_not_optimise_if_number_of_repetitions_is_zero, FitnessMetricFixture)
BOOST_FIXTURE_TEST_CASE(evaluate_should_not_optimise_if_number_of_repetitions_is_zero, ProgramBasedMetricFixture)
{
Chromosome chromosome(vector<string>{UnusedPruner::name, EquivalentFunctionCombiner::name});
Program optimisedProgram = m_program;
optimisedProgram.optimise(chromosome.optimisationSteps());
assert(m_program.codeSize() != optimisedProgram.codeSize());
ProgramSize metric(m_program, 0);
size_t fitness = metric.evaluate(m_chromosome);
BOOST_TEST(metric.evaluate(chromosome) == m_program.codeSize());
BOOST_TEST(metric.evaluate(chromosome) != optimisedProgram.codeSize());
BOOST_TEST(fitness == m_program.codeSize());
BOOST_TEST(fitness != m_optimisedProgram.codeSize());
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(RelativeProgramSizeTest)
BOOST_FIXTURE_TEST_CASE(evaluate_should_compute_the_size_ratio_between_optimised_program_and_original_program, ProgramBasedMetricFixture)
{
BOOST_TEST(RelativeProgramSize(m_program, 3).evaluate(m_chromosome) == round(1000.0 * m_optimisedProgram.codeSize() / m_program.codeSize()));
}
BOOST_FIXTURE_TEST_CASE(evaluate_should_repeat_the_optimisation_specified_number_of_times, ProgramBasedMetricFixture)
{
Program const& programOptimisedOnce = m_optimisedProgram;
Program programOptimisedTwice = optimisedProgram(programOptimisedOnce);
RelativeProgramSize metric(m_program, 3, 2);
size_t fitness = metric.evaluate(m_chromosome);
BOOST_TEST(fitness != 1000);
BOOST_TEST(fitness != RelativeProgramSize(programOptimisedTwice, 3, 1).evaluate(m_chromosome));
BOOST_TEST(fitness == round(1000.0 * programOptimisedTwice.codeSize() / m_program.codeSize()));
}
BOOST_FIXTURE_TEST_CASE(evaluate_should_return_one_if_number_of_repetitions_is_zero, ProgramBasedMetricFixture)
{
RelativeProgramSize metric(m_program, 3, 0);
BOOST_TEST(metric.evaluate(m_chromosome) == 1000);
}
BOOST_FIXTURE_TEST_CASE(evaluate_should_return_one_if_the_original_program_size_is_zero, ProgramBasedMetricFixture)
{
CharStream sourceStream = CharStream("{}", "");
Program program = get<Program>(Program::load(sourceStream));
RelativeProgramSize metric(program, 3);
BOOST_TEST(metric.evaluate(m_chromosome) == 1000);
BOOST_TEST(metric.evaluate(Chromosome("")) == 1000);
BOOST_TEST(metric.evaluate(Chromosome("afcxjLTLTDoO")) == 1000);
}
BOOST_FIXTURE_TEST_CASE(evaluate_should_multiply_the_result_by_scaling_factor, ProgramBasedMetricFixture)
{
double sizeRatio = static_cast<double>(m_optimisedProgram.codeSize()) / m_program.codeSize();
BOOST_TEST(RelativeProgramSize(m_program, 0).evaluate(m_chromosome) == round(1.0 * sizeRatio));
BOOST_TEST(RelativeProgramSize(m_program, 1).evaluate(m_chromosome) == round(10.0 * sizeRatio));
BOOST_TEST(RelativeProgramSize(m_program, 2).evaluate(m_chromosome) == round(100.0 * sizeRatio));
BOOST_TEST(RelativeProgramSize(m_program, 3).evaluate(m_chromosome) == round(1000.0 * sizeRatio));
BOOST_TEST(RelativeProgramSize(m_program, 4).evaluate(m_chromosome) == round(10000.0 * sizeRatio));
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(FitnessMetricCombinationTest)
BOOST_FIXTURE_TEST_CASE(FitnessMetricAverage_evaluate_should_compute_average_of_values_returned_by_metrics_passed_to_it, FitnessMetricCombinationFixture)
{
FitnessMetricAverage metric(m_simpleMetrics);
assert(m_simpleMetrics.size() == 3);
BOOST_TEST(metric.evaluate(m_chromosome) == (m_fitness[0] + m_fitness[1] + m_fitness[2]) / 3);
BOOST_TEST(metric.metrics() == m_simpleMetrics);
}
BOOST_FIXTURE_TEST_CASE(FitnessMetricSum_evaluate_should_compute_sum_of_values_returned_by_metrics_passed_to_it, FitnessMetricCombinationFixture)
{
FitnessMetricSum metric(m_simpleMetrics);
assert(m_simpleMetrics.size() == 3);
BOOST_TEST(metric.evaluate(m_chromosome) == m_fitness[0] + m_fitness[1] + m_fitness[2]);
BOOST_TEST(metric.metrics() == m_simpleMetrics);
}
BOOST_FIXTURE_TEST_CASE(FitnessMetricMaximum_evaluate_should_compute_maximum_of_values_returned_by_metrics_passed_to_it, FitnessMetricCombinationFixture)
{
FitnessMetricMaximum metric(m_simpleMetrics);
assert(m_simpleMetrics.size() == 3);
BOOST_TEST(metric.evaluate(m_chromosome) == max(m_fitness[0], max(m_fitness[1], m_fitness[2])));
BOOST_TEST(metric.metrics() == m_simpleMetrics);
}
BOOST_FIXTURE_TEST_CASE(FitnessMetricMinimum_evaluate_should_compute_minimum_of_values_returned_by_metrics_passed_to_it, FitnessMetricCombinationFixture)
{
FitnessMetricMinimum metric(m_simpleMetrics);
assert(m_simpleMetrics.size() == 3);
BOOST_TEST(metric.evaluate(m_chromosome) == min(m_fitness[0], min(m_fitness[1], m_fitness[2])));
BOOST_TEST(metric.metrics() == m_simpleMetrics);
}
BOOST_AUTO_TEST_SUITE_END()
@ -110,4 +232,3 @@ BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE_END()
}

View File

@ -58,9 +58,20 @@ protected:
class FitnessMetricFactoryFixture
{
protected:
CharStream m_sourceStream = CharStream("{}", "");
Program m_program = get<Program>(Program::load(m_sourceStream));
vector<CharStream> m_sourceStreams = {
CharStream("{}", ""),
CharStream("{{}}", ""),
CharStream("{{{}}}", ""),
};
vector<Program> m_programs = {
get<Program>(Program::load(m_sourceStreams[0])),
get<Program>(Program::load(m_sourceStreams[1])),
get<Program>(Program::load(m_sourceStreams[2])),
};
FitnessMetricFactory::Options m_options = {
/* metric = */ MetricChoice::CodeSize,
/* metricAggregator = */ MetricAggregatorChoice::Average,
/* relativeMetricScale = */ 5,
/* chromosomeRepetitions = */ 1,
};
};
@ -141,25 +152,67 @@ BOOST_AUTO_TEST_SUITE(FitnessMetricFactoryTest)
BOOST_FIXTURE_TEST_CASE(build_should_create_metric_of_the_right_type, FitnessMetricFactoryFixture)
{
unique_ptr<FitnessMetric> metric = FitnessMetricFactory::build(m_options, m_program);
m_options.metric = MetricChoice::RelativeCodeSize;
m_options.metricAggregator = MetricAggregatorChoice::Sum;
unique_ptr<FitnessMetric> metric = FitnessMetricFactory::build(m_options, {m_programs[0]});
BOOST_REQUIRE(metric != nullptr);
auto programSizeMetric = dynamic_cast<ProgramSize*>(metric.get());
BOOST_REQUIRE(programSizeMetric != nullptr);
BOOST_TEST(toString(programSizeMetric->program()) == toString(m_program));
auto sumMetric = dynamic_cast<FitnessMetricSum*>(metric.get());
BOOST_REQUIRE(sumMetric != nullptr);
BOOST_REQUIRE(sumMetric->metrics().size() == 1);
BOOST_REQUIRE(sumMetric->metrics()[0] != nullptr);
auto relativeProgramSizeMetric = dynamic_cast<RelativeProgramSize*>(sumMetric->metrics()[0].get());
BOOST_REQUIRE(relativeProgramSizeMetric != nullptr);
BOOST_TEST(toString(relativeProgramSizeMetric->program()) == toString(m_programs[0]));
}
BOOST_FIXTURE_TEST_CASE(build_should_respect_chromosome_repetitions_option, FitnessMetricFactoryFixture)
{
m_options.metric = MetricChoice::CodeSize;
m_options.metricAggregator = MetricAggregatorChoice::Average;
m_options.chromosomeRepetitions = 5;
unique_ptr<FitnessMetric> metric = FitnessMetricFactory::build(m_options, m_program);
unique_ptr<FitnessMetric> metric = FitnessMetricFactory::build(m_options, {m_programs[0]});
BOOST_REQUIRE(metric != nullptr);
auto programSizeMetric = dynamic_cast<ProgramSize*>(metric.get());
auto averageMetric = dynamic_cast<FitnessMetricAverage*>(metric.get());
BOOST_REQUIRE(averageMetric != nullptr);
BOOST_REQUIRE(averageMetric->metrics().size() == 1);
BOOST_REQUIRE(averageMetric->metrics()[0] != nullptr);
auto programSizeMetric = dynamic_cast<ProgramSize*>(averageMetric->metrics()[0].get());
BOOST_REQUIRE(programSizeMetric != nullptr);
BOOST_TEST(programSizeMetric->repetitionCount() == m_options.chromosomeRepetitions);
}
BOOST_FIXTURE_TEST_CASE(build_should_set_relative_metric_scale, FitnessMetricFactoryFixture)
{
m_options.metric = MetricChoice::RelativeCodeSize;
m_options.metricAggregator = MetricAggregatorChoice::Average;
m_options.relativeMetricScale = 10;
unique_ptr<FitnessMetric> metric = FitnessMetricFactory::build(m_options, {m_programs[0]});
BOOST_REQUIRE(metric != nullptr);
auto averageMetric = dynamic_cast<FitnessMetricAverage*>(metric.get());
BOOST_REQUIRE(averageMetric != nullptr);
BOOST_REQUIRE(averageMetric->metrics().size() == 1);
BOOST_REQUIRE(averageMetric->metrics()[0] != nullptr);
auto relativeProgramSizeMetric = dynamic_cast<RelativeProgramSize*>(averageMetric->metrics()[0].get());
BOOST_REQUIRE(relativeProgramSizeMetric != nullptr);
BOOST_TEST(relativeProgramSizeMetric->fixedPointPrecision() == m_options.relativeMetricScale);
}
BOOST_FIXTURE_TEST_CASE(build_should_create_metric_for_each_input_program, FitnessMetricFactoryFixture)
{
unique_ptr<FitnessMetric> metric = FitnessMetricFactory::build(m_options, m_programs);
BOOST_REQUIRE(metric != nullptr);
auto combinedMetric = dynamic_cast<FitnessMetricCombination*>(metric.get());
BOOST_REQUIRE(combinedMetric != nullptr);
BOOST_REQUIRE(combinedMetric->metrics().size() == m_programs.size());
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(PopulationFactoryTest)
@ -267,20 +320,30 @@ BOOST_FIXTURE_TEST_CASE(build_should_combine_populations_from_all_sources, Poula
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(ProgramFactoryTest)
BOOST_AUTO_TEST_CASE(build_should_load_program_from_file)
BOOST_AUTO_TEST_CASE(build_should_load_programs_from_files)
{
TemporaryDirectory tempDir;
vector<string> sources{"{}", "{{}}", "{{{}}}"};
ProgramFactory::Options options{/* inputFiles = */ {
tempDir.memberPath("program1.yul"),
tempDir.memberPath("program2.yul"),
tempDir.memberPath("program3.yul"),
}};
for (size_t i = 0; i < sources.size(); ++i)
{
ofstream tmpFile(tempDir.memberPath("program.yul"));
tmpFile << "{}" << endl;
ofstream tmpFile(options.inputFiles[i]);
tmpFile << sources[i] << endl;
}
ProgramFactory::Options options{/* inputFile = */ tempDir.memberPath("program.yul")};
CharStream expectedProgramSource("{}", "");
vector<Program> programs = ProgramFactory::build(options);
auto program = ProgramFactory::build(options);
BOOST_TEST(toString(program) == toString(get<Program>(Program::load(expectedProgramSource))));
BOOST_TEST(programs.size() == sources.size());
for (size_t i = 0; i < sources.size(); ++i)
{
CharStream sourceStream(sources[i], options.inputFiles[i]);
BOOST_TEST(toString(programs[i]) == toString(get<Program>(Program::load(sourceStream))));
}
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -51,7 +51,7 @@ class ChromosomeLengthMetric: public FitnessMetric
{
public:
using FitnessMetric::FitnessMetric;
size_t evaluate(Chromosome const& _chromosome) const override { return _chromosome.length(); }
size_t evaluate(Chromosome const& _chromosome) override { return _chromosome.length(); }
};
// MUTATIONS

View File

@ -17,14 +17,80 @@
#include <tools/yulPhaser/FitnessMetrics.h>
#include <cmath>
using namespace std;
using namespace solidity::phaser;
size_t ProgramSize::evaluate(Chromosome const& _chromosome) const
Program ProgramBasedMetric::optimisedProgram(Chromosome const& _chromosome) const
{
Program programCopy = m_program;
for (size_t i = 0; i < m_repetitionCount; ++i)
programCopy.optimise(_chromosome.optimisationSteps());
return programCopy.codeSize();
return programCopy;
}
size_t ProgramSize::evaluate(Chromosome const& _chromosome)
{
return optimisedProgram(_chromosome).codeSize();
}
size_t RelativeProgramSize::evaluate(Chromosome const& _chromosome)
{
size_t const scalingFactor = pow(10, m_fixedPointPrecision);
size_t unoptimisedSize = optimisedProgram(Chromosome("")).codeSize();
if (unoptimisedSize == 0)
return scalingFactor;
size_t optimisedSize = optimisedProgram(_chromosome).codeSize();
return static_cast<size_t>(round(
static_cast<double>(optimisedSize) / unoptimisedSize * scalingFactor
));
}
size_t FitnessMetricAverage::evaluate(Chromosome const& _chromosome)
{
assert(m_metrics.size() > 0);
size_t total = m_metrics[0]->evaluate(_chromosome);
for (size_t i = 1; i < m_metrics.size(); ++i)
total += m_metrics[i]->evaluate(_chromosome);
return total / m_metrics.size();
}
size_t FitnessMetricSum::evaluate(Chromosome const& _chromosome)
{
assert(m_metrics.size() > 0);
size_t total = m_metrics[0]->evaluate(_chromosome);
for (size_t i = 1; i < m_metrics.size(); ++i)
total += m_metrics[i]->evaluate(_chromosome);
return total;
}
size_t FitnessMetricMaximum::evaluate(Chromosome const& _chromosome)
{
assert(m_metrics.size() > 0);
size_t maximum = m_metrics[0]->evaluate(_chromosome);
for (size_t i = 1; i < m_metrics.size(); ++i)
maximum = max(maximum, m_metrics[i]->evaluate(_chromosome));
return maximum;
}
size_t FitnessMetricMinimum::evaluate(Chromosome const& _chromosome)
{
assert(m_metrics.size() > 0);
size_t minimum = m_metrics[0]->evaluate(_chromosome);
for (size_t i = 1; i < m_metrics.size(); ++i)
minimum = min(minimum, m_metrics[i]->evaluate(_chromosome));
return minimum;
}

View File

@ -43,28 +43,130 @@ public:
FitnessMetric& operator=(FitnessMetric const&) = delete;
virtual ~FitnessMetric() = default;
virtual size_t evaluate(Chromosome const& _chromosome) const = 0;
virtual size_t evaluate(Chromosome const& _chromosome) = 0;
};
/**
* Fitness metric based on the size of a specific program after applying the optimisations from the
* chromosome to it.
* Abstract base class for fitness metrics that return values based on program size.
*
* The class provides utilities for optimising programs according to the information stored in
* chromosomes.
*
* It can also store weights for the @a CodeSize metric. It does not do anything with
* them because it does not actually compute the code size but they are readily available for use
* by derived classes.
*/
class ProgramSize: public FitnessMetric
class ProgramBasedMetric: public FitnessMetric
{
public:
explicit ProgramSize(Program _program, size_t _repetitionCount = 1):
explicit ProgramBasedMetric(
Program _program,
size_t _repetitionCount = 1
):
m_program(std::move(_program)),
m_repetitionCount(_repetitionCount) {}
Program const& program() const { return m_program; }
size_t repetitionCount() const { return m_repetitionCount; }
size_t evaluate(Chromosome const& _chromosome) const override;
Program optimisedProgram(Chromosome const& _chromosome) const;
private:
Program m_program;
size_t m_repetitionCount;
};
/**
* Fitness metric based on the size of a specific program after applying the optimisations from the
* chromosome to it.
*/
class ProgramSize: public ProgramBasedMetric
{
public:
using ProgramBasedMetric::ProgramBasedMetric;
size_t evaluate(Chromosome const& _chromosome) override;
};
/**
* Fitness metric based on the size of a specific program after applying the optimisations from the
* chromosome to it in relation to the original, unoptimised program.
*
* Since metric values are integers, the class multiplies the ratio by 10^@a _fixedPointPrecision
* before rounding it.
*/
class RelativeProgramSize: public ProgramBasedMetric
{
public:
explicit RelativeProgramSize(
Program _program,
size_t _fixedPointPrecision,
size_t _repetitionCount = 1
):
ProgramBasedMetric(std::move(_program), _repetitionCount),
m_fixedPointPrecision(_fixedPointPrecision) {}
size_t fixedPointPrecision() const { return m_fixedPointPrecision; }
size_t evaluate(Chromosome const& _chromosome) override;
private:
size_t m_fixedPointPrecision;
};
/**
* Abstract base class for fitness metrics that compute their value based on values of multiple
* other, nested metrics.
*/
class FitnessMetricCombination: public FitnessMetric
{
public:
explicit FitnessMetricCombination(std::vector<std::shared_ptr<FitnessMetric>> _metrics):
m_metrics(std::move(_metrics)) {}
std::vector<std::shared_ptr<FitnessMetric>> const& metrics() const { return m_metrics; }
protected:
std::vector<std::shared_ptr<FitnessMetric>> m_metrics;
};
/**
* Fitness metric that returns the average of values of its nested metrics.
*/
class FitnessMetricAverage: public FitnessMetricCombination
{
public:
using FitnessMetricCombination::FitnessMetricCombination;
size_t evaluate(Chromosome const& _chromosome) override;
};
/**
* Fitness metric that returns the sum of values of its nested metrics.
*/
class FitnessMetricSum: public FitnessMetricCombination
{
public:
using FitnessMetricCombination::FitnessMetricCombination;
size_t evaluate(Chromosome const& _chromosome) override;
};
/**
* Fitness metric that returns the highest of values of its nested metrics.
*/
class FitnessMetricMaximum: public FitnessMetricCombination
{
public:
using FitnessMetricCombination::FitnessMetricCombination;
size_t evaluate(Chromosome const& _chromosome) override;
};
/**
* Fitness metric that returns the lowest of values of its nested metrics.
*/
class FitnessMetricMinimum: public FitnessMetricCombination
{
public:
using FitnessMetricCombination::FitnessMetricCombination;
size_t evaluate(Chromosome const& _chromosome) override;
};
}

View File

@ -53,10 +53,30 @@ map<Algorithm, string> const AlgorithmToStringMap =
};
map<string, Algorithm> const StringToAlgorithmMap = invertMap(AlgorithmToStringMap);
map<MetricChoice, string> MetricChoiceToStringMap =
{
{MetricChoice::CodeSize, "code-size"},
{MetricChoice::RelativeCodeSize, "relative-code-size"},
};
map<string, MetricChoice> const StringToMetricChoiceMap = invertMap(MetricChoiceToStringMap);
map<MetricAggregatorChoice, string> const MetricAggregatorChoiceToStringMap =
{
{MetricAggregatorChoice::Average, "average"},
{MetricAggregatorChoice::Sum, "sum"},
{MetricAggregatorChoice::Maximum, "maximum"},
{MetricAggregatorChoice::Minimum, "minimum"},
};
map<string, MetricAggregatorChoice> const StringToMetricAggregatorChoiceMap = invertMap(MetricAggregatorChoiceToStringMap);
}
istream& phaser::operator>>(istream& _inputStream, Algorithm& _algorithm) { return deserializeChoice(_inputStream, _algorithm, StringToAlgorithmMap); }
ostream& phaser::operator<<(ostream& _outputStream, Algorithm _algorithm) { return serializeChoice(_outputStream, _algorithm, AlgorithmToStringMap); }
istream& phaser::operator>>(istream& _inputStream, MetricChoice& _metric) { return deserializeChoice(_inputStream, _metric, StringToMetricChoiceMap); }
ostream& phaser::operator<<(ostream& _outputStream, MetricChoice _metric) { return serializeChoice(_outputStream, _metric, MetricChoiceToStringMap); }
istream& phaser::operator>>(istream& _inputStream, MetricAggregatorChoice& _aggregator) { return deserializeChoice(_inputStream, _aggregator, StringToMetricAggregatorChoiceMap); }
ostream& phaser::operator<<(ostream& _outputStream, MetricAggregatorChoice _aggregator) { return serializeChoice(_outputStream, _aggregator, MetricAggregatorChoiceToStringMap); }
GeneticAlgorithmFactory::Options GeneticAlgorithmFactory::Options::fromCommandLine(po::variables_map const& _arguments)
{
@ -129,16 +149,60 @@ unique_ptr<GeneticAlgorithm> GeneticAlgorithmFactory::build(
FitnessMetricFactory::Options FitnessMetricFactory::Options::fromCommandLine(po::variables_map const& _arguments)
{
return {
_arguments["metric"].as<MetricChoice>(),
_arguments["metric-aggregator"].as<MetricAggregatorChoice>(),
_arguments["relative-metric-scale"].as<size_t>(),
_arguments["chromosome-repetitions"].as<size_t>(),
};
}
unique_ptr<FitnessMetric> FitnessMetricFactory::build(
Options const& _options,
Program _program
vector<Program> _programs
)
{
return make_unique<ProgramSize>(move(_program), _options.chromosomeRepetitions);
assert(_programs.size() > 0 && "Validations should prevent this from being executed with zero files.");
vector<shared_ptr<FitnessMetric>> metrics;
switch (_options.metric)
{
case MetricChoice::CodeSize:
{
for (Program& program: _programs)
metrics.push_back(make_unique<ProgramSize>(
move(program),
_options.chromosomeRepetitions
));
break;
}
case MetricChoice::RelativeCodeSize:
{
for (Program& program: _programs)
metrics.push_back(make_unique<RelativeProgramSize>(
move(program),
_options.relativeMetricScale,
_options.chromosomeRepetitions
));
break;
}
default:
assertThrow(false, solidity::util::Exception, "Invalid MetricChoice value.");
}
switch (_options.metricAggregator)
{
case MetricAggregatorChoice::Average:
return make_unique<FitnessMetricAverage>(move(metrics));
case MetricAggregatorChoice::Sum:
return make_unique<FitnessMetricSum>(move(metrics));
case MetricAggregatorChoice::Maximum:
return make_unique<FitnessMetricMaximum>(move(metrics));
case MetricAggregatorChoice::Minimum:
return make_unique<FitnessMetricMinimum>(move(metrics));
default:
assertThrow(false, solidity::util::Exception, "Invalid MetricAggregatorChoice value.");
}
}
PopulationFactory::Options PopulationFactory::Options::fromCommandLine(po::variables_map const& _arguments)
@ -220,20 +284,26 @@ Population PopulationFactory::buildFromFile(
ProgramFactory::Options ProgramFactory::Options::fromCommandLine(po::variables_map const& _arguments)
{
return {
_arguments["input-file"].as<string>(),
_arguments["input-files"].as<vector<string>>(),
};
}
Program ProgramFactory::build(Options const& _options)
vector<Program> ProgramFactory::build(Options const& _options)
{
CharStream sourceCode = loadSource(_options.inputFile);
variant<Program, ErrorList> programOrErrors = Program::load(sourceCode);
if (holds_alternative<ErrorList>(programOrErrors))
vector<Program> inputPrograms;
for (auto& path: _options.inputFiles)
{
cerr << get<ErrorList>(programOrErrors) << endl;
assertThrow(false, InvalidProgram, "Failed to load program " + _options.inputFile);
CharStream sourceCode = loadSource(path);
variant<Program, ErrorList> programOrErrors = Program::load(sourceCode);
if (holds_alternative<ErrorList>(programOrErrors))
{
cerr << get<ErrorList>(programOrErrors) << endl;
assertThrow(false, InvalidProgram, "Failed to load program " + path);
}
inputPrograms.push_back(move(get<Program>(programOrErrors)));
}
return move(get<Program>(programOrErrors));
return inputPrograms;
}
CharStream ProgramFactory::loadSource(string const& _sourcePath)
@ -277,7 +347,7 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription()
po::options_description generalDescription("GENERAL", lineLength, minDescriptionLength);
generalDescription.add_options()
("help", "Show help message and exit.")
("input-file", po::value<string>()->required()->value_name("<PATH>"), "Input file.")
("input-files", po::value<vector<string>>()->required()->value_name("<PATH>"), "Input files.")
("seed", po::value<uint32_t>()->value_name("<NUM>"), "Seed for the random number generator.")
(
"rounds",
@ -391,6 +461,29 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription()
po::options_description metricsDescription("METRICS", lineLength, minDescriptionLength);
metricsDescription.add_options()
(
"metric",
po::value<MetricChoice>()->value_name("<NAME>")->default_value(MetricChoice::RelativeCodeSize),
"Metric used to evaluate the fitness of a chromosome."
)
(
"metric-aggregator",
po::value<MetricAggregatorChoice>()->value_name("<NAME>")->default_value(MetricAggregatorChoice::Average),
"Operator used to combine multiple fitness metric obtained by evaluating a chromosome "
"separately for each input program."
)
(
"relative-metric-scale",
po::value<size_t>()->value_name("<EXPONENT>")->default_value(3),
"Scaling factor for values produced by relative fitness metrics. \n"
"Since all metrics must produce integer values, the fractional part of the result is discarded. "
"To keep the numbers meaningful, a relative metric multiples its values by a scaling factor "
"and this option specifies the exponent of this factor. "
"For example with value of 3 the factor is 10^3 = 1000 and the metric will return "
"500 to represent 0.5, 1000 for 1.0, 2000 for 2.0 and so on. "
"Using a bigger factor allows discerning smaller relative differences between chromosomes "
"but makes the numbers less readable and may also lose precision if the numbers are very large."
)
(
"chromosome-repetitions",
po::value<size_t>()->value_name("<COUNT>")->default_value(1),
@ -400,7 +493,7 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription()
keywordDescription.add(metricsDescription);
po::positional_options_description positionalDescription;
positionalDescription.add("input-file", 1);
positionalDescription.add("input-files", -1);
return {keywordDescription, positionalDescription};
}
@ -422,8 +515,8 @@ optional<po::variables_map> Phaser::parseCommandLine(int _argc, char** _argv)
return nullopt;
}
if (arguments.count("input-file") == 0)
assertThrow(false, NoInputFiles, "Missing argument: input-file.");
if (arguments.count("input-files") == 0)
assertThrow(false, NoInputFiles, "Missing argument: input-files.");
return arguments;
}
@ -458,8 +551,8 @@ void Phaser::runAlgorithm(po::variables_map const& _arguments)
auto populationOptions = PopulationFactory::Options::fromCommandLine(_arguments);
auto algorithmOptions = GeneticAlgorithmFactory::Options::fromCommandLine(_arguments);
Program program = ProgramFactory::build(programOptions);
unique_ptr<FitnessMetric> fitnessMetric = FitnessMetricFactory::build(metricOptions, move(program));
vector<Program> programs = ProgramFactory::build(programOptions);
unique_ptr<FitnessMetric> fitnessMetric = FitnessMetricFactory::build(metricOptions, move(programs));
Population population = PopulationFactory::build(populationOptions, move(fitnessMetric));
unique_ptr<GeneticAlgorithm> geneticAlgorithm = GeneticAlgorithmFactory::build(

View File

@ -52,8 +52,26 @@ enum class Algorithm
GEWEP,
};
enum class MetricChoice
{
CodeSize,
RelativeCodeSize,
};
enum class MetricAggregatorChoice
{
Average,
Sum,
Maximum,
Minimum,
};
std::istream& operator>>(std::istream& _inputStream, solidity::phaser::Algorithm& _algorithm);
std::ostream& operator<<(std::ostream& _outputStream, solidity::phaser::Algorithm _algorithm);
std::istream& operator>>(std::istream& _inputStream, solidity::phaser::MetricChoice& _metric);
std::ostream& operator<<(std::ostream& _outputStream, solidity::phaser::MetricChoice _metric);
std::istream& operator>>(std::istream& _inputStream, solidity::phaser::MetricAggregatorChoice& _aggregator);
std::ostream& operator<<(std::ostream& _outputStream, solidity::phaser::MetricAggregatorChoice _aggregator);
/**
* Builds and validates instances of @a GeneticAlgorithm and its derived classes.
@ -91,6 +109,9 @@ class FitnessMetricFactory
public:
struct Options
{
MetricChoice metric;
MetricAggregatorChoice metricAggregator;
size_t relativeMetricScale;
size_t chromosomeRepetitions;
static Options fromCommandLine(boost::program_options::variables_map const& _arguments);
@ -98,7 +119,7 @@ public:
static std::unique_ptr<FitnessMetric> build(
Options const& _options,
Program _program
std::vector<Program> _programs
);
};
@ -147,12 +168,12 @@ class ProgramFactory
public:
struct Options
{
std::string inputFile;
std::vector<std::string> inputFiles;
static Options fromCommandLine(boost::program_options::variables_map const& _arguments);
};
static Program build(Options const& _options);
static std::vector<Program> build(Options const& _options);
private:
static langutil::CharStream loadSource(std::string const& _sourcePath);

View File

@ -59,7 +59,7 @@ bool phaser::isFitter(Individual const& a, Individual const& b)
}
Population Population::makeRandom(
shared_ptr<FitnessMetric const> _fitnessMetric,
shared_ptr<FitnessMetric> _fitnessMetric,
size_t _size,
function<size_t()> _chromosomeLengthGenerator
)
@ -72,7 +72,7 @@ Population Population::makeRandom(
}
Population Population::makeRandom(
shared_ptr<FitnessMetric const> _fitnessMetric,
shared_ptr<FitnessMetric> _fitnessMetric,
size_t _size,
size_t _minChromosomeLength,
size_t _maxChromosomeLength
@ -145,7 +145,7 @@ ostream& phaser::operator<<(ostream& _stream, Population const& _population)
}
vector<Individual> Population::chromosomesToIndividuals(
FitnessMetric const& _fitnessMetric,
FitnessMetric& _fitnessMetric,
vector<Chromosome> _chromosomes
)
{

View File

@ -55,7 +55,7 @@ struct Individual
Individual(Chromosome _chromosome, size_t _fitness):
chromosome(std::move(_chromosome)),
fitness(_fitness) {}
Individual(Chromosome _chromosome, FitnessMetric const& _fitnessMetric):
Individual(Chromosome _chromosome, FitnessMetric& _fitnessMetric):
chromosome(std::move(_chromosome)),
fitness(_fitnessMetric.evaluate(chromosome)) {}
@ -85,7 +85,7 @@ class Population
{
public:
explicit Population(
std::shared_ptr<FitnessMetric const> _fitnessMetric,
std::shared_ptr<FitnessMetric> _fitnessMetric,
std::vector<Chromosome> _chromosomes = {}
):
Population(
@ -94,12 +94,12 @@ public:
) {}
static Population makeRandom(
std::shared_ptr<FitnessMetric const> _fitnessMetric,
std::shared_ptr<FitnessMetric> _fitnessMetric,
size_t _size,
std::function<size_t()> _chromosomeLengthGenerator
);
static Population makeRandom(
std::shared_ptr<FitnessMetric const> _fitnessMetric,
std::shared_ptr<FitnessMetric> _fitnessMetric,
size_t _size,
size_t _minChromosomeLength,
size_t _maxChromosomeLength
@ -110,7 +110,7 @@ public:
Population crossover(PairSelection const& _selection, std::function<Crossover> _crossover) const;
friend Population (::operator+)(Population _a, Population _b);
std::shared_ptr<FitnessMetric const> fitnessMetric() const { return m_fitnessMetric; }
std::shared_ptr<FitnessMetric> fitnessMetric() { return m_fitnessMetric; }
std::vector<Individual> const& individuals() const { return m_individuals; }
static size_t uniformChromosomeLength(size_t _min, size_t _max) { return SimulationRNG::uniformInt(_min, _max); }
@ -122,17 +122,17 @@ public:
friend std::ostream& operator<<(std::ostream& _stream, Population const& _population);
private:
explicit Population(std::shared_ptr<FitnessMetric const> _fitnessMetric, std::vector<Individual> _individuals):
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 const& _fitnessMetric,
FitnessMetric& _fitnessMetric,
std::vector<Chromosome> _chromosomes
);
static std::vector<Individual> sortedIndividuals(std::vector<Individual> _individuals);
std::shared_ptr<FitnessMetric const> m_fitnessMetric;
std::shared_ptr<FitnessMetric> m_fitnessMetric;
std::vector<Individual> m_individuals;
};