From 023f6022de1da6123b4ab31e398f092d96edeb74 Mon Sep 17 00:00:00 2001 From: Wojciech Jarosz Date: Wed, 13 Dec 2023 11:10:16 +1100 Subject: [PATCH] adding Faure samplers, fixing memory and UI bugs --- CMakeLists.txt | 4 +- include/sampler/Faure.h | 58 +++++++++++++++++++++++++++++ src/app.cpp | 24 ++++++------ src/sampler/Faure.cpp | 82 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+), 12 deletions(-) create mode 100644 include/sampler/Faure.h create mode 100644 src/sampler/Faure.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ff7832b..461f6a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,7 +179,7 @@ if(sobol_ADDED) set_target_properties(sobol PROPERTIES CXX_STANDARD 17) endif() -CPMAddPackage("gh:Andrew-Helmer/stochastic-generation#7dfaf471277424b1d9940ef15f8c925086c5fec3") +CPMAddPackage("gh:wkjarosz/stochastic-generation#b2dbbb3a24d5c461514fc1f7b61082d30db78c1c") if(stochastic-generation_ADDED) message(STATUS "stochastic-generation library added") # stochastic-generation has no CMake support, so we create our own target @@ -218,6 +218,7 @@ add_library( samplerlib OBJECT STATIC include/sampler/CSVFile.h + include/sampler/Faure.h include/sampler/fwd.h include/sampler/halton_sampler.h include/sampler/Halton.h @@ -240,6 +241,7 @@ add_library( include/sampler/Sobol.h include/sampler/Sudoku.h src/sampler/CSVFile.cpp + src/sampler/Faure.cpp src/sampler/Halton.cpp src/sampler/Jittered.cpp src/sampler/LP.cpp diff --git a/include/sampler/Faure.h b/include/sampler/Faure.h new file mode 100644 index 0000000..049b1c4 --- /dev/null +++ b/include/sampler/Faure.h @@ -0,0 +1,58 @@ +/** \file Faure.h + \author Wojciech Jarosz +*/ +#pragma once + +#include +#include + +/// Stochastic Faure quasi-random number sequence. +/** + A wrapper for Helmer's Owen-scrambled stochastic (0,s) Faure sampler with s=3,5,7,11 +*/ +class Faure : public TSamplerMinMaxDim<1, 11> +{ +public: + Faure(unsigned dimensions = 2, unsigned numSamples = 1); + + void sample(float[], unsigned i) override; + + /// Returns an appropriate grid resolution to help visualize stratification + int coarseGridRes(int samples) const override + { + return int(std::pow(samples, (1.f / s()))); + } + + unsigned dimensions() const override + { + return m_numDimensions; + } + void setDimensions(unsigned n) override; + + bool randomized() const override + { + return m_owen; + } + void setRandomized(bool b = true) override + { + m_owen = b; + regenerate(); + } + + std::string name() const override; + + int numSamples() const override + { + return m_numSamples; + } + int setNumSamples(unsigned n) override; + +protected: + void regenerate(); + unsigned s() const; + + unsigned m_numSamples; + unsigned m_numDimensions; + bool m_owen; + std::vector m_samples; +}; diff --git a/src/app.cpp b/src/app.cpp index 7316793..3f0c969 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -11,6 +11,7 @@ #include "portable-file-dialogs.h" #include +#include #include #include #include @@ -88,6 +89,7 @@ SampleViewer::SampleViewer() m_samplers.emplace_back(new SSobol(m_num_dimensions)); m_samplers.emplace_back(new ZeroTwo(1, m_num_dimensions, false)); m_samplers.emplace_back(new ZeroTwo(1, m_num_dimensions, true)); + m_samplers.emplace_back(new Faure(m_num_dimensions, 1)); m_samplers.emplace_back(new Halton(m_num_dimensions)); m_samplers.emplace_back(new HaltonZaremba(m_num_dimensions)); m_samplers.emplace_back(new Hammersley(m_num_dimensions, 1)); @@ -639,9 +641,6 @@ void SampleViewer::draw_editor() m_gpu_points_dirty = m_cpu_points_dirty = m_gpu_grids_dirty = true; HelloImGui::Log(HelloImGui::LogLevel::Debug, "Regenerated %d points.", m_point_count); } - // now that the user has finished editing, sync the GUI value - if (ImGui::IsItemDeactivatedAfterEdit()) - m_target_point_count = m_point_count; tooltip( "Set the target number of points to generate. For samplers that only support certain numbers of points " @@ -770,8 +769,13 @@ void SampleViewer::draw_editor() if (big_header(ICON_FA_RANDOM " Dimension mapping")) // ========================================================= { - if (ImGui::SliderInt3("XYZ", &m_dimension[0], 0, m_num_dimensions - 1, "%d", ImGuiSliderFlags_AlwaysClamp)) + int3 dims = m_dimension; + if (ImGui::SliderInt3("XYZ", &dims[0], 0, m_num_dimensions - 1, "%d", ImGuiSliderFlags_AlwaysClamp)) + { m_gpu_points_dirty = true; + m_dimension = dims; + } + tooltip("Set which dimensions should be used for the XYZ dimensions of the displayed 3D points."); ImGui::Dummy({0, HelloImGui::EmSize(0.25f)}); @@ -878,7 +882,6 @@ void SampleViewer::process_hotkeys() else if (ImGui::IsKeyPressed(ImGuiKey_D)) { m_num_dimensions = std::clamp(m_num_dimensions + (ImGui::IsKeyDown(ImGuiMod_Shift) ? 1 : -1), 2, 10); - m_dimension = linalg::clamp(m_dimension, int3{0}, int3{m_num_dimensions - 1}); m_gpu_points_dirty = m_cpu_points_dirty = m_gpu_grids_dirty = true; } else if (ImGui::IsKeyPressed(ImGuiKey_R)) @@ -946,7 +949,6 @@ void SampleViewer::update_points(bool regenerate) generator->setRandomized(m_randomize); generator->setDimensions(m_num_dimensions); - m_num_dimensions = generator->dimensions(); int num_pts = generator->setNumSamples(m_target_point_count); m_point_count = num_pts >= 0 ? num_pts : m_target_point_count; @@ -986,7 +988,6 @@ void SampleViewer::update_points(bool regenerate) for (int i = 0; i < m_points.sizeX(); ++i) { float v = m_points(i, std::clamp(m_subset_axis, 0, m_num_dimensions - 1)); - v += 0.5f; if (v >= (m_subset_level + 0.0f) / m_num_subset_levels && v < (m_subset_level + 1.0f) / m_num_subset_levels) { // copy all dimensions (rows) of point i @@ -997,9 +998,9 @@ void SampleViewer::update_points(bool regenerate) } } + int3 dims = linalg::clamp(m_dimension, int3{0}, int3{m_num_dimensions - 1}); for (size_t i = 0; i < m_3d_points.size(); ++i) - m_3d_points[i] = float3{m_subset_points(i, m_dimension.x), m_subset_points(i, m_dimension.y), - m_subset_points(i, m_dimension.z)}; + m_3d_points[i] = float3{m_subset_points(i, dims.x), m_subset_points(i, dims.y), m_subset_points(i, dims.z)}; // // Upload points to the GPU @@ -1355,8 +1356,9 @@ string SampleViewer::export_XYZ_points(const string &format) m_show_coarse_grid, m_show_bbox); } - out += (format == "eps") ? draw_points_eps(mvp, m_dimension, m_subset_points, get_draw_range()) - : draw_points_svg(mvp, m_dimension, m_subset_points, get_draw_range(), radius); + int3 dims = linalg::clamp(m_dimension, int3{0}, int3{m_num_dimensions - 1}); + out += (format == "eps") ? draw_points_eps(mvp, dims, m_subset_points, get_draw_range()) + : draw_points_svg(mvp, dims, m_subset_points, get_draw_range(), radius); out += (format == "eps") ? footer_eps() : footer_svg(); return out; diff --git a/src/sampler/Faure.cpp b/src/sampler/Faure.cpp new file mode 100644 index 0000000..aba64cf --- /dev/null +++ b/src/sampler/Faure.cpp @@ -0,0 +1,82 @@ +/** \file Faure.cpp + \author Wojciech Jarosz +*/ + +#include +#include +#include +#include + +#include "sampling/sfaure.h" + +Faure::Faure(unsigned dimensions, unsigned numSamples) : m_numSamples(numSamples), m_numDimensions(dimensions) +{ + regenerate(); +} + +unsigned Faure::s() const +{ + if (m_numDimensions <= 3) + return 3; + else if (m_numDimensions <= 5) + return 5; + else if (m_numDimensions <= 7) + return 7; + else // if (m_numDimensions <= 11) + return 11; +} + +std::string Faure::name() const +{ + return "Stochastic Faure (0," + std::to_string(s()) + ")"; +} + +void Faure::regenerate() +{ + std::cout << "regenerating " << m_numSamples << std::endl; + // compute all points, and store them + m_samples.clear(); + m_samples.resize(m_numSamples * s()); + + if (m_numSamples > 0) + { + if (m_numDimensions <= 3) + sampling::GetStochasticFaure03Samples(m_numSamples, s(), false, 1, m_owen, &m_samples[0]); + else if (m_numDimensions <= 5) + sampling::GetStochasticFaure05Samples(m_numSamples, s(), false, 1, m_owen, &m_samples[0]); + else if (m_numDimensions <= 7) + sampling::GetStochasticFaure07Samples(m_numSamples, s(), false, 1, m_owen, &m_samples[0]); + else if (m_numDimensions <= 11) + sampling::GetStochasticFaure011Samples(m_numSamples, s(), false, 1, m_owen, &m_samples[0]); + } +} + +void Faure::setDimensions(unsigned n) +{ + auto newD = std::clamp(n, (unsigned)MIN_DIMENSION, (unsigned)MAX_DIMENSION); + if (newD != m_numDimensions) + { + m_numDimensions = newD; + regenerate(); + } +} + +int Faure::setNumSamples(unsigned n) +{ + auto oldN = m_numSamples; + m_numSamples = (n == 0) ? 1 : n; + if (oldN < m_numSamples) + regenerate(); + + return m_numSamples; +} + +void Faure::sample(float r[], unsigned i) +{ + assert(i < m_numSamples); + for (unsigned d = 0; d < dimensions(); ++d) + { + assert(s() * i + d < m_samples.size()); + r[d] = m_samples[s() * i + d]; + } +}