diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7cf466e0b..37a78287d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -139,10 +139,13 @@ set(libyul_sources detect_stray_source_files("${libyul_sources}" "libyul/") set(yul_phaser_sources + yulPhaser/Common.h + yulPhaser/Common.cpp + yulPhaser/CommonTest.cpp yulPhaser/Chromosome.cpp yulPhaser/Population.cpp yulPhaser/Program.cpp - yulPhaser/Random.cpp + yulPhaser/SimulationRNG.cpp # FIXME: yul-phaser is not a library so I can't just add it to target_link_libraries(). # My current workaround is just to include its source files here but this introduces @@ -150,7 +153,7 @@ set(yul_phaser_sources ../tools/yulPhaser/Chromosome.cpp ../tools/yulPhaser/Population.cpp ../tools/yulPhaser/Program.cpp - ../tools/yulPhaser/Random.cpp + ../tools/yulPhaser/SimulationRNG.cpp ) detect_stray_source_files("${yul_phaser_sources}" "yulPhaser/") diff --git a/test/yulPhaser/Chromosome.cpp b/test/yulPhaser/Chromosome.cpp index bd07e1b2e..13f2f6811 100644 --- a/test/yulPhaser/Chromosome.cpp +++ b/test/yulPhaser/Chromosome.cpp @@ -15,7 +15,10 @@ along with solidity. If not, see . */ +#include + #include +#include #include #include @@ -95,6 +98,24 @@ BOOST_AUTO_TEST_CASE(output_operator_should_create_concise_and_unambiguous_strin BOOST_TEST(toString(chromosome) == "flcCUnDvejsxIOoighTLMrmVatud"); } +BOOST_AUTO_TEST_CASE(randomOptimisationStep_should_return_each_step_with_same_probability) +{ + SimulationRNG::reset(1); + constexpr int samplesPerStep = 100; + constexpr double relativeTolerance = 0.01; + + map stepIndices = enumerateOptmisationSteps(); + vector samples; + for (size_t i = 0; i <= stepIndices.size() * samplesPerStep; ++i) + samples.push_back(stepIndices.at(Chromosome::randomOptimisationStep())); + + const double expectedValue = (stepIndices.size() - 1) / 2.0; + const double variance = (stepIndices.size() * stepIndices.size() - 1) / 12.0; + + BOOST_TEST(abs(mean(samples) - expectedValue) < expectedValue * relativeTolerance); + BOOST_TEST(abs(meanSquaredError(samples, expectedValue) - variance) < variance * relativeTolerance); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() diff --git a/tools/yulPhaser/Random.h b/test/yulPhaser/Common.cpp similarity index 60% rename from tools/yulPhaser/Random.h rename to test/yulPhaser/Common.cpp index 25091ddef..3cef4ff2b 100644 --- a/tools/yulPhaser/Random.h +++ b/test/yulPhaser/Common.cpp @@ -15,14 +15,20 @@ along with solidity. If not, see . */ -#pragma once +#include -#include +#include -namespace solidity::phaser +using namespace std; +using namespace solidity; +using namespace solidity::yul; + +map phaser::test::enumerateOptmisationSteps() { + map stepIndices; + size_t i = 0; + for (auto const& nameAndAbbreviation: OptimiserSuite::stepNameToAbbreviationMap()) + stepIndices.insert({nameAndAbbreviation.first, i++}); -uint32_t uniformRandomInt(uint32_t _min, uint32_t _max); -uint32_t binomialRandomInt(uint32_t _numTrials, double _successProbability); - + return stepIndices; } diff --git a/test/yulPhaser/Common.h b/test/yulPhaser/Common.h new file mode 100644 index 000000000..a3dd24bce --- /dev/null +++ b/test/yulPhaser/Common.h @@ -0,0 +1,88 @@ +/* + 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 . +*/ +/** + * Miscellaneous utilities for use in yul-phaser's test cases. + * + * - Generic code that's only used in these particular tests. + * - Convenience functions and wrappers to make tests more concise. + * - Mocks and dummy objects/functions used in multiple test suites. + * + * Note that the code included here may be not as generic, robust and/or complete as it could be + * since it's not meant for production use. If some utility seems useful enough to be moved to + * the normal code base, you should review its implementation before doing so. + */ + +#pragma once + +#include +#include +#include +#include + +namespace solidity::phaser::test +{ + +// CHROMOSOME AND POPULATION HELPERS +/// Assigns indices from 0 to N to all optimisation steps available in the OptimiserSuite. +/// This is a convenience helper to make it easier to test their distribution with tools made for +/// integers. +std::map enumerateOptmisationSteps(); + +// STATISTICAL UTILITIES + +/// Calculates the mean value of a series of samples given in a vector. +/// +/// Supports any integer and real type as a convenience but the precision of the result is limited +/// to the precision of type @a double as all the values are internally converted to it. +/// +/// This is a very simple, naive implementation that's more than enough for tests where we usually +/// deal with relatively short sequences of small, positive integers. It's not numerically stable +/// in more complicated cases. Don't use in production. +template +double mean(std::vector const& _samples) +{ + assert(_samples.size() > 0); + + double sum = 0; + for (T const& sample: _samples) + sum += static_cast(sample); + + return sum / _samples.size(); +} + +/// Calculates the sum of squared differences between @a _expectedValue and the values of a series +/// of samples given in a vector. +/// +/// Supports any integer and real type as a convenience but the precision of the result is limited +/// to the precision of type @a double as all the values are internally converted to it. +/// +/// This is a very simple, naive implementation that's more than enough for tests where we usually +/// deal with relatively short sequences of small, positive integers. It's not numerically stable +/// in more complicated cases. Don't use in production. +template +double meanSquaredError(std::vector const& _samples, double _expectedValue) +{ + assert(_samples.size() > 0); + + double sumOfSquaredDifferences = 0; + for (T const& sample: _samples) + sumOfSquaredDifferences += (sample - _expectedValue) * (sample - _expectedValue); + + return sumOfSquaredDifferences / _samples.size(); +} + +} diff --git a/test/yulPhaser/CommonTest.cpp b/test/yulPhaser/CommonTest.cpp new file mode 100644 index 000000000..d7268c669 --- /dev/null +++ b/test/yulPhaser/CommonTest.cpp @@ -0,0 +1,82 @@ +/* + 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 + +using namespace std; +using namespace solidity::yul; +using namespace boost::test_tools; + +namespace solidity::phaser::test +{ + +BOOST_AUTO_TEST_SUITE(Phaser) +BOOST_AUTO_TEST_SUITE(CommonTest) + +BOOST_AUTO_TEST_CASE(enumerateOptimisationSteps_should_assing_indices_to_all_available_optimisation_steps) +{ + map stepsAndAbbreviations = OptimiserSuite::stepNameToAbbreviationMap(); + map stepsAndIndices = enumerateOptmisationSteps(); + BOOST_TEST(stepsAndIndices.size() == stepsAndAbbreviations.size()); + + set stepsSoFar; + for (auto& [name, index]: stepsAndIndices) + { + BOOST_TEST(index >= 0); + BOOST_TEST(index <= stepsAndAbbreviations.size()); + BOOST_TEST(stepsAndAbbreviations.count(name) == 1); + BOOST_TEST(stepsSoFar.count(name) == 0); + + stepsSoFar.insert(name); + } +} + +BOOST_AUTO_TEST_CASE(mean_should_calculate_statistical_mean) +{ + BOOST_TEST(mean({0}) == 0.0); + BOOST_TEST(mean({0, 0, 0, 0}) == 0.0); + BOOST_TEST(mean({5, 5, 5, 5}) == 5.0); + BOOST_TEST(mean({0, 1, 2, 3}) == 1.5); + BOOST_TEST(mean({-4, -3, -2, -1, 0, 1, 2, 3}) == -0.5); + + BOOST_TEST(mean({1.3, 1.1, 0.0, 1.5, 1.1, 2.0, 1.5, 1.5}) == 1.25); +} + +BOOST_AUTO_TEST_CASE(meanSquaredError_should_calculate_average_squared_difference_between_samples_and_expected_value) +{ + BOOST_TEST(meanSquaredError({0}, 0.0) == 0.0); + BOOST_TEST(meanSquaredError({0}, 1.0) == 1.0); + BOOST_TEST(meanSquaredError({0, 0, 0, 0}, 0.0) == 0.0); + BOOST_TEST(meanSquaredError({0, 0, 0, 0}, 1.0) == 1.0); + BOOST_TEST(meanSquaredError({0, 0, 0, 0}, 2.0) == 4.0); + BOOST_TEST(meanSquaredError({5, 5, 5, 5}, 1.0) == 16.0); + BOOST_TEST(meanSquaredError({0, 1, 2, 3}, 2.0) == 1.5); + BOOST_TEST(meanSquaredError({-4, -3, -2, -1, 0, 1, 2, 3}, -4.0) == 17.5); + + BOOST_TEST(meanSquaredError({1.3, 1.1, 0.0, 1.5, 1.1, 2.0, 1.5, 1.5}, 1.0) == 0.3575, tolerance(0.0001)); +} + +BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE_END() + +} diff --git a/test/yulPhaser/Random.cpp b/test/yulPhaser/Random.cpp deleted file mode 100644 index c69b91055..000000000 --- a/test/yulPhaser/Random.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/* - 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 - -using namespace std; - -namespace solidity::phaser::test -{ - -BOOST_AUTO_TEST_SUITE(Phaser) -BOOST_AUTO_TEST_SUITE(RandomTest) - -BOOST_AUTO_TEST_CASE(uniformRandomInt_returns_different_values_when_called_multiple_times) -{ - constexpr uint32_t numSamples = 1000; - constexpr uint32_t numOutcomes = 100; - - vector samples1; - vector samples2; - for (uint32_t i = 0; i < numSamples; ++i) - { - samples1.push_back(uniformRandomInt(0, numOutcomes - 1)); - samples2.push_back(uniformRandomInt(0, numOutcomes - 1)); - } - - vector counts1(numSamples, 0); - vector counts2(numSamples, 0); - for (uint32_t i = 0; i < numSamples; ++i) - { - ++counts1[samples1[i]]; - ++counts2[samples2[i]]; - } - - // This test rules out not only the possibility that the two sequences are the same but also - // that they're just different permutations of the same values. The test is probabilistic so - // it's technically possible for it to fail even if generator is good but the probability is - // so low that it would happen on average once very 10^125 billion years if you repeated it - // every second. The chance is much lower than 1 in 1000^100 / 100!. - // - // This does not really guarantee that the generated numbers have the right distribution or - // or that they don't come in long, repeating sequences but the implementation is very simple - // (it just calls a generator from boost) so our goal here is just to make sure it's used - // properly and we're not getting something totally non-random, e.g. the same number every time. - BOOST_TEST(counts1 != counts2); -} - -BOOST_AUTO_TEST_CASE(binomialRandomInt_returns_different_values_when_called_multiple_times) -{ - constexpr uint32_t numSamples = 1000; - constexpr uint32_t numTrials = 100; - constexpr double successProbability = 0.6; - - vector samples1; - vector samples2; - for (uint32_t i = 0; i < numSamples; ++i) - { - samples1.push_back(binomialRandomInt(numTrials, successProbability)); - samples2.push_back(binomialRandomInt(numTrials, successProbability)); - } - - vector counts1(numSamples, 0); - vector counts2(numSamples, 0); - for (uint32_t i = 0; i < numSamples; ++i) - { - ++counts1[samples1[i]]; - ++counts2[samples2[i]]; - } - - // See remark for uniformRandomInt() above. Same applies here. - BOOST_TEST(counts1 != counts2); -} - -BOOST_AUTO_TEST_SUITE_END() -BOOST_AUTO_TEST_SUITE_END() - -} diff --git a/test/yulPhaser/SimulationRNG.cpp b/test/yulPhaser/SimulationRNG.cpp new file mode 100644 index 000000000..f158fcbf6 --- /dev/null +++ b/test/yulPhaser/SimulationRNG.cpp @@ -0,0 +1,194 @@ +/* + 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 + +using namespace std; + +namespace solidity::phaser::test +{ + +BOOST_AUTO_TEST_SUITE(Phaser) +BOOST_AUTO_TEST_SUITE(RandomTest) + +BOOST_AUTO_TEST_CASE(bernoulliTrial_should_produce_samples_with_right_expected_value_and_variance) +{ + SimulationRNG::reset(1); + constexpr size_t numSamples = 10000; + constexpr double successProbability = 0.4; + constexpr double relativeTolerance = 0.05; + + // For bernoulli distribution with success probability p: EX = p, VarX = p(1 - p) + constexpr double expectedValue = successProbability; + constexpr double variance = successProbability * (1 - successProbability); + + vector samples; + for (uint32_t i = 0; i < numSamples; ++i) + samples.push_back(static_cast(SimulationRNG::bernoulliTrial(successProbability))); + + BOOST_TEST(abs(mean(samples) - expectedValue) < expectedValue * relativeTolerance); + BOOST_TEST(abs(meanSquaredError(samples, expectedValue) - variance) < variance * relativeTolerance); +} + +BOOST_AUTO_TEST_CASE(bernoulliTrial_can_be_reset) +{ + constexpr size_t numSamples = 10; + constexpr double successProbability = 0.4; + + SimulationRNG::reset(1); + vector samples1; + for (uint32_t i = 0; i < numSamples; ++i) + samples1.push_back(static_cast(SimulationRNG::bernoulliTrial(successProbability))); + + vector samples2; + for (uint32_t i = 0; i < numSamples; ++i) + samples2.push_back(static_cast(SimulationRNG::bernoulliTrial(successProbability))); + + SimulationRNG::reset(1); + vector samples3; + for (uint32_t i = 0; i < numSamples; ++i) + samples3.push_back(static_cast(SimulationRNG::bernoulliTrial(successProbability))); + + SimulationRNG::reset(2); + vector samples4; + for (uint32_t i = 0; i < numSamples; ++i) + samples4.push_back(static_cast(SimulationRNG::bernoulliTrial(successProbability))); + + BOOST_TEST(samples1 != samples2); + BOOST_TEST(samples1 == samples3); + BOOST_TEST(samples1 != samples4); + BOOST_TEST(samples2 != samples3); + BOOST_TEST(samples2 != samples4); + BOOST_TEST(samples3 != samples4); +} + +BOOST_AUTO_TEST_CASE(uniformInt_returns_different_values_when_called_multiple_times) +{ + SimulationRNG::reset(1); + constexpr size_t numSamples = 1000; + constexpr uint32_t minValue = 50; + constexpr uint32_t maxValue = 80; + constexpr double relativeTolerance = 0.05; + + // For uniform distribution from range a..b: EX = (a + b) / 2, VarX = ((b - a + 1)^2 - 1) / 12 + constexpr double expectedValue = (minValue + maxValue) / 2.0; + constexpr double variance = ((maxValue - minValue + 1) * (maxValue - minValue + 1) - 1) / 12.0; + + vector samples; + for (uint32_t i = 0; i < numSamples; ++i) + samples.push_back(SimulationRNG::uniformInt(minValue, maxValue)); + + BOOST_TEST(abs(mean(samples) - expectedValue) < expectedValue * relativeTolerance); + BOOST_TEST(abs(meanSquaredError(samples, expectedValue) - variance) < variance * relativeTolerance); +} + +BOOST_AUTO_TEST_CASE(uniformInt_can_be_reset) +{ + constexpr size_t numSamples = 10; + constexpr uint32_t minValue = 50; + constexpr uint32_t maxValue = 80; + + SimulationRNG::reset(1); + vector samples1; + for (uint32_t i = 0; i < numSamples; ++i) + samples1.push_back(SimulationRNG::uniformInt(minValue, maxValue)); + + vector samples2; + for (uint32_t i = 0; i < numSamples; ++i) + samples2.push_back(SimulationRNG::uniformInt(minValue, maxValue)); + + SimulationRNG::reset(1); + vector samples3; + for (uint32_t i = 0; i < numSamples; ++i) + samples3.push_back(SimulationRNG::uniformInt(minValue, maxValue)); + + SimulationRNG::reset(2); + vector samples4; + for (uint32_t i = 0; i < numSamples; ++i) + samples4.push_back(SimulationRNG::uniformInt(minValue, maxValue)); + + BOOST_TEST(samples1 != samples2); + BOOST_TEST(samples1 == samples3); + BOOST_TEST(samples1 != samples4); + BOOST_TEST(samples2 != samples3); + BOOST_TEST(samples2 != samples4); + BOOST_TEST(samples3 != samples4); +} + +BOOST_AUTO_TEST_CASE(binomialInt_should_produce_samples_with_right_expected_value_and_variance) +{ + SimulationRNG::reset(1); + constexpr size_t numSamples = 1000; + constexpr uint32_t numTrials = 100; + constexpr double successProbability = 0.2; + constexpr double relativeTolerance = 0.05; + + // For binomial distribution with n trials and success probability p: EX = np, VarX = np(1 - p) + constexpr double expectedValue = numTrials * successProbability; + constexpr double variance = numTrials * successProbability * (1 - successProbability); + + vector samples; + for (uint32_t i = 0; i < numSamples; ++i) + samples.push_back(SimulationRNG::binomialInt(numTrials, successProbability)); + + BOOST_TEST(abs(mean(samples) - expectedValue) < expectedValue * relativeTolerance); + BOOST_TEST(abs(meanSquaredError(samples, expectedValue) - variance) < variance * relativeTolerance); +} + +BOOST_AUTO_TEST_CASE(binomialInt_can_be_reset) +{ + constexpr size_t numSamples = 10; + constexpr uint32_t numTrials = 10; + constexpr double successProbability = 0.6; + + SimulationRNG::reset(1); + vector samples1; + for (uint32_t i = 0; i < numSamples; ++i) + samples1.push_back(SimulationRNG::binomialInt(numTrials, successProbability)); + + vector samples2; + for (uint32_t i = 0; i < numSamples; ++i) + samples2.push_back(SimulationRNG::binomialInt(numTrials, successProbability)); + + SimulationRNG::reset(1); + vector samples3; + for (uint32_t i = 0; i < numSamples; ++i) + samples3.push_back(SimulationRNG::binomialInt(numTrials, successProbability)); + + SimulationRNG::reset(2); + vector samples4; + for (uint32_t i = 0; i < numSamples; ++i) + samples4.push_back(SimulationRNG::binomialInt(numTrials, successProbability)); + + BOOST_TEST(samples1 != samples2); + BOOST_TEST(samples1 == samples3); + BOOST_TEST(samples1 != samples4); + BOOST_TEST(samples2 != samples3); + BOOST_TEST(samples2 != samples4); + BOOST_TEST(samples3 != samples4); +} + +BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE_END() + +} diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index a2aa807af..cd33c4006 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -21,8 +21,8 @@ add_executable(yul-phaser yulPhaser/Chromosome.cpp yulPhaser/Program.h yulPhaser/Program.cpp - yulPhaser/Random.h - yulPhaser/Random.cpp + yulPhaser/SimulationRNG.h + yulPhaser/SimulationRNG.cpp ) target_link_libraries(yul-phaser PRIVATE solidity Boost::program_options) diff --git a/tools/yulPhaser/Chromosome.cpp b/tools/yulPhaser/Chromosome.cpp index b332266ba..ae30a87e9 100644 --- a/tools/yulPhaser/Chromosome.cpp +++ b/tools/yulPhaser/Chromosome.cpp @@ -17,7 +17,7 @@ #include -#include +#include #include #include @@ -66,5 +66,5 @@ string const& Chromosome::randomOptimisationStep() { static vector stepNames = allStepNames(); - return stepNames[uniformRandomInt(0, stepNames.size() - 1)]; + return stepNames[SimulationRNG::uniformInt(0, stepNames.size() - 1)]; } diff --git a/tools/yulPhaser/Chromosome.h b/tools/yulPhaser/Chromosome.h index 6db99dc4e..bf91dbd30 100644 --- a/tools/yulPhaser/Chromosome.h +++ b/tools/yulPhaser/Chromosome.h @@ -52,9 +52,10 @@ public: bool operator==(Chromosome const& _other) const { return m_optimisationSteps == _other.m_optimisationSteps; } bool operator!=(Chromosome const& _other) const { return !(*this == _other); } + static std::string const& randomOptimisationStep(); + private: static std::vector allStepNames(); - static std::string const& randomOptimisationStep(); std::vector m_optimisationSteps; }; diff --git a/tools/yulPhaser/Population.h b/tools/yulPhaser/Population.h index e8a38dc14..276db83ff 100644 --- a/tools/yulPhaser/Population.h +++ b/tools/yulPhaser/Population.h @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include @@ -61,7 +61,7 @@ public: std::vector const& individuals() const { return m_individuals; } - static size_t randomChromosomeLength() { return binomialRandomInt(MaxChromosomeLength, 0.5); } + static size_t randomChromosomeLength() { return SimulationRNG::binomialInt(MaxChromosomeLength, 0.5); } static size_t measureFitness(Chromosome const& _chromosome, Program const& _program); friend std::ostream& operator<<(std::ostream& _stream, Population const& _population); diff --git a/tools/yulPhaser/Random.cpp b/tools/yulPhaser/Random.cpp deleted file mode 100644 index 645afb395..000000000 --- a/tools/yulPhaser/Random.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - 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 - -using namespace solidity; - -uint32_t phaser::uniformRandomInt(uint32_t _min, uint32_t _max) -{ - // TODO: Seed must be configurable - static boost::random::mt19937 generator(time(0)); - boost::random::uniform_int_distribution<> distribution(_min, _max); - - return distribution(generator); -} - -uint32_t phaser::binomialRandomInt(uint32_t _numTrials, double _successProbability) -{ - // TODO: Seed must be configurable - static boost::random::mt19937 generator(time(0)); - boost::random::binomial_distribution<> distribution(_numTrials, _successProbability); - - return distribution(generator); -} diff --git a/tools/yulPhaser/SimulationRNG.cpp b/tools/yulPhaser/SimulationRNG.cpp new file mode 100644 index 000000000..1103db5a8 --- /dev/null +++ b/tools/yulPhaser/SimulationRNG.cpp @@ -0,0 +1,56 @@ +/* + 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 + +using namespace solidity; +using namespace solidity::phaser; + +thread_local boost::random::mt19937 SimulationRNG::s_generator(SimulationRNG::generateSeed()); + +bool SimulationRNG::bernoulliTrial(double _successProbability) +{ + boost::random::bernoulli_distribution<> distribution(_successProbability); + + return static_cast(distribution(s_generator)); +} + +uint32_t SimulationRNG::uniformInt(uint32_t _min, uint32_t _max) +{ + boost::random::uniform_int_distribution<> distribution(_min, _max); + return distribution(s_generator); +} + +uint32_t SimulationRNG::binomialInt(uint32_t _numTrials, double _successProbability) +{ + boost::random::binomial_distribution<> distribution(_numTrials, _successProbability); + return distribution(s_generator); +} + +uint32_t SimulationRNG::generateSeed() +{ + // This is not a secure way to seed the generator but it's good enough for simulation purposes. + // The only thing that matters for us is that the sequence is different on each run and that + // it fits the expected distribution. It does not have to be 100% unpredictable. + return time(0); +} diff --git a/tools/yulPhaser/SimulationRNG.h b/tools/yulPhaser/SimulationRNG.h new file mode 100644 index 000000000..1e8a1da18 --- /dev/null +++ b/tools/yulPhaser/SimulationRNG.h @@ -0,0 +1,57 @@ +/* + 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 . +*/ + +#pragma once + +#include + +#include + +namespace solidity::phaser +{ + +/** + * A class that provides functions for generating random numbers good enough for simulation purposes. + * + * The functions share a common instance of the generator which can be reset with a known seed + * to deterministically generate a given sequence of numbers. Initially the generator is seeded with + * a value from @a generateSeed() which is different on each run. + * + * The numbers are not cryptographically secure so do not use this for anything that requires + * them to be truly unpredictable. + */ +class SimulationRNG +{ +public: + static bool bernoulliTrial(double _successProbability); + static uint32_t uniformInt(uint32_t _min, uint32_t _max); + static uint32_t binomialInt(uint32_t _numTrials, double _successProbability); + + /// Resets generator to a known state given by the @a seed. Given the same seed, a fixed + /// sequence of calls to the members generating random values is guaranteed to produce the + /// same results. + static void reset(uint32_t seed) { s_generator = boost::random::mt19937(seed); } + + /// Generates a seed that's different on each run of the program. + /// Does **not** use the generator and is not affected by @a reset(). + static uint32_t generateSeed(); + +private: + thread_local static boost::random::mt19937 s_generator; +}; + +} diff --git a/tools/yulPhaser/main.cpp b/tools/yulPhaser/main.cpp index bb11b9fcf..b5c522ce7 100644 --- a/tools/yulPhaser/main.cpp +++ b/tools/yulPhaser/main.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -45,6 +46,19 @@ struct CommandLineParsingResult po::variables_map arguments; }; + +void initializeRNG(po::variables_map const& arguments) +{ + uint32_t seed; + if (arguments.count("seed") > 0) + seed = arguments["seed"].as(); + else + seed = SimulationRNG::generateSeed(); + + SimulationRNG::reset(seed); + cout << "Random seed: " << seed << endl; +} + CharStream loadSource(string const& _sourcePath) { assertThrow(boost::filesystem::exists(_sourcePath), InvalidProgram, "Source file does not exist"); @@ -79,6 +93,7 @@ CommandLineParsingResult parseCommandLine(int argc, char** argv) description.add_options() ("help", "Show help message and exit.") ("input-file", po::value()->required(), "Input file") + ("seed", po::value(), "Seed for the random number generator") ; po::positional_options_description positionalDescription; @@ -121,6 +136,8 @@ int main(int argc, char** argv) if (parsingResult.exitCode != 0) return parsingResult.exitCode; + initializeRNG(parsingResult.arguments); + try { runAlgorithm(parsingResult.arguments["input-file"].as());