From 8b1326129ad27e805bc7ae84ff3722bdfb28c567 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 23 Nov 2023 02:42:02 +0100 Subject: [PATCH] Library: RandomDraw - implementation complete and tested. --- src/lib/meta/function-closure.hpp | 23 +- src/lib/random-draw.hpp | 108 ++++-- tests/15library.tests | 2 +- tests/library/random-draw-test.cpp | 214 ++++++----- wiki/thinkPad.ichthyo.mm | 556 ++++++++++++----------------- 5 files changed, 447 insertions(+), 456 deletions(-) diff --git a/src/lib/meta/function-closure.hpp b/src/lib/meta/function-closure.hpp index 1e0980b10..9b6fa99bd 100644 --- a/src/lib/meta/function-closure.hpp +++ b/src/lib/meta/function-closure.hpp @@ -54,6 +54,7 @@ #include "lib/meta/function.hpp" #include "lib/meta/tuple-helper.hpp" +#include "lib/util.hpp" #include #include @@ -556,6 +557,11 @@ namespace func{ * @note the construction of this helper template does not verify or * match types to the signature. In case of mismatch, you'll get * a compilation failure from `std::bind` (which can be confusing) + * @todo 11/2023 started with modernising these functor utils. + * The most relevant bindFirst() / bindLast() operations do no longer + * rely on the PApply template. There is however the more general case + * of _binding multiple arguments,_ which is still used at a few places. + * Possibly PApply should be rewritten from scratch, using modern tooling. */ template class PApply @@ -704,6 +710,8 @@ namespace func{ namespace { // ...helpers for specifying types in function declarations.... using std::get; + using util::unConst; + template struct _Sig @@ -739,7 +747,7 @@ namespace func{ ,forward (f2) }; return [binding = move(binding)] - (ARGS ...args) mutable -> RET + (ARGS ...args) -> RET { auto& functor1 = get<0>(binding); auto& functor2 = get<1>(binding); @@ -770,11 +778,11 @@ namespace func{ ,forward (arg) }; return [binding = move(binding)] - (ARGS ...args) mutable -> RET + (ARGS ...args) -> RET { auto& functor = get<0>(binding); - // - return functor ( forward (get<1>(binding)) + // //Warning: might corrupt ownership + return functor ( forward (unConst (get<1>(binding))) , forward (args)...); }; } @@ -801,12 +809,12 @@ namespace func{ ,forward (arg) }; return [binding = move(binding)] - (ARGS ...args) mutable -> RET + (ARGS ...args) -> RET { auto& functor = get<0>(binding); // return functor ( forward (args)... - , forward (get<1>(binding))); + , forward (unConst (get<1>(binding)))); }; } }; @@ -859,7 +867,8 @@ namespace func{ } - /** close the given function over the first argument */ + /** close the given function over the first argument. + * @warning never tie an ownership-managing object by-value! */ template inline auto applyFirst (FUN&& fun, ARG&& arg) diff --git a/src/lib/random-draw.hpp b/src/lib/random-draw.hpp index eeed7be57..827081c81 100644 --- a/src/lib/random-draw.hpp +++ b/src/lib/random-draw.hpp @@ -25,38 +25,39 @@ ** Generally speaking, RandomDraw uses some suitable source of randomness to "draw" a result ** value with a limited target domain. The intended usage scenario is to parametrise some ** configuration or computation »randomly«, with well defined probabilities and value ranges. - ** A DSL is provide to simplify the common configuration and value mapping scenarios. + ** A DSL is provided to simplify the common configuration and value mapping scenarios. ** @paragraph The underlying implementation was extracted 11/2023 from (and later used by) ** TestChainLoad; there, random numbers are derived from node hash values and must be mapped ** to yield control parameters governing the topology of a DAG datastructure. Notably, a ** draw is performed on each step to decide if the graph should fork. While numerically ** simple, this turned out to be rather error-prone, and resulting code is dense and - ** difficult to understand, hence the desire to put a library component in front. + ** difficult to understand, hence the desire to wrap it into a library component. ** ** # Implementation structure ** RandomDraw inherits from a _policy template_, which in turn is-a std::function. The signature - ** of this function defines the input to work on; its output is assumed to be some variation of - ** a [»limited value«](\ref Limited). Notably, results are assumed to conform to an ordered + ** of this function defines the input to work on; its output is assumed to be some variation + ** of a [»limited value«](\ref Limited). Notably, results are assumed to conform to an ordered ** interval of integral values. The [core functionality](\ref drawLimited) is to use the value ** from the random source (a `size_t` hash), break it down by some _modulus_ to create an arbitrary - ** selection and then map this _drawn value_ into the target value range. This mapping however allows - ** to discard some of the _possible drawn values_ — which equates to define a probability of producing - ** a result different than "zero" (the neutral value of the result range). Moreover, the actual value - ** mapping can be limited and configured even more within the confines of the target type. + ** selection, followed by mapping this _drawn value_ into the target value range. This mapping allows + ** to discard some of the _possible drawn values_ however — which equates to define a probability of + ** producing a result different than "zero" (the neutral value of the result range). Moreover, the + ** actual value mapping can be limited and configured within the confines of the target type. ** - ** Additional flexibility can be gained by _binding a functor_ thereby defining further mapping and + ** Additional flexibility can be gained by _binding a functor,_ thereby defining further mapping and ** transformations. A wide array of function signatures can be accepted, as long as it is possible ** somehow to _adapt_ those functions to conform to the overall scheme as defined by the Policy base. ** Such a mapping function can be given directly at construction, or it can be set up later through - ** the configuration DSL. As a special twist, it is even possible to bind a function exposing a - ** reference to some RandomDraw instance (which can be the host object itself). Since such a - ** function can likewise accept the input randomness source, this setup opens the ability - ** for dynamic parametrisation of the result probabilities. + ** the configuration DSL. As a special twist, it is even possible to bind a function to _manipulate_ + ** the actual instance of RandomDraw dynamically. Such a function takes `RandomDraw&` as first + ** argument, plus any sequence of further arguments which can be adapted from the overall input; + ** it is invoked prior to evaluating each input value and can tweak the instance by side-effect. + ** After that, the input value is passed to the adapted instance. ** ** ## Policy template - ** For practical use, the RandomDraw template must be instantiate with a custom provided + ** For practical use, the RandomDraw template must be instantiated with a custom provided ** policy template. This configuration allows to attach to locally defined types and facilities. - ** The policy template is assumed to conform to the following requirements + ** The policy template is assumed to conform to the following requirements: ** - its base type is std::function, with a result value similar to \ref Limited ** - more specifically, the result type must be number-like and expose extension points ** to determine the `minVal()`, `maxVal()` and `zeroVal()` @@ -67,7 +68,7 @@ ** - optionally, this policy may also define a template `Adaptor`, possibly with ** specialisations for various function signatures. These adaptors are used to ** conform any mapping function and thus allow to simplify or widen the - ** possibly configurations at usage site. + ** possible configurations at usage site. ** @todo 11/2023 This is a first draft and was extracted from an actual usage scenario. ** It remains to be seen if the scheme as defined is of any further use henceforth. ** @see RandomDraw_test @@ -80,6 +81,8 @@ #define LIB_RANDOM_DRAW_H +#include "lib/error.h" +#include "lib/nocopy.hpp" #include "lib/meta/function.hpp" #include "lib/meta/function-closure.hpp" #include "lib/util-quant.hpp" @@ -90,6 +93,7 @@ namespace lib { + namespace err = lumiera::error; using lib::meta::_Fun; using std::function; @@ -103,6 +107,7 @@ namespace lib { * @tparam T underlying base type (number like) * @tparam max maximum allowed param value (inclusive) * @tparam max minimum allowed param value (inclusive) - defaults to "zero". + * @tparam zero the _neutral value_ in the value range */ template struct Limited @@ -161,6 +166,7 @@ namespace lib { template class RandomDraw : public POL + , util::MoveOnly { using Sig = typename _Fun::Sig; using Fun = function; @@ -170,6 +176,11 @@ namespace lib { Tar minResult_{Tar::minVal()}; ///< minimum result val actually to produce > min double probability_{0}; ///< probability that value is in [min .. max] \ neutral + /** due to function binding, this object + * should actually be non-copyable, + * which would defeat DLS use */ + bool fixedLocation_{false}; + /** @internal quantise into limited result value */ Tar @@ -246,20 +257,40 @@ namespace lib { { } /** - * Build a RandomDraw by adapting a value-processing function, + * Build a RandomDraw by attaching a value-processing function, * which is adapted to accept the nominal input type. The effect - * of the given function is determined by its output value + * of the given function is determined by its output value... * - `size_t`: the function output is used as source of randomness * - `double`: output is directly used as draw value `[0.0..1.0[` - * - `RandomDraw` : the function yields a parametrised instance, - * which is directly used to produce the output, bypassing any - * further local settings (#probability_, #maxResult_) + * - `void(RandomDraw&, ...)` : the function manipulates the current + * instance, to control parameters dynamically, based on input. */ template RandomDraw(FUN&& fun) - : POL{adaptOut(adaptIn(std::forward (fun)))} + : POL{} , probability_{1.0} - { } + , fixedLocation_{true} // prevent move-copy (function binds to *this) + { + mapping (forward (fun)); + } + + /** + * Object can be move-initialised, unless + * a custom processing function was installed. + */ + RandomDraw(RandomDraw && rr) + : POL{adaptOut(POL::defaultSrc)} + , maxResult_{move(rr.maxResult_)} + , minResult_{move(rr.minResult_)} + , probability_{rr.probability_} + , fixedLocation_{rr.fixedLocation_} + { + if (fixedLocation_) + throw err::State{"RandomDraw was already configured with a processing function, " + "which binds into a fixed object location. It can not be moved anymore." + , err::LUMIERA_ERROR_LIFECYCLE}; + } + /* ===== Builder API ===== */ @@ -295,6 +326,7 @@ namespace lib { { Fun& thisMapping = static_cast (*this); thisMapping = adaptOut(adaptIn(std::forward (fun))); + fixedLocation_ = true; // function will bind to *this return move (*this); } @@ -302,6 +334,7 @@ namespace lib { private: + /// metafunction: the given function wants to manipulate `*this` dynamically template struct is_Manipulator : std::false_type { }; @@ -311,11 +344,12 @@ namespace lib { : std::true_type { }; + /** @internal adapt input side of a given function to conform to the * global input arguments as defined in the Policy base function. * @return a function pre-fitted with a suitable Adapter from the Policy */ template - decltype(auto) + auto adaptIn (FUN&& fun) { using lib::meta::func::applyFirst; @@ -333,7 +367,7 @@ namespace lib { else if constexpr (is_Manipulator()) // function wants to manipulate *this dynamically... - return adaptIn (applyFirst(forward (fun), *this)); + return adaptIn (applyFirst (forward (fun), *this)); else return Adaptor::build (forward (fun)); } @@ -342,13 +376,13 @@ namespace lib { * - a function producing the overall result-type is installed as-is * - a `size_t` result is assumed be a hash and passed into #drawLimited * - likewise a `double` is assumed to be already a random val to be #limited - * - special treatment is given to a function which produces reference to some - * `RandomDraw`; this allows to produce a dynamic parametrisation, which is - * then invoked on the same input arguments to produce the result value. + * - special treatment is given to a function with `void` result, which is assumed + * to perform some manipulation on this RandomDraw instance by side-effect; + * this allows to changes parameters dynamically, based on the input data. * @return adapted function which produces a result value of type #Tar */ template - decltype(auto) + auto adaptOut (FUN&& fun) { static_assert (lib::meta::_Fun(), "Need something function-like."); @@ -365,26 +399,26 @@ namespace lib { ,[this](size_t hash){ return drawLimited(hash); } ); else - if constexpr (std::is_same_v)// ◁───────────────────────┨ function yields random value to be quantised + if constexpr (std::is_same_v)// ◁───────────────────────┨ function yields mapping value to be quantised return chained (std::forward(fun) ,[this](double rand){ return limited(rand); } ); else - if constexpr (std::is_same_v) // ◁──────────────────────────┨ function manipulates parameters by side-effect + if constexpr (std::is_same_v) // ◁────────────────────────┨ function manipulates parameters by side-effect return [functor=std::forward(fun) ,processDraw=getCurrMapping()] - (auto&& ...inArgs) mutable -> _FunRet + (auto&& ...inArgs) -> _FunRet { functor(inArgs...); // invoke manipulator with copy return processDraw (forward (inArgs)...); - }; // forward arguments to *this + }; // forward arguments to mapping-fun else static_assert (not sizeof(Res), "unable to adapt / handle result type"); NOTREACHED("Handle based on return type"); } - /** @internal capture the current mapping processing-chain as function. + /** @internal capture the current mapping processing-chain as function. * RandomDraw is-a function to process and map the input argument into a * limited and quantised output value. The actual chain can be re-configured. * This function picks up a snapshot copy of the current configuration; it is @@ -395,10 +429,10 @@ namespace lib { getCurrMapping() { Fun& currentProcessingFunction = *this; - if (not currentProcessingFunction) - return Fun{adaptOut(POL::defaultSrc)}; - else + if (fixedLocation_ and currentProcessingFunction) return Fun{currentProcessingFunction}; + else + return Fun{adaptOut(POL::defaultSrc)}; } }; diff --git a/tests/15library.tests b/tests/15library.tests index e19501ae3..873651aa9 100644 --- a/tests/15library.tests +++ b/tests/15library.tests @@ -508,7 +508,7 @@ return: 0 END -PLANNED "pick limited numbers randomly" RandomDraw_test < @@ -40,9 +39,8 @@ namespace lib { namespace test{ using util::_Fmt; - using lib::time::FSecs; - using lib::time::TimeVar; using lib::meta::_FunRet; + using err::LUMIERA_ERROR_LIFECYCLE; @@ -79,7 +77,7 @@ namespace test{ build (FUN&& fun) { return [functor=std::forward(fun)] - (size_t hash) mutable -> _FunRet + (size_t hash) -> _FunRet { return functor(uint(hash/64), uint(hash%64)); }; @@ -139,7 +137,6 @@ namespace test{ /** @test demonstrate a basic usage scenario - * @todo WIP 11/23 ✔ define ⟶ ✔ implement */ void simpleUse() @@ -151,8 +148,8 @@ namespace test{ CHECK (draw( 40) == 2); CHECK (draw( 48) == -2); CHECK (draw( 56) == -1); - CHECK (draw( 64) == 0); - CHECK (draw( 95) == 0); + CHECK (draw( 64) == 0); // values repeat after 64 steps + CHECK (draw( 95) == 0); // ~ half of each cycle yields the »neutral value« CHECK (draw( 96) == 1); CHECK (draw(127) == -1); CHECK (draw(128) == 0); @@ -163,7 +160,15 @@ namespace test{ /** @test verify configuration through policy template - * @todo WIP 11/23 ✔ define ⟶ ✔ implement + * - use the default policy, which takes no input values, + * but rather directly generates a random number; in this + * case here, input values are ∈ [0 .. 5] + * - define another policy template, to produce char values, + * while always requiring two input data values `(char,uint)`; + * moreover, define the `defaultSrc()` directly to produce the + * raw mapping values (double) using a custom formula; the + * resulting RandomDraw instance is now a function with + * two input arguments, producing char values. */ void verify_policy() @@ -177,7 +182,7 @@ namespace test{ { static double defaultSrc (char b, uint off) { return fmod ((b-'A'+off)/double('Z'-'A'), 1.0); } }; - + auto d2 = RandomDraw().probability(1.0); CHECK (d2('A', 2) == 'D'); CHECK (d2('M',10) == 'X'); @@ -187,7 +192,7 @@ namespace test{ - /** @test 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 @@ -196,13 +201,12 @@ namespace test{ * - 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) - { + { // investigate value distribution using Arr = std::array; Arr step{-1,-1,-1,-1,-1}; Arr freq{0}; @@ -559,14 +563,13 @@ namespace test{ /** @test bind custom mapping transformation functions. - * - use different translation into positional values as input for the - * actual result value mapping - * - use a mapping function with different arguments, which is wired - * by the appropriate Adapter from the Policy + * - use different translation into positional values + * as input for the actual result value mapping; + * - use a mapping function with different arguments, + * which is wired by the appropriate Adapter from the Policy; * - moreover, the concrete Policy may tap into the context, which is * demonstrated here by accessing a global variable. In practice, * this capability allows to accept custom types as data source. - * @todo WIP 11/23 ✔ define ⟶ ✔ implement */ void verify_adaptMapping() @@ -610,7 +613,7 @@ namespace test{ CHECK (d1( 1) == 0); CHECK (d1( 2) == 0); CHECK (d1( 3) == 0); - CHECK (d1( 4) == +1); + CHECK (d1( 4) == +1); // probability 0.7 CHECK (d1( 5) == +1); CHECK (d1( 6) == +2); CHECK (d1( 7) == +2); @@ -686,7 +689,7 @@ namespace test{ CHECK (d2( 4) == -2); // start here due to probability 0.5 CHECK (d2( 5) == -2); CHECK (d2( 6) == -1); - CHECK (d2( 7) == -1); // cycle-length: 4 + CHECK (d2( 7) == -1); // cycle-length: 8 CHECK (d2( 8) == 0); CHECK (d2( 9) == 0); CHECK (d2(10) == 0); @@ -694,84 +697,111 @@ namespace test{ - template - auto - _applyFirst (FUN&& fun, ARG&& arg, _Fun) - { - std::tuple binding{std::forward (fun) - ,std::forward(arg) - }; - return [binding = std::move(binding)] - (ARGS ...args) mutable -> RET - { - auto& functor = std::get<0> (binding); - // - return functor ( std::forward (std::get<1> (binding)) - , std::forward (args)...); - }; - } - template - auto - applyFirst (FUN&& fun, ARG&& arg) - { - using Ret = typename lib::meta::_Fun::Ret; - using AllArgs = typename lib::meta::_Fun::Args; - using RestArgs = typename lib::meta::Split::Tail; - using AdaptedFun = typename lib::meta::BuildFunType::Fun; - - return _applyFirst( std::forward (fun) - , std::forward (arg) - , AdaptedFun{}); - } - - /** @test TODO change the generation profile dynamically - * @todo WIP 11/23 🔁 define ⟶ implement + /** @test change the generation profile dynamically + * - a »manipulator function« gets the current RandomDraw instance, + * and any arguments that can generally be adapted for mapping functions; + * it uses these arguments to manipulate the state before each new invocation; + * in the example here, the probability is manipulated in each cycle. + * - a »manipulator function« can be installed on top of any existing configuration, + * including another custom mapping function; in the example here, we first install + * a custom mapping for the hash values, to change the cycle to 4 steps only. Then, + * in a second step, a »manipulator« is installed on top, this time accepting the + * raw hash value and manipulating the minValue. After the manipulator was invoked, + * the RandomDraw instance will be evaluated through the mapping-chain present + * prior to installation of the »manipulator« — in this case, still the mapping + * to change the cycle to 4 steps length; so in the result, the minValue is + * increased in each cycle. */ void verify_dynamicChange() { - auto d1 = Draw([](Draw& draw, uint cycle, uint){ - draw.probability((cycle+1)*0.25); - }); + auto d1 = Draw([](Draw& draw, uint cycle, uint) + { // manipulate the probability + draw.probability((cycle+1)*0.25); + }); -SHOW_EXPR(int(d1( 0))); -SHOW_EXPR(int(d1( 8))); -SHOW_EXPR(int(d1( 16))); -SHOW_EXPR(int(d1( 24))); -SHOW_EXPR(int(d1( 32))); -SHOW_EXPR(int(d1( 40))); -SHOW_EXPR(int(d1( 48))); -SHOW_EXPR(int(d1( 56))); -SHOW_EXPR(int(d1( 63))); -SHOW_EXPR(int(d1( 64 +0))); -SHOW_EXPR(int(d1( 64 +8))); -SHOW_EXPR(int(d1( 64+16))); -SHOW_EXPR(int(d1( 64+24))); -SHOW_EXPR(int(d1( 64+32))); -SHOW_EXPR(int(d1( 64+40))); -SHOW_EXPR(int(d1( 64+48))); -SHOW_EXPR(int(d1( 64+56))); -SHOW_EXPR(int(d1( 64+63))); -SHOW_EXPR(int(d1(128 +0))); -SHOW_EXPR(int(d1(128 +8))); -SHOW_EXPR(int(d1(128 +16))); -SHOW_EXPR(int(d1(128 +24))); -SHOW_EXPR(int(d1(128 +32))); -SHOW_EXPR(int(d1(128 +40))); -SHOW_EXPR(int(d1(128 +48))); -SHOW_EXPR(int(d1(128 +56))); -SHOW_EXPR(int(d1(128 +63))); -SHOW_EXPR(int(d1(128+64 +0))); -SHOW_EXPR(int(d1(128+64 +8))); -SHOW_EXPR(int(d1(128+64+16))); -SHOW_EXPR(int(d1(128+64+24))); -SHOW_EXPR(int(d1(128+64+32))); -SHOW_EXPR(int(d1(128+64+40))); -SHOW_EXPR(int(d1(128+64+48))); -SHOW_EXPR(int(d1(128+64+56))); -SHOW_EXPR(int(d1(128+64+63))); -SHOW_EXPR(int(d1(128+64+64))); + CHECK (d1( 0) == 0); + CHECK (d1( 8) == 0); + CHECK (d1( 16) == 0); + CHECK (d1( 24) == 0); + CHECK (d1( 32) == 0); + CHECK (d1( 40) == 0); + CHECK (d1( 48) == 1); // 1st cycle: 25% probability + CHECK (d1( 56) == -2); + CHECK (d1( 63) == -1); + CHECK (d1( 64 +0) == 0); + CHECK (d1( 64 +8) == 0); + CHECK (d1( 64+16) == 0); + CHECK (d1( 64+24) == 0); + CHECK (d1( 64+32) == 1); // 2nd cycle: 50% probability + CHECK (d1( 64+40) == 2); + CHECK (d1( 64+48) == -2); + CHECK (d1( 64+56) == -1); + CHECK (d1( 64+63) == -1); + CHECK (d1(128 +0) == 0); + CHECK (d1(128 +8) == 0); + CHECK (d1(128 +16) == 1); // 3rd cycle: 75% probability + CHECK (d1(128 +24) == 1); + CHECK (d1(128 +32) == 2); + CHECK (d1(128 +40) == -2); + CHECK (d1(128 +48) == -2); + CHECK (d1(128 +56) == -1); + CHECK (d1(128 +63) == -1); + CHECK (d1(128+64 +0) == 1); // 4rth cycle: 100% probability + CHECK (d1(128+64 +8) == 1); + CHECK (d1(128+64+16) == 2); + CHECK (d1(128+64+24) == 2); + CHECK (d1(128+64+32) == -2); + CHECK (d1(128+64+40) == -2); + CHECK (d1(128+64+48) == -1); + CHECK (d1(128+64+56) == -1); + CHECK (d1(128+64+63) == -1); + CHECK (d1(128+64+64) == 1); + + // NOTE: once a custom mapping function has been installed, + // the object can no longer be moved, due to reference binding. + VERIFY_ERROR (LIFECYCLE, Draw dx{move(d1)} ); + + + auto d2 = Draw([](size_t hash) + { // change cycle 4 steps only + return fmod (hash/4.0, 1.0); + }); + + CHECK (d2( 0) == +1); // 1st cycle + CHECK (d2( 1) == +2); + CHECK (d2( 2) == -2); + CHECK (d2( 3) == -1); + CHECK (d2( 4) == +1); // 2nd cycle + CHECK (d2( 5) == +2); + CHECK (d2( 6) == -2); + CHECK (d2( 7) == -1); + CHECK (d2( 8) == +1); // 3rd cycle + CHECK (d2( 9) == +2); + CHECK (d2(10) == -2); + CHECK (d2(11) == -1); + CHECK (d2(12) == +1); + + d2.mapping([](Draw& draw, size_t hash) + { // manipulate the minVal per cycle + int cycle = hash / 4; + draw.minVal(-2+cycle); + }); + + CHECK (d2( 0) == +1); // 1st cycle -> minVal ≡ -2 + CHECK (d2( 1) == +2); + CHECK (d2( 2) == -2); + CHECK (d2( 3) == -1); + CHECK (d2( 4) == +1); // 2nd cycle -> minVal ≡ -1 + CHECK (d2( 5) == +1); + CHECK (d2( 6) == +2); + CHECK (d2( 7) == -1); + CHECK (d2( 8) == +1); // 3rd cycle -> minVal ≡ 0 + CHECK (d2( 9) == +1); + CHECK (d2(10) == +2); + CHECK (d2(11) == +2); + CHECK (d2(12) == +1); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 9fdf71e76..a4c8f7f73 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -57425,7 +57425,7 @@ - + @@ -57442,9 +57442,7 @@ - - - +

der Rückgabetyp muß explizit deklariert werden — ähnlich wie decltype(auto) — denn es kann sein, daß die zusammengesetzte Funktion eine Referenz liefert @@ -57465,29 +57463,23 @@ - - - +

...also eine Funktion, der man andere Funktoren gibt, und diese baut einen manipulierten Funktor. Sowas ist extrem nützlich...

- -
+
- - - +

also die Möglichkeit, Argumente durch Funktions-artige »Binder« zu schließen, die dann aber erst zum Aufruf-Zeitpunkt aktiviert werden. In verallgemeinterter Form könnten das beliebige passende Funktoren sein, die auch weiterhin Argumente benötigen — und es müßte ein neuer Funktionstyp synthetisiert werden (klingt schlimmer als es tatsächlich umzusetzen ist, da wir so viele Tuple-Manipulationsfunktionen haben)

- -
+
@@ -57497,9 +57489,7 @@ - - - +

warum? weil das leichtgewichtiger und inline-freundlicher ist. Ein λ ist ja erst mal eine anonyme Klasse mit jedweder Storage direkt inline; es läßt sich per Kopie und Verschiebung handhaben. Wer damit Probleme hat, kann sich das resultierende Lambda immer in eine Funktion packen, und den dafür passenden Funktor-Typ sollten wir auch als Metafunktion anbieten. Der Nachteil (oder auch Vorteil) von std::function ist, daß das ein Objekt fester (relativ begrenzter) Größe ist, und notfalls automatisch ein Erweiterungsspeicher auf dem Heap mitgeführt wird. @@ -57517,9 +57507,7 @@ - - - +

und zwar @@ -57536,15 +57524,12 @@ - - + - - - +

...das bekannte leidige Problem: argument packs sind keine Typen; man kann sie daher nicht verarbeiten und weitergeben; stattdessen müsen wir einen Typ mit varidadic parameters verwenden, um am Zielort in der Argumentliste dagegen zu matchen. Da lib::meta::_Fun mehr und mehr zum Analyse-Tool für Funktions-artige Entitäten wird, bietet es sich an, diese Metafunktion selber auch als Transporter für Funktionssignaturen zu verwenden @@ -57561,6 +57546,31 @@ + + + + + + + +

+ wenn man ein Objekt wie einen unique_ptr oder einen vector per value  an den partial-application-Functor übergibt, dann wandert die Ownership zu der Kopie, die in der Closure gespeichert ist. Mit dem ersten Aufruf der Funktion wander die Ownership weiter zum jeweils gebunden Funktions-Argument, und hinterläßt in der Closure ein »verbrauchtes Objekt«. Weitere Aufrufe der gleichen Funktion bekommen dann dieses potentiell invalidierte Objekt. +

+ +
+ +
+ + + + + + + + + + + @@ -96011,7 +96021,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -96023,26 +96033,23 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- +
- + - - - +

hatte den Basis-Offset von 8 übersehen, der ja sofort abgezogen wird, bevor das Fenster überhaupt zum Tragen kommt

- -
+
@@ -96050,9 +96057,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

aber nur N21,N23 und N24 können die realisieren, @@ -96061,37 +96066,30 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
denn dann haben wir alle bis zur vorletzen N30 verbraucht

- -
+
- - - +

das war der Fix von gestern

- -
+
- - - +

die letze vorhandene Node

- -
+
@@ -96112,16 +96110,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

ist das „gut so“ ?

- -
+ @@ -96130,64 +96125,52 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

der resultierende Baum ist grundlos unregelmäßig

- -
+
- - - +

und (bei meinem aktuellen Kentnissstand): ich kann es leicht fixen...

- -
+
- - - +

Der Baum sieht nun sinnig aus — für das Auge des Mathematikers (nota bene: nicht für mein subjektives Auge). Und der Code ist tatsächlich klarer geworden, weil in der Behandlung nicht mehr zwei getrennte Belange überlagert sind.

- -
+
- - - +

ich hab jetzt dreimal „draufgedroschen“, bis ich das Wahrscheinlichkeitsverhalten hatte, das ich wollte (wenngleich ich auch dabei einen Bug entdeckt habe...)

- -
+ - +
@@ -96242,31 +96225,25 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - - - +

Ausgangspunkt: Cap ⟶ Ergebnis-Typ Draw

- -
+ - - - +

...dabei ist Cap die bereits geschaffene Hilfs-Komponente, die einen Zahlenwert mit einer Klammer von Iber/Untergrenze speichert und ihn in in diesen Rahmen hinein konditionieren kann. Das war ein relativ leichtgewichtiger erster Schritt, reicht aber nicht aus, um die techniche Komplexität aus dem Nutzkontext zu entfernen

- -
+
@@ -96323,9 +96300,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

will sagen: es ist nicht hilftreich, @@ -96334,45 +96309,35 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
das kombinieren zu wollen...

- -
+ - - - +

....aber irgendwie scheint mir genau das vorzuschweben...

- -
+
- - - +

...zumindest der Möglichkeit nach

- -
+
- - - +

hab das Gefühl daß ich schon wieder mal viel zu weit ausgreife, für ein Stück Infrastruktur, das einem einzigen Zweck dient (noch dazu lediglich für Tests) und dann wahrscheinlich nur noch rumliegt und zufällig vorbeikommende Leser verwirrt

- -
+
@@ -96383,16 +96348,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

mir sind die im Moment ganz klar, dagegen beim Leser kann man nur so etwas wie eine generelle Idee von einem DAG voraussetzen

- -
+
@@ -96401,25 +96363,20 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

aber Kaskadieren der Auswertung bleibt eine Möglichkeit

- -
+ - - - +

konsequenterweise sollte die ganze Freiheit @@ -96431,8 +96388,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
nun komplett aufgegeben werden

- -
+ @@ -96440,16 +96396,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

und gemäß YAGNI werde ich nur 10% der Möglichkeiten  wirklich brauchen

- -
+
@@ -96463,16 +96416,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...der müßte dann aber absolut gelten, und Teil der Spec werden. Ja warum nicht

- -
+
@@ -96490,16 +96440,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...da sich alle auf den gleichen Hash beziehen, und daher auch korreliert ziehen werden

- -
+
@@ -96649,16 +96596,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...diese Variante ist erst mit C++17 möglich, da nun der Template-Parameter inferiert werden kann

- -
+
@@ -96722,16 +96666,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Fazit Design-Analyse

- -
+ @@ -96739,16 +96680,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

warum? eine zweckgebundene Lösung mit diesem Komplexitätsgrad wäre nicht zu rechtfertigen; man würde dann nach einer einfacheren Alternative suchen. Konkret komme ich allerdings von dieser einfacheren Alternative her, weil sie immer noch nicht einfach und klar genug war. Insofern bin ich froh, überhaupt eine Lösung zu haben, die im Rückblick sinnvoll darstellbar ist

- -
+
@@ -96757,10 +96695,18 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - + + + + +

+ Library-Komponente extrahieren: RandomDraw +

+ +
+ + + @@ -96792,27 +96738,21 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

geht heutzutage relativ kompakt mit generischen Lambdas

- -
+ - - - +

...ich hab nicht „lesbar“ gesagt...

- -
+
@@ -96840,16 +96780,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

muß die Funktionsargumente kopieren

- -
+
@@ -96877,16 +96814,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Beispiel: -2 .. 0 ..+2  ⟹ Wahrscheinlichkeit definiert für Werte ≠ 0

- -
+
@@ -96902,27 +96836,21 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

natürliche Rundung is towards zero

- -
+ - - - +

...und das ist obendrein implementation defined...

- -
+
@@ -96934,7 +96862,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -96949,16 +96877,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

und zwar unter den einfachen Fall

- -
+ @@ -96969,52 +96894,41 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

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, @@ -97026,13 +96940,12 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
korrekt mit behandelt, indem die 1.Hälfte wegfällt

- -
+
- + @@ -97063,16 +96976,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

grade wenn die Maximalgrenze nahe an einer Zweierpotenz liegt (Beispiel 10 Werte) dann werden einige Werte deutlich wahrscheinlicher

- -
+
@@ -97100,9 +97010,34 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + + + + + +

+ ...würde den DSL-Gebrauch stark einschränken. Das häufigste Idiom dürfte sein: auto draw = Draw().probaility(###); +

+ +
+ +
+ + + + +

+ verwende stattdessen eine Sperre, die »eintrastet«, sobald eine custom-defined-function installiert wurde. Ab dem Punkt könnte sonst jeder Aufruf zum SEGFAULT führen +

+ +
+
+
+ + + @@ -97112,51 +97047,42 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - - - +

das ist für den aktuellen Use-case unabdingbar

- -
+ - - - +

...im Gegensatz zu dem ganzen weiteren Schlonz, den ich die letzten Tage eingebaut habe, „weils grad gang“; ich brauche irgende einen Mechanismus, über den ein später hinzukonfigurierter Funktor in Abhängigkeit von aktuellen Parameter-Daten an der Parametrisierung des RandomDraw "drehen" kann...

- -
+
- + - + - - - +

dangling Reference ⟹ CRASH

- -
+ @@ -97170,18 +97096,15 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - - +

Lösung-2: er bindet irgendwie eine Referenz darauf

- -
+ @@ -97213,42 +97136,37 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + - - - +

das wäre schön sauber — ABER ...

- -
+ - + - + - - - +

läuft auf eine partielle Anwendung der Funktion hinaus

- -
+ @@ -97269,28 +97187,48 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - - + + - + + + + + +

+ genausogut kann man hier einen const_cast machen +

+ +
+ +
+ + + + + + + - - - - + + + + + + @@ -97301,24 +97239,27 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + +
- - + + + + +
- - + + @@ -97406,13 +97347,16 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + + + +
+
+
-
-
-
@@ -97563,9 +97507,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

kein Platz für eine r-Node ⟹ @@ -97574,8 +97516,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
ein Vorgänger könnte mit allen Nachfolgern verbunden sein

- -
+ @@ -97584,9 +97525,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

die Situtation ist nur so lange logisch konsistent, @@ -97595,37 +97534,30 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
wie ich die carry-on-Node einfügen kann

- -
+ - - - +

...denn dann würden wir eine Kette unterbrechen, und das darf keinesfalls passieren (es sei denn, das ist per Ctrl-Rule so vorgegeben worden)

- -
+
- - - +

...das liegt an der Grundstrukture der Topologie-Generierung: wir iterieren ja über die Vorgänger. Solange wir einen Vorgänger in der Hand haben, kann dieser noch nicht per Reduction oder Carry-on an irgend einen Nachhänger verschaltet worden sein (aber der Vorgänger kann sehr wohl per Expansion mit allen Nachfolgern verbunden sein)

- -
+
@@ -98014,9 +97946,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...aber sage erst mal YAGNI @@ -104300,55 +104230,43 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

[](auto&& ...args) -> RetType { return fun (forward<decltype(args)> (args) ...); }

- -
+
- - - +

weil bei normaler Typinferenz ein decay stattfindet

- -
+ - - - +

„zum Beispiel...“ wenn die im Lamba verwendete Funktion eine Referenz zurückliefert, dann passiert ein decay auf den unterliegenden Objekttyp, d.h. man erzeugt eine schwebende Kopie....

- -
+
- - - +

hab mich grad geschnitten und 5 Stunden nach der Ursache gesucht

- -
+