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:
Fischlurch 2023-11-27 21:58:37 +01:00
parent 619a5173b0
commit a780d696e5
6 changed files with 568 additions and 262 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -99444,8 +99444,8 @@ Date:&#160;&#160;&#160;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="&#xfc;bertr&#xe4;gt den seed-Hash in die &#xbb;startNodes&#xab;"/>
@ -99460,13 +99460,87 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1701033623128" ID="ID_1915341389" MODIFIED="1701033635439" TEXT="durch das L&#xf6;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&#xe4;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&#xfc;fen">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1701033523034" ID="ID_1580648658" MODIFIED="1701117602855" TEXT="irgendwie geschickt auf die Nodes zugreifen und pr&#xfc;fen">
<icon BUILTIN="button_ok"/>
<node CREATED="1701058823384" ID="ID_1832007636" MODIFIED="1701058836698" TEXT="die Teilb&#xe4;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&#xe4;re eine gute Erg&#xe4;nzung f&#xfc;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&#223; da nicht lang &#252;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&#xdf; dann aber &#xfc;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&#228;re zwar nicht notwendig, da in der Node-struct lauter pointer und einfache Werte gespeichert werden; aber eine Node-Kopie w&#228;re ein hochgradig inkonsistentes Gebilde, da dann die R&#252;ck-Referenzen nicht mehr stimmen w&#252;rden.
</p>
</body>
</html>
</richcontent>
</node>
<node CREATED="1701117396795" ID="ID_887215905" MODIFIED="1701117581653" TEXT="und der grouped()-Mechanismus aber values speichern mu&#xdf;">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
...das folgt aus dem Design von IterExplorer insgesamt. Es w&#228;re hochgradig gef&#228;hrlich, wenn man in der Gruppe Referenzen speichern w&#252;rde. Wenn &#252;berhaupt, dann mu&#223; 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>