diff --git a/src/lib/random-draw.hpp b/src/lib/random-draw.hpp index 6afd82188..115beb731 100644 --- a/src/lib/random-draw.hpp +++ b/src/lib/random-draw.hpp @@ -48,11 +48,11 @@ ** 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 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. + ** the configuration DSL. As a special twist, it is even possible to change parameters dynamically, + ** based on the current input value. This requires the mapping function to construct a pristine + ** instance of RandomDraw, apply configuration based on the input and then return this instance + ** by value — without ever »engaging« and invoking; this dynamically configured instance will + ** then be invoked once, passing the current input values to yield the result value. ** ** ## Policy template ** For practical use, the RandomDraw template must be instantiated with a custom provided @@ -344,17 +344,6 @@ namespace lib { private: - /// metafunction: the given function wants to manipulate `*this` dynamically - template - struct is_Manipulator - : std::false_type { }; - - template - struct is_Manipulator - : std::true_type { }; - - - /** @internal adapt a function and install it to control drawing and mapping */ template void @@ -383,10 +372,6 @@ namespace lib { if constexpr (std::is_same_v) // function accepts same arguments as this RandomDraw return forward (fun); // pass-through directly - else - if constexpr (is_Manipulator()) - // function wants to manipulate *this dynamically... - return adaptIn (applyFirst (forward (fun), *this)); else {// attempt to find a custom adaptor via Policy template using Adaptor = typename POL::template Adaptor; @@ -398,9 +383,9 @@ 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 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. + * - special treatment is given to a function returning a `RandomDraw` instance + * by value; such a function is assumed to set some parametrisation based + * on the input data, allowing to change parameters dynamically. * @return adapted function which produces a result value of type #Tar */ template @@ -426,36 +411,17 @@ namespace lib { ,[this](double rand){ return limited(rand); } ); else - if constexpr (std::is_same_v) // ◁────────────────────────┨ function manipulates parameters by side-effect - return [functor=std::forward(fun) - ,processDraw=getCurrMapping()] + if constexpr (std::is_same_v) // ◁───────────────────┨ RandomDraw with dynamically adjusted parameters + return [functor=std::forward(fun)] (auto&& ...inArgs) -> _FunRet - { - functor(inArgs...); // invoke manipulator with copy - return processDraw (forward (inArgs)...); - }; // forward arguments to mapping-fun + { // invoke manipulator with copy + RandomDraw adaptedDraw = functor(inArgs...); + return adaptedDraw (forward (inArgs)...); + }; // 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. - * 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 - * used to build a chain of functions, which incorporates this current function. - * If no function was configured yet, the default processing chain is returned. - */ - Fun - getCurrMapping() - { - Fun& currentProcessingFunction = *this; - if (currentProcessingFunction) - return Fun{currentProcessingFunction}; - else - return Fun{adaptOut(POL::defaultSrc)}; - } }; diff --git a/src/lib/test/transiently.hpp b/src/lib/test/transiently.hpp index 49f28ee40..79e7e8573 100644 --- a/src/lib/test/transiently.hpp +++ b/src/lib/test/transiently.hpp @@ -65,7 +65,7 @@ namespace test{ ~Transiently() { - manipulated_ = originalVal_; + manipulated_ = std::move (originalVal_); } template diff --git a/tests/library/random-draw-test.cpp b/tests/library/random-draw-test.cpp index 8996de0e6..23e21f5c4 100644 --- a/tests/library/random-draw-test.cpp +++ b/tests/library/random-draw-test.cpp @@ -723,32 +723,24 @@ namespace test{ CHECK (d2( 8) == 0); CHECK (d2( 9) == 0); CHECK (d2(10) == 0); + + // 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(d2)} ); } - /** @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; + /** @test change the generation profile dynamically, based on current input; * 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) - { // manipulate the probability - draw.probability((cycle+1)*0.25); + auto d1 = Draw([](uint cycle, uint) + { // dynamically control probability + return Draw().probability((cycle+1)*0.25); }); CHECK (d1( 0) == 0); @@ -788,50 +780,6 @@ namespace test{ 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/tests/vault/gear/test-chain-load-test.cpp b/tests/vault/gear/test-chain-load-test.cpp index ae4d93ea0..4dc84b60e 100644 --- a/tests/vault/gear/test-chain-load-test.cpp +++ b/tests/vault/gear/test-chain-load-test.cpp @@ -298,9 +298,6 @@ namespace test { CHECK (stat.indicators[STAT_JOIN].pL == "0.78378378"_expect); // but also almost one join per level to deal with the limitation CHECK (stat.indicators[STAT_FORK].frac == "0.24609375"_expect); // 25% forks (there is just not enough room for more forks) CHECK (stat.indicators[STAT_JOIN].frac == "0.11328125"_expect); // and 11% joins -//SHOW_EXPR(graph.getHash()) -//SHOW_EXPR(stat.indicators[STAT_NODE].pL) -//SHOW_EXPR(stat.indicators[STAT_JOIN].cL) } @@ -312,8 +309,27 @@ namespace test { void verify_Reduction() { + ChainLoad32 graph; + // moderate symmetrical expansion with 40% probability and maximal +2 links + graph.expansionRule(graph.rule().mapping([&](Node* n) + { + if (isStart(n)) + return graph.rule().probability(1.0).maxVal(8).mapping([](size_t){ return 1.0; }); + else + return graph.rule(); + })) + .reductionRule(graph.rule().probability(0.2).maxVal(3).shuffle(555)) + .buildToplolgy() + .printTopologyDOT() + .printTopologyStatistics() + ; +// CHECK (graph.getHash() == 0xAE332109116C5100); +SHOW_EXPR(graph.getHash()) } +//SHOW_EXPR(graph.getHash()) +//SHOW_EXPR(stat.indicators[STAT_NODE].pL) +//SHOW_EXPR(stat.indicators[STAT_JOIN].cL) diff --git a/tests/vault/gear/test-chain-load.hpp b/tests/vault/gear/test-chain-load.hpp index 2fb402230..4a18ad27e 100644 --- a/tests/vault/gear/test-chain-load.hpp +++ b/tests/vault/gear/test-chain-load.hpp @@ -47,12 +47,13 @@ ** configurable _control functions_ driven by each node's (hash)value. This way, each node ** can optionally fork out to several successor nodes, but can also reduce and combine its ** predecessor nodes; additionally, new chains can be spawned (to simulate the effect of - ** data loading Jobs without predecessor). The computation always begins with the _root - ** node_, proceeds over the node links and finally leads to the _top node,_ which connects - ** all chains of computation, leaving no dead end. The probabilistic rules controlling the - ** topology can be configured using the lib::RandomDraw component, allowing either just - ** to set a fixed probability or to define elaborate dynamic configurations based on the - ** graph height or node connectivity properties. + ** data loading Jobs without predecessor) and chains can be deliberately pruned, possibly + ** splitting the computation into several disjoint sub-graphs. Anyway, the computation always + ** begins with the _root node_, proceeds over the node links and finally connects any open + ** chains of computation to the _top node,_ leaving no dead end. The probabilistic rules + ** controlling the topology can be configured using the lib::RandomDraw component, allowing + ** either just to set a fixed probability or to define elaborate dynamic configurations + ** based on the graph height or node connectivity properties. ** ** ## Usage ** A TestChainLoad instance is created with predetermined maximum fan factor and a fixed @@ -80,7 +81,7 @@ #include "vault/common.hpp" -#include "lib/test/test-helper.hpp" +#include "lib/test/transiently.hpp" //#include "vault/gear/job.h" //#include "vault/gear/activity.hpp" @@ -127,6 +128,7 @@ namespace test { using util::toString; using util::showHashLSB; using lib::meta::_FunRet; + using lib::test::Transiently; // using std::forward; // using std::string; @@ -348,6 +350,9 @@ namespace test { /** * Use current configuration and seed to (re)build Node connectivity. + * While working in-place, the wiring and thus the resulting hash values + * are completely rewritten, progressing from start and controlled by + * evaluating the _drawing rules_ on the current node, computing its hash. */ TestChainLoad&& buildToplolgy() @@ -358,11 +363,11 @@ namespace test { Node* node = &nodes_->front(); size_t level{0}; - // local copy of all rules (non-copyable, once engaged) - Rule expansionRule = expansionRule_; - Rule reductionRule = reductionRule_; - Rule seedingRule = seedingRule_; - Rule pruningRule = pruningRule_; + // transient snapshot of rules (non-copyable, once engaged) + Transiently originalExpansionRule{expansionRule_}; + Transiently originalReductionRule{reductionRule_}; + Transiently originalseedingRule {seedingRule_}; + Transiently originalPruningRule {pruningRule_}; // prepare building blocks for the topology generation... auto moreNext = [&]{ return next->size() < maxFan; }; @@ -392,10 +397,10 @@ namespace test { for (Node* o : *curr) { // follow-up on all Nodes in current level... o->calculate(); - if (apply (pruningRule,o)) + if (apply (pruningRule_,o)) continue; // discontinue - size_t toSeed = apply (seedingRule, o); - size_t toExpand = apply (expansionRule,o); + size_t toSeed = apply (seedingRule_, o); + size_t toExpand = apply (expansionRule_,o); while (0 < toSeed and spaceLeft()) { // start a new chain from seed addNode(this->getSeed()); @@ -410,7 +415,7 @@ namespace test { if (not toReduce) { // carry-on chain from o r = spaceLeft()? addNode():nullptr; - toReduce = apply (reductionRule, o); + toReduce = apply (reductionRule_, o); } else --toReduce; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 0d433e522..1346f0699 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -96233,6 +96233,40 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + +

+ das bedingt eine dynamische Regel +

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

+ mit Abstrichen: jetzt läufts... +

+ +
+ + +
+
+
+
@@ -96795,7 +96829,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -98164,6 +98198,57 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + +

+ es ist zu »funktional« +

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

+ konkret hilft mir: +

+

+ die »Manipulation« kann per Definitionem +

+

+ auf einem leeren Objekt statfinden... +

+ +
+ + + +

+ vom konkreten use-Case her gedacht, ist es typischerweise gar nicht hilfreich, wenn das zu manipulierende Objekt schon »Zustand« hat; man schafft viel klarere Verhältnisse, wenn es per Definition ein default-konstruiertes Objekt ist +

+ +
+ +
+
@@ -98195,8 +98280,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -98221,6 +98307,12 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + @@ -98262,8 +98354,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -98380,6 +98473,78 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + +
+
+ + + + +

+ dynamische Manipulation versaut this +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -98516,7 +98681,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -98538,7 +98703,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -98579,9 +98744,134 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

+ + + +

+ ...da RandomDraw jetzt so wunderbar elegant kopierbar ist... +

+ +
- + + + + + + + + + + +

+ ...während der Berechnung der Topologie (und das ist die einzige tatsächliche Verwendung der Regeln) arbeiten wir auf lokalen Kopien; eine dynamische Änderung aus der Berechnung heraus hinterläßt Spuren in der »schwebenden« globalen Regelkonfiguration — dringt aber grade nicht in die aktivierte Regel-Konfiguration durch. +

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

+ das macht fast genau das hier Benötigte +

+ +
+
+ + + + + + + +

+ ...zum einen ist TestChainLoad selbst ein Test-Tool +

+

+ ...und außerdem ist der lib::test - Namespace weder verboten noch gefährlich +

+

+ ...und schließlich steht der Makro-Name TRANSIENTLY nicht wirklich in Konkurrenz zu irgendwas +

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

+ ...sondern nur eine Verbesserung in der Code-Struktur (deshalb belasse ich es jetzt auch dabei).... +

+

+ Das eigentliche Problem zeigte sich erst einen Schritt weiter: es ist eben nicht möglich, eine bereits gebundene Mapping-Funktion nochmal zu „entbinden“ und separat aufzurufen.. +

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

+ mit Abstrichen: jetzt läufts... +

+ +
+ + +