Seed and randomness #7579
Replies: 3 comments 5 replies
-
please check out http://varia.artificiel.org/of/randomexplorer/randomexplorer.html (chrome a bit better); you can peruse the lib here: https://github.com/artificiel/openFrameworks/blob/ofRandom/libs/openFrameworks/utils/ofRandom.h and the app here: https://github.com/artificiel/openFrameworks/tree/ofRandom/apps/devApps/random%20explorer/src NOTES
|
Beta Was this translation helpful? Give feedback.
-
yes, i'm not conceptually drawn to web/emscripten as an artistic dissemination platform, but for tests/examples/etc there is great value there. i'd like to make sure of a few things:
|
Beta Was this translation helpful? Give feedback.
-
good idea to "parallelize" the seeding of once that's confirmed stable, the next step will be to introduce the distributions and replace all calls to I'll be able to do that within the next few days. |
Beta Was this translation helpful? Give feedback.
-
Taking the cce8428 occasion of fixing
ofRandomShuffle()
to revisit seeding and random generators in OF. The concept of randomness and the relationship with seeding is not immediately evident, as one can be under the impression that computers can pick "random numbers" out of thin air while actually, everything is deterministic — it just seems unpredictable to us. Of course with time-based processes, async events, recursivity and real world sensor input, it quickly becomes very hard to predict, but it's still a direct consequence of iterating a math function over itself from a given "seed" starting point.CLARIFYING THE SEEDING INTERFACE
As of now OF has a function call
ofSeedRandom()
which does two very different things: (1) called with no arguments, it seeds the random generator with the current time which is generally considered equivalent to a "random" number (so placing the system in an unpredictable state as it does as part as the OF scaffolding inof::priv::initutils()
); and (2) called with an argument, it seeds the random generator with the argument value, placing the system in a reproducible state.The first form is what most people colloquially expect when requesting "random" stuff, and is mostly handled internally by default. This second form is useful to organise a deterministic result in the style of reproducible generative "versions" of artworks à la brün.
Proposal: deprecate
ofSeedRandom()
(which is not a great name anyway) and introduce:This removes the no-argument version of
ofSeedRandom()
as there is no reason to reinitialize the seed to an otherwise unknown value (the behaviour will not differ from simply advancing the random engine). at App init,ofSetRandomSeed()
will be called with a proper pseudorandom number, placing the App in unpredictable state by default, which the user can override with a desired seed insetup()
(or elsewhere).CENTRAL ENGINE IMPLEMENTATION AND THREAD SAFETY
Currently in OF the old-school random generators rely on
srand()
, which is slowly crumbling. In order to benefit from the modern C++ numerical generators OF needs a better random engine. The correct way is to put astd::random_engine
into a generator such asstd::mt19937
, and use that to build distributions as needed (so all the distributions are fed from the same seed, enabling deterministic/reproducible results).However that is inherently thread-risky as the
mt19937
is not thread safe, and while OF/GL is mainly a single thread paradigm, things are evolving and more and more threading is expected, both in user code and library code. So a thread-safe implementation should be favoured, something better than a static pointer in the anonymous namespace.The usual approach is to use the
thread_local
keyword, which essentially make statics an instance-per-thread. However this requires different seeds for the different threads (else run the risk of the threads generating the same sequences, probably not what is expected). This makes is hard to support the "deterministically-seeded" design, as you would need to deterministically seed N threads, where N might be unknown to you, and might even not be deterministic due to OS changes or decisions.A solid barrier is thus required around a single-seed engine. For optimal performance I suggest applying a double-check-lock to a singleton pattern, and construct the singleton in
initutils()
; and at the same time I suggest adapting a DLCP singleton implementation asofSingleton
and insert it in the OF utils, based on: https://github.com/jimmy-park/singleton/blob/main/include/singleton_dclp.hpp so it can be reused across OF "global monolithic" components with absolute safety and state-of-the-art performance.BTW the current OF random implementation is not thread-safe:
openFrameworks/libs/openFrameworks/math/ofMath.h
Line 30 in cce8428
GENERATOR INTERFACE
Currently most of the OF random comes from
ofRandom
, which callsrand()
(which depends onsrand()
) and optionally applies a range. This implies a uniform distribution, which is what most people expect from a "random generator" (thinking about a roll of the dice, or a number on the roulette). However there are many distributions, and I am convinced OF (and the computing/generative arts in general) would gain by having a cleaner, more direct interface to various distributions (and raise questions as early as possible in one's creative journey along the lines of what is (pseudo)randomness and how does it affect my work?). C++11 introduced a lot of distributions, and it would be great to provide a streamlined interface. The cumbersome part is the seeding and engine handling, but since OF handles that centrally, calling a specific distribution can be a one-liner.NB: I looked around for existing "<random> wrapper libraries", as well as ofxaddons, and nothing is proposing productive solutions in the context of a computing/generative artist's point of view. I also tried to find streamlined patterns to use directly the stdlib distributions in order to facilitate and encourage their use, but it's not practical to reach a on-liner interface and OF is exactly concerned about such efficiency in expressivity.
So I aim for expressivity in the writer’s point of view, as well as “growing” the potential of the form as needed. At the same time, minimizing “invention” and relying on the language and stdlib for as much as possible. In the context of OF, it means streamlining usage by reducing lines of code and localizing on the practical needs.
1. basics (backwards-compatible)
these sample a
std::uniform_real_distribution
based on the discussedmt19937
singleton.2. distributions
… etc as much as wanted from https://en.cppreference.com/w/cpp/numeric/random and by maintaining a 1:1 name & argument relationship the docs for the distributions are the C++ ones.
3. output type
4. refined vector ranges
In short, OF provides a central random device with auto or specific seed, and harnesses it to generate all the possible distributions, with templated output for the common types (floating, integer, glm::vec).
OTHER UTILITIES
So the starting point for this was to fix
ofRandomShuffle()
— in this case the implementation moves tostd::shuffle
, which relies on the mt19937 singleton engine seeded as above. If other OF functions rely on randomness, they should be adapted to feed from the same engine to ensure reproducible results in deterministic seeding scenarios.ofNoise()
for example should follow the seed.Note about immortality: the distributions, as well as things like
std::shuffle
are compiler implementation-dependent. This means that if you are concerned that your seeded artwork produces deterministic randomness "for ever, everywhere" you should investigate implementing your own distributions (or at least lifting the ones you use out of the compiler/platform libs). As cryptography pushes the parameters of randomness, it is to be expected that mt19937 will eventually be deprecated or "fixed", andofSetRandomSeed(1984)
will suddenly engender a different deterministic result. And of course already today it might also be different on macOS, windows, linux, or across CPUs/architectures, or across compilers vendors or versions.NAMESPACE?
This might warrant a different discussion but what is the current view on namespaces? Is it a correct time to move these functions into an
of::
namespace? or evenof::random::
/of::rand::
? It should be determined if it's a singleof::
or if there are submodules; #5641 seems to prefer a single rootof::
to make things visually more streamlined (and it satisfies the collision criteria as everything withinof::
can be easily controlled).I dont mind one or the other but now that the vector types are
glm::
and the introduction ofof::filesystem
my take is that any OF component that is worked on should be refactored intoof::
— namespaces should now be basic C++ literacy even for absolute beginners.(of course
ofRandom()
& al will keep working)Beta Was this translation helpful? Give feedback.
All reactions