Lumiera  0.pre.03
»edit your freedom«
random-draw.hpp File Reference

Go to the source code of this file.

Description

Build a component to select limited values randomly.

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 provided to simplify the common configuration and value mapping scenarios.

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 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«. Notably, results are assumed to conform to an ordered interval of integral values. The core functionality is to use the value from the random source (a size_t hash), break it down by some modulus to create an arbitrary 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 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 change parameters dynamically, based on the current input value. This requires the mapping function to construct a pristine instance of RandomDraw, apply configuration based on the input and then return this instance by value — without ever »engaging« and invoking; this dynamically configured instance will then be invoked once, passing the current input values to yield the result value.

Policy template

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:

  • its base type is std::function, with a result value similar to Limited
  • more specifically, the result type must be number-like and expose extension points to determine the minVal(), maxVal() and zeroVal()
  • moreover, the policy must define a function defaultSrc(args...); this function must accept input arguments in accordance to the function signature of the Policy (i.e. it must read "the randomness source") and produce a result that can be adapted and fed into the regular processing chain (the same as for any mapping function)
  • 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 possible configurations at usage site.

Copy inhibition

The configuration of the RandomDraw processing pipeline makes heavy use of function composition and adaptation to handle a wide selection of input types and usage patterns. Unfortunately this requires to link the generated configuration-λ to the object instance (capturing by reference); not allowing this would severely limit the possible configurations. This implies that an object instance must not be moved anymore, once the processing pipeline has been configured. And this in turn would severely limit it's usage in a DSL. As a compromise, RandomDraw relies on lazy on-demand initialisation: as long as the processing function has not been invoked, the internal pipeline is unconfigured, and the object can be moved and copied. Once invoked, the prepared configuration is assembled and the function »engaged«; from this point on, any attempt to move or copy the object will throw an exception, while it is still possible to assign other RandomDraw instances to this object.

Todo:
11/2023 This is a first draft and was extracted from an actual usage scenario. It remains to be seen if the scheme as defined is of any further use henceforth.
See also
RandomDraw_test
lazy-init.hpp
TestChainLoad_test
SchedulerStress_test

Definition in file random-draw.hpp.

#include "lib/error.h"
#include "lib/random.hpp"
#include "lib/lazy-init.hpp"
#include "lib/meta/function.hpp"
#include "lib/meta/function-closure.hpp"
#include "lib/util-quant.hpp"
#include "lib/util.hpp"
#include <functional>
#include <utility>

Classes

struct  Limited< T, max, min, zero >
 A Result Value confined into fixed bounds. More...
 
struct  LimitedRandomGenerate< max >
 Default policy for RandomDraw: generate limted-range random numbers. More...
 
class  RandomDraw< POL >
 A component and builder to draw limited parameter values based on some source of randomness (or hash input). More...
 

Namespaces

 lib
 Implementation namespace for support and library code.