Chain-Load: now able to define RandomDraw rules

...all existing tests reproduced
...yet notation is hopefully more readable

Old:
  graph.expansionRule([](size_t h,double){ return Cap{8, h%16, 63}; })

New:
  graph.expansionRule(graph.rule().probability(0.5).maxVal(4))
This commit is contained in:
Fischlurch 2023-11-26 03:04:59 +01:00
parent f1c156b4cd
commit dbe71029b7
7 changed files with 144 additions and 106 deletions

View file

@ -143,6 +143,12 @@ namespace meta{
: _Fun<SIG>
{ };
/** Specialisation to strip spurious const for type analysis */
template<typename SIG>
struct _Fun<SIG const&>
: _Fun<SIG>
{ };
/** Specialisation when using a function reference */
template<typename SIG>
struct _Fun<SIG&>

View file

@ -135,6 +135,12 @@ namespace meta {
/** helper to prevent a template constructor from shadowing inherited copy ctors */
template<typename ARG, class SELF>
using disable_if_self = disable_if<std::is_same<std::remove_reference_t<ARG>, SELF>>;
/** detect possibility of a conversion to string.
* Naive implementation, which first attempts to build a string instance by
* implicit conversion, and then tries to invoke an explicit string conversion.

View file

@ -69,9 +69,23 @@
** specialisations for various function signatures. These adaptors are used to
** conform any mapping function and thus allow to simplify or widen the
** possible configurations at usage site.
**
** ## Copy inhibition
** The configuration of the RandomDraw processing pipeline makes heavy use of function composition
** and adaptation to handle a wide selection of input types and usage patterns. Unfortunately this
** requires to like the generated configuration-λ to the object instance (capturing by reference);
** not allowing this would severely limit the possible configurations. This implies that an object
** instance must not be moved anymore, once the processing pipeline has been configured. And this
** in turn would severely limit it's usage in a DSL. As a compromise, RandomDraw relies on
** [lazy on-demand initialisation](\ref lazy-init.hpp): as long as the processing function has
** not been invoked, the internal pipeline is unconfigured, and the object can be moved and copied.
** Once invoked, the prepared configuration is assembled and the function »engaged«; from this point
** on, any attempt to move or copy the object will throw an exception, while it is still possible
** to assign other RandomDraw instances to this object.
** @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 lazy-init.hpp
** @see TestChainLoad_test
** @see SchedulerStress_test
*/
@ -82,7 +96,7 @@
#include "lib/error.h"
#include "lib/nocopy.hpp"
#include "lib/lazy-init.hpp"
#include "lib/meta/function.hpp"
#include "lib/meta/function-closure.hpp"
#include "lib/util-quant.hpp"
@ -95,6 +109,7 @@
namespace lib {
namespace err = lumiera::error;
using lib::meta::disable_if_self;
using lib::meta::_Fun;
using std::function;
using std::forward;
@ -165,9 +180,11 @@ namespace lib {
*/
template<class POL>
class RandomDraw
: public POL
, util::MoveOnly
: public LazyInit<POL>
{
using Lazy = LazyInit<POL>;
using Disabled = typename Lazy::MarkDisabled;
using Sig = typename _Fun<POL>::Sig;
using Fun = function<Sig>;
using Tar = typename _Fun<POL>::Ret;
@ -176,11 +193,6 @@ 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
@ -253,8 +265,10 @@ namespace lib {
* Drawing is _disabled_ by default, always yielding "zero"
*/
RandomDraw()
: POL{adaptOut(POL::defaultSrc)}
{ }
: Lazy{Disabled()}
{
mapping (POL::defaultSrc);
}
/**
* Build a RandomDraw by attaching a value-processing function,
@ -265,32 +279,14 @@ namespace lib {
* - `void(RandomDraw&, ...)` : the function manipulates the current
* instance, to control parameters dynamically, based on input.
*/
template<class FUN>
template<class FUN, typename =disable_if_self<FUN, RandomDraw>>
RandomDraw(FUN&& fun)
: POL{}
: Lazy{Disabled()}
, probability_{1.0}
, fixedLocation_{true} // prevent move-copy (function binds to *this)
{
mapping (forward<FUN> (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 ===== */
@ -325,8 +321,12 @@ namespace lib {
mapping (FUN&& fun)
{
Fun& thisMapping = static_cast<Fun&> (*this);
thisMapping = adaptOut(adaptIn(std::forward<FUN> (fun)));
fixedLocation_ = true; // function will bind to *this
Lazy::installInitialiser (thisMapping
,[theFun = forward<FUN> (fun)]
(RandomDraw* self)
{ // when lazy init is performed....
self->installAdapted (theFun);
});
return move (*this);
}
@ -345,6 +345,16 @@ namespace lib {
/** @internal adapt a function and install it to control drawing and mapping */
template<class FUN>
void
installAdapted (FUN&& fun)
{
Fun& thisMapping = static_cast<Fun&> (*this);
thisMapping = adaptOut(adaptIn(std::forward<FUN> (fun)));
}
/** @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 */
@ -359,7 +369,6 @@ namespace lib {
using Sig = typename _Fun::Sig;
using Args = typename _Fun::Args;
using BaseIn = typename lib::meta::_Fun<POL>::Args;
using Adaptor = typename POL::template Adaptor<Sig>;
if constexpr (std::is_same_v<Args, BaseIn>)
// function accepts same arguments as this RandomDraw
@ -369,7 +378,10 @@ namespace lib {
// function wants to manipulate *this dynamically...
return adaptIn (applyFirst (forward<FUN> (fun), *this));
else
return Adaptor::build (forward<FUN> (fun));
{// attempt to find a custom adaptor via Policy template
using Adaptor = typename POL::template Adaptor<Sig>;
return Adaptor::build (forward<FUN> (fun));
}
}
/** @internal adapt output side of a given function, allowing to handle it's results
@ -429,7 +441,7 @@ namespace lib {
getCurrMapping()
{
Fun& currentProcessingFunction = *this;
if (fixedLocation_ and currentProcessingFunction)
if (currentProcessingFunction)
return Fun{currentProcessingFunction};
else
return Fun{adaptOut(POL::defaultSrc)};

View file

@ -41,11 +41,8 @@ namespace test{
using util::isSameObject;
using lib::meta::isFunMember;
using lib::meta::disable_if;
using lib::meta::disable_if_self;
using err::LUMIERA_ERROR_LIFECYCLE;
using std::is_same;
using std::remove_reference_t;
using std::make_unique;
@ -360,7 +357,7 @@ namespace test{
installInitialiser(fun, buildInit([](int){ return 0; }));
}
// prevent this ctor from shadowing the copy ctors //////TICKET #963
template<typename FUN, typename =disable_if<is_same<remove_reference_t<FUN>, LazyDemo>>>
template<typename FUN, typename =disable_if_self<FUN, LazyDemo>>
LazyDemo (FUN&& someFun)
: LazyInit{MarkDisabled()}
, fun{}

View file

@ -222,7 +222,7 @@ namespace test {
{
auto graph = TestChainLoad<32>{};
graph.expansionRule([](size_t h,double){ return Cap{8, h%16, 63}; })
graph.expansionRule(graph.rule().probability(0.5).maxVal(4))
.buildToplolgy()
.printTopologyDOT();
}

View file

@ -49,7 +49,10 @@
** 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.
** 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.
**
** ## Usage
** A TestChainLoad instance is created with predetermined maximum fan factor and a fixed
@ -68,6 +71,7 @@
**
** @see TestChainLoad_test
** @see SchedulerStress_test
** @see random-draw.hpp
*/
@ -131,49 +135,14 @@ namespace test {
namespace { // Default definitions for topology generation
const size_t DEFAULT_FAN = 16;
const size_t DEFAULT_SIZ = 256;
const double CAP_EPSILON = 0.001; ///< tiny bias to absorb rounding problems
}
/**
* Helper to cap and map to a value range.
*/
struct Cap
{
double lower{0};
double value{0};
double upper{1};
Cap (int i) : value(i){ }
Cap (size_t s) : value(s){ }
Cap (double d) : value{d}{ }
template<typename NL, typename NV, typename NU>
Cap (NL l, NV v, NU u)
: lower(l)
, value(v)
, upper(u)
{ }
size_t
mapped (size_t scale)
{
if (value==lower)
return 0;
value -= lower;
value /= upper-lower;
value *= scale;
value += CAP_EPSILON;
value = limited (size_t(0), value, scale);
return size_t(value);
}
};
/**
/***********************************************************************//**
* A Generator for synthetic Render Jobs for Scheduler load testing.
* Allocates a fixed set of #numNodes and generates connecting toplology.
* Allocates a fixed set of #numNodes and generates connecting topology.
* @tparam maxFan maximal fan-in/out from a node, also limits maximal parallel strands.
* @see TestChainLoad_test
*/
@ -282,13 +251,12 @@ namespace test {
private:
using NodeTab = typename Node::Tab;
using NodeStorage = std::array<Node, numNodes>;
using CtrlRule = std::function<Cap(size_t, double)>;
std::unique_ptr<NodeStorage> nodes_;
CtrlRule seedingRule_ {[](size_t, double){ return 0; }};
CtrlRule expansionRule_{[](size_t, double){ return 0; }};
CtrlRule reductionRule_{[](size_t, double){ return 0; }};
Rule seedingRule_ {};
Rule expansionRule_{};
Rule reductionRule_{};
public:
TestChainLoad()
@ -313,24 +281,26 @@ namespace test {
/* ===== topology control ===== */
static Rule rule() { return Rule(); }
TestChainLoad&&
seedingRule (CtrlRule r)
seedingRule (Rule&& r)
{
seedingRule_ = r;
seedingRule_ = move(r);
return move(*this);
}
TestChainLoad&&
expansionRule (CtrlRule r)
expansionRule (Rule&& r)
{
expansionRule_ = r;
expansionRule_ = move(r);
return move(*this);
}
TestChainLoad&&
reductionRule (CtrlRule r)
reductionRule (Rule&& r)
{
reductionRule_ = r;
reductionRule_ = move(r);
return move(*this);
}
@ -346,7 +316,11 @@ namespace test {
*next{&b}; // the next set of nodes connected to current
Node* node = &nodes_->front();
size_t level{0};
size_t expectedLevel = max (1u, numNodes/maxFan); // guess, typically too low
// local copy of all rules (they are non-copyable, once engaged)
Rule expansionRule = expansionRule_;
Rule reductionRule = reductionRule_;
Rule seedingRule = seedingRule_;
// prepare building blocks for the topology generation...
auto moreNext = [&]{ return next->size() < maxFan; };
@ -358,14 +332,9 @@ namespace test {
n->level = level;
return n;
};
auto height = [&](double level)
auto apply = [&](Rule& rule, Node* n)
{
return level/expectedLevel;
};
auto apply = [&](CtrlRule& rule, Node* n)
{
Cap param = rule (n->hash, height(level));
return param.mapped (maxFan);
return rule(n);
};
addNode(); // prime next with root node
@ -381,8 +350,8 @@ namespace test {
for (Node* o : *curr)
{ // follow-up on all Nodes in current level...
o->calculate();
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
Node* n = addNode();
@ -398,7 +367,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;
@ -503,8 +472,9 @@ namespace test {
*/
template<size_t numNodes, size_t maxFan>
class TestChainLoad<numNodes,maxFan>::NodeControlBinding
: protected std::function<Param(Node*)>
: public std::function<Param(Node*)>
{
protected:
/** by default use Node-hash directly as source of randomness */
static size_t
defaultSrc (Node* node)

View file

@ -96262,9 +96262,9 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="stop-sign"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700240155314" ID="ID_6655140" MODIFIED="1700243338694" TEXT="Hilfsmittel: Regel-Builder">
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1700240155314" ID="ID_6655140" MODIFIED="1700969828908" TEXT="Hilfsmittel: Regel-Builder">
<linktarget COLOR="#fef7b5" DESTINATION="ID_6655140" ENDARROW="Default" ENDINCLINATION="159;-197;" ID="Arrow_ID_1782877370" SOURCE="ID_697379621" STARTARROW="None" STARTINCLINATION="-63;175;"/>
<icon BUILTIN="flag-yellow"/>
<icon BUILTIN="yes"/>
<node COLOR="#338800" CREATED="1700264860430" ID="ID_911248195" MODIFIED="1700713935881" TEXT="nochmal umst&#xfc;lpen....">
<icon BUILTIN="button_ok"/>
<node CREATED="1700264870441" ID="ID_1812170860" MODIFIED="1700491182299">
@ -98410,8 +98410,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700713941245" ID="ID_1647717999" MODIFIED="1700713949917" TEXT="die neue Lib-Komponente einbauen">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1700713941245" ID="ID_1647717999" MODIFIED="1700969800622" TEXT="die neue Lib-Komponente einbauen">
<icon BUILTIN="button_ok"/>
<node CREATED="1700763561056" ID="ID_1249462296" MODIFIED="1700764033885" TEXT="Bisherige L&#xf6;sung mit Hilfsklasse Cap f&#xe4;llt weg">
<richcontent TYPE="NOTE"><html>
<head/>
@ -98439,11 +98439,14 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node COLOR="#338800" CREATED="1700769068457" ID="ID_476049536" MODIFIED="1700770746672" TEXT="Typ f&#xfc;r &#xbb;config-rules&#xab; auf RandomDraw aufbauen">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1700969876652" ID="ID_1123165498" MODIFIED="1700969885748" TEXT="Tests laufen wieder wie zuvor">
<icon BUILTIN="button_ok"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700240183293" ID="ID_1092101612" MODIFIED="1700240190782" TEXT="Regel-Baukasten">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1700770889303" ID="ID_1475798694" MODIFIED="1700770937999" TEXT="Non-copyable Rules &#x2014; problematisch f&#xfc;r DSL">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1700770889303" ID="ID_1475798694" MODIFIED="1700969773151" TEXT="Non-copyable Rules &#x2014; problematisch f&#xfc;r DSL">
<icon BUILTIN="messagebox_warning"/>
<node COLOR="#5b280f" CREATED="1700770943791" ID="ID_1199767820" MODIFIED="1700770980394" TEXT="sie m&#xfc;ssen non-copyable sein wegen internem Function-binding">
<icon BUILTIN="broken-line"/>
@ -98465,6 +98468,50 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="idea"/>
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#437298" CREATED="1700969491009" FOLDED="true" ID="ID_1984571390" MODIFIED="1700969900504">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
l&#246;st das Problem <i>fast...</i>
</p>
</body>
</html></richcontent>
<icon BUILTIN="smiley-oh"/>
<node CREATED="1700969510140" ID="ID_1447787379" MODIFIED="1700969626628">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
nachdem man n&#228;mlich <font face="Monospaced">buildTopology()</font>&#160;aufgerufen hat,
</p>
<p>
explodiert TestChainLoad am Ende der DSL-Methode.
</p>
<p>
Denn da steht ganz vertr&#228;umt...
</p>
<p>
<font face="Monospaced" color="#a33434">return move(*this)</font>
</p>
</body>
</html></richcontent>
</node>
<node BACKGROUND_COLOR="#e6d5a6" COLOR="#fa002a" CREATED="1700969637165" ID="ID_899591341" MODIFIED="1700969754144" TEXT="irgendwie wollen die Windm&#xfc;hlen nicht recht sterben...">
<icon BUILTIN="broken-line"/>
</node>
<node CREATED="1700969657586" ID="ID_347427762" MODIFIED="1700969694884">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
nun gut: dann verwendet buildTopology() eben <b>lokale Kopien</b>
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1700969673859" ID="ID_1388522888" MODIFIED="1700969690306" TEXT="...da RandomDraw jetzt so wunderbar elegant kopierbar ist..."/>
</node>
</node>
</node>
</node>