From e3f1d0ec73fddf913cac2fd5f1bda5429868c807 Mon Sep 17 00:00:00 2001 From: alexandre burton Date: Fri, 8 Nov 2024 18:02:13 -0500 Subject: [PATCH 01/10] Random Distributions: typos, docs and tweaks --- .../utils/ofRandomDistributions.h | 69 +++++++++---------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/libs/openFrameworks/utils/ofRandomDistributions.h b/libs/openFrameworks/utils/ofRandomDistributions.h index fca79cb3ed3..31602e1efca 100644 --- a/libs/openFrameworks/utils/ofRandomDistributions.h +++ b/libs/openFrameworks/utils/ofRandomDistributions.h @@ -17,7 +17,6 @@ // https://rovdownloads.com/blog/quick-overview-of-probability-distributions/ namespace of::random { - // Trait to check if T is a random engine -- // poor man's concept; simply based on the // fact that it's callable(), with min() & max() @@ -77,7 +76,7 @@ T make_vector(D d, G & g = of::random::gen()) { /// \returns a new vector or color T template >> T make_vector(G & g = of::random::gen()) { - + if constexpr (std::is_same_v) { return T { D{}(g), D{}(g) @@ -283,7 +282,7 @@ uniform(T min, T max, G & g = of::random::gen()) { // better to cast params as double (otherwise casual ints produce unexpected results) -/// \brief Generates a real normal random number +/// \brief Generates a real normal random number /// /// according to the Normal (or Gaussian) random number distribution /// https://en.cppreference.com/w/cpp/numeric/random/normal_distribution /// (note that this is aliased to 'gaussian' too) @@ -296,7 +295,7 @@ uniform(T min, T max, G & g = of::random::gen()) { template >> std::enable_if_t, T> normal(double mean, double stddev, G & g = of::random::gen()) { - return std::normal_distribution(mean, stddev)(g); // parentheses allow narrowing (¿ good or bad ?) + return T(std::normal_distribution(mean, stddev)(g)); // parentheses allow narrowing (¿ good or bad ?) } /// \brief Generates an integer random number @@ -322,8 +321,8 @@ normal(double mean, double stddev, G & g = of::random::gen()) { /// (note that this is aliased to 'gaussian' too) /// \tparam T the desired vector type /// \tparam G the type of random engine -/// \param min the mean values -/// \param max the stddev values +/// \param mean the mean values +/// \param stddev the stddev values /// \param g the random engine (default: OF internal of::random::gen()) /// \return a T vec of normally-distributed random floats with homogenous parameters template >> @@ -332,15 +331,15 @@ normal(float mean, float stddev, G & g = of::random::gen()) { return make_vector>(mean, stddev, g); } -/// \brief Generates a glm::vec of random numbers +/// \brief Generates a glm::vec of random numbers /// according to the Normal (or Gaussian) random number distribution /// https://en.cppreference.com/w/cpp/numeric/random/normal_distribution /// with specialized parameters (different params for members of the vector) /// where T, mean and stdev must be of the same type /// \tparam T the desired vector type /// \tparam G the type of random engine -/// \param min the mean values -/// \param max the stddev values +/// \param mean the mean values +/// \param stddev the stddev values /// \param g the random engine (default: OF internal of::random::gen()) /// \return a T vec of normally-distributed random floats with distinct parameters template >> @@ -451,8 +450,8 @@ gamma(T alpha, T beta, G & g = of::random::gen()) { /// \return a gamma random value ot type T template >> std::enable_if_t, T> -poisson(double mean, G & gen = of::random::gen()) { - return std::poisson_distribution{mean}(gen); +poisson(double mean, G & g = of::random::gen()) { + return std::poisson_distribution{mean}(g); } /// \brief Generates a vector of random non-negative integer values according to poisson distribution @@ -501,7 +500,6 @@ exponential(T lambda, G & g = of::random::gen()) { /// \brief Generates a vector of random non-negative values according to exponential distribution /// https://en.cppreference.com/w/cpp/numeric/random/exponential_distribution -/// \param mean the lambda /// \tparam T the desired vector output type /// \tparam G the type of random engine /// \param lambda the lambda values @@ -516,7 +514,6 @@ exponential(float lambda, G & g = of::random::gen()) { /// \brief Generates a vector of random non-negative values according to exponential distribution /// https://en.cppreference.com/w/cpp/numeric/random/exponential_distribution /// with specialized parameters (different params for members of the vector) -/// \param mean the vector of lambdas (double) /// \tparam T the desired vector output type /// \tparam G the type of random engine /// \param lambda the lambda values @@ -582,12 +579,12 @@ chi_squared(T freedom, G & g = of::random::gen()) { /// \return a binomial random value ot type T template >> std::enable_if_t, T> -binomial(int p, double t, G & gen = of::random::gen()) { +binomial(int p, double t, G & g = of::random::gen()) { if (t >= 1) { std::cout << "of::random::binomial(): t must be < 1.0\n"; return 0; } else { - return std::binomial_distribution{p, t}(gen); + return std::binomial_distribution{p, t}(g); } } @@ -618,10 +615,10 @@ binomial(int p, double t, G & g = of::random::gen()) { /// \return a glm::vec2 of binomial random values (int cast to float) template >> std::enable_if_t, T> -binomial(T p, T t, G & gen = of::random::gen()) { +binomial(T p, T t, G & g = of::random::gen()) { return { - std::binomial_distribution { int(p.x), t.x }(gen), - std::binomial_distribution { int(p.y), t.y }(gen) + std::binomial_distribution { int(p.x), t.x }(g), + std::binomial_distribution { int(p.y), t.y }(g) }; } @@ -638,11 +635,11 @@ binomial(T p, T t, G & gen = of::random::gen()) { /// \return a glm::vec3 of binomial random values (int cast to float) template >> std::enable_if_t, T> -binomial(T p, T t, G & gen = of::random::gen()) { +binomial(T p, T t, G & g = of::random::gen()) { return { - std::binomial_distribution { int(p.x), t.x }(gen), - std::binomial_distribution { int(p.y), t.y }(gen), - std::binomial_distribution { int(p.z), t.z }(gen) + std::binomial_distribution { int(p.x), t.x }(g), + std::binomial_distribution { int(p.y), t.y }(g), + std::binomial_distribution { int(p.z), t.z }(g) }; } @@ -659,12 +656,12 @@ binomial(T p, T t, G & gen = of::random::gen()) { /// \return a glm::vec4 of binomial random values (int cast to float)template template >> std::enable_if_t, T> -binomial(T p, T t, G & gen = of::random::gen()) { +binomial(T p, T t, G & g = of::random::gen()) { return { - std::binomial_distribution { int(p.x), t.x }(gen), - std::binomial_distribution { int(p.y), t.y }(gen), - std::binomial_distribution { int(p.z), t.z }(gen), - std::binomial_distribution { int(p.w), t.w }(gen) + std::binomial_distribution { int(p.x), t.x }(g), + std::binomial_distribution { int(p.y), t.y }(g), + std::binomial_distribution { int(p.z), t.z }(g), + std::binomial_distribution { int(p.w), t.w }(g) }; } @@ -773,7 +770,7 @@ bound_normal(float min, float max, float focus = 4.0f, G & g = of::random::gen() } else { T v; do { - v = of::random::normal((max + min) / 2.0f, (max - min) / (2 * focus), g); + v = of::random::normal((max + min) / 2.0f, (max - min) / (2 * focus), g); } while (v < min || v > max); return v; } @@ -834,7 +831,7 @@ bound_normal(T min, T max, std::optional focus = std::nullopt, G & g = of::ra template >> std::enable_if_t or std::is_same_v or std::is_same_v, T> bound_normal(float min, float max, float focus = 4.0f, G & g = of::random::gen()) { - return bound_normal(T{min}, T{max}, T{focus}); + return bound_normal(T{min}, T{max}, T{focus}, g); } } // end namespace of::random @@ -956,9 +953,9 @@ T ofRandomGamma(Args &&... args) { return of::random::gamma(std::forward T ofRandomGamma(T a, T b) { return of::random::gamma(a, b); } @@ -980,8 +977,8 @@ T ofRandomBinomial(Args &&... args) { return of::random::binomial(std::forwar /// \brief Generates a random non-negative integer value according to the binomial distribution /// https://en.cppreference.com/w/cpp/numeric/random/binomial_distribution /// \tparam T the desired output type; defaults to int -/// \param p the first parameter -/// \param t the second parameter +/// \param mean the mean +/// \param stddev the deviation /// \return a binomial random value ot type T template T ofRandomBinomial(T mean, T stddev) { return of::random::binomial(mean, stddev); } @@ -1003,8 +1000,8 @@ T ofRandomBoundNormal(float min, float max, float focus = 4.0f) { template std::enable_if_t or std::is_same_v or std::is_same_v, T> -ofRandomBoundNormal(T min, T max, std::optional focus = std::nullopt) { - return of::random::bound_normal(min, max, focus.value_or(T{4.0})); +ofRandomBoundNormal(T min, T max, T focus = T{4.0}) { + return of::random::bound_normal(min, max, focus); } } // end anonymous namespace From 13f624b301334c3ae66af71629fef3116ae05d06 Mon Sep 17 00:00:00 2001 From: alexandre burton Date: Fri, 8 Nov 2024 18:50:32 -0500 Subject: [PATCH 02/10] RandomExplorer: option to disable graphs --- apps/devApps/RandomExplorer/src/Dist.hpp | 122 +++++++++++----------- apps/devApps/RandomExplorer/src/ofApp.cpp | 12 ++- apps/devApps/RandomExplorer/src/ofApp.h | 7 +- 3 files changed, 73 insertions(+), 68 deletions(-) diff --git a/apps/devApps/RandomExplorer/src/Dist.hpp b/apps/devApps/RandomExplorer/src/Dist.hpp index b68a5574c8f..cc33b828378 100644 --- a/apps/devApps/RandomExplorer/src/Dist.hpp +++ b/apps/devApps/RandomExplorer/src/Dist.hpp @@ -21,7 +21,7 @@ struct Dist { virtual auto gen() -> void = 0; virtual auto clear() -> void = 0; virtual auto compile() -> void = 0; - virtual auto draw(float x, float y, float w, float h) -> void = 0; + virtual auto draw(float x, float y, float w, float h, bool graph = true) -> void = 0; virtual ~Dist() = default; Dist() {}; @@ -94,7 +94,7 @@ struct ConcreteDist : public Dist { } } - auto draw(float x, float y, float w, float h) -> void override { + auto draw(float x, float y, float w, float h, bool graph = true) -> void override { ofPushStyle(); ofPushMatrix(); { @@ -108,66 +108,68 @@ struct ConcreteDist : public Dist { ofSetColor(255, 255, 255, 255); ofDrawBitmapStringHighlight(parameters_.getName() + " " + ofToString(cost_ * 1000, 2, 5) + "ms", w + 5, 12); - if constexpr (std::is_arithmetic_v) { - - auto p = 0.0f; - double incr = w / bins_.size(); - auto fact = h / max_; - if (discrete_) { - - // line bars for discrete - ofTranslate(incr / 2, 0); - for (auto y : bins_) { - ofDrawLine(0, h, 0, h - float(y) * fact); - if (y == 0) { - ofNoFill(); - ofDrawCircle(0, h - float(y) * fact, 2.5); - ofFill(); - } else { - ofDrawCircle(0, h - float(y) * fact, 3); + if (graph) { + if constexpr (std::is_arithmetic_v) { + + auto p = 0.0f; + double incr = w / bins_.size(); + auto fact = h / max_; + if (discrete_) { + + // line bars for discrete + ofTranslate(incr / 2, 0); + for (auto ypos : bins_) { + ofDrawLine(0, h, 0, h - float(ypos) * fact); + if (y == 0) { + ofNoFill(); + ofDrawCircle(0, h - float(ypos) * fact, 2.5); + ofFill(); + } else { + ofDrawCircle(0, h - float(ypos) * fact, 3); + } + ofTranslate(int(incr), 0); } - ofTranslate(int(incr), 0); + } else { + + // integral for reals + ofPolyline line; + line.addVertex(0, h - bins_[0] * fact); + for (auto ypos : bins_) + line.lineTo(p += incr, h - float(ypos) * fact); + line.draw(); } + + } else if constexpr (std::is_same_v) { + + ofSetColor(255, 255, 255, 96); + for (const auto & d : data_) { + if (d.x > w || d.y > h) underflow_++; + if (d.x < 0 || d.y < 0) overflow_++; + ofDrawCircle(d, 0.5); + } + + } else if constexpr (std::is_same_v) { + + ofSetColor(255, 255, 255, 32); + of3dPrimitive prim; + for (const auto & d : data_) { + if (d.x > w || d.y > h) underflow_++; + if (d.x < 0 || d.y < 0) overflow_++; + } + prim.getMesh().getVertices() = data_; + prim.getMesh().setMode(OF_PRIMITIVE_POINTS); + prim.rotateDeg(70, { 0.2, 0.3, 0.5 }); // just some perspective + + ofPushMatrix(); + { + ofTranslate(w * 0.2, h * 0.2); + prim.drawWireframe(); + prim.drawAxes(w * 0.5); + } + ofPopMatrix(); } else { - - // integral for reals - ofPolyline line; - line.addVertex(0, h - bins_[0] * fact); - for (auto y : bins_) - line.lineTo(p += incr, h - float(y) * fact); - line.draw(); - } - - } else if constexpr (std::is_same_v) { - - ofSetColor(255, 255, 255, 96); - for (const auto & d : data_) { - if (d.x > w || d.y > h) underflow_++; - if (d.x < 0 || d.y < 0) overflow_++; - ofDrawCircle(d, 0.5); - } - - } else if constexpr (std::is_same_v) { - - ofSetColor(255, 255, 255, 32); - of3dPrimitive prim; - for (const auto & d : data_) { - if (d.x > w || d.y > h) underflow_++; - if (d.x < 0 || d.y < 0) overflow_++; - } - prim.getMesh().getVertices() = data_; - prim.getMesh().setMode(OF_PRIMITIVE_POINTS); - prim.rotateDeg(70, { 0.2, 0.3, 0.5 }); // just some perspective - - ofPushMatrix(); - { - ofTranslate(w * 0.2, h * 0.2); - prim.drawWireframe(); - prim.drawAxes(w * 0.5); + ofDrawBitmapString("unsupported visualisation", 10, 10); } - ofPopMatrix(); - } else { - ofDrawBitmapString("unsupported visualisation", 10, 10); } ofSetColor(ofColor::deepPink); if (underflow_) ofDrawBitmapString("undershoot: " + ofToString(underflow_), w + 5, 70); @@ -185,7 +187,7 @@ struct DistGroup { DistGroup(std::vector> dists) : dists_(dists) { } - auto draw(std::string label, int square, int gap) { + auto draw(std::string label, int square, int gap, bool graph = true) { panel_.draw(); ofPushMatrix(); { @@ -193,7 +195,7 @@ struct DistGroup { ofDrawBitmapString(label, 0, -10); ofTranslate(panel_.getWidth() + 20, 0); for (const auto & dist : dists_) { - dist->draw(0, 0, square, square); + dist->draw(0, 0, square, square, graph); ofTranslate(0, square + gap); } } diff --git a/apps/devApps/RandomExplorer/src/ofApp.cpp b/apps/devApps/RandomExplorer/src/ofApp.cpp index ab7f9bb7bfa..be89dab5875 100644 --- a/apps/devApps/RandomExplorer/src/ofApp.cpp +++ b/apps/devApps/RandomExplorer/src/ofApp.cpp @@ -12,6 +12,7 @@ void ofApp::setup() { panel_.add(size_); panel_.add(seed_); panel_.add(reinit_); + panel_.add(draw_graphs_); // panel_.add(ok_color_); // panel_.add(saturation_); @@ -79,12 +80,13 @@ void ofApp::draw() { } else { ofDrawBitmapStringHighlight("engine is non-deterministic", x, 20, ofColor::black, ofColor::white); } - + panel_.draw(); - dists_["core"]->draw("C++ fundamental distributions", square_, gap_); - dists_["special"]->draw("more specialized distributions", square_, gap_); - dists_["of"]->draw("OF/art-centric wrappers/utils", square_, gap_); - dists_["old"]->draw("Previous implementation (reference)", square_, gap_); + dists_["core"]->draw("C++ fundamental distributions", square_, gap_, draw_graphs_); + dists_["special"]->draw("more specialized distributions", square_, gap_, draw_graphs_); + dists_["of"]->draw("OF/art-centric wrappers/utils", square_, gap_, draw_graphs_); + dists_["old"]->draw("Previous implementation (reference)", square_, gap_, draw_graphs_); + ofDrawBitmapStringHighlight("Performance: on M1/M2 processors, the old srand is faster\nthan uniform in Debug, but slower in Release...\nPlease make sure to evaluate performance in Release!", dists_["old"]->panel_.getPosition() + glm::vec2(0, square_ + gap_), ofColor(50, 0, 0), ofColor(ofColor::white)); diff --git a/apps/devApps/RandomExplorer/src/ofApp.h b/apps/devApps/RandomExplorer/src/ofApp.h index 4dd4f353dee..fa5e6a1f3cc 100644 --- a/apps/devApps/RandomExplorer/src/ofApp.h +++ b/apps/devApps/RandomExplorer/src/ofApp.h @@ -20,10 +20,11 @@ class ofApp : public ofBaseApp { ofParameter size_ { "size (cube root)", 25, 1, 50 }; ofParameter seed_ { "seed", 0, 0, 1000 }; ofParameter reinit_ { "re-init engine" }; + ofParameter draw_graphs_ { "draw graphs", true }; ofParameter ok_color_ { "ok_color", true }; - ofParameter saturation_ { "saturation", 0.95 }; - ofParameter value_ { "value", .45 }; - ofParameter offset_ { "offset", 0 }; + ofParameter saturation_ { "saturation", 0.95f }; + ofParameter value_ { "value", .45f }; + ofParameter offset_ { "offset", 0.0f }; size_t col_w_ = 640; size_t square_ = 110; From e0394d82ceb7e24ccea21debb535982ec7b89336 Mon Sep 17 00:00:00 2001 From: alexandre burton Date: Fri, 8 Nov 2024 18:51:04 -0500 Subject: [PATCH 03/10] ofSIngleton: tweaks/updates --- libs/openFrameworks/utils/ofSingleton.h | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/libs/openFrameworks/utils/ofSingleton.h b/libs/openFrameworks/utils/ofSingleton.h index 53566ea5a9f..658c0b8c5e3 100644 --- a/libs/openFrameworks/utils/ofSingleton.h +++ b/libs/openFrameworks/utils/ofSingleton.h @@ -4,6 +4,15 @@ // atomic C++17 DCLP CRTP singleton adapted by burton@artificiel.org from // https://github.com/jimmy-park/singleton/blob/main/include/singleton_dclp.hpp (as of df5e4a2) +// the atomic CRTP Singleton is explicitely constructed (not simply as a side effect +// of being the first call to the instance) which makes it easier to audit and +// configure (arguments are explictely passed as required)) +// +// using the Singleton make the code thread-safe and better contained than +// arbitrarily allocated pointers in the global namespace +// +// this implements abstract class injection to prevent construction at compile time + #include #include #include @@ -18,9 +27,9 @@ class Singleton { /// \brief constructs the instance template static void construct(Args &&... args) { - struct Dummy : public Derived { + struct Dummy final : Derived { using Derived::Derived; - void prohibit_construct_from_derived() const override { } + void prohibit_construct_from_derived() const noexcept override { } }; using Instance = Dummy; @@ -49,7 +58,6 @@ class Singleton { if (!the_instance) { std::shared_lock lock { mutex_ }; the_instance = instance_.load(std::memory_order_relaxed); - assert(the_instance); } return the_instance; } @@ -57,13 +65,13 @@ class Singleton { protected: Singleton() = default; Singleton(const Singleton &) = delete; - Singleton(Singleton &&) noexcept = delete; + Singleton(Singleton &&) = delete; Singleton & operator=(const Singleton &) = delete; - Singleton & operator=(Singleton &&) noexcept = delete; + Singleton & operator=(Singleton &&) = delete; virtual ~Singleton() = default; private: - virtual void prohibit_construct_from_derived() const = 0; + virtual void prohibit_construct_from_derived() const noexcept = 0; inline static std::atomic instance_ { nullptr }; inline static std::shared_mutex mutex_; }; From b8423dde736d9e9dba7cfb6a136416329602090d Mon Sep 17 00:00:00 2001 From: alexandre burton Date: Fri, 8 Nov 2024 18:51:31 -0500 Subject: [PATCH 04/10] ofRandomExample: more cases --- examples/math/randomExample/src/ofApp.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/math/randomExample/src/ofApp.cpp b/examples/math/randomExample/src/ofApp.cpp index deaa60bb14a..60e99dceb8e 100644 --- a/examples/math/randomExample/src/ofApp.cpp +++ b/examples/math/randomExample/src/ofApp.cpp @@ -302,6 +302,9 @@ void ofApp::perform() { ofLogNotice("ofRandomBoundNormal(10, 20)") << ofRandomBoundNormal(10, 20); ofLogNotice("ofRandomBoundNormal({100 ,200, 300, 400},{110, 210, 310, 410})") << ofRandomBoundNormal({100 ,200, 300, 400},{110, 210, 310, 410}); + ofLogNotice("ofRandomBoundNormal(10, 20, 1)") << ofRandomBoundNormal(10, 20, 1); + ofLogNotice("ofRandomBoundNormal(10, 20, 1)") << ofRandomBoundNormal({10, 10}, {20,20}, {1,1}); + ofLogNotice("======= alternate engines test ====="); std::random_device rd {}; std::seed_seq seq { rd(), rd(), rd(), rd() }; From 28fa6a777bcb06c3b25b7be447df709c17c230f5 Mon Sep 17 00:00:00 2001 From: alexandre burton Date: Sun, 24 Nov 2024 23:08:24 -0500 Subject: [PATCH 05/10] thread-safety for random urn --- libs/openFrameworks/utils/ofUtils.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/libs/openFrameworks/utils/ofUtils.h b/libs/openFrameworks/utils/ofUtils.h index 7d21af90bc1..3c5cdcc5ef6 100644 --- a/libs/openFrameworks/utils/ofUtils.h +++ b/libs/openFrameworks/utils/ofUtils.h @@ -273,7 +273,8 @@ class urn { Container values_; typename Container::const_iterator phase_; - + std::mutex mutex_; + auto prepare() { if (valid()) { if (auto_configure_edge_repeat_) { @@ -313,6 +314,7 @@ class urn { /// \param other the other Urn /// \return a new Urn auto & operator=(urn && other) noexcept { + std::scoped_lock lock(mutex_); std::swap(values_, other.values_); prepare(); return *this; @@ -323,6 +325,7 @@ class urn { /// \return a new Urn auto & operator=(urn & other) { if (this == &other) return *this; + std::scoped_lock lock(mutex_); values_ = other.get_values(); prepare(); return *this; @@ -332,6 +335,7 @@ class urn { /// \param Container the container of values /// \return void auto & operator=(Container & values) { + std::scoped_lock lock(mutex_); values_ = values; prepare(); return *this; @@ -351,6 +355,7 @@ class urn { /// \return void template auto operator=(Args &&... args) { + std::scoped_lock lock(mutex_); set(std::forward(args)...); } @@ -358,6 +363,7 @@ class urn { /// \param Container the container of values /// \return void auto set(Container & values) { + std::scoped_lock lock(mutex_); values_ = values; prepare(); } @@ -366,6 +372,7 @@ class urn { /// \param urn the other urn /// \return void auto set(urn & urn) { + std::scoped_lock lock(mutex_); values_ = urn.get_values(); prepare(); } @@ -375,6 +382,7 @@ class urn { /// \return void template auto set(Args &&... args) { + std::scoped_lock lock(mutex_); values_.clear(); values_.reserve(sizeof...(Args)); std::cout << ("preparing for") << sizeof...(Args) << std::endl; @@ -406,6 +414,7 @@ class urn { /// \return void auto refill() { if (valid()) { + // cannot be scoped_lock should be behind another private call auto last = *values_.end(); ofShuffle(values_); if (!allow_edge_repeat_) { @@ -424,6 +433,7 @@ class urn { /// \return a T from the urn (could be garbage if Urn is invalid) auto pull() { if (valid()) { + std::scoped_lock lock(mutex_); if (depleted()) refill(); return *phase_++; } else { @@ -437,6 +447,7 @@ class urn { /// \return an optional from the urn, nullopt if empty or invalid auto pull_or_empty() { if (valid()) { + std::scoped_lock lock(mutex_); if (depleted()) return std::optional {}; return std::optional(*phase_++); } else { From b6c90c9c3857cbd9abc5dd6961c743f427248dd9 Mon Sep 17 00:00:00 2001 From: alexandre burton Date: Sun, 24 Nov 2024 23:26:28 -0500 Subject: [PATCH 06/10] Urn: explicit edge rules --- libs/openFrameworks/utils/ofUtils.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libs/openFrameworks/utils/ofUtils.h b/libs/openFrameworks/utils/ofUtils.h index 3c5cdcc5ef6..0c91711ba4f 100644 --- a/libs/openFrameworks/utils/ofUtils.h +++ b/libs/openFrameworks/utils/ofUtils.h @@ -278,8 +278,12 @@ class urn { auto prepare() { if (valid()) { if (auto_configure_edge_repeat_) { - std::sort(values_.begin(), values_.end()); // perhaps costly for large sets - allow_edge_repeat_ = std::adjacent_find(values_.begin(), values_.end()) != values_.end(); + if (values_.size()>1) { + std::sort(values_.begin(), values_.end()); // perhaps costly for large sets + allow_edge_repeat_ = std::adjacent_find(values_.begin(), values_.end()) != values_.end(); + } else { + allow_edge_repeat_ = true; // set of 1 cannot not repeat + } } return refill(); } else { From 1b056c81e5fa44e1b0a884fbc044b65ae2523e64 Mon Sep 17 00:00:00 2001 From: alexandre burton Date: Sun, 24 Nov 2024 23:27:10 -0500 Subject: [PATCH 07/10] Urn: better iterator init --- libs/openFrameworks/utils/ofUtils.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/openFrameworks/utils/ofUtils.h b/libs/openFrameworks/utils/ofUtils.h index 0c91711ba4f..52f03836ee2 100644 --- a/libs/openFrameworks/utils/ofUtils.h +++ b/libs/openFrameworks/utils/ofUtils.h @@ -288,6 +288,7 @@ class urn { return refill(); } else { // ofLogError("ofUrn::prepare()") << "called on unitialized Urn"; + phase_ = values_.cend(); return false; } } @@ -300,7 +301,7 @@ class urn { bool auto_configure_edge_repeat_ { true }; /// \brief Construct an unitialized urn - urn() = default; + urn(): phase_(values_.cend()) {}; /// \brief Copy-Construct an urn with contents of other urn /// \param other the other Urn @@ -429,6 +430,7 @@ class urn { return true; } else { // ofLogError("of::urn::refill()") << "called on unitialized Urn"; + phase_ = values_.cend(); // Mark as depleted return false; } } From 2cce9bf81663eefeb1ec064bacb7408c6f243ac6 Mon Sep 17 00:00:00 2001 From: alexandre burton Date: Sun, 24 Nov 2024 23:27:24 -0500 Subject: [PATCH 08/10] Urn: correct end matching --- libs/openFrameworks/utils/ofUtils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/openFrameworks/utils/ofUtils.h b/libs/openFrameworks/utils/ofUtils.h index 52f03836ee2..682e831d9cc 100644 --- a/libs/openFrameworks/utils/ofUtils.h +++ b/libs/openFrameworks/utils/ofUtils.h @@ -420,7 +420,7 @@ class urn { auto refill() { if (valid()) { // cannot be scoped_lock should be behind another private call - auto last = *values_.end(); + auto last = *std::prev(values_.end()); ofShuffle(values_); if (!allow_edge_repeat_) { while (last == *values_.begin()) From e16570adf43a7def558780d4c099410b2234c90e Mon Sep 17 00:00:00 2001 From: alexandre burton Date: Sun, 24 Nov 2024 23:27:53 -0500 Subject: [PATCH 09/10] Urn: non-leaky move constructor --- libs/openFrameworks/utils/ofUtils.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/openFrameworks/utils/ofUtils.h b/libs/openFrameworks/utils/ofUtils.h index 682e831d9cc..f875df7eb79 100644 --- a/libs/openFrameworks/utils/ofUtils.h +++ b/libs/openFrameworks/utils/ofUtils.h @@ -310,8 +310,10 @@ class urn { prepare(); } - urn(urn && other) noexcept - : urn(std::exchange(other.values_, nullptr)) { + urn(urn && other) noexcept { + std::scoped_lock lock(other.mutex_); + values_ = std::move(other.values_); + other.values_.clear(); prepare(); } From 750d44119777369171afab29337320b90af9edd5 Mon Sep 17 00:00:00 2001 From: alexandre burton Date: Wed, 26 Mar 2025 16:43:57 -0400 Subject: [PATCH 10/10] feat: removal of Urn --- libs/openFrameworks/utils/ofUtils.h | 233 ---------------------------- 1 file changed, 233 deletions(-) diff --git a/libs/openFrameworks/utils/ofUtils.h b/libs/openFrameworks/utils/ofUtils.h index f875df7eb79..bc877f6f08a 100644 --- a/libs/openFrameworks/utils/ofUtils.h +++ b/libs/openFrameworks/utils/ofUtils.h @@ -247,239 +247,6 @@ void ofShuffle(Args &&... args) { of::shuffle(std::forward(args)...); } -namespace of { - -/// \class ofUrn -/// -/// A vector-backed class that can be progressively "emptied" by randomly pulling values out of it. -/// Useful to get non-repeated patterns from a set, aka Sampling Without Replacement. -/// When the Urn is depleted, it is automatically refilled (with ways to know about such events; see below) -/// -/// By default: -/// if the Urn contains unique values, repetitions are not allowed across phases; -/// if the Urn contains duplicates, repetitions are allowed. -/// -/// this arbitrary behavioral choice is "practical" and can controlled with auto_configure_edge_repeat_ -/// -/// note that objects deposited herein get copied around when shuffling to refill; for large objects -/// TODO: an optimisation would be to shadow the objects with a vector of iterators and shuffle the iterators instead -/// (or maybe it's the user's responsibility to pass a containe of references/pointers? -/// in any case it's not clear where the threshold (qty x size) for such an optimisation lies) -/// -/// \tparam T the type of contained values -/// \tparam Container the type of the underlying Container (only tested with vector; in place for future expansion) -template > -class urn { - - Container values_; - typename Container::const_iterator phase_; - std::mutex mutex_; - - auto prepare() { - if (valid()) { - if (auto_configure_edge_repeat_) { - if (values_.size()>1) { - std::sort(values_.begin(), values_.end()); // perhaps costly for large sets - allow_edge_repeat_ = std::adjacent_find(values_.begin(), values_.end()) != values_.end(); - } else { - allow_edge_repeat_ = true; // set of 1 cannot not repeat - } - } - return refill(); - } else { - // ofLogError("ofUrn::prepare()") << "called on unitialized Urn"; - phase_ = values_.cend(); - return false; - } - } - -public: - /// \brief if true, repetitions are not allowed for vectors of unique values - bool allow_edge_repeat_ { false }; - - /// \brief if true, allow edge repeats if the vector contains duplicates - bool auto_configure_edge_repeat_ { true }; - - /// \brief Construct an unitialized urn - urn(): phase_(values_.cend()) {}; - - /// \brief Copy-Construct an urn with contents of other urn - /// \param other the other Urn - urn(urn & other) { - values_ = other.get_values(); - prepare(); - } - - urn(urn && other) noexcept { - std::scoped_lock lock(other.mutex_); - values_ = std::move(other.values_); - other.values_.clear(); - prepare(); - } - - /// \brief move-assign an urn with another urn - /// \param other the other Urn - /// \return a new Urn - auto & operator=(urn && other) noexcept { - std::scoped_lock lock(mutex_); - std::swap(values_, other.values_); - prepare(); - return *this; - } - - /// \brief Assign-Construct an urn with contents of other urn - /// \param other the other Urn - /// \return a new Urn - auto & operator=(urn & other) { - if (this == &other) return *this; - std::scoped_lock lock(mutex_); - values_ = other.get_values(); - prepare(); - return *this; - } - - /// \brief Assigns with values from compatibe container and resets the phase - /// \param Container the container of values - /// \return void - auto & operator=(Container & values) { - std::scoped_lock lock(mutex_); - values_ = values; - prepare(); - return *this; - } - - /// \brief Construct an urn initialized with contents - /// \param Args the values - template - urn(Args &&... args) { - set(std::forward(args)...); - } - - ~urn() = default; - - /// \brief Sets values by assignement and resets the phase - /// \param Args the values - /// \return void - template - auto operator=(Args &&... args) { - std::scoped_lock lock(mutex_); - set(std::forward(args)...); - } - - /// \brief Assigns with values from another container and resets the phase - /// \param Container the container of values - /// \return void - auto set(Container & values) { - std::scoped_lock lock(mutex_); - values_ = values; - prepare(); - } - - /// \brief Sets the values of the container from another Urn and resets the phase - /// \param urn the other urn - /// \return void - auto set(urn & urn) { - std::scoped_lock lock(mutex_); - values_ = urn.get_values(); - prepare(); - } - - /// \brief Sets the values of the container and resets the phase - /// \param Args the values - /// \return void - template - auto set(Args &&... args) { - std::scoped_lock lock(mutex_); - values_.clear(); - values_.reserve(sizeof...(Args)); - std::cout << ("preparing for") << sizeof...(Args) << std::endl; - (values_.emplace_back(std::forward(args)), ...); - prepare(); - } - - /// \brief Check if urn contains potential values - /// (not using the name empty to distinguish from depleted()) - /// \return True if not empty - auto valid() const { return !values_.empty(); } - - /// \brief Check if urn is depleted - /// (not using the name empty to distinguish from valid()) - /// \return true if the phase is at the end of the container - auto depleted() const { return phase_ == values_.cend(); } - - /// \brief Get the total number of elements given a full urn - /// (not using the name size to distinguish from remain()) - /// \return number of elements - auto capacity() const { return values_.size(); } - - /// \brief Get the remaining number of elements in phase - /// (not using the name size to distinguish from capacity()) - /// \return number of remaining elements in phase - auto remain() const { return std::distance(phase_, values_.cend()); } - - /// \brief Refills the urn with the original elements and resets the phase - /// \return void - auto refill() { - if (valid()) { - // cannot be scoped_lock should be behind another private call - auto last = *std::prev(values_.end()); - ofShuffle(values_); - if (!allow_edge_repeat_) { - while (last == *values_.begin()) - ofShuffle(values_); - } - phase_ = values_.begin(); - return true; - } else { - // ofLogError("of::urn::refill()") << "called on unitialized Urn"; - phase_ = values_.cend(); // Mark as depleted - return false; - } - } - - /// \brief Get a random element from the urn, refilling as needed - /// \return a T from the urn (could be garbage if Urn is invalid) - auto pull() { - if (valid()) { - std::scoped_lock lock(mutex_); - if (depleted()) refill(); - return *phase_++; - } else { - // ofLogError("of::urn::pull()") << "called on unitialized Urn -- returning unitialized T"; - T v; - return v; - } - } - - /// \brief Get a random element from the urn - /// \return an optional from the urn, nullopt if empty or invalid - auto pull_or_empty() { - if (valid()) { - std::scoped_lock lock(mutex_); - if (depleted()) return std::optional {}; - return std::optional(*phase_++); - } else { - // ofLogError("of::urn::pull_or_empty()") << "called on unitialized Urn -- returning nullopt"; - return std::optional {}; - } - } - - /// \brief Access the internals (advanced introspection) - /// \return a reference to the underlying stuff - auto & get_values() const { return values_; } - auto & get_phase() const { return phase_; } -}; - -/// \brief CTAD (namely to help deduce references across the perfect forwarding) -template -urn(T) -> urn; -} - -/// \brief alias to ofUrn -/// note that not all deductions carry across the aliasing, so ofUrn is slightly disavantaged vs of::urn -template > -using ofUrn = typename of::urn; - /// \section Vectors /// \brief Randomly reorder the values in a vector. /// \tparam T the type contained by the vector.