Chain-Load: verify connectivity and recalculation
It seams indicated to verify the generated connectivity and the hash calculation and recalculation explicitly at least for one example topology; choosing a topology comprised of several sub-graphs, to also verify the propagation of seed values to further start-nodes. In order to avoid addressing nodes directly by index number, those sub-graphs can be processed by ''grouping of nodes''; all parts are congruent because topology is determined by the node hashes and thus a regular pattern can be exploited. To allow for easy processing of groups, I have developed a simplistic grouping device within the IterExplorer framework.
This commit is contained in:
parent
619a5173b0
commit
a780d696e5
6 changed files with 568 additions and 262 deletions
396
research/try.cpp
396
research/try.cpp
|
|
@ -48,28 +48,11 @@
|
|||
// 08/22 - techniques to supply additional feature selectors to a constructor call
|
||||
// 10/23 - search for ways to detect signatures of member functions and functors uniformly
|
||||
// 11/23 - prototype for a builder-DSL to configure a functor to draw and map random values
|
||||
// 11/23 - prototype for grouping from iterator
|
||||
|
||||
|
||||
/** @file try.cpp
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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)
|
||||
* @note transformed into a generic library component for usage by vault::gear::TestChainLoad
|
||||
* Investigate how best to integrate a grouping device into the iterator pipeline framework.
|
||||
*/
|
||||
|
||||
typedef unsigned int uint;
|
||||
|
|
@ -78,239 +61,186 @@ typedef unsigned int uint;
|
|||
#include "lib/format-cout.hpp"
|
||||
#include "lib/test/test-helper.hpp"
|
||||
#include "lib/test/diagnostic-output.hpp"
|
||||
#include "lib/format-util.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include "lib/meta/function.hpp"
|
||||
#include <functional>
|
||||
#include "lib/iter-explorer.hpp"
|
||||
#include "lib/test/test-coll.hpp"
|
||||
#include <utility>
|
||||
#include <array>
|
||||
|
||||
using lib::meta::_Fun;
|
||||
using std::function;
|
||||
using std::forward;
|
||||
using std::move;
|
||||
|
||||
template<typename T, T max>
|
||||
struct Limited
|
||||
{
|
||||
static constexpr T minVal() { return T(0); }
|
||||
static constexpr T maxVal() { return max; }
|
||||
namespace lib {
|
||||
namespace iter_explorer {
|
||||
|
||||
T val;
|
||||
|
||||
template<typename X>
|
||||
Limited (X raw)
|
||||
: val(util::limited (X(minVal()), raw, X(maxVal())))
|
||||
{ }
|
||||
};
|
||||
|
||||
template<typename T, T max>
|
||||
struct Spec
|
||||
{
|
||||
using Lim = Limited<T,max>;
|
||||
static constexpr double CAP_EPSILON = 0.0001;
|
||||
template<class SRC, class RES, uint grp>
|
||||
class Groupy
|
||||
: public SRC
|
||||
{
|
||||
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
|
||||
|
||||
double probability{0};
|
||||
T maxResult{Lim::maxVal()};
|
||||
|
||||
Spec() = default;
|
||||
|
||||
explicit
|
||||
Spec (double p) : probability{p}{ }
|
||||
|
||||
Lim
|
||||
limited (double val)
|
||||
{
|
||||
if (probability == 0.0 or val == 0.0)
|
||||
return Lim{0};
|
||||
double q = (1.0 - probability);
|
||||
val -= q;
|
||||
val /= probability;
|
||||
val *= maxResult;
|
||||
val += 1 + CAP_EPSILON;
|
||||
return Lim{val};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename T, T max>
|
||||
struct Draw
|
||||
: Spec<T,max>
|
||||
, function<Limited<T,max>(size_t)>
|
||||
{
|
||||
using Spc = Spec<T,max>;
|
||||
using Lim = typename Spc::Lim;
|
||||
using Fun = function<Lim(size_t)>;
|
||||
|
||||
Draw()
|
||||
: Spc{}
|
||||
, Fun{[this](size_t hash){ return Spc::limited (asRand (hash)); }}
|
||||
{ }
|
||||
|
||||
template<class FUN>
|
||||
Draw(FUN&& fun)
|
||||
: Spc{1.0}
|
||||
, Fun{adaptOut(adaptIn(std::forward<FUN> (fun)))}
|
||||
{ }
|
||||
|
||||
|
||||
Draw&&
|
||||
probability (double p)
|
||||
{
|
||||
Spc::probability = p;
|
||||
return move (*this);
|
||||
}
|
||||
|
||||
Draw&&
|
||||
maxVal (uint m)
|
||||
{
|
||||
Spc::maxResult = m;
|
||||
return move (*this);
|
||||
}
|
||||
|
||||
template<class FUN>
|
||||
Draw&&
|
||||
mapping (FUN&& fun)
|
||||
{
|
||||
Fun(*this) = adaptOut(adaptIn(std::forward<FUN> (fun)));
|
||||
return move (*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
double
|
||||
asRand (size_t hash)
|
||||
{
|
||||
return double(hash % 256)/256;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal helper to expose the signature `size_t(size_t)`
|
||||
* by wrapping a given lambda or functor.
|
||||
*/
|
||||
template<class SIG, typename SEL=void>
|
||||
struct Adaptor
|
||||
{
|
||||
static_assert (not sizeof(SIG), "Unable to adapt given functor.");
|
||||
};
|
||||
|
||||
template<typename RES>
|
||||
struct Adaptor<RES(size_t)>
|
||||
{
|
||||
template<typename FUN>
|
||||
static decltype(auto)
|
||||
build (FUN&& fun)
|
||||
protected:
|
||||
using Group = std::array<RES, grp>;
|
||||
using Iter = typename Group::iterator;
|
||||
union Buffer
|
||||
{
|
||||
return std::forward<FUN>(fun);
|
||||
char storage[sizeof(Group)];
|
||||
Group group;
|
||||
|
||||
Iter begin() { return group.begin();}
|
||||
Iter end() { return group.end(); }
|
||||
};
|
||||
Buffer buff_;
|
||||
uint pos_{0};
|
||||
|
||||
|
||||
public:
|
||||
using value_type = Group;
|
||||
using reference = Group&;
|
||||
using pointer = Group*;
|
||||
|
||||
Groupy() =default;
|
||||
// inherited default copy operations
|
||||
|
||||
Groupy (SRC&& dataSrc)
|
||||
: SRC{move (dataSrc)}
|
||||
{
|
||||
pullGroup(); // initially pull to establish the invariant
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterate over the Elements in the current group.
|
||||
* @return a Lumiera Forward Iterator with value type RES
|
||||
*/
|
||||
auto
|
||||
getGroupedElms()
|
||||
{
|
||||
ENSURE (buff_.begin()+pos_ <= buff_.end());
|
||||
// Array iterators are actually pointers
|
||||
return RangeIter{buff_.begin(), buff_.begin()+pos_};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the tail elements produced by the source,
|
||||
* which did not suffice to fill a full group.
|
||||
* @remark getRest() is NIL during regular iteration, but
|
||||
* possibly yields elements when checkPoint() = false;
|
||||
*/
|
||||
auto
|
||||
getRestElms()
|
||||
{
|
||||
return checkPoint()? RangeIter<Iter>()
|
||||
: getGroupedElms();
|
||||
}
|
||||
|
||||
/** refresh state when other layers manipulate the source sequence.
|
||||
* @note possibly pulls to re-establish the invariant */
|
||||
void
|
||||
expandChildren()
|
||||
{
|
||||
SRC::expandChildren();
|
||||
pullGroup();
|
||||
}
|
||||
|
||||
public: /* === Iteration control API for IterableDecorator === */
|
||||
|
||||
bool
|
||||
checkPoint() const
|
||||
{
|
||||
return pos_ == grp;
|
||||
}
|
||||
|
||||
reference
|
||||
yield() const
|
||||
{
|
||||
return unConst(buff_).group;
|
||||
}
|
||||
|
||||
void
|
||||
iterNext()
|
||||
{
|
||||
pullGroup();
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
SRC&
|
||||
srcIter() const
|
||||
{
|
||||
return unConst(*this);
|
||||
}
|
||||
|
||||
/** @note establishes the invariant:
|
||||
* source has been consumed to fill a group */
|
||||
void
|
||||
pullGroup ()
|
||||
{
|
||||
for (pos_=0
|
||||
; pos_<grp and srcIter()
|
||||
; ++pos_,++srcIter()
|
||||
)
|
||||
buff_.group[pos_] = *srcIter();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename RES>
|
||||
struct Adaptor<RES(void)>
|
||||
{
|
||||
template<typename FUN>
|
||||
static auto
|
||||
build (FUN&& fun)
|
||||
{
|
||||
return [functor=std::forward<FUN>(fun)]
|
||||
(size_t)
|
||||
{
|
||||
return functor();
|
||||
};
|
||||
}
|
||||
};
|
||||
template<uint grp, class IT>
|
||||
auto
|
||||
groupy (IT&& src)
|
||||
{
|
||||
using Value = typename meta::ValueTypeBinding<IT>::value_type;
|
||||
using ResCore = Groupy<IT, Value, grp>;
|
||||
using ResIter = typename _DecoratorTraits<ResCore>::SrcIter;
|
||||
|
||||
return IterExplorer<ResIter> (ResCore {move(src)});
|
||||
}
|
||||
|
||||
}//iter_explorer
|
||||
}//lib
|
||||
|
||||
using lib::test::getTestSeq_int;
|
||||
using lib::test::VecI;
|
||||
|
||||
/** Diagnostic helper: join all the elements from a _copy_ of the iterator */
|
||||
template<class II>
|
||||
inline string
|
||||
materialise (II&& ii)
|
||||
{
|
||||
return util::join (std::forward<II> (ii), "-");
|
||||
}
|
||||
|
||||
template<uint num, uint grp>
|
||||
void
|
||||
test()
|
||||
{
|
||||
VecI vec1 = getTestSeq_int<VecI> (num);
|
||||
cout <<"---"<<grp<<" of "<<num<<"---\n"
|
||||
<< materialise(vec1) <<endl;
|
||||
|
||||
template<class FUN>
|
||||
decltype(auto)
|
||||
adaptIn (FUN&& fun)
|
||||
{
|
||||
static_assert (lib::meta::_Fun<FUN>(), "Need something function-like.");
|
||||
static_assert (lib::meta::_Fun<FUN>::ARITY <= 1, "Function with zero or one argument expected.");
|
||||
|
||||
using Sig = typename lib::meta::_Fun<FUN>::Sig;
|
||||
|
||||
return Adaptor<Sig>::build (forward<FUN> (fun));
|
||||
}
|
||||
|
||||
template<class FUN>
|
||||
decltype(auto)
|
||||
adaptOut (FUN&& fun)
|
||||
{
|
||||
static_assert (lib::meta::_Fun<FUN>(), "Need something function-like.");
|
||||
static_assert (lib::meta::_Fun<FUN>::ARITY ==1, "Function with exactly one argument required.");
|
||||
|
||||
using Res = typename lib::meta::_Fun<FUN>::Ret;
|
||||
|
||||
if constexpr (std::is_same_v<Res, Lim>)
|
||||
return std::forward<FUN>(fun);
|
||||
else
|
||||
if constexpr (std::is_same_v<Res, size_t>)
|
||||
return [functor=std::forward<FUN>(fun), this]
|
||||
(size_t rawHash)
|
||||
{
|
||||
size_t hash = functor(rawHash);
|
||||
double randomNum = asRand (hash);
|
||||
return Spc::limited (randomNum);
|
||||
};
|
||||
else
|
||||
if constexpr (std::is_same_v<Res, double>)
|
||||
return [functor=std::forward<FUN>(fun), this]
|
||||
(size_t rawHash)
|
||||
{
|
||||
double randomNum = functor(rawHash);
|
||||
return Spc::limited (randomNum);
|
||||
};
|
||||
else
|
||||
if constexpr (std::is_same_v<Res, Draw>)
|
||||
return [functor=std::forward<FUN>(fun), this]
|
||||
(size_t rawHash)
|
||||
{
|
||||
Draw parametricDraw = functor(rawHash);
|
||||
return parametricDraw (rawHash);
|
||||
};
|
||||
else
|
||||
static_assert (not sizeof(Res), "unable to adapt / handle result type");
|
||||
NOTREACHED("Handle based on return type");
|
||||
}
|
||||
|
||||
};
|
||||
auto it = lib::explore(vec1);
|
||||
auto groupie = lib::explore(lib::iter_explorer::groupy<grp> (move(it)))
|
||||
.transform([](auto it){ return "["+util::join(*it)+"]"; });
|
||||
|
||||
for ( ;groupie; ++groupie)
|
||||
cout << *groupie<<"-";
|
||||
|
||||
CHECK (not groupie);
|
||||
CHECK (groupie.getGroupedElms());
|
||||
CHECK (groupie.getRestElms());
|
||||
for (auto r = groupie.getRestElms() ;r; ++r)
|
||||
cout << *r<<"+";
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
int
|
||||
main (int, char**)
|
||||
{
|
||||
using D = Draw<uint,16>;
|
||||
using L = typename D::Lim;
|
||||
using S = typename D::Spc;
|
||||
D draw;
|
||||
SHOW_EXPR(draw)
|
||||
SHOW_EXPR(draw(5).val)
|
||||
|
||||
draw = D{[](size_t i){ return 0.75; }};
|
||||
SHOW_EXPR(draw(5).val)
|
||||
|
||||
draw = D{}.probability(0.25).maxVal(5);
|
||||
SHOW_EXPR(draw(256-65).val)
|
||||
SHOW_EXPR(draw(256-64).val)
|
||||
SHOW_EXPR(draw(256-64+ 1).val)
|
||||
SHOW_EXPR(draw(256-64+10).val)
|
||||
SHOW_EXPR(draw(256-64+11).val)
|
||||
SHOW_EXPR(draw(256-64+12).val)
|
||||
SHOW_EXPR(draw(256-64+13).val)
|
||||
SHOW_EXPR(draw(256-64+23).val)
|
||||
SHOW_EXPR(draw(256-64+24).val)
|
||||
SHOW_EXPR(draw(256-64+25).val)
|
||||
SHOW_EXPR(draw(256-64+26).val)
|
||||
SHOW_EXPR(draw(256-64+36).val)
|
||||
SHOW_EXPR(draw(256-64+37).val)
|
||||
SHOW_EXPR(draw(256-64+38).val)
|
||||
SHOW_EXPR(draw(256-64+39).val)
|
||||
SHOW_EXPR(draw(256-64+49).val)
|
||||
SHOW_EXPR(draw(256-64+50).val)
|
||||
SHOW_EXPR(draw(256-64+51).val)
|
||||
SHOW_EXPR(draw(256-64+52).val)
|
||||
SHOW_EXPR(draw(256-64+62).val)
|
||||
SHOW_EXPR(draw(256-64+63).val)
|
||||
SHOW_EXPR(draw(256).val)
|
||||
test<10,3>();
|
||||
test<13,5>();
|
||||
test<55,23>();
|
||||
test<23,55>();
|
||||
|
||||
cout << "\n.gulp.\n";
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -849,6 +849,128 @@ namespace lib {
|
|||
|
||||
|
||||
|
||||
/**
|
||||
* @internal Decorator for IterExplorer to group consecutive elements into fixed sized chunks.
|
||||
* One group of elements is always prepared eagerly, and then the next one on iteration.
|
||||
* The group is packaged into a std::array, returning a _reference_ into the internal buffer.
|
||||
* If there are leftover elements at the end of the source sequence, which are not sufficient
|
||||
* to fill a full group, these can be retrieved through the special API getRestElms(), which
|
||||
* returns an iterator.
|
||||
*/
|
||||
template<class SRC, class RES, uint grp>
|
||||
class Grouping
|
||||
: public SRC
|
||||
{
|
||||
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
|
||||
|
||||
protected:
|
||||
using Group = std::array<RES, grp>;
|
||||
using Iter = typename Group::iterator;
|
||||
struct Buffer
|
||||
{
|
||||
char storage[sizeof(Group)];
|
||||
|
||||
Group& group() { return reinterpret_cast<Group&> (storage); }
|
||||
|
||||
Iter begin() { return group().begin();}
|
||||
Iter end() { return group().end(); }
|
||||
};
|
||||
Buffer buff_;
|
||||
uint pos_{0};
|
||||
|
||||
|
||||
public:
|
||||
using value_type = Group;
|
||||
using reference = Group&;
|
||||
using pointer = Group*;
|
||||
|
||||
Grouping() =default;
|
||||
// inherited default copy operations
|
||||
|
||||
Grouping (SRC&& dataSrc)
|
||||
: SRC{move (dataSrc)}
|
||||
{
|
||||
pullGroup(); // initially pull to establish the invariant
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterate over the Elements in the current group.
|
||||
* @return a Lumiera Forward Iterator with value type RES
|
||||
*/
|
||||
auto
|
||||
getGroupedElms()
|
||||
{
|
||||
ENSURE (buff_.begin()+pos_ <= buff_.end());
|
||||
// Array iterators are actually pointers
|
||||
return RangeIter{buff_.begin(), buff_.begin()+pos_};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the tail elements produced by the source,
|
||||
* which did not suffice to fill a full group.
|
||||
* @remark getRest() is NIL during regular iteration, but
|
||||
* possibly yields elements when checkPoint() = false;
|
||||
*/
|
||||
auto
|
||||
getRestElms()
|
||||
{
|
||||
return checkPoint()? RangeIter<Iter>()
|
||||
: getGroupedElms();
|
||||
}
|
||||
|
||||
/** refresh state when other layers manipulate the source sequence.
|
||||
* @note possibly pulls to re-establish the invariant */
|
||||
void
|
||||
expandChildren()
|
||||
{
|
||||
SRC::expandChildren();
|
||||
pullGroup();
|
||||
}
|
||||
|
||||
public: /* === Iteration control API for IterableDecorator === */
|
||||
|
||||
bool
|
||||
checkPoint() const
|
||||
{
|
||||
return pos_ == grp;
|
||||
}
|
||||
|
||||
reference
|
||||
yield() const
|
||||
{
|
||||
return unConst(buff_).group();
|
||||
}
|
||||
|
||||
void
|
||||
iterNext()
|
||||
{
|
||||
pullGroup();
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
SRC&
|
||||
srcIter() const
|
||||
{
|
||||
return unConst(*this);
|
||||
}
|
||||
|
||||
/** @note establishes the invariant:
|
||||
* source has been consumed to fill a group */
|
||||
void
|
||||
pullGroup ()
|
||||
{
|
||||
for (pos_=0
|
||||
; pos_<grp and srcIter()
|
||||
; ++pos_,++srcIter()
|
||||
)
|
||||
buff_.group()[pos_] = *srcIter();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @internal Decorator for IterExplorer to filter elements based on a predicate.
|
||||
* Similar to the Transformer, the given functor is adapted as appropriate. However,
|
||||
|
|
@ -1459,6 +1581,26 @@ namespace lib {
|
|||
}
|
||||
|
||||
|
||||
/** adapt this IterExplorer group result elements into fixed size chunks, packaged as std::array.
|
||||
* The first group of elements is pulled eagerly at construction, while further groups are formed
|
||||
* on consecutive iteration. Iteration ends when no further full group can be formed; this may
|
||||
* leave out some leftover elements, which can then be retrieved by iteration through the
|
||||
* special API [getRestElms()](\ref iter_explorer::Grouping::getRestElms).
|
||||
* @return processing pipeline with attached [Grouping](\ref iter_explorer::Grouping) decorator
|
||||
* @warning yields a reference into the internal buffer, changed on next iteration.
|
||||
*/
|
||||
template<uint grp>
|
||||
auto
|
||||
grouped()
|
||||
{
|
||||
using Value = typename meta::ValueTypeBinding<SRC>::value_type;
|
||||
using ResCore = iter_explorer::Grouping<SRC, Value, grp>;
|
||||
using ResIter = typename _DecoratorTraits<ResCore>::SrcIter;
|
||||
|
||||
return IterExplorer<ResIter> (ResCore {move(*this)});
|
||||
}
|
||||
|
||||
|
||||
/** adapt this IterExplorer to iterate only as long as a condition holds true.
|
||||
* @return processing pipeline with attached [stop condition](\ref iter_explorer::StopTrigger)
|
||||
*/
|
||||
|
|
@ -1577,6 +1719,25 @@ namespace lib {
|
|||
|
||||
|
||||
|
||||
/** preconfigured transformer to pass pointers down the pipeline */
|
||||
auto
|
||||
asPtr()
|
||||
{
|
||||
using Val = typename meta::ValueTypeBinding<SRC>::value_type;
|
||||
static_assert (not std::is_pointer_v<Val>);
|
||||
return IterExplorer::transform ([](Val& ref){ return &ref; });
|
||||
}
|
||||
|
||||
/** preconfigured transformer to dereference pointers into references */
|
||||
auto
|
||||
derefPtr()
|
||||
{
|
||||
using Ptr = typename meta::ValueTypeBinding<SRC>::value_type;
|
||||
return IterExplorer::transform ([](Ptr ptr){ return *ptr; });
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** _terminal builder_ to package the processing pipeline as IterSource.
|
||||
* Invoking this function moves the whole iterator compound, as assembled by the preceding
|
||||
* builder calls, into heap allocated memory and returns an [iterator front-end](\ref IterExploreSource).
|
||||
|
|
|
|||
|
|
@ -278,6 +278,7 @@ namespace test{
|
|||
verify_expandOperation();
|
||||
verify_expand_rootCurrent();
|
||||
verify_transformOperation();
|
||||
verify_elementGroupingOperation();
|
||||
verify_combinedExpandTransform();
|
||||
verify_customProcessingLayer();
|
||||
verify_scheduledExpansion();
|
||||
|
|
@ -667,6 +668,67 @@ namespace test{
|
|||
}
|
||||
|
||||
|
||||
/** @test package elements from the source pipeline into fixed-sized groups.
|
||||
* These groups are implemented as std::array and initialised with the values
|
||||
* yielded consecutively from the underlying source pipeline. The main iterator
|
||||
* then yields a reference to this data (which can be unpacked conveniently
|
||||
* by a structured binding, or processed as a STL container.
|
||||
* Moreover, there is a secondary interface, allowing to iterate over the
|
||||
* values stored in this group; this is also exposed for the rest, which
|
||||
* did not suffice to fill a full group.
|
||||
*/
|
||||
void
|
||||
verify_elementGroupingOperation()
|
||||
{
|
||||
auto showGroup = [](auto it){ return "["+util::join(*it)+"]"; };
|
||||
CHECK (materialise (
|
||||
explore(CountDown{10})
|
||||
.grouped<3>()
|
||||
.transform(showGroup)
|
||||
)
|
||||
== "[10, 9, 8]-[7, 6, 5]-[4, 3, 2]"_expect);
|
||||
|
||||
|
||||
auto ii = explore(CountDown{23})
|
||||
.grouped<5>();
|
||||
CHECK(ii);
|
||||
CHECK(ii.getGroupedElms());
|
||||
CHECK(not ii.getRestElms());
|
||||
CHECK (materialise(ii.getGroupedElms()) == "23-22-21-20-19"_expect);
|
||||
|
||||
CHECK ( test::showType<decltype(*ii)>()== "array<unsigned int, 5ul>&"_expect);
|
||||
|
||||
uint s = *(ii.getGroupedElms());
|
||||
for ( ; ii; ++ii)
|
||||
{
|
||||
auto grp = *ii;
|
||||
CHECK (5 == grp.size());
|
||||
auto& [a,b,c,d,e] = grp;
|
||||
CHECK (a == s);
|
||||
CHECK (b == a-1);
|
||||
CHECK (c == a-2);
|
||||
CHECK (d == a-3);
|
||||
CHECK (e == a-4);
|
||||
CHECK (not ii.getRestElms());
|
||||
s -= 5;
|
||||
}
|
||||
CHECK (s < 5);
|
||||
CHECK (s == 3);
|
||||
|
||||
CHECK (not ii);
|
||||
CHECK(ii.getGroupedElms());
|
||||
CHECK(ii.getRestElms());
|
||||
CHECK (materialise(ii.getGroupedElms()) == "3-2-1"_expect);
|
||||
CHECK (materialise(ii.getRestElms()) == "3-2-1"_expect);
|
||||
|
||||
|
||||
auto iii = explore(CountDown{4})
|
||||
.grouped<5>();
|
||||
CHECK (not iii);
|
||||
CHECK (materialise(iii.getRestElms()) == "4-3-2-1"_expect);
|
||||
}
|
||||
|
||||
|
||||
/** @test combine the recursion into children with a tail mapping operation.
|
||||
* Wile basically this is just the layering structure of IterExplorer put into action,
|
||||
* you should note one specific twist: the iter_explorer::Expander::expandChildren() call
|
||||
|
|
|
|||
|
|
@ -48,6 +48,19 @@ namespace vault{
|
|||
namespace gear {
|
||||
namespace test {
|
||||
|
||||
namespace { // shorthands and parameters for test...
|
||||
|
||||
/** shorthand for specific parameters employed by the following tests */
|
||||
using ChainLoad32 = TestChainLoad<32,16>;
|
||||
using Node = ChainLoad32::Node;
|
||||
auto isStartNode = [](Node& n){ return isStart(n); };
|
||||
auto isInnerNode = [](Node& n){ return isInner(n); };
|
||||
auto isExitNode = [](Node& n){ return isExit(n); };
|
||||
|
||||
}//(End)test definitions
|
||||
|
||||
|
||||
|
||||
|
||||
/*****************************************************************//**
|
||||
* @test verify a tool to generate synthetic load for Scheduler tests.
|
||||
|
|
@ -126,7 +139,7 @@ namespace test {
|
|||
|
||||
CHECK (n0.hash == 0);
|
||||
n0.calculate(); // but now hash calculation combines predecessors
|
||||
CHECK (n0.hash == 6050854883719206282u);
|
||||
CHECK (n0.hash == 0x53F8F4753B85558A);
|
||||
|
||||
Node n00; // another Node...
|
||||
n00.addPred(n2) // just adding the predecessors in reversed order
|
||||
|
|
@ -134,8 +147,8 @@ namespace test {
|
|||
|
||||
CHECK (n00.hash == 0);
|
||||
n00.calculate(); // ==> hash is different, since it depends on order
|
||||
CHECK (n00.hash == 17052526497278249714u);
|
||||
CHECK (n0.hash == 6050854883719206282u);
|
||||
CHECK (n00.hash == 0xECA6BE804934CAF2);
|
||||
CHECK (n0.hash == 0x53F8F4753B85558A);
|
||||
|
||||
CHECK (isSameObject (*n1.succ[0], n0));
|
||||
CHECK (isSameObject (*n1.succ[1], n00));
|
||||
|
|
@ -146,9 +159,9 @@ namespace test {
|
|||
CHECK (isSameObject (*n0.pred[0], n1));
|
||||
CHECK (isSameObject (*n0.pred[1], n2));
|
||||
|
||||
CHECK (n00.hash == 17052526497278249714u);
|
||||
CHECK (n00.hash == 0xECA6BE804934CAF2);
|
||||
n00.calculate(); // calculation is NOT idempotent (inherently statefull)
|
||||
CHECK (n00.hash == 13151338213516862912u);
|
||||
CHECK (n00.hash == 0xB682F06D29B165C0);
|
||||
|
||||
CHECK (isnil (n0.succ)); // number of predecessors or successors properly accounted for
|
||||
CHECK (isnil (n00.succ));
|
||||
|
|
@ -175,12 +188,12 @@ namespace test {
|
|||
void
|
||||
verify_Topology()
|
||||
{
|
||||
auto graph = TestChainLoad<32>{}
|
||||
auto graph = ChainLoad32{}
|
||||
.buildToplolgy();
|
||||
|
||||
CHECK (graph.topLevel() == 31);
|
||||
CHECK (graph.getSeed() == 0);
|
||||
CHECK (graph.getHash() == 6692160254289221734u);
|
||||
CHECK (graph.getHash() == 0x5CDF544B70E59866);
|
||||
|
||||
auto* node = & *graph.allNodes();
|
||||
CHECK (node->hash == graph.getSeed());
|
||||
|
|
@ -210,7 +223,7 @@ namespace test {
|
|||
CHECK (steps == 31);
|
||||
CHECK (steps == graph.topLevel());
|
||||
CHECK (node->hash == graph.getHash());
|
||||
CHECK (node->hash == 6692160254289221734u);
|
||||
CHECK (node->hash == 0x5CDF544B70E59866);
|
||||
} // hash of the graph is hash of last node
|
||||
|
||||
|
||||
|
|
@ -221,7 +234,7 @@ namespace test {
|
|||
void
|
||||
control_Topology()
|
||||
{
|
||||
auto graph = TestChainLoad<32>{};
|
||||
ChainLoad32 graph;
|
||||
|
||||
graph.expansionRule(graph.rule().probability(0.8).maxVal(1))
|
||||
.pruningRule(graph.rule().probability(0.6))
|
||||
|
|
@ -231,32 +244,88 @@ namespace test {
|
|||
|
||||
|
||||
|
||||
/** @test set and propagate seed values and recalculate all node hashes
|
||||
* @todo WIP 11/23 🔁 define ⟶ implement
|
||||
/** @test set and propagate seed values and recalculate all node hashes.
|
||||
* @remark This test uses parameter rules with some expansion and a
|
||||
* pruning rule with 60% probability. This setup is known to
|
||||
* create a sequence of tiny isolated trees with 4 nodes each;
|
||||
* there are 8 such groups, each with a fork and two exit nodes;
|
||||
* the last group is wired differently however, because there the
|
||||
* limiting-mechanism of the topology generation activates to ensure
|
||||
* that the last node is an exit node. The following code traverses
|
||||
* all nodes grouped into 4-node clusters to verify this regular
|
||||
* pattern and the calculated hashes.
|
||||
* @todo WIP 11/23 ✔ define ⟶ ✔ implement
|
||||
*/
|
||||
void
|
||||
reseed_recalculate()
|
||||
{
|
||||
auto graph = TestChainLoad<32>{};
|
||||
ChainLoad32 graph;
|
||||
graph.expansionRule(graph.rule().probability(0.8).maxVal(1))
|
||||
.pruningRule(graph.rule().probability(0.6))
|
||||
.buildToplolgy();
|
||||
|
||||
using Node = TestChainLoad<32>::Node;
|
||||
auto isStartNode = [](Node& n){ return isStart(n); };
|
||||
auto isExitNode = [](Node& n){ return isExit(n); };
|
||||
|
||||
CHECK (8 == graph.allNodes().filter(isStartNode).count());
|
||||
CHECK (15 == graph.allNodes().filter(isExitNode).count());
|
||||
|
||||
CHECK (graph.getHash() == 14172386810742845390u);
|
||||
CHECK (graph.getHash() == 0xC4AE6EB741C22FCE);
|
||||
graph.allNodePtr().grouped<4>()
|
||||
.foreach([&](auto group)
|
||||
{ // verify wiring pattern
|
||||
// and the resulting exit hashes
|
||||
auto& [a,b,c,d] = *group;
|
||||
CHECK (isStart(a));
|
||||
CHECK (isInner(b));
|
||||
if (b->succ.size() == 2)
|
||||
{
|
||||
CHECK (isExit(c));
|
||||
CHECK (isExit(d));
|
||||
CHECK (c->hash == 0xAEDC04CFA2E5B999);
|
||||
CHECK (d->hash == 0xAEDC04CFA2E5B999);
|
||||
}
|
||||
else
|
||||
{ // the last chunk is wired differently
|
||||
CHECK (b->succ.size() == 1);
|
||||
CHECK (b->succ[0] == c);
|
||||
CHECK (isInner(c));
|
||||
CHECK (isExit(d));
|
||||
CHECK (graph.nodeID(d) == 31);
|
||||
CHECK (d->hash == graph.getHash());
|
||||
} // this is the global exit node
|
||||
});
|
||||
|
||||
|
||||
graph.setSeed(55).clearNodeHashes();
|
||||
CHECK (graph.getSeed() == 55);
|
||||
CHECK (graph.getHash() == 0);
|
||||
graph.allNodePtr().grouped<4>()
|
||||
.foreach([&](auto group)
|
||||
{ // verify hashes have been reset
|
||||
auto& [a,b,c,d] = *group;
|
||||
CHECK (a->hash == 55);
|
||||
CHECK (b->hash == 0);
|
||||
CHECK (b->hash == 0);
|
||||
CHECK (b->hash == 0);
|
||||
});
|
||||
|
||||
graph.recalculate();
|
||||
CHECK (graph.getHash() == 6093128458724583708u);
|
||||
CHECK (graph.getHash() == 0x548F240CE91A291C);
|
||||
graph.allNodePtr().grouped<4>()
|
||||
.foreach([&](auto group)
|
||||
{ // verify hashes were recalculated
|
||||
// based on the new seed
|
||||
auto& [a,b,c,d] = *group;
|
||||
CHECK (a->hash == 55);
|
||||
if (b->succ.size() == 2)
|
||||
{
|
||||
CHECK (c->hash == 0x7887993B0ED41395);
|
||||
CHECK (d->hash == 0x7887993B0ED41395);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK (graph.nodeID(d) == 31);
|
||||
CHECK (d->hash == graph.getHash());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -239,10 +239,12 @@ namespace test {
|
|||
}
|
||||
|
||||
friend bool isStart (Node const& n) { return isnil (n.pred); };
|
||||
friend bool isStart (Node const* n) { return n and isnil (n->pred); };
|
||||
|
||||
friend bool isExit (Node const& n) { return isnil (n.succ); };
|
||||
friend bool isExit (Node const* n) { return n and isnil (n->succ); };
|
||||
friend bool isInner (Node const& n) { return not (isStart(n) or isExit(n)); }
|
||||
|
||||
friend bool isStart (Node const* n) { return n and isStart (*n); };
|
||||
friend bool isExit (Node const* n) { return n and isExit (*n); };
|
||||
friend bool isInner (Node const* n) { return n and isInner (*n); };
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -285,6 +287,16 @@ namespace test {
|
|||
{
|
||||
return lib::explore (*nodes_);
|
||||
}
|
||||
auto
|
||||
allNodePtr()
|
||||
{
|
||||
return allNodes().asPtr();
|
||||
}
|
||||
|
||||
/** @return the node's index number, based on its storage location */
|
||||
size_t nodeID(Node const* n){ return size_t(n - &nodes_->front()); };
|
||||
size_t nodeID(Node const& n){ return nodeID (&n); };
|
||||
|
||||
|
||||
|
||||
/* ===== topology control ===== */
|
||||
|
|
@ -477,8 +489,6 @@ namespace test {
|
|||
Code TOP {"shape=box, style=rounded"};
|
||||
Code DEFAULT{};
|
||||
|
||||
auto nodeID = [&](Node& n){ return size_t(&n - &nodes_->front()); };
|
||||
|
||||
// prepare time-level zero
|
||||
size_t level(0);
|
||||
auto timeLevel = scope(level).rank("min ");
|
||||
|
|
|
|||
|
|
@ -99444,8 +99444,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
<linktarget COLOR="#42b360" DESTINATION="ID_1175949615" ENDARROW="Default" ENDINCLINATION="334;0;" ID="Arrow_ID_357935660" SOURCE="ID_479859970" STARTARROW="None" STARTINCLINATION="-103;5;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1700105549294" ID="ID_1778641158" MODIFIED="1701033551086" TEXT="Neuberechnung">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node COLOR="#338800" CREATED="1700105549294" ID="ID_1778641158" MODIFIED="1701117645291" TEXT="Neuberechnung">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node COLOR="#435e98" CREATED="1701033351833" ID="ID_133315306" MODIFIED="1701033556660" TEXT="clearNodeHashes()">
|
||||
<node CREATED="1701033364791" ID="ID_442706492" MODIFIED="1701033368037" TEXT="besucht alle Nodes"/>
|
||||
<node CREATED="1701033368583" ID="ID_244375144" MODIFIED="1701033384649" TEXT="überträgt den seed-Hash in die »startNodes«"/>
|
||||
|
|
@ -99460,13 +99460,87 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
<node CREATED="1701033623128" ID="ID_1915341389" MODIFIED="1701033635439" TEXT="durch das Löschen wird ein sehr naheliegender Fehler vermieden"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1701033495195" ID="ID_193483520" MODIFIED="1701033506285" TEXT="Ergebnisse im Test verifizieren">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node COLOR="#338800" CREATED="1701033495195" ID="ID_193483520" MODIFIED="1701117639835" TEXT="Ergebnisse im Test verifizieren">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node COLOR="#338800" CREATED="1701033509532" ID="ID_1793560814" MODIFIED="1701033521653" TEXT="Filter-Prägdikate: isStart und isExit">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1701033523034" ID="ID_1580648658" MODIFIED="1701033543744" TEXT="irgendwie geschickt auf die Nodes zugreifen und prüfen">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node COLOR="#338800" CREATED="1701033523034" ID="ID_1580648658" MODIFIED="1701117602855" TEXT="irgendwie geschickt auf die Nodes zugreifen und prüfen">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1701058823384" ID="ID_1832007636" MODIFIED="1701058836698" TEXT="die Teilbäume sind fast alle gleich"/>
|
||||
<node COLOR="#435e98" CREATED="1701058838854" ID="ID_1499838566" MODIFIED="1701059035860" TEXT="Idee: die Nodes gruppieren">
|
||||
<icon BUILTIN="idea"/>
|
||||
<node BACKGROUND_COLOR="#ccb59b" COLOR="#6e2a38" CREATED="1701058863851" ID="ID_560691111" MODIFIED="1701059030270" TEXT="sowas wäre eine gute Ergänzung für den IterExplorer">
|
||||
<font ITALIC="true" NAME="SansSerif" SIZE="14"/>
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1701058881832" ID="ID_348005830" MODIFIED="1701059018446">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
sollte <i>vergleichsweise </i>einfach zu implementieren sein
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
...will sagen, ich muß da nicht lang überlegen und kann es mehr oder weniger runterschreiben (auch wenn's ein paar Stunden braucht)
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1701058923620" ID="ID_1761514428" MODIFIED="1701059020156" TEXT="gleiches Bauschema wie beim Filter-Iter (pull one group eagerly)">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1701058974044" ID="ID_621330728" MODIFIED="1701058979307" TEXT="eingebaut und getestet">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1701117355642" ID="ID_778739432" MODIFIED="1701117369148" TEXT="muß dann aber über Node* iterieren">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
<node CREATED="1701117373814" ID="ID_340694208" MODIFIED="1701117498363" TEXT="weil Node als move-only deklariert ist (inhaltlich sinnvoll)">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
...es wäre zwar nicht notwendig, da in der Node-struct lauter pointer und einfache Werte gespeichert werden; aber eine Node-Kopie wäre ein hochgradig inkonsistentes Gebilde, da dann die Rück-Referenzen nicht mehr stimmen würden.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
<node CREATED="1701117396795" ID="ID_887215905" MODIFIED="1701117581653" TEXT="und der grouped()-Mechanismus aber values speichern muß">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
...das folgt aus dem Design von IterExplorer insgesamt. Es wäre hochgradig gefährlich, wenn man in der Gruppe Referenzen speichern würde. Wenn überhaupt, dann muß das explizit so angefordert werden, indem man in der Pipeline mit Pointern arbeitet.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1701117656420" ID="ID_1549878593" MODIFIED="1701117683318" TEXT="repeat-Funktion hier mit abdecken">
|
||||
<icon BUILTIN="bell"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#ccb59b" COLOR="#6e2a38" CREATED="1701117618701" ID="ID_1109241766" MODIFIED="1701117636221" TEXT="so ein Test ist nur einmal sinnvoll, um die Berechnung abzudecken">
|
||||
<font ITALIC="true" NAME="SansSerif" SIZE="14"/>
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
|
|
|
|||
Loading…
Reference in a new issue