From aafd277ebeb5e6c5200b3e9f104e9856c3086c09 Mon Sep 17 00:00:00 2001
From: Ichthyostega
Date: Thu, 30 Nov 2023 02:13:39 +0100
Subject: [PATCH] Chain-Load: rework the pattern for dynamic rules
...as it turns out, the solution embraced first was the cleanest way
to handle dynamic configuration of parameters; just it did not work
at that time, due to the reference binding problem in the Lambdas.
Meanwhile, the latter has been resolved by relying on the LazyInit
mechanism. Thus it is now possible to abandon the manipulation by
side effect and rather require the dynamic rule to return a
''pristine instance''.
With these adjustments, it is now possible to install a rule
which expands only for some kinds of nodes; this is used here
to crate a starting point for a **reduction rule** to kick in.
---
src/lib/random-draw.hpp | 62 +----
src/lib/test/transiently.hpp | 2 +-
tests/library/random-draw-test.cpp | 68 +----
tests/vault/gear/test-chain-load-test.cpp | 22 +-
tests/vault/gear/test-chain-load.hpp | 37 +--
wiki/thinkPad.ichthyo.mm | 302 +++++++++++++++++++++-
6 files changed, 359 insertions(+), 134 deletions(-)
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...
+
+
+
+
+
+