From 63f13c5b18c6828b0f190491fb3643eb7fedfbe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Sun, 1 Mar 2020 14:34:23 +0100 Subject: [PATCH 01/15] [yul-phaser] Add --chromosome-repetitions option --- tools/yulPhaser/Phaser.cpp | 22 ++++++++++++++++++++-- tools/yulPhaser/Phaser.h | 8 +++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index 5f0e3d87b..f02e942f7 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -96,11 +96,19 @@ unique_ptr GeneticAlgorithmFactory::build( } } +FitnessMetricFactory::Options FitnessMetricFactory::Options::fromCommandLine(po::variables_map const& _arguments) +{ + return { + _arguments["chromosome-repetitions"].as(), + }; +} + unique_ptr FitnessMetricFactory::build( + Options const& _options, Program _program ) { - return make_unique(move(_program), RepetitionCount); + return make_unique(move(_program), _options.chromosomeRepetitions); } Population PopulationFactory::build( @@ -190,6 +198,15 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() ; keywordDescription.add(algorithmDescription); + po::options_description metricsDescription("METRICS", lineLength, minDescriptionLength); + metricsDescription.add_options() + ( + "chromosome-repetitions", + po::value()->value_name("")->default_value(1), + "Number of times to repeat the sequence optimisation steps represented by a chromosome." + ) + ; + keywordDescription.add(metricsDescription); po::positional_options_description positionalDescription; positionalDescription.add("input-file", 1); @@ -235,10 +252,11 @@ void Phaser::initialiseRNG(po::variables_map const& _arguments) void Phaser::runAlgorithm(po::variables_map const& _arguments) { auto programOptions = ProgramFactory::Options::fromCommandLine(_arguments); + auto metricOptions = FitnessMetricFactory::Options::fromCommandLine(_arguments); auto algorithmOptions = GeneticAlgorithmFactory::Options::fromCommandLine(_arguments); Program program = ProgramFactory::build(programOptions); - unique_ptr fitnessMetric = FitnessMetricFactory::build(move(program)); + unique_ptr fitnessMetric = FitnessMetricFactory::build(metricOptions, move(program)); Population population = PopulationFactory::build(move(fitnessMetric)); unique_ptr geneticAlgorithm = GeneticAlgorithmFactory::build( diff --git a/tools/yulPhaser/Phaser.h b/tools/yulPhaser/Phaser.h index 9ac6f81f0..20d9ace25 100644 --- a/tools/yulPhaser/Phaser.h +++ b/tools/yulPhaser/Phaser.h @@ -80,9 +80,15 @@ public: class FitnessMetricFactory { public: - static constexpr size_t RepetitionCount = 5; + struct Options + { + size_t chromosomeRepetitions; + + static Options fromCommandLine(boost::program_options::variables_map const& _arguments); + }; static std::unique_ptr build( + Options const& _options, Program _program ); }; From d8e5f8f9653e6b6ee196136a9f978f15e49d9329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 21 Feb 2020 20:01:00 +0100 Subject: [PATCH 02/15] [yul-phaser] Add --rounds option --- tools/yulPhaser/Phaser.cpp | 14 +++++++++++++- tools/yulPhaser/Phaser.h | 3 +++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index f02e942f7..3d45db1e8 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -185,6 +185,11 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() ("help", "Show help message and exit.") ("input-file", po::value()->required()->value_name(""), "Input file.") ("seed", po::value()->value_name(""), "Seed for the random number generator.") + ( + "rounds", + po::value()->value_name(""), + "The number of rounds after which the algorithm should stop. (default=no limit)." + ) ; keywordDescription.add(generalDescription); @@ -249,6 +254,13 @@ void Phaser::initialiseRNG(po::variables_map const& _arguments) cout << "Random seed: " << seed << endl; } +AlgorithmRunner::Options Phaser::buildAlgorithmRunnerOptions(po::variables_map const& _arguments) +{ + return { + _arguments.count("rounds") > 0 ? static_cast>(_arguments["rounds"].as()) : nullopt + }; +} + void Phaser::runAlgorithm(po::variables_map const& _arguments) { auto programOptions = ProgramFactory::Options::fromCommandLine(_arguments); @@ -266,6 +278,6 @@ void Phaser::runAlgorithm(po::variables_map const& _arguments) PopulationFactory::MaxChromosomeLength ); - AlgorithmRunner algorithmRunner(population, AlgorithmRunner::Options{}, cout); + AlgorithmRunner algorithmRunner(population, buildAlgorithmRunnerOptions(_arguments), cout); algorithmRunner.run(*geneticAlgorithm); } diff --git a/tools/yulPhaser/Phaser.h b/tools/yulPhaser/Phaser.h index 20d9ace25..ac6f70def 100644 --- a/tools/yulPhaser/Phaser.h +++ b/tools/yulPhaser/Phaser.h @@ -21,6 +21,8 @@ #pragma once +#include + #include #include @@ -147,6 +149,7 @@ private: static CommandLineDescription buildCommandLineDescription(); static std::optional parseCommandLine(int _argc, char** _argv); static void initialiseRNG(boost::program_options::variables_map const& _arguments); + static AlgorithmRunner::Options buildAlgorithmRunnerOptions(boost::program_options::variables_map const& _arguments); static void runAlgorithm(boost::program_options::variables_map const& _arguments); }; From af090876b508f6d2f55641a6e0ab1dc2546edf24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Sat, 22 Feb 2020 00:00:29 +0100 Subject: [PATCH 03/15] [yul-phaser] Add --random-population option --- tools/yulPhaser/Phaser.cpp | 45 ++++++++++++++++++++++++++++++++++++-- tools/yulPhaser/Phaser.h | 13 ++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index 3d45db1e8..768088e49 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -111,13 +111,43 @@ unique_ptr FitnessMetricFactory::build( return make_unique(move(_program), _options.chromosomeRepetitions); } +PopulationFactory::Options PopulationFactory::Options::fromCommandLine(po::variables_map const& _arguments) +{ + return { + _arguments.count("random-population") > 0 ? + _arguments["random-population"].as>() : + vector{}, + }; +} + Population PopulationFactory::build( + Options const& _options, + shared_ptr _fitnessMetric +) +{ + Population population(_fitnessMetric, vector{}); + + size_t combinedSize = 0; + for (size_t populationSize: _options.randomPopulation) + combinedSize += populationSize; + + population = move(population) + buildRandom( + combinedSize, + _fitnessMetric + ); + + return population; +} + + +Population PopulationFactory::buildRandom( + size_t _populationSize, shared_ptr _fitnessMetric ) { return Population::makeRandom( move(_fitnessMetric), - PopulationSize, + _populationSize, MinChromosomeLength, MaxChromosomeLength ); @@ -203,6 +233,16 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() ; keywordDescription.add(algorithmDescription); + po::options_description populationDescription("POPULATION", lineLength, minDescriptionLength); + populationDescription.add_options() + ( + "random-population", + po::value>()->value_name(""), + "The number of randomly generated chromosomes to be included in the initial population." + ) + ; + keywordDescription.add(populationDescription); + po::options_description metricsDescription("METRICS", lineLength, minDescriptionLength); metricsDescription.add_options() ( @@ -265,11 +305,12 @@ void Phaser::runAlgorithm(po::variables_map const& _arguments) { auto programOptions = ProgramFactory::Options::fromCommandLine(_arguments); auto metricOptions = FitnessMetricFactory::Options::fromCommandLine(_arguments); + auto populationOptions = PopulationFactory::Options::fromCommandLine(_arguments); auto algorithmOptions = GeneticAlgorithmFactory::Options::fromCommandLine(_arguments); Program program = ProgramFactory::build(programOptions); unique_ptr fitnessMetric = FitnessMetricFactory::build(metricOptions, move(program)); - Population population = PopulationFactory::build(move(fitnessMetric)); + Population population = PopulationFactory::build(populationOptions, move(fitnessMetric)); unique_ptr geneticAlgorithm = GeneticAlgorithmFactory::build( algorithmOptions, diff --git a/tools/yulPhaser/Phaser.h b/tools/yulPhaser/Phaser.h index ac6f70def..4df0109f7 100644 --- a/tools/yulPhaser/Phaser.h +++ b/tools/yulPhaser/Phaser.h @@ -101,11 +101,22 @@ public: class PopulationFactory { public: - static constexpr size_t PopulationSize = 20; static constexpr size_t MinChromosomeLength = 12; static constexpr size_t MaxChromosomeLength = 30; + struct Options + { + std::vector randomPopulation; + + static Options fromCommandLine(boost::program_options::variables_map const& _arguments); + }; + static Population build( + Options const& _options, + std::shared_ptr _fitnessMetric + ); + static Population buildRandom( + size_t _populationSize, std::shared_ptr _fitnessMetric ); }; From 5e00b57e02a435cb752502d11bc50b306aa97f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Sat, 22 Feb 2020 00:01:06 +0100 Subject: [PATCH 04/15] [yul-phaser] Add --population option --- tools/yulPhaser/Phaser.cpp | 23 ++++++++++++++++++++++- tools/yulPhaser/Phaser.h | 5 +++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index 768088e49..8c5fcbc26 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -114,6 +114,9 @@ unique_ptr FitnessMetricFactory::build( PopulationFactory::Options PopulationFactory::Options::fromCommandLine(po::variables_map const& _arguments) { return { + _arguments.count("population") > 0 ? + _arguments["population"].as>() : + vector{}, _arguments.count("random-population") > 0 ? _arguments["random-population"].as>() : vector{}, @@ -125,7 +128,7 @@ Population PopulationFactory::build( shared_ptr _fitnessMetric ) { - Population population(_fitnessMetric, vector{}); + Population population = buildFromStrings(_options.population, _fitnessMetric); size_t combinedSize = 0; for (size_t populationSize: _options.randomPopulation) @@ -139,6 +142,17 @@ Population PopulationFactory::build( return population; } +Population PopulationFactory::buildFromStrings( + vector const& _geneSequences, + shared_ptr _fitnessMetric +) +{ + vector chromosomes; + for (string const& geneSequence: _geneSequences) + chromosomes.emplace_back(geneSequence); + + return Population(move(_fitnessMetric), move(chromosomes)); +} Population PopulationFactory::buildRandom( size_t _populationSize, @@ -235,6 +249,13 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() po::options_description populationDescription("POPULATION", lineLength, minDescriptionLength); populationDescription.add_options() + ( + "population", + po::value>()->multitoken()->value_name(""), + "List of chromosomes to be included in the initial population. " + "You can specify multiple values separated with spaces or invoke the option multiple times " + "and all the values will be included." + ) ( "random-population", po::value>()->value_name(""), diff --git a/tools/yulPhaser/Phaser.h b/tools/yulPhaser/Phaser.h index 4df0109f7..a12ccd87a 100644 --- a/tools/yulPhaser/Phaser.h +++ b/tools/yulPhaser/Phaser.h @@ -106,6 +106,7 @@ public: struct Options { + std::vector population; std::vector randomPopulation; static Options fromCommandLine(boost::program_options::variables_map const& _arguments); @@ -115,6 +116,10 @@ public: Options const& _options, std::shared_ptr _fitnessMetric ); + static Population buildFromStrings( + std::vector const& _geneSequences, + std::shared_ptr _fitnessMetric + ); static Population buildRandom( size_t _populationSize, std::shared_ptr _fitnessMetric From 5e814acc3c632429d4523b4bf4a5c7d0a96367ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Wed, 4 Mar 2020 14:25:31 +0100 Subject: [PATCH 05/15] [yul-phaser] TestHelpers: Add TemporaryDirectory class --- test/yulPhaser/TestHelpers.cpp | 40 ++++++++++++++++++++ test/yulPhaser/TestHelpers.h | 25 ++++++++++++ test/yulPhaser/TestHelpersTest.cpp | 61 ++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+) diff --git a/test/yulPhaser/TestHelpers.cpp b/test/yulPhaser/TestHelpers.cpp index b52cee084..2379016fe 100644 --- a/test/yulPhaser/TestHelpers.cpp +++ b/test/yulPhaser/TestHelpers.cpp @@ -19,12 +19,18 @@ #include +#include + #include +#include using namespace std; using namespace solidity; using namespace solidity::yul; using namespace solidity::phaser; +using namespace solidity::phaser::test; + +namespace fs = boost::filesystem; function phaser::test::wholeChromosomeReplacement(Chromosome _newChromosome) { @@ -71,6 +77,40 @@ size_t phaser::test::countDifferences(Chromosome const& _chromosome1, Chromosome return count + abs(static_cast(_chromosome1.length() - _chromosome2.length())); } +TemporaryDirectory::TemporaryDirectory(std::string const& _prefix): + m_path((fs::temp_directory_path() / fs::unique_path(_prefix + "%%%%-%%%%-%%%%-%%%%")).string()) +{ + // Prefix should just be a file name and not contain anything that would make us step out of /tmp. + assert(fs::path(_prefix) == fs::path(_prefix).stem()); + + fs::create_directory(m_path); +} + +TemporaryDirectory::~TemporaryDirectory() +{ + // A few paranoid sanity checks just to be extra sure we're not deleting someone's homework. + assert(m_path.find(fs::temp_directory_path().string()) == 0); + assert(fs::path(m_path) != fs::temp_directory_path()); + assert(fs::path(m_path) != fs::path(m_path).root_path()); + assert(!fs::path(m_path).empty()); + + boost::system::error_code errorCode; + uintmax_t numRemoved = fs::remove_all(m_path, errorCode); + if (errorCode.value() != boost::system::errc::success) + { + cerr << "Failed to completely remove temporary directory '" << m_path << "'. "; + cerr << "Only " << numRemoved << " files were actually removed." << endl; + cerr << "Reason: " << errorCode.message() << endl; + } +} + +string TemporaryDirectory::memberPath(string const& _relativePath) const +{ + assert(fs::path(_relativePath).is_relative()); + + return (fs::path(m_path) / _relativePath).string(); +} + string phaser::test::stripWhitespace(string const& input) { regex whitespaceRegex("\\s+"); diff --git a/test/yulPhaser/TestHelpers.h b/test/yulPhaser/TestHelpers.h index a59c7eed1..aa76ae6da 100644 --- a/test/yulPhaser/TestHelpers.h +++ b/test/yulPhaser/TestHelpers.h @@ -79,6 +79,31 @@ size_t countDifferences(Chromosome const& _chromosome1, Chromosome const& _chrom /// integers. std::map enumerateOptmisationSteps(); +// FILESYSTEM UTILITIES + +/** + * An object that creates a unique temporary directory and automatically deletes it and its + * content upon being destroyed. + * + * The directory is guaranteed to be newly created and empty. Directory names are generated + * randomly. If a directory with the same name already exists (very unlikely but possible) the + * object won't reuse it and will fail with an exception instead. + */ +class TemporaryDirectory +{ +public: + TemporaryDirectory(std::string const& _prefix = "yul-phaser-test-"); + ~TemporaryDirectory(); + + std::string const& path() const { return m_path; } + + /// Converts a path relative to the directory held by the object into an absolute one. + std::string memberPath(std::string const& _relativePath) const; + +private: + std::string m_path; +}; + // STRING UTILITIES /// Returns the input string with all the whitespace characters (spaces, line endings, etc.) removed. diff --git a/test/yulPhaser/TestHelpersTest.cpp b/test/yulPhaser/TestHelpersTest.cpp index 98d609050..7fc4ef0af 100644 --- a/test/yulPhaser/TestHelpersTest.cpp +++ b/test/yulPhaser/TestHelpersTest.cpp @@ -19,14 +19,18 @@ #include +#include #include +#include #include using namespace std; using namespace solidity::yul; using namespace boost::test_tools; +namespace fs = boost::filesystem; + namespace solidity::phaser::test { @@ -114,6 +118,63 @@ BOOST_AUTO_TEST_CASE(enumerateOptimisationSteps_should_assing_indices_to_all_ava } } +BOOST_AUTO_TEST_CASE(TemporaryDirectory_should_create_and_delete_a_unique_and_empty_directory) +{ + fs::path dirPath; + { + TemporaryDirectory tempDir("temporary-directory-test-"); + dirPath = tempDir.path(); + + BOOST_TEST(dirPath.stem().string().find("temporary-directory-test-") == 0); + BOOST_TEST(fs::equivalent(dirPath.parent_path(), fs::temp_directory_path())); + BOOST_TEST(fs::is_directory(dirPath)); + BOOST_TEST(fs::is_empty(dirPath)); + } + BOOST_TEST(!fs::exists(dirPath)); +} + +BOOST_AUTO_TEST_CASE(TemporaryDirectory_should_delete_its_directory_even_if_not_empty) +{ + fs::path dirPath; + { + TemporaryDirectory tempDir("temporary-directory-test-"); + dirPath = tempDir.path(); + + BOOST_TEST(fs::is_directory(dirPath)); + + { + ofstream tmpFile((dirPath / "test-file.txt").string()); + tmpFile << "Delete me!" << endl; + } + assert(fs::is_regular_file(dirPath / "test-file.txt")); + } + BOOST_TEST(!fs::exists(dirPath / "test-file.txt")); +} + +BOOST_AUTO_TEST_CASE(TemporaryDirectory_memberPath_should_construct_paths_relative_to_the_temporary_directory) +{ + TemporaryDirectory tempDir("temporary-directory-test-"); + + BOOST_TEST(fs::equivalent(tempDir.memberPath(""), tempDir.path())); + BOOST_TEST(fs::equivalent(tempDir.memberPath("."), tempDir.path() / fs::path("."))); + BOOST_TEST(fs::equivalent(tempDir.memberPath(".."), tempDir.path() / fs::path(".."))); + + // NOTE: fs::equivalent() only works with paths that actually exist + { + ofstream file; + file.open(tempDir.memberPath("file.txt"), ios::out); + } + BOOST_TEST(fs::equivalent(tempDir.memberPath("file.txt"), tempDir.path() / fs::path("file.txt"))); + + { + fs::create_directories(tempDir.memberPath("a/b/")); + + ofstream file; + file.open(tempDir.memberPath("a/b/file.txt"), ios::out); + } + BOOST_TEST(fs::equivalent(tempDir.memberPath("a/b/file.txt"), tempDir.path() / fs::path("a") / fs::path("b") / fs::path("file.txt"))); +} + BOOST_AUTO_TEST_CASE(stripWhitespace_should_remove_all_whitespace_characters_from_a_string) { BOOST_TEST(stripWhitespace("") == ""); From ff99d25bc36309d9931f2b7c938c199bba51fa60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Sat, 22 Feb 2020 00:48:35 +0100 Subject: [PATCH 06/15] [yul-phaser] Common: Add readLinesFromFile() --- test/CMakeLists.txt | 1 + test/yulPhaser/Common.cpp | 20 ++++++++++++++++ tools/CMakeLists.txt | 1 + tools/yulPhaser/Common.cpp | 45 ++++++++++++++++++++++++++++++++++++ tools/yulPhaser/Common.h | 9 ++++++++ tools/yulPhaser/Exceptions.h | 3 +++ 6 files changed, 79 insertions(+) create mode 100644 tools/yulPhaser/Common.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fa9127e72..d5bf4b686 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -158,6 +158,7 @@ set(yul_phaser_sources # My current workaround is just to include its source files here but this introduces # unnecessary duplication. Create a library or find a way to reuse the list in both places. ../tools/yulPhaser/AlgorithmRunner.cpp + ../tools/yulPhaser/Common.cpp ../tools/yulPhaser/Chromosome.cpp ../tools/yulPhaser/FitnessMetrics.cpp ../tools/yulPhaser/GeneticAlgorithms.cpp diff --git a/test/yulPhaser/Common.cpp b/test/yulPhaser/Common.cpp index 891495193..39e6dd784 100644 --- a/test/yulPhaser/Common.cpp +++ b/test/yulPhaser/Common.cpp @@ -15,6 +15,8 @@ along with solidity. If not, see . */ +#include + #include #include @@ -22,6 +24,7 @@ #include #include +#include #include #include @@ -32,6 +35,12 @@ using namespace solidity::util; namespace solidity::phaser::test { +class ReadLinesFromFileFixture +{ +protected: + TemporaryDirectory m_tempDir; +}; + namespace { @@ -60,6 +69,17 @@ map const StringToTestEnumMap = invertMap(TestEnumToStringMap) BOOST_AUTO_TEST_SUITE(Phaser) BOOST_AUTO_TEST_SUITE(CommonTest) +BOOST_FIXTURE_TEST_CASE(readLinesFromFile_should_return_all_lines_from_a_text_file_as_strings_without_newlines, ReadLinesFromFileFixture) +{ + { + ofstream tmpFile(m_tempDir.memberPath("test-file.txt")); + tmpFile << endl << "Line 1" << endl << endl << endl << "Line 2" << endl << "#" << endl << endl; + } + + vector lines = readLinesFromFile(m_tempDir.memberPath("test-file.txt")); + BOOST_TEST((lines == vector{"", "Line 1", "", "", "Line 2", "#", ""})); +} + BOOST_AUTO_TEST_CASE(deserializeChoice_should_convert_string_to_enum) { istringstream aStream("a"); diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index fb2d411b5..fd5f92bbe 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -16,6 +16,7 @@ install(TARGETS solidity-upgrade DESTINATION "${CMAKE_INSTALL_BINDIR}") add_executable(yul-phaser yulPhaser/main.cpp yulPhaser/Common.h + yulPhaser/Common.cpp yulPhaser/AlgorithmRunner.h yulPhaser/AlgorithmRunner.cpp yulPhaser/Phaser.h diff --git a/tools/yulPhaser/Common.cpp b/tools/yulPhaser/Common.cpp new file mode 100644 index 000000000..aa7ba85b5 --- /dev/null +++ b/tools/yulPhaser/Common.cpp @@ -0,0 +1,45 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +#include + +#include + +#include +#include +#include + +using namespace std; +using namespace solidity; +using namespace solidity::phaser; + +vector phaser::readLinesFromFile(string const& _path) +{ + ifstream inputStream(_path); + assertThrow(inputStream.is_open(), FileOpenError, "Could not open file '" + _path + "': " + strerror(errno)); + + string line; + vector lines; + while (!getline(inputStream, line).fail()) + lines.push_back(line); + + assertThrow(!inputStream.bad(), FileReadError, "Error while reading from file '" + _path + "': " + strerror(errno)); + + return lines; +} diff --git a/tools/yulPhaser/Common.h b/tools/yulPhaser/Common.h index 9bf3f6204..c37afa2fb 100644 --- a/tools/yulPhaser/Common.h +++ b/tools/yulPhaser/Common.h @@ -22,10 +22,19 @@ #include #include +#include +#include namespace solidity::phaser { +/// Loads the whole file into memory, splits the content into lines, strips newlines and +/// returns the result as a list of strings. +/// +/// Throws FileOpenError if the file does not exist or cannot be opened for reading. +/// Throws FileReadError if any read operation fails during the whole process. +std::vector readLinesFromFile(std::string const& _path); + /// Reads a token from the input stream and translates it to a string using a map. /// Sets the failbit in the stream if there's no matching value in the map. template diff --git a/tools/yulPhaser/Exceptions.h b/tools/yulPhaser/Exceptions.h index 1f675a9ef..e3a06fbed 100644 --- a/tools/yulPhaser/Exceptions.h +++ b/tools/yulPhaser/Exceptions.h @@ -27,4 +27,7 @@ struct InvalidProgram: virtual BadInput {}; struct NoInputFiles: virtual BadInput {}; struct MissingFile: virtual BadInput {}; +struct FileOpenError: virtual util::Exception {}; +struct FileReadError: virtual util::Exception {}; + } From 04c7c56d84a24c0c6b056c17ef5f029e8b044958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Sat, 22 Feb 2020 00:49:06 +0100 Subject: [PATCH 07/15] [yul-phaser] Add --population-from-file option --- tools/yulPhaser/Phaser.cpp | 19 +++++++++++++++++++ tools/yulPhaser/Phaser.h | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index 8c5fcbc26..c8bc38a96 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -120,6 +120,9 @@ PopulationFactory::Options PopulationFactory::Options::fromCommandLine(po::varia _arguments.count("random-population") > 0 ? _arguments["random-population"].as>() : vector{}, + _arguments.count("population-from-file") > 0 ? + _arguments["population-from-file"].as>() : + vector{}, }; } @@ -139,6 +142,9 @@ Population PopulationFactory::build( _fitnessMetric ); + for (string const& populationFilePath: _options.populationFromFile) + population = move(population) + buildFromFile(populationFilePath, _fitnessMetric); + return population; } @@ -167,6 +173,14 @@ Population PopulationFactory::buildRandom( ); } +Population PopulationFactory::buildFromFile( + string const& _filePath, + shared_ptr _fitnessMetric +) +{ + return buildFromStrings(readLinesFromFile(_filePath), move(_fitnessMetric)); +} + ProgramFactory::Options ProgramFactory::Options::fromCommandLine(po::variables_map const& _arguments) { return { @@ -261,6 +275,11 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() po::value>()->value_name(""), "The number of randomly generated chromosomes to be included in the initial population." ) + ( + "population-from-file", + po::value>()->value_name(""), + "A text file with a list of chromosomes (one per line) to be included in the initial population." + ) ; keywordDescription.add(populationDescription); diff --git a/tools/yulPhaser/Phaser.h b/tools/yulPhaser/Phaser.h index a12ccd87a..63f570594 100644 --- a/tools/yulPhaser/Phaser.h +++ b/tools/yulPhaser/Phaser.h @@ -108,6 +108,7 @@ public: { std::vector population; std::vector randomPopulation; + std::vector populationFromFile; static Options fromCommandLine(boost::program_options::variables_map const& _arguments); }; @@ -124,6 +125,10 @@ public: size_t _populationSize, std::shared_ptr _fitnessMetric ); + static Population buildFromFile( + std::string const& _filePath, + std::shared_ptr _fitnessMetric + ); }; /** From 1b5960111d90a650dad554fb89ad02a762dc5e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Sat, 22 Feb 2020 01:39:33 +0100 Subject: [PATCH 08/15] [yul-phaser] AlgorithmRunner: Population autosave --- test/yulPhaser/AlgorithmRunner.cpp | 101 +++++++++++++++++++++++++++- tools/yulPhaser/AlgorithmRunner.cpp | 34 ++++++++++ tools/yulPhaser/AlgorithmRunner.h | 3 + tools/yulPhaser/Exceptions.h | 1 + 4 files changed, 136 insertions(+), 3 deletions(-) diff --git a/test/yulPhaser/AlgorithmRunner.cpp b/test/yulPhaser/AlgorithmRunner.cpp index f6102a9cb..7b20d8db4 100644 --- a/test/yulPhaser/AlgorithmRunner.cpp +++ b/test/yulPhaser/AlgorithmRunner.cpp @@ -18,9 +18,11 @@ #include #include +#include #include +#include #include #include @@ -29,10 +31,12 @@ using namespace boost::unit_test::framework; using namespace boost::test_tools; using namespace solidity::util; +namespace fs = boost::filesystem; + namespace solidity::phaser::test { -class DummyAlgorithm: public GeneticAlgorithm +class CountingAlgorithm: public GeneticAlgorithm { public: using GeneticAlgorithm::GeneticAlgorithm; @@ -45,6 +49,16 @@ public: size_t m_currentRound = 0; }; +class RandomisingAlgorithm: public GeneticAlgorithm +{ +public: + using GeneticAlgorithm::GeneticAlgorithm; + Population runNextRound(Population _population) override + { + return Population::makeRandom(_population.fitnessMetric(), _population.individuals().size(), 10, 20); + } +}; + class AlgorithmRunnerFixture { protected: @@ -53,6 +67,25 @@ protected: AlgorithmRunner::Options m_options; }; +class AlgorithmRunnerAutosaveFixture: public AlgorithmRunnerFixture +{ +public: + static vector chromosomeStrings(Population const& _population) + { + vector lines; + for (auto const& individual: _population.individuals()) + lines.push_back(toString(individual.chromosome)); + + return lines; + } + +protected: + TemporaryDirectory m_tempDir; + string const m_autosavePath = m_tempDir.memberPath("population-autosave.txt"); + Population const m_population = Population::makeRandom(m_fitnessMetric, 5, 0, 20); + RandomisingAlgorithm m_algorithm; +}; + BOOST_AUTO_TEST_SUITE(Phaser) BOOST_AUTO_TEST_SUITE(AlgorithmRunnerTest) @@ -60,7 +93,8 @@ BOOST_FIXTURE_TEST_CASE(run_should_call_runNextRound_once_per_round, AlgorithmRu { m_options.maxRounds = 5; AlgorithmRunner runner(Population(m_fitnessMetric), m_options, m_output); - DummyAlgorithm algorithm; + + CountingAlgorithm algorithm; BOOST_TEST(algorithm.m_currentRound == 0); runner.run(algorithm); @@ -82,7 +116,7 @@ BOOST_FIXTURE_TEST_CASE(run_should_print_the_top_chromosome, AlgorithmRunnerFixt m_output ); - DummyAlgorithm algorithm; + CountingAlgorithm algorithm; BOOST_TEST(m_output.is_empty()); runner.run(algorithm); @@ -93,6 +127,67 @@ BOOST_FIXTURE_TEST_CASE(run_should_print_the_top_chromosome, AlgorithmRunnerFixt BOOST_TEST(countSubstringOccurrences(m_output.str(), toString(runner.population().individuals()[0].chromosome)) == 4); } +BOOST_FIXTURE_TEST_CASE(run_should_save_initial_population_to_file_if_autosave_file_specified, AlgorithmRunnerAutosaveFixture) +{ + m_options.maxRounds = 0; + m_options.populationAutosaveFile = m_autosavePath; + AlgorithmRunner runner(m_population, m_options, m_output); + assert(!fs::exists(m_autosavePath)); + + runner.run(m_algorithm); + assert(runner.population() == m_population); + + BOOST_TEST(fs::is_regular_file(m_autosavePath)); + BOOST_TEST(readLinesFromFile(m_autosavePath) == chromosomeStrings(runner.population())); +} + +BOOST_FIXTURE_TEST_CASE(run_should_save_population_to_file_if_autosave_file_specified, AlgorithmRunnerAutosaveFixture) +{ + m_options.maxRounds = 1; + m_options.populationAutosaveFile = m_autosavePath; + AlgorithmRunner runner(m_population, m_options, m_output); + assert(!fs::exists(m_autosavePath)); + + runner.run(m_algorithm); + assert(runner.population() != m_population); + + BOOST_TEST(fs::is_regular_file(m_autosavePath)); + BOOST_TEST(readLinesFromFile(m_autosavePath) == chromosomeStrings(runner.population())); +} + +BOOST_FIXTURE_TEST_CASE(run_should_overwrite_existing_file_if_autosave_file_specified, AlgorithmRunnerAutosaveFixture) +{ + m_options.maxRounds = 5; + m_options.populationAutosaveFile = m_autosavePath; + AlgorithmRunner runner(m_population, m_options, m_output); + assert(!fs::exists(m_autosavePath)); + + vector originalContent = {"Original content"}; + { + ofstream tmpFile(m_autosavePath); + tmpFile << originalContent[0] << endl; + } + assert(fs::exists(m_autosavePath)); + assert(readLinesFromFile(m_autosavePath) == originalContent); + + runner.run(m_algorithm); + + BOOST_TEST(fs::is_regular_file(m_autosavePath)); + BOOST_TEST(readLinesFromFile(m_autosavePath) != originalContent); +} + +BOOST_FIXTURE_TEST_CASE(run_should_not_save_population_to_file_if_autosave_file_not_specified, AlgorithmRunnerAutosaveFixture) +{ + m_options.maxRounds = 5; + m_options.populationAutosaveFile = nullopt; + AlgorithmRunner runner(m_population, m_options, m_output); + assert(!fs::exists(m_autosavePath)); + + runner.run(m_algorithm); + + BOOST_TEST(!fs::exists(m_autosavePath)); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() diff --git a/tools/yulPhaser/AlgorithmRunner.cpp b/tools/yulPhaser/AlgorithmRunner.cpp index 0efe19b7c..a075fa785 100644 --- a/tools/yulPhaser/AlgorithmRunner.cpp +++ b/tools/yulPhaser/AlgorithmRunner.cpp @@ -17,16 +17,50 @@ #include +#include + +#include + +#include +#include +#include + using namespace std; using namespace solidity::phaser; void AlgorithmRunner::run(GeneticAlgorithm& _algorithm) { + populationAutosave(); + for (size_t round = 0; !m_options.maxRounds.has_value() || round < m_options.maxRounds.value(); ++round) { m_population = _algorithm.runNextRound(m_population); m_outputStream << "---------- ROUND " << round + 1 << " ----------" << endl; m_outputStream << m_population; + + populationAutosave(); } } + +void AlgorithmRunner::populationAutosave() const +{ + if (!m_options.populationAutosaveFile.has_value()) + return; + + ofstream outputStream(m_options.populationAutosaveFile.value(), ios::out | ios::trunc); + assertThrow( + outputStream.is_open(), + FileOpenError, + "Could not open file '" + m_options.populationAutosaveFile.value() + "': " + strerror(errno) + ); + + for (auto& individual: m_population.individuals()) + outputStream << individual.chromosome << endl; + + assertThrow( + !outputStream.bad(), + FileWriteError, + "Error while writing to file '" + m_options.populationAutosaveFile.value() + "': " + strerror(errno) + ); +} diff --git a/tools/yulPhaser/AlgorithmRunner.h b/tools/yulPhaser/AlgorithmRunner.h index c0aaa5621..93c8348d5 100644 --- a/tools/yulPhaser/AlgorithmRunner.h +++ b/tools/yulPhaser/AlgorithmRunner.h @@ -42,6 +42,7 @@ public: struct Options { std::optional maxRounds = std::nullopt; + std::optional populationAutosaveFile = std::nullopt; }; AlgorithmRunner( @@ -59,6 +60,8 @@ public: Population const& population() const { return m_population; } private: + void populationAutosave() const; + Population m_population; Options m_options; std::ostream& m_outputStream; diff --git a/tools/yulPhaser/Exceptions.h b/tools/yulPhaser/Exceptions.h index e3a06fbed..6807d7180 100644 --- a/tools/yulPhaser/Exceptions.h +++ b/tools/yulPhaser/Exceptions.h @@ -29,5 +29,6 @@ struct MissingFile: virtual BadInput {}; struct FileOpenError: virtual util::Exception {}; struct FileReadError: virtual util::Exception {}; +struct FileWriteError: virtual util::Exception {}; } From 3f7ada1689d1027eb1208c7647a309551e1aac20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Sat, 22 Feb 2020 01:39:56 +0100 Subject: [PATCH 09/15] [yul-phaser] Add --population-autosave option --- tools/yulPhaser/Phaser.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index c8bc38a96..ee732298d 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -280,6 +280,11 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() po::value>()->value_name(""), "A text file with a list of chromosomes (one per line) to be included in the initial population." ) + ( + "population-autosave", + po::value()->value_name(""), + "If specified, the population is saved in the specified file after each round. (default=autosave disabled)" + ) ; keywordDescription.add(populationDescription); @@ -337,7 +342,8 @@ void Phaser::initialiseRNG(po::variables_map const& _arguments) AlgorithmRunner::Options Phaser::buildAlgorithmRunnerOptions(po::variables_map const& _arguments) { return { - _arguments.count("rounds") > 0 ? static_cast>(_arguments["rounds"].as()) : nullopt + _arguments.count("rounds") > 0 ? static_cast>(_arguments["rounds"].as()) : nullopt, + _arguments.count("population-autosave") > 0 ? static_cast>(_arguments["population-autosave"].as()) : nullopt, }; } From 55ea92dbec7af62c71b9515909daad09b06a0e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Wed, 26 Feb 2020 22:35:54 +0100 Subject: [PATCH 10/15] [yul-phaser] Add --min-chromosome-length and --max-chromosome-length options --- tools/yulPhaser/Phaser.cpp | 38 ++++++++++++++++++++++++++------------ tools/yulPhaser/Phaser.h | 13 +++++++------ 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index ee732298d..97e429968 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -62,14 +62,14 @@ GeneticAlgorithmFactory::Options GeneticAlgorithmFactory::Options::fromCommandLi { return { _arguments["algorithm"].as(), + _arguments["min-chromosome-length"].as(), + _arguments["max-chromosome-length"].as(), }; } unique_ptr GeneticAlgorithmFactory::build( Options const& _options, - size_t _populationSize, - size_t _minChromosomeLength, - size_t _maxChromosomeLength + size_t _populationSize ) { assert(_populationSize > 0); @@ -79,8 +79,8 @@ unique_ptr GeneticAlgorithmFactory::build( case Algorithm::Random: return make_unique(RandomAlgorithm::Options{ /* elitePoolSize = */ 1.0 / _populationSize, - /* minChromosomeLength = */ _minChromosomeLength, - /* maxChromosomeLength = */ _maxChromosomeLength, + /* minChromosomeLength = */ _options.minChromosomeLength, + /* maxChromosomeLength = */ _options.maxChromosomeLength, }); case Algorithm::GEWEP: return make_unique(GenerationalElitistWithExclusivePools::Options{ @@ -88,8 +88,8 @@ unique_ptr GeneticAlgorithmFactory::build( /* crossoverPoolSize = */ 0.25, /* randomisationChance = */ 0.9, /* deletionVsAdditionChance = */ 0.5, - /* percentGenesToRandomise = */ 1.0 / _maxChromosomeLength, - /* percentGenesToAddOrDelete = */ 1.0 / _maxChromosomeLength, + /* percentGenesToRandomise = */ 1.0 / _options.maxChromosomeLength, + /* percentGenesToAddOrDelete = */ 1.0 / _options.maxChromosomeLength, }); default: assertThrow(false, solidity::util::Exception, "Invalid Algorithm value."); @@ -114,6 +114,8 @@ unique_ptr FitnessMetricFactory::build( PopulationFactory::Options PopulationFactory::Options::fromCommandLine(po::variables_map const& _arguments) { return { + _arguments["min-chromosome-length"].as(), + _arguments["max-chromosome-length"].as(), _arguments.count("population") > 0 ? _arguments["population"].as>() : vector{}, @@ -139,6 +141,8 @@ Population PopulationFactory::build( population = move(population) + buildRandom( combinedSize, + _options.minChromosomeLength, + _options.maxChromosomeLength, _fitnessMetric ); @@ -162,14 +166,16 @@ Population PopulationFactory::buildFromStrings( Population PopulationFactory::buildRandom( size_t _populationSize, + size_t _minChromosomeLength, + size_t _maxChromosomeLength, shared_ptr _fitnessMetric ) { return Population::makeRandom( move(_fitnessMetric), _populationSize, - MinChromosomeLength, - MaxChromosomeLength + _minChromosomeLength, + _maxChromosomeLength ); } @@ -258,6 +264,16 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() po::value()->value_name("")->default_value(Algorithm::GEWEP), "Algorithm" ) + ( + "min-chromosome-length", + po::value()->value_name("")->default_value(12), + "Minimum length of randomly generated chromosomes." + ) + ( + "max-chromosome-length", + po::value()->value_name("")->default_value(30), + "Maximum length of randomly generated chromosomes." + ) ; keywordDescription.add(algorithmDescription); @@ -360,9 +376,7 @@ void Phaser::runAlgorithm(po::variables_map const& _arguments) unique_ptr geneticAlgorithm = GeneticAlgorithmFactory::build( algorithmOptions, - population.individuals().size(), - PopulationFactory::MinChromosomeLength, - PopulationFactory::MaxChromosomeLength + population.individuals().size() ); AlgorithmRunner algorithmRunner(population, buildAlgorithmRunnerOptions(_arguments), cout); diff --git a/tools/yulPhaser/Phaser.h b/tools/yulPhaser/Phaser.h index 63f570594..b45a9dd6e 100644 --- a/tools/yulPhaser/Phaser.h +++ b/tools/yulPhaser/Phaser.h @@ -64,15 +64,15 @@ public: struct Options { Algorithm algorithm; + size_t minChromosomeLength; + size_t maxChromosomeLength; static Options fromCommandLine(boost::program_options::variables_map const& _arguments); }; static std::unique_ptr build( Options const& _options, - size_t _populationSize, - size_t _minChromosomeLength, - size_t _maxChromosomeLength + size_t _populationSize ); }; @@ -101,11 +101,10 @@ public: class PopulationFactory { public: - static constexpr size_t MinChromosomeLength = 12; - static constexpr size_t MaxChromosomeLength = 30; - struct Options { + size_t minChromosomeLength; + size_t maxChromosomeLength; std::vector population; std::vector randomPopulation; std::vector populationFromFile; @@ -123,6 +122,8 @@ public: ); static Population buildRandom( size_t _populationSize, + size_t _minChromosomeLength, + size_t _maxChromosomeLength, std::shared_ptr _fitnessMetric ); static Population buildFromFile( From b11eff7c88fb8c3d2474196a5c20aad3ca3bb357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Wed, 26 Feb 2020 22:53:56 +0100 Subject: [PATCH 11/15] [yul-phaser] Add options for all algorithm-specific parameters --- tools/yulPhaser/Phaser.cpp | 92 +++++++++++++++++++++++++++++++++++--- tools/yulPhaser/Phaser.h | 7 +++ 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index 97e429968..b1ee03f56 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -64,6 +64,19 @@ GeneticAlgorithmFactory::Options GeneticAlgorithmFactory::Options::fromCommandLi _arguments["algorithm"].as(), _arguments["min-chromosome-length"].as(), _arguments["max-chromosome-length"].as(), + _arguments.count("random-elite-pool-size") > 0 ? + _arguments["random-elite-pool-size"].as() : + optional{}, + _arguments["gewep-mutation-pool-size"].as(), + _arguments["gewep-crossover-pool-size"].as(), + _arguments["gewep-randomisation-chance"].as(), + _arguments["gewep-deletion-vs-addition-chance"].as(), + _arguments.count("gewep-genes-to-randomise") > 0 ? + _arguments["gewep-genes-to-randomise"].as() : + optional{}, + _arguments.count("gewep-genes-to-add-or-delete") > 0 ? + _arguments["gewep-genes-to-add-or-delete"].as() : + optional{}, }; } @@ -77,20 +90,37 @@ unique_ptr GeneticAlgorithmFactory::build( switch (_options.algorithm) { case Algorithm::Random: + { + double elitePoolSize = 1.0 / _populationSize; + + if (_options.randomElitePoolSize.has_value()) + elitePoolSize = _options.randomElitePoolSize.value(); + return make_unique(RandomAlgorithm::Options{ - /* elitePoolSize = */ 1.0 / _populationSize, + /* elitePoolSize = */ elitePoolSize, /* minChromosomeLength = */ _options.minChromosomeLength, /* maxChromosomeLength = */ _options.maxChromosomeLength, }); + } case Algorithm::GEWEP: + { + double percentGenesToRandomise = 1.0 / _options.maxChromosomeLength; + double percentGenesToAddOrDelete = percentGenesToRandomise; + + if (_options.gewepGenesToRandomise.has_value()) + percentGenesToRandomise = _options.gewepGenesToRandomise.value(); + if (_options.gewepGenesToAddOrDelete.has_value()) + percentGenesToAddOrDelete = _options.gewepGenesToAddOrDelete.value(); + return make_unique(GenerationalElitistWithExclusivePools::Options{ - /* mutationPoolSize = */ 0.25, - /* crossoverPoolSize = */ 0.25, - /* randomisationChance = */ 0.9, - /* deletionVsAdditionChance = */ 0.5, - /* percentGenesToRandomise = */ 1.0 / _options.maxChromosomeLength, - /* percentGenesToAddOrDelete = */ 1.0 / _options.maxChromosomeLength, + /* mutationPoolSize = */ _options.gewepMutationPoolSize, + /* crossoverPoolSize = */ _options.gewepCrossoverPoolSize, + /* randomisationChance = */ _options.gewepRandomisationChance, + /* deletionVsAdditionChance = */ _options.gewepDeletionVsAdditionChance, + /* percentGenesToRandomise = */ percentGenesToRandomise, + /* percentGenesToAddOrDelete = */ percentGenesToAddOrDelete, }); + } default: assertThrow(false, solidity::util::Exception, "Invalid Algorithm value."); } @@ -277,6 +307,54 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() ; keywordDescription.add(algorithmDescription); + po::options_description gewepAlgorithmDescription("GEWEP ALGORITHM", lineLength, minDescriptionLength); + gewepAlgorithmDescription.add_options() + ( + "gewep-mutation-pool-size", + po::value()->value_name("")->default_value(0.25), + "Percentage of population to regenerate using mutations in each round." + ) + ( + "gewep-crossover-pool-size", + po::value()->value_name("")->default_value(0.25), + "Percentage of population to regenerate using crossover in each round." + ) + ( + "gewep-randomisation-chance", + po::value()->value_name("")->default_value(0.9), + "The chance of choosing gene randomisation as the mutation to perform." + ) + ( + "gewep-deletion-vs-addition-chance", + po::value()->value_name("")->default_value(0.5), + "The chance of choosing gene deletion as the mutation if randomisation was not chosen." + ) + ( + "gewep-genes-to-randomise", + po::value()->value_name(""), + "The chance of any given gene being mutated in gene randomisation. " + "(default=1/max-chromosome-length)" + ) + ( + "gewep-genes-to-add-or-delete", + po::value()->value_name(""), + "The chance of a gene being added (or deleted) in gene addition (or deletion). " + "(default=1/max-chromosome-length)" + ) + ; + keywordDescription.add(gewepAlgorithmDescription); + + po::options_description randomAlgorithmDescription("RANDOM ALGORITHM", lineLength, minDescriptionLength); + randomAlgorithmDescription.add_options() + ( + "random-elite-pool-size", + po::value()->value_name(""), + "Percentage of the population preserved in each round. " + "(default=one individual, regardless of population size)" + ) + ; + keywordDescription.add(randomAlgorithmDescription); + po::options_description populationDescription("POPULATION", lineLength, minDescriptionLength); populationDescription.add_options() ( diff --git a/tools/yulPhaser/Phaser.h b/tools/yulPhaser/Phaser.h index b45a9dd6e..9631aa4cb 100644 --- a/tools/yulPhaser/Phaser.h +++ b/tools/yulPhaser/Phaser.h @@ -66,6 +66,13 @@ public: Algorithm algorithm; size_t minChromosomeLength; size_t maxChromosomeLength; + std::optional randomElitePoolSize; + double gewepMutationPoolSize; + double gewepCrossoverPoolSize; + double gewepRandomisationChance; + double gewepDeletionVsAdditionChance; + std::optional gewepGenesToRandomise; + std::optional gewepGenesToAddOrDelete; static Options fromCommandLine(boost::program_options::variables_map const& _arguments); }; From 18f0d6eb9420312d1ec587cdefa4c1716cb47b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 27 Feb 2020 00:40:57 +0100 Subject: [PATCH 12/15] [yul-phaser] AlgorithmRunner: Duplicate chromosome randomisation --- test/yulPhaser/AlgorithmRunner.cpp | 49 +++++++++++++++++++++++++++++ tools/yulPhaser/AlgorithmRunner.cpp | 39 +++++++++++++++++++++++ tools/yulPhaser/AlgorithmRunner.h | 9 ++++++ tools/yulPhaser/Phaser.cpp | 3 ++ 4 files changed, 100 insertions(+) diff --git a/test/yulPhaser/AlgorithmRunner.cpp b/test/yulPhaser/AlgorithmRunner.cpp index 7b20d8db4..0b54ad38f 100644 --- a/test/yulPhaser/AlgorithmRunner.cpp +++ b/test/yulPhaser/AlgorithmRunner.cpp @@ -188,6 +188,55 @@ BOOST_FIXTURE_TEST_CASE(run_should_not_save_population_to_file_if_autosave_file_ BOOST_TEST(!fs::exists(m_autosavePath)); } +BOOST_FIXTURE_TEST_CASE(run_should_randomise_duplicate_chromosomes_if_requested, AlgorithmRunnerFixture) +{ + Chromosome duplicate("afc"); + Population population(m_fitnessMetric, {duplicate, duplicate, duplicate}); + CountingAlgorithm algorithm; + + m_options.maxRounds = 1; + m_options.randomiseDuplicates = true; + m_options.minChromosomeLength = 50; + m_options.maxChromosomeLength = 50; + AlgorithmRunner runner(population, m_options, m_output); + + runner.run(algorithm); + + auto const& newIndividuals = runner.population().individuals(); + + BOOST_TEST(newIndividuals.size() == 3); + BOOST_TEST(( + newIndividuals[0].chromosome == duplicate || + newIndividuals[1].chromosome == duplicate || + newIndividuals[2].chromosome == duplicate + )); + BOOST_TEST(newIndividuals[0] != newIndividuals[1]); + BOOST_TEST(newIndividuals[0] != newIndividuals[2]); + BOOST_TEST(newIndividuals[1] != newIndividuals[2]); + + BOOST_TEST((newIndividuals[0].chromosome.length() == 50 || newIndividuals[0].chromosome == duplicate)); + BOOST_TEST((newIndividuals[1].chromosome.length() == 50 || newIndividuals[1].chromosome == duplicate)); + BOOST_TEST((newIndividuals[2].chromosome.length() == 50 || newIndividuals[2].chromosome == duplicate)); +} + +BOOST_FIXTURE_TEST_CASE(run_should_not_randomise_duplicate_chromosomes_if_not_requested, AlgorithmRunnerFixture) +{ + Chromosome duplicate("afc"); + Population population(m_fitnessMetric, {duplicate, duplicate, duplicate}); + CountingAlgorithm algorithm; + + m_options.maxRounds = 1; + m_options.randomiseDuplicates = false; + AlgorithmRunner runner(population, m_options, m_output); + + runner.run(algorithm); + + BOOST_TEST(runner.population().individuals().size() == 3); + BOOST_TEST(runner.population().individuals()[0].chromosome == duplicate); + BOOST_TEST(runner.population().individuals()[1].chromosome == duplicate); + BOOST_TEST(runner.population().individuals()[2].chromosome == duplicate); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() diff --git a/tools/yulPhaser/AlgorithmRunner.cpp b/tools/yulPhaser/AlgorithmRunner.cpp index a075fa785..b9f22171b 100644 --- a/tools/yulPhaser/AlgorithmRunner.cpp +++ b/tools/yulPhaser/AlgorithmRunner.cpp @@ -35,6 +35,7 @@ void AlgorithmRunner::run(GeneticAlgorithm& _algorithm) for (size_t round = 0; !m_options.maxRounds.has_value() || round < m_options.maxRounds.value(); ++round) { m_population = _algorithm.runNextRound(m_population); + randomiseDuplicates(); m_outputStream << "---------- ROUND " << round + 1 << " ----------" << endl; m_outputStream << m_population; @@ -64,3 +65,41 @@ void AlgorithmRunner::populationAutosave() const "Error while writing to file '" + m_options.populationAutosaveFile.value() + "': " + strerror(errno) ); } + +void AlgorithmRunner::randomiseDuplicates() +{ + if (m_options.randomiseDuplicates) + { + assert(m_options.minChromosomeLength.has_value()); + assert(m_options.maxChromosomeLength.has_value()); + + m_population = randomiseDuplicates( + m_population, + m_options.minChromosomeLength.value(), + m_options.maxChromosomeLength.value() + ); + } +} + +Population AlgorithmRunner::randomiseDuplicates( + Population _population, + size_t _minChromosomeLength, + size_t _maxChromosomeLength +) +{ + if (_population.individuals().size() == 0) + return _population; + + vector chromosomes{_population.individuals()[0].chromosome}; + 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); + + return ( + Population(_population.fitnessMetric(), chromosomes) + + Population::makeRandom(_population.fitnessMetric(), duplicateCount, _minChromosomeLength, _maxChromosomeLength) + ); +} diff --git a/tools/yulPhaser/AlgorithmRunner.h b/tools/yulPhaser/AlgorithmRunner.h index 93c8348d5..ff0d0e3c3 100644 --- a/tools/yulPhaser/AlgorithmRunner.h +++ b/tools/yulPhaser/AlgorithmRunner.h @@ -43,6 +43,9 @@ public: { std::optional maxRounds = std::nullopt; std::optional populationAutosaveFile = std::nullopt; + bool randomiseDuplicates = false; + std::optional minChromosomeLength = std::nullopt; + std::optional maxChromosomeLength = std::nullopt; }; AlgorithmRunner( @@ -61,6 +64,12 @@ public: private: void populationAutosave() const; + void randomiseDuplicates(); + static Population randomiseDuplicates( + Population _population, + size_t _minChromosomeLength, + size_t _maxChromosomeLength + ); Population m_population; Options m_options; diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index b1ee03f56..e8902a1bc 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -438,6 +438,9 @@ AlgorithmRunner::Options Phaser::buildAlgorithmRunnerOptions(po::variables_map c return { _arguments.count("rounds") > 0 ? static_cast>(_arguments["rounds"].as()) : nullopt, _arguments.count("population-autosave") > 0 ? static_cast>(_arguments["population-autosave"].as()) : nullopt, + false, + nullopt, + nullopt, }; } From 2563e7a7e308c5b2bd31133a9f14b1795e5243d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Tue, 3 Mar 2020 11:04:41 +0100 Subject: [PATCH 13/15] [yul-phaser] Add --no-randomise-duplicates option --- tools/yulPhaser/Phaser.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index e8902a1bc..7640fbf28 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -294,6 +294,13 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() po::value()->value_name("")->default_value(Algorithm::GEWEP), "Algorithm" ) + ( + "no-randomise-duplicates", + po::bool_switch(), + "By default, after each round of the algorithm duplicate chromosomes are removed from" + "the population and replaced with randomly generated ones. " + "This option disables this postprocessing." + ) ( "min-chromosome-length", po::value()->value_name("")->default_value(12), @@ -438,9 +445,9 @@ AlgorithmRunner::Options Phaser::buildAlgorithmRunnerOptions(po::variables_map c return { _arguments.count("rounds") > 0 ? static_cast>(_arguments["rounds"].as()) : nullopt, _arguments.count("population-autosave") > 0 ? static_cast>(_arguments["population-autosave"].as()) : nullopt, - false, - nullopt, - nullopt, + !_arguments["no-randomise-duplicates"].as(), + _arguments["min-chromosome-length"].as(), + _arguments["max-chromosome-length"].as(), }; } From 7a1f6a27db5e498dad81f2f3931f21173de73c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Wed, 4 Mar 2020 19:46:46 +0100 Subject: [PATCH 14/15] [yul-phaser] More data accessors in metric and algorithm classes --- tools/yulPhaser/FitnessMetrics.h | 3 +++ tools/yulPhaser/GeneticAlgorithms.h | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/tools/yulPhaser/FitnessMetrics.h b/tools/yulPhaser/FitnessMetrics.h index 7fac5f080..95608e89b 100644 --- a/tools/yulPhaser/FitnessMetrics.h +++ b/tools/yulPhaser/FitnessMetrics.h @@ -57,6 +57,9 @@ public: 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; private: diff --git a/tools/yulPhaser/GeneticAlgorithms.h b/tools/yulPhaser/GeneticAlgorithms.h index b9ccb4302..a2a7484d6 100644 --- a/tools/yulPhaser/GeneticAlgorithms.h +++ b/tools/yulPhaser/GeneticAlgorithms.h @@ -81,6 +81,8 @@ public: assert(_options.isValid()); } + Options const& options() const { return m_options; } + Population runNextRound(Population _population) override; private: @@ -129,6 +131,8 @@ public: assert(_options.isValid()); } + Options const& options() const { return m_options; } + Population runNextRound(Population _population) override; private: From a2821db1dd5d7e9c3b5621d06cabe6aa43cd69cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Wed, 4 Mar 2020 19:48:05 +0100 Subject: [PATCH 15/15] [yul-phaser] Phaser: Tests for factories --- test/CMakeLists.txt | 2 + test/yulPhaser/Phaser.cpp | 290 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 test/yulPhaser/Phaser.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d5bf4b686..7165a5e27 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -149,6 +149,7 @@ set(yul_phaser_sources yulPhaser/GeneticAlgorithms.cpp yulPhaser/Mutations.cpp yulPhaser/PairSelections.cpp + yulPhaser/Phaser.cpp yulPhaser/Population.cpp yulPhaser/Program.cpp yulPhaser/Selections.cpp @@ -164,6 +165,7 @@ set(yul_phaser_sources ../tools/yulPhaser/GeneticAlgorithms.cpp ../tools/yulPhaser/Mutations.cpp ../tools/yulPhaser/PairSelections.cpp + ../tools/yulPhaser/Phaser.cpp ../tools/yulPhaser/Population.cpp ../tools/yulPhaser/Program.cpp ../tools/yulPhaser/Selections.cpp diff --git a/test/yulPhaser/Phaser.cpp b/test/yulPhaser/Phaser.cpp new file mode 100644 index 000000000..26fe3c55e --- /dev/null +++ b/test/yulPhaser/Phaser.cpp @@ -0,0 +1,290 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +#include +#include + +#include + +#include + +#include +#include + +#include + +using namespace std; +using namespace solidity::util; +using namespace solidity::langutil; + +namespace fs = boost::filesystem; + +namespace solidity::phaser::test +{ + +class GeneticAlgorithmFactoryFixture +{ +protected: + GeneticAlgorithmFactory::Options m_options = { + /* algorithm = */ Algorithm::Random, + /* minChromosomeLength = */ 50, + /* maxChromosomeLength = */ 100, + /* randomElitePoolSize = */ 0.5, + /* gewepMutationPoolSize = */ 0.1, + /* gewepCrossoverPoolSize = */ 0.1, + /* gewepRandomisationChance = */ 0.6, + /* gewepDeletionVsAdditionChance = */ 0.3, + /* gewepGenesToRandomise = */ 0.4, + /* gewepGenesToAddOrDelete = */ 0.2, + }; +}; + +class FitnessMetricFactoryFixture +{ +protected: + CharStream m_sourceStream = CharStream("{}", ""); + Program m_program = get(Program::load(m_sourceStream)); + FitnessMetricFactory::Options m_options = { + /* chromosomeRepetitions = */ 1, + }; +}; + +class PoulationFactoryFixture +{ +protected: + shared_ptr m_fitnessMetric = make_shared(); + PopulationFactory::Options m_options = { + /* minChromosomeLength = */ 0, + /* maxChromosomeLength = */ 0, + /* population = */ {}, + /* randomPopulation = */ {}, + /* populationFromFile = */ {}, + }; +}; + +BOOST_AUTO_TEST_SUITE(Phaser) +BOOST_AUTO_TEST_SUITE(PhaserTest) +BOOST_AUTO_TEST_SUITE(GeneticAlgorithmFactoryTest) + +BOOST_FIXTURE_TEST_CASE(build_should_select_the_right_algorithm_and_pass_the_options_to_it, GeneticAlgorithmFactoryFixture) +{ + m_options.algorithm = Algorithm::Random; + unique_ptr algorithm1 = GeneticAlgorithmFactory::build(m_options, 100); + BOOST_REQUIRE(algorithm1 != nullptr); + + auto randomAlgorithm = dynamic_cast(algorithm1.get()); + BOOST_REQUIRE(randomAlgorithm != nullptr); + BOOST_TEST(randomAlgorithm->options().elitePoolSize == m_options.randomElitePoolSize.value()); + BOOST_TEST(randomAlgorithm->options().minChromosomeLength == m_options.minChromosomeLength); + BOOST_TEST(randomAlgorithm->options().maxChromosomeLength == m_options.maxChromosomeLength); + + m_options.algorithm = Algorithm::GEWEP; + unique_ptr algorithm2 = GeneticAlgorithmFactory::build(m_options, 100); + BOOST_REQUIRE(algorithm2 != nullptr); + + auto gewepAlgorithm = dynamic_cast(algorithm2.get()); + BOOST_REQUIRE(gewepAlgorithm != nullptr); + BOOST_TEST(gewepAlgorithm->options().mutationPoolSize == m_options.gewepMutationPoolSize); + BOOST_TEST(gewepAlgorithm->options().crossoverPoolSize == m_options.gewepCrossoverPoolSize); + BOOST_TEST(gewepAlgorithm->options().randomisationChance == m_options.gewepRandomisationChance); + 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()); +} + +BOOST_FIXTURE_TEST_CASE(build_should_set_random_algorithm_elite_pool_size_based_on_population_size_if_not_specified, GeneticAlgorithmFactoryFixture) +{ + m_options.algorithm = Algorithm::Random; + m_options.randomElitePoolSize = nullopt; + unique_ptr algorithm = GeneticAlgorithmFactory::build(m_options, 100); + BOOST_REQUIRE(algorithm != nullptr); + + auto randomAlgorithm = dynamic_cast(algorithm.get()); + BOOST_REQUIRE(randomAlgorithm != nullptr); + BOOST_TEST(randomAlgorithm->options().elitePoolSize == 1.0 / 100.0); +} + +BOOST_FIXTURE_TEST_CASE(build_should_set_gewep_mutation_percentages_based_on_maximum_chromosome_length_if_not_specified, GeneticAlgorithmFactoryFixture) +{ + m_options.algorithm = Algorithm::GEWEP; + m_options.gewepGenesToRandomise = nullopt; + m_options.gewepGenesToAddOrDelete = nullopt; + m_options.maxChromosomeLength = 125; + + unique_ptr algorithm = GeneticAlgorithmFactory::build(m_options, 100); + BOOST_REQUIRE(algorithm != nullptr); + + auto gewepAlgorithm = dynamic_cast(algorithm.get()); + BOOST_REQUIRE(gewepAlgorithm != nullptr); + BOOST_TEST(gewepAlgorithm->options().percentGenesToRandomise == 1.0 / 125.0); + BOOST_TEST(gewepAlgorithm->options().percentGenesToAddOrDelete == 1.0 / 125.0); +} + +BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE(FitnessMetricFactoryTest) + +BOOST_FIXTURE_TEST_CASE(build_should_create_metric_of_the_right_type, FitnessMetricFactoryFixture) +{ + unique_ptr metric = FitnessMetricFactory::build(m_options, m_program); + BOOST_REQUIRE(metric != nullptr); + + auto programSizeMetric = dynamic_cast(metric.get()); + BOOST_REQUIRE(programSizeMetric != nullptr); + BOOST_TEST(toString(programSizeMetric->program()) == toString(m_program)); +} + +BOOST_FIXTURE_TEST_CASE(build_should_respect_chromosome_repetitions_option, FitnessMetricFactoryFixture) +{ + m_options.chromosomeRepetitions = 5; + unique_ptr metric = FitnessMetricFactory::build(m_options, m_program); + BOOST_REQUIRE(metric != nullptr); + + auto programSizeMetric = dynamic_cast(metric.get()); + BOOST_REQUIRE(programSizeMetric != nullptr); + BOOST_TEST(programSizeMetric->repetitionCount() == m_options.chromosomeRepetitions); +} + +BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE(PopulationFactoryTest) + +BOOST_FIXTURE_TEST_CASE(build_should_create_an_empty_population_if_no_specific_options_given, PoulationFactoryFixture) +{ + m_options.population = {}; + m_options.randomPopulation = {}; + m_options.populationFromFile = {}; + BOOST_TEST( + PopulationFactory::build(m_options, m_fitnessMetric) == + Population(m_fitnessMetric, vector{}) + ); +} + +BOOST_FIXTURE_TEST_CASE(build_should_respect_population_option, PoulationFactoryFixture) +{ + m_options.population = {"a", "afc", "xadd"}; + BOOST_TEST( + PopulationFactory::build(m_options, m_fitnessMetric) == + Population(m_fitnessMetric, {Chromosome("a"), Chromosome("afc"), Chromosome("xadd")}) + ); +} + +BOOST_FIXTURE_TEST_CASE(build_should_respect_random_population_option, PoulationFactoryFixture) +{ + m_options.randomPopulation = {5, 3, 2}; + m_options.minChromosomeLength = 5; + m_options.maxChromosomeLength = 10; + + auto population = PopulationFactory::build(m_options, m_fitnessMetric); + + BOOST_TEST(population.individuals().size() == 10); + BOOST_TEST(all_of( + population.individuals().begin(), + population.individuals().end(), + [](auto const& individual){ return 5 <= individual.chromosome.length() && individual.chromosome.length() <= 10; } + )); +} + +BOOST_FIXTURE_TEST_CASE(build_should_respect_population_from_file_option, PoulationFactoryFixture) +{ + map> fileContent = { + {"a.txt", {"a", "fff", "", "jxccLTa"}}, + {"b.txt", {}}, + {"c.txt", {""}}, + {"d.txt", {"c", "T"}}, + }; + + TemporaryDirectory tempDir; + for (auto const& [fileName, chromosomes]: fileContent) + { + ofstream tmpFile(tempDir.memberPath(fileName)); + for (auto const& chromosome: chromosomes) + tmpFile << chromosome << endl; + + m_options.populationFromFile.push_back(tempDir.memberPath(fileName)); + } + + BOOST_TEST( + PopulationFactory::build(m_options, m_fitnessMetric) == + Population(m_fitnessMetric, { + Chromosome("a"), + Chromosome("fff"), + Chromosome(""), + Chromosome("jxccLTa"), + Chromosome(""), + Chromosome("c"), + Chromosome("T"), + }) + ); +} + +BOOST_FIXTURE_TEST_CASE(build_should_throw_FileOpenError_if_population_file_does_not_exist, PoulationFactoryFixture) +{ + m_options.populationFromFile = {"a-file-that-does-not-exist.abcdefgh"}; + assert(!fs::exists(m_options.populationFromFile[0])); + + BOOST_CHECK_THROW(PopulationFactory::build(m_options, m_fitnessMetric), FileOpenError); +} + +BOOST_FIXTURE_TEST_CASE(build_should_combine_populations_from_all_sources, PoulationFactoryFixture) +{ + TemporaryDirectory tempDir; + { + ofstream tmpFile(tempDir.memberPath("population.txt")); + tmpFile << "axc" << endl << "fcL" << endl; + } + + m_options.population = {"axc", "fcL"}; + m_options.randomPopulation = {2}; + m_options.populationFromFile = {tempDir.memberPath("population.txt")}; + m_options.minChromosomeLength = 3; + m_options.maxChromosomeLength = 3; + + auto population = PopulationFactory::build(m_options, m_fitnessMetric); + + auto begin = population.individuals().begin(); + auto end = population.individuals().end(); + BOOST_TEST(population.individuals().size() == 6); + BOOST_TEST(all_of(begin, end, [](auto const& individual){ return individual.chromosome.length() == 3; })); + BOOST_TEST(count(begin, end, Individual(Chromosome("axc"), *m_fitnessMetric)) >= 2); + BOOST_TEST(count(begin, end, Individual(Chromosome("fcL"), *m_fitnessMetric)) >= 2); +} + +BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE(ProgramFactoryTest) + +BOOST_AUTO_TEST_CASE(build_should_load_program_from_file) +{ + TemporaryDirectory tempDir; + { + ofstream tmpFile(tempDir.memberPath("program.yul")); + tmpFile << "{}" << endl; + } + + ProgramFactory::Options options{/* inputFile = */ tempDir.memberPath("program.yul")}; + CharStream expectedProgramSource("{}", ""); + + auto program = ProgramFactory::build(options); + + BOOST_TEST(toString(program) == toString(get(Program::load(expectedProgramSource)))); +} + +BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE_END() + +}