Library: RandomDraw - implementation complete and tested.

This commit is contained in:
Fischlurch 2023-11-23 02:42:02 +01:00
parent 3808166494
commit 8b1326129a
5 changed files with 447 additions and 456 deletions

View file

@ -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)

View file

@ -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)};
}
};

View file

@ -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

View file

@ -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