Library: RandomDraw - rework mapping rule to support origin

The first step was to allow setting a minimum value,
which in theory could also be negative (at no point is the
code actually limited to unsigned values; this is rather
the default in practice).

But reconsidering this extensions, then you'd also want
the "neutral value" to be handled properly. Within context,
this means that the *probability* controls when values other
than the neutral value are produced; especially with p = 1.0
the neutral value shall not be produced at all
This commit is contained in:
Fischlurch 2023-11-21 17:49:50 +01:00
parent 75dd4210f2
commit 5b9a463b38
3 changed files with 209 additions and 49 deletions

View file

@ -22,25 +22,54 @@
/** @file random-draw.hpp
** Build a component to select limited values randomly.
** Prototyping to find a suitable DSL to configure drawing of random numbers and mapping results.
** The underlying implementation shall be extracted from (and later used by) TestChainLoad; the
** random numbers will be derived from node hash values and must be mapped to yield parameters
** limited to a very small value range. While numerically simple, this turns out to be rather
** error-prone, hence the desire to put a DSL in front. The challenge however arises from
** the additional requirement to support various usage patters, all with minimal specs.
** 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.
** @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.
**
** The following code lays out the ground structure, while treating Spec as a distinct
** type, which is then mixed into Draw. This logical separation basically was led me to the
** final solution: Draw both _is_ a function and _embodies_ the implementation of this function.
** This somewhat surprising layout is what enables use as a DSL builder, because it allows both
** to have the _builder use_ and the _converter use_ in the same class, even allowing to _define_
** a Draw by giving a function which _produces_ a (dynamically parametrised) Draw.
** # 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
** 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.
**
** In this prototype, all of the functor adaptation is also part of the Draw template; for the
** real implementation this will have to be supplied at usage site through a traits template,
** otherwise it would not be possible to integrate seamlessly with custom data sources (as
** happens in the intended use case, where actually a Node is the data source)
** 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.
**
** ## Policy template
** For practical use, the RandomDraw template must be instantiate 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
** - 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()`
** - moreover, the policy must define a function `defaultSrc(args...)`; this function must
** accept input arguments in accordance to the function signature of the Policy (i.e. it
** must read "the randomness source") and produce a result that can be adapted and fed
** into the regular processing chain (the same as for any mapping function)
** - optionally, this policy may also define a template `Adaptor<Sig>`, 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.
** @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
** @see TestChainLoad_test
** @see SchedulerStress_test
@ -53,6 +82,7 @@
#include "lib/meta/function.hpp"
#include "lib/meta/function-closure.hpp"
#include "lib/util-quant.hpp"
#include "lib/util.hpp"
#include <functional>
@ -74,11 +104,15 @@ namespace lib {
* @tparam max maximum allowed param value (inclusive)
* @tparam max minimum allowed param value (inclusive) - defaults to "zero".
*/
template<typename T, T max, T min =T(0)>
template<typename T, T max, T min =T(0), T zero =min>
struct Limited
{
static constexpr T minVal() { return min; }
static_assert (min < max);
static_assert (min <= zero and zero < max);
static constexpr T maxVal() { return max; }
static constexpr T minVal() { return min; }
static constexpr T zeroVal(){ return zero;}
T val;
@ -98,6 +132,7 @@ namespace lib {
};
namespace random_draw { // Policy definitions
/**
@ -115,7 +150,7 @@ 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.
@ -128,7 +163,8 @@ namespace lib {
using Fun = typename _Fun<POL>::Functor;
using Tar = typename _Fun<POL>::Ret;
Tar maxResult_{Tar::maxVal()}; ///< maximum parameter val actually to produce < max
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]
@ -137,15 +173,34 @@ namespace lib {
limited (double val)
{
if (probability_ == 0.0 or val == 0.0)
return Tar{0};
return Tar::zeroVal();
//
REQUIRE (Tar::minVal() <= minResult_);
REQUIRE (Tar::maxVal() >= maxResult_);
REQUIRE (minResult_ < maxResult_);
REQUIRE (0.0 <= probability_);
REQUIRE (probability_ <= 1.0);
auto org = util::max (Tar::zeroVal(), minResult_);
double q = (1.0 - probability_);
auto org = Tar::minVal();
val -= q; // [0 .. [q .. 1[
val /= probability_; // [0 .. 1[
val *= maxResult_ - org; // [0 .. m[
val += org+1; // [1 .. m]
val += CAP_EPSILON; // round down yet absorb dust
return Tar{val};
if (org == minResult_)
{ // simple standard case
val *= maxResult_ - org; // [0 .. m[
val += org+1; // [1 .. m]
val += CAP_EPSILON; // round down yet absorb dust
return Tar{val};
}
else// Origin is somewhere within value range
{// ==> wrap "negative" part above max
// to map 0.0 ⟼ org (≙neutral)
val *= maxResult_ - minResult_;
val += org+1; // max inclusive but <0 ⟼ org
if (val > maxResult_) // wrap the "negatives"
val -= maxResult_+1 - minResult_;
val += CAP_EPSILON;
return Tar{val};
}
}
static size_t constexpr QUANTISER = 1 << 8;
@ -167,7 +222,9 @@ namespace lib {
public:
/** Drawing is _disabled_ by default, always yielding "zero" */
/**
* Drawing is _disabled_ by default, always yielding "zero"
*/
RandomDraw()
: Fun{adaptOut(POL::defaultSrc)}
{ }
@ -204,7 +261,7 @@ namespace lib {
maxResult_ = m;
return move (*this);
}
template<class FUN>
RandomDraw&&
mapping (FUN&& fun)
@ -217,7 +274,9 @@ namespace lib {
private:
/** @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<class FUN>
decltype(auto)
adaptIn (FUN&& fun)
@ -230,6 +289,15 @@ namespace lib {
return Adaptor::build (forward<FUN> (fun));
}
/** @internal adapt output side of a given function, allowing to handle it's results
* - 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.
* @return adapted function which produces a result value of type #Tar
*/
template<class FUN>
decltype(auto)
adaptOut (FUN&& fun)
@ -244,24 +312,25 @@ namespace lib {
else
if constexpr (std::is_same_v<Res, size_t>)// ◁───────────────────────┨ function yields random source to draw value
return chained (std::forward<FUN>(fun)
,[this](size_t hash){ return drawLimited(hash); });
,[this](size_t hash){ return drawLimited(hash); }
);
else
if constexpr (std::is_same_v<Res, double>)// ◁───────────────────────┨ function yields random value to be quantised
return chained (std::forward<FUN>(fun)
,[this](double rand){ return limited(rand); });
,[this](double rand){ return limited(rand); }
);
else
if constexpr (std::is_same_v<Res, RandomDraw>)// ◁────────────────────┨ function yields parametrised RandomDraw to invoke
if constexpr (std::is_same_v<Res, RandomDraw const&>)// ◁─────────────┨ function yields parametrised RandomDraw to invoke
return [functor=std::forward<FUN>(fun), this]
(auto&& ...inArgs)
{ // invoke with copy
RandomDraw parametricDraw = functor(inArgs...);
{ // invoke with copy
RandomDraw const& parametricDraw = functor(inArgs...);
return parametricDraw (forward<decltype(inArgs)> (inArgs)...);
};
}; // forward arguments
else
static_assert (not sizeof(Res), "unable to adapt / handle result type");
NOTREACHED("Handle based on return type");
}
};

View file

@ -129,7 +129,7 @@ namespace test{
/** @test TODO demonstrate a basic usage scenario
* @todo WIP 11/23 🔁 define implement
* @todo WIP 11/23 define 🔁 implement
*/
void
simpleUse()
@ -152,7 +152,7 @@ SHOW_EXPR (int(draw(256)));
/** @test TODO verify configuration through policy template
* @todo WIP 11/23 🔁 define implement
* @todo WIP 11/23 🔁 define 🔁 implement
*/
void
verify_policy()

View file

@ -96631,17 +96631,19 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node COLOR="#435e98" CREATED="1700513461881" ID="ID_1696400503" MODIFIED="1700513889095" TEXT="definiert den Basis-Funktionstyp"/>
<node COLOR="#435e98" CREATED="1700513505882" ID="ID_1225658406" MODIFIED="1700513889096" TEXT="definiert dar&#xfc;ber auch implizit den Ergebnistyp"/>
<node COLOR="#435e98" CREATED="1700513474582" ID="ID_1399385360" MODIFIED="1700513889096" TEXT="liefert spezielle Adapter f&#xfc;r alle unterst&#xfc;tzten Eingangs-Typen"/>
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1700513891593" ID="ID_1527819070" MODIFIED="1700513932582" TEXT="unklar: default-Eingang">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1700513891593" ID="ID_1527819070" MODIFIED="1700577854558" TEXT="unklar: default-Eingang">
<icon BUILTIN="messagebox_warning"/>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1700515452147" ID="ID_1797461825" MODIFIED="1700515470815" TEXT="mu&#xdf; ich irgendwo einen Standard-Eingangstyp annehmen?">
<node COLOR="#435e98" CREATED="1700515452147" ID="ID_1797461825" MODIFIED="1700577912934" TEXT="mu&#xdf; ich irgendwo einen Standard-Eingangstyp annehmen?">
<icon BUILTIN="help"/>
<node CREATED="1700577876306" ID="ID_1799486545" MODIFIED="1700577891756" TEXT="&#xfc;berraschenderweise nirgends im Implementierungs-Code"/>
<node CREATED="1700577892392" ID="ID_269852112" MODIFIED="1700577911162" TEXT="es gen&#xfc;gt die implizite Definition durch die Signatur der Policy"/>
</node>
<node CREATED="1700515476582" ID="ID_474690726" MODIFIED="1700515499125" TEXT="im Grunde w&#xfc;rde das gesamte Schema mit beliebigen Eingangs-Signaturen funktionieren">
<icon BUILTIN="idea"/>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1700515519514" ID="ID_1957414628" MODIFIED="1700515559845" TEXT="&#x27f9; dann m&#xfc;&#xdf;te aber die Policy einen Default-Eingang liefern..."/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700515561328" ID="ID_408143188" MODIFIED="1700515586492" TEXT="brauche Funktions-Komposition">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#435e98" CREATED="1700515519514" ID="ID_1957414628" MODIFIED="1700577869042" TEXT="&#x27f9; dann mu&#xdf; aber die Policy einen Default-Eingang liefern..."/>
<node COLOR="#338800" CREATED="1700515561328" FOLDED="true" ID="ID_408143188" MODIFIED="1700577929389" TEXT="brauche Funktions-Komposition">
<icon BUILTIN="button_ok"/>
<node COLOR="#435e98" CREATED="1700515591564" ID="ID_9942626" MODIFIED="1700536064914" TEXT="&#xbb;beliebige Funktion&#xab; kann nicht einfach so als Lambda geschrieben werden">
<icon BUILTIN="messagebox_warning"/>
</node>
@ -96717,18 +96719,107 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1700542362630" ID="ID_730331755" MODIFIED="1700542375501" TEXT="mit speziellem Test ausleuchten...">
<icon BUILTIN="pencil"/>
<node CREATED="1700542382819" ID="ID_1832228113" MODIFIED="1700542395074" TEXT="eine Policy die zwei Eingangs-Argumente nimmt"/>
<node COLOR="#338800" CREATED="1700542382819" ID="ID_1832228113" MODIFIED="1700574242519" TEXT="eine Policy die zwei Eingangs-Argumente nimmt">
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700574245126" ID="ID_1720583823" MODIFIED="1700574258660" TEXT="Adaption von Manipulator-Funktionen">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
</node>
<node CREATED="1700491985219" ID="ID_460437503" MODIFIED="1700491990343" TEXT="Adaptierung restrukturieren"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700577987947" ID="ID_1072243558" MODIFIED="1700578044322" TEXT="Result-Mapping fertigstellen">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1700578049835" ID="ID_1039524348" MODIFIED="1700578324330" TEXT="(optional) Minimal-Wert beachten">
<icon BUILTIN="pencil"/>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1700578067566" ID="ID_1673296895" MODIFIED="1700578327612" TEXT="(optional) neutralen Wert definieren">
<icon BUILTIN="pencil"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700578338693" ID="ID_1746695653" MODIFIED="1700578488824" TEXT="Wunsch: neutralen Wert mitten im Wertebereich">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Beispiel: -2 .. 0 ..+2&#160;&#160;&#10233; Wahrscheinlichkeit definiert f&#252;r Werte &#8800; 0
</p>
</body>
</html>
</richcontent>
<icon BUILTIN="yes"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700578688806" ID="ID_1002441906" MODIFIED="1700578701690" TEXT="brauche anderes Mapping-Layout">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700578703420" ID="ID_1936439930" MODIFIED="1700578736098" TEXT="erst mal als statischen-Zweig realisieren">
<icon BUILTIN="yes"/>
</node>
<node CREATED="1700578747462" ID="ID_1744331355" MODIFIED="1700578808542" TEXT="Idee: [0....0[1...max|min...-1]">
<icon BUILTIN="idea"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700579867534" ID="ID_1525251156" MODIFIED="1700579872321" TEXT="Quantisierung verbessern">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1700579884119" ID="ID_805978365" MODIFIED="1700579898358" TEXT="Probleme mit der Wahrscheinlichkeitsverteilung">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1700580065522" ID="ID_1293962654" MODIFIED="1700580090733" TEXT="f&#xfc;r Zuf&#xe4;lligkeit w&#xe4;re quant &#x2259; card ideal"/>
<node CREATED="1700580100458" ID="ID_508177019" MODIFIED="1700580112917" TEXT="f&#xfc;r Gleichverteilung mu&#xdf; quant eine 2-er-Potenz sein"/>
<node CREATED="1700580270404" ID="ID_1058992457" MODIFIED="1700580289113" TEXT="Problem sind kleine Wahrscheinlichkeiten">
<node CREATED="1700580327644" ID="ID_163898715" MODIFIED="1700580337478" TEXT="der verbleibende Rest leuchtet nicht mehr alle Werte aus"/>
</node>
<node CREATED="1700580296744" ID="ID_1912245268" MODIFIED="1700580306635" TEXT="auch unebene Teil-Wertbereiche sind problematisch"/>
</node>
<node CREATED="1700579942375" ID="ID_1570826487" MODIFIED="1700579973751" TEXT="Quantiser auf 2-log der Kardinalit&#xe4;t aufbauen">
<node CREATED="1700580399003" ID="ID_1712768640" MODIFIED="1700580402212" TEXT="statische L&#xf6;sung">
<node CREATED="1700580403024" ID="ID_340644857" MODIFIED="1700580414388" TEXT="kann dann nur auf max(Template-Param) aufsetzen"/>
<node CREATED="1700580415496" ID="ID_241609475" MODIFIED="1700580425235" TEXT="mu&#xdf; mit zus&#xe4;tzlichem Headroom arbeiten"/>
</node>
<node CREATED="1700580428485" ID="ID_209321513" MODIFIED="1700580431707" TEXT="dynamische L&#xf6;sung">
<node CREATED="1700580432790" ID="ID_1779483070" MODIFIED="1700580440161" TEXT="w&#xe4;re ein zus&#xe4;tzliches Objektfeld"/>
<node CREATED="1700580440749" ID="ID_1688731462" MODIFIED="1700580454914" TEXT="m&#xfc;&#xdf;te von jedem Param-Setter mit bedient werden"/>
<node CREATED="1700580455526" ID="ID_1568213898" MODIFIED="1700580468829" TEXT="w&#xfc;rde den (schnellen) ilogb verwenden"/>
<node CREATED="1700580486714" ID="ID_1999552035" MODIFIED="1700580606017" TEXT="aber auch hier brauchen wir Headroom">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
grade wenn die Maximalgrenze nahe an einer Zweierpotenz liegt (Beispiel 10 Werte) dann werden einige Werte deutlich wahrscheinlicher
</p>
</body>
</html>
</richcontent>
</node>
</node>
</node>
</node>
<node COLOR="#338800" CREATED="1700578085130" ID="ID_1160195104" MODIFIED="1700578334393" TEXT="Konsistenz der Definitionen pr&#xfc;fen (statisch)">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700491985219" ID="ID_460437503" MODIFIED="1700577836021" TEXT="Adaptierung restrukturieren">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700577811739" ID="ID_1356052727" MODIFIED="1700577841449" TEXT="Konfigurations-DSL aufbauen">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1700491877926" ID="ID_1958310493" MODIFIED="1700502021655" TEXT="Test zur Dokumentation">
<icon BUILTIN="pencil"/>
<node CREATED="1700491902622" ID="ID_523469259" MODIFIED="1700491929304" TEXT="soll auch den Proze&#xdf; der Spezialisierung demonstrieren"/>
<node CREATED="1700491930155" ID="ID_1583745392" MODIFIED="1700491949372" TEXT="numerische Grenzen beleuchten"/>
<node CREATED="1700501979956" ID="ID_571312425" MODIFIED="1700501992697" TEXT="Wirkung des Builder-API dokumentieren"/>
<node CREATED="1700501993218" ID="ID_271630234" MODIFIED="1700502005459" TEXT="dynamische Parametrisierung demonstrieren"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1700491902622" ID="ID_523469259" MODIFIED="1700577965111" TEXT="soll auch den Proze&#xdf; der Spezialisierung demonstrieren">
<icon BUILTIN="pencil"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700491930155" ID="ID_1583745392" MODIFIED="1700577970844" TEXT="numerische Grenzen beleuchten">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700501979956" ID="ID_571312425" MODIFIED="1700577970844" TEXT="Wirkung des Builder-API dokumentieren">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700501993218" ID="ID_271630234" MODIFIED="1700577970844" TEXT="dynamische Parametrisierung demonstrieren">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
</node>
</node>