From 2578df7c1d950316b536a16895a048205c2b9fde Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 22 Nov 2023 02:36:34 +0100 Subject: [PATCH] Library: RandomDraw - verify numerics (II) - strive at complete branch coverage for the mapping function - decide that the neutral value can deliberately lie outside the value range, in which case the probability setting controls the number of _value_ result incidents vs neutral value result incidents. - introduce a third path to define this case clearly - implement the range setting Builder-API functions - absorb boundrary and illegal cases --- src/lib/random-draw.hpp | 30 +++- tests/library/random-draw-test.cpp | 271 +++++++++++++++++++++++------ wiki/thinkPad.ichthyo.mm | 207 ++++++++++++++++++++-- 3 files changed, 435 insertions(+), 73 deletions(-) diff --git a/src/lib/random-draw.hpp b/src/lib/random-draw.hpp index 33e651202..9c2446373 100644 --- a/src/lib/random-draw.hpp +++ b/src/lib/random-draw.hpp @@ -150,10 +150,12 @@ namespace lib { + /**********************************************************//** * A component and builder to draw limited parameter values * based on some source of randomness (or hash input). * Effectively this is a function which "draws" on invocation. + * Probabilities and ranges can be configured by builder API. * @tparam POL configuration policy baseclass */ template @@ -165,7 +167,7 @@ namespace lib { Tar maxResult_{Tar::maxVal()}; ///< maximum result val actually to produce < max Tar minResult_{Tar::minVal()}; ///< minimum result val actually to produce > min - double probability_{0}; ///< probability that value is in [1 .. m] + double probability_{0}; ///< probability that value is in [min .. max] \ neutral /** @internal quantise into limited result value */ @@ -180,12 +182,12 @@ namespace lib { REQUIRE (minResult_ < maxResult_); REQUIRE (0.0 <= probability_); REQUIRE (probability_ <= 1.0); - auto org = util::max (Tar::zeroVal(), minResult_); double q = (1.0 - probability_); if (val < q) // control probability of values ≠ neutral return Tar::zeroVal(); val -= q; // [0 .. [q .. 1[ val /= probability_; // [0 .. 1[ + auto org = Tar::zeroVal(); if (org == minResult_) { // simple standard case val *= maxResult_ - org; // [0 .. m[ @@ -193,6 +195,15 @@ namespace lib { val += CAP_EPSILON; // round down yet absorb dust return Tar{floor (val)}; } + else + if (org < minResult_ or org > maxResult_) + { // disjoint form origin, but compact + org = minResult_; // ensure all values covered + val *= maxResult_ - org + 1; // [o .. m] + val += org; + val += CAP_EPSILON; + return Tar{floor (val)}; + } else// Origin is somewhere within value range {// ==> wrap "negative" part above max // to map 0.0 ⟼ org (≙neutral) @@ -253,14 +264,25 @@ namespace lib { RandomDraw&& probability (double p) { - probability_ = p; + probability_ = util::limited (0.0, p ,1.0); return move (*this); } RandomDraw&& maxVal (Tar m) { - maxResult_ = m; + maxResult_ = util::min (m, Tar::maxVal()); + if (minResult_>=maxResult_) + minVal (--m); + return move (*this); + } + + RandomDraw&& + minVal (Tar m) + { + minResult_ = util::max (m, Tar::minVal()); + if (maxResult_<=minResult_) + maxVal (++m); return move (*this); } diff --git a/tests/library/random-draw-test.cpp b/tests/library/random-draw-test.cpp index c44225d43..74c8b4349 100644 --- a/tests/library/random-draw-test.cpp +++ b/tests/library/random-draw-test.cpp @@ -26,16 +26,12 @@ - #include "lib/test/run.hpp" -//#include "lib/test/test-helper.hpp" #include "lib/random-draw.hpp" #include "lib/time/timevalue.hpp" #include "lib/test/diagnostic-output.hpp"////////////////////TODO #include "lib/format-string.hpp" -//#include "lib/util.hpp" -//#include #include @@ -43,29 +39,28 @@ namespace lib { namespace test{ -// using util::isSameObject; -// using std::rand; using util::_Fmt; using lib::time::FSecs; using lib::time::TimeVar; -// namespace error = lumiera::error; -// using error::LUMIERA_ERROR_FATAL; -// using error::LUMIERA_ERROR_STATE; - namespace { -// const Literal THE_END = "all dead and hero got the girl"; + namespace { // policy and configuration for test... + /** + * @note the test uses a rather elaborate result value setting + * - produces five distinct values + * - value range is symmetrical to origin + * - zero is defined as the _neutral value_ + * - accepts a `size_t` hash value as basic input + */ struct SymmetricFive : function(size_t)> { + /** by default use the hash directly as source of randomness */ static size_t defaultSrc (size_t hash) { return hash; } - /** - * @internal helper to expose the signature `size_t(size_t)` - * by wrapping a given lambda or functor. - */ + /** Adaptor to handle further mapping functions */ template struct Adaptor { @@ -97,9 +92,9 @@ namespace test{ }; } }; - }; - } + // + }//(End) Test config using Draw = RandomDraw; @@ -110,9 +105,12 @@ namespace test{ * @test Verify a flexible builder for random-value generators; using a config template, * these can be outfitted to use a suitable source of randomness and to produce * values from a desired target type and limited range. - * - TODO - * @see result.hpp - * @see lib::ThreadJoinable usage example + * - for this test, generated result values are ∈ [-2 .. 0 .. +2] + * - no actual randomness is used; rather a `size_t` challenge is + * sent in to verify precisely deterministic numeric results. + * @see random-draw.hpp + * @see vault::gear::TestChainLoad as usage example + * @see SchedulerStress_test */ class RandomDraw_test : public Test @@ -131,32 +129,13 @@ namespace test{ - /** @test TODO demonstrate a basic usage scenario - * @todo WIP 11/23 ✔ define ⟶ 🔁 implement + /** @test demonstrate a basic usage scenario + * @todo WIP 11/23 ✔ define ⟶ ✔ implement */ void simpleUse() { auto draw = Draw().probability(0.5); -//SHOW_EXPR (int(draw(0) )); -//SHOW_EXPR (int(draw(16 ))); -//SHOW_EXPR (int(draw(31 ))); -//SHOW_EXPR (int(draw(32 ))); -//SHOW_EXPR (int(draw(39 ))); -//SHOW_EXPR (int(draw(40 ))); -//SHOW_EXPR (int(draw(47 ))); -//SHOW_EXPR (int(draw(48 ))); -//SHOW_EXPR (int(draw(55 ))); -//SHOW_EXPR (int(draw(56 ))); -//SHOW_EXPR (int(draw(63 ))); -//SHOW_EXPR (int(draw(64 ))); -//SHOW_EXPR (int(draw(65 ))); -//SHOW_EXPR (int(draw(95 ))); -//SHOW_EXPR (int(draw(96 ))); -//SHOW_EXPR (int(draw(127))); -//SHOW_EXPR (int(draw(128))); -//SHOW_EXPR (int(draw(168))); -//SHOW_EXPR (int(draw(256))); CHECK (draw( 0) == 0); CHECK (draw( 16) == 0); CHECK (draw( 32) == 1); @@ -199,19 +178,24 @@ namespace test{ - /** @test TODO verify random number transformations + /** @test verify random number transformations * - use a Draw instance with result values `[-2..0..+2]` * - values are evenly distributed within limits of quantisation * - the probability parameter controls the amount of neutral results - * @todo WIP 11/23 🔁 define ⟶ ✔ implement + * - maximum and minimum value settings will be respected + * - the interval [min..max] is independent from neutral value + * - probability defines the cases within [min..max] \ neutral + * - all other cases `q = 1 - p` will yield the neutral value + * - implausible max/min settings will be corrected automatically + * @todo WIP 11/23 ✔ define ⟶ ✔ implement */ void verify_numerics() { auto distribution = [](Draw const& draw) { - using Arr = std::array; - Arr step{0}; + using Arr = std::array; + Arr step{-1,-1,-1,-1,-1}; Arr freq{0}; for (uint i=0; i<128; ++i) { @@ -219,7 +203,7 @@ namespace test{ CHECK (-2 <= res and res <= +2); int idx = res+2; freq[idx] += 1; - if (res and not step[idx]) + if (step[idx] < 0) step[idx] = i; } _Fmt line{"val:%+d (%02d|%5.2f%%)\n"}; @@ -241,11 +225,11 @@ namespace test{ report += distribution(draw); CHECK (report == "+++| --empty-- \n" - "val:-2 (00| 0.00%)\n" - "val:-1 (00| 0.00%)\n" + "val:-2 (-1| 0.00%)\n" + "val:-1 (-1| 0.00%)\n" "val:+0 (00|100.00%)\n" - "val:+1 (00| 0.00%)\n" - "val:+2 (00| 0.00%)\n"_expect); + "val:+1 (-1| 0.00%)\n" + "val:+2 (-1| 0.00%)\n"_expect); draw.probability(1.0); @@ -266,8 +250,8 @@ namespace test{ "+++| p ≔ 1.0 \n" "val:-2 (32|25.00%)\n" "val:-1 (48|25.00%)\n" - "val:+0 (00| 0.00%)\n" - "val:+1 (01|25.00%)\n" + "val:+0 (-1| 0.00%)\n" + "val:+1 (00|25.00%)\n" "val:+2 (16|25.00%)\n"_expect); @@ -378,6 +362,189 @@ namespace test{ "val:+0 (00|90.62%)\n" "val:+1 (58| 3.12%)\n" "val:+2 (60| 1.56%)\n"_expect); + + + // ═════════ + draw.probability(0.5).maxVal(1); + CHECK (draw( 0) == 0); + CHECK (draw( 16) == 0); + CHECK (draw( 31) == 0); + CHECK (draw( 32) == +1); + CHECK (draw( 42) == +1); + CHECK (draw( 43) == -2); + CHECK (draw( 53) == -2); + CHECK (draw( 54) == -1); + CHECK (draw( 63) == -1); + CHECK (draw( 64) == 0); + CHECK (draw( 95) == 0); + CHECK (draw( 96) == +1); + + report = "+++| p ≔ 0.50 max ≔ 1 \n"; + report += distribution(draw); + CHECK (report == + "+++| p ≔ 0.50 max ≔ 1 \n" + "val:-2 (43|17.19%)\n" + "val:-1 (54|15.62%)\n" + "val:+0 (00|50.00%)\n" + "val:+1 (32|17.19%)\n" + "val:+2 (-1| 0.00%)\n"_expect); + + + draw.probability(1.0).maxVal(1); + CHECK (draw( 0) == +1); + CHECK (draw( 16) == +1); + CHECK (draw( 21) == +1); + CHECK (draw( 22) == -2); + CHECK (draw( 42) == -2); + CHECK (draw( 43) == -1); + CHECK (draw( 63) == -1); + CHECK (draw( 64) == +1); + CHECK (draw( 85) == +1); + CHECK (draw( 86) == -2); + CHECK (draw( 96) == -2); + + report = "+++| p ≔ 1.0 max ≔ 1 \n"; + report += distribution(draw); + CHECK (report == + "+++| p ≔ 1.0 max ≔ 1 \n" + "val:-2 (22|32.81%)\n" + "val:-1 (43|32.81%)\n" + "val:+0 (-1| 0.00%)\n" + "val:+1 (00|34.38%)\n" + "val:+2 (-1| 0.00%)\n"_expect); + + + // ═════════ + draw.probability(0.5).maxVal(0); + CHECK (draw( 0) == 0); + CHECK (draw( 31) == 0); + CHECK (draw( 32) == -2); + CHECK (draw( 47) == -2); + CHECK (draw( 48) == -1); + CHECK (draw( 63) == -1); + CHECK (draw( 64) == 0); + CHECK (draw( 95) == 0); + CHECK (draw( 96) == -2); + + report = "+++| p ≔ 0.50 max ≔ 0 \n"; + report += distribution(draw); + CHECK (report == + "+++| p ≔ 0.50 max ≔ 0 \n" + "val:-2 (32|25.00%)\n" + "val:-1 (48|25.00%)\n" + "val:+0 (00|50.00%)\n" + "val:+1 (-1| 0.00%)\n" + "val:+2 (-1| 0.00%)\n"_expect); + + + draw.probability(1.0).maxVal(0); + CHECK (draw( 0) == -2); + CHECK (draw( 31) == -2); + CHECK (draw( 32) == -1); + CHECK (draw( 63) == -1); + CHECK (draw( 64) == -2); + CHECK (draw( 96) == -1); + + report = "+++| p ≔ 1.0 max ≔ 0 \n"; + report += distribution(draw); + CHECK (report == + "+++| p ≔ 1.0 max ≔ 0 \n" + "val:-2 (00|50.00%)\n" + "val:-1 (32|50.00%)\n" + "val:+0 (-1| 0.00%)\n" + "val:+1 (-1| 0.00%)\n" + "val:+2 (-1| 0.00%)\n"_expect); + + + // ═════════ + draw.probability(0.5).maxVal(-1); + CHECK (draw( 32) == -2); + CHECK (draw( 47) == -2); + CHECK (draw( 48) == -1); + CHECK (draw( 63) == -1); + CHECK (draw( 64) == 0); + CHECK (draw( 95) == 0); + CHECK (draw( 96) == -2); + + report = "+++| p ≔ 0.50 max ≔ -1 \n"; + report += distribution(draw); + CHECK (report == + "+++| p ≔ 0.50 max ≔ -1 \n" + "val:-2 (32|25.00%)\n" + "val:-1 (48|25.00%)\n" + "val:+0 (00|50.00%)\n" + "val:+1 (-1| 0.00%)\n" + "val:+2 (-1| 0.00%)\n"_expect); + + + draw.probability(1.0).maxVal(-1); + CHECK (draw( 0) == -2); + CHECK (draw( 31) == -2); + CHECK (draw( 32) == -1); + CHECK (draw( 63) == -1); + CHECK (draw( 64) == -2); + + report = "+++| p ≔ 1.0 max ≔ -1 \n"; + report += distribution(draw); + CHECK (report == + "+++| p ≔ 1.0 max ≔ -1 \n" + "val:-2 (00|50.00%)\n" + "val:-1 (32|50.00%)\n" + "val:+0 (-1| 0.00%)\n" + "val:+1 (-1| 0.00%)\n" + "val:+2 (-1| 0.00%)\n"_expect); + + + // ═════════ + draw.probability(0.5).maxVal(2).minVal(1); + CHECK (draw( 32) == +1); + CHECK (draw( 48) == +2); + CHECK (draw( 63) == +2); + CHECK (draw( 64) == 0); + + report = "+++| p ≔ 0.50 min ≔ 1 max ≔ 2 \n"; + report += distribution(draw); + CHECK (report == + "+++| p ≔ 0.50 min ≔ 1 max ≔ 2 \n" + "val:-2 (-1| 0.00%)\n" + "val:-1 (-1| 0.00%)\n" + "val:+0 (00|50.00%)\n" + "val:+1 (32|25.00%)\n" + "val:+2 (48|25.00%)\n"_expect); + + + draw.probability(1.0).maxVal(2).minVal(1); + CHECK (draw( 0) == +1); + CHECK (draw( 32) == +2); + CHECK (draw( 63) == +2); + CHECK (draw( 64) == +1); + + report = "+++| p ≔ 1.0 min ≔ 1 max ≔ 2 \n"; + report += distribution(draw); + CHECK (report == + "+++| p ≔ 1.0 min ≔ 1 max ≔ 2 \n" + "val:-2 (-1| 0.00%)\n" + "val:-1 (-1| 0.00%)\n" + "val:+0 (-1| 0.00%)\n" + "val:+1 (00|50.00%)\n" + "val:+2 (32|50.00%)\n"_expect); + + + // ═════════ + draw.probability(0.5).maxVal(0); + CHECK (draw( 32) == -1); + CHECK (draw( 63) == -1); + CHECK (draw( 64) == 0); + + report = "+++| p ≔ 0.50 max ≔ 0 (-> min ≔ -1) \n"; + report += distribution(draw); + CHECK (report == + "+++| p ≔ 0.50 max ≔ 0 (-> min ≔ -1) \n" + "val:-2 (-1| 0.00%)\n" + "val:-1 (32|50.00%)\n" + "val:+0 (00|50.00%)\n" + "val:+1 (-1| 0.00%)\n" + "val:+2 (-1| 0.00%)\n"_expect); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index cc8fd6860..1649645c9 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -96728,13 +96728,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - - + + @@ -96786,10 +96786,115 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + - - - + + + + + + + + + + + + + + + + + + + + +

+ und zwar unter den einfachen Fall +

+ + +
+ + + + + + + + + + + + + + +

+ Konsequenz: speziellen Fall schaffen... +

+ + +
+ + + + + + + +

+ explizit org ≔ minResult_ +

+ + +
+
+ + + + + + + + +

+ dabei beachten: val ≡ 1.0 liegt am Rand und wird genau nicht mehr erreicht +

+ + +
+
+
+
+ + + + + + +

+ der Fall, daß der neutrale Wert am oberen Rand liegt, +

+

+ wird mehr zufälligerweise vom innenliegenden Fall +

+

+ korrekt mit behandelt, indem die 1.Hälfte wegfällt +

+ + +
+ +
+
+
+ + + @@ -96797,6 +96902,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ @@ -96834,12 +96940,19 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + + + + + + - - + + @@ -96848,18 +96961,27 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + + + - + + + - - - + + + + + + @@ -96880,6 +97002,57 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +