Library: RandomDraw - implementation complete and tested.
This commit is contained in:
parent
3808166494
commit
8b1326129a
5 changed files with 447 additions and 456 deletions
|
|
@ -54,6 +54,7 @@
|
|||
|
||||
#include "lib/meta/function.hpp"
|
||||
#include "lib/meta/tuple-helper.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
|
@ -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<typename SIG, typename VAL>
|
||||
class PApply
|
||||
|
|
@ -704,6 +710,8 @@ namespace func{
|
|||
namespace { // ...helpers for specifying types in function declarations....
|
||||
|
||||
using std::get;
|
||||
using util::unConst;
|
||||
|
||||
|
||||
template<typename RET, typename ARG>
|
||||
struct _Sig
|
||||
|
|
@ -739,7 +747,7 @@ namespace func{
|
|||
,forward<F2> (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<A> (arg)
|
||||
};
|
||||
return [binding = move(binding)]
|
||||
(ARGS ...args) mutable -> RET
|
||||
(ARGS ...args) -> RET
|
||||
{
|
||||
auto& functor = get<0>(binding);
|
||||
//
|
||||
return functor ( forward<A> (get<1>(binding))
|
||||
// //Warning: might corrupt ownership
|
||||
return functor ( forward<A> (unConst (get<1>(binding)))
|
||||
, forward<ARGS> (args)...);
|
||||
};
|
||||
}
|
||||
|
|
@ -801,12 +809,12 @@ namespace func{
|
|||
,forward<A> (arg)
|
||||
};
|
||||
return [binding = move(binding)]
|
||||
(ARGS ...args) mutable -> RET
|
||||
(ARGS ...args) -> RET
|
||||
{
|
||||
auto& functor = get<0>(binding);
|
||||
//
|
||||
return functor ( forward<ARGS> (args)...
|
||||
, forward<A> (get<1>(binding)));
|
||||
, forward<A> (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<typename FUN, typename ARG>
|
||||
inline auto
|
||||
applyFirst (FUN&& fun, ARG&& arg)
|
||||
|
|
|
|||
|
|
@ -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<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.
|
||||
** 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<typename T, T max, T min =T(0), T zero =min>
|
||||
struct Limited
|
||||
|
|
@ -161,6 +166,7 @@ namespace lib {
|
|||
template<class POL>
|
||||
class RandomDraw
|
||||
: public POL
|
||||
, util::MoveOnly
|
||||
{
|
||||
using Sig = typename _Fun<POL>::Sig;
|
||||
using Fun = function<Sig>;
|
||||
|
|
@ -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<class FUN>
|
||||
RandomDraw(FUN&& fun)
|
||||
: POL{adaptOut(adaptIn(std::forward<FUN> (fun)))}
|
||||
: POL{}
|
||||
, 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 ===== */
|
||||
|
|
@ -295,6 +326,7 @@ namespace lib {
|
|||
{
|
||||
Fun& thisMapping = static_cast<Fun&> (*this);
|
||||
thisMapping = adaptOut(adaptIn(std::forward<FUN> (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<class SIG>
|
||||
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<class FUN>
|
||||
decltype(auto)
|
||||
auto
|
||||
adaptIn (FUN&& fun)
|
||||
{
|
||||
using lib::meta::func::applyFirst;
|
||||
|
|
@ -333,7 +367,7 @@ namespace lib {
|
|||
else
|
||||
if constexpr (is_Manipulator<Sig>())
|
||||
// function wants to manipulate *this dynamically...
|
||||
return adaptIn (applyFirst(forward<FUN> (fun), *this));
|
||||
return adaptIn (applyFirst (forward<FUN> (fun), *this));
|
||||
else
|
||||
return Adaptor::build (forward<FUN> (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<class FUN>
|
||||
decltype(auto)
|
||||
auto
|
||||
adaptOut (FUN&& fun)
|
||||
{
|
||||
static_assert (lib::meta::_Fun<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<Res, double>)// ◁───────────────────────┨ function yields random value to be quantised
|
||||
if constexpr (std::is_same_v<Res, double>)// ◁───────────────────────┨ function yields mapping value to be quantised
|
||||
return chained (std::forward<FUN>(fun)
|
||||
,[this](double rand){ return limited(rand); }
|
||||
);
|
||||
else
|
||||
if constexpr (std::is_same_v<Res, void>) // ◁──────────────────────────┨ function manipulates parameters by side-effect
|
||||
if constexpr (std::is_same_v<Res, void>) // ◁────────────────────────┨ function manipulates parameters by side-effect
|
||||
return [functor=std::forward<FUN>(fun)
|
||||
,processDraw=getCurrMapping()]
|
||||
(auto&& ...inArgs) mutable -> _FunRet<RandomDraw>
|
||||
(auto&& ...inArgs) -> _FunRet<RandomDraw>
|
||||
{
|
||||
functor(inArgs...); // invoke manipulator with copy
|
||||
return processDraw (forward<decltype(inArgs)> (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)};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -508,7 +508,7 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
PLANNED "pick limited numbers randomly" RandomDraw_test <<END
|
||||
TEST "pick limited numbers randomly" RandomDraw_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
|
|
|||
|
|
@ -28,9 +28,8 @@
|
|||
|
||||
#include "lib/test/run.hpp"
|
||||
#include "lib/random-draw.hpp"
|
||||
#include "lib/time/timevalue.hpp"
|
||||
#include "lib/test/diagnostic-output.hpp"////////////////////TODO
|
||||
#include "lib/format-string.hpp"
|
||||
#include "lib/test/test-helper.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
|
|
@ -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>(fun)]
|
||||
(size_t hash) mutable -> _FunRet<FUN>
|
||||
(size_t hash) -> _FunRet<FUN>
|
||||
{
|
||||
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<SpecialPolicy>().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<int,5>;
|
||||
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<typename FUN, typename ARG, typename RET, typename... ARGS>
|
||||
auto
|
||||
_applyFirst (FUN&& fun, ARG&& arg, _Fun<RET(ARGS...)>)
|
||||
{
|
||||
std::tuple<FUN,ARG> binding{std::forward<FUN> (fun)
|
||||
,std::forward<ARG>(arg)
|
||||
};
|
||||
return [binding = std::move(binding)]
|
||||
(ARGS ...args) mutable -> RET
|
||||
{
|
||||
auto& functor = std::get<0> (binding);
|
||||
//
|
||||
return functor ( std::forward<ARG> (std::get<1> (binding))
|
||||
, std::forward<ARGS> (args)...);
|
||||
};
|
||||
}
|
||||
|
||||
template<typename FUN, typename ARG>
|
||||
auto
|
||||
applyFirst (FUN&& fun, ARG&& arg)
|
||||
{
|
||||
using Ret = typename lib::meta::_Fun<FUN>::Ret;
|
||||
using AllArgs = typename lib::meta::_Fun<FUN>::Args;
|
||||
using RestArgs = typename lib::meta::Split<AllArgs>::Tail;
|
||||
using AdaptedFun = typename lib::meta::BuildFunType<Ret,RestArgs>::Fun;
|
||||
|
||||
return _applyFirst( std::forward<FUN> (fun)
|
||||
, std::forward<ARG> (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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue