diff --git a/benchmark/histogram_filling.cpp b/benchmark/histogram_filling.cpp index dbe8bf1b..470aafcf 100644 --- a/benchmark/histogram_filling.cpp +++ b/benchmark/histogram_filling.cpp @@ -31,7 +31,33 @@ using DStore = boost::histogram::adaptive_storage<>; #endif using namespace boost::histogram; +namespace op = boost::histogram::axis::option; using reg = axis::regular<>; +using reg_closed = + axis::regular; + +class reg_closed_unsafe { +public: + reg_closed_unsafe(axis::index_type n, double start, double stop) + : min_{start}, delta_{stop - start}, size_{n} {} + + axis::index_type index(double x) const noexcept { + // Runs in hot loop, please measure impact of changes + auto z = (x - min_) / delta_; + // assume that z < 0 and z > 1 never happens, promised by inclusive() + if (z == 1) return size() - 1; + return static_cast(z * size()); + } + + axis::index_type size() const noexcept { return size_; } + + static constexpr bool inclusive() { return true; } + +private: + double min_; + double delta_; + axis::index_type size_; +}; template static void fill_1d(benchmark::State& state) { @@ -41,6 +67,22 @@ static void fill_1d(benchmark::State& state) { state.SetItemsProcessed(state.iterations()); } +template +static void fill_1d_closed(benchmark::State& state) { + auto h = make_s(Tag(), Storage(), reg_closed(100, 0, 1)); + auto gen = generator(); + for (auto _ : state) benchmark::DoNotOptimize(h(gen())); + state.SetItemsProcessed(state.iterations()); +} + +template +static void fill_1d_closed_unsafe(benchmark::State& state) { + auto h = make_s(Tag(), Storage(), reg_closed_unsafe(100, 0, 1)); + auto gen = generator(); + for (auto _ : state) benchmark::DoNotOptimize(h(gen())); + state.SetItemsProcessed(state.iterations()); +} + template static void fill_n_1d(benchmark::State& state) { auto h = make_s(Tag(), Storage(), reg(100, 0, 1)); @@ -49,6 +91,22 @@ static void fill_n_1d(benchmark::State& state) { state.SetItemsProcessed(state.iterations() * gen.size()); } +template +static void fill_n_1d_closed(benchmark::State& state) { + auto h = make_s(Tag(), Storage(), reg_closed(100, 0, 1)); + auto gen = generator(); + for (auto _ : state) h.fill(gen); + state.SetItemsProcessed(state.iterations() * gen.size()); +} + +template +static void fill_n_1d_closed_unsafe(benchmark::State& state) { + auto h = make_s(Tag(), Storage(), reg_closed_unsafe(100, 0, 1)); + auto gen = generator(); + for (auto _ : state) h.fill(gen); + state.SetItemsProcessed(state.iterations() * gen.size()); +} + template static void fill_2d(benchmark::State& state) { auto h = make_s(Tag(), Storage(), reg(100, 0, 1), reg(100, 0, 1)); @@ -111,6 +169,8 @@ BENCHMARK_TEMPLATE(fill_1d, uniform, dynamic_tag); // BENCHMARK_TEMPLATE(fill_1d, uniform, dynamic_tag, DStore); BENCHMARK_TEMPLATE(fill_1d, normal, dynamic_tag); // BENCHMARK_TEMPLATE(fill_1d, normal, dynamic_tag, DStore); +BENCHMARK_TEMPLATE(fill_1d_closed, uniform, static_tag); +BENCHMARK_TEMPLATE(fill_1d_closed_unsafe, uniform, static_tag); BENCHMARK_TEMPLATE(fill_n_1d, uniform, static_tag); // BENCHMARK_TEMPLATE(fill_n_1d, uniform, static_tag, DStore); @@ -120,6 +180,8 @@ BENCHMARK_TEMPLATE(fill_n_1d, uniform, dynamic_tag); // BENCHMARK_TEMPLATE(fill_n_1d, uniform, dynamic_tag, DStore); BENCHMARK_TEMPLATE(fill_n_1d, normal, dynamic_tag); // BENCHMARK_TEMPLATE(fill_n_1d, normal, dynamic_tag, DStore); +BENCHMARK_TEMPLATE(fill_n_1d_closed, uniform, static_tag); +BENCHMARK_TEMPLATE(fill_n_1d_closed_unsafe, uniform, static_tag); BENCHMARK_TEMPLATE(fill_2d, uniform, static_tag); // BENCHMARK_TEMPLATE(fill_2d, uniform, static_tag, DStore); diff --git a/include/boost/histogram/axis/category.hpp b/include/boost/histogram/axis/category.hpp index 9871e9b7..bcaececc 100644 --- a/include/boost/histogram/axis/category.hpp +++ b/include/boost/histogram/axis/category.hpp @@ -26,22 +26,21 @@ namespace boost { namespace histogram { namespace axis { -/** - Maps at a set of unique values to bin indices. - - The axis maps a set of values to bins, following the order of arguments in the - constructor. The optional overflow bin for this axis counts input values that - are not part of the set. Binning has O(N) complexity, but with a very small - factor. For small N (the typical use case) it beats other kinds of lookup. - - @tparam Value input value type, must be equal-comparable. - @tparam MetaData type to store meta data. - @tparam Options see boost::histogram::axis::option. - @tparam Allocator allocator to use for dynamic memory management. - - The options `underflow` and `circular` are not allowed. The options `growth` - and `overflow` are mutually exclusive. -*/ +/** Maps at a set of unique values to bin indices. + + The axis maps a set of values to bins, following the order of arguments in the + constructor. The optional overflow bin for this axis counts input values that + are not part of the set. Binning has O(N) complexity, but with a very small + factor. For small N (the typical use case) it beats other kinds of lookup. + + @tparam Value input value type, must be equal-comparable. + @tparam MetaData type to store meta data. + @tparam Options see boost::histogram::axis::option. + @tparam Allocator allocator to use for dynamic memory management. + + The options `underflow` and `circular` are not allowed. The options `growth` + and `overflow` are mutually exclusive. + */ template class category : public iterator_mixin>, public metadata_base_t { @@ -66,12 +65,12 @@ class category : public iterator_mixin> category(It begin, It end, metadata_type meta = {}, options_type options = {}, @@ -91,11 +90,11 @@ class category : public iterator_mixin> category(const C& iterable, metadata_type meta = {}, options_type options = {}, @@ -110,11 +109,11 @@ class category : public iterator_mixin category(std::initializer_list list, metadata_type meta = {}, diff --git a/include/boost/histogram/axis/integer.hpp b/include/boost/histogram/axis/integer.hpp index 4110875c..d61dfdd8 100644 --- a/include/boost/histogram/axis/integer.hpp +++ b/include/boost/histogram/axis/integer.hpp @@ -29,14 +29,13 @@ namespace boost { namespace histogram { namespace axis { -/** - Axis for an interval of integer values with unit steps. +/** Axis for an interval of integer values with unit steps. - Binning is a O(1) operation. This axis bins faster than a regular axis. + Binning is a O(1) operation. This axis bins faster than a regular axis. - @tparam Value input value type. Must be integer or floating point. - @tparam MetaData type to store meta data. - @tparam Options see boost::histogram::axis::option. + @tparam Value input value type. Must be integer or floating point. + @tparam MetaData type to store meta data. + @tparam Options see boost::histogram::axis::option. */ template class integer : public iterator_mixin>, @@ -72,11 +71,11 @@ class integer : public iterator_mixin>, constexpr integer() = default; /** Construct over semi-open integer interval [start, stop). - * - * @param start first integer of covered range. - * @param stop one past last integer of covered range. - * @param meta description of the axis (optional). - * @param options see boost::histogram::axis::option (optional). + + @param start first integer of covered range. + @param stop one past last integer of covered range. + @param meta description of the axis (optional). + @param options see boost::histogram::axis::option (optional). */ integer(value_type start, value_type stop, metadata_type meta = {}, options_type options = {}) diff --git a/include/boost/histogram/axis/regular.hpp b/include/boost/histogram/axis/regular.hpp index 3f7df9e0..403931f8 100644 --- a/include/boost/histogram/axis/regular.hpp +++ b/include/boost/histogram/axis/regular.hpp @@ -165,15 +165,22 @@ step_type step(T t) { return step_type{t}; } -/** - Axis for equidistant intervals on the real line. +/** Axis for equidistant intervals on the real line. + + The most common binning strategy. Very fast. Binning is a O(1) operation. - The most common binning strategy. Very fast. Binning is a O(1) operation. + If the axis has an overflow bin (the default), a value on the upper edge of the last + bin is put in the overflow bin. The axis range represents a semi-open interval. - @tparam Value input value type, must be floating point. - @tparam Transform builtin or user-defined transform type. - @tparam MetaData type to store meta data. - @tparam Options see boost::histogram::axis::option. + If the overflow bin is deactivated, then a value on the upper edge of the last bin is + still counted towards the last bin. The axis range represents a closed interval. This + is the desired behavior for random numbers drawn from a bounded interval, which is + usually closed. + + @tparam Value input value type, must be floating point. + @tparam Transform builtin or user-defined transform type. + @tparam MetaData type to store meta data. + @tparam Options see boost::histogram::axis::option. */ template class regular : public iterator_mixin>, @@ -207,13 +214,13 @@ class regular : public iterator_mixin regular(transform_type trans, step_type step, value_type start, value_type stop, @@ -267,16 +274,16 @@ class regular : public iterator_mixin regular(step_type step, value_type start, value_type stop, metadata_type meta = {}, @@ -311,6 +318,8 @@ class regular : public iterator_mixin class variable : public iterator_mixin>, public metadata_base_t { @@ -72,12 +79,12 @@ class variable : public iterator_mixin> variable(It begin, It end, metadata_type meta = {}, options_type options = {}, @@ -106,11 +113,11 @@ class variable : public iterator_mixin> variable(const U& iterable, metadata_type meta = {}, options_type options = {}, @@ -125,11 +132,11 @@ class variable : public iterator_mixin variable(std::initializer_list list, metadata_type meta = {}, @@ -159,6 +166,8 @@ class variable : public iterator_mixin(std::upper_bound(vec_.begin(), vec_.end(), x) - vec_.begin() - 1); } diff --git a/test/axis_regular_test.cpp b/test/axis_regular_test.cpp index 2fece7f2..3c3102e2 100644 --- a/test/axis_regular_test.cpp +++ b/test/axis_regular_test.cpp @@ -20,6 +20,7 @@ int main() { using namespace boost::histogram; using def = use_default; namespace tr = axis::transform; + namespace op = axis::option; BOOST_TEST(std::is_nothrow_move_assignable>::value); BOOST_TEST(std::is_nothrow_move_constructible>::value); @@ -202,7 +203,7 @@ int main() { // with growth { using pii_t = std::pair; - axis::regular a{1, 0, 1}; + axis::regular a{1, 0, 1}; BOOST_TEST_EQ(a.size(), 1); BOOST_TEST_EQ(a.update(0), pii_t(0, 0)); BOOST_TEST_EQ(a.size(), 1); @@ -223,11 +224,32 @@ int main() { BOOST_TEST_EQ(a.update(-std::numeric_limits::infinity()), pii_t(-1, 0)); } + // axis with overflow bin represents open interval + { + axis::regular a{2, 0, 1}; + BOOST_TEST_EQ(a.index(0), 0); + BOOST_TEST_EQ(a.index(0.49), 0); + BOOST_TEST_EQ(a.index(0.50), 1); + BOOST_TEST_EQ(a.index(0.99), 1); + BOOST_TEST_EQ(a.index(1), 2); // overflow bin + BOOST_TEST_EQ(a.index(1.1), 2); // overflow bin + } + + // axis without overflow bin represents a closed interval + { + axis::regular a{2, 0, 1}; + BOOST_TEST_EQ(a.index(0), 0); + BOOST_TEST_EQ(a.index(0.49), 0); + BOOST_TEST_EQ(a.index(0.50), 1); + BOOST_TEST_EQ(a.index(0.99), 1); + BOOST_TEST_EQ(a.index(1), 1); // last ordinary bin + BOOST_TEST_EQ(a.index(1.1), 2); // out of range + } + // iterators { test_axis_iterator(axis::regular<>(5, 0, 1), 0, 5); - test_axis_iterator(axis::regular(5, 0, 1), 0, - 5); + test_axis_iterator(axis::regular(5, 0, 1), 0, 5); test_axis_iterator(axis::circular<>(5, 0, 1), 0, 5); } diff --git a/test/axis_variable_test.cpp b/test/axis_variable_test.cpp index b4c51ba1..5b205c71 100644 --- a/test/axis_variable_test.cpp +++ b/test/axis_variable_test.cpp @@ -18,6 +18,7 @@ #include "utility_str.hpp" using namespace boost::histogram; +namespace op = boost::histogram::axis::option; int main() { constexpr auto inf = std::numeric_limits::infinity(); @@ -104,7 +105,7 @@ int main() { // axis::variable circular { - axis::variable a{-1, 1, 2}; + axis::variable a{-1, 1, 2}; BOOST_TEST_EQ(a.value(-2), -4); BOOST_TEST_EQ(a.value(-1), -2); BOOST_TEST_EQ(a.value(0), -1); @@ -125,7 +126,7 @@ int main() { // axis::regular with growth { using pii_t = std::pair; - axis::variable a{0, 1}; + axis::variable a{0, 1}; BOOST_TEST_EQ(a.size(), 1); BOOST_TEST_EQ(a.update(0), pii_t(0, 0)); BOOST_TEST_EQ(a.size(), 1); @@ -149,11 +150,33 @@ int main() { BOOST_TEST_EQ(a.update(nan), pii_t(a.size(), 0)); } + // axis with overflow bin represents open interval + { + axis::variable a{0.0, 0.5, 1.0}; + BOOST_TEST_EQ(a.index(0), 0); + BOOST_TEST_EQ(a.index(0.49), 0); + BOOST_TEST_EQ(a.index(0.50), 1); + BOOST_TEST_EQ(a.index(0.99), 1); + BOOST_TEST_EQ(a.index(1), 2); // overflow bin + BOOST_TEST_EQ(a.index(1.1), 2); // overflow bin + } + + // axis without overflow bin represents a closed interval + { + axis::variable a{0.0, 0.5, 1.0}; + BOOST_TEST_EQ(a.index(0), 0); + BOOST_TEST_EQ(a.index(0.49), 0); + BOOST_TEST_EQ(a.index(0.50), 1); + BOOST_TEST_EQ(a.index(0.99), 1); + BOOST_TEST_EQ(a.index(1), 1); // last ordinary bin + BOOST_TEST_EQ(a.index(1.1), 2); // out of range + } + // iterators { test_axis_iterator(axis::variable<>{1, 2, 3}, 0, 2); - test_axis_iterator( - axis::variable{1, 2, 3}, 0, 2); + test_axis_iterator(axis::variable{1, 2, 3}, + 0, 2); } // shrink and rebin @@ -176,7 +199,7 @@ int main() { // shrink and rebin with circular option { - using A = axis::variable; + using A = axis::variable; auto a = A({1, 2, 3, 4, 5}); BOOST_TEST_THROWS(A(a, 1, 4, 1), std::invalid_argument); BOOST_TEST_THROWS(A(a, 0, 3, 1), std::invalid_argument);